From 84477867e122251b8c47db240a8d699b0836aa6f Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Thu, 30 Apr 2026 11:02:54 +0200 Subject: [PATCH 0001/1328] tty: xtensa/iss: use strnlen to improve iss_console_write Use strnlen() to limit scanning 's' to 'count' bytes. Use the length of 's' to decide if simc_write() should be called instead of dereferencing it first and then calling strlen(). With strnlen(), iss_console_write() is further hardened against callers where 's' is not NUL-terminated. Signed-off-by: Thorsten Blum Message-ID: <20260430090253.237654-3-thorsten.blum@linux.dev> Signed-off-by: Max Filippov --- arch/xtensa/platforms/iss/console.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/arch/xtensa/platforms/iss/console.c b/arch/xtensa/platforms/iss/console.c index 8b95221375a8..8e54625cb2ba 100644 --- a/arch/xtensa/platforms/iss/console.c +++ b/arch/xtensa/platforms/iss/console.c @@ -166,8 +166,9 @@ late_initcall(rs_init); static void iss_console_write(struct console *co, const char *s, unsigned count) { - if (s && *s != 0) - simc_write(1, s, min(count, strlen(s))); + count = s ? strnlen(s, count) : 0; + if (count) + simc_write(1, s, count); } static struct tty_driver* iss_console_device(struct console *c, int *index) From d5bbc9b3d20c3f90b31a64c62bb4a800b9a5941c Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 5 May 2026 09:22:29 +0100 Subject: [PATCH 0002/1328] ARM: 9473/1: kprobes: test: add MODULE_DESCRIPTION All loadable modules must have a description, and since commit 6c6c1fc09de3 ("modpost: require a MODULE_DESCRIPTION()"), the kernel build warns about it missing: WARNING: modpost: missing MODULE_DESCRIPTION() in arch/arm/probes/kprobes/test-kprobes.o Add the missing description tags. Noticing that the existing license tag is unnecessarily hidden in an #ifdef section, remove the #ifdef and use conventional placing of the initcall and module_exit tags as well as the license and description. Tested-by: Randy Dunlap # build-tested Reviewed-by: Randy Dunlap Signed-off-by: Arnd Bergmann Signed-off-by: Russell King --- arch/arm/probes/kprobes/test-core.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/arch/arm/probes/kprobes/test-core.c b/arch/arm/probes/kprobes/test-core.c index 7a2baa135f0f..2de28088dac8 100644 --- a/arch/arm/probes/kprobes/test-core.c +++ b/arch/arm/probes/kprobes/test-core.c @@ -1649,24 +1649,16 @@ static int __init run_all_tests(void) return ret; } - +late_initcall(run_all_tests); /* * Module setup */ -#ifdef MODULE - static void __exit kprobe_test_exit(void) { } - -module_init(run_all_tests) module_exit(kprobe_test_exit) + +MODULE_DESCRIPTION("Test code for ARM kprobes"); MODULE_LICENSE("GPL"); - -#else /* !MODULE */ - -late_initcall(run_all_tests); - -#endif From a7c958e8721eb2724ee2e2ef13129bc67e1c8a75 Mon Sep 17 00:00:00 2001 From: Ethan Nelson-Moore Date: Mon, 15 Jun 2026 15:49:54 -0700 Subject: [PATCH 0003/1328] xtensa: correct CONFIG_XTENSA_CALIBRATE_CCOUNT macro name in comment A comment in arch/xtensa/include/asm/platform.h incorrectly refers to CONFIG_XTENSA_CALIBRATE instead of CONFIG_XTENSA_CALIBRATE_CCOUNT. Correct it. Discovered while searching for CONFIG_* symbols referenced in code but not defined in any Kconfig file. Signed-off-by: Ethan Nelson-Moore Message-ID: <20260615224957.21574-1-enelsonmoore@gmail.com> Signed-off-by: Max Filippov --- arch/xtensa/include/asm/platform.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/xtensa/include/asm/platform.h b/arch/xtensa/include/asm/platform.h index 94f13fabf7cd..f2e50fc12b4d 100644 --- a/arch/xtensa/include/asm/platform.h +++ b/arch/xtensa/include/asm/platform.h @@ -33,7 +33,7 @@ extern void platform_setup (char **); extern void platform_idle (void); /* - * platform_calibrate_ccount calibrates cpu clock freq (CONFIG_XTENSA_CALIBRATE) + * platform_calibrate_ccount calibrates cpu clock freq (CONFIG_XTENSA_CALIBRATE_CCOUNT) */ extern void platform_calibrate_ccount (void); From c4a16f90797e2d8bebf875fd02547fef76ae3b76 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Mon, 15 Jun 2026 11:48:43 +0100 Subject: [PATCH 0004/1328] dt-bindings: clock: renesas,r9a09g077/87: Add LCDC_CLKD clock ID Add the LCDC clockd (LCDC_CLKD) definition for the Renesas RZ/T2H (R9A09G077) and RZ/N2H (R9A09G087) SoCs. LCDC_CLKD is used as the operating clock for LCDC. Signed-off-by: Lad Prabhakar Acked-by: Conor Dooley Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260615104845.4122868-4-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Geert Uytterhoeven --- include/dt-bindings/clock/renesas,r9a09g077-cpg-mssr.h | 1 + include/dt-bindings/clock/renesas,r9a09g087-cpg-mssr.h | 1 + 2 files changed, 2 insertions(+) diff --git a/include/dt-bindings/clock/renesas,r9a09g077-cpg-mssr.h b/include/dt-bindings/clock/renesas,r9a09g077-cpg-mssr.h index c4863e444458..f6cb8d649a46 100644 --- a/include/dt-bindings/clock/renesas,r9a09g077-cpg-mssr.h +++ b/include/dt-bindings/clock/renesas,r9a09g077-cpg-mssr.h @@ -34,5 +34,6 @@ #define R9A09G077_XSPI_CLK0 22 #define R9A09G077_XSPI_CLK1 23 #define R9A09G077_PCLKCAN 24 +#define R9A09G077_LCDC_CLKD 25 #endif /* __DT_BINDINGS_CLOCK_RENESAS_R9A09G077_CPG_H__ */ diff --git a/include/dt-bindings/clock/renesas,r9a09g087-cpg-mssr.h b/include/dt-bindings/clock/renesas,r9a09g087-cpg-mssr.h index 0d53f1e65077..312e563b322e 100644 --- a/include/dt-bindings/clock/renesas,r9a09g087-cpg-mssr.h +++ b/include/dt-bindings/clock/renesas,r9a09g087-cpg-mssr.h @@ -34,5 +34,6 @@ #define R9A09G087_XSPI_CLK0 22 #define R9A09G087_XSPI_CLK1 23 #define R9A09G087_PCLKCAN 24 +#define R9A09G087_LCDC_CLKD 25 #endif /* __DT_BINDINGS_CLOCK_RENESAS_R9A09G087_CPG_H__ */ From 2b005b458f6eeffdbe7705e6667437013b54e209 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Mon, 15 Jun 2026 15:39:42 +0100 Subject: [PATCH 0005/1328] dt-bindings: clock: renesas,r9a09g077/87: Add PCLKRTC clock ID Add the peripheral clock ID definition for the Real-Time Clock (PCLKRTC) on the Renesas RZ/T2H (R9A09G077) and RZ/N2H (R9A09G087) SoCs. Note that the PCLKRTC clock is utilized as the operating clock source for the RTC IP. Signed-off-by: Lad Prabhakar Acked-by: Conor Dooley Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260615143943.1610095-2-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Geert Uytterhoeven --- include/dt-bindings/clock/renesas,r9a09g077-cpg-mssr.h | 1 + include/dt-bindings/clock/renesas,r9a09g087-cpg-mssr.h | 1 + 2 files changed, 2 insertions(+) diff --git a/include/dt-bindings/clock/renesas,r9a09g077-cpg-mssr.h b/include/dt-bindings/clock/renesas,r9a09g077-cpg-mssr.h index f6cb8d649a46..aa47685f329a 100644 --- a/include/dt-bindings/clock/renesas,r9a09g077-cpg-mssr.h +++ b/include/dt-bindings/clock/renesas,r9a09g077-cpg-mssr.h @@ -35,5 +35,6 @@ #define R9A09G077_XSPI_CLK1 23 #define R9A09G077_PCLKCAN 24 #define R9A09G077_LCDC_CLKD 25 +#define R9A09G077_PCLKRTC 26 #endif /* __DT_BINDINGS_CLOCK_RENESAS_R9A09G077_CPG_H__ */ diff --git a/include/dt-bindings/clock/renesas,r9a09g087-cpg-mssr.h b/include/dt-bindings/clock/renesas,r9a09g087-cpg-mssr.h index 312e563b322e..1c73d0dcef18 100644 --- a/include/dt-bindings/clock/renesas,r9a09g087-cpg-mssr.h +++ b/include/dt-bindings/clock/renesas,r9a09g087-cpg-mssr.h @@ -35,5 +35,6 @@ #define R9A09G087_XSPI_CLK1 23 #define R9A09G087_PCLKCAN 24 #define R9A09G087_LCDC_CLKD 25 +#define R9A09G087_PCLKRTC 26 #endif /* __DT_BINDINGS_CLOCK_RENESAS_R9A09G087_CPG_H__ */ From 628b6fee9fca292f12d07f0f1bcf1edefa949d81 Mon Sep 17 00:00:00 2001 From: Martin Blumenstingl Date: Tue, 23 Jun 2026 22:19:56 +0200 Subject: [PATCH 0006/1328] clk: meson: align gxbb_32k_clk_sel number of parents with actual count The following out-of-bounds read has been observed by Christian on a GXBB WeTek Hub: ================================================================== BUG: KASAN: global-out-of-bounds in __clk_register+0x1b70/0x2418 Read of size 8 at addr ffffd66320cf88e0 by task swapper/0/1 CPU: 0 UID: 0 PID: 1 Comm: swapper/0 Not tainted 7.0.0-rc5 #1 PREEMPT Hardware name: WeTek Hub (DT) Call trace: show_stack+0x14/0x20 (C) dump_stack_lvl+0x74/0x94 print_report+0x164/0x4b0 kasan_report+0x98/0xd8 __asan_report_load8_noabort+0x1c/0x24 __clk_register+0x1b70/0x2418 devm_clk_hw_register+0x74/0x15c meson_clkc_init+0xd4/0x20c meson_clkc_syscon_probe+0x5c/0x94 platform_probe+0xbc/0x17c really_probe+0x184/0x844 __driver_probe_device+0x154/0x35c driver_probe_device+0x60/0x188 __driver_attach+0x168/0x4a0 bus_for_each_dev+0xec/0x180 driver_attach+0x38/0x58 bus_add_driver+0x238/0x4c0 driver_register+0x150/0x388 __platform_driver_register+0x54/0x7c gxbb_clkc_driver_init+0x18/0x20 do_one_initcall+0xb8/0x340 kernel_init_freeable+0x49c/0x52c kernel_init+0x24/0x148 ret_from_fork+0x10/0x20 The buggy address belongs to the variable: gxbb_32k_clk_parents+0x60/0x400 The buggy address belongs to a vmalloc virtual mapping The buggy address belongs to the physical page: Memory state around the buggy address: ffffd66320cf8780: 00 00 00 00 f9 f9 f9 f9 00 f9 f9 f9 f9 f9 f9 f9 ffffd66320cf8800: 00 04 f9 f9 f9 f9 f9 f9 00 04 f9 f9 f9 f9 f9 f9 >ffffd66320cf8880: 00 00 00 00 00 00 00 00 00 00 00 00 f9 f9 f9 f9 ^ ffffd66320cf8900: 00 01 f9 f9 f9 f9 f9 f9 00 06 f9 f9 f9 f9 f9 f9 ffffd66320cf8980: 00 00 02 f9 f9 f9 f9 f9 00 00 02 f9 f9 f9 f9 f9 ================================================================== Commit 7915d7d5407c ("clk: amlogic: gxbb: drop non existing 32k clock parent") dropped a non-existing clock parent from the gxbb_32k_clk_sel mux but didn't adjust the hard-coded num_parents field. Fix the actual number of parents of that mux by using ARRAY_SIZE instead (avoiding similar problems in future). Fixes: 7915d7d5407c ("clk: amlogic: gxbb: drop non existing 32k clock parent") Reported-by: Christian Hewitt Cc: stable@vger.kernel.org Tested-by: Christian Hewitt Signed-off-by: Martin Blumenstingl Link: https://patch.msgid.link/20260623201956.1324992-1-martin.blumenstingl@googlemail.com Signed-off-by: Jerome Brunet --- drivers/clk/meson/gxbb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/meson/gxbb.c b/drivers/clk/meson/gxbb.c index f9131d014ef4..d432e08d1777 100644 --- a/drivers/clk/meson/gxbb.c +++ b/drivers/clk/meson/gxbb.c @@ -1394,7 +1394,7 @@ static struct clk_regmap gxbb_32k_clk_sel = { .name = "32k_clk_sel", .ops = &clk_regmap_mux_ops, .parent_data = gxbb_32k_clk_parents, - .num_parents = 4, + .num_parents = ARRAY_SIZE(gxbb_32k_clk_parents), .flags = CLK_SET_RATE_PARENT, }, }; From d425596035b34f0119a688e38d0e65bf43fa73c6 Mon Sep 17 00:00:00 2001 From: Jian Hu Date: Tue, 23 Jun 2026 10:55:33 +0800 Subject: [PATCH 0007/1328] dt-bindings: clock: Add Amlogic A9 AO clock controller Add the Always-On clock controller dt-bindings for the Amlogic A9 SoC family. Acked-by: Conor Dooley Signed-off-by: Jian Hu Link: https://patch.msgid.link/20260623-a9_aoclk-v5-1-c7cb1ff9ebf1@amlogic.com Signed-off-by: Jerome Brunet --- .../bindings/clock/amlogic,a9-aoclkc.yaml | 76 +++++++++++++++++++ include/dt-bindings/clock/amlogic,a9-aoclkc.h | 76 +++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 Documentation/devicetree/bindings/clock/amlogic,a9-aoclkc.yaml create mode 100644 include/dt-bindings/clock/amlogic,a9-aoclkc.h diff --git a/Documentation/devicetree/bindings/clock/amlogic,a9-aoclkc.yaml b/Documentation/devicetree/bindings/clock/amlogic,a9-aoclkc.yaml new file mode 100644 index 000000000000..1fa9b3a32fbb --- /dev/null +++ b/Documentation/devicetree/bindings/clock/amlogic,a9-aoclkc.yaml @@ -0,0 +1,76 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright (C) 2026 Amlogic, Inc. All rights reserved +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/clock/amlogic,a9-aoclkc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Amlogic A9 Series Always-On Clock Controller + +maintainers: + - Neil Armstrong + - Jerome Brunet + - Jian Hu + - Xianwei Zhao + +properties: + compatible: + const: amlogic,a9-aoclkc + + reg: + maxItems: 1 + + '#clock-cells': + const: 1 + + clocks: + minItems: 5 + items: + - description: input oscillator + - description: input fclk div 3 + - description: input fclk div 4 + - description: input fclk div 5 + - description: input sys clk + - description: external fixed 32k (optional) + + clock-names: + minItems: 5 + items: + - const: xtal + - const: fdiv3 + - const: fdiv4 + - const: fdiv5 + - const: sys + - const: ext_32k + +required: + - compatible + - reg + - '#clock-cells' + - clocks + - clock-names + +additionalProperties: false + +examples: + - | + soc { + #address-cells = <2>; + #size-cells = <2>; + + clock-controller@0 { + compatible = "amlogic,a9-aoclkc"; + reg = <0x0 0x0 0x0 0x58>; + #clock-cells = <1>; + clocks = <&xtal>, + <&scmi_clk 14>, + <&scmi_clk 16>, + <&scmi_clk 18>, + <&scmi_clk 21>; + clock-names = "xtal", + "fdiv3", + "fdiv4", + "fdiv5", + "sys"; + }; + }; diff --git a/include/dt-bindings/clock/amlogic,a9-aoclkc.h b/include/dt-bindings/clock/amlogic,a9-aoclkc.h new file mode 100644 index 000000000000..a7d704d4b58e --- /dev/null +++ b/include/dt-bindings/clock/amlogic,a9-aoclkc.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ +/* + * Copyright (C) 2026 Amlogic, Inc. All rights reserved. + */ + +#ifndef __AMLOGIC_A9_AO_CLKC_H +#define __AMLOGIC_A9_AO_CLKC_H + +#define CLKID_AO_XTAL_IN 0 +#define CLKID_AO_XTAL 1 +#define CLKID_AO_SYS 2 +#define CLKID_AO_SYS_I3C 3 +#define CLKID_AO_SYS_RTC_REG 4 +#define CLKID_AO_SYS_CLKTREE 5 +#define CLKID_AO_SYS_RST_CTRL 6 +#define CLKID_AO_SYS_PAD 7 +#define CLKID_AO_SYS_RTC_DIG 8 +#define CLKID_AO_SYS_IRQ 9 +#define CLKID_AO_SYS_PWRCTRL 10 +#define CLKID_AO_SYS_PWM_A 11 +#define CLKID_AO_SYS_PWM_B 12 +#define CLKID_AO_SYS_PWM_C 13 +#define CLKID_AO_SYS_PWM_D 14 +#define CLKID_AO_SYS_PWM_E 15 +#define CLKID_AO_SYS_PWM_F 16 +#define CLKID_AO_SYS_PWM_G 17 +#define CLKID_AO_SYS_I2C_A 18 +#define CLKID_AO_SYS_I2C_B 19 +#define CLKID_AO_SYS_I2C_C 20 +#define CLKID_AO_SYS_I2C_D 21 +#define CLKID_AO_SYS_SED 22 +#define CLKID_AO_SYS_IR_CTRL 23 +#define CLKID_AO_SYS_UART_B 24 +#define CLKID_AO_SYS_UART_C 25 +#define CLKID_AO_SYS_UART_D 26 +#define CLKID_AO_SYS_UART_E 27 +#define CLKID_AO_SYS_SPISG_0 28 +#define CLKID_AO_SYS_RTC_SECURE 29 +#define CLKID_AO_SYS_CEC 30 +#define CLKID_AO_SYS_AOCPU 31 +#define CLKID_AO_SYS_SRAM 32 +#define CLKID_AO_SYS_SPISG_1 33 +#define CLKID_AO_SYS_SPISG_2 34 +#define CLKID_AO_PWM_A_SEL 35 +#define CLKID_AO_PWM_A_DIV 36 +#define CLKID_AO_PWM_A 37 +#define CLKID_AO_PWM_B_SEL 38 +#define CLKID_AO_PWM_B_DIV 39 +#define CLKID_AO_PWM_B 40 +#define CLKID_AO_PWM_C_SEL 41 +#define CLKID_AO_PWM_C_DIV 42 +#define CLKID_AO_PWM_C 43 +#define CLKID_AO_PWM_D_SEL 44 +#define CLKID_AO_PWM_D_DIV 45 +#define CLKID_AO_PWM_D 46 +#define CLKID_AO_PWM_E_SEL 47 +#define CLKID_AO_PWM_E_DIV 48 +#define CLKID_AO_PWM_E 49 +#define CLKID_AO_PWM_F_SEL 50 +#define CLKID_AO_PWM_F_DIV 51 +#define CLKID_AO_PWM_F 52 +#define CLKID_AO_PWM_G_SEL 53 +#define CLKID_AO_PWM_G_DIV 54 +#define CLKID_AO_PWM_G 55 +#define CLKID_AO_RTC_DUALDIV_IN 56 +#define CLKID_AO_RTC_DUALDIV_DIV 57 +#define CLKID_AO_RTC_DUALDIV_SEL 58 +#define CLKID_AO_RTC_DUALDIV 59 +#define CLKID_AO_RTC 60 +#define CLKID_AO_CEC_DUALDIV_IN 61 +#define CLKID_AO_CEC_DUALDIV_DIV 62 +#define CLKID_AO_CEC_DUALDIV_SEL 63 +#define CLKID_AO_CEC_DUALDIV 64 +#define CLKID_AO_CEC 65 + +#endif /* __AMLOGIC_A9_AO_CLKC_H */ From a1b4c3a63a7e56b98b2eccc3a82b9f31d51c2e35 Mon Sep 17 00:00:00 2001 From: Jian Hu Date: Tue, 23 Jun 2026 10:55:34 +0800 Subject: [PATCH 0008/1328] clk: amlogic: Add A9 AO clock controller driver Add the Always-on clock controller driver for the Amlogic A9 SoC family. Signed-off-by: Jian Hu Link: https://patch.msgid.link/20260623-a9_aoclk-v5-2-c7cb1ff9ebf1@amlogic.com Signed-off-by: Jerome Brunet --- drivers/clk/meson/Kconfig | 13 + drivers/clk/meson/Makefile | 1 + drivers/clk/meson/a9-aoclk.c | 489 +++++++++++++++++++++++++++++++++++ 3 files changed, 503 insertions(+) create mode 100644 drivers/clk/meson/a9-aoclk.c diff --git a/drivers/clk/meson/Kconfig b/drivers/clk/meson/Kconfig index cf8cf3f9e4ee..b71299898197 100644 --- a/drivers/clk/meson/Kconfig +++ b/drivers/clk/meson/Kconfig @@ -132,6 +132,19 @@ config COMMON_CLK_A1_PERIPHERALS device, A1 SoC Family. Say Y if you want A1 Peripherals clock controller to work. +config COMMON_CLK_A9_AO + tristate "Amlogic A9 SoC AO clock controller support" + depends on ARM64 || COMPILE_TEST + default ARCH_MESON + select COMMON_CLK_MESON_REGMAP + select COMMON_CLK_MESON_CLKC_UTILS + select COMMON_CLK_MESON_DUALDIV + imply COMMON_CLK_SCMI + help + Support for the AO clock controller on Amlogic A311Y3 based + device, AKA A9. + Say Y if you want A9 AO clock controller to work. + config COMMON_CLK_C3_PLL tristate "Amlogic C3 PLL clock controller" depends on ARM64 diff --git a/drivers/clk/meson/Makefile b/drivers/clk/meson/Makefile index c6719694a242..f89d027c282c 100644 --- a/drivers/clk/meson/Makefile +++ b/drivers/clk/meson/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_COMMON_CLK_AXG) += axg.o axg-aoclk.o obj-$(CONFIG_COMMON_CLK_AXG_AUDIO) += axg-audio.o obj-$(CONFIG_COMMON_CLK_A1_PLL) += a1-pll.o obj-$(CONFIG_COMMON_CLK_A1_PERIPHERALS) += a1-peripherals.o +obj-$(CONFIG_COMMON_CLK_A9_AO) += a9-aoclk.o obj-$(CONFIG_COMMON_CLK_C3_PLL) += c3-pll.o obj-$(CONFIG_COMMON_CLK_C3_PERIPHERALS) += c3-peripherals.o obj-$(CONFIG_COMMON_CLK_GXBB) += gxbb.o gxbb-aoclk.o diff --git a/drivers/clk/meson/a9-aoclk.c b/drivers/clk/meson/a9-aoclk.c new file mode 100644 index 000000000000..c82d4b1b5b44 --- /dev/null +++ b/drivers/clk/meson/a9-aoclk.c @@ -0,0 +1,489 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) +/* + * Copyright (C) 2026 Amlogic, Inc. All rights reserved + */ + +#include +#include +#include +#include +#include "clk-regmap.h" +#include "clk-dualdiv.h" +#include "meson-clkc-utils.h" + +#define AO_OSCIN_CTRL 0x00 +#define AO_SYS_CLK0 0x04 +#define AO_PWM_CLK_A_CTRL 0x1c +#define AO_PWM_CLK_B_CTRL 0x20 +#define AO_PWM_CLK_C_CTRL 0x24 +#define AO_PWM_CLK_D_CTRL 0x28 +#define AO_PWM_CLK_E_CTRL 0x2c +#define AO_PWM_CLK_F_CTRL 0x30 +#define AO_PWM_CLK_G_CTRL 0x34 +#define AO_CEC_CTRL0 0x38 +#define AO_CEC_CTRL1 0x3c +#define AO_RTC_BY_OSCIN_CTRL0 0x50 +#define AO_RTC_BY_OSCIN_CTRL1 0x54 + +#define A9_COMP_SEL(_name, _reg, _shift, _mask, _pdata) \ + MESON_COMP_SEL(a9_ao_, _name, _reg, _shift, _mask, _pdata, NULL, 0, 0) + +#define A9_COMP_DIV(_name, _reg, _shift, _width) \ + MESON_COMP_DIV(a9_ao_, _name, _reg, _shift, _width, 0, CLK_SET_RATE_PARENT) + +#define A9_COMP_GATE(_name, _reg, _bit) \ + MESON_COMP_GATE(a9_ao_, _name, _reg, _bit, CLK_SET_RATE_PARENT) + +static struct clk_regmap a9_ao_xtal_in = { + .data = &(struct clk_regmap_gate_data){ + .offset = AO_OSCIN_CTRL, + .bit_idx = 3, + }, + .hw.init = &(struct clk_init_data) { + .name = "ao_xtal_in", + .ops = &clk_regmap_gate_ops, + .parent_data = &(const struct clk_parent_data) { + .fw_name = "xtal", + }, + .num_parents = 1, + }, +}; + +static struct clk_regmap a9_ao_xtal = { + .data = &(struct clk_regmap_mux_data) { + .offset = AO_OSCIN_CTRL, + .mask = 0x1, + .shift = 0, + }, + .hw.init = &(struct clk_init_data){ + .name = "ao_xtal", + .ops = &clk_regmap_mux_ops, + /* ext_32k is from external PAD, do not automatically reparent */ + .parent_data = (const struct clk_parent_data []) { + { .hw = &a9_ao_xtal_in.hw }, + { .fw_name = "ext_32k", }, + }, + .num_parents = 2, + .flags = CLK_SET_RATE_NO_REPARENT, + }, +}; + +static struct clk_regmap a9_ao_sys = { + .data = &(struct clk_regmap_mux_data) { + .offset = AO_OSCIN_CTRL, + .mask = 0x1, + .shift = 1, + }, + .hw.init = &(struct clk_init_data){ + .name = "ao_sys", + .ops = &clk_regmap_mux_ops, + .parent_data = (const struct clk_parent_data []) { + { .hw = &a9_ao_xtal.hw }, + { .fw_name = "sys", }, + }, + .num_parents = 2, + }, +}; + +static const struct clk_parent_data a9_ao_pclk_parents = { .hw = &a9_ao_sys.hw }; + +#define A9_AO_PCLK(_name, _bit, _flags) \ + MESON_PCLK(a9_ao_sys_##_name, AO_SYS_CLK0, _bit, \ + &a9_ao_pclk_parents, _flags) + +/* + * A9 integrates a low-power microprocessor (Always-on CPU: AOCPU). Some AO sys + * clocks control the AOCPU modules. Mark the AOCPU-related clocks with + * CLK_IS_CRITICAL to avoid them being disabled and impacting AOCPU functionality. + * AOCPU-related clocks list: + * - clktree + * - rst_ctrl + * - pad + * - irq + * - pwrctrl + * - aocpu + * - sram + */ +static A9_AO_PCLK(i3c, 0, 0); +static A9_AO_PCLK(rtc_reg, 1, 0); +static A9_AO_PCLK(clktree, 2, CLK_IS_CRITICAL); +static A9_AO_PCLK(rst_ctrl, 3, CLK_IS_CRITICAL); +static A9_AO_PCLK(pad, 4, CLK_IS_CRITICAL); +static A9_AO_PCLK(rtc_dig, 5, 0); +static A9_AO_PCLK(irq, 6, CLK_IS_CRITICAL); +static A9_AO_PCLK(pwrctrl, 7, CLK_IS_CRITICAL); +static A9_AO_PCLK(pwm_a, 8, 0); +static A9_AO_PCLK(pwm_b, 9, 0); +static A9_AO_PCLK(pwm_c, 10, 0); +static A9_AO_PCLK(pwm_d, 11, 0); +static A9_AO_PCLK(pwm_e, 12, 0); +static A9_AO_PCLK(pwm_f, 13, 0); +static A9_AO_PCLK(pwm_g, 14, 0); +static A9_AO_PCLK(i2c_a, 15, 0); +static A9_AO_PCLK(i2c_b, 16, 0); +static A9_AO_PCLK(i2c_c, 17, 0); +static A9_AO_PCLK(i2c_d, 18, 0); +static A9_AO_PCLK(sed, 19, 0); +static A9_AO_PCLK(ir_ctrl, 20, 0); +static A9_AO_PCLK(uart_b, 21, 0); +static A9_AO_PCLK(uart_c, 22, 0); +static A9_AO_PCLK(uart_d, 23, 0); +static A9_AO_PCLK(uart_e, 24, 0); +static A9_AO_PCLK(spisg_0, 25, 0); +static A9_AO_PCLK(rtc_secure, 26, 0); +static A9_AO_PCLK(cec, 27, 0); +static A9_AO_PCLK(aocpu, 28, CLK_IS_CRITICAL); +static A9_AO_PCLK(sram, 29, CLK_IS_CRITICAL); +static A9_AO_PCLK(spisg_1, 30, 0); +static A9_AO_PCLK(spisg_2, 31, 0); + +static const struct clk_parent_data a9_ao_pwm_parents[] = { + { .hw = &a9_ao_xtal.hw }, + { .fw_name = "fdiv5", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv3", } +}; + +static A9_COMP_SEL(pwm_a, AO_PWM_CLK_A_CTRL, 9, 0x7, a9_ao_pwm_parents); +static A9_COMP_DIV(pwm_a, AO_PWM_CLK_A_CTRL, 0, 8); +static A9_COMP_GATE(pwm_a, AO_PWM_CLK_A_CTRL, 8); + +static A9_COMP_SEL(pwm_b, AO_PWM_CLK_B_CTRL, 9, 0x7, a9_ao_pwm_parents); +static A9_COMP_DIV(pwm_b, AO_PWM_CLK_B_CTRL, 0, 8); +static A9_COMP_GATE(pwm_b, AO_PWM_CLK_B_CTRL, 8); + +static A9_COMP_SEL(pwm_c, AO_PWM_CLK_C_CTRL, 9, 0x7, a9_ao_pwm_parents); +static A9_COMP_DIV(pwm_c, AO_PWM_CLK_C_CTRL, 0, 8); +static A9_COMP_GATE(pwm_c, AO_PWM_CLK_C_CTRL, 8); + +static A9_COMP_SEL(pwm_d, AO_PWM_CLK_D_CTRL, 9, 0x7, a9_ao_pwm_parents); +static A9_COMP_DIV(pwm_d, AO_PWM_CLK_D_CTRL, 0, 8); +static A9_COMP_GATE(pwm_d, AO_PWM_CLK_D_CTRL, 8); + +static A9_COMP_SEL(pwm_e, AO_PWM_CLK_E_CTRL, 9, 0x7, a9_ao_pwm_parents); +static A9_COMP_DIV(pwm_e, AO_PWM_CLK_E_CTRL, 0, 8); +static A9_COMP_GATE(pwm_e, AO_PWM_CLK_E_CTRL, 8); + +static A9_COMP_SEL(pwm_f, AO_PWM_CLK_F_CTRL, 9, 0x7, a9_ao_pwm_parents); +static A9_COMP_DIV(pwm_f, AO_PWM_CLK_F_CTRL, 0, 8); +static A9_COMP_GATE(pwm_f, AO_PWM_CLK_F_CTRL, 8); + +static A9_COMP_SEL(pwm_g, AO_PWM_CLK_G_CTRL, 9, 0x7, a9_ao_pwm_parents); +static A9_COMP_DIV(pwm_g, AO_PWM_CLK_G_CTRL, 0, 8); +static A9_COMP_GATE(pwm_g, AO_PWM_CLK_G_CTRL, 8); + +static struct clk_regmap a9_ao_rtc_dualdiv_in = { + .data = &(struct clk_regmap_gate_data){ + .offset = AO_RTC_BY_OSCIN_CTRL0, + .bit_idx = 31, + }, + .hw.init = &(struct clk_init_data) { + .name = "ao_rtc_dualdiv_in", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_ao_xtal.hw + }, + .num_parents = 1, + }, +}; + +static const struct meson_clk_dualdiv_param a9_ao_dualdiv_table[] = { + { 733, 732, 8, 11, 1 }, + { /* sentinel */ } +}; + +static struct clk_regmap a9_ao_rtc_dualdiv_div = { + .data = &(struct meson_clk_dualdiv_data){ + .n1 = { + .reg_off = AO_RTC_BY_OSCIN_CTRL0, + .shift = 0, + .width = 12, + }, + .n2 = { + .reg_off = AO_RTC_BY_OSCIN_CTRL0, + .shift = 12, + .width = 12, + }, + .m1 = { + .reg_off = AO_RTC_BY_OSCIN_CTRL1, + .shift = 0, + .width = 12, + }, + .m2 = { + .reg_off = AO_RTC_BY_OSCIN_CTRL1, + .shift = 12, + .width = 12, + }, + .dual = { + .reg_off = AO_RTC_BY_OSCIN_CTRL0, + .shift = 28, + .width = 1, + }, + .table = a9_ao_dualdiv_table, + }, + .hw.init = &(struct clk_init_data){ + .name = "a9_ao_rtc_dualdiv_div", + .ops = &meson_clk_dualdiv_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_ao_rtc_dualdiv_in.hw + }, + .num_parents = 1, + }, +}; + +static struct clk_regmap a9_ao_rtc_dualdiv_sel = { + .data = &(struct clk_regmap_mux_data) { + .offset = AO_RTC_BY_OSCIN_CTRL1, + .mask = 0x1, + .shift = 24, + }, + .hw.init = &(struct clk_init_data){ + .name = "ao_rtc_dualdiv_sel", + .ops = &clk_regmap_mux_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_ao_rtc_dualdiv_div.hw, + &a9_ao_rtc_dualdiv_in.hw, + }, + .num_parents = 2, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_ao_rtc_dualdiv = { + .data = &(struct clk_regmap_gate_data){ + .offset = AO_RTC_BY_OSCIN_CTRL0, + .bit_idx = 30, + }, + .hw.init = &(struct clk_init_data) { + .name = "ao_rtc_dualdiv", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_ao_rtc_dualdiv_sel.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_ao_rtc = { + .data = &(struct clk_regmap_mux_data) { + .offset = AO_RTC_BY_OSCIN_CTRL1, + .mask = 0x1, + .shift = 30, + }, + .hw.init = &(struct clk_init_data){ + .name = "ao_rtc", + .ops = &clk_regmap_mux_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_ao_xtal.hw, + &a9_ao_rtc_dualdiv.hw, + }, + .num_parents = 2, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_ao_cec_dualdiv_in = { + .data = &(struct clk_regmap_gate_data){ + .offset = AO_CEC_CTRL0, + .bit_idx = 31, + }, + .hw.init = &(struct clk_init_data) { + .name = "ao_cec_dualdiv_in", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_ao_xtal.hw + }, + .num_parents = 1, + }, +}; + +static struct clk_regmap a9_ao_cec_dualdiv_div = { + .data = &(struct meson_clk_dualdiv_data){ + .n1 = { + .reg_off = AO_CEC_CTRL0, + .shift = 0, + .width = 12, + }, + .n2 = { + .reg_off = AO_CEC_CTRL0, + .shift = 12, + .width = 12, + }, + .m1 = { + .reg_off = AO_CEC_CTRL1, + .shift = 0, + .width = 12, + }, + .m2 = { + .reg_off = AO_CEC_CTRL1, + .shift = 12, + .width = 12, + }, + .dual = { + .reg_off = AO_CEC_CTRL0, + .shift = 28, + .width = 1, + }, + .table = a9_ao_dualdiv_table, + }, + .hw.init = &(struct clk_init_data){ + .name = "ao_cec_dualdiv_div", + .ops = &meson_clk_dualdiv_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_ao_cec_dualdiv_in.hw + }, + .num_parents = 1, + }, +}; + +static struct clk_regmap a9_ao_cec_dualdiv_sel = { + .data = &(struct clk_regmap_mux_data) { + .offset = AO_CEC_CTRL1, + .mask = 0x1, + .shift = 24, + }, + .hw.init = &(struct clk_init_data){ + .name = "ao_cec_dualdiv_sel", + .ops = &clk_regmap_mux_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_ao_cec_dualdiv_div.hw, + &a9_ao_cec_dualdiv_in.hw, + }, + .num_parents = 2, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_ao_cec_dualdiv = { + .data = &(struct clk_regmap_gate_data){ + .offset = AO_CEC_CTRL0, + .bit_idx = 30, + }, + .hw.init = &(struct clk_init_data){ + .name = "ao_cec_dualdiv", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_ao_cec_dualdiv_sel.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_ao_cec = { + .data = &(struct clk_regmap_mux_data) { + .offset = AO_CEC_CTRL1, + .mask = 0x1, + .shift = 30, + }, + .hw.init = &(struct clk_init_data){ + .name = "ao_cec", + .ops = &clk_regmap_mux_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_ao_cec_dualdiv.hw, + &a9_ao_rtc.hw, + }, + .num_parents = 2, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_hw *a9_ao_hw_clks[] = { + [CLKID_AO_XTAL_IN] = &a9_ao_xtal_in.hw, + [CLKID_AO_XTAL] = &a9_ao_xtal.hw, + [CLKID_AO_SYS] = &a9_ao_sys.hw, + [CLKID_AO_SYS_I3C] = &a9_ao_sys_i3c.hw, + [CLKID_AO_SYS_RTC_REG] = &a9_ao_sys_rtc_reg.hw, + [CLKID_AO_SYS_CLKTREE] = &a9_ao_sys_clktree.hw, + [CLKID_AO_SYS_RST_CTRL] = &a9_ao_sys_rst_ctrl.hw, + [CLKID_AO_SYS_PAD] = &a9_ao_sys_pad.hw, + [CLKID_AO_SYS_RTC_DIG] = &a9_ao_sys_rtc_dig.hw, + [CLKID_AO_SYS_IRQ] = &a9_ao_sys_irq.hw, + [CLKID_AO_SYS_PWRCTRL] = &a9_ao_sys_pwrctrl.hw, + [CLKID_AO_SYS_PWM_A] = &a9_ao_sys_pwm_a.hw, + [CLKID_AO_SYS_PWM_B] = &a9_ao_sys_pwm_b.hw, + [CLKID_AO_SYS_PWM_C] = &a9_ao_sys_pwm_c.hw, + [CLKID_AO_SYS_PWM_D] = &a9_ao_sys_pwm_d.hw, + [CLKID_AO_SYS_PWM_E] = &a9_ao_sys_pwm_e.hw, + [CLKID_AO_SYS_PWM_F] = &a9_ao_sys_pwm_f.hw, + [CLKID_AO_SYS_PWM_G] = &a9_ao_sys_pwm_g.hw, + [CLKID_AO_SYS_I2C_A] = &a9_ao_sys_i2c_a.hw, + [CLKID_AO_SYS_I2C_B] = &a9_ao_sys_i2c_b.hw, + [CLKID_AO_SYS_I2C_C] = &a9_ao_sys_i2c_c.hw, + [CLKID_AO_SYS_I2C_D] = &a9_ao_sys_i2c_d.hw, + [CLKID_AO_SYS_SED] = &a9_ao_sys_sed.hw, + [CLKID_AO_SYS_IR_CTRL] = &a9_ao_sys_ir_ctrl.hw, + [CLKID_AO_SYS_UART_B] = &a9_ao_sys_uart_b.hw, + [CLKID_AO_SYS_UART_C] = &a9_ao_sys_uart_c.hw, + [CLKID_AO_SYS_UART_D] = &a9_ao_sys_uart_d.hw, + [CLKID_AO_SYS_UART_E] = &a9_ao_sys_uart_e.hw, + [CLKID_AO_SYS_SPISG_0] = &a9_ao_sys_spisg_0.hw, + [CLKID_AO_SYS_RTC_SECURE] = &a9_ao_sys_rtc_secure.hw, + [CLKID_AO_SYS_CEC] = &a9_ao_sys_cec.hw, + [CLKID_AO_SYS_AOCPU] = &a9_ao_sys_aocpu.hw, + [CLKID_AO_SYS_SRAM] = &a9_ao_sys_sram.hw, + [CLKID_AO_SYS_SPISG_1] = &a9_ao_sys_spisg_1.hw, + [CLKID_AO_SYS_SPISG_2] = &a9_ao_sys_spisg_2.hw, + [CLKID_AO_PWM_A_SEL] = &a9_ao_pwm_a_sel.hw, + [CLKID_AO_PWM_A_DIV] = &a9_ao_pwm_a_div.hw, + [CLKID_AO_PWM_A] = &a9_ao_pwm_a.hw, + [CLKID_AO_PWM_B_SEL] = &a9_ao_pwm_b_sel.hw, + [CLKID_AO_PWM_B_DIV] = &a9_ao_pwm_b_div.hw, + [CLKID_AO_PWM_B] = &a9_ao_pwm_b.hw, + [CLKID_AO_PWM_C_SEL] = &a9_ao_pwm_c_sel.hw, + [CLKID_AO_PWM_C_DIV] = &a9_ao_pwm_c_div.hw, + [CLKID_AO_PWM_C] = &a9_ao_pwm_c.hw, + [CLKID_AO_PWM_D_SEL] = &a9_ao_pwm_d_sel.hw, + [CLKID_AO_PWM_D_DIV] = &a9_ao_pwm_d_div.hw, + [CLKID_AO_PWM_D] = &a9_ao_pwm_d.hw, + [CLKID_AO_PWM_E_SEL] = &a9_ao_pwm_e_sel.hw, + [CLKID_AO_PWM_E_DIV] = &a9_ao_pwm_e_div.hw, + [CLKID_AO_PWM_E] = &a9_ao_pwm_e.hw, + [CLKID_AO_PWM_F_SEL] = &a9_ao_pwm_f_sel.hw, + [CLKID_AO_PWM_F_DIV] = &a9_ao_pwm_f_div.hw, + [CLKID_AO_PWM_F] = &a9_ao_pwm_f.hw, + [CLKID_AO_PWM_G_SEL] = &a9_ao_pwm_g_sel.hw, + [CLKID_AO_PWM_G_DIV] = &a9_ao_pwm_g_div.hw, + [CLKID_AO_PWM_G] = &a9_ao_pwm_g.hw, + [CLKID_AO_RTC_DUALDIV_IN] = &a9_ao_rtc_dualdiv_in.hw, + [CLKID_AO_RTC_DUALDIV_DIV] = &a9_ao_rtc_dualdiv_div.hw, + [CLKID_AO_RTC_DUALDIV_SEL] = &a9_ao_rtc_dualdiv_sel.hw, + [CLKID_AO_RTC_DUALDIV] = &a9_ao_rtc_dualdiv.hw, + [CLKID_AO_RTC] = &a9_ao_rtc.hw, + [CLKID_AO_CEC_DUALDIV_IN] = &a9_ao_cec_dualdiv_in.hw, + [CLKID_AO_CEC_DUALDIV_DIV] = &a9_ao_cec_dualdiv_div.hw, + [CLKID_AO_CEC_DUALDIV_SEL] = &a9_ao_cec_dualdiv_sel.hw, + [CLKID_AO_CEC_DUALDIV] = &a9_ao_cec_dualdiv.hw, + [CLKID_AO_CEC] = &a9_ao_cec.hw, +}; + +static const struct meson_clkc_data a9_ao_clkc_data = { + .hw_clks = { + .hws = a9_ao_hw_clks, + .num = ARRAY_SIZE(a9_ao_hw_clks), + }, +}; + +static const struct of_device_id a9_ao_clkc_match_table[] = { + { + .compatible = "amlogic,a9-aoclkc", + .data = &a9_ao_clkc_data, + }, + { } +}; +MODULE_DEVICE_TABLE(of, a9_ao_clkc_match_table); + +static struct platform_driver a9_ao_clkc_driver = { + .probe = meson_clkc_mmio_probe, + .driver = { + .name = "a9-aoclkc", + .of_match_table = a9_ao_clkc_match_table, + }, +}; +module_platform_driver(a9_ao_clkc_driver); + +MODULE_DESCRIPTION("Amlogic A9 Always-ON Clock Controller driver"); +MODULE_AUTHOR("Jian Hu "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("CLK_MESON"); From e25d8d35e8cbc1a4c04a8b86eed6aa7229f6449e Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Tue, 23 Jun 2026 18:40:18 +0900 Subject: [PATCH 0009/1328] clk: versaclock7: Fix APLL clock leak on probe failure vc7_probe() registers the APLL with clk_register_fixed_rate(), which is not devm-managed and must be explicitly unregistered on probe failure. Most later errors already unwind through err_clk, but a failure from vc7_get_bank_clk() in the output registration loop returned directly. That skipped clk_unregister_fixed_rate() and leaked the APLL clock. Route that error through the existing err_clk label so the fixed-rate clock is released consistently with the other probe failure paths. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: 48c5e98fedd9 ("clk: Renesas versaclock7 ccf device driver") Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/clk-versaclock7.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/clk-versaclock7.c b/drivers/clk/clk-versaclock7.c index adcc603e3259..e3a36dcd98b8 100644 --- a/drivers/clk/clk-versaclock7.c +++ b/drivers/clk/clk-versaclock7.c @@ -1197,7 +1197,7 @@ static int vc7_probe(struct i2c_client *client) if (ret) { dev_err_probe(&client->dev, ret, "unable to register output %d\n", i); - return ret; + goto err_clk; } switch (bank_src_map.type) { From d4873b9181345f089d4bdd0019e5744567a755f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Thu, 18 Jun 2026 18:04:55 +0200 Subject: [PATCH 0010/1328] clk: clk-lmk04832: Simplify device abstraction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The driver was introduced in 2021 and since then only supports a single chip variant. Simplify the driver by hard-coding the device properties instead of using the id_table's abstraction for a single chip type. While touching the id table, use a single space in the table terminator to match the most used style. Signed-off-by: Uwe Kleine-König (The Capable Hub) Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/clk-lmk04832.c | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/drivers/clk/clk-lmk04832.c b/drivers/clk/clk-lmk04832.c index 9bf86caad829..c644d31b223e 100644 --- a/drivers/clk/clk-lmk04832.c +++ b/drivers/clk/clk-lmk04832.c @@ -175,10 +175,6 @@ #define LMK04832_REG_RB_HOLDOVER 0x188 #define LMK04832_REG_SPI_LOCK 0x555 -enum lmk04832_device_types { - LMK04832, -}; - /** * struct lmk04832_device_info - Holds static device information that is * specific to the chip revision @@ -197,14 +193,12 @@ struct lmk04832_device_info { unsigned int vco1_range[2]; }; -static const struct lmk04832_device_info lmk04832_device_info[] = { - [LMK04832] = { - .pid = 0x63d1, /* WARNING PROD_ID is inverted in the datasheet */ - .maskrev = 0x70, - .num_channels = 14, - .vco0_range = { 2440, 2580 }, - .vco1_range = { 2945, 3255 }, - }, +static const struct lmk04832_device_info lmk04832_device_info = { + .pid = 0x63d1, /* WARNING PROD_ID is inverted in the datasheet */ + .maskrev = 0x70, + .num_channels = 14, + .vco0_range = { 2440, 2580 }, + .vco1_range = { 2945, 3255 }, }; enum lmk04832_rdbk_type { @@ -422,11 +416,10 @@ static unsigned long lmk04832_vco_recalc_rate(struct clk_hw *hw, */ static int lmk04832_check_vco_ranges(struct lmk04832 *lmk, unsigned long rate) { - struct spi_device *spi = to_spi_device(lmk->dev); const struct lmk04832_device_info *info; unsigned long mhz = rate / 1000000; - info = &lmk04832_device_info[spi_get_device_id(spi)->driver_data]; + info = &lmk04832_device_info; if (mhz >= info->vco0_range[0] && mhz <= info->vco0_range[1]) return LMK04832_VAL_VCO_MUX_VCO0; @@ -1405,7 +1398,7 @@ static int lmk04832_probe(struct spi_device *spi) int ret; int i; - info = &lmk04832_device_info[spi_get_device_id(spi)->driver_data]; + info = &lmk04832_device_info; lmk = devm_kzalloc(&spi->dev, sizeof(struct lmk04832), GFP_KERNEL); if (!lmk) @@ -1553,8 +1546,8 @@ static int lmk04832_probe(struct spi_device *spi) } static const struct spi_device_id lmk04832_id[] = { - { "lmk04832", LMK04832 }, - {} + { .name = "lmk04832" }, + { } }; MODULE_DEVICE_TABLE(spi, lmk04832_id); From 804bac4a2654ac69e328d68e1961eb984fb18a01 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Thu, 18 Jun 2026 05:50:30 +0100 Subject: [PATCH 0011/1328] clk: canaan: Clear rate fields before reprogramming dividers The rate set_rate helpers perform a read-modify-write on the divider and multiplier registers but only ever OR the new value in, without first masking off the existing field. The first write after reset lands on a zeroed field and looks correct, but any later reprogramming leaves the old bits set: the field becomes the bitwise OR of the previous and new encodings, corrupting the divider or multiplier. Mask off each field before writing the new value so reprogramming a clock to a different rate produces the intended register contents. Fixes: a7b7c7c6c016 ("clk: canaan: Add clock driver for Canaan K230") Signed-off-by: David Carlier Reviewed-by: Brian Masney Acked-by: Xukai Wang Signed-off-by: Brian Masney --- drivers/clk/clk-k230.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/clk/clk-k230.c b/drivers/clk/clk-k230.c index cfc437038e4e..f34a3e6d3bca 100644 --- a/drivers/clk/clk-k230.c +++ b/drivers/clk/clk-k230.c @@ -2227,6 +2227,7 @@ static int k230_clk_set_rate_mul(struct clk_hw *hw, unsigned long rate, guard(spinlock)(rate_self->lock); mul_reg = readl(rate_self->reg + clk->mul_reg_off); + mul_reg &= ~(rate_self->mul_mask << rate_self->mul_shift); mul_reg |= ((mul - 1) & rate_self->mul_mask) << (rate_self->mul_shift); mul_reg |= BIT(rate_self->write_enable_bit); writel(mul_reg, rate_self->reg + clk->mul_reg_off); @@ -2257,6 +2258,7 @@ static int k230_clk_set_rate_div(struct clk_hw *hw, unsigned long rate, guard(spinlock)(rate_self->lock); div_reg = readl(rate_self->reg + clk->div_reg_off); + div_reg &= ~(rate_self->div_mask << rate_self->div_shift); div_reg |= ((div - 1) & rate_self->div_mask) << (rate_self->div_shift); div_reg |= BIT(rate_self->write_enable_bit); writel(div_reg, rate_self->reg + clk->div_reg_off); @@ -2287,11 +2289,13 @@ static int k230_clk_set_rate_mul_div(struct clk_hw *hw, unsigned long rate, guard(spinlock)(rate_self->lock); div_reg = readl(rate_self->reg + clk->div_reg_off); + div_reg &= ~(rate_self->div_mask << rate_self->div_shift); div_reg |= ((div - 1) & rate_self->div_mask) << (rate_self->div_shift); div_reg |= BIT(rate_self->write_enable_bit); writel(div_reg, rate_self->reg + clk->div_reg_off); mul_reg = readl(rate_self->reg + clk->mul_reg_off); + mul_reg &= ~(rate_self->mul_mask << rate_self->mul_shift); mul_reg |= ((mul - 1) & rate_self->mul_mask) << (rate_self->mul_shift); mul_reg |= BIT(rate_self->write_enable_bit); writel(mul_reg, rate_self->reg + clk->mul_reg_off); From 03221f26ed7cb566131cf798bc653b7ff2ef23aa Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Fri, 12 Jun 2026 16:52:51 -0500 Subject: [PATCH 0012/1328] clk: at91: Read "reg" with helper The "reg" property is an address-sized DT cell property. The AT91 compat clock parser only uses a small bus id from it, but reading it with the u8 helper does not match the property encoding. Use of_property_read_reg() so the code goes through the helper for "reg" properties, then keep the existing range check before passing the bus id to the clock registration code. Assisted-by: Codex:gpt-5-5 Signed-off-by: Rob Herring (Arm) Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/at91/dt-compat.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/clk/at91/dt-compat.c b/drivers/clk/at91/dt-compat.c index 5d543e807843..dad26dd5e71d 100644 --- a/drivers/clk/at91/dt-compat.c +++ b/drivers/clk/at91/dt-compat.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -217,7 +218,7 @@ CLK_OF_DECLARE(of_sama5d4_clk_h32mx_setup, "atmel,sama5d4-clk-h32mx", static void __init of_sama5d2_clk_i2s_mux_setup(struct device_node *np) { struct regmap *regmap_sfr; - u8 bus_id; + u64 bus_id; const char *parent_names[2]; struct device_node *i2s_mux_np; struct clk_hw *hw; @@ -228,7 +229,7 @@ static void __init of_sama5d2_clk_i2s_mux_setup(struct device_node *np) return; for_each_child_of_node(np, i2s_mux_np) { - if (of_property_read_u8(i2s_mux_np, "reg", &bus_id)) + if (of_property_read_reg(i2s_mux_np, 0, &bus_id, NULL)) continue; if (bus_id > I2S_BUS_NR) From ae794aa6e88d43289ab9a9b682f5cc5f6ac92657 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Fri, 12 Jun 2026 16:46:23 +0800 Subject: [PATCH 0013/1328] dt-bindings: clock: Add spread spectrum definition Per dt-schema, the modulation methods are: down-spread(3), up-spread(2), center-spread(1), no-spread(0). So define them in dt-bindings to avoid write the magic number in device tree. Reviewed-by: Brian Masney Acked-by: Rob Herring (Arm) Reviewed-by: Sebin Francis Signed-off-by: Peng Fan Signed-off-by: Brian Masney --- include/dt-bindings/clock/clock.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 include/dt-bindings/clock/clock.h diff --git a/include/dt-bindings/clock/clock.h b/include/dt-bindings/clock/clock.h new file mode 100644 index 000000000000..155e2653a120 --- /dev/null +++ b/include/dt-bindings/clock/clock.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +/* + * Copyright 2025 NXP + */ + +#ifndef __DT_BINDINGS_CLOCK_H +#define __DT_BINDINGS_CLOCK_H + +#define CLK_SSC_NO_SPREAD 0 +#define CLK_SSC_CENTER_SPREAD 1 +#define CLK_SSC_UP_SPREAD 2 +#define CLK_SSC_DOWN_SPREAD 3 + +#endif /* __DT_BINDINGS_CLOCK_H */ From c86814e70390a48bd4323ba4318cd8c0246e019b Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Fri, 12 Jun 2026 16:46:24 +0800 Subject: [PATCH 0014/1328] clk: Introduce clk_hw_set_spread_spectrum Add clk_hw_set_spread_spectrum to configure a clock to enable spread spectrum feature. set_spread_spectrum ops is added for clk drivers to have their own hardware specific implementation. Reviewed-by: Brian Masney Reviewed-by: Sebin Francis Signed-off-by: Peng Fan Signed-off-by: Brian Masney --- drivers/clk/clk.c | 27 +++++++++++++++++++++++++++ include/linux/clk-provider.h | 31 +++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index 048adfa86a5d..8c78621cde25 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -2774,6 +2774,33 @@ int clk_set_max_rate(struct clk *clk, unsigned long rate) } EXPORT_SYMBOL_GPL(clk_set_max_rate); +int clk_hw_set_spread_spectrum(struct clk_hw *hw, const struct clk_spread_spectrum *ss_conf) +{ + struct clk_core *core; + int ret; + + if (!hw) + return 0; + + core = hw->core; + + clk_prepare_lock(); + + ret = clk_pm_runtime_get(core); + if (ret) + goto fail; + + if (core->ops->set_spread_spectrum) + ret = core->ops->set_spread_spectrum(hw, ss_conf); + + clk_pm_runtime_put(core); + +fail: + clk_prepare_unlock(); + return ret; +} +EXPORT_SYMBOL_GPL(clk_hw_set_spread_spectrum); + /** * clk_get_parent - return the parent of a clk * @clk: the clk whose parent gets returned diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h index b01a38fef8cf..7d3747378739 100644 --- a/include/linux/clk-provider.h +++ b/include/linux/clk-provider.h @@ -6,6 +6,7 @@ #ifndef __LINUX_CLK_PROVIDER_H #define __LINUX_CLK_PROVIDER_H +#include #include #include @@ -84,6 +85,26 @@ struct clk_duty { unsigned int den; }; +enum clk_ssc_method { + CLK_SPREAD_NO = CLK_SSC_NO_SPREAD, + CLK_SPREAD_CENTER = CLK_SSC_CENTER_SPREAD, + CLK_SPREAD_UP = CLK_SSC_UP_SPREAD, + CLK_SPREAD_DOWN = CLK_SSC_DOWN_SPREAD, +}; + +/** + * struct clk_spread_spectrum - Structure encoding spread spectrum of a clock + * + * @modfreq_hz: Modulation frequency + * @spread_bp: Modulation percent in permyriad + * @method: Modulation method + */ +struct clk_spread_spectrum { + u32 modfreq_hz; + u32 spread_bp; + enum clk_ssc_method method; +}; + /** * struct clk_ops - Callback operations for hardware clocks; these are to * be provided by the clock implementation, and will be called by drivers @@ -174,6 +195,12 @@ struct clk_duty { * separately via calls to .set_parent and .set_rate. * Returns 0 on success, -EERROR otherwise. * + * @set_spread_spectrum: Optional callback used to configure the spread + * spectrum modulation frequency, percentage, and method + * to reduce EMI by spreading the clock frequency over a + * wider range. + * Returns 0 on success, -EERROR otherwise. + * * @recalc_accuracy: Recalculate the accuracy of this clock. The clock accuracy * is expressed in ppb (parts per billion). The parent accuracy is * an input parameter. @@ -249,6 +276,8 @@ struct clk_ops { int (*set_rate_and_parent)(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate, u8 index); + int (*set_spread_spectrum)(struct clk_hw *hw, + const struct clk_spread_spectrum *ss_conf); unsigned long (*recalc_accuracy)(struct clk_hw *hw, unsigned long parent_accuracy); int (*get_phase)(struct clk_hw *hw); @@ -1436,6 +1465,8 @@ void clk_hw_get_rate_range(struct clk_hw *hw, unsigned long *min_rate, unsigned long *max_rate); void clk_hw_set_rate_range(struct clk_hw *hw, unsigned long min_rate, unsigned long max_rate); +int clk_hw_set_spread_spectrum(struct clk_hw *hw, + const struct clk_spread_spectrum *ss_conf); static inline void __clk_hw_set_clk(struct clk_hw *dst, struct clk_hw *src) { From a7f16660af39f80dd3ed3916458abc7f2b90e0de Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Fri, 12 Jun 2026 16:46:25 +0800 Subject: [PATCH 0015/1328] clk: conf: Support assigned-clock-sscs Parse the Spread Spectrum Configuration(SSC) from device tree and configure them before using the clock. Each SSC is three u32 elements which means '', so assigned-clock-sscs is an array of multiple three u32 elements. Reviewed-by: Brian Masney Reviewed-by: Sebin Francis Signed-off-by: Peng Fan Signed-off-by: Brian Masney --- drivers/clk/clk-conf.c | 76 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/drivers/clk/clk-conf.c b/drivers/clk/clk-conf.c index 303a0bb26e54..550b8ae375a2 100644 --- a/drivers/clk/clk-conf.c +++ b/drivers/clk/clk-conf.c @@ -155,6 +155,78 @@ static int __set_clk_rates(struct device_node *node, bool clk_supplier) return 0; } +static int __set_clk_spread_spectrum(struct device_node *node, bool clk_supplier) +{ + u32 elem_size = sizeof(struct clk_spread_spectrum); + struct clk_spread_spectrum *sscs; + struct of_phandle_args clkspec; + int rc, count, index; + struct clk *clk; + + /* modfreq, spreadPercent, modmethod */ + count = of_property_count_elems_of_size(node, "assigned-clock-sscs", elem_size); + if (count <= 0) + return 0; + + sscs = kcalloc(count, elem_size, GFP_KERNEL); + if (!sscs) + return -ENOMEM; + + rc = of_property_read_u32_array(node, "assigned-clock-sscs", (u32 *)sscs, + count * 3); + if (rc) + goto free_sscs; + + for (index = 0; index < count; index++) { + struct clk_spread_spectrum *conf = &sscs[index]; + struct clk_hw *hw; + + if (!conf->modfreq_hz && !conf->spread_bp && !conf->method) + continue; + + rc = of_parse_phandle_with_args(node, "assigned-clocks", "#clock-cells", + index, &clkspec); + if (rc < 0) { + /* skip empty (null) phandles */ + if (rc == -ENOENT) { + rc = 0; + continue; + } else + goto free_sscs; + } + + if (clkspec.np == node && !clk_supplier) { + of_node_put(clkspec.np); + goto free_sscs; + } + + clk = of_clk_get_from_provider(&clkspec); + of_node_put(clkspec.np); + if (IS_ERR(clk)) { + if (PTR_ERR(clk) != -EPROBE_DEFER) + pr_warn("clk: couldn't get clock %d for %pOF\n", + index, node); + rc = PTR_ERR(clk); + goto free_sscs; + } + + hw = __clk_get_hw(clk); + rc = clk_hw_set_spread_spectrum(hw, conf); + if (rc < 0) { + pr_err("clk: couldn't set %s clk spread spectrum %u %u %u: %d\n", + __clk_get_name(clk), conf->modfreq_hz, conf->spread_bp, + conf->method, rc); + /* Do not fail */ + rc = 0; + } + clk_put(clk); + } + +free_sscs: + kfree(sscs); + return rc; +} + /** * of_clk_set_defaults() - parse and set assigned clocks configuration * @node: device node to apply clock settings for @@ -174,6 +246,10 @@ int of_clk_set_defaults(struct device_node *node, bool clk_supplier) if (!node) return 0; + rc = __set_clk_spread_spectrum(node, clk_supplier); + if (rc < 0) + return rc; + rc = __set_clk_parents(node, clk_supplier); if (rc < 0) return rc; From 7d98de6ffdab5bf65854f2464be362f33a606238 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Fri, 12 Jun 2026 16:46:26 +0800 Subject: [PATCH 0016/1328] clk: Add KUnit tests for assigned-clock-sscs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add KUnit test coverage for the assigned-clock-sscs DT property that configures spread spectrum on clocks before they are used. Extend the existing test infrastructure to support spread spectrum: - Add struct clk_spread_spectrum field to clk_dummy_context and a clk_dummy_set_spread_spectrum callback - Wire set_spread_spectrum into all dummy clock ops - Extend clk_assigned_rates_register_clk and test parameter struct to propagate initial SSCS values Add a new separate test suite clk_assigned_sscs with three categories: 1. clk_assigned_sscs_assigns_one — verifies that a single assigned-clock-sscs entry correctly configures spread spectrum on one clock, testing both provider and consumer paths 2. clk_assigned_sscs_assigns_multiple — verifies that multiple assigned-clock-sscs entries configure spread spectrum on two clocks, testing both provider and consumer paths 3. clk_assigned_sscs_skips — verifies that malformed DT properties are correctly skipped without error: missing assigned-clocks, zero-valued SSCS, and null phandles, tested for both provider and consumer scenarios New DT overlays are added for all test scenarios: - kunit_clk_assigned_sscs_one{,consumer} — single valid entry - kunit_clk_assigned_sscs_multiple{,consumer} — two valid entries - kunit_clk_assigned_sscs_without{,consumer} — missing assigned-clocks - kunit_clk_assigned_sscs_zero{,consumer} — all-zero SSCS values - kunit_clk_assigned_sscs_null{,consumer} — null phandle Co-developed-by: Brian Masney Signed-off-by: Peng Fan Signed-off-by: Brian Masney --- drivers/clk/Makefile | 10 + drivers/clk/clk_test.c | 203 +++++++++++++++++- drivers/clk/kunit_clk_assigned_rates.h | 10 + ...kunit_clk_assigned_rates_u64_multiple.dtso | 6 + ..._assigned_rates_u64_multiple_consumer.dtso | 6 + .../clk/kunit_clk_assigned_rates_u64_one.dtso | 3 + ...t_clk_assigned_rates_u64_one_consumer.dtso | 3 + .../clk/kunit_clk_assigned_sscs_multiple.dtso | 20 ++ ...t_clk_assigned_sscs_multiple_consumer.dtso | 24 +++ drivers/clk/kunit_clk_assigned_sscs_null.dtso | 16 ++ ...kunit_clk_assigned_sscs_null_consumer.dtso | 20 ++ drivers/clk/kunit_clk_assigned_sscs_one.dtso | 16 ++ .../kunit_clk_assigned_sscs_one_consumer.dtso | 20 ++ .../clk/kunit_clk_assigned_sscs_without.dtso | 15 ++ ...it_clk_assigned_sscs_without_consumer.dtso | 19 ++ drivers/clk/kunit_clk_assigned_sscs_zero.dtso | 12 ++ ...kunit_clk_assigned_sscs_zero_consumer.dtso | 16 ++ 17 files changed, 416 insertions(+), 3 deletions(-) create mode 100644 drivers/clk/kunit_clk_assigned_sscs_multiple.dtso create mode 100644 drivers/clk/kunit_clk_assigned_sscs_multiple_consumer.dtso create mode 100644 drivers/clk/kunit_clk_assigned_sscs_null.dtso create mode 100644 drivers/clk/kunit_clk_assigned_sscs_null_consumer.dtso create mode 100644 drivers/clk/kunit_clk_assigned_sscs_one.dtso create mode 100644 drivers/clk/kunit_clk_assigned_sscs_one_consumer.dtso create mode 100644 drivers/clk/kunit_clk_assigned_sscs_without.dtso create mode 100644 drivers/clk/kunit_clk_assigned_sscs_without_consumer.dtso create mode 100644 drivers/clk/kunit_clk_assigned_sscs_zero.dtso create mode 100644 drivers/clk/kunit_clk_assigned_sscs_zero_consumer.dtso diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index cc108a75a900..6a726331b6c9 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -18,6 +18,16 @@ clk-test-y := clk_test.o \ kunit_clk_assigned_rates_without_consumer.dtbo.o \ kunit_clk_assigned_rates_zero.dtbo.o \ kunit_clk_assigned_rates_zero_consumer.dtbo.o \ + kunit_clk_assigned_sscs_one.dtbo.o \ + kunit_clk_assigned_sscs_one_consumer.dtbo.o \ + kunit_clk_assigned_sscs_multiple.dtbo.o \ + kunit_clk_assigned_sscs_multiple_consumer.dtbo.o \ + kunit_clk_assigned_sscs_null.dtbo.o \ + kunit_clk_assigned_sscs_null_consumer.dtbo.o \ + kunit_clk_assigned_sscs_without.dtbo.o \ + kunit_clk_assigned_sscs_without_consumer.dtbo.o \ + kunit_clk_assigned_sscs_zero.dtbo.o \ + kunit_clk_assigned_sscs_zero_consumer.dtbo.o \ kunit_clk_hw_get_dev_of_node.dtbo.o \ kunit_clk_parent_data_test.dtbo.o obj-$(CONFIG_COMMON_CLK) += clk-divider.o diff --git a/drivers/clk/clk_test.c b/drivers/clk/clk_test.c index b1961daac5e2..824adc95e0b2 100644 --- a/drivers/clk/clk_test.c +++ b/drivers/clk/clk_test.c @@ -28,6 +28,7 @@ static const struct clk_ops empty_clk_ops = { }; struct clk_dummy_context { struct clk_hw hw; unsigned long rate; + struct clk_spread_spectrum sscs; }; static unsigned long clk_dummy_recalc_rate(struct clk_hw *hw, @@ -83,6 +84,17 @@ static int clk_dummy_set_rate(struct clk_hw *hw, return 0; } +static int clk_dummy_set_spread_spectrum(struct clk_hw *hw, + const struct clk_spread_spectrum *ss_conf) +{ + struct clk_dummy_context *ctx = + container_of(hw, struct clk_dummy_context, hw); + + ctx->sscs = *ss_conf; + + return 0; +} + static int clk_dummy_single_set_parent(struct clk_hw *hw, u8 index) { if (index >= clk_hw_get_num_parents(hw)) @@ -100,18 +112,21 @@ static const struct clk_ops clk_dummy_rate_ops = { .recalc_rate = clk_dummy_recalc_rate, .determine_rate = clk_dummy_determine_rate, .set_rate = clk_dummy_set_rate, + .set_spread_spectrum = clk_dummy_set_spread_spectrum, }; static const struct clk_ops clk_dummy_maximize_rate_ops = { .recalc_rate = clk_dummy_recalc_rate, .determine_rate = clk_dummy_maximize_rate, .set_rate = clk_dummy_set_rate, + .set_spread_spectrum = clk_dummy_set_spread_spectrum, }; static const struct clk_ops clk_dummy_minimize_rate_ops = { .recalc_rate = clk_dummy_recalc_rate, .determine_rate = clk_dummy_minimize_rate, .set_rate = clk_dummy_set_rate, + .set_spread_spectrum = clk_dummy_set_spread_spectrum, }; static const struct clk_ops clk_dummy_single_parent_ops = { @@ -3097,6 +3112,7 @@ struct clk_assigned_rates_context { * @overlay_end: Pointer to end of DT overlay to apply for test * @rate0: Initial rate of first clk * @rate1: Initial rate of second clk + * @sscs: Initial spread spectrum settings * @consumer_test: true if a consumer is being tested */ struct clk_assigned_rates_test_param { @@ -3105,6 +3121,7 @@ struct clk_assigned_rates_test_param { u8 *overlay_end; unsigned long rate0; unsigned long rate1; + struct clk_spread_spectrum sscs; bool consumer_test; }; @@ -3116,7 +3133,7 @@ static void clk_assigned_rates_register_clk(struct kunit *test, struct clk_dummy_context *ctx, struct device_node *np, const char *name, - unsigned long rate) + unsigned long rate, const struct clk_spread_spectrum *sscs) { struct clk_init_data init = { }; @@ -3124,6 +3141,7 @@ clk_assigned_rates_register_clk(struct kunit *test, init.ops = &clk_dummy_rate_ops; ctx->hw.init = &init; ctx->rate = rate; + ctx->sscs = *sscs; KUNIT_ASSERT_EQ(test, 0, of_clk_hw_register_kunit(test, np, &ctx->hw)); KUNIT_ASSERT_EQ(test, ctx->rate, rate); @@ -3167,14 +3185,16 @@ static int clk_assigned_rates_test_init(struct kunit *test) KUNIT_ASSERT_LT(test, clk_cells, 2); clk_assigned_rates_register_clk(test, &ctx->clk0, np, - "test_assigned_rate0", test_param->rate0); + "test_assigned_rate0", test_param->rate0, + &test_param->sscs); if (clk_cells == 0) { KUNIT_ASSERT_EQ(test, 0, of_clk_add_hw_provider_kunit(test, np, of_clk_hw_simple_get, &ctx->clk0.hw)); } else if (clk_cells == 1) { clk_assigned_rates_register_clk(test, &ctx->clk1, np, - "test_assigned_rate1", test_param->rate1); + "test_assigned_rate1", test_param->rate1, + &test_param->sscs); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, data = kunit_kzalloc(test, struct_size(data, hws, 2), GFP_KERNEL)); @@ -3403,6 +3423,182 @@ static struct kunit_suite clk_assigned_rates_suite = { .init = clk_assigned_rates_test_init, }; +OF_OVERLAY_DECLARE(kunit_clk_assigned_sscs_one); +OF_OVERLAY_DECLARE(kunit_clk_assigned_sscs_one_consumer); +OF_OVERLAY_DECLARE(kunit_clk_assigned_sscs_multiple); +OF_OVERLAY_DECLARE(kunit_clk_assigned_sscs_multiple_consumer); +OF_OVERLAY_DECLARE(kunit_clk_assigned_sscs_without); +OF_OVERLAY_DECLARE(kunit_clk_assigned_sscs_without_consumer); +OF_OVERLAY_DECLARE(kunit_clk_assigned_sscs_zero); +OF_OVERLAY_DECLARE(kunit_clk_assigned_sscs_zero_consumer); +OF_OVERLAY_DECLARE(kunit_clk_assigned_sscs_null); +OF_OVERLAY_DECLARE(kunit_clk_assigned_sscs_null_consumer); + +static void clk_assigned_sscs_assigns_one(struct kunit *test) +{ + struct clk_assigned_rates_context *ctx = test->priv; + + KUNIT_EXPECT_EQ(test, ctx->clk0.sscs.modfreq_hz, ASSIGNED_SSCS_0_MODFREQ); + KUNIT_EXPECT_EQ(test, ctx->clk0.sscs.spread_bp, ASSIGNED_SSCS_0_SPREAD); + KUNIT_EXPECT_EQ(test, ctx->clk0.sscs.method, ASSIGNED_SSCS_0_METHOD); +} + +/* Test cases that assign sscs for one clk */ +static const struct clk_assigned_rates_test_param clk_assigned_sscs_assigns_one_test_params[] = { + { + /* + * Test that a single cell assigned-clock-sscs property + * assigns the sscs when the property is in the provider. + */ + .desc = "provider assigns", + TEST_PARAM_OVERLAY(kunit_clk_assigned_sscs_one), + }, + { + /* + * Test that a single cell assigned-clock-sscs property + * assigns the sscs when the property is in the consumer. + */ + .desc = "consumer assigns", + TEST_PARAM_OVERLAY(kunit_clk_assigned_sscs_one_consumer), + .consumer_test = true, + }, +}; +KUNIT_ARRAY_PARAM_DESC(clk_assigned_sscs_assigns_one, + clk_assigned_sscs_assigns_one_test_params, desc) + +static void clk_assigned_sscs_assigns_multiple(struct kunit *test) +{ + struct clk_assigned_rates_context *ctx = test->priv; + + KUNIT_EXPECT_EQ(test, ctx->clk0.sscs.modfreq_hz, ASSIGNED_SSCS_0_MODFREQ); + KUNIT_EXPECT_EQ(test, ctx->clk0.sscs.spread_bp, ASSIGNED_SSCS_0_SPREAD); + KUNIT_EXPECT_EQ(test, ctx->clk0.sscs.method, ASSIGNED_SSCS_0_METHOD); + KUNIT_EXPECT_EQ(test, ctx->clk1.sscs.modfreq_hz, ASSIGNED_SSCS_1_MODFREQ); + KUNIT_EXPECT_EQ(test, ctx->clk1.sscs.spread_bp, ASSIGNED_SSCS_1_SPREAD); + KUNIT_EXPECT_EQ(test, ctx->clk1.sscs.method, ASSIGNED_SSCS_1_METHOD); +} + +/* Test cases that assign sscs for multiple clks */ +static const +struct clk_assigned_rates_test_param clk_assigned_sscs_assigns_multiple_test_params[] = { + { + /* + * Test that a multiple cell assigned-clock-sscs property + * assigns the sscs when the property is in the provider. + */ + .desc = "provider assigns", + TEST_PARAM_OVERLAY(kunit_clk_assigned_sscs_multiple), + }, + { + /* + * Test that a multiple cell assigned-clock-sscs property + * assigns the sscs when the property is in the consumer. + */ + .desc = "consumer assigns", + TEST_PARAM_OVERLAY(kunit_clk_assigned_sscs_multiple_consumer), + .consumer_test = true, + }, +}; +KUNIT_ARRAY_PARAM_DESC(clk_assigned_sscs_assigns_multiple, + clk_assigned_sscs_assigns_multiple_test_params, + desc) + +static void clk_assigned_sscs_skips(struct kunit *test) +{ + struct clk_assigned_rates_context *ctx = test->priv; + const struct clk_assigned_rates_test_param *test_param = test->param_value; + + KUNIT_EXPECT_NE(test, ctx->clk0.sscs.modfreq_hz, ASSIGNED_SSCS_0_MODFREQ); + KUNIT_EXPECT_NE(test, ctx->clk0.sscs.spread_bp, ASSIGNED_SSCS_0_SPREAD); + KUNIT_EXPECT_NE(test, ctx->clk0.sscs.method, ASSIGNED_SSCS_0_METHOD); + KUNIT_EXPECT_EQ(test, ctx->clk0.sscs.modfreq_hz, test_param->sscs.modfreq_hz); + KUNIT_EXPECT_EQ(test, ctx->clk0.sscs.spread_bp, test_param->sscs.spread_bp); + KUNIT_EXPECT_EQ(test, ctx->clk0.sscs.method, test_param->sscs.method); +} + +/* Test cases that skip changing the sscs due to malformed DT */ +static const struct clk_assigned_rates_test_param clk_assigned_sscs_skips_test_params[] = { + { + /* + * Test that an assigned-clock-sscs property without an assigned-clocks + * property fails when the property is in the provider. + */ + .desc = "provider missing assigned-clocks", + TEST_PARAM_OVERLAY(kunit_clk_assigned_sscs_without), + .sscs = {50000, 60000, 3}, + }, + { + /* + * Test that an assigned-clock-sscs property without an assigned-clocks + * property fails when the property is in the consumer. + */ + .desc = "consumer missing assigned-clocks", + TEST_PARAM_OVERLAY(kunit_clk_assigned_sscs_without_consumer), + .sscs = {50000, 60000, 3}, + .consumer_test = true, + }, + { + /* + * Test that an assigned-clock-sscs property of zero doesn't + * set sscs when the property is in the provider. + */ + .desc = "provider assigned-clock-sscs of zero", + TEST_PARAM_OVERLAY(kunit_clk_assigned_sscs_zero), + .sscs = {50000, 60000, 3}, + }, + { + /* + * Test that an assigned-clock-sscs property of zero doesn't + * set sscs when the property is in the consumer. + */ + .desc = "consumer assigned-clock-sscs of zero", + TEST_PARAM_OVERLAY(kunit_clk_assigned_sscs_zero_consumer), + .sscs = {50000, 60000, 3}, + .consumer_test = true, + }, + { + /* + * Test that an assigned-clocks property with a null phandle + * doesn't set sscs when the property is in the provider. + */ + .desc = "provider assigned-clocks null phandle", + TEST_PARAM_OVERLAY(kunit_clk_assigned_sscs_null), + .sscs = {50000, 60000, 3}, + }, + { + /* + * Test that an assigned-clocks property with a null phandle + * doesn't set sscs when the property is in the consumer. + */ + .desc = "consumer assigned-clocks null phandle", + TEST_PARAM_OVERLAY(kunit_clk_assigned_sscs_null_consumer), + .sscs = {50000, 60000, 3}, + .consumer_test = true, + }, +}; +KUNIT_ARRAY_PARAM_DESC(clk_assigned_sscs_skips, + clk_assigned_sscs_skips_test_params, + desc) + +static struct kunit_case clk_assigned_sscs_test_cases[] = { + KUNIT_CASE_PARAM(clk_assigned_sscs_assigns_one, + clk_assigned_sscs_assigns_one_gen_params), + KUNIT_CASE_PARAM(clk_assigned_sscs_assigns_multiple, + clk_assigned_sscs_assigns_multiple_gen_params), + KUNIT_CASE_PARAM(clk_assigned_sscs_skips, + clk_assigned_sscs_skips_gen_params), + {} +}; + +/* + * Test suite for assigned-clock-sscs DT property. + */ +static struct kunit_suite clk_assigned_sscs_suite = { + .name = "clk_assigned_sscs", + .test_cases = clk_assigned_sscs_test_cases, + .init = clk_assigned_rates_test_init, +}; + static const struct clk_init_data clk_hw_get_dev_of_node_init_data = { .name = "clk_hw_get_dev_of_node", .ops = &empty_clk_ops, @@ -3544,6 +3740,7 @@ static struct kunit_suite clk_hw_get_dev_of_node_test_suite = { kunit_test_suites( &clk_assigned_rates_suite, + &clk_assigned_sscs_suite, &clk_hw_get_dev_of_node_test_suite, &clk_leaf_mux_set_rate_parent_test_suite, &clk_test_suite, diff --git a/drivers/clk/kunit_clk_assigned_rates.h b/drivers/clk/kunit_clk_assigned_rates.h index df2d84dcaa93..d7ae5ec2d25b 100644 --- a/drivers/clk/kunit_clk_assigned_rates.h +++ b/drivers/clk/kunit_clk_assigned_rates.h @@ -1,8 +1,18 @@ /* SPDX-License-Identifier: GPL-2.0 */ + +#include + #ifndef _KUNIT_CLK_ASSIGNED_RATES_H #define _KUNIT_CLK_ASSIGNED_RATES_H #define ASSIGNED_RATES_0_RATE 1600000 #define ASSIGNED_RATES_1_RATE 9700000 +#define ASSIGNED_SSCS_0_MODFREQ 10000 +#define ASSIGNED_SSCS_0_SPREAD 30000 +#define ASSIGNED_SSCS_0_METHOD CLK_SSC_CENTER_SPREAD +#define ASSIGNED_SSCS_1_MODFREQ 20000 +#define ASSIGNED_SSCS_1_SPREAD 40000 +#define ASSIGNED_SSCS_1_METHOD CLK_SSC_UP_SPREAD + #endif diff --git a/drivers/clk/kunit_clk_assigned_rates_u64_multiple.dtso b/drivers/clk/kunit_clk_assigned_rates_u64_multiple.dtso index 389b4e2eb7f7..3a717dab2d00 100644 --- a/drivers/clk/kunit_clk_assigned_rates_u64_multiple.dtso +++ b/drivers/clk/kunit_clk_assigned_rates_u64_multiple.dtso @@ -12,5 +12,11 @@ clk: kunit-clock { <&clk 1>; assigned-clock-rates-u64 = /bits/ 64 , /bits/ 64 ; + assigned-clock-sscs = , + ; }; }; diff --git a/drivers/clk/kunit_clk_assigned_rates_u64_multiple_consumer.dtso b/drivers/clk/kunit_clk_assigned_rates_u64_multiple_consumer.dtso index 3e117fd59b7d..cbee7cbad068 100644 --- a/drivers/clk/kunit_clk_assigned_rates_u64_multiple_consumer.dtso +++ b/drivers/clk/kunit_clk_assigned_rates_u64_multiple_consumer.dtso @@ -16,5 +16,11 @@ kunit-clock-consumer { <&clk 1>; assigned-clock-rates-u64 = /bits/ 64 , /bits/ 64 ; + assigned-clock-sscs = , + ; }; }; diff --git a/drivers/clk/kunit_clk_assigned_rates_u64_one.dtso b/drivers/clk/kunit_clk_assigned_rates_u64_one.dtso index 87041264e8f5..9b04d6927f08 100644 --- a/drivers/clk/kunit_clk_assigned_rates_u64_one.dtso +++ b/drivers/clk/kunit_clk_assigned_rates_u64_one.dtso @@ -10,5 +10,8 @@ clk: kunit-clock { #clock-cells = <0>; assigned-clocks = <&clk>; assigned-clock-rates-u64 = /bits/ 64 ; + assigned-clock-sscs = ; }; }; diff --git a/drivers/clk/kunit_clk_assigned_rates_u64_one_consumer.dtso b/drivers/clk/kunit_clk_assigned_rates_u64_one_consumer.dtso index 3259c003aec0..4784d40520f4 100644 --- a/drivers/clk/kunit_clk_assigned_rates_u64_one_consumer.dtso +++ b/drivers/clk/kunit_clk_assigned_rates_u64_one_consumer.dtso @@ -14,5 +14,8 @@ kunit-clock-consumer { compatible = "test,clk-consumer"; assigned-clocks = <&clk>; assigned-clock-rates-u64 = /bits/ 64 ; + assigned-clock-sscs = ; }; }; diff --git a/drivers/clk/kunit_clk_assigned_sscs_multiple.dtso b/drivers/clk/kunit_clk_assigned_sscs_multiple.dtso new file mode 100644 index 000000000000..e3472f95987c --- /dev/null +++ b/drivers/clk/kunit_clk_assigned_sscs_multiple.dtso @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0 +/dts-v1/; +/plugin/; + +#include "kunit_clk_assigned_rates.h" + +&{/} { + clk: kunit-clock { + compatible = "test,clk-assigned-rates"; + #clock-cells = <1>; + assigned-clocks = <&clk 0>, + <&clk 1>; + assigned-clock-sscs = , + ; + }; +}; diff --git a/drivers/clk/kunit_clk_assigned_sscs_multiple_consumer.dtso b/drivers/clk/kunit_clk_assigned_sscs_multiple_consumer.dtso new file mode 100644 index 000000000000..6e8971bd272a --- /dev/null +++ b/drivers/clk/kunit_clk_assigned_sscs_multiple_consumer.dtso @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0 +/dts-v1/; +/plugin/; + +#include "kunit_clk_assigned_rates.h" + +&{/} { + clk: kunit-clock { + compatible = "test,clk-assigned-rates"; + #clock-cells = <1>; + }; + + kunit-clock-consumer { + compatible = "test,clk-consumer"; + assigned-clocks = <&clk 0>, + <&clk 1>; + assigned-clock-sscs = , + ; + }; +}; diff --git a/drivers/clk/kunit_clk_assigned_sscs_null.dtso b/drivers/clk/kunit_clk_assigned_sscs_null.dtso new file mode 100644 index 000000000000..43b2068c845d --- /dev/null +++ b/drivers/clk/kunit_clk_assigned_sscs_null.dtso @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0 +/dts-v1/; +/plugin/; + +#include "kunit_clk_assigned_rates.h" + +&{/} { + clk: kunit-clock { + compatible = "test,clk-assigned-rates"; + #clock-cells = <0>; + assigned-clocks = <0>; + assigned-clock-sscs = ; + }; +}; diff --git a/drivers/clk/kunit_clk_assigned_sscs_null_consumer.dtso b/drivers/clk/kunit_clk_assigned_sscs_null_consumer.dtso new file mode 100644 index 000000000000..bda008f5aaa3 --- /dev/null +++ b/drivers/clk/kunit_clk_assigned_sscs_null_consumer.dtso @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0 +/dts-v1/; +/plugin/; + +#include "kunit_clk_assigned_rates.h" + +&{/} { + clk: kunit-clock { + compatible = "test,clk-assigned-rates"; + #clock-cells = <0>; + }; + + kunit-clock-consumer { + compatible = "test,clk-consumer"; + assigned-clocks = <0>; + assigned-clock-sscs = ; + }; +}; diff --git a/drivers/clk/kunit_clk_assigned_sscs_one.dtso b/drivers/clk/kunit_clk_assigned_sscs_one.dtso new file mode 100644 index 000000000000..91f585b5d8c9 --- /dev/null +++ b/drivers/clk/kunit_clk_assigned_sscs_one.dtso @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0 +/dts-v1/; +/plugin/; + +#include "kunit_clk_assigned_rates.h" + +&{/} { + clk: kunit-clock { + compatible = "test,clk-assigned-rates"; + #clock-cells = <0>; + assigned-clocks = <&clk>; + assigned-clock-sscs = ; + }; +}; diff --git a/drivers/clk/kunit_clk_assigned_sscs_one_consumer.dtso b/drivers/clk/kunit_clk_assigned_sscs_one_consumer.dtso new file mode 100644 index 000000000000..0bc8a03c2041 --- /dev/null +++ b/drivers/clk/kunit_clk_assigned_sscs_one_consumer.dtso @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0 +/dts-v1/; +/plugin/; + +#include "kunit_clk_assigned_rates.h" + +&{/} { + clk: kunit-clock { + compatible = "test,clk-assigned-rates"; + #clock-cells = <0>; + }; + + kunit-clock-consumer { + compatible = "test,clk-consumer"; + assigned-clocks = <&clk>; + assigned-clock-sscs = ; + }; +}; diff --git a/drivers/clk/kunit_clk_assigned_sscs_without.dtso b/drivers/clk/kunit_clk_assigned_sscs_without.dtso new file mode 100644 index 000000000000..08660846b55c --- /dev/null +++ b/drivers/clk/kunit_clk_assigned_sscs_without.dtso @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0 +/dts-v1/; +/plugin/; + +#include "kunit_clk_assigned_rates.h" + +&{/} { + clk: kunit-clock { + compatible = "test,clk-assigned-rates"; + #clock-cells = <0>; + assigned-clock-sscs = ; + }; +}; diff --git a/drivers/clk/kunit_clk_assigned_sscs_without_consumer.dtso b/drivers/clk/kunit_clk_assigned_sscs_without_consumer.dtso new file mode 100644 index 000000000000..e1c089c6f0c0 --- /dev/null +++ b/drivers/clk/kunit_clk_assigned_sscs_without_consumer.dtso @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0 +/dts-v1/; +/plugin/; + +#include "kunit_clk_assigned_rates.h" + +&{/} { + clk: kunit-clock { + compatible = "test,clk-assigned-rates"; + #clock-cells = <0>; + }; + + kunit-clock-consumer { + compatible = "test,clk-consumer"; + assigned-clock-sscs = ; + }; +}; diff --git a/drivers/clk/kunit_clk_assigned_sscs_zero.dtso b/drivers/clk/kunit_clk_assigned_sscs_zero.dtso new file mode 100644 index 000000000000..f39f4e754e53 --- /dev/null +++ b/drivers/clk/kunit_clk_assigned_sscs_zero.dtso @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0 +/dts-v1/; +/plugin/; + +&{/} { + clk: kunit-clock { + compatible = "test,clk-assigned-rates"; + #clock-cells = <0>; + assigned-clocks = <&clk>; + assigned-clock-sscs = <0 0 0>; + }; +}; diff --git a/drivers/clk/kunit_clk_assigned_sscs_zero_consumer.dtso b/drivers/clk/kunit_clk_assigned_sscs_zero_consumer.dtso new file mode 100644 index 000000000000..d6bd7dfada7e --- /dev/null +++ b/drivers/clk/kunit_clk_assigned_sscs_zero_consumer.dtso @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0 +/dts-v1/; +/plugin/; + +&{/} { + clk: kunit-clock { + compatible = "test,clk-assigned-rates"; + #clock-cells = <0>; + }; + + kunit-clock-consumer { + compatible = "test,clk-consumer"; + assigned-clocks = <&clk>; + assigned-clock-sscs = <0 0 0>; + }; +}; From a73edd3ed8d3c633be4fbe101ae9756222249a5f Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Fri, 12 Jun 2026 16:46:27 +0800 Subject: [PATCH 0017/1328] clk: scmi: Introduce common header for SCMI clock interface Added a new header file 'clk-scmi.h' to define common structures and interfaces for the SCMI clock driver. This header will also be used by OEM-specific extensions to ensure consistency and reusability. Moved relevant structure definitions from the driver implementation to 'clk-scmi.h' to facilitate shared usage. Reviewed-by: Sebin Francis Reviewed-by: Cristian Marussi Signed-off-by: Peng Fan Signed-off-by: Brian Masney --- drivers/clk/clk-scmi.c | 27 +-------------------------- drivers/clk/clk-scmi.h | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 26 deletions(-) create mode 100644 drivers/clk/clk-scmi.h diff --git a/drivers/clk/clk-scmi.c b/drivers/clk/clk-scmi.c index 7c562559ad8b..d88e78cc9a12 100644 --- a/drivers/clk/clk-scmi.c +++ b/drivers/clk/clk-scmi.c @@ -13,32 +13,7 @@ #include #include -#define NOT_ATOMIC false -#define ATOMIC true - -enum scmi_clk_feats { - SCMI_CLK_ATOMIC_SUPPORTED, - SCMI_CLK_STATE_CTRL_SUPPORTED, - SCMI_CLK_RATE_CTRL_SUPPORTED, - SCMI_CLK_PARENT_CTRL_SUPPORTED, - SCMI_CLK_DUTY_CYCLE_SUPPORTED, - SCMI_CLK_FEATS_COUNT -}; - -#define SCMI_MAX_CLK_OPS BIT(SCMI_CLK_FEATS_COUNT) - -static const struct scmi_clk_proto_ops *scmi_proto_clk_ops; - -struct scmi_clk { - u32 id; - struct device *dev; - struct clk_hw hw; - const struct scmi_clock_info *info; - const struct scmi_protocol_handle *ph; - struct clk_parent_data *parent_data; -}; - -#define to_scmi_clk(clk) container_of(clk, struct scmi_clk, hw) +const struct scmi_clk_proto_ops *scmi_proto_clk_ops; static unsigned long scmi_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) diff --git a/drivers/clk/clk-scmi.h b/drivers/clk/clk-scmi.h new file mode 100644 index 000000000000..6ef6adc77c83 --- /dev/null +++ b/drivers/clk/clk-scmi.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2025 NXP + */ + +#ifndef __SCMI_CLK_H +#define __SCMI_CLK_H + +#include +#include +#include + +#define NOT_ATOMIC false +#define ATOMIC true + +enum scmi_clk_feats { + SCMI_CLK_ATOMIC_SUPPORTED, + SCMI_CLK_STATE_CTRL_SUPPORTED, + SCMI_CLK_RATE_CTRL_SUPPORTED, + SCMI_CLK_PARENT_CTRL_SUPPORTED, + SCMI_CLK_DUTY_CYCLE_SUPPORTED, + SCMI_CLK_FEATS_COUNT +}; + +#define SCMI_MAX_CLK_OPS BIT(SCMI_CLK_FEATS_COUNT) + +struct scmi_clk { + u32 id; + struct device *dev; + struct clk_hw hw; + const struct scmi_clock_info *info; + const struct scmi_protocol_handle *ph; + struct clk_parent_data *parent_data; +}; + +#define to_scmi_clk(clk) container_of(clk, struct scmi_clk, hw) + +extern const struct scmi_clk_proto_ops *scmi_proto_clk_ops; + +#endif From 77369b1e6a37134f4c7bf9c175c7c4ec36c3ee11 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Fri, 12 Jun 2026 16:46:28 +0800 Subject: [PATCH 0018/1328] clk: scmi: Add i.MX95 OEM extension support for SCMI clock driver - Introduce 'clk-scmi-oem.c' to support vendor-specific OEM extensions for the SCMI clock driver, allows clean integration of vendor-specific features without impacting the core SCMI clock driver logic. - Extend 'clk-scmi.h' with 'scmi_clk_oem' structure and related declarations. - Initialize OEM extensions via 'scmi_clk_oem_init()'. - Support querying OEM-specific features and setting spread spectrum. - Pass 'scmi_device' to 'scmi_clk_ops_select()' for OEM data access. Reviewed-by: Sebin Francis Reviewed-by: Cristian Marussi Signed-off-by: Peng Fan Signed-off-by: Brian Masney --- drivers/clk/Makefile | 2 +- drivers/clk/clk-scmi-oem.c | 108 +++++++++++++++++++++++++++++++++++++ drivers/clk/clk-scmi.c | 19 +++++-- drivers/clk/clk-scmi.h | 11 ++++ 4 files changed, 136 insertions(+), 4 deletions(-) create mode 100644 drivers/clk/clk-scmi-oem.c diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 6a726331b6c9..c2ae700ec0f2 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -98,7 +98,7 @@ obj-$(CONFIG_COMMON_CLK_RP1) += clk-rp1.o obj-$(CONFIG_COMMON_CLK_RPMI) += clk-rpmi.o obj-$(CONFIG_COMMON_CLK_HI655X) += clk-hi655x.o obj-$(CONFIG_COMMON_CLK_S2MPS11) += clk-s2mps11.o -obj-$(CONFIG_COMMON_CLK_SCMI) += clk-scmi.o +obj-$(CONFIG_COMMON_CLK_SCMI) += clk-scmi.o clk-scmi-oem.o obj-$(CONFIG_COMMON_CLK_SCPI) += clk-scpi.o obj-$(CONFIG_COMMON_CLK_SI5341) += clk-si5341.o obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o diff --git a/drivers/clk/clk-scmi-oem.c b/drivers/clk/clk-scmi-oem.c new file mode 100644 index 000000000000..be11d359b4ec --- /dev/null +++ b/drivers/clk/clk-scmi-oem.c @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * The Vendor OEM extension for System Control and Power Interface (SCMI) + * Protocol based clock driver + * + * Copyright 2025 NXP + */ + +#include +#include +#include +#include + +#include "clk-scmi.h" + +#define SCMI_CLOCK_CFG_IMX_SSC 0x80 +#define SCMI_CLOCK_IMX_SS_PERCENTAGE_MASK GENMASK(7, 0) +#define SCMI_CLOCK_IMX_SS_MOD_FREQ_MASK GENMASK(23, 8) +#define SCMI_CLOCK_IMX_SS_ENABLE_MASK BIT(24) + +/* + * Selection is based on SCMI vendor_id/sub_vendor_id and optional machine + * compatible string, without involving impl_ver. impl_ver‑specific behavior + * should be considered a bug and handled via SCMI Quirk framework. + */ +struct scmi_clk_oem_info { + char *vendor_id; + char *sub_vendor_id; + char *compatible; + const void *data; +}; + +static int +scmi_clk_imx_set_spread_spectrum(struct clk_hw *hw, + const struct clk_spread_spectrum *ss_conf) +{ + struct scmi_clk *clk = to_scmi_clk(hw); + int ret; + u32 val; + + /* + * extConfigValue[7:0] - spread percentage (%) + * extConfigValue[23:8] - Modulation Frequency + * extConfigValue[24] - Enable/Disable + * extConfigValue[31:25] - Reserved + */ + val = FIELD_PREP(SCMI_CLOCK_IMX_SS_PERCENTAGE_MASK, ss_conf->spread_bp / 10000); + val |= FIELD_PREP(SCMI_CLOCK_IMX_SS_MOD_FREQ_MASK, ss_conf->modfreq_hz); + if (ss_conf->method != CLK_SPREAD_NO) + val |= SCMI_CLOCK_IMX_SS_ENABLE_MASK; + ret = scmi_proto_clk_ops->config_oem_set(clk->ph, clk->id, + SCMI_CLOCK_CFG_IMX_SSC, + val, false); + if (ret) + dev_warn(clk->dev, + "Failed to set spread spectrum(%u,%u,%u) for clock ID %d\n", + ss_conf->modfreq_hz, ss_conf->spread_bp, ss_conf->method, + clk->id); + + return ret; +} + +static int +scmi_clk_imx_query_oem_feats(const struct scmi_protocol_handle *ph, u32 id, + unsigned int *feats_key) +{ + int ret; + u32 val; + + ret = scmi_proto_clk_ops->config_oem_get(ph, id, + SCMI_CLOCK_CFG_IMX_SSC, + &val, NULL, false); + if (!ret) + *feats_key |= BIT(SCMI_CLK_EXT_OEM_SSC_SUPPORTED); + + return 0; +} + +static const struct scmi_clk_oem scmi_clk_oem_imx = { + .query_ext_oem_feats = scmi_clk_imx_query_oem_feats, + .set_spread_spectrum = scmi_clk_imx_set_spread_spectrum, +}; + +static const struct scmi_clk_oem_info info[] = { + { SCMI_IMX_VENDOR, SCMI_IMX_SUBVENDOR, NULL, &scmi_clk_oem_imx }, +}; + +int scmi_clk_oem_init(struct scmi_device *sdev) +{ + const struct scmi_handle *handle = sdev->handle; + int i, size = ARRAY_SIZE(info); + + for (i = 0; i < size; i++) { + if (strcmp(handle->version->vendor_id, info[i].vendor_id) || + strcmp(handle->version->sub_vendor_id, info[i].sub_vendor_id)) + continue; + if (info[i].compatible && + !of_machine_is_compatible(info[i].compatible)) + continue; + + break; + } + + if (i < size) + dev_set_drvdata(&sdev->dev, (void *)info[i].data); + + return 0; +} diff --git a/drivers/clk/clk-scmi.c b/drivers/clk/clk-scmi.c index d88e78cc9a12..2dd50c5b4ea8 100644 --- a/drivers/clk/clk-scmi.c +++ b/drivers/clk/clk-scmi.c @@ -13,6 +13,8 @@ #include #include +#include "clk-scmi.h" + const struct scmi_clk_proto_ops *scmi_proto_clk_ops; static unsigned long scmi_clk_recalc_rate(struct clk_hw *hw, @@ -210,6 +212,7 @@ static int scmi_clk_ops_init(struct device *dev, struct scmi_clk *sclk, static const struct clk_ops * scmi_clk_ops_alloc(struct device *dev, unsigned long feats_key) { + struct scmi_clk_oem *oem_data = dev_get_drvdata(dev); struct clk_ops *ops; ops = devm_kzalloc(dev, sizeof(*ops), GFP_KERNEL); @@ -256,11 +259,15 @@ scmi_clk_ops_alloc(struct device *dev, unsigned long feats_key) ops->set_duty_cycle = scmi_clk_set_duty_cycle; } + if (oem_data && (feats_key & BIT(SCMI_CLK_EXT_OEM_SSC_SUPPORTED))) + ops->set_spread_spectrum = oem_data->set_spread_spectrum; + return ops; } /** * scmi_clk_ops_select() - Select a proper set of clock operations + * @sdev: pointer to the SCMI device * @sclk: A reference to an SCMI clock descriptor * @atomic_capable: A flag to indicate if atomic mode is supported by the * transport @@ -285,8 +292,8 @@ scmi_clk_ops_alloc(struct device *dev, unsigned long feats_key) * NULL otherwise. */ static const struct clk_ops * -scmi_clk_ops_select(struct scmi_clk *sclk, bool atomic_capable, - unsigned int atomic_threshold_us, +scmi_clk_ops_select(struct scmi_device *sdev, struct scmi_clk *sclk, + bool atomic_capable, unsigned int atomic_threshold_us, const struct clk_ops **clk_ops_db, size_t db_size) { int ret; @@ -294,6 +301,7 @@ scmi_clk_ops_select(struct scmi_clk *sclk, bool atomic_capable, const struct scmi_clock_info *ci = sclk->info; unsigned int feats_key = 0; const struct clk_ops *ops; + struct scmi_clk_oem *oem_data = dev_get_drvdata(&sdev->dev); /* * Note that when transport is atomic but SCMI protocol did not @@ -318,6 +326,9 @@ scmi_clk_ops_select(struct scmi_clk *sclk, bool atomic_capable, &val, NULL, false); if (!ret) feats_key |= BIT(SCMI_CLK_DUTY_CYCLE_SUPPORTED); + + if (oem_data && oem_data->query_ext_oem_feats) + oem_data->query_ext_oem_feats(sclk->ph, sclk->id, &feats_key); } if (WARN_ON(feats_key >= db_size)) @@ -375,6 +386,8 @@ static int scmi_clocks_probe(struct scmi_device *sdev) clk_data->num = count; hws = clk_data->hws; + scmi_clk_oem_init(sdev); + transport_is_atomic = handle->is_transport_atomic(handle, &atomic_threshold_us); @@ -406,7 +419,7 @@ static int scmi_clocks_probe(struct scmi_device *sdev) * to avoid sharing the devm_ allocated clk_ops between multiple * SCMI clk driver instances. */ - scmi_ops = scmi_clk_ops_select(sclk, transport_is_atomic, + scmi_ops = scmi_clk_ops_select(sdev, sclk, transport_is_atomic, atomic_threshold_us, scmi_clk_ops_db, ARRAY_SIZE(scmi_clk_ops_db)); diff --git a/drivers/clk/clk-scmi.h b/drivers/clk/clk-scmi.h index 6ef6adc77c83..d7f63f36c56d 100644 --- a/drivers/clk/clk-scmi.h +++ b/drivers/clk/clk-scmi.h @@ -7,6 +7,7 @@ #define __SCMI_CLK_H #include +#include #include #include @@ -19,6 +20,7 @@ enum scmi_clk_feats { SCMI_CLK_RATE_CTRL_SUPPORTED, SCMI_CLK_PARENT_CTRL_SUPPORTED, SCMI_CLK_DUTY_CYCLE_SUPPORTED, + SCMI_CLK_EXT_OEM_SSC_SUPPORTED, SCMI_CLK_FEATS_COUNT }; @@ -37,4 +39,13 @@ struct scmi_clk { extern const struct scmi_clk_proto_ops *scmi_proto_clk_ops; +struct scmi_clk_oem { + int (*query_ext_oem_feats)(const struct scmi_protocol_handle *ph, + u32 id, unsigned int *feats_key); + int (*set_spread_spectrum)(struct clk_hw *hw, + const struct clk_spread_spectrum *ss_conf); +}; + +int scmi_clk_oem_init(struct scmi_device *dev); + #endif From 9cb19714779f7fc53583e2b074a794329e96df0d Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sun, 7 Jun 2026 22:16:02 -0700 Subject: [PATCH 0019/1328] clk: kirkwood: use kzalloc_flex Simplify allocation by using a flexible array member and kzalloc_flex to combine allocations. Add __counted_by for extra runtime analysis. Move counting variable assignment to right after allocation. kzalloc_flex does this automatically with GCC >= 15. Signed-off-by: Rosen Penev Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/mvebu/kirkwood.c | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/drivers/clk/mvebu/kirkwood.c b/drivers/clk/mvebu/kirkwood.c index ed061d82fb65..f4f62b241193 100644 --- a/drivers/clk/mvebu/kirkwood.c +++ b/drivers/clk/mvebu/kirkwood.c @@ -253,8 +253,8 @@ struct clk_muxing_soc_desc { struct clk_muxing_ctrl { spinlock_t *lock; - struct clk **muxes; int num_muxes; + struct clk *muxes[] __counted_by(num_muxes); }; static const char *powersave_parents[] = { @@ -297,21 +297,18 @@ static void __init kirkwood_clk_muxing_setup(struct device_node *np, if (WARN_ON(!base)) return; - ctrl = kzalloc_obj(*ctrl); - if (WARN_ON(!ctrl)) - goto ctrl_out; - - /* lock must already be initialized */ - ctrl->lock = &ctrl_gating_lock; - /* Count, allocate, and register clock muxes */ for (n = 0; desc[n].name;) n++; + ctrl = kzalloc_flex(*ctrl, muxes, n); + if (WARN_ON(!ctrl)) + goto ctrl_out; + ctrl->num_muxes = n; - ctrl->muxes = kzalloc_objs(struct clk *, ctrl->num_muxes); - if (WARN_ON(!ctrl->muxes)) - goto muxes_out; + + /* lock must already be initialized */ + ctrl->lock = &ctrl_gating_lock; for (n = 0; n < ctrl->num_muxes; n++) { ctrl->muxes[n] = clk_register_mux(NULL, desc[n].name, @@ -324,8 +321,6 @@ static void __init kirkwood_clk_muxing_setup(struct device_node *np, of_clk_add_provider(np, clk_muxing_get_src, ctrl); return; -muxes_out: - kfree(ctrl); ctrl_out: iounmap(base); } From 91d00e377f65933d92a6d8aa9a4ab5a4bc5b84c4 Mon Sep 17 00:00:00 2001 From: David Laight Date: Sat, 6 Jun 2026 21:26:29 +0100 Subject: [PATCH 0020/1328] drivers/clk/clk_test: Use strscpy() to copy the test description The buffer length is known to be KUNIT_PARAM_DESC_SIZE Signed-off-by: David Laight Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/clk_test.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/clk/clk_test.c b/drivers/clk/clk_test.c index 824adc95e0b2..94972f38e2e1 100644 --- a/drivers/clk/clk_test.c +++ b/drivers/clk/clk_test.c @@ -2247,7 +2247,7 @@ static void clk_leaf_mux_set_rate_parent_determine_rate_test_case_to_desc( const struct clk_leaf_mux_set_rate_parent_determine_rate_test_case *t, char *desc) { - strcpy(desc, t->desc); + strscpy(desc, t->desc, KUNIT_PARAM_DESC_SIZE); } static const struct clk_leaf_mux_set_rate_parent_determine_rate_test_case @@ -2659,7 +2659,7 @@ static void clk_register_clk_parent_data_test_case_to_desc( const struct clk_register_clk_parent_data_test_case *t, char *desc) { - strcpy(desc, t->desc); + strscpy(desc, t->desc, KUNIT_PARAM_DESC_SIZE); } static const struct clk_register_clk_parent_data_test_case From 4adf593c6fc5aed4639add011f71a074a1bd3966 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20L=C3=B6bl?= Date: Fri, 5 Jun 2026 15:03:40 +0200 Subject: [PATCH 0021/1328] clk: clocking-wizard: fix integer overflow in rate calculation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When using driver on Zynq-7000 (32-bit) determine_rate calculation overflows. For instance requesting 32MHz with 100MHz parent clock results in 100000000*(4*1000+0) 32-bit multiplication. Replace the expression with mult_frac which is already used in clk_wzrd_recalc_ratef. Cc: stable@vger.kernel.org Fixes: 7681f64e6404 ("clk: clocking-wizard: calculate dividers fractional parts") Signed-off-by: Pale Löbl Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/xilinx/clk-xlnx-clock-wizard.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/clk/xilinx/clk-xlnx-clock-wizard.c b/drivers/clk/xilinx/clk-xlnx-clock-wizard.c index 4a0136349f71..dbef983eb425 100644 --- a/drivers/clk/xilinx/clk-xlnx-clock-wizard.c +++ b/drivers/clk/xilinx/clk-xlnx-clock-wizard.c @@ -663,8 +663,8 @@ static int clk_wzrd_determine_rate_all(struct clk_hw *hw, d = divider->d; o = divider->o; - req->rate = div_u64(req->best_parent_rate * (m * 1000 + divider->m_frac), - d * (o * 1000 + divider->o_frac)); + req->rate = mult_frac(req->best_parent_rate, m * 1000 + divider->m_frac, + d * (o * 1000 + divider->o_frac)); return 0; } From 22109b7329f9b3bf2fee73087c73ce46e9bf3751 Mon Sep 17 00:00:00 2001 From: Xuyang Dong Date: Fri, 5 Jun 2026 17:21:18 +0800 Subject: [PATCH 0022/1328] clk: eswin: Add CLK_IGNORE_UNUSED to NoC clock The gate_noc_nsp_clk provides the essential clock source for NPU, DSP, and PCIe subsystems. During kernel init, the clock framework attempts to disable unused clocks when clk_ignore_unused kernel parameter is not set. However, gate_noc_nsp_clk is required to remain enabled for these critical subsystems to function properly, causing PCIe boot failures when auto-disabled. Add CLK_IGNORE_UNUSED flag to gate_noc_nsp_clk to ensure it stays enabled even when clk_ignore_unused is not specified in kernel command line. Fixes: cd44f127c1d4 ("clk: eswin: Add eic7700 clock driver") Signed-off-by: Xuyang Dong Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/eswin/clk-eic7700.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/clk/eswin/clk-eic7700.c b/drivers/clk/eswin/clk-eic7700.c index be81d74192da..43a47fe16ab1 100644 --- a/drivers/clk/eswin/clk-eic7700.c +++ b/drivers/clk/eswin/clk-eic7700.c @@ -791,7 +791,8 @@ static struct eswin_clk_info eic7700_clks[] = { EIC7700_CLK_MUX_CPU_ROOT_3MUX1_GFREE, CLK_SET_RATE_PARENT, EIC7700_REG_OFFSET_U84, 27, 0), ESWIN_GATE_TYPE(EIC7700_CLK_GATE_NOC_NSP_CLK, "gate_noc_nsp_clk", - EIC7700_CLK_DIV_NOC_NSP_DYNM, CLK_SET_RATE_PARENT, + EIC7700_CLK_DIV_NOC_NSP_DYNM, + CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, EIC7700_REG_OFFSET_NOC, 31, 0), ESWIN_GATE_TYPE(EIC7700_CLK_GATE_BOOTSPI, "gate_clk_bootspi", EIC7700_CLK_MUX_BOOTSPI_CLK_2MUX1_GFREE, From fc8c21a2b5247a56c3b5e2a40e3c59b6805c2212 Mon Sep 17 00:00:00 2001 From: Xuyang Dong Date: Fri, 5 Jun 2026 14:10:02 +0800 Subject: [PATCH 0023/1328] dt-bindings: clock: Add ESWIN eic7700 HSP clock and reset generator Add bindings for the high-speed peripherals clock and reset generator on the ESWIN EIC7700 HSP. Acked-by: Conor Dooley Signed-off-by: Xuyang Dong Signed-off-by: Brian Masney --- .../bindings/clock/eswin,eic7700-hspcrg.yaml | 63 +++++++++++++++++++ MAINTAINERS | 5 +- .../dt-bindings/clock/eswin,eic7700-hspcrg.h | 33 ++++++++++ .../dt-bindings/reset/eswin,eic7700-hspcrg.h | 21 +++++++ 4 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 Documentation/devicetree/bindings/clock/eswin,eic7700-hspcrg.yaml create mode 100644 include/dt-bindings/clock/eswin,eic7700-hspcrg.h create mode 100644 include/dt-bindings/reset/eswin,eic7700-hspcrg.h diff --git a/Documentation/devicetree/bindings/clock/eswin,eic7700-hspcrg.yaml b/Documentation/devicetree/bindings/clock/eswin,eic7700-hspcrg.yaml new file mode 100644 index 000000000000..43df689ae647 --- /dev/null +++ b/Documentation/devicetree/bindings/clock/eswin,eic7700-hspcrg.yaml @@ -0,0 +1,63 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/clock/eswin,eic7700-hspcrg.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ESWIN EIC7700 HSP Clock and Reset Generator + +maintainers: + - Xuyang Dong + +description: + Clock and reset generator for the ESWIN EIC7700 HSP (high-speed peripherals). + +properties: + compatible: + const: eswin,eic7700-hspcrg + + reg: + maxItems: 1 + + clocks: + items: + - description: HSP configuration top clock + - description: MMC top clock + - description: SATA top clock + + clock-names: + items: + - const: cfg + - const: mmc + - const: sata + + '#clock-cells': + const: 1 + description: + See for valid indices. + + '#reset-cells': + const: 1 + description: + See for valid indices. + +required: + - compatible + - reg + - clocks + - clock-names + - '#clock-cells' + - '#reset-cells' + +additionalProperties: false + +examples: + - | + clock-controller@50440000 { + compatible = "eswin,eic7700-hspcrg"; + reg = <0x50440000 0x2000>; + clocks = <&clock 171>, <&clock 254>, <&clock 187>; + clock-names = "cfg", "mmc", "sata"; + #clock-cells = <1>; + #reset-cells = <1>; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 15011f5752a9..2b8151f366ec 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9660,9 +9660,10 @@ ESWIN EIC7700 CLOCK DRIVER M: Yifeng Huang M: Xuyang Dong S: Maintained -F: Documentation/devicetree/bindings/clock/eswin,eic7700-clock.yaml +F: Documentation/devicetree/bindings/clock/eswin,eic7700* F: drivers/clk/eswin/ -F: include/dt-bindings/clock/eswin,eic7700-clock.h +F: include/dt-bindings/clock/eswin,eic7700* +F: include/dt-bindings/reset/eswin,eic7700-hspcrg.h ET131X NETWORK DRIVER M: Mark Einon diff --git a/include/dt-bindings/clock/eswin,eic7700-hspcrg.h b/include/dt-bindings/clock/eswin,eic7700-hspcrg.h new file mode 100644 index 000000000000..1d1ff15c1154 --- /dev/null +++ b/include/dt-bindings/clock/eswin,eic7700-hspcrg.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ +/* + * Copyright 2026, Beijing ESWIN Computing Technology Co., Ltd.. + * All rights reserved. + * + * Device Tree binding constants for EIC7700 HSP clock controller. + * + * Authors: Xuyang Dong + */ + +#ifndef _DT_BINDINGS_ESWIN_EIC7700_HSPCRG_CLOCK_H_ +#define _DT_BINDINGS_ESWIN_EIC7700_HSPCRG_CLOCK_H_ + +#define EIC7700_HSP_CLK_FAC_CFG_DIV2 0 +#define EIC7700_HSP_CLK_FAC_CFG_DIV4 1 +#define EIC7700_HSP_CLK_FAC_MMC_DIV10 2 +#define EIC7700_HSP_CLK_MUX_EMMC_3MUX1 3 +#define EIC7700_HSP_CLK_MUX_SD0_3MUX1 4 +#define EIC7700_HSP_CLK_MUX_SD1_3MUX1 5 +#define EIC7700_HSP_CLK_MUX_EMMC_CQE_2MUX1 6 +#define EIC7700_HSP_CLK_MUX_SD0_CQE_2MUX1 7 +#define EIC7700_HSP_CLK_MUX_SD1_CQE_2MUX1 8 +#define EIC7700_HSP_CLK_GATE_MSHC0_TMR 9 +#define EIC7700_HSP_CLK_GATE_EMMC 10 +#define EIC7700_HSP_CLK_GATE_MSHC1_TMR 11 +#define EIC7700_HSP_CLK_GATE_SD0 12 +#define EIC7700_HSP_CLK_GATE_MSHC2_TMR 13 +#define EIC7700_HSP_CLK_GATE_SD1 14 +#define EIC7700_HSP_CLK_GATE_USB0 15 +#define EIC7700_HSP_CLK_GATE_USB1 16 +#define EIC7700_HSP_CLK_GATE_SATA 17 + +#endif /* _DT_BINDINGS_ESWIN_EIC7700_HSPCRG_CLOCK_H_ */ diff --git a/include/dt-bindings/reset/eswin,eic7700-hspcrg.h b/include/dt-bindings/reset/eswin,eic7700-hspcrg.h new file mode 100644 index 000000000000..413fcd08c701 --- /dev/null +++ b/include/dt-bindings/reset/eswin,eic7700-hspcrg.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ +/* + * Copyright 2026, Beijing ESWIN Computing Technology Co., Ltd.. + * All rights reserved. + * + * Device Tree binding constants for EIC7700 HSP reset controller. + * + * Authors: Xuyang Dong + */ + +#ifndef _DT_BINDINGS_ESWIN_EIC7700_HSPCRG_RESET_H_ +#define _DT_BINDINGS_ESWIN_EIC7700_HSPCRG_RESET_H_ + +#define EIC7700_HSP_RST_SATA_P0 0 +#define EIC7700_HSP_RST_SATA_PHY 1 +#define EIC7700_HSP_RST_USB0 2 +#define EIC7700_HSP_RST_USB1 3 +#define EIC7700_HSP_RST_USB0_PHY 4 +#define EIC7700_HSP_RST_USB1_PHY 5 + +#endif /* _DT_BINDINGS_ESWIN_EIC7700_HSPCRG_RESET_H_ */ From 8d92085fd79a7c13b72e8a05c5f89c800f575339 Mon Sep 17 00:00:00 2001 From: Xuyang Dong Date: Fri, 5 Jun 2026 14:13:34 +0800 Subject: [PATCH 0024/1328] clk: eswin: Add eic7700 HSP clock driver Add driver for the ESWIN EIC7700 high-speed peripherals system clock controller and register an auxiliary device for system reset controller which is named as "hsp-reset". Reviewed-by: Brian Masney Signed-off-by: Xuyang Dong Signed-off-by: Brian Masney --- drivers/clk/eswin/Kconfig | 13 ++ drivers/clk/eswin/Makefile | 1 + drivers/clk/eswin/clk-eic7700-hsp.c | 345 ++++++++++++++++++++++++++++ 3 files changed, 359 insertions(+) create mode 100644 drivers/clk/eswin/clk-eic7700-hsp.c diff --git a/drivers/clk/eswin/Kconfig b/drivers/clk/eswin/Kconfig index 0406ec499ec9..37857bd77335 100644 --- a/drivers/clk/eswin/Kconfig +++ b/drivers/clk/eswin/Kconfig @@ -13,3 +13,16 @@ config COMMON_CLK_EIC7700 SoC. The clock controller generates and supplies clocks to various peripherals within the SoC. Say yes here to support the clock controller on the EIC7700 SoC. + +config COMMON_CLK_EIC7700_HSP + tristate "EIC7700 HSP Clock Driver" + depends on ARCH_ESWIN || COMPILE_TEST + select AUXILIARY_BUS + select COMMON_CLK_EIC7700 + select RESET_EIC7700_HSP if RESET_CONTROLLER + select REGMAP_MMIO + help + This driver provides support for clock controller on ESWIN EIC7700 + HSP. The clock controller generates and supplies clocks to high + speed peripherals within the SoC. + Say yes here to support the clock controller on the EIC7700 HSP. diff --git a/drivers/clk/eswin/Makefile b/drivers/clk/eswin/Makefile index 4a7c2af82164..21a09a3396df 100644 --- a/drivers/clk/eswin/Makefile +++ b/drivers/clk/eswin/Makefile @@ -6,3 +6,4 @@ obj-$(CONFIG_COMMON_CLK_ESWIN) += clk.o obj-$(CONFIG_COMMON_CLK_EIC7700) += clk-eic7700.o +obj-$(CONFIG_COMMON_CLK_EIC7700_HSP) += clk-eic7700-hsp.o diff --git a/drivers/clk/eswin/clk-eic7700-hsp.c b/drivers/clk/eswin/clk-eic7700-hsp.c new file mode 100644 index 000000000000..6ddb8fbd605e --- /dev/null +++ b/drivers/clk/eswin/clk-eic7700-hsp.c @@ -0,0 +1,345 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2026, Beijing ESWIN Computing Technology Co., Ltd.. + * All rights reserved. + * + * ESWIN EIC7700 HSP Clock Driver + * + * Authors: Xuyang Dong + */ + +#include +#include +#include +#include + +#include + +#include "common.h" + +#define EIC7700_HSP_SATA_REG 0x300 +#define EIC7700_HSP_MSHC0_REG 0x510 +#define EIC7700_HSP_MSHC1_REG 0x610 +#define EIC7700_HSP_MSHC2_REG 0x710 +#define EIC7700_HSP_USB0_REG 0x800 +#define EIC7700_HSP_USB0_REF_REG 0x83c +#define EIC7700_HSP_USB1_REG 0x900 +#define EIC7700_HSP_USB1_REF_REG 0x93c + +#define USB_REF_XTAL24M 0x2a +#define EIC7700_HSP_NR_CLKS (EIC7700_HSP_CLK_GATE_SATA + 1) + +struct eic7700_hsp_clk_gate { + struct clk_hw hw; + unsigned int id; + struct regmap *regmap; + unsigned int reg; + unsigned int ref_reg; + const char *name; + const struct clk_parent_data *parent_data; + unsigned long flags; + unsigned int offset; + unsigned int ref_offset; + u8 bit_idx; +}; + +static const struct regmap_config eic7700_hsp_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .max_register = 0x1ffc, + .reg_stride = 4, + .fast_io = true, + .use_raw_spinlock = true, +}; + +static inline struct eic7700_hsp_clk_gate *to_gate_clk(struct clk_hw *hw) +{ + return container_of(hw, struct eic7700_hsp_clk_gate, hw); +} + +#define EIC7700_HSP_GATE(_id, _name, _pdata, _flags, _offset, _idx, \ + _ref_offset) \ + { \ + .id = _id, \ + .name = _name, \ + .parent_data = _pdata, \ + .flags = _flags, \ + .offset = _offset, \ + .ref_offset = _ref_offset, \ + .bit_idx = _idx, \ + } + +static void hsp_clk_gate_endisable(struct clk_hw *hw, bool enable) +{ + struct eic7700_hsp_clk_gate *gate = to_gate_clk(hw); + + if (enable) { + /* + * Hardware bug: The USB reference clock must be 24MHz. + * The default register value after reset is invalid. + * Workaround: Rewrite the correct value before enabling + * the USB gate clock. + */ + regmap_update_bits(gate->regmap, gate->ref_reg, 0x3f, + USB_REF_XTAL24M); + } + regmap_assign_bits(gate->regmap, gate->reg, BIT(gate->bit_idx), enable); +} + +static int hsp_clk_gate_enable(struct clk_hw *hw) +{ + hsp_clk_gate_endisable(hw, true); + + return 0; +} + +static void hsp_clk_gate_disable(struct clk_hw *hw) +{ + hsp_clk_gate_endisable(hw, false); +} + +static int hsp_clk_gate_is_enabled(struct clk_hw *hw) +{ + struct eic7700_hsp_clk_gate *gate = to_gate_clk(hw); + unsigned int val; + int ret; + + ret = regmap_read(gate->regmap, gate->reg, &val); + if (ret != 0) + return ret; + + return !!(val & BIT(gate->bit_idx)); +} + +static const struct clk_ops hsp_clk_gate_ops = { + .enable = hsp_clk_gate_enable, + .disable = hsp_clk_gate_disable, + .is_enabled = hsp_clk_gate_is_enabled, +}; + +static struct clk_hw * +hsp_clk_register_gate(struct device *dev, unsigned int id, const char *name, + const struct clk_parent_data *parent_data, + unsigned long flags, struct regmap *regmap, + unsigned int reg, unsigned int ref_reg, u8 bit_idx) +{ + struct eic7700_hsp_clk_gate *gate; + struct clk_init_data init = {}; + struct clk_hw *hw; + int ret; + + gate = devm_kzalloc(dev, sizeof(*gate), GFP_KERNEL); + if (!gate) + return ERR_PTR(-ENOMEM); + + init.name = name; + init.ops = &hsp_clk_gate_ops; + init.flags = flags; + init.parent_data = parent_data; + init.num_parents = 1; + + gate->id = id; + gate->regmap = regmap; + gate->reg = reg; + gate->ref_reg = ref_reg; + gate->bit_idx = bit_idx; + gate->hw.init = &init; + + hw = &gate->hw; + ret = devm_clk_hw_register(dev, hw); + if (ret) + hw = ERR_PTR(ret); + + return hw; +} + +static const struct clk_parent_data hsp_cfg[] = { + { .index = 0 } +}; + +static const struct clk_parent_data hsp_mmc[] = { + { .index = 1 } +}; + +static const struct clk_parent_data hsp_usb_sata[] = { + { .index = 2 } +}; + +static struct eswin_fixed_factor_clock eic7700_hsp_factor_clks[] = { + ESWIN_FACTOR(EIC7700_HSP_CLK_FAC_CFG_DIV2, "factor_hsp_cfg_div2", + hsp_cfg, 1, 2, 0), + ESWIN_FACTOR(EIC7700_HSP_CLK_FAC_CFG_DIV4, "factor_hsp_cfg_div4", + hsp_cfg, 1, 4, 0), + ESWIN_FACTOR(EIC7700_HSP_CLK_FAC_MMC_DIV10, "factor_hsp_mmc_div10", + hsp_mmc, 1, 10, 0), +}; + +static struct eswin_gate_clock eic7700_hsp_gate_clks[] = { + ESWIN_GATE(EIC7700_HSP_CLK_GATE_SATA, "gate_clk_hsp_sata", hsp_usb_sata, + CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, + EIC7700_HSP_SATA_REG, 28, 0), + ESWIN_GATE(EIC7700_HSP_CLK_GATE_MSHC0_TMR, "gate_clk_hsp_mshc0_tmr", + hsp_mmc, CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, + EIC7700_HSP_MSHC0_REG, 8, 0), + ESWIN_GATE(EIC7700_HSP_CLK_GATE_MSHC1_TMR, "gate_clk_hsp_mshc1_tmr", + hsp_mmc, CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, + EIC7700_HSP_MSHC1_REG, 8, 0), + ESWIN_GATE(EIC7700_HSP_CLK_GATE_MSHC2_TMR, "gate_clk_hsp_mshc2_tmr", + hsp_mmc, CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, + EIC7700_HSP_MSHC2_REG, 8, 0), +}; + +static struct eic7700_hsp_clk_gate eic7700_hsp_spec_gate_clks[] = { + EIC7700_HSP_GATE(EIC7700_HSP_CLK_GATE_USB0, "gate_clk_hsp_usb0", + hsp_usb_sata, CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, + EIC7700_HSP_USB0_REG, 28, EIC7700_HSP_USB0_REF_REG), + EIC7700_HSP_GATE(EIC7700_HSP_CLK_GATE_USB1, "gate_clk_hsp_usb1", + hsp_usb_sata, CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, + EIC7700_HSP_USB1_REG, 28, EIC7700_HSP_USB1_REF_REG), +}; + +static const struct clk_parent_data mux_mmc_3mux1_p[] = { + { .fw_name = "cfg" }, + { .hw = &eic7700_hsp_factor_clks[0].hw }, + { .hw = &eic7700_hsp_factor_clks[1].hw }, +}; + +static const struct clk_parent_data mux_mmc_2mux1_p[] = { + { .fw_name = "mmc" }, + { .hw = &eic7700_hsp_factor_clks[2].hw }, +}; + +static u32 mux_mmc_3mux1_tbl[] = { 0x0, 0x1, 0x3 }; + +static struct eswin_mux_clock eic7700_hsp_mux_clks[] = { + ESWIN_MUX_TBL(EIC7700_HSP_CLK_MUX_EMMC_3MUX1, "mux_hsp_emmc_3mux1", + mux_mmc_3mux1_p, ARRAY_SIZE(mux_mmc_3mux1_p), + CLK_SET_RATE_PARENT, EIC7700_HSP_MSHC0_REG, 16, 2, 0, + mux_mmc_3mux1_tbl), + ESWIN_MUX_TBL(EIC7700_HSP_CLK_MUX_SD0_3MUX1, "mux_hsp_sd0_3mux1", + mux_mmc_3mux1_p, ARRAY_SIZE(mux_mmc_3mux1_p), + CLK_SET_RATE_PARENT, EIC7700_HSP_MSHC1_REG, 16, 2, 0, + mux_mmc_3mux1_tbl), + ESWIN_MUX_TBL(EIC7700_HSP_CLK_MUX_SD1_3MUX1, "mux_hsp_sd1_3mux1", + mux_mmc_3mux1_p, ARRAY_SIZE(mux_mmc_3mux1_p), + CLK_SET_RATE_PARENT, EIC7700_HSP_MSHC2_REG, 16, 2, 0, + mux_mmc_3mux1_tbl), + ESWIN_MUX(EIC7700_HSP_CLK_MUX_EMMC_CQE_2MUX1, "mux_hsp_emmc_cqe_2mux1", + mux_mmc_2mux1_p, ARRAY_SIZE(mux_mmc_2mux1_p), + CLK_SET_RATE_PARENT, EIC7700_HSP_MSHC0_REG, 0, 1, 0), + ESWIN_MUX(EIC7700_HSP_CLK_MUX_SD0_CQE_2MUX1, "mux_hsp_sd0_cqe_2mux1", + mux_mmc_2mux1_p, ARRAY_SIZE(mux_mmc_2mux1_p), + CLK_SET_RATE_PARENT, EIC7700_HSP_MSHC1_REG, 0, 1, 0), + ESWIN_MUX(EIC7700_HSP_CLK_MUX_SD1_CQE_2MUX1, "mux_hsp_sd1_cqe_2mux1", + mux_mmc_2mux1_p, ARRAY_SIZE(mux_mmc_2mux1_p), + CLK_SET_RATE_PARENT, EIC7700_HSP_MSHC2_REG, 0, 1, 0), +}; + +static struct eswin_clk_info eic7700_hsp_clks[] = { + ESWIN_GATE_TYPE(EIC7700_HSP_CLK_GATE_EMMC, "gate_clk_hsp_emmc", + EIC7700_HSP_CLK_MUX_EMMC_3MUX1, + CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, + EIC7700_HSP_MSHC0_REG, 24, 0), + ESWIN_GATE_TYPE(EIC7700_HSP_CLK_GATE_SD0, "gate_clk_hsp_sd0", + EIC7700_HSP_CLK_MUX_SD0_3MUX1, + CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, + EIC7700_HSP_MSHC1_REG, 24, 0), + ESWIN_GATE_TYPE(EIC7700_HSP_CLK_GATE_SD1, "gate_clk_hsp_sd1", + EIC7700_HSP_CLK_MUX_SD1_3MUX1, + CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, + EIC7700_HSP_MSHC2_REG, 24, 0), +}; + +static int eic7700_hsp_clk_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct auxiliary_device *adev; + struct eswin_clock_data *data; + struct regmap *regmap; + struct clk_hw *hw; + int i, ret; + + data = eswin_clk_init(pdev, EIC7700_HSP_NR_CLKS); + if (IS_ERR(data)) + return dev_err_probe(dev, PTR_ERR(data), + "failed to get clk data!\n"); + + regmap = devm_regmap_init_mmio(dev, data->base, + &eic7700_hsp_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), + "failed to get regmap!\n"); + + ret = eswin_clk_register_fixed_factor(dev, eic7700_hsp_factor_clks, + ARRAY_SIZE(eic7700_hsp_factor_clks), + data); + if (ret) + return dev_err_probe(dev, ret, + "failed to register fixed factor clock\n"); + + ret = eswin_clk_register_gate(dev, eic7700_hsp_gate_clks, + ARRAY_SIZE(eic7700_hsp_gate_clks), data); + if (ret) + return dev_err_probe(dev, ret, + "failed to register gate clock\n"); + + ret = eswin_clk_register_mux(dev, eic7700_hsp_mux_clks, + ARRAY_SIZE(eic7700_hsp_mux_clks), + data); + if (ret) + return dev_err_probe(dev, ret, + "failed to register mux clock\n"); + + ret = eswin_clk_register_clks(dev, eic7700_hsp_clks, + ARRAY_SIZE(eic7700_hsp_clks), data); + if (ret) + return dev_err_probe(dev, ret, + "failed to register clock\n"); + + for (i = 0; i < ARRAY_SIZE(eic7700_hsp_spec_gate_clks); i++) { + struct eic7700_hsp_clk_gate *gate; + + gate = &eic7700_hsp_spec_gate_clks[i]; + hw = hsp_clk_register_gate(dev, gate->id, gate->name, + gate->parent_data, gate->flags, + regmap, gate->offset, + gate->ref_offset, gate->bit_idx); + if (IS_ERR(hw)) + return dev_err_probe(dev, PTR_ERR(hw), + "failed to register gate clock\n"); + + data->clk_data.hws[gate->id] = hw; + } + + ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, + &data->clk_data); + if (ret) + return dev_err_probe(dev, ret, "add clk provider failed\n"); + + adev = devm_auxiliary_device_create(dev, "hsp-reset", NULL); + if (!adev) + return dev_err_probe(dev, -ENODEV, + "register hsp-reset device failed\n"); + + return 0; +} + +static const struct of_device_id eic7700_hsp_clock_dt_ids[] = { + { .compatible = "eswin,eic7700-hspcrg", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, eic7700_hsp_clock_dt_ids); + +static struct platform_driver eic7700_hsp_clock_driver = { + .probe = eic7700_hsp_clk_probe, + .driver = { + .name = "eic7700-hsp-clock", + .of_match_table = eic7700_hsp_clock_dt_ids, + }, +}; + +module_platform_driver(eic7700_hsp_clock_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Xuyang Dong "); +MODULE_DESCRIPTION("ESWIN EIC7700 HSP clock controller driver"); From b37fe0ee3691b9da358cdfbbdad67874e562ac90 Mon Sep 17 00:00:00 2001 From: Xuyang Dong Date: Fri, 5 Jun 2026 14:14:17 +0800 Subject: [PATCH 0025/1328] reset: eswin: Add eic7700 HSP reset driver Add auxiliary driver to support ESWIN EIC7700 high-speed peripherals system. The reset controller is created using the auxiliary device framework and set up in the clock driver. Reviewed-by: Philipp Zabel Signed-off-by: Xuyang Dong Signed-off-by: Brian Masney --- drivers/reset/Kconfig | 11 +++ drivers/reset/Makefile | 1 + drivers/reset/reset-eic7700-hsp.c | 113 ++++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+) create mode 100644 drivers/reset/reset-eic7700-hsp.c diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig index d009eb0849a3..f63e89ed6a4e 100644 --- a/drivers/reset/Kconfig +++ b/drivers/reset/Kconfig @@ -83,6 +83,17 @@ config RESET_EIC7700 The driver supports eic7700 series chips and provides functionality for asserting and deasserting resets on the chip. +config RESET_EIC7700_HSP + tristate "EIC7700 HSP Reset controller" + depends on ARCH_ESWIN || COMPILE_TEST + select AUXILIARY_BUS + help + This enables the HSP reset controller driver for ESWIN SoCs. This + driver is specific to ESWIN SoCs and should only be enabled if using + such hardware. + The driver supports EIC7700 series chips and provides functionality + for asserting and deasserting resets on the chip. + config RESET_EYEQ bool "Mobileye EyeQ reset controller" depends on EYEQ || COMPILE_TEST diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile index 3e52569bd276..a75af831ef58 100644 --- a/drivers/reset/Makefile +++ b/drivers/reset/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_RESET_BERLIN) += reset-berlin.o obj-$(CONFIG_RESET_BRCMSTB) += reset-brcmstb.o obj-$(CONFIG_RESET_BRCMSTB_RESCAL) += reset-brcmstb-rescal.o obj-$(CONFIG_RESET_EIC7700) += reset-eic7700.o +obj-$(CONFIG_RESET_EIC7700_HSP) += reset-eic7700-hsp.o obj-$(CONFIG_RESET_EYEQ) += reset-eyeq.o obj-$(CONFIG_RESET_GPIO) += reset-gpio.o obj-$(CONFIG_RESET_HSDK) += reset-hsdk.o diff --git a/drivers/reset/reset-eic7700-hsp.c b/drivers/reset/reset-eic7700-hsp.c new file mode 100644 index 000000000000..1575ee4a20d6 --- /dev/null +++ b/drivers/reset/reset-eic7700-hsp.c @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2026, Beijing ESWIN Computing Technology Co., Ltd.. + * All rights reserved. + * + * ESWIN EIC7700 HSP Reset Driver + * + * Authors: Xuyang Dong + */ + +#include +#include +#include +#include +#include + +#include + +/** + * struct eic7700_hsp_reset_data - reset controller information structure + * @rcdev: reset controller entity + * @regmap: regmap handle containing the memory-mapped reset registers + */ +struct eic7700_hsp_reset_data { + struct reset_controller_dev rcdev; + struct regmap *regmap; +}; + +struct eic7700_hsp_reg { + u32 reg; + u32 bit; + bool active_low; +}; + +static inline struct eic7700_hsp_reset_data * +to_eic7700_hsp_reset(struct reset_controller_dev *rcdev) +{ + return container_of(rcdev, struct eic7700_hsp_reset_data, rcdev); +} + +static const struct eic7700_hsp_reg eic7700_hsp_reset[] = { + [EIC7700_HSP_RST_SATA_P0] = {0x340, BIT(0), false}, + [EIC7700_HSP_RST_SATA_PHY] = {0x340, BIT(1), false}, + [EIC7700_HSP_RST_USB0] = {0x800, BIT(24), true}, + [EIC7700_HSP_RST_USB1] = {0x900, BIT(24), true}, + [EIC7700_HSP_RST_USB0_PHY] = {0x800, BIT(25), false}, + [EIC7700_HSP_RST_USB1_PHY] = {0x900, BIT(25), false}, +}; + +static int eic7700_hsp_reset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct eic7700_hsp_reset_data *data = to_eic7700_hsp_reset(rcdev); + + return regmap_assign_bits(data->regmap, eic7700_hsp_reset[id].reg, + eic7700_hsp_reset[id].bit, + !eic7700_hsp_reset[id].active_low); +} + +static int eic7700_hsp_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct eic7700_hsp_reset_data *data = to_eic7700_hsp_reset(rcdev); + + return regmap_assign_bits(data->regmap, eic7700_hsp_reset[id].reg, + eic7700_hsp_reset[id].bit, + eic7700_hsp_reset[id].active_low); +} + +static const struct reset_control_ops eic7700_hsp_reset_ops = { + .assert = eic7700_hsp_reset_assert, + .deassert = eic7700_hsp_reset_deassert, +}; + +static int eic7700_hsp_reset_probe(struct auxiliary_device *adev, + const struct auxiliary_device_id *id) +{ + struct eic7700_hsp_reset_data *data; + struct device *dev = &adev->dev; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->regmap = dev_get_regmap(dev->parent, NULL); + if (!data->regmap) + return dev_err_probe(dev, -ENODEV, "failed to get regmap!\n"); + + data->rcdev.owner = THIS_MODULE; + data->rcdev.ops = &eic7700_hsp_reset_ops; + data->rcdev.of_node = dev->parent->of_node; + data->rcdev.dev = dev; + data->rcdev.nr_resets = ARRAY_SIZE(eic7700_hsp_reset); + + return devm_reset_controller_register(dev, &data->rcdev); +} + +static const struct auxiliary_device_id eic7700_hsp_reset_ids[] = { + { .name = "clk_eic7700_hsp.hsp-reset", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(auxiliary, eic7700_hsp_reset_ids); + +static struct auxiliary_driver eic7700_hsp_reset_driver = { + .probe = eic7700_hsp_reset_probe, + .id_table = eic7700_hsp_reset_ids, +}; + +module_auxiliary_driver(eic7700_hsp_reset_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Xuyang Dong "); +MODULE_DESCRIPTION("ESWIN EIC7700 HSP Reset Controller Driver"); From 50ca555d6ae8205374e406b411ae0e8e9447568d Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Tue, 2 Jun 2026 21:02:42 +0100 Subject: [PATCH 0026/1328] clk: divider: Add KUnit tests for clk_divider_bestdiv() ULONG_MAX handling Add KUnit tests to verify the behaviour of clk_divider_bestdiv() when clk_round_rate() is called with ULONG_MAX, which is the canonical way to probe the maximum rate a clock can produce. Two test cases are introduced: - clk_divider_bestdiv_ulong_max_returns_max_rate: registers a 1 GHz fixed-rate parent driving a table-based divider whose smallest entry is div=2 (entries: 2, 4, 8). Calls clk_hw_round_rate(div_hw, ULONG_MAX) and checks the result. - clk_divider_bestdiv_mux_ulong_max_returns_max_rate: places a two-input mux (4 GHz and 2 GHz fixed-rate parents, CLK_SET_RATE_PARENT) ahead of the same table-based divider to verify correct parent selection under ULONG_MAX. Both tests use an explicit clk_div_table with a minimum divider of 2 so that the pre-loop maxdiv clamping in clk_divider_bestdiv(): maxdiv = min(ULONG_MAX / rate, maxdiv); clamps maxdiv to 1, causing _next_div() to return 2 on the first iteration and skip the loop body entirely. This makes bestdiv fall back to the maximum divider, returning the minimum rate rather than the maximum. The expected values intentionally reflect the buggy output: - test 1: PARENT_RATE_1GHZ / 8 (minimum rate, not maximum) - test 2: 0 (invalid, loop never populated bestdiv) These will be corrected to PARENT_RATE_1GHZ / 2 and PARENT_RATE_4GHZ / 2 respectively once the fix to clk_divider_bestdiv() is applied. Signed-off-by: Lad Prabhakar Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/.kunitconfig | 1 + drivers/clk/Kconfig | 8 ++ drivers/clk/Makefile | 1 + drivers/clk/clk-divider_test.c | 153 +++++++++++++++++++++++++++++++++ 4 files changed, 163 insertions(+) create mode 100644 drivers/clk/clk-divider_test.c diff --git a/drivers/clk/.kunitconfig b/drivers/clk/.kunitconfig index 8a0ea41934a2..ea05b9a28c80 100644 --- a/drivers/clk/.kunitconfig +++ b/drivers/clk/.kunitconfig @@ -4,6 +4,7 @@ CONFIG_OF=y CONFIG_OF_OVERLAY=y CONFIG_COMMON_CLK=y CONFIG_CLK_KUNIT_TEST=y +CONFIG_CLK_DIVIDER_KUNIT_TEST=y CONFIG_CLK_FIXED_RATE_KUNIT_TEST=y CONFIG_CLK_GATE_KUNIT_TEST=y CONFIG_CLK_FD_KUNIT_TEST=y diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index 1717ce75a907..3d94c7611356 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -579,4 +579,12 @@ config CLK_FD_KUNIT_TEST help Kunit test for the clk-fractional-divider type. +config CLK_DIVIDER_KUNIT_TEST + tristate "KUnit tests for clk divider bestdiv" if !KUNIT_ALL_TESTS + depends on KUNIT + depends on !S390 + default KUNIT_ALL_TESTS + help + Kunit test for the clk-divider type. + endif diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index c2ae700ec0f2..d042b4aa9c0f 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -31,6 +31,7 @@ clk-test-y := clk_test.o \ kunit_clk_hw_get_dev_of_node.dtbo.o \ kunit_clk_parent_data_test.dtbo.o obj-$(CONFIG_COMMON_CLK) += clk-divider.o +obj-$(CONFIG_CLK_DIVIDER_KUNIT_TEST) += clk-divider_test.o obj-$(CONFIG_COMMON_CLK) += clk-fixed-factor.o obj-$(CONFIG_COMMON_CLK) += clk-fixed-rate.o obj-$(CONFIG_CLK_FIXED_RATE_KUNIT_TEST) += clk-fixed-rate-test.o diff --git a/drivers/clk/clk-divider_test.c b/drivers/clk/clk-divider_test.c new file mode 100644 index 000000000000..cb56931456b8 --- /dev/null +++ b/drivers/clk/clk-divider_test.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit tests for clk_divider_bestdiv() + */ +#include +#include +#include +#include +#include + +#define PARENT_RATE_1GHZ GIGA +#define PARENT_RATE_2GHZ (2 * GIGA) +#define PARENT_RATE_4GHZ (4 * GIGA) + +KUNIT_DEFINE_ACTION_WRAPPER(clk_hw_unregister_fixed_rate_wrapper, + clk_hw_unregister_fixed_rate, struct clk_hw *); +KUNIT_DEFINE_ACTION_WRAPPER(clk_hw_unregister_divider_wrapper, + clk_hw_unregister_divider, struct clk_hw *); +KUNIT_DEFINE_ACTION_WRAPPER(clk_hw_unregister_mux_wrapper, + clk_hw_unregister_mux, struct clk_hw *); + +static const struct clk_div_table bestdiv_table[] = { + { .val = 0, .div = 2 }, + { .val = 1, .div = 4 }, + { .val = 2, .div = 8 }, + { /* sentinel */ } +}; + +/* + * Test that clk_round_rate(clk, ULONG_MAX) returns the maximum achievable + * rate for a divider clock. + */ +static void clk_divider_bestdiv_ulong_max_returns_max_rate(struct kunit *test) +{ + struct clk_hw *parent_hw, *div_hw; + unsigned long rate; + u32 *fake_reg; + + fake_reg = kunit_kzalloc(test, sizeof(*fake_reg), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fake_reg); + + parent_hw = clk_hw_register_fixed_rate(NULL, "bestdiv-parent", + NULL, 0, PARENT_RATE_1GHZ); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, parent_hw); + KUNIT_ASSERT_EQ(test, 0, + kunit_add_action_or_reset(test, clk_hw_unregister_fixed_rate_wrapper, + parent_hw)); + + div_hw = clk_hw_register_divider_table(NULL, "bestdiv-div", + "bestdiv-parent", + CLK_SET_RATE_PARENT, + (void __iomem __force *)fake_reg, + 0, 2, 0, bestdiv_table, NULL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, div_hw); + KUNIT_ASSERT_EQ(test, 0, + kunit_add_action_or_reset(test, clk_hw_unregister_divider_wrapper, + div_hw)); + + /* + * ULONG_MAX is the canonical way to probe the maximum rate a clock + * can produce. + */ + rate = clk_hw_round_rate(div_hw, ULONG_MAX); + KUNIT_EXPECT_EQ(test, rate, PARENT_RATE_1GHZ / 8); +} + +/* + * Test that clk_round_rate(clk, ULONG_MAX) returns the correct maximum rate + * when a mux clock sits between a divider and its parent candidates. + * + * Topology: + * + * [fixed 4 GHz] --\ + * +--> [mux CLK_SET_RATE_PARENT] --> [div {2,4,8} CLK_SET_RATE_PARENT] + * [fixed 2 GHz] --/ + * + */ +static void clk_divider_bestdiv_mux_ulong_max_returns_max_rate(struct kunit *test) +{ + static const char * const mux_parents[] = { + "bestdiv-mux-parent-a", + "bestdiv-mux-parent-b", + }; + struct clk_hw *parent_a_hw, *parent_b_hw, *mux_hw, *div_hw; + u32 *fake_reg_mux, *fake_reg_div; + unsigned long rate; + + fake_reg_mux = kunit_kzalloc(test, sizeof(*fake_reg_mux), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fake_reg_mux); + + fake_reg_div = kunit_kzalloc(test, sizeof(*fake_reg_div), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fake_reg_div); + + /* Higher-rate parent: the mux should select this for ULONG_MAX. */ + parent_a_hw = clk_hw_register_fixed_rate(NULL, "bestdiv-mux-parent-a", + NULL, 0, PARENT_RATE_4GHZ); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, parent_a_hw); + KUNIT_ASSERT_EQ(test, 0, + kunit_add_action_or_reset(test, clk_hw_unregister_fixed_rate_wrapper, + parent_a_hw)); + + /* Lower-rate parent: should not be selected. */ + parent_b_hw = clk_hw_register_fixed_rate(NULL, "bestdiv-mux-parent-b", + NULL, 0, PARENT_RATE_2GHZ); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, parent_b_hw); + KUNIT_ASSERT_EQ(test, 0, + kunit_add_action_or_reset(test, clk_hw_unregister_fixed_rate_wrapper, + parent_b_hw)); + + /* + * 1-bit mux register selects between the two parents. + * CLK_SET_RATE_PARENT allows the divider's rate request to + * propagate into clk_mux_determine_rate(). + */ + mux_hw = clk_hw_register_mux(NULL, "bestdiv-mux", + mux_parents, ARRAY_SIZE(mux_parents), + CLK_SET_RATE_PARENT, + (void __iomem __force *)fake_reg_mux, + 0, 1, 0, NULL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, mux_hw); + KUNIT_ASSERT_EQ(test, 0, + kunit_add_action_or_reset(test, clk_hw_unregister_mux_wrapper, + mux_hw)); + + div_hw = clk_hw_register_divider_table(NULL, "bestdiv-mux-div", + "bestdiv-mux", + CLK_SET_RATE_PARENT, + (void __iomem __force *)fake_reg_div, + 0, 2, 0, bestdiv_table, NULL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, div_hw); + KUNIT_ASSERT_EQ(test, 0, + kunit_add_action_or_reset(test, clk_hw_unregister_divider_wrapper, + div_hw)); + + rate = clk_hw_round_rate(div_hw, ULONG_MAX); + KUNIT_EXPECT_EQ(test, rate, 0); +} + +static struct kunit_case clk_divider_bestdiv_test_cases[] = { + KUNIT_CASE(clk_divider_bestdiv_ulong_max_returns_max_rate), + KUNIT_CASE(clk_divider_bestdiv_mux_ulong_max_returns_max_rate), + {} +}; + +static struct kunit_suite clk_divider_bestdiv_test_suite = { + .name = "clk_divider_bestdiv", + .test_cases = clk_divider_bestdiv_test_cases, +}; + +kunit_test_suite(clk_divider_bestdiv_test_suite); + +MODULE_DESCRIPTION("KUnit tests for clk divider"); +MODULE_LICENSE("GPL"); From e129a400d8a787e5968e30309fa72859580b263a Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Tue, 2 Jun 2026 21:02:43 +0100 Subject: [PATCH 0027/1328] clk: divider: Fix clk_divider_bestdiv() returning min rate for large rate requests clk_divider_bestdiv() clamps maxdiv using: maxdiv = min(ULONG_MAX / rate, maxdiv); to avoid overflow in rate * i. However, requests like clk_round_rate(clk, ULONG_MAX), which are used to determine the maximum supported rate of a clock, result in maxdiv being clamped to 1. If no valid divider of 1 exists in the table the loop is never entered and bestdiv falls back to the maximum divider with the minimum parent rate, causing clk_round_rate(clk, ULONG_MAX) to incorrectly return the minimum supported rate instead of the maximum. Fix this by removing the pre-loop maxdiv clamping and replacing the unprotected rate * i multiplication with check_mul_overflow(). Guard the exact-match short-circuit with !overflow to prevent a clamped target_parent_rate of ULONG_MAX from falsely matching parent_rate_saved and causing premature loop exit. Break out of the loop after evaluating the first overflowing divider since clk_hw_round_rate(parent, ULONG_MAX) returns a constant for all subsequent iterations, meaning no better candidate can be found, and continuing would cause exponential recursive calls in chained divider clocks. Update the KUnit test expected values to reflect the corrected behaviour: - clk_divider_bestdiv_ulong_max_returns_max_rate: PARENT_RATE_1GHZ / 8 (minimum rate, pre-fix) -> PARENT_RATE_1GHZ / 2 (maximum rate) - clk_divider_bestdiv_mux_ulong_max_returns_max_rate: 0 (invalid, pre-fix) -> PARENT_RATE_4GHZ / 2 (maximum rate with mux selecting the 4 GHz parent and applying the smallest table divider of 2) Signed-off-by: Lad Prabhakar Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/clk-divider.c | 25 +++++++++++++++++-------- drivers/clk/clk-divider_test.c | 4 ++-- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c index b3b485d23ea8..f68216c65d3d 100644 --- a/drivers/clk/clk-divider.c +++ b/drivers/clk/clk-divider.c @@ -15,6 +15,7 @@ #include #include #include +#include /* * DOC: basic adjustable divider clock that cannot gate @@ -301,6 +302,7 @@ static int clk_divider_bestdiv(struct clk_hw *hw, struct clk_hw *parent, int i, bestdiv = 0; unsigned long parent_rate, best = 0, now, maxdiv; unsigned long parent_rate_saved = *best_parent_rate; + unsigned long target_parent_rate; if (!rate) rate = 1; @@ -315,15 +317,11 @@ static int clk_divider_bestdiv(struct clk_hw *hw, struct clk_hw *parent, return bestdiv; } - /* - * The maximum divider we can use without overflowing - * unsigned long in rate * i below - */ - maxdiv = min(ULONG_MAX / rate, maxdiv); - for (i = _next_div(table, 0, flags); i <= maxdiv; i = _next_div(table, i, flags)) { - if (rate * i == parent_rate_saved) { + bool overflow = check_mul_overflow(rate, (unsigned long)i, &target_parent_rate); + + if (!overflow && target_parent_rate == parent_rate_saved) { /* * It's the most ideal case if the requested rate can be * divided from parent clock without needing to change @@ -332,13 +330,24 @@ static int clk_divider_bestdiv(struct clk_hw *hw, struct clk_hw *parent, *best_parent_rate = parent_rate_saved; return i; } - parent_rate = clk_hw_round_rate(parent, rate * i); + /* + * Clamp target_parent_rate to ULONG_MAX on overflow. The true + * required parent rate exceeds what can be represented, so ask + * the parent for the highest rate it can produce. There is no + * point continuing the loop past this since larger dividers + * only move further from the requested rate. + */ + if (overflow) + target_parent_rate = ULONG_MAX; + parent_rate = clk_hw_round_rate(parent, target_parent_rate); now = DIV_ROUND_UP_ULL((u64)parent_rate, i); if (_is_best_div(rate, now, best, flags)) { bestdiv = i; best = now; *best_parent_rate = parent_rate; } + if (overflow) + break; } if (!bestdiv) { diff --git a/drivers/clk/clk-divider_test.c b/drivers/clk/clk-divider_test.c index cb56931456b8..8ee1614128d0 100644 --- a/drivers/clk/clk-divider_test.c +++ b/drivers/clk/clk-divider_test.c @@ -61,7 +61,7 @@ static void clk_divider_bestdiv_ulong_max_returns_max_rate(struct kunit *test) * can produce. */ rate = clk_hw_round_rate(div_hw, ULONG_MAX); - KUNIT_EXPECT_EQ(test, rate, PARENT_RATE_1GHZ / 8); + KUNIT_EXPECT_EQ(test, rate, PARENT_RATE_1GHZ / 2); } /* @@ -133,7 +133,7 @@ static void clk_divider_bestdiv_mux_ulong_max_returns_max_rate(struct kunit *tes div_hw)); rate = clk_hw_round_rate(div_hw, ULONG_MAX); - KUNIT_EXPECT_EQ(test, rate, 0); + KUNIT_EXPECT_EQ(test, rate, PARENT_RATE_4GHZ / 2); } static struct kunit_case clk_divider_bestdiv_test_cases[] = { From ff29670735e0d9ac683529e316b8d4dcd09177ac Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Tue, 2 Jun 2026 09:43:34 +0100 Subject: [PATCH 0028/1328] clk: tegra: Support unique names for multi-socket platforms On multi-socket platforms each socket has its own BPMP which exposes the same clock names. Fix this by using the NUMA ID as a prefix for the clock names on multi-socket platforms. Use 'sizeof(info->name)' in the strscpy() and snprintf() functions to future proof against anyone changing the size of the 'name' array. Co-developed-by: Timo Alho Signed-off-by: Timo Alho Signed-off-by: Jon Hunter Reviewed-by: Brian Masney Reviewed-by: Thierry Reding Signed-off-by: Brian Masney --- drivers/clk/tegra/clk-bpmp.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/clk/tegra/clk-bpmp.c b/drivers/clk/tegra/clk-bpmp.c index f6d2b934228b..067a8555c4e7 100644 --- a/drivers/clk/tegra/clk-bpmp.c +++ b/drivers/clk/tegra/clk-bpmp.c @@ -367,7 +367,15 @@ static int tegra_bpmp_clk_get_info(struct tegra_bpmp *bpmp, unsigned int id, if (err < 0) return err; - strscpy(info->name, response.name, MRQ_CLK_NAME_MAXLEN); + if (dev_to_node(bpmp->dev) == NUMA_NO_NODE) { + strscpy(info->name, response.name, sizeof(info->name)); + } else { + err = snprintf(info->name, sizeof(info->name), "%d-%s", + dev_to_node(bpmp->dev), response.name); + if (WARN_ON(err >= sizeof(info->name))) + return -E2BIG; + } + info->num_parents = response.num_parents; for (i = 0; i < info->num_parents; i++) From d4d0d335e8d37c6c7e1c5664c7664288eca5a09b Mon Sep 17 00:00:00 2001 From: William Theesfeld Date: Mon, 1 Jun 2026 16:35:00 -0400 Subject: [PATCH 0029/1328] clk: zynq: handle kasprintf() failure in periph_clk registration zynq_clk_register_periph_clk() ignores the return value of the two kasprintf() calls used to build the mux and divider clock names, and passes the resulting (possibly NULL) pointers straight into clk_register_mux(), clk_register_divider() and clk_register_gate() as the clock '"'name'"' argument. On allocation failure that name later gets dereferenced by the clock framework (e.g. in debugfs name formatting), causing a NULL-pointer dereference. Check both kasprintf() returns. On failure unwind any allocated name buffer and the spinlock, then fall through to the existing err label which sets clks[] to ERR_PTR(-ENOMEM). Freeing the spinlock on the error path is correct here because no clk_register_*() call has had a chance to take ownership of it; the success path intentionally hands it off to the registered clocks. The neighbouring zynq_clk_register_fclk() in the same file already uses this per-allocation goto-label cleanup pattern; this change brings periph_clk into line with it. Signed-off-by: William Theesfeld Reviewed-by: Brian Masney Reviewed-by: Michal Simek Signed-off-by: Brian Masney --- drivers/clk/zynq/clkc.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/clk/zynq/clkc.c b/drivers/clk/zynq/clkc.c index 6a22cbbc1235..777187744f4e 100644 --- a/drivers/clk/zynq/clkc.c +++ b/drivers/clk/zynq/clkc.c @@ -186,7 +186,11 @@ static void __init zynq_clk_register_periph_clk(enum zynq_clk clk0, spin_lock_init(lock); mux_name = kasprintf(GFP_KERNEL, "%s_mux", clk_name0); + if (!mux_name) + goto err_mux_name; div_name = kasprintf(GFP_KERNEL, "%s_div", clk_name0); + if (!div_name) + goto err_div_name; clk_register_mux(NULL, mux_name, parents, 4, CLK_SET_RATE_NO_REPARENT, clk_ctrl, 4, 2, 0, lock); @@ -205,6 +209,10 @@ static void __init zynq_clk_register_periph_clk(enum zynq_clk clk0, return; +err_div_name: + kfree(mux_name); +err_mux_name: + kfree(lock); err: clks[clk0] = ERR_PTR(-ENOMEM); if (two_gates) From 483932d86b0063a9a7f0eefa4b2e557d753fe086 Mon Sep 17 00:00:00 2001 From: Yu-Chun Lin Date: Sun, 31 May 2026 21:18:25 +0800 Subject: [PATCH 0030/1328] clk: bcm: kona: Stop defaulting to parent index 0 on error Stop defaulting to 0 on BAD_CLK_INDEX (U8_MAX). Passing the out-of-bounds index directly allows the clk core to fail gracefully during parent lookup. Link: https://lore.kernel.org/lkml/177431305509.5403.15386021337517970667@lazor/ Signed-off-by: Yu-Chun Lin Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/bcm/clk-kona.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/clk/bcm/clk-kona.c b/drivers/clk/bcm/clk-kona.c index 0171e6b2bfca..d6f5872d7d6a 100644 --- a/drivers/clk/bcm/clk-kona.c +++ b/drivers/clk/bcm/clk-kona.c @@ -1098,8 +1098,7 @@ static u8 kona_peri_clk_get_parent(struct clk_hw *hw) index = selector_read_index(bcm_clk->ccu, &data->sel); - /* Not all callers would handle an out-of-range value gracefully */ - return index == BAD_CLK_INDEX ? 0 : index; + return index; } static int kona_peri_clk_set_rate(struct clk_hw *hw, unsigned long rate, From 4f04b40ae7e27970041375193b312274b8293ae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Thu, 28 May 2026 12:29:38 +0200 Subject: [PATCH 0031/1328] clk: Use named initializers for platform_device_id arrays MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Named initializers are better readable and more robust to changes of the struct definition. This robustness is relevant for a planned change to struct platform_device_id replacing .driver_data by an anonymous union. While touching these arrays unify spacing and usage of commas. Signed-off-by: Uwe Kleine-König (The Capable Hub) Reviewed-by: Matti Vaittinen Reviewed-by: Brian Masney Reviewed-by: Peter Griffin Reviewed-by: Tudor Ambarus Signed-off-by: Brian Masney --- drivers/clk/clk-bd718x7.c | 12 ++++++------ drivers/clk/clk-max77686.c | 8 ++++---- drivers/clk/clk-s2mps11.c | 12 ++++++------ drivers/clk/samsung/clk-acpm.c | 4 ++-- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/drivers/clk/clk-bd718x7.c b/drivers/clk/clk-bd718x7.c index 1cae974e6d1d..8ddb50c67757 100644 --- a/drivers/clk/clk-bd718x7.c +++ b/drivers/clk/clk-bd718x7.c @@ -147,12 +147,12 @@ static int bd71837_clk_probe(struct platform_device *pdev) } static const struct platform_device_id bd718x7_clk_id[] = { - { "bd71837-clk", ROHM_CHIP_TYPE_BD71837 }, - { "bd71847-clk", ROHM_CHIP_TYPE_BD71847 }, - { "bd71828-clk", ROHM_CHIP_TYPE_BD71828 }, - { "bd71815-clk", ROHM_CHIP_TYPE_BD71815 }, - { "bd72720-clk", ROHM_CHIP_TYPE_BD72720 }, - { }, + { .name = "bd71837-clk", .driver_data = ROHM_CHIP_TYPE_BD71837 }, + { .name = "bd71847-clk", .driver_data = ROHM_CHIP_TYPE_BD71847 }, + { .name = "bd71828-clk", .driver_data = ROHM_CHIP_TYPE_BD71828 }, + { .name = "bd71815-clk", .driver_data = ROHM_CHIP_TYPE_BD71815 }, + { .name = "bd72720-clk", .driver_data = ROHM_CHIP_TYPE_BD72720 }, + { } }; MODULE_DEVICE_TABLE(platform, bd718x7_clk_id); diff --git a/drivers/clk/clk-max77686.c b/drivers/clk/clk-max77686.c index 9149ce4f702d..e6fde914c5ef 100644 --- a/drivers/clk/clk-max77686.c +++ b/drivers/clk/clk-max77686.c @@ -264,10 +264,10 @@ static int max77686_clk_probe(struct platform_device *pdev) } static const struct platform_device_id max77686_clk_id[] = { - { "max77686-clk", .driver_data = CHIP_MAX77686, }, - { "max77802-clk", .driver_data = CHIP_MAX77802, }, - { "max77620-clock", .driver_data = CHIP_MAX77620, }, - {}, + { .name = "max77686-clk", .driver_data = CHIP_MAX77686 }, + { .name = "max77802-clk", .driver_data = CHIP_MAX77802 }, + { .name = "max77620-clock", .driver_data = CHIP_MAX77620 }, + { } }; MODULE_DEVICE_TABLE(platform, max77686_clk_id); diff --git a/drivers/clk/clk-s2mps11.c b/drivers/clk/clk-s2mps11.c index ff7ce12a5da6..fa5ac8f673f6 100644 --- a/drivers/clk/clk-s2mps11.c +++ b/drivers/clk/clk-s2mps11.c @@ -225,12 +225,12 @@ static void s2mps11_clk_remove(struct platform_device *pdev) } static const struct platform_device_id s2mps11_clk_id[] = { - { "s2mpg10-clk", S2MPG10}, - { "s2mps11-clk", S2MPS11X}, - { "s2mps13-clk", S2MPS13X}, - { "s2mps14-clk", S2MPS14X}, - { "s5m8767-clk", S5M8767X}, - { }, + { .name = "s2mpg10-clk", .driver_data = S2MPG10 }, + { .name = "s2mps11-clk", .driver_data = S2MPS11X }, + { .name = "s2mps13-clk", .driver_data = S2MPS13X }, + { .name = "s2mps14-clk", .driver_data = S2MPS14X }, + { .name = "s5m8767-clk", .driver_data = S5M8767X }, + { } }; MODULE_DEVICE_TABLE(platform, s2mps11_clk_id); diff --git a/drivers/clk/samsung/clk-acpm.c b/drivers/clk/samsung/clk-acpm.c index 953ca8d5720a..25cfa953ccac 100644 --- a/drivers/clk/samsung/clk-acpm.c +++ b/drivers/clk/samsung/clk-acpm.c @@ -166,8 +166,8 @@ static int acpm_clk_probe(struct platform_device *pdev) } static const struct platform_device_id acpm_clk_id[] = { - { "gs101-acpm-clk" }, - {} + { .name = "gs101-acpm-clk" }, + { } }; MODULE_DEVICE_TABLE(platform, acpm_clk_id); From 9f275f2ee9ca60ea4c092bdc0195987945ad8ad8 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 26 May 2026 22:42:26 +0200 Subject: [PATCH 0032/1328] clk: moxart: remove unused variables, fix refcount leak Not only these error checks are redundand, those of_clk_get() return values weren't cleaned up via clk_put(). Fixes: c7bb4fc16ead ("clk: add MOXA ART SoCs clock driver") Signed-off-by: Alexander A. Klimov Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/clk-moxart.c | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/drivers/clk/clk-moxart.c b/drivers/clk/clk-moxart.c index 3786a0153ad1..40663ef3ef0a 100644 --- a/drivers/clk/clk-moxart.c +++ b/drivers/clk/clk-moxart.c @@ -17,7 +17,6 @@ static void __init moxart_of_pll_clk_init(struct device_node *node) { void __iomem *base; struct clk_hw *hw; - struct clk *ref_clk; unsigned int mul; const char *name = node->name; const char *parent_name; @@ -34,12 +33,6 @@ static void __init moxart_of_pll_clk_init(struct device_node *node) mul = readl(base + 0x30) >> 3 & 0x3f; iounmap(base); - ref_clk = of_clk_get(node, 0); - if (IS_ERR(ref_clk)) { - pr_err("%pOF: of_clk_get failed\n", node); - return; - } - hw = clk_hw_register_fixed_factor(NULL, name, parent_name, 0, mul, 1); if (IS_ERR(hw)) { pr_err("%pOF: failed to register clock\n", node); @@ -56,7 +49,6 @@ static void __init moxart_of_apb_clk_init(struct device_node *node) { void __iomem *base; struct clk_hw *hw; - struct clk *pll_clk; unsigned int div, val; unsigned int div_idx[] = { 2, 3, 4, 6, 8}; const char *name = node->name; @@ -78,12 +70,6 @@ static void __init moxart_of_apb_clk_init(struct device_node *node) val = 0; div = div_idx[val] * 2; - pll_clk = of_clk_get(node, 0); - if (IS_ERR(pll_clk)) { - pr_err("%pOF: of_clk_get failed\n", node); - return; - } - hw = clk_hw_register_fixed_factor(NULL, name, parent_name, 0, 1, div); if (IS_ERR(hw)) { pr_err("%pOF: failed to register clock\n", node); From f63aecdb45e9dd19c340fd62df698374d95b9024 Mon Sep 17 00:00:00 2001 From: Akari Tsuyukusa Date: Fri, 22 May 2026 22:30:23 +0900 Subject: [PATCH 0033/1328] clk: mediatek: mt8196: Select REGMAP_MMIO for vlpckgen The MediaTek MT8196 vlpckgen clock driver uses __devm_regmap_init_mmio_clk() by devm_regmap_init_mmio(), which is defined in drivers/base/regmap/regmap-mmio.c. However, the driver's Kconfig entry does not select REGMAP_MMIO. This causes a linker error when REGMAP_MMIO is not enabled. Fix this by selecting REGMAP_MMIO in the Kconfig entry. Fixes: 2f8b3ae6f0cb ("clk: mediatek: Add MT8196 vlpckgen clock support") Cc: stable@vger.kernel.org Signed-off-by: Akari Tsuyukusa Reviewed-by: Brian Masney Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Brian Masney --- drivers/clk/mediatek/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/clk/mediatek/Kconfig b/drivers/clk/mediatek/Kconfig index 2c09fd729bab..fd8440122ec2 100644 --- a/drivers/clk/mediatek/Kconfig +++ b/drivers/clk/mediatek/Kconfig @@ -1006,6 +1006,7 @@ config COMMON_CLK_MT8196 tristate "Clock driver for MediaTek MT8196" depends on ARM64 || COMPILE_TEST select COMMON_CLK_MEDIATEK + select REGMAP_MMIO default ARCH_MEDIATEK help This driver supports MediaTek MT8196 basic clocks. From 622ec01756ee9d483662655ff7315cb0e23afd46 Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Thu, 12 Feb 2026 13:16:03 -0800 Subject: [PATCH 0034/1328] apparmor: fix net.h and policy.h circular include pattern While the #ifdef guards prevent the circular include from blowing up, policy.h does not actually need anything from net.h. Remove, that include and instead include net.h in the other files that need it. Signed-off-by: Ryan Lee Signed-off-by: John Johansen --- security/apparmor/af_unix.c | 1 + security/apparmor/apparmorfs.c | 1 + security/apparmor/include/policy.h | 1 - security/apparmor/policy.c | 1 + security/apparmor/policy_unpack.c | 1 + 5 files changed, 4 insertions(+), 1 deletion(-) diff --git a/security/apparmor/af_unix.c b/security/apparmor/af_unix.c index b9b22edae202..9ca9d1b890ba 100644 --- a/security/apparmor/af_unix.c +++ b/security/apparmor/af_unix.c @@ -20,6 +20,7 @@ #include "include/apparmor.h" #include "include/file.h" #include "include/label.h" +#include "include/net.h" #include "include/path.h" #include "include/policy.h" #include "include/cred.h" diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 56155d7d5b2f..c0203d65715b 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -33,6 +33,7 @@ #include "include/crypto.h" #include "include/ipc.h" #include "include/label.h" +#include "include/net.h" #include "include/lib.h" #include "include/policy.h" #include "include/policy_ns.h" diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index 3895f8774a3f..169349beda34 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -26,7 +26,6 @@ #include "file.h" #include "lib.h" #include "label.h" -#include "net.h" #include "perms.h" #include "resource.h" diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 94b4a7e727cc..cc279fb6f40e 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -81,6 +81,7 @@ #include "include/file.h" #include "include/ipc.h" #include "include/match.h" +#include "include/net.h" #include "include/path.h" #include "include/policy.h" #include "include/policy_ns.h" diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index d9dcff167c48..bce2c47bdfe5 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -25,6 +25,7 @@ #include "include/crypto.h" #include "include/file.h" #include "include/match.h" +#include "include/net.h" #include "include/path.h" #include "include/policy.h" #include "include/policy_unpack.h" From 37077e4cfad4905128853f83c9e4ddf6a31e04f3 Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Fri, 13 Feb 2026 12:33:11 -0800 Subject: [PATCH 0035/1328] apparmor: make include headers self-contained Besides of resolving clangd IDE warnings, self-contained headers will be less likely to break if the surrounding includes in .c files using them change. Signed-off-by: Ryan Lee Signed-off-by: John Johansen --- security/apparmor/domain.c | 1 + security/apparmor/include/apparmorfs.h | 3 +++ security/apparmor/include/capability.h | 1 + security/apparmor/include/path.h | 3 +++ security/apparmor/include/policy_unpack.h | 1 + security/apparmor/include/procattr.h | 2 ++ security/apparmor/include/task.h | 5 +++++ 7 files changed, 16 insertions(+) diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index d6958eb00e30..4154964cdac5 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -24,6 +24,7 @@ #include "include/domain.h" #include "include/file.h" #include "include/ipc.h" +#include "include/lib.h" #include "include/match.h" #include "include/path.h" #include "include/policy.h" diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h index 33243d11fd10..c6c8fcde728f 100644 --- a/security/apparmor/include/apparmorfs.h +++ b/security/apparmor/include/apparmorfs.h @@ -11,6 +11,9 @@ #ifndef __AA_APPARMORFS_H #define __AA_APPARMORFS_H +#include +#include + extern struct path aa_null; enum aa_sfs_type { diff --git a/security/apparmor/include/capability.h b/security/apparmor/include/capability.h index 1ddcec2d1160..3fb8b8f2182a 100644 --- a/security/apparmor/include/capability.h +++ b/security/apparmor/include/capability.h @@ -11,6 +11,7 @@ #ifndef __AA_CAPABILITY_H #define __AA_CAPABILITY_H +#include #include #include "apparmorfs.h" diff --git a/security/apparmor/include/path.h b/security/apparmor/include/path.h index 8bb915d48dc7..250812a77e11 100644 --- a/security/apparmor/include/path.h +++ b/security/apparmor/include/path.h @@ -11,6 +11,9 @@ #ifndef __AA_PATH_H #define __AA_PATH_H +#include +#include + enum path_flags { PATH_IS_DIR = 0x1, /* path is a directory */ PATH_SOCK_COND = 0x2, diff --git a/security/apparmor/include/policy_unpack.h b/security/apparmor/include/policy_unpack.h index 4ea9b6479a3e..24afa061a43a 100644 --- a/security/apparmor/include/policy_unpack.h +++ b/security/apparmor/include/policy_unpack.h @@ -16,6 +16,7 @@ #include #include +#include "lib.h" struct aa_load_ent { struct list_head list; diff --git a/security/apparmor/include/procattr.h b/security/apparmor/include/procattr.h index 03dbfdb2f2c0..56acd1bdb634 100644 --- a/security/apparmor/include/procattr.h +++ b/security/apparmor/include/procattr.h @@ -11,6 +11,8 @@ #ifndef __AA_PROCATTR_H #define __AA_PROCATTR_H +#include "label.h" + int aa_getprocattr(struct aa_label *label, char **string, bool newline); int aa_setprocattr_changehat(char *args, size_t size, int flags); diff --git a/security/apparmor/include/task.h b/security/apparmor/include/task.h index b1aaaf60fa8b..017d8b06b8f2 100644 --- a/security/apparmor/include/task.h +++ b/security/apparmor/include/task.h @@ -10,6 +10,11 @@ #ifndef __AA_TASK_H #define __AA_TASK_H +#include + +#include "audit.h" +#include "label.h" + static inline struct aa_task_ctx *task_ctx(struct task_struct *task) { return task->security + apparmor_blob_sizes.lbs_task; From 17b5758bf35c7a113363cd7a350b7e6a251b80f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20B=C3=A9lair?= Date: Thu, 1 Feb 2024 09:09:50 +0100 Subject: [PATCH 0036/1328] apparmor: Initial support for compressed policies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch allows policies to be compressed in userspace and be sent to the kernel through the existing ".load" and ".replace" kernel interfaces. The benefits of this approach are: - Save kernel time when loading policies - Allow userspace to provide a higher level of compression than the one provided by the kernel (ZSTD_CLEVEL_DEFAULT), thus saving space. - Allow small embedded systems to only store the compressed version of policies in userspace, saving memory. Userspace-compressed policies improve system time by up to ~30% for big profiles. Signed-off-by: Maxime Bélair Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 97 ++++++++++++++++++++++- security/apparmor/include/policy.h | 3 +- security/apparmor/include/policy_unpack.h | 3 +- security/apparmor/policy.c | 7 +- security/apparmor/policy_unpack.c | 23 +++++- 5 files changed, 123 insertions(+), 10 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index c0203d65715b..514ec34975a4 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -482,6 +482,69 @@ static struct aa_loaddata *aa_simple_write_to_buffer(const char __user *userbuf, return data; } +static int decompress_zstd(char *src, size_t slen, char *dst, size_t dlen); +/** + * aa_get_data_from_compressed - common routine for getting compressed policy + * from user and get both compressed and uncompressed version. + * @userbuf: user buffer to copy data from (NOT NULL) + * @buffer_size: size of user buffer + * @pos: position write is at in the file (NOT NULL) + * @compressed_data Ptr on compressed data. *compressed_data is allocated there + * + * Returns: kernel buffer containing copy of user buffer data or an + * ERR_PTR on failure. + */ + +static struct aa_loaddata *aa_get_data_from_compressed(const char __user *userbuf, + size_t buffer_size, + loff_t *pos, + char **compressed_data) +{ + struct aa_loaddata *data; + zstd_frame_header header; + int error; + + if (!userbuf || !pos) + return ERR_PTR(-EINVAL); + if (*pos) + return ERR_PTR(-ESPIPE); + + *compressed_data = kvmalloc(buffer_size, GFP_KERNEL); + if (!*compressed_data) + return ERR_PTR(-ENOMEM); + error = copy_from_user(*compressed_data, userbuf, buffer_size); + if (error) + goto fail; + + error = zstd_get_frame_header(&header, *compressed_data, buffer_size); + if (error || header.frameContentSize == ZSTD_CONTENTSIZE_UNKNOWN || + header.frameContentSize == ZSTD_CONTENTSIZE_ERROR) { + error = -EINVAL; + goto fail; + } + + data = aa_loaddata_alloc(header.frameContentSize); + if (IS_ERR(data)) { + error = PTR_ERR(data); + goto fail; + } + + // We then decompress the data + error = decompress_zstd(*compressed_data, buffer_size, data->data, + header.frameContentSize); + if (error) + goto fail_decompress; + + data->size = header.frameContentSize; + return data; + +fail_decompress: + aa_put_i_loaddata(data); +fail: + kvfree(*compressed_data); + return ERR_PTR(error); + +} static ssize_t policy_update(u32 mask, const char __user *buf, size_t size, loff_t *pos, struct aa_ns *ns, @@ -490,6 +553,9 @@ static ssize_t policy_update(u32 mask, const char __user *buf, size_t size, struct aa_loaddata *data; struct aa_label *label; ssize_t error; + char *compressed_data = NULL; + __le32 magic_le; + bool is_compressed; label = begin_current_label_crit_section(); @@ -500,10 +566,33 @@ static ssize_t policy_update(u32 mask, const char __user *buf, size_t size, if (error) goto end_section; - data = aa_simple_write_to_buffer(buf, size, size, pos); - error = PTR_ERR(data); + /* If the policy is userspace compressed we start by decompressing it + * to make the required checks (computing hash, verifying profile, ...) + * + * Getting a userspace-compressed version then decompressing it in the + * kernel actually makes sense since zstd decompression is ~3.5x faster + * than compression. This also allow to increase the compression level. + */ + + if (size >= sizeof(__le32) && + !copy_from_user(&magic_le, buf, sizeof(magic_le)) && + le32_to_cpu(magic_le) == ZSTD_MAGICNUMBER) + is_compressed = true; + else + is_compressed = false; + + if (is_compressed) { + + data = aa_get_data_from_compressed(buf, size, pos, &compressed_data); + error = PTR_ERR(data); + } else { + data = aa_simple_write_to_buffer(buf, size, size, pos); + error = PTR_ERR(data); + } + if (!IS_ERR(data)) { - error = aa_replace_profiles(ns, label, mask, data); + error = aa_replace_profiles(ns, label, mask, data, + compressed_data, size); /* put pcount, which will put count and free if no * profiles referencing it. */ @@ -550,6 +639,7 @@ static const struct file_operations aa_fs_profile_replace = { .llseek = default_llseek, }; + /* .remove file hook fn to remove loaded policy */ static ssize_t profile_remove(struct file *f, const char __user *buf, size_t size, loff_t *pos) @@ -2484,6 +2574,7 @@ static struct aa_sfs_entry aa_sfs_entry_policy[] = { AA_SFS_FILE_STRING("permstable32", PERMS32STR), AA_SFS_FILE_U64("state32", 1), AA_SFS_DIR("unconfined_restrictions", aa_sfs_entry_unconfined), + AA_SFS_FILE_BOOLEAN("compressed_load", 1), { } }; diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index 169349beda34..240b2eba7687 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -304,7 +304,8 @@ struct aa_profile *aa_fqlookupn_profile(struct aa_label *base, const char *fqname, size_t n); ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_label *label, - u32 mask, struct aa_loaddata *udata); + u32 mask, struct aa_loaddata *udata, + char *compressed_profile, size_t compressed_size); ssize_t aa_remove_profiles(struct aa_ns *view, struct aa_label *label, char *name, size_t size); void __aa_profile_list_release(struct list_head *head); diff --git a/security/apparmor/include/policy_unpack.h b/security/apparmor/include/policy_unpack.h index 24afa061a43a..c01f6885dbe3 100644 --- a/security/apparmor/include/policy_unpack.h +++ b/security/apparmor/include/policy_unpack.h @@ -129,7 +129,8 @@ struct aa_loaddata { char *data; }; -int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, const char **ns); +int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, const char **ns, + char *compressed_data, size_t compressed_size); /** * aa_get_i_loaddata - get a reference count from a counted data reference diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index cc279fb6f40e..f6f1b72d7c3d 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -1159,6 +1159,8 @@ static struct aa_profile *update_to_newest_parent(struct aa_profile *new) * @label: label that is attempting to load/replace policy * @mask: permission mask * @udata: serialized data stream (NOT NULL) + * @compressed_profile: The userspace-provided compressed profile. May be NULL + * @compressed_size: If compressed_data is not NULL, the compressed data size * * unpack and replace a profile on the profile list and uses of that profile * by any task creds via invalidating the old version of the profile, which @@ -1168,7 +1170,8 @@ static struct aa_profile *update_to_newest_parent(struct aa_profile *new) * Returns: size of data consumed else error code on failure. */ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label, - u32 mask, struct aa_loaddata *udata) + u32 mask, struct aa_loaddata *udata, + char *compressed_profile, size_t compressed_size) { const char *ns_name = NULL, *info = NULL; struct aa_ns *ns = NULL; @@ -1181,7 +1184,7 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label, op = mask & AA_MAY_REPLACE_POLICY ? OP_PROF_REPL : OP_PROF_LOAD; aa_get_profile_loaddata(udata); /* released below */ - error = aa_unpack(udata, &lh, &ns_name); + error = aa_unpack(udata, &lh, &ns_name, compressed_profile, compressed_size); if (error) goto out; diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index bce2c47bdfe5..5969d78f16af 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -1718,6 +1718,8 @@ static int compress_loaddata(struct aa_loaddata *data) * @udata: user data copied to kmem (NOT NULL) * @lh: list to place unpacked profiles in a aa_repl_ws * @ns: Returns namespace profile is in if specified else NULL (NOT NULL) + * @compressed_data: The userspace-provided compressed data. May be NULL + * @compressed_size: If compressed_data is not NULL, the compressed data size * * Unpack user data and return refcounted allocated profile(s) stored in * @lh in order of discovery, with the list chain stored in base.list @@ -1726,12 +1728,12 @@ static int compress_loaddata(struct aa_loaddata *data) * Returns: profile(s) on @lh else error pointer if fails to unpack */ int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, - const char **ns) + const char **ns, char *compressed_data, size_t compressed_size) { struct aa_load_ent *tmp, *ent; struct aa_profile *profile = NULL; char *ns_name = NULL; - int error; + int error = 0; struct aa_ext e = { .start = udata->data, .end = udata->data + udata->size, @@ -1784,10 +1786,23 @@ int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, } if (aa_g_export_binary) { - error = compress_loaddata(udata); + /* Do we have userspace-compressed data? */ + if (compressed_data) { + kvfree(udata->data); + udata->data = compressed_data; + udata->compressed_size = compressed_size; + compressed_data = NULL; /* consumed */ + + } else + error = compress_loaddata(udata); + if (error) goto fail; + } else if (compressed_data) { + kvfree(compressed_data); + compressed_data = NULL; } + return 0; fail_profile: @@ -1795,6 +1810,8 @@ int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, aa_put_profile(profile); fail: + if (compressed_data) + kvfree(compressed_data); list_for_each_entry_safe(ent, tmp, lh, list) { list_del_init(&ent->list); aa_load_ent_free(ent); From b3a2223a7805c7e6759a32a5d6ca574ad07e2710 Mon Sep 17 00:00:00 2001 From: Joey Lu Date: Thu, 21 May 2026 09:42:18 +0800 Subject: [PATCH 0037/1328] clk: nuvoton: ma35d1: fix ignored div_u64 return values in PLL freq calculation div_u64() does not modify its argument in place; the return value must be assigned. Both ma35d1_calc_smic_pll_freq() and ma35d1_calc_pll_freq() called div_u64() and discarded the result, leaving pll_freq holding the undivided product and thus returning a frequency orders of magnitude too high. Fixes: 691521a367cf ("clk: nuvoton: Add clock driver for ma35d1 clock controller") Reviewed-by: Brian Masney Signed-off-by: Joey Lu Signed-off-by: Brian Masney --- drivers/clk/nuvoton/clk-ma35d1-pll.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/clk/nuvoton/clk-ma35d1-pll.c b/drivers/clk/nuvoton/clk-ma35d1-pll.c index 4620acfe47e8..bfedd45bd04b 100644 --- a/drivers/clk/nuvoton/clk-ma35d1-pll.c +++ b/drivers/clk/nuvoton/clk-ma35d1-pll.c @@ -92,7 +92,7 @@ static unsigned long ma35d1_calc_smic_pll_freq(u32 pll0_ctl0, p = FIELD_GET(SPLL0_CTL0_OUTDIV, pll0_ctl0); outdiv = 1 << p; pll_freq = (u64)parent_rate * n; - div_u64(pll_freq, m * outdiv); + pll_freq = div_u64(pll_freq, m * outdiv); return pll_freq; } @@ -110,7 +110,7 @@ static unsigned long ma35d1_calc_pll_freq(u8 mode, u32 *reg_ctl, unsigned long p if (mode == PLL_MODE_INT) { pll_freq = (u64)parent_rate * n; - div_u64(pll_freq, m * p); + pll_freq = div_u64(pll_freq, m * p); } else { x = FIELD_GET(PLL_CTL1_FRAC, reg_ctl[1]); /* 2 decimal places floating to integer (ex. 1.23 to 123) */ From 26de5aed72d80bd8aec2583134aca3597c64fda9 Mon Sep 17 00:00:00 2001 From: Joey Lu Date: Thu, 21 May 2026 09:42:19 +0800 Subject: [PATCH 0038/1328] clk: nuvoton: ma35d1: fix PLL_CTL1_FRAC bit field width and fractional calc PLL_CTL1_FRAC was defined as GENMASK(31, 24), covering only 8 bits. The hardware fractional field occupies bits [31:8] (24 bits), so the mask must be GENMASK(31, 8). The previous fractional-mode calculation used FIELD_MAX(PLL_CTL1_FRAC) as the denominator to obtain 2 decimal places. With the corrected 24-bit mask the old divisor is wrong; replace the arithmetic with a proper 24-bit fixed-point rounding to 3 decimal places using the kernel's DIV_ROUND_CLOSEST_ULL helper: n_frac = n * 1000 + DIV_ROUND_CLOSEST_ULL(x * 1000, 1 << 24) Fixes: 691521a367cf ("clk: nuvoton: Add clock driver for ma35d1 clock controller") Signed-off-by: Joey Lu Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/nuvoton/clk-ma35d1-pll.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/clk/nuvoton/clk-ma35d1-pll.c b/drivers/clk/nuvoton/clk-ma35d1-pll.c index bfedd45bd04b..eb9d69d2077b 100644 --- a/drivers/clk/nuvoton/clk-ma35d1-pll.c +++ b/drivers/clk/nuvoton/clk-ma35d1-pll.c @@ -48,7 +48,7 @@ #define PLL_CTL1_PD BIT(0) #define PLL_CTL1_BP BIT(1) #define PLL_CTL1_OUTDIV GENMASK(6, 4) -#define PLL_CTL1_FRAC GENMASK(31, 24) +#define PLL_CTL1_FRAC GENMASK(31, 8) #define PLL_CTL2_SLOPE GENMASK(23, 0) #define INDIV_MIN 1 @@ -113,9 +113,9 @@ static unsigned long ma35d1_calc_pll_freq(u8 mode, u32 *reg_ctl, unsigned long p pll_freq = div_u64(pll_freq, m * p); } else { x = FIELD_GET(PLL_CTL1_FRAC, reg_ctl[1]); - /* 2 decimal places floating to integer (ex. 1.23 to 123) */ - n = n * 100 + ((x * 100) / FIELD_MAX(PLL_CTL1_FRAC)); - pll_freq = div_u64(parent_rate * n, 100 * m * p); + /* convert 24-bit fraction to 3 decimal digits, rounding to closest */ + n = n * 1000 + DIV_ROUND_CLOSEST_ULL((u64)x * 1000, 1ULL << 24); + pll_freq = div_u64((u64)parent_rate * n, 1000 * m * p); } return pll_freq; } From e1311954cb600d5f95cd9e2fe9a7376edc2ac3c5 Mon Sep 17 00:00:00 2001 From: Joey Lu Date: Thu, 21 May 2026 09:42:20 +0800 Subject: [PATCH 0039/1328] clk: nuvoton: ma35d1: fix ma35d1_clk_pll_determine_rate logic ma35d1_clk_pll_determine_rate() called ma35d1_pll_find_closest() unconditionally before the switch statement, and then every case branch overwrote pll_freq by reading the current hardware registers. For CAPLL and DDRPLL this means find_closest() ran unnecessarily (and incorrectly, since those PLLs are read-only) and its result was silently discarded. Fix by moving the find_closest() call inside the APLL/EPLL/VPLL branch where it belongs. Group CAPLL and DDRPLL together as read-only PLLs that simply report their current rate; handle them with an explicit if/else to keep the CAPLL (SMIC design) and DDRPLL (standard design) paths distinct. Fixes: 691521a367cf ("clk: nuvoton: Add clock driver for ma35d1 clock controller") Signed-off-by: Joey Lu Signed-off-by: Brian Masney --- drivers/clk/nuvoton/clk-ma35d1-pll.c | 30 ++++++++++++++-------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/drivers/clk/nuvoton/clk-ma35d1-pll.c b/drivers/clk/nuvoton/clk-ma35d1-pll.c index eb9d69d2077b..c7c0dc91a012 100644 --- a/drivers/clk/nuvoton/clk-ma35d1-pll.c +++ b/drivers/clk/nuvoton/clk-ma35d1-pll.c @@ -255,32 +255,32 @@ static int ma35d1_clk_pll_determine_rate(struct clk_hw *hw, if (req->best_parent_rate < PLL_FREF_MIN_FREQ || req->best_parent_rate > PLL_FREF_MAX_FREQ) return -EINVAL; - ret = ma35d1_pll_find_closest(pll, req->rate, req->best_parent_rate, - reg_ctl, &pll_freq); - if (ret < 0) - return ret; - switch (pll->id) { case CAPLL: - reg_ctl[0] = readl_relaxed(pll->ctl0_base); - pll_freq = ma35d1_calc_smic_pll_freq(reg_ctl[0], req->best_parent_rate); - req->rate = pll_freq; - - return 0; case DDRPLL: + /* Read-only PLLs: return current rate */ + reg_ctl[0] = readl_relaxed(pll->ctl0_base); + if (pll->id == CAPLL) { + pll_freq = ma35d1_calc_smic_pll_freq(reg_ctl[0], req->best_parent_rate); + } else { + reg_ctl[1] = readl_relaxed(pll->ctl1_base); + pll_freq = ma35d1_calc_pll_freq(pll->mode, reg_ctl, req->best_parent_rate); + } + req->rate = pll_freq; + return 0; case APLL: case EPLL: case VPLL: - reg_ctl[0] = readl_relaxed(pll->ctl0_base); - reg_ctl[1] = readl_relaxed(pll->ctl1_base); - pll_freq = ma35d1_calc_pll_freq(pll->mode, reg_ctl, req->best_parent_rate); + /* Configurable PLLs: find closest achievable rate */ + ret = ma35d1_pll_find_closest(pll, req->rate, req->best_parent_rate, + reg_ctl, &pll_freq); + if (ret < 0) + return ret; req->rate = pll_freq; - return 0; } req->rate = 0; - return 0; } From 0bf68e8dcb843f094ed73c2c54e9fe58a7a4f774 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Tue, 19 May 2026 15:26:39 -0700 Subject: [PATCH 0040/1328] clk: stm32: add missing bitfield.h header It seems some ARM header includes this and the build passes there, but nowhere else. Note that the driver has COMPILE_TEST in depends. Fixes: 37ae8501cdb0 ("clk: stm32: introduce clocks for STM32MP21 platfor") Signed-off-by: Rosen Penev Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/stm32/clk-stm32mp21.c | 1 + drivers/clk/stm32/clk-stm32mp25.c | 1 + 2 files changed, 2 insertions(+) diff --git a/drivers/clk/stm32/clk-stm32mp21.c b/drivers/clk/stm32/clk-stm32mp21.c index c8a37b716bd5..bdb17419908c 100644 --- a/drivers/clk/stm32/clk-stm32mp21.c +++ b/drivers/clk/stm32/clk-stm32mp21.c @@ -4,6 +4,7 @@ * Author: Gabriel Fernandez for STMicroelectronics. */ +#include #include #include #include diff --git a/drivers/clk/stm32/clk-stm32mp25.c b/drivers/clk/stm32/clk-stm32mp25.c index 52f0e8a12926..eb0bc918ecee 100644 --- a/drivers/clk/stm32/clk-stm32mp25.c +++ b/drivers/clk/stm32/clk-stm32mp25.c @@ -4,6 +4,7 @@ * Author: Gabriel Fernandez for STMicroelectronics. */ +#include #include #include #include From dda61023f976d7ab3bc7c8b46d26d6d23424f890 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sat, 9 May 2026 18:20:27 -0700 Subject: [PATCH 0041/1328] apparmor: fix alternate loaders ability to load compressed policy When policy is compressed it is given a header in userspace. The parser knows to strip this header, but alternate profile loaders like aa-load and systemd have not been updated so that they know to strip the header. This allows those loaders to just load the binary file without having to be updated. Fixes: 17b5758bf35c7 ("apparmor: Initial support for compressed policies") Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 37 +++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 514ec34975a4..2ae9ab94a5a9 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -546,6 +547,15 @@ static struct aa_loaddata *aa_get_data_from_compressed(const char __user *userbu } +struct aa_user_hdr { + uint8_t version; + uint8_t compress_level; + uint8_t padding[6]; /* force 8-byte alignment */ +} __packed __aligned(8); + +#define aa_hdr_magic "\x04\x08\x00\x76\x65\x72\x73\x69\x6f\x6e\x00\x02" +#define aa_hdr_magic_size 12 + static ssize_t policy_update(u32 mask, const char __user *buf, size_t size, loff_t *pos, struct aa_ns *ns, const struct cred *ocred) @@ -556,6 +566,7 @@ static ssize_t policy_update(u32 mask, const char __user *buf, size_t size, char *compressed_data = NULL; __le32 magic_le; bool is_compressed; + u8 aahdr[aa_hdr_magic_size]; label = begin_current_label_crit_section(); @@ -576,11 +587,30 @@ static ssize_t policy_update(u32 mask, const char __user *buf, size_t size, if (size >= sizeof(__le32) && !copy_from_user(&magic_le, buf, sizeof(magic_le)) && - le32_to_cpu(magic_le) == ZSTD_MAGICNUMBER) + le32_to_cpu(magic_le) == ZSTD_MAGICNUMBER) { is_compressed = true; - else + } else if (size >= sizeof(struct aa_user_hdr) + sizeof(__le32) && + !copy_from_user(&magic_le, + buf + sizeof(struct aa_user_hdr), + sizeof(magic_le)) && + le32_to_cpu(magic_le) == ZSTD_MAGICNUMBER) { + is_compressed = true; + /* skip the userspace header if present */ + buf += sizeof(struct aa_user_hdr); + size -= sizeof(struct aa_user_hdr); + } else if (size >= sizeof(struct aa_user_hdr) + + aa_hdr_magic_size && + !copy_from_user(&aahdr, + buf + sizeof(struct aa_user_hdr), + aa_hdr_magic_size) && + memcmp(&aahdr, aa_hdr_magic, aa_hdr_magic_size) == 0) { + /* uncompressed blob with user hdr */ + buf += sizeof(struct aa_user_hdr); + size -= sizeof(struct aa_user_hdr); is_compressed = false; - + } else { + is_compressed = false; + } if (is_compressed) { data = aa_get_data_from_compressed(buf, size, pos, &compressed_data); @@ -2575,6 +2605,7 @@ static struct aa_sfs_entry aa_sfs_entry_policy[] = { AA_SFS_FILE_U64("state32", 1), AA_SFS_DIR("unconfined_restrictions", aa_sfs_entry_unconfined), AA_SFS_FILE_BOOLEAN("compressed_load", 1), + AA_SFS_FILE_BOOLEAN("extended_policy_header", 1), { } }; From f5964864856c3422c8193e22d9ae80e2edbbcf18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Fri, 15 May 2026 17:10:39 +0200 Subject: [PATCH 0042/1328] clk: si5341: Drop unused i2c driver_data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The driver doesn't make use of the value that was explicitly assigned to the .driver_data member. Drop the assignment. Signed-off-by: Uwe Kleine-König (The Capable Hub) Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/clk-si5341.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/clk/clk-si5341.c b/drivers/clk/clk-si5341.c index 2499b771cd83..5311c23532b4 100644 --- a/drivers/clk/clk-si5341.c +++ b/drivers/clk/clk-si5341.c @@ -1825,11 +1825,11 @@ static void si5341_remove(struct i2c_client *client) } static const struct i2c_device_id si5341_id[] = { - { "si5340", 0 }, - { "si5341", 1 }, - { "si5342", 2 }, - { "si5344", 4 }, - { "si5345", 5 }, + { .name = "si5340" }, + { .name = "si5341" }, + { .name = "si5342" }, + { .name = "si5344" }, + { .name = "si5345" }, { } }; MODULE_DEVICE_TABLE(i2c, si5341_id); From cae51b908edc6edca381f4cf9fb139ee40968a88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Fri, 15 May 2026 17:10:40 +0200 Subject: [PATCH 0043/1328] clk: Use named initializers for arrays of i2c_device_data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While being less compact, using named initializers allows to more easily see which members of the structs are assigned which value without having to lookup the declaration of the struct. And it's also more robust against changes to the struct definition. The mentioned robustness is relevant for a planned change to struct i2c_device_id that replaces .driver_data by an anonymous union. While touching all these arrays, unify usage of whitespace and commas. This patch doesn't modify the compiled arrays, only their representation in source form benefits. The former was confirmed with x86 and arm64 builds. Signed-off-by: Uwe Kleine-König (The Capable Hub) Reviewed-by: Luca Ceresoli # clk-versaclock5 Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/clk-cdce706.c | 2 +- drivers/clk/clk-cdce925.c | 8 ++++---- drivers/clk/clk-cs2000-cp.c | 4 ++-- drivers/clk/clk-renesas-pcie.c | 6 +++--- drivers/clk/clk-si514.c | 2 +- drivers/clk/clk-si521xx.c | 6 +++--- drivers/clk/clk-si5351.c | 8 ++++---- drivers/clk/clk-si544.c | 6 +++--- drivers/clk/clk-si570.c | 8 ++++---- drivers/clk/clk-versaclock5.c | 16 ++++++++-------- drivers/clk/clk-versaclock7.c | 4 ++-- 11 files changed, 35 insertions(+), 35 deletions(-) diff --git a/drivers/clk/clk-cdce706.c b/drivers/clk/clk-cdce706.c index a495d313b02f..425a0481f46f 100644 --- a/drivers/clk/clk-cdce706.c +++ b/drivers/clk/clk-cdce706.c @@ -680,7 +680,7 @@ MODULE_DEVICE_TABLE(of, cdce706_dt_match); #endif static const struct i2c_device_id cdce706_id[] = { - { "cdce706" }, + { .name = "cdce706" }, { } }; MODULE_DEVICE_TABLE(i2c, cdce706_id); diff --git a/drivers/clk/clk-cdce925.c b/drivers/clk/clk-cdce925.c index 0b2ad21e6e4d..30d0493c7718 100644 --- a/drivers/clk/clk-cdce925.c +++ b/drivers/clk/clk-cdce925.c @@ -826,10 +826,10 @@ static const struct clk_cdce925_chip_info clk_cdce949_info = { }; static const struct i2c_device_id cdce925_id[] = { - { "cdce913", (kernel_ulong_t)&clk_cdce913_info }, - { "cdce925", (kernel_ulong_t)&clk_cdce925_info }, - { "cdce937", (kernel_ulong_t)&clk_cdce937_info }, - { "cdce949", (kernel_ulong_t)&clk_cdce949_info }, + { .name = "cdce913", .driver_data = (kernel_ulong_t)&clk_cdce913_info }, + { .name = "cdce925", .driver_data = (kernel_ulong_t)&clk_cdce925_info }, + { .name = "cdce937", .driver_data = (kernel_ulong_t)&clk_cdce937_info }, + { .name = "cdce949", .driver_data = (kernel_ulong_t)&clk_cdce949_info }, { } }; MODULE_DEVICE_TABLE(i2c, cdce925_id); diff --git a/drivers/clk/clk-cs2000-cp.c b/drivers/clk/clk-cs2000-cp.c index 8800472ba63f..fd277dcefad7 100644 --- a/drivers/clk/clk-cs2000-cp.c +++ b/drivers/clk/clk-cs2000-cp.c @@ -122,8 +122,8 @@ static const struct of_device_id cs2000_of_match[] = { MODULE_DEVICE_TABLE(of, cs2000_of_match); static const struct i2c_device_id cs2000_id[] = { - { "cs2000-cp", }, - {} + { .name = "cs2000-cp" }, + { } }; MODULE_DEVICE_TABLE(i2c, cs2000_id); diff --git a/drivers/clk/clk-renesas-pcie.c b/drivers/clk/clk-renesas-pcie.c index aa108df12e44..b59607e3e659 100644 --- a/drivers/clk/clk-renesas-pcie.c +++ b/drivers/clk/clk-renesas-pcie.c @@ -395,9 +395,9 @@ static const struct rs9_chip_info renesas_9fgv0841_info = { }; static const struct i2c_device_id rs9_id[] = { - { "9fgv0241", .driver_data = (kernel_ulong_t)&renesas_9fgv0241_info }, - { "9fgv0441", .driver_data = (kernel_ulong_t)&renesas_9fgv0441_info }, - { "9fgv0841", .driver_data = (kernel_ulong_t)&renesas_9fgv0841_info }, + { .name = "9fgv0241", .driver_data = (kernel_ulong_t)&renesas_9fgv0241_info }, + { .name = "9fgv0441", .driver_data = (kernel_ulong_t)&renesas_9fgv0441_info }, + { .name = "9fgv0841", .driver_data = (kernel_ulong_t)&renesas_9fgv0841_info }, { } }; MODULE_DEVICE_TABLE(i2c, rs9_id); diff --git a/drivers/clk/clk-si514.c b/drivers/clk/clk-si514.c index f61590d70575..c0b433ad9372 100644 --- a/drivers/clk/clk-si514.c +++ b/drivers/clk/clk-si514.c @@ -379,7 +379,7 @@ static int si514_probe(struct i2c_client *client) } static const struct i2c_device_id si514_id[] = { - { "si514" }, + { .name = "si514" }, { } }; MODULE_DEVICE_TABLE(i2c, si514_id); diff --git a/drivers/clk/clk-si521xx.c b/drivers/clk/clk-si521xx.c index 4ed4e1a5f4f2..e41d922437ee 100644 --- a/drivers/clk/clk-si521xx.c +++ b/drivers/clk/clk-si521xx.c @@ -365,9 +365,9 @@ static int __maybe_unused si521xx_resume(struct device *dev) } static const struct i2c_device_id si521xx_id[] = { - { "si52144", .driver_data = SI521XX_OE_MAP(0x5, 0xc0) }, - { "si52146", .driver_data = SI521XX_OE_MAP(0x15, 0xe0) }, - { "si52147", .driver_data = SI521XX_OE_MAP(0x17, 0xf8) }, + { .name = "si52144", .driver_data = SI521XX_OE_MAP(0x5, 0xc0) }, + { .name = "si52146", .driver_data = SI521XX_OE_MAP(0x15, 0xe0) }, + { .name = "si52147", .driver_data = SI521XX_OE_MAP(0x17, 0xf8) }, { } }; MODULE_DEVICE_TABLE(i2c, si521xx_id); diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c index e755db545e2e..20b0c3c48b86 100644 --- a/drivers/clk/clk-si5351.c +++ b/drivers/clk/clk-si5351.c @@ -1425,10 +1425,10 @@ si53351_of_clk_get(struct of_phandle_args *clkspec, void *data) #endif /* CONFIG_OF */ static const struct i2c_device_id si5351_i2c_ids[] = { - { "si5351a", SI5351_VARIANT_A }, - { "si5351a-msop", SI5351_VARIANT_A3 }, - { "si5351b", SI5351_VARIANT_B }, - { "si5351c", SI5351_VARIANT_C }, + { .name = "si5351a", .driver_data = SI5351_VARIANT_A }, + { .name = "si5351a-msop", .driver_data = SI5351_VARIANT_A3 }, + { .name = "si5351b", .driver_data = SI5351_VARIANT_B }, + { .name = "si5351c", .driver_data = SI5351_VARIANT_C }, { } }; MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids); diff --git a/drivers/clk/clk-si544.c b/drivers/clk/clk-si544.c index 09c06ecec1a5..f45f3f73aa82 100644 --- a/drivers/clk/clk-si544.c +++ b/drivers/clk/clk-si544.c @@ -479,9 +479,9 @@ static int si544_probe(struct i2c_client *client) } static const struct i2c_device_id si544_id[] = { - { "si544a", 1500000000 }, - { "si544b", 800000000 }, - { "si544c", 350000000 }, + { .name = "si544a", .driver_data = 1500000000 }, + { .name = "si544b", .driver_data = 800000000 }, + { .name = "si544c", .driver_data = 350000000 }, { } }; MODULE_DEVICE_TABLE(i2c, si544_id); diff --git a/drivers/clk/clk-si570.c b/drivers/clk/clk-si570.c index b0b1830dd430..3063e38c66da 100644 --- a/drivers/clk/clk-si570.c +++ b/drivers/clk/clk-si570.c @@ -503,10 +503,10 @@ static const struct clk_si570_info clk_si590_info = { }; static const struct i2c_device_id si570_id[] = { - { "si570", (kernel_ulong_t)&clk_si570_info }, - { "si571", (kernel_ulong_t)&clk_si570_info }, - { "si598", (kernel_ulong_t)&clk_si590_info }, - { "si599", (kernel_ulong_t)&clk_si590_info }, + { .name = "si570", .driver_data = (kernel_ulong_t)&clk_si570_info }, + { .name = "si571", .driver_data = (kernel_ulong_t)&clk_si570_info }, + { .name = "si598", .driver_data = (kernel_ulong_t)&clk_si590_info }, + { .name = "si599", .driver_data = (kernel_ulong_t)&clk_si590_info }, { } }; MODULE_DEVICE_TABLE(i2c, si570_id); diff --git a/drivers/clk/clk-versaclock5.c b/drivers/clk/clk-versaclock5.c index 57228e88e81d..484b02726b3a 100644 --- a/drivers/clk/clk-versaclock5.c +++ b/drivers/clk/clk-versaclock5.c @@ -1311,14 +1311,14 @@ static const struct vc5_chip_info idt_5p49v6975_info = { }; static const struct i2c_device_id vc5_id[] = { - { "5p49v5923", .driver_data = (kernel_ulong_t)&idt_5p49v5923_info }, - { "5p49v5925", .driver_data = (kernel_ulong_t)&idt_5p49v5925_info }, - { "5p49v5933", .driver_data = (kernel_ulong_t)&idt_5p49v5933_info }, - { "5p49v5935", .driver_data = (kernel_ulong_t)&idt_5p49v5935_info }, - { "5p49v60", .driver_data = (kernel_ulong_t)&idt_5p49v60_info }, - { "5p49v6901", .driver_data = (kernel_ulong_t)&idt_5p49v6901_info }, - { "5p49v6965", .driver_data = (kernel_ulong_t)&idt_5p49v6965_info }, - { "5p49v6975", .driver_data = (kernel_ulong_t)&idt_5p49v6975_info }, + { .name = "5p49v5923", .driver_data = (kernel_ulong_t)&idt_5p49v5923_info }, + { .name = "5p49v5925", .driver_data = (kernel_ulong_t)&idt_5p49v5925_info }, + { .name = "5p49v5933", .driver_data = (kernel_ulong_t)&idt_5p49v5933_info }, + { .name = "5p49v5935", .driver_data = (kernel_ulong_t)&idt_5p49v5935_info }, + { .name = "5p49v60", .driver_data = (kernel_ulong_t)&idt_5p49v60_info }, + { .name = "5p49v6901", .driver_data = (kernel_ulong_t)&idt_5p49v6901_info }, + { .name = "5p49v6965", .driver_data = (kernel_ulong_t)&idt_5p49v6965_info }, + { .name = "5p49v6975", .driver_data = (kernel_ulong_t)&idt_5p49v6975_info }, { } }; MODULE_DEVICE_TABLE(i2c, vc5_id); diff --git a/drivers/clk/clk-versaclock7.c b/drivers/clk/clk-versaclock7.c index e3a36dcd98b8..1340d2f512be 100644 --- a/drivers/clk/clk-versaclock7.c +++ b/drivers/clk/clk-versaclock7.c @@ -1288,8 +1288,8 @@ static const struct regmap_config vc7_regmap_config = { }; static const struct i2c_device_id vc7_i2c_id[] = { - { "rc21008a", .driver_data = (kernel_ulong_t)&vc7_rc21008a_info }, - {} + { .name = "rc21008a", .driver_data = (kernel_ulong_t)&vc7_rc21008a_info }, + { } }; MODULE_DEVICE_TABLE(i2c, vc7_i2c_id); From 003d9ba31a5d21712a98f43a431fc00ff66c3d21 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Tue, 12 May 2026 14:24:53 -0700 Subject: [PATCH 0044/1328] clk: add missing function short descriptions for kernel-doc Add short descriptions for 3 functions which are missing it. Modify the parameter name in comments to be @core instead of clk. Use corrected function names (with leading "__") in a few places. Warning: drivers/clk/clk.c:1899 missing initial short description on line: * __clk_recalc_accuracies Warning: drivers/clk/clk.c:1972 missing initial short description on line: * __clk_recalc_rates Warning: drivers/clk/clk.c:2244 missing initial short description on line: * __clk_speculate_rates This eliminates warnings in this file except for missing return value warnings, of which there are around 70. Signed-off-by: Randy Dunlap Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/clk.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index 8c78621cde25..83a80e5cff2e 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -1846,10 +1846,10 @@ static int __clk_notify(struct clk_core *core, unsigned long msg, } /** - * __clk_recalc_accuracies + * __clk_recalc_accuracies - recalculate all accuracies in the clk subtree * @core: first clk in the subtree * - * Walks the subtree of clks starting with clk and recalculates accuracies as + * Walks the subtree of clks starting with @core and recalculates accuracies as * it goes. Note that if a clk does not implement the .recalc_accuracy * callback then it is assumed that the clock will take on the accuracy of its * parent. @@ -1919,16 +1919,16 @@ static unsigned long clk_recalc(struct clk_core *core, } /** - * __clk_recalc_rates + * __clk_recalc_rates - recalculate all rates in the clk subtree * @core: first clk in the subtree * @update_req: Whether req_rate should be updated with the new rate * @msg: notification type (see include/linux/clk.h) * - * Walks the subtree of clks starting with clk and recalculates rates as it + * Walks the subtree of clks starting with @core and recalculates rates as it * goes. Note that if a clk does not implement the .recalc_rate callback then * it is assumed that the clock will take on the rate of its parent. * - * clk_recalc_rates also propagates the POST_RATE_CHANGE notification, + * __clk_recalc_rates also propagates the POST_RATE_CHANGE notification, * if necessary. */ static void __clk_recalc_rates(struct clk_core *core, bool update_req, @@ -2191,14 +2191,14 @@ static int __clk_set_parent(struct clk_core *core, struct clk_core *parent, } /** - * __clk_speculate_rates + * __clk_speculate_rates - speculate all rates in the clk subtree * @core: first clk in the subtree * @parent_rate: the "future" rate of clk's parent * - * Walks the subtree of clks starting with clk, speculating rates as it + * Walks the subtree of clks starting with @core, speculating rates as it * goes and firing off PRE_RATE_CHANGE notifications as necessary. * - * Unlike clk_recalc_rates, clk_speculate_rates exists only for sending + * Unlike __clk_recalc_rates, __clk_speculate_rates exists only for sending * pre-rate change notifications and returns early if no clks in the * subtree have subscribed to the notifications. Note that if a clk does not * implement the .recalc_rate callback then it is assumed that the clock will From a8036f4591542de4b38ec81d3e2ba47bc0b2652b Mon Sep 17 00:00:00 2001 From: Min zhang Date: Tue, 12 May 2026 08:06:42 +0800 Subject: [PATCH 0045/1328] clk: hisilicon: reset: Use devm_kzalloc to initialize hisi_reset_controller Using devm_kmalloc() does not zero-initialize the allocated structure. Uninitialized members in struct hisi_reset_controller may contain garbage data, which can cause reset_controller_register() to fail unexpectedly. Replace devm_kmalloc() with devm_kzalloc() to ensure all structure fields are properly zero-initialized. Fixes: 97b7129cd2afb ("reset: hisilicon: change the definition of hisi_reset_init") Reviewed-by: Brian Masney Signed-off-by: Min zhang Signed-off-by: Brian Masney --- drivers/clk/hisilicon/reset.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/hisilicon/reset.c b/drivers/clk/hisilicon/reset.c index 93cee17db8b1..c3b7daac9313 100644 --- a/drivers/clk/hisilicon/reset.c +++ b/drivers/clk/hisilicon/reset.c @@ -91,7 +91,7 @@ struct hisi_reset_controller *hisi_reset_init(struct platform_device *pdev) { struct hisi_reset_controller *rstc; - rstc = devm_kmalloc(&pdev->dev, sizeof(*rstc), GFP_KERNEL); + rstc = devm_kzalloc(&pdev->dev, sizeof(*rstc), GFP_KERNEL); if (!rstc) return NULL; From 48d5f26f1d5c14e2edf21c843e815a105badd1ba Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Fri, 8 May 2026 22:11:10 -0700 Subject: [PATCH 0046/1328] clk: hisilicon: allow COMPILE_TEST builds The HiSilicon clock symbols already depend on ARCH_HISI or COMPILE_TEST, but the parent clock Makefile only descends into the hisilicon directory when ARCH_HISI is enabled. Add a hidden family gate selected by the HiSilicon clock and reset symbols, default it for ARCH_HISI, and use it for the parent Makefile descent. This keeps existing platform builds unchanged while allowing compile-test coverage. Tested with: make LLVM=1 ARCH=loongarch drivers/clk/hisilicon/ Assisted-by: Codex:GPT-5.5 Signed-off-by: Rosen Penev Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/Makefile | 2 +- drivers/clk/hisilicon/Kconfig | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index d042b4aa9c0f..c55e323d3d92 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -132,7 +132,7 @@ obj-y += bcm/ obj-$(CONFIG_ARCH_BERLIN) += berlin/ obj-$(CONFIG_ARCH_DAVINCI) += davinci/ obj-$(CONFIG_COMMON_CLK_ESWIN) += eswin/ -obj-$(CONFIG_ARCH_HISI) += hisilicon/ +obj-$(CONFIG_COMMON_CLK_HISI) += hisilicon/ obj-y += imgtec/ obj-y += imx/ obj-y += ingenic/ diff --git a/drivers/clk/hisilicon/Kconfig b/drivers/clk/hisilicon/Kconfig index c1ec75aa4ccd..e70bcfa9105b 100644 --- a/drivers/clk/hisilicon/Kconfig +++ b/drivers/clk/hisilicon/Kconfig @@ -1,8 +1,13 @@ # SPDX-License-Identifier: GPL-2.0-only +config COMMON_CLK_HISI + bool + default ARCH_HISI + config COMMON_CLK_HI3516CV300 tristate "HI3516CV300 Clock Driver" depends on ARCH_HISI || COMPILE_TEST select RESET_HISI + select COMMON_CLK_HISI default ARCH_HISI help Build the clock driver for hi3516cv300. @@ -11,6 +16,7 @@ config COMMON_CLK_HI3519 tristate "Hi3519 Clock Driver" depends on ARCH_HISI || COMPILE_TEST select RESET_HISI + select COMMON_CLK_HISI default ARCH_HISI help Build the clock driver for hi3519. @@ -19,6 +25,7 @@ config COMMON_CLK_HI3559A bool "Hi3559A Clock Driver" depends on ARCH_HISI || COMPILE_TEST select RESET_HISI + select COMMON_CLK_HISI default ARCH_HISI help Build the clock driver for hi3559a. @@ -26,6 +33,7 @@ config COMMON_CLK_HI3559A config COMMON_CLK_HI3660 bool "Hi3660 Clock Driver" depends on ARCH_HISI || COMPILE_TEST + select COMMON_CLK_HISI default ARCH_HISI help Build the clock driver for hi3660. @@ -33,6 +41,7 @@ config COMMON_CLK_HI3660 config COMMON_CLK_HI3670 bool "Hi3670 Clock Driver" depends on ARCH_HISI || COMPILE_TEST + select COMMON_CLK_HISI default ARCH_HISI help Build the clock driver for hi3670. @@ -41,6 +50,7 @@ config COMMON_CLK_HI3798CV200 tristate "Hi3798CV200 Clock Driver" depends on ARCH_HISI || COMPILE_TEST select RESET_HISI + select COMMON_CLK_HISI default ARCH_HISI help Build the clock driver for hi3798cv200. @@ -48,6 +58,7 @@ config COMMON_CLK_HI3798CV200 config COMMON_CLK_HI6220 bool "Hi6220 Clock Driver" depends on ARCH_HISI || COMPILE_TEST + select COMMON_CLK_HISI default ARCH_HISI help Build the Hisilicon Hi6220 clock driver based on the common clock framework. @@ -55,6 +66,7 @@ config COMMON_CLK_HI6220 config RESET_HISI bool "HiSilicon Reset Controller Driver" depends on ARCH_HISI || COMPILE_TEST + select COMMON_CLK_HISI select RESET_CONTROLLER help Build reset controller driver for HiSilicon device chipsets. @@ -63,6 +75,7 @@ config STUB_CLK_HI6220 bool "Hi6220 Stub Clock Driver" if EXPERT depends on (COMMON_CLK_HI6220 || COMPILE_TEST) depends on MAILBOX + select COMMON_CLK_HISI default COMMON_CLK_HI6220 help Build the Hisilicon Hi6220 stub clock driver. @@ -71,6 +84,7 @@ config STUB_CLK_HI3660 bool "Hi3660 Stub Clock Driver" if EXPERT depends on (COMMON_CLK_HI3660 || COMPILE_TEST) depends on MAILBOX + select COMMON_CLK_HISI default COMMON_CLK_HI3660 help Build the Hisilicon Hi3660 stub clock driver. From ef9f74ee4ccc5f98108eb2dfbf293f64db61ccb1 Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Mon, 11 May 2026 21:35:04 -0400 Subject: [PATCH 0047/1328] clk: add kernel docs for the core flags Let's add a DOC section for the clk core flags, and move the documentation for each flag into the doc header so that it can be easily referenced in the generated kernel documentation. Note: The comment about "Please update clk_flags..." is included as a separate comment so it doesn't show up in the generated documents. Reviewed-by: Maxime Ripard Signed-off-by: Brian Masney --- include/linux/clk-provider.h | 46 +++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h index 7d3747378739..a2348b961538 100644 --- a/include/linux/clk-provider.h +++ b/include/linux/clk-provider.h @@ -10,28 +10,40 @@ #include #include -/* - * flags used across common struct clk. these flags should only affect the - * top-level framework. custom flags for dealing with hardware specifics - * belong in struct clk_foo +/** + * DOC: clk framework flags * - * Please update clk_flags[] in drivers/clk/clk.c when making changes here! + * Flags used across common struct clk. These flags should only affect the + * top-level framework. Custom flags for dealing with hardware specifics + * belong in struct clk_foo. + * + * * CLK_SET_RATE_GATE - must be gated across rate change + * * CLK_SET_PARENT_GATE - must be gated across re-parent + * * CLK_SET_RATE_PARENT - propagate rate change up one level + * * CLK_IGNORE_UNUSED - do not gate even if unused + * * CLK_GET_RATE_NOCACHE - do not use the cached clk rate + * * CLK_SET_RATE_NO_REPARENT - don't re-parent on rate change + * * CLK_GET_ACCURACY_NOCACHE - do not use the cached clk accuracy + * * CLK_RECALC_NEW_RATES - recalc rates after notifications + * * CLK_SET_RATE_UNGATE - clock needs to run to set rate + * * CLK_IS_CRITICAL - do not gate, ever + * * CLK_OPS_PARENT_ENABLE - parents need enable during gate/ungate, set rate and re-parent + * * CLK_DUTY_CYCLE_PARENT - duty cycle call may be forwarded to the parent clock */ -#define CLK_SET_RATE_GATE BIT(0) /* must be gated across rate change */ -#define CLK_SET_PARENT_GATE BIT(1) /* must be gated across re-parent */ -#define CLK_SET_RATE_PARENT BIT(2) /* propagate rate change up one level */ -#define CLK_IGNORE_UNUSED BIT(3) /* do not gate even if unused */ +/* Please update clk_flags[] in drivers/clk/clk.c when making changes here! */ +#define CLK_SET_RATE_GATE BIT(0) +#define CLK_SET_PARENT_GATE BIT(1) +#define CLK_SET_RATE_PARENT BIT(2) +#define CLK_IGNORE_UNUSED BIT(3) /* unused */ /* unused */ -#define CLK_GET_RATE_NOCACHE BIT(6) /* do not use the cached clk rate */ -#define CLK_SET_RATE_NO_REPARENT BIT(7) /* don't re-parent on rate change */ -#define CLK_GET_ACCURACY_NOCACHE BIT(8) /* do not use the cached clk accuracy */ -#define CLK_RECALC_NEW_RATES BIT(9) /* recalc rates after notifications */ -#define CLK_SET_RATE_UNGATE BIT(10) /* clock needs to run to set rate */ -#define CLK_IS_CRITICAL BIT(11) /* do not gate, ever */ -/* parents need enable during gate/ungate, set rate and re-parent */ +#define CLK_GET_RATE_NOCACHE BIT(6) +#define CLK_SET_RATE_NO_REPARENT BIT(7) +#define CLK_GET_ACCURACY_NOCACHE BIT(8) +#define CLK_RECALC_NEW_RATES BIT(9) +#define CLK_SET_RATE_UNGATE BIT(10) +#define CLK_IS_CRITICAL BIT(11) #define CLK_OPS_PARENT_ENABLE BIT(12) -/* duty cycle call may be forwarded to the parent clock */ #define CLK_DUTY_CYCLE_PARENT BIT(13) struct clk; From d131daafa99ffb4ce47e1d0e8a01c8d61a7b7142 Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Mon, 11 May 2026 21:35:05 -0400 Subject: [PATCH 0048/1328] clk: add kernel docs for struct clk_core Document all of the members of struct clk_core. Reviewed-by: Maxime Ripard Acked-by: Randy Dunlap Tested-by: Randy Dunlap Signed-off-by: Brian Masney --- drivers/clk/clk.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index 83a80e5cff2e..f97a7cecb200 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -63,6 +63,57 @@ struct clk_parent_map { int index; }; +/** + * struct clk_core - The internal state of a clk in the clk tree. + * @name: Unique name of the clk for identification. + * @ops: Pointer to hardware-specific operations for this clk. + * @hw: Pointer for traversing from a struct clk to its + * corresponding hardware-specific structure. + * @owner: Kernel module owning this clk (for reference counting). + * @dev: Device associated with this clk (optional) + * @rpm_node: Node for runtime power management list management. + * @of_node: Device tree node associated with this clk (if applicable) + * @parent: Pointer to the current parent in the clock tree. + * @parents: Array of possible parents (for muxes/selectable parents). + * @num_parents: Number of possible parents. + * @new_parent_index: Index of the new parent during parent change operations. + * @rate: Current cached clock rate (Hz). + * @req_rate: The last rate requested by a call to clk_set_rate(). It's + * initialized to clk_core->rate. It's also updated to + * clk_core->rate every time the clock is reparented, and + * when we're doing the orphan -> !orphan transition. + * @new_rate: New rate to be set during a rate change operation. + * @new_parent: Pointer to new parent during parent change. This is also + * used when a clk's rate is changed. + * @new_child: Pointer to new child during reparenting. This is also + * used when a clk's rate is changed. + * @flags: Clock property and capability flags. See + * `clk framework flags`. + * @orphan: True if this clk is currently orphaned. + * @rpm_enabled: True if runtime power management is enabled for this clk. + * @enable_count: Reference count of enables. + * @prepare_count: Reference count of prepares. + * @protect_count: Protection reference count against disable. + * @min_rate: Minimum supported clock rate (Hz). + * @max_rate: Maximum supported clock rate (Hz). + * @accuracy: Accuracy of the clock rate (parts per billion). + * @phase: Current phase (degrees). + * @duty: Current duty cycle configuration (as ratio: num/den). + * @children: All of the children of this clk. + * @child_node: Node for linking as a child in the parent's list. + * @hashtable_node: Node for hash table that allows fast clk lookup by name. + * @clks: All of the clk consumers registered. + * @notifier_count: Number of notifiers registered for this clk. + * @dentry: DebugFS entry for this clk. + * @debug_node: DebugFS node for this clk. + * @ref: Reference count for structure lifetime management. + * + * Managed by the clk framework. Clk providers and consumers do not interact + * with this structure directly. Instead, clk operations flow through the + * framework and the framework manipulates this structure to keep track of + * parent/child relationships, rate, enable state, etc. + * + */ struct clk_core { const char *name; const struct clk_ops *ops; From 378ca82af05ade38ff4fa63bc2bbbcb51cc882b6 Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Mon, 11 May 2026 21:35:06 -0400 Subject: [PATCH 0049/1328] docs: clk: include some identifiers to keep documentation up to date The clk documentation currently has a separate list of some members of struct clk_core and struct clk_ops. Now that all of these structures have proper kernel docs, let's go ahead and just include them here via the identifiers statement in kerneldoc. While changes are being made here, let's also include the clk flags. Reviewed-by: Maxime Ripard Acked-by: Randy Dunlap Tested-by: Randy Dunlap Signed-off-by: Brian Masney --- Documentation/driver-api/clk.rst | 58 +++++++------------------------- 1 file changed, 12 insertions(+), 46 deletions(-) diff --git a/Documentation/driver-api/clk.rst b/Documentation/driver-api/clk.rst index c6aca8186a78..294ead519bbf 100644 --- a/Documentation/driver-api/clk.rst +++ b/Documentation/driver-api/clk.rst @@ -42,21 +42,8 @@ clock interface. Common data structures and api ============================== -Below is the common struct clk_core definition from -drivers/clk/clk.c, modified for brevity:: - - struct clk_core { - const char *name; - const struct clk_ops *ops; - struct clk_hw *hw; - struct module *owner; - struct clk_core *parent; - const char **parent_names; - struct clk_core **parents; - u8 num_parents; - u8 new_parent_index; - ... - }; +.. kernel-doc:: drivers/clk/clk.c + :identifiers: struct clk_core The members above make up the core of the clk tree topology. The clk api itself defines several driver-facing functions which operate on @@ -64,38 +51,17 @@ struct clk. That api is documented in include/linux/clk.h. Platforms and devices utilizing the common struct clk_core use the struct clk_ops pointer in struct clk_core to perform the hardware-specific parts of -the operations defined in clk-provider.h:: +the operations defined in clk-provider.h, and can set one or more +framework-level flags documented below. - struct clk_ops { - int (*prepare)(struct clk_hw *hw); - void (*unprepare)(struct clk_hw *hw); - int (*is_prepared)(struct clk_hw *hw); - void (*unprepare_unused)(struct clk_hw *hw); - int (*enable)(struct clk_hw *hw); - void (*disable)(struct clk_hw *hw); - int (*is_enabled)(struct clk_hw *hw); - void (*disable_unused)(struct clk_hw *hw); - unsigned long (*recalc_rate)(struct clk_hw *hw, - unsigned long parent_rate); - int (*determine_rate)(struct clk_hw *hw, - struct clk_rate_request *req); - int (*set_parent)(struct clk_hw *hw, u8 index); - u8 (*get_parent)(struct clk_hw *hw); - int (*set_rate)(struct clk_hw *hw, - unsigned long rate, - unsigned long parent_rate); - int (*set_rate_and_parent)(struct clk_hw *hw, - unsigned long rate, - unsigned long parent_rate, - u8 index); - unsigned long (*recalc_accuracy)(struct clk_hw *hw, - unsigned long parent_accuracy); - int (*get_phase)(struct clk_hw *hw); - int (*set_phase)(struct clk_hw *hw, int degrees); - void (*init)(struct clk_hw *hw); - void (*debug_init)(struct clk_hw *hw, - struct dentry *dentry); - }; +.. kernel-doc:: include/linux/clk-provider.h + :identifiers: struct clk_ops + +Core flags +========== + +.. kernel-doc:: include/linux/clk-provider.h + :doc: clk framework flags Hardware clk implementations ============================ From 4750122e2f6baa7b46e39510cd97624516a7cc62 Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Mon, 11 May 2026 21:35:07 -0400 Subject: [PATCH 0050/1328] clk: test: convert constants to use HZ_PER_MHZ Convert the DUMMY_CLOCK_* constants over to use HZ_PER_MHZ. Reviewed-by: Maxime Ripard Signed-off-by: Brian Masney --- drivers/clk/clk_test.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/clk/clk_test.c b/drivers/clk/clk_test.c index 94972f38e2e1..1c5c8b7c1f3c 100644 --- a/drivers/clk/clk_test.c +++ b/drivers/clk/clk_test.c @@ -7,6 +7,7 @@ #include #include #include +#include /* Needed for clk_hw_get_clk() */ #include "clk.h" @@ -21,9 +22,9 @@ static const struct clk_ops empty_clk_ops = { }; -#define DUMMY_CLOCK_INIT_RATE (42 * 1000 * 1000) -#define DUMMY_CLOCK_RATE_1 (142 * 1000 * 1000) -#define DUMMY_CLOCK_RATE_2 (242 * 1000 * 1000) +#define DUMMY_CLOCK_INIT_RATE (42 * HZ_PER_MHZ) +#define DUMMY_CLOCK_RATE_1 (142 * HZ_PER_MHZ) +#define DUMMY_CLOCK_RATE_2 (242 * HZ_PER_MHZ) struct clk_dummy_context { struct clk_hw hw; From 5f2db1ce201216e81333ecc2ab51494410b2fe0d Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Tue, 5 May 2026 20:48:57 -0400 Subject: [PATCH 0051/1328] clk: imx: scu: drop redundant init.ops variable assignment The init.ops is assigned a default value, however right below it is an if, else if, and else where all of them also assign a value to init.ops. Drop the redundant init.ops assignment at the top. Fixes: 3b9ea606cda53 ("clk: imx: scu: add cpu frequency scaling support") Reviewed-by: Peng Fan Signed-off-by: Brian Masney --- drivers/clk/imx/clk-scu.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/clk/imx/clk-scu.c b/drivers/clk/imx/clk-scu.c index 9b33df9967ec..658b6d94de9b 100644 --- a/drivers/clk/imx/clk-scu.c +++ b/drivers/clk/imx/clk-scu.c @@ -475,7 +475,6 @@ struct clk_hw *__imx_clk_scu(struct device *dev, const char *name, clk->clk_type = clk_type; init.name = name; - init.ops = &clk_scu_ops; if (rsrc_id == IMX_SC_R_A35 || rsrc_id == IMX_SC_R_A53 || rsrc_id == IMX_SC_R_A72) init.ops = &clk_scu_cpu_ops; else if (rsrc_id == IMX_SC_R_PI_0_PLL) From 862e0773f130d21a65c991f493b01f4ec040e821 Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Tue, 5 May 2026 20:48:58 -0400 Subject: [PATCH 0052/1328] clk: add clk_determine_rate_noop() Add a new helper clk_determine_rate_noop() that's for clocks where the rate rounding is handled by the firmware/hardware, or the clock is capable of any rate. The requested rate is passed through unchanged, and the actual rate will be learned via recalc_rate() after the rate is set. This shared helper will be used to get rid of the driver-specific empty determine rate implementations that are present in the tree. Signed-off-by: Brian Masney --- drivers/clk/clk.c | 18 ++++++++++++++++++ include/linux/clk-provider.h | 1 + 2 files changed, 19 insertions(+) diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index f97a7cecb200..fef87167a60b 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -933,6 +933,24 @@ int clk_hw_determine_rate_no_reparent(struct clk_hw *hw, } EXPORT_SYMBOL_GPL(clk_hw_determine_rate_no_reparent); +/** + * clk_determine_rate_noop - clk_ops::determine_rate noop implementation + * @hw: clk to determine rate on + * @req: rate request + * + * Noop determine rate for clocks where the rate rounding is handled by the + * firmware/hardware, or the clock is capable of any rate. The requested rate is + * passed through unchanged, and the actual rate will be learned via + * recalc_rate() after the rate is set. + * + * Returns: 0 always + */ +int clk_determine_rate_noop(struct clk_hw *hw, struct clk_rate_request *req) +{ + return 0; +} +EXPORT_SYMBOL_GPL(clk_determine_rate_noop); + /*** clk api ***/ static void clk_core_rate_unprotect(struct clk_core *core) diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h index a2348b961538..9d32e4a16eb8 100644 --- a/include/linux/clk-provider.h +++ b/include/linux/clk-provider.h @@ -1472,6 +1472,7 @@ int clk_mux_determine_rate_flags(struct clk_hw *hw, unsigned long flags); int clk_hw_determine_rate_no_reparent(struct clk_hw *hw, struct clk_rate_request *req); +int clk_determine_rate_noop(struct clk_hw *hw, struct clk_rate_request *req); void clk_hw_reparent(struct clk_hw *hw, struct clk_hw *new_parent); void clk_hw_get_rate_range(struct clk_hw *hw, unsigned long *min_rate, unsigned long *max_rate); From 7ea6726c91b51a196d09fafd40b801103c6fa555 Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Tue, 5 May 2026 20:48:59 -0400 Subject: [PATCH 0053/1328] clk: hisilicon: hi3660-stub: use clk_determine_rate_noop() Drop the driver-specific empty determine_rate() function and use the new shared clk_determine_rate_noop() helper. Signed-off-by: Brian Masney --- drivers/clk/hisilicon/clk-hi3660-stub.c | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/drivers/clk/hisilicon/clk-hi3660-stub.c b/drivers/clk/hisilicon/clk-hi3660-stub.c index 7c8b00ee6019..0cb1c0538eae 100644 --- a/drivers/clk/hisilicon/clk-hi3660-stub.c +++ b/drivers/clk/hisilicon/clk-hi3660-stub.c @@ -67,16 +67,6 @@ static unsigned long hi3660_stub_clk_recalc_rate(struct clk_hw *hw, return stub_clk->rate; } -static int hi3660_stub_clk_determine_rate(struct clk_hw *hw, - struct clk_rate_request *req) -{ - /* - * LPM3 handles rate rounding so just return whatever - * rate is requested. - */ - return 0; -} - static int hi3660_stub_clk_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { @@ -97,7 +87,7 @@ static int hi3660_stub_clk_set_rate(struct clk_hw *hw, unsigned long rate, static const struct clk_ops hi3660_stub_clk_ops = { .recalc_rate = hi3660_stub_clk_recalc_rate, - .determine_rate = hi3660_stub_clk_determine_rate, + .determine_rate = clk_determine_rate_noop, .set_rate = hi3660_stub_clk_set_rate, }; From af137873866bce94a076001bde5a81a0a18eedb5 Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Tue, 5 May 2026 20:49:00 -0400 Subject: [PATCH 0054/1328] clk: imx: scu: use clk_determine_rate_noop() Drop the driver-specific empty determine_rate() function and use the new shared clk_determine_rate_noop() helper. Acked-by: Abel Vesa Reviewed-by: Peng Fan Signed-off-by: Brian Masney --- drivers/clk/imx/clk-scu.c | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/drivers/clk/imx/clk-scu.c b/drivers/clk/imx/clk-scu.c index 658b6d94de9b..44220e9c5c2a 100644 --- a/drivers/clk/imx/clk-scu.c +++ b/drivers/clk/imx/clk-scu.c @@ -262,23 +262,6 @@ static unsigned long clk_scu_recalc_rate(struct clk_hw *hw, return le32_to_cpu(msg.data.resp.rate); } -/* - * clk_scu_determine_rate - Returns the closest rate for a SCU clock - * @hw: clock to round rate for - * @req: clock rate request - * - * Returns 0 on success, a negative error on failure - */ -static int clk_scu_determine_rate(struct clk_hw *hw, - struct clk_rate_request *req) -{ - /* - * Assume we support all the requested rate and let the SCU firmware - * to handle the left work - */ - return 0; -} - static int clk_scu_atf_set_cpu_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { @@ -436,7 +419,7 @@ static void clk_scu_unprepare(struct clk_hw *hw) static const struct clk_ops clk_scu_ops = { .recalc_rate = clk_scu_recalc_rate, - .determine_rate = clk_scu_determine_rate, + .determine_rate = clk_determine_rate_noop, .set_rate = clk_scu_set_rate, .get_parent = clk_scu_get_parent, .set_parent = clk_scu_set_parent, @@ -446,7 +429,7 @@ static const struct clk_ops clk_scu_ops = { static const struct clk_ops clk_scu_cpu_ops = { .recalc_rate = clk_scu_recalc_rate, - .determine_rate = clk_scu_determine_rate, + .determine_rate = clk_determine_rate_noop, .set_rate = clk_scu_atf_set_cpu_rate, .prepare = clk_scu_prepare, .unprepare = clk_scu_unprepare, @@ -454,7 +437,7 @@ static const struct clk_ops clk_scu_cpu_ops = { static const struct clk_ops clk_scu_pi_ops = { .recalc_rate = clk_scu_recalc_rate, - .determine_rate = clk_scu_determine_rate, + .determine_rate = clk_determine_rate_noop, .set_rate = clk_scu_set_rate, }; From 42334774a5c85bdf8270f1b01e68578d27611426 Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Tue, 5 May 2026 20:49:01 -0400 Subject: [PATCH 0055/1328] clk: qcom: rpm: use clk_determine_rate_noop() Drop the driver-specific empty determine_rate() function and use the new shared clk_determine_rate_noop() helper. Reviewed-by: Konrad Dybcio Signed-off-by: Brian Masney --- drivers/clk/qcom/clk-rpm.c | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/drivers/clk/qcom/clk-rpm.c b/drivers/clk/qcom/clk-rpm.c index be0145631197..74f569b58017 100644 --- a/drivers/clk/qcom/clk-rpm.c +++ b/drivers/clk/qcom/clk-rpm.c @@ -351,17 +351,6 @@ static int clk_rpm_set_rate(struct clk_hw *hw, return 0; } -static int clk_rpm_determine_rate(struct clk_hw *hw, - struct clk_rate_request *req) -{ - /* - * RPM handles rate rounding and we don't have a way to - * know what the rate will be, so just return whatever - * rate is requested. - */ - return 0; -} - static unsigned long clk_rpm_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) { @@ -383,7 +372,7 @@ static const struct clk_ops clk_rpm_xo_ops = { static const struct clk_ops clk_rpm_fixed_ops = { .prepare = clk_rpm_fixed_prepare, .unprepare = clk_rpm_fixed_unprepare, - .determine_rate = clk_rpm_determine_rate, + .determine_rate = clk_determine_rate_noop, .recalc_rate = clk_rpm_recalc_rate, }; @@ -391,7 +380,7 @@ static const struct clk_ops clk_rpm_ops = { .prepare = clk_rpm_prepare, .unprepare = clk_rpm_unprepare, .set_rate = clk_rpm_set_rate, - .determine_rate = clk_rpm_determine_rate, + .determine_rate = clk_determine_rate_noop, .recalc_rate = clk_rpm_recalc_rate, }; From bc88bae2529b26ea6e93639eacda39ae76e4f030 Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Tue, 5 May 2026 20:49:02 -0400 Subject: [PATCH 0056/1328] clk: qcom: rpmh: use clk_determine_rate_noop() Drop the driver-specific empty determine_rate() function and use the new shared clk_determine_rate_noop() helper. Reviewed-by: Konrad Dybcio Signed-off-by: Brian Masney --- drivers/clk/qcom/clk-rpmh.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/drivers/clk/qcom/clk-rpmh.c b/drivers/clk/qcom/clk-rpmh.c index 6367b2a5a4e0..a224a94ae273 100644 --- a/drivers/clk/qcom/clk-rpmh.c +++ b/drivers/clk/qcom/clk-rpmh.c @@ -319,12 +319,6 @@ static int clk_rpmh_bcm_set_rate(struct clk_hw *hw, unsigned long rate, return 0; } -static int clk_rpmh_determine_rate(struct clk_hw *hw, - struct clk_rate_request *req) -{ - return 0; -} - static unsigned long clk_rpmh_bcm_recalc_rate(struct clk_hw *hw, unsigned long prate) { @@ -337,7 +331,7 @@ static const struct clk_ops clk_rpmh_bcm_ops = { .prepare = clk_rpmh_bcm_prepare, .unprepare = clk_rpmh_bcm_unprepare, .set_rate = clk_rpmh_bcm_set_rate, - .determine_rate = clk_rpmh_determine_rate, + .determine_rate = clk_determine_rate_noop, .recalc_rate = clk_rpmh_bcm_recalc_rate, }; From 972ff9a2510201a6752b865761a1a4355bcf0e3e Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Tue, 5 May 2026 20:49:03 -0400 Subject: [PATCH 0057/1328] clk: qcom: smd-rpm: use clk_determine_rate_noop() Drop the driver-specific empty determine_rate() function and use the new shared clk_determine_rate_noop() helper. Reviewed-by: Konrad Dybcio Signed-off-by: Brian Masney --- drivers/clk/qcom/clk-smd-rpm.c | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/drivers/clk/qcom/clk-smd-rpm.c b/drivers/clk/qcom/clk-smd-rpm.c index 103db984a40b..f28cf9646cb9 100644 --- a/drivers/clk/qcom/clk-smd-rpm.c +++ b/drivers/clk/qcom/clk-smd-rpm.c @@ -370,17 +370,6 @@ static int clk_smd_rpm_set_rate(struct clk_hw *hw, unsigned long rate, return 0; } -static int clk_smd_rpm_determine_rate(struct clk_hw *hw, - struct clk_rate_request *req) -{ - /* - * RPM handles rate rounding and we don't have a way to - * know what the rate will be, so just return whatever - * rate is requested. - */ - return 0; -} - static unsigned long clk_smd_rpm_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) { @@ -427,7 +416,7 @@ static const struct clk_ops clk_smd_rpm_ops = { .prepare = clk_smd_rpm_prepare, .unprepare = clk_smd_rpm_unprepare, .set_rate = clk_smd_rpm_set_rate, - .determine_rate = clk_smd_rpm_determine_rate, + .determine_rate = clk_determine_rate_noop, .recalc_rate = clk_smd_rpm_recalc_rate, }; From 917079a285e7085c1ec663683622607e31e8ebbb Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Tue, 5 May 2026 20:49:04 -0400 Subject: [PATCH 0058/1328] clk: renesas: rzg2l-cpg: use clk_determine_rate_noop() Drop the driver-specific empty determine_rate() function and use the new shared clk_determine_rate_noop() helper. Reviewed-by: Geert Uytterhoeven Acked-by: Geert Uytterhoeven Signed-off-by: Brian Masney --- drivers/clk/renesas/rzg2l-cpg.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/drivers/clk/renesas/rzg2l-cpg.c b/drivers/clk/renesas/rzg2l-cpg.c index 51c9e19e1575..584680509a0a 100644 --- a/drivers/clk/renesas/rzg2l-cpg.c +++ b/drivers/clk/renesas/rzg2l-cpg.c @@ -945,12 +945,6 @@ static unsigned long rzg2l_cpg_sipll5_recalc_rate(struct clk_hw *hw, return pll5_rate; } -static int rzg2l_cpg_sipll5_determine_rate(struct clk_hw *hw, - struct clk_rate_request *req) -{ - return 0; -} - static int rzg2l_cpg_sipll5_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) @@ -1022,7 +1016,7 @@ static int rzg2l_cpg_sipll5_set_rate(struct clk_hw *hw, static const struct clk_ops rzg2l_cpg_sipll5_ops = { .recalc_rate = rzg2l_cpg_sipll5_recalc_rate, - .determine_rate = rzg2l_cpg_sipll5_determine_rate, + .determine_rate = clk_determine_rate_noop, .set_rate = rzg2l_cpg_sipll5_set_rate, }; From df5c96aa570e769aaa0f283c8c8dc462a62341d0 Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Tue, 5 May 2026 20:49:05 -0400 Subject: [PATCH 0059/1328] clk: rp1: use clk_determine_rate_noop() Drop the driver-specific empty determine_rate() function and use the new shared clk_determine_rate_noop() helper. Reviewed-by: Andrea della Porta Signed-off-by: Brian Masney --- drivers/clk/clk-rp1.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/drivers/clk/clk-rp1.c b/drivers/clk/clk-rp1.c index fd144755b879..0f7d9fd77481 100644 --- a/drivers/clk/clk-rp1.c +++ b/drivers/clk/clk-rp1.c @@ -1174,12 +1174,6 @@ static unsigned long rp1_varsrc_recalc_rate(struct clk_hw *hw, return clock->cached_rate; } -static int rp1_varsrc_determine_rate(struct clk_hw *hw, - struct clk_rate_request *req) -{ - return 0; -} - static const struct clk_ops rp1_pll_core_ops = { .is_prepared = rp1_pll_core_is_on, .prepare = rp1_pll_core_on, @@ -1227,7 +1221,7 @@ static const struct clk_ops rp1_clk_ops = { static const struct clk_ops rp1_varsrc_ops = { .set_rate = rp1_varsrc_set_rate, .recalc_rate = rp1_varsrc_recalc_rate, - .determine_rate = rp1_varsrc_determine_rate, + .determine_rate = clk_determine_rate_noop, }; static struct clk_hw *rp1_register_pll(struct rp1_clockman *clockman, From d0f1d7ed5fa97c364f3abd54a35478d6188b28dc Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Tue, 5 May 2026 20:49:06 -0400 Subject: [PATCH 0060/1328] clk: samsung: acpm: use clk_determine_rate_noop() Drop the driver-specific empty determine_rate() function and use the new shared clk_determine_rate_noop() helper. Acked-by: Tudor Ambarus Signed-off-by: Brian Masney --- drivers/clk/samsung/clk-acpm.c | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/drivers/clk/samsung/clk-acpm.c b/drivers/clk/samsung/clk-acpm.c index 25cfa953ccac..7680bc8c5275 100644 --- a/drivers/clk/samsung/clk-acpm.c +++ b/drivers/clk/samsung/clk-acpm.c @@ -72,18 +72,6 @@ static unsigned long acpm_clk_recalc_rate(struct clk_hw *hw, clk->id); } -static int acpm_clk_determine_rate(struct clk_hw *hw, - struct clk_rate_request *req) -{ - /* - * We can't figure out what rate it will be, so just return the - * rate back to the caller. acpm_clk_recalc_rate() will be called - * after the rate is set and we'll know what rate the clock is - * running at then. - */ - return 0; -} - static int acpm_clk_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { @@ -95,7 +83,7 @@ static int acpm_clk_set_rate(struct clk_hw *hw, unsigned long rate, static const struct clk_ops acpm_clk_ops = { .recalc_rate = acpm_clk_recalc_rate, - .determine_rate = acpm_clk_determine_rate, + .determine_rate = clk_determine_rate_noop, .set_rate = acpm_clk_set_rate, }; From 6dae6cf18bc3e5e5b09ea46c9149f6031afeb7e9 Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Tue, 5 May 2026 20:49:07 -0400 Subject: [PATCH 0061/1328] clk: scpi: use clk_determine_rate_noop() Drop the driver-specific empty determine_rate() function and use the new shared clk_determine_rate_noop() helper. Signed-off-by: Brian Masney --- drivers/clk/clk-scpi.c | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/drivers/clk/clk-scpi.c b/drivers/clk/clk-scpi.c index 24cee7c9fda6..2328d2abf6d8 100644 --- a/drivers/clk/clk-scpi.c +++ b/drivers/clk/clk-scpi.c @@ -32,18 +32,6 @@ static unsigned long scpi_clk_recalc_rate(struct clk_hw *hw, return clk->scpi_ops->clk_get_val(clk->id); } -static int scpi_clk_determine_rate(struct clk_hw *hw, - struct clk_rate_request *req) -{ - /* - * We can't figure out what rate it will be, so just return the - * rate back to the caller. scpi_clk_recalc_rate() will be called - * after the rate is set and we'll know what rate the clock is - * running at then. - */ - return 0; -} - static int scpi_clk_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { @@ -54,7 +42,7 @@ static int scpi_clk_set_rate(struct clk_hw *hw, unsigned long rate, static const struct clk_ops scpi_clk_ops = { .recalc_rate = scpi_clk_recalc_rate, - .determine_rate = scpi_clk_determine_rate, + .determine_rate = clk_determine_rate_noop, .set_rate = scpi_clk_set_rate, }; From f934c2c10e741bd96fcc2621dfdf98e932a83527 Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Tue, 5 May 2026 20:49:08 -0400 Subject: [PATCH 0062/1328] clk: sprd: use clk_determine_rate_noop() Drop the driver-specific empty determine_rate() function and use the new shared clk_determine_rate_noop() helper. Signed-off-by: Brian Masney --- drivers/clk/sprd/pll.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/drivers/clk/sprd/pll.c b/drivers/clk/sprd/pll.c index bc2db19aec0e..4aec30777d8a 100644 --- a/drivers/clk/sprd/pll.c +++ b/drivers/clk/sprd/pll.c @@ -254,16 +254,10 @@ static int sprd_pll_clk_prepare(struct clk_hw *hw) return 0; } -static int sprd_pll_determine_rate(struct clk_hw *hw, - struct clk_rate_request *req) -{ - return 0; -} - const struct clk_ops sprd_pll_ops = { .prepare = sprd_pll_clk_prepare, .recalc_rate = sprd_pll_recalc_rate, - .determine_rate = sprd_pll_determine_rate, + .determine_rate = clk_determine_rate_noop, .set_rate = sprd_pll_set_rate, }; EXPORT_SYMBOL_GPL(sprd_pll_ops); From a621e3dfd4cb7b1eebae435ff6b976063e89e8b3 Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Tue, 5 May 2026 20:49:09 -0400 Subject: [PATCH 0063/1328] phy: mediatek: phy-mtk-hdmi-mt2701: use clk_determine_rate_noop() Drop the driver-specific empty determine_rate() function and use the new shared clk_determine_rate_noop() helper. Acked-by: Vinod Koul Signed-off-by: Brian Masney --- drivers/phy/mediatek/phy-mtk-hdmi-mt2701.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/drivers/phy/mediatek/phy-mtk-hdmi-mt2701.c b/drivers/phy/mediatek/phy-mtk-hdmi-mt2701.c index b0b6497e7eed..c9a1f20e939b 100644 --- a/drivers/phy/mediatek/phy-mtk-hdmi-mt2701.c +++ b/drivers/phy/mediatek/phy-mtk-hdmi-mt2701.c @@ -90,12 +90,6 @@ static void mtk_hdmi_pll_unprepare(struct clk_hw *hw) usleep_range(80, 100); } -static int mtk_hdmi_pll_determine_rate(struct clk_hw *hw, - struct clk_rate_request *req) -{ - return 0; -} - static int mtk_hdmi_pll_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { @@ -170,7 +164,7 @@ static const struct clk_ops mtk_hdmi_phy_pll_ops = { .prepare = mtk_hdmi_pll_prepare, .unprepare = mtk_hdmi_pll_unprepare, .set_rate = mtk_hdmi_pll_set_rate, - .determine_rate = mtk_hdmi_pll_determine_rate, + .determine_rate = clk_determine_rate_noop, .recalc_rate = mtk_hdmi_pll_recalc_rate, }; From b37966e116cb0456e5804c485f7d0872d0c17449 Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Tue, 5 May 2026 20:49:10 -0400 Subject: [PATCH 0064/1328] pmdomain: mediatek: airoha: use clk_determine_rate_noop() Drop the driver-specific empty determine_rate() function and use the new shared clk_determine_rate_noop() helper. Acked-by: Ulf Hansson Signed-off-by: Brian Masney --- drivers/pmdomain/mediatek/airoha-cpu-pmdomain.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/drivers/pmdomain/mediatek/airoha-cpu-pmdomain.c b/drivers/pmdomain/mediatek/airoha-cpu-pmdomain.c index 3b1d202f89dc..5efd7a38f4da 100644 --- a/drivers/pmdomain/mediatek/airoha-cpu-pmdomain.c +++ b/drivers/pmdomain/mediatek/airoha-cpu-pmdomain.c @@ -21,12 +21,6 @@ struct airoha_cpu_pmdomain_priv { struct generic_pm_domain pd; }; -static int airoha_cpu_pmdomain_clk_determine_rate(struct clk_hw *hw, - struct clk_rate_request *req) -{ - return 0; -} - static unsigned long airoha_cpu_pmdomain_clk_get(struct clk_hw *hw, unsigned long parent_rate) { @@ -48,7 +42,7 @@ static int airoha_cpu_pmdomain_clk_is_enabled(struct clk_hw *hw) static const struct clk_ops airoha_cpu_pmdomain_clk_ops = { .recalc_rate = airoha_cpu_pmdomain_clk_get, .is_enabled = airoha_cpu_pmdomain_clk_is_enabled, - .determine_rate = airoha_cpu_pmdomain_clk_determine_rate, + .determine_rate = clk_determine_rate_noop, }; static int airoha_cpu_pmdomain_set_performance_state(struct generic_pm_domain *domain, From e3a9ad35af677ecdff0aed12891f9360d1a493bd Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Tue, 5 May 2026 20:49:11 -0400 Subject: [PATCH 0065/1328] pmdomain: mediatek: mtk-mfg: use clk_determine_rate_noop() Drop the driver-specific empty determine_rate() function and use the new shared clk_determine_rate_noop() helper. Acked-by: Ulf Hansson Signed-off-by: Brian Masney --- drivers/pmdomain/mediatek/mtk-mfg-pmdomain.c | 23 ++------------------ 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/drivers/pmdomain/mediatek/mtk-mfg-pmdomain.c b/drivers/pmdomain/mediatek/mtk-mfg-pmdomain.c index 53bdab66cf15..cdcd1d1a812f 100644 --- a/drivers/pmdomain/mediatek/mtk-mfg-pmdomain.c +++ b/drivers/pmdomain/mediatek/mtk-mfg-pmdomain.c @@ -310,25 +310,6 @@ static unsigned long mtk_mfg_recalc_rate_gpu(struct clk_hw *hw, return readl(mfg->shared_mem + GF_REG_FREQ_OUT_GPU) * HZ_PER_KHZ; } -static int mtk_mfg_determine_rate(struct clk_hw *hw, - struct clk_rate_request *req) -{ - /* - * The determine_rate callback needs to be implemented to avoid returning - * the current clock frequency, rather than something even remotely - * close to the frequency that was asked for. - * - * Instead of writing considerable amounts of possibly slow code just to - * somehow figure out which of the three PLLs to round for, or even to - * do a search through one of two OPP tables in order to find the closest - * OPP of a frequency, just return the rate as-is. This avoids devfreq - * "rounding" a request for the lowest frequency to the possibly very - * high current frequency, breaking the powersave governor in the process. - */ - - return 0; -} - static unsigned long mtk_mfg_recalc_rate_stack(struct clk_hw *hw, unsigned long parent_rate) { @@ -339,12 +320,12 @@ static unsigned long mtk_mfg_recalc_rate_stack(struct clk_hw *hw, static const struct clk_ops mtk_mfg_clk_gpu_ops = { .recalc_rate = mtk_mfg_recalc_rate_gpu, - .determine_rate = mtk_mfg_determine_rate, + .determine_rate = clk_determine_rate_noop, }; static const struct clk_ops mtk_mfg_clk_stack_ops = { .recalc_rate = mtk_mfg_recalc_rate_stack, - .determine_rate = mtk_mfg_determine_rate, + .determine_rate = clk_determine_rate_noop, }; static const struct clk_init_data mtk_mfg_clk_gpu_init = { From c67b3c631a1bc02dba2c93bcb75eda54b607eae6 Mon Sep 17 00:00:00 2001 From: John Madieu Date: Mon, 25 May 2026 11:05:56 +0000 Subject: [PATCH 0066/1328] dt-bindings: clock: renesas: Add audio clock inputs for RZ/V2H family RZ/V2H, RZ/V2N, and RZ/G3E support two optional external audio clock inputs (AUDIO_CLKB and AUDIO_CLKC) that can be used by the Audio Clock Generator (ADG) to derive internal audio clocks. The third ADG input (AUDIO_CLKA) is fed internally by the AUDIO_EXTAL pin and does not need a separate binding entry. Update the bindings to allow these optional clocks for all RZ/V2H family SoCs. Signed-off-by: John Madieu Reviewed-by: Geert Uytterhoeven Acked-by: Conor Dooley Link: https://patch.msgid.link/20260525110603.4018170-2-john.madieu.xa@bp.renesas.com Signed-off-by: Geert Uytterhoeven --- .../devicetree/bindings/clock/renesas,rzv2h-cpg.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Documentation/devicetree/bindings/clock/renesas,rzv2h-cpg.yaml b/Documentation/devicetree/bindings/clock/renesas,rzv2h-cpg.yaml index f261445bf341..dd3e66a4559b 100644 --- a/Documentation/devicetree/bindings/clock/renesas,rzv2h-cpg.yaml +++ b/Documentation/devicetree/bindings/clock/renesas,rzv2h-cpg.yaml @@ -26,16 +26,22 @@ properties: maxItems: 1 clocks: + minItems: 3 items: - description: AUDIO_EXTAL clock input - description: RTXIN clock input - description: QEXTAL clock input + - description: AUDIO_CLKB clock input + - description: AUDIO_CLKC clock input clock-names: + minItems: 3 items: - const: audio_extal - const: rtxin - const: qextal + - const: audio_clkb + - const: audio_clkc '#clock-cells': description: | From 7774d5b240e9e1d35775e7a4f58473cf77b25798 Mon Sep 17 00:00:00 2001 From: John Madieu Date: Mon, 25 May 2026 11:05:57 +0000 Subject: [PATCH 0067/1328] clk: renesas: r9a09g047: Add audio clock and reset support Add clock and reset entries for audio-related modules on the RZ/G3E SoC. Target modules are: - SSIU (Serial Sound Interface Unit) with SSI ch0-ch9, - SCU (Sampling Rate Converter Unit) with SRC ch0-ch9, DVC ch0-ch1, CTU/MIX ch0-ch1, - DMACpp (Audio DMA Controller), - ADG (Audio Clock Generator) with divider input clocks and audio master clock outputs. The ADG SSI clock outputs (adg_ssi[0-9]_clk) are parented on CLK_PLLCLN_DIV8 as a deliberate simplification: the ADG dynamically muxes each output between adg_0_clk_195m and audio_clk[a,b,c] at runtime via ADG_AUDIO_CLK_SEL{0,1,2}, owned by the rsnd-adg driver. While at it, reorder plldty_div16 to group it with the other plldty fixed dividers. Signed-off-by: John Madieu Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260525110603.4018170-3-john.madieu.xa@bp.renesas.com Signed-off-by: Geert Uytterhoeven --- drivers/clk/renesas/r9a09g047-cpg.c | 112 +++++++++++++++++++++++++++- 1 file changed, 111 insertions(+), 1 deletion(-) diff --git a/drivers/clk/renesas/r9a09g047-cpg.c b/drivers/clk/renesas/r9a09g047-cpg.c index 94158b6834e6..ab9feb5a33ea 100644 --- a/drivers/clk/renesas/r9a09g047-cpg.c +++ b/drivers/clk/renesas/r9a09g047-cpg.c @@ -23,6 +23,8 @@ enum clk_ids { CLK_AUDIO_EXTAL, CLK_RTXIN, CLK_QEXTAL, + CLK_AUDIO_CLKB, + CLK_AUDIO_CLKC, /* PLL Clocks */ CLK_PLLCM33, @@ -44,6 +46,7 @@ enum clk_ids { CLK_SMUX2_XSPI_CLK1, CLK_PLLCM33_XSPI, CLK_PLLCLN_DIV2, + CLK_PLLCLN_DIV4, CLK_PLLCLN_DIV8, CLK_PLLCLN_DIV16, CLK_PLLCLN_DIV20, @@ -159,6 +162,8 @@ static const struct cpg_core_clk r9a09g047_core_clks[] __initconst = { DEF_INPUT("audio_extal", CLK_AUDIO_EXTAL), DEF_INPUT("rtxin", CLK_RTXIN), DEF_INPUT("qextal", CLK_QEXTAL), + DEF_INPUT("audio_clkb", CLK_AUDIO_CLKB), + DEF_INPUT("audio_clkc", CLK_AUDIO_CLKC), /* PLL Clocks */ DEF_FIXED(".pllcm33", CLK_PLLCM33, CLK_QEXTAL, 200, 3), @@ -183,6 +188,7 @@ static const struct cpg_core_clk r9a09g047_core_clks[] __initconst = { DEF_CSDIV(".pllcm33_xspi", CLK_PLLCM33_XSPI, CLK_SMUX2_XSPI_CLK1, CSDIV0_DIVCTL3, dtable_2_16), DEF_FIXED(".pllcln_div2", CLK_PLLCLN_DIV2, CLK_PLLCLN, 1, 2), + DEF_FIXED(".pllcln_div4", CLK_PLLCLN_DIV4, CLK_PLLCLN, 1, 4), DEF_FIXED(".pllcln_div8", CLK_PLLCLN_DIV8, CLK_PLLCLN, 1, 8), DEF_FIXED(".pllcln_div16", CLK_PLLCLN_DIV16, CLK_PLLCLN, 1, 16), DEF_FIXED(".pllcln_div20", CLK_PLLCLN_DIV20, CLK_PLLCLN, 1, 20), @@ -194,6 +200,7 @@ static const struct cpg_core_clk r9a09g047_core_clks[] __initconst = { DEF_FIXED(".plldty_acpu_div2", CLK_PLLDTY_ACPU_DIV2, CLK_PLLDTY_ACPU, 1, 2), DEF_FIXED(".plldty_acpu_div4", CLK_PLLDTY_ACPU_DIV4, CLK_PLLDTY_ACPU, 1, 4), DEF_FIXED(".plldty_div8", CLK_PLLDTY_DIV8, CLK_PLLDTY, 1, 8), + DEF_FIXED(".plldty_div16", CLK_PLLDTY_DIV16, CLK_PLLDTY, 1, 16), DEF_FIXED(".plleth_250_fix", CLK_PLLETH_DIV_250_FIX, CLK_PLLETH, 1, 4), DEF_FIXED(".plleth_125_fix", CLK_PLLETH_DIV_125_FIX, CLK_PLLETH_DIV_250_FIX, 1, 2), @@ -205,7 +212,6 @@ static const struct cpg_core_clk r9a09g047_core_clks[] __initconst = { DEF_SMUX(".smux2_gbe0_rxclk", CLK_SMUX2_GBE0_RXCLK, SSEL0_SELCTL3, smux2_gbe0_rxclk), DEF_SMUX(".smux2_gbe1_txclk", CLK_SMUX2_GBE1_TXCLK, SSEL1_SELCTL0, smux2_gbe1_txclk), DEF_SMUX(".smux2_gbe1_rxclk", CLK_SMUX2_GBE1_RXCLK, SSEL1_SELCTL1, smux2_gbe1_rxclk), - DEF_FIXED(".plldty_div16", CLK_PLLDTY_DIV16, CLK_PLLDTY, 1, 16), DEF_DDIV(".plldty_rcpu", CLK_PLLDTY_RCPU, CLK_PLLDTY, CDDIV3_DIVCTL2, dtable_2_64), DEF_FIXED(".plldty_rcpu_div4", CLK_PLLDTY_RCPU_DIV4, CLK_PLLDTY_RCPU, 1, 4), @@ -530,8 +536,98 @@ static const struct rzv2h_mod_clk r9a09g047_mod_clks[] __initconst = { BUS_MSTOP(3, BIT(4))), DEF_MOD("ge3d_ace_clk", CLK_PLLDTY_ACPU_DIV2, 15, 2, 7, 18, BUS_MSTOP(3, BIT(4))), + DEF_MOD("ssif_0_clk", CLK_PLLCLN_DIV8, 15, 5, 7, 21, + BUS_MSTOP(2, BIT(3) | BIT(4))), + DEF_MOD("scu_0_clk", CLK_PLLCLN_DIV8, 15, 6, 7, 22, + BUS_MSTOP(2, BIT(0) | BIT(1))), + DEF_MOD("scu_0_clkx2", CLK_PLLCLN_DIV4, 15, 7, 7, 23, + BUS_MSTOP(2, BIT(0) | BIT(1))), + DEF_MOD("dmacpp_0_clk", CLK_PLLCLN_DIV8, 15, 8, 7, 24, + BUS_MSTOP(2, BIT(5))), + DEF_MOD("adg_0_clks1", CLK_PLLCLN_DIV8, 15, 9, 7, 25, + BUS_MSTOP(2, BIT(2))), + DEF_MOD("adg_0_clk_195m", CLK_PLLCLN_DIV8, 15, 10, 7, 26, + BUS_MSTOP(2, BIT(2))), + DEF_MOD("adg_0_audio_clka", CLK_AUDIO_EXTAL, 15, 11, 7, 27, + BUS_MSTOP(2, BIT(2))), + DEF_MOD("adg_0_audio_clkb", CLK_AUDIO_CLKB, 15, 12, 7, 28, + BUS_MSTOP(2, BIT(2))), + DEF_MOD("adg_0_audio_clkc", CLK_AUDIO_CLKC, 15, 13, 7, 29, + BUS_MSTOP(2, BIT(2))), DEF_MOD("tsu_1_pclk", CLK_QEXTAL, 16, 10, 8, 10, BUS_MSTOP(2, BIT(15))), + DEF_MOD("adg_ssi0_clk", CLK_PLLCLN_DIV8, 22, 0, -1, -1, + BUS_MSTOP(2, BIT(2))), + DEF_MOD("adg_ssi1_clk", CLK_PLLCLN_DIV8, 22, 1, -1, -1, + BUS_MSTOP(2, BIT(2))), + DEF_MOD("adg_ssi2_clk", CLK_PLLCLN_DIV8, 22, 2, -1, -1, + BUS_MSTOP(2, BIT(2))), + DEF_MOD("adg_ssi3_clk", CLK_PLLCLN_DIV8, 22, 3, -1, -1, + BUS_MSTOP(2, BIT(2))), + DEF_MOD("adg_ssi4_clk", CLK_PLLCLN_DIV8, 22, 4, -1, -1, + BUS_MSTOP(2, BIT(2))), + DEF_MOD("adg_ssi5_clk", CLK_PLLCLN_DIV8, 22, 5, -1, -1, + BUS_MSTOP(2, BIT(2))), + DEF_MOD("adg_ssi6_clk", CLK_PLLCLN_DIV8, 22, 6, -1, -1, + BUS_MSTOP(2, BIT(2))), + DEF_MOD("adg_ssi7_clk", CLK_PLLCLN_DIV8, 22, 7, -1, -1, + BUS_MSTOP(2, BIT(2))), + DEF_MOD("adg_ssi8_clk", CLK_PLLCLN_DIV8, 22, 8, -1, -1, + BUS_MSTOP(2, BIT(2))), + DEF_MOD("adg_ssi9_clk", CLK_PLLCLN_DIV8, 22, 9, -1, -1, + BUS_MSTOP(2, BIT(2))), + DEF_MOD("dvc0_clk", CLK_PLLCLN_DIV8, 23, 0, -1, -1, + BUS_MSTOP(2, BIT(0) | BIT(1))), + DEF_MOD("dvc1_clk", CLK_PLLCLN_DIV8, 23, 1, -1, -1, + BUS_MSTOP(2, BIT(0) | BIT(1))), + DEF_MOD("ctu0_mix0_clk", CLK_PLLCLN_DIV8, 23, 2, -1, -1, + BUS_MSTOP(2, BIT(0) | BIT(1))), + DEF_MOD("ctu1_mix1_clk", CLK_PLLCLN_DIV8, 23, 3, -1, -1, + BUS_MSTOP(2, BIT(0) | BIT(1))), + DEF_MOD("src0_clk", CLK_PLLCLN_DIV8, 23, 4, -1, -1, + BUS_MSTOP(2, BIT(0) | BIT(1))), + DEF_MOD("src1_clk", CLK_PLLCLN_DIV8, 23, 5, -1, -1, + BUS_MSTOP(2, BIT(0) | BIT(1))), + DEF_MOD("src2_clk", CLK_PLLCLN_DIV8, 23, 6, -1, -1, + BUS_MSTOP(2, BIT(0) | BIT(1))), + DEF_MOD("src3_clk", CLK_PLLCLN_DIV8, 23, 7, -1, -1, + BUS_MSTOP(2, BIT(0) | BIT(1))), + DEF_MOD("src4_clk", CLK_PLLCLN_DIV8, 23, 8, -1, -1, + BUS_MSTOP(2, BIT(0) | BIT(1))), + DEF_MOD("src5_clk", CLK_PLLCLN_DIV8, 23, 9, -1, -1, + BUS_MSTOP(2, BIT(0) | BIT(1))), + DEF_MOD("src6_clk", CLK_PLLCLN_DIV8, 23, 10, -1, -1, + BUS_MSTOP(2, BIT(0) | BIT(1))), + DEF_MOD("src7_clk", CLK_PLLCLN_DIV8, 23, 11, -1, -1, + BUS_MSTOP(2, BIT(0) | BIT(1))), + DEF_MOD("src8_clk", CLK_PLLCLN_DIV8, 23, 12, -1, -1, + BUS_MSTOP(2, BIT(0) | BIT(1))), + DEF_MOD("src9_clk", CLK_PLLCLN_DIV8, 23, 13, -1, -1, + BUS_MSTOP(2, BIT(0) | BIT(1))), + DEF_MOD("scu_supply_clk", CLK_PLLCLN_DIV8, 23, 14, -1, -1, + BUS_MSTOP(2, BIT(0) | BIT(1))), + DEF_MOD("ssiu_supply_clk", CLK_PLLCLN_DIV8, 24, 0, -1, -1, + BUS_MSTOP(2, BIT(3) | BIT(4))), + DEF_MOD("ssi0_clk", CLK_PLLCLN_DIV8, 24, 1, -1, -1, + BUS_MSTOP(2, BIT(3) | BIT(4))), + DEF_MOD("ssi1_clk", CLK_PLLCLN_DIV8, 24, 2, -1, -1, + BUS_MSTOP(2, BIT(3) | BIT(4))), + DEF_MOD("ssi2_clk", CLK_PLLCLN_DIV8, 24, 3, -1, -1, + BUS_MSTOP(2, BIT(3) | BIT(4))), + DEF_MOD("ssi3_clk", CLK_PLLCLN_DIV8, 24, 4, -1, -1, + BUS_MSTOP(2, BIT(3) | BIT(4))), + DEF_MOD("ssi4_clk", CLK_PLLCLN_DIV8, 24, 5, -1, -1, + BUS_MSTOP(2, BIT(3) | BIT(4))), + DEF_MOD("ssi5_clk", CLK_PLLCLN_DIV8, 24, 6, -1, -1, + BUS_MSTOP(2, BIT(3) | BIT(4))), + DEF_MOD("ssi6_clk", CLK_PLLCLN_DIV8, 24, 7, -1, -1, + BUS_MSTOP(2, BIT(3) | BIT(4))), + DEF_MOD("ssi7_clk", CLK_PLLCLN_DIV8, 24, 8, -1, -1, + BUS_MSTOP(2, BIT(3) | BIT(4))), + DEF_MOD("ssi8_clk", CLK_PLLCLN_DIV8, 24, 9, -1, -1, + BUS_MSTOP(2, BIT(3) | BIT(4))), + DEF_MOD("ssi9_clk", CLK_PLLCLN_DIV8, 24, 10, -1, -1, + BUS_MSTOP(2, BIT(3) | BIT(4))), DEF_MOD("dsi_0_vclk2", CLK_SMUX2_DSI1_CLK, 25, 0, 10, 21, BUS_MSTOP(9, BIT(15) | BIT(14))), DEF_MOD("lcdc_1_clk_a", CLK_PLLDTY_ACPU_DIV2, 26, 8, 10, 30, @@ -621,6 +717,20 @@ static const struct rzv2h_reset r9a09g047_resets[] __initconst = { DEF_RST(13, 13, 6, 14), /* GE3D_RESETN */ DEF_RST(13, 14, 6, 15), /* GE3D_AXI_RESETN */ DEF_RST(13, 15, 6, 16), /* GE3D_ACE_RESETN */ + DEF_RST(14, 1, 6, 18), /* SSIF_0_ASYNC_RESET_SSI */ + DEF_RST(14, 2, 6, 19), /* SSIF_0_SYNC_RESET_SSI0 */ + DEF_RST(14, 3, 6, 20), /* SSIF_0_SYNC_RESET_SSI1 */ + DEF_RST(14, 4, 6, 21), /* SSIF_0_SYNC_RESET_SSI2 */ + DEF_RST(14, 5, 6, 22), /* SSIF_0_SYNC_RESET_SSI3 */ + DEF_RST(14, 6, 6, 23), /* SSIF_0_SYNC_RESET_SSI4 */ + DEF_RST(14, 7, 6, 24), /* SSIF_0_SYNC_RESET_SSI5 */ + DEF_RST(14, 8, 6, 25), /* SSIF_0_SYNC_RESET_SSI6 */ + DEF_RST(14, 9, 6, 26), /* SSIF_0_SYNC_RESET_SSI7 */ + DEF_RST(14, 10, 6, 27), /* SSIF_0_SYNC_RESET_SSI8 */ + DEF_RST(14, 11, 6, 28), /* SSIF_0_SYNC_RESET_SSI9 */ + DEF_RST(14, 12, 6, 29), /* SCU_0_RESET_SRU */ + DEF_RST(14, 13, 6, 30), /* DMACpp_0_ARST */ + DEF_RST(14, 14, 6, 31), /* ADG_0_RST_RESET_ADG */ DEF_RST(15, 8, 7, 9), /* TSU_1_PRESETN */ DEF_RST(17, 14, 8, 15), /* LCDC_1_RESET_N */ }; From a1fa2f528c4b43db2772b26c0700b3f30ff28f38 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Wed, 3 Jun 2026 07:57:02 +0100 Subject: [PATCH 0068/1328] clk: renesas: r9a08g046: Add clock and reset entries for SDHI Add clock and reset entries for SDHI. Signed-off-by: Biju Das Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260603065731.93243-3-biju.das.jz@bp.renesas.com Signed-off-by: Geert Uytterhoeven --- drivers/clk/renesas/r9a08g046-cpg.c | 92 +++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/drivers/clk/renesas/r9a08g046-cpg.c b/drivers/clk/renesas/r9a08g046-cpg.c index a57638734ce7..272922b76e1e 100644 --- a/drivers/clk/renesas/r9a08g046-cpg.c +++ b/drivers/clk/renesas/r9a08g046-cpg.c @@ -17,10 +17,13 @@ /* RZ/G3L Specific registers. */ #define G3L_CPG_PL2_DDIV (0x204) #define G3L_CPG_PL3_DDIV (0x208) +#define G3L_CPG_SDHI_DDIV (0x218) #define G3L_CPG_CA55CORE_DDIV (0x234) #define G3L_CPG_RSCI_DDIV (0x238) #define G3L_CPG_RSPI_DDIV (0x23c) +#define G3L_CPG_SDHI_DSEL (0x244) #define G3L_CLKDIVSTATUS (0x280) +#define G3L_CLKSELSTATUS (0x284) #define G3L_CPG_ETH_SSEL (0x410) #define G3L_CPG_RSCI_SSEL (0x414) #define G3L_CPG_RSPI_SSEL (0x418) @@ -30,6 +33,9 @@ #define G3L_DIVPL2A DDIV_PACK(G3L_CPG_PL2_DDIV, 0, 2) #define G3L_DIVPL2B DDIV_PACK(G3L_CPG_PL2_DDIV, 4, 2) #define G3L_DIVPL3A DDIV_PACK(G3L_CPG_PL3_DDIV, 0, 2) +#define G3L_DIV_SDHI0 DDIV_PACK(G3L_CPG_SDHI_DDIV, 0, 2) +#define G3L_DIV_SDHI1 DDIV_PACK(G3L_CPG_SDHI_DDIV, 4, 2) +#define G3L_DIV_SDHI2 DDIV_PACK(G3L_CPG_SDHI_DDIV, 8, 2) #define G3L_DIV_CA55_CORE0 DDIV_PACK(G3L_CPG_CA55CORE_DDIV, 0, 3) #define G3L_DIV_CA55_CORE1 DDIV_PACK(G3L_CPG_CA55CORE_DDIV, 4, 3) #define G3L_DIV_CA55_CORE2 DDIV_PACK(G3L_CPG_CA55CORE_DDIV, 8, 3) @@ -61,8 +67,18 @@ #define G3L_DIV_RSPI0_STS DDIV_PACK(G3L_CLKDIVSTATUS, 20, 1) #define G3L_DIV_RSPI1_STS DDIV_PACK(G3L_CLKDIVSTATUS, 21, 1) #define G3L_DIV_RSPI2_STS DDIV_PACK(G3L_CLKDIVSTATUS, 22, 1) +#define G3L_DIV_SDHI0_STS DDIV_PACK(G3L_CLKDIVSTATUS, 24, 1) +#define G3L_DIV_SDHI1_STS DDIV_PACK(G3L_CLKDIVSTATUS, 25, 1) +#define G3L_DIV_SDHI2_STS DDIV_PACK(G3L_CLKDIVSTATUS, 26, 1) + +#define G3L_SEL_SDHI0_STS SEL_PLL_PACK(G3L_CLKSELSTATUS, 16, 1) +#define G3L_SEL_SDHI1_STS SEL_PLL_PACK(G3L_CLKSELSTATUS, 17, 1) +#define G3L_SEL_SDHI2_STS SEL_PLL_PACK(G3L_CLKSELSTATUS, 18, 1) /* RZ/G3L Specific clocks select. */ +#define G3L_SEL_SDHI0 SEL_PLL_PACK(G3L_CPG_SDHI_DSEL, 0, 2) +#define G3L_SEL_SDHI1 SEL_PLL_PACK(G3L_CPG_SDHI_DSEL, 4, 2) +#define G3L_SEL_SDHI2 SEL_PLL_PACK(G3L_CPG_SDHI_DSEL, 8, 2) #define G3L_SEL_ETH0_TX SEL_PLL_PACK(G3L_CPG_ETH_SSEL, 0, 1) #define G3L_SEL_ETH0_RX SEL_PLL_PACK(G3L_CPG_ETH_SSEL, 1, 1) #define G3L_SEL_ETH0_RM SEL_PLL_PACK(G3L_CPG_ETH_SSEL, 2, 1) @@ -94,6 +110,7 @@ enum clk_ids { /* Internal Core Clocks */ CLK_PLL1, + CLK_PLL1_DIV2, CLK_PLL2, CLK_PLL2_DIV2, CLK_PLL2_DIV2_4, @@ -117,16 +134,29 @@ enum clk_ids { CLK_SEL_RSPI0, CLK_SEL_RSPI1, CLK_SEL_RSPI2, + CLK_SEL_SDHI0, + CLK_SEL_SDHI1, + CLK_SEL_SDHI2, CLK_ETH0_TR, CLK_ETH0_RM, CLK_ETH1_TR, CLK_ETH1_RM, + CLK_SD0_DIV2, + CLK_SD1_DIV2, + CLK_SD2_DIV2, /* Module Clocks */ MOD_CLK_BASE, }; /* Divider tables */ +static const struct clk_div_table dtable_1_4[] = { + { 0, 1 }, + { 1, 2 }, + { 2, 4 }, + { 0, 0 }, +}; + static const struct clk_div_table dtable_1_8[] = { { 0, 1 }, { 1, 2 }, @@ -190,11 +220,15 @@ static const char * const sel_eth1_tx[] = { ".div_eth1_tr", "eth1_txc_tx_clk" }; static const char * const sel_eth1_rx[] = { ".div_eth1_tr", "eth1_rxc_rx_clk" }; static const char * const sel_eth1_rm[] = { ".pll6_div10", "eth1_rxc_rx_clk" }; static const char * const sel_rsci_rspi[] = { ".pll2_div5", ".pll2_div6", ".pll2_div7", ".pll2_div2_4" }; +static const char * const sel_sdhi[] = { ".pll2_div2", ".pll1_div2", ".pll6", ".pll2_div6" }; static const char * const sel_eth0_clk_tx_i[] = { ".sel_eth0_tx", ".div_eth0_rm" }; static const char * const sel_eth0_clk_rx_i[] = { ".sel_eth0_rx", ".div_eth0_rm" }; static const char * const sel_eth1_clk_tx_i[] = { ".sel_eth1_tx", ".div_eth1_rm" }; static const char * const sel_eth1_clk_rx_i[] = { ".sel_eth1_rx", ".div_eth1_rm" }; +/* Mux clock indices tables. */ +static const u32 mtable_sd[] = { 0, 1, 2, 3 }; + static const struct cpg_core_clk r9a08g046_core_clks[] __initconst = { /* External Clock Inputs */ DEF_INPUT("extal", CLK_EXTAL), @@ -210,6 +244,7 @@ static const struct cpg_core_clk r9a08g046_core_clks[] __initconst = { DEF_FIXED(".pll3", CLK_PLL3, CLK_EXTAL, 200, 3), DEF_G3L_PLL(".pll6", CLK_PLL6, CLK_EXTAL, CPG_PLL_CONF(0x50, 0), 500000000UL), + DEF_FIXED(".pll1_div2", CLK_PLL1_DIV2, CLK_PLL1, 1, 2), DEF_FIXED(".pll2_div2", CLK_PLL2_DIV2, CLK_PLL2, 1, 2), DEF_FIXED(".pll2_div2_4", CLK_PLL2_DIV2_4, CLK_PLL2_DIV2, 1, 4), DEF_FIXED(".pll2_div5", CLK_PLL2_DIV5, CLK_PLL2, 1, 5), @@ -217,6 +252,12 @@ static const struct cpg_core_clk r9a08g046_core_clks[] __initconst = { DEF_FIXED(".pll2_div7", CLK_PLL2_DIV7, CLK_PLL2, 1, 7), DEF_FIXED(".pll3_div2", CLK_PLL3_DIV2, CLK_PLL3, 1, 2), DEF_FIXED(".pll6_div10", CLK_PLL6_DIV10, CLK_PLL6, 1, 10), + DEF_SD_MUX(".sel_sdhi0", CLK_SEL_SDHI0, G3L_SEL_SDHI0, G3L_SEL_SDHI0_STS, sel_sdhi, + mtable_sd, 0, NULL), + DEF_SD_MUX(".sel_sdhi1", CLK_SEL_SDHI1, G3L_SEL_SDHI1, G3L_SEL_SDHI1_STS, sel_sdhi, + mtable_sd, 0, NULL), + DEF_SD_MUX(".sel_sdhi2", CLK_SEL_SDHI2, G3L_SEL_SDHI2, G3L_SEL_SDHI2_STS, sel_sdhi, + mtable_sd, 0, NULL), DEF_MUX(".sel_rsci0", CLK_SEL_RSCI0, G3L_SEL_RSCI0, sel_rsci_rspi), DEF_MUX(".sel_rsci1", CLK_SEL_RSCI1, G3L_SEL_RSCI1, sel_rsci_rspi), DEF_MUX(".sel_rsci2", CLK_SEL_RSCI2, G3L_SEL_RSCI2, sel_rsci_rspi), @@ -264,6 +305,18 @@ static const struct cpg_core_clk r9a08g046_core_clks[] __initconst = { dtable_1_8, 0, 200000000UL, 0, NULL), DEF_G3S_DIV("P19", R9A08G046_CLK_P19, CLK_SEL_RSPI2, G3L_DIV_RSPI2, G3L_DIV_RSPI2_STS, dtable_1_8, 0, 200000000UL, 0, NULL), + DEF_G3S_DIV("SD0", R9A08G046_CLK_SD0, CLK_SEL_SDHI0, G3L_DIV_SDHI0, G3L_DIV_SDHI0_STS, + dtable_1_4, 800000000UL, 600000000UL, CLK_SET_RATE_PARENT, + rzg3s_cpg_div_clk_notifier), + DEF_G3S_DIV("SD1", R9A08G046_CLK_SD1, CLK_SEL_SDHI1, G3L_DIV_SDHI1, G3L_DIV_SDHI1_STS, + dtable_1_4, 800000000UL, 600000000UL, CLK_SET_RATE_PARENT, + rzg3s_cpg_div_clk_notifier), + DEF_G3S_DIV("SD2", R9A08G046_CLK_SD2, CLK_SEL_SDHI2, G3L_DIV_SDHI2, G3L_DIV_SDHI2_STS, + dtable_1_4, 800000000UL, 600000000UL, CLK_SET_RATE_PARENT, + rzg3s_cpg_div_clk_notifier), + DEF_FIXED(".sd0_div2", CLK_SD0_DIV2, R9A08G046_CLK_SD0, 1, 2), + DEF_FIXED(".sd1_div2", CLK_SD1_DIV2, R9A08G046_CLK_SD1, 1, 2), + DEF_FIXED(".sd2_div2", CLK_SD2_DIV2, R9A08G046_CLK_SD2, 1, 2), DEF_FIXED("HP", R9A08G046_CLK_HP, CLK_PLL6_DIV10, 1, 1), DEF_MUX_FLAGS("ETHTX01", R9A08G046_CLK_ETHTX01, G3L_SEL_ETH0_CLK_TX_I, sel_eth0_clk_tx_i, CLK_SET_RATE_PARENT), @@ -297,6 +350,36 @@ static const struct rzg2l_mod_clk r9a08g046_mod_clks[] = { MSTOP(BUS_REG0, BIT(0))), DEF_MOD("wdt0_clk", R9A08G046_WDT0_CLK, R9A08G046_OSCCLK, 0x548, 1, MSTOP(BUS_REG0, BIT(0))), + DEF_MOD("sdhi0_imclk", R9A08G046_SDHI0_IMCLK, CLK_SD0_DIV2, 0x554, 0, + MSTOP(BUS_PERI_COM, BIT(0))), + DEF_MOD("sdhi0_imclk2", R9A08G046_SDHI0_IMCLK2, CLK_SD0_DIV2, 0x554, 1, + MSTOP(BUS_PERI_COM, BIT(0))), + DEF_MOD("sdhi0_clk_hs", R9A08G046_SDHI0_CLK_HS, R9A08G046_CLK_SD0, 0x554, 2, + MSTOP(BUS_PERI_COM, BIT(0))), + DEF_MOD("sdhi0_iaclks", R9A08G046_SDHI0_IACLKS, R9A08G046_CLK_P1, 0x554, 3, + MSTOP(BUS_PERI_COM, BIT(0))), + DEF_MOD("sdhi0_iaclkm", R9A08G046_SDHI0_IACLKM, R9A08G046_CLK_P1, 0x554, 12, + MSTOP(BUS_PERI_COM, BIT(0))), + DEF_MOD("sdhi1_imclk", R9A08G046_SDHI1_IMCLK, CLK_SD1_DIV2, 0x554, 4, + MSTOP(BUS_PERI_COM, BIT(1))), + DEF_MOD("sdhi1_imclk2", R9A08G046_SDHI1_IMCLK2, CLK_SD1_DIV2, 0x554, 5, + MSTOP(BUS_PERI_COM, BIT(1))), + DEF_MOD("sdhi1_clk_hs", R9A08G046_SDHI1_CLK_HS, R9A08G046_CLK_SD1, 0x554, 6, + MSTOP(BUS_PERI_COM, BIT(1))), + DEF_MOD("sdhi1_iaclks", R9A08G046_SDHI1_IACLKS, R9A08G046_CLK_P1, 0x554, 7, + MSTOP(BUS_PERI_COM, BIT(1))), + DEF_MOD("sdhi1_iaclkm", R9A08G046_SDHI1_IACLKM, R9A08G046_CLK_P1, 0x554, 13, + MSTOP(BUS_PERI_COM, BIT(1))), + DEF_MOD("sdhi2_imclk", R9A08G046_SDHI2_IMCLK, CLK_SD2_DIV2, 0x554, 8, + MSTOP(BUS_PERI_COM, BIT(11))), + DEF_MOD("sdhi2_imclk2", R9A08G046_SDHI2_IMCLK2, CLK_SD2_DIV2, 0x554, 9, + MSTOP(BUS_PERI_COM, BIT(11))), + DEF_MOD("sdhi2_clk_hs", R9A08G046_SDHI2_CLK_HS, R9A08G046_CLK_SD2, 0x554, 10, + MSTOP(BUS_PERI_COM, BIT(11))), + DEF_MOD("sdhi2_iaclks", R9A08G046_SDHI2_IACLKS, R9A08G046_CLK_P1, 0x554, 11, + MSTOP(BUS_PERI_COM, BIT(11))), + DEF_MOD("sdhi2_iaclkm", R9A08G046_SDHI2_IACLKM, R9A08G046_CLK_P1, 0x554, 14, + MSTOP(BUS_PERI_COM, BIT(11))), DEF_MOD("ssi0_pclk2", R9A08G046_SSI0_PCLK2, R9A08G046_CLK_P0, 0x570, 0, MSTOP(BUS_MCPU1, BIT(10))), DEF_MOD("ssi0_pclk_sfr", R9A08G046_SSI0_PCLK_SFR, R9A08G046_CLK_P0, 0x570, 1, @@ -412,6 +495,15 @@ static const struct rzg2l_reset r9a08g046_resets[] = { DEF_RST(R9A08G046_DMAC_ARESETN, 0x82c, 0), DEF_RST(R9A08G046_DMAC_RST_ASYNC, 0x82c, 1), DEF_RST(R9A08G046_WDT0_PRESETN, 0x848, 0), + DEF_RST(R9A08G046_SDHI0_IXRST, 0x854, 0), + DEF_RST(R9A08G046_SDHI1_IXRST, 0x854, 1), + DEF_RST(R9A08G046_SDHI2_IXRST, 0x854, 2), + DEF_RST(R9A08G046_SDHI0_IXRSTAXIM, 0x854, 3), + DEF_RST(R9A08G046_SDHI0_IXRSTAXIS, 0x854, 4), + DEF_RST(R9A08G046_SDHI1_IXRSTAXIM, 0x854, 5), + DEF_RST(R9A08G046_SDHI1_IXRSTAXIS, 0x854, 6), + DEF_RST(R9A08G046_SDHI2_IXRSTAXIM, 0x854, 7), + DEF_RST(R9A08G046_SDHI2_IXRSTAXIS, 0x854, 8), DEF_RST(R9A08G046_SSI0_RST_M2_REG, 0x870, 0), DEF_RST(R9A08G046_SSI1_RST_M2_REG, 0x870, 1), DEF_RST(R9A08G046_SSI2_RST_M2_REG, 0x870, 2), From b63c613a140f2bd16f68cd23ea56ed5fa83698b8 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Thu, 4 Jun 2026 16:18:49 +0100 Subject: [PATCH 0069/1328] clk: renesas: r9a08g046: Add clock and reset entries for GE3D Add clock and reset entries for GE3D. Signed-off-by: Biju Das Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260604151855.307772-2-biju.das.jz@bp.renesas.com Signed-off-by: Geert Uytterhoeven --- drivers/clk/renesas/r9a08g046-cpg.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/drivers/clk/renesas/r9a08g046-cpg.c b/drivers/clk/renesas/r9a08g046-cpg.c index 272922b76e1e..edc83a4104b2 100644 --- a/drivers/clk/renesas/r9a08g046-cpg.c +++ b/drivers/clk/renesas/r9a08g046-cpg.c @@ -18,12 +18,14 @@ #define G3L_CPG_PL2_DDIV (0x204) #define G3L_CPG_PL3_DDIV (0x208) #define G3L_CPG_SDHI_DDIV (0x218) +#define G3L_CPG_GE3D_DDIV (0x224) #define G3L_CPG_CA55CORE_DDIV (0x234) #define G3L_CPG_RSCI_DDIV (0x238) #define G3L_CPG_RSPI_DDIV (0x23c) #define G3L_CPG_SDHI_DSEL (0x244) #define G3L_CLKDIVSTATUS (0x280) #define G3L_CLKSELSTATUS (0x284) +#define G3L_CPG_GE3D_SSEL (0x40c) #define G3L_CPG_ETH_SSEL (0x410) #define G3L_CPG_RSCI_SSEL (0x414) #define G3L_CPG_RSPI_SSEL (0x418) @@ -36,6 +38,7 @@ #define G3L_DIV_SDHI0 DDIV_PACK(G3L_CPG_SDHI_DDIV, 0, 2) #define G3L_DIV_SDHI1 DDIV_PACK(G3L_CPG_SDHI_DDIV, 4, 2) #define G3L_DIV_SDHI2 DDIV_PACK(G3L_CPG_SDHI_DDIV, 8, 2) +#define G3L_DIV_GE3D DDIV_PACK(G3L_CPG_GE3D_DDIV, 0, 3) #define G3L_DIV_CA55_CORE0 DDIV_PACK(G3L_CPG_CA55CORE_DDIV, 0, 3) #define G3L_DIV_CA55_CORE1 DDIV_PACK(G3L_CPG_CA55CORE_DDIV, 4, 3) #define G3L_DIV_CA55_CORE2 DDIV_PACK(G3L_CPG_CA55CORE_DDIV, 8, 3) @@ -74,6 +77,7 @@ #define G3L_SEL_SDHI0_STS SEL_PLL_PACK(G3L_CLKSELSTATUS, 16, 1) #define G3L_SEL_SDHI1_STS SEL_PLL_PACK(G3L_CLKSELSTATUS, 17, 1) #define G3L_SEL_SDHI2_STS SEL_PLL_PACK(G3L_CLKSELSTATUS, 18, 1) +#define G3L_DIV_GE3D_STS DDIV_PACK(G3L_CLKDIVSTATUS, 27, 1) /* RZ/G3L Specific clocks select. */ #define G3L_SEL_SDHI0 SEL_PLL_PACK(G3L_CPG_SDHI_DSEL, 0, 2) @@ -89,6 +93,7 @@ #define G3L_SEL_ETH1_RM SEL_PLL_PACK(G3L_CPG_ETH_SSEL, 10, 1) #define G3L_SEL_ETH1_CLK_TX_I SEL_PLL_PACK(G3L_CPG_ETH_SSEL, 11, 1) #define G3L_SEL_ETH1_CLK_RX_I SEL_PLL_PACK(G3L_CPG_ETH_SSEL, 12, 1) +#define G3L_SEL_GE3D SEL_PLL_PACK(G3L_CPG_GE3D_SSEL, 0, 2) #define G3L_SEL_RSCI0 SEL_PLL_PACK(G3L_CPG_RSCI_SSEL, 0, 2) #define G3L_SEL_RSCI1 SEL_PLL_PACK(G3L_CPG_RSCI_SSEL, 2, 2) #define G3L_SEL_RSCI2 SEL_PLL_PACK(G3L_CPG_RSCI_SSEL, 4, 2) @@ -119,6 +124,8 @@ enum clk_ids { CLK_PLL2_DIV7, CLK_PLL3, CLK_PLL3_DIV2, + CLK_PLL3_DIV2_2, + CLK_PLL3_DIV3, CLK_PLL6, CLK_PLL6_DIV10, CLK_SEL_ETH0_TX, @@ -127,6 +134,7 @@ enum clk_ids { CLK_SEL_ETH1_TX, CLK_SEL_ETH1_RX, CLK_SEL_ETH1_RM, + CLK_SEL_GE3D, CLK_SEL_RSCI0, CLK_SEL_RSCI1, CLK_SEL_RSCI2, @@ -219,6 +227,7 @@ static const char * const sel_eth0_rm[] = { ".pll6_div10", "eth0_rxc_rx_clk" }; static const char * const sel_eth1_tx[] = { ".div_eth1_tr", "eth1_txc_tx_clk" }; static const char * const sel_eth1_rx[] = { ".div_eth1_tr", "eth1_rxc_rx_clk" }; static const char * const sel_eth1_rm[] = { ".pll6_div10", "eth1_rxc_rx_clk" }; +static const char * const sel_ge3d[] = { ".pll1_div2", ".pll3_div3", ".pll6", ".pll3_div2_2" }; static const char * const sel_rsci_rspi[] = { ".pll2_div5", ".pll2_div6", ".pll2_div7", ".pll2_div2_4" }; static const char * const sel_sdhi[] = { ".pll2_div2", ".pll1_div2", ".pll6", ".pll2_div6" }; static const char * const sel_eth0_clk_tx_i[] = { ".sel_eth0_tx", ".div_eth0_rm" }; @@ -251,6 +260,8 @@ static const struct cpg_core_clk r9a08g046_core_clks[] __initconst = { DEF_FIXED(".pll2_div6", CLK_PLL2_DIV6, CLK_PLL2, 1, 6), DEF_FIXED(".pll2_div7", CLK_PLL2_DIV7, CLK_PLL2, 1, 7), DEF_FIXED(".pll3_div2", CLK_PLL3_DIV2, CLK_PLL3, 1, 2), + DEF_FIXED(".pll3_div2_2", CLK_PLL3_DIV2_2, CLK_PLL3_DIV2, 1, 2), + DEF_FIXED(".pll3_div3", CLK_PLL3_DIV3, CLK_PLL3, 1, 3), DEF_FIXED(".pll6_div10", CLK_PLL6_DIV10, CLK_PLL6, 1, 10), DEF_SD_MUX(".sel_sdhi0", CLK_SEL_SDHI0, G3L_SEL_SDHI0, G3L_SEL_SDHI0_STS, sel_sdhi, mtable_sd, 0, NULL), @@ -258,6 +269,7 @@ static const struct cpg_core_clk r9a08g046_core_clks[] __initconst = { mtable_sd, 0, NULL), DEF_SD_MUX(".sel_sdhi2", CLK_SEL_SDHI2, G3L_SEL_SDHI2, G3L_SEL_SDHI2_STS, sel_sdhi, mtable_sd, 0, NULL), + DEF_MUX(".sel_ge3d", CLK_SEL_GE3D, G3L_SEL_GE3D, sel_ge3d), DEF_MUX(".sel_rsci0", CLK_SEL_RSCI0, G3L_SEL_RSCI0, sel_rsci_rspi), DEF_MUX(".sel_rsci1", CLK_SEL_RSCI1, G3L_SEL_RSCI1, sel_rsci_rspi), DEF_MUX(".sel_rsci2", CLK_SEL_RSCI2, G3L_SEL_RSCI2, sel_rsci_rspi), @@ -332,6 +344,8 @@ static const struct cpg_core_clk r9a08g046_core_clks[] __initconst = { DEF_FIXED("ETHRM1", R9A08G046_CLK_ETHRM1, CLK_SEL_ETH1_RM, 1, 1), DEF_FIXED("ETHTX12", R9A08G046_CLK_ETHTX12, CLK_SEL_ETH1_TX, 1, 1), DEF_FIXED("ETHRX12", R9A08G046_CLK_ETHRX12, CLK_SEL_ETH1_RX, 1, 1), + DEF_G3S_DIV("G", R9A08G046_CLK_G, CLK_SEL_GE3D, G3L_DIV_GE3D, G3L_DIV_GE3D_STS, + dtable_1_32, 0, 0, 0, NULL), DEF_FIXED("OSCCLK", R9A08G046_OSCCLK, CLK_EXTAL, 1, 1), }; @@ -380,6 +394,12 @@ static const struct rzg2l_mod_clk r9a08g046_mod_clks[] = { MSTOP(BUS_PERI_COM, BIT(11))), DEF_MOD("sdhi2_iaclkm", R9A08G046_SDHI2_IACLKM, R9A08G046_CLK_P1, 0x554, 14, MSTOP(BUS_PERI_COM, BIT(11))), + DEF_MOD("ge3d_clk", R9A08G046_GE3D_CLK, R9A08G046_CLK_G, 0x558, 0, + MSTOP(BUS_PERI_VIDEO, BIT(12))), + DEF_MOD("ge3d_axi_clk", R9A08G046_GE3D_AXI_CLK, R9A08G046_CLK_P1, 0x558, 1, + MSTOP(BUS_PERI_VIDEO, BIT(12))), + DEF_MOD("ge3d_ace_clk", R9A08G046_GE3D_ACE_CLK, R9A08G046_CLK_P1, 0x558, 2, + MSTOP(BUS_PERI_VIDEO, BIT(12))), DEF_MOD("ssi0_pclk2", R9A08G046_SSI0_PCLK2, R9A08G046_CLK_P0, 0x570, 0, MSTOP(BUS_MCPU1, BIT(10))), DEF_MOD("ssi0_pclk_sfr", R9A08G046_SSI0_PCLK_SFR, R9A08G046_CLK_P0, 0x570, 1, @@ -504,6 +524,9 @@ static const struct rzg2l_reset r9a08g046_resets[] = { DEF_RST(R9A08G046_SDHI1_IXRSTAXIS, 0x854, 6), DEF_RST(R9A08G046_SDHI2_IXRSTAXIM, 0x854, 7), DEF_RST(R9A08G046_SDHI2_IXRSTAXIS, 0x854, 8), + DEF_RST(R9A08G046_GE3D_RESETN, 0x858, 0), + DEF_RST(R9A08G046_GE3D_AXI_RESETN, 0x858, 1), + DEF_RST(R9A08G046_GE3D_ACE_RESETN, 0x858, 2), DEF_RST(R9A08G046_SSI0_RST_M2_REG, 0x870, 0), DEF_RST(R9A08G046_SSI1_RST_M2_REG, 0x870, 1), DEF_RST(R9A08G046_SSI2_RST_M2_REG, 0x870, 2), From 0a6643b1a5ee72f76ed0c606c1b34c7cf632cbfb Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Mon, 15 Jun 2026 11:48:42 +0100 Subject: [PATCH 0070/1328] clk: renesas: cpg-mssr: Implement dedicated MSTP delay logic for RZ/T2H LCDC and RTC Introduce a dedicated clock delay mechanism, cpg_rzt2h_mstp_delay(), to satisfy the module-stop (MSTP) state release requirements specified in the RZ/T2H hardware manual. Per the hardware manual, while a standard 10 us delay (satisfying 7 dummy reads) is sufficient for most IP blocks, the LCDC requires 100 dummy reads (142 us) and the RTC requires 300 dummy reads (428 us) to stabilize after being released from a module-stop state. Implement a conditional bitmask filter helper that switches wait intervals based on the packaged module clock index. In cpg_mstp_clock_endisable(), the clock index and individual target bits are known, allowing an exact match. In the resume path cpg_mssr_resume_noirq(), where individual bits are not tracked, pass a fallback register index base (`reg * 32`) with bit verification masked out to match on the peripheral's register group block instead. Signed-off-by: Lad Prabhakar Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260615104845.4122868-3-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Geert Uytterhoeven --- drivers/clk/renesas/renesas-cpg-mssr.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/drivers/clk/renesas/renesas-cpg-mssr.c b/drivers/clk/renesas/renesas-cpg-mssr.c index 5b84cbee030b..4ed056b18d31 100644 --- a/drivers/clk/renesas/renesas-cpg-mssr.c +++ b/drivers/clk/renesas/renesas-cpg-mssr.c @@ -253,6 +253,22 @@ static void cpg_rzt2h_mstp_write(struct cpg_mssr_priv *priv, u16 offset, u32 val writel(value, base + RZT2H_MSTPCR_OFFSET(offset)); } +static void cpg_rzt2h_mstp_delay(u32 idx, bool bit_valid) +{ + unsigned int mask = bit_valid ? GENMASK(31, 0) : GENMASK(31, 5); + + if (idx == (MOD_CLK_PACK(1204) & mask)) { + /* LCDC needs 100 dummy reads, or 142us */ + udelay(142); + } else if (idx == (MOD_CLK_PACK(605) & mask)) { + /* RTC needs 300 dummy reads, or 428us */ + udelay(428); + } else { + /* default 7 dummy reads, or 10us */ + udelay(10); + } +} + static int cpg_mstp_clock_endisable(struct clk_hw *hw, bool enable) { struct mstp_clock *clock = to_mstp_clock(hw); @@ -312,7 +328,7 @@ static int cpg_mstp_clock_endisable(struct clk_hw *hw, bool enable) * register, we simply add a delay after the read operation. */ cpg_rzt2h_mstp_read(priv, priv->control_regs[reg]); - udelay(10); + cpg_rzt2h_mstp_delay(clock->index, true); return 0; } @@ -1142,7 +1158,7 @@ static int cpg_mssr_resume_noirq(struct device *dev) cpg_rzt2h_mstp_write(priv, priv->control_regs[reg], newval); /* See cpg_mstp_clock_endisable() on why this is necessary. */ cpg_rzt2h_mstp_read(priv, priv->control_regs[reg]); - udelay(10); + cpg_rzt2h_mstp_delay(reg * 32, false); continue; } else writel(newval, priv->pub.base0 + priv->control_regs[reg]); From 182b03cbe80fea16c8a2f6faaf890e0d8ee0ba46 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Mon, 15 Jun 2026 15:39:43 +0100 Subject: [PATCH 0071/1328] clk: renesas: r9a09g077: Add RTC clocks Add the core and module clock definitions for the Real-Time Clock (RTC) peripheral on the Renesas RZ/T2H (R9A09G077) and RZ/N2H (R9A09G087) SoCs. Signed-off-by: Lad Prabhakar Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260615143943.1610095-3-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Geert Uytterhoeven --- drivers/clk/renesas/r9a09g077-cpg.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/clk/renesas/r9a09g077-cpg.c b/drivers/clk/renesas/r9a09g077-cpg.c index f777601a23b9..e8f760776c99 100644 --- a/drivers/clk/renesas/r9a09g077-cpg.c +++ b/drivers/clk/renesas/r9a09g077-cpg.c @@ -86,7 +86,7 @@ enum rzt2h_clk_types { enum clk_ids { /* Core Clock Outputs exported to DT */ - LAST_DT_CORE_CLK = R9A09G077_PCLKCAN, + LAST_DT_CORE_CLK = R9A09G077_PCLKRTC, /* External Input Clocks */ CLK_EXTAL, @@ -242,6 +242,7 @@ static const struct cpg_core_clk r9a09g077_core_clks[] __initconst = { FSELXSPI1, dtable_6_8_16_32_64), DEF_MUX("PCLKCAN", R9A09G077_PCLKCAN, FSELCANFD, sel_clk_pll4d3_div10_div20, ARRAY_SIZE(sel_clk_pll4d3_div10_div20), 0), + DEF_FIXED("PCLKRTC", R9A09G077_PCLKRTC, CLK_EXTAL, 128, 1), }; static const struct mssr_mod_clk r9a09g077_mod_clks[] __initconst = { @@ -272,6 +273,7 @@ static const struct mssr_mod_clk r9a09g077_mod_clks[] __initconst = { DEF_MOD("sci5fck", 600, CLK_SCI5ASYNC), DEF_MOD("iic2", 601, R9A09G077_CLK_PCLKL), DEF_MOD("spi3", 602, CLK_SPI3ASYNC), + DEF_MOD("rtc", 605, R9A09G077_CLK_PCLKL), DEF_MOD("sdhi0", 1212, R9A09G077_CLK_PCLKAM), DEF_MOD("sdhi1", 1213, R9A09G077_CLK_PCLKAM), }; From 2bbc0f34ef0c056a98f0235fcd8b611e90de5592 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Tue, 16 Jun 2026 11:44:45 +0100 Subject: [PATCH 0072/1328] clk: renesas: r9a08g046: Add USB2.0 clock and reset entries Add module clock and reset definitions for the USB2.0 interfaces on the RZ/G3L (r9a08g046) SoC. Signed-off-by: Biju Das Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260616104459.410743-4-biju.das.jz@bp.renesas.com Signed-off-by: Geert Uytterhoeven --- drivers/clk/renesas/r9a08g046-cpg.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/clk/renesas/r9a08g046-cpg.c b/drivers/clk/renesas/r9a08g046-cpg.c index edc83a4104b2..4488bd1988e8 100644 --- a/drivers/clk/renesas/r9a08g046-cpg.c +++ b/drivers/clk/renesas/r9a08g046-cpg.c @@ -416,6 +416,16 @@ static const struct rzg2l_mod_clk r9a08g046_mod_clks[] = { MSTOP(BUS_MCPU1, BIT(13))), DEF_MOD("ssi3_pclk_sfr", R9A08G046_SSI3_PCLK_SFR, R9A08G046_CLK_P0, 0x570, 7, MSTOP(BUS_MCPU1, BIT(13))), + DEF_MOD("usb_u2h0_hclk", R9A08G046_USB_U2H0_HCLK, R9A08G046_CLK_P1, 0x578, 0, + MSTOP(BUS_PERI_COM, BIT(5))), + DEF_MOD("usb_u2h1_hclk", R9A08G046_USB_U2H1_HCLK, R9A08G046_CLK_P1, 0x578, 1, + MSTOP(BUS_PERI_COM, BIT(7))), + DEF_MOD("usb_u2p0_exr_cpuclk", R9A08G046_USB_U2P0_EXR_CPUCLK, R9A08G046_CLK_P1, 0x578, 2, + MSTOP(BUS_PERI_COM, BIT(6))), + DEF_MOD("usb_pclk", R9A08G046_USB_PCLK, R9A08G046_CLK_P1, 0x578, 3, + MSTOP(BUS_PERI_COM, BIT(4))), + DEF_MOD("usb_u2p1_exr_cpuclk", R9A08G046_USB_U2P1_EXR_CPUCLK, R9A08G046_CLK_P1, 0x578, 4, + MSTOP(BUS_PERI_COM, BIT(13))), DEF_MOD("eth0_clk_axi", R9A08G046_ETH0_CLK_AXI, R9A08G046_CLK_P1, 0x57c, 0, MSTOP(BUS_PERI_COM, BIT(2))), DEF_MOD("eth1_clk_axi", R9A08G046_ETH1_CLK_AXI, R9A08G046_CLK_P1, 0x57c, 1, @@ -531,6 +541,11 @@ static const struct rzg2l_reset r9a08g046_resets[] = { DEF_RST(R9A08G046_SSI1_RST_M2_REG, 0x870, 1), DEF_RST(R9A08G046_SSI2_RST_M2_REG, 0x870, 2), DEF_RST(R9A08G046_SSI3_RST_M2_REG, 0x870, 3), + DEF_RST(R9A08G046_USB_U2H0_HRESETN, 0x878, 0), + DEF_RST(R9A08G046_USB_U2H1_HRESETN, 0x878, 1), + DEF_RST(R9A08G046_USB_U2P0_EXL_SYSRST, 0x878, 2), + DEF_RST(R9A08G046_USB_PRESETN, 0x878, 3), + DEF_RST(R9A08G046_USB_U2P1_EXL_SYSRST, 0x878, 4), DEF_RST(R9A08G046_ETH0_ARESET_N, 0x87c, 0), DEF_RST(R9A08G046_ETH1_ARESET_N, 0x87c, 1), DEF_RST(R9A08G046_I2C0_MRST, 0x880, 0), From 4f4230ff5d0aec3a5f3b3d9bab39b3db13800a44 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Thu, 18 Jun 2026 19:19:45 +0100 Subject: [PATCH 0073/1328] clk: renesas: rzv2h-cpg: Use per-SoC PLL reference frequency for calculations Introduce a per-SoC PLL reference input frequency parameter to avoid relying on a hardcoded 24MHz constant during PLL configuration math. Add an input_fref member to struct rzv2h_pll_limits. In the core calculation helper rzv2h_get_pll_pars(), derive the base input clock rate from limits->input_fref, utilizing the conditional ternary operator to fall back to 24MHz if the struct field is left uninitialized (0), and drop the obsolete macro RZ_V2H_OSC_CLK_IN_MEGA. This abstraction permits the reuse of the common PLL divider logic on newer SoC platforms like the RZ/T2H, which feature a 48 MHz PLL reference clock input instead of the 24 MHz signal used by RZ/V2H(P), without disrupting existing platforms. Signed-off-by: Lad Prabhakar Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260618181949.3036280-2-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Geert Uytterhoeven --- drivers/clk/renesas/rzv2h-cpg.c | 8 ++++---- include/linux/clk/renesas.h | 5 +++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/clk/renesas/rzv2h-cpg.c b/drivers/clk/renesas/rzv2h-cpg.c index e271c04cee34..fff89f2bdc0b 100644 --- a/drivers/clk/renesas/rzv2h-cpg.c +++ b/drivers/clk/renesas/rzv2h-cpg.c @@ -218,7 +218,6 @@ struct rzv2h_plldsi_div_clk { #define to_plldsi_div_clk(_hw) \ container_of(_hw, struct rzv2h_plldsi_div_clk, hw) -#define RZ_V2H_OSC_CLK_IN_MEGA (24 * MEGA) #define RZV2H_MAX_DIV_TABLES (16) /** @@ -242,6 +241,7 @@ struct rzv2h_plldsi_div_clk { bool rzv2h_get_pll_pars(const struct rzv2h_pll_limits *limits, struct rzv2h_pll_pars *pars, u64 freq_millihz) { + unsigned long input_fref = limits->input_fref ?: (24 * MEGA); u64 fout_min_millihz = mul_u32_u32(limits->fout.min, MILLI); u64 fout_max_millihz = mul_u32_u32(limits->fout.max, MILLI); struct rzv2h_pll_pars p, best; @@ -254,7 +254,7 @@ bool rzv2h_get_pll_pars(const struct rzv2h_pll_limits *limits, best.error_millihz = S64_MAX; for (p.p = limits->p.min; p.p <= limits->p.max; p.p++) { - u32 fref = RZ_V2H_OSC_CLK_IN_MEGA / p.p; + u32 fref = input_fref / p.p; u16 divider; for (divider = 1 << limits->s.min, p.s = limits->s.min; @@ -335,9 +335,9 @@ bool rzv2h_get_pll_pars(const struct rzv2h_pll_limits *limits, continue; /* PLL_M component of (output * 65536 * PLL_P) */ - output = mul_u32_u32(p.m * 65536, RZ_V2H_OSC_CLK_IN_MEGA); + output = mul_u32_u32(p.m * 65536, input_fref); /* PLL_K component of (output * 65536 * PLL_P) */ - output += p.k * RZ_V2H_OSC_CLK_IN_MEGA; + output += p.k * input_fref; /* Make it in mHz */ output *= MILLI; output = DIV_U64_ROUND_CLOSEST(output, 65536 * p.p * divider); diff --git a/include/linux/clk/renesas.h b/include/linux/clk/renesas.h index 0949400f44de..798bb0b54bab 100644 --- a/include/linux/clk/renesas.h +++ b/include/linux/clk/renesas.h @@ -53,6 +53,9 @@ static inline void rzg2l_cpg_dsi_div_set_divider(u8 divider, int target) { } * various parameters used to configure a PLL. These limits ensure * the PLL operates within valid and stable ranges. * + * @input_fref: Reference input frequency to the PLL (in Hz). If set + * to 0, a default value of 24MHz is used. + * * @fout: Output frequency range (in MHz) * @fout.min: Minimum allowed output frequency * @fout.max: Maximum allowed output frequency @@ -78,6 +81,8 @@ static inline void rzg2l_cpg_dsi_div_set_divider(u8 divider, int target) { } * @k.max: Maximum delta-sigma value */ struct rzv2h_pll_limits { + u32 input_fref; + struct { u32 min; u32 max; From 73c360100dec2ecb0e905ceb58a01df45fda8988 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Thu, 18 Jun 2026 19:19:48 +0100 Subject: [PATCH 0074/1328] clk: renesas: rzv2h-cpg: Extract PLL calculation helpers into shared library Move the RZ/V2H PLL and divider parameter calculation helpers from rzv2h-cpg.c into a new reusable library. Introduce the CLK_RZV2H_CPG_LIB Kconfig symbol and add rzv2h-cpg-lib.c to host the PLL parameter search algorithms currently implemented by rzv2h_get_pll_pars() and rzv2h_get_pll_divs_pars(). Export the helpers as rzv2h_cpg_get_pll_pars() and rzv2h_cpg_get_pll_divs_pars() for use by other drivers. Update the public clock header to expose the new interfaces and provide compatibility aliases for the existing helper names, avoiding build breakage for current users while allowing future conversions to the new API. This prepares for reuse of the PLL and divider calculation logic by other Renesas clock drivers, including upcoming RZ/T2H and RZ/N2H CPG support, without duplicating the implementation. Signed-off-by: Lad Prabhakar Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260618181949.3036280-5-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Geert Uytterhoeven --- drivers/clk/renesas/Kconfig | 4 + drivers/clk/renesas/Makefile | 1 + drivers/clk/renesas/rzv2h-cpg-lib.c | 217 ++++++++++++++++++++++++++++ drivers/clk/renesas/rzv2h-cpg.c | 203 -------------------------- include/linux/clk/renesas.h | 29 ++-- 5 files changed, 238 insertions(+), 216 deletions(-) create mode 100644 drivers/clk/renesas/rzv2h-cpg-lib.c diff --git a/drivers/clk/renesas/Kconfig b/drivers/clk/renesas/Kconfig index 0203ecbb3882..7659550b8566 100644 --- a/drivers/clk/renesas/Kconfig +++ b/drivers/clk/renesas/Kconfig @@ -260,8 +260,12 @@ config CLK_RZG2L config CLK_RZV2H bool "RZ/{G3E,V2H(P)} family clock support" if COMPILE_TEST + select CLK_RZV2H_CPG_LIB select RESET_CONTROLLER +config CLK_RZV2H_CPG_LIB + bool "RZV2H CPG library functions" if COMPILE_TEST + config CLK_RENESAS_VBATTB tristate "Renesas VBATTB clock controller" depends on ARCH_RZG2L || COMPILE_TEST diff --git a/drivers/clk/renesas/Makefile b/drivers/clk/renesas/Makefile index bd2bed91ab29..ac790e56034b 100644 --- a/drivers/clk/renesas/Makefile +++ b/drivers/clk/renesas/Makefile @@ -52,6 +52,7 @@ obj-$(CONFIG_CLK_RCAR_GEN3_CPG) += rcar-gen3-cpg.o obj-$(CONFIG_CLK_RCAR_GEN4_CPG) += rcar-gen4-cpg.o obj-$(CONFIG_CLK_RCAR_USB2_CLOCK_SEL) += rcar-usb2-clock-sel.o obj-$(CONFIG_CLK_RZG2L) += rzg2l-cpg.o +obj-$(CONFIG_CLK_RZV2H_CPG_LIB) += rzv2h-cpg-lib.o obj-$(CONFIG_CLK_RZV2H) += rzv2h-cpg.o # Generic diff --git a/drivers/clk/renesas/rzv2h-cpg-lib.c b/drivers/clk/renesas/rzv2h-cpg-lib.c new file mode 100644 index 000000000000..124239c7327e --- /dev/null +++ b/drivers/clk/renesas/rzv2h-cpg-lib.c @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * RZV2H CPG Library. This library provides common functions to calculate + * PLL parameters for the RZV2H SoC. + * + * Copyright (C) 2026 Renesas Electronics Corp. + * + */ + +#include +#include +#include +#include +#include + +/** + * rzv2h_cpg_get_pll_pars - Finds the best combination of PLL parameters + * for a given frequency. + * + * @limits: Pointer to the structure containing the limits for the PLL parameters + * @pars: Pointer to the structure where the best calculated PLL parameters values + * will be stored + * @freq_millihz: Target output frequency in millihertz + * + * This function calculates the best set of PLL parameters (M, K, P, S) to achieve + * the desired frequency. + * There is no direct formula to calculate the PLL parameters, as it's an open + * system of equations, therefore this function uses an iterative approach to + * determine the best solution. The best solution is one that minimizes the error + * (desired frequency - actual frequency). + * + * Return: true if a valid set of parameters values is found, false otherwise. + */ +bool rzv2h_cpg_get_pll_pars(const struct rzv2h_pll_limits *limits, + struct rzv2h_pll_pars *pars, u64 freq_millihz) +{ + unsigned long input_fref = limits->input_fref ?: (24 * MEGA); + u64 fout_min_millihz = mul_u32_u32(limits->fout.min, MILLI); + u64 fout_max_millihz = mul_u32_u32(limits->fout.max, MILLI); + struct rzv2h_pll_pars p, best; + + if (freq_millihz > fout_max_millihz || + freq_millihz < fout_min_millihz) + return false; + + /* Initialize best error to maximum possible value */ + best.error_millihz = S64_MAX; + + for (p.p = limits->p.min; p.p <= limits->p.max; p.p++) { + u32 fref = input_fref / p.p; + u16 divider; + + for (divider = 1 << limits->s.min, p.s = limits->s.min; + p.s <= limits->s.max; p.s++, divider <<= 1) { + for (p.m = limits->m.min; p.m <= limits->m.max; p.m++) { + u64 output_m, output_k_range; + s64 pll_k, output_k; + u64 fvco, output; + + /* + * The frequency generated by the PLL + divider + * is calculated as follows: + * + * With: + * Freq = Ffout = Ffvco / 2^(pll_s) + * Ffvco = (pll_m + (pll_k / 65536)) * Ffref + * Ffref = 24MHz / pll_p + * + * Freq can also be rewritten as: + * Freq = Ffvco / 2^(pll_s) + * = ((pll_m + (pll_k / 65536)) * Ffref) / 2^(pll_s) + * = (pll_m * Ffref) / 2^(pll_s) + ((pll_k / 65536) * Ffref) / 2^(pll_s) + * = output_m + output_k + * + * Every parameter has been determined at this + * point, but pll_k. + * + * Considering that: + * limits->k.min <= pll_k <= limits->k.max + * Then: + * -0.5 <= (pll_k / 65536) < 0.5 + * Therefore: + * -Ffref / (2 * 2^(pll_s)) <= output_k < Ffref / (2 * 2^(pll_s)) + */ + + /* Compute output M component (in mHz) */ + output_m = DIV_ROUND_CLOSEST_ULL(mul_u32_u32(p.m, fref) * MILLI, + divider); + /* Compute range for output K (in mHz) */ + output_k_range = DIV_ROUND_CLOSEST_ULL(mul_u32_u32(fref, MILLI), + 2 * divider); + /* + * No point in continuing if we can't achieve + * the desired frequency + */ + if (freq_millihz < (output_m - output_k_range) || + freq_millihz >= (output_m + output_k_range)) { + continue; + } + + /* + * Compute the K component + * + * Since: + * Freq = output_m + output_k + * Then: + * output_k = Freq - output_m + * = ((pll_k / 65536) * Ffref) / 2^(pll_s) + * Therefore: + * pll_k = (output_k * 65536 * 2^(pll_s)) / Ffref + */ + output_k = freq_millihz - output_m; + pll_k = div_s64(output_k * 65536ULL * divider, + fref); + pll_k = DIV_S64_ROUND_CLOSEST(pll_k, MILLI); + + /* Validate K value within allowed limits */ + if (pll_k < limits->k.min || + pll_k > limits->k.max) + continue; + + p.k = pll_k; + + /* Compute (Ffvco * 65536) */ + fvco = mul_u32_u32(p.m * 65536 + p.k, fref); + if (fvco < mul_u32_u32(limits->fvco.min, 65536) || + fvco > mul_u32_u32(limits->fvco.max, 65536)) + continue; + + /* PLL_M component of (output * 65536 * PLL_P) */ + output = mul_u32_u32(p.m * 65536, input_fref); + /* PLL_K component of (output * 65536 * PLL_P) */ + output += p.k * input_fref; + /* Make it in mHz */ + output *= MILLI; + output = DIV_U64_ROUND_CLOSEST(output, 65536 * p.p * divider); + + /* Check output frequency against limits */ + if (output < fout_min_millihz || + output > fout_max_millihz) + continue; + + p.error_millihz = freq_millihz - output; + p.freq_millihz = output; + + /* If an exact match is found, return immediately */ + if (p.error_millihz == 0) { + *pars = p; + return true; + } + + /* Update best match if error is smaller */ + if (abs(best.error_millihz) > abs(p.error_millihz)) + best = p; + } + } + } + + /* If no valid parameters were found, return false */ + if (best.error_millihz == S64_MAX) + return false; + + *pars = best; + return true; +} +EXPORT_SYMBOL_NS_GPL(rzv2h_cpg_get_pll_pars, "RZV2H_CPG"); + +/* + * rzv2h_cpg_get_pll_divs_pars - Finds the best combination of PLL parameters + * and divider value for a given frequency. + * + * @limits: Pointer to the structure containing the limits for the PLL parameters + * @pars: Pointer to the structure where the best calculated PLL parameters and + * divider values will be stored + * @table: Pointer to the array of valid divider values + * @table_size: Size of the divider values array + * @freq_millihz: Target output frequency in millihertz + * + * This function calculates the best set of PLL parameters (M, K, P, S) and divider + * value to achieve the desired frequency. See rzv2h_cpg_get_pll_pars() for more + * details on how the PLL parameters are calculated. + * + * freq_millihz is the desired frequency generated by the PLL followed by a + * a gear. + */ +bool rzv2h_cpg_get_pll_divs_pars(const struct rzv2h_pll_limits *limits, + struct rzv2h_pll_div_pars *pars, + const u8 *table, u8 table_size, u64 freq_millihz) +{ + struct rzv2h_pll_div_pars p, best; + + best.div.error_millihz = S64_MAX; + p.div.error_millihz = S64_MAX; + for (unsigned int i = 0; i < table_size; i++) { + if (!rzv2h_cpg_get_pll_pars(limits, &p.pll, freq_millihz * table[i])) + continue; + + p.div.divider_value = table[i]; + p.div.freq_millihz = DIV_U64_ROUND_CLOSEST(p.pll.freq_millihz, table[i]); + p.div.error_millihz = freq_millihz - p.div.freq_millihz; + + if (p.div.error_millihz == 0) { + *pars = p; + return true; + } + + if (abs(best.div.error_millihz) > abs(p.div.error_millihz)) + best = p; + } + + if (best.div.error_millihz == S64_MAX) + return false; + + *pars = best; + return true; +} +EXPORT_SYMBOL_NS_GPL(rzv2h_cpg_get_pll_divs_pars, "RZV2H_CPG"); diff --git a/drivers/clk/renesas/rzv2h-cpg.c b/drivers/clk/renesas/rzv2h-cpg.c index fff89f2bdc0b..738dfafc6d9c 100644 --- a/drivers/clk/renesas/rzv2h-cpg.c +++ b/drivers/clk/renesas/rzv2h-cpg.c @@ -220,209 +220,6 @@ struct rzv2h_plldsi_div_clk { #define RZV2H_MAX_DIV_TABLES (16) -/** - * rzv2h_get_pll_pars - Finds the best combination of PLL parameters - * for a given frequency. - * - * @limits: Pointer to the structure containing the limits for the PLL parameters - * @pars: Pointer to the structure where the best calculated PLL parameters values - * will be stored - * @freq_millihz: Target output frequency in millihertz - * - * This function calculates the best set of PLL parameters (M, K, P, S) to achieve - * the desired frequency. - * There is no direct formula to calculate the PLL parameters, as it's an open - * system of equations, therefore this function uses an iterative approach to - * determine the best solution. The best solution is one that minimizes the error - * (desired frequency - actual frequency). - * - * Return: true if a valid set of parameters values is found, false otherwise. - */ -bool rzv2h_get_pll_pars(const struct rzv2h_pll_limits *limits, - struct rzv2h_pll_pars *pars, u64 freq_millihz) -{ - unsigned long input_fref = limits->input_fref ?: (24 * MEGA); - u64 fout_min_millihz = mul_u32_u32(limits->fout.min, MILLI); - u64 fout_max_millihz = mul_u32_u32(limits->fout.max, MILLI); - struct rzv2h_pll_pars p, best; - - if (freq_millihz > fout_max_millihz || - freq_millihz < fout_min_millihz) - return false; - - /* Initialize best error to maximum possible value */ - best.error_millihz = S64_MAX; - - for (p.p = limits->p.min; p.p <= limits->p.max; p.p++) { - u32 fref = input_fref / p.p; - u16 divider; - - for (divider = 1 << limits->s.min, p.s = limits->s.min; - p.s <= limits->s.max; p.s++, divider <<= 1) { - for (p.m = limits->m.min; p.m <= limits->m.max; p.m++) { - u64 output_m, output_k_range; - s64 pll_k, output_k; - u64 fvco, output; - - /* - * The frequency generated by the PLL + divider - * is calculated as follows: - * - * With: - * Freq = Ffout = Ffvco / 2^(pll_s) - * Ffvco = (pll_m + (pll_k / 65536)) * Ffref - * Ffref = 24MHz / pll_p - * - * Freq can also be rewritten as: - * Freq = Ffvco / 2^(pll_s) - * = ((pll_m + (pll_k / 65536)) * Ffref) / 2^(pll_s) - * = (pll_m * Ffref) / 2^(pll_s) + ((pll_k / 65536) * Ffref) / 2^(pll_s) - * = output_m + output_k - * - * Every parameter has been determined at this - * point, but pll_k. - * - * Considering that: - * limits->k.min <= pll_k <= limits->k.max - * Then: - * -0.5 <= (pll_k / 65536) < 0.5 - * Therefore: - * -Ffref / (2 * 2^(pll_s)) <= output_k < Ffref / (2 * 2^(pll_s)) - */ - - /* Compute output M component (in mHz) */ - output_m = DIV_ROUND_CLOSEST_ULL(mul_u32_u32(p.m, fref) * MILLI, - divider); - /* Compute range for output K (in mHz) */ - output_k_range = DIV_ROUND_CLOSEST_ULL(mul_u32_u32(fref, MILLI), - 2 * divider); - /* - * No point in continuing if we can't achieve - * the desired frequency - */ - if (freq_millihz < (output_m - output_k_range) || - freq_millihz >= (output_m + output_k_range)) { - continue; - } - - /* - * Compute the K component - * - * Since: - * Freq = output_m + output_k - * Then: - * output_k = Freq - output_m - * = ((pll_k / 65536) * Ffref) / 2^(pll_s) - * Therefore: - * pll_k = (output_k * 65536 * 2^(pll_s)) / Ffref - */ - output_k = freq_millihz - output_m; - pll_k = div_s64(output_k * 65536ULL * divider, - fref); - pll_k = DIV_S64_ROUND_CLOSEST(pll_k, MILLI); - - /* Validate K value within allowed limits */ - if (pll_k < limits->k.min || - pll_k > limits->k.max) - continue; - - p.k = pll_k; - - /* Compute (Ffvco * 65536) */ - fvco = mul_u32_u32(p.m * 65536 + p.k, fref); - if (fvco < mul_u32_u32(limits->fvco.min, 65536) || - fvco > mul_u32_u32(limits->fvco.max, 65536)) - continue; - - /* PLL_M component of (output * 65536 * PLL_P) */ - output = mul_u32_u32(p.m * 65536, input_fref); - /* PLL_K component of (output * 65536 * PLL_P) */ - output += p.k * input_fref; - /* Make it in mHz */ - output *= MILLI; - output = DIV_U64_ROUND_CLOSEST(output, 65536 * p.p * divider); - - /* Check output frequency against limits */ - if (output < fout_min_millihz || - output > fout_max_millihz) - continue; - - p.error_millihz = freq_millihz - output; - p.freq_millihz = output; - - /* If an exact match is found, return immediately */ - if (p.error_millihz == 0) { - *pars = p; - return true; - } - - /* Update best match if error is smaller */ - if (abs(best.error_millihz) > abs(p.error_millihz)) - best = p; - } - } - } - - /* If no valid parameters were found, return false */ - if (best.error_millihz == S64_MAX) - return false; - - *pars = best; - return true; -} -EXPORT_SYMBOL_NS_GPL(rzv2h_get_pll_pars, "RZV2H_CPG"); - -/* - * rzv2h_get_pll_divs_pars - Finds the best combination of PLL parameters - * and divider value for a given frequency. - * - * @limits: Pointer to the structure containing the limits for the PLL parameters - * @pars: Pointer to the structure where the best calculated PLL parameters and - * divider values will be stored - * @table: Pointer to the array of valid divider values - * @table_size: Size of the divider values array - * @freq_millihz: Target output frequency in millihertz - * - * This function calculates the best set of PLL parameters (M, K, P, S) and divider - * value to achieve the desired frequency. See rzv2h_get_pll_pars() for more details - * on how the PLL parameters are calculated. - * - * freq_millihz is the desired frequency generated by the PLL followed by a - * a gear. - */ -bool rzv2h_get_pll_divs_pars(const struct rzv2h_pll_limits *limits, - struct rzv2h_pll_div_pars *pars, - const u8 *table, u8 table_size, u64 freq_millihz) -{ - struct rzv2h_pll_div_pars p, best; - - best.div.error_millihz = S64_MAX; - p.div.error_millihz = S64_MAX; - for (unsigned int i = 0; i < table_size; i++) { - if (!rzv2h_get_pll_pars(limits, &p.pll, freq_millihz * table[i])) - continue; - - p.div.divider_value = table[i]; - p.div.freq_millihz = DIV_U64_ROUND_CLOSEST(p.pll.freq_millihz, table[i]); - p.div.error_millihz = freq_millihz - p.div.freq_millihz; - - if (p.div.error_millihz == 0) { - *pars = p; - return true; - } - - if (abs(best.div.error_millihz) > abs(p.div.error_millihz)) - best = p; - } - - if (best.div.error_millihz == S64_MAX) - return false; - - *pars = best; - return true; -} -EXPORT_SYMBOL_NS_GPL(rzv2h_get_pll_divs_pars, "RZV2H_CPG"); - /** * struct rzv2h_plldsi_mux_clk - PLL DSI MUX clock * diff --git a/include/linux/clk/renesas.h b/include/linux/clk/renesas.h index 798bb0b54bab..c9495558cd5c 100644 --- a/include/linux/clk/renesas.h +++ b/include/linux/clk/renesas.h @@ -189,28 +189,31 @@ struct rzv2h_pll_div_pars { .k = { .min = -32768, .max = 32767 }, \ } \ -#ifdef CONFIG_CLK_RZV2H -bool rzv2h_get_pll_pars(const struct rzv2h_pll_limits *limits, - struct rzv2h_pll_pars *pars, u64 freq_millihz); +#ifdef CONFIG_CLK_RZV2H_CPG_LIB +bool rzv2h_cpg_get_pll_pars(const struct rzv2h_pll_limits *limits, + struct rzv2h_pll_pars *pars, u64 freq_millihz); -bool rzv2h_get_pll_divs_pars(const struct rzv2h_pll_limits *limits, - struct rzv2h_pll_div_pars *pars, - const u8 *table, u8 table_size, u64 freq_millihz); +bool rzv2h_cpg_get_pll_divs_pars(const struct rzv2h_pll_limits *limits, + struct rzv2h_pll_div_pars *pars, + const u8 *table, u8 table_size, u64 freq_millihz); #else -static inline bool rzv2h_get_pll_pars(const struct rzv2h_pll_limits *limits, - struct rzv2h_pll_pars *pars, - u64 freq_millihz) +static inline bool rzv2h_cpg_get_pll_pars(const struct rzv2h_pll_limits *limits, + struct rzv2h_pll_pars *pars, + u64 freq_millihz) { return false; } -static inline bool rzv2h_get_pll_divs_pars(const struct rzv2h_pll_limits *limits, - struct rzv2h_pll_div_pars *pars, - const u8 *table, u8 table_size, - u64 freq_millihz) +static inline bool rzv2h_cpg_get_pll_divs_pars(const struct rzv2h_pll_limits *limits, + struct rzv2h_pll_div_pars *pars, + const u8 *table, u8 table_size, + u64 freq_millihz) { return false; } #endif +#define rzv2h_get_pll_pars rzv2h_cpg_get_pll_pars +#define rzv2h_get_pll_divs_pars rzv2h_cpg_get_pll_divs_pars + #endif From 6745ca5d95040ddfd3669c27d90b8b1bb126a75f Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Thu, 18 Jun 2026 19:19:49 +0100 Subject: [PATCH 0075/1328] clk: renesas: r9a09g077: Add LCDC and PLL3 clock support for RZ/T2H display pipeline Add the clock definitions and PLL logic required to supply the LCDC (VSPD/FCPVD/DU) blocks on the RZ/T2H (R9A09G077) SoC. The RZ/T2H display subsystem depends on a dedicated PLL (PLL3) and a set of new derived clocks. Introduce a new PLL clock type and implement rate recalculation, programming and locking sequences for PLL3 using the RZ/T2H specific divider and VCO limits. Add the corresponding muxes and divider entries, expose the LCDC core clock, and register the LCDC module clock using the correct PCLK parent. This enables the RZ/T2H clock driver to generate the display pipeline clocking tree needed by the DU and VSP-based composition engines, allowing upcoming display support to be integrated without duplicating CPG logic. Signed-off-by: Lad Prabhakar Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260618181949.3036280-6-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Geert Uytterhoeven --- drivers/clk/renesas/Kconfig | 2 + drivers/clk/renesas/r9a09g077-cpg.c | 371 ++++++++++++++++++++++++++++ 2 files changed, 373 insertions(+) diff --git a/drivers/clk/renesas/Kconfig b/drivers/clk/renesas/Kconfig index 7659550b8566..5c0238e878b7 100644 --- a/drivers/clk/renesas/Kconfig +++ b/drivers/clk/renesas/Kconfig @@ -218,10 +218,12 @@ config CLK_R9A09G057 config CLK_R9A09G077 bool "RZ/T2H clock support" if COMPILE_TEST select CLK_RENESAS_CPG_MSSR + select CLK_RZV2H_CPG_LIB config CLK_R9A09G087 bool "RZ/N2H clock support" if COMPILE_TEST select CLK_RENESAS_CPG_MSSR + select CLK_RZV2H_CPG_LIB config CLK_SH73A0 bool "SH-Mobile AG5 clock support" if COMPILE_TEST diff --git a/drivers/clk/renesas/r9a09g077-cpg.c b/drivers/clk/renesas/r9a09g077-cpg.c index e8f760776c99..5640c2035e5a 100644 --- a/drivers/clk/renesas/r9a09g077-cpg.c +++ b/drivers/clk/renesas/r9a09g077-cpg.c @@ -8,16 +8,23 @@ #include #include +#include #include #include +#include +#include #include #include +#include #include +#include #include #include #include "renesas-cpg-mssr.h" +MODULE_IMPORT_NS("RZV2H_CPG"); + #define RZT2H_REG_BLOCK_SHIFT 11 #define RZT2H_REG_OFFSET_MASK GENMASK(10, 0) #define RZT2H_REG_CONF(block, offset) (((block) << RZT2H_REG_BLOCK_SHIFT) | \ @@ -66,11 +73,26 @@ #define DIVSCI2ASYNC CONF_PACK(SCKCR3, 10, 2) #define DIVSCI3ASYNC CONF_PACK(SCKCR3, 12, 2) #define DIVSCI4ASYNC CONF_PACK(SCKCR3, 14, 2) +#define LCDCDIVSEL CONF_PACK(SCKCR3, 20, 4) + +#define PLL3EN FIELD_PREP_CONST(OFFSET_MASK, (0xc0)) + +#define CPG_PLL_EN_EN BIT(0) +#define CPG_PLL3_VCO_CTR0(x) ((x) + 0x4) +#define CPG_PLL3_VCO_CTR0_PDIV GENMASK(21, 16) +#define CPG_PLL3_VCO_CTR0_MDIV GENMASK(9, 0) +#define CPG_PLL3_VCO_CTR1(x) ((x) + 0x8) +#define CPG_PLL3_VCO_CTR1_KDIV GENMASK(31, 16) +#define CPG_PLL3_VCO_CTR1_SDIV GENMASK(2, 0) +#define CPG_PLL_MON(x) ((x) - 0x10) +#define CPG_PLL_MON_LOCK BIT(0) enum rzt2h_clk_types { CLK_TYPE_RZT2H_DIV = CLK_TYPE_CUSTOM, /* Clock with divider */ CLK_TYPE_RZT2H_MUX, /* Clock with clock source selector */ CLK_TYPE_RZT2H_FSELXSPI, /* Clock with FSELXSPIn source selector */ + CLK_TYPE_RZT2H_PLL3, /* PLL3 Clock */ + CLK_TYPE_RZT2H_LCDCDIV, /* LCDC divider clock */ }; #define DEF_DIV(_name, _id, _parent, _conf, _dtable) \ @@ -83,6 +105,47 @@ enum rzt2h_clk_types { #define DEF_DIV_FSELXSPI(_name, _id, _parent, _conf, _dtable) \ DEF_TYPE(_name, _id, CLK_TYPE_RZT2H_FSELXSPI, .conf = _conf, \ .parent = _parent, .dtable = _dtable, .flag = 0) +#define DEF_PLL3(_name, _id, _parent, _conf) \ + DEF_TYPE(_name, _id, CLK_TYPE_RZT2H_PLL3, .conf = _conf, \ + .parent = _parent) +#define DEF_DIV_LCDC(_name, _id, _parent, _conf, _dtable) \ + DEF_TYPE(_name, _id, CLK_TYPE_RZT2H_LCDCDIV, .conf = _conf, \ + .parent = _parent, .dtable = _dtable, .flag = CLK_SET_RATE_PARENT) + +struct pll_clk { + void __iomem *reg; + const struct rzv2h_pll_limits *limits; + struct device *dev; + struct rzv2h_pll_pars pll_parameters; + struct clk_hw hw; + unsigned long cur_rate; +}; + +#define to_pll(_hw) container_of(_hw, struct pll_clk, hw) + +struct r9a09g077_lcdc_div_clk { + const struct clk_div_table *dtable; + void __iomem *reg; + struct device *dev; + struct clk_hw hw; + u32 conf; + u8 divider; +}; + +#define to_lcdc_div_clk(_hw) \ + container_of(_hw, struct r9a09g077_lcdc_div_clk, hw) + +#define RZT2H_MAX_LCDC_DIV_TABLES 16 + +static const struct rzv2h_pll_limits r9a09g077_cpg_pll3_limits = { + .input_fref = 48 * MEGA, + .fout = { .min = 25 * MEGA, .max = 430 * MEGA }, + .fvco = { .min = 1600 * MEGA, .max = 3200 * MEGA }, + .m = { .min = 0x40, .max = 0x3ff }, + .p = { .min = 0x2, .max = 0x8 }, + .s = { .min = 0x0, .max = 0x6 }, + .k = { .min = -32768, .max = 32767 }, +}; enum clk_ids { /* Core Clock Outputs exported to DT */ @@ -96,10 +159,12 @@ enum clk_ids { CLK_PLL0, CLK_PLL1, CLK_PLL2, + CLK_PLL3, CLK_PLL4, CLK_SEL_CLK_PLL0, CLK_SEL_CLK_PLL1, CLK_SEL_CLK_PLL2, + CLK_SEL_CLK_PLL3, CLK_SEL_CLK_PLL4, CLK_PLL4D1, CLK_PLL4D1_DIV3, @@ -107,6 +172,7 @@ enum clk_ids { CLK_PLL4D3, CLK_PLL4D3_DIV10, CLK_PLL4D3_DIV20, + CLK_PLL4D50, CLK_SCI0ASYNC, CLK_SCI1ASYNC, CLK_SCI2ASYNC, @@ -119,6 +185,7 @@ enum clk_ids { CLK_SPI3ASYNC, CLK_DIVSELXSPI0_SCKCR, CLK_DIVSELXSPI1_SCKCR, + CLK_LCDDIVSEL, /* Module Clocks */ MOD_CLK_BASE, @@ -130,6 +197,26 @@ static const struct clk_div_table dtable_1_2[] = { {0, 0}, }; +static const struct clk_div_table dtable_2_32[] = { + {0, 2}, + {1, 4}, + {2, 6}, + {3, 8}, + {4, 10}, + {5, 12}, + {6, 14}, + {7, 16}, + {8, 18}, + {9, 20}, + {10, 22}, + {11, 24}, + {12, 26}, + {13, 28}, + {14, 30}, + {15, 32}, + {0, 0}, +}; + static const struct clk_div_table dtable_6_8_16_32_64[] = { {6, 64}, {5, 32}, @@ -152,6 +239,7 @@ static const struct clk_div_table dtable_24_25_30_32[] = { static const char * const sel_clk_pll0[] = { ".loco", ".pll0" }; static const char * const sel_clk_pll1[] = { ".loco", ".pll1" }; static const char * const sel_clk_pll2[] = { ".loco", ".pll2" }; +static const char * const sel_clk_pll3[] = { ".loco", ".pll3" }; static const char * const sel_clk_pll4[] = { ".loco", ".pll4" }; static const char * const sel_clk_pll4d1_div3_div4[] = { ".pll4d1_div3", ".pll4d1_div4" }; static const char * const sel_clk_pll4d3_div10_div20[] = { ".pll4d3_div10", ".pll4d3_div20" }; @@ -173,10 +261,14 @@ static const struct cpg_core_clk r9a09g077_core_clks[] __initconst = { sel_clk_pll1, ARRAY_SIZE(sel_clk_pll1), CLK_MUX_READ_ONLY), DEF_MUX(".sel_clk_pll2", CLK_SEL_CLK_PLL2, SEL_PLL, sel_clk_pll2, ARRAY_SIZE(sel_clk_pll2), CLK_MUX_READ_ONLY), + DEF_MUX(".sel_clk_pll3", CLK_SEL_CLK_PLL3, SEL_PLL, + sel_clk_pll3, ARRAY_SIZE(sel_clk_pll3), CLK_MUX_READ_ONLY), DEF_MUX(".sel_clk_pll4", CLK_SEL_CLK_PLL4, SEL_PLL, sel_clk_pll4, ARRAY_SIZE(sel_clk_pll4), CLK_MUX_READ_ONLY), DEF_FIXED(".pll4d1", CLK_PLL4D1, CLK_SEL_CLK_PLL4, 1, 1), + DEF_FIXED(".pll4d50", CLK_PLL4D50, CLK_SEL_CLK_PLL4, 50, 1), + DEF_PLL3(".pll3", CLK_PLL3, CLK_PLL4D50, PLL3EN), DEF_FIXED(".pll4d1_div3", CLK_PLL4D1_DIV3, CLK_PLL4D1, 3, 1), DEF_FIXED(".pll4d1_div4", CLK_PLL4D1_DIV4, CLK_PLL4D1, 4, 1), DEF_FIXED(".pll4d3", CLK_PLL4D3, CLK_SEL_CLK_PLL4, 3, 1), @@ -229,6 +321,7 @@ static const struct cpg_core_clk r9a09g077_core_clks[] __initconst = { DEF_FIXED("PCLKL", R9A09G077_CLK_PCLKL, CLK_SEL_CLK_PLL1, 16, 1), DEF_FIXED("PCLKAH", R9A09G077_CLK_PCLKAH, CLK_PLL4D1, 6, 1), DEF_FIXED("PCLKAM", R9A09G077_CLK_PCLKAM, CLK_PLL4D1, 12, 1), + DEF_FIXED("PCLKAL", R9A09G077_CLK_PCLKAL, CLK_PLL4D1, 24, 1), DEF_FIXED("SDHI_CLKHS", R9A09G077_SDHI_CLKHS, CLK_SEL_CLK_PLL2, 1, 1), DEF_FIXED("USB_CLK", R9A09G077_USB_CLK, CLK_PLL4D1, 48, 1), DEF_FIXED("ETCLKA", R9A09G077_ETCLKA, CLK_SEL_CLK_PLL1, 5, 1), @@ -242,6 +335,8 @@ static const struct cpg_core_clk r9a09g077_core_clks[] __initconst = { FSELXSPI1, dtable_6_8_16_32_64), DEF_MUX("PCLKCAN", R9A09G077_PCLKCAN, FSELCANFD, sel_clk_pll4d3_div10_div20, ARRAY_SIZE(sel_clk_pll4d3_div10_div20), 0), + DEF_DIV_LCDC("LCDC_CLKD", R9A09G077_LCDC_CLKD, CLK_SEL_CLK_PLL3, LCDCDIVSEL, + dtable_2_32), DEF_FIXED("PCLKRTC", R9A09G077_PCLKRTC, CLK_EXTAL, 128, 1), }; @@ -274,6 +369,7 @@ static const struct mssr_mod_clk r9a09g077_mod_clks[] __initconst = { DEF_MOD("iic2", 601, R9A09G077_CLK_PCLKL), DEF_MOD("spi3", 602, CLK_SPI3ASYNC), DEF_MOD("rtc", 605, R9A09G077_CLK_PCLKL), + DEF_MOD("lcdc", 1204, R9A09G077_CLK_PCLKAL), DEF_MOD("sdhi0", 1212, R9A09G077_CLK_PCLKAM), DEF_MOD("sdhi1", 1213, R9A09G077_CLK_PCLKAM), }; @@ -483,6 +579,276 @@ r9a09g077_cpg_fselxspi_div_clk_register(struct device *dev, return hw->clk; } +static unsigned long r9a09g077_cpg_pll3_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct pll_clk *pll_clk = to_pll(hw); + u32 ctr0, ctr1; + u8 pdiv, sdiv; + u64 rate; + u16 mdiv; + s16 kdiv; + + ctr0 = readl(CPG_PLL3_VCO_CTR0(pll_clk->reg)); + ctr1 = readl(CPG_PLL3_VCO_CTR1(pll_clk->reg)); + + pdiv = FIELD_GET(CPG_PLL3_VCO_CTR0_PDIV, ctr0); + mdiv = FIELD_GET(CPG_PLL3_VCO_CTR0_MDIV, ctr0); + kdiv = (s16)FIELD_GET(CPG_PLL3_VCO_CTR1_KDIV, ctr1); + sdiv = FIELD_GET(CPG_PLL3_VCO_CTR1_SDIV, ctr1); + + rate = mul_u64_u32_shr(parent_rate, (mdiv << 16) + kdiv, 16 + sdiv); + + return DIV_ROUND_CLOSEST_ULL(rate, pdiv); +} + +static int r9a09g077_cpg_pll3_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct pll_clk *pll_clk = to_pll(hw); + u64 rate_millihz; + + if (req->rate == pll_clk->cur_rate) + return 0; + + rate_millihz = mul_u32_u32(req->rate, MILLI); + if (!rzv2h_cpg_get_pll_pars(pll_clk->limits, &pll_clk->pll_parameters, + rate_millihz)) { + dev_dbg(pll_clk->dev, + "failed to determine rate for req->rate: %lu\n", + req->rate); + return -EINVAL; + } + req->rate = DIV_ROUND_CLOSEST_ULL(pll_clk->pll_parameters.freq_millihz, MILLI); + pll_clk->cur_rate = req->rate; + + return 0; +} + +static int r9a09g077_cpg_pll3_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct pll_clk *pll_clk = to_pll(hw); + struct rzv2h_pll_pars *params = &pll_clk->pll_parameters; + void __iomem *offset = pll_clk->reg; + u32 val; + int ret; + + /* Put PLL into standby mode */ + writel(0, offset); + ret = readl_poll_timeout_atomic(CPG_PLL_MON(offset), + val, !(val & CPG_PLL_MON_LOCK), + 100, 2000); + if (ret) { + dev_err(pll_clk->dev, "Failed to put PLL into standby mode"); + return ret; + } + + /* Output clock setting 1 */ + val = readl(CPG_PLL3_VCO_CTR0(offset)); + FIELD_MODIFY(CPG_PLL3_VCO_CTR0_MDIV, &val, params->m); + FIELD_MODIFY(CPG_PLL3_VCO_CTR0_PDIV, &val, params->p); + writel(val, CPG_PLL3_VCO_CTR0(offset)); + + /* Output clock setting 2 */ + val = readl(CPG_PLL3_VCO_CTR1(offset)); + FIELD_MODIFY(CPG_PLL3_VCO_CTR1_KDIV, &val, params->k); + FIELD_MODIFY(CPG_PLL3_VCO_CTR1_SDIV, &val, params->s); + writel(val, CPG_PLL3_VCO_CTR1(offset)); + + writel(CPG_PLL_EN_EN, offset); + + /* PLL normal mode transition, output clock stability check */ + ret = readl_poll_timeout_atomic(CPG_PLL_MON(offset), + val, (val & CPG_PLL_MON_LOCK), + 100, 2000); + if (ret) { + writel(0, offset); + dev_err(pll_clk->dev, "Failed to put PLL into normal mode"); + return ret; + } + + return 0; +} + +static const struct clk_ops r9a09g077_cpg_pll3_ops = { + .recalc_rate = r9a09g077_cpg_pll3_clk_recalc_rate, + .determine_rate = r9a09g077_cpg_pll3_determine_rate, + .set_rate = r9a09g077_cpg_pll3_set_rate, +}; + +static struct clk * __init +r9a09g077_cpg_pll3_clk_register(struct device *dev, + const struct cpg_core_clk *core, + void __iomem *addr, + struct cpg_mssr_pub *pub, + const struct rzv2h_pll_limits *limits) +{ + struct clk_init_data init = {}; + const struct clk *parent; + const char *parent_name; + struct pll_clk *pll_clk; + int ret; + + parent = pub->clks[core->parent]; + if (IS_ERR(parent)) + return ERR_CAST(parent); + + pll_clk = devm_kzalloc(dev, sizeof(*pll_clk), GFP_KERNEL); + if (!pll_clk) + return ERR_PTR(-ENOMEM); + + parent_name = __clk_get_name(parent); + init.name = core->name; + init.ops = &r9a09g077_cpg_pll3_ops; + init.parent_names = &parent_name; + init.num_parents = 1; + + pll_clk->dev = dev; + pll_clk->hw.init = &init; + pll_clk->reg = addr; + pll_clk->limits = limits; + + ret = devm_clk_hw_register(dev, &pll_clk->hw); + if (ret) + return ERR_PTR(ret); + + return pll_clk->hw.clk; +} + +static int r9a09g077_cpg_lcdc_div_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct r9a09g077_lcdc_div_clk *dsi_div = to_lcdc_div_clk(hw); + struct clk_hw *mux_hw = clk_hw_get_parent(hw); + u8 table[RZT2H_MAX_LCDC_DIV_TABLES] = { 0 }; + struct rzv2h_pll_div_pars dsi_params; + const struct clk_div_table *div; + struct pll_clk *pll_clk; + unsigned int i = 0; + u64 freq_millihz; + + /* index 1 is always .pll3 in sel_clk_pll3[] */ + pll_clk = to_pll(clk_hw_get_parent_by_index(mux_hw, 1)); + + for (div = dsi_div->dtable; div->div; div++) { + if (i >= RZT2H_MAX_LCDC_DIV_TABLES) + return -EINVAL; + table[i++] = div->div; + } + + freq_millihz = mul_u32_u32(req->rate, MILLI); + + if (!rzv2h_cpg_get_pll_divs_pars(pll_clk->limits, &dsi_params, table, + i, freq_millihz)) { + dev_err(dsi_div->dev, + "LCDC divider failed to determine rate for req->rate: %lu\n", + req->rate); + return -EINVAL; + } + + req->rate = DIV_ROUND_CLOSEST_ULL(dsi_params.div.freq_millihz, MILLI); + req->best_parent_rate = req->rate * dsi_params.div.divider_value; + dsi_div->divider = dsi_params.div.divider_value; + pll_clk->cur_rate = req->best_parent_rate; + pll_clk->pll_parameters = dsi_params.pll; + + return 0; +} + +static int r9a09g077_cpg_lcdc_div_set_rate(struct clk_hw *hw, + unsigned long rate, + unsigned long parent_rate) +{ + struct r9a09g077_lcdc_div_clk *dsi_div = to_lcdc_div_clk(hw); + const struct clk_div_table *clkt; + bool divider_found = false; + u32 val, shift; + + for (clkt = dsi_div->dtable; clkt->div; clkt++) { + if (clkt->div == dsi_div->divider) { + divider_found = true; + break; + } + } + + if (!divider_found) + return -EINVAL; + + shift = GET_SHIFT(dsi_div->conf); + val = readl(dsi_div->reg); + val &= ~(clk_div_mask(GET_WIDTH(dsi_div->conf)) << shift); + val |= clkt->val << shift; + writel(val, dsi_div->reg); + + return 0; +} + +static unsigned long +r9a09g077_cpg_lcdc_div_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct r9a09g077_lcdc_div_clk *dsi_div = to_lcdc_div_clk(hw); + u32 div; + + div = readl(dsi_div->reg); + div >>= GET_SHIFT(dsi_div->conf); + div &= clk_div_mask(GET_WIDTH(dsi_div->conf)); + div = dsi_div->dtable[div].div; + + return DIV_ROUND_CLOSEST_ULL(parent_rate, div); +} + +static const struct clk_ops r9a09g077_cpg_lcdc_div_ops = { + .recalc_rate = r9a09g077_cpg_lcdc_div_recalc_rate, + .determine_rate = r9a09g077_cpg_lcdc_div_determine_rate, + .set_rate = r9a09g077_cpg_lcdc_div_set_rate, +}; + +static struct clk * __init +r9a09g077_cpg_lcdc_div_clk_register(struct device *dev, + const struct cpg_core_clk *core, + void __iomem *addr, + struct cpg_mssr_pub *pub) +{ + struct r9a09g077_lcdc_div_clk *clk_hw_data; + struct clk_init_data init = {}; + struct clk **clks = pub->clks; + const struct clk *parent; + const char *parent_name; + struct clk_hw *hw; + int ret; + + parent = clks[core->parent]; + if (IS_ERR(parent)) + return ERR_CAST(parent); + + clk_hw_data = devm_kzalloc(dev, sizeof(*clk_hw_data), GFP_KERNEL); + if (!clk_hw_data) + return ERR_PTR(-ENOMEM); + + clk_hw_data->dtable = core->dtable; + clk_hw_data->reg = addr; + clk_hw_data->conf = core->conf; + clk_hw_data->dev = dev; + clk_hw_data->divider = 32; /* Initialize divider for LCDC */ + + parent_name = __clk_get_name(parent); + init.name = core->name; + init.ops = &r9a09g077_cpg_lcdc_div_ops; + init.flags = core->flag; + init.parent_names = &parent_name; + init.num_parents = 1; + + hw = &clk_hw_data->hw; + hw->init = &init; + ret = devm_clk_hw_register(dev, hw); + if (ret) + return ERR_PTR(ret); + + return hw->clk; +} + static struct clk * __init r9a09g077_cpg_clk_register(struct device *dev, const struct cpg_core_clk *core, const struct cpg_mssr_info *info, @@ -499,6 +865,11 @@ r9a09g077_cpg_clk_register(struct device *dev, const struct cpg_core_clk *core, return r9a09g077_cpg_mux_clk_register(dev, core, addr, pub); case CLK_TYPE_RZT2H_FSELXSPI: return r9a09g077_cpg_fselxspi_div_clk_register(dev, core, addr, pub); + case CLK_TYPE_RZT2H_PLL3: + return r9a09g077_cpg_pll3_clk_register(dev, core, pub->base1 + offset, + pub, &r9a09g077_cpg_pll3_limits); + case CLK_TYPE_RZT2H_LCDCDIV: + return r9a09g077_cpg_lcdc_div_clk_register(dev, core, addr, pub); default: return ERR_PTR(-EINVAL); } From b3044f9e8f1387c0f5fb2e9ff57227c5ba1f768c Mon Sep 17 00:00:00 2001 From: Tommaso Merciai Date: Tue, 23 Jun 2026 17:28:49 +0200 Subject: [PATCH 0076/1328] clk: renesas: r9a09g047: Add LVDS clocks and resets Add LVDS clocks and resets entries to the r9a09g047 CPG driver. Signed-off-by: Tommaso Merciai Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260623152851.478573-1-tommaso.merciai.xr@bp.renesas.com Signed-off-by: Geert Uytterhoeven --- drivers/clk/renesas/r9a09g047-cpg.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/clk/renesas/r9a09g047-cpg.c b/drivers/clk/renesas/r9a09g047-cpg.c index ab9feb5a33ea..f576dd441a11 100644 --- a/drivers/clk/renesas/r9a09g047-cpg.c +++ b/drivers/clk/renesas/r9a09g047-cpg.c @@ -630,6 +630,16 @@ static const struct rzv2h_mod_clk r9a09g047_mod_clks[] __initconst = { BUS_MSTOP(2, BIT(3) | BIT(4))), DEF_MOD("dsi_0_vclk2", CLK_SMUX2_DSI1_CLK, 25, 0, 10, 21, BUS_MSTOP(9, BIT(15) | BIT(14))), + DEF_MOD("lvds_top_clk_ch0", CLK_PLLDSI0, 26, 0, 10, 22, + BUS_MSTOP(13, BIT(0))), + DEF_MOD("lvds_top_clk_ch1", CLK_PLLDSI1, 26, 1, 10, 23, + BUS_MSTOP(13, BIT(0))), + DEF_MOD("lvds_top_clk_dot_ch0", CLK_SMUX2_DSI0_CLK, 26, 2, 10, 24, + BUS_MSTOP(13, BIT(0))), + DEF_MOD("lvds_top_clk_dot_ch1", CLK_SMUX2_DSI1_CLK, 26, 3, 10, 25, + BUS_MSTOP(13, BIT(0))), + DEF_MOD("lvds_top_pclk", CLK_PLLDTY_DIV16, 26, 4, 10, 26, + BUS_MSTOP(13, BIT(0))), DEF_MOD("lcdc_1_clk_a", CLK_PLLDTY_ACPU_DIV2, 26, 8, 10, 30, BUS_MSTOP(13, BIT(5) | BIT(4) | BIT(3))), DEF_MOD("lcdc_1_clk_p", CLK_PLLDTY_DIV16, 26, 9, 10, 31, @@ -732,6 +742,7 @@ static const struct rzv2h_reset r9a09g047_resets[] __initconst = { DEF_RST(14, 13, 6, 30), /* DMACpp_0_ARST */ DEF_RST(14, 14, 6, 31), /* ADG_0_RST_RESET_ADG */ DEF_RST(15, 8, 7, 9), /* TSU_1_PRESETN */ + DEF_RST(17, 10, 8, 11), /* LVDS_TOP_RESET_N */ DEF_RST(17, 14, 8, 15), /* LCDC_1_RESET_N */ }; From 91fb4643954379f8493dba649d520c23f0d1f4e6 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Tue, 23 Jun 2026 17:20:37 +0100 Subject: [PATCH 0077/1328] dt-bindings: clock: renesas,versaclock7: Update maintainer Alex' email is bouncing. Update the maintainers list with my contact details to take over the schema maintenance. While at it, extend the entry in MAINTAINERS to the Versaclock 3 clock driver. Signed-off-by: Biju Das Reviewed-by: Brian Masney Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20250905143441.7082-1-biju.das.jz@bp.renesas.com Link: https://patch.msgid.link/20260623162039.153291-1-biju.das.jz@bp.renesas.com Signed-off-by: Geert Uytterhoeven --- .../devicetree/bindings/clock/renesas,versaclock7.yaml | 2 +- MAINTAINERS | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/clock/renesas,versaclock7.yaml b/Documentation/devicetree/bindings/clock/renesas,versaclock7.yaml index b339f1f9f072..990d287d0a90 100644 --- a/Documentation/devicetree/bindings/clock/renesas,versaclock7.yaml +++ b/Documentation/devicetree/bindings/clock/renesas,versaclock7.yaml @@ -7,7 +7,7 @@ $schema: http://devicetree.org/meta-schemas/core.yaml# title: Renesas Versaclock7 Programmable Clock maintainers: - - Alex Helms + - Biju Das description: | Renesas Versaclock7 is a family of configurable clock generator and diff --git a/MAINTAINERS b/MAINTAINERS index 15011f5752a9..112e5f031007 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -23090,10 +23090,12 @@ L: linux-renesas-soc@vger.kernel.org S: Maintained F: drivers/phy/renesas/phy-rcar-gen3-usb*.c -RENESAS VERSACLOCK 7 CLOCK DRIVER -M: Alex Helms +RENESAS VERSACLOCK 3 and VERSACLOCK 7 CLOCK DRIVER +M: Biju Das S: Maintained +F: Documentation/devicetree/bindings/clock/renesas,5p35023.yaml F: Documentation/devicetree/bindings/clock/renesas,versaclock7.yaml +F: drivers/clk/clk-versaclock3.c F: drivers/clk/clk-versaclock7.c RENESAS X9250 DIGITAL POTENTIOMETERS DRIVER From 782cca1ac6f43f5e6d0b1512b63fa517da223aca Mon Sep 17 00:00:00 2001 From: Jonas Rebmann Date: Fri, 5 Jun 2026 09:27:48 +0200 Subject: [PATCH 0078/1328] leds: pwm-multicolor: Introduce default-intensity property Like all LED drivers, pwm-multicolor supports turning on an LED on boot by setting linux,default-trigger, e.g. to "default-on". pwm-multicolor however scales the brightness of the color-component sub-LEDs with their individual intensity value. Since these intensities are zero-initialized, on boot a trigger is invisible until colors are set from userspace. Fix linux,default-trigger for pwm-multicolor by allowing for nonzero default-intensities but default to 0 for backwards-compatibility. Signed-off-by: Jonas Rebmann Link: https://patch.msgid.link/20260605-multicolor-default-v2-2-ed07271df6b0@pengutronix.de Signed-off-by: Lee Jones --- drivers/leds/rgb/leds-pwm-multicolor.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/leds/rgb/leds-pwm-multicolor.c b/drivers/leds/rgb/leds-pwm-multicolor.c index e0d7d3c9215c..3e515df40583 100644 --- a/drivers/leds/rgb/leds-pwm-multicolor.c +++ b/drivers/leds/rgb/leds-pwm-multicolor.c @@ -95,6 +95,8 @@ static int iterate_subleds(struct device *dev, struct pwm_mc_led *priv, } subled[priv->mc_cdev.num_colors].color_index = color; + fwnode_property_read_u32(fwnode, "default-intensity", + &subled[priv->mc_cdev.num_colors].intensity); priv->mc_cdev.num_colors++; } From 7ddc04d1bd08f80ffc1e2fb97f3fc6cacab0ffc0 Mon Sep 17 00:00:00 2001 From: "Mike Marciniszyn (Meta)" Date: Wed, 20 May 2026 16:03:35 -0400 Subject: [PATCH 0079/1328] leds: trigger: netdev: Extend speeds up to 100G Add 25G, 40G, 50G, and 100G as available speeds to the netdev LED trigger. Signed-off-by: Mike Marciniszyn (Meta) Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260520200337.204431-2-mike.marciniszyn@gmail.com Signed-off-by: Lee Jones --- drivers/leds/trigger/ledtrig-netdev.c | 46 ++++++++++++++++++++++++++- include/linux/leds.h | 4 +++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/drivers/leds/trigger/ledtrig-netdev.c b/drivers/leds/trigger/ledtrig-netdev.c index 64c078e997f2..5b4e92c14dbb 100644 --- a/drivers/leds/trigger/ledtrig-netdev.c +++ b/drivers/leds/trigger/ledtrig-netdev.c @@ -129,6 +129,22 @@ static void set_baseline_state(struct led_netdev_data *trigger_data) trigger_data->link_speed == SPEED_10000) blink_on = true; + if (test_bit(TRIGGER_NETDEV_LINK_25000, &trigger_data->mode) && + trigger_data->link_speed == SPEED_25000) + blink_on = true; + + if (test_bit(TRIGGER_NETDEV_LINK_40000, &trigger_data->mode) && + trigger_data->link_speed == SPEED_40000) + blink_on = true; + + if (test_bit(TRIGGER_NETDEV_LINK_50000, &trigger_data->mode) && + trigger_data->link_speed == SPEED_50000) + blink_on = true; + + if (test_bit(TRIGGER_NETDEV_LINK_100000, &trigger_data->mode) && + trigger_data->link_speed == SPEED_100000) + blink_on = true; + if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &trigger_data->mode) && trigger_data->duplex == DUPLEX_HALF) blink_on = true; @@ -342,6 +358,10 @@ static ssize_t netdev_led_attr_show(struct device *dev, char *buf, case TRIGGER_NETDEV_LINK_2500: case TRIGGER_NETDEV_LINK_5000: case TRIGGER_NETDEV_LINK_10000: + case TRIGGER_NETDEV_LINK_25000: + case TRIGGER_NETDEV_LINK_40000: + case TRIGGER_NETDEV_LINK_50000: + case TRIGGER_NETDEV_LINK_100000: case TRIGGER_NETDEV_HALF_DUPLEX: case TRIGGER_NETDEV_FULL_DUPLEX: case TRIGGER_NETDEV_TX: @@ -378,6 +398,10 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf, case TRIGGER_NETDEV_LINK_2500: case TRIGGER_NETDEV_LINK_5000: case TRIGGER_NETDEV_LINK_10000: + case TRIGGER_NETDEV_LINK_25000: + case TRIGGER_NETDEV_LINK_40000: + case TRIGGER_NETDEV_LINK_50000: + case TRIGGER_NETDEV_LINK_100000: case TRIGGER_NETDEV_HALF_DUPLEX: case TRIGGER_NETDEV_FULL_DUPLEX: case TRIGGER_NETDEV_TX: @@ -401,7 +425,11 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf, test_bit(TRIGGER_NETDEV_LINK_1000, &mode) || test_bit(TRIGGER_NETDEV_LINK_2500, &mode) || test_bit(TRIGGER_NETDEV_LINK_5000, &mode) || - test_bit(TRIGGER_NETDEV_LINK_10000, &mode))) + test_bit(TRIGGER_NETDEV_LINK_10000, &mode) || + test_bit(TRIGGER_NETDEV_LINK_25000, &mode) || + test_bit(TRIGGER_NETDEV_LINK_40000, &mode) || + test_bit(TRIGGER_NETDEV_LINK_50000, &mode) || + test_bit(TRIGGER_NETDEV_LINK_100000, &mode))) return -EINVAL; cancel_delayed_work_sync(&trigger_data->work); @@ -438,6 +466,10 @@ DEFINE_NETDEV_TRIGGER(link_1000, TRIGGER_NETDEV_LINK_1000); DEFINE_NETDEV_TRIGGER(link_2500, TRIGGER_NETDEV_LINK_2500); DEFINE_NETDEV_TRIGGER(link_5000, TRIGGER_NETDEV_LINK_5000); DEFINE_NETDEV_TRIGGER(link_10000, TRIGGER_NETDEV_LINK_10000); +DEFINE_NETDEV_TRIGGER(link_25000, TRIGGER_NETDEV_LINK_25000); +DEFINE_NETDEV_TRIGGER(link_40000, TRIGGER_NETDEV_LINK_40000); +DEFINE_NETDEV_TRIGGER(link_50000, TRIGGER_NETDEV_LINK_50000); +DEFINE_NETDEV_TRIGGER(link_100000, TRIGGER_NETDEV_LINK_100000); DEFINE_NETDEV_TRIGGER(half_duplex, TRIGGER_NETDEV_HALF_DUPLEX); DEFINE_NETDEV_TRIGGER(full_duplex, TRIGGER_NETDEV_FULL_DUPLEX); DEFINE_NETDEV_TRIGGER(tx, TRIGGER_NETDEV_TX); @@ -526,6 +558,10 @@ static umode_t netdev_trig_link_speed_visible(struct kobject *kobj, CHECK_LINK_MODE_ATTR(2500); CHECK_LINK_MODE_ATTR(5000); CHECK_LINK_MODE_ATTR(10000); + CHECK_LINK_MODE_ATTR(25000); + CHECK_LINK_MODE_ATTR(40000); + CHECK_LINK_MODE_ATTR(50000); + CHECK_LINK_MODE_ATTR(100000); } return 0; @@ -538,6 +574,10 @@ static struct attribute *netdev_trig_link_speed_attrs[] = { &dev_attr_link_2500.attr, &dev_attr_link_5000.attr, &dev_attr_link_10000.attr, + &dev_attr_link_25000.attr, + &dev_attr_link_40000.attr, + &dev_attr_link_50000.attr, + &dev_attr_link_100000.attr, NULL }; @@ -673,6 +713,10 @@ static void netdev_trig_work(struct work_struct *work) test_bit(TRIGGER_NETDEV_LINK_2500, &trigger_data->mode) || test_bit(TRIGGER_NETDEV_LINK_5000, &trigger_data->mode) || test_bit(TRIGGER_NETDEV_LINK_10000, &trigger_data->mode) || + test_bit(TRIGGER_NETDEV_LINK_25000, &trigger_data->mode) || + test_bit(TRIGGER_NETDEV_LINK_40000, &trigger_data->mode) || + test_bit(TRIGGER_NETDEV_LINK_50000, &trigger_data->mode) || + test_bit(TRIGGER_NETDEV_LINK_100000, &trigger_data->mode) || test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &trigger_data->mode) || test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &trigger_data->mode); interval = jiffies_to_msecs( diff --git a/include/linux/leds.h b/include/linux/leds.h index b16b803cc1ac..bf31c246d9e2 100644 --- a/include/linux/leds.h +++ b/include/linux/leds.h @@ -607,6 +607,10 @@ enum led_trigger_netdev_modes { TRIGGER_NETDEV_LINK_2500, TRIGGER_NETDEV_LINK_5000, TRIGGER_NETDEV_LINK_10000, + TRIGGER_NETDEV_LINK_25000, + TRIGGER_NETDEV_LINK_40000, + TRIGGER_NETDEV_LINK_50000, + TRIGGER_NETDEV_LINK_100000, TRIGGER_NETDEV_HALF_DUPLEX, TRIGGER_NETDEV_FULL_DUPLEX, TRIGGER_NETDEV_TX, From 53637506884dbd5c91a89b1a3547d99d80f8ed2c Mon Sep 17 00:00:00 2001 From: Yousef Alhouseen Date: Wed, 24 Jun 2026 19:53:53 +0200 Subject: [PATCH 0080/1328] ipmi: ipmb: validate write message length ipmb_write() read message fields before validating the length byte. A zero or short write can read uninitialized stack bytes. A length smaller than the SMBus header underflows the block write length. Require a non-empty buffer and the minimum IPMB request length. Also require the length byte plus payload before parsing the message. Fixes: 51bd6f291583 ("Add support for IPMB driver") Cc: stable@vger.kernel.org Signed-off-by: Yousef Alhouseen Message-ID: <20260624175353.8592-1-alhouseenyousef@gmail.com> Signed-off-by: Corey Minyard --- drivers/char/ipmi/ipmb_dev_int.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/char/ipmi/ipmb_dev_int.c b/drivers/char/ipmi/ipmb_dev_int.c index 680ff15c30ab..e4c50d9ae3e1 100644 --- a/drivers/char/ipmi/ipmb_dev_int.c +++ b/drivers/char/ipmi/ipmb_dev_int.c @@ -141,13 +141,14 @@ static ssize_t ipmb_write(struct file *file, const char __user *buf, u8 msg[MAX_MSG_LEN]; ssize_t ret; - if (count > sizeof(msg)) + if (!count || count > sizeof(msg)) return -EINVAL; if (copy_from_user(&msg, buf, count)) return -EFAULT; - if (count < msg[0]) + if (msg[IPMB_MSG_LEN_IDX] < IPMB_REQUEST_LEN_MIN || + count < (size_t)msg[IPMB_MSG_LEN_IDX] + 1) return -EINVAL; rq_sa = GET_7BIT_ADDR(msg[RQ_SA_8BIT_IDX]); From 6d920a75df9a83ab096b3cde7a643b656e4fdfeb Mon Sep 17 00:00:00 2001 From: Seiji Nishikawa Date: Wed, 1 Jul 2026 02:43:48 +0900 Subject: [PATCH 0081/1328] ipmi: si: Fix NULL pointer dereference after failed registration try_smi_init() allocates new_smi->si_sm and later calls ipmi_register_smi_mod(), which maps to ipmi_add_smi(). During ipmi_add_smi(), the upper IPMI message handler obtains the initial BMC device information through __bmc_get_device_id(). This can fail if the BMC does not return a successful response to the Get Device ID command. When the BMC returns a nonzero completion code, the device-id helper retries the command and eventually returns -EIO if the device ID still cannot be fetched. On this failure path, ipmi_add_smi() logs "Unable to get the device id" and goes to out_err_started, where it invokes the lower driver's shutdown callback. try_smi_init() then logs the returned registration failure: ipmi_si IPI0001:00: IPMI message handler: Unable to get the device id: -5 ipmi_si IPI0001:00: Unable to register device: error -5 For ipmi_si, the shutdown callback is shutdown_smi(), which cleans up the SI state machine data, frees smi_info->si_sm, and sets smi_info->si_sm and smi_info->intf to NULL. However, intf->in_shutdown is not set on this failed-registration rollback path. Therefore, the asynchronous redo_bmc_reg work item can still retry BMC device-id probing after the lower driver has already cleared its SI state machine data. In the observed case, that retry path reached start_next_msg(), which passed the NULL smi_info->si_sm pointer to the selected KCS state machine handler: BUG: unable to handle kernel NULL pointer dereference at 0000000000000000 Workqueue: events redo_bmc_reg [ipmi_msghandler] RIP: start_kcs_transaction+0x2c/0x190 [ipmi_si] Call Trace: start_next_msg+0x50/0x80 [ipmi_si] check_start_timer_thread.part.9+0x3b/0x50 [ipmi_si] sender+0x69/0x80 [ipmi_si] i_ipmi_request+0x2ac/0x9d0 [ipmi_msghandler] __get_device_id.isra.29+0xaa/0x180 [ipmi_msghandler] __bmc_get_device_id+0xef/0x950 [ipmi_msghandler] redo_bmc_reg+0x52/0x60 [ipmi_msghandler] process_one_work+0x1a7/0x360 Set intf->in_shutdown on the out_err_started path before invoking the lower driver's shutdown callback. This prevents later redo_bmc_reg retries from using an interface whose lower driver state has been cleaned up, and applies the same shutdown state to other IPMI interfaces as well. Fixes: 2512e40e48d2 ("ipmi: Rework SMI registration failure") Cc: stable@vger.kernel.org Signed-off-by: Seiji Nishikawa Message-ID: <20260630174348.1483814-1-snishika@redhat.com> Signed-off-by: Corey Minyard --- drivers/char/ipmi/ipmi_msghandler.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/char/ipmi/ipmi_msghandler.c b/drivers/char/ipmi/ipmi_msghandler.c index ab4c85f3d6fe..8d9f2e647d9b 100644 --- a/drivers/char/ipmi/ipmi_msghandler.c +++ b/drivers/char/ipmi/ipmi_msghandler.c @@ -3757,6 +3757,7 @@ int ipmi_add_smi(struct module *owner, out_err_bmc_reg: ipmi_bmc_unregister(intf); out_err_started: + intf->in_shutdown = true; if (intf->handlers->shutdown) intf->handlers->shutdown(intf->send_info); out_err: From 10a5a70c02277a1c12999b669a1bd1922558338a Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sat, 20 Jun 2026 17:51:53 +0200 Subject: [PATCH 0082/1328] leds: lp5860: Fix a potential double-unlock In lp5860_device_init(), if lp5860_init_dt() fails, an already unlocked mutex is unlocked another time. Slightly rework how the lock is taken/released to avoid this potential double unlock. Fixes: f0a66563aa2d ("leds: Add support for TI LP5860 LED driver chip") Signed-off-by: Christophe JAILLET Link: https://patch.msgid.link/0f4d556e0532bfa881d7d83c1e244572117a89e3.1781970674.git.christophe.jaillet@wanadoo.fr Signed-off-by: Lee Jones --- drivers/leds/rgb/leds-lp5860-core.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/leds/rgb/leds-lp5860-core.c b/drivers/leds/rgb/leds-lp5860-core.c index fd0e2f6e6e0f..e21d5f2302be 100644 --- a/drivers/leds/rgb/leds-lp5860-core.c +++ b/drivers/leds/rgb/leds-lp5860-core.c @@ -204,9 +204,9 @@ int lp5860_device_init(struct device *dev) mutex_lock(&lp->lock); ret = regmap_update_bits(lp->regmap, LP5860_REG_DEV_INITIAL, LP5860_MODE_MASK, LP5860_MODE_1 << LP5860_MODE_SHIFT); + mutex_unlock(&lp->lock); if (ret) goto err_disable; - mutex_unlock(&lp->lock); ret = lp5860_init_dt(lp); if (ret) @@ -215,7 +215,6 @@ int lp5860_device_init(struct device *dev) return 0; err_disable: - mutex_unlock(&lp->lock); lp5860_chip_enable(lp, LP5860_CHIP_DISABLE); return ret; } From f647a2266289a35eaa4865f629f2ab7046900d9b Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sat, 20 Jun 2026 17:53:22 +0200 Subject: [PATCH 0083/1328] leds: lp5860-spi: Fix an error handling path If lp5860_device_init() fails, a missing mutex_destroy() should be called. Use devm_mutex_init() instead of mutex_init() to fix it. This also simplifies the remove function. Fixes: f0a66563aa2d ("leds: Add support for TI LP5860 LED driver chip") Signed-off-by: Christophe JAILLET Link: https://patch.msgid.link/311792e767ab803d4744bc26155e6dac253d9b45.1781970783.git.christophe.jaillet@wanadoo.fr Signed-off-by: Lee Jones --- drivers/leds/rgb/leds-lp5860-spi.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/leds/rgb/leds-lp5860-spi.c b/drivers/leds/rgb/leds-lp5860-spi.c index 5e0c44854a68..6bf6a625c28a 100644 --- a/drivers/leds/rgb/leds-lp5860-spi.c +++ b/drivers/leds/rgb/leds-lp5860-spi.c @@ -38,6 +38,7 @@ static int lp5860_probe(struct spi_device *spi) struct device *dev = &spi->dev; struct lp5860 *lp5860; unsigned int multi_leds; + int ret; multi_leds = device_get_child_node_count(dev); if (!multi_leds) { @@ -61,7 +62,10 @@ static int lp5860_probe(struct spi_device *spi) "Failed to initialise Regmap.\n"); lp5860->dev = dev; - mutex_init(&lp5860->lock); + + ret = devm_mutex_init(dev, &lp5860->lock); + if (ret) + return ret; spi_set_drvdata(spi, lp5860); @@ -70,10 +74,6 @@ static int lp5860_probe(struct spi_device *spi) static void lp5860_remove(struct spi_device *spi) { - struct lp5860 *lp5860 = spi_get_drvdata(spi); - - mutex_destroy(&lp5860->lock); - lp5860_device_remove(&spi->dev); } From 2019a2a7268f6f7387f052110558b9f90b500324 Mon Sep 17 00:00:00 2001 From: Neil Armstrong Date: Fri, 29 May 2026 21:23:08 +0200 Subject: [PATCH 0084/1328] dt-bindings: leds: backlight: Document the SY7758 6-channel High Efficiency LED Driver Document the Silergy SY7758 6-channel High Efficiency LED Driver used for backlight brightness control. Signed-off-by: Neil Armstrong Reviewed-by: Krzysztof Kozlowski Reviewed-by: Daniel Thompson (RISCstar) Link: https://patch.msgid.link/20260529-topic-sm8650-ayaneo-pocket-s2-sy7758-v5-1-03aacd49747c@linaro.org Signed-off-by: Lee Jones --- .../leds/backlight/silergy,sy7758.yaml | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 Documentation/devicetree/bindings/leds/backlight/silergy,sy7758.yaml diff --git a/Documentation/devicetree/bindings/leds/backlight/silergy,sy7758.yaml b/Documentation/devicetree/bindings/leds/backlight/silergy,sy7758.yaml new file mode 100644 index 000000000000..64af6b34641a --- /dev/null +++ b/Documentation/devicetree/bindings/leds/backlight/silergy,sy7758.yaml @@ -0,0 +1,52 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/leds/backlight/silergy,sy7758.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Silergy SY7758 6-channel High Efficiency LED Driver + +maintainers: + - Neil Armstrong + +description: + Silergy SY7758 is a high efficiency 6-channels LED backlight + driver with I2C brightness control. + +allOf: + - $ref: common.yaml# + +properties: + compatible: + const: silergy,sy7758 + + reg: + maxItems: 1 + + vdd-supply: true + + enable-gpios: + maxItems: 1 + +required: + - compatible + - reg + - enable-gpios + +unevaluatedProperties: false + +examples: + - | + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + backlight@11 { + compatible = "silergy,sy7758"; + reg = <0x11>; + vdd-supply = <&bl_vdd>; + enable-gpios = <&gpio 16 GPIO_ACTIVE_HIGH>; + }; + }; From 110d67699a430daf0316f941254c46221e3f0914 Mon Sep 17 00:00:00 2001 From: KancyJoe Date: Fri, 29 May 2026 21:23:09 +0200 Subject: [PATCH 0085/1328] backlight: Add SY7758 6-channel High Efficiency LED Driver support Implement support for the Silergy SY7758 6-channel High Efficiency LED Driver used for backlight brightness control in the Ayaneo Pocket S2 dual-DSI panel. Signed-off-by: KancyJoe Signed-off-by: Neil Armstrong Reviewed-by: Daniel Thompson (RISCstar) Link: https://patch.msgid.link/20260529-topic-sm8650-ayaneo-pocket-s2-sy7758-v5-2-03aacd49747c@linaro.org Signed-off-by: Lee Jones --- drivers/video/backlight/Kconfig | 8 + drivers/video/backlight/Makefile | 1 + drivers/video/backlight/sy7758.c | 259 +++++++++++++++++++++++++++++++ 3 files changed, 268 insertions(+) create mode 100644 drivers/video/backlight/sy7758.c diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig index 7aa1c4b21111..7c66b8840d88 100644 --- a/drivers/video/backlight/Kconfig +++ b/drivers/video/backlight/Kconfig @@ -207,6 +207,14 @@ config BACKLIGHT_KTZ8866 Say Y to enable the backlight driver for the Kinetic KTZ8866 found in Xiaomi Mi Pad 5 series. +config BACKLIGHT_SY7758 + tristate "Backlight Driver for Silergy SY7758" + depends on I2C + select REGMAP_I2C + help + Say Y to enable the backlight driver for the Silergy SY7758 + backlight controller found in Ayaneo Pocket S2. + config BACKLIGHT_LM3533 tristate "Backlight Driver for LM3533" depends on MFD_LM3533 diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile index 21c8313cfb12..34469711c6cd 100644 --- a/drivers/video/backlight/Makefile +++ b/drivers/video/backlight/Makefile @@ -57,6 +57,7 @@ obj-$(CONFIG_BACKLIGHT_PWM) += pwm_bl.o obj-$(CONFIG_BACKLIGHT_QCOM_WLED) += qcom-wled.o obj-$(CONFIG_BACKLIGHT_RT4831) += rt4831-backlight.o obj-$(CONFIG_BACKLIGHT_SAHARA) += kb3886_bl.o +obj-$(CONFIG_BACKLIGHT_SY7758) += sy7758.o obj-$(CONFIG_BACKLIGHT_SKY81452) += sky81452-backlight.o obj-$(CONFIG_BACKLIGHT_TPS65217) += tps65217_bl.o obj-$(CONFIG_BACKLIGHT_WM831X) += wm831x_bl.o diff --git a/drivers/video/backlight/sy7758.c b/drivers/video/backlight/sy7758.c new file mode 100644 index 000000000000..786589cb8df9 --- /dev/null +++ b/drivers/video/backlight/sy7758.c @@ -0,0 +1,259 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Silergy SY7758 6-channel High Efficiency LED Driver + * + * Copyright (C) 2025 Kancy Joe + * Copyright (C) 2026 Linaro Limited + * Author: Neil Armstrong + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEFAULT_BRIGHTNESS 1024 +#define MAX_BRIGHTNESS 4080 +#define REG_MAX 0xAE + +/* Registers */ +#define REG_DEV_CTL 0x01 +#define REG_DEV_ID 0x03 +#define REG_BRT_12BIT_L 0x10 +#define REG_BRT_12BIT_H 0x11 + +/* OTP memory */ +#define REG_OTP_CFG0 0xA0 +#define REG_OTP_CFG1 0xA1 +#define REG_OTP_CFG2 0xA2 +#define REG_OTP_CFG5 0xA5 +#define REG_OTP_CFG9 0xA9 + +/* Fields */ +#define BIT_DEV_CTL_FAST BIT(7) +#define MSK_DEV_CTL_BRT_MODE GENMASK(2, 1) +#define BIT_DEV_CTL_BL_CTLB BIT(0) + +#define MSK_BRT_12BIT_L GENMASK(7, 0) +#define MSK_BRT_12BIT_H GENMASK(3, 0) + +#define MSK_CFG0_CURRENT_LOW GENMASK(7, 0) + +#define BIT_CFG1_PDET_STDBY BIT(7) +#define MSK_CFG1_CURRENT_MAX GENMASK(6, 4) +#define MSK_CFG1_CURRENT_HIGH GENMASK(3, 0) + +#define BIT_CFG2_UVLO_EN BIT(5) +#define BIT_CFG2_UVLO_TH BIT(4) +#define BIT_CFG2_BL_ON BIT(3) +#define BIT_CFG2_ISET_EN BIT(2) +#define BIT_CFG2_BST_ESET_EN BIT(1) + +#define BIT_CFG5_PWM_DIRECT BIT(7) +#define MSK_CFG5_PS_MODE GENMASK(6, 4) +#define MSK_CFG5_PWM_FREQ GENMASK(3, 0) + +#define MSK_CFG9_VBST_MAX GENMASK(7, 5) +#define BIT_CFG9_JUMP_EN BIT(4) +#define MSK_CFG9_JUMP_TH GENMASK(3, 2) +#define MSK_CFG9_JUMP_VOLTAGE GENMASK(1, 0) + +struct sy7758 { + struct i2c_client *client; + struct regmap *regmap; + struct gpio_desc *gpio; + struct backlight_device *bl; +}; + +static const struct regmap_config sy7758_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = REG_MAX, +}; + +static int sy7758_backlight_update_status(struct backlight_device *backlight_dev) +{ + struct sy7758 *sydev = bl_get_data(backlight_dev); + unsigned int brightness = backlight_get_brightness(backlight_dev); + int ret; + + ret = regmap_write(sydev->regmap, REG_BRT_12BIT_L, + FIELD_PREP(MSK_BRT_12BIT_L, + brightness & 0xff)); + if (ret) + return ret; + + ret = regmap_write(sydev->regmap, REG_BRT_12BIT_H, + FIELD_PREP(MSK_BRT_12BIT_H, + (brightness >> 8) & 0xf)); + if (ret) + return ret; + + return 0; +} + +static const struct backlight_ops sy7758_backlight_ops = { + .options = BL_CORE_SUSPENDRESUME, + .update_status = sy7758_backlight_update_status, +}; + +static int sy7758_init(struct sy7758 *sydev) +{ + int ret = 0; + + ret = regmap_write(sydev->regmap, REG_DEV_CTL, + BIT_DEV_CTL_FAST | BIT_DEV_CTL_BL_CTLB | + FIELD_PREP(MSK_DEV_CTL_BRT_MODE, 2)); + if (ret) + return ret; + + ret = regmap_write(sydev->regmap, REG_BRT_12BIT_L, + FIELD_PREP(MSK_BRT_12BIT_L, + DEFAULT_BRIGHTNESS & 0xff)); + if (ret) + return ret; + + ret = regmap_write(sydev->regmap, REG_BRT_12BIT_H, + FIELD_PREP(MSK_BRT_12BIT_H, + (DEFAULT_BRIGHTNESS >> 8))); + if (ret) + return ret; + + ret = regmap_write(sydev->regmap, REG_OTP_CFG5, + FIELD_PREP(MSK_CFG5_PS_MODE, 6) | + FIELD_PREP(MSK_CFG5_PWM_FREQ, 4)); + if (ret) + return ret; + + ret = regmap_write(sydev->regmap, REG_OTP_CFG0, + FIELD_PREP(MSK_CFG0_CURRENT_LOW, 85)); + if (ret) + return ret; + + ret = regmap_write(sydev->regmap, REG_OTP_CFG1, + BIT_CFG1_PDET_STDBY | + FIELD_PREP(MSK_CFG1_CURRENT_MAX, 1) | + FIELD_PREP(MSK_CFG1_CURRENT_HIGH, 10)); + if (ret) + return ret; + + ret = regmap_write(sydev->regmap, REG_OTP_CFG9, + FIELD_PREP(MSK_CFG9_VBST_MAX, 4)); + if (ret) + return ret; + + ret = regmap_write(sydev->regmap, REG_OTP_CFG2, + BIT_CFG2_BL_ON | BIT_CFG2_UVLO_EN); + if (ret) + return ret; + + return 0; +} + +static int sy7758_probe(struct i2c_client *client) +{ + struct backlight_properties props = { }; + struct device *dev = &client->dev; + struct sy7758 *sydev; + unsigned int dev_id; + int ret; + + sydev = devm_kzalloc(dev, sizeof(*sydev), GFP_KERNEL); + if (!sydev) + return -ENOMEM; + + i2c_set_clientdata(client, sydev); + + /* Initialize regmap */ + sydev->client = client; + sydev->regmap = devm_regmap_init_i2c(client, &sy7758_regmap_config); + if (IS_ERR(sydev->regmap)) + return dev_err_probe(dev, PTR_ERR(sydev->regmap), + "failed to init regmap\n"); + + /* Get and enable regulator */ + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "failed to get regulator\n"); + + fsleep(100); + + /* Get enable GPIO and set to high */ + sydev->gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_HIGH); + if (IS_ERR(sydev->gpio)) + return dev_err_probe(dev, PTR_ERR(sydev->gpio), + "failed to get enable GPIO\n"); + + /* Let some time for HW to settle */ + fsleep(10000); + + /* try read and check device id */ + ret = regmap_read(sydev->regmap, REG_DEV_ID, &dev_id); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to read device id\n"); + if (dev_id != 0x63) { + dev_err(dev, "unexpected device id: 0x%02x\n", dev_id); + return -ENODEV; + } + + /* Initialize and set default brightness */ + ret = sy7758_init(sydev); + if (ret) + return ret; + + props.type = BACKLIGHT_RAW; + props.max_brightness = MAX_BRIGHTNESS; + props.brightness = DEFAULT_BRIGHTNESS; + props.scale = BACKLIGHT_SCALE_LINEAR; + + sydev->bl = devm_backlight_device_register(dev, "sy7758-backlight", + dev, sydev, &sy7758_backlight_ops, + &props); + if (IS_ERR(sydev->bl)) + return dev_err_probe(dev, PTR_ERR(sydev->bl), + "failed to register backlight device\n"); + + return backlight_update_status(sydev->bl); +} + +static void sy7758_remove(struct i2c_client *client) +{ + struct sy7758 *sydev = i2c_get_clientdata(client); + + backlight_disable(sydev->bl); +} + +static const struct i2c_device_id sy7758_ids[] = { + { "sy7758" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, sy7758_ids); + +static const struct of_device_id sy7758_match_table[] = { + { .compatible = "silergy,sy7758", }, + { }, +}; +MODULE_DEVICE_TABLE(of, sy7758_match_table); + +static struct i2c_driver sy7758_driver = { + .driver = { + .name = "sy7758", + .of_match_table = sy7758_match_table, + }, + .probe = sy7758_probe, + .remove = sy7758_remove, + .id_table = sy7758_ids, +}; + +module_i2c_driver(sy7758_driver); + +MODULE_DESCRIPTION("Silergy SY7758 Backlight Driver"); +MODULE_AUTHOR("Kancy Joe "); +MODULE_AUTHOR("Neil Armstrong "); +MODULE_LICENSE("GPL"); From 7b9f8fe9c9e0e028cdc99343f4ff75bcfb9e57aa Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Mon, 6 Jul 2026 11:32:09 +0200 Subject: [PATCH 0086/1328] dt-bindings: rtc: sun6i: no clock-output-names on h616/r329 On h616 and r329 chips, clock output names are never defined through DT and are not meant to be. Just disallow the property for those chips. Reported-by: Sashiko Closes: http://lore.kernel.org/r/20260629125305.0DF981F000E9@smtp.kernel.org Acked-by: Alexandre Belloni Acked-by: Conor Dooley Reviewed-by: Chen-Yu Tsai Signed-off-by: Jerome Brunet Link: https://patch.msgid.link/20260706-a733-rtc-v4-1-f330728db3d3@baylibre.com Signed-off-by: Chen-Yu Tsai --- .../bindings/rtc/allwinner,sun6i-a31-rtc.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Documentation/devicetree/bindings/rtc/allwinner,sun6i-a31-rtc.yaml b/Documentation/devicetree/bindings/rtc/allwinner,sun6i-a31-rtc.yaml index 9df5cdb6f63f..959a012c626f 100644 --- a/Documentation/devicetree/bindings/rtc/allwinner,sun6i-a31-rtc.yaml +++ b/Documentation/devicetree/bindings/rtc/allwinner,sun6i-a31-rtc.yaml @@ -175,6 +175,18 @@ allOf: interrupts: minItems: 2 + - if: + properties: + compatible: + contains: + enum: + - allwinner,sun50i-h616-rtc + - allwinner,sun50i-r329-rtc + + then: + properties: + clock-output-names: false + required: - "#clock-cells" - compatible From 38d6b194a21c8626647b0a773bb8db5f2b84b0ad Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Mon, 6 Jul 2026 11:32:10 +0200 Subject: [PATCH 0087/1328] dt-bindings: rtc: sun6i: add sun60i-a733 support Add a new rtc compatible for the sun60i-a733 SoC and new IDs for the peripheral oscillator clock gates of this SoC. Acked-by: Alexandre Belloni Acked-by: Conor Dooley Signed-off-by: Jerome Brunet Link: https://patch.msgid.link/20260706-a733-rtc-v4-2-f330728db3d3@baylibre.com Signed-off-by: Chen-Yu Tsai --- .../devicetree/bindings/rtc/allwinner,sun6i-a31-rtc.yaml | 1 + include/dt-bindings/clock/sun6i-rtc.h | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/rtc/allwinner,sun6i-a31-rtc.yaml b/Documentation/devicetree/bindings/rtc/allwinner,sun6i-a31-rtc.yaml index 959a012c626f..f2b91186ed37 100644 --- a/Documentation/devicetree/bindings/rtc/allwinner,sun6i-a31-rtc.yaml +++ b/Documentation/devicetree/bindings/rtc/allwinner,sun6i-a31-rtc.yaml @@ -33,6 +33,7 @@ properties: - enum: - allwinner,sun20i-d1-rtc - allwinner,sun55i-a523-rtc + - allwinner,sun60i-a733-rtc - const: allwinner,sun50i-r329-rtc reg: diff --git a/include/dt-bindings/clock/sun6i-rtc.h b/include/dt-bindings/clock/sun6i-rtc.h index 3bd3aa3d57ce..5132a393ca4b 100644 --- a/include/dt-bindings/clock/sun6i-rtc.h +++ b/include/dt-bindings/clock/sun6i-rtc.h @@ -6,5 +6,9 @@ #define CLK_OSC32K 0 #define CLK_OSC32K_FANOUT 1 #define CLK_IOSC 2 +#define CLK_HOSC_UFS 8 +#define CLK_HOSC_HDMI 9 +#define CLK_HOSC_SERDES0 10 +#define CLK_HOSC_SERDES1 11 #endif /* _DT_BINDINGS_CLK_SUN6I_RTC_H_ */ From 81f4ddc9f7e0c11d3e9d24d0f30023845a02f7ee Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Mon, 6 Jul 2026 11:32:11 +0200 Subject: [PATCH 0088/1328] clk: sunxi-ng: fix ccu probe clock unregister on error When registering clocks with sunxi_ccu_probe(), the number of ccu_clocks and the number of hw clocks might be different, eventhough they usually are the same. If they are different, it could lead to out-of-bound access or registered clock left behind on error. Use a different variable when iterating on hw clocks so every registered clock, and only those, gets unregistered on error. Reported-by: Sashiko Closes: https://lore.kernel.org/r/20260629131254.7E34C1F00A3A@smtp.kernel.org Reviewed-by: Chen-Yu Tsai Signed-off-by: Jerome Brunet Link: https://patch.msgid.link/20260706-a733-rtc-v4-3-f330728db3d3@baylibre.com Signed-off-by: Chen-Yu Tsai --- drivers/clk/sunxi-ng/ccu_common.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/clk/sunxi-ng/ccu_common.c b/drivers/clk/sunxi-ng/ccu_common.c index 1c083b4d0b7e..43d8eca6abee 100644 --- a/drivers/clk/sunxi-ng/ccu_common.c +++ b/drivers/clk/sunxi-ng/ccu_common.c @@ -114,7 +114,7 @@ static int sunxi_ccu_probe(struct sunxi_ccu *ccu, struct device *dev, const struct sunxi_ccu_desc *desc) { struct ccu_reset *reset; - int i, ret; + int i, j, ret; ccu->desc = desc; @@ -130,8 +130,8 @@ static int sunxi_ccu_probe(struct sunxi_ccu *ccu, struct device *dev, cclk->lock = &ccu->lock; } - for (i = 0; i < desc->hw_clks->num ; i++) { - struct clk_hw *hw = desc->hw_clks->hws[i]; + for (j = 0; j < desc->hw_clks->num ; j++) { + struct clk_hw *hw = desc->hw_clks->hws[j]; const char *name; if (!hw) @@ -143,7 +143,7 @@ static int sunxi_ccu_probe(struct sunxi_ccu *ccu, struct device *dev, else ret = of_clk_hw_register(node, hw); if (ret) { - pr_err("Couldn't register clock %d - %s\n", i, name); + pr_err("Couldn't register clock %d - %s\n", j, name); goto err_clk_unreg; } } @@ -186,8 +186,8 @@ static int sunxi_ccu_probe(struct sunxi_ccu *ccu, struct device *dev, err_del_provider: of_clk_del_provider(node); err_clk_unreg: - while (--i >= 0) { - struct clk_hw *hw = desc->hw_clks->hws[i]; + while (--j >= 0) { + struct clk_hw *hw = desc->hw_clks->hws[j]; if (!hw) continue; From 192c6220e14d5b7924f116680bdf073179cde025 Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Mon, 6 Jul 2026 11:32:12 +0200 Subject: [PATCH 0089/1328] clk: sunxi-ng: sun6i-rtc: clean up DT usage With sun6i-rtc compatible devices, the "ext-osc32k" clock input is optional for the devices that support this input (r329 and onward). Probably preparing for older SoC support, the driver does something funny when parsing DT. It check if "ext-osc32k" is present in the clock-names and if it is not, it uses the first clock as "ext-osc32k". This clock will actually be the rtc bus clock so what the driver does is wrong. At the moment, the driver does not support the older SoCs that would have an external 32k clock provided on index #0 so just remove this quirk. Reviewed-by: Chen-Yu Tsai Signed-off-by: Jerome Brunet Link: https://patch.msgid.link/20260706-a733-rtc-v4-4-f330728db3d3@baylibre.com Signed-off-by: Chen-Yu Tsai --- drivers/clk/sunxi-ng/ccu-sun6i-rtc.c | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/drivers/clk/sunxi-ng/ccu-sun6i-rtc.c b/drivers/clk/sunxi-ng/ccu-sun6i-rtc.c index f6bfeba009e8..0f528bfaed00 100644 --- a/drivers/clk/sunxi-ng/ccu-sun6i-rtc.c +++ b/drivers/clk/sunxi-ng/ccu-sun6i-rtc.c @@ -191,10 +191,8 @@ static struct ccu_common iosc_32k_clk = { CLK_GET_RATE_NOCACHE), }; -static const struct clk_hw *ext_osc32k[] = { NULL }; /* updated during probe */ - -static SUNXI_CCU_GATE_HWS(ext_osc32k_gate_clk, "ext-osc32k-gate", - ext_osc32k, 0x0, BIT(4), 0); +static SUNXI_CCU_GATE_FW(ext_osc32k_gate_clk, "ext-osc32k-gate", + "ext-osc32k", 0x0, BIT(4), 0); static const struct clk_hw *osc32k_parents[] = { &iosc_32k_clk.hw, @@ -352,7 +350,6 @@ MODULE_DEVICE_TABLE(of, sun6i_rtc_ccu_match); int sun6i_rtc_ccu_probe(struct device *dev, void __iomem *reg) { const struct sun6i_rtc_match_data *data; - struct clk *ext_osc32k_clk = NULL; const struct of_device_id *match; /* This driver is only used for newer variants of the hardware. */ @@ -363,21 +360,7 @@ int sun6i_rtc_ccu_probe(struct device *dev, void __iomem *reg) data = match->data; have_iosc_calibration = data->have_iosc_calibration; - if (data->have_ext_osc32k) { - const char *fw_name; - - /* ext-osc32k was the only input clock in the old binding. */ - fw_name = of_property_present(dev->of_node, "clock-names") - ? "ext-osc32k" : NULL; - ext_osc32k_clk = devm_clk_get_optional(dev, fw_name); - if (IS_ERR(ext_osc32k_clk)) - return PTR_ERR(ext_osc32k_clk); - } - - if (ext_osc32k_clk) { - /* Link ext-osc32k-gate to its parent. */ - *ext_osc32k = __clk_get_hw(ext_osc32k_clk); - } else { + if (!data->have_ext_osc32k) { /* ext-osc32k-gate is an orphan, so do not register it. */ sun6i_rtc_ccu_hw_clks.hws[CLK_EXT_OSC32K_GATE] = NULL; osc32k_init_data.num_parents = 1; From d2fcd82ca07dad71fc6102db7cb7b3225c489774 Mon Sep 17 00:00:00 2001 From: Junhui Liu Date: Mon, 6 Jul 2026 11:32:13 +0200 Subject: [PATCH 0090/1328] clk: sunxi-ng: sun6i-rtc: Add feature bit for IOSC calibration The sun6i-rtc CCU driver currently uses a global static variable to denote whether calibration is supported, which makes IOSC operations tightly coupled to this file. Convert this into a feature bit to decouple the logic. This allows the IOSC clock code to be moved into a shared module for reuse by other SoCs. Signed-off-by: Junhui Liu Reviewed-by: Chen-Yu Tsai Signed-off-by: Jerome Brunet Link: https://patch.msgid.link/20260706-a733-rtc-v4-5-f330728db3d3@baylibre.com Signed-off-by: Chen-Yu Tsai --- drivers/clk/sunxi-ng/ccu-sun6i-rtc.c | 17 +++++++++-------- drivers/clk/sunxi-ng/ccu_common.h | 1 + 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/drivers/clk/sunxi-ng/ccu-sun6i-rtc.c b/drivers/clk/sunxi-ng/ccu-sun6i-rtc.c index 0f528bfaed00..b24c8b196e66 100644 --- a/drivers/clk/sunxi-ng/ccu-sun6i-rtc.c +++ b/drivers/clk/sunxi-ng/ccu-sun6i-rtc.c @@ -52,8 +52,6 @@ struct sun6i_rtc_match_data { u8 osc32k_fanout_nparents; }; -static bool have_iosc_calibration; - static int ccu_iosc_enable(struct clk_hw *hw) { struct ccu_common *cm = hw_to_ccu_common(hw); @@ -80,7 +78,7 @@ static unsigned long ccu_iosc_recalc_rate(struct clk_hw *hw, { struct ccu_common *cm = hw_to_ccu_common(hw); - if (have_iosc_calibration) { + if (cm->features & CCU_FEATURE_IOSC_CALIBRATION) { u32 reg = readl(cm->base + IOSC_CLK_CALI_REG); /* @@ -119,7 +117,7 @@ static int ccu_iosc_32k_prepare(struct clk_hw *hw) struct ccu_common *cm = hw_to_ccu_common(hw); u32 val; - if (!have_iosc_calibration) + if (!(cm->features & CCU_FEATURE_IOSC_CALIBRATION)) return 0; val = readl(cm->base + IOSC_CLK_CALI_REG); @@ -134,7 +132,7 @@ static void ccu_iosc_32k_unprepare(struct clk_hw *hw) struct ccu_common *cm = hw_to_ccu_common(hw); u32 val; - if (!have_iosc_calibration) + if (!(cm->features & CCU_FEATURE_IOSC_CALIBRATION)) return; val = readl(cm->base + IOSC_CLK_CALI_REG); @@ -148,7 +146,7 @@ static unsigned long ccu_iosc_32k_recalc_rate(struct clk_hw *hw, struct ccu_common *cm = hw_to_ccu_common(hw); u32 val; - if (have_iosc_calibration) { + if (cm->features & CCU_FEATURE_IOSC_CALIBRATION) { val = readl(cm->base + IOSC_CLK_CALI_REG); /* Assume the calibrated 32k clock is accurate. */ @@ -167,7 +165,7 @@ static unsigned long ccu_iosc_32k_recalc_accuracy(struct clk_hw *hw, struct ccu_common *cm = hw_to_ccu_common(hw); u32 val; - if (have_iosc_calibration) { + if (cm->features & CCU_FEATURE_IOSC_CALIBRATION) { val = readl(cm->base + IOSC_CLK_CALI_REG); /* Assume the calibrated 32k clock is accurate. */ @@ -358,7 +356,10 @@ int sun6i_rtc_ccu_probe(struct device *dev, void __iomem *reg) return 0; data = match->data; - have_iosc_calibration = data->have_iosc_calibration; + if (data->have_iosc_calibration) { + iosc_clk.features |= CCU_FEATURE_IOSC_CALIBRATION; + iosc_32k_clk.features |= CCU_FEATURE_IOSC_CALIBRATION; + } if (!data->have_ext_osc32k) { /* ext-osc32k-gate is an orphan, so do not register it. */ diff --git a/drivers/clk/sunxi-ng/ccu_common.h b/drivers/clk/sunxi-ng/ccu_common.h index bbec283b9d99..d9dc24ad5503 100644 --- a/drivers/clk/sunxi-ng/ccu_common.h +++ b/drivers/clk/sunxi-ng/ccu_common.h @@ -21,6 +21,7 @@ #define CCU_FEATURE_CLOSEST_RATE BIT(9) #define CCU_FEATURE_DUAL_DIV BIT(10) #define CCU_FEATURE_UPDATE_BIT BIT(11) +#define CCU_FEATURE_IOSC_CALIBRATION BIT(12) /* MMC timing mode switch bit */ #define CCU_MMC_NEW_TIMING_MODE BIT(30) From c52b5090bf1e3b88745f8a81f264f34100ecde42 Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Mon, 6 Jul 2026 11:32:15 +0200 Subject: [PATCH 0091/1328] clk: sunxi-ng: mux: remove unneeded export The export ccu_mux_helper_apply_prediv() symbol is actually not necessary. No module other than sunxi-ccu would use it at the moment so just remove the export. Signed-off-by: Jerome Brunet Link: https://patch.msgid.link/20260706-a733-rtc-v4-7-f330728db3d3@baylibre.com Signed-off-by: Chen-Yu Tsai --- drivers/clk/sunxi-ng/ccu_mux.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/clk/sunxi-ng/ccu_mux.c b/drivers/clk/sunxi-ng/ccu_mux.c index 766f27cff748..09230728c400 100644 --- a/drivers/clk/sunxi-ng/ccu_mux.c +++ b/drivers/clk/sunxi-ng/ccu_mux.c @@ -66,7 +66,6 @@ unsigned long ccu_mux_helper_apply_prediv(struct ccu_common *common, { return parent_rate / ccu_mux_get_prediv(common, cm, parent_index); } -EXPORT_SYMBOL_NS_GPL(ccu_mux_helper_apply_prediv, "SUNXI_CCU"); static unsigned long ccu_mux_helper_unapply_prediv(struct ccu_common *common, struct ccu_mux_internal *cm, From 0661ee1d650facefdf61401c7d00eb96fad40b10 Mon Sep 17 00:00:00 2001 From: Jagadeesh Kona Date: Mon, 27 Apr 2026 12:08:55 +0530 Subject: [PATCH 0092/1328] clk: qcom: gdsc: Add custom disable callback for GX GDSC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The GX GDSC is a special power domain that should only be disabled by OS during GMU recovery. In all other scenarios, the GMU firmware is responsible for handling its disable sequence, and OS must not interfere. During the resume_noirq() phase of system resume, the GenPD framework enables all power domains and later disables them in the complete() phase if there are no active votes from OS. This behavior can incorrectly disable the GX GDSC while the GMU firmware is still using it. To prevent this, implement a custom disable callback for GX GDSC that relies on GenPD’s synced_poweroff flag. The GMU driver sets this flag only during recovery, allowing OS to explicitly disable GX GDSC in hardware in that case. In all other situations, the disable callback will avoid touching GX GDSC hardware. Signed-off-by: Jagadeesh Kona Signed-off-by: Taniya Das Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260427-gfx-clk-fixes-v2-1-797e54b3d464@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gdsc.c | 22 ++++++++++++++++++++++ drivers/clk/qcom/gdsc.h | 1 + 2 files changed, 23 insertions(+) diff --git a/drivers/clk/qcom/gdsc.c b/drivers/clk/qcom/gdsc.c index ee5f86ca50cb..f419a28f616b 100644 --- a/drivers/clk/qcom/gdsc.c +++ b/drivers/clk/qcom/gdsc.c @@ -708,3 +708,25 @@ int gdsc_gx_do_nothing_enable(struct generic_pm_domain *domain) return ret; } EXPORT_SYMBOL_GPL(gdsc_gx_do_nothing_enable); + +/* + * GX GDSC is a special power domain. Normally, its disable sequence + * is managed by the GMU firmware, and high level OS must not attempt + * to disable it. The only exception is during GMU recovery, where the + * GMU driver can set GenPD’s synced_poweroff flag to allow explicitly + * disable GX GDSC in hardware. + */ +int gdsc_gx_disable(struct generic_pm_domain *domain) +{ + struct gdsc *sc = domain_to_gdsc(domain); + + if (domain->synced_poweroff) + return gdsc_disable(domain); + + /* Remove parent-supply placed in enable */ + if (sc->rsupply) + return regulator_disable(sc->rsupply); + + return 0; +} +EXPORT_SYMBOL_GPL(gdsc_gx_disable); diff --git a/drivers/clk/qcom/gdsc.h b/drivers/clk/qcom/gdsc.h index 92ff6bcce7b1..2f9665b664e6 100644 --- a/drivers/clk/qcom/gdsc.h +++ b/drivers/clk/qcom/gdsc.h @@ -93,6 +93,7 @@ int gdsc_register(struct gdsc_desc *desc, struct reset_controller_dev *, struct regmap *); void gdsc_unregister(struct gdsc_desc *desc); int gdsc_gx_do_nothing_enable(struct generic_pm_domain *domain); +int gdsc_gx_disable(struct generic_pm_domain *domain); #else static inline int gdsc_register(struct gdsc_desc *desc, struct reset_controller_dev *rcdev, From badf361c00c802738c776fb5f4e8b08b4d0bad1c Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Mon, 27 Apr 2026 12:08:56 +0530 Subject: [PATCH 0093/1328] clk: qcom: gxclkctl: Use custom disable callback for gx_gdsc The GX GDSC represents a special GPU power domain that must not be disabled during normal runtime PM flows. As per the GMU architecture, GX GDSC should only be force-disabled during GMU/GPU recovery, where the OS explicitly resets the GX power domain. However, when managed by the generic GDSC runtime PM path, GX GDSC may be disabled during GMU runtime suspend, resulting in warnings such as: gx_clkctl_gx_gdsc status stuck at 'on' and failures in gdsc_toggle_logic() during rpm suspend. Use the newly added custom disable callback for gx_gdsc to ensure the GDSC is toggled only in recovery scenarios, while preventing unintended disable attempts during normal GMU runtime PM operations. Reported-by: Pengyu Luo Closes: https://lore.kernel.org/all/CAH2e8h4Vp9fJYAUUbOmoHSKB25wakPBvmpwa62BTRqgRQbMWuw@mail.gmail.com/ Reported-by: Alexander Koskovich Closes: https://lore.kernel.org/all/gwVAH2mJerU4dBInw8pKmOs5aQK55Q7W6q_UQAlLFCsEgX6eyvSgXAWbNNMqAX4WmPlYCKUSMhfkr5Jry4Ps5EqnxYZqEEDd3Whwv7ZXGlc=@pm.me/ Fixes: 5af11acae660 ("clk: qcom: Add a driver for SM8750 GPU clocks") Reviewed-by: Konrad Dybcio Signed-off-by: Taniya Das Link: https://lore.kernel.org/r/20260427-gfx-clk-fixes-v2-2-797e54b3d464@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gxclkctl-kaanapali.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/clk/qcom/gxclkctl-kaanapali.c b/drivers/clk/qcom/gxclkctl-kaanapali.c index 7b0af0ba1e68..a39813e4e2a8 100644 --- a/drivers/clk/qcom/gxclkctl-kaanapali.c +++ b/drivers/clk/qcom/gxclkctl-kaanapali.c @@ -26,6 +26,7 @@ static struct gdsc gx_clkctl_gx_gdsc = { .pd = { .name = "gx_clkctl_gx_gdsc", .power_on = gdsc_gx_do_nothing_enable, + .power_off = gdsc_gx_disable, }, .pwrsts = PWRSTS_OFF_ON, .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE, From a5da6c5697a50a5956ae7645e52d0007ac8b3395 Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Mon, 27 Apr 2026 12:08:57 +0530 Subject: [PATCH 0094/1328] clk: qcom: common: ensure runtime PM suspend completes on probe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the clock controller is probed with 'use_rpm' enabled, the runtime PM reference is currently released using pm_runtime_put(), which may return before the runtime suspend has completed. When the clock controller device is registered through this function, calling pm_runtime_disable() immediately after pm_runtime_put() prevents the runtime suspend from completing, leaving the clock controller active and the HW rails in the ON state. Use pm_runtime_put_sync() instead to ensure the runtime PM “putV completes synchronously during probe. This does not have any functional impact, but it guarantees that the device is fully runtime-suspended before returning. Signed-off-by: Taniya Das Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260427-gfx-clk-fixes-v2-3-797e54b3d464@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/qcom/common.c b/drivers/clk/qcom/common.c index eec369d2173b..2c09abaf1d2a 100644 --- a/drivers/clk/qcom/common.c +++ b/drivers/clk/qcom/common.c @@ -428,7 +428,7 @@ int qcom_cc_really_probe(struct device *dev, put_rpm: if (desc->use_rpm) - pm_runtime_put(dev); + pm_runtime_put_sync(dev); return ret; } From 1c99638642c2137d7e699e7b2ab400df7ef15774 Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Mon, 27 Apr 2026 12:08:58 +0530 Subject: [PATCH 0095/1328] clk: qcom: gxclkctl: Remove GX/GMxC rail votes to align with IFPC The GX GDSC control is handled through a dedicated clock controller, and the enable/disable sequencing depends on correct rail voting. The driver votes for the GX/GMxC rails and CX GDSC before toggling the GX GDSC. Currently, during GMU runtime PM resume, rails remain enabled due to upstream votes propagated via RPM-enabled devlinks and explicit pm_runtime votes on GX GDSC. This is not an expected behaviour of IFPC(Inter Frame Power Collapse) requirements of GPU as GMU firmware is expected to control these rails, except during the GPU/GMU recovery via the OS and that is where the GX GDSC should be voting for the rails (GX/GMxC and CX GDSC) before toggling the GX GDSC. Thus, disable runtime PM after successfully registering the clock controller. Signed-off-by: Taniya Das Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260427-gfx-clk-fixes-v2-4-797e54b3d464@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gxclkctl-kaanapali.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/clk/qcom/gxclkctl-kaanapali.c b/drivers/clk/qcom/gxclkctl-kaanapali.c index a39813e4e2a8..1991cb651652 100644 --- a/drivers/clk/qcom/gxclkctl-kaanapali.c +++ b/drivers/clk/qcom/gxclkctl-kaanapali.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -62,7 +63,15 @@ MODULE_DEVICE_TABLE(of, gx_clkctl_kaanapali_match_table); static int gx_clkctl_kaanapali_probe(struct platform_device *pdev) { - return qcom_cc_probe(pdev, &gx_clkctl_kaanapali_desc); + int ret; + + ret = qcom_cc_probe(pdev, &gx_clkctl_kaanapali_desc); + if (ret) + return ret; + + pm_runtime_disable(&pdev->dev); + + return ret; } static struct platform_driver gx_clkctl_kaanapali_driver = { From 027dc284d5b36733c0868045ff58b6b25b786bb5 Mon Sep 17 00:00:00 2001 From: Luca Weiss Date: Wed, 1 Jul 2026 08:19:30 +0200 Subject: [PATCH 0096/1328] clk: qcom: Don't use all caps for Glymur GPUCC All other Glymur driver use "Glymur" and not all caps "GLYMUR". Align the prompt and help text. Signed-off-by: Luca Weiss Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260701-glymur-uncaps-v1-1-5f7065348eb2@fairphone.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/Kconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig index 7d84c2f1d911..4169ecd95ef3 100644 --- a/drivers/clk/qcom/Kconfig +++ b/drivers/clk/qcom/Kconfig @@ -65,11 +65,11 @@ config CLK_GLYMUR_GCC I2C, USB, UFS, SDCC, etc. config CLK_GLYMUR_GPUCC - tristate "GLYMUR Graphics Clock Controller" + tristate "Glymur Graphics Clock Controller" depends on ARM64 || COMPILE_TEST select CLK_GLYMUR_GCC help - Support for the graphics clock controller on GLYMUR devices. + Support for the graphics clock controller on Glymur devices. Say Y if you want to support graphics controller devices and functionality such as 3D graphics. From 7399034fd78615ba826b864fca2e4572f13cf8e3 Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Wed, 17 Jun 2026 16:37:52 +0530 Subject: [PATCH 0097/1328] clk: qcom: gcc-glymur: Move EVA clocks to critical clock list The gcc_eva_ahb_clk and gcc_eva_xo_clk branch clocks should not be registered as standalone GCC branch clocks. Drop these clocks from the GCC clock list and instead add their CBCR registers to the GCC critical clocks list to ensure they remain enabled during early boot. If these clocks are registered as normal branch clocks, they may be gated, which breaks access to the EVA clock controller during clock controller probe, thus leave them as critical clocks similar to other subsystem AHB and XO clocks. Fixes: efe504300a17 ("clk: qcom: gcc: Add support for Global Clock Controller") Signed-off-by: Taniya Das Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260617-evacc_glymur-v2-1-905108dacaaa@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gcc-glymur.c | 32 ++------------------------------ 1 file changed, 2 insertions(+), 30 deletions(-) diff --git a/drivers/clk/qcom/gcc-glymur.c b/drivers/clk/qcom/gcc-glymur.c index 2736465efdea..32d23bdc819b 100644 --- a/drivers/clk/qcom/gcc-glymur.c +++ b/drivers/clk/qcom/gcc-glymur.c @@ -3669,21 +3669,6 @@ static struct clk_branch gcc_disp_hf_axi_clk = { }, }; -static struct clk_branch gcc_eva_ahb_clk = { - .halt_reg = 0x9b004, - .halt_check = BRANCH_HALT_VOTED, - .hwcg_reg = 0x9b004, - .hwcg_bit = 1, - .clkr = { - .enable_reg = 0x9b004, - .enable_mask = BIT(0), - .hw.init = &(const struct clk_init_data) { - .name = "gcc_eva_ahb_clk", - .ops = &clk_branch2_ops, - }, - }, -}; - static struct clk_branch gcc_eva_axi0_clk = { .halt_reg = 0x9b008, .halt_check = BRANCH_HALT_SKIP, @@ -3714,19 +3699,6 @@ static struct clk_branch gcc_eva_axi0c_clk = { }, }; -static struct clk_branch gcc_eva_xo_clk = { - .halt_reg = 0x9b024, - .halt_check = BRANCH_HALT, - .clkr = { - .enable_reg = 0x9b024, - .enable_mask = BIT(0), - .hw.init = &(const struct clk_init_data) { - .name = "gcc_eva_xo_clk", - .ops = &clk_branch2_ops, - }, - }, -}; - static struct clk_branch gcc_gp1_clk = { .halt_reg = 0x64000, .halt_check = BRANCH_HALT, @@ -7993,10 +7965,8 @@ static struct clk_regmap *gcc_glymur_clocks[] = { [GCC_CFG_NOC_USB_ANOC_AHB_CLK] = &gcc_cfg_noc_usb_anoc_ahb_clk.clkr, [GCC_CFG_NOC_USB_ANOC_SOUTH_AHB_CLK] = &gcc_cfg_noc_usb_anoc_south_ahb_clk.clkr, [GCC_DISP_HF_AXI_CLK] = &gcc_disp_hf_axi_clk.clkr, - [GCC_EVA_AHB_CLK] = &gcc_eva_ahb_clk.clkr, [GCC_EVA_AXI0_CLK] = &gcc_eva_axi0_clk.clkr, [GCC_EVA_AXI0C_CLK] = &gcc_eva_axi0c_clk.clkr, - [GCC_EVA_XO_CLK] = &gcc_eva_xo_clk.clkr, [GCC_GP1_CLK] = &gcc_gp1_clk.clkr, [GCC_GP1_CLK_SRC] = &gcc_gp1_clk_src.clkr, [GCC_GP2_CLK] = &gcc_gp2_clk.clkr, @@ -8545,6 +8515,8 @@ static const u32 gcc_glymur_critical_cbcrs[] = { 0x71004, /* GCC_GPU_CFG_AHB_CLK */ 0x32004, /* GCC_VIDEO_AHB_CLK */ 0x32058, /* GCC_VIDEO_XO_CLK */ + 0x9b004, /* GCC_EVA_AHB_CLK */ + 0x9b024, /* GCC_EVA_XO_CLK */ }; static const struct regmap_config gcc_glymur_regmap_config = { From e291ec812dec1a5a37aeaed0a510761ffcf2bef0 Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Wed, 17 Jun 2026 16:37:54 +0530 Subject: [PATCH 0098/1328] clk: qcom: Add EVA clock controller driver for Glymur SoC Add the Enhanced Video Analytics (EVA) clock controller driver for the Glymur SoC. The EVACC manages the PLL, RCGs, branch clocks, GDSCs and resets for the EVA subsystem which handles vision processing workloads. Signed-off-by: Taniya Das Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260617-evacc_glymur-v2-3-905108dacaaa@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/Kconfig | 11 + drivers/clk/qcom/Makefile | 1 + drivers/clk/qcom/evacc-glymur.c | 455 ++++++++++++++++++++++++++++++++ 3 files changed, 467 insertions(+) create mode 100644 drivers/clk/qcom/evacc-glymur.c diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig index 4169ecd95ef3..225a0782cf71 100644 --- a/drivers/clk/qcom/Kconfig +++ b/drivers/clk/qcom/Kconfig @@ -55,6 +55,17 @@ config CLK_GLYMUR_DISPCC Say Y if you want to support display devices and functionality such as splash screen. +config CLK_GLYMUR_EVACC + tristate "Glymur EVA Clock Controller" + depends on ARM64 || COMPILE_TEST + default m if ARCH_QCOM + select CLK_GLYMUR_GCC + help + Support for the Enhanced Video Analytics (EVA) clock controller on + Qualcomm Technologies, Inc. Glymur devices. + Say Y if you want to support EVA devices and functionality such as + vision processing. + config CLK_GLYMUR_GCC tristate "Glymur Global Clock Controller" depends on ARM64 || COMPILE_TEST diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile index 58f9a5eb6fd7..97c04bf27409 100644 --- a/drivers/clk/qcom/Makefile +++ b/drivers/clk/qcom/Makefile @@ -25,6 +25,7 @@ obj-$(CONFIG_CLK_ELIZA_GCC) += gcc-eliza.o obj-$(CONFIG_CLK_ELIZA_TCSRCC) += tcsrcc-eliza.o obj-$(CONFIG_CLK_GFM_LPASS_SM8250) += lpass-gfm-sm8250.o obj-$(CONFIG_CLK_GLYMUR_DISPCC) += dispcc-glymur.o +obj-$(CONFIG_CLK_GLYMUR_EVACC) += evacc-glymur.o obj-$(CONFIG_CLK_GLYMUR_GCC) += gcc-glymur.o obj-$(CONFIG_CLK_GLYMUR_GPUCC) += gpucc-glymur.o gxclkctl-kaanapali.o obj-$(CONFIG_CLK_GLYMUR_TCSRCC) += tcsrcc-glymur.o diff --git a/drivers/clk/qcom/evacc-glymur.c b/drivers/clk/qcom/evacc-glymur.c new file mode 100644 index 000000000000..ba124cfc1a4d --- /dev/null +++ b/drivers/clk/qcom/evacc-glymur.c @@ -0,0 +1,455 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "clk-alpha-pll.h" +#include "clk-branch.h" +#include "clk-pll.h" +#include "clk-rcg.h" +#include "clk-regmap.h" +#include "clk-regmap-divider.h" +#include "clk-regmap-mux.h" +#include "common.h" +#include "gdsc.h" +#include "reset.h" + +enum { + DT_AHB_CLK, + DT_BI_TCXO, + DT_SLEEP_CLK, +}; + +enum { + P_BI_TCXO, + P_EVA_CC_PLL0_OUT_MAIN, + P_SLEEP_CLK, +}; + +static const struct pll_vco taycan_eko_t_vco[] = { + { 249600000, 2500000000, 0 }, +}; + +/* 840.0 MHz Configuration */ +static const struct alpha_pll_config eva_cc_pll0_config = { + .l = 0x2b, + .alpha = 0xc000, + .config_ctl_val = 0x25c400e7, + .config_ctl_hi_val = 0x0a8060e0, + .config_ctl_hi1_val = 0xf51dea20, + .user_ctl_val = 0x00000008, + .user_ctl_hi_val = 0x00000002, +}; + +static struct clk_alpha_pll eva_cc_pll0 = { + .offset = 0x0, + .config = &eva_cc_pll0_config, + .vco_table = taycan_eko_t_vco, + .num_vco = ARRAY_SIZE(taycan_eko_t_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_TAYCAN_EKO_T], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "eva_cc_pll0", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_taycan_eko_t_ops, + }, + }, +}; + +static const struct parent_map eva_cc_parent_map_0[] = { + { P_BI_TCXO, 0 }, +}; + +static const struct clk_parent_data eva_cc_parent_data_0[] = { + { .index = DT_BI_TCXO }, +}; + +static const struct parent_map eva_cc_parent_map_1[] = { + { P_BI_TCXO, 0 }, + { P_EVA_CC_PLL0_OUT_MAIN, 1 }, +}; + +static const struct clk_parent_data eva_cc_parent_data_1[] = { + { .index = DT_BI_TCXO }, + { .hw = &eva_cc_pll0.clkr.hw }, +}; + +static const struct parent_map eva_cc_parent_map_2[] = { + { P_SLEEP_CLK, 0 }, +}; + +static const struct clk_parent_data eva_cc_parent_data_2[] = { + { .index = DT_SLEEP_CLK }, +}; + +static const struct freq_tbl ftbl_eva_cc_ahb_clk_src[] = { + F(19200000, P_BI_TCXO, 1, 0, 0), + { } +}; + +static struct clk_rcg2 eva_cc_ahb_clk_src = { + .cmd_rcgr = 0x8018, + .mnd_width = 0, + .hid_width = 5, + .parent_map = eva_cc_parent_map_0, + .freq_tbl = ftbl_eva_cc_ahb_clk_src, + .hw_clk_ctrl = true, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "eva_cc_ahb_clk_src", + .parent_data = eva_cc_parent_data_0, + .num_parents = ARRAY_SIZE(eva_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, + }, +}; + +static const struct freq_tbl ftbl_eva_cc_mvs0_clk_src[] = { + F(840000000, P_EVA_CC_PLL0_OUT_MAIN, 1, 0, 0), + F(1050000000, P_EVA_CC_PLL0_OUT_MAIN, 1, 0, 0), + F(1350000000, P_EVA_CC_PLL0_OUT_MAIN, 1, 0, 0), + F(1500000000, P_EVA_CC_PLL0_OUT_MAIN, 1, 0, 0), + F(1650000000, P_EVA_CC_PLL0_OUT_MAIN, 1, 0, 0), + { } +}; + +static struct clk_rcg2 eva_cc_mvs0_clk_src = { + .cmd_rcgr = 0x8000, + .mnd_width = 0, + .hid_width = 5, + .parent_map = eva_cc_parent_map_1, + .freq_tbl = ftbl_eva_cc_mvs0_clk_src, + .hw_clk_ctrl = true, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "eva_cc_mvs0_clk_src", + .parent_data = eva_cc_parent_data_1, + .num_parents = ARRAY_SIZE(eva_cc_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, + }, +}; + +static const struct freq_tbl ftbl_eva_cc_sleep_clk_src[] = { + F(32000, P_SLEEP_CLK, 1, 0, 0), + { } +}; + +static struct clk_rcg2 eva_cc_sleep_clk_src = { + .cmd_rcgr = 0x80e0, + .mnd_width = 0, + .hid_width = 5, + .parent_map = eva_cc_parent_map_2, + .freq_tbl = ftbl_eva_cc_sleep_clk_src, + .hw_clk_ctrl = true, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "eva_cc_sleep_clk_src", + .parent_data = eva_cc_parent_data_2, + .num_parents = ARRAY_SIZE(eva_cc_parent_data_2), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, + }, +}; + +static struct clk_rcg2 eva_cc_xo_clk_src = { + .cmd_rcgr = 0x80bc, + .mnd_width = 0, + .hid_width = 5, + .parent_map = eva_cc_parent_map_0, + .freq_tbl = ftbl_eva_cc_ahb_clk_src, + .hw_clk_ctrl = true, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "eva_cc_xo_clk_src", + .parent_data = eva_cc_parent_data_0, + .num_parents = ARRAY_SIZE(eva_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, + }, +}; + +static struct clk_regmap_div eva_cc_mvs0_div_clk_src = { + .reg = 0x809c, + .shift = 0, + .width = 4, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "eva_cc_mvs0_div_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &eva_cc_mvs0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_regmap_div_ro_ops, + }, +}; + +static struct clk_regmap_div eva_cc_mvs0c_div2_div_clk_src = { + .reg = 0x8060, + .shift = 0, + .width = 4, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "eva_cc_mvs0c_div2_div_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &eva_cc_mvs0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_regmap_div_ro_ops, + }, +}; + +static struct clk_branch eva_cc_mvs0_clk = { + .halt_reg = 0x807c, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x807c, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x807c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "eva_cc_mvs0_clk", + .parent_hws = (const struct clk_hw*[]) { + &eva_cc_mvs0_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch eva_cc_mvs0_freerun_clk = { + .halt_reg = 0x808c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x808c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "eva_cc_mvs0_freerun_clk", + .parent_hws = (const struct clk_hw*[]) { + &eva_cc_mvs0_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch eva_cc_mvs0_shift_clk = { + .halt_reg = 0x80d8, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x80d8, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x80d8, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "eva_cc_mvs0_shift_clk", + .parent_hws = (const struct clk_hw*[]) { + &eva_cc_xo_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch eva_cc_mvs0c_clk = { + .halt_reg = 0x804c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x804c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "eva_cc_mvs0c_clk", + .parent_hws = (const struct clk_hw*[]) { + &eva_cc_mvs0c_div2_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch eva_cc_mvs0c_freerun_clk = { + .halt_reg = 0x805c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x805c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "eva_cc_mvs0c_freerun_clk", + .parent_hws = (const struct clk_hw*[]) { + &eva_cc_mvs0c_div2_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch eva_cc_mvs0c_shift_clk = { + .halt_reg = 0x80dc, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x80dc, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x80dc, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "eva_cc_mvs0c_shift_clk", + .parent_hws = (const struct clk_hw*[]) { + &eva_cc_xo_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct gdsc eva_cc_mvs0c_gdsc = { + .gdscr = 0x8034, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0x6, + .pd = { + .name = "eva_cc_mvs0c_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct gdsc eva_cc_mvs0_gdsc = { + .gdscr = 0x8068, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0x6, + .pd = { + .name = "eva_cc_mvs0_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .flags = HW_CTRL_TRIGGER | POLL_CFG_GDSCR | RETAIN_FF_ENABLE, + .parent = &eva_cc_mvs0c_gdsc.pd, +}; + +static struct clk_regmap *eva_cc_glymur_clocks[] = { + [EVA_CC_AHB_CLK_SRC] = &eva_cc_ahb_clk_src.clkr, + [EVA_CC_MVS0_CLK] = &eva_cc_mvs0_clk.clkr, + [EVA_CC_MVS0_CLK_SRC] = &eva_cc_mvs0_clk_src.clkr, + [EVA_CC_MVS0_DIV_CLK_SRC] = &eva_cc_mvs0_div_clk_src.clkr, + [EVA_CC_MVS0_FREERUN_CLK] = &eva_cc_mvs0_freerun_clk.clkr, + [EVA_CC_MVS0_SHIFT_CLK] = &eva_cc_mvs0_shift_clk.clkr, + [EVA_CC_MVS0C_CLK] = &eva_cc_mvs0c_clk.clkr, + [EVA_CC_MVS0C_DIV2_DIV_CLK_SRC] = &eva_cc_mvs0c_div2_div_clk_src.clkr, + [EVA_CC_MVS0C_FREERUN_CLK] = &eva_cc_mvs0c_freerun_clk.clkr, + [EVA_CC_MVS0C_SHIFT_CLK] = &eva_cc_mvs0c_shift_clk.clkr, + [EVA_CC_PLL0] = &eva_cc_pll0.clkr, + [EVA_CC_SLEEP_CLK_SRC] = &eva_cc_sleep_clk_src.clkr, + [EVA_CC_XO_CLK_SRC] = &eva_cc_xo_clk_src.clkr, +}; + +static struct gdsc *eva_cc_glymur_gdscs[] = { + [EVA_CC_MVS0_GDSC] = &eva_cc_mvs0_gdsc, + [EVA_CC_MVS0C_GDSC] = &eva_cc_mvs0c_gdsc, +}; + +static const struct qcom_reset_map eva_cc_glymur_resets[] = { + [EVA_CC_INTERFACE_BCR] = { 0x80a0 }, + [EVA_CC_MVS0_BCR] = { 0x8064 }, + [EVA_CC_MVS0C_CLK_ARES] = { 0x804c, 2 }, + [EVA_CC_MVS0C_BCR] = { 0x8030 }, + [EVA_CC_MVS0C_FREERUN_CLK_ARES] = { 0x805c, 2 }, +}; + +static struct clk_alpha_pll *eva_cc_glymur_plls[] = { + &eva_cc_pll0, +}; + +static const u32 eva_cc_glymur_critical_cbcrs[] = { + 0x80a4, /* EVA_CC_AHB_CLK */ + 0x80f8, /* EVA_CC_SLEEP_CLK */ + 0x80d4, /* EVA_CC_XO_CLK */ +}; + +static const struct regmap_config eva_cc_glymur_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x9f50, + .fast_io = true, +}; + +static void clk_glymur_regs_configure(struct device *dev, struct regmap *regmap) +{ + /* + * Update CTRL_IN register as per HW recommendation to ensure clocks + * stay cycle‑aligned when the EVA core is ON. + */ + regmap_set_bits(regmap, 0x9f24, BIT(0)); +} + +static const struct qcom_cc_driver_data eva_cc_glymur_driver_data = { + .alpha_plls = eva_cc_glymur_plls, + .num_alpha_plls = ARRAY_SIZE(eva_cc_glymur_plls), + .clk_cbcrs = eva_cc_glymur_critical_cbcrs, + .num_clk_cbcrs = ARRAY_SIZE(eva_cc_glymur_critical_cbcrs), + .clk_regs_configure = clk_glymur_regs_configure, +}; + +static const struct qcom_cc_desc eva_cc_glymur_desc = { + .config = &eva_cc_glymur_regmap_config, + .clks = eva_cc_glymur_clocks, + .num_clks = ARRAY_SIZE(eva_cc_glymur_clocks), + .resets = eva_cc_glymur_resets, + .num_resets = ARRAY_SIZE(eva_cc_glymur_resets), + .gdscs = eva_cc_glymur_gdscs, + .num_gdscs = ARRAY_SIZE(eva_cc_glymur_gdscs), + .use_rpm = true, + .driver_data = &eva_cc_glymur_driver_data, +}; + +static const struct of_device_id eva_cc_glymur_match_table[] = { + { .compatible = "qcom,glymur-evacc" }, + { } +}; +MODULE_DEVICE_TABLE(of, eva_cc_glymur_match_table); + +static int eva_cc_glymur_probe(struct platform_device *pdev) +{ + return qcom_cc_probe(pdev, &eva_cc_glymur_desc); +} + +static struct platform_driver eva_cc_glymur_driver = { + .probe = eva_cc_glymur_probe, + .driver = { + .name = "evacc-glymur", + .of_match_table = eva_cc_glymur_match_table, + }, +}; + +module_platform_driver(eva_cc_glymur_driver); + +MODULE_DESCRIPTION("QTI EVACC Glymur Driver"); +MODULE_LICENSE("GPL"); From d9ef4ed45866531d37a2e9c62dc795cd678b5b0c Mon Sep 17 00:00:00 2001 From: Lin Li Date: Thu, 2 Jul 2026 10:08:07 -0700 Subject: [PATCH 0099/1328] dt-bindings: clock: qcom: Add Hawi video clock controller Add device tree bindings for the video clock controller on Qualcomm Hawi SoC. Signed-off-by: Lin Li Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260702-hawi-videocc-v1-1-6c1e640b0954@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- .../bindings/clock/qcom,sm8450-videocc.yaml | 3 + include/dt-bindings/clock/qcom,hawi-videocc.h | 64 +++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 include/dt-bindings/clock/qcom,hawi-videocc.h diff --git a/Documentation/devicetree/bindings/clock/qcom,sm8450-videocc.yaml b/Documentation/devicetree/bindings/clock/qcom,sm8450-videocc.yaml index 5d77029bfaf8..a6fd1992d6d2 100644 --- a/Documentation/devicetree/bindings/clock/qcom,sm8450-videocc.yaml +++ b/Documentation/devicetree/bindings/clock/qcom,sm8450-videocc.yaml @@ -16,6 +16,7 @@ description: | See also: include/dt-bindings/clock/qcom,glymur-videocc.h + include/dt-bindings/clock/qcom,hawi-videocc.h include/dt-bindings/clock/qcom,kaanapali-videocc.h include/dt-bindings/clock/qcom,sm8450-videocc.h include/dt-bindings/clock/qcom,sm8650-videocc.h @@ -26,6 +27,7 @@ properties: compatible: enum: - qcom,glymur-videocc + - qcom,hawi-videocc - qcom,kaanapali-videocc - qcom,sm8450-videocc - qcom,sm8475-videocc @@ -68,6 +70,7 @@ allOf: contains: enum: - qcom,glymur-videocc + - qcom,hawi-videocc - qcom,kaanapali-videocc - qcom,sm8450-videocc - qcom,sm8550-videocc diff --git a/include/dt-bindings/clock/qcom,hawi-videocc.h b/include/dt-bindings/clock/qcom,hawi-videocc.h new file mode 100644 index 000000000000..8c97079ff1a7 --- /dev/null +++ b/include/dt-bindings/clock/qcom,hawi-videocc.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef _DT_BINDINGS_CLK_QCOM_VIDEO_CC_HAWI_H +#define _DT_BINDINGS_CLK_QCOM_VIDEO_CC_HAWI_H + +/* VIDEO_CC clocks */ +#define VIDEO_CC_AHB_CLK 0 +#define VIDEO_CC_AHB_CLK_SRC 1 +#define VIDEO_CC_CX_AXI0_CLK 2 +#define VIDEO_CC_CX_DBGCH_XO_CLK 3 +#define VIDEO_CC_CX_XO_CLK 4 +#define VIDEO_CC_DBGCH_XO_CLK 5 +#define VIDEO_CC_MVS0_CLK 6 +#define VIDEO_CC_MVS0_CLK_SRC 7 +#define VIDEO_CC_MVS0_SHIFT_CLK 8 +#define VIDEO_CC_MVS0_VPP0_CLK 9 +#define VIDEO_CC_MVS0_VPP0_VPP1_GATING_CLK 10 +#define VIDEO_CC_MVS0_VPP1_CLK 11 +#define VIDEO_CC_MVS0A_CLK 12 +#define VIDEO_CC_MVS0A_CLK_SRC 13 +#define VIDEO_CC_MVS0B_CLK 14 +#define VIDEO_CC_MVS0B_CLK_SRC 15 +#define VIDEO_CC_MVS0C_CLK 16 +#define VIDEO_CC_MVS0C_CLK_SRC 17 +#define VIDEO_CC_MVS0C_CTL_FREERUN_CLK 18 +#define VIDEO_CC_MVS0C_DEBUG_CLK 19 +#define VIDEO_CC_MVS0C_FREERUN_CLK 20 +#define VIDEO_CC_MVS0C_SHIFT_CLK 21 +#define VIDEO_CC_PLL0 22 +#define VIDEO_CC_PLL0_OUT_EVEN 23 +#define VIDEO_CC_PLL1 24 +#define VIDEO_CC_PLL2 25 +#define VIDEO_CC_PLL3 26 +#define VIDEO_CC_SLEEP_CLK 27 +#define VIDEO_CC_XO_CLK 28 +#define VIDEO_CC_XO_CLK_SRC 29 + +/* VIDEO_CC power domains */ +#define VIDEO_CC_AXI0_CX_INT_GDSC 0 +#define VIDEO_CC_MM_INT_GDSC 1 +#define VIDEO_CC_MVS0_GDSC 2 +#define VIDEO_CC_MVS0_VPP0_GDSC 3 +#define VIDEO_CC_MVS0_VPP1_GDSC 4 +#define VIDEO_CC_MVS0A_GDSC 5 +#define VIDEO_CC_MVS0C_GDSC 6 + +/* VIDEO_CC resets */ +#define VIDEO_CC_AXI0_CX_INT_BCR 0 +#define VIDEO_CC_INTERFACE_BCR 1 +#define VIDEO_CC_MM_INT_BCR 2 +#define VIDEO_CC_MVS0_BCR 3 +#define VIDEO_CC_MVS0_VPP0_BCR 4 +#define VIDEO_CC_MVS0_VPP1_BCR 5 +#define VIDEO_CC_MVS0A_BCR 6 +#define VIDEO_CC_MVS0C_CLK_ARES 7 +#define VIDEO_CC_MVS0C_BCR 8 +#define VIDEO_CC_MVS0C_CTL_FREERUN_CLK_ARES 9 +#define VIDEO_CC_MVS0C_FREERUN_CLK_ARES 10 +#define VIDEO_CC_XO_CLK_ARES 11 + +#endif From edbd5d323183ca15676fac30344ec0713b98dc46 Mon Sep 17 00:00:00 2001 From: Lin Li Date: Thu, 2 Jul 2026 10:08:08 -0700 Subject: [PATCH 0100/1328] clk: qcom: Add support for VideoCC driver for Hawi Enable Hawi video clock driver for video SW to be able to control the clocks from the Video SW driver. Reviewed-by: Konrad Dybcio Signed-off-by: Lin Li Link: https://lore.kernel.org/r/20260702-hawi-videocc-v1-2-6c1e640b0954@oss.qualcomm.com [bjorn: Added default m, per request from Krzysztof] Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/Kconfig | 10 + drivers/clk/qcom/Makefile | 1 + drivers/clk/qcom/videocc-hawi.c | 840 ++++++++++++++++++++++++++++++++ 3 files changed, 851 insertions(+) create mode 100644 drivers/clk/qcom/videocc-hawi.c diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig index 225a0782cf71..6fd4aac1b6cf 100644 --- a/drivers/clk/qcom/Kconfig +++ b/drivers/clk/qcom/Kconfig @@ -345,6 +345,16 @@ config CLK_HAWI_TCSRCC Support for the TCSR clock controller on Hawi devices. Say Y if you want to use peripheral devices such as PCIe, USB, UFS. +config CLK_HAWI_VIDEOCC + tristate "Hawi Video Clock Controller" + depends on ARM64 || COMPILE_TEST + select CLK_HAWI_GCC + default m if ARCH_QCOM + help + Support for the video clock controller on Hawi devices. + Say Y if you want to support video devices and functionality such as + video encode/decode. + config APQ_GCC_8084 tristate "APQ8084 Global Clock Controller" depends on ARM || COMPILE_TEST diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile index 97c04bf27409..81237e2c8db3 100644 --- a/drivers/clk/qcom/Makefile +++ b/drivers/clk/qcom/Makefile @@ -32,6 +32,7 @@ obj-$(CONFIG_CLK_GLYMUR_TCSRCC) += tcsrcc-glymur.o obj-$(CONFIG_CLK_GLYMUR_VIDEOCC) += videocc-glymur.o obj-$(CONFIG_CLK_HAWI_GCC) += gcc-hawi.o obj-$(CONFIG_CLK_HAWI_TCSRCC) += tcsrcc-hawi.o +obj-$(CONFIG_CLK_HAWI_VIDEOCC) += videocc-hawi.o obj-$(CONFIG_CLK_KAANAPALI_CAMCC) += cambistmclkcc-kaanapali.o camcc-kaanapali.o obj-$(CONFIG_CLK_KAANAPALI_DISPCC) += dispcc-kaanapali.o obj-$(CONFIG_CLK_KAANAPALI_GCC) += gcc-kaanapali.o diff --git a/drivers/clk/qcom/videocc-hawi.c b/drivers/clk/qcom/videocc-hawi.c new file mode 100644 index 000000000000..ac3ff7202fa0 --- /dev/null +++ b/drivers/clk/qcom/videocc-hawi.c @@ -0,0 +1,840 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include +#include + +#include + +#include "clk-alpha-pll.h" +#include "clk-branch.h" +#include "clk-rcg.h" +#include "clk-regmap.h" +#include "clk-regmap-divider.h" +#include "common.h" +#include "gdsc.h" +#include "reset.h" + +enum { + DT_BI_TCXO, + DT_AHB_CLK, +}; + +enum { + P_BI_TCXO, + P_VIDEO_CC_PLL0_OUT_EVEN, + P_VIDEO_CC_PLL0_OUT_MAIN, + P_VIDEO_CC_PLL1_OUT_MAIN, + P_VIDEO_CC_PLL2_OUT_MAIN, + P_VIDEO_CC_PLL3_OUT_MAIN, +}; + +static const struct pll_vco taycan_eha_t_vco[] = { + { 249600000, 2500000000, 0 }, +}; + +/* 360.0 MHz Configuration */ +static const struct alpha_pll_config video_cc_pll0_config = { + .l = 0x12, + .cal_l = 0x48, + .alpha = 0xc000, + .config_ctl_val = 0xa5c400e7, + .config_ctl_hi_val = 0x0a8060e0, + .config_ctl_hi1_val = 0xf51dea20, + .user_ctl_val = 0x00000400, + .user_ctl_hi_val = 0x00000002, +}; + +static struct clk_alpha_pll video_cc_pll0 = { + .offset = 0x0, + .config = &video_cc_pll0_config, + .vco_table = taycan_eha_t_vco, + .num_vco = ARRAY_SIZE(taycan_eha_t_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_TAYCAN_EHA_T], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_pll0", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_taycan_eha_t_ops, + }, + }, +}; + +static const struct clk_div_table post_div_table_video_cc_pll0_out_even[] = { + { 0x1, 2 }, + { } +}; + +static struct clk_alpha_pll_postdiv video_cc_pll0_out_even = { + .offset = 0x0, + .post_div_shift = 10, + .post_div_table = post_div_table_video_cc_pll0_out_even, + .num_post_div = ARRAY_SIZE(post_div_table_video_cc_pll0_out_even), + .width = 4, + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_TAYCAN_EHA_T], + .clkr.hw.init = &(const struct clk_init_data) { + .name = "video_cc_pll0_out_even", + .parent_hws = (const struct clk_hw*[]) { + &video_cc_pll0.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_alpha_pll_postdiv_taycan_eha_t_ops, + }, +}; + +/* 300.0 MHz Configuration */ +static const struct alpha_pll_config video_cc_pll1_config = { + .l = 0xf, + .cal_l = 0x48, + .alpha = 0xa000, + .config_ctl_val = 0xa5c400e7, + .config_ctl_hi_val = 0x0a8060e0, + .config_ctl_hi1_val = 0xf51dea20, + .user_ctl_val = 0x00000000, + .user_ctl_hi_val = 0x00000002, +}; + +static struct clk_alpha_pll video_cc_pll1 = { + .offset = 0x1000, + .config = &video_cc_pll1_config, + .vco_table = taycan_eha_t_vco, + .num_vco = ARRAY_SIZE(taycan_eha_t_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_TAYCAN_EHA_T], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_pll1", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_taycan_eha_t_ops, + }, + }, +}; + +/* 300.0 MHz Configuration */ +static const struct alpha_pll_config video_cc_pll2_config = { + .l = 0xf, + .cal_l = 0x48, + .alpha = 0xa000, + .config_ctl_val = 0xa5c400e7, + .config_ctl_hi_val = 0x0a8060e0, + .config_ctl_hi1_val = 0xf51dea20, + .user_ctl_val = 0x00000000, + .user_ctl_hi_val = 0x00000002, +}; + +static struct clk_alpha_pll video_cc_pll2 = { + .offset = 0x2000, + .config = &video_cc_pll2_config, + .vco_table = taycan_eha_t_vco, + .num_vco = ARRAY_SIZE(taycan_eha_t_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_TAYCAN_EHA_T], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_pll2", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_taycan_eha_t_ops, + }, + }, +}; + +/* 300.0 MHz Configuration */ +static const struct alpha_pll_config video_cc_pll3_config = { + .l = 0xf, + .cal_l = 0x48, + .alpha = 0xa000, + .config_ctl_val = 0xa5c400e7, + .config_ctl_hi_val = 0x0a8060e0, + .config_ctl_hi1_val = 0xf51dea20, + .user_ctl_val = 0x00000000, + .user_ctl_hi_val = 0x00000002, +}; + +static struct clk_alpha_pll video_cc_pll3 = { + .offset = 0x3000, + .config = &video_cc_pll3_config, + .vco_table = taycan_eha_t_vco, + .num_vco = ARRAY_SIZE(taycan_eha_t_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_TAYCAN_EHA_T], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_pll3", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_taycan_eha_t_ops, + }, + }, +}; + +static const struct parent_map video_cc_parent_map_0[] = { + { P_BI_TCXO, 0 }, +}; + +static const struct clk_parent_data video_cc_parent_data_0[] = { + { .index = DT_BI_TCXO }, +}; + +static const struct parent_map video_cc_parent_map_1[] = { + { P_BI_TCXO, 0 }, + { P_VIDEO_CC_PLL1_OUT_MAIN, 1 }, +}; + +static const struct clk_parent_data video_cc_parent_data_1[] = { + { .index = DT_BI_TCXO }, + { .hw = &video_cc_pll1.clkr.hw }, +}; + +static const struct parent_map video_cc_parent_map_2[] = { + { P_BI_TCXO, 0 }, + { P_VIDEO_CC_PLL3_OUT_MAIN, 1 }, +}; + +static const struct clk_parent_data video_cc_parent_data_2[] = { + { .index = DT_BI_TCXO }, + { .hw = &video_cc_pll3.clkr.hw }, +}; + +static const struct parent_map video_cc_parent_map_3[] = { + { P_BI_TCXO, 0 }, + { P_VIDEO_CC_PLL2_OUT_MAIN, 1 }, +}; + +static const struct clk_parent_data video_cc_parent_data_3[] = { + { .index = DT_BI_TCXO }, + { .hw = &video_cc_pll2.clkr.hw }, +}; + +static const struct parent_map video_cc_parent_map_4[] = { + { P_BI_TCXO, 0 }, + { P_VIDEO_CC_PLL0_OUT_MAIN, 1 }, + { P_VIDEO_CC_PLL0_OUT_EVEN, 2 }, +}; + +static const struct clk_parent_data video_cc_parent_data_4[] = { + { .index = DT_BI_TCXO }, + { .hw = &video_cc_pll0.clkr.hw }, + { .hw = &video_cc_pll0_out_even.clkr.hw }, +}; + +static const struct freq_tbl ftbl_video_cc_ahb_clk_src[] = { + F(19200000, P_BI_TCXO, 1, 0, 0), + { } +}; + +static struct clk_rcg2 video_cc_ahb_clk_src = { + .cmd_rcgr = 0x8060, + .mnd_width = 0, + .hid_width = 5, + .parent_map = video_cc_parent_map_0, + .freq_tbl = ftbl_video_cc_ahb_clk_src, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "video_cc_ahb_clk_src", + .parent_data = video_cc_parent_data_0, + .num_parents = ARRAY_SIZE(video_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_video_cc_mvs0_clk_src[] = { + F(150000000, P_VIDEO_CC_PLL1_OUT_MAIN, 2, 0, 0), + F(240000000, P_VIDEO_CC_PLL1_OUT_MAIN, 2, 0, 0), + F(285000000, P_VIDEO_CC_PLL1_OUT_MAIN, 2, 0, 0), + F(311000000, P_VIDEO_CC_PLL1_OUT_MAIN, 2, 0, 0), + F(420000000, P_VIDEO_CC_PLL1_OUT_MAIN, 2, 0, 0), + F(444000000, P_VIDEO_CC_PLL1_OUT_MAIN, 2, 0, 0), + F(533000000, P_VIDEO_CC_PLL1_OUT_MAIN, 2, 0, 0), + F(630000000, P_VIDEO_CC_PLL1_OUT_MAIN, 2, 0, 0), + F(714000000, P_VIDEO_CC_PLL1_OUT_MAIN, 2, 0, 0), + { } +}; + +static struct clk_rcg2 video_cc_mvs0_clk_src = { + .cmd_rcgr = 0x8030, + .mnd_width = 0, + .hid_width = 5, + .parent_map = video_cc_parent_map_1, + .freq_tbl = ftbl_video_cc_mvs0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0_clk_src", + .parent_data = video_cc_parent_data_1, + .num_parents = ARRAY_SIZE(video_cc_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_video_cc_mvs0a_clk_src[] = { + F(150000000, P_VIDEO_CC_PLL3_OUT_MAIN, 2, 0, 0), + F(240000000, P_VIDEO_CC_PLL3_OUT_MAIN, 2, 0, 0), + F(338000000, P_VIDEO_CC_PLL3_OUT_MAIN, 2, 0, 0), + F(420000000, P_VIDEO_CC_PLL3_OUT_MAIN, 2, 0, 0), + F(444000000, P_VIDEO_CC_PLL3_OUT_MAIN, 2, 0, 0), + F(533000000, P_VIDEO_CC_PLL3_OUT_MAIN, 2, 0, 0), + F(630000000, P_VIDEO_CC_PLL3_OUT_MAIN, 2, 0, 0), + F(710000000, P_VIDEO_CC_PLL3_OUT_MAIN, 2, 0, 0), + { } +}; + +static struct clk_rcg2 video_cc_mvs0a_clk_src = { + .cmd_rcgr = 0x8000, + .mnd_width = 0, + .hid_width = 5, + .parent_map = video_cc_parent_map_2, + .freq_tbl = ftbl_video_cc_mvs0a_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0a_clk_src", + .parent_data = video_cc_parent_data_2, + .num_parents = ARRAY_SIZE(video_cc_parent_data_2), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_video_cc_mvs0b_clk_src[] = { + F(150000000, P_VIDEO_CC_PLL2_OUT_MAIN, 2, 0, 0), + F(240000000, P_VIDEO_CC_PLL2_OUT_MAIN, 2, 0, 0), + F(311000000, P_VIDEO_CC_PLL2_OUT_MAIN, 2, 0, 0), + F(420000000, P_VIDEO_CC_PLL2_OUT_MAIN, 2, 0, 0), + F(444000000, P_VIDEO_CC_PLL2_OUT_MAIN, 2, 0, 0), + F(533000000, P_VIDEO_CC_PLL2_OUT_MAIN, 2, 0, 0), + F(630000000, P_VIDEO_CC_PLL2_OUT_MAIN, 2, 0, 0), + F(667000000, P_VIDEO_CC_PLL2_OUT_MAIN, 2, 0, 0), + { } +}; + +static struct clk_rcg2 video_cc_mvs0b_clk_src = { + .cmd_rcgr = 0x8018, + .mnd_width = 0, + .hid_width = 5, + .parent_map = video_cc_parent_map_3, + .freq_tbl = ftbl_video_cc_mvs0b_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0b_clk_src", + .parent_data = video_cc_parent_data_3, + .num_parents = ARRAY_SIZE(video_cc_parent_data_3), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_video_cc_mvs0c_clk_src[] = { + F(225000000, P_VIDEO_CC_PLL0_OUT_EVEN, 1, 0, 0), + F(360000000, P_VIDEO_CC_PLL0_OUT_MAIN, 1, 0, 0), + F(430000000, P_VIDEO_CC_PLL0_OUT_MAIN, 1, 0, 0), + F(557000000, P_VIDEO_CC_PLL0_OUT_MAIN, 1, 0, 0), + F(634000000, P_VIDEO_CC_PLL0_OUT_MAIN, 1, 0, 0), + F(782000000, P_VIDEO_CC_PLL0_OUT_MAIN, 1, 0, 0), + F(928000000, P_VIDEO_CC_PLL0_OUT_MAIN, 1, 0, 0), + F(1060000000, P_VIDEO_CC_PLL0_OUT_MAIN, 1, 0, 0), + { } +}; + +static struct clk_rcg2 video_cc_mvs0c_clk_src = { + .cmd_rcgr = 0x8048, + .mnd_width = 0, + .hid_width = 5, + .parent_map = video_cc_parent_map_4, + .freq_tbl = ftbl_video_cc_mvs0c_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0c_clk_src", + .parent_data = video_cc_parent_data_4, + .num_parents = ARRAY_SIZE(video_cc_parent_data_4), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 video_cc_xo_clk_src = { + .cmd_rcgr = 0x8180, + .mnd_width = 0, + .hid_width = 5, + .parent_map = video_cc_parent_map_0, + .freq_tbl = ftbl_video_cc_ahb_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "video_cc_xo_clk_src", + .parent_data = video_cc_parent_data_0, + .num_parents = ARRAY_SIZE(video_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_ops, + }, +}; + +static struct clk_branch video_cc_cx_axi0_clk = { + .halt_reg = 0x81e8, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x81e8, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x81e8, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_cx_axi0_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch video_cc_mvs0_clk = { + .halt_reg = 0x80cc, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x80cc, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x80cc, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0_clk", + .parent_hws = (const struct clk_hw*[]) { + &video_cc_mvs0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_aon_ops, + }, + }, +}; + +static struct clk_branch video_cc_mvs0_shift_clk = { + .halt_reg = 0x81a4, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x81a4, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x81a4, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0_shift_clk", + .parent_hws = (const struct clk_hw*[]) { + &video_cc_xo_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch video_cc_mvs0_vpp0_clk = { + .halt_reg = 0x811c, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x811c, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x811c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0_vpp0_clk", + .parent_hws = (const struct clk_hw*[]) { + &video_cc_mvs0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_aon_ops, + }, + }, +}; + +static struct clk_branch video_cc_mvs0_vpp0_vpp1_gating_clk = { + .halt_reg = 0x80c8, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x80c8, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x80c8, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0_vpp0_vpp1_gating_clk", + .parent_hws = (const struct clk_hw*[]) { + &video_cc_mvs0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch video_cc_mvs0_vpp1_clk = { + .halt_reg = 0x80f4, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x80f4, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x80f4, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0_vpp1_clk", + .parent_hws = (const struct clk_hw*[]) { + &video_cc_mvs0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_aon_ops, + }, + }, +}; + +static struct clk_branch video_cc_mvs0a_clk = { + .halt_reg = 0x8090, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x8090, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x8090, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0a_clk", + .parent_hws = (const struct clk_hw*[]) { + &video_cc_mvs0a_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch video_cc_mvs0b_clk = { + .halt_reg = 0x80b8, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x80b8, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x80b8, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0b_clk", + .parent_hws = (const struct clk_hw*[]) { + &video_cc_mvs0b_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch video_cc_mvs0c_clk = { + .halt_reg = 0x814c, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x814c, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x814c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0c_clk", + .parent_hws = (const struct clk_hw*[]) { + &video_cc_mvs0c_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch video_cc_mvs0c_ctl_freerun_clk = { + .halt_reg = 0x8160, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8160, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0c_ctl_freerun_clk", + .parent_hws = (const struct clk_hw*[]) { + &video_cc_mvs0c_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch video_cc_mvs0c_debug_clk = { + .halt_reg = 0x8148, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8148, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0c_debug_clk", + .parent_hws = (const struct clk_hw*[]) { + &video_cc_mvs0c_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch video_cc_mvs0c_freerun_clk = { + .halt_reg = 0x815c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x815c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0c_freerun_clk", + .parent_hws = (const struct clk_hw*[]) { + &video_cc_mvs0c_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch video_cc_mvs0c_shift_clk = { + .halt_reg = 0x81a8, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x81a8, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x81a8, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0c_shift_clk", + .parent_hws = (const struct clk_hw*[]) { + &video_cc_xo_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct gdsc video_cc_mvs0c_gdsc = { + .gdscr = 0x8130, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0x6, + .pd = { + .name = "video_cc_mvs0c_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct gdsc video_cc_axi0_cx_int_gdsc = { + .gdscr = 0x81cc, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0xf, + .pd = { + .name = "video_cc_axi0_cx_int_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .parent = &video_cc_mvs0c_gdsc.pd, + .flags = HW_CTRL_TRIGGER | POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct gdsc video_cc_mm_int_gdsc = { + .gdscr = 0x81b4, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0x2, + .pd = { + .name = "video_cc_mm_int_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .parent = &video_cc_axi0_cx_int_gdsc.pd, + .flags = HW_CTRL_TRIGGER | POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct gdsc video_cc_mvs0_gdsc = { + .gdscr = 0x80a4, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0x6, + .pd = { + .name = "video_cc_mvs0_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .parent = &video_cc_mm_int_gdsc.pd, + .flags = HW_CTRL_TRIGGER | POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct gdsc video_cc_mvs0_vpp0_gdsc = { + .gdscr = 0x8108, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0xf, + .pd = { + .name = "video_cc_mvs0_vpp0_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .parent = &video_cc_mvs0_gdsc.pd, + .flags = HW_CTRL_TRIGGER | POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct gdsc video_cc_mvs0_vpp1_gdsc = { + .gdscr = 0x80e0, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0xf, + .pd = { + .name = "video_cc_mvs0_vpp1_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .parent = &video_cc_mvs0_gdsc.pd, + .flags = HW_CTRL_TRIGGER | POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct gdsc video_cc_mvs0a_gdsc = { + .gdscr = 0x807c, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0xf, + .pd = { + .name = "video_cc_mvs0a_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .parent = &video_cc_mm_int_gdsc.pd, + .flags = HW_CTRL_TRIGGER | POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct clk_regmap *video_cc_hawi_clocks[] = { + [VIDEO_CC_AHB_CLK_SRC] = &video_cc_ahb_clk_src.clkr, + [VIDEO_CC_CX_AXI0_CLK] = &video_cc_cx_axi0_clk.clkr, + [VIDEO_CC_MVS0_CLK] = &video_cc_mvs0_clk.clkr, + [VIDEO_CC_MVS0_CLK_SRC] = &video_cc_mvs0_clk_src.clkr, + [VIDEO_CC_MVS0_SHIFT_CLK] = &video_cc_mvs0_shift_clk.clkr, + [VIDEO_CC_MVS0_VPP0_CLK] = &video_cc_mvs0_vpp0_clk.clkr, + [VIDEO_CC_MVS0_VPP0_VPP1_GATING_CLK] = &video_cc_mvs0_vpp0_vpp1_gating_clk.clkr, + [VIDEO_CC_MVS0_VPP1_CLK] = &video_cc_mvs0_vpp1_clk.clkr, + [VIDEO_CC_MVS0A_CLK] = &video_cc_mvs0a_clk.clkr, + [VIDEO_CC_MVS0A_CLK_SRC] = &video_cc_mvs0a_clk_src.clkr, + [VIDEO_CC_MVS0B_CLK] = &video_cc_mvs0b_clk.clkr, + [VIDEO_CC_MVS0B_CLK_SRC] = &video_cc_mvs0b_clk_src.clkr, + [VIDEO_CC_MVS0C_CLK] = &video_cc_mvs0c_clk.clkr, + [VIDEO_CC_MVS0C_CLK_SRC] = &video_cc_mvs0c_clk_src.clkr, + [VIDEO_CC_MVS0C_CTL_FREERUN_CLK] = &video_cc_mvs0c_ctl_freerun_clk.clkr, + [VIDEO_CC_MVS0C_DEBUG_CLK] = &video_cc_mvs0c_debug_clk.clkr, + [VIDEO_CC_MVS0C_FREERUN_CLK] = &video_cc_mvs0c_freerun_clk.clkr, + [VIDEO_CC_MVS0C_SHIFT_CLK] = &video_cc_mvs0c_shift_clk.clkr, + [VIDEO_CC_PLL0] = &video_cc_pll0.clkr, + [VIDEO_CC_PLL0_OUT_EVEN] = &video_cc_pll0_out_even.clkr, + [VIDEO_CC_PLL1] = &video_cc_pll1.clkr, + [VIDEO_CC_PLL2] = &video_cc_pll2.clkr, + [VIDEO_CC_PLL3] = &video_cc_pll3.clkr, + [VIDEO_CC_XO_CLK_SRC] = &video_cc_xo_clk_src.clkr, +}; + +static struct gdsc *video_cc_hawi_gdscs[] = { + [VIDEO_CC_AXI0_CX_INT_GDSC] = &video_cc_axi0_cx_int_gdsc, + [VIDEO_CC_MM_INT_GDSC] = &video_cc_mm_int_gdsc, + [VIDEO_CC_MVS0_GDSC] = &video_cc_mvs0_gdsc, + [VIDEO_CC_MVS0_VPP0_GDSC] = &video_cc_mvs0_vpp0_gdsc, + [VIDEO_CC_MVS0_VPP1_GDSC] = &video_cc_mvs0_vpp1_gdsc, + [VIDEO_CC_MVS0A_GDSC] = &video_cc_mvs0a_gdsc, + [VIDEO_CC_MVS0C_GDSC] = &video_cc_mvs0c_gdsc, +}; + +static const struct qcom_reset_map video_cc_hawi_resets[] = { + [VIDEO_CC_AXI0_CX_INT_BCR] = { 0x81c8 }, + [VIDEO_CC_INTERFACE_BCR] = { 0x8164 }, + [VIDEO_CC_MM_INT_BCR] = { 0x81b0 }, + [VIDEO_CC_MVS0_BCR] = { 0x80a0 }, + [VIDEO_CC_MVS0_VPP0_BCR] = { 0x8104 }, + [VIDEO_CC_MVS0_VPP1_BCR] = { 0x80dc }, + [VIDEO_CC_MVS0A_BCR] = { 0x8078 }, + [VIDEO_CC_MVS0C_CLK_ARES] = { 0x814c, 2 }, + [VIDEO_CC_MVS0C_BCR] = { 0x812c }, + [VIDEO_CC_MVS0C_CTL_FREERUN_CLK_ARES] = { 0x8160, 2 }, + [VIDEO_CC_MVS0C_FREERUN_CLK_ARES] = { 0x815c, 2 }, + [VIDEO_CC_XO_CLK_ARES] = { 0x8198, 2 }, +}; + +static struct clk_alpha_pll *video_cc_hawi_plls[] = { + &video_cc_pll0, + &video_cc_pll1, + &video_cc_pll2, + &video_cc_pll3, +}; + +static const u32 video_cc_hawi_critical_cbcrs[] = { + 0x8168, /* VIDEO_CC_AHB_CLK */ + 0x81e4, /* VIDEO_CC_CX_DBGCH_XO_CLK */ + 0x81e0, /* VIDEO_CC_CX_XO_CLK */ + 0x81a0, /* VIDEO_CC_DBGCH_XO_CLK */ + 0x81ac, /* VIDEO_CC_SLEEP_CLK */ + 0x819c, /* VIDEO_CC_TS_XO_CLK */ + 0x8198, /* VIDEO_CC_XO_CLK */ +}; + +static const struct regmap_config video_cc_hawi_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0xa018, + .fast_io = true, +}; + +static struct qcom_cc_driver_data video_cc_hawi_driver_data = { + .alpha_plls = video_cc_hawi_plls, + .num_alpha_plls = ARRAY_SIZE(video_cc_hawi_plls), + .clk_cbcrs = video_cc_hawi_critical_cbcrs, + .num_clk_cbcrs = ARRAY_SIZE(video_cc_hawi_critical_cbcrs), +}; + +static const struct qcom_cc_desc video_cc_hawi_desc = { + .config = &video_cc_hawi_regmap_config, + .clks = video_cc_hawi_clocks, + .num_clks = ARRAY_SIZE(video_cc_hawi_clocks), + .resets = video_cc_hawi_resets, + .num_resets = ARRAY_SIZE(video_cc_hawi_resets), + .gdscs = video_cc_hawi_gdscs, + .num_gdscs = ARRAY_SIZE(video_cc_hawi_gdscs), + .use_rpm = true, + .driver_data = &video_cc_hawi_driver_data, +}; + +static const struct of_device_id video_cc_hawi_match_table[] = { + { .compatible = "qcom,hawi-videocc" }, + { } +}; +MODULE_DEVICE_TABLE(of, video_cc_hawi_match_table); + +static int video_cc_hawi_probe(struct platform_device *pdev) +{ + return qcom_cc_probe(pdev, &video_cc_hawi_desc); +} + +static struct platform_driver video_cc_hawi_driver = { + .probe = video_cc_hawi_probe, + .driver = { + .name = "videocc-hawi", + .of_match_table = video_cc_hawi_match_table, + }, +}; + +module_platform_driver(video_cc_hawi_driver); + +MODULE_DESCRIPTION("QTI VIDEOCC Hawi Driver"); +MODULE_LICENSE("GPL"); From 13b10571cc353448275365ede1a5396d20dfe839 Mon Sep 17 00:00:00 2001 From: Heiko Stuebner Date: Wed, 4 Mar 2026 13:14:25 +0100 Subject: [PATCH 0101/1328] clk: rockchip: rk3588: Don't change PLL rates when setting dclk_vop2_src dclk_vop2_src currently has the CLK_SET_RATE_PARENT flag set, which is very different from dclk_vop0_src or dclk_vop1_src, which don't have it. With this flag in dclk_vop2_src, actually setting the clock then results in a lot of other peripherals breaking, because setting the rate results in the PLL source getting changed: [ 14.898718] clk_core_set_rate_nolock: setting rate for dclk_vop2 to 152840000 [ 15.155017] clk_change_rate: setting rate for pll_gpll to 1680000000 [ clk adjusting every gpll user ] This includes possibly the other vops, i2s, spdif and even the uarts. Among other possible things, this breaks the uart console on a board I use. Sometimes it recovers later on, but there will be a big block of garbled output for a while at least. Shared PLLs should not be changed by individual users, so drop this flag from dclk_vop2_src. Fixes: f1c506d152ff ("clk: rockchip: add clock controller for the RK3588") Cc: stable@vger.kernel.org Tested-by: Quentin Schulz # RK3588 Tiger w/ DP Signed-off-by: Heiko Stuebner Reviewed-by: Quentin Schulz Tested-by: Chris Morgan Link: https://patch.msgid.link/20260304121426.1184680-2-heiko@sntech.de Signed-off-by: Heiko Stuebner --- drivers/clk/rockchip/clk-rk3588.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/rockchip/clk-rk3588.c b/drivers/clk/rockchip/clk-rk3588.c index 2ba9976654cf..ff95f6c28b89 100644 --- a/drivers/clk/rockchip/clk-rk3588.c +++ b/drivers/clk/rockchip/clk-rk3588.c @@ -2102,7 +2102,7 @@ static struct rockchip_clk_branch rk3588_early_clk_branches[] __initdata = { COMPOSITE(DCLK_VOP1_SRC, "dclk_vop1_src", gpll_cpll_v0pll_aupll_p, 0, RK3588_CLKSEL_CON(111), 14, 2, MFLAGS, 9, 5, DFLAGS, RK3588_CLKGATE_CON(52), 11, GFLAGS), - COMPOSITE(DCLK_VOP2_SRC, "dclk_vop2_src", gpll_cpll_v0pll_aupll_p, CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT, + COMPOSITE(DCLK_VOP2_SRC, "dclk_vop2_src", gpll_cpll_v0pll_aupll_p, CLK_SET_RATE_NO_REPARENT, RK3588_CLKSEL_CON(112), 5, 2, MFLAGS, 0, 5, DFLAGS, RK3588_CLKGATE_CON(52), 12, GFLAGS), COMPOSITE_NODIV(DCLK_VOP0, "dclk_vop0", dclk_vop0_p, From 778dd30867ad783b6903b0aa24a0ab5e97c2dff6 Mon Sep 17 00:00:00 2001 From: Heiko Stuebner Date: Wed, 4 Mar 2026 13:14:26 +0100 Subject: [PATCH 0102/1328] clk: rockchip: rk3588: Allow VP2 the same sourcing options as other VPs Right now dclk_vop2_src is not allowed to change its parent, while the other two dclks are allowed to do this. All three VPs can drive non-static outputs (HDMI, DP, eDP) and even the eDP can also support full DP connections (though no DP+). Therefore remove the CLK_SET_RATE_NO_REPARENT flag, to allow VP2 the same flexibility in acquiring its clock rate. Tested-by: Quentin Schulz # RK3588 Tiger w/ DP Signed-off-by: Heiko Stuebner Reviewed-by: Quentin Schulz Tested-by: Chris Morgan Link: https://patch.msgid.link/20260304121426.1184680-3-heiko@sntech.de Signed-off-by: Heiko Stuebner --- drivers/clk/rockchip/clk-rk3588.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/rockchip/clk-rk3588.c b/drivers/clk/rockchip/clk-rk3588.c index ff95f6c28b89..f20764b0ffcf 100644 --- a/drivers/clk/rockchip/clk-rk3588.c +++ b/drivers/clk/rockchip/clk-rk3588.c @@ -2102,7 +2102,7 @@ static struct rockchip_clk_branch rk3588_early_clk_branches[] __initdata = { COMPOSITE(DCLK_VOP1_SRC, "dclk_vop1_src", gpll_cpll_v0pll_aupll_p, 0, RK3588_CLKSEL_CON(111), 14, 2, MFLAGS, 9, 5, DFLAGS, RK3588_CLKGATE_CON(52), 11, GFLAGS), - COMPOSITE(DCLK_VOP2_SRC, "dclk_vop2_src", gpll_cpll_v0pll_aupll_p, CLK_SET_RATE_NO_REPARENT, + COMPOSITE(DCLK_VOP2_SRC, "dclk_vop2_src", gpll_cpll_v0pll_aupll_p, 0, RK3588_CLKSEL_CON(112), 5, 2, MFLAGS, 0, 5, DFLAGS, RK3588_CLKGATE_CON(52), 12, GFLAGS), COMPOSITE_NODIV(DCLK_VOP0, "dclk_vop0", dclk_vop0_p, From ab171a6afd4ea68444667cfa5c7b24237b9d09d3 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Fri, 15 May 2026 14:59:24 +0300 Subject: [PATCH 0103/1328] clk: qcom: gcc-sm6115: Set HW_CTRL_TRIGGER for video GDSC The venus video driver will uses dev_pm_genpd_set_hwmode() API to switch the video GDSC to HW and SW control modes at runtime. This requires domain to have the HW_CTRL_TRIGGER flag. Signed-off-by: Dmitry Baryshkov Reviewed-by: Vishnu Reddy Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260515-iris-sm6115-v2-1-2ab75229de61@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gcc-sm6115.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/clk/qcom/gcc-sm6115.c b/drivers/clk/qcom/gcc-sm6115.c index 4c3804701e24..c5251aff9886 100644 --- a/drivers/clk/qcom/gcc-sm6115.c +++ b/drivers/clk/qcom/gcc-sm6115.c @@ -3218,6 +3218,7 @@ static struct gdsc gcc_vcodec0_gdsc = { .pd = { .name = "gcc_vcodec0", }, + .flags = HW_CTRL_TRIGGER, .pwrsts = PWRSTS_OFF_ON, }; From d69f0c2b8d292b4890c9f0fbe184dfc26c4de86c Mon Sep 17 00:00:00 2001 From: Herman van Hazendonk Date: Tue, 2 Jun 2026 16:09:32 +0200 Subject: [PATCH 0104/1328] clk: qcom: gdsc: propagate gdsc_check_status() errors from gdsc_poll_status gdsc_check_status() returns negative errno when the underlying regmap_read() fails -- e.g. when a parent regmap dies during system suspend, a CSR is removed by an HW debug tool, or the bus controller goes into protection. gdsc_poll_status() treats the result as a plain boolean ("is the GDSC in the requested state?"), so any negative error return is truncated to "true" and the poll exits with success even though the rail's real state is unknown: do { if (gdsc_check_status(sc, status)) return 0; } while (ktime_us_delta(ktime_get(), start) < STATUS_POLL_TIMEOUT_US); if (gdsc_check_status(sc, status)) return 0; return -ETIMEDOUT; This silently misleads gdsc_toggle_logic() (which writes/un-writes SW_COLLAPSE on the strength of the poll succeeding) and the gdsc_init() sync path (which assumes the readback represents real silicon state). Latch the return value, propagate negative errno immediately, and only treat a strictly-positive value as "reached the target state". Make the same change in the post-timeout final check so a regmap that comes back after the deadline does not silently degrade to -ETIMEDOUT. Signed-off-by: Herman van Hazendonk Fixes: 77b1067a19b4 ("clk: qcom: gdsc: Add support for gdscs with gds hw controller") Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20260602140934.796697-2-github.com@herrie.org Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gdsc.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/clk/qcom/gdsc.c b/drivers/clk/qcom/gdsc.c index f419a28f616b..428d63865798 100644 --- a/drivers/clk/qcom/gdsc.c +++ b/drivers/clk/qcom/gdsc.c @@ -104,14 +104,21 @@ static int gdsc_hwctrl(struct gdsc *sc, bool en) static int gdsc_poll_status(struct gdsc *sc, enum gdsc_status status) { ktime_t start; + int ret; start = ktime_get(); do { - if (gdsc_check_status(sc, status)) + ret = gdsc_check_status(sc, status); + if (ret < 0) + return ret; + if (ret) return 0; } while (ktime_us_delta(ktime_get(), start) < STATUS_POLL_TIMEOUT_US); - if (gdsc_check_status(sc, status)) + ret = gdsc_check_status(sc, status); + if (ret < 0) + return ret; + if (ret) return 0; return -ETIMEDOUT; From eea55fc694e132aacbe2cf4be7f345115e3d1801 Mon Sep 17 00:00:00 2001 From: Herman van Hazendonk Date: Tue, 2 Jun 2026 16:09:33 +0200 Subject: [PATCH 0105/1328] clk: qcom: gdsc: propagate gdsc_enable() failure for ALWAYS_ON domains GENPD_FLAG_ALWAYS_ON requires the underlying domain to be on at genpd_init() time -- the framework will refuse to register the domain otherwise. When the cold readback in gdsc_init() finds an ALWAYS_ON GDSC powered down, the driver tries to bring it back up: } else if (sc->flags & ALWAYS_ON) { /* If ALWAYS_ON GDSCs are not ON, turn them ON */ gdsc_enable(&sc->pd); on = true; } but discards the return value: if gdsc_enable() fails (regmap write error, the long-form sequence's status poll times out, or the HW_CTRL hand-off errors) the code still sets on=true and falls through to pm_genpd_init(..., !on) -- which then registers the domain in the ON state and sets GENPD_FLAG_ALWAYS_ON, even though the silicon is actually off. Subsequent consumer probes will see genpd report "on" while accessing dead registers and hang or read garbage. Catch the failure and surface it: returning the error from gdsc_init() makes the provider probe fail with the underlying errno, which propagates to consumers as -EPROBE_DEFER (or fatal if the hardware really is broken) rather than silently lying about the rail state. Signed-off-by: Herman van Hazendonk Fixes: fb55bea1fe43 ("clk: qcom: gdsc: Add support for ALWAYS_ON gdscs") Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20260602140934.796697-3-github.com@herrie.org Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gdsc.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/clk/qcom/gdsc.c b/drivers/clk/qcom/gdsc.c index 428d63865798..17717b7fe6a0 100644 --- a/drivers/clk/qcom/gdsc.c +++ b/drivers/clk/qcom/gdsc.c @@ -500,7 +500,9 @@ static int gdsc_init(struct gdsc *sc) } else if (sc->flags & ALWAYS_ON) { /* If ALWAYS_ON GDSCs are not ON, turn them ON */ - gdsc_enable(&sc->pd); + ret = gdsc_enable(&sc->pd); + if (ret) + return ret; on = true; } From 86b23609d5e17a770d03037e53c6a443e742a6e6 Mon Sep 17 00:00:00 2001 From: Herman van Hazendonk Date: Tue, 2 Jun 2026 16:09:34 +0200 Subject: [PATCH 0106/1328] clk: qcom: gdsc: tear down per-domain genpds in gdsc_unregister() gdsc_unregister() removes the OF provider entry and tears down the parent/subdomain wiring, but never calls pm_genpd_remove() on the individual generic_pm_domain structures registered by gdsc_init(): void gdsc_unregister(struct gdsc_desc *desc) { struct device *dev = desc->dev; size_t num = desc->num; gdsc_pm_subdomain_remove(desc, num); of_genpd_del_provider(dev->of_node); } That leaves dangling entries on the global gpd_list. After a provider unbind/rebind cycle (deferred-probe replay during early boot, real module unload of a clk driver that owns GDSCs, or an OF-overlay tear- down) the next gdsc_init() will end up trying to re-register a name that is still in the list and pm_genpd_init() returns -EEXIST. While we are here, flip the order so the consumer-facing OF provider entry is the first thing removed -- otherwise a fresh of_genpd_get_from_provider() call racing with the teardown could attach to a domain that is mid-removal. Iterate the scs[] array and pm_genpd_remove() each registered domain after the subdomain links are torn down. The regulators stay devm- managed (devm_regulator_get_optional() in gdsc_register()), so the release happens automatically when the underlying device is unbound; just the genpd accounting needs to be undone explicitly. Signed-off-by: Herman van Hazendonk Fixes: 45dd0e55317c ("clk: qcom: Add support for GDSCs") Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20260602140934.796697-4-github.com@herrie.org Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gdsc.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/clk/qcom/gdsc.c b/drivers/clk/qcom/gdsc.c index 17717b7fe6a0..b99d70149af3 100644 --- a/drivers/clk/qcom/gdsc.c +++ b/drivers/clk/qcom/gdsc.c @@ -678,10 +678,18 @@ int gdsc_register(struct gdsc_desc *desc, void gdsc_unregister(struct gdsc_desc *desc) { struct device *dev = desc->dev; + struct gdsc **scs = desc->scs; size_t num = desc->num; + int i; - gdsc_pm_subdomain_remove(desc, num); of_genpd_del_provider(dev->of_node); + gdsc_pm_subdomain_remove(desc, num); + + for (i = 0; i < num; i++) { + if (!scs[i]) + continue; + pm_genpd_remove(&scs[i]->pd); + } } /* From 20469662a552101c859d1e5b55e6d2f5db42bb3a Mon Sep 17 00:00:00 2001 From: Imran Shaik Date: Mon, 8 Jun 2026 17:51:51 +0530 Subject: [PATCH 0107/1328] clk: qcom: smd-rpm: Add missing RF_CLK1/RF_CLK2 clocks support on Agatti Add support for missing RF_CLK1/RF_CLK2 clocks on Qualcomm Agatti (QCM2290) SoC. Signed-off-by: Imran Shaik Reviewed-by: Dmitry Baryshkov Reviewed-by: Taniya Das Reviewed-by: Jagadeesh Kona Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260608-shikra-gcc-rpmcc-clks-v5-3-94cefe092ee3@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/clk-smd-rpm.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/clk/qcom/clk-smd-rpm.c b/drivers/clk/qcom/clk-smd-rpm.c index 103db984a40b..0b624ed4715c 100644 --- a/drivers/clk/qcom/clk-smd-rpm.c +++ b/drivers/clk/qcom/clk-smd-rpm.c @@ -495,6 +495,7 @@ DEFINE_CLK_SMD_RPM_XO_BUFFER_PINCTRL(rf_clk2, 5, 19200000); DEFINE_CLK_SMD_RPM_XO_BUFFER_PINCTRL(rf_clk3, 6, 19200000); DEFINE_CLK_SMD_RPM_XO_BUFFER_PINCTRL(ln_bb_clk, 8, 19200000); +DEFINE_CLK_SMD_RPM_XO_BUFFER_PREFIX(38m4_, rf_clk2, 5, 38400000); DEFINE_CLK_SMD_RPM_XO_BUFFER_PREFIX(38m4_, rf_clk3, 6, 38400000); DEFINE_CLK_SMD_RPM_XO_BUFFER_PINCTRL(cxo_d0, 1, 19200000); @@ -1262,6 +1263,10 @@ static struct clk_smd_rpm *qcm2290_clks[] = { [RPM_SMD_QDSS_A_CLK] = &clk_smd_rpm_branch_qdss_a_clk, [RPM_SMD_LN_BB_CLK2] = &clk_smd_rpm_ln_bb_clk2, [RPM_SMD_LN_BB_CLK2_A] = &clk_smd_rpm_ln_bb_clk2_a, + [RPM_SMD_RF_CLK1] = &clk_smd_rpm_rf_clk1, + [RPM_SMD_RF_CLK1_A] = &clk_smd_rpm_rf_clk1_a, + [RPM_SMD_RF_CLK2] = &clk_smd_rpm_38m4_rf_clk2, + [RPM_SMD_RF_CLK2_A] = &clk_smd_rpm_38m4_rf_clk2_a, [RPM_SMD_RF_CLK3] = &clk_smd_rpm_38m4_rf_clk3, [RPM_SMD_RF_CLK3_A] = &clk_smd_rpm_38m4_rf_clk3_a, [RPM_SMD_IPA_CLK] = &clk_smd_rpm_ipa_clk, From 01cf3e27824d1cf2dcb815e59087c6556819b039 Mon Sep 17 00:00:00 2001 From: Imran Shaik Date: Mon, 8 Jun 2026 17:51:52 +0530 Subject: [PATCH 0108/1328] clk: qcom: Add Global clock controller support on Qualcomm Shikra SoC Add support for Global clock controller (GCC) on Qualcomm Shikra SoC. Reviewed-by: Taniya Das Reviewed-by: Dmitry Baryshkov Signed-off-by: Imran Shaik Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260608-shikra-gcc-rpmcc-clks-v5-4-94cefe092ee3@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/Kconfig | 10 + drivers/clk/qcom/Makefile | 1 + drivers/clk/qcom/gcc-shikra.c | 4431 +++++++++++++++++++++++++++++++++ 3 files changed, 4442 insertions(+) create mode 100644 drivers/clk/qcom/gcc-shikra.c diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig index 6fd4aac1b6cf..00880d676296 100644 --- a/drivers/clk/qcom/Kconfig +++ b/drivers/clk/qcom/Kconfig @@ -166,6 +166,16 @@ config CLK_NORD_GCC SPI, I2C, USB, SD/UFS, PCIe etc. The clock controller is a combination of GCC, SE_GCC, NE_GCC and NW_GCC. +config CLK_SHIKRA_GCC + tristate "Shikra Global Clock Controller" + depends on ARM64 || COMPILE_TEST + select QCOM_GDSC + default ARCH_QCOM + help + Support for the global clock controller on Shikra devices. + Say Y if you want to use multimedia devices or peripheral + devices such as Camera, Video, UART, SPI, I2C, USB, SD/eMMC etc. + config CLK_X1E80100_CAMCC tristate "X1E80100 Camera Clock Controller" depends on ARM64 || COMPILE_TEST diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile index 81237e2c8db3..76946206d4aa 100644 --- a/drivers/clk/qcom/Makefile +++ b/drivers/clk/qcom/Makefile @@ -41,6 +41,7 @@ obj-$(CONFIG_CLK_KAANAPALI_TCSRCC) += tcsrcc-kaanapali.o obj-$(CONFIG_CLK_KAANAPALI_VIDEOCC) += videocc-kaanapali.o obj-$(CONFIG_CLK_NORD_GCC) += gcc-nord.o negcc-nord.o nwgcc-nord.o segcc-nord.o obj-$(CONFIG_CLK_NORD_TCSRCC) += tcsrcc-nord.o +obj-$(CONFIG_CLK_SHIKRA_GCC) += gcc-shikra.o obj-$(CONFIG_CLK_X1E80100_CAMCC) += camcc-x1e80100.o obj-$(CONFIG_CLK_X1E80100_DISPCC) += dispcc-x1e80100.o obj-$(CONFIG_CLK_X1E80100_GCC) += gcc-x1e80100.o diff --git a/drivers/clk/qcom/gcc-shikra.c b/drivers/clk/qcom/gcc-shikra.c new file mode 100644 index 000000000000..d5222756f214 --- /dev/null +++ b/drivers/clk/qcom/gcc-shikra.c @@ -0,0 +1,4431 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include +#include + +#include + +#include "clk-alpha-pll.h" +#include "clk-branch.h" +#include "clk-pll.h" +#include "clk-rcg.h" +#include "clk-regmap.h" +#include "clk-regmap-divider.h" +#include "clk-regmap-mux.h" +#include "clk-regmap-phy-mux.h" +#include "common.h" +#include "gdsc.h" +#include "reset.h" + +enum { + DT_BI_TCXO, + DT_SLEEP_CLK, + DT_EMAC0_SGMIIPHY_RCLK, + DT_EMAC0_SGMIIPHY_TCLK, + DT_EMAC1_SGMIIPHY_RCLK, + DT_EMAC1_SGMIIPHY_TCLK, + DT_PCIE_PIPE_CLK, + DT_USB3_PHY_WRAPPER_GCC_USB30_PIPE_CLK, +}; + +enum { + P_BI_TCXO, + P_EMAC0_SGMIIPHY_RCLK, + P_EMAC0_SGMIIPHY_TCLK, + P_EMAC1_SGMIIPHY_RCLK, + P_EMAC1_SGMIIPHY_TCLK, + P_GPLL0_OUT_AUX2, + P_GPLL0_OUT_EARLY, + P_GPLL10_OUT_MAIN, + P_GPLL11_OUT_AUX, + P_GPLL11_OUT_AUX2, + P_GPLL11_OUT_MAIN, + P_GPLL12_OUT_AUX2, + P_GPLL12_OUT_EARLY, + P_GPLL3_OUT_EARLY, + P_GPLL3_OUT_MAIN, + P_GPLL4_OUT_MAIN, + P_GPLL5_OUT_MAIN, + P_GPLL6_OUT_EARLY, + P_GPLL6_OUT_MAIN, + P_GPLL7_OUT_MAIN, + P_GPLL8_OUT_EARLY, + P_GPLL8_OUT_MAIN, + P_GPLL9_OUT_EARLY, + P_GPLL9_OUT_MAIN, + P_PCIE_PIPE_CLK, + P_SLEEP_CLK, + P_USB3_PHY_WRAPPER_GCC_USB30_PIPE_CLK, +}; + +static const struct pll_vco brammo_vco[] = { + { 500000000, 1250000000, 0 }, +}; + +static const struct pll_vco default_vco[] = { + { 500000000, 1000000000, 2 }, +}; + +static const struct pll_vco spark_vco[] = { + { 750000000, 1500000000, 1 }, +}; + +static struct clk_alpha_pll gpll0 = { + .offset = 0x0, + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_DEFAULT_EVO], + .clkr = { + .enable_reg = 0x79000, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpll0", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_fixed_ops, + }, + }, +}; + +static const struct clk_div_table post_div_table_gpll0_out_aux2[] = { + { 0x1, 2 }, + { } +}; + +static struct clk_alpha_pll_postdiv gpll0_out_aux2 = { + .offset = 0x0, + .post_div_shift = 8, + .post_div_table = post_div_table_gpll0_out_aux2, + .num_post_div = ARRAY_SIZE(post_div_table_gpll0_out_aux2), + .width = 4, + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_DEFAULT_EVO], + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gpll0_out_aux2", + .parent_hws = (const struct clk_hw*[]) { + &gpll0.clkr.hw, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_postdiv_ro_ops, + }, +}; + +/* 1152.0 MHz Configuration */ +static const struct alpha_pll_config gpll10_config = { + .l = 0x3c, + .alpha = 0x0, + .vco_val = BIT(20), + .vco_mask = GENMASK(21, 20), + .main_output_mask = BIT(0), + .config_ctl_val = 0x4001055b, + .test_ctl_hi1_val = 0x1, +}; + +static struct clk_alpha_pll gpll10 = { + .offset = 0xa000, + .config = &gpll10_config, + .vco_table = spark_vco, + .num_vco = ARRAY_SIZE(spark_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_DEFAULT_EVO], + .clkr = { + .enable_reg = 0x79000, + .enable_mask = BIT(10), + .hw.init = &(const struct clk_init_data) { + .name = "gpll10", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_ops, + }, + }, +}; + +/* 600.0 MHz Configuration */ +static const struct alpha_pll_config gpll11_config = { + .l = 0x1f, + .alpha = 0x0, + .alpha_hi = 0x40, + .alpha_en_mask = BIT(24), + .vco_val = BIT(21), + .vco_mask = GENMASK(21, 20), + .main_output_mask = BIT(0), + .config_ctl_val = 0x4001055b, + .test_ctl_hi1_val = 0x1, +}; + +static struct clk_alpha_pll gpll11 = { + .offset = 0xb000, + .config = &gpll11_config, + .vco_table = default_vco, + .num_vco = ARRAY_SIZE(default_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_DEFAULT_EVO], + .flags = SUPPORTS_DYNAMIC_UPDATE, + .clkr = { + .enable_reg = 0x79000, + .enable_mask = BIT(11), + .hw.init = &(const struct clk_init_data) { + .name = "gpll11", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_ops, + }, + }, +}; + +static struct clk_alpha_pll gpll12 = { + .offset = 0xc000, + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_DEFAULT_EVO], + .clkr = { + .enable_reg = 0x79000, + .enable_mask = BIT(12), + .hw.init = &(const struct clk_init_data) { + .name = "gpll12", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_fixed_ops, + }, + }, +}; + +static const struct clk_div_table post_div_table_gpll12_out_aux2[] = { + { 0x1, 2 }, + { } +}; + +static struct clk_alpha_pll_postdiv gpll12_out_aux2 = { + .offset = 0xc000, + .post_div_shift = 8, + .post_div_table = post_div_table_gpll12_out_aux2, + .num_post_div = ARRAY_SIZE(post_div_table_gpll12_out_aux2), + .width = 4, + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_DEFAULT_EVO], + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gpll12_out_aux2", + .parent_hws = (const struct clk_hw*[]) { + &gpll12.clkr.hw, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_postdiv_ro_ops, + }, +}; + +static struct clk_alpha_pll gpll3 = { + .offset = 0x3000, + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_DEFAULT_EVO], + .clkr = { + .enable_reg = 0x79000, + .enable_mask = BIT(3), + .hw.init = &(const struct clk_init_data) { + .name = "gpll3", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_fixed_ops, + }, + }, +}; + +static const struct clk_div_table post_div_table_gpll3_out_main[] = { + { 0x1, 2 }, + { } +}; + +static struct clk_alpha_pll_postdiv gpll3_out_main = { + .offset = 0x3000, + .post_div_shift = 8, + .post_div_table = post_div_table_gpll3_out_main, + .num_post_div = ARRAY_SIZE(post_div_table_gpll3_out_main), + .width = 4, + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_DEFAULT_EVO], + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gpll3_out_main", + .parent_hws = (const struct clk_hw*[]) { + &gpll3.clkr.hw, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_postdiv_ro_ops, + }, +}; + +static struct clk_alpha_pll gpll4 = { + .offset = 0x4000, + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_DEFAULT_EVO], + .clkr = { + .enable_reg = 0x79000, + .enable_mask = BIT(4), + .hw.init = &(const struct clk_init_data) { + .name = "gpll4", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_fixed_ops, + }, + }, +}; + +static struct clk_alpha_pll gpll5 = { + .offset = 0x5000, + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_DEFAULT_EVO], + .clkr = { + .enable_reg = 0x79000, + .enable_mask = BIT(5), + .hw.init = &(const struct clk_init_data) { + .name = "gpll5", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_fixed_ops, + }, + }, +}; + +static struct clk_alpha_pll gpll6 = { + .offset = 0x6000, + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_DEFAULT_EVO], + .clkr = { + .enable_reg = 0x79000, + .enable_mask = BIT(6), + .hw.init = &(const struct clk_init_data) { + .name = "gpll6", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_fixed_ops, + }, + }, +}; + +static const struct clk_div_table post_div_table_gpll6_out_main[] = { + { 0x1, 2 }, + { } +}; + +static struct clk_alpha_pll_postdiv gpll6_out_main = { + .offset = 0x6000, + .post_div_shift = 8, + .post_div_table = post_div_table_gpll6_out_main, + .num_post_div = ARRAY_SIZE(post_div_table_gpll6_out_main), + .width = 4, + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_DEFAULT_EVO], + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gpll6_out_main", + .parent_hws = (const struct clk_hw*[]) { + &gpll6.clkr.hw, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_postdiv_ro_ops, + }, +}; + +static struct clk_alpha_pll gpll7 = { + .offset = 0x7000, + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_DEFAULT_EVO], + .clkr = { + .enable_reg = 0x79000, + .enable_mask = BIT(7), + .hw.init = &(const struct clk_init_data) { + .name = "gpll7", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_fixed_ops, + }, + }, +}; + +/* 533.2 MHz Configuration */ +static const struct alpha_pll_config gpll8_config = { + .l = 0x1b, + .alpha = 0x55555555, + .alpha_hi = 0xc5, + .alpha_en_mask = BIT(24), + .vco_val = BIT(21), + .vco_mask = GENMASK(21, 20), + .main_output_mask = BIT(0), + .early_output_mask = BIT(3), + .post_div_val = BIT(8), + .post_div_mask = GENMASK(11, 8), + .config_ctl_val = 0x4001055b, + .test_ctl_hi1_val = 0x1, +}; + +static struct clk_alpha_pll gpll8 = { + .offset = 0x8000, + .config = &gpll8_config, + .vco_table = default_vco, + .num_vco = ARRAY_SIZE(default_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_DEFAULT_EVO], + .flags = SUPPORTS_DYNAMIC_UPDATE, + .clkr = { + .enable_reg = 0x79000, + .enable_mask = BIT(8), + .hw.init = &(const struct clk_init_data) { + .name = "gpll8", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_ops, + }, + }, +}; + +static const struct clk_div_table post_div_table_gpll8_out_main[] = { + { 0x1, 2 }, + { } +}; + +static struct clk_alpha_pll_postdiv gpll8_out_main = { + .offset = 0x8000, + .post_div_shift = 8, + .post_div_table = post_div_table_gpll8_out_main, + .num_post_div = ARRAY_SIZE(post_div_table_gpll8_out_main), + .width = 4, + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_DEFAULT_EVO], + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gpll8_out_main", + .parent_hws = (const struct clk_hw*[]) { + &gpll8.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_alpha_pll_postdiv_ro_ops, + }, +}; + +/* 1152.0 MHz Configuration */ +static const struct alpha_pll_config gpll9_config = { + .l = 0x3c, + .alpha = 0x0, + .post_div_val = BIT(8), + .post_div_mask = GENMASK(9, 8), + .main_output_mask = BIT(0), + .early_output_mask = BIT(3), + .config_ctl_val = 0x00004289, + .test_ctl_val = 0x08000000, +}; + +static struct clk_alpha_pll gpll9 = { + .offset = 0x9000, + .config = &gpll9_config, + .vco_table = brammo_vco, + .num_vco = ARRAY_SIZE(brammo_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_BRAMMO_EVO], + .clkr = { + .enable_reg = 0x79000, + .enable_mask = BIT(9), + .hw.init = &(const struct clk_init_data) { + .name = "gpll9", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_ops, + }, + }, +}; + +static const struct clk_div_table post_div_table_gpll9_out_main[] = { + { 0x1, 2 }, + { } +}; + +static struct clk_alpha_pll_postdiv gpll9_out_main = { + .offset = 0x9000, + .post_div_shift = 8, + .post_div_table = post_div_table_gpll9_out_main, + .num_post_div = ARRAY_SIZE(post_div_table_gpll9_out_main), + .width = 2, + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_BRAMMO_EVO], + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gpll9_out_main", + .parent_hws = (const struct clk_hw*[]) { + &gpll9.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_alpha_pll_postdiv_ro_ops, + }, +}; + +static const struct parent_map gcc_parent_map_0[] = { + { P_BI_TCXO, 0 }, + { P_GPLL0_OUT_EARLY, 1 }, + { P_GPLL0_OUT_AUX2, 2 }, +}; + +static const struct clk_parent_data gcc_parent_data_0[] = { + { .index = DT_BI_TCXO }, + { .hw = &gpll0.clkr.hw }, + { .hw = &gpll0_out_aux2.clkr.hw }, +}; + +static const struct parent_map gcc_parent_map_1[] = { + { P_BI_TCXO, 0 }, + { P_GPLL0_OUT_EARLY, 1 }, + { P_GPLL0_OUT_AUX2, 2 }, + { P_GPLL6_OUT_MAIN, 4 }, +}; + +static const struct clk_parent_data gcc_parent_data_1[] = { + { .index = DT_BI_TCXO }, + { .hw = &gpll0.clkr.hw }, + { .hw = &gpll0_out_aux2.clkr.hw }, + { .hw = &gpll6_out_main.clkr.hw }, +}; + +static const struct parent_map gcc_parent_map_2[] = { + { P_BI_TCXO, 0 }, + { P_GPLL0_OUT_EARLY, 1 }, + { P_GPLL0_OUT_AUX2, 2 }, + { P_SLEEP_CLK, 5 }, +}; + +static const struct clk_parent_data gcc_parent_data_2[] = { + { .index = DT_BI_TCXO }, + { .hw = &gpll0.clkr.hw }, + { .hw = &gpll0_out_aux2.clkr.hw }, + { .index = DT_SLEEP_CLK }, +}; + +static const struct parent_map gcc_parent_map_3[] = { + { P_BI_TCXO, 0 }, + { P_GPLL0_OUT_EARLY, 1 }, + { P_GPLL9_OUT_EARLY, 2 }, + { P_GPLL10_OUT_MAIN, 3 }, + { P_GPLL9_OUT_MAIN, 5 }, + { P_GPLL3_OUT_MAIN, 6 }, +}; + +static const struct clk_parent_data gcc_parent_data_3[] = { + { .index = DT_BI_TCXO }, + { .hw = &gpll0.clkr.hw }, + { .hw = &gpll9.clkr.hw }, + { .hw = &gpll10.clkr.hw }, + { .hw = &gpll9_out_main.clkr.hw }, + { .hw = &gpll3_out_main.clkr.hw }, +}; + +static const struct parent_map gcc_parent_map_4[] = { + { P_BI_TCXO, 0 }, + { P_SLEEP_CLK, 5 }, +}; + +static const struct clk_parent_data gcc_parent_data_4[] = { + { .index = DT_BI_TCXO }, + { .index = DT_SLEEP_CLK }, +}; + +static const struct parent_map gcc_parent_map_5[] = { + { P_BI_TCXO, 0 }, + { P_GPLL0_OUT_EARLY, 1 }, + { P_GPLL0_OUT_AUX2, 2 }, + { P_GPLL10_OUT_MAIN, 3 }, + { P_GPLL4_OUT_MAIN, 5 }, + { P_GPLL3_OUT_EARLY, 6 }, +}; + +static const struct clk_parent_data gcc_parent_data_5[] = { + { .index = DT_BI_TCXO }, + { .hw = &gpll0.clkr.hw }, + { .hw = &gpll0_out_aux2.clkr.hw }, + { .hw = &gpll10.clkr.hw }, + { .hw = &gpll4.clkr.hw }, + { .hw = &gpll3.clkr.hw }, +}; + +static const struct parent_map gcc_parent_map_6[] = { + { P_BI_TCXO, 0 }, + { P_GPLL0_OUT_EARLY, 1 }, + { P_GPLL0_OUT_AUX2, 2 }, + { P_GPLL4_OUT_MAIN, 5 }, + { P_GPLL3_OUT_MAIN, 6 }, +}; + +static const struct clk_parent_data gcc_parent_data_6[] = { + { .index = DT_BI_TCXO }, + { .hw = &gpll0.clkr.hw }, + { .hw = &gpll0_out_aux2.clkr.hw }, + { .hw = &gpll4.clkr.hw }, + { .hw = &gpll3_out_main.clkr.hw }, +}; + +static const struct parent_map gcc_parent_map_7[] = { + { P_BI_TCXO, 0 }, + { P_GPLL0_OUT_EARLY, 1 }, + { P_GPLL8_OUT_EARLY, 2 }, + { P_GPLL10_OUT_MAIN, 3 }, + { P_GPLL8_OUT_MAIN, 4 }, + { P_GPLL9_OUT_MAIN, 5 }, + { P_GPLL3_OUT_EARLY, 6 }, +}; + +static const struct clk_parent_data gcc_parent_data_7[] = { + { .index = DT_BI_TCXO }, + { .hw = &gpll0.clkr.hw }, + { .hw = &gpll8.clkr.hw }, + { .hw = &gpll10.clkr.hw }, + { .hw = &gpll8_out_main.clkr.hw }, + { .hw = &gpll9.clkr.hw }, + { .hw = &gpll3.clkr.hw }, +}; + +static const struct parent_map gcc_parent_map_8[] = { + { P_BI_TCXO, 0 }, + { P_GPLL0_OUT_EARLY, 1 }, + { P_GPLL8_OUT_EARLY, 2 }, + { P_GPLL10_OUT_MAIN, 3 }, + { P_GPLL8_OUT_MAIN, 4 }, + { P_GPLL9_OUT_MAIN, 5 }, + { P_GPLL3_OUT_MAIN, 6 }, +}; + +static const struct clk_parent_data gcc_parent_data_8[] = { + { .index = DT_BI_TCXO }, + { .hw = &gpll0.clkr.hw }, + { .hw = &gpll8.clkr.hw }, + { .hw = &gpll10.clkr.hw }, + { .hw = &gpll8_out_main.clkr.hw }, + { .hw = &gpll9.clkr.hw }, + { .hw = &gpll3_out_main.clkr.hw }, +}; + +static const struct parent_map gcc_parent_map_9[] = { + { P_BI_TCXO, 0 }, + { P_GPLL0_OUT_EARLY, 1 }, + { P_GPLL8_OUT_EARLY, 2 }, + { P_GPLL10_OUT_MAIN, 3 }, + { P_GPLL6_OUT_MAIN, 4 }, + { P_GPLL9_OUT_MAIN, 5 }, + { P_GPLL3_OUT_EARLY, 6 }, +}; + +static const struct clk_parent_data gcc_parent_data_9[] = { + { .index = DT_BI_TCXO }, + { .hw = &gpll0.clkr.hw }, + { .hw = &gpll8.clkr.hw }, + { .hw = &gpll10.clkr.hw }, + { .hw = &gpll6_out_main.clkr.hw }, + { .hw = &gpll9.clkr.hw }, + { .hw = &gpll3.clkr.hw }, +}; + +static const struct parent_map gcc_parent_map_10[] = { + { P_BI_TCXO, 0 }, + { P_GPLL0_OUT_EARLY, 1 }, + { P_SLEEP_CLK, 5 }, +}; + +static const struct clk_parent_data gcc_parent_data_10[] = { + { .index = DT_BI_TCXO }, + { .hw = &gpll0.clkr.hw }, + { .index = DT_SLEEP_CLK }, +}; + +static const struct parent_map gcc_parent_map_11[] = { + { P_BI_TCXO, 0 }, + { P_GPLL12_OUT_EARLY, 1 }, + { P_GPLL12_OUT_AUX2, 4 }, + { P_GPLL3_OUT_EARLY, 6 }, +}; + +static const struct clk_parent_data gcc_parent_data_11[] = { + { .index = DT_BI_TCXO }, + { .hw = &gpll12.clkr.hw }, + { .hw = &gpll12_out_aux2.clkr.hw }, + { .hw = &gpll3.clkr.hw }, +}; + +static const struct parent_map gcc_parent_map_12[] = { + { P_BI_TCXO, 0 }, + { P_GPLL12_OUT_EARLY, 1 }, + { P_GPLL0_OUT_AUX2, 2 }, + { P_GPLL12_OUT_AUX2, 4 }, +}; + +static const struct clk_parent_data gcc_parent_data_12[] = { + { .index = DT_BI_TCXO }, + { .hw = &gpll12.clkr.hw }, + { .hw = &gpll0_out_aux2.clkr.hw }, + { .hw = &gpll12_out_aux2.clkr.hw }, +}; + +static const struct parent_map gcc_parent_map_13[] = { + { P_BI_TCXO, 0 }, + { P_GPLL0_OUT_EARLY, 1 }, +}; + +static const struct clk_parent_data gcc_parent_data_13[] = { + { .index = DT_BI_TCXO }, + { .hw = &gpll0.clkr.hw }, +}; + +static const struct parent_map gcc_parent_map_14[] = { + { P_BI_TCXO, 0 }, + { P_GPLL0_OUT_EARLY, 1 }, + { P_GPLL0_OUT_AUX2, 2 }, + { P_GPLL10_OUT_MAIN, 3 }, + { P_GPLL8_OUT_MAIN, 4 }, + { P_GPLL9_OUT_MAIN, 5 }, + { P_GPLL3_OUT_EARLY, 6 }, +}; + +static const struct clk_parent_data gcc_parent_data_14[] = { + { .index = DT_BI_TCXO }, + { .hw = &gpll0.clkr.hw }, + { .hw = &gpll0_out_aux2.clkr.hw }, + { .hw = &gpll10.clkr.hw }, + { .hw = &gpll8_out_main.clkr.hw }, + { .hw = &gpll9.clkr.hw }, + { .hw = &gpll3.clkr.hw }, +}; + +static const struct parent_map gcc_parent_map_15[] = { + { P_BI_TCXO, 0 }, + { P_GPLL0_OUT_EARLY, 1 }, + { P_GPLL8_OUT_EARLY, 2 }, + { P_GPLL10_OUT_MAIN, 3 }, + { P_GPLL6_OUT_EARLY, 5 }, + { P_GPLL3_OUT_MAIN, 6 }, +}; + +static const struct clk_parent_data gcc_parent_data_15[] = { + { .index = DT_BI_TCXO }, + { .hw = &gpll0.clkr.hw }, + { .hw = &gpll8.clkr.hw }, + { .hw = &gpll10.clkr.hw }, + { .hw = &gpll6.clkr.hw }, + { .hw = &gpll3_out_main.clkr.hw }, +}; + +static const struct parent_map gcc_parent_map_21[] = { + { P_BI_TCXO, 0 }, + { P_GPLL0_OUT_EARLY, 1 }, + { P_GPLL0_OUT_AUX2, 2 }, + { P_GPLL7_OUT_MAIN, 3 }, + { P_GPLL4_OUT_MAIN, 5 }, +}; + +static const struct clk_parent_data gcc_parent_data_21[] = { + { .index = DT_BI_TCXO }, + { .hw = &gpll0.clkr.hw }, + { .hw = &gpll0_out_aux2.clkr.hw }, + { .hw = &gpll7.clkr.hw }, + { .hw = &gpll4.clkr.hw }, +}; + +static const struct parent_map gcc_parent_map_22[] = { + { P_BI_TCXO, 0 }, + { P_GPLL12_OUT_EARLY, 1 }, + { P_GPLL5_OUT_MAIN, 3 }, + { P_GPLL12_OUT_AUX2, 4 }, + { P_GPLL3_OUT_EARLY, 6 }, +}; + +static const struct clk_parent_data gcc_parent_data_22[] = { + { .index = DT_BI_TCXO }, + { .hw = &gpll12.clkr.hw }, + { .hw = &gpll5.clkr.hw }, + { .hw = &gpll12_out_aux2.clkr.hw }, + { .hw = &gpll3.clkr.hw }, +}; + +static const struct parent_map gcc_parent_map_24[] = { + { P_BI_TCXO, 0 }, + { P_GPLL11_OUT_MAIN, 1 }, + { P_GPLL11_OUT_AUX, 2 }, + { P_GPLL11_OUT_AUX2, 3 }, +}; + +static const struct clk_parent_data gcc_parent_data_24[] = { + { .index = DT_BI_TCXO }, + { .hw = &gpll11.clkr.hw }, + { .hw = &gpll11.clkr.hw }, + { .hw = &gpll11.clkr.hw }, +}; + +static struct clk_regmap_phy_mux gcc_emac0_cc_sgmiiphy_rx_clk_src = { + .reg = 0xad048, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "gcc_emac0_cc_sgmiiphy_rx_clk_src", + .parent_data = &(const struct clk_parent_data) { + .index = DT_EMAC0_SGMIIPHY_RCLK, + }, + .num_parents = 1, + .ops = &clk_regmap_phy_mux_ops, + }, + }, +}; + +static struct clk_regmap_phy_mux gcc_emac0_cc_sgmiiphy_tx_clk_src = { + .reg = 0xad040, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "gcc_emac0_cc_sgmiiphy_tx_clk_src", + .parent_data = &(const struct clk_parent_data) { + .index = DT_EMAC0_SGMIIPHY_TCLK, + }, + .num_parents = 1, + .ops = &clk_regmap_phy_mux_ops, + }, + }, +}; + +static struct clk_regmap_phy_mux gcc_emac1_cc_sgmiiphy_rx_clk_src = { + .reg = 0xae048, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "gcc_emac1_cc_sgmiiphy_rx_clk_src", + .parent_data = &(const struct clk_parent_data) { + .index = DT_EMAC1_SGMIIPHY_RCLK, + }, + .num_parents = 1, + .ops = &clk_regmap_phy_mux_ops, + }, + }, +}; + +static struct clk_regmap_phy_mux gcc_emac1_cc_sgmiiphy_tx_clk_src = { + .reg = 0xae040, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "gcc_emac1_cc_sgmiiphy_tx_clk_src", + .parent_data = &(const struct clk_parent_data) { + .index = DT_EMAC1_SGMIIPHY_TCLK, + }, + .num_parents = 1, + .ops = &clk_regmap_phy_mux_ops, + }, + }, +}; + +static struct clk_regmap_phy_mux gcc_pcie_pipe_clk_src = { + .reg = 0xaf058, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "gcc_pcie_pipe_clk_src", + .parent_data = &(const struct clk_parent_data) { + .index = DT_PCIE_PIPE_CLK, + }, + .num_parents = 1, + .ops = &clk_regmap_phy_mux_ops, + }, + }, +}; + +static struct clk_regmap_phy_mux gcc_usb3_prim_phy_pipe_clk_src = { + .reg = 0x1a05c, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "gcc_usb3_prim_phy_pipe_clk_src", + .parent_data = &(const struct clk_parent_data) { + .index = DT_USB3_PHY_WRAPPER_GCC_USB30_PIPE_CLK, + }, + .num_parents = 1, + .ops = &clk_regmap_phy_mux_ops, + }, + }, +}; + +static const struct freq_tbl ftbl_gcc_camss_axi_clk_src[] = { + F(19200000, P_BI_TCXO, 1, 0, 0), + F(150000000, P_GPLL0_OUT_AUX2, 2, 0, 0), + F(200000000, P_GPLL0_OUT_AUX2, 1.5, 0, 0), + F(300000000, P_GPLL0_OUT_AUX2, 1, 0, 0), + { } +}; + +static struct clk_rcg2 gcc_camss_axi_clk_src = { + .cmd_rcgr = 0x5802c, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_5, + .freq_tbl = ftbl_gcc_camss_axi_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_axi_clk_src", + .parent_data = gcc_parent_data_5, + .num_parents = ARRAY_SIZE(gcc_parent_data_5), + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_gcc_camss_cci_clk_src[] = { + F(19200000, P_BI_TCXO, 1, 0, 0), + F(37500000, P_GPLL0_OUT_AUX2, 8, 0, 0), + { } +}; + +static struct clk_rcg2 gcc_camss_cci_clk_src = { + .cmd_rcgr = 0x56000, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_14, + .freq_tbl = ftbl_gcc_camss_cci_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_cci_clk_src", + .parent_data = gcc_parent_data_14, + .num_parents = ARRAY_SIZE(gcc_parent_data_14), + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_gcc_camss_csi0phytimer_clk_src[] = { + F(19200000, P_BI_TCXO, 1, 0, 0), + F(100000000, P_GPLL0_OUT_AUX2, 3, 0, 0), + F(200000000, P_GPLL0_OUT_AUX2, 1.5, 0, 0), + F(268800000, P_GPLL4_OUT_MAIN, 3, 0, 0), + { } +}; + +static struct clk_rcg2 gcc_camss_csi0phytimer_clk_src = { + .cmd_rcgr = 0x45000, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_6, + .freq_tbl = ftbl_gcc_camss_csi0phytimer_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_csi0phytimer_clk_src", + .parent_data = gcc_parent_data_6, + .num_parents = ARRAY_SIZE(gcc_parent_data_6), + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 gcc_camss_csi1phytimer_clk_src = { + .cmd_rcgr = 0x4501c, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_6, + .freq_tbl = ftbl_gcc_camss_csi0phytimer_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_csi1phytimer_clk_src", + .parent_data = gcc_parent_data_6, + .num_parents = ARRAY_SIZE(gcc_parent_data_6), + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_gcc_camss_mclk0_clk_src[] = { + F(19200000, P_GPLL9_OUT_EARLY, 1, 1, 60), + F(24000000, P_GPLL9_OUT_MAIN, 1, 1, 24), + F(64000000, P_GPLL9_OUT_EARLY, 9, 1, 2), + { } +}; + +static struct clk_rcg2 gcc_camss_mclk0_clk_src = { + .cmd_rcgr = 0x51000, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_3, + .freq_tbl = ftbl_gcc_camss_mclk0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_mclk0_clk_src", + .parent_data = gcc_parent_data_3, + .num_parents = ARRAY_SIZE(gcc_parent_data_3), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 gcc_camss_mclk1_clk_src = { + .cmd_rcgr = 0x5101c, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_3, + .freq_tbl = ftbl_gcc_camss_mclk0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_mclk1_clk_src", + .parent_data = gcc_parent_data_3, + .num_parents = ARRAY_SIZE(gcc_parent_data_3), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 gcc_camss_mclk2_clk_src = { + .cmd_rcgr = 0x51038, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_3, + .freq_tbl = ftbl_gcc_camss_mclk0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_mclk2_clk_src", + .parent_data = gcc_parent_data_3, + .num_parents = ARRAY_SIZE(gcc_parent_data_3), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 gcc_camss_mclk3_clk_src = { + .cmd_rcgr = 0x51054, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_3, + .freq_tbl = ftbl_gcc_camss_mclk0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_mclk3_clk_src", + .parent_data = gcc_parent_data_3, + .num_parents = ARRAY_SIZE(gcc_parent_data_3), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_gcc_camss_ope_ahb_clk_src[] = { + F(19200000, P_BI_TCXO, 1, 0, 0), + F(171428571, P_GPLL0_OUT_EARLY, 3.5, 0, 0), + F(240000000, P_GPLL0_OUT_EARLY, 2.5, 0, 0), + { } +}; + +static struct clk_rcg2 gcc_camss_ope_ahb_clk_src = { + .cmd_rcgr = 0x55024, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_7, + .freq_tbl = ftbl_gcc_camss_ope_ahb_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_ope_ahb_clk_src", + .parent_data = gcc_parent_data_7, + .num_parents = ARRAY_SIZE(gcc_parent_data_7), + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_gcc_camss_ope_clk_src[] = { + F(19200000, P_BI_TCXO, 1, 0, 0), + F(200000000, P_GPLL8_OUT_MAIN, 2, 0, 0), + F(266600000, P_GPLL8_OUT_MAIN, 1, 0, 0), + F(465000000, P_GPLL8_OUT_MAIN, 1, 0, 0), + F(580000000, P_GPLL8_OUT_EARLY, 1, 0, 0), + { } +}; + +static struct clk_rcg2 gcc_camss_ope_clk_src = { + .cmd_rcgr = 0x55004, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_7, + .freq_tbl = ftbl_gcc_camss_ope_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_ope_clk_src", + .parent_data = gcc_parent_data_7, + .num_parents = ARRAY_SIZE(gcc_parent_data_7), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_gcc_camss_tfe_0_clk_src[] = { + F(19200000, P_BI_TCXO, 1, 0, 0), + F(128000000, P_GPLL10_OUT_MAIN, 9, 0, 0), + F(135529412, P_GPLL10_OUT_MAIN, 8.5, 0, 0), + F(144000000, P_GPLL10_OUT_MAIN, 8, 0, 0), + F(153600000, P_GPLL10_OUT_MAIN, 7.5, 0, 0), + F(164571429, P_GPLL10_OUT_MAIN, 7, 0, 0), + F(177230769, P_GPLL10_OUT_MAIN, 6.5, 0, 0), + F(192000000, P_GPLL10_OUT_MAIN, 6, 0, 0), + F(209454545, P_GPLL10_OUT_MAIN, 5.5, 0, 0), + F(230400000, P_GPLL10_OUT_MAIN, 5, 0, 0), + F(256000000, P_GPLL10_OUT_MAIN, 4.5, 0, 0), + F(288000000, P_GPLL10_OUT_MAIN, 4, 0, 0), + F(329142857, P_GPLL10_OUT_MAIN, 3.5, 0, 0), + F(384000000, P_GPLL10_OUT_MAIN, 3, 0, 0), + F(460800000, P_GPLL10_OUT_MAIN, 2.5, 0, 0), + F(576000000, P_GPLL10_OUT_MAIN, 2, 0, 0), + { } +}; + +static struct clk_rcg2 gcc_camss_tfe_0_clk_src = { + .cmd_rcgr = 0x52004, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_8, + .freq_tbl = ftbl_gcc_camss_tfe_0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_tfe_0_clk_src", + .parent_data = gcc_parent_data_8, + .num_parents = ARRAY_SIZE(gcc_parent_data_8), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_gcc_camss_tfe_0_csid_clk_src[] = { + F(19200000, P_BI_TCXO, 1, 0, 0), + F(120000000, P_GPLL0_OUT_EARLY, 5, 0, 0), + F(192000000, P_GPLL6_OUT_MAIN, 2, 0, 0), + F(240000000, P_GPLL0_OUT_EARLY, 2.5, 0, 0), + F(384000000, P_GPLL6_OUT_MAIN, 1, 0, 0), + F(426400000, P_GPLL3_OUT_EARLY, 2.5, 0, 0), + { } +}; + +static struct clk_rcg2 gcc_camss_tfe_0_csid_clk_src = { + .cmd_rcgr = 0x52094, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_9, + .freq_tbl = ftbl_gcc_camss_tfe_0_csid_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_tfe_0_csid_clk_src", + .parent_data = gcc_parent_data_9, + .num_parents = ARRAY_SIZE(gcc_parent_data_9), + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 gcc_camss_tfe_1_clk_src = { + .cmd_rcgr = 0x52024, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_8, + .freq_tbl = ftbl_gcc_camss_tfe_0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_tfe_1_clk_src", + .parent_data = gcc_parent_data_8, + .num_parents = ARRAY_SIZE(gcc_parent_data_8), + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 gcc_camss_tfe_1_csid_clk_src = { + .cmd_rcgr = 0x520b4, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_9, + .freq_tbl = ftbl_gcc_camss_tfe_0_csid_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_tfe_1_csid_clk_src", + .parent_data = gcc_parent_data_9, + .num_parents = ARRAY_SIZE(gcc_parent_data_9), + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_gcc_camss_tfe_cphy_rx_clk_src[] = { + F(19200000, P_BI_TCXO, 1, 0, 0), + F(240000000, P_GPLL0_OUT_EARLY, 2.5, 0, 0), + F(341333333, P_GPLL6_OUT_EARLY, 1, 4, 9), + F(384000000, P_GPLL6_OUT_EARLY, 2, 0, 0), + { } +}; + +static struct clk_rcg2 gcc_camss_tfe_cphy_rx_clk_src = { + .cmd_rcgr = 0x52064, + .mnd_width = 16, + .hid_width = 5, + .parent_map = gcc_parent_map_15, + .freq_tbl = ftbl_gcc_camss_tfe_cphy_rx_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_tfe_cphy_rx_clk_src", + .parent_data = gcc_parent_data_15, + .num_parents = ARRAY_SIZE(gcc_parent_data_15), + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_gcc_camss_top_ahb_clk_src[] = { + F(19200000, P_BI_TCXO, 1, 0, 0), + F(40000000, P_GPLL0_OUT_AUX2, 7.5, 0, 0), + F(80000000, P_GPLL0_OUT_EARLY, 7.5, 0, 0), + { } +}; + +static struct clk_rcg2 gcc_camss_top_ahb_clk_src = { + .cmd_rcgr = 0x58010, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_5, + .freq_tbl = ftbl_gcc_camss_top_ahb_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_top_ahb_clk_src", + .parent_data = gcc_parent_data_5, + .num_parents = ARRAY_SIZE(gcc_parent_data_5), + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_gcc_emac0_axi_clk_src[] = { + F(75000000, P_GPLL0_OUT_AUX2, 4, 0, 0), + F(120000000, P_GPLL0_OUT_AUX2, 2.5, 0, 0), + F(150000000, P_GPLL0_OUT_AUX2, 2, 0, 0), + F(200000000, P_GPLL0_OUT_AUX2, 1.5, 0, 0), + F(240000000, P_GPLL0_OUT_EARLY, 2.5, 0, 0), + { } +}; + +static struct clk_rcg2 gcc_emac0_axi_clk_src = { + .cmd_rcgr = 0x109dc, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_0, + .freq_tbl = ftbl_gcc_emac0_axi_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_emac0_axi_clk_src", + .parent_data = gcc_parent_data_0, + .num_parents = ARRAY_SIZE(gcc_parent_data_0), + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_gcc_emac0_phy_aux_clk_src[] = { + F(19200000, P_BI_TCXO, 1, 0, 0), + { } +}; + +static struct clk_rcg2 gcc_emac0_phy_aux_clk_src = { + .cmd_rcgr = 0xad01c, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_10, + .freq_tbl = ftbl_gcc_emac0_phy_aux_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_emac0_phy_aux_clk_src", + .parent_data = gcc_parent_data_10, + .num_parents = ARRAY_SIZE(gcc_parent_data_10), + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_gcc_emac0_ptp_clk_src[] = { + F(250000000, P_GPLL12_OUT_AUX2, 2, 0, 0), + { } +}; + +static struct clk_rcg2 gcc_emac0_ptp_clk_src = { + .cmd_rcgr = 0xad064, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_11, + .freq_tbl = ftbl_gcc_emac0_ptp_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_emac0_ptp_clk_src", + .parent_data = gcc_parent_data_11, + .num_parents = ARRAY_SIZE(gcc_parent_data_11), + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_gcc_emac0_rgmii_clk_src[] = { + F(50000000, P_GPLL0_OUT_AUX2, 6, 0, 0), + F(125000000, P_GPLL12_OUT_AUX2, 4, 0, 0), + F(250000000, P_GPLL12_OUT_EARLY, 4, 0, 0), + { } +}; + +static struct clk_rcg2 gcc_emac0_rgmii_clk_src = { + .cmd_rcgr = 0xad04c, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_12, + .freq_tbl = ftbl_gcc_emac0_rgmii_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_emac0_rgmii_clk_src", + .parent_data = gcc_parent_data_12, + .num_parents = ARRAY_SIZE(gcc_parent_data_12), + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 gcc_emac1_axi_clk_src = { + .cmd_rcgr = 0x109fc, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_0, + .freq_tbl = ftbl_gcc_emac0_axi_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_emac1_axi_clk_src", + .parent_data = gcc_parent_data_0, + .num_parents = ARRAY_SIZE(gcc_parent_data_0), + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 gcc_emac1_phy_aux_clk_src = { + .cmd_rcgr = 0xae01c, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_10, + .freq_tbl = ftbl_gcc_emac0_phy_aux_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_emac1_phy_aux_clk_src", + .parent_data = gcc_parent_data_10, + .num_parents = ARRAY_SIZE(gcc_parent_data_10), + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 gcc_emac1_ptp_clk_src = { + .cmd_rcgr = 0xae064, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_11, + .freq_tbl = ftbl_gcc_emac0_ptp_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_emac1_ptp_clk_src", + .parent_data = gcc_parent_data_11, + .num_parents = ARRAY_SIZE(gcc_parent_data_11), + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 gcc_emac1_rgmii_clk_src = { + .cmd_rcgr = 0xae04c, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_12, + .freq_tbl = ftbl_gcc_emac0_rgmii_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_emac1_rgmii_clk_src", + .parent_data = gcc_parent_data_12, + .num_parents = ARRAY_SIZE(gcc_parent_data_12), + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_gcc_gp1_clk_src[] = { + F(25000000, P_GPLL0_OUT_AUX2, 12, 0, 0), + F(50000000, P_GPLL0_OUT_AUX2, 6, 0, 0), + F(100000000, P_GPLL0_OUT_AUX2, 3, 0, 0), + F(200000000, P_GPLL0_OUT_AUX2, 1.5, 0, 0), + { } +}; + +static struct clk_rcg2 gcc_gp1_clk_src = { + .cmd_rcgr = 0x4d004, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_2, + .freq_tbl = ftbl_gcc_gp1_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_gp1_clk_src", + .parent_data = gcc_parent_data_2, + .num_parents = ARRAY_SIZE(gcc_parent_data_2), + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 gcc_gp2_clk_src = { + .cmd_rcgr = 0x4e004, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_2, + .freq_tbl = ftbl_gcc_gp1_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_gp2_clk_src", + .parent_data = gcc_parent_data_2, + .num_parents = ARRAY_SIZE(gcc_parent_data_2), + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 gcc_gp3_clk_src = { + .cmd_rcgr = 0x4f004, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_2, + .freq_tbl = ftbl_gcc_gp1_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_gp3_clk_src", + .parent_data = gcc_parent_data_2, + .num_parents = ARRAY_SIZE(gcc_parent_data_2), + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 gcc_pcie_aux_clk_src = { + .cmd_rcgr = 0xaf074, + .mnd_width = 16, + .hid_width = 5, + .parent_map = gcc_parent_map_4, + .freq_tbl = ftbl_gcc_emac0_phy_aux_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_pcie_aux_clk_src", + .parent_data = gcc_parent_data_4, + .num_parents = ARRAY_SIZE(gcc_parent_data_4), + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 gcc_pcie_aux_phy_clk_src = { + .cmd_rcgr = 0xaf05c, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_4, + .freq_tbl = ftbl_gcc_emac0_phy_aux_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_pcie_aux_phy_clk_src", + .parent_data = gcc_parent_data_4, + .num_parents = ARRAY_SIZE(gcc_parent_data_4), + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_gcc_pcie_rchng_phy_clk_src[] = { + F(100000000, P_GPLL0_OUT_AUX2, 3, 0, 0), + { } +}; + +static struct clk_rcg2 gcc_pcie_rchng_phy_clk_src = { + .cmd_rcgr = 0xaf028, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_0, + .freq_tbl = ftbl_gcc_pcie_rchng_phy_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_pcie_rchng_phy_clk_src", + .parent_data = gcc_parent_data_0, + .num_parents = ARRAY_SIZE(gcc_parent_data_0), + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_gcc_pdm2_clk_src[] = { + F(19200000, P_BI_TCXO, 1, 0, 0), + F(60000000, P_GPLL0_OUT_AUX2, 5, 0, 0), + { } +}; + +static struct clk_rcg2 gcc_pdm2_clk_src = { + .cmd_rcgr = 0x20010, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_0, + .freq_tbl = ftbl_gcc_pdm2_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_pdm2_clk_src", + .parent_data = gcc_parent_data_0, + .num_parents = ARRAY_SIZE(gcc_parent_data_0), + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_gcc_qupv3_wrap0_s0_clk_src[] = { + F(7372800, P_GPLL0_OUT_AUX2, 1, 384, 15625), + F(14745600, P_GPLL0_OUT_AUX2, 1, 768, 15625), + F(19200000, P_BI_TCXO, 1, 0, 0), + F(29491200, P_GPLL0_OUT_AUX2, 1, 1536, 15625), + F(32000000, P_GPLL0_OUT_AUX2, 1, 8, 75), + F(48000000, P_GPLL0_OUT_AUX2, 1, 4, 25), + F(64000000, P_GPLL0_OUT_AUX2, 1, 16, 75), + F(75000000, P_GPLL0_OUT_AUX2, 4, 0, 0), + F(80000000, P_GPLL0_OUT_AUX2, 1, 4, 15), + F(96000000, P_GPLL0_OUT_AUX2, 1, 8, 25), + F(100000000, P_GPLL0_OUT_AUX2, 3, 0, 0), + F(102400000, P_GPLL0_OUT_AUX2, 1, 128, 375), + F(112000000, P_GPLL0_OUT_AUX2, 1, 28, 75), + F(117964800, P_GPLL0_OUT_AUX2, 1, 6144, 15625), + F(120000000, P_GPLL0_OUT_AUX2, 2.5, 0, 0), + F(128000000, P_GPLL6_OUT_MAIN, 3, 0, 0), + { } +}; + +static struct clk_init_data gcc_qupv3_wrap0_s0_clk_src_init = { + .name = "gcc_qupv3_wrap0_s0_clk_src", + .parent_data = gcc_parent_data_1, + .num_parents = ARRAY_SIZE(gcc_parent_data_1), + .ops = &clk_rcg2_shared_ops, +}; + +static struct clk_rcg2 gcc_qupv3_wrap0_s0_clk_src = { + .cmd_rcgr = 0x1f148, + .mnd_width = 16, + .hid_width = 5, + .parent_map = gcc_parent_map_1, + .freq_tbl = ftbl_gcc_qupv3_wrap0_s0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &gcc_qupv3_wrap0_s0_clk_src_init, +}; + +static struct clk_init_data gcc_qupv3_wrap0_s1_clk_src_init = { + .name = "gcc_qupv3_wrap0_s1_clk_src", + .parent_data = gcc_parent_data_1, + .num_parents = ARRAY_SIZE(gcc_parent_data_1), + .ops = &clk_rcg2_shared_ops, +}; + +static struct clk_rcg2 gcc_qupv3_wrap0_s1_clk_src = { + .cmd_rcgr = 0x1f278, + .mnd_width = 16, + .hid_width = 5, + .parent_map = gcc_parent_map_1, + .freq_tbl = ftbl_gcc_qupv3_wrap0_s0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &gcc_qupv3_wrap0_s1_clk_src_init, +}; + +static struct clk_init_data gcc_qupv3_wrap0_s2_clk_src_init = { + .name = "gcc_qupv3_wrap0_s2_clk_src", + .parent_data = gcc_parent_data_1, + .num_parents = ARRAY_SIZE(gcc_parent_data_1), + .ops = &clk_rcg2_shared_ops, +}; + +static struct clk_rcg2 gcc_qupv3_wrap0_s2_clk_src = { + .cmd_rcgr = 0x1f3a8, + .mnd_width = 16, + .hid_width = 5, + .parent_map = gcc_parent_map_1, + .freq_tbl = ftbl_gcc_qupv3_wrap0_s0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &gcc_qupv3_wrap0_s2_clk_src_init, +}; + +static struct clk_init_data gcc_qupv3_wrap0_s3_clk_src_init = { + .name = "gcc_qupv3_wrap0_s3_clk_src", + .parent_data = gcc_parent_data_1, + .num_parents = ARRAY_SIZE(gcc_parent_data_1), + .ops = &clk_rcg2_shared_ops, +}; + +static struct clk_rcg2 gcc_qupv3_wrap0_s3_clk_src = { + .cmd_rcgr = 0x1f4d8, + .mnd_width = 16, + .hid_width = 5, + .parent_map = gcc_parent_map_1, + .freq_tbl = ftbl_gcc_qupv3_wrap0_s0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &gcc_qupv3_wrap0_s3_clk_src_init, +}; + +static struct clk_init_data gcc_qupv3_wrap0_s4_clk_src_init = { + .name = "gcc_qupv3_wrap0_s4_clk_src", + .parent_data = gcc_parent_data_1, + .num_parents = ARRAY_SIZE(gcc_parent_data_1), + .ops = &clk_rcg2_shared_ops, +}; + +static struct clk_rcg2 gcc_qupv3_wrap0_s4_clk_src = { + .cmd_rcgr = 0x1f608, + .mnd_width = 16, + .hid_width = 5, + .parent_map = gcc_parent_map_1, + .freq_tbl = ftbl_gcc_qupv3_wrap0_s0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &gcc_qupv3_wrap0_s4_clk_src_init, +}; + +static struct clk_init_data gcc_qupv3_wrap0_s5_clk_src_init = { + .name = "gcc_qupv3_wrap0_s5_clk_src", + .parent_data = gcc_parent_data_1, + .num_parents = ARRAY_SIZE(gcc_parent_data_1), + .ops = &clk_rcg2_shared_ops, +}; + +static struct clk_rcg2 gcc_qupv3_wrap0_s5_clk_src = { + .cmd_rcgr = 0x1f738, + .mnd_width = 16, + .hid_width = 5, + .parent_map = gcc_parent_map_1, + .freq_tbl = ftbl_gcc_qupv3_wrap0_s0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &gcc_qupv3_wrap0_s5_clk_src_init, +}; + +static struct clk_init_data gcc_qupv3_wrap0_s6_clk_src_init = { + .name = "gcc_qupv3_wrap0_s6_clk_src", + .parent_data = gcc_parent_data_1, + .num_parents = ARRAY_SIZE(gcc_parent_data_1), + .ops = &clk_rcg2_shared_ops, +}; + +static struct clk_rcg2 gcc_qupv3_wrap0_s6_clk_src = { + .cmd_rcgr = 0x1f868, + .mnd_width = 16, + .hid_width = 5, + .parent_map = gcc_parent_map_1, + .freq_tbl = ftbl_gcc_qupv3_wrap0_s0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &gcc_qupv3_wrap0_s6_clk_src_init, +}; + +static struct clk_init_data gcc_qupv3_wrap0_s7_clk_src_init = { + .name = "gcc_qupv3_wrap0_s7_clk_src", + .parent_data = gcc_parent_data_1, + .num_parents = ARRAY_SIZE(gcc_parent_data_1), + .ops = &clk_rcg2_shared_ops, +}; + +static struct clk_rcg2 gcc_qupv3_wrap0_s7_clk_src = { + .cmd_rcgr = 0x1f998, + .mnd_width = 16, + .hid_width = 5, + .parent_map = gcc_parent_map_1, + .freq_tbl = ftbl_gcc_qupv3_wrap0_s0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &gcc_qupv3_wrap0_s7_clk_src_init, +}; + +static struct clk_init_data gcc_qupv3_wrap0_s8_clk_src_init = { + .name = "gcc_qupv3_wrap0_s8_clk_src", + .parent_data = gcc_parent_data_1, + .num_parents = ARRAY_SIZE(gcc_parent_data_1), + .ops = &clk_rcg2_shared_ops, +}; + +static struct clk_rcg2 gcc_qupv3_wrap0_s8_clk_src = { + .cmd_rcgr = 0x1fac8, + .mnd_width = 16, + .hid_width = 5, + .parent_map = gcc_parent_map_1, + .freq_tbl = ftbl_gcc_qupv3_wrap0_s0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &gcc_qupv3_wrap0_s8_clk_src_init, +}; + +static struct clk_init_data gcc_qupv3_wrap0_s9_clk_src_init = { + .name = "gcc_qupv3_wrap0_s9_clk_src", + .parent_data = gcc_parent_data_1, + .num_parents = ARRAY_SIZE(gcc_parent_data_1), + .ops = &clk_rcg2_shared_ops, +}; + +static struct clk_rcg2 gcc_qupv3_wrap0_s9_clk_src = { + .cmd_rcgr = 0x1fbf8, + .mnd_width = 16, + .hid_width = 5, + .parent_map = gcc_parent_map_1, + .freq_tbl = ftbl_gcc_qupv3_wrap0_s0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &gcc_qupv3_wrap0_s9_clk_src_init, +}; + +static const struct freq_tbl ftbl_gcc_sdcc1_apps_clk_src[] = { + F(144000, P_BI_TCXO, 16, 3, 25), + F(400000, P_BI_TCXO, 12, 1, 4), + F(20000000, P_GPLL0_OUT_AUX2, 5, 1, 3), + F(25000000, P_GPLL0_OUT_AUX2, 6, 1, 2), + F(50000000, P_GPLL0_OUT_AUX2, 6, 0, 0), + F(100000000, P_GPLL0_OUT_AUX2, 3, 0, 0), + F(192000000, P_GPLL6_OUT_MAIN, 2, 0, 0), + F(384000000, P_GPLL6_OUT_MAIN, 1, 0, 0), + { } +}; + +static struct clk_rcg2 gcc_sdcc1_apps_clk_src = { + .cmd_rcgr = 0x38028, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_1, + .freq_tbl = ftbl_gcc_sdcc1_apps_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_sdcc1_apps_clk_src", + .parent_data = gcc_parent_data_1, + .num_parents = ARRAY_SIZE(gcc_parent_data_1), + .ops = &clk_rcg2_shared_floor_ops, + }, +}; + +static const struct freq_tbl ftbl_gcc_sdcc1_ice_core_clk_src[] = { + F(75000000, P_GPLL0_OUT_AUX2, 4, 0, 0), + F(100000000, P_GPLL0_OUT_AUX2, 3, 0, 0), + F(150000000, P_GPLL0_OUT_AUX2, 2, 0, 0), + F(200000000, P_GPLL0_OUT_EARLY, 3, 0, 0), + F(300000000, P_GPLL0_OUT_AUX2, 1, 0, 0), + { } +}; + +static struct clk_rcg2 gcc_sdcc1_ice_core_clk_src = { + .cmd_rcgr = 0x38010, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_0, + .freq_tbl = ftbl_gcc_sdcc1_ice_core_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_sdcc1_ice_core_clk_src", + .parent_data = gcc_parent_data_0, + .num_parents = ARRAY_SIZE(gcc_parent_data_0), + .ops = &clk_rcg2_shared_floor_ops, + }, +}; + +static const struct freq_tbl ftbl_gcc_sdcc2_apps_clk_src[] = { + F(400000, P_BI_TCXO, 12, 1, 4), + F(19200000, P_BI_TCXO, 1, 0, 0), + F(25000000, P_GPLL0_OUT_AUX2, 12, 0, 0), + F(50000000, P_GPLL0_OUT_AUX2, 6, 0, 0), + F(100000000, P_GPLL0_OUT_AUX2, 3, 0, 0), + F(202000000, P_GPLL7_OUT_MAIN, 4, 0, 0), + { } +}; + +static struct clk_rcg2 gcc_sdcc2_apps_clk_src = { + .cmd_rcgr = 0x1e00c, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_21, + .freq_tbl = ftbl_gcc_sdcc2_apps_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_sdcc2_apps_clk_src", + .parent_data = gcc_parent_data_21, + .num_parents = ARRAY_SIZE(gcc_parent_data_21), + .ops = &clk_rcg2_shared_floor_ops, + }, +}; + +static struct clk_rcg2 gcc_tscss_clk_src = { + .cmd_rcgr = 0xac004, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_22, + .freq_tbl = ftbl_gcc_emac0_ptp_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_tscss_clk_src", + .parent_data = gcc_parent_data_22, + .num_parents = ARRAY_SIZE(gcc_parent_data_22), + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_gcc_usb20_master_clk_src[] = { + F(60000000, P_GPLL0_OUT_AUX2, 5, 0, 0), + F(120000000, P_GPLL0_OUT_EARLY, 5, 0, 0), + { } +}; + +static struct clk_rcg2 gcc_usb20_master_clk_src = { + .cmd_rcgr = 0xb003c, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_0, + .freq_tbl = ftbl_gcc_usb20_master_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_usb20_master_clk_src", + .parent_data = gcc_parent_data_0, + .num_parents = ARRAY_SIZE(gcc_parent_data_0), + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 gcc_usb20_mock_utmi_clk_src = { + .cmd_rcgr = 0xb0020, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_13, + .freq_tbl = ftbl_gcc_emac0_phy_aux_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_usb20_mock_utmi_clk_src", + .parent_data = gcc_parent_data_13, + .num_parents = ARRAY_SIZE(gcc_parent_data_13), + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_gcc_usb30_prim_master_clk_src[] = { + F(66666667, P_GPLL0_OUT_AUX2, 4.5, 0, 0), + F(133333333, P_GPLL0_OUT_EARLY, 4.5, 0, 0), + F(200000000, P_GPLL0_OUT_EARLY, 3, 0, 0), + F(240000000, P_GPLL0_OUT_EARLY, 2.5, 0, 0), + { } +}; + +static struct clk_rcg2 gcc_usb30_prim_master_clk_src = { + .cmd_rcgr = 0x1a01c, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_0, + .freq_tbl = ftbl_gcc_usb30_prim_master_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_usb30_prim_master_clk_src", + .parent_data = gcc_parent_data_0, + .num_parents = ARRAY_SIZE(gcc_parent_data_0), + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 gcc_usb30_prim_mock_utmi_clk_src = { + .cmd_rcgr = 0x1a034, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_0, + .freq_tbl = ftbl_gcc_emac0_phy_aux_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_usb30_prim_mock_utmi_clk_src", + .parent_data = gcc_parent_data_0, + .num_parents = ARRAY_SIZE(gcc_parent_data_0), + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 gcc_usb3_prim_phy_aux_clk_src = { + .cmd_rcgr = 0x1a060, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_4, + .freq_tbl = ftbl_gcc_emac0_phy_aux_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_usb3_prim_phy_aux_clk_src", + .parent_data = gcc_parent_data_4, + .num_parents = ARRAY_SIZE(gcc_parent_data_4), + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_gcc_video_venus_clk_src[] = { + F(133333333, P_GPLL11_OUT_MAIN, 4.5, 0, 0), + F(240000000, P_GPLL11_OUT_MAIN, 2.5, 0, 0), + F(300000000, P_GPLL11_OUT_MAIN, 2, 0, 0), + F(384000000, P_GPLL11_OUT_MAIN, 2, 0, 0), + { } +}; + +static struct clk_rcg2 gcc_video_venus_clk_src = { + .cmd_rcgr = 0x6d000, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_24, + .freq_tbl = ftbl_gcc_video_venus_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_video_venus_clk_src", + .parent_data = gcc_parent_data_24, + .num_parents = ARRAY_SIZE(gcc_parent_data_24), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_regmap_div gcc_disp_gpll0_clk_src = { + .reg = 0x17058, + .shift = 0, + .width = 2, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_disp_gpll0_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &gpll0.clkr.hw, + }, + .num_parents = 1, + .ops = &clk_regmap_div_ops, + }, +}; + +static struct clk_regmap_div gcc_usb20_mock_utmi_postdiv_clk_src = { + .reg = 0xb0038, + .shift = 0, + .width = 4, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_usb20_mock_utmi_postdiv_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &gcc_usb20_mock_utmi_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_regmap_div_ro_ops, + }, +}; + +static struct clk_regmap_div gcc_usb30_prim_mock_utmi_postdiv_clk_src = { + .reg = 0x1a04c, + .shift = 0, + .width = 2, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_usb30_prim_mock_utmi_postdiv_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &gcc_usb30_prim_mock_utmi_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_regmap_div_ro_ops, + }, +}; + +static struct clk_branch gcc_ahb2phy_csi_clk = { + .halt_reg = 0x1d004, + .halt_check = BRANCH_HALT_DELAY, + .hwcg_reg = 0x1d004, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x1d004, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_ahb2phy_csi_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_ahb2phy_usb_clk = { + .halt_reg = 0x1d008, + .halt_check = BRANCH_HALT, + .hwcg_reg = 0x1d008, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x1d008, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_ahb2phy_usb_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_boot_rom_ahb_clk = { + .halt_reg = 0x23004, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x23004, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x7900c, + .enable_mask = BIT(1), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_boot_rom_ahb_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_cam_throttle_nrt_clk = { + .halt_reg = 0x17070, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x17070, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x79004, + .enable_mask = BIT(16), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_cam_throttle_nrt_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_cam_throttle_rt_clk = { + .halt_reg = 0x1706c, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x1706c, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x79004, + .enable_mask = BIT(15), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_cam_throttle_rt_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_axi_clk = { + .halt_reg = 0x58044, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x58044, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_axi_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_camss_axi_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_camnoc_atb_clk = { + .halt_reg = 0x5804c, + .halt_check = BRANCH_HALT_DELAY, + .hwcg_reg = 0x5804c, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x5804c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_camnoc_atb_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_camnoc_dragonlink_atb_clk = { + .halt_reg = 0x58060, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x58060, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x58060, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_camnoc_dragonlink_atb_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_camnoc_nts_xo_clk = { + .halt_reg = 0x58050, + .halt_check = BRANCH_HALT_DELAY, + .hwcg_reg = 0x58050, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x58050, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_camnoc_nts_xo_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_cci_0_clk = { + .halt_reg = 0x56018, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x56018, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_cci_0_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_camss_cci_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_cphy_0_clk = { + .halt_reg = 0x52088, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x52088, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_cphy_0_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_camss_tfe_cphy_rx_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_cphy_1_clk = { + .halt_reg = 0x5208c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x5208c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_cphy_1_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_camss_tfe_cphy_rx_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_csi0phytimer_clk = { + .halt_reg = 0x45018, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x45018, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_csi0phytimer_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_camss_csi0phytimer_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_csi1phytimer_clk = { + .halt_reg = 0x45034, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x45034, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_csi1phytimer_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_camss_csi1phytimer_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_mclk0_clk = { + .halt_reg = 0x51018, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x51018, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_mclk0_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_camss_mclk0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_mclk1_clk = { + .halt_reg = 0x51034, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x51034, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_mclk1_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_camss_mclk1_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_mclk2_clk = { + .halt_reg = 0x51050, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x51050, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_mclk2_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_camss_mclk2_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_mclk3_clk = { + .halt_reg = 0x5106c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x5106c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_mclk3_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_camss_mclk3_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_nrt_axi_clk = { + .halt_reg = 0x58054, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x58054, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_nrt_axi_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_ope_ahb_clk = { + .halt_reg = 0x5503c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x5503c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_ope_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_camss_ope_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_ope_clk = { + .halt_reg = 0x5501c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x5501c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_ope_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_camss_ope_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_rt_axi_clk = { + .halt_reg = 0x5805c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x5805c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_rt_axi_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_tfe_0_clk = { + .halt_reg = 0x5201c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x5201c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_tfe_0_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_camss_tfe_0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_tfe_0_cphy_rx_clk = { + .halt_reg = 0x5207c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x5207c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_tfe_0_cphy_rx_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_camss_tfe_cphy_rx_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_tfe_0_csid_clk = { + .halt_reg = 0x520ac, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x520ac, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_tfe_0_csid_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_camss_tfe_0_csid_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_tfe_1_clk = { + .halt_reg = 0x5203c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x5203c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_tfe_1_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_camss_tfe_1_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_tfe_1_cphy_rx_clk = { + .halt_reg = 0x52080, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x52080, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_tfe_1_cphy_rx_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_camss_tfe_cphy_rx_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_tfe_1_csid_clk = { + .halt_reg = 0x520cc, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x520cc, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_tfe_1_csid_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_camss_tfe_1_csid_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_top_ahb_clk = { + .halt_reg = 0x58028, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x58028, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_camss_top_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_camss_top_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_cfg_noc_usb2_prim_axi_clk = { + .halt_reg = 0x111c4, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x111c4, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x111c4, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_cfg_noc_usb2_prim_axi_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_usb20_master_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_cfg_noc_usb3_prim_axi_clk = { + .halt_reg = 0x1a07c, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x1a07c, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x1a07c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_cfg_noc_usb3_prim_axi_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_usb30_prim_master_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_ddrss_gpu_axi_clk = { + .halt_reg = 0x71000, + .halt_check = BRANCH_HALT_SKIP, + .hwcg_reg = 0x71000, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x71000, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_ddrss_gpu_axi_clk", + .ops = &clk_branch2_aon_ops, + }, + }, +}; + +static struct clk_branch gcc_ddrss_memnoc_pcie_sf_clk = { + .halt_reg = 0x29044, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x29044, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x29044, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_ddrss_memnoc_pcie_sf_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_disp_gpll0_div_clk_src = { + .halt_check = BRANCH_HALT_DELAY, + .clkr = { + .enable_reg = 0x79004, + .enable_mask = BIT(11), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_disp_gpll0_div_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &gcc_disp_gpll0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_disp_hf_axi_clk = { + .halt_reg = 0x17020, + .halt_check = BRANCH_HALT, + .hwcg_reg = 0x17020, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x17020, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_disp_hf_axi_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_disp_throttle_core_clk = { + .halt_reg = 0x17064, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x17064, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x79004, + .enable_mask = BIT(13), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_disp_throttle_core_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_emac0_ahb_clk = { + .halt_reg = 0xad010, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0xad010, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0xad010, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_emac0_ahb_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_emac0_axi_clk = { + .halt_reg = 0xad014, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0xad014, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0xad014, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_emac0_axi_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_emac0_axi_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_emac0_axi_sys_noc_clk = { + .halt_reg = 0x109d4, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x109d4, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x109d4, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_emac0_axi_sys_noc_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_emac0_axi_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_emac0_cc_sgmiiphy_rx_clk = { + .halt_reg = 0xad044, + .halt_check = BRANCH_HALT_DELAY, + .clkr = { + .enable_reg = 0xad044, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_emac0_cc_sgmiiphy_rx_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_emac0_cc_sgmiiphy_rx_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_emac0_cc_sgmiiphy_tx_clk = { + .halt_reg = 0xad03c, + .halt_check = BRANCH_HALT_DELAY, + .hwcg_reg = 0xad03c, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0xad03c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_emac0_cc_sgmiiphy_tx_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_emac0_cc_sgmiiphy_tx_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_emac0_phy_aux_clk = { + .halt_reg = 0xad018, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0xad018, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_emac0_phy_aux_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_emac0_phy_aux_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_emac0_ptp_clk = { + .halt_reg = 0xad034, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0xad034, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_emac0_ptp_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_emac0_ptp_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_emac0_rgmii_clk = { + .halt_reg = 0xad038, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0xad038, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_emac0_rgmii_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_emac0_rgmii_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_emac1_ahb_clk = { + .halt_reg = 0xae010, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0xae010, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0xae010, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_emac1_ahb_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_emac1_axi_clk = { + .halt_reg = 0xae014, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0xae014, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0xae014, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_emac1_axi_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_emac1_axi_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_emac1_axi_sys_noc_clk = { + .halt_reg = 0x109f4, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x109f4, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x109f4, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_emac1_axi_sys_noc_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_emac1_axi_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_emac1_cc_sgmiiphy_rx_clk = { + .halt_reg = 0xae044, + .halt_check = BRANCH_HALT_DELAY, + .clkr = { + .enable_reg = 0xae044, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_emac1_cc_sgmiiphy_rx_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_emac1_cc_sgmiiphy_rx_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_emac1_cc_sgmiiphy_tx_clk = { + .halt_reg = 0xae03c, + .halt_check = BRANCH_HALT_DELAY, + .hwcg_reg = 0xae03c, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0xae03c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_emac1_cc_sgmiiphy_tx_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_emac1_cc_sgmiiphy_tx_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_emac1_phy_aux_clk = { + .halt_reg = 0xae018, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0xae018, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_emac1_phy_aux_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_emac1_phy_aux_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_emac1_ptp_clk = { + .halt_reg = 0xae034, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0xae034, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_emac1_ptp_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_emac1_ptp_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_emac1_rgmii_clk = { + .halt_reg = 0xae038, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0xae038, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_emac1_rgmii_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_emac1_rgmii_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_gp1_clk = { + .halt_reg = 0x4d000, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x4d000, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_gp1_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_gp1_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_gp2_clk = { + .halt_reg = 0x4e000, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x4e000, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_gp2_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_gp2_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_gp3_clk = { + .halt_reg = 0x4f000, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x4f000, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_gp3_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_gp3_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_gpu_gpll0_clk_src = { + .halt_check = BRANCH_HALT_DELAY, + .clkr = { + .enable_reg = 0x7900c, + .enable_mask = BIT(18), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_gpu_gpll0_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &gpll0.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_gpu_gpll0_div_clk_src = { + .halt_check = BRANCH_HALT_DELAY, + .clkr = { + .enable_reg = 0x7900c, + .enable_mask = BIT(19), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_gpu_gpll0_div_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &gpll0_out_aux2.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_gpu_memnoc_gfx_clk = { + .halt_reg = 0x3600c, + .halt_check = BRANCH_VOTED, + .hwcg_reg = 0x3600c, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x3600c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_gpu_memnoc_gfx_clk", + .ops = &clk_branch2_aon_ops, + }, + }, +}; + +static struct clk_branch gcc_gpu_smmu_vote_clk = { + .halt_reg = 0x7d000, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x7d000, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_gpu_smmu_vote_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_gpu_snoc_dvm_gfx_clk = { + .halt_reg = 0x36018, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x36018, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_gpu_snoc_dvm_gfx_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_gpu_throttle_core_clk = { + .halt_reg = 0x36048, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x36048, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x7900c, + .enable_mask = BIT(21), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_gpu_throttle_core_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_mmu_tcu_vote_clk = { + .halt_reg = 0x7d06c, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x7d06c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_mmu_tcu_vote_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_pcie_aux_clk = { + .halt_reg = 0xaf044, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0xaf044, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x79018, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_pcie_aux_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_pcie_aux_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_pcie_cfg_ahb_clk = { + .halt_reg = 0xaf010, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0xaf010, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x7900c, + .enable_mask = BIT(27), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_pcie_cfg_ahb_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_pcie_clkref_en = { + .halt_reg = 0xb8000, + .halt_check = BRANCH_HALT_DELAY, + .clkr = { + .enable_reg = 0xb8000, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_pcie_clkref_en", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_pcie_mstr_axi_clk = { + .halt_reg = 0xaf020, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0xaf020, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x7900c, + .enable_mask = BIT(30), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_pcie_mstr_axi_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_pcie_pipe_clk = { + .halt_reg = 0xaf050, + .halt_check = BRANCH_HALT_DELAY, + .hwcg_reg = 0xaf050, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x79018, + .enable_mask = BIT(2), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_pcie_pipe_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_pcie_pipe_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_pcie_rchng_phy_clk = { + .halt_reg = 0xaf040, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0xaf040, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x7900c, + .enable_mask = BIT(31), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_pcie_rchng_phy_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_pcie_rchng_phy_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_pcie_sleep_clk = { + .halt_reg = 0xaf04c, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0xaf04c, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x79018, + .enable_mask = BIT(1), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_pcie_sleep_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_pcie_aux_phy_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_pcie_slv_axi_clk = { + .halt_reg = 0xaf018, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x7900c, + .enable_mask = BIT(29), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_pcie_slv_axi_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_pcie_slv_q2a_axi_clk = { + .halt_reg = 0xaf014, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0xaf014, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x7900c, + .enable_mask = BIT(28), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_pcie_slv_q2a_axi_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_pcie_tbu_clk = { + .halt_reg = 0xaf098, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0xaf098, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x79018, + .enable_mask = BIT(6), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_pcie_tbu_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_pcie_throttle_core_clk = { + .halt_reg = 0xaf094, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0xaf094, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x79018, + .enable_mask = BIT(5), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_pcie_throttle_core_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_pcie_throttle_xo_clk = { + .halt_reg = 0xaf090, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x79018, + .enable_mask = BIT(4), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_pcie_throttle_xo_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_pcie_tile_axi_sys_noc_clk = { + .halt_reg = 0x10f2c, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x10f2c, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x10f2c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_pcie_tile_axi_sys_noc_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_emac0_axi_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_pdm2_clk = { + .halt_reg = 0x2000c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x2000c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_pdm2_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_pdm2_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_pdm_ahb_clk = { + .halt_reg = 0x20004, + .halt_check = BRANCH_HALT, + .hwcg_reg = 0x20004, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x20004, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_pdm_ahb_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_pdm_xo4_clk = { + .halt_reg = 0x20008, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x20008, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_pdm_xo4_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_pwm0_xo512_clk = { + .halt_reg = 0x2002c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x2002c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_pwm0_xo512_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_qmip_camera_nrt_ahb_clk = { + .halt_reg = 0x17014, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x17014, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x79004, + .enable_mask = BIT(9), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_qmip_camera_nrt_ahb_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_qmip_camera_rt_ahb_clk = { + .halt_reg = 0x17060, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x17060, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x79004, + .enable_mask = BIT(12), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_qmip_camera_rt_ahb_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_qmip_disp_ahb_clk = { + .halt_reg = 0x17018, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x17018, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x79004, + .enable_mask = BIT(10), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_qmip_disp_ahb_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_qmip_gpu_cfg_ahb_clk = { + .halt_reg = 0x36040, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x36040, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x7900c, + .enable_mask = BIT(20), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_qmip_gpu_cfg_ahb_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_qmip_pcie_cfg_ahb_clk = { + .halt_reg = 0xaf08c, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0xaf08c, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x79018, + .enable_mask = BIT(3), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_qmip_pcie_cfg_ahb_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_qmip_video_vcodec_ahb_clk = { + .halt_reg = 0x17010, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x17010, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x79004, + .enable_mask = BIT(8), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_qmip_video_vcodec_ahb_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_qupv3_wrap0_core_2x_clk = { + .halt_reg = 0x1f014, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x79004, + .enable_mask = BIT(21), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_qupv3_wrap0_core_2x_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_qupv3_wrap0_core_clk = { + .halt_reg = 0x1f00c, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x79004, + .enable_mask = BIT(20), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_qupv3_wrap0_core_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_qupv3_wrap0_s0_clk = { + .halt_reg = 0x1f144, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x79004, + .enable_mask = BIT(22), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_qupv3_wrap0_s0_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_qupv3_wrap0_s0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_qupv3_wrap0_s1_clk = { + .halt_reg = 0x1f274, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x79004, + .enable_mask = BIT(23), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_qupv3_wrap0_s1_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_qupv3_wrap0_s1_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_qupv3_wrap0_s2_clk = { + .halt_reg = 0x1f3a4, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x79004, + .enable_mask = BIT(24), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_qupv3_wrap0_s2_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_qupv3_wrap0_s2_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_qupv3_wrap0_s3_clk = { + .halt_reg = 0x1f4d4, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x79004, + .enable_mask = BIT(25), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_qupv3_wrap0_s3_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_qupv3_wrap0_s3_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_qupv3_wrap0_s4_clk = { + .halt_reg = 0x1f604, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x79004, + .enable_mask = BIT(26), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_qupv3_wrap0_s4_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_qupv3_wrap0_s4_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_qupv3_wrap0_s5_clk = { + .halt_reg = 0x1f734, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x79004, + .enable_mask = BIT(27), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_qupv3_wrap0_s5_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_qupv3_wrap0_s5_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_qupv3_wrap0_s6_clk = { + .halt_reg = 0x1f864, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x79004, + .enable_mask = BIT(28), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_qupv3_wrap0_s6_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_qupv3_wrap0_s6_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_qupv3_wrap0_s7_clk = { + .halt_reg = 0x1f994, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x79004, + .enable_mask = BIT(29), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_qupv3_wrap0_s7_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_qupv3_wrap0_s7_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_qupv3_wrap0_s8_clk = { + .halt_reg = 0x1fac4, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x79004, + .enable_mask = BIT(30), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_qupv3_wrap0_s8_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_qupv3_wrap0_s8_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_qupv3_wrap0_s9_clk = { + .halt_reg = 0x1fbf4, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x79004, + .enable_mask = BIT(31), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_qupv3_wrap0_s9_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_qupv3_wrap0_s9_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_qupv3_wrap_0_m_ahb_clk = { + .halt_reg = 0x1f004, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x1f004, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x79004, + .enable_mask = BIT(18), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_qupv3_wrap_0_m_ahb_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_qupv3_wrap_0_s_ahb_clk = { + .halt_reg = 0x1f008, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x1f008, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x79004, + .enable_mask = BIT(19), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_qupv3_wrap_0_s_ahb_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_sdcc1_ahb_clk = { + .halt_reg = 0x38008, + .halt_check = BRANCH_HALT, + .hwcg_reg = 0x38008, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x38008, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_sdcc1_ahb_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_sdcc1_apps_clk = { + .halt_reg = 0x38004, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x38004, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_sdcc1_apps_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_sdcc1_apps_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_sdcc1_ice_core_clk = { + .halt_reg = 0x3800c, + .halt_check = BRANCH_HALT, + .hwcg_reg = 0x3800c, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x3800c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_sdcc1_ice_core_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_sdcc1_ice_core_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_sdcc2_ahb_clk = { + .halt_reg = 0x1e008, + .halt_check = BRANCH_HALT, + .hwcg_reg = 0x1e008, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x1e008, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_sdcc2_ahb_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_sdcc2_apps_clk = { + .halt_reg = 0x1e004, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1e004, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_sdcc2_apps_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_sdcc2_apps_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_sys_noc_usb2_prim_axi_clk = { + .halt_reg = 0x10a14, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x10a14, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x10a14, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_sys_noc_usb2_prim_axi_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_usb20_master_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_sys_noc_usb3_prim_axi_clk = { + .halt_reg = 0x1a078, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x1a078, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x1a078, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_sys_noc_usb3_prim_axi_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_usb30_prim_master_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_tscss_ahb_clk = { + .halt_reg = 0xac024, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0xac024, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0xac024, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_tscss_ahb_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_tscss_cntr_clk = { + .halt_reg = 0xac020, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0xac020, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_tscss_cntr_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_tscss_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_tscss_etu_clk = { + .halt_reg = 0xac01c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0xac01c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_tscss_etu_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_tscss_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_ufs_clkref_en = { + .halt_reg = 0x8c000, + .halt_check = BRANCH_HALT_DELAY, + .clkr = { + .enable_reg = 0x8c000, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_ufs_clkref_en", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_usb20_master_clk = { + .halt_reg = 0xb0010, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0xb0010, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0xb0010, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_usb20_master_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_usb20_master_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_usb20_mock_utmi_clk = { + .halt_reg = 0xb001c, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0xb001c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_usb20_mock_utmi_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_usb20_mock_utmi_postdiv_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_usb20_sleep_clk = { + .halt_reg = 0xb0018, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0xb0018, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_usb20_sleep_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_usb30_prim_master_clk = { + .halt_reg = 0x1a010, + .halt_check = BRANCH_HALT, + .hwcg_reg = 0x1a010, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x1a010, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_usb30_prim_master_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_usb30_prim_master_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_usb30_prim_mock_utmi_clk = { + .halt_reg = 0x1a018, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1a018, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_usb30_prim_mock_utmi_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_usb30_prim_mock_utmi_postdiv_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_usb30_prim_sleep_clk = { + .halt_reg = 0x1a014, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1a014, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_usb30_prim_sleep_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_usb3_prim_clkref_en = { + .halt_reg = 0x9f000, + .halt_check = BRANCH_HALT_DELAY, + .clkr = { + .enable_reg = 0x9f000, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_usb3_prim_clkref_en", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_usb3_prim_phy_com_aux_clk = { + .halt_reg = 0x1a054, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1a054, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_usb3_prim_phy_com_aux_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_usb3_prim_phy_aux_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_usb3_prim_phy_pipe_clk = { + .halt_reg = 0x1a058, + .halt_check = BRANCH_HALT_DELAY, + .hwcg_reg = 0x1a058, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x1a058, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_usb3_prim_phy_pipe_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_usb3_prim_phy_pipe_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_vcodec0_axi_clk = { + .halt_reg = 0x6e008, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x6e008, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_vcodec0_axi_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_venus_ahb_clk = { + .halt_reg = 0x6e010, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x6e010, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_venus_ahb_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_venus_ctl_axi_clk = { + .halt_reg = 0x6e004, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x6e004, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_venus_ctl_axi_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_video_axi0_clk = { + .halt_reg = 0x1701c, + .halt_check = BRANCH_HALT, + .hwcg_reg = 0x1701c, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x1701c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_video_axi0_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_video_throttle_core_clk = { + .halt_reg = 0x17068, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x17068, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x79004, + .enable_mask = BIT(14), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_video_throttle_core_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_video_vcodec0_sys_clk = { + .halt_reg = 0x6d044, + .halt_check = BRANCH_HALT_DELAY, + .hwcg_reg = 0x6d044, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x6d044, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_video_vcodec0_sys_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_video_venus_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_video_venus_ctl_clk = { + .halt_reg = 0x6d02c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x6d02c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_video_venus_ctl_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_video_venus_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct gdsc gcc_camss_top_gdsc = { + .gdscr = 0x58004, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0xf, + .pd = { + .name = "gcc_camss_top_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct gdsc gcc_emac0_gdsc = { + .gdscr = 0xad004, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0x2, + .pd = { + .name = "gcc_emac0_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct gdsc gcc_emac1_gdsc = { + .gdscr = 0xae004, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0x2, + .pd = { + .name = "gcc_emac1_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct gdsc gcc_pcie_gdsc = { + .gdscr = 0xaf004, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0xf, + .pd = { + .name = "gcc_pcie_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct gdsc gcc_usb20_gdsc = { + .gdscr = 0xb0004, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0xf, + .pd = { + .name = "gcc_usb20_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct gdsc gcc_usb30_prim_gdsc = { + .gdscr = 0x1a004, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0x2, + .pd = { + .name = "gcc_usb30_prim_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct gdsc gcc_venus_gdsc = { + .gdscr = 0x6d01c, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0xf, + .pd = { + .name = "gcc_venus_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct gdsc gcc_vcodec0_gdsc = { + .gdscr = 0x6d038, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0xf, + .pd = { + .name = "gcc_vcodec0_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .parent = &gcc_venus_gdsc.pd, + .flags = HW_CTRL_TRIGGER | POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct clk_regmap *gcc_shikra_clocks[] = { + [GCC_AHB2PHY_CSI_CLK] = &gcc_ahb2phy_csi_clk.clkr, + [GCC_AHB2PHY_USB_CLK] = &gcc_ahb2phy_usb_clk.clkr, + [GCC_BOOT_ROM_AHB_CLK] = &gcc_boot_rom_ahb_clk.clkr, + [GCC_CAM_THROTTLE_NRT_CLK] = &gcc_cam_throttle_nrt_clk.clkr, + [GCC_CAM_THROTTLE_RT_CLK] = &gcc_cam_throttle_rt_clk.clkr, + [GCC_CAMSS_AXI_CLK] = &gcc_camss_axi_clk.clkr, + [GCC_CAMSS_AXI_CLK_SRC] = &gcc_camss_axi_clk_src.clkr, + [GCC_CAMSS_CAMNOC_ATB_CLK] = &gcc_camss_camnoc_atb_clk.clkr, + [GCC_CAMSS_CAMNOC_DRAGONLINK_ATB_CLK] = &gcc_camss_camnoc_dragonlink_atb_clk.clkr, + [GCC_CAMSS_CAMNOC_NTS_XO_CLK] = &gcc_camss_camnoc_nts_xo_clk.clkr, + [GCC_CAMSS_CCI_0_CLK] = &gcc_camss_cci_0_clk.clkr, + [GCC_CAMSS_CCI_CLK_SRC] = &gcc_camss_cci_clk_src.clkr, + [GCC_CAMSS_CPHY_0_CLK] = &gcc_camss_cphy_0_clk.clkr, + [GCC_CAMSS_CPHY_1_CLK] = &gcc_camss_cphy_1_clk.clkr, + [GCC_CAMSS_CSI0PHYTIMER_CLK] = &gcc_camss_csi0phytimer_clk.clkr, + [GCC_CAMSS_CSI0PHYTIMER_CLK_SRC] = &gcc_camss_csi0phytimer_clk_src.clkr, + [GCC_CAMSS_CSI1PHYTIMER_CLK] = &gcc_camss_csi1phytimer_clk.clkr, + [GCC_CAMSS_CSI1PHYTIMER_CLK_SRC] = &gcc_camss_csi1phytimer_clk_src.clkr, + [GCC_CAMSS_MCLK0_CLK] = &gcc_camss_mclk0_clk.clkr, + [GCC_CAMSS_MCLK0_CLK_SRC] = &gcc_camss_mclk0_clk_src.clkr, + [GCC_CAMSS_MCLK1_CLK] = &gcc_camss_mclk1_clk.clkr, + [GCC_CAMSS_MCLK1_CLK_SRC] = &gcc_camss_mclk1_clk_src.clkr, + [GCC_CAMSS_MCLK2_CLK] = &gcc_camss_mclk2_clk.clkr, + [GCC_CAMSS_MCLK2_CLK_SRC] = &gcc_camss_mclk2_clk_src.clkr, + [GCC_CAMSS_MCLK3_CLK] = &gcc_camss_mclk3_clk.clkr, + [GCC_CAMSS_MCLK3_CLK_SRC] = &gcc_camss_mclk3_clk_src.clkr, + [GCC_CAMSS_NRT_AXI_CLK] = &gcc_camss_nrt_axi_clk.clkr, + [GCC_CAMSS_OPE_AHB_CLK] = &gcc_camss_ope_ahb_clk.clkr, + [GCC_CAMSS_OPE_AHB_CLK_SRC] = &gcc_camss_ope_ahb_clk_src.clkr, + [GCC_CAMSS_OPE_CLK] = &gcc_camss_ope_clk.clkr, + [GCC_CAMSS_OPE_CLK_SRC] = &gcc_camss_ope_clk_src.clkr, + [GCC_CAMSS_RT_AXI_CLK] = &gcc_camss_rt_axi_clk.clkr, + [GCC_CAMSS_TFE_0_CLK] = &gcc_camss_tfe_0_clk.clkr, + [GCC_CAMSS_TFE_0_CLK_SRC] = &gcc_camss_tfe_0_clk_src.clkr, + [GCC_CAMSS_TFE_0_CPHY_RX_CLK] = &gcc_camss_tfe_0_cphy_rx_clk.clkr, + [GCC_CAMSS_TFE_0_CSID_CLK] = &gcc_camss_tfe_0_csid_clk.clkr, + [GCC_CAMSS_TFE_0_CSID_CLK_SRC] = &gcc_camss_tfe_0_csid_clk_src.clkr, + [GCC_CAMSS_TFE_1_CLK] = &gcc_camss_tfe_1_clk.clkr, + [GCC_CAMSS_TFE_1_CLK_SRC] = &gcc_camss_tfe_1_clk_src.clkr, + [GCC_CAMSS_TFE_1_CPHY_RX_CLK] = &gcc_camss_tfe_1_cphy_rx_clk.clkr, + [GCC_CAMSS_TFE_1_CSID_CLK] = &gcc_camss_tfe_1_csid_clk.clkr, + [GCC_CAMSS_TFE_1_CSID_CLK_SRC] = &gcc_camss_tfe_1_csid_clk_src.clkr, + [GCC_CAMSS_TFE_CPHY_RX_CLK_SRC] = &gcc_camss_tfe_cphy_rx_clk_src.clkr, + [GCC_CAMSS_TOP_AHB_CLK] = &gcc_camss_top_ahb_clk.clkr, + [GCC_CAMSS_TOP_AHB_CLK_SRC] = &gcc_camss_top_ahb_clk_src.clkr, + [GCC_CFG_NOC_USB2_PRIM_AXI_CLK] = &gcc_cfg_noc_usb2_prim_axi_clk.clkr, + [GCC_CFG_NOC_USB3_PRIM_AXI_CLK] = &gcc_cfg_noc_usb3_prim_axi_clk.clkr, + [GCC_DDRSS_GPU_AXI_CLK] = &gcc_ddrss_gpu_axi_clk.clkr, + [GCC_DDRSS_MEMNOC_PCIE_SF_CLK] = &gcc_ddrss_memnoc_pcie_sf_clk.clkr, + [GCC_DISP_GPLL0_CLK_SRC] = &gcc_disp_gpll0_clk_src.clkr, + [GCC_DISP_GPLL0_DIV_CLK_SRC] = &gcc_disp_gpll0_div_clk_src.clkr, + [GCC_DISP_HF_AXI_CLK] = &gcc_disp_hf_axi_clk.clkr, + [GCC_DISP_THROTTLE_CORE_CLK] = &gcc_disp_throttle_core_clk.clkr, + [GCC_EMAC0_AHB_CLK] = &gcc_emac0_ahb_clk.clkr, + [GCC_EMAC0_AXI_CLK] = &gcc_emac0_axi_clk.clkr, + [GCC_EMAC0_AXI_CLK_SRC] = &gcc_emac0_axi_clk_src.clkr, + [GCC_EMAC0_AXI_SYS_NOC_CLK] = &gcc_emac0_axi_sys_noc_clk.clkr, + [GCC_EMAC0_CC_SGMIIPHY_RX_CLK] = &gcc_emac0_cc_sgmiiphy_rx_clk.clkr, + [GCC_EMAC0_CC_SGMIIPHY_RX_CLK_SRC] = &gcc_emac0_cc_sgmiiphy_rx_clk_src.clkr, + [GCC_EMAC0_CC_SGMIIPHY_TX_CLK] = &gcc_emac0_cc_sgmiiphy_tx_clk.clkr, + [GCC_EMAC0_CC_SGMIIPHY_TX_CLK_SRC] = &gcc_emac0_cc_sgmiiphy_tx_clk_src.clkr, + [GCC_EMAC0_PHY_AUX_CLK] = &gcc_emac0_phy_aux_clk.clkr, + [GCC_EMAC0_PHY_AUX_CLK_SRC] = &gcc_emac0_phy_aux_clk_src.clkr, + [GCC_EMAC0_PTP_CLK] = &gcc_emac0_ptp_clk.clkr, + [GCC_EMAC0_PTP_CLK_SRC] = &gcc_emac0_ptp_clk_src.clkr, + [GCC_EMAC0_RGMII_CLK] = &gcc_emac0_rgmii_clk.clkr, + [GCC_EMAC0_RGMII_CLK_SRC] = &gcc_emac0_rgmii_clk_src.clkr, + [GCC_EMAC1_AHB_CLK] = &gcc_emac1_ahb_clk.clkr, + [GCC_EMAC1_AXI_CLK] = &gcc_emac1_axi_clk.clkr, + [GCC_EMAC1_AXI_CLK_SRC] = &gcc_emac1_axi_clk_src.clkr, + [GCC_EMAC1_AXI_SYS_NOC_CLK] = &gcc_emac1_axi_sys_noc_clk.clkr, + [GCC_EMAC1_CC_SGMIIPHY_RX_CLK] = &gcc_emac1_cc_sgmiiphy_rx_clk.clkr, + [GCC_EMAC1_CC_SGMIIPHY_RX_CLK_SRC] = &gcc_emac1_cc_sgmiiphy_rx_clk_src.clkr, + [GCC_EMAC1_CC_SGMIIPHY_TX_CLK] = &gcc_emac1_cc_sgmiiphy_tx_clk.clkr, + [GCC_EMAC1_CC_SGMIIPHY_TX_CLK_SRC] = &gcc_emac1_cc_sgmiiphy_tx_clk_src.clkr, + [GCC_EMAC1_PHY_AUX_CLK] = &gcc_emac1_phy_aux_clk.clkr, + [GCC_EMAC1_PHY_AUX_CLK_SRC] = &gcc_emac1_phy_aux_clk_src.clkr, + [GCC_EMAC1_PTP_CLK] = &gcc_emac1_ptp_clk.clkr, + [GCC_EMAC1_PTP_CLK_SRC] = &gcc_emac1_ptp_clk_src.clkr, + [GCC_EMAC1_RGMII_CLK] = &gcc_emac1_rgmii_clk.clkr, + [GCC_EMAC1_RGMII_CLK_SRC] = &gcc_emac1_rgmii_clk_src.clkr, + [GCC_GP1_CLK] = &gcc_gp1_clk.clkr, + [GCC_GP1_CLK_SRC] = &gcc_gp1_clk_src.clkr, + [GCC_GP2_CLK] = &gcc_gp2_clk.clkr, + [GCC_GP2_CLK_SRC] = &gcc_gp2_clk_src.clkr, + [GCC_GP3_CLK] = &gcc_gp3_clk.clkr, + [GCC_GP3_CLK_SRC] = &gcc_gp3_clk_src.clkr, + [GCC_GPU_GPLL0_CLK_SRC] = &gcc_gpu_gpll0_clk_src.clkr, + [GCC_GPU_GPLL0_DIV_CLK_SRC] = &gcc_gpu_gpll0_div_clk_src.clkr, + [GCC_GPU_MEMNOC_GFX_CLK] = &gcc_gpu_memnoc_gfx_clk.clkr, + [GCC_GPU_SMMU_VOTE_CLK] = &gcc_gpu_smmu_vote_clk.clkr, + [GCC_GPU_SNOC_DVM_GFX_CLK] = &gcc_gpu_snoc_dvm_gfx_clk.clkr, + [GCC_GPU_THROTTLE_CORE_CLK] = &gcc_gpu_throttle_core_clk.clkr, + [GCC_MMU_TCU_VOTE_CLK] = &gcc_mmu_tcu_vote_clk.clkr, + [GCC_PCIE_AUX_CLK] = &gcc_pcie_aux_clk.clkr, + [GCC_PCIE_AUX_CLK_SRC] = &gcc_pcie_aux_clk_src.clkr, + [GCC_PCIE_AUX_PHY_CLK_SRC] = &gcc_pcie_aux_phy_clk_src.clkr, + [GCC_PCIE_CFG_AHB_CLK] = &gcc_pcie_cfg_ahb_clk.clkr, + [GCC_PCIE_CLKREF_EN] = &gcc_pcie_clkref_en.clkr, + [GCC_PCIE_MSTR_AXI_CLK] = &gcc_pcie_mstr_axi_clk.clkr, + [GCC_PCIE_PIPE_CLK] = &gcc_pcie_pipe_clk.clkr, + [GCC_PCIE_PIPE_CLK_SRC] = &gcc_pcie_pipe_clk_src.clkr, + [GCC_PCIE_RCHNG_PHY_CLK] = &gcc_pcie_rchng_phy_clk.clkr, + [GCC_PCIE_RCHNG_PHY_CLK_SRC] = &gcc_pcie_rchng_phy_clk_src.clkr, + [GCC_PCIE_SLEEP_CLK] = &gcc_pcie_sleep_clk.clkr, + [GCC_PCIE_SLV_AXI_CLK] = &gcc_pcie_slv_axi_clk.clkr, + [GCC_PCIE_SLV_Q2A_AXI_CLK] = &gcc_pcie_slv_q2a_axi_clk.clkr, + [GCC_PCIE_TBU_CLK] = &gcc_pcie_tbu_clk.clkr, + [GCC_PCIE_THROTTLE_CORE_CLK] = &gcc_pcie_throttle_core_clk.clkr, + [GCC_PCIE_THROTTLE_XO_CLK] = &gcc_pcie_throttle_xo_clk.clkr, + [GCC_PCIE_TILE_AXI_SYS_NOC_CLK] = &gcc_pcie_tile_axi_sys_noc_clk.clkr, + [GCC_PDM2_CLK] = &gcc_pdm2_clk.clkr, + [GCC_PDM2_CLK_SRC] = &gcc_pdm2_clk_src.clkr, + [GCC_PDM_AHB_CLK] = &gcc_pdm_ahb_clk.clkr, + [GCC_PDM_XO4_CLK] = &gcc_pdm_xo4_clk.clkr, + [GCC_PWM0_XO512_CLK] = &gcc_pwm0_xo512_clk.clkr, + [GCC_QMIP_CAMERA_NRT_AHB_CLK] = &gcc_qmip_camera_nrt_ahb_clk.clkr, + [GCC_QMIP_CAMERA_RT_AHB_CLK] = &gcc_qmip_camera_rt_ahb_clk.clkr, + [GCC_QMIP_DISP_AHB_CLK] = &gcc_qmip_disp_ahb_clk.clkr, + [GCC_QMIP_GPU_CFG_AHB_CLK] = &gcc_qmip_gpu_cfg_ahb_clk.clkr, + [GCC_QMIP_PCIE_CFG_AHB_CLK] = &gcc_qmip_pcie_cfg_ahb_clk.clkr, + [GCC_QMIP_VIDEO_VCODEC_AHB_CLK] = &gcc_qmip_video_vcodec_ahb_clk.clkr, + [GCC_QUPV3_WRAP0_CORE_2X_CLK] = &gcc_qupv3_wrap0_core_2x_clk.clkr, + [GCC_QUPV3_WRAP0_CORE_CLK] = &gcc_qupv3_wrap0_core_clk.clkr, + [GCC_QUPV3_WRAP0_S0_CLK] = &gcc_qupv3_wrap0_s0_clk.clkr, + [GCC_QUPV3_WRAP0_S0_CLK_SRC] = &gcc_qupv3_wrap0_s0_clk_src.clkr, + [GCC_QUPV3_WRAP0_S1_CLK] = &gcc_qupv3_wrap0_s1_clk.clkr, + [GCC_QUPV3_WRAP0_S1_CLK_SRC] = &gcc_qupv3_wrap0_s1_clk_src.clkr, + [GCC_QUPV3_WRAP0_S2_CLK] = &gcc_qupv3_wrap0_s2_clk.clkr, + [GCC_QUPV3_WRAP0_S2_CLK_SRC] = &gcc_qupv3_wrap0_s2_clk_src.clkr, + [GCC_QUPV3_WRAP0_S3_CLK] = &gcc_qupv3_wrap0_s3_clk.clkr, + [GCC_QUPV3_WRAP0_S3_CLK_SRC] = &gcc_qupv3_wrap0_s3_clk_src.clkr, + [GCC_QUPV3_WRAP0_S4_CLK] = &gcc_qupv3_wrap0_s4_clk.clkr, + [GCC_QUPV3_WRAP0_S4_CLK_SRC] = &gcc_qupv3_wrap0_s4_clk_src.clkr, + [GCC_QUPV3_WRAP0_S5_CLK] = &gcc_qupv3_wrap0_s5_clk.clkr, + [GCC_QUPV3_WRAP0_S5_CLK_SRC] = &gcc_qupv3_wrap0_s5_clk_src.clkr, + [GCC_QUPV3_WRAP0_S6_CLK] = &gcc_qupv3_wrap0_s6_clk.clkr, + [GCC_QUPV3_WRAP0_S6_CLK_SRC] = &gcc_qupv3_wrap0_s6_clk_src.clkr, + [GCC_QUPV3_WRAP0_S7_CLK] = &gcc_qupv3_wrap0_s7_clk.clkr, + [GCC_QUPV3_WRAP0_S7_CLK_SRC] = &gcc_qupv3_wrap0_s7_clk_src.clkr, + [GCC_QUPV3_WRAP0_S8_CLK] = &gcc_qupv3_wrap0_s8_clk.clkr, + [GCC_QUPV3_WRAP0_S8_CLK_SRC] = &gcc_qupv3_wrap0_s8_clk_src.clkr, + [GCC_QUPV3_WRAP0_S9_CLK] = &gcc_qupv3_wrap0_s9_clk.clkr, + [GCC_QUPV3_WRAP0_S9_CLK_SRC] = &gcc_qupv3_wrap0_s9_clk_src.clkr, + [GCC_QUPV3_WRAP_0_M_AHB_CLK] = &gcc_qupv3_wrap_0_m_ahb_clk.clkr, + [GCC_QUPV3_WRAP_0_S_AHB_CLK] = &gcc_qupv3_wrap_0_s_ahb_clk.clkr, + [GCC_SDCC1_AHB_CLK] = &gcc_sdcc1_ahb_clk.clkr, + [GCC_SDCC1_APPS_CLK] = &gcc_sdcc1_apps_clk.clkr, + [GCC_SDCC1_APPS_CLK_SRC] = &gcc_sdcc1_apps_clk_src.clkr, + [GCC_SDCC1_ICE_CORE_CLK] = &gcc_sdcc1_ice_core_clk.clkr, + [GCC_SDCC1_ICE_CORE_CLK_SRC] = &gcc_sdcc1_ice_core_clk_src.clkr, + [GCC_SDCC2_AHB_CLK] = &gcc_sdcc2_ahb_clk.clkr, + [GCC_SDCC2_APPS_CLK] = &gcc_sdcc2_apps_clk.clkr, + [GCC_SDCC2_APPS_CLK_SRC] = &gcc_sdcc2_apps_clk_src.clkr, + [GCC_SYS_NOC_USB2_PRIM_AXI_CLK] = &gcc_sys_noc_usb2_prim_axi_clk.clkr, + [GCC_SYS_NOC_USB3_PRIM_AXI_CLK] = &gcc_sys_noc_usb3_prim_axi_clk.clkr, + [GCC_TSCSS_AHB_CLK] = &gcc_tscss_ahb_clk.clkr, + [GCC_TSCSS_CLK_SRC] = &gcc_tscss_clk_src.clkr, + [GCC_TSCSS_CNTR_CLK] = &gcc_tscss_cntr_clk.clkr, + [GCC_TSCSS_ETU_CLK] = &gcc_tscss_etu_clk.clkr, + [GCC_UFS_CLKREF_EN] = &gcc_ufs_clkref_en.clkr, + [GCC_USB20_MASTER_CLK] = &gcc_usb20_master_clk.clkr, + [GCC_USB20_MASTER_CLK_SRC] = &gcc_usb20_master_clk_src.clkr, + [GCC_USB20_MOCK_UTMI_CLK] = &gcc_usb20_mock_utmi_clk.clkr, + [GCC_USB20_MOCK_UTMI_CLK_SRC] = &gcc_usb20_mock_utmi_clk_src.clkr, + [GCC_USB20_MOCK_UTMI_POSTDIV_CLK_SRC] = &gcc_usb20_mock_utmi_postdiv_clk_src.clkr, + [GCC_USB20_SLEEP_CLK] = &gcc_usb20_sleep_clk.clkr, + [GCC_USB30_PRIM_MASTER_CLK] = &gcc_usb30_prim_master_clk.clkr, + [GCC_USB30_PRIM_MASTER_CLK_SRC] = &gcc_usb30_prim_master_clk_src.clkr, + [GCC_USB30_PRIM_MOCK_UTMI_CLK] = &gcc_usb30_prim_mock_utmi_clk.clkr, + [GCC_USB30_PRIM_MOCK_UTMI_CLK_SRC] = &gcc_usb30_prim_mock_utmi_clk_src.clkr, + [GCC_USB30_PRIM_MOCK_UTMI_POSTDIV_CLK_SRC] = &gcc_usb30_prim_mock_utmi_postdiv_clk_src.clkr, + [GCC_USB30_PRIM_SLEEP_CLK] = &gcc_usb30_prim_sleep_clk.clkr, + [GCC_USB3_PRIM_CLKREF_EN] = &gcc_usb3_prim_clkref_en.clkr, + [GCC_USB3_PRIM_PHY_AUX_CLK_SRC] = &gcc_usb3_prim_phy_aux_clk_src.clkr, + [GCC_USB3_PRIM_PHY_COM_AUX_CLK] = &gcc_usb3_prim_phy_com_aux_clk.clkr, + [GCC_USB3_PRIM_PHY_PIPE_CLK] = &gcc_usb3_prim_phy_pipe_clk.clkr, + [GCC_USB3_PRIM_PHY_PIPE_CLK_SRC] = &gcc_usb3_prim_phy_pipe_clk_src.clkr, + [GCC_VCODEC0_AXI_CLK] = &gcc_vcodec0_axi_clk.clkr, + [GCC_VENUS_AHB_CLK] = &gcc_venus_ahb_clk.clkr, + [GCC_VENUS_CTL_AXI_CLK] = &gcc_venus_ctl_axi_clk.clkr, + [GCC_VIDEO_AXI0_CLK] = &gcc_video_axi0_clk.clkr, + [GCC_VIDEO_THROTTLE_CORE_CLK] = &gcc_video_throttle_core_clk.clkr, + [GCC_VIDEO_VCODEC0_SYS_CLK] = &gcc_video_vcodec0_sys_clk.clkr, + [GCC_VIDEO_VENUS_CLK_SRC] = &gcc_video_venus_clk_src.clkr, + [GCC_VIDEO_VENUS_CTL_CLK] = &gcc_video_venus_ctl_clk.clkr, + [GPLL0] = &gpll0.clkr, + [GPLL0_OUT_AUX2] = &gpll0_out_aux2.clkr, + [GPLL10] = &gpll10.clkr, + [GPLL11] = &gpll11.clkr, + [GPLL12] = &gpll12.clkr, + [GPLL12_OUT_AUX2] = &gpll12_out_aux2.clkr, + [GPLL3] = &gpll3.clkr, + [GPLL3_OUT_MAIN] = &gpll3_out_main.clkr, + [GPLL4] = &gpll4.clkr, + [GPLL5] = &gpll5.clkr, + [GPLL6] = &gpll6.clkr, + [GPLL6_OUT_MAIN] = &gpll6_out_main.clkr, + [GPLL7] = &gpll7.clkr, + [GPLL8] = &gpll8.clkr, + [GPLL8_OUT_MAIN] = &gpll8_out_main.clkr, + [GPLL9] = &gpll9.clkr, + [GPLL9_OUT_MAIN] = &gpll9_out_main.clkr, +}; + +static struct gdsc *gcc_shikra_gdscs[] = { + [GCC_CAMSS_TOP_GDSC] = &gcc_camss_top_gdsc, + [GCC_EMAC0_GDSC] = &gcc_emac0_gdsc, + [GCC_EMAC1_GDSC] = &gcc_emac1_gdsc, + [GCC_PCIE_GDSC] = &gcc_pcie_gdsc, + [GCC_USB20_GDSC] = &gcc_usb20_gdsc, + [GCC_USB30_PRIM_GDSC] = &gcc_usb30_prim_gdsc, + [GCC_VCODEC0_GDSC] = &gcc_vcodec0_gdsc, + [GCC_VENUS_GDSC] = &gcc_venus_gdsc, +}; + +static const struct qcom_reset_map gcc_shikra_resets[] = { + [GCC_CAMSS_OPE_BCR] = { 0x55000 }, + [GCC_CAMSS_TFE_BCR] = { 0x52000 }, + [GCC_CAMSS_TOP_BCR] = { 0x58000 }, + [GCC_EMAC0_BCR] = { 0xad000 }, + [GCC_EMAC1_BCR] = { 0xae000 }, + [GCC_GPU_BCR] = { 0x36000 }, + [GCC_MMSS_BCR] = { 0x17000 }, + [GCC_PCIE_BCR] = { 0xaf000 }, + [GCC_PCIE_PHY_BCR] = { 0xb1000 }, + [GCC_PDM_BCR] = { 0x20000 }, + [GCC_QUPV3_WRAPPER_0_BCR] = { 0x1f000 }, + [GCC_QUSB2PHY_PRIM_BCR] = { 0x1c000 }, + [GCC_QUSB2PHY_SEC_BCR] = { 0x1c004 }, + [GCC_SDCC1_BCR] = { 0x38000 }, + [GCC_SDCC2_BCR] = { 0x1e000 }, + [GCC_TSCSS_BCR] = { 0xac000 }, + [GCC_USB20_BCR] = { 0xb0000 }, + [GCC_USB30_PRIM_BCR] = { 0x1a000 }, + [GCC_USB3PHY_PHY_PRIM_SP0_BCR] = { 0x1b008 }, + [GCC_USB3_DP_PHY_PRIM_BCR] = { 0x1b020 }, + [GCC_USB3_PHY_PRIM_SP0_BCR] = { 0x1b000 }, + [GCC_USB_PHY_CFG_AHB2PHY_BCR] = { 0x1d000 }, + [GCC_VCODEC0_BCR] = { 0x6d034 }, + [GCC_VENUS_BCR] = { 0x6d018 }, + [GCC_VIDEO_INTERFACE_BCR] = { 0x6e000 }, +}; + +static struct clk_alpha_pll *gcc_shikra_plls[] = { + &gpll10, + &gpll11, + &gpll8, + &gpll9, +}; + +static const u32 gcc_shikra_critical_cbcrs[] = { + 0x17008, /* GCC_CAMERA_AHB_CLK */ + 0x17028, /* GCC_CAMERA_XO_CLK */ + 0x1700c, /* GCC_DISP_AHB_CLK */ + 0x1702c, /* GCC_DISP_XO_CLK */ + 0x36004, /* GCC_GPU_CFG_AHB_CLK */ + 0x36100, /* GCC_GPU_IREF_CLK */ + 0x3a00c, /* GCC_LPASS_CONFIG_CLK */ + 0x3a008, /* GCC_LPASS_CORE_AXIM_CLK */ + 0x79004, /* GCC_SYS_NOC_CPUSS_AHB_CLK */ + 0x17004, /* GCC_VIDEO_AHB_CLK */ + 0x17024, /* GCC_VIDEO_XO_CLK */ +}; + +static const struct clk_rcg_dfs_data gcc_shikra_dfs_clocks[] = { + DEFINE_RCG_DFS(gcc_qupv3_wrap0_s0_clk_src), + DEFINE_RCG_DFS(gcc_qupv3_wrap0_s1_clk_src), + DEFINE_RCG_DFS(gcc_qupv3_wrap0_s2_clk_src), + DEFINE_RCG_DFS(gcc_qupv3_wrap0_s3_clk_src), + DEFINE_RCG_DFS(gcc_qupv3_wrap0_s4_clk_src), + DEFINE_RCG_DFS(gcc_qupv3_wrap0_s5_clk_src), + DEFINE_RCG_DFS(gcc_qupv3_wrap0_s6_clk_src), + DEFINE_RCG_DFS(gcc_qupv3_wrap0_s7_clk_src), + DEFINE_RCG_DFS(gcc_qupv3_wrap0_s8_clk_src), + DEFINE_RCG_DFS(gcc_qupv3_wrap0_s9_clk_src), +}; + +static const struct regmap_config gcc_shikra_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0xc7000, + .fast_io = true, +}; + +static const struct qcom_cc_driver_data gcc_shikra_driver_data = { + .alpha_plls = gcc_shikra_plls, + .num_alpha_plls = ARRAY_SIZE(gcc_shikra_plls), + .clk_cbcrs = gcc_shikra_critical_cbcrs, + .num_clk_cbcrs = ARRAY_SIZE(gcc_shikra_critical_cbcrs), + .dfs_rcgs = gcc_shikra_dfs_clocks, + .num_dfs_rcgs = ARRAY_SIZE(gcc_shikra_dfs_clocks), +}; + +static const struct qcom_cc_desc gcc_shikra_desc = { + .config = &gcc_shikra_regmap_config, + .clks = gcc_shikra_clocks, + .num_clks = ARRAY_SIZE(gcc_shikra_clocks), + .resets = gcc_shikra_resets, + .num_resets = ARRAY_SIZE(gcc_shikra_resets), + .gdscs = gcc_shikra_gdscs, + .num_gdscs = ARRAY_SIZE(gcc_shikra_gdscs), + .driver_data = &gcc_shikra_driver_data, +}; + +static const struct of_device_id gcc_shikra_match_table[] = { + { .compatible = "qcom,shikra-gcc" }, + { } +}; +MODULE_DEVICE_TABLE(of, gcc_shikra_match_table); + +static int gcc_shikra_probe(struct platform_device *pdev) +{ + return qcom_cc_probe(pdev, &gcc_shikra_desc); +} + +static struct platform_driver gcc_shikra_driver = { + .probe = gcc_shikra_probe, + .driver = { + .name = "gcc-shikra", + .of_match_table = gcc_shikra_match_table, + }, +}; + +static int __init gcc_shikra_init(void) +{ + return platform_driver_register(&gcc_shikra_driver); +} +subsys_initcall(gcc_shikra_init); + +static void __exit gcc_shikra_exit(void) +{ + platform_driver_unregister(&gcc_shikra_driver); +} +module_exit(gcc_shikra_exit); + +MODULE_DESCRIPTION("QTI GCC Shikra Driver"); +MODULE_LICENSE("GPL"); From f3d8db39d91e52f5db14047547b3eb3467446f78 Mon Sep 17 00:00:00 2001 From: Kathiravan Thirumoorthy Date: Wed, 10 Jun 2026 19:49:17 +0530 Subject: [PATCH 0109/1328] clk: qcom: gcc-ipq5424: drop the CLK_IGNORE_UNUSED flag Since PCIe and USB are enabled and consume GPLL4, there is no need to keep the CLK_IGNORE_UNUSED flag. Drop it. Signed-off-by: Kathiravan Thirumoorthy Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20260610-ipq5424_drop_clk_ignore_unused-v2-1-15240af073d6@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gcc-ipq5424.c | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/drivers/clk/qcom/gcc-ipq5424.c b/drivers/clk/qcom/gcc-ipq5424.c index 35af6ffeeb85..3ddd4b3aedea 100644 --- a/drivers/clk/qcom/gcc-ipq5424.c +++ b/drivers/clk/qcom/gcc-ipq5424.c @@ -139,17 +139,6 @@ static struct clk_alpha_pll gpll4 = { .parent_data = &gcc_parent_data_xo, .num_parents = 1, .ops = &clk_alpha_pll_ops, - /* - * There are no consumers for this GPLL in kernel yet, - * (will be added soon), so the clock framework - * disables this source. But some of the clocks - * initialized by boot loaders uses this source. So we - * need to keep this clock ON. Add the - * CLK_IGNORE_UNUSED flag so the clock will not be - * disabled. Once the consumer in kernel is added, we - * can get rid of this flag. - */ - .flags = CLK_IGNORE_UNUSED, }, }, }; From 11b170abe4d324cac0d15a410282d1ec2b6bafa0 Mon Sep 17 00:00:00 2001 From: Loic Poulain Date: Mon, 29 Jun 2026 21:22:55 +0200 Subject: [PATCH 0110/1328] clk: qcom: gcc-qcs8300: Use retention for PCIe power domains As the PCIe host controller driver does not yet support dealing with the loss of state during suspend, use retention for relevant GDSCs. Fix the PCIe link not surviving upon resume, and GDSC error: gcc_pcie_0_gdsc status stuck at 'off' Fixes: 95eeb2ffce73 ("clk: qcom: Add support for Global Clock Controller on QCS8300") Signed-off-by: Loic Poulain Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260629-monza-suspend-v1-1-b601d8a2f2f8@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gcc-qcs8300.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/clk/qcom/gcc-qcs8300.c b/drivers/clk/qcom/gcc-qcs8300.c index 80831c7dea3b..009672b75fb9 100644 --- a/drivers/clk/qcom/gcc-qcs8300.c +++ b/drivers/clk/qcom/gcc-qcs8300.c @@ -3268,7 +3268,7 @@ static struct gdsc gcc_pcie_0_gdsc = { .pd = { .name = "gcc_pcie_0_gdsc", }, - .pwrsts = PWRSTS_OFF_ON, + .pwrsts = PWRSTS_RET_ON, .flags = VOTABLE | RETAIN_FF_ENABLE | POLL_CFG_GDSCR, }; @@ -3282,7 +3282,7 @@ static struct gdsc gcc_pcie_1_gdsc = { .pd = { .name = "gcc_pcie_1_gdsc", }, - .pwrsts = PWRSTS_OFF_ON, + .pwrsts = PWRSTS_RET_ON, .flags = VOTABLE | RETAIN_FF_ENABLE | POLL_CFG_GDSCR, }; From d8638610e0c9ebab2800b7ad6c2c2a3737090da9 Mon Sep 17 00:00:00 2001 From: Loic Poulain Date: Mon, 29 Jun 2026 21:22:56 +0200 Subject: [PATCH 0111/1328] clk: qcom: gcc-qcs8300: Use retention for USB power domains The USB subsystem does not expect to lose its state on suspend: xhci-hcd xhci-hcd.1.auto: xHC error in resume, USBSTS 0x401, Reinit usb usb1: root hub lost power or was reset To maintain state during suspend, the relevant GDSCs need to stay in retention mode, like they do on other similar SoCs. Change the mode to PWRSTS_RET_ON to fix. Fixes: 95eeb2ffce73 ("clk: qcom: Add support for Global Clock Controller on QCS8300") Signed-off-by: Loic Poulain Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260629-monza-suspend-v1-2-b601d8a2f2f8@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gcc-qcs8300.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/clk/qcom/gcc-qcs8300.c b/drivers/clk/qcom/gcc-qcs8300.c index 009672b75fb9..fe0632687f9f 100644 --- a/drivers/clk/qcom/gcc-qcs8300.c +++ b/drivers/clk/qcom/gcc-qcs8300.c @@ -3306,7 +3306,7 @@ static struct gdsc gcc_usb20_prim_gdsc = { .pd = { .name = "gcc_usb20_prim_gdsc", }, - .pwrsts = PWRSTS_OFF_ON, + .pwrsts = PWRSTS_RET_ON, .flags = RETAIN_FF_ENABLE | POLL_CFG_GDSCR, }; @@ -3318,7 +3318,7 @@ static struct gdsc gcc_usb30_prim_gdsc = { .pd = { .name = "gcc_usb30_prim_gdsc", }, - .pwrsts = PWRSTS_OFF_ON, + .pwrsts = PWRSTS_RET_ON, .flags = RETAIN_FF_ENABLE | POLL_CFG_GDSCR, }; From dd04ad1cdabcad51e34b74b4e91b9aeb7180d05d Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 26 Jun 2026 13:38:22 +0300 Subject: [PATCH 0112/1328] thermal/drivers/rcar: Fix error checking in probe() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This code accidentally calls thermal_zone_device_enable() before checking whether thermal_zone_device_register_with_trips() failed. Move the call until later to avoid an error pointer dereference of "priv->zone". The driver works differently depending on if we are using OF thermal or not. We use thermal_add_hwmon_sysfs() if we are using OF thermal and call thermal_zone_device_enable() if not. We can share same error check for if either of these fail. Moving the thermal_zone_device_enable() call is a bit cleaner as well. The original code used a three step process to cleanup: 1. Call thermal_zone_device_unregister() to cleanup. 2. Set priv->zone to an error pointer to preserve the error code. 3. Set priv->zone to NULL to avoid a second call to thermal_zone_device_unregister() in the rcar_thermal_remove() function. Now we can just do a direct goto error_unregister and rcar_thermal_remove() handles the cleanup properly. Fixes: bbcf90c0646a ("thermal: Explicitly enable non-changing thermal zone devices") Reviewed-by: Geert Uytterhoeven Reviewed-by: Niklas Söderlund Signed-off-by: Dan Carpenter Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/aj5WnseULiwgmlWv@stanley.mountain --- drivers/thermal/renesas/rcar_thermal.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/drivers/thermal/renesas/rcar_thermal.c b/drivers/thermal/renesas/rcar_thermal.c index 6e5dcac5d47a..fd686da9252e 100644 --- a/drivers/thermal/renesas/rcar_thermal.c +++ b/drivers/thermal/renesas/rcar_thermal.c @@ -492,12 +492,6 @@ static int rcar_thermal_probe(struct platform_device *pdev) "rcar_thermal", trips, ARRAY_SIZE(trips), priv, &rcar_thermal_zone_ops, NULL, 0, idle); - - ret = thermal_zone_device_enable(priv->zone); - if (ret) { - thermal_zone_device_unregister(priv->zone); - priv->zone = ERR_PTR(ret); - } } if (IS_ERR(priv->zone)) { dev_err(dev, "can't register thermal zone\n"); @@ -506,11 +500,12 @@ static int rcar_thermal_probe(struct platform_device *pdev) goto error_unregister; } - if (chip->use_of_thermal) { + if (chip->use_of_thermal) ret = thermal_add_hwmon_sysfs(priv->zone); - if (ret) - goto error_unregister; - } + else + ret = thermal_zone_device_enable(priv->zone); + if (ret) + goto error_unregister; rcar_thermal_irq_enable(priv); From cbe31d5ce49873a2a5a3ca5decb4935672396e77 Mon Sep 17 00:00:00 2001 From: "Bryan B. Lima" Date: Tue, 30 Jun 2026 22:07:18 -0300 Subject: [PATCH 0113/1328] thermal/drivers/armada: Use bitfield and bitmask macros Replace manual bitfield manipulations with FIELD_MODIFY() and define constants with BIT() and GENMASK() to make code more readable. Also, remove offset and shift constants for clarity in use of bitfield macros. Signed-off-by: Bryan B. Lima Co-developed-by: Gustavo S. Correa Signed-off-by: Gustavo S. Correa Signed-off-by: Daniel Lezcano Reviewed-by: Miquel Raynal Link: https://patch.msgid.link/20260701010802.99029-1-bblima@usp.br --- drivers/thermal/armada_thermal.c | 98 ++++++++++++++------------------ 1 file changed, 43 insertions(+), 55 deletions(-) diff --git a/drivers/thermal/armada_thermal.c b/drivers/thermal/armada_thermal.c index c2fbdb534f61..be6240984002 100644 --- a/drivers/thermal/armada_thermal.c +++ b/drivers/thermal/armada_thermal.c @@ -20,41 +20,35 @@ #include /* Thermal Manager Control and Status Register */ -#define PMU_TDC0_SW_RST_MASK (0x1 << 1) -#define PMU_TM_DISABLE_OFFS 0 -#define PMU_TM_DISABLE_MASK (0x1 << PMU_TM_DISABLE_OFFS) -#define PMU_TDC0_REF_CAL_CNT_OFFS 11 -#define PMU_TDC0_REF_CAL_CNT_MASK (0x1ff << PMU_TDC0_REF_CAL_CNT_OFFS) -#define PMU_TDC0_OTF_CAL_MASK (0x1 << 30) -#define PMU_TDC0_START_CAL_MASK (0x1 << 25) +#define PMU_TDC0_SW_RST_MASK BIT(1) +#define PMU_TM_DISABLE_MASK BIT(0) +#define PMU_TDC0_REF_CAL_CNT_MASK GENMASK(19, 11) +#define PMU_TDC0_OTF_CAL_MASK BIT(30) +#define PMU_TDC0_START_CAL_MASK BIT(25) -#define A375_UNIT_CONTROL_SHIFT 27 -#define A375_UNIT_CONTROL_MASK 0x7 +#define A375_UNIT_CONTROL_MASK GENMASK(29, 27) #define A375_READOUT_INVERT BIT(15) #define A375_HW_RESETn BIT(8) /* Errata fields */ -#define CONTROL0_TSEN_TC_TRIM_MASK 0x7 +#define CONTROL0_TSEN_TC_TRIM_MASK GENMASK(2, 0) #define CONTROL0_TSEN_TC_TRIM_VAL 0x3 #define CONTROL0_TSEN_START BIT(0) #define CONTROL0_TSEN_RESET BIT(1) #define CONTROL0_TSEN_ENABLE BIT(2) #define CONTROL0_TSEN_AVG_BYPASS BIT(6) -#define CONTROL0_TSEN_CHAN_SHIFT 13 -#define CONTROL0_TSEN_CHAN_MASK 0xF -#define CONTROL0_TSEN_OSR_SHIFT 24 -#define CONTROL0_TSEN_OSR_MAX 0x3 -#define CONTROL0_TSEN_MODE_SHIFT 30 +#define CONTROL0_TSEN_CHAN_MASK GENMASK(16, 13) +#define CONTROL0_TSEN_OSR_MASK GENMASK(25, 24) +#define CONTROL0_TSEN_OSR_MAX FIELD_MAX(CONTROL0_TSEN_OSR_MASK) +#define CONTROL0_TSEN_MODE_MASK GENMASK(31, 30) #define CONTROL0_TSEN_MODE_EXTERNAL 0x2 -#define CONTROL0_TSEN_MODE_MASK 0x3 -#define CONTROL1_TSEN_AVG_MASK 0x7 +#define CONTROL1_TSEN_AVG_MASK GENMASK(2, 0) #define CONTROL1_EXT_TSEN_SW_RESET BIT(7) #define CONTROL1_EXT_TSEN_HW_RESETn BIT(8) #define CONTROL1_TSEN_INT_EN BIT(25) -#define CONTROL1_TSEN_SELECT_OFF 21 -#define CONTROL1_TSEN_SELECT_MASK 0x3 +#define CONTROL1_TSEN_SELECT_MASK GENMASK(22, 21) #define STATUS_POLL_PERIOD_US 1000 #define STATUS_POLL_TIMEOUT_US 100000 @@ -140,23 +134,21 @@ static void armadaxp_init(struct platform_device *pdev, u32 reg; regmap_read(priv->syscon, data->syscon_control1_off, ®); - reg |= PMU_TDC0_OTF_CAL_MASK; + FIELD_MODIFY(PMU_TDC0_OTF_CAL_MASK, ®, 1); /* Reference calibration value */ - reg &= ~PMU_TDC0_REF_CAL_CNT_MASK; - reg |= (0xf1 << PMU_TDC0_REF_CAL_CNT_OFFS); + FIELD_MODIFY(PMU_TDC0_REF_CAL_CNT_MASK, ®, 0xf1); /* Reset the sensor */ - reg |= PMU_TDC0_SW_RST_MASK; - + FIELD_MODIFY(PMU_TDC0_SW_RST_MASK, ®, 1); regmap_write(priv->syscon, data->syscon_control1_off, reg); - reg &= ~PMU_TDC0_SW_RST_MASK; + FIELD_MODIFY(PMU_TDC0_SW_RST_MASK, ®, 0); regmap_write(priv->syscon, data->syscon_control1_off, reg); /* Enable the sensor */ regmap_read(priv->syscon, data->syscon_status_off, ®); - reg &= ~PMU_TM_DISABLE_MASK; + FIELD_MODIFY(PMU_TM_DISABLE_MASK, ®, 0); regmap_write(priv->syscon, data->syscon_status_off, reg); } @@ -167,14 +159,13 @@ static void armada370_init(struct platform_device *pdev, u32 reg; regmap_read(priv->syscon, data->syscon_control1_off, ®); - reg |= PMU_TDC0_OTF_CAL_MASK; + FIELD_MODIFY(PMU_TDC0_OTF_CAL_MASK, ®, 1); /* Reference calibration value */ - reg &= ~PMU_TDC0_REF_CAL_CNT_MASK; - reg |= (0xf1 << PMU_TDC0_REF_CAL_CNT_OFFS); + FIELD_MODIFY(PMU_TDC0_REF_CAL_CNT_MASK, ®, 0xf1); /* Reset the sensor */ - reg &= ~PMU_TDC0_START_CAL_MASK; + FIELD_MODIFY(PMU_TDC0_START_CAL_MASK, ®, 0); regmap_write(priv->syscon, data->syscon_control1_off, reg); @@ -188,14 +179,14 @@ static void armada375_init(struct platform_device *pdev, u32 reg; regmap_read(priv->syscon, data->syscon_control1_off, ®); - reg &= ~(A375_UNIT_CONTROL_MASK << A375_UNIT_CONTROL_SHIFT); - reg &= ~A375_READOUT_INVERT; - reg &= ~A375_HW_RESETn; + FIELD_MODIFY(A375_UNIT_CONTROL_MASK, ®, 0); + FIELD_MODIFY(A375_READOUT_INVERT, ®, 0); + FIELD_MODIFY(A375_HW_RESETn, ®, 0); regmap_write(priv->syscon, data->syscon_control1_off, reg); msleep(20); - reg |= A375_HW_RESETn; + FIELD_MODIFY(A375_HW_RESETn, ®, 1); regmap_write(priv->syscon, data->syscon_control1_off, reg); msleep(50); @@ -220,14 +211,13 @@ static void armada380_init(struct platform_device *pdev, /* Disable the HW/SW reset */ regmap_read(priv->syscon, data->syscon_control1_off, ®); - reg |= CONTROL1_EXT_TSEN_HW_RESETn; - reg &= ~CONTROL1_EXT_TSEN_SW_RESET; + FIELD_MODIFY(CONTROL1_EXT_TSEN_HW_RESETn, ®, 1); + FIELD_MODIFY(CONTROL1_EXT_TSEN_SW_RESET, ®, 0); regmap_write(priv->syscon, data->syscon_control1_off, reg); /* Set Tsen Tc Trim to correct default value (errata #132698) */ regmap_read(priv->syscon, data->syscon_control0_off, ®); - reg &= ~CONTROL0_TSEN_TC_TRIM_MASK; - reg |= CONTROL0_TSEN_TC_TRIM_VAL; + FIELD_MODIFY(CONTROL0_TSEN_TC_TRIM_MASK, ®, CONTROL0_TSEN_TC_TRIM_VAL); regmap_write(priv->syscon, data->syscon_control0_off, reg); } @@ -238,14 +228,15 @@ static void armada_ap80x_init(struct platform_device *pdev, u32 reg; regmap_read(priv->syscon, data->syscon_control0_off, ®); - reg &= ~CONTROL0_TSEN_RESET; - reg |= CONTROL0_TSEN_START | CONTROL0_TSEN_ENABLE; + FIELD_MODIFY(CONTROL0_TSEN_RESET, ®, 0); + FIELD_MODIFY(CONTROL0_TSEN_START, ®, 1); + FIELD_MODIFY(CONTROL0_TSEN_ENABLE, ®, 1); /* Sample every ~2ms */ - reg |= CONTROL0_TSEN_OSR_MAX << CONTROL0_TSEN_OSR_SHIFT; + FIELD_MODIFY(CONTROL0_TSEN_OSR_MASK, ®, CONTROL0_TSEN_OSR_MAX); /* Enable average (2 samples by default) */ - reg &= ~CONTROL0_TSEN_AVG_BYPASS; + FIELD_MODIFY(CONTROL0_TSEN_AVG_BYPASS, ®, 0); regmap_write(priv->syscon, data->syscon_control0_off, reg); } @@ -260,13 +251,12 @@ static void armada_cp110_init(struct platform_device *pdev, /* Sample every ~2ms */ regmap_read(priv->syscon, data->syscon_control0_off, ®); - reg |= CONTROL0_TSEN_OSR_MAX << CONTROL0_TSEN_OSR_SHIFT; + FIELD_MODIFY(CONTROL0_TSEN_OSR_MASK, ®, CONTROL0_TSEN_OSR_MAX); regmap_write(priv->syscon, data->syscon_control0_off, reg); /* Average the output value over 2^1 = 2 samples */ regmap_read(priv->syscon, data->syscon_control1_off, ®); - reg &= ~CONTROL1_TSEN_AVG_MASK; - reg |= 1; + FIELD_MODIFY(CONTROL1_TSEN_AVG_MASK, ®, 1); regmap_write(priv->syscon, data->syscon_control1_off, reg); } @@ -313,7 +303,7 @@ armada_disable_overheat_interrupt(struct armada_thermal_priv *priv) u32 reg; regmap_read(priv->syscon, data->syscon_control1_off, ®); - reg &= ~CONTROL1_TSEN_INT_EN; + FIELD_MODIFY(CONTROL1_TSEN_INT_EN, ®, 0); regmap_write(priv->syscon, data->syscon_control1_off, reg); } @@ -331,20 +321,18 @@ static int armada_select_channel(struct armada_thermal_priv *priv, int channel) /* Stop the measurements */ regmap_read(priv->syscon, data->syscon_control0_off, &ctrl0); - ctrl0 &= ~CONTROL0_TSEN_START; + FIELD_MODIFY(CONTROL0_TSEN_START, &ctrl0, 0); regmap_write(priv->syscon, data->syscon_control0_off, ctrl0); - /* Reset the mode, internal sensor will be automatically selected */ - ctrl0 &= ~(CONTROL0_TSEN_MODE_MASK << CONTROL0_TSEN_MODE_SHIFT); - /* Other channels are external and should be selected accordingly */ if (channel) { /* Change the mode to external */ - ctrl0 |= CONTROL0_TSEN_MODE_EXTERNAL << - CONTROL0_TSEN_MODE_SHIFT; + FIELD_MODIFY(CONTROL0_TSEN_MODE_MASK, &ctrl0, CONTROL0_TSEN_MODE_EXTERNAL); /* Select the sensor */ - ctrl0 &= ~(CONTROL0_TSEN_CHAN_MASK << CONTROL0_TSEN_CHAN_SHIFT); - ctrl0 |= (channel - 1) << CONTROL0_TSEN_CHAN_SHIFT; + FIELD_MODIFY(CONTROL0_TSEN_CHAN_MASK, &ctrl0, channel - 1); + } else { + /* Reset the mode, internal sensor will be automatically selected */ + FIELD_MODIFY(CONTROL0_TSEN_MODE_MASK, &ctrl0, 0); } /* Actually set the mode/channel */ @@ -352,7 +340,7 @@ static int armada_select_channel(struct armada_thermal_priv *priv, int channel) priv->current_channel = channel; /* Re-start the measurements */ - ctrl0 |= CONTROL0_TSEN_START; + FIELD_MODIFY(CONTROL0_TSEN_START, &ctrl0, 1); regmap_write(priv->syscon, data->syscon_control0_off, ctrl0); /* From 65a38a28a0b04af19a5e1fbf3869051412eeac96 Mon Sep 17 00:00:00 2001 From: Cosmo Chou Date: Fri, 3 Jul 2026 09:42:01 +0800 Subject: [PATCH 0114/1328] leds: pca9532: Fix inverted GPIO output polarity The pca9532_gpio_set_value() function incorrectly mapped the requested value to PCA9532_ON and PCA9532_OFF, inverting the GPIO output polarity. A requested logical high (val=1) incorrectly enabled the LED output driver, which on this open-drain device pulls the pin low, while a requested logical low (val=0) released the pin. Correct the mapping so that val=1 yields PCA9532_OFF (pin released / high-impedance) and val=0 yields PCA9532_ON (pin driven low). pca9532_gpio_direction_input() is also updated to pass val=1 to pca9532_gpio_set_value() to align with the corrected polarity mapping, ensuring the pin remains not driven when configured as an input. Fixes: 3c1ab50d0a31 ("drivers/leds/leds-pca9532.c: add gpio capability") Signed-off-by: Cosmo Chou Reviewed-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260703014201.69829-1-chou.cosmo@gmail.com Signed-off-by: Lee Jones --- drivers/leds/leds-pca9532.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/leds/leds-pca9532.c b/drivers/leds/leds-pca9532.c index f3bf59495b68..2d37e00e459d 100644 --- a/drivers/leds/leds-pca9532.c +++ b/drivers/leds/leds-pca9532.c @@ -327,9 +327,9 @@ static int pca9532_gpio_set_value(struct gpio_chip *gc, unsigned int offset, struct pca9532_led *led = &data->leds[offset]; if (val) - led->state = PCA9532_ON; - else led->state = PCA9532_OFF; + else + led->state = PCA9532_ON; pca9532_setled(led); @@ -349,7 +349,7 @@ static int pca9532_gpio_get_value(struct gpio_chip *gc, unsigned offset) static int pca9532_gpio_direction_input(struct gpio_chip *gc, unsigned offset) { /* To use as input ensure pin is not driven */ - pca9532_gpio_set_value(gc, offset, 0); + pca9532_gpio_set_value(gc, offset, 1); return 0; } From 9c019a8cb95d820e0bd03e75cfbad2c5b13941b7 Mon Sep 17 00:00:00 2001 From: Manuel Fombuena Date: Thu, 2 Jul 2026 23:27:22 +0100 Subject: [PATCH 0115/1328] leds: st1202: Stop pattern sequence before reprogramming The LED1202 datasheet (section 4.8) states that modifications to the Pattern Sequence Repetition register (PAT_REP) and pattern duration registers are only applied after the sequence has completed or been stopped. When the device is running in infinite loop mode (PAT_REP = 0xFF) the sequence never completes on its own, so these writes are silently ignored by the hardware. Neither pattern_clear() nor pattern_set() stop the running sequence before modifying pattern registers, causing any subsequent pattern reprogramming to have no effect when the previous pattern was set to infinite repeat. Fix this by clearing PATS in the Configuration register before touching any pattern registers in both functions, ensuring the hardware accepts the new values immediately. Note that the LED1202 has a single global pattern sequencer shared by all channels: PATS, PATSR, the duration registers, and PAT_REP are chip-wide. Stopping the sequencer in pattern_clear() therefore halts any pattern running on other channels. This is an inherent hardware constraint; pattern_set() restarts the sequencer when a new pattern is programmed. Fixes: 259230378c65 ("leds: Add LED1202 I2C driver") Signed-off-by: Manuel Fombuena Assisted-by: Claude:claude-sonnet-4-6 Link: https://patch.msgid.link/GV1PR08MB84978D0F499774773C7DA1FCC5F52@GV1PR08MB8497.eurprd08.prod.outlook.com Signed-off-by: Lee Jones --- drivers/leds/leds-st1202.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/leds/leds-st1202.c b/drivers/leds/leds-st1202.c index 7f68d956f694..316ed8eb054f 100644 --- a/drivers/leds/leds-st1202.c +++ b/drivers/leds/leds-st1202.c @@ -200,6 +200,10 @@ static int st1202_led_pattern_clear(struct led_classdev *ldev) guard(mutex)(&chip->lock); + ret = st1202_write_reg(chip, ST1202_CONFIG_REG, ST1202_CONFIG_REG_SHFT); + if (ret != 0) + return ret; + for (int patt = 0; patt < ST1202_MAX_PATTERNS; patt++) { ret = st1202_pwm_pattern_write(chip, led->led_num, patt, LED_OFF); if (ret != 0) @@ -226,6 +230,10 @@ static int st1202_led_pattern_set(struct led_classdev *ldev, guard(mutex)(&chip->lock); + ret = st1202_write_reg(chip, ST1202_CONFIG_REG, ST1202_CONFIG_REG_SHFT); + if (ret != 0) + return ret; + for (int patt = 0; patt < len; patt++) { if (pattern[patt].delta_t < ST1202_MILLIS_PATTERN_DUR_MIN || pattern[patt].delta_t > ST1202_MILLIS_PATTERN_DUR_MAX) From fd2529ba8fb44cd4b56f1069363b949644b42cec Mon Sep 17 00:00:00 2001 From: Manuel Fombuena Date: Thu, 2 Jul 2026 23:28:20 +0100 Subject: [PATCH 0116/1328] leds: st1202: Validate pattern input before stopping the sequence Input validation for pattern duration is performed inside the write loop, after the pattern sequence has already been stopped. If validation fails mid-loop the chip is left with the sequence stopped and partially written pattern data, with no recovery. Move all input validation before the mutex and before any hardware interaction, so an invalid input leaves the chip state unchanged. Signed-off-by: Manuel Fombuena Assisted-by: Claude:claude-sonnet-4-6 Link: https://patch.msgid.link/GV1PR08MB84975929B6ED7CDFBCEB7D76C5F52@GV1PR08MB8497.eurprd08.prod.outlook.com Signed-off-by: Lee Jones --- drivers/leds/leds-st1202.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/leds/leds-st1202.c b/drivers/leds/leds-st1202.c index 316ed8eb054f..a750c95ef3f8 100644 --- a/drivers/leds/leds-st1202.c +++ b/drivers/leds/leds-st1202.c @@ -228,6 +228,12 @@ static int st1202_led_pattern_set(struct led_classdev *ldev, if (len > ST1202_MAX_PATTERNS) return -EINVAL; + for (int patt = 0; patt < len; patt++) { + if (pattern[patt].delta_t < ST1202_MILLIS_PATTERN_DUR_MIN || + pattern[patt].delta_t > ST1202_MILLIS_PATTERN_DUR_MAX) + return -EINVAL; + } + guard(mutex)(&chip->lock); ret = st1202_write_reg(chip, ST1202_CONFIG_REG, ST1202_CONFIG_REG_SHFT); @@ -235,10 +241,6 @@ static int st1202_led_pattern_set(struct led_classdev *ldev, return ret; for (int patt = 0; patt < len; patt++) { - if (pattern[patt].delta_t < ST1202_MILLIS_PATTERN_DUR_MIN || - pattern[patt].delta_t > ST1202_MILLIS_PATTERN_DUR_MAX) - return -EINVAL; - ret = st1202_pwm_pattern_write(chip, led->led_num, patt, pattern[patt].brightness); if (ret != 0) return ret; From d32f8bdc2b417a3013e1316a54a0b314f973bbc1 Mon Sep 17 00:00:00 2001 From: Manuel Fombuena Date: Thu, 2 Jul 2026 23:29:39 +0100 Subject: [PATCH 0117/1328] leds: st1202: Fix pattern duration prescaler and pattern_clear skip marker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The PATy_DUR register encodes duration as N × 22.2 ms, with register value 0 reserved as a pattern skip indicator (§7.10). The driver incorrectly subtracted 1 from the register value: value / ST1202_MILLIS_PATTERN_DUR_MIN - 1 This caused two problems: - All programmed durations were off by one step (~22 ms too short). - Writing the minimum duration (22 ms) produced register value 0, silently skipping the pattern step instead of setting a 22 ms duration. The maximum duration constant was also wrong at 5660 ms. The 8-bit register saturates at 255, giving a maximum of 5610 ms (22 ms × 255). Values above 5653 ms were already producing a uint8_t overflow and writing 0 to the hardware. Fix the formula by removing the erroneous subtraction, and derive the maximum from the register width so the relationship is explicit. Update the documentation to reflect the correct maximum. This exposes a secondary issue: pattern_clear() was calling st1202_duration_pattern_write() with ST1202_MILLIS_PATTERN_DUR_MIN to reset unused slots, accidentally relying on the broken formula to produce register value 0. With the corrected formula, the same call writes 0x01 (22 ms), leaving unused slots as valid 22 ms zero-PWM steps and making the LED appear off for 7 × 22 ms out of every cycle. Write 0 directly to the duration registers in pattern_clear() so unused slots are always explicitly marked as skip, independently of the conversion formula. Fixes: 259230378c65 ("leds: Add LED1202 I2C driver") Signed-off-by: Manuel Fombuena Assisted-by: Claude:claude-sonnet-4-6 Link: https://patch.msgid.link/GV1PR08MB84971D3AF982F4F707A378F0C5F52@GV1PR08MB8497.eurprd08.prod.outlook.com Signed-off-by: Lee Jones --- Documentation/leds/leds-st1202.rst | 2 +- drivers/leds/leds-st1202.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Documentation/leds/leds-st1202.rst b/Documentation/leds/leds-st1202.rst index 1a09fbfcedcf..a2353549469e 100644 --- a/Documentation/leds/leds-st1202.rst +++ b/Documentation/leds/leds-st1202.rst @@ -17,7 +17,7 @@ To be compatible with the hardware pattern format, maximum 8 tuples of brightness (PWM) and duration must be written to hw_pattern. - Min pattern duration: 22 ms -- Max pattern duration: 5660 ms +- Max pattern duration: 5610 ms The format of the hardware pattern values should be: "brightness duration brightness duration ..." diff --git a/drivers/leds/leds-st1202.c b/drivers/leds/leds-st1202.c index a750c95ef3f8..b4adade49898 100644 --- a/drivers/leds/leds-st1202.c +++ b/drivers/leds/leds-st1202.c @@ -31,7 +31,7 @@ #define ST1202_ILED_REG0 0x09 #define ST1202_MAX_LEDS 12 #define ST1202_MAX_PATTERNS 8 -#define ST1202_MILLIS_PATTERN_DUR_MAX 5660 +#define ST1202_MILLIS_PATTERN_DUR_MAX (ST1202_MILLIS_PATTERN_DUR_MIN * U8_MAX) #define ST1202_MILLIS_PATTERN_DUR_MIN 22 #define ST1202_PATTERN_DUR 0x16 #define ST1202_PATTERN_PWM 0x1E @@ -85,7 +85,7 @@ static int st1202_write_reg(struct st1202_chip *chip, int reg, uint8_t val) static uint8_t st1202_prescalar_to_miliseconds(unsigned int value) { - return value / ST1202_MILLIS_PATTERN_DUR_MIN - 1; + return value / ST1202_MILLIS_PATTERN_DUR_MIN; } static int st1202_pwm_pattern_write(struct st1202_chip *chip, int led_num, @@ -209,7 +209,7 @@ static int st1202_led_pattern_clear(struct led_classdev *ldev) if (ret != 0) return ret; - ret = st1202_duration_pattern_write(chip, patt, ST1202_MILLIS_PATTERN_DUR_MIN); + ret = st1202_write_reg(chip, ST1202_PATTERN_DUR + patt, 0); if (ret != 0) return ret; } From d2ca0e2b6d6430f9c60bb2e0ee0b2b3dc4e5d86a Mon Sep 17 00:00:00 2001 From: Manuel Fombuena Date: Thu, 2 Jul 2026 23:30:50 +0100 Subject: [PATCH 0118/1328] leds: st1202: Set all pattern PWM slots to full after clearing pattern pattern_clear() sets all PWM registers for the channel to LED_OFF (0). In static mode (PATS=0), the LED output is ILED x Pattern0_PWM / 4095; with Pattern0 at zero the LED remains dark regardless of the ILED value. The LED1202 has a single global sequencer shared across all channels. If another channel starts the sequencer after this one has been cleared, the cleared channel runs through all 8 steps at zero duty cycle and stays dark regardless of ILED. Set all 8 PWM slots to ST1202_PATTERN_PWM_FULL so that ILED alone controls the channel brightness in both static and sequencer modes. Signed-off-by: Manuel Fombuena Assisted-by: Claude:claude-sonnet-4-6 Link: https://patch.msgid.link/GV1PR08MB849732C162CFE9E2C525AC16C5F52@GV1PR08MB8497.eurprd08.prod.outlook.com Signed-off-by: Lee Jones --- drivers/leds/leds-st1202.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/leds/leds-st1202.c b/drivers/leds/leds-st1202.c index b4adade49898..3203b3768ec5 100644 --- a/drivers/leds/leds-st1202.c +++ b/drivers/leds/leds-st1202.c @@ -35,6 +35,7 @@ #define ST1202_MILLIS_PATTERN_DUR_MIN 22 #define ST1202_PATTERN_DUR 0x16 #define ST1202_PATTERN_PWM 0x1E +#define ST1202_PATTERN_PWM_FULL 0x0FFF #define ST1202_PATTERN_REP 0x15 struct st1202_led { @@ -205,7 +206,7 @@ static int st1202_led_pattern_clear(struct led_classdev *ldev) return ret; for (int patt = 0; patt < ST1202_MAX_PATTERNS; patt++) { - ret = st1202_pwm_pattern_write(chip, led->led_num, patt, LED_OFF); + ret = st1202_pwm_pattern_write(chip, led->led_num, patt, ST1202_PATTERN_PWM_FULL); if (ret != 0) return ret; From dcc31246aaf0d330a3ba9a725f56c33e6d634caa Mon Sep 17 00:00:00 2001 From: Manuel Fombuena Date: Thu, 2 Jul 2026 23:31:55 +0100 Subject: [PATCH 0119/1328] leds: st1202: Fix spurious pattern sequence start in setup st1202_setup() writes PATS and PATSR to the Configuration register as its final step, which starts the hardware pattern sequencer during device probe before any patterns have been programmed. This causes the device to run a sequence with whatever values happen to be in the pattern registers at the time. Remove the write. The device reset at the start of setup restores all registers to their power-on defaults, leaving PATS and PATSR cleared. Fixes: 259230378c65 ("leds: Add LED1202 I2C driver") Signed-off-by: Manuel Fombuena Assisted-by: Claude:claude-sonnet-4-6 Link: https://patch.msgid.link/GV1PR08MB849724B0FF00255F4760FAE0C5F52@GV1PR08MB8497.eurprd08.prod.outlook.com Signed-off-by: Lee Jones --- drivers/leds/leds-st1202.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/drivers/leds/leds-st1202.c b/drivers/leds/leds-st1202.c index 3203b3768ec5..b8a571282900 100644 --- a/drivers/leds/leds-st1202.c +++ b/drivers/leds/leds-st1202.c @@ -333,11 +333,6 @@ static int st1202_setup(struct st1202_chip *chip) if (ret < 0) return ret; - ret = st1202_write_reg(chip, ST1202_CONFIG_REG, - ST1202_CONFIG_REG_PATS | ST1202_CONFIG_REG_PATSR); - if (ret < 0) - return ret; - return 0; } From 7cbe470366bdd43c7e8114fb2c4d74fa69093121 Mon Sep 17 00:00:00 2001 From: Manuel Fombuena Date: Thu, 2 Jul 2026 23:33:16 +0100 Subject: [PATCH 0120/1328] leds: st1202: Fix brightness having no effect while pattern mode is active Once a hardware pattern is running (PATS=1), writing to the brightness sysfs attribute only updates the ILED register. The visible output is ILED x Pattern_PWM / 4095, so the change has little effect and the LED never returns to steady static operation as the user expects. The LED1202 has a single global sequencer shared across all channels. Stopping it in brightness_set() to force static mode would halt running patterns on all other active LEDs. Instead, set all 8 PWM slots for the channel to ST1202_PATTERN_PWM_FULL before writing ILED. With every step at full duty cycle, the output is ILED x FULL / 4095 = ILED regardless of the sequencer state, without disturbing other channels. This also enables basic LED operation without the pattern trigger: with the trigger set to none, the brightness sysfs attribute fully controls the LED as a simple on/off device. Fixes: 259230378c65 ("leds: Add LED1202 I2C driver") Signed-off-by: Manuel Fombuena Assisted-by: Claude:claude-sonnet-4-6 Link: https://patch.msgid.link/GV1PR08MB8497570FD162D0D42A9864E3C5F52@GV1PR08MB8497.eurprd08.prod.outlook.com Signed-off-by: Lee Jones --- drivers/leds/leds-st1202.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/leds/leds-st1202.c b/drivers/leds/leds-st1202.c index b8a571282900..4909985ade04 100644 --- a/drivers/leds/leds-st1202.c +++ b/drivers/leds/leds-st1202.c @@ -136,6 +136,8 @@ static void st1202_brightness_set(struct led_classdev *led_cdev, guard(mutex)(&chip->lock); + for (int patt = 0; patt < ST1202_MAX_PATTERNS; patt++) + st1202_pwm_pattern_write(chip, led->led_num, patt, ST1202_PATTERN_PWM_FULL); st1202_write_reg(chip, ST1202_ILED_REG0 + led->led_num, value); } From 0767335233a8cbab00bbe260a4e4bd380c7677fd Mon Sep 17 00:00:00 2001 From: Manuel Fombuena Date: Thu, 2 Jul 2026 23:34:37 +0100 Subject: [PATCH 0121/1328] leds: st1202: Disable channel when brightness is set to zero When brightness_set() is called with LED_OFF, only the ILED register is zeroed; the channel enable bit is left set from probe time. A hardware channel enabled with ILED=0 still draws a small residual current, causing a dim glow even when the LED is supposed to be off. Fix this by splitting st1202_channel_set() into a lockless inner function __st1202_channel_set() and a locking wrapper, then calling the inner function from brightness_set() while it already holds the mutex. The channel is now disabled when value is zero and re-enabled when non-zero, in the same lock region as the ILED write. Fixes: 259230378c65 ("leds: Add LED1202 I2C driver") Signed-off-by: Manuel Fombuena Assisted-by: Claude:claude-sonnet-4-6 Link: https://patch.msgid.link/GV1PR08MB8497F11B30FE7D74CAA25135C5F52@GV1PR08MB8497.eurprd08.prod.outlook.com Signed-off-by: Lee Jones --- drivers/leds/leds-st1202.c | 68 ++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/drivers/leds/leds-st1202.c b/drivers/leds/leds-st1202.c index 4909985ade04..2ecd36aae336 100644 --- a/drivers/leds/leds-st1202.c +++ b/drivers/leds/leds-st1202.c @@ -128,39 +128,11 @@ static int st1202_duration_pattern_write(struct st1202_chip *chip, int pattern, st1202_prescalar_to_miliseconds(value)); } -static void st1202_brightness_set(struct led_classdev *led_cdev, - enum led_brightness value) -{ - struct st1202_led *led = cdev_to_st1202_led(led_cdev); - struct st1202_chip *chip = led->chip; - - guard(mutex)(&chip->lock); - - for (int patt = 0; patt < ST1202_MAX_PATTERNS; patt++) - st1202_pwm_pattern_write(chip, led->led_num, patt, ST1202_PATTERN_PWM_FULL); - st1202_write_reg(chip, ST1202_ILED_REG0 + led->led_num, value); -} - -static enum led_brightness st1202_brightness_get(struct led_classdev *led_cdev) -{ - struct st1202_led *led = cdev_to_st1202_led(led_cdev); - struct st1202_chip *chip = led->chip; - u8 value = 0; - - guard(mutex)(&chip->lock); - - st1202_read_reg(chip, ST1202_ILED_REG0 + led->led_num, &value); - - return value; -} - -static int st1202_channel_set(struct st1202_chip *chip, int led_num, bool active) +static int __st1202_channel_set(struct st1202_chip *chip, int led_num, bool active) { u8 chan_low, chan_high; int ret; - guard(mutex)(&chip->lock); - if (led_num <= 7) { ret = st1202_read_reg(chip, ST1202_CHAN_ENABLE_LOW, &chan_low); if (ret < 0) @@ -188,6 +160,40 @@ static int st1202_channel_set(struct st1202_chip *chip, int led_num, bool active return 0; } +static int st1202_channel_set(struct st1202_chip *chip, int led_num, bool active) +{ + guard(mutex)(&chip->lock); + + return __st1202_channel_set(chip, led_num, active); +} + +static void st1202_brightness_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct st1202_led *led = cdev_to_st1202_led(led_cdev); + struct st1202_chip *chip = led->chip; + + guard(mutex)(&chip->lock); + + for (int patt = 0; patt < ST1202_MAX_PATTERNS; patt++) + st1202_pwm_pattern_write(chip, led->led_num, patt, ST1202_PATTERN_PWM_FULL); + st1202_write_reg(chip, ST1202_ILED_REG0 + led->led_num, value); + __st1202_channel_set(chip, led->led_num, !!value); +} + +static enum led_brightness st1202_brightness_get(struct led_classdev *led_cdev) +{ + struct st1202_led *led = cdev_to_st1202_led(led_cdev); + struct st1202_chip *chip = led->chip; + u8 value = 0; + + guard(mutex)(&chip->lock); + + st1202_read_reg(chip, ST1202_ILED_REG0 + led->led_num, &value); + + return value; +} + static int st1202_led_set(struct led_classdev *ldev, enum led_brightness value) { struct st1202_led *led = cdev_to_st1202_led(ldev); @@ -257,6 +263,10 @@ static int st1202_led_pattern_set(struct led_classdev *ldev, if (ret != 0) return ret; + ret = __st1202_channel_set(chip, led->led_num, true); + if (ret != 0) + return ret; + ret = st1202_write_reg(chip, ST1202_CONFIG_REG, (ST1202_CONFIG_REG_PATSR | ST1202_CONFIG_REG_PATS | ST1202_CONFIG_REG_SHFT)); if (ret != 0) From cf197514bdfd3877f42b5dce1efd40b7b686547e Mon Sep 17 00:00:00 2001 From: Manuel Fombuena Date: Thu, 2 Jul 2026 23:35:58 +0100 Subject: [PATCH 0122/1328] leds: st1202: Validate LED reg property against channel count The reg property from the device tree is used directly as an array index into chip->leds[] without bounds checking. A value >= ST1202_MAX_LEDS would cause an out-of-bounds write during probe. Fixes: 259230378c65 ("leds: Add LED1202 I2C driver") Signed-off-by: Manuel Fombuena Assisted-by: Claude:claude-sonnet-4-6 Link: https://patch.msgid.link/GV1PR08MB849718B43321DB7E5A05D17BC5F52@GV1PR08MB8497.eurprd08.prod.outlook.com Signed-off-by: Lee Jones --- drivers/leds/leds-st1202.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/leds/leds-st1202.c b/drivers/leds/leds-st1202.c index 2ecd36aae336..168df5ecf27b 100644 --- a/drivers/leds/leds-st1202.c +++ b/drivers/leds/leds-st1202.c @@ -279,13 +279,19 @@ static int st1202_dt_init(struct st1202_chip *chip) { struct device *dev = &chip->client->dev; struct st1202_led *led; - int err, reg; + int err; + u32 reg; for_each_available_child_of_node_scoped(dev_of_node(dev), child) { err = of_property_read_u32(child, "reg", ®); if (err) return dev_err_probe(dev, err, "Invalid register\n"); + if (reg >= ST1202_MAX_LEDS) + return dev_err_probe(dev, -EINVAL, + "LED reg %u out of range [0, %d]\n", + reg, ST1202_MAX_LEDS - 1); + led = &chip->leds[reg]; led->is_active = true; led->fwnode = of_fwnode_handle(child); From 96fa4d69958cf90740d756d1e45cdb02cface41b Mon Sep 17 00:00:00 2001 From: Manuel Fombuena Date: Thu, 2 Jul 2026 23:37:03 +0100 Subject: [PATCH 0123/1328] leds: st1202: Correct and extend hw_pattern documentation Fix the repeat section: -1 is a valid value meaning infinite repeat, as accepted by the ledtrig-pattern sysfs interface; only 0 and values below -1 are rejected. The previous text incorrectly stated all negative numbers were invalid. Also remove the redundant trailing sentence since the behaviour is now covered inline. Add the brightness range (0-255) to the hw_pattern section, which was previously undocumented. Fixes: b1816b22381b ("Documentation:leds: Add leds-st1202.rst") Signed-off-by: Manuel Fombuena Assisted-by: Claude:claude-sonnet-4-6 Link: https://patch.msgid.link/GV1PR08MB8497B2FB4F5AC4B142790CE5C5F52@GV1PR08MB8497.eurprd08.prod.outlook.com Signed-off-by: Lee Jones --- Documentation/leds/leds-st1202.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Documentation/leds/leds-st1202.rst b/Documentation/leds/leds-st1202.rst index a2353549469e..ed32eb3a27d4 100644 --- a/Documentation/leds/leds-st1202.rst +++ b/Documentation/leds/leds-st1202.rst @@ -16,6 +16,7 @@ in terms of PWM duty-cycle and duration (ms). To be compatible with the hardware pattern format, maximum 8 tuples of brightness (PWM) and duration must be written to hw_pattern. +- Brightness range: 0-255 - Min pattern duration: 22 ms - Max pattern duration: 5610 ms @@ -26,9 +27,7 @@ The format of the hardware pattern values should be: ---------------------------- Specify a pattern repeat number, which is common for all channels. -Default is 1; negative numbers and 0 are invalid. +Default is 1. Writing 0 is invalid. Writing -1 or 255 repeats the +pattern indefinitely. This file will always return the originally written repeat number. - -When the 255 value is written to it, all patterns will repeat -indefinitely. From 4b3f5baf67dea91452b5b114dcb19139feba71dd Mon Sep 17 00:00:00 2001 From: Junjie Cao Date: Mon, 6 Jul 2026 15:33:07 +0800 Subject: [PATCH 0124/1328] MAINTAINERS: Update my email address for the AW99706 backlight driver Switch my maintainer entry for the Awinic AW99706 WLED backlight driver from my personal Gmail address to junjie.cao@linux.dev. Update both MAINTAINERS and the device-tree binding maintainers field. Signed-off-by: Junjie Cao Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260706073307.1038430-1-junjie.cao@linux.dev Signed-off-by: Lee Jones --- .../devicetree/bindings/leds/backlight/awinic,aw99706.yaml | 2 +- MAINTAINERS | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/leds/backlight/awinic,aw99706.yaml b/Documentation/devicetree/bindings/leds/backlight/awinic,aw99706.yaml index f48ce7a3434d..60eae50ff742 100644 --- a/Documentation/devicetree/bindings/leds/backlight/awinic,aw99706.yaml +++ b/Documentation/devicetree/bindings/leds/backlight/awinic,aw99706.yaml @@ -7,7 +7,7 @@ $schema: http://devicetree.org/meta-schemas/core.yaml# title: Awinic AW99706 6-channel WLED Backlight Driver maintainers: - - Junjie Cao + - Junjie Cao allOf: - $ref: common.yaml# diff --git a/MAINTAINERS b/MAINTAINERS index 15011f5752a9..780f80766780 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4388,7 +4388,7 @@ S: Maintained F: drivers/media/pci/hws/ AWINIC AW99706 WLED BACKLIGHT DRIVER -M: Junjie Cao +M: Junjie Cao S: Maintained F: Documentation/devicetree/bindings/leds/backlight/awinic,aw99706.yaml F: drivers/video/backlight/aw99706.c From 7a588b62679c51abd08171d31df6e44589f0097d Mon Sep 17 00:00:00 2001 From: Yixun Lan Date: Tue, 30 Jun 2026 12:52:45 +0000 Subject: [PATCH 0125/1328] clk: spacemit: k3: fix parent clock of UFS aclk According to SpacemiT updated clock docs, the previous UFS aclk parent clock was wrong, the correct one is illustrated below, so fix it. --> pll1_d5_491p52 --\ --> pll1_d6_409p6 --| --> pll2_d6 --|--> div --> gate --> ufs_aclk --> pll2_d5 --/ Fixes: e371a77255b8 ("clk: spacemit: k3: add the clock tree") Link: https://patch.msgid.link/20260630-06-clk-ufs-support-v1-1-cf7521d1d0fe@kernel.org Signed-off-by: Yixun Lan --- drivers/clk/spacemit/ccu-k3.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/clk/spacemit/ccu-k3.c b/drivers/clk/spacemit/ccu-k3.c index cb0c4277f72a..42293a38b098 100644 --- a/drivers/clk/spacemit/ccu-k3.c +++ b/drivers/clk/spacemit/ccu-k3.c @@ -926,11 +926,10 @@ CCU_MUX_DIV_GATE_FC_DEFINE(dpu_aclk, dpu_aclk_parents, APMU_LCD_CLK_RES_CTRL5, 1 20, 3, BIT(16), 0); static const struct clk_parent_data ufs_aclk_parents[] = { - CCU_PARENT_HW(pll1_d6_409p6), CCU_PARENT_HW(pll1_d5_491p52), - CCU_PARENT_HW(pll1_d4_614p4), - CCU_PARENT_HW(pll1_d8_307p2), - CCU_PARENT_HW(pll2_d4), + CCU_PARENT_HW(pll1_d6_409p6), + CCU_PARENT_HW(pll2_d6), + CCU_PARENT_HW(pll2_d5), }; CCU_MUX_DIV_GATE_FC_DEFINE(ufs_aclk, ufs_aclk_parents, APMU_UFS_CLK_RES_CTRL, 5, 3, BIT(8), 2, 3, BIT(1), 0); From 4fc567e1bacdb5da85628de8dd1d1e3328060798 Mon Sep 17 00:00:00 2001 From: Yixun Lan Date: Tue, 30 Jun 2026 12:52:46 +0000 Subject: [PATCH 0126/1328] dt-bindings: soc: spacemit: k3: Add clock ID for UFS refclk Add clock ID for the UFS refclk clock in SpacemiT K3 SoC. Acked-by: Conor Dooley Link: https://patch.msgid.link/20260630-06-clk-ufs-support-v1-2-cf7521d1d0fe@kernel.org Signed-off-by: Yixun Lan --- include/dt-bindings/clock/spacemit,k3-clocks.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/dt-bindings/clock/spacemit,k3-clocks.h b/include/dt-bindings/clock/spacemit,k3-clocks.h index dfae52547cda..69978ab0b29f 100644 --- a/include/dt-bindings/clock/spacemit,k3-clocks.h +++ b/include/dt-bindings/clock/spacemit,k3-clocks.h @@ -385,6 +385,7 @@ #define CLK_APMU_PCIE_PORTC_DBI 91 #define CLK_APMU_PCIE_PORTD_DBI 92 #define CLK_APMU_PCIE_PORTE_DBI 93 +#define CLK_APMU_UFS_REFCLK 94 /* DCIU clocks */ #define CLK_DCIU_HDMA 0 From 8abba78dc0ee4ffa7c4e0e05b93c58bd94ff11c0 Mon Sep 17 00:00:00 2001 From: Yixun Lan Date: Tue, 30 Jun 2026 12:52:47 +0000 Subject: [PATCH 0127/1328] clk: spacemit: k3: Add UFS refclk clock Add the UFS reference clock for SpacemiT K3 SoC. Link: https://patch.msgid.link/20260630-06-clk-ufs-support-v1-3-cf7521d1d0fe@kernel.org Signed-off-by: Yixun Lan --- drivers/clk/spacemit/ccu-k3.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/clk/spacemit/ccu-k3.c b/drivers/clk/spacemit/ccu-k3.c index 42293a38b098..f47d26e1d642 100644 --- a/drivers/clk/spacemit/ccu-k3.c +++ b/drivers/clk/spacemit/ccu-k3.c @@ -934,6 +934,8 @@ static const struct clk_parent_data ufs_aclk_parents[] = { CCU_MUX_DIV_GATE_FC_DEFINE(ufs_aclk, ufs_aclk_parents, APMU_UFS_CLK_RES_CTRL, 5, 3, BIT(8), 2, 3, BIT(1), 0); +CCU_FACTOR_DEFINE(ufs_refclk, CCU_PARENT_HW(pll1_d64_38p4), 2, 1); + static const struct clk_parent_data edp0_pclk_parents[] = { CCU_PARENT_HW(lcd_pxclk), CCU_PARENT_NAME(external_clk), @@ -1391,6 +1393,7 @@ static struct clk_hw *k3_ccu_apmu_hws[] = { [CLK_APMU_DSI4LN2_DPU_ACLK] = &dsi4ln2_dpu_aclk.common.hw, [CLK_APMU_DPU_ACLK] = &dpu_aclk.common.hw, [CLK_APMU_UFS_ACLK] = &ufs_aclk.common.hw, + [CLK_APMU_UFS_REFCLK] = &ufs_refclk.common.hw, [CLK_APMU_EDP0_PXCLK] = &edp0_pxclk.common.hw, [CLK_APMU_EDP1_PXCLK] = &edp1_pxclk.common.hw, [CLK_APMU_PCIE_PORTA_MSTE] = &pciea_mstr_clk.common.hw, From 97255eedd6905980c85eb93b438edcb91277f831 Mon Sep 17 00:00:00 2001 From: Jagadeesh Kona Date: Wed, 24 Jun 2026 20:20:30 +0530 Subject: [PATCH 0128/1328] clk: qcom: camcc-glymur: Add camera clock controller driver Add support for the camera clock controller for camera clients to be able to request for camcc clocks on Glymur platform. Reviewed-by: Taniya Das Reviewed-by: Konrad Dybcio Reviewed-by: Vladimir Zapolskiy Signed-off-by: Jagadeesh Kona Link: https://lore.kernel.org/r/20260624-glymur_camcc-v5-2-a321df74b1a1@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/Kconfig | 11 + drivers/clk/qcom/Makefile | 1 + drivers/clk/qcom/camcc-glymur.c | 2280 +++++++++++++++++++++++++++++++ 3 files changed, 2292 insertions(+) create mode 100644 drivers/clk/qcom/camcc-glymur.c diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig index 00880d676296..856d86ee52cb 100644 --- a/drivers/clk/qcom/Kconfig +++ b/drivers/clk/qcom/Kconfig @@ -45,6 +45,17 @@ config CLK_ELIZA_TCSRCC Support for the TCSR clock controller on Eliza devices. Say Y if you want to use peripheral devices such as USB/PCIe/UFS. +config CLK_GLYMUR_CAMCC + tristate "Glymur Camera Clock Controller" + depends on ARM64 || COMPILE_TEST + select CLK_GLYMUR_GCC + default m if ARCH_QCOM + help + Support for the camera clock controller on Qualcomm Technologies, Inc + Glymur devices. + Say Y if you want to support camera devices and functionality such as + capturing pictures. + config CLK_GLYMUR_DISPCC tristate "Glymur Display Clock Controller" depends on ARM64 || COMPILE_TEST diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile index 76946206d4aa..db7689ee5dc6 100644 --- a/drivers/clk/qcom/Makefile +++ b/drivers/clk/qcom/Makefile @@ -24,6 +24,7 @@ obj-$(CONFIG_CLK_ELIZA_DISPCC) += dispcc-eliza.o obj-$(CONFIG_CLK_ELIZA_GCC) += gcc-eliza.o obj-$(CONFIG_CLK_ELIZA_TCSRCC) += tcsrcc-eliza.o obj-$(CONFIG_CLK_GFM_LPASS_SM8250) += lpass-gfm-sm8250.o +obj-$(CONFIG_CLK_GLYMUR_CAMCC) += camcc-glymur.o obj-$(CONFIG_CLK_GLYMUR_DISPCC) += dispcc-glymur.o obj-$(CONFIG_CLK_GLYMUR_EVACC) += evacc-glymur.o obj-$(CONFIG_CLK_GLYMUR_GCC) += gcc-glymur.o diff --git a/drivers/clk/qcom/camcc-glymur.c b/drivers/clk/qcom/camcc-glymur.c new file mode 100644 index 000000000000..a80f05b7bc48 --- /dev/null +++ b/drivers/clk/qcom/camcc-glymur.c @@ -0,0 +1,2280 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include +#include + +#include + +#include "clk-alpha-pll.h" +#include "clk-branch.h" +#include "clk-rcg.h" +#include "clk-regmap.h" +#include "common.h" +#include "gdsc.h" +#include "reset.h" + +enum { + DT_IFACE, + DT_BI_TCXO, + DT_BI_TCXO_AO, + DT_SLEEP_CLK, +}; + +enum { + P_BI_TCXO, + P_BI_TCXO_AO, + P_CAM_CC_PLL0_OUT_EVEN, + P_CAM_CC_PLL0_OUT_MAIN, + P_CAM_CC_PLL0_OUT_ODD, + P_CAM_CC_PLL1_OUT_EVEN, + P_CAM_CC_PLL2_OUT_EVEN, + P_CAM_CC_PLL2_OUT_MAIN, + P_CAM_CC_PLL3_OUT_EVEN, + P_CAM_CC_PLL4_OUT_EVEN, + P_CAM_CC_PLL5_OUT_EVEN, + P_SLEEP_CLK, +}; + +static const struct pll_vco rivian_eko_t_vco[] = { + { 883200000, 1171200000, 0 }, +}; + +static const struct pll_vco taycan_eko_t_vco[] = { + { 249600000, 2500000000, 0 }, +}; + +/* 1200.0 MHz Configuration */ +static const struct alpha_pll_config cam_cc_pll0_config = { + .l = 0x3e, + .alpha = 0x8000, + .config_ctl_val = 0x25c400e7, + .config_ctl_hi_val = 0x0a8060e0, + .config_ctl_hi1_val = 0xf51dea20, + .user_ctl_val = 0x00008408, + .user_ctl_hi_val = 0x00000002, +}; + +static struct clk_alpha_pll cam_cc_pll0 = { + .offset = 0x0, + .config = &cam_cc_pll0_config, + .vco_table = taycan_eko_t_vco, + .num_vco = ARRAY_SIZE(taycan_eko_t_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_TAYCAN_EKO_T], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_pll0", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_taycan_eko_t_ops, + }, + }, +}; + +static const struct clk_div_table post_div_table_cam_cc_pll0_out_even[] = { + { 0x1, 2 }, + { } +}; + +static struct clk_alpha_pll_postdiv cam_cc_pll0_out_even = { + .offset = 0x0, + .post_div_shift = 10, + .post_div_table = post_div_table_cam_cc_pll0_out_even, + .num_post_div = ARRAY_SIZE(post_div_table_cam_cc_pll0_out_even), + .width = 4, + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_TAYCAN_EKO_T], + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_pll0_out_even", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_pll0.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_alpha_pll_postdiv_taycan_eko_t_ops, + }, +}; + +static const struct clk_div_table post_div_table_cam_cc_pll0_out_odd[] = { + { 0x2, 3 }, + { } +}; + +static struct clk_alpha_pll_postdiv cam_cc_pll0_out_odd = { + .offset = 0x0, + .post_div_shift = 14, + .post_div_table = post_div_table_cam_cc_pll0_out_odd, + .num_post_div = ARRAY_SIZE(post_div_table_cam_cc_pll0_out_odd), + .width = 4, + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_TAYCAN_EKO_T], + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_pll0_out_odd", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_pll0.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_alpha_pll_postdiv_taycan_eko_t_ops, + }, +}; + +/* 608.0 MHz Configuration */ +static const struct alpha_pll_config cam_cc_pll1_config = { + .l = 0x1f, + .alpha = 0xaaaa, + .config_ctl_val = 0x25c400e7, + .config_ctl_hi_val = 0x0a8060e0, + .config_ctl_hi1_val = 0xf51dea20, + .user_ctl_val = 0x00000408, + .user_ctl_hi_val = 0x00000002, +}; + +static struct clk_alpha_pll cam_cc_pll1 = { + .offset = 0x1000, + .config = &cam_cc_pll1_config, + .vco_table = taycan_eko_t_vco, + .num_vco = ARRAY_SIZE(taycan_eko_t_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_TAYCAN_EKO_T], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_pll1", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_taycan_eko_t_ops, + }, + }, +}; + +static const struct clk_div_table post_div_table_cam_cc_pll1_out_even[] = { + { 0x1, 2 }, + { } +}; + +static struct clk_alpha_pll_postdiv cam_cc_pll1_out_even = { + .offset = 0x1000, + .post_div_shift = 10, + .post_div_table = post_div_table_cam_cc_pll1_out_even, + .num_post_div = ARRAY_SIZE(post_div_table_cam_cc_pll1_out_even), + .width = 4, + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_TAYCAN_EKO_T], + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_pll1_out_even", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_pll1.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_alpha_pll_postdiv_taycan_eko_t_ops, + }, +}; + +/* 960.0 MHz Configuration */ +static const struct alpha_pll_config cam_cc_pll2_config = { + .l = 0x32, + .alpha = 0x0, + .config_ctl_val = 0x12000000, + .config_ctl_hi_val = 0x00890263, + .config_ctl_hi1_val = 0x1af04237, + .config_ctl_hi2_val = 0x00000000, +}; + +static struct clk_alpha_pll cam_cc_pll2 = { + .offset = 0x2000, + .config = &cam_cc_pll2_config, + .vco_table = rivian_eko_t_vco, + .num_vco = ARRAY_SIZE(rivian_eko_t_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_RIVIAN_EKO_T], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_pll2", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_rivian_eko_t_ops, + }, + }, +}; + +/* 691.2 MHz Configuration */ +static const struct alpha_pll_config cam_cc_pll3_config = { + .l = 0x24, + .alpha = 0x0, + .config_ctl_val = 0x25c400e7, + .config_ctl_hi_val = 0x0a8060e0, + .config_ctl_hi1_val = 0xf51dea20, + .user_ctl_val = 0x00000408, + .user_ctl_hi_val = 0x00000002, +}; + +static struct clk_alpha_pll cam_cc_pll3 = { + .offset = 0x3000, + .config = &cam_cc_pll3_config, + .vco_table = taycan_eko_t_vco, + .num_vco = ARRAY_SIZE(taycan_eko_t_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_TAYCAN_EKO_T], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_pll3", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_taycan_eko_t_ops, + }, + }, +}; + +static const struct clk_div_table post_div_table_cam_cc_pll3_out_even[] = { + { 0x1, 2 }, + { } +}; + +static struct clk_alpha_pll_postdiv cam_cc_pll3_out_even = { + .offset = 0x3000, + .post_div_shift = 10, + .post_div_table = post_div_table_cam_cc_pll3_out_even, + .num_post_div = ARRAY_SIZE(post_div_table_cam_cc_pll3_out_even), + .width = 4, + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_TAYCAN_EKO_T], + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_pll3_out_even", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_pll3.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_alpha_pll_postdiv_taycan_eko_t_ops, + }, +}; + +/* 691.2 MHz Configuration */ +static const struct alpha_pll_config cam_cc_pll4_config = { + .l = 0x24, + .alpha = 0x0, + .config_ctl_val = 0x25c400e7, + .config_ctl_hi_val = 0x0a8060e0, + .config_ctl_hi1_val = 0xf51dea20, + .user_ctl_val = 0x00000408, + .user_ctl_hi_val = 0x00000002, +}; + +static struct clk_alpha_pll cam_cc_pll4 = { + .offset = 0x4000, + .config = &cam_cc_pll4_config, + .vco_table = taycan_eko_t_vco, + .num_vco = ARRAY_SIZE(taycan_eko_t_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_TAYCAN_EKO_T], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_pll4", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_taycan_eko_t_ops, + }, + }, +}; + +static const struct clk_div_table post_div_table_cam_cc_pll4_out_even[] = { + { 0x1, 2 }, + { } +}; + +static struct clk_alpha_pll_postdiv cam_cc_pll4_out_even = { + .offset = 0x4000, + .post_div_shift = 10, + .post_div_table = post_div_table_cam_cc_pll4_out_even, + .num_post_div = ARRAY_SIZE(post_div_table_cam_cc_pll4_out_even), + .width = 4, + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_TAYCAN_EKO_T], + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_pll4_out_even", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_pll4.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_alpha_pll_postdiv_taycan_eko_t_ops, + }, +}; + +/* 960.0 MHz Configuration */ +static const struct alpha_pll_config cam_cc_pll5_config = { + .l = 0x32, + .alpha = 0x0, + .config_ctl_val = 0x25c400e7, + .config_ctl_hi_val = 0x0a8060e0, + .config_ctl_hi1_val = 0xf51dea20, + .user_ctl_val = 0x00000408, + .user_ctl_hi_val = 0x00000002, +}; + +static struct clk_alpha_pll cam_cc_pll5 = { + .offset = 0x5000, + .config = &cam_cc_pll5_config, + .vco_table = taycan_eko_t_vco, + .num_vco = ARRAY_SIZE(taycan_eko_t_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_TAYCAN_EKO_T], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_pll5", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_taycan_eko_t_ops, + }, + }, +}; + +static const struct clk_div_table post_div_table_cam_cc_pll5_out_even[] = { + { 0x1, 2 }, + { } +}; + +static struct clk_alpha_pll_postdiv cam_cc_pll5_out_even = { + .offset = 0x5000, + .post_div_shift = 10, + .post_div_table = post_div_table_cam_cc_pll5_out_even, + .num_post_div = ARRAY_SIZE(post_div_table_cam_cc_pll5_out_even), + .width = 4, + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_TAYCAN_EKO_T], + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_pll5_out_even", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_pll5.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_alpha_pll_postdiv_taycan_eko_t_ops, + }, +}; + +static const struct parent_map cam_cc_parent_map_0[] = { + { P_BI_TCXO, 0 }, + { P_CAM_CC_PLL0_OUT_MAIN, 1 }, + { P_CAM_CC_PLL0_OUT_EVEN, 2 }, + { P_CAM_CC_PLL0_OUT_ODD, 3 }, + { P_CAM_CC_PLL5_OUT_EVEN, 5 }, +}; + +static const struct clk_parent_data cam_cc_parent_data_0[] = { + { .index = DT_BI_TCXO }, + { .hw = &cam_cc_pll0.clkr.hw }, + { .hw = &cam_cc_pll0_out_even.clkr.hw }, + { .hw = &cam_cc_pll0_out_odd.clkr.hw }, + { .hw = &cam_cc_pll5_out_even.clkr.hw }, +}; + +static const struct parent_map cam_cc_parent_map_1[] = { + { P_BI_TCXO, 0 }, + { P_CAM_CC_PLL2_OUT_EVEN, 3 }, + { P_CAM_CC_PLL2_OUT_MAIN, 5 }, +}; + +static const struct clk_parent_data cam_cc_parent_data_1[] = { + { .index = DT_BI_TCXO }, + { .hw = &cam_cc_pll2.clkr.hw }, + { .hw = &cam_cc_pll2.clkr.hw }, +}; + +static const struct parent_map cam_cc_parent_map_2[] = { + { P_BI_TCXO, 0 }, + { P_CAM_CC_PLL3_OUT_EVEN, 6 }, +}; + +static const struct clk_parent_data cam_cc_parent_data_2[] = { + { .index = DT_BI_TCXO }, + { .hw = &cam_cc_pll3_out_even.clkr.hw }, +}; + +static const struct parent_map cam_cc_parent_map_3[] = { + { P_BI_TCXO, 0 }, + { P_CAM_CC_PLL4_OUT_EVEN, 6 }, +}; + +static const struct clk_parent_data cam_cc_parent_data_3[] = { + { .index = DT_BI_TCXO }, + { .hw = &cam_cc_pll4_out_even.clkr.hw }, +}; + +static const struct parent_map cam_cc_parent_map_4[] = { + { P_BI_TCXO, 0 }, + { P_CAM_CC_PLL1_OUT_EVEN, 4 }, +}; + +static const struct clk_parent_data cam_cc_parent_data_4[] = { + { .index = DT_BI_TCXO }, + { .hw = &cam_cc_pll1_out_even.clkr.hw }, +}; + +static const struct parent_map cam_cc_parent_map_5[] = { + { P_SLEEP_CLK, 0 }, +}; + +static const struct clk_parent_data cam_cc_parent_data_5[] = { + { .index = DT_SLEEP_CLK }, +}; + +static const struct parent_map cam_cc_parent_map_6_ao[] = { + { P_BI_TCXO_AO, 0 }, +}; + +static const struct clk_parent_data cam_cc_parent_data_6_ao[] = { + { .index = DT_BI_TCXO_AO }, +}; + +static const struct freq_tbl ftbl_cam_cc_bps_clk_src[] = { + F(160000000, P_CAM_CC_PLL0_OUT_ODD, 2.5, 0, 0), + F(200000000, P_CAM_CC_PLL0_OUT_ODD, 2, 0, 0), + F(400000000, P_CAM_CC_PLL0_OUT_ODD, 1, 0, 0), + F(600000000, P_CAM_CC_PLL0_OUT_EVEN, 1, 0, 0), + { } +}; + +static struct clk_rcg2 cam_cc_bps_clk_src = { + .cmd_rcgr = 0x10278, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_0, + .freq_tbl = ftbl_cam_cc_bps_clk_src, + .hw_clk_ctrl = true, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_bps_clk_src", + .parent_data = cam_cc_parent_data_0, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, + }, +}; + +static const struct freq_tbl ftbl_cam_cc_camnoc_axi_rt_clk_src[] = { + F(240000000, P_CAM_CC_PLL0_OUT_EVEN, 2.5, 0, 0), + F(300000000, P_CAM_CC_PLL0_OUT_EVEN, 2, 0, 0), + F(400000000, P_CAM_CC_PLL0_OUT_ODD, 1, 0, 0), + { } +}; + +static struct clk_rcg2 cam_cc_camnoc_axi_rt_clk_src = { + .cmd_rcgr = 0x137b4, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_0, + .freq_tbl = ftbl_cam_cc_camnoc_axi_rt_clk_src, + .hw_clk_ctrl = true, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_camnoc_axi_rt_clk_src", + .parent_data = cam_cc_parent_data_0, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, + }, +}; + +static const struct freq_tbl ftbl_cam_cc_cci_0_clk_src[] = { + F(30000000, P_CAM_CC_PLL5_OUT_EVEN, 16, 0, 0), + F(37500000, P_CAM_CC_PLL0_OUT_EVEN, 16, 0, 0), + { } +}; + +static struct clk_rcg2 cam_cc_cci_0_clk_src = { + .cmd_rcgr = 0x1350c, + .mnd_width = 8, + .hid_width = 5, + .parent_map = cam_cc_parent_map_0, + .freq_tbl = ftbl_cam_cc_cci_0_clk_src, + .hw_clk_ctrl = true, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_cci_0_clk_src", + .parent_data = cam_cc_parent_data_0, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, + }, +}; + +static struct clk_rcg2 cam_cc_cci_1_clk_src = { + .cmd_rcgr = 0x1363c, + .mnd_width = 8, + .hid_width = 5, + .parent_map = cam_cc_parent_map_0, + .freq_tbl = ftbl_cam_cc_cci_0_clk_src, + .hw_clk_ctrl = true, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_cci_1_clk_src", + .parent_data = cam_cc_parent_data_0, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, + }, +}; + +static const struct freq_tbl ftbl_cam_cc_cphy_rx_clk_src[] = { + F(300000000, P_CAM_CC_PLL0_OUT_MAIN, 4, 0, 0), + F(400000000, P_CAM_CC_PLL0_OUT_MAIN, 3, 0, 0), + F(480000000, P_CAM_CC_PLL0_OUT_MAIN, 2.5, 0, 0), + { } +}; + +static struct clk_rcg2 cam_cc_cphy_rx_clk_src = { + .cmd_rcgr = 0x11168, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_0, + .freq_tbl = ftbl_cam_cc_cphy_rx_clk_src, + .hw_clk_ctrl = true, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_cphy_rx_clk_src", + .parent_data = cam_cc_parent_data_0, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, + }, +}; + +static const struct freq_tbl ftbl_cam_cc_csi0phytimer_clk_src[] = { + F(266666667, P_CAM_CC_PLL0_OUT_ODD, 1.5, 0, 0), + F(400000000, P_CAM_CC_PLL0_OUT_ODD, 1, 0, 0), + { } +}; + +static struct clk_rcg2 cam_cc_csi0phytimer_clk_src = { + .cmd_rcgr = 0x150e0, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_0, + .freq_tbl = ftbl_cam_cc_csi0phytimer_clk_src, + .hw_clk_ctrl = true, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_csi0phytimer_clk_src", + .parent_data = cam_cc_parent_data_0, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, + }, +}; + +static struct clk_rcg2 cam_cc_csi1phytimer_clk_src = { + .cmd_rcgr = 0x15104, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_0, + .freq_tbl = ftbl_cam_cc_csi0phytimer_clk_src, + .hw_clk_ctrl = true, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_csi1phytimer_clk_src", + .parent_data = cam_cc_parent_data_0, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, + }, +}; + +static struct clk_rcg2 cam_cc_csi4phytimer_clk_src = { + .cmd_rcgr = 0x15124, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_0, + .freq_tbl = ftbl_cam_cc_csi0phytimer_clk_src, + .hw_clk_ctrl = true, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_csi4phytimer_clk_src", + .parent_data = cam_cc_parent_data_0, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, + }, +}; + +static struct clk_rcg2 cam_cc_csid_clk_src = { + .cmd_rcgr = 0x1378c, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_0, + .freq_tbl = ftbl_cam_cc_cphy_rx_clk_src, + .hw_clk_ctrl = true, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_csid_clk_src", + .parent_data = cam_cc_parent_data_0, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, + }, +}; + +static const struct freq_tbl ftbl_cam_cc_fast_ahb_clk_src[] = { + F(80000000, P_CAM_CC_PLL0_OUT_EVEN, 7.5, 0, 0), + F(100000000, P_CAM_CC_PLL0_OUT_EVEN, 6, 0, 0), + F(200000000, P_CAM_CC_PLL0_OUT_EVEN, 3, 0, 0), + F(300000000, P_CAM_CC_PLL0_OUT_MAIN, 4, 0, 0), + F(400000000, P_CAM_CC_PLL0_OUT_MAIN, 3, 0, 0), + { } +}; + +static struct clk_rcg2 cam_cc_fast_ahb_clk_src = { + .cmd_rcgr = 0x10018, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_0, + .freq_tbl = ftbl_cam_cc_fast_ahb_clk_src, + .hw_clk_ctrl = true, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_fast_ahb_clk_src", + .parent_data = cam_cc_parent_data_0, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, + }, +}; + +static const struct freq_tbl ftbl_cam_cc_icp_clk_src[] = { + F(300000000, P_CAM_CC_PLL0_OUT_EVEN, 2, 0, 0), + F(400000000, P_CAM_CC_PLL0_OUT_ODD, 1, 0, 0), + F(480000000, P_CAM_CC_PLL5_OUT_EVEN, 1, 0, 0), + F(600000000, P_CAM_CC_PLL0_OUT_MAIN, 2, 0, 0), + { } +}; + +static struct clk_rcg2 cam_cc_icp_clk_src = { + .cmd_rcgr = 0x133cc, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_0, + .freq_tbl = ftbl_cam_cc_icp_clk_src, + .hw_clk_ctrl = true, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_icp_clk_src", + .parent_data = cam_cc_parent_data_0, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, + }, +}; + +static const struct freq_tbl ftbl_cam_cc_ife_0_clk_src[] = { + F(345600000, P_CAM_CC_PLL3_OUT_EVEN, 1, 0, 0), + F(432000000, P_CAM_CC_PLL3_OUT_EVEN, 1, 0, 0), + F(594000000, P_CAM_CC_PLL3_OUT_EVEN, 1, 0, 0), + F(675000000, P_CAM_CC_PLL3_OUT_EVEN, 1, 0, 0), + F(727000000, P_CAM_CC_PLL3_OUT_EVEN, 1, 0, 0), + { } +}; + +static struct clk_rcg2 cam_cc_ife_0_clk_src = { + .cmd_rcgr = 0x11018, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_2, + .freq_tbl = ftbl_cam_cc_ife_0_clk_src, + .hw_clk_ctrl = true, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_0_clk_src", + .parent_data = cam_cc_parent_data_2, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_2), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, + }, +}; + +static const struct freq_tbl ftbl_cam_cc_ife_1_clk_src[] = { + F(345600000, P_CAM_CC_PLL4_OUT_EVEN, 1, 0, 0), + F(432000000, P_CAM_CC_PLL4_OUT_EVEN, 1, 0, 0), + F(594000000, P_CAM_CC_PLL4_OUT_EVEN, 1, 0, 0), + F(675000000, P_CAM_CC_PLL4_OUT_EVEN, 1, 0, 0), + F(727000000, P_CAM_CC_PLL4_OUT_EVEN, 1, 0, 0), + { } +}; + +static struct clk_rcg2 cam_cc_ife_1_clk_src = { + .cmd_rcgr = 0x12018, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_3, + .freq_tbl = ftbl_cam_cc_ife_1_clk_src, + .hw_clk_ctrl = true, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_1_clk_src", + .parent_data = cam_cc_parent_data_3, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_3), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, + }, +}; + +static const struct freq_tbl ftbl_cam_cc_ife_lite_clk_src[] = { + F(266666667, P_CAM_CC_PLL0_OUT_ODD, 1.5, 0, 0), + F(400000000, P_CAM_CC_PLL0_OUT_ODD, 1, 0, 0), + F(480000000, P_CAM_CC_PLL5_OUT_EVEN, 1, 0, 0), + { } +}; + +static struct clk_rcg2 cam_cc_ife_lite_clk_src = { + .cmd_rcgr = 0x13000, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_0, + .freq_tbl = ftbl_cam_cc_ife_lite_clk_src, + .hw_clk_ctrl = true, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_lite_clk_src", + .parent_data = cam_cc_parent_data_0, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, + }, +}; + +static struct clk_rcg2 cam_cc_ife_lite_csid_clk_src = { + .cmd_rcgr = 0x13140, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_0, + .freq_tbl = ftbl_cam_cc_ife_lite_clk_src, + .hw_clk_ctrl = true, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_lite_csid_clk_src", + .parent_data = cam_cc_parent_data_0, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, + }, +}; + +static const struct freq_tbl ftbl_cam_cc_ipe_nps_clk_src[] = { + F(304000000, P_CAM_CC_PLL1_OUT_EVEN, 1, 0, 0), + F(364000000, P_CAM_CC_PLL1_OUT_EVEN, 1, 0, 0), + F(500000000, P_CAM_CC_PLL1_OUT_EVEN, 1, 0, 0), + F(600000000, P_CAM_CC_PLL1_OUT_EVEN, 1, 0, 0), + F(700000000, P_CAM_CC_PLL1_OUT_EVEN, 1, 0, 0), + { } +}; + +static struct clk_rcg2 cam_cc_ipe_nps_clk_src = { + .cmd_rcgr = 0x103d0, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_4, + .freq_tbl = ftbl_cam_cc_ipe_nps_clk_src, + .hw_clk_ctrl = true, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ipe_nps_clk_src", + .parent_data = cam_cc_parent_data_4, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_4), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, + }, +}; + +static const struct freq_tbl ftbl_cam_cc_jpeg_clk_src[] = { + F(160000000, P_CAM_CC_PLL0_OUT_ODD, 2.5, 0, 0), + F(200000000, P_CAM_CC_PLL0_OUT_ODD, 2, 0, 0), + F(400000000, P_CAM_CC_PLL0_OUT_ODD, 1, 0, 0), + F(480000000, P_CAM_CC_PLL5_OUT_EVEN, 1, 0, 0), + F(600000000, P_CAM_CC_PLL0_OUT_EVEN, 1, 0, 0), + { } +}; + +static struct clk_rcg2 cam_cc_jpeg_clk_src = { + .cmd_rcgr = 0x13284, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_0, + .freq_tbl = ftbl_cam_cc_jpeg_clk_src, + .hw_clk_ctrl = true, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_jpeg_clk_src", + .parent_data = cam_cc_parent_data_0, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, + }, +}; + +static const struct freq_tbl ftbl_cam_cc_mclk0_clk_src[] = { + F(19200000, P_BI_TCXO, 1, 0, 0), + F(24000000, P_CAM_CC_PLL2_OUT_MAIN, 10, 1, 4), + F(68571429, P_CAM_CC_PLL2_OUT_MAIN, 14, 0, 0), + { } +}; + +static struct clk_rcg2 cam_cc_mclk0_clk_src = { + .cmd_rcgr = 0x15000, + .mnd_width = 8, + .hid_width = 5, + .parent_map = cam_cc_parent_map_1, + .freq_tbl = ftbl_cam_cc_mclk0_clk_src, + .hw_clk_ctrl = true, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_mclk0_clk_src", + .parent_data = cam_cc_parent_data_1, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, + }, +}; + +static struct clk_rcg2 cam_cc_mclk1_clk_src = { + .cmd_rcgr = 0x1501c, + .mnd_width = 8, + .hid_width = 5, + .parent_map = cam_cc_parent_map_1, + .freq_tbl = ftbl_cam_cc_mclk0_clk_src, + .hw_clk_ctrl = true, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_mclk1_clk_src", + .parent_data = cam_cc_parent_data_1, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, + }, +}; + +static struct clk_rcg2 cam_cc_mclk2_clk_src = { + .cmd_rcgr = 0x15038, + .mnd_width = 8, + .hid_width = 5, + .parent_map = cam_cc_parent_map_1, + .freq_tbl = ftbl_cam_cc_mclk0_clk_src, + .hw_clk_ctrl = true, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_mclk2_clk_src", + .parent_data = cam_cc_parent_data_1, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, + }, +}; + +static struct clk_rcg2 cam_cc_mclk3_clk_src = { + .cmd_rcgr = 0x15054, + .mnd_width = 8, + .hid_width = 5, + .parent_map = cam_cc_parent_map_1, + .freq_tbl = ftbl_cam_cc_mclk0_clk_src, + .hw_clk_ctrl = true, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_mclk3_clk_src", + .parent_data = cam_cc_parent_data_1, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, + }, +}; + +static struct clk_rcg2 cam_cc_mclk4_clk_src = { + .cmd_rcgr = 0x15070, + .mnd_width = 8, + .hid_width = 5, + .parent_map = cam_cc_parent_map_1, + .freq_tbl = ftbl_cam_cc_mclk0_clk_src, + .hw_clk_ctrl = true, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_mclk4_clk_src", + .parent_data = cam_cc_parent_data_1, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, + }, +}; + +static struct clk_rcg2 cam_cc_mclk5_clk_src = { + .cmd_rcgr = 0x1508c, + .mnd_width = 8, + .hid_width = 5, + .parent_map = cam_cc_parent_map_1, + .freq_tbl = ftbl_cam_cc_mclk0_clk_src, + .hw_clk_ctrl = true, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_mclk5_clk_src", + .parent_data = cam_cc_parent_data_1, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, + }, +}; + +static struct clk_rcg2 cam_cc_mclk6_clk_src = { + .cmd_rcgr = 0x150a8, + .mnd_width = 8, + .hid_width = 5, + .parent_map = cam_cc_parent_map_1, + .freq_tbl = ftbl_cam_cc_mclk0_clk_src, + .hw_clk_ctrl = true, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_mclk6_clk_src", + .parent_data = cam_cc_parent_data_1, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, + }, +}; + +static struct clk_rcg2 cam_cc_mclk7_clk_src = { + .cmd_rcgr = 0x150c4, + .mnd_width = 8, + .hid_width = 5, + .parent_map = cam_cc_parent_map_1, + .freq_tbl = ftbl_cam_cc_mclk0_clk_src, + .hw_clk_ctrl = true, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_mclk7_clk_src", + .parent_data = cam_cc_parent_data_1, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, + }, +}; + +static const struct freq_tbl ftbl_cam_cc_qdss_debug_clk_src[] = { + F(60000000, P_CAM_CC_PLL5_OUT_EVEN, 8, 0, 0), + F(75000000, P_CAM_CC_PLL0_OUT_EVEN, 8, 0, 0), + F(150000000, P_CAM_CC_PLL0_OUT_EVEN, 4, 0, 0), + F(300000000, P_CAM_CC_PLL0_OUT_MAIN, 4, 0, 0), + { } +}; + +static struct clk_rcg2 cam_cc_qdss_debug_clk_src = { + .cmd_rcgr = 0x137fc, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_0, + .freq_tbl = ftbl_cam_cc_qdss_debug_clk_src, + .hw_clk_ctrl = true, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_qdss_debug_clk_src", + .parent_data = cam_cc_parent_data_0, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, + }, +}; + +static const struct freq_tbl ftbl_cam_cc_sleep_clk_src[] = { + F(32000, P_SLEEP_CLK, 1, 0, 0), + { } +}; + +static struct clk_rcg2 cam_cc_sleep_clk_src = { + .cmd_rcgr = 0x13964, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_5, + .freq_tbl = ftbl_cam_cc_sleep_clk_src, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_sleep_clk_src", + .parent_data = cam_cc_parent_data_5, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_5), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, + }, +}; + +static const struct freq_tbl ftbl_cam_cc_slow_ahb_clk_src[] = { + F(64000000, P_CAM_CC_PLL5_OUT_EVEN, 7.5, 0, 0), + F(80000000, P_CAM_CC_PLL0_OUT_EVEN, 7.5, 0, 0), + { } +}; + +static struct clk_rcg2 cam_cc_slow_ahb_clk_src = { + .cmd_rcgr = 0x10148, + .mnd_width = 8, + .hid_width = 5, + .parent_map = cam_cc_parent_map_0, + .freq_tbl = ftbl_cam_cc_slow_ahb_clk_src, + .hw_clk_ctrl = true, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_slow_ahb_clk_src", + .parent_data = cam_cc_parent_data_0, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, + }, +}; + +static const struct freq_tbl ftbl_cam_cc_xo_clk_src[] = { + F(19200000, P_BI_TCXO_AO, 1, 0, 0), + { } +}; + +static struct clk_rcg2 cam_cc_xo_clk_src = { + .cmd_rcgr = 0x13948, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_6_ao, + .freq_tbl = ftbl_cam_cc_xo_clk_src, + .hw_clk_ctrl = true, + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_xo_clk_src", + .parent_data = cam_cc_parent_data_6_ao, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_6_ao), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, + }, +}; + +static struct clk_branch cam_cc_bps_ahb_clk = { + .halt_reg = 0x10274, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x10274, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_bps_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_slow_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_bps_clk = { + .halt_reg = 0x103a4, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x103a4, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_bps_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_bps_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_bps_fast_ahb_clk = { + .halt_reg = 0x10144, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x10144, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_bps_fast_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_fast_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_camnoc_axi_nrt_clk = { + .halt_reg = 0x137e0, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x137e0, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x137e0, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_camnoc_axi_nrt_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_camnoc_axi_rt_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_camnoc_axi_rt_clk = { + .halt_reg = 0x137cc, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x137cc, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_camnoc_axi_rt_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_camnoc_axi_rt_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_camnoc_dcd_xo_clk = { + .halt_reg = 0x137f0, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x137f0, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_camnoc_dcd_xo_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_xo_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_camnoc_xo_clk = { + .halt_reg = 0x137f4, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x137f4, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_camnoc_xo_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_xo_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_cci_0_clk = { + .halt_reg = 0x13638, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x13638, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_cci_0_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_cci_0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_cci_1_clk = { + .halt_reg = 0x13768, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x13768, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_cci_1_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_cci_1_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_core_ahb_clk = { + .halt_reg = 0x13944, + .halt_check = BRANCH_HALT_DELAY, + .clkr = { + .enable_reg = 0x13944, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_core_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_slow_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_cpas_ahb_clk = { + .halt_reg = 0x1376c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1376c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_cpas_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_slow_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_cpas_bps_clk = { + .halt_reg = 0x103b4, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x103b4, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_cpas_bps_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_bps_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_cpas_fast_ahb_clk = { + .halt_reg = 0x1377c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1377c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_cpas_fast_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_fast_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_cpas_ife_0_clk = { + .halt_reg = 0x11154, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x11154, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_cpas_ife_0_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ife_0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_cpas_ife_1_clk = { + .halt_reg = 0x12040, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x12040, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_cpas_ife_1_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ife_1_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_cpas_ife_lite_clk = { + .halt_reg = 0x1313c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1313c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_cpas_ife_lite_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ife_lite_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_cpas_ipe_nps_clk = { + .halt_reg = 0x1050c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1050c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_cpas_ipe_nps_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ipe_nps_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_csi0phytimer_clk = { + .halt_reg = 0x150f8, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x150f8, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_csi0phytimer_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_csi0phytimer_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_csi1phytimer_clk = { + .halt_reg = 0x1511c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1511c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_csi1phytimer_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_csi1phytimer_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_csi4phytimer_clk = { + .halt_reg = 0x15250, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x15250, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_csi4phytimer_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_csi4phytimer_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_csid_clk = { + .halt_reg = 0x137a4, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x137a4, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_csid_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_csid_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_csid_csiphy_rx_clk = { + .halt_reg = 0x15100, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x15100, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_csid_csiphy_rx_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_cphy_rx_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_csiphy0_clk = { + .halt_reg = 0x150fc, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x150fc, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_csiphy0_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_cphy_rx_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_csiphy1_clk = { + .halt_reg = 0x15120, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x15120, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_csiphy1_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_cphy_rx_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_csiphy4_clk = { + .halt_reg = 0x15254, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x15254, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_csiphy4_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_cphy_rx_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_icp_ahb_clk = { + .halt_reg = 0x13508, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x13508, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_icp_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_slow_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_icp_clk = { + .halt_reg = 0x134f8, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x134f8, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_icp_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_icp_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ife_0_clk = { + .halt_reg = 0x11144, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x11144, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_0_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ife_0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ife_0_dsp_clk = { + .halt_reg = 0x11158, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x11158, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_0_dsp_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ife_0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ife_0_fast_ahb_clk = { + .halt_reg = 0x11164, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x11164, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_0_fast_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_fast_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ife_1_clk = { + .halt_reg = 0x12030, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x12030, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_1_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ife_1_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ife_1_dsp_clk = { + .halt_reg = 0x12044, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x12044, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_1_dsp_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ife_1_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ife_1_fast_ahb_clk = { + .halt_reg = 0x12050, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x12050, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_1_fast_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_fast_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ife_lite_ahb_clk = { + .halt_reg = 0x13280, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x13280, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_lite_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_slow_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ife_lite_clk = { + .halt_reg = 0x1312c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1312c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_lite_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ife_lite_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ife_lite_cphy_rx_clk = { + .halt_reg = 0x1327c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1327c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_lite_cphy_rx_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_cphy_rx_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ife_lite_csid_clk = { + .halt_reg = 0x1326c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1326c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_lite_csid_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ife_lite_csid_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ipe_nps_ahb_clk = { + .halt_reg = 0x10528, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x10528, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ipe_nps_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_slow_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ipe_nps_clk = { + .halt_reg = 0x104fc, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x104fc, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ipe_nps_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ipe_nps_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ipe_nps_fast_ahb_clk = { + .halt_reg = 0x1052c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1052c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ipe_nps_fast_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_fast_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ipe_pps_clk = { + .halt_reg = 0x10510, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x10510, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ipe_pps_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ipe_nps_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ipe_pps_fast_ahb_clk = { + .halt_reg = 0x10530, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x10530, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ipe_pps_fast_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_fast_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_jpeg_clk = { + .halt_reg = 0x133b0, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x133b0, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_jpeg_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_jpeg_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_mclk0_clk = { + .halt_reg = 0x15018, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x15018, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_mclk0_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_mclk0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_mclk1_clk = { + .halt_reg = 0x15034, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x15034, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_mclk1_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_mclk1_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_mclk2_clk = { + .halt_reg = 0x15050, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x15050, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_mclk2_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_mclk2_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_mclk3_clk = { + .halt_reg = 0x1506c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1506c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_mclk3_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_mclk3_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_mclk4_clk = { + .halt_reg = 0x15088, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x15088, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_mclk4_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_mclk4_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_mclk5_clk = { + .halt_reg = 0x150a4, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x150a4, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_mclk5_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_mclk5_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_mclk6_clk = { + .halt_reg = 0x150c0, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x150c0, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_mclk6_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_mclk6_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_mclk7_clk = { + .halt_reg = 0x150dc, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x150dc, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_mclk7_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_mclk7_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_qdss_debug_clk = { + .halt_reg = 0x13928, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x13928, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_qdss_debug_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_qdss_debug_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_qdss_debug_xo_clk = { + .halt_reg = 0x1392c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1392c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_qdss_debug_xo_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_xo_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct gdsc cam_cc_titan_top_gdsc = { + .gdscr = 0x13930, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0xf, + .pd = { + .name = "cam_cc_titan_top_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct gdsc cam_cc_bps_gdsc = { + .gdscr = 0x10004, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0xf, + .pd = { + .name = "cam_cc_bps_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .parent = &cam_cc_titan_top_gdsc.pd, + .flags = HW_CTRL_TRIGGER | POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct gdsc cam_cc_ife_0_gdsc = { + .gdscr = 0x11004, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0xf, + .pd = { + .name = "cam_cc_ife_0_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .parent = &cam_cc_titan_top_gdsc.pd, + .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct gdsc cam_cc_ife_1_gdsc = { + .gdscr = 0x12004, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0xf, + .pd = { + .name = "cam_cc_ife_1_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .parent = &cam_cc_titan_top_gdsc.pd, + .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct gdsc cam_cc_ipe_0_gdsc = { + .gdscr = 0x103bc, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0xf, + .pd = { + .name = "cam_cc_ipe_0_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .parent = &cam_cc_titan_top_gdsc.pd, + .flags = HW_CTRL_TRIGGER | POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct clk_regmap *cam_cc_glymur_clocks[] = { + [CAM_CC_BPS_AHB_CLK] = &cam_cc_bps_ahb_clk.clkr, + [CAM_CC_BPS_CLK] = &cam_cc_bps_clk.clkr, + [CAM_CC_BPS_CLK_SRC] = &cam_cc_bps_clk_src.clkr, + [CAM_CC_BPS_FAST_AHB_CLK] = &cam_cc_bps_fast_ahb_clk.clkr, + [CAM_CC_CAMNOC_AXI_NRT_CLK] = &cam_cc_camnoc_axi_nrt_clk.clkr, + [CAM_CC_CAMNOC_AXI_RT_CLK] = &cam_cc_camnoc_axi_rt_clk.clkr, + [CAM_CC_CAMNOC_AXI_RT_CLK_SRC] = &cam_cc_camnoc_axi_rt_clk_src.clkr, + [CAM_CC_CAMNOC_DCD_XO_CLK] = &cam_cc_camnoc_dcd_xo_clk.clkr, + [CAM_CC_CAMNOC_XO_CLK] = &cam_cc_camnoc_xo_clk.clkr, + [CAM_CC_CCI_0_CLK] = &cam_cc_cci_0_clk.clkr, + [CAM_CC_CCI_0_CLK_SRC] = &cam_cc_cci_0_clk_src.clkr, + [CAM_CC_CCI_1_CLK] = &cam_cc_cci_1_clk.clkr, + [CAM_CC_CCI_1_CLK_SRC] = &cam_cc_cci_1_clk_src.clkr, + [CAM_CC_CORE_AHB_CLK] = &cam_cc_core_ahb_clk.clkr, + [CAM_CC_CPAS_AHB_CLK] = &cam_cc_cpas_ahb_clk.clkr, + [CAM_CC_CPAS_BPS_CLK] = &cam_cc_cpas_bps_clk.clkr, + [CAM_CC_CPAS_FAST_AHB_CLK] = &cam_cc_cpas_fast_ahb_clk.clkr, + [CAM_CC_CPAS_IFE_0_CLK] = &cam_cc_cpas_ife_0_clk.clkr, + [CAM_CC_CPAS_IFE_1_CLK] = &cam_cc_cpas_ife_1_clk.clkr, + [CAM_CC_CPAS_IFE_LITE_CLK] = &cam_cc_cpas_ife_lite_clk.clkr, + [CAM_CC_CPAS_IPE_NPS_CLK] = &cam_cc_cpas_ipe_nps_clk.clkr, + [CAM_CC_CPHY_RX_CLK_SRC] = &cam_cc_cphy_rx_clk_src.clkr, + [CAM_CC_CSI0PHYTIMER_CLK] = &cam_cc_csi0phytimer_clk.clkr, + [CAM_CC_CSI0PHYTIMER_CLK_SRC] = &cam_cc_csi0phytimer_clk_src.clkr, + [CAM_CC_CSI1PHYTIMER_CLK] = &cam_cc_csi1phytimer_clk.clkr, + [CAM_CC_CSI1PHYTIMER_CLK_SRC] = &cam_cc_csi1phytimer_clk_src.clkr, + [CAM_CC_CSI4PHYTIMER_CLK] = &cam_cc_csi4phytimer_clk.clkr, + [CAM_CC_CSI4PHYTIMER_CLK_SRC] = &cam_cc_csi4phytimer_clk_src.clkr, + [CAM_CC_CSID_CLK] = &cam_cc_csid_clk.clkr, + [CAM_CC_CSID_CLK_SRC] = &cam_cc_csid_clk_src.clkr, + [CAM_CC_CSID_CSIPHY_RX_CLK] = &cam_cc_csid_csiphy_rx_clk.clkr, + [CAM_CC_CSIPHY0_CLK] = &cam_cc_csiphy0_clk.clkr, + [CAM_CC_CSIPHY1_CLK] = &cam_cc_csiphy1_clk.clkr, + [CAM_CC_CSIPHY4_CLK] = &cam_cc_csiphy4_clk.clkr, + [CAM_CC_FAST_AHB_CLK_SRC] = &cam_cc_fast_ahb_clk_src.clkr, + [CAM_CC_ICP_AHB_CLK] = &cam_cc_icp_ahb_clk.clkr, + [CAM_CC_ICP_CLK] = &cam_cc_icp_clk.clkr, + [CAM_CC_ICP_CLK_SRC] = &cam_cc_icp_clk_src.clkr, + [CAM_CC_IFE_0_CLK] = &cam_cc_ife_0_clk.clkr, + [CAM_CC_IFE_0_CLK_SRC] = &cam_cc_ife_0_clk_src.clkr, + [CAM_CC_IFE_0_DSP_CLK] = &cam_cc_ife_0_dsp_clk.clkr, + [CAM_CC_IFE_0_FAST_AHB_CLK] = &cam_cc_ife_0_fast_ahb_clk.clkr, + [CAM_CC_IFE_1_CLK] = &cam_cc_ife_1_clk.clkr, + [CAM_CC_IFE_1_CLK_SRC] = &cam_cc_ife_1_clk_src.clkr, + [CAM_CC_IFE_1_DSP_CLK] = &cam_cc_ife_1_dsp_clk.clkr, + [CAM_CC_IFE_1_FAST_AHB_CLK] = &cam_cc_ife_1_fast_ahb_clk.clkr, + [CAM_CC_IFE_LITE_AHB_CLK] = &cam_cc_ife_lite_ahb_clk.clkr, + [CAM_CC_IFE_LITE_CLK] = &cam_cc_ife_lite_clk.clkr, + [CAM_CC_IFE_LITE_CLK_SRC] = &cam_cc_ife_lite_clk_src.clkr, + [CAM_CC_IFE_LITE_CPHY_RX_CLK] = &cam_cc_ife_lite_cphy_rx_clk.clkr, + [CAM_CC_IFE_LITE_CSID_CLK] = &cam_cc_ife_lite_csid_clk.clkr, + [CAM_CC_IFE_LITE_CSID_CLK_SRC] = &cam_cc_ife_lite_csid_clk_src.clkr, + [CAM_CC_IPE_NPS_AHB_CLK] = &cam_cc_ipe_nps_ahb_clk.clkr, + [CAM_CC_IPE_NPS_CLK] = &cam_cc_ipe_nps_clk.clkr, + [CAM_CC_IPE_NPS_CLK_SRC] = &cam_cc_ipe_nps_clk_src.clkr, + [CAM_CC_IPE_NPS_FAST_AHB_CLK] = &cam_cc_ipe_nps_fast_ahb_clk.clkr, + [CAM_CC_IPE_PPS_CLK] = &cam_cc_ipe_pps_clk.clkr, + [CAM_CC_IPE_PPS_FAST_AHB_CLK] = &cam_cc_ipe_pps_fast_ahb_clk.clkr, + [CAM_CC_JPEG_CLK] = &cam_cc_jpeg_clk.clkr, + [CAM_CC_JPEG_CLK_SRC] = &cam_cc_jpeg_clk_src.clkr, + [CAM_CC_MCLK0_CLK] = &cam_cc_mclk0_clk.clkr, + [CAM_CC_MCLK0_CLK_SRC] = &cam_cc_mclk0_clk_src.clkr, + [CAM_CC_MCLK1_CLK] = &cam_cc_mclk1_clk.clkr, + [CAM_CC_MCLK1_CLK_SRC] = &cam_cc_mclk1_clk_src.clkr, + [CAM_CC_MCLK2_CLK] = &cam_cc_mclk2_clk.clkr, + [CAM_CC_MCLK2_CLK_SRC] = &cam_cc_mclk2_clk_src.clkr, + [CAM_CC_MCLK3_CLK] = &cam_cc_mclk3_clk.clkr, + [CAM_CC_MCLK3_CLK_SRC] = &cam_cc_mclk3_clk_src.clkr, + [CAM_CC_MCLK4_CLK] = &cam_cc_mclk4_clk.clkr, + [CAM_CC_MCLK4_CLK_SRC] = &cam_cc_mclk4_clk_src.clkr, + [CAM_CC_MCLK5_CLK] = &cam_cc_mclk5_clk.clkr, + [CAM_CC_MCLK5_CLK_SRC] = &cam_cc_mclk5_clk_src.clkr, + [CAM_CC_MCLK6_CLK] = &cam_cc_mclk6_clk.clkr, + [CAM_CC_MCLK6_CLK_SRC] = &cam_cc_mclk6_clk_src.clkr, + [CAM_CC_MCLK7_CLK] = &cam_cc_mclk7_clk.clkr, + [CAM_CC_MCLK7_CLK_SRC] = &cam_cc_mclk7_clk_src.clkr, + [CAM_CC_PLL0] = &cam_cc_pll0.clkr, + [CAM_CC_PLL0_OUT_EVEN] = &cam_cc_pll0_out_even.clkr, + [CAM_CC_PLL0_OUT_ODD] = &cam_cc_pll0_out_odd.clkr, + [CAM_CC_PLL1] = &cam_cc_pll1.clkr, + [CAM_CC_PLL1_OUT_EVEN] = &cam_cc_pll1_out_even.clkr, + [CAM_CC_PLL2] = &cam_cc_pll2.clkr, + [CAM_CC_PLL3] = &cam_cc_pll3.clkr, + [CAM_CC_PLL3_OUT_EVEN] = &cam_cc_pll3_out_even.clkr, + [CAM_CC_PLL4] = &cam_cc_pll4.clkr, + [CAM_CC_PLL4_OUT_EVEN] = &cam_cc_pll4_out_even.clkr, + [CAM_CC_PLL5] = &cam_cc_pll5.clkr, + [CAM_CC_PLL5_OUT_EVEN] = &cam_cc_pll5_out_even.clkr, + [CAM_CC_QDSS_DEBUG_CLK] = &cam_cc_qdss_debug_clk.clkr, + [CAM_CC_QDSS_DEBUG_CLK_SRC] = &cam_cc_qdss_debug_clk_src.clkr, + [CAM_CC_QDSS_DEBUG_XO_CLK] = &cam_cc_qdss_debug_xo_clk.clkr, + [CAM_CC_SLEEP_CLK_SRC] = &cam_cc_sleep_clk_src.clkr, + [CAM_CC_SLOW_AHB_CLK_SRC] = &cam_cc_slow_ahb_clk_src.clkr, + [CAM_CC_XO_CLK_SRC] = &cam_cc_xo_clk_src.clkr, +}; + +static struct gdsc *cam_cc_glymur_gdscs[] = { + [CAM_CC_BPS_GDSC] = &cam_cc_bps_gdsc, + [CAM_CC_IFE_0_GDSC] = &cam_cc_ife_0_gdsc, + [CAM_CC_IFE_1_GDSC] = &cam_cc_ife_1_gdsc, + [CAM_CC_IPE_0_GDSC] = &cam_cc_ipe_0_gdsc, + [CAM_CC_TITAN_TOP_GDSC] = &cam_cc_titan_top_gdsc, +}; + +static const struct qcom_reset_map cam_cc_glymur_resets[] = { + [CAM_CC_BPS_BCR] = { 0x10000 }, + [CAM_CC_ICP_BCR] = { 0x133c8 }, + [CAM_CC_IFE_0_BCR] = { 0x11000 }, + [CAM_CC_IFE_1_BCR] = { 0x12000 }, + [CAM_CC_IPE_0_BCR] = { 0x103b8 }, + [CAM_CC_QDSS_DEBUG_BCR] = { 0x137f8 }, +}; + +static struct clk_alpha_pll *cam_cc_glymur_plls[] = { + &cam_cc_pll0, + &cam_cc_pll1, + &cam_cc_pll2, + &cam_cc_pll3, + &cam_cc_pll4, + &cam_cc_pll5, +}; + +static u32 cam_cc_glymur_critical_cbcrs[] = { + 0x13960, /* CAM_CC_GDSC_CLK */ + 0x1397c, /* CAM_CC_SLEEP_CLK */ +}; + +static const struct regmap_config cam_cc_glymur_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x1603c, + .fast_io = true, +}; + +static struct qcom_cc_driver_data cam_cc_glymur_driver_data = { + .alpha_plls = cam_cc_glymur_plls, + .num_alpha_plls = ARRAY_SIZE(cam_cc_glymur_plls), + .clk_cbcrs = cam_cc_glymur_critical_cbcrs, + .num_clk_cbcrs = ARRAY_SIZE(cam_cc_glymur_critical_cbcrs), +}; + +static const struct qcom_cc_desc cam_cc_glymur_desc = { + .config = &cam_cc_glymur_regmap_config, + .clks = cam_cc_glymur_clocks, + .num_clks = ARRAY_SIZE(cam_cc_glymur_clocks), + .resets = cam_cc_glymur_resets, + .num_resets = ARRAY_SIZE(cam_cc_glymur_resets), + .gdscs = cam_cc_glymur_gdscs, + .num_gdscs = ARRAY_SIZE(cam_cc_glymur_gdscs), + .use_rpm = true, + .driver_data = &cam_cc_glymur_driver_data, +}; + +static const struct of_device_id cam_cc_glymur_match_table[] = { + { .compatible = "qcom,glymur-camcc" }, + { } +}; +MODULE_DEVICE_TABLE(of, cam_cc_glymur_match_table); + +static int cam_cc_glymur_probe(struct platform_device *pdev) +{ + return qcom_cc_probe(pdev, &cam_cc_glymur_desc); +} + +static struct platform_driver cam_cc_glymur_driver = { + .probe = cam_cc_glymur_probe, + .driver = { + .name = "camcc-glymur", + .of_match_table = cam_cc_glymur_match_table, + }, +}; + +module_platform_driver(cam_cc_glymur_driver); + +MODULE_DESCRIPTION("QTI CAMCC GLYMUR Driver"); +MODULE_LICENSE("GPL"); From 2ce505b437e98adc3c009d22762f491cd2cb15ee Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 3 Jul 2026 12:03:59 +0200 Subject: [PATCH 0129/1328] clk: qcom: Restrict IPQ5424, IPQ6018,IPQ9574, QCM2290 and others to ARM64 Some clock drivers for IPQ5424, IPQ9574, QCM2290, QDU1000 and SA8775 already depend on ARM64. IPQ6018 is ARM64 only SoC (at least upstream) so should not be a choice for ARM 32-bit builds, to make kernels smaller and user choices easier. IPQ_CMN_PLL is used only by the SoCs already having clock controllers restricted to ARM64 Reviewed-by: Dmitry Baryshkov Reviewed-by: Konrad Dybcio Signed-off-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260703-clk-qcom-defaults-v3-1-78894525e54a@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/Kconfig | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig index 856d86ee52cb..d5fcdff954db 100644 --- a/drivers/clk/qcom/Kconfig +++ b/drivers/clk/qcom/Kconfig @@ -405,6 +405,7 @@ config IPQ_APSS_PLL config IPQ_APSS_5424 tristate "IPQ5424 APSS Clock Controller" + depends on ARM64 || COMPILE_TEST select IPQ_APSS_PLL default y if IPQ_GCC_5424 help @@ -415,6 +416,7 @@ config IPQ_APSS_5424 config IPQ_APSS_6018 tristate "IPQ6018 APSS Clock Controller" select IPQ_APSS_PLL + depends on ARM64 || COMPILE_TEST depends on QCOM_APCS_IPC || COMPILE_TEST depends on QCOM_SMEM help @@ -426,6 +428,7 @@ config IPQ_APSS_6018 config IPQ_CMN_PLL tristate "IPQ CMN PLL Clock Controller" + depends on ARM64 || COMPILE_TEST help Support for CMN PLL clock controller on IPQ platform. The CMN PLL consumes the AHB/SYS clocks from GCC and supplies @@ -474,6 +477,7 @@ config IPQ_GCC_5424 config IPQ_GCC_6018 tristate "IPQ6018 Global Clock Controller" + depends on ARM64 || COMPILE_TEST help Support for global clock controller on ipq6018 devices. Say Y if you want to use peripheral devices such as UART, SPI, @@ -499,6 +503,7 @@ config IPQ_LCC_806X config IPQ_GCC_8074 tristate "IPQ8074 Global Clock Controller" + depends on ARM64 || COMPILE_TEST select QCOM_GDSC help Support for global clock controller on ipq8074 devices. @@ -508,6 +513,7 @@ config IPQ_GCC_8074 config IPQ_GCC_9574 tristate "IPQ9574 Global Clock Controller" + depends on ARM64 || COMPILE_TEST help Support for global clock controller on ipq9574 devices. Say Y if you want to use peripheral devices such as UART, SPI, @@ -732,6 +738,7 @@ config MSM_MMCC_8998 config QCM_GCC_2290 tristate "QCM2290 Global Clock Controller" + depends on ARM64 || COMPILE_TEST select QCOM_GDSC help Support for the global clock controller on QCM2290 devices. @@ -740,6 +747,7 @@ config QCM_GCC_2290 config QCM_DISPCC_2290 tristate "QCM2290 Display Clock Controller" + depends on ARM64 || COMPILE_TEST select QCM_GCC_2290 help Support for the display clock controller on Qualcomm Technologies, Inc @@ -911,6 +919,7 @@ config SC_DISPCC_8280XP config SA_GCC_8775P tristate "SA8775 Global Clock Controller" select QCOM_GDSC + depends on ARM64 || COMPILE_TEST depends on COMMON_CLK_QCOM help Support for the global clock controller on SA8775 devices. @@ -919,6 +928,7 @@ config SA_GCC_8775P config SA_GPUCC_8775P tristate "SA8775P Graphics clock controller" + depends on ARM64 || COMPILE_TEST select QCOM_GDSC select SA_GCC_8775P help @@ -1121,6 +1131,7 @@ config QCS_Q6SSTOP_404 config QDU_GCC_1000 tristate "QDU1000/QRU1000 Global Clock Controller" + depends on ARM64 || COMPILE_TEST select QCOM_GDSC help Support for the global clock controller on QDU1000 and From 9904402cf417140f0dbdd0b9b3b5f5fd0527a447 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 3 Jul 2026 12:04:00 +0200 Subject: [PATCH 0130/1328] clk: qcom: Restrict A7PLL and IPQ4019 GCC to ARM IPQ4019 is ARM 32-bit only SoC and QCOM_A7PLL is used only on SDX55 and SDX65, which are 32-bit as well. Do not allow building them for ARM64 to make built kernels smaller and user choices easier. Reviewed-by: Konrad Dybcio Reviewed-by: Dmitry Baryshkov Signed-off-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260703-clk-qcom-defaults-v3-2-78894525e54a@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/Kconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig index d5fcdff954db..5ec1da999e52 100644 --- a/drivers/clk/qcom/Kconfig +++ b/drivers/clk/qcom/Kconfig @@ -282,6 +282,7 @@ config QCOM_A53PLL config QCOM_A7PLL tristate "A7 PLL driver for SDX55 and SDX65" + depends on ARM || COMPILE_TEST help Support for the A7 PLL on SDX55 and SDX65 devices. It provides the CPU with frequencies above 1GHz. @@ -438,6 +439,7 @@ config IPQ_CMN_PLL config IPQ_GCC_4019 tristate "IPQ4019 Global Clock Controller" + depends on ARM || COMPILE_TEST help Support for the global clock controller on ipq4019 devices. Say Y if you want to use peripheral devices such as UART, SPI, From 5c38f9b7df7716c2ff54418d6a0aa4328133a58e Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 3 Jul 2026 12:04:01 +0200 Subject: [PATCH 0131/1328] clk: qcom: Make important ARM64 drivers default Clock controller drivers 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'. Switch all Qualcomm clock controller drivers which are chosen in defconfig to respective default 'yes' or 'module'. This has no impact on ARM64 defconfig include/generated/autoconf.h, however changes few drivers for ARM 32-bit: 1. multi_v7_defconfig: Enable QCOM_CLK_RPMH as module, because SDX55 (ARM 32-bit) uses it. 2. qcom_defconfig: Enable QCOM_A53PLL, QCOM_CLK_APCS_MSM8916 and MSM_GCC_8916 as built-in, because MSM8916 is also used ARM 32-bit. Reviewed-by: Konrad Dybcio Signed-off-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260703-clk-qcom-defaults-v3-3-78894525e54a@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/Kconfig | 122 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig index 5ec1da999e52..0e0167fd1f6d 100644 --- a/drivers/clk/qcom/Kconfig +++ b/drivers/clk/qcom/Kconfig @@ -23,6 +23,7 @@ config CLK_ELIZA_DISPCC tristate "Eliza Display Clock Controller" depends on ARM64 || COMPILE_TEST select CLK_ELIZA_GCC + default m if ARCH_QCOM help Support for the display clock controllers on Eliza SoCs. Say Y if you want to support display devices and functionality such as @@ -32,6 +33,7 @@ config CLK_ELIZA_GCC tristate "Eliza Global Clock Controller" depends on ARM64 || COMPILE_TEST select QCOM_GDSC + default ARCH_QCOM help Support for the global clock controller on Eliza devices. Say Y if you want to use peripheral devices such as UART, SPI, @@ -41,6 +43,7 @@ config CLK_ELIZA_TCSRCC tristate "Eliza TCSR Clock Controller" depends on ARM64 || COMPILE_TEST select QCOM_GDSC + default m if ARCH_QCOM help Support for the TCSR clock controller on Eliza devices. Say Y if you want to use peripheral devices such as USB/PCIe/UFS. @@ -60,6 +63,7 @@ config CLK_GLYMUR_DISPCC tristate "Glymur Display Clock Controller" depends on ARM64 || COMPILE_TEST select CLK_GLYMUR_GCC + default m if ARCH_QCOM help Support for the display clock controllers on Qualcomm Technologies, Inc. Glymur devices. @@ -81,6 +85,7 @@ config CLK_GLYMUR_GCC tristate "Glymur Global Clock Controller" depends on ARM64 || COMPILE_TEST select QCOM_GDSC + default ARCH_QCOM help Support for the global clock controller on Glymur devices. Say Y if you want to use peripheral devices such as UART, SPI, @@ -99,6 +104,7 @@ config CLK_GLYMUR_TCSRCC tristate "Glymur TCSR Clock Controller" depends on ARM64 || COMPILE_TEST select QCOM_GDSC + default m if ARCH_QCOM help Support for the TCSR clock controller on Glymur devices. Say Y if you want to use peripheral devices such as USB/PCIe/EDP. @@ -116,6 +122,7 @@ config CLK_KAANAPALI_CAMCC tristate "Kaanapali Camera Clock Controller" depends on ARM64 || COMPILE_TEST select CLK_KAANAPALI_GCC + default m if ARCH_QCOM help Support for the camera clock controller on Qualcomm Technologies, Inc Kaanapali devices. @@ -126,6 +133,7 @@ config CLK_KAANAPALI_DISPCC tristate "Kaanapali Display Clock Controller" depends on ARM64 || COMPILE_TEST select CLK_KAANAPALI_GCC + default m if ARCH_QCOM help Support for the display clock controller on Qualcomm Technologies, Inc Kaanapali devices. @@ -136,6 +144,7 @@ config CLK_KAANAPALI_GCC tristate "Kaanapali Global Clock Controller" depends on ARM64 || COMPILE_TEST select QCOM_GDSC + default ARCH_QCOM help Support for the global clock controller on Kaanapali devices. Say Y if you want to use peripheral devices such as UART, @@ -145,6 +154,7 @@ config CLK_KAANAPALI_GPUCC tristate "Kaanapali Graphics Clock Controller" depends on ARM64 || COMPILE_TEST select CLK_KAANAPALI_GCC + default m if ARCH_QCOM help Support for the graphics clock controller on Kaanapali devices. Say Y if you want to support graphics controller devices and @@ -154,6 +164,7 @@ config CLK_KAANAPALI_TCSRCC tristate "Kaanapali TCSR Clock Controller" depends on ARM64 || COMPILE_TEST select QCOM_GDSC + default m if ARCH_QCOM help Support for the TCSR clock controller on Kaanapali devices. Say Y if you want to use peripheral devices such as PCIe, USB, UFS. @@ -162,6 +173,7 @@ config CLK_KAANAPALI_VIDEOCC tristate "Kaanapali Video Clock Controller" depends on ARM64 || COMPILE_TEST select CLK_KAANAPALI_GCC + default m if ARCH_QCOM help Support for the video clock controller on Kaanapali devices. Say Y if you want to support video devices and functionality such as @@ -191,6 +203,7 @@ config CLK_X1E80100_CAMCC tristate "X1E80100 Camera Clock Controller" depends on ARM64 || COMPILE_TEST select CLK_X1E80100_GCC + default m if ARCH_QCOM help Support for the camera clock controller on X1E80100 devices. Say Y if you want to support camera devices and camera functionality. @@ -199,6 +212,7 @@ config CLK_X1E80100_DISPCC tristate "X1E80100 Display Clock Controller" depends on ARM64 || COMPILE_TEST select CLK_X1E80100_GCC + default m if ARCH_QCOM help Support for the two display clock controllers on Qualcomm Technologies, Inc. X1E80100 devices. @@ -209,6 +223,7 @@ config CLK_X1E80100_GCC tristate "X1E80100 Global Clock Controller" depends on ARM64 || COMPILE_TEST select QCOM_GDSC + default ARCH_QCOM help Support for the global clock controller on Qualcomm Technologies, Inc X1E80100 devices. @@ -219,6 +234,7 @@ config CLK_X1E80100_GPUCC tristate "X1E80100 Graphics Clock Controller" depends on ARM64 || COMPILE_TEST select CLK_X1E80100_GCC + default m if ARCH_QCOM help Support for the graphics clock controller on X1E80100 devices. Say Y if you want to support graphics controller devices and @@ -228,6 +244,7 @@ config CLK_X1E80100_TCSRCC tristate "X1E80100 TCSR Clock Controller" depends on ARM64 || COMPILE_TEST select QCOM_GDSC + default ARCH_QCOM help Support for the TCSR clock controller on X1E80100 devices. Say Y if you want to use peripheral devices such as SD/UFS. @@ -247,6 +264,7 @@ config CLK_X1P42100_GPUCC tristate "X1P42100 Graphics Clock Controller" depends on ARM64 || COMPILE_TEST select CLK_X1E80100_GCC + default m if ARCH_QCOM help Support for the graphics clock controller on X1P42100 devices. Say Y if you want to support graphics controller devices and @@ -267,6 +285,7 @@ config CLK_QCM2290_GPUCC tristate "QCM2290 Graphics Clock Controller" depends on ARM64 || COMPILE_TEST select QCM_GCC_2290 + default m if ARCH_QCOM help Support for the graphics clock controller on QCM2290 devices. Say Y if you want to support graphics controller devices and @@ -274,6 +293,7 @@ config CLK_QCM2290_GPUCC config QCOM_A53PLL tristate "MSM8916 A53 PLL" + default ARCH_QCOM help Support for the A53 PLL on MSM8916 devices. It provides the CPU with frequencies above 1GHz. @@ -292,6 +312,7 @@ config QCOM_A7PLL config QCOM_CLK_APCS_MSM8916 tristate "MSM8916 APCS Clock Controller" depends on QCOM_APCS_IPC || COMPILE_TEST + default ARCH_QCOM help Support for the APCS Clock Controller on msm8916 devices. The APCS is managing the mux and divider which feeds the CPUs. @@ -303,6 +324,7 @@ config QCOM_CLK_APCC_MSM8996 select QCOM_KRYO_L2_ACCESSORS select INTERCONNECT_CLK if INTERCONNECT depends on ARM64 + default ARCH_QCOM help Support for the CPU clock controller on msm8996 devices. Say Y if you want to support CPU clock scaling using CPUfreq @@ -333,6 +355,7 @@ config QCOM_CLK_RPM config QCOM_CLK_SMD_RPM tristate "RPM over SMD based Clock Controller" depends on QCOM_SMD_RPM + default ARCH_QCOM help The RPM (Resource Power Manager) is a dedicated hardware engine for managing the shared SoC resources in order to keep the lowest power @@ -345,6 +368,7 @@ config QCOM_CLK_SMD_RPM config QCOM_CLK_RPMH tristate "RPMh Clock Driver" depends on QCOM_RPMH + default ARCH_QCOM help RPMh manages shared resources on some Qualcomm Technologies, Inc. SoCs. It accepts requests from other hardware subsystems via RSC. @@ -420,6 +444,7 @@ config IPQ_APSS_6018 depends on ARM64 || COMPILE_TEST depends on QCOM_APCS_IPC || COMPILE_TEST depends on QCOM_SMEM + default ARCH_QCOM help Support for APSS clock controller on IPQ platforms. The APSS clock controller manages the Mux and enable block that feeds the @@ -430,6 +455,7 @@ config IPQ_APSS_6018 config IPQ_CMN_PLL tristate "IPQ CMN PLL Clock Controller" depends on ARM64 || COMPILE_TEST + default m if ARCH_QCOM help Support for CMN PLL clock controller on IPQ platform. The CMN PLL consumes the AHB/SYS clocks from GCC and supplies @@ -448,6 +474,7 @@ config IPQ_GCC_4019 config IPQ_GCC_5018 tristate "IPQ5018 Global Clock Controller" depends on ARM || ARM64 || COMPILE_TEST + default ARCH_QCOM && ARM64 help Support for global clock controller on ipq5018 devices. Say Y if you want to use peripheral devices such as UART, SPI, @@ -456,6 +483,7 @@ config IPQ_GCC_5018 config IPQ_GCC_5210 tristate "IPQ5210 Global Clock Controller" depends on ARM64 || COMPILE_TEST + default ARCH_QCOM help Support for the global clock controller on ipq5210 devices. Say Y if you want to use peripheral devices such as UART, SPI, @@ -464,6 +492,7 @@ config IPQ_GCC_5210 config IPQ_GCC_5332 tristate "IPQ5332 Global Clock Controller" depends on ARM64 || COMPILE_TEST + default ARCH_QCOM help Support for the global clock controller on ipq5332 devices. Say Y if you want to use peripheral devices such as UART, SPI, @@ -472,6 +501,7 @@ config IPQ_GCC_5332 config IPQ_GCC_5424 tristate "IPQ5424 Global Clock Controller" depends on ARM64 || COMPILE_TEST + default ARCH_QCOM help Support for the global clock controller on ipq5424 devices. Say Y if you want to use peripheral devices such as UART, SPI, @@ -480,6 +510,7 @@ config IPQ_GCC_5424 config IPQ_GCC_6018 tristate "IPQ6018 Global Clock Controller" depends on ARM64 || COMPILE_TEST + default ARCH_QCOM help Support for global clock controller on ipq6018 devices. Say Y if you want to use peripheral devices such as UART, SPI, @@ -507,6 +538,7 @@ config IPQ_GCC_8074 tristate "IPQ8074 Global Clock Controller" depends on ARM64 || COMPILE_TEST select QCOM_GDSC + default ARCH_QCOM help Support for global clock controller on ipq8074 devices. Say Y if you want to use peripheral devices such as UART, SPI, @@ -516,6 +548,7 @@ config IPQ_GCC_8074 config IPQ_GCC_9574 tristate "IPQ9574 Global Clock Controller" depends on ARM64 || COMPILE_TEST + default ARCH_QCOM help Support for global clock controller on ipq9574 devices. Say Y if you want to use peripheral devices such as UART, SPI, @@ -536,6 +569,7 @@ config IPQ_NSSCC_5424 tristate "IPQ5424 NSS Clock Controller" depends on ARM64 || COMPILE_TEST depends on IPQ_GCC_5424 + default m if ARCH_QCOM help Support for NSS clock controller on ipq5424 devices. NSSCC receives the clock sources from GCC, CMN PLL and UNIPHY (PCS). @@ -547,6 +581,7 @@ config IPQ_NSSCC_9574 tristate "IPQ9574 NSS Clock Controller" depends on ARM64 || COMPILE_TEST depends on IPQ_GCC_9574 + default m if ARCH_QCOM help Support for NSS clock controller on ipq9574 devices. @@ -579,6 +614,7 @@ config MSM_GCC_8909 config MSM_GCC_8916 tristate "MSM8916 Global Clock Controller" select QCOM_GDSC + default ARCH_QCOM help Support for the global clock controller on msm8916 devices. Say Y if you want to use devices such as UART, SPI i2c, USB, @@ -647,6 +683,7 @@ config MSM_MMCC_8960 config MSM_GCC_8953 tristate "MSM8953 Global Clock Controller" select QCOM_GDSC + default ARCH_QCOM if ARM64 help Support for the global clock controller on msm8953 devices. Say Y if you want to use devices such as UART, SPI i2c, USB, @@ -683,6 +720,7 @@ config MSM_MMCC_8994 tristate "MSM8994 Multimedia Clock Controller" select MSM_GCC_8994 select QCOM_GDSC + default m if ARCH_QCOM && ARM64 help Support for the multimedia clock controller on msm8994 devices. Say Y if you want to support multimedia devices such as display, @@ -690,6 +728,7 @@ config MSM_MMCC_8994 config MSM_GCC_8994 tristate "MSM8994 Global Clock Controller" + default ARCH_QCOM if ARM64 help Support for the global clock controller on msm8994 devices. Say Y if you want to use peripheral devices such as UART, SPI, @@ -698,6 +737,7 @@ config MSM_GCC_8994 config MSM_GCC_8996 tristate "MSM8996 Global Clock Controller" select QCOM_GDSC + default ARCH_QCOM if ARM64 help Support for the global clock controller on msm8996 devices. Say Y if you want to use peripheral devices such as UART, SPI, @@ -707,6 +747,7 @@ config MSM_MMCC_8996 tristate "MSM8996 Multimedia Clock Controller" select MSM_GCC_8996 select QCOM_GDSC + default m if ARCH_QCOM && ARM64 help Support for the multimedia clock controller on msm8996 devices. Say Y if you want to support multimedia devices such as display, @@ -715,6 +756,7 @@ config MSM_MMCC_8996 config MSM_GCC_8998 tristate "MSM8998 Global Clock Controller" select QCOM_GDSC + default ARCH_QCOM if ARM64 help Support for the global clock controller on msm8998 devices. Say Y if you want to use peripheral devices such as UART, SPI, @@ -733,6 +775,7 @@ config MSM_MMCC_8998 tristate "MSM8998 Multimedia Clock Controller" select MSM_GCC_8998 select QCOM_GDSC + default m if ARCH_QCOM && ARM64 help Support for the multimedia clock controller on msm8998 devices. Say Y if you want to support multimedia devices such as display, @@ -742,6 +785,7 @@ config QCM_GCC_2290 tristate "QCM2290 Global Clock Controller" depends on ARM64 || COMPILE_TEST select QCOM_GDSC + default ARCH_QCOM help Support for the global clock controller on QCM2290 devices. Say Y if you want to use multimedia devices or peripheral @@ -751,6 +795,7 @@ config QCM_DISPCC_2290 tristate "QCM2290 Display Clock Controller" depends on ARM64 || COMPILE_TEST select QCM_GCC_2290 + default m if ARCH_QCOM help Support for the display clock controller on Qualcomm Technologies, Inc QCM2290 devices. @@ -761,6 +806,7 @@ config QCS_DISPCC_615 tristate "QCS615 Display Clock Controller" depends on ARM64 || COMPILE_TEST select QCS_GCC_615 + default m if ARCH_QCOM help Support for the display clock controller on Qualcomm Technologies, Inc QCS615 devices. @@ -771,6 +817,7 @@ config QCS_CAMCC_615 tristate "QCS615 Camera Clock Controller" depends on ARM64 || COMPILE_TEST select QCS_GCC_615 + default m if ARCH_QCOM help Support for the camera clock controller on Qualcomm Technologies, Inc QCS615 devices. @@ -779,6 +826,7 @@ config QCS_CAMCC_615 config QCS_GCC_404 tristate "QCS404 Global Clock Controller" + default y if ARCH_QCOM && ARM64 help Support for the global clock controller on QCS404 devices. Say Y if you want to use multimedia devices or peripheral @@ -795,6 +843,7 @@ config SA_CAMCC_8775P tristate "SA8775P Camera Clock Controller" depends on ARM64 || COMPILE_TEST select SA_GCC_8775P + default m if ARCH_QCOM help Support for the camera clock controller on Qualcomm Technologies, Inc SA8775P devices. @@ -805,6 +854,7 @@ config QCS_GCC_8300 tristate "QCS8300 Global Clock Controller" depends on ARM64 || COMPILE_TEST select QCOM_GDSC + default ARCH_QCOM help Support for the global clock controller on Qualcomm Technologies, Inc QCS8300 devices. @@ -815,6 +865,7 @@ config QCS_GCC_615 tristate "QCS615 Global Clock Controller" depends on ARM64 || COMPILE_TEST select QCOM_GDSC + default ARCH_QCOM help Support for the global clock controller on QCS615 devices. Say Y if you want to use multimedia devices or peripheral @@ -824,6 +875,7 @@ config QCS_GPUCC_615 tristate "QCS615 Graphics clock controller" depends on ARM64 || COMPILE_TEST select QCS_GCC_615 + default m if ARCH_QCOM help Support for the graphics clock controller on QCS615 devices. Say Y if you want to support graphics controller devices and @@ -833,6 +885,7 @@ config QCS_VIDEOCC_615 tristate "QCS615 Video Clock Controller" depends on ARM64 || COMPILE_TEST select QCS_GCC_615 + default m if ARCH_QCOM help Support for the video clock controller on QCS615 devices. Say Y if you want to support video devices and functionality such as @@ -852,6 +905,7 @@ config SC_CAMCC_7280 tristate "SC7280 Camera Clock Controller" depends on ARM64 || COMPILE_TEST select SC_GCC_7280 + default m if ARCH_QCOM help Support for the camera clock controller on Qualcomm Technologies, Inc SC7280 devices. @@ -872,6 +926,7 @@ config SC_CAMCC_8280XP tristate "SC8280XP Camera Clock Controller" depends on ARM64 || COMPILE_TEST select SC_GCC_8280XP + default m if ARCH_QCOM help Support for the camera clock controller on Qualcomm Technologies, Inc SC8280XP devices. @@ -882,6 +937,7 @@ config SA_DISPCC_8775P tristate "SA8775P Display Clock Controller" depends on ARM64 || COMPILE_TEST select SA_GCC_8775P + default m if ARCH_QCOM help Support for the two display clock controllers on Qualcomm Technologies, Inc. SA8775P devices. @@ -902,6 +958,7 @@ config SC_DISPCC_7280 tristate "SC7280 Display Clock Controller" depends on ARM64 || COMPILE_TEST select SC_GCC_7280 + default m if ARCH_QCOM help Support for the display clock controller on Qualcomm Technologies, Inc. SC7280 devices. @@ -912,6 +969,7 @@ config SC_DISPCC_8280XP tristate "SC8280XP Display Clock Controller" depends on ARM64 || COMPILE_TEST select SC_GCC_8280XP + default m if ARCH_QCOM help Support for the two display clock controllers on Qualcomm Technologies, Inc. SC8280XP devices. @@ -923,6 +981,7 @@ config SA_GCC_8775P select QCOM_GDSC depends on ARM64 || COMPILE_TEST depends on COMMON_CLK_QCOM + default ARCH_QCOM help Support for the global clock controller on SA8775 devices. Say Y if you want to use peripheral devices such as UART, SPI, @@ -933,6 +992,7 @@ config SA_GPUCC_8775P depends on ARM64 || COMPILE_TEST select QCOM_GDSC select SA_GCC_8775P + default m if ARCH_QCOM help Support for the graphics clock controller on SA8775P devices. Say Y if you want to support graphics controller devices and @@ -961,6 +1021,7 @@ config SC_GCC_7180 select QCOM_GDSC depends on COMMON_CLK_QCOM depends on ARM64 || COMPILE_TEST + default ARCH_QCOM help Support for the global clock controller on SC7180 devices. Say Y if you want to use peripheral devices such as UART, SPI, @@ -971,6 +1032,7 @@ config SC_GCC_7280 select QCOM_GDSC depends on COMMON_CLK_QCOM depends on ARM64 || COMPILE_TEST + default ARCH_QCOM help Support for the global clock controller on SC7280 devices. Say Y if you want to use peripheral devices such as UART, SPI, @@ -981,6 +1043,7 @@ config SC_GCC_8180X select QCOM_GDSC depends on COMMON_CLK_QCOM depends on ARM64 || COMPILE_TEST + default ARCH_QCOM help Support for the global clock controller on SC8180X devices. Say Y if you want to use peripheral devices such as UART, SPI, @@ -991,6 +1054,7 @@ config SC_GCC_8280XP select QCOM_GDSC depends on COMMON_CLK_QCOM depends on ARM64 || COMPILE_TEST + default ARCH_QCOM help Support for the global clock controller on SC8280XP devices. Say Y if you want to use peripheral devices such as UART, SPI, @@ -1009,6 +1073,7 @@ config SC_GPUCC_7280 tristate "SC7280 Graphics Clock Controller" depends on ARM64 || COMPILE_TEST select SC_GCC_7280 + default m if ARCH_QCOM help Support for the graphics clock controller on SC7280 devices. Say Y if you want to support graphics controller devices and @@ -1018,6 +1083,7 @@ config SC_GPUCC_8280XP tristate "SC8280XP Graphics Clock Controller" depends on ARM64 || COMPILE_TEST select SC_GCC_8280XP + default m if ARCH_QCOM help Support for the graphics clock controller on SC8280XP devices. Say Y if you want to support graphics controller devices and @@ -1036,6 +1102,7 @@ config SC_LPASSCC_8280XP tristate "SC8280 Low Power Audio Subsystem (LPASS) Clock Controller" depends on ARM64 || COMPILE_TEST select SC_GCC_8280XP + default m if ARCH_QCOM help Support for the LPASS clock controller on SC8280XP devices. Say Y if you want to use the LPASS branch clocks of the LPASS clock @@ -1056,6 +1123,7 @@ config SC_LPASS_CORECC_7280 depends on ARM64 || COMPILE_TEST select SC_GCC_7280 select QCOM_GDSC + default m if ARCH_QCOM help Support for the LPASS(Low Power Audio Subsystem) core and audio clock controller on SC7280 devices. @@ -1075,6 +1143,7 @@ config SC_VIDEOCC_7280 tristate "SC7280 Video Clock Controller" depends on ARM64 || COMPILE_TEST select SC_GCC_7280 + default m if ARCH_QCOM help Support for the video clock controller on SC7280 devices. Say Y if you want to support video devices and functionality such as @@ -1084,6 +1153,7 @@ config SDM_CAMCC_845 tristate "SDM845 Camera Clock Controller" depends on ARM64 || COMPILE_TEST select SDM_GCC_845 + default m if ARCH_QCOM help Support for the camera clock controller on SDM845 devices. Say Y if you want to support camera devices and camera functionality. @@ -1135,6 +1205,7 @@ config QDU_GCC_1000 tristate "QDU1000/QRU1000 Global Clock Controller" depends on ARM64 || COMPILE_TEST select QCOM_GDSC + default ARCH_QCOM help Support for the global clock controller on QDU1000 and QRU1000 devices. Say Y if you want to use peripheral @@ -1153,6 +1224,7 @@ config SDM_GCC_845 tristate "SDM845/SDM670 Global Clock Controller" depends on ARM64 || COMPILE_TEST select QCOM_GDSC + default ARCH_QCOM help Support for the global clock controller on SDM845 and SDM670 devices. Say Y if you want to use peripheral devices such as UART, SPI, @@ -1162,6 +1234,7 @@ config SDM_GPUCC_845 tristate "SDM845 Graphics Clock Controller" depends on ARM64 || COMPILE_TEST select SDM_GCC_845 + default m if ARCH_QCOM help Support for the graphics clock controller on SDM845 devices. Say Y if you want to support graphics controller devices and @@ -1172,6 +1245,7 @@ config SDM_VIDEOCC_845 depends on ARM64 || COMPILE_TEST select SDM_GCC_845 select QCOM_GDSC + default m if ARCH_QCOM help Support for the video clock controller on SDM845 devices. Say Y if you want to support video devices and functionality such as @@ -1181,6 +1255,7 @@ config SDM_DISPCC_845 tristate "SDM845 Display Clock Controller" depends on ARM64 || COMPILE_TEST select SDM_GCC_845 + default m if ARCH_QCOM help Support for the display clock controller on Qualcomm Technologies, Inc SDM845 devices. @@ -1191,6 +1266,7 @@ config SDM_LPASSCC_845 tristate "SDM845 Low Power Audio Subsystem (LPAAS) Clock Controller" depends on ARM64 || COMPILE_TEST select SDM_GCC_845 + default m if ARCH_QCOM help Support for the LPASS clock controller on SDM845 devices. Say Y if you want to use the LPASS branch clocks of the LPASS clock @@ -1217,6 +1293,7 @@ config SDX_GCC_65 config SDX_GCC_75 tristate "SDX75 Global Clock Controller" select QCOM_GDSC + default ARCH_QCOM if ARM64 help Support for the global clock controller on SDX75 devices. Say Y if you want to use peripheral devices such as UART, @@ -1234,6 +1311,7 @@ config SM_CAMCC_6350 tristate "SM6350 Camera Clock Controller" depends on ARM64 || COMPILE_TEST select SM_GCC_6350 + default m if ARCH_QCOM help Support for the camera clock controller on SM6350 devices. Say Y if you want to support camera devices and camera functionality. @@ -1250,6 +1328,7 @@ config SM_CAMCC_MILOS tristate "Milos Camera Clock Controller" depends on ARM64 || COMPILE_TEST select SM_GCC_MILOS + default m if ARCH_QCOM help Support for the camera clock controller on Milos devices. Say Y if you want to support camera devices and camera functionality. @@ -1268,6 +1347,7 @@ config SM_CAMCC_8250 tristate "SM8250 Camera Clock Controller" depends on ARM64 || COMPILE_TEST select SM_GCC_8250 + default m if ARCH_QCOM help Support for the camera clock controller on SM8250 devices. Say Y if you want to support camera devices and camera functionality. @@ -1284,6 +1364,7 @@ config SM_CAMCC_8550 tristate "SM8550 Camera Clock Controller" depends on ARM64 || COMPILE_TEST select SM_GCC_8550 + default m if ARCH_QCOM help Support for the camera clock controller on SM8550 devices. Say Y if you want to support camera devices and camera functionality. @@ -1292,6 +1373,7 @@ config SM_CAMCC_8650 tristate "SM8650 Camera Clock Controller" depends on ARM64 || COMPILE_TEST select SM_GCC_8650 + default m if ARCH_QCOM help Support for the camera clock controller on SM8650 devices. Say Y if you want to support camera devices and camera functionality. @@ -1300,6 +1382,7 @@ config SM_CAMCC_8750 tristate "SM8750 Camera Clock Controller" depends on ARM64 || COMPILE_TEST select SM_GCC_8750 + default m if ARCH_QCOM help Support for the camera clock controller on SM8750 devices. The camera clock controller has a separate cambist controller which @@ -1320,6 +1403,7 @@ config SM_DISPCC_6115 tristate "SM6115 Display Clock Controller" depends on ARM64 || COMPILE_TEST depends on SM_GCC_6115 + default m if ARCH_QCOM help Support for the display clock controller on Qualcomm Technologies, Inc SM6115/SM4250 devices. @@ -1350,6 +1434,7 @@ config SM_DISPCC_8250 tristate "SM8150/SM8250/SM8350 Display Clock Controller" depends on ARM64 || COMPILE_TEST depends on SM_GCC_8150 || SM_GCC_8250 || SM_GCC_8350 + default m if ARCH_QCOM help Support for the display clock controller on Qualcomm Technologies, Inc SM8150/SM8250/SM8350 devices. @@ -1360,6 +1445,7 @@ config SM_DISPCC_6350 tristate "SM6350 Display Clock Controller" depends on ARM64 || COMPILE_TEST depends on SM_GCC_6350 + default m if ARCH_QCOM help Support for the display clock controller on Qualcomm Technologies, Inc SM6350 devices. @@ -1380,6 +1466,7 @@ config SM_DISPCC_MILOS tristate "Milos Display Clock Controller" depends on ARM64 || COMPILE_TEST depends on SM_GCC_MILOS + default m if ARCH_QCOM help Support for the display clock controller on Qualcomm Technologies, Inc Milos devices. @@ -1390,6 +1477,7 @@ config SM_DISPCC_8450 tristate "SM8450 Display Clock Controller" depends on ARM64 || COMPILE_TEST depends on SM_GCC_8450 + default m if ARCH_QCOM help Support for the display clock controller on Qualcomm Technologies, Inc SM8450 or SM8475 devices. @@ -1400,6 +1488,7 @@ config SM_DISPCC_8550 tristate "SM8550 Display Clock Controller" depends on ARM64 || COMPILE_TEST depends on SM_GCC_8550 || SM_GCC_8650 || SAR_GCC_2130P + default m if ARCH_QCOM help Support for the display clock controller on Qualcomm Technologies, Inc SAR2130P, SM8550 or SM8650 devices. @@ -1411,6 +1500,7 @@ config SM_DISPCC_8750 depends on ARM64 || COMPILE_TEST depends on SM_GCC_8750 select QCOM_GDSC + default m if ARCH_QCOM help Support for the display clock controller on Qualcomm Technologies, Inc SM8750 devices. @@ -1421,6 +1511,7 @@ config SM_GCC_4450 tristate "SM4450 Global Clock Controller" depends on ARM64 || COMPILE_TEST select QCOM_GDSC + default ARCH_QCOM help Support for the global clock controller on SM4450 devices. Say Y if you want to use peripheral devices such as UART, SPI, @@ -1430,6 +1521,7 @@ config SM_GCC_6115 tristate "SM6115 and SM4250 Global Clock Controller" depends on ARM64 || COMPILE_TEST select QCOM_GDSC + default ARCH_QCOM help Support for the global clock controller on SM6115 and SM4250 devices. Say Y if you want to use peripheral devices such as UART, SPI, @@ -1448,6 +1540,7 @@ config SM_GCC_6350 tristate "SM6350 Global Clock Controller" depends on ARM64 || COMPILE_TEST select QCOM_GDSC + default ARCH_QCOM help Support for the global clock controller on SM6350 devices. Say Y if you want to use peripheral devices such as UART, @@ -1475,6 +1568,7 @@ config SM_GCC_MILOS tristate "Milos Global Clock Controller" depends on ARM64 || COMPILE_TEST select QCOM_GDSC + default ARCH_QCOM help Support for the global clock controller on Milos devices. Say Y if you want to use peripheral devices such as UART, @@ -1484,6 +1578,7 @@ config SM_GCC_8150 tristate "SM8150 Global Clock Controller" depends on ARM64 || COMPILE_TEST select QCOM_GDSC + default ARCH_QCOM help Support for the global clock controller on SM8150 devices. Say Y if you want to use peripheral devices such as UART, @@ -1493,6 +1588,7 @@ config SM_GCC_8250 tristate "SM8250 Global Clock Controller" depends on ARM64 || COMPILE_TEST select QCOM_GDSC + default ARCH_QCOM help Support for the global clock controller on SM8250 devices. Say Y if you want to use peripheral devices such as UART, @@ -1502,6 +1598,7 @@ config SM_GCC_8350 tristate "SM8350 Global Clock Controller" depends on ARM64 || COMPILE_TEST select QCOM_GDSC + default ARCH_QCOM help Support for the global clock controller on SM8350 devices. Say Y if you want to use peripheral devices such as UART, @@ -1511,6 +1608,7 @@ config SM_GCC_8450 tristate "SM8450 Global Clock Controller" depends on ARM64 || COMPILE_TEST select QCOM_GDSC + default ARCH_QCOM help Support for the global clock controller on SM8450 or SM8475 devices. @@ -1521,6 +1619,7 @@ config SM_GCC_8550 tristate "SM8550 Global Clock Controller" depends on ARM64 || COMPILE_TEST select QCOM_GDSC + default ARCH_QCOM help Support for the global clock controller on SM8550 devices. Say Y if you want to use peripheral devices such as UART, @@ -1530,6 +1629,7 @@ config SM_GCC_8650 tristate "SM8650 Global Clock Controller" depends on ARM64 || COMPILE_TEST select QCOM_GDSC + default ARCH_QCOM help Support for the global clock controller on SM8650 devices. Say Y if you want to use peripheral devices such as UART, @@ -1539,6 +1639,7 @@ config SM_GCC_8750 tristate "SM8750 Global Clock Controller" depends on ARM64 || COMPILE_TEST select QCOM_GDSC + default ARCH_QCOM help Support for the global clock controller on SM8750 devices. Say Y if you want to use peripheral devices such as UART, @@ -1557,6 +1658,7 @@ config SM_GPUCC_6115 tristate "SM6115 Graphics Clock Controller" select SM_GCC_6115 depends on ARM64 || COMPILE_TEST + default m if ARCH_QCOM help Support for the graphics clock controller on SM6115 devices. Say Y if you want to support graphics controller devices and @@ -1584,6 +1686,7 @@ config SM_GPUCC_6350 tristate "SM6350 Graphics Clock Controller" depends on ARM64 || COMPILE_TEST select SM_GCC_6350 + default m if ARCH_QCOM help Support for the graphics clock controller on SM6350 devices. Say Y if you want to support graphics controller devices and @@ -1593,6 +1696,7 @@ config SM_GPUCC_MILOS tristate "Milos Graphics Clock Controller" depends on ARM64 || COMPILE_TEST select SM_GCC_MILOS + default m if ARCH_QCOM help Support for the graphics clock controller on Milos devices. Say Y if you want to support graphics controller devices and @@ -1602,6 +1706,7 @@ config SM_GPUCC_8150 tristate "SM8150 Graphics Clock Controller" depends on ARM64 || COMPILE_TEST select SM_GCC_8150 + default m if ARCH_QCOM help Support for the graphics clock controller on SM8150 devices. Say Y if you want to support graphics controller devices and @@ -1611,6 +1716,7 @@ config SM_GPUCC_8250 tristate "SM8250 Graphics Clock Controller" depends on ARM64 || COMPILE_TEST select SM_GCC_8250 + default m if ARCH_QCOM help Support for the graphics clock controller on SM8250 devices. Say Y if you want to support graphics controller devices and @@ -1620,6 +1726,7 @@ config SM_GPUCC_8350 tristate "SM8350 Graphics Clock Controller" depends on ARM64 || COMPILE_TEST select SM_GCC_8350 + default m if ARCH_QCOM help Support for the graphics clock controller on SM8350 devices. Say Y if you want to support graphics controller devices and @@ -1629,6 +1736,7 @@ config SM_GPUCC_8450 tristate "SM8450 Graphics Clock Controller" depends on ARM64 || COMPILE_TEST select SM_GCC_8450 + default m if ARCH_QCOM help Support for the graphics clock controller on SM8450 or SM8475 devices. @@ -1639,6 +1747,7 @@ config SM_GPUCC_8550 tristate "SM8550 Graphics Clock Controller" depends on ARM64 || COMPILE_TEST select SM_GCC_8550 + default m if ARCH_QCOM help Support for the graphics clock controller on SM8550 devices. Say Y if you want to support graphics controller devices and @@ -1648,6 +1757,7 @@ config SM_GPUCC_8650 tristate "SM8650 Graphics Clock Controller" depends on ARM64 || COMPILE_TEST select SM_GCC_8650 + default m if ARCH_QCOM help Support for the graphics clock controller on SM8650 devices. Say Y if you want to support graphics controller devices and @@ -1657,6 +1767,7 @@ config SM_GPUCC_8750 tristate "SM8750 Graphics Clock Controller" depends on ARM64 || COMPILE_TEST select SM_GCC_8750 + default m if ARCH_QCOM help Support for the graphics clock controller on SM8750 devices. Say Y if you want to support graphics controller devices and @@ -1675,6 +1786,7 @@ config SM_TCSRCC_8550 tristate "SM8550 TCSR Clock Controller" depends on ARM64 || COMPILE_TEST select QCOM_GDSC + default ARCH_QCOM help Support for the TCSR clock controller on SM8550 devices. Say Y if you want to use peripheral devices such as SD/UFS. @@ -1683,6 +1795,7 @@ config SM_TCSRCC_8650 tristate "SM8650 TCSR Clock Controller" depends on ARM64 || COMPILE_TEST select QCOM_GDSC + default ARCH_QCOM help Support for the TCSR clock controller on SM8650 devices. Say Y if you want to use peripheral devices such as SD/UFS. @@ -1691,6 +1804,7 @@ config SM_TCSRCC_8750 tristate "SM8750 TCSR Clock Controller" depends on ARM64 || COMPILE_TEST select QCOM_GDSC + default m if ARCH_QCOM help Support for the TCSR clock controller on SM8750 devices. Say Y if you want to use peripheral devices such as UFS/USB/PCIe. @@ -1700,6 +1814,7 @@ config SA_VIDEOCC_8775P depends on ARM64 || COMPILE_TEST select SA_GCC_8775P select QCOM_GDSC + default m if ARCH_QCOM help Support for the video clock controller on Qualcomm Technologies, Inc. SA8775P devices. @@ -1711,6 +1826,7 @@ config SM_VIDEOCC_6350 depends on ARM64 || COMPILE_TEST select SM_GCC_6350 select QCOM_GDSC + default m if ARCH_QCOM help Support for the video clock controller on SM6350 devices. Say Y if you want to support video devices and functionality such as @@ -1731,6 +1847,7 @@ config SM_VIDEOCC_MILOS depends on ARM64 || COMPILE_TEST select SM_GCC_MILOS select QCOM_GDSC + default m if ARCH_QCOM help Support for the video clock controller on Qualcomm Technologies, Inc. Milos devices. @@ -1752,6 +1869,7 @@ config SM_VIDEOCC_8250 depends on ARM64 || COMPILE_TEST select SM_GCC_8250 select QCOM_GDSC + default m if ARCH_QCOM help Support for the video clock controller on SM8250 devices. Say Y if you want to support video devices and functionality such as @@ -1771,6 +1889,7 @@ config SM_VIDEOCC_8550 tristate "SM8550 Video Clock Controller" depends on ARM64 || COMPILE_TEST select QCOM_GDSC + default m if ARCH_QCOM help Support for the video clock controller on Qualcomm Technologies, Inc. SM8550 or SM8650 or X1E80100 devices. @@ -1782,6 +1901,7 @@ config SM_VIDEOCC_8750 depends on ARM64 || COMPILE_TEST select SM_GCC_8750 select QCOM_GDSC + default m if ARCH_QCOM help Support for the video clock controller on Qualcomm Technologies, Inc. SM8750 devices. @@ -1822,6 +1942,7 @@ config KRAITCC config CLK_GFM_LPASS_SM8250 tristate "SM8250 GFM LPASS Clocks" depends on ARM64 || COMPILE_TEST + default m if ARCH_QCOM help Support for the Glitch Free Mux (GFM) Low power audio subsystem (LPASS) clocks found on SM8250 SoCs. @@ -1831,6 +1952,7 @@ config SM_VIDEOCC_8450 depends on ARM64 || COMPILE_TEST select SM_GCC_8450 select QCOM_GDSC + default m if ARCH_QCOM help Support for the video clock controller on Qualcomm Technologies, Inc. SM8450 or SM8475 devices. From d643934c275d83470a195a115cd16ac13d1ebbd6 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 3 Jul 2026 12:04:02 +0200 Subject: [PATCH 0132/1328] clk: qcom: Make important ARM32 drivers default Clock controller drivers 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'. Switch all Qualcomm clock controller drivers which are chosen in ARM 32-bit qcom defconfig to a default 'yes'. This has no impact on ARM32 qcom defconfig and ARM64 defconfig include/generated/autoconf.h, however enables a few drivers for ARM32 multi_v7 defconfig: IPQ_GCC_4019, QCOM_CLK_APCS_SDX55, MSM_LCC_8960, SDX_GCC_55, QCOM_CLK_RPMH as module, IPQ_GCC_806X, IPQ_LCC_806X and QCOM_A7PLL. Enabling these on multi_v7 is expected to properly boot these SoCs with defconfig. Signed-off-by: Krzysztof Kozlowski Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260703-clk-qcom-defaults-v3-4-78894525e54a@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/Kconfig | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig index 0e0167fd1f6d..883a5e80b17a 100644 --- a/drivers/clk/qcom/Kconfig +++ b/drivers/clk/qcom/Kconfig @@ -303,6 +303,7 @@ config QCOM_A53PLL config QCOM_A7PLL tristate "A7 PLL driver for SDX55 and SDX65" depends on ARM || COMPILE_TEST + default ARCH_QCOM help Support for the A7 PLL on SDX55 and SDX65 devices. It provides the CPU with frequencies above 1GHz. @@ -334,6 +335,7 @@ config QCOM_CLK_APCS_SDX55 tristate "SDX55 and SDX65 APCS Clock Controller" depends on QCOM_APCS_IPC || COMPILE_TEST depends on ARM || COMPILE_TEST + default ARCH_QCOM help Support for the APCS Clock Controller on SDX55, SDX65 platforms. The APCS is managing the mux and divider which feeds the CPUs. @@ -343,6 +345,7 @@ config QCOM_CLK_APCS_SDX55 config QCOM_CLK_RPM tristate "RPM based Clock Controller" depends on MFD_QCOM_RPM + default ARCH_QCOM help The RPM (Resource Power Manager) is a dedicated hardware engine for managing the shared SoC resources in order to keep the lowest power @@ -405,6 +408,7 @@ config APQ_GCC_8084 tristate "APQ8084 Global Clock Controller" depends on ARM || COMPILE_TEST select QCOM_GDSC + default ARCH_QCOM help Support for the global clock controller on apq8084 devices. Say Y if you want to use peripheral devices such as UART, SPI, @@ -415,6 +419,7 @@ config APQ_MMCC_8084 depends on ARM || COMPILE_TEST select APQ_GCC_8084 select QCOM_GDSC + default ARCH_QCOM help Support for the multimedia clock controller on apq8084 devices. Say Y if you want to support multimedia devices such as display, @@ -466,6 +471,7 @@ config IPQ_CMN_PLL config IPQ_GCC_4019 tristate "IPQ4019 Global Clock Controller" depends on ARM || COMPILE_TEST + default ARCH_QCOM help Support for the global clock controller on ipq4019 devices. Say Y if you want to use peripheral devices such as UART, SPI, @@ -520,6 +526,7 @@ config IPQ_GCC_6018 config IPQ_GCC_806X tristate "IPQ806x Global Clock Controller" depends on ARM || COMPILE_TEST + default ARCH_QCOM help Support for the global clock controller on ipq806x devices. Say Y if you want to use peripheral devices such as UART, SPI, @@ -529,6 +536,7 @@ config IPQ_LCC_806X tristate "IPQ806x LPASS Clock Controller" depends on ARM || COMPILE_TEST select IPQ_GCC_806X + default ARCH_QCOM help Support for the LPASS clock controller on ipq806x devices. Say Y if you want to use audio devices such as i2s, pcm, @@ -597,6 +605,7 @@ config IPQ_NSSCC_QCA8K config MSM_GCC_8660 tristate "MSM8660 Global Clock Controller" depends on ARM || COMPILE_TEST + default ARCH_QCOM help Support for the global clock controller on msm8660 devices. Say Y if you want to use peripheral devices such as UART, SPI, @@ -641,6 +650,7 @@ config MSM_GCC_8939 config MSM_GCC_8960 tristate "APQ8064/MSM8960 Global Clock Controller" depends on ARM || COMPILE_TEST + default ARCH_QCOM help Support for the global clock controller on apq8064/msm8960 devices. Say Y if you want to use peripheral devices such as UART, SPI, @@ -649,6 +659,7 @@ config MSM_GCC_8960 config MSM_LCC_8960 tristate "APQ8064/MSM8960/MDM9650 LPASS Clock Controller" depends on ARM || COMPILE_TEST + default ARCH_QCOM help Support for the LPASS clock controller on apq8064/msm8960/mdm9650 devices. @@ -675,6 +686,7 @@ config MSM_MMCC_8960 tristate "MSM8960 Multimedia Clock Controller" depends on ARM || COMPILE_TEST select MSM_GCC_8960 + default ARCH_QCOM help Support for the multimedia clock controller on msm8960 devices. Say Y if you want to support multimedia devices such as display, @@ -693,6 +705,7 @@ config MSM_GCC_8974 tristate "MSM8974 Global Clock Controller" depends on ARM || COMPILE_TEST select QCOM_GDSC + default ARCH_QCOM help Support for the global clock controller on msm8974 devices. Say Y if you want to use peripheral devices such as UART, SPI, @@ -703,6 +716,7 @@ config MSM_MMCC_8974 depends on ARM || COMPILE_TEST select MSM_GCC_8974 select QCOM_GDSC + default ARCH_QCOM help Support for the multimedia clock controller on msm8974 devices. Say Y if you want to support multimedia devices such as display, @@ -1276,6 +1290,7 @@ config SDX_GCC_55 tristate "SDX55 Global Clock Controller" depends on ARM || COMPILE_TEST select QCOM_GDSC + default ARCH_QCOM help Support for the global clock controller on SDX55 devices. Say Y if you want to use peripheral devices such as UART, From 6b150572bb76dc508b0d9fbe2c5a56a67429e376 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 3 Jul 2026 12:04:03 +0200 Subject: [PATCH 0133/1328] clk: qcom: Add defaults for desired arm64 drivers Clock controller drivers 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 drivers for upstreamed or being upstreamed SoCs, which are not yed enabled in defconfig: Glymur, Hawi, Nord, MSM8976, MSM8998 (GPU CC), SC7180, SC8180X, SC8280XP, SC7280, SDM660, QDU1000, SM4450, SM7150, SM8150, SM8450, SM6125, SM6375. Note that main GCC clock controller drivers are usually already enabled for these. Reviewed-by: Konrad Dybcio Signed-off-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260703-clk-qcom-defaults-v3-5-78894525e54a@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/Kconfig | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig index 883a5e80b17a..63f271aff177 100644 --- a/drivers/clk/qcom/Kconfig +++ b/drivers/clk/qcom/Kconfig @@ -75,6 +75,7 @@ config CLK_GLYMUR_EVACC depends on ARM64 || COMPILE_TEST default m if ARCH_QCOM select CLK_GLYMUR_GCC + default m if ARCH_QCOM help Support for the Enhanced Video Analytics (EVA) clock controller on Qualcomm Technologies, Inc. Glymur devices. @@ -113,6 +114,7 @@ config CLK_GLYMUR_VIDEOCC tristate "Glymur Video Clock Controller" depends on ARM64 || COMPILE_TEST select CLK_GLYMUR_GCC + default m if ARCH_QCOM help Support for the video clock controller on Glymur devices. Say Y if you want to support video devices and functionality such as @@ -183,6 +185,7 @@ config CLK_NORD_GCC tristate "Nord Global Clock Controller" depends on ARM64 || COMPILE_TEST select QCOM_GDSC + default ARCH_QCOM help Support for the global clock controller on Nord devices. Say Y if you want to use peripheral devices such as UART, @@ -382,6 +385,7 @@ config CLK_HAWI_GCC tristate "Hawi Global Clock Controller" depends on ARM64 || COMPILE_TEST select QCOM_GDSC + default ARCH_QCOM help Support for the global clock controller on Hawi devices. Say Y if you want to use peripheral devices such as UART, SPI, @@ -390,6 +394,7 @@ config CLK_HAWI_GCC config CLK_HAWI_TCSRCC tristate "Hawi TCSR Clock Controller" depends on ARM64 || COMPILE_TEST + default m if ARCH_QCOM help Support for the TCSR clock controller on Hawi devices. Say Y if you want to use peripheral devices such as PCIe, USB, UFS. @@ -725,6 +730,7 @@ config MSM_MMCC_8974 config MSM_GCC_8976 tristate "MSM8956/76 Global Clock Controller" select QCOM_GDSC + default ARCH_QCOM if ARM64 help Support for the global clock controller on msm8956/76 devices. Say Y if you want to use peripheral devices such as UART, SPI, @@ -780,6 +786,7 @@ config MSM_GPUCC_8998 tristate "MSM8998 Graphics Clock Controller" select MSM_GCC_8998 select QCOM_GDSC + default m if ARCH_QCOM && ARM64 help Support for the graphics clock controller on MSM8998 devices. Say Y if you want to support graphics controller devices and @@ -849,6 +856,7 @@ config QCS_GCC_404 config CLK_NORD_TCSRCC tristate "Nord TCSR Clock Controller" depends on ARM64 || COMPILE_TEST + default m if ARCH_QCOM help Support for the TCSR clock controller on Nord devices. Say Y if you want to use peripheral devices such as PCIe, USB, UFS etc. @@ -909,6 +917,7 @@ config SC_CAMCC_7180 tristate "SC7180 Camera Clock Controller" depends on ARM64 || COMPILE_TEST select SC_GCC_7180 + default m if ARCH_QCOM help Support for the camera clock controller on Qualcomm Technologies, Inc SC7180 devices. @@ -930,6 +939,7 @@ config SC_CAMCC_8180X tristate "SC8180X Camera Clock Controller" depends on ARM64 || COMPILE_TEST select SC_GCC_8180X + default m if ARCH_QCOM help Support for the camera clock controller on Qualcomm Technologies, Inc SC8180X devices. @@ -962,6 +972,7 @@ config SC_DISPCC_7180 tristate "SC7180 Display Clock Controller" depends on ARM64 || COMPILE_TEST select SC_GCC_7180 + default m if ARCH_QCOM help Support for the display clock controller on Qualcomm Technologies, Inc SC7180 devices. @@ -1078,6 +1089,7 @@ config SC_GPUCC_7180 tristate "SC7180 Graphics Clock Controller" depends on ARM64 || COMPILE_TEST select SC_GCC_7180 + default m if ARCH_QCOM help Support for the graphics clock controller on SC7180 devices. Say Y if you want to support graphics controller devices and @@ -1107,6 +1119,7 @@ config SC_LPASSCC_7280 tristate "SC7280 Low Power Audio Subsystem (LPASS) Clock Controller" depends on ARM64 || COMPILE_TEST select SC_GCC_7280 + default m if ARCH_QCOM help Support for the LPASS clock controller on SC7280 devices. Say Y if you want to use the LPASS branch clocks of the LPASS clock @@ -1148,6 +1161,7 @@ config SC_VIDEOCC_7180 tristate "SC7180 Video Clock Controller" depends on ARM64 || COMPILE_TEST select SC_GCC_7180 + default m if ARCH_QCOM help Support for the video clock controller on SC7180 devices. Say Y if you want to support video devices and functionality such as @@ -1176,6 +1190,7 @@ config SDM_GCC_660 tristate "SDM660 Global Clock Controller" depends on ARM64 || COMPILE_TEST select QCOM_GDSC + default ARCH_QCOM help Support for the global clock controller on SDM660 devices. Say Y if you want to use peripheral devices such as UART, SPI, @@ -1186,6 +1201,7 @@ config SDM_MMCC_660 depends on ARM64 || COMPILE_TEST select SDM_GCC_660 select QCOM_GDSC + default m if ARCH_QCOM help Support for the multimedia clock controller on SDM660 devices. Say Y if you want to support multimedia devices such as display, @@ -1196,6 +1212,7 @@ config SDM_GPUCC_660 depends on ARM64 || COMPILE_TEST select SDM_GCC_660 select QCOM_GDSC + default m if ARCH_QCOM help Support for the graphics clock controller on SDM630/636/660 devices. Say Y if you want to support graphics controller devices and @@ -1229,6 +1246,7 @@ config QDU_ECPRICC_1000 tristate "QDU1000/QRU1000 ECPRI Clock Controller" depends on ARM64 || COMPILE_TEST select QDU_GCC_1000 + default m if ARCH_QCOM help Support for the ECPRI clock controller on QDU1000 and QRU1000 devices. Say Y if you want to support the ECPRI @@ -1318,6 +1336,7 @@ config SM_CAMCC_4450 tristate "SM4450 Camera Clock Controller" depends on ARM64 || COMPILE_TEST select SM_GCC_4450 + default m if ARCH_QCOM help Support for the camera clock controller on SM4450 devices. Say Y if you want to support camera devices and camera functionality. @@ -1335,6 +1354,7 @@ config SM_CAMCC_7150 tristate "SM7150 Camera Clock Controller" depends on ARM64 || COMPILE_TEST select SM_GCC_7150 + default m if ARCH_QCOM help Support for the camera clock controller on SM7150 devices. Say Y if you want to support camera devices and camera functionality. @@ -1352,6 +1372,7 @@ config SM_CAMCC_8150 tristate "SM8150 Camera Clock Controller" depends on ARM64 || COMPILE_TEST select SM_GCC_8150 + default m if ARCH_QCOM help Support for the camera clock controller on Qualcomm Technologies, Inc SM8150 devices. @@ -1371,6 +1392,7 @@ config SM_CAMCC_8450 tristate "SM8450 Camera Clock Controller" depends on ARM64 || COMPILE_TEST select SM_GCC_8450 + default m if ARCH_QCOM help Support for the camera clock controller on SM8450 or SM8475 devices. Say Y if you want to support camera devices and camera functionality. @@ -1408,6 +1430,7 @@ config SM_DISPCC_4450 tristate "SM4450 Display Clock Controller" depends on ARM64 || COMPILE_TEST depends on SM_GCC_4450 + default m if ARCH_QCOM help Support for the display clock controller on Qualcomm Technologies, Inc SM4450 devices. @@ -1429,6 +1452,7 @@ config SM_DISPCC_6125 tristate "SM6125 Display Clock Controller" depends on ARM64 || COMPILE_TEST depends on SM_GCC_6125 + default m if ARCH_QCOM help Support for the display clock controller on Qualcomm Technologies, Inc SM6125 devices. @@ -1439,6 +1463,7 @@ config SM_DISPCC_7150 tristate "SM7150 Display Clock Controller" depends on ARM64 || COMPILE_TEST depends on SM_GCC_7150 + default m if ARCH_QCOM help Support for the display clock controller on Qualcomm Technologies, Inc SM7150 devices. @@ -1471,6 +1496,7 @@ config SM_DISPCC_6375 tristate "SM6375 Display Clock Controller" depends on ARM64 || COMPILE_TEST depends on SM_GCC_6375 + default m if ARCH_QCOM help Support for the display clock controller on Qualcomm Technologies, Inc SM6375 devices. @@ -1546,6 +1572,7 @@ config SM_GCC_6125 tristate "SM6125 Global Clock Controller" depends on ARM64 || COMPILE_TEST select QCOM_GDSC + default ARCH_QCOM help Support for the global clock controller on SM6125 devices. Say Y if you want to use peripheral devices such as UART, @@ -1565,6 +1592,7 @@ config SM_GCC_6375 tristate "SM6375 Global Clock Controller" depends on ARM64 || COMPILE_TEST select QCOM_GDSC + default ARCH_QCOM help Support for the global clock controller on SM6375 devices. Say Y if you want to use peripheral devices such as UART, @@ -1574,6 +1602,7 @@ config SM_GCC_7150 tristate "SM7150 Global Clock Controller" depends on ARM64 || COMPILE_TEST select QCOM_GDSC + default ARCH_QCOM help Support for the global clock controller on SM7150 devices. Say Y if you want to use peripheral devices such as UART, @@ -1664,6 +1693,7 @@ config SM_GPUCC_4450 tristate "SM4450 Graphics Clock Controller" depends on ARM64 || COMPILE_TEST select SM_GCC_4450 + default m if ARCH_QCOM help Support for the graphics clock controller on SM4450 devices. Say Y if you want to support graphics controller devices and @@ -1683,6 +1713,7 @@ config SM_GPUCC_6125 tristate "SM6125 Graphics Clock Controller" select SM_GCC_6125 depends on ARM64 || COMPILE_TEST + default m if ARCH_QCOM help Support for the graphics clock controller on SM6125 devices. Say Y if you want to support graphics controller devices and @@ -1692,6 +1723,7 @@ config SM_GPUCC_6375 tristate "SM6375 Graphics Clock Controller" select SM_GCC_6375 depends on ARM64 || COMPILE_TEST + default m if ARCH_QCOM help Support for the graphics clock controller on SM6375 devices. Say Y if you want to support graphics controller devices and @@ -1792,6 +1824,7 @@ config SM_LPASSCC_6115 tristate "SM6115 Low Power Audio Subsystem (LPASS) Clock Controller" depends on ARM64 || COMPILE_TEST select SM_GCC_6115 + default m if ARCH_QCOM help Support for the LPASS clock controller on SM6115 devices. Say Y if you want to toggle LPASS-adjacent resets within @@ -1852,6 +1885,7 @@ config SM_VIDEOCC_7150 depends on ARM64 || COMPILE_TEST select SM_GCC_7150 select QCOM_GDSC + default m if ARCH_QCOM help Support for the video clock controller on SM7150 devices. Say Y if you want to support video devices and functionality such as @@ -1874,6 +1908,7 @@ config SM_VIDEOCC_8150 depends on ARM64 || COMPILE_TEST select SM_GCC_8150 select QCOM_GDSC + default m if ARCH_QCOM help Support for the video clock controller on SM8150 devices. Say Y if you want to support video devices and functionality such as @@ -1895,6 +1930,7 @@ config SM_VIDEOCC_8350 depends on ARM64 || COMPILE_TEST depends on SM_GCC_8350 || SC_GCC_8280XP select QCOM_GDSC + default m if ARCH_QCOM help Support for the video clock controller on SM8350 or SC8280XP devices. Say Y if you want to support video devices and functionality such as From 3fb968b400bd3306ff25852c396a05f581f0d4cc Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sun, 5 Jul 2026 19:18:12 +0200 Subject: [PATCH 0134/1328] clk: qcom: camcc-x1p42100: Constify qcom_cc_driver_data and list of critical CBCR registers The static 'struct qcom_cc_desc', 'struct qcom_cc_driver_data' and array 'xxx_critical_cbcrs' are already treated by common.c code as pointers to const, so constify few remaining pieces. Signed-off-by: Krzysztof Kozlowski Reviewed-by: Konrad Dybcio Reviewed-by: Taniya Das Link: https://lore.kernel.org/r/20260705171811.115542-3-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/camcc-x1p42100.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/clk/qcom/camcc-x1p42100.c b/drivers/clk/qcom/camcc-x1p42100.c index c1a61c267919..7e45e5693c19 100644 --- a/drivers/clk/qcom/camcc-x1p42100.c +++ b/drivers/clk/qcom/camcc-x1p42100.c @@ -2166,7 +2166,7 @@ static struct clk_alpha_pll *cam_cc_x1p42100_plls[] = { &cam_cc_pll6, }; -static u32 cam_cc_x1p42100_critical_cbcrs[] = { +static const u32 cam_cc_x1p42100_critical_cbcrs[] = { 0x13a9c, /* CAM_CC_GDSC_CLK */ 0x13ab8, /* CAM_CC_SLEEP_CLK */ }; @@ -2179,14 +2179,14 @@ static const struct regmap_config cam_cc_x1p42100_regmap_config = { .fast_io = true, }; -static struct qcom_cc_driver_data cam_cc_x1p42100_driver_data = { +static const struct qcom_cc_driver_data cam_cc_x1p42100_driver_data = { .alpha_plls = cam_cc_x1p42100_plls, .num_alpha_plls = ARRAY_SIZE(cam_cc_x1p42100_plls), .clk_cbcrs = cam_cc_x1p42100_critical_cbcrs, .num_clk_cbcrs = ARRAY_SIZE(cam_cc_x1p42100_critical_cbcrs), }; -static struct qcom_cc_desc cam_cc_x1p42100_desc = { +static const struct qcom_cc_desc cam_cc_x1p42100_desc = { .config = &cam_cc_x1p42100_regmap_config, .clks = cam_cc_x1p42100_clocks, .num_clks = ARRAY_SIZE(cam_cc_x1p42100_clocks), From e5b2050ff760064c84799ad2954ab30aca415cea Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sun, 5 Jul 2026 19:18:13 +0200 Subject: [PATCH 0135/1328] clk: qcom: videocc-x1p42100: Constify qcom_cc_driver_data and list of critical CBCR registers The static 'struct qcom_cc_driver_data' and array 'xxx_critical_cbcrs' are already treated by common.c code as pointers to const, so constify few remaining pieces. Signed-off-by: Krzysztof Kozlowski Reviewed-by: Konrad Dybcio Reviewed-by: Taniya Das Link: https://lore.kernel.org/r/20260705171811.115542-4-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/videocc-x1p42100.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/clk/qcom/videocc-x1p42100.c b/drivers/clk/qcom/videocc-x1p42100.c index 2bb40ac6fcc5..fc73928128be 100644 --- a/drivers/clk/qcom/videocc-x1p42100.c +++ b/drivers/clk/qcom/videocc-x1p42100.c @@ -527,7 +527,7 @@ static struct clk_alpha_pll *video_cc_x1p42100_plls[] = { &video_cc_pll1, }; -static u32 video_cc_x1p42100_critical_cbcrs[] = { +static const u32 video_cc_x1p42100_critical_cbcrs[] = { 0x80f4, /* VIDEO_CC_AHB_CLK */ 0x8150, /* VIDEO_CC_SLEEP_CLK */ 0x8124, /* VIDEO_CC_XO_CLK */ @@ -541,7 +541,7 @@ static const struct regmap_config video_cc_x1p42100_regmap_config = { .fast_io = true, }; -static struct qcom_cc_driver_data video_cc_x1p42100_driver_data = { +static const struct qcom_cc_driver_data video_cc_x1p42100_driver_data = { .alpha_plls = video_cc_x1p42100_plls, .num_alpha_plls = ARRAY_SIZE(video_cc_x1p42100_plls), .clk_cbcrs = video_cc_x1p42100_critical_cbcrs, From 499b4cb6710f9a351d8b57a2132f9b4389d8464a Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Wed, 8 Jul 2026 18:30:06 -0400 Subject: [PATCH 0136/1328] clk: qcom: camcc-sc8280xp: unregister CAMCC_GDSC_CLK With the introduction of sync_state support in the clk and pmdomain subsystems, the following warning happens when the unused clocks are shutdown in camcc-sc8280xp: [ 15.408367] titan_top_gdsc status stuck at 'on' [ 15.408429] WARNING: drivers/clk/qcom/gdsc.c:178 at gdsc_toggle_logic+0x14c/0x160, CPU#2: kworker/u32:1/14 [ 15.408462] Modules linked in: bnep vfat fat ath11k_pci(+) ath11k mac80211 cfg80211 mhi libarc4 snd_soc_wcd938x snd_soc_wcd938x_sdw snd_soc_wcd_classh hci_uart snd_soc_wcd_common snd_soc_sc8280xp soundwire_qcom snd_soc_wcd_mbhc snd_soc_qcom_sdw slimbus snd_soc_qcom_common regmap_sdw btqca btrtl qcom_camss soundwire_bus btbcm btintel snd_soc_sdca snd_soc_lpass_wsa_macro bluetooth snd_soc_lpass_tx_macro snd_soc_lpass_va_macro snd_soc_lpass_rx_macro snd_soc_hdmi_codec snd_soc_lpass_macro_common videobuf2_dma_sg ov5675 v4l2_fwnode videobuf2_memops qcom_spmi_adc5 snd_soc_core qcom_spmi_adc_tm5 videobuf2_v4l2 snd_seq snd_seq_device videobuf2_common v4l2_async qcom_vadc_common qcom_spmi_temp_alarm pm8941_pwrkey industrialio videodev snd_compress rfkill ac97_bus snd_pcm_dmaengine qcom_tsens mc qcom_edac snd_pcm pci_pwrctrl_pwrseq qcom_cpufreq_hw snd_timer snd qcomtee soundcore tee leds_gpio joydev binfmt_misc zram lz4hc_compress governor_simpleondemand panel_edp msm xhci_plat_hcd nvme nvme_core dwc3 qcom_pm8008_regulator [ 15.408688] ucsi_glink nvme_keyring nvme_auth pmic_glink_altmode udc_core typec_ucsi aux_hpd_bridge qcom_battmgr ulpi ubwc_config socinfo ocmem drm_gpuvm qcom_q6v5_pas drm_exec qcom_pil_info leds_qcom_lpg gpu_sched led_class_multicolor rtc_pm8xxx qcom_pbs qcom_common drm_display_helper qcom_pon qcom_glink_smem qcom_glink ghash_ce pwrseq_qcom_wcn gpio_sbu_mux qcom_stats phy_qcom_qmp_combo qcom_q6v5 gf128mul cec dispcc_sc8280xp phy_qcom_edp camcc_sc8280xp i2c_qcom_cci qcom_sysmon drm_dp_aux_bus mdt_loader aux_bridge qcom_pm8008 i2c_hid_of_elan dwc3_qcom_legacy llcc_qcom icc_bwmon gpi typec qcom_refgen_regulator phy_qcom_qmp_usb nvmem_qfprom qcom_ipcc phy_qcom_snps_femto_v2 gpucc_sc8280xp pinctrl_sc8280xp_lpass_lpi qcom_hwspinlock pinctrl_lpass_lpi lpasscc_sc8280xp qrtr qcom_aoss pmic_glink pdr_interface phy_qcom_qmp_pcie qcom_smd qcom_pdr_msg icc_osm_l3 qcom_wdt qmi_helpers qcom_rng smp2p rpmsg_core gpio_keys pwm_bl smem hid_multitouch fuse i2c_dev [ 15.408928] CPU: 2 UID: 0 PID: 14 Comm: kworker/u32:1 Not tainted 7.1.0+ #2 PREEMPT(lazy) [ 15.408937] Hardware name: LENOVO 21BX0016US/21BX0016US, BIOS N3HET88W (1.60 ) 03/14/2024 [ 15.408942] Workqueue: pm pm_runtime_work [ 15.408959] pstate: 60400005 (nZCv daif +PAN -UAO -TCO -DIT -SSBS BTYPE=--) [ 15.408967] pc : gdsc_toggle_logic+0x14c/0x160 [ 15.408978] lr : gdsc_toggle_logic+0x14c/0x160 [ 15.408987] sp : ffff8000800f3b40 [ 15.408991] x29: ffff8000800f3b40 x28: 0000000000000000 x27: 0000000000000000 [ 15.409003] x26: 0000000000000000 x25: 0000000000000000 x24: 0000000000000000 [ 15.409014] x23: 0000000000000000 x22: 0000000000000001 x21: ffffa33f298fca88 [ 15.409024] x20: 0000000000000000 x19: ffffa33f298fc5b0 x18: 00cd15db75dacefd [ 15.409035] x17: 000000040044ffff x16: ffffa33f3b1a3d88 x15: 726f776b80000002 [ 15.409045] x14: ffffffffffffffff x13: 0000000000000028 x12: 0101010101010101 [ 15.409056] x11: 7f7f7f7f7f7f7f7f x10: fefeff3039313274 x9 : ffffa33f3a5edafc [ 15.409067] x8 : ffff8000800f3780 x7 : 0000000000000001 x6 : 0000000000000001 [ 15.409078] x5 : ffff000bf3ca1288 x4 : 0000000000000000 x3 : ffff5cccb6a3f000 [ 15.409088] x2 : 0000000000000000 x1 : 0000000000000000 x0 : ffff000080ae0000 [ 15.409098] Call trace: [ 15.409103] gdsc_toggle_logic+0x14c/0x160 (P) [ 15.409115] gdsc_disable+0x4c/0x190 [ 15.409126] _genpd_power_off+0xa0/0x1a8 [ 15.409137] genpd_power_off.part.0+0x180/0x2a0 [ 15.409149] genpd_runtime_suspend+0x218/0x310 [ 15.409155] __rpm_callback+0x50/0x1f8 [ 15.409166] rpm_callback+0x7c/0x90 [ 15.409175] rpm_suspend+0xe8/0x690 [ 15.409185] pm_runtime_work+0xd0/0xe0 [ 15.409195] process_one_work+0x18c/0x518 [ 15.409208] worker_thread+0x190/0x320 [ 15.409218] kthread+0x110/0x130 [ 15.409227] ret_from_fork+0x10/0x20 This clock is force enabled to be on in the probe, and registered with the Common Clk Framework, resulting in them being toggled off after unused clocks are shutdown. This clock is required for the GDSC transitions. Similar to the fix in commit b60521eff227 ("clk: qcom: gcc-x1e80100: Unregister GCC_GPU_CFG_AHB_CLK/GCC_DISP_XO_CLK"), let's just unregister this clock. Link: https://lore.kernel.org/linux-clk/20260626-camcc-sc8280xp-titan-top-v1-1-2ca246886493@redhat.com/ Fixes: ff93872a9c616 ("clk: qcom: camcc-sc8280xp: Add sc8280xp CAMCC") Suggested-by: Jagadeesh Kona Signed-off-by: Brian Masney Reviewed-by: Konrad Dybcio Reviewed-by: Dmitry Baryshkov Reviewed-by: Bryan O'Donoghue Link: https://lore.kernel.org/r/20260708-camcc-sc8280xp-remove-gdsc-v1-1-dfaab98a3bf5@redhat.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/camcc-sc8280xp.c | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/drivers/clk/qcom/camcc-sc8280xp.c b/drivers/clk/qcom/camcc-sc8280xp.c index 18f5a3eb313e..1de238a85ab9 100644 --- a/drivers/clk/qcom/camcc-sc8280xp.c +++ b/drivers/clk/qcom/camcc-sc8280xp.c @@ -1754,24 +1754,6 @@ static struct clk_branch camcc_csiphy3_clk = { }, }; -static struct clk_branch camcc_gdsc_clk = { - .halt_reg = 0xc1e4, - .halt_check = BRANCH_HALT, - .clkr = { - .enable_reg = 0xc1e4, - .enable_mask = BIT(0), - .hw.init = &(struct clk_init_data){ - .name = "camcc_gdsc_clk", - .parent_hws = (const struct clk_hw*[]){ - &camcc_xo_clk_src.clkr.hw, - }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, - .ops = &clk_branch2_ops, - }, - }, -}; - static struct clk_branch camcc_icp_ahb_clk = { .halt_reg = 0xc0d8, .halt_check = BRANCH_HALT, @@ -2840,7 +2822,6 @@ static struct clk_regmap *camcc_sc8280xp_clocks[] = { [CAMCC_CSIPHY2_CLK] = &camcc_csiphy2_clk.clkr, [CAMCC_CSIPHY3_CLK] = &camcc_csiphy3_clk.clkr, [CAMCC_FAST_AHB_CLK_SRC] = &camcc_fast_ahb_clk_src.clkr, - [CAMCC_GDSC_CLK] = &camcc_gdsc_clk.clkr, [CAMCC_ICP_AHB_CLK] = &camcc_icp_ahb_clk.clkr, [CAMCC_ICP_CLK] = &camcc_icp_clk.clkr, [CAMCC_ICP_CLK_SRC] = &camcc_icp_clk_src.clkr, From f54ac9a7096bc091813e3ef92021e1acf1e725a9 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Wed, 8 Jul 2026 11:48:39 +0200 Subject: [PATCH 0137/1328] dt-bindings: clock: qcom,sm8450-camcc: Drop qcom,sc8280xp-camcc from "if" block qcom,sc8280xp-camcc compatible was moved to own binding file in commit 842fa7482915 ("dt-bindings: clock: qcom,sm8450-camcc: Move sc8280xp camcc to sa8775p camcc"), so cleanup redundant entry in "if:then:" block. Signed-off-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260708-dt-bindings-camcc-required-opps-v1-1-ae0871774210@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- Documentation/devicetree/bindings/clock/qcom,sm8450-camcc.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/Documentation/devicetree/bindings/clock/qcom,sm8450-camcc.yaml b/Documentation/devicetree/bindings/clock/qcom,sm8450-camcc.yaml index 8492a7ef7324..e6ffacc81d0a 100644 --- a/Documentation/devicetree/bindings/clock/qcom,sm8450-camcc.yaml +++ b/Documentation/devicetree/bindings/clock/qcom,sm8450-camcc.yaml @@ -74,7 +74,6 @@ allOf: enum: - qcom,kaanapali-cambistmclkcc - qcom,kaanapali-camcc - - qcom,sc8280xp-camcc - qcom,sm8450-camcc - qcom,sm8550-camcc then: From 9b9139d88c1a14535e8fd85e11e15ee3ccda48c9 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Wed, 8 Jul 2026 11:48:40 +0200 Subject: [PATCH 0138/1328] dt-bindings: clock: qcom,sm8450-camcc: Require required-opps on SM8750 Reverse the logic within "if:then:" when expecting "required-opps" property to have negative list of only variants which do not require these. CAMCC on SM8650 does not need "required-opps" as explained in the commit db30c1160ca5 ("dt-bindings: clock: qcom: Drop required-opps in required on SM8650 camcc"). Requirement on CAMCC on SM8475 is not known. Reverting the logic in "if:" part has two impacts: 1. Adds requirement of "required-opps" on CAMCC on SM8750 to match hardware expectation, because it needs minimum voltage levels of MMCX rail (already expressed in DTS). 2. Reduces the chances of forgetting to update constraints when adding new compatibles (like for SM8750). Signed-off-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260708-dt-bindings-camcc-required-opps-v1-2-ae0871774210@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- .../devicetree/bindings/clock/qcom,sm8450-camcc.yaml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Documentation/devicetree/bindings/clock/qcom,sm8450-camcc.yaml b/Documentation/devicetree/bindings/clock/qcom,sm8450-camcc.yaml index e6ffacc81d0a..b41247b257b1 100644 --- a/Documentation/devicetree/bindings/clock/qcom,sm8450-camcc.yaml +++ b/Documentation/devicetree/bindings/clock/qcom,sm8450-camcc.yaml @@ -70,12 +70,11 @@ allOf: - if: properties: compatible: - contains: - enum: - - qcom,kaanapali-cambistmclkcc - - qcom,kaanapali-camcc - - qcom,sm8450-camcc - - qcom,sm8550-camcc + not: + contains: + enum: + - qcom,sm8475-camcc + - qcom,sm8650-camcc then: required: - required-opps From ecb42d97c8e143e8d9482e8d7019d06019cd961b Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Wed, 8 Jul 2026 11:48:41 +0200 Subject: [PATCH 0139/1328] dt-bindings: clock: qcom,sm8450-video: Require required-opps on X1E80100 Reverse the logic within "if:then:" when expecting "required-opps" property to have negative list of only variants which do not require these. VIDEOCC on SM8650 does not need "required-opps" as explained in the commit 6720e8dbcb1b ("dt-bindings: clock: qcom: Drop required-opps in required on sm8650 videocc"). Requirement on VIDEOCC on SM8475 is not known. Reverting the logic in "if:" part has two impacts: 1. Adds requirement of "required-opps" on VIDEOCC on Hamoa (X1E80100), to match hardware expectation, similarly to existing its variant X1P42100 (already expressed in DTS). 2. Reduces the chances of forgetting to update constraints when adding new compatibles (like for X1E80100). Signed-off-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260708-dt-bindings-camcc-required-opps-v1-3-ae0871774210@oss.qualcomm.com [bjorn: Rebased, assuming the newly added hawi compatible also requires opps] Signed-off-by: Bjorn Andersson --- .../bindings/clock/qcom,sm8450-videocc.yaml | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/Documentation/devicetree/bindings/clock/qcom,sm8450-videocc.yaml b/Documentation/devicetree/bindings/clock/qcom,sm8450-videocc.yaml index a6fd1992d6d2..1e1fec1cd7ce 100644 --- a/Documentation/devicetree/bindings/clock/qcom,sm8450-videocc.yaml +++ b/Documentation/devicetree/bindings/clock/qcom,sm8450-videocc.yaml @@ -67,15 +67,11 @@ allOf: - if: properties: compatible: - contains: - enum: - - qcom,glymur-videocc - - qcom,hawi-videocc - - qcom,kaanapali-videocc - - qcom,sm8450-videocc - - qcom,sm8550-videocc - - qcom,sm8750-videocc - - qcom,x1p42100-videocc + not: + contains: + enum: + - qcom,sm8475-videocc + - qcom,sm8650-videocc then: required: - required-opps From dd7ddd057fcd9301ff0c9908e8a856f5ca112f29 Mon Sep 17 00:00:00 2001 From: Kathiravan Thirumoorthy Date: Tue, 7 Jul 2026 12:25:23 +0530 Subject: [PATCH 0140/1328] dt-bindings: clock: qcom: Add IPQ9650 REFGEN clock IDs Add the REFGEN clock IDs for the IPQ9650 SoC. These clocks are used to enable the REFGEN block, which provides the reference current to the PHYs in the SoC. Signed-off-by: Kathiravan Thirumoorthy Acked-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260707-b4-ipq9650_refgen_clocks-v1-1-e070624d03d2@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- include/dt-bindings/clock/qcom,ipq9650-gcc.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/dt-bindings/clock/qcom,ipq9650-gcc.h b/include/dt-bindings/clock/qcom,ipq9650-gcc.h index afd17c00d96e..2d43ca36c09c 100644 --- a/include/dt-bindings/clock/qcom,ipq9650-gcc.h +++ b/include/dt-bindings/clock/qcom,ipq9650-gcc.h @@ -169,4 +169,9 @@ #define GPLL2 160 #define GPLL2_OUT_MAIN 161 #define GPLL4 162 +#define GCC_REFGEN_CORE_CLK_SRC 163 +#define GCC_REFGEN_PCIE_CORE_CLK 164 +#define GCC_REFGEN_PCIE_HCLK 165 +#define GCC_REFGEN_CMN_UPHY_HCLK 166 +#define GCC_REFGEN_CMN_UPHY_CORE_CLK 167 #endif From e27e02fff418d713bc52c2d929ea29aa6ac4530c Mon Sep 17 00:00:00 2001 From: Kathiravan Thirumoorthy Date: Tue, 7 Jul 2026 12:25:24 +0530 Subject: [PATCH 0141/1328] clk: qcom: ipq9650: Add clocks for the REFGEN block Add the clocks required to enable the REFGEN block which provides the reference current to the PHYs in the SoC. Reviewed-by: Konrad Dybcio Signed-off-by: Kathiravan Thirumoorthy Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20260707-b4-ipq9650_refgen_clocks-v1-2-e070624d03d2@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gcc-ipq9650.c | 89 ++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/drivers/clk/qcom/gcc-ipq9650.c b/drivers/clk/qcom/gcc-ipq9650.c index c556c2bbfd96..e65a52fdfa91 100644 --- a/drivers/clk/qcom/gcc-ipq9650.c +++ b/drivers/clk/qcom/gcc-ipq9650.c @@ -3022,6 +3022,90 @@ static struct clk_branch gcc_usb1_sleep_clk = { }, }; +static struct clk_rcg2 gcc_refgen_core_clk_src = { + .cmd_rcgr = 0x23004, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_3, + .freq_tbl = ftbl_gcc_nss_ts_clk_src, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_refgen_core_clk_src", + .parent_data = gcc_parent_data_3, + .num_parents = ARRAY_SIZE(gcc_parent_data_3), + .ops = &clk_rcg2_ops, + }, +}; + +static struct clk_branch gcc_refgen_cmn_uphy_core_clk = { + .halt_reg = 0x2300c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x2300c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_refgen_cmn_uphy_core_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_refgen_core_clk_src.clkr.hw, + }, + .num_parents = 1, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_refgen_pcie_core_clk = { + .halt_reg = 0x23020, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x23020, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_refgen_pcie_core_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_refgen_core_clk_src.clkr.hw, + }, + .num_parents = 1, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_refgen_pcie_hclk = { + .halt_reg = 0x23024, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x23024, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_refgen_pcie_hclk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_pcnoc_bfdcd_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_refgen_cmn_uphy_hclk = { + .halt_reg = 0x23010, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x23010, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_refgen_cmn_uphy_hclk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_pcnoc_bfdcd_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + static struct clk_regmap *gcc_ipq9650_clocks[] = { [GCC_ADSS_PWM_CLK] = &gcc_adss_pwm_clk.clkr, [GCC_ADSS_PWM_CLK_SRC] = &gcc_adss_pwm_clk_src.clkr, @@ -3179,6 +3263,11 @@ static struct clk_regmap *gcc_ipq9650_clocks[] = { [GPLL2] = &gpll2.clkr, [GPLL2_OUT_MAIN] = &gpll2_out_main.clkr, [GPLL4] = &gpll4.clkr, + [GCC_REFGEN_CORE_CLK_SRC] = &gcc_refgen_core_clk_src.clkr, + [GCC_REFGEN_PCIE_CORE_CLK] = &gcc_refgen_pcie_core_clk.clkr, + [GCC_REFGEN_PCIE_HCLK] = &gcc_refgen_pcie_hclk.clkr, + [GCC_REFGEN_CMN_UPHY_HCLK] = &gcc_refgen_cmn_uphy_hclk.clkr, + [GCC_REFGEN_CMN_UPHY_CORE_CLK] = &gcc_refgen_cmn_uphy_core_clk.clkr, }; static const struct qcom_reset_map gcc_ipq9650_resets[] = { From b7088d58dccfba87fe8dd2ab7c493ee1d9d09277 Mon Sep 17 00:00:00 2001 From: Jens Glathe Date: Mon, 8 Jun 2026 06:54:39 +0200 Subject: [PATCH 0142/1328] drm/msm/dp: add missing drm_edid_connector_update() before add_modes on cached EDID After the refactor to struct drm_edid, the fast path in msm_dp_panel_get_modes() that already held a cached EDID called drm_edid_connector_add_modes() directly without first calling drm_edid_connector_update(). The new API requires the update step to associate the EDID with the connector. Add the missing call. This restores correct behaviour for the cached-EDID path. Fixes: 5bea90ad9743 ("drm/msm/dp: switch to struct drm_edid") Reviewed-by: Dmitry Baryshkov Signed-off-by: Jens Glathe Patchwork: https://patchwork.freedesktop.org/patch/731125/ Link: https://lore.kernel.org/r/20260608-drm_plug_flaky_edid-v3-1-1ca632938e7f@oldschoolsolutions.biz Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_panel.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/dp/dp_panel.c b/drivers/gpu/drm/msm/dp/dp_panel.c index 6bb021820d7c..5b4954e7cb74 100644 --- a/drivers/gpu/drm/msm/dp/dp_panel.c +++ b/drivers/gpu/drm/msm/dp/dp_panel.c @@ -332,8 +332,10 @@ int msm_dp_panel_get_modes(struct msm_dp_panel *msm_dp_panel, return -EINVAL; } - if (msm_dp_panel->drm_edid) + if (msm_dp_panel->drm_edid) { + drm_edid_connector_update(connector, msm_dp_panel->drm_edid); return drm_edid_connector_add_modes(connector); + } return 0; } From 6cd33b6f4155efc20485929fd0b56bb704641db9 Mon Sep 17 00:00:00 2001 From: Kavan Smith Date: Mon, 6 Jul 2026 18:32:40 -0700 Subject: [PATCH 0143/1328] drm/msm/dsi: round 6G byte clock rate to the PLL-achievable value MSM8916 runtime DSI commands still go through msm_dsi_host_xfer_prepare(), which re-applies the link clock rate before enabling the link clocks. That is fine in principle, but on DSI 6G the requested byte clock rate often does not exactly match the DSI PHY PLL's realizable rate. For example, the driver can request 56250000 Hz while the PLL actually runs at 56246337 Hz. Because the requested and actual rates differ slightly, every later link_clk_set_rate() call is treated as a real clock change and re-locks the PLL. On a video-mode panel without an internal timing generator, such as samsung,s6d7aa0 / lsl080al03 on MSM8916, that live-clock glitch makes the panel lose pixel lock and visibly corrupts scanout on each runtime DCS command, including backlight writes. Fix this by rounding the computed 6G byte clock rate up front, before it is stored in msm_host->byte_clk_rate and reused by later transfers. Once the host carries the PLL-achievable rate instead of the idealized one, repeated link_clk_set_rate() calls become no-ops in the common clock framework and no longer re-lock the PLL. This keeps the normal transfer callback sequencing intact, preserves the OPP vote path in link_clk_set_rate(), and matches the fix direction suggested in the original 2018 discussion. Reported-by: Daniel Mack Closes: https://lore.kernel.org/all/1a682c5b-7fc9-3aaa-120b-64b239a355a3@zonque.org/ Fixes: 6b16f05aa39f ("drm/msm/dsi: Split clk rate setting and enable") Cc: stable@vger.kernel.org Signed-off-by: Kavan Smith Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/738234/ Link: https://lore.kernel.org/r/20260707013240.681012-1-kavansmith82@gmail.com [DB: dropped extra chunk from the patch] Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dsi/dsi_host.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/gpu/drm/msm/dsi/dsi_host.c b/drivers/gpu/drm/msm/dsi/dsi_host.c index 982abaaac00d..5e1b313f04c0 100644 --- a/drivers/gpu/drm/msm/dsi/dsi_host.c +++ b/drivers/gpu/drm/msm/dsi/dsi_host.c @@ -670,12 +670,24 @@ static void dsi_calc_pclk(struct msm_dsi_host *msm_host, bool is_bonded_dsi) int dsi_calc_clk_rate_6g(struct msm_dsi_host *msm_host, bool is_bonded_dsi) { + long rounded_byte_clk_rate; + if (!msm_host->mode) { pr_err("%s: mode not set\n", __func__); return -EINVAL; } dsi_calc_pclk(msm_host, is_bonded_dsi); + + rounded_byte_clk_rate = clk_round_rate(msm_host->byte_clk, + msm_host->byte_clk_rate); + if (rounded_byte_clk_rate < 0) { + pr_err("%s: failed to round byte clock rate, %ld\n", + __func__, rounded_byte_clk_rate); + return rounded_byte_clk_rate; + } + + msm_host->byte_clk_rate = rounded_byte_clk_rate; msm_host->esc_clk_rate = clk_get_rate(msm_host->esc_clk); return 0; } From 14ea7c582879978a7ec0ddbd65d485f2a826053c Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sat, 30 May 2026 13:13:42 -0700 Subject: [PATCH 0144/1328] drm/msm/dpu: fix parameter name in dpu_core_perf_adjusted_mode_clk kernel-doc The kernel-doc referred to @crtc_clk_rate but the actual parameter is @mode_clk_rate. Assisted-by: Opencode:Big-pickle Signed-off-by: Rosen Penev Fixes: 62b7d6835288 ("drm/msm/dpu: Filter modes based on adjusted mode clock") Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/729413/ Link: https://lore.kernel.org/r/20260530201342.10538-1-rosenp@gmail.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/disp/dpu1/dpu_core_perf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_core_perf.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_core_perf.c index 13cc658065c5..2ff255d7795e 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_core_perf.c +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_core_perf.c @@ -34,7 +34,7 @@ enum dpu_perf_mode { /** * dpu_core_perf_adjusted_mode_clk - Adjust given mode clock rate according to * the perf clock factor. - * @crtc_clk_rate - Unadjusted mode clock rate + * @mode_clk_rate: unadjusted mode clock rate * @perf_cfg: performance configuration */ u64 dpu_core_perf_adjusted_mode_clk(u64 mode_clk_rate, From 44784327815b2a1ad8bb56b9236770cb538c7c27 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Sun, 12 Jul 2026 14:56:55 +0300 Subject: [PATCH 0145/1328] Revert "drm/msm: dsi: fix PLL init in bonded mode" Commit 93c97bc8d85d ("drm/msm: dsi: fix PLL init in bonded mode") fixed one of the issues with the DSI bonded mode, but broke non-bonded usecase for DSI as reported by Mohit Dsor. Clock divider is being programmed incorrectly, resultin in the wrong display mode being selected. Revert the offending commit, letting Neil to work on a better fix. Fixes: 93c97bc8d85d ("drm/msm: dsi: fix PLL init in bonded mode") Reported-by: Mohit Dsor Closes: https://lore.kernel.org/r/ae07cef84AmXK43H@hu-mdsor-hyd.qualcomm.com Cc: Neil Armstrong Cc: Thorsten Leemhuis Signed-off-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/739459/ Link: https://lore.kernel.org/r/20260712-msm-revert-dsi-pll-fix-v1-1-40122689ea25@oss.qualcomm.com --- drivers/gpu/drm/msm/dsi/phy/dsi_phy.h | 1 + drivers/gpu/drm/msm/dsi/phy/dsi_phy_7nm.c | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/msm/dsi/phy/dsi_phy.h b/drivers/gpu/drm/msm/dsi/phy/dsi_phy.h index 21a59d66e8dc..f5d3e806f8fd 100644 --- a/drivers/gpu/drm/msm/dsi/phy/dsi_phy.h +++ b/drivers/gpu/drm/msm/dsi/phy/dsi_phy.h @@ -111,6 +111,7 @@ struct msm_dsi_phy { struct msm_dsi_dphy_timing timing; const struct msm_dsi_phy_cfg *cfg; void *tuning_cfg; + void *pll_data; enum msm_dsi_phy_usecase usecase; bool regulator_ldo_mode; diff --git a/drivers/gpu/drm/msm/dsi/phy/dsi_phy_7nm.c b/drivers/gpu/drm/msm/dsi/phy/dsi_phy_7nm.c index 984a66085dfb..5d805a797abd 100644 --- a/drivers/gpu/drm/msm/dsi/phy/dsi_phy_7nm.c +++ b/drivers/gpu/drm/msm/dsi/phy/dsi_phy_7nm.c @@ -426,8 +426,11 @@ static void dsi_pll_enable_pll_bias(struct dsi_pll_7nm *pll) u32 data; spin_lock_irqsave(&pll->pll_enable_lock, flags); - pll->pll_enable_cnt++; - WARN_ON(pll->pll_enable_cnt == INT_MAX); + if (pll->pll_enable_cnt++) { + spin_unlock_irqrestore(&pll->pll_enable_lock, flags); + WARN_ON(pll->pll_enable_cnt == INT_MAX); + return; + } data = readl(pll->phy->base + REG_DSI_7nm_PHY_CMN_CTRL_0); data |= DSI_7nm_PHY_CMN_CTRL_0_PLL_SHUTDOWNB; @@ -873,6 +876,7 @@ static int dsi_pll_7nm_init(struct msm_dsi_phy *phy) spin_lock_init(&pll_7nm->pll_enable_lock); pll_7nm->phy = phy; + phy->pll_data = pll_7nm; ret = pll_7nm_register(pll_7nm, phy->provided_clocks->hws); if (ret) { @@ -961,8 +965,10 @@ static int dsi_7nm_phy_enable(struct msm_dsi_phy *phy, u32 const delay_us = 5; u32 const timeout_us = 1000; struct msm_dsi_dphy_timing *timing = &phy->timing; + struct dsi_pll_7nm *pll = phy->pll_data; void __iomem *base = phy->base; bool less_than_1500_mhz; + unsigned long flags; u32 vreg_ctrl_0, vreg_ctrl_1, lane_ctrl0; u32 glbl_pemph_ctrl_0; u32 glbl_str_swi_cal_sel_ctrl, glbl_hstx_str_ctrl_0; @@ -1084,10 +1090,13 @@ static int dsi_7nm_phy_enable(struct msm_dsi_phy *phy, glbl_rescode_bot_ctrl = 0x3c; } + spin_lock_irqsave(&pll->pll_enable_lock, flags); + pll->pll_enable_cnt = 1; /* de-assert digital and pll power down */ data = DSI_7nm_PHY_CMN_CTRL_0_DIGTOP_PWRDN_B | DSI_7nm_PHY_CMN_CTRL_0_PLL_SHUTDOWNB; writel(data, base + REG_DSI_7nm_PHY_CMN_CTRL_0); + spin_unlock_irqrestore(&pll->pll_enable_lock, flags); /* Assert PLL core reset */ writel(0x00, base + REG_DSI_7nm_PHY_CMN_PLL_CNTRL); @@ -1200,7 +1209,9 @@ static bool dsi_7nm_set_continuous_clock(struct msm_dsi_phy *phy, bool enable) static void dsi_7nm_phy_disable(struct msm_dsi_phy *phy) { + struct dsi_pll_7nm *pll = phy->pll_data; void __iomem *base = phy->base; + unsigned long flags; u32 data; DBG(""); @@ -1227,8 +1238,11 @@ static void dsi_7nm_phy_disable(struct msm_dsi_phy *phy) writel(data, base + REG_DSI_7nm_PHY_CMN_CTRL_0); writel(0, base + REG_DSI_7nm_PHY_CMN_LANE_CTRL0); + spin_lock_irqsave(&pll->pll_enable_lock, flags); + pll->pll_enable_cnt = 0; /* Turn off all PHY blocks */ writel(0x00, base + REG_DSI_7nm_PHY_CMN_CTRL_0); + spin_unlock_irqrestore(&pll->pll_enable_lock, flags); /* make sure phy is turned off */ wmb(); From d5083527908a994496519f5b9098b33460f365d6 Mon Sep 17 00:00:00 2001 From: Denzeel Oliva Date: Sat, 13 Jun 2026 07:36:16 -0500 Subject: [PATCH 0146/1328] dt-bindings: clock: exynos990: Add CLK_GOUT_PERIS_TMU_SUB_PCLK Add the missing TMU_SUB_PCLK clock ID for the Exynos990 PERIS CMU. Signed-off-by: Denzeel Oliva Reviewed-by: Peter Griffin Link: https://patch.msgid.link/20260613-exynos990-peris-fix-v3-v3-1-2b230db78ae4@gmail.com Signed-off-by: Krzysztof Kozlowski --- include/dt-bindings/clock/samsung,exynos990.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/dt-bindings/clock/samsung,exynos990.h b/include/dt-bindings/clock/samsung,exynos990.h index 47540307cb52..c06f591d9d90 100644 --- a/include/dt-bindings/clock/samsung,exynos990.h +++ b/include/dt-bindings/clock/samsung,exynos990.h @@ -434,5 +434,6 @@ #define CLK_GOUT_PERIS_TMU_TOP_PCLK 17 #define CLK_GOUT_PERIS_OTP_CON_BIRA_OSCCLK 18 #define CLK_GOUT_PERIS_OTP_CON_TOP_OSCCLK 19 +#define CLK_GOUT_PERIS_TMU_SUB_PCLK 20 #endif From b7d6221aa44fc523c3418ecd94c582eea6e6d53a Mon Sep 17 00:00:00 2001 From: Denzeel Oliva Date: Sat, 13 Jun 2026 07:36:17 -0500 Subject: [PATCH 0147/1328] clk: samsung: exynos990: Add PERIS TMU_SUB_PCLK gate Add the missing CLK_GOUT_PERIS_TMU_SUB_PCLK gate clock for the Thermal Management Unit sub-block and update CLKS_NR_PERIS accordingly. Signed-off-by: Denzeel Oliva Reviewed-by: Peter Griffin Link: https://patch.msgid.link/20260613-exynos990-peris-fix-v3-v3-2-2b230db78ae4@gmail.com Signed-off-by: Krzysztof Kozlowski --- drivers/clk/samsung/clk-exynos990.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/clk/samsung/clk-exynos990.c b/drivers/clk/samsung/clk-exynos990.c index 4385c3b76dd6..ee3566b8e57c 100644 --- a/drivers/clk/samsung/clk-exynos990.c +++ b/drivers/clk/samsung/clk-exynos990.c @@ -21,7 +21,7 @@ #define CLKS_NR_HSI0 (CLK_GOUT_HSI0_LHS_ACEL_D_HSI0_CLK + 1) #define CLKS_NR_PERIC0 (CLK_GOUT_PERIC0_SYSREG_PCLK + 1) #define CLKS_NR_PERIC1 (CLK_GOUT_PERIC1_XIU_P_ACLK + 1) -#define CLKS_NR_PERIS (CLK_GOUT_PERIS_OTP_CON_TOP_OSCCLK + 1) +#define CLKS_NR_PERIS (CLK_GOUT_PERIS_TMU_SUB_PCLK + 1) /* ---- CMU_TOP ------------------------------------------------------------- */ @@ -2619,6 +2619,10 @@ static const struct samsung_gate_clock peris_gate_clks[] __initconst = { "gout_peris_d_tzpc_peris_pclk", "mout_peris_bus_user", CLK_CON_GAT_GOUT_BLK_PERIS_UID_D_TZPC_PERIS_IPCLKPORT_PCLK, 21, 0, 0), + GATE(CLK_GOUT_PERIS_TMU_SUB_PCLK, + "gout_peris_tmu_sub_pclk", "mout_peris_bus_user", + CLK_CON_GAT_GOUT_BLK_PERIS_UID_TMU_SUB_IPCLKPORT_PCLK, + 21, 0, 0), GATE(CLK_GOUT_PERIS_TMU_TOP_PCLK, "gout_peris_tmu_top_pclk", "mout_peris_clk_peris_gic", CLK_CON_GAT_GOUT_BLK_PERIS_UID_TMU_TOP_IPCLKPORT_PCLK, From a016e89781478e4b756fbf104945d037d0cdc809 Mon Sep 17 00:00:00 2001 From: Denzeel Oliva Date: Sat, 13 Jun 2026 07:36:18 -0500 Subject: [PATCH 0148/1328] clk: samsung: exynos990: Fix PERIS gate clock parents Correct eight PERIS gate clock parents to match the hardware clock tree and reorder the GIC mux parents so mout_peris_bus_user is the default source. Signed-off-by: Denzeel Oliva Reviewed-by: Peter Griffin Link: https://patch.msgid.link/20260613-exynos990-peris-fix-v3-v3-3-2b230db78ae4@gmail.com Signed-off-by: Krzysztof Kozlowski --- drivers/clk/samsung/clk-exynos990.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/clk/samsung/clk-exynos990.c b/drivers/clk/samsung/clk-exynos990.c index ee3566b8e57c..df5928833b23 100644 --- a/drivers/clk/samsung/clk-exynos990.c +++ b/drivers/clk/samsung/clk-exynos990.c @@ -2551,7 +2551,7 @@ static const unsigned long peris_clk_regs[] __initconst = { /* Parent clock list for CMU_PERIS muxes */ PNAME(mout_peris_bus_user_p) = { "oscclk", "mout_cmu_peris_bus" }; -PNAME(mout_peris_clk_peris_gic_p) = { "oscclk", "mout_peris_bus_user" }; +PNAME(mout_peris_clk_peris_gic_p) = { "mout_peris_bus_user", "oscclk" }; static const struct samsung_mux_clock peris_mux_clks[] __initconst = { MUX(CLK_MOUT_PERIS_BUS_USER, "mout_peris_bus_user", @@ -2584,15 +2584,15 @@ static const struct samsung_gate_clock peris_gate_clks[] __initconst = { CLK_CON_GAT_GOUT_BLK_PERIS_UID_RSTNSYNC_CLK_PERIS_BUSP_IPCLKPORT_CLK, 21, 0, 0), GATE(CLK_GOUT_PERIS_CLK_PERIS_OSCCLK_CLK, - "gout_peris_clk_peris_oscclk_clk", "mout_peris_bus_user", + "gout_peris_clk_peris_oscclk_clk", "oscclk", CLK_CON_GAT_CLK_BLK_PERIS_UID_RSTNSYNC_CLK_PERIS_OSCCLK_IPCLKPORT_CLK, 21, 0, 0), GATE(CLK_GOUT_PERIS_CLK_PERIS_GIC_CLK, - "gout_peris_clk_peris_gic_clk", "mout_peris_bus_user", + "gout_peris_clk_peris_gic_clk", "mout_peris_clk_peris_gic", CLK_CON_GAT_GOUT_BLK_PERIS_UID_RSTNSYNC_CLK_PERIS_GIC_IPCLKPORT_CLK, 21, 0, 0), GATE(CLK_GOUT_PERIS_AD_AXI_P_PERIS_ACLKM, - "gout_peris_ad_axi_p_peris_aclkm", "mout_peris_bus_user", + "gout_peris_ad_axi_p_peris_aclkm", "mout_peris_clk_peris_gic", CLK_CON_GAT_GOUT_BLK_PERIS_UID_AD_AXI_P_PERIS_IPCLKPORT_ACLKM, 21, CLK_IGNORE_UNUSED, 0), GATE(CLK_GOUT_PERIS_OTP_CON_BIRA_PCLK, @@ -2600,19 +2600,19 @@ static const struct samsung_gate_clock peris_gate_clks[] __initconst = { CLK_CON_GAT_GOUT_BLK_PERIS_UID_OTP_CON_BIRA_IPCLKPORT_PCLK, 21, 0, 0), GATE(CLK_GOUT_PERIS_GIC_CLK, - "gout_peris_gic_clk", "mout_peris_bus_user", + "gout_peris_gic_clk", "mout_peris_clk_peris_gic", CLK_CON_GAT_GOUT_BLK_PERIS_UID_GIC_IPCLKPORT_CLK, 21, CLK_IS_CRITICAL, 0), GATE(CLK_GOUT_PERIS_LHM_AXI_P_PERIS_CLK, - "gout_peris_lhm_axi_p_peris_clk", "oscclk", + "gout_peris_lhm_axi_p_peris_clk", "mout_peris_bus_user", CLK_CON_GAT_GOUT_BLK_PERIS_UID_LHM_AXI_P_PERIS_IPCLKPORT_I_CLK, 21, CLK_IGNORE_UNUSED, 0), GATE(CLK_GOUT_PERIS_MCT_PCLK, - "gout_peris_mct_pclk", "mout_peris_clk_peris_gic", + "gout_peris_mct_pclk", "mout_peris_bus_user", CLK_CON_GAT_GOUT_BLK_PERIS_UID_MCT_IPCLKPORT_PCLK, 21, 0, 0), GATE(CLK_GOUT_PERIS_OTP_CON_TOP_PCLK, - "gout_peris_otp_con_top_pclk", "mout_peris_clk_peris_gic", + "gout_peris_otp_con_top_pclk", "mout_peris_bus_user", CLK_CON_GAT_GOUT_BLK_PERIS_UID_OTP_CON_TOP_IPCLKPORT_PCLK, 21, 0, 0), GATE(CLK_GOUT_PERIS_D_TZPC_PERIS_PCLK, @@ -2624,7 +2624,7 @@ static const struct samsung_gate_clock peris_gate_clks[] __initconst = { CLK_CON_GAT_GOUT_BLK_PERIS_UID_TMU_SUB_IPCLKPORT_PCLK, 21, 0, 0), GATE(CLK_GOUT_PERIS_TMU_TOP_PCLK, - "gout_peris_tmu_top_pclk", "mout_peris_clk_peris_gic", + "gout_peris_tmu_top_pclk", "mout_peris_bus_user", CLK_CON_GAT_GOUT_BLK_PERIS_UID_TMU_TOP_IPCLKPORT_PCLK, 21, 0, 0), GATE(CLK_GOUT_PERIS_OTP_CON_BIRA_OSCCLK, From 5d9e3bf34fec9a5d237e4b7cef4a707bc2e091bc Mon Sep 17 00:00:00 2001 From: Xiang Mei Date: Sat, 4 Jul 2026 14:03:32 -0700 Subject: [PATCH 0149/1328] xfrm6: fix out-of-bounds write in xfrm6_input_addr() when secpath is full The depth check in xfrm6_input_addr() is off by one: if (1 + sp->len == XFRM_MAX_DEPTH) goto drop; ... sp->xvec[sp->len++] = x; xfrm_input() can leave sp->len == XFRM_MAX_DEPTH, and the transport-mode receive path re-enters IPv6 input via xfrm_trans_reinject() with that secpath preserved. If the inner packet carries a destination-options HAO option or a type-2 routing header, xfrm6_input_addr() is called with sp->len == XFRM_MAX_DEPTH; the check (1 + 6 == 6) is false, so sp->xvec[sp->len++] writes one slot past the 6-element xvec[]. The write stays within the sec_path allocation (invisible to KASAN); UBSAN_BOUNDS flags it and panics under panic_on_warn. Use "sp->len >= XFRM_MAX_DEPTH", matching xfrm_input(). This also restores one chain level the old check rejected at sp->len == 5. UBSAN: array-index-out-of-bounds in net/ipv6/xfrm6_input.c:309:10 index 6 is out of range for type 'xfrm_state *[6]' Fixes: 9473e1f631de ("[XFRM] MIPv6: Fix to input RO state correctly.") Reported-by: Weiming Shi Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Xiang Mei Signed-off-by: Steffen Klassert --- net/ipv6/xfrm6_input.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/ipv6/xfrm6_input.c b/net/ipv6/xfrm6_input.c index 89d0443b5307..07edef258984 100644 --- a/net/ipv6/xfrm6_input.c +++ b/net/ipv6/xfrm6_input.c @@ -247,7 +247,7 @@ int xfrm6_input_addr(struct sk_buff *skb, xfrm_address_t *daddr, goto drop; } - if (1 + sp->len == XFRM_MAX_DEPTH) { + if (sp->len >= XFRM_MAX_DEPTH) { XFRM_INC_STATS(net, LINUX_MIB_XFRMINBUFFERERROR); goto drop; } From 21697720ff43b8dfa25b8e8d9ca7f56f4597fc80 Mon Sep 17 00:00:00 2001 From: Maher Azzouzi Date: Sun, 12 Jul 2026 18:05:30 +0100 Subject: [PATCH 0150/1328] esp: do not unref managed frag pages in esp_ssg_unref() esp_ssg_unref() releases the page references held on the source scatterlist after the AEAD operation completes. It calls skb_page_unref() on every frag page for an out-of-place transform (req->src != req->dst), and in the error path of esp_output_tail() (already_unref == true) on the request's own scatterlist. This is wrong when the skb carries managed frags (SKBFL_MANAGED_FRAG_REFS). Managed frags are owned by a zerocopy ubuf and the skb does not hold a per-frag page reference; io_uring SEND_ZC with a registered buffer attaches the bvec pages this way via io_sg_from_iter(). The rest of the stack honours this invariant: skb_release_data() skips the per-frag unref when SKBFL_MANAGED_FRAG_REFS is set, and skb_zcopy_managed() is the guard used at the other unref sites. esp_ssg_unref() is missing that guard, so for a managed-frag skb it drops a page reference the skb never acquired. This can underflow the page reference count and free a page that is still in use. Guard the function with skb_zcopy_managed() so both unref paths are skipped for managed-frag skbs, matching skb_release_data(). Fixes: cac2661c53f3 ("esp4: Avoid skb_cow_data whenever possible") Fixes: 03e2a30f6a27 ("esp6: Avoid skb_cow_data whenever possible") Signed-off-by: Maher Azzouzi Signed-off-by: Steffen Klassert --- net/ipv4/esp4.c | 7 +++++++ net/ipv6/esp6.c | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/net/ipv4/esp4.c b/net/ipv4/esp4.c index dfc81ee969ae..fa1710e27e50 100644 --- a/net/ipv4/esp4.c +++ b/net/ipv4/esp4.c @@ -104,6 +104,13 @@ static void esp_ssg_unref(struct xfrm_state *x, void *tmp, struct sk_buff *skb, struct aead_request *req; struct scatterlist *sg; + /* Managed frags are owned by the zerocopy ubuf; the skb holds no + * per-frag page reference, so we must not drop one here. Mirrors + * the SKBFL_MANAGED_FRAG_REFS handling in skb_release_data(). + */ + if (skb_zcopy_managed(skb)) + return; + if (x->props.flags & XFRM_STATE_ESN) extralen += sizeof(struct esp_output_extra); diff --git a/net/ipv6/esp6.c b/net/ipv6/esp6.c index 296b57926abb..7d216b9c59f0 100644 --- a/net/ipv6/esp6.c +++ b/net/ipv6/esp6.c @@ -121,6 +121,13 @@ static void esp_ssg_unref(struct xfrm_state *x, void *tmp, struct sk_buff *skb, struct aead_request *req; struct scatterlist *sg; + /* Managed frags are owned by the zerocopy ubuf; the skb holds no + * per-frag page reference, so we must not drop one here. Mirrors + * the SKBFL_MANAGED_FRAG_REFS handling in skb_release_data(). + */ + if (skb_zcopy_managed(skb)) + return; + if (x->props.flags & XFRM_STATE_ESN) extralen += sizeof(struct esp_output_extra); From 2df26b8663b6f315d0e9364927c8503280fe3dd3 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Thu, 9 Jul 2026 21:23:25 +0300 Subject: [PATCH 0151/1328] clk: r9a08g045: Add clocks and resets for CAN-FD Renesas RZ/G3S SoC has a CAN-FD IP. Add clocks and resets for it. Reviewed-by: Biju Das Reviewed-by: Geert Uytterhoeven Signed-off-by: Claudiu Beznea Link: https://patch.msgid.link/20260709182332.876408-2-claudiu.beznea+renesas@tuxon.dev Signed-off-by: Geert Uytterhoeven --- drivers/clk/renesas/r9a08g045-cpg.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/clk/renesas/r9a08g045-cpg.c b/drivers/clk/renesas/r9a08g045-cpg.c index 624fc5e6fb24..ea2a6a71aebd 100644 --- a/drivers/clk/renesas/r9a08g045-cpg.c +++ b/drivers/clk/renesas/r9a08g045-cpg.c @@ -77,6 +77,7 @@ enum clk_ids { CLK_SEL_PLL4, CLK_P1_DIV2, CLK_P3_DIV2, + CLK_P4_DIV2, CLK_SD0_DIV4, CLK_SD1_DIV4, CLK_SD2_DIV4, @@ -172,6 +173,8 @@ static const struct cpg_core_clk r9a08g045_core_clks[] __initconst = { DEF_G3S_DIV("P3", R9A08G045_CLK_P3, CLK_PLL3_DIV2_4, DIVPL3C, G3S_DIVPL3C_STS, dtable_1_32, 0, 0, 0, NULL), DEF_FIXED("P3_DIV2", CLK_P3_DIV2, R9A08G045_CLK_P3, 1, 2), + DEF_FIXED("P4", R9A08G045_CLK_P4, CLK_PLL2_DIV2, 1, 5), + DEF_FIXED("P4_DIV2", CLK_P4_DIV2, R9A08G045_CLK_P4, 1, 2), DEF_FIXED("P5", R9A08G045_CLK_P5, CLK_PLL2_DIV2, 1, 4), DEF_FIXED("ZT", R9A08G045_CLK_ZT, CLK_PLL3_DIV2_8, 1, 1), DEF_FIXED("S0", R9A08G045_CLK_S0, CLK_SEL_PLL4, 1, 2), @@ -274,6 +277,10 @@ static const struct rzg2l_mod_clk r9a08g045_mod_clks[] = { MSTOP(BUS_MCPU2, BIT(5))), DEF_MOD("scif5_clk_pck", R9A08G045_SCIF5_CLK_PCK, R9A08G045_CLK_P0, 0x584, 5, MSTOP(BUS_MCPU3, BIT(4))), + DEF_MOD("canfd_pclk", R9A08G045_CANFD_PCLK, CLK_P4_DIV2, 0x594, 0, + MSTOP(BUS_MCPU2, BIT(9))), + DEF_MOD("canfd_clk_ram", R9A08G045_CANFD_CLK_RAM, R9A08G045_CLK_P4, 0x594, 1, + MSTOP(BUS_MCPU2, BIT(9))), DEF_MOD("gpio_hclk", R9A08G045_GPIO_HCLK, R9A08G045_OSCCLK, 0x598, 0, MSTOP(BUS_PERI_CPU, BIT(6))), DEF_MOD("adc_adclk", R9A08G045_ADC_ADCLK, R9A08G045_CLK_TSU, 0x5a8, 0, @@ -324,6 +331,8 @@ static const struct rzg2l_reset r9a08g045_resets[] = { DEF_RST(R9A08G045_SCIF3_RST_SYSTEM_N, 0x884, 3), DEF_RST(R9A08G045_SCIF4_RST_SYSTEM_N, 0x884, 4), DEF_RST(R9A08G045_SCIF5_RST_SYSTEM_N, 0x884, 5), + DEF_RST(R9A08G045_CANFD_RSTP_N, 0x894, 0), + DEF_RST(R9A08G045_CANFD_RSTC_N, 0x894, 1), DEF_RST(R9A08G045_GPIO_RSTN, 0x898, 0), DEF_RST(R9A08G045_GPIO_PORT_RESETN, 0x898, 1), DEF_RST(R9A08G045_GPIO_SPARE_RESETN, 0x898, 2), From b2ee00d0bf4cca5dd02702fc339d2b523191966f Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Mon, 6 Jul 2026 17:56:19 -0700 Subject: [PATCH 0152/1328] clk: thead: allow COMPILE_TEST builds Follow the pattern of many other directories in this Makefile (sunxi-ng, tenstorrent, ti, etc.) by using obj-y. This allows COMPILE_TEST to select the driver without having CONFIG_ARCH_THEAD enabled Tested with: make LLVM=1 ARCH=loongarch drivers/clk/thead/ Assisted-by: Codex:GPT-5.5 Acked-by: Drew Fustini Reviewed-by: Brian Masney Signed-off-by: Rosen Penev Signed-off-by: Drew Fustini --- drivers/clk/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index cc108a75a900..6899e32e14aa 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -157,7 +157,7 @@ obj-$(CONFIG_ARCH_SUNXI) += sunxi/ obj-y += sunxi-ng/ obj-$(CONFIG_ARCH_TEGRA) += tegra/ obj-y += tenstorrent/ -obj-$(CONFIG_ARCH_THEAD) += thead/ +obj-y += thead/ obj-y += ti/ obj-$(CONFIG_CLK_UNIPHIER) += uniphier/ obj-$(CONFIG_ARCH_U8500) += ux500/ From c431011ab65fd4ef205e3ce8cfc4b031c67768d9 Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Sun, 12 Jul 2026 19:17:59 +0900 Subject: [PATCH 0153/1328] apparmor: replace decompress_zstd() prototype with its entity Fix "undefined symbol: decompress_zstd" error caused by decompress_zstd() being guarded by CONFIG_SECURITY_APPARMOR_EXPORT_BINARY=y. Reported-by: syzbot+1f14a35d0c73d31555e4@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=1f14a35d0c73d31555e4 Fixes: 17b5758bf35c ("apparmor: Initial support for compressed policies") Signed-off-by: Tetsuo Handa Acked-by: Georgia Garcia Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 72 +++++++++++++++++----------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 2ae9ab94a5a9..152c7967ff1b 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -483,7 +483,42 @@ static struct aa_loaddata *aa_simple_write_to_buffer(const char __user *userbuf, return data; } -static int decompress_zstd(char *src, size_t slen, char *dst, size_t dlen); + +static int decompress_zstd(char *src, size_t slen, char *dst, size_t dlen) +{ + if (slen < dlen) { + const size_t wksp_len = zstd_dctx_workspace_bound(); + zstd_dctx *ctx; + void *wksp; + size_t out_len; + int ret = 0; + + wksp = kvzalloc(wksp_len, GFP_KERNEL); + if (!wksp) { + ret = -ENOMEM; + goto cleanup; + } + ctx = zstd_init_dctx(wksp, wksp_len); + if (ctx == NULL) { + ret = -ENOMEM; + goto cleanup; + } + out_len = zstd_decompress_dctx(ctx, dst, dlen, src, slen); + if (zstd_is_error(out_len)) { + ret = -EINVAL; + goto cleanup; + } +cleanup: + kvfree(wksp); + return ret; + } + + if (dlen < slen) + return -EINVAL; + memcpy(dst, src, slen); + return 0; +} + /** * aa_get_data_from_compressed - common routine for getting compressed policy * from user and get both compressed and uncompressed version. @@ -1517,41 +1552,6 @@ SEQ_RAWDATA_FOPS(revision); SEQ_RAWDATA_FOPS(hash); SEQ_RAWDATA_FOPS(compressed_size); -static int decompress_zstd(char *src, size_t slen, char *dst, size_t dlen) -{ - if (slen < dlen) { - const size_t wksp_len = zstd_dctx_workspace_bound(); - zstd_dctx *ctx; - void *wksp; - size_t out_len; - int ret = 0; - - wksp = kvzalloc(wksp_len, GFP_KERNEL); - if (!wksp) { - ret = -ENOMEM; - goto cleanup; - } - ctx = zstd_init_dctx(wksp, wksp_len); - if (ctx == NULL) { - ret = -ENOMEM; - goto cleanup; - } - out_len = zstd_decompress_dctx(ctx, dst, dlen, src, slen); - if (zstd_is_error(out_len)) { - ret = -EINVAL; - goto cleanup; - } -cleanup: - kvfree(wksp); - return ret; - } - - if (dlen < slen) - return -EINVAL; - memcpy(dst, src, slen); - return 0; -} - static ssize_t rawdata_read(struct file *file, char __user *buf, size_t size, loff_t *ppos) { From d22a37e9fba7838cdcb8c55b913b1a37a7a4a198 Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Wed, 15 Jul 2026 16:29:14 +0300 Subject: [PATCH 0154/1328] dt-bindings: clock: qcom: glymur-gcc: Add missing CX power domain The GDSCs provided by the Glymur GCC are supplied by the RPMh CX power domain. Model that parent domain in the GCC binding so the provider can describe the dependency in devicetree. Add a single CX power-domain entry to the binding and make it required, matching the hardware description needed by the GCC node. Fixes: ee2d967030fe ("dt-bindings: clock: qcom: document the Glymur Global Clock Controller") Signed-off-by: Abel Vesa Link: https://lore.kernel.org/r/20260715-glymur-fix-gcc-cx-scaling-v3-1-72eb5adad156@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- .../devicetree/bindings/clock/qcom,glymur-gcc.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Documentation/devicetree/bindings/clock/qcom,glymur-gcc.yaml b/Documentation/devicetree/bindings/clock/qcom,glymur-gcc.yaml index b05b0e6c4483..7a4054c9f215 100644 --- a/Documentation/devicetree/bindings/clock/qcom,glymur-gcc.yaml +++ b/Documentation/devicetree/bindings/clock/qcom,glymur-gcc.yaml @@ -65,9 +65,15 @@ properties: - description: USB4 PHY 2 pcie pipe clock source - description: USB4 PHY 2 Max pipe clock source + power-domains: + description: + A phandle and PM domain specifier for the CX power domain. + maxItems: 1 + required: - compatible - clocks + - power-domains - '#power-domain-cells' allOf: @@ -78,6 +84,7 @@ unevaluatedProperties: false examples: - | #include + #include clock-controller@100000 { compatible = "qcom,glymur-gcc"; reg = <0x100000 0x1f9000>; @@ -113,6 +120,7 @@ examples: <&usb4_phy_0_pcie_pipe>, <&usb4_phy_0_max_pipe>, <&usb4_phy_1_pcie_pipe>, <&usb4_phy_1_max_pipe>, <&usb4_phy_2_pcie_pipe>, <&usb4_phy_2_max_pipe>; + power-domains = <&rpmhpd RPMHPD_CX>; #clock-cells = <1>; #reset-cells = <1>; #power-domain-cells = <1>; From 8d4f342369d0d77f32a0211692442d3b6d455872 Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Wed, 15 Jul 2026 16:29:15 +0300 Subject: [PATCH 0155/1328] clk: qcom: gcc-glymur: Enable runtime PM Enable runtime PM for the controller so the common GCC probe path resumes the attached domain while registering clocks, resets and GDSCs. This lets GDSC consumers propagate their votes through the GCC provider to the CX parent domain. Fixes: efe504300a17 ("clk: qcom: gcc: Add support for Global Clock Controller") Signed-off-by: Abel Vesa Link: https://lore.kernel.org/r/20260715-glymur-fix-gcc-cx-scaling-v3-2-72eb5adad156@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gcc-glymur.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/clk/qcom/gcc-glymur.c b/drivers/clk/qcom/gcc-glymur.c index 32d23bdc819b..6bf8db437c17 100644 --- a/drivers/clk/qcom/gcc-glymur.c +++ b/drivers/clk/qcom/gcc-glymur.c @@ -8549,6 +8549,7 @@ static const struct qcom_cc_desc gcc_glymur_desc = { .num_resets = ARRAY_SIZE(gcc_glymur_resets), .gdscs = gcc_glymur_gdscs, .num_gdscs = ARRAY_SIZE(gcc_glymur_gdscs), + .use_rpm = true, .driver_data = &gcc_glymur_driver_data, }; From 4d8da7e3dc0d4512e5e6f29df3ca136d7a90d464 Mon Sep 17 00:00:00 2001 From: Samuel Holland Date: Sat, 11 Jul 2026 14:28:25 -0500 Subject: [PATCH 0156/1328] dt-bindings: leds: Document "gpio" trigger The "gpio" trigger is used to control LEDs by the level of a GPIO input referenced by the trigger-sources property. This is already documented in the trigger-sources property description and is used in at least one devicetree (am335x-regor.dtsi). Document it so validation of those and future DTs will pass. Signed-off-by: Samuel Holland Acked-by: Conor Dooley Link: https://patch.msgid.link/20260711192842.845048-2-samuel@sholland.org Signed-off-by: Lee Jones --- Documentation/devicetree/bindings/leds/common.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/leds/common.yaml b/Documentation/devicetree/bindings/leds/common.yaml index f4e44b33f56d..e148b4277a6f 100644 --- a/Documentation/devicetree/bindings/leds/common.yaml +++ b/Documentation/devicetree/bindings/leds/common.yaml @@ -106,6 +106,8 @@ properties: - bluetooth-power # LED indicates camera flash state - flash + # LED indicates level of GPIO input referenced by trigger-sources + - gpio # LED indicated keyboard capslock - kbd-capslock # LED indicates MTD memory activity From ce6fc04113241c82a698f362f8dc6e1cc2f65448 Mon Sep 17 00:00:00 2001 From: Manuel Ebner Date: Fri, 10 Jul 2026 13:25:29 +0200 Subject: [PATCH 0157/1328] dt-bindings: backlight: 88pm860x: Add missing bracket Add missing '};' to code. Signed-off-by: Manuel Ebner Link: https://patch.msgid.link/20260710112529.435341-2-manuelebner@mailbox.org Signed-off-by: Lee Jones --- Documentation/devicetree/bindings/leds/backlight/88pm860x.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/leds/backlight/88pm860x.txt b/Documentation/devicetree/bindings/leds/backlight/88pm860x.txt index 261df2799315..9e17807d2ce5 100644 --- a/Documentation/devicetree/bindings/leds/backlight/88pm860x.txt +++ b/Documentation/devicetree/bindings/leds/backlight/88pm860x.txt @@ -13,3 +13,4 @@ Example: }; backlight-2 { }; + }; From 627666f7c9cd89e7c4023ba790a44610ccfb9471 Mon Sep 17 00:00:00 2001 From: Ninad Naik Date: Wed, 8 Jul 2026 00:14:28 +0530 Subject: [PATCH 0158/1328] dt-bindings: leds: bcm6358: Convert to DT schema Convert the brcm,bcm6358 to DT schema. Signed-off-by: Ninad Naik Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260707184437.500278-1-ninadnaik07@gmail.com Signed-off-by: Lee Jones --- .../bindings/leds/brcm,bcm6358-leds.yaml | 96 ++++++++++++ .../devicetree/bindings/leds/leds-bcm6358.txt | 143 ------------------ 2 files changed, 96 insertions(+), 143 deletions(-) create mode 100644 Documentation/devicetree/bindings/leds/brcm,bcm6358-leds.yaml delete mode 100644 Documentation/devicetree/bindings/leds/leds-bcm6358.txt diff --git a/Documentation/devicetree/bindings/leds/brcm,bcm6358-leds.yaml b/Documentation/devicetree/bindings/leds/brcm,bcm6358-leds.yaml new file mode 100644 index 000000000000..95fa311fddb5 --- /dev/null +++ b/Documentation/devicetree/bindings/leds/brcm,bcm6358-leds.yaml @@ -0,0 +1,96 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/leds/brcm,bcm6358-leds.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: LEDs connected to Broadcom BCM6358 controller + +description: | + This controller is present on BCM6358 and BCM6368. + In these SoCs there are Serial LEDs (LEDs connected to a 74x164 controller), + which can either be controlled by software (exporting the 74x164 as spi-gpio. + See Documentation/devicetree/bindings/gpio/fairchild,74hc595.yaml), or + by hardware using this driver. + +maintainers: + - Álvaro Fernández Rojas + +properties: + compatible: + const: brcm,bcm6358-leds + + reg: + maxItems: 1 + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + + brcm,clk-div: + description: SCK signal divider. + default: 1 + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [1, 2, 4, 8] + + brcm,clk-dat-low: + description: Makes clock and data signals active low. + type: boolean + +patternProperties: + "^led@1?[0-9a-f]$": + type: object + $ref: common.yaml# + unevaluatedProperties: false + description: Each LED is represented as a sub-node of + this device. + + properties: + reg: + description: LED pin number (0 to 31). + maxItems: 1 + + required: + - reg + +required: + - compatible + - reg + - "#address-cells" + - "#size-cells" + +additionalProperties: false + +examples: + - | + #include + led-controller@fffe00d0 { + compatible = "brcm,bcm6358-leds"; + #address-cells = <1>; + #size-cells = <0>; + reg = <0xfffe00d0 0x8>; + + led@0 { + reg = <0>; + active-low; + label = "white:alarm"; + }; + led@2 { + reg = <2>; + active-low; + label = "white:tv"; + }; + led@3 { + reg = <3>; + active-low; + label = "white:tel"; + }; + led@4 { + reg = <4>; + active-low; + label = "white:adsl"; + }; + }; +... diff --git a/Documentation/devicetree/bindings/leds/leds-bcm6358.txt b/Documentation/devicetree/bindings/leds/leds-bcm6358.txt deleted file mode 100644 index 211ffc3c4a20..000000000000 --- a/Documentation/devicetree/bindings/leds/leds-bcm6358.txt +++ /dev/null @@ -1,143 +0,0 @@ -LEDs connected to Broadcom BCM6358 controller - -This controller is present on BCM6358 and BCM6368. -In these SoCs there are Serial LEDs (LEDs connected to a 74x164 controller), -which can either be controlled by software (exporting the 74x164 as spi-gpio. -See Documentation/devicetree/bindings/gpio/fairchild,74hc595.yaml), or -by hardware using this driver. - -Required properties: - - compatible : should be "brcm,bcm6358-leds". - - #address-cells : must be 1. - - #size-cells : must be 0. - - reg : BCM6358 LED controller address and size. - -Optional properties: - - brcm,clk-div : SCK signal divider. Possible values are 1, 2, 4 and 8. - Default : 1 - - brcm,clk-dat-low : Boolean, makes clock and data signals active low. - Default : false - -Each LED is represented as a sub-node of the brcm,bcm6358-leds device. - -LED sub-node required properties: - - reg : LED pin number (only LEDs 0 to 31 are valid). - -LED sub-node optional properties: - - label : see Documentation/devicetree/bindings/leds/common.txt - - default-state : see - Documentation/devicetree/bindings/leds/common.txt - - linux,default-trigger : see - Documentation/devicetree/bindings/leds/common.txt - -Examples: -Scenario 1 : BCM6358 - leds0: led-controller@fffe00d0 { - compatible = "brcm,bcm6358-leds"; - #address-cells = <1>; - #size-cells = <0>; - reg = <0xfffe00d0 0x8>; - - alarm_white { - reg = <0>; - active-low; - label = "white:alarm"; - }; - tv_white { - reg = <2>; - active-low; - label = "white:tv"; - }; - tel_white { - reg = <3>; - active-low; - label = "white:tel"; - }; - adsl_white { - reg = <4>; - active-low; - label = "white:adsl"; - }; - }; - -Scenario 2 : BCM6368 - leds0: led-controller@100000d0 { - compatible = "brcm,bcm6358-leds"; - #address-cells = <1>; - #size-cells = <0>; - reg = <0x100000d0 0x8>; - brcm,pol-low; - brcm,clk-div = <4>; - - power_red { - reg = <0>; - active-low; - label = "red:power"; - }; - power_green { - reg = <1>; - active-low; - label = "green:power"; - default-state = "on"; - }; - power_blue { - reg = <2>; - label = "blue:power"; - }; - broadband_red { - reg = <3>; - active-low; - label = "red:broadband"; - }; - broadband_green { - reg = <4>; - label = "green:broadband"; - }; - broadband_blue { - reg = <5>; - active-low; - label = "blue:broadband"; - }; - wireless_red { - reg = <6>; - active-low; - label = "red:wireless"; - }; - wireless_green { - reg = <7>; - active-low; - label = "green:wireless"; - }; - wireless_blue { - reg = <8>; - label = "blue:wireless"; - }; - phone_red { - reg = <9>; - active-low; - label = "red:phone"; - }; - phone_green { - reg = <10>; - active-low; - label = "green:phone"; - }; - phone_blue { - reg = <11>; - label = "blue:phone"; - }; - upgrading_red { - reg = <12>; - active-low; - label = "red:upgrading"; - }; - upgrading_green { - reg = <13>; - active-low; - label = "green:upgrading"; - }; - upgrading_blue { - reg = <14>; - label = "blue:upgrading"; - }; - }; From b8a9c9c5787bed1243e5364c89ca66c26b4e4d83 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 11 Jun 2026 13:27:30 +0300 Subject: [PATCH 0159/1328] drm/msm/adreno: fix use after free on error path in a6xx_gpu_init() The a6xx_destroy() function frees "a6xx_gpu" and so "adreno_gpu" points to freed memory. Preserve the error code before freeing the memory to avoid a use after free. Fixes: d158886cba08 ("drm/msm/adreno: Trust the SSoT UBWC config") Signed-off-by: Dan Carpenter Reviewed-by: Konrad Dybcio Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/732275/ Message-ID: Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/adreno/a6xx_gpu.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gpu.c b/drivers/gpu/drm/msm/adreno/a6xx_gpu.c index 8b3bb2fd433b..a44380316aaa 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gpu.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_gpu.c @@ -2770,8 +2770,9 @@ static struct msm_gpu *a6xx_gpu_init(struct drm_device *dev) adreno_gpu->ubwc_config = qcom_ubwc_config_get_data(); if (IS_ERR(adreno_gpu->ubwc_config)) { + ret = PTR_ERR(adreno_gpu->ubwc_config); a6xx_destroy(&(a6xx_gpu->base.base)); - return ERR_CAST(adreno_gpu->ubwc_config); + return ERR_PTR(ret); } /* Set up the preemption specific bits and pieces for each ringbuffer */ From be0e82b8e0c96649b8bc77a99ab0d185243b7659 Mon Sep 17 00:00:00 2001 From: Alexandre MINETTE Date: Wed, 10 Jun 2026 09:49:17 +0200 Subject: [PATCH 0160/1328] drm/msm/a3xx: Drain VBIF before GPU suspend A3xx hangs after every runtime suspend on the Samsung Galaxy S4 GT-I9505. Even simple GPU workloads, such as drawing a single triangle, hang reliably once the GPU has been suspended by runtime PM. The generic MSM GPU suspend path disables clocks/power, but A3xx also needs to ensure that pending VBIF transactions are drained before that happens. Add an A3xx-specific pm_suspend callback. Wait for the GPU to become idle, halt all VBIF XIN clients, wait for the corresponding acknowledgment, and only then enter the generic MSM GPU suspend path. This fixes reliable A3xx GPU hangs observed after runtime PM on the Samsung Galaxy S4 GT-I9505, codename jflte. The failure is reported as: mdp4 5100000.display-controller: [drm:hangcheck_handler] *ERROR* 3.2.0.2: hangcheck detected gpu lockup rb 0! mdp4 5100000.display-controller: [drm:hangcheck_handler] *ERROR* 3.2.0.2: completed fence: 4294967041 mdp4 5100000.display-controller: [drm:hangcheck_handler] *ERROR* 3.2.0.2: submitted fence: 4294967049 mdp4 5100000.display-controller: [drm:recover_worker] *ERROR* 3.2.0.2: hangcheck recover! Link: https://github.com/freedreno-zz/freedreno/issues/12 Signed-off-by: Alexandre MINETTE Patchwork: https://patchwork.freedesktop.org/patch/731919/ Message-ID: <20260610-mainline-fix-a3xx-gpu-hang-sending-v1-1-9282182840b5@alex-min.fr> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/adreno/a3xx_gpu.c | 36 ++++++++++++++++++- drivers/gpu/drm/msm/registers/adreno/a3xx.xml | 2 ++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/adreno/a3xx_gpu.c b/drivers/gpu/drm/msm/adreno/a3xx_gpu.c index c17e9777beae..0259e60b8011 100644 --- a/drivers/gpu/drm/msm/adreno/a3xx_gpu.c +++ b/drivers/gpu/drm/msm/adreno/a3xx_gpu.c @@ -25,6 +25,8 @@ extern bool hang_debug; +#define A3XX_VBIF_XIN_HALT_CTRL0_MASK GENMASK(5, 0) + static void a3xx_dump(struct msm_gpu *gpu); static bool a3xx_idle(struct msm_gpu *gpu); @@ -496,6 +498,38 @@ static u64 a3xx_gpu_busy(struct msm_gpu *gpu, unsigned long *out_sample_rate) return busy_cycles; } +static int a3xx_vbif_halt(struct msm_gpu *gpu) +{ + u32 ack; + int ret; + + gpu_write(gpu, REG_A3XX_VBIF_XIN_HALT_CTRL0, + A3XX_VBIF_XIN_HALT_CTRL0_MASK); + ret = spin_until(((ack = gpu_read(gpu, REG_A3XX_VBIF_XIN_HALT_CTRL1)) & + A3XX_VBIF_XIN_HALT_CTRL0_MASK) == + A3XX_VBIF_XIN_HALT_CTRL0_MASK); + gpu_write(gpu, REG_A3XX_VBIF_XIN_HALT_CTRL0, 0); + + if (ret) + return -EBUSY; + + return 0; +} + +static int a3xx_pm_suspend(struct msm_gpu *gpu) +{ + int ret; + + if (!a3xx_idle(gpu)) + return -EBUSY; + + ret = a3xx_vbif_halt(gpu); + if (ret) + return ret; + + return msm_gpu_pm_suspend(gpu); +} + static u32 a3xx_get_rptr(struct msm_gpu *gpu, struct msm_ringbuffer *ring) { ring->memptrs->rptr = gpu_read(gpu, REG_AXXX_CP_RB_RPTR); @@ -581,7 +615,7 @@ const struct adreno_gpu_funcs a3xx_gpu_funcs = { .get_param = adreno_get_param, .set_param = adreno_set_param, .hw_init = a3xx_hw_init, - .pm_suspend = msm_gpu_pm_suspend, + .pm_suspend = a3xx_pm_suspend, .pm_resume = msm_gpu_pm_resume, .recover = a3xx_recover, .submit = a3xx_submit, diff --git a/drivers/gpu/drm/msm/registers/adreno/a3xx.xml b/drivers/gpu/drm/msm/registers/adreno/a3xx.xml index 09c9606fc3e1..21b00a17ef3e 100644 --- a/drivers/gpu/drm/msm/registers/adreno/a3xx.xml +++ b/drivers/gpu/drm/msm/registers/adreno/a3xx.xml @@ -1495,6 +1495,8 @@ xsi:schemaLocation="https://gitlab.freedesktop.org/freedreno/ rules-fd.xsd"> + + From d9108bfdb746edacdb05bd27959a4ae63c6c7f3f Mon Sep 17 00:00:00 2001 From: Shivam Rawat Date: Fri, 5 Jun 2026 01:38:17 +0530 Subject: [PATCH 0161/1328] drm/msm/a6xx: Fix stale rpmh votes after suspend There are stale RPMH votes (BCM votes) observed after GMU suspend. This is because the rpmh stop sequences are skipped during gmu suspend. Fix this and also move GMU to reset state to avoid any further activity. Fixes: f248d5d5159a ("drm/msm/a6xx: Fix PDC sleep sequence") Signed-off-by: Shivam Rawat Signed-off-by: Akhil P Oommen Tested-by: Neil Armstrong # on SM8650-HDK Reviewed-by: Dmitry Baryshkov Reviewed-by: Konrad Dybcio Patchwork: https://patchwork.freedesktop.org/patch/730652/ Message-ID: <20260605-assorted-fixes-june-v1-1-2caa04f7287c@oss.qualcomm.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/adreno/a6xx_gmu.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gmu.c b/drivers/gpu/drm/msm/adreno/a6xx_gmu.c index 2e5d7b53a0c3..a2f6918c4f7f 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gmu.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_gmu.c @@ -642,7 +642,7 @@ static void a6xx_rpmh_stop(struct a6xx_gmu *gmu) int ret; u32 val; - if (test_and_clear_bit(GMU_STATUS_FW_START, &gmu->status)) + if (!test_and_clear_bit(GMU_STATUS_FW_START, &gmu->status)) return; if (adreno_is_a840(adreno_gpu)) @@ -1465,6 +1465,9 @@ static void a6xx_gmu_shutdown(struct a6xx_gmu *gmu) /* Stop the interrupts and mask the hardware */ a6xx_gmu_irq_disable(gmu); + /* Halt the gmu cm3 core */ + gmu_write(gmu, REG_A6XX_GMU_CM3_SYSRESET, 1); + /* Tell RPMh to power off the GPU */ a6xx_rpmh_stop(gmu); From b303e1d52811de7d1bcf793560754d4df68d4a1c Mon Sep 17 00:00:00 2001 From: Jie Zhang Date: Fri, 5 Jun 2026 01:38:18 +0530 Subject: [PATCH 0162/1328] drm/msm: Recover HW before retire hung submit During recovery, it is not safe to retire the hung submit before we recover the GPU. Retiring the submit triggers BO free and that can result in GPU pagefaults since the GPU may be actively accessing those BOs. To fix this, retire the submits after gpu recovery is complete in recover_worker(). Fixes: 1a370be9ac51 ("drm/msm: restart queued submits after hang") Signed-off-by: Jie Zhang Signed-off-by: Akhil P Oommen Acked-by: Konrad Dybcio Patchwork: https://patchwork.freedesktop.org/patch/730655/ Message-ID: <20260605-assorted-fixes-june-v1-2-2caa04f7287c@oss.qualcomm.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/msm_gpu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_gpu.c b/drivers/gpu/drm/msm/msm_gpu.c index 18ed00e5f143..9ac7740a87f0 100644 --- a/drivers/gpu/drm/msm/msm_gpu.c +++ b/drivers/gpu/drm/msm/msm_gpu.c @@ -552,11 +552,11 @@ static void recover_worker(struct kthread_work *work) msm_update_fence(ring->fctx, fence); } + gpu->funcs->recover(gpu); + /* retire completed submits, plus the one that hung: */ retire_submits(gpu); - gpu->funcs->recover(gpu); - /* * Replay all remaining submits starting with highest priority * ring From fc7ccbc6174b79ffab5be5dca5b6e253df22f030 Mon Sep 17 00:00:00 2001 From: Jie Zhang Date: Fri, 5 Jun 2026 01:38:19 +0530 Subject: [PATCH 0163/1328] drm/msm/a6xx: Fix A663 GPUCC register list for state capture The GPUCC register list for A663 is incorrect, which can cause out-of-bounds register access during GPU state capture. Update it to use the correct register ranges. Fixes: 5773cce8615c ("drm/msm/a6xx: Add support for A663") Signed-off-by: Jie Zhang Signed-off-by: Akhil P Oommen Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/730656/ Message-ID: <20260605-assorted-fixes-june-v1-3-2caa04f7287c@oss.qualcomm.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c b/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c index 166365359fa6..2a62a22077f9 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c @@ -1244,7 +1244,9 @@ static void a6xx_get_gmu_registers(struct msm_gpu *gpu, _a6xx_get_gmu_registers(gpu, a6xx_state, &a6xx_gmu_reglist[1], &a6xx_state->gmu_registers[1], true); - if (adreno_is_a621(adreno_gpu) || adreno_is_a623(adreno_gpu)) + if (adreno_is_a621(adreno_gpu) || + adreno_is_a623(adreno_gpu) || + adreno_is_a663(adreno_gpu)) _a6xx_get_gmu_registers(gpu, a6xx_state, &a621_gpucc_reg, &a6xx_state->gmu_registers[2], false); else From d052d0358fb89b59718b9c24871d72006d4b89b0 Mon Sep 17 00:00:00 2001 From: Jie Zhang Date: Fri, 5 Jun 2026 01:38:20 +0530 Subject: [PATCH 0164/1328] drm/msm/a6xx: Fix A621 GPUCC register list for state capture A621 uses an incorrect GPUCC register list during state capture. The existing list matches A623/A663. Rename it accordingly and add a dedicated A621 GPUCC register list. Fixes: 11cdb81b3c1b ("drm/msm/a6xx: Fix gpucc register block for A621") Signed-off-by: Jie Zhang Signed-off-by: Akhil P Oommen Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/730659/ Message-ID: <20260605-assorted-fixes-june-v1-4-2caa04f7287c@oss.qualcomm.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c | 7 ++++--- drivers/gpu/drm/msm/adreno/a6xx_gpu_state.h | 12 ++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c b/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c index 2a62a22077f9..3ea8ff8c7404 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c @@ -1244,11 +1244,12 @@ static void a6xx_get_gmu_registers(struct msm_gpu *gpu, _a6xx_get_gmu_registers(gpu, a6xx_state, &a6xx_gmu_reglist[1], &a6xx_state->gmu_registers[1], true); - if (adreno_is_a621(adreno_gpu) || - adreno_is_a623(adreno_gpu) || - adreno_is_a663(adreno_gpu)) + if (adreno_is_a621(adreno_gpu)) _a6xx_get_gmu_registers(gpu, a6xx_state, &a621_gpucc_reg, &a6xx_state->gmu_registers[2], false); + else if (adreno_is_a623(adreno_gpu) || adreno_is_a663(adreno_gpu)) + _a6xx_get_gmu_registers(gpu, a6xx_state, &a623_gpucc_reg, + &a6xx_state->gmu_registers[2], false); else _a6xx_get_gmu_registers(gpu, a6xx_state, &a6xx_gpucc_reg, &a6xx_state->gmu_registers[2], false); diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.h b/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.h index b49d8427b59e..0a13a65f89ac 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.h +++ b/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.h @@ -377,6 +377,17 @@ static const u32 a6xx_gmu_gpucc_registers[] = { }; static const u32 a621_gmu_gpucc_registers[] = { + /* GPU CC */ + 0x24000, 0x2400e, 0x24400, 0x2440e, 0x24800, 0x24805, 0x24c00, 0x24cff, + 0x25800, 0x25804, 0x25c00, 0x25c04, 0x26000, 0x26004, 0x26400, 0x26405, + 0x26414, 0x2641d, 0x2642a, 0x26430, 0x26432, 0x26432, 0x26441, 0x26455, + 0x26466, 0x26468, 0x26478, 0x2647a, 0x26489, 0x2648a, 0x2649c, 0x2649e, + 0x264a0, 0x264a3, 0x264b3, 0x264b5, 0x264c5, 0x264c7, 0x264d6, 0x264d8, + 0x264e8, 0x264e9, 0x264f9, 0x264fc, 0x2650b, 0x2650c, 0x2651c, 0x2651e, + 0x26540, 0x26570, 0x26600, 0x26616, 0x26620, 0x2662d, +}; + +static const u32 a623_gmu_gpucc_registers[] = { /* GPU CC */ 0x24000, 0x2400e, 0x24400, 0x2440e, 0x25800, 0x25804, 0x25c00, 0x25c04, 0x26000, 0x26004, 0x26400, 0x26405, 0x26414, 0x2641d, 0x2642a, 0x26430, @@ -402,6 +413,7 @@ static const struct a6xx_registers a6xx_gmu_reglist[] = { static const struct a6xx_registers a6xx_gpucc_reg = REGS(a6xx_gmu_gpucc_registers, 0, 0); static const struct a6xx_registers a621_gpucc_reg = REGS(a621_gmu_gpucc_registers, 0, 0); +static const struct a6xx_registers a623_gpucc_reg = REGS(a623_gmu_gpucc_registers, 0, 0); static u32 a6xx_get_cp_roq_size(struct msm_gpu *gpu); static u32 a7xx_get_cp_roq_size(struct msm_gpu *gpu); From bc024d325e98b6b2806e00455e030622fb8e1820 Mon Sep 17 00:00:00 2001 From: Jie Zhang Date: Fri, 5 Jun 2026 01:38:21 +0530 Subject: [PATCH 0165/1328] drm/msm/a6xx: Fix IRQ storm during msm_recovery test Once a hang is triggered by the msm_recovery test, the gpu error irq remains asserted and triggers an interrupt storm. In the worst case, this IRQ storm lands on the CPU core where the hangcheck timer is scheduled, blocking it from running. This eventually leads to CPU watchdog timeouts. To fix this, mask the gpu error irqs during msm_recovery test and enable them back during the recovery. Fixes: 5edf2750d998 ("drm/msm: Add debugfs to disable hw err handling") Signed-off-by: Jie Zhang Signed-off-by: Akhil P Oommen Patchwork: https://patchwork.freedesktop.org/patch/730660/ Message-ID: <20260605-assorted-fixes-june-v1-5-2caa04f7287c@oss.qualcomm.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/adreno/a5xx_gpu.c | 5 +++++ drivers/gpu/drm/msm/adreno/a6xx_gpu.c | 5 ++++- drivers/gpu/drm/msm/adreno/a8xx_gpu.c | 5 ++++- drivers/gpu/drm/msm/msm_gpu.c | 2 ++ 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/msm/adreno/a5xx_gpu.c b/drivers/gpu/drm/msm/adreno/a5xx_gpu.c index 2c0bbac43c52..f1df2514c613 100644 --- a/drivers/gpu/drm/msm/adreno/a5xx_gpu.c +++ b/drivers/gpu/drm/msm/adreno/a5xx_gpu.c @@ -1275,6 +1275,11 @@ static irqreturn_t a5xx_irq(struct msm_gpu *gpu) status & ~A5XX_RBBM_INT_0_MASK_RBBM_AHB_ERROR); if (priv->disable_err_irq) { + /* Turn off interrupts to avoid interrupt storm */ + gpu_write(gpu, REG_A5XX_RBBM_INT_0_MASK, + A5XX_RBBM_INT_0_MASK_CP_CACHE_FLUSH_TS | + A5XX_RBBM_INT_0_MASK_CP_SW); + status &= A5XX_RBBM_INT_0_MASK_CP_CACHE_FLUSH_TS | A5XX_RBBM_INT_0_MASK_CP_SW; } diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gpu.c b/drivers/gpu/drm/msm/adreno/a6xx_gpu.c index a44380316aaa..e293b4ca808a 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gpu.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_gpu.c @@ -1911,8 +1911,11 @@ static irqreturn_t a6xx_irq(struct msm_gpu *gpu) gpu_write(gpu, REG_A6XX_RBBM_INT_CLEAR_CMD, status); - if (priv->disable_err_irq) + if (priv->disable_err_irq) { + /* Turn off interrupts to avoid interrupt storm */ + gpu_write(gpu, REG_A6XX_RBBM_INT_0_MASK, A6XX_RBBM_INT_0_MASK_CP_CACHE_FLUSH_TS); status &= A6XX_RBBM_INT_0_MASK_CP_CACHE_FLUSH_TS; + } if (status & A6XX_RBBM_INT_0_MASK_RBBM_HANG_DETECT) a6xx_fault_detect_irq(gpu); diff --git a/drivers/gpu/drm/msm/adreno/a8xx_gpu.c b/drivers/gpu/drm/msm/adreno/a8xx_gpu.c index 9e44fd1ae634..0f6fd35bd587 100644 --- a/drivers/gpu/drm/msm/adreno/a8xx_gpu.c +++ b/drivers/gpu/drm/msm/adreno/a8xx_gpu.c @@ -1211,8 +1211,11 @@ irqreturn_t a8xx_irq(struct msm_gpu *gpu) gpu_write(gpu, REG_A8XX_RBBM_INT_CLEAR_CMD, status); - if (priv->disable_err_irq) + if (priv->disable_err_irq) { + /* Turn off interrupts to avoid interrupt storm */ + gpu_write(gpu, REG_A8XX_RBBM_INT_0_MASK, A6XX_RBBM_INT_0_MASK_CP_CACHE_FLUSH_TS); status &= A6XX_RBBM_INT_0_MASK_CP_CACHE_FLUSH_TS; + } if (status & A6XX_RBBM_INT_0_MASK_RBBM_HANG_DETECT) a8xx_fault_detect_irq(gpu); diff --git a/drivers/gpu/drm/msm/msm_gpu.c b/drivers/gpu/drm/msm/msm_gpu.c index 9ac7740a87f0..48ac51f4119b 100644 --- a/drivers/gpu/drm/msm/msm_gpu.c +++ b/drivers/gpu/drm/msm/msm_gpu.c @@ -552,6 +552,8 @@ static void recover_worker(struct kthread_work *work) msm_update_fence(ring->fctx, fence); } + priv->disable_err_irq = false; + gpu->funcs->recover(gpu); /* retire completed submits, plus the one that hung: */ From 40b793714ad8f393ab3d469f9d00b20ebda46257 Mon Sep 17 00:00:00 2001 From: Jie Zhang Date: Fri, 5 Jun 2026 01:38:22 +0530 Subject: [PATCH 0166/1328] drm/msm: Fix task_struct reference leak in recover_worker get_pid_task() increments the task reference count, but the corresponding put_task_struct() was missing in the else branch, leaking a reference on every GPU hang recovery. Fixes: 25654a1756a4 ("drm/msm: Update global fault counter when faulty process has already ended") Signed-off-by: Jie Zhang Signed-off-by: Akhil P Oommen Reviewed-by: Konrad Dybcio Patchwork: https://patchwork.freedesktop.org/patch/730662/ Message-ID: <20260605-assorted-fixes-june-v1-6-2caa04f7287c@oss.qualcomm.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/msm_gpu.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/msm/msm_gpu.c b/drivers/gpu/drm/msm/msm_gpu.c index 48ac51f4119b..03c057856065 100644 --- a/drivers/gpu/drm/msm/msm_gpu.c +++ b/drivers/gpu/drm/msm/msm_gpu.c @@ -505,6 +505,8 @@ static void recover_worker(struct kthread_work *work) */ if (!vm->managed) msm_gem_vm_unusable(submit->vm); + + put_task_struct(task); } noreclaim_flag = memalloc_noreclaim_save(); From e2332abed2a4d3caa59052095dc16e4ce44791ea Mon Sep 17 00:00:00 2001 From: Ruoyu Wang Date: Thu, 9 Jul 2026 14:23:09 +0800 Subject: [PATCH 0167/1328] drm/msm: Only fini scheduler after successful init msm_ringbuffer_new() destroys a partially initialized ring through msm_ringbuffer_destroy() when an allocation or scheduler setup step fails. If drm_sched_init() fails before it finishes initializing the scheduler, the failure path still calls drm_sched_fini(). That teardown path assumes the scheduler work items, lists, and workqueue state were initialized. Track successful scheduler initialization and call drm_sched_fini() only after drm_sched_init() returned 0. This issue was found by a static analysis checker and confirmed by manual source review. Fixes: 1d8a5ca436ee ("drm/msm: Conversion to drm scheduler") Signed-off-by: Ruoyu Wang Patchwork: https://patchwork.freedesktop.org/patch/738905/ Message-ID: <20260709062309.4168362-1-ruoyuw560@gmail.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/msm_ringbuffer.c | 7 ++++--- drivers/gpu/drm/msm/msm_ringbuffer.h | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_ringbuffer.c b/drivers/gpu/drm/msm/msm_ringbuffer.c index 30ddb5351e98..f060e6362ae4 100644 --- a/drivers/gpu/drm/msm/msm_ringbuffer.c +++ b/drivers/gpu/drm/msm/msm_ringbuffer.c @@ -109,9 +109,9 @@ struct msm_ringbuffer *msm_ringbuffer_new(struct msm_gpu *gpu, int id, ring->memptrs_iova = memptrs_iova; ret = drm_sched_init(&ring->sched, &args); - if (ret) { + if (ret) goto fail; - } + ring->sched_initialized = true; INIT_LIST_HEAD(&ring->submits); spin_lock_init(&ring->submit_lock); @@ -133,7 +133,8 @@ void msm_ringbuffer_destroy(struct msm_ringbuffer *ring) if (IS_ERR_OR_NULL(ring)) return; - drm_sched_fini(&ring->sched); + if (ring->sched_initialized) + drm_sched_fini(&ring->sched); msm_fence_context_free(ring->fctx); diff --git a/drivers/gpu/drm/msm/msm_ringbuffer.h b/drivers/gpu/drm/msm/msm_ringbuffer.h index 28ca8c9f7463..3631ec283c6e 100644 --- a/drivers/gpu/drm/msm/msm_ringbuffer.h +++ b/drivers/gpu/drm/msm/msm_ringbuffer.h @@ -56,6 +56,7 @@ struct msm_ringbuffer { * The job scheduler for this ring. */ struct drm_gpu_scheduler sched; + bool sched_initialized; /* * List of in-flight submits on this ring. Protected by submit_lock. From c74d7d80218eb3587d900087f50bbd4fbd06660d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Monin?= Date: Thu, 25 Sep 2025 17:09:13 +0200 Subject: [PATCH 0168/1328] dt-bindings: soc: mobileye: Add EyeQ7H OLB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Other Logic Blocks (OLB) found in Mobileye SoCs contain hardware sub-functions grouped in a logical device. The EyeQ7H features 14 such OLB. The main differences with the previous generation of SoC are that some blocks have two clock sources instead of one and that the clock source can be the one of the clock output of another OLB instead of the main oscillator. For the blocks with a single parent clock, the name of that clock is "ref", similar to what is done for the OLB of the previous SoC. The blocks with two parent clocks use either "ref" for the main oscillator, "ref_100p0" for a 100MHz reference clock or "ref_106p6" for 106.6MHz reference clock. Some OLB also contain a reset controller with one or more reset domain, like the blocks found in the EyeQ6H. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Benoît Monin --- .../soc/mobileye/mobileye,eyeq7h-olb.yaml | 192 ++++++++++++++++++ .../dt-bindings/clock/mobileye,eyeq7h-clk.h | 119 +++++++++++ 2 files changed, 311 insertions(+) create mode 100644 Documentation/devicetree/bindings/soc/mobileye/mobileye,eyeq7h-olb.yaml create mode 100644 include/dt-bindings/clock/mobileye,eyeq7h-clk.h diff --git a/Documentation/devicetree/bindings/soc/mobileye/mobileye,eyeq7h-olb.yaml b/Documentation/devicetree/bindings/soc/mobileye/mobileye,eyeq7h-olb.yaml new file mode 100644 index 000000000000..2958ca9e330b --- /dev/null +++ b/Documentation/devicetree/bindings/soc/mobileye/mobileye,eyeq7h-olb.yaml @@ -0,0 +1,192 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/soc/mobileye/mobileye,eyeq7h-olb.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Mobileye EyeQ7H SoC system controller + +maintainers: + - Benoît Monin + - Grégory Clement + - Théo Lebrun + - Vladimir Kondratiev + +description: + OLB ("Other Logic Block") is a hardware system controller grouping + smaller blocks. Clocks and resets are generated by those blocks and + used by internal controllers of the SoC. The EyeQ7H SoC hosts 14 + different OLB. + +properties: + compatible: + items: + - enum: + - mobileye,eyeq7h-acc0-olb + - mobileye,eyeq7h-acc1-olb + - mobileye,eyeq7h-ddr0-olb + - mobileye,eyeq7h-ddr1-olb + - mobileye,eyeq7h-east-olb + - mobileye,eyeq7h-mips0-olb + - mobileye,eyeq7h-mips1-olb + - mobileye,eyeq7h-mips2-olb + - mobileye,eyeq7h-periph-east-olb + - mobileye,eyeq7h-periph-west-olb + - mobileye,eyeq7h-south-olb + - mobileye,eyeq7h-west-olb + - mobileye,eyeq7h-xnn0-olb + - mobileye,eyeq7h-xnn1-olb + - const: syscon + + reg: + maxItems: 1 + + '#reset-cells': + description: + First cell is domain and optional if compatible has a single reset domain. + Second cell is reset index inside that domain. + enum: [ 1, 2 ] + + '#clock-cells': + const: 1 + + clocks: + minItems: 1 + maxItems: 2 + + clock-names: + minItems: 1 + maxItems: 2 + +required: + - compatible + - reg + - '#clock-cells' + - clocks + - clock-names + +allOf: + - if: + properties: + compatible: + contains: + enum: + - mobileye,eyeq7h-ddr0-olb + - mobileye,eyeq7h-ddr1-olb + - mobileye,eyeq7h-mips0-olb + - mobileye,eyeq7h-mips1-olb + - mobileye,eyeq7h-mips2-olb + - mobileye,eyeq7h-periph-east-olb + - mobileye,eyeq7h-south-olb + then: + properties: + clocks: + items: + - description: Reference input clock. + clock-names: + items: + - const: ref + + - if: + properties: + compatible: + contains: + enum: + - mobileye,eyeq7h-east-olb + - mobileye,eyeq7h-west-olb + then: + properties: + clocks: + items: + - description: Reference input clock from the main oscillator. + - description: 100MHz reference input clock. + clock-names: + items: + - const: ref + - const: ref_100p0 + + - if: + properties: + compatible: + contains: + enum: + - mobileye,eyeq7h-acc0-olb + - mobileye,eyeq7h-acc1-olb + - mobileye,eyeq7h-periph-west-olb + - mobileye,eyeq7h-xnn0-olb + - mobileye,eyeq7h-xnn1-olb + then: + properties: + clocks: + items: + - description: 100MHz reference input clock. + - description: 106.6MHz reference input clock. + clock-names: + items: + - const: ref_100p0 + - const: ref_106p6 + + - if: + properties: + compatible: + contains: + enum: + - mobileye,eyeq7h-ddr0-olb + - mobileye,eyeq7h-ddr1-olb + - mobileye,eyeq7h-east-olb + - mobileye,eyeq7h-periph-east-olb + - mobileye,eyeq7h-periph-west-olb + - mobileye,eyeq7h-west-olb + then: + properties: + '#reset-cells': + const: 1 + required: + - '#reset-cells' + + - if: + properties: + compatible: + contains: + enum: + - mobileye,eyeq7h-acc0-olb + - mobileye,eyeq7h-acc1-olb + - mobileye,eyeq7h-south-olb + - mobileye,eyeq7h-xnn0-olb + - mobileye,eyeq7h-xnn1-olb + then: + properties: + '#reset-cells': + const: 2 + required: + - '#reset-cells' + + - if: + properties: + compatible: + contains: + enum: + - mobileye,eyeq7h-mips0-olb + - mobileye,eyeq7h-mips1-olb + - mobileye,eyeq7h-mips2-olb + then: + properties: + '#reset-cells': false + +additionalProperties: false + +examples: + - | + soc { + #address-cells = <2>; + #size-cells = <2>; + + system-controller0@45000000 { + compatible = "mobileye,eyeq7h-acc0-olb", "syscon"; + reg = <0x0 0x45000000 0x0 0x1000>; + #reset-cells = <2>; + #clock-cells = <1>; + clocks = <&olb_south 7>, <&olb_east 5>; + clock-names = "ref_100p0", "ref_106p6"; + }; + }; diff --git a/include/dt-bindings/clock/mobileye,eyeq7h-clk.h b/include/dt-bindings/clock/mobileye,eyeq7h-clk.h new file mode 100644 index 000000000000..76e06a0abd02 --- /dev/null +++ b/include/dt-bindings/clock/mobileye,eyeq7h-clk.h @@ -0,0 +1,119 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ +/* + * Copyright (C) 2025 Mobileye Vision Technologies Ltd. + */ + +#ifndef _DT_BINDINGS_CLOCK_MOBILEYE_EYEQ7H_CLK_H +#define _DT_BINDINGS_CLOCK_MOBILEYE_EYEQ7H_CLK_H + +/* ACC0 and ACC1 OLBs PLL and dividers */ +#define EQ7HC_ACC_PLL_VMP 0 +#define EQ7HC_ACC_PLL_MPC 1 +#define EQ7HC_ACC_PLL_PMA 2 +#define EQ7HC_ACC_PLL_NOC 3 +#define EQ7HC_ACC_DIV_PMA 4 +#define EQ7HC_ACC_DIV_NCORE 5 +#define EQ7HC_ACC_DIV_CFG 6 + +/* DDR0 and DDR1 OLBs PLL and dividers */ +#define EQ7HC_DDR_PLL 0 +#define EQ7HC_DDR_DIV_APB 1 +#define EQ7HC_DDR_DIV_PLLREF 2 +#define EQ7HC_DDR_DIV_DFI 3 + +/* east OLB PLL and dividers */ +#define EQ7HC_EAST_PLL_106P6 0 +#define EQ7HC_EAST_DIV_REF_106P6 1 +#define EQ7HC_EAST_PLL_NOC 2 +#define EQ7HC_EAST_PLL_ISP 3 +#define EQ7HC_EAST_PLL_VEU 4 +#define EQ7HC_EAST_DIV_REF_DDR_PHY 5 +#define EQ7HC_EAST_DIV_CORE 6 +#define EQ7HC_EAST_DIV_CORE_MBIST 7 +#define EQ7HC_EAST_DIV_ISRAM_MBIST 8 +#define EQ7HC_EAST_DIV_CFG 9 +#define EQ7HC_EAST_DIV_VEU_CORE 10 +#define EQ7HC_EAST_DIV_VEU_MBIST 11 +#define EQ7HC_EAST_DIV_VEU_OCP 12 +#define EQ7HC_EAST_DIV_LBITS 13 +#define EQ7HC_EAST_DIV_ISP0_CORE 14 + +/* MIPS0, MIPS1 and MIPS2 OLBs PLL and dividers */ +#define EQ7HC_MIPS_PLL_CPU 0 +#define EQ7HC_MIPS_DIV_CM 1 + +/* periph east OLB PLL and dividers */ +#define EQ7HC_PERIPH_EAST_PLL_PER 0 +#define EQ7HC_PERIPH_EAST_DIV_PER 1 + +/* periph west OLB PLL and dividers */ +#define EQ7HC_PERIPH_WEST_PLL_PER 0 +#define EQ7HC_PERIPH_WEST_PLL_I2S 1 +#define EQ7HC_PERIPH_WEST_DIV_PER 2 +#define EQ7HC_PERIPH_WEST_DIV_I2S 3 + +/* south OLB PLL and dividers */ +#define EQ7HC_SOUTH_PLL_100P0 0 +#define EQ7HC_SOUTH_DIV_REF_100P0 1 +#define EQ7HC_SOUTH_PLL_XSPI 2 +#define EQ7HC_SOUTH_PLL_VDIO 3 +#define EQ7HC_SOUTH_PLL_PER 4 +#define EQ7HC_SOUTH_DIV_VDO_DSI_SYS 5 +#define EQ7HC_SOUTH_DIV_PMA_CMN_REF 6 +#define EQ7HC_SOUTH_DIV_REF_UFS 7 +#define EQ7HC_SOUTH_DIV_XSPI_SYS 8 +#define EQ7HC_SOUTH_DIV_XSPI_MBIST 9 +#define EQ7HC_SOUTH_DIV_NOC_S 10 +#define EQ7HC_SOUTH_DIV_PCIE_SYS 11 +#define EQ7HC_SOUTH_DIV_PCIE_SYS_MBIST 12 +#define EQ7HC_SOUTH_DIV_PCIE_GBE_PHY 13 +#define EQ7HC_SOUTH_DIV_UFS_CORE 14 +#define EQ7HC_SOUTH_DIV_UFS_SMS 15 +#define EQ7HC_SOUTH_DIV_UFS_ROM_SMS 16 +#define EQ7HC_SOUTH_DIV_ETH_SYS 17 +#define EQ7HC_SOUTH_DIV_ETH_MBIST 18 +#define EQ7HC_SOUTH_DIV_CFG_S 19 +#define EQ7HC_SOUTH_DIV_TSU 20 +#define EQ7HC_SOUTH_DIV_VDIO 21 +#define EQ7HC_SOUTH_DIV_VDIO_CORE 22 +#define EQ7HC_SOUTH_DIV_VDIO_CORE_MBIST 23 +#define EQ7HC_SOUTH_DIV_VDO_CORE_MBIST 24 +#define EQ7HC_SOUTH_DIV_VDO_P 25 +#define EQ7HC_SOUTH_DIV_VDIO_CFG 26 +#define EQ7HC_SOUTH_DIV_VDIO_TXCLKESC 27 + +/* west OLB PLL and dividers */ +#define EQ7HC_WEST_PLL_106P6 0 +#define EQ7HC_WEST_DIV_REF_106P6 1 +#define EQ7HC_WEST_PLL_NOC 2 +#define EQ7HC_WEST_PLL_GPU 3 +#define EQ7HC_WEST_PLL_SSI 4 +#define EQ7HC_WEST_DIV_GPU 5 +#define EQ7HC_WEST_DIV_GPU_MBIST 6 +#define EQ7HC_WEST_DIV_LBITS 7 +#define EQ7HC_WEST_DIV_MIPS_TIMER 8 +#define EQ7HC_WEST_DIV_SSI_CORE 9 +#define EQ7HC_WEST_DIV_SSI_CORE_MBIST 10 +#define EQ7HC_WEST_DIV_SSI_ROM 11 +#define EQ7HC_WEST_DIV_SSI_ROM_MBIST 12 +#define EQ7HC_WEST_DIV_REF_DDR_PHY 13 +#define EQ7HC_WEST_DIV_CORE 14 +#define EQ7HC_WEST_DIV_CORE_MBIST 15 +#define EQ7HC_WEST_DIV_CFG 16 +#define EQ7HC_WEST_DIV_CAU 17 +#define EQ7HC_WEST_DIV_CAU_MBIST 18 + +/* XNN0 and XNN1 OLBs PLL and dividers */ +#define EQ7HC_XNN_PLL_XNN0 0 +#define EQ7HC_XNN_PLL_XNN1 1 +#define EQ7HC_XNN_PLL_XNN2 2 +#define EQ7HC_XNN_PLL_CLSTR 3 +#define EQ7HC_XNN_DIV_XNN0 4 +#define EQ7HC_XNN_DIV_XNN1 5 +#define EQ7HC_XNN_DIV_XNN2 6 +#define EQ7HC_XNN_DIV_CLSTR 7 +#define EQ7HC_XNN_DIV_I2 8 +#define EQ7HC_XNN_DIV_I2_SMS 9 +#define EQ7HC_XNN_DIV_CFG 10 + +#endif From 379fc3b26b5774898c4061a25e81c0a58dde88e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Monin?= Date: Thu, 21 Aug 2025 15:35:43 +0200 Subject: [PATCH 0169/1328] reset: eyeq: Add EyeQ7H compatibles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for the reset controllers found in the EyeQ7H OLB. For this, three new types of reset domain are added to the driver. The EQR_EYEQ7H_ACRP reset domain is similar to the EQR_EYEQ5_ACRP domain, sharing the same register address calculation but featuring a different register layout. When writing to the register, MBIST bits are set to zero to ensure normal device operation. The EQR_EYEQ7H_CFG reset domain is similar to the EQR_EYEQ5_PCIE domain, with two bits per device instead of one. These two bits, clock enable and nreset, are kept in sync when asserting and deasserting the device reset. The EQR_EYEQ7H_ACC reset domain is similar to the EQR_EYEQ6H_SARCR domain, with a different registers layout and no busy waiting. Alongside these new reset domains, add EQR_NB_DOM_TYPES at the end of the eqr_domain_type enumeration and use it to declare the eqr_timings array. This ensures that we have the expected number of entries when using the timings in eqr_busy_wait_locked(). Add and order the auxiliary_device_id entries in eqr_id_table. Originally-by: Sari Khoury Reviewed-by: Philipp Zabel Acked-by: Philipp Zabel Signed-off-by: Benoît Monin --- drivers/reset/reset-eyeq.c | 271 +++++++++++++++++++++++++++++++++++-- 1 file changed, 262 insertions(+), 9 deletions(-) diff --git a/drivers/reset/reset-eyeq.c b/drivers/reset/reset-eyeq.c index 1a3857983897..886b05ec25d5 100644 --- a/drivers/reset/reset-eyeq.c +++ b/drivers/reset/reset-eyeq.c @@ -1,10 +1,11 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Reset driver for the Mobileye EyeQ5, EyeQ6L and EyeQ6H platforms. + * Reset driver for the Mobileye EyeQ5, EyeQ6L, EyeQ6H and EyeQ7H platforms. * * Controllers live in a shared register region called OLB. EyeQ5 and EyeQ6L * have a single OLB instance for a single reset controller. EyeQ6H has seven - * OLB instances; three host reset controllers. + * OLB instances; three host reset controllers. EyeQ7H has fourteen OLB instances; + * eleven host reset controllers. * * Each reset controller has one or more domain. Domains are of a given type * (see enum eqr_domain_type), with a valid offset mask (up to 32 resets per @@ -73,6 +74,43 @@ * 9. PMA0 10. PMA1 11. MPC0 12. MPC1 * 13. MPC2 14. MPC3 15. PERIPH * + * Known resets in EyeQ7H acc domain 0 (type EQR_EYEQ7H_ACRP) + * 0. VMP0 1. XVMP1 2. VMP2 3. VMP3 + * 4. MPC0 5. MPC1 6. PMA0 7. PMA1 + * + * Known resets in EyeQ7H acc domain 1 (type EQR_EYEQ7H_ACC) + * 0. NCORE0 1. NCORE1 2. NCORE0_M 3. NCORE1_M + * 4. NCORE_NOC 5. VMP_NOC 6. MPC_NOC 7. PMA_NOC + * + * Known resets in EyeQ7H ddr (type EQR_EYEQ7H_CFG) + * 0. APB 1. DMI 2. DFI 3. PHY_SMS + * 4. CTL_SMS + * + * Known resets in EyeQ7H east (type EQR_EYEQ7H_CFG) + * 0. ISP 1. VEU 2. LBIST + * + * Known resets in EyeQ7H periph (type EQR_EYEQ6H_SARCR) + * 0. gpio 1.EXT TIMER 2.UART 3. SPI + * 4. I2C0 5. I2C1 6.I2C2 7. I2S + * + * Known resets in EyeQ7H south domain 0 (type EQR_EYEQ7H_CFG) + * 0. PCI_PHY 1. PCI_CTL 2. S_NOC 3. GBE_PHY + * 4. GBE_CTL + * + * Known resets in EyeQ7H south domain 1 (type EQR_EYEQ7H_CFG) + * 0. XSPI 1. UFS 2. VDIO + * + * Known resets in EyeQ7H west (type EQR_EYEQ7H_CFG) + * 0. GPU 1. CAU 2. LBIST 3. GPU_LBIST + * + * Known resets in EyeQ7H xnn domain 0 (type EQR_EYEQ7H_ACRP) + * 0. XNN0 1. XNN1 2.XNN2 + * + * Known resets in EyeQ7H xnn domain 1 (type EQR_EYEQ7H_ACC) + * 0. XNN0 1. XNN1 2. XNN2 3. XNN3 + * 4. NCORE 5. L2_0 6. L2_1 7. SMS_0 + * 8. SMS_1 + * * Abbreviations: * - PMA: Programmable Macro Array * - MPC: Multi-threaded Processing Clusters @@ -114,6 +152,11 @@ enum eqr_domain_type { EQR_EYEQ5_ACRP, EQR_EYEQ5_PCIE, EQR_EYEQ6H_SARCR, + EQR_EYEQ7H_ACC, + EQR_EYEQ7H_ACRP, + EQR_EYEQ7H_CFG, + + EQR_NB_DOM_TYPES /* number of domain types, keep at the end */ }; /* @@ -138,16 +181,34 @@ enum eqr_domain_type { #define EQR_EYEQ6H_SARCR_RST_STATUS (0x008) #define EQR_EYEQ6H_SARCR_CLK_REQUEST (0x00C) +/* + * Domain type EQR_EYEQ7H_ACC register offsets. + */ +#define EQR_EYEQ7H_ACC_CLK_EN (0x000) +#define EQR_EYEQ7H_ACC_RST_EN (0x004) + +/* + * Domain type EQR_EYEQ7H_ACRP register masks. + * Registers are: base + 4 * offset. + */ +#define EQR_EYEQ7H_ACRP_PD_REQ BIT(0) +#define EQR_EYEQ7H_ACRP_MBIST_CFG GENMASK(3, 1) +#define EQR_EYEQ7H_ACRP_ST_POWER_DOWN BIT(13) +#define EQR_EYEQ7H_ACRP_ST_ACTIVE BIT(14) + struct eqr_busy_wait_timings { unsigned long sleep_us; unsigned long timeout_us; }; -static const struct eqr_busy_wait_timings eqr_timings[] = { +static const struct eqr_busy_wait_timings eqr_timings[EQR_NB_DOM_TYPES] = { [EQR_EYEQ5_SARCR] = {1, 10}, [EQR_EYEQ5_ACRP] = {1, 40 * USEC_PER_MSEC}, /* LBIST implies long timeout. */ /* EQR_EYEQ5_PCIE does no busy waiting. */ [EQR_EYEQ6H_SARCR] = {1, 400}, + /* EQR_EYEQ7H_ACC does no busy waiting. */ + [EQR_EYEQ7H_ACRP] = {1, 40 * USEC_PER_MSEC}, + /* EQR_EYEQ7H_CFG does no busy waiting. */ }; #define EQR_MAX_DOMAIN_COUNT 3 @@ -221,10 +282,6 @@ static int eqr_busy_wait_locked(struct eqr_private *priv, struct device *dev, sleep_us, timeout_us); break; - case EQR_EYEQ5_PCIE: - ret = 0; /* No busy waiting. */ - break; - case EQR_EYEQ6H_SARCR: /* * Wait until both bits change: @@ -241,6 +298,23 @@ static int eqr_busy_wait_locked(struct eqr_private *priv, struct device *dev, &rst_status, &clk_status); break; + case EQR_EYEQ7H_ACRP: + reg = base + 4 * offset; + if (assert) + mask = EQR_EYEQ7H_ACRP_ST_POWER_DOWN; + else + mask = EQR_EYEQ7H_ACRP_ST_ACTIVE; + + ret = readl_poll_timeout(reg, val, !!(val & mask), + sleep_us, timeout_us); + break; + + case EQR_EYEQ5_PCIE: + case EQR_EYEQ7H_ACC: + case EQR_EYEQ7H_CFG: + ret = 0; /* No busy waiting. */ + break; + default: WARN_ON(1); ret = -EINVAL; @@ -285,6 +359,28 @@ static void eqr_assert_locked(struct eqr_private *priv, u32 domain, u32 offset) writel(val, base + EQR_EYEQ6H_SARCR_CLK_REQUEST); break; + case EQR_EYEQ7H_ACC: + /* RST_REQUEST and CLK_REQUEST must be kept in sync. */ + val = readl(base + EQR_EYEQ7H_ACC_RST_EN); + val &= ~BIT(offset); + writel(val, base + EQR_EYEQ7H_ACC_RST_EN); + writel(val, base + EQR_EYEQ7H_ACC_CLK_EN); + break; + + case EQR_EYEQ7H_ACRP: + /* set powerdown and leave MBIST bits at zero */ + reg = base + 4 * offset; + val = readl(reg) & ~EQR_EYEQ7H_ACRP_MBIST_CFG; + writel(val | EQR_EYEQ7H_ACRP_PD_REQ, reg); + break; + + case EQR_EYEQ7H_CFG: + /* clear clock enable and NRESET bits */ + val = readl(base); + val &= ~GENMASK(2 * offset + 1, 2 * offset); + writel(val, base); + break; + default: WARN_ON(1); break; @@ -339,6 +435,28 @@ static void eqr_deassert_locked(struct eqr_private *priv, u32 domain, writel(val, base + EQR_EYEQ6H_SARCR_CLK_REQUEST); break; + case EQR_EYEQ7H_ACC: + /* RST_REQUEST and CLK_REQUEST must be kept in sync. */ + val = readl(base + EQR_EYEQ7H_ACC_RST_EN); + val |= BIT(offset); + writel(val, base + EQR_EYEQ7H_ACC_RST_EN); + writel(val, base + EQR_EYEQ7H_ACC_CLK_EN); + break; + + case EQR_EYEQ7H_ACRP: + /* clear powerdown and leave MBIST bits at zero */ + reg = base + 4 * offset; + val = readl(reg) & ~EQR_EYEQ7H_ACRP_MBIST_CFG; + writel(val & ~EQR_EYEQ7H_ACRP_PD_REQ, reg); + break; + + case EQR_EYEQ7H_CFG: + /* set clock enable and NRESET bits */ + val = readl(base); + val |= GENMASK(2 * offset + 1, 2 * offset); + writel(val, base); + break; + default: WARN_ON(1); break; @@ -385,6 +503,14 @@ static int eqr_status(struct reset_controller_dev *rcdev, unsigned long id) case EQR_EYEQ6H_SARCR: reg = base + EQR_EYEQ6H_SARCR_RST_STATUS; return !(readl(reg) & BIT(offset)); + case EQR_EYEQ7H_ACC: + reg = base + EQR_EYEQ7H_ACC_RST_EN; + return !(readl(reg) & BIT(offset)); + case EQR_EYEQ7H_ACRP: + reg = base + 4 * offset; + return !(readl(reg) & EQR_EYEQ7H_ACRP_ST_ACTIVE); + case EQR_EYEQ7H_CFG: + return !(readl(base) & BIT(2 * offset)); default: return -EINVAL; } @@ -558,6 +684,113 @@ static const struct eqr_match_data eqr_eyeq6h_acc_data = { .domains = eqr_eyeq6h_acc_domains, }; +static const struct eqr_domain_descriptor eqr_eyeq7h_acc_domains[] = { + { + .type = EQR_EYEQ7H_ACRP, + .valid_mask = 0xFF, + .offset = 0x000, + }, + { + .type = EQR_EYEQ7H_ACC, + .valid_mask = 0xFF, + .offset = 0x060, + }, +}; + +static const struct eqr_match_data eqr_eyeq7h_acc_data = { + .domain_count = ARRAY_SIZE(eqr_eyeq7h_acc_domains), + .domains = eqr_eyeq7h_acc_domains, +}; + +static const struct eqr_domain_descriptor eqr_eyeq7h_ddr_domains[] = { + { + .type = EQR_EYEQ7H_CFG, + .valid_mask = 0x1F, + .offset = 0x008, + }, +}; + +static const struct eqr_match_data eqr_eyeq7h_ddr_data = { + .domain_count = ARRAY_SIZE(eqr_eyeq7h_ddr_domains), + .domains = eqr_eyeq7h_ddr_domains, +}; + +static const struct eqr_domain_descriptor eqr_eyeq7h_east_domains[] = { + { + .type = EQR_EYEQ7H_CFG, + .valid_mask = 0x7, + .offset = 0x060, + }, +}; + +static const struct eqr_match_data eqr_eyeq7h_east_data = { + .domain_count = ARRAY_SIZE(eqr_eyeq7h_east_domains), + .domains = eqr_eyeq7h_east_domains, +}; + +/* Periph OLBs each have an instance. */ +static const struct eqr_domain_descriptor eqr_eyeq7h_per_domains[] = { + { + .type = EQR_EYEQ6H_SARCR, + .valid_mask = 0xFF, + .offset = 0x030, + }, +}; + +static const struct eqr_match_data eqr_eyeq7h_per_data = { + .domain_count = ARRAY_SIZE(eqr_eyeq7h_per_domains), + .domains = eqr_eyeq7h_per_domains, +}; + +static const struct eqr_domain_descriptor eqr_eyeq7h_south_domains[] = { + { + .type = EQR_EYEQ7H_CFG, + .valid_mask = 0x1F, + .offset = 0x070, + }, + { + .type = EQR_EYEQ7H_CFG, + .valid_mask = 0x7, + .offset = 0x074, + }, +}; + +static const struct eqr_match_data eqr_eyeq7h_south_data = { + .domain_count = ARRAY_SIZE(eqr_eyeq7h_south_domains), + .domains = eqr_eyeq7h_south_domains, +}; + +static const struct eqr_domain_descriptor eqr_eyeq7h_west_domains[] = { + { + .type = EQR_EYEQ7H_CFG, + .valid_mask = 0xf, + .offset = 0x068, + }, +}; + +static const struct eqr_match_data eqr_eyeq7h_west_data = { + .domain_count = ARRAY_SIZE(eqr_eyeq7h_west_domains), + .domains = eqr_eyeq7h_west_domains, +}; + +static const struct eqr_domain_descriptor eqr_eyeq7h_xnn_domains[] = { + { + .type = EQR_EYEQ7H_ACRP, + .valid_mask = 0x7, + .offset = 0x000, + }, + { + .type = EQR_EYEQ7H_ACC, + .valid_mask = 0x1FF, + .offset = 0x060, + }, +}; + +static const struct eqr_match_data eqr_eyeq7h_xnn_data = { + .domain_count = ARRAY_SIZE(eqr_eyeq7h_xnn_domains), + .domains = eqr_eyeq7h_xnn_domains, +}; + /* * Table describes OLB system-controller compatibles. * It does not get used to match against devicetree node. @@ -569,15 +802,35 @@ static const struct of_device_id eqr_match_table[] = { { .compatible = "mobileye,eyeq6h-west-olb", .data = &eqr_eyeq6h_we_data }, { .compatible = "mobileye,eyeq6h-east-olb", .data = &eqr_eyeq6h_we_data }, { .compatible = "mobileye,eyeq6h-acc-olb", .data = &eqr_eyeq6h_acc_data }, + { .compatible = "mobileye,eyeq7h-acc0-olb", .data = &eqr_eyeq7h_acc_data }, + { .compatible = "mobileye,eyeq7h-acc1-olb", .data = &eqr_eyeq7h_acc_data }, + { .compatible = "mobileye,eyeq7h-ddr0-olb", .data = &eqr_eyeq7h_ddr_data }, + { .compatible = "mobileye,eyeq7h-ddr1-olb", .data = &eqr_eyeq7h_ddr_data }, + { .compatible = "mobileye,eyeq7h-east-olb", .data = &eqr_eyeq7h_east_data }, + { .compatible = "mobileye,eyeq7h-periph-east-olb", .data = &eqr_eyeq7h_per_data }, + { .compatible = "mobileye,eyeq7h-periph-west-olb", .data = &eqr_eyeq7h_per_data }, + { .compatible = "mobileye,eyeq7h-south-olb", .data = &eqr_eyeq7h_south_data }, + { .compatible = "mobileye,eyeq7h-west-olb", .data = &eqr_eyeq7h_west_data }, + { .compatible = "mobileye,eyeq7h-xnn0-olb", .data = &eqr_eyeq7h_xnn_data }, + { .compatible = "mobileye,eyeq7h-xnn1-olb", .data = &eqr_eyeq7h_xnn_data }, {} }; MODULE_DEVICE_TABLE(of, eqr_match_table); static const struct auxiliary_device_id eqr_id_table[] = { { .name = "clk_eyeq.reset" }, - { .name = "clk_eyeq.reset_west" }, - { .name = "clk_eyeq.reset_east" }, { .name = "clk_eyeq.reset_acc" }, + { .name = "clk_eyeq.reset_acc0" }, + { .name = "clk_eyeq.reset_acc1" }, + { .name = "clk_eyeq.reset_ddr0" }, + { .name = "clk_eyeq.reset_ddr1" }, + { .name = "clk_eyeq.reset_east" }, + { .name = "clk_eyeq.reset_periph_east" }, + { .name = "clk_eyeq.reset_periph_west" }, + { .name = "clk_eyeq.reset_south" }, + { .name = "clk_eyeq.reset_west" }, + { .name = "clk_eyeq.reset_xnn0" }, + { .name = "clk_eyeq.reset_xnn1" }, {} }; MODULE_DEVICE_TABLE(auxiliary, eqr_id_table); From 46dda4d9750e10bbe9fae8a20ac3145179453e77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Monin?= Date: Mon, 20 Oct 2025 09:28:00 +0200 Subject: [PATCH 0170/1328] clk: fixed-factor: Rework initialization with parent clocks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the same sequence as clk-divider, clk-gate and other to set the parent_names, parent_hws and parent_data in the init struct when registering a fixed-factor clock. The number of parent clocks is now only set to one if a parent clock is provided. Previously the number of parent clocks was always one, forcing callers of __clk_hw_register_fixed_factor() to provide a dummy parent_data struct with an invalid clock index in case they were not provided with a non-NULL parent_name or parent_hw. Drop this dummy parent_data as is not necessary anymore. This change only has a small impact on mis-configured fixed-factor. Now a call to clk_hw_register_fixed_factor() with a NULL parent will register a fixed-factor with zero parent while previously it was registered with one invalid parent. In both cases the rate of the fixed-factor is 0Hz but it is no longer shown as orphaned. This has no impact on properly configured fixed-factors clocks which have a valid parent set. In clk_factor_determine_rate(), make sure the parent clock is valid before accessing it because the mis-configured fixed-factor now have a NULL parent. Reviewed-by: Brian Masney Signed-off-by: Benoît Monin --- drivers/clk/clk-fixed-factor.c | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/drivers/clk/clk-fixed-factor.c b/drivers/clk/clk-fixed-factor.c index 850e8b95f352..60e32482d347 100644 --- a/drivers/clk/clk-fixed-factor.c +++ b/drivers/clk/clk-fixed-factor.c @@ -36,10 +36,14 @@ static int clk_factor_determine_rate(struct clk_hw *hw, struct clk_fixed_factor *fix = to_clk_fixed_factor(hw); if (clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT) { + struct clk_hw *parent_hw = clk_hw_get_parent(hw); unsigned long best_parent; + if (!parent_hw) + return -EINVAL; + best_parent = (req->rate / fix->mult) * fix->div; - req->best_parent_rate = clk_hw_round_rate(clk_hw_get_parent(hw), best_parent); + req->best_parent_rate = clk_hw_round_rate(parent_hw, best_parent); } req->rate = (req->best_parent_rate / fix->div) * fix->mult; @@ -124,13 +128,13 @@ __clk_hw_register_fixed_factor(struct device *dev, struct device_node *np, init.name = name; init.ops = &clk_fixed_factor_ops; init.flags = flags; - if (parent_name) - init.parent_names = &parent_name; - else if (parent_hw) - init.parent_hws = &parent_hw; + init.parent_names = parent_name ? &parent_name : NULL; + init.parent_hws = parent_hw ? &parent_hw : NULL; + init.parent_data = pdata; + if (parent_name || parent_hw || pdata) + init.num_parents = 1; else - init.parent_data = pdata; - init.num_parents = 1; + init.num_parents = 0; hw = &fix->hw; if (dev) @@ -190,10 +194,8 @@ struct clk_hw *devm_clk_hw_register_fixed_factor_parent_hw(struct device *dev, const char *name, const struct clk_hw *parent_hw, unsigned long flags, unsigned int mult, unsigned int div) { - const struct clk_parent_data pdata = { .index = -1 }; - return __clk_hw_register_fixed_factor(dev, NULL, name, NULL, parent_hw, - &pdata, flags, mult, div, 0, 0, true); + NULL, flags, mult, div, 0, 0, true); } EXPORT_SYMBOL_GPL(devm_clk_hw_register_fixed_factor_parent_hw); @@ -201,10 +203,8 @@ struct clk_hw *clk_hw_register_fixed_factor_parent_hw(struct device *dev, const char *name, const struct clk_hw *parent_hw, unsigned long flags, unsigned int mult, unsigned int div) { - const struct clk_parent_data pdata = { .index = -1 }; - return __clk_hw_register_fixed_factor(dev, NULL, name, NULL, parent_hw, - &pdata, flags, mult, div, 0, 0, false); + NULL, flags, mult, div, 0, 0, false); } EXPORT_SYMBOL_GPL(clk_hw_register_fixed_factor_parent_hw); @@ -212,10 +212,8 @@ struct clk_hw *clk_hw_register_fixed_factor(struct device *dev, const char *name, const char *parent_name, unsigned long flags, unsigned int mult, unsigned int div) { - const struct clk_parent_data pdata = { .index = -1 }; - return __clk_hw_register_fixed_factor(dev, NULL, name, parent_name, NULL, - &pdata, flags, mult, div, 0, 0, false); + NULL, flags, mult, div, 0, 0, false); } EXPORT_SYMBOL_GPL(clk_hw_register_fixed_factor); @@ -296,10 +294,8 @@ struct clk_hw *devm_clk_hw_register_fixed_factor(struct device *dev, const char *name, const char *parent_name, unsigned long flags, unsigned int mult, unsigned int div) { - const struct clk_parent_data pdata = { .index = -1 }; - return __clk_hw_register_fixed_factor(dev, NULL, name, parent_name, NULL, - &pdata, flags, mult, div, 0, 0, true); + NULL, flags, mult, div, 0, 0, true); } EXPORT_SYMBOL_GPL(devm_clk_hw_register_fixed_factor); From 257a34a028aef463357441504a882f9be7f7e0e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Monin?= Date: Mon, 20 Oct 2025 15:28:56 +0200 Subject: [PATCH 0171/1328] clk: fixed-factor: Export __clk_hw_register_fixed_factor() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make the base registration function for fixed-factor clocks public and re-implement the various registration functions that are a direct call to __clk_hw_register_fixed_factor() as macros. This is similar to how the registration functions of divider, mux and other clocks are implemented. Add a new macro clk_hw_register_fixed_factor_pdata() to register a fixed-factor clock with its parent clock passed as a struct clk_parent_data. Reviewed-by: Brian Masney Signed-off-by: Benoît Monin --- drivers/clk/clk-fixed-factor.c | 52 ++----------------------------- include/linux/clk-provider.h | 56 ++++++++++++++++++++++++++-------- 2 files changed, 46 insertions(+), 62 deletions(-) diff --git a/drivers/clk/clk-fixed-factor.c b/drivers/clk/clk-fixed-factor.c index 60e32482d347..ae473e0af903 100644 --- a/drivers/clk/clk-fixed-factor.c +++ b/drivers/clk/clk-fixed-factor.c @@ -94,7 +94,7 @@ static void devm_clk_hw_register_fixed_factor_release(struct device *dev, void * clk_hw_unregister(&fix->hw); } -static struct clk_hw * +struct clk_hw * __clk_hw_register_fixed_factor(struct device *dev, struct device_node *np, const char *name, const char *parent_name, const struct clk_hw *parent_hw, const struct clk_parent_data *pdata, @@ -152,6 +152,7 @@ __clk_hw_register_fixed_factor(struct device *dev, struct device_node *np, return hw; } +EXPORT_SYMBOL_GPL(__clk_hw_register_fixed_factor); /** * devm_clk_hw_register_fixed_factor_index - Register a fixed factor clock with @@ -177,46 +178,6 @@ struct clk_hw *devm_clk_hw_register_fixed_factor_index(struct device *dev, } EXPORT_SYMBOL_GPL(devm_clk_hw_register_fixed_factor_index); -/** - * devm_clk_hw_register_fixed_factor_parent_hw - Register a fixed factor clock with - * pointer to parent clock - * @dev: device that is registering this clock - * @name: name of this clock - * @parent_hw: pointer to parent clk - * @flags: fixed factor flags - * @mult: multiplier - * @div: divider - * - * Return: Pointer to fixed factor clk_hw structure that was registered or - * an error pointer. - */ -struct clk_hw *devm_clk_hw_register_fixed_factor_parent_hw(struct device *dev, - const char *name, const struct clk_hw *parent_hw, - unsigned long flags, unsigned int mult, unsigned int div) -{ - return __clk_hw_register_fixed_factor(dev, NULL, name, NULL, parent_hw, - NULL, flags, mult, div, 0, 0, true); -} -EXPORT_SYMBOL_GPL(devm_clk_hw_register_fixed_factor_parent_hw); - -struct clk_hw *clk_hw_register_fixed_factor_parent_hw(struct device *dev, - const char *name, const struct clk_hw *parent_hw, - unsigned long flags, unsigned int mult, unsigned int div) -{ - return __clk_hw_register_fixed_factor(dev, NULL, name, NULL, parent_hw, - NULL, flags, mult, div, 0, 0, false); -} -EXPORT_SYMBOL_GPL(clk_hw_register_fixed_factor_parent_hw); - -struct clk_hw *clk_hw_register_fixed_factor(struct device *dev, - const char *name, const char *parent_name, unsigned long flags, - unsigned int mult, unsigned int div) -{ - return __clk_hw_register_fixed_factor(dev, NULL, name, parent_name, NULL, - NULL, flags, mult, div, 0, 0, false); -} -EXPORT_SYMBOL_GPL(clk_hw_register_fixed_factor); - struct clk_hw *clk_hw_register_fixed_factor_fwname(struct device *dev, struct device_node *np, const char *name, const char *fw_name, unsigned long flags, unsigned int mult, unsigned int div) @@ -290,15 +251,6 @@ void clk_hw_unregister_fixed_factor(struct clk_hw *hw) } EXPORT_SYMBOL_GPL(clk_hw_unregister_fixed_factor); -struct clk_hw *devm_clk_hw_register_fixed_factor(struct device *dev, - const char *name, const char *parent_name, unsigned long flags, - unsigned int mult, unsigned int div) -{ - return __clk_hw_register_fixed_factor(dev, NULL, name, parent_name, NULL, - NULL, flags, mult, div, 0, 0, true); -} -EXPORT_SYMBOL_GPL(devm_clk_hw_register_fixed_factor); - struct clk_hw *devm_clk_hw_register_fixed_factor_fwname(struct device *dev, struct device_node *np, const char *name, const char *fw_name, unsigned long flags, unsigned int mult, unsigned int div) diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h index b01a38fef8cf..aba71f5dcc21 100644 --- a/include/linux/clk-provider.h +++ b/include/linux/clk-provider.h @@ -1152,13 +1152,16 @@ struct clk_fixed_factor { #define to_clk_fixed_factor(_hw) container_of(_hw, struct clk_fixed_factor, hw) extern const struct clk_ops clk_fixed_factor_ops; +struct clk_hw * +__clk_hw_register_fixed_factor(struct device *dev, struct device_node *np, + const char *name, const char *parent_name, + const struct clk_hw *parent_hw, const struct clk_parent_data *pdata, + unsigned long flags, unsigned int mult, unsigned int div, + unsigned long acc, unsigned int fixflags, bool devm); struct clk *clk_register_fixed_factor(struct device *dev, const char *name, const char *parent_name, unsigned long flags, unsigned int mult, unsigned int div); void clk_unregister_fixed_factor(struct clk *clk); -struct clk_hw *clk_hw_register_fixed_factor(struct device *dev, - const char *name, const char *parent_name, unsigned long flags, - unsigned int mult, unsigned int div); struct clk_hw *clk_hw_register_fixed_factor_fwname(struct device *dev, struct device_node *np, const char *name, const char *fw_name, unsigned long flags, unsigned int mult, unsigned int div); @@ -1170,9 +1173,6 @@ struct clk_hw *clk_hw_register_fixed_factor_index(struct device *dev, const char *name, unsigned int index, unsigned long flags, unsigned int mult, unsigned int div); void clk_hw_unregister_fixed_factor(struct clk_hw *hw); -struct clk_hw *devm_clk_hw_register_fixed_factor(struct device *dev, - const char *name, const char *parent_name, unsigned long flags, - unsigned int mult, unsigned int div); struct clk_hw *devm_clk_hw_register_fixed_factor_fwname(struct device *dev, struct device_node *np, const char *name, const char *fw_name, unsigned long flags, unsigned int mult, unsigned int div); @@ -1184,13 +1184,45 @@ struct clk_hw *devm_clk_hw_register_fixed_factor_index(struct device *dev, const char *name, unsigned int index, unsigned long flags, unsigned int mult, unsigned int div); -struct clk_hw *devm_clk_hw_register_fixed_factor_parent_hw(struct device *dev, - const char *name, const struct clk_hw *parent_hw, - unsigned long flags, unsigned int mult, unsigned int div); +#define clk_hw_register_fixed_factor(dev, name, parent_name, \ + flags, mult, div) \ + __clk_hw_register_fixed_factor((dev), NULL, (name), (parent_name), \ + NULL, NULL, (flags), (mult), (div), \ + 0, 0, false) +#define clk_hw_register_fixed_factor_pdata(dev, np, name, pdata, \ + flags, mult, div, acc, fixflags) \ + __clk_hw_register_fixed_factor((dev), (np), (name), NULL, NULL, \ + (pdata), (flags), (mult), (div), \ + (acc), (fixflags), false) +#define devm_clk_hw_register_fixed_factor(dev, name, parent_name, flags, \ + mult, div) \ + __clk_hw_register_fixed_factor((dev), NULL, (name), (parent_name), \ + NULL, NULL, (flags), (mult), (div), 0, \ + 0, true) +/** + * devm_clk_hw_register_fixed_factor_parent_hw - Register a fixed factor clock with + * pointer to parent clock + * @dev: device that is registering this clock + * @name: name of this clock + * @parent_hw: pointer to parent clk + * @flags: fixed factor flags + * @mult: multiplier + * @div: divider + * + * Return: Pointer to fixed factor clk_hw structure that was registered or + * an error pointer. + */ +#define devm_clk_hw_register_fixed_factor_parent_hw(dev, name, parent_hw, \ + flags, mult, div) \ + __clk_hw_register_fixed_factor((dev), NULL, (name), NULL, \ + (parent_hw), NULL, (flags), (mult), \ + (div), 0, 0, true) -struct clk_hw *clk_hw_register_fixed_factor_parent_hw(struct device *dev, - const char *name, const struct clk_hw *parent_hw, - unsigned long flags, unsigned int mult, unsigned int div); +#define clk_hw_register_fixed_factor_parent_hw(dev, name, parent_hw, flags, \ + mult, div) \ + __clk_hw_register_fixed_factor((dev), NULL, (name), NULL, \ + (parent_hw), NULL, (flags), (mult), \ + (div), 0, 0, false) /** * struct clk_fractional_divider - adjustable fractional divider clock * From 111f35a673079c4fc0c930716836652bf579e276 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Monin?= Date: Wed, 3 Sep 2025 14:47:20 +0200 Subject: [PATCH 0172/1328] clk: eyeq: Prefix the PLL registers with the PLL type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rename the PLL registers to make room for other PLL types that are present in the eyeQ7H. Move the access to the PLL register inside the function parsing it as both call sites were doing the same thing. Signed-off-by: Benoît Monin --- drivers/clk/clk-eyeq.c | 76 +++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 41 deletions(-) diff --git a/drivers/clk/clk-eyeq.c b/drivers/clk/clk-eyeq.c index d9303c2c7aa5..e4e690a12a23 100644 --- a/drivers/clk/clk-eyeq.c +++ b/drivers/clk/clk-eyeq.c @@ -48,28 +48,28 @@ #include /* In frac mode, it enables fractional noise canceling DAC. Else, no function. */ -#define PCSR0_DAC_EN BIT(0) +#define FRACG_PCSR0_DAC_EN BIT(0) /* Fractional or integer mode */ -#define PCSR0_DSM_EN BIT(1) -#define PCSR0_PLL_EN BIT(2) +#define FRACG_PCSR0_DSM_EN BIT(1) +#define FRACG_PCSR0_PLL_EN BIT(2) /* All clocks output held at 0 */ -#define PCSR0_FOUTPOSTDIV_EN BIT(3) -#define PCSR0_POST_DIV1 GENMASK(6, 4) -#define PCSR0_POST_DIV2 GENMASK(9, 7) -#define PCSR0_REF_DIV GENMASK(15, 10) -#define PCSR0_INTIN GENMASK(27, 16) -#define PCSR0_BYPASS BIT(28) +#define FRACG_PCSR0_FOUTPOSTDIV_EN BIT(3) +#define FRACG_PCSR0_POST_DIV1 GENMASK(6, 4) +#define FRACG_PCSR0_POST_DIV2 GENMASK(9, 7) +#define FRACG_PCSR0_REF_DIV GENMASK(15, 10) +#define FRACG_PCSR0_INTIN GENMASK(27, 16) +#define FRACG_PCSR0_BYPASS BIT(28) /* Bits 30..29 are reserved */ -#define PCSR0_PLL_LOCKED BIT(31) +#define FRACG_PCSR0_PLL_LOCKED BIT(31) -#define PCSR1_RESET BIT(0) -#define PCSR1_SSGC_DIV GENMASK(4, 1) +#define FRACG_PCSR1_RESET BIT(0) +#define FRACG_PCSR1_SSGC_DIV GENMASK(4, 1) /* Spread amplitude (% = 0.1 * SPREAD[4:0]) */ -#define PCSR1_SPREAD GENMASK(9, 5) -#define PCSR1_DIS_SSCG BIT(10) +#define FRACG_PCSR1_SPREAD GENMASK(9, 5) +#define FRACG_PCSR1_DIS_SSCG BIT(10) /* Down-spread or center-spread */ -#define PCSR1_DOWN_SPREAD BIT(11) -#define PCSR1_FRAC_IN GENMASK(31, 12) +#define FRACG_PCSR1_DOWN_SPREAD BIT(11) +#define FRACG_PCSR1_FRAC_IN GENMASK(31, 12) struct eqc_pll { unsigned int index; @@ -162,34 +162,40 @@ static void eqc_pll_downshift_factors(unsigned long *mult, unsigned long *div) *div >>= shift; } -static int eqc_pll_parse_registers(u32 r0, u32 r1, unsigned long *mult, - unsigned long *div, unsigned long *acc) +static int eqc_pll_parse_fracg(void __iomem *base, unsigned long *mult, + unsigned long *div, unsigned long *acc) { unsigned long spread; + u32 r0, r1; + u64 val; - if (r0 & PCSR0_BYPASS) { + val = readq(base); + r0 = val; + r1 = val >> 32; + + if (r0 & FRACG_PCSR0_BYPASS) { *mult = 1; *div = 1; *acc = 0; return 0; } - if (!(r0 & PCSR0_PLL_LOCKED)) + if (!(r0 & FRACG_PCSR0_PLL_LOCKED)) return -EINVAL; - *mult = FIELD_GET(PCSR0_INTIN, r0); - *div = FIELD_GET(PCSR0_REF_DIV, r0); + *mult = FIELD_GET(FRACG_PCSR0_INTIN, r0); + *div = FIELD_GET(FRACG_PCSR0_REF_DIV, r0); /* Fractional mode, in 2^20 (0x100000) parts. */ - if (r0 & PCSR0_DSM_EN) { + if (r0 & FRACG_PCSR0_DSM_EN) { *div *= (1ULL << 20); - *mult = *mult * (1ULL << 20) + FIELD_GET(PCSR1_FRAC_IN, r1); + *mult = *mult * (1ULL << 20) + FIELD_GET(FRACG_PCSR1_FRAC_IN, r1); } if (!*mult || !*div) return -EINVAL; - if (r1 & (PCSR1_RESET | PCSR1_DIS_SSCG)) { + if (r1 & (FRACG_PCSR1_RESET | FRACG_PCSR1_DIS_SSCG)) { *acc = 0; return 0; } @@ -204,10 +210,10 @@ static int eqc_pll_parse_registers(u32 r0, u32 r1, unsigned long *mult, * * Care is taken to avoid overflowing or losing precision. */ - spread = FIELD_GET(PCSR1_SPREAD, r1); + spread = FIELD_GET(FRACG_PCSR1_SPREAD, r1); *acc = DIV_ROUND_CLOSEST(spread * 1000000000, 1024 * 2); - if (r1 & PCSR1_DOWN_SPREAD) { + if (r1 & FRACG_PCSR1_DOWN_SPREAD) { /* * Downspreading: the central frequency is half a * spread lower. @@ -232,18 +238,12 @@ static void eqc_probe_init_plls(struct device *dev, const struct eqc_match_data const struct eqc_pll *pll; struct clk_hw *hw; unsigned int i; - u32 r0, r1; - u64 val; int ret; for (i = 0; i < data->pll_count; i++) { pll = &data->plls[i]; - val = readq(base + pll->reg64); - r0 = val; - r1 = val >> 32; - - ret = eqc_pll_parse_registers(r0, r1, &mult, &div, &acc); + ret = eqc_pll_parse_fracg(base + pll->reg64, &mult, &div, &acc); if (ret) { dev_warn(dev, "failed parsing state of %s\n", pll->name); cells->hws[pll->index] = ERR_PTR(ret); @@ -799,14 +799,8 @@ static void __init eqc_early_init(struct device_node *np, const struct eqc_pll *pll = &early_data->early_plls[i]; unsigned long mult, div, acc; struct clk_hw *hw; - u32 r0, r1; - u64 val; - val = readq(base + pll->reg64); - r0 = val; - r1 = val >> 32; - - ret = eqc_pll_parse_registers(r0, r1, &mult, &div, &acc); + ret = eqc_pll_parse_fracg(base + pll->reg64, &mult, &div, &acc); if (ret) { pr_err("failed parsing state of %s\n", pll->name); goto err; From a922374afc3693542f21661cf8b31f5a16c24879 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Monin?= Date: Wed, 10 Dec 2025 17:01:25 +0100 Subject: [PATCH 0173/1328] clk: eyeq: Introduce a generic clock type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, the clocks contained in the OLB are represented as three separate structures: PLL, dividers and fixed factors. These clock objects are stored in three separate arrays in the match data and registered in a fixed order: first the PLL, then the dividers, and finally the fixed factors. While this is sufficient for the clocks found in the OLB of the EyeQ5 and EyeQ6, it does not allow declaring the more complex clock interdependencies for those found in the OLB of the EyeQ7H. We add a new type of clock represented by the struct eqc_clock that covers all types of clocks found in OLB. It contains the clock index and its name, alongside the parent clock index and name. The index refers to the position in the array of clk_hw in the struct clk_hw_onecell_data that is filled when registering the clocks. The parent name is optional and can refer to the parent clock either via the device tree or via its globally unique name. Two special index values are used to select which type of lookup is done. The function eqc_fill_parent_data() fill a clk_parent_data structure based on the parent index and name values. The struct eqc_clock also contains two function pointers: .probe() and .unregister(). The probe() function parses the eqc_clock structure, registers a new clock as a clk_hw and adds it to the clk_hw_onecell_data structure. It can be called during probe and early init. The unregister() function unregisters the clk_hw. This patch adds the probe functions for the PLLs, the dividers and the fixed factors found in the EyeQ OLB. Finally, a union is also part of the eqc_clock structure to store the data specific to each type of clock. To help in declaring struct eqc_clock, three macros are added. They set the correct function pointers for .probe() and .unregister() based on the type of clock being declared. An array of eqc_clock is added to the match data and early match data. They are parsed during probe and early initialization respectively. There is no user yet of the eqc_clock structure. Signed-off-by: Benoît Monin --- drivers/clk/clk-eyeq.c | 215 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 212 insertions(+), 3 deletions(-) diff --git a/drivers/clk/clk-eyeq.c b/drivers/clk/clk-eyeq.c index e4e690a12a23..7b8de08388d3 100644 --- a/drivers/clk/clk-eyeq.c +++ b/drivers/clk/clk-eyeq.c @@ -71,6 +71,13 @@ #define FRACG_PCSR1_DOWN_SPREAD BIT(11) #define FRACG_PCSR1_FRAC_IN GENMASK(31, 12) +/* + * Special index values to lookup a parent clock by its name + * from the device tree or by its globally unique name. + */ +#define PARENT_BY_FWNAME (-1) +#define PARENT_BY_NAME (-2) + struct eqc_pll { unsigned int index; const char *name; @@ -98,6 +105,33 @@ struct eqc_fixed_factor { unsigned int parent; }; +struct eqc_clock { + int index; + int parent_idx; + const char *name; + const char *parent_name; + int (*probe)(struct device *dev, struct device_node *np, + const struct eqc_clock *clk, void __iomem *base, + struct clk_hw_onecell_data *cells); + void (*unregister)(struct clk_hw *hw); + union { + struct { + unsigned int reg; + u8 shift; + u8 width; + unsigned long flags; + const struct clk_div_table *table; + } div; + struct { + unsigned int mult; + unsigned int div; + } ff; + struct { + unsigned int reg; + } pll; + }; +}; + struct eqc_match_data { unsigned int pll_count; const struct eqc_pll *plls; @@ -108,6 +142,9 @@ struct eqc_match_data { unsigned int fixed_factor_count; const struct eqc_fixed_factor *fixed_factors; + unsigned int clk_count; + const struct eqc_clock *clks; + const char *reset_auxdev_name; const char *pinctrl_auxdev_name; const char *eth_phy_auxdev_name; @@ -122,6 +159,9 @@ struct eqc_early_match_data { unsigned int early_fixed_factor_count; const struct eqc_fixed_factor *early_fixed_factors; + unsigned int early_clk_count; + const struct eqc_clock *early_clks; + /* * We want our of_xlate callback to EPROBE_DEFER instead of dev_err() * and EINVAL. For that, we must know the total clock count. @@ -336,6 +376,102 @@ static void eqc_auxdev_create_optional(struct device *dev, void __iomem *base, } } +static int eqc_fill_parent_data(const struct eqc_clock *clk, + struct clk_hw_onecell_data *cells, + struct clk_parent_data *parent_data) +{ + int pidx = clk->parent_idx; + + memset(parent_data, 0, sizeof(struct clk_parent_data)); + + if (pidx == PARENT_BY_FWNAME) { + /* lookup the parent clock by its fw_name */ + parent_data->index = -1; + parent_data->fw_name = clk->parent_name; + } else if (pidx == PARENT_BY_NAME) { + /* lookup the parent clock by its global name */ + parent_data->index = -1; + parent_data->name = clk->parent_name; + } else if (pidx >= 0 && pidx < cells->num && !IS_ERR(cells->hws[pidx])) { + /* get the parent hw directly */ + parent_data->hw = cells->hws[pidx]; + } else { + /* no parent lookup by index: explicitly fail */ + return -EINVAL; + } + + return 0; +} + +static int eqc_probe_divider(struct device *dev, struct device_node *np, + const struct eqc_clock *clk, void __iomem *base, + struct clk_hw_onecell_data *cells) +{ + struct clk_parent_data parent_data; + struct clk_hw *hw; + int ret; + + ret = eqc_fill_parent_data(clk, cells, &parent_data); + if (ret) + return ret; + + hw = clk_hw_register_divider_table_parent_data(dev, clk->name, + &parent_data, 0, base + clk->div.reg, clk->div.shift, + clk->div.width, clk->div.flags, clk->div.table, NULL); + if (IS_ERR(hw)) + return PTR_ERR(hw); + + cells->hws[clk->index] = hw; + return 0; +} + +static int eqc_probe_fixed_factor(struct device *dev, struct device_node *np, + const struct eqc_clock *clk, void __iomem *base, + struct clk_hw_onecell_data *cells) +{ + struct clk_parent_data parent_data; + struct clk_hw *hw; + int ret; + + ret = eqc_fill_parent_data(clk, cells, &parent_data); + if (ret) + return ret; + + hw = clk_hw_register_fixed_factor_pdata(dev, np, clk->name, &parent_data, 0, + clk->ff.mult, clk->ff.div, 0, 0); + if (IS_ERR(hw)) + return PTR_ERR(hw); + + cells->hws[clk->index] = hw; + return 0; +} + +static int eqc_probe_pll_fracg(struct device *dev, struct device_node *np, + const struct eqc_clock *clk, void __iomem *base, + struct clk_hw_onecell_data *cells) +{ + struct clk_parent_data parent_data; + unsigned long mult, div, acc; + struct clk_hw *hw; + int ret; + + ret = eqc_pll_parse_fracg(base + clk->pll.reg, &mult, &div, &acc); + if (ret) + return ret; + + ret = eqc_fill_parent_data(clk, cells, &parent_data); + if (ret) + return ret; + + hw = clk_hw_register_fixed_factor_pdata(dev, np, clk->name, &parent_data, 0, mult, + div, acc, CLK_FIXED_FACTOR_FIXED_ACCURACY); + if (IS_ERR(hw)) + return PTR_ERR(hw); + + cells->hws[clk->index] = hw; + return 0; +} + static int eqc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -345,6 +481,7 @@ static int eqc_probe(struct platform_device *pdev) unsigned int i, clk_count; struct resource *res; void __iomem *base; + int ret; data = device_get_match_data(dev); if (!data) @@ -363,11 +500,12 @@ static int eqc_probe(struct platform_device *pdev) eqc_auxdev_create_optional(dev, base, data->pinctrl_auxdev_name); eqc_auxdev_create_optional(dev, base, data->eth_phy_auxdev_name); - if (data->pll_count + data->div_count + data->fixed_factor_count == 0) + if (data->pll_count + data->div_count + data->fixed_factor_count + data->clk_count == 0) return 0; /* Zero clocks, we are done. */ clk_count = data->pll_count + data->div_count + - data->fixed_factor_count + data->early_clk_count; + data->fixed_factor_count + data->clk_count + + data->early_clk_count; cells = kzalloc_flex(*cells, hws, clk_count); if (!cells) return -ENOMEM; @@ -384,9 +522,59 @@ static int eqc_probe(struct platform_device *pdev) eqc_probe_init_fixed_factors(dev, data, cells); + for (i = 0; i < data->clk_count; i++) { + const struct eqc_clock *clk = &data->clks[i]; + + if (clk->probe) + ret = clk->probe(dev, NULL, clk, base, cells); + else + ret = -EINVAL; + if (ret) + dev_warn(dev, "failed probing clock %s: %d\n", clk->name, ret); + } + return of_clk_add_hw_provider(np, of_clk_hw_onecell_get, cells); } +#define DIV(_index, _parent_idx, _name, _parent_name, \ + _reg, _shift, _width) \ + { \ + .index = _index, \ + .parent_idx = _parent_idx, \ + .name = _name, \ + .parent_name = _parent_name, \ + .probe = eqc_probe_divider, \ + .unregister = clk_hw_unregister_divider, \ + .div.reg = _reg, \ + .div.shift = _shift, \ + .div.width = _width, \ + .div.flags = CLK_DIVIDER_EVEN_INTEGERS, \ + .div.table = NULL, \ + } + +#define FF(_index, _parent_idx, _name, _parent_name, _mult, _div) \ + { \ + .index = _index, \ + .parent_idx = _parent_idx, \ + .name = _name, \ + .parent_name = _parent_name, \ + .probe = eqc_probe_fixed_factor, \ + .unregister = clk_hw_unregister_fixed_factor, \ + .ff.mult = _mult, \ + .ff.div = _div, \ + } + +#define PLL_FRACG(_index, _parent_idx, _name, _parent_name, _reg) \ + { \ + .index = _index, \ + .parent_idx = _parent_idx, \ + .name = _name, \ + .parent_name = _parent_name, \ + .probe = eqc_probe_pll_fracg, \ + .unregister = clk_hw_unregister_fixed_factor, \ + .pll.reg = _reg, \ + } + /* Required early for GIC timer (pll-cpu) and UARTs (pll-per). */ static const struct eqc_pll eqc_eyeq5_early_plls[] = { { .index = EQ5C_PLL_CPU, .name = "pll-cpu", .reg64 = 0x02C }, @@ -769,7 +957,7 @@ static void __init eqc_early_init(struct device_node *np, int ret; clk_count = early_data->early_pll_count + early_data->early_fixed_factor_count + - early_data->late_clk_count; + early_data->early_clk_count + early_data->late_clk_count; cells = kzalloc_flex(*cells, hws, clk_count); if (!cells) { ret = -ENOMEM; @@ -831,6 +1019,19 @@ static void __init eqc_early_init(struct device_node *np, } } + for (i = 0; i < early_data->early_clk_count; i++) { + const struct eqc_clock *clk = &early_data->early_clks[i]; + + if (clk->probe) + ret = clk->probe(NULL, np, clk, base, cells); + else + ret = -EINVAL; + if (ret) { + pr_err("failed registering %s\n", clk->name); + goto err; + } + } + ret = of_clk_add_hw_provider(np, of_clk_hw_onecell_get, cells); if (ret) { pr_err("failed registering clk provider: %d\n", ret); @@ -860,6 +1061,14 @@ static void __init eqc_early_init(struct device_node *np, clk_hw_unregister_fixed_factor(hw); } + for (i = 0; i < early_data->early_clk_count; i++) { + const struct eqc_clock *clk = &early_data->early_clks[i]; + struct clk_hw *hw = cells->hws[clk->index]; + + if (!IS_ERR_OR_NULL(hw) && clk->unregister) + clk->unregister(hw); + } + kfree(cells); } } From 5e80c4b4f4ae7bd1b711540add83cfccfb7e8dec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Monin?= Date: Wed, 10 Dec 2025 18:09:51 +0100 Subject: [PATCH 0174/1328] clk: eyeq: Convert clocks declaration to eqc_clock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert all declarations of PLLs, dividers, and fixed factors to struct eqc_clock for the EyeQ5, EyeQ6H, EyeQ6L, and EyeQ6Lplus, both for the match data used during probe and the early match data used in early initialization. Tested on the EyeQ5, EyeQ6H and EyeQ6Lplus evaluation boards. For the EyeQ6Lplus and EyeQ6H, the clock tree is unchanged. For the EyeQ5, this change allows us to set the parent of some probed dividers and fixed factors by name as they refer to clocks registered in early init. While these clocks were previously orphaned, they were not yet used by a peripheral. Signed-off-by: Benoît Monin --- drivers/clk/clk-eyeq.c | 354 ++++++++++++++++------------------------- 1 file changed, 135 insertions(+), 219 deletions(-) diff --git a/drivers/clk/clk-eyeq.c b/drivers/clk/clk-eyeq.c index 7b8de08388d3..8a38ad9afb5e 100644 --- a/drivers/clk/clk-eyeq.c +++ b/drivers/clk/clk-eyeq.c @@ -575,23 +575,6 @@ static int eqc_probe(struct platform_device *pdev) .pll.reg = _reg, \ } -/* Required early for GIC timer (pll-cpu) and UARTs (pll-per). */ -static const struct eqc_pll eqc_eyeq5_early_plls[] = { - { .index = EQ5C_PLL_CPU, .name = "pll-cpu", .reg64 = 0x02C }, - { .index = EQ5C_PLL_PER, .name = "pll-per", .reg64 = 0x05C }, -}; - -static const struct eqc_pll eqc_eyeq5_plls[] = { - { .index = EQ5C_PLL_VMP, .name = "pll-vmp", .reg64 = 0x034 }, - { .index = EQ5C_PLL_PMA, .name = "pll-pma", .reg64 = 0x03C }, - { .index = EQ5C_PLL_VDI, .name = "pll-vdi", .reg64 = 0x044 }, - { .index = EQ5C_PLL_DDR0, .name = "pll-ddr0", .reg64 = 0x04C }, - { .index = EQ5C_PLL_PCI, .name = "pll-pci", .reg64 = 0x054 }, - { .index = EQ5C_PLL_PMAC, .name = "pll-pmac", .reg64 = 0x064 }, - { .index = EQ5C_PLL_MPC, .name = "pll-mpc", .reg64 = 0x06C }, - { .index = EQ5C_PLL_DDR1, .name = "pll-ddr1", .reg64 = 0x074 }, -}; - enum { /* * EQ5C_PLL_CPU children. @@ -629,263 +612,206 @@ enum { EQ5C_PER_FCMU_A, }; -static const struct eqc_fixed_factor eqc_eyeq5_early_fixed_factors[] = { - /* EQ5C_PLL_CPU children */ - { EQ5C_CPU_OCC, "occ-cpu", 1, 1, EQ5C_PLL_CPU }, - { EQ5C_CPU_SI_CSS0, "si-css0", 1, 1, EQ5C_CPU_OCC }, - { EQ5C_CPU_CORE0, "core0", 1, 1, EQ5C_CPU_SI_CSS0 }, - { EQ5C_CPU_CORE1, "core1", 1, 1, EQ5C_CPU_SI_CSS0 }, - { EQ5C_CPU_CORE2, "core2", 1, 1, EQ5C_CPU_SI_CSS0 }, - { EQ5C_CPU_CORE3, "core3", 1, 1, EQ5C_CPU_SI_CSS0 }, +/* Required early for GIC timer (pll-cpu) and UARTs (pll-per). */ +static const struct eqc_clock eqc_eyeq5_early_clks[] = { + PLL_FRACG(EQ5C_PLL_CPU, PARENT_BY_FWNAME, "pll-cpu", "ref", 0x02C), + PLL_FRACG(EQ5C_PLL_PER, PARENT_BY_FWNAME, "pll-per", "ref", 0x05C), - /* EQ5C_PLL_PER children */ - { EQ5C_PER_OCC, "occ-periph", 1, 16, EQ5C_PLL_PER }, - { EQ5C_PER_UART, "uart", 1, 1, EQ5C_PER_OCC }, + FF(EQ5C_CPU_OCC, EQ5C_PLL_CPU, "occ-cpu", NULL, 1, 1), + FF(EQ5C_CPU_SI_CSS0, EQ5C_CPU_OCC, "si-css0", NULL, 1, 1), + FF(EQ5C_CPU_CORE0, EQ5C_CPU_SI_CSS0, "core0", NULL, 1, 1), + FF(EQ5C_CPU_CORE1, EQ5C_CPU_SI_CSS0, "core1", NULL, 1, 1), + FF(EQ5C_CPU_CORE2, EQ5C_CPU_SI_CSS0, "core2", NULL, 1, 1), + FF(EQ5C_CPU_CORE3, EQ5C_CPU_SI_CSS0, "core3", NULL, 1, 1), + + FF(EQ5C_PER_OCC, EQ5C_PLL_PER, "occ-periph", NULL, 1, 16), + FF(EQ5C_PER_UART, EQ5C_PER_OCC, "uart", NULL, 1, 1), }; -static const struct eqc_fixed_factor eqc_eyeq5_fixed_factors[] = { - /* EQ5C_PLL_CPU children */ - { EQ5C_CPU_CPC, "cpc", 1, 1, EQ5C_CPU_SI_CSS0 }, - { EQ5C_CPU_CM, "cm", 1, 1, EQ5C_CPU_SI_CSS0 }, - { EQ5C_CPU_MEM, "mem", 1, 1, EQ5C_CPU_SI_CSS0 }, - { EQ5C_CPU_OCC_ISRAM, "occ-isram", 1, 2, EQ5C_PLL_CPU }, - { EQ5C_CPU_ISRAM, "isram", 1, 1, EQ5C_CPU_OCC_ISRAM }, - { EQ5C_CPU_OCC_DBU, "occ-dbu", 1, 10, EQ5C_PLL_CPU }, - { EQ5C_CPU_SI_DBU_TP, "si-dbu-tp", 1, 1, EQ5C_CPU_OCC_DBU }, +static const struct eqc_clock eqc_eyeq5_clks[] = { + PLL_FRACG(EQ5C_PLL_VMP, PARENT_BY_FWNAME, "pll-vmp", "ref", 0x034), + PLL_FRACG(EQ5C_PLL_PMA, PARENT_BY_FWNAME, "pll-pma", "ref", 0x03C), + PLL_FRACG(EQ5C_PLL_VDI, PARENT_BY_FWNAME, "pll-vdi", "ref", 0x044), + PLL_FRACG(EQ5C_PLL_DDR0, PARENT_BY_FWNAME, "pll-ddr0", "ref", 0x04C), + PLL_FRACG(EQ5C_PLL_PCI, PARENT_BY_FWNAME, "pll-pci", "ref", 0x054), + PLL_FRACG(EQ5C_PLL_PMAC, PARENT_BY_FWNAME, "pll-pmac", "ref", 0x064), + PLL_FRACG(EQ5C_PLL_MPC, PARENT_BY_FWNAME, "pll-mpc", "ref", 0x06C), + PLL_FRACG(EQ5C_PLL_DDR1, PARENT_BY_FWNAME, "pll-ddr1", "ref", 0x074), - /* EQ5C_PLL_VDI children */ - { EQ5C_VDI_OCC_VDI, "occ-vdi", 1, 2, EQ5C_PLL_VDI }, - { EQ5C_VDI_VDI, "vdi", 1, 1, EQ5C_VDI_OCC_VDI }, - { EQ5C_VDI_OCC_CAN_SER, "occ-can-ser", 1, 16, EQ5C_PLL_VDI }, - { EQ5C_VDI_CAN_SER, "can-ser", 1, 1, EQ5C_VDI_OCC_CAN_SER }, - { EQ5C_VDI_I2C_SER, "i2c-ser", 1, 20, EQ5C_PLL_VDI }, + DIV(EQ5C_DIV_OSPI, PARENT_BY_NAME, "div-ospi", "pll-per", 0x11C, 0, 4), - /* EQ5C_PLL_PER children */ - { EQ5C_PER_PERIPH, "periph", 1, 1, EQ5C_PER_OCC }, - { EQ5C_PER_CAN, "can", 1, 1, EQ5C_PER_OCC }, - { EQ5C_PER_SPI, "spi", 1, 1, EQ5C_PER_OCC }, - { EQ5C_PER_I2C, "i2c", 1, 1, EQ5C_PER_OCC }, - { EQ5C_PER_TIMER, "timer", 1, 1, EQ5C_PER_OCC }, - { EQ5C_PER_GPIO, "gpio", 1, 1, EQ5C_PER_OCC }, - { EQ5C_PER_EMMC, "emmc-sys", 1, 10, EQ5C_PLL_PER }, - { EQ5C_PER_CCF, "ccf-ctrl", 1, 4, EQ5C_PLL_PER }, - { EQ5C_PER_OCC_MJPEG, "occ-mjpeg", 1, 2, EQ5C_PLL_PER }, - { EQ5C_PER_HSM, "hsm", 1, 1, EQ5C_PER_OCC_MJPEG }, - { EQ5C_PER_MJPEG, "mjpeg", 1, 1, EQ5C_PER_OCC_MJPEG }, - { EQ5C_PER_FCMU_A, "fcmu-a", 1, 20, EQ5C_PLL_PER }, - { EQ5C_PER_OCC_PCI, "occ-pci-sys", 1, 8, EQ5C_PLL_PER }, -}; + FF(EQ5C_CPU_CPC, PARENT_BY_NAME, "cpc", "si-css0", 1, 1), + FF(EQ5C_CPU_CM, PARENT_BY_NAME, "cm", "si-css0", 1, 1), + FF(EQ5C_CPU_MEM, PARENT_BY_NAME, "mem", "si-css0", 1, 1), + FF(EQ5C_CPU_OCC_ISRAM, PARENT_BY_NAME, "occ-isram", "pll-cpu", 1, 2), + FF(EQ5C_CPU_ISRAM, EQ5C_CPU_OCC_ISRAM, "isram", NULL, 1, 1), + FF(EQ5C_CPU_OCC_DBU, PARENT_BY_NAME, "occ-dbu", "pll-cpu", 1, 10), + FF(EQ5C_CPU_SI_DBU_TP, EQ5C_CPU_OCC_DBU, "si-dbu-tp", NULL, 1, 1), -static const struct eqc_div eqc_eyeq5_divs[] = { - { - .index = EQ5C_DIV_OSPI, - .name = "div-ospi", - .parent = EQ5C_PLL_PER, - .reg = 0x11C, - .shift = 0, - .width = 4, - }, + FF(EQ5C_VDI_OCC_VDI, PARENT_BY_NAME, "occ-vdi", "pll-vdi", 1, 2), + FF(EQ5C_VDI_VDI, EQ5C_VDI_OCC_VDI, "vdi", NULL, 1, 1), + FF(EQ5C_VDI_OCC_CAN_SER, PARENT_BY_NAME, "occ-can-ser", "pll-vdi", 1, 16), + FF(EQ5C_VDI_CAN_SER, EQ5C_VDI_OCC_CAN_SER, "can-ser", NULL, 1, 1), + FF(EQ5C_VDI_I2C_SER, PARENT_BY_NAME, "i2c-ser", "pll-vdi", 1, 20), + + FF(EQ5C_PER_PERIPH, PARENT_BY_NAME, "periph", "occ-periph", 1, 1), + FF(EQ5C_PER_CAN, PARENT_BY_NAME, "can", "occ-periph", 1, 1), + FF(EQ5C_PER_SPI, PARENT_BY_NAME, "spi", "occ-periph", 1, 1), + FF(EQ5C_PER_I2C, PARENT_BY_NAME, "i2c", "occ-periph", 1, 1), + FF(EQ5C_PER_TIMER, PARENT_BY_NAME, "timer", "occ-periph", 1, 1), + FF(EQ5C_PER_GPIO, PARENT_BY_NAME, "gpio", "occ-periph", 1, 1), + FF(EQ5C_PER_EMMC, PARENT_BY_NAME, "emmc-sys", "pll-per", 1, 10), + FF(EQ5C_PER_CCF, PARENT_BY_NAME, "ccf-ctrl", "pll-per", 1, 4), + FF(EQ5C_PER_OCC_MJPEG, PARENT_BY_NAME, "occ-mjpeg", "pll-per", 1, 2), + FF(EQ5C_PER_HSM, EQ5C_PER_OCC_MJPEG, "hsm", NULL, 1, 1), + FF(EQ5C_PER_MJPEG, EQ5C_PER_OCC_MJPEG, "mjpeg", NULL, 1, 1), + FF(EQ5C_PER_FCMU_A, PARENT_BY_NAME, "fcmu-a", "pll-per", 1, 20), + FF(EQ5C_PER_OCC_PCI, PARENT_BY_NAME, "occ-pci-sys", "pll-per", 1, 8), }; static const struct eqc_early_match_data eqc_eyeq5_early_match_data __initconst = { - .early_pll_count = ARRAY_SIZE(eqc_eyeq5_early_plls), - .early_plls = eqc_eyeq5_early_plls, + .early_clk_count = ARRAY_SIZE(eqc_eyeq5_early_clks), + .early_clks = eqc_eyeq5_early_clks, - .early_fixed_factor_count = ARRAY_SIZE(eqc_eyeq5_early_fixed_factors), - .early_fixed_factors = eqc_eyeq5_early_fixed_factors, - - .late_clk_count = ARRAY_SIZE(eqc_eyeq5_plls) + ARRAY_SIZE(eqc_eyeq5_divs) + - ARRAY_SIZE(eqc_eyeq5_fixed_factors), + .late_clk_count = ARRAY_SIZE(eqc_eyeq5_clks), }; static const struct eqc_match_data eqc_eyeq5_match_data = { - .pll_count = ARRAY_SIZE(eqc_eyeq5_plls), - .plls = eqc_eyeq5_plls, - - .div_count = ARRAY_SIZE(eqc_eyeq5_divs), - .divs = eqc_eyeq5_divs, - - .fixed_factor_count = ARRAY_SIZE(eqc_eyeq5_fixed_factors), - .fixed_factors = eqc_eyeq5_fixed_factors, + .clk_count = ARRAY_SIZE(eqc_eyeq5_clks), + .clks = eqc_eyeq5_clks, .reset_auxdev_name = "reset", .pinctrl_auxdev_name = "pinctrl", .eth_phy_auxdev_name = "phy", - .early_clk_count = ARRAY_SIZE(eqc_eyeq5_early_plls) + - ARRAY_SIZE(eqc_eyeq5_early_fixed_factors), + .early_clk_count = ARRAY_SIZE(eqc_eyeq5_early_clks), }; -static const struct eqc_pll eqc_eyeq6l_plls[] = { - { .index = EQ6LC_PLL_DDR, .name = "pll-ddr", .reg64 = 0x02C }, - { .index = EQ6LC_PLL_CPU, .name = "pll-cpu", .reg64 = 0x034 }, /* also acc */ - { .index = EQ6LC_PLL_PER, .name = "pll-per", .reg64 = 0x03C }, - { .index = EQ6LC_PLL_VDI, .name = "pll-vdi", .reg64 = 0x044 }, +static const struct eqc_clock eqc_eyeq6l_clks[] = { + PLL_FRACG(EQ6LC_PLL_DDR, PARENT_BY_FWNAME, "pll-ddr", "ref", 0x02C), + PLL_FRACG(EQ6LC_PLL_CPU, PARENT_BY_FWNAME, "pll-cpu", "ref", 0x034), + PLL_FRACG(EQ6LC_PLL_PER, PARENT_BY_FWNAME, "pll-per", "ref", 0x03C), + PLL_FRACG(EQ6LC_PLL_VDI, PARENT_BY_FWNAME, "pll-vdi", "ref", 0x044), }; static const struct eqc_match_data eqc_eyeq6l_match_data = { - .pll_count = ARRAY_SIZE(eqc_eyeq6l_plls), - .plls = eqc_eyeq6l_plls, + .clk_count = ARRAY_SIZE(eqc_eyeq6l_clks), + .clks = eqc_eyeq6l_clks, .reset_auxdev_name = "reset", }; -static const struct eqc_pll eqc_eyeq6lplus_early_plls[] = { - { .index = EQ6LPC_PLL_CPU, .name = "pll-cpu", .reg64 = 0x058 }, +static const struct eqc_clock eqc_eyeq6lplus_early_clks[] = { + PLL_FRACG(EQ6LPC_PLL_CPU, PARENT_BY_FWNAME, "pll-cpu", "ref", 0x058), + + FF(EQ6LPC_CPU_OCC, EQ6LPC_PLL_CPU, "occ-cpu", NULL, 1, 1), }; -static const struct eqc_pll eqc_eyeq6lplus_plls[] = { - { .index = EQ6LPC_PLL_DDR, .name = "pll-ddr", .reg64 = 0x02C }, - { .index = EQ6LPC_PLL_ACC, .name = "pll-acc", .reg64 = 0x034 }, - { .index = EQ6LPC_PLL_PER, .name = "pll-per", .reg64 = 0x03C }, - { .index = EQ6LPC_PLL_VDI, .name = "pll-vdi", .reg64 = 0x044 }, -}; +static const struct eqc_clock eqc_eyeq6lplus_clks[] = { + PLL_FRACG(EQ6LPC_PLL_DDR, PARENT_BY_FWNAME, "pll-ddr", "ref", 0x02C), + PLL_FRACG(EQ6LPC_PLL_ACC, PARENT_BY_FWNAME, "pll-acc", "ref", 0x034), + PLL_FRACG(EQ6LPC_PLL_PER, PARENT_BY_FWNAME, "pll-per", "ref", 0x03C), + PLL_FRACG(EQ6LPC_PLL_VDI, PARENT_BY_FWNAME, "pll-vdi", "ref", 0x044), -static const struct eqc_fixed_factor eqc_eyeq6lplus_early_fixed_factors[] = { - { EQ6LPC_CPU_OCC, "occ-cpu", 1, 1, EQ6LPC_PLL_CPU }, -}; + FF(EQ6LPC_DDR_OCC, EQ6LPC_PLL_DDR, "occ-ddr", NULL, 1, 1), -static const struct eqc_fixed_factor eqc_eyeq6lplus_fixed_factors[] = { - { EQ6LPC_DDR_OCC, "occ-ddr", 1, 1, EQ6LPC_PLL_DDR }, + FF(EQ6LPC_ACC_VDI, EQ6LPC_PLL_ACC, "vdi-div", NULL, 1, 10), + FF(EQ6LPC_ACC_OCC, EQ6LPC_PLL_ACC, "occ-acc", NULL, 1, 1), + FF(EQ6LPC_ACC_FCMU, EQ6LPC_ACC_OCC, "fcmu-a-clk", NULL, 1, 10), - { EQ6LPC_ACC_VDI, "vdi-div", 1, 10, EQ6LPC_PLL_ACC }, - { EQ6LPC_ACC_OCC, "occ-acc", 1, 1, EQ6LPC_PLL_ACC }, - { EQ6LPC_ACC_FCMU, "fcmu-a-clk", 1, 10, EQ6LPC_ACC_OCC }, + FF(EQ6LPC_PER_OCC, EQ6LPC_PLL_PER, "occ-per", NULL, 1, 1), + FF(EQ6LPC_PER_I2C_SER, EQ6LPC_PER_OCC, "i2c-ser-clk", NULL, 1, 10), + FF(EQ6LPC_PER_PCLK, EQ6LPC_PER_OCC, "pclk", NULL, 1, 4), + FF(EQ6LPC_PER_TSU, EQ6LPC_PER_OCC, "tsu-clk", NULL, 1, 8), + FF(EQ6LPC_PER_OSPI, EQ6LPC_PER_OCC, "ospi-ref-clk", NULL, 1, 10), + FF(EQ6LPC_PER_GPIO, EQ6LPC_PER_OCC, "gpio-clk", NULL, 1, 4), + FF(EQ6LPC_PER_TIMER, EQ6LPC_PER_OCC, "timer-clk", NULL, 1, 4), + FF(EQ6LPC_PER_I2C, EQ6LPC_PER_OCC, "i2c-clk", NULL, 1, 4), + FF(EQ6LPC_PER_UART, EQ6LPC_PER_OCC, "uart-clk", NULL, 1, 4), + FF(EQ6LPC_PER_SPI, EQ6LPC_PER_OCC, "spi-clk", NULL, 1, 4), + FF(EQ6LPC_PER_PERIPH, EQ6LPC_PER_OCC, "periph-clk", NULL, 1, 1), - { EQ6LPC_PER_OCC, "occ-per", 1, 1, EQ6LPC_PLL_PER }, - { EQ6LPC_PER_I2C_SER, "i2c-ser-clk", 1, 10, EQ6LPC_PER_OCC }, - { EQ6LPC_PER_PCLK, "pclk", 1, 4, EQ6LPC_PER_OCC }, - { EQ6LPC_PER_TSU, "tsu-clk", 1, 8, EQ6LPC_PER_OCC }, - { EQ6LPC_PER_OSPI, "ospi-ref-clk", 1, 10, EQ6LPC_PER_OCC }, - { EQ6LPC_PER_GPIO, "gpio-clk", 1, 4, EQ6LPC_PER_OCC }, - { EQ6LPC_PER_TIMER, "timer-clk", 1, 4, EQ6LPC_PER_OCC }, - { EQ6LPC_PER_I2C, "i2c-clk", 1, 4, EQ6LPC_PER_OCC }, - { EQ6LPC_PER_UART, "uart-clk", 1, 4, EQ6LPC_PER_OCC }, - { EQ6LPC_PER_SPI, "spi-clk", 1, 4, EQ6LPC_PER_OCC }, - { EQ6LPC_PER_PERIPH, "periph-clk", 1, 1, EQ6LPC_PER_OCC }, - - { EQ6LPC_VDI_OCC, "occ-vdi", 1, 1, EQ6LPC_PLL_VDI }, + FF(EQ6LPC_VDI_OCC, EQ6LPC_PLL_VDI, "occ-vdi", NULL, 1, 1), }; static const struct eqc_early_match_data eqc_eyeq6lplus_early_match_data __initconst = { - .early_pll_count = ARRAY_SIZE(eqc_eyeq6lplus_early_plls), - .early_plls = eqc_eyeq6lplus_early_plls, + .early_clk_count = ARRAY_SIZE(eqc_eyeq6lplus_early_clks), + .early_clks = eqc_eyeq6lplus_early_clks, - .early_fixed_factor_count = ARRAY_SIZE(eqc_eyeq6lplus_early_fixed_factors), - .early_fixed_factors = eqc_eyeq6lplus_early_fixed_factors, - - .late_clk_count = ARRAY_SIZE(eqc_eyeq6lplus_plls) + - ARRAY_SIZE(eqc_eyeq6lplus_fixed_factors), + .late_clk_count = ARRAY_SIZE(eqc_eyeq6lplus_clks), }; static const struct eqc_match_data eqc_eyeq6lplus_match_data = { - .pll_count = ARRAY_SIZE(eqc_eyeq6lplus_plls), - .plls = eqc_eyeq6lplus_plls, - - .fixed_factor_count = ARRAY_SIZE(eqc_eyeq6lplus_fixed_factors), - .fixed_factors = eqc_eyeq6lplus_fixed_factors, + .clk_count = ARRAY_SIZE(eqc_eyeq6lplus_clks), + .clks = eqc_eyeq6lplus_clks, .reset_auxdev_name = "reset", .pinctrl_auxdev_name = "pinctrl", - .early_clk_count = ARRAY_SIZE(eqc_eyeq6lplus_early_plls) + - ARRAY_SIZE(eqc_eyeq6lplus_early_fixed_factors), + .early_clk_count = ARRAY_SIZE(eqc_eyeq6lplus_early_clks), }; static const struct eqc_match_data eqc_eyeq6h_west_match_data = { .reset_auxdev_name = "reset_west", }; -static const struct eqc_pll eqc_eyeq6h_east_plls[] = { - { .index = 0, .name = "pll-east", .reg64 = 0x074 }, +static const struct eqc_clock eqc_eyeq6h_east_clks[] = { + PLL_FRACG(0, PARENT_BY_FWNAME, "pll-east", "ref", 0x074), }; static const struct eqc_match_data eqc_eyeq6h_east_match_data = { - .pll_count = ARRAY_SIZE(eqc_eyeq6h_east_plls), - .plls = eqc_eyeq6h_east_plls, + .clk_count = ARRAY_SIZE(eqc_eyeq6h_east_clks), + .clks = eqc_eyeq6h_east_clks, .reset_auxdev_name = "reset_east", }; -static const struct eqc_pll eqc_eyeq6h_south_plls[] = { - { .index = EQ6HC_SOUTH_PLL_VDI, .name = "pll-vdi", .reg64 = 0x000 }, - { .index = EQ6HC_SOUTH_PLL_PCIE, .name = "pll-pcie", .reg64 = 0x008 }, - { .index = EQ6HC_SOUTH_PLL_PER, .name = "pll-per", .reg64 = 0x010 }, - { .index = EQ6HC_SOUTH_PLL_ISP, .name = "pll-isp", .reg64 = 0x018 }, -}; +static const struct eqc_clock eqc_eyeq6h_south_clks[] = { + PLL_FRACG(EQ6HC_SOUTH_PLL_VDI, PARENT_BY_FWNAME, "pll-vdi", "ref", 0x000), + PLL_FRACG(EQ6HC_SOUTH_PLL_PCIE, PARENT_BY_FWNAME, "pll-pcie", "ref", 0x008), + PLL_FRACG(EQ6HC_SOUTH_PLL_PER, PARENT_BY_FWNAME, "pll-per", "ref", 0x010), + PLL_FRACG(EQ6HC_SOUTH_PLL_ISP, PARENT_BY_FWNAME, "pll-isp", "ref", 0x018), -static const struct eqc_div eqc_eyeq6h_south_divs[] = { - { - .index = EQ6HC_SOUTH_DIV_EMMC, - .name = "div-emmc", - .parent = EQ6HC_SOUTH_PLL_PER, - .reg = 0x070, - .shift = 4, - .width = 4, - }, - { - .index = EQ6HC_SOUTH_DIV_OSPI_REF, - .name = "div-ospi-ref", - .parent = EQ6HC_SOUTH_PLL_PER, - .reg = 0x090, - .shift = 4, - .width = 4, - }, - { - .index = EQ6HC_SOUTH_DIV_OSPI_SYS, - .name = "div-ospi-sys", - .parent = EQ6HC_SOUTH_PLL_PER, - .reg = 0x090, - .shift = 8, - .width = 1, - }, - { - .index = EQ6HC_SOUTH_DIV_TSU, - .name = "div-tsu", - .parent = EQ6HC_SOUTH_PLL_PCIE, - .reg = 0x098, - .shift = 4, - .width = 8, - }, + DIV(EQ6HC_SOUTH_DIV_EMMC, EQ6HC_SOUTH_PLL_PER, "div-emmc", NULL, 0x070, 4, 4), + DIV(EQ6HC_SOUTH_DIV_OSPI_REF, EQ6HC_SOUTH_PLL_PER, "div-ospi-ref", NULL, 0x090, 4, 4), + DIV(EQ6HC_SOUTH_DIV_OSPI_SYS, EQ6HC_SOUTH_PLL_PER, "div-ospi-sys", NULL, 0x090, 8, 1), + DIV(EQ6HC_SOUTH_DIV_TSU, EQ6HC_SOUTH_PLL_PCIE, "div-tsu", NULL, 0x098, 4, 8), }; static const struct eqc_match_data eqc_eyeq6h_south_match_data = { - .pll_count = ARRAY_SIZE(eqc_eyeq6h_south_plls), - .plls = eqc_eyeq6h_south_plls, - - .div_count = ARRAY_SIZE(eqc_eyeq6h_south_divs), - .divs = eqc_eyeq6h_south_divs, + .clk_count = ARRAY_SIZE(eqc_eyeq6h_south_clks), + .clks = eqc_eyeq6h_south_clks, }; -static const struct eqc_pll eqc_eyeq6h_ddr0_plls[] = { - { .index = 0, .name = "pll-ddr0", .reg64 = 0x074 }, +static const struct eqc_clock eqc_eyeq6h_ddr0_clks[] = { + PLL_FRACG(0, PARENT_BY_FWNAME, "pll-ddr0", "ref", 0x074), }; static const struct eqc_match_data eqc_eyeq6h_ddr0_match_data = { - .pll_count = ARRAY_SIZE(eqc_eyeq6h_ddr0_plls), - .plls = eqc_eyeq6h_ddr0_plls, + .clk_count = ARRAY_SIZE(eqc_eyeq6h_ddr0_clks), + .clks = eqc_eyeq6h_ddr0_clks, }; -static const struct eqc_pll eqc_eyeq6h_ddr1_plls[] = { - { .index = 0, .name = "pll-ddr1", .reg64 = 0x074 }, +static const struct eqc_clock eqc_eyeq6h_ddr1_clks[] = { + PLL_FRACG(0, PARENT_BY_FWNAME, "pll-ddr1", "ref", 0x074), }; static const struct eqc_match_data eqc_eyeq6h_ddr1_match_data = { - .pll_count = ARRAY_SIZE(eqc_eyeq6h_ddr1_plls), - .plls = eqc_eyeq6h_ddr1_plls, + .clk_count = ARRAY_SIZE(eqc_eyeq6h_ddr1_clks), + .clks = eqc_eyeq6h_ddr1_clks, }; -static const struct eqc_pll eqc_eyeq6h_acc_plls[] = { - { .index = EQ6HC_ACC_PLL_XNN, .name = "pll-xnn", .reg64 = 0x040 }, - { .index = EQ6HC_ACC_PLL_VMP, .name = "pll-vmp", .reg64 = 0x050 }, - { .index = EQ6HC_ACC_PLL_PMA, .name = "pll-pma", .reg64 = 0x05C }, - { .index = EQ6HC_ACC_PLL_MPC, .name = "pll-mpc", .reg64 = 0x068 }, - { .index = EQ6HC_ACC_PLL_NOC, .name = "pll-noc", .reg64 = 0x070 }, +static const struct eqc_clock eqc_eyeq6h_acc_clks[] = { + PLL_FRACG(EQ6HC_ACC_PLL_XNN, PARENT_BY_FWNAME, "pll-xnn", "ref", 0x040), + PLL_FRACG(EQ6HC_ACC_PLL_VMP, PARENT_BY_FWNAME, "pll-vmp", "ref", 0x050), + PLL_FRACG(EQ6HC_ACC_PLL_PMA, PARENT_BY_FWNAME, "pll-pma", "ref", 0x05C), + PLL_FRACG(EQ6HC_ACC_PLL_MPC, PARENT_BY_FWNAME, "pll-mpc", "ref", 0x068), + PLL_FRACG(EQ6HC_ACC_PLL_NOC, PARENT_BY_FWNAME, "pll-noc", "ref", 0x070), }; static const struct eqc_match_data eqc_eyeq6h_acc_match_data = { - .pll_count = ARRAY_SIZE(eqc_eyeq6h_acc_plls), - .plls = eqc_eyeq6h_acc_plls, + .clk_count = ARRAY_SIZE(eqc_eyeq6h_acc_clks), + .clks = eqc_eyeq6h_acc_clks, .reset_auxdev_name = "reset_acc", }; @@ -914,38 +840,28 @@ static struct platform_driver eqc_driver = { builtin_platform_driver(eqc_driver); /* Required early for GIC timer. */ -static const struct eqc_pll eqc_eyeq6h_central_early_plls[] = { - { .index = EQ6HC_CENTRAL_PLL_CPU, .name = "pll-cpu", .reg64 = 0x02C }, -}; +static const struct eqc_clock eqc_eyeq6h_central_early_clks[] = { + PLL_FRACG(EQ6HC_CENTRAL_PLL_CPU, PARENT_BY_FWNAME, "pll-cpu", "ref", 0x02C), -static const struct eqc_fixed_factor eqc_eyeq6h_central_early_fixed_factors[] = { - { EQ6HC_CENTRAL_CPU_OCC, "occ-cpu", 1, 1, EQ6HC_CENTRAL_PLL_CPU }, + FF(EQ6HC_CENTRAL_CPU_OCC, EQ6HC_CENTRAL_PLL_CPU, "occ-cpu", NULL, 1, 1), }; static const struct eqc_early_match_data eqc_eyeq6h_central_early_match_data __initconst = { - .early_pll_count = ARRAY_SIZE(eqc_eyeq6h_central_early_plls), - .early_plls = eqc_eyeq6h_central_early_plls, - - .early_fixed_factor_count = ARRAY_SIZE(eqc_eyeq6h_central_early_fixed_factors), - .early_fixed_factors = eqc_eyeq6h_central_early_fixed_factors, + .early_clk_count = ARRAY_SIZE(eqc_eyeq6h_central_early_clks), + .early_clks = eqc_eyeq6h_central_early_clks, }; /* Required early for UART. */ -static const struct eqc_pll eqc_eyeq6h_west_early_plls[] = { - { .index = EQ6HC_WEST_PLL_PER, .name = "pll-west", .reg64 = 0x074 }, -}; +static const struct eqc_clock eqc_eyeq6h_west_early_clks[] = { + PLL_FRACG(EQ6HC_WEST_PLL_PER, PARENT_BY_FWNAME, "pll-west", "ref", 0x074), -static const struct eqc_fixed_factor eqc_eyeq6h_west_early_fixed_factors[] = { - { EQ6HC_WEST_PER_OCC, "west-per-occ", 1, 10, EQ6HC_WEST_PLL_PER }, - { EQ6HC_WEST_PER_UART, "west-per-uart", 1, 1, EQ6HC_WEST_PER_OCC }, + FF(EQ6HC_WEST_PER_OCC, EQ6HC_WEST_PLL_PER, "west-per-occ", NULL, 1, 10), + FF(EQ6HC_WEST_PER_UART, EQ6HC_WEST_PER_OCC, "west-per-uart", NULL, 1, 1), }; static const struct eqc_early_match_data eqc_eyeq6h_west_early_match_data __initconst = { - .early_pll_count = ARRAY_SIZE(eqc_eyeq6h_west_early_plls), - .early_plls = eqc_eyeq6h_west_early_plls, - - .early_fixed_factor_count = ARRAY_SIZE(eqc_eyeq6h_west_early_fixed_factors), - .early_fixed_factors = eqc_eyeq6h_west_early_fixed_factors, + .early_clk_count = ARRAY_SIZE(eqc_eyeq6h_west_early_clks), + .early_clks = eqc_eyeq6h_west_early_clks, }; static void __init eqc_early_init(struct device_node *np, From 0fe7490b034c86266c3ffd91d6951e21499d1d13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Monin?= Date: Wed, 10 Dec 2025 18:14:20 +0100 Subject: [PATCH 0175/1328] clk: eyeq: Drop PLL, dividers, and fixed factors structs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now that there are no users of the eqc_pll, eqc_div, and eqc_fixed_factor structures since they have been converted to eqc_clock, remove these structs and the code related to their parsing in probe and early initialization. Signed-off-by: Benoît Monin --- drivers/clk/clk-eyeq.c | 192 +---------------------------------------- 1 file changed, 3 insertions(+), 189 deletions(-) diff --git a/drivers/clk/clk-eyeq.c b/drivers/clk/clk-eyeq.c index 8a38ad9afb5e..5200d5f0ff06 100644 --- a/drivers/clk/clk-eyeq.c +++ b/drivers/clk/clk-eyeq.c @@ -78,33 +78,6 @@ #define PARENT_BY_FWNAME (-1) #define PARENT_BY_NAME (-2) -struct eqc_pll { - unsigned int index; - const char *name; - unsigned int reg64; -}; - -/* - * Divider clock. Divider is 2*(v+1), with v the register value. - * Min divider is 2, max is 2*(2^width). - */ -struct eqc_div { - unsigned int index; - const char *name; - unsigned int parent; - unsigned int reg; - u8 shift; - u8 width; -}; - -struct eqc_fixed_factor { - unsigned int index; - const char *name; - unsigned int mult; - unsigned int div; - unsigned int parent; -}; - struct eqc_clock { int index; int parent_idx; @@ -133,15 +106,6 @@ struct eqc_clock { }; struct eqc_match_data { - unsigned int pll_count; - const struct eqc_pll *plls; - - unsigned int div_count; - const struct eqc_div *divs; - - unsigned int fixed_factor_count; - const struct eqc_fixed_factor *fixed_factors; - unsigned int clk_count; const struct eqc_clock *clks; @@ -153,12 +117,6 @@ struct eqc_match_data { }; struct eqc_early_match_data { - unsigned int early_pll_count; - const struct eqc_pll *early_plls; - - unsigned int early_fixed_factor_count; - const struct eqc_fixed_factor *early_fixed_factors; - unsigned int early_clk_count; const struct eqc_clock *early_clks; @@ -271,97 +229,6 @@ static int eqc_pll_parse_fracg(void __iomem *base, unsigned long *mult, return 0; } -static void eqc_probe_init_plls(struct device *dev, const struct eqc_match_data *data, - void __iomem *base, struct clk_hw_onecell_data *cells) -{ - unsigned long mult, div, acc; - const struct eqc_pll *pll; - struct clk_hw *hw; - unsigned int i; - int ret; - - for (i = 0; i < data->pll_count; i++) { - pll = &data->plls[i]; - - ret = eqc_pll_parse_fracg(base + pll->reg64, &mult, &div, &acc); - if (ret) { - dev_warn(dev, "failed parsing state of %s\n", pll->name); - cells->hws[pll->index] = ERR_PTR(ret); - continue; - } - - hw = clk_hw_register_fixed_factor_with_accuracy_fwname(dev, - dev->of_node, pll->name, "ref", 0, mult, div, acc); - cells->hws[pll->index] = hw; - if (IS_ERR(hw)) - dev_warn(dev, "failed registering %s: %pe\n", pll->name, hw); - } -} - -static void eqc_probe_init_divs(struct device *dev, const struct eqc_match_data *data, - void __iomem *base, struct clk_hw_onecell_data *cells) -{ - struct clk_parent_data parent_data = { }; - const struct eqc_div *div; - struct clk_hw *parent; - void __iomem *reg; - struct clk_hw *hw; - unsigned int i; - - for (i = 0; i < data->div_count; i++) { - div = &data->divs[i]; - reg = base + div->reg; - parent = cells->hws[div->parent]; - - if (IS_ERR(parent)) { - /* Parent is in early clk provider. */ - parent_data.index = div->parent; - parent_data.hw = NULL; - } else { - /* Avoid clock lookup when we already have the hw reference. */ - parent_data.index = 0; - parent_data.hw = parent; - } - - hw = clk_hw_register_divider_table_parent_data(dev, div->name, - &parent_data, 0, reg, div->shift, div->width, - CLK_DIVIDER_EVEN_INTEGERS, NULL, NULL); - cells->hws[div->index] = hw; - if (IS_ERR(hw)) - dev_warn(dev, "failed registering %s: %pe\n", - div->name, hw); - } -} - -static void eqc_probe_init_fixed_factors(struct device *dev, - const struct eqc_match_data *data, - struct clk_hw_onecell_data *cells) -{ - const struct eqc_fixed_factor *ff; - struct clk_hw *hw, *parent_hw; - unsigned int i; - - for (i = 0; i < data->fixed_factor_count; i++) { - ff = &data->fixed_factors[i]; - parent_hw = cells->hws[ff->parent]; - - if (IS_ERR(parent_hw)) { - /* Parent is in early clk provider. */ - hw = clk_hw_register_fixed_factor_index(dev, ff->name, - ff->parent, 0, ff->mult, ff->div); - } else { - /* Avoid clock lookup when we already have the hw reference. */ - hw = clk_hw_register_fixed_factor_parent_hw(dev, ff->name, - parent_hw, 0, ff->mult, ff->div); - } - - cells->hws[ff->index] = hw; - if (IS_ERR(hw)) - dev_warn(dev, "failed registering %s: %pe\n", - ff->name, hw); - } -} - static void eqc_auxdev_create_optional(struct device *dev, void __iomem *base, const char *name) { @@ -500,12 +367,10 @@ static int eqc_probe(struct platform_device *pdev) eqc_auxdev_create_optional(dev, base, data->pinctrl_auxdev_name); eqc_auxdev_create_optional(dev, base, data->eth_phy_auxdev_name); - if (data->pll_count + data->div_count + data->fixed_factor_count + data->clk_count == 0) + if (data->clk_count == 0) return 0; /* Zero clocks, we are done. */ - clk_count = data->pll_count + data->div_count + - data->fixed_factor_count + data->clk_count + - data->early_clk_count; + clk_count = data->clk_count + data->early_clk_count; cells = kzalloc_flex(*cells, hws, clk_count); if (!cells) return -ENOMEM; @@ -516,12 +381,6 @@ static int eqc_probe(struct platform_device *pdev) for (i = 0; i < clk_count; i++) cells->hws[i] = ERR_PTR(-EINVAL); - eqc_probe_init_plls(dev, data, base, cells); - - eqc_probe_init_divs(dev, data, base, cells); - - eqc_probe_init_fixed_factors(dev, data, cells); - for (i = 0; i < data->clk_count; i++) { const struct eqc_clock *clk = &data->clks[i]; @@ -872,8 +731,7 @@ static void __init eqc_early_init(struct device_node *np, void __iomem *base; int ret; - clk_count = early_data->early_pll_count + early_data->early_fixed_factor_count + - early_data->early_clk_count + early_data->late_clk_count; + clk_count = early_data->early_clk_count + early_data->late_clk_count; cells = kzalloc_flex(*cells, hws, clk_count); if (!cells) { ret = -ENOMEM; @@ -899,42 +757,6 @@ static void __init eqc_early_init(struct device_node *np, goto err; } - for (i = 0; i < early_data->early_pll_count; i++) { - const struct eqc_pll *pll = &early_data->early_plls[i]; - unsigned long mult, div, acc; - struct clk_hw *hw; - - ret = eqc_pll_parse_fracg(base + pll->reg64, &mult, &div, &acc); - if (ret) { - pr_err("failed parsing state of %s\n", pll->name); - goto err; - } - - hw = clk_hw_register_fixed_factor_with_accuracy_fwname(NULL, - np, pll->name, "ref", 0, mult, div, acc); - cells->hws[pll->index] = hw; - if (IS_ERR(hw)) { - pr_err("failed registering %s: %pe\n", pll->name, hw); - ret = PTR_ERR(hw); - goto err; - } - } - - for (i = 0; i < early_data->early_fixed_factor_count; i++) { - const struct eqc_fixed_factor *ff = &early_data->early_fixed_factors[i]; - struct clk_hw *parent_hw = cells->hws[ff->parent]; - struct clk_hw *hw; - - hw = clk_hw_register_fixed_factor_parent_hw(NULL, ff->name, - parent_hw, 0, ff->mult, ff->div); - cells->hws[ff->index] = hw; - if (IS_ERR(hw)) { - pr_err("failed registering %s: %pe\n", ff->name, hw); - ret = PTR_ERR(hw); - goto err; - } - } - for (i = 0; i < early_data->early_clk_count; i++) { const struct eqc_clock *clk = &early_data->early_clks[i]; @@ -969,14 +791,6 @@ static void __init eqc_early_init(struct device_node *np, if (cells) { of_clk_del_provider(np); - for (i = 0; i < early_data->early_pll_count; i++) { - const struct eqc_pll *pll = &early_data->early_plls[i]; - struct clk_hw *hw = cells->hws[pll->index]; - - if (!IS_ERR_OR_NULL(hw)) - clk_hw_unregister_fixed_factor(hw); - } - for (i = 0; i < early_data->early_clk_count; i++) { const struct eqc_clock *clk = &early_data->early_clks[i]; struct clk_hw *hw = cells->hws[clk->index]; From 44af1b974949c8f2f7cca469c0c7eee481750309 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Monin?= Date: Thu, 11 Dec 2025 10:47:09 +0100 Subject: [PATCH 0176/1328] clk: eyeq: Add EyeQ7H compatibles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the entries for the 14 Other Logic Blocks found in the EyeQ7H SoC. The clock tree is more complex than the previous generation of SoC, as some OLB depend on the clock output of other OLB instead of all referring to the main oscillator. The OLB south, east and west generate those reference clocks used by other blocks. They also use the reference clock internally. The reference clock provided by OLB south is named "ref_100p0", "ref_106p6_e" by OLB east and "ref_106p6_w" by OLB west. For the OLB with a single parent clock, We use the same logic as the blocks found in previous SoC and refer to it with the name "ref". The OLB with two parent clocks use the reference clock provided by the OLB south, east and west as "ref_100p0" and "ref_106p6" and the main oscillator as "ref". The three dividers found in the DDR OLBs are configured by the same register and are declared read-only. The reset controllers found is 11 of the OLB are declared as auxiliary device attached to the clock device. Also add the functions to parse the registers of the two types of PLL in the EyeQ7H OLB. The JFRACR PLL have similar properties as the FRACG PLL, but its configuration is spread on three registers instead of two. It also have a wider fractional part for the multiplier on 24 bits instead of 20. The AINTP PLL does not support spread spectrum and uses a single register. It is registered as a fixed factor without the flag CLK_FIXED_FACTOR_FIXED_ACCURACY and thus inherit the accuracy of its parent clock. The output of these two types of PLL is routed before the post-divisor, similar to the FRACG PLL. Signed-off-by: Benoît Monin --- drivers/clk/clk-eyeq.c | 519 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 516 insertions(+), 3 deletions(-) diff --git a/drivers/clk/clk-eyeq.c b/drivers/clk/clk-eyeq.c index 5200d5f0ff06..bf4a8421cfcc 100644 --- a/drivers/clk/clk-eyeq.c +++ b/drivers/clk/clk-eyeq.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * PLL clock driver for the Mobileye EyeQ5, EyeQ6L and EyeQ6H platforms. + * PLL clock driver for the Mobileye EyeQ platforms. * * This controller handles: * - Read-only PLLs, all derived from the same main crystal clock. @@ -46,6 +46,7 @@ #include #include +#include /* In frac mode, it enables fractional noise canceling DAC. Else, no function. */ #define FRACG_PCSR0_DAC_EN BIT(0) @@ -71,6 +72,41 @@ #define FRACG_PCSR1_DOWN_SPREAD BIT(11) #define FRACG_PCSR1_FRAC_IN GENMASK(31, 12) +#define JFRACR_PCSR0_BYPASS BIT(0) +#define JFRACR_PCSR0_PLL_EN BIT(1) +#define JFRACR_PCSR0_FOUTVCO_EN BIT(2) +#define JFRACR_PCSR0_FOUTPOSTDIV_EN BIT(3) +#define JFRACR_PCSR0_POST_DIV1 GENMASK(6, 4) +#define JFRACR_PCSR0_POST_DIV2 GENMASK(9, 7) +#define JFRACR_PCSR0_REF_DIV GENMASK(15, 10) +#define JFRACR_PCSR0_FB_DIV GENMASK(27, 16) +#define JFRACR_PCSR0_VCO_SEL GENMASK(29, 28) +#define JFRACR_PCSR0_PLL_LOCKED GENMASK(31, 30) + +#define JFRACR_PCSR1_FRAC_IN GENMASK(23, 0) +#define JFRACR_PCSR1_FOUT4PHASE_EN BIT(24) +#define JFRACR_PCSR1_DAC_EN BIT(25) +#define JFRACR_PCSR1_DSM_EN BIT(26) +/* Bits 31..27 are reserved */ +#define JFRACR_PCSR2_RESET BIT(0) +#define JFRACR_PCSR2_DIS_SSCG BIT(1) +#define JFRACR_PCSR2_DOWN_SPREAD BIT(2) +#define JFRACR_PCSR2_SSGC_DIV GENMASK(7, 4) +#define JFRACR_PCSR2_SPREAD GENMASK(12, 8) +/* Bits 31..13 are reserved */ + +#define AINTP_PCSR_BYPASS BIT(0) +#define AINTP_PCSR_PLL_EN BIT(1) +#define AINTP_PCSR_FOUTVCO_EN BIT(2) +#define AINTP_PCSR_FOUTPOSTDIV_EN BIT(3) +#define AINTP_PCSR_POST_DIV1 GENMASK(6, 4) +#define AINTP_PCSR_POST_DIV2 GENMASK(9, 7) +#define AINTP_PCSR_REF_DIV GENMASK(15, 10) +#define AINTP_PCSR_FB_DIV GENMASK(27, 16) +#define AINTP_PCSR_VCO_SEL GENMASK(29, 28) +/* bit 30 is reserved */ +#define AINTP_PCSR_PLL_LOCKED BIT(31) + /* * Special index values to lookup a parent clock by its name * from the device tree or by its globally unique name. @@ -128,8 +164,10 @@ struct eqc_early_match_data { }; /* - * Both factors (mult and div) must fit in 32 bits. When an operation overflows, - * this function throws away low bits so that factors still fit in 32 bits. + * All Mobileye EyeQ SoC have 64 bits long, but both factors (mult and div) + * must fit in 32 bits as clk_fixed_factor uses unsigned int. When an + * operation overflows, this function throws away low bits so that + * factors still fit in 32 bits. * * Precision loss depends on amplitude of mult and div. Worst theoretical * loss is: (UINT_MAX+1) / UINT_MAX - 1 = 2.3e-10. @@ -160,6 +198,29 @@ static void eqc_pll_downshift_factors(unsigned long *mult, unsigned long *div) *div >>= shift; } +static int eqc_pll_parse_aintp(void __iomem *base, unsigned long *mult, unsigned long *div) +{ + u32 r0; + + r0 = readl(base); + if (r0 & AINTP_PCSR_BYPASS) { + *mult = 1; + *div = 1; + return 0; + } + + if (!(r0 & AINTP_PCSR_PLL_LOCKED)) + return -EINVAL; + + *mult = FIELD_GET(AINTP_PCSR_FB_DIV, r0); + *div = FIELD_GET(AINTP_PCSR_REF_DIV, r0); + + if (!*mult || !*div) + return -EINVAL; + + return 0; +} + static int eqc_pll_parse_fracg(void __iomem *base, unsigned long *mult, unsigned long *div, unsigned long *acc) { @@ -229,6 +290,60 @@ static int eqc_pll_parse_fracg(void __iomem *base, unsigned long *mult, return 0; } +static int eqc_pll_parse_jfracr(void __iomem *base, unsigned long *mult, + unsigned long *div, unsigned long *acc) +{ + unsigned long spread; + u32 r0, r1, r2; + u64 val; + + val = readq(base); + r0 = val; + r1 = val >> 32; + r2 = readl(base + 8); + + if (r0 & JFRACR_PCSR0_BYPASS) { + *mult = 1; + *div = 1; + *acc = 0; + return 0; + } + + /* Consider the PLL locked if either the phase or the frequency is locked */ + if (!(r0 & JFRACR_PCSR0_PLL_LOCKED)) + return -EINVAL; + + *mult = FIELD_GET(JFRACR_PCSR0_FB_DIV, r0); + *div = FIELD_GET(JFRACR_PCSR0_REF_DIV, r0); + + /* fractional part on 24 bits */ + if (r1 & JFRACR_PCSR1_DSM_EN) { + *div *= (1ULL << 24); + *mult = *mult * (1ULL << 24) + FIELD_GET(JFRACR_PCSR1_FRAC_IN, r1); + } + + if (!*mult || !*div) + return -EINVAL; + + if (r2 & (JFRACR_PCSR2_RESET | JFRACR_PCSR2_DIS_SSCG)) { + *acc = 0; + } else { + /* spread spectrum is identical to FRACG PLL */ + spread = FIELD_GET(JFRACR_PCSR2_SPREAD, r2); + *acc = DIV_ROUND_CLOSEST(spread * 1000000000, 1024 * 2); + + if (r2 & JFRACR_PCSR2_DOWN_SPREAD) { + *mult *= 2048 - spread; + *div *= 2048; + } + } + + /* make sure mult and div fit in 32 bits */ + eqc_pll_downshift_factors(mult, div); + + return 0; +} + static void eqc_auxdev_create_optional(struct device *dev, void __iomem *base, const char *name) { @@ -313,6 +428,33 @@ static int eqc_probe_fixed_factor(struct device *dev, struct device_node *np, return 0; } +static int eqc_probe_pll_aintp(struct device *dev, struct device_node *np, + const struct eqc_clock *clk, void __iomem *base, + struct clk_hw_onecell_data *cells) +{ + struct clk_parent_data parent_data = { }; + unsigned long mult, div; + struct clk_hw *hw; + int ret; + + ret = eqc_pll_parse_aintp(base + clk->pll.reg, &mult, &div); + if (ret) + return ret; + + ret = eqc_fill_parent_data(clk, cells, &parent_data); + if (ret) + return ret; + + hw = clk_hw_register_fixed_factor_pdata(dev, np, clk->name, &parent_data, + 0, mult, div, 0, 0); + + if (IS_ERR(hw)) + return PTR_ERR(hw); + + cells->hws[clk->index] = hw; + return 0; +} + static int eqc_probe_pll_fracg(struct device *dev, struct device_node *np, const struct eqc_clock *clk, void __iomem *base, struct clk_hw_onecell_data *cells) @@ -339,6 +481,32 @@ static int eqc_probe_pll_fracg(struct device *dev, struct device_node *np, return 0; } +static int eqc_probe_pll_jfracr(struct device *dev, struct device_node *np, + const struct eqc_clock *clk, void __iomem *base, + struct clk_hw_onecell_data *cells) +{ + struct clk_parent_data parent_data = { }; + unsigned long mult, div, acc; + struct clk_hw *hw; + int ret; + + ret = eqc_pll_parse_jfracr(base + clk->pll.reg, &mult, &div, &acc); + if (ret) + return ret; + + ret = eqc_fill_parent_data(clk, cells, &parent_data); + if (ret) + return ret; + + hw = clk_hw_register_fixed_factor_pdata(dev, np, clk->name, &parent_data, 0, mult, + div, acc, CLK_FIXED_FACTOR_FIXED_ACCURACY); + if (IS_ERR(hw)) + return PTR_ERR(hw); + + cells->hws[clk->index] = hw; + return 0; +} + static int eqc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -411,6 +579,22 @@ static int eqc_probe(struct platform_device *pdev) .div.table = NULL, \ } +#define DIV_TABLE_RO(_index, _parent_idx, _name, _parent_name, \ + _reg, _shift, _width, _table) \ + { \ + .index = _index, \ + .parent_idx = _parent_idx, \ + .name = _name, \ + .parent_name = _parent_name, \ + .probe = eqc_probe_divider, \ + .unregister = clk_hw_unregister_divider, \ + .div.reg = _reg, \ + .div.shift = _shift, \ + .div.width = _width, \ + .div.flags = CLK_DIVIDER_READ_ONLY, \ + .div.table = _table, \ + } + #define FF(_index, _parent_idx, _name, _parent_name, _mult, _div) \ { \ .index = _index, \ @@ -423,6 +607,17 @@ static int eqc_probe(struct platform_device *pdev) .ff.div = _div, \ } +#define PLL_AINTP(_index, _parent_idx, _name, _parent_name, _reg) \ + { \ + .index = _index, \ + .parent_idx = _parent_idx, \ + .name = _name, \ + .parent_name = _parent_name, \ + .probe = eqc_probe_pll_aintp, \ + .unregister = clk_hw_unregister_fixed_factor, \ + .pll.reg = _reg, \ + } + #define PLL_FRACG(_index, _parent_idx, _name, _parent_name, _reg) \ { \ .index = _index, \ @@ -434,6 +629,17 @@ static int eqc_probe(struct platform_device *pdev) .pll.reg = _reg, \ } +#define PLL_JFRACR(_index, _parent_idx, _name, _parent_name, _reg) \ + { \ + .index = _index, \ + .parent_idx = _parent_idx, \ + .name = _name, \ + .parent_name = _parent_name, \ + .probe = eqc_probe_pll_jfracr, \ + .unregister = clk_hw_unregister_fixed_factor, \ + .pll.reg = _reg, \ + } + enum { /* * EQ5C_PLL_CPU children. @@ -675,6 +881,297 @@ static const struct eqc_match_data eqc_eyeq6h_acc_match_data = { .reset_auxdev_name = "reset_acc", }; +static const struct eqc_clock eqc_eyeq7h_acc0_clks[] = { + PLL_AINTP(EQ7HC_ACC_PLL_VMP, PARENT_BY_FWNAME, "pll-acc0-vmp", "ref_100p0", 0x400), + PLL_AINTP(EQ7HC_ACC_PLL_MPC, PARENT_BY_FWNAME, "pll-acc0-mpc", "ref_100p0", 0x404), + PLL_AINTP(EQ7HC_ACC_PLL_PMA, PARENT_BY_FWNAME, "pll-acc0-pma", "ref_100p0", 0x408), + PLL_AINTP(EQ7HC_ACC_PLL_NOC, PARENT_BY_FWNAME, "pll-acc0-noc-acc", "ref_106p6", 0x40c), + + FF(EQ7HC_ACC_DIV_PMA, EQ7HC_ACC_PLL_PMA, "acc0_pma", NULL, 1, 2), + FF(EQ7HC_ACC_DIV_NCORE, EQ7HC_ACC_PLL_NOC, "acc0_ncore", NULL, 1, 2), + FF(EQ7HC_ACC_DIV_CFG, EQ7HC_ACC_PLL_NOC, "acc0_cfg", NULL, 1, 8), +}; + +static const struct eqc_match_data eqc_eyeq7h_acc0_match_data = { + .clk_count = ARRAY_SIZE(eqc_eyeq7h_acc0_clks), + .clks = eqc_eyeq7h_acc0_clks, + + .reset_auxdev_name = "reset_acc0", +}; + +static const struct eqc_clock eqc_eyeq7h_acc1_clks[] = { + PLL_AINTP(EQ7HC_ACC_PLL_VMP, PARENT_BY_FWNAME, "pll-acc1-vmp", "ref_100p0", 0x400), + PLL_AINTP(EQ7HC_ACC_PLL_MPC, PARENT_BY_FWNAME, "pll-acc1-mpc", "ref_100p0", 0x404), + PLL_AINTP(EQ7HC_ACC_PLL_PMA, PARENT_BY_FWNAME, "pll-acc1-pma", "ref_100p0", 0x408), + PLL_AINTP(EQ7HC_ACC_PLL_NOC, PARENT_BY_FWNAME, "pll-acc1-noc-acc", "ref_106p6", 0x40c), +}; + +static const struct eqc_match_data eqc_eyeq7h_acc1_match_data = { + .clk_count = ARRAY_SIZE(eqc_eyeq7h_acc1_clks), + .clks = eqc_eyeq7h_acc1_clks, + + .reset_auxdev_name = "reset_acc1", +}; + +static const struct clk_div_table eqc_eyeq7h_ddr_apb_div_table[] = { + { .val = 0, .div = 8 }, + { .val = 1, .div = 128 }, + { .val = 0, .div = 0 }, +}; + +static const struct clk_div_table eqc_eyeq7h_ddr_ref_div_table[] = { + { .val = 0, .div = 2 }, + { .val = 1, .div = 8 }, + { .val = 0, .div = 0 }, +}; + +static const struct clk_div_table eqc_eyeq7h_ddr_dfi_div_table[] = { + { .val = 0, .div = 2 }, + { .val = 1, .div = 32 }, + { .val = 0, .div = 0 }, +}; + +static const struct eqc_clock eqc_eyeq7h_ddr0_clks[] = { + PLL_AINTP(EQ7HC_DDR_PLL, PARENT_BY_FWNAME, "pll-ddr0", "ref", 0x0), + + /* A single bit configures the 3 dividers below */ + DIV_TABLE_RO(EQ7HC_DDR_DIV_APB, EQ7HC_DDR_PLL, "div-ddr0_apb", NULL, + 0x08, 10, 1, eqc_eyeq7h_ddr_apb_div_table), + DIV_TABLE_RO(EQ7HC_DDR_DIV_PLLREF, EQ7HC_DDR_PLL, "div-ddr0_pllref", NULL, + 0x08, 10, 1, eqc_eyeq7h_ddr_ref_div_table), + DIV_TABLE_RO(EQ7HC_DDR_DIV_DFI, EQ7HC_DDR_PLL, "div-ddr0-dfi", NULL, + 0x08, 10, 1, eqc_eyeq7h_ddr_dfi_div_table), +}; + +static const struct eqc_match_data eqc_eyeq7h_ddr0_match_data = { + .clk_count = ARRAY_SIZE(eqc_eyeq7h_ddr0_clks), + .clks = eqc_eyeq7h_ddr0_clks, + + .reset_auxdev_name = "reset_ddr0", +}; + +static const struct eqc_clock eqc_eyeq7h_ddr1_clks[] = { + PLL_AINTP(EQ7HC_DDR_PLL, PARENT_BY_FWNAME, "pll-ddr1", "ref", 0x0), + + /* A single bit configures the 3 dividers below */ + DIV_TABLE_RO(EQ7HC_DDR_DIV_APB, EQ7HC_DDR_PLL, "div-ddr1_apb", NULL, + 0x08, 10, 1, eqc_eyeq7h_ddr_apb_div_table), + DIV_TABLE_RO(EQ7HC_DDR_DIV_PLLREF, EQ7HC_DDR_PLL, "div-ddr1_pllref", NULL, + 0x08, 10, 1, eqc_eyeq7h_ddr_ref_div_table), + DIV_TABLE_RO(EQ7HC_DDR_DIV_DFI, EQ7HC_DDR_PLL, "div-ddr1-dfi", NULL, + 0x08, 10, 1, eqc_eyeq7h_ddr_dfi_div_table), +}; + +static const struct eqc_match_data eqc_eyeq7h_ddr1_match_data = { + .clk_count = ARRAY_SIZE(eqc_eyeq7h_ddr1_clks), + .clks = eqc_eyeq7h_ddr1_clks, + + .reset_auxdev_name = "reset_ddr1", +}; + +static const struct eqc_clock eqc_eyeq7h_east_clocks[] = { + PLL_JFRACR(EQ7HC_EAST_PLL_106P6, PARENT_BY_FWNAME, "pll-106p6-e", "ref", 0x00), + + FF(EQ7HC_EAST_DIV_REF_106P6, EQ7HC_EAST_PLL_106P6, "ref_106p6_e", NULL, 1, 40), + + PLL_AINTP(EQ7HC_EAST_PLL_NOC, EQ7HC_EAST_DIV_REF_106P6, "pll-noc-e", NULL, 0x30), + PLL_AINTP(EQ7HC_EAST_PLL_ISP, PARENT_BY_FWNAME, "pll-isp", "ref_100p0", 0x38), + PLL_AINTP(EQ7HC_EAST_PLL_VEU, PARENT_BY_FWNAME, "pll-veu", "ref_100p0", 0x40), + + FF(EQ7HC_EAST_DIV_REF_DDR_PHY, EQ7HC_EAST_PLL_106P6, "ref_ddr_phy_e", NULL, 1, 2), + + FF(EQ7HC_EAST_DIV_CORE, EQ7HC_EAST_PLL_NOC, "core_e", NULL, 1, 2), + FF(EQ7HC_EAST_DIV_CORE_MBIST, EQ7HC_EAST_PLL_NOC, "core_mbist_e", NULL, 1, 2), + FF(EQ7HC_EAST_DIV_ISRAM_MBIST, EQ7HC_EAST_PLL_NOC, "isram_mbist_e", NULL, 1, 2), + FF(EQ7HC_EAST_DIV_CFG, EQ7HC_EAST_PLL_NOC, "cfg_e", NULL, 1, 4), + + FF(EQ7HC_EAST_DIV_VEU_CORE, EQ7HC_EAST_PLL_VEU, "veu_core", NULL, 1, 4), + FF(EQ7HC_EAST_DIV_VEU_MBIST, EQ7HC_EAST_PLL_VEU, "veu_mbist", NULL, 1, 4), + FF(EQ7HC_EAST_DIV_VEU_OCP, EQ7HC_EAST_PLL_VEU, "veu_ocp", NULL, 1, 16), + + FF(EQ7HC_EAST_DIV_LBITS, EQ7HC_EAST_PLL_ISP, "lbits_e", NULL, 1, 48), + FF(EQ7HC_EAST_DIV_ISP0_CORE, EQ7HC_EAST_PLL_ISP, "isp0_core", NULL, 1, 2), +}; + +static const struct eqc_match_data eqc_eyeq7h_east_match_data = { + .clk_count = ARRAY_SIZE(eqc_eyeq7h_east_clocks), + .clks = eqc_eyeq7h_east_clocks, + + .reset_auxdev_name = "reset_east", +}; + +static const struct eqc_clock eqc_eyeq7h_mips0_clks[] = { + PLL_AINTP(EQ7HC_MIPS_PLL_CPU, PARENT_BY_FWNAME, "pll-cpu0", "ref", 0x0), + + FF(EQ7HC_MIPS_DIV_CM, EQ7HC_MIPS_PLL_CPU, "mips0_cm", NULL, 1, 2), +}; + +static const struct eqc_match_data eqc_eyeq7h_mips0_match_data = { + .clk_count = ARRAY_SIZE(eqc_eyeq7h_mips0_clks), + .clks = eqc_eyeq7h_mips0_clks, +}; + +static const struct eqc_clock eqc_eyeq7h_mips1_clks[] = { + PLL_AINTP(EQ7HC_MIPS_PLL_CPU, PARENT_BY_FWNAME, "pll-cpu1", "ref", 0x0), + + FF(EQ7HC_MIPS_DIV_CM, EQ7HC_MIPS_PLL_CPU, "mips1_cm", NULL, 1, 2), +}; + +static const struct eqc_match_data eqc_eyeq7h_mips1_match_data = { + .clk_count = ARRAY_SIZE(eqc_eyeq7h_mips1_clks), + .clks = eqc_eyeq7h_mips1_clks, +}; + +static const struct eqc_clock eqc_eyeq7h_mips2_clks[] = { + PLL_AINTP(EQ7HC_MIPS_PLL_CPU, PARENT_BY_FWNAME, "pll-cpu2", "ref", 0x0), + + FF(EQ7HC_MIPS_DIV_CM, EQ7HC_MIPS_PLL_CPU, "mips2_cm", NULL, 1, 2), +}; + +static const struct eqc_match_data eqc_eyeq7h_mips2_match_data = { + .clk_count = ARRAY_SIZE(eqc_eyeq7h_mips2_clks), + .clks = eqc_eyeq7h_mips2_clks, +}; + +static const struct eqc_clock eqc_eyeq7h_periph_east_clks[] = { + PLL_AINTP(EQ7HC_PERIPH_EAST_PLL_PER, PARENT_BY_FWNAME, "pll-periph_east_per", "ref", 0x0), + + FF(EQ7HC_PERIPH_EAST_DIV_PER, EQ7HC_PERIPH_EAST_PLL_PER, "periph_e", NULL, 1, 10), +}; + +static const struct eqc_match_data eqc_eyeq7h_periph_east_match_data = { + .clk_count = ARRAY_SIZE(eqc_eyeq7h_periph_east_clks), + .clks = eqc_eyeq7h_periph_east_clks, + + .reset_auxdev_name = "reset_periph_east", +}; + +static const struct eqc_clock eqc_eyeq7h_periph_west_clks[] = { + PLL_AINTP(EQ7HC_PERIPH_WEST_PLL_PER, PARENT_BY_FWNAME, + "pll-periph_west_per", "ref_100p0", 0x0), + PLL_AINTP(EQ7HC_PERIPH_WEST_PLL_I2S, PARENT_BY_FWNAME, + "pll-periph_west_i2s", "ref_106p6", 0x4), + + FF(EQ7HC_PERIPH_WEST_DIV_PER, EQ7HC_PERIPH_WEST_PLL_PER, "periph_w", NULL, 1, 10), + FF(EQ7HC_PERIPH_WEST_DIV_I2S, EQ7HC_PERIPH_WEST_PLL_I2S, "periph_i2s_ser_w", NULL, 1, 100), +}; + +static const struct eqc_match_data eqc_eyeq7h_periph_west_match_data = { + .clk_count = ARRAY_SIZE(eqc_eyeq7h_periph_west_clks), + .clks = eqc_eyeq7h_periph_west_clks, + + .reset_auxdev_name = "reset_periph_west", +}; + +static const struct eqc_clock eqc_eyeq7h_south_clks[] = { + PLL_JFRACR(EQ7HC_SOUTH_PLL_100P0, PARENT_BY_FWNAME, "pll-100p0", "ref", 0x40), + + FF(EQ7HC_SOUTH_DIV_REF_100P0, EQ7HC_SOUTH_PLL_100P0, "ref_100p0", NULL, 1, 48), + + PLL_AINTP(EQ7HC_SOUTH_PLL_XSPI, EQ7HC_SOUTH_DIV_REF_100P0, "pll-xspi", NULL, 0x10), + PLL_AINTP(EQ7HC_SOUTH_PLL_VDIO, EQ7HC_SOUTH_DIV_REF_100P0, "pll-vdio", NULL, 0x18), + PLL_AINTP(EQ7HC_SOUTH_PLL_PER, EQ7HC_SOUTH_DIV_REF_100P0, "pll-per-s", NULL, 0x20), + + FF(EQ7HC_SOUTH_DIV_VDO_DSI_SYS, EQ7HC_SOUTH_PLL_100P0, "vdo_dsi_sys", NULL, 1, 9), + FF(EQ7HC_SOUTH_DIV_PMA_CMN_REF, EQ7HC_SOUTH_PLL_100P0, "pma_cmn_ref", NULL, 1, 48), + FF(EQ7HC_SOUTH_DIV_REF_UFS, EQ7HC_SOUTH_PLL_100P0, "ref_ufs", NULL, 1, 250), + FF(EQ7HC_SOUTH_DIV_XSPI_SYS, EQ7HC_SOUTH_PLL_XSPI, "xspi_sys", NULL, 1, 8), + FF(EQ7HC_SOUTH_DIV_XSPI_MBIST, EQ7HC_SOUTH_PLL_XSPI, "xspi_mbist", NULL, 1, 8), + FF(EQ7HC_SOUTH_DIV_NOC_S, EQ7HC_SOUTH_PLL_PER, "noc_s", NULL, 1, 2), + FF(EQ7HC_SOUTH_DIV_PCIE_SYS, EQ7HC_SOUTH_PLL_PER, "pcie_sys", NULL, 1, 4), + FF(EQ7HC_SOUTH_DIV_PCIE_SYS_MBIST, EQ7HC_SOUTH_PLL_PER, "pcie_sys_mbist", NULL, 1, 4), + FF(EQ7HC_SOUTH_DIV_PCIE_GBE_PHY, EQ7HC_SOUTH_PLL_PER, "pcie_gbe_phy_apb", NULL, 1, 16), + FF(EQ7HC_SOUTH_DIV_UFS_CORE, EQ7HC_SOUTH_PLL_PER, "ufs_core", NULL, 1, 8), + FF(EQ7HC_SOUTH_DIV_UFS_SMS, EQ7HC_SOUTH_PLL_PER, "ufs_sms", NULL, 1, 5), + FF(EQ7HC_SOUTH_DIV_UFS_ROM_SMS, EQ7HC_SOUTH_PLL_PER, "ufs_rom_sms", NULL, 1, 5), + FF(EQ7HC_SOUTH_DIV_ETH_SYS, EQ7HC_SOUTH_PLL_PER, "eth_sys", NULL, 1, 8), + FF(EQ7HC_SOUTH_DIV_ETH_MBIST, EQ7HC_SOUTH_PLL_PER, "eth_mbist", NULL, 1, 8), + FF(EQ7HC_SOUTH_DIV_CFG_S, EQ7HC_SOUTH_PLL_PER, "cfg_s", NULL, 1, 8), + FF(EQ7HC_SOUTH_DIV_TSU, EQ7HC_SOUTH_PLL_PER, "tsu", NULL, 1, 64), + FF(EQ7HC_SOUTH_DIV_VDIO, EQ7HC_SOUTH_PLL_VDIO, "vdio", NULL, 1, 4), + FF(EQ7HC_SOUTH_DIV_VDIO_CORE, EQ7HC_SOUTH_PLL_VDIO, "vdio_core", NULL, 1, 4), + FF(EQ7HC_SOUTH_DIV_VDIO_CORE_MBIST, EQ7HC_SOUTH_PLL_VDIO, "vdio_core_mbist", NULL, 1, 4), + FF(EQ7HC_SOUTH_DIV_VDO_CORE_MBIST, EQ7HC_SOUTH_PLL_VDIO, "vdo_core_mbist", NULL, 1, 4), + FF(EQ7HC_SOUTH_DIV_VDO_P, EQ7HC_SOUTH_PLL_VDIO, "vdo_p", NULL, 1, 40), + FF(EQ7HC_SOUTH_DIV_VDIO_CFG, EQ7HC_SOUTH_PLL_VDIO, "vdio_cfg", NULL, 1, 150), + FF(EQ7HC_SOUTH_DIV_VDIO_TXCLKESC, EQ7HC_SOUTH_PLL_VDIO, "vdio_txclkesc", NULL, 1, 8), +}; + +static const struct eqc_match_data eqc_eyeq7h_south_match_data = { + .clk_count = ARRAY_SIZE(eqc_eyeq7h_south_clks), + .clks = eqc_eyeq7h_south_clks, + + .reset_auxdev_name = "reset_south", +}; + +static const struct eqc_clock eqc_eyeq7h_west_clks[] = { + PLL_JFRACR(EQ7HC_WEST_PLL_106P6, PARENT_BY_FWNAME, "pll-106p6-w", "ref", 0x0), + + FF(EQ7HC_WEST_DIV_REF_106P6, EQ7HC_WEST_PLL_106P6, "ref_106p6_w", NULL, 1, 40), + + PLL_AINTP(EQ7HC_WEST_PLL_NOC, EQ7HC_WEST_DIV_REF_106P6, "pll-noc-w", NULL, 0x30), + PLL_AINTP(EQ7HC_WEST_PLL_GPU, PARENT_BY_FWNAME, "pll-gpu", "ref_100p0", 0x38), + PLL_AINTP(EQ7HC_WEST_PLL_SSI, PARENT_BY_FWNAME, "pll-ssi", "ref_100p0", 0x40), + + FF(EQ7HC_WEST_DIV_GPU, EQ7HC_WEST_PLL_GPU, "gpu", NULL, 1, 2), + FF(EQ7HC_WEST_DIV_GPU_MBIST, EQ7HC_WEST_PLL_GPU, "gpu_mbist", NULL, 1, 2), + FF(EQ7HC_WEST_DIV_LBITS, EQ7HC_WEST_PLL_GPU, "lbits_w", NULL, 1, 40), + FF(EQ7HC_WEST_DIV_MIPS_TIMER, EQ7HC_WEST_PLL_SSI, "mips_timer", NULL, 1, 24), + FF(EQ7HC_WEST_DIV_SSI_CORE, EQ7HC_WEST_PLL_SSI, "ssi_core", NULL, 1, 2), + FF(EQ7HC_WEST_DIV_SSI_CORE_MBIST, EQ7HC_WEST_PLL_SSI, "ssi_core_mbist", NULL, 1, 2), + FF(EQ7HC_WEST_DIV_SSI_ROM, EQ7HC_WEST_PLL_SSI, "ssi_rom", NULL, 1, 8), + FF(EQ7HC_WEST_DIV_SSI_ROM_MBIST, EQ7HC_WEST_PLL_SSI, "ssi_rom_mbist", NULL, 1, 8), + FF(EQ7HC_WEST_DIV_REF_DDR_PHY, EQ7HC_WEST_PLL_106P6, "ref_ddr_phy_w", NULL, 1, 2), + FF(EQ7HC_WEST_DIV_CORE, EQ7HC_WEST_PLL_NOC, "core_w", NULL, 1, 2), + FF(EQ7HC_WEST_DIV_CORE_MBIST, EQ7HC_WEST_PLL_NOC, "core_mbist_w", NULL, 1, 2), + FF(EQ7HC_WEST_DIV_CFG, EQ7HC_WEST_PLL_NOC, "cfg_w", NULL, 1, 4), + FF(EQ7HC_WEST_DIV_CAU, EQ7HC_WEST_PLL_NOC, "cau_w", NULL, 1, 8), + FF(EQ7HC_WEST_DIV_CAU_MBIST, EQ7HC_WEST_PLL_NOC, "cau_mbist_w", NULL, 1, 8), +}; + +static const struct eqc_match_data eqc_eyeq7h_west_match_data = { + .clk_count = ARRAY_SIZE(eqc_eyeq7h_west_clks), + .clks = eqc_eyeq7h_west_clks, + + .reset_auxdev_name = "reset_west", +}; + +static const struct eqc_clock eqc_eyeq7h_xnn0_clks[] = { + PLL_AINTP(EQ7HC_XNN_PLL_XNN0, PARENT_BY_FWNAME, "pll-xnn0-0", "ref_100p0", 0x400), + PLL_AINTP(EQ7HC_XNN_PLL_XNN1, PARENT_BY_FWNAME, "pll-xnn0-1", "ref_100p0", 0x404), + PLL_AINTP(EQ7HC_XNN_PLL_XNN2, PARENT_BY_FWNAME, "pll-xnn0-2", "ref_100p0", 0x408), + PLL_AINTP(EQ7HC_XNN_PLL_CLSTR, PARENT_BY_FWNAME, "pll-xnn0-clstr", "ref_106p6", 0x410), + + FF(EQ7HC_XNN_DIV_XNN0, EQ7HC_XNN_PLL_XNN0, "xnn0", NULL, 1, 2), + FF(EQ7HC_XNN_DIV_XNN1, EQ7HC_XNN_PLL_XNN1, "xnn1", NULL, 1, 2), + FF(EQ7HC_XNN_DIV_XNN2, EQ7HC_XNN_PLL_XNN2, "xnn2", NULL, 1, 2), + FF(EQ7HC_XNN_DIV_CLSTR, EQ7HC_XNN_PLL_CLSTR, "xnn0_clstr", NULL, 1, 2), + FF(EQ7HC_XNN_DIV_I2, EQ7HC_XNN_PLL_CLSTR, "xnn0_i2", NULL, 1, 4), + FF(EQ7HC_XNN_DIV_I2_SMS, EQ7HC_XNN_PLL_CLSTR, "xnn0_i2_sms", NULL, 1, 4), + FF(EQ7HC_XNN_DIV_CFG, EQ7HC_XNN_PLL_CLSTR, "xnn0_cfg", NULL, 1, 8), +}; + +static const struct eqc_match_data eqc_eyeq7h_xnn0_match_data = { + .clk_count = ARRAY_SIZE(eqc_eyeq7h_xnn0_clks), + .clks = eqc_eyeq7h_xnn0_clks, + + .reset_auxdev_name = "reset_xnn0", +}; + +static const struct eqc_clock eqc_eyeq7h_xnn1_clks[] = { + PLL_AINTP(EQ7HC_XNN_PLL_XNN0, PARENT_BY_FWNAME, "pll-xnn1-0", "ref_100p0", 0x400), + PLL_AINTP(EQ7HC_XNN_PLL_XNN1, PARENT_BY_FWNAME, "pll-xnn1-1", "ref_100p0", 0x404), + PLL_AINTP(EQ7HC_XNN_PLL_XNN2, PARENT_BY_FWNAME, "pll-xnn1-2", "ref_100p0", 0x408), + PLL_AINTP(EQ7HC_XNN_PLL_CLSTR, PARENT_BY_FWNAME, "pll-xnn1-clstr", "ref_106p6", 0x410), +}; + +static const struct eqc_match_data eqc_eyeq7h_xnn1_match_data = { + .clk_count = ARRAY_SIZE(eqc_eyeq7h_xnn1_clks), + .clks = eqc_eyeq7h_xnn1_clks, + + .reset_auxdev_name = "reset_xnn1", +}; + static const struct of_device_id eqc_match_table[] = { { .compatible = "mobileye,eyeq5-olb", .data = &eqc_eyeq5_match_data }, { .compatible = "mobileye,eyeq6l-olb", .data = &eqc_eyeq6l_match_data }, @@ -685,6 +1182,22 @@ static const struct of_device_id eqc_match_table[] = { { .compatible = "mobileye,eyeq6h-ddr0-olb", .data = &eqc_eyeq6h_ddr0_match_data }, { .compatible = "mobileye,eyeq6h-ddr1-olb", .data = &eqc_eyeq6h_ddr1_match_data }, { .compatible = "mobileye,eyeq6h-acc-olb", .data = &eqc_eyeq6h_acc_match_data }, + { .compatible = "mobileye,eyeq7h-acc0-olb", .data = &eqc_eyeq7h_acc0_match_data }, + { .compatible = "mobileye,eyeq7h-acc1-olb", .data = &eqc_eyeq7h_acc1_match_data }, + { .compatible = "mobileye,eyeq7h-ddr0-olb", .data = &eqc_eyeq7h_ddr0_match_data }, + { .compatible = "mobileye,eyeq7h-ddr1-olb", .data = &eqc_eyeq7h_ddr1_match_data }, + { .compatible = "mobileye,eyeq7h-east-olb", .data = &eqc_eyeq7h_east_match_data }, + { .compatible = "mobileye,eyeq7h-mips0-olb", .data = &eqc_eyeq7h_mips0_match_data }, + { .compatible = "mobileye,eyeq7h-mips1-olb", .data = &eqc_eyeq7h_mips1_match_data }, + { .compatible = "mobileye,eyeq7h-mips2-olb", .data = &eqc_eyeq7h_mips2_match_data }, + { .compatible = "mobileye,eyeq7h-periph-east-olb", + .data = &eqc_eyeq7h_periph_east_match_data }, + { .compatible = "mobileye,eyeq7h-periph-west-olb", + .data = &eqc_eyeq7h_periph_west_match_data }, + { .compatible = "mobileye,eyeq7h-south-olb", .data = &eqc_eyeq7h_south_match_data }, + { .compatible = "mobileye,eyeq7h-west-olb", .data = &eqc_eyeq7h_west_match_data }, + { .compatible = "mobileye,eyeq7h-xnn0-olb", .data = &eqc_eyeq7h_xnn0_match_data }, + { .compatible = "mobileye,eyeq7h-xnn1-olb", .data = &eqc_eyeq7h_xnn1_match_data }, {} }; From e44580b601d44238ac615f441ce1179bc88a47ad Mon Sep 17 00:00:00 2001 From: Akhil P Oommen Date: Tue, 7 Jul 2026 03:10:19 +0530 Subject: [PATCH 0177/1328] dt-bindings: display/msm: gpu: Document Adreno 840 Adreno 840 GPU found in Kaanapali chipsets belongs to the A8x family. It is a new IP which features the new slice architecture with 3 slices, raytracing support, and the highest GMEM size seen so far on a Snapdragon mobile chipsets. Update the dt bindings documentation to describe this GPU. Acked-by: Krzysztof Kozlowski Signed-off-by: Akhil P Oommen Patchwork: https://patchwork.freedesktop.org/patch/738143/ Message-ID: <20260707-kaana-gpu-dt-v3-1-8dac9a60dd5c@oss.qualcomm.com> Signed-off-by: Rob Clark --- Documentation/devicetree/bindings/display/msm/gpu.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/display/msm/gpu.yaml b/Documentation/devicetree/bindings/display/msm/gpu.yaml index dbbd8b814189..d0b7304c17af 100644 --- a/Documentation/devicetree/bindings/display/msm/gpu.yaml +++ b/Documentation/devicetree/bindings/display/msm/gpu.yaml @@ -413,6 +413,7 @@ allOf: contains: enum: - qcom,adreno-44010000 + - qcom,adreno-44050a01 - qcom,adreno-44070001 then: properties: @@ -448,6 +449,7 @@ allOf: - qcom,adreno-43050c01 - qcom,adreno-43051401 - qcom,adreno-44010000 + - qcom,adreno-44050a01 - qcom,adreno-44070001 then: # Starting with A6xx, the clocks are usually defined in the GMU node From 1f69339da06255a7f8560b4768fff83992c03deb Mon Sep 17 00:00:00 2001 From: Aditya Sherawat Date: Fri, 10 Jul 2026 00:12:00 +0530 Subject: [PATCH 0178/1328] drm/msm/adreno: Add support for A704 GPU Adreno A704 GPU found in Shikra is an IP reuse of A702 GPU with very minimal changes. The only KMD facing difference is the chipid and the zap firmware which is specified via devicetree. Just add the new chipid to enable support for A704 GPU in Shikra. Signed-off-by: Aditya Sherawat Reviewed-by: Konrad Dybcio Reviewed-by: Dmitry Baryshkov Signed-off-by: Akhil P Oommen Patchwork: https://patchwork.freedesktop.org/patch/738931/ Message-ID: <20260710-shikra-gpu-v6-1-b388ec5dce77@oss.qualcomm.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/adreno/a6xx_catalog.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/adreno/a6xx_catalog.c b/drivers/gpu/drm/msm/adreno/a6xx_catalog.c index 3e6f409d13a2..2de3ab010135 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_catalog.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_catalog.c @@ -1454,7 +1454,7 @@ DECLARE_ADRENO_REGLIST_PIPE_LIST(a7xx_dyn_pwrup_reglist); static const struct adreno_info a7xx_gpus[] = { { - .chip_ids = ADRENO_CHIP_IDS(0x07000200), + .chip_ids = ADRENO_CHIP_IDS(0x07000200, 0x07000400), .family = ADRENO_6XX_GEN1, /* NOT a mistake! */ .fw = { [ADRENO_FW_SQE] = "a702_sqe.fw", From 9479a45e5f59c1fab40e0d43410ac7193d662624 Mon Sep 17 00:00:00 2001 From: Aditya Sherawat Date: Fri, 10 Jul 2026 00:12:01 +0530 Subject: [PATCH 0179/1328] dt-bindings: display/msm/gpu: Add support for A704 GPU Adreno A704 GPU found Shikra SoC is an IP reuse of A702 GPU with very minimal changes. Signed-off-by: Aditya Sherawat Reviewed-by: Krzysztof Kozlowski Signed-off-by: Akhil P Oommen Patchwork: https://patchwork.freedesktop.org/patch/738933/ Message-ID: <20260710-shikra-gpu-v6-2-b388ec5dce77@oss.qualcomm.com> Signed-off-by: Rob Clark --- Documentation/devicetree/bindings/display/msm/gpu.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/display/msm/gpu.yaml b/Documentation/devicetree/bindings/display/msm/gpu.yaml index d0b7304c17af..992d3de7c117 100644 --- a/Documentation/devicetree/bindings/display/msm/gpu.yaml +++ b/Documentation/devicetree/bindings/display/msm/gpu.yaml @@ -353,6 +353,7 @@ allOf: - qcom,adreno-610.0 - qcom,adreno-619.1 - qcom,adreno-07000200 + - qcom,adreno-07000400 then: properties: clocks: From 504a65121cf15b07c9af9f04e0ec723db531c2fa Mon Sep 17 00:00:00 2001 From: Bibek Kumar Patro Date: Fri, 10 Jul 2026 00:12:02 +0530 Subject: [PATCH 0180/1328] dt-bindings: arm-smmu: Document GPU SMMU for Shikra SoC Add specific compatible strings to document the GPU SMMU present in the Shikra SoC. Signed-off-by: Bibek Kumar Patro Reviewed-by: Krzysztof Kozlowski Signed-off-by: Akhil P Oommen Patchwork: https://patchwork.freedesktop.org/patch/738935/ Message-ID: <20260710-shikra-gpu-v6-3-b388ec5dce77@oss.qualcomm.com> Signed-off-by: Rob Clark --- Documentation/devicetree/bindings/iommu/arm,smmu.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/iommu/arm,smmu.yaml b/Documentation/devicetree/bindings/iommu/arm,smmu.yaml index a701dec2fa0a..ad15fda5c25e 100644 --- a/Documentation/devicetree/bindings/iommu/arm,smmu.yaml +++ b/Documentation/devicetree/bindings/iommu/arm,smmu.yaml @@ -108,6 +108,7 @@ properties: - qcom,sc7280-smmu-500 - qcom,sc8180x-smmu-500 - qcom,sc8280xp-smmu-500 + - qcom,shikra-smmu-500 - qcom,sm6115-smmu-500 - qcom,sm6125-smmu-500 - qcom,sm8150-smmu-500 @@ -543,6 +544,7 @@ allOf: - enum: - qcom,milos-smmu-500 - qcom,sar2130p-smmu-500 + - qcom,shikra-smmu-500 - qcom,sm8550-smmu-500 - qcom,sm8650-smmu-500 - qcom,x1e80100-smmu-500 From 01bcc0398f43099acb407a6067481e635c3e1b84 Mon Sep 17 00:00:00 2001 From: Puranam V G Tejaswi Date: Sat, 18 Jul 2026 02:11:26 +0530 Subject: [PATCH 0181/1328] drm/msm/a6xx: Fix RBBM_CLOCK_CNTL3_TP0 value in a730_hwcg The RBBM_CLOCK_CNTL3_TP0 entry in a730_hwcg has bits[19:16] set to 2 (clock gating enabled for that TP0 stage). As per the latest recommendation, clear this nibble to disable clock gating for this particular stage. Fixes: 9588d2f860a4 ("drm/msm/a6xx: Add A730 support") Signed-off-by: Puranam V G Tejaswi Reviewed-by: Konrad Dybcio Signed-off-by: Akhil P Oommen Patchwork: https://patchwork.freedesktop.org/patch/740955/ Message-ID: <20260718-eliza-gpu-v2-1-64379dbebd7a@oss.qualcomm.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/adreno/a6xx_catalog.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/adreno/a6xx_catalog.c b/drivers/gpu/drm/msm/adreno/a6xx_catalog.c index 2de3ab010135..8f74b2558035 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_catalog.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_catalog.c @@ -1199,7 +1199,7 @@ static const struct adreno_reglist a730_hwcg[] = { { REG_A6XX_RBBM_CLOCK_DELAY_SP0, 0x00000080 }, { REG_A6XX_RBBM_CLOCK_CNTL_TP0, 0x22222220 }, { REG_A6XX_RBBM_CLOCK_CNTL2_TP0, 0x22222222 }, - { REG_A6XX_RBBM_CLOCK_CNTL3_TP0, 0x22222222 }, + { REG_A6XX_RBBM_CLOCK_CNTL3_TP0, 0x22220222 }, { REG_A6XX_RBBM_CLOCK_CNTL4_TP0, 0x00222222 }, { REG_A6XX_RBBM_CLOCK_HYST_TP0, 0x77777777 }, { REG_A6XX_RBBM_CLOCK_HYST2_TP0, 0x77777777 }, From 352116dbc4fc61ae22eae748da36e46a3f45f65c Mon Sep 17 00:00:00 2001 From: Akhil P Oommen Date: Sat, 18 Jul 2026 02:11:27 +0530 Subject: [PATCH 0182/1328] drm/msm/a6xx: Rename GBIF_CX_CONFIG to a A6XX- variant register The GBIF_CX_CONFIG register exists on GPUs prior to A8XX (it is used on A722, for example), so it should be tagged as an A6XX variant to match the register spec. Widen its variant range from "A8XX-" to "A6XX-" in the register XML and rename the generated macro accordingly at all existing usage sites. Reviewed-by: Dmitry Baryshkov Reviewed-by: Konrad Dybcio Signed-off-by: Akhil P Oommen Patchwork: https://patchwork.freedesktop.org/patch/740957/ Message-ID: <20260718-eliza-gpu-v2-2-64379dbebd7a@oss.qualcomm.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/adreno/a6xx_catalog.c | 2 +- drivers/gpu/drm/msm/adreno/a6xx_gmu.c | 2 +- drivers/gpu/drm/msm/adreno/a8xx_gpu.c | 2 +- drivers/gpu/drm/msm/registers/adreno/a6xx.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/msm/adreno/a6xx_catalog.c b/drivers/gpu/drm/msm/adreno/a6xx_catalog.c index 8f74b2558035..b89da68145bc 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_catalog.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_catalog.c @@ -2180,7 +2180,7 @@ static const struct adreno_reglist a840_gbif[] = { { REG_A6XX_GBIF_QSB_SIDE1, 0x00071e20 }, { REG_A6XX_GBIF_QSB_SIDE2, 0x00071e20 }, { REG_A6XX_GBIF_QSB_SIDE3, 0x00071e20 }, - { REG_A8XX_GBIF_CX_CONFIG, 0x20023000 }, + { REG_A6XX_GBIF_CX_CONFIG, 0x20023000 }, { }, }; diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gmu.c b/drivers/gpu/drm/msm/adreno/a6xx_gmu.c index a2f6918c4f7f..a5a5f3534fc0 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gmu.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_gmu.c @@ -1032,7 +1032,7 @@ static int a6xx_gmu_fw_start(struct a6xx_gmu *gmu, unsigned int state) gpu_write(gpu, gbif_cx[i].offset, gbif_cx[i].value); if (adreno_is_a8xx(adreno_gpu)) { - gpu_write(gpu, REG_A8XX_GBIF_CX_CONFIG, 0x20023000); + gpu_write(gpu, REG_A6XX_GBIF_CX_CONFIG, 0x20023000); gmu_write(gmu, REG_A6XX_GMU_MRC_GBIF_QOS_CTRL, 0x33); } diff --git a/drivers/gpu/drm/msm/adreno/a8xx_gpu.c b/drivers/gpu/drm/msm/adreno/a8xx_gpu.c index 0f6fd35bd587..add93c1fa039 100644 --- a/drivers/gpu/drm/msm/adreno/a8xx_gpu.c +++ b/drivers/gpu/drm/msm/adreno/a8xx_gpu.c @@ -228,7 +228,7 @@ static void a8xx_set_hwcg(struct msm_gpu *gpu, bool state) * GMU enables clk gating in GBIF during boot up. So, * override that here when hwcg feature is disabled */ - gpu_rmw(gpu, REG_A8XX_GBIF_CX_CONFIG, BIT(0), 0); + gpu_rmw(gpu, REG_A6XX_GBIF_CX_CONFIG, BIT(0), 0); } } diff --git a/drivers/gpu/drm/msm/registers/adreno/a6xx.xml b/drivers/gpu/drm/msm/registers/adreno/a6xx.xml index 3349c01646e1..69dd0446f8d2 100644 --- a/drivers/gpu/drm/msm/registers/adreno/a6xx.xml +++ b/drivers/gpu/drm/msm/registers/adreno/a6xx.xml @@ -1268,7 +1268,7 @@ by a particular renderpass/blit. - + From a25fd4227f59b9147ae6d23336878c28e5437a4d Mon Sep 17 00:00:00 2001 From: Puranam V G Tejaswi Date: Sat, 18 Jul 2026 02:11:28 +0530 Subject: [PATCH 0183/1328] drm/msm/a6xx: Add Adreno 722 support Add support for Adreno A722, a member of the GEN1 A7xx family. It is derived from A730 and shares the same IP-level configurations: HWCG registers, protected registers, GBIF CX registers and gmu_cgc_mode. Major differences include lower cache/core counts, 1MB GMEM, no Concurrent Binning & LPAC support. Some of the peripheral blocks like RSCC are from A740 that resulted in updates to RSC layout. Add a new entry to the catalog to describe the usual configuration and few additional fixup mainly due to missing CB/LPAC features and updated RSC layout. Signed-off-by: Puranam V G Tejaswi Signed-off-by: Akhil P Oommen Patchwork: https://patchwork.freedesktop.org/patch/740959/ Message-ID: <20260718-eliza-gpu-v2-3-64379dbebd7a@oss.qualcomm.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/adreno/a6xx_catalog.c | 34 ++ drivers/gpu/drm/msm/adreno/a6xx_gmu.c | 15 +- drivers/gpu/drm/msm/adreno/a6xx_gpu.c | 4 +- drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c | 81 +++- drivers/gpu/drm/msm/adreno/a6xx_gpu_state.h | 5 + .../msm/adreno/adreno_gen7_17_0_snapshot.h | 428 ++++++++++++++++++ drivers/gpu/drm/msm/adreno/adreno_gpu.h | 5 + 7 files changed, 549 insertions(+), 23 deletions(-) create mode 100644 drivers/gpu/drm/msm/adreno/adreno_gen7_17_0_snapshot.h diff --git a/drivers/gpu/drm/msm/adreno/a6xx_catalog.c b/drivers/gpu/drm/msm/adreno/a6xx_catalog.c index b89da68145bc..90a0c7ffb3c8 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_catalog.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_catalog.c @@ -1500,6 +1500,40 @@ static const struct adreno_info a7xx_gpus[] = { .gmu_cgc_mode = 0x00020000, }, .preempt_record_size = 2860 * SZ_1K, + }, { + .chip_ids = ADRENO_CHIP_IDS(0x43020100), + .family = ADRENO_7XX_GEN1, + .fw = { + [ADRENO_FW_SQE] = "qcom/gen71700_sqe.fw", + [ADRENO_FW_GMU] = "qcom/gen71700_gmu.bin", + }, + .gmem = SZ_1M, + .inactive_period = DRM_MSM_INACTIVE_PERIOD, + .quirks = ADRENO_QUIRK_HAS_CACHED_COHERENT | + ADRENO_QUIRK_HAS_HW_APRIV | + ADRENO_QUIRK_PREEMPTION, + .funcs = &a7xx_gpu_funcs, + .a6xx = &(const struct a6xx_info) { + .hwcg = a730_hwcg, + .protect = &a730_protect, + .pwrup_reglist = &a7xx_pwrup_reglist, + .dyn_pwrup_reglist = &a7xx_dyn_pwrup_reglist, + .gbif_cx = a640_gbif, + .gmu_chipid = 0x07110000, + .gmu_cgc_mode = 0x00020000, + .bcms = (const struct a6xx_bcm[]) { + { .name = "SH0", .buswidth = 16 }, + { .name = "MC0", .buswidth = 4 }, + { + .name = "ACV", + .fixed = true, + .perfmode = BIT(3), + .perfmode_bw = 16500000, + }, + { /* sentinel */ }, + }, + }, + .preempt_record_size = 1536 * SZ_1K, }, { .chip_ids = ADRENO_CHIP_IDS(0x43050a01), /* "C510v2" */ .family = ADRENO_7XX_GEN2, diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gmu.c b/drivers/gpu/drm/msm/adreno/a6xx_gmu.c index a5a5f3534fc0..e97d8d8ee65a 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gmu.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_gmu.c @@ -710,7 +710,8 @@ static void a6xx_gmu_rpmh_init(struct a6xx_gmu *gmu) gmu_write_rscc(gmu, REG_A6XX_RSCC_HIDDEN_TCS_CMD0_DATA + 2, 0); gmu_write_rscc(gmu, REG_A6XX_RSCC_HIDDEN_TCS_CMD0_ADDR + 2, 0); gmu_write_rscc(gmu, REG_A6XX_RSCC_HIDDEN_TCS_CMD0_DATA + 4, - adreno_is_a740_family(adreno_gpu) ? 0x80000021 : 0x80000000); + (adreno_is_a740_family(adreno_gpu) || + adreno_is_a722(adreno_gpu)) ? 0x80000021 : 0x80000000); gmu_write_rscc(gmu, REG_A6XX_RSCC_HIDDEN_TCS_CMD0_ADDR + 4, 0); gmu_write_rscc(gmu, REG_A6XX_RSCC_OVERRIDE_START_ADDR, 0); gmu_write_rscc(gmu, REG_A6XX_RSCC_PDC_SEQ_START_ADDR, 0x4520); @@ -718,7 +719,7 @@ static void a6xx_gmu_rpmh_init(struct a6xx_gmu *gmu) gmu_write_rscc(gmu, REG_A6XX_RSCC_PDC_MATCH_VALUE_HI, 0x4514); /* The second spin of A7xx GPUs messed with some register offsets.. */ - if (adreno_is_a740_family(adreno_gpu)) + if (adreno_is_a740_family(adreno_gpu) || adreno_is_a722(adreno_gpu)) seqmem0_drv0_reg = REG_A7XX_RSCC_SEQ_MEM_0_DRV0_A740; /* Load RSC sequencer uCode for sleep and wakeup */ @@ -1034,7 +1035,9 @@ static int a6xx_gmu_fw_start(struct a6xx_gmu *gmu, unsigned int state) if (adreno_is_a8xx(adreno_gpu)) { gpu_write(gpu, REG_A6XX_GBIF_CX_CONFIG, 0x20023000); gmu_write(gmu, REG_A6XX_GMU_MRC_GBIF_QOS_CTRL, 0x33); - } + } else if (adreno_is_a722(adreno_gpu)) + gpu_rmw(gpu, REG_A6XX_GBIF_CX_CONFIG, GENMASK(31, 29), + FIELD_PREP(GENMASK(31, 29), 2)); /* Set up the lowest idle level on the GMU */ a6xx_gmu_power_config(gmu); @@ -1087,7 +1090,8 @@ static void a6xx_gmu_rpmh_off(struct a6xx_gmu *gmu) u32 val, seqmem_off = 0; /* The second spin of A7xx GPUs messed with some register offsets.. */ - if (adreno_is_a740_family(adreno_gpu) || adreno_is_a8xx(adreno_gpu)) + if (adreno_is_a740_family(adreno_gpu) || adreno_is_a722(adreno_gpu) || + adreno_is_a8xx(adreno_gpu)) seqmem_off = 4; /* Make sure there are no outstanding RPMh votes */ @@ -1100,7 +1104,8 @@ static void a6xx_gmu_rpmh_off(struct a6xx_gmu *gmu) gmu_poll_timeout_rscc(gmu, REG_A6XX_RSCC_TCS3_DRV0_STATUS + seqmem_off, val, (val & 1), 100, 1000); - if (!adreno_is_a740_family(adreno_gpu) && !adreno_is_a8xx(adreno_gpu)) + if (!adreno_is_a740_family(adreno_gpu) && !adreno_is_a722(adreno_gpu) && + !adreno_is_a8xx(adreno_gpu)) return; gmu_poll_timeout_rscc(gmu, REG_A7XX_RSCC_TCS4_DRV0_STATUS + seqmem_off, diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gpu.c b/drivers/gpu/drm/msm/adreno/a6xx_gpu.c index e293b4ca808a..2c5faee881b3 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gpu.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_gpu.c @@ -1273,7 +1273,8 @@ static int hw_init(struct msm_gpu *gpu) if (!(adreno_is_a650_family(adreno_gpu) || adreno_is_a702(adreno_gpu) || adreno_is_a730(adreno_gpu))) { - gmem_range_min = adreno_is_a740_family(adreno_gpu) ? SZ_16M : SZ_1M; + gmem_range_min = (adreno_is_a740_family(adreno_gpu) || + adreno_is_a722(adreno_gpu)) ? SZ_16M : SZ_1M; /* Set the GMEM VA range [0x100000:0x100000 + gpu->gmem - 1] */ gpu_write64(gpu, REG_A6XX_UCHE_GMEM_RANGE_MIN, gmem_range_min); @@ -1338,6 +1339,7 @@ static int hw_init(struct msm_gpu *gpu) /* Enable fault detection */ if (adreno_is_a612(adreno_gpu) || + adreno_is_a722(adreno_gpu) || adreno_is_a730(adreno_gpu) || adreno_is_a740_family(adreno_gpu)) gpu_write(gpu, REG_A6XX_RBBM_INTERFACE_HANG_INT_CNTL, (1 << 30) | 0xcfffff); diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c b/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c index 3ea8ff8c7404..26c17861cd5d 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c @@ -11,12 +11,14 @@ static const unsigned int *gen7_0_0_external_core_regs[] __always_unused; static const unsigned int *gen7_2_0_external_core_regs[] __always_unused; static const unsigned int *gen7_9_0_external_core_regs[] __always_unused; +static const unsigned int *gen7_17_0_external_core_regs[] __always_unused; static const struct gen7_sptp_cluster_registers gen7_9_0_sptp_clusters[] __always_unused; static const u32 gen7_9_0_cx_debugbus_blocks[] __always_unused; #include "adreno_gen7_0_0_snapshot.h" #include "adreno_gen7_2_0_snapshot.h" #include "adreno_gen7_9_0_snapshot.h" +#include "adreno_gen7_17_0_snapshot.h" struct a6xx_gpu_state_obj { const void *handle; @@ -404,8 +406,13 @@ static void a7xx_get_debugbus_blocks(struct msm_gpu *gpu, int i; if (adreno_gpu->info->family == ADRENO_7XX_GEN1) { - debugbus_blocks = gen7_0_0_debugbus_blocks; - debugbus_blocks_count = ARRAY_SIZE(gen7_0_0_debugbus_blocks); + if (adreno_is_a722(adreno_gpu)) { + debugbus_blocks = gen7_17_0_debugbus_blocks; + debugbus_blocks_count = ARRAY_SIZE(gen7_17_0_debugbus_blocks); + } else { + debugbus_blocks = gen7_0_0_debugbus_blocks; + debugbus_blocks_count = ARRAY_SIZE(gen7_0_0_debugbus_blocks); + } gbif_debugbus_blocks = a7xx_gbif_debugbus_blocks; gbif_debugbus_blocks_count = ARRAY_SIZE(a7xx_gbif_debugbus_blocks); } else if (adreno_gpu->info->family == ADRENO_7XX_GEN2) { @@ -678,8 +685,13 @@ static void a7xx_get_dbgahb_clusters(struct msm_gpu *gpu, unsigned dbgahb_clusters_size; if (adreno_gpu->info->family == ADRENO_7XX_GEN1) { - dbgahb_clusters = gen7_0_0_sptp_clusters; - dbgahb_clusters_size = ARRAY_SIZE(gen7_0_0_sptp_clusters); + if (adreno_is_a722(adreno_gpu)) { + dbgahb_clusters = gen7_17_0_sptp_clusters; + dbgahb_clusters_size = ARRAY_SIZE(gen7_17_0_sptp_clusters); + } else { + dbgahb_clusters = gen7_0_0_sptp_clusters; + dbgahb_clusters_size = ARRAY_SIZE(gen7_0_0_sptp_clusters); + } } else if (adreno_gpu->info->family == ADRENO_7XX_GEN2) { dbgahb_clusters = gen7_2_0_sptp_clusters; dbgahb_clusters_size = ARRAY_SIZE(gen7_2_0_sptp_clusters); @@ -839,8 +851,13 @@ static void a7xx_get_clusters(struct msm_gpu *gpu, unsigned clusters_size; if (adreno_gpu->info->family == ADRENO_7XX_GEN1) { - clusters = gen7_0_0_clusters; - clusters_size = ARRAY_SIZE(gen7_0_0_clusters); + if (adreno_is_a722(adreno_gpu)) { + clusters = gen7_17_0_clusters; + clusters_size = ARRAY_SIZE(gen7_17_0_clusters); + } else { + clusters = gen7_0_0_clusters; + clusters_size = ARRAY_SIZE(gen7_0_0_clusters); + } } else if (adreno_gpu->info->family == ADRENO_7XX_GEN2) { clusters = gen7_2_0_clusters; clusters_size = ARRAY_SIZE(gen7_2_0_clusters); @@ -977,8 +994,13 @@ static void a7xx_get_shaders(struct msm_gpu *gpu, int i; if (adreno_gpu->info->family == ADRENO_7XX_GEN1) { - shader_blocks = gen7_0_0_shader_blocks; - num_shader_blocks = ARRAY_SIZE(gen7_0_0_shader_blocks); + if (adreno_is_a722(adreno_gpu)) { + shader_blocks = gen7_17_0_shader_blocks; + num_shader_blocks = ARRAY_SIZE(gen7_17_0_shader_blocks); + } else { + shader_blocks = gen7_0_0_shader_blocks; + num_shader_blocks = ARRAY_SIZE(gen7_0_0_shader_blocks); + } } else if (adreno_gpu->info->family == ADRENO_7XX_GEN2) { shader_blocks = gen7_2_0_shader_blocks; num_shader_blocks = ARRAY_SIZE(gen7_2_0_shader_blocks); @@ -1379,8 +1401,13 @@ static void a7xx_get_registers(struct msm_gpu *gpu, const struct gen7_reg_list *reglist; if (adreno_gpu->info->family == ADRENO_7XX_GEN1) { - reglist = gen7_0_0_reg_list; - pre_crashdumper_regs = gen7_0_0_pre_crashdumper_gpu_registers; + if (adreno_is_a722(adreno_gpu)) { + reglist = gen7_17_0_reg_list; + pre_crashdumper_regs = gen7_9_0_pre_crashdumper_gpu_registers; + } else { + reglist = gen7_0_0_reg_list; + pre_crashdumper_regs = gen7_0_0_pre_crashdumper_gpu_registers; + } } else if (adreno_gpu->info->family == ADRENO_7XX_GEN2) { reglist = gen7_2_0_reg_list; pre_crashdumper_regs = gen7_0_0_pre_crashdumper_gpu_registers; @@ -1436,7 +1463,9 @@ static void a7xx_get_post_crashdumper_registers(struct msm_gpu *gpu, const u32 *regs; BUG_ON(adreno_gpu->info->family > ADRENO_7XX_GEN3); - regs = gen7_0_0_post_crashdumper_registers; + regs = adreno_is_a722(adreno_gpu) ? + gen7_17_0_post_crashdumper_registers : + gen7_0_0_post_crashdumper_registers; a7xx_get_ahb_gpu_registers(gpu, a6xx_state, regs, @@ -1543,19 +1572,35 @@ static void a7xx_get_indexed_registers(struct msm_gpu *gpu, { struct adreno_gpu *adreno_gpu = to_adreno_gpu(gpu); const struct a6xx_indexed_registers *indexed_regs; + const struct a6xx_indexed_registers *mempool_regs; int i, indexed_count, mempool_count; + bool concurrent_binning; - if (adreno_gpu->info->family <= ADRENO_7XX_GEN2) { + if (adreno_is_a722(adreno_gpu)) { + /* + * Eliza has no BV or LPAC SQE — skip the BV/LPAC indexed + * registers and the BV mempool + */ + indexed_regs = gen7_17_0_cp_indexed_reglist; + indexed_count = ARRAY_SIZE(gen7_17_0_cp_indexed_reglist); + mempool_regs = a7xx_cp_mempool_indexed; + mempool_count = ARRAY_SIZE(a7xx_cp_mempool_indexed); + concurrent_binning = false; + } else if (adreno_gpu->info->family <= ADRENO_7XX_GEN2) { indexed_regs = a7xx_indexed_reglist; indexed_count = ARRAY_SIZE(a7xx_indexed_reglist); + mempool_regs = a7xx_cp_bv_mempool_indexed; + mempool_count = ARRAY_SIZE(a7xx_cp_bv_mempool_indexed); + concurrent_binning = true; } else { BUG_ON(adreno_gpu->info->family != ADRENO_7XX_GEN3); indexed_regs = gen7_9_0_cp_indexed_reg_list; indexed_count = ARRAY_SIZE(gen7_9_0_cp_indexed_reg_list); + mempool_regs = a7xx_cp_bv_mempool_indexed; + mempool_count = ARRAY_SIZE(a7xx_cp_bv_mempool_indexed); + concurrent_binning = true; } - mempool_count = ARRAY_SIZE(a7xx_cp_bv_mempool_indexed); - a6xx_state->indexed_regs = state_kcalloc(a6xx_state, indexed_count + mempool_count, sizeof(*a6xx_state->indexed_regs)); @@ -1570,15 +1615,17 @@ static void a7xx_get_indexed_registers(struct msm_gpu *gpu, &a6xx_state->indexed_regs[i]); gpu_rmw(gpu, REG_A6XX_CP_CHICKEN_DBG, 0, BIT(2)); - gpu_rmw(gpu, REG_A7XX_CP_BV_CHICKEN_DBG, 0, BIT(2)); + if (concurrent_binning) + gpu_rmw(gpu, REG_A7XX_CP_BV_CHICKEN_DBG, 0, BIT(2)); /* Get the contents of the CP_BV mempool */ for (i = 0; i < mempool_count; i++) - a6xx_get_indexed_regs(gpu, a6xx_state, &a7xx_cp_bv_mempool_indexed[i], + a6xx_get_indexed_regs(gpu, a6xx_state, &mempool_regs[i], &a6xx_state->indexed_regs[indexed_count + i]); gpu_rmw(gpu, REG_A6XX_CP_CHICKEN_DBG, BIT(2), 0); - gpu_rmw(gpu, REG_A7XX_CP_BV_CHICKEN_DBG, BIT(2), 0); + if (concurrent_binning) + gpu_rmw(gpu, REG_A7XX_CP_BV_CHICKEN_DBG, BIT(2), 0); return; } diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.h b/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.h index 0a13a65f89ac..6f592394f866 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.h +++ b/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.h @@ -474,6 +474,11 @@ static const struct a6xx_indexed_registers a7xx_cp_bv_mempool_indexed[] = { REG_A7XX_CP_BV_MEM_POOL_DBG_DATA, 0x2200, NULL }, }; +static const struct a6xx_indexed_registers a7xx_cp_mempool_indexed[] = { + { "CP_MEM_POOL_DBG", REG_A6XX_CP_MEM_POOL_DBG_ADDR, + REG_A6XX_CP_MEM_POOL_DBG_DATA, 0x2200, NULL }, +}; + #define DEBUGBUS(_id, _count) { .id = _id, .name = #_id, .count = _count } static const struct a6xx_debugbus_block { diff --git a/drivers/gpu/drm/msm/adreno/adreno_gen7_17_0_snapshot.h b/drivers/gpu/drm/msm/adreno/adreno_gen7_17_0_snapshot.h new file mode 100644 index 000000000000..00a4a0fc97d2 --- /dev/null +++ b/drivers/gpu/drm/msm/adreno/adreno_gen7_17_0_snapshot.h @@ -0,0 +1,428 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) Qualcomm Innovation Center, Inc. All rights reserved. + */ +#ifndef __ADRENO_GEN7_17_0_SNAPSHOT_H +#define __ADRENO_GEN7_17_0_SNAPSHOT_H + +#include "a6xx_gpu_state.h" + +/* + * Snapshot tables for Adreno A722 (Eliza). + * Cluster sub-arrays that are identical to A730 reference gen7_0_0_* + * symbols; adreno_gen7_0_0_snapshot.h is included first in the TU. + */ + +static const u32 gen7_17_0_rscc_registers[] = { + 0x14000, 0x14034, 0x14036, 0x14036, 0x14040, 0x14042, 0x14044, 0x14045, + 0x14047, 0x14047, 0x14080, 0x14084, 0x14089, 0x1408c, 0x14091, 0x14094, + 0x14099, 0x1409c, 0x140a1, 0x140a4, 0x140a9, 0x140ac, 0x140b1, 0x140b4, + 0x140b9, 0x140bc, 0x14100, 0x14104, 0x14114, 0x14119, 0x14124, 0x14132, + 0x14154, 0x1416b, 0x14340, 0x14341, 0x14344, 0x14344, 0x14346, 0x1437c, + 0x143f0, 0x143f8, 0x143fa, 0x143fe, 0x14400, 0x14404, 0x14406, 0x1440a, + 0x1440c, 0x14410, 0x14412, 0x14416, 0x14418, 0x1441c, 0x1441e, 0x14422, + 0x14424, 0x14424, 0x14498, 0x144a0, 0x144a2, 0x144a6, 0x144a8, 0x144ac, + 0x144ae, 0x144b2, 0x144b4, 0x144b8, 0x144ba, 0x144be, 0x144c0, 0x144c4, + 0x144c6, 0x144ca, 0x144cc, 0x144cc, 0x14540, 0x14548, 0x1454a, 0x1454e, + 0x14550, 0x14554, 0x14556, 0x1455a, 0x1455c, 0x14560, 0x14562, 0x14566, + 0x14568, 0x1456c, 0x1456e, 0x14572, 0x14574, 0x14574, 0x145e8, 0x145f0, + 0x145f2, 0x145f6, 0x145f8, 0x145fc, 0x145fe, 0x14602, 0x14604, 0x14608, + 0x1460a, 0x1460e, 0x14610, 0x14614, 0x14616, 0x1461a, 0x1461c, 0x1461c, + 0x14690, 0x14698, 0x1469a, 0x1469e, 0x146a0, 0x146a4, 0x146a6, 0x146aa, + 0x146ac, 0x146b0, 0x146b2, 0x146b6, 0x146b8, 0x146bc, 0x146be, 0x146c2, + 0x146c4, 0x146c4, 0x14738, 0x14740, 0x14742, 0x14746, 0x14748, 0x1474c, + 0x1474e, 0x14752, 0x14754, 0x14758, 0x1475a, 0x1475e, 0x14760, 0x14764, + 0x14766, 0x1476a, 0x1476c, 0x1476c, 0x147e0, 0x147e8, 0x147ea, 0x147ee, + 0x147f0, 0x147f4, 0x147f6, 0x147fa, 0x147fc, 0x14800, 0x14802, 0x14806, + 0x14808, 0x1480c, 0x1480e, 0x14812, 0x14814, 0x14814, 0x14888, 0x14890, + 0x14892, 0x14896, 0x14898, 0x1489c, 0x1489e, 0x148a2, 0x148a4, 0x148a8, + 0x148aa, 0x148ae, 0x148b0, 0x148b4, 0x148b6, 0x148ba, 0x148bc, 0x148bc, + 0x14930, 0x14938, 0x1493a, 0x1493e, 0x14940, 0x14944, 0x14946, 0x1494a, + 0x1494c, 0x14950, 0x14952, 0x14956, 0x14958, 0x1495c, 0x1495e, 0x14962, + 0x14964, 0x14964, + UINT_MAX, UINT_MAX, +}; +static_assert(IS_ALIGNED(sizeof(gen7_17_0_rscc_registers), 8)); + +static const u32 gen7_17_0_cpr_registers[] = { + 0x26800, 0x26805, 0x26808, 0x2680c, 0x26814, 0x26814, 0x2681c, 0x2681c, + 0x26820, 0x26838, 0x26840, 0x26840, 0x26848, 0x26848, 0x26850, 0x26850, + 0x26880, 0x2688e, 0x26980, 0x269b0, 0x269c0, 0x269c2, 0x269c6, 0x269c8, + 0x269e0, 0x269ee, 0x269fb, 0x269ff, 0x26a02, 0x26a07, 0x26a09, 0x26a0b, + 0x26a10, 0x26b0f, 0x27440, 0x27441, 0x27444, 0x27444, 0x27480, 0x274a2, + 0x274ac, 0x274c4, 0x274c8, 0x274da, + UINT_MAX, UINT_MAX, +}; +static_assert(IS_ALIGNED(sizeof(gen7_17_0_cpr_registers), 8)); + +static const u32 gen7_17_0_gpucc_registers[] = { + 0x24000, 0x2400f, 0x24400, 0x2440f, 0x24800, 0x24805, 0x24c00, 0x24cff, + 0x25400, 0x25404, 0x25800, 0x25804, 0x25c00, 0x25c04, 0x26000, 0x26004, + 0x26400, 0x26405, 0x26414, 0x2641d, 0x2642a, 0x2642c, 0x2642e, 0x26432, + 0x26434, 0x26434, 0x26443, 0x26457, 0x26459, 0x2645d, 0x2645f, 0x26464, + 0x26477, 0x26479, 0x26489, 0x2648b, 0x2649a, 0x2649b, 0x264ad, 0x264af, + 0x264b1, 0x264b5, 0x264d6, 0x264d8, 0x264e7, 0x264e9, 0x264f9, 0x264fa, + 0x2650a, 0x2650d, 0x2651f, 0x26520, 0x2652d, 0x2652f, 0x2653e, 0x2653e, + 0x26540, 0x2654e, 0x26554, 0x26573, 0x26576, 0x26576, 0x26593, 0x26593, + 0x26600, 0x26616, 0x26620, 0x2662d, 0x26630, 0x26631, 0x26635, 0x26635, + 0x26637, 0x26637, 0x2663a, 0x2663a, 0x26642, 0x26642, 0x26656, 0x26658, + 0x2665b, 0x2665d, 0x2665f, 0x26662, + UINT_MAX, UINT_MAX, +}; +static_assert(IS_ALIGNED(sizeof(gen7_17_0_gpucc_registers), 8)); + +static const u32 *gen7_17_0_external_core_regs[] = { + gen7_17_0_gpucc_registers, + gen7_17_0_cpr_registers, +}; + +static const u32 gen7_17_0_debugbus_blocks[] = { + A7XX_DBGBUS_CP_0_0, + A7XX_DBGBUS_CP_0_1, + A7XX_DBGBUS_RBBM, + A7XX_DBGBUS_HLSQ, + A7XX_DBGBUS_UCHE_0, + A7XX_DBGBUS_TESS_BR, + A7XX_DBGBUS_PC_BR, + A7XX_DBGBUS_VFDP_BR, + A7XX_DBGBUS_VPC_BR, + A7XX_DBGBUS_TSE_BR, + A7XX_DBGBUS_RAS_BR, + A7XX_DBGBUS_VSC, + A7XX_DBGBUS_COM_0, + A7XX_DBGBUS_LRZ_BR, + A7XX_DBGBUS_UFC_0, + A7XX_DBGBUS_UFC_1, + A7XX_DBGBUS_GMU_GX, + A7XX_DBGBUS_DBGC, + A7XX_DBGBUS_GPC_BR, + A7XX_DBGBUS_LARC, + A7XX_DBGBUS_HLSQ_SPTP, + A7XX_DBGBUS_RB_0, + A7XX_DBGBUS_RB_1, + A7XX_DBGBUS_UCHE_WRAPPER, + A7XX_DBGBUS_CCU_0, + A7XX_DBGBUS_CCU_1, + A7XX_DBGBUS_VFD_BR_0, + A7XX_DBGBUS_VFD_BR_1, + A7XX_DBGBUS_VFD_BR_2, + A7XX_DBGBUS_VFD_BR_3, + A7XX_DBGBUS_USP_0, + A7XX_DBGBUS_USP_1, + A7XX_DBGBUS_TP_0, + A7XX_DBGBUS_TP_1, + A7XX_DBGBUS_TP_2, + A7XX_DBGBUS_TP_3, + A7XX_DBGBUS_USPTP_0, + A7XX_DBGBUS_USPTP_1, + A7XX_DBGBUS_USPTP_2, + A7XX_DBGBUS_USPTP_3, +}; + +static const struct gen7_sel_reg gen7_17_0_rb_rac_sel = { + .host_reg = REG_A6XX_RB_SUB_BLOCK_SEL_CNTL_HOST, + .cd_reg = REG_A6XX_RB_SUB_BLOCK_SEL_CNTL_CD, + .val = 0x0, +}; + +static const struct gen7_sel_reg gen7_17_0_rb_rbp_sel = { + .host_reg = REG_A6XX_RB_SUB_BLOCK_SEL_CNTL_HOST, + .cd_reg = REG_A6XX_RB_SUB_BLOCK_SEL_CNTL_CD, + .val = 0x9, +}; + +static const u32 gen7_17_0_post_crashdumper_registers[] = { + 0x00535, 0x00535, + UINT_MAX, UINT_MAX, +}; +static_assert(IS_ALIGNED(sizeof(gen7_17_0_post_crashdumper_registers), 8)); + +static const u32 gen7_17_0_gpu_registers[] = { + 0x00000, 0x00000, 0x00002, 0x00002, 0x00011, 0x00012, 0x00016, 0x0001b, + 0x0001f, 0x00032, 0x00038, 0x0003c, 0x00042, 0x00042, 0x00044, 0x00044, + 0x00047, 0x00047, 0x00049, 0x0004a, 0x0004c, 0x0004c, 0x00050, 0x00050, + 0x00056, 0x00056, 0x00073, 0x00075, 0x000ad, 0x000ae, 0x000b0, 0x000b0, + 0x000b4, 0x000b4, 0x000b8, 0x000b8, 0x000bc, 0x000bc, 0x000c0, 0x000c0, + 0x000c4, 0x000c4, 0x000c8, 0x000c8, 0x000cc, 0x000cc, 0x000d0, 0x000d0, + 0x000d4, 0x000d4, 0x000d8, 0x000d8, 0x000dc, 0x000dc, 0x000e0, 0x000e0, + 0x000e4, 0x000e4, 0x000e8, 0x000e8, 0x000ec, 0x000ec, 0x000f0, 0x000f0, + 0x000f4, 0x000f4, 0x000f8, 0x000f8, 0x00100, 0x00100, 0x00104, 0x0010b, + 0x0010f, 0x0011d, 0x0012f, 0x0012f, 0x00200, 0x0020d, 0x00215, 0x00243, + 0x00260, 0x00268, 0x00272, 0x00274, 0x00286, 0x00286, 0x0028a, 0x0028a, + 0x0028c, 0x0028c, 0x00300, 0x00401, 0x00500, 0x00500, 0x00507, 0x0050b, + 0x0050f, 0x0050f, 0x00511, 0x00511, 0x00533, 0x00534, 0x00540, 0x00555, + 0x00564, 0x00567, 0x00800, 0x00808, 0x00810, 0x00813, 0x00820, 0x00821, + 0x00823, 0x00827, 0x00830, 0x00834, 0x00840, 0x00841, 0x00843, 0x00847, + 0x0084f, 0x00886, 0x008a0, 0x008ab, 0x008c0, 0x008c0, 0x008c4, 0x008c5, + 0x008d0, 0x008dd, 0x008f0, 0x008f3, 0x00900, 0x00903, 0x00908, 0x00911, + 0x00928, 0x0093e, 0x00942, 0x0094d, 0x00980, 0x00984, 0x0098d, 0x0098f, + 0x009b0, 0x009b4, 0x009c2, 0x009c9, 0x009ce, 0x009d7, 0x00a00, 0x00a00, + 0x00a02, 0x00a03, 0x00a10, 0x00a4f, 0x00a67, 0x00a6c, 0x00a9c, 0x00a9f, + 0x00c00, 0x00c00, 0x00c02, 0x00c04, 0x00c06, 0x00c06, 0x00c10, 0x00cd9, + 0x00ce0, 0x00d0c, 0x00df0, 0x00df4, 0x00e01, 0x00e02, 0x00e07, 0x00e0e, + 0x00e10, 0x00e12, 0x00e17, 0x00e17, 0x00e19, 0x00e19, 0x00e1b, 0x00e2b, + 0x00e30, 0x00e32, 0x00e38, 0x00e3c, + UINT_MAX, UINT_MAX, +}; +static_assert(IS_ALIGNED(sizeof(gen7_17_0_gpu_registers), 8)); + +static const u32 gen7_17_0_dbgc_registers[] = { + 0x00600, 0x0061c, 0x0061e, 0x00634, 0x00640, 0x0065a, 0x00679, 0x0067a, + 0x00699, 0x00699, 0x0069b, 0x0069e, 0x18400, 0x1841c, 0x1841e, 0x18434, + 0x18440, 0x1845c, 0x18479, 0x1847c, 0x18580, 0x18581, + UINT_MAX, UINT_MAX, +}; +static_assert(IS_ALIGNED(sizeof(gen7_17_0_dbgc_registers), 8)); + +static const u32 gen7_17_0_noncontext_pipe_br_registers[] = { + 0x00887, 0x0088c, 0x08600, 0x08600, 0x08602, 0x08602, 0x08610, 0x0861b, + 0x08620, 0x08620, 0x08630, 0x08630, 0x08637, 0x08639, 0x08640, 0x08640, + 0x09600, 0x09600, 0x09602, 0x09603, 0x0960a, 0x09616, 0x09624, 0x0963a, + 0x09640, 0x09640, 0x09e00, 0x09e00, 0x09e02, 0x09e07, 0x09e0a, 0x09e16, + 0x09e19, 0x09e19, 0x09e1c, 0x09e1c, 0x09e20, 0x09e25, 0x09e30, 0x09e31, + 0x09e40, 0x09e51, 0x09e64, 0x09e64, 0x09e70, 0x09e72, 0x09e78, 0x09e79, + 0x09e80, 0x09fff, 0x0a600, 0x0a600, 0x0a603, 0x0a603, 0x0a610, 0x0a61f, + 0x0a630, 0x0a631, 0x0a638, 0x0a638, + UINT_MAX, UINT_MAX, +}; +static_assert(IS_ALIGNED(sizeof(gen7_17_0_noncontext_pipe_br_registers), 8)); + +static const u32 gen7_17_0_noncontext_rb_rac_pipe_br_registers[] = { + 0x08e10, 0x08e1c, 0x08e20, 0x08e25, 0x08e51, 0x08e54, + UINT_MAX, UINT_MAX, +}; +static_assert(IS_ALIGNED(sizeof(gen7_17_0_noncontext_rb_rac_pipe_br_registers), 8)); + +static const u32 gen7_17_0_noncontext_rb_rbp_pipe_br_registers[] = { + 0x08e01, 0x08e01, 0x08e04, 0x08e04, 0x08e06, 0x08e09, 0x08e0c, 0x08e0c, + 0x08e28, 0x08e28, 0x08e2c, 0x08e35, 0x08e3b, 0x08e3f, 0x08e50, 0x08e50, + 0x08e5b, 0x08e5d, 0x08e5f, 0x08e5f, 0x08e61, 0x08e61, 0x08e63, 0x08e65, + 0x08e68, 0x08e68, 0x08e70, 0x08e79, 0x08e80, 0x08e8f, + UINT_MAX, UINT_MAX, +}; +static_assert(IS_ALIGNED(sizeof(gen7_17_0_noncontext_rb_rbp_pipe_br_registers), 8)); + +static const u32 gen7_17_0_pc_cluster_fe_pipe_br_registers[] = { + 0x09800, 0x09804, 0x09806, 0x0980a, 0x09810, 0x09811, 0x09884, 0x09886, + 0x09970, 0x09972, 0x09b00, 0x09b08, + UINT_MAX, UINT_MAX, +}; +static_assert(IS_ALIGNED(sizeof(gen7_17_0_pc_cluster_fe_pipe_br_registers), 8)); + +static const u32 gen7_17_0_sp_cluster_sp_ps_pipe_lpac_hlsq_state_registers[] = { + 0x0aa40, 0x0aabf, + UINT_MAX, UINT_MAX, +}; +static_assert(IS_ALIGNED(sizeof(gen7_17_0_sp_cluster_sp_ps_pipe_lpac_hlsq_state_registers), 8)); + +static const u32 gen7_17_0_sp_cluster_sp_ps_pipe_lpac_usptp_registers[] = { + 0x0aa40, 0x0aabf, + UINT_MAX, UINT_MAX, +}; +static_assert(IS_ALIGNED(sizeof(gen7_17_0_sp_cluster_sp_ps_pipe_lpac_usptp_registers), 8)); + +static const u32 gen7_17_0_non_context_tpl1_pipe_none_usptp_registers[] = { + 0x0b602, 0x0b602, 0x0b604, 0x0b604, 0x0b608, 0x0b60c, 0x0b60f, 0x0b621, + 0x0b630, 0x0b633, + UINT_MAX, UINT_MAX, +}; +static_assert(IS_ALIGNED(sizeof(gen7_17_0_non_context_tpl1_pipe_none_usptp_registers), 8)); + +static const u32 gen7_17_0_non_context_tpl1_pipe_br_usptp_registers[] = { + 0x0b600, 0x0b600, + UINT_MAX, UINT_MAX, +}; +static_assert(IS_ALIGNED(sizeof(gen7_17_0_non_context_tpl1_pipe_br_usptp_registers), 8)); + +static const u32 gen7_17_0_tpl1_cluster_sp_vs_pipe_br_usptp_registers[] = { + 0x0b300, 0x0b307, 0x0b309, 0x0b309, 0x0b310, 0x0b310, + UINT_MAX, UINT_MAX, +}; +static_assert(IS_ALIGNED(sizeof(gen7_17_0_tpl1_cluster_sp_vs_pipe_br_usptp_registers), 8)); + +static const u32 gen7_17_0_tpl1_cluster_sp_ps_pipe_br_usptp_registers[] = { + 0x0b180, 0x0b183, 0x0b190, 0x0b195, 0x0b2c0, 0x0b2d5, 0x0b300, 0x0b307, + 0x0b309, 0x0b309, 0x0b310, 0x0b310, + UINT_MAX, UINT_MAX, +}; +static_assert(IS_ALIGNED(sizeof(gen7_17_0_tpl1_cluster_sp_ps_pipe_br_usptp_registers), 8)); + +/* No BV pipe — gen7_0_0_* sub-arrays are shared from adreno_gen7_0_0_snapshot.h */ +static struct gen7_cluster_registers gen7_17_0_clusters[] = { + { A7XX_CLUSTER_NONE, PIPE_BR, STATE_NON_CONTEXT, + gen7_17_0_noncontext_pipe_br_registers, }, + { A7XX_CLUSTER_NONE, PIPE_BR, STATE_NON_CONTEXT, + gen7_17_0_noncontext_rb_rac_pipe_br_registers, &gen7_17_0_rb_rac_sel, }, + { A7XX_CLUSTER_NONE, PIPE_BR, STATE_NON_CONTEXT, + gen7_17_0_noncontext_rb_rbp_pipe_br_registers, &gen7_17_0_rb_rbp_sel, }, + { A7XX_CLUSTER_PS, PIPE_BR, STATE_FORCE_CTXT_0, + gen7_0_0_rb_rac_cluster_ps_pipe_br_registers, &gen7_17_0_rb_rac_sel, }, + { A7XX_CLUSTER_PS, PIPE_BR, STATE_FORCE_CTXT_1, + gen7_0_0_rb_rac_cluster_ps_pipe_br_registers, &gen7_17_0_rb_rac_sel, }, + { A7XX_CLUSTER_PS, PIPE_BR, STATE_FORCE_CTXT_0, + gen7_0_0_rb_rbp_cluster_ps_pipe_br_registers, &gen7_17_0_rb_rbp_sel, }, + { A7XX_CLUSTER_PS, PIPE_BR, STATE_FORCE_CTXT_1, + gen7_0_0_rb_rbp_cluster_ps_pipe_br_registers, &gen7_17_0_rb_rbp_sel, }, + { A7XX_CLUSTER_GRAS, PIPE_BR, STATE_FORCE_CTXT_0, + gen7_0_0_gras_cluster_gras_pipe_br_registers, }, + { A7XX_CLUSTER_GRAS, PIPE_BR, STATE_FORCE_CTXT_1, + gen7_0_0_gras_cluster_gras_pipe_br_registers, }, + { A7XX_CLUSTER_FE, PIPE_BR, STATE_FORCE_CTXT_0, + gen7_17_0_pc_cluster_fe_pipe_br_registers, }, + { A7XX_CLUSTER_FE, PIPE_BR, STATE_FORCE_CTXT_1, + gen7_17_0_pc_cluster_fe_pipe_br_registers, }, + { A7XX_CLUSTER_FE, PIPE_BR, STATE_FORCE_CTXT_0, + gen7_0_0_vfd_cluster_fe_pipe_bv_registers, }, + { A7XX_CLUSTER_FE, PIPE_BR, STATE_FORCE_CTXT_1, + gen7_0_0_vfd_cluster_fe_pipe_bv_registers, }, + { A7XX_CLUSTER_FE, PIPE_BR, STATE_FORCE_CTXT_0, + gen7_0_0_vpc_cluster_fe_pipe_br_registers, }, + { A7XX_CLUSTER_FE, PIPE_BR, STATE_FORCE_CTXT_1, + gen7_0_0_vpc_cluster_fe_pipe_br_registers, }, + { A7XX_CLUSTER_PC_VS, PIPE_BR, STATE_FORCE_CTXT_0, + gen7_0_0_vpc_cluster_pc_vs_pipe_br_registers, }, + { A7XX_CLUSTER_PC_VS, PIPE_BR, STATE_FORCE_CTXT_1, + gen7_0_0_vpc_cluster_pc_vs_pipe_br_registers, }, + { A7XX_CLUSTER_VPC_PS, PIPE_BR, STATE_FORCE_CTXT_0, + gen7_0_0_vpc_cluster_vpc_ps_pipe_br_registers, }, + { A7XX_CLUSTER_VPC_PS, PIPE_BR, STATE_FORCE_CTXT_1, + gen7_0_0_vpc_cluster_vpc_ps_pipe_br_registers, }, +}; + +/* No BV pipe; 2 SPs, 2 USPTPs */ +static struct gen7_sptp_cluster_registers gen7_17_0_sptp_clusters[] = { + { A7XX_CLUSTER_NONE, A7XX_SP_NCTX_REG, PIPE_BR, 0, A7XX_HLSQ_STATE, + gen7_0_0_sp_noncontext_pipe_br_hlsq_state_registers, 0xae00 }, + { A7XX_CLUSTER_NONE, A7XX_SP_NCTX_REG, PIPE_BR, 0, A7XX_SP_TOP, + gen7_0_0_sp_noncontext_pipe_br_sp_top_registers, 0xae00 }, + { A7XX_CLUSTER_NONE, A7XX_SP_NCTX_REG, PIPE_BR, 0, A7XX_USPTP, + gen7_0_0_sp_noncontext_pipe_br_usptp_registers, 0xae00 }, + { A7XX_CLUSTER_SP_VS, A7XX_SP_CTX0_3D_CVS_REG, PIPE_BR, 0, A7XX_HLSQ_STATE, + gen7_0_0_sp_cluster_sp_vs_pipe_br_hlsq_state_registers, 0xa800 }, + { A7XX_CLUSTER_SP_VS, A7XX_SP_CTX0_3D_CVS_REG, PIPE_BR, 0, A7XX_SP_TOP, + gen7_0_0_sp_cluster_sp_vs_pipe_br_sp_top_registers, 0xa800 }, + { A7XX_CLUSTER_SP_VS, A7XX_SP_CTX0_3D_CVS_REG, PIPE_BR, 0, A7XX_USPTP, + gen7_0_0_sp_cluster_sp_vs_pipe_br_usptp_registers, 0xa800 }, + { A7XX_CLUSTER_SP_VS, A7XX_SP_CTX1_3D_CVS_REG, PIPE_BR, 1, A7XX_HLSQ_STATE, + gen7_0_0_sp_cluster_sp_vs_pipe_br_hlsq_state_registers, 0xa800 }, + { A7XX_CLUSTER_SP_VS, A7XX_SP_CTX1_3D_CVS_REG, PIPE_BR, 1, A7XX_SP_TOP, + gen7_0_0_sp_cluster_sp_vs_pipe_br_sp_top_registers, 0xa800 }, + { A7XX_CLUSTER_SP_VS, A7XX_SP_CTX1_3D_CVS_REG, PIPE_BR, 1, A7XX_USPTP, + gen7_0_0_sp_cluster_sp_vs_pipe_br_usptp_registers, 0xa800 }, + { A7XX_CLUSTER_SP_PS, A7XX_SP_CTX0_3D_CPS_REG, PIPE_BR, 0, A7XX_HLSQ_STATE, + gen7_0_0_sp_cluster_sp_ps_pipe_br_hlsq_state_registers, 0xa800 }, + { A7XX_CLUSTER_SP_PS, A7XX_SP_CTX0_3D_CPS_REG, PIPE_BR, 0, A7XX_HLSQ_DP, + gen7_0_0_sp_cluster_sp_ps_pipe_br_hlsq_dp_registers, 0xa800 }, + { A7XX_CLUSTER_SP_PS, A7XX_SP_CTX0_3D_CPS_REG, PIPE_BR, 0, A7XX_SP_TOP, + gen7_0_0_sp_cluster_sp_ps_pipe_br_sp_top_registers, 0xa800 }, + { A7XX_CLUSTER_SP_PS, A7XX_SP_CTX0_3D_CPS_REG, PIPE_BR, 0, A7XX_USPTP, + gen7_0_0_sp_cluster_sp_ps_pipe_br_usptp_registers, 0xa800 }, + { A7XX_CLUSTER_SP_PS, A7XX_SP_CTX0_3D_CPS_REG, PIPE_LPAC, 0, A7XX_HLSQ_STATE, + gen7_17_0_sp_cluster_sp_ps_pipe_lpac_hlsq_state_registers, 0xa800 }, + { A7XX_CLUSTER_SP_PS, A7XX_SP_CTX0_3D_CPS_REG, PIPE_LPAC, 0, A7XX_USPTP, + gen7_17_0_sp_cluster_sp_ps_pipe_lpac_usptp_registers, 0xa800 }, + { A7XX_CLUSTER_SP_PS, A7XX_SP_CTX1_3D_CPS_REG, PIPE_BR, 1, A7XX_HLSQ_STATE, + gen7_0_0_sp_cluster_sp_ps_pipe_br_hlsq_state_registers, 0xa800 }, + { A7XX_CLUSTER_SP_PS, A7XX_SP_CTX1_3D_CPS_REG, PIPE_BR, 1, A7XX_HLSQ_DP, + gen7_0_0_sp_cluster_sp_ps_pipe_br_hlsq_dp_registers, 0xa800 }, + { A7XX_CLUSTER_SP_PS, A7XX_SP_CTX1_3D_CPS_REG, PIPE_BR, 1, A7XX_SP_TOP, + gen7_0_0_sp_cluster_sp_ps_pipe_br_sp_top_registers, 0xa800 }, + { A7XX_CLUSTER_SP_PS, A7XX_SP_CTX1_3D_CPS_REG, PIPE_BR, 1, A7XX_USPTP, + gen7_0_0_sp_cluster_sp_ps_pipe_br_usptp_registers, 0xa800 }, + { A7XX_CLUSTER_SP_PS, A7XX_SP_CTX2_3D_CPS_REG, PIPE_BR, 2, A7XX_HLSQ_DP, + gen7_0_0_sp_cluster_sp_ps_pipe_br_hlsq_dp_registers, 0xa800 }, + { A7XX_CLUSTER_SP_PS, A7XX_SP_CTX2_3D_CPS_REG, PIPE_BR, 2, A7XX_SP_TOP, + gen7_0_0_sp_cluster_sp_ps_pipe_br_sp_top_registers, 0xa800 }, + { A7XX_CLUSTER_SP_PS, A7XX_SP_CTX2_3D_CPS_REG, PIPE_BR, 2, A7XX_USPTP, + gen7_0_0_sp_cluster_sp_ps_pipe_br_usptp_registers, 0xa800 }, + { A7XX_CLUSTER_SP_PS, A7XX_SP_CTX3_3D_CPS_REG, PIPE_BR, 3, A7XX_HLSQ_DP, + gen7_0_0_sp_cluster_sp_ps_pipe_br_hlsq_dp_registers, 0xa800 }, + { A7XX_CLUSTER_SP_PS, A7XX_SP_CTX3_3D_CPS_REG, PIPE_BR, 3, A7XX_SP_TOP, + gen7_0_0_sp_cluster_sp_ps_pipe_br_sp_top_registers, 0xa800 }, + { A7XX_CLUSTER_SP_PS, A7XX_SP_CTX3_3D_CPS_REG, PIPE_BR, 3, A7XX_USPTP, + gen7_0_0_sp_cluster_sp_ps_pipe_br_usptp_registers, 0xa800 }, + { A7XX_CLUSTER_NONE, A7XX_TP0_NCTX_REG, PIPE_NONE, 0, A7XX_USPTP, + gen7_17_0_non_context_tpl1_pipe_none_usptp_registers, 0xb600 }, + { A7XX_CLUSTER_NONE, A7XX_TP0_NCTX_REG, PIPE_BR, 0, A7XX_USPTP, + gen7_17_0_non_context_tpl1_pipe_br_usptp_registers, 0xb600 }, + { A7XX_CLUSTER_SP_VS, A7XX_TP0_CTX0_3D_CVS_REG, PIPE_BR, 0, A7XX_USPTP, + gen7_17_0_tpl1_cluster_sp_vs_pipe_br_usptp_registers, 0xb000 }, + { A7XX_CLUSTER_SP_VS, A7XX_TP0_CTX1_3D_CVS_REG, PIPE_BR, 1, A7XX_USPTP, + gen7_17_0_tpl1_cluster_sp_vs_pipe_br_usptp_registers, 0xb000 }, + { A7XX_CLUSTER_SP_PS, A7XX_TP0_CTX0_3D_CPS_REG, PIPE_BR, 0, A7XX_USPTP, + gen7_17_0_tpl1_cluster_sp_ps_pipe_br_usptp_registers, 0xb000 }, + { A7XX_CLUSTER_SP_PS, A7XX_TP0_CTX1_3D_CPS_REG, PIPE_BR, 1, A7XX_USPTP, + gen7_17_0_tpl1_cluster_sp_ps_pipe_br_usptp_registers, 0xb000 }, + { A7XX_CLUSTER_SP_PS, A7XX_TP0_CTX2_3D_CPS_REG, PIPE_BR, 2, A7XX_USPTP, + gen7_17_0_tpl1_cluster_sp_ps_pipe_br_usptp_registers, 0xb000 }, + { A7XX_CLUSTER_SP_PS, A7XX_TP0_CTX3_3D_CPS_REG, PIPE_BR, 3, A7XX_USPTP, + gen7_17_0_tpl1_cluster_sp_ps_pipe_br_usptp_registers, 0xb000 }, +}; + +static struct gen7_shader_block gen7_17_0_shader_blocks[] = { + { A7XX_TP0_TMO_DATA, 0x0200, 2, 2, PIPE_BR, A7XX_USPTP }, + { A7XX_TP0_SMO_DATA, 0x0080, 2, 2, PIPE_BR, A7XX_USPTP }, + { A7XX_TP0_MIPMAP_BASE_DATA, 0x03c0, 2, 2, PIPE_BR, A7XX_USPTP }, + { A7XX_SP_INST_DATA, 0x0800, 2, 2, PIPE_BR, A7XX_USPTP }, + { A7XX_SP_INST_DATA_1, 0x0800, 2, 2, PIPE_BR, A7XX_USPTP }, + { A7XX_SP_LB_0_DATA, 0x0800, 2, 2, PIPE_BR, A7XX_USPTP }, + { A7XX_SP_LB_1_DATA, 0x0800, 2, 2, PIPE_BR, A7XX_USPTP }, + { A7XX_SP_LB_2_DATA, 0x0800, 2, 2, PIPE_BR, A7XX_USPTP }, + { A7XX_SP_LB_3_DATA, 0x0800, 2, 2, PIPE_BR, A7XX_USPTP }, + { A7XX_SP_LB_4_DATA, 0x0800, 2, 2, PIPE_BR, A7XX_USPTP }, + { A7XX_SP_LB_5_DATA, 0x0800, 2, 2, PIPE_BR, A7XX_USPTP }, + { A7XX_SP_CB_RAM, 0x0390, 2, 2, PIPE_BR, A7XX_USPTP }, + { A7XX_SP_INST_TAG, 0x0090, 2, 2, PIPE_BR, A7XX_USPTP }, + { A7XX_SP_TMO_TAG, 0x0080, 2, 2, PIPE_BR, A7XX_USPTP }, + { A7XX_SP_SMO_TAG, 0x0080, 2, 2, PIPE_BR, A7XX_USPTP }, + { A7XX_SP_STATE_DATA, 0x0040, 2, 2, PIPE_BR, A7XX_USPTP }, + { A7XX_SP_HWAVE_RAM, 0x0100, 2, 2, PIPE_BR, A7XX_USPTP }, + { A7XX_SP_L0_INST_BUF, 0x0050, 2, 2, PIPE_BR, A7XX_USPTP }, + { A7XX_HLSQ_CVS_BE_CTXT_BUF_RAM_TAG, 0x0010, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_CPS_BE_CTXT_BUF_RAM_TAG, 0x0010, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_GFX_CVS_BE_CTXT_BUF_RAM, 0x0300, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_GFX_CPS_BE_CTXT_BUF_RAM, 0x0300, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_CHUNK_CVS_RAM, 0x01c0, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_CHUNK_CPS_RAM, 0x0300, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_CHUNK_CVS_RAM_TAG, 0x0040, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_CHUNK_CPS_RAM_TAG, 0x0040, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_ICB_CVS_CB_BASE_TAG, 0x0010, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_ICB_CPS_CB_BASE_TAG, 0x0010, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_CVS_MISC_RAM, 0x0280, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_CPS_MISC_RAM, 0x0800, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_CPS_MISC_RAM_1, 0x0200, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_INST_RAM, 0x0800, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_GFX_CVS_CONST_RAM, 0x0800, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_GFX_CPS_CONST_RAM, 0x0800, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_CVS_MISC_RAM_TAG, 0x0010, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_CPS_MISC_RAM_TAG, 0x0010, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_INST_RAM_TAG, 0x0080, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_GFX_CVS_CONST_RAM_TAG, 0x0064, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_GFX_CPS_CONST_RAM_TAG, 0x0064, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_INST_RAM_1, 0x0800, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_STPROC_META, 0x0010, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_BV_BE_META, 0x0010, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_DATAPATH_META, 0x0020, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_FRONTEND_META, 0x0040, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_INDIRECT_META, 0x0010, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_BACKEND_META, 0x0040, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, +}; + +static struct gen7_reg_list gen7_17_0_reg_list[] = { + { gen7_17_0_gpu_registers, NULL }, + { gen7_17_0_dbgc_registers, NULL }, + { NULL, NULL }, +}; + +static const struct a6xx_indexed_registers gen7_17_0_cp_indexed_reglist[] = { + { "CP_SQE_STAT", REG_A6XX_CP_SQE_STAT_ADDR, + REG_A6XX_CP_SQE_STAT_DATA, 0x40, NULL }, + { "CP_DRAW_STATE", REG_A6XX_CP_DRAW_STATE_ADDR, + REG_A6XX_CP_DRAW_STATE_DATA, 0x100, NULL }, + { "CP_SQE_UCODE_DBG", REG_A6XX_CP_SQE_UCODE_DBG_ADDR, + REG_A6XX_CP_SQE_UCODE_DBG_DATA, 0x8000, NULL }, + { "CP_ROQ_DBG", REG_A6XX_CP_ROQ_DBG_ADDR, + REG_A6XX_CP_ROQ_DBG_DATA, 0, a7xx_get_cp_roq_size }, +}; + +#endif /* __ADRENO_GEN7_17_0_SNAPSHOT_H */ diff --git a/drivers/gpu/drm/msm/adreno/adreno_gpu.h b/drivers/gpu/drm/msm/adreno/adreno_gpu.h index 1f201322cb6e..114a40f79ef3 100644 --- a/drivers/gpu/drm/msm/adreno/adreno_gpu.h +++ b/drivers/gpu/drm/msm/adreno/adreno_gpu.h @@ -562,6 +562,11 @@ static inline int adreno_is_x185(struct adreno_gpu *gpu) return gpu->info->chip_ids[0] == 0x43050c01; } +static inline int adreno_is_a722(struct adreno_gpu *gpu) +{ + return gpu->info->chip_ids[0] == 0x43020100; +} + static inline int adreno_is_a740_family(struct adreno_gpu *gpu) { if (WARN_ON_ONCE(!gpu->info)) From cee19f08f460bc7a9b531472f25137aa8626cd51 Mon Sep 17 00:00:00 2001 From: Akhil P Oommen Date: Sat, 18 Jul 2026 02:11:29 +0530 Subject: [PATCH 0184/1328] dt-bindings: arm-smmu: Document GPU SMMU for Eliza SoC Add specific compatible strings to document the GPU SMMU present in the Eliza SoC. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Akhil P Oommen Patchwork: https://patchwork.freedesktop.org/patch/740962/ Message-ID: <20260718-eliza-gpu-v2-4-64379dbebd7a@oss.qualcomm.com> Signed-off-by: Rob Clark --- Documentation/devicetree/bindings/iommu/arm,smmu.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/iommu/arm,smmu.yaml b/Documentation/devicetree/bindings/iommu/arm,smmu.yaml index ad15fda5c25e..d87281026a71 100644 --- a/Documentation/devicetree/bindings/iommu/arm,smmu.yaml +++ b/Documentation/devicetree/bindings/iommu/arm,smmu.yaml @@ -95,6 +95,7 @@ properties: - description: Qcom Adreno GPUs implementing "qcom,smmu-500" and "arm,mmu-500" items: - enum: + - qcom,eliza-smmu-500 - qcom,glymur-smmu-500 - qcom,hawi-smmu-500 - qcom,kaanapali-smmu-500 @@ -572,6 +573,7 @@ allOf: compatible: items: - enum: + - qcom,eliza-smmu-500 - qcom,glymur-smmu-500 - qcom,hawi-smmu-500 - qcom,kaanapali-smmu-500 From 0b69e4f4ee325ee0b50396657fd19d4d7b0a6294 Mon Sep 17 00:00:00 2001 From: Puranam V G Tejaswi Date: Sat, 18 Jul 2026 02:11:30 +0530 Subject: [PATCH 0185/1328] dt-bindings: display/msm: Document Adreno 722 GPU and GMU Adreno 722 found in Eliza chipset belongs to the A7x Gen1 family. It is derived from A730 and shares the same IP-level configurations: HWCG registers, protected registers, GBIF CX registers and gmu_cgc_mode. Major differences include lower cache/core counts, 1MB GMEM, no Concurrent Binning & LPAC support. Some of the peripheral blocks like RSCC are from A740 that resulted in updates to RSC layout. Update the dt-binding docs to document this GPU and GMU. Signed-off-by: Puranam V G Tejaswi Acked-by: Krzysztof Kozlowski Signed-off-by: Akhil P Oommen Patchwork: https://patchwork.freedesktop.org/patch/740964/ Message-ID: <20260718-eliza-gpu-v2-5-64379dbebd7a@oss.qualcomm.com> Signed-off-by: Rob Clark --- Documentation/devicetree/bindings/display/msm/gmu.yaml | 1 + Documentation/devicetree/bindings/display/msm/gpu.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/display/msm/gmu.yaml b/Documentation/devicetree/bindings/display/msm/gmu.yaml index 8578c2f8122e..9e459f12ce3f 100644 --- a/Documentation/devicetree/bindings/display/msm/gmu.yaml +++ b/Documentation/devicetree/bindings/display/msm/gmu.yaml @@ -262,6 +262,7 @@ allOf: compatible: contains: enum: + - qcom,adreno-gmu-722.0 - qcom,adreno-gmu-730.1 - qcom,adreno-gmu-740.1 - qcom,adreno-gmu-750.1 diff --git a/Documentation/devicetree/bindings/display/msm/gpu.yaml b/Documentation/devicetree/bindings/display/msm/gpu.yaml index 992d3de7c117..e5efe2d35ddc 100644 --- a/Documentation/devicetree/bindings/display/msm/gpu.yaml +++ b/Documentation/devicetree/bindings/display/msm/gpu.yaml @@ -445,6 +445,7 @@ allOf: - qcom,adreno-680.1 - qcom,adreno-690.0 - qcom,adreno-730.1 + - qcom,adreno-43020100 - qcom,adreno-43030c00 - qcom,adreno-43050a01 - qcom,adreno-43050c01 From 7b2a8eb5c7b5545793e18036c8b66106f2d9fbb8 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sun, 28 Jun 2026 20:25:38 -0700 Subject: [PATCH 0186/1328] clk: samsung: use kzalloc_flex Simplify allocation by using kzalloc_flex with a flexible array member to combine allocations. samsung_clk_alloc_reg_dump is no longer needed for this struct but is needed elsewhere. Add __counted_by for extra runtime analysis. Move counting variable assignment after kzalloc_flex which does the same with GCC 15 and above. Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260629032540.2331559-2-rosenp@gmail.com Signed-off-by: Krzysztof Kozlowski --- drivers/clk/samsung/clk.c | 11 ++++++----- drivers/clk/samsung/clk.h | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/drivers/clk/samsung/clk.c b/drivers/clk/samsung/clk.c index 91e5cdbc79d7..2895c77534ae 100644 --- a/drivers/clk/samsung/clk.c +++ b/drivers/clk/samsung/clk.c @@ -430,21 +430,22 @@ void samsung_clk_extended_sleep_init(void __iomem *reg_base, unsigned long nr_rsuspend) { struct samsung_clock_reg_cache *reg_cache; + int i; - reg_cache = kzalloc_obj(struct samsung_clock_reg_cache); + reg_cache = kzalloc_flex(*reg_cache, rdump, nr_rdump); if (!reg_cache) panic("could not allocate register reg_cache.\n"); - reg_cache->rdump = samsung_clk_alloc_reg_dump(rdump, nr_rdump); - if (!reg_cache->rdump) - panic("could not allocate register dump storage.\n"); + reg_cache->rd_num = nr_rdump; + + for (i = 0; i < nr_rdump; ++i) + reg_cache->rdump[i].offset = rdump[i]; if (list_empty(&clock_reg_cache_list)) register_syscore(&samsung_clk_syscore); reg_cache->reg_base = reg_base; reg_cache->sysreg = sysreg; - reg_cache->rd_num = nr_rdump; reg_cache->rsuspend = rsuspend; reg_cache->rsuspend_num = nr_rsuspend; list_add_tail(®_cache->node, &clock_reg_cache_list); diff --git a/drivers/clk/samsung/clk.h b/drivers/clk/samsung/clk.h index b1192ca03db5..be733eba08a2 100644 --- a/drivers/clk/samsung/clk.h +++ b/drivers/clk/samsung/clk.h @@ -324,10 +324,10 @@ struct samsung_clock_reg_cache { struct list_head node; void __iomem *reg_base; struct regmap *sysreg; - struct samsung_clk_reg_dump *rdump; - unsigned int rd_num; const struct samsung_clk_reg_dump *rsuspend; unsigned int rsuspend_num; + unsigned int rd_num; + struct samsung_clk_reg_dump rdump[] __counted_by(rd_num); }; /** From 421ccea8f872c0e7cf6fd754a4a3bef4f45cf2dc Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sun, 28 Jun 2026 20:25:39 -0700 Subject: [PATCH 0187/1328] clk: samsung: cpu: use kzalloc_flex Use a flexible array member to combine allocations. As kmemdup_array is really kcalloc + memcpy and kzalloc_flex kzalloc + kcalloc, kzalloc and kcalloc combine leaving the memcpy. Add __counted_by for extra runtime analysis. Remove fake const num_cfgs. It needs to be assigned for __counted_by to work. Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260629032540.2331559-3-rosenp@gmail.com Signed-off-by: Krzysztof Kozlowski --- drivers/clk/samsung/clk-cpu.c | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/drivers/clk/samsung/clk-cpu.c b/drivers/clk/samsung/clk-cpu.c index ffc33e5decf5..37cf47533dc6 100644 --- a/drivers/clk/samsung/clk-cpu.c +++ b/drivers/clk/samsung/clk-cpu.c @@ -101,11 +101,11 @@ struct exynos_cpuclk { const struct clk_hw *alt_parent; void __iomem *base; spinlock_t *lock; - const struct exynos_cpuclk_cfg_data *cfg; - const unsigned long num_cfgs; + unsigned long num_cfgs; struct notifier_block clk_nb; unsigned long flags; const struct exynos_cpuclk_chip *chip; + struct exynos_cpuclk_cfg_data cfg[] __counted_by(num_cfgs); }; /* ---- Common code --------------------------------------------------------- */ @@ -660,10 +660,6 @@ static int __init exynos_register_cpu_clock(struct samsung_clk_provider *ctx, return -EINVAL; } - cpuclk = kzalloc_obj(*cpuclk); - if (!cpuclk) - return -ENOMEM; - parent_name = clk_hw_get_name(parent); init.name = clk_data->name; @@ -672,6 +668,17 @@ static int __init exynos_register_cpu_clock(struct samsung_clk_provider *ctx, init.num_parents = 1; init.ops = &exynos_cpuclk_clk_ops; + /* Find count of configuration rates in cfg */ + for (num_cfgs = 0; clk_data->cfg[num_cfgs].prate != 0; ) + num_cfgs++; + + cpuclk = kzalloc_flex(*cpuclk, cfg, num_cfgs); + if (!cpuclk) + return -ENOMEM; + + cpuclk->num_cfgs = num_cfgs; + + memcpy(cpuclk->cfg, clk_data->cfg, num_cfgs * sizeof(*cpuclk->cfg)); cpuclk->alt_parent = alt_parent; cpuclk->hw.init = &init; cpuclk->base = ctx->reg_base + clk_data->offset; @@ -687,29 +694,16 @@ static int __init exynos_register_cpu_clock(struct samsung_clk_provider *ctx, goto free_cpuclk; } - /* Find count of configuration rates in cfg */ - for (num_cfgs = 0; clk_data->cfg[num_cfgs].prate != 0; ) - num_cfgs++; - - cpuclk->cfg = kmemdup_array(clk_data->cfg, num_cfgs, sizeof(*cpuclk->cfg), - GFP_KERNEL); - if (!cpuclk->cfg) { - ret = -ENOMEM; - goto unregister_clk_nb; - } - ret = clk_hw_register(NULL, &cpuclk->hw); if (ret) { pr_err("%s: could not register cpuclk %s\n", __func__, clk_data->name); - goto free_cpuclk_data; + goto unregister_clk_nb; } samsung_clk_add_lookup(ctx, &cpuclk->hw, clk_data->id); return 0; -free_cpuclk_data: - kfree(cpuclk->cfg); unregister_clk_nb: clk_notifier_unregister(parent->clk, &cpuclk->clk_nb); free_cpuclk: From 6d32ae6403694035896cbf0436088fca4c6a35f1 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sun, 28 Jun 2026 20:25:40 -0700 Subject: [PATCH 0188/1328] clk: samsung: pll: use kzalloc_flex Simplify allocation by using a flexible array member to combine allocations and remove a kfree. Use __counted_by for extra runtime analysis. Since rate_table is now a flexible array member, NULL checks don't work. So use the counting variable to check allocation. Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260629032540.2331559-4-rosenp@gmail.com Signed-off-by: Krzysztof Kozlowski --- drivers/clk/samsung/clk-pll.c | 56 ++++++++++++++++------------------- 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/drivers/clk/samsung/clk-pll.c b/drivers/clk/samsung/clk-pll.c index fdb84bcec912..e74552846ba3 100644 --- a/drivers/clk/samsung/clk-pll.c +++ b/drivers/clk/samsung/clk-pll.c @@ -28,7 +28,7 @@ struct samsung_clk_pll { unsigned short lock_offs; enum samsung_pll_type type; unsigned int rate_count; - const struct samsung_pll_rate_table *rate_table; + struct samsung_pll_rate_table rate_table[] __counted_by(rate_count); }; #define to_clk_pll(_hw) container_of(_hw, struct samsung_clk_pll, hw) @@ -1593,35 +1593,32 @@ static void __init _samsung_clk_register_pll(struct samsung_clk_provider *ctx, { struct samsung_clk_pll *pll; struct clk_init_data init; - int ret, len; + unsigned int len = 0; + int ret; - pll = kzalloc_obj(*pll); + if (pll_clk->rate_table) { + /* find count of rates in rate_table */ + while (pll_clk->rate_table[len].rate != 0) + len++; + } + + pll = kzalloc_flex(*pll, rate_table, len); if (!pll) { pr_err("%s: could not allocate pll clk %s\n", __func__, pll_clk->name); return; } + pll->rate_count = len; + if (len) + memcpy(pll->rate_table, pll_clk->rate_table, + len * sizeof(*pll->rate_table)); + init.name = pll_clk->name; init.flags = pll_clk->flags; init.parent_names = &pll_clk->parent_name; init.num_parents = 1; - if (pll_clk->rate_table) { - /* find count of rates in rate_table */ - for (len = 0; pll_clk->rate_table[len].rate != 0; ) - len++; - - pll->rate_count = len; - pll->rate_table = kmemdup_array(pll_clk->rate_table, - pll->rate_count, - sizeof(*pll->rate_table), - GFP_KERNEL); - WARN(!pll->rate_table, - "%s: could not allocate rate table for %s\n", - __func__, pll_clk->name); - } - switch (pll_clk->type) { case pll_2126: init.ops = &samsung_pll2126_clk_ops; @@ -1640,7 +1637,7 @@ static void __init _samsung_clk_register_pll(struct samsung_clk_provider *ctx, case pll_a9fracm: pll->enable_offs = PLL35XX_ENABLE_SHIFT; pll->lock_offs = PLL35XX_LOCK_STAT_SHIFT; - if (!pll->rate_table) + if (!pll->rate_count) init.ops = &samsung_pll35xx_clk_min_ops; else init.ops = &samsung_pll35xx_clk_ops; @@ -1659,7 +1656,7 @@ static void __init _samsung_clk_register_pll(struct samsung_clk_provider *ctx, case pll_0732x: pll->enable_offs = PLL0822X_ENABLE_SHIFT; pll->lock_offs = PLL0822X_LOCK_STAT_SHIFT; - if (!pll->rate_table) + if (!pll->rate_count) init.ops = &samsung_pll0822x_clk_min_ops; else init.ops = &samsung_pll0822x_clk_ops; @@ -1669,7 +1666,7 @@ static void __init _samsung_clk_register_pll(struct samsung_clk_provider *ctx, break; case pll_4502: case pll_4508: - if (!pll->rate_table) + if (!pll->rate_count) init.ops = &samsung_pll45xx_clk_min_ops; else init.ops = &samsung_pll45xx_clk_ops; @@ -1679,7 +1676,7 @@ static void __init _samsung_clk_register_pll(struct samsung_clk_provider *ctx, case pll_2650: pll->enable_offs = PLL36XX_ENABLE_SHIFT; pll->lock_offs = PLL36XX_LOCK_STAT_SHIFT; - if (!pll->rate_table) + if (!pll->rate_count) init.ops = &samsung_pll36xx_clk_min_ops; else init.ops = &samsung_pll36xx_clk_ops; @@ -1687,7 +1684,7 @@ static void __init _samsung_clk_register_pll(struct samsung_clk_provider *ctx, case pll_0831x: pll->enable_offs = PLL0831X_ENABLE_SHIFT; pll->lock_offs = PLL0831X_LOCK_STAT_SHIFT; - if (!pll->rate_table) + if (!pll->rate_count) init.ops = &samsung_pll0831x_clk_min_ops; else init.ops = &samsung_pll0831x_clk_ops; @@ -1703,7 +1700,7 @@ static void __init _samsung_clk_register_pll(struct samsung_clk_provider *ctx, case pll_4650: case pll_4650c: case pll_1460x: - if (!pll->rate_table) + if (!pll->rate_count) init.ops = &samsung_pll46xx_clk_min_ops; else init.ops = &samsung_pll46xx_clk_ops; @@ -1712,19 +1709,19 @@ static void __init _samsung_clk_register_pll(struct samsung_clk_provider *ctx, init.ops = &samsung_pll2550x_clk_ops; break; case pll_2550xx: - if (!pll->rate_table) + if (!pll->rate_count) init.ops = &samsung_pll2550xx_clk_min_ops; else init.ops = &samsung_pll2550xx_clk_ops; break; case pll_2650x: - if (!pll->rate_table) + if (!pll->rate_count) init.ops = &samsung_pll2650x_clk_min_ops; else init.ops = &samsung_pll2650x_clk_ops; break; case pll_2650xx: - if (!pll->rate_table) + if (!pll->rate_count) init.ops = &samsung_pll2650xx_clk_min_ops; else init.ops = &samsung_pll2650xx_clk_ops; @@ -1734,7 +1731,7 @@ static void __init _samsung_clk_register_pll(struct samsung_clk_provider *ctx, init.ops = &samsung_pll531x_clk_ops; break; case pll_1031x: - if (!pll->rate_table) + if (!pll->rate_count) init.ops = &samsung_pll1031x_clk_min_ops; else init.ops = &samsung_pll1031x_clk_ops; @@ -1742,7 +1739,7 @@ static void __init _samsung_clk_register_pll(struct samsung_clk_provider *ctx, case pll_a9fraco: pll->enable_offs = PLLA9FRACO_ENABLE_SHIFT; pll->lock_offs = PLLA9FRACO_LOCK_STAT_SHIFT; - if (!pll->rate_table) + if (!pll->rate_count) init.ops = &samsung_a9fraco_clk_min_ops; else init.ops = &samsung_a9fraco_clk_ops; @@ -1761,7 +1758,6 @@ static void __init _samsung_clk_register_pll(struct samsung_clk_provider *ctx, if (ret) { pr_err("%s: failed to register pll clock %s : %d\n", __func__, pll_clk->name, ret); - kfree(pll->rate_table); kfree(pll); return; } From a9954d40de71cee344dbf9d61600b79379f74bb9 Mon Sep 17 00:00:00 2001 From: Jagadeesh Kona Date: Tue, 14 Jul 2026 10:59:35 +0530 Subject: [PATCH 0189/1328] clk: qcom: camcc-glymur: Add const qualifier for driver_data & CBCRs list The qcom_cc_driver_data and critical CBCRs list are never modified by common code and are expected to be const. Hence add const qualifier for these fields. Signed-off-by: Jagadeesh Kona Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260714-glymur_camcc_const_fixes-v1-1-c635123ebbeb@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/camcc-glymur.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/clk/qcom/camcc-glymur.c b/drivers/clk/qcom/camcc-glymur.c index a80f05b7bc48..044cc590624e 100644 --- a/drivers/clk/qcom/camcc-glymur.c +++ b/drivers/clk/qcom/camcc-glymur.c @@ -2223,7 +2223,7 @@ static struct clk_alpha_pll *cam_cc_glymur_plls[] = { &cam_cc_pll5, }; -static u32 cam_cc_glymur_critical_cbcrs[] = { +static const u32 cam_cc_glymur_critical_cbcrs[] = { 0x13960, /* CAM_CC_GDSC_CLK */ 0x1397c, /* CAM_CC_SLEEP_CLK */ }; @@ -2236,7 +2236,7 @@ static const struct regmap_config cam_cc_glymur_regmap_config = { .fast_io = true, }; -static struct qcom_cc_driver_data cam_cc_glymur_driver_data = { +static const struct qcom_cc_driver_data cam_cc_glymur_driver_data = { .alpha_plls = cam_cc_glymur_plls, .num_alpha_plls = ARRAY_SIZE(cam_cc_glymur_plls), .clk_cbcrs = cam_cc_glymur_critical_cbcrs, From deb232e884877bf10b4ce2580909eedec986c284 Mon Sep 17 00:00:00 2001 From: Sabrina Dubroca Date: Thu, 16 Jul 2026 22:54:59 +0200 Subject: [PATCH 0190/1328] xfrm: espintcp: fix UAF during close ZDI reported and analyzed a race condition during close for espintcp sockets: espintcp_close() frees emsg->skb via kfree_skb() without holding any socket lock. Concurrently, the xfrm_trans_reinject work queue invokes esp_output_tcp_finish() -> espintcp_push_skb() -> espintcp_push_msgs() -> skb_send_sock_locked(), which reads the same skb as a data source. Fix this by adding a synchronize_rcu() call after resetting sk_prot, since esp_output_tcp_finish() runs under RCU and won't use a socket with sk_prot == &tcp_prot. Simply taking the socket lock in espintcp_close() could lead to leaks, if esp_output_tcp_finish() re-adds an skb in the slot we just freed. After this, the existing barrier() is no longer needed. Cc: stable@vger.kernel.org Fixes: e27cca96cd68 ("xfrm: add espintcp (RFC 8229)") Reported-by: zdi-disclosures@trendmicro.com Signed-off-by: Sabrina Dubroca Reviewed-by: Breno Leitao Signed-off-by: Steffen Klassert --- net/xfrm/espintcp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/xfrm/espintcp.c b/net/xfrm/espintcp.c index 374e1b964438..cd817b855ba1 100644 --- a/net/xfrm/espintcp.c +++ b/net/xfrm/espintcp.c @@ -515,7 +515,8 @@ static void espintcp_close(struct sock *sk, long timeout) strp_stop(&ctx->strp); sk->sk_prot = &tcp_prot; - barrier(); + + synchronize_rcu(); disable_work_sync(&ctx->work); strp_done(&ctx->strp); From e1d7c5ac1c246ce5775f604515de0a59fbf2116e Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Sat, 18 Jul 2026 15:12:50 +0800 Subject: [PATCH 0191/1328] xfrm: drop ESP-in-TCP packets with no ingress device ESP-in-TCP receives records through the TCP strparser. handle_esp() restores skb->dev from the saved skb_iif before passing the packet into the XFRM input path. Queued TCP data can be processed after the original ingress device has been removed, for example during veth or net namespace teardown. In that case dev_get_by_index_rcu() returns NULL. The XFRM IPv4 and IPv6 input paths both expect skb->dev to be valid while building the route lookup, so queued ESP-in-TCP data can dereference a NULL device. Drop the packet if the saved ingress device can no longer be resolved. Such a packet can no longer be routed through the normal XFRM receive path, and this preserves the existing behaviour for packets whose ingress device still exists. Fixes: e27cca96cd68 ("xfrm: add espintcp (RFC 8229)") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Zhiling Zou Assisted-by: Codex:gpt-5.4 Reviewed-by: Ren Wei Signed-off-by: Steffen Klassert --- net/xfrm/espintcp.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/xfrm/espintcp.c b/net/xfrm/espintcp.c index cd817b855ba1..674aedc5af5a 100644 --- a/net/xfrm/espintcp.c +++ b/net/xfrm/espintcp.c @@ -37,6 +37,11 @@ static void handle_esp(struct sk_buff *skb, struct sock *sk) rcu_read_lock(); skb->dev = dev_get_by_index_rcu(sock_net(sk), skb->skb_iif); + if (!skb->dev) { + XFRM_INC_STATS(sock_net(sk), LINUX_MIB_XFRMINERROR); + kfree_skb(skb); + goto out; + } local_bh_disable(); #if IS_ENABLED(CONFIG_IPV6) if (sk->sk_family == AF_INET6) @@ -45,6 +50,7 @@ static void handle_esp(struct sk_buff *skb, struct sock *sk) #endif xfrm4_rcv_encap(skb, IPPROTO_ESP, 0, TCP_ENCAP_ESPINTCP); local_bh_enable(); +out: rcu_read_unlock(); } From 3693ff45ec256f31c5081fe751923b32f5900145 Mon Sep 17 00:00:00 2001 From: Paul Moore Date: Fri, 17 Jul 2026 17:52:55 -0400 Subject: [PATCH 0192/1328] apparmor: leverage audit_log_n_untrustedstring() when possible Make use of the audit_log_n_untrustedstring() function to simplify the code in aa_label_xaudit(). Signed-off-by: Paul Moore Signed-off-by: John Johansen --- security/apparmor/label.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/security/apparmor/label.c b/security/apparmor/label.c index 3fd384d8c41a..a165cadf8249 100644 --- a/security/apparmor/label.c +++ b/security/apparmor/label.c @@ -1743,10 +1743,7 @@ void aa_label_xaudit(struct audit_buffer *ab, struct aa_ns *ns, str = (char *) label->hname; len = strlen(str); } - if (audit_string_contains_control(str, len)) - audit_log_n_hex(ab, str, len); - else - audit_log_n_string(ab, str, len); + audit_log_n_untrustedstring(ab, str, len); kfree(name); } From 763fe700b7c58ad64fe5202c5638848244dd4127 Mon Sep 17 00:00:00 2001 From: Zihan Xi Date: Tue, 21 Jul 2026 23:25:42 +0800 Subject: [PATCH 0193/1328] xfrm: avoid lock inversion in nat keepalive work nat_keepalive_work() walks the state table while xfrm_state_walk() holds net->xfrm.xfrm_state_lock. Its callback then acquires x->lock, which conflicts with the delete path taking the same locks in reverse order via xfrm_state_delete() and __xfrm_state_delete(). This creates an AB-BA deadlock that is reported by lockdep when a NAT keepalive worker races with SA deletion. Fix this by splitting the keepalive walk into two phases. First, collect the candidate states while the walk holds xfrm_state_lock and take a reference on each state. Then, after the walk completes, process each collected state and acquire x->lock without nesting it under xfrm_state_lock. Fixes: f531d13bdfe3 ("xfrm: support sending NAT keepalives in ESP in UDP states") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: Codex:gpt-5.4 Signed-off-by: Zihan Xi Signed-off-by: Ren Wei Signed-off-by: Steffen Klassert --- net/xfrm/xfrm_nat_keepalive.c | 57 +++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/net/xfrm/xfrm_nat_keepalive.c b/net/xfrm/xfrm_nat_keepalive.c index eb1b6f67739e..8679c68c10a1 100644 --- a/net/xfrm/xfrm_nat_keepalive.c +++ b/net/xfrm/xfrm_nat_keepalive.c @@ -156,24 +156,51 @@ static void nat_keepalive_send(struct nat_keepalive *ka) } struct nat_keepalive_work_ctx { + struct list_head states; time64_t next_run; time64_t now; }; -static int nat_keepalive_work_single(struct xfrm_state *x, int count, void *ptr) +struct nat_keepalive_state { + struct list_head list; + struct xfrm_state *x; +}; + +static int nat_keepalive_work_collect(struct xfrm_state *x, int count, void *ptr) { struct nat_keepalive_work_ctx *ctx = ptr; + struct nat_keepalive_state *state; + + if (!READ_ONCE(x->nat_keepalive_interval)) + return 0; + + state = kmalloc_obj(*state, GFP_ATOMIC); + if (!state) + return -ENOMEM; + + xfrm_state_hold(x); + state->x = x; + list_add_tail(&state->list, &ctx->states); + return 0; +} + +static void nat_keepalive_work_single(struct xfrm_state *x, + struct nat_keepalive_work_ctx *ctx) +{ bool send_keepalive = false; struct nat_keepalive ka; - time64_t next_run; + time64_t next_run = 0; u32 interval; int delta; + spin_lock_bh(&x->lock); + + if (x->km.state == XFRM_STATE_DEAD) + goto out; + interval = x->nat_keepalive_interval; if (!interval) - return 0; - - spin_lock(&x->lock); + goto out; delta = (int)(ctx->now - x->lastused); if (delta < interval) { @@ -187,29 +214,41 @@ static int nat_keepalive_work_single(struct xfrm_state *x, int count, void *ptr) send_keepalive = true; } - spin_unlock(&x->lock); +out: + spin_unlock_bh(&x->lock); if (send_keepalive) nat_keepalive_send(&ka); - if (!ctx->next_run || next_run < ctx->next_run) + if (next_run && (!ctx->next_run || next_run < ctx->next_run)) ctx->next_run = next_run; - return 0; } static void nat_keepalive_work(struct work_struct *work) { + struct nat_keepalive_state *state, *tmp; struct nat_keepalive_work_ctx ctx; struct xfrm_state_walk walk; struct net *net; + int err; + INIT_LIST_HEAD(&ctx.states); ctx.next_run = 0; ctx.now = ktime_get_real_seconds(); net = container_of(work, struct net, xfrm.nat_keepalive_work.work); xfrm_state_walk_init(&walk, IPPROTO_ESP, NULL); - xfrm_state_walk(net, &walk, nat_keepalive_work_single, &ctx); + err = xfrm_state_walk(net, &walk, nat_keepalive_work_collect, &ctx); xfrm_state_walk_done(&walk, net); + list_for_each_entry_safe(state, tmp, &ctx.states, list) { + nat_keepalive_work_single(state->x, &ctx); + xfrm_state_put(state->x); + kfree(state); + } + if (err == -ENOMEM) { + schedule_delayed_work(&net->xfrm.nat_keepalive_work, 0); + return; + } if (ctx.next_run) schedule_delayed_work(&net->xfrm.nat_keepalive_work, (ctx.next_run - ctx.now) * HZ); From 2aed51fc58d9ce450e2c116efb956160fd06fa02 Mon Sep 17 00:00:00 2001 From: Sanghyun Park Date: Wed, 22 Jul 2026 16:28:38 +0900 Subject: [PATCH 0194/1328] xfrm: Fix skb double-free in xfrm_dev_direct_output() A return value other than 1 from local_out() means that the skb has been consumed or its ownership was transferred. xfrm_dev_direct_output() nevertheless frees the skb on this path, causing a double-free when netfilter drops the packet and invalidating any other owner. Return the local_out() result directly, matching the ownership handling in xfrm_output_resume(). Fixes: 5eddd76ec2fd ("xfrm: fix tunnel mode TX datapath in packet offload mode") Signed-off-by: Sanghyun Park Signed-off-by: Steffen Klassert --- net/xfrm/xfrm_output.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/net/xfrm/xfrm_output.c b/net/xfrm/xfrm_output.c index cc35c2fcbbe0..e305ba32e356 100644 --- a/net/xfrm/xfrm_output.c +++ b/net/xfrm/xfrm_output.c @@ -636,10 +636,8 @@ static int xfrm_dev_direct_output(struct sock *sk, struct xfrm_state *x, nf_reset_ct(skb); err = skb_dst(skb)->ops->local_out(net, sk, skb); - if (unlikely(err != 1)) { - kfree_skb(skb); + if (unlikely(err != 1)) return err; - } /* In transport mode, network destination is * directly reachable, while in tunnel mode, From 3c28d9d0b7e0890df3c16dc7ad5f0fd17e775ab4 Mon Sep 17 00:00:00 2001 From: Edelweise Escala Date: Thu, 9 Jul 2026 08:41:19 +0800 Subject: [PATCH 0195/1328] dt-bindings: leds: Add LTC3220 18 channel LED Driver LTC3220 is a multi-display LED driver with I2C interface. The LTC3220 provides individual brightness control (64-step), blinking, and gradation features for up to 18 LED outputs. Signed-off-by: Edelweise Escala Reviewed-by: Conor Dooley Link: https://patch.msgid.link/20260709-ltc3220-driver-v13-1-9b53237642eb@analog.com Signed-off-by: Lee Jones --- .../devicetree/bindings/leds/adi,ltc3220.yaml | 133 ++++++++++++++++++ MAINTAINERS | 7 + 2 files changed, 140 insertions(+) create mode 100644 Documentation/devicetree/bindings/leds/adi,ltc3220.yaml diff --git a/Documentation/devicetree/bindings/leds/adi,ltc3220.yaml b/Documentation/devicetree/bindings/leds/adi,ltc3220.yaml new file mode 100644 index 000000000000..48215ad82d9a --- /dev/null +++ b/Documentation/devicetree/bindings/leds/adi,ltc3220.yaml @@ -0,0 +1,133 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/leds/adi,ltc3220.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices LTC3220 LED Driver + +maintainers: + - Edelweise Escala + +description: > + The LTC3220 is a multi-display LED driver, which contains a high-efficiency, + low-noise charge pump to provide power to up to 18 LED current sources. + The LEDs are individually configurable to 64-step linear brightness control, + blinking and gradation control via 2-wire I2C interface. + + For more product information please see the link below + https://www.analog.com/en/products/ltc3220.html + +properties: + compatible: + const: adi,ltc3220 + + reg: + maxItems: 1 + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + + reset-gpios: + maxItems: 1 + +patternProperties: + '^led@([1-9a-f]|1[0-2])$': + type: object + $ref: /schemas/leds/common.yaml# + unevaluatedProperties: false + properties: + reg: + description: + Output channel for the LED (1-18 maps to LED outputs D1-D18). + Unit-address must be in hexadecimal (1-12 hex = 1-18 decimal). + For aggregated LED control, define only one LED node with reg = <0x1> + and use led-sources to list all controlled outputs. Only reg 1 should + be present when using led-sources. + items: + - minimum: 1 + maximum: 18 + + required: + - reg + + if: + required: + - led-sources + then: + properties: + reg: + items: + - const: 1 + +required: + - compatible + - reg + - '#address-cells' + - '#size-cells' + +additionalProperties: false + +examples: + - | + // Independent LEDs + #include + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + led-controller@1c { + compatible = "adi,ltc3220"; + reg = <0x1c>; + #address-cells = <1>; + #size-cells = <0>; + reset-gpios = <&gpio 17 GPIO_ACTIVE_LOW>; + + led@1 { + reg = <0x1>; + function = LED_FUNCTION_INDICATOR; + function-enumerator = <1>; + }; + + led@2 { + reg = <0x2>; + function = LED_FUNCTION_INDICATOR; + function-enumerator = <2>; + }; + + led@3 { + reg = <0x3>; + function = LED_FUNCTION_INDICATOR; + function-enumerator = <3>; + }; + }; + }; + + - | + // Aggregated LED + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + led-controller@1c { + compatible = "adi,ltc3220"; + reg = <0x1c>; + #address-cells = <1>; + #size-cells = <0>; + + led@1 { + reg = <0x1>; + led-sources = <0x1 0x2 0x3 0x4 0x5 0x6 0x7 0x8 0x9 0xa 0xb 0xc 0xd 0xe 0xf 0x10 0x11 0x12>; + function = LED_FUNCTION_BACKLIGHT; + }; + }; + }; + +... diff --git a/MAINTAINERS b/MAINTAINERS index 15011f5752a9..69bd487d3076 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15380,6 +15380,13 @@ W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/temperature/adi,ltc2983.yaml F: drivers/iio/temperature/ltc2983.c +LTC3220 LED DRIVER +M: Edelweise Escala +L: linux-leds@vger.kernel.org +S: Maintained +W: https://ez.analog.com/linux-software-drivers +F: Documentation/devicetree/bindings/leds/adi,ltc3220.yaml + LTC4282 HARDWARE MONITOR DRIVER M: Nuno Sa L: linux-hwmon@vger.kernel.org From 07e028d93a57fa6217cf8eb1375ec29e7d1e4b27 Mon Sep 17 00:00:00 2001 From: Edelweise Escala Date: Thu, 9 Jul 2026 08:41:20 +0800 Subject: [PATCH 0196/1328] leds: ltc3220: Add Support for LTC3220 18 channel LED Driver Add driver for the LTC3220 18-channel LED driver with I2C interface, individual brightness control, and hardware-assisted blink/gradation features. Signed-off-by: Edelweise Escala Link: https://patch.msgid.link/20260709-ltc3220-driver-v13-2-9b53237642eb@analog.com Signed-off-by: Lee Jones --- MAINTAINERS | 1 + drivers/leds/Kconfig | 13 + drivers/leds/Makefile | 1 + drivers/leds/leds-ltc3220.c | 586 ++++++++++++++++++++++++++++++++++++ 4 files changed, 601 insertions(+) create mode 100644 drivers/leds/leds-ltc3220.c diff --git a/MAINTAINERS b/MAINTAINERS index 69bd487d3076..747ef98f21e8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15386,6 +15386,7 @@ L: linux-leds@vger.kernel.org S: Maintained W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/leds/adi,ltc3220.yaml +F: drivers/leds/leds-ltc3220.c LTC4282 HARDWARE MONITOR DRIVER M: Nuno Sa diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index f4a0a3c8c870..31b1e3ff094c 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -1000,6 +1000,19 @@ config LEDS_ST1202 Say Y to enable support for LEDs connected to LED1202 LED driver chips accessed via the I2C bus. +config LEDS_LTC3220 + tristate "LED Driver for Analog Devices Inc. LTC3220" + depends on I2C && LEDS_CLASS + select REGMAP_I2C + help + Say Y to enable support for the Analog Devices LTC3220 + 18-channel LED controller with I2C interface. + The driver supports individual LED brightness control (64 steps), + hardware-assisted blinking and gradation effects. + + To compile this driver as a module, choose M here: the module will + be called leds-ltc3220. + config LEDS_TPS6105X tristate "LED support for TI TPS6105X" depends on LEDS_CLASS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 7db3768912ca..a68244bd50fb 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -61,6 +61,7 @@ obj-$(CONFIG_LEDS_LP8788) += leds-lp8788.o obj-$(CONFIG_LEDS_LP8860) += leds-lp8860.o obj-$(CONFIG_LEDS_LP8864) += leds-lp8864.o obj-$(CONFIG_LEDS_LT3593) += leds-lt3593.o +obj-$(CONFIG_LEDS_LTC3220) += leds-ltc3220.o obj-$(CONFIG_LEDS_MAX5970) += leds-max5970.o obj-$(CONFIG_LEDS_MAX77650) += leds-max77650.o obj-$(CONFIG_LEDS_MAX77705) += leds-max77705.o diff --git a/drivers/leds/leds-ltc3220.c b/drivers/leds/leds-ltc3220.c new file mode 100644 index 000000000000..5060d5042fe1 --- /dev/null +++ b/drivers/leds/leds-ltc3220.c @@ -0,0 +1,586 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * LTC3220 18-Channel LED Driver + * + * Copyright 2026 Analog Devices Inc. + * + * Author: Edelweise Escala + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* LTC3220 Registers */ +#define LTC3220_COMMAND_REG 0x00 +#define LTC3220_QUICK_WRITE_MASK BIT(0) +#define LTC3220_SHUTDOWN_MASK BIT(3) + +#define LTC3220_ULED_REG(x) (0x01 + (x)) +#define LTC3220_LED_CURRENT_MASK GENMASK(5, 0) +#define LTC3220_LED_MODE_MASK GENMASK(7, 6) + +#define LTC3220_GRAD_BLINK_REG 0x13 +#define LTC3220_GRADATION_MASK GENMASK(2, 0) +#define LTC3220_GRADATION_DIRECTION_MASK BIT(0) +#define LTC3220_GRADATION_PERIOD_MASK GENMASK(2, 1) +#define LTC3220_BLINK_MASK GENMASK(4, 3) + +#define LTC3220_NUM_LEDS 18 +#define LTC3220_MAX_BRIGHTNESS 63 + +#define LTC3220_GRADATION_RAMP_TIME_240MS 240 +#define LTC3220_GRADATION_RAMP_TIME_480MS 480 + +#define LTC3220_BLINK_ON_156MS 156 +#define LTC3220_BLINK_ON_625MS 625 +#define LTC3220_BLINK_PERIOD_1250MS 1250 +#define LTC3220_BLINK_PERIOD_2500MS 2500 + +#define LTC3220_BLINK_SHORT_ON_TIME BIT(0) +#define LTC3220_BLINK_LONG_PERIOD BIT(1) + +enum ltc3220_led_mode { + LTC3220_NORMAL_MODE, + LTC3220_BLINK_MODE, + LTC3220_GRADATION_MODE, +}; + +enum ltc3220_blink_mode { + LTC3220_BLINK_MODE_625MS_1250MS, + LTC3220_BLINK_MODE_156MS_1250MS, + LTC3220_BLINK_MODE_625MS_2500MS, + LTC3220_BLINK_MODE_156MS_2500MS +}; + +enum ltc3220_gradation_mode { + LTC3220_GRADATION_MODE_DISABLED, + LTC3220_GRADATION_MODE_240MS_RAMP_TIME, + LTC3220_GRADATION_MODE_480MS_RAMP_TIME, + LTC3220_GRADATION_MODE_960MS_RAMP_TIME +}; + +static const struct regmap_config ltc3220_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = LTC3220_GRAD_BLINK_REG, + .cache_type = REGCACHE_FLAT_S, +}; + +struct ltc3220_uled_cfg { + struct led_classdev led_cdev; + u8 reg_value; + u8 led_index; + bool registered; +}; + +struct ltc3220 { + struct ltc3220_uled_cfg uled_cfg[LTC3220_NUM_LEDS]; + struct regmap *regmap; + struct mutex lock; +}; + +/* + * Set LED brightness. Hardware supports 0-63 brightness levels. + * Mode switching (blink/gradation) is handled through dedicated callbacks. + * + * In aggregated mode only a single LED (reg = 1) is registered and the + * hardware quick-write feature propagates the write to all 18 channels, so + * there is no need to update the other registers explicitly. + */ +static int __ltc3220_set_led_data(struct ltc3220 *ltc3220, + struct ltc3220_uled_cfg *uled_cfg, + enum led_brightness brightness) +{ + int ret; + + brightness &= LTC3220_LED_CURRENT_MASK; + + ret = regmap_write(ltc3220->regmap, LTC3220_ULED_REG(uled_cfg->led_index), + brightness); + if (ret) + return ret; + + uled_cfg->reg_value = brightness; + + return 0; +} + +static int ltc3220_set_led_data(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct ltc3220_uled_cfg *uled_cfg = container_of(led_cdev, struct ltc3220_uled_cfg, + led_cdev); + struct ltc3220 *ltc3220 = container_of(uled_cfg - uled_cfg->led_index, struct ltc3220, + uled_cfg[0]); + int ret; + + mutex_lock(<c3220->lock); + ret = __ltc3220_set_led_data(ltc3220, uled_cfg, brightness); + mutex_unlock(<c3220->lock); + + return ret; +} + +static enum led_brightness ltc3220_get_led_data(struct led_classdev *led_cdev) +{ + struct ltc3220_uled_cfg *uled_cfg = container_of(led_cdev, struct ltc3220_uled_cfg, + led_cdev); + + return uled_cfg->reg_value; +} + +/* + * LTC3220 pattern support for hardware-assisted breathing/gradation. + * The hardware supports 3 gradation ramp times (240ms, 480ms, 960ms) + * and can ramp up or down. The gradation period and direction are chip-global + * registers (LTC3220_GRAD_BLINK_REG), affecting all 18 channels simultaneously. + * This is a hardware limitation, not a driver bug. + * + * Pattern array interpretation: + * pattern[0].brightness = start brightness (0-63) + * pattern[0].delta_t = ramp time in milliseconds + * pattern[1].brightness = end brightness (0-63) + * pattern[1].delta_t = (optional, can be 0 or same as pattern[0].delta_t) + */ +static int ltc3220_pattern_set(struct led_classdev *led_cdev, + struct led_pattern *pattern, + u32 len, int repeat) +{ + struct ltc3220_uled_cfg *uled_cfg = container_of(led_cdev, struct ltc3220_uled_cfg, + led_cdev); + struct ltc3220 *ltc3220 = container_of(uled_cfg - uled_cfg->led_index, struct ltc3220, + uled_cfg[0]); + u8 gradation_period; + u8 start_brightness; + u8 end_brightness; + u8 gradation_val; + u8 led_mode; + bool is_increasing; + int ret; + + if (len != 2) + return -EINVAL; + + start_brightness = clamp_val(pattern[0].brightness, 0, LTC3220_LED_CURRENT_MASK); + end_brightness = clamp_val(pattern[1].brightness, 0, LTC3220_LED_CURRENT_MASK); + + is_increasing = end_brightness > start_brightness; + + if (pattern[0].delta_t == 0) + gradation_period = LTC3220_GRADATION_MODE_DISABLED; + else if (pattern[0].delta_t <= LTC3220_GRADATION_RAMP_TIME_240MS) + gradation_period = LTC3220_GRADATION_MODE_240MS_RAMP_TIME; + else if (pattern[0].delta_t <= LTC3220_GRADATION_RAMP_TIME_480MS) + gradation_period = LTC3220_GRADATION_MODE_480MS_RAMP_TIME; + else + gradation_period = LTC3220_GRADATION_MODE_960MS_RAMP_TIME; + + gradation_val = FIELD_PREP(LTC3220_GRADATION_PERIOD_MASK, gradation_period); + gradation_val |= FIELD_PREP(LTC3220_GRADATION_DIRECTION_MASK, is_increasing); + + /* + * With the ramp disabled (delta_t == 0) there is no gradation to run, + * so apply the end brightness directly in NORMAL mode instead of + * leaving the channel in gradation mode with a disabled ramp. + */ + led_mode = gradation_period == LTC3220_GRADATION_MODE_DISABLED ? + LTC3220_NORMAL_MODE : LTC3220_GRADATION_MODE; + + mutex_lock(<c3220->lock); + + ret = regmap_update_bits(ltc3220->regmap, LTC3220_GRAD_BLINK_REG, + LTC3220_GRADATION_MASK, gradation_val); + if (ret) + goto unlock; + + if (led_mode == LTC3220_GRADATION_MODE) { + ret = regmap_write(ltc3220->regmap, LTC3220_ULED_REG(uled_cfg->led_index), + start_brightness & LTC3220_LED_CURRENT_MASK); + if (ret) + goto unlock; + + ret = regmap_write(ltc3220->regmap, LTC3220_ULED_REG(uled_cfg->led_index), + FIELD_PREP(LTC3220_LED_MODE_MASK, led_mode) | + (end_brightness & LTC3220_LED_CURRENT_MASK)); + if (ret) + goto unlock; + + uled_cfg->reg_value = end_brightness; + } else { + ret = __ltc3220_set_led_data(ltc3220, uled_cfg, end_brightness); + if (ret) + goto unlock; + } + +unlock: + mutex_unlock(<c3220->lock); + return ret; +} + +static int ltc3220_pattern_clear(struct led_classdev *led_cdev) +{ + struct ltc3220_uled_cfg *uled_cfg = container_of(led_cdev, struct ltc3220_uled_cfg, + led_cdev); + struct ltc3220 *ltc3220 = container_of(uled_cfg - uled_cfg->led_index, struct ltc3220, + uled_cfg[0]); + int ret; + + mutex_lock(<c3220->lock); + + ret = regmap_update_bits(ltc3220->regmap, LTC3220_ULED_REG(uled_cfg->led_index), + LTC3220_LED_MODE_MASK, LTC3220_NORMAL_MODE); + if (ret) + goto unlock; + + ret = __ltc3220_set_led_data(ltc3220, uled_cfg, LED_OFF); + +unlock: + mutex_unlock(<c3220->lock); + return ret; +} + +/* + * LTC3220 has a global blink configuration that affects all LEDs. + * This implementation allows per-LED blink requests via sysfs, but setting + * blink on any LED reprograms the timing for all 18 channels simultaneously. + * The delay values are mapped to the hardware's discrete blink rates. + * + * HARDWARE LIMITATION: This is not a driver bug. Per-LED blink timing control + * is not possible with this hardware due to the global blink register. + */ +static int ltc3220_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + struct ltc3220_uled_cfg *uled_cfg = container_of(led_cdev, struct ltc3220_uled_cfg, + led_cdev); + struct ltc3220 *ltc3220 = container_of(uled_cfg - uled_cfg->led_index, struct ltc3220, + uled_cfg[0]); + u8 blink_brightness; + u8 blink_mode = 0; + int ret; + + if (*delay_on <= LTC3220_BLINK_ON_156MS) + blink_mode = LTC3220_BLINK_SHORT_ON_TIME; + + if (*delay_on + *delay_off > LTC3220_BLINK_PERIOD_1250MS) + blink_mode |= LTC3220_BLINK_LONG_PERIOD; + + switch (blink_mode) { + case LTC3220_BLINK_MODE_625MS_1250MS: + *delay_on = LTC3220_BLINK_ON_625MS; + *delay_off = LTC3220_BLINK_PERIOD_1250MS - LTC3220_BLINK_ON_625MS; + break; + case LTC3220_BLINK_MODE_156MS_1250MS: + *delay_on = LTC3220_BLINK_ON_156MS; + *delay_off = LTC3220_BLINK_PERIOD_1250MS - LTC3220_BLINK_ON_156MS; + break; + case LTC3220_BLINK_MODE_625MS_2500MS: + *delay_on = LTC3220_BLINK_ON_625MS; + *delay_off = LTC3220_BLINK_PERIOD_2500MS - LTC3220_BLINK_ON_625MS; + break; + case LTC3220_BLINK_MODE_156MS_2500MS: + *delay_on = LTC3220_BLINK_ON_156MS; + *delay_off = LTC3220_BLINK_PERIOD_2500MS - LTC3220_BLINK_ON_156MS; + break; + } + + mutex_lock(<c3220->lock); + + ret = regmap_update_bits(ltc3220->regmap, LTC3220_GRAD_BLINK_REG, + LTC3220_BLINK_MASK, FIELD_PREP(LTC3220_BLINK_MASK, blink_mode)); + if (ret) + goto unlock; + + blink_brightness = uled_cfg->reg_value ? : led_cdev->max_brightness; + + ret = regmap_write(ltc3220->regmap, LTC3220_ULED_REG(uled_cfg->led_index), + FIELD_PREP(LTC3220_LED_MODE_MASK, LTC3220_BLINK_MODE) | + (blink_brightness & LTC3220_LED_CURRENT_MASK)); + if (ret) + goto unlock; + + uled_cfg->reg_value = blink_brightness; + +unlock: + mutex_unlock(<c3220->lock); + return ret; +} + +static void ltc3220_reset_gpio_action(void *data) +{ + struct gpio_desc *reset_gpio = data; + + gpiod_set_value_cansleep(reset_gpio, 1); +} + +static int ltc3220_reset(struct ltc3220 *ltc3220, struct i2c_client *client) +{ + struct gpio_desc *reset_gpio; + int ret; + + reset_gpio = devm_gpiod_get_optional(&client->dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(reset_gpio)) + return dev_err_probe(&client->dev, PTR_ERR(reset_gpio), "Failed on reset GPIO\n"); + + if (reset_gpio) { + usleep_range(10000, 12000); + gpiod_set_value_cansleep(reset_gpio, 0); + usleep_range(10000, 12000); + + ret = devm_add_action_or_reset(&client->dev, ltc3220_reset_gpio_action, + reset_gpio); + if (ret) + return ret; + } + + ret = regmap_write(ltc3220->regmap, LTC3220_COMMAND_REG, 0); + if (ret) + return ret; + + for (int i = 0; i < LTC3220_NUM_LEDS; i++) { + ret = regmap_write(ltc3220->regmap, LTC3220_ULED_REG(i), 0); + if (ret) + return ret; + } + + return regmap_write(ltc3220->regmap, LTC3220_GRAD_BLINK_REG, 0); +} + +static int ltc3220_suspend(struct device *dev) +{ + struct ltc3220 *ltc3220 = i2c_get_clientdata(to_i2c_client(dev)); + int ret; + + ret = regmap_update_bits(ltc3220->regmap, LTC3220_COMMAND_REG, + LTC3220_SHUTDOWN_MASK, LTC3220_SHUTDOWN_MASK); + if (ret) + return ret; + + regcache_mark_dirty(ltc3220->regmap); + + return 0; +} + +static int ltc3220_resume(struct device *dev) +{ + struct ltc3220 *ltc3220 = i2c_get_clientdata(to_i2c_client(dev)); + bool quick_write_enabled; + unsigned int command_reg; + int ret; + + ret = regmap_read(ltc3220->regmap, LTC3220_COMMAND_REG, &command_reg); + if (ret) + return ret; + + quick_write_enabled = command_reg & LTC3220_QUICK_WRITE_MASK; + + if (quick_write_enabled) { + ret = regmap_update_bits(ltc3220->regmap, LTC3220_COMMAND_REG, + LTC3220_QUICK_WRITE_MASK, 0); + if (ret) + return ret; + } + + ret = regmap_update_bits(ltc3220->regmap, LTC3220_COMMAND_REG, + LTC3220_SHUTDOWN_MASK, 0); + if (ret) + return ret; + + usleep_range(10000, 12000); + + ret = regcache_sync(ltc3220->regmap); + if (ret) + return ret; + + if (quick_write_enabled) { + ret = regmap_update_bits(ltc3220->regmap, LTC3220_COMMAND_REG, + LTC3220_QUICK_WRITE_MASK, + LTC3220_QUICK_WRITE_MASK); + if (ret) + return ret; + } + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(ltc3220_pm_ops, ltc3220_suspend, ltc3220_resume); + +static int ltc3220_probe(struct i2c_client *client) +{ + struct ltc3220 *ltc3220; + bool aggregated_led_found = false; + int num_leds = 0; + u8 led_index = 0; + int ret; + + ltc3220 = devm_kzalloc(&client->dev, sizeof(*ltc3220), GFP_KERNEL); + if (!ltc3220) + return -ENOMEM; + + ltc3220->regmap = devm_regmap_init_i2c(client, <c3220_regmap_config); + if (IS_ERR(ltc3220->regmap)) + return dev_err_probe(&client->dev, PTR_ERR(ltc3220->regmap), + "Failed to initialize regmap\n"); + + ret = devm_mutex_init(&client->dev, <c3220->lock); + if (ret) + return ret; + + i2c_set_clientdata(client, ltc3220); + + ret = ltc3220_reset(ltc3220, client); + if (ret) + return dev_err_probe(&client->dev, ret, "Failed to reset device\n"); + + /* First pass: validate configuration and set up LED structures */ + device_for_each_child_node_scoped(&client->dev, child) { + struct ltc3220_uled_cfg *led; + u32 source; + + ret = fwnode_property_read_u32(child, "reg", &source); + if (ret) + return dev_err_probe(&client->dev, ret, "Couldn't read LED address\n"); + + if (!source || source > LTC3220_NUM_LEDS) + return dev_err_probe(&client->dev, -EINVAL, "LED address out of range\n"); + + if (fwnode_property_present(child, "led-sources")) { + u32 led_sources[LTC3220_NUM_LEDS]; + int count; + + if (source != 1) + return dev_err_probe(&client->dev, -EINVAL, + "Aggregated LED out of range\n"); + + if (aggregated_led_found) + return dev_err_probe(&client->dev, -EINVAL, + "One Aggregated LED only\n"); + + count = fwnode_property_count_u32(child, "led-sources"); + if (count != LTC3220_NUM_LEDS) + return dev_err_probe(&client->dev, -EINVAL, + "Aggregated mode requires all %d outputs in led-sources, got %d\n", + LTC3220_NUM_LEDS, count); + + ret = fwnode_property_read_u32_array(child, "led-sources", + led_sources, LTC3220_NUM_LEDS); + if (ret) + return dev_err_probe(&client->dev, ret, + "Failed to read led-sources array\n"); + + /* + * Validate array contents for DT correctness. The hardware + * quick-write broadcasts to all 18 channels regardless of + * array contents, but checking helps catch DT mistakes. + */ + for (int i = 0; i < LTC3220_NUM_LEDS; i++) { + if (led_sources[i] < 1 || led_sources[i] > LTC3220_NUM_LEDS) + return dev_err_probe(&client->dev, -EINVAL, + "Invalid output %u in led-sources\n", + led_sources[i]); + } + + aggregated_led_found = true; + } + + num_leds++; + + /* LED node reg/index/address goes from 1 to 18 */ + led_index = source - 1; + led = <c3220->uled_cfg[led_index]; + + if (led->registered) + return dev_err_probe(&client->dev, -EINVAL, + "Duplicate LED reg %u found\n", source); + + led->registered = true; + led->led_index = led_index; + led->reg_value = 0; + led->led_cdev.brightness_set_blocking = ltc3220_set_led_data; + led->led_cdev.brightness_get = ltc3220_get_led_data; + led->led_cdev.max_brightness = LTC3220_MAX_BRIGHTNESS; + led->led_cdev.blink_set = ltc3220_blink_set; + led->led_cdev.pattern_set = ltc3220_pattern_set; + led->led_cdev.pattern_clear = ltc3220_pattern_clear; + } + + /* + * Aggregated LED mode uses hardware quick-write to control all 18 LEDs + * simultaneously. This is mutually exclusive with individual LED control. + * See Documentation/devicetree/bindings/leds/adi,ltc3220.yaml for details + * on how to configure aggregated LED mode. + */ + if (aggregated_led_found && num_leds > 1) + return dev_err_probe(&client->dev, -EINVAL, + "Aggregated LED must be the only LED node\n"); + + if (num_leds == 0) + return dev_err_probe(&client->dev, -EINVAL, + "No LED nodes found in device tree\n"); + + if (aggregated_led_found) { + ret = regmap_update_bits(ltc3220->regmap, + LTC3220_COMMAND_REG, + LTC3220_QUICK_WRITE_MASK, + LTC3220_QUICK_WRITE_MASK); + if (ret) + return dev_err_probe(&client->dev, ret, + "Failed to set quick write mode\n"); + } + + /* Second pass: register LEDs after validation */ + device_for_each_child_node_scoped(&client->dev, child) { + struct led_init_data init_data = {}; + struct ltc3220_uled_cfg *led; + u32 source; + + ret = fwnode_property_read_u32(child, "reg", &source); + if (ret) + return ret; + + if (!source || source > LTC3220_NUM_LEDS) + return dev_err_probe(&client->dev, -EINVAL, + "LED address out of range in second pass\n"); + + init_data.fwnode = child; + init_data.devicename = "ltc3220"; + + led_index = source - 1; + led = <c3220->uled_cfg[led_index]; + + ret = devm_led_classdev_register_ext(&client->dev, &led->led_cdev, &init_data); + if (ret) + return dev_err_probe(&client->dev, ret, "Failed to register LED class\n"); + } + + return 0; +} + +static const struct of_device_id ltc3220_of_match[] = { + { .compatible = "adi,ltc3220" }, + { } +}; +MODULE_DEVICE_TABLE(of, ltc3220_of_match); + +static struct i2c_driver ltc3220_led_driver = { + .driver = { + .name = "ltc3220", + .of_match_table = ltc3220_of_match, + .pm = pm_sleep_ptr(<c3220_pm_ops), + }, + .probe = ltc3220_probe, +}; +module_i2c_driver(ltc3220_led_driver); + +MODULE_AUTHOR("Edelweise Escala "); +MODULE_DESCRIPTION("LED driver for LTC3220 controllers"); +MODULE_LICENSE("GPL"); From 2ebd1cb772b00d1223895718155a03af4c56f396 Mon Sep 17 00:00:00 2001 From: Jonas Rebmann Date: Mon, 13 Jul 2026 10:50:28 +0200 Subject: [PATCH 0197/1328] dt-bindings: leds: Add default-intensity property Document the default-intensity property to set a default color on multicolor LEDs. Update pwm-multicolor to support it and update the example to turn the LED red on boot. Signed-off-by: Jonas Rebmann Acked-by: Conor Dooley Link: https://patch.msgid.link/20260713-multicolor-default-v3-1-fb43f3f789cf@pengutronix.de Signed-off-by: Lee Jones --- Documentation/devicetree/bindings/leds/common.yaml | 10 ++++++++++ .../devicetree/bindings/leds/leds-pwm-multicolor.yaml | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/Documentation/devicetree/bindings/leds/common.yaml b/Documentation/devicetree/bindings/leds/common.yaml index e148b4277a6f..a0ef2f2f0724 100644 --- a/Documentation/devicetree/bindings/leds/common.yaml +++ b/Documentation/devicetree/bindings/leds/common.yaml @@ -73,6 +73,16 @@ properties: - keep default: off + default-intensity: + description: + The initial intensity of the LED color component. As the intensity of + each sub-LED is multiplied with the overall brightness, without this + property on a sub-LED, it may effectively be initialized at minimum + brightness regardless of its linux,default-trigger and default-brightness + properties. + $ref: /schemas/types.yaml#/definitions/uint32 + default: 0 + linux,default-trigger: description: This parameter, if present, is a string defining the trigger assigned to diff --git a/Documentation/devicetree/bindings/leds/leds-pwm-multicolor.yaml b/Documentation/devicetree/bindings/leds/leds-pwm-multicolor.yaml index a31a202afe5c..7bf687c89411 100644 --- a/Documentation/devicetree/bindings/leds/leds-pwm-multicolor.yaml +++ b/Documentation/devicetree/bindings/leds/leds-pwm-multicolor.yaml @@ -45,6 +45,8 @@ properties: color: true + default-intensity: true + required: - pwms - color @@ -63,12 +65,14 @@ examples: multi-led { color = ; + linux,default-trigger = "default-on"; function = LED_FUNCTION_INDICATOR; max-brightness = <65535>; led-red { pwms = <&pwm1 0 1000000>; color = ; + default-intensity = <65535>; }; led-green { From b6e08e0ad4cfafab2c2070456e7eeba17608a35d Mon Sep 17 00:00:00 2001 From: Ninad Naik Date: Tue, 14 Jul 2026 01:37:32 +0530 Subject: [PATCH 0198/1328] leds: bcm63138: Use %pe to print pinctrl error instead of %ld Pass the pinctrl error pointer directly to %pe to print the symbolic error name. Detected by Coccinelle. Tested by compiling. Signed-off-by: Ninad Naik Link: https://patch.msgid.link/20260713200732.2470666-1-ninadnaik07@gmail.com Signed-off-by: Lee Jones --- drivers/leds/blink/leds-bcm63138.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/leds/blink/leds-bcm63138.c b/drivers/leds/blink/leds-bcm63138.c index 45c0662df933..12b70ee31b9a 100644 --- a/drivers/leds/blink/leds-bcm63138.c +++ b/drivers/leds/blink/leds-bcm63138.c @@ -236,8 +236,8 @@ static void bcm63138_leds_create_led(struct bcm63138_leds *leds, pinctrl = devm_pinctrl_get_select_default(led->cdev.dev); if (IS_ERR(pinctrl) && PTR_ERR(pinctrl) != -ENODEV) { - dev_warn(led->cdev.dev, "Failed to select %pOF pinctrl: %ld\n", - np, PTR_ERR(pinctrl)); + dev_warn(led->cdev.dev, "Failed to select %pOF pinctrl: %pe\n", + np, pinctrl); } bit = BIT(led->pin); From 98c5c7b0d4269ecabfc86b8b49ffcfa47979e99d Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 10 Jul 2026 23:18:51 +0200 Subject: [PATCH 0199/1328] leds: gpio: Make legacy gpiolib interface optional There are still a handful of ancient mips/armv5/sh boards that use the gpio_led:gpio member to pass an old-style gpio number, but all modern users have been converted to gpio descriptors. While the CONFIG_GPIOLIB_LEGACY option that guards devm_gpio_request_one() and related helpers is currently turned on in all kernel builds, the plan is to only enable it on the few platforms that actually pass gpio numbers in any platform_data. Split out the legacy portion of the platform_data handling into a custom helper function that is guarded with in #ifdef block, to allow the the leds-gpio driver to compile cleanly when CONFIG_GPIOLIB_LEGACY gets turned off. Once the last user is converted, this function can be removed. Link: https://lore.kernel.org/all/e9252384-a55c-4a91-9c61-06e05a0b2ce4@app.fastmail.com/ Signed-off-by: Arnd Bergmann Reviewed-by: Linus Walleij Reviewed-by: Bartosz Golaszewski Reviewed-by: Andy Shevchenko Acked-by: Dmitry Torokhov # for input Link: https://patch.msgid.link/20260710211854.1371746-4-arnd@kernel.org Signed-off-by: Lee Jones --- drivers/leds/leds-gpio.c | 52 +++++++++++++++++++++++++++------------- include/linux/leds.h | 2 ++ 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/drivers/leds/leds-gpio.c b/drivers/leds/leds-gpio.c index a3428b22de3a..9cbcf7e40a15 100644 --- a/drivers/leds/leds-gpio.c +++ b/drivers/leds/leds-gpio.c @@ -9,8 +9,8 @@ #include #include #include -#include #include +#include #include #include #include @@ -212,7 +212,6 @@ static struct gpio_desc *gpio_led_get_gpiod(struct device *dev, int idx, const struct gpio_led *template) { struct gpio_desc *gpiod; - int ret; /* * This means the LED does not come from the device tree @@ -223,16 +222,29 @@ static struct gpio_desc *gpio_led_get_gpiod(struct device *dev, int idx, gpiod = devm_gpiod_get_index_optional(dev, NULL, idx, GPIOD_OUT_LOW); if (IS_ERR(gpiod)) return gpiod; - if (gpiod) { - gpiod_set_consumer_name(gpiod, template->name); - return gpiod; - } - /* - * This is the legacy code path for platform code that - * still uses GPIO numbers. Ultimately we would like to get - * rid of this block completely. - */ + gpiod_set_consumer_name(gpiod, template->name); + return gpiod; +} + +#ifdef CONFIG_GPIOLIB_LEGACY +/* + * This is the legacy code path for platform code that still uses + * GPIO numbers, mainly MIPS and SuperH board files. + * Ultimately we would like to get rid of this block completely. + * + * ppc44x-warp sets the template->gpiod directly instead of + * adding a lookup table or device properties. This is not + * much better. + */ +static struct gpio_desc *gpio_led_get_legacy_gpiod(struct device *dev, int idx, + const struct gpio_led *template) +{ + struct gpio_desc *gpiod; + int ret; + + if (template->gpiod) + return template->gpiod; /* skip leds that aren't available */ if (!gpio_is_valid(template->gpio)) @@ -252,6 +264,13 @@ static struct gpio_desc *gpio_led_get_gpiod(struct device *dev, int idx, return gpiod; } +#else +static struct gpio_desc *gpio_led_get_legacy_gpiod(struct device *dev, int idx, + const struct gpio_led *template) +{ + return template->gpiod ?: ERR_PTR(-ENOENT); +} +#endif static int gpio_led_probe(struct platform_device *pdev) { @@ -270,14 +289,13 @@ static int gpio_led_probe(struct platform_device *pdev) const struct gpio_led *template = &pdata->leds[i]; struct gpio_led_data *led_dat = &priv->leds[i]; - if (template->gpiod) - led_dat->gpiod = template->gpiod; - else + led_dat->gpiod = gpio_led_get_gpiod(dev, i, template); + if (!led_dat->gpiod) led_dat->gpiod = - gpio_led_get_gpiod(dev, i, template); + gpio_led_get_legacy_gpiod(dev, i, template); if (IS_ERR(led_dat->gpiod)) { - dev_info(dev, "Skipping unavailable LED gpio %d (%s)\n", - template->gpio, template->name); + dev_info(dev, "Skipping unavailable LED gpio %s\n", + template->name); continue; } diff --git a/include/linux/leds.h b/include/linux/leds.h index bf31c246d9e2..a515f075c29a 100644 --- a/include/linux/leds.h +++ b/include/linux/leds.h @@ -680,8 +680,10 @@ typedef int (*gpio_blink_set_t)(struct gpio_desc *desc, int state, struct gpio_led { const char *name; const char *default_trigger; +#ifdef CONFIG_GPIOLIB_LEGACY unsigned gpio; unsigned active_low : 1; +#endif unsigned retain_state_suspended : 1; unsigned panic_indicator : 1; unsigned default_state : 2; From 8d6b6c05b8e33d11e3fb3203309385e1a9cceecd Mon Sep 17 00:00:00 2001 From: Cosmo Chou Date: Wed, 15 Jul 2026 16:07:47 +0800 Subject: [PATCH 0200/1328] leds: pca9532: Fix phantom device registration on missing hardware The initial PWM and PSC register writes in pca9532_configure() do not check the return values of i2c_smbus_write_byte_data(). If the I2C device is physically absent from the bus, the write fails with -ENXIO. However, the driver ignores this error and allows probe() to complete successfully. This results in the registration of phantom LED class devices and gpiochips backed by non-existent hardware. Subsequent GPIO reads from these phantom chips return bogus values (due to -ENXIO being truncated to an unsigned char in pca9532_gpio_get_value()), silently corrupting hardware state tracking in userspace. Propagate the I2C write failures back to probe() so the driver core can gracefully abort binding and release devres-managed resources. Fixes: e14fa82439d3 ("leds: Add pca9532 led driver") Signed-off-by: Cosmo Chou Reviewed-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260715080747.1638097-1-chou.cosmo@gmail.com Signed-off-by: Lee Jones --- drivers/leds/leds-pca9532.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/leds/leds-pca9532.c b/drivers/leds/leds-pca9532.c index 2d37e00e459d..b2e081c8f139 100644 --- a/drivers/leds/leds-pca9532.c +++ b/drivers/leds/leds-pca9532.c @@ -397,10 +397,14 @@ static int pca9532_configure(struct i2c_client *client, for (i = 0; i < 2; i++) { data->pwm[i] = pdata->pwm[i]; data->psc[i] = pdata->psc[i]; - i2c_smbus_write_byte_data(client, PCA9532_REG_PWM(maxleds, i), - data->pwm[i]); - i2c_smbus_write_byte_data(client, PCA9532_REG_PSC(maxleds, i), - data->psc[i]); + err = i2c_smbus_write_byte_data(client, PCA9532_REG_PWM(maxleds, i), + data->pwm[i]); + if (err < 0) + return err; + err = i2c_smbus_write_byte_data(client, PCA9532_REG_PSC(maxleds, i), + data->psc[i]); + if (err < 0) + return err; } data->hw_blink = true; From 4bcba49984ff8c77729f003dd32082b10c02c23b Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Thu, 23 Jul 2026 11:39:51 +0200 Subject: [PATCH 0201/1328] clk: sunxi-ng: mux: fix determine helper rate propagation Applying the pre divider on the parent rate is wrong because, while handling rate propagation through determine_rate(), the framework will likely round the parent rate again while cycling through the possibilities, throwing away the prediv applied. This means, the parent rate will then be wrong when the prediv is unapplied from a parent rate on which it was never applied to begin with. The right way to do it is to unapply the prediv from the requested rate, which is the wanted rate at the input on the clock element, and pass this to framework to do its thing. Change the determine rate mux helper in this way. Fixes: 1c8d7af61b37 ("clk: sunxi-ng: convert from divider_round_rate_parent() to divider_determine_rate()") Signed-off-by: Jerome Brunet Link: https://patch.msgid.link/20260723-a733-rtc-v7-1-8fd68aab94ae@baylibre.com Signed-off-by: Chen-Yu Tsai --- drivers/clk/sunxi-ng/ccu_mux.c | 57 +++++++++++++++++----------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/drivers/clk/sunxi-ng/ccu_mux.c b/drivers/clk/sunxi-ng/ccu_mux.c index 09230728c400..4503c9780c39 100644 --- a/drivers/clk/sunxi-ng/ccu_mux.c +++ b/drivers/clk/sunxi-ng/ccu_mux.c @@ -92,66 +92,65 @@ int ccu_mux_helper_determine_rate(struct ccu_common *common, struct clk_rate_request adj_req = *req; best_parent = clk_hw_get_parent(hw); - best_parent_rate = clk_hw_get_rate(best_parent); - + adj_req.best_parent_rate = clk_hw_get_rate(best_parent); adj_req.best_parent_hw = best_parent; - adj_req.best_parent_rate = ccu_mux_helper_apply_prediv(common, cm, -1, - best_parent_rate); + + /* + * This effectively treats the predivider as a postdivider. + * It stays mathematically correct and ensures whatever + * round() will do stays correct while walking the tree. + * It may query the parent rate too while handling rate + * propagation. + */ + adj_req.rate = ccu_mux_helper_unapply_prediv(common, cm, -1, + req->rate); ret = round(cm, &adj_req, data); if (ret) return ret; - best_rate = adj_req.rate; - /* - * best_parent_rate might have been modified by our clock. - * Unapply the pre-divider if there's one, and give - * the actual frequency the parent needs to run at. + * parent_rate might have been modified by our clock as part + * of the rate propagation mechanism. Same goes below. */ - best_parent_rate = ccu_mux_helper_unapply_prediv(common, cm, -1, - adj_req.best_parent_rate); + best_parent_rate = adj_req.best_parent_rate; + best_rate = ccu_mux_helper_apply_prediv(common, cm, -1, + adj_req.rate); goto out; } for (i = 0; i < clk_hw_get_num_parents(hw); i++) { struct clk_rate_request tmp_req = *req; - unsigned long parent_rate; + unsigned long rate; struct clk_hw *parent; parent = clk_hw_get_parent_by_index(hw, i); if (!parent) continue; - parent_rate = ccu_mux_helper_apply_prediv(common, cm, i, - clk_hw_get_rate(parent)); - tmp_req.best_parent_hw = parent; - tmp_req.best_parent_rate = parent_rate; + tmp_req.best_parent_rate = clk_hw_get_rate(parent); + tmp_req.rate = ccu_mux_helper_unapply_prediv(common, cm, i, + req->rate); ret = round(cm, &tmp_req, data); if (ret) continue; - /* - * parent_rate might have been modified by our clock. - * Unapply the pre-divider if there's one, and give - * the actual frequency the parent needs to run at. - */ - parent_rate = ccu_mux_helper_unapply_prediv(common, cm, i, - tmp_req.best_parent_rate); + rate = ccu_mux_helper_apply_prediv(common, cm, i, + tmp_req.rate); - if (tmp_req.rate == req->rate) { + if (rate == req->rate) { best_parent = parent; - best_parent_rate = parent_rate; - best_rate = tmp_req.rate; + best_parent_rate = tmp_req.best_parent_rate; + best_rate = rate; goto out; } - if (ccu_is_better_rate(common, req->rate, tmp_req.rate, best_rate)) { - best_rate = tmp_req.rate; - best_parent_rate = parent_rate; + if (ccu_is_better_rate(common, req->rate, rate, best_rate)) { + best_rate = rate; + best_parent_rate = tmp_req.best_parent_rate; best_parent = parent; } } From 7bbbfcd4e4956a126b9599bd8aa316cf0c63c8ed Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Thu, 23 Jul 2026 11:39:52 +0200 Subject: [PATCH 0202/1328] clk: sunxi-ng: div: add read-only operation support Add support for sunxi-ng read-only dividers. This will be useful to the a733 oscillator detection logic. Reviewed-by: Chen-Yu Tsai Signed-off-by: Jerome Brunet Link: https://patch.msgid.link/20260723-a733-rtc-v7-2-8fd68aab94ae@baylibre.com Signed-off-by: Chen-Yu Tsai --- drivers/clk/sunxi-ng/ccu_div.c | 31 +++++++++++++++++++++++++++++-- drivers/clk/sunxi-ng/ccu_div.h | 1 + 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/drivers/clk/sunxi-ng/ccu_div.c b/drivers/clk/sunxi-ng/ccu_div.c index 62d680ccb524..c385e0160f13 100644 --- a/drivers/clk/sunxi-ng/ccu_div.c +++ b/drivers/clk/sunxi-ng/ccu_div.c @@ -20,8 +20,22 @@ static int ccu_div_determine_rate_helper(struct ccu_mux_internal *mux, if (cd->common.features & CCU_FEATURE_FIXED_POSTDIV) req->rate *= cd->fixed_post_div; - ret = divider_determine_rate(&cd->common.hw, req, cd->div.table, - cd->div.width, cd->div.flags); + if (cd->div.flags & CLK_DIVIDER_READ_ONLY) { + unsigned long val; + u32 reg; + + reg = readl(cd->common.base + cd->common.reg); + val = reg >> cd->div.shift; + val &= (1 << cd->div.width) - 1; + + ret = divider_ro_determine_rate(&cd->common.hw, req, cd->div.table, + cd->div.width, cd->div.flags, val); + + } else { + ret = divider_determine_rate(&cd->common.hw, req, cd->div.table, + cd->div.width, cd->div.flags); + } + if (ret) return ret; @@ -143,3 +157,16 @@ const struct clk_ops ccu_div_ops = { .set_rate = ccu_div_set_rate, }; EXPORT_SYMBOL_NS_GPL(ccu_div_ops, "SUNXI_CCU"); + +const struct clk_ops ccu_rodiv_ops = { + .disable = ccu_div_disable, + .enable = ccu_div_enable, + .is_enabled = ccu_div_is_enabled, + + .get_parent = ccu_div_get_parent, + .set_parent = ccu_div_set_parent, + + .determine_rate = ccu_div_determine_rate, + .recalc_rate = ccu_div_recalc_rate, +}; +EXPORT_SYMBOL_NS_GPL(ccu_rodiv_ops, "SUNXI_CCU"); diff --git a/drivers/clk/sunxi-ng/ccu_div.h b/drivers/clk/sunxi-ng/ccu_div.h index be00b3277e97..a30a92780a05 100644 --- a/drivers/clk/sunxi-ng/ccu_div.h +++ b/drivers/clk/sunxi-ng/ccu_div.h @@ -300,5 +300,6 @@ static inline struct ccu_div *hw_to_ccu_div(struct clk_hw *hw) } extern const struct clk_ops ccu_div_ops; +extern const struct clk_ops ccu_rodiv_ops; #endif /* _CCU_DIV_H_ */ From 176184e7f799fc18fe9cae63e21d7b16b41a0d59 Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Thu, 23 Jul 2026 11:39:53 +0200 Subject: [PATCH 0203/1328] clk: sunxi-ng: sun6i-rtc: split main oscillator div and gate On the a733 the "osc24M-32k" clock has the same gate bits as the previously supported SoC but a different divider implementation. Instead of a fixed 750 divider, the divider is selected based on the rate of the oscillator. It can be seen as a simple read-only divider. On the A733 the gate and the divider are on different register which is not supported by sunxi combined clocks. These element will be split on the A733. Split them for the older SoC as well to keep on sharing the gate definition. Reviewed-by: Chen-Yu Tsai Signed-off-by: Jerome Brunet Link: https://patch.msgid.link/20260723-a733-rtc-v7-3-8fd68aab94ae@baylibre.com Signed-off-by: Chen-Yu Tsai --- drivers/clk/sunxi-ng/ccu-sun6i-rtc.c | 20 +++++++++++--------- drivers/clk/sunxi-ng/ccu-sun6i-rtc.h | 3 ++- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/drivers/clk/sunxi-ng/ccu-sun6i-rtc.c b/drivers/clk/sunxi-ng/ccu-sun6i-rtc.c index b24c8b196e66..25dd87e78eb7 100644 --- a/drivers/clk/sunxi-ng/ccu-sun6i-rtc.c +++ b/drivers/clk/sunxi-ng/ccu-sun6i-rtc.c @@ -218,17 +218,18 @@ static const struct clk_parent_data osc24M[] = { { .fw_name = "hosc", .name = "osc24M" } }; -static struct ccu_gate osc24M_32k_clk = { - .enable = BIT(16), - .common = { - .reg = LOSC_OUT_GATING_REG, - .prediv = 750, - .features = CCU_FEATURE_ALL_PREDIV, - .hw.init = CLK_HW_INIT_PARENTS_DATA("osc24M-32k", osc24M, - &ccu_gate_ops, 0), - }, +static struct clk_fixed_factor osc24M_32k_div_clk = { + .mult = 1, + .div = 750, + .hw.init = CLK_HW_INIT_PARENTS_DATA("osc24M-32k-div", + osc24M, + &clk_fixed_factor_ops, + 0), }; +static SUNXI_CCU_GATE_HW(osc24M_32k_clk, "osc24M-32k", &osc24M_32k_div_clk.hw, + LOSC_OUT_GATING_REG, BIT(16), 0); + static const struct clk_hw *rtc_32k_parents[] = { &osc32k_clk.common.hw, &osc24M_32k_clk.common.hw @@ -286,6 +287,7 @@ static struct clk_hw_onecell_data sun6i_rtc_ccu_hw_clks = { [CLK_EXT_OSC32K_GATE] = &ext_osc32k_gate_clk.common.hw, [CLK_OSC24M_32K] = &osc24M_32k_clk.common.hw, [CLK_RTC_32K] = &rtc_32k_clk.common.hw, + [CLK_OSC24M_32K_DIV] = &osc24M_32k_div_clk.hw, }, }; diff --git a/drivers/clk/sunxi-ng/ccu-sun6i-rtc.h b/drivers/clk/sunxi-ng/ccu-sun6i-rtc.h index 9ae821fc2599..ab7b92b47f59 100644 --- a/drivers/clk/sunxi-ng/ccu-sun6i-rtc.h +++ b/drivers/clk/sunxi-ng/ccu-sun6i-rtc.h @@ -9,7 +9,8 @@ #define CLK_EXT_OSC32K_GATE 4 #define CLK_OSC24M_32K 5 #define CLK_RTC_32K 6 +#define CLK_OSC24M_32K_DIV 7 -#define CLK_NUMBER (CLK_RTC_32K + 1) +#define CLK_NUMBER (CLK_OSC24M_32K_DIV + 1) #endif /* _CCU_SUN6I_RTC_H */ From 0a136efc0fc2d53b1b1389b3c3fb0f76edca38bf Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Thu, 23 Jul 2026 11:39:54 +0200 Subject: [PATCH 0204/1328] clk: sunxi-ng: sun6i-rtc: add a733 support Add support for the sun60i a733 CCU RTC. Compared to the a523, this SoC has a different input oscillator divider which auto-detects the oscillator rate and select a divider to provide a fixed 32768Hz clock. It also provides several phy reference clocks with dedicated clock gates. Reviewed-by: Chen-Yu Tsai Signed-off-by: Jerome Brunet Link: https://patch.msgid.link/20260723-a733-rtc-v7-4-8fd68aab94ae@baylibre.com Signed-off-by: Chen-Yu Tsai --- drivers/clk/sunxi-ng/ccu-sun6i-rtc.c | 77 ++++++++++++++++++++++++++-- drivers/clk/sunxi-ng/ccu-sun6i-rtc.h | 2 +- 2 files changed, 75 insertions(+), 4 deletions(-) diff --git a/drivers/clk/sunxi-ng/ccu-sun6i-rtc.c b/drivers/clk/sunxi-ng/ccu-sun6i-rtc.c index 25dd87e78eb7..98ae10307f43 100644 --- a/drivers/clk/sunxi-ng/ccu-sun6i-rtc.c +++ b/drivers/clk/sunxi-ng/ccu-sun6i-rtc.c @@ -44,9 +44,15 @@ #define DCXO_CTRL_REG 0x160 #define DCXO_CTRL_CLK16M_RC_EN BIT(0) +#define DCXO_GATING_REG 0x16c + +#define CLK_NUMBER_NO_GATES (CLK_OSC24M_32K_DIV + 1) + struct sun6i_rtc_match_data { bool have_ext_osc32k : 1; bool have_iosc_calibration : 1; + bool have_dcxo_status : 1; + bool have_phy_ref_gates : 1; bool rtc_32k_single_parent : 1; const struct clk_parent_data *osc32k_fanout_parents; u8 osc32k_fanout_nparents; @@ -213,7 +219,12 @@ static struct ccu_mux osc32k_clk = { }, }; -/* This falls back to the global name for fwnodes without a named reference. */ +/* + * This falls back to the global name for fwnodes without a named reference. + * NOTE: osc24M name might be misleading the oscillator could also be a 26MHz + * or a 19.2MHz one starting with the a733. The original name is kept anyway + * in case anything is relying on it. + */ static const struct clk_parent_data osc24M[] = { { .fw_name = "hosc", .name = "osc24M" } }; @@ -227,8 +238,30 @@ static struct clk_fixed_factor osc24M_32k_div_clk = { 0), }; -static SUNXI_CCU_GATE_HW(osc24M_32k_clk, "osc24M-32k", &osc24M_32k_div_clk.hw, - LOSC_OUT_GATING_REG, BIT(16), 0); +static struct clk_div_table osc24M_32k_div_a733_table[] = { + { .val = 0, .div = 732 }, + { .val = 1, .div = 586 }, + { .val = 2, .div = 793 }, + { .val = 3, .div = 732 }, + { /* Sentinel */ }, +}; + +static struct ccu_div osc24M_32k_div_a733_clk = { + .enable = BIT(1), + .div = _SUNXI_CCU_DIV_TABLE_FLAGS(14, 2, + osc24M_32k_div_a733_table, + CLK_DIVIDER_READ_ONLY), + .common = { + .reg = DCXO_CTRL_REG, + .hw.init = CLK_HW_INIT_PARENTS_DATA("osc24M-32k-div", + osc24M, + &ccu_rodiv_ops, + 0), + }, +}; + +static SUNXI_CCU_GATE(osc24M_32k_clk, "osc24M-32k", "osc24M-32k-div", + LOSC_OUT_GATING_REG, BIT(16), 0); static const struct clk_hw *rtc_32k_parents[] = { &osc32k_clk.common.hw, @@ -267,6 +300,15 @@ static struct ccu_mux osc32k_fanout_clk = { }, }; +static SUNXI_CCU_GATE_FW(hosc_serdes1_clk, "hosc-serdes1", "hosc", + DCXO_GATING_REG, BIT(5), 0); +static SUNXI_CCU_GATE_FW(hosc_serdes0_clk, "hosc-serdes0", "hosc", + DCXO_GATING_REG, BIT(4), 0); +static SUNXI_CCU_GATE_FW(hosc_hdmi_clk, "hosc-hdmi", "hosc", + DCXO_GATING_REG, BIT(1), 0); +static SUNXI_CCU_GATE_FW(hosc_ufs_clk, "hosc-ufs", "hosc", + DCXO_GATING_REG, BIT(0), 0); + static struct ccu_common *sun6i_rtc_ccu_clks[] = { &iosc_clk, &iosc_32k_clk, @@ -275,6 +317,11 @@ static struct ccu_common *sun6i_rtc_ccu_clks[] = { &osc24M_32k_clk.common, &rtc_32k_clk.common, &osc32k_fanout_clk.common, + &osc24M_32k_div_a733_clk.common, + &hosc_serdes1_clk.common, + &hosc_serdes0_clk.common, + &hosc_hdmi_clk.common, + &hosc_ufs_clk.common, }; static struct clk_hw_onecell_data sun6i_rtc_ccu_hw_clks = { @@ -288,6 +335,10 @@ static struct clk_hw_onecell_data sun6i_rtc_ccu_hw_clks = { [CLK_OSC24M_32K] = &osc24M_32k_clk.common.hw, [CLK_RTC_32K] = &rtc_32k_clk.common.hw, [CLK_OSC24M_32K_DIV] = &osc24M_32k_div_clk.hw, + [CLK_HOSC_UFS] = &hosc_ufs_clk.common.hw, + [CLK_HOSC_HDMI] = &hosc_hdmi_clk.common.hw, + [CLK_HOSC_SERDES0] = &hosc_serdes0_clk.common.hw, + [CLK_HOSC_SERDES1] = &hosc_serdes1_clk.common.hw, }, }; @@ -330,6 +381,15 @@ static const struct sun6i_rtc_match_data sun55i_a523_rtc_ccu_data = { .osc32k_fanout_nparents = ARRAY_SIZE(sun50i_r329_osc32k_fanout_parents), }; +static const struct sun6i_rtc_match_data sun60i_a733_rtc_ccu_data = { + .have_ext_osc32k = true, + .have_iosc_calibration = true, + .have_dcxo_status = true, + .have_phy_ref_gates = true, + .osc32k_fanout_parents = sun50i_r329_osc32k_fanout_parents, + .osc32k_fanout_nparents = ARRAY_SIZE(sun50i_r329_osc32k_fanout_parents), +}; + static const struct of_device_id sun6i_rtc_ccu_match[] = { { .compatible = "allwinner,sun50i-h616-rtc", @@ -343,6 +403,10 @@ static const struct of_device_id sun6i_rtc_ccu_match[] = { .compatible = "allwinner,sun55i-a523-rtc", .data = &sun55i_a523_rtc_ccu_data, }, + { + .compatible = "allwinner,sun60i-a733-rtc", + .data = &sun60i_a733_rtc_ccu_data, + }, {}, }; MODULE_DEVICE_TABLE(of, sun6i_rtc_ccu_match); @@ -375,6 +439,13 @@ int sun6i_rtc_ccu_probe(struct device *dev, void __iomem *reg) osc32k_fanout_init_data.parent_data = data->osc32k_fanout_parents; osc32k_fanout_init_data.num_parents = data->osc32k_fanout_nparents; + if (data->have_dcxo_status) + sun6i_rtc_ccu_hw_clks.hws[CLK_OSC24M_32K_DIV] = + &osc24M_32k_div_a733_clk.common.hw; + + if (!data->have_phy_ref_gates) + sun6i_rtc_ccu_hw_clks.num = CLK_NUMBER_NO_GATES; + return devm_sunxi_ccu_probe(dev, reg, &sun6i_rtc_ccu_desc); } diff --git a/drivers/clk/sunxi-ng/ccu-sun6i-rtc.h b/drivers/clk/sunxi-ng/ccu-sun6i-rtc.h index ab7b92b47f59..4f4f4cb00f1d 100644 --- a/drivers/clk/sunxi-ng/ccu-sun6i-rtc.h +++ b/drivers/clk/sunxi-ng/ccu-sun6i-rtc.h @@ -11,6 +11,6 @@ #define CLK_RTC_32K 6 #define CLK_OSC24M_32K_DIV 7 -#define CLK_NUMBER (CLK_OSC24M_32K_DIV + 1) +#define CLK_NUMBER (CLK_HOSC_SERDES1 + 1) #endif /* _CCU_SUN6I_RTC_H */ From d6ae9f9b9bd373461442f17ef157ed9ee850a413 Mon Sep 17 00:00:00 2001 From: Andrei Kuchynski Date: Mon, 8 Jun 2026 21:15:17 +0000 Subject: [PATCH 0205/1328] platform/chrome: cros_ec_proto: Introduce cros_ec_read_features helper Extract the EC feature-reading logic from cros_ec_check_features() into cros_ec_read_features() helper function. Currently, cros_ec_check_features() swallows command transfer errors. By isolating the transaction logic into an explicit helper that returns the actual transfer error code, subsequent callers (such as the cros_ec_dev driver during device probing) can catch a read error. Signed-off-by: Andrei Kuchynski Acked-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260608211518.2214740-2-akuchynski@chromium.org Signed-off-by: Lee Jones --- drivers/platform/chrome/cros_ec_proto.c | 30 +++++++++++++++------ include/linux/platform_data/cros_ec_proto.h | 2 ++ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/drivers/platform/chrome/cros_ec_proto.c b/drivers/platform/chrome/cros_ec_proto.c index 1d8d9168ec1a..724d1313f6b2 100644 --- a/drivers/platform/chrome/cros_ec_proto.c +++ b/drivers/platform/chrome/cros_ec_proto.c @@ -946,6 +946,27 @@ u32 cros_ec_get_host_event(struct cros_ec_device *ec_dev) } EXPORT_SYMBOL(cros_ec_get_host_event); +/** + * cros_ec_read_features() - Read EC features + * + * @ec: EC device. + * + * Return: >= 0 on success, negative error number on failure. + */ +int cros_ec_read_features(struct cros_ec_dev *ec) +{ + int ret = cros_ec_cmd(ec->ec_dev, 0, EC_CMD_GET_FEATURES + ec->cmd_offset, + NULL, 0, &ec->features, sizeof(ec->features)); + + if (ret < 0) { + dev_warn(ec->dev, "cannot get EC features: %d\n", ret); + memset(&ec->features, 0, sizeof(ec->features)); + } + + return ret; +} +EXPORT_SYMBOL_GPL(cros_ec_read_features); + /** * cros_ec_check_features() - Test for the presence of EC features * @@ -960,17 +981,10 @@ EXPORT_SYMBOL(cros_ec_get_host_event); bool cros_ec_check_features(struct cros_ec_dev *ec, int feature) { struct ec_response_get_features *features = &ec->features; - int ret; if (features->flags[0] == -1U && features->flags[1] == -1U) { /* features bitmap not read yet */ - ret = cros_ec_cmd(ec->ec_dev, 0, EC_CMD_GET_FEATURES + ec->cmd_offset, - NULL, 0, features, sizeof(*features)); - if (ret < 0) { - dev_warn(ec->dev, "cannot get EC features: %d\n", ret); - memset(features, 0, sizeof(*features)); - } - + cros_ec_read_features(ec); dev_dbg(ec->dev, "EC features %08x %08x\n", features->flags[0], features->flags[1]); } diff --git a/include/linux/platform_data/cros_ec_proto.h b/include/linux/platform_data/cros_ec_proto.h index 6ed1c4c5ce2e..a1ccecf5e1f8 100644 --- a/include/linux/platform_data/cros_ec_proto.h +++ b/include/linux/platform_data/cros_ec_proto.h @@ -271,6 +271,8 @@ int cros_ec_get_next_event(struct cros_ec_device *ec_dev, u32 cros_ec_get_host_event(struct cros_ec_device *ec_dev); +int cros_ec_read_features(struct cros_ec_dev *ec); + bool cros_ec_check_features(struct cros_ec_dev *ec, int feature); int cros_ec_get_sensor_count(struct cros_ec_dev *ec); From 5855464dff6a2171847463775c7cdac92742e0f9 Mon Sep 17 00:00:00 2001 From: Andrei Kuchynski Date: Mon, 8 Jun 2026 21:15:18 +0000 Subject: [PATCH 0206/1328] mfd: cros_ec: Read EC features during probe to catch transfer error cros_ec_check_features() does not return an error if the underlying EC_CMD_GET_FEATURES command fails. Consequently, when the Fingerprint device fails to respond, the probe function ignores the failure and falls back to installing it as 'cros_ec' device instead of 'cros_fp'. This leads to a sysfs duplicate filename collision later when the real 'cros_ec' device attempts to register: cros-ec-spi spi5.0: EC failed to respond in time cros-ec-dev.19.auto: cannot get EC features: -110 sysfs : cannot create duplicate filename '/class/chromeos/cros_ec' : sysfs_do_create_link_sd+0x94/0xdc : ec_device_probe+0x150/0x4f0 Fix this by explicitly calling the newly introduced cros_ec_read_features() function. If the transfer fails, abort the broken device initialization. Move the initialization of class_dev before this call to prevent a missing release() callback warning on the error path. Signed-off-by: Andrei Kuchynski Link: https://patch.msgid.link/20260608211518.2214740-3-akuchynski@chromium.org Signed-off-by: Lee Jones --- drivers/mfd/cros_ec_dev.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/drivers/mfd/cros_ec_dev.c b/drivers/mfd/cros_ec_dev.c index 11ee1146cf71..4add37f30414 100644 --- a/drivers/mfd/cros_ec_dev.c +++ b/drivers/mfd/cros_ec_dev.c @@ -198,6 +198,17 @@ static int ec_device_probe(struct platform_device *pdev) ec->features.flags[1] = -1U; /* Not cached yet */ device_initialize(&ec->class_dev); + /* + * Add the class device + */ + ec->class_dev.class = &cros_class; + ec->class_dev.parent = dev; + ec->class_dev.release = cros_ec_class_release; + + retval = cros_ec_read_features(ec); + if (retval < 0) + goto failed; + for (i = 0; i < ARRAY_SIZE(cros_mcu_devices); i++) { /* * Check whether this is actually a dedicated MCU rather @@ -215,13 +226,6 @@ static int ec_device_probe(struct platform_device *pdev) } } - /* - * Add the class device - */ - ec->class_dev.class = &cros_class; - ec->class_dev.parent = dev; - ec->class_dev.release = cros_ec_class_release; - retval = dev_set_name(&ec->class_dev, "%s", ec_platform->ec_name); if (retval) { dev_err(dev, "dev_set_name failed => %d\n", retval); From 50507d6fc80117c3448029e4bd335704cd5e58ec Mon Sep 17 00:00:00 2001 From: Baruch Siach Date: Thu, 23 Jul 2026 20:25:54 +0300 Subject: [PATCH 0207/1328] apparmor: switch website link to https Should make harder for MITM to redirect to somewhere else. Signed-off-by: Baruch Siach Signed-off-by: John Johansen --- security/apparmor/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig index 1e3bd44643da..e1bfd164a23a 100644 --- a/security/apparmor/Kconfig +++ b/security/apparmor/Kconfig @@ -11,7 +11,7 @@ config SECURITY_APPARMOR This enables the AppArmor security module. Required userspace tools (if they are not included in your distribution) and further information may be found at - http://apparmor.wiki.kernel.org + https://apparmor.wiki.kernel.org If you are unsure how to answer this question, answer N. From f9cbc4bddb636dd9d446bfba31f58fbceaf09424 Mon Sep 17 00:00:00 2001 From: Qiang Yu Date: Tue, 21 Jul 2026 22:58:56 -0700 Subject: [PATCH 0208/1328] dt-bindings: clock: qcom: Move glymur TCSR to own binding The QREF block supplies reference clocks to PCIe PHYs and requires dedicated LDO supplies to operate. The digital control interface for QREF (clkref_en registers) resides in TCSR on glymur. Since QREF has no dedicated DT node of its own, these supply properties are placed in the TCSR node which acts as the control interface for QREF. Add a dedicated binding file for qcom,glymur-tcsr and document the supply properties. As this binding will grow to cover more SoCs, mark the required supplies per compatible using an allOf/if/then conditional. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Qiang Yu Link: https://lore.kernel.org/r/20260721-tcsr_qre_0721-v10-1-d2fa68c63b78@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- .../bindings/clock/qcom,glymur-tcsr.yaml | 126 ++++++++++++++++++ .../bindings/clock/qcom,sm8550-tcsr.yaml | 2 - 2 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 Documentation/devicetree/bindings/clock/qcom,glymur-tcsr.yaml diff --git a/Documentation/devicetree/bindings/clock/qcom,glymur-tcsr.yaml b/Documentation/devicetree/bindings/clock/qcom,glymur-tcsr.yaml new file mode 100644 index 000000000000..ec89feff89e4 --- /dev/null +++ b/Documentation/devicetree/bindings/clock/qcom,glymur-tcsr.yaml @@ -0,0 +1,126 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/clock/qcom,glymur-tcsr.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm TCSR Clock Controller on Glymur + +maintainers: + - Bjorn Andersson + - Taniya Das + +description: | + Qualcomm TCSR clock control module provides the clocks, resets and + power domains on Glymur + + See also: + - include/dt-bindings/clock/qcom,glymur-tcsr.h + +properties: + compatible: + items: + - enum: + - qcom,glymur-tcsr + - const: syscon + + clocks: + items: + - description: TCXO pad clock + + reg: + maxItems: 1 + + '#clock-cells': + const: 1 + + '#reset-cells': + const: 1 + + vdda-qrefrpt0-0p9-supply: true + vdda-qrefrpt1-0p9-supply: true + vdda-qrefrpt2-0p9-supply: true + vdda-qrefrpt3-0p9-supply: true + vdda-qrefrpt4-0p9-supply: true + vdda-qrefrpt5-0p9-supply: true + vdda-qrefrx0-0p9-supply: true + vdda-qrefrx1-0p9-supply: true + vdda-qrefrx2-0p9-supply: true + vdda-qrefrx3-0p9-supply: true + vdda-qrefrx4-0p9-supply: true + vdda-qrefrx5-0p9-supply: true + vdda-qreftx0-0p9-supply: true + vdda-qreftx0-1p2-supply: true + vdda-qreftx1-0p9-supply: true + vdda-refgen3-0p9-supply: true + vdda-refgen3-1p2-supply: true + vdda-refgen4-0p9-supply: true + vdda-refgen4-1p2-supply: true + +allOf: + - if: + properties: + compatible: + contains: + const: qcom,glymur-tcsr + then: + required: + - vdda-qrefrpt0-0p9-supply + - vdda-qrefrpt1-0p9-supply + - vdda-qrefrpt2-0p9-supply + - vdda-qrefrpt3-0p9-supply + - vdda-qrefrpt4-0p9-supply + - vdda-qrefrx0-0p9-supply + - vdda-qrefrx1-0p9-supply + - vdda-qrefrx2-0p9-supply + - vdda-qrefrx4-0p9-supply + - vdda-qrefrx5-0p9-supply + - vdda-qreftx0-0p9-supply + - vdda-qreftx0-1p2-supply + - vdda-qreftx1-0p9-supply + - vdda-refgen3-0p9-supply + - vdda-refgen3-1p2-supply + - vdda-refgen4-0p9-supply + - vdda-refgen4-1p2-supply + +required: + - compatible + - clocks + +additionalProperties: false + +examples: + - | + #include + + soc { + #address-cells = <2>; + #size-cells = <2>; + + clock-controller@1fd5000 { + compatible = "qcom,glymur-tcsr", "syscon"; + reg = <0x0 0x1fd5000 0x0 0x21000>; + clocks = <&rpmhcc RPMH_CXO_CLK>; + #clock-cells = <1>; + #reset-cells = <1>; + vdda-qrefrpt0-0p9-supply = <&vreg_l1a>; + vdda-qrefrpt1-0p9-supply = <&vreg_l1a>; + vdda-qrefrpt2-0p9-supply = <&vreg_l1a>; + vdda-qrefrpt3-0p9-supply = <&vreg_l1a>; + vdda-qrefrpt4-0p9-supply = <&vreg_l1a>; + vdda-qrefrx0-0p9-supply = <&vreg_l1a>; + vdda-qrefrx1-0p9-supply = <&vreg_l1a>; + vdda-qrefrx2-0p9-supply = <&vreg_l1a>; + vdda-qrefrx4-0p9-supply = <&vreg_l1a>; + vdda-qrefrx5-0p9-supply = <&vreg_l1a>; + vdda-qreftx0-0p9-supply = <&vreg_l1a>; + vdda-qreftx0-1p2-supply = <&vreg_l2a>; + vdda-qreftx1-0p9-supply = <&vreg_l1a>; + vdda-refgen3-0p9-supply = <&vreg_l1a>; + vdda-refgen3-1p2-supply = <&vreg_l2a>; + vdda-refgen4-0p9-supply = <&vreg_l1a>; + vdda-refgen4-1p2-supply = <&vreg_l2a>; + }; + }; + +... diff --git a/Documentation/devicetree/bindings/clock/qcom,sm8550-tcsr.yaml b/Documentation/devicetree/bindings/clock/qcom,sm8550-tcsr.yaml index 08824f848973..19ae0634b922 100644 --- a/Documentation/devicetree/bindings/clock/qcom,sm8550-tcsr.yaml +++ b/Documentation/devicetree/bindings/clock/qcom,sm8550-tcsr.yaml @@ -16,7 +16,6 @@ description: | See also: - include/dt-bindings/clock/qcom,eliza-tcsr.h - - include/dt-bindings/clock/qcom,glymur-tcsr.h - include/dt-bindings/clock/qcom,hawi-tcsrcc.h - include/dt-bindings/clock/qcom,nord-tcsrcc.h - include/dt-bindings/clock/qcom,sm8550-tcsr.h @@ -28,7 +27,6 @@ properties: items: - enum: - qcom,eliza-tcsr - - qcom,glymur-tcsr - qcom,hawi-tcsrcc - qcom,kaanapali-tcsr - qcom,milos-tcsr From 0998cf30b0e0d02dbcdfc1f989b745137b21f682 Mon Sep 17 00:00:00 2001 From: Qiang Yu Date: Tue, 21 Jul 2026 22:58:57 -0700 Subject: [PATCH 0209/1328] dt-bindings: clock: qcom,glymur-tcsr: Add mahua support Mahua shares the same QREF TX/RPT/RX component naming as Glymur, but has a different topology: a single QREF block fed by REFGEN4 only, rather than the two independent blocks fed by REFGEN3 and REFGEN4 on Glymur. Add qcom,mahua-tcsr compatible and document its required supply properties. Note that REFGEN4 is supplied by regulators vdda-refgen3-1p2 and vdda-refgen3-0p9 on Mahua. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Qiang Yu Link: https://lore.kernel.org/r/20260721-tcsr_qre_0721-v10-2-d2fa68c63b78@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- .../bindings/clock/qcom,glymur-tcsr.yaml | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Documentation/devicetree/bindings/clock/qcom,glymur-tcsr.yaml b/Documentation/devicetree/bindings/clock/qcom,glymur-tcsr.yaml index ec89feff89e4..2b6422627165 100644 --- a/Documentation/devicetree/bindings/clock/qcom,glymur-tcsr.yaml +++ b/Documentation/devicetree/bindings/clock/qcom,glymur-tcsr.yaml @@ -22,6 +22,7 @@ properties: items: - enum: - qcom,glymur-tcsr + - qcom,mahua-tcsr - const: syscon clocks: @@ -82,6 +83,25 @@ allOf: - vdda-refgen3-1p2-supply - vdda-refgen4-0p9-supply - vdda-refgen4-1p2-supply + - if: + properties: + compatible: + contains: + const: qcom,mahua-tcsr + then: + required: + - vdda-qrefrpt0-0p9-supply + - vdda-qrefrpt1-0p9-supply + - vdda-qrefrpt2-0p9-supply + - vdda-qrefrpt3-0p9-supply + - vdda-qrefrpt4-0p9-supply + - vdda-qrefrpt5-0p9-supply + - vdda-qrefrx1-0p9-supply + - vdda-qrefrx2-0p9-supply + - vdda-qrefrx3-0p9-supply + - vdda-qreftx1-0p9-supply + - vdda-refgen3-0p9-supply + - vdda-refgen3-1p2-supply required: - compatible From 22c70e5732005356a4bc2601e7dcf878aab101e0 Mon Sep 17 00:00:00 2001 From: Qiang Yu Date: Tue, 21 Jul 2026 22:58:58 -0700 Subject: [PATCH 0210/1328] clk: qcom: Add generic clkref_en support Before XO refclk is distributed to PCIe/USB/eDP PHYs, it passes through a QREF block. QREF is powered by dedicated LDO rails, and the clkref_en register controls whether refclk is gated through to the PHY side. These clkref controls are different from typical GCC branch clocks: - only a single enable bit is present, without branch-style config bits - regulators must be voted before enable and unvoted after disable Model this as a dedicated clk_ref clock type with custom clk_ops instead of reusing struct clk_branch semantics. Also provide a common registration/probe API so the same clkref model can be reused regardless of where clkref_en registers are placed, e.g. TCSR on glymur and TLMM on SM8750. Reviewed-by: Konrad Dybcio Signed-off-by: Qiang Yu Link: https://lore.kernel.org/r/20260721-tcsr_qre_0721-v10-3-d2fa68c63b78@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/Makefile | 1 + drivers/clk/qcom/clk-ref.c | 205 +++++++++++++++++++++++++++++++++++++ include/linux/clk/qcom.h | 67 ++++++++++++ 3 files changed, 273 insertions(+) create mode 100644 drivers/clk/qcom/clk-ref.c create mode 100644 include/linux/clk/qcom.h diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile index db7689ee5dc6..fff069da16de 100644 --- a/drivers/clk/qcom/Makefile +++ b/drivers/clk/qcom/Makefile @@ -8,6 +8,7 @@ clk-qcom-y += clk-pll.o clk-qcom-y += clk-rcg.o clk-qcom-y += clk-rcg2.o clk-qcom-y += clk-branch.o +clk-qcom-y += clk-ref.o clk-qcom-y += clk-regmap-divider.o clk-qcom-y += clk-regmap-mux.o clk-qcom-y += clk-regmap-mux-div.o diff --git a/drivers/clk/qcom/clk-ref.c b/drivers/clk/qcom/clk-ref.c new file mode 100644 index 000000000000..3efdf6749770 --- /dev/null +++ b/drivers/clk/qcom/clk-ref.c @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2026, Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define QCOM_CLK_REF_EN_MASK BIT(0) + +struct qcom_clk_ref_provider { + size_t num_refs; + struct qcom_clk_ref refs[] __counted_by(num_refs); +}; + +static inline struct qcom_clk_ref *to_qcom_clk_ref(struct clk_hw *hw) +{ + return container_of(hw, struct qcom_clk_ref, hw); +} + +static const struct clk_parent_data qcom_clk_ref_parent_data = { + .index = 0, +}; + +static int qcom_clk_ref_prepare(struct clk_hw *hw) +{ + struct qcom_clk_ref *rclk = to_qcom_clk_ref(hw); + int ret; + + if (!rclk->desc.num_regulators) + return 0; + + ret = regulator_bulk_enable(rclk->desc.num_regulators, rclk->regulators); + if (ret) + pr_err("Failed to enable regulators for %s: %d\n", + clk_hw_get_name(hw), ret); + + return ret; +} + +static void qcom_clk_ref_unprepare(struct clk_hw *hw) +{ + struct qcom_clk_ref *rclk = to_qcom_clk_ref(hw); + + if (rclk->desc.num_regulators) + regulator_bulk_disable(rclk->desc.num_regulators, rclk->regulators); +} + +static int qcom_clk_ref_enable(struct clk_hw *hw) +{ + struct qcom_clk_ref *rclk = to_qcom_clk_ref(hw); + int ret; + + ret = regmap_set_bits(rclk->regmap, rclk->desc.offset, QCOM_CLK_REF_EN_MASK); + if (ret) + return ret; + + udelay(10); + + return 0; +} + +static void qcom_clk_ref_disable(struct clk_hw *hw) +{ + struct qcom_clk_ref *rclk = to_qcom_clk_ref(hw); + + regmap_clear_bits(rclk->regmap, rclk->desc.offset, QCOM_CLK_REF_EN_MASK); + udelay(10); +} + +static int qcom_clk_ref_is_enabled(struct clk_hw *hw) +{ + struct qcom_clk_ref *rclk = to_qcom_clk_ref(hw); + u32 val; + int ret; + + ret = regmap_read(rclk->regmap, rclk->desc.offset, &val); + if (ret) + return 0; + + return !!(val & QCOM_CLK_REF_EN_MASK); +} + +static const struct clk_ops qcom_clk_ref_ops = { + .prepare = qcom_clk_ref_prepare, + .unprepare = qcom_clk_ref_unprepare, + .enable = qcom_clk_ref_enable, + .disable = qcom_clk_ref_disable, + .is_enabled = qcom_clk_ref_is_enabled, +}; + +static int qcom_clk_ref_register(struct device *dev, struct regmap *regmap, + struct qcom_clk_ref *clk_refs, + const struct qcom_clk_ref_desc * const *descs, + size_t num_clk_refs) +{ + const struct qcom_clk_ref_desc *desc; + struct clk_init_data init_data = {}; + struct qcom_clk_ref *clk_ref; + size_t clk_idx; + unsigned int i; + int ret; + + for (clk_idx = 0; clk_idx < num_clk_refs; clk_idx++) { + clk_ref = &clk_refs[clk_idx]; + desc = descs[clk_idx]; + + /* Skip unpopulated indices; the array is indexed by clock ID. */ + if (!desc) + continue; + + if (WARN_ON(!desc->name)) + continue; + + clk_ref->regmap = regmap; + clk_ref->desc = *desc; + + if (clk_ref->desc.num_regulators) { + clk_ref->regulators = devm_kcalloc(dev, clk_ref->desc.num_regulators, + sizeof(*clk_ref->regulators), + GFP_KERNEL); + if (!clk_ref->regulators) + return -ENOMEM; + + for (i = 0; i < clk_ref->desc.num_regulators; i++) + clk_ref->regulators[i].supply = + clk_ref->desc.regulator_names[i]; + + ret = devm_regulator_bulk_get(dev, clk_ref->desc.num_regulators, + clk_ref->regulators); + if (ret) + return dev_err_probe(dev, ret, + "Failed to get regulators for %s\n", + clk_ref->desc.name); + } + + init_data.name = clk_ref->desc.name; + init_data.parent_data = &qcom_clk_ref_parent_data; + init_data.num_parents = 1; + init_data.ops = &qcom_clk_ref_ops; + clk_ref->hw.init = &init_data; + + ret = devm_clk_hw_register(dev, &clk_ref->hw); + if (ret) + return ret; + } + + return 0; +} + +static struct clk_hw *qcom_clk_ref_provider_get(struct of_phandle_args *clkspec, void *data) +{ + struct qcom_clk_ref_provider *provider = data; + unsigned int idx = clkspec->args[0]; + + if (idx >= provider->num_refs) + return ERR_PTR(-EINVAL); + + if (!provider->refs[idx].regmap) + return ERR_PTR(-ENOENT); + + return &provider->refs[idx].hw; +} + +int qcom_clk_ref_probe(struct platform_device *pdev, + const struct regmap_config *config, + const struct qcom_clk_ref_desc * const *descs, + size_t num_clk_refs) +{ + struct qcom_clk_ref_provider *provider; + struct device *dev = &pdev->dev; + struct regmap *regmap; + void __iomem *base; + int ret; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + regmap = devm_regmap_init_mmio(dev, base, config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + provider = devm_kzalloc(dev, struct_size(provider, refs, num_clk_refs), + GFP_KERNEL); + if (!provider) + return -ENOMEM; + + provider->num_refs = num_clk_refs; + + ret = qcom_clk_ref_register(dev, regmap, provider->refs, descs, + provider->num_refs); + if (ret) + return ret; + + return devm_of_clk_add_hw_provider(dev, qcom_clk_ref_provider_get, provider); +} +EXPORT_SYMBOL_GPL(qcom_clk_ref_probe); diff --git a/include/linux/clk/qcom.h b/include/linux/clk/qcom.h new file mode 100644 index 000000000000..11ac2d42ff9e --- /dev/null +++ b/include/linux/clk/qcom.h @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2026, Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef __LINUX_CLK_QCOM_H +#define __LINUX_CLK_QCOM_H + +#include +#include +#include +#include +#include + +struct device; +struct platform_device; +struct regulator_bulk_data; + +/** + * struct qcom_clk_ref_desc - descriptor for a clkref_en gate clock + * @name: clock name exposed to the common clock framework + * @offset: clkref_en register offset from the block base + * @regulator_names: optional supply names enabled while preparing the clock + * @num_regulators: number of entries in @regulator_names + */ +struct qcom_clk_ref_desc { + const char *name; + u32 offset; + const char * const *regulator_names; + unsigned int num_regulators; +}; + +/** + * struct qcom_clk_ref - per-clock data for a clkref_en gate clock + * @hw: common clock framework hardware clock handle + * @regmap: register map backing the clkref_en register + * @desc: clock descriptor copied at registration time + * @regulators: optional bulk regulator handles for @desc.regulator_names + */ +struct qcom_clk_ref { + struct clk_hw hw; + struct regmap *regmap; + struct qcom_clk_ref_desc desc; + struct regulator_bulk_data *regulators; +}; + +#if IS_ENABLED(CONFIG_COMMON_CLK_QCOM) + +int qcom_clk_ref_probe(struct platform_device *pdev, + const struct regmap_config *config, + const struct qcom_clk_ref_desc * const *descs, + size_t num_clk_refs); + +#else + +static inline int +qcom_clk_ref_probe(struct platform_device *pdev, + const struct regmap_config *config, + const struct qcom_clk_ref_desc * const *descs, + size_t num_clk_refs) +{ + return -EOPNOTSUPP; +} + +#endif + +#endif From 0753091abc4b480abf76cb40c10fffe2764bfcf7 Mon Sep 17 00:00:00 2001 From: Qiang Yu Date: Tue, 21 Jul 2026 22:58:59 -0700 Subject: [PATCH 0211/1328] clk: qcom: tcsrcc-glymur: Add regulator supplies and migrate to clk_ref helper Replace local clk_branch-based clkref definitions with descriptor-based registration via qcom_clk_ref_probe(). This keeps the glymur driver focused on clock metadata and reuses common runtime logic for regulator handling, enable/disable sequencing, and OF provider wiring. Co-developed-by: Konrad Dybcio Signed-off-by: Konrad Dybcio Reviewed-by: Konrad Dybcio Signed-off-by: Qiang Yu Link: https://lore.kernel.org/r/20260721-tcsr_qre_0721-v10-4-d2fa68c63b78@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/tcsrcc-glymur.c | 365 ++++++++++--------------------- 1 file changed, 118 insertions(+), 247 deletions(-) diff --git a/drivers/clk/qcom/tcsrcc-glymur.c b/drivers/clk/qcom/tcsrcc-glymur.c index 9c0edebcdbb1..e066feb6675f 100644 --- a/drivers/clk/qcom/tcsrcc-glymur.c +++ b/drivers/clk/qcom/tcsrcc-glymur.c @@ -4,263 +4,57 @@ */ #include +#include #include #include +#include #include #include #include -#include "clk-alpha-pll.h" -#include "clk-branch.h" -#include "clk-pll.h" -#include "clk-rcg.h" -#include "clk-regmap.h" -#include "clk-regmap-divider.h" -#include "clk-regmap-mux.h" -#include "common.h" -#include "gdsc.h" -#include "reset.h" - -enum { - DT_BI_TCXO_PAD, +static const char * const glymur_tcsr_tx0_rx5_regulators[] = { + "vdda-refgen3-0p9", + "vdda-refgen3-1p2", + "vdda-qrefrx5-0p9", + "vdda-qreftx0-0p9", + "vdda-qreftx0-1p2", }; -static struct clk_branch tcsr_edp_clkref_en = { - .halt_reg = 0x60, - .halt_check = BRANCH_HALT_DELAY, - .clkr = { - .enable_reg = 0x60, - .enable_mask = BIT(0), - .hw.init = &(const struct clk_init_data) { - .name = "tcsr_edp_clkref_en", - .parent_data = &(const struct clk_parent_data){ - .index = DT_BI_TCXO_PAD, - }, - .num_parents = 1, - .ops = &clk_branch2_ops, - }, - }, +static const char * const glymur_tcsr_tx1_rpt0_rx0_regulators[] = { + "vdda-refgen4-0p9", + "vdda-refgen4-1p2", + "vdda-qreftx1-0p9", + "vdda-qrefrpt0-0p9", + "vdda-qrefrx0-0p9", }; -static struct clk_branch tcsr_pcie_1_clkref_en = { - .halt_reg = 0x48, - .halt_check = BRANCH_HALT_DELAY, - .clkr = { - .enable_reg = 0x48, - .enable_mask = BIT(0), - .hw.init = &(const struct clk_init_data) { - .name = "tcsr_pcie_1_clkref_en", - .parent_data = &(const struct clk_parent_data){ - .index = DT_BI_TCXO_PAD, - }, - .num_parents = 1, - .ops = &clk_branch2_ops, - }, - }, +static const char * const glymur_tcsr_tx1_rpt01_rx1_regulators[] = { + "vdda-refgen4-0p9", + "vdda-refgen4-1p2", + "vdda-qreftx1-0p9", + "vdda-qrefrpt0-0p9", + "vdda-qrefrpt1-0p9", + "vdda-qrefrx1-0p9", }; -static struct clk_branch tcsr_pcie_2_clkref_en = { - .halt_reg = 0x4c, - .halt_check = BRANCH_HALT_DELAY, - .clkr = { - .enable_reg = 0x4c, - .enable_mask = BIT(0), - .hw.init = &(const struct clk_init_data) { - .name = "tcsr_pcie_2_clkref_en", - .parent_data = &(const struct clk_parent_data){ - .index = DT_BI_TCXO_PAD, - }, - .num_parents = 1, - .ops = &clk_branch2_ops, - }, - }, +static const char * const glymur_tcsr_tx1_rpt012_rx2_regulators[] = { + "vdda-refgen4-0p9", + "vdda-refgen4-1p2", + "vdda-qreftx1-0p9", + "vdda-qrefrpt0-0p9", + "vdda-qrefrpt1-0p9", + "vdda-qrefrpt2-0p9", + "vdda-qrefrx2-0p9", }; -static struct clk_branch tcsr_pcie_3_clkref_en = { - .halt_reg = 0x54, - .halt_check = BRANCH_HALT_DELAY, - .clkr = { - .enable_reg = 0x54, - .enable_mask = BIT(0), - .hw.init = &(const struct clk_init_data) { - .name = "tcsr_pcie_3_clkref_en", - .parent_data = &(const struct clk_parent_data){ - .index = DT_BI_TCXO_PAD, - }, - .num_parents = 1, - .ops = &clk_branch2_ops, - }, - }, -}; - -static struct clk_branch tcsr_pcie_4_clkref_en = { - .halt_reg = 0x58, - .halt_check = BRANCH_HALT_DELAY, - .clkr = { - .enable_reg = 0x58, - .enable_mask = BIT(0), - .hw.init = &(const struct clk_init_data) { - .name = "tcsr_pcie_4_clkref_en", - .parent_data = &(const struct clk_parent_data){ - .index = DT_BI_TCXO_PAD, - }, - .num_parents = 1, - .ops = &clk_branch2_ops, - }, - }, -}; - -static struct clk_branch tcsr_usb2_1_clkref_en = { - .halt_reg = 0x6c, - .halt_check = BRANCH_HALT_DELAY, - .clkr = { - .enable_reg = 0x6c, - .enable_mask = BIT(0), - .hw.init = &(const struct clk_init_data) { - .name = "tcsr_usb2_1_clkref_en", - .parent_data = &(const struct clk_parent_data){ - .index = DT_BI_TCXO_PAD, - }, - .num_parents = 1, - .ops = &clk_branch2_ops, - }, - }, -}; - -static struct clk_branch tcsr_usb2_2_clkref_en = { - .halt_reg = 0x70, - .halt_check = BRANCH_HALT_DELAY, - .clkr = { - .enable_reg = 0x70, - .enable_mask = BIT(0), - .hw.init = &(const struct clk_init_data) { - .name = "tcsr_usb2_2_clkref_en", - .parent_data = &(const struct clk_parent_data){ - .index = DT_BI_TCXO_PAD, - }, - .num_parents = 1, - .ops = &clk_branch2_ops, - }, - }, -}; - -static struct clk_branch tcsr_usb2_3_clkref_en = { - .halt_reg = 0x74, - .halt_check = BRANCH_HALT_DELAY, - .clkr = { - .enable_reg = 0x74, - .enable_mask = BIT(0), - .hw.init = &(const struct clk_init_data) { - .name = "tcsr_usb2_3_clkref_en", - .parent_data = &(const struct clk_parent_data){ - .index = DT_BI_TCXO_PAD, - }, - .num_parents = 1, - .ops = &clk_branch2_ops, - }, - }, -}; - -static struct clk_branch tcsr_usb2_4_clkref_en = { - .halt_reg = 0x88, - .halt_check = BRANCH_HALT_DELAY, - .clkr = { - .enable_reg = 0x88, - .enable_mask = BIT(0), - .hw.init = &(const struct clk_init_data) { - .name = "tcsr_usb2_4_clkref_en", - .parent_data = &(const struct clk_parent_data){ - .index = DT_BI_TCXO_PAD, - }, - .num_parents = 1, - .ops = &clk_branch2_ops, - }, - }, -}; - -static struct clk_branch tcsr_usb3_0_clkref_en = { - .halt_reg = 0x64, - .halt_check = BRANCH_HALT_DELAY, - .clkr = { - .enable_reg = 0x64, - .enable_mask = BIT(0), - .hw.init = &(const struct clk_init_data) { - .name = "tcsr_usb3_0_clkref_en", - .parent_data = &(const struct clk_parent_data){ - .index = DT_BI_TCXO_PAD, - }, - .num_parents = 1, - .ops = &clk_branch2_ops, - }, - }, -}; - -static struct clk_branch tcsr_usb3_1_clkref_en = { - .halt_reg = 0x68, - .halt_check = BRANCH_HALT_DELAY, - .clkr = { - .enable_reg = 0x68, - .enable_mask = BIT(0), - .hw.init = &(const struct clk_init_data) { - .name = "tcsr_usb3_1_clkref_en", - .parent_data = &(const struct clk_parent_data){ - .index = DT_BI_TCXO_PAD, - }, - .num_parents = 1, - .ops = &clk_branch2_ops, - }, - }, -}; - -static struct clk_branch tcsr_usb4_1_clkref_en = { - .halt_reg = 0x44, - .halt_check = BRANCH_HALT_DELAY, - .clkr = { - .enable_reg = 0x44, - .enable_mask = BIT(0), - .hw.init = &(const struct clk_init_data) { - .name = "tcsr_usb4_1_clkref_en", - .parent_data = &(const struct clk_parent_data){ - .index = DT_BI_TCXO_PAD, - }, - .num_parents = 1, - .ops = &clk_branch2_ops, - }, - }, -}; - -static struct clk_branch tcsr_usb4_2_clkref_en = { - .halt_reg = 0x5c, - .halt_check = BRANCH_HALT_DELAY, - .clkr = { - .enable_reg = 0x5c, - .enable_mask = BIT(0), - .hw.init = &(const struct clk_init_data) { - .name = "tcsr_usb4_2_clkref_en", - .parent_data = &(const struct clk_parent_data){ - .index = DT_BI_TCXO_PAD, - }, - .num_parents = 1, - .ops = &clk_branch2_ops, - }, - }, -}; - -static struct clk_regmap *tcsr_cc_glymur_clocks[] = { - [TCSR_EDP_CLKREF_EN] = &tcsr_edp_clkref_en.clkr, - [TCSR_PCIE_1_CLKREF_EN] = &tcsr_pcie_1_clkref_en.clkr, - [TCSR_PCIE_2_CLKREF_EN] = &tcsr_pcie_2_clkref_en.clkr, - [TCSR_PCIE_3_CLKREF_EN] = &tcsr_pcie_3_clkref_en.clkr, - [TCSR_PCIE_4_CLKREF_EN] = &tcsr_pcie_4_clkref_en.clkr, - [TCSR_USB2_1_CLKREF_EN] = &tcsr_usb2_1_clkref_en.clkr, - [TCSR_USB2_2_CLKREF_EN] = &tcsr_usb2_2_clkref_en.clkr, - [TCSR_USB2_3_CLKREF_EN] = &tcsr_usb2_3_clkref_en.clkr, - [TCSR_USB2_4_CLKREF_EN] = &tcsr_usb2_4_clkref_en.clkr, - [TCSR_USB3_0_CLKREF_EN] = &tcsr_usb3_0_clkref_en.clkr, - [TCSR_USB3_1_CLKREF_EN] = &tcsr_usb3_1_clkref_en.clkr, - [TCSR_USB4_1_CLKREF_EN] = &tcsr_usb4_1_clkref_en.clkr, - [TCSR_USB4_2_CLKREF_EN] = &tcsr_usb4_2_clkref_en.clkr, +static const char * const glymur_tcsr_tx1_rpt34_rx4_regulators[] = { + "vdda-refgen4-0p9", + "vdda-refgen4-1p2", + "vdda-qreftx1-0p9", + "vdda-qrefrpt3-0p9", + "vdda-qrefrpt4-0p9", + "vdda-qrefrx4-0p9", }; static const struct regmap_config tcsr_cc_glymur_regmap_config = { @@ -271,10 +65,85 @@ static const struct regmap_config tcsr_cc_glymur_regmap_config = { .fast_io = true, }; -static const struct qcom_cc_desc tcsr_cc_glymur_desc = { - .config = &tcsr_cc_glymur_regmap_config, - .clks = tcsr_cc_glymur_clocks, - .num_clks = ARRAY_SIZE(tcsr_cc_glymur_clocks), +static const struct qcom_clk_ref_desc * const tcsr_cc_glymur_clk_descs[] = { + [TCSR_EDP_CLKREF_EN] = &(const struct qcom_clk_ref_desc) { + .name = "tcsr_edp_clkref_en", + .offset = 0x60, + .regulator_names = glymur_tcsr_tx1_rpt0_rx0_regulators, + .num_regulators = ARRAY_SIZE(glymur_tcsr_tx1_rpt0_rx0_regulators), + }, + [TCSR_PCIE_1_CLKREF_EN] = &(const struct qcom_clk_ref_desc) { + .name = "tcsr_pcie_1_clkref_en", + .offset = 0x48, + .regulator_names = glymur_tcsr_tx0_rx5_regulators, + .num_regulators = ARRAY_SIZE(glymur_tcsr_tx0_rx5_regulators), + }, + [TCSR_PCIE_2_CLKREF_EN] = &(const struct qcom_clk_ref_desc) { + .name = "tcsr_pcie_2_clkref_en", + .offset = 0x4c, + .regulator_names = glymur_tcsr_tx1_rpt012_rx2_regulators, + .num_regulators = ARRAY_SIZE(glymur_tcsr_tx1_rpt012_rx2_regulators), + }, + [TCSR_PCIE_3_CLKREF_EN] = &(const struct qcom_clk_ref_desc) { + .name = "tcsr_pcie_3_clkref_en", + .offset = 0x54, + .regulator_names = glymur_tcsr_tx1_rpt01_rx1_regulators, + .num_regulators = ARRAY_SIZE(glymur_tcsr_tx1_rpt01_rx1_regulators), + }, + [TCSR_PCIE_4_CLKREF_EN] = &(const struct qcom_clk_ref_desc) { + .name = "tcsr_pcie_4_clkref_en", + .offset = 0x58, + .regulator_names = glymur_tcsr_tx1_rpt012_rx2_regulators, + .num_regulators = ARRAY_SIZE(glymur_tcsr_tx1_rpt012_rx2_regulators), + }, + [TCSR_USB2_1_CLKREF_EN] = &(const struct qcom_clk_ref_desc) { + .name = "tcsr_usb2_1_clkref_en", + .offset = 0x6c, + .regulator_names = glymur_tcsr_tx1_rpt34_rx4_regulators, + .num_regulators = ARRAY_SIZE(glymur_tcsr_tx1_rpt34_rx4_regulators), + }, + [TCSR_USB2_2_CLKREF_EN] = &(const struct qcom_clk_ref_desc) { + .name = "tcsr_usb2_2_clkref_en", + .offset = 0x70, + .regulator_names = glymur_tcsr_tx1_rpt01_rx1_regulators, + .num_regulators = ARRAY_SIZE(glymur_tcsr_tx1_rpt01_rx1_regulators), + }, + [TCSR_USB2_3_CLKREF_EN] = &(const struct qcom_clk_ref_desc) { + .name = "tcsr_usb2_3_clkref_en", + .offset = 0x74, + .regulator_names = glymur_tcsr_tx1_rpt34_rx4_regulators, + .num_regulators = ARRAY_SIZE(glymur_tcsr_tx1_rpt34_rx4_regulators), + }, + [TCSR_USB2_4_CLKREF_EN] = &(const struct qcom_clk_ref_desc) { + .name = "tcsr_usb2_4_clkref_en", + .offset = 0x88, + .regulator_names = glymur_tcsr_tx1_rpt34_rx4_regulators, + .num_regulators = ARRAY_SIZE(glymur_tcsr_tx1_rpt34_rx4_regulators), + }, + [TCSR_USB3_0_CLKREF_EN] = &(const struct qcom_clk_ref_desc) { + .name = "tcsr_usb3_0_clkref_en", + .offset = 0x64, + .regulator_names = glymur_tcsr_tx1_rpt34_rx4_regulators, + .num_regulators = ARRAY_SIZE(glymur_tcsr_tx1_rpt34_rx4_regulators), + }, + [TCSR_USB3_1_CLKREF_EN] = &(const struct qcom_clk_ref_desc) { + .name = "tcsr_usb3_1_clkref_en", + .offset = 0x68, + .regulator_names = glymur_tcsr_tx1_rpt34_rx4_regulators, + .num_regulators = ARRAY_SIZE(glymur_tcsr_tx1_rpt34_rx4_regulators), + }, + [TCSR_USB4_1_CLKREF_EN] = &(const struct qcom_clk_ref_desc) { + .name = "tcsr_usb4_1_clkref_en", + .offset = 0x44, + .regulator_names = glymur_tcsr_tx0_rx5_regulators, + .num_regulators = ARRAY_SIZE(glymur_tcsr_tx0_rx5_regulators), + }, + [TCSR_USB4_2_CLKREF_EN] = &(const struct qcom_clk_ref_desc) { + .name = "tcsr_usb4_2_clkref_en", + .offset = 0x5c, + .regulator_names = glymur_tcsr_tx1_rpt01_rx1_regulators, + .num_regulators = ARRAY_SIZE(glymur_tcsr_tx1_rpt01_rx1_regulators), + }, }; static const struct of_device_id tcsr_cc_glymur_match_table[] = { @@ -285,7 +154,9 @@ MODULE_DEVICE_TABLE(of, tcsr_cc_glymur_match_table); static int tcsr_cc_glymur_probe(struct platform_device *pdev) { - return qcom_cc_probe(pdev, &tcsr_cc_glymur_desc); + return qcom_clk_ref_probe(pdev, &tcsr_cc_glymur_regmap_config, + tcsr_cc_glymur_clk_descs, + ARRAY_SIZE(tcsr_cc_glymur_clk_descs)); } static struct platform_driver tcsr_cc_glymur_driver = { From 0f87ded5f8398182fbac232f0f287d45baa10d6a Mon Sep 17 00:00:00 2001 From: Qiang Yu Date: Tue, 21 Jul 2026 22:59:00 -0700 Subject: [PATCH 0212/1328] clk: qcom: tcsrcc-glymur: Add Mahua QREF regulator support Mahua is based on Glymur but uses a different QREF topology, requiring distinct regulator lists and clock descriptors for its PCIe clock references. Add mahua-specific regulator arrays and clk descriptor table, and use match_data to select the correct descriptor table per compatible string at probe time. Reviewed-by: Konrad Dybcio Signed-off-by: Qiang Yu Link: https://lore.kernel.org/r/20260721-tcsr_qre_0721-v10-5-d2fa68c63b78@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/tcsrcc-glymur.c | 136 ++++++++++++++++++++++++++++++- 1 file changed, 133 insertions(+), 3 deletions(-) diff --git a/drivers/clk/qcom/tcsrcc-glymur.c b/drivers/clk/qcom/tcsrcc-glymur.c index e066feb6675f..3388c1c3a2aa 100644 --- a/drivers/clk/qcom/tcsrcc-glymur.c +++ b/drivers/clk/qcom/tcsrcc-glymur.c @@ -13,6 +13,11 @@ #include +struct tcsrcc_glymur_data { + const struct qcom_clk_ref_desc * const *descs; + size_t num_descs; +}; + static const char * const glymur_tcsr_tx0_rx5_regulators[] = { "vdda-refgen3-0p9", "vdda-refgen3-1p2", @@ -57,6 +62,43 @@ static const char * const glymur_tcsr_tx1_rpt34_rx4_regulators[] = { "vdda-qrefrx4-0p9", }; +static const char * const mahua_tcsr_tx1_rpt01_rx1_regulators[] = { + "vdda-refgen3-0p9", + "vdda-refgen3-1p2", + "vdda-qreftx1-0p9", + "vdda-qrefrpt0-0p9", + "vdda-qrefrpt1-0p9", + "vdda-qrefrx1-0p9", +}; + +static const char * const mahua_tcsr_tx1_rpt012_rx2_regulators[] = { + "vdda-refgen3-0p9", + "vdda-refgen3-1p2", + "vdda-qreftx1-0p9", + "vdda-qrefrpt0-0p9", + "vdda-qrefrpt1-0p9", + "vdda-qrefrpt2-0p9", + "vdda-qrefrx2-0p9", +}; + +static const char * const mahua_tcsr_tx1_rpt0_rx0_regulators[] = { + "vdda-refgen3-0p9", + "vdda-refgen3-1p2", + "vdda-qreftx1-0p9", + "vdda-qrefrpt0-0p9", + "vdda-qrefrx0-0p9", +}; + +static const char * const mahua_tcsr_tx1_rpt345_rx3_regulators[] = { + "vdda-refgen3-0p9", + "vdda-refgen3-1p2", + "vdda-qreftx1-0p9", + "vdda-qrefrpt3-0p9", + "vdda-qrefrpt4-0p9", + "vdda-qrefrpt5-0p9", + "vdda-qrefrx3-0p9", +}; + static const struct regmap_config tcsr_cc_glymur_regmap_config = { .reg_bits = 32, .reg_stride = 4, @@ -146,17 +188,105 @@ static const struct qcom_clk_ref_desc * const tcsr_cc_glymur_clk_descs[] = { }, }; +static const struct qcom_clk_ref_desc * const tcsr_cc_mahua_clk_descs[] = { + [TCSR_EDP_CLKREF_EN] = &(const struct qcom_clk_ref_desc) { + .name = "tcsr_edp_clkref_en", + .offset = 0x60, + .regulator_names = mahua_tcsr_tx1_rpt0_rx0_regulators, + .num_regulators = ARRAY_SIZE(mahua_tcsr_tx1_rpt0_rx0_regulators), + }, + [TCSR_PCIE_2_CLKREF_EN] = &(const struct qcom_clk_ref_desc) { + .name = "tcsr_pcie_2_clkref_en", + .offset = 0x4c, + .regulator_names = mahua_tcsr_tx1_rpt01_rx1_regulators, + .num_regulators = ARRAY_SIZE(mahua_tcsr_tx1_rpt01_rx1_regulators), + }, + [TCSR_PCIE_3_CLKREF_EN] = &(const struct qcom_clk_ref_desc) { + .name = "tcsr_pcie_3_clkref_en", + .offset = 0x54, + .regulator_names = mahua_tcsr_tx1_rpt012_rx2_regulators, + .num_regulators = ARRAY_SIZE(mahua_tcsr_tx1_rpt012_rx2_regulators), + }, + [TCSR_PCIE_4_CLKREF_EN] = &(const struct qcom_clk_ref_desc) { + .name = "tcsr_pcie_4_clkref_en", + .offset = 0x58, + .regulator_names = mahua_tcsr_tx1_rpt012_rx2_regulators, + .num_regulators = ARRAY_SIZE(mahua_tcsr_tx1_rpt012_rx2_regulators), + }, + [TCSR_USB2_1_CLKREF_EN] = &(const struct qcom_clk_ref_desc) { + .name = "tcsr_usb2_1_clkref_en", + .offset = 0x6c, + .regulator_names = mahua_tcsr_tx1_rpt345_rx3_regulators, + .num_regulators = ARRAY_SIZE(mahua_tcsr_tx1_rpt345_rx3_regulators), + }, + [TCSR_USB2_2_CLKREF_EN] = &(const struct qcom_clk_ref_desc) { + .name = "tcsr_usb2_2_clkref_en", + .offset = 0x70, + .regulator_names = mahua_tcsr_tx1_rpt345_rx3_regulators, + .num_regulators = ARRAY_SIZE(mahua_tcsr_tx1_rpt345_rx3_regulators), + }, + [TCSR_USB2_3_CLKREF_EN] = &(const struct qcom_clk_ref_desc) { + .name = "tcsr_usb2_3_clkref_en", + .offset = 0x74, + .regulator_names = mahua_tcsr_tx1_rpt345_rx3_regulators, + .num_regulators = ARRAY_SIZE(mahua_tcsr_tx1_rpt345_rx3_regulators), + }, + [TCSR_USB2_4_CLKREF_EN] = &(const struct qcom_clk_ref_desc) { + .name = "tcsr_usb2_4_clkref_en", + .offset = 0x88, + .regulator_names = mahua_tcsr_tx1_rpt0_rx0_regulators, + .num_regulators = ARRAY_SIZE(mahua_tcsr_tx1_rpt0_rx0_regulators), + }, + [TCSR_USB3_0_CLKREF_EN] = &(const struct qcom_clk_ref_desc) { + .name = "tcsr_usb3_0_clkref_en", + .offset = 0x64, + .regulator_names = mahua_tcsr_tx1_rpt345_rx3_regulators, + .num_regulators = ARRAY_SIZE(mahua_tcsr_tx1_rpt345_rx3_regulators), + }, + [TCSR_USB3_1_CLKREF_EN] = &(const struct qcom_clk_ref_desc) { + .name = "tcsr_usb3_1_clkref_en", + .offset = 0x68, + .regulator_names = mahua_tcsr_tx1_rpt345_rx3_regulators, + .num_regulators = ARRAY_SIZE(mahua_tcsr_tx1_rpt345_rx3_regulators), + }, + [TCSR_USB4_1_CLKREF_EN] = &(const struct qcom_clk_ref_desc) { + .name = "tcsr_usb4_1_clkref_en", + .offset = 0x44, + }, + [TCSR_USB4_2_CLKREF_EN] = &(const struct qcom_clk_ref_desc) { + .name = "tcsr_usb4_2_clkref_en", + .offset = 0x5c, + .regulator_names = mahua_tcsr_tx1_rpt01_rx1_regulators, + .num_regulators = ARRAY_SIZE(mahua_tcsr_tx1_rpt01_rx1_regulators), + }, +}; + +static const struct tcsrcc_glymur_data tcsr_cc_glymur_data = { + .descs = tcsr_cc_glymur_clk_descs, + .num_descs = ARRAY_SIZE(tcsr_cc_glymur_clk_descs), +}; + +static const struct tcsrcc_glymur_data tcsr_cc_mahua_data = { + .descs = tcsr_cc_mahua_clk_descs, + .num_descs = ARRAY_SIZE(tcsr_cc_mahua_clk_descs), +}; + static const struct of_device_id tcsr_cc_glymur_match_table[] = { - { .compatible = "qcom,glymur-tcsr" }, + { .compatible = "qcom,glymur-tcsr", .data = &tcsr_cc_glymur_data }, + { .compatible = "qcom,mahua-tcsr", .data = &tcsr_cc_mahua_data }, { } }; MODULE_DEVICE_TABLE(of, tcsr_cc_glymur_match_table); static int tcsr_cc_glymur_probe(struct platform_device *pdev) { + const struct tcsrcc_glymur_data *data = device_get_match_data(&pdev->dev); + + if (!data) + return -ENODEV; + return qcom_clk_ref_probe(pdev, &tcsr_cc_glymur_regmap_config, - tcsr_cc_glymur_clk_descs, - ARRAY_SIZE(tcsr_cc_glymur_clk_descs)); + data->descs, data->num_descs); } static struct platform_driver tcsr_cc_glymur_driver = { From 33972977d632b0d2bc3bf81ffad6d4d846ff6a64 Mon Sep 17 00:00:00 2001 From: Jian Hu Date: Wed, 1 Jul 2026 16:30:02 +0800 Subject: [PATCH 0213/1328] dt-bindings: clock: Add Amlogic A9 peripherals clock controller Add the peripherals clock controller dt-bindings for the Amlogic A9 SoC family. Acked-by: Conor Dooley Signed-off-by: Jian Hu Link: https://patch.msgid.link/20260701-a9_peripherals-v6-1-9630f39879e5@amlogic.com Signed-off-by: Jerome Brunet --- .../clock/amlogic,a9-peripherals-clkc.yaml | 164 ++++++++ .../clock/amlogic,a9-peripherals-clkc.h | 351 ++++++++++++++++++ 2 files changed, 515 insertions(+) create mode 100644 Documentation/devicetree/bindings/clock/amlogic,a9-peripherals-clkc.yaml create mode 100644 include/dt-bindings/clock/amlogic,a9-peripherals-clkc.h diff --git a/Documentation/devicetree/bindings/clock/amlogic,a9-peripherals-clkc.yaml b/Documentation/devicetree/bindings/clock/amlogic,a9-peripherals-clkc.yaml new file mode 100644 index 000000000000..f0aef005ecf3 --- /dev/null +++ b/Documentation/devicetree/bindings/clock/amlogic,a9-peripherals-clkc.yaml @@ -0,0 +1,164 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright (C) 2026 Amlogic, Inc. All rights reserved +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/clock/amlogic,a9-peripherals-clkc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Amlogic A9 Series Peripherals Clock Controller + +maintainers: + - Neil Armstrong + - Jerome Brunet + - Jian Hu + - Xianwei Zhao + +properties: + compatible: + const: amlogic,a9-peripherals-clkc + + reg: + maxItems: 1 + + '#clock-cells': + const: 1 + + clocks: + minItems: 27 + items: + - description: input oscillator + - description: input fclk div 2 + - description: input fclk div 3 + - description: input fclk div 4 + - description: input fclk div 5 + - description: input fclk div 7 + - description: input fclk div 2p5 + - description: input sys clk + - description: input gp1 pll + - description: input gp2 pll + - description: input sys pll div 16 + - description: input cpu clk div 16 + - description: input a78 clk div 16 + - description: input dsu clk div 16 + - description: input rtc clk + - description: input gp0 pll + - description: input hifi0 pll + - description: input hifi1 pll + - description: input mclk0 pll + - description: input mclk1 pll + - description: input video1 pll + - description: input video2 pll + - description: input hdmi out2 clk + - description: input hdmi pixel clk + - description: input pixel0 pll + - description: input pixel1 pll + - description: input ddr pll test clk + - description: external input rmii oscillator (optional) + + clock-names: + minItems: 27 + items: + - const: xtal + - const: fdiv2 + - const: fdiv3 + - const: fdiv4 + - const: fdiv5 + - const: fdiv7 + - const: fdiv2p5 + - const: sys + - const: gp1 + - const: gp2 + - const: sysplldiv16 + - const: cpudiv16 + - const: a78div16 + - const: dsudiv16 + - const: rtc + - const: gp0 + - const: hifi0 + - const: hifi1 + - const: mclk0 + - const: mclk1 + - const: vid1 + - const: vid2 + - const: hdmiout2 + - const: hdmipix + - const: pix0 + - const: pix1 + - const: ddr_test + - const: ext_rmii + +required: + - compatible + - reg + - '#clock-cells' + - clocks + - clock-names + +additionalProperties: false + +examples: + - | + soc { + #address-cells = <2>; + #size-cells = <2>; + + clock-controller@200 { + compatible = "amlogic,a9-peripherals-clkc"; + reg = <0x0 0x200 0x0 0x2f8>; + #clock-cells = <1>; + clocks = <&xtal>, + <&scmi_clk 10>, + <&scmi_clk 12>, + <&scmi_clk 14>, + <&scmi_clk 16>, + <&scmi_clk 18>, + <&scmi_clk 20>, + <&scmi_clk 21>, + <&scmi_clk 33>, + <&scmi_clk 34>, + <&scmi_clk 35>, + <&scmi_clk 36>, + <&scmi_clk 37>, + <&scmi_clk 38>, + <&scmi_clk 40>, + <&gp0 3>, + <&hifi0 3>, + <&hifi1 3>, + <&mclk0 3>, + <&mclk1 3>, + <&vid1>, + <&vid2>, + <&hdmitx 10>, + <&hdmitx 11>, + <&pix0>, + <&pix1>, + <&ddr 3>; + clock-names = "xtal", + "fdiv2", + "fdiv3", + "fdiv4", + "fdiv5", + "fdiv7", + "fdiv2p5", + "sys", + "gp1", + "gp2", + "sysplldiv16", + "cpudiv16", + "a78div16", + "dsudiv16", + "rtc", + "gp0", + "hifi0", + "hifi1", + "mclk0", + "mclk1", + "vid1", + "vid2", + "hdmiout2", + "hdmipix", + "pix0", + "pix1", + "ddr_test"; + }; + }; diff --git a/include/dt-bindings/clock/amlogic,a9-peripherals-clkc.h b/include/dt-bindings/clock/amlogic,a9-peripherals-clkc.h new file mode 100644 index 000000000000..09487a414197 --- /dev/null +++ b/include/dt-bindings/clock/amlogic,a9-peripherals-clkc.h @@ -0,0 +1,351 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ +/* + * Copyright (C) 2026 Amlogic, Inc. All rights reserved. + */ + +#ifndef __AMLOGIC_A9_PERIPHERALS_CLKC_H +#define __AMLOGIC_A9_PERIPHERALS_CLKC_H + +#define CLKID_SYS_AM_AXI 0 +#define CLKID_SYS_DOS 1 +#define CLKID_SYS_MIPI_DSI0 2 +#define CLKID_SYS_ETH_PHY 3 +#define CLKID_SYS_AMFC 4 +#define CLKID_SYS_MALI 5 +#define CLKID_SYS_NNA 6 +#define CLKID_SYS_ETH_AXI 7 +#define CLKID_SYS_DP_APB 8 +#define CLKID_SYS_EDPTX_APB 9 +#define CLKID_SYS_U3HSG 10 +#define CLKID_SYS_AUCPU 11 +#define CLKID_SYS_GLB 12 +#define CLKID_SYS_COMBO_DPHY_APB 13 +#define CLKID_SYS_HDMIRX_APB 14 +#define CLKID_SYS_HDMIRX_PCLK 15 +#define CLKID_SYS_MIPI_DSI0_PHY 16 +#define CLKID_SYS_CAN0 17 +#define CLKID_SYS_CAN1 18 +#define CLKID_SYS_SD_EMMC_A 19 +#define CLKID_SYS_SD_EMMC_B 20 +#define CLKID_SYS_SD_EMMC_C 21 +#define CLKID_SYS_SC 22 +#define CLKID_SYS_ACODEC 23 +#define CLKID_SYS_MIPI_ISP 24 +#define CLKID_SYS_MSR 25 +#define CLKID_SYS_AUDIO 26 +#define CLKID_SYS_MIPI_DSI1 27 +#define CLKID_SYS_MIPI_DSI1_PHY 28 +#define CLKID_SYS_ETH 29 +#define CLKID_SYS_ETH_1G_MAC 30 +#define CLKID_SYS_UART_A 31 +#define CLKID_SYS_UART_F 32 +#define CLKID_SYS_TS_A55 33 +#define CLKID_SYS_ETH_1G_AXI 34 +#define CLKID_SYS_TS_DOS 35 +#define CLKID_SYS_U3DRD_B 36 +#define CLKID_SYS_TS_CORE 37 +#define CLKID_SYS_TS_PLL 38 +#define CLKID_SYS_CSI_DIG_CLKIN 39 +#define CLKID_SYS_CVE 40 +#define CLKID_SYS_GE2D 41 +#define CLKID_SYS_SPISG 42 +#define CLKID_SYS_U2H 43 +#define CLKID_SYS_PCIE_MAC_A 44 +#define CLKID_SYS_U3DRD_A 45 +#define CLKID_SYS_U2DRD 46 +#define CLKID_SYS_PCIE_PHY 47 +#define CLKID_SYS_PCIE_MAC_B 48 +#define CLKID_SYS_PERIPH 49 +#define CLKID_SYS_PIO 50 +#define CLKID_SYS_I3C 51 +#define CLKID_SYS_I2C_M_E 52 +#define CLKID_SYS_I2C_M_F 53 +#define CLKID_SYS_HDMITX_APB 54 +#define CLKID_SYS_I2C_M_I 55 +#define CLKID_SYS_I2C_M_G 56 +#define CLKID_SYS_I2C_M_H 57 +#define CLKID_SYS_HDMI20_AES 58 +#define CLKID_SYS_CSI2_HOST 59 +#define CLKID_SYS_CSI2_ADAPT 60 +#define CLKID_SYS_DSPA 61 +#define CLKID_SYS_PP_DMA 62 +#define CLKID_SYS_PP_WRAPPER 63 +#define CLKID_SYS_VPU_INTR 64 +#define CLKID_SYS_CSI2_PHY 65 +#define CLKID_SYS_SARADC 66 +#define CLKID_SYS_PWM_J 67 +#define CLKID_SYS_PWM_I 68 +#define CLKID_SYS_PWM_H 69 +#define CLKID_SYS_PWM_N 70 +#define CLKID_SYS_PWM_M 71 +#define CLKID_SYS_PWM_L 72 +#define CLKID_SYS_PWM_K 73 +#define CLKID_SD_EMMC_A_SEL 74 +#define CLKID_SD_EMMC_A_DIV 75 +#define CLKID_SD_EMMC_A 76 +#define CLKID_SD_EMMC_B_SEL 77 +#define CLKID_SD_EMMC_B_DIV 78 +#define CLKID_SD_EMMC_B 79 +#define CLKID_SD_EMMC_C_SEL 80 +#define CLKID_SD_EMMC_C_DIV 81 +#define CLKID_SD_EMMC_C 82 +#define CLKID_PWM_H_SEL 83 +#define CLKID_PWM_H_DIV 84 +#define CLKID_PWM_H 85 +#define CLKID_PWM_I_SEL 86 +#define CLKID_PWM_I_DIV 87 +#define CLKID_PWM_I 88 +#define CLKID_PWM_J_SEL 89 +#define CLKID_PWM_J_DIV 90 +#define CLKID_PWM_J 91 +#define CLKID_PWM_K_SEL 92 +#define CLKID_PWM_K_DIV 93 +#define CLKID_PWM_K 94 +#define CLKID_PWM_L_SEL 95 +#define CLKID_PWM_L_DIV 96 +#define CLKID_PWM_L 97 +#define CLKID_PWM_M_SEL 98 +#define CLKID_PWM_M_DIV 99 +#define CLKID_PWM_M 100 +#define CLKID_PWM_N_SEL 101 +#define CLKID_PWM_N_DIV 102 +#define CLKID_PWM_N 103 +#define CLKID_SPISG0_SEL 104 +#define CLKID_SPISG0_DIV 105 +#define CLKID_SPISG0 106 +#define CLKID_SPISG1_SEL 107 +#define CLKID_SPISG1_DIV 108 +#define CLKID_SPISG1 109 +#define CLKID_SPISG2_SEL 110 +#define CLKID_SPISG2_DIV 111 +#define CLKID_SPISG2 112 +#define CLKID_SARADC_SEL 113 +#define CLKID_SARADC_DIV 114 +#define CLKID_SARADC 115 +#define CLKID_AMFC_SEL 116 +#define CLKID_AMFC_DIV 117 +#define CLKID_AMFC 118 +#define CLKID_NNA_SEL 119 +#define CLKID_NNA_DIV 120 +#define CLKID_NNA 121 +#define CLKID_USB_250M_SEL 122 +#define CLKID_USB_250M_DIV 123 +#define CLKID_USB_250M 124 +#define CLKID_USB_48M_PRE_SEL 125 +#define CLKID_USB_48M_PRE_DIV 126 +#define CLKID_USB_48M_PRE 127 +#define CLKID_PCIE0_TL_SEL 128 +#define CLKID_PCIE0_TL_DIV 129 +#define CLKID_PCIE0_TL 130 +#define CLKID_PCIE1_TL_SEL 131 +#define CLKID_PCIE1_TL_DIV 132 +#define CLKID_PCIE1_TL 133 +#define CLKID_CMPR_SEL 134 +#define CLKID_CMPR_DIV 135 +#define CLKID_CMPR 136 +#define CLKID_DEWARPA_SEL 137 +#define CLKID_DEWARPA_DIV 138 +#define CLKID_DEWARPA 139 +#define CLKID_SC_PRE_SEL 140 +#define CLKID_SC_PRE_DIV 141 +#define CLKID_SC_PRE 142 +#define CLKID_SC 143 +#define CLKID_DPTX_APB2_SEL 144 +#define CLKID_DPTX_APB2_DIV 145 +#define CLKID_DPTX_APB2 146 +#define CLKID_DPTX_AUD_SEL 147 +#define CLKID_DPTX_AUD_DIV 148 +#define CLKID_DPTX_AUD 149 +#define CLKID_ISP_SEL 150 +#define CLKID_ISP_DIV 151 +#define CLKID_ISP 152 +#define CLKID_CVE_SEL 153 +#define CLKID_CVE_DIV 154 +#define CLKID_CVE 155 +#define CLKID_VGE_SEL 156 +#define CLKID_VGE_DIV 157 +#define CLKID_VGE 158 +#define CLKID_PP_SEL 159 +#define CLKID_PP_DIV 160 +#define CLKID_PP 161 +#define CLKID_GLB_SEL 162 +#define CLKID_GLB_DIV 163 +#define CLKID_GLB 164 +#define CLKID_USB_48M_DUALDIV_IN 165 +#define CLKID_USB_48M_DUALDIV_DIV 166 +#define CLKID_USB_48M_DUALDIV_SEL 167 +#define CLKID_USB_48M_DUALDIV 168 +#define CLKID_USB_48M 169 +#define CLKID_CAN0_PE_SEL 170 +#define CLKID_CAN0_PE_DIV 171 +#define CLKID_CAN0_PE 172 +#define CLKID_CAN1_PE_SEL 173 +#define CLKID_CAN1_PE_DIV 174 +#define CLKID_CAN1_PE 175 +#define CLKID_CAN0_FILTER_SEL 176 +#define CLKID_CAN0_FILTER_DIV 177 +#define CLKID_CAN0_FILTER 178 +#define CLKID_CAN1_FILTER_SEL 179 +#define CLKID_CAN1_FILTER_DIV 180 +#define CLKID_CAN1_FILTER 181 +#define CLKID_I3C_SEL 182 +#define CLKID_I3C_DIV 183 +#define CLKID_I3C 184 +#define CLKID_TS_DIV 185 +#define CLKID_TS 186 +#define CLKID_ETH_125M_DIV 187 +#define CLKID_ETH_125M 188 +#define CLKID_ETH_RMII_SEL 189 +#define CLKID_ETH_RMII_DIV 190 +#define CLKID_ETH_RMII 191 +#define CLKID_GEN_SEL 192 +#define CLKID_GEN_DIV 193 +#define CLKID_GEN 194 +#define CLKID_CLK24M_IN 195 +#define CLKID_CLK12_24M 196 +#define CLKID_MALI_0_SEL 197 +#define CLKID_MALI_0_DIV 198 +#define CLKID_MALI_0 199 +#define CLKID_MALI_1_SEL 200 +#define CLKID_MALI_1_DIV 201 +#define CLKID_MALI_1 202 +#define CLKID_MALI 203 +#define CLKID_MALI_STACK_0_SEL 204 +#define CLKID_MALI_STACK_0_DIV 205 +#define CLKID_MALI_STACK_0 206 +#define CLKID_MALI_STACK_1_SEL 207 +#define CLKID_MALI_STACK_1_DIV 208 +#define CLKID_MALI_STACK_1 209 +#define CLKID_MALI_STACK 210 +#define CLKID_DSPA_0_SEL 211 +#define CLKID_DSPA_0_DIV 212 +#define CLKID_DSPA_0 213 +#define CLKID_DSPA_1_SEL 214 +#define CLKID_DSPA_1_DIV 215 +#define CLKID_DSPA_1 216 +#define CLKID_DSPA 217 +#define CLKID_HEVCF_0_SEL 218 +#define CLKID_HEVCF_0_DIV 219 +#define CLKID_HEVCF_0 220 +#define CLKID_HEVCF_1_SEL 221 +#define CLKID_HEVCF_1_DIV 222 +#define CLKID_HEVCF_1 223 +#define CLKID_HEVCF 224 +#define CLKID_HCODEC_0_SEL 225 +#define CLKID_HCODEC_0_DIV 226 +#define CLKID_HCODEC_0 227 +#define CLKID_HCODEC_1_SEL 228 +#define CLKID_HCODEC_1_DIV 229 +#define CLKID_HCODEC_1 230 +#define CLKID_HCODEC 231 +#define CLKID_VPU_0_SEL 232 +#define CLKID_VPU_0_DIV 233 +#define CLKID_VPU_0 234 +#define CLKID_VPU_1_SEL 235 +#define CLKID_VPU_1_DIV 236 +#define CLKID_VPU_1 237 +#define CLKID_VPU 238 +#define CLKID_VAPB_0_SEL 239 +#define CLKID_VAPB_0_DIV 240 +#define CLKID_VAPB_0 241 +#define CLKID_VAPB_1_SEL 242 +#define CLKID_VAPB_1_DIV 243 +#define CLKID_VAPB_1 244 +#define CLKID_VAPB 245 +#define CLKID_GE2D 246 +#define CLKID_VPU_CLKB_TMP_SEL 247 +#define CLKID_VPU_CLKB_TMP_DIV 248 +#define CLKID_VPU_CLKB_TMP 249 +#define CLKID_VPU_CLKB_DIV 250 +#define CLKID_VPU_CLKB 251 +#define CLKID_HDMITX_SYS_SEL 252 +#define CLKID_HDMITX_SYS_DIV 253 +#define CLKID_HDMITX_SYS 254 +#define CLKID_HDMITX_PRIF_SEL 255 +#define CLKID_HDMITX_PRIF_DIV 256 +#define CLKID_HDMITX_PRIF 257 +#define CLKID_HDMITX_200M_SEL 258 +#define CLKID_HDMITX_200M_DIV 259 +#define CLKID_HDMITX_200M 260 +#define CLKID_HDMITX_AUD_SEL 261 +#define CLKID_HDMITX_AUD_DIV 262 +#define CLKID_HDMITX_AUD 263 +#define CLKID_HDMIRX_5M_SEL 264 +#define CLKID_HDMIRX_5M_DIV 265 +#define CLKID_HDMIRX_5M 266 +#define CLKID_HDMIRX_2M_SEL 267 +#define CLKID_HDMIRX_2M_DIV 268 +#define CLKID_HDMIRX_2M 269 +#define CLKID_HDMIRX_CFG_SEL 270 +#define CLKID_HDMIRX_CFG_DIV 271 +#define CLKID_HDMIRX_CFG 272 +#define CLKID_HDMIRX_HDCP2X_SEL 273 +#define CLKID_HDMIRX_HDCP2X_DIV 274 +#define CLKID_HDMIRX_HDCP2X 275 +#define CLKID_HDMIRX_ACR_REF_SEL 276 +#define CLKID_HDMIRX_ACR_REF_DIV 277 +#define CLKID_HDMIRX_ACR_REF 278 +#define CLKID_HDMIRX_METER_SEL 279 +#define CLKID_HDMIRX_METER_DIV 280 +#define CLKID_HDMIRX_METER 281 +#define CLKID_VID_LOCK_SEL 282 +#define CLKID_VID_LOCK_DIV 283 +#define CLKID_VID_LOCK 284 +#define CLKID_VDIN_MEAS_SEL 285 +#define CLKID_VDIN_MEAS_DIV 286 +#define CLKID_VDIN_MEAS 287 +#define CLKID_VID_PLL_DIV 288 +#define CLKID_VID_PLL_SEL 289 +#define CLKID_VID_PLL 290 +#define CLKID_VID_PLL_VCLK 291 +#define CLKID_VCLK0_SEL 292 +#define CLKID_VCLK0_IN 293 +#define CLKID_VCLK0_DIV 294 +#define CLKID_VCLK0 295 +#define CLKID_VCLK0_DIV1_EN 296 +#define CLKID_VCLK0_DIV2_EN 297 +#define CLKID_VCLK0_DIV2 298 +#define CLKID_VCLK0_DIV4_EN 299 +#define CLKID_VCLK0_DIV4 300 +#define CLKID_VCLK0_DIV6_EN 301 +#define CLKID_VCLK0_DIV6 302 +#define CLKID_VCLK0_DIV12_EN 303 +#define CLKID_VCLK0_DIV12 304 +#define CLKID_VCLK1_SEL 305 +#define CLKID_VCLK1_IN 306 +#define CLKID_VCLK1_DIV 307 +#define CLKID_VCLK1 308 +#define CLKID_VCLK1_DIV1_EN 309 +#define CLKID_VCLK1_DIV2_EN 310 +#define CLKID_VCLK1_DIV2 311 +#define CLKID_VCLK1_DIV4_EN 312 +#define CLKID_VCLK1_DIV4 313 +#define CLKID_VCLK1_DIV6_EN 314 +#define CLKID_VCLK1_DIV6 315 +#define CLKID_VCLK1_DIV12_EN 316 +#define CLKID_VCLK1_DIV12 317 +#define CLKID_VDAC_SEL 318 +#define CLKID_VDAC 319 +#define CLKID_ENCODER0_SEL 320 +#define CLKID_ENCODER0 321 +#define CLKID_ENCODER1_SEL 322 +#define CLKID_ENCODER1 323 +#define CLKID_HDMITX0_PIXEL_SEL 324 +#define CLKID_HDMITX0_PIXEL 325 +#define CLKID_HDMITX0_FE_SEL 326 +#define CLKID_HDMITX0_FE 327 +#define CLKID_HDMITX1_PIXEL_SEL 328 +#define CLKID_HDMITX1_PIXEL 329 +#define CLKID_HDMITX1_FE_SEL 330 +#define CLKID_HDMITX1_FE 331 +#define CLKID_CSI_PHY_SEL 332 +#define CLKID_CSI_PHY_DIV 333 +#define CLKID_CSI_PHY 334 +#define CLKID_DSI0_MEAS_SEL 335 +#define CLKID_DSI0_MEAS_DIV 336 +#define CLKID_DSI0_MEAS 337 +#define CLKID_DSI1_MEAS_SEL 338 +#define CLKID_DSI1_MEAS_DIV 339 +#define CLKID_DSI1_MEAS 340 + +#endif /* __AMLOGIC_A9_PERIPHERALS_CLKC_H */ From 6f809ca8104b01dff31a20e91fa748b49fe2971f Mon Sep 17 00:00:00 2001 From: Jian Hu Date: Wed, 1 Jul 2026 16:30:03 +0800 Subject: [PATCH 0214/1328] clk: amlogic: Add A9 peripherals clock controller driver Add the peripherals clock controller driver for the Amlogic A9 SoC family. Signed-off-by: Jian Hu Link: https://patch.msgid.link/20260701-a9_peripherals-v6-2-9630f39879e5@amlogic.com [jbrunet: removed dependency en A9 PLL] Signed-off-by: Jerome Brunet --- drivers/clk/meson/Kconfig | 14 + drivers/clk/meson/Makefile | 1 + drivers/clk/meson/a9-peripherals.c | 2089 ++++++++++++++++++++++++++++ 3 files changed, 2104 insertions(+) create mode 100644 drivers/clk/meson/a9-peripherals.c diff --git a/drivers/clk/meson/Kconfig b/drivers/clk/meson/Kconfig index b71299898197..d460514bb8a1 100644 --- a/drivers/clk/meson/Kconfig +++ b/drivers/clk/meson/Kconfig @@ -132,6 +132,20 @@ config COMMON_CLK_A1_PERIPHERALS device, A1 SoC Family. Say Y if you want A1 Peripherals clock controller to work. +config COMMON_CLK_A9_PERIPHERALS + tristate "Amlogic A9 SoC peripherals clock controller support" + depends on ARM64 || COMPILE_TEST + default ARCH_MESON + select COMMON_CLK_MESON_REGMAP + select COMMON_CLK_MESON_CLKC_UTILS + select COMMON_CLK_MESON_DUALDIV + select COMMON_CLK_MESON_VID_PLL_DIV + imply COMMON_CLK_SCMI + help + Support for the peripherals clock controller on Amlogic A311Y3 based + device, AKA A9. Peripherals are required by most peripheral to operate. + Say Y if you want A9 peripherals clock controller to work. + config COMMON_CLK_A9_AO tristate "Amlogic A9 SoC AO clock controller support" depends on ARM64 || COMPILE_TEST diff --git a/drivers/clk/meson/Makefile b/drivers/clk/meson/Makefile index f89d027c282c..56b04254eb1a 100644 --- a/drivers/clk/meson/Makefile +++ b/drivers/clk/meson/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_COMMON_CLK_AXG) += axg.o axg-aoclk.o obj-$(CONFIG_COMMON_CLK_AXG_AUDIO) += axg-audio.o obj-$(CONFIG_COMMON_CLK_A1_PLL) += a1-pll.o obj-$(CONFIG_COMMON_CLK_A1_PERIPHERALS) += a1-peripherals.o +obj-$(CONFIG_COMMON_CLK_A9_PERIPHERALS) += a9-peripherals.o obj-$(CONFIG_COMMON_CLK_A9_AO) += a9-aoclk.o obj-$(CONFIG_COMMON_CLK_C3_PLL) += c3-pll.o obj-$(CONFIG_COMMON_CLK_C3_PERIPHERALS) += c3-peripherals.o diff --git a/drivers/clk/meson/a9-peripherals.c b/drivers/clk/meson/a9-peripherals.c new file mode 100644 index 000000000000..b77e35f48c1f --- /dev/null +++ b/drivers/clk/meson/a9-peripherals.c @@ -0,0 +1,2089 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) +/* + * Copyright (C) 2026 Amlogic, Inc. All rights reserved + */ + +#include +#include +#include +#include +#include "clk-regmap.h" +#include "clk-dualdiv.h" +#include "meson-clkc-utils.h" +#include "vid-pll-div.h" + +#define SYS_CLK_EN0_REG0 0x30 +#define SYS_CLK_EN0_REG1 0x34 +#define SYS_CLK_EN0_REG2 0x38 +#define SYS_CLK_EN0_REG3 0x3c +#define SD_EMMC_CLK_CTRL0 0x90 +#define SD_EMMC_CLK_CTRL1 0x94 +#define PWM_CLK_H_CTRL 0xbc +#define PWM_CLK_I_CTRL 0xc0 +#define PWM_CLK_J_CTRL 0xc4 +#define PWM_CLK_K_CTRL 0xc8 +#define PWM_CLK_L_CTRL 0xcc +#define PWM_CLK_M_CTRL 0xd0 +#define PWM_CLK_N_CTRL 0xd4 +#define SPISG_CLK_CTRL 0x100 +#define SPISG_CLK_CTRL1 0x104 +#define SAR_CLK_CTRL 0x150 +#define AMFC_CLK_CTRL 0x154 +#define NNA_CLK_CTRL 0x15c +#define USB_CLK_CTRL 0x160 +#define PCIE_TL_CLK_CTRL 0x164 +#define CMPR_CLK_CTRL 0x168 +#define DEWARP_CLK_CTRL 0x16c +#define SC_CLK_CTRL 0x170 +#define DPTX_CLK_CTRL 0x178 +#define ISP_CLK_CTRL 0x17c +#define CVE_CLK_CTRL 0x180 +#define PP_CLK_CTRL 0x184 +#define GLB_CLK_CTRL 0x188 +#define USB_CLK_CTRL0 0x18c +#define USB_CLK_CTRL1 0x190 +#define CAN_CLK_CTRL 0x194 +#define CAN_CLK_CTRL1 0x198 +#define I3C_CLK_CTRL 0x19c +#define TS_CLK_CTRL 0x1a0 +#define ETH_CLK_CTRL 0x1a4 +#define GEN_CLK_CTRL 0x1a8 +#define CLK12_24_CTRL 0x1ac +#define MALI_CLK_CTRL 0x200 +#define MALI_STACK_CLK_CTRL 0x204 +#define DSPA_CLK_CTRL 0x220 +#define HEVCF_CLK_CTRL 0x240 +#define HCODEC_CLK_CTRL 0x244 +#define VPU_CLK_CTRL 0x260 +#define VAPB_CLK_CTRL 0x268 +#define VPU_CLKB_CTRL 0x280 +#define HDMI_CLK_CTRL 0x284 +#define HTX_CLK_CTRL 0x28c +#define HTX_CLK_CTRL1 0x290 +#define HRX_CLK_CTRL 0x294 +#define HRX_CLK_CTRL1 0x298 +#define HRX_CLK_CTRL2 0x29c +#define HRX_CLK_CTRL3 0x2a0 +#define VID_LOCK_CLK_CTRL 0x2a4 +#define VDIN_MEAS_CLK_CTRL 0x2a8 +#define VID_PLL_CLK_DIV 0x2b0 +#define VID_CLK_CTRL 0x2c0 +#define VID_CLK_CTRL2 0x2c4 +#define VID_CLK_DIV 0x2c8 +#define VIID_CLK_DIV 0x2cc +#define VIID_CLK_CTRL 0x2d0 +#define MIPI_CSI_PHY_CLK_CTRL 0x2e0 +#define DSI_MEAS_CLK_CTRL 0x2f4 + +#define A9_COMP_SEL(_name, _reg, _shift, _mask, _pdata, _table) \ + MESON_COMP_SEL(a9_, _name, _reg, _shift, _mask, _pdata, _table, 0, 0) + +#define A9_COMP_DIV(_name, _reg, _shift, _width) \ + MESON_COMP_DIV(a9_, _name, _reg, _shift, _width, 0, CLK_SET_RATE_PARENT) + +#define A9_COMP_GATE(_name, _reg, _bit, _iflags) \ + MESON_COMP_GATE(a9_, _name, _reg, _bit, CLK_SET_RATE_PARENT | (_iflags)) + +static const struct clk_parent_data a9_sys_pclk_parents = { .fw_name = "sys" }; + +#define A9_SYS_PCLK(_name, _reg, _bit) \ + MESON_PCLK(a9_##_name, _reg, _bit, &a9_sys_pclk_parents, 0) + +static A9_SYS_PCLK(sys_am_axi, SYS_CLK_EN0_REG0, 0); +static A9_SYS_PCLK(sys_dos, SYS_CLK_EN0_REG0, 1); +static A9_SYS_PCLK(sys_mipi_dsi0, SYS_CLK_EN0_REG0, 3); +static A9_SYS_PCLK(sys_eth_phy, SYS_CLK_EN0_REG0, 4); +static A9_SYS_PCLK(sys_amfc, SYS_CLK_EN0_REG0, 5); +static A9_SYS_PCLK(sys_mali, SYS_CLK_EN0_REG0, 6); +static A9_SYS_PCLK(sys_nna, SYS_CLK_EN0_REG0, 7); +static A9_SYS_PCLK(sys_eth_axi, SYS_CLK_EN0_REG0, 8); +static A9_SYS_PCLK(sys_dp_apb, SYS_CLK_EN0_REG0, 9); +static A9_SYS_PCLK(sys_edptx_apb, SYS_CLK_EN0_REG0, 10); +static A9_SYS_PCLK(sys_u3hsg, SYS_CLK_EN0_REG0, 11); +static A9_SYS_PCLK(sys_aucpu, SYS_CLK_EN0_REG0, 14); +static A9_SYS_PCLK(sys_glb, SYS_CLK_EN0_REG0, 15); +static A9_SYS_PCLK(sys_combo_dphy_apb, SYS_CLK_EN0_REG0, 17); +static A9_SYS_PCLK(sys_hdmirx_apb, SYS_CLK_EN0_REG0, 18); +static A9_SYS_PCLK(sys_hdmirx_pclk, SYS_CLK_EN0_REG0, 19); +static A9_SYS_PCLK(sys_mipi_dsi0_phy, SYS_CLK_EN0_REG0, 20); +static A9_SYS_PCLK(sys_can0, SYS_CLK_EN0_REG0, 21); +static A9_SYS_PCLK(sys_can1, SYS_CLK_EN0_REG0, 22); +static A9_SYS_PCLK(sys_sd_emmc_a, SYS_CLK_EN0_REG0, 24); +static A9_SYS_PCLK(sys_sd_emmc_b, SYS_CLK_EN0_REG0, 25); +static A9_SYS_PCLK(sys_sd_emmc_c, SYS_CLK_EN0_REG0, 26); +static A9_SYS_PCLK(sys_sc, SYS_CLK_EN0_REG0, 27); +static A9_SYS_PCLK(sys_acodec, SYS_CLK_EN0_REG0, 28); +static A9_SYS_PCLK(sys_mipi_isp, SYS_CLK_EN0_REG0, 29); +static A9_SYS_PCLK(sys_msr, SYS_CLK_EN0_REG0, 30); +static A9_SYS_PCLK(sys_audio, SYS_CLK_EN0_REG1, 0); +static A9_SYS_PCLK(sys_mipi_dsi1, SYS_CLK_EN0_REG1, 1); +static A9_SYS_PCLK(sys_mipi_dsi1_phy, SYS_CLK_EN0_REG1, 2); +static A9_SYS_PCLK(sys_eth, SYS_CLK_EN0_REG1, 3); +static A9_SYS_PCLK(sys_eth_1g_mac, SYS_CLK_EN0_REG1, 4); +static A9_SYS_PCLK(sys_uart_a, SYS_CLK_EN0_REG1, 5); +static A9_SYS_PCLK(sys_uart_f, SYS_CLK_EN0_REG1, 10); +static A9_SYS_PCLK(sys_ts_a55, SYS_CLK_EN0_REG1, 11); +static A9_SYS_PCLK(sys_eth_1g_axi, SYS_CLK_EN0_REG1, 12); +static A9_SYS_PCLK(sys_ts_dos, SYS_CLK_EN0_REG1, 13); +static A9_SYS_PCLK(sys_u3drd_b, SYS_CLK_EN0_REG1, 14); +static A9_SYS_PCLK(sys_ts_core, SYS_CLK_EN0_REG1, 15); +static A9_SYS_PCLK(sys_ts_pll, SYS_CLK_EN0_REG1, 16); +static A9_SYS_PCLK(sys_csi_dig_clkin, SYS_CLK_EN0_REG1, 18); +static A9_SYS_PCLK(sys_cve, SYS_CLK_EN0_REG1, 19); +static A9_SYS_PCLK(sys_ge2d, SYS_CLK_EN0_REG1, 20); +static A9_SYS_PCLK(sys_spisg, SYS_CLK_EN0_REG1, 21); +static A9_SYS_PCLK(sys_u2h, SYS_CLK_EN0_REG1, 23); +static A9_SYS_PCLK(sys_pcie_mac_a, SYS_CLK_EN0_REG1, 24); +static A9_SYS_PCLK(sys_u3drd_a, SYS_CLK_EN0_REG1, 25); +static A9_SYS_PCLK(sys_u2drd, SYS_CLK_EN0_REG1, 26); +static A9_SYS_PCLK(sys_pcie_phy, SYS_CLK_EN0_REG1, 27); +static A9_SYS_PCLK(sys_pcie_mac_b, SYS_CLK_EN0_REG1, 28); +static A9_SYS_PCLK(sys_periph, SYS_CLK_EN0_REG1, 29); +static A9_SYS_PCLK(sys_pio, SYS_CLK_EN0_REG2, 0); +static A9_SYS_PCLK(sys_i3c, SYS_CLK_EN0_REG2, 1); +static A9_SYS_PCLK(sys_i2c_m_e, SYS_CLK_EN0_REG2, 2); +static A9_SYS_PCLK(sys_i2c_m_f, SYS_CLK_EN0_REG2, 3); +static A9_SYS_PCLK(sys_hdmitx_apb, SYS_CLK_EN0_REG2, 4); +static A9_SYS_PCLK(sys_i2c_m_i, SYS_CLK_EN0_REG2, 5); +static A9_SYS_PCLK(sys_i2c_m_g, SYS_CLK_EN0_REG2, 6); +static A9_SYS_PCLK(sys_i2c_m_h, SYS_CLK_EN0_REG2, 7); +static A9_SYS_PCLK(sys_hdmi20_aes, SYS_CLK_EN0_REG2, 9); +static A9_SYS_PCLK(sys_csi2_host, SYS_CLK_EN0_REG2, 16); +static A9_SYS_PCLK(sys_csi2_adapt, SYS_CLK_EN0_REG2, 17); +static A9_SYS_PCLK(sys_dspa, SYS_CLK_EN0_REG2, 21); +static A9_SYS_PCLK(sys_pp_dma, SYS_CLK_EN0_REG2, 22); +static A9_SYS_PCLK(sys_pp_wrapper, SYS_CLK_EN0_REG2, 23); +static A9_SYS_PCLK(sys_vpu_intr, SYS_CLK_EN0_REG2, 25); +static A9_SYS_PCLK(sys_csi2_phy, SYS_CLK_EN0_REG2, 27); +static A9_SYS_PCLK(sys_saradc, SYS_CLK_EN0_REG2, 28); +static A9_SYS_PCLK(sys_pwm_j, SYS_CLK_EN0_REG2, 30); +static A9_SYS_PCLK(sys_pwm_i, SYS_CLK_EN0_REG2, 31); +static A9_SYS_PCLK(sys_pwm_h, SYS_CLK_EN0_REG3, 0); +static A9_SYS_PCLK(sys_pwm_n, SYS_CLK_EN0_REG3, 8); +static A9_SYS_PCLK(sys_pwm_m, SYS_CLK_EN0_REG3, 9); +static A9_SYS_PCLK(sys_pwm_l, SYS_CLK_EN0_REG3, 10); +static A9_SYS_PCLK(sys_pwm_k, SYS_CLK_EN0_REG3, 11); + +/* Channel 5 is unconnected. */ +static u32 a9_sd_emmc_parents_val_table[] = { 0, 1, 2, 3, 4, 6, 7 }; +static const struct clk_parent_data a9_sd_emmc_parents[] = { + { .fw_name = "xtal", }, + { .fw_name = "fdiv2", }, + { .fw_name = "fdiv3", }, + { .fw_name = "hifi0", }, + { .fw_name = "fdiv2p5", }, + { .fw_name = "gp1", }, + { .fw_name = "gp0", } +}; + +static A9_COMP_SEL(sd_emmc_a, SD_EMMC_CLK_CTRL0, 9, 0x7, a9_sd_emmc_parents, + a9_sd_emmc_parents_val_table); +static A9_COMP_DIV(sd_emmc_a, SD_EMMC_CLK_CTRL0, 0, 7); +static A9_COMP_GATE(sd_emmc_a, SD_EMMC_CLK_CTRL0, 8, 0); + +static A9_COMP_SEL(sd_emmc_b, SD_EMMC_CLK_CTRL0, 25, 0x7, a9_sd_emmc_parents, + a9_sd_emmc_parents_val_table); +static A9_COMP_DIV(sd_emmc_b, SD_EMMC_CLK_CTRL0, 16, 7); +static A9_COMP_GATE(sd_emmc_b, SD_EMMC_CLK_CTRL0, 24, 0); + +static A9_COMP_SEL(sd_emmc_c, SD_EMMC_CLK_CTRL1, 9, 0x7, a9_sd_emmc_parents, + a9_sd_emmc_parents_val_table); +static A9_COMP_DIV(sd_emmc_c, SD_EMMC_CLK_CTRL1, 0, 7); +static A9_COMP_GATE(sd_emmc_c, SD_EMMC_CLK_CTRL1, 8, 0); + +static const struct clk_parent_data a9_pwm_parents[] = { + { .fw_name = "xtal", }, + { .fw_name = "fdiv5", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv3", } +}; + +static A9_COMP_SEL(pwm_h, PWM_CLK_H_CTRL, 9, 0x7, a9_pwm_parents, NULL); +static A9_COMP_DIV(pwm_h, PWM_CLK_H_CTRL, 0, 8); +static A9_COMP_GATE(pwm_h, PWM_CLK_H_CTRL, 8, 0); + +static A9_COMP_SEL(pwm_i, PWM_CLK_I_CTRL, 9, 0x7, a9_pwm_parents, NULL); +static A9_COMP_DIV(pwm_i, PWM_CLK_I_CTRL, 0, 8); +static A9_COMP_GATE(pwm_i, PWM_CLK_I_CTRL, 8, 0); + +static A9_COMP_SEL(pwm_j, PWM_CLK_J_CTRL, 9, 0x7, a9_pwm_parents, NULL); +static A9_COMP_DIV(pwm_j, PWM_CLK_J_CTRL, 0, 8); +static A9_COMP_GATE(pwm_j, PWM_CLK_J_CTRL, 8, 0); + +static A9_COMP_SEL(pwm_k, PWM_CLK_K_CTRL, 9, 0x7, a9_pwm_parents, NULL); +static A9_COMP_DIV(pwm_k, PWM_CLK_K_CTRL, 0, 8); +static A9_COMP_GATE(pwm_k, PWM_CLK_K_CTRL, 8, 0); + +static A9_COMP_SEL(pwm_l, PWM_CLK_L_CTRL, 9, 0x7, a9_pwm_parents, NULL); +static A9_COMP_DIV(pwm_l, PWM_CLK_L_CTRL, 0, 8); +static A9_COMP_GATE(pwm_l, PWM_CLK_L_CTRL, 8, 0); + +static A9_COMP_SEL(pwm_m, PWM_CLK_M_CTRL, 9, 0x7, a9_pwm_parents, NULL); +static A9_COMP_DIV(pwm_m, PWM_CLK_M_CTRL, 0, 8); +static A9_COMP_GATE(pwm_m, PWM_CLK_M_CTRL, 8, 0); + +static A9_COMP_SEL(pwm_n, PWM_CLK_N_CTRL, 9, 0x7, a9_pwm_parents, NULL); +static A9_COMP_DIV(pwm_n, PWM_CLK_N_CTRL, 0, 8); +static A9_COMP_GATE(pwm_n, PWM_CLK_N_CTRL, 8, 0); + +static const struct clk_parent_data a9_spisg_parents[] = { + { .fw_name = "xtal", }, + { .fw_name = "sys", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv2", }, + { .fw_name = "fdiv5", }, + { .fw_name = "fdiv7", }, + { .fw_name = "gp0", } +}; + +static A9_COMP_SEL(spisg0, SPISG_CLK_CTRL, 9, 0x7, a9_spisg_parents, NULL); +static A9_COMP_DIV(spisg0, SPISG_CLK_CTRL, 0, 6); +static A9_COMP_GATE(spisg0, SPISG_CLK_CTRL, 8, 0); + +static A9_COMP_SEL(spisg1, SPISG_CLK_CTRL, 25, 0x7, a9_spisg_parents, NULL); +static A9_COMP_DIV(spisg1, SPISG_CLK_CTRL, 16, 6); +static A9_COMP_GATE(spisg1, SPISG_CLK_CTRL, 24, 0); + +static A9_COMP_SEL(spisg2, SPISG_CLK_CTRL1, 9, 0x7, a9_spisg_parents, NULL); +static A9_COMP_DIV(spisg2, SPISG_CLK_CTRL1, 0, 6); +static A9_COMP_GATE(spisg2, SPISG_CLK_CTRL1, 8, 0); + +static const struct clk_parent_data a9_saradc_parents[] = { + { .fw_name = "xtal", }, + { .fw_name = "sys", } +}; + +static A9_COMP_SEL(saradc, SAR_CLK_CTRL, 9, 0x7, a9_saradc_parents, NULL); +static A9_COMP_DIV(saradc, SAR_CLK_CTRL, 0, 8); +static A9_COMP_GATE(saradc, SAR_CLK_CTRL, 8, 0); + +static const struct clk_parent_data a9_amfc_parents[] = { + { .fw_name = "xtal", }, + { .fw_name = "sys", }, + { .fw_name = "fdiv2", }, + { .fw_name = "fdiv2p5", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv5", }, + { .fw_name = "fdiv7", } +}; + +static A9_COMP_SEL(amfc, AMFC_CLK_CTRL, 9, 0x7, a9_amfc_parents, NULL); +static A9_COMP_DIV(amfc, AMFC_CLK_CTRL, 0, 6); +static A9_COMP_GATE(amfc, AMFC_CLK_CTRL, 8, 0); + +static const struct clk_parent_data a9_nna_parents[] = { + { .fw_name = "xtal", }, + { .fw_name = "fdiv2p5", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv5", }, + { .fw_name = "fdiv2", }, + { .fw_name = "gp2", }, + { .fw_name = "hifi0", } +}; + +static A9_COMP_SEL(nna, NNA_CLK_CTRL, 9, 0x7, a9_nna_parents, NULL); +static A9_COMP_DIV(nna, NNA_CLK_CTRL, 0, 7); +static A9_COMP_GATE(nna, NNA_CLK_CTRL, 8, 0); + +/* Channel 5 and 6 are unconnected. */ +static u32 a9_usb_250m_parents_val_table[] = { 0, 1, 2, 3, 4, 7 }; +static const struct clk_parent_data a9_usb_250m_parents[] = { + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv5", }, + { .fw_name = "fdiv2", }, + { .fw_name = "fdiv7", }, + { .fw_name = "fdiv2p5", } +}; + +static A9_COMP_SEL(usb_250m, USB_CLK_CTRL, 9, 0x7, a9_usb_250m_parents, + a9_usb_250m_parents_val_table); +static A9_COMP_DIV(usb_250m, USB_CLK_CTRL, 0, 7); +static A9_COMP_GATE(usb_250m, USB_CLK_CTRL, 8, 0); + +static const struct clk_parent_data a9_usb_48m_pre_parents[] = { + { .fw_name = "gp0", }, + { .fw_name = "gp1", }, + { .fw_name = "gp2", }, + { .fw_name = "fdiv2", } +}; + +static A9_COMP_SEL(usb_48m_pre, USB_CLK_CTRL, 25, 0x3, a9_usb_48m_pre_parents, NULL); +static A9_COMP_DIV(usb_48m_pre, USB_CLK_CTRL, 16, 7); +static A9_COMP_GATE(usb_48m_pre, USB_CLK_CTRL, 24, 0); + +static const struct clk_parent_data a9_pcie_tl_parents[] = { + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv5", }, + { .fw_name = "fdiv2", }, + { .fw_name = "fdiv2p5", }, + { .fw_name = "gp0", }, + { .fw_name = "sys", }, + { .fw_name = "xtal", } +}; + +static A9_COMP_SEL(pcie0_tl, PCIE_TL_CLK_CTRL, 9, 0x7, a9_pcie_tl_parents, NULL); +static A9_COMP_DIV(pcie0_tl, PCIE_TL_CLK_CTRL, 0, 7); +static A9_COMP_GATE(pcie0_tl, PCIE_TL_CLK_CTRL, 8, 0); + +static A9_COMP_SEL(pcie1_tl, PCIE_TL_CLK_CTRL, 25, 0x7, a9_pcie_tl_parents, NULL); +static A9_COMP_DIV(pcie1_tl, PCIE_TL_CLK_CTRL, 16, 7); +static A9_COMP_GATE(pcie1_tl, PCIE_TL_CLK_CTRL, 24, 0); + +static const struct clk_parent_data a9_cmpr_parents[] = { + { .fw_name = "xtal", }, + { .fw_name = "fdiv2p5", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv5", }, + { .fw_name = "fdiv7", }, + { .fw_name = "hifi0", }, + { .fw_name = "gp1", } +}; + +static A9_COMP_SEL(cmpr, CMPR_CLK_CTRL, 25, 0x7, a9_cmpr_parents, NULL); +static A9_COMP_DIV(cmpr, CMPR_CLK_CTRL, 16, 7); +static A9_COMP_GATE(cmpr, CMPR_CLK_CTRL, 24, 0); + +static const struct clk_parent_data a9_dewarpa_parents[] = { + { .fw_name = "fdiv2p5", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv5", }, + { .fw_name = "fdiv7", }, + { .fw_name = "gp0", }, + { .fw_name = "hifi0", }, + { .fw_name = "gp1", } +}; + +static A9_COMP_SEL(dewarpa, DEWARP_CLK_CTRL, 9, 0x7, a9_dewarpa_parents, NULL); +static A9_COMP_DIV(dewarpa, DEWARP_CLK_CTRL, 0, 7); +static A9_COMP_GATE(dewarpa, DEWARP_CLK_CTRL, 8, 0); + +static const struct clk_parent_data a9_sc_parents[] = { + { .fw_name = "fdiv2", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv5", }, + { .fw_name = "xtal", } +}; + +static A9_COMP_SEL(sc_pre, SC_CLK_CTRL, 9, 0x7, a9_sc_parents, NULL); +static A9_COMP_DIV(sc_pre, SC_CLK_CTRL, 0, 8); +static A9_COMP_GATE(sc_pre, SC_CLK_CTRL, 8, 0); + +static struct clk_regmap a9_sc = { + .data = &(struct clk_regmap_div_data) { + .offset = SC_CLK_CTRL, + .shift = 16, + .width = 4, + }, + .hw.init = &(struct clk_init_data) { + .name = "sc", + .ops = &clk_regmap_divider_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_sc_pre.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static const struct clk_parent_data a9_dptx_apb2_parents[] = { + { .fw_name = "xtal", }, + { .fw_name = "sys", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv5", } +}; + +static A9_COMP_SEL(dptx_apb2, DPTX_CLK_CTRL, 9, 0x7, a9_dptx_apb2_parents, NULL); +static A9_COMP_DIV(dptx_apb2, DPTX_CLK_CTRL, 0, 7); +static A9_COMP_GATE(dptx_apb2, DPTX_CLK_CTRL, 8, 0); + +static const struct clk_parent_data a9_dptx_aud_parents[] = { + { .fw_name = "xtal", }, + { .fw_name = "sys", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv4", } +}; + +static A9_COMP_SEL(dptx_aud, DPTX_CLK_CTRL, 25, 0x7, a9_dptx_aud_parents, NULL); +static A9_COMP_DIV(dptx_aud, DPTX_CLK_CTRL, 16, 7); +static A9_COMP_GATE(dptx_aud, DPTX_CLK_CTRL, 24, 0); + +static const struct clk_parent_data a9_isp_parents[] = { + { .fw_name = "fdiv2p5", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv5", }, + { .fw_name = "gp0", }, + { .fw_name = "hifi0", }, + { .fw_name = "fdiv2", }, + { .fw_name = "xtal", } +}; + +static A9_COMP_SEL(isp, ISP_CLK_CTRL, 9, 0x7, a9_isp_parents, NULL); +static A9_COMP_DIV(isp, ISP_CLK_CTRL, 0, 7); +static A9_COMP_GATE(isp, ISP_CLK_CTRL, 8, 0); + +static const struct clk_parent_data a9_cve_vge_parents[] = { + { .fw_name = "xtal", }, + { .fw_name = "fdiv2p5", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv4", }, + { .fw_name = "hifi0", }, + { .fw_name = "fdiv5", }, + { .fw_name = "gp0", }, + { .fw_name = "rtc", } +}; + +static A9_COMP_SEL(cve, CVE_CLK_CTRL, 9, 0x7, a9_cve_vge_parents, NULL); +static A9_COMP_DIV(cve, CVE_CLK_CTRL, 0, 7); +static A9_COMP_GATE(cve, CVE_CLK_CTRL, 8, 0); + +static A9_COMP_SEL(vge, CVE_CLK_CTRL, 25, 0x7, a9_cve_vge_parents, NULL); +static A9_COMP_DIV(vge, CVE_CLK_CTRL, 16, 7); +static A9_COMP_GATE(vge, CVE_CLK_CTRL, 24, 0); + +static const struct clk_parent_data a9_pp_parents[] = { + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv5", }, + { .fw_name = "fdiv2", }, + { .fw_name = "fdiv2p5", }, + { .fw_name = "gp0", }, + { .fw_name = "sys", }, + { .fw_name = "xtal", } +}; + +static A9_COMP_SEL(pp, PP_CLK_CTRL, 9, 0x7, a9_pp_parents, NULL); +static A9_COMP_DIV(pp, PP_CLK_CTRL, 0, 6); +static A9_COMP_GATE(pp, PP_CLK_CTRL, 8, 0); + +static const struct clk_parent_data a9_dspa_parents[] = { + { .fw_name = "xtal", }, + { .fw_name = "fdiv2p5", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv5", }, + { .fw_name = "gp2", }, + { .fw_name = "fdiv4", }, + { .fw_name = "hifi0", }, + { .fw_name = "rtc", } +}; + +static A9_COMP_SEL(dspa_0, DSPA_CLK_CTRL, 9, 0x7, a9_dspa_parents, NULL); +static A9_COMP_DIV(dspa_0, DSPA_CLK_CTRL, 0, 7); +static A9_COMP_GATE(dspa_0, DSPA_CLK_CTRL, 8, CLK_SET_RATE_GATE); + +static A9_COMP_SEL(dspa_1, DSPA_CLK_CTRL, 25, 0x7, a9_dspa_parents, NULL); +static A9_COMP_DIV(dspa_1, DSPA_CLK_CTRL, 16, 7); +static A9_COMP_GATE(dspa_1, DSPA_CLK_CTRL, 24, CLK_SET_RATE_GATE); + +static struct clk_regmap a9_dspa = { + .data = &(struct clk_regmap_mux_data){ + .offset = DSPA_CLK_CTRL, + .mask = 0x1, + .shift = 31, + }, + .hw.init = &(struct clk_init_data){ + .name = "dspa", + .ops = &clk_regmap_mux_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_dspa_0.hw, + &a9_dspa_1.hw + }, + .num_parents = 2, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +/* Channel 6 is unconnected. */ +static u32 a9_glb_parents_val_table[] = { 0, 1, 2, 3, 4, 5, 7 }; +static const struct clk_parent_data a9_glb_parents[] = { + { .fw_name = "xtal", }, + { .hw = &a9_dspa.hw }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv5", }, + { .hw = &a9_isp.hw }, + { .fw_name = "rtc", } +}; + +static A9_COMP_SEL(glb, GLB_CLK_CTRL, 9, 0x7, a9_glb_parents, a9_glb_parents_val_table); +static A9_COMP_DIV(glb, GLB_CLK_CTRL, 0, 7); +static A9_COMP_GATE(glb, GLB_CLK_CTRL, 8, 0); + +static struct clk_regmap a9_usb_48m_dualdiv_in = { + .data = &(struct clk_regmap_gate_data) { + .offset = USB_CLK_CTRL, + .bit_idx = 31, + }, + .hw.init = &(struct clk_init_data) { + .name = "usb_48m_dualdiv_in", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_usb_48m_pre.hw + }, + .num_parents = 1, + }, +}; + +static const struct meson_clk_dualdiv_param a9_usb_48m_dualdiv_div_table[] = { + { 733, 732, 8, 11, 1 }, + { /* sentinel */ } +}; + +static struct clk_regmap a9_usb_48m_dualdiv_div = { + .data = &(struct meson_clk_dualdiv_data) { + .n1 = { + .reg_off = USB_CLK_CTRL0, + .shift = 0, + .width = 12, + }, + .n2 = { + .reg_off = USB_CLK_CTRL0, + .shift = 12, + .width = 12, + }, + .m1 = { + .reg_off = USB_CLK_CTRL1, + .shift = 0, + .width = 12, + }, + .m2 = { + .reg_off = USB_CLK_CTRL1, + .shift = 12, + .width = 12, + }, + .dual = { + .reg_off = USB_CLK_CTRL0, + .shift = 28, + .width = 1, + }, + .table = a9_usb_48m_dualdiv_div_table, + }, + .hw.init = &(struct clk_init_data) { + .name = "usb_48m_dualdiv_div", + .ops = &meson_clk_dualdiv_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_usb_48m_dualdiv_in.hw + }, + .num_parents = 1, + }, +}; + +static struct clk_regmap a9_usb_48m_dualdiv_sel = { + .data = &(struct clk_regmap_mux_data) { + .offset = USB_CLK_CTRL1, + .mask = 0x1, + .shift = 24, + }, + .hw.init = &(struct clk_init_data) { + .name = "usb_48m_dualdiv_sel", + .ops = &clk_regmap_mux_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_usb_48m_dualdiv_in.hw, + &a9_usb_48m_dualdiv_div.hw, + }, + .num_parents = 2, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_usb_48m_dualdiv = { + .data = &(struct clk_regmap_gate_data) { + .offset = USB_CLK_CTRL0, + .bit_idx = 30, + }, + .hw.init = &(struct clk_init_data) { + .name = "usb_48m_dualdiv", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_usb_48m_dualdiv_sel.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_usb_48m = { + .data = &(struct clk_regmap_mux_data) { + .offset = USB_CLK_CTRL1, + .mask = 0x3, + .shift = 30, + }, + .hw.init = &(struct clk_init_data) { + .name = "usb_48m", + .ops = &clk_regmap_mux_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_usb_48m_pre.hw, + &a9_usb_48m_dualdiv.hw, + }, + .num_parents = 2, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +/* Channel 2 is unconnected. */ +static u32 a9_can_pe_parents_val_table[] = { 0, 1, 3 }; +static const struct clk_parent_data a9_can_pe_parents[] = { + { .fw_name = "sys", }, + { .fw_name = "xtal", }, + { .fw_name = "fdiv5", } +}; + +static A9_COMP_SEL(can0_pe, CAN_CLK_CTRL, 9, 0x7, a9_can_pe_parents, a9_can_pe_parents_val_table); +static A9_COMP_DIV(can0_pe, CAN_CLK_CTRL, 0, 7); +static A9_COMP_GATE(can0_pe, CAN_CLK_CTRL, 8, 0); + +static A9_COMP_SEL(can1_pe, CAN_CLK_CTRL, 25, 0x7, a9_can_pe_parents, a9_can_pe_parents_val_table); +static A9_COMP_DIV(can1_pe, CAN_CLK_CTRL, 16, 7); +static A9_COMP_GATE(can1_pe, CAN_CLK_CTRL, 24, 0); + +static const struct clk_parent_data a9_can_filter_parents[] = { + { .fw_name = "sys", }, + { .fw_name = "xtal", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv5", } +}; + +static A9_COMP_SEL(can0_filter, CAN_CLK_CTRL1, 9, 0x7, a9_can_filter_parents, NULL); +static A9_COMP_DIV(can0_filter, CAN_CLK_CTRL1, 0, 7); +static A9_COMP_GATE(can0_filter, CAN_CLK_CTRL1, 8, 0); + +static A9_COMP_SEL(can1_filter, CAN_CLK_CTRL1, 25, 0x7, a9_can_filter_parents, NULL); +static A9_COMP_DIV(can1_filter, CAN_CLK_CTRL1, 16, 7); +static A9_COMP_GATE(can1_filter, CAN_CLK_CTRL1, 24, 0); + +static const struct clk_parent_data a9_i3c_parents[] = { + { .fw_name = "sys", }, + { .fw_name = "xtal", }, + { .fw_name = "fdiv5", } +}; + +static A9_COMP_SEL(i3c, I3C_CLK_CTRL, 9, 0x7, a9_i3c_parents, NULL); +static A9_COMP_DIV(i3c, I3C_CLK_CTRL, 0, 8); +static A9_COMP_GATE(i3c, I3C_CLK_CTRL, 8, 0); + +static struct clk_regmap a9_ts_div = { + .data = &(struct clk_regmap_div_data) { + .offset = TS_CLK_CTRL, + .shift = 0, + .width = 8, + }, + .hw.init = &(struct clk_init_data) { + .name = "ts_div", + .ops = &clk_regmap_divider_ops, + .parent_data = &(const struct clk_parent_data) { + .fw_name = "xtal", + }, + .num_parents = 1, + }, +}; + +static struct clk_regmap a9_ts = { + .data = &(struct clk_regmap_gate_data) { + .offset = TS_CLK_CTRL, + .bit_idx = 8, + }, + .hw.init = &(struct clk_init_data) { + .name = "ts", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_ts_div.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_fixed_factor a9_eth_125m_div = { + .mult = 1, + .div = 8, + .hw.init = &(struct clk_init_data) { + .name = "eth_125m_div", + .ops = &clk_fixed_factor_ops, + .parent_data = &(const struct clk_parent_data) { + .fw_name = "fdiv2", + }, + .num_parents = 1, + }, +}; + +static struct clk_regmap a9_eth_125m = { + .data = &(struct clk_regmap_gate_data) { + .offset = ETH_CLK_CTRL, + .bit_idx = 7, + }, + .hw.init = &(struct clk_init_data) { + .name = "eth_125m", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_eth_125m_div.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +/* + * Channel 1, 2, 3, 4, 5 and 6 are unconnected, + * Channel 7(ext_rmii) connects external PAD. Do not automatically reparent. + */ +static u32 a9_eth_rmii_parents_val_table[] = { 0, 7 }; +static const struct clk_parent_data a9_eth_rmii_parents[] = { + { .fw_name = "fdiv2", }, + { .fw_name = "ext_rmii", } +}; + +static struct clk_regmap a9_eth_rmii_sel = { + .data = &(struct clk_regmap_mux_data) { + .offset = ETH_CLK_CTRL, + .mask = 0x7, + .shift = 9, + .table = a9_eth_rmii_parents_val_table, + }, + .hw.init = &(struct clk_init_data){ + .name = "eth_rmii_sel", + .ops = &clk_regmap_mux_ops, + .parent_data = a9_eth_rmii_parents, + .num_parents = ARRAY_SIZE(a9_eth_rmii_parents), + .flags = CLK_SET_RATE_NO_REPARENT, + }, +}; + +static struct clk_regmap a9_eth_rmii_div = { + .data = &(struct clk_regmap_div_data) { + .offset = ETH_CLK_CTRL, + .shift = 0, + .width = 7, + }, + .hw.init = &(struct clk_init_data){ + .name = "eth_rmii_div", + .ops = &clk_regmap_divider_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_eth_rmii_sel.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_eth_rmii = { + .data = &(struct clk_regmap_gate_data) { + .offset = ETH_CLK_CTRL, + .bit_idx = 8, + }, + .hw.init = &(struct clk_init_data){ + .name = "eth_rmii", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_eth_rmii_div.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_vid_pll_div = { + .data = &(struct meson_vid_pll_div_data){ + .val = { + .reg_off = VID_PLL_CLK_DIV, + .shift = 0, + .width = 15, + }, + .sel = { + .reg_off = VID_PLL_CLK_DIV, + .shift = 16, + .width = 2, + }, + }, + .hw.init = &(struct clk_init_data) { + .name = "vid_pll_div", + .ops = &meson_vid_pll_div_ro_ops, + .parent_data = (const struct clk_parent_data []) { + { .fw_name = "hdmiout2", } + }, + .num_parents = 1, + }, +}; + +static struct clk_regmap a9_vid_pll_sel = { + .data = &(struct clk_regmap_mux_data){ + .offset = VID_PLL_CLK_DIV, + .mask = 0x1, + .shift = 18, + }, + .hw.init = &(struct clk_init_data){ + .name = "vid_pll_sel", + .ops = &clk_regmap_mux_ops, + .parent_data = (const struct clk_parent_data []) { + { .hw = &a9_vid_pll_div.hw }, + { .fw_name = "hdmiout2", } + }, + .num_parents = 2, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_vid_pll = { + .data = &(struct clk_regmap_gate_data){ + .offset = VID_PLL_CLK_DIV, + .bit_idx = 19, + }, + .hw.init = &(struct clk_init_data) { + .name = "vid_pll", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_vid_pll_sel.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +/* + * Channel 12 (msr_clk) is managed by the clock measurement module and is not part of the clock + * tree. It depends on the measurement source selected through the measurement control registers. + * + * Channel 10, 11, 13, 14 and 16 are unconnected. + */ +static u32 a9_gen_parents_val_table[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 15, 17, 18, + 19, 20, 21, 22, 23, 24, 25, 26}; +static const struct clk_parent_data a9_gen_parents[] = { + { .fw_name = "xtal" }, + { .fw_name = "rtc" }, + { .fw_name = "sysplldiv16" }, + { .fw_name = "ddr_test" }, + { .hw = &a9_vid_pll.hw }, + { .fw_name = "gp0" }, + { .fw_name = "hifi1" }, + { .fw_name = "hifi0" }, + { .fw_name = "gp1" }, + { .fw_name = "gp2" }, + { .fw_name = "dsudiv16" }, + { .fw_name = "cpudiv16" }, + { .fw_name = "a78div16" }, + { .fw_name = "fdiv2" }, + { .fw_name = "fdiv2p5" }, + { .fw_name = "fdiv3" }, + { .fw_name = "fdiv4" }, + { .fw_name = "fdiv5" }, + { .fw_name = "fdiv7" }, + { .fw_name = "mclk0" }, + { .fw_name = "mclk1" } +}; + +static A9_COMP_SEL(gen, GEN_CLK_CTRL, 12, 0x1f, a9_gen_parents, a9_gen_parents_val_table); +static A9_COMP_DIV(gen, GEN_CLK_CTRL, 0, 11); +static A9_COMP_GATE(gen, GEN_CLK_CTRL, 11, 0); + +static struct clk_regmap a9_24m_in = { + .data = &(struct clk_regmap_gate_data) { + .offset = CLK12_24_CTRL, + .bit_idx = 11, + }, + .hw.init = &(struct clk_init_data) { + .name = "24m_in", + .ops = &clk_regmap_gate_ops, + .parent_data = &(const struct clk_parent_data) { + .fw_name = "xtal", + }, + .num_parents = 1, + }, +}; + +static struct clk_regmap a9_12_24m = { + .data = &(struct clk_regmap_div_data) { + .offset = CLK12_24_CTRL, + .shift = 10, + .width = 1, + }, + .hw.init = &(struct clk_init_data) { + .name = "12_24m", + .ops = &clk_regmap_divider_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_24m_in.hw + }, + .num_parents = 1, + }, +}; + +static const struct clk_parent_data a9_mali_parents[] = { + { .fw_name = "xtal", }, + { .fw_name = "gp1", }, + { .fw_name = "fdiv2", }, + { .fw_name = "fdiv2p5", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv5", }, + { .fw_name = "fdiv7", } +}; + +static A9_COMP_SEL(mali_0, MALI_CLK_CTRL, 9, 0x7, a9_mali_parents, NULL); +static A9_COMP_DIV(mali_0, MALI_CLK_CTRL, 0, 7); +static A9_COMP_GATE(mali_0, MALI_CLK_CTRL, 8, CLK_SET_RATE_GATE); + +static A9_COMP_SEL(mali_1, MALI_CLK_CTRL, 25, 0x7, a9_mali_parents, NULL); +static A9_COMP_DIV(mali_1, MALI_CLK_CTRL, 16, 7); +static A9_COMP_GATE(mali_1, MALI_CLK_CTRL, 24, CLK_SET_RATE_GATE); + +static struct clk_regmap a9_mali = { + .data = &(struct clk_regmap_mux_data){ + .offset = MALI_CLK_CTRL, + .mask = 0x1, + .shift = 31, + }, + .hw.init = &(struct clk_init_data){ + .name = "mali", + .ops = &clk_regmap_mux_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_mali_0.hw, + &a9_mali_1.hw + }, + .num_parents = 2, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static A9_COMP_SEL(mali_stack_0, MALI_STACK_CLK_CTRL, 9, 0x7, a9_mali_parents, NULL); +static A9_COMP_DIV(mali_stack_0, MALI_STACK_CLK_CTRL, 0, 7); +static A9_COMP_GATE(mali_stack_0, MALI_STACK_CLK_CTRL, 8, CLK_SET_RATE_GATE); + +static A9_COMP_SEL(mali_stack_1, MALI_STACK_CLK_CTRL, 25, 0x7, a9_mali_parents, NULL); +static A9_COMP_DIV(mali_stack_1, MALI_STACK_CLK_CTRL, 16, 7); +static A9_COMP_GATE(mali_stack_1, MALI_STACK_CLK_CTRL, 24, CLK_SET_RATE_GATE); + +static struct clk_regmap a9_mali_stack = { + .data = &(struct clk_regmap_mux_data){ + .offset = MALI_STACK_CLK_CTRL, + .mask = 0x1, + .shift = 31, + }, + .hw.init = &(struct clk_init_data){ + .name = "mali_stack", + .ops = &clk_regmap_mux_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_mali_stack_0.hw, + &a9_mali_stack_1.hw + }, + .num_parents = 2, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static const struct clk_parent_data a9_hevcf_parents[] = { + { .fw_name = "fdiv2p5", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv5", }, + { .fw_name = "fdiv7", }, + { .fw_name = "hifi0", }, + { .fw_name = "gp1", }, + { .fw_name = "xtal", } +}; + +static A9_COMP_SEL(hevcf_0, HEVCF_CLK_CTRL, 9, 0x7, a9_hevcf_parents, NULL); +static A9_COMP_DIV(hevcf_0, HEVCF_CLK_CTRL, 0, 7); +static A9_COMP_GATE(hevcf_0, HEVCF_CLK_CTRL, 8, CLK_SET_RATE_GATE); + +static A9_COMP_SEL(hevcf_1, HEVCF_CLK_CTRL, 25, 0x7, a9_hevcf_parents, NULL); +static A9_COMP_DIV(hevcf_1, HEVCF_CLK_CTRL, 16, 7); +static A9_COMP_GATE(hevcf_1, HEVCF_CLK_CTRL, 24, CLK_SET_RATE_GATE); + +static struct clk_regmap a9_hevcf = { + .data = &(struct clk_regmap_mux_data){ + .offset = HEVCF_CLK_CTRL, + .mask = 0x1, + .shift = 31, + }, + .hw.init = &(struct clk_init_data){ + .name = "hevcf", + .ops = &clk_regmap_mux_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_hevcf_0.hw, + &a9_hevcf_1.hw + }, + .num_parents = 2, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static const struct clk_parent_data a9_hcodec_parents[] = { + { .fw_name = "fdiv2p5", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv5", }, + { .fw_name = "fdiv7", }, + { .fw_name = "hifi0", }, + { .fw_name = "gp0", }, + { .fw_name = "xtal", } +}; + +static A9_COMP_SEL(hcodec_0, HCODEC_CLK_CTRL, 9, 0x7, a9_hcodec_parents, NULL); +static A9_COMP_DIV(hcodec_0, HCODEC_CLK_CTRL, 0, 7); +static A9_COMP_GATE(hcodec_0, HCODEC_CLK_CTRL, 8, CLK_SET_RATE_GATE); + +static A9_COMP_SEL(hcodec_1, HCODEC_CLK_CTRL, 25, 0x7, a9_hcodec_parents, NULL); +static A9_COMP_DIV(hcodec_1, HCODEC_CLK_CTRL, 16, 7); +static A9_COMP_GATE(hcodec_1, HCODEC_CLK_CTRL, 24, CLK_SET_RATE_GATE); + +static struct clk_regmap a9_hcodec = { + .data = &(struct clk_regmap_mux_data){ + .offset = HCODEC_CLK_CTRL, + .mask = 0x1, + .shift = 31, + }, + .hw.init = &(struct clk_init_data){ + .name = "hcodec", + .ops = &clk_regmap_mux_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_hcodec_0.hw, + &a9_hcodec_1.hw + }, + .num_parents = 2, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static const struct clk_parent_data a9_vpu_parents[] = { + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv5", }, + { .fw_name = "vid1", }, + { .fw_name = "fdiv2", }, + { .hw = &a9_vid_pll.hw }, + { .fw_name = "vid2", }, + { .fw_name = "gp1", } +}; + +static A9_COMP_SEL(vpu_0, VPU_CLK_CTRL, 9, 0x7, a9_vpu_parents, NULL); +static A9_COMP_DIV(vpu_0, VPU_CLK_CTRL, 0, 7); +static A9_COMP_GATE(vpu_0, VPU_CLK_CTRL, 8, CLK_SET_RATE_GATE); + +static A9_COMP_SEL(vpu_1, VPU_CLK_CTRL, 25, 0x7, a9_vpu_parents, NULL); +static A9_COMP_DIV(vpu_1, VPU_CLK_CTRL, 16, 7); +static A9_COMP_GATE(vpu_1, VPU_CLK_CTRL, 24, CLK_SET_RATE_GATE); + +static struct clk_regmap a9_vpu = { + .data = &(struct clk_regmap_mux_data){ + .offset = VPU_CLK_CTRL, + .mask = 0x1, + .shift = 31, + }, + .hw.init = &(struct clk_init_data){ + .name = "vpu", + .ops = &clk_regmap_mux_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_vpu_0.hw, + &a9_vpu_1.hw + }, + .num_parents = 2, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static const struct clk_parent_data a9_vapb_parents[] = { + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv5", }, + { .fw_name = "fdiv7", }, + { .fw_name = "fdiv2", }, + { .hw = &a9_vid_pll.hw }, + { .fw_name = "hifi0", }, + { .fw_name = "fdiv2p5", } +}; + +static A9_COMP_SEL(vapb_0, VAPB_CLK_CTRL, 9, 0x7, a9_vapb_parents, NULL); +static A9_COMP_DIV(vapb_0, VAPB_CLK_CTRL, 0, 7); +static A9_COMP_GATE(vapb_0, VAPB_CLK_CTRL, 8, CLK_SET_RATE_GATE); + +static A9_COMP_SEL(vapb_1, VAPB_CLK_CTRL, 25, 0x7, a9_vapb_parents, NULL); +static A9_COMP_DIV(vapb_1, VAPB_CLK_CTRL, 16, 7); +static A9_COMP_GATE(vapb_1, VAPB_CLK_CTRL, 24, CLK_SET_RATE_GATE); + +static struct clk_regmap a9_vapb = { + .data = &(struct clk_regmap_mux_data){ + .offset = VAPB_CLK_CTRL, + .mask = 0x1, + .shift = 31, + }, + .hw.init = &(struct clk_init_data){ + .name = "vapb", + .ops = &clk_regmap_mux_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_vapb_0.hw, + &a9_vapb_1.hw + }, + .num_parents = 2, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_ge2d = { + .data = &(struct clk_regmap_gate_data) { + .offset = VAPB_CLK_CTRL, + .bit_idx = 30, + }, + .hw.init = &(struct clk_init_data) { + .name = "ge2d", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_vapb.hw, + }, + .num_parents = 1, + }, +}; + +static const struct clk_parent_data a9_vpu_clkb_tmp_parents[] = { + { .hw = &a9_vpu.hw }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv5", }, + { .fw_name = "fdiv7", } +}; + +static A9_COMP_SEL(vpu_clkb_tmp, VPU_CLKB_CTRL, 25, 0x7, a9_vpu_clkb_tmp_parents, NULL); +static A9_COMP_DIV(vpu_clkb_tmp, VPU_CLKB_CTRL, 16, 4); +static A9_COMP_GATE(vpu_clkb_tmp, VPU_CLKB_CTRL, 24, 0); + +static struct clk_regmap a9_vpu_clkb_div = { + .data = &(struct clk_regmap_div_data) { + .offset = VPU_CLKB_CTRL, + .shift = 0, + .width = 8, + }, + .hw.init = &(struct clk_init_data) { + .name = "vpu_clkb_div", + .ops = &clk_regmap_divider_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_vpu_clkb_tmp.hw, + }, + .num_parents = 1, + }, +}; + +static struct clk_regmap a9_vpu_clkb = { + .data = &(struct clk_regmap_gate_data) { + .offset = VPU_CLKB_CTRL, + .bit_idx = 8, + }, + .hw.init = &(struct clk_init_data) { + .name = "vpu_clkb", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_vpu_clkb_div.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static const struct clk_parent_data a9_hdmi_parents[] = { + { .fw_name = "xtal", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv5", } +}; + +static A9_COMP_SEL(hdmitx_sys, HDMI_CLK_CTRL, 9, 0x7, a9_hdmi_parents, NULL); +static A9_COMP_DIV(hdmitx_sys, HDMI_CLK_CTRL, 0, 7); +static A9_COMP_GATE(hdmitx_sys, HDMI_CLK_CTRL, 8, 0); + +static A9_COMP_SEL(hdmitx_prif, HTX_CLK_CTRL, 9, 0x7, a9_hdmi_parents, NULL); +static A9_COMP_DIV(hdmitx_prif, HTX_CLK_CTRL, 0, 7); +static A9_COMP_GATE(hdmitx_prif, HTX_CLK_CTRL, 8, 0); + +static A9_COMP_SEL(hdmitx_200m, HTX_CLK_CTRL, 25, 0x7, a9_hdmi_parents, NULL); +static A9_COMP_DIV(hdmitx_200m, HTX_CLK_CTRL, 16, 7); +static A9_COMP_GATE(hdmitx_200m, HTX_CLK_CTRL, 24, 0); + +static A9_COMP_SEL(hdmitx_aud, HTX_CLK_CTRL1, 9, 0x7, a9_hdmi_parents, NULL); +static A9_COMP_DIV(hdmitx_aud, HTX_CLK_CTRL1, 0, 7); +static A9_COMP_GATE(hdmitx_aud, HTX_CLK_CTRL1, 8, 0); + +static A9_COMP_SEL(hdmirx_5m, HRX_CLK_CTRL, 9, 0x7, a9_hdmi_parents, NULL); +static A9_COMP_DIV(hdmirx_5m, HRX_CLK_CTRL, 0, 7); +static A9_COMP_GATE(hdmirx_5m, HRX_CLK_CTRL, 8, 0); + +static A9_COMP_SEL(hdmirx_2m, HRX_CLK_CTRL, 25, 0x7, a9_hdmi_parents, NULL); +static A9_COMP_DIV(hdmirx_2m, HRX_CLK_CTRL, 16, 7); +static A9_COMP_GATE(hdmirx_2m, HRX_CLK_CTRL, 24, 0); + +static A9_COMP_SEL(hdmirx_cfg, HRX_CLK_CTRL1, 9, 0x7, a9_hdmi_parents, NULL); +static A9_COMP_DIV(hdmirx_cfg, HRX_CLK_CTRL1, 0, 7); +static A9_COMP_GATE(hdmirx_cfg, HRX_CLK_CTRL1, 8, 0); + +static A9_COMP_SEL(hdmirx_hdcp2x, HRX_CLK_CTRL1, 25, 0x7, a9_hdmi_parents, NULL); +static A9_COMP_DIV(hdmirx_hdcp2x, HRX_CLK_CTRL1, 16, 7); +static A9_COMP_GATE(hdmirx_hdcp2x, HRX_CLK_CTRL1, 24, 0); + +static A9_COMP_SEL(hdmirx_acr_ref, HRX_CLK_CTRL2, 25, 0x7, a9_hdmi_parents, NULL); +static A9_COMP_DIV(hdmirx_acr_ref, HRX_CLK_CTRL2, 16, 7); +static A9_COMP_GATE(hdmirx_acr_ref, HRX_CLK_CTRL2, 24, 0); + +static A9_COMP_SEL(hdmirx_meter, HRX_CLK_CTRL3, 9, 0x7, a9_hdmi_parents, NULL); +static A9_COMP_DIV(hdmirx_meter, HRX_CLK_CTRL3, 0, 7); +static A9_COMP_GATE(hdmirx_meter, HRX_CLK_CTRL3, 8, 0); + +static struct clk_regmap a9_vid_pll_vclk = { + .data = &(struct clk_regmap_mux_data){ + .offset = HDMI_CLK_CTRL, + .mask = 0x1, + .shift = 15, + }, + .hw.init = &(struct clk_init_data){ + .name = "vid_pll_vclk", + .ops = &clk_regmap_mux_ops, + .parent_data = (const struct clk_parent_data []) { + { .hw = &a9_vid_pll.hw }, + { .fw_name = "hdmipix", } + }, + .num_parents = 2, + }, +}; + +static const struct clk_parent_data a9_vclk_parents[] = { + { .hw = &a9_vid_pll_vclk.hw }, + { .fw_name = "pix0", }, + { .fw_name = "vid1", }, + { .fw_name = "pix1", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv5", }, + { .fw_name = "vid2", } +}; + +static struct clk_regmap a9_vclk0_sel = { + .data = &(struct clk_regmap_mux_data){ + .offset = VID_CLK_CTRL, + .mask = 0x7, + .shift = 16, + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk0_sel", + .ops = &clk_regmap_mux_ops, + .parent_data = a9_vclk_parents, + .num_parents = ARRAY_SIZE(a9_vclk_parents), + }, +}; + +static struct clk_regmap a9_vclk0_in = { + .data = &(struct clk_regmap_gate_data){ + .offset = VID_CLK_DIV, + .bit_idx = 16, + }, + .hw.init = &(struct clk_init_data) { + .name = "vclk0_in", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { &a9_vclk0_sel.hw }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_vclk0_div = { + .data = &(struct clk_regmap_div_data){ + .offset = VID_CLK_DIV, + .shift = 0, + .width = 8, + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk0_div", + .ops = &clk_regmap_divider_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_vclk0_in.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_vclk0 = { + .data = &(struct clk_regmap_gate_data){ + .offset = VID_CLK_CTRL, + .bit_idx = 19, + }, + .hw.init = &(struct clk_init_data) { + .name = "vclk0", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { &a9_vclk0_div.hw }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +#define A9_VCLK_GATE(_name, _reg, _bit, _parent) \ +struct clk_regmap a9_##_name##_en = { \ + .data = &(struct clk_regmap_gate_data) { \ + .offset = (_reg), \ + .bit_idx = (_bit), \ + }, \ + .hw.init = &(struct clk_init_data) { \ + .name = #_name "_en", \ + .ops = &clk_regmap_gate_ops, \ + .parent_hws = (const struct clk_hw *[]) { \ + &(_parent).hw \ + }, \ + .num_parents = 1, \ + .flags = CLK_SET_RATE_PARENT, \ + }, \ +} + +#define A9_VCLK_DIV(_name, _div) \ +struct clk_fixed_factor a9_##_name = { \ + .mult = 1, \ + .div = (_div), \ + .hw.init = &(struct clk_init_data) { \ + .name = #_name, \ + .ops = &clk_fixed_factor_ops, \ + .parent_hws = (const struct clk_hw *[]) { \ + &a9_##_name##_en.hw \ + }, \ + .num_parents = 1, \ + .flags = CLK_SET_RATE_PARENT, \ + }, \ +} + +static A9_VCLK_GATE(vclk0_div1, VID_CLK_CTRL, 0, a9_vclk0); +static A9_VCLK_GATE(vclk0_div2, VID_CLK_CTRL, 1, a9_vclk0); +static A9_VCLK_DIV(vclk0_div2, 2); +static A9_VCLK_GATE(vclk0_div4, VID_CLK_CTRL, 2, a9_vclk0); +static A9_VCLK_DIV(vclk0_div4, 4); +static A9_VCLK_GATE(vclk0_div6, VID_CLK_CTRL, 3, a9_vclk0); +static A9_VCLK_DIV(vclk0_div6, 6); +static A9_VCLK_GATE(vclk0_div12, VID_CLK_CTRL, 4, a9_vclk0); +static A9_VCLK_DIV(vclk0_div12, 12); + +static struct clk_regmap a9_vclk1_sel = { + .data = &(struct clk_regmap_mux_data){ + .offset = VIID_CLK_CTRL, + .mask = 0x7, + .shift = 16, + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk1_sel", + .ops = &clk_regmap_mux_ops, + .parent_data = a9_vclk_parents, + .num_parents = ARRAY_SIZE(a9_vclk_parents), + }, +}; + +static struct clk_regmap a9_vclk1_in = { + .data = &(struct clk_regmap_gate_data){ + .offset = VIID_CLK_DIV, + .bit_idx = 16, + }, + .hw.init = &(struct clk_init_data) { + .name = "vclk1_in", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { &a9_vclk1_sel.hw }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_vclk1_div = { + .data = &(struct clk_regmap_div_data){ + .offset = VIID_CLK_DIV, + .shift = 0, + .width = 8, + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk1_div", + .ops = &clk_regmap_divider_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_vclk1_in.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_vclk1 = { + .data = &(struct clk_regmap_gate_data){ + .offset = VIID_CLK_CTRL, + .bit_idx = 19, + }, + .hw.init = &(struct clk_init_data) { + .name = "vclk1", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { &a9_vclk1_div.hw }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static A9_VCLK_GATE(vclk1_div1, VIID_CLK_CTRL, 0, a9_vclk1); +static A9_VCLK_GATE(vclk1_div2, VIID_CLK_CTRL, 1, a9_vclk1); +static A9_VCLK_DIV(vclk1_div2, 2); +static A9_VCLK_GATE(vclk1_div4, VIID_CLK_CTRL, 2, a9_vclk1); +static A9_VCLK_DIV(vclk1_div4, 4); +static A9_VCLK_GATE(vclk1_div6, VIID_CLK_CTRL, 3, a9_vclk1); +static A9_VCLK_DIV(vclk1_div6, 6); +static A9_VCLK_GATE(vclk1_div12, VIID_CLK_CTRL, 4, a9_vclk1); +static A9_VCLK_DIV(vclk1_div12, 12); + +/* Channel 5, 6 and 7 are unconnected */ +static u32 a9_vid_parents_val_table[] = { 0, 1, 2, 3, 4, 8, 9, 10, 11, 12 }; +static const struct clk_hw *a9_vid_parents[] = { + &a9_vclk0_div1_en.hw, + &a9_vclk0_div2.hw, + &a9_vclk0_div4.hw, + &a9_vclk0_div6.hw, + &a9_vclk0_div12.hw, + &a9_vclk1_div1_en.hw, + &a9_vclk1_div2.hw, + &a9_vclk1_div4.hw, + &a9_vclk1_div6.hw, + &a9_vclk1_div12.hw +}; + +static struct clk_regmap a9_encoder0_sel = { + .data = &(struct clk_regmap_mux_data){ + .offset = VIID_CLK_DIV, + .mask = 0xf, + .shift = 12, + .table = a9_vid_parents_val_table, + }, + .hw.init = &(struct clk_init_data){ + .name = "encoder0_sel", + .ops = &clk_regmap_mux_ops, + .parent_hws = a9_vid_parents, + .num_parents = ARRAY_SIZE(a9_vid_parents), + }, +}; + +static struct clk_regmap a9_encoder0 = { + .data = &(struct clk_regmap_gate_data){ + .offset = VID_CLK_CTRL2, + .bit_idx = 10, + }, + .hw.init = &(struct clk_init_data) { + .name = "encoder0", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_encoder0_sel.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_encoder1_sel = { + .data = &(struct clk_regmap_mux_data){ + .offset = VIID_CLK_DIV, + .mask = 0xf, + .shift = 8, + .table = a9_vid_parents_val_table, + }, + .hw.init = &(struct clk_init_data){ + .name = "encoder1_sel", + .ops = &clk_regmap_mux_ops, + .parent_hws = a9_vid_parents, + .num_parents = ARRAY_SIZE(a9_vid_parents), + }, +}; + +static struct clk_regmap a9_encoder1 = { + .data = &(struct clk_regmap_gate_data){ + .offset = VID_CLK_CTRL2, + .bit_idx = 11, + }, + .hw.init = &(struct clk_init_data) { + .name = "encoder1", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_encoder1_sel.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static const struct clk_parent_data a9_vid_lock_parents[] = { + { .fw_name = "xtal", }, + { .hw = &a9_encoder0.hw }, + { .hw = &a9_encoder1.hw } +}; + +static A9_COMP_SEL(vid_lock, VID_LOCK_CLK_CTRL, 9, 0x7, a9_vid_lock_parents, NULL); +static A9_COMP_DIV(vid_lock, VID_LOCK_CLK_CTRL, 0, 7); +static A9_COMP_GATE(vid_lock, VID_LOCK_CLK_CTRL, 8, 0); + +static const struct clk_parent_data a9_vdin_meas_parents[] = { + { .fw_name = "xtal", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv5", } +}; + +static A9_COMP_SEL(vdin_meas, VDIN_MEAS_CLK_CTRL, 9, 0x7, a9_vdin_meas_parents, NULL); +static A9_COMP_DIV(vdin_meas, VDIN_MEAS_CLK_CTRL, 0, 7); +static A9_COMP_GATE(vdin_meas, VDIN_MEAS_CLK_CTRL, 8, 0); + +static struct clk_regmap a9_vdac_sel = { + .data = &(struct clk_regmap_mux_data){ + .offset = VIID_CLK_DIV, + .mask = 0xf, + .shift = 28, + .table = a9_vid_parents_val_table, + }, + .hw.init = &(struct clk_init_data){ + .name = "vdac_sel", + .ops = &clk_regmap_mux_ops, + .parent_hws = a9_vid_parents, + .num_parents = ARRAY_SIZE(a9_vid_parents), + }, +}; + +static struct clk_regmap a9_vdac = { + .data = &(struct clk_regmap_gate_data){ + .offset = VID_CLK_CTRL2, + .bit_idx = 4, + }, + .hw.init = &(struct clk_init_data) { + .name = "vdac", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_vdac_sel.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_hdmitx0_pixel_sel = { + .data = &(struct clk_regmap_mux_data){ + .offset = HDMI_CLK_CTRL, + .mask = 0xf, + .shift = 16, + .table = a9_vid_parents_val_table, + }, + .hw.init = &(struct clk_init_data){ + .name = "hdmitx0_pixel_sel", + .ops = &clk_regmap_mux_ops, + .parent_hws = a9_vid_parents, + .num_parents = ARRAY_SIZE(a9_vid_parents), + }, +}; + +static struct clk_regmap a9_hdmitx0_pixel = { + .data = &(struct clk_regmap_gate_data){ + .offset = VID_CLK_CTRL2, + .bit_idx = 5, + }, + .hw.init = &(struct clk_init_data) { + .name = "hdmitx0_pixel", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_hdmitx0_pixel_sel.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_hdmitx0_fe_sel = { + .data = &(struct clk_regmap_mux_data){ + .offset = HDMI_CLK_CTRL, + .mask = 0xf, + .shift = 20, + .table = a9_vid_parents_val_table, + }, + .hw.init = &(struct clk_init_data){ + .name = "hdmitx0_fe_sel", + .ops = &clk_regmap_mux_ops, + .parent_hws = a9_vid_parents, + .num_parents = ARRAY_SIZE(a9_vid_parents), + }, +}; + +static struct clk_regmap a9_hdmitx0_fe = { + .data = &(struct clk_regmap_gate_data){ + .offset = VID_CLK_CTRL2, + .bit_idx = 9, + }, + .hw.init = &(struct clk_init_data) { + .name = "hdmitx0_fe", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_hdmitx0_fe_sel.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_hdmitx1_pixel_sel = { + .data = &(struct clk_regmap_mux_data){ + .offset = HDMI_CLK_CTRL, + .mask = 0xf, + .shift = 24, + .table = a9_vid_parents_val_table, + }, + .hw.init = &(struct clk_init_data){ + .name = "hdmitx1_pixel_sel", + .ops = &clk_regmap_mux_ops, + .parent_hws = a9_vid_parents, + .num_parents = ARRAY_SIZE(a9_vid_parents), + }, +}; + +static struct clk_regmap a9_hdmitx1_pixel = { + .data = &(struct clk_regmap_gate_data){ + .offset = VID_CLK_CTRL2, + .bit_idx = 12, + }, + .hw.init = &(struct clk_init_data) { + .name = "hdmitx1_pixel", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_hdmitx1_pixel_sel.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_hdmitx1_fe_sel = { + .data = &(struct clk_regmap_mux_data){ + .offset = HDMI_CLK_CTRL, + .mask = 0xf, + .shift = 28, + .table = a9_vid_parents_val_table, + }, + .hw.init = &(struct clk_init_data){ + .name = "hdmitx1_fe_sel", + .ops = &clk_regmap_mux_ops, + .parent_hws = a9_vid_parents, + .num_parents = ARRAY_SIZE(a9_vid_parents), + }, +}; + +static struct clk_regmap a9_hdmitx1_fe = { + .data = &(struct clk_regmap_gate_data){ + .offset = VID_CLK_CTRL2, + .bit_idx = 13, + }, + .hw.init = &(struct clk_init_data) { + .name = "hdmitx1_fe", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_hdmitx1_fe_sel.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static const struct clk_parent_data a9_csi_phy_parents[] = { + { .fw_name = "fdiv2p5", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv5", }, + { .fw_name = "gp0", }, + { .fw_name = "hifi0", }, + { .fw_name = "fdiv2", }, + { .fw_name = "xtal", } +}; + +static A9_COMP_SEL(csi_phy, MIPI_CSI_PHY_CLK_CTRL, 9, 0x7, a9_csi_phy_parents, NULL); +static A9_COMP_DIV(csi_phy, MIPI_CSI_PHY_CLK_CTRL, 0, 7); +static A9_COMP_GATE(csi_phy, MIPI_CSI_PHY_CLK_CTRL, 8, 0); + +static const struct clk_parent_data a9_dsi_meas_parents[] = { + { .fw_name = "xtal", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv5", }, + { .hw = &a9_vid_pll.hw }, + { .fw_name = "gp0", }, + { .fw_name = "vid1", }, + { .fw_name = "vid2", } +}; + +static A9_COMP_SEL(dsi0_meas, DSI_MEAS_CLK_CTRL, 9, 0x7, a9_dsi_meas_parents, NULL); +static A9_COMP_DIV(dsi0_meas, DSI_MEAS_CLK_CTRL, 0, 7); +static A9_COMP_GATE(dsi0_meas, DSI_MEAS_CLK_CTRL, 8, 0); + +static A9_COMP_SEL(dsi1_meas, DSI_MEAS_CLK_CTRL, 25, 0x7, a9_dsi_meas_parents, NULL); +static A9_COMP_DIV(dsi1_meas, DSI_MEAS_CLK_CTRL, 16, 7); +static A9_COMP_GATE(dsi1_meas, DSI_MEAS_CLK_CTRL, 24, 0); + +static struct clk_hw *a9_peripherals_hw_clks[] = { + [CLKID_SYS_AM_AXI] = &a9_sys_am_axi.hw, + [CLKID_SYS_DOS] = &a9_sys_dos.hw, + [CLKID_SYS_MIPI_DSI0] = &a9_sys_mipi_dsi0.hw, + [CLKID_SYS_ETH_PHY] = &a9_sys_eth_phy.hw, + [CLKID_SYS_AMFC] = &a9_sys_amfc.hw, + [CLKID_SYS_MALI] = &a9_sys_mali.hw, + [CLKID_SYS_NNA] = &a9_sys_nna.hw, + [CLKID_SYS_ETH_AXI] = &a9_sys_eth_axi.hw, + [CLKID_SYS_DP_APB] = &a9_sys_dp_apb.hw, + [CLKID_SYS_EDPTX_APB] = &a9_sys_edptx_apb.hw, + [CLKID_SYS_U3HSG] = &a9_sys_u3hsg.hw, + [CLKID_SYS_AUCPU] = &a9_sys_aucpu.hw, + [CLKID_SYS_GLB] = &a9_sys_glb.hw, + [CLKID_SYS_COMBO_DPHY_APB] = &a9_sys_combo_dphy_apb.hw, + [CLKID_SYS_HDMIRX_APB] = &a9_sys_hdmirx_apb.hw, + [CLKID_SYS_HDMIRX_PCLK] = &a9_sys_hdmirx_pclk.hw, + [CLKID_SYS_MIPI_DSI0_PHY] = &a9_sys_mipi_dsi0_phy.hw, + [CLKID_SYS_CAN0] = &a9_sys_can0.hw, + [CLKID_SYS_CAN1] = &a9_sys_can1.hw, + [CLKID_SYS_SD_EMMC_A] = &a9_sys_sd_emmc_a.hw, + [CLKID_SYS_SD_EMMC_B] = &a9_sys_sd_emmc_b.hw, + [CLKID_SYS_SD_EMMC_C] = &a9_sys_sd_emmc_c.hw, + [CLKID_SYS_SC] = &a9_sys_sc.hw, + [CLKID_SYS_ACODEC] = &a9_sys_acodec.hw, + [CLKID_SYS_MIPI_ISP] = &a9_sys_mipi_isp.hw, + [CLKID_SYS_MSR] = &a9_sys_msr.hw, + [CLKID_SYS_AUDIO] = &a9_sys_audio.hw, + [CLKID_SYS_MIPI_DSI1] = &a9_sys_mipi_dsi1.hw, + [CLKID_SYS_MIPI_DSI1_PHY] = &a9_sys_mipi_dsi1_phy.hw, + [CLKID_SYS_ETH] = &a9_sys_eth.hw, + [CLKID_SYS_ETH_1G_MAC] = &a9_sys_eth_1g_mac.hw, + [CLKID_SYS_UART_A] = &a9_sys_uart_a.hw, + [CLKID_SYS_UART_F] = &a9_sys_uart_f.hw, + [CLKID_SYS_TS_A55] = &a9_sys_ts_a55.hw, + [CLKID_SYS_ETH_1G_AXI] = &a9_sys_eth_1g_axi.hw, + [CLKID_SYS_TS_DOS] = &a9_sys_ts_dos.hw, + [CLKID_SYS_U3DRD_B] = &a9_sys_u3drd_b.hw, + [CLKID_SYS_TS_CORE] = &a9_sys_ts_core.hw, + [CLKID_SYS_TS_PLL] = &a9_sys_ts_pll.hw, + [CLKID_SYS_CSI_DIG_CLKIN] = &a9_sys_csi_dig_clkin.hw, + [CLKID_SYS_CVE] = &a9_sys_cve.hw, + [CLKID_SYS_GE2D] = &a9_sys_ge2d.hw, + [CLKID_SYS_SPISG] = &a9_sys_spisg.hw, + [CLKID_SYS_U2H] = &a9_sys_u2h.hw, + [CLKID_SYS_PCIE_MAC_A] = &a9_sys_pcie_mac_a.hw, + [CLKID_SYS_U3DRD_A] = &a9_sys_u3drd_a.hw, + [CLKID_SYS_U2DRD] = &a9_sys_u2drd.hw, + [CLKID_SYS_PCIE_PHY] = &a9_sys_pcie_phy.hw, + [CLKID_SYS_PCIE_MAC_B] = &a9_sys_pcie_mac_b.hw, + [CLKID_SYS_PERIPH] = &a9_sys_periph.hw, + [CLKID_SYS_PIO] = &a9_sys_pio.hw, + [CLKID_SYS_I3C] = &a9_sys_i3c.hw, + [CLKID_SYS_I2C_M_E] = &a9_sys_i2c_m_e.hw, + [CLKID_SYS_I2C_M_F] = &a9_sys_i2c_m_f.hw, + [CLKID_SYS_HDMITX_APB] = &a9_sys_hdmitx_apb.hw, + [CLKID_SYS_I2C_M_I] = &a9_sys_i2c_m_i.hw, + [CLKID_SYS_I2C_M_G] = &a9_sys_i2c_m_g.hw, + [CLKID_SYS_I2C_M_H] = &a9_sys_i2c_m_h.hw, + [CLKID_SYS_HDMI20_AES] = &a9_sys_hdmi20_aes.hw, + [CLKID_SYS_CSI2_HOST] = &a9_sys_csi2_host.hw, + [CLKID_SYS_CSI2_ADAPT] = &a9_sys_csi2_adapt.hw, + [CLKID_SYS_DSPA] = &a9_sys_dspa.hw, + [CLKID_SYS_PP_DMA] = &a9_sys_pp_dma.hw, + [CLKID_SYS_PP_WRAPPER] = &a9_sys_pp_wrapper.hw, + [CLKID_SYS_VPU_INTR] = &a9_sys_vpu_intr.hw, + [CLKID_SYS_CSI2_PHY] = &a9_sys_csi2_phy.hw, + [CLKID_SYS_SARADC] = &a9_sys_saradc.hw, + [CLKID_SYS_PWM_J] = &a9_sys_pwm_j.hw, + [CLKID_SYS_PWM_I] = &a9_sys_pwm_i.hw, + [CLKID_SYS_PWM_H] = &a9_sys_pwm_h.hw, + [CLKID_SYS_PWM_N] = &a9_sys_pwm_n.hw, + [CLKID_SYS_PWM_M] = &a9_sys_pwm_m.hw, + [CLKID_SYS_PWM_L] = &a9_sys_pwm_l.hw, + [CLKID_SYS_PWM_K] = &a9_sys_pwm_k.hw, + [CLKID_SD_EMMC_A_SEL] = &a9_sd_emmc_a_sel.hw, + [CLKID_SD_EMMC_A_DIV] = &a9_sd_emmc_a_div.hw, + [CLKID_SD_EMMC_A] = &a9_sd_emmc_a.hw, + [CLKID_SD_EMMC_B_SEL] = &a9_sd_emmc_b_sel.hw, + [CLKID_SD_EMMC_B_DIV] = &a9_sd_emmc_b_div.hw, + [CLKID_SD_EMMC_B] = &a9_sd_emmc_b.hw, + [CLKID_SD_EMMC_C_SEL] = &a9_sd_emmc_c_sel.hw, + [CLKID_SD_EMMC_C_DIV] = &a9_sd_emmc_c_div.hw, + [CLKID_SD_EMMC_C] = &a9_sd_emmc_c.hw, + [CLKID_PWM_H_SEL] = &a9_pwm_h_sel.hw, + [CLKID_PWM_H_DIV] = &a9_pwm_h_div.hw, + [CLKID_PWM_H] = &a9_pwm_h.hw, + [CLKID_PWM_I_SEL] = &a9_pwm_i_sel.hw, + [CLKID_PWM_I_DIV] = &a9_pwm_i_div.hw, + [CLKID_PWM_I] = &a9_pwm_i.hw, + [CLKID_PWM_J_SEL] = &a9_pwm_j_sel.hw, + [CLKID_PWM_J_DIV] = &a9_pwm_j_div.hw, + [CLKID_PWM_J] = &a9_pwm_j.hw, + [CLKID_PWM_K_SEL] = &a9_pwm_k_sel.hw, + [CLKID_PWM_K_DIV] = &a9_pwm_k_div.hw, + [CLKID_PWM_K] = &a9_pwm_k.hw, + [CLKID_PWM_L_SEL] = &a9_pwm_l_sel.hw, + [CLKID_PWM_L_DIV] = &a9_pwm_l_div.hw, + [CLKID_PWM_L] = &a9_pwm_l.hw, + [CLKID_PWM_M_SEL] = &a9_pwm_m_sel.hw, + [CLKID_PWM_M_DIV] = &a9_pwm_m_div.hw, + [CLKID_PWM_M] = &a9_pwm_m.hw, + [CLKID_PWM_N_SEL] = &a9_pwm_n_sel.hw, + [CLKID_PWM_N_DIV] = &a9_pwm_n_div.hw, + [CLKID_PWM_N] = &a9_pwm_n.hw, + [CLKID_SPISG0_SEL] = &a9_spisg0_sel.hw, + [CLKID_SPISG0_DIV] = &a9_spisg0_div.hw, + [CLKID_SPISG0] = &a9_spisg0.hw, + [CLKID_SPISG1_SEL] = &a9_spisg1_sel.hw, + [CLKID_SPISG1_DIV] = &a9_spisg1_div.hw, + [CLKID_SPISG1] = &a9_spisg1.hw, + [CLKID_SPISG2_SEL] = &a9_spisg2_sel.hw, + [CLKID_SPISG2_DIV] = &a9_spisg2_div.hw, + [CLKID_SPISG2] = &a9_spisg2.hw, + [CLKID_SARADC_SEL] = &a9_saradc_sel.hw, + [CLKID_SARADC_DIV] = &a9_saradc_div.hw, + [CLKID_SARADC] = &a9_saradc.hw, + [CLKID_AMFC_SEL] = &a9_amfc_sel.hw, + [CLKID_AMFC_DIV] = &a9_amfc_div.hw, + [CLKID_AMFC] = &a9_amfc.hw, + [CLKID_NNA_SEL] = &a9_nna_sel.hw, + [CLKID_NNA_DIV] = &a9_nna_div.hw, + [CLKID_NNA] = &a9_nna.hw, + [CLKID_USB_250M_SEL] = &a9_usb_250m_sel.hw, + [CLKID_USB_250M_DIV] = &a9_usb_250m_div.hw, + [CLKID_USB_250M] = &a9_usb_250m.hw, + [CLKID_USB_48M_PRE_SEL] = &a9_usb_48m_pre_sel.hw, + [CLKID_USB_48M_PRE_DIV] = &a9_usb_48m_pre_div.hw, + [CLKID_USB_48M_PRE] = &a9_usb_48m_pre.hw, + [CLKID_PCIE0_TL_SEL] = &a9_pcie0_tl_sel.hw, + [CLKID_PCIE0_TL_DIV] = &a9_pcie0_tl_div.hw, + [CLKID_PCIE0_TL] = &a9_pcie0_tl.hw, + [CLKID_PCIE1_TL_SEL] = &a9_pcie1_tl_sel.hw, + [CLKID_PCIE1_TL_DIV] = &a9_pcie1_tl_div.hw, + [CLKID_PCIE1_TL] = &a9_pcie1_tl.hw, + [CLKID_CMPR_SEL] = &a9_cmpr_sel.hw, + [CLKID_CMPR_DIV] = &a9_cmpr_div.hw, + [CLKID_CMPR] = &a9_cmpr.hw, + [CLKID_DEWARPA_SEL] = &a9_dewarpa_sel.hw, + [CLKID_DEWARPA_DIV] = &a9_dewarpa_div.hw, + [CLKID_DEWARPA] = &a9_dewarpa.hw, + [CLKID_SC_PRE_SEL] = &a9_sc_pre_sel.hw, + [CLKID_SC_PRE_DIV] = &a9_sc_pre_div.hw, + [CLKID_SC_PRE] = &a9_sc_pre.hw, + [CLKID_SC] = &a9_sc.hw, + [CLKID_DPTX_APB2_SEL] = &a9_dptx_apb2_sel.hw, + [CLKID_DPTX_APB2_DIV] = &a9_dptx_apb2_div.hw, + [CLKID_DPTX_APB2] = &a9_dptx_apb2.hw, + [CLKID_DPTX_AUD_SEL] = &a9_dptx_aud_sel.hw, + [CLKID_DPTX_AUD_DIV] = &a9_dptx_aud_div.hw, + [CLKID_DPTX_AUD] = &a9_dptx_aud.hw, + [CLKID_ISP_SEL] = &a9_isp_sel.hw, + [CLKID_ISP_DIV] = &a9_isp_div.hw, + [CLKID_ISP] = &a9_isp.hw, + [CLKID_CVE_SEL] = &a9_cve_sel.hw, + [CLKID_CVE_DIV] = &a9_cve_div.hw, + [CLKID_CVE] = &a9_cve.hw, + [CLKID_VGE_SEL] = &a9_vge_sel.hw, + [CLKID_VGE_DIV] = &a9_vge_div.hw, + [CLKID_VGE] = &a9_vge.hw, + [CLKID_PP_SEL] = &a9_pp_sel.hw, + [CLKID_PP_DIV] = &a9_pp_div.hw, + [CLKID_PP] = &a9_pp.hw, + [CLKID_GLB_SEL] = &a9_glb_sel.hw, + [CLKID_GLB_DIV] = &a9_glb_div.hw, + [CLKID_GLB] = &a9_glb.hw, + [CLKID_USB_48M_DUALDIV_IN] = &a9_usb_48m_dualdiv_in.hw, + [CLKID_USB_48M_DUALDIV_DIV] = &a9_usb_48m_dualdiv_div.hw, + [CLKID_USB_48M_DUALDIV_SEL] = &a9_usb_48m_dualdiv_sel.hw, + [CLKID_USB_48M_DUALDIV] = &a9_usb_48m_dualdiv.hw, + [CLKID_USB_48M] = &a9_usb_48m.hw, + [CLKID_CAN0_PE_SEL] = &a9_can0_pe_sel.hw, + [CLKID_CAN0_PE_DIV] = &a9_can0_pe_div.hw, + [CLKID_CAN0_PE] = &a9_can0_pe.hw, + [CLKID_CAN1_PE_SEL] = &a9_can1_pe_sel.hw, + [CLKID_CAN1_PE_DIV] = &a9_can1_pe_div.hw, + [CLKID_CAN1_PE] = &a9_can1_pe.hw, + [CLKID_CAN0_FILTER_SEL] = &a9_can0_filter_sel.hw, + [CLKID_CAN0_FILTER_DIV] = &a9_can0_filter_div.hw, + [CLKID_CAN0_FILTER] = &a9_can0_filter.hw, + [CLKID_CAN1_FILTER_SEL] = &a9_can1_filter_sel.hw, + [CLKID_CAN1_FILTER_DIV] = &a9_can1_filter_div.hw, + [CLKID_CAN1_FILTER] = &a9_can1_filter.hw, + [CLKID_I3C_SEL] = &a9_i3c_sel.hw, + [CLKID_I3C_DIV] = &a9_i3c_div.hw, + [CLKID_I3C] = &a9_i3c.hw, + [CLKID_TS_DIV] = &a9_ts_div.hw, + [CLKID_TS] = &a9_ts.hw, + [CLKID_ETH_125M_DIV] = &a9_eth_125m_div.hw, + [CLKID_ETH_125M] = &a9_eth_125m.hw, + [CLKID_ETH_RMII_SEL] = &a9_eth_rmii_sel.hw, + [CLKID_ETH_RMII_DIV] = &a9_eth_rmii_div.hw, + [CLKID_ETH_RMII] = &a9_eth_rmii.hw, + [CLKID_GEN_SEL] = &a9_gen_sel.hw, + [CLKID_GEN_DIV] = &a9_gen_div.hw, + [CLKID_GEN] = &a9_gen.hw, + [CLKID_CLK24M_IN] = &a9_24m_in.hw, + [CLKID_CLK12_24M] = &a9_12_24m.hw, + [CLKID_MALI_0_SEL] = &a9_mali_0_sel.hw, + [CLKID_MALI_0_DIV] = &a9_mali_0_div.hw, + [CLKID_MALI_0] = &a9_mali_0.hw, + [CLKID_MALI_1_SEL] = &a9_mali_1_sel.hw, + [CLKID_MALI_1_DIV] = &a9_mali_1_div.hw, + [CLKID_MALI_1] = &a9_mali_1.hw, + [CLKID_MALI] = &a9_mali.hw, + [CLKID_MALI_STACK_0_SEL] = &a9_mali_stack_0_sel.hw, + [CLKID_MALI_STACK_0_DIV] = &a9_mali_stack_0_div.hw, + [CLKID_MALI_STACK_0] = &a9_mali_stack_0.hw, + [CLKID_MALI_STACK_1_SEL] = &a9_mali_stack_1_sel.hw, + [CLKID_MALI_STACK_1_DIV] = &a9_mali_stack_1_div.hw, + [CLKID_MALI_STACK_1] = &a9_mali_stack_1.hw, + [CLKID_MALI_STACK] = &a9_mali_stack.hw, + [CLKID_DSPA_0_SEL] = &a9_dspa_0_sel.hw, + [CLKID_DSPA_0_DIV] = &a9_dspa_0_div.hw, + [CLKID_DSPA_0] = &a9_dspa_0.hw, + [CLKID_DSPA_1_SEL] = &a9_dspa_1_sel.hw, + [CLKID_DSPA_1_DIV] = &a9_dspa_1_div.hw, + [CLKID_DSPA_1] = &a9_dspa_1.hw, + [CLKID_DSPA] = &a9_dspa.hw, + [CLKID_HEVCF_0_SEL] = &a9_hevcf_0_sel.hw, + [CLKID_HEVCF_0_DIV] = &a9_hevcf_0_div.hw, + [CLKID_HEVCF_0] = &a9_hevcf_0.hw, + [CLKID_HEVCF_1_SEL] = &a9_hevcf_1_sel.hw, + [CLKID_HEVCF_1_DIV] = &a9_hevcf_1_div.hw, + [CLKID_HEVCF_1] = &a9_hevcf_1.hw, + [CLKID_HEVCF] = &a9_hevcf.hw, + [CLKID_HCODEC_0_SEL] = &a9_hcodec_0_sel.hw, + [CLKID_HCODEC_0_DIV] = &a9_hcodec_0_div.hw, + [CLKID_HCODEC_0] = &a9_hcodec_0.hw, + [CLKID_HCODEC_1_SEL] = &a9_hcodec_1_sel.hw, + [CLKID_HCODEC_1_DIV] = &a9_hcodec_1_div.hw, + [CLKID_HCODEC_1] = &a9_hcodec_1.hw, + [CLKID_HCODEC] = &a9_hcodec.hw, + [CLKID_VPU_0_SEL] = &a9_vpu_0_sel.hw, + [CLKID_VPU_0_DIV] = &a9_vpu_0_div.hw, + [CLKID_VPU_0] = &a9_vpu_0.hw, + [CLKID_VPU_1_SEL] = &a9_vpu_1_sel.hw, + [CLKID_VPU_1_DIV] = &a9_vpu_1_div.hw, + [CLKID_VPU_1] = &a9_vpu_1.hw, + [CLKID_VPU] = &a9_vpu.hw, + [CLKID_VAPB_0_SEL] = &a9_vapb_0_sel.hw, + [CLKID_VAPB_0_DIV] = &a9_vapb_0_div.hw, + [CLKID_VAPB_0] = &a9_vapb_0.hw, + [CLKID_VAPB_1_SEL] = &a9_vapb_1_sel.hw, + [CLKID_VAPB_1_DIV] = &a9_vapb_1_div.hw, + [CLKID_VAPB_1] = &a9_vapb_1.hw, + [CLKID_VAPB] = &a9_vapb.hw, + [CLKID_GE2D] = &a9_ge2d.hw, + [CLKID_VPU_CLKB_TMP_SEL] = &a9_vpu_clkb_tmp_sel.hw, + [CLKID_VPU_CLKB_TMP_DIV] = &a9_vpu_clkb_tmp_div.hw, + [CLKID_VPU_CLKB_TMP] = &a9_vpu_clkb_tmp.hw, + [CLKID_VPU_CLKB_DIV] = &a9_vpu_clkb_div.hw, + [CLKID_VPU_CLKB] = &a9_vpu_clkb.hw, + [CLKID_HDMITX_SYS_SEL] = &a9_hdmitx_sys_sel.hw, + [CLKID_HDMITX_SYS_DIV] = &a9_hdmitx_sys_div.hw, + [CLKID_HDMITX_SYS] = &a9_hdmitx_sys.hw, + [CLKID_HDMITX_PRIF_SEL] = &a9_hdmitx_prif_sel.hw, + [CLKID_HDMITX_PRIF_DIV] = &a9_hdmitx_prif_div.hw, + [CLKID_HDMITX_PRIF] = &a9_hdmitx_prif.hw, + [CLKID_HDMITX_200M_SEL] = &a9_hdmitx_200m_sel.hw, + [CLKID_HDMITX_200M_DIV] = &a9_hdmitx_200m_div.hw, + [CLKID_HDMITX_200M] = &a9_hdmitx_200m.hw, + [CLKID_HDMITX_AUD_SEL] = &a9_hdmitx_aud_sel.hw, + [CLKID_HDMITX_AUD_DIV] = &a9_hdmitx_aud_div.hw, + [CLKID_HDMITX_AUD] = &a9_hdmitx_aud.hw, + [CLKID_HDMIRX_5M_SEL] = &a9_hdmirx_5m_sel.hw, + [CLKID_HDMIRX_5M_DIV] = &a9_hdmirx_5m_div.hw, + [CLKID_HDMIRX_5M] = &a9_hdmirx_5m.hw, + [CLKID_HDMIRX_2M_SEL] = &a9_hdmirx_2m_sel.hw, + [CLKID_HDMIRX_2M_DIV] = &a9_hdmirx_2m_div.hw, + [CLKID_HDMIRX_2M] = &a9_hdmirx_2m.hw, + [CLKID_HDMIRX_CFG_SEL] = &a9_hdmirx_cfg_sel.hw, + [CLKID_HDMIRX_CFG_DIV] = &a9_hdmirx_cfg_div.hw, + [CLKID_HDMIRX_CFG] = &a9_hdmirx_cfg.hw, + [CLKID_HDMIRX_HDCP2X_SEL] = &a9_hdmirx_hdcp2x_sel.hw, + [CLKID_HDMIRX_HDCP2X_DIV] = &a9_hdmirx_hdcp2x_div.hw, + [CLKID_HDMIRX_HDCP2X] = &a9_hdmirx_hdcp2x.hw, + [CLKID_HDMIRX_ACR_REF_SEL] = &a9_hdmirx_acr_ref_sel.hw, + [CLKID_HDMIRX_ACR_REF_DIV] = &a9_hdmirx_acr_ref_div.hw, + [CLKID_HDMIRX_ACR_REF] = &a9_hdmirx_acr_ref.hw, + [CLKID_HDMIRX_METER_SEL] = &a9_hdmirx_meter_sel.hw, + [CLKID_HDMIRX_METER_DIV] = &a9_hdmirx_meter_div.hw, + [CLKID_HDMIRX_METER] = &a9_hdmirx_meter.hw, + [CLKID_VID_LOCK_SEL] = &a9_vid_lock_sel.hw, + [CLKID_VID_LOCK_DIV] = &a9_vid_lock_div.hw, + [CLKID_VID_LOCK] = &a9_vid_lock.hw, + [CLKID_VDIN_MEAS_SEL] = &a9_vdin_meas_sel.hw, + [CLKID_VDIN_MEAS_DIV] = &a9_vdin_meas_div.hw, + [CLKID_VDIN_MEAS] = &a9_vdin_meas.hw, + [CLKID_VID_PLL_DIV] = &a9_vid_pll_div.hw, + [CLKID_VID_PLL_SEL] = &a9_vid_pll_sel.hw, + [CLKID_VID_PLL] = &a9_vid_pll.hw, + [CLKID_VID_PLL_VCLK] = &a9_vid_pll_vclk.hw, + [CLKID_VCLK0_SEL] = &a9_vclk0_sel.hw, + [CLKID_VCLK0_IN] = &a9_vclk0_in.hw, + [CLKID_VCLK0_DIV] = &a9_vclk0_div.hw, + [CLKID_VCLK0] = &a9_vclk0.hw, + [CLKID_VCLK0_DIV1_EN] = &a9_vclk0_div1_en.hw, + [CLKID_VCLK0_DIV2_EN] = &a9_vclk0_div2_en.hw, + [CLKID_VCLK0_DIV2] = &a9_vclk0_div2.hw, + [CLKID_VCLK0_DIV4_EN] = &a9_vclk0_div4_en.hw, + [CLKID_VCLK0_DIV4] = &a9_vclk0_div4.hw, + [CLKID_VCLK0_DIV6_EN] = &a9_vclk0_div6_en.hw, + [CLKID_VCLK0_DIV6] = &a9_vclk0_div6.hw, + [CLKID_VCLK0_DIV12_EN] = &a9_vclk0_div12_en.hw, + [CLKID_VCLK0_DIV12] = &a9_vclk0_div12.hw, + [CLKID_VCLK1_SEL] = &a9_vclk1_sel.hw, + [CLKID_VCLK1_IN] = &a9_vclk1_in.hw, + [CLKID_VCLK1_DIV] = &a9_vclk1_div.hw, + [CLKID_VCLK1] = &a9_vclk1.hw, + [CLKID_VCLK1_DIV1_EN] = &a9_vclk1_div1_en.hw, + [CLKID_VCLK1_DIV2_EN] = &a9_vclk1_div2_en.hw, + [CLKID_VCLK1_DIV2] = &a9_vclk1_div2.hw, + [CLKID_VCLK1_DIV4_EN] = &a9_vclk1_div4_en.hw, + [CLKID_VCLK1_DIV4] = &a9_vclk1_div4.hw, + [CLKID_VCLK1_DIV6_EN] = &a9_vclk1_div6_en.hw, + [CLKID_VCLK1_DIV6] = &a9_vclk1_div6.hw, + [CLKID_VCLK1_DIV12_EN] = &a9_vclk1_div12_en.hw, + [CLKID_VCLK1_DIV12] = &a9_vclk1_div12.hw, + [CLKID_VDAC_SEL] = &a9_vdac_sel.hw, + [CLKID_VDAC] = &a9_vdac.hw, + [CLKID_ENCODER0_SEL] = &a9_encoder0_sel.hw, + [CLKID_ENCODER0] = &a9_encoder0.hw, + [CLKID_ENCODER1_SEL] = &a9_encoder1_sel.hw, + [CLKID_ENCODER1] = &a9_encoder1.hw, + [CLKID_HDMITX0_PIXEL_SEL] = &a9_hdmitx0_pixel_sel.hw, + [CLKID_HDMITX0_PIXEL] = &a9_hdmitx0_pixel.hw, + [CLKID_HDMITX0_FE_SEL] = &a9_hdmitx0_fe_sel.hw, + [CLKID_HDMITX0_FE] = &a9_hdmitx0_fe.hw, + [CLKID_HDMITX1_PIXEL_SEL] = &a9_hdmitx1_pixel_sel.hw, + [CLKID_HDMITX1_PIXEL] = &a9_hdmitx1_pixel.hw, + [CLKID_HDMITX1_FE_SEL] = &a9_hdmitx1_fe_sel.hw, + [CLKID_HDMITX1_FE] = &a9_hdmitx1_fe.hw, + [CLKID_CSI_PHY_SEL] = &a9_csi_phy_sel.hw, + [CLKID_CSI_PHY_DIV] = &a9_csi_phy_div.hw, + [CLKID_CSI_PHY] = &a9_csi_phy.hw, + [CLKID_DSI0_MEAS_SEL] = &a9_dsi0_meas_sel.hw, + [CLKID_DSI0_MEAS_DIV] = &a9_dsi0_meas_div.hw, + [CLKID_DSI0_MEAS] = &a9_dsi0_meas.hw, + [CLKID_DSI1_MEAS_SEL] = &a9_dsi1_meas_sel.hw, + [CLKID_DSI1_MEAS_DIV] = &a9_dsi1_meas_div.hw, + [CLKID_DSI1_MEAS] = &a9_dsi1_meas.hw, +}; + +static const struct meson_clkc_data a9_peripherals_clkc_data = { + .hw_clks = { + .hws = a9_peripherals_hw_clks, + .num = ARRAY_SIZE(a9_peripherals_hw_clks), + }, +}; + +static const struct of_device_id a9_peripherals_clkc_match_table[] = { + { + .compatible = "amlogic,a9-peripherals-clkc", + .data = &a9_peripherals_clkc_data, + }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, a9_peripherals_clkc_match_table); + +static struct platform_driver a9_peripherals_clkc_driver = { + .probe = meson_clkc_mmio_probe, + .driver = { + .name = "a9-peripherals-clkc", + .of_match_table = a9_peripherals_clkc_match_table, + }, +}; +module_platform_driver(a9_peripherals_clkc_driver); + +MODULE_DESCRIPTION("Amlogic A9 Peripherals Clock Controller driver"); +MODULE_AUTHOR("Jian Hu "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("CLK_MESON"); From 7bad4bda74dc4713f398d3b7624ff05478e3a568 Mon Sep 17 00:00:00 2001 From: Asim Viladi Oglu Manizada Date: Thu, 23 Jul 2026 09:35:48 +0000 Subject: [PATCH 0215/1328] xfrm: ah6: validate routing header segments_left AH6 rearranges routing-header addresses before computing or verifying the ICV. ipv6_rearrange_rthdr() assumes that segments_left is not larger than the number of addresses described by the routing header's hdrlen field. That assumption does not hold for raw IPv6 HDRINCL packets. A packet with hdrlen equal to 2 describes one address, but can carry an arbitrary segments_left value. With segments_left equal to 255, the function moves its address pointer 4,064 bytes backwards and passes a 4,064-byte length to memmove(), resulting in an out-of-bounds access. Validate the invariant locally before modifying the routing header or performing any address-pointer arithmetic, and propagate malformed-header errors to the existing AH6 input and output error paths. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Assisted-by: avom-custom-harness:gpt-5.5-qwen3.6-mod-mix Signed-off-by: Asim Viladi Oglu Manizada Signed-off-by: Steffen Klassert --- net/ipv6/ah6.c | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/net/ipv6/ah6.c b/net/ipv6/ah6.c index 76f7a2de9108..c96f7e0d0a48 100644 --- a/net/ipv6/ah6.c +++ b/net/ipv6/ah6.c @@ -232,26 +232,28 @@ static void ipv6_rearrange_destopt(struct ipv6hdr *iph, struct ipv6_opt_hdr *des * Rearrange the destination address in @iph and the addresses in @rthdr * so that they appear in the order they will at the final destination. * See Appendix A2 of RFC 2402 for details. + * + * Return: 0 on success, -EINVAL if segments_left exceeds the number of + * addresses described by hdrlen. */ -static void ipv6_rearrange_rthdr(struct ipv6hdr *iph, struct ipv6_rt_hdr *rthdr) +static int ipv6_rearrange_rthdr(struct ipv6hdr *iph, struct ipv6_rt_hdr *rthdr) { - int segments, segments_left; + unsigned int segments, segments_left; struct in6_addr *addrs; struct in6_addr final_addr; segments_left = rthdr->segments_left; if (segments_left == 0) - return; - rthdr->segments_left = 0; + return 0; - /* The value of rthdr->hdrlen has been verified either by the system - * call if it is locally generated, or by ipv6_rthdr_rcv() for incoming - * packets. So we can assume that it is even and that segments is - * greater than or equal to segments_left. - * - * For the same reason we can assume that this option is of type 0. + /* Raw locally generated packets can reach AH6 without the invariant + * required by the rt0-style address rearrangement below. */ segments = rthdr->hdrlen >> 1; + if (segments_left > segments) + return -EINVAL; + + rthdr->segments_left = 0; addrs = ((struct rt0_hdr *)rthdr)->addr; final_addr = addrs[segments - 1]; @@ -261,6 +263,8 @@ static void ipv6_rearrange_rthdr(struct ipv6hdr *iph, struct ipv6_rt_hdr *rthdr) addrs[0] = iph->daddr; iph->daddr = final_addr; + + return 0; } static int ipv6_clear_mutable_options(struct ipv6hdr *iph, int len, int dir) @@ -273,6 +277,7 @@ static int ipv6_clear_mutable_options(struct ipv6hdr *iph, int len, int dir) } exthdr = { .iph = iph }; char *end = exthdr.raw + len; int nexthdr = iph->nexthdr; + int err; exthdr.iph++; @@ -292,7 +297,9 @@ static int ipv6_clear_mutable_options(struct ipv6hdr *iph, int len, int dir) break; case NEXTHDR_ROUTING: - ipv6_rearrange_rthdr(iph, exthdr.rth); + err = ipv6_rearrange_rthdr(iph, exthdr.rth); + if (err) + return err; break; default: From b08e97bee0e1438a7d3283f901b51121aa2640d5 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Sun, 5 Jul 2026 18:43:12 +0200 Subject: [PATCH 0216/1328] thermal: Remove unneeded 'fast_io' parameter in regmap_config When using MMIO with regmap, fast_io is implied. No need to set it again. Signed-off-by: Wolfram Sang Signed-off-by: Daniel Lezcano Reviewed-by: Miquel Raynal Link: https://patch.msgid.link/20260705164311.2273-2-wsa+renesas@sang-engineering.com --- drivers/thermal/armada_thermal.c | 1 - drivers/thermal/sun8i_thermal.c | 1 - 2 files changed, 2 deletions(-) diff --git a/drivers/thermal/armada_thermal.c b/drivers/thermal/armada_thermal.c index be6240984002..f64e46fcf0be 100644 --- a/drivers/thermal/armada_thermal.c +++ b/drivers/thermal/armada_thermal.c @@ -710,7 +710,6 @@ static const struct regmap_config armada_thermal_regmap_config = { .reg_bits = 32, .reg_stride = 4, .val_bits = 32, - .fast_io = true, }; static int armada_thermal_probe_legacy(struct platform_device *pdev, diff --git a/drivers/thermal/sun8i_thermal.c b/drivers/thermal/sun8i_thermal.c index 22674790629a..284684137c43 100644 --- a/drivers/thermal/sun8i_thermal.c +++ b/drivers/thermal/sun8i_thermal.c @@ -149,7 +149,6 @@ static const struct regmap_config config = { .reg_bits = 32, .val_bits = 32, .reg_stride = 4, - .fast_io = true, .max_register = 0xfc, }; From 251621813fb4275e24431f9a0690aec9b15823e7 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Thu, 2 Jul 2026 11:48:29 +0200 Subject: [PATCH 0217/1328] thermal/drivers/airoha: Fix copy paste error on clamp_t low temp In airoha_thermal_set_trips, there is a copy paste error on clamping the value for the low trip temp point. Fix it to the correct value and actually clamp for the low variable. Fixes: 42de37f40e1b ("thermal/drivers: Add support for Airoha EN7581 thermal sensor") Signed-off-by: Christian Marangi Signed-off-by: Daniel Lezcano Reviewed-by: Wayen Yan Link: https://patch.msgid.link/20260702094846.17325-2-ansuelsmth@gmail.com --- drivers/thermal/airoha_thermal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thermal/airoha_thermal.c b/drivers/thermal/airoha_thermal.c index b9fd6bfc88e5..439aa011b75c 100644 --- a/drivers/thermal/airoha_thermal.c +++ b/drivers/thermal/airoha_thermal.c @@ -273,7 +273,7 @@ static int airoha_thermal_set_trips(struct thermal_zone_device *tz, int low, if (low != -INT_MAX) { /* Validate low and clamp it to a supported value */ - low = clamp_t(int, high, RAW_TO_TEMP(priv, 0), + low = clamp_t(int, low, RAW_TO_TEMP(priv, 0), RAW_TO_TEMP(priv, FIELD_MAX(EN7581_DOUT_TADC_MASK))); /* We offset the low temp of 1°C to trigger correct event */ From 6791265d609549be55bb35b747c9648d0b570c12 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Thu, 2 Jul 2026 11:48:30 +0200 Subject: [PATCH 0218/1328] thermal/drivers/airoha: Fix copy paste error for sen internal In airoha_thermal_setup_monitor there is a copy paste error on configuring the internval for temp monitor. Fix the error and use the correct mask for the sen interval for the EN7581_TEMPMONCTL2 register. Fixes: 42de37f40e1b ("thermal/drivers: Add support for Airoha EN7581 thermal sensor") Signed-off-by: Christian Marangi Signed-off-by: Daniel Lezcano Reviewed-by: Wayen Yan Link: https://patch.msgid.link/20260702094846.17325-3-ansuelsmth@gmail.com --- drivers/thermal/airoha_thermal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thermal/airoha_thermal.c b/drivers/thermal/airoha_thermal.c index 439aa011b75c..829a7327fc40 100644 --- a/drivers/thermal/airoha_thermal.c +++ b/drivers/thermal/airoha_thermal.c @@ -403,7 +403,7 @@ static void airoha_thermal_setup_monitor(struct airoha_thermal_priv *priv) * sen interval is 379 * 52.715us = 19.97ms */ writel(FIELD_PREP(EN7581_FILT_INTERVAL, 1) | - FIELD_PREP(EN7581_FILT_INTERVAL, 379), + FIELD_PREP(EN7581_SEN_INTERVAL, 379), priv->base + EN7581_TEMPMONCTL2); /* AHB poll is set to 146 * 68.64 = 10.02us */ From a3137f1aa44cd431e495afb6a97ccc498b9ebe75 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Thu, 2 Jul 2026 11:48:31 +0200 Subject: [PATCH 0219/1328] thermal/drivers/airoha: Convert to regmap API In preparation for support of Airoha AN7583, convert the driver to regmap API. This is needed as Airoha AN7583 will be based on syscon regmap. Signed-off-by: Christian Marangi Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260702094846.17325-4-ansuelsmth@gmail.com --- drivers/thermal/airoha_thermal.c | 77 +++++++++++++++++++------------- 1 file changed, 45 insertions(+), 32 deletions(-) diff --git a/drivers/thermal/airoha_thermal.c b/drivers/thermal/airoha_thermal.c index 829a7327fc40..b63893a8997a 100644 --- a/drivers/thermal/airoha_thermal.c +++ b/drivers/thermal/airoha_thermal.c @@ -194,7 +194,7 @@ #define AIROHA_MAX_SAMPLES 6 struct airoha_thermal_priv { - void __iomem *base; + struct regmap *map; struct regmap *chip_scu; struct resource scu_adc_res; @@ -265,8 +265,8 @@ static int airoha_thermal_set_trips(struct thermal_zone_device *tz, int low, RAW_TO_TEMP(priv, FIELD_MAX(EN7581_DOUT_TADC_MASK))); /* We offset the high temp of 1°C to trigger correct event */ - writel(TEMP_TO_RAW(priv, high) >> 4, - priv->base + EN7581_TEMPOFFSETH); + regmap_write(priv->map, EN7581_TEMPOFFSETH, + TEMP_TO_RAW(priv, high) >> 4); enable_monitor = true; } @@ -277,15 +277,15 @@ static int airoha_thermal_set_trips(struct thermal_zone_device *tz, int low, RAW_TO_TEMP(priv, FIELD_MAX(EN7581_DOUT_TADC_MASK))); /* We offset the low temp of 1°C to trigger correct event */ - writel(TEMP_TO_RAW(priv, low) >> 4, - priv->base + EN7581_TEMPOFFSETL); + regmap_write(priv->map, EN7581_TEMPOFFSETL, + TEMP_TO_RAW(priv, low) >> 4); enable_monitor = true; } /* Enable sensor 0 monitor after trip are set */ if (enable_monitor) - writel(EN7581_SENSE0_EN, priv->base + EN7581_TEMPMONCTL0); + regmap_write(priv->map, EN7581_TEMPMONCTL0, EN7581_SENSE0_EN); return 0; } @@ -300,9 +300,9 @@ static irqreturn_t airoha_thermal_irq(int irq, void *data) struct airoha_thermal_priv *priv = data; enum thermal_notify_event event; bool update = false; - u32 status; + u32 status = 0; - status = readl(priv->base + EN7581_TEMPMONINTSTS); + regmap_read(priv->map, EN7581_TEMPMONINTSTS, &status); switch (status & (EN7581_HOFSINTSTS0 | EN7581_LOFSINTSTS0)) { case EN7581_HOFSINTSTS0: event = THERMAL_TRIP_VIOLATED; @@ -318,7 +318,7 @@ static irqreturn_t airoha_thermal_irq(int irq, void *data) } /* Reset Interrupt */ - writel(status, priv->base + EN7581_TEMPMONINTSTS); + regmap_write(priv->map, EN7581_TEMPMONINTSTS, status); if (update) thermal_zone_device_update(priv->tz, event); @@ -329,18 +329,19 @@ static irqreturn_t airoha_thermal_irq(int irq, void *data) static void airoha_thermal_setup_adc_val(struct device *dev, struct airoha_thermal_priv *priv) { - u32 efuse_calib_info, cpu_sensor; + u32 efuse_calib_info = 0; + u32 cpu_sensor = 0; /* Setup thermal sensor to ADC mode and setup the mux to DIODE1 */ airoha_init_thermal_ADC_mode(priv); /* sleep 10 ms for ADC to enable */ usleep_range(10 * USEC_PER_MSEC, 11 * USEC_PER_MSEC); - efuse_calib_info = readl(priv->base + EN7581_EFUSE_TEMP_OFFSET_REG); + regmap_read(priv->map, EN7581_EFUSE_TEMP_OFFSET_REG, &efuse_calib_info); if (efuse_calib_info) { priv->default_offset = FIELD_GET(EN7581_EFUSE_TEMP_OFFSET, efuse_calib_info); /* Different slope are applied if the sensor is used for CPU or for package */ - cpu_sensor = readl(priv->base + EN7581_EFUSE_TEMP_CPU_SENSOR_REG); + regmap_read(priv->map, EN7581_EFUSE_TEMP_CPU_SENSOR_REG, &cpu_sensor); if (cpu_sensor) { priv->default_slope = EN7581_SLOPE_X100_DIO_DEFAULT; priv->init_temp = EN7581_INIT_TEMP_FTK_X10; @@ -359,8 +360,8 @@ static void airoha_thermal_setup_adc_val(struct device *dev, static void airoha_thermal_setup_monitor(struct airoha_thermal_priv *priv) { /* Set measure mode */ - writel(FIELD_PREP(EN7581_MSRCTL0, EN7581_MSRCTL_6SAMPLE_MAX_MIX_AVG4), - priv->base + EN7581_TEMPMSRCTL0); + regmap_write(priv->map, EN7581_TEMPMSRCTL0, + FIELD_PREP(EN7581_MSRCTL0, EN7581_MSRCTL_6SAMPLE_MAX_MIX_AVG4)); /* * Configure ADC valid reading addr @@ -375,15 +376,15 @@ static void airoha_thermal_setup_monitor(struct airoha_thermal_priv *priv) * We set valid instead of volt as we don't enable valid/volt * split reading and AHB read valid addr in such case. */ - writel(priv->scu_adc_res.start + EN7581_DOUT_TADC, - priv->base + EN7581_TEMPADCVALIDADDR); + regmap_write(priv->map, EN7581_TEMPADCVALIDADDR, + priv->scu_adc_res.start + EN7581_DOUT_TADC); /* * Configure valid bit on a fake value of bit 16. The ADC outputs * max of 2 bytes for voltage. */ - writel(FIELD_PREP(EN7581_ADV_RD_VALID_POS, 16), - priv->base + EN7581_TEMPADCVALIDMASK); + regmap_write(priv->map, EN7581_TEMPADCVALIDMASK, + FIELD_PREP(EN7581_ADV_RD_VALID_POS, 16)); /* * AHB supports max 12 bytes for ADC voltage. Shift the read @@ -391,40 +392,52 @@ static void airoha_thermal_setup_monitor(struct airoha_thermal_priv *priv) * in the order of half a °C and is acceptable in the context * of triggering interrupt in critical condition. */ - writel(FIELD_PREP(EN7581_ADC_VOLTAGE_SHIFT, 4), - priv->base + EN7581_TEMPADCVOLTAGESHIFT); + regmap_write(priv->map, EN7581_TEMPADCVOLTAGESHIFT, + FIELD_PREP(EN7581_ADC_VOLTAGE_SHIFT, 4)); /* BUS clock is 300MHz counting unit is 3 * 68.64 * 256 = 52.715us */ - writel(FIELD_PREP(EN7581_PERIOD_UNIT, 3), - priv->base + EN7581_TEMPMONCTL1); + regmap_write(priv->map, EN7581_TEMPMONCTL1, + FIELD_PREP(EN7581_PERIOD_UNIT, 3)); /* * filt interval is 1 * 52.715us = 52.715us, * sen interval is 379 * 52.715us = 19.97ms */ - writel(FIELD_PREP(EN7581_FILT_INTERVAL, 1) | - FIELD_PREP(EN7581_SEN_INTERVAL, 379), - priv->base + EN7581_TEMPMONCTL2); + regmap_write(priv->map, EN7581_TEMPMONCTL2, + FIELD_PREP(EN7581_FILT_INTERVAL, 1) | + FIELD_PREP(EN7581_SEN_INTERVAL, 379)); /* AHB poll is set to 146 * 68.64 = 10.02us */ - writel(FIELD_PREP(EN7581_ADC_POLL_INTVL, 146), - priv->base + EN7581_TEMPAHBPOLL); + regmap_write(priv->map, EN7581_TEMPAHBPOLL, + FIELD_PREP(EN7581_ADC_POLL_INTVL, 146)); } +static const struct regmap_config airoha_thermal_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, +}; + static int airoha_thermal_probe(struct platform_device *pdev) { struct airoha_thermal_priv *priv; struct device_node *chip_scu_np; struct device *dev = &pdev->dev; + void __iomem *base; int irq, ret; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; - priv->base = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(priv->base)) - return PTR_ERR(priv->base); + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + priv->map = devm_regmap_init_mmio(dev, base, + &airoha_thermal_regmap_config); + if (IS_ERR(priv->map)) + return PTR_ERR(priv->map); chip_scu_np = of_parse_phandle(dev->of_node, "airoha,chip-scu", 0); if (!chip_scu_np) @@ -462,8 +475,8 @@ static int airoha_thermal_probe(struct platform_device *pdev) platform_set_drvdata(pdev, priv); /* Enable LOW and HIGH interrupt */ - writel(EN7581_HOFSINTEN0 | EN7581_LOFSINTEN0, - priv->base + EN7581_TEMPMONINT); + regmap_write(priv->map, EN7581_TEMPMONINT, + EN7581_HOFSINTEN0 | EN7581_LOFSINTEN0); return 0; } From 62f6a66189b161d475dbdb31055b3ce8963cc418 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Thu, 2 Jul 2026 11:48:32 +0200 Subject: [PATCH 0220/1328] thermal/drivers/airoha: Generalize probe function In preparation for support of Airoha AN7583, generalize the probe function to address for the 2 SoC difference. Implement a match_data struct where it's possible to define a more specific probe and post_probe function and specific thermal ops and pllrg protect value. Signed-off-by: Christian Marangi Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260702094846.17325-5-ansuelsmth@gmail.com --- drivers/thermal/airoha_thermal.c | 102 +++++++++++++++++++++++-------- 1 file changed, 75 insertions(+), 27 deletions(-) diff --git a/drivers/thermal/airoha_thermal.c b/drivers/thermal/airoha_thermal.c index b63893a8997a..ebb47ae5f2ce 100644 --- a/drivers/thermal/airoha_thermal.c +++ b/drivers/thermal/airoha_thermal.c @@ -198,12 +198,23 @@ struct airoha_thermal_priv { struct regmap *chip_scu; struct resource scu_adc_res; + u32 pllrg_protect; + struct thermal_zone_device *tz; int init_temp; int default_slope; int default_offset; }; +struct airoha_thermal_soc_data { + u32 pllrg_protect; + + const struct thermal_zone_device_ops *thdev_ops; + int (*probe)(struct platform_device *pdev, + struct airoha_thermal_priv *priv); + int (*post_probe)(struct platform_device *pdev); +}; + static int airoha_get_thermal_ADC(struct airoha_thermal_priv *priv) { u32 val; @@ -220,7 +231,8 @@ static void airoha_init_thermal_ADC_mode(struct airoha_thermal_priv *priv) regmap_read(priv->chip_scu, EN7581_PLLRG_PROTECT, &pllrg); /* Give access to thermal regs */ - regmap_write(priv->chip_scu, EN7581_PLLRG_PROTECT, EN7581_SCU_THERMAL_PROTECT_KEY); + regmap_write(priv->chip_scu, EN7581_PLLRG_PROTECT, + priv->pllrg_protect); adc_mux = FIELD_PREP(EN7581_MUX_TADC, EN7581_SCU_THERMAL_MUX_DIODE1); regmap_write(priv->chip_scu, EN7581_PWD_TADC, adc_mux); @@ -228,7 +240,7 @@ static void airoha_init_thermal_ADC_mode(struct airoha_thermal_priv *priv) regmap_write(priv->chip_scu, EN7581_PLLRG_PROTECT, pllrg); } -static int airoha_thermal_get_temp(struct thermal_zone_device *tz, int *temp) +static int en7581_thermal_get_temp(struct thermal_zone_device *tz, int *temp) { struct airoha_thermal_priv *priv = thermal_zone_device_priv(tz); int min_value, max_value, avg_value, value; @@ -253,7 +265,7 @@ static int airoha_thermal_get_temp(struct thermal_zone_device *tz, int *temp) return 0; } -static int airoha_thermal_set_trips(struct thermal_zone_device *tz, int low, +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); @@ -290,12 +302,12 @@ static int airoha_thermal_set_trips(struct thermal_zone_device *tz, int low, return 0; } -static const struct thermal_zone_device_ops thdev_ops = { - .get_temp = airoha_thermal_get_temp, - .set_trips = airoha_thermal_set_trips, +static const struct thermal_zone_device_ops en7581_thdev_ops = { + .get_temp = en7581_thermal_get_temp, + .set_trips = en7581_thermal_set_trips, }; -static irqreturn_t airoha_thermal_irq(int irq, void *data) +static irqreturn_t en7581_thermal_irq(int irq, void *data) { struct airoha_thermal_priv *priv = data; enum thermal_notify_event event; @@ -326,7 +338,7 @@ static irqreturn_t airoha_thermal_irq(int irq, void *data) return IRQ_HANDLED; } -static void airoha_thermal_setup_adc_val(struct device *dev, +static void en7581_thermal_setup_adc_val(struct device *dev, struct airoha_thermal_priv *priv) { u32 efuse_calib_info = 0; @@ -357,7 +369,7 @@ static void airoha_thermal_setup_adc_val(struct device *dev, } } -static void airoha_thermal_setup_monitor(struct airoha_thermal_priv *priv) +static void en7581_thermal_setup_monitor(struct airoha_thermal_priv *priv) { /* Set measure mode */ regmap_write(priv->map, EN7581_TEMPMSRCTL0, @@ -412,30 +424,26 @@ static void airoha_thermal_setup_monitor(struct airoha_thermal_priv *priv) FIELD_PREP(EN7581_ADC_POLL_INTVL, 146)); } -static const struct regmap_config airoha_thermal_regmap_config = { +static const struct regmap_config en7581_thermal_regmap_config = { .reg_bits = 32, .reg_stride = 4, .val_bits = 32, }; -static int airoha_thermal_probe(struct platform_device *pdev) +static int en7581_thermal_probe(struct platform_device *pdev, + struct airoha_thermal_priv *priv) { - struct airoha_thermal_priv *priv; struct device_node *chip_scu_np; struct device *dev = &pdev->dev; void __iomem *base; int irq, ret; - priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(base)) return PTR_ERR(base); priv->map = devm_regmap_init_mmio(dev, base, - &airoha_thermal_regmap_config); + &en7581_thermal_regmap_config); if (IS_ERR(priv->map)) return PTR_ERR(priv->map); @@ -455,18 +463,55 @@ static int airoha_thermal_probe(struct platform_device *pdev) return irq; ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, - airoha_thermal_irq, IRQF_ONESHOT, + en7581_thermal_irq, IRQF_ONESHOT, pdev->name, priv); if (ret) { dev_err(dev, "Can't get interrupt working.\n"); return ret; } - airoha_thermal_setup_monitor(priv); - airoha_thermal_setup_adc_val(dev, priv); + en7581_thermal_setup_monitor(priv); + en7581_thermal_setup_adc_val(dev, priv); + + return 0; +} + +static int en7581_thermal_post_probe(struct platform_device *pdev) +{ + struct airoha_thermal_priv *priv = platform_get_drvdata(pdev); + + /* Enable LOW and HIGH interrupt (if supported) */ + regmap_write(priv->map, EN7581_TEMPMONINT, + EN7581_HOFSINTEN0 | EN7581_LOFSINTEN0); + + return 0; +} + +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; + + soc_data = device_get_match_data(dev); + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->pllrg_protect = soc_data->pllrg_protect; + + if (!soc_data->probe) + return -EINVAL; + + ret = soc_data->probe(pdev, priv); + if (ret) + return ret; /* register of thermal sensor and get info from DT */ - priv->tz = devm_thermal_of_zone_register(dev, 0, priv, &thdev_ops); + 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); @@ -474,15 +519,18 @@ static int airoha_thermal_probe(struct platform_device *pdev) platform_set_drvdata(pdev, priv); - /* Enable LOW and HIGH interrupt */ - regmap_write(priv->map, EN7581_TEMPMONINT, - EN7581_HOFSINTEN0 | EN7581_LOFSINTEN0); - - return 0; + return soc_data->post_probe ? soc_data->post_probe(pdev) : 0; } +static const struct airoha_thermal_soc_data en7581_data = { + .pllrg_protect = EN7581_SCU_THERMAL_PROTECT_KEY, + .thdev_ops = &en7581_thdev_ops, + .probe = &en7581_thermal_probe, + .post_probe = &en7581_thermal_post_probe, +}; + static const struct of_device_id airoha_thermal_match[] = { - { .compatible = "airoha,en7581-thermal" }, + { .compatible = "airoha,en7581-thermal", .data = &en7581_data }, {}, }; MODULE_DEVICE_TABLE(of, airoha_thermal_match); From 86741c9128629a3059a0a759b665442958dedea0 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Thu, 2 Jul 2026 11:48:33 +0200 Subject: [PATCH 0221/1328] thermal/drivers/airoha: Generalize get_thermal_ADC and set_mux function In preparation for support of Airoha AN7583, generalize get_thermal_ADC() and set_thermal_mux() with the use of reg_field API. This is to take into account the same logic between the current supported SoC and the new one but with different register address. While at it also further improve some comments and move sleep inside the set_thermal_mux function. Signed-off-by: Christian Marangi Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260702094846.17325-6-ansuelsmth@gmail.com --- drivers/thermal/airoha_thermal.c | 56 +++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/drivers/thermal/airoha_thermal.c b/drivers/thermal/airoha_thermal.c index ebb47ae5f2ce..249abbbd46bc 100644 --- a/drivers/thermal/airoha_thermal.c +++ b/drivers/thermal/airoha_thermal.c @@ -193,9 +193,18 @@ #define AIROHA_MAX_SAMPLES 6 +enum airoha_thermal_chip_scu_field { + AIROHA_THERMAL_DOUT_TADC, + AIROHA_THERMAL_MUX_TADC, + + /* keep last */ + AIROHA_THERMAL_FIELD_MAX, +}; + struct airoha_thermal_priv { struct regmap *map; struct regmap *chip_scu; + struct regmap_field *chip_scu_fields[AIROHA_THERMAL_FIELD_MAX]; struct resource scu_adc_res; u32 pllrg_protect; @@ -219,25 +228,32 @@ static int airoha_get_thermal_ADC(struct airoha_thermal_priv *priv) { u32 val; - regmap_read(priv->chip_scu, EN7581_DOUT_TADC, &val); - return FIELD_GET(EN7581_DOUT_TADC_MASK, val); + regmap_field_read(priv->chip_scu_fields[AIROHA_THERMAL_DOUT_TADC], + &val); + return val; } -static void airoha_init_thermal_ADC_mode(struct airoha_thermal_priv *priv) +static void airoha_set_thermal_mux(struct airoha_thermal_priv *priv, + int tdac_idx) { - u32 adc_mux, pllrg; + u32 pllrg; /* Save PLLRG current value */ regmap_read(priv->chip_scu, EN7581_PLLRG_PROTECT, &pllrg); - /* Give access to thermal regs */ + /* Give access to Thermal regs */ regmap_write(priv->chip_scu, EN7581_PLLRG_PROTECT, priv->pllrg_protect); - adc_mux = FIELD_PREP(EN7581_MUX_TADC, EN7581_SCU_THERMAL_MUX_DIODE1); - regmap_write(priv->chip_scu, EN7581_PWD_TADC, adc_mux); + + /* Configure Thermal ADC mux to tdac_idx */ + regmap_field_write(priv->chip_scu_fields[AIROHA_THERMAL_MUX_TADC], + tdac_idx); /* Restore PLLRG value on exit */ regmap_write(priv->chip_scu, EN7581_PLLRG_PROTECT, pllrg); + + /* Sleep 10 ms for Thermal ADC to enable */ + usleep_range(10 * USEC_PER_MSEC, 11 * USEC_PER_MSEC); } static int en7581_thermal_get_temp(struct thermal_zone_device *tz, int *temp) @@ -344,10 +360,8 @@ static void en7581_thermal_setup_adc_val(struct device *dev, u32 efuse_calib_info = 0; u32 cpu_sensor = 0; - /* Setup thermal sensor to ADC mode and setup the mux to DIODE1 */ - airoha_init_thermal_ADC_mode(priv); - /* sleep 10 ms for ADC to enable */ - usleep_range(10 * USEC_PER_MSEC, 11 * USEC_PER_MSEC); + /* Setup Thermal Sensor to ADC mode and setup the mux to DIODE1 */ + airoha_set_thermal_mux(priv, EN7581_SCU_THERMAL_MUX_DIODE1); regmap_read(priv->map, EN7581_EFUSE_TEMP_OFFSET_REG, &efuse_calib_info); if (efuse_calib_info) { @@ -430,13 +444,18 @@ static const struct regmap_config en7581_thermal_regmap_config = { .val_bits = 32, }; +static const struct reg_field en7581_chip_scu_fields[AIROHA_THERMAL_FIELD_MAX] = { + [AIROHA_THERMAL_DOUT_TADC] = REG_FIELD(EN7581_DOUT_TADC, 0, 15), + [AIROHA_THERMAL_MUX_TADC] = REG_FIELD(EN7581_PWD_TADC, 1, 3), +}; + static int en7581_thermal_probe(struct platform_device *pdev, struct airoha_thermal_priv *priv) { struct device_node *chip_scu_np; struct device *dev = &pdev->dev; void __iomem *base; - int irq, ret; + int i, irq, ret; base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(base)) @@ -455,6 +474,19 @@ static int en7581_thermal_probe(struct platform_device *pdev, if (IS_ERR(priv->chip_scu)) return PTR_ERR(priv->chip_scu); + for (i = 0; i < AIROHA_THERMAL_FIELD_MAX; i++) { + struct regmap_field *field; + + field = devm_regmap_field_alloc(dev, priv->chip_scu, + en7581_chip_scu_fields[i]); + if (IS_ERR(field)) { + of_node_put(chip_scu_np); + return PTR_ERR(field); + } + + priv->chip_scu_fields[i] = field; + } + of_address_to_resource(chip_scu_np, 0, &priv->scu_adc_res); of_node_put(chip_scu_np); From fff997256c652d3405c71dd107fe5756c8295963 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Thu, 2 Jul 2026 11:48:34 +0200 Subject: [PATCH 0222/1328] dt-bindings: arm: airoha: Add the chip-scu node for AN7583 SoC Document support for Airoha AN7583 chip-scu node. This is similar to Airoha EN7581 with the addition of the presence of thermal sensor in addition to controlling HW PIN and other miscellaneous pheriperals. Signed-off-by: Christian Marangi Signed-off-by: Daniel Lezcano Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260702094846.17325-7-ansuelsmth@gmail.com --- .../bindings/arm/airoha,en7581-chip-scu.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Documentation/devicetree/bindings/arm/airoha,en7581-chip-scu.yaml b/Documentation/devicetree/bindings/arm/airoha,en7581-chip-scu.yaml index 67c449d804c2..cc564dc7b414 100644 --- a/Documentation/devicetree/bindings/arm/airoha,en7581-chip-scu.yaml +++ b/Documentation/devicetree/bindings/arm/airoha,en7581-chip-scu.yaml @@ -19,15 +19,29 @@ properties: items: - enum: - airoha,en7581-chip-scu + - airoha,an7583-chip-scu - const: syscon reg: maxItems: 1 + '#thermal-sensor-cells': + const: 0 + required: - compatible - reg +if: + properties: + compatible: + contains: + const: airoha,en7581-chip-scu + +then: + properties: + '#thermal-sensor-cells': false + additionalProperties: false examples: From b20d9782756a05ddae69512d79ffa0353c488b38 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Thu, 2 Jul 2026 11:48:35 +0200 Subject: [PATCH 0223/1328] thermal/drivers/airoha: Add support for AN7583 Thermal Sensor Add support for Airoha AN7583 Thermal driver. This apply similar logic on how to read the temperature but totally drop support for the PTP_THERMAL subsystem. PTP_THERMAL subsystem was a way to trigger trip point from hardware by configuring how to read the temperature internally. This subsystem has been totally removed from Airoha AN7583 permitting only to read the temperature. The SoC support up to 3 sensor but the original driver always read the BGA sensor hence it's currently implemented reading only this specific sensor. Reference and values for the other 2 sensor are defined for further implementation if confirmed working. set_thermal_mux() is extended to also address muxing the sensor as AN7583 use a different way to read the temperature from 3 different diode. The EN7581 code is updated to account for these changes. Signed-off-by: Christian Marangi Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260702094846.17325-8-ansuelsmth@gmail.com --- drivers/thermal/airoha_thermal.c | 161 ++++++++++++++++++++++++++++++- 1 file changed, 157 insertions(+), 4 deletions(-) diff --git a/drivers/thermal/airoha_thermal.c b/drivers/thermal/airoha_thermal.c index 249abbbd46bc..c84b5c36e880 100644 --- a/drivers/thermal/airoha_thermal.c +++ b/drivers/thermal/airoha_thermal.c @@ -18,6 +18,12 @@ #define EN7581_DOUT_TADC 0x2f8 #define EN7581_DOUT_TADC_MASK GENMASK(15, 0) +#define AN7583_MUX_SENSOR 0x2a0 +#define AN7583_LOAD_ADJ GENMASK(3, 2) +#define AN7583_MUX_TADC 0x2e4 +#define AN7583_MUX_TADC_MASK GENMASK(3, 1) +#define AN7583_DOUT_TADC 0x2f0 + /* PTP_THERMAL regs */ #define EN7581_TEMPMONCTL0 0x800 #define EN7581_SENSE3_EN BIT(3) @@ -181,6 +187,11 @@ #define EN7581_SCU_THERMAL_PROTECT_KEY 0x12 #define EN7581_SCU_THERMAL_MUX_DIODE1 0x7 +#define AN7583_SCU_THERMAL_PROTECT_KEY 0x80 +#define AN7583_NUM_SENSOR 3 + +#define AIROHA_THERMAL_NO_MUX_SENSOR -1 + /* 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) * \ (priv)->default_slope) / 1000) + \ @@ -193,8 +204,39 @@ #define AIROHA_MAX_SAMPLES 6 +/* + * AN7583 supports all these ADC mux but the original driver + * always checked temp with the AN7583_BGP_TEMP_SENSOR. + * Assume using the other sensor temperature is invalid and + * always read from AN7583_BGP_TEMP_SENSOR. + * + * On top of this it's defined that AN7583 supports 3 + * sensor: AN7583_BGP_TEMP_SENSOR, AN7583_GBE_TEMP_SENSOR, + * AN7583_CPU_TEMP_SENSOR. + * + * Provide the ADC mux for reference. + */ +enum an7583_thermal_adc_mux { + AN7583_BGP_TEMP_SENSOR, + AN7583_PAD_AVS, + AN7583_CORE_POWER, + AN7583_AVSDAC_OUT, + AN7583_VCM, + AN7583_GBE_TEMP_SENSOR, + AN7583_CPU_TEMP_SENSOR, + + AN7583_ADC_MUX_MAX, +}; + +enum an7583_thermal_diode_mux { + AN7583_D0_TADC, + AN7583_ZERO_TADC, + AN7583_D1_TADC, +}; + enum airoha_thermal_chip_scu_field { AIROHA_THERMAL_DOUT_TADC, + AIROHA_THERMAL_MUX_SENSOR, AIROHA_THERMAL_MUX_TADC, /* keep last */ @@ -208,6 +250,7 @@ struct airoha_thermal_priv { struct resource scu_adc_res; u32 pllrg_protect; + int current_adc; struct thermal_zone_device *tz; int init_temp; @@ -224,6 +267,24 @@ struct airoha_thermal_soc_data { int (*post_probe)(struct platform_device *pdev); }; +static const unsigned int an7583_thermal_coeff[AN7583_ADC_MUX_MAX] = { + [AN7583_BGP_TEMP_SENSOR] = 973, + [AN7583_GBE_TEMP_SENSOR] = 995, + [AN7583_CPU_TEMP_SENSOR] = 1035, +}; + +static const unsigned int an7583_thermal_slope[AN7583_ADC_MUX_MAX] = { + [AN7583_BGP_TEMP_SENSOR] = 7440, + [AN7583_GBE_TEMP_SENSOR] = 7620, + [AN7583_CPU_TEMP_SENSOR] = 8390, +}; + +static const unsigned int an7583_thermal_offset[AN7583_ADC_MUX_MAX] = { + [AN7583_BGP_TEMP_SENSOR] = 294, + [AN7583_GBE_TEMP_SENSOR] = 298, + [AN7583_CPU_TEMP_SENSOR] = 344, +}; + static int airoha_get_thermal_ADC(struct airoha_thermal_priv *priv) { u32 val; @@ -234,7 +295,7 @@ static int airoha_get_thermal_ADC(struct airoha_thermal_priv *priv) } static void airoha_set_thermal_mux(struct airoha_thermal_priv *priv, - int tdac_idx) + int tdac_idx, int sensor_idx) { u32 pllrg; @@ -245,9 +306,20 @@ static void airoha_set_thermal_mux(struct airoha_thermal_priv *priv, regmap_write(priv->chip_scu, EN7581_PLLRG_PROTECT, priv->pllrg_protect); + /* + * Configure Thermal Sensor mux to sensor_idx. + * (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 */ - regmap_field_write(priv->chip_scu_fields[AIROHA_THERMAL_MUX_TADC], - tdac_idx); + if (priv->current_adc != tdac_idx) { + regmap_field_write(priv->chip_scu_fields[AIROHA_THERMAL_MUX_TADC], + tdac_idx); + priv->current_adc = tdac_idx; + } /* Restore PLLRG value on exit */ regmap_write(priv->chip_scu, EN7581_PLLRG_PROTECT, pllrg); @@ -361,7 +433,8 @@ static void en7581_thermal_setup_adc_val(struct device *dev, u32 cpu_sensor = 0; /* Setup Thermal Sensor to ADC mode and setup the mux to DIODE1 */ - airoha_set_thermal_mux(priv, EN7581_SCU_THERMAL_MUX_DIODE1); + airoha_set_thermal_mux(priv, EN7581_SCU_THERMAL_MUX_DIODE1, + AIROHA_THERMAL_NO_MUX_SENSOR); regmap_read(priv->map, EN7581_EFUSE_TEMP_OFFSET_REG, &efuse_calib_info); if (efuse_calib_info) { @@ -477,6 +550,10 @@ static int en7581_thermal_probe(struct platform_device *pdev, for (i = 0; i < AIROHA_THERMAL_FIELD_MAX; i++) { struct regmap_field *field; + /* Skip registering MUX_SENSOR field as not supported */ + if (i == AIROHA_THERMAL_MUX_SENSOR) + continue; + field = devm_regmap_field_alloc(dev, priv->chip_scu, en7581_chip_scu_fields[i]); if (IS_ERR(field)) { @@ -519,6 +596,74 @@ static int en7581_thermal_post_probe(struct platform_device *pdev) return 0; } +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 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]; + + 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); + + delta_diode = diode_d1 - diode_d0; + delta_gain = (delta_diode * coeff) / 100 + (diode_zero - diode_d1); + if (!delta_gain) + return -EINVAL; + + *temp = (slope * delta_diode * 10) / delta_gain - offset * 10; + *temp *= 100; + + return 0; +} + +static const struct thermal_zone_device_ops an7583_tz_ops = { + .get_temp = an7583_thermal_get_temp, +}; + +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), +}; + +static int an7583_thermal_probe(struct platform_device *pdev, + struct airoha_thermal_priv *priv) +{ + struct device *dev = &pdev->dev; + int i; + + priv->chip_scu = device_node_to_regmap(dev->of_node); + if (IS_ERR(priv->chip_scu)) + return PTR_ERR(priv->chip_scu); + + for (i = 0; i < AIROHA_THERMAL_FIELD_MAX; i++) { + struct regmap_field *field; + + field = devm_regmap_field_alloc(dev, priv->chip_scu, + an7583_chip_scu_fields[i]); + if (IS_ERR(field)) + return PTR_ERR(field); + + priv->chip_scu_fields[i] = field; + } + + return 0; +} + static int airoha_thermal_probe(struct platform_device *pdev) { const struct airoha_thermal_soc_data *soc_data; @@ -533,6 +678,7 @@ static int airoha_thermal_probe(struct platform_device *pdev) return -ENOMEM; priv->pllrg_protect = soc_data->pllrg_protect; + priv->current_adc = -1; if (!soc_data->probe) return -EINVAL; @@ -561,8 +707,15 @@ static const struct airoha_thermal_soc_data en7581_data = { .post_probe = &en7581_thermal_post_probe, }; +static const struct airoha_thermal_soc_data an7583_data = { + .pllrg_protect = AN7583_SCU_THERMAL_PROTECT_KEY, + .thdev_ops = &an7583_tz_ops, + .probe = &an7583_thermal_probe, +}; + static const struct of_device_id airoha_thermal_match[] = { { .compatible = "airoha,en7581-thermal", .data = &en7581_data }, + { .compatible = "airoha,an7583-chip-scu", .data = &an7583_data }, {}, }; MODULE_DEVICE_TABLE(of, airoha_thermal_match); From 0c569e22020f53ddfac0099b0aa193907bfbcd6f Mon Sep 17 00:00:00 2001 From: Rakesh Kota Date: Fri, 24 Jul 2026 16:30:24 +0530 Subject: [PATCH 0224/1328] thermal/drivers/qcom-spmi-adc-tm5: Drop IIO_VAL_INT check in adc_tm5_get_temp Commit bb21ee31f575 ("iio: Fix iio_multiply_value use in iio_read_channel_processed_scale") fixed the iio_read_channel_processed_scale to return 0 on success instead of IIO_VAL_INT (1). The existing check in adc_tm5_get_temp() treated a successful return as an error because it expected IIO_VAL_INT. Drop the redundant `ret != IIO_VAL_INT` condition and rely solely on the negative error check. Fixes: bb21ee31f575 ("iio: Fix iio_multiply_value use in iio_read_channel_processed_scale") Signed-off-by: Rakesh Kota Signed-off-by: Daniel Lezcano Reviewed-by: Jonathan Cameron Link: https://patch.msgid.link/20260724-adc-tm5-drop-iio-val-int-check-v1-1-0b85a0895dd7@oss.qualcomm.com --- drivers/thermal/qcom/qcom-spmi-adc-tm5.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/thermal/qcom/qcom-spmi-adc-tm5.c b/drivers/thermal/qcom/qcom-spmi-adc-tm5.c index d7f2e6ca92c2..d1b086737bcd 100644 --- a/drivers/thermal/qcom/qcom-spmi-adc-tm5.c +++ b/drivers/thermal/qcom/qcom-spmi-adc-tm5.c @@ -369,9 +369,6 @@ static int adc_tm5_get_temp(struct thermal_zone_device *tz, int *temp) if (ret < 0) return ret; - if (ret != IIO_VAL_INT) - return -EINVAL; - return 0; } From bcc6d886e5006a4656901d2d7fb6a215c96068a0 Mon Sep 17 00:00:00 2001 From: Can Peng Date: Wed, 22 Jul 2026 16:49:09 +0800 Subject: [PATCH 0225/1328] thermal/drivers/imx: Disable clock on runtime resume failure imx_thermal_runtime_resume() enables the thermal clock before powering up the sensor and enabling measurements. If either regmap_write() fails, the function returns with the clock still enabled. This leaves the clock enable count unbalanced after a failed runtime resume. Disable the clock on those failure paths before returning the error. Fixes: 4cf2ddf16e17 ("thermal/drivers/imx: Implement runtime PM support") Cc: stable@vger.kernel.org Signed-off-by: Can Peng Signed-off-by: Daniel Lezcano Reviewed-by: Frank Li Link: https://patch.msgid.link/20260722084909.463437-1-pengcan@kylinos.cn --- drivers/thermal/imx_thermal.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/thermal/imx_thermal.c b/drivers/thermal/imx_thermal.c index 5aaacbc53478..b0e88b6a9352 100644 --- a/drivers/thermal/imx_thermal.c +++ b/drivers/thermal/imx_thermal.c @@ -832,12 +832,12 @@ static int imx_thermal_runtime_resume(struct device *dev) ret = regmap_write(map, socdata->sensor_ctrl + REG_CLR, socdata->power_down_mask); if (ret) - return ret; + goto disable_clk; ret = regmap_write(map, socdata->sensor_ctrl + REG_SET, socdata->measure_temp_mask); if (ret) - return ret; + goto disable_clk; /* * According to the temp sensor designers, it may require up to ~17us @@ -846,6 +846,11 @@ static int imx_thermal_runtime_resume(struct device *dev) usleep_range(20, 50); return 0; + +disable_clk: + clk_disable_unprepare(data->thermal_clk); + + return ret; } static const struct dev_pm_ops imx_thermal_pm_ops = { From 935ad6242c47b37380d0cb7ec366516fe11855b4 Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Wed, 24 Jun 2026 15:23:55 +0900 Subject: [PATCH 0226/1328] clk: mediatek: mt6735: Unregister PLLs on probe failure mtk_clk_register_plls() registers the apmixedsys PLL clocks manually, while clk_mt6735_apmixed_remove() unregisters them on driver removal. If devm_of_clk_add_hw_provider() fails after the PLL registration succeeds, probe returns the error directly and the remove callback is not run. This leaves the registered PLL clocks behind on the probe failure path. Unregister the PLLs in that failure branch before returning the error. Fixes: 43c04ed79189 ("clk: mediatek: Add drivers for MediaTek MT6735 main clock and reset drivers") Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/mediatek/clk-mt6735-apmixedsys.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/clk/mediatek/clk-mt6735-apmixedsys.c b/drivers/clk/mediatek/clk-mt6735-apmixedsys.c index 9e30c089a209..b6eb6a581c31 100644 --- a/drivers/clk/mediatek/clk-mt6735-apmixedsys.c +++ b/drivers/clk/mediatek/clk-mt6735-apmixedsys.c @@ -102,9 +102,12 @@ static int clk_mt6735_apmixed_probe(struct platform_device *pdev) ret = devm_of_clk_add_hw_provider(&pdev->dev, of_clk_hw_onecell_get, clk_data); - if (ret) + if (ret) { dev_err(&pdev->dev, "Failed to register clock provider: %d\n", ret); + mtk_clk_unregister_plls(apmixedsys_plls, ARRAY_SIZE(apmixedsys_plls), + clk_data); + } return ret; } From 35e5554ec14b12d31886d03de1e9fbbb13073564 Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Tue, 7 Jul 2026 19:39:34 +0800 Subject: [PATCH 0227/1328] f2fs: drop FGP_NOFS in f2fs_write_begin() This patch proposes to drop FGP_NOFS from f2fs_filemap_get_folio() in f2fs_write_begin(), I don't see there is potential deadlock issue when __filemap_get_folio() calling into filesystem reclaim interfaces, e.g. .writepages, evict_inode, shrinker. Cc: Matthew Wilcox Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/data.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c index a765fda71536..62ea74e65db8 100644 --- a/fs/f2fs/data.c +++ b/fs/f2fs/data.c @@ -3975,7 +3975,7 @@ static int f2fs_write_begin(const struct kiocb *iocb, * Will wait that below with our IO control. */ folio = f2fs_filemap_get_folio(mapping, index, - FGP_LOCK | FGP_WRITE | FGP_CREAT | FGP_NOFS, + FGP_LOCK | FGP_WRITE | FGP_CREAT, mapping_gfp_mask(mapping)); if (IS_ERR(folio)) { err = PTR_ERR(folio); From b41d9ff965f70f88b2c33089fdb26865914ca92d Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Tue, 7 Jul 2026 19:39:33 +0800 Subject: [PATCH 0228/1328] f2fs: quota: do not use GFP_NOFS in f2fs_quota_read() Commit 02117b8ae9c0 ("f2fs: Set GF_NOFS in read_cache_page_gfp while doing f2fs_quota_read") adds GFP_NOFS in f2fs_quota_read() to avoid below deadlock: - do_sys_open - vfs_open - dquot_file_open - dquot_initialize - dqget - dquot_acquire : locks &dqopt->dqio_mutex (VFS Quota Mutex) - qtree_read_dquot - f2fs_quota_read - read_mapping_page (GFP_KERNEL / allows GFP_FS) - __alloc_pages_nodemask - try_to_free_pages (Direct Reclaim) - prune_icache_sb - evict - f2fs_evict_inode - dquot_drop - dqput - dquot_commit : tries to lock &dqopt->dqio_mutex again ==> DEADLOCK (waiting for itself) As Jan Kara mentioned, quota system has fixed this issue w/ commit 537e11cdc7a6 ("quota: Prevent memory allocation recursion while holding dq_lock"), so this GFP_NOFS flag should be relic, let's use GFP_KERNEL instead. Cc: Jan Kara Cc: Matthew Wilcox Signed-off-by: Chao Yu Reviewed-by: Jan Kara Signed-off-by: Jaegeuk Kim --- fs/f2fs/super.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c index 9760e4efeffe..f41a23828508 100644 --- a/fs/f2fs/super.c +++ b/fs/f2fs/super.c @@ -3168,7 +3168,7 @@ static ssize_t f2fs_quota_read(struct super_block *sb, int type, char *data, repeat: folio = mapping_read_folio_gfp(mapping, off >> PAGE_SHIFT, - GFP_NOFS); + GFP_KERNEL); if (IS_ERR(folio)) { if (PTR_ERR(folio) == -ENOMEM) { memalloc_retry_wait(GFP_NOFS); From 3d7bca9d583793bb7d0bac0d95a24ddd2e129eed Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Mon, 6 Jul 2026 12:33:21 +0000 Subject: [PATCH 0229/1328] f2fs: embed f2fs_gc_kthread in f2fs_sb_info Instead of allocating f2fs_gc_kthread dynamically, embed it in f2fs_sb_info. This simplifies lifetime management and prepares for fixing race conditions during teardown. - __sbi_store - remount|shutdown - f2fs_stop_gc_thread - access sbi->gc_thread - sbi->gc_thread = NULL - access sbi->gc_thread->f2fs_gc_task Fixes: 52190933c37a ("f2fs: sysfs: introduce critical_task_priority") Fixes: 7950e9ac638e ("f2fs: stop gc/discard thread after fs shutdown") Cc: stable@kernel.org Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/debug.c | 4 ---- fs/f2fs/f2fs.h | 29 ++++++++++++++++++++++++++++- fs/f2fs/gc.c | 36 +++++++++++++++--------------------- fs/f2fs/gc.h | 27 +-------------------------- fs/f2fs/segment.c | 9 ++++----- fs/f2fs/super.c | 4 ++-- fs/f2fs/sysfs.c | 22 +++++++++++----------- 7 files changed, 61 insertions(+), 70 deletions(-) diff --git a/fs/f2fs/debug.c b/fs/f2fs/debug.c index af88db8fdb71..ff379aff4472 100644 --- a/fs/f2fs/debug.c +++ b/fs/f2fs/debug.c @@ -352,10 +352,6 @@ static void update_mem_info(struct f2fs_sb_info *sbi) get_cache: si->cache_mem = 0; - /* build gc */ - if (sbi->gc_thread) - si->cache_mem += sizeof(struct f2fs_gc_kthread); - /* build merge flush thread */ if (SM_I(sbi)->fcc_info) si->cache_mem += sizeof(struct flush_cmd_control); diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index 8f3e632f315c..8011bbdf2c68 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -1748,6 +1748,33 @@ struct decompress_io_ctx { #define MAX_COMPRESS_LOG_SIZE 8 #define MAX_COMPRESS_WINDOW_SIZE(log_size) ((PAGE_SIZE) << (log_size)) +struct f2fs_gc_kthread { + struct task_struct *f2fs_gc_task; + wait_queue_head_t gc_wait_queue_head; + + /* for gc sleep time */ + unsigned int urgent_sleep_time; + unsigned int min_sleep_time; + unsigned int max_sleep_time; + unsigned int no_gc_sleep_time; + + /* for changing gc mode */ + bool gc_wake; + + /* for GC_MERGE mount option */ + wait_queue_head_t fggc_wq; /* + * caller of f2fs_balance_fs() + * will wait on this wait queue. + */ + + /* for gc control for zoned devices */ + unsigned int no_zoned_gc_percent; + unsigned int boost_zoned_gc_percent; + unsigned int valid_thresh_ratio; + unsigned int boost_gc_multiple; + unsigned int boost_gc_greedy; +}; + struct f2fs_sb_info { struct super_block *sb; /* pointer to VFS super block */ struct proc_dir_entry *s_proc; /* proc entry */ @@ -1883,7 +1910,7 @@ struct f2fs_sb_info { * semaphore for GC, avoid * race between GC and GC or CP */ - struct f2fs_gc_kthread *gc_thread; /* GC thread */ + struct f2fs_gc_kthread gc_thread; /* GC thread */ struct atgc_management am; /* atgc management */ unsigned int cur_victim_sec; /* current victim section num */ unsigned int gc_mode; /* current GC state */ diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c index ffaa7ba76a1b..d04633f872ef 100644 --- a/fs/f2fs/gc.c +++ b/fs/f2fs/gc.c @@ -31,9 +31,9 @@ static unsigned int count_bits(const unsigned long *addr, static int gc_thread_func(void *data) { struct f2fs_sb_info *sbi = data; - struct f2fs_gc_kthread *gc_th = sbi->gc_thread; - wait_queue_head_t *wq = &sbi->gc_thread->gc_wait_queue_head; - wait_queue_head_t *fggc_wq = &sbi->gc_thread->fggc_wq; + struct f2fs_gc_kthread *gc_th = &sbi->gc_thread; + wait_queue_head_t *wq = &sbi->gc_thread.gc_wait_queue_head; + wait_queue_head_t *fggc_wq = &sbi->gc_thread.fggc_wq; unsigned int wait_ms; struct f2fs_gc_control gc_control = { .victim_segno = NULL_SEGNO, @@ -193,13 +193,9 @@ static int gc_thread_func(void *data) int f2fs_start_gc_thread(struct f2fs_sb_info *sbi) { - struct f2fs_gc_kthread *gc_th; + struct f2fs_gc_kthread *gc_th = &sbi->gc_thread; dev_t dev = sbi->sb->s_bdev->bd_dev; - gc_th = f2fs_kmalloc(sbi, sizeof(struct f2fs_gc_kthread), GFP_KERNEL); - if (!gc_th) - return -ENOMEM; - gc_th->urgent_sleep_time = DEF_GC_THREAD_URGENT_SLEEP_TIME; gc_th->valid_thresh_ratio = DEF_GC_THREAD_VALID_THRESH_RATIO; gc_th->boost_gc_multiple = BOOST_GC_MULTIPLE; @@ -221,16 +217,14 @@ int f2fs_start_gc_thread(struct f2fs_sb_info *sbi) gc_th->gc_wake = false; - sbi->gc_thread = gc_th; - init_waitqueue_head(&sbi->gc_thread->gc_wait_queue_head); - init_waitqueue_head(&sbi->gc_thread->fggc_wq); - sbi->gc_thread->f2fs_gc_task = kthread_run(gc_thread_func, sbi, + init_waitqueue_head(&gc_th->gc_wait_queue_head); + init_waitqueue_head(&gc_th->fggc_wq); + gc_th->f2fs_gc_task = kthread_run(gc_thread_func, sbi, "f2fs_gc-%u:%u", MAJOR(dev), MINOR(dev)); if (IS_ERR(gc_th->f2fs_gc_task)) { int err = PTR_ERR(gc_th->f2fs_gc_task); - kfree(gc_th); - sbi->gc_thread = NULL; + gc_th->f2fs_gc_task = NULL; return err; } @@ -241,14 +235,14 @@ int f2fs_start_gc_thread(struct f2fs_sb_info *sbi) void f2fs_stop_gc_thread(struct f2fs_sb_info *sbi) { - struct f2fs_gc_kthread *gc_th = sbi->gc_thread; + struct f2fs_gc_kthread *gc_th = &sbi->gc_thread; - if (!gc_th) + if (!gc_th->f2fs_gc_task) return; + kthread_stop(gc_th->f2fs_gc_task); + gc_th->f2fs_gc_task = NULL; wake_up_all(&gc_th->fggc_wq); - kfree(gc_th); - sbi->gc_thread = NULL; } static int select_gc_type(struct f2fs_sb_info *sbi, int gc_type) @@ -796,7 +790,7 @@ int f2fs_get_victim(struct f2fs_sb_info *sbi, unsigned int *result, if (one_time) { p.one_time_gc = one_time; if (has_enough_free_secs(sbi, 0, NR_PERSISTENT_LOG)) - valid_thresh_ratio = sbi->gc_thread->valid_thresh_ratio; + valid_thresh_ratio = sbi->gc_thread.valid_thresh_ratio; } retry: @@ -1807,9 +1801,9 @@ static int do_garbage_collect(struct f2fs_sb_info *sbi, if (f2fs_sb_has_blkzoned(sbi) && !has_enough_free_blocks(sbi, - sbi->gc_thread->boost_zoned_gc_percent)) + sbi->gc_thread.boost_zoned_gc_percent)) window_granularity *= - sbi->gc_thread->boost_gc_multiple; + sbi->gc_thread.boost_gc_multiple; end_segno = start_segno + window_granularity; } diff --git a/fs/f2fs/gc.h b/fs/f2fs/gc.h index 6c4d4567571e..b015742fb455 100644 --- a/fs/f2fs/gc.h +++ b/fs/f2fs/gc.h @@ -45,32 +45,7 @@ #define NR_GC_CHECKPOINT_SECS (3) /* data/node/dentry sections */ -struct f2fs_gc_kthread { - struct task_struct *f2fs_gc_task; - wait_queue_head_t gc_wait_queue_head; - /* for gc sleep time */ - unsigned int urgent_sleep_time; - unsigned int min_sleep_time; - unsigned int max_sleep_time; - unsigned int no_gc_sleep_time; - - /* for changing gc mode */ - bool gc_wake; - - /* for GC_MERGE mount option */ - wait_queue_head_t fggc_wq; /* - * caller of f2fs_balance_fs() - * will wait on this wait queue. - */ - - /* for gc control for zoned devices */ - unsigned int no_zoned_gc_percent; - unsigned int boost_zoned_gc_percent; - unsigned int valid_thresh_ratio; - unsigned int boost_gc_multiple; - unsigned int boost_gc_greedy; -}; struct gc_inode_list { struct list_head ilist; @@ -197,6 +172,6 @@ static inline bool need_to_boost_gc(struct f2fs_sb_info *sbi) { if (f2fs_sb_has_blkzoned(sbi)) return !has_enough_free_blocks(sbi, - sbi->gc_thread->boost_zoned_gc_percent); + sbi->gc_thread.boost_zoned_gc_percent); return has_enough_invalid_blocks(sbi); } diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c index d71ddb3ee918..0b706568b034 100644 --- a/fs/f2fs/segment.c +++ b/fs/f2fs/segment.c @@ -452,15 +452,14 @@ void f2fs_balance_fs(struct f2fs_sb_info *sbi, bool need) f2fs_submit_merged_write(sbi, DATA); f2fs_submit_all_merged_ipu_writes(sbi); - if (test_opt(sbi, GC_MERGE) && sbi->gc_thread && - sbi->gc_thread->f2fs_gc_task) { + if (test_opt(sbi, GC_MERGE) && sbi->gc_thread.f2fs_gc_task) { DEFINE_WAIT(wait); - prepare_to_wait(&sbi->gc_thread->fggc_wq, &wait, + prepare_to_wait(&sbi->gc_thread.fggc_wq, &wait, TASK_UNINTERRUPTIBLE); - wake_up(&sbi->gc_thread->gc_wait_queue_head); + wake_up(&sbi->gc_thread.gc_wait_queue_head); io_schedule(); - finish_wait(&sbi->gc_thread->fggc_wq, &wait); + finish_wait(&sbi->gc_thread.fggc_wq, &wait); } else { struct f2fs_gc_control gc_control = { .victim_segno = NULL_SEGNO, diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c index f41a23828508..90a47cf86378 100644 --- a/fs/f2fs/super.c +++ b/fs/f2fs/super.c @@ -2943,11 +2943,11 @@ static int __f2fs_remount(struct fs_context *fc, struct super_block *sb) if ((flags & SB_RDONLY) || (F2FS_OPTION(sbi).bggc_mode == BGGC_MODE_OFF && !test_opt(sbi, GC_MERGE))) { - if (sbi->gc_thread) { + if (sbi->gc_thread.f2fs_gc_task) { f2fs_stop_gc_thread(sbi); need_restart_gc = true; } - } else if (!sbi->gc_thread) { + } else if (!sbi->gc_thread.f2fs_gc_task) { err = f2fs_start_gc_thread(sbi); if (err) goto restore_opts; diff --git a/fs/f2fs/sysfs.c b/fs/f2fs/sysfs.c index 665687244c93..be92c05a5420 100644 --- a/fs/f2fs/sysfs.c +++ b/fs/f2fs/sysfs.c @@ -75,7 +75,7 @@ static ssize_t f2fs_sbi_show(struct f2fs_attr *a, static unsigned char *__struct_ptr(struct f2fs_sb_info *sbi, int struct_type) { if (struct_type == GC_THREAD) - return (unsigned char *)sbi->gc_thread; + return (unsigned char *)&sbi->gc_thread; else if (struct_type == SM_INFO) return (unsigned char *)SM_I(sbi); else if (struct_type == DCC_INFO) @@ -664,20 +664,20 @@ static ssize_t __sbi_store(struct f2fs_attr *a, sbi->gc_mode = GC_NORMAL; } else if (t == 1) { sbi->gc_mode = GC_URGENT_HIGH; - if (sbi->gc_thread) { - sbi->gc_thread->gc_wake = true; + if (sbi->gc_thread.f2fs_gc_task) { + sbi->gc_thread.gc_wake = true; wake_up_interruptible_all( - &sbi->gc_thread->gc_wait_queue_head); + &sbi->gc_thread.gc_wait_queue_head); wake_up_discard_thread(sbi, true); } } else if (t == 2) { sbi->gc_mode = GC_URGENT_LOW; } else if (t == 3) { sbi->gc_mode = GC_URGENT_MID; - if (sbi->gc_thread) { - sbi->gc_thread->gc_wake = true; + if (sbi->gc_thread.f2fs_gc_task) { + sbi->gc_thread.gc_wake = true; wake_up_interruptible_all( - &sbi->gc_thread->gc_wait_queue_head); + &sbi->gc_thread.gc_wait_queue_head); } } else { return -EINVAL; @@ -934,14 +934,14 @@ static ssize_t __sbi_store(struct f2fs_attr *a, if (!strcmp(a->attr.name, "gc_boost_gc_multiple")) { if (t < 1 || t > SEGS_PER_SEC(sbi)) return -EINVAL; - sbi->gc_thread->boost_gc_multiple = (unsigned int)t; + sbi->gc_thread.boost_gc_multiple = (unsigned int)t; return count; } if (!strcmp(a->attr.name, "gc_boost_gc_greedy")) { if (t > GC_GREEDY) return -EINVAL; - sbi->gc_thread->boost_gc_greedy = (unsigned int)t; + sbi->gc_thread.boost_gc_greedy = (unsigned int)t; return count; } @@ -989,8 +989,8 @@ static ssize_t __sbi_store(struct f2fs_attr *a, if (sbi->cprc_info.f2fs_issue_ckpt) set_user_nice(sbi->cprc_info.f2fs_issue_ckpt, PRIO_TO_NICE(sbi->critical_task_priority)); - if (sbi->gc_thread && sbi->gc_thread->f2fs_gc_task) - set_user_nice(sbi->gc_thread->f2fs_gc_task, + if (sbi->gc_thread.f2fs_gc_task) + set_user_nice(sbi->gc_thread.f2fs_gc_task, PRIO_TO_NICE(sbi->critical_task_priority)); return count; } From d0a481fad5c7a3a56ecf54a099651216869f4d0a Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Mon, 29 Jun 2026 11:49:18 +0000 Subject: [PATCH 0230/1328] f2fs: fix to avoid potential section-unaligned pinfile Blocks of pinfile may not aligned to section size due to wrong use on pinfile, result in heavy overhead of GC, let avoid this by adding additional check condition in f2fs_setattr(). - truncate -s 8mb pinfile : random checkpoint may persist filesize w/ inode - fallocate -o 0 -l 8mb pinfile - f2fs_fallocate - f2fs_expand_inode_data - f2fs_allocate_pinning_section - f2fs_map_blocks - f2fs_map_lock - __allocate_data_block - file_need_truncate : w/ FADVISE_TRUNC_BIT, we can expect unaligned mapping can be truncated while open() if f2fs is not umount abnormally - f2fs_map_unlock : following f2fs checkpoint and sudden power-cut - mount - open pinfile - f2fs_file_open - finish_preallocate_blocks - truncate_setsize : filesize is 8mb - f2fs_truncate : can only truncate block outside filesize, rather than truncating unaligned blocks inside filesize Fixes: f5a53edcf01e ("f2fs: support aligned pinned file") Cc: stable@kernel.org Cc: Daeho Jeong Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/file.c | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index 4b52c56d71f0..c54897a25981 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -1107,17 +1107,23 @@ int f2fs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, !IS_ALIGNED(attr->ia_size, F2FS_BLK_TO_BYTES(fi->i_cluster_size))) return -EINVAL; - /* - * To prevent scattered pin block generation, we don't allow - * smaller/equal size unaligned truncation for pinned file. - * We only support overwrite IO to pinned file, so don't - * care about larger size truncation. - */ - if (f2fs_is_pinned_file(inode) && - attr->ia_size <= i_size_read(inode) && - !IS_ALIGNED(attr->ia_size, - F2FS_BLK_TO_BYTES(CAP_BLKS_PER_SEC(sbi)))) - return -EINVAL; + + if (f2fs_is_pinned_file(inode)) { + /* + * It may break section-aligned fallocate recovery + * mechanism, so do not allow larger size truncation. + */ + if (attr->ia_size > i_size_read(inode)) + return -EINVAL; + /* + * To prevent scattered pin block generation, we don't + * allow smaller/equal size unaligned truncation for + * pinned file. + */ + else if (!IS_ALIGNED(attr->ia_size, + F2FS_BLK_TO_BYTES(CAP_BLKS_PER_SEC(sbi)))) + return -EINVAL; + } } if (is_quota_modification(idmap, inode, attr)) { From 0dab71381f1b4d12dc2056f8bd5aaa9d93ce9082 Mon Sep 17 00:00:00 2001 From: Zhaoyang Huang Date: Mon, 13 Jul 2026 19:19:44 +0800 Subject: [PATCH 0231/1328] f2fs: fix folio_nr_pages() race after put in large folio invalidate Our v6.18 based Android system is continuely suffering livelock and bad page stat as shown in[1] which related to broken xarray slot status. By investigating big folio operations within f2fs, we find below races and fix it by get the nr_pages before drop the refcount and folio_lock. f2fs_get_read_data_folio() calls f2fs_folio_put() before folio_nr_pages() when invalidating a large folio from the page cache. That unlocks the folio and drops the caller reference, leaving a window where a concurrent truncate or folio split can shrink the compound folio or free it before the invalidate range is computed. An undersized range then leaves split sub-folios in mapping->i_pages, which can later interact badly with truncate and reclaim (stale xarray entries and bad page state when folio->mapping no longer matches the mapping being truncated). [1] PID: 2594 TASK: ffffff8169b81580 CPU: 7 COMMAND: "Thread-3" #0 [ffffffc08ef2b8a0] xas_load at ffffffe52d1f42a4 #1 [ffffffc08ef2b900] find_get_entries at ffffffe52c185798 #2 [ffffffc08ef2bb60] truncate_inode_pages_range at ffffffe52c19e83c #3 [ffffffc08ef2bbc0] truncate_inode_pages_final at ffffffe52c19ec2c #4 [ffffffc08ef2bc20] f2fs_evict_inode at ffffffe52c4c8400 #5 [ffffffc08ef2bcc0] evict at ffffffe52c2de9f4 #6 [ffffffc08ef2bd00] iput at ffffffe52c2db1b4 #7 [ffffffc08ef2bd30] dentry_unlink_inode at ffffffe52c2d7204 #8 [ffffffc08ef2bd50] __dentry_kill at ffffffe52c2d3dcc #9 [ffffffc08ef2bd80] dput at ffffffe52c2d3c3c #10 [ffffffc08ef2bda0] __fput at ffffffe52c2b0a7c #11 [ffffffc08ef2bde0] ____fput at ffffffe52c2b1034 #12 [ffffffc08ef2bdf0] task_work_run at ffffffe52beea200 #13 [ffffffc08ef2be20] exit_to_user_mode_loop at ffffffe52bfbc17c #14 [ffffffc08ef2be80] el0_svc at ffffffe52d1f8e54 #15 [ffffffc08ef2beb0] el0t_64_sync_handler at ffffffe52d1f8d10 Cc: stable@kernel.org Fixes: 05e65c14ea59 ("f2fs: support large folio for immutable non-compressed case") Reviewed-by: Chao Yu Signed-off-by: Zhaoyang Huang Signed-off-by: Jaegeuk Kim --- fs/f2fs/data.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c index 62ea74e65db8..042ed8ad9cc3 100644 --- a/fs/f2fs/data.c +++ b/fs/f2fs/data.c @@ -1323,10 +1323,11 @@ struct folio *f2fs_get_read_data_folio(struct inode *inode, pgoff_t index, if (folio_test_large(folio)) { pgoff_t folio_index = mapping_align_index(mapping, index); + unsigned long nr_pages = folio_nr_pages(folio); f2fs_folio_put(folio, true); invalidate_inode_pages2_range(mapping, folio_index, - folio_index + folio_nr_pages(folio) - 1); + folio_index + nr_pages - 1); f2fs_schedule_timeout(DEFAULT_SCHEDULE_TIMEOUT); goto retry; } From cca7d3e30bf30333314e31bc70b9a739f1342167 Mon Sep 17 00:00:00 2001 From: Guanghui Yang <3497809730@qq.com> Date: Sun, 19 Jul 2026 16:45:14 +0800 Subject: [PATCH 0232/1328] f2fs: fix dentry folio leak in find_in_level find_in_level() gets a dentry folio with f2fs_find_data_folio() before calling find_in_block(). If find_in_block() returns an error, the function stores the error in res_folio and breaks out of the loop without dropping the dentry folio. This leaks the folio reference on the find_in_block() error path. Drop the dentry folio before returning the error to the caller. Fixes: 7ad08a58bf67 ("f2fs: Handle casefolding with Encryption") Cc: stable@vger.kernel.org Reviewed-by: Chao Yu Signed-off-by: Guanghui Yang <3497809730@qq.com> Signed-off-by: Jaegeuk Kim --- fs/f2fs/dir.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/f2fs/dir.c b/fs/f2fs/dir.c index a9563f7fcd88..0562ad211351 100644 --- a/fs/f2fs/dir.c +++ b/fs/f2fs/dir.c @@ -320,6 +320,7 @@ static struct f2fs_dir_entry *find_in_level(struct inode *dir, de = find_in_block(dir, dentry_folio, fname, &max_slots, use_hash); if (IS_ERR(de)) { + f2fs_folio_put(dentry_folio, false); *res_folio = ERR_CAST(de); de = NULL; break; From c12cbf56320fb633484ee0ca1fb7d68d6b64b213 Mon Sep 17 00:00:00 2001 From: Zihan Xi Date: Tue, 28 Jul 2026 01:30:32 +0800 Subject: [PATCH 0233/1328] xfrm: fix xfrm_state_construct() auth-trunc leak attach_auth_trunc() can allocate x->aalg while leaving x->props.aalgo at zero when the selected auth algorithm has no sadb_alg_id. One real case is cmac(aes). xfrm_state_construct() then treats !x->props.aalgo as "no auth algorithm attached yet" and calls attach_auth(). That overwrites x->aalg and loses the first allocation. Any later failure or teardown only frees the replacement pointer. Check whether x->aalg is already attached instead of inferring that state from x->props.aalgo. Fixes: 4447bb33f094 ("xfrm: Store aalg in xfrm_state with a user specified truncation length") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: Codex:gpt-5.4 Signed-off-by: Zihan Xi Signed-off-by: Ren Wei Signed-off-by: Steffen Klassert --- net/xfrm/xfrm_user.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c index d6db63304ba6..6266a92cf302 100644 --- a/net/xfrm/xfrm_user.c +++ b/net/xfrm/xfrm_user.c @@ -940,7 +940,7 @@ static struct xfrm_state *xfrm_state_construct(struct net *net, if ((err = attach_auth_trunc(&x->aalg, &x->props.aalgo, attrs[XFRMA_ALG_AUTH_TRUNC], extack))) goto error; - if (!x->props.aalgo) { + if (!x->aalg) { if ((err = attach_auth(&x->aalg, &x->props.aalgo, attrs[XFRMA_ALG_AUTH], extack))) goto error; From 468238ced6c39dd4927392f16cedcde5f27cd0ef Mon Sep 17 00:00:00 2001 From: Jacky Bai Date: Thu, 4 Jun 2026 21:48:01 +0800 Subject: [PATCH 0234/1328] clk: imx: Add audio PLL debugfs for K-divider control Add debugfs support for runtime tuning of the audio PLL K divider, which enables fine-grained frequency adjustments for audio PLL. This is used for: - Audio clock calibration and testing - Debugging audio synchronization issues Two debug interfaces are exported to userspace: - delta_k: It is used to adjust the K divider in PLL based on small steps - pll_parameter: It is used for get PLL's current M-divider, P-divider, S-divider & K-divider setting in PLL register Signed-off-by: Jacky Bai Reviewed-by: Peng Fan Reviewed-by: Abel Vesa Link: https://patch.msgid.link/20260604-imx8m_pll_debugfs-v3-1-4e331ebc85d7@nxp.com Signed-off-by: Abel Vesa --- drivers/clk/imx/clk-imx8mm.c | 6 ++ drivers/clk/imx/clk-pll14xx.c | 119 +++++++++++++++++++++++++++++++++- drivers/clk/imx/clk.h | 1 + 3 files changed, 125 insertions(+), 1 deletion(-) diff --git a/drivers/clk/imx/clk-imx8mm.c b/drivers/clk/imx/clk-imx8mm.c index 319af4deec01..89d442415a01 100644 --- a/drivers/clk/imx/clk-imx8mm.c +++ b/drivers/clk/imx/clk-imx8mm.c @@ -300,6 +300,7 @@ static int imx8mm_clocks_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; + struct clk_hw *audio_pll_hws[2]; void __iomem *base; int ret; @@ -610,6 +611,11 @@ static int imx8mm_clocks_probe(struct platform_device *pdev) imx_register_uart_clocks(); + /* Add debug interface for audio PLLs */ + audio_pll_hws[0] = hws[IMX8MM_AUDIO_PLL1]; + audio_pll_hws[1] = hws[IMX8MM_AUDIO_PLL2]; + imx_audio_pll_debug_init(audio_pll_hws, ARRAY_SIZE(audio_pll_hws)); + return 0; unregister_hws: diff --git a/drivers/clk/imx/clk-pll14xx.c b/drivers/clk/imx/clk-pll14xx.c index 39600ee22be3..b6f1cc9f5700 100644 --- a/drivers/clk/imx/clk-pll14xx.c +++ b/drivers/clk/imx/clk-pll14xx.c @@ -8,11 +8,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include "clk.h" @@ -40,6 +42,8 @@ struct clk_pll14xx { enum imx_pll14xx_type type; const struct imx_pll14xx_rate_table *rate_table; int rate_count; + s16 delta_k; + spinlock_t lock; }; #define to_clk_pll14xx(_hw) container_of(_hw, struct clk_pll14xx, hw) @@ -134,6 +138,7 @@ static void imx_pll14xx_calc_settings(struct clk_pll14xx *pll, unsigned long rat u32 pll_div_ctl0, pll_div_ctl1; int mdiv, pdiv, sdiv, kdiv; long fout, rate_min, rate_max, dist, best = LONG_MAX; + unsigned long flags; const struct imx_pll14xx_rate_table *tt; /* @@ -161,11 +166,16 @@ static void imx_pll14xx_calc_settings(struct clk_pll14xx *pll, unsigned long rat return; } + spin_lock_irqsave(&pll->lock, flags); + pll_div_ctl0 = readl_relaxed(pll->base + DIV_CTL0); + pll_div_ctl1 = readl_relaxed(pll->base + DIV_CTL1); + + spin_unlock_irqrestore(&pll->lock, flags); + mdiv = FIELD_GET(MDIV_MASK, pll_div_ctl0); pdiv = FIELD_GET(PDIV_MASK, pll_div_ctl0); sdiv = FIELD_GET(SDIV_MASK, pll_div_ctl0); - pll_div_ctl1 = readl_relaxed(pll->base + DIV_CTL1); /* Then see if we can get the desired rate by only adjusting kdiv (glitch free) */ rate_min = pll14xx_calc_rate(pll, mdiv, pdiv, sdiv, KDIV_MIN, prate); @@ -361,11 +371,14 @@ static int clk_pll1443x_set_rate(struct clk_hw *hw, unsigned long drate, { struct clk_pll14xx *pll = to_clk_pll14xx(hw); struct imx_pll14xx_rate_table rate; + unsigned long flags; u32 gnrl_ctl, div_ctl0; int ret; imx_pll14xx_calc_settings(pll, drate, prate, &rate); + spin_lock_irqsave(&pll->lock, flags); + div_ctl0 = readl_relaxed(pll->base + DIV_CTL0); if (!clk_pll14xx_mp_change(&rate, div_ctl0)) { @@ -377,6 +390,8 @@ static int clk_pll1443x_set_rate(struct clk_hw *hw, unsigned long drate, writel_relaxed(FIELD_PREP(KDIV_MASK, rate.kdiv), pll->base + DIV_CTL1); + spin_unlock_irqrestore(&pll->lock, flags); + return 0; } @@ -396,6 +411,8 @@ static int clk_pll1443x_set_rate(struct clk_hw *hw, unsigned long drate, writel_relaxed(FIELD_PREP(KDIV_MASK, rate.kdiv), pll->base + DIV_CTL1); + spin_unlock_irqrestore(&pll->lock, flags); + /* * According to SPEC, t3 - t2 need to be greater than * 1us and 1/FREF, respectively. @@ -508,6 +525,8 @@ struct clk_hw *imx_dev_clk_hw_pll14xx(struct device *dev, const char *name, if (!pll) return ERR_PTR(-ENOMEM); + spin_lock_init(&pll->lock); + init.name = name; init.flags = pll_clk->flags; init.parent_names = &parent_name; @@ -551,3 +570,101 @@ struct clk_hw *imx_dev_clk_hw_pll14xx(struct device *dev, const char *name, return hw; } EXPORT_SYMBOL_GPL(imx_dev_clk_hw_pll14xx); + +/* + * Debugfs interface for Audio PLL runtime monitoring and control + * + * This interface allows dynamic adjustment of the Audio PLL + * K-divider for precise frequency tuning, particularly useful + * for audio applications. + * + * examples for the usage of the two interfaces: + * 1): Get the current PLL setting of dividers + * cat /sys/kernel/debug/audio_pll_monitor/audio_pll1/pll_parameter + * + * 2): Adjust the K-divider by a small delta_k + * echo 1 > /sys/kernel/debug/audio_pll_monitor/audio_pll1/delta_k; + */ +#ifdef CONFIG_DEBUG_FS +static int pll_delta_k_get(void *data, u64 *val) +{ + struct clk_pll14xx *pll = to_clk_pll14xx(data); + *val = pll->delta_k; + return 0; +} + +static int pll_delta_k_set(void *data, u64 val) +{ + struct clk_pll14xx *pll = to_clk_pll14xx(data); + unsigned long flags; + u32 div_ctl1; + s16 kdiv, delta_k; + + delta_k = (s16)clamp_t(s64, val, KDIV_MIN, KDIV_MAX); + + spin_lock_irqsave(&pll->lock, flags); + + pll->delta_k = delta_k; + + div_ctl1 = readl_relaxed(pll->base + DIV_CTL1); + kdiv = (s16)FIELD_GET(KDIV_MASK, div_ctl1); + kdiv = (s16)clamp_t(s32, (s32)kdiv + delta_k, KDIV_MIN, KDIV_MAX); + writel_relaxed(FIELD_PREP(KDIV_MASK, kdiv), pll->base + DIV_CTL1); + + spin_unlock_irqrestore(&pll->lock, flags); + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE_SIGNED(delta_k_fops, pll_delta_k_get, pll_delta_k_set, "%lld\n"); + +static int pll_setting_show(struct seq_file *s, void *data) +{ + struct clk_pll14xx *pll = to_clk_pll14xx(s->private); + unsigned long flags; + u32 div_ctl0, div_ctl1; + u32 mdiv, pdiv, sdiv, kdiv; + + spin_lock_irqsave(&pll->lock, flags); + + div_ctl0 = readl_relaxed(pll->base + DIV_CTL0); + div_ctl1 = readl_relaxed(pll->base + DIV_CTL1); + + spin_unlock_irqrestore(&pll->lock, flags); + + mdiv = FIELD_GET(MDIV_MASK, div_ctl0); + pdiv = FIELD_GET(PDIV_MASK, div_ctl0); + sdiv = FIELD_GET(SDIV_MASK, div_ctl0); + kdiv = FIELD_GET(KDIV_MASK, div_ctl1); + + seq_printf(s, "Mdiv: 0x%x; Pdiv: 0x%x; Sdiv: 0x%x; Kdiv: 0x%x\n", + mdiv, pdiv, sdiv, kdiv); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(pll_setting); + +void imx_audio_pll_debug_init(struct clk_hw *hws[], unsigned int num_plls) +{ + struct dentry *rootdir, *audio_pll_dir; + const char *pll_name; + int i; + + rootdir = debugfs_create_dir("audio_pll_monitor", NULL); + + for (i = 0; i < num_plls; i++) { + if (!IS_ERR_OR_NULL(hws[i])) { + pll_name = clk_hw_get_name(hws[i]); + audio_pll_dir = debugfs_create_dir(pll_name, rootdir); + debugfs_create_file_unsafe("delta_k", 0600, audio_pll_dir, + hws[i], &delta_k_fops); + debugfs_create_file("pll_parameter", 0444, audio_pll_dir, + hws[i], &pll_setting_fops); + } + } +} +#else /* !CONFIG_DEBUG_FS */ +void imx_audio_pll_debug_init(struct clk_hw *hws[], unsigned int num_plls) +{ +} +#endif /* CONFIG_DEBUG_FS */ +EXPORT_SYMBOL_GPL(imx_audio_pll_debug_init); diff --git a/drivers/clk/imx/clk.h b/drivers/clk/imx/clk.h index aa5202f284f3..40bb41e353f9 100644 --- a/drivers/clk/imx/clk.h +++ b/drivers/clk/imx/clk.h @@ -487,4 +487,5 @@ struct clk_hw *imx_clk_gpr_mux(const char *name, const char *compatible, u32 reg, const char **parent_names, u8 num_parents, const u32 *mux_table, u32 mask); +void imx_audio_pll_debug_init(struct clk_hw *hws[], unsigned int num_plls); #endif From e89485455ac49925565061733c0d73d568333fc0 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sat, 4 Jul 2026 23:01:41 +0800 Subject: [PATCH 0235/1328] clk: imx: imx8qxp-lpcg: add missing MODULE_DEVICE_TABLE() The driver has a match table for the of bus wired into its driver structure, but the table is not exported with MODULE_DEVICE_TABLE(). Add the missing MODULE_DEVICE_TABLE() entry so module alias information is generated for automatic module loading. This is a source-level fix. It does not claim dynamic hardware reproduction; the evidence is the driver-owned match table, its use by the driver registration structure, and the missing module alias publication. Signed-off-by: Pengpeng Hou Reviewed-by: Frank Li Reviewed-by: Brian Masney Reviewed-by: Peng Fan Link: https://patch.msgid.link/20260704150344.59563-1-pengpeng@iscas.ac.cn Signed-off-by: Abel Vesa --- drivers/clk/imx/clk-imx8qxp-lpcg.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/clk/imx/clk-imx8qxp-lpcg.c b/drivers/clk/imx/clk-imx8qxp-lpcg.c index 1dae3410ee99..f0cf6cf91899 100644 --- a/drivers/clk/imx/clk-imx8qxp-lpcg.c +++ b/drivers/clk/imx/clk-imx8qxp-lpcg.c @@ -354,6 +354,7 @@ static const struct of_device_id imx8qxp_lpcg_match[] = { { .compatible = "fsl,imx8qxp-lpcg", NULL }, { /* sentinel */ } }; +MODULE_DEVICE_TABLE(of, imx8qxp_lpcg_match); static struct platform_driver imx8qxp_lpcg_clk_driver = { .driver = { From cd21fdcbef3fb32136f72075ab668b075423b3f6 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 5 Jul 2026 08:17:05 +0800 Subject: [PATCH 0236/1328] clk: imx: imx8qxp: add missing MODULE_DEVICE_TABLE() The driver has a match table for the of bus wired into its driver structure, but the table is not exported with MODULE_DEVICE_TABLE(). Add the missing MODULE_DEVICE_TABLE() entry so module alias information is generated for automatic module loading. This is a source-level fix. It does not claim dynamic hardware reproduction; the evidence is the driver-owned match table, its use by the driver registration structure, and the missing module alias publication. Signed-off-by: Pengpeng Hou Reviewed-by: Brian Masney Link: https://patch.msgid.link/20260705001705.70400-1-pengpeng@iscas.ac.cn Signed-off-by: Abel Vesa --- drivers/clk/imx/clk-imx8qxp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/clk/imx/clk-imx8qxp.c b/drivers/clk/imx/clk-imx8qxp.c index c781425a005e..1f6d9f45dad7 100644 --- a/drivers/clk/imx/clk-imx8qxp.c +++ b/drivers/clk/imx/clk-imx8qxp.c @@ -337,6 +337,7 @@ static const struct of_device_id imx8qxp_match[] = { { .compatible = "fsl,imx8qm-clk", &imx_clk_scu_rsrc_imx8qm, }, { /* sentinel */ } }; +MODULE_DEVICE_TABLE(of, imx8qxp_match); static struct platform_driver imx8qxp_clk_driver = { .driver = { From 9475ebe129329817e5a1ea9e0c4f945bde993bb5 Mon Sep 17 00:00:00 2001 From: Herve Codina Date: Tue, 30 Jun 2026 12:30:50 +0200 Subject: [PATCH 0237/1328] clk: lan966x: Add MCHP_LAN966X_PCI dependency The lan966x clock controller depends on the LAN969x architecture or the LAN966x SoC. This clock controller can be used by the LAN966x PCI device and so it needs to be available when the LAN966x PCI device is enabled. Signed-off-by: Herve Codina Acked-by: Stephen Boyd Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index 1717ce75a907..682840e67798 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -278,7 +278,7 @@ config COMMON_CLK_LAN966X tristate "Generic Clock Controller driver for LAN966X SoC" depends on HAS_IOMEM depends on OF - depends on SOC_LAN966 || ARCH_LAN969X || COMPILE_TEST + depends on SOC_LAN966 || ARCH_LAN969X || MCHP_LAN966X_PCI || COMPILE_TEST help This driver provides support for Generic Clock Controller(GCK) on LAN966X SoC. GCK generates and supplies clock to various peripherals From d1828369fafb01678761de5712e38dbf6cfa7e28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20L=C3=B6bl?= Date: Wed, 1 Jul 2026 16:51:00 +0200 Subject: [PATCH 0238/1328] dt-bindings: clock: si544: add si549 compatible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adding Si549 compatible, which uses different internal oscillator frequency. Like in Si544 case, there are three types ending with latters a,b and c. Each with different maximum output frequency. Also fix datasheet links after Silicon Labs acquisition by Skyworks Solutions. Signed-off-by: Pavel Löbl Acked-by: Conor Dooley Signed-off-by: Brian Masney --- .../devicetree/bindings/clock/silabs,si544.yaml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/clock/silabs,si544.yaml b/Documentation/devicetree/bindings/clock/silabs,si544.yaml index f87e71867108..218f5abdef26 100644 --- a/Documentation/devicetree/bindings/clock/silabs,si544.yaml +++ b/Documentation/devicetree/bindings/clock/silabs,si544.yaml @@ -4,7 +4,7 @@ $id: http://devicetree.org/schemas/clock/silabs,si544.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Silicon Labs SI514/SI544 clock generator +title: Silicon Labs SI514/SI544/SI549 clock generator maintainers: - Mike Looijmans @@ -13,8 +13,9 @@ description: > Silicon Labs 514/544 programmable I2C clock generator. Details about the device can be found in the datasheet: - https://www.silabs.com/Support%20Documents/TechnicalDocs/si514.pdf - https://www.silabs.com/documents/public/data-sheets/si544-datasheet.pdf + https://www.skyworksinc.com/-/media/Skyworks/SL/documents/public/data-sheets/Si514.pdf + https://www.skyworksinc.com/-/media/Skyworks/SL/documents/public/data-sheets/si544-datasheet.pdf + https://www.skyworksinc.com/-/media/Skyworks/SL/documents/public/data-sheets/si549-datasheet.pdf properties: compatible: @@ -23,6 +24,9 @@ properties: - silabs,si544a - silabs,si544b - silabs,si544c + - silabs,si549a + - silabs,si549b + - silabs,si549c reg: maxItems: 1 From bdba63b59897e4f79eafd09601fa76a14c680b88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20L=C3=B6bl?= Date: Wed, 1 Jul 2026 16:51:01 +0200 Subject: [PATCH 0239/1328] clk: si544: add support for si549 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for Si549, which only differs from Si544 in internal oscilator frequency. Si549 also comes in three types A,B and C. Each having different maximum output frequency. This patch also fixes maximum frequency of Si544 C. Which should be 325MHz according to datasheet. Signed-off-by: Pavel Löbl Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/Kconfig | 6 +-- drivers/clk/clk-si544.c | 82 ++++++++++++++++++++++++++++++++--------- 2 files changed, 68 insertions(+), 20 deletions(-) diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index 682840e67798..b80c957b0040 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -155,12 +155,12 @@ config COMMON_CLK_SI514 generator. config COMMON_CLK_SI544 - tristate "Clock driver for SiLabs 544 devices" + tristate "Clock driver for SiLabs 544 and compatible devices" depends on I2C select REGMAP_I2C help - This driver supports the Silicon Labs 544 programmable clock - generator. + This driver supports the Silicon Labs 544/549 programmable clock + generators. config COMMON_CLK_SI570 tristate "Clock driver for SiLabs 570 and compatible devices" diff --git a/drivers/clk/clk-si544.c b/drivers/clk/clk-si544.c index 09c06ecec1a5..b13aeead9c02 100644 --- a/drivers/clk/clk-si544.c +++ b/drivers/clk/clk-si544.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Driver for Silicon Labs Si544 Programmable Oscillator + * Driver for Silicon Labs Si544/Si549 Programmable Oscillator * Copyright (C) 2018 Topic Embedded Products * Author: Mike Looijmans */ @@ -40,7 +40,9 @@ #define SI544_MIN_FREQ 200000U /* Si544 Internal oscillator runs at 55.05 MHz */ -#define FXO 55050000U +#define SI544_XO_FREQ 55050000U +/* Si549 Internal oscilator runs at 152.60 MHz */ +#define SI549_XO_FREQ 152600000U /* VCO range is 10.8 .. 12.1 GHz, max depends on speed grade */ #define FVCO_MIN 10800000000ULL @@ -56,11 +58,16 @@ #define DELTA_M_FRAC_NUM 19 #define DELTA_M_FRAC_DEN 20000 +struct si544_clk_desc { + unsigned long max_freq; + unsigned long xo_freq; +}; + struct clk_si544 { struct clk_hw hw; struct regmap *regmap; struct i2c_client *i2c_client; - unsigned long max_freq; + const struct si544_clk_desc *chip_info; }; #define to_clk_si544(_hw) container_of(_hw, struct clk_si544, hw) @@ -79,6 +86,7 @@ struct clk_si544_muldiv { u16 hs_div; u8 ls_div_bits; s32 delta_m; + u32 xo_freq; }; /* Enables or disables the output driver */ @@ -145,6 +153,8 @@ static int si544_get_muldiv(struct clk_si544 *data, settings->delta_m = reg[0] << 8 | reg[1] << 16 | reg[2] << 24; settings->delta_m >>= 8; + settings->xo_freq = data->chip_info->xo_freq; + return 0; } @@ -193,7 +203,7 @@ static bool is_valid_frequency(const struct clk_si544 *data, if (frequency < SI544_MIN_FREQ) return false; - return frequency <= data->max_freq; + return frequency <= data->chip_info->max_freq; } /* Calculate divider settings for a given frequency */ @@ -201,6 +211,7 @@ static int si544_calc_muldiv(struct clk_si544_muldiv *settings, unsigned long frequency) { u64 vco; + u32 fxo = settings->xo_freq; u32 ls_freq; u32 tmp; u8 res; @@ -238,13 +249,13 @@ static int si544_calc_muldiv(struct clk_si544_muldiv *settings, vco = (u64)ls_freq * settings->hs_div; /* Calculate the integer part of the feedback divider */ - tmp = do_div(vco, FXO); + tmp = do_div(vco, fxo); settings->fb_div_int = vco; /* And the fractional bits using the remainder */ vco = (u64)tmp << 32; - vco += FXO / 2; /* Round to nearest multiple */ - do_div(vco, FXO); + vco += fxo / 2; /* Round to nearest multiple */ + do_div(vco, fxo); settings->fb_div_frac = vco; /* Reset the frequency adjustment */ @@ -258,15 +269,16 @@ static unsigned long si544_calc_center_rate( const struct clk_si544_muldiv *settings) { u32 d = settings->hs_div * BIT(settings->ls_div_bits); + u32 fxo = settings->xo_freq; u64 vco; /* Calculate VCO from the fractional part */ - vco = (u64)settings->fb_div_frac * FXO; - vco += (FXO / 2); + vco = (u64)settings->fb_div_frac * fxo; + vco += (fxo / 2); vco >>= 32; /* Add the integer part of the VCO frequency */ - vco += (u64)settings->fb_div_int * FXO; + vco += (u64)settings->fb_div_int * fxo; /* Apply divider to obtain the generated frequency */ do_div(vco, d); @@ -446,7 +458,7 @@ static int si544_probe(struct i2c_client *client) init.num_parents = 0; data->hw.init = &init; data->i2c_client = client; - data->max_freq = (uintptr_t)i2c_get_match_data(client); + data->chip_info = i2c_get_match_data(client); if (of_property_read_string(client->dev.of_node, "clock-output-names", &init.name)) @@ -478,18 +490,54 @@ static int si544_probe(struct i2c_client *client) return 0; } +static const struct si544_clk_desc clk_si544a_info = { + .xo_freq = SI544_XO_FREQ, + .max_freq = 1500000000, +}; + +static const struct si544_clk_desc clk_si544b_info = { + .xo_freq = SI544_XO_FREQ, + .max_freq = 800000000, +}; + +static const struct si544_clk_desc clk_si544c_info = { + .xo_freq = SI544_XO_FREQ, + .max_freq = 325000000, +}; + +static const struct si544_clk_desc clk_si549a_info = { + .xo_freq = SI549_XO_FREQ, + .max_freq = 1500000000, +}; + +static const struct si544_clk_desc clk_si549b_info = { + .xo_freq = SI549_XO_FREQ, + .max_freq = 800000000, +}; + +static const struct si544_clk_desc clk_si549c_info = { + .xo_freq = SI549_XO_FREQ, + .max_freq = 325000000, +}; + static const struct i2c_device_id si544_id[] = { - { "si544a", 1500000000 }, - { "si544b", 800000000 }, - { "si544c", 350000000 }, + { "si544a", (kernel_ulong_t)&clk_si544a_info }, + { "si544b", (kernel_ulong_t)&clk_si544b_info }, + { "si544c", (kernel_ulong_t)&clk_si544c_info }, + { "si549a", (kernel_ulong_t)&clk_si549a_info }, + { "si549b", (kernel_ulong_t)&clk_si549b_info }, + { "si549c", (kernel_ulong_t)&clk_si549c_info }, { } }; MODULE_DEVICE_TABLE(i2c, si544_id); static const struct of_device_id clk_si544_of_match[] = { - { .compatible = "silabs,si544a", .data = (void *)1500000000 }, - { .compatible = "silabs,si544b", .data = (void *)800000000 }, - { .compatible = "silabs,si544c", .data = (void *)350000000 }, + { .compatible = "silabs,si544a", .data = &clk_si544a_info }, + { .compatible = "silabs,si544b", .data = &clk_si544b_info }, + { .compatible = "silabs,si544c", .data = &clk_si544c_info }, + { .compatible = "silabs,si549a", .data = &clk_si549a_info }, + { .compatible = "silabs,si549b", .data = &clk_si549b_info }, + { .compatible = "silabs,si549c", .data = &clk_si549c_info }, { } }; MODULE_DEVICE_TABLE(of, clk_si544_of_match); From 096ab64a0e69460bfb3557d09e17d2933280a1c6 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sat, 4 Jul 2026 20:44:50 +0800 Subject: [PATCH 0240/1328] clk: mediatek: mt6735-vdecsys: add missing MODULE_DEVICE_TABLE() The driver has an OF match table wired to .of_match_table, but does not export the table with MODULE_DEVICE_TABLE(). Add the missing MODULE_DEVICE_TABLE(of, ...) entry so module alias information is generated for OF based module autoloading. This is a source-level fix. It does not claim dynamic hardware reproduction; the evidence is the driver-owned match table, its use by the platform driver, and the missing module alias publication. Signed-off-by: Pengpeng Hou Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/mediatek/clk-mt6735-vdecsys.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/clk/mediatek/clk-mt6735-vdecsys.c b/drivers/clk/mediatek/clk-mt6735-vdecsys.c index 8817085fc1db..d53ebf92e878 100644 --- a/drivers/clk/mediatek/clk-mt6735-vdecsys.c +++ b/drivers/clk/mediatek/clk-mt6735-vdecsys.c @@ -63,6 +63,7 @@ static const struct of_device_id of_match_mt6735_vdecsys[] = { { .compatible = "mediatek,mt6735-vdecsys", .data = &vdecsys_clks }, { /* sentinel */ } }; +MODULE_DEVICE_TABLE(of, of_match_mt6735_vdecsys); static struct platform_driver clk_mt6735_vdecsys = { .probe = mtk_clk_simple_probe, From 36cf8d279ff90835f4b002dec28eedb059554842 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sat, 4 Jul 2026 20:48:08 +0800 Subject: [PATCH 0241/1328] clk: mediatek: mt6735-vencsys: add missing MODULE_DEVICE_TABLE() The driver has an OF match table wired to .of_match_table, but does not export the table with MODULE_DEVICE_TABLE(). Add the missing MODULE_DEVICE_TABLE(of, ...) entry so module alias information is generated for OF based module autoloading. This is a source-level fix. It does not claim dynamic hardware reproduction; the evidence is the driver-owned match table, its use by the platform driver, and the missing module alias publication. Signed-off-by: Pengpeng Hou Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/mediatek/clk-mt6735-vencsys.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/clk/mediatek/clk-mt6735-vencsys.c b/drivers/clk/mediatek/clk-mt6735-vencsys.c index 8dec7f98492a..64ba9da9fd42 100644 --- a/drivers/clk/mediatek/clk-mt6735-vencsys.c +++ b/drivers/clk/mediatek/clk-mt6735-vencsys.c @@ -37,6 +37,7 @@ static const struct of_device_id of_match_mt6735_vencsys[] = { { .compatible = "mediatek,mt6735-vencsys", .data = &vencsys_clks }, { /* sentinel */ } }; +MODULE_DEVICE_TABLE(of, of_match_mt6735_vencsys); static struct platform_driver clk_mt6735_vencsys = { .probe = mtk_clk_simple_probe, From 23454c4635b9f01af303118cbffd8cb3b3b57b6b Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Sun, 5 Jul 2026 18:35:24 +0200 Subject: [PATCH 0242/1328] clk: mediatek: remove unneeded 'fast_io' parameter in regmap_config When using MMIO with regmap, fast_io is implied. No need to set it again. Signed-off-by: Wolfram Sang Reviewed-by: Brian Masney Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Brian Masney --- drivers/clk/mediatek/clk-mt8196-vlpckgen.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/clk/mediatek/clk-mt8196-vlpckgen.c b/drivers/clk/mediatek/clk-mt8196-vlpckgen.c index 7dcc164627c5..ee88dc4a231a 100644 --- a/drivers/clk/mediatek/clk-mt8196-vlpckgen.c +++ b/drivers/clk/mediatek/clk-mt8196-vlpckgen.c @@ -629,7 +629,6 @@ static const struct regmap_config vlpckgen_regmap_config = { .val_bits = 32, .reg_stride = 4, .max_register = 0x1000, - .fast_io = true, }; static int clk_mt8196_vlp_probe(struct platform_device *pdev) From 584dfca970a256fb4af0121017862a9259cc019e Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Wed, 8 Jul 2026 10:40:44 +0200 Subject: [PATCH 0243/1328] clk: en7523: reset PCIE HB on init for AN7581 It was reported that PCIE HB should be reset for AN7581 or some instability or link training issue will be present on warm boot scenario. Reset PCIE HB on clk HW init to handle warm boot problem with PCIe. Signed-off-by: Christian Marangi Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/clk-en7523.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/clk/clk-en7523.c b/drivers/clk/clk-en7523.c index 1ab0e2eca5d3..c450d89bfa96 100644 --- a/drivers/clk/clk-en7523.c +++ b/drivers/clk/clk-en7523.c @@ -57,6 +57,7 @@ #define REG_RST_CTRL2 0x830 #define REG_RST_CTRL1 0x834 +#define REG_PCIE_HB_RST BIT(29) #define EN751221_REG_RST_DMT 0x84 #define EN751221_REG_RST_USB 0xec @@ -853,6 +854,12 @@ static int en7581_clk_hw_init(struct platform_device *pdev, val = readl(base + REG_NP_SCU_PCIC); writel(val | 3, base + REG_NP_SCU_PCIC); + val = readl(base + REG_RST_CTRL1); + val |= REG_PCIE_HB_RST; + writel(val, base + REG_RST_CTRL1); + val &= ~REG_PCIE_HB_RST; + writel(val, base + REG_RST_CTRL1); + return en7581_reset_register(&pdev->dev, base, en7581_rst_map, ARRAY_SIZE(en7581_rst_map), en7581_rst_ofs); From f726279f5eab813f9a8b6f38ddf2a4b062d038ff Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Wed, 8 Jul 2026 19:20:58 +0800 Subject: [PATCH 0244/1328] clk: tegra: tegra124-emc: put EMC node on register failure tegra124_clk_register_emc() stores a device node reference returned by of_parse_phandle() in tegra->emc_node. If clk_register() fails, the function returns an error before that reference can be consumed and released by the normal runtime path. The tegra_clk_emc object is freed on this failure path, but freeing the object does not drop the OF node reference stored in it. Drop the EMC node reference before freeing the tegra_clk_emc object. of_node_put() is safe for a NULL node, so this also covers the case where the phandle is absent. Fixes: 2db04f16b589 ("clk: tegra: Add EMC clock driver") Signed-off-by: Guangshuo Li Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/tegra/clk-tegra124-emc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/clk/tegra/clk-tegra124-emc.c b/drivers/clk/tegra/clk-tegra124-emc.c index f3b2c96fdcfc..94ac24ea1e6a 100644 --- a/drivers/clk/tegra/clk-tegra124-emc.c +++ b/drivers/clk/tegra/clk-tegra124-emc.c @@ -537,6 +537,7 @@ struct clk *tegra124_clk_register_emc(void __iomem *base, struct device_node *np clk = clk_register(NULL, &tegra->hw); if (IS_ERR(clk)) { + of_node_put(tegra->emc_node); kfree(tegra); return clk; } From 540d91480bcb1b28a62d7023aa70947ea44c55b9 Mon Sep 17 00:00:00 2001 From: Louis-Alexis Eyraud Date: Thu, 9 Jul 2026 14:23:00 +0200 Subject: [PATCH 0245/1328] clk: mediatek: pllfh: Fix IO remapping leak in register_pllfhs error path When mtk_clk_register_pllfhs function fails to register a PLL, it unregisters all PLLs and cleans up itself in its error path before returning, so the function callers don't need to do it. But contrary to mtk_clk_unregister_pllfhs function, that does almost the same sequence, it does not free the IO memory mapped on fhctl node, leading to a leak. Fix this leak by factorizing the cleanup sequence in a new private function and use it both mtk_clk_register_pllfhs and mtk_clk_unregister_pllfhs functions. Also, change the loop index start value to avoid the -1 operation on index at each loop. Fixes: d7964de8a8ea ("clk: mediatek: Add new clock driver to handle FHCTL hardware") Signed-off-by: Louis-Alexis Eyraud Reviewed-by: Brian Masney Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Brian Masney --- drivers/clk/mediatek/clk-pllfh.c | 98 ++++++++++++++++---------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/drivers/clk/mediatek/clk-pllfh.c b/drivers/clk/mediatek/clk-pllfh.c index aa95cd9197b3..6249fb87b1f5 100644 --- a/drivers/clk/mediatek/clk-pllfh.c +++ b/drivers/clk/mediatek/clk-pllfh.c @@ -197,12 +197,56 @@ static void mtk_clk_unregister_pllfh(struct clk_hw *hw) kfree(fh); } +static void mtk_clk_cleanup_pllfhs(void __iomem *iomem_base, + const struct mtk_pll_data *plls, int num_plls, + void __iomem *iomem_fhctl_base, + struct mtk_pllfh_data *pllfhs, int num_fhs, + struct clk_hw_onecell_data *clk_data) +{ + void __iomem *base = iomem_base; + void __iomem *fhctl_base = iomem_fhctl_base; + int i; + + for (i = num_plls - 1; i >= 0; i--) { + const struct mtk_pll_data *pll = &plls[i]; + struct mtk_pllfh_data *pllfh; + bool use_fhctl; + + if (IS_ERR_OR_NULL(clk_data->hws[pll->id])) + continue; + + pllfh = get_pllfh_by_id(pllfhs, num_fhs, pll->id); + use_fhctl = fhctl_is_supported_and_enabled(pllfh); + + if (!base) + base = mtk_clk_pll_get_base(clk_data->hws[pll->id], + pll); + + if (use_fhctl) { + if (!fhctl_base) + fhctl_base = pllfh->state.base; + mtk_clk_unregister_pllfh(clk_data->hws[pll->id]); + } else { + mtk_clk_unregister_pll(clk_data->hws[pll->id]); + } + + clk_data->hws[pll->id] = ERR_PTR(-ENOENT); + } + + if (fhctl_base) + iounmap(fhctl_base); + + if (base) + iounmap(base); +} + + int mtk_clk_register_pllfhs(struct device *dev, const struct mtk_pll_data *plls, int num_plls, struct mtk_pllfh_data *pllfhs, int num_fhs, struct clk_hw_onecell_data *clk_data) { - void __iomem *base; + void __iomem *base, *fhctl_base = NULL; int i; struct clk_hw *hw; @@ -238,24 +282,8 @@ int mtk_clk_register_pllfhs(struct device *dev, return 0; err: - while (--i >= 0) { - const struct mtk_pll_data *pll = &plls[i]; - struct mtk_pllfh_data *pllfh; - bool use_fhctl; - - pllfh = get_pllfh_by_id(pllfhs, num_fhs, pll->id); - use_fhctl = fhctl_is_supported_and_enabled(pllfh); - - if (use_fhctl) - mtk_clk_unregister_pllfh(clk_data->hws[pll->id]); - else - mtk_clk_unregister_pll(clk_data->hws[pll->id]); - - clk_data->hws[pll->id] = ERR_PTR(-ENOENT); - } - - iounmap(base); - + mtk_clk_cleanup_pllfhs(base, plls, i, fhctl_base, pllfhs, num_fhs, + clk_data); return PTR_ERR(hw); } EXPORT_SYMBOL_GPL(mtk_clk_register_pllfhs); @@ -264,38 +292,10 @@ void mtk_clk_unregister_pllfhs(const struct mtk_pll_data *plls, int num_plls, struct mtk_pllfh_data *pllfhs, int num_fhs, struct clk_hw_onecell_data *clk_data) { - void __iomem *base = NULL, *fhctl_base = NULL; - int i; - if (!clk_data) return; - for (i = num_plls; i > 0; i--) { - const struct mtk_pll_data *pll = &plls[i - 1]; - struct mtk_pllfh_data *pllfh; - bool use_fhctl; - - if (IS_ERR_OR_NULL(clk_data->hws[pll->id])) - continue; - - pllfh = get_pllfh_by_id(pllfhs, num_fhs, pll->id); - use_fhctl = fhctl_is_supported_and_enabled(pllfh); - - if (use_fhctl) { - fhctl_base = pllfh->state.base; - mtk_clk_unregister_pllfh(clk_data->hws[pll->id]); - } else { - base = mtk_clk_pll_get_base(clk_data->hws[pll->id], - pll); - mtk_clk_unregister_pll(clk_data->hws[pll->id]); - } - - clk_data->hws[pll->id] = ERR_PTR(-ENOENT); - } - - if (fhctl_base) - iounmap(fhctl_base); - - iounmap(base); + mtk_clk_cleanup_pllfhs(NULL, plls, num_plls, NULL, pllfhs, + num_fhs, clk_data); } EXPORT_SYMBOL_GPL(mtk_clk_unregister_pllfhs); From 011d8de504bc84402aabc1dda1cf0552fe9a5af2 Mon Sep 17 00:00:00 2001 From: Kostas Damaskinakis Date: Fri, 10 Jul 2026 17:15:20 +0300 Subject: [PATCH 0246/1328] clk: eswin: Zero-initialize stack-allocated clk_init_data eswin_clk_register_pll() and eswin_register_clkdiv() declare a struct clk_init_data on the stack and only initialize some of its fields (parent_data respectively parent_hws). clk_core_populate_parent_map() checks parent_names first and parent_data second before falling back to parent_hws, so leftover stack garbage in the uninitialized fields hijacks parent resolution and the clk core dereferences a bogus pointer: Unable to handle kernel NULL pointer dereference at virtual address 000000000000000c Oops [#1] epc : __clk_register+0x31a/0x7f0 [] __clk_register+0x31a/0x7f0 [] devm_clk_hw_register+0x2a/0x94 [] eswin_register_clkdiv+0x80/0xd0 [] eswin_clk_register_clks+0x162/0x1a0 [] eic7700_clk_probe+0x146/0x180 [] platform_probe+0x3c/0x7a Observed on EIC7700 hardware (with the driver backported to a 6.17 tree); whether the bug triggers depends entirely on what the stack happens to contain when the registration helpers run. Zero-initialize both structures. Fixes: cd44f127c1d4 ("clk: eswin: Add eic7700 clock driver") Signed-off-by: Kostas Damaskinakis Reviewed-by: Brian Masney Acked-by: Xuyang Dong Signed-off-by: Brian Masney --- drivers/clk/eswin/clk.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/clk/eswin/clk.c b/drivers/clk/eswin/clk.c index e09a52cc3587..79d1e4c5e637 100644 --- a/drivers/clk/eswin/clk.c +++ b/drivers/clk/eswin/clk.c @@ -204,7 +204,7 @@ int eswin_clk_register_pll(struct device *dev, struct eswin_pll_clock *clks, int nums, struct eswin_clock_data *data) { struct eswin_clk_pll *p_clk = NULL; - struct clk_init_data init; + struct clk_init_data init = {}; struct clk_hw *clk_hw; int i, ret; @@ -419,7 +419,7 @@ struct clk_hw *eswin_register_clkdiv(struct device *dev, unsigned int id, unsigned long priv_flag, spinlock_t *lock) { struct eswin_divider_clock *dclk; - struct clk_init_data init; + struct clk_init_data init = {}; struct clk_hw *clk_hw; int ret; From ccda84fcbf3a972973f772384935928f41817b3a Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Sat, 11 Jul 2026 23:17:42 +0900 Subject: [PATCH 0247/1328] clk: palmas: Manage external-control prepare with devm palmas_clks_init_configure() prepares the clock when an external control pin is configured. The current driver only drops that prepare reference when external control configuration fails. If provider registration fails after that point, or if the driver is later removed, the prepare reference remains held. Register a device-managed action after clk_prepare() succeeds. This balances the prepare reference on subsequent probe failure and driver removal. Fixes: 942d1d674931 ("clk: Add driver for Palmas clk32kg and clk32kgaudio clocks") Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/clk-palmas.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/drivers/clk/clk-palmas.c b/drivers/clk/clk-palmas.c index 39049f62dbbb..86a51edac827 100644 --- a/drivers/clk/clk-palmas.c +++ b/drivers/clk/clk-palmas.c @@ -194,6 +194,13 @@ static void palmas_clks_get_clk_data(struct platform_device *pdev, cinfo->ext_control_pin = prop; } +static void palmas_clks_unprepare_ext_control(void *data) +{ + struct palmas_clock_info *cinfo = data; + + clk_unprepare(cinfo->hw.clk); +} + static int palmas_clks_init_configure(struct palmas_clock_info *cinfo) { int ret; @@ -214,13 +221,18 @@ static int palmas_clks_init_configure(struct palmas_clock_info *cinfo) return ret; } + ret = devm_add_action_or_reset(cinfo->dev, + palmas_clks_unprepare_ext_control, + cinfo); + if (ret) + return ret; + ret = palmas_ext_control_req_config(cinfo->palmas, cinfo->clk_desc->sleep_reqstr_id, cinfo->ext_control_pin, true); if (ret < 0) { dev_err(cinfo->dev, "Ext config for %s failed, %d\n", cinfo->clk_desc->clk_name, ret); - clk_unprepare(cinfo->hw.clk); return ret; } } From 10f9dc786a1b53b81073b1b155ef4815e3b7b6f7 Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Sat, 11 Jul 2026 23:17:43 +0900 Subject: [PATCH 0248/1328] clk: palmas: Use devm_of_clk_add_hw_provider() Use devm_of_clk_add_hw_provider() to register the clock provider so its lifetime is tied to the device. This removes the need for a remove callback that only unregisters the provider. Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/clk-palmas.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/drivers/clk/clk-palmas.c b/drivers/clk/clk-palmas.c index 86a51edac827..fdbf8445ebc2 100644 --- a/drivers/clk/clk-palmas.c +++ b/drivers/clk/clk-palmas.c @@ -242,7 +242,6 @@ static int palmas_clks_init_configure(struct palmas_clock_info *cinfo) static int palmas_clks_probe(struct platform_device *pdev) { struct palmas *palmas = dev_get_drvdata(pdev->dev.parent); - struct device_node *node = pdev->dev.of_node; const struct palmas_clks_of_match_data *match_data; struct palmas_clock_info *cinfo; int ret; @@ -276,24 +275,19 @@ static int palmas_clks_probe(struct platform_device *pdev) return ret; } - ret = of_clk_add_hw_provider(node, of_clk_hw_simple_get, &cinfo->hw); + ret = devm_of_clk_add_hw_provider(&pdev->dev, of_clk_hw_simple_get, + &cinfo->hw); if (ret < 0) dev_err(&pdev->dev, "Fail to add clock driver, %d\n", ret); return ret; } -static void palmas_clks_remove(struct platform_device *pdev) -{ - of_clk_del_provider(pdev->dev.of_node); -} - static struct platform_driver palmas_clks_driver = { .driver = { .name = "palmas-clk", .of_match_table = palmas_clks_of_match, }, .probe = palmas_clks_probe, - .remove = palmas_clks_remove, }; module_platform_driver(palmas_clks_driver); From 0cad34fd953082a1bef863a0e446dc2559b8eed0 Mon Sep 17 00:00:00 2001 From: Chen Ni Date: Tue, 14 Jul 2026 13:25:25 +0800 Subject: [PATCH 0249/1328] clk: mmp: pxa1908-apbc: Remove unneeded semicolon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove unnecessary semicolons reported by Coccinelle/coccicheck and the semantic patch at scripts/coccinelle/misc/semicolon.cocci. Signed-off-by: Chen Ni Reviewed-by: Brian Masney Acked-by: Duje Mihanović Signed-off-by: Brian Masney --- drivers/clk/mmp/clk-pxa1908-apbc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/mmp/clk-pxa1908-apbc.c b/drivers/clk/mmp/clk-pxa1908-apbc.c index 438ece4f047d..16c1f13bca1f 100644 --- a/drivers/clk/mmp/clk-pxa1908-apbc.c +++ b/drivers/clk/mmp/clk-pxa1908-apbc.c @@ -114,7 +114,7 @@ static void pxa1908_clk_reset_init(struct device_node *np, cells[i].bits = BIT(2); cells[i].flags = 0; cells[i].lock = apbc_gate_clks[i].lock; - }; + } mmp_clk_reset_register(np, cells, nr_cells); } From 2a97ca25f7d5fc7c7a475ff2a92b3fd2cfc8faf1 Mon Sep 17 00:00:00 2001 From: Chen Ni Date: Tue, 14 Jul 2026 13:30:59 +0800 Subject: [PATCH 0250/1328] clk: mmp: pxa1908-apbcp: Remove unneeded semicolon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove unnecessary semicolons reported by Coccinelle/coccicheck and the semantic patch at scripts/coccinelle/misc/semicolon.cocci. Signed-off-by: Chen Ni Reviewed-by: Brian Masney Acked-by: Duje Mihanović Signed-off-by: Brian Masney --- drivers/clk/mmp/clk-pxa1908-apbcp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/mmp/clk-pxa1908-apbcp.c b/drivers/clk/mmp/clk-pxa1908-apbcp.c index 1aa476103553..4e646e8b7440 100644 --- a/drivers/clk/mmp/clk-pxa1908-apbcp.c +++ b/drivers/clk/mmp/clk-pxa1908-apbcp.c @@ -61,7 +61,7 @@ static void pxa1908_clk_reset_init(struct device_node *np, cells[i].bits = BIT(2); cells[i].flags = 0; cells[i].lock = apbcp_gate_clks[i].lock; - }; + } mmp_clk_reset_register(np, cells, nr_cells); } From 18e9d14cbac33db1c1fb933c26a736eef53dd538 Mon Sep 17 00:00:00 2001 From: longlong yan Date: Tue, 14 Jul 2026 14:51:32 +0800 Subject: [PATCH 0251/1328] clk/x86: pmc_atom: add kasprintf return value check The kasprintf() function returns NULL on memory allocation failure, but the code in plt_clk_register() was not checking this return value. If kasprintf fails, init.name would be NULL and could cause NULL pointer dereference when clkdev_hw_create() uses it. Add proper error checking for the kasprintf() return value and return ERR_PTR(-ENOMEM) on failure. Fixes: 1141d9d08184 ("clk: x86: Add Atom PMC platform clocks") Signed-off-by: longlong yan Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/x86/clk-pmc-atom.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/clk/x86/clk-pmc-atom.c b/drivers/clk/x86/clk-pmc-atom.c index 99291ba65da7..08c83e0abc41 100644 --- a/drivers/clk/x86/clk-pmc-atom.c +++ b/drivers/clk/x86/clk-pmc-atom.c @@ -160,6 +160,9 @@ static struct clk_plt *plt_clk_register(struct platform_device *pdev, int id, return ERR_PTR(-ENOMEM); init.name = kasprintf(GFP_KERNEL, "%s_%d", PLT_CLK_NAME_BASE, id); + if (!init.name) + return ERR_PTR(-ENOMEM); + init.ops = &plt_clk_ops; init.flags = 0; init.parent_names = parent_names; From d3045184699d6220e5c03c2d6b1dfc449c3b873e Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Tue, 14 Jul 2026 18:50:18 +0300 Subject: [PATCH 0252/1328] clk: document that clk_get_parent() returns NULL The documentation in the clk.h file says that clk_get_parent() returns error pointers but it doesn't. It's also not consistent with the comments next to the clk_get_parent() implementation which say that it returns NULL when the clk is NULL. Update the comments so they are consistent and accurate and say that it returns NULL. Signed-off-by: Dan Carpenter Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- include/linux/clk.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/clk.h b/include/linux/clk.h index 998ba3f261da..993fd6e916c7 100644 --- a/include/linux/clk.h +++ b/include/linux/clk.h @@ -951,8 +951,8 @@ int clk_set_parent(struct clk *clk, struct clk *parent); * clk_get_parent - get the parent clock source for this clock * @clk: clock source * - * Returns struct clk corresponding to parent clock source, or - * valid IS_ERR() condition containing errno. + * Returns struct clk corresponding to parent clock source, or NULL + * if clk is NULL. */ struct clk *clk_get_parent(struct clk *clk); From 438623bb9350609069ac9fdb3d87b64beb3e9407 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sun, 19 Jul 2026 14:50:39 -0700 Subject: [PATCH 0253/1328] clk: stm32: add missing bitfield.h header It seems some ARM header includes this and the build passes there, but nowhere else. Note that the driver has COMPILE_TEST in depends. Fixes: 37ae8501cdb0 ("clk: stm32: introduce clocks for STM32MP21 platfor") Signed-off-by: Rosen Penev Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/stm32/clk-stm32mp21.c | 1 + drivers/clk/stm32/clk-stm32mp25.c | 1 + 2 files changed, 2 insertions(+) diff --git a/drivers/clk/stm32/clk-stm32mp21.c b/drivers/clk/stm32/clk-stm32mp21.c index c8a37b716bd5..bdb17419908c 100644 --- a/drivers/clk/stm32/clk-stm32mp21.c +++ b/drivers/clk/stm32/clk-stm32mp21.c @@ -4,6 +4,7 @@ * Author: Gabriel Fernandez for STMicroelectronics. */ +#include #include #include #include diff --git a/drivers/clk/stm32/clk-stm32mp25.c b/drivers/clk/stm32/clk-stm32mp25.c index 52f0e8a12926..eb0bc918ecee 100644 --- a/drivers/clk/stm32/clk-stm32mp25.c +++ b/drivers/clk/stm32/clk-stm32mp25.c @@ -4,6 +4,7 @@ * Author: Gabriel Fernandez for STMicroelectronics. */ +#include #include #include #include From 745dfe874d5b9136090af69eba3f61170ceaa168 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sun, 19 Jul 2026 14:50:40 -0700 Subject: [PATCH 0254/1328] clk: stm32: allow STM32MP COMPILE_TEST builds COMMON_CLK_STM32MP already allows COMPILE_TEST, but the parent clock Makefile only descends into drivers/clk/stm32 for ARCH_STM32. Use the STM32MP clock symbol for that directory gate instead. Tested with: make LLVM=1 ARCH=loongarch drivers/clk/stm32/ Assisted-by: Codex:GPT-5.5 Signed-off-by: Rosen Penev Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index cc108a75a900..7c928f962d0b 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -151,7 +151,7 @@ obj-y += spacemit/ obj-$(CONFIG_PLAT_SPEAR) += spear/ obj-y += sprd/ obj-$(CONFIG_ARCH_STI) += st/ -obj-$(CONFIG_ARCH_STM32) += stm32/ +obj-$(CONFIG_COMMON_CLK_STM32MP) += stm32/ obj-y += starfive/ obj-$(CONFIG_ARCH_SUNXI) += sunxi/ obj-y += sunxi-ng/ From 3194c0f961a4d198e28d055b0be3f597b85012c9 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sun, 19 Jul 2026 14:54:56 -0700 Subject: [PATCH 0255/1328] clk: hisilicon: hi3660-stub: use devm_platform_ioremap_resource Replace the open-coded platform_get_resource() plus devm_ioremap() sequence with devm_platform_ioremap_resource(), which fetches the resource, requests the region and maps it in one call. Switch the error check to IS_ERR()/PTR_ERR() and drop the now-unused struct resource pointer. The only in-tree user (stub_clock@e896b500 in hi3660.dtsi) has a single 0x100-byte reg region, so the region reservation added by devm_ioremap_resource() is exclusive and does not introduce overlap failures. Built for ARM (drivers/clk/hisilicon/clk-hi3660-stub.o) with LLVM=1. Assisted-by: opencode:hy3-free Signed-off-by: Rosen Penev Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/hisilicon/clk-hi3660-stub.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/drivers/clk/hisilicon/clk-hi3660-stub.c b/drivers/clk/hisilicon/clk-hi3660-stub.c index 7c8b00ee6019..1b81f26670ed 100644 --- a/drivers/clk/hisilicon/clk-hi3660-stub.c +++ b/drivers/clk/hisilicon/clk-hi3660-stub.c @@ -124,7 +124,6 @@ static struct clk_hw *hi3660_stub_clk_hw_get(struct of_phandle_args *clkspec, static int hi3660_stub_clk_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct resource *res; unsigned int i; int ret; @@ -139,12 +138,9 @@ static int hi3660_stub_clk_probe(struct platform_device *pdev) if (IS_ERR(stub_clk_chan.mbox)) return PTR_ERR(stub_clk_chan.mbox); - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) - return -EINVAL; - freq_reg = devm_ioremap(dev, res->start, resource_size(res)); - if (!freq_reg) - return -ENOMEM; + freq_reg = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(freq_reg)) + return PTR_ERR(freq_reg); freq_reg += HI3660_STUB_CLOCK_DATA; From f600fbdb2093b18f2e21401ba7c6325e4d2b5b16 Mon Sep 17 00:00:00 2001 From: Jia Wang Date: Mon, 20 Jul 2026 08:38:04 +0800 Subject: [PATCH 0256/1328] clk: composite: Export devm composite pdata helper Allow modular clock drivers to use devm_clk_hw_register_composite_pdata() by exporting the helper. The non-devm composite helpers are already available to modules. Export this devm variant as well so users do not need to open-code devres cleanup. Signed-off-by: Jia Wang Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/clk-composite.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/clk/clk-composite.c b/drivers/clk/clk-composite.c index 835b1e4e5869..11842bce5918 100644 --- a/drivers/clk/clk-composite.c +++ b/drivers/clk/clk-composite.c @@ -462,3 +462,4 @@ struct clk_hw *devm_clk_hw_register_composite_pdata(struct device *dev, rate_hw, rate_ops, gate_hw, gate_ops, flags); } +EXPORT_SYMBOL_GPL(devm_clk_hw_register_composite_pdata); From e1d57ac7fe53dc52bb8aab9db59008c7fec8d863 Mon Sep 17 00:00:00 2001 From: Jia Wang Date: Mon, 20 Jul 2026 08:38:05 +0800 Subject: [PATCH 0257/1328] dt-bindings: clock: ultrarisc: Add DP1000 Clock Controller Add doc for the clock controller on the UltraRISC DP1000 RISC-V SoC. Signed-off-by: Jia Wang Reviewed-by: Conor Dooley Signed-off-by: Brian Masney --- .../bindings/clock/ultrarisc,dp1000-clk.yaml | 60 +++++++++++++++++++ MAINTAINERS | 7 +++ .../dt-bindings/clock/ultrarisc,dp1000-clk.h | 27 +++++++++ 3 files changed, 94 insertions(+) create mode 100644 Documentation/devicetree/bindings/clock/ultrarisc,dp1000-clk.yaml create mode 100644 include/dt-bindings/clock/ultrarisc,dp1000-clk.h diff --git a/Documentation/devicetree/bindings/clock/ultrarisc,dp1000-clk.yaml b/Documentation/devicetree/bindings/clock/ultrarisc,dp1000-clk.yaml new file mode 100644 index 000000000000..ede565ec440c --- /dev/null +++ b/Documentation/devicetree/bindings/clock/ultrarisc,dp1000-clk.yaml @@ -0,0 +1,60 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/clock/ultrarisc,dp1000-clk.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: UltraRISC DP1000 Clock Controller + +maintainers: + - Jia Wang + +description: | + The UltraRISC DP1000 clock controller is driven from a single external + oscillator input. It provides a system PLL with fractional multiplier + and post-divider stages, several fixed-ratio derived clocks for + the on-chip subsystem, Clock Configuration Register (CCR) divider + outputs for GMAC and the UART, I2C, and SPI root clocks, and + per-instance gate clocks for UART0-3, I2C0-3, and SPI0-1. + + All available clocks are defined as preprocessor macros in + include/dt-bindings/clock/ultrarisc,dp1000-clk.h + +properties: + compatible: + const: ultrarisc,dp1000-clk + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + description: + External oscillator input clock used as the parent of the PLLs. + + "#clock-cells": + const: 1 + +required: + - compatible + - reg + - clocks + - "#clock-cells" + +additionalProperties: false + +examples: + - | + #include + + soc { + #address-cells = <2>; + #size-cells = <2>; + + clock-controller@11080000 { + compatible = "ultrarisc,dp1000-clk"; + reg = <0x0 0x11080000 0x0 0x1000>; + clocks = <&osc>; + #clock-cells = <1>; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 15011f5752a9..ce93b28497e6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -27669,6 +27669,13 @@ S: Maintained F: drivers/usb/common/ulpi.c F: include/linux/ulpi/ +ULTRARISC DP1000 CLOCK DRIVER +M: Jia Wang +L: linux-clk@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/clock/ultrarisc,dp1000-clk.yaml +F: include/dt-bindings/clock/ultrarisc,dp1000-clk.h + ULTRARISC DP1000 PINCTRL DRIVER M: Jia Wang L: linux-gpio@vger.kernel.org diff --git a/include/dt-bindings/clock/ultrarisc,dp1000-clk.h b/include/dt-bindings/clock/ultrarisc,dp1000-clk.h new file mode 100644 index 000000000000..751125f99965 --- /dev/null +++ b/include/dt-bindings/clock/ultrarisc,dp1000-clk.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ +#ifndef _DT_BINDINGS_CLOCK_ULTRARISC_DP1000_CLK_H +#define _DT_BINDINGS_CLOCK_ULTRARISC_DP1000_CLK_H + +#define DP1000_CLK_SYSPLL 0 +#define DP1000_CLK_SYSPLL_DIV2 1 +#define DP1000_CLK_SUBSYS 2 +#define DP1000_CLK_GMAC 3 +#define DP1000_CLK_UART_ROOT 4 +#define DP1000_CLK_I2C_ROOT 5 +#define DP1000_CLK_SPI_ROOT 6 +#define DP1000_CLK_PCIE_DBI 7 +#define DP1000_CLK_PCIEX4_CORE 8 +#define DP1000_CLK_PCIEX16_CORE 9 +#define DP1000_CLK_PCIE_AUX 10 +#define DP1000_CLK_UART0 11 +#define DP1000_CLK_UART1 12 +#define DP1000_CLK_UART2 13 +#define DP1000_CLK_UART3 14 +#define DP1000_CLK_I2C0 15 +#define DP1000_CLK_I2C1 16 +#define DP1000_CLK_I2C2 17 +#define DP1000_CLK_I2C3 18 +#define DP1000_CLK_SPI0 19 +#define DP1000_CLK_SPI1 20 + +#endif /* _DT_BINDINGS_CLOCK_ULTRARISC_DP1000_CLK_H */ From 3948a36be42b67f43f1d3f113dad4c8b347efb9b Mon Sep 17 00:00:00 2001 From: Jia Wang Date: Mon, 20 Jul 2026 08:38:06 +0800 Subject: [PATCH 0258/1328] clk: ultrarisc: Add DP1000 clock driver Add a clock driver for the UltraRISC DP1000 SoC. The clock tree is driven by a SYSPLL and provides fixed-factor clocks for the subsystem and PCIe, divider-based root clocks for GMAC and the UART, I2C, and SPI blocks, and per-instance gate clocks for UART0-3, I2C0-3, and SPI0-1. Signed-off-by: Jia Wang Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- MAINTAINERS | 1 + drivers/clk/Kconfig | 1 + drivers/clk/Makefile | 1 + drivers/clk/ultrarisc/Kconfig | 18 ++ drivers/clk/ultrarisc/Makefile | 4 + drivers/clk/ultrarisc/clk-dp1000.c | 154 ++++++++++ drivers/clk/ultrarisc/clk-ultrarisc.c | 399 ++++++++++++++++++++++++++ drivers/clk/ultrarisc/clk-ultrarisc.h | 71 +++++ 8 files changed, 649 insertions(+) create mode 100644 drivers/clk/ultrarisc/Kconfig create mode 100644 drivers/clk/ultrarisc/Makefile create mode 100644 drivers/clk/ultrarisc/clk-dp1000.c create mode 100644 drivers/clk/ultrarisc/clk-ultrarisc.c create mode 100644 drivers/clk/ultrarisc/clk-ultrarisc.h diff --git a/MAINTAINERS b/MAINTAINERS index ce93b28497e6..4d21a2dca3fc 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -27674,6 +27674,7 @@ M: Jia Wang L: linux-clk@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/clock/ultrarisc,dp1000-clk.yaml +F: drivers/clk/ultrarisc/* F: include/dt-bindings/clock/ultrarisc,dp1000-clk.h ULTRARISC DP1000 PINCTRL DRIVER diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index b80c957b0040..f83dda6ffc88 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -541,6 +541,7 @@ source "drivers/clk/tenstorrent/Kconfig" source "drivers/clk/thead/Kconfig" source "drivers/clk/stm32/Kconfig" source "drivers/clk/ti/Kconfig" +source "drivers/clk/ultrarisc/Kconfig" source "drivers/clk/uniphier/Kconfig" source "drivers/clk/visconti/Kconfig" source "drivers/clk/x86/Kconfig" diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 7c928f962d0b..dc56d0249450 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -159,6 +159,7 @@ obj-$(CONFIG_ARCH_TEGRA) += tegra/ obj-y += tenstorrent/ obj-$(CONFIG_ARCH_THEAD) += thead/ obj-y += ti/ +obj-y += ultrarisc/ obj-$(CONFIG_CLK_UNIPHIER) += uniphier/ obj-$(CONFIG_ARCH_U8500) += ux500/ obj-y += versatile/ diff --git a/drivers/clk/ultrarisc/Kconfig b/drivers/clk/ultrarisc/Kconfig new file mode 100644 index 000000000000..2eecc6ac3119 --- /dev/null +++ b/drivers/clk/ultrarisc/Kconfig @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config CLK_ULTRARISC + tristate + depends on OF + depends on ARCH_ULTRARISC || COMPILE_TEST + +config CLK_ULTRARISC_DP1000 + tristate "UltraRISC DP1000 clock controller" + select CLK_ULTRARISC + depends on OF && HAS_IOMEM + depends on ARCH_ULTRARISC || COMPILE_TEST + default ARCH_ULTRARISC + help + This driver provides the clock controller for the UltraRISC + DP1000 SoC. It exposes the PLL output, derived fixed-factor + clocks, programmable divider clocks, and peripheral gate + clocks to Linux consumers. diff --git a/drivers/clk/ultrarisc/Makefile b/drivers/clk/ultrarisc/Makefile new file mode 100644 index 000000000000..b013708c9444 --- /dev/null +++ b/drivers/clk/ultrarisc/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_CLK_ULTRARISC) += clk-ultrarisc.o +obj-$(CONFIG_CLK_ULTRARISC_DP1000) += clk-dp1000.o diff --git a/drivers/clk/ultrarisc/clk-dp1000.c b/drivers/clk/ultrarisc/clk-dp1000.c new file mode 100644 index 000000000000..ffea77c4cca0 --- /dev/null +++ b/drivers/clk/ultrarisc/clk-dp1000.c @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2026 UltraRISC Technology (Shanghai) Co., Ltd. + */ + +#include + +#include + +#include "clk-ultrarisc.h" + +#define DP1000_PLL_CFG1_OFFSET 0x400 +#define DP1000_PLL_CFG2_OFFSET 0x404 + +#define DP1000_CCR_UART_OFFSET 0x220 +#define DP1000_CCR_I2C_OFFSET 0x224 +#define DP1000_CCR_GMAC_OFFSET 0x228 +#define DP1000_CCR_SPI_OFFSET 0x22c +#define DP1000_PERI_CLKENA_OFFSET 0x270 + +#define DP1000_CCR_LOAD BIT(16) + +#define DP1000_PERI_MAX_RATE 62500000UL +#define DP1000_CLK_NUM 21 + +static const struct ultrarisc_pll_layout dp1000_pll_layout = { + .cfg1_offset = DP1000_PLL_CFG1_OFFSET, + .cfg2_offset = DP1000_PLL_CFG2_OFFSET, + .frac_mask = GENMASK(23, 0), + .m_mask = GENMASK(23, 16), + .n_mask = GENMASK(11, 6), + .oddiv1_mask = GENMASK(1, 0), + .oddiv2_mask = GENMASK(4, 3), +}; + +static const struct ultrarisc_pll_desc dp1000_plls[] = { + { + .id = DP1000_CLK_SYSPLL, + .name = "syspll_clk", + }, +}; + +#define DP1000_FIXED_FACTOR(_id, _name, _parent, _mult, _div) \ + { \ + .id = (_id), \ + .name = (_name), \ + .parent_id = (_parent), \ + .mult = (_mult), \ + .div = (_div), \ + } + +#define DP1000_DIV(_id, _name, _offset, _parent, _max_rate) \ + { \ + .id = (_id), \ + .name = (_name), \ + .offset = (_offset), \ + .parent_id = (_parent), \ + .max_rate = (_max_rate), \ + .load_mask = DP1000_CCR_LOAD, \ + .div_shift = 8, \ + .div_width = 4, \ + .gate_bit = 0, \ + .divider_flags = CLK_DIVIDER_ONE_BASED, \ + .gate_flags = 0, \ + } + +#define DP1000_GATE(_id, _name, _parent, _bit) \ + { \ + .id = (_id), \ + .name = (_name), \ + .offset = DP1000_PERI_CLKENA_OFFSET, \ + .parent_id = (_parent), \ + .gate_bit = (_bit), \ + .gate_flags = 0, \ + } + +static const struct ultrarisc_fixed_factor_desc dp1000_fixed_factor_clks[] = { + DP1000_FIXED_FACTOR(DP1000_CLK_SYSPLL_DIV2, "syspll_div2_clk", + DP1000_CLK_SYSPLL, 1, 2), + DP1000_FIXED_FACTOR(DP1000_CLK_SUBSYS, "subsys_clk", + DP1000_CLK_SYSPLL_DIV2, 1, 2), + DP1000_FIXED_FACTOR(DP1000_CLK_PCIE_DBI, "pcie_dbi_clk", + DP1000_CLK_SYSPLL, 1, 10), + DP1000_FIXED_FACTOR(DP1000_CLK_PCIEX4_CORE, "pciex4_core_clk", + DP1000_CLK_SYSPLL, 1, 2), + DP1000_FIXED_FACTOR(DP1000_CLK_PCIEX16_CORE, "pciex16_core_clk", + DP1000_CLK_SYSPLL, 1, 1), + DP1000_FIXED_FACTOR(DP1000_CLK_PCIE_AUX, "pcie_aux_clk", + DP1000_CLK_SYSPLL, 1, 40), +}; + +static const struct ultrarisc_divider_desc dp1000_divider_clks[] = { + DP1000_DIV(DP1000_CLK_GMAC, "gmac_clk", DP1000_CCR_GMAC_OFFSET, + DP1000_CLK_SYSPLL_DIV2, 0), + DP1000_DIV(DP1000_CLK_UART_ROOT, "uart_root_clk", + DP1000_CCR_UART_OFFSET, DP1000_CLK_SUBSYS, + DP1000_PERI_MAX_RATE), + DP1000_DIV(DP1000_CLK_I2C_ROOT, "i2c_root_clk", + DP1000_CCR_I2C_OFFSET, DP1000_CLK_SUBSYS, + DP1000_PERI_MAX_RATE), + DP1000_DIV(DP1000_CLK_SPI_ROOT, "spi_root_clk", + DP1000_CCR_SPI_OFFSET, DP1000_CLK_SUBSYS, + DP1000_PERI_MAX_RATE), +}; + +static const struct ultrarisc_gate_desc dp1000_gate_clks[] = { + DP1000_GATE(DP1000_CLK_UART0, "uart0_clk", DP1000_CLK_UART_ROOT, 0), + DP1000_GATE(DP1000_CLK_UART1, "uart1_clk", DP1000_CLK_UART_ROOT, 1), + DP1000_GATE(DP1000_CLK_UART2, "uart2_clk", DP1000_CLK_UART_ROOT, 2), + DP1000_GATE(DP1000_CLK_UART3, "uart3_clk", DP1000_CLK_UART_ROOT, 3), + DP1000_GATE(DP1000_CLK_I2C0, "i2c0_clk", DP1000_CLK_I2C_ROOT, 4), + DP1000_GATE(DP1000_CLK_I2C1, "i2c1_clk", DP1000_CLK_I2C_ROOT, 5), + DP1000_GATE(DP1000_CLK_I2C2, "i2c2_clk", DP1000_CLK_I2C_ROOT, 6), + DP1000_GATE(DP1000_CLK_I2C3, "i2c3_clk", DP1000_CLK_I2C_ROOT, 7), + DP1000_GATE(DP1000_CLK_SPI0, "spi0_clk", DP1000_CLK_SPI_ROOT, 8), + DP1000_GATE(DP1000_CLK_SPI1, "spi1_clk", DP1000_CLK_SPI_ROOT, 9), +}; + +static const struct ultrarisc_clk_soc_data dp1000_clk_soc_data = { + .num_clks = DP1000_CLK_NUM, + .pll_layout = &dp1000_pll_layout, + .plls = dp1000_plls, + .num_plls = ARRAY_SIZE(dp1000_plls), + .fixed_factors = dp1000_fixed_factor_clks, + .num_fixed_factors = ARRAY_SIZE(dp1000_fixed_factor_clks), + .dividers = dp1000_divider_clks, + .num_dividers = ARRAY_SIZE(dp1000_divider_clks), + .gates = dp1000_gate_clks, + .num_gates = ARRAY_SIZE(dp1000_gate_clks), +}; + +static int dp1000_clk_probe(struct platform_device *pdev) +{ + return ultrarisc_clk_probe(pdev, &dp1000_clk_soc_data); +} + +static const struct of_device_id dp1000_clk_of_match[] = { + { .compatible = "ultrarisc,dp1000-clk" }, + { } +}; +MODULE_DEVICE_TABLE(of, dp1000_clk_of_match); + +static struct platform_driver dp1000_clk_driver = { + .probe = dp1000_clk_probe, + .driver = { + .name = "ultrarisc-dp1000-clk", + .of_match_table = dp1000_clk_of_match, + }, +}; +module_platform_driver(dp1000_clk_driver); + +MODULE_IMPORT_NS("CLK_ULTRARISC"); +MODULE_DESCRIPTION("UltraRISC DP1000 clock controller"); +MODULE_LICENSE("GPL"); diff --git a/drivers/clk/ultrarisc/clk-ultrarisc.c b/drivers/clk/ultrarisc/clk-ultrarisc.c new file mode 100644 index 000000000000..bbcd2234ea71 --- /dev/null +++ b/drivers/clk/ultrarisc/clk-ultrarisc.c @@ -0,0 +1,399 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2026 UltraRISC Technology (Shanghai) Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include + +#include "clk-ultrarisc.h" + +struct ultrarisc_pll_clk { + struct clk_hw hw; + void __iomem *base; + const struct ultrarisc_pll_layout *layout; +}; + +struct ultrarisc_divider_clk { + struct clk_divider divider; + struct clk_gate gate; + u32 load_mask; +}; + +#define to_ultrarisc_pll_clk(_hw) \ + container_of(_hw, struct ultrarisc_pll_clk, hw) + +static inline struct ultrarisc_divider_clk *to_ultrarisc_divider_clk(struct clk_hw *hw) +{ + struct clk_divider *divider = to_clk_divider(hw); + + return container_of(divider, struct ultrarisc_divider_clk, divider); +} + +static unsigned long ultrarisc_pll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct ultrarisc_pll_clk *pll = to_ultrarisc_pll_clk(hw); + const struct ultrarisc_pll_layout *layout = pll->layout; + u32 oddiv1_div, oddiv2_div; + u64 mult, rate, den; + u32 frac, m, n; + u32 cfg1, cfg2; + + cfg1 = readl_relaxed(pll->base + layout->cfg1_offset); + cfg2 = readl_relaxed(pll->base + layout->cfg2_offset); + + frac = field_get(layout->frac_mask, cfg1); + m = field_get(layout->m_mask, cfg2); + n = field_get(layout->n_mask, cfg2); + if (!n) + return 0; + + oddiv1_div = 1U << field_get(layout->oddiv1_mask, cfg2); + oddiv2_div = 1U << field_get(layout->oddiv2_mask, cfg2); + + /* + * The output frequency is calculated as: + * fvco = parent * (m + frac / 2^24) / n + * fout = fvco / (2^oddiv1_raw * 2^oddiv2_raw) + * + * The output divider values are derived from the raw register field values as: + * oddivX_div = 1 << oddivX_raw + */ + mult = ((u64)m << 24) + frac; + rate = (u64)parent_rate * mult; + den = ((u64)n << 24) * oddiv1_div * oddiv2_div; + + return div64_u64(rate + (den >> 1), den); +} + +static const struct clk_ops ultrarisc_pll_ro_ops = { + .recalc_rate = ultrarisc_pll_recalc_rate, +}; + +static unsigned long ultrarisc_divider_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_divider *divider = to_clk_divider(hw); + u32 val; + + val = readl_relaxed(divider->reg) >> divider->shift; + val &= clk_div_mask(divider->width); + + return divider_recalc_rate(hw, parent_rate, val, divider->table, + divider->flags, divider->width); +} + +static int ultrarisc_divider_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct clk_divider *divider = to_clk_divider(hw); + + return divider_determine_rate(hw, req, divider->table, divider->width, + divider->flags); +} + +static int ultrarisc_divider_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct ultrarisc_divider_clk *divider_clk = to_ultrarisc_divider_clk(hw); + struct clk_divider *divider = ÷r_clk->divider; + int value; + u32 val; + + value = divider_get_val(rate, parent_rate, divider->table, + divider->width, divider->flags); + if (value < 0) + return value; + + scoped_guard(spinlock_irqsave, divider->lock) { + val = readl_relaxed(divider->reg); + val &= ~(clk_div_mask(divider->width) << divider->shift); + val |= value << divider->shift; + writel_relaxed(val, divider->reg); + + if (divider_clk->load_mask) { + /* + * Program the new divider field, then write 1 to the + * load bit to trigger the update. The load bit is + * write-triggered and reads back as 0 on this hardware. + */ + writel_relaxed(val | divider_clk->load_mask, divider->reg); + } + } + + return 0; +} + +static const struct clk_ops ultrarisc_divider_ops = { + .recalc_rate = ultrarisc_divider_recalc_rate, + .determine_rate = ultrarisc_divider_determine_rate, + .set_rate = ultrarisc_divider_set_rate, +}; + +static struct clk_hw *ultrarisc_clk_register_pll(struct device *dev, + const struct ultrarisc_pll_desc *desc, + const struct ultrarisc_pll_layout *layout, + void __iomem *base) +{ + struct clk_parent_data pdata = { .index = 0 }; + struct ultrarisc_pll_clk *pll; + struct clk_init_data init = { + .name = desc->name, + .ops = &ultrarisc_pll_ro_ops, + .parent_data = &pdata, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + }; + int ret; + + pll = devm_kzalloc(dev, sizeof(*pll), GFP_KERNEL); + if (!pll) + return ERR_PTR(-ENOMEM); + + pll->base = base; + pll->layout = layout; + pll->hw.init = &init; + + ret = devm_clk_hw_register(dev, &pll->hw); + if (ret) + return ERR_PTR(ret); + + return &pll->hw; +} + +static struct clk_hw * +ultrarisc_clk_register_divider(struct device *dev, + const struct ultrarisc_divider_desc *desc, + struct clk_hw *parent_hw, void __iomem *base, + spinlock_t *lock) +{ + const struct clk_parent_data pdata = { .hw = parent_hw }; + void __iomem *reg = base + desc->offset; + struct ultrarisc_divider_clk *divider; + + if (!desc->div_width) + return ERR_PTR(-EINVAL); + + if (!lock) + return ERR_PTR(-EINVAL); + + divider = devm_kzalloc(dev, sizeof(*divider), GFP_KERNEL); + if (!divider) + return ERR_PTR(-ENOMEM); + + divider->divider.reg = reg; + divider->divider.shift = desc->div_shift; + divider->divider.width = desc->div_width; + divider->divider.flags = desc->divider_flags; + divider->divider.lock = lock; + divider->load_mask = desc->load_mask; + divider->gate.reg = reg; + divider->gate.bit_idx = desc->gate_bit; + divider->gate.flags = desc->gate_flags; + divider->gate.lock = lock; + + return devm_clk_hw_register_composite_pdata(dev, desc->name, + &pdata, 1, NULL, NULL, + ÷r->divider.hw, + &ultrarisc_divider_ops, + ÷r->gate.hw, + &clk_gate_ops, 0); +} + +static int ultrarisc_clk_register_fixed_factors(struct device *dev, + struct clk_hw_onecell_data *clk_data, + const struct ultrarisc_clk_soc_data *soc_data) +{ + u32 i; + + for (i = 0; i < soc_data->num_fixed_factors; i++) { + const struct ultrarisc_fixed_factor_desc *desc; + struct clk_hw *parent_hw; + struct clk_hw *hw; + + desc = &soc_data->fixed_factors[i]; + if (desc->id >= clk_data->num || desc->parent_id >= clk_data->num) + return -EINVAL; + + parent_hw = clk_data->hws[desc->parent_id]; + if (!parent_hw) + return -EINVAL; + + hw = devm_clk_hw_register_fixed_factor_parent_hw(dev, desc->name, + parent_hw, 0, + desc->mult, + desc->div); + if (IS_ERR(hw)) + return PTR_ERR(hw); + + clk_data->hws[desc->id] = hw; + } + + return 0; +} + +static int ultrarisc_clk_register_plls(struct platform_device *pdev, + struct clk_hw_onecell_data *clk_data, + const struct ultrarisc_clk_soc_data *soc_data, + void __iomem *base) +{ + struct device *dev = &pdev->dev; + u32 i; + + for (i = 0; i < soc_data->num_plls; i++) { + const struct ultrarisc_pll_desc *desc = &soc_data->plls[i]; + struct clk_hw *hw; + + if (desc->id >= clk_data->num) { + dev_err(dev, "%s invalid clock ID %u >= %u\n", + desc->name, desc->id, clk_data->num); + return -EINVAL; + } + + hw = ultrarisc_clk_register_pll(dev, desc, soc_data->pll_layout, base); + if (IS_ERR(hw)) + return PTR_ERR(hw); + + clk_data->hws[desc->id] = hw; + } + + return 0; +} + +static int ultrarisc_clk_register_dividers(struct platform_device *pdev, + struct clk_hw_onecell_data *clk_data, + const struct ultrarisc_clk_soc_data *soc_data, + void __iomem *base, + spinlock_t *lock) +{ + struct device *dev = &pdev->dev; + u32 i; + + for (i = 0; i < soc_data->num_dividers; i++) { + const struct ultrarisc_divider_desc *desc; + struct clk_hw *parent_hw; + struct clk_hw *hw; + + desc = &soc_data->dividers[i]; + if (desc->id >= clk_data->num || desc->parent_id >= clk_data->num) + return -EINVAL; + + parent_hw = clk_data->hws[desc->parent_id]; + if (!parent_hw) + return -EINVAL; + + hw = ultrarisc_clk_register_divider(dev, desc, parent_hw, base, + lock); + if (IS_ERR(hw)) + return PTR_ERR(hw); + + if (desc->max_rate) + clk_hw_set_rate_range(hw, 0, desc->max_rate); + + clk_data->hws[desc->id] = hw; + } + + return 0; +} + +static int ultrarisc_clk_register_gates(struct platform_device *pdev, + struct clk_hw_onecell_data *clk_data, + const struct ultrarisc_clk_soc_data *soc_data, + void __iomem *base, + spinlock_t *lock) +{ + struct device *dev = &pdev->dev; + u32 i; + + for (i = 0; i < soc_data->num_gates; i++) { + const struct ultrarisc_gate_desc *desc; + struct clk_hw *parent_hw; + struct clk_hw *hw; + + desc = &soc_data->gates[i]; + if (desc->id >= clk_data->num || desc->parent_id >= clk_data->num) + return -EINVAL; + + parent_hw = clk_data->hws[desc->parent_id]; + if (!parent_hw) + return -EINVAL; + + hw = devm_clk_hw_register_gate_parent_hw(dev, desc->name, + parent_hw, 0, + base + desc->offset, + desc->gate_bit, + desc->gate_flags, + lock); + if (IS_ERR(hw)) + return PTR_ERR(hw); + + clk_data->hws[desc->id] = hw; + } + + return 0; +} + +int ultrarisc_clk_probe(struct platform_device *pdev, + const struct ultrarisc_clk_soc_data *soc_data) +{ + struct clk_hw_onecell_data *clk_data; + struct device *dev = &pdev->dev; + void __iomem *base; + spinlock_t *lock; + int ret; + + if (!soc_data) + return -EINVAL; + + lock = devm_kzalloc(dev, sizeof(*lock), GFP_KERNEL); + if (!lock) + return -ENOMEM; + + spin_lock_init(lock); + + clk_data = devm_kzalloc(dev, struct_size(clk_data, hws, + soc_data->num_clks), + GFP_KERNEL); + if (!clk_data) + return -ENOMEM; + + clk_data->num = soc_data->num_clks; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + ret = ultrarisc_clk_register_plls(pdev, clk_data, soc_data, base); + if (ret) + return ret; + + ret = ultrarisc_clk_register_fixed_factors(dev, clk_data, soc_data); + if (ret) + return ret; + + ret = ultrarisc_clk_register_dividers(pdev, clk_data, soc_data, base, lock); + if (ret) + return ret; + + ret = ultrarisc_clk_register_gates(pdev, clk_data, soc_data, base, lock); + if (ret) + return ret; + + for (int i = 0; i < clk_data->num; i++) { + if (!clk_data->hws[i]) { + dev_err(dev, "missing clock ID %u\n", i); + return -EINVAL; + } + } + + return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, clk_data); +} +EXPORT_SYMBOL_NS_GPL(ultrarisc_clk_probe, "CLK_ULTRARISC"); + +MODULE_DESCRIPTION("UltraRISC clock core driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/clk/ultrarisc/clk-ultrarisc.h b/drivers/clk/ultrarisc/clk-ultrarisc.h new file mode 100644 index 000000000000..8202b7ed0e31 --- /dev/null +++ b/drivers/clk/ultrarisc/clk-ultrarisc.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef __ULTRARISC_CLK_ULTRARISC_H +#define __ULTRARISC_CLK_ULTRARISC_H + +#include +#include +#include + +struct ultrarisc_pll_layout { + u32 cfg1_offset; + u32 cfg2_offset; + u32 frac_mask; + u32 m_mask; + u32 n_mask; + u32 oddiv1_mask; + u32 oddiv2_mask; +}; + +struct ultrarisc_pll_desc { + u32 id; + const char *name; +}; + +struct ultrarisc_fixed_factor_desc { + u32 id; + const char *name; + u32 parent_id; + u32 mult; + u32 div; +}; + +struct ultrarisc_divider_desc { + u32 id; + const char *name; + u32 offset; + u32 parent_id; + unsigned long max_rate; + u32 load_mask; + u8 div_shift; + u8 div_width; + u8 gate_bit; + u16 divider_flags; + u8 gate_flags; +}; + +struct ultrarisc_gate_desc { + u32 id; + const char *name; + u32 offset; + u32 parent_id; + u8 gate_bit; + u8 gate_flags; +}; + +struct ultrarisc_clk_soc_data { + const struct ultrarisc_pll_layout *pll_layout; + const struct ultrarisc_pll_desc *plls; + u32 num_plls; + const struct ultrarisc_fixed_factor_desc *fixed_factors; + u32 num_fixed_factors; + const struct ultrarisc_divider_desc *dividers; + u32 num_dividers; + const struct ultrarisc_gate_desc *gates; + u32 num_gates; + u32 num_clks; +}; + +int ultrarisc_clk_probe(struct platform_device *pdev, + const struct ultrarisc_clk_soc_data *soc_data); + +#endif /* __ULTRARISC_CLK_ULTRARISC_H */ From 48ceae43be9f3537eee179bcc53266eadbc40bcc Mon Sep 17 00:00:00 2001 From: Joakim Zhang Date: Thu, 23 Jul 2026 17:08:05 +0800 Subject: [PATCH 0259/1328] dt-bindings: soc: cix: add sky1 audss cru controller The Cix Sky1 Audio Subsystem (AUDSS) Clock and Reset Unit (CRU) groups clock muxing, gating and block-level software reset control in a single register block. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Joakim Zhang Signed-off-by: Brian Masney --- .../bindings/soc/cix/cix,sky1-audss-cru.yaml | 92 +++++++++++++++++++ .../dt-bindings/clock/cix,sky1-audss-cru.h | 60 ++++++++++++ .../dt-bindings/reset/cix,sky1-audss-cru.h | 25 +++++ 3 files changed, 177 insertions(+) create mode 100644 Documentation/devicetree/bindings/soc/cix/cix,sky1-audss-cru.yaml create mode 100644 include/dt-bindings/clock/cix,sky1-audss-cru.h create mode 100644 include/dt-bindings/reset/cix,sky1-audss-cru.h diff --git a/Documentation/devicetree/bindings/soc/cix/cix,sky1-audss-cru.yaml b/Documentation/devicetree/bindings/soc/cix/cix,sky1-audss-cru.yaml new file mode 100644 index 000000000000..50dd0593e1d9 --- /dev/null +++ b/Documentation/devicetree/bindings/soc/cix/cix,sky1-audss-cru.yaml @@ -0,0 +1,92 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/soc/cix/cix,sky1-audss-cru.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Cix Sky1 audio subsystem clock and reset unit + +maintainers: + - Joakim Zhang + +description: | + The Cix Sky1 Audio Subsystem (AUDSS) Clock and Reset Unit (CRU) groups + audio-related clock muxing, gating and block-level software reset control + in a single register block. + + A single device node exposes both the clock controller and software reset + lines. The clock driver registers as a platform driver; the reset controller + is registered by an auxiliary driver bound from the clock driver. + + Four SoC-level reference clocks listed in clocks/clock-names feed the AUDSS + clock tree. Internal AUDSS clocks are exposed via #clock-cells; indices are + defined in include/dt-bindings/clock/cix,sky1-audss-cru.h. + + Block-level software reset indices are exposed via #reset-cells; indices + are defined in include/dt-bindings/reset/cix,sky1-audss-cru.h. + + The SoC syscon NoC (or bus) reset is described via resets. The audio + subsystem power domain is described via power-domains. + +properties: + compatible: + const: cix,sky1-audss-cru + + reg: + maxItems: 1 + + '#clock-cells': + const: 1 + description: + Clock indices are defined in include/dt-bindings/clock/cix,sky1-audss-cru.h. + + '#reset-cells': + const: 1 + description: + Reset indices are defined in include/dt-bindings/reset/cix,sky1-audss-cru.h. + + clocks: + items: + - description: I2S parent clock for sampling rates multiple of 8kHz. + - description: I2S parent clock for sampling rates multiple of 11.025kHz. + - description: Clock feeding most devices in AUDSS (NOC, DSP, SRAM, HDA, DMAC, I2S, and mailbox). + - description: Clock feeding HDA, timer and watchdog, which is a dedicated 48 MHz clock. + + clock-names: + items: + - const: x8k + - const: x11k + - const: sys + - const: 48m + + power-domains: + maxItems: 1 + + resets: + maxItems: 1 + +required: + - compatible + - reg + - '#clock-cells' + - '#reset-cells' + - clocks + - clock-names + - power-domains + - resets + +additionalProperties: false + +examples: + - | + audss_cru: clock-controller@7110000 { + compatible = "cix,sky1-audss-cru"; + reg = <0x7110000 0x10000>; + #clock-cells = <1>; + #reset-cells = <1>; + clocks = <&scmi_clk 76>, <&scmi_clk 78>, + <&scmi_clk 70>, <&scmi_clk 71>; + clock-names = "x8k", "x11k", "sys", "48m"; + power-domains = <&smc_devpd 0>; + resets = <&s5_syscon 31>; + }; diff --git a/include/dt-bindings/clock/cix,sky1-audss-cru.h b/include/dt-bindings/clock/cix,sky1-audss-cru.h new file mode 100644 index 000000000000..8c58ef8bf682 --- /dev/null +++ b/include/dt-bindings/clock/cix,sky1-audss-cru.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ +/* + * Copyright 2026 Cix Technology Group Co., Ltd. + */ + +#ifndef _DT_BINDINGS_CLOCK_CIX_SKY1_AUDSS_CRU_H +#define _DT_BINDINGS_CLOCK_CIX_SKY1_AUDSS_CRU_H + +#define CLK_AUD_CLK4_DIV2 0 +#define CLK_AUD_CLK4_DIV4 1 +#define CLK_AUD_CLK5_DIV2 2 + +#define CLK_DSP_CLK 3 +#define CLK_DSP_BCLK 4 +#define CLK_DSP_PBCLK 5 + +#define CLK_SRAM_AXI 6 + +#define CLK_HDA_SYS 7 +#define CLK_HDA_HDA 8 + +#define CLK_DMAC_AXI 9 + +#define CLK_WDG_APB 10 +#define CLK_WDG_WDG 11 + +#define CLK_TIMER_APB 12 +#define CLK_TIMER_TIMER 13 + +#define CLK_MB_0_APB 14 /* MB0: ap->dsp */ +#define CLK_MB_1_APB 15 /* MB1: dsp->ap */ + +#define CLK_I2S0_APB 16 +#define CLK_I2S1_APB 17 +#define CLK_I2S2_APB 18 +#define CLK_I2S3_APB 19 +#define CLK_I2S4_APB 20 +#define CLK_I2S5_APB 21 +#define CLK_I2S6_APB 22 +#define CLK_I2S7_APB 23 +#define CLK_I2S8_APB 24 +#define CLK_I2S9_APB 25 +#define CLK_I2S0 26 +#define CLK_I2S1 27 +#define CLK_I2S2 28 +#define CLK_I2S3 29 +#define CLK_I2S4 30 +#define CLK_I2S5 31 +#define CLK_I2S6 32 +#define CLK_I2S7 33 +#define CLK_I2S8 34 +#define CLK_I2S9 35 + +#define CLK_MCLK0 36 +#define CLK_MCLK1 37 +#define CLK_MCLK2 38 +#define CLK_MCLK3 39 +#define CLK_MCLK4 40 + +#endif diff --git a/include/dt-bindings/reset/cix,sky1-audss-cru.h b/include/dt-bindings/reset/cix,sky1-audss-cru.h new file mode 100644 index 000000000000..55e9f3797b30 --- /dev/null +++ b/include/dt-bindings/reset/cix,sky1-audss-cru.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ +/* + * Copyright 2026 Cix Technology Group Co., Ltd. + */ +#ifndef DT_BINDINGS_RESET_CIX_SKY1_AUDSS_CRU_H +#define DT_BINDINGS_RESET_CIX_SKY1_AUDSS_CRU_H + +#define AUDSS_I2S0_SW_RST 0 +#define AUDSS_I2S1_SW_RST 1 +#define AUDSS_I2S2_SW_RST 2 +#define AUDSS_I2S3_SW_RST 3 +#define AUDSS_I2S4_SW_RST 4 +#define AUDSS_I2S5_SW_RST 5 +#define AUDSS_I2S6_SW_RST 6 +#define AUDSS_I2S7_SW_RST 7 +#define AUDSS_I2S8_SW_RST 8 +#define AUDSS_I2S9_SW_RST 9 +#define AUDSS_WDT_SW_RST 10 +#define AUDSS_TIMER_SW_RST 11 +#define AUDSS_MB0_SW_RST 12 +#define AUDSS_MB1_SW_RST 13 +#define AUDSS_HDA_SW_RST 14 +#define AUDSS_DMAC_SW_RST 15 + +#endif From 3730fe6032d9a5e1140994693feefe0efa927acb Mon Sep 17 00:00:00 2001 From: Joakim Zhang Date: Thu, 23 Jul 2026 17:08:06 +0800 Subject: [PATCH 0260/1328] clk: cix: add sky1 audss clock controller Add a platform driver for the Cix Sky1 AUDSS CRU. The driver maps the CRU registers and registers mux, divider and gate clocks for DSP, SRAM, HDA, DMAC, I2S, mailbox, watchdog and timer blocks. Four SoC-level audio reference clocks are enabled as inputs to the internal clock tree. The driver releases the AUDSS NOC reset, enables runtime PM and instantiates the auxiliary reset device. Signed-off-by: Joakim Zhang Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/Kconfig | 1 + drivers/clk/Makefile | 1 + drivers/clk/cix/Kconfig | 16 + drivers/clk/cix/Makefile | 3 + drivers/clk/cix/clk-sky1-audss.c | 1206 ++++++++++++++++++++++++++++++ 5 files changed, 1227 insertions(+) create mode 100644 drivers/clk/cix/Kconfig create mode 100644 drivers/clk/cix/Makefile create mode 100644 drivers/clk/cix/clk-sky1-audss.c diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index f83dda6ffc88..057feabf2f23 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -509,6 +509,7 @@ source "drivers/clk/actions/Kconfig" source "drivers/clk/analogbits/Kconfig" source "drivers/clk/aspeed/Kconfig" source "drivers/clk/bcm/Kconfig" +source "drivers/clk/cix/Kconfig" source "drivers/clk/eswin/Kconfig" source "drivers/clk/hisilicon/Kconfig" source "drivers/clk/imgtec/Kconfig" diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index dc56d0249450..0b6241305d4e 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -119,6 +119,7 @@ obj-$(CONFIG_ARCH_ARTPEC) += axis/ obj-$(CONFIG_ARC_PLAT_AXS10X) += axs10x/ obj-y += bcm/ obj-$(CONFIG_ARCH_BERLIN) += berlin/ +obj-y += cix/ obj-$(CONFIG_ARCH_DAVINCI) += davinci/ obj-$(CONFIG_COMMON_CLK_ESWIN) += eswin/ obj-$(CONFIG_ARCH_HISI) += hisilicon/ diff --git a/drivers/clk/cix/Kconfig b/drivers/clk/cix/Kconfig new file mode 100644 index 000000000000..b057ef85fab6 --- /dev/null +++ b/drivers/clk/cix/Kconfig @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0 +# Audio subsystem clock support for Cixtech SoC family +menu "Cixtech Audio Subsystem Clock Driver" + +config CLK_SKY1_AUDSS + tristate "Cixtech Sky1 Audio Subsystem Clock Driver" + depends on ARCH_CIX || COMPILE_TEST + select AUXILIARY_BUS + select REGMAP_MMIO + select RESET_CONTROLLER + help + Support for the Audio Subsystem clock controller present on + Cixtech Sky1 SoC. This driver provides mux, divider and gate + clocks for DSP, I2S, HDA and related blocks in the audio + subsystem. Say M or Y here if you want to build this driver. +endmenu diff --git a/drivers/clk/cix/Makefile b/drivers/clk/cix/Makefile new file mode 100644 index 000000000000..bc612f1d08b2 --- /dev/null +++ b/drivers/clk/cix/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_CLK_SKY1_AUDSS) += clk-sky1-audss.o diff --git a/drivers/clk/cix/clk-sky1-audss.c b/drivers/clk/cix/clk-sky1-audss.c new file mode 100644 index 000000000000..e096b8121df3 --- /dev/null +++ b/drivers/clk/cix/clk-sky1-audss.c @@ -0,0 +1,1206 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright 2026 Cix Technology Group Co., Ltd. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define INFO_HIFI0 0x00 +#define INFO_CLK_GATE 0x10 +#define INFO_CLK_DIV 0x14 +#define INFO_CLK_MUX 0x18 +#define INFO_MCLK 0x70 + +#define SKY1_AUDSS_CLK_PARENTS_CNT 4 +#define SKY1_AUDSS_NUM_CLKS (CLK_MCLK4 + 1) + +static u32 sky1_reg_save[][2] = { + { INFO_HIFI0, 0 }, + { INFO_CLK_GATE, 0 }, + { INFO_CLK_DIV, 0 }, + { INFO_CLK_MUX, 0 }, + { INFO_MCLK, 0 }, +}; + +static const char * const sky1_audss_clk_names[SKY1_AUDSS_CLK_PARENTS_CNT] = { + "x8k", "x11k", "sys", "48m", +}; + +static const u32 sky1_clk_rate_default[SKY1_AUDSS_CLK_PARENTS_CNT] = { + 294912000, + 270950400, + 800000000, + 48000000, +}; + +static const char * const dsp_clk_parent[] = { + "audio_clk4" +}; + +static const char * const dsp_bclk_parent[] = { + "audio_clk4_div2" +}; + +static const char * const dsp_pbclk_parent[] = { + "audio_clk4_div4" +}; + +static const char * const sram_axi_parent[] = { + "audio_clk4_div2" +}; + +static const char * const hda_sys_parent[] = { + "audio_clk4_div2" +}; + +static const char * const hda_hda_parent[] = { + "audio_clk5" +}; + +static const char * const dmac_axi_parent[] = { + "audio_clk4_div2" +}; + +static const char * const wdg_apb_parent[] = { + "audio_clk5_div2" +}; + +static const char * const wdg_wdg_parent[] = { + "audio_clk5_div2" +}; + +static const char * const timer_apb_parent[] = { + "audio_clk4_div4" +}; + +static const char * const timer_timer_parent[] = { + "audio_clk5_div2" +}; + +static const char * const mailbox_apb_parent[] = { + "audio_clk4_div4" +}; + +static const char * const i2s_apb_parent[] = { + "audio_clk4_div4" +}; + +static const char * const i2s0_parents[] = { + "audio_clk0", "audio_clk2" +}; + +static const char * const i2s1_parents[] = { + "audio_clk0", "audio_clk2" +}; + +static const char * const i2s2_parents[] = { + "audio_clk0", "audio_clk2" +}; + +static const char * const i2s3_parents[] = { + "audio_clk0", "audio_clk2" +}; + +static const char * const i2s4_parents[] = { + "audio_clk0", "audio_clk2" +}; + +static const char * const i2s5_parents[] = { + "audio_clk0", "audio_clk2" +}; + +static const char * const i2s6_parents[] = { + "audio_clk0", "audio_clk2" +}; + +static const char * const i2s7_parents[] = { + "audio_clk0", "audio_clk2" +}; + +static const char * const i2s8_parents[] = { + "audio_clk0", "audio_clk2" +}; + +static const char * const i2s9_parents[] = { + "audio_clk0", "audio_clk2" +}; + +static const char * const mclk_parents[] = { + "audio_clk0", "audio_clk2" +}; + +static const u32 i2s3_mux_table[] = { 0, 2 }; +static const u32 i2s4_mux_table[] = { 0, 2 }; + +/* + * audss composite clock definition + */ +struct muxdiv_cfg { + int offset; + u8 shift; + u8 width; + u8 flags; +}; + +struct gate_cfg { + int offset; + u8 shift; + u8 flags; +}; + +struct composite_clk_cfg { + u32 id; + const char * const name; + const char * const *parent_names; + int num_parents; + const u32 *mux_table; + struct muxdiv_cfg *mux_cfg; + struct muxdiv_cfg *div_cfg; + struct gate_cfg *gate_cfg; + unsigned long flags; +}; + +#define CFG(_id,\ + _name,\ + _parent_names,\ + _mux_table,\ + _mux_offset, _mux_shift, _mux_width, _mux_flags,\ + _div_offset, _div_shift, _div_width, _div_flags,\ + _gate_offset, _gate_shift, _gate_flags,\ + _flags)\ +{\ + .id = _id,\ + .name = _name,\ + .parent_names = _parent_names,\ + .num_parents = ARRAY_SIZE(_parent_names),\ + .mux_table = _mux_table,\ + .mux_cfg = &(struct muxdiv_cfg) { _mux_offset, _mux_shift, _mux_width, _mux_flags },\ + .div_cfg = &(struct muxdiv_cfg) { _div_offset, _div_shift, _div_width, _div_flags },\ + .gate_cfg = &(struct gate_cfg) { _gate_offset, _gate_shift, _gate_flags },\ + .flags = _flags,\ +} + +static const struct composite_clk_cfg sky1_audss_clks[] = { + /* dsp */ + CFG(CLK_DSP_CLK, + "audss_dsp_clk", + dsp_clk_parent, + NULL, + -1, 0, 0, 0, + INFO_CLK_DIV, 0, 2, 0, + INFO_HIFI0, 0, 0, + 0), + CFG(CLK_DSP_BCLK, + "audss_dsp_bclk", + dsp_bclk_parent, + NULL, + -1, 0, 0, 0, + INFO_CLK_DIV, 0, 2, 0, + -1, 0, 0, + 0), + CFG(CLK_DSP_PBCLK, + "audss_dsp_pbclk", + dsp_pbclk_parent, + NULL, + -1, 0, 0, 0, + INFO_CLK_DIV, 0, 2, 0, + -1, 0, 0, + 0), + /* sram */ + CFG(CLK_SRAM_AXI, + "audss_sram_axi", + sram_axi_parent, + NULL, + -1, 0, 0, 0, + INFO_CLK_DIV, 0, 2, 0, + INFO_CLK_GATE, 16, 0, + 0), + /* hda */ + CFG(CLK_HDA_SYS, + "audss_hda_sys", + hda_sys_parent, + NULL, + -1, 0, 0, 0, + INFO_CLK_DIV, 0, 2, 0, + INFO_CLK_GATE, 14, 0, + 0), + CFG(CLK_HDA_HDA, + "audss_hda_hda", + hda_hda_parent, + NULL, + -1, 0, 0, 0, + -1, 0, 0, 0, + INFO_CLK_GATE, 14, 0, + 0), + /* dmac */ + CFG(CLK_DMAC_AXI, + "audss_dmac_axi", + dmac_axi_parent, + NULL, + -1, 0, 0, 0, + INFO_CLK_DIV, 0, 2, 0, + INFO_CLK_GATE, 15, 0, + 0), + /* wdg */ + CFG(CLK_WDG_APB, + "audss_wdg_apb", + wdg_apb_parent, + NULL, + -1, 0, 0, 0, + -1, 0, 0, 0, + INFO_CLK_GATE, 10, 0, + 0), + CFG(CLK_WDG_WDG, + "audss_wdg_wdg", + wdg_wdg_parent, + NULL, + -1, 0, 0, 0, + -1, 0, 0, 0, + INFO_CLK_GATE, 10, 0, + 0), + /* timer */ + CFG(CLK_TIMER_APB, + "audss_timer_apb", + timer_apb_parent, + NULL, + -1, 0, 0, 0, + INFO_CLK_DIV, 0, 2, 0, + INFO_CLK_GATE, 11, 0, + 0), + CFG(CLK_TIMER_TIMER, + "audss_timer_timer", + timer_timer_parent, + NULL, + -1, 0, 0, 0, + -1, 0, 0, 0, + INFO_CLK_GATE, 11, 0, + 0), + /* mailbox: mb0(ap->dsp), mb1(dsp->ap) */ + CFG(CLK_MB_0_APB, + "audss_mb_0_apb", + mailbox_apb_parent, + NULL, + -1, 0, 0, 0, + -1, 0, 0, 0, + INFO_CLK_GATE, 12, 0, + 0), + CFG(CLK_MB_1_APB, + "audss_mb_1_apb", + mailbox_apb_parent, + NULL, + -1, 0, 0, 0, + -1, 0, 0, 0, + INFO_CLK_GATE, 13, 0, + 0), + /* i2s */ + CFG(CLK_I2S0_APB, + "audss_i2s0_apb", + i2s_apb_parent, + NULL, + -1, 0, 0, 0, + INFO_CLK_DIV, 0, 2, 0, + INFO_CLK_GATE, 0, 0, + 0), + CFG(CLK_I2S1_APB, + "audss_i2s1_apb", + i2s_apb_parent, + NULL, + -1, 0, 0, 0, + INFO_CLK_DIV, 0, 2, 0, + INFO_CLK_GATE, 1, 0, + 0), + CFG(CLK_I2S2_APB, + "audss_i2s2_apb", + i2s_apb_parent, + NULL, + -1, 0, 0, 0, + INFO_CLK_DIV, 0, 2, 0, + INFO_CLK_GATE, 2, 0, + 0), + CFG(CLK_I2S3_APB, + "audss_i2s3_apb", + i2s_apb_parent, + NULL, + -1, 0, 0, 0, + INFO_CLK_DIV, 0, 2, 0, + INFO_CLK_GATE, 3, 0, + 0), + CFG(CLK_I2S4_APB, + "audss_i2s4_apb", + i2s_apb_parent, + NULL, + -1, 0, 0, 0, + INFO_CLK_DIV, 0, 2, 0, + INFO_CLK_GATE, 4, 0, + 0), + CFG(CLK_I2S5_APB, + "audss_i2s5_apb", + i2s_apb_parent, + NULL, + -1, 0, 0, 0, + INFO_CLK_DIV, 0, 2, 0, + INFO_CLK_GATE, 5, 0, + 0), + CFG(CLK_I2S6_APB, + "audss_i2s6_apb", + i2s_apb_parent, + NULL, + -1, 0, 0, 0, + INFO_CLK_DIV, 0, 2, 0, + INFO_CLK_GATE, 6, 0, + 0), + CFG(CLK_I2S7_APB, + "audss_i2s7_apb", + i2s_apb_parent, + NULL, + -1, 0, 0, 0, + INFO_CLK_DIV, 0, 2, 0, + INFO_CLK_GATE, 7, 0, + 0), + CFG(CLK_I2S8_APB, + "audss_i2s8_apb", + i2s_apb_parent, + NULL, + -1, 0, 0, 0, + INFO_CLK_DIV, 0, 2, 0, + INFO_CLK_GATE, 8, 0, + 0), + CFG(CLK_I2S9_APB, + "audss_i2s9_apb", + i2s_apb_parent, + NULL, + -1, 0, 0, 0, + INFO_CLK_DIV, 0, 2, 0, + INFO_CLK_GATE, 9, 0, + 0), + CFG(CLK_I2S0, + "audss_i2s0", + i2s0_parents, + NULL, + INFO_CLK_MUX, 0, 2, 0, + INFO_CLK_DIV, 2, 2, 0, + INFO_CLK_GATE, 0, 0, + 0), + CFG(CLK_I2S1, + "audss_i2s1", + i2s1_parents, + NULL, + INFO_CLK_MUX, 2, 2, 0, + INFO_CLK_DIV, 4, 2, 0, + INFO_CLK_GATE, 1, 0, + 0), + CFG(CLK_I2S2, + "audss_i2s2", + i2s2_parents, + NULL, + INFO_CLK_MUX, 4, 2, 0, + INFO_CLK_DIV, 6, 2, 0, + INFO_CLK_GATE, 2, 0, + 0), + CFG(CLK_I2S3, + "audss_i2s3", + i2s3_parents, + i2s3_mux_table, + INFO_CLK_MUX, 6, 2, 0, + INFO_CLK_DIV, 8, 2, 0, + INFO_CLK_GATE, 3, 0, + 0), + CFG(CLK_I2S4, + "audss_i2s4", + i2s4_parents, + i2s4_mux_table, + INFO_CLK_MUX, 8, 2, 0, + INFO_CLK_DIV, 10, 2, 0, + INFO_CLK_GATE, 4, 0, + 0), + CFG(CLK_I2S5, + "audss_i2s5", + i2s5_parents, + NULL, + INFO_CLK_MUX, 10, 2, 0, + INFO_CLK_DIV, 12, 2, 0, + INFO_CLK_GATE, 5, 0, + 0), + CFG(CLK_I2S6, + "audss_i2s6", + i2s6_parents, + NULL, + INFO_CLK_MUX, 12, 2, 0, + INFO_CLK_DIV, 14, 2, 0, + INFO_CLK_GATE, 6, 0, + 0), + CFG(CLK_I2S7, + "audss_i2s7", + i2s7_parents, + NULL, + INFO_CLK_MUX, 14, 2, 0, + INFO_CLK_DIV, 16, 2, 0, + INFO_CLK_GATE, 7, 0, + 0), + CFG(CLK_I2S8, + "audss_i2s8", + i2s8_parents, + NULL, + INFO_CLK_MUX, 16, 2, 0, + INFO_CLK_DIV, 18, 2, 0, + INFO_CLK_GATE, 8, 0, + 0), + CFG(CLK_I2S9, + "audss_i2s9", + i2s9_parents, + NULL, + INFO_CLK_MUX, 18, 2, 0, + INFO_CLK_DIV, 20, 2, 0, + INFO_CLK_GATE, 9, 0, + 0), + /* mclk */ + CFG(CLK_MCLK0, + "audss_mclk0", + mclk_parents, + NULL, + INFO_MCLK, 5, 1, 0, + -1, 0, 0, 0, + INFO_MCLK, 0, 0, + 0), + CFG(CLK_MCLK1, + "audss_mclk1", + mclk_parents, + NULL, + INFO_MCLK, 6, 1, 0, + -1, 0, 0, 0, + INFO_MCLK, 1, 0, + 0), + CFG(CLK_MCLK2, + "audss_mclk2", + mclk_parents, + NULL, + INFO_MCLK, 7, 1, 0, + -1, 0, 0, 0, + INFO_MCLK, 2, 0, + 0), + CFG(CLK_MCLK3, + "audss_mclk3", + mclk_parents, + NULL, + INFO_MCLK, 8, 1, 0, + -1, 0, 0, 0, + INFO_MCLK, 3, 0, + 0), + CFG(CLK_MCLK4, + "audss_mclk4", + mclk_parents, + NULL, + INFO_MCLK, 9, 1, 0, + -1, 0, 0, 0, + INFO_MCLK, 4, 0, + 0), +}; + +struct sky1_audss_clks_devtype_data { + u32 (*reg_save)[2]; + size_t reg_save_size; + const char * const *clk_names; + size_t clk_num; + const u32 *clk_rate_default; + const struct composite_clk_cfg *clk_cfg; + size_t clk_cfg_size; +}; + +static const struct regmap_config sky1_audss_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, +}; + +struct sky1_audss_clks_priv { + struct device *dev; + struct regmap *regmap_cru; + struct reset_control *rst_noc; + struct clk *clks[SKY1_AUDSS_CLK_PARENTS_CNT]; + const struct sky1_audss_clks_devtype_data *devtype_data; + spinlock_t lock; + struct clk_hw_onecell_data *clk_data; +}; + +#if IS_ENABLED(CONFIG_RESET_SKY1_AUDSS) + +static int sky1_audss_reset_controller_register(struct device *dev) +{ + struct auxiliary_device *adev; + + if (!of_property_present(dev->of_node, "#reset-cells")) + return 0; + + adev = devm_auxiliary_device_create(dev, "reset", NULL); + if (!adev) + return -ENODEV; + + return 0; +} + +#else + +static int sky1_audss_reset_controller_register(struct device *dev) +{ + return 0; +} + +#endif + +/* + * clk_ops for audss clock mux/divider/gate + */ +struct sky1_clk_divider { + struct clk_divider div; + struct regmap *regmap; + int offset; +}; + +struct sky1_clk_gate { + struct clk_gate gate; + struct regmap *regmap; + int offset; +}; + +struct sky1_clk_mux { + struct clk_mux mux; + struct regmap *regmap; + int offset; +}; + +static inline struct sky1_clk_mux *to_sky1_clk_mux(struct clk_mux *mux) +{ + return container_of(mux, struct sky1_clk_mux, mux); +} + +static u8 sky1_audss_clk_mux_get_parent(struct clk_hw *hw) +{ + struct clk_mux *mux = to_clk_mux(hw); + struct sky1_clk_mux *sky1_mux = to_sky1_clk_mux(mux); + u32 val; + + regmap_read(sky1_mux->regmap, sky1_mux->offset, &val); + val = val >> mux->shift; + val &= mux->mask; + + return clk_mux_val_to_index(hw, mux->table, mux->flags, val); +} + +static int sky1_audss_clk_mux_set_parent(struct clk_hw *hw, u8 index) +{ + struct clk_mux *mux = to_clk_mux(hw); + u32 val = clk_mux_index_to_val(mux->table, mux->flags, index); + struct sky1_clk_mux *sky1_mux = to_sky1_clk_mux(mux); + unsigned long flags = 0; + u32 reg; + + if (mux->lock) + spin_lock_irqsave(mux->lock, flags); + else + __acquire(mux->lock); + + if (mux->flags & CLK_MUX_HIWORD_MASK) { + reg = mux->mask << (mux->shift + 16); + } else { + regmap_read(sky1_mux->regmap, sky1_mux->offset, ®); + reg &= ~(mux->mask << mux->shift); + } + val = val << mux->shift; + reg |= val; + regmap_write(sky1_mux->regmap, sky1_mux->offset, reg); + + if (mux->lock) + spin_unlock_irqrestore(mux->lock, flags); + else + __release(mux->lock); + + return 0; +} + +static int sky1_audss_clk_mux_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct clk_mux *mux = to_clk_mux(hw); + + return clk_mux_determine_rate_flags(hw, req, mux->flags); +} + +static const struct clk_ops sky1_audss_clk_mux_ops = { + .get_parent = sky1_audss_clk_mux_get_parent, + .set_parent = sky1_audss_clk_mux_set_parent, + .determine_rate = sky1_audss_clk_mux_determine_rate, +}; + +static inline struct sky1_clk_divider *to_sky1_clk_divider(struct clk_divider *div) +{ + return container_of(div, struct sky1_clk_divider, div); +} + +static unsigned long sky1_audss_clk_divider_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_divider *divider = to_clk_divider(hw); + struct sky1_clk_divider *sky1_div = to_sky1_clk_divider(divider); + unsigned int val; + + regmap_read(sky1_div->regmap, sky1_div->offset, &val); + val = val >> divider->shift; + val &= clk_div_mask(divider->width); + + return divider_recalc_rate(hw, parent_rate, val, divider->table, + divider->flags, divider->width); +} + +static int sky1_audss_clk_divider_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct clk_divider *divider = to_clk_divider(hw); + struct sky1_clk_divider *sky1_div = to_sky1_clk_divider(divider); + + /* if read only, just return current value */ + if (divider->flags & CLK_DIVIDER_READ_ONLY) { + u32 val; + + regmap_read(sky1_div->regmap, sky1_div->offset, &val); + val = val >> divider->shift; + val &= clk_div_mask(divider->width); + + return divider_ro_determine_rate(hw, req, divider->table, + divider->width, + divider->flags, val); + } + + return divider_determine_rate(hw, req, divider->table, divider->width, + divider->flags); +} + +static int sky1_audss_clk_divider_set_rate(struct clk_hw *hw, + unsigned long rate, + unsigned long parent_rate) +{ + struct clk_divider *divider = to_clk_divider(hw); + struct sky1_clk_divider *sky1_div = to_sky1_clk_divider(divider); + int value; + unsigned long flags = 0; + u32 val; + + value = divider_get_val(rate, parent_rate, divider->table, + divider->width, divider->flags); + if (value < 0) + return value; + + if (divider->lock) + spin_lock_irqsave(divider->lock, flags); + else + __acquire(divider->lock); + + if (divider->flags & CLK_DIVIDER_HIWORD_MASK) { + val = clk_div_mask(divider->width) << (divider->shift + 16); + } else { + regmap_read(sky1_div->regmap, sky1_div->offset, &val); + val &= ~(clk_div_mask(divider->width) << divider->shift); + } + val |= (u32)value << divider->shift; + regmap_write(sky1_div->regmap, sky1_div->offset, val); + + if (divider->lock) + spin_unlock_irqrestore(divider->lock, flags); + else + __release(divider->lock); + + return 0; +} + +static const struct clk_ops sky1_audss_clk_divider_ops = { + .recalc_rate = sky1_audss_clk_divider_recalc_rate, + .determine_rate = sky1_audss_clk_divider_determine_rate, + .set_rate = sky1_audss_clk_divider_set_rate, +}; + +static inline struct sky1_clk_gate *to_sky1_clk_gate(struct clk_gate *gate) +{ + return container_of(gate, struct sky1_clk_gate, gate); +} + +static void sky1_audss_clk_gate_endisable(struct clk_hw *hw, int enable) +{ + struct clk_gate *gate = to_clk_gate(hw); + struct sky1_clk_gate *sky1_gate = to_sky1_clk_gate(gate); + int set = gate->flags & CLK_GATE_SET_TO_DISABLE ? 1 : 0; + unsigned long flags = 0; + u32 reg; + + set ^= enable; + + if (gate->lock) + spin_lock_irqsave(gate->lock, flags); + else + __acquire(gate->lock); + + if (gate->flags & CLK_GATE_HIWORD_MASK) { + reg = BIT(gate->bit_idx + 16); + if (set) + reg |= BIT(gate->bit_idx); + } else { + regmap_read(sky1_gate->regmap, sky1_gate->offset, ®); + + if (set) + reg |= BIT(gate->bit_idx); + else + reg &= ~BIT(gate->bit_idx); + } + + regmap_write(sky1_gate->regmap, sky1_gate->offset, reg); + + if (gate->lock) + spin_unlock_irqrestore(gate->lock, flags); + else + __release(gate->lock); +} + +static int sky1_audss_clk_gate_enable(struct clk_hw *hw) +{ + sky1_audss_clk_gate_endisable(hw, 1); + + return 0; +} + +static void sky1_audss_clk_gate_disable(struct clk_hw *hw) +{ + sky1_audss_clk_gate_endisable(hw, 0); +} + +static int sky1_audss_clk_gate_is_enabled(struct clk_hw *hw) +{ + struct clk_gate *gate = to_clk_gate(hw); + struct sky1_clk_gate *sky1_gate = to_sky1_clk_gate(gate); + u32 reg; + + regmap_read(sky1_gate->regmap, sky1_gate->offset, ®); + + /* if a set bit disables this clk, flip it before masking */ + if (gate->flags & CLK_GATE_SET_TO_DISABLE) + reg ^= BIT(gate->bit_idx); + + reg &= BIT(gate->bit_idx); + + return !!reg; +} + +static const struct clk_ops sky1_audss_clk_gate_ops = { + .enable = sky1_audss_clk_gate_enable, + .disable = sky1_audss_clk_gate_disable, + .is_enabled = sky1_audss_clk_gate_is_enabled, +}; + +static struct clk_hw *sky1_audss_clk_register(struct device *dev, + const char *name, + const char * const *parent_names, + int num_parents, + struct regmap *regmap, + const u32 *mux_table, + struct muxdiv_cfg *mux_cfg, + struct muxdiv_cfg *div_cfg, + struct gate_cfg *gate_cfg, + unsigned long flags, + spinlock_t *lock) +{ + const struct clk_ops *sky1_gate_ops = NULL; + const struct clk_ops *sky1_mux_ops = NULL; + const struct clk_ops *sky1_div_ops = NULL; + struct sky1_clk_divider *sky1_div = NULL; + struct sky1_clk_gate *sky1_gate = NULL; + struct sky1_clk_mux *sky1_mux = NULL; + struct clk_hw *hw = ERR_PTR(-ENOMEM); + struct clk_parent_data *parent_data; + int i; + + parent_data = devm_kcalloc(dev, num_parents, sizeof(*parent_data), GFP_KERNEL); + if (!parent_data) + return ERR_PTR(-ENOMEM); + + for (i = 0; i < num_parents; i++) + parent_data[i].name = parent_names[i]; + + if (mux_cfg->offset >= 0) { + sky1_mux = devm_kzalloc(dev, sizeof(*sky1_mux), GFP_KERNEL); + if (!sky1_mux) + return ERR_PTR(-ENOMEM); + + sky1_mux->mux.reg = NULL; + sky1_mux->mux.shift = mux_cfg->shift; + sky1_mux->mux.mask = BIT(mux_cfg->width) - 1; + sky1_mux->mux.flags = mux_cfg->flags; + sky1_mux->mux.table = mux_table; + sky1_mux->mux.lock = lock; + sky1_mux_ops = &sky1_audss_clk_mux_ops; + sky1_mux->regmap = regmap; + sky1_mux->offset = mux_cfg->offset; + } + + if (div_cfg->offset >= 0) { + sky1_div = devm_kzalloc(dev, sizeof(*sky1_div), GFP_KERNEL); + if (!sky1_div) + return ERR_PTR(-ENOMEM); + + sky1_div->div.reg = NULL; + sky1_div->div.shift = div_cfg->shift; + sky1_div->div.width = div_cfg->width; + sky1_div->div.flags = div_cfg->flags | CLK_DIVIDER_POWER_OF_TWO; + sky1_div->div.lock = lock; + sky1_div_ops = &sky1_audss_clk_divider_ops; + sky1_div->regmap = regmap; + sky1_div->offset = div_cfg->offset; + } + + if (gate_cfg->offset >= 0) { + sky1_gate = devm_kzalloc(dev, sizeof(*sky1_gate), GFP_KERNEL); + if (!sky1_gate) + return ERR_PTR(-ENOMEM); + + sky1_gate->gate.reg = NULL; + sky1_gate->gate.bit_idx = gate_cfg->shift; + sky1_gate->gate.flags = gate_cfg->flags; + sky1_gate->gate.lock = lock; + sky1_gate_ops = &sky1_audss_clk_gate_ops; + sky1_gate->regmap = regmap; + sky1_gate->offset = gate_cfg->offset; + } + + hw = devm_clk_hw_register_composite_pdata(dev, name, parent_data, num_parents, + sky1_mux ? &sky1_mux->mux.hw : NULL, sky1_mux_ops, + sky1_div ? &sky1_div->div.hw : NULL, sky1_div_ops, + sky1_gate ? &sky1_gate->gate.hw : NULL, sky1_gate_ops, + flags); + if (IS_ERR(hw)) { + dev_err(dev, "register %s clock failed with err = %ld\n", + name, PTR_ERR(hw)); + return hw; + } + + return hw; +} + +static int sky1_audss_clks_get(struct sky1_audss_clks_priv *priv) +{ + const struct sky1_audss_clks_devtype_data *devtype_data = priv->devtype_data; + int i; + + for (i = 0; i < devtype_data->clk_num; i++) { + priv->clks[i] = devm_clk_get(priv->dev, devtype_data->clk_names[i]); + if (IS_ERR(priv->clks[i])) + return dev_err_probe(priv->dev, PTR_ERR(priv->clks[i]), + "failed to get clock %s", devtype_data->clk_names[i]); + } + + return 0; +} + +static int sky1_audss_clks_enable(struct sky1_audss_clks_priv *priv) +{ + const struct sky1_audss_clks_devtype_data *devtype_data = priv->devtype_data; + int i, err; + + for (i = 0; i < devtype_data->clk_num; i++) { + err = clk_prepare_enable(priv->clks[i]); + if (err) { + dev_err(priv->dev, "failed to enable clock %s\n", + devtype_data->clk_names[i]); + goto err_clks; + } + } + + return 0; + +err_clks: + while (--i >= 0) + clk_disable_unprepare(priv->clks[i]); + + return err; +} + +static void sky1_audss_clks_disable(struct sky1_audss_clks_priv *priv) +{ + const struct sky1_audss_clks_devtype_data *devtype_data = priv->devtype_data; + int i; + + for (i = 0; i < devtype_data->clk_num; i++) + clk_disable_unprepare(priv->clks[i]); +} + +static int sky1_audss_clks_set_rate(struct sky1_audss_clks_priv *priv) +{ + const struct sky1_audss_clks_devtype_data *devtype_data = priv->devtype_data; + int i, err; + + for (i = 0; i < devtype_data->clk_num; i++) { + err = clk_set_rate(priv->clks[i], devtype_data->clk_rate_default[i]); + if (err) { + dev_err(priv->dev, "failed to set clock rate %s\n", + devtype_data->clk_names[i]); + return err; + } + } + + return 0; +} + +static void sky1_audss_clk_rpm_cleanup(void *data) +{ + pm_runtime_force_suspend(data); +} + +/* register sky1 audio subsystem clocks */ +static int sky1_audss_clk_probe(struct platform_device *pdev) +{ + const struct sky1_audss_clks_devtype_data *devtype_data; + struct sky1_audss_clks_priv *priv; + struct device *dev = &pdev->dev; + struct clk_hw **clk_table; + void __iomem *base; + int i, ret; + + devtype_data = device_get_match_data(dev); + if (!devtype_data) + return -ENODEV; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + spin_lock_init(&priv->lock); + + priv->clk_data = devm_kzalloc(dev, + struct_size(priv->clk_data, hws, SKY1_AUDSS_NUM_CLKS), + GFP_KERNEL); + if (!priv->clk_data) + return -ENOMEM; + + priv->clk_data->num = SKY1_AUDSS_NUM_CLKS; + clk_table = priv->clk_data->hws; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + priv->regmap_cru = devm_regmap_init_mmio(dev, base, &sky1_audss_regmap_config); + if (IS_ERR(priv->regmap_cru)) + return dev_err_probe(dev, PTR_ERR(priv->regmap_cru), + "failed to initialize regmap\n"); + + priv->dev = dev; + priv->devtype_data = devtype_data; + + priv->rst_noc = devm_reset_control_get_exclusive(dev, NULL); + if (IS_ERR(priv->rst_noc)) + return dev_err_probe(dev, PTR_ERR(priv->rst_noc), + "failed to get audss noc reset"); + + platform_set_drvdata(pdev, priv); + + reset_control_assert(priv->rst_noc); + + ret = sky1_audss_clks_get(priv); + if (ret) + return ret; + + ret = sky1_audss_clks_enable(priv); + if (ret) + return ret; + + /* release noc reset after clock on */ + ret = reset_control_deassert(priv->rst_noc); + if (ret) { + sky1_audss_clks_disable(priv); + return ret; + } + + pm_runtime_get_noresume(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + /* + * Register cleanup action before clocks/provider (released last under LIFO). + */ + ret = devm_add_action_or_reset(dev, sky1_audss_clk_rpm_cleanup, dev); + if (ret) + return ret; + + ret = sky1_audss_clks_set_rate(priv); + if (ret) + goto fail_pm; + + /* audio_clk4 clock fixed divider */ + clk_table[CLK_AUD_CLK4_DIV2] = + devm_clk_hw_register_fixed_factor(dev, + "audio_clk4_div2", + "audio_clk4", + 0, + 1, 2); + if (IS_ERR(clk_table[CLK_AUD_CLK4_DIV2])) { + ret = PTR_ERR(clk_table[CLK_AUD_CLK4_DIV2]); + dev_err(dev, "failed to register clock %d, ret:%d\n", CLK_AUD_CLK4_DIV2, ret); + goto fail_pm; + } + + clk_table[CLK_AUD_CLK4_DIV4] = + devm_clk_hw_register_fixed_factor(dev, + "audio_clk4_div4", + "audio_clk4", + 0, + 1, 4); + if (IS_ERR(clk_table[CLK_AUD_CLK4_DIV4])) { + ret = PTR_ERR(clk_table[CLK_AUD_CLK4_DIV4]); + dev_err(dev, "failed to register clock %d, ret:%d\n", CLK_AUD_CLK4_DIV4, ret); + goto fail_pm; + } + + /* audio_clk5 clock fixed divider */ + clk_table[CLK_AUD_CLK5_DIV2] = + devm_clk_hw_register_fixed_factor(dev, + "audio_clk5_div2", + "audio_clk5", + 0, + 1, 2); + if (IS_ERR(clk_table[CLK_AUD_CLK5_DIV2])) { + ret = PTR_ERR(clk_table[CLK_AUD_CLK5_DIV2]); + dev_err(dev, "failed to register clock %d, ret:%d\n", CLK_AUD_CLK5_DIV2, ret); + goto fail_pm; + } + + for (i = 0; i < devtype_data->clk_cfg_size; i++) { + clk_table[devtype_data->clk_cfg[i].id] = + sky1_audss_clk_register(dev, + devtype_data->clk_cfg[i].name, + devtype_data->clk_cfg[i].parent_names, + devtype_data->clk_cfg[i].num_parents, + priv->regmap_cru, + devtype_data->clk_cfg[i].mux_table, + devtype_data->clk_cfg[i].mux_cfg, + devtype_data->clk_cfg[i].div_cfg, + devtype_data->clk_cfg[i].gate_cfg, + devtype_data->clk_cfg[i].flags, + &priv->lock); + if (IS_ERR(clk_table[devtype_data->clk_cfg[i].id])) { + ret = PTR_ERR(clk_table[devtype_data->clk_cfg[i].id]); + dev_err(dev, "failed to register clock %d, ret:%d\n", + devtype_data->clk_cfg[i].id, ret); + goto fail_pm; + } + } + + ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, priv->clk_data); + if (ret) { + dev_err(dev, "failed to add clock provider: %d\n", ret); + goto fail_pm; + } + + ret = sky1_audss_reset_controller_register(dev); + if (ret) { + dev_err(dev, "failed to register reset controller: %d\n", ret); + goto fail_pm; + } + + pm_runtime_put_sync(dev); + + return 0; + +fail_pm: + pm_runtime_put_sync(dev); + return ret; +} + +static int __maybe_unused sky1_audss_clk_runtime_suspend(struct device *dev) +{ + struct sky1_audss_clks_priv *priv = dev_get_drvdata(dev); + const struct sky1_audss_clks_devtype_data *devtype_data = priv->devtype_data; + unsigned long flags; + int i; + + spin_lock_irqsave(&priv->lock, flags); + for (i = 0; i < devtype_data->reg_save_size; i++) + regmap_read(priv->regmap_cru, + devtype_data->reg_save[i][0], &devtype_data->reg_save[i][1]); + spin_unlock_irqrestore(&priv->lock, flags); + + reset_control_assert(priv->rst_noc); + + sky1_audss_clks_disable(priv); + + return 0; +} + +static int __maybe_unused sky1_audss_clk_runtime_resume(struct device *dev) +{ + struct sky1_audss_clks_priv *priv = dev_get_drvdata(dev); + const struct sky1_audss_clks_devtype_data *devtype_data = priv->devtype_data; + unsigned long flags; + int i, ret; + + ret = sky1_audss_clks_enable(priv); + if (ret) { + dev_err(dev, "failed to enable clocks\n"); + return ret; + } + + ret = reset_control_deassert(priv->rst_noc); + if (ret) { + sky1_audss_clks_disable(priv); + return ret; + } + + spin_lock_irqsave(&priv->lock, flags); + for (i = 0; i < devtype_data->reg_save_size; i++) + regmap_write(priv->regmap_cru, + devtype_data->reg_save[i][0], devtype_data->reg_save[i][1]); + spin_unlock_irqrestore(&priv->lock, flags); + + return 0; +} + +static const struct dev_pm_ops sky1_audss_clk_pm_ops = { + SET_RUNTIME_PM_OPS(sky1_audss_clk_runtime_suspend, + sky1_audss_clk_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) +}; + +static const struct sky1_audss_clks_devtype_data sky1_devtype_data = { + .reg_save = sky1_reg_save, + .reg_save_size = ARRAY_SIZE(sky1_reg_save), + .clk_names = sky1_audss_clk_names, + .clk_num = ARRAY_SIZE(sky1_audss_clk_names), + .clk_rate_default = sky1_clk_rate_default, + .clk_cfg = sky1_audss_clks, + .clk_cfg_size = ARRAY_SIZE(sky1_audss_clks), +}; + +static const struct of_device_id sky1_audss_clk_of_match[] = { + { .compatible = "cix,sky1-audss-cru", .data = &sky1_devtype_data, }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, sky1_audss_clk_of_match); + +static struct platform_driver sky1_audss_clk_driver = { + .probe = sky1_audss_clk_probe, + .driver = { + .name = "sky1-audss-clk", + .suppress_bind_attrs = true, + .of_match_table = sky1_audss_clk_of_match, + .pm = pm_ptr(&sky1_audss_clk_pm_ops), + }, +}; +module_platform_driver(sky1_audss_clk_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Joakim Zhang "); +MODULE_DESCRIPTION("Cixtech Sky1 Audio Subsystem Clock Controller Driver"); From b5fa9d3beec483d125757127b804470941e191af Mon Sep 17 00:00:00 2001 From: Joakim Zhang Date: Thu, 23 Jul 2026 17:08:07 +0800 Subject: [PATCH 0261/1328] reset: cix: add sky1 audss auxiliary reset driver Add an auxiliary reset controller driver for the AUDSS CRU. Sixteen software reset lines for audio subsystem peripherals are controlled through one register in the CRU register map. The driver is created by the AUDSS clock platform driver and registers the reset controller on the CRU device node. Signed-off-by: Joakim Zhang Reviewed-by: Philipp Zabel Acked-by: Philipp Zabel Signed-off-by: Brian Masney --- drivers/reset/Kconfig | 13 ++++ drivers/reset/Makefile | 1 + drivers/reset/reset-sky1-audss.c | 127 +++++++++++++++++++++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 drivers/reset/reset-sky1-audss.c diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig index d009eb0849a3..b19e719f2abe 100644 --- a/drivers/reset/Kconfig +++ b/drivers/reset/Kconfig @@ -300,6 +300,19 @@ config RESET_SKY1 help This enables the reset controller for Cix Sky1. +config RESET_SKY1_AUDSS + tristate "Cix Sky1 Audio Subsystem reset controller" + depends on ARCH_CIX || COMPILE_TEST + select AUXILIARY_BUS + default CLK_SKY1_AUDSS + help + Support for block-level software reset lines in the Cix Sky1 + Audio Subsystem (AUDSS) Clock and Reset Unit. Sixteen reset + outputs for audio peripherals are controlled through the CRU + register map. The driver binds as an auxiliary device from + the AUDSS clock driver. Say M or Y here if you want to build + this driver. + config RESET_SOCFPGA bool "SoCFPGA Reset Driver" if COMPILE_TEST && (!ARM || !ARCH_INTEL_SOCFPGA) default ARM && ARCH_INTEL_SOCFPGA diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile index 3e52569bd276..e81407ea3e29 100644 --- a/drivers/reset/Makefile +++ b/drivers/reset/Makefile @@ -39,6 +39,7 @@ obj-$(CONFIG_RESET_RZV2H_USB2PHY) += reset-rzv2h-usb2phy.o obj-$(CONFIG_RESET_SCMI) += reset-scmi.o obj-$(CONFIG_RESET_SIMPLE) += reset-simple.o obj-$(CONFIG_RESET_SKY1) += reset-sky1.o +obj-$(CONFIG_RESET_SKY1_AUDSS) += reset-sky1-audss.o obj-$(CONFIG_RESET_SOCFPGA) += reset-socfpga.o obj-$(CONFIG_RESET_SUNPLUS) += reset-sunplus.o obj-$(CONFIG_RESET_SUNXI) += reset-sunxi.o diff --git a/drivers/reset/reset-sky1-audss.c b/drivers/reset/reset-sky1-audss.c new file mode 100644 index 000000000000..d9735db681c7 --- /dev/null +++ b/drivers/reset/reset-sky1-audss.c @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Cix Sky1 Audio Subsystem reset controller driver + * + * Copyright 2026 Cix Technology Group Co., Ltd. + */ + +#include + +#include +#include +#include +#include +#include +#include + +#define SKY1_RESET_SLEEP_US 50 + +#define AUDSS_SW_RST 0x78 + +struct sky1_audss_reset_map { + unsigned int offset; + unsigned int mask; +}; + +struct sky1_audss_reset { + struct reset_controller_dev rcdev; + struct regmap *regmap; + const struct sky1_audss_reset_map *map; +}; + +static const struct sky1_audss_reset_map sky1_audss_reset_map[] = { + [AUDSS_I2S0_SW_RST] = { AUDSS_SW_RST, BIT(0) }, + [AUDSS_I2S1_SW_RST] = { AUDSS_SW_RST, BIT(1) }, + [AUDSS_I2S2_SW_RST] = { AUDSS_SW_RST, BIT(2) }, + [AUDSS_I2S3_SW_RST] = { AUDSS_SW_RST, BIT(3) }, + [AUDSS_I2S4_SW_RST] = { AUDSS_SW_RST, BIT(4) }, + [AUDSS_I2S5_SW_RST] = { AUDSS_SW_RST, BIT(5) }, + [AUDSS_I2S6_SW_RST] = { AUDSS_SW_RST, BIT(6) }, + [AUDSS_I2S7_SW_RST] = { AUDSS_SW_RST, BIT(7) }, + [AUDSS_I2S8_SW_RST] = { AUDSS_SW_RST, BIT(8) }, + [AUDSS_I2S9_SW_RST] = { AUDSS_SW_RST, BIT(9) }, + [AUDSS_WDT_SW_RST] = { AUDSS_SW_RST, BIT(10) }, + [AUDSS_TIMER_SW_RST] = { AUDSS_SW_RST, BIT(11) }, + [AUDSS_MB0_SW_RST] = { AUDSS_SW_RST, BIT(12) }, + [AUDSS_MB1_SW_RST] = { AUDSS_SW_RST, BIT(13) }, + [AUDSS_HDA_SW_RST] = { AUDSS_SW_RST, BIT(14) }, + [AUDSS_DMAC_SW_RST] = { AUDSS_SW_RST, BIT(15) }, +}; + +static struct sky1_audss_reset *to_sky1_audss_reset(struct reset_controller_dev *rcdev) +{ + return container_of(rcdev, struct sky1_audss_reset, rcdev); +} + +static int sky1_audss_reset_set(struct reset_controller_dev *rcdev, + unsigned long id, bool assert) +{ + struct sky1_audss_reset *priv = to_sky1_audss_reset(rcdev); + const struct sky1_audss_reset_map *signal = &priv->map[id]; + int ret; + + ret = regmap_assign_bits(priv->regmap, signal->offset, + signal->mask, !assert); + if (ret) + return ret; + + fsleep(SKY1_RESET_SLEEP_US); + return 0; +} + +static int sky1_audss_reset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return sky1_audss_reset_set(rcdev, id, true); +} + +static int sky1_audss_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return sky1_audss_reset_set(rcdev, id, false); +} + +static const struct reset_control_ops sky1_audss_reset_ops = { + .assert = sky1_audss_reset_assert, + .deassert = sky1_audss_reset_deassert, +}; + +static int sky1_audss_reset_probe(struct auxiliary_device *adev, + const struct auxiliary_device_id *id) +{ + struct sky1_audss_reset *priv; + struct device *dev = &adev->dev; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->regmap = dev_get_regmap(dev->parent, NULL); + if (!priv->regmap) + return dev_err_probe(dev, -ENODEV, "failed to get parent regmap\n"); + + priv->map = sky1_audss_reset_map; + priv->rcdev.owner = THIS_MODULE; + priv->rcdev.nr_resets = ARRAY_SIZE(sky1_audss_reset_map); + priv->rcdev.ops = &sky1_audss_reset_ops; + priv->rcdev.of_node = dev->of_node; + priv->rcdev.dev = dev; + + return devm_reset_controller_register(dev, &priv->rcdev); +} + +static const struct auxiliary_device_id sky1_audss_reset_ids[] = { + { .name = "clk_sky1_audss.reset" }, + { } +}; +MODULE_DEVICE_TABLE(auxiliary, sky1_audss_reset_ids); + +static struct auxiliary_driver sky1_audss_reset_driver = { + .probe = sky1_audss_reset_probe, + .id_table = sky1_audss_reset_ids, +}; +module_auxiliary_driver(sky1_audss_reset_driver); + +MODULE_AUTHOR("Joakim Zhang "); +MODULE_DESCRIPTION("Cix Sky1 Audio Subsystem reset driver"); +MODULE_LICENSE("GPL"); From a2b1c025cf288473d9604be0b75d3b3362dac113 Mon Sep 17 00:00:00 2001 From: Joakim Zhang Date: Thu, 23 Jul 2026 17:08:08 +0800 Subject: [PATCH 0262/1328] arm64: dts: cix: sky1: add audss cru Add the AUDSS CRU device node providing clocks and software resets for audio subsystem peripherals. Signed-off-by: Joakim Zhang Signed-off-by: Brian Masney --- arch/arm64/boot/dts/cix/sky1.dtsi | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/arch/arm64/boot/dts/cix/sky1.dtsi b/arch/arm64/boot/dts/cix/sky1.dtsi index a77c52296ebd..9b2d969f6bfe 100644 --- a/arch/arm64/boot/dts/cix/sky1.dtsi +++ b/arch/arm64/boot/dts/cix/sky1.dtsi @@ -6,6 +6,10 @@ #include #include +#include +#include +#include +#include #include "sky1-power.h" / { @@ -558,6 +562,20 @@ mbox_pm2ap: mailbox@65a0080 { cix,mbox-dir = "rx"; }; + audss_cru: clock-controller@7110000 { + compatible = "cix,sky1-audss-cru"; + reg = <0x0 0x07110000 0x0 0x10000>; + #clock-cells = <1>; + #reset-cells = <1>; + clocks = <&scmi_clk CLK_TREE_AUDIO_CLK0>, + <&scmi_clk CLK_TREE_AUDIO_CLK2>, + <&scmi_clk CLK_TREE_AUDIO_CLK4>, + <&scmi_clk CLK_TREE_AUDIO_CLK5>; + clock-names = "x8k", "x11k", "sys", "48m"; + power-domains = <&smc_devpd SKY1_PD_AUDIO>; + resets = <&s5_syscon SKY1_AUDIO_HIFI5_NOC_RESET_N>; + }; + mbox_sfh2ap: mailbox@8090000 { compatible = "cix,sky1-mbox"; reg = <0x0 0x08090000 0x0 0x10000>; From e6850b69b674604d6d6dec980655e5e45a177c9b Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Thu, 23 Jul 2026 21:01:30 +0200 Subject: [PATCH 0263/1328] dt-bindings: clock: airoha: Add additional reset for PCIe PERSTOUT Add additional reset to control PCIe PERSTOUT reset line for each of the 3 PCIe lines. Signed-off-by: Christian Marangi Acked-by: Krzysztof Kozlowski Signed-off-by: Brian Masney --- include/dt-bindings/reset/airoha,en7581-reset.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/dt-bindings/reset/airoha,en7581-reset.h b/include/dt-bindings/reset/airoha,en7581-reset.h index 6544a1790b83..25e75534daa9 100644 --- a/include/dt-bindings/reset/airoha,en7581-reset.h +++ b/include/dt-bindings/reset/airoha,en7581-reset.h @@ -62,5 +62,9 @@ #define EN7581_CPU_TIMER_RST 50 #define EN7581_PCIE_HB_RST 51 #define EN7581_XPON_MAC_RST 52 +/* RST_PCIC */ +#define EN7581_PCIC_PERSTOUT0_RST 53 +#define EN7581_PCIC_PERSTOUT1_RST 54 +#define EN7581_PCIC_PERSTOUT2_RST 55 #endif /* __DT_BINDINGS_RESET_CONTROLLER_AIROHA_EN7581_H_ */ From 6712f48eb3a15842925171441505fa2e7699c57e Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Thu, 23 Jul 2026 21:01:31 +0200 Subject: [PATCH 0264/1328] clk: en7523: add support for dedicated PCIe PERSTOUT reset Add support for resetting the PCIe lines with the PERSTOUT reset. These special reset are controlled by the PCIC register and are specific to each of the 3 PCIe lines. Contrary to the dedicated reset register where 0 deassert and 1 assert the reset line for the related bit, for PCIe PERSTOUT reset in the PCIC register, the deassert and assert value is swapped, with 1 for deassert and 0 for assert. (HW have these bit set as 1 by default after a cold boot) This is internally handled in the reset function. PCI enable/disable are updated to drop PERSTOUT bits in favor dedicated reset handling. Signed-off-by: Christian Marangi Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/clk-en7523.c | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/drivers/clk/clk-en7523.c b/drivers/clk/clk-en7523.c index c450d89bfa96..5c45aacb6409 100644 --- a/drivers/clk/clk-en7523.c +++ b/drivers/clk/clk-en7523.c @@ -339,6 +339,7 @@ static const struct en_clk_desc en7581_base_clks[] = { static const u16 en7581_rst_ofs[] = { REG_RST_CTRL2, REG_RST_CTRL1, + REG_NP_SCU_PCIC, }; static const u16 en751221_rst_ofs[] = { @@ -451,6 +452,11 @@ static const u16 en7581_rst_map[] = { [EN7581_CPU_TIMER_RST] = RST_NR_PER_BANK + 28, [EN7581_PCIE_HB_RST] = RST_NR_PER_BANK + 29, [EN7581_XPON_MAC_RST] = RST_NR_PER_BANK + 31, + + /* RST_PCIC */ + [EN7581_PCIC_PERSTOUT0_RST] = 2 * RST_NR_PER_BANK + 29, + [EN7581_PCIC_PERSTOUT1_RST] = 2 * RST_NR_PER_BANK + 26, + [EN7581_PCIC_PERSTOUT2_RST] = 2 * RST_NR_PER_BANK + 16, }; static const u16 en751221_rst_map[] = { @@ -636,9 +642,7 @@ static int en7581_pci_enable(struct clk_hw *hw) void __iomem *np_base = cg->base; u32 val, mask; - mask = REG_PCI_CONTROL_REFCLK_EN0 | REG_PCI_CONTROL_REFCLK_EN1 | - REG_PCI_CONTROL_PERSTOUT1 | REG_PCI_CONTROL_PERSTOUT2 | - REG_PCI_CONTROL_PERSTOUT; + mask = REG_PCI_CONTROL_REFCLK_EN0 | REG_PCI_CONTROL_REFCLK_EN1; val = readl(np_base + REG_PCI_CONTROL); writel(val | mask, np_base + REG_PCI_CONTROL); @@ -651,9 +655,7 @@ static void en7581_pci_disable(struct clk_hw *hw) void __iomem *np_base = cg->base; u32 val, mask; - mask = REG_PCI_CONTROL_REFCLK_EN0 | REG_PCI_CONTROL_REFCLK_EN1 | - REG_PCI_CONTROL_PERSTOUT1 | REG_PCI_CONTROL_PERSTOUT2 | - REG_PCI_CONTROL_PERSTOUT; + mask = REG_PCI_CONTROL_REFCLK_EN0 | REG_PCI_CONTROL_REFCLK_EN1; val = readl(np_base + REG_PCI_CONTROL); writel(val & ~mask, np_base + REG_PCI_CONTROL); usleep_range(1000, 2000); @@ -755,11 +757,17 @@ static int en7523_reset_update(struct reset_controller_dev *rcdev, unsigned long id, bool assert) { struct en_rst_data *rst_data = container_of(rcdev, struct en_rst_data, rcdev); - void __iomem *addr = rst_data->base + rst_data->bank_ofs[id / RST_NR_PER_BANK]; + u32 offset = rst_data->bank_ofs[id / RST_NR_PER_BANK]; + void __iomem *addr = rst_data->base + offset; + bool inverted = false; u32 val; + /* For PCIC reset logic is inverted, 0:assert 1:deassert */ + if (offset == REG_NP_SCU_PCIC) + inverted = true; + val = readl(addr); - if (assert) + if (assert ^ inverted) val |= BIT(id % RST_NR_PER_BANK); else val &= ~BIT(id % RST_NR_PER_BANK); @@ -784,9 +792,17 @@ static int en7523_reset_status(struct reset_controller_dev *rcdev, unsigned long id) { struct en_rst_data *rst_data = container_of(rcdev, struct en_rst_data, rcdev); - void __iomem *addr = rst_data->base + rst_data->bank_ofs[id / RST_NR_PER_BANK]; + u32 offset = rst_data->bank_ofs[id / RST_NR_PER_BANK]; + void __iomem *addr = rst_data->base + offset; + bool inverted = false; + u32 val; - return !!(readl(addr) & BIT(id % RST_NR_PER_BANK)); + /* For PCIC reset logic is inverted, 0:assert 1:deassert */ + if (offset == REG_NP_SCU_PCIC) + inverted = true; + + val = readl(addr) & BIT(id % RST_NR_PER_BANK); + return inverted ? !val : !!val; } static int en7523_reset_xlate(struct reset_controller_dev *rcdev, From b698927accb1c974040dc6596c8df1d72682b2ed Mon Sep 17 00:00:00 2001 From: Suraj Gupta Date: Thu, 23 Jul 2026 18:08:32 +0530 Subject: [PATCH 0265/1328] clk: Add devm_clk_bulk_get_enable() devm_clk_bulk_get_optional_enable() gets, prepares and enables a set of clocks with device-managed cleanup, but treats every clock as optional: a missing clock is silently returned as NULL instead of failing. Consumers that need a fixed set of mandatory clocks enabled for the lifetime of the device currently have to open-code devm_clk_bulk_get() followed by clk_bulk_prepare_enable(), which loses the managed disable on unbind, or fall back to per-clock devm_clk_get_enabled() calls. Add devm_clk_bulk_get_enable() as the non-optional counterpart. The underlying __devm_clk_bulk_get_enable() helper already supports the required (optional = false) path, so only export a thin wrapper for it. Signed-off-by: Suraj Gupta Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- .../driver-api/driver-model/devres.rst | 1 + drivers/clk/clk-devres.c | 7 ++++++ include/linux/clk.h | 23 +++++++++++++++++++ 3 files changed, 31 insertions(+) diff --git a/Documentation/driver-api/driver-model/devres.rst b/Documentation/driver-api/driver-model/devres.rst index 017fb155a5bc..5067500ded5c 100644 --- a/Documentation/driver-api/driver-model/devres.rst +++ b/Documentation/driver-api/driver-model/devres.rst @@ -248,6 +248,7 @@ CLOCK devm_clk_put() devm_clk_bulk_get() devm_clk_bulk_get_all() + devm_clk_bulk_get_enable() devm_clk_bulk_get_optional() devm_get_clk_from_child() devm_clk_hw_register() diff --git a/drivers/clk/clk-devres.c b/drivers/clk/clk-devres.c index 994d5bc5168b..49d4782991eb 100644 --- a/drivers/clk/clk-devres.c +++ b/drivers/clk/clk-devres.c @@ -222,6 +222,13 @@ static int __devm_clk_bulk_get_enable(struct device *dev, int num_clks, return ret; } +int __must_check devm_clk_bulk_get_enable(struct device *dev, int num_clks, + struct clk_bulk_data *clks) +{ + return __devm_clk_bulk_get_enable(dev, num_clks, clks, false); +} +EXPORT_SYMBOL_GPL(devm_clk_bulk_get_enable); + int __must_check devm_clk_bulk_get_optional_enable(struct device *dev, int num_clks, struct clk_bulk_data *clks) { diff --git a/include/linux/clk.h b/include/linux/clk.h index 993fd6e916c7..db9cf184fd99 100644 --- a/include/linux/clk.h +++ b/include/linux/clk.h @@ -502,6 +502,22 @@ int __must_check devm_clk_bulk_get(struct device *dev, int num_clks, */ int __must_check devm_clk_bulk_get_optional(struct device *dev, int num_clks, struct clk_bulk_data *clks); +/** + * devm_clk_bulk_get_enable - Get and enable bulk clocks (managed) + * @dev: device for clock "consumer" + * @num_clks: the number of clk_bulk_data + * @clks: pointer to the clk_bulk_data table of consumer + * + * Behaves the same as devm_clk_bulk_get() but also prepares and enables the + * clocks in one operation with management. The clks will automatically be + * disabled, unprepared and freed when the device is unbound. + * + * Return: 0 if all clocks specified in clk_bulk_data table are obtained and + * enabled successfully. Otherwise returns valid IS_ERR() condition containing + * errno. + */ +int __must_check devm_clk_bulk_get_enable(struct device *dev, int num_clks, + struct clk_bulk_data *clks); /** * devm_clk_bulk_get_optional_enable - Get and enable optional bulk clocks (managed) * @dev: device for clock "consumer" @@ -1052,6 +1068,13 @@ static inline int __must_check devm_clk_bulk_get_optional(struct device *dev, return 0; } +static inline int __must_check devm_clk_bulk_get_enable(struct device *dev, + int num_clks, + struct clk_bulk_data *clks) +{ + return 0; +} + static inline int __must_check devm_clk_bulk_get_optional_enable(struct device *dev, int num_clks, struct clk_bulk_data *clks) From 4e324f1bc00390508444256b01e8568b5ddbdae7 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Fri, 24 Jul 2026 03:45:19 +0900 Subject: [PATCH 0266/1328] clk: remove conditional return with no effect Both branches of the check return the same value, so the check has no effect. Remove it and return the value directly. In the lmk04832 driver, the hardware sequence comments are moved above the final return by hand. This is the result of running the Coccinelle script from scripts/coccinelle/misc/cond_return_no_effect.cocci. Signed-off-by: Sang-Heon Jeon Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/clk-cs2000-cp.c | 6 +----- drivers/clk/clk-lmk04832.c | 12 ++++-------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/drivers/clk/clk-cs2000-cp.c b/drivers/clk/clk-cs2000-cp.c index 8800472ba63f..4de581771d7d 100644 --- a/drivers/clk/clk-cs2000-cp.c +++ b/drivers/clk/clk-cs2000-cp.c @@ -404,11 +404,7 @@ static int cs2000_enable(struct clk_hw *hw) if (ret < 0) return ret; - ret = cs2000_wait_pll_lock(priv); - if (ret < 0) - return ret; - - return ret; + return cs2000_wait_pll_lock(priv); } static void cs2000_disable(struct clk_hw *hw) diff --git a/drivers/clk/clk-lmk04832.c b/drivers/clk/clk-lmk04832.c index 9bf86caad829..f006401c1586 100644 --- a/drivers/clk/clk-lmk04832.c +++ b/drivers/clk/clk-lmk04832.c @@ -822,13 +822,6 @@ static int lmk04832_sclk_sync_sequence(struct lmk04832 *lmk) if (ret) return ret; - ret = regmap_update_bits(lmk->regmap, LMK04832_REG_SYNC, - LMK04832_BIT_SYNC_MODE, - FIELD_PREP(LMK04832_BIT_SYNC_MODE, - lmk->sync_mode)); - if (ret) - return ret; - /* * 9. (optional) if SCLKx_y_DIS_MODE was used to mute SYSREF outputs * during the SYNC event, restore SCLKx_y_DIS_MODE=0 for active state, @@ -843,7 +836,10 @@ static int lmk04832_sclk_sync_sequence(struct lmk04832 *lmk) * SYNC pulse to delay the output by some number of VCO counts). */ - return ret; + return regmap_update_bits(lmk->regmap, LMK04832_REG_SYNC, + LMK04832_BIT_SYNC_MODE, + FIELD_PREP(LMK04832_BIT_SYNC_MODE, + lmk->sync_mode)); } static int lmk04832_sclk_is_enabled(struct clk_hw *hw) From ef6ca9c4df02753fac81d70ae4c3f02bf784a4f0 Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Fri, 24 Jul 2026 11:03:52 +0200 Subject: [PATCH 0267/1328] clk: mvebu: clean-up simple provider misuse of the consumer API Clock provider should not be using the consumer interface. In other words, a provider should not be dealing with struct clk. This change targets occurrences for which the provider uses the consumer interface and corresponding clk_hw interface exist. Reviewed-by: Brian Masney Signed-off-by: Jerome Brunet Reviewed-by: Thierry Reding Signed-off-by: Brian Masney --- drivers/clk/mvebu/clk-cpu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/mvebu/clk-cpu.c b/drivers/clk/mvebu/clk-cpu.c index 26d52ecaac50..24196e288fe2 100644 --- a/drivers/clk/mvebu/clk-cpu.c +++ b/drivers/clk/mvebu/clk-cpu.c @@ -153,7 +153,7 @@ static int clk_cpu_on_set_rate(struct clk_hw *hwclk, unsigned long rate, static int clk_cpu_set_rate(struct clk_hw *hwclk, unsigned long rate, unsigned long parent_rate) { - if (__clk_is_enabled(hwclk->clk)) + if (clk_hw_is_enabled(hwclk)) return clk_cpu_on_set_rate(hwclk, rate, parent_rate); else return clk_cpu_off_set_rate(hwclk, rate, parent_rate); From 227d023952058976bf336a36079d49b8a50c9b02 Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Fri, 24 Jul 2026 11:03:56 +0200 Subject: [PATCH 0268/1328] clk: st: clean-up simple provider misuse of the consumer API Clock provider should not be using the consumer interface. In other words, a provider should not be dealing with struct clk. This change targets occurrences for which the provider uses the consumer interface and corresponding clk_hw interface exist. Reviewed-by: Brian Masney Signed-off-by: Jerome Brunet Reviewed-by: Thierry Reding Signed-off-by: Brian Masney --- drivers/clk/st/clkgen-pll.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/clk/st/clkgen-pll.c b/drivers/clk/st/clkgen-pll.c index 9eb2cb83fa69..17551c6a879a 100644 --- a/drivers/clk/st/clkgen-pll.c +++ b/drivers/clk/st/clkgen-pll.c @@ -260,7 +260,7 @@ static int __clkgen_pll_enable(struct clk_hw *hw) if (pll->data->switch2pll_en) CLKGEN_WRITE(pll, switch2pll, 0); - pr_debug("%s:%s enabled\n", __clk_get_name(hw->clk), __func__); + pr_debug("%s:%s enabled\n", clk_hw_get_name(hw), __func__); } return ret; @@ -295,7 +295,7 @@ static void __clkgen_pll_disable(struct clk_hw *hw) CLKGEN_WRITE(pll, pdn_ctrl, 1); - pr_debug("%s:%s disabled\n", __clk_get_name(hw->clk), __func__); + pr_debug("%s:%s disabled\n", clk_hw_get_name(hw), __func__); } static void clkgen_pll_disable(struct clk_hw *hw) @@ -405,14 +405,14 @@ static int stm_pll3200c32_determine_rate(struct clk_hw *hw, &req->rate); else { pr_debug("%s: %s rate %ld Invalid\n", __func__, - __clk_get_name(hw->clk), req->rate); + clk_hw_get_name(hw), req->rate); req->rate = 0; return 0; } pr_debug("%s: %s new rate %ld [ndiv=%u] [idf=%u]\n", - __func__, __clk_get_name(hw->clk), + __func__, clk_hw_get_name(hw), req->rate, (unsigned int)params.ndiv, (unsigned int)params.idf); @@ -434,7 +434,7 @@ static int set_rate_stm_pll3200c32(struct clk_hw *hw, unsigned long rate, clk_pll3200c32_get_rate(parent_rate, ¶ms, &hwrate); pr_debug("%s: %s new rate %ld [ndiv=0x%x] [idf=0x%x]\n", - __func__, __clk_get_name(hw->clk), + __func__, clk_hw_get_name(hw), hwrate, (unsigned int)params.ndiv, (unsigned int)params.idf); @@ -547,7 +547,7 @@ static unsigned long recalc_stm_pll4600c28(struct clk_hw *hw, clk_pll4600c28_get_rate(parent_rate, ¶ms, &rate); - pr_debug("%s:%s rate %lu\n", __clk_get_name(hw->clk), __func__, rate); + pr_debug("%s:%s rate %lu\n", clk_hw_get_name(hw), __func__, rate); return rate; } @@ -562,14 +562,14 @@ static int stm_pll4600c28_determine_rate(struct clk_hw *hw, &req->rate); } else { pr_debug("%s: %s rate %ld Invalid\n", __func__, - __clk_get_name(hw->clk), req->rate); + clk_hw_get_name(hw), req->rate); req->rate = 0; return 0; } pr_debug("%s: %s new rate %ld [ndiv=%u] [idf=%u]\n", - __func__, __clk_get_name(hw->clk), + __func__, clk_hw_get_name(hw), req->rate, (unsigned int)params.ndiv, (unsigned int)params.idf); @@ -591,12 +591,12 @@ static int set_rate_stm_pll4600c28(struct clk_hw *hw, unsigned long rate, clk_pll4600c28_get_rate(parent_rate, ¶ms, &hwrate); } else { pr_debug("%s: %s rate %ld Invalid\n", __func__, - __clk_get_name(hw->clk), rate); + clk_hw_get_name(hw), rate); return -EINVAL; } pr_debug("%s: %s new rate %ld [ndiv=0x%x] [idf=0x%x]\n", - __func__, __clk_get_name(hw->clk), + __func__, clk_hw_get_name(hw), hwrate, (unsigned int)params.ndiv, (unsigned int)params.idf); From 3ed463b69be8120d8f674051f0fe3304ef9839cc Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Fri, 24 Jul 2026 11:03:57 +0200 Subject: [PATCH 0269/1328] clk: tegra: clean-up simple provider misuse of the consumer API Clock provider should not be using the consumer interface. In other words, a provider should not be dealing with struct clk. This change targets occurrences for which the provider uses the consumer interface and corresponding clk_hw interface exist. Reviewed-by: Brian Masney Signed-off-by: Jerome Brunet Reviewed-by: Thierry Reding Signed-off-by: Brian Masney --- drivers/clk/tegra/clk-tegra210.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/tegra/clk-tegra210.c b/drivers/clk/tegra/clk-tegra210.c index 0c86cff719e3..df1d7b471745 100644 --- a/drivers/clk/tegra/clk-tegra210.c +++ b/drivers/clk/tegra/clk-tegra210.c @@ -1460,7 +1460,7 @@ static int tegra210_pllx_dyn_ramp(struct tegra_clk_pll *pllx, udelay(1); pr_debug("%s: dynamic ramp to m = %u n = %u p = %u, Fout = %lu kHz\n", - __clk_get_name(pllx->hw.clk), cfg->m, cfg->n, cfg->p, + clk_hw_get_name(&pllx->hw), cfg->m, cfg->n, cfg->p, cfg->input_rate / cfg->m * cfg->n / pllx->params->pdiv_tohw[cfg->p].pdiv / 1000); From 442d24ce7f4c09a7d14b949d7a6b640bd63cc4e0 Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Tue, 28 Jul 2026 17:17:59 +0800 Subject: [PATCH 0270/1328] dt-bindings: clock: mediatek: Add mt8173 mfgtop The MFG (GPU) block on the MT8173 has a small glue layer, named MFG_TOP in the datasheet, that contains clock gates, some power sequence signal delays, and other unknown registers that get toggled when the GPU is powered on. The clock gates are exposed as clocks provided by a clock controller, while the power sequencing bits are exposed as one singular power domain. Reviewed-by: Conor Dooley Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Chen-Yu Tsai Signed-off-by: Brian Masney --- .../clock/mediatek,mt8173-mfgtop.yaml | 70 +++++++++++++++++++ include/dt-bindings/clock/mt8173-clk.h | 7 ++ 2 files changed, 77 insertions(+) create mode 100644 Documentation/devicetree/bindings/clock/mediatek,mt8173-mfgtop.yaml diff --git a/Documentation/devicetree/bindings/clock/mediatek,mt8173-mfgtop.yaml b/Documentation/devicetree/bindings/clock/mediatek,mt8173-mfgtop.yaml new file mode 100644 index 000000000000..03db1ee9e594 --- /dev/null +++ b/Documentation/devicetree/bindings/clock/mediatek,mt8173-mfgtop.yaml @@ -0,0 +1,70 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- + +$id: http://devicetree.org/schemas/clock/mediatek,mt8173-mfgtop.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: MediaTek MT8173 MFG TOP controller + +maintainers: + - AngeloGioacchino Del Regno + +description: + The MFG TOP glue layer controls various signals going to the MFG (GPU) + block on the MT8173. + +properties: + compatible: + const: mediatek,mt8173-mfgtop + + reg: + maxItems: 1 + + clocks: + maxItems: 4 + + clock-names: + items: + - const: sys + - const: mem + - const: core + - const: clk26m + + power-domains: + maxItems: 1 + + '#clock-cells': + const: 1 + + '#power-domain-cells': + const: 0 + +required: + - compatible + - reg + - clocks + - clock-names + - power-domains + - '#clock-cells' + - '#power-domain-cells' + +additionalProperties: false + +examples: + - | + #include + #include + + clock-controller@13fff000 { + compatible = "mediatek,mt8173-mfgtop"; + reg = <0x13fff000 0x1000>; + clocks = <&topckgen CLK_TOP_AXI_MFG_IN_SEL>, + <&topckgen CLK_TOP_MEM_MFG_IN_SEL>, + <&topckgen CLK_TOP_MFG_SEL>, + <&clk26m>; + clock-names = "sys", "mem", "core", "clk26m"; + power-domains = <&spm MT8173_POWER_DOMAIN_MFG>; + #clock-cells = <1>; + #power-domain-cells = <0>; + }; diff --git a/include/dt-bindings/clock/mt8173-clk.h b/include/dt-bindings/clock/mt8173-clk.h index 3d00c98b9654..89e982f771db 100644 --- a/include/dt-bindings/clock/mt8173-clk.h +++ b/include/dt-bindings/clock/mt8173-clk.h @@ -243,6 +243,13 @@ #define CLK_IMG_FD 7 #define CLK_IMG_NR_CLK 8 +/* MFG_SYS */ + +#define CLK_MFG_AXI 0 +#define CLK_MFG_MEM 1 +#define CLK_MFG_G3D 2 +#define CLK_MFG_26M 3 + /* MM_SYS */ #define CLK_MM_SMI_COMMON 1 From ebd0b73d2137c2277440da04124a0ce7011b2d1a Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Tue, 28 Jul 2026 17:18:00 +0800 Subject: [PATCH 0271/1328] clk: mediatek: Add mt8173-mfgtop driver The MFG (GPU) block on the MT8173 has a small glue layer, named MFG_TOP in the datasheet, that contains clock gates, some power sequence signal delays, and other unknown registers that get toggled when the GPU is powered on. The clock gates are exposed as clocks provided by a clock controller, while the power sequencing bits are exposed as one singular power domain. Tested-by: Icenowy Zheng Reviewed-by: Brian Masney Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Chen-Yu Tsai Signed-off-by: Brian Masney --- drivers/clk/mediatek/Kconfig | 10 + drivers/clk/mediatek/Makefile | 1 + drivers/clk/mediatek/clk-mt8173-mfgtop.c | 242 +++++++++++++++++++++++ 3 files changed, 253 insertions(+) create mode 100644 drivers/clk/mediatek/clk-mt8173-mfgtop.c diff --git a/drivers/clk/mediatek/Kconfig b/drivers/clk/mediatek/Kconfig index 2c09fd729bab..8eea3766a910 100644 --- a/drivers/clk/mediatek/Kconfig +++ b/drivers/clk/mediatek/Kconfig @@ -537,6 +537,16 @@ config COMMON_CLK_MT8173_IMGSYS help This driver supports MediaTek MT8173 imgsys clocks. +config COMMON_CLK_MT8173_MFGTOP + tristate "Clock and power driver for MediaTek MT8173 mfgtop" + depends on COMMON_CLK_MT8173 + depends on PM + default COMMON_CLK_MT8173 + select PM_GENERIC_DOMAINS + select PM_GENERIC_DOMAINS_OF + help + This driver supports MediaTek MT8173 mfgtop clocks and power domain. + config COMMON_CLK_MT8173_MMSYS tristate "Clock driver for MediaTek MT8173 mmsys" depends on COMMON_CLK_MT8173 diff --git a/drivers/clk/mediatek/Makefile b/drivers/clk/mediatek/Makefile index d8736a060dbd..892a54eeb281 100644 --- a/drivers/clk/mediatek/Makefile +++ b/drivers/clk/mediatek/Makefile @@ -82,6 +82,7 @@ obj-$(CONFIG_COMMON_CLK_MT8167_VDECSYS) += clk-mt8167-vdec.o obj-$(CONFIG_COMMON_CLK_MT8173) += clk-mt8173-apmixedsys.o clk-mt8173-infracfg.o \ clk-mt8173-pericfg.o clk-mt8173-topckgen.o obj-$(CONFIG_COMMON_CLK_MT8173_IMGSYS) += clk-mt8173-img.o +obj-$(CONFIG_COMMON_CLK_MT8173_MFGTOP) += clk-mt8173-mfgtop.o obj-$(CONFIG_COMMON_CLK_MT8173_MMSYS) += clk-mt8173-mm.o obj-$(CONFIG_COMMON_CLK_MT8173_VDECSYS) += clk-mt8173-vdecsys.o obj-$(CONFIG_COMMON_CLK_MT8173_VENCSYS) += clk-mt8173-vencsys.o diff --git a/drivers/clk/mediatek/clk-mt8173-mfgtop.c b/drivers/clk/mediatek/clk-mt8173-mfgtop.c new file mode 100644 index 000000000000..5669ca9954dc --- /dev/null +++ b/drivers/clk/mediatek/clk-mt8173-mfgtop.c @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2024 Google LLC + * Author: Chen-Yu Tsai + * + * Based on driver in downstream ChromeOS v5.15 kernel. + * + * Copyright (c) 2014 MediaTek Inc. + * Author: Chiawen Lee + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "clk-gate.h" +#include "clk-mtk.h" + +static const struct mtk_gate_regs mfg_cg_regs = { + .sta_ofs = 0x0000, + .clr_ofs = 0x0008, + .set_ofs = 0x0004, +}; + +#define GATE_MFG(_id, _name, _parent, _shift, _flags) \ + GATE_MTK_FLAGS(_id, _name, _parent, &mfg_cg_regs, _shift, &mtk_clk_gate_ops_setclr, _flags) + +/* TODO: The block actually has dividers for the core and mem clocks. */ +static const struct mtk_gate mfg_clks[] = { + GATE_MFG(CLK_MFG_AXI, "mfg_axi", "axi_mfg_in_sel", 0, CLK_SET_RATE_PARENT), + GATE_MFG(CLK_MFG_MEM, "mfg_mem", "mem_mfg_in_sel", 1, CLK_SET_RATE_PARENT), + GATE_MFG(CLK_MFG_G3D, "mfg_g3d", "mfg_sel", 2, CLK_SET_RATE_PARENT), + GATE_MFG(CLK_MFG_26M, "mfg_26m", "clk26m", 3, 0), +}; + +struct mt8173_mfgtop_data { + struct clk_hw_onecell_data *clk_data; + struct regmap *regmap; + struct generic_pm_domain genpd; + struct of_phandle_args parent_pd, child_pd; + struct clk *clk_26m; +}; + +/* Delay count in clock cycles */ +#define MFG_ACTIVE_POWER_CON0 0x24 + #define RST_B_DELAY_CNT GENMASK(7, 0) /* pwr_rst_b de-assert delay during power-up */ + #define CLK_EN_DELAY_CNT GENMASK(15, 8) /* CLK_DIS deassert delay during power-up */ + #define CLK_DIS_DELAY_CNT GENMASK(23, 16) /* CLK_DIS assert delay during power-down */ + #define FORCE_ABORT BIT(30) /* write 1 to force abort a power event */ + #define ACTIVE_PWRCTL_EN BIT(31) /* enable ACTIVE_POWER */ + +#define MFG_ACTIVE_POWER_CON1 0x28 + #define PWR_ON_S_DELAY_CNT GENMASK(7, 0) /* pwr_on_s assert delay during power-up */ + #define ISO_DELAY_CNT GENMASK(15, 8) /* ISO assert delay during power-down */ + #define ISOOFF_DELAY_CNT GENMASK(23, 16) /* ISO de-assert delay during power-up */ + #define RST_DELAY_CNT GENMASK(31, 24) /* pwr_rsb_b assert delay during power-down */ + +static int clk_mt8173_mfgtop_power_on(struct generic_pm_domain *domain) +{ + struct mt8173_mfgtop_data *data = container_of(domain, struct mt8173_mfgtop_data, genpd); + int ret; + + /* drives internal power management */ + ret = clk_prepare_enable(data->clk_26m); + if (ret) + return ret; + + /* Power on/off delays for various signals */ + regmap_write(data->regmap, MFG_ACTIVE_POWER_CON0, + FIELD_PREP(RST_B_DELAY_CNT, 77) | + FIELD_PREP(CLK_EN_DELAY_CNT, 61) | + FIELD_PREP(CLK_DIS_DELAY_CNT, 60) | + FIELD_PREP(ACTIVE_PWRCTL_EN, 0)); + regmap_write(data->regmap, MFG_ACTIVE_POWER_CON1, + FIELD_PREP(PWR_ON_S_DELAY_CNT, 11) | + FIELD_PREP(ISO_DELAY_CNT, 68) | + FIELD_PREP(ISOOFF_DELAY_CNT, 69) | + FIELD_PREP(RST_DELAY_CNT, 77)); + + /* Magic numbers related to core switch sequence and delays */ + regmap_write(data->regmap, 0xe0, 0x7a710184); + regmap_write(data->regmap, 0xe4, 0x835f6856); + regmap_write(data->regmap, 0xe8, 0x002b0234); + regmap_write(data->regmap, 0xec, 0x80000000); + regmap_write(data->regmap, 0xa0, 0x08000000); + + return 0; +} + +static int clk_mt8173_mfgtop_power_off(struct generic_pm_domain *domain) +{ + struct mt8173_mfgtop_data *data = container_of(domain, struct mt8173_mfgtop_data, genpd); + + /* Magic numbers related to core switch sequence and delays */ + regmap_write(data->regmap, 0xec, 0); + + /* drives internal power management */ + clk_disable_unprepare(data->clk_26m); + + return 0; +} + +static int clk_mt8173_mfgtop_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct mt8173_mfgtop_data *data; + int ret; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + platform_set_drvdata(pdev, data); + + data->clk_data = mtk_devm_alloc_clk_data(dev, ARRAY_SIZE(mfg_clks)); + if (!data->clk_data) + return -ENOMEM; + + /* MTK clock gates also uses regmap */ + data->regmap = device_node_to_regmap(node); + if (IS_ERR(data->regmap)) + return dev_err_probe(dev, PTR_ERR(data->regmap), "Failed to get regmap\n"); + + data->child_pd.np = node; + data->child_pd.args_count = 0; + ret = of_parse_phandle_with_args(node, "power-domains", "#power-domain-cells", 0, + &data->parent_pd); + if (ret) + return dev_err_probe(dev, ret, "Failed to parse power domain\n"); + + devm_pm_runtime_enable(dev); + /* + * Do a pm_runtime_resume_and_get() to workaround a possible + * deadlock between clk_register() and the genpd framework. + */ + ret = pm_runtime_resume_and_get(dev); + if (ret) { + dev_err_probe(dev, ret, "Failed to runtime resume device\n"); + goto put_of_node; + } + + ret = mtk_clk_register_gates(dev, node, mfg_clks, ARRAY_SIZE(mfg_clks), + data->clk_data); + if (ret) { + dev_err_probe(dev, ret, "Failed to register clock gates\n"); + goto put_pm_runtime; + } + + data->clk_26m = clk_hw_get_clk(data->clk_data->hws[CLK_MFG_26M], "26m"); + if (IS_ERR(data->clk_26m)) { + ret = dev_err_probe(dev, PTR_ERR(data->clk_26m), "Failed to get 26 MHz clock\n"); + goto unregister_clks; + } + + ret = of_clk_add_hw_provider(node, of_clk_hw_onecell_get, data->clk_data); + if (ret) { + dev_err_probe(dev, ret, "Failed to add clk OF provider\n"); + goto put_26m_clk; + } + + data->genpd.name = "mfg-top"; + data->genpd.power_on = clk_mt8173_mfgtop_power_on; + data->genpd.power_off = clk_mt8173_mfgtop_power_off; + ret = pm_genpd_init(&data->genpd, NULL, true); + if (ret) { + dev_err_probe(dev, ret, "Failed to add power domain\n"); + goto del_clk_provider; + } + + ret = of_genpd_add_provider_simple(node, &data->genpd); + if (ret) { + dev_err_probe(dev, ret, "Failed to add power domain OF provider\n"); + goto remove_pd; + } + + ret = of_genpd_add_subdomain(&data->parent_pd, &data->child_pd); + if (ret) { + dev_err_probe(dev, ret, "Failed to link PM domains\n"); + goto del_pd_provider; + } + + pm_runtime_put(dev); + return 0; + +del_pd_provider: + of_genpd_del_provider(node); +remove_pd: + pm_genpd_remove(&data->genpd); +del_clk_provider: + of_clk_del_provider(node); +put_26m_clk: + clk_put(data->clk_26m); +unregister_clks: + mtk_clk_unregister_gates(mfg_clks, ARRAY_SIZE(mfg_clks), data->clk_data); +put_pm_runtime: + pm_runtime_put_sync(dev); +put_of_node: + of_node_put(data->parent_pd.np); + return ret; +} + +static void clk_mt8173_mfgtop_remove(struct platform_device *pdev) +{ + struct mt8173_mfgtop_data *data = platform_get_drvdata(pdev); + struct device_node *node = pdev->dev.of_node; + + of_genpd_remove_subdomain(&data->parent_pd, &data->child_pd); + of_genpd_del_provider(node); + pm_genpd_remove(&data->genpd); + of_clk_del_provider(node); + clk_put(data->clk_26m); + mtk_clk_unregister_gates(mfg_clks, ARRAY_SIZE(mfg_clks), data->clk_data); + of_node_put(data->parent_pd.np); +} + +static const struct of_device_id of_match_clk_mt8173_mfgtop[] = { + { .compatible = "mediatek,mt8173-mfgtop" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, of_match_clk_mt8173_mfgtop); + +static struct platform_driver clk_mt8173_mfgtop_drv = { + .probe = clk_mt8173_mfgtop_probe, + .remove = clk_mt8173_mfgtop_remove, + .driver = { + .name = "clk-mt8173-mfgtop", + .of_match_table = of_match_clk_mt8173_mfgtop, + }, +}; +module_platform_driver(clk_mt8173_mfgtop_drv); + +MODULE_DESCRIPTION("MediaTek MT8173 mfgtop clock driver"); +MODULE_LICENSE("GPL"); From 51be18310cd82fc4f1692d9cb7c4f53d26cacd74 Mon Sep 17 00:00:00 2001 From: Ryan Chen Date: Wed, 24 Jun 2026 13:44:59 +0800 Subject: [PATCH 0272/1328] dt-bindings: clock: ast2700: add PECI clock Add SCU1_CLK_PECI for the SoC1 PECI controller clock source, and SCU1_CLK_HPLL_DIV4 which serves as one of the PECI clock mux parents. Signed-off-by: Ryan Chen Acked-by: Conor Dooley Signed-off-by: Brian Masney --- include/dt-bindings/clock/aspeed,ast2700-scu.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/dt-bindings/clock/aspeed,ast2700-scu.h b/include/dt-bindings/clock/aspeed,ast2700-scu.h index bacf712e8e04..138f78ce5f07 100644 --- a/include/dt-bindings/clock/aspeed,ast2700-scu.h +++ b/include/dt-bindings/clock/aspeed,ast2700-scu.h @@ -163,5 +163,7 @@ #define SCU1_CLK_GATE_PORTDUSB2CLK 85 #define SCU1_CLK_GATE_LTPI1TXCLK 86 #define SCU1_CLK_I3C 87 +#define SCU1_CLK_HPLL_DIV4 88 +#define SCU1_CLK_PECI 89 #endif From 5ffa9d308c7a30192f1b05ee0bfd9e1976493a54 Mon Sep 17 00:00:00 2001 From: Ryan Chen Date: Wed, 24 Jun 2026 13:45:00 +0800 Subject: [PATCH 0273/1328] clk: aspeed: add AST2700 PECI clock Register the SoC1 PECI clock as a mux selected by SCU1_CLK_SEL2 bit 16, choosing between the 25MHz CLKIN and HPLL/4. Add the HPLL/4 fixed factor clock to serve as the second mux parent. Signed-off-by: Ryan Chen Signed-off-by: Brian Masney --- drivers/clk/aspeed/clk-ast2700.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/clk/aspeed/clk-ast2700.c b/drivers/clk/aspeed/clk-ast2700.c index 8b7b382f6f3e..5f499ec0e1d1 100644 --- a/drivers/clk/aspeed/clk-ast2700.c +++ b/drivers/clk/aspeed/clk-ast2700.c @@ -273,6 +273,13 @@ static const unsigned int sdclk_parent_ids[] = { static const struct clk_hw *sdclk_parent_hws[ARRAY_SIZE(sdclk_parent_ids)]; +static const unsigned int peciclk_parent_ids[] = { + SCU1_CLKIN, + SCU1_CLK_HPLL_DIV4 +}; + +static const struct clk_hw *peciclk_parent_hws[ARRAY_SIZE(peciclk_parent_ids)]; + #define FIXED_CLK(_id, _name, _rate) \ { \ .id = _id, \ @@ -458,6 +465,7 @@ static const struct ast2700_clk_info ast2700_scu1_clk_info[] __initconst = { PLL_CLK(SCU1_CLK_HPLL, CLK_PLL, "soc1-hpll", SCU1_CLKIN, SCU1_HPLL_PARAM), PLL_CLK(SCU1_CLK_APLL, CLK_PLL, "soc1-apll", SCU1_CLKIN, SCU1_APLL_PARAM), PLL_CLK(SCU1_CLK_DPLL, CLK_PLL, "soc1-dpll", SCU1_CLKIN, SCU1_DPLL_PARAM), + FIXED_FACTOR_CLK(SCU1_CLK_HPLL_DIV4, "soc1-hpll_div4", SCU1_CLK_HPLL, 1, 4), FIXED_FACTOR_CLK(SCU1_CLK_APLL_DIV2, "soc1-apll_div2", SCU1_CLK_APLL, 1, 2), FIXED_FACTOR_CLK(SCU1_CLK_APLL_DIV4, "soc1-apll_div4", SCU1_CLK_APLL, 1, 4), FIXED_FACTOR_CLK(SCU1_CLK_CAN, "canclk", SCU1_CLK_APLL, 1, 10), @@ -481,6 +489,8 @@ static const struct ast2700_clk_info ast2700_scu1_clk_info[] __initconst = { uxclk_parent_hws, SCU1_CLK_SEL2, 0, 2), MUX_CLK(SCU1_CLK_HUXCLK, "huxclk", uxclk_parent_ids, ARRAY_SIZE(uxclk_parent_ids), uxclk_parent_hws, SCU1_CLK_SEL2, 3, 2), + MUX_CLK(SCU1_CLK_PECI, "peciclk", peciclk_parent_ids, ARRAY_SIZE(peciclk_parent_ids), + peciclk_parent_hws, SCU1_CLK_SEL2, 16, 1), DIVIDER_CLK(SCU1_CLK_SDCLK, "sdclk", SCU1_CLK_SDMUX, SCU1_CLK_SEL1, 14, 3, ast2700_clk_div_table), PLL_CLK(SCU1_CLK_UARTX, CLK_UART_PLL, "uartxclk", SCU1_CLK_UXCLK, SCU1_UXCLK_CTRL), From 46e31d69a547530fbef499bde888b6bb45a1deac Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Thu, 25 Jun 2026 11:30:52 +0300 Subject: [PATCH 0274/1328] clk: tegra: support 48MHz clock for pll_p_out1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UEFI on Surface2 sets pll_p_out1 to 48MHz which is not supported by kernel and causes BUG() early on. Add 48MHz clock support for pll_p_out1. Acked-by: Thierry Reding Signed-off-by: Dmitry Osipenko Signed-off-by: Jonas Schwöbel Signed-off-by: Svyatoslav Ryhel Signed-off-by: Brian Masney --- drivers/clk/tegra/clk-pll.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/clk/tegra/clk-pll.c b/drivers/clk/tegra/clk-pll.c index d86003b6d94f..adfb74f111ef 100644 --- a/drivers/clk/tegra/clk-pll.c +++ b/drivers/clk/tegra/clk-pll.c @@ -575,6 +575,7 @@ static int _calc_rate(struct clk_hw *hw, struct tegra_clk_pll_freq_table *cfg, break; case 9600000: case 28800000: + case 48000000: /* * PLL_P_OUT1 rate is not listed in PLLA table */ From d85028afefad94b4b2f9f7f36997f18d7e8984d2 Mon Sep 17 00:00:00 2001 From: Shubhrajyoti Datta Date: Mon, 29 Jun 2026 11:21:46 +0530 Subject: [PATCH 0275/1328] clk: clocking-wizard: Program PLL CP/RES and lock parameters on reconfig When dynamically reconfiguring the PLL multiplier, the charge pump (CP), loop filter resistance (RES), and lock timing parameters must be updated to match the new multiplier value. Without this, the PLL may fail to lock or exhibit jitter at certain multiply factors. Add lookup tables for CP/RES and lock timing values indexed by multiplier range, and program registers for chargepump and lock delay during dynamic reconfiguration. Signed-off-by: Shubhrajyoti Datta Signed-off-by: Brian Masney --- drivers/clk/xilinx/clk-xlnx-clock-wizard.c | 134 +++++++++++++++++++++ 1 file changed, 134 insertions(+) diff --git a/drivers/clk/xilinx/clk-xlnx-clock-wizard.c b/drivers/clk/xilinx/clk-xlnx-clock-wizard.c index 4a0136349f71..f5d14543167a 100644 --- a/drivers/clk/xilinx/clk-xlnx-clock-wizard.c +++ b/drivers/clk/xilinx/clk-xlnx-clock-wizard.c @@ -35,6 +35,10 @@ #define WZRD_DIVCLK 21 #define WZRD_CLKFBOUT_4 51 #define WZRD_CLKFBOUT_3 48 +#define WZRD_CP 18 +#define WZRD_LOCK 27 +#define WZRD_LOCK_REF_DLY 28 +#define WZRD_RES 30 #define WZRD_DUTY_CYCLE 2 #define WZRD_O_DIV 4 @@ -49,6 +53,10 @@ #define WZRD_P5FEDGE_SHIFT 15 #define WZRD_CLKOUT0_PREDIV2 BIT(11) #define WZRD_EDGE_SHIFT 8 +#define WZRD_CP_MASK GENMASK(3, 0) +#define WZRD_RES_MASK GENMASK(4, 1) +#define WZRD_LOCK_FB_DLY_MASK GENMASK(14, 10) +#define WZRD_LOCK_REF_DLY_LOCK_REF_DLY_MASK GENMASK(14, 10) #define WZRD_CLKFBOUT_MULT_SHIFT 8 #define WZRD_CLKFBOUT_MULT_MASK (0xff << WZRD_CLKFBOUT_MULT_SHIFT) @@ -440,6 +448,130 @@ static int clk_wzrd_reconfig(struct clk_wzrd_divider *divider, void __iomem *div WZRD_USEC_POLL, WZRD_TIMEOUT_POLL); } +struct wzrd_pll_filter { + u32 m_min; + u32 m_max; + u32 cp; + u32 res; +}; + +static const struct wzrd_pll_filter wzrd_cp_res_table[] = { + { 4, 4, 5, 15 }, + { 5, 5, 6, 15 }, + { 6, 6, 7, 15 }, + { 7, 7, 13, 15 }, + { 8, 8, 14, 15 }, + { 9, 9, 15, 15 }, + { 10, 10, 14, 7 }, + { 11, 11, 15, 7 }, + { 12, 13, 15, 11 }, + { 14, 14, 15, 13 }, + { 15, 15, 15, 3 }, + { 16, 17, 14, 5 }, + { 18, 19, 15, 5 }, + { 20, 21, 15, 9 }, + { 22, 23, 14, 14 }, + { 24, 26, 15, 14 }, + { 27, 28, 14, 1 }, + { 29, 33, 15, 1 }, + { 34, 37, 14, 6 }, + { 38, 44, 15, 6 }, + { 45, 57, 15, 10 }, + { 58, 63, 13, 12 }, + { 64, 70, 14, 12 }, + { 71, 86, 15, 12 }, + { 87, 94, 14, 2 }, + { 95, 145, 15, 2 }, + { 146, 163, 12, 4 }, + { 164, 181, 13, 4 }, + { 182, 200, 14, 4 }, + { 201, 273, 15, 4 }, + { 274, 300, 13, 8 }, + { 301, 325, 14, 8 }, + { 326, 432, 15, 8 }, +}; + +struct wzrd_lock_timing { + u32 m_min; + u32 m_max; + u32 ref_dly; + u32 fb_dly; + u32 lock_cnt; +}; + +static const struct wzrd_lock_timing wzrd_lock_table[] = { + { 4, 4, 4, 4, 1000 }, + { 5, 5, 6, 6, 1000 }, + { 6, 8, 7, 7, 1000 }, + { 9, 12, 8, 8, 1000 }, + { 13, 13, 10, 10, 1000 }, + { 14, 16, 13, 13, 1000 }, + { 17, 17, 16, 16, 825 }, + { 18, 18, 16, 16, 750 }, + { 19, 20, 16, 16, 700 }, + { 21, 21, 16, 16, 650 }, + { 22, 23, 16, 16, 625 }, + { 24, 24, 16, 16, 575 }, + { 25, 25, 16, 16, 550 }, + { 26, 28, 16, 16, 525 }, + { 29, 30, 16, 16, 475 }, + { 31, 31, 16, 16, 450 }, + { 32, 33, 16, 16, 425 }, + { 34, 36, 16, 16, 400 }, + { 37, 37, 16, 16, 375 }, + { 38, 40, 16, 16, 350 }, + { 41, 43, 16, 16, 325 }, + { 44, 47, 16, 16, 300 }, + { 48, 51, 16, 16, 275 }, + { 52, 205, 16, 16, 250 }, + { 206, 432, 16, 16, 225 }, +}; + +static void clk_wzrd_update_cp_res_lock(struct clk_wzrd_divider *divider, u32 m) +{ + u32 lock_ref_dly = 16, lock_fb_dly = 16, lock_cnt = 250, cp = 15, res = 15; + void __iomem *base = divider->base; + u32 reg; + int i; + + for (i = 0; i < ARRAY_SIZE(wzrd_cp_res_table); i++) { + if (m >= wzrd_cp_res_table[i].m_min && + m <= wzrd_cp_res_table[i].m_max) { + cp = wzrd_cp_res_table[i].cp; + res = wzrd_cp_res_table[i].res; + break; + } + } + + for (i = 0; i < ARRAY_SIZE(wzrd_lock_table); i++) { + if (m >= wzrd_lock_table[i].m_min && + m <= wzrd_lock_table[i].m_max) { + lock_ref_dly = wzrd_lock_table[i].ref_dly; + lock_fb_dly = wzrd_lock_table[i].fb_dly; + lock_cnt = wzrd_lock_table[i].lock_cnt; + break; + } + } + + reg = readl(base + WZRD_CLK_CFG_REG(1, WZRD_CP)); + reg &= ~WZRD_CP_MASK; + reg |= FIELD_PREP(WZRD_CP_MASK, cp); + writel(reg, base + WZRD_CLK_CFG_REG(1, WZRD_CP)); + + reg = readl(base + WZRD_CLK_CFG_REG(1, WZRD_RES)); + reg &= ~WZRD_RES_MASK; + reg |= FIELD_PREP(WZRD_RES_MASK, res); + writel(reg, base + WZRD_CLK_CFG_REG(1, WZRD_RES)); + + reg = lock_cnt | FIELD_PREP(WZRD_LOCK_FB_DLY_MASK, lock_fb_dly); + writel(reg, base + WZRD_CLK_CFG_REG(1, WZRD_LOCK)); + + reg = readl(base + WZRD_CLK_CFG_REG(1, WZRD_LOCK_REF_DLY)); + reg &= ~WZRD_LOCK_REF_DLY_LOCK_REF_DLY_MASK; + reg |= FIELD_PREP(WZRD_LOCK_REF_DLY_LOCK_REF_DLY_MASK, lock_ref_dly); + writel(reg, base + WZRD_CLK_CFG_REG(1, WZRD_LOCK_REF_DLY)); +} + static int clk_wzrd_dynamic_ver_all_nolock(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { @@ -471,6 +603,8 @@ static int clk_wzrd_dynamic_ver_all_nolock(struct clk_hw *hw, unsigned long rate writel(regval1, divider->base + WZRD_CLK_CFG_REG(1, WZRD_CLKFBOUT_2)); + clk_wzrd_update_cp_res_lock(divider, m); + value2 = divider->d; edged = value2 % WZRD_DUTY_CYCLE; regh = (value2 / WZRD_DUTY_CYCLE); From fd0e3e4edea6a3e4da91be608ca2fb9b348f9e32 Mon Sep 17 00:00:00 2001 From: Akari Tsuyukusa Date: Mon, 29 Jun 2026 22:20:43 +0900 Subject: [PATCH 0276/1328] clk: mediatek: mt8135: Fix inverted gate control for devapc_ck The devapc_ck (CLK_INFRA_DEVAPC) on MT8135 is currently using "mtk_clk_gate_ops_setclr". However, checking the downstream kernel reveals that this clock is configured with set:enable and clr:disable making "mtk_clk_gate_ops_setclr_inv" the appropriate choice. But, it is strange that some downstream kernels are not like that. Amazon: INV ChromiumOS (early): not INV ChromiumOS 3.16 to 3.18-revew-v2: INV ChromiumOS 3.18-review-v3 and later (sent to kernel.org): not INV Link: https://github.com/amazon-oss/android_kernel_amazon_mt8135/blob/e2b2163a8ec4a7c8d961c89003a15b4ba0f0e371/arch/arm/mach-mt8135/mt_clkmgr.c#L1022-L1028 Link: https://github.com/mtk09422/chromiumos-third_party-kernel-mediatek/blob/4b624ee66e65d5dcd43fca36b313086efae8922a/arch/arm/boot/dts/mt8135-clocks.dtsi#L944-L948 Link: https://github.com/mtk09422/chromiumos-third_party-kernel-mediatek/blob/decd80c01d0dbe9f3afa8ff72273b5618b418180/drivers/clk/mediatek/clk-mt8135.c#L881-L882 Link: https://github.com/mtk09422/chromiumos-third_party-kernel-mediatek/blob/9b6f06cb7637100aa1a42e1fc351b36b384a1c54/drivers/clk/mediatek/clk-mt8135.c#L450 Fixes: a8aede794843 ("clk: mediatek: Add basic clocks for Mediatek MT8135.") Signed-off-by: Akari Tsuyukusa Signed-off-by: Brian Masney --- drivers/clk/mediatek/clk-mt8135.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/clk/mediatek/clk-mt8135.c b/drivers/clk/mediatek/clk-mt8135.c index 084e48a554c2..1d20e15608f7 100644 --- a/drivers/clk/mediatek/clk-mt8135.c +++ b/drivers/clk/mediatek/clk-mt8135.c @@ -409,6 +409,9 @@ static const struct mtk_gate_regs infra_cg_regs = { GATE_MTK_FLAGS(_id, _name, _parent, &infra_cg_regs, _shift, \ &mtk_clk_gate_ops_setclr, CLK_IS_CRITICAL) +#define GATE_ICG_INV(_id, _name, _parent, _shift) \ + GATE_MTK(_id, _name, _parent, &infra_cg_regs, _shift, &mtk_clk_gate_ops_setclr_inv) + static const struct mtk_gate infra_clks[] = { GATE_DUMMY(CLK_DUMMY, "infra_dummy"), GATE_ICG(CLK_INFRA_PMIC_WRAP, "pmic_wrap_ck", "axi_sel", 23), @@ -419,7 +422,7 @@ static const struct mtk_gate infra_clks[] = { GATE_ICG(CLK_INFRA_CPUM, "cpum_ck", "cpum_tck_in", 15), GATE_ICG_AO(CLK_INFRA_M4U, "m4u_ck", "mem_sel", 8), GATE_ICG(CLK_INFRA_MFGAXI, "mfgaxi_ck", "axi_sel", 7), - GATE_ICG(CLK_INFRA_DEVAPC, "devapc_ck", "axi_sel", 6), + GATE_ICG_INV(CLK_INFRA_DEVAPC, "devapc_ck", "axi_sel", 6), GATE_ICG(CLK_INFRA_AUDIO, "audio_ck", "aud_intbus_sel", 5), GATE_ICG(CLK_INFRA_MFG_BUS, "mfg_bus_ck", "axi_sel", 2), GATE_ICG(CLK_INFRA_SMI, "smi_ck", "smi_sel", 1), From 006ba909c240b832c876b7aed29c6beec9d1f5e5 Mon Sep 17 00:00:00 2001 From: Colin Foster Date: Mon, 29 Jun 2026 15:53:44 -0500 Subject: [PATCH 0277/1328] clk: clocking-wizard: fix clock difference detection Reviewed-by: Shubhrajyoti Datta Reviewed-by: Brian Masney Signed-off-by: Colin Foster Signed-off-by: Brian Masney --- drivers/clk/xilinx/clk-xlnx-clock-wizard.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/xilinx/clk-xlnx-clock-wizard.c b/drivers/clk/xilinx/clk-xlnx-clock-wizard.c index f5d14543167a..05168de0c291 100644 --- a/drivers/clk/xilinx/clk-xlnx-clock-wizard.c +++ b/drivers/clk/xilinx/clk-xlnx-clock-wizard.c @@ -414,7 +414,7 @@ static int clk_wzrd_get_divisors(struct clk_hw *hw, unsigned long rate, if (o < omin || o > omax) continue; freq = DIV_ROUND_CLOSEST_ULL(vco_freq, o); - diff = freq - rate; + diff = abs(freq - rate); if (diff < best_diff) { best_diff = diff; divider->m = m >> 3; From 9329714414d1f3902fae7100adb358ec946484b5 Mon Sep 17 00:00:00 2001 From: Colin Foster Date: Mon, 29 Jun 2026 15:53:45 -0500 Subject: [PATCH 0278/1328] clk: clocking-wizard: optimize clock search When an exact clock match is found, there is no need to continue searching. This process was optimized for versal as part of 'commit e0a94c6bb5b4 ("clk: xilinx: Optimize divisor search in clk_wzrd_get_divisors_ver()")' but that logic wasn't applied to the non-versal driver. Apply this fast-exit logic to the non-versal driver. Reviewed-by: Shubhrajyoti Datta Reviewed-by: Brian Masney Signed-off-by: Colin Foster Signed-off-by: Brian Masney --- drivers/clk/xilinx/clk-xlnx-clock-wizard.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/clk/xilinx/clk-xlnx-clock-wizard.c b/drivers/clk/xilinx/clk-xlnx-clock-wizard.c index 05168de0c291..11010257ed2e 100644 --- a/drivers/clk/xilinx/clk-xlnx-clock-wizard.c +++ b/drivers/clk/xilinx/clk-xlnx-clock-wizard.c @@ -422,6 +422,9 @@ static int clk_wzrd_get_divisors(struct clk_hw *hw, unsigned long rate, divider->d = d; divider->o = o >> 3; divider->o_frac = (o - (divider->o << 3)) * 125; + + if (!diff) + return 0; } } } From 54ed94b709e03fb065d426f378e9d415fa7ee939 Mon Sep 17 00:00:00 2001 From: Colin Foster Date: Mon, 29 Jun 2026 15:53:46 -0500 Subject: [PATCH 0279/1328] clk: clocking-wizard: remove 20kHz restriction Reviewed-by: Shubhrajyoti Datta Reviewed-by: Brian Masney Signed-off-by: Colin Foster Signed-off-by: Brian Masney --- drivers/clk/xilinx/clk-xlnx-clock-wizard.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/clk/xilinx/clk-xlnx-clock-wizard.c b/drivers/clk/xilinx/clk-xlnx-clock-wizard.c index 11010257ed2e..1ee30a0c65b1 100644 --- a/drivers/clk/xilinx/clk-xlnx-clock-wizard.c +++ b/drivers/clk/xilinx/clk-xlnx-clock-wizard.c @@ -113,7 +113,6 @@ #define VER_WZRD_VCO_MAX 4320000000ULL #define VER_WZRD_O_MIN 2 #define VER_WZRD_O_MAX 511 -#define WZRD_MIN_ERR 20000 #define WZRD_FRAC_POINTS 1000 /* Get the mask from width */ @@ -428,7 +427,7 @@ static int clk_wzrd_get_divisors(struct clk_hw *hw, unsigned long rate, } } } - return best_diff < WZRD_MIN_ERR ? 0 : -EBUSY; + return best_diff != -1ULL ? 0 : -EBUSY; } static int clk_wzrd_reconfig(struct clk_wzrd_divider *divider, void __iomem *div_addr) From cf561283254dd9f55805800237e6f82f08d91497 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Thu, 25 Jun 2026 11:19:06 +0300 Subject: [PATCH 0280/1328] clk: tegra: set up proper EMC clock implementation for Tegra114 Remove current emc and emc_mux clocks and replace them with the proper EMC clock implementation for correct EMC driver support. Acked-by: Thierry Reding Signed-off-by: Svyatoslav Ryhel Reviewed-by: Mikko Perttunen Signed-off-by: Brian Masney --- drivers/clk/tegra/clk-tegra114.c | 39 ++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/drivers/clk/tegra/clk-tegra114.c b/drivers/clk/tegra/clk-tegra114.c index a4f40533cc43..f1bca81b7776 100644 --- a/drivers/clk/tegra/clk-tegra114.c +++ b/drivers/clk/tegra/clk-tegra114.c @@ -620,10 +620,6 @@ static const char *mux_plld_out0_plld2_out0[] = { }; #define mux_plld_out0_plld2_out0_idx NULL -static const char *mux_pllmcp_clkm[] = { - "pll_m_out0", "pll_c_out0", "pll_p_out0", "clk_m", "pll_m_ud", -}; - static const struct clk_div_table pll_re_div_table[] = { { .val = 0, .div = 1 }, { .val = 1, .div = 2 }, @@ -670,7 +666,6 @@ static struct tegra_clk tegra114_clks[tegra_clk_max] __initdata = { [tegra_clk_csi] = { .dt_id = TEGRA114_CLK_CSI, .present = true }, [tegra_clk_i2c2] = { .dt_id = TEGRA114_CLK_I2C2, .present = true }, [tegra_clk_uartc] = { .dt_id = TEGRA114_CLK_UARTC, .present = true }, - [tegra_clk_emc] = { .dt_id = TEGRA114_CLK_EMC, .present = true }, [tegra_clk_usb2] = { .dt_id = TEGRA114_CLK_USB2, .present = true }, [tegra_clk_usb3] = { .dt_id = TEGRA114_CLK_USB3, .present = true }, [tegra_clk_vde_8] = { .dt_id = TEGRA114_CLK_VDE, .present = true }, @@ -1051,14 +1046,7 @@ static __init void tegra114_periph_clk_init(void __iomem *clk_base, periph_clk_enb_refcnt); clks[TEGRA114_CLK_CSUS] = clk; - /* emc mux */ - clk = clk_register_mux(NULL, "emc_mux", mux_pllmcp_clkm, - ARRAY_SIZE(mux_pllmcp_clkm), - CLK_SET_RATE_NO_REPARENT, - clk_base + CLK_SOURCE_EMC, - 29, 3, 0, &emc_lock); - - clk = tegra_clk_register_mc("mc", "emc_mux", clk_base + CLK_SOURCE_EMC, + clk = tegra_clk_register_mc("mc", "emc", clk_base + CLK_SOURCE_EMC, &emc_lock); clks[TEGRA114_CLK_MC] = clk; @@ -1324,6 +1312,26 @@ static int tegra114_reset_deassert(unsigned long id) return 0; } +static struct clk *tegra114_clk_src_onecell_get(struct of_phandle_args *clkspec, + void *data) +{ + struct clk_hw *hw; + struct clk *clk; + + clk = of_clk_src_onecell_get(clkspec, data); + if (IS_ERR(clk)) + return clk; + + hw = __clk_get_hw(clk); + + if (clkspec->args[0] == TEGRA114_CLK_EMC) { + if (!tegra124_clk_emc_driver_available(hw)) + return ERR_PTR(-EPROBE_DEFER); + } + + return clk; +} + static void __init tegra114_clock_init(struct device_node *np) { struct device_node *node; @@ -1371,7 +1379,10 @@ static void __init tegra114_clock_init(struct device_node *np) tegra_init_special_resets(1, tegra114_reset_assert, tegra114_reset_deassert); - tegra_add_of_provider(np, of_clk_src_onecell_get); + tegra_add_of_provider(np, tegra114_clk_src_onecell_get); + clks[TEGRA114_CLK_EMC] = tegra124_clk_register_emc(clk_base, np, + &emc_lock); + tegra_register_devclks(devclks, ARRAY_SIZE(devclks)); tegra_clk_apply_init_table = tegra114_clock_apply_init_table; From c08b809d8c2cd32ce14715f6c5712dbcdd920b45 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 28 Jul 2026 11:26:09 +0200 Subject: [PATCH 0281/1328] drm/msm: Fix stale comments in uapi/drm/msm_drm.h At some point drm_msm_gem_syncobj was renamed to drm_msm_syncobj and MSM_SUBMIT_SYNCOBJ_FLAGS was renamed to MSM_SYNCOBJ_FLAGS but some comments still refer to the old names. Update the comments with the new names. Signed-off-by: Hans de Goede Patchwork: https://patchwork.freedesktop.org/patch/742717/ Message-ID: <20260728092609.22049-1-johannes.goede@oss.qualcomm.com> Signed-off-by: Rob Clark --- include/uapi/drm/msm_drm.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/uapi/drm/msm_drm.h b/include/uapi/drm/msm_drm.h index 7f2e594be4eb..3d363f12f66a 100644 --- a/include/uapi/drm/msm_drm.h +++ b/include/uapi/drm/msm_drm.h @@ -228,7 +228,7 @@ struct drm_msm_gem_cpu_fini { struct drm_msm_syncobj { __u32 handle; /* in, syncobj handle. */ - __u32 flags; /* in, from MSM_SUBMIT_SYNCOBJ_FLAGS */ + __u32 flags; /* in, from MSM_SYNCOBJ_FLAGS */ __u64 point; /* in, timepoint for timeline syncobjs. */ }; @@ -393,9 +393,9 @@ struct drm_msm_vm_bind { __s32 fence_fd; /** @queue_id: in, submitqueue id */ __u32 queue_id; - /** @in_syncobjs: in, ptr to array of drm_msm_gem_syncobj */ + /** @in_syncobjs: in, ptr to array of drm_msm_syncobj */ __u64 in_syncobjs; - /** @out_syncobjs: in, ptr to array of drm_msm_gem_syncobj */ + /** @out_syncobjs: in, ptr to array of drm_msm_syncobj */ __u64 out_syncobjs; /** @nr_in_syncobjs: in, number of entries in in_syncobj */ __u32 nr_in_syncobjs; From 52aef653c3d0c24013dfa9eccf692594eacdbe17 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Thu, 23 Jul 2026 14:21:58 +0400 Subject: [PATCH 0282/1328] clk: rockchip: Fix the fractional part denominator on RK3588/RK3576 PLLs According to the TRM, the fractional PLL coefficient should be divided by 65536 rather than 65535 to obtain the output rate. Fix the denominator and add a comment with the TRM provided clock formulae for future reference. See RK3576 TRM Part 1 V1.2 section 2.13.1.4 Setting Guide on P, M, S and K or equivalently RK3588 TRM part 1 V1.0 section 2.17.1.4 Setting Guide on P, M, S and K. Fractional PLL rates don't seem to be used by any current mainline consumers, so this is purely a correctness fix. It will also be important to properly support DisplayPort output going forward, as the video output controller derives its pixel clock from system PLLs with no dedicated PHY PLL option for DP unlike HDMI, and some display modes are only achievable with fractional PLL rates. Fixes: 8f6594494b1c ("clk: rockchip: add pll type for RK3588") Signed-off-by: Alexey Charkov Reviewed-by: Quentin Schulz Link: https://patch.msgid.link/20260723-rk3588-fracpll-v2-1-3adfb9dda235@flipper.net Signed-off-by: Heiko Stuebner --- drivers/clk/rockchip/clk-pll.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/clk/rockchip/clk-pll.c b/drivers/clk/rockchip/clk-pll.c index 6b853800cb6b..bf8acf7cee0d 100644 --- a/drivers/clk/rockchip/clk-pll.c +++ b/drivers/clk/rockchip/clk-pll.c @@ -900,6 +900,13 @@ static void rockchip_rk3588_pll_get_params(struct rockchip_clk_pll *pll, rate->k = ((pllcon >> RK3588_PLLCON2_K_SHIFT) & RK3588_PLLCON2_K_MASK); } +/* + * 2250 MHz <= Fvco <= 4500 MHz + * For Fvco > 3 GHz: period jitter +-1% frac PLL, +-0.75% int PLL + * For Fvco < 3 GHz: period jitter +-2% frac PLL, +-1.50% int PLL + * Fvco = ((m + k / 65536) * Fin) / p + * Fout = ((m + k / 65536) * Fin) / (p * 2^s) + */ static unsigned long rockchip_rk3588_pll_recalc_rate(struct clk_hw *hw, unsigned long prate) { struct rockchip_clk_pll *pll = to_rockchip_clk_pll(hw); @@ -915,7 +922,7 @@ static unsigned long rockchip_rk3588_pll_recalc_rate(struct clk_hw *hw, unsigned /* fractional mode */ u64 frac_rate64 = prate * cur.k; - postdiv = cur.p * 65535; + postdiv = cur.p * 65536; do_div(frac_rate64, postdiv); rate64 += frac_rate64; } From 5814788834774ed6ad1a27ae91b44eeca80cd27f Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Thu, 23 Jul 2026 14:21:59 +0400 Subject: [PATCH 0283/1328] clk: rockchip: Fractional PLL coefficient on RK3588/RK3576 is two's complement When the PLL rates table was first committed for RK3588 (and later reused for RK3576), the fractional PLL coefficient was defined as an unsigned value, while the TRM clearly states that it is a two's complement 16-bit value. Treating the fractional PLL coefficient as unsigned in rate recalculation results in a kernel-visible rate which deviates from what the hardware actually generates by Fin / (p * 2^s), or 2 MHz for the two affected table entries. Rockchip's downstream kernel later revised the fractional PLL code [1] to account for the two's complement nature of the coefficient, but that change wasn't upstreamed. Change the PLL table definition to use two's complement for the fractional coefficient and update its users accordingly. Note that a negative fractional coefficient is meant to be subtracted from the next larger integer multiplier, so the m values in the table are also adjusted accordingly for the two negative-k entries. Rockchip's downstream commit introducing the two's complement logic for k also does unrelated tweaks to the PLL parameters which are not explained by the switch to the two's complement, so they are not replicated here. If any of the parameters prove to need further tweaks (e.g. for precision or jitter) that would better be done in targeted follow-up commits. Fractional PLL rates don't seem to be used by any current mainline consumers, so this is purely a correctness fix. It will also be important to properly support DisplayPort output going forward, as the video output controller derives its pixel clock from system PLLs with no dedicated PHY PLL option for DP unlike HDMI, and some display modes are only achievable using fractional PLL rates. Link: https://github.com/flipperdevices/rockchip-linux/commit/7a72bc05dcc3a51e85ae531749e6270bf9b9212d [1] Fixes: f1c506d152ff ("clk: rockchip: add clock controller for the RK3588") Fixes: cc40f5baa91b ("clk: rockchip: Add clock controller for the RK3576") Signed-off-by: Alexey Charkov Link: https://patch.msgid.link/20260723-rk3588-fracpll-v2-2-3adfb9dda235@flipper.net Signed-off-by: Heiko Stuebner --- drivers/clk/rockchip/clk-pll.c | 7 ++++--- drivers/clk/rockchip/clk-rk3576.c | 4 ++-- drivers/clk/rockchip/clk-rk3588.c | 4 ++-- drivers/clk/rockchip/clk.h | 8 ++++---- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/drivers/clk/rockchip/clk-pll.c b/drivers/clk/rockchip/clk-pll.c index bf8acf7cee0d..706ca4b344d3 100644 --- a/drivers/clk/rockchip/clk-pll.c +++ b/drivers/clk/rockchip/clk-pll.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include "clk.h" @@ -906,6 +907,7 @@ static void rockchip_rk3588_pll_get_params(struct rockchip_clk_pll *pll, * For Fvco < 3 GHz: period jitter +-2% frac PLL, +-1.50% int PLL * Fvco = ((m + k / 65536) * Fin) / p * Fout = ((m + k / 65536) * Fin) / (p * 2^s) + * -32768 <= k <= 32767 (only available in frac PLLs, not int PLLs) */ static unsigned long rockchip_rk3588_pll_recalc_rate(struct clk_hw *hw, unsigned long prate) { @@ -920,11 +922,10 @@ static unsigned long rockchip_rk3588_pll_recalc_rate(struct clk_hw *hw, unsigned if (cur.k) { /* fractional mode */ - u64 frac_rate64 = prate * cur.k; + s64 frac_rate64 = (s64)prate * cur.k; postdiv = cur.p * 65536; - do_div(frac_rate64, postdiv); - rate64 += frac_rate64; + rate64 += div_s64(frac_rate64, postdiv); } rate64 = rate64 >> cur.s; diff --git a/drivers/clk/rockchip/clk-rk3576.c b/drivers/clk/rockchip/clk-rk3576.c index 2557358e0b9d..63f229e73a45 100644 --- a/drivers/clk/rockchip/clk-rk3576.c +++ b/drivers/clk/rockchip/clk-rk3576.c @@ -79,13 +79,13 @@ static struct rockchip_pll_rate_table rk3576_pll_rates[] = { RK3588_PLL_RATE(1008000000, 2, 336, 2, 0), RK3588_PLL_RATE(1000000000, 3, 500, 2, 0), RK3588_PLL_RATE(983040000, 4, 655, 2, 23592), - RK3588_PLL_RATE(955520000, 3, 477, 2, 49806), + RK3588_PLL_RATE(955520000, 3, 478, 2, -15730), RK3588_PLL_RATE(903168000, 6, 903, 2, 11009), RK3588_PLL_RATE(900000000, 2, 300, 2, 0), RK3588_PLL_RATE(816000000, 2, 272, 2, 0), RK3588_PLL_RATE(786432000, 2, 262, 2, 9437), RK3588_PLL_RATE(786000000, 1, 131, 2, 0), - RK3588_PLL_RATE(785560000, 3, 392, 2, 51117), + RK3588_PLL_RATE(785560000, 3, 393, 2, -14419), RK3588_PLL_RATE(722534400, 8, 963, 2, 24850), RK3588_PLL_RATE(600000000, 2, 200, 2, 0), RK3588_PLL_RATE(594000000, 2, 198, 2, 0), diff --git a/drivers/clk/rockchip/clk-rk3588.c b/drivers/clk/rockchip/clk-rk3588.c index f20764b0ffcf..8a06e16d045f 100644 --- a/drivers/clk/rockchip/clk-rk3588.c +++ b/drivers/clk/rockchip/clk-rk3588.c @@ -79,14 +79,14 @@ static struct rockchip_pll_rate_table rk3588_pll_rates[] = { RK3588_PLL_RATE(1008000000, 2, 336, 2, 0), RK3588_PLL_RATE(1000000000, 3, 500, 2, 0), RK3588_PLL_RATE(983040000, 4, 655, 2, 23592), - RK3588_PLL_RATE(955520000, 3, 477, 2, 49806), + RK3588_PLL_RATE(955520000, 3, 478, 2, -15730), RK3588_PLL_RATE(903168000, 6, 903, 2, 11009), RK3588_PLL_RATE(900000000, 2, 300, 2, 0), RK3588_PLL_RATE(850000000, 3, 425, 2, 0), RK3588_PLL_RATE(816000000, 2, 272, 2, 0), RK3588_PLL_RATE(786432000, 2, 262, 2, 9437), RK3588_PLL_RATE(786000000, 1, 131, 2, 0), - RK3588_PLL_RATE(785560000, 3, 392, 2, 51117), + RK3588_PLL_RATE(785560000, 3, 393, 2, -14419), RK3588_PLL_RATE(722534400, 8, 963, 2, 24850), RK3588_PLL_RATE(600000000, 2, 200, 2, 0), RK3588_PLL_RATE(594000000, 2, 198, 2, 0), diff --git a/drivers/clk/rockchip/clk.h b/drivers/clk/rockchip/clk.h index 9e3503e2ffc2..72b36bba3152 100644 --- a/drivers/clk/rockchip/clk.h +++ b/drivers/clk/rockchip/clk.h @@ -635,10 +635,10 @@ struct rockchip_pll_rate_table { }; struct { /* for RK3588 */ - unsigned int m; - unsigned int p; - unsigned int s; - unsigned int k; + unsigned int m; /* main divider, 10 bit unsigned */ + unsigned int p; /* pre-divider, 6 bit unsigned */ + unsigned int s; /* scaler, 3 bit unsigned */ + s16 k; /* fractional part, 16 bit two's complement */ }; }; }; From cf59a8742581aa4a026b20c7a413be06f4fc9831 Mon Sep 17 00:00:00 2001 From: Troy Mitchell Date: Fri, 17 Jul 2026 00:26:01 -0700 Subject: [PATCH 0284/1328] dt-bindings: soc: spacemit: k3: add i2s_sysclk, i2s_bclk_factor and i2s1_sysclk_src IDs Add three new clock IDs to expose clocks introduced by the topology fix: - CLK_MPMU_I2S_SYSCLK (51): the common i2s sysclk DDN at MPMU_ISCCR1 - CLK_MPMU_I2S_BCLK_FACTOR (52): the implicit /2 factor feeding i2s_bclk - CLK_MPMU_I2S1_SYSCLK_SRC (53): the dedicated i2s1 sysclk source mux CLK_MPMU_I2S1_SYSCLK keeps its existing ID (34) but will be repointed to the real per-instance i2s1 clock in a subsequent patch. No in-tree user references this ID so the semantic change is contained. Fixes: efe897b557e2 ("dt-bindings: soc: spacemit: k3: add clock support") Signed-off-by: Troy Mitchell Reviewed-by: Yixun Lan Link: https://patch.msgid.link/20260717-k3-clk-fix-i2s-v1-1-e95001a692ee@linux.spacemit.com Signed-off-by: Yixun Lan --- include/dt-bindings/clock/spacemit,k3-clocks.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/dt-bindings/clock/spacemit,k3-clocks.h b/include/dt-bindings/clock/spacemit,k3-clocks.h index 69978ab0b29f..4f1099f5da1c 100644 --- a/include/dt-bindings/clock/spacemit,k3-clocks.h +++ b/include/dt-bindings/clock/spacemit,k3-clocks.h @@ -145,6 +145,9 @@ #define CLK_MPMU_I2S3_SYSCLK 48 #define CLK_MPMU_I2S4_SYSCLK 49 #define CLK_MPMU_I2S5_SYSCLK 50 +#define CLK_MPMU_I2S_SYSCLK 51 +#define CLK_MPMU_I2S_BCLK_FACTOR 52 +#define CLK_MPMU_I2S1_SYSCLK_SRC 53 /* APBC clocks */ #define CLK_APBC_UART0 0 From e6b17a690c2f920963d6a9328642bb090f3be587 Mon Sep 17 00:00:00 2001 From: Troy Mitchell Date: Fri, 17 Jul 2026 00:26:02 -0700 Subject: [PATCH 0285/1328] clk: spacemit: k3: fix i2s clock topology The K3 i2s clocks were modelled as a single path behind one MPMU register: pll1_d96_25p6 / i2s_153p6_base `-- i2s_sysclk_src (mux+gate, MPMU_ISCCR) `-- i2s1_sysclk (DDN, MPMU_ISCCR) |-- i2s_bclk (div+gate, MPMU_ISCCR) `-- i2s2_sysclk (mux, parent 0) The hardware actually has two i2s clock control registers, ISCCR0 (0x0040) and ISCCR1 (0x0044): ISCCR1 drives the common sysclk shared by i2s0/2/3/4/5 and the common bclk, whose divider always implies a fixed 1/2 factor in front, while ISCCR0 drives a dedicated path for i2s1: pll1_d96_25p6 / i2s_153p6_base |-- i2s_sysclk_src (mux+gate, MPMU_ISCCR1) | `-- i2s_sysclk (DDN, MPMU_ISCCR1) | |-- i2s_bclk_factor (fixed factor, /2) | | `-- i2s_bclk (div+gate, MPMU_ISCCR1) | `-- i2s2_sysclk (mux, parent 0) `-- i2s1_sysclk_src (mux+gate, MPMU_ISCCR0) `-- i2s1_sysclk (DDN, MPMU_ISCCR0) Because of this mismatch, i2s_bclk reported twice the real rate, and the dedicated i2s1 clock path could not be described in DT at all. Model the tree as above: split the MPMU_ISCCR register macro into MPMU_ISCCR0 and MPMU_ISCCR1 to match the hardware register names, rename the common DDN to i2s_sysclk, insert the fixed 1/2 factor i2s_bclk_factor in front of i2s_bclk, and add the i2s1_sysclk_src mux and i2s1_sysclk DDN backed by MPMU_ISCCR0. CLK_MPMU_I2S1_SYSCLK now refers to the dedicated i2s1 clock; no in-tree user references this ID, so nothing is affected by the change of meaning. Fixes: e371a77255b8 ("clk: spacemit: k3: add the clock tree") Signed-off-by: Troy Mitchell Reviewed-by: Yixun Lan Link: https://patch.msgid.link/20260717-k3-clk-fix-i2s-v1-2-e95001a692ee@linux.spacemit.com Signed-off-by: Yixun Lan --- drivers/clk/spacemit/ccu-k3.c | 21 ++++++++++++++++----- include/soc/spacemit/k3-syscon.h | 3 ++- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/drivers/clk/spacemit/ccu-k3.c b/drivers/clk/spacemit/ccu-k3.c index f47d26e1d642..3133b5d5ec00 100644 --- a/drivers/clk/spacemit/ccu-k3.c +++ b/drivers/clk/spacemit/ccu-k3.c @@ -218,11 +218,19 @@ static const struct clk_parent_data i2s_sysclk_src_parents[] = { CCU_PARENT_HW(pll1_d96_25p6), CCU_PARENT_HW(i2s_153p6_base), }; -CCU_MUX_GATE_DEFINE(i2s_sysclk_src, i2s_sysclk_src_parents, MPMU_ISCCR, 30, 1, BIT(31), 0); +CCU_MUX_GATE_DEFINE(i2s_sysclk_src, i2s_sysclk_src_parents, MPMU_ISCCR1, 30, 1, BIT(31), 0); -CCU_DDN_DEFINE(i2s1_sysclk, i2s_sysclk_src, MPMU_ISCCR, 0, 15, 15, 12, 1, 0); +CCU_DDN_DEFINE(i2s_sysclk, i2s_sysclk_src, MPMU_ISCCR1, 0, 15, 15, 12, 1, 0); -CCU_DIV_GATE_DEFINE(i2s_bclk, CCU_PARENT_HW(i2s1_sysclk), MPMU_ISCCR, 27, 2, BIT(29), 0); +CCU_FACTOR_DEFINE(i2s_bclk_factor, CCU_PARENT_HW(i2s_sysclk), 2, 1); +/* + * Divider of i2s_bclk always implies a 1/2 factor, which is + * described by i2s_bclk_factor. + */ +CCU_DIV_GATE_DEFINE(i2s_bclk, CCU_PARENT_HW(i2s_bclk_factor), MPMU_ISCCR1, 27, 2, BIT(29), 0); + +CCU_MUX_GATE_DEFINE(i2s1_sysclk_src, i2s_sysclk_src_parents, MPMU_ISCCR0, 30, 1, BIT(31), 0); +CCU_DDN_DEFINE(i2s1_sysclk, i2s1_sysclk_src, MPMU_ISCCR0, 0, 15, 15, 12, 1, 0); static const struct clk_parent_data i2s_sysclk_parents[] = { CCU_PARENT_HW(pll1_d4_614p4), @@ -243,7 +251,7 @@ CCU_DDN_DEFINE(i2s4_sysclk_div, i2s4_sysclk_sel, MPMU_I2S4_SYSCLK, 0, 16, 16, 16 CCU_DDN_DEFINE(i2s5_sysclk_div, i2s5_sysclk_sel, MPMU_I2S5_SYSCLK, 0, 16, 16, 16, 1, 0); static const struct clk_parent_data i2s2_sysclk_parents[] = { - CCU_PARENT_HW(i2s1_sysclk), + CCU_PARENT_HW(i2s_sysclk), CCU_PARENT_HW(i2s2_sysclk_div), }; CCU_GATE_DEFINE(i2s0_sysclk, CCU_PARENT_HW(i2s0_sysclk_div), MPMU_I2S_SYSCLK_CTRL, BIT(2), 0); @@ -1162,8 +1170,11 @@ static struct clk_hw *k3_ccu_mpmu_hws[] = { [CLK_MPMU_I2S_153P6] = &i2s_153p6.common.hw, [CLK_MPMU_I2S_153P6_BASE] = &i2s_153p6_base.common.hw, [CLK_MPMU_I2S_SYSCLK_SRC] = &i2s_sysclk_src.common.hw, - [CLK_MPMU_I2S1_SYSCLK] = &i2s1_sysclk.common.hw, + [CLK_MPMU_I2S_SYSCLK] = &i2s_sysclk.common.hw, + [CLK_MPMU_I2S_BCLK_FACTOR] = &i2s_bclk_factor.common.hw, [CLK_MPMU_I2S_BCLK] = &i2s_bclk.common.hw, + [CLK_MPMU_I2S1_SYSCLK_SRC] = &i2s1_sysclk_src.common.hw, + [CLK_MPMU_I2S1_SYSCLK] = &i2s1_sysclk.common.hw, [CLK_MPMU_I2S0_SYSCLK_SEL] = &i2s0_sysclk_sel.common.hw, [CLK_MPMU_I2S2_SYSCLK_SEL] = &i2s2_sysclk_sel.common.hw, [CLK_MPMU_I2S3_SYSCLK_SEL] = &i2s3_sysclk_sel.common.hw, diff --git a/include/soc/spacemit/k3-syscon.h b/include/soc/spacemit/k3-syscon.h index a68255dd641f..959d8d0786fe 100644 --- a/include/soc/spacemit/k3-syscon.h +++ b/include/soc/spacemit/k3-syscon.h @@ -45,7 +45,8 @@ #define POSR_PLL7_LOCK BIT(30) #define POSR_PLL8_LOCK BIT(31) #define MPMU_SUCCR 0x0014 -#define MPMU_ISCCR 0x0044 +#define MPMU_ISCCR0 0x0040 +#define MPMU_ISCCR1 0x0044 #define MPMU_WDTPCR 0x0200 #define MPMU_RIPCCR 0x0210 #define MPMU_ACGR 0x1024 From 8a7d4b1924d2a424b1a6fe69de4f9464ee1fb485 Mon Sep 17 00:00:00 2001 From: Troy Mitchell Date: Fri, 17 Jul 2026 00:26:03 -0700 Subject: [PATCH 0286/1328] clk: spacemit: k3: fix missing /2 factor in i2s sysclk dividers The i2s{0,2,3,4,5}_sysclk_div DDNs have an additional fixed 1/2 divider in the hardware IP after the configurable divider, so the real output rate is: rate = parent_rate * den / (num * 2) Set pre_div to 2 to account for it. Fixes: e371a77255b8 ("clk: spacemit: k3: add the clock tree") Signed-off-by: Troy Mitchell Reviewed-by: Yixun Lan Link: https://patch.msgid.link/20260717-k3-clk-fix-i2s-v1-3-e95001a692ee@linux.spacemit.com Signed-off-by: Yixun Lan --- drivers/clk/spacemit/ccu-k3.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/clk/spacemit/ccu-k3.c b/drivers/clk/spacemit/ccu-k3.c index 3133b5d5ec00..a0622b80ac0a 100644 --- a/drivers/clk/spacemit/ccu-k3.c +++ b/drivers/clk/spacemit/ccu-k3.c @@ -244,11 +244,11 @@ CCU_MUX_DEFINE(i2s3_sysclk_sel, i2s_sysclk_parents, MPMU_I2S_SYSCLK_CTRL, 12, 2, CCU_MUX_DEFINE(i2s4_sysclk_sel, i2s_sysclk_parents, MPMU_I2S_SYSCLK_CTRL, 16, 2, 0); CCU_MUX_DEFINE(i2s5_sysclk_sel, i2s_sysclk_parents, MPMU_I2S_SYSCLK_CTRL, 20, 2, 0); -CCU_DDN_DEFINE(i2s0_sysclk_div, i2s0_sysclk_sel, MPMU_I2S0_SYSCLK, 0, 16, 16, 16, 1, 0); -CCU_DDN_DEFINE(i2s2_sysclk_div, i2s2_sysclk_sel, MPMU_I2S2_SYSCLK, 0, 16, 16, 16, 1, 0); -CCU_DDN_DEFINE(i2s3_sysclk_div, i2s3_sysclk_sel, MPMU_I2S3_SYSCLK, 0, 16, 16, 16, 1, 0); -CCU_DDN_DEFINE(i2s4_sysclk_div, i2s4_sysclk_sel, MPMU_I2S4_SYSCLK, 0, 16, 16, 16, 1, 0); -CCU_DDN_DEFINE(i2s5_sysclk_div, i2s5_sysclk_sel, MPMU_I2S5_SYSCLK, 0, 16, 16, 16, 1, 0); +CCU_DDN_DEFINE(i2s0_sysclk_div, i2s0_sysclk_sel, MPMU_I2S0_SYSCLK, 0, 16, 16, 16, 2, 0); +CCU_DDN_DEFINE(i2s2_sysclk_div, i2s2_sysclk_sel, MPMU_I2S2_SYSCLK, 0, 16, 16, 16, 2, 0); +CCU_DDN_DEFINE(i2s3_sysclk_div, i2s3_sysclk_sel, MPMU_I2S3_SYSCLK, 0, 16, 16, 16, 2, 0); +CCU_DDN_DEFINE(i2s4_sysclk_div, i2s4_sysclk_sel, MPMU_I2S4_SYSCLK, 0, 16, 16, 16, 2, 0); +CCU_DDN_DEFINE(i2s5_sysclk_div, i2s5_sysclk_sel, MPMU_I2S5_SYSCLK, 0, 16, 16, 16, 2, 0); static const struct clk_parent_data i2s2_sysclk_parents[] = { CCU_PARENT_HW(i2s_sysclk), From 83723f32cb3de23d45c1ac09241b5e0cfb32cc9b Mon Sep 17 00:00:00 2001 From: Anna Maniscalco Date: Thu, 23 Jul 2026 21:23:22 +0200 Subject: [PATCH 0287/1328] drm/msm: remove objects from evit list after pinning them Once objects are pinned they should not be kept in the evict list as that will cause drm_gpuvm_validate to keep ieterating a growing list of objects needlessly. Once an object is pinned remove it from the list. Fixes: 2e6a8a1fe2b2 ("drm/msm: Add VM_BIND ioctl") Signed-off-by: Anna Maniscalco Patchwork: https://patchwork.freedesktop.org/patch/742166/ Message-ID: <20260723-evict_list_fix-v2-1-bd0725e56253@gmail.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/msm_gem_vma.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/msm/msm_gem_vma.c b/drivers/gpu/drm/msm/msm_gem_vma.c index 3ed05ab0eeef..50d4812bdb4c 100644 --- a/drivers/gpu/drm/msm/msm_gem_vma.c +++ b/drivers/gpu/drm/msm/msm_gem_vma.c @@ -458,6 +458,8 @@ msm_gem_vm_bo_validate(struct drm_gpuvm_bo *vm_bo, struct drm_exec *exec) return ret; } + drm_gpuvm_bo_evict(vm_bo, false); + return 0; } From ae88499d71ce80ecd25c055eddddace108266321 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 29 Jul 2026 08:55:38 -0700 Subject: [PATCH 0288/1328] drm/msm: Fix barriers accessing ctx vm Don't rely on store ordering to protect us from caller seeing a partially initialized vm. Reported-by: Sashiko Signed-off-by: Rob Clark Patchwork: https://patchwork.freedesktop.org/patch/743079/ Message-ID: <20260729155609.20190-2-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/msm_drv.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c index ac86b427c0e5..29075aafc5cc 100644 --- a/drivers/gpu/drm/msm/msm_drv.c +++ b/drivers/gpu/drm/msm/msm_drv.c @@ -224,18 +224,19 @@ struct drm_gpuvm *msm_context_vm(struct drm_device *dev, struct msm_context *ctx { static DEFINE_MUTEX(init_lock); struct msm_drm_private *priv = dev->dev_private; + struct drm_gpuvm *vm = smp_load_acquire(&ctx->vm); /* Once ctx->vm is created it is valid for the lifetime of the context: */ - if (ctx->vm) - return ctx->vm; + if (vm) + return vm; + + guard(mutex)(&init_lock); - mutex_lock(&init_lock); if (!ctx->vm) { - ctx->vm = msm_gpu_create_private_vm( + vm = msm_gpu_create_private_vm( priv->gpu, current, !ctx->userspace_managed_vm); - + smp_store_release(&ctx->vm, vm); } - mutex_unlock(&init_lock); return ctx->vm; } From ea69d489d3a6822e2cf10d6806a889dd2e3271f0 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 29 Jul 2026 08:55:39 -0700 Subject: [PATCH 0289/1328] drm/msm: Rework queuelock Rename to ctxlock, and use cleanup guards to manage releasing the lock. This will let us re-use it for other per-context read/write serial- ization, such as VM creation. Signed-off-by: Rob Clark Patchwork: https://patchwork.freedesktop.org/patch/743080/ Message-ID: <20260729155609.20190-3-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/msm_drv.c | 2 +- drivers/gpu/drm/msm/msm_gpu.h | 4 ++-- drivers/gpu/drm/msm/msm_submitqueue.c | 14 +++----------- 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c index 29075aafc5cc..77681cb4fdc2 100644 --- a/drivers/gpu/drm/msm/msm_drv.c +++ b/drivers/gpu/drm/msm/msm_drv.c @@ -251,7 +251,7 @@ static int context_init(struct drm_device *dev, struct drm_file *file) return -ENOMEM; INIT_LIST_HEAD(&ctx->submitqueues); - rwlock_init(&ctx->queuelock); + init_rwsem(&ctx->ctxlock); kref_init(&ctx->ref); msm_submitqueue_init(dev, ctx); diff --git a/drivers/gpu/drm/msm/msm_gpu.h b/drivers/gpu/drm/msm/msm_gpu.h index 6c83b8cbbb90..d27d54bdb7a7 100644 --- a/drivers/gpu/drm/msm/msm_gpu.h +++ b/drivers/gpu/drm/msm/msm_gpu.h @@ -392,8 +392,8 @@ msm_gpu_sysprof_no_ifpc(struct msm_gpu *gpu) * struct msm_context - per-drm_file context */ struct msm_context { - /** @queuelock: synchronizes access to submitqueues list */ - rwlock_t queuelock; + /** @ctxlock: synchronizes access to submitqueues list, etc */ + struct rw_semaphore ctxlock; /** @submitqueues: list of &msm_gpu_submitqueue created by userspace */ struct list_head submitqueues; /** diff --git a/drivers/gpu/drm/msm/msm_submitqueue.c b/drivers/gpu/drm/msm/msm_submitqueue.c index 1a5a77b28016..8b5980d5a6a7 100644 --- a/drivers/gpu/drm/msm/msm_submitqueue.c +++ b/drivers/gpu/drm/msm/msm_submitqueue.c @@ -93,18 +93,15 @@ struct msm_gpu_submitqueue *msm_submitqueue_get(struct msm_context *ctx, if (!ctx) return NULL; - read_lock(&ctx->queuelock); + guard(rwsem_read)(&ctx->ctxlock); list_for_each_entry(entry, &ctx->submitqueues, node) { if (entry->id == id) { kref_get(&entry->ref); - read_unlock(&ctx->queuelock); - return entry; } } - read_unlock(&ctx->queuelock); return NULL; } @@ -237,7 +234,7 @@ int msm_submitqueue_create(struct drm_device *drm, struct msm_context *ctx, return ret; } - write_lock(&ctx->queuelock); + guard(rwsem_write)(&ctx->ctxlock); queue->ctx = msm_context_get(ctx); queue->id = ctx->queueid++; @@ -251,8 +248,6 @@ int msm_submitqueue_create(struct drm_device *drm, struct msm_context *ctx, list_add_tail(&queue->node, &ctx->submitqueues); - write_unlock(&ctx->queuelock); - return 0; } @@ -335,19 +330,16 @@ int msm_submitqueue_remove(struct msm_context *ctx, u32 id) if (!id) return -ENOENT; - write_lock(&ctx->queuelock); + guard(rwsem_write)(&ctx->ctxlock); list_for_each_entry(entry, &ctx->submitqueues, node) { if (entry->id == id) { list_del(&entry->node); - write_unlock(&ctx->queuelock); - msm_submitqueue_put(entry); return 0; } } - write_unlock(&ctx->queuelock); return -ENOENT; } From 2c26f9e46d39571a11ad8a6c3cdf150af70e4f12 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 29 Jul 2026 08:55:40 -0700 Subject: [PATCH 0290/1328] drm/msm: Synchronize VM creation on ctxlock And serialize setting EN_VM_BIND against VM creation. Signed-off-by: Rob Clark Patchwork: https://patchwork.freedesktop.org/patch/743083/ Message-ID: <20260729155609.20190-4-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/adreno/adreno_gpu.c | 5 ++++- drivers/gpu/drm/msm/msm_drv.c | 3 +-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/msm/adreno/adreno_gpu.c b/drivers/gpu/drm/msm/adreno/adreno_gpu.c index c62c45bb0ddb..0d54141ff089 100644 --- a/drivers/gpu/drm/msm/adreno/adreno_gpu.c +++ b/drivers/gpu/drm/msm/adreno/adreno_gpu.c @@ -503,7 +503,9 @@ int adreno_set_param(struct msm_gpu *gpu, struct msm_context *ctx, if (!perfmon_capable()) return UERR(EPERM, drm, "invalid permissions"); return msm_context_set_sysprof(ctx, gpu, value); - case MSM_PARAM_EN_VM_BIND: + case MSM_PARAM_EN_VM_BIND: { + guard(rwsem_read)(&ctx->ctxlock); + /* We can only support VM_BIND with per-process pgtables: */ if (ctx->vm == gpu->vm) return UERR(EINVAL, drm, "requires per-process pgtables"); @@ -518,6 +520,7 @@ int adreno_set_param(struct msm_gpu *gpu, struct msm_context *ctx, ctx->userspace_managed_vm = value; return 0; + } default: return UERR(EINVAL, drm, "%s: invalid param: %u", gpu->name, param); } diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c index 77681cb4fdc2..a5714e24fcc6 100644 --- a/drivers/gpu/drm/msm/msm_drv.c +++ b/drivers/gpu/drm/msm/msm_drv.c @@ -222,7 +222,6 @@ static void load_gpu(struct drm_device *dev) */ struct drm_gpuvm *msm_context_vm(struct drm_device *dev, struct msm_context *ctx) { - static DEFINE_MUTEX(init_lock); struct msm_drm_private *priv = dev->dev_private; struct drm_gpuvm *vm = smp_load_acquire(&ctx->vm); @@ -230,7 +229,7 @@ struct drm_gpuvm *msm_context_vm(struct drm_device *dev, struct msm_context *ctx if (vm) return vm; - guard(mutex)(&init_lock); + guard(rwsem_write)(&ctx->ctxlock); if (!ctx->vm) { vm = msm_gpu_create_private_vm( From 2b1bfcb59d3f9bd4ab37eb6747fc783c67a7f82b Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 29 Jul 2026 08:55:41 -0700 Subject: [PATCH 0291/1328] drm/msm: Synchronize set_sysprof on ctxlock A user that was perfmon_capable() could try to race setting SYSPROF param on multiple threads to trigger a reference leak. Signed-off-by: Rob Clark Patchwork: https://patchwork.freedesktop.org/patch/743091/ Message-ID: <20260729155609.20190-5-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/msm_submitqueue.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/msm/msm_submitqueue.c b/drivers/gpu/drm/msm/msm_submitqueue.c index 8b5980d5a6a7..233c4f35ab12 100644 --- a/drivers/gpu/drm/msm/msm_submitqueue.c +++ b/drivers/gpu/drm/msm/msm_submitqueue.c @@ -9,6 +9,8 @@ int msm_context_set_sysprof(struct msm_context *ctx, struct msm_gpu *gpu, int sysprof) { + guard(rwsem_write)(&ctx->ctxlock); + /* * Since pm_runtime and sysprof_active are both refcounts, we * call apply the new value first, and then unwind the previous From 9ee2884ed58e6b39da35732146446d5930e4746b Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 29 Jul 2026 08:55:42 -0700 Subject: [PATCH 0292/1328] drm/msm: Move nr_cmds initialization Previously if we entered an error path between these two points, we could leak the relocs tables due to submit->nr_cmds still being zero. In practice, relocs are disallowed on a6xx+, and non-ancient userspace will not use relocs on earlier gens unless running on an ancient kernel. But userspace could use this to trigger a memory leak. Reported-by: Sashiko Signed-off-by: Rob Clark Patchwork: https://patchwork.freedesktop.org/patch/743085/ Message-ID: <20260729155609.20190-6-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/msm_gem_submit.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_gem_submit.c b/drivers/gpu/drm/msm/msm_gem_submit.c index 3c6bc90c3d48..aa48ea4e7f58 100644 --- a/drivers/gpu/drm/msm/msm_gem_submit.c +++ b/drivers/gpu/drm/msm/msm_gem_submit.c @@ -260,6 +260,9 @@ static int submit_lookup_cmds(struct msm_gem_submit *submit, ret = -ENOMEM; goto out; } + + submit->nr_cmds = i + 1; + ret = copy_from_user(submit->cmd[i].relocs, userptr, sz); if (ret) { ret = -EFAULT; @@ -719,8 +722,6 @@ int msm_ioctl_gem_submit(struct drm_device *dev, void *data, goto out; } - submit->nr_cmds = args->nr_cmds; - idr_preload(GFP_KERNEL); spin_lock(&queue->idr_lock); From 0369a2619c50bab5e9ef33e13f1def0c8aca25e8 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 29 Jul 2026 08:55:43 -0700 Subject: [PATCH 0293/1328] drm/msm: Remove redundant SIZE_MAX check kmalloc() will already fail and return NULL if passed SIZE_MAX. Signed-off-by: Rob Clark Patchwork: https://patchwork.freedesktop.org/patch/743082/ Message-ID: <20260729155609.20190-7-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/msm_gem_submit.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_gem_submit.c b/drivers/gpu/drm/msm/msm_gem_submit.c index aa48ea4e7f58..040354ee16d1 100644 --- a/drivers/gpu/drm/msm/msm_gem_submit.c +++ b/drivers/gpu/drm/msm/msm_gem_submit.c @@ -250,11 +250,6 @@ static int submit_lookup_cmds(struct msm_gem_submit *submit, sz = array_size(submit_cmd.nr_relocs, sizeof(struct drm_msm_gem_submit_reloc)); - /* check for overflow: */ - if (sz == SIZE_MAX) { - ret = -ENOMEM; - goto out; - } submit->cmd[i].relocs = kmalloc(sz, GFP_KERNEL | __GFP_NOWARN); if (!submit->cmd[i].relocs) { ret = -ENOMEM; From b87c50d2cabc48a74b2d0d79d9ff852eb8b73b58 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 29 Jul 2026 08:55:44 -0700 Subject: [PATCH 0294/1328] drm/msm/a6xx: Access VM directly in submit path The GEM_SUBMIT ioctl has already ensured that the VM is created, so we aren't expecting to lazily create the VM this deep into the ioctl. Signed-off-by: Rob Clark Patchwork: https://patchwork.freedesktop.org/patch/743101/ Message-ID: <20260729155609.20190-8-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/adreno/a6xx_gpu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gpu.c b/drivers/gpu/drm/msm/adreno/a6xx_gpu.c index 2c5faee881b3..f9de9329dee3 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gpu.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_gpu.c @@ -229,7 +229,7 @@ static void a6xx_set_pagetable(struct a6xx_gpu *a6xx_gpu, { bool sysprof = msm_gpu_sysprof_no_perfcntr_zap(&a6xx_gpu->base.base); struct msm_context *ctx = submit->queue->ctx; - struct drm_gpuvm *vm = msm_context_vm(submit->dev, ctx); + struct drm_gpuvm *vm = ctx->vm; struct adreno_gpu *adreno_gpu = &a6xx_gpu->base; phys_addr_t ttbr; u32 asid; From 517ca9a86a4aaed7a79e5133c9a3e399cec6cdf5 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 29 Jul 2026 08:55:45 -0700 Subject: [PATCH 0295/1328] drm/msm: Add helper to check for per-process pgtables VM This will simplify a following commit to allow lazy VM creation to fail. Signed-off-by: Rob Clark Patchwork: https://patchwork.freedesktop.org/patch/743103/ Message-ID: <20260729155609.20190-9-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/adreno/adreno_gpu.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/msm/adreno/adreno_gpu.c b/drivers/gpu/drm/msm/adreno/adreno_gpu.c index 0d54141ff089..5738eac9aebd 100644 --- a/drivers/gpu/drm/msm/adreno/adreno_gpu.c +++ b/drivers/gpu/drm/msm/adreno/adreno_gpu.c @@ -356,6 +356,12 @@ int adreno_fault_handler(struct msm_gpu *gpu, unsigned long iova, int flags, return 0; } +static bool +valid_per_process_vm(struct msm_gpu *gpu, struct drm_gpuvm *vm) +{ + return (vm != gpu->vm); +} + int adreno_get_param(struct msm_gpu *gpu, struct msm_context *ctx, uint32_t param, uint64_t *value, uint32_t *len) { @@ -414,12 +420,12 @@ int adreno_get_param(struct msm_gpu *gpu, struct msm_context *ctx, *value = gpu->suspend_count; return 0; case MSM_PARAM_VA_START: - if (vm == gpu->vm) + if (!valid_per_process_vm(gpu, vm)) return UERR(EINVAL, drm, "requires per-process pgtables"); *value = vm->mm_start; return 0; case MSM_PARAM_VA_SIZE: - if (vm == gpu->vm) + if (!valid_per_process_vm(gpu, vm)) return UERR(EINVAL, drm, "requires per-process pgtables"); *value = vm->mm_range; return 0; From e6863b085606f632082192d847d8d578013d4a66 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 29 Jul 2026 08:55:46 -0700 Subject: [PATCH 0296/1328] drm/msm/gem: Fix dma_buf import error paths Set import_attach early, so that if we hit an error path msm_gem_free_object() goes down the drm_gem_is_imported() path. Set sgt late so _free_object() skips drm_prime_gem_destroy() as this is done by drm_gem_prime_import_dev(). Reported-by: Sashiko Signed-off-by: Rob Clark Patchwork: https://patchwork.freedesktop.org/patch/743084/ Message-ID: <20260729155609.20190-10-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/msm_gem.c | 17 ++++++++++++++--- drivers/gpu/drm/msm/msm_gem.h | 3 ++- drivers/gpu/drm/msm/msm_gem_prime.c | 2 +- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_gem.c b/drivers/gpu/drm/msm/msm_gem.c index efd3d3c9a449..74c3728f45bd 100644 --- a/drivers/gpu/drm/msm/msm_gem.c +++ b/drivers/gpu/drm/msm/msm_gem.c @@ -1093,7 +1093,9 @@ static void msm_gem_free_object(struct drm_gem_object *obj) */ kvfree(msm_obj->pages); - drm_prime_gem_destroy(obj, msm_obj->sgt); + /* In msm_gem_import() error path, sgt won't be set yet: */ + if (msm_obj->sgt) + drm_prime_gem_destroy(obj, msm_obj->sgt); } else { msm_gem_vunmap(obj); put_pages(obj); @@ -1282,11 +1284,13 @@ struct drm_gem_object *msm_gem_new(struct drm_device *dev, size_t size, uint32_t } struct drm_gem_object *msm_gem_import(struct drm_device *dev, - struct dma_buf *dmabuf, struct sg_table *sgt) + struct dma_buf_attachment *attach, + struct sg_table *sgt) { struct msm_drm_private *priv = dev->dev_private; struct msm_gem_object *msm_obj; struct drm_gem_object *obj; + struct dma_buf *dmabuf = attach->dmabuf; size_t size, npages; int ret; @@ -1296,13 +1300,17 @@ struct drm_gem_object *msm_gem_import(struct drm_device *dev, if (ret) return ERR_PTR(ret); + /* + * Set import_attach here in case we hit an error path that ends + * up in drm_gem_object_put() -> msm_gem_free_object() + */ + obj->import_attach = attach; drm_gem_private_object_init(dev, obj, size); npages = size / PAGE_SIZE; msm_obj = to_msm_bo(obj); msm_gem_lock(obj); - msm_obj->sgt = sgt; msm_obj->pages = kvmalloc_objs(struct page *, npages); if (!msm_obj->pages) { msm_gem_unlock(obj); @@ -1328,6 +1336,9 @@ struct drm_gem_object *msm_gem_import(struct drm_device *dev, if (ret) goto fail; + /* Now that we are past potential failure points, set sgt: */ + msm_obj->sgt = sgt; + return obj; fail: diff --git a/drivers/gpu/drm/msm/msm_gem.h b/drivers/gpu/drm/msm/msm_gem.h index 762e546d25ef..86dc93cc1804 100644 --- a/drivers/gpu/drm/msm/msm_gem.h +++ b/drivers/gpu/drm/msm/msm_gem.h @@ -302,7 +302,8 @@ void *msm_gem_kernel_new(struct drm_device *dev, size_t size, uint32_t flags, uint64_t *iova); void msm_gem_kernel_put(struct drm_gem_object *bo, struct drm_gpuvm *vm); struct drm_gem_object *msm_gem_import(struct drm_device *dev, - struct dma_buf *dmabuf, struct sg_table *sgt); + struct dma_buf_attachment *attach, + struct sg_table *sgt); __printf(2, 3) void msm_gem_object_set_name(struct drm_gem_object *bo, const char *fmt, ...); diff --git a/drivers/gpu/drm/msm/msm_gem_prime.c b/drivers/gpu/drm/msm/msm_gem_prime.c index 036d34c674d9..beb7f22fd694 100644 --- a/drivers/gpu/drm/msm/msm_gem_prime.c +++ b/drivers/gpu/drm/msm/msm_gem_prime.c @@ -83,7 +83,7 @@ struct drm_gem_object *msm_gem_prime_import(struct drm_device *dev, struct drm_gem_object *msm_gem_prime_import_sg_table(struct drm_device *dev, struct dma_buf_attachment *attach, struct sg_table *sg) { - return msm_gem_import(dev, attach->dmabuf, sg); + return msm_gem_import(dev, attach, sg); } struct dma_buf *msm_gem_prime_export(struct drm_gem_object *obj, int flags) From 4e67b8fc55c4389652321355ff71ae8d4cb39783 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 29 Jul 2026 08:55:47 -0700 Subject: [PATCH 0297/1328] drm/msm/gem: Remove useless locking in GEM import The locking has changed a few times over the years, and this extra locking was the mistake of evolution. Harmless but useless. Signed-off-by: Rob Clark Patchwork: https://patchwork.freedesktop.org/patch/743087/ Message-ID: <20260729155609.20190-11-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/msm_gem.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_gem.c b/drivers/gpu/drm/msm/msm_gem.c index 74c3728f45bd..cc45392691e5 100644 --- a/drivers/gpu/drm/msm/msm_gem.c +++ b/drivers/gpu/drm/msm/msm_gem.c @@ -1310,22 +1310,17 @@ struct drm_gem_object *msm_gem_import(struct drm_device *dev, npages = size / PAGE_SIZE; msm_obj = to_msm_bo(obj); - msm_gem_lock(obj); msm_obj->pages = kvmalloc_objs(struct page *, npages); if (!msm_obj->pages) { - msm_gem_unlock(obj); ret = -ENOMEM; goto fail; } ret = drm_prime_sg_to_page_array(sgt, msm_obj->pages, npages); if (ret) { - msm_gem_unlock(obj); goto fail; } - msm_gem_unlock(obj); - drm_gem_lru_move_tail(&priv->lru.pinned, obj); mutex_lock(&priv->obj_lock); From 695d2b042af032bdbbfe4a1db475de2a2024e320 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 29 Jul 2026 08:55:48 -0700 Subject: [PATCH 0298/1328] drm/msm/gem: Extract bookkeeping init helper Clean up duplicated logic between import and new paths. Signed-off-by: Rob Clark Patchwork: https://patchwork.freedesktop.org/patch/743089/ Message-ID: <20260729155609.20190-12-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/msm_gem.c | 38 +++++++++++++++++------------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_gem.c b/drivers/gpu/drm/msm/msm_gem.c index cc45392691e5..3c36b1c4a4d5 100644 --- a/drivers/gpu/drm/msm/msm_gem.c +++ b/drivers/gpu/drm/msm/msm_gem.c @@ -1234,10 +1234,25 @@ static int msm_gem_new_impl(struct drm_device *dev, uint32_t flags, return 0; } +static int msm_gem_init_bookkeeping(struct drm_gem_object *obj) +{ + struct msm_drm_private *priv = obj->dev->dev_private; + + if (drm_gem_is_imported(obj)) { + drm_gem_lru_move_tail(&priv->lru.pinned, obj); + } else { + drm_gem_lru_move_tail(&priv->lru.unbacked, obj); + } + + mutex_lock(&priv->obj_lock); + list_add_tail(&to_msm_bo(obj)->node, &priv->objects); + mutex_unlock(&priv->obj_lock); + + return drm_gem_create_mmap_offset(obj); +} + struct drm_gem_object *msm_gem_new(struct drm_device *dev, size_t size, uint32_t flags) { - struct msm_drm_private *priv = dev->dev_private; - struct msm_gem_object *msm_obj; struct drm_gem_object *obj = NULL; int ret; @@ -1253,8 +1268,6 @@ struct drm_gem_object *msm_gem_new(struct drm_device *dev, size_t size, uint32_t if (ret) return ERR_PTR(ret); - msm_obj = to_msm_bo(obj); - ret = drm_gem_object_init(dev, obj, size); if (ret) goto fail; @@ -1266,13 +1279,7 @@ struct drm_gem_object *msm_gem_new(struct drm_device *dev, size_t size, uint32_t */ mapping_set_gfp_mask(obj->filp->f_mapping, GFP_HIGHUSER); - drm_gem_lru_move_tail(&priv->lru.unbacked, obj); - - mutex_lock(&priv->obj_lock); - list_add_tail(&msm_obj->node, &priv->objects); - mutex_unlock(&priv->obj_lock); - - ret = drm_gem_create_mmap_offset(obj); + ret = msm_gem_init_bookkeeping(obj); if (ret) goto fail; @@ -1287,7 +1294,6 @@ struct drm_gem_object *msm_gem_import(struct drm_device *dev, struct dma_buf_attachment *attach, struct sg_table *sgt) { - struct msm_drm_private *priv = dev->dev_private; struct msm_gem_object *msm_obj; struct drm_gem_object *obj; struct dma_buf *dmabuf = attach->dmabuf; @@ -1321,13 +1327,7 @@ struct drm_gem_object *msm_gem_import(struct drm_device *dev, goto fail; } - drm_gem_lru_move_tail(&priv->lru.pinned, obj); - - mutex_lock(&priv->obj_lock); - list_add_tail(&msm_obj->node, &priv->objects); - mutex_unlock(&priv->obj_lock); - - ret = drm_gem_create_mmap_offset(obj); + ret = msm_gem_init_bookkeeping(obj); if (ret) goto fail; From df68029e639b490e8f5f7715135a8af29e1b6cda Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 29 Jul 2026 08:55:49 -0700 Subject: [PATCH 0299/1328] drm/msm/gem: Set resv before exposing obj Don't swap the resv object _after_ exposing the newly created obj in LRU or global objects list, as that creates a race condition where another thread could lock the object using the original (per-obj) resv, but then unlock after the resv is replaced. Signed-off-by: Rob Clark Patchwork: https://patchwork.freedesktop.org/patch/743109/ Message-ID: <20260729155609.20190-13-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/adreno/a6xx_gmu.c | 2 +- drivers/gpu/drm/msm/disp/mdp4/mdp4_kms.c | 2 +- drivers/gpu/drm/msm/msm_fb.c | 4 +-- drivers/gpu/drm/msm/msm_gem.c | 32 ++++++++++++++---------- drivers/gpu/drm/msm/msm_gem.h | 2 +- 5 files changed, 24 insertions(+), 18 deletions(-) diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gmu.c b/drivers/gpu/drm/msm/adreno/a6xx_gmu.c index e97d8d8ee65a..27cac853975f 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gmu.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_gmu.c @@ -1554,7 +1554,7 @@ static int a6xx_gmu_memory_alloc(struct a6xx_gmu *gmu, struct a6xx_gmu_bo *bo, flags |= MSM_BO_MAP_PRIV; } - bo->obj = msm_gem_new(dev, size, flags); + bo->obj = msm_gem_new(dev, size, flags, NULL); if (IS_ERR(bo->obj)) return PTR_ERR(bo->obj); diff --git a/drivers/gpu/drm/msm/disp/mdp4/mdp4_kms.c b/drivers/gpu/drm/msm/disp/mdp4/mdp4_kms.c index 7726edb0d4ed..c289dff78cd5 100644 --- a/drivers/gpu/drm/msm/disp/mdp4/mdp4_kms.c +++ b/drivers/gpu/drm/msm/disp/mdp4/mdp4_kms.c @@ -463,7 +463,7 @@ static int mdp4_kms_init(struct drm_device *dev) goto fail; } - mdp4_kms->blank_cursor_bo = msm_gem_new(dev, SZ_16K, MSM_BO_WC | MSM_BO_SCANOUT); + mdp4_kms->blank_cursor_bo = msm_gem_new(dev, SZ_16K, MSM_BO_WC | MSM_BO_SCANOUT, NULL); if (IS_ERR(mdp4_kms->blank_cursor_bo)) { ret = PTR_ERR(mdp4_kms->blank_cursor_bo); DRM_DEV_ERROR(dev->dev, "could not allocate blank-cursor bo: %d\n", ret); diff --git a/drivers/gpu/drm/msm/msm_fb.c b/drivers/gpu/drm/msm/msm_fb.c index 9b681e144c07..ee17a47f31c4 100644 --- a/drivers/gpu/drm/msm/msm_fb.c +++ b/drivers/gpu/drm/msm/msm_fb.c @@ -267,11 +267,11 @@ msm_alloc_stolen_fb(struct drm_device *dev, int w, int h, int p, uint32_t format /* allocate backing bo */ size = mode_cmd.pitches[0] * mode_cmd.height; DBG("allocating %d bytes for fb %d", size, dev->primary->index); - bo = msm_gem_new(dev, size, MSM_BO_SCANOUT | MSM_BO_WC | MSM_BO_STOLEN); + bo = msm_gem_new(dev, size, MSM_BO_SCANOUT | MSM_BO_WC | MSM_BO_STOLEN, NULL); if (IS_ERR(bo)) { dev_warn(dev->dev, "could not allocate stolen bo\n"); /* try regular bo: */ - bo = msm_gem_new(dev, size, MSM_BO_SCANOUT | MSM_BO_WC); + bo = msm_gem_new(dev, size, MSM_BO_SCANOUT | MSM_BO_WC, NULL); } if (IS_ERR(bo)) { DRM_DEV_ERROR(dev->dev, "failed to allocate buffer object\n"); diff --git a/drivers/gpu/drm/msm/msm_gem.c b/drivers/gpu/drm/msm/msm_gem.c index 3c36b1c4a4d5..535c6e1acb3c 100644 --- a/drivers/gpu/drm/msm/msm_gem.c +++ b/drivers/gpu/drm/msm/msm_gem.c @@ -1136,10 +1136,17 @@ int msm_gem_new_handle(struct drm_device *dev, struct drm_file *file, size_t size, uint32_t flags, uint32_t *handle, char *name) { - struct drm_gem_object *obj; + struct drm_gem_object *obj, *r_obj = NULL; int ret; - obj = msm_gem_new(dev, size, flags); + if (flags & MSM_BO_NO_SHARE) { + struct msm_context *ctx = file->driver_priv; + struct drm_gpuvm *vm = msm_context_vm(dev, ctx); + + r_obj = drm_gpuvm_resv_obj(vm); + } + + obj = msm_gem_new(dev, size, flags, r_obj); if (IS_ERR(obj)) return PTR_ERR(obj); @@ -1147,15 +1154,6 @@ int msm_gem_new_handle(struct drm_device *dev, struct drm_file *file, if (name) msm_gem_object_set_name(obj, "%s", name); - if (flags & MSM_BO_NO_SHARE) { - struct msm_context *ctx = file->driver_priv; - struct drm_gem_object *r_obj = drm_gpuvm_resv_obj(ctx->vm); - - drm_gem_object_get(r_obj); - - obj->resv = r_obj->resv; - } - ret = drm_gem_handle_create(file, obj, handle); /* drop reference from allocate - handle holds it now */ @@ -1251,7 +1249,9 @@ static int msm_gem_init_bookkeeping(struct drm_gem_object *obj) return drm_gem_create_mmap_offset(obj); } -struct drm_gem_object *msm_gem_new(struct drm_device *dev, size_t size, uint32_t flags) +struct drm_gem_object * +msm_gem_new(struct drm_device *dev, size_t size, uint32_t flags, + struct drm_gem_object *r_obj) { struct drm_gem_object *obj = NULL; int ret; @@ -1268,6 +1268,11 @@ struct drm_gem_object *msm_gem_new(struct drm_device *dev, size_t size, uint32_t if (ret) return ERR_PTR(ret); + if (flags & MSM_BO_NO_SHARE) { + drm_gem_object_get(r_obj); + obj->resv = r_obj->resv; + } + ret = drm_gem_object_init(dev, obj, size); if (ret) goto fail; @@ -1311,6 +1316,7 @@ struct drm_gem_object *msm_gem_import(struct drm_device *dev, * up in drm_gem_object_put() -> msm_gem_free_object() */ obj->import_attach = attach; + obj->resv = dmabuf->resv; drm_gem_private_object_init(dev, obj, size); npages = size / PAGE_SIZE; @@ -1346,7 +1352,7 @@ void *msm_gem_kernel_new(struct drm_device *dev, size_t size, uint32_t flags, uint64_t *iova) { void *vaddr; - struct drm_gem_object *obj = msm_gem_new(dev, size, flags); + struct drm_gem_object *obj = msm_gem_new(dev, size, flags, NULL); int ret; if (IS_ERR(obj)) diff --git a/drivers/gpu/drm/msm/msm_gem.h b/drivers/gpu/drm/msm/msm_gem.h index 86dc93cc1804..dff60cbc9d95 100644 --- a/drivers/gpu/drm/msm/msm_gem.h +++ b/drivers/gpu/drm/msm/msm_gem.h @@ -296,7 +296,7 @@ int msm_gem_cpu_fini(struct drm_gem_object *obj); int msm_gem_new_handle(struct drm_device *dev, struct drm_file *file, size_t size, uint32_t flags, uint32_t *handle, char *name); struct drm_gem_object *msm_gem_new(struct drm_device *dev, - size_t size, uint32_t flags); + size_t size, uint32_t flags, struct drm_gem_object *r_obj); void *msm_gem_kernel_new(struct drm_device *dev, size_t size, uint32_t flags, struct drm_gpuvm *vm, struct drm_gem_object **bo, uint64_t *iova); From a6d87a272b2c8fe1366ea9a8e2e4cccddbb4157f Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 29 Jul 2026 08:55:50 -0700 Subject: [PATCH 0300/1328] drm/msm/gem: Validate lazy VM in GEM_NEW Otherwise creating a _NO_SHARE BO before any BOs are mapped could cause a NPE. Reported-by: Sashiko Signed-off-by: Rob Clark Patchwork: https://patchwork.freedesktop.org/patch/743104/ Message-ID: <20260729155609.20190-14-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/msm_gem.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/msm/msm_gem.c b/drivers/gpu/drm/msm/msm_gem.c index 535c6e1acb3c..de4e60e28a71 100644 --- a/drivers/gpu/drm/msm/msm_gem.c +++ b/drivers/gpu/drm/msm/msm_gem.c @@ -1140,9 +1140,13 @@ int msm_gem_new_handle(struct drm_device *dev, struct drm_file *file, int ret; if (flags & MSM_BO_NO_SHARE) { + struct msm_drm_private *priv = dev->dev_private; struct msm_context *ctx = file->driver_priv; struct drm_gpuvm *vm = msm_context_vm(dev, ctx); + if (!priv->gpu || !vm) + return UERR(EINVAL, dev, "not supported with shared VM"); + r_obj = drm_gpuvm_resv_obj(vm); } From 00dfa76bdfc2fa807881df260f30053e2515c665 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 29 Jul 2026 08:55:51 -0700 Subject: [PATCH 0301/1328] drm/msm: Allow lazy VM creation to fail In the next commit, we'll stop falling back to shared VM if private VM creation fails. This isn't expected to happen in practice, it would either require small memory allocations to fail, or missing support in arm-smmu-qcom for setting up per-process pgtable support (ie. missing patch during bringup). Signed-off-by: Rob Clark Patchwork: https://patchwork.freedesktop.org/patch/743088/ Message-ID: <20260729155609.20190-15-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/adreno/adreno_gpu.c | 2 +- drivers/gpu/drm/msm/msm_drv.c | 9 ++++++++- drivers/gpu/drm/msm/msm_gem_submit.c | 12 ++++++++---- drivers/gpu/drm/msm/msm_gem_vma.c | 12 ++++++++---- drivers/gpu/drm/msm/msm_submitqueue.c | 8 +++++++- 5 files changed, 32 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/msm/adreno/adreno_gpu.c b/drivers/gpu/drm/msm/adreno/adreno_gpu.c index 5738eac9aebd..84101e534ebe 100644 --- a/drivers/gpu/drm/msm/adreno/adreno_gpu.c +++ b/drivers/gpu/drm/msm/adreno/adreno_gpu.c @@ -359,7 +359,7 @@ int adreno_fault_handler(struct msm_gpu *gpu, unsigned long iova, int flags, static bool valid_per_process_vm(struct msm_gpu *gpu, struct drm_gpuvm *vm) { - return (vm != gpu->vm); + return vm && (vm != gpu->vm); } int adreno_get_param(struct msm_gpu *gpu, struct msm_context *ctx, diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c index a5714e24fcc6..84b126c6347f 100644 --- a/drivers/gpu/drm/msm/msm_drv.c +++ b/drivers/gpu/drm/msm/msm_drv.c @@ -422,10 +422,14 @@ static int msm_ioctl_gem_info_iova(struct drm_device *dev, { struct msm_drm_private *priv = dev->dev_private; struct msm_context *ctx = file->driver_priv; + struct drm_gpuvm *vm = msm_context_vm(dev, ctx); if (!priv->gpu) return -EINVAL; + if (!vm) + return UERR(ENOMEM, dev, "no VM"); + if (msm_context_is_vmbind(ctx)) return UERR(EINVAL, dev, "VM_BIND is enabled"); @@ -436,7 +440,7 @@ static int msm_ioctl_gem_info_iova(struct drm_device *dev, * Don't pin the memory here - just get an address so that userspace can * be productive */ - return msm_gem_get_iova(obj, msm_context_vm(dev, ctx), iova); + return msm_gem_get_iova(obj, vm, iova); } static int msm_ioctl_gem_info_set_iova(struct drm_device *dev, @@ -450,6 +454,9 @@ static int msm_ioctl_gem_info_set_iova(struct drm_device *dev, if (!priv->gpu) return -EINVAL; + if (!vm) + return UERR(ENOMEM, dev, "no VM"); + if (msm_context_is_vmbind(ctx)) return UERR(EINVAL, dev, "VM_BIND is enabled"); diff --git a/drivers/gpu/drm/msm/msm_gem_submit.c b/drivers/gpu/drm/msm/msm_gem_submit.c index 040354ee16d1..6b0bee6c39bc 100644 --- a/drivers/gpu/drm/msm/msm_gem_submit.c +++ b/drivers/gpu/drm/msm/msm_gem_submit.c @@ -30,7 +30,7 @@ */ static struct msm_gem_submit *submit_create(struct drm_device *dev, - struct msm_gpu *gpu, + struct msm_gpu *gpu, struct drm_gpuvm *vm, struct msm_gpu_submitqueue *queue, uint32_t nr_bos, uint32_t nr_cmds, u64 drm_client_id) { @@ -66,7 +66,7 @@ static struct msm_gem_submit *submit_create(struct drm_device *dev, kref_init(&submit->ref); submit->dev = dev; - submit->vm = msm_context_vm(dev, queue->ctx); + submit->vm = vm; submit->gpu = gpu; submit->cmd = (void *)&submit->bos[nr_bos]; submit->queue = queue; @@ -552,6 +552,7 @@ int msm_ioctl_gem_submit(struct drm_device *dev, void *data, struct msm_drm_private *priv = dev->dev_private; struct drm_msm_gem_submit *args = data; struct msm_context *ctx = file->driver_priv; + struct drm_gpuvm *vm = msm_context_vm(dev, ctx); struct msm_gem_submit *submit = NULL; struct msm_gpu *gpu = priv->gpu; struct msm_gpu_submitqueue *queue; @@ -567,10 +568,13 @@ int msm_ioctl_gem_submit(struct drm_device *dev, void *data, if (!gpu) return -ENXIO; + if (!vm) + return UERR(ENOMEM, dev, "no VM"); + if (args->pad) return -EINVAL; - if (to_msm_vm(ctx->vm)->unusable) + if (to_msm_vm(vm)->unusable) return UERR(EPIPE, dev, "context is unusable"); /* for now, we just have 3d pipe.. eventually this would need to @@ -607,7 +611,7 @@ int msm_ioctl_gem_submit(struct drm_device *dev, void *data, } } - submit = submit_create(dev, gpu, queue, args->nr_bos, args->nr_cmds, + submit = submit_create(dev, gpu, vm, queue, args->nr_bos, args->nr_cmds, file->client_id); if (IS_ERR(submit)) { ret = PTR_ERR(submit); diff --git a/drivers/gpu/drm/msm/msm_gem_vma.c b/drivers/gpu/drm/msm/msm_gem_vma.c index 50d4812bdb4c..1f9b75fc12fd 100644 --- a/drivers/gpu/drm/msm/msm_gem_vma.c +++ b/drivers/gpu/drm/msm/msm_gem_vma.c @@ -956,7 +956,7 @@ msm_gem_vm_close(struct drm_gpuvm *gpuvm) static struct msm_vm_bind_job * -vm_bind_job_create(struct drm_device *dev, struct drm_file *file, +vm_bind_job_create(struct drm_device *dev, struct drm_file *file, struct drm_gpuvm *vm, struct msm_gpu_submitqueue *queue, uint32_t nr_ops) { struct msm_vm_bind_job *job; @@ -973,7 +973,7 @@ vm_bind_job_create(struct drm_device *dev, struct drm_file *file, return ERR_PTR(ret); } - job->vm = msm_context_vm(dev, queue->ctx); + job->vm = vm; job->queue = queue; INIT_LIST_HEAD(&job->vm_ops); @@ -1432,6 +1432,7 @@ msm_ioctl_vm_bind(struct drm_device *dev, void *data, struct drm_file *file) struct msm_drm_private *priv = dev->dev_private; struct drm_msm_vm_bind *args = data; struct msm_context *ctx = file->driver_priv; + struct drm_gpuvm *vm = msm_context_vm(dev, ctx); struct msm_vm_bind_job *job = NULL; struct msm_gpu *gpu = priv->gpu; struct msm_gpu_submitqueue *queue; @@ -1446,11 +1447,14 @@ msm_ioctl_vm_bind(struct drm_device *dev, void *data, struct drm_file *file) if (!gpu) return -ENXIO; + if (!vm) + return UERR(ENOMEM, dev, "no VM"); + /* * Maybe we could allow just UNMAP ops? OTOH userspace should just * immediately close the device file and all will be torn down. */ - if (to_msm_vm(msm_context_vm(dev, ctx))->unusable) + if (to_msm_vm(vm)->unusable) return UERR(EPIPE, dev, "context is unusable"); /* @@ -1481,7 +1485,7 @@ msm_ioctl_vm_bind(struct drm_device *dev, void *data, struct drm_file *file) } } - job = vm_bind_job_create(dev, file, queue, args->nr_ops); + job = vm_bind_job_create(dev, file, vm, queue, args->nr_ops); if (IS_ERR(job)) { ret = PTR_ERR(job); goto out_post_unlock; diff --git a/drivers/gpu/drm/msm/msm_submitqueue.c b/drivers/gpu/drm/msm/msm_submitqueue.c index 233c4f35ab12..d307cdd17d90 100644 --- a/drivers/gpu/drm/msm/msm_submitqueue.c +++ b/drivers/gpu/drm/msm/msm_submitqueue.c @@ -174,6 +174,7 @@ int msm_submitqueue_create(struct drm_device *drm, struct msm_context *ctx, struct msm_drm_private *priv = drm->dev_private; struct msm_gpu_submitqueue *queue; enum drm_sched_priority sched_prio; + struct drm_gpuvm *vm = NULL; unsigned ring_nr; int ret; @@ -186,6 +187,11 @@ int msm_submitqueue_create(struct drm_device *drm, struct msm_context *ctx, if (flags & MSM_SUBMITQUEUE_VM_BIND) { unsigned sz; + vm = msm_context_vm(drm, ctx); + + if (!vm) + return UERR(ENOMEM, drm, "no VM"); + /* Not allowed for kernel managed VMs (ie. kernel allocs VA) */ if (!msm_context_is_vmbind(ctx)) return -EINVAL; @@ -217,7 +223,7 @@ int msm_submitqueue_create(struct drm_device *drm, struct msm_context *ctx, queue->flags = flags; if (flags & MSM_SUBMITQUEUE_VM_BIND) { - struct drm_gpu_scheduler *sched = &to_msm_vm(msm_context_vm(drm, ctx))->sched; + struct drm_gpu_scheduler *sched = &to_msm_vm(vm)->sched; queue->entity = &queue->_vm_bind_entity[0]; From 3b35a5c528baf3c8cff89b44ae1c5174007b127a Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 29 Jul 2026 08:55:52 -0700 Subject: [PATCH 0302/1328] drm/msm: Don't fallback to shared VM for VM_BIND If the user wants a userspace managed VM (EN_VM_BIND) don't silently fall back to shared VM. Signed-off-by: Rob Clark Patchwork: https://patchwork.freedesktop.org/patch/743110/ Message-ID: <20260729155609.20190-16-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/msm_drv.c | 3 ++- drivers/gpu/drm/msm/msm_gpu.c | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c index 84b126c6347f..db1b655dd055 100644 --- a/drivers/gpu/drm/msm/msm_drv.c +++ b/drivers/gpu/drm/msm/msm_drv.c @@ -234,7 +234,8 @@ struct drm_gpuvm *msm_context_vm(struct drm_device *dev, struct msm_context *ctx if (!ctx->vm) { vm = msm_gpu_create_private_vm( priv->gpu, current, !ctx->userspace_managed_vm); - smp_store_release(&ctx->vm, vm); + if (!IS_ERR_OR_NULL(vm)) + smp_store_release(&ctx->vm, vm); } return ctx->vm; diff --git a/drivers/gpu/drm/msm/msm_gpu.c b/drivers/gpu/drm/msm/msm_gpu.c index 03c057856065..0c2c35636251 100644 --- a/drivers/gpu/drm/msm/msm_gpu.c +++ b/drivers/gpu/drm/msm/msm_gpu.c @@ -879,7 +879,7 @@ msm_gpu_create_private_vm(struct msm_gpu *gpu, struct task_struct *task, to_msm_vm(vm)->pid = get_pid(task_pid(task)); } - if (IS_ERR_OR_NULL(vm)) + if (IS_ERR_OR_NULL(vm) && kernel_managed) vm = drm_gpuvm_get(gpu->vm); return vm; From 1b8029394fb77adde9c494a3acd40b0b39793b55 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 29 Jul 2026 08:55:53 -0700 Subject: [PATCH 0303/1328] drm/msm: Fix per-process-pgtables check ctx->vm should not be inialized yet (or if it has, an error is returned immediately following this check), so this isn't a valid way to check for per-process-pgtable support. Instead just check if create_private_vm() is supported. Reported-by: Sashiko Signed-off-by: Rob Clark Patchwork: https://patchwork.freedesktop.org/patch/743096/ Message-ID: <20260729155609.20190-17-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/adreno/adreno_gpu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/adreno/adreno_gpu.c b/drivers/gpu/drm/msm/adreno/adreno_gpu.c index 84101e534ebe..3370cd44382f 100644 --- a/drivers/gpu/drm/msm/adreno/adreno_gpu.c +++ b/drivers/gpu/drm/msm/adreno/adreno_gpu.c @@ -513,7 +513,7 @@ int adreno_set_param(struct msm_gpu *gpu, struct msm_context *ctx, guard(rwsem_read)(&ctx->ctxlock); /* We can only support VM_BIND with per-process pgtables: */ - if (ctx->vm == gpu->vm) + if (!gpu->funcs->create_private_vm) return UERR(EINVAL, drm, "requires per-process pgtables"); /* From 7aeada642ebfd52ddb87aaf1f0324a7fcf636610 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 29 Jul 2026 08:55:54 -0700 Subject: [PATCH 0304/1328] drm/msm: Fixup invalid overflow check On overflow struct_size() would return SIZE_MAX. But kzalloc() (and friends) check this already, so we can just remove the check. On the other hand, we should be using the overflow helpers to calculate the cmd array size. Reported-by: Sashiko Signed-off-by: Rob Clark Patchwork: https://patchwork.freedesktop.org/patch/743111/ Message-ID: <20260729155609.20190-18-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/msm_gem_submit.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_gem_submit.c b/drivers/gpu/drm/msm/msm_gem_submit.c index 6b0bee6c39bc..5862db05297a 100644 --- a/drivers/gpu/drm/msm/msm_gem_submit.c +++ b/drivers/gpu/drm/msm/msm_gem_submit.c @@ -36,14 +36,11 @@ static struct msm_gem_submit *submit_create(struct drm_device *dev, { static atomic_t ident = ATOMIC_INIT(0); struct msm_gem_submit *submit; - uint64_t sz; + size_t sz; int ret; - sz = struct_size(submit, bos, nr_bos) + - ((u64)nr_cmds * sizeof(submit->cmd[0])); - - if (sz > SIZE_MAX) - return ERR_PTR(-ENOMEM); + sz = size_add(struct_size(submit, bos, nr_bos), + array_size(sizeof(submit->cmd[0]), nr_cmds)); submit = kzalloc(sz, GFP_KERNEL | __GFP_NOWARN); if (!submit) From 9e4c1ef73b4301c5f9dc918982dd4f1ffb3e1052 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 29 Jul 2026 15:40:57 -0700 Subject: [PATCH 0305/1328] apparmor: compressed_data not described in aa_get_data_from_compressed Warning: security/apparmor/apparmorfs.c:501 function parameter 'compressed_data' not described in 'aa_get_data_from_compressed' Warning: security/apparmor/apparmorfs.c:501 function parameter 'compressed_data' not described in 'aa_get_data_from_compressed' Warning: security/apparmor/apparmorfs.c:501 function parameter 'compressed_data' not described in 'aa_get_data_from_compressed' Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202607240144.4iqulDF1-lkp@intel.com/ Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 152c7967ff1b..79b91c680e46 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -525,7 +525,7 @@ static int decompress_zstd(char *src, size_t slen, char *dst, size_t dlen) * @userbuf: user buffer to copy data from (NOT NULL) * @buffer_size: size of user buffer * @pos: position write is at in the file (NOT NULL) - * @compressed_data Ptr on compressed data. *compressed_data is allocated there + * @compressed_data: Ptr on compressed data. *compressed_data is allocated there * * Returns: kernel buffer containing copy of user buffer data or an * ERR_PTR on failure. From 39268dbd7bc7feecb684b7f84eb20efaf2241ff1 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 8 Jun 2026 22:44:24 +0200 Subject: [PATCH 0306/1328] dt-bindings: mfd: syscon: Disallow simple-bus with syscon "syscon" is a system controller with registers having their own functions, thus not really a trivial MMIO simple bus. "simple-bus" on the other hand is just a bus on which multiple devices sit and the "simple" means no functions are allowed here. Combination of both "syscon" and "simple-bus" is abuse of DT for easier instantiating of Linux device drivers so add a schema to disallow that. Unfortunately there are a few old cases of that patterns, so add exceptions: 1. "cznic,turris1x-cpld" and "img,pistachio-cr-periph" are already used in upstream DTS. 2. TI has several DTSI with a child of SCM device (e.g. "ti,am3-scm") using "syscon" and "simple-bus" but without a dedicated compatible documented anywhere. Add new compatibles for such cases. Additionally, add comments around code enforcing two or three compatibles: it is similar safeguard detecting incorrect bindings. Signed-off-by: Krzysztof Kozlowski Acked-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260608-n-dt-bindings-simple-bus-syscon-v3-1-4eba9ec1212a@oss.qualcomm.com Signed-off-by: Lee Jones --- .../bindings/mfd/syscon-common.yaml | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/Documentation/devicetree/bindings/mfd/syscon-common.yaml b/Documentation/devicetree/bindings/mfd/syscon-common.yaml index 14a08e7bc8bd..2d5eef5add54 100644 --- a/Documentation/devicetree/bindings/mfd/syscon-common.yaml +++ b/Documentation/devicetree/bindings/mfd/syscon-common.yaml @@ -32,6 +32,7 @@ properties: compatible: contains: const: syscon + # Always require a specific compatible for syscon minItems: 2 maxItems: 5 # Should be enough @@ -52,11 +53,44 @@ allOf: contains: const: simple-mfd then: + # Always require a specific compatible for syscon with simple-mfd properties: compatible: minItems: 3 maxItems: 5 + - if: + properties: + compatible: + contains: + const: simple-bus + then: + # simple-bus conflicts with syscon - if a device is a system controller + # with miscellaneous registers, then it has at least one dedicated + # function thus it is not a simple bus. Allow existing exceptions. + if: + properties: + compatible: + not: + contains: + # This list CANNOT grow + enum: + - cznic,turris1x-cpld + - img,pistachio-cr-periph + - ti,am3352-scm-conf + - ti,am4372-scm-conf + - ti,dm814-scm-conf + - ti,dm8168-scm-conf + - ti,dra7-scm-conf + - ti,omap2-scm-conf + - ti,omap3-scm-conf + - ti,omap4-sysc-padconf-global + - ti,omap5-scm-wkup-conf + - ti,omap5-sysc-padconf-global + then: + required: + - incorrect-usage-of-simple-bus-and-syscon + additionalProperties: true examples: From 6165900f3922f923bb363d58c2893005d26d1b30 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 8 Jun 2026 22:44:25 +0200 Subject: [PATCH 0307/1328] dt-bindings: mfd: syscon: Drop custom select for older dtschema Older dtschema <2024.02 required custom select to avoid applying this binding to anything having "syscon" compatible. That's not the case anymore and this additional select has two headaches: 1. Duplicates all the compatibles listed in the schema. 2. Is error-prone, because it requires contributor to add the compatible in two places, otherwise the schema will be silently ignored. The select list already misses mentioning compatibles: mediatek,mt8365-infracfg-nao and renesas,r9a08g046-lvds-cmn (with the latter being reverted for different reasons). This requires bumping minimum dtschema requirement to v2024.04, which feels old enough to be a safe requirement. Signed-off-by: Krzysztof Kozlowski Acked-by: Conor Dooley Link: https://patch.msgid.link/20260608-n-dt-bindings-simple-bus-syscon-v3-2-4eba9ec1212a@oss.qualcomm.com Signed-off-by: Lee Jones --- Documentation/devicetree/bindings/Makefile | 2 +- .../devicetree/bindings/mfd/syscon.yaml | 116 ------------------ 2 files changed, 1 insertion(+), 117 deletions(-) diff --git a/Documentation/devicetree/bindings/Makefile b/Documentation/devicetree/bindings/Makefile index 00149e824261..6b4b4f51c371 100644 --- a/Documentation/devicetree/bindings/Makefile +++ b/Documentation/devicetree/bindings/Makefile @@ -6,7 +6,7 @@ DT_MK_SCHEMA ?= dt-mk-schema DT_SCHEMA_LINT = $(shell which yamllint || \ echo "warning: python package 'yamllint' not installed, skipping" >&2) -DT_SCHEMA_MIN_VERSION = 2023.9 +DT_SCHEMA_MIN_VERSION = 2024.4 PHONY += check_dtschema_version check_dtschema_version: diff --git a/Documentation/devicetree/bindings/mfd/syscon.yaml b/Documentation/devicetree/bindings/mfd/syscon.yaml index e22867088063..fe882d3f828b 100644 --- a/Documentation/devicetree/bindings/mfd/syscon.yaml +++ b/Documentation/devicetree/bindings/mfd/syscon.yaml @@ -19,122 +19,6 @@ description: | maintainers: - Lee Jones -# Need a select with all compatibles listed for compatibility with older -# dtschema (<2024.02), so this will not be selected for other schemas having -# syscon fallback. -select: - properties: - compatible: - contains: - enum: - - airoha,en7581-pbus-csr - - al,alpine-sysfabric-service - - allwinner,sun8i-a83t-system-controller - - allwinner,sun8i-h3-system-controller - - allwinner,sun8i-v3s-system-controller - - allwinner,sun50i-a64-system-controller - - altr,l3regs - - altr,sdr-ctl - - amd,pensando-elba-syscon - - amlogic,meson-mx-assist - - amlogic,meson-mx-bootrom - - amlogic,meson8-analog-top - - amlogic,meson8b-analog-top - - amlogic,meson8-pmu - - amlogic,meson8b-pmu - - apm,merlin-poweroff-mailbox - - apm,mustang-poweroff-mailbox - - apm,xgene-csw - - apm,xgene-efuse - - apm,xgene-mcb - - apm,xgene-rb - - apm,xgene-scu - - atmel,sama5d2-sfrbu - - atmel,sama5d3-nfc-io - - atmel,sama5d3-sfrbu - - atmel,sama5d4-sfrbu - - axis,artpec6-syscon - - brcm,cru-clkset - - brcm,sr-cdru - - brcm,sr-mhb - - cirrus,ep7209-syscon1 - - cirrus,ep7209-syscon2 - - cirrus,ep7209-syscon3 - - cnxt,cx92755-uc - - econet,en751221-chip-scu - - freecom,fsg-cs2-system-controller - - fsl,imx93-aonmix-ns-syscfg - - fsl,imx93-wakeupmix-syscfg - - fsl,ls1088a-reset - - fsl,vf610-anatop - - fsl,vf610-mscm-cpucfg - - hisilicon,dsa-subctrl - - hisilicon,hi6220-sramctrl - - hisilicon,hip04-ppe - - hisilicon,pcie-sas-subctrl - - hisilicon,peri-subctrl - - hpe,gxp-sysreg - - loongson,ls1b-syscon - - loongson,ls1c-syscon - - lsi,axxia-syscon - - marvell,armada-3700-cpu-misc - - marvell,armada-3700-nb-pm - - marvell,armada-3700-avs - - marvell,armada-3700-usb2-host-device-misc - - marvell,armada-3700-usb2-host-misc - - marvell,dove-global-config - - mediatek,mt2701-pctl-a-syscfg - - mediatek,mt2712-pctl-a-syscfg - - mediatek,mt6397-pctl-pmic-syscfg - - mediatek,mt7981-topmisc - - mediatek,mt7988-topmisc - - mediatek,mt8135-pctl-a-syscfg - - mediatek,mt8135-pctl-b-syscfg - - mediatek,mt8173-pctl-a-syscfg - - mediatek,mt8365-syscfg - - microchip,lan966x-cpu-syscon - - microchip,mpfs-control-scb - - microchip,mpfs-sysreg-scb - - microchip,sam9x60-sfr - - microchip,sama7d65-ddr3phy - - microchip,sama7d65-sfrbu - - microchip,sama7g5-ddr3phy - - mscc,ocelot-cpu-syscon - - mstar,msc313-pmsleep - - nuvoton,ma35d1-sys - - nuvoton,wpcm450-shm - - nxp,s32g2-gpr - - nxp,s32g3-gpr - - qcom,apq8064-mmss-sfpb - - qcom,apq8064-sps-sic - - rockchip,px30-qos - - rockchip,rk3036-qos - - rockchip,rk3066-qos - - rockchip,rk3128-qos - - rockchip,rk3228-qos - - rockchip,rk3288-qos - - rockchip,rk3368-qos - - rockchip,rk3399-qos - - rockchip,rk3528-qos - - rockchip,rk3562-qos - - rockchip,rk3568-qos - - rockchip,rk3576-qos - - rockchip,rk3588-qos - - rockchip,rv1126-qos - - st,spear1340-misc - - stericsson,nomadik-pmu - - starfive,jh7100-sysmain - - ti,am62-opp-efuse-table - - ti,am62-usb-phy-ctrl - - ti,am625-dss-oldi-io-ctrl - - ti,am62p-cpsw-mac-efuse - - ti,am654-dss-oldi-io-ctrl - - ti,j784s4-acspcie-proxy-ctrl - - ti,j784s4-pcie-ctrl - - ti,keystone-pllctrl - required: - - compatible - properties: compatible: oneOf: From 90d9f2988d007ec25706022533a03dc52678f58b Mon Sep 17 00:00:00 2001 From: Roman Vivchar Date: Wed, 17 Jun 2026 12:48:46 +0300 Subject: [PATCH 0308/1328] mfd: mt6397-core: Add mt6323 EFUSE support The mt6323 PMIC includes an EFUSE. Register the EFUSE in the mt6323 devices array to allow the corresponding driver to probe using compatible string. Signed-off-by: Roman Vivchar Tested-by: Ben Grisdale # Amazon Echo Dot (2nd Generation) Link: https://patch.msgid.link/20260617-mt6323-nvmem-v2-3-4f30e36aa0f4@protonmail.com Signed-off-by: Lee Jones --- drivers/mfd/mt6397-core.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/mfd/mt6397-core.c b/drivers/mfd/mt6397-core.c index 1bdacda9a933..ea1d039477e3 100644 --- a/drivers/mfd/mt6397-core.c +++ b/drivers/mfd/mt6397-core.c @@ -125,6 +125,9 @@ static const struct resource mt6323_pwrc_resources[] = { static const struct mfd_cell mt6323_devs[] = { { + .name = "mt6323-efuse", + .of_compatible = "mediatek,mt6323-efuse", + }, { .name = "mt6323-rtc", .num_resources = ARRAY_SIZE(mt6323_rtc_resources), .resources = mt6323_rtc_resources, From b87549a9bbda8736b7cf98d51bdc1d148e6cd09b Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 8 Jun 2026 10:57:34 +0200 Subject: [PATCH 0309/1328] mfd: si476x: Modernize GPIO handling The SI476X driver depends on the legacy GPIO API. As it only really use a single GPIO for reset, and this can be easily converted to use a GPIO descriptor, modernize the driver. The "reset" GPIO is obtained from a device property, such as a device tree ("reset-gpios", which is standard, but this hardware has no DT bindings as of now) or a software node for static platforms. Out-of-tree users can easily adopt to providing a GPIO descriptor this way. Signed-off-by: Linus Walleij Reviewed-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260608-mfd-si476x-v2-1-da5f779c1888@kernel.org Signed-off-by: Lee Jones --- drivers/mfd/Kconfig | 1 - drivers/mfd/si476x-cmd.c | 1 - drivers/mfd/si476x-i2c.c | 46 ++++++++++------------------- include/linux/mfd/si476x-core.h | 5 ++-- include/linux/mfd/si476x-platform.h | 2 -- 5 files changed, 19 insertions(+), 36 deletions(-) diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 763ce6a34782..35f6e9b76d05 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -1461,7 +1461,6 @@ config MFD_SEC_I2C config MFD_SI476X_CORE tristate "Silicon Laboratories 4761/64/68 AM/FM radio." depends on I2C - depends on GPIOLIB_LEGACY select MFD_CORE select REGMAP_I2C help diff --git a/drivers/mfd/si476x-cmd.c b/drivers/mfd/si476x-cmd.c index 3bb2decfebd3..58e9bea7e90a 100644 --- a/drivers/mfd/si476x-cmd.c +++ b/drivers/mfd/si476x-cmd.c @@ -15,7 +15,6 @@ #include #include #include -#include #include #include diff --git a/drivers/mfd/si476x-i2c.c b/drivers/mfd/si476x-i2c.c index 7ddc97dfc940..55700ce711f4 100644 --- a/drivers/mfd/si476x-i2c.c +++ b/drivers/mfd/si476x-i2c.c @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include #include @@ -130,8 +130,8 @@ int si476x_core_start(struct si476x_core *core, bool soft) int err; if (!soft) { - if (gpio_is_valid(core->gpio_reset)) - gpio_set_value_cansleep(core->gpio_reset, 1); + if (core->reset) + gpiod_set_value_cansleep(core->reset, 0); if (client->irq) enable_irq(client->irq); @@ -197,8 +197,8 @@ int si476x_core_start(struct si476x_core *core, bool soft) else cancel_delayed_work_sync(&core->status_monitor); - if (gpio_is_valid(core->gpio_reset)) - gpio_set_value_cansleep(core->gpio_reset, 0); + if (core->reset) + gpiod_set_value_cansleep(core->reset, 1); return err; } @@ -243,8 +243,8 @@ int si476x_core_stop(struct si476x_core *core, bool soft) cancel_delayed_work_sync(&core->status_monitor); if (!soft) { - if (gpio_is_valid(core->gpio_reset)) - gpio_set_value_cansleep(core->gpio_reset, 0); + if (core->reset) + gpiod_set_value_cansleep(core->reset, 1); } return err; } @@ -712,24 +712,18 @@ static int si476x_core_probe(struct i2c_client *client) atomic_set(&core->is_alive, 0); core->power_state = SI476X_POWER_DOWN; + core->reset = devm_gpiod_get_optional(&client->dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(core->reset)) + return dev_err_probe(&client->dev, PTR_ERR(core->reset), + "error getting reset GPIO\n"); + gpiod_set_consumer_name(core->reset, "si476x reset"); + pdata = dev_get_platdata(&client->dev); if (pdata) { memcpy(&core->power_up_parameters, &pdata->power_up_parameters, sizeof(core->power_up_parameters)); - - core->gpio_reset = -1; - if (gpio_is_valid(pdata->gpio_reset)) { - rval = gpio_request(pdata->gpio_reset, "si476x reset"); - if (rval) { - dev_err(&client->dev, - "Failed to request gpio: %d\n", rval); - return rval; - } - core->gpio_reset = pdata->gpio_reset; - gpio_direction_output(core->gpio_reset, 0); - } - core->diversity_mode = pdata->diversity_mode; memcpy(&core->pinmux, &pdata->pinmux, sizeof(struct si476x_pinmux)); @@ -748,7 +742,7 @@ static int si476x_core_probe(struct i2c_client *client) core->supplies); if (rval) { dev_err(&client->dev, "Failed to get all of the regulators\n"); - goto free_gpio; + return rval; } mutex_init(&core->cmd_lock); @@ -761,7 +755,7 @@ static int si476x_core_probe(struct i2c_client *client) GFP_KERNEL); if (rval) { dev_err(&client->dev, "Could not allocate the FIFO\n"); - goto free_gpio; + return rval; } mutex_init(&core->rds_drainer_status_lock); init_waitqueue_head(&core->rds_read_queue); @@ -827,11 +821,6 @@ static int si476x_core_probe(struct i2c_client *client) free_kfifo: kfifo_free(&core->rds_fifo); - -free_gpio: - if (gpio_is_valid(core->gpio_reset)) - gpio_free(core->gpio_reset); - return rval; } @@ -848,9 +837,6 @@ static void si476x_core_remove(struct i2c_client *client) cancel_delayed_work_sync(&core->status_monitor); kfifo_free(&core->rds_fifo); - - if (gpio_is_valid(core->gpio_reset)) - gpio_free(core->gpio_reset); } diff --git a/include/linux/mfd/si476x-core.h b/include/linux/mfd/si476x-core.h index e913b2cdf77d..d9e3a322134c 100644 --- a/include/linux/mfd/si476x-core.h +++ b/include/linux/mfd/si476x-core.h @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -104,7 +105,7 @@ enum si476x_power_state { * @power_state: Current power state of the device. * @supplies: Structure containing handles to all power supplies used * by the device (NULL ones are ignored). - * @gpio_reset: GPIO pin connectet to the RSTB pin of the chip. + * @reset: GPIO connected to the RSTB pin of the chip. * @pinmux: Chip's configurable pins configuration. * @diversity_mode: Chips role when functioning in diversity mode. * @is_alive: Chip is initialized and active. @@ -142,7 +143,7 @@ struct si476x_core { struct regulator_bulk_data supplies[4]; - int gpio_reset; + struct gpio_desc *reset; struct si476x_pinmux pinmux; enum si476x_phase_diversity_mode diversity_mode; diff --git a/include/linux/mfd/si476x-platform.h b/include/linux/mfd/si476x-platform.h index cb99e16ca947..f9e1f6b27277 100644 --- a/include/linux/mfd/si476x-platform.h +++ b/include/linux/mfd/si476x-platform.h @@ -246,8 +246,6 @@ enum si476x_phase_diversity_mode { * Platform dependent definition */ struct si476x_platform_data { - int gpio_reset; /* < 0 if not used */ - struct si476x_power_up_args power_up_parameters; enum si476x_phase_diversity_mode diversity_mode; From da3a98c941753255418616d249e5b40db0927c26 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Wed, 10 Jun 2026 16:03:52 -0700 Subject: [PATCH 0310/1328] mfd: ipaq-micro: Fix out-of-bounds stack read in ipaq_micro_str ipaq_micro_str() decodes a UTF-16LE string into an ASCII string. It copies characters to a stack buffer retstr, but fails to null-terminate it. When kstrdup() is called on retstr, it can read past the buffer into uninitialized stack memory, potentially leaking stack contents. Fix this by initializing retstr to zero. Reported-by: sashiko-bot@kernel.org Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Dmitry Torokhov Link: https://patch.msgid.link/aintJF4X5tWDW-Ej@google.com Signed-off-by: Lee Jones --- drivers/mfd/ipaq-micro.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mfd/ipaq-micro.c b/drivers/mfd/ipaq-micro.c index 4b757d847282..5146a6eb0e5a 100644 --- a/drivers/mfd/ipaq-micro.c +++ b/drivers/mfd/ipaq-micro.c @@ -221,7 +221,7 @@ static void ipaq_micro_eeprom_read(struct ipaq_micro *micro, static char *ipaq_micro_str(u8 *wchar, u8 len) { - char retstr[256]; + char retstr[256] = { 0 }; u8 i; for (i = 0; i < len / 2; i++) From c17b056c136d569977bbdbf1dae6dcb70918c9ce Mon Sep 17 00:00:00 2001 From: Fenglin Wu Date: Tue, 9 Jun 2026 23:49:59 -0700 Subject: [PATCH 0311/1328] dt-bindings: mfd: qcom,spmi-pmic: Document PMG1110 Add compatible string for PMG1110 which is used on Maili platform. Signed-off-by: Fenglin Wu Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260609-pmg1110-v1-1-6604d0adc907@oss.qualcomm.com Signed-off-by: Lee Jones --- Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml b/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml index 644c42b5e2e5..809be2756a0c 100644 --- a/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml +++ b/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml @@ -80,6 +80,7 @@ properties: - qcom,pmcx0102 - qcom,pmd8028 - qcom,pmd9635 + - qcom,pmg1110 - qcom,pmh0101 - qcom,pmh0104 - qcom,pmh0110 From 3756bf05c963563f1325af105356541882dd5899 Mon Sep 17 00:00:00 2001 From: Oleg Proshkin Date: Thu, 11 Jun 2026 18:46:29 +0300 Subject: [PATCH 0312/1328] mfd: axp20x: Preserve other control bits when powering off axp20x_power_off() triggers shutdown by writing AXP20X_OFF (BIT(7)) to the power-off control register with regmap_write(), which rewrites the whole register and clears other control bits in it. On the AXP221/AXP223 (and the register-compatible AXP228) - that register also holds the CHGLED auto-control bit. Clearing it during an orderly shutdown disables the hardware charge indicator, so the charge LED stays dark while the board is powered off and charging. Other variants keep unrelated configuration in the same register too. Set only the power-off bit with regmap_set_bits() and leave the rest of the register untouched. The shutdown register is readable on every variant, so the read-modify-write should be safe. Tested on a ClockworkPi uConsole (Raspberry Pi Compute Module 4, AXP228, which enumerates as AXP221): register AXP20X_OFF_CTRL reads 0x08 at runtime, so the old code left it 0x80 whereas setting only BIT(7) leaves 0x88. Writing 0x88 at power-off enables the charge LED while still powering off the PMIC. Signed-off-by: Oleg Proshkin Reviewed-by: Chen-Yu Tsai Link: https://patch.msgid.link/20260611154629.76607-1-oleg.pro171@gmail.com Signed-off-by: Lee Jones --- drivers/mfd/axp20x.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mfd/axp20x.c b/drivers/mfd/axp20x.c index 679364189ea5..50df66de24e0 100644 --- a/drivers/mfd/axp20x.c +++ b/drivers/mfd/axp20x.c @@ -1253,7 +1253,7 @@ static int axp20x_power_off(struct sys_off_data *data) break; } - regmap_write(axp20x->regmap, shutdown_reg, AXP20X_OFF); + regmap_set_bits(axp20x->regmap, shutdown_reg, AXP20X_OFF); /* Give capacitors etc. time to drain to avoid kernel panic msg. */ mdelay(500); From 9bcc081252d8a424932a8d8adeb5c2f5d921260a Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 29 Apr 2026 11:42:36 +0200 Subject: [PATCH 0313/1328] MAINTAINERS: Add Intel LPSS section to follow the changes Add Intel LPSS section to follow the changes in the related drivers. These are all for the Intel SoCs and platforms starting from Sky Lake. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260429094521.3672945-1-andriy.shevchenko@linux.intel.com Signed-off-by: Lee Jones --- MAINTAINERS | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 15011f5752a9..bf6c7e83d8c8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13229,6 +13229,17 @@ F: drivers/spi/spi-ljca.c F: drivers/usb/misc/usb-ljca.c F: include/linux/usb/ljca.h +INTEL LPSS (Low Power SubSystem) DRIVERS +R: Andy Shevchenko +S: Supported +F: drivers/dma/idma64* +F: drivers/i2c/busses/i2c-designware-* +F: drivers/mfd/intel-lpss* +F: drivers/pwm/pwm-lpss* +F: drivers/tty/serial/8250/8250_dw.c +F: drivers/tty/serial/8250/8250_dwlib.* +F: drivers/spi/spi-pxa2xx* + INTEL MANAGEMENT ENGINE (mei) M: Alexander Usyskin L: linux-kernel@vger.kernel.org From 4d8ba321a7867bfb91426e9f54518d040fb04ebc Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Mon, 8 Jun 2026 11:27:07 +0100 Subject: [PATCH 0314/1328] mfd: cs42l43: Use new SoundWire enumeration helper Now the new wait for SoundWire enumeration helper no longer depends on unattach_request it is safe to use from probe time. Update the driver to use the new core helper. Signed-off-by: Charles Keepax Reviewed-by: Srinivas Kandagatla Tested-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260608102714.2503120-4-ckeepax@opensource.cirrus.com Signed-off-by: Lee Jones --- drivers/mfd/cs42l43-i2c.c | 2 -- drivers/mfd/cs42l43-sdw.c | 7 ------- drivers/mfd/cs42l43.c | 15 ++++++--------- include/linux/mfd/cs42l43.h | 2 -- 4 files changed, 6 insertions(+), 20 deletions(-) diff --git a/drivers/mfd/cs42l43-i2c.c b/drivers/mfd/cs42l43-i2c.c index 0a0ab5e549a5..4db452b41220 100644 --- a/drivers/mfd/cs42l43-i2c.c +++ b/drivers/mfd/cs42l43-i2c.c @@ -45,8 +45,6 @@ static int cs42l43_i2c_probe(struct i2c_client *i2c) cs42l43->dev = &i2c->dev; cs42l43->irq = i2c->irq; - /* A device on an I2C is always attached by definition. */ - cs42l43->attached = true; cs42l43->variant_id = (long)device_get_match_data(cs42l43->dev); cs42l43->regmap = devm_regmap_init_i2c(i2c, &cs42l43_i2c_regmap); diff --git a/drivers/mfd/cs42l43-sdw.c b/drivers/mfd/cs42l43-sdw.c index 794c98378175..2b87ae2d79c5 100644 --- a/drivers/mfd/cs42l43-sdw.c +++ b/drivers/mfd/cs42l43-sdw.c @@ -100,17 +100,10 @@ static int cs42l43_sdw_update_status(struct sdw_slave *sdw, enum sdw_slave_statu sdw_write_no_pm(sdw, CS42L43_GEN_INT_MASK_1, CS42L43_INT_STAT_GEN1_MASK); - - cs42l43->attached = true; - - complete(&cs42l43->device_attach); break; case SDW_SLAVE_UNATTACHED: dev_dbg(cs42l43->dev, "Device detach\n"); - cs42l43->attached = false; - - reinit_completion(&cs42l43->device_attach); complete(&cs42l43->device_detach); break; default: diff --git a/drivers/mfd/cs42l43.c b/drivers/mfd/cs42l43.c index ed6d93893de0..d2bbd2f18af7 100644 --- a/drivers/mfd/cs42l43.c +++ b/drivers/mfd/cs42l43.c @@ -586,15 +586,13 @@ static int cs42l43_soft_reset(struct cs42l43 *cs42l43) */ static int cs42l43_wait_for_attach(struct cs42l43 *cs42l43) { - if (!cs42l43->attached) { - unsigned long timeout = msecs_to_jiffies(CS42L43_SDW_ATTACH_TIMEOUT_MS); - unsigned long time; + int ret; - time = wait_for_completion_timeout(&cs42l43->device_attach, timeout); - if (!time) { - dev_err(cs42l43->dev, "Timed out waiting for device re-attach\n"); - return -ETIMEDOUT; - } + if (cs42l43->sdw) { + ret = sdw_slave_wait_for_init(cs42l43->sdw, + CS42L43_SDW_ATTACH_TIMEOUT_MS); + if (ret) + return ret; } regcache_cache_only(cs42l43->regmap, false); @@ -1120,7 +1118,6 @@ int cs42l43_dev_probe(struct cs42l43 *cs42l43) dev_set_drvdata(cs42l43->dev, cs42l43); mutex_init(&cs42l43->pll_lock); - init_completion(&cs42l43->device_attach); init_completion(&cs42l43->device_detach); init_completion(&cs42l43->firmware_download); INIT_WORK(&cs42l43->boot_work, cs42l43_boot_work); diff --git a/include/linux/mfd/cs42l43.h b/include/linux/mfd/cs42l43.h index ff0f7e365a19..8e993fb535e6 100644 --- a/include/linux/mfd/cs42l43.h +++ b/include/linux/mfd/cs42l43.h @@ -86,7 +86,6 @@ struct cs42l43 { struct regmap_irq_chip_data *irq_data; struct work_struct boot_work; - struct completion device_attach; struct completion device_detach; struct completion firmware_download; int firmware_error; @@ -96,7 +95,6 @@ struct cs42l43 { struct mutex pll_lock; bool sdw_pll_active; - bool attached; bool hw_lock; long variant_id; }; From 6ebc88313c4a9481802cf54ca83a4cb6add9754a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Duje=20Mihanovi=C4=87?= Date: Sat, 13 Jun 2026 16:20:53 +0200 Subject: [PATCH 0315/1328] dt-bindings: mfd: 88pm886: Allow vbus regulator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add vbus to patternProperties for the regulators node to allow for the PMIC's vbus regulator to be exposed. Reviewed-by: Karel Balej Acked-by: Conor Dooley Signed-off-by: Duje Mihanović Link: https://patch.msgid.link/20260613-88pm886-vbus-v2-1-021dfb02c6bb@dujemihanovic.xyz Signed-off-by: Lee Jones --- Documentation/devicetree/bindings/mfd/marvell,88pm886-a1.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/mfd/marvell,88pm886-a1.yaml b/Documentation/devicetree/bindings/mfd/marvell,88pm886-a1.yaml index 92a72a99fd79..940262898353 100644 --- a/Documentation/devicetree/bindings/mfd/marvell,88pm886-a1.yaml +++ b/Documentation/devicetree/bindings/mfd/marvell,88pm886-a1.yaml @@ -29,7 +29,7 @@ properties: type: object additionalProperties: false patternProperties: - "^(ldo(1[0-6]|[1-9])|buck[1-5])$": + "^(ldo(1[0-6]|[1-9])|buck[1-5]|vbus)$": type: object $ref: /schemas/regulator/regulator.yaml# description: LDO or buck regulator. From 824b2c8473f1cb28fb4a0e107ac423ccc8d2da4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Duje=20Mihanovi=C4=87?= Date: Sat, 13 Jun 2026 16:20:54 +0200 Subject: [PATCH 0316/1328] mfd: 88pm886: Initialize the battery page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Initialize the PMIC's battery page. The battery page registers are shared between Vbus regulator, charger, fuelgauge and camera flash blocks, hence the commonization of the page. Signed-off-by: Duje Mihanović Reviewed-by: Karel Balej Link: https://patch.msgid.link/20260613-88pm886-vbus-v2-2-021dfb02c6bb@dujemihanovic.xyz Signed-off-by: Lee Jones --- drivers/mfd/88pm886.c | 21 ++++++++++++++++++++- include/linux/mfd/88pm886.h | 5 +++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/drivers/mfd/88pm886.c b/drivers/mfd/88pm886.c index e411d8dee554..f8401d5e6dbe 100644 --- a/drivers/mfd/88pm886.c +++ b/drivers/mfd/88pm886.c @@ -16,6 +16,12 @@ static const struct regmap_config pm886_regmap_config = { .max_register = PM886_REG_RTC_SPARE6, }; +static const struct regmap_config pm886_regmap_battery_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = PM886_REG_CLS_CONFIG1, +}; + static const struct regmap_irq pm886_regmap_irqs[] = { REGMAP_IRQ_REG(PM886_IRQ_ONKEY, 0, PM886_INT_ENA1_ONKEY), }; @@ -85,10 +91,11 @@ static int pm886_setup_irq(struct pm886_chip *chip, static int pm886_probe(struct i2c_client *client) { + struct regmap *regmap, *regmap_battery; struct regmap_irq_chip_data *irq_data; struct device *dev = &client->dev; + struct i2c_client *battery_page; struct pm886_chip *chip; - struct regmap *regmap; unsigned int chip_id; int err; @@ -112,6 +119,18 @@ static int pm886_probe(struct i2c_client *client) if (chip->chip_id != chip_id) return dev_err_probe(dev, -EINVAL, "Unsupported chip: 0x%x\n", chip_id); + battery_page = devm_i2c_new_dummy_device(dev, client->adapter, + client->addr + PM886_PAGE_OFFSET_BATTERY); + if (IS_ERR(battery_page)) + return dev_err_probe(dev, PTR_ERR(battery_page), + "Failed to initialize battery page\n"); + + regmap_battery = devm_regmap_init_i2c(battery_page, &pm886_regmap_battery_config); + if (IS_ERR(regmap_battery)) + return dev_err_probe(dev, PTR_ERR(regmap_battery), + "Failed to initialize battery regmap\n"); + chip->regmap_battery = regmap_battery; + err = pm886_setup_irq(chip, &irq_data); if (err) return err; diff --git a/include/linux/mfd/88pm886.h b/include/linux/mfd/88pm886.h index 38892ba7b8a4..2c24dd3032ab 100644 --- a/include/linux/mfd/88pm886.h +++ b/include/linux/mfd/88pm886.h @@ -11,6 +11,7 @@ #define PM886_PAGE_OFFSET_REGULATORS 1 #define PM886_PAGE_OFFSET_GPADC 2 +#define PM886_PAGE_OFFSET_BATTERY 3 #define PM886_REG_ID 0x00 @@ -128,9 +129,13 @@ #define PM886_GPADC_BIAS_LEVELS 16 #define PM886_GPADC_INDEX_TO_BIAS_uA(i) (1 + (i) * 5) +/* Battery block register definitions */ +#define PM886_REG_CLS_CONFIG1 0x71 + struct pm886_chip { struct i2c_client *client; unsigned int chip_id; struct regmap *regmap; + struct regmap *regmap_battery; }; #endif /* __MFD_88PM886_H */ From b47afb5a7c02096fb4eb17a098850dbabd2fde07 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Fri, 19 Jun 2026 09:07:14 +0100 Subject: [PATCH 0317/1328] MAINTAINERS: Add a mailing list entry to MFD This is to be included by all contributors and will be leaned on for Sashiko's "reply to author" support. Signed-off-by: Lee Jones --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index bf6c7e83d8c8..c92dfca718f0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18453,6 +18453,7 @@ F: drivers/net/ethernet/mucse/ MULTIFUNCTION DEVICES (MFD) M: Lee Jones +L: mfd@lists.linux.dev S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd.git F: Documentation/devicetree/bindings/mfd/ From b8bc38bcecb77880a802d0430862b023c0aa7392 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Fri, 19 Jun 2026 22:27:10 +0200 Subject: [PATCH 0318/1328] mfd: db8500-prcmu: Fold dbx500 header into db8500 Move the DBx500 PRCMU definitions into the DB8500 PRCMU header and delete the wrapper header. Convert users of simple PRCMU wrappers to call the DB8500 helpers directly. The dbx500-prcmu.h header was the result of an earlier attempt to abstract several DBx5x SoC PRCMU units to use the same abstract header. They are deleted from the kernel and this is not just causing maintenance burden and build errors. The stub code is using -ENOSYS in a way checkpatch complains about so replace these with -EINVAL while we're at it. Assisted-by: Codex:gpt-5-5 Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202606180825.vUSQntkJ-lkp@intel.com/ Signed-off-by: Linus Walleij Acked-by: Brian Masney Acked-by: Guenter Roeck Acked-by: Mark Brown Link: https://lore.kernel.org/oe-kbuild-all/202606180825.vUSQntkJ-lkp@intel.com/ Link: https://patch.msgid.link/20260619-mfd-prcmu-merge-headers-v1-1-8ea0ee23b4d6@kernel.org Signed-off-by: Lee Jones --- arch/arm/mach-ux500/cpu-db8500.c | 6 +- drivers/clk/ux500/clk-prcmu.c | 20 +- drivers/clk/ux500/u8500_of_clk.c | 2 +- drivers/cpuidle/cpuidle-ux500.c | 6 +- drivers/mfd/ab8500-core.c | 2 +- drivers/mfd/db8500-prcmu.c | 6 +- drivers/regulator/db8500-prcmu.c | 12 +- drivers/thermal/db8500_thermal.c | 10 +- drivers/watchdog/db8500_wdt.c | 22 +- include/linux/mfd/db8500-prcmu.h | 252 +++++++++++++- include/linux/mfd/dbx500-prcmu.h | 575 ------------------------------- sound/soc/ux500/ux500_msp_dai.c | 2 +- 12 files changed, 294 insertions(+), 621 deletions(-) delete mode 100644 include/linux/mfd/dbx500-prcmu.h diff --git a/arch/arm/mach-ux500/cpu-db8500.c b/arch/arm/mach-ux500/cpu-db8500.c index b1a70f203372..0d7530fb6ad0 100644 --- a/arch/arm/mach-ux500/cpu-db8500.c +++ b/arch/arm/mach-ux500/cpu-db8500.c @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include #include #include @@ -81,7 +81,7 @@ static void __init ux500_init_irq(void) struct resource r; irqchip_init(); - prcmu_early_init(); + db8500_prcmu_early_init(); np = of_find_compatible_node(NULL, NULL, "stericsson,db8500-prcmu"); of_address_to_resource(np, 0, &r); of_node_put(np); @@ -101,7 +101,7 @@ static void ux500_restart(enum reboot_mode mode, const char *cmd) local_irq_disable(); local_fiq_disable(); - prcmu_system_reset(0); + db8500_prcmu_system_reset(0); } static const struct of_device_id u8500_local_bus_nodes[] = { diff --git a/drivers/clk/ux500/clk-prcmu.c b/drivers/clk/ux500/clk-prcmu.c index ddc86551bf57..ac96c46bd1bb 100644 --- a/drivers/clk/ux500/clk-prcmu.c +++ b/drivers/clk/ux500/clk-prcmu.c @@ -7,7 +7,7 @@ */ #include -#include +#include #include #include #include @@ -35,13 +35,13 @@ static int clk_prcmu_prepare(struct clk_hw *hw) { struct clk_prcmu *clk = to_clk_prcmu(hw); - return prcmu_request_clock(clk->cg_sel, true); + return db8500_prcmu_request_clock(clk->cg_sel, true); } static void clk_prcmu_unprepare(struct clk_hw *hw) { struct clk_prcmu *clk = to_clk_prcmu(hw); - if (prcmu_request_clock(clk->cg_sel, false)) + if (db8500_prcmu_request_clock(clk->cg_sel, false)) pr_err("clk_prcmu: %s failed to disable %s.\n", __func__, clk_hw_get_name(hw)); } @@ -86,7 +86,7 @@ static int clk_prcmu_opp_prepare(struct clk_hw *hw) clk->opp_requested = 1; } - err = prcmu_request_clock(clk->cg_sel, true); + err = db8500_prcmu_request_clock(clk->cg_sel, true); if (err) { prcmu_qos_remove_requirement(PRCMU_QOS_APE_OPP, (char *)clk_hw_get_name(hw)); @@ -101,7 +101,7 @@ static void clk_prcmu_opp_unprepare(struct clk_hw *hw) { struct clk_prcmu *clk = to_clk_prcmu(hw); - if (prcmu_request_clock(clk->cg_sel, false)) { + if (db8500_prcmu_request_clock(clk->cg_sel, false)) { pr_err("clk_prcmu: %s failed to disable %s.\n", __func__, clk_hw_get_name(hw)); return; @@ -120,7 +120,7 @@ static int clk_prcmu_opp_volt_prepare(struct clk_hw *hw) struct clk_prcmu *clk = to_clk_prcmu(hw); if (!clk->opp_requested) { - err = prcmu_request_ape_opp_100_voltage(true); + err = db8500_prcmu_request_ape_opp_100_voltage(true); if (err) { pr_err("clk_prcmu: %s fail req APE OPP VOLT for %s.\n", __func__, clk_hw_get_name(hw)); @@ -129,9 +129,9 @@ static int clk_prcmu_opp_volt_prepare(struct clk_hw *hw) clk->opp_requested = 1; } - err = prcmu_request_clock(clk->cg_sel, true); + err = db8500_prcmu_request_clock(clk->cg_sel, true); if (err) { - prcmu_request_ape_opp_100_voltage(false); + db8500_prcmu_request_ape_opp_100_voltage(false); clk->opp_requested = 0; return err; } @@ -143,14 +143,14 @@ static void clk_prcmu_opp_volt_unprepare(struct clk_hw *hw) { struct clk_prcmu *clk = to_clk_prcmu(hw); - if (prcmu_request_clock(clk->cg_sel, false)) { + if (db8500_prcmu_request_clock(clk->cg_sel, false)) { pr_err("clk_prcmu: %s failed to disable %s.\n", __func__, clk_hw_get_name(hw)); return; } if (clk->opp_requested) { - prcmu_request_ape_opp_100_voltage(false); + db8500_prcmu_request_ape_opp_100_voltage(false); clk->opp_requested = 0; } } diff --git a/drivers/clk/ux500/u8500_of_clk.c b/drivers/clk/ux500/u8500_of_clk.c index 6f78808387b1..d2499815226f 100644 --- a/drivers/clk/ux500/u8500_of_clk.c +++ b/drivers/clk/ux500/u8500_of_clk.c @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include "clk.h" #include "prcc.h" diff --git a/drivers/cpuidle/cpuidle-ux500.c b/drivers/cpuidle/cpuidle-ux500.c index f7d778580e9b..6d6c52c0bcc2 100644 --- a/drivers/cpuidle/cpuidle-ux500.c +++ b/drivers/cpuidle/cpuidle-ux500.c @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include #include @@ -66,7 +66,7 @@ static inline int ux500_enter_idle(struct cpuidle_device *dev, /* Go to the retention state, the prcmu will wait for the * cpu to go WFI and this is what happens after exiting this * 'master' critical section */ - if (prcmu_set_power_state(PRCMU_AP_IDLE, true, true)) + if (db8500_prcmu_set_power_state(PRCMU_AP_IDLE, true, true)) goto out; /* When we switch to retention, the prcmu is in charge @@ -109,7 +109,7 @@ static struct cpuidle_driver ux500_idle_driver = { static int dbx500_cpuidle_probe(struct platform_device *pdev) { /* Configure wake up reasons */ - prcmu_enable_wakeups(PRCMU_WAKEUP(ARM) | PRCMU_WAKEUP(RTC) | + db8500_prcmu_enable_wakeups(PRCMU_WAKEUP(ARM) | PRCMU_WAKEUP(RTC) | PRCMU_WAKEUP(ABB)); return cpuidle_register(&ux500_idle_driver, NULL); diff --git a/drivers/mfd/ab8500-core.c b/drivers/mfd/ab8500-core.c index f0bc0b5a6f4a..86fa99022cb3 100644 --- a/drivers/mfd/ab8500-core.c +++ b/drivers/mfd/ab8500-core.c @@ -19,7 +19,7 @@ #include #include #include -#include +#include #include /* diff --git a/drivers/mfd/db8500-prcmu.c b/drivers/mfd/db8500-prcmu.c index 21e68a382b11..6672c55f2ebc 100644 --- a/drivers/mfd/db8500-prcmu.c +++ b/drivers/mfd/db8500-prcmu.c @@ -32,7 +32,7 @@ #include #include #include -#include +#include #include #include #include @@ -2285,7 +2285,7 @@ void db8500_prcmu_system_reset(u16 reset_code) /** * db8500_prcmu_get_reset_code - Retrieve SW reset reason code * - * Retrieves the reset reason code stored by prcmu_system_reset() before + * Retrieves the reset reason code stored by db8500_prcmu_system_reset() before * last restart. */ u16 db8500_prcmu_get_reset_code(void) @@ -3041,7 +3041,7 @@ static int db8500_prcmu_probe(struct platform_device *pdev) db8500_irq_init(np); - prcmu_config_esram0_deep_sleep(ESRAM0_DEEP_SLEEP_STATE_RET); + db8500_prcmu_config_esram0_deep_sleep(ESRAM0_DEEP_SLEEP_STATE_RET); err = mfd_add_devices(&pdev->dev, 0, common_prcmu_devs, ARRAY_SIZE(common_prcmu_devs), NULL, 0, db8500_irq_domain); diff --git a/drivers/regulator/db8500-prcmu.c b/drivers/regulator/db8500-prcmu.c index 1ec2e1348891..751fe36580fa 100644 --- a/drivers/regulator/db8500-prcmu.c +++ b/drivers/regulator/db8500-prcmu.c @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include #include @@ -93,13 +93,13 @@ static int enable_epod(u16 epod_id, bool ramret) if (ramret) { if (!epod_on[epod_id]) { - ret = prcmu_set_epod(epod_id, EPOD_STATE_RAMRET); + ret = db8500_prcmu_set_epod(epod_id, EPOD_STATE_RAMRET); if (ret < 0) return ret; } epod_ramret[epod_id] = true; } else { - ret = prcmu_set_epod(epod_id, EPOD_STATE_ON); + ret = db8500_prcmu_set_epod(epod_id, EPOD_STATE_ON); if (ret < 0) return ret; epod_on[epod_id] = true; @@ -114,18 +114,18 @@ static int disable_epod(u16 epod_id, bool ramret) if (ramret) { if (!epod_on[epod_id]) { - ret = prcmu_set_epod(epod_id, EPOD_STATE_OFF); + ret = db8500_prcmu_set_epod(epod_id, EPOD_STATE_OFF); if (ret < 0) return ret; } epod_ramret[epod_id] = false; } else { if (epod_ramret[epod_id]) { - ret = prcmu_set_epod(epod_id, EPOD_STATE_RAMRET); + ret = db8500_prcmu_set_epod(epod_id, EPOD_STATE_RAMRET); if (ret < 0) return ret; } else { - ret = prcmu_set_epod(epod_id, EPOD_STATE_OFF); + ret = db8500_prcmu_set_epod(epod_id, EPOD_STATE_OFF); if (ret < 0) return ret; } diff --git a/drivers/thermal/db8500_thermal.c b/drivers/thermal/db8500_thermal.c index 576f88b6a1b3..cf1706569e6d 100644 --- a/drivers/thermal/db8500_thermal.c +++ b/drivers/thermal/db8500_thermal.c @@ -10,7 +10,7 @@ #include #include -#include +#include #include #include #include @@ -82,7 +82,7 @@ static void db8500_thermal_update_config(struct db8500_thermal_zone *th, unsigned long next_low, unsigned long next_high) { - prcmu_stop_temp_sense(); + db8500_prcmu_stop_temp_sense(); th->cur_index = idx; th->interpolated_temp = (next_low + next_high)/2; @@ -91,8 +91,8 @@ static void db8500_thermal_update_config(struct db8500_thermal_zone *th, * The PRCMU accept absolute temperatures in celsius so divide * down the millicelsius with 1000 */ - prcmu_config_hotmon((u8)(next_low/1000), (u8)(next_high/1000)); - prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME); + db8500_prcmu_config_hotmon((u8)(next_low / 1000), (u8)(next_high / 1000)); + db8500_prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME); } static irqreturn_t prcmu_low_irq_handler(int irq, void *irq_data) @@ -204,7 +204,7 @@ static int db8500_thermal_probe(struct platform_device *pdev) static int db8500_thermal_suspend(struct platform_device *pdev, pm_message_t state) { - prcmu_stop_temp_sense(); + db8500_prcmu_stop_temp_sense(); return 0; } diff --git a/drivers/watchdog/db8500_wdt.c b/drivers/watchdog/db8500_wdt.c index 97148ac0aa54..70ccea13288d 100644 --- a/drivers/watchdog/db8500_wdt.c +++ b/drivers/watchdog/db8500_wdt.c @@ -16,7 +16,7 @@ #include #include -#include +#include #define WATCHDOG_TIMEOUT 600 /* 10 minutes */ @@ -37,24 +37,24 @@ MODULE_PARM_DESC(nowayout, static int db8500_wdt_start(struct watchdog_device *wdd) { - return prcmu_enable_a9wdog(PRCMU_WDOG_ALL); + return db8500_prcmu_enable_a9wdog(PRCMU_WDOG_ALL); } static int db8500_wdt_stop(struct watchdog_device *wdd) { - return prcmu_disable_a9wdog(PRCMU_WDOG_ALL); + return db8500_prcmu_disable_a9wdog(PRCMU_WDOG_ALL); } static int db8500_wdt_keepalive(struct watchdog_device *wdd) { - return prcmu_kick_a9wdog(PRCMU_WDOG_ALL); + return db8500_prcmu_kick_a9wdog(PRCMU_WDOG_ALL); } static int db8500_wdt_set_timeout(struct watchdog_device *wdd, unsigned int timeout) { db8500_wdt_stop(wdd); - prcmu_load_a9wdog(PRCMU_WDOG_ALL, timeout * 1000); + db8500_prcmu_load_a9wdog(PRCMU_WDOG_ALL, timeout * 1000); db8500_wdt_start(wdd); return 0; @@ -91,10 +91,10 @@ static int db8500_wdt_probe(struct platform_device *pdev) watchdog_set_nowayout(&db8500_wdt, nowayout); /* disable auto off on sleep */ - prcmu_config_a9wdog(PRCMU_WDOG_CPU1, false); + db8500_prcmu_config_a9wdog(PRCMU_WDOG_CPU1, false); /* set HW initial value */ - prcmu_load_a9wdog(PRCMU_WDOG_ALL, timeout * 1000); + db8500_prcmu_load_a9wdog(PRCMU_WDOG_ALL, timeout * 1000); ret = devm_watchdog_register_device(dev, &db8500_wdt); if (ret) @@ -110,9 +110,9 @@ static int db8500_wdt_suspend(struct platform_device *pdev, { if (watchdog_active(&db8500_wdt)) { db8500_wdt_stop(&db8500_wdt); - prcmu_config_a9wdog(PRCMU_WDOG_CPU1, true); + db8500_prcmu_config_a9wdog(PRCMU_WDOG_CPU1, true); - prcmu_load_a9wdog(PRCMU_WDOG_ALL, timeout * 1000); + db8500_prcmu_load_a9wdog(PRCMU_WDOG_ALL, timeout * 1000); db8500_wdt_start(&db8500_wdt); } return 0; @@ -122,9 +122,9 @@ static int db8500_wdt_resume(struct platform_device *pdev) { if (watchdog_active(&db8500_wdt)) { db8500_wdt_stop(&db8500_wdt); - prcmu_config_a9wdog(PRCMU_WDOG_CPU1, false); + db8500_prcmu_config_a9wdog(PRCMU_WDOG_CPU1, false); - prcmu_load_a9wdog(PRCMU_WDOG_ALL, timeout * 1000); + db8500_prcmu_load_a9wdog(PRCMU_WDOG_ALL, timeout * 1000); db8500_wdt_start(&db8500_wdt); } return 0; diff --git a/include/linux/mfd/db8500-prcmu.h b/include/linux/mfd/db8500-prcmu.h index a62de3d155ed..c939c9a1170a 100644 --- a/include/linux/mfd/db8500-prcmu.h +++ b/include/linux/mfd/db8500-prcmu.h @@ -12,6 +12,9 @@ #include #include +#include + +#include /* For clock identifiers */ /* * Registers @@ -24,6 +27,38 @@ #define DB8500_PRCM_DSI_SW_RESET_DSI1_SW_RESETN BIT(1) #define DB8500_PRCM_DSI_SW_RESET_DSI2_SW_RESETN BIT(2) +/* Offset for the firmware version within the TCPM */ +#define DB8500_PRCMU_FW_VERSION_OFFSET 0xA4 + +#define DB8500_PRCMU_LEGACY_OFFSET 0xDD4 + +/* + * CLKOUT sources + */ +#define PRCMU_CLKSRC_CLK38M 0x00 +#define PRCMU_CLKSRC_ACLK 0x01 +#define PRCMU_CLKSRC_SYSCLK 0x02 +#define PRCMU_CLKSRC_LCDCLK 0x03 +#define PRCMU_CLKSRC_SDMMCCLK 0x04 +#define PRCMU_CLKSRC_TVCLK 0x05 +#define PRCMU_CLKSRC_TIMCLK 0x06 +#define PRCMU_CLKSRC_CLK009 0x07 +/* These are only valid for CLKOUT1: */ +#define PRCMU_CLKSRC_SIAMMDSPCLK 0x40 +#define PRCMU_CLKSRC_I2CCLK 0x41 +#define PRCMU_CLKSRC_MSP02CLK 0x42 +#define PRCMU_CLKSRC_ARMPLL_OBSCLK 0x43 +#define PRCMU_CLKSRC_HSIRXCLK 0x44 +#define PRCMU_CLKSRC_HSITXCLK 0x45 +#define PRCMU_CLKSRC_ARMCLKFIX 0x46 +#define PRCMU_CLKSRC_HDMICLK 0x47 + +/* + * Definitions for controlling ESRAM0 in deep sleep. + */ +#define ESRAM0_DEEP_SLEEP_STATE_OFF 1 +#define ESRAM0_DEEP_SLEEP_STATE_RET 2 + /* This portion previously known as */ /** @@ -451,10 +486,173 @@ enum prcmu_power_status { PRCMU_ARMPENDINGIT_ER = 0x93, }; +/* PRCMU Wakeup defines */ +enum prcmu_wakeup_index { + PRCMU_WAKEUP_INDEX_RTC, + PRCMU_WAKEUP_INDEX_RTT0, + PRCMU_WAKEUP_INDEX_RTT1, + PRCMU_WAKEUP_INDEX_HSI0, + PRCMU_WAKEUP_INDEX_HSI1, + PRCMU_WAKEUP_INDEX_USB, + PRCMU_WAKEUP_INDEX_ABB, + PRCMU_WAKEUP_INDEX_ABB_FIFO, + PRCMU_WAKEUP_INDEX_ARM, + PRCMU_WAKEUP_INDEX_CD_IRQ, + NUM_PRCMU_WAKEUP_INDICES +}; + +#define PRCMU_WAKEUP(_name) (BIT(PRCMU_WAKEUP_INDEX_##_name)) + +/** + * enum prcmu_wdog_id - PRCMU watchdog IDs + * @PRCMU_WDOG_ALL: use all timers + * @PRCMU_WDOG_CPU1: use first CPU timer only + * @PRCMU_WDOG_CPU2: use second CPU timer conly + */ +enum prcmu_wdog_id { + PRCMU_WDOG_ALL = 0x00, + PRCMU_WDOG_CPU1 = 0x01, + PRCMU_WDOG_CPU2 = 0x02, +}; + +/** + * enum ape_opp - APE OPP states definition + * @APE_OPP_INIT: + * @APE_NO_CHANGE: The APE operating point is unchanged + * @APE_100_OPP: The new APE operating point is ape100opp + * @APE_50_OPP: 50% + * @APE_50_PARTLY_25_OPP: 50%, except some clocks at 25%. + */ +enum ape_opp { + APE_OPP_INIT = 0x00, + APE_NO_CHANGE = 0x01, + APE_100_OPP = 0x02, + APE_50_OPP = 0x03, + APE_50_PARTLY_25_OPP = 0xFF, +}; + +/** + * enum arm_opp - ARM OPP states definition + * @ARM_OPP_INIT: + * @ARM_NO_CHANGE: The ARM operating point is unchanged + * @ARM_100_OPP: The new ARM operating point is arm100opp + * @ARM_50_OPP: The new ARM operating point is arm50opp + * @ARM_MAX_OPP: Operating point is "max" (more than 100) + * @ARM_MAX_FREQ100OPP: Set max opp if available, else 100 + * @ARM_EXTCLK: The new ARM operating point is armExtClk + */ +enum arm_opp { + ARM_OPP_INIT = 0x00, + ARM_NO_CHANGE = 0x01, + ARM_100_OPP = 0x02, + ARM_50_OPP = 0x03, + ARM_MAX_OPP = 0x04, + ARM_MAX_FREQ100OPP = 0x05, + ARM_EXTCLK = 0x07 +}; + +/** + * enum ddr_opp - DDR OPP states definition + * @DDR_100_OPP: The new DDR operating point is ddr100opp + * @DDR_50_OPP: The new DDR operating point is ddr50opp + * @DDR_25_OPP: The new DDR operating point is ddr25opp + */ +enum ddr_opp { + DDR_100_OPP = 0x00, + DDR_50_OPP = 0x01, + DDR_25_OPP = 0x02, +}; + +/** + * enum ddr_pwrst - DDR power states definition + * @DDR_PWR_STATE_UNCHANGED: SDRAM and DDR controller state is unchanged + * @DDR_PWR_STATE_ON: + * @DDR_PWR_STATE_OFFLOWLAT: + * @DDR_PWR_STATE_OFFHIGHLAT: + */ +enum ddr_pwrst { + DDR_PWR_STATE_UNCHANGED = 0x00, + DDR_PWR_STATE_ON = 0x01, + DDR_PWR_STATE_OFFLOWLAT = 0x02, + DDR_PWR_STATE_OFFHIGHLAT = 0x03 +}; + /* * Definitions for autonomous power management configuration. */ +/* EPOD (power domain) IDs */ + +/* + * DB8500 EPODs + * - EPOD_ID_SVAMMDSP: power domain for SVA MMDSP + * - EPOD_ID_SVAPIPE: power domain for SVA pipe + * - EPOD_ID_SIAMMDSP: power domain for SIA MMDSP + * - EPOD_ID_SIAPIPE: power domain for SIA pipe + * - EPOD_ID_SGA: power domain for SGA + * - EPOD_ID_B2R2_MCDE: power domain for B2R2 and MCDE + * - EPOD_ID_ESRAM12: power domain for ESRAM 1 and 2 + * - EPOD_ID_ESRAM34: power domain for ESRAM 3 and 4 + * - NUM_EPOD_ID: number of power domains + * + * TODO: These should be prefixed. + */ +#define EPOD_ID_SVAMMDSP 0 +#define EPOD_ID_SVAPIPE 1 +#define EPOD_ID_SIAMMDSP 2 +#define EPOD_ID_SIAPIPE 3 +#define EPOD_ID_SGA 4 +#define EPOD_ID_B2R2_MCDE 5 +#define EPOD_ID_ESRAM12 6 +#define EPOD_ID_ESRAM34 7 +#define NUM_EPOD_ID 8 + +/* + * state definition for EPOD (power domain) + * - EPOD_STATE_NO_CHANGE: The EPOD should remain unchanged + * - EPOD_STATE_OFF: The EPOD is switched off + * - EPOD_STATE_RAMRET: The EPOD is switched off with its internal RAM in + * retention + * - EPOD_STATE_ON_CLK_OFF: The EPOD is switched on, clock is still off + * - EPOD_STATE_ON: Same as above, but with clock enabled + */ +#define EPOD_STATE_NO_CHANGE 0x00 +#define EPOD_STATE_OFF 0x01 +#define EPOD_STATE_RAMRET 0x02 +#define EPOD_STATE_ON_CLK_OFF 0x03 +#define EPOD_STATE_ON 0x04 + +#define PRCMU_FW_PROJECT_U8500 2 +#define PRCMU_FW_PROJECT_U8400 3 +#define PRCMU_FW_PROJECT_U9500 4 /* Customer specific */ +#define PRCMU_FW_PROJECT_U8500_MBB 5 +#define PRCMU_FW_PROJECT_U8500_C1 6 +#define PRCMU_FW_PROJECT_U8500_C2 7 +#define PRCMU_FW_PROJECT_U8500_C3 8 +#define PRCMU_FW_PROJECT_U8500_C4 9 +#define PRCMU_FW_PROJECT_U9500_MBL 10 +#define PRCMU_FW_PROJECT_U8500_SSG1 11 /* Samsung specific */ +#define PRCMU_FW_PROJECT_U8500_MBL2 12 /* Customer specific */ +#define PRCMU_FW_PROJECT_U8520 13 +#define PRCMU_FW_PROJECT_U8420 14 +#define PRCMU_FW_PROJECT_U8500_SSG2 15 /* Samsung specific */ +#define PRCMU_FW_PROJECT_U8420_SYSCLK 17 +#define PRCMU_FW_PROJECT_A9420 20 +/* [32..63] 9540 and derivatives */ +#define PRCMU_FW_PROJECT_U9540 32 +/* [64..95] 8540 and derivatives */ +#define PRCMU_FW_PROJECT_L8540 64 +/* [96..126] 8580 and derivatives */ +#define PRCMU_FW_PROJECT_L8580 96 + +#define PRCMU_FW_PROJECT_NAME_LEN 20 + +/* PRCMU QoS APE OPP class */ +#define PRCMU_QOS_APE_OPP 1 +#define PRCMU_QOS_DDR_OPP 2 +#define PRCMU_QOS_ARM_OPP 3 +#define PRCMU_QOS_DEFAULT_VALUE -1 + #define PRCMU_AUTO_PM_OFF 0 #define PRCMU_AUTO_PM_ON 1 @@ -469,6 +667,14 @@ enum prcmu_auto_pm_policy { PRCMU_AUTO_PM_POLICY_DSP_CLK_OFF_HWP_CLK_OFF, }; +struct prcmu_fw_version { + u32 project; /* Notice, project shifted with 8 on ux540 */ + u8 api_version; + u8 func_version; + u8 errata; + char project_name[PRCMU_FW_PROJECT_NAME_LEN]; +}; + /** * struct prcmu_auto_pm_config - Autonomous power management configuration. * @sia_auto_pm_enable: SIA autonomous pm enable. (PRCMU_AUTO_PM_{OFF,ON}) @@ -501,6 +707,9 @@ void prcmu_configure_auto_pm(struct prcmu_auto_pm_config *sleep, bool prcmu_is_auto_pm_enabled(void); int prcmu_config_clkout(u8 clkout, u8 source, u8 div); +unsigned long prcmu_clock_rate(u8 clock); +long prcmu_round_clock_rate(u8 clock, unsigned long rate); +int prcmu_set_clock_rate(u8 clock, unsigned long rate); int prcmu_set_clock_divider(u8 clock, u8 divider); int db8500_prcmu_config_hotdog(u8 threshold); int db8500_prcmu_config_hotmon(u8 low, u8 high); @@ -508,6 +717,8 @@ int db8500_prcmu_start_temp_sense(u16 cycles32k); int db8500_prcmu_stop_temp_sense(void); int prcmu_abb_read(u8 slave, u8 reg, u8 *value, u8 size); int prcmu_abb_write(u8 slave, u8 reg, u8 *value, u8 size); +int prcmu_abb_write_masked(u8 slave, u8 reg, u8 *value, + u8 *mask, u8 size); int prcmu_ac_wake_req(void); void prcmu_ac_sleep_req(void); @@ -610,6 +821,21 @@ static inline int prcmu_config_clkout(u8 clkout, u8 source, u8 div) return 0; } +static inline unsigned long prcmu_clock_rate(u8 clock) +{ + return 0; +} + +static inline long prcmu_round_clock_rate(u8 clock, unsigned long rate) +{ + return 0; +} + +static inline int prcmu_set_clock_rate(u8 clock, unsigned long rate) +{ + return 0; +} + static inline int prcmu_set_clock_divider(u8 clock, u8 divider) { return 0; @@ -637,12 +863,18 @@ static inline int db8500_prcmu_stop_temp_sense(void) static inline int prcmu_abb_read(u8 slave, u8 reg, u8 *value, u8 size) { - return -ENOSYS; + return -EINVAL; } static inline int prcmu_abb_write(u8 slave, u8 reg, u8 *value, u8 size) { - return -ENOSYS; + return -EINVAL; +} + +static inline int prcmu_abb_write_masked(u8 slave, u8 reg, + u8 *value, u8 *mask, u8 size) +{ + return -EINVAL; } static inline int prcmu_ac_wake_req(void) @@ -745,4 +977,20 @@ static inline void db8500_prcmu_write_masked(unsigned int reg, u32 mask, #endif /* !CONFIG_MFD_DB8500_PRCMU */ +static inline int prcmu_qos_add_requirement(int prcmu_qos_class, + char *name, s32 value) +{ + return 0; +} + +static inline int prcmu_qos_update_requirement(int prcmu_qos_class, + char *name, s32 new_value) +{ + return 0; +} + +static inline void prcmu_qos_remove_requirement(int prcmu_qos_class, char *name) +{ +} + #endif /* __MFD_DB8500_PRCMU_H */ diff --git a/include/linux/mfd/dbx500-prcmu.h b/include/linux/mfd/dbx500-prcmu.h deleted file mode 100644 index 828362b7860c..000000000000 --- a/include/linux/mfd/dbx500-prcmu.h +++ /dev/null @@ -1,575 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Copyright (C) ST Ericsson SA 2011 - * - * STE Ux500 PRCMU API - */ -#ifndef __MACH_PRCMU_H -#define __MACH_PRCMU_H - -#include -#include -#include - -#include /* For clock identifiers */ - -/* Offset for the firmware version within the TCPM */ -#define DB8500_PRCMU_FW_VERSION_OFFSET 0xA4 -#define DBX540_PRCMU_FW_VERSION_OFFSET 0xA8 - -/* PRCMU Wakeup defines */ -enum prcmu_wakeup_index { - PRCMU_WAKEUP_INDEX_RTC, - PRCMU_WAKEUP_INDEX_RTT0, - PRCMU_WAKEUP_INDEX_RTT1, - PRCMU_WAKEUP_INDEX_HSI0, - PRCMU_WAKEUP_INDEX_HSI1, - PRCMU_WAKEUP_INDEX_USB, - PRCMU_WAKEUP_INDEX_ABB, - PRCMU_WAKEUP_INDEX_ABB_FIFO, - PRCMU_WAKEUP_INDEX_ARM, - PRCMU_WAKEUP_INDEX_CD_IRQ, - NUM_PRCMU_WAKEUP_INDICES -}; -#define PRCMU_WAKEUP(_name) (BIT(PRCMU_WAKEUP_INDEX_##_name)) - -/* EPOD (power domain) IDs */ - -/* - * DB8500 EPODs - * - EPOD_ID_SVAMMDSP: power domain for SVA MMDSP - * - EPOD_ID_SVAPIPE: power domain for SVA pipe - * - EPOD_ID_SIAMMDSP: power domain for SIA MMDSP - * - EPOD_ID_SIAPIPE: power domain for SIA pipe - * - EPOD_ID_SGA: power domain for SGA - * - EPOD_ID_B2R2_MCDE: power domain for B2R2 and MCDE - * - EPOD_ID_ESRAM12: power domain for ESRAM 1 and 2 - * - EPOD_ID_ESRAM34: power domain for ESRAM 3 and 4 - * - NUM_EPOD_ID: number of power domains - * - * TODO: These should be prefixed. - */ -#define EPOD_ID_SVAMMDSP 0 -#define EPOD_ID_SVAPIPE 1 -#define EPOD_ID_SIAMMDSP 2 -#define EPOD_ID_SIAPIPE 3 -#define EPOD_ID_SGA 4 -#define EPOD_ID_B2R2_MCDE 5 -#define EPOD_ID_ESRAM12 6 -#define EPOD_ID_ESRAM34 7 -#define NUM_EPOD_ID 8 - -/* - * state definition for EPOD (power domain) - * - EPOD_STATE_NO_CHANGE: The EPOD should remain unchanged - * - EPOD_STATE_OFF: The EPOD is switched off - * - EPOD_STATE_RAMRET: The EPOD is switched off with its internal RAM in - * retention - * - EPOD_STATE_ON_CLK_OFF: The EPOD is switched on, clock is still off - * - EPOD_STATE_ON: Same as above, but with clock enabled - */ -#define EPOD_STATE_NO_CHANGE 0x00 -#define EPOD_STATE_OFF 0x01 -#define EPOD_STATE_RAMRET 0x02 -#define EPOD_STATE_ON_CLK_OFF 0x03 -#define EPOD_STATE_ON 0x04 - -/* - * CLKOUT sources - */ -#define PRCMU_CLKSRC_CLK38M 0x00 -#define PRCMU_CLKSRC_ACLK 0x01 -#define PRCMU_CLKSRC_SYSCLK 0x02 -#define PRCMU_CLKSRC_LCDCLK 0x03 -#define PRCMU_CLKSRC_SDMMCCLK 0x04 -#define PRCMU_CLKSRC_TVCLK 0x05 -#define PRCMU_CLKSRC_TIMCLK 0x06 -#define PRCMU_CLKSRC_CLK009 0x07 -/* These are only valid for CLKOUT1: */ -#define PRCMU_CLKSRC_SIAMMDSPCLK 0x40 -#define PRCMU_CLKSRC_I2CCLK 0x41 -#define PRCMU_CLKSRC_MSP02CLK 0x42 -#define PRCMU_CLKSRC_ARMPLL_OBSCLK 0x43 -#define PRCMU_CLKSRC_HSIRXCLK 0x44 -#define PRCMU_CLKSRC_HSITXCLK 0x45 -#define PRCMU_CLKSRC_ARMCLKFIX 0x46 -#define PRCMU_CLKSRC_HDMICLK 0x47 - -/** - * enum prcmu_wdog_id - PRCMU watchdog IDs - * @PRCMU_WDOG_ALL: use all timers - * @PRCMU_WDOG_CPU1: use first CPU timer only - * @PRCMU_WDOG_CPU2: use second CPU timer conly - */ -enum prcmu_wdog_id { - PRCMU_WDOG_ALL = 0x00, - PRCMU_WDOG_CPU1 = 0x01, - PRCMU_WDOG_CPU2 = 0x02, -}; - -/** - * enum ape_opp - APE OPP states definition - * @APE_OPP_INIT: - * @APE_NO_CHANGE: The APE operating point is unchanged - * @APE_100_OPP: The new APE operating point is ape100opp - * @APE_50_OPP: 50% - * @APE_50_PARTLY_25_OPP: 50%, except some clocks at 25%. - */ -enum ape_opp { - APE_OPP_INIT = 0x00, - APE_NO_CHANGE = 0x01, - APE_100_OPP = 0x02, - APE_50_OPP = 0x03, - APE_50_PARTLY_25_OPP = 0xFF, -}; - -/** - * enum arm_opp - ARM OPP states definition - * @ARM_OPP_INIT: - * @ARM_NO_CHANGE: The ARM operating point is unchanged - * @ARM_100_OPP: The new ARM operating point is arm100opp - * @ARM_50_OPP: The new ARM operating point is arm50opp - * @ARM_MAX_OPP: Operating point is "max" (more than 100) - * @ARM_MAX_FREQ100OPP: Set max opp if available, else 100 - * @ARM_EXTCLK: The new ARM operating point is armExtClk - */ -enum arm_opp { - ARM_OPP_INIT = 0x00, - ARM_NO_CHANGE = 0x01, - ARM_100_OPP = 0x02, - ARM_50_OPP = 0x03, - ARM_MAX_OPP = 0x04, - ARM_MAX_FREQ100OPP = 0x05, - ARM_EXTCLK = 0x07 -}; - -/** - * enum ddr_opp - DDR OPP states definition - * @DDR_100_OPP: The new DDR operating point is ddr100opp - * @DDR_50_OPP: The new DDR operating point is ddr50opp - * @DDR_25_OPP: The new DDR operating point is ddr25opp - */ -enum ddr_opp { - DDR_100_OPP = 0x00, - DDR_50_OPP = 0x01, - DDR_25_OPP = 0x02, -}; - -/* - * Definitions for controlling ESRAM0 in deep sleep. - */ -#define ESRAM0_DEEP_SLEEP_STATE_OFF 1 -#define ESRAM0_DEEP_SLEEP_STATE_RET 2 - -/** - * enum ddr_pwrst - DDR power states definition - * @DDR_PWR_STATE_UNCHANGED: SDRAM and DDR controller state is unchanged - * @DDR_PWR_STATE_ON: - * @DDR_PWR_STATE_OFFLOWLAT: - * @DDR_PWR_STATE_OFFHIGHLAT: - */ -enum ddr_pwrst { - DDR_PWR_STATE_UNCHANGED = 0x00, - DDR_PWR_STATE_ON = 0x01, - DDR_PWR_STATE_OFFLOWLAT = 0x02, - DDR_PWR_STATE_OFFHIGHLAT = 0x03 -}; - -#define DB8500_PRCMU_LEGACY_OFFSET 0xDD4 - -#define PRCMU_FW_PROJECT_U8500 2 -#define PRCMU_FW_PROJECT_U8400 3 -#define PRCMU_FW_PROJECT_U9500 4 /* Customer specific */ -#define PRCMU_FW_PROJECT_U8500_MBB 5 -#define PRCMU_FW_PROJECT_U8500_C1 6 -#define PRCMU_FW_PROJECT_U8500_C2 7 -#define PRCMU_FW_PROJECT_U8500_C3 8 -#define PRCMU_FW_PROJECT_U8500_C4 9 -#define PRCMU_FW_PROJECT_U9500_MBL 10 -#define PRCMU_FW_PROJECT_U8500_SSG1 11 /* Samsung specific */ -#define PRCMU_FW_PROJECT_U8500_MBL2 12 /* Customer specific */ -#define PRCMU_FW_PROJECT_U8520 13 -#define PRCMU_FW_PROJECT_U8420 14 -#define PRCMU_FW_PROJECT_U8500_SSG2 15 /* Samsung specific */ -#define PRCMU_FW_PROJECT_U8420_SYSCLK 17 -#define PRCMU_FW_PROJECT_A9420 20 -/* [32..63] 9540 and derivatives */ -#define PRCMU_FW_PROJECT_U9540 32 -/* [64..95] 8540 and derivatives */ -#define PRCMU_FW_PROJECT_L8540 64 -/* [96..126] 8580 and derivatives */ -#define PRCMU_FW_PROJECT_L8580 96 - -#define PRCMU_FW_PROJECT_NAME_LEN 20 -struct prcmu_fw_version { - u32 project; /* Notice, project shifted with 8 on ux540 */ - u8 api_version; - u8 func_version; - u8 errata; - char project_name[PRCMU_FW_PROJECT_NAME_LEN]; -}; - -#include - -#if defined(CONFIG_UX500_SOC_DB8500) - -static inline void __init prcmu_early_init(void) -{ - db8500_prcmu_early_init(); -} - -static inline int prcmu_set_power_state(u8 state, bool keep_ulp_clk, - bool keep_ap_pll) -{ - return db8500_prcmu_set_power_state(state, keep_ulp_clk, - keep_ap_pll); -} - -static inline u8 prcmu_get_power_state_result(void) -{ - return db8500_prcmu_get_power_state_result(); -} - -static inline int prcmu_set_epod(u16 epod_id, u8 epod_state) -{ - return db8500_prcmu_set_epod(epod_id, epod_state); -} - -static inline void prcmu_enable_wakeups(u32 wakeups) -{ - db8500_prcmu_enable_wakeups(wakeups); -} - -static inline void prcmu_disable_wakeups(void) -{ - prcmu_enable_wakeups(0); -} - -static inline void prcmu_config_abb_event_readout(u32 abb_events) -{ - db8500_prcmu_config_abb_event_readout(abb_events); -} - -static inline void prcmu_get_abb_event_buffer(void __iomem **buf) -{ - db8500_prcmu_get_abb_event_buffer(buf); -} - -int prcmu_abb_read(u8 slave, u8 reg, u8 *value, u8 size); -int prcmu_abb_write(u8 slave, u8 reg, u8 *value, u8 size); -int prcmu_abb_write_masked(u8 slave, u8 reg, u8 *value, u8 *mask, u8 size); - -int prcmu_config_clkout(u8 clkout, u8 source, u8 div); - -static inline int prcmu_request_clock(u8 clock, bool enable) -{ - return db8500_prcmu_request_clock(clock, enable); -} - -unsigned long prcmu_clock_rate(u8 clock); -long prcmu_round_clock_rate(u8 clock, unsigned long rate); -int prcmu_set_clock_rate(u8 clock, unsigned long rate); - -static inline int prcmu_get_ddr_opp(void) -{ - return db8500_prcmu_get_ddr_opp(); -} - -static inline int prcmu_set_arm_opp(u8 opp) -{ - return db8500_prcmu_set_arm_opp(opp); -} - -static inline int prcmu_get_arm_opp(void) -{ - return db8500_prcmu_get_arm_opp(); -} - -static inline int prcmu_set_ape_opp(u8 opp) -{ - return db8500_prcmu_set_ape_opp(opp); -} - -static inline int prcmu_get_ape_opp(void) -{ - return db8500_prcmu_get_ape_opp(); -} - -static inline int prcmu_request_ape_opp_100_voltage(bool enable) -{ - return db8500_prcmu_request_ape_opp_100_voltage(enable); -} - -static inline void prcmu_system_reset(u16 reset_code) -{ - db8500_prcmu_system_reset(reset_code); -} - -static inline u16 prcmu_get_reset_code(void) -{ - return db8500_prcmu_get_reset_code(); -} - -int prcmu_ac_wake_req(void); -void prcmu_ac_sleep_req(void); -static inline void prcmu_modem_reset(void) -{ - db8500_prcmu_modem_reset(); -} - -static inline bool prcmu_is_ac_wake_requested(void) -{ - return db8500_prcmu_is_ac_wake_requested(); -} - -static inline int prcmu_config_esram0_deep_sleep(u8 state) -{ - return db8500_prcmu_config_esram0_deep_sleep(state); -} - -static inline int prcmu_config_hotdog(u8 threshold) -{ - return db8500_prcmu_config_hotdog(threshold); -} - -static inline int prcmu_config_hotmon(u8 low, u8 high) -{ - return db8500_prcmu_config_hotmon(low, high); -} - -static inline int prcmu_start_temp_sense(u16 cycles32k) -{ - return db8500_prcmu_start_temp_sense(cycles32k); -} - -static inline int prcmu_stop_temp_sense(void) -{ - return db8500_prcmu_stop_temp_sense(); -} - -static inline u32 prcmu_read(unsigned int reg) -{ - return db8500_prcmu_read(reg); -} - -static inline void prcmu_write(unsigned int reg, u32 value) -{ - db8500_prcmu_write(reg, value); -} - -static inline void prcmu_write_masked(unsigned int reg, u32 mask, u32 value) -{ - db8500_prcmu_write_masked(reg, mask, value); -} - -static inline int prcmu_enable_a9wdog(u8 id) -{ - return db8500_prcmu_enable_a9wdog(id); -} - -static inline int prcmu_disable_a9wdog(u8 id) -{ - return db8500_prcmu_disable_a9wdog(id); -} - -static inline int prcmu_kick_a9wdog(u8 id) -{ - return db8500_prcmu_kick_a9wdog(id); -} - -static inline int prcmu_load_a9wdog(u8 id, u32 timeout) -{ - return db8500_prcmu_load_a9wdog(id, timeout); -} - -static inline int prcmu_config_a9wdog(u8 num, bool sleep_auto_off) -{ - return db8500_prcmu_config_a9wdog(num, sleep_auto_off); -} -#else - -static inline void prcmu_early_init(void) {} - -static inline int prcmu_set_power_state(u8 state, bool keep_ulp_clk, - bool keep_ap_pll) -{ - return 0; -} - -static inline int prcmu_set_epod(u16 epod_id, u8 epod_state) -{ - return 0; -} - -static inline void prcmu_enable_wakeups(u32 wakeups) {} - -static inline void prcmu_disable_wakeups(void) {} - -static inline int prcmu_abb_read(u8 slave, u8 reg, u8 *value, u8 size) -{ - return -ENOSYS; -} - -static inline int prcmu_abb_write(u8 slave, u8 reg, u8 *value, u8 size) -{ - return -ENOSYS; -} - -static inline int prcmu_abb_write_masked(u8 slave, u8 reg, u8 *value, u8 *mask, - u8 size) -{ - return -ENOSYS; -} - -static inline int prcmu_config_clkout(u8 clkout, u8 source, u8 div) -{ - return 0; -} - -static inline int prcmu_request_clock(u8 clock, bool enable) -{ - return 0; -} - -static inline long prcmu_round_clock_rate(u8 clock, unsigned long rate) -{ - return 0; -} - -static inline int prcmu_set_clock_rate(u8 clock, unsigned long rate) -{ - return 0; -} - -static inline unsigned long prcmu_clock_rate(u8 clock) -{ - return 0; -} - -static inline int prcmu_set_ape_opp(u8 opp) -{ - return 0; -} - -static inline int prcmu_get_ape_opp(void) -{ - return APE_100_OPP; -} - -static inline int prcmu_request_ape_opp_100_voltage(bool enable) -{ - return 0; -} - -static inline int prcmu_set_arm_opp(u8 opp) -{ - return 0; -} - -static inline int prcmu_get_arm_opp(void) -{ - return ARM_100_OPP; -} - -static inline int prcmu_get_ddr_opp(void) -{ - return DDR_100_OPP; -} - -static inline void prcmu_system_reset(u16 reset_code) {} - -static inline u16 prcmu_get_reset_code(void) -{ - return 0; -} - -static inline int prcmu_ac_wake_req(void) -{ - return 0; -} - -static inline void prcmu_ac_sleep_req(void) {} - -static inline void prcmu_modem_reset(void) {} - -static inline bool prcmu_is_ac_wake_requested(void) -{ - return false; -} - -static inline int prcmu_config_esram0_deep_sleep(u8 state) -{ - return 0; -} - -static inline void prcmu_config_abb_event_readout(u32 abb_events) {} - -static inline void prcmu_get_abb_event_buffer(void __iomem **buf) -{ - *buf = NULL; -} - -static inline int prcmu_config_hotdog(u8 threshold) -{ - return 0; -} - -static inline int prcmu_config_hotmon(u8 low, u8 high) -{ - return 0; -} - -static inline int prcmu_start_temp_sense(u16 cycles32k) -{ - return 0; -} - -static inline int prcmu_stop_temp_sense(void) -{ - return 0; -} - -static inline u32 prcmu_read(unsigned int reg) -{ - return 0; -} - -static inline void prcmu_write(unsigned int reg, u32 value) {} - -static inline void prcmu_write_masked(unsigned int reg, u32 mask, u32 value) {} - -#endif - -static inline void prcmu_set(unsigned int reg, u32 bits) -{ - prcmu_write_masked(reg, bits, bits); -} - -static inline void prcmu_clear(unsigned int reg, u32 bits) -{ - prcmu_write_masked(reg, bits, 0); -} - -/* PRCMU QoS APE OPP class */ -#define PRCMU_QOS_APE_OPP 1 -#define PRCMU_QOS_DDR_OPP 2 -#define PRCMU_QOS_ARM_OPP 3 -#define PRCMU_QOS_DEFAULT_VALUE -1 - -static inline int prcmu_qos_add_requirement(int prcmu_qos_class, - char *name, s32 value) -{ - return 0; -} - -static inline int prcmu_qos_update_requirement(int prcmu_qos_class, - char *name, s32 new_value) -{ - return 0; -} - -static inline void prcmu_qos_remove_requirement(int prcmu_qos_class, char *name) -{ -} - -#endif /* __MACH_PRCMU_H */ diff --git a/sound/soc/ux500/ux500_msp_dai.c b/sound/soc/ux500/ux500_msp_dai.c index 7798957c6504..499e826d7120 100644 --- a/sound/soc/ux500/ux500_msp_dai.c +++ b/sound/soc/ux500/ux500_msp_dai.c @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include #include From 0f2da45318c2e970c59da0c03b01a564ce60a980 Mon Sep 17 00:00:00 2001 From: Louis-Alexis Eyraud Date: Wed, 1 Jul 2026 17:14:06 +0200 Subject: [PATCH 0319/1328] dt-bindings: mfd: mediatek,mt8195-scpsys: Add support for MT8189 SoC Add a compatible string for the scpsys block found in the MediaTek MT8189 SoC. Signed-off-by: Louis-Alexis Eyraud Acked-by: Conor Dooley Link: https://patch.msgid.link/20260701-mt8189-dt-bindings-scpsys-v1-1-2c04f0fda1b7@collabora.com Signed-off-by: Lee Jones --- .../devicetree/bindings/mfd/mediatek,mt8195-scpsys.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/mfd/mediatek,mt8195-scpsys.yaml b/Documentation/devicetree/bindings/mfd/mediatek,mt8195-scpsys.yaml index 4cafa381979b..9f073d0c28ef 100644 --- a/Documentation/devicetree/bindings/mfd/mediatek,mt8195-scpsys.yaml +++ b/Documentation/devicetree/bindings/mfd/mediatek,mt8195-scpsys.yaml @@ -26,6 +26,7 @@ properties: - mediatek,mt8183-scpsys - mediatek,mt8186-scpsys - mediatek,mt8188-scpsys + - mediatek,mt8189-scpsys - mediatek,mt8192-scpsys - mediatek,mt8195-scpsys - mediatek,mt8365-scpsys From 733fbd0d1198836b71e54a0d3078cf9df2cd132c Mon Sep 17 00:00:00 2001 From: Mohammad Shahid Date: Fri, 3 Jul 2026 16:29:37 +0530 Subject: [PATCH 0320/1328] mfd: viperboard: Remove redundant NULL check before kfree() kfree() safely handles NULL pointers, so the explicit NULL check before calling kfree() is unnecessary. This issue was reported by ifnullfree.cocci. Signed-off-by: Mohammad Shahid Link: https://patch.msgid.link/20260703105937.62541-1-mdshahid03@gmail.com Signed-off-by: Lee Jones --- drivers/mfd/viperboard.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/mfd/viperboard.c b/drivers/mfd/viperboard.c index 888737b8e7be..36be8e69f45c 100644 --- a/drivers/mfd/viperboard.c +++ b/drivers/mfd/viperboard.c @@ -96,8 +96,7 @@ static int vprbrd_probe(struct usb_interface *interface, return 0; error: - if (vb) - kfree(vb); + kfree(vb); return ret; } From abf98c166e41d3c3473216f4b8a508e284ad13f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Otto=20Pfl=C3=BCger?= Date: Wed, 1 Jul 2026 17:00:01 +0200 Subject: [PATCH 0321/1328] mfd: sprd-sc27xx: Add SC2730 regulator cell MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add an MFD cell to register the SC2730 PMIC's regulators. Signed-off-by: Otto Pflüger Link: https://patch.msgid.link/20260701-sc2730-regulators-v7-1-6e145ce83657@abscue.de Signed-off-by: Lee Jones --- drivers/mfd/sprd-sc27xx-spi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mfd/sprd-sc27xx-spi.c b/drivers/mfd/sprd-sc27xx-spi.c index aa052f646623..214bcbef0c27 100644 --- a/drivers/mfd/sprd-sc27xx-spi.c +++ b/drivers/mfd/sprd-sc27xx-spi.c @@ -61,6 +61,7 @@ static const struct mfd_cell sc2730_devices[] = { MFD_CELL_OF("sc2730-efuse", NULL, NULL, 0, 0, "sprd,sc2730-efuse"), MFD_CELL_OF("sc2730-eic", NULL, NULL, 0, 0, "sprd,sc2730-eic"), MFD_CELL_OF("sc2730-fgu", NULL, NULL, 0, 0, "sprd,sc2730-fgu"), + MFD_CELL_NAME("sc2730-regulator"), MFD_CELL_OF("sc2730-rtc", NULL, NULL, 0, 0, "sprd,sc2730-rtc"), MFD_CELL_OF("sc2730-vibrator", NULL, NULL, 0, 0, "sprd,sc2730-vibrator"), }; From e9929b4b61dc5eb0e68a208e272f6e8b2d2eb0d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Thu, 2 Jul 2026 16:53:39 +0200 Subject: [PATCH 0322/1328] mfd: Drop unused assignment of spi_device_id driver data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The drivers explicitly set the .driver_data member of struct spi_device_id to zero without relying on that value. Drop these unused assignments. While touching these arrays use named initializers for .name. This patch doesn't modify the compiled arrays, only their representation in source form benefits. The former was confirmed with x86 and arm64 builds. Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://patch.msgid.link/15dc05c1a49f79b6cb43684d8bcf60adcd1be7de.1783003256.git.u.kleine-koenig@baylibre.com Signed-off-by: Lee Jones --- drivers/mfd/ocelot-spi.c | 2 +- drivers/mfd/rk8xx-spi.c | 2 +- drivers/mfd/tps65912-spi.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/mfd/ocelot-spi.c b/drivers/mfd/ocelot-spi.c index 1fed9878c323..051d48c55763 100644 --- a/drivers/mfd/ocelot-spi.c +++ b/drivers/mfd/ocelot-spi.c @@ -271,7 +271,7 @@ static int ocelot_spi_probe(struct spi_device *spi) } static const struct spi_device_id ocelot_spi_ids[] = { - { "vsc7512", 0 }, + { .name = "vsc7512" }, { } }; MODULE_DEVICE_TABLE(spi, ocelot_spi_ids); diff --git a/drivers/mfd/rk8xx-spi.c b/drivers/mfd/rk8xx-spi.c index 3405fb82ff9f..bb85fe60518f 100644 --- a/drivers/mfd/rk8xx-spi.c +++ b/drivers/mfd/rk8xx-spi.c @@ -104,7 +104,7 @@ static const struct of_device_id rk8xx_spi_of_match[] = { MODULE_DEVICE_TABLE(of, rk8xx_spi_of_match); static const struct spi_device_id rk8xx_spi_id_table[] = { - { "rk806", 0 }, + { .name = "rk806" }, { } }; MODULE_DEVICE_TABLE(spi, rk8xx_spi_id_table); diff --git a/drivers/mfd/tps65912-spi.c b/drivers/mfd/tps65912-spi.c index 2a77dccd6059..2442a2e67d67 100644 --- a/drivers/mfd/tps65912-spi.c +++ b/drivers/mfd/tps65912-spi.c @@ -43,7 +43,7 @@ static int tps65912_spi_probe(struct spi_device *spi) } static const struct spi_device_id tps65912_spi_id_table[] = { - { "tps65912", 0 }, + { .name = "tps65912" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(spi, tps65912_spi_id_table); From 02c54e90785c4d75eb0adf8e08f69b181e1d4758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Thu, 2 Jul 2026 16:53:40 +0200 Subject: [PATCH 0323/1328] mfd: Initialize spi_device_id arrays using member names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While being less compact, using named initializers allows to more easily see which members of the structs are assigned which value without having to lookup the declaration of the struct. And it's also more robust against changes to the struct definition. The mentioned robustness is relevant for a planned change to struct spi_device_id that replaces .driver_data by an anonymous union. Also adapt spacing and usage of commas to the most common style. This patch doesn't modify the compiled array, only its representation in source form benefits. Signed-off-by: Uwe Kleine-König (The Capable Hub) Reviewed-by: Charles Keepax Link: https://patch.msgid.link/f83c8292e7e3ba9425792591fb136d4ae1468215.1783003256.git.u.kleine-koenig@baylibre.com Signed-off-by: Lee Jones --- drivers/mfd/arizona-spi.c | 12 ++++++------ drivers/mfd/cs40l50-spi.c | 4 ++-- drivers/mfd/da9052-spi.c | 12 ++++++------ drivers/mfd/intel-m10-bmc-spi.c | 6 +++--- drivers/mfd/madera-spi.c | 18 +++++++++--------- drivers/mfd/rsmu_spi.c | 12 ++++++------ drivers/mfd/stmpe-spi.c | 12 ++++++------ drivers/mfd/wm831x-spi.c | 16 ++++++++-------- 8 files changed, 46 insertions(+), 46 deletions(-) diff --git a/drivers/mfd/arizona-spi.c b/drivers/mfd/arizona-spi.c index eaa2b2bc5dd0..04baf5a1c652 100644 --- a/drivers/mfd/arizona-spi.c +++ b/drivers/mfd/arizona-spi.c @@ -255,12 +255,12 @@ static void arizona_spi_remove(struct spi_device *spi) } static const struct spi_device_id arizona_spi_ids[] = { - { "wm5102", WM5102 }, - { "wm5110", WM5110 }, - { "wm8280", WM8280 }, - { "wm1831", WM1831 }, - { "cs47l24", CS47L24 }, - { }, + { .name = "wm5102", .driver_data = WM5102 }, + { .name = "wm5110", .driver_data = WM5110 }, + { .name = "wm8280", .driver_data = WM8280 }, + { .name = "wm1831", .driver_data = WM1831 }, + { .name = "cs47l24", .driver_data = CS47L24 }, + { } }; MODULE_DEVICE_TABLE(spi, arizona_spi_ids); diff --git a/drivers/mfd/cs40l50-spi.c b/drivers/mfd/cs40l50-spi.c index 53526b595a0d..a635951ca6b7 100644 --- a/drivers/mfd/cs40l50-spi.c +++ b/drivers/mfd/cs40l50-spi.c @@ -40,8 +40,8 @@ static void cs40l50_spi_remove(struct spi_device *spi) } static const struct spi_device_id cs40l50_id_spi[] = { - { "cs40l50" }, - {} + { .name = "cs40l50" }, + { } }; MODULE_DEVICE_TABLE(spi, cs40l50_id_spi); diff --git a/drivers/mfd/da9052-spi.c b/drivers/mfd/da9052-spi.c index be5f2b34e18a..29cf2c17fde1 100644 --- a/drivers/mfd/da9052-spi.c +++ b/drivers/mfd/da9052-spi.c @@ -63,12 +63,12 @@ static void da9052_spi_remove(struct spi_device *spi) } static const struct spi_device_id da9052_spi_id[] = { - {"da9052", DA9052}, - {"da9053-aa", DA9053_AA}, - {"da9053-ba", DA9053_BA}, - {"da9053-bb", DA9053_BB}, - {"da9053-bc", DA9053_BC}, - {} + { .name = "da9052", .driver_data = DA9052 }, + { .name = "da9053-aa", .driver_data = DA9053_AA }, + { .name = "da9053-ba", .driver_data = DA9053_BA }, + { .name = "da9053-bb", .driver_data = DA9053_BB }, + { .name = "da9053-bc", .driver_data = DA9053_BC }, + { } }; static struct spi_driver da9052_spi_driver = { diff --git a/drivers/mfd/intel-m10-bmc-spi.c b/drivers/mfd/intel-m10-bmc-spi.c index cfa620f0c70e..94b9c99bb4f8 100644 --- a/drivers/mfd/intel-m10-bmc-spi.c +++ b/drivers/mfd/intel-m10-bmc-spi.c @@ -160,9 +160,9 @@ static const struct intel_m10bmc_platform_info m10bmc_spi_n5010 = { }; static const struct spi_device_id m10bmc_spi_id[] = { - { "m10-n3000", (kernel_ulong_t)&m10bmc_spi_n3000 }, - { "m10-d5005", (kernel_ulong_t)&m10bmc_spi_d5005 }, - { "m10-n5010", (kernel_ulong_t)&m10bmc_spi_n5010 }, + { .name = "m10-n3000", .driver_data = (kernel_ulong_t)&m10bmc_spi_n3000 }, + { .name = "m10-d5005", .driver_data = (kernel_ulong_t)&m10bmc_spi_d5005 }, + { .name = "m10-n5010", .driver_data = (kernel_ulong_t)&m10bmc_spi_n5010 }, { } }; MODULE_DEVICE_TABLE(spi, m10bmc_spi_id); diff --git a/drivers/mfd/madera-spi.c b/drivers/mfd/madera-spi.c index ce9e90322c9c..3fffa21ceadc 100644 --- a/drivers/mfd/madera-spi.c +++ b/drivers/mfd/madera-spi.c @@ -112,15 +112,15 @@ static void madera_spi_remove(struct spi_device *spi) } static const struct spi_device_id madera_spi_ids[] = { - { "cs47l15", CS47L15 }, - { "cs47l35", CS47L35 }, - { "cs47l85", CS47L85 }, - { "cs47l90", CS47L90 }, - { "cs47l91", CS47L91 }, - { "cs42l92", CS42L92 }, - { "cs47l92", CS47L92 }, - { "cs47l93", CS47L93 }, - { "wm1840", WM1840 }, + { .name = "cs47l15", .driver_data = CS47L15 }, + { .name = "cs47l35", .driver_data = CS47L35 }, + { .name = "cs47l85", .driver_data = CS47L85 }, + { .name = "cs47l90", .driver_data = CS47L90 }, + { .name = "cs47l91", .driver_data = CS47L91 }, + { .name = "cs42l92", .driver_data = CS42L92 }, + { .name = "cs47l92", .driver_data = CS47L92 }, + { .name = "cs47l93", .driver_data = CS47L93 }, + { .name = "wm1840", .driver_data = WM1840 }, { } }; MODULE_DEVICE_TABLE(spi, madera_spi_ids); diff --git a/drivers/mfd/rsmu_spi.c b/drivers/mfd/rsmu_spi.c index e07f21482439..cdb0f9797ec6 100644 --- a/drivers/mfd/rsmu_spi.c +++ b/drivers/mfd/rsmu_spi.c @@ -239,12 +239,12 @@ static void rsmu_spi_remove(struct spi_device *client) } static const struct spi_device_id rsmu_spi_id[] = { - { "8a34000", RSMU_CM }, - { "8a34001", RSMU_CM }, - { "8a34002", RSMU_CM }, - { "82p33810", RSMU_SABRE }, - { "82p33811", RSMU_SABRE }, - {} + { .name = "8a34000", .driver_data = RSMU_CM }, + { .name = "8a34001", .driver_data = RSMU_CM }, + { .name = "8a34002", .driver_data = RSMU_CM }, + { .name = "82p33810", .driver_data = RSMU_SABRE }, + { .name = "82p33811", .driver_data = RSMU_SABRE }, + { } }; MODULE_DEVICE_TABLE(spi, rsmu_spi_id); diff --git a/drivers/mfd/stmpe-spi.c b/drivers/mfd/stmpe-spi.c index dea31efface6..22a3da062dee 100644 --- a/drivers/mfd/stmpe-spi.c +++ b/drivers/mfd/stmpe-spi.c @@ -121,12 +121,12 @@ static const struct of_device_id stmpe_spi_of_match[] = { MODULE_DEVICE_TABLE(of, stmpe_spi_of_match); static const struct spi_device_id stmpe_spi_id[] = { - { "stmpe610", STMPE610 }, - { "stmpe801", STMPE801 }, - { "stmpe811", STMPE811 }, - { "stmpe1601", STMPE1601 }, - { "stmpe2401", STMPE2401 }, - { "stmpe2403", STMPE2403 }, + { .name = "stmpe610", .driver_data = STMPE610 }, + { .name = "stmpe801", .driver_data = STMPE801 }, + { .name = "stmpe811", .driver_data = STMPE811 }, + { .name = "stmpe1601", .driver_data = STMPE1601 }, + { .name = "stmpe2401", .driver_data = STMPE2401 }, + { .name = "stmpe2403", .driver_data = STMPE2403 }, { } }; MODULE_DEVICE_TABLE(spi, stmpe_spi_id); diff --git a/drivers/mfd/wm831x-spi.c b/drivers/mfd/wm831x-spi.c index 54c87267917b..1e519fd9a9e1 100644 --- a/drivers/mfd/wm831x-spi.c +++ b/drivers/mfd/wm831x-spi.c @@ -77,14 +77,14 @@ static const struct dev_pm_ops wm831x_spi_pm = { }; static const struct spi_device_id wm831x_spi_ids[] = { - { "wm8310", WM8310 }, - { "wm8311", WM8311 }, - { "wm8312", WM8312 }, - { "wm8320", WM8320 }, - { "wm8321", WM8321 }, - { "wm8325", WM8325 }, - { "wm8326", WM8326 }, - { }, + { .name = "wm8310", .driver_data = WM8310 }, + { .name = "wm8311", .driver_data = WM8311 }, + { .name = "wm8312", .driver_data = WM8312 }, + { .name = "wm8320", .driver_data = WM8320 }, + { .name = "wm8321", .driver_data = WM8321 }, + { .name = "wm8325", .driver_data = WM8325 }, + { .name = "wm8326", .driver_data = WM8326 }, + { } }; static struct spi_driver wm831x_spi_driver = { From d0a1023960c8d4b64bd100e48ad0628e0ac82c5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Thu, 2 Jul 2026 16:53:41 +0200 Subject: [PATCH 0324/1328] mfd: Unify style of spi_device_id arrays MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The two previous commits adapted the style of some spi_device_id arrays. Fix the remaining arrays to the same style, that is: - no comma after the list terminator and after an initializer iff the closing } is on the same line - a single space in the list terminator Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://patch.msgid.link/323dc79eb1bcc55caf0163e26501e1de3e710554.1783003256.git.u.kleine-koenig@baylibre.com Signed-off-by: Lee Jones --- drivers/mfd/altera-a10sr.c | 2 +- drivers/mfd/motorola-cpcap.c | 6 +++--- drivers/mfd/sprd-sc27xx-spi.c | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/mfd/altera-a10sr.c b/drivers/mfd/altera-a10sr.c index d53e433ab5c1..9c0262228655 100644 --- a/drivers/mfd/altera-a10sr.c +++ b/drivers/mfd/altera-a10sr.c @@ -155,7 +155,7 @@ MODULE_DEVICE_TABLE(of, altr_a10sr_spi_of_match); static const struct spi_device_id altr_a10sr_spi_ids[] = { { .name = "a10sr" }, - { }, + { } }; MODULE_DEVICE_TABLE(spi, altr_a10sr_spi_ids); diff --git a/drivers/mfd/motorola-cpcap.c b/drivers/mfd/motorola-cpcap.c index d8243b956f87..38c93b1d7842 100644 --- a/drivers/mfd/motorola-cpcap.c +++ b/drivers/mfd/motorola-cpcap.c @@ -203,9 +203,9 @@ static const struct of_device_id cpcap_of_match[] = { MODULE_DEVICE_TABLE(of, cpcap_of_match); static const struct spi_device_id cpcap_spi_ids[] = { - { .name = "cpcap", }, - { .name = "6556002", }, - {}, + { .name = "cpcap" }, + { .name = "6556002" }, + { } }; MODULE_DEVICE_TABLE(spi, cpcap_spi_ids); diff --git a/drivers/mfd/sprd-sc27xx-spi.c b/drivers/mfd/sprd-sc27xx-spi.c index 214bcbef0c27..9a8e6add8fca 100644 --- a/drivers/mfd/sprd-sc27xx-spi.c +++ b/drivers/mfd/sprd-sc27xx-spi.c @@ -295,7 +295,7 @@ MODULE_DEVICE_TABLE(of, sprd_pmic_match); static const struct spi_device_id sprd_pmic_spi_ids[] = { { .name = "sc2730", .driver_data = PMIC_TYPE_SC2730 }, { .name = "sc2731", .driver_data = PMIC_TYPE_SC2731 }, - {}, + { } }; MODULE_DEVICE_TABLE(spi, sprd_pmic_spi_ids); From 0f83e6003f920aefef55165562fa03cac0479989 Mon Sep 17 00:00:00 2001 From: Komal Bajaj Date: Fri, 1 May 2026 23:31:17 +0530 Subject: [PATCH 0325/1328] dt-bindings: mfd: qcom,tcsr: Add compatible for Shikra Document the qcom,shikra-tcsr compatible. Signed-off-by: Komal Bajaj Reviewed-by: Mukesh Ojha Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260501-shikra-tcsr-binding-v1-1-0c136d193634@oss.qualcomm.com Signed-off-by: Lee Jones --- Documentation/devicetree/bindings/mfd/qcom,tcsr.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/mfd/qcom,tcsr.yaml b/Documentation/devicetree/bindings/mfd/qcom,tcsr.yaml index 7dd2fe035e6d..fba5ff5283b1 100644 --- a/Documentation/devicetree/bindings/mfd/qcom,tcsr.yaml +++ b/Documentation/devicetree/bindings/mfd/qcom,tcsr.yaml @@ -34,6 +34,7 @@ properties: - qcom,sdx55-tcsr - qcom,sdx65-tcsr - qcom,sdx75-tcsr + - qcom,shikra-tcsr - qcom,sm4450-tcsr - qcom,sm6115-tcsr - qcom,sm8150-tcsr From cd958e48bc4b8f4980f41bfa6edc9d00e63a120c Mon Sep 17 00:00:00 2001 From: Samuel Holland Date: Sat, 11 Jul 2026 14:28:26 -0500 Subject: [PATCH 0326/1328] dt-bindings: vendor-prefixes: Add techvision Techvision Intelligent Technology Co., Ltd[1] aka Shenzhen Huiwei Intelligent Technology Co., Ltd[2] is a manufacturer of single-board computers. Link: https://cn.techvision.com.cn/ [1] Link: https://www.techvision.com.cn/ [2] Signed-off-by: Samuel Holland Acked-by: Conor Dooley Link: https://patch.msgid.link/20260711192842.845048-3-samuel@sholland.org Signed-off-by: Lee Jones --- Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index 396044f368e7..2355505013a0 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -1662,6 +1662,8 @@ patternProperties: description: Technologic Systems "^techstar,.*": description: Shenzhen Techstar Electronics Co., Ltd. + "^techvision,.*": + description: Techvision Intelligent Technology Co., Ltd "^techwell,.*": description: Techwell, Inc. "^teejet,.*": From d7a9410cd921d9fe1eb52a5c9f9ab10674545c2f Mon Sep 17 00:00:00 2001 From: Frank Li Date: Mon, 6 Jul 2026 15:29:32 -0400 Subject: [PATCH 0327/1328] dt-bindings: mfd: st,stmpe: Fix typo st,stmpe601 (should be st,stmpe610) The compatible string "st,stmpe601" is a typo and does not correspond to any existing STMPE device in either the driver or DTS files. The correct compatible string is "st,stmpe610". Fix the typo to ensure proper schema matching and eliminate the following CHECK_DTBS warning: imx53-m53evk.dtb: /soc/bus@60000000/i2c@63fc4000/touchscreen@41: failed to match any schema with compatible: ['st,stmpe610'] Fixes: e10038ce1ba9 ("dt-bindings: mfd: Convert STMPE to YAML schema") Reviewed-by: Linus Walleij Signed-off-by: Frank Li Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260706192932.1573584-1-Frank.Li@oss.nxp.com Signed-off-by: Lee Jones --- Documentation/devicetree/bindings/mfd/st,stmpe.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/mfd/st,stmpe.yaml b/Documentation/devicetree/bindings/mfd/st,stmpe.yaml index 4bb05d544901..ddb27ae64ba3 100644 --- a/Documentation/devicetree/bindings/mfd/st,stmpe.yaml +++ b/Documentation/devicetree/bindings/mfd/st,stmpe.yaml @@ -20,7 +20,7 @@ allOf: properties: compatible: enum: - - st,stmpe601 + - st,stmpe610 - st,stmpe801 - st,stmpe811 - st,stmpe1600 From 2e68a2188d5b5db86a56c5ba98c682d5505dc57b Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Wed, 8 Jul 2026 11:09:14 +0100 Subject: [PATCH 0328/1328] mfd: cs42l43: Remove redundant NULL checks on SoundWire The SoundWire core helpers now check for NULL on the SoundWire peripheral so there is no need to do so locally, remove the duplicate checks. Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20260708100914.1298080-1-ckeepax@opensource.cirrus.com Signed-off-by: Lee Jones --- drivers/mfd/cs42l43.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/mfd/cs42l43.c b/drivers/mfd/cs42l43.c index d2bbd2f18af7..33479ddd539a 100644 --- a/drivers/mfd/cs42l43.c +++ b/drivers/mfd/cs42l43.c @@ -588,12 +588,9 @@ static int cs42l43_wait_for_attach(struct cs42l43 *cs42l43) { int ret; - if (cs42l43->sdw) { - ret = sdw_slave_wait_for_init(cs42l43->sdw, - CS42L43_SDW_ATTACH_TIMEOUT_MS); - if (ret) - return ret; - } + ret = sdw_slave_wait_for_init(cs42l43->sdw, CS42L43_SDW_ATTACH_TIMEOUT_MS); + if (ret) + return ret; regcache_cache_only(cs42l43->regmap, false); From a3027963217c5d2b0eb7f29d0131d80c9c632d23 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Wed, 8 Jul 2026 15:00:38 +0100 Subject: [PATCH 0329/1328] mfd: cs42l43: Tidy up formatting on sdw_device_id table Remove spaces after cast as they generate check patch warnings, and update the terminator to better match kernel coding guidelines. Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20260708140039.1993489-3-ckeepax@opensource.cirrus.com Signed-off-by: Lee Jones --- drivers/mfd/cs42l43-sdw.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/mfd/cs42l43-sdw.c b/drivers/mfd/cs42l43-sdw.c index 2b87ae2d79c5..6ccfdcd3f669 100644 --- a/drivers/mfd/cs42l43-sdw.c +++ b/drivers/mfd/cs42l43-sdw.c @@ -182,9 +182,9 @@ static int cs42l43_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id * } static const struct sdw_device_id cs42l43_sdw_id[] = { - SDW_SLAVE_ENTRY(0x01FA, 0x4243, (void *) CS42L43_DEVID_VAL), - SDW_SLAVE_ENTRY(0x01FA, 0x2A3B, (void *) CS42L43B_DEVID_VAL), - {} + SDW_SLAVE_ENTRY(0x01FA, 0x4243, (void *)CS42L43_DEVID_VAL), + SDW_SLAVE_ENTRY(0x01FA, 0x2A3B, (void *)CS42L43B_DEVID_VAL), + { } }; MODULE_DEVICE_TABLE(sdw, cs42l43_sdw_id); From 0674e2cd7f4049d0028681cc8f1f4b550327e54f Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Mon, 6 Jul 2026 22:01:30 -0700 Subject: [PATCH 0330/1328] mfd: ucb1x00: Register software node for GPIO controller Define a static software node for the UCB1x00 GPIO controller and attach it to the core MFD device in ucb1x00_probe(). This node will also be used by the created GPIO chip. This allows machine subdrivers (such as Assabet evaluation board support) to reference the UCB1x00 GPIO controller in property entries when converting legacy platform data to software nodes, resolving pin bindings directly via the attached firmware node without relying on name matching. Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Dmitry Torokhov Acked-by: Arnd Bergmann Reviewed-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260706-ucb1x00-assabet-swnode-v2-1-e6271ea3d3dc@gmail.com Signed-off-by: Lee Jones --- drivers/mfd/ucb1x00-core.c | 16 +++++++++++++++- include/linux/mfd/ucb1x00.h | 3 +++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/drivers/mfd/ucb1x00-core.c b/drivers/mfd/ucb1x00-core.c index 16f64e2b2f77..90edf0352d05 100644 --- a/drivers/mfd/ucb1x00-core.c +++ b/drivers/mfd/ucb1x00-core.c @@ -25,6 +25,7 @@ #include #include #include +#include #include static DEFINE_MUTEX(ucb1x00_mutex); @@ -492,6 +493,11 @@ static struct class ucb1x00_class = { .dev_release = ucb1x00_release, }; +const struct software_node ucb1x00_gpiochip_node = { + .name = "ucb1x00-gpio", +}; +EXPORT_SYMBOL_GPL(ucb1x00_gpiochip_node); + static int ucb1x00_probe(struct mcp *mcp) { struct ucb1x00_plat_data *pdata = mcp->attached_device.platform_data; @@ -530,6 +536,10 @@ static int ucb1x00_probe(struct mcp *mcp) ucb->id = id; ucb->mcp = mcp; + ret = device_add_software_node(&ucb->dev, &ucb1x00_gpiochip_node); + if (ret) + goto err_swnode_add; + ret = device_add(&ucb->dev); if (ret) goto err_dev_add; @@ -604,6 +614,8 @@ static int ucb1x00_probe(struct mcp *mcp) err_no_irq: device_del(&ucb->dev); err_dev_add: + device_remove_software_node(&ucb->dev); + err_swnode_add: put_device(&ucb->dev); out: if (pdata && pdata->reset) @@ -630,7 +642,9 @@ static void ucb1x00_remove(struct mcp *mcp) irq_set_chained_handler(ucb->irq, NULL); irq_free_descs(ucb->irq_base, 16); - device_unregister(&ucb->dev); + device_del(&ucb->dev); + device_remove_software_node(&ucb->dev); + put_device(&ucb->dev); if (pdata && pdata->reset) pdata->reset(UCB_RST_REMOVE); diff --git a/include/linux/mfd/ucb1x00.h b/include/linux/mfd/ucb1x00.h index ede237384723..214c71a12e84 100644 --- a/include/linux/mfd/ucb1x00.h +++ b/include/linux/mfd/ucb1x00.h @@ -103,6 +103,9 @@ #define UCB_MODE_DYN_VFLAG_ENA (1 << 12) #define UCB_MODE_AUD_OFF_CAN (1 << 13) +struct software_node; +extern const struct software_node ucb1x00_gpiochip_node; + enum ucb1x00_reset { UCB_RST_PROBE, UCB_RST_RESUME, From cbff6a720d94cec10265740f2369762acb3290e3 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Mon, 6 Jul 2026 22:01:31 -0700 Subject: [PATCH 0331/1328] mfd: ucb1x00: Convert Assabet gpio-keys to use software nodes Convert the legacy gpio-keys platform device on the StrongARM SA-1100 Assabet evaluation board to use software nodes and device properties. This allows describing the buttons and their GPIO bindings via software nodes so that platform data support can eventually be removed from the gpio-keys driver. Define static software nodes for the gpio-keys device and the six button child nodes at file scope using relative pin indexing on the UCB1x00 GPIO controller node. In ucb1x00_assabet_add(), register the software node group and use platform_device_register_full() to register the device. Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Dmitry Torokhov Reviewed-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260706-ucb1x00-assabet-swnode-v2-2-e6271ea3d3dc@gmail.com Signed-off-by: Lee Jones --- drivers/mfd/ucb1x00-assabet.c | 120 +++++++++++++++++++++++++++------- 1 file changed, 96 insertions(+), 24 deletions(-) diff --git a/drivers/mfd/ucb1x00-assabet.c b/drivers/mfd/ucb1x00-assabet.c index 6a389737c615..ee49ac779d1a 100644 --- a/drivers/mfd/ucb1x00-assabet.c +++ b/drivers/mfd/ucb1x00-assabet.c @@ -6,15 +6,18 @@ * * We handle the machine-specific bits of the UCB1x00 driver here. */ -#include -#include #include #include #include -#include +#include +#include +#include #include +#include #include #include +#include +#include #include #define UCB1X00_ATTR(name,input)\ @@ -34,50 +37,119 @@ UCB1X00_ATTR(vbatt, UCB_ADC_INP_AD1); UCB1X00_ATTR(vcharger, UCB_ADC_INP_AD0); UCB1X00_ATTR(batt_temp, UCB_ADC_INP_AD2); +static const struct property_entry ucb1x00_gpio_keys_props[] = { + PROPERTY_ENTRY_STRING("label", "ucb1x00"), + PROPERTY_ENTRY_U32("poll-interval", 50), + { } +}; + +#define UCB1X00_BTN_PROPS(_idx) \ +struct property_entry ucb1x00_btn##_idx##_props[] = { \ + PROPERTY_ENTRY_U32("linux,code", BTN_0 + (_idx)), \ + PROPERTY_ENTRY_GPIO("gpios", &ucb1x00_gpiochip_node, \ + _idx, GPIO_ACTIVE_HIGH), \ + PROPERTY_ENTRY_STRING("label", "btn" #_idx), \ + PROPERTY_ENTRY_BOOL("linux,can-disable"), \ + { } \ +} + +static const UCB1X00_BTN_PROPS(0); +static const UCB1X00_BTN_PROPS(1); +static const UCB1X00_BTN_PROPS(2); +static const UCB1X00_BTN_PROPS(3); +static const UCB1X00_BTN_PROPS(4); +static const UCB1X00_BTN_PROPS(5); + +static const struct property_entry * const ucb1x00_btn_props[] = { + ucb1x00_btn0_props, + ucb1x00_btn1_props, + ucb1x00_btn2_props, + ucb1x00_btn3_props, + ucb1x00_btn4_props, + ucb1x00_btn5_props, +}; + +struct ucb1x00_assabet_priv { + struct platform_device *pdev; + struct fwnode_handle *keys_node; + struct fwnode_handle *button_nodes[ARRAY_SIZE(ucb1x00_btn_props)]; +}; + +static void ucb1x00_assabet_remove_nodes(struct ucb1x00_assabet_priv *priv, int n) +{ + while (--n >= 0) + fwnode_remove_software_node(priv->button_nodes[n]); + + fwnode_remove_software_node(priv->keys_node); +} + static int ucb1x00_assabet_add(struct ucb1x00_dev *dev) { struct ucb1x00 *ucb = dev->ucb; - struct platform_device *pdev; - struct gpio_keys_platform_data keys; - static struct gpio_keys_button buttons[6]; - unsigned i; + struct platform_device_info pdevinfo = { + .name = "gpio-keys", + .id = PLATFORM_DEVID_NONE, + .parent = &ucb->dev, + }; + int ret; + int i; - memset(buttons, 0, sizeof(buttons)); - memset(&keys, 0, sizeof(keys)); + struct ucb1x00_assabet_priv *priv; - for (i = 0; i < ARRAY_SIZE(buttons); i++) { - buttons[i].code = BTN_0 + i; - buttons[i].gpio = ucb->gpio.base + i; - buttons[i].type = EV_KEY; - buttons[i].can_disable = true; + priv = kzalloc_obj(*priv, GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->keys_node = fwnode_create_software_node(ucb1x00_gpio_keys_props, NULL); + if (IS_ERR(priv->keys_node)) { + ret = PTR_ERR(priv->keys_node); + goto err_free_priv; } - keys.buttons = buttons; - keys.nbuttons = ARRAY_SIZE(buttons); - keys.poll_interval = 50; - keys.name = "ucb1x00"; + for (i = 0; i < ARRAY_SIZE(ucb1x00_btn_props); i++) { + priv->button_nodes[i] = fwnode_create_software_node(ucb1x00_btn_props[i], + priv->keys_node); + if (IS_ERR(priv->button_nodes[i])) { + ret = PTR_ERR(priv->button_nodes[i]); + goto err_free_buttons; + } + } - pdev = platform_device_register_data(&ucb->dev, "gpio-keys", -1, - &keys, sizeof(keys)); + pdevinfo.fwnode = priv->keys_node; + + priv->pdev = platform_device_register_full(&pdevinfo); + ret = PTR_ERR_OR_ZERO(priv->pdev); + if (ret) + goto err_free_buttons; device_create_file(&ucb->dev, &dev_attr_vbatt); device_create_file(&ucb->dev, &dev_attr_vcharger); device_create_file(&ucb->dev, &dev_attr_batt_temp); - dev->priv = pdev; + dev->priv = priv; return 0; + +err_free_buttons: + ucb1x00_assabet_remove_nodes(priv, i); +err_free_priv: + kfree(priv); + return ret; } static void ucb1x00_assabet_remove(struct ucb1x00_dev *dev) { - struct platform_device *pdev = dev->priv; + struct ucb1x00_assabet_priv *priv = dev->priv; - if (!IS_ERR(pdev)) - platform_device_unregister(pdev); + if (!IS_ERR(priv->pdev)) + platform_device_unregister(priv->pdev); + + ucb1x00_assabet_remove_nodes(priv, ARRAY_SIZE(priv->button_nodes)); device_remove_file(&dev->ucb->dev, &dev_attr_batt_temp); device_remove_file(&dev->ucb->dev, &dev_attr_vcharger); device_remove_file(&dev->ucb->dev, &dev_attr_vbatt); + + kfree(priv); } static struct ucb1x00_driver ucb1x00_assabet_driver = { From 1b86b4ef3abf18a5324278fc03e024c10e47cfce Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Mon, 6 Jul 2026 16:24:50 -0700 Subject: [PATCH 0332/1328] mfd: rohm: Factor out power button registration Factor out the power button registration logic using software nodes from rohm-bd718x7 and rohm-bd71828 drivers into a shared module rohm-pwrbutton. This reduces duplication and makes it easier to support other ROHM PMICs with similar power button configurations. Suggested-by: Lee Jones Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Dmitry Torokhov Reviewed-by: Matti Vaittinen Link: https://patch.msgid.link/akw4naN2Khjv8itB@google.com Signed-off-by: Lee Jones --- MAINTAINERS | 2 + drivers/mfd/Kconfig | 6 ++ drivers/mfd/Makefile | 1 + drivers/mfd/rohm-bd71828.c | 84 ++------------------------ drivers/mfd/rohm-bd718x7.c | 84 ++------------------------ drivers/mfd/rohm-pwrbutton.c | 112 +++++++++++++++++++++++++++++++++++ drivers/mfd/rohm-pwrbutton.h | 12 ++++ 7 files changed, 141 insertions(+), 160 deletions(-) create mode 100644 drivers/mfd/rohm-pwrbutton.c create mode 100644 drivers/mfd/rohm-pwrbutton.h diff --git a/MAINTAINERS b/MAINTAINERS index c92dfca718f0..9b501458e672 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -23555,6 +23555,8 @@ F: drivers/mfd/rohm-bd71828.c F: drivers/mfd/rohm-bd718x7.c F: drivers/mfd/rohm-bd9576.c F: drivers/mfd/rohm-bd96801.c +F: drivers/mfd/rohm-pwrbutton.c +F: drivers/mfd/rohm-pwrbutton.h F: drivers/regulator/bd71815-regulator.c F: drivers/regulator/bd71828-regulator.c F: drivers/regulator/bd718x7-regulator.c diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 35f6e9b76d05..e4fd4572472f 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -2207,6 +2207,10 @@ config MFD_STW481X in various ST Microelectronics and ST-Ericsson embedded Nomadik series. +config MFD_ROHM_PWRBUTTON + tristate + select MFD_CORE + config MFD_ROHM_BD718XX tristate "ROHM BD71837 Power Management IC" depends on I2C=y @@ -2214,6 +2218,7 @@ config MFD_ROHM_BD718XX select REGMAP_I2C select REGMAP_IRQ select MFD_CORE + select MFD_ROHM_PWRBUTTON help Select this option to get support for the ROHM BD71837 Power Management ICs. BD71837 is designed to power processors like @@ -2227,6 +2232,7 @@ config MFD_ROHM_BD71828 select REGMAP_I2C select REGMAP_IRQ select MFD_CORE + select MFD_ROHM_PWRBUTTON help Select this option to get support for the ROHM BD71815, BD71828, BD71879, BD72720 and BD73900 Power Management ICs (PMICs). These are diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index dd4bb7e77c33..72d3944b0ad8 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -273,6 +273,7 @@ obj-$(CONFIG_MFD_STM32_TIMERS) += stm32-timers.o obj-$(CONFIG_MFD_MXS_LRADC) += mxs-lradc.o obj-$(CONFIG_MFD_SC27XX_PMIC) += sprd-sc27xx-spi.o obj-$(CONFIG_RAVE_SP_CORE) += rave-sp.o +obj-$(CONFIG_MFD_ROHM_PWRBUTTON) += rohm-pwrbutton.o obj-$(CONFIG_MFD_ROHM_BD71828) += rohm-bd71828.o obj-$(CONFIG_MFD_ROHM_BD718XX) += rohm-bd718x7.o obj-$(CONFIG_MFD_ROHM_BD957XMUF) += rohm-bd9576.o diff --git a/drivers/mfd/rohm-bd71828.c b/drivers/mfd/rohm-bd71828.c index 5fb6142cf087..bf4df8e1b34c 100644 --- a/drivers/mfd/rohm-bd71828.c +++ b/drivers/mfd/rohm-bd71828.c @@ -5,8 +5,6 @@ * ROHM BD718[15/28/79] and BD72720 PMIC driver */ -#include -#include #include #include #include @@ -19,10 +17,11 @@ #include #include #include -#include #include #include +#include "rohm-pwrbutton.h" + #define BD72720_TYPED_IRQ_REG(_irq, _stat_offset, _mask, _type_offset) \ [_irq] = { \ .reg_offset = (_stat_offset), \ @@ -859,83 +858,7 @@ static int set_clk_mode(struct device *dev, struct regmap *regmap, OUT32K_MODE_CMOS); } -static const struct property_entry bd71828_powerkey_parent_props[] = { - PROPERTY_ENTRY_STRING("label", "bd71828-pwrkey"), - { } -}; -static const struct property_entry bd71828_powerkey_props[] = { - PROPERTY_ENTRY_U32("linux,code", KEY_POWER), - PROPERTY_ENTRY_BOOL("wakeup-source"), - { } -}; - -#define GPIO_KEYS 0 /* Node corresponding to gpio-keys device itself */ -#define PWRON_KEY 1 /* Node describing power button in gpio-keys */ - -static int bd71828_i2c_register_swnodes(const struct software_node *nodes) -{ - const struct software_node * const node_group[] = { - &nodes[GPIO_KEYS], &nodes[PWRON_KEY], NULL - }; - - return software_node_register_node_group(node_group); -} - -static void bd71828_i2c_unregister_swnodes(void *data) -{ - const struct software_node *nodes = data; - const struct software_node * const node_group[] = { - &nodes[GPIO_KEYS], &nodes[PWRON_KEY], NULL - }; - - software_node_unregister_node_group(node_group); -} - -static int bd71828_i2c_register_pwrbutton(struct device *dev, int button_irq, - struct irq_domain *irq_domain) -{ - const struct resource res[] = { - DEFINE_RES_IRQ_NAMED(button_irq, "bd71828-pwrkey"), - }; - struct mfd_cell gpio_keys_cell = { - .name = "gpio-keys", - .resources = res, - .num_resources = ARRAY_SIZE(res), - }; - struct software_node *nodes; - int ret; - - nodes = devm_kcalloc(dev, 2, sizeof(*nodes), GFP_KERNEL); - if (!nodes) - return -ENOMEM; - - nodes[GPIO_KEYS].name = devm_kasprintf(dev, GFP_KERNEL, "%s-power-key", dev_name(dev)); - if (!nodes[GPIO_KEYS].name) - return -ENOMEM; - - nodes[GPIO_KEYS].properties = bd71828_powerkey_parent_props; - - nodes[PWRON_KEY].parent = &nodes[GPIO_KEYS]; - nodes[PWRON_KEY].properties = bd71828_powerkey_props; - - ret = bd71828_i2c_register_swnodes(nodes); - if (ret) - return ret; - - ret = devm_add_action_or_reset(dev, bd71828_i2c_unregister_swnodes, nodes); - if (ret) - return ret; - - gpio_keys_cell.swnode = &nodes[GPIO_KEYS]; - - ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, &gpio_keys_cell, 1, - NULL, 0, irq_domain); - if (ret) - return dev_err_probe(dev, ret, "Failed to register power-button"); - - return 0; -} static struct i2c_client *bd71828_dev; static void bd71828_power_off(void) @@ -1096,7 +1019,8 @@ static int bd71828_i2c_probe(struct i2c_client *i2c) return dev_err_probe(&i2c->dev, ret, "Failed to create subdevices\n"); if (button_irq) { - ret = bd71828_i2c_register_pwrbutton(&i2c->dev, button_irq, irq_domain); + ret = rohm_register_pwrbutton(&i2c->dev, button_irq, + "bd71828-pwrkey", true, irq_domain); if (ret) return ret; } diff --git a/drivers/mfd/rohm-bd718x7.c b/drivers/mfd/rohm-bd718x7.c index be2acc429fe3..0b0f8e27e329 100644 --- a/drivers/mfd/rohm-bd718x7.c +++ b/drivers/mfd/rohm-bd718x7.c @@ -7,8 +7,6 @@ // Datasheet for BD71837MWV available from // https://www.rohm.com/datasheet/BD71837MWV/bd71837mwv-e -#include -#include #include #include #include @@ -16,10 +14,11 @@ #include #include #include -#include #include #include +#include "rohm-pwrbutton.h" + static struct mfd_cell bd71837_mfd_cells[] = { { .name = "bd71837-clk", }, { .name = "bd71837-pmic", }, @@ -105,83 +104,7 @@ static int bd718xx_init_press_duration(struct regmap *regmap, return 0; } -static const struct property_entry bd718xx_powerkey_parent_props[] = { - PROPERTY_ENTRY_STRING("label", "bd718xx-pwrkey"), - { } -}; -static const struct property_entry bd718xx_powerkey_props[] = { - PROPERTY_ENTRY_U32("linux,code", KEY_POWER), - { } -}; - -static const struct resource bd718xx_powerkey_resources[] = { - DEFINE_RES_IRQ_NAMED(BD718XX_INT_PWRBTN_S, "bd718xx-pwrkey"), -}; - -#define GPIO_KEYS 0 /* Node corresponding to gpio-keys device itself */ -#define PWRON_KEY 1 /* Node describing power button in gpio-keys */ - -static int bd718xx_i2c_register_swnodes(const struct software_node *nodes) -{ - const struct software_node * const node_group[] = { - &nodes[GPIO_KEYS], &nodes[PWRON_KEY], NULL - }; - - return software_node_register_node_group(node_group); -} - -static void bd718xx_i2c_unregister_swnodes(void *data) -{ - const struct software_node *nodes = data; - const struct software_node * const node_group[] = { - &nodes[GPIO_KEYS], &nodes[PWRON_KEY], NULL - }; - - software_node_unregister_node_group(node_group); -} - -static int bd718xx_i2c_register_pwrbutton(struct device *dev, - struct irq_domain *irq_domain) -{ - struct mfd_cell gpio_keys_cell = { - .name = "gpio-keys", - .resources = bd718xx_powerkey_resources, - .num_resources = ARRAY_SIZE(bd718xx_powerkey_resources), - }; - struct software_node *nodes; - int ret; - - nodes = devm_kcalloc(dev, 2, sizeof(*nodes), GFP_KERNEL); - if (!nodes) - return -ENOMEM; - - nodes[GPIO_KEYS].name = devm_kasprintf(dev, GFP_KERNEL, "%s-power-key", dev_name(dev)); - if (!nodes[GPIO_KEYS].name) - return -ENOMEM; - - nodes[GPIO_KEYS].properties = bd718xx_powerkey_parent_props; - - nodes[PWRON_KEY].parent = &nodes[GPIO_KEYS]; - nodes[PWRON_KEY].properties = bd718xx_powerkey_props; - - ret = bd718xx_i2c_register_swnodes(nodes); - if (ret) - return ret; - - ret = devm_add_action_or_reset(dev, bd718xx_i2c_unregister_swnodes, nodes); - if (ret) - return ret; - - gpio_keys_cell.swnode = &nodes[GPIO_KEYS]; - - ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, &gpio_keys_cell, 1, - NULL, 0, irq_domain); - if (ret) - return dev_err_probe(dev, ret, "Failed to register power-button"); - - return 0; -} static int bd718xx_i2c_probe(struct i2c_client *i2c) { @@ -235,7 +158,8 @@ static int bd718xx_i2c_probe(struct i2c_client *i2c) if (ret) return dev_err_probe(&i2c->dev, ret, "Failed to create subdevices\n"); - ret = bd718xx_i2c_register_pwrbutton(&i2c->dev, irq_domain); + ret = rohm_register_pwrbutton(&i2c->dev, BD718XX_INT_PWRBTN_S, + "bd718xx-pwrkey", false, irq_domain); if (ret) return ret; diff --git a/drivers/mfd/rohm-pwrbutton.c b/drivers/mfd/rohm-pwrbutton.c new file mode 100644 index 000000000000..96a6d1f01db5 --- /dev/null +++ b/drivers/mfd/rohm-pwrbutton.c @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Shared helper for ROHM PMIC power button registration + * + * Copyright 2018, 2019 ROHM Semiconductors + * Copyright 2026 Google LLC + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "rohm-pwrbutton.h" + +#define GPIO_KEYS 0 /* Node corresponding to gpio-keys device itself */ +#define PWRON_KEY 1 /* Node describing power button in gpio-keys */ + +static int rohm_pwrbutton_register_swnodes(const struct software_node *nodes) +{ + const struct software_node * const node_group[] = { + &nodes[GPIO_KEYS], &nodes[PWRON_KEY], NULL + }; + + return software_node_register_node_group(node_group); +} + +static void rohm_pwrbutton_unregister_swnodes(void *data) +{ + const struct software_node *nodes = data; + const struct software_node * const node_group[] = { + &nodes[GPIO_KEYS], &nodes[PWRON_KEY], NULL + }; + + software_node_unregister_node_group(node_group); +} + +int rohm_register_pwrbutton(struct device *dev, int irq, const char *name, + bool wakeup, struct irq_domain *irq_domain) +{ + const struct resource res[] = { + DEFINE_RES_IRQ_NAMED(irq, name), + }; + struct mfd_cell gpio_keys_cell = { + .name = "gpio-keys", + .resources = res, + .num_resources = ARRAY_SIZE(res), + }; + struct property_entry *parent_props; + struct property_entry *child_props; + struct software_node *nodes; + int n_props; + int ret; + + if (irq <= 0) + return -EINVAL; + + nodes = devm_kcalloc(dev, 2, sizeof(*nodes), GFP_KERNEL); + if (!nodes) + return -ENOMEM; + + nodes[GPIO_KEYS].name = devm_kasprintf(dev, GFP_KERNEL, "%s-power-key", dev_name(dev)); + if (!nodes[GPIO_KEYS].name) + return -ENOMEM; + + parent_props = devm_kcalloc(dev, 2, sizeof(*parent_props), GFP_KERNEL); + if (!parent_props) + return -ENOMEM; + + parent_props[0] = PROPERTY_ENTRY_STRING("label", name); + nodes[GPIO_KEYS].properties = parent_props; + + n_props = 2; /* linux,code and terminator */ + if (wakeup) + n_props++; + + child_props = devm_kcalloc(dev, n_props, sizeof(*child_props), GFP_KERNEL); + if (!child_props) + return -ENOMEM; + + child_props[0] = PROPERTY_ENTRY_U32("linux,code", KEY_POWER); + if (wakeup) + child_props[1] = PROPERTY_ENTRY_BOOL("wakeup-source"); + + nodes[PWRON_KEY].parent = &nodes[GPIO_KEYS]; + nodes[PWRON_KEY].properties = child_props; + + ret = rohm_pwrbutton_register_swnodes(nodes); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, rohm_pwrbutton_unregister_swnodes, nodes); + if (ret) + return ret; + + gpio_keys_cell.swnode = &nodes[GPIO_KEYS]; + + ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, &gpio_keys_cell, 1, + NULL, 0, irq_domain); + if (ret) + return dev_err_probe(dev, ret, "Failed to register power-button"); + + return 0; +} +EXPORT_SYMBOL_GPL(rohm_register_pwrbutton); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Dmitry Torokhov "); +MODULE_DESCRIPTION("Shared helper for ROHM PMIC power button registration"); diff --git a/drivers/mfd/rohm-pwrbutton.h b/drivers/mfd/rohm-pwrbutton.h new file mode 100644 index 000000000000..47ae5c9d1e90 --- /dev/null +++ b/drivers/mfd/rohm-pwrbutton.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef __LINUX_MFD_ROHM_PWRBUTTON_H__ +#define __LINUX_MFD_ROHM_PWRBUTTON_H__ + +struct device; +struct irq_domain; + +int rohm_register_pwrbutton(struct device *dev, int irq, const char *name, + bool wakeup, struct irq_domain *irq_domain); + +#endif /* __LINUX_MFD_ROHM_PWRBUTTON_H__ */ From 412ce33fadd94c0b9a0588959b463ab9d9b32d28 Mon Sep 17 00:00:00 2001 From: Mukesh Ojha Date: Sat, 11 Jul 2026 00:57:37 +0530 Subject: [PATCH 0333/1328] dt-bindings: mfd: qcom,tcsr: Add compatible for Hawi and Maili SoCs Document Top Control and Status Register (TCSR) controller for Qualcomm Hawi and Maili SoCs. Signed-off-by: Mukesh Ojha Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260710192737.1689453-1-mukesh.ojha@oss.qualcomm.com Signed-off-by: Lee Jones --- Documentation/devicetree/bindings/mfd/qcom,tcsr.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/mfd/qcom,tcsr.yaml b/Documentation/devicetree/bindings/mfd/qcom,tcsr.yaml index fba5ff5283b1..1297f2ba914f 100644 --- a/Documentation/devicetree/bindings/mfd/qcom,tcsr.yaml +++ b/Documentation/devicetree/bindings/mfd/qcom,tcsr.yaml @@ -17,6 +17,8 @@ properties: compatible: items: - enum: + - qcom,hawi-tcsr + - qcom,maili-tcsr - qcom,msm8976-tcsr - qcom,msm8998-tcsr - qcom,nord-tcsr From 1ae94ed761a4300df4d947f0f9b5babc3c0a2cb9 Mon Sep 17 00:00:00 2001 From: Roman Vivchar Date: Thu, 9 Jul 2026 13:52:49 +0300 Subject: [PATCH 0334/1328] mfd: mt6397-core: Add mt6323 AUXADC support The mt6323 PMIC includes an AUXADC. Register the AUXADC in the mt6323 devices array to allow the corresponding driver to probe using compatible string. Signed-off-by: Roman Vivchar Tested-by: Ben Grisdale # Amazon Echo Dot (2nd Generation) Reviewed-by: David Lechner Link: https://patch.msgid.link/20260709-mt6323-adc-v5-3-d11b8332a735@protonmail.com Signed-off-by: Lee Jones --- drivers/mfd/mt6397-core.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/mfd/mt6397-core.c b/drivers/mfd/mt6397-core.c index ea1d039477e3..9ec951996588 100644 --- a/drivers/mfd/mt6397-core.c +++ b/drivers/mfd/mt6397-core.c @@ -125,6 +125,9 @@ static const struct resource mt6323_pwrc_resources[] = { static const struct mfd_cell mt6323_devs[] = { { + .name = "mt6323-auxadc", + .of_compatible = "mediatek,mt6323-auxadc", + }, { .name = "mt6323-efuse", .of_compatible = "mediatek,mt6323-efuse", }, { From 2970c2db8ab3d97ea6250c14ca49b1e1731115ab Mon Sep 17 00:00:00 2001 From: Thomas Richard Date: Mon, 13 Jul 2026 16:43:43 +0200 Subject: [PATCH 0335/1328] mfd: cgbc: Fix teardown ordering in cgbc_remove() Release Board Controller session once children are removed by the core. Cc: stable@vger.kernel.org Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/cover.1783507945.git.u.kleine-koenig%40baylibre.com?part=19 Fixes: 6f1067cfbee7 ("mfd: Add Congatec Board Controller driver") Signed-off-by: Thomas Richard Link: https://patch.msgid.link/20260713-cgbc-core-fix-cgbc-remove-v1-1-79274ad62b3a@bootlin.com Signed-off-by: Lee Jones --- drivers/mfd/cgbc-core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/mfd/cgbc-core.c b/drivers/mfd/cgbc-core.c index 10bb4b414c34..2becaf797646 100644 --- a/drivers/mfd/cgbc-core.c +++ b/drivers/mfd/cgbc-core.c @@ -364,9 +364,9 @@ static void cgbc_remove(struct platform_device *pdev) { struct cgbc_device_data *cgbc = platform_get_drvdata(pdev); - cgbc_session_release(cgbc); - mfd_remove_devices(&pdev->dev); + + cgbc_session_release(cgbc); } static struct platform_driver cgbc_driver = { From a764e2a617592e5b0cb7646d81973ad765569b3a Mon Sep 17 00:00:00 2001 From: Eduard Bostina Date: Wed, 8 Jul 2026 12:33:28 +0000 Subject: [PATCH 0336/1328] dt-bindings: mfd: Convert OMAP USB TLL to DT schema Convert the OMAP HS USB Host TLL bindings to DT schema. During the conversion, ti,hwmods has been made optional to resolve dtbs_check warnings. Modern OMAP platforms do not require this property, but it is still required for older platforms. Signed-off-by: Eduard Bostina Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260708123328.1768794-1-egbostina@gmail.com Signed-off-by: Lee Jones --- .../devicetree/bindings/mfd/omap-usb-tll.txt | 27 ---------- .../devicetree/bindings/mfd/ti,usbhs-tll.yaml | 53 +++++++++++++++++++ 2 files changed, 53 insertions(+), 27 deletions(-) delete mode 100644 Documentation/devicetree/bindings/mfd/omap-usb-tll.txt create mode 100644 Documentation/devicetree/bindings/mfd/ti,usbhs-tll.yaml diff --git a/Documentation/devicetree/bindings/mfd/omap-usb-tll.txt b/Documentation/devicetree/bindings/mfd/omap-usb-tll.txt deleted file mode 100644 index c58d70437fce..000000000000 --- a/Documentation/devicetree/bindings/mfd/omap-usb-tll.txt +++ /dev/null @@ -1,27 +0,0 @@ -OMAP HS USB Host TLL (Transceiver-Less Interface) - -Required properties: - -- compatible : should be "ti,usbhs-tll" -- reg : should contain one register range i.e. start and length -- interrupts : should contain the TLL module's interrupt -- ti,hwmod : must contain "usb_tll_hs" - -Optional properties: - -- clocks: a list of phandles and clock-specifier pairs, one for each entry in - clock-names. - -- clock-names: should include: - * "usb_tll_hs_usb_ch0_clk" - USB TLL channel 0 clock - * "usb_tll_hs_usb_ch1_clk" - USB TLL channel 1 clock - * "usb_tll_hs_usb_ch2_clk" - USB TLL channel 2 clock - -Example: - - usbhstll: usbhstll@4a062000 { - compatible = "ti,usbhs-tll"; - reg = <0x4a062000 0x1000>; - interrupts = <78>; - ti,hwmods = "usb_tll_hs"; - }; diff --git a/Documentation/devicetree/bindings/mfd/ti,usbhs-tll.yaml b/Documentation/devicetree/bindings/mfd/ti,usbhs-tll.yaml new file mode 100644 index 000000000000..78f7a109392c --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/ti,usbhs-tll.yaml @@ -0,0 +1,53 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mfd/ti,usbhs-tll.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: OMAP HS USB Host TLL (Transceiver-Less Interface) + +maintainers: + - Eduard Bostina + +properties: + compatible: + const: ti,usbhs-tll + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + ti,hwmods: + description: Name of the hwmod associated with the USB TLL. + $ref: /schemas/types.yaml#/definitions/string + const: usb_tll_hs + + clocks: + minItems: 1 + maxItems: 3 + description: A list of phandles and clock-specifier pairs. + + clock-names: + minItems: 1 + items: + - const: usb_tll_hs_usb_ch0_clk + - const: usb_tll_hs_usb_ch1_clk + - const: usb_tll_hs_usb_ch2_clk + +required: + - compatible + - reg + - interrupts + +additionalProperties: false + +examples: + - | + usb-tll@4a062000 { + compatible = "ti,usbhs-tll"; + reg = <0x4a062000 0x1000>; + interrupts = <78>; + ti,hwmods = "usb_tll_hs"; + }; From b1eb3de64d02cec71b419b9918d1697797fa156d Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 15 Jul 2026 21:16:03 +0200 Subject: [PATCH 0337/1328] mfd: si476x-i2c: Get rid of duplicate NULL checks GPIO descriptor APIs are NULL-aware and since the requested line is optional we don't need to have an additional check each time we want to toggle GPIO. Get rid of duplicate NULL checks. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260715191603.1325479-1-andriy.shevchenko@linux.intel.com Signed-off-by: Lee Jones --- drivers/mfd/si476x-i2c.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/drivers/mfd/si476x-i2c.c b/drivers/mfd/si476x-i2c.c index 55700ce711f4..9bce720a0a08 100644 --- a/drivers/mfd/si476x-i2c.c +++ b/drivers/mfd/si476x-i2c.c @@ -130,8 +130,7 @@ int si476x_core_start(struct si476x_core *core, bool soft) int err; if (!soft) { - if (core->reset) - gpiod_set_value_cansleep(core->reset, 0); + gpiod_set_value_cansleep(core->reset, 0); if (client->irq) enable_irq(client->irq); @@ -197,8 +196,7 @@ int si476x_core_start(struct si476x_core *core, bool soft) else cancel_delayed_work_sync(&core->status_monitor); - if (core->reset) - gpiod_set_value_cansleep(core->reset, 1); + gpiod_set_value_cansleep(core->reset, 1); return err; } @@ -242,10 +240,9 @@ int si476x_core_stop(struct si476x_core *core, bool soft) else cancel_delayed_work_sync(&core->status_monitor); - if (!soft) { - if (core->reset) - gpiod_set_value_cansleep(core->reset, 1); - } + if (!soft) + gpiod_set_value_cansleep(core->reset, 1); + return err; } EXPORT_SYMBOL_GPL(si476x_core_stop); From 1c5f27e845e84f58ed6bbe3e6bc12d6a013e74b5 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 30 Jul 2026 02:30:58 -0700 Subject: [PATCH 0338/1328] apparmor: Fix build failure when ZSTD_DECOMPRESS is not enabled commit 17b5758bf35c ("apparmor: Initial support for compressed policies") added the ability for apparmor to load compressed policy, unfortunately it did not add a config option or select CONFIG_ZSTD_DECOMPRESS which it depends on, leading to the following build failure apparmorfs.c makes calls into zstd_*() even when CONFIG_SECURITY_APPARMOR_EXPORT_BINARY is not set, causing build errors: /usr/bin/ld.bfd: security/apparmor/apparmorfs.o: in function `policy_update': apparmorfs.c:(.text+0x1307): undefined reference to `zstd_get_frame_header' /usr/bin/ld.bfd: apparmorfs.c:(.text+0x1359): undefined reference to `zstd_dctx_workspace_bound' /usr/bin/ld.bfd: apparmorfs.c:(.text+0x13f7): undefined reference to `zstd_init_dctx' /usr/bin/ld.bfd: apparmorfs.c:(.text+0x140c): undefined reference to `zstd_decompress_dctx' /usr/bin/ld.bfd: apparmorfs.c:(.text+0x1411): undefined reference to `zstd_is_error' Add a new config option to enable compress policy loading as using the existing CONFIG_SECURITY_APPARMOR_EXPORT_BINARY is in appropriate as that is about retaining loaded policy so that it can be introspected at a later date. Fixes: 17b5758bf35c ("apparmor: Initial support for compressed policies") Reviewed-by: Georgia Garcia Signed-off-by: John Johansen --- security/apparmor/Kconfig | 12 ++++++++++++ security/apparmor/apparmorfs.c | 15 ++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig index e1bfd164a23a..a1f3749bdfd0 100644 --- a/security/apparmor/Kconfig +++ b/security/apparmor/Kconfig @@ -93,6 +93,18 @@ config SECURITY_APPARMOR_EXPORT_BINARY also increases policy load time. This option is required for checkpoint and restore support, and debugging of loaded policy. +config SECURITY_APPARMOR_COMPRESSED_POLICY + bool "Allow loading policy in a compressed format" + depends on SECURITY_APPARMOR + select ZSTD_DECOMPRESS + default y + help + This option allows loading policy from userspace in a + compressed format. This allows for userspace to not have to + decrompress caches before loading policy, and also allows + for less kernel memory to be used when "exporting the raw + binary policy" is enabled. + config SECURITY_APPARMOR_PARANOID_LOAD bool "Perform full verification of loaded policy" depends on SECURITY_APPARMOR diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 79b91c680e46..4309555b5541 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -484,6 +484,7 @@ static struct aa_loaddata *aa_simple_write_to_buffer(const char __user *userbuf, return data; } +#ifdef CONFIG_SECURITY_APPARMOR_COMPRESSED_POLICY static int decompress_zstd(char *src, size_t slen, char *dst, size_t dlen) { if (slen < dlen) { @@ -581,7 +582,15 @@ static struct aa_loaddata *aa_get_data_from_compressed(const char __user *userbu return ERR_PTR(error); } - +#else +static struct aa_loaddata *aa_get_data_from_compressed(const char __user *userbuf __always_unused, + size_t buffer_size __always_unused, + loff_t *pos __always_unused, + char **compressed_data __always_unused) +{ + return ERR_PTR(-EINVAL); +} +#endif /* CONFIG_SECURITY_APPARMOR_COMPRESSED_POLICY */ struct aa_user_hdr { uint8_t version; uint8_t compress_level; @@ -2604,8 +2613,10 @@ static struct aa_sfs_entry aa_sfs_entry_policy[] = { AA_SFS_FILE_STRING("permstable32", PERMS32STR), AA_SFS_FILE_U64("state32", 1), AA_SFS_DIR("unconfined_restrictions", aa_sfs_entry_unconfined), +#ifdef CONFIG_SECURITY_APPARMOR_COMPRESSED_POLICY AA_SFS_FILE_BOOLEAN("compressed_load", 1), AA_SFS_FILE_BOOLEAN("extended_policy_header", 1), +#endif { } }; @@ -2670,8 +2681,10 @@ static struct aa_sfs_entry aa_sfs_entry_apparmor[] = { AA_SFS_FILE_FOPS(".ns_level", 0444, &seq_ns_level_fops), AA_SFS_FILE_FOPS(".ns_name", 0444, &seq_ns_name_fops), AA_SFS_FILE_FOPS("profiles", 0444, &aa_sfs_profiles_fops), +#ifdef CONFIG_SECURITY_APPARMOR_COMPRESSED_POLICY AA_SFS_FILE_FOPS("raw_data_compression_level_min", 0444, &seq_ns_compress_min_fops), AA_SFS_FILE_FOPS("raw_data_compression_level_max", 0444, &seq_ns_compress_max_fops), +#endif AA_SFS_DIR("features", aa_sfs_entry_features), { } }; From 884dfb2dfe2cde33b8efe91ebf91542c88ca9034 Mon Sep 17 00:00:00 2001 From: Richard Zhu Date: Thu, 30 Jul 2026 16:55:42 +0800 Subject: [PATCH 0339/1328] clk: imx95-blk-ctl: Add func_out_en clock for i.MX9x PCIe Add a func_out_en clock for i.MX9x PCIe to serve as the parent gate clock of the CREF_EN (BIT6) gate clock. Both of these two gate clocks enable the output of the internal 100MHz differential reference clock. When the internal PLL clock is used as the PCIe reference clock, both BIT6 (CREF_EN) and BIT2 (FUNC_OUTPUT_EN) control the PCIE_REF_OUT_CLK. If these bits default to 1, the output clock is enabled. With typical 100-ohm termination on the board, this results in approximately 6mA of unnecessary power consumption when the PCIe internal PLL clock is not in use. To eliminate this power consumption, add a func_out_en clock gate that serves as the parent of the existing CREF_EN (BIT6) gate clock. Both gates must be enabled to output the internal 100MHz differential reference clock, and both will be disabled when the clock is not needed. Signed-off-by: Richard Zhu Reviewed-by: Peng Fan Link: https://patch.msgid.link/20260730085542.263025-1-hongxing.zhu@oss.nxp.com Signed-off-by: Abel Vesa --- drivers/clk/imx/clk-imx95-blk-ctl.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/drivers/clk/imx/clk-imx95-blk-ctl.c b/drivers/clk/imx/clk-imx95-blk-ctl.c index 56bed4471995..1f9259f45607 100644 --- a/drivers/clk/imx/clk-imx95-blk-ctl.c +++ b/drivers/clk/imx/clk-imx95-blk-ctl.c @@ -286,18 +286,28 @@ static const struct imx95_blk_ctl_dev_data netcmix_dev_data = { static const struct imx95_blk_ctl_clk_dev_data hsio_blk_ctl_clk_dev_data[] = { [0] = { .name = "hsio_blk_ctl_clk", - .parent_names = (const char *[]){ "hsio_pll", }, + .parent_names = (const char *[]){ "func_out_en", }, .num_parents = 1, .reg = 0, .bit_idx = 6, .bit_width = 1, .type = CLK_GATE, .flags = CLK_SET_RATE_PARENT, + }, + [1] = { + .name = "func_out_en", + .parent_names = (const char *[]){ "hsio_pll", }, + .num_parents = 1, + .reg = 0, + .bit_idx = 2, + .bit_width = 1, + .type = CLK_GATE, + .flags = CLK_SET_RATE_PARENT, } }; static const struct imx95_blk_ctl_dev_data hsio_blk_ctl_dev_data = { - .num_clks = 1, + .num_clks = ARRAY_SIZE(hsio_blk_ctl_clk_dev_data), .clk_dev_data = hsio_blk_ctl_clk_dev_data, .clk_reg_offset = 0, }; From 39ec460b56b26319d1f31b459e8be7ea34ae9c67 Mon Sep 17 00:00:00 2001 From: Richard Zhu Date: Thu, 30 Jul 2026 17:04:47 +0800 Subject: [PATCH 0340/1328] clk: imx95-blk-ctl: Fix REFCLK rise-fall mismatch on i.MX95 When the internal PLL is used as the PCIe reference clock source on i.MX95, a REFCLK rise-fall time mismatch is observed during PCIe Gen1 compliance testing with the Lfast IO analyzer. Fix this issue by configuring the IREF_TX field to 0xF (15), which adjusts the transmitter current reference to meet the PCIe specification timing requirements. Signed-off-by: Richard Zhu Reviewed-by: Peng Fan Link: https://patch.msgid.link/20260730090447.271109-1-hongxing.zhu@oss.nxp.com Signed-off-by: Abel Vesa --- drivers/clk/imx/clk-imx95-blk-ctl.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/clk/imx/clk-imx95-blk-ctl.c b/drivers/clk/imx/clk-imx95-blk-ctl.c index 1f9259f45607..bc6957299cec 100644 --- a/drivers/clk/imx/clk-imx95-blk-ctl.c +++ b/drivers/clk/imx/clk-imx95-blk-ctl.c @@ -44,6 +44,8 @@ struct imx95_blk_ctl_clk_dev_data { const char * const *parent_names; u32 num_parents; u32 reg; + u32 reg_init_msk; + u32 reg_init_val; u32 bit_idx; u32 bit_width; u32 clk_type; @@ -289,6 +291,8 @@ static const struct imx95_blk_ctl_clk_dev_data hsio_blk_ctl_clk_dev_data[] = { .parent_names = (const char *[]){ "func_out_en", }, .num_parents = 1, .reg = 0, + .reg_init_msk = GENMASK(10, 7), + .reg_init_val = GENMASK(10, 7), .bit_idx = 6, .bit_width = 1, .type = CLK_GATE, @@ -410,6 +414,9 @@ static int imx95_bc_probe(struct platform_device *pdev) const struct imx95_blk_ctl_clk_dev_data *data = &bc->pdata->clk_dev_data[i]; void __iomem *reg = base + data->reg; + if (data->reg_init_msk) + writel((readl(reg) & ~data->reg_init_msk) | data->reg_init_val, reg); + if (data->type == CLK_MUX) { hws[i] = clk_hw_register_mux(dev, data->name, data->parent_names, data->num_parents, data->flags, reg, From 9c4cee964e0ccc155e4ab8fa6cec88fffc262c63 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Wed, 22 Jul 2026 15:46:08 +0300 Subject: [PATCH 0341/1328] clk: qcom: gcc-qcm2290: don't park QUP RCGs upon registration The gcc_qupv3_wrap0_s[0-5]_clk_src RCGs feed the QUP serial engines (UART/I2C/SPI). Since shared RCGs are parked to XO at registration time, binding the gcc-qcm2290 driver reprograms these clocks away from the rate configured by the bootloader. For the UART used as the boot console this drops early console output until the serial driver later reconfigures the clock. Switch the QUP wrap0 clock sources over to clk_rcg2_shared_no_init_park_ops so their frequency is left unchanged at registration time, keeping the bootloader-configured console working across the gcc driver probe. Fixes: 01a0a6cc8cfd ("clk: qcom: Park shared RCGs upon registration") Signed-off-by: Dmitry Baryshkov Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260722-agatti-no-park-v1-1-31ae3a4774e5@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gcc-qcm2290.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/clk/qcom/gcc-qcm2290.c b/drivers/clk/qcom/gcc-qcm2290.c index 6684cab63ae1..02ca37f58477 100644 --- a/drivers/clk/qcom/gcc-qcm2290.c +++ b/drivers/clk/qcom/gcc-qcm2290.c @@ -1082,7 +1082,7 @@ static struct clk_init_data gcc_qupv3_wrap0_s0_clk_src_init = { .name = "gcc_qupv3_wrap0_s0_clk_src", .parent_data = gcc_parents_1, .num_parents = ARRAY_SIZE(gcc_parents_1), - .ops = &clk_rcg2_shared_ops, + .ops = &clk_rcg2_shared_no_init_park_ops, }; static struct clk_rcg2 gcc_qupv3_wrap0_s0_clk_src = { @@ -1098,7 +1098,7 @@ static struct clk_init_data gcc_qupv3_wrap0_s1_clk_src_init = { .name = "gcc_qupv3_wrap0_s1_clk_src", .parent_data = gcc_parents_1, .num_parents = ARRAY_SIZE(gcc_parents_1), - .ops = &clk_rcg2_shared_ops, + .ops = &clk_rcg2_shared_no_init_park_ops, }; static struct clk_rcg2 gcc_qupv3_wrap0_s1_clk_src = { @@ -1114,7 +1114,7 @@ static struct clk_init_data gcc_qupv3_wrap0_s2_clk_src_init = { .name = "gcc_qupv3_wrap0_s2_clk_src", .parent_data = gcc_parents_1, .num_parents = ARRAY_SIZE(gcc_parents_1), - .ops = &clk_rcg2_shared_ops, + .ops = &clk_rcg2_shared_no_init_park_ops, }; static struct clk_rcg2 gcc_qupv3_wrap0_s2_clk_src = { @@ -1130,7 +1130,7 @@ static struct clk_init_data gcc_qupv3_wrap0_s3_clk_src_init = { .name = "gcc_qupv3_wrap0_s3_clk_src", .parent_data = gcc_parents_1, .num_parents = ARRAY_SIZE(gcc_parents_1), - .ops = &clk_rcg2_shared_ops, + .ops = &clk_rcg2_shared_no_init_park_ops, }; static struct clk_rcg2 gcc_qupv3_wrap0_s3_clk_src = { @@ -1146,7 +1146,7 @@ static struct clk_init_data gcc_qupv3_wrap0_s4_clk_src_init = { .name = "gcc_qupv3_wrap0_s4_clk_src", .parent_data = gcc_parents_1, .num_parents = ARRAY_SIZE(gcc_parents_1), - .ops = &clk_rcg2_shared_ops, + .ops = &clk_rcg2_shared_no_init_park_ops, }; static struct clk_rcg2 gcc_qupv3_wrap0_s4_clk_src = { @@ -1162,7 +1162,7 @@ static struct clk_init_data gcc_qupv3_wrap0_s5_clk_src_init = { .name = "gcc_qupv3_wrap0_s5_clk_src", .parent_data = gcc_parents_1, .num_parents = ARRAY_SIZE(gcc_parents_1), - .ops = &clk_rcg2_shared_ops, + .ops = &clk_rcg2_shared_no_init_park_ops, }; static struct clk_rcg2 gcc_qupv3_wrap0_s5_clk_src = { From 60276a9469a1df6c86666ecc21fa8ad10d90e809 Mon Sep 17 00:00:00 2001 From: Imran Shaik Date: Wed, 22 Jul 2026 10:11:14 +0530 Subject: [PATCH 0342/1328] clk: qcom: gcc-shikra: Add additional frequencies for EMAC RGMII clocks Add support for 2.5MHz and 25MHz GCC EMAC RGMII clock frequencies required for EMAC 10M and 100M speeds respectively on Qualcomm Shikra SoC. Tested-by: Mohd Ayaan Anwar Reviewed-by: Konrad Dybcio Reviewed-by: Taniya Das Signed-off-by: Imran Shaik Link: https://lore.kernel.org/r/20260722-b4-shikra-gcc-emac-freqs-v2-1-2e480e2de8de@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gcc-shikra.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/clk/qcom/gcc-shikra.c b/drivers/clk/qcom/gcc-shikra.c index d5222756f214..f2ab50209e1c 100644 --- a/drivers/clk/qcom/gcc-shikra.c +++ b/drivers/clk/qcom/gcc-shikra.c @@ -1244,6 +1244,8 @@ static struct clk_rcg2 gcc_emac0_ptp_clk_src = { }; static const struct freq_tbl ftbl_gcc_emac0_rgmii_clk_src[] = { + F(2500000, P_GPLL0_OUT_AUX2, 10, 1, 12), + F(25000000, P_GPLL0_OUT_AUX2, 12, 0, 0), F(50000000, P_GPLL0_OUT_AUX2, 6, 0, 0), F(125000000, P_GPLL12_OUT_AUX2, 4, 0, 0), F(250000000, P_GPLL12_OUT_EARLY, 4, 0, 0), From c5339edc6abb601ea10df910ea0b1592fa1016f3 Mon Sep 17 00:00:00 2001 From: Stephan Gerhold Date: Mon, 6 Jul 2026 17:02:08 +0200 Subject: [PATCH 0343/1328] clk: qcom: gcc-msm8916: Fix enable_reg for gcc_blsp1_sleep_clk According to the APQ8016E TRM, the GCC_BLSP1_SLEEP_CBCR register is read-only and only has the CLK_OFF bit to check if the clock is running. This is a shared vote clock, the correct way to enable it is to vote for BLSP1_SLEEP_CLK_ENA (BIT(9)) in GCC_APCS_CLOCK_BRANCH_ENA_VOTE (0x45004). Cc: stable@vger.kernel.org Fixes: 3966fab8b6ab ("clk: qcom: Add MSM8916 Global Clock Controller support") Reviewed-by: Konrad Dybcio Signed-off-by: Stephan Gerhold Link: https://lore.kernel.org/r/20260706-qcom-clk-mdm9607-fixes-v2-1-745565101869@linaro.org Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gcc-msm8916.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/clk/qcom/gcc-msm8916.c b/drivers/clk/qcom/gcc-msm8916.c index 9c7c6b23ce32..e91406e6f527 100644 --- a/drivers/clk/qcom/gcc-msm8916.c +++ b/drivers/clk/qcom/gcc-msm8916.c @@ -1589,9 +1589,10 @@ static struct clk_branch gcc_blsp1_ahb_clk = { static struct clk_branch gcc_blsp1_sleep_clk = { .halt_reg = 0x01004, + .halt_check = BRANCH_HALT_VOTED, .clkr = { - .enable_reg = 0x01004, - .enable_mask = BIT(0), + .enable_reg = 0x45004, + .enable_mask = BIT(9), .hw.init = &(struct clk_init_data){ .name = "gcc_blsp1_sleep_clk", .parent_data = &(const struct clk_parent_data){ From fc611445b021262b0d4ace6f716a360663816287 Mon Sep 17 00:00:00 2001 From: Stephan Gerhold Date: Mon, 6 Jul 2026 17:02:09 +0200 Subject: [PATCH 0344/1328] clk: qcom: gcc-msm8939: Fix enable_reg for gcc_blsp1_sleep_clk MSM8939 is similar to MSM8916, where the GCC_BLSP1_SLEEP_CBCR register is read-only and only has the CLK_OFF bit to check if the clock is running. This is a shared vote clock, the correct way to enable it is to vote for BLSP1_SLEEP_CLK_ENA (BIT(9)) in GCC_APCS_CLOCK_BRANCH_ENA_VOTE (0x45004). Cc: stable@vger.kernel.org Fixes: 1664014e4679 ("clk: qcom: gcc-msm8939: Add MSM8939 Generic Clock Controller") Reviewed-by: Konrad Dybcio Signed-off-by: Stephan Gerhold Link: https://lore.kernel.org/r/20260706-qcom-clk-mdm9607-fixes-v2-2-745565101869@linaro.org Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gcc-msm8939.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/clk/qcom/gcc-msm8939.c b/drivers/clk/qcom/gcc-msm8939.c index 45193b3d714b..ffd7f14fcbaf 100644 --- a/drivers/clk/qcom/gcc-msm8939.c +++ b/drivers/clk/qcom/gcc-msm8939.c @@ -1929,9 +1929,10 @@ static struct clk_branch gcc_blsp1_ahb_clk = { static struct clk_branch gcc_blsp1_sleep_clk = { .halt_reg = 0x01004, + .halt_check = BRANCH_HALT_VOTED, .clkr = { - .enable_reg = 0x01004, - .enable_mask = BIT(0), + .enable_reg = 0x45004, + .enable_mask = BIT(9), .hw.init = &(struct clk_init_data){ .name = "gcc_blsp1_sleep_clk", .ops = &clk_branch2_ops, From 944d0fb38cffe57a1d1ebf82c5c077bad82dcdbb Mon Sep 17 00:00:00 2001 From: Stephan Gerhold Date: Mon, 6 Jul 2026 17:02:10 +0200 Subject: [PATCH 0345/1328] clk: qcom: gcc-mdm9607: Fix enable_reg for gcc_blsp1_sleep_clk MDM9607 is similar to MSM8909, where the GCC_BLSP1_SLEEP_CBCR register is read-only and only has the CLK_OFF bit to check if the clock is running. This is a shared vote clock, the correct way to enable it is to vote for BLSP1_SLEEP_CLK_ENA (BIT(9)) in GCC_APCS_CLOCK_BRANCH_ENA_VOTE (0x45004). Cc: stable@vger.kernel.org Fixes: 48b7253264ea ("clk: qcom: Add MDM9607 GCC driver") Reviewed-by: Konrad Dybcio Signed-off-by: Stephan Gerhold Link: https://lore.kernel.org/r/20260706-qcom-clk-mdm9607-fixes-v2-3-745565101869@linaro.org Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gcc-mdm9607.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/clk/qcom/gcc-mdm9607.c b/drivers/clk/qcom/gcc-mdm9607.c index 07f1b78d737a..499e0fbbfab9 100644 --- a/drivers/clk/qcom/gcc-mdm9607.c +++ b/drivers/clk/qcom/gcc-mdm9607.c @@ -790,9 +790,10 @@ static struct clk_branch gcc_blsp1_ahb_clk = { static struct clk_branch gcc_blsp1_sleep_clk = { .halt_reg = 0x1004, + .halt_check = BRANCH_HALT_VOTED, .clkr = { - .enable_reg = 0x1004, - .enable_mask = BIT(0), + .enable_reg = 0x45004, + .enable_mask = BIT(9), .hw.init = &(struct clk_init_data){ .name = "gcc_blsp1_sleep_clk", .parent_data = &(const struct clk_parent_data){ From ca7e6cc30cde4f0cbeff2e205a84bedf431e9156 Mon Sep 17 00:00:00 2001 From: Stephan Gerhold Date: Mon, 6 Jul 2026 17:02:11 +0200 Subject: [PATCH 0346/1328] clk: qcom: gcc-mdm9607: Fix halt_reg for gcc_apss_axi_clk gcc_apss_axi_clk specifies a halt_reg of 0x4601c, but this is already used by gcc_apss_ahb_clk. The correct value according to the downstream driver is 0x46020. Cc: stable@vger.kernel.org Fixes: 48b7253264ea ("clk: qcom: Add MDM9607 GCC driver") Reviewed-by: Taniya Das Reviewed-by: Konrad Dybcio Signed-off-by: Stephan Gerhold Link: https://lore.kernel.org/r/20260706-qcom-clk-mdm9607-fixes-v2-4-745565101869@linaro.org Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gcc-mdm9607.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/qcom/gcc-mdm9607.c b/drivers/clk/qcom/gcc-mdm9607.c index 499e0fbbfab9..feb91e1fca1f 100644 --- a/drivers/clk/qcom/gcc-mdm9607.c +++ b/drivers/clk/qcom/gcc-mdm9607.c @@ -1460,7 +1460,7 @@ static struct clk_branch gcc_apss_ahb_clk = { }; static struct clk_branch gcc_apss_axi_clk = { - .halt_reg = 0x4601c, + .halt_reg = 0x46020, .halt_check = BRANCH_HALT_VOTED, .clkr = { .enable_reg = 0x45004, From c99bc8e83848358bd4a0436e4bdae5b7919babb2 Mon Sep 17 00:00:00 2001 From: Stephan Gerhold Date: Mon, 6 Jul 2026 17:02:12 +0200 Subject: [PATCH 0347/1328] clk: qcom: gcc-mdm9607: Increase delay for USB PHY reset To conform to the specifications of the USB PHY, the reset signal should be asserted for at least 10us. Guarantee that by increasing the delay for the USB2_HS_PHY_ONLY_BCR reset control similar to commit dcc6c9fb7128 ("clk: qcom: gcc-msm8909: Increase delay for USB PHY reset"). Cc: stable@vger.kernel.org Fixes: 48b7253264ea ("clk: qcom: Add MDM9607 GCC driver") Reviewed-by: Konrad Dybcio Signed-off-by: Stephan Gerhold Link: https://lore.kernel.org/r/20260706-qcom-clk-mdm9607-fixes-v2-5-745565101869@linaro.org Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gcc-mdm9607.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/qcom/gcc-mdm9607.c b/drivers/clk/qcom/gcc-mdm9607.c index feb91e1fca1f..092076a5a0c9 100644 --- a/drivers/clk/qcom/gcc-mdm9607.c +++ b/drivers/clk/qcom/gcc-mdm9607.c @@ -1568,7 +1568,7 @@ static const struct qcom_reset_map gcc_mdm9607_resets[] = { [USB_HS_HSIC_BCR] = { 0x3d05c }, [GCC_MSS_RESTART] = { 0x3e000 }, [USB_HS_BCR] = { 0x41000 }, - [USB2_HS_PHY_ONLY_BCR] = { 0x41034 }, + [USB2_HS_PHY_ONLY_BCR] = { .reg = 0x41034, .udelay = 15 }, [QUSB2_PHY_BCR] = { 0x4103c }, }; From 38d06956f60675f906dc3f5b70b3b52103b86a7d Mon Sep 17 00:00:00 2001 From: Stephan Gerhold Date: Mon, 6 Jul 2026 17:02:13 +0200 Subject: [PATCH 0348/1328] clk: qcom: gcc-mdm9607: Drop incorrect apss_tcu_clk_src This clock does not exist on the specified address on MDM9607. Reading/writing the registers always results in 0 or crashes. The math in the frequency table is also broken. GPLL2 on MDM9607 runs at 480 MHz, so: - F(155000000, P_GPLL2, 6, 0, 0), // 480 MHz/6 = 80 MHz, not 155 MHz - F(310000000, P_GPLL2, 3, 0, 0), // 480 MHz/3 = 160 MHz, not 310 MHz Presumably, this definition was mistakenly copied as-is from gcc-msm8916 (which uses 930 MHz for GPLL2). There are no branch consumers of this root clock inside gcc-mdm9607 (notably, gcc_apss_tcu_clk has bimc_ddr_clk_src as parent instead of this clock), so we can just drop it. It seems like this clock does exist on this SoC on a different address, but since there is no user and reference code for it, it is still better to drop it. Cc: stable@vger.kernel.org Fixes: 48b7253264ea ("clk: qcom: Add MDM9607 GCC driver") Reviewed-by: Konrad Dybcio Signed-off-by: Stephan Gerhold Link: https://lore.kernel.org/r/20260706-qcom-clk-mdm9607-fixes-v2-6-745565101869@linaro.org Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gcc-mdm9607.c | 35 ---------------------------------- 1 file changed, 35 deletions(-) diff --git a/drivers/clk/qcom/gcc-mdm9607.c b/drivers/clk/qcom/gcc-mdm9607.c index 092076a5a0c9..f9ed655513ef 100644 --- a/drivers/clk/qcom/gcc-mdm9607.c +++ b/drivers/clk/qcom/gcc-mdm9607.c @@ -158,20 +158,6 @@ static const struct clk_parent_data gcc_xo_gpll0_gpll2[] = { { .hw = &gpll2.clkr.hw }, }; -static const struct parent_map gcc_xo_gpll0_gpll1_gpll2_map[] = { - { P_XO, 0 }, - { P_GPLL0, 1 }, - { P_GPLL1, 2 }, - { P_GPLL2, 3 }, -}; - -static const struct clk_parent_data gcc_xo_gpll0_gpll1_gpll2[] = { - { .fw_name = "xo" }, - { .hw = &gpll0.clkr.hw }, - { .hw = &gpll1_vote.hw }, - { .hw = &gpll2.clkr.hw }, -}; - static const struct freq_tbl ftbl_apss_ahb_clk[] = { F(19200000, P_XO, 1, 0, 0), F(50000000, P_GPLL0, 16, 0, 0), @@ -674,26 +660,6 @@ static struct clk_rcg2 sdcc2_apps_clk_src = { }, }; -static const struct freq_tbl ftbl_gcc_apss_tcu_clk[] = { - F(155000000, P_GPLL2, 6, 0, 0), - F(310000000, P_GPLL2, 3, 0, 0), - F(400000000, P_GPLL0, 2, 0, 0), - { } -}; - -static struct clk_rcg2 apss_tcu_clk_src = { - .cmd_rcgr = 0x1207c, - .hid_width = 5, - .parent_map = gcc_xo_gpll0_gpll1_gpll2_map, - .freq_tbl = ftbl_gcc_apss_tcu_clk, - .clkr.hw.init = &(struct clk_init_data){ - .name = "apss_tcu_clk_src", - .parent_data = gcc_xo_gpll0_gpll1_gpll2, - .num_parents = 4, - .ops = &clk_rcg2_ops, - }, -}; - static const struct freq_tbl ftbl_gcc_usb_hs_system_clk[] = { F(19200000, P_XO, 1, 0, 0), F(57140000, P_GPLL0, 14, 0, 0), @@ -1512,7 +1478,6 @@ static struct clk_regmap *gcc_mdm9607_clocks[] = { [PDM2_CLK_SRC] = &pdm2_clk_src.clkr, [SDCC1_APPS_CLK_SRC] = &sdcc1_apps_clk_src.clkr, [SDCC2_APPS_CLK_SRC] = &sdcc2_apps_clk_src.clkr, - [APSS_TCU_CLK_SRC] = &apss_tcu_clk_src.clkr, [USB_HS_SYSTEM_CLK_SRC] = &usb_hs_system_clk_src.clkr, [GCC_BLSP1_AHB_CLK] = &gcc_blsp1_ahb_clk.clkr, [GCC_BLSP1_SLEEP_CLK] = &gcc_blsp1_sleep_clk.clkr, From 17784427df923a0573a7ea83f9198456ff1ec1a5 Mon Sep 17 00:00:00 2001 From: Stephan Gerhold Date: Mon, 6 Jul 2026 17:02:14 +0200 Subject: [PATCH 0349/1328] clk: qcom: gcc-mdm9607: Drop incorrect system_noc_bfdcd_clk_src This clock does not exist on MDM9607. Reading/writing the registers always results in 0. Presumably, this definition was mistakenly copied from gcc-msm8916. On MSM8916, this root clock is used for multimedia subsystems (camera, display, video). MDM9607 has none of that, so this clock was probably omitted in the hardware. There are no users inside gcc-mdm9607, so we can just drop it. Cc: stable@vger.kernel.org Fixes: 48b7253264ea ("clk: qcom: Add MDM9607 GCC driver") Reviewed-by: Konrad Dybcio Signed-off-by: Stephan Gerhold Link: https://lore.kernel.org/r/20260706-qcom-clk-mdm9607-fixes-v2-7-745565101869@linaro.org Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gcc-mdm9607.c | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/drivers/clk/qcom/gcc-mdm9607.c b/drivers/clk/qcom/gcc-mdm9607.c index f9ed655513ef..c6abe93ce2e8 100644 --- a/drivers/clk/qcom/gcc-mdm9607.c +++ b/drivers/clk/qcom/gcc-mdm9607.c @@ -240,18 +240,6 @@ static struct clk_rcg2 pcnoc_bfdcd_clk_src = { }, }; -static struct clk_rcg2 system_noc_bfdcd_clk_src = { - .cmd_rcgr = 0x26004, - .hid_width = 5, - .parent_map = gcc_xo_gpll0_bimc_map, - .clkr.hw.init = &(struct clk_init_data){ - .name = "system_noc_bfdcd_clk_src", - .parent_data = gcc_xo_gpll0_bimc, - .num_parents = ARRAY_SIZE(gcc_xo_gpll0_bimc), - .ops = &clk_rcg2_ops, - }, -}; - static const struct freq_tbl ftbl_gcc_blsp1_qup1_6_i2c_apps_clk[] = { F(19200000, P_XO, 1, 0, 0), F(50000000, P_GPLL0, 16, 0, 0), @@ -1451,7 +1439,6 @@ static struct clk_regmap *gcc_mdm9607_clocks[] = { [BIMC_PLL_VOTE] = &bimc_pll_vote, [BIMC_DDR_CLK_SRC] = &bimc_ddr_clk_src.clkr, [PCNOC_BFDCD_CLK_SRC] = &pcnoc_bfdcd_clk_src.clkr, - [SYSTEM_NOC_BFDCD_CLK_SRC] = &system_noc_bfdcd_clk_src.clkr, [APSS_AHB_CLK_SRC] = &apss_ahb_clk_src.clkr, [BLSP1_QUP1_I2C_APPS_CLK_SRC] = &blsp1_qup1_i2c_apps_clk_src.clkr, [BLSP1_QUP1_SPI_APPS_CLK_SRC] = &blsp1_qup1_spi_apps_clk_src.clkr, From e111ddda092ad961870a634e4c9263c10a3e8485 Mon Sep 17 00:00:00 2001 From: Stephan Gerhold Date: Mon, 6 Jul 2026 17:02:15 +0200 Subject: [PATCH 0350/1328] clk: qcom: gcc-mdm9607: Drop incorrect BIMC PLL and related clocks The gcc-mdm9607 driver was originally based on gcc-msm8916, but a closer match nowadays is gcc-msm8909. Looking at the differences between gcc-mdm9607 and gcc-msm8909, there is quite some confusion around the definitions for the BIMC PLL. It turns out the BIMC PLL on MDM9607 is actually an Alpha PLL just like on MSM8909. We can vote for it using BIT(2), which explains why BIT(3) was used for GPLL2. In practice, the BIMC PLL is exclusively controlled by the RPM firmware and Linux should not touch it. So far, it was defined to model the full clock hierarchy, but even in read-only mode with CLK_GET_RATE_NOCACHE this is problematic since the RPM will silently change the clock parents without notifying Linux about it. The clock framework reads the clock parent only once during boot, so the resulting rates will still be often wrong. Follow the example of more recent SoCs and drop the BIMC PLL and all remaining related clocks to avoid reporting stale status data. This means we cannot determine the rate of these clocks anymore. This is not a big problem in practice, since these are NoC-related clocks that are independently managed through the interconnect subsystem. Cc: stable@vger.kernel.org Fixes: 48b7253264ea ("clk: qcom: Add MDM9607 GCC driver") Signed-off-by: Stephan Gerhold Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260706-qcom-clk-mdm9607-fixes-v2-8-745565101869@linaro.org Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gcc-mdm9607.c | 125 +-------------------------------- 1 file changed, 1 insertion(+), 124 deletions(-) diff --git a/drivers/clk/qcom/gcc-mdm9607.c b/drivers/clk/qcom/gcc-mdm9607.c index c6abe93ce2e8..4b749c99586b 100644 --- a/drivers/clk/qcom/gcc-mdm9607.c +++ b/drivers/clk/qcom/gcc-mdm9607.c @@ -26,7 +26,6 @@ enum { P_XO, - P_BIMC, P_GPLL0, P_GPLL1, P_GPLL2, @@ -121,7 +120,7 @@ static struct clk_alpha_pll gpll2_early = { .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_DEFAULT], .clkr = { .enable_reg = 0x45000, - .enable_mask = BIT(3), /* Yeah, apparently it's not 2 */ + .enable_mask = BIT(3), /* BIT(2) is used for BIMC PLL */ .hw.init = &(struct clk_init_data) { .name = "gpll2_early", @@ -178,68 +177,6 @@ static struct clk_rcg2 apss_ahb_clk_src = { }, }; -static struct clk_pll bimc_pll = { - .l_reg = 0x23004, - .m_reg = 0x23008, - .n_reg = 0x2300c, - .config_reg = 0x23010, - .mode_reg = 0x23000, - .status_reg = 0x2301c, - .status_bit = 17, - .clkr.hw.init = &(struct clk_init_data){ - .name = "bimc_pll", - .parent_data = &(const struct clk_parent_data){ - .fw_name = "xo", - }, - .num_parents = 1, - .ops = &clk_pll_ops, - }, -}; - -static struct clk_regmap bimc_pll_vote = { - .enable_reg = 0x45000, - .enable_mask = BIT(3), - .hw.init = &(struct clk_init_data){ - .name = "bimc_pll_vote", - .parent_hws = (const struct clk_hw *[]){ &bimc_pll.clkr.hw }, - .num_parents = 1, - .ops = &clk_pll_vote_ops, - }, -}; - -static const struct parent_map gcc_xo_gpll0_bimc_map[] = { - { P_XO, 0 }, - { P_GPLL0, 1 }, - { P_BIMC, 2 }, -}; - -static const struct clk_parent_data gcc_xo_gpll0_bimc[] = { - { .fw_name = "xo" }, - { .hw = &gpll0.clkr.hw }, - { .hw = &bimc_pll_vote.hw }, -}; - -static const struct freq_tbl ftbl_pcnoc_bfdcd_clk_src[] = { - F(19200000, P_XO, 1, 0, 0), - F(50000000, P_GPLL0, 16, 0, 0), - F(100000000, P_GPLL0, 8, 0, 0), - { } -}; - -static struct clk_rcg2 pcnoc_bfdcd_clk_src = { - .cmd_rcgr = 0x27000, - .freq_tbl = ftbl_pcnoc_bfdcd_clk_src, - .hid_width = 5, - .parent_map = gcc_xo_gpll0_bimc_map, - .clkr.hw.init = &(struct clk_init_data){ - .name = "pcnoc_bfdcd_clk_src", - .parent_data = gcc_xo_gpll0_bimc, - .num_parents = ARRAY_SIZE(gcc_xo_gpll0_bimc), - .ops = &clk_rcg2_ops, - .flags = CLK_IS_CRITICAL, - }, -}; - static const struct freq_tbl ftbl_gcc_blsp1_qup1_6_i2c_apps_clk[] = { F(19200000, P_XO, 1, 0, 0), F(50000000, P_GPLL0, 16, 0, 0), @@ -735,8 +672,6 @@ static struct clk_branch gcc_blsp1_ahb_clk = { .enable_mask = BIT(10), .hw.init = &(struct clk_init_data){ .name = "gcc_blsp1_ahb_clk", - .parent_hws = (const struct clk_hw *[]){ &pcnoc_bfdcd_clk_src.clkr.hw }, - .num_parents = 1, .ops = &clk_branch2_ops, }, }, @@ -1038,8 +973,6 @@ static struct clk_branch gcc_boot_rom_ahb_clk = { .enable_mask = BIT(7), .hw.init = &(struct clk_init_data){ .name = "gcc_boot_rom_ahb_clk", - .parent_hws = (const struct clk_hw *[]){ &pcnoc_bfdcd_clk_src.clkr.hw }, - .num_parents = 1, .ops = &clk_branch2_ops, }, }, @@ -1053,9 +986,6 @@ static struct clk_branch gcc_crypto_ahb_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_crypto_ahb_clk", - .parent_hws = (const struct clk_hw *[]){ &pcnoc_bfdcd_clk_src.clkr.hw }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -1069,9 +999,6 @@ static struct clk_branch gcc_crypto_axi_clk = { .enable_mask = BIT(1), .hw.init = &(struct clk_init_data){ .name = "gcc_crypto_axi_clk", - .parent_hws = (const struct clk_hw *[]){ &pcnoc_bfdcd_clk_src.clkr.hw }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -1145,9 +1072,6 @@ static struct clk_branch gcc_mss_cfg_ahb_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_mss_cfg_ahb_clk", - .parent_hws = (const struct clk_hw *[]){ &pcnoc_bfdcd_clk_src.clkr.hw }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -1175,9 +1099,6 @@ static struct clk_branch gcc_pdm_ahb_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_pdm_ahb_clk", - .parent_hws = (const struct clk_hw *[]){ &pcnoc_bfdcd_clk_src.clkr.hw }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -1191,9 +1112,6 @@ static struct clk_branch gcc_prng_ahb_clk = { .enable_mask = BIT(8), .hw.init = &(struct clk_init_data){ .name = "gcc_prng_ahb_clk", - .parent_hws = (const struct clk_hw *[]){ &pcnoc_bfdcd_clk_src.clkr.hw }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -1206,9 +1124,6 @@ static struct clk_branch gcc_sdcc1_ahb_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_sdcc1_ahb_clk", - .parent_hws = (const struct clk_hw *[]){ &pcnoc_bfdcd_clk_src.clkr.hw }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -1236,9 +1151,6 @@ static struct clk_branch gcc_sdcc2_ahb_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_sdcc2_ahb_clk", - .parent_hws = (const struct clk_hw *[]){ &pcnoc_bfdcd_clk_src.clkr.hw }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -1259,19 +1171,6 @@ static struct clk_branch gcc_sdcc2_apps_clk = { }, }; -static struct clk_rcg2 bimc_ddr_clk_src = { - .cmd_rcgr = 0x32004, - .hid_width = 5, - .parent_map = gcc_xo_gpll0_bimc_map, - .clkr.hw.init = &(struct clk_init_data){ - .name = "bimc_ddr_clk_src", - .parent_data = gcc_xo_gpll0_bimc, - .num_parents = 3, - .ops = &clk_rcg2_ops, - .flags = CLK_GET_RATE_NOCACHE, - }, -}; - static struct clk_branch gcc_mss_q6_bimc_axi_clk = { .halt_reg = 0x49004, .clkr = { @@ -1279,9 +1178,6 @@ static struct clk_branch gcc_mss_q6_bimc_axi_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_mss_q6_bimc_axi_clk", - .parent_hws = (const struct clk_hw *[]){ &bimc_ddr_clk_src.clkr.hw }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -1295,8 +1191,6 @@ static struct clk_branch gcc_apss_tcu_clk = { .enable_mask = BIT(1), .hw.init = &(struct clk_init_data){ .name = "gcc_apss_tcu_clk", - .parent_hws = (const struct clk_hw *[]){ &bimc_ddr_clk_src.clkr.hw }, - .num_parents = 1, .ops = &clk_branch2_ops, }, }, @@ -1310,9 +1204,6 @@ static struct clk_branch gcc_smmu_cfg_clk = { .enable_mask = BIT(12), .hw.init = &(struct clk_init_data){ .name = "gcc_smmu_cfg_clk", - .parent_hws = (const struct clk_hw *[]){ &pcnoc_bfdcd_clk_src.clkr.hw }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -1360,9 +1251,6 @@ static struct clk_branch gcc_usb_hs_phy_cfg_ahb_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_usb_hs_phy_cfg_ahb_clk", - .parent_hws = (const struct clk_hw *[]){ &pcnoc_bfdcd_clk_src.clkr.hw }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -1375,9 +1263,6 @@ static struct clk_branch gcc_usb_hs_ahb_clk = { .enable_mask = BIT(0), .hw.init = &(struct clk_init_data){ .name = "gcc_usb_hs_ahb_clk", - .parent_hws = (const struct clk_hw *[]){ &pcnoc_bfdcd_clk_src.clkr.hw }, - .num_parents = 1, - .flags = CLK_SET_RATE_PARENT, .ops = &clk_branch2_ops, }, }, @@ -1406,8 +1291,6 @@ static struct clk_branch gcc_apss_ahb_clk = { .enable_mask = BIT(14), .hw.init = &(struct clk_init_data){ .name = "gcc_apss_ahb_clk", - .parent_hws = (const struct clk_hw *[]){ &pcnoc_bfdcd_clk_src.clkr.hw }, - .num_parents = 1, .ops = &clk_branch2_ops, }, }, @@ -1421,8 +1304,6 @@ static struct clk_branch gcc_apss_axi_clk = { .enable_mask = BIT(13), .hw.init = &(struct clk_init_data){ .name = "gcc_apss_axi_clk", - .parent_hws = (const struct clk_hw *[]){ &pcnoc_bfdcd_clk_src.clkr.hw }, - .num_parents = 1, .ops = &clk_branch2_ops, }, }, @@ -1435,10 +1316,6 @@ static struct clk_regmap *gcc_mdm9607_clocks[] = { [GPLL1_VOTE] = &gpll1_vote, [GPLL2] = &gpll2.clkr, [GPLL2_EARLY] = &gpll2_early.clkr, - [BIMC_PLL] = &bimc_pll.clkr, - [BIMC_PLL_VOTE] = &bimc_pll_vote, - [BIMC_DDR_CLK_SRC] = &bimc_ddr_clk_src.clkr, - [PCNOC_BFDCD_CLK_SRC] = &pcnoc_bfdcd_clk_src.clkr, [APSS_AHB_CLK_SRC] = &apss_ahb_clk_src.clkr, [BLSP1_QUP1_I2C_APPS_CLK_SRC] = &blsp1_qup1_i2c_apps_clk_src.clkr, [BLSP1_QUP1_SPI_APPS_CLK_SRC] = &blsp1_qup1_spi_apps_clk_src.clkr, From 1df0fa742c5a1a0409eb64cea2993748efc42591 Mon Sep 17 00:00:00 2001 From: Stephan Gerhold Date: Mon, 6 Jul 2026 17:02:16 +0200 Subject: [PATCH 0351/1328] dt-bindings: clock: qcom: gcc-mdm9607: Drop SYSTEM_NOC_BFDCD_CLK_SRC This clock does not exist in the hardware, it has no consumers and reading/writing to the registers fails. Drop it from the bindings. Acked-by: Krzysztof Kozlowski Signed-off-by: Stephan Gerhold Link: https://lore.kernel.org/r/20260706-qcom-clk-mdm9607-fixes-v2-9-745565101869@linaro.org Signed-off-by: Bjorn Andersson --- include/dt-bindings/clock/qcom,gcc-mdm9607.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/dt-bindings/clock/qcom,gcc-mdm9607.h b/include/dt-bindings/clock/qcom,gcc-mdm9607.h index 357a680a40da..246bce28137e 100644 --- a/include/dt-bindings/clock/qcom,gcc-mdm9607.h +++ b/include/dt-bindings/clock/qcom,gcc-mdm9607.h @@ -13,7 +13,6 @@ #define GPLL2 4 #define GPLL2_EARLY 5 #define PCNOC_BFDCD_CLK_SRC 6 -#define SYSTEM_NOC_BFDCD_CLK_SRC 7 #define GCC_SMMU_CFG_CLK 8 #define APSS_AHB_CLK_SRC 9 #define GCC_QDSS_DAP_CLK 10 From 5c117709e7e1f1a53642f5704f29c3250a14c6f0 Mon Sep 17 00:00:00 2001 From: Stephan Gerhold Date: Mon, 6 Jul 2026 17:02:17 +0200 Subject: [PATCH 0352/1328] dt-bindings: clock: qcom: gcc-mdm9607: Add missing "clocks" property gcc-mdm9607.c uses "fw_name", so it requires specifying the "xo" and "sleep_clk" clock source in the device tree. For some reason, this was never documented in the dt-bindings. Nowadays, qcom,gcc-mdm9607 has a dedicated schema, so we can just add it to the properties without any additional conditionals. Fixes: 6faa7e4ddce6 ("dt-bindings: clock: Add MDM9607 GCC clock bindings") Reviewed-by: Krzysztof Kozlowski Reviewed-by: Konrad Dybcio Signed-off-by: Stephan Gerhold Link: https://lore.kernel.org/r/20260706-qcom-clk-mdm9607-fixes-v2-10-745565101869@linaro.org Signed-off-by: Bjorn Andersson --- .../bindings/clock/qcom,gcc-mdm9607.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Documentation/devicetree/bindings/clock/qcom,gcc-mdm9607.yaml b/Documentation/devicetree/bindings/clock/qcom,gcc-mdm9607.yaml index d7da30b0e7ee..ef4b274b5c09 100644 --- a/Documentation/devicetree/bindings/clock/qcom,gcc-mdm9607.yaml +++ b/Documentation/devicetree/bindings/clock/qcom,gcc-mdm9607.yaml @@ -25,8 +25,20 @@ properties: enum: - qcom,gcc-mdm9607 + clocks: + items: + - description: XO source + - description: Sleep clock source + + clock-names: + items: + - const: xo + - const: sleep_clk + required: - compatible + - clocks + - clock-names - '#power-domain-cells' unevaluatedProperties: false @@ -39,5 +51,7 @@ examples: #clock-cells = <1>; #reset-cells = <1>; #power-domain-cells = <1>; + clocks = <&xo_board>, <&sleep_clk>; + clock-names = "xo", "sleep_clk"; }; ... From 75ae68fcbddca8f093c3b561cc82f9615b867b3d Mon Sep 17 00:00:00 2001 From: Stephan Gerhold Date: Mon, 6 Jul 2026 17:02:18 +0200 Subject: [PATCH 0353/1328] dt-bindings: clock: qcom: gcc-mdm9607: Use proper address in example Given that this is a dedicated schema for qcom,gcc-mdm9607, we might as well use the correct memory addresses in the example. This does not affect the validation itself, but will reduce confusion for readers. Acked-by: Krzysztof Kozlowski Reviewed-by: Konrad Dybcio Signed-off-by: Stephan Gerhold Link: https://lore.kernel.org/r/20260706-qcom-clk-mdm9607-fixes-v2-11-745565101869@linaro.org Signed-off-by: Bjorn Andersson --- Documentation/devicetree/bindings/clock/qcom,gcc-mdm9607.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/clock/qcom,gcc-mdm9607.yaml b/Documentation/devicetree/bindings/clock/qcom,gcc-mdm9607.yaml index ef4b274b5c09..1b0efb1526a6 100644 --- a/Documentation/devicetree/bindings/clock/qcom,gcc-mdm9607.yaml +++ b/Documentation/devicetree/bindings/clock/qcom,gcc-mdm9607.yaml @@ -45,9 +45,9 @@ unevaluatedProperties: false examples: - | - clock-controller@900000 { + clock-controller@1800000 { compatible = "qcom,gcc-mdm9607"; - reg = <0x900000 0x4000>; + reg = <0x01800000 0x80000>; #clock-cells = <1>; #reset-cells = <1>; #power-domain-cells = <1>; From d4d2fcb3e3e929e71a7d1ca5443dbe08acd8f128 Mon Sep 17 00:00:00 2001 From: Stephan Gerhold Date: Mon, 6 Jul 2026 17:02:19 +0200 Subject: [PATCH 0354/1328] clk: qcom: gcc-mdm9607: Drop redundant register update during probe GPLL0 is pretty much guaranteed to be already on (and voted) during boot, since it's used by the CPU and also various other components such as UART. We also vote for this bit in the actual GPLL0 definition, which will be set as soon as any driver in the kernel requires using the GPLL0 clock. All in all, this makes separately voting for GPLL0 during probe redundant, especially because the "acpuclock" in the comment is a downstream construct that does not exist in upstream. Reviewed-by: Konrad Dybcio Signed-off-by: Stephan Gerhold Link: https://lore.kernel.org/r/20260706-qcom-clk-mdm9607-fixes-v2-12-745565101869@linaro.org Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gcc-mdm9607.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/drivers/clk/qcom/gcc-mdm9607.c b/drivers/clk/qcom/gcc-mdm9607.c index 4b749c99586b..2e4cdb590623 100644 --- a/drivers/clk/qcom/gcc-mdm9607.c +++ b/drivers/clk/qcom/gcc-mdm9607.c @@ -1425,16 +1425,7 @@ MODULE_DEVICE_TABLE(of, gcc_mdm9607_match_table); static int gcc_mdm9607_probe(struct platform_device *pdev) { - struct regmap *regmap; - - regmap = qcom_cc_map(pdev, &gcc_mdm9607_desc); - if (IS_ERR(regmap)) - return PTR_ERR(regmap); - - /* Vote for GPLL0 to turn on. Needed by acpuclock. */ - regmap_update_bits(regmap, 0x45000, BIT(0), BIT(0)); - - return qcom_cc_really_probe(&pdev->dev, &gcc_mdm9607_desc, regmap); + return qcom_cc_probe(pdev, &gcc_mdm9607_desc); } static struct platform_driver gcc_mdm9607_driver = { From 205986f9e81ddad510763f26e9ccdea0f49b3083 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Thu, 16 Jul 2026 20:21:05 +0200 Subject: [PATCH 0355/1328] clk: qcom: Drop inclusions of MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The only used symbol in these four drivers that is provided by is of_device_id and this is also provided by . So drop the unneeded include which brings us one step closer to removing . Signed-off-by: Uwe Kleine-König (The Capable Hub) Reviewed-by: Brian Masney Link: https://lore.kernel.org/r/7b6441aa3b0dba35112ea486bfea3b3171d8527b.1784225976.git.ukleinek@kernel.org Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/camcc-glymur.c | 1 - drivers/clk/qcom/evacc-glymur.c | 1 - drivers/clk/qcom/gcc-shikra.c | 1 - drivers/clk/qcom/videocc-hawi.c | 1 - 4 files changed, 4 deletions(-) diff --git a/drivers/clk/qcom/camcc-glymur.c b/drivers/clk/qcom/camcc-glymur.c index 044cc590624e..81c1e102b110 100644 --- a/drivers/clk/qcom/camcc-glymur.c +++ b/drivers/clk/qcom/camcc-glymur.c @@ -4,7 +4,6 @@ */ #include -#include #include #include #include diff --git a/drivers/clk/qcom/evacc-glymur.c b/drivers/clk/qcom/evacc-glymur.c index ba124cfc1a4d..7f031b05a94a 100644 --- a/drivers/clk/qcom/evacc-glymur.c +++ b/drivers/clk/qcom/evacc-glymur.c @@ -4,7 +4,6 @@ */ #include -#include #include #include #include diff --git a/drivers/clk/qcom/gcc-shikra.c b/drivers/clk/qcom/gcc-shikra.c index f2ab50209e1c..cd192ff38913 100644 --- a/drivers/clk/qcom/gcc-shikra.c +++ b/drivers/clk/qcom/gcc-shikra.c @@ -4,7 +4,6 @@ */ #include -#include #include #include #include diff --git a/drivers/clk/qcom/videocc-hawi.c b/drivers/clk/qcom/videocc-hawi.c index ac3ff7202fa0..318270105dfe 100644 --- a/drivers/clk/qcom/videocc-hawi.c +++ b/drivers/clk/qcom/videocc-hawi.c @@ -4,7 +4,6 @@ */ #include -#include #include #include #include From 126ee572d04e537394e16dbf6bf0702436af87d0 Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Thu, 30 Jul 2026 23:24:34 +0530 Subject: [PATCH 0356/1328] dt-bindings: clock: qcom-rpmhcc: Add RPMH clock controller for Maili Document the RPMH clock controller for the Qualcomm Maili SoC. Maili SoC is a derivative of the Hawi SoC and the rpmh clock controller is identical to that of Hawi. Therefore Maili uses the fallback compatible to reuse the Hawi rpmhcc driver. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Taniya Das Link: https://lore.kernel.org/r/20260730-maili_initial_clock-v4-1-ed52ade264f0@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- .../bindings/clock/qcom,rpmhcc.yaml | 65 ++++++++++--------- 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/Documentation/devicetree/bindings/clock/qcom,rpmhcc.yaml b/Documentation/devicetree/bindings/clock/qcom,rpmhcc.yaml index d344b3386042..2b446aca5207 100644 --- a/Documentation/devicetree/bindings/clock/qcom,rpmhcc.yaml +++ b/Documentation/devicetree/bindings/clock/qcom,rpmhcc.yaml @@ -16,36 +16,41 @@ description: | properties: compatible: - enum: - - qcom,eliza-rpmh-clk - - qcom,glymur-rpmh-clk - - qcom,hawi-rpmh-clk - - qcom,kaanapali-rpmh-clk - - qcom,milos-rpmh-clk - - qcom,nord-rpmh-clk - - qcom,qcs615-rpmh-clk - - qcom,qdu1000-rpmh-clk - - qcom,sa8775p-rpmh-clk - - qcom,sar2130p-rpmh-clk - - qcom,sc7180-rpmh-clk - - qcom,sc7280-rpmh-clk - - qcom,sc8180x-rpmh-clk - - qcom,sc8280xp-rpmh-clk - - qcom,sdm670-rpmh-clk - - qcom,sdm845-rpmh-clk - - qcom,sdx55-rpmh-clk - - qcom,sdx65-rpmh-clk - - qcom,sdx75-rpmh-clk - - qcom,sm4450-rpmh-clk - - qcom,sm6350-rpmh-clk - - qcom,sm8150-rpmh-clk - - qcom,sm8250-rpmh-clk - - qcom,sm8350-rpmh-clk - - qcom,sm8450-rpmh-clk - - qcom,sm8550-rpmh-clk - - qcom,sm8650-rpmh-clk - - qcom,sm8750-rpmh-clk - - qcom,x1e80100-rpmh-clk + oneOf: + - enum: + - qcom,eliza-rpmh-clk + - qcom,glymur-rpmh-clk + - qcom,hawi-rpmh-clk + - qcom,kaanapali-rpmh-clk + - qcom,milos-rpmh-clk + - qcom,nord-rpmh-clk + - qcom,qcs615-rpmh-clk + - qcom,qdu1000-rpmh-clk + - qcom,sa8775p-rpmh-clk + - qcom,sar2130p-rpmh-clk + - qcom,sc7180-rpmh-clk + - qcom,sc7280-rpmh-clk + - qcom,sc8180x-rpmh-clk + - qcom,sc8280xp-rpmh-clk + - qcom,sdm670-rpmh-clk + - qcom,sdm845-rpmh-clk + - qcom,sdx55-rpmh-clk + - qcom,sdx65-rpmh-clk + - qcom,sdx75-rpmh-clk + - qcom,sm4450-rpmh-clk + - qcom,sm6350-rpmh-clk + - qcom,sm8150-rpmh-clk + - qcom,sm8250-rpmh-clk + - qcom,sm8350-rpmh-clk + - qcom,sm8450-rpmh-clk + - qcom,sm8550-rpmh-clk + - qcom,sm8650-rpmh-clk + - qcom,sm8750-rpmh-clk + - qcom,x1e80100-rpmh-clk + - items: + - enum: + - qcom,maili-rpmh-clk + - const: qcom,hawi-rpmh-clk clocks: maxItems: 1 From 2f4a1d2ed50514ca19a370aa7bf6005ca3f913ff Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Thu, 30 Jul 2026 23:24:35 +0530 Subject: [PATCH 0357/1328] dt-bindings: clock: qcom: Add Maili TCSR clock controller Add bindings documentation for TCSR clock controller on the Qualcomm Maili SoC. Maili is a derivative of the Hawi SoC and the tcsr clock controller is identical to that of Hawi. Therefore Maili uses the fallback compatible to reuse the Hawi tcsrcc driver. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Taniya Das Link: https://lore.kernel.org/r/20260730-maili_initial_clock-v4-2-ed52ade264f0@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- .../bindings/clock/qcom,sm8550-tcsr.yaml | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/Documentation/devicetree/bindings/clock/qcom,sm8550-tcsr.yaml b/Documentation/devicetree/bindings/clock/qcom,sm8550-tcsr.yaml index 19ae0634b922..1f3332275c17 100644 --- a/Documentation/devicetree/bindings/clock/qcom,sm8550-tcsr.yaml +++ b/Documentation/devicetree/bindings/clock/qcom,sm8550-tcsr.yaml @@ -24,19 +24,25 @@ description: | properties: compatible: - items: - - enum: - - qcom,eliza-tcsr - - qcom,hawi-tcsrcc - - qcom,kaanapali-tcsr - - qcom,milos-tcsr - - qcom,nord-tcsrcc - - qcom,sar2130p-tcsr - - qcom,sm8550-tcsr - - qcom,sm8650-tcsr - - qcom,sm8750-tcsr - - qcom,x1e80100-tcsr - - const: syscon + oneOf: + - items: + - enum: + - qcom,eliza-tcsr + - qcom,hawi-tcsrcc + - qcom,kaanapali-tcsr + - qcom,milos-tcsr + - qcom,nord-tcsrcc + - qcom,sar2130p-tcsr + - qcom,sm8550-tcsr + - qcom,sm8650-tcsr + - qcom,sm8750-tcsr + - qcom,x1e80100-tcsr + - const: syscon + - items: + - enum: + - qcom,maili-tcsrcc + - const: qcom,hawi-tcsrcc + - const: syscon clocks: items: From 86ea9de5fae96051b468ab87a67b7a8f7a1e324d Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Thu, 30 Jul 2026 23:24:36 +0530 Subject: [PATCH 0358/1328] dt-bindings: clock: qcom: Add Maili global clock controller Add device tree bindings for the global clock controller (GCC) on the Qualcomm Maili SoC by extending the existing Qualcomm Hawi GCC bindings, since the Maili GCC is identical to the Hawi GCC apart from a few additional clocks. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Taniya Das Link: https://lore.kernel.org/r/20260730-maili_initial_clock-v4-3-ed52ade264f0@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- .../bindings/clock/qcom,hawi-gcc.yaml | 11 +++++++--- include/dt-bindings/clock/qcom,maili-gcc.h | 21 +++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 include/dt-bindings/clock/qcom,maili-gcc.h diff --git a/Documentation/devicetree/bindings/clock/qcom,hawi-gcc.yaml b/Documentation/devicetree/bindings/clock/qcom,hawi-gcc.yaml index 4f428c0f7286..2101f4e4bb0d 100644 --- a/Documentation/devicetree/bindings/clock/qcom,hawi-gcc.yaml +++ b/Documentation/devicetree/bindings/clock/qcom,hawi-gcc.yaml @@ -8,16 +8,21 @@ title: Qualcomm Global Clock & Reset Controller on Hawi maintainers: - Vivek Aknurwar + - Taniya Das description: | Qualcomm global clock control module provides the clocks, resets and power - domains on Hawi. + domains on Hawi and Maili. - See also: include/dt-bindings/clock/qcom,hawi-gcc.h + See also: + include/dt-bindings/clock/qcom,hawi-gcc.h + include/dt-bindings/clock/qcom,maili-gcc.h properties: compatible: - const: qcom,hawi-gcc + enum: + - qcom,hawi-gcc + - qcom,maili-gcc clocks: items: diff --git a/include/dt-bindings/clock/qcom,maili-gcc.h b/include/dt-bindings/clock/qcom,maili-gcc.h new file mode 100644 index 000000000000..9bea90adb988 --- /dev/null +++ b/include/dt-bindings/clock/qcom,maili-gcc.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef _DT_BINDINGS_CLK_QCOM_GCC_MAILI_H +#define _DT_BINDINGS_CLK_QCOM_GCC_MAILI_H + +#include "qcom,hawi-gcc.h" + +/* Maili has below additional clocks on top of Hawi */ +#define GCC_QUPV3_WRAP5_CORE_2X_CLK 188 +#define GCC_QUPV3_WRAP5_CORE_CLK 189 +#define GCC_QUPV3_WRAP5_QSPI_REF_CLK 190 +#define GCC_QUPV3_WRAP5_QSPI_REF_CLK_SRC 191 +#define GCC_QUPV3_WRAP5_S0_CLK 192 +#define GCC_QUPV3_WRAP5_S0_CLK_SRC 193 +#define GCC_QUPV3_WRAP_5_M_AHB_CLK 194 +#define GCC_QUPV3_WRAP_5_S_AHB_CLK 195 + +#endif From cc881afb66ab91cfdb32dc68078d843679d88baa Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Thu, 30 Jul 2026 23:24:37 +0530 Subject: [PATCH 0359/1328] clk: qcom: gcc-hawi: Add support for global clock controller on Maili Add support for the global clock controller (GCC) on the Qualcomm Maili SoC by extending the Hawi global clock controller since Maili is identical to Hawi and has few additional clocks. Reviewed-by: Konrad Dybcio Signed-off-by: Taniya Das Link: https://lore.kernel.org/r/20260730-maili_initial_clock-v4-4-ed52ade264f0@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gcc-hawi.c | 201 ++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) diff --git a/drivers/clk/qcom/gcc-hawi.c b/drivers/clk/qcom/gcc-hawi.c index 6dd07c772c29..ff1873c7e547 100644 --- a/drivers/clk/qcom/gcc-hawi.c +++ b/drivers/clk/qcom/gcc-hawi.c @@ -11,6 +11,7 @@ #include #include +#include #include "clk-alpha-pll.h" #include "clk-branch.h" @@ -1115,6 +1116,31 @@ static struct clk_rcg2 gcc_qupv3_wrap4_s4_clk_src = { .clkr.hw.init = &gcc_qupv3_wrap4_s4_clk_src_init, }; +static const struct freq_tbl ftbl_gcc_qupv3_wrap5_qspi_ref_clk_src[] = { + F(150000000, P_GCC_GPLL0_OUT_EVEN, 2, 0, 0), + F(196078431, P_GCC_GPLL0_OUT_EVEN, 1, 100, 153), + F(300000000, P_GCC_GPLL0_OUT_EVEN, 1, 0, 0), + { } +}; + +static struct clk_init_data gcc_qupv3_wrap5_qspi_ref_clk_src_init = { + .name = "gcc_qupv3_wrap5_qspi_ref_clk_src", + .parent_data = gcc_parent_data_1, + .num_parents = ARRAY_SIZE(gcc_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_no_init_park_ops, +}; + +static struct clk_rcg2 gcc_qupv3_wrap5_qspi_ref_clk_src = { + .cmd_rcgr = 0xad024, + .mnd_width = 16, + .hid_width = 5, + .parent_map = gcc_parent_map_1, + .freq_tbl = ftbl_gcc_qupv3_wrap5_qspi_ref_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &gcc_qupv3_wrap5_qspi_ref_clk_src_init, +}; + static const struct freq_tbl ftbl_gcc_sdcc2_apps_clk_src[] = { F(400000, P_BI_TCXO, 12, 1, 4), F(25000000, P_GCC_GPLL0_OUT_EVEN, 12, 0, 0), @@ -1282,6 +1308,21 @@ static struct clk_rcg2 gcc_usb30_prim_master_clk_src = { }, }; +static struct clk_regmap_div gcc_qupv3_wrap5_s0_clk_src = { + .reg = 0xad018, + .shift = 0, + .width = 4, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gcc_qupv3_wrap5_s0_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &gcc_qupv3_wrap5_qspi_ref_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_regmap_div_ro_ops, + }, +}; + static struct clk_rcg2 gcc_usb30_prim_mock_utmi_clk_src = { .cmd_rcgr = 0x3904c, .mnd_width = 0, @@ -2740,6 +2781,68 @@ static struct clk_branch gcc_qupv3_wrap4_s4_clk = { }, }; +static struct clk_branch gcc_qupv3_wrap5_core_2x_clk = { + .halt_reg = 0x236bc, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x52020, + .enable_mask = BIT(16), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_qupv3_wrap5_core_2x_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_qupv3_wrap5_core_clk = { + .halt_reg = 0x236a8, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x52020, + .enable_mask = BIT(15), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_qupv3_wrap5_core_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_qupv3_wrap5_qspi_ref_clk = { + .halt_reg = 0xad01c, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x52020, + .enable_mask = BIT(18), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_qupv3_wrap5_qspi_ref_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_qupv3_wrap5_qspi_ref_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_qupv3_wrap5_s0_clk = { + .halt_reg = 0xad004, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x52020, + .enable_mask = BIT(17), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_qupv3_wrap5_s0_clk", + .parent_hws = (const struct clk_hw*[]) { + &gcc_qupv3_wrap5_s0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + static struct clk_branch gcc_qupv3_wrap_1_m_axi_clk = { .halt_reg = 0x23140, .halt_check = BRANCH_HALT_VOTED, @@ -2860,6 +2963,36 @@ static struct clk_branch gcc_qupv3_wrap_4_s_ahb_clk = { }, }; +static struct clk_branch gcc_qupv3_wrap_5_m_ahb_clk = { + .halt_reg = 0x236a0, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x236a0, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x52020, + .enable_mask = BIT(13), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_qupv3_wrap_5_m_ahb_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_qupv3_wrap_5_s_ahb_clk = { + .halt_reg = 0x236a4, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x236a4, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x52020, + .enable_mask = BIT(14), + .hw.init = &(const struct clk_init_data) { + .name = "gcc_qupv3_wrap_5_s_ahb_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + static struct clk_branch gcc_sdcc2_ahb_clk = { .halt_reg = 0x14014, .halt_check = BRANCH_HALT, @@ -3485,6 +3618,11 @@ static struct clk_regmap *gcc_hawi_clocks[] = { [GCC_USB3_PRIM_PHY_PIPE_CLK_SRC] = &gcc_usb3_prim_phy_pipe_clk_src.clkr, [GCC_VIDEO_AXI0_CLK] = &gcc_video_axi0_clk.clkr, [GCC_VIDEO_AXI0C_CLK] = &gcc_video_axi0c_clk.clkr, + /* + * Maili has more clocks than Hawi. Ensure the array is appropriately + * sized and assign the additional clocks in .probe() + */ + [GCC_QUPV3_WRAP_5_S_AHB_CLK] = NULL, }; static struct gdsc *gcc_hawi_gdscs[] = { @@ -3588,6 +3726,34 @@ static const struct clk_rcg_dfs_data gcc_hawi_dfs_clocks[] = { DEFINE_RCG_DFS(gcc_qupv3_wrap4_s4_clk_src), }; +static const struct clk_rcg_dfs_data gcc_maili_dfs_clocks[] = { + DEFINE_RCG_DFS(gcc_qupv3_wrap1_qspi_ref_clk_src), + DEFINE_RCG_DFS(gcc_qupv3_wrap1_s0_clk_src), + DEFINE_RCG_DFS(gcc_qupv3_wrap1_s1_clk_src), + DEFINE_RCG_DFS(gcc_qupv3_wrap1_s3_clk_src), + DEFINE_RCG_DFS(gcc_qupv3_wrap1_s4_clk_src), + DEFINE_RCG_DFS(gcc_qupv3_wrap1_s5_clk_src), + DEFINE_RCG_DFS(gcc_qupv3_wrap1_s6_clk_src), + DEFINE_RCG_DFS(gcc_qupv3_wrap1_s7_clk_src), + DEFINE_RCG_DFS(gcc_qupv3_wrap2_s0_clk_src), + DEFINE_RCG_DFS(gcc_qupv3_wrap2_s1_clk_src), + DEFINE_RCG_DFS(gcc_qupv3_wrap2_s2_clk_src), + DEFINE_RCG_DFS(gcc_qupv3_wrap2_s3_clk_src), + DEFINE_RCG_DFS(gcc_qupv3_wrap2_s4_clk_src), + DEFINE_RCG_DFS(gcc_qupv3_wrap3_qspi_ref_clk_src), + DEFINE_RCG_DFS(gcc_qupv3_wrap3_s0_clk_src), + DEFINE_RCG_DFS(gcc_qupv3_wrap3_s2_clk_src), + DEFINE_RCG_DFS(gcc_qupv3_wrap3_s3_clk_src), + DEFINE_RCG_DFS(gcc_qupv3_wrap3_s4_clk_src), + DEFINE_RCG_DFS(gcc_qupv3_wrap3_s5_clk_src), + DEFINE_RCG_DFS(gcc_qupv3_wrap4_s0_clk_src), + DEFINE_RCG_DFS(gcc_qupv3_wrap4_s1_clk_src), + DEFINE_RCG_DFS(gcc_qupv3_wrap4_s2_clk_src), + DEFINE_RCG_DFS(gcc_qupv3_wrap4_s3_clk_src), + DEFINE_RCG_DFS(gcc_qupv3_wrap4_s4_clk_src), + DEFINE_RCG_DFS(gcc_qupv3_wrap5_qspi_ref_clk_src), +}; + static const struct regmap_config gcc_hawi_regmap_config = { .reg_bits = 32, .reg_stride = 4, @@ -3622,14 +3788,49 @@ static const struct qcom_cc_desc gcc_hawi_desc = { .driver_data = &gcc_hawi_driver_data, }; +static const struct qcom_cc_driver_data gcc_maili_driver_data = { + .clk_cbcrs = gcc_hawi_critical_cbcrs, + .num_clk_cbcrs = ARRAY_SIZE(gcc_hawi_critical_cbcrs), + .dfs_rcgs = gcc_maili_dfs_clocks, + .num_dfs_rcgs = ARRAY_SIZE(gcc_maili_dfs_clocks), + .clk_regs_configure = clk_hawi_regs_configure, +}; + +static const struct qcom_cc_desc gcc_maili_desc = { + .config = &gcc_hawi_regmap_config, + .clks = gcc_hawi_clocks, + .num_clks = ARRAY_SIZE(gcc_hawi_clocks), + .resets = gcc_hawi_resets, + .num_resets = ARRAY_SIZE(gcc_hawi_resets), + .gdscs = gcc_hawi_gdscs, + .num_gdscs = ARRAY_SIZE(gcc_hawi_gdscs), + .use_rpm = true, + .driver_data = &gcc_maili_driver_data, +}; + static const struct of_device_id gcc_hawi_match_table[] = { { .compatible = "qcom,hawi-gcc" }, + { .compatible = "qcom,maili-gcc" }, { } }; MODULE_DEVICE_TABLE(of, gcc_hawi_match_table); static int gcc_hawi_probe(struct platform_device *pdev) { + if (of_device_is_compatible(pdev->dev.of_node, "qcom,maili-gcc")) { + gcc_hawi_clocks[GCC_QUPV3_WRAP5_CORE_2X_CLK] = &gcc_qupv3_wrap5_core_2x_clk.clkr; + gcc_hawi_clocks[GCC_QUPV3_WRAP5_CORE_CLK] = &gcc_qupv3_wrap5_core_clk.clkr; + gcc_hawi_clocks[GCC_QUPV3_WRAP5_QSPI_REF_CLK] = &gcc_qupv3_wrap5_qspi_ref_clk.clkr; + gcc_hawi_clocks[GCC_QUPV3_WRAP5_QSPI_REF_CLK_SRC] = + &gcc_qupv3_wrap5_qspi_ref_clk_src.clkr; + gcc_hawi_clocks[GCC_QUPV3_WRAP5_S0_CLK] = &gcc_qupv3_wrap5_s0_clk.clkr; + gcc_hawi_clocks[GCC_QUPV3_WRAP5_S0_CLK_SRC] = &gcc_qupv3_wrap5_s0_clk_src.clkr; + gcc_hawi_clocks[GCC_QUPV3_WRAP_5_M_AHB_CLK] = &gcc_qupv3_wrap_5_m_ahb_clk.clkr; + gcc_hawi_clocks[GCC_QUPV3_WRAP_5_S_AHB_CLK] = &gcc_qupv3_wrap_5_s_ahb_clk.clkr; + + return qcom_cc_probe(pdev, &gcc_maili_desc); + } + return qcom_cc_probe(pdev, &gcc_hawi_desc); } From 830ead322c39c99bf972425b3c35323ec56c29de Mon Sep 17 00:00:00 2001 From: Imran Shaik Date: Wed, 29 Jul 2026 20:53:47 +0530 Subject: [PATCH 0360/1328] clk: qcom: Fix test_ctl_hi field for DEFAULT_EVO PLLs CLK_ALPHA_PLL_TYPE_DEFAULT_EVO type PLLs do not have the PLL_TEST_CTL_U1 register, so clk_alpha_pll_configure() does not program test_ctl_hi1_val for this PLL type. The GCC PLL configurations for QCM2290, Shikra and SM6115 wrongly use test_ctl_hi1_val instead of test_ctl_hi_val, deviating from the hardware recommended settings. Fix them to use test_ctl_hi_val. Fixes: 496d1a13d405 ("clk: qcom: Add Global Clock Controller driver for QCM2290") Fixes: 01cf3e27824d ("clk: qcom: Add Global clock controller support on Qualcomm Shikra SoC") Fixes: e88c533d8a2a ("clk: qcom: gcc-sm6115: Add missing PLL config properties") Cc: stable@vger.kernel.org Signed-off-by: Imran Shaik Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260729-pll-test-ctrl-fixup-v1-1-246d79589380@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gcc-qcm2290.c | 6 +++--- drivers/clk/qcom/gcc-shikra.c | 6 +++--- drivers/clk/qcom/gcc-sm6115.c | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/clk/qcom/gcc-qcm2290.c b/drivers/clk/qcom/gcc-qcm2290.c index 02ca37f58477..77cff0e9af93 100644 --- a/drivers/clk/qcom/gcc-qcm2290.c +++ b/drivers/clk/qcom/gcc-qcm2290.c @@ -116,7 +116,7 @@ static const struct alpha_pll_config gpll10_config = { .vco_mask = GENMASK(21, 20), .main_output_mask = BIT(0), .config_ctl_val = 0x4001055B, - .test_ctl_hi1_val = 0x1, + .test_ctl_hi_val = 0x1, }; static struct clk_alpha_pll gpll10 = { @@ -148,7 +148,7 @@ static const struct alpha_pll_config gpll11_config = { .vco_mask = GENMASK(21, 20), .main_output_mask = BIT(0), .config_ctl_val = 0x4001055B, - .test_ctl_hi1_val = 0x1, + .test_ctl_hi_val = 0x1, }; static struct clk_alpha_pll gpll11 = { @@ -309,7 +309,7 @@ static const struct alpha_pll_config gpll8_config = { .post_div_val = 0x1 << 8, .post_div_mask = GENMASK(11, 8), .config_ctl_val = 0x4001055B, - .test_ctl_hi1_val = 0x1, + .test_ctl_hi_val = 0x1, }; static struct clk_alpha_pll gpll8 = { diff --git a/drivers/clk/qcom/gcc-shikra.c b/drivers/clk/qcom/gcc-shikra.c index cd192ff38913..2a1c1fc253aa 100644 --- a/drivers/clk/qcom/gcc-shikra.c +++ b/drivers/clk/qcom/gcc-shikra.c @@ -122,7 +122,7 @@ static const struct alpha_pll_config gpll10_config = { .vco_mask = GENMASK(21, 20), .main_output_mask = BIT(0), .config_ctl_val = 0x4001055b, - .test_ctl_hi1_val = 0x1, + .test_ctl_hi_val = 0x1, }; static struct clk_alpha_pll gpll10 = { @@ -155,7 +155,7 @@ static const struct alpha_pll_config gpll11_config = { .vco_mask = GENMASK(21, 20), .main_output_mask = BIT(0), .config_ctl_val = 0x4001055b, - .test_ctl_hi1_val = 0x1, + .test_ctl_hi_val = 0x1, }; static struct clk_alpha_pll gpll11 = { @@ -360,7 +360,7 @@ static const struct alpha_pll_config gpll8_config = { .post_div_val = BIT(8), .post_div_mask = GENMASK(11, 8), .config_ctl_val = 0x4001055b, - .test_ctl_hi1_val = 0x1, + .test_ctl_hi_val = 0x1, }; static struct clk_alpha_pll gpll8 = { diff --git a/drivers/clk/qcom/gcc-sm6115.c b/drivers/clk/qcom/gcc-sm6115.c index c5251aff9886..a3c9dbe3a48a 100644 --- a/drivers/clk/qcom/gcc-sm6115.c +++ b/drivers/clk/qcom/gcc-sm6115.c @@ -120,7 +120,7 @@ static const struct alpha_pll_config gpll10_config = { .vco_mask = GENMASK(21, 20), .main_output_mask = BIT(0), .config_ctl_val = 0x4001055b, - .test_ctl_hi1_val = 0x1, + .test_ctl_hi_val = 0x1, .test_ctl_hi_mask = 0x1, }; @@ -173,7 +173,7 @@ static const struct alpha_pll_config gpll11_config = { .vco_val = 0x2 << 20, .vco_mask = GENMASK(21, 20), .config_ctl_val = 0x4001055b, - .test_ctl_hi1_val = 0x1, + .test_ctl_hi_val = 0x1, .test_ctl_hi_mask = 0x1, }; @@ -367,7 +367,7 @@ static const struct alpha_pll_config gpll8_config = { .post_div_val = 0x1 << 8, .post_div_mask = GENMASK(11, 8), .config_ctl_val = 0x4001055b, - .test_ctl_hi1_val = 0x1, + .test_ctl_hi_val = 0x1, .test_ctl_hi_mask = 0x1, }; From 3a8a7d597b07ffe388b47fb524827be52c9f7e0f Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Thu, 16 Jul 2026 16:29:11 +0530 Subject: [PATCH 0361/1328] clk: qcom: nord: use BRANCH_HALT_SKIP for PHY pipe clocks The PCIe and USB3 pipe clocks on Nord are sourced from their respective PHYs. The halt bit for these branches does not toggle reliably when the PHY is powered down or not yet brought up, so polling for it with BRANCH_HALT_VOTED can spuriously time out. Switch these pipe clock branches to BRANCH_HALT_SKIP, matching the convention used for PHY-sourced pipe clocks elsewhere in the Qualcomm clock drivers. Fixes: a4f780cd5c7a ("clk: qcom: gcc: Add multiple global clock controller driver for Nord SoC") Signed-off-by: Taniya Das Tested-by: Shawn Guo Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260716-b4-nord-pipe-clk-fixes-v1-1-e4f583633356@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gcc-nord.c | 8 ++++---- drivers/clk/qcom/negcc-nord.c | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/clk/qcom/gcc-nord.c b/drivers/clk/qcom/gcc-nord.c index 8a6e429f2640..93ecebeab826 100644 --- a/drivers/clk/qcom/gcc-nord.c +++ b/drivers/clk/qcom/gcc-nord.c @@ -702,7 +702,7 @@ static struct clk_branch gcc_pcie_a_phy_rchng_clk = { static struct clk_branch gcc_pcie_a_pipe_clk = { .halt_reg = 0x49068, - .halt_check = BRANCH_HALT_VOTED, + .halt_check = BRANCH_HALT_SKIP, .hwcg_reg = 0x49068, .hwcg_bit = 1, .clkr = { @@ -851,7 +851,7 @@ static struct clk_branch gcc_pcie_b_phy_rchng_clk = { static struct clk_branch gcc_pcie_b_pipe_clk = { .halt_reg = 0x4a068, - .halt_check = BRANCH_HALT_VOTED, + .halt_check = BRANCH_HALT_SKIP, .clkr = { .enable_reg = 0x9d008, .enable_mask = BIT(24), @@ -996,7 +996,7 @@ static struct clk_branch gcc_pcie_c_phy_rchng_clk = { static struct clk_branch gcc_pcie_c_pipe_clk = { .halt_reg = 0x4b068, - .halt_check = BRANCH_HALT_VOTED, + .halt_check = BRANCH_HALT_SKIP, .clkr = { .enable_reg = 0x9d010, .enable_mask = BIT(1), @@ -1141,7 +1141,7 @@ static struct clk_branch gcc_pcie_d_phy_rchng_clk = { static struct clk_branch gcc_pcie_d_pipe_clk = { .halt_reg = 0x4c068, - .halt_check = BRANCH_HALT_VOTED, + .halt_check = BRANCH_HALT_SKIP, .clkr = { .enable_reg = 0x9d010, .enable_mask = BIT(10), diff --git a/drivers/clk/qcom/negcc-nord.c b/drivers/clk/qcom/negcc-nord.c index 2e653ef0fe0e..19e4ebbe313f 100644 --- a/drivers/clk/qcom/negcc-nord.c +++ b/drivers/clk/qcom/negcc-nord.c @@ -1642,7 +1642,7 @@ static struct clk_branch ne_gcc_usb3_prim_phy_com_aux_clk = { static struct clk_branch ne_gcc_usb3_prim_phy_pipe_clk = { .halt_reg = 0x2a074, - .halt_check = BRANCH_HALT_VOTED, + .halt_check = BRANCH_HALT_SKIP, .hwcg_reg = 0x2a074, .hwcg_bit = 1, .clkr = { @@ -1698,7 +1698,7 @@ static struct clk_branch ne_gcc_usb3_sec_phy_com_aux_clk = { static struct clk_branch ne_gcc_usb3_sec_phy_pipe_clk = { .halt_reg = 0x2c074, - .halt_check = BRANCH_HALT_VOTED, + .halt_check = BRANCH_HALT_SKIP, .hwcg_reg = 0x2c074, .hwcg_bit = 1, .clkr = { From a5c2859ffe9c8e033e2df1ef62357fa5692008f1 Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Thu, 16 Jul 2026 16:29:12 +0530 Subject: [PATCH 0362/1328] clk: qcom: negcc-nord: use clk_regmap_phy_mux for USB3 pipe clock srcs ne_gcc_usb3_prim_phy_pipe_clk_src and ne_gcc_usb3_sec_phy_pipe_clk_src are 2-bit muxes selecting between a PHY-sourced USB3 pipe clock and BI_TCXO, implemented with clk_regmap_mux_closest_ops. This requires manual parent switching and does not park the mux on the reference clock when the clock is disabled. Convert both to clk_regmap_phy_mux with clk_regmap_phy_mux_ops, which automatically parks the mux on the XO/ref source on disable and restores the PHY parent on enable, matching the existing UFS symbol clock conversions in this driver. Fixes: a4f780cd5c7a ("clk: qcom: gcc: Add multiple global clock controller driver for Nord SoC") Signed-off-by: Taniya Das Tested-by: Shawn Guo Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260716-b4-nord-pipe-clk-fixes-v1-2-e4f583633356@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/negcc-nord.c | 49 +++++++++-------------------------- 1 file changed, 12 insertions(+), 37 deletions(-) diff --git a/drivers/clk/qcom/negcc-nord.c b/drivers/clk/qcom/negcc-nord.c index 19e4ebbe313f..09c7e957be54 100644 --- a/drivers/clk/qcom/negcc-nord.c +++ b/drivers/clk/qcom/negcc-nord.c @@ -17,7 +17,6 @@ #include "clk-rcg.h" #include "clk-regmap.h" #include "clk-regmap-divider.h" -#include "clk-regmap-mux.h" #include "clk-regmap-phy-mux.h" #include "common.h" #include "gdsc.h" @@ -42,8 +41,6 @@ enum { P_UFS_PHY_RX_SYMBOL_0_CLK, P_UFS_PHY_RX_SYMBOL_1_CLK, P_UFS_PHY_TX_SYMBOL_0_CLK, - P_USB3_PHY_SEC_WRAPPER_NE_GCC_USB31_PIPE_CLK, - P_USB3_PHY_WRAPPER_NE_GCC_USB31_PIPE_CLK, }; static struct clk_alpha_pll ne_gcc_gpll0 = { @@ -166,26 +163,6 @@ static const struct clk_parent_data ne_gcc_parent_data_5[] = { { .index = DT_BI_TCXO }, }; -static const struct parent_map ne_gcc_parent_map_6[] = { - { P_USB3_PHY_WRAPPER_NE_GCC_USB31_PIPE_CLK, 0 }, - { P_BI_TCXO, 2 }, -}; - -static const struct clk_parent_data ne_gcc_parent_data_6[] = { - { .index = DT_USB3_PHY_WRAPPER_NE_GCC_USB31_PIPE_CLK }, - { .index = DT_BI_TCXO }, -}; - -static const struct parent_map ne_gcc_parent_map_7[] = { - { P_USB3_PHY_SEC_WRAPPER_NE_GCC_USB31_PIPE_CLK, 0 }, - { P_BI_TCXO, 2 }, -}; - -static const struct clk_parent_data ne_gcc_parent_data_7[] = { - { .index = DT_USB3_PHY_SEC_WRAPPER_NE_GCC_USB31_PIPE_CLK }, - { .index = DT_BI_TCXO }, -}; - static struct clk_regmap_phy_mux ne_gcc_ufs_phy_rx_symbol_0_clk_src = { .reg = 0x33068, .clkr = { @@ -228,32 +205,30 @@ static struct clk_regmap_phy_mux ne_gcc_ufs_phy_tx_symbol_0_clk_src = { }, }; -static struct clk_regmap_mux ne_gcc_usb3_prim_phy_pipe_clk_src = { +static struct clk_regmap_phy_mux ne_gcc_usb3_prim_phy_pipe_clk_src = { .reg = 0x2a078, - .shift = 0, - .width = 2, - .parent_map = ne_gcc_parent_map_6, .clkr = { .hw.init = &(const struct clk_init_data) { .name = "ne_gcc_usb3_prim_phy_pipe_clk_src", - .parent_data = ne_gcc_parent_data_6, - .num_parents = ARRAY_SIZE(ne_gcc_parent_data_6), - .ops = &clk_regmap_mux_closest_ops, + .parent_data = &(const struct clk_parent_data){ + .index = DT_USB3_PHY_WRAPPER_NE_GCC_USB31_PIPE_CLK, + }, + .num_parents = 1, + .ops = &clk_regmap_phy_mux_ops, }, }, }; -static struct clk_regmap_mux ne_gcc_usb3_sec_phy_pipe_clk_src = { +static struct clk_regmap_phy_mux ne_gcc_usb3_sec_phy_pipe_clk_src = { .reg = 0x2c078, - .shift = 0, - .width = 2, - .parent_map = ne_gcc_parent_map_7, .clkr = { .hw.init = &(const struct clk_init_data) { .name = "ne_gcc_usb3_sec_phy_pipe_clk_src", - .parent_data = ne_gcc_parent_data_7, - .num_parents = ARRAY_SIZE(ne_gcc_parent_data_7), - .ops = &clk_regmap_mux_closest_ops, + .parent_data = &(const struct clk_parent_data){ + .index = DT_USB3_PHY_SEC_WRAPPER_NE_GCC_USB31_PIPE_CLK, + }, + .num_parents = 1, + .ops = &clk_regmap_phy_mux_ops, }, }, }; From 470bd66370ecbf22c2dc27249315b9d39914c479 Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Fri, 24 Jul 2026 11:03:53 +0200 Subject: [PATCH 0363/1328] clk: qcom: clean-up simple provider misuse of the consumer API Clock provider should not be using the consumer interface. In other words, a provider should not be dealing with struct clk. This change targets occurrences for which the provider uses the consumer interface and corresponding clk_hw interface exist. Reviewed-by: Brian Masney Signed-off-by: Jerome Brunet Reviewed-by: Thierry Reding Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260724-clk-provider-simple-clean-v2-2-56f306156d25@baylibre.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/clk-hfpll.c | 4 ++-- drivers/clk/qcom/clk-krait.c | 2 +- drivers/clk/qcom/clk-rcg.c | 4 ++-- drivers/clk/qcom/clk-rcg2.c | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/clk/qcom/clk-hfpll.c b/drivers/clk/qcom/clk-hfpll.c index 705352aff067..9e9118d24df7 100644 --- a/drivers/clk/qcom/clk-hfpll.c +++ b/drivers/clk/qcom/clk-hfpll.c @@ -166,7 +166,7 @@ static int clk_hfpll_set_rate(struct clk_hw *hw, unsigned long rate, spin_lock_irqsave(&h->lock, flags); - enabled = __clk_is_enabled(hw->clk); + enabled = clk_hw_is_enabled(hw); if (enabled) __clk_hfpll_disable(h); @@ -220,7 +220,7 @@ static int clk_hfpll_init(struct clk_hw *hw) regmap_read(regmap, hd->status_reg, &status); if (!(status & BIT(hd->lock_bit))) { WARN(1, "HFPLL %s is ON, but not locked!\n", - __clk_get_name(hw->clk)); + clk_hw_get_name(hw)); clk_hfpll_disable(hw); __clk_hfpll_init_once(hw); } diff --git a/drivers/clk/qcom/clk-krait.c b/drivers/clk/qcom/clk-krait.c index f5ce403e1e27..5e4408b7445d 100644 --- a/drivers/clk/qcom/clk-krait.c +++ b/drivers/clk/qcom/clk-krait.c @@ -68,7 +68,7 @@ static int krait_mux_set_parent(struct clk_hw *hw, u8 index) sel = clk_mux_index_to_val(mux->parent_map, 0, index); mux->en_mask = sel; /* Don't touch mux if CPU is off as it won't work */ - if (__clk_is_enabled(hw->clk)) + if (clk_hw_is_enabled(hw)) __krait_mux_set_sel(mux, sel); mux->reparent = true; diff --git a/drivers/clk/qcom/clk-rcg.c b/drivers/clk/qcom/clk-rcg.c index 31f0650b48ba..1664101e8f78 100644 --- a/drivers/clk/qcom/clk-rcg.c +++ b/drivers/clk/qcom/clk-rcg.c @@ -208,7 +208,7 @@ static int configure_bank(struct clk_dyn_rcg *rcg, const struct freq_tbl *f) bool banked_p = !!rcg->p[1].pre_div_width; struct clk_hw *hw = &rcg->clkr.hw; - enabled = __clk_is_enabled(hw->clk); + enabled = clk_hw_is_enabled(hw); ret = regmap_read(rcg->clkr.regmap, rcg->bank_reg, ®); if (ret) @@ -771,7 +771,7 @@ static int clk_rcg_lcc_set_rate(struct clk_hw *hw, unsigned long rate, regmap_update_bits(rcg->clkr.regmap, rcg->ns_reg, gfm, 0); ret = __clk_rcg_set_rate(rcg, f); /* Switch back to M/N if it's clocking */ - if (__clk_is_enabled(hw->clk)) + if (clk_hw_is_enabled(hw)) regmap_update_bits(rcg->clkr.regmap, rcg->ns_reg, gfm, gfm); return ret; diff --git a/drivers/clk/qcom/clk-rcg2.c b/drivers/clk/qcom/clk-rcg2.c index 6064a0e17d51..d7914e59129f 100644 --- a/drivers/clk/qcom/clk-rcg2.c +++ b/drivers/clk/qcom/clk-rcg2.c @@ -489,7 +489,7 @@ static int clk_rcg2_determine_gp_rate(struct clk_hw *hw, u64 parent_rate; parent = clk_hw_get_parent(hw); - parent_rate = clk_get_rate(parent->clk); + parent_rate = clk_hw_get_rate(parent); if (!parent_rate) return -EINVAL; From 6c3a4d971503b04b00d75a37882becb3c2f57f30 Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Fri, 24 Jul 2026 16:25:05 +0530 Subject: [PATCH 0364/1328] clk: qcom: gcc-nord: mark PCIe link clocks as critical The PCIe link AHB and XO clocks must remain enabled for proper operation. Representing them as clk_branch instances allows them to be gated, which is undesirable. Remove their clk_branch definitions and register their CBCRs as critical clocks instead so they remain enabled. This matches the handling of similar always-on clocks in other Qualcomm clock drivers. Fixes: a4f780cd5c7a ("clk: qcom: gcc: Add multiple global clock controller driver for Nord SoC") Reviewed-by: Konrad Dybcio Reviewed-by: Alexandre Mergnat Signed-off-by: Taniya Das Link: https://lore.kernel.org/r/20260724-nords_mm_v1-v3-1-32b45232217f@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gcc-nord.c | 37 +++++++------------------------------ 1 file changed, 7 insertions(+), 30 deletions(-) diff --git a/drivers/clk/qcom/gcc-nord.c b/drivers/clk/qcom/gcc-nord.c index 93ecebeab826..eae2a7720fa8 100644 --- a/drivers/clk/qcom/gcc-nord.c +++ b/drivers/clk/qcom/gcc-nord.c @@ -1185,34 +1185,6 @@ static struct clk_branch gcc_pcie_d_slv_q2a_axi_clk = { }, }; -static struct clk_branch gcc_pcie_link_ahb_clk = { - .halt_reg = 0x52464, - .halt_check = BRANCH_HALT, - .clkr = { - .enable_reg = 0x52464, - .enable_mask = BIT(0), - .hw.init = &(const struct clk_init_data) { - .name = "gcc_pcie_link_ahb_clk", - .ops = &clk_branch2_ops, - }, - }, -}; - -static struct clk_branch gcc_pcie_link_xo_clk = { - .halt_reg = 0x52468, - .halt_check = BRANCH_HALT_VOTED, - .hwcg_reg = 0x52468, - .hwcg_bit = 1, - .clkr = { - .enable_reg = 0x52468, - .enable_mask = BIT(0), - .hw.init = &(const struct clk_init_data) { - .name = "gcc_pcie_link_xo_clk", - .ops = &clk_branch2_ops, - }, - }, -}; - static struct clk_branch gcc_pcie_noc_async_bridge_clk = { .halt_reg = 0x52048, .halt_check = BRANCH_HALT_SKIP, @@ -1758,8 +1730,6 @@ static struct clk_regmap *gcc_nord_clocks[] = { [GCC_PCIE_D_PIPE_CLK_SRC] = &gcc_pcie_d_pipe_clk_src.clkr, [GCC_PCIE_D_SLV_AXI_CLK] = &gcc_pcie_d_slv_axi_clk.clkr, [GCC_PCIE_D_SLV_Q2A_AXI_CLK] = &gcc_pcie_d_slv_q2a_axi_clk.clkr, - [GCC_PCIE_LINK_AHB_CLK] = &gcc_pcie_link_ahb_clk.clkr, - [GCC_PCIE_LINK_XO_CLK] = &gcc_pcie_link_xo_clk.clkr, [GCC_PCIE_NOC_ASYNC_BRIDGE_CLK] = &gcc_pcie_noc_async_bridge_clk.clkr, [GCC_PCIE_NOC_CNOC_SF_QX_CLK] = &gcc_pcie_noc_cnoc_sf_qx_clk.clkr, [GCC_PCIE_NOC_M_CFG_CLK] = &gcc_pcie_noc_m_cfg_clk.clkr, @@ -1850,9 +1820,16 @@ static const struct regmap_config gcc_nord_regmap_config = { .fast_io = true, }; +static const u32 gcc_nord_critical_cbcrs[] = { + 0x52464, /* GCC_PCIE_LINK_AHB_CLK */ + 0x52468, /* GCC_PCIE_LINK_XO_CLK */ +}; + static const struct qcom_cc_driver_data gcc_nord_driver_data = { .dfs_rcgs = gcc_nord_dfs_clocks, .num_dfs_rcgs = ARRAY_SIZE(gcc_nord_dfs_clocks), + .clk_cbcrs = gcc_nord_critical_cbcrs, + .num_clk_cbcrs = ARRAY_SIZE(gcc_nord_critical_cbcrs), }; static const struct qcom_cc_desc gcc_nord_desc = { From 936e98ef12f714c19150a5595bbd296d9489b2e1 Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Fri, 24 Jul 2026 16:25:06 +0530 Subject: [PATCH 0365/1328] clk: qcom: negcc-nord: keep GPU2 CFG clock enabled via critical CBCR The GPU2 CFG clock must remain enabled for correct operation and should not be exposed as a controllable clk_branch. Remove the clk_branch and mark its CBCR as critical instead to prevent unintended gating. This follows the same approach as 'nw_gcc_gpu_cfg_ahb_clk' and aligns with other always-on clocks in Qualcomm CC drivers. Fixes: a4f780cd5c7a ("clk: qcom: gcc: Add multiple global clock controller driver for Nord SoC") Reviewed-by: Konrad Dybcio Reviewed-by: Alexandre Mergnat Signed-off-by: Taniya Das Link: https://lore.kernel.org/r/20260724-nords_mm_v1-v3-2-32b45232217f@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/negcc-nord.c | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/drivers/clk/qcom/negcc-nord.c b/drivers/clk/qcom/negcc-nord.c index 09c7e957be54..bb3b798caf05 100644 --- a/drivers/clk/qcom/negcc-nord.c +++ b/drivers/clk/qcom/negcc-nord.c @@ -927,21 +927,6 @@ static struct clk_branch ne_gcc_gp2_clk = { }, }; -static struct clk_branch ne_gcc_gpu_2_cfg_clk = { - .halt_reg = 0x34004, - .halt_check = BRANCH_HALT_VOTED, - .hwcg_reg = 0x34004, - .hwcg_bit = 1, - .clkr = { - .enable_reg = 0x34004, - .enable_mask = BIT(0), - .hw.init = &(const struct clk_init_data) { - .name = "ne_gcc_gpu_2_cfg_clk", - .ops = &clk_branch2_ops, - }, - }, -}; - static struct clk_branch ne_gcc_gpu_2_gpll0_clk_src = { .halt_check = BRANCH_HALT_DELAY, .clkr = { @@ -1792,7 +1777,6 @@ static struct clk_regmap *ne_gcc_nord_clocks[] = { [NE_GCC_GPLL0] = &ne_gcc_gpll0.clkr, [NE_GCC_GPLL0_OUT_EVEN] = &ne_gcc_gpll0_out_even.clkr, [NE_GCC_GPLL2] = &ne_gcc_gpll2.clkr, - [NE_GCC_GPU_2_CFG_CLK] = &ne_gcc_gpu_2_cfg_clk.clkr, [NE_GCC_GPU_2_GPLL0_CLK_SRC] = &ne_gcc_gpu_2_gpll0_clk_src.clkr, [NE_GCC_GPU_2_GPLL0_DIV_CLK_SRC] = &ne_gcc_gpu_2_gpll0_div_clk_src.clkr, [NE_GCC_GPU_2_HSCNOC_GFX_CLK] = &ne_gcc_gpu_2_hscnoc_gfx_clk.clkr, @@ -1921,10 +1905,16 @@ static void clk_nord_regs_configure(struct device *dev, struct regmap *regmap) qcom_branch_set_force_mem_core(regmap, ne_gcc_ufs_phy_axi_clk, true); } +static const u32 ne_gcc_nord_critical_cbcrs[] = { + 0x34004, /* NE_GCC_GPU_2_CFG_CLK */ +}; + static const struct qcom_cc_driver_data ne_gcc_nord_driver_data = { .dfs_rcgs = ne_gcc_nord_dfs_clocks, .num_dfs_rcgs = ARRAY_SIZE(ne_gcc_nord_dfs_clocks), .clk_regs_configure = clk_nord_regs_configure, + .clk_cbcrs = ne_gcc_nord_critical_cbcrs, + .num_clk_cbcrs = ARRAY_SIZE(ne_gcc_nord_critical_cbcrs), }; static const struct qcom_cc_desc ne_gcc_nord_desc = { From d6c34c2929f56c9f349062c751ee5c483df3af25 Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Fri, 24 Jul 2026 16:25:07 +0530 Subject: [PATCH 0366/1328] dt-bindings: clock: qcom: Document Nord display clock controller Add Device Tree binding documentation for the display clock controller on the Qualcomm Nord SoC. The Nord platform contains two instances of the display clock controller, DISPCC_0 and DISPCC_1. Update the bindings to include compatible strings for both instances. Reviewed-by: Alexandre Mergnat Reviewed-by: Krzysztof Kozlowski Reviewed-by: Shawn Guo Signed-off-by: Taniya Das Link: https://lore.kernel.org/r/20260724-nords_mm_v1-v3-3-32b45232217f@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- .../bindings/clock/qcom,sm8550-dispcc.yaml | 3 + include/dt-bindings/clock/qcom,nord-dispcc.h | 115 ++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 include/dt-bindings/clock/qcom,nord-dispcc.h diff --git a/Documentation/devicetree/bindings/clock/qcom,sm8550-dispcc.yaml b/Documentation/devicetree/bindings/clock/qcom,sm8550-dispcc.yaml index 591ce91b8d54..61f58fbd5bd2 100644 --- a/Documentation/devicetree/bindings/clock/qcom,sm8550-dispcc.yaml +++ b/Documentation/devicetree/bindings/clock/qcom,sm8550-dispcc.yaml @@ -16,6 +16,7 @@ description: | See also: - include/dt-bindings/clock/qcom,kaanapali-dispcc.h + - include/dt-bindings/clock/qcom,nord-dispcc.h - include/dt-bindings/clock/qcom,sm8550-dispcc.h - include/dt-bindings/clock/qcom,sm8650-dispcc.h - include/dt-bindings/clock/qcom,sm8750-dispcc.h @@ -25,6 +26,8 @@ properties: compatible: enum: - qcom,kaanapali-dispcc + - qcom,nord-dispcc0 + - qcom,nord-dispcc1 - qcom,sar2130p-dispcc - qcom,sm8550-dispcc - qcom,sm8650-dispcc diff --git a/include/dt-bindings/clock/qcom,nord-dispcc.h b/include/dt-bindings/clock/qcom,nord-dispcc.h new file mode 100644 index 000000000000..219facbbda10 --- /dev/null +++ b/include/dt-bindings/clock/qcom,nord-dispcc.h @@ -0,0 +1,115 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef _DT_BINDINGS_CLK_QCOM_DISP_CC_NORD_H +#define _DT_BINDINGS_CLK_QCOM_DISP_CC_NORD_H + +/* DISP_CC_0 and DISP_CC_1 clocks */ +#define MDSS_DISP_CC_ACMU_CLK 0 +#define MDSS_DISP_CC_MDSS_ACCU_SHIFT_CLK 1 +#define MDSS_DISP_CC_MDSS_AHB1_CLK 2 +#define MDSS_DISP_CC_MDSS_AHB_CLK 3 +#define MDSS_DISP_CC_MDSS_AHB_CLK_SRC 4 +#define MDSS_DISP_CC_MDSS_BYTE0_CLK 5 +#define MDSS_DISP_CC_MDSS_BYTE0_CLK_SRC 6 +#define MDSS_DISP_CC_MDSS_BYTE0_DIV_CLK_SRC 7 +#define MDSS_DISP_CC_MDSS_BYTE0_INTF_CLK 8 +#define MDSS_DISP_CC_MDSS_BYTE1_CLK 9 +#define MDSS_DISP_CC_MDSS_BYTE1_CLK_SRC 10 +#define MDSS_DISP_CC_MDSS_BYTE1_DIV_CLK_SRC 11 +#define MDSS_DISP_CC_MDSS_BYTE1_INTF_CLK 12 +#define MDSS_DISP_CC_MDSS_DPTX0_AUX_CLK 13 +#define MDSS_DISP_CC_MDSS_DPTX0_AUX_CLK_SRC 14 +#define MDSS_DISP_CC_MDSS_DPTX0_CRYPTO_CLK 15 +#define MDSS_DISP_CC_MDSS_DPTX0_LINK_CLK 16 +#define MDSS_DISP_CC_MDSS_DPTX0_LINK_CLK_SRC 17 +#define MDSS_DISP_CC_MDSS_DPTX0_LINK_DIV_CLK_SRC 18 +#define MDSS_DISP_CC_MDSS_DPTX0_LINK_INTF_CLK 19 +#define MDSS_DISP_CC_MDSS_DPTX0_PIXEL0_CLK 20 +#define MDSS_DISP_CC_MDSS_DPTX0_PIXEL0_CLK_SRC 21 +#define MDSS_DISP_CC_MDSS_DPTX0_PIXEL1_CLK 22 +#define MDSS_DISP_CC_MDSS_DPTX0_PIXEL1_CLK_SRC 23 +#define MDSS_DISP_CC_MDSS_DPTX0_PIXEL2_CLK 24 +#define MDSS_DISP_CC_MDSS_DPTX0_PIXEL2_CLK_SRC 25 +#define MDSS_DISP_CC_MDSS_DPTX0_PIXEL3_CLK 26 +#define MDSS_DISP_CC_MDSS_DPTX0_PIXEL3_CLK_SRC 27 +#define MDSS_DISP_CC_MDSS_DPTX0_USB_ROUTER_LINK_INTF_CLK 28 +#define MDSS_DISP_CC_MDSS_DPTX1_AUX_CLK 29 +#define MDSS_DISP_CC_MDSS_DPTX1_AUX_CLK_SRC 30 +#define MDSS_DISP_CC_MDSS_DPTX1_CRYPTO_CLK 31 +#define MDSS_DISP_CC_MDSS_DPTX1_LINK_CLK 32 +#define MDSS_DISP_CC_MDSS_DPTX1_LINK_CLK_SRC 33 +#define MDSS_DISP_CC_MDSS_DPTX1_LINK_DIV_CLK_SRC 34 +#define MDSS_DISP_CC_MDSS_DPTX1_LINK_INTF_CLK 35 +#define MDSS_DISP_CC_MDSS_DPTX1_PIXEL0_CLK 36 +#define MDSS_DISP_CC_MDSS_DPTX1_PIXEL0_CLK_SRC 37 +#define MDSS_DISP_CC_MDSS_DPTX1_PIXEL1_CLK 38 +#define MDSS_DISP_CC_MDSS_DPTX1_PIXEL1_CLK_SRC 39 +#define MDSS_DISP_CC_MDSS_DPTX1_PIXEL2_CLK 40 +#define MDSS_DISP_CC_MDSS_DPTX1_PIXEL2_CLK_SRC 41 +#define MDSS_DISP_CC_MDSS_DPTX1_PIXEL3_CLK 42 +#define MDSS_DISP_CC_MDSS_DPTX1_PIXEL3_CLK_SRC 43 +#define MDSS_DISP_CC_MDSS_DPTX1_USB_ROUTER_LINK_INTF_CLK 44 +#define MDSS_DISP_CC_MDSS_DPTX2_AUX_CLK 45 +#define MDSS_DISP_CC_MDSS_DPTX2_AUX_CLK_SRC 46 +#define MDSS_DISP_CC_MDSS_DPTX2_CRYPTO_CLK 47 +#define MDSS_DISP_CC_MDSS_DPTX2_LINK_CLK 48 +#define MDSS_DISP_CC_MDSS_DPTX2_LINK_CLK_SRC 49 +#define MDSS_DISP_CC_MDSS_DPTX2_LINK_DIV_CLK_SRC 50 +#define MDSS_DISP_CC_MDSS_DPTX2_LINK_INTF_CLK 51 +#define MDSS_DISP_CC_MDSS_DPTX2_PIXEL0_CLK 52 +#define MDSS_DISP_CC_MDSS_DPTX2_PIXEL0_CLK_SRC 53 +#define MDSS_DISP_CC_MDSS_DPTX2_PIXEL1_CLK 54 +#define MDSS_DISP_CC_MDSS_DPTX2_PIXEL1_CLK_SRC 55 +#define MDSS_DISP_CC_MDSS_DPTX3_AUX_CLK 56 +#define MDSS_DISP_CC_MDSS_DPTX3_AUX_CLK_SRC 57 +#define MDSS_DISP_CC_MDSS_DPTX3_CRYPTO_CLK 58 +#define MDSS_DISP_CC_MDSS_DPTX3_LINK_CLK 59 +#define MDSS_DISP_CC_MDSS_DPTX3_LINK_CLK_SRC 60 +#define MDSS_DISP_CC_MDSS_DPTX3_LINK_DIV_CLK_SRC 61 +#define MDSS_DISP_CC_MDSS_DPTX3_LINK_INTF_CLK 62 +#define MDSS_DISP_CC_MDSS_DPTX3_PIXEL0_CLK 63 +#define MDSS_DISP_CC_MDSS_DPTX3_PIXEL0_CLK_SRC 64 +#define MDSS_DISP_CC_MDSS_ESC0_CLK 65 +#define MDSS_DISP_CC_MDSS_ESC0_CLK_SRC 66 +#define MDSS_DISP_CC_MDSS_ESC1_CLK 67 +#define MDSS_DISP_CC_MDSS_ESC1_CLK_SRC 68 +#define MDSS_DISP_CC_MDSS_MDP1_CLK 69 +#define MDSS_DISP_CC_MDSS_MDP_CLK 70 +#define MDSS_DISP_CC_MDSS_MDP_CLK_SRC 71 +#define MDSS_DISP_CC_MDSS_MDP_LUT1_CLK 72 +#define MDSS_DISP_CC_MDSS_MDP_LUT_CLK 73 +#define MDSS_DISP_CC_MDSS_NON_GDSC_AHB_CLK 74 +#define MDSS_DISP_CC_MDSS_PCLK0_CLK 75 +#define MDSS_DISP_CC_MDSS_PCLK0_CLK_SRC 76 +#define MDSS_DISP_CC_MDSS_PCLK1_CLK 77 +#define MDSS_DISP_CC_MDSS_PCLK1_CLK_SRC 78 +#define MDSS_DISP_CC_MDSS_PCLK2_CLK 79 +#define MDSS_DISP_CC_MDSS_PCLK2_CLK_SRC 80 +#define MDSS_DISP_CC_MDSS_RSCC_AHB_CLK 81 +#define MDSS_DISP_CC_MDSS_RSCC_VSYNC_CLK 82 +#define MDSS_DISP_CC_MDSS_VSYNC1_CLK 83 +#define MDSS_DISP_CC_MDSS_VSYNC_CLK 84 +#define MDSS_DISP_CC_MDSS_VSYNC_CLK_SRC 85 +#define MDSS_DISP_CC_PLL0 86 +#define MDSS_DISP_CC_PLL1 87 +#define MDSS_DISP_CC_PLL2 88 +#define MDSS_DISP_CC_PLL3 89 +#define MDSS_DISP_CC_SLEEP_CLK 90 +#define MDSS_DISP_CC_SLEEP_CLK_SRC 91 +#define MDSS_DISP_CC_SM_DIV_CLK_SRC 92 +#define MDSS_DISP_CC_XO_CLK 93 +#define MDSS_DISP_CC_XO_CLK_SRC 94 + +/* DISP_CC_0 and DISP_CC_1 power domains */ +#define MDSS_DISP_CC_MDSS_CORE_GDSC 0 +#define MDSS_DISP_CC_MDSS_CORE_INT2_GDSC 1 + +/* DISP_CC_0 and DISP_CC_1 resets */ +#define MDSS_DISP_CC_MDSS_CORE_BCR 0 +#define MDSS_DISP_CC_MDSS_CORE_INT2_BCR 1 +#define MDSS_DISP_CC_MDSS_RSCC_BCR 2 + +#endif From 9dcc782e208251471cade8ac36c7717a7345ecb4 Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Fri, 24 Jul 2026 16:25:08 +0530 Subject: [PATCH 0367/1328] clk: qcom: Add Nord display clock controller support Add support for the display clock controllers (DISPCC) on the Qualcomm Nord platform. The platform includes two display clock controller instances, display0 and display1. Register support for both controllers. Reviewed-by: Konrad Dybcio Reviewed-by: Shawn Guo Signed-off-by: Taniya Das Reviewed-by: Alexandre Mergnat Link: https://lore.kernel.org/r/20260724-nords_mm_v1-v3-4-32b45232217f@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/Kconfig | 11 + drivers/clk/qcom/Makefile | 1 + drivers/clk/qcom/dispcc0-nord.c | 2004 +++++++++++++++++++++++++++++++ drivers/clk/qcom/dispcc1-nord.c | 2004 +++++++++++++++++++++++++++++++ 4 files changed, 4020 insertions(+) create mode 100644 drivers/clk/qcom/dispcc0-nord.c create mode 100644 drivers/clk/qcom/dispcc1-nord.c diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig index 63f271aff177..9f125da2a5ae 100644 --- a/drivers/clk/qcom/Kconfig +++ b/drivers/clk/qcom/Kconfig @@ -181,6 +181,17 @@ config CLK_KAANAPALI_VIDEOCC Say Y if you want to support video devices and functionality such as video encode/decode. +config CLK_NORD_DISPCC + tristate "Nord Display Clock Controller" + depends on ARM64 || COMPILE_TEST + select CLK_NORD_GCC + default m if ARCH_QCOM + help + Support for the display clock controllers on Qualcomm Technologies, Inc + Nord devices. There are two display clock controllers on Nord SoC. + Say Y if you want to support display devices and functionality such as + splash screen. + config CLK_NORD_GCC tristate "Nord Global Clock Controller" depends on ARM64 || COMPILE_TEST diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile index fff069da16de..a7572a6712e9 100644 --- a/drivers/clk/qcom/Makefile +++ b/drivers/clk/qcom/Makefile @@ -41,6 +41,7 @@ obj-$(CONFIG_CLK_KAANAPALI_GCC) += gcc-kaanapali.o obj-$(CONFIG_CLK_KAANAPALI_GPUCC) += gpucc-kaanapali.o gxclkctl-kaanapali.o obj-$(CONFIG_CLK_KAANAPALI_TCSRCC) += tcsrcc-kaanapali.o obj-$(CONFIG_CLK_KAANAPALI_VIDEOCC) += videocc-kaanapali.o +obj-$(CONFIG_CLK_NORD_DISPCC) += dispcc0-nord.o dispcc1-nord.o obj-$(CONFIG_CLK_NORD_GCC) += gcc-nord.o negcc-nord.o nwgcc-nord.o segcc-nord.o obj-$(CONFIG_CLK_NORD_TCSRCC) += tcsrcc-nord.o obj-$(CONFIG_CLK_SHIKRA_GCC) += gcc-shikra.o diff --git a/drivers/clk/qcom/dispcc0-nord.c b/drivers/clk/qcom/dispcc0-nord.c new file mode 100644 index 000000000000..835aba159613 --- /dev/null +++ b/drivers/clk/qcom/dispcc0-nord.c @@ -0,0 +1,2004 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include + +#include + +#include "clk-alpha-pll.h" +#include "clk-branch.h" +#include "clk-pll.h" +#include "clk-rcg.h" +#include "clk-regmap.h" +#include "clk-regmap-divider.h" +#include "clk-regmap-mux.h" +#include "common.h" +#include "gdsc.h" +#include "reset.h" + +enum { + DT_BI_TCXO, + DT_BI_TCXO_AO, + DT_AHB_CLK, + DT_SLEEP_CLK, + + DT_DSI0_PHY_PLL_OUT_BYTECLK, + DT_DSI0_PHY_PLL_OUT_DSICLK, + DT_DSI1_PHY_PLL_OUT_BYTECLK, + DT_DSI1_PHY_PLL_OUT_DSICLK, + + DT_DP0_PHY_PLL_LINK_CLK, + DT_DP0_PHY_PLL_VCO_DIV_CLK, + DT_DP1_PHY_PLL_LINK_CLK, + DT_DP1_PHY_PLL_VCO_DIV_CLK, + DT_DP2_PHY_PLL_LINK_CLK, + DT_DP2_PHY_PLL_VCO_DIV_CLK, + DT_DP3_PHY_PLL_LINK_CLK, + DT_DP3_PHY_PLL_VCO_DIV_CLK, +}; + +enum { + P_BI_TCXO, + P_MDSS_0_DISP_CC_PLL0_OUT_MAIN, + P_MDSS_0_DISP_CC_PLL1_OUT_EVEN, + P_MDSS_0_DISP_CC_PLL1_OUT_MAIN, + P_MDSS_0_DISP_CC_PLL2_OUT_MAIN, + P_MDSS_0_DISP_CC_PLL3_OUT_MAIN, + P_DP0_PHY_PLL_LINK_CLK, + P_DP0_PHY_PLL_VCO_DIV_CLK, + P_DP1_PHY_PLL_LINK_CLK, + P_DP1_PHY_PLL_VCO_DIV_CLK, + P_DP2_PHY_PLL_LINK_CLK, + P_DP2_PHY_PLL_VCO_DIV_CLK, + P_DP3_PHY_PLL_LINK_CLK, + P_DP3_PHY_PLL_VCO_DIV_CLK, + P_DSI0_PHY_PLL_OUT_BYTECLK, + P_DSI0_PHY_PLL_OUT_DSICLK, + P_DSI1_PHY_PLL_OUT_BYTECLK, + P_DSI1_PHY_PLL_OUT_DSICLK, + P_SLEEP_CLK, +}; + +static const struct pll_vco lucid_ole_vco[] = { + { 249600000, 2300000000, 0 }, +}; + +static const struct pll_vco zonda_ole_vco[] = { + { 700000000, 3600000000, 0 }, +}; + +/* 900.0 MHz Configuration */ +static const struct alpha_pll_config mdss_0_disp_cc_pll0_config = { + .l = 0x2e, + .alpha = 0xe000, + .config_ctl_val = 0x20485699, + .config_ctl_hi_val = 0x00182261, + .config_ctl_hi1_val = 0x82aa299c, + .test_ctl_val = 0x00000000, + .test_ctl_hi_val = 0x00000003, + .test_ctl_hi1_val = 0x00009000, + .test_ctl_hi2_val = 0x00000034, + .user_ctl_val = 0x00000000, + .user_ctl_hi_val = 0x00400005, +}; + +static struct clk_alpha_pll mdss_0_disp_cc_pll0 = { + .offset = 0x0, + .config = &mdss_0_disp_cc_pll0_config, + .vco_table = lucid_ole_vco, + .num_vco = ARRAY_SIZE(lucid_ole_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_pll0", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_lucid_evo_ops, + }, + }, +}; + +/* 600.0 MHz Configuration */ +static const struct alpha_pll_config mdss_0_disp_cc_pll1_config = { + .l = 0x1f, + .alpha = 0x4000, + .config_ctl_val = 0x20485699, + .config_ctl_hi_val = 0x00182261, + .config_ctl_hi1_val = 0x82aa299c, + .test_ctl_val = 0x00000000, + .test_ctl_hi_val = 0x00000003, + .test_ctl_hi1_val = 0x00009000, + .test_ctl_hi2_val = 0x00000034, + .user_ctl_val = 0x00000000, + .user_ctl_hi_val = 0x00400005, +}; + +static struct clk_alpha_pll mdss_0_disp_cc_pll1 = { + .offset = 0x1000, + .config = &mdss_0_disp_cc_pll1_config, + .vco_table = lucid_ole_vco, + .num_vco = ARRAY_SIZE(lucid_ole_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_pll1", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_lucid_evo_ops, + }, + }, +}; + +/* 1363.2 MHz Configuration */ +static const struct alpha_pll_config mdss_0_disp_cc_pll2_config = { + .l = 0x47, + .alpha = 0x0, + .config_ctl_val = 0x08240800, + .config_ctl_hi_val = 0x05008001, + .config_ctl_hi1_val = 0x00000000, + .config_ctl_hi2_val = 0x00000000, + .user_ctl_val = 0x00000000, + .user_ctl_hi_val = 0x00000080, +}; + +static struct clk_alpha_pll mdss_0_disp_cc_pll2 = { + .offset = 0x2000, + .config = &mdss_0_disp_cc_pll2_config, + .vco_table = zonda_ole_vco, + .num_vco = ARRAY_SIZE(zonda_ole_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_ZONDA_OLE], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_pll2", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_zonda_ole_ops, + }, + }, +}; + +/* 1363.2 MHz Configuration */ +static const struct alpha_pll_config mdss_0_disp_cc_pll3_config = { + .l = 0x47, + .alpha = 0x0, + .config_ctl_val = 0x08240800, + .config_ctl_hi_val = 0x05008001, + .config_ctl_hi1_val = 0x00000000, + .config_ctl_hi2_val = 0x00000000, + .user_ctl_val = 0x00000000, + .user_ctl_hi_val = 0x00000080, +}; + +static struct clk_alpha_pll mdss_0_disp_cc_pll3 = { + .offset = 0x3000, + .config = &mdss_0_disp_cc_pll3_config, + .vco_table = zonda_ole_vco, + .num_vco = ARRAY_SIZE(zonda_ole_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_ZONDA_OLE], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_pll3", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_zonda_ole_ops, + }, + }, +}; + +static const struct parent_map disp_cc_0_parent_map_0[] = { + { P_BI_TCXO, 0 }, + { P_MDSS_0_DISP_CC_PLL2_OUT_MAIN, 1 }, + { P_DP0_PHY_PLL_VCO_DIV_CLK, 2 }, + { P_DP3_PHY_PLL_VCO_DIV_CLK, 3 }, + { P_DP1_PHY_PLL_VCO_DIV_CLK, 4 }, + { P_MDSS_0_DISP_CC_PLL3_OUT_MAIN, 5 }, + { P_DP2_PHY_PLL_VCO_DIV_CLK, 6 }, +}; + +static const struct clk_parent_data disp_cc_0_parent_data_0[] = { + { .index = DT_BI_TCXO }, + { .hw = &mdss_0_disp_cc_pll2.clkr.hw }, + { .index = DT_DP0_PHY_PLL_VCO_DIV_CLK }, + { .index = DT_DP3_PHY_PLL_VCO_DIV_CLK }, + { .index = DT_DP1_PHY_PLL_VCO_DIV_CLK }, + { .hw = &mdss_0_disp_cc_pll3.clkr.hw }, + { .index = DT_DP2_PHY_PLL_VCO_DIV_CLK }, +}; + +static const struct parent_map disp_cc_0_parent_map_1[] = { + { P_BI_TCXO, 0 }, +}; + +static const struct clk_parent_data disp_cc_0_parent_data_1[] = { + { .index = DT_BI_TCXO }, +}; + +static const struct parent_map disp_cc_0_parent_map_2[] = { + { P_BI_TCXO, 0 }, + { P_DSI0_PHY_PLL_OUT_DSICLK, 1 }, + { P_DSI0_PHY_PLL_OUT_BYTECLK, 2 }, + { P_DSI1_PHY_PLL_OUT_DSICLK, 3 }, + { P_DSI1_PHY_PLL_OUT_BYTECLK, 4 }, +}; + +static const struct clk_parent_data disp_cc_0_parent_data_2[] = { + { .index = DT_BI_TCXO }, + { .index = DT_DSI0_PHY_PLL_OUT_DSICLK }, + { .index = DT_DSI0_PHY_PLL_OUT_BYTECLK }, + { .index = DT_DSI1_PHY_PLL_OUT_DSICLK }, + { .index = DT_DSI1_PHY_PLL_OUT_BYTECLK }, +}; + +static const struct parent_map disp_cc_0_parent_map_3[] = { + { P_BI_TCXO, 0 }, + { P_MDSS_0_DISP_CC_PLL2_OUT_MAIN, 1 }, + { P_DP3_PHY_PLL_VCO_DIV_CLK, 3 }, + { P_MDSS_0_DISP_CC_PLL3_OUT_MAIN, 5 }, + { P_DP2_PHY_PLL_VCO_DIV_CLK, 6 }, +}; + +static const struct clk_parent_data disp_cc_0_parent_data_3[] = { + { .index = DT_BI_TCXO }, + { .hw = &mdss_0_disp_cc_pll2.clkr.hw }, + { .index = DT_DP3_PHY_PLL_VCO_DIV_CLK }, + { .hw = &mdss_0_disp_cc_pll3.clkr.hw }, + { .index = DT_DP2_PHY_PLL_VCO_DIV_CLK }, +}; + +static const struct parent_map disp_cc_0_parent_map_4[] = { + { P_BI_TCXO, 0 }, + { P_DP0_PHY_PLL_LINK_CLK, 1 }, + { P_DP1_PHY_PLL_LINK_CLK, 2 }, + { P_DP2_PHY_PLL_LINK_CLK, 3 }, + { P_DP3_PHY_PLL_LINK_CLK, 4 }, +}; + +static const struct clk_parent_data disp_cc_0_parent_data_4[] = { + { .index = DT_BI_TCXO }, + { .index = DT_DP0_PHY_PLL_LINK_CLK }, + { .index = DT_DP1_PHY_PLL_LINK_CLK }, + { .index = DT_DP2_PHY_PLL_LINK_CLK }, + { .index = DT_DP3_PHY_PLL_LINK_CLK }, +}; + +static const struct parent_map disp_cc_0_parent_map_5[] = { + { P_BI_TCXO, 0 }, + { P_DP2_PHY_PLL_LINK_CLK, 3 }, + { P_DP3_PHY_PLL_LINK_CLK, 4 }, +}; + +static const struct clk_parent_data disp_cc_0_parent_data_5[] = { + { .index = DT_BI_TCXO }, + { .index = DT_DP2_PHY_PLL_LINK_CLK }, + { .index = DT_DP3_PHY_PLL_LINK_CLK }, +}; + +static const struct parent_map disp_cc_0_parent_map_6[] = { + { P_BI_TCXO, 0 }, + { P_DSI0_PHY_PLL_OUT_BYTECLK, 2 }, + { P_DSI1_PHY_PLL_OUT_BYTECLK, 4 }, +}; + +static const struct clk_parent_data disp_cc_0_parent_data_6[] = { + { .index = DT_BI_TCXO }, + { .index = DT_DSI0_PHY_PLL_OUT_BYTECLK }, + { .index = DT_DSI1_PHY_PLL_OUT_BYTECLK }, +}; + +static const struct parent_map disp_cc_0_parent_map_7[] = { + { P_BI_TCXO, 0 }, + { P_MDSS_0_DISP_CC_PLL1_OUT_MAIN, 4 }, + { P_MDSS_0_DISP_CC_PLL1_OUT_EVEN, 6 }, +}; + +static const struct clk_parent_data disp_cc_0_parent_data_7[] = { + { .index = DT_BI_TCXO }, + { .hw = &mdss_0_disp_cc_pll1.clkr.hw }, + { .hw = &mdss_0_disp_cc_pll1.clkr.hw }, +}; + +static const struct parent_map disp_cc_0_parent_map_8[] = { + { P_BI_TCXO, 0 }, + { P_MDSS_0_DISP_CC_PLL0_OUT_MAIN, 1 }, + { P_MDSS_0_DISP_CC_PLL1_OUT_MAIN, 4 }, + { P_MDSS_0_DISP_CC_PLL1_OUT_EVEN, 6 }, +}; + +static const struct clk_parent_data disp_cc_0_parent_data_8[] = { + { .index = DT_BI_TCXO }, + { .hw = &mdss_0_disp_cc_pll0.clkr.hw }, + { .hw = &mdss_0_disp_cc_pll1.clkr.hw }, + { .hw = &mdss_0_disp_cc_pll1.clkr.hw }, +}; + +static const struct parent_map disp_cc_0_parent_map_9[] = { + { P_SLEEP_CLK, 0 }, +}; + +static const struct clk_parent_data disp_cc_0_parent_data_9[] = { + { .index = DT_SLEEP_CLK }, +}; + +static const struct freq_tbl ftbl_mdss_0_disp_cc_mdss_ahb_clk_src[] = { + F(37500000, P_MDSS_0_DISP_CC_PLL1_OUT_MAIN, 16, 0, 0), + F(75000000, P_MDSS_0_DISP_CC_PLL1_OUT_MAIN, 8, 0, 0), + { } +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_ahb_clk_src = { + .cmd_rcgr = 0x837c, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_7, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_ahb_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_ahb_clk_src", + .parent_data = disp_cc_0_parent_data_7, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_7), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_mdss_0_disp_cc_mdss_byte0_clk_src[] = { + F(19200000, P_BI_TCXO, 1, 0, 0), + { } +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_byte0_clk_src = { + .cmd_rcgr = 0x813c, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_2, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_byte0_clk_src", + .parent_data = disp_cc_0_parent_data_2, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_2), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_byte2_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_byte1_clk_src = { + .cmd_rcgr = 0x8158, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_2, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_byte1_clk_src", + .parent_data = disp_cc_0_parent_data_2, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_2), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_byte2_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_dptx0_aux_clk_src = { + .cmd_rcgr = 0x8220, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_1, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx0_aux_clk_src", + .parent_data = disp_cc_0_parent_data_1, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_dptx0_link_clk_src = { + .cmd_rcgr = 0x81a4, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_4, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx0_link_clk_src", + .parent_data = disp_cc_0_parent_data_4, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_4), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_byte2_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_dptx0_pixel0_clk_src = { + .cmd_rcgr = 0x81c0, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_0, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx0_pixel0_clk_src", + .parent_data = disp_cc_0_parent_data_0, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_dptx0_pixel1_clk_src = { + .cmd_rcgr = 0x81d8, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_0, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx0_pixel1_clk_src", + .parent_data = disp_cc_0_parent_data_0, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_dptx0_pixel2_clk_src = { + .cmd_rcgr = 0x81f0, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_0, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx0_pixel2_clk_src", + .parent_data = disp_cc_0_parent_data_0, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_dptx0_pixel3_clk_src = { + .cmd_rcgr = 0x8208, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_0, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx0_pixel3_clk_src", + .parent_data = disp_cc_0_parent_data_0, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_dptx1_aux_clk_src = { + .cmd_rcgr = 0x82b4, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_1, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx1_aux_clk_src", + .parent_data = disp_cc_0_parent_data_1, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_dptx1_link_clk_src = { + .cmd_rcgr = 0x8298, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_4, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx1_link_clk_src", + .parent_data = disp_cc_0_parent_data_4, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_4), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_byte2_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_dptx1_pixel0_clk_src = { + .cmd_rcgr = 0x8238, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_0, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx1_pixel0_clk_src", + .parent_data = disp_cc_0_parent_data_0, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_dptx1_pixel1_clk_src = { + .cmd_rcgr = 0x8250, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_0, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx1_pixel1_clk_src", + .parent_data = disp_cc_0_parent_data_0, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_dptx1_pixel2_clk_src = { + .cmd_rcgr = 0x8268, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_0, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx1_pixel2_clk_src", + .parent_data = disp_cc_0_parent_data_0, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_dptx1_pixel3_clk_src = { + .cmd_rcgr = 0x8280, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_0, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx1_pixel3_clk_src", + .parent_data = disp_cc_0_parent_data_0, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_dptx2_aux_clk_src = { + .cmd_rcgr = 0x8318, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_1, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx2_aux_clk_src", + .parent_data = disp_cc_0_parent_data_1, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_dptx2_link_clk_src = { + .cmd_rcgr = 0x82cc, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_5, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx2_link_clk_src", + .parent_data = disp_cc_0_parent_data_5, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_5), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_byte2_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_dptx2_pixel0_clk_src = { + .cmd_rcgr = 0x82e8, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_3, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx2_pixel0_clk_src", + .parent_data = disp_cc_0_parent_data_3, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_3), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_dptx2_pixel1_clk_src = { + .cmd_rcgr = 0x8300, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_3, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx2_pixel1_clk_src", + .parent_data = disp_cc_0_parent_data_3, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_3), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_dptx3_aux_clk_src = { + .cmd_rcgr = 0x8364, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_1, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx3_aux_clk_src", + .parent_data = disp_cc_0_parent_data_1, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_dptx3_link_clk_src = { + .cmd_rcgr = 0x8348, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_5, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx3_link_clk_src", + .parent_data = disp_cc_0_parent_data_5, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_5), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_byte2_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_dptx3_pixel0_clk_src = { + .cmd_rcgr = 0x8330, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_3, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx3_pixel0_clk_src", + .parent_data = disp_cc_0_parent_data_3, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_3), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_esc0_clk_src = { + .cmd_rcgr = 0x8174, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_6, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_esc0_clk_src", + .parent_data = disp_cc_0_parent_data_6, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_6), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_esc1_clk_src = { + .cmd_rcgr = 0x818c, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_6, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_esc1_clk_src", + .parent_data = disp_cc_0_parent_data_6, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_6), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_mdss_0_disp_cc_mdss_mdp_clk_src[] = { + F(300000000, P_MDSS_0_DISP_CC_PLL0_OUT_MAIN, 3, 0, 0), + F(417000000, P_MDSS_0_DISP_CC_PLL0_OUT_MAIN, 3, 0, 0), + F(532000000, P_MDSS_0_DISP_CC_PLL0_OUT_MAIN, 3, 0, 0), + F(650000000, P_MDSS_0_DISP_CC_PLL0_OUT_MAIN, 3, 0, 0), + F(710000000, P_MDSS_0_DISP_CC_PLL0_OUT_MAIN, 3, 0, 0), + { } +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_mdp_clk_src = { + .cmd_rcgr = 0x810c, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_8, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_mdp_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_mdp_clk_src", + .parent_data = disp_cc_0_parent_data_8, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_8), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_pclk0_clk_src = { + .cmd_rcgr = 0x80c4, + .mnd_width = 8, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_2, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_pclk0_clk_src", + .parent_data = disp_cc_0_parent_data_2, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_2), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_pixel_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_pclk1_clk_src = { + .cmd_rcgr = 0x80dc, + .mnd_width = 8, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_2, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_pclk1_clk_src", + .parent_data = disp_cc_0_parent_data_2, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_2), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_pixel_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_pclk2_clk_src = { + .cmd_rcgr = 0x80f4, + .mnd_width = 8, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_2, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_pclk2_clk_src", + .parent_data = disp_cc_0_parent_data_2, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_2), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_pixel_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_vsync_clk_src = { + .cmd_rcgr = 0x8124, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_1, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_vsync_clk_src", + .parent_data = disp_cc_0_parent_data_1, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_mdss_0_disp_cc_sleep_clk_src[] = { + F(32000, P_SLEEP_CLK, 1, 0, 0), + { } +}; + +static struct clk_rcg2 mdss_0_disp_cc_sleep_clk_src = { + .cmd_rcgr = 0xe064, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_9, + .freq_tbl = ftbl_mdss_0_disp_cc_sleep_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_sleep_clk_src", + .parent_data = disp_cc_0_parent_data_9, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_9), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_xo_clk_src = { + .cmd_rcgr = 0xe044, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_1, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_xo_clk_src", + .parent_data = disp_cc_0_parent_data_1, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_ops, + }, +}; + +static struct clk_regmap_div mdss_0_disp_cc_mdss_byte0_div_clk_src = { + .reg = 0x8154, + .shift = 0, + .width = 4, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_byte0_div_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_byte0_clk_src.clkr.hw, + }, + .num_parents = 1, + .ops = &clk_regmap_div_ro_ops, + }, +}; + +static struct clk_regmap_div mdss_0_disp_cc_mdss_byte1_div_clk_src = { + .reg = 0x8170, + .shift = 0, + .width = 4, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_byte1_div_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_byte1_clk_src.clkr.hw, + }, + .num_parents = 1, + .ops = &clk_regmap_div_ro_ops, + }, +}; + +static struct clk_regmap_div mdss_0_disp_cc_mdss_dptx0_link_div_clk_src = { + .reg = 0x81bc, + .shift = 0, + .width = 4, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx0_link_div_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx0_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_regmap_div_ro_ops, + }, +}; + +static struct clk_regmap_div mdss_0_disp_cc_mdss_dptx1_link_div_clk_src = { + .reg = 0x82b0, + .shift = 0, + .width = 4, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx1_link_div_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx1_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_regmap_div_ro_ops, + }, +}; + +static struct clk_regmap_div mdss_0_disp_cc_mdss_dptx2_link_div_clk_src = { + .reg = 0x82e4, + .shift = 0, + .width = 4, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx2_link_div_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx2_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_regmap_div_ro_ops, + }, +}; + +static struct clk_regmap_div mdss_0_disp_cc_mdss_dptx3_link_div_clk_src = { + .reg = 0x8360, + .shift = 0, + .width = 4, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx3_link_div_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx3_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_regmap_div_ro_ops, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_accu_shift_clk = { + .halt_reg = 0xe060, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0xe060, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_accu_shift_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_xo_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_ahb1_clk = { + .halt_reg = 0xa028, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0xa028, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_ahb1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_ahb_clk = { + .halt_reg = 0x80c0, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x80c0, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_byte0_clk = { + .halt_reg = 0x8034, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8034, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_byte0_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_byte0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_byte0_intf_clk = { + .halt_reg = 0x8038, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8038, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_byte0_intf_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_byte0_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_byte1_clk = { + .halt_reg = 0x803c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x803c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_byte1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_byte1_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_byte1_intf_clk = { + .halt_reg = 0x8040, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8040, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_byte1_intf_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_byte1_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx0_aux_clk = { + .halt_reg = 0x806c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x806c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx0_aux_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx0_aux_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx0_crypto_clk = { + .halt_reg = 0x8058, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x8058, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx0_crypto_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx0_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx0_link_clk = { + .halt_reg = 0x804c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x804c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx0_link_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx0_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx0_link_intf_clk = { + .halt_reg = 0x8054, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8054, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx0_link_intf_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx0_link_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx0_pixel0_clk = { + .halt_reg = 0x805c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x805c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx0_pixel0_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx0_pixel0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx0_pixel1_clk = { + .halt_reg = 0x8060, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8060, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx0_pixel1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx0_pixel1_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx0_pixel2_clk = { + .halt_reg = 0x8064, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8064, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx0_pixel2_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx0_pixel2_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx0_pixel3_clk = { + .halt_reg = 0x8068, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8068, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx0_pixel3_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx0_pixel3_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx0_usb_router_link_intf_clk = { + .halt_reg = 0x8050, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8050, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx0_usb_router_link_intf_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx0_link_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx1_aux_clk = { + .halt_reg = 0x8090, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8090, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx1_aux_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx1_aux_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx1_crypto_clk = { + .halt_reg = 0x808c, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x808c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx1_crypto_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx1_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx1_link_clk = { + .halt_reg = 0x8080, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8080, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx1_link_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx1_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx1_link_intf_clk = { + .halt_reg = 0x8088, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8088, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx1_link_intf_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx1_link_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx1_pixel0_clk = { + .halt_reg = 0x8070, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8070, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx1_pixel0_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx1_pixel0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx1_pixel1_clk = { + .halt_reg = 0x8074, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8074, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx1_pixel1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx1_pixel1_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx1_pixel2_clk = { + .halt_reg = 0x8078, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8078, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx1_pixel2_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx1_pixel2_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx1_pixel3_clk = { + .halt_reg = 0x807c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x807c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx1_pixel3_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx1_pixel3_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx1_usb_router_link_intf_clk = { + .halt_reg = 0x8084, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8084, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx1_usb_router_link_intf_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx1_link_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx2_aux_clk = { + .halt_reg = 0x80a8, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x80a8, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx2_aux_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx2_aux_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx2_crypto_clk = { + .halt_reg = 0x80a4, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x80a4, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx2_crypto_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx2_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx2_link_clk = { + .halt_reg = 0x809c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x809c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx2_link_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx2_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx2_link_intf_clk = { + .halt_reg = 0x80a0, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x80a0, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx2_link_intf_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx2_link_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx2_pixel0_clk = { + .halt_reg = 0x8094, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8094, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx2_pixel0_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx2_pixel0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx2_pixel1_clk = { + .halt_reg = 0x8098, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8098, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx2_pixel1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx2_pixel1_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx3_aux_clk = { + .halt_reg = 0x80b8, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x80b8, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx3_aux_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx3_aux_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx3_crypto_clk = { + .halt_reg = 0x80bc, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x80bc, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx3_crypto_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx3_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx3_link_clk = { + .halt_reg = 0x80b0, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x80b0, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx3_link_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx3_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx3_link_intf_clk = { + .halt_reg = 0x80b4, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x80b4, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx3_link_intf_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx3_link_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx3_pixel0_clk = { + .halt_reg = 0x80ac, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x80ac, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx3_pixel0_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx3_pixel0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_esc0_clk = { + .halt_reg = 0x8044, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8044, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_esc0_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_esc0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_esc1_clk = { + .halt_reg = 0x8048, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8048, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_esc1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_esc1_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_mdp1_clk = { + .halt_reg = 0xa004, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0xa004, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_mdp1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_mdp_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_mdp_clk = { + .halt_reg = 0x8010, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8010, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_mdp_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_mdp_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_mdp_lut1_clk = { + .halt_reg = 0xa014, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0xa014, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_mdp_lut1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_mdp_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_mdp_lut_clk = { + .halt_reg = 0x8020, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x8020, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_mdp_lut_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_mdp_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_non_gdsc_ahb_clk = { + .halt_reg = 0xc004, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0xc004, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_non_gdsc_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_pclk0_clk = { + .halt_reg = 0x8004, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8004, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_pclk0_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_pclk0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_pclk1_clk = { + .halt_reg = 0x8008, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8008, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_pclk1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_pclk1_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_pclk2_clk = { + .halt_reg = 0x800c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x800c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_pclk2_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_pclk2_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_vsync1_clk = { + .halt_reg = 0xa024, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0xa024, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_vsync1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_vsync_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_vsync_clk = { + .halt_reg = 0x8030, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8030, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_vsync_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_vsync_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct gdsc mdss_0_disp_cc_mdss_core_gdsc = { + .gdscr = 0x9000, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0xf, + .pd = { + .name = "mdss_0_disp_cc_mdss_core_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .flags = HW_CTRL_TRIGGER | POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct gdsc mdss_0_disp_cc_mdss_core_int2_gdsc = { + .gdscr = 0xb000, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0xf, + .pd = { + .name = "mdss_0_disp_cc_mdss_core_int2_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .flags = HW_CTRL_TRIGGER | POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct clk_regmap *disp_cc_0_nord_clocks[] = { + [MDSS_DISP_CC_MDSS_ACCU_SHIFT_CLK] = &mdss_0_disp_cc_mdss_accu_shift_clk.clkr, + [MDSS_DISP_CC_MDSS_AHB1_CLK] = &mdss_0_disp_cc_mdss_ahb1_clk.clkr, + [MDSS_DISP_CC_MDSS_AHB_CLK] = &mdss_0_disp_cc_mdss_ahb_clk.clkr, + [MDSS_DISP_CC_MDSS_AHB_CLK_SRC] = &mdss_0_disp_cc_mdss_ahb_clk_src.clkr, + [MDSS_DISP_CC_MDSS_BYTE0_CLK] = &mdss_0_disp_cc_mdss_byte0_clk.clkr, + [MDSS_DISP_CC_MDSS_BYTE0_CLK_SRC] = &mdss_0_disp_cc_mdss_byte0_clk_src.clkr, + [MDSS_DISP_CC_MDSS_BYTE0_DIV_CLK_SRC] = &mdss_0_disp_cc_mdss_byte0_div_clk_src.clkr, + [MDSS_DISP_CC_MDSS_BYTE0_INTF_CLK] = &mdss_0_disp_cc_mdss_byte0_intf_clk.clkr, + [MDSS_DISP_CC_MDSS_BYTE1_CLK] = &mdss_0_disp_cc_mdss_byte1_clk.clkr, + [MDSS_DISP_CC_MDSS_BYTE1_CLK_SRC] = &mdss_0_disp_cc_mdss_byte1_clk_src.clkr, + [MDSS_DISP_CC_MDSS_BYTE1_DIV_CLK_SRC] = &mdss_0_disp_cc_mdss_byte1_div_clk_src.clkr, + [MDSS_DISP_CC_MDSS_BYTE1_INTF_CLK] = &mdss_0_disp_cc_mdss_byte1_intf_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_AUX_CLK] = &mdss_0_disp_cc_mdss_dptx0_aux_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_AUX_CLK_SRC] = &mdss_0_disp_cc_mdss_dptx0_aux_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_CRYPTO_CLK] = &mdss_0_disp_cc_mdss_dptx0_crypto_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_LINK_CLK] = &mdss_0_disp_cc_mdss_dptx0_link_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_LINK_CLK_SRC] = &mdss_0_disp_cc_mdss_dptx0_link_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_LINK_DIV_CLK_SRC] = + &mdss_0_disp_cc_mdss_dptx0_link_div_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_LINK_INTF_CLK] = &mdss_0_disp_cc_mdss_dptx0_link_intf_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_PIXEL0_CLK] = &mdss_0_disp_cc_mdss_dptx0_pixel0_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_PIXEL0_CLK_SRC] = &mdss_0_disp_cc_mdss_dptx0_pixel0_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_PIXEL1_CLK] = &mdss_0_disp_cc_mdss_dptx0_pixel1_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_PIXEL1_CLK_SRC] = &mdss_0_disp_cc_mdss_dptx0_pixel1_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_PIXEL2_CLK] = &mdss_0_disp_cc_mdss_dptx0_pixel2_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_PIXEL2_CLK_SRC] = &mdss_0_disp_cc_mdss_dptx0_pixel2_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_PIXEL3_CLK] = &mdss_0_disp_cc_mdss_dptx0_pixel3_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_PIXEL3_CLK_SRC] = &mdss_0_disp_cc_mdss_dptx0_pixel3_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_USB_ROUTER_LINK_INTF_CLK] = + &mdss_0_disp_cc_mdss_dptx0_usb_router_link_intf_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_AUX_CLK] = &mdss_0_disp_cc_mdss_dptx1_aux_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_AUX_CLK_SRC] = &mdss_0_disp_cc_mdss_dptx1_aux_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_CRYPTO_CLK] = &mdss_0_disp_cc_mdss_dptx1_crypto_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_LINK_CLK] = &mdss_0_disp_cc_mdss_dptx1_link_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_LINK_CLK_SRC] = &mdss_0_disp_cc_mdss_dptx1_link_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_LINK_DIV_CLK_SRC] = + &mdss_0_disp_cc_mdss_dptx1_link_div_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_LINK_INTF_CLK] = &mdss_0_disp_cc_mdss_dptx1_link_intf_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_PIXEL0_CLK] = &mdss_0_disp_cc_mdss_dptx1_pixel0_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_PIXEL0_CLK_SRC] = &mdss_0_disp_cc_mdss_dptx1_pixel0_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_PIXEL1_CLK] = &mdss_0_disp_cc_mdss_dptx1_pixel1_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_PIXEL1_CLK_SRC] = &mdss_0_disp_cc_mdss_dptx1_pixel1_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_PIXEL2_CLK] = &mdss_0_disp_cc_mdss_dptx1_pixel2_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_PIXEL2_CLK_SRC] = &mdss_0_disp_cc_mdss_dptx1_pixel2_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_PIXEL3_CLK] = &mdss_0_disp_cc_mdss_dptx1_pixel3_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_PIXEL3_CLK_SRC] = &mdss_0_disp_cc_mdss_dptx1_pixel3_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_USB_ROUTER_LINK_INTF_CLK] = + &mdss_0_disp_cc_mdss_dptx1_usb_router_link_intf_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_AUX_CLK] = &mdss_0_disp_cc_mdss_dptx2_aux_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_AUX_CLK_SRC] = &mdss_0_disp_cc_mdss_dptx2_aux_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_CRYPTO_CLK] = &mdss_0_disp_cc_mdss_dptx2_crypto_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_LINK_CLK] = &mdss_0_disp_cc_mdss_dptx2_link_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_LINK_CLK_SRC] = &mdss_0_disp_cc_mdss_dptx2_link_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_LINK_DIV_CLK_SRC] = + &mdss_0_disp_cc_mdss_dptx2_link_div_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_LINK_INTF_CLK] = &mdss_0_disp_cc_mdss_dptx2_link_intf_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_PIXEL0_CLK] = &mdss_0_disp_cc_mdss_dptx2_pixel0_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_PIXEL0_CLK_SRC] = &mdss_0_disp_cc_mdss_dptx2_pixel0_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_PIXEL1_CLK] = &mdss_0_disp_cc_mdss_dptx2_pixel1_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_PIXEL1_CLK_SRC] = &mdss_0_disp_cc_mdss_dptx2_pixel1_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX3_AUX_CLK] = &mdss_0_disp_cc_mdss_dptx3_aux_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX3_AUX_CLK_SRC] = &mdss_0_disp_cc_mdss_dptx3_aux_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX3_CRYPTO_CLK] = &mdss_0_disp_cc_mdss_dptx3_crypto_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX3_LINK_CLK] = &mdss_0_disp_cc_mdss_dptx3_link_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX3_LINK_CLK_SRC] = &mdss_0_disp_cc_mdss_dptx3_link_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX3_LINK_DIV_CLK_SRC] = + &mdss_0_disp_cc_mdss_dptx3_link_div_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX3_LINK_INTF_CLK] = &mdss_0_disp_cc_mdss_dptx3_link_intf_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX3_PIXEL0_CLK] = &mdss_0_disp_cc_mdss_dptx3_pixel0_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX3_PIXEL0_CLK_SRC] = &mdss_0_disp_cc_mdss_dptx3_pixel0_clk_src.clkr, + [MDSS_DISP_CC_MDSS_ESC0_CLK] = &mdss_0_disp_cc_mdss_esc0_clk.clkr, + [MDSS_DISP_CC_MDSS_ESC0_CLK_SRC] = &mdss_0_disp_cc_mdss_esc0_clk_src.clkr, + [MDSS_DISP_CC_MDSS_ESC1_CLK] = &mdss_0_disp_cc_mdss_esc1_clk.clkr, + [MDSS_DISP_CC_MDSS_ESC1_CLK_SRC] = &mdss_0_disp_cc_mdss_esc1_clk_src.clkr, + [MDSS_DISP_CC_MDSS_MDP1_CLK] = &mdss_0_disp_cc_mdss_mdp1_clk.clkr, + [MDSS_DISP_CC_MDSS_MDP_CLK] = &mdss_0_disp_cc_mdss_mdp_clk.clkr, + [MDSS_DISP_CC_MDSS_MDP_CLK_SRC] = &mdss_0_disp_cc_mdss_mdp_clk_src.clkr, + [MDSS_DISP_CC_MDSS_MDP_LUT1_CLK] = &mdss_0_disp_cc_mdss_mdp_lut1_clk.clkr, + [MDSS_DISP_CC_MDSS_MDP_LUT_CLK] = &mdss_0_disp_cc_mdss_mdp_lut_clk.clkr, + [MDSS_DISP_CC_MDSS_NON_GDSC_AHB_CLK] = &mdss_0_disp_cc_mdss_non_gdsc_ahb_clk.clkr, + [MDSS_DISP_CC_MDSS_PCLK0_CLK] = &mdss_0_disp_cc_mdss_pclk0_clk.clkr, + [MDSS_DISP_CC_MDSS_PCLK0_CLK_SRC] = &mdss_0_disp_cc_mdss_pclk0_clk_src.clkr, + [MDSS_DISP_CC_MDSS_PCLK1_CLK] = &mdss_0_disp_cc_mdss_pclk1_clk.clkr, + [MDSS_DISP_CC_MDSS_PCLK1_CLK_SRC] = &mdss_0_disp_cc_mdss_pclk1_clk_src.clkr, + [MDSS_DISP_CC_MDSS_PCLK2_CLK] = &mdss_0_disp_cc_mdss_pclk2_clk.clkr, + [MDSS_DISP_CC_MDSS_PCLK2_CLK_SRC] = &mdss_0_disp_cc_mdss_pclk2_clk_src.clkr, + [MDSS_DISP_CC_MDSS_VSYNC1_CLK] = &mdss_0_disp_cc_mdss_vsync1_clk.clkr, + [MDSS_DISP_CC_MDSS_VSYNC_CLK] = &mdss_0_disp_cc_mdss_vsync_clk.clkr, + [MDSS_DISP_CC_MDSS_VSYNC_CLK_SRC] = &mdss_0_disp_cc_mdss_vsync_clk_src.clkr, + [MDSS_DISP_CC_PLL0] = &mdss_0_disp_cc_pll0.clkr, + [MDSS_DISP_CC_PLL1] = &mdss_0_disp_cc_pll1.clkr, + [MDSS_DISP_CC_PLL2] = &mdss_0_disp_cc_pll2.clkr, + [MDSS_DISP_CC_PLL3] = &mdss_0_disp_cc_pll3.clkr, + [MDSS_DISP_CC_SLEEP_CLK_SRC] = &mdss_0_disp_cc_sleep_clk_src.clkr, + [MDSS_DISP_CC_XO_CLK_SRC] = &mdss_0_disp_cc_xo_clk_src.clkr, +}; + +static struct gdsc *disp_cc_0_nord_gdscs[] = { + [MDSS_DISP_CC_MDSS_CORE_GDSC] = &mdss_0_disp_cc_mdss_core_gdsc, + [MDSS_DISP_CC_MDSS_CORE_INT2_GDSC] = &mdss_0_disp_cc_mdss_core_int2_gdsc, +}; + +static const struct qcom_reset_map disp_cc_0_nord_resets[] = { + [MDSS_DISP_CC_MDSS_CORE_BCR] = { 0x8000 }, + [MDSS_DISP_CC_MDSS_CORE_INT2_BCR] = { 0xa000 }, + [MDSS_DISP_CC_MDSS_RSCC_BCR] = { 0xc000 }, +}; + +static struct clk_alpha_pll *disp_cc_0_nord_plls[] = { + &mdss_0_disp_cc_pll0, + &mdss_0_disp_cc_pll1, + &mdss_0_disp_cc_pll2, + &mdss_0_disp_cc_pll3, +}; + +static const u32 disp_cc_0_nord_critical_cbcrs[] = { + 0xc00c, /* MDSS_DISP_CC_RSCC_AHB_CLK */ + 0xc008, /* MDSS_DISP_CC_RSCC_VSYNC_CLK */ + 0xe07c, /* MDSS_DISP_CC_SLEEP_CLK */ + 0xe05c, /* MDSS_DISP_CC_XO_CLK */ +}; + +static const struct regmap_config disp_cc_0_nord_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x1a00c, + .fast_io = true, +}; + +static const struct qcom_cc_driver_data disp_cc_0_nord_driver_data = { + .alpha_plls = disp_cc_0_nord_plls, + .num_alpha_plls = ARRAY_SIZE(disp_cc_0_nord_plls), + .clk_cbcrs = disp_cc_0_nord_critical_cbcrs, + .num_clk_cbcrs = ARRAY_SIZE(disp_cc_0_nord_critical_cbcrs), +}; + +static const struct qcom_cc_desc disp_cc_0_nord_desc = { + .config = &disp_cc_0_nord_regmap_config, + .clks = disp_cc_0_nord_clocks, + .num_clks = ARRAY_SIZE(disp_cc_0_nord_clocks), + .resets = disp_cc_0_nord_resets, + .num_resets = ARRAY_SIZE(disp_cc_0_nord_resets), + .gdscs = disp_cc_0_nord_gdscs, + .num_gdscs = ARRAY_SIZE(disp_cc_0_nord_gdscs), + .use_rpm = true, + .driver_data = &disp_cc_0_nord_driver_data, +}; + +static const struct of_device_id disp_cc_0_nord_match_table[] = { + { .compatible = "qcom,nord-dispcc0" }, + { } +}; +MODULE_DEVICE_TABLE(of, disp_cc_0_nord_match_table); + +static int disp_cc_0_nord_probe(struct platform_device *pdev) +{ + return qcom_cc_probe(pdev, &disp_cc_0_nord_desc); +} + +static struct platform_driver disp_cc_0_nord_driver = { + .probe = disp_cc_0_nord_probe, + .driver = { + .name = "dispcc0-nord", + .of_match_table = disp_cc_0_nord_match_table, + }, +}; + +module_platform_driver(disp_cc_0_nord_driver); + +MODULE_DESCRIPTION("QTI DISPCC0 NORD Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/clk/qcom/dispcc1-nord.c b/drivers/clk/qcom/dispcc1-nord.c new file mode 100644 index 000000000000..6b4efa735b9e --- /dev/null +++ b/drivers/clk/qcom/dispcc1-nord.c @@ -0,0 +1,2004 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include + +#include + +#include "clk-alpha-pll.h" +#include "clk-branch.h" +#include "clk-pll.h" +#include "clk-rcg.h" +#include "clk-regmap.h" +#include "clk-regmap-divider.h" +#include "clk-regmap-mux.h" +#include "common.h" +#include "gdsc.h" +#include "reset.h" + +enum { + DT_BI_TCXO, + DT_BI_TCXO_AO, + DT_AHB_CLK, + DT_SLEEP_CLK, + + DT_DSI0_PHY_PLL_OUT_BYTECLK, + DT_DSI0_PHY_PLL_OUT_DSICLK, + DT_DSI1_PHY_PLL_OUT_BYTECLK, + DT_DSI1_PHY_PLL_OUT_DSICLK, + + DT_DP0_PHY_PLL_LINK_CLK, + DT_DP0_PHY_PLL_VCO_DIV_CLK, + DT_DP1_PHY_PLL_LINK_CLK, + DT_DP1_PHY_PLL_VCO_DIV_CLK, + DT_DP2_PHY_PLL_LINK_CLK, + DT_DP2_PHY_PLL_VCO_DIV_CLK, + DT_DP3_PHY_PLL_LINK_CLK, + DT_DP3_PHY_PLL_VCO_DIV_CLK, +}; + +enum { + P_BI_TCXO, + P_MDSS_1_DISP_CC_PLL0_OUT_MAIN, + P_MDSS_1_DISP_CC_PLL1_OUT_EVEN, + P_MDSS_1_DISP_CC_PLL1_OUT_MAIN, + P_MDSS_1_DISP_CC_PLL2_OUT_MAIN, + P_MDSS_1_DISP_CC_PLL3_OUT_MAIN, + P_DP0_PHY_PLL_LINK_CLK, + P_DP0_PHY_PLL_VCO_DIV_CLK, + P_DP1_PHY_PLL_LINK_CLK, + P_DP1_PHY_PLL_VCO_DIV_CLK, + P_DP2_PHY_PLL_LINK_CLK, + P_DP2_PHY_PLL_VCO_DIV_CLK, + P_DP3_PHY_PLL_LINK_CLK, + P_DP3_PHY_PLL_VCO_DIV_CLK, + P_DSI0_PHY_PLL_OUT_BYTECLK, + P_DSI0_PHY_PLL_OUT_DSICLK, + P_DSI1_PHY_PLL_OUT_BYTECLK, + P_DSI1_PHY_PLL_OUT_DSICLK, + P_SLEEP_CLK, +}; + +static const struct pll_vco lucid_ole_vco[] = { + { 249600000, 2300000000, 0 }, +}; + +static const struct pll_vco zonda_ole_vco[] = { + { 700000000, 3600000000, 0 }, +}; + +/* 900.0 MHz Configuration */ +static const struct alpha_pll_config mdss_1_disp_cc_pll0_config = { + .l = 0x2e, + .alpha = 0xe000, + .config_ctl_val = 0x20485699, + .config_ctl_hi_val = 0x00182261, + .config_ctl_hi1_val = 0x82aa299c, + .test_ctl_val = 0x00000000, + .test_ctl_hi_val = 0x00000003, + .test_ctl_hi1_val = 0x00009000, + .test_ctl_hi2_val = 0x00000034, + .user_ctl_val = 0x00000000, + .user_ctl_hi_val = 0x00400005, +}; + +static struct clk_alpha_pll mdss_1_disp_cc_pll0 = { + .offset = 0x0, + .config = &mdss_1_disp_cc_pll0_config, + .vco_table = lucid_ole_vco, + .num_vco = ARRAY_SIZE(lucid_ole_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_pll0", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_lucid_evo_ops, + }, + }, +}; + +/* 600.0 MHz Configuration */ +static const struct alpha_pll_config mdss_1_disp_cc_pll1_config = { + .l = 0x1f, + .alpha = 0x4000, + .config_ctl_val = 0x20485699, + .config_ctl_hi_val = 0x00182261, + .config_ctl_hi1_val = 0x82aa299c, + .test_ctl_val = 0x00000000, + .test_ctl_hi_val = 0x00000003, + .test_ctl_hi1_val = 0x00009000, + .test_ctl_hi2_val = 0x00000034, + .user_ctl_val = 0x00000000, + .user_ctl_hi_val = 0x00400005, +}; + +static struct clk_alpha_pll mdss_1_disp_cc_pll1 = { + .offset = 0x1000, + .config = &mdss_1_disp_cc_pll1_config, + .vco_table = lucid_ole_vco, + .num_vco = ARRAY_SIZE(lucid_ole_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_pll1", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_lucid_evo_ops, + }, + }, +}; + +/* 1363.2 MHz Configuration */ +static const struct alpha_pll_config mdss_1_disp_cc_pll2_config = { + .l = 0x47, + .alpha = 0x0, + .config_ctl_val = 0x08240800, + .config_ctl_hi_val = 0x05008001, + .config_ctl_hi1_val = 0x00000000, + .config_ctl_hi2_val = 0x00000000, + .user_ctl_val = 0x00000000, + .user_ctl_hi_val = 0x00000080, +}; + +static struct clk_alpha_pll mdss_1_disp_cc_pll2 = { + .offset = 0x2000, + .config = &mdss_1_disp_cc_pll2_config, + .vco_table = zonda_ole_vco, + .num_vco = ARRAY_SIZE(zonda_ole_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_ZONDA_OLE], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_pll2", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_zonda_ole_ops, + }, + }, +}; + +/* 1363.2 MHz Configuration */ +static const struct alpha_pll_config mdss_1_disp_cc_pll3_config = { + .l = 0x47, + .alpha = 0x0, + .config_ctl_val = 0x08240800, + .config_ctl_hi_val = 0x05008001, + .config_ctl_hi1_val = 0x00000000, + .config_ctl_hi2_val = 0x00000000, + .user_ctl_val = 0x00000000, + .user_ctl_hi_val = 0x00000080, +}; + +static struct clk_alpha_pll mdss_1_disp_cc_pll3 = { + .offset = 0x3000, + .config = &mdss_1_disp_cc_pll3_config, + .vco_table = zonda_ole_vco, + .num_vco = ARRAY_SIZE(zonda_ole_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_ZONDA_OLE], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_pll3", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_zonda_ole_ops, + }, + }, +}; + +static const struct parent_map disp_cc_1_parent_map_0[] = { + { P_BI_TCXO, 0 }, + { P_MDSS_1_DISP_CC_PLL2_OUT_MAIN, 1 }, + { P_DP0_PHY_PLL_VCO_DIV_CLK, 2 }, + { P_DP3_PHY_PLL_VCO_DIV_CLK, 3 }, + { P_DP1_PHY_PLL_VCO_DIV_CLK, 4 }, + { P_MDSS_1_DISP_CC_PLL3_OUT_MAIN, 5 }, + { P_DP2_PHY_PLL_VCO_DIV_CLK, 6 }, +}; + +static const struct clk_parent_data disp_cc_1_parent_data_0[] = { + { .index = DT_BI_TCXO }, + { .hw = &mdss_1_disp_cc_pll2.clkr.hw }, + { .index = DT_DP0_PHY_PLL_VCO_DIV_CLK }, + { .index = DT_DP3_PHY_PLL_VCO_DIV_CLK }, + { .index = DT_DP1_PHY_PLL_VCO_DIV_CLK }, + { .hw = &mdss_1_disp_cc_pll3.clkr.hw }, + { .index = DT_DP2_PHY_PLL_VCO_DIV_CLK }, +}; + +static const struct parent_map disp_cc_1_parent_map_1[] = { + { P_BI_TCXO, 0 }, +}; + +static const struct clk_parent_data disp_cc_1_parent_data_1[] = { + { .index = DT_BI_TCXO }, +}; + +static const struct parent_map disp_cc_1_parent_map_2[] = { + { P_BI_TCXO, 0 }, + { P_DSI0_PHY_PLL_OUT_DSICLK, 1 }, + { P_DSI0_PHY_PLL_OUT_BYTECLK, 2 }, + { P_DSI1_PHY_PLL_OUT_DSICLK, 3 }, + { P_DSI1_PHY_PLL_OUT_BYTECLK, 4 }, +}; + +static const struct clk_parent_data disp_cc_1_parent_data_2[] = { + { .index = DT_BI_TCXO }, + { .index = DT_DSI0_PHY_PLL_OUT_DSICLK }, + { .index = DT_DSI0_PHY_PLL_OUT_BYTECLK }, + { .index = DT_DSI1_PHY_PLL_OUT_DSICLK }, + { .index = DT_DSI1_PHY_PLL_OUT_BYTECLK }, +}; + +static const struct parent_map disp_cc_1_parent_map_3[] = { + { P_BI_TCXO, 0 }, + { P_MDSS_1_DISP_CC_PLL2_OUT_MAIN, 1 }, + { P_DP3_PHY_PLL_VCO_DIV_CLK, 3 }, + { P_MDSS_1_DISP_CC_PLL3_OUT_MAIN, 5 }, + { P_DP2_PHY_PLL_VCO_DIV_CLK, 6 }, +}; + +static const struct clk_parent_data disp_cc_1_parent_data_3[] = { + { .index = DT_BI_TCXO }, + { .hw = &mdss_1_disp_cc_pll2.clkr.hw }, + { .index = DT_DP3_PHY_PLL_VCO_DIV_CLK }, + { .hw = &mdss_1_disp_cc_pll3.clkr.hw }, + { .index = DT_DP2_PHY_PLL_VCO_DIV_CLK }, +}; + +static const struct parent_map disp_cc_1_parent_map_4[] = { + { P_BI_TCXO, 0 }, + { P_DP0_PHY_PLL_LINK_CLK, 1 }, + { P_DP1_PHY_PLL_LINK_CLK, 2 }, + { P_DP2_PHY_PLL_LINK_CLK, 3 }, + { P_DP3_PHY_PLL_LINK_CLK, 4 }, +}; + +static const struct clk_parent_data disp_cc_1_parent_data_4[] = { + { .index = DT_BI_TCXO }, + { .index = DT_DP0_PHY_PLL_LINK_CLK }, + { .index = DT_DP1_PHY_PLL_LINK_CLK }, + { .index = DT_DP2_PHY_PLL_LINK_CLK }, + { .index = DT_DP3_PHY_PLL_LINK_CLK }, +}; + +static const struct parent_map disp_cc_1_parent_map_5[] = { + { P_BI_TCXO, 0 }, + { P_DP2_PHY_PLL_LINK_CLK, 3 }, + { P_DP3_PHY_PLL_LINK_CLK, 4 }, +}; + +static const struct clk_parent_data disp_cc_1_parent_data_5[] = { + { .index = DT_BI_TCXO }, + { .index = DT_DP2_PHY_PLL_LINK_CLK }, + { .index = DT_DP3_PHY_PLL_LINK_CLK }, +}; + +static const struct parent_map disp_cc_1_parent_map_6[] = { + { P_BI_TCXO, 0 }, + { P_DSI0_PHY_PLL_OUT_BYTECLK, 2 }, + { P_DSI1_PHY_PLL_OUT_BYTECLK, 4 }, +}; + +static const struct clk_parent_data disp_cc_1_parent_data_6[] = { + { .index = DT_BI_TCXO }, + { .index = DT_DSI0_PHY_PLL_OUT_BYTECLK }, + { .index = DT_DSI1_PHY_PLL_OUT_BYTECLK }, +}; + +static const struct parent_map disp_cc_1_parent_map_7[] = { + { P_BI_TCXO, 0 }, + { P_MDSS_1_DISP_CC_PLL1_OUT_MAIN, 4 }, + { P_MDSS_1_DISP_CC_PLL1_OUT_EVEN, 6 }, +}; + +static const struct clk_parent_data disp_cc_1_parent_data_7[] = { + { .index = DT_BI_TCXO }, + { .hw = &mdss_1_disp_cc_pll1.clkr.hw }, + { .hw = &mdss_1_disp_cc_pll1.clkr.hw }, +}; + +static const struct parent_map disp_cc_1_parent_map_8[] = { + { P_BI_TCXO, 0 }, + { P_MDSS_1_DISP_CC_PLL0_OUT_MAIN, 1 }, + { P_MDSS_1_DISP_CC_PLL1_OUT_MAIN, 4 }, + { P_MDSS_1_DISP_CC_PLL1_OUT_EVEN, 6 }, +}; + +static const struct clk_parent_data disp_cc_1_parent_data_8[] = { + { .index = DT_BI_TCXO }, + { .hw = &mdss_1_disp_cc_pll0.clkr.hw }, + { .hw = &mdss_1_disp_cc_pll1.clkr.hw }, + { .hw = &mdss_1_disp_cc_pll1.clkr.hw }, +}; + +static const struct parent_map disp_cc_1_parent_map_9[] = { + { P_SLEEP_CLK, 0 }, +}; + +static const struct clk_parent_data disp_cc_1_parent_data_9[] = { + { .index = DT_SLEEP_CLK }, +}; + +static const struct freq_tbl ftbl_mdss_1_disp_cc_mdss_ahb_clk_src[] = { + F(37500000, P_MDSS_1_DISP_CC_PLL1_OUT_MAIN, 16, 0, 0), + F(75000000, P_MDSS_1_DISP_CC_PLL1_OUT_MAIN, 8, 0, 0), + { } +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_ahb_clk_src = { + .cmd_rcgr = 0x837c, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_7, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_ahb_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_ahb_clk_src", + .parent_data = disp_cc_1_parent_data_7, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_7), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_mdss_1_disp_cc_mdss_byte0_clk_src[] = { + F(19200000, P_BI_TCXO, 1, 0, 0), + { } +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_byte0_clk_src = { + .cmd_rcgr = 0x813c, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_2, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_byte0_clk_src", + .parent_data = disp_cc_1_parent_data_2, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_2), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_byte2_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_byte1_clk_src = { + .cmd_rcgr = 0x8158, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_2, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_byte1_clk_src", + .parent_data = disp_cc_1_parent_data_2, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_2), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_byte2_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_dptx0_aux_clk_src = { + .cmd_rcgr = 0x8220, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_1, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx0_aux_clk_src", + .parent_data = disp_cc_1_parent_data_1, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_dptx0_link_clk_src = { + .cmd_rcgr = 0x81a4, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_4, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx0_link_clk_src", + .parent_data = disp_cc_1_parent_data_4, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_4), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_byte2_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_dptx0_pixel0_clk_src = { + .cmd_rcgr = 0x81c0, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_0, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx0_pixel0_clk_src", + .parent_data = disp_cc_1_parent_data_0, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_dptx0_pixel1_clk_src = { + .cmd_rcgr = 0x81d8, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_0, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx0_pixel1_clk_src", + .parent_data = disp_cc_1_parent_data_0, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_dptx0_pixel2_clk_src = { + .cmd_rcgr = 0x81f0, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_0, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx0_pixel2_clk_src", + .parent_data = disp_cc_1_parent_data_0, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_dptx0_pixel3_clk_src = { + .cmd_rcgr = 0x8208, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_0, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx0_pixel3_clk_src", + .parent_data = disp_cc_1_parent_data_0, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_dptx1_aux_clk_src = { + .cmd_rcgr = 0x82b4, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_1, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx1_aux_clk_src", + .parent_data = disp_cc_1_parent_data_1, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_dptx1_link_clk_src = { + .cmd_rcgr = 0x8298, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_4, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx1_link_clk_src", + .parent_data = disp_cc_1_parent_data_4, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_4), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_byte2_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_dptx1_pixel0_clk_src = { + .cmd_rcgr = 0x8238, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_0, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx1_pixel0_clk_src", + .parent_data = disp_cc_1_parent_data_0, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_dptx1_pixel1_clk_src = { + .cmd_rcgr = 0x8250, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_0, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx1_pixel1_clk_src", + .parent_data = disp_cc_1_parent_data_0, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_dptx1_pixel2_clk_src = { + .cmd_rcgr = 0x8268, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_0, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx1_pixel2_clk_src", + .parent_data = disp_cc_1_parent_data_0, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_dptx1_pixel3_clk_src = { + .cmd_rcgr = 0x8280, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_0, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx1_pixel3_clk_src", + .parent_data = disp_cc_1_parent_data_0, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_dptx2_aux_clk_src = { + .cmd_rcgr = 0x8318, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_1, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx2_aux_clk_src", + .parent_data = disp_cc_1_parent_data_1, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_dptx2_link_clk_src = { + .cmd_rcgr = 0x82cc, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_5, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx2_link_clk_src", + .parent_data = disp_cc_1_parent_data_5, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_5), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_byte2_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_dptx2_pixel0_clk_src = { + .cmd_rcgr = 0x82e8, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_3, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx2_pixel0_clk_src", + .parent_data = disp_cc_1_parent_data_3, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_3), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_dptx2_pixel1_clk_src = { + .cmd_rcgr = 0x8300, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_3, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx2_pixel1_clk_src", + .parent_data = disp_cc_1_parent_data_3, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_3), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_dptx3_aux_clk_src = { + .cmd_rcgr = 0x8364, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_1, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx3_aux_clk_src", + .parent_data = disp_cc_1_parent_data_1, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_dptx3_link_clk_src = { + .cmd_rcgr = 0x8348, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_5, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx3_link_clk_src", + .parent_data = disp_cc_1_parent_data_5, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_5), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_byte2_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_dptx3_pixel0_clk_src = { + .cmd_rcgr = 0x8330, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_3, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx3_pixel0_clk_src", + .parent_data = disp_cc_1_parent_data_3, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_3), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_esc0_clk_src = { + .cmd_rcgr = 0x8174, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_6, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_esc0_clk_src", + .parent_data = disp_cc_1_parent_data_6, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_6), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_esc1_clk_src = { + .cmd_rcgr = 0x818c, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_6, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_esc1_clk_src", + .parent_data = disp_cc_1_parent_data_6, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_6), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_mdss_1_disp_cc_mdss_mdp_clk_src[] = { + F(300000000, P_MDSS_1_DISP_CC_PLL0_OUT_MAIN, 3, 0, 0), + F(417000000, P_MDSS_1_DISP_CC_PLL0_OUT_MAIN, 3, 0, 0), + F(532000000, P_MDSS_1_DISP_CC_PLL0_OUT_MAIN, 3, 0, 0), + F(650000000, P_MDSS_1_DISP_CC_PLL0_OUT_MAIN, 3, 0, 0), + F(710000000, P_MDSS_1_DISP_CC_PLL0_OUT_MAIN, 3, 0, 0), + { } +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_mdp_clk_src = { + .cmd_rcgr = 0x810c, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_8, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_mdp_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_mdp_clk_src", + .parent_data = disp_cc_1_parent_data_8, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_8), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_pclk0_clk_src = { + .cmd_rcgr = 0x80c4, + .mnd_width = 8, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_2, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_pclk0_clk_src", + .parent_data = disp_cc_1_parent_data_2, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_2), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_pixel_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_pclk1_clk_src = { + .cmd_rcgr = 0x80dc, + .mnd_width = 8, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_2, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_pclk1_clk_src", + .parent_data = disp_cc_1_parent_data_2, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_2), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_pixel_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_pclk2_clk_src = { + .cmd_rcgr = 0x80f4, + .mnd_width = 8, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_2, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_pclk2_clk_src", + .parent_data = disp_cc_1_parent_data_2, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_2), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_pixel_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_vsync_clk_src = { + .cmd_rcgr = 0x8124, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_1, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_vsync_clk_src", + .parent_data = disp_cc_1_parent_data_1, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_mdss_1_disp_cc_sleep_clk_src[] = { + F(32000, P_SLEEP_CLK, 1, 0, 0), + { } +}; + +static struct clk_rcg2 mdss_1_disp_cc_sleep_clk_src = { + .cmd_rcgr = 0xe064, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_9, + .freq_tbl = ftbl_mdss_1_disp_cc_sleep_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_sleep_clk_src", + .parent_data = disp_cc_1_parent_data_9, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_9), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_xo_clk_src = { + .cmd_rcgr = 0xe044, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_1, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_xo_clk_src", + .parent_data = disp_cc_1_parent_data_1, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_ops, + }, +}; + +static struct clk_regmap_div mdss_1_disp_cc_mdss_byte0_div_clk_src = { + .reg = 0x8154, + .shift = 0, + .width = 4, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_byte0_div_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_byte0_clk_src.clkr.hw, + }, + .num_parents = 1, + .ops = &clk_regmap_div_ro_ops, + }, +}; + +static struct clk_regmap_div mdss_1_disp_cc_mdss_byte1_div_clk_src = { + .reg = 0x8170, + .shift = 0, + .width = 4, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_byte1_div_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_byte1_clk_src.clkr.hw, + }, + .num_parents = 1, + .ops = &clk_regmap_div_ro_ops, + }, +}; + +static struct clk_regmap_div mdss_1_disp_cc_mdss_dptx0_link_div_clk_src = { + .reg = 0x81bc, + .shift = 0, + .width = 4, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx0_link_div_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx0_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_regmap_div_ro_ops, + }, +}; + +static struct clk_regmap_div mdss_1_disp_cc_mdss_dptx1_link_div_clk_src = { + .reg = 0x82b0, + .shift = 0, + .width = 4, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx1_link_div_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx1_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_regmap_div_ro_ops, + }, +}; + +static struct clk_regmap_div mdss_1_disp_cc_mdss_dptx2_link_div_clk_src = { + .reg = 0x82e4, + .shift = 0, + .width = 4, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx2_link_div_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx2_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_regmap_div_ro_ops, + }, +}; + +static struct clk_regmap_div mdss_1_disp_cc_mdss_dptx3_link_div_clk_src = { + .reg = 0x8360, + .shift = 0, + .width = 4, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx3_link_div_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx3_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_regmap_div_ro_ops, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_accu_shift_clk = { + .halt_reg = 0xe060, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0xe060, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_accu_shift_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_xo_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_ahb1_clk = { + .halt_reg = 0xa028, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0xa028, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_ahb1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_ahb_clk = { + .halt_reg = 0x80c0, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x80c0, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_byte0_clk = { + .halt_reg = 0x8034, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8034, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_byte0_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_byte0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_byte0_intf_clk = { + .halt_reg = 0x8038, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8038, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_byte0_intf_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_byte0_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_byte1_clk = { + .halt_reg = 0x803c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x803c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_byte1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_byte1_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_byte1_intf_clk = { + .halt_reg = 0x8040, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8040, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_byte1_intf_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_byte1_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx0_aux_clk = { + .halt_reg = 0x806c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x806c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx0_aux_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx0_aux_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx0_crypto_clk = { + .halt_reg = 0x8058, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x8058, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx0_crypto_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx0_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx0_link_clk = { + .halt_reg = 0x804c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x804c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx0_link_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx0_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx0_link_intf_clk = { + .halt_reg = 0x8054, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8054, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx0_link_intf_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx0_link_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx0_pixel0_clk = { + .halt_reg = 0x805c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x805c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx0_pixel0_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx0_pixel0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx0_pixel1_clk = { + .halt_reg = 0x8060, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8060, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx0_pixel1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx0_pixel1_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx0_pixel2_clk = { + .halt_reg = 0x8064, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8064, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx0_pixel2_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx0_pixel2_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx0_pixel3_clk = { + .halt_reg = 0x8068, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8068, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx0_pixel3_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx0_pixel3_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx0_usb_router_link_intf_clk = { + .halt_reg = 0x8050, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8050, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx0_usb_router_link_intf_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx0_link_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx1_aux_clk = { + .halt_reg = 0x8090, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8090, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx1_aux_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx1_aux_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx1_crypto_clk = { + .halt_reg = 0x808c, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x808c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx1_crypto_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx1_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx1_link_clk = { + .halt_reg = 0x8080, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8080, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx1_link_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx1_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx1_link_intf_clk = { + .halt_reg = 0x8088, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8088, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx1_link_intf_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx1_link_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx1_pixel0_clk = { + .halt_reg = 0x8070, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8070, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx1_pixel0_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx1_pixel0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx1_pixel1_clk = { + .halt_reg = 0x8074, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8074, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx1_pixel1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx1_pixel1_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx1_pixel2_clk = { + .halt_reg = 0x8078, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8078, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx1_pixel2_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx1_pixel2_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx1_pixel3_clk = { + .halt_reg = 0x807c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x807c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx1_pixel3_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx1_pixel3_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx1_usb_router_link_intf_clk = { + .halt_reg = 0x8084, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8084, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx1_usb_router_link_intf_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx1_link_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx2_aux_clk = { + .halt_reg = 0x80a8, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x80a8, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx2_aux_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx2_aux_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx2_crypto_clk = { + .halt_reg = 0x80a4, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x80a4, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx2_crypto_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx2_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx2_link_clk = { + .halt_reg = 0x809c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x809c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx2_link_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx2_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx2_link_intf_clk = { + .halt_reg = 0x80a0, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x80a0, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx2_link_intf_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx2_link_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx2_pixel0_clk = { + .halt_reg = 0x8094, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8094, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx2_pixel0_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx2_pixel0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx2_pixel1_clk = { + .halt_reg = 0x8098, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8098, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx2_pixel1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx2_pixel1_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx3_aux_clk = { + .halt_reg = 0x80b8, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x80b8, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx3_aux_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx3_aux_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx3_crypto_clk = { + .halt_reg = 0x80bc, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x80bc, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx3_crypto_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx3_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx3_link_clk = { + .halt_reg = 0x80b0, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x80b0, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx3_link_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx3_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx3_link_intf_clk = { + .halt_reg = 0x80b4, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x80b4, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx3_link_intf_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx3_link_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx3_pixel0_clk = { + .halt_reg = 0x80ac, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x80ac, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx3_pixel0_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx3_pixel0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_esc0_clk = { + .halt_reg = 0x8044, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8044, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_esc0_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_esc0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_esc1_clk = { + .halt_reg = 0x8048, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8048, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_esc1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_esc1_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_mdp1_clk = { + .halt_reg = 0xa004, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0xa004, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_mdp1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_mdp_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_mdp_clk = { + .halt_reg = 0x8010, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8010, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_mdp_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_mdp_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_mdp_lut1_clk = { + .halt_reg = 0xa014, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0xa014, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_mdp_lut1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_mdp_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_mdp_lut_clk = { + .halt_reg = 0x8020, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x8020, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_mdp_lut_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_mdp_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_non_gdsc_ahb_clk = { + .halt_reg = 0xc004, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0xc004, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_non_gdsc_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_pclk0_clk = { + .halt_reg = 0x8004, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8004, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_pclk0_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_pclk0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_pclk1_clk = { + .halt_reg = 0x8008, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8008, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_pclk1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_pclk1_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_pclk2_clk = { + .halt_reg = 0x800c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x800c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_pclk2_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_pclk2_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_vsync1_clk = { + .halt_reg = 0xa024, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0xa024, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_vsync1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_vsync_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_vsync_clk = { + .halt_reg = 0x8030, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8030, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_vsync_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_vsync_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct gdsc mdss_1_disp_cc_mdss_core_gdsc = { + .gdscr = 0x9000, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0xf, + .pd = { + .name = "mdss_1_disp_cc_mdss_core_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .flags = HW_CTRL_TRIGGER | POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct gdsc mdss_1_disp_cc_mdss_core_int2_gdsc = { + .gdscr = 0xb000, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0xf, + .pd = { + .name = "mdss_1_disp_cc_mdss_core_int2_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .flags = HW_CTRL_TRIGGER | POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct clk_regmap *disp_cc_1_nord_clocks[] = { + [MDSS_DISP_CC_MDSS_ACCU_SHIFT_CLK] = &mdss_1_disp_cc_mdss_accu_shift_clk.clkr, + [MDSS_DISP_CC_MDSS_AHB1_CLK] = &mdss_1_disp_cc_mdss_ahb1_clk.clkr, + [MDSS_DISP_CC_MDSS_AHB_CLK] = &mdss_1_disp_cc_mdss_ahb_clk.clkr, + [MDSS_DISP_CC_MDSS_AHB_CLK_SRC] = &mdss_1_disp_cc_mdss_ahb_clk_src.clkr, + [MDSS_DISP_CC_MDSS_BYTE0_CLK] = &mdss_1_disp_cc_mdss_byte0_clk.clkr, + [MDSS_DISP_CC_MDSS_BYTE0_CLK_SRC] = &mdss_1_disp_cc_mdss_byte0_clk_src.clkr, + [MDSS_DISP_CC_MDSS_BYTE0_DIV_CLK_SRC] = &mdss_1_disp_cc_mdss_byte0_div_clk_src.clkr, + [MDSS_DISP_CC_MDSS_BYTE0_INTF_CLK] = &mdss_1_disp_cc_mdss_byte0_intf_clk.clkr, + [MDSS_DISP_CC_MDSS_BYTE1_CLK] = &mdss_1_disp_cc_mdss_byte1_clk.clkr, + [MDSS_DISP_CC_MDSS_BYTE1_CLK_SRC] = &mdss_1_disp_cc_mdss_byte1_clk_src.clkr, + [MDSS_DISP_CC_MDSS_BYTE1_DIV_CLK_SRC] = &mdss_1_disp_cc_mdss_byte1_div_clk_src.clkr, + [MDSS_DISP_CC_MDSS_BYTE1_INTF_CLK] = &mdss_1_disp_cc_mdss_byte1_intf_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_AUX_CLK] = &mdss_1_disp_cc_mdss_dptx0_aux_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_AUX_CLK_SRC] = &mdss_1_disp_cc_mdss_dptx0_aux_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_CRYPTO_CLK] = &mdss_1_disp_cc_mdss_dptx0_crypto_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_LINK_CLK] = &mdss_1_disp_cc_mdss_dptx0_link_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_LINK_CLK_SRC] = &mdss_1_disp_cc_mdss_dptx0_link_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_LINK_DIV_CLK_SRC] = + &mdss_1_disp_cc_mdss_dptx0_link_div_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_LINK_INTF_CLK] = &mdss_1_disp_cc_mdss_dptx0_link_intf_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_PIXEL0_CLK] = &mdss_1_disp_cc_mdss_dptx0_pixel0_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_PIXEL0_CLK_SRC] = &mdss_1_disp_cc_mdss_dptx0_pixel0_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_PIXEL1_CLK] = &mdss_1_disp_cc_mdss_dptx0_pixel1_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_PIXEL1_CLK_SRC] = &mdss_1_disp_cc_mdss_dptx0_pixel1_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_PIXEL2_CLK] = &mdss_1_disp_cc_mdss_dptx0_pixel2_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_PIXEL2_CLK_SRC] = &mdss_1_disp_cc_mdss_dptx0_pixel2_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_PIXEL3_CLK] = &mdss_1_disp_cc_mdss_dptx0_pixel3_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_PIXEL3_CLK_SRC] = &mdss_1_disp_cc_mdss_dptx0_pixel3_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_USB_ROUTER_LINK_INTF_CLK] = + &mdss_1_disp_cc_mdss_dptx0_usb_router_link_intf_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_AUX_CLK] = &mdss_1_disp_cc_mdss_dptx1_aux_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_AUX_CLK_SRC] = &mdss_1_disp_cc_mdss_dptx1_aux_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_CRYPTO_CLK] = &mdss_1_disp_cc_mdss_dptx1_crypto_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_LINK_CLK] = &mdss_1_disp_cc_mdss_dptx1_link_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_LINK_CLK_SRC] = &mdss_1_disp_cc_mdss_dptx1_link_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_LINK_DIV_CLK_SRC] = + &mdss_1_disp_cc_mdss_dptx1_link_div_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_LINK_INTF_CLK] = &mdss_1_disp_cc_mdss_dptx1_link_intf_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_PIXEL0_CLK] = &mdss_1_disp_cc_mdss_dptx1_pixel0_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_PIXEL0_CLK_SRC] = &mdss_1_disp_cc_mdss_dptx1_pixel0_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_PIXEL1_CLK] = &mdss_1_disp_cc_mdss_dptx1_pixel1_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_PIXEL1_CLK_SRC] = &mdss_1_disp_cc_mdss_dptx1_pixel1_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_PIXEL2_CLK] = &mdss_1_disp_cc_mdss_dptx1_pixel2_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_PIXEL2_CLK_SRC] = &mdss_1_disp_cc_mdss_dptx1_pixel2_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_PIXEL3_CLK] = &mdss_1_disp_cc_mdss_dptx1_pixel3_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_PIXEL3_CLK_SRC] = &mdss_1_disp_cc_mdss_dptx1_pixel3_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_USB_ROUTER_LINK_INTF_CLK] = + &mdss_1_disp_cc_mdss_dptx1_usb_router_link_intf_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_AUX_CLK] = &mdss_1_disp_cc_mdss_dptx2_aux_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_AUX_CLK_SRC] = &mdss_1_disp_cc_mdss_dptx2_aux_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_CRYPTO_CLK] = &mdss_1_disp_cc_mdss_dptx2_crypto_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_LINK_CLK] = &mdss_1_disp_cc_mdss_dptx2_link_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_LINK_CLK_SRC] = &mdss_1_disp_cc_mdss_dptx2_link_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_LINK_DIV_CLK_SRC] = + &mdss_1_disp_cc_mdss_dptx2_link_div_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_LINK_INTF_CLK] = &mdss_1_disp_cc_mdss_dptx2_link_intf_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_PIXEL0_CLK] = &mdss_1_disp_cc_mdss_dptx2_pixel0_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_PIXEL0_CLK_SRC] = &mdss_1_disp_cc_mdss_dptx2_pixel0_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_PIXEL1_CLK] = &mdss_1_disp_cc_mdss_dptx2_pixel1_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_PIXEL1_CLK_SRC] = &mdss_1_disp_cc_mdss_dptx2_pixel1_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX3_AUX_CLK] = &mdss_1_disp_cc_mdss_dptx3_aux_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX3_AUX_CLK_SRC] = &mdss_1_disp_cc_mdss_dptx3_aux_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX3_CRYPTO_CLK] = &mdss_1_disp_cc_mdss_dptx3_crypto_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX3_LINK_CLK] = &mdss_1_disp_cc_mdss_dptx3_link_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX3_LINK_CLK_SRC] = &mdss_1_disp_cc_mdss_dptx3_link_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX3_LINK_DIV_CLK_SRC] = + &mdss_1_disp_cc_mdss_dptx3_link_div_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX3_LINK_INTF_CLK] = &mdss_1_disp_cc_mdss_dptx3_link_intf_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX3_PIXEL0_CLK] = &mdss_1_disp_cc_mdss_dptx3_pixel0_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX3_PIXEL0_CLK_SRC] = &mdss_1_disp_cc_mdss_dptx3_pixel0_clk_src.clkr, + [MDSS_DISP_CC_MDSS_ESC0_CLK] = &mdss_1_disp_cc_mdss_esc0_clk.clkr, + [MDSS_DISP_CC_MDSS_ESC0_CLK_SRC] = &mdss_1_disp_cc_mdss_esc0_clk_src.clkr, + [MDSS_DISP_CC_MDSS_ESC1_CLK] = &mdss_1_disp_cc_mdss_esc1_clk.clkr, + [MDSS_DISP_CC_MDSS_ESC1_CLK_SRC] = &mdss_1_disp_cc_mdss_esc1_clk_src.clkr, + [MDSS_DISP_CC_MDSS_MDP1_CLK] = &mdss_1_disp_cc_mdss_mdp1_clk.clkr, + [MDSS_DISP_CC_MDSS_MDP_CLK] = &mdss_1_disp_cc_mdss_mdp_clk.clkr, + [MDSS_DISP_CC_MDSS_MDP_CLK_SRC] = &mdss_1_disp_cc_mdss_mdp_clk_src.clkr, + [MDSS_DISP_CC_MDSS_MDP_LUT1_CLK] = &mdss_1_disp_cc_mdss_mdp_lut1_clk.clkr, + [MDSS_DISP_CC_MDSS_MDP_LUT_CLK] = &mdss_1_disp_cc_mdss_mdp_lut_clk.clkr, + [MDSS_DISP_CC_MDSS_NON_GDSC_AHB_CLK] = &mdss_1_disp_cc_mdss_non_gdsc_ahb_clk.clkr, + [MDSS_DISP_CC_MDSS_PCLK0_CLK] = &mdss_1_disp_cc_mdss_pclk0_clk.clkr, + [MDSS_DISP_CC_MDSS_PCLK0_CLK_SRC] = &mdss_1_disp_cc_mdss_pclk0_clk_src.clkr, + [MDSS_DISP_CC_MDSS_PCLK1_CLK] = &mdss_1_disp_cc_mdss_pclk1_clk.clkr, + [MDSS_DISP_CC_MDSS_PCLK1_CLK_SRC] = &mdss_1_disp_cc_mdss_pclk1_clk_src.clkr, + [MDSS_DISP_CC_MDSS_PCLK2_CLK] = &mdss_1_disp_cc_mdss_pclk2_clk.clkr, + [MDSS_DISP_CC_MDSS_PCLK2_CLK_SRC] = &mdss_1_disp_cc_mdss_pclk2_clk_src.clkr, + [MDSS_DISP_CC_MDSS_VSYNC1_CLK] = &mdss_1_disp_cc_mdss_vsync1_clk.clkr, + [MDSS_DISP_CC_MDSS_VSYNC_CLK] = &mdss_1_disp_cc_mdss_vsync_clk.clkr, + [MDSS_DISP_CC_MDSS_VSYNC_CLK_SRC] = &mdss_1_disp_cc_mdss_vsync_clk_src.clkr, + [MDSS_DISP_CC_PLL0] = &mdss_1_disp_cc_pll0.clkr, + [MDSS_DISP_CC_PLL1] = &mdss_1_disp_cc_pll1.clkr, + [MDSS_DISP_CC_PLL2] = &mdss_1_disp_cc_pll2.clkr, + [MDSS_DISP_CC_PLL3] = &mdss_1_disp_cc_pll3.clkr, + [MDSS_DISP_CC_SLEEP_CLK_SRC] = &mdss_1_disp_cc_sleep_clk_src.clkr, + [MDSS_DISP_CC_XO_CLK_SRC] = &mdss_1_disp_cc_xo_clk_src.clkr, +}; + +static struct gdsc *disp_cc_1_nord_gdscs[] = { + [MDSS_DISP_CC_MDSS_CORE_GDSC] = &mdss_1_disp_cc_mdss_core_gdsc, + [MDSS_DISP_CC_MDSS_CORE_INT2_GDSC] = &mdss_1_disp_cc_mdss_core_int2_gdsc, +}; + +static const struct qcom_reset_map disp_cc_1_nord_resets[] = { + [MDSS_DISP_CC_MDSS_CORE_BCR] = { 0x8000 }, + [MDSS_DISP_CC_MDSS_CORE_INT2_BCR] = { 0xa000 }, + [MDSS_DISP_CC_MDSS_RSCC_BCR] = { 0xc000 }, +}; + +static struct clk_alpha_pll *disp_cc_1_nord_plls[] = { + &mdss_1_disp_cc_pll0, + &mdss_1_disp_cc_pll1, + &mdss_1_disp_cc_pll2, + &mdss_1_disp_cc_pll3, +}; + +static const u32 disp_cc_1_nord_critical_cbcrs[] = { + 0xc00c, /* MDSS_DISP_CC_RSCC_AHB_CLK */ + 0xc008, /* MDSS_DISP_CC_RSCC_VSYNC CLK */ + 0xe07c, /* MDSS_DISP_CC_SLEEP_CLK */ + 0xe05c, /* MDSS_DISP_CC_XO_CLK */ +}; + +static const struct regmap_config disp_cc_1_nord_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x1a00c, + .fast_io = true, +}; + +static const struct qcom_cc_driver_data disp_cc_1_nord_driver_data = { + .alpha_plls = disp_cc_1_nord_plls, + .num_alpha_plls = ARRAY_SIZE(disp_cc_1_nord_plls), + .clk_cbcrs = disp_cc_1_nord_critical_cbcrs, + .num_clk_cbcrs = ARRAY_SIZE(disp_cc_1_nord_critical_cbcrs), +}; + +static const struct qcom_cc_desc disp_cc_1_nord_desc = { + .config = &disp_cc_1_nord_regmap_config, + .clks = disp_cc_1_nord_clocks, + .num_clks = ARRAY_SIZE(disp_cc_1_nord_clocks), + .resets = disp_cc_1_nord_resets, + .num_resets = ARRAY_SIZE(disp_cc_1_nord_resets), + .gdscs = disp_cc_1_nord_gdscs, + .num_gdscs = ARRAY_SIZE(disp_cc_1_nord_gdscs), + .use_rpm = true, + .driver_data = &disp_cc_1_nord_driver_data, +}; + +static const struct of_device_id disp_cc_1_nord_match_table[] = { + { .compatible = "qcom,nord-dispcc1" }, + { } +}; +MODULE_DEVICE_TABLE(of, disp_cc_1_nord_match_table); + +static int disp_cc_1_nord_probe(struct platform_device *pdev) +{ + return qcom_cc_probe(pdev, &disp_cc_1_nord_desc); +} + +static struct platform_driver disp_cc_1_nord_driver = { + .probe = disp_cc_1_nord_probe, + .driver = { + .name = "dispcc1-nord", + .of_match_table = disp_cc_1_nord_match_table, + }, +}; + +module_platform_driver(disp_cc_1_nord_driver); + +MODULE_DESCRIPTION("QTI DISPCC1 NORD Driver"); +MODULE_LICENSE("GPL"); From 0c51d1ad27cbb236d909165ef8874a10f3537468 Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Fri, 24 Jul 2026 16:25:09 +0530 Subject: [PATCH 0368/1328] dt-bindings: clock: qcom: Document Nord GPU clock controllers Add Device Tree binding documentation for the GPU clock controllers on the Qualcomm Nord platform. The platform includes two GPU clock controller instances, GPUCC and GPU2CC. Document the compatible strings for both controllers. Reviewed-by: Alexandre Mergnat Reviewed-by: Krzysztof Kozlowski Reviewed-by: Shawn Guo Signed-off-by: Taniya Das Link: https://lore.kernel.org/r/20260724-nords_mm_v1-v3-5-32b45232217f@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- .../bindings/clock/qcom,sm8450-gpucc.yaml | 4 ++ include/dt-bindings/clock/qcom,nord-gpu2cc.h | 47 ++++++++++++++++++ include/dt-bindings/clock/qcom,nord-gpucc.h | 48 +++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 include/dt-bindings/clock/qcom,nord-gpu2cc.h create mode 100644 include/dt-bindings/clock/qcom,nord-gpucc.h diff --git a/Documentation/devicetree/bindings/clock/qcom,sm8450-gpucc.yaml b/Documentation/devicetree/bindings/clock/qcom,sm8450-gpucc.yaml index fdbdf605ee69..2a2d37c59442 100644 --- a/Documentation/devicetree/bindings/clock/qcom,sm8450-gpucc.yaml +++ b/Documentation/devicetree/bindings/clock/qcom,sm8450-gpucc.yaml @@ -18,6 +18,8 @@ description: | include/dt-bindings/clock/qcom,glymur-gpucc.h include/dt-bindings/clock/qcom,kaanapali-gpucc.h include/dt-bindings/clock/qcom,milos-gpucc.h + include/dt-bindings/clock/qcom,nord-gpu2cc.h + include/dt-bindings/clock/qcom,nord-gpucc.h include/dt-bindings/clock/qcom,sar2130p-gpucc.h include/dt-bindings/clock/qcom,sm4450-gpucc.h include/dt-bindings/clock/qcom,sm8450-gpucc.h @@ -33,6 +35,8 @@ properties: - qcom,glymur-gpucc - qcom,kaanapali-gpucc - qcom,milos-gpucc + - qcom,nord-gpu2cc + - qcom,nord-gpucc - qcom,sar2130p-gpucc - qcom,sm4450-gpucc - qcom,sm8450-gpucc diff --git a/include/dt-bindings/clock/qcom,nord-gpu2cc.h b/include/dt-bindings/clock/qcom,nord-gpu2cc.h new file mode 100644 index 000000000000..3886af669c61 --- /dev/null +++ b/include/dt-bindings/clock/qcom,nord-gpu2cc.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef _DT_BINDINGS_CLK_QCOM_GPU_2_CC_NORD_H +#define _DT_BINDINGS_CLK_QCOM_GPU_2_CC_NORD_H + +/* GPU_2_CC clocks */ +#define GPU_2_CC_AHB_CLK 0 +#define GPU_2_CC_CRC_AHB_CLK 1 +#define GPU_2_CC_CX_ACCU_SHIFT_CLK 2 +#define GPU_2_CC_CX_FF_CLK 3 +#define GPU_2_CC_CX_GMU_CLK 4 +#define GPU_2_CC_CXO_AON_CLK 5 +#define GPU_2_CC_CXO_CLK 6 +#define GPU_2_CC_DEMET_CLK 7 +#define GPU_2_CC_FF_CLK_SRC 8 +#define GPU_2_CC_FREQ_MEASURE_CLK 9 +#define GPU_2_CC_GMU_CLK_SRC 10 +#define GPU_2_CC_GPU_SMMU_VOTE_CLK 11 +#define GPU_2_CC_HUB_AON_CLK 12 +#define GPU_2_CC_HUB_CLK_SRC 13 +#define GPU_2_CC_HUB_CX_INT_CLK 14 +#define GPU_2_CC_MEMNOC_GFX_CLK 15 +#define GPU_2_CC_MND1X_0_GFX3D_CLK 16 +#define GPU_2_CC_MND1X_1_GFX3D_CLK 17 +#define GPU_2_CC_PLL0 18 +#define GPU_2_CC_PLL1 19 +#define GPU_2_CC_SLEEP_CLK 20 + +/* GPU_2_CC power domains */ +#define GPU_2_CC_CX_GDSC 0 +#define GPU_2_CC_GX_GDSC 1 + +/* GPU_2_CC resets */ +#define GPU_2_CC_ACD_BCR 0 +#define GPU_2_CC_CB_BCR 1 +#define GPU_2_CC_CX_BCR 2 +#define GPU_2_CC_FAST_HUB_BCR 3 +#define GPU_2_CC_FF_BCR 4 +#define GPU_2_CC_GFX3D_AON_BCR 5 +#define GPU_2_CC_GMU_BCR 6 +#define GPU_2_CC_GX_BCR 7 +#define GPU_2_CC_XO_BCR 8 + +#endif diff --git a/include/dt-bindings/clock/qcom,nord-gpucc.h b/include/dt-bindings/clock/qcom,nord-gpucc.h new file mode 100644 index 000000000000..3cc348be5668 --- /dev/null +++ b/include/dt-bindings/clock/qcom,nord-gpucc.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef _DT_BINDINGS_CLK_QCOM_GPU_CC_NORD_H +#define _DT_BINDINGS_CLK_QCOM_GPU_CC_NORD_H + +/* GPU_CC clocks */ +#define GPU_CC_ACD_GFX3D_CLK 0 +#define GPU_CC_ACMU_CLK 1 +#define GPU_CC_AHB_CLK 2 +#define GPU_CC_CRC_AHB_CLK 3 +#define GPU_CC_CX_ACCU_SHIFT_CLK 4 +#define GPU_CC_CX_FF_CLK 5 +#define GPU_CC_CX_GMU_CLK 6 +#define GPU_CC_CXO_AON_CLK 7 +#define GPU_CC_CXO_CLK 8 +#define GPU_CC_DEMET_CLK 9 +#define GPU_CC_DPM_CLK 10 +#define GPU_CC_FF_CLK_SRC 11 +#define GPU_CC_FREQ_MEASURE_CLK 12 +#define GPU_CC_GMU_CLK_SRC 13 +#define GPU_CC_GPU_SMMU_VOTE_CLK 14 +#define GPU_CC_HUB_AON_CLK 15 +#define GPU_CC_HUB_CLK_SRC 16 +#define GPU_CC_HUB_CX_INT_CLK 17 +#define GPU_CC_HUB_DIV_CLK_SRC 18 +#define GPU_CC_MEMNOC_GFX_CLK 19 +#define GPU_CC_MND1X_GFX3D_CLK 20 +#define GPU_CC_PLL0 21 +#define GPU_CC_PLL1 22 +#define GPU_CC_SLEEP_CLK 23 + +/* GPU_CC power domains */ +#define GPU_CC_CX_GDSC 0 +#define GPU_CC_GX_GDSC 1 + +/* GPU_CC resets */ +#define GPU_CC_ACD_BCR 0 +#define GPU_CC_CB_BCR 1 +#define GPU_CC_CX_BCR 2 +#define GPU_CC_FAST_HUB_BCR 3 +#define GPU_CC_GFX3D_AON_BCR 4 +#define GPU_CC_GX_BCR 5 +#define GPU_CC_XO_BCR 6 + +#endif From 47f2e6e1cee19f29d2581a216a9e4c80fd6a34dd Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Fri, 24 Jul 2026 16:25:10 +0530 Subject: [PATCH 0369/1328] clk: qcom: gpucc: Add Nord graphics clock controller support Add support for the GPU clock controllers (GPUCC) on the Qualcomm Nord platform. The platform includes two GPU clock controller instances,GPUCC and GPU2CC. Register support for both controllers, which provide clocks required for the graphics subsystem. Reviewed-by: Alexandre Mergnat Reviewed-by: Shawn Guo Signed-off-by: Taniya Das Link: https://lore.kernel.org/r/20260724-nords_mm_v1-v3-6-32b45232217f@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/Kconfig | 11 + drivers/clk/qcom/Makefile | 1 + drivers/clk/qcom/gpu2cc-nord.c | 545 ++++++++++++++++++++++++++++++ drivers/clk/qcom/gpucc-nord.c | 593 +++++++++++++++++++++++++++++++++ 4 files changed, 1150 insertions(+) create mode 100644 drivers/clk/qcom/gpu2cc-nord.c create mode 100644 drivers/clk/qcom/gpucc-nord.c diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig index 9f125da2a5ae..476e0e9c7fa6 100644 --- a/drivers/clk/qcom/Kconfig +++ b/drivers/clk/qcom/Kconfig @@ -213,6 +213,17 @@ config CLK_SHIKRA_GCC Say Y if you want to use multimedia devices or peripheral devices such as Camera, Video, UART, SPI, I2C, USB, SD/eMMC etc. +config CLK_NORD_GPUCC + tristate "Nord Graphics Clock Controller" + depends on ARM64 || COMPILE_TEST + select CLK_NORD_GCC + default m if ARCH_QCOM + help + Support for the graphics clock controllers on Nord devices. There are two + graphics clock controllers on Nord SoC. + Say Y if you want to support graphics controller devices and + functionality such as 3D graphics. + config CLK_X1E80100_CAMCC tristate "X1E80100 Camera Clock Controller" depends on ARM64 || COMPILE_TEST diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile index a7572a6712e9..9c78ae7a90d8 100644 --- a/drivers/clk/qcom/Makefile +++ b/drivers/clk/qcom/Makefile @@ -43,6 +43,7 @@ obj-$(CONFIG_CLK_KAANAPALI_TCSRCC) += tcsrcc-kaanapali.o obj-$(CONFIG_CLK_KAANAPALI_VIDEOCC) += videocc-kaanapali.o obj-$(CONFIG_CLK_NORD_DISPCC) += dispcc0-nord.o dispcc1-nord.o obj-$(CONFIG_CLK_NORD_GCC) += gcc-nord.o negcc-nord.o nwgcc-nord.o segcc-nord.o +obj-$(CONFIG_CLK_NORD_GPUCC) += gpucc-nord.o gpu2cc-nord.o obj-$(CONFIG_CLK_NORD_TCSRCC) += tcsrcc-nord.o obj-$(CONFIG_CLK_SHIKRA_GCC) += gcc-shikra.o obj-$(CONFIG_CLK_X1E80100_CAMCC) += camcc-x1e80100.o diff --git a/drivers/clk/qcom/gpu2cc-nord.c b/drivers/clk/qcom/gpu2cc-nord.c new file mode 100644 index 000000000000..a05ff9fa1506 --- /dev/null +++ b/drivers/clk/qcom/gpu2cc-nord.c @@ -0,0 +1,545 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include + +#include + +#include "clk-alpha-pll.h" +#include "clk-branch.h" +#include "clk-pll.h" +#include "clk-rcg.h" +#include "clk-regmap.h" +#include "clk-regmap-divider.h" +#include "clk-regmap-mux.h" +#include "common.h" +#include "gdsc.h" +#include "reset.h" + +enum { + DT_BI_TCXO, + DT_GPLL0_OUT_MAIN, + DT_GPLL0_OUT_MAIN_DIV, +}; + +enum { + P_BI_TCXO, + P_GPLL0_OUT_MAIN, + P_GPLL0_OUT_MAIN_DIV, + P_GPU_2_CC_PLL0_OUT_MAIN, + P_GPU_2_CC_PLL1_OUT_MAIN, +}; + +static const struct pll_vco lucid_ole_vco[] = { + { 249600000, 2300000000, 0 }, +}; + +/* 934.0 MHz Configuration */ +static const struct alpha_pll_config gpu_2_cc_pll0_config = { + .l = 0x30, + .alpha = 0xa555, + .config_ctl_val = 0x20485699, + .config_ctl_hi_val = 0x00182261, + .config_ctl_hi1_val = 0x82aa299c, + .test_ctl_val = 0x00000000, + .test_ctl_hi_val = 0x00000003, + .test_ctl_hi1_val = 0x00009000, + .test_ctl_hi2_val = 0x00000034, + .user_ctl_val = 0x00000000, + .user_ctl_hi_val = 0x00400005, +}; + +static struct clk_alpha_pll gpu_2_cc_pll0 = { + .offset = 0x0, + .config = &gpu_2_cc_pll0_config, + .vco_table = lucid_ole_vco, + .num_vco = ARRAY_SIZE(lucid_ole_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "gpu_2_cc_pll0", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_lucid_evo_ops, + }, + }, +}; + +/* 1100.0 MHz Configuration */ +static const struct alpha_pll_config gpu_2_cc_pll1_config = { + .l = 0x39, + .alpha = 0x4aaa, + .config_ctl_val = 0x20485699, + .config_ctl_hi_val = 0x00182261, + .config_ctl_hi1_val = 0x82aa299c, + .test_ctl_val = 0x00000000, + .test_ctl_hi_val = 0x00000003, + .test_ctl_hi1_val = 0x00009000, + .test_ctl_hi2_val = 0x00000034, + .user_ctl_val = 0x00000000, + .user_ctl_hi_val = 0x00400005, +}; + +static struct clk_alpha_pll gpu_2_cc_pll1 = { + .offset = 0x1000, + .config = &gpu_2_cc_pll1_config, + .vco_table = lucid_ole_vco, + .num_vco = ARRAY_SIZE(lucid_ole_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "gpu_2_cc_pll1", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_lucid_evo_ops, + }, + }, +}; + +static const struct parent_map gpu_2_cc_parent_map_0[] = { + { P_BI_TCXO, 0 }, + { P_GPLL0_OUT_MAIN, 5 }, + { P_GPLL0_OUT_MAIN_DIV, 6 }, +}; + +static const struct clk_parent_data gpu_2_cc_parent_data_0[] = { + { .index = DT_BI_TCXO }, + { .index = DT_GPLL0_OUT_MAIN }, + { .index = DT_GPLL0_OUT_MAIN_DIV }, +}; + +static const struct parent_map gpu_2_cc_parent_map_1[] = { + { P_BI_TCXO, 0 }, + { P_GPU_2_CC_PLL0_OUT_MAIN, 1 }, + { P_GPU_2_CC_PLL1_OUT_MAIN, 3 }, + { P_GPLL0_OUT_MAIN, 5 }, + { P_GPLL0_OUT_MAIN_DIV, 6 }, +}; + +static const struct clk_parent_data gpu_2_cc_parent_data_1[] = { + { .index = DT_BI_TCXO }, + { .hw = &gpu_2_cc_pll0.clkr.hw }, + { .hw = &gpu_2_cc_pll1.clkr.hw }, + { .index = DT_GPLL0_OUT_MAIN }, + { .index = DT_GPLL0_OUT_MAIN_DIV }, +}; + +static const struct parent_map gpu_2_cc_parent_map_2[] = { + { P_BI_TCXO, 0 }, + { P_GPU_2_CC_PLL1_OUT_MAIN, 3 }, + { P_GPLL0_OUT_MAIN, 5 }, + { P_GPLL0_OUT_MAIN_DIV, 6 }, +}; + +static const struct clk_parent_data gpu_2_cc_parent_data_2[] = { + { .index = DT_BI_TCXO }, + { .hw = &gpu_2_cc_pll1.clkr.hw }, + { .index = DT_GPLL0_OUT_MAIN }, + { .index = DT_GPLL0_OUT_MAIN_DIV }, +}; + +static const struct freq_tbl ftbl_gpu_2_cc_ff_clk_src[] = { + F(200000000, P_GPLL0_OUT_MAIN, 3, 0, 0), + { } +}; + +static struct clk_rcg2 gpu_2_cc_ff_clk_src = { + .cmd_rcgr = 0x91c4, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gpu_2_cc_parent_map_0, + .freq_tbl = ftbl_gpu_2_cc_ff_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gpu_2_cc_ff_clk_src", + .parent_data = gpu_2_cc_parent_data_0, + .num_parents = ARRAY_SIZE(gpu_2_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_gpu_2_cc_gmu_clk_src[] = { + F(550000000, P_GPU_2_CC_PLL1_OUT_MAIN, 2, 0, 0), + { } +}; + +static struct clk_rcg2 gpu_2_cc_gmu_clk_src = { + .cmd_rcgr = 0x9174, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gpu_2_cc_parent_map_1, + .freq_tbl = ftbl_gpu_2_cc_gmu_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gpu_2_cc_gmu_clk_src", + .parent_data = gpu_2_cc_parent_data_1, + .num_parents = ARRAY_SIZE(gpu_2_cc_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 gpu_2_cc_hub_clk_src = { + .cmd_rcgr = 0x91a8, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gpu_2_cc_parent_map_2, + .freq_tbl = ftbl_gpu_2_cc_ff_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gpu_2_cc_hub_clk_src", + .parent_data = gpu_2_cc_parent_data_2, + .num_parents = ARRAY_SIZE(gpu_2_cc_parent_data_2), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_branch gpu_2_cc_ahb_clk = { + .halt_reg = 0x90cc, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x90cc, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_2_cc_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &gpu_2_cc_hub_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_2_cc_crc_ahb_clk = { + .halt_reg = 0x90d0, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x90d0, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_2_cc_crc_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &gpu_2_cc_hub_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_2_cc_cx_accu_shift_clk = { + .halt_reg = 0x9114, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x9114, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_2_cc_cx_accu_shift_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_2_cc_cx_ff_clk = { + .halt_reg = 0x9100, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x9100, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_2_cc_cx_ff_clk", + .parent_hws = (const struct clk_hw*[]) { + &gpu_2_cc_ff_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_2_cc_cx_gmu_clk = { + .halt_reg = 0x90e8, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x90e8, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_2_cc_cx_gmu_clk", + .parent_hws = (const struct clk_hw*[]) { + &gpu_2_cc_gmu_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_aon_ops, + }, + }, +}; + +static struct clk_branch gpu_2_cc_cxo_clk = { + .halt_reg = 0x90f8, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x90f8, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_2_cc_cxo_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_2_cc_freq_measure_clk = { + .halt_reg = 0x9008, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x9008, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_2_cc_freq_measure_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_2_cc_gpu_smmu_vote_clk = { + .halt_reg = 0x7000, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x7000, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_2_cc_gpu_smmu_vote_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_2_cc_hub_aon_clk = { + .halt_reg = 0x91a4, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x91a4, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_2_cc_hub_aon_clk", + .parent_hws = (const struct clk_hw*[]) { + &gpu_2_cc_hub_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_aon_ops, + }, + }, +}; + +static struct clk_branch gpu_2_cc_hub_cx_int_clk = { + .halt_reg = 0x90fc, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x90fc, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_2_cc_hub_cx_int_clk", + .parent_hws = (const struct clk_hw*[]) { + &gpu_2_cc_hub_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_aon_ops, + }, + }, +}; + +static struct clk_branch gpu_2_cc_memnoc_gfx_clk = { + .halt_reg = 0x9104, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x9104, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_2_cc_memnoc_gfx_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_2_cc_mnd1x_0_gfx3d_clk = { + .halt_reg = 0x9164, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x9164, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_2_cc_mnd1x_0_gfx3d_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_2_cc_mnd1x_1_gfx3d_clk = { + .halt_reg = 0x9168, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x9168, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_2_cc_mnd1x_1_gfx3d_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_2_cc_sleep_clk = { + .halt_reg = 0x90e0, + .halt_check = BRANCH_HALT_SKIP, + .clkr = { + .enable_reg = 0x90e0, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_2_cc_sleep_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct gdsc gpu_2_cc_cx_gdsc = { + .gdscr = 0x9090, + .gds_hw_ctrl = 0x90a4, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0xf, + .pd = { + .name = "gpu_2_cc_cx_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .flags = VOTABLE | RETAIN_FF_ENABLE, +}; + +static struct gdsc gpu_2_cc_gx_gdsc = { + .gdscr = 0x9034, + .clamp_io_ctrl = 0x9504, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0xf, + .pd = { + .name = "gpu_2_cc_gx_gdsc", + .power_on = gdsc_gx_do_nothing_enable, + }, + .pwrsts = PWRSTS_OFF_ON, + .flags = CLAMP_IO | POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct clk_regmap *gpu_2_cc_nord_clocks[] = { + [GPU_2_CC_AHB_CLK] = &gpu_2_cc_ahb_clk.clkr, + [GPU_2_CC_CRC_AHB_CLK] = &gpu_2_cc_crc_ahb_clk.clkr, + [GPU_2_CC_CX_ACCU_SHIFT_CLK] = &gpu_2_cc_cx_accu_shift_clk.clkr, + [GPU_2_CC_CX_FF_CLK] = &gpu_2_cc_cx_ff_clk.clkr, + [GPU_2_CC_CX_GMU_CLK] = &gpu_2_cc_cx_gmu_clk.clkr, + [GPU_2_CC_CXO_CLK] = &gpu_2_cc_cxo_clk.clkr, + [GPU_2_CC_FF_CLK_SRC] = &gpu_2_cc_ff_clk_src.clkr, + [GPU_2_CC_FREQ_MEASURE_CLK] = &gpu_2_cc_freq_measure_clk.clkr, + [GPU_2_CC_GMU_CLK_SRC] = &gpu_2_cc_gmu_clk_src.clkr, + [GPU_2_CC_GPU_SMMU_VOTE_CLK] = &gpu_2_cc_gpu_smmu_vote_clk.clkr, + [GPU_2_CC_HUB_AON_CLK] = &gpu_2_cc_hub_aon_clk.clkr, + [GPU_2_CC_HUB_CLK_SRC] = &gpu_2_cc_hub_clk_src.clkr, + [GPU_2_CC_HUB_CX_INT_CLK] = &gpu_2_cc_hub_cx_int_clk.clkr, + [GPU_2_CC_MEMNOC_GFX_CLK] = &gpu_2_cc_memnoc_gfx_clk.clkr, + [GPU_2_CC_MND1X_0_GFX3D_CLK] = &gpu_2_cc_mnd1x_0_gfx3d_clk.clkr, + [GPU_2_CC_MND1X_1_GFX3D_CLK] = &gpu_2_cc_mnd1x_1_gfx3d_clk.clkr, + [GPU_2_CC_PLL0] = &gpu_2_cc_pll0.clkr, + [GPU_2_CC_PLL1] = &gpu_2_cc_pll1.clkr, + [GPU_2_CC_SLEEP_CLK] = &gpu_2_cc_sleep_clk.clkr, +}; + +static struct gdsc *gpu_2_cc_nord_gdscs[] = { + [GPU_2_CC_CX_GDSC] = &gpu_2_cc_cx_gdsc, + [GPU_2_CC_GX_GDSC] = &gpu_2_cc_gx_gdsc, +}; + +static const struct qcom_reset_map gpu_2_cc_nord_resets[] = { + [GPU_2_CC_ACD_BCR] = { 0x918c }, + [GPU_2_CC_CB_BCR] = { 0x9198 }, + [GPU_2_CC_CX_BCR] = { 0x908c }, + [GPU_2_CC_FAST_HUB_BCR] = { 0x91a0 }, + [GPU_2_CC_FF_BCR] = { 0x91c0 }, + [GPU_2_CC_GFX3D_AON_BCR] = { 0x9118 }, + [GPU_2_CC_GMU_BCR] = { 0x9170 }, + [GPU_2_CC_GX_BCR] = { 0x9030 }, + [GPU_2_CC_XO_BCR] = { 0x9000 }, +}; + +static struct clk_alpha_pll *gpu_2_cc_nord_plls[] = { + &gpu_2_cc_pll0, + &gpu_2_cc_pll1, +}; + +static const u32 gpu_2_cc_nord_critical_cbcrs[] = { + 0x9004, /* GPU_2_CC_CXO_AON_CLK */ + 0x900c, /* GPU_2_CC_DEMET_CLK */ +}; + +static const struct regmap_config gpu_2_cc_nord_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x9ff0, + .fast_io = true, +}; + +static const struct qcom_cc_driver_data gpu_2_cc_nord_driver_data = { + .alpha_plls = gpu_2_cc_nord_plls, + .num_alpha_plls = ARRAY_SIZE(gpu_2_cc_nord_plls), + .clk_cbcrs = gpu_2_cc_nord_critical_cbcrs, + .num_clk_cbcrs = ARRAY_SIZE(gpu_2_cc_nord_critical_cbcrs), +}; + +static const struct qcom_cc_desc gpu_2_cc_nord_desc = { + .config = &gpu_2_cc_nord_regmap_config, + .clks = gpu_2_cc_nord_clocks, + .num_clks = ARRAY_SIZE(gpu_2_cc_nord_clocks), + .resets = gpu_2_cc_nord_resets, + .num_resets = ARRAY_SIZE(gpu_2_cc_nord_resets), + .gdscs = gpu_2_cc_nord_gdscs, + .num_gdscs = ARRAY_SIZE(gpu_2_cc_nord_gdscs), + .use_rpm = true, + .driver_data = &gpu_2_cc_nord_driver_data, +}; + +static const struct of_device_id gpu_2_cc_nord_match_table[] = { + { .compatible = "qcom,nord-gpu2cc" }, + { } +}; +MODULE_DEVICE_TABLE(of, gpu_2_cc_nord_match_table); + +static int gpu_2_cc_nord_probe(struct platform_device *pdev) +{ + return qcom_cc_probe(pdev, &gpu_2_cc_nord_desc); +} + +static struct platform_driver gpu_2_cc_nord_driver = { + .probe = gpu_2_cc_nord_probe, + .driver = { + .name = "gpu2cc-nord", + .of_match_table = gpu_2_cc_nord_match_table, + }, +}; + +module_platform_driver(gpu_2_cc_nord_driver); + +MODULE_DESCRIPTION("QTI GPU2CC Nord Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/clk/qcom/gpucc-nord.c b/drivers/clk/qcom/gpucc-nord.c new file mode 100644 index 000000000000..f37bc32e80ff --- /dev/null +++ b/drivers/clk/qcom/gpucc-nord.c @@ -0,0 +1,593 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include + +#include + +#include "clk-alpha-pll.h" +#include "clk-branch.h" +#include "clk-pll.h" +#include "clk-rcg.h" +#include "clk-regmap.h" +#include "clk-regmap-divider.h" +#include "clk-regmap-mux.h" +#include "common.h" +#include "gdsc.h" +#include "reset.h" + +enum { + DT_BI_TCXO, + DT_GPLL0_OUT_MAIN, + DT_GPLL0_OUT_MAIN_DIV, +}; + +enum { + P_BI_TCXO, + P_GPLL0_OUT_MAIN, + P_GPLL0_OUT_MAIN_DIV, + P_GPU_CC_PLL0_OUT_MAIN, + P_GPU_CC_PLL1_OUT_MAIN, +}; + +static const struct pll_vco lucid_ole_vco[] = { + { 249600000, 2300000000, 0 }, +}; + +/* 936.0 MHz Configuration */ +static const struct alpha_pll_config gpu_cc_pll0_config = { + .l = 0x30, + .alpha = 0xc000, + .config_ctl_val = 0x20485699, + .config_ctl_hi_val = 0x00182261, + .config_ctl_hi1_val = 0x82aa299c, + .test_ctl_val = 0x00000000, + .test_ctl_hi_val = 0x00000003, + .test_ctl_hi1_val = 0x00009000, + .test_ctl_hi2_val = 0x00000034, + .user_ctl_val = 0x00000000, + .user_ctl_hi_val = 0x00400005, +}; + +static struct clk_alpha_pll gpu_cc_pll0 = { + .offset = 0x0, + .config = &gpu_cc_pll0_config, + .vco_table = lucid_ole_vco, + .num_vco = ARRAY_SIZE(lucid_ole_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_pll0", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_lucid_evo_ops, + }, + }, +}; + +/* 1250.0 MHz Configuration */ +static const struct alpha_pll_config gpu_cc_pll1_config = { + .l = 0x41, + .alpha = 0x1aaa, + .config_ctl_val = 0x20485699, + .config_ctl_hi_val = 0x00182261, + .config_ctl_hi1_val = 0x82aa299c, + .test_ctl_val = 0x00000000, + .test_ctl_hi_val = 0x00000003, + .test_ctl_hi1_val = 0x00009000, + .test_ctl_hi2_val = 0x00000034, + .user_ctl_val = 0x00000000, + .user_ctl_hi_val = 0x00400005, +}; + +static struct clk_alpha_pll gpu_cc_pll1 = { + .offset = 0x1000, + .config = &gpu_cc_pll1_config, + .vco_table = lucid_ole_vco, + .num_vco = ARRAY_SIZE(lucid_ole_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_pll1", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_lucid_evo_ops, + }, + }, +}; + +static const struct parent_map gpu_cc_parent_map_0[] = { + { P_BI_TCXO, 0 }, + { P_GPLL0_OUT_MAIN, 5 }, + { P_GPLL0_OUT_MAIN_DIV, 6 }, +}; + +static const struct clk_parent_data gpu_cc_parent_data_0[] = { + { .index = DT_BI_TCXO }, + { .index = DT_GPLL0_OUT_MAIN }, + { .index = DT_GPLL0_OUT_MAIN_DIV }, +}; + +static const struct parent_map gpu_cc_parent_map_1[] = { + { P_BI_TCXO, 0 }, + { P_GPU_CC_PLL0_OUT_MAIN, 1 }, + { P_GPU_CC_PLL1_OUT_MAIN, 3 }, + { P_GPLL0_OUT_MAIN, 5 }, + { P_GPLL0_OUT_MAIN_DIV, 6 }, +}; + +static const struct clk_parent_data gpu_cc_parent_data_1[] = { + { .index = DT_BI_TCXO }, + { .hw = &gpu_cc_pll0.clkr.hw }, + { .hw = &gpu_cc_pll1.clkr.hw }, + { .index = DT_GPLL0_OUT_MAIN }, + { .index = DT_GPLL0_OUT_MAIN_DIV }, +}; + +static const struct parent_map gpu_cc_parent_map_2[] = { + { P_BI_TCXO, 0 }, + { P_GPU_CC_PLL1_OUT_MAIN, 3 }, + { P_GPLL0_OUT_MAIN, 5 }, + { P_GPLL0_OUT_MAIN_DIV, 6 }, +}; + +static const struct clk_parent_data gpu_cc_parent_data_2[] = { + { .index = DT_BI_TCXO }, + { .hw = &gpu_cc_pll1.clkr.hw }, + { .index = DT_GPLL0_OUT_MAIN }, + { .index = DT_GPLL0_OUT_MAIN_DIV }, +}; + +static const struct freq_tbl ftbl_gpu_cc_ff_clk_src[] = { + F(200000000, P_GPLL0_OUT_MAIN, 3, 0, 0), + { } +}; + +static struct clk_rcg2 gpu_cc_ff_clk_src = { + .cmd_rcgr = 0x93d4, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gpu_cc_parent_map_0, + .freq_tbl = ftbl_gpu_cc_ff_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_ff_clk_src", + .parent_data = gpu_cc_parent_data_0, + .num_parents = ARRAY_SIZE(gpu_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_gpu_cc_gmu_clk_src[] = { + F(416666667, P_GPU_CC_PLL1_OUT_MAIN, 3, 0, 0), + F(625000000, P_GPU_CC_PLL1_OUT_MAIN, 2, 0, 0), + { } +}; + +static struct clk_rcg2 gpu_cc_gmu_clk_src = { + .cmd_rcgr = 0x92b8, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gpu_cc_parent_map_1, + .freq_tbl = ftbl_gpu_cc_gmu_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_gmu_clk_src", + .parent_data = gpu_cc_parent_data_1, + .num_parents = ARRAY_SIZE(gpu_cc_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_gpu_cc_hub_clk_src[] = { + F(300000000, P_GPLL0_OUT_MAIN, 2, 0, 0), + { } +}; + +static struct clk_rcg2 gpu_cc_hub_clk_src = { + .cmd_rcgr = 0x938c, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gpu_cc_parent_map_2, + .freq_tbl = ftbl_gpu_cc_hub_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_hub_clk_src", + .parent_data = gpu_cc_parent_data_2, + .num_parents = ARRAY_SIZE(gpu_cc_parent_data_2), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_regmap_div gpu_cc_hub_div_clk_src = { + .reg = 0x93cc, + .shift = 0, + .width = 4, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_hub_div_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &gpu_cc_hub_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_regmap_div_ro_ops, + }, +}; + +static struct clk_branch gpu_cc_acd_gfx3d_clk = { + .halt_reg = 0x92a8, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x92a8, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_acd_gfx3d_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_cc_acmu_clk = { + .halt_reg = 0x9294, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x9294, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_acmu_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_cc_ahb_clk = { + .halt_reg = 0x9150, + .halt_check = BRANCH_HALT_DELAY, + .clkr = { + .enable_reg = 0x9150, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &gpu_cc_hub_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_cc_crc_ahb_clk = { + .halt_reg = 0x9154, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x9154, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_crc_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &gpu_cc_hub_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_cc_cx_accu_shift_clk = { + .halt_reg = 0x91a4, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x91a4, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_cx_accu_shift_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_cc_cx_ff_clk = { + .halt_reg = 0x9184, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x9184, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_cx_ff_clk", + .parent_hws = (const struct clk_hw*[]) { + &gpu_cc_ff_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_cc_cx_gmu_clk = { + .halt_reg = 0x916c, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x916c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_cx_gmu_clk", + .parent_hws = (const struct clk_hw*[]) { + &gpu_cc_gmu_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_aon_ops, + }, + }, +}; + +static struct clk_branch gpu_cc_cxo_clk = { + .halt_reg = 0x917c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x917c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_cxo_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_cc_dpm_clk = { + .halt_reg = 0x91a8, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x91a8, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_dpm_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_cc_freq_measure_clk = { + .halt_reg = 0x9008, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x9008, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_freq_measure_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_cc_gpu_smmu_vote_clk = { + .halt_reg = 0x7000, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x7000, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_gpu_smmu_vote_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_cc_hub_aon_clk = { + .halt_reg = 0x9388, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x9388, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_hub_aon_clk", + .parent_hws = (const struct clk_hw*[]) { + &gpu_cc_hub_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_aon_ops, + }, + }, +}; + +static struct clk_branch gpu_cc_hub_cx_int_clk = { + .halt_reg = 0x9180, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x9180, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_hub_cx_int_clk", + .parent_hws = (const struct clk_hw*[]) { + &gpu_cc_hub_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_aon_ops, + }, + }, +}; + +static struct clk_branch gpu_cc_memnoc_gfx_clk = { + .halt_reg = 0x9188, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x9188, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_memnoc_gfx_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_cc_mnd1x_gfx3d_clk = { + .halt_reg = 0x92ac, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x92ac, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_mnd1x_gfx3d_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_cc_sleep_clk = { + .halt_reg = 0x9164, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x9164, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_sleep_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct gdsc gpu_cc_cx_gdsc = { + .gdscr = 0x90e8, + .gds_hw_ctrl = 0x9128, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0xf, + .pd = { + .name = "gpu_cc_cx_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .flags = VOTABLE | RETAIN_FF_ENABLE, +}; + +static struct gdsc gpu_cc_gx_gdsc = { + .gdscr = 0x905c, + .clamp_io_ctrl = 0x9504, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0xf, + .pd = { + .name = "gpu_cc_gx_gdsc", + .power_on = gdsc_gx_do_nothing_enable, + }, + .pwrsts = PWRSTS_OFF_ON, + .flags = CLAMP_IO | POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct clk_regmap *gpu_cc_nord_clocks[] = { + [GPU_CC_ACD_GFX3D_CLK] = &gpu_cc_acd_gfx3d_clk.clkr, + [GPU_CC_ACMU_CLK] = &gpu_cc_acmu_clk.clkr, + [GPU_CC_AHB_CLK] = &gpu_cc_ahb_clk.clkr, + [GPU_CC_CRC_AHB_CLK] = &gpu_cc_crc_ahb_clk.clkr, + [GPU_CC_CX_ACCU_SHIFT_CLK] = &gpu_cc_cx_accu_shift_clk.clkr, + [GPU_CC_CX_FF_CLK] = &gpu_cc_cx_ff_clk.clkr, + [GPU_CC_CX_GMU_CLK] = &gpu_cc_cx_gmu_clk.clkr, + [GPU_CC_CXO_CLK] = &gpu_cc_cxo_clk.clkr, + [GPU_CC_DPM_CLK] = &gpu_cc_dpm_clk.clkr, + [GPU_CC_FF_CLK_SRC] = &gpu_cc_ff_clk_src.clkr, + [GPU_CC_FREQ_MEASURE_CLK] = &gpu_cc_freq_measure_clk.clkr, + [GPU_CC_GMU_CLK_SRC] = &gpu_cc_gmu_clk_src.clkr, + [GPU_CC_GPU_SMMU_VOTE_CLK] = &gpu_cc_gpu_smmu_vote_clk.clkr, + [GPU_CC_HUB_AON_CLK] = &gpu_cc_hub_aon_clk.clkr, + [GPU_CC_HUB_CLK_SRC] = &gpu_cc_hub_clk_src.clkr, + [GPU_CC_HUB_CX_INT_CLK] = &gpu_cc_hub_cx_int_clk.clkr, + [GPU_CC_HUB_DIV_CLK_SRC] = &gpu_cc_hub_div_clk_src.clkr, + [GPU_CC_MEMNOC_GFX_CLK] = &gpu_cc_memnoc_gfx_clk.clkr, + [GPU_CC_MND1X_GFX3D_CLK] = &gpu_cc_mnd1x_gfx3d_clk.clkr, + [GPU_CC_PLL0] = &gpu_cc_pll0.clkr, + [GPU_CC_PLL1] = &gpu_cc_pll1.clkr, + [GPU_CC_SLEEP_CLK] = &gpu_cc_sleep_clk.clkr, +}; + +static struct gdsc *gpu_cc_nord_gdscs[] = { + [GPU_CC_CX_GDSC] = &gpu_cc_cx_gdsc, + [GPU_CC_GX_GDSC] = &gpu_cc_gx_gdsc, +}; + +static const struct qcom_reset_map gpu_cc_nord_resets[] = { + [GPU_CC_ACD_BCR] = { 0x92f8 }, + [GPU_CC_CB_BCR] = { 0x9340 }, + [GPU_CC_CX_BCR] = { 0x90e4 }, + [GPU_CC_FAST_HUB_BCR] = { 0x9384 }, + [GPU_CC_GFX3D_AON_BCR] = { 0x91ac }, + [GPU_CC_GX_BCR] = { 0x9058 }, + [GPU_CC_XO_BCR] = { 0x9000 }, +}; + +static struct clk_alpha_pll *gpu_cc_nord_plls[] = { + &gpu_cc_pll0, + &gpu_cc_pll1, +}; + +static const u32 gpu_cc_nord_critical_cbcrs[] = { + 0x9004, /* GPU_CC_CXO_AON_CLK */ + 0x900c, /* GPU_CC_DEMET_CLK */ +}; + +static const struct regmap_config gpu_cc_nord_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x9660, + .fast_io = true, +}; + +static const struct qcom_cc_driver_data gpu_cc_nord_driver_data = { + .alpha_plls = gpu_cc_nord_plls, + .num_alpha_plls = ARRAY_SIZE(gpu_cc_nord_plls), + .clk_cbcrs = gpu_cc_nord_critical_cbcrs, + .num_clk_cbcrs = ARRAY_SIZE(gpu_cc_nord_critical_cbcrs), +}; + +static const struct qcom_cc_desc gpu_cc_nord_desc = { + .config = &gpu_cc_nord_regmap_config, + .clks = gpu_cc_nord_clocks, + .num_clks = ARRAY_SIZE(gpu_cc_nord_clocks), + .resets = gpu_cc_nord_resets, + .num_resets = ARRAY_SIZE(gpu_cc_nord_resets), + .gdscs = gpu_cc_nord_gdscs, + .num_gdscs = ARRAY_SIZE(gpu_cc_nord_gdscs), + .use_rpm = true, + .driver_data = &gpu_cc_nord_driver_data, +}; + +static const struct of_device_id gpu_cc_nord_match_table[] = { + { .compatible = "qcom,nord-gpucc" }, + { } +}; +MODULE_DEVICE_TABLE(of, gpu_cc_nord_match_table); + +static int gpu_cc_nord_probe(struct platform_device *pdev) +{ + return qcom_cc_probe(pdev, &gpu_cc_nord_desc); +} + +static struct platform_driver gpu_cc_nord_driver = { + .probe = gpu_cc_nord_probe, + .driver = { + .name = "gpucc-nord", + .of_match_table = gpu_cc_nord_match_table, + }, +}; + +module_platform_driver(gpu_cc_nord_driver); + +MODULE_DESCRIPTION("QTI GPUCC Nord Driver"); +MODULE_LICENSE("GPL"); From 6ef116708c63b6c4e5889c7682bf6f92efd30f3e Mon Sep 17 00:00:00 2001 From: Imran Shaik Date: Fri, 24 Jul 2026 22:45:32 +0530 Subject: [PATCH 0370/1328] dt-bindings: clock: qcom: Add Qualcomm Shikra AudioCoreCC and AudioCoreCSR Add device tree bindings for the Audio Core Clock Controller (AudioCoreCC) that provides clocks and Audio Core CSR that provides resets on Qualcomm Shikra SoC. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Imran Shaik Link: https://lore.kernel.org/r/20260724-shikra-audiocorecc-v4-1-0a89bb13d817@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- .../clock/qcom,shikra-audiocorecc.yaml | 55 +++++++++++++++++++ .../reset/qcom,shikra-audiocore-csr.yaml | 42 ++++++++++++++ .../clock/qcom,shikra-audiocorecc.h | 49 +++++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 Documentation/devicetree/bindings/clock/qcom,shikra-audiocorecc.yaml create mode 100644 Documentation/devicetree/bindings/reset/qcom,shikra-audiocore-csr.yaml create mode 100644 include/dt-bindings/clock/qcom,shikra-audiocorecc.h diff --git a/Documentation/devicetree/bindings/clock/qcom,shikra-audiocorecc.yaml b/Documentation/devicetree/bindings/clock/qcom,shikra-audiocorecc.yaml new file mode 100644 index 000000000000..d59f7e47a0d7 --- /dev/null +++ b/Documentation/devicetree/bindings/clock/qcom,shikra-audiocorecc.yaml @@ -0,0 +1,55 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/clock/qcom,shikra-audiocorecc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Audio Core Clock Controller for Qualcomm Shikra SoC + +maintainers: + - Imran Shaik + +description: | + Audio core clock control module provides the clocks on Qualcomm Shikra + SoC platform. + + See also: + - include/dt-bindings/clock/qcom,shikra-audiocorecc.h + +properties: + compatible: + const: qcom,shikra-audiocorecc + + clocks: + items: + - description: Board XO source + - description: Board sleep clock + - description: Audio ref clock source + + reg: + maxItems: 1 + + '#clock-cells': + const: 1 + +required: + - compatible + - reg + - clocks + - '#clock-cells' + +additionalProperties: false + +examples: + - | + #include + #include + clock-controller@a0a0000 { + compatible = "qcom,shikra-audiocorecc"; + reg = <0x0a0a0000 0x10000>; + clocks = <&rpmcc RPM_SMD_XO_CLK_SRC>, + <&sleep_clk>, + <&aud_ref_clk_src>; + #clock-cells = <1>; + }; +... diff --git a/Documentation/devicetree/bindings/reset/qcom,shikra-audiocore-csr.yaml b/Documentation/devicetree/bindings/reset/qcom,shikra-audiocore-csr.yaml new file mode 100644 index 000000000000..bfad1d20e7cd --- /dev/null +++ b/Documentation/devicetree/bindings/reset/qcom,shikra-audiocore-csr.yaml @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/reset/qcom,shikra-audiocore-csr.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Audio Core CSR for Qualcomm Shikra SoC + +maintainers: + - Imran Shaik + +description: | + Audio Core CSR module provides the resets on Qualcomm Shikra SoC platform. + + See also: + - include/dt-bindings/clock/qcom,shikra-audiocorecc.h + +properties: + compatible: + const: qcom,shikra-audiocore-csr + + reg: + maxItems: 1 + + '#reset-cells': + const: 1 + +required: + - compatible + - reg + - '#reset-cells' + +additionalProperties: false + +examples: + - | + reset-controller@a0b4000 { + compatible = "qcom,shikra-audiocore-csr"; + reg = <0x0a0b4000 0x1000>; + #reset-cells = <1>; + }; +... diff --git a/include/dt-bindings/clock/qcom,shikra-audiocorecc.h b/include/dt-bindings/clock/qcom,shikra-audiocorecc.h new file mode 100644 index 000000000000..0e64a42523f1 --- /dev/null +++ b/include/dt-bindings/clock/qcom,shikra-audiocorecc.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef _DT_BINDINGS_CLK_QCOM_AUDIO_CORE_CC_SHIKRA_H +#define _DT_BINDINGS_CLK_QCOM_AUDIO_CORE_CC_SHIKRA_H + +/* AUDIO_CORE_CC clocks */ +#define AUDIO_CORE_CC_DIG_PLL_OUT_AUX 0 +#define AUDIO_CORE_CC_DIG_PLL_OUT_AUX2 1 +#define AUDIO_CORE_CC_DIG_PLL 2 +#define AUDIO_CORE_CC_AIF_IF0_CLK_SRC 3 +#define AUDIO_CORE_CC_AIF_IF0_EBIT_CLK 4 +#define AUDIO_CORE_CC_AIF_IF0_IBIT_CLK 5 +#define AUDIO_CORE_CC_AIF_IF1_CLK_SRC 6 +#define AUDIO_CORE_CC_AIF_IF1_EBIT_CLK 7 +#define AUDIO_CORE_CC_AIF_IF1_IBIT_CLK 8 +#define AUDIO_CORE_CC_AIF_IF2_CLK_SRC 9 +#define AUDIO_CORE_CC_AIF_IF2_EBIT_CLK 10 +#define AUDIO_CORE_CC_AIF_IF2_IBIT_CLK 11 +#define AUDIO_CORE_CC_AIF_IF3_CLK_SRC 12 +#define AUDIO_CORE_CC_AIF_IF3_EBIT_CLK 13 +#define AUDIO_CORE_CC_AIF_IF3_IBIT_CLK 14 +#define AUDIO_CORE_CC_AUD_DMA_CLK 15 +#define AUDIO_CORE_CC_AUD_DMA_CLK_SRC 16 +#define AUDIO_CORE_CC_AUD_DMA_MEM_CLK 17 +#define AUDIO_CORE_CC_BUS_CLK 18 +#define AUDIO_CORE_CC_BUS_CLK_SRC 19 +#define AUDIO_CORE_CC_CDIV_TX_MCLK_DIV_CLK_SRC 20 +#define AUDIO_CORE_CC_EXT_MCLKA_CLK_SRC 21 +#define AUDIO_CORE_CC_EXT_MCLKA_OUT_CLK 22 +#define AUDIO_CORE_CC_EXT_MCLKB_CLK_SRC 23 +#define AUDIO_CORE_CC_EXT_MCLKB_OUT_CLK 24 +#define AUDIO_CORE_CC_IM_SLEEP_CLK 25 +#define AUDIO_CORE_CC_LPAIF_PCMOE_CLK 26 +#define AUDIO_CORE_CC_LPAIF_PCMOE_CLK_SRC 27 +#define AUDIO_CORE_CC_RX_MCLK_2X_CLK 28 +#define AUDIO_CORE_CC_RX_MCLK_CLK 29 +#define AUDIO_CORE_CC_SAMPLING_CLK 30 +#define AUDIO_CORE_CC_TX_MCLK_2X_CLK 31 +#define AUDIO_CORE_CC_TX_MCLK_CLK 32 +#define AUDIO_CORE_CC_TX_MCLK_RCG_CLK_SRC 33 + +/* AUDIO_CORE_CSR resets */ +#define AUDIO_CORE_CSR_RX_SWR_CGCR 0 +#define AUDIO_CORE_CSR_TX_SWR_CGCR 1 + +#endif From 3e7b00804665c8d2e87faaf3ca8e170bc0246896 Mon Sep 17 00:00:00 2001 From: Imran Shaik Date: Fri, 24 Jul 2026 22:45:33 +0530 Subject: [PATCH 0371/1328] clk: qcom: Add Audio Core clock controller support on Qualcomm Shikra SoC Add support for Audio Core Clock Controller (AUDIOCORECC) and Audio Core CSR resets on Qualcomm Shikra SoC. Reviewed-by: Konrad Dybcio Reviewed-by: Dmitry Baryshkov Signed-off-by: Imran Shaik Link: https://lore.kernel.org/r/20260724-shikra-audiocorecc-v4-2-0a89bb13d817@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/Kconfig | 10 + drivers/clk/qcom/Makefile | 1 + drivers/clk/qcom/audiocorecc-shikra.c | 810 ++++++++++++++++++++++++++ 3 files changed, 821 insertions(+) create mode 100644 drivers/clk/qcom/audiocorecc-shikra.c diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig index 476e0e9c7fa6..2187270e373f 100644 --- a/drivers/clk/qcom/Kconfig +++ b/drivers/clk/qcom/Kconfig @@ -203,6 +203,16 @@ config CLK_NORD_GCC SPI, I2C, USB, SD/UFS, PCIe etc. The clock controller is a combination of GCC, SE_GCC, NE_GCC and NW_GCC. +config CLK_SHIKRA_AUDIOCORECC + tristate "Shikra Audio Core Clock Controller" + depends on ARM64 || COMPILE_TEST + select CLK_SHIKRA_GCC + default m if ARCH_QCOM + help + Support for the Audio Core clock controller on Qualcomm Shikra devices. + Say Y if you want to use AudioCoreCC clocks required to support audio + devices and it's functionality. + config CLK_SHIKRA_GCC tristate "Shikra Global Clock Controller" depends on ARM64 || COMPILE_TEST diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile index 9c78ae7a90d8..cc62bda00d2a 100644 --- a/drivers/clk/qcom/Makefile +++ b/drivers/clk/qcom/Makefile @@ -45,6 +45,7 @@ obj-$(CONFIG_CLK_NORD_DISPCC) += dispcc0-nord.o dispcc1-nord.o obj-$(CONFIG_CLK_NORD_GCC) += gcc-nord.o negcc-nord.o nwgcc-nord.o segcc-nord.o obj-$(CONFIG_CLK_NORD_GPUCC) += gpucc-nord.o gpu2cc-nord.o obj-$(CONFIG_CLK_NORD_TCSRCC) += tcsrcc-nord.o +obj-$(CONFIG_CLK_SHIKRA_AUDIOCORECC) += audiocorecc-shikra.o obj-$(CONFIG_CLK_SHIKRA_GCC) += gcc-shikra.o obj-$(CONFIG_CLK_X1E80100_CAMCC) += camcc-x1e80100.o obj-$(CONFIG_CLK_X1E80100_DISPCC) += dispcc-x1e80100.o diff --git a/drivers/clk/qcom/audiocorecc-shikra.c b/drivers/clk/qcom/audiocorecc-shikra.c new file mode 100644 index 000000000000..19f67b50d623 --- /dev/null +++ b/drivers/clk/qcom/audiocorecc-shikra.c @@ -0,0 +1,810 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include + +#include + +#include "clk-alpha-pll.h" +#include "clk-branch.h" +#include "clk-pll.h" +#include "clk-rcg.h" +#include "clk-regmap.h" +#include "clk-regmap-divider.h" +#include "clk-regmap-mux.h" +#include "common.h" +#include "reset.h" + +enum { + DT_BI_TCXO, + DT_SLEEP_CLK, + DT_AUD_REF_CLK_SRC, +}; + +enum { + P_AUD_REF_CLK_SRC, + P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX, + P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX2, + P_BI_TCXO, + P_SLEEP_CLK, +}; + +static const struct pll_vco spark_vco[] = { + { 500000000, 1000000000, 2 }, +}; + +/* 614.4 MHz Configuration */ +static const struct alpha_pll_config audio_core_cc_dig_pll_config = { + .l = 0x20, + .alpha = 0x0, + .vco_val = BIT(21), + .post_div_val = 0x28100, + .post_div_mask = GENMASK(17, 8), + .vco_mask = GENMASK(21, 20), + .main_output_mask = BIT(0), + .aux_output_mask = BIT(1), + .aux2_output_mask = BIT(2), + .config_ctl_val = 0x4001055b, + .test_ctl_hi_val = 0x1, + .test_ctl_hi_mask = 0x1, +}; + +static struct clk_alpha_pll audio_core_cc_dig_pll = { + .offset = 0x0, + .config = &audio_core_cc_dig_pll_config, + .vco_table = spark_vco, + .num_vco = ARRAY_SIZE(spark_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_DEFAULT], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "audio_core_cc_dig_pll", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_fixed_ops, + }, + }, +}; + +static struct clk_fixed_factor audio_core_cc_dig_pll_out_aux = { + .mult = 1, + .div = 5, + .hw.init = &(struct clk_init_data) { + .name = "audio_core_cc_dig_pll_out_aux", + .parent_data = &(const struct clk_parent_data) { + .hw = &audio_core_cc_dig_pll.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor audio_core_cc_dig_pll_out_aux2 = { + .mult = 1, + .div = 2, + .hw.init = &(struct clk_init_data) { + .name = "audio_core_cc_dig_pll_out_aux2", + .parent_data = &(const struct clk_parent_data) { + .hw = &audio_core_cc_dig_pll.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_fixed_factor_ops, + }, +}; + +static const struct parent_map audio_core_cc_parent_map_0[] = { + { P_BI_TCXO, 0 }, + { P_AUD_REF_CLK_SRC, 1 }, + { P_SLEEP_CLK, 2 }, + { P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX, 4 }, + { P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX2, 6 }, +}; + +static const struct clk_parent_data audio_core_cc_parent_data_0[] = { + { .index = DT_BI_TCXO }, + { .index = DT_AUD_REF_CLK_SRC }, + { .index = DT_SLEEP_CLK }, + { .hw = &audio_core_cc_dig_pll_out_aux.hw }, + { .hw = &audio_core_cc_dig_pll_out_aux2.hw }, +}; + +static const struct freq_tbl ftbl_audio_core_cc_aif_if0_clk_src[] = { + F(240000, P_BI_TCXO, 10, 1, 8), + F(256000, P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX, 15, 1, 32), + F(512000, P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX, 15, 1, 16), + F(768000, P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX, 10, 1, 16), + F(1024000, P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX, 15, 1, 8), + F(1536000, P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX, 10, 1, 8), + F(2048000, P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX, 15, 1, 4), + F(3072000, P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX, 10, 1, 4), + F(4096000, P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX, 15, 1, 2), + F(6144000, P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX, 10, 1, 2), + F(8192000, P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX, 15, 0, 0), + F(9600000, P_BI_TCXO, 2, 0, 0), + F(12288000, P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX, 10, 0, 0), + F(19200000, P_BI_TCXO, 1, 0, 0), + F(24576000, P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX, 5, 0, 0), + { } +}; + +static struct clk_rcg2 audio_core_cc_aif_if0_clk_src = { + .cmd_rcgr = 0x104c, + .mnd_width = 16, + .hid_width = 5, + .parent_map = audio_core_cc_parent_map_0, + .freq_tbl = ftbl_audio_core_cc_aif_if0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "audio_core_cc_aif_if0_clk_src", + .parent_data = audio_core_cc_parent_data_0, + .num_parents = ARRAY_SIZE(audio_core_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 audio_core_cc_aif_if1_clk_src = { + .cmd_rcgr = 0x10b0, + .mnd_width = 16, + .hid_width = 5, + .parent_map = audio_core_cc_parent_map_0, + .freq_tbl = ftbl_audio_core_cc_aif_if0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "audio_core_cc_aif_if1_clk_src", + .parent_data = audio_core_cc_parent_data_0, + .num_parents = ARRAY_SIZE(audio_core_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 audio_core_cc_aif_if2_clk_src = { + .cmd_rcgr = 0x1114, + .mnd_width = 16, + .hid_width = 5, + .parent_map = audio_core_cc_parent_map_0, + .freq_tbl = ftbl_audio_core_cc_aif_if0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "audio_core_cc_aif_if2_clk_src", + .parent_data = audio_core_cc_parent_data_0, + .num_parents = ARRAY_SIZE(audio_core_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_audio_core_cc_aif_if3_clk_src[] = { + F(240000, P_BI_TCXO, 10, 1, 8), + F(256000, P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX, 15, 1, 32), + F(512000, P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX, 15, 1, 16), + F(768000, P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX, 10, 1, 16), + F(1024000, P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX, 15, 1, 8), + F(1536000, P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX, 10, 1, 8), + F(2048000, P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX, 15, 1, 4), + F(3072000, P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX, 10, 1, 4), + F(4096000, P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX, 15, 1, 2), + F(6144000, P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX, 10, 1, 2), + F(8192000, P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX, 15, 0, 0), + F(9600000, P_BI_TCXO, 2, 0, 0), + F(12288000, P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX, 10, 0, 0), + F(19200000, P_BI_TCXO, 1, 0, 0), + F(24576000, P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX, 5, 0, 0), + F(49152000, P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX, 2.5, 0, 0), + { } +}; + +static struct clk_rcg2 audio_core_cc_aif_if3_clk_src = { + .cmd_rcgr = 0x1178, + .mnd_width = 16, + .hid_width = 5, + .parent_map = audio_core_cc_parent_map_0, + .freq_tbl = ftbl_audio_core_cc_aif_if3_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "audio_core_cc_aif_if3_clk_src", + .parent_data = audio_core_cc_parent_data_0, + .num_parents = ARRAY_SIZE(audio_core_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_audio_core_cc_aud_dma_clk_src[] = { + F(38400000, P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX2, 8, 0, 0), + F(102400000, P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX2, 3, 0, 0), + F(153600000, P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX2, 2, 0, 0), + F(307200000, P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX2, 1, 0, 0), + { } +}; + +static struct clk_rcg2 audio_core_cc_aud_dma_clk_src = { + .cmd_rcgr = 0x1028, + .mnd_width = 0, + .hid_width = 5, + .parent_map = audio_core_cc_parent_map_0, + .freq_tbl = ftbl_audio_core_cc_aud_dma_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "audio_core_cc_aud_dma_clk_src", + .parent_data = audio_core_cc_parent_data_0, + .num_parents = ARRAY_SIZE(audio_core_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_audio_core_cc_bus_clk_src[] = { + F(38400000, P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX2, 8, 0, 0), + F(76800000, P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX2, 4, 0, 0), + { } +}; + +static struct clk_rcg2 audio_core_cc_bus_clk_src = { + .cmd_rcgr = 0x1008, + .mnd_width = 0, + .hid_width = 5, + .parent_map = audio_core_cc_parent_map_0, + .freq_tbl = ftbl_audio_core_cc_bus_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "audio_core_cc_bus_clk_src", + .parent_data = audio_core_cc_parent_data_0, + .num_parents = ARRAY_SIZE(audio_core_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 audio_core_cc_ext_mclka_clk_src = { + .cmd_rcgr = 0x123c, + .mnd_width = 8, + .hid_width = 5, + .parent_map = audio_core_cc_parent_map_0, + .freq_tbl = ftbl_audio_core_cc_aif_if0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "audio_core_cc_ext_mclka_clk_src", + .parent_data = audio_core_cc_parent_data_0, + .num_parents = ARRAY_SIZE(audio_core_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 audio_core_cc_ext_mclkb_clk_src = { + .cmd_rcgr = 0x125c, + .mnd_width = 8, + .hid_width = 5, + .parent_map = audio_core_cc_parent_map_0, + .freq_tbl = ftbl_audio_core_cc_aif_if0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "audio_core_cc_ext_mclkb_clk_src", + .parent_data = audio_core_cc_parent_data_0, + .num_parents = ARRAY_SIZE(audio_core_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_audio_core_cc_lpaif_pcmoe_clk_src[] = { + F(9600000, P_BI_TCXO, 2, 0, 0), + F(15360000, P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX, 8, 0, 0), + F(30720000, P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX, 4, 0, 0), + F(61440000, P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX, 2, 0, 0), + { } +}; + +static struct clk_rcg2 audio_core_cc_lpaif_pcmoe_clk_src = { + .cmd_rcgr = 0x12ac, + .mnd_width = 8, + .hid_width = 5, + .parent_map = audio_core_cc_parent_map_0, + .freq_tbl = ftbl_audio_core_cc_lpaif_pcmoe_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "audio_core_cc_lpaif_pcmoe_clk_src", + .parent_data = audio_core_cc_parent_data_0, + .num_parents = ARRAY_SIZE(audio_core_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_audio_core_cc_tx_mclk_rcg_clk_src[] = { + F(19200000, P_BI_TCXO, 1, 0, 0), + F(24576000, P_AUDIO_CORE_CC_DIG_PLL_OUT_AUX, 5, 0, 0), + { } +}; + +static struct clk_rcg2 audio_core_cc_tx_mclk_rcg_clk_src = { + .cmd_rcgr = 0x127c, + .mnd_width = 8, + .hid_width = 5, + .parent_map = audio_core_cc_parent_map_0, + .freq_tbl = ftbl_audio_core_cc_tx_mclk_rcg_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "audio_core_cc_tx_mclk_rcg_clk_src", + .parent_data = audio_core_cc_parent_data_0, + .num_parents = ARRAY_SIZE(audio_core_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_regmap_div audio_core_cc_cdiv_tx_mclk_div_clk_src = { + .reg = 0x129c, + .shift = 0, + .width = 4, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "audio_core_cc_cdiv_tx_mclk_div_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &audio_core_cc_tx_mclk_rcg_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_regmap_div_ro_ops, + }, +}; + +static struct clk_branch audio_core_cc_aif_if0_ebit_clk = { + .halt_reg = 0x1068, + .halt_check = BRANCH_HALT_DELAY, + .clkr = { + .enable_reg = 0x1068, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "audio_core_cc_aif_if0_ebit_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch audio_core_cc_aif_if0_ibit_clk = { + .halt_reg = 0x1064, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1064, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "audio_core_cc_aif_if0_ibit_clk", + .parent_hws = (const struct clk_hw*[]) { + &audio_core_cc_aif_if0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch audio_core_cc_aif_if1_ebit_clk = { + .halt_reg = 0x10cc, + .halt_check = BRANCH_HALT_DELAY, + .clkr = { + .enable_reg = 0x10cc, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "audio_core_cc_aif_if1_ebit_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch audio_core_cc_aif_if1_ibit_clk = { + .halt_reg = 0x10c8, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x10c8, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "audio_core_cc_aif_if1_ibit_clk", + .parent_hws = (const struct clk_hw*[]) { + &audio_core_cc_aif_if1_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch audio_core_cc_aif_if2_ebit_clk = { + .halt_reg = 0x1130, + .halt_check = BRANCH_HALT_DELAY, + .clkr = { + .enable_reg = 0x1130, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "audio_core_cc_aif_if2_ebit_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch audio_core_cc_aif_if2_ibit_clk = { + .halt_reg = 0x112c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x112c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "audio_core_cc_aif_if2_ibit_clk", + .parent_hws = (const struct clk_hw*[]) { + &audio_core_cc_aif_if2_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch audio_core_cc_aif_if3_ebit_clk = { + .halt_reg = 0x1194, + .halt_check = BRANCH_HALT_DELAY, + .clkr = { + .enable_reg = 0x1194, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "audio_core_cc_aif_if3_ebit_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch audio_core_cc_aif_if3_ibit_clk = { + .halt_reg = 0x1190, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1190, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "audio_core_cc_aif_if3_ibit_clk", + .parent_hws = (const struct clk_hw*[]) { + &audio_core_cc_aif_if3_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch audio_core_cc_aud_dma_clk = { + .halt_reg = 0x1040, + .halt_check = BRANCH_HALT, + .hwcg_reg = 0x1040, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x1040, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "audio_core_cc_aud_dma_clk", + .parent_hws = (const struct clk_hw*[]) { + &audio_core_cc_aud_dma_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch audio_core_cc_aud_dma_mem_clk = { + .halt_reg = 0x1044, + .halt_check = BRANCH_HALT, + .hwcg_reg = 0x1044, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x1044, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "audio_core_cc_aud_dma_mem_clk", + .parent_hws = (const struct clk_hw*[]) { + &audio_core_cc_aud_dma_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch audio_core_cc_bus_clk = { + .halt_reg = 0x1020, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x1020, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "audio_core_cc_bus_clk", + .parent_hws = (const struct clk_hw*[]) { + &audio_core_cc_bus_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_aon_ops, + }, + }, +}; + +static struct clk_branch audio_core_cc_ext_mclka_out_clk = { + .halt_reg = 0x1254, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1254, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "audio_core_cc_ext_mclka_out_clk", + .parent_hws = (const struct clk_hw*[]) { + &audio_core_cc_ext_mclka_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch audio_core_cc_ext_mclkb_out_clk = { + .halt_reg = 0x1274, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1274, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "audio_core_cc_ext_mclkb_out_clk", + .parent_hws = (const struct clk_hw*[]) { + &audio_core_cc_ext_mclkb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch audio_core_cc_im_sleep_clk = { + .halt_reg = 0x12cc, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x12cc, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "audio_core_cc_im_sleep_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch audio_core_cc_lpaif_pcmoe_clk = { + .halt_reg = 0x12c4, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x12c4, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "audio_core_cc_lpaif_pcmoe_clk", + .parent_hws = (const struct clk_hw*[]) { + &audio_core_cc_lpaif_pcmoe_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch audio_core_cc_rx_mclk_2x_clk = { + .halt_reg = 0x1298, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1298, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "audio_core_cc_rx_mclk_2x_clk", + .parent_hws = (const struct clk_hw*[]) { + &audio_core_cc_tx_mclk_rcg_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch audio_core_cc_rx_mclk_clk = { + .halt_reg = 0x12a4, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x12a4, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "audio_core_cc_rx_mclk_clk", + .parent_hws = (const struct clk_hw*[]) { + &audio_core_cc_cdiv_tx_mclk_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch audio_core_cc_sampling_clk = { + .halt_reg = 0x1000, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1000, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "audio_core_cc_sampling_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch audio_core_cc_tx_mclk_2x_clk = { + .halt_reg = 0x1294, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1294, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "audio_core_cc_tx_mclk_2x_clk", + .parent_hws = (const struct clk_hw*[]) { + &audio_core_cc_tx_mclk_rcg_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch audio_core_cc_tx_mclk_clk = { + .halt_reg = 0x12a0, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x12a0, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "audio_core_cc_tx_mclk_clk", + .parent_hws = (const struct clk_hw*[]) { + &audio_core_cc_cdiv_tx_mclk_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_hw *audio_core_cc_shikra_hws[] = { + [AUDIO_CORE_CC_DIG_PLL_OUT_AUX] = &audio_core_cc_dig_pll_out_aux.hw, + [AUDIO_CORE_CC_DIG_PLL_OUT_AUX2] = &audio_core_cc_dig_pll_out_aux2.hw, +}; + +static struct clk_regmap *audio_core_cc_shikra_clocks[] = { + [AUDIO_CORE_CC_AIF_IF0_CLK_SRC] = &audio_core_cc_aif_if0_clk_src.clkr, + [AUDIO_CORE_CC_AIF_IF0_EBIT_CLK] = &audio_core_cc_aif_if0_ebit_clk.clkr, + [AUDIO_CORE_CC_AIF_IF0_IBIT_CLK] = &audio_core_cc_aif_if0_ibit_clk.clkr, + [AUDIO_CORE_CC_AIF_IF1_CLK_SRC] = &audio_core_cc_aif_if1_clk_src.clkr, + [AUDIO_CORE_CC_AIF_IF1_EBIT_CLK] = &audio_core_cc_aif_if1_ebit_clk.clkr, + [AUDIO_CORE_CC_AIF_IF1_IBIT_CLK] = &audio_core_cc_aif_if1_ibit_clk.clkr, + [AUDIO_CORE_CC_AIF_IF2_CLK_SRC] = &audio_core_cc_aif_if2_clk_src.clkr, + [AUDIO_CORE_CC_AIF_IF2_EBIT_CLK] = &audio_core_cc_aif_if2_ebit_clk.clkr, + [AUDIO_CORE_CC_AIF_IF2_IBIT_CLK] = &audio_core_cc_aif_if2_ibit_clk.clkr, + [AUDIO_CORE_CC_AIF_IF3_CLK_SRC] = &audio_core_cc_aif_if3_clk_src.clkr, + [AUDIO_CORE_CC_AIF_IF3_EBIT_CLK] = &audio_core_cc_aif_if3_ebit_clk.clkr, + [AUDIO_CORE_CC_AIF_IF3_IBIT_CLK] = &audio_core_cc_aif_if3_ibit_clk.clkr, + [AUDIO_CORE_CC_AUD_DMA_CLK] = &audio_core_cc_aud_dma_clk.clkr, + [AUDIO_CORE_CC_AUD_DMA_CLK_SRC] = &audio_core_cc_aud_dma_clk_src.clkr, + [AUDIO_CORE_CC_AUD_DMA_MEM_CLK] = &audio_core_cc_aud_dma_mem_clk.clkr, + [AUDIO_CORE_CC_BUS_CLK] = &audio_core_cc_bus_clk.clkr, + [AUDIO_CORE_CC_BUS_CLK_SRC] = &audio_core_cc_bus_clk_src.clkr, + [AUDIO_CORE_CC_CDIV_TX_MCLK_DIV_CLK_SRC] = &audio_core_cc_cdiv_tx_mclk_div_clk_src.clkr, + [AUDIO_CORE_CC_DIG_PLL] = &audio_core_cc_dig_pll.clkr, + [AUDIO_CORE_CC_EXT_MCLKA_CLK_SRC] = &audio_core_cc_ext_mclka_clk_src.clkr, + [AUDIO_CORE_CC_EXT_MCLKA_OUT_CLK] = &audio_core_cc_ext_mclka_out_clk.clkr, + [AUDIO_CORE_CC_EXT_MCLKB_CLK_SRC] = &audio_core_cc_ext_mclkb_clk_src.clkr, + [AUDIO_CORE_CC_EXT_MCLKB_OUT_CLK] = &audio_core_cc_ext_mclkb_out_clk.clkr, + [AUDIO_CORE_CC_IM_SLEEP_CLK] = &audio_core_cc_im_sleep_clk.clkr, + [AUDIO_CORE_CC_LPAIF_PCMOE_CLK] = &audio_core_cc_lpaif_pcmoe_clk.clkr, + [AUDIO_CORE_CC_LPAIF_PCMOE_CLK_SRC] = &audio_core_cc_lpaif_pcmoe_clk_src.clkr, + [AUDIO_CORE_CC_RX_MCLK_2X_CLK] = &audio_core_cc_rx_mclk_2x_clk.clkr, + [AUDIO_CORE_CC_RX_MCLK_CLK] = &audio_core_cc_rx_mclk_clk.clkr, + [AUDIO_CORE_CC_SAMPLING_CLK] = &audio_core_cc_sampling_clk.clkr, + [AUDIO_CORE_CC_TX_MCLK_2X_CLK] = &audio_core_cc_tx_mclk_2x_clk.clkr, + [AUDIO_CORE_CC_TX_MCLK_CLK] = &audio_core_cc_tx_mclk_clk.clkr, + [AUDIO_CORE_CC_TX_MCLK_RCG_CLK_SRC] = &audio_core_cc_tx_mclk_rcg_clk_src.clkr, +}; + +static struct clk_alpha_pll *audio_core_cc_shikra_plls[] = { + &audio_core_cc_dig_pll, +}; + +static const struct qcom_cc_driver_data audio_core_cc_shikra_driver_data = { + .alpha_plls = audio_core_cc_shikra_plls, + .num_alpha_plls = ARRAY_SIZE(audio_core_cc_shikra_plls), +}; + +static const struct regmap_config audio_core_cc_shikra_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x305c, + .fast_io = true, +}; + +static const struct qcom_reset_map audio_core_csr_shikra_resets[] = { + [AUDIO_CORE_CSR_RX_SWR_CGCR] = { 0x1c, 1 }, + [AUDIO_CORE_CSR_TX_SWR_CGCR] = { 0x30, 1 }, +}; + +static const struct regmap_config audio_core_csr_shikra_regmap_config = { + .name = "audio_core_cc_shikra_reset", + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .fast_io = true, + .max_register = 0x34, +}; + +static const struct qcom_cc_desc audio_core_csr_shikra_desc = { + .config = &audio_core_csr_shikra_regmap_config, + .resets = audio_core_csr_shikra_resets, + .num_resets = ARRAY_SIZE(audio_core_csr_shikra_resets), +}; + +static const struct qcom_cc_desc audio_core_cc_shikra_desc = { + .config = &audio_core_cc_shikra_regmap_config, + .clk_hws = audio_core_cc_shikra_hws, + .num_clk_hws = ARRAY_SIZE(audio_core_cc_shikra_hws), + .clks = audio_core_cc_shikra_clocks, + .num_clks = ARRAY_SIZE(audio_core_cc_shikra_clocks), + .driver_data = &audio_core_cc_shikra_driver_data, +}; + +static const struct of_device_id audio_core_cc_shikra_match_table[] = { + { .compatible = "qcom,shikra-audiocorecc", .data = &audio_core_cc_shikra_desc }, + { .compatible = "qcom,shikra-audiocore-csr", .data = &audio_core_csr_shikra_desc }, + { } +}; +MODULE_DEVICE_TABLE(of, audio_core_cc_shikra_match_table); + +static int audio_core_cc_shikra_probe(struct platform_device *pdev) +{ + const struct qcom_cc_desc *desc; + + desc = device_get_match_data(&pdev->dev); + if (!desc) + return -EINVAL; + + return qcom_cc_probe(pdev, desc); +} + +static struct platform_driver audio_core_cc_shikra_driver = { + .probe = audio_core_cc_shikra_probe, + .driver = { + .name = "audiocorecc-shikra", + .of_match_table = audio_core_cc_shikra_match_table, + }, +}; + +module_platform_driver(audio_core_cc_shikra_driver); + +MODULE_DESCRIPTION("QTI AUDIOCORECC Shikra Driver"); +MODULE_LICENSE("GPL"); From 70ef527b483f6373c4b954e81bbb73f3bdc3184a Mon Sep 17 00:00:00 2001 From: Imran Shaik Date: Sat, 18 Jul 2026 18:26:08 +0530 Subject: [PATCH 0372/1328] dt-bindings: clock: qcom,qcm2290-dispcc: Add missing power-domains property Add the missing power-domains property to associate DISPCC with RPMPD_CX. This is to ensure the genpd performance state votes on the GDSC to get propagated to the CX rail and to avoid the rail under-voltage conditions. This change breaks ABI, as the power-domains property is marked as required. Fixes: 85cedb4e0c9d ("dt-bindings: clock: Add qualcomm QCM2290 DISPCC bindings") Reviewed-by: Krzysztof Kozlowski Signed-off-by: Imran Shaik Link: https://lore.kernel.org/r/20260718-shikra-dispcc-gpucc-v6-1-62703e05ef0f@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- .../devicetree/bindings/clock/qcom,qcm2290-dispcc.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Documentation/devicetree/bindings/clock/qcom,qcm2290-dispcc.yaml b/Documentation/devicetree/bindings/clock/qcom,qcm2290-dispcc.yaml index 4a533b45eec2..e9c2326adfd2 100644 --- a/Documentation/devicetree/bindings/clock/qcom,qcm2290-dispcc.yaml +++ b/Documentation/devicetree/bindings/clock/qcom,qcm2290-dispcc.yaml @@ -37,10 +37,15 @@ properties: - const: dsi0_phy_pll_out_byteclk - const: dsi0_phy_pll_out_dsiclk + power-domains: + items: + - description: CX domain + required: - compatible - clocks - clock-names + - power-domains - '#power-domain-cells' allOf: @@ -53,6 +58,7 @@ examples: #include #include #include + #include clock-controller@5f00000 { compatible = "qcom,qcm2290-dispcc"; reg = <0x5f00000 0x20000>; @@ -68,6 +74,7 @@ examples: "gcc_disp_gpll0_div_clk_src", "dsi0_phy_pll_out_byteclk", "dsi0_phy_pll_out_dsiclk"; + power-domains = <&rpmpd RPMPD_VDDCX>; #clock-cells = <1>; #reset-cells = <1>; #power-domain-cells = <1>; From b4ae34a7e6618618666e0df325d59a634efe5753 Mon Sep 17 00:00:00 2001 From: Imran Shaik Date: Sat, 18 Jul 2026 18:26:09 +0530 Subject: [PATCH 0373/1328] clk: qcom: gcc-qcm2290: Keep the critical clocks always-on from probe Some GCC branch clocks are required to be kept always-on due to the hardware requirements. Drop the modelling of those always-on QCM2290 GCC clocks and use the latest .clk_cbcr convention to keep them enabled from probe. Reviewed-by: Konrad Dybcio Reviewed-by: Dmitry Baryshkov Signed-off-by: Imran Shaik Link: https://lore.kernel.org/r/20260718-shikra-dispcc-gpucc-v6-2-62703e05ef0f@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gcc-qcm2290.c | 113 +++++---------------------------- 1 file changed, 15 insertions(+), 98 deletions(-) diff --git a/drivers/clk/qcom/gcc-qcm2290.c b/drivers/clk/qcom/gcc-qcm2290.c index 77cff0e9af93..2532c0b36bc5 100644 --- a/drivers/clk/qcom/gcc-qcm2290.c +++ b/drivers/clk/qcom/gcc-qcm2290.c @@ -1397,36 +1397,6 @@ static struct clk_branch gcc_cam_throttle_rt_clk = { }, }; -static struct clk_branch gcc_camera_ahb_clk = { - .halt_reg = 0x17008, - .halt_check = BRANCH_HALT_DELAY, - .hwcg_reg = 0x17008, - .hwcg_bit = 1, - .clkr = { - .enable_reg = 0x17008, - .enable_mask = BIT(0), - .hw.init = &(struct clk_init_data){ - .name = "gcc_camera_ahb_clk", - .flags = CLK_IS_CRITICAL, - .ops = &clk_branch2_ops, - }, - }, -}; - -static struct clk_branch gcc_camera_xo_clk = { - .halt_reg = 0x17028, - .halt_check = BRANCH_HALT, - .clkr = { - .enable_reg = 0x17028, - .enable_mask = BIT(0), - .hw.init = &(struct clk_init_data){ - .name = "gcc_camera_xo_clk", - .flags = CLK_IS_CRITICAL, - .ops = &clk_branch2_ops, - }, - }, -}; - static struct clk_branch gcc_camss_axi_clk = { .halt_reg = 0x58044, .halt_check = BRANCH_HALT, @@ -1825,22 +1795,6 @@ static struct clk_branch gcc_cfg_noc_usb3_prim_axi_clk = { }, }; -static struct clk_branch gcc_disp_ahb_clk = { - .halt_reg = 0x1700c, - .halt_check = BRANCH_HALT, - .hwcg_reg = 0x1700c, - .hwcg_bit = 1, - .clkr = { - .enable_reg = 0x1700c, - .enable_mask = BIT(0), - .hw.init = &(struct clk_init_data){ - .name = "gcc_disp_ahb_clk", - .flags = CLK_IS_CRITICAL, - .ops = &clk_branch2_ops, - }, - }, -}; - static struct clk_regmap_div gcc_disp_gpll0_clk_src = { .reg = 0x17058, .shift = 0, @@ -1899,20 +1853,6 @@ static struct clk_branch gcc_disp_throttle_core_clk = { }, }; -static struct clk_branch gcc_disp_xo_clk = { - .halt_reg = 0x1702c, - .halt_check = BRANCH_HALT, - .clkr = { - .enable_reg = 0x1702c, - .enable_mask = BIT(0), - .hw.init = &(struct clk_init_data){ - .name = "gcc_disp_xo_clk", - .flags = CLK_IS_CRITICAL, - .ops = &clk_branch2_ops, - }, - }, -}; - static struct clk_branch gcc_gp1_clk = { .halt_reg = 0x4d000, .halt_check = BRANCH_HALT, @@ -1964,22 +1904,6 @@ static struct clk_branch gcc_gp3_clk = { }, }; -static struct clk_branch gcc_gpu_cfg_ahb_clk = { - .halt_reg = 0x36004, - .halt_check = BRANCH_HALT, - .hwcg_reg = 0x36004, - .hwcg_bit = 1, - .clkr = { - .enable_reg = 0x36004, - .enable_mask = BIT(0), - .hw.init = &(struct clk_init_data){ - .name = "gcc_gpu_cfg_ahb_clk", - .flags = CLK_IS_CRITICAL, - .ops = &clk_branch2_ops, - }, - }, -}; - static struct clk_branch gcc_gpu_gpll0_clk_src = { .halt_check = BRANCH_HALT_DELAY, .clkr = { @@ -2439,22 +2363,6 @@ static struct clk_branch gcc_sdcc2_apps_clk = { }, }; -static struct clk_branch gcc_sys_noc_cpuss_ahb_clk = { - .halt_reg = 0x2b06c, - .halt_check = BRANCH_HALT_VOTED, - .hwcg_reg = 0x2b06c, - .hwcg_bit = 1, - .clkr = { - .enable_reg = 0x79004, - .enable_mask = BIT(0), - .hw.init = &(struct clk_init_data){ - .name = "gcc_sys_noc_cpuss_ahb_clk", - .flags = CLK_IS_CRITICAL, - .ops = &clk_branch2_ops, - }, - }, -}; - static struct clk_branch gcc_sys_noc_usb3_prim_axi_clk = { .halt_reg = 0x1a080, .halt_check = BRANCH_HALT, @@ -2775,8 +2683,6 @@ static struct clk_regmap *gcc_qcm2290_clocks[] = { [GCC_BOOT_ROM_AHB_CLK] = &gcc_boot_rom_ahb_clk.clkr, [GCC_CAM_THROTTLE_NRT_CLK] = &gcc_cam_throttle_nrt_clk.clkr, [GCC_CAM_THROTTLE_RT_CLK] = &gcc_cam_throttle_rt_clk.clkr, - [GCC_CAMERA_AHB_CLK] = &gcc_camera_ahb_clk.clkr, - [GCC_CAMERA_XO_CLK] = &gcc_camera_xo_clk.clkr, [GCC_CAMSS_AXI_CLK] = &gcc_camss_axi_clk.clkr, [GCC_CAMSS_AXI_CLK_SRC] = &gcc_camss_axi_clk_src.clkr, [GCC_CAMSS_CAMNOC_ATB_CLK] = &gcc_camss_camnoc_atb_clk.clkr, @@ -2817,19 +2723,16 @@ static struct clk_regmap *gcc_qcm2290_clocks[] = { [GCC_CAMSS_TOP_AHB_CLK] = &gcc_camss_top_ahb_clk.clkr, [GCC_CAMSS_TOP_AHB_CLK_SRC] = &gcc_camss_top_ahb_clk_src.clkr, [GCC_CFG_NOC_USB3_PRIM_AXI_CLK] = &gcc_cfg_noc_usb3_prim_axi_clk.clkr, - [GCC_DISP_AHB_CLK] = &gcc_disp_ahb_clk.clkr, [GCC_DISP_GPLL0_CLK_SRC] = &gcc_disp_gpll0_clk_src.clkr, [GCC_DISP_GPLL0_DIV_CLK_SRC] = &gcc_disp_gpll0_div_clk_src.clkr, [GCC_DISP_HF_AXI_CLK] = &gcc_disp_hf_axi_clk.clkr, [GCC_DISP_THROTTLE_CORE_CLK] = &gcc_disp_throttle_core_clk.clkr, - [GCC_DISP_XO_CLK] = &gcc_disp_xo_clk.clkr, [GCC_GP1_CLK] = &gcc_gp1_clk.clkr, [GCC_GP1_CLK_SRC] = &gcc_gp1_clk_src.clkr, [GCC_GP2_CLK] = &gcc_gp2_clk.clkr, [GCC_GP2_CLK_SRC] = &gcc_gp2_clk_src.clkr, [GCC_GP3_CLK] = &gcc_gp3_clk.clkr, [GCC_GP3_CLK_SRC] = &gcc_gp3_clk_src.clkr, - [GCC_GPU_CFG_AHB_CLK] = &gcc_gpu_cfg_ahb_clk.clkr, [GCC_GPU_GPLL0_CLK_SRC] = &gcc_gpu_gpll0_clk_src.clkr, [GCC_GPU_GPLL0_DIV_CLK_SRC] = &gcc_gpu_gpll0_div_clk_src.clkr, [GCC_GPU_IREF_CLK] = &gcc_gpu_iref_clk.clkr, @@ -2870,7 +2773,6 @@ static struct clk_regmap *gcc_qcm2290_clocks[] = { [GCC_SDCC2_AHB_CLK] = &gcc_sdcc2_ahb_clk.clkr, [GCC_SDCC2_APPS_CLK] = &gcc_sdcc2_apps_clk.clkr, [GCC_SDCC2_APPS_CLK_SRC] = &gcc_sdcc2_apps_clk_src.clkr, - [GCC_SYS_NOC_CPUSS_AHB_CLK] = &gcc_sys_noc_cpuss_ahb_clk.clkr, [GCC_SYS_NOC_USB3_PRIM_AXI_CLK] = &gcc_sys_noc_usb3_prim_axi_clk.clkr, [GCC_USB30_PRIM_MASTER_CLK] = &gcc_usb30_prim_master_clk.clkr, [GCC_USB30_PRIM_MASTER_CLK_SRC] = &gcc_usb30_prim_master_clk_src.clkr, @@ -2943,6 +2845,15 @@ static struct gdsc *gcc_qcm2290_gdscs[] = { [HLOS1_VOTE_MM_SNOC_MMU_TBU_NRT_GDSC] = &hlos1_vote_mm_snoc_mmu_tbu_nrt_gdsc, }; +static const u32 gcc_qcm2290_critical_cbcrs[] = { + 0x17008, /* GCC_CAMERA_AHB_CLK */ + 0x17028, /* GCC_CAMERA_XO_CLK */ + 0x1700c, /* GCC_DISP_AHB_CLK */ + 0x1702c, /* GCC_DISP_XO_CLK */ + 0x36004, /* GCC_GPU_CFG_AHB_CLK */ + 0x79004, /* GCC_SYS_NOC_CPUSS_AHB_CLK */ +}; + static const struct clk_rcg_dfs_data gcc_dfs_clocks[] = { DEFINE_RCG_DFS(gcc_qupv3_wrap0_s0_clk_src), DEFINE_RCG_DFS(gcc_qupv3_wrap0_s1_clk_src), @@ -2960,6 +2871,11 @@ static const struct regmap_config gcc_qcm2290_regmap_config = { .fast_io = true, }; +static const struct qcom_cc_driver_data gcc_qcm2290_driver_data = { + .clk_cbcrs = gcc_qcm2290_critical_cbcrs, + .num_clk_cbcrs = ARRAY_SIZE(gcc_qcm2290_critical_cbcrs), +}; + static const struct qcom_cc_desc gcc_qcm2290_desc = { .config = &gcc_qcm2290_regmap_config, .clks = gcc_qcm2290_clocks, @@ -2968,6 +2884,7 @@ static const struct qcom_cc_desc gcc_qcm2290_desc = { .num_resets = ARRAY_SIZE(gcc_qcm2290_resets), .gdscs = gcc_qcm2290_gdscs, .num_gdscs = ARRAY_SIZE(gcc_qcm2290_gdscs), + .driver_data = &gcc_qcm2290_driver_data, }; static const struct of_device_id gcc_qcm2290_match_table[] = { From b8b5b26aa10e57c154ceb7d32d573afb864823aa Mon Sep 17 00:00:00 2001 From: Imran Shaik Date: Sat, 18 Jul 2026 18:26:10 +0530 Subject: [PATCH 0374/1328] clk: qcom: dispcc-qcm2290: Move to the latest common qcom_cc_probe() model Update the QCM2290 DISPCC driver to use the qcom_cc_probe() model by moving the critical clocks handling and PLL configurations from probe to the driver_data to align with the latest convention. Reviewed-by: Konrad Dybcio Reviewed-by: Dmitry Baryshkov Signed-off-by: Imran Shaik Link: https://lore.kernel.org/r/20260718-shikra-dispcc-gpucc-v6-3-62703e05ef0f@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/dispcc-qcm2290.c | 38 +++++++++++++++---------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/drivers/clk/qcom/dispcc-qcm2290.c b/drivers/clk/qcom/dispcc-qcm2290.c index 6d88d067337f..1c21267ae0f7 100644 --- a/drivers/clk/qcom/dispcc-qcm2290.c +++ b/drivers/clk/qcom/dispcc-qcm2290.c @@ -2,6 +2,7 @@ /* * Copyright (c) 2020, The Linux Foundation. All rights reserved. * Copyright (c) 2021, Linaro Ltd. + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ #include @@ -49,6 +50,7 @@ static const struct alpha_pll_config disp_cc_pll0_config = { static struct clk_alpha_pll disp_cc_pll0 = { .offset = 0x0, + .config = &disp_cc_pll0_config, .vco_table = spark_vco, .num_vco = ARRAY_SIZE(spark_vco), .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_DEFAULT], @@ -483,6 +485,14 @@ static struct clk_regmap *disp_cc_qcm2290_clocks[] = { [DISP_CC_SLEEP_CLK_SRC] = &disp_cc_sleep_clk_src.clkr, }; +static struct clk_alpha_pll *disp_cc_qcm2290_plls[] = { + &disp_cc_pll0, +}; + +static const u32 disp_cc_qcm2290_critical_cbcrs[] = { + 0x604c, /* DISP_CC_XO_CLK */ +}; + static const struct regmap_config disp_cc_qcm2290_regmap_config = { .reg_bits = 32, .reg_stride = 4, @@ -491,6 +501,13 @@ static const struct regmap_config disp_cc_qcm2290_regmap_config = { .fast_io = true, }; +static const struct qcom_cc_driver_data disp_cc_qcm2290_driver_data = { + .alpha_plls = disp_cc_qcm2290_plls, + .num_alpha_plls = ARRAY_SIZE(disp_cc_qcm2290_plls), + .clk_cbcrs = disp_cc_qcm2290_critical_cbcrs, + .num_clk_cbcrs = ARRAY_SIZE(disp_cc_qcm2290_critical_cbcrs), +}; + static const struct qcom_cc_desc disp_cc_qcm2290_desc = { .config = &disp_cc_qcm2290_regmap_config, .clks = disp_cc_qcm2290_clocks, @@ -499,6 +516,7 @@ static const struct qcom_cc_desc disp_cc_qcm2290_desc = { .num_gdscs = ARRAY_SIZE(disp_cc_qcm2290_gdscs), .resets = disp_cc_qcm2290_resets, .num_resets = ARRAY_SIZE(disp_cc_qcm2290_resets), + .driver_data = &disp_cc_qcm2290_driver_data, }; static const struct of_device_id disp_cc_qcm2290_match_table[] = { @@ -509,25 +527,7 @@ MODULE_DEVICE_TABLE(of, disp_cc_qcm2290_match_table); static int disp_cc_qcm2290_probe(struct platform_device *pdev) { - struct regmap *regmap; - int ret; - - regmap = qcom_cc_map(pdev, &disp_cc_qcm2290_desc); - if (IS_ERR(regmap)) - return PTR_ERR(regmap); - - clk_alpha_pll_configure(&disp_cc_pll0, regmap, &disp_cc_pll0_config); - - /* Keep some clocks always-on */ - qcom_branch_set_clk_en(regmap, 0x604c); /* DISP_CC_XO_CLK */ - - ret = qcom_cc_really_probe(&pdev->dev, &disp_cc_qcm2290_desc, regmap); - if (ret) { - dev_err(&pdev->dev, "Failed to register DISP CC clocks\n"); - return ret; - } - - return ret; + return qcom_cc_probe(pdev, &disp_cc_qcm2290_desc); } static struct platform_driver disp_cc_qcm2290_driver = { From 11345cbf17cacc3828f7bcb021ecc07c687d04f2 Mon Sep 17 00:00:00 2001 From: Imran Shaik Date: Sat, 18 Jul 2026 18:26:11 +0530 Subject: [PATCH 0375/1328] clk: qcom: dispcc-qcm2290: Enable runtime PM support The QCM2290 DISPCC is now associated with a power domain (RPMPD_CX) to propagate genpd performance state votes to the CX rail. Set use_rpm to true so that a runtime PM reference is acquired and released around probe, instead of leaving a permanent 'enable' vote on the power domain. Fixes: cc517ea3333f ("clk: qcom: Add display clock controller driver for QCM2290") Signed-off-by: Imran Shaik Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260718-shikra-dispcc-gpucc-v6-4-62703e05ef0f@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/dispcc-qcm2290.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/clk/qcom/dispcc-qcm2290.c b/drivers/clk/qcom/dispcc-qcm2290.c index 1c21267ae0f7..3b6f537f03ba 100644 --- a/drivers/clk/qcom/dispcc-qcm2290.c +++ b/drivers/clk/qcom/dispcc-qcm2290.c @@ -516,6 +516,7 @@ static const struct qcom_cc_desc disp_cc_qcm2290_desc = { .num_gdscs = ARRAY_SIZE(disp_cc_qcm2290_gdscs), .resets = disp_cc_qcm2290_resets, .num_resets = ARRAY_SIZE(disp_cc_qcm2290_resets), + .use_rpm = true, .driver_data = &disp_cc_qcm2290_driver_data, }; From 6b0044bc75b04374c58d082d4cd59f57db83e8dc Mon Sep 17 00:00:00 2001 From: Imran Shaik Date: Sat, 18 Jul 2026 18:26:12 +0530 Subject: [PATCH 0376/1328] clk: qcom: qcm2290: Set POLL_CFG_GDSCR flag for DISPCC and GPUCC GDSCs The Qualcomm QCM2290 SoC GDSCR status bit may not reflect the actual state of the GDSC, instead the power on/off bits in CFG_GDSCR must be polled to determine the GDSC state correctly. Set POLL_CFG_GDSCR flag for the QCM2290 MDSS GDSC and GPUCC GX GDSC to ensure the correct GDSC status. This is not applicable for GPUCC CX GDSC, which relies on gds_hw_ctrl status. Fixes: cc517ea3333f ("clk: qcom: Add display clock controller driver for QCM2290") Fixes: 8cab033628b1 ("clk: qcom: Add QCM2290 GPU clock controller driver") Reviewed-by: Konrad Dybcio Signed-off-by: Imran Shaik Link: https://lore.kernel.org/r/20260718-shikra-dispcc-gpucc-v6-5-62703e05ef0f@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/dispcc-qcm2290.c | 2 +- drivers/clk/qcom/gpucc-qcm2290.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/clk/qcom/dispcc-qcm2290.c b/drivers/clk/qcom/dispcc-qcm2290.c index 3b6f537f03ba..80dc9b4083af 100644 --- a/drivers/clk/qcom/dispcc-qcm2290.c +++ b/drivers/clk/qcom/dispcc-qcm2290.c @@ -456,7 +456,7 @@ static struct gdsc mdss_gdsc = { .name = "mdss_gdsc", }, .pwrsts = PWRSTS_OFF_ON, - .flags = HW_CTRL, + .flags = HW_CTRL | POLL_CFG_GDSCR, }; static struct gdsc *disp_cc_qcm2290_gdscs[] = { diff --git a/drivers/clk/qcom/gpucc-qcm2290.c b/drivers/clk/qcom/gpucc-qcm2290.c index dc369dff882e..1611360d122a 100644 --- a/drivers/clk/qcom/gpucc-qcm2290.c +++ b/drivers/clk/qcom/gpucc-qcm2290.c @@ -314,7 +314,7 @@ static struct gdsc gpu_gx_gdsc = { }, .parent = &gpu_cx_gdsc.pd, .pwrsts = PWRSTS_OFF_ON, - .flags = CLAMP_IO | AON_RESET | SW_RESET, + .flags = POLL_CFG_GDSCR | CLAMP_IO | AON_RESET | SW_RESET, }; static struct clk_regmap *gpu_cc_qcm2290_clocks[] = { From 899dfd90229e3d83aa85a27b3d5d1d78bdb7385d Mon Sep 17 00:00:00 2001 From: Imran Shaik Date: Sat, 18 Jul 2026 18:26:13 +0530 Subject: [PATCH 0377/1328] clk: qcom: qcm2290: Add RETAIN_FF_ENABLE flag for DISPCC and GPUCC GDSCs Add RETAIN_FF_ENABLE flag for DISPCC and GPUCC GDSCs on QCM2290 to retain the register context across GDSC power collapse. Fixes: cc517ea3333f ("clk: qcom: Add display clock controller driver for QCM2290") Fixes: 8cab033628b1 ("clk: qcom: Add QCM2290 GPU clock controller driver") Reviewed-by: Konrad Dybcio Signed-off-by: Imran Shaik Link: https://lore.kernel.org/r/20260718-shikra-dispcc-gpucc-v6-6-62703e05ef0f@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/dispcc-qcm2290.c | 2 +- drivers/clk/qcom/gpucc-qcm2290.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/clk/qcom/dispcc-qcm2290.c b/drivers/clk/qcom/dispcc-qcm2290.c index 80dc9b4083af..064dc0122c9e 100644 --- a/drivers/clk/qcom/dispcc-qcm2290.c +++ b/drivers/clk/qcom/dispcc-qcm2290.c @@ -456,7 +456,7 @@ static struct gdsc mdss_gdsc = { .name = "mdss_gdsc", }, .pwrsts = PWRSTS_OFF_ON, - .flags = HW_CTRL | POLL_CFG_GDSCR, + .flags = HW_CTRL | POLL_CFG_GDSCR | RETAIN_FF_ENABLE, }; static struct gdsc *disp_cc_qcm2290_gdscs[] = { diff --git a/drivers/clk/qcom/gpucc-qcm2290.c b/drivers/clk/qcom/gpucc-qcm2290.c index 1611360d122a..563a551a269c 100644 --- a/drivers/clk/qcom/gpucc-qcm2290.c +++ b/drivers/clk/qcom/gpucc-qcm2290.c @@ -301,7 +301,7 @@ static struct gdsc gpu_cx_gdsc = { .name = "gpu_cx_gdsc", }, .pwrsts = PWRSTS_OFF_ON, - .flags = VOTABLE, + .flags = RETAIN_FF_ENABLE | VOTABLE, }; static struct gdsc gpu_gx_gdsc = { @@ -314,7 +314,7 @@ static struct gdsc gpu_gx_gdsc = { }, .parent = &gpu_cx_gdsc.pd, .pwrsts = PWRSTS_OFF_ON, - .flags = POLL_CFG_GDSCR | CLAMP_IO | AON_RESET | SW_RESET, + .flags = RETAIN_FF_ENABLE | POLL_CFG_GDSCR | CLAMP_IO | AON_RESET | SW_RESET, }; static struct clk_regmap *gpu_cc_qcm2290_clocks[] = { From 08cfbfd81dd6e27167b1331e0c485bd0414cf5fe Mon Sep 17 00:00:00 2001 From: Imran Shaik Date: Sat, 18 Jul 2026 18:26:14 +0530 Subject: [PATCH 0378/1328] clk: qcom: qcm2290: Update DISPCC and GPUCC GDSC *wait_val values Update the QCM2290 DISPCC and GPUCC GDSC wait_val fields to match the hardware default values. Incorrect settings can cause the GDSC FSM to stuck, leading to power on/off failures. Fixes: cc517ea3333f ("clk: qcom: Add display clock controller driver for QCM2290") Fixes: 8cab033628b1 ("clk: qcom: Add QCM2290 GPU clock controller driver") Reviewed-by: Dmitry Baryshkov Reviewed-by: Konrad Dybcio Signed-off-by: Imran Shaik Link: https://lore.kernel.org/r/20260718-shikra-dispcc-gpucc-v6-7-62703e05ef0f@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/dispcc-qcm2290.c | 3 +++ drivers/clk/qcom/gpucc-qcm2290.c | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/drivers/clk/qcom/dispcc-qcm2290.c b/drivers/clk/qcom/dispcc-qcm2290.c index 064dc0122c9e..d9441a98ba1b 100644 --- a/drivers/clk/qcom/dispcc-qcm2290.c +++ b/drivers/clk/qcom/dispcc-qcm2290.c @@ -452,6 +452,9 @@ static const struct qcom_reset_map disp_cc_qcm2290_resets[] = { static struct gdsc mdss_gdsc = { .gdscr = 0x3000, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0xf, .pd = { .name = "mdss_gdsc", }, diff --git a/drivers/clk/qcom/gpucc-qcm2290.c b/drivers/clk/qcom/gpucc-qcm2290.c index 563a551a269c..1f658bd59bda 100644 --- a/drivers/clk/qcom/gpucc-qcm2290.c +++ b/drivers/clk/qcom/gpucc-qcm2290.c @@ -297,6 +297,9 @@ static struct clk_branch gpu_cc_hlos1_vote_gpu_smmu_clk = { static struct gdsc gpu_cx_gdsc = { .gdscr = 0x106c, .gds_hw_ctrl = 0x1540, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0x2, .pd = { .name = "gpu_cx_gdsc", }, @@ -309,6 +312,9 @@ static struct gdsc gpu_gx_gdsc = { .clamp_io_ctrl = 0x1508, .resets = (unsigned int []){ GPU_GX_BCR }, .reset_count = 1, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0x2, .pd = { .name = "gpu_gx_gdsc", }, From 1b2e1f5a37dd4c856a9269bc7e5988c642db5847 Mon Sep 17 00:00:00 2001 From: Imran Shaik Date: Sat, 18 Jul 2026 18:26:15 +0530 Subject: [PATCH 0379/1328] clk: qcom: gpucc-qcm2290: Drop pm_clk handling Drop the pm_clk handling from QCM2290 GPUCC driver as the required GCC AHB clocks are kept always enabled by the GCC driver during probe. Reviewed-by: Dmitry Baryshkov Reviewed-by: Konrad Dybcio Signed-off-by: Imran Shaik Link: https://lore.kernel.org/r/20260718-shikra-dispcc-gpucc-v6-8-62703e05ef0f@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gpucc-qcm2290.c | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/drivers/clk/qcom/gpucc-qcm2290.c b/drivers/clk/qcom/gpucc-qcm2290.c index 1f658bd59bda..5e38dad86ba1 100644 --- a/drivers/clk/qcom/gpucc-qcm2290.c +++ b/drivers/clk/qcom/gpucc-qcm2290.c @@ -8,7 +8,6 @@ #include #include #include -#include #include #include @@ -386,16 +385,6 @@ static int gpu_cc_qcm2290_probe(struct platform_device *pdev) if (ret) return ret; - ret = devm_pm_clk_create(&pdev->dev); - if (ret) - return ret; - - ret = pm_clk_add(&pdev->dev, NULL); - if (ret < 0) { - dev_err(&pdev->dev, "failed to acquire ahb clock\n"); - return ret; - } - ret = pm_runtime_resume_and_get(&pdev->dev); if (ret) return ret; From 1b4599bc20e98d61c274b3449ca392b8e2b07b4a Mon Sep 17 00:00:00 2001 From: Imran Shaik Date: Sat, 18 Jul 2026 18:26:16 +0530 Subject: [PATCH 0380/1328] clk: qcom: gpucc-qcm2290: Move to the latest common qcom_cc_probe() model Update the QCM2290 GPUCC driver to use the qcom_cc_probe() model by moving the critical clocks handling and PLL configurations from probe to the driver_data to align with the latest convention. Reviewed-by: Dmitry Baryshkov Reviewed-by: Konrad Dybcio Signed-off-by: Imran Shaik Link: https://lore.kernel.org/r/20260718-shikra-dispcc-gpucc-v6-9-62703e05ef0f@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gpucc-qcm2290.c | 50 +++++++++++++------------------- 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/drivers/clk/qcom/gpucc-qcm2290.c b/drivers/clk/qcom/gpucc-qcm2290.c index 5e38dad86ba1..2b3076f2977a 100644 --- a/drivers/clk/qcom/gpucc-qcm2290.c +++ b/drivers/clk/qcom/gpucc-qcm2290.c @@ -2,13 +2,13 @@ /* * Copyright (c) 2020, The Linux Foundation. All rights reserved. * Copyright (c) 2024, Linaro Limited + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ #include #include #include #include -#include #include #include @@ -20,6 +20,7 @@ #include "clk-regmap-divider.h" #include "clk-regmap-mux.h" #include "clk-regmap-phy-mux.h" +#include "common.h" #include "gdsc.h" #include "reset.h" @@ -56,6 +57,7 @@ static const struct alpha_pll_config gpu_cc_pll0_config = { static struct clk_alpha_pll gpu_cc_pll0 = { .offset = 0x0, + .config = &gpu_cc_pll0_config, .vco_table = huayra_vco, .num_vco = ARRAY_SIZE(huayra_vco), .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_HUAYRA_2290], @@ -347,6 +349,14 @@ static struct gdsc *gpu_cc_qcm2290_gdscs[] = { [GPU_GX_GDSC] = &gpu_gx_gdsc, }; +static struct clk_alpha_pll *gpu_cc_qcm2290_plls[] = { + &gpu_cc_pll0, +}; + +static const u32 gpu_cc_qcm2290_critical_cbcrs[] = { + 0x1060, /* GPU_CC_GX_CXO_CLK */ +}; + static const struct regmap_config gpu_cc_qcm2290_regmap_config = { .reg_bits = 32, .reg_stride = 4, @@ -355,6 +365,12 @@ static const struct regmap_config gpu_cc_qcm2290_regmap_config = { .fast_io = true, }; +static const struct qcom_cc_driver_data gpu_cc_qcm2290_driver_data = { + .alpha_plls = gpu_cc_qcm2290_plls, + .num_alpha_plls = ARRAY_SIZE(gpu_cc_qcm2290_plls), + .clk_cbcrs = gpu_cc_qcm2290_critical_cbcrs, + .num_clk_cbcrs = ARRAY_SIZE(gpu_cc_qcm2290_critical_cbcrs), +}; static const struct qcom_cc_desc gpu_cc_qcm2290_desc = { .config = &gpu_cc_qcm2290_regmap_config, @@ -364,6 +380,8 @@ static const struct qcom_cc_desc gpu_cc_qcm2290_desc = { .num_resets = ARRAY_SIZE(gpu_cc_qcm2290_resets), .gdscs = gpu_cc_qcm2290_gdscs, .num_gdscs = ARRAY_SIZE(gpu_cc_qcm2290_gdscs), + .use_rpm = true, + .driver_data = &gpu_cc_qcm2290_driver_data, }; static const struct of_device_id gpu_cc_qcm2290_match_table[] = { @@ -374,35 +392,7 @@ MODULE_DEVICE_TABLE(of, gpu_cc_qcm2290_match_table); static int gpu_cc_qcm2290_probe(struct platform_device *pdev) { - struct regmap *regmap; - int ret; - - regmap = qcom_cc_map(pdev, &gpu_cc_qcm2290_desc); - if (IS_ERR(regmap)) - return PTR_ERR(regmap); - - ret = devm_pm_runtime_enable(&pdev->dev); - if (ret) - return ret; - - ret = pm_runtime_resume_and_get(&pdev->dev); - if (ret) - return ret; - - clk_huayra_2290_pll_configure(&gpu_cc_pll0, regmap, &gpu_cc_pll0_config); - - regmap_update_bits(regmap, 0x1060, BIT(0), BIT(0)); /* GPU_CC_GX_CXO_CLK */ - - ret = qcom_cc_really_probe(&pdev->dev, &gpu_cc_qcm2290_desc, regmap); - if (ret) { - dev_err(&pdev->dev, "Failed to register display clock controller\n"); - goto out_pm_runtime_put; - } - -out_pm_runtime_put: - pm_runtime_put_sync(&pdev->dev); - - return 0; + return qcom_cc_probe(pdev, &gpu_cc_qcm2290_desc); } static struct platform_driver gpu_cc_qcm2290_driver = { From 7274ea68d2b4be491de0ece45fdce709cfde3154 Mon Sep 17 00:00:00 2001 From: Imran Shaik Date: Sat, 18 Jul 2026 18:26:17 +0530 Subject: [PATCH 0381/1328] clk: qcom: gpucc-qcm2290: Keep the critical clocks always-on from probe Drop modelling of gpu_cc_ahb_clk and keep it always enabled from probe similar to other critical clocks, since marking it as CLK_IS_CRITICAL causes the clock framework to invoke clk_pm_runtime_get() during prepare, which prevents the associated power domains from collapsing. Fixes: 8cab033628b1 ("clk: qcom: Add QCM2290 GPU clock controller driver") Reviewed-by: Dmitry Baryshkov Reviewed-by: Konrad Dybcio Signed-off-by: Imran Shaik Link: https://lore.kernel.org/r/20260718-shikra-dispcc-gpucc-v6-10-62703e05ef0f@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gpucc-qcm2290.c | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/drivers/clk/qcom/gpucc-qcm2290.c b/drivers/clk/qcom/gpucc-qcm2290.c index 2b3076f2977a..1c2349ee8353 100644 --- a/drivers/clk/qcom/gpucc-qcm2290.c +++ b/drivers/clk/qcom/gpucc-qcm2290.c @@ -149,20 +149,6 @@ static struct clk_rcg2 gpu_cc_gx_gfx3d_clk_src = { }, }; -static struct clk_branch gpu_cc_ahb_clk = { - .halt_reg = 0x1078, - .halt_check = BRANCH_HALT_DELAY, - .clkr = { - .enable_reg = 0x1078, - .enable_mask = BIT(0), - .hw.init = &(struct clk_init_data){ - .name = "gpu_cc_ahb_clk", - .flags = CLK_IS_CRITICAL, - .ops = &clk_branch2_ops, - }, - }, -}; - static struct clk_branch gpu_cc_crc_ahb_clk = { .halt_reg = 0x107c, .halt_check = BRANCH_HALT_DELAY, @@ -325,7 +311,6 @@ static struct gdsc gpu_gx_gdsc = { }; static struct clk_regmap *gpu_cc_qcm2290_clocks[] = { - [GPU_CC_AHB_CLK] = &gpu_cc_ahb_clk.clkr, [GPU_CC_CRC_AHB_CLK] = &gpu_cc_crc_ahb_clk.clkr, [GPU_CC_CX_GFX3D_CLK] = &gpu_cc_cx_gfx3d_clk.clkr, [GPU_CC_CX_GMU_CLK] = &gpu_cc_cx_gmu_clk.clkr, @@ -354,6 +339,7 @@ static struct clk_alpha_pll *gpu_cc_qcm2290_plls[] = { }; static const u32 gpu_cc_qcm2290_critical_cbcrs[] = { + 0x1078, /* GPU_CC_AHB_CLK */ 0x1060, /* GPU_CC_GX_CXO_CLK */ }; From ab46b5fb668b8b9b848a8f036fc4c06ce86b7e3b Mon Sep 17 00:00:00 2001 From: Imran Shaik Date: Sat, 18 Jul 2026 18:26:18 +0530 Subject: [PATCH 0382/1328] clk: qcom: gpucc-qcm2290: Park RCG's clk source at XO during disable The RCG's clk src has to be parked at XO while disabling as per hardware team's recommendation, hence use clk_rcg2_shared_ops to achieve the same. Fixes: 8cab033628b1 ("clk: qcom: Add QCM2290 GPU clock controller driver") Reviewed-by: Dmitry Baryshkov Reviewed-by: Konrad Dybcio Signed-off-by: Imran Shaik Link: https://lore.kernel.org/r/20260718-shikra-dispcc-gpucc-v6-11-62703e05ef0f@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gpucc-qcm2290.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/qcom/gpucc-qcm2290.c b/drivers/clk/qcom/gpucc-qcm2290.c index 1c2349ee8353..9b61c066949c 100644 --- a/drivers/clk/qcom/gpucc-qcm2290.c +++ b/drivers/clk/qcom/gpucc-qcm2290.c @@ -145,7 +145,7 @@ static struct clk_rcg2 gpu_cc_gx_gfx3d_clk_src = { .parent_data = gpu_cc_parent_data_1, .num_parents = ARRAY_SIZE(gpu_cc_parent_data_1), .flags = CLK_SET_RATE_PARENT, - .ops = &clk_rcg2_ops, + .ops = &clk_rcg2_shared_ops, }, }; From 99c28d653aac038111afb452151f222b4efec047 Mon Sep 17 00:00:00 2001 From: Imran Shaik Date: Sat, 18 Jul 2026 18:26:20 +0530 Subject: [PATCH 0383/1328] dt-bindings: clock: qcom: Add Qualcomm Shikra Display clock controller The Qualcomm Shikra Display clock controller has clocks same as QCM2290. Hence, add support to use the QCM2290 DISPCC compatible as fallback for Shikra DISPCC. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Imran Shaik Link: https://lore.kernel.org/r/20260718-shikra-dispcc-gpucc-v6-13-62703e05ef0f@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- .../devicetree/bindings/clock/qcom,qcm2290-dispcc.yaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/clock/qcom,qcm2290-dispcc.yaml b/Documentation/devicetree/bindings/clock/qcom,qcm2290-dispcc.yaml index e9c2326adfd2..8d4f53c8a6cb 100644 --- a/Documentation/devicetree/bindings/clock/qcom,qcm2290-dispcc.yaml +++ b/Documentation/devicetree/bindings/clock/qcom,qcm2290-dispcc.yaml @@ -17,7 +17,13 @@ description: | properties: compatible: - const: qcom,qcm2290-dispcc + oneOf: + - items: + - enum: + - qcom,shikra-dispcc + - const: qcom,qcm2290-dispcc + - enum: + - qcom,qcm2290-dispcc clocks: items: From 46d50405c96905043aaa6a9626e1a42b5c56563e Mon Sep 17 00:00:00 2001 From: Imran Shaik Date: Sat, 18 Jul 2026 18:26:21 +0530 Subject: [PATCH 0384/1328] dt-bindings: clock: qcom: Add Qualcomm Shikra GPU clock controller The Qualcomm Shikra GPU clock controller is similar to QCM2290 GPUCC hardware block, with same set of clocks and minor other differences. Hence reuse the QCM2290 header file for Shikra and document the Qualcomm Shikra GPUCC compatible. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Imran Shaik Link: https://lore.kernel.org/r/20260718-shikra-dispcc-gpucc-v6-14-62703e05ef0f@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- .../devicetree/bindings/clock/qcom,qcm2290-gpucc.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/clock/qcom,qcm2290-gpucc.yaml b/Documentation/devicetree/bindings/clock/qcom,qcm2290-gpucc.yaml index 734880805c1b..1bd70d091fcd 100644 --- a/Documentation/devicetree/bindings/clock/qcom,qcm2290-gpucc.yaml +++ b/Documentation/devicetree/bindings/clock/qcom,qcm2290-gpucc.yaml @@ -18,7 +18,9 @@ description: | properties: compatible: - const: qcom,qcm2290-gpucc + enum: + - qcom,qcm2290-gpucc + - qcom,shikra-gpucc reg: maxItems: 1 From 58410321dbdc6e11037158b16f889fdb076774b0 Mon Sep 17 00:00:00 2001 From: Jagadeesh Kona Date: Fri, 17 Jul 2026 09:44:44 +0530 Subject: [PATCH 0385/1328] dt-bindings: clock: qcom: Add Qualcomm Maili video clock controller Add device tree bindings for the video clock controller on Qualcomm Maili SoC. Signed-off-by: Jagadeesh Kona Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260717-maili_videocc-v3-1-6656694b06a7@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- .../bindings/clock/qcom,sm8450-videocc.yaml | 2 + .../dt-bindings/clock/qcom,maili-videocc.h | 50 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 include/dt-bindings/clock/qcom,maili-videocc.h diff --git a/Documentation/devicetree/bindings/clock/qcom,sm8450-videocc.yaml b/Documentation/devicetree/bindings/clock/qcom,sm8450-videocc.yaml index 1e1fec1cd7ce..910c6d5672b8 100644 --- a/Documentation/devicetree/bindings/clock/qcom,sm8450-videocc.yaml +++ b/Documentation/devicetree/bindings/clock/qcom,sm8450-videocc.yaml @@ -18,6 +18,7 @@ description: | include/dt-bindings/clock/qcom,glymur-videocc.h include/dt-bindings/clock/qcom,hawi-videocc.h include/dt-bindings/clock/qcom,kaanapali-videocc.h + include/dt-bindings/clock/qcom,maili-videocc.h include/dt-bindings/clock/qcom,sm8450-videocc.h include/dt-bindings/clock/qcom,sm8650-videocc.h include/dt-bindings/clock/qcom,sm8750-videocc.h @@ -29,6 +30,7 @@ properties: - qcom,glymur-videocc - qcom,hawi-videocc - qcom,kaanapali-videocc + - qcom,maili-videocc - qcom,sm8450-videocc - qcom,sm8475-videocc - qcom,sm8550-videocc diff --git a/include/dt-bindings/clock/qcom,maili-videocc.h b/include/dt-bindings/clock/qcom,maili-videocc.h new file mode 100644 index 000000000000..f8461048b04d --- /dev/null +++ b/include/dt-bindings/clock/qcom,maili-videocc.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef _DT_BINDINGS_CLK_QCOM_VIDEO_CC_MAILI_H +#define _DT_BINDINGS_CLK_QCOM_VIDEO_CC_MAILI_H + +/* VIDEO_CC clocks */ +#define VIDEO_CC_AHB_CLK 0 +#define VIDEO_CC_AHB_CLK_SRC 1 +#define VIDEO_CC_MVS0_CLK 2 +#define VIDEO_CC_MVS0_CLK_SRC 3 +#define VIDEO_CC_MVS0_FREERUN_CLK 4 +#define VIDEO_CC_MVS0_SHIFT_CLK 5 +#define VIDEO_CC_MVS0_VPP0_CLK 6 +#define VIDEO_CC_MVS0_VPP0_FREERUN_CLK 7 +#define VIDEO_CC_MVS0B_CLK 8 +#define VIDEO_CC_MVS0B_CLK_SRC 9 +#define VIDEO_CC_MVS0B_FREERUN_CLK 10 +#define VIDEO_CC_MVS0C_CLK 11 +#define VIDEO_CC_MVS0C_CLK_SRC 12 +#define VIDEO_CC_MVS0C_DEBUG_CLK 13 +#define VIDEO_CC_MVS0C_FREERUN_CLK 14 +#define VIDEO_CC_MVS0C_SHIFT_CLK 15 +#define VIDEO_CC_PLL0 16 +#define VIDEO_CC_PLL1 17 +#define VIDEO_CC_PLL2 18 +#define VIDEO_CC_SLEEP_CLK 19 +#define VIDEO_CC_TS_XO_CLK 20 +#define VIDEO_CC_XO_CLK 21 +#define VIDEO_CC_XO_CLK_SRC 22 + +/* VIDEO_CC power domains */ +#define VIDEO_CC_MVS0_GDSC 0 +#define VIDEO_CC_MVS0_VPP0_GDSC 1 +#define VIDEO_CC_MVS0C_GDSC 2 + +/* VIDEO_CC resets */ +#define VIDEO_CC_INTERFACE_BCR 0 +#define VIDEO_CC_MVS0_BCR 1 +#define VIDEO_CC_MVS0_CLK_ARES 2 +#define VIDEO_CC_MVS0_FREERUN_CLK_ARES 3 +#define VIDEO_CC_MVS0_VPP0_BCR 4 +#define VIDEO_CC_MVS0C_BCR 5 +#define VIDEO_CC_MVS0C_CLK_ARES 6 +#define VIDEO_CC_MVS0C_FREERUN_CLK_ARES 7 +#define VIDEO_CC_XO_CLK_ARES 8 + +#endif From 778149217dc01becdca9df2b004ba1b6dd939e27 Mon Sep 17 00:00:00 2001 From: Jagadeesh Kona Date: Fri, 17 Jul 2026 09:44:45 +0530 Subject: [PATCH 0386/1328] clk: qcom: Add support for videocc driver on Qualcomm Maili SoC Add support for Qualcomm Maili video clock controller driver for video clients to be able to request for videocc clocks. Reviewed-by: Konrad Dybcio Reviewed-by: Taniya Das Signed-off-by: Jagadeesh Kona Link: https://lore.kernel.org/r/20260717-maili_videocc-v3-2-6656694b06a7@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/Kconfig | 10 + drivers/clk/qcom/Makefile | 1 + drivers/clk/qcom/videocc-maili.c | 660 +++++++++++++++++++++++++++++++ 3 files changed, 671 insertions(+) create mode 100644 drivers/clk/qcom/videocc-maili.c diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig index 2187270e373f..d1fc9bf87126 100644 --- a/drivers/clk/qcom/Kconfig +++ b/drivers/clk/qcom/Kconfig @@ -192,6 +192,16 @@ config CLK_NORD_DISPCC Say Y if you want to support display devices and functionality such as splash screen. +config CLK_MAILI_VIDEOCC + tristate "Maili Video Clock Controller" + depends on ARM64 || COMPILE_TEST + select CLK_HAWI_GCC + default m if ARCH_QCOM + help + Support for the video clock controller on Maili devices. + Say Y if you want to support video devices and functionality such as + video encode/decode. + config CLK_NORD_GCC tristate "Nord Global Clock Controller" depends on ARM64 || COMPILE_TEST diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile index cc62bda00d2a..daf25d583be2 100644 --- a/drivers/clk/qcom/Makefile +++ b/drivers/clk/qcom/Makefile @@ -41,6 +41,7 @@ obj-$(CONFIG_CLK_KAANAPALI_GCC) += gcc-kaanapali.o obj-$(CONFIG_CLK_KAANAPALI_GPUCC) += gpucc-kaanapali.o gxclkctl-kaanapali.o obj-$(CONFIG_CLK_KAANAPALI_TCSRCC) += tcsrcc-kaanapali.o obj-$(CONFIG_CLK_KAANAPALI_VIDEOCC) += videocc-kaanapali.o +obj-$(CONFIG_CLK_MAILI_VIDEOCC) += videocc-maili.o obj-$(CONFIG_CLK_NORD_DISPCC) += dispcc0-nord.o dispcc1-nord.o obj-$(CONFIG_CLK_NORD_GCC) += gcc-nord.o negcc-nord.o nwgcc-nord.o segcc-nord.o obj-$(CONFIG_CLK_NORD_GPUCC) += gpucc-nord.o gpu2cc-nord.o diff --git a/drivers/clk/qcom/videocc-maili.c b/drivers/clk/qcom/videocc-maili.c new file mode 100644 index 000000000000..a7044009f078 --- /dev/null +++ b/drivers/clk/qcom/videocc-maili.c @@ -0,0 +1,660 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include + +#include + +#include "clk-alpha-pll.h" +#include "clk-branch.h" +#include "clk-rcg.h" +#include "clk-regmap.h" +#include "clk-regmap-divider.h" +#include "common.h" +#include "gdsc.h" +#include "reset.h" + +#define ACCU_CFG_MASK GENMASK(25, 21) + +enum { + DT_BI_TCXO, + DT_AHB_CLK, +}; + +enum { + P_BI_TCXO, + P_VIDEO_CC_PLL0_OUT_MAIN, + P_VIDEO_CC_PLL1_OUT_MAIN, + P_VIDEO_CC_PLL2_OUT_MAIN, +}; + +static const struct pll_vco taycan_eha_t_vco[] = { + { 249600000, 2500000000, 0 }, +}; + +/* 360.0 MHz Configuration */ +static const struct alpha_pll_config video_cc_pll0_config = { + .l = 0x12, + .cal_l = 0x42, + .alpha = 0xc000, + .config_ctl_val = 0xa5c400e7, + .config_ctl_hi_val = 0x0a806160, + .config_ctl_hi1_val = 0xf51dea20, + .user_ctl_val = 0x00000000, + .user_ctl_hi_val = 0x00000002, +}; + +static struct clk_alpha_pll video_cc_pll0 = { + .offset = 0x0, + .config = &video_cc_pll0_config, + .vco_table = taycan_eha_t_vco, + .num_vco = ARRAY_SIZE(taycan_eha_t_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_TAYCAN_EHA_T], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_pll0", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_taycan_eha_t_ops, + }, + }, +}; + +/* 480.0 MHz Configuration */ +static const struct alpha_pll_config video_cc_pll1_config = { + .l = 0x19, + .cal_l = 0x42, + .alpha = 0x0, + .config_ctl_val = 0xa5c400e7, + .config_ctl_hi_val = 0x0a806160, + .config_ctl_hi1_val = 0xf51dea20, + .user_ctl_val = 0x00000000, + .user_ctl_hi_val = 0x00000002, +}; + +static struct clk_alpha_pll video_cc_pll1 = { + .offset = 0x1000, + .config = &video_cc_pll1_config, + .vco_table = taycan_eha_t_vco, + .num_vco = ARRAY_SIZE(taycan_eha_t_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_TAYCAN_EHA_T], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_pll1", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_taycan_eha_t_ops, + }, + }, +}; + +/* 480.0 MHz Configuration */ +static const struct alpha_pll_config video_cc_pll2_config = { + .l = 0x19, + .cal_l = 0x42, + .alpha = 0x0, + .config_ctl_val = 0xa5c400e7, + .config_ctl_hi_val = 0x0a806160, + .config_ctl_hi1_val = 0xf51dea20, + .user_ctl_val = 0x00000000, + .user_ctl_hi_val = 0x00000002, +}; + +static struct clk_alpha_pll video_cc_pll2 = { + .offset = 0x2000, + .config = &video_cc_pll2_config, + .vco_table = taycan_eha_t_vco, + .num_vco = ARRAY_SIZE(taycan_eha_t_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_TAYCAN_EHA_T], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_pll2", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_taycan_eha_t_ops, + }, + }, +}; + +static const struct parent_map video_cc_parent_map_0[] = { + { P_BI_TCXO, 0 }, +}; + +static const struct clk_parent_data video_cc_parent_data_0[] = { + { .index = DT_BI_TCXO }, +}; + +static const struct parent_map video_cc_parent_map_1[] = { + { P_BI_TCXO, 0 }, + { P_VIDEO_CC_PLL1_OUT_MAIN, 1 }, +}; + +static const struct clk_parent_data video_cc_parent_data_1[] = { + { .index = DT_BI_TCXO }, + { .hw = &video_cc_pll1.clkr.hw }, +}; + +static const struct parent_map video_cc_parent_map_2[] = { + { P_BI_TCXO, 0 }, + { P_VIDEO_CC_PLL2_OUT_MAIN, 1 }, +}; + +static const struct clk_parent_data video_cc_parent_data_2[] = { + { .index = DT_BI_TCXO }, + { .hw = &video_cc_pll2.clkr.hw }, +}; + +static const struct parent_map video_cc_parent_map_3[] = { + { P_BI_TCXO, 0 }, + { P_VIDEO_CC_PLL0_OUT_MAIN, 1 }, +}; + +static const struct clk_parent_data video_cc_parent_data_3[] = { + { .index = DT_BI_TCXO }, + { .hw = &video_cc_pll0.clkr.hw }, +}; + +static const struct freq_tbl ftbl_video_cc_ahb_clk_src[] = { + F(19200000, P_BI_TCXO, 1, 0, 0), + { } +}; + +static struct clk_rcg2 video_cc_ahb_clk_src = { + .cmd_rcgr = 0x8060, + .mnd_width = 0, + .hid_width = 5, + .parent_map = video_cc_parent_map_0, + .freq_tbl = ftbl_video_cc_ahb_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "video_cc_ahb_clk_src", + .parent_data = video_cc_parent_data_0, + .num_parents = ARRAY_SIZE(video_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_video_cc_mvs0_clk_src[] = { + F(240000000, P_VIDEO_CC_PLL1_OUT_MAIN, 2, 0, 0), + F(338000000, P_VIDEO_CC_PLL1_OUT_MAIN, 2, 0, 0), + F(420000000, P_VIDEO_CC_PLL1_OUT_MAIN, 2, 0, 0), + F(444000000, P_VIDEO_CC_PLL1_OUT_MAIN, 2, 0, 0), + F(600000000, P_VIDEO_CC_PLL1_OUT_MAIN, 2, 0, 0), + F(630000000, P_VIDEO_CC_PLL1_OUT_MAIN, 2, 0, 0), + F(800000000, P_VIDEO_CC_PLL1_OUT_MAIN, 2, 0, 0), + F(1000000000, P_VIDEO_CC_PLL1_OUT_MAIN, 2, 0, 0), + { } +}; + +static struct clk_rcg2 video_cc_mvs0_clk_src = { + .cmd_rcgr = 0x8030, + .mnd_width = 0, + .hid_width = 5, + .parent_map = video_cc_parent_map_1, + .freq_tbl = ftbl_video_cc_mvs0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0_clk_src", + .parent_data = video_cc_parent_data_1, + .num_parents = ARRAY_SIZE(video_cc_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_video_cc_mvs0b_clk_src[] = { + F(240000000, P_VIDEO_CC_PLL2_OUT_MAIN, 2, 0, 0), + F(338000000, P_VIDEO_CC_PLL2_OUT_MAIN, 2, 0, 0), + F(420000000, P_VIDEO_CC_PLL2_OUT_MAIN, 2, 0, 0), + F(444000000, P_VIDEO_CC_PLL2_OUT_MAIN, 2, 0, 0), + F(533000000, P_VIDEO_CC_PLL2_OUT_MAIN, 2, 0, 0), + F(630000000, P_VIDEO_CC_PLL2_OUT_MAIN, 2, 0, 0), + F(800000000, P_VIDEO_CC_PLL2_OUT_MAIN, 2, 0, 0), + { } +}; + +static struct clk_rcg2 video_cc_mvs0b_clk_src = { + .cmd_rcgr = 0x8018, + .mnd_width = 0, + .hid_width = 5, + .parent_map = video_cc_parent_map_2, + .freq_tbl = ftbl_video_cc_mvs0b_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0b_clk_src", + .parent_data = video_cc_parent_data_2, + .num_parents = ARRAY_SIZE(video_cc_parent_data_2), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_video_cc_mvs0c_clk_src[] = { + F(360000000, P_VIDEO_CC_PLL0_OUT_MAIN, 1, 0, 0), + F(507000000, P_VIDEO_CC_PLL0_OUT_MAIN, 1, 0, 0), + F(630000000, P_VIDEO_CC_PLL0_OUT_MAIN, 1, 0, 0), + F(666000000, P_VIDEO_CC_PLL0_OUT_MAIN, 1, 0, 0), + F(800000000, P_VIDEO_CC_PLL0_OUT_MAIN, 1, 0, 0), + F(1104000000, P_VIDEO_CC_PLL0_OUT_MAIN, 1, 0, 0), + F(1260000000, P_VIDEO_CC_PLL0_OUT_MAIN, 1, 0, 0), + { } +}; + +static struct clk_rcg2 video_cc_mvs0c_clk_src = { + .cmd_rcgr = 0x8048, + .mnd_width = 0, + .hid_width = 5, + .parent_map = video_cc_parent_map_3, + .freq_tbl = ftbl_video_cc_mvs0c_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0c_clk_src", + .parent_data = video_cc_parent_data_3, + .num_parents = ARRAY_SIZE(video_cc_parent_data_3), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 video_cc_xo_clk_src = { + .cmd_rcgr = 0x8194, + .mnd_width = 0, + .hid_width = 5, + .parent_map = video_cc_parent_map_0, + .freq_tbl = ftbl_video_cc_ahb_clk_src, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "video_cc_xo_clk_src", + .parent_data = video_cc_parent_data_0, + .num_parents = ARRAY_SIZE(video_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_ops, + }, +}; + +static struct clk_branch video_cc_mvs0_clk = { + .halt_reg = 0x80d0, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x80d0, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x80d0, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0_clk", + .parent_hws = (const struct clk_hw*[]) { + &video_cc_mvs0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_mem_branch video_cc_mvs0_freerun_clk = { + .mem_enable_reg = 0x80e4, + .mem_ack_reg = 0x80e4, + .mem_enable_mask = BIT(3), + .mem_enable_ack_mask = GENMASK(11, 10), + .mem_enable_invert = true, + .branch = { + .halt_reg = 0x80e0, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x80e0, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0_freerun_clk", + .parent_hws = (const struct clk_hw*[]) { + &video_cc_mvs0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_mem_ops, + }, + }, + }, +}; + +static struct clk_branch video_cc_mvs0_shift_clk = { + .halt_reg = 0x81b4, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x81b4, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x81b4, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0_shift_clk", + .parent_hws = (const struct clk_hw*[]) { + &video_cc_xo_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch video_cc_mvs0_vpp0_clk = { + .halt_reg = 0x8134, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x8134, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x8134, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0_vpp0_clk", + .parent_hws = (const struct clk_hw*[]) { + &video_cc_mvs0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch video_cc_mvs0_vpp0_freerun_clk = { + .halt_reg = 0x8144, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8144, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0_vpp0_freerun_clk", + .parent_hws = (const struct clk_hw*[]) { + &video_cc_mvs0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch video_cc_mvs0b_clk = { + .halt_reg = 0x80bc, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x80bc, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x80bc, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0b_clk", + .parent_hws = (const struct clk_hw*[]) { + &video_cc_mvs0b_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch video_cc_mvs0b_freerun_clk = { + .halt_reg = 0x80cc, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x80cc, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0b_freerun_clk", + .parent_hws = (const struct clk_hw*[]) { + &video_cc_mvs0b_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch video_cc_mvs0c_clk = { + .halt_reg = 0x8164, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x8164, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x8164, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0c_clk", + .parent_hws = (const struct clk_hw*[]) { + &video_cc_mvs0c_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch video_cc_mvs0c_debug_clk = { + .halt_reg = 0x81c0, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x81c0, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0c_debug_clk", + .parent_hws = (const struct clk_hw*[]) { + &video_cc_mvs0c_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch video_cc_mvs0c_freerun_clk = { + .halt_reg = 0x8174, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8174, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0c_freerun_clk", + .parent_hws = (const struct clk_hw*[]) { + &video_cc_mvs0c_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch video_cc_mvs0c_shift_clk = { + .halt_reg = 0x81b8, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x81b8, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x81b8, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0c_shift_clk", + .parent_hws = (const struct clk_hw*[]) { + &video_cc_xo_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct gdsc video_cc_mvs0c_gdsc = { + .gdscr = 0x814c, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0x6, + .pd = { + .name = "video_cc_mvs0c_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct gdsc video_cc_mvs0_gdsc = { + .gdscr = 0x80a8, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0x6, + .pd = { + .name = "video_cc_mvs0_gdsc", + }, + .parent = &video_cc_mvs0c_gdsc.pd, + .pwrsts = PWRSTS_OFF_ON, + .flags = HW_CTRL_TRIGGER | POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct gdsc video_cc_mvs0_vpp0_gdsc = { + .gdscr = 0x8120, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0xf, + .pd = { + .name = "video_cc_mvs0_vpp0_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .flags = HW_CTRL_TRIGGER | POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct clk_regmap *video_cc_maili_clocks[] = { + [VIDEO_CC_AHB_CLK_SRC] = &video_cc_ahb_clk_src.clkr, + [VIDEO_CC_MVS0_CLK] = &video_cc_mvs0_clk.clkr, + [VIDEO_CC_MVS0_CLK_SRC] = &video_cc_mvs0_clk_src.clkr, + [VIDEO_CC_MVS0_FREERUN_CLK] = &video_cc_mvs0_freerun_clk.branch.clkr, + [VIDEO_CC_MVS0_SHIFT_CLK] = &video_cc_mvs0_shift_clk.clkr, + [VIDEO_CC_MVS0_VPP0_CLK] = &video_cc_mvs0_vpp0_clk.clkr, + [VIDEO_CC_MVS0_VPP0_FREERUN_CLK] = &video_cc_mvs0_vpp0_freerun_clk.clkr, + [VIDEO_CC_MVS0B_CLK] = &video_cc_mvs0b_clk.clkr, + [VIDEO_CC_MVS0B_CLK_SRC] = &video_cc_mvs0b_clk_src.clkr, + [VIDEO_CC_MVS0B_FREERUN_CLK] = &video_cc_mvs0b_freerun_clk.clkr, + [VIDEO_CC_MVS0C_CLK] = &video_cc_mvs0c_clk.clkr, + [VIDEO_CC_MVS0C_CLK_SRC] = &video_cc_mvs0c_clk_src.clkr, + [VIDEO_CC_MVS0C_DEBUG_CLK] = &video_cc_mvs0c_debug_clk.clkr, + [VIDEO_CC_MVS0C_FREERUN_CLK] = &video_cc_mvs0c_freerun_clk.clkr, + [VIDEO_CC_MVS0C_SHIFT_CLK] = &video_cc_mvs0c_shift_clk.clkr, + [VIDEO_CC_PLL0] = &video_cc_pll0.clkr, + [VIDEO_CC_PLL1] = &video_cc_pll1.clkr, + [VIDEO_CC_PLL2] = &video_cc_pll2.clkr, + [VIDEO_CC_XO_CLK_SRC] = &video_cc_xo_clk_src.clkr, +}; + +static struct gdsc *video_cc_maili_gdscs[] = { + [VIDEO_CC_MVS0_GDSC] = &video_cc_mvs0_gdsc, + [VIDEO_CC_MVS0_VPP0_GDSC] = &video_cc_mvs0_vpp0_gdsc, + [VIDEO_CC_MVS0C_GDSC] = &video_cc_mvs0c_gdsc, +}; + +static const struct qcom_reset_map video_cc_maili_resets[] = { + [VIDEO_CC_INTERFACE_BCR] = { 0x8178 }, + [VIDEO_CC_MVS0_BCR] = { 0x80a4 }, + [VIDEO_CC_MVS0_CLK_ARES] = { 0x80d0, 2 }, + [VIDEO_CC_MVS0_FREERUN_CLK_ARES] = { 0x80e0, 2 }, + [VIDEO_CC_MVS0_VPP0_BCR] = { 0x811c }, + [VIDEO_CC_MVS0C_BCR] = { 0x8148 }, + [VIDEO_CC_MVS0C_CLK_ARES] = { 0x8164, 2 }, + [VIDEO_CC_MVS0C_FREERUN_CLK_ARES] = { 0x8174, 2 }, + [VIDEO_CC_XO_CLK_ARES] = { 0x81ac, 2 }, +}; + +static struct clk_alpha_pll *video_cc_maili_plls[] = { + &video_cc_pll0, + &video_cc_pll1, + &video_cc_pll2, +}; + +static const u32 video_cc_maili_critical_cbcrs[] = { + 0x817c, /* VIDEO_CC_AHB_CLK */ + 0x81bc, /* VIDEO_CC_SLEEP_CLK */ + 0x81b0, /* VIDEO_CC_TS_XO_CLK */ + 0x81ac, /* VIDEO_CC_XO_CLK */ +}; + +static const struct regmap_config video_cc_maili_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0xa010, + .fast_io = true, +}; + +static void clk_maili_regs_configure(struct device *dev, struct regmap *regmap) +{ + /* + * Enable clk_on sync for MVS0 and VPP clocks as per the + * hardware recommendation. + */ + regmap_set_bits(regmap, 0x9f24, BIT(0)); + + /* + * Update ACCU configuration for MVS0 CFG3, MVS0 VPP0 CFG3, + * MVS0C CFG3 GDSCRs as per the hardware recommendation. + */ + regmap_set_bits(regmap, 0x80b4, ACCU_CFG_MASK); + regmap_set_bits(regmap, 0x812c, ACCU_CFG_MASK); + regmap_set_bits(regmap, 0x8158, ACCU_CFG_MASK); +} + +static const struct qcom_cc_driver_data video_cc_maili_driver_data = { + .alpha_plls = video_cc_maili_plls, + .num_alpha_plls = ARRAY_SIZE(video_cc_maili_plls), + .clk_cbcrs = video_cc_maili_critical_cbcrs, + .num_clk_cbcrs = ARRAY_SIZE(video_cc_maili_critical_cbcrs), + .clk_regs_configure = clk_maili_regs_configure, +}; + +static const struct qcom_cc_desc video_cc_maili_desc = { + .config = &video_cc_maili_regmap_config, + .clks = video_cc_maili_clocks, + .num_clks = ARRAY_SIZE(video_cc_maili_clocks), + .resets = video_cc_maili_resets, + .num_resets = ARRAY_SIZE(video_cc_maili_resets), + .gdscs = video_cc_maili_gdscs, + .num_gdscs = ARRAY_SIZE(video_cc_maili_gdscs), + .use_rpm = true, + .driver_data = &video_cc_maili_driver_data, +}; + +static const struct of_device_id video_cc_maili_match_table[] = { + { .compatible = "qcom,maili-videocc" }, + { } +}; +MODULE_DEVICE_TABLE(of, video_cc_maili_match_table); + +static int video_cc_maili_probe(struct platform_device *pdev) +{ + return qcom_cc_probe(pdev, &video_cc_maili_desc); +} + +static struct platform_driver video_cc_maili_driver = { + .probe = video_cc_maili_probe, + .driver = { + .name = "videocc-maili", + .of_match_table = video_cc_maili_match_table, + }, +}; + +module_platform_driver(video_cc_maili_driver); + +MODULE_DESCRIPTION("QTI VIDEOCC Maili Driver"); +MODULE_LICENSE("GPL"); From 4af10c020e29607dacf1e06e98aa4286cf71d4c0 Mon Sep 17 00:00:00 2001 From: Varadarajan Narayanan Date: Fri, 3 Jul 2026 10:33:47 +0530 Subject: [PATCH 0387/1328] dt-bindings: clock: qcom,a53pll: Add IPQ5210 compatible Add the qcom,ipq5210-a53pll compatible for the A53 PLL found on IPQ5210 SoCs. Signed-off-by: Varadarajan Narayanan Acked-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260703-apss-clk-v3-1-4785e89a9c58@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- Documentation/devicetree/bindings/clock/qcom,a53pll.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/clock/qcom,a53pll.yaml b/Documentation/devicetree/bindings/clock/qcom,a53pll.yaml index 47ceab641a4c..1eec77e75a0a 100644 --- a/Documentation/devicetree/bindings/clock/qcom,a53pll.yaml +++ b/Documentation/devicetree/bindings/clock/qcom,a53pll.yaml @@ -17,6 +17,7 @@ properties: compatible: enum: - qcom,ipq5018-a53pll + - qcom,ipq5210-a53pll - qcom,ipq5332-a53pll - qcom,ipq6018-a53pll - qcom,ipq8074-a53pll From 546e0bc2aa207f7e9c78c88b0190b0045f796169 Mon Sep 17 00:00:00 2001 From: Varadarajan Narayanan Date: Fri, 3 Jul 2026 10:33:48 +0530 Subject: [PATCH 0388/1328] clk: qcom: apss-ipq-pll: Add IPQ5210 support The Application Processor Subsystem on the IPQ5210 platform sources its clock from the Huayra PLL. Add the configuration data necessary to set it up. Reviewed-by: Konrad Dybcio Reviewed-by: Dmitry Baryshkov Signed-off-by: Varadarajan Narayanan Link: https://lore.kernel.org/r/20260703-apss-clk-v3-2-4785e89a9c58@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/apss-ipq-pll.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/drivers/clk/qcom/apss-ipq-pll.c b/drivers/clk/qcom/apss-ipq-pll.c index 3a8987fe7008..5653bb5e93f7 100644 --- a/drivers/clk/qcom/apss-ipq-pll.c +++ b/drivers/clk/qcom/apss-ipq-pll.c @@ -79,6 +79,18 @@ static const struct alpha_pll_config ipq5018_pll_config = { .test_ctl_hi_val = 0x00400003, }; +static const struct alpha_pll_config ipq5210_pll_config = { + .l = 0x22, + .config_ctl_val = 0x4001075b, + .config_ctl_hi_val = 0x6, + .early_output_mask = BIT(3), + .aux2_output_mask = BIT(2), + .aux_output_mask = BIT(1), + .main_output_mask = BIT(0), + .test_ctl_val = 0x0, + .test_ctl_hi_val = 0x400003, +}; + /* 1.080 GHz configuration */ static const struct alpha_pll_config ipq5332_pll_config = { .l = 0x2d, @@ -140,6 +152,12 @@ static const struct apss_pll_data ipq5018_pll_data = { .pll_config = &ipq5018_pll_config, }; +static const struct apss_pll_data ipq5210_pll_data = { + .pll_type = CLK_ALPHA_PLL_TYPE_HUAYRA, + .pll = &ipq_pll_huayra, + .pll_config = &ipq5210_pll_config, +}; + static const struct apss_pll_data ipq5332_pll_data = { .pll_type = CLK_ALPHA_PLL_TYPE_STROMER_PLUS, .pll = &ipq_pll_stromer_plus, @@ -207,6 +225,7 @@ static int apss_ipq_pll_probe(struct platform_device *pdev) static const struct of_device_id apss_ipq_pll_match_table[] = { { .compatible = "qcom,ipq5018-a53pll", .data = &ipq5018_pll_data }, + { .compatible = "qcom,ipq5210-a53pll", .data = &ipq5210_pll_data }, { .compatible = "qcom,ipq5332-a53pll", .data = &ipq5332_pll_data }, { .compatible = "qcom,ipq6018-a53pll", .data = &ipq6018_pll_data }, { .compatible = "qcom,ipq8074-a53pll", .data = &ipq8074_pll_data }, From 22d9257f08913b6eec3e8ece4d13d9c41f14428b Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Mon, 29 Jun 2026 19:21:27 +0300 Subject: [PATCH 0389/1328] clk: qcom: Return expected ENOMEM error on dynamic allocation failure If a dynamic memory allocation fails, the returned error code in clock controller driver probe functions on a few legacy platforms should be set to -ENOMEM instead of -EINVAL. Fixes: ee15faffef11 ("clk: qcom: common: Add API to register board clocks backwards compatibly") Signed-off-by: Vladimir Zapolskiy Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260629162127.3910603-1-vz@kernel.org Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/common.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/clk/qcom/common.c b/drivers/clk/qcom/common.c index 2c09abaf1d2a..08fd62ac780d 100644 --- a/drivers/clk/qcom/common.c +++ b/drivers/clk/qcom/common.c @@ -169,7 +169,7 @@ static int _qcom_cc_register_board_clk(struct device *dev, const char *path, if (!node) { fixed = devm_kzalloc(dev, sizeof(*fixed), GFP_KERNEL); if (!fixed) - return -EINVAL; + return -ENOMEM; fixed->fixed_rate = rate; fixed->hw.init = &init_data; @@ -186,7 +186,7 @@ static int _qcom_cc_register_board_clk(struct device *dev, const char *path, if (add_factor) { factor = devm_kzalloc(dev, sizeof(*factor), GFP_KERNEL); if (!factor) - return -EINVAL; + return -ENOMEM; factor->mult = factor->div = 1; factor->hw.init = &init_data; From c8ec4b10864ee5cfd4ea88bca4238bf7df24b10b Mon Sep 17 00:00:00 2001 From: Nabige Aala Date: Mon, 8 Jun 2026 12:31:42 +0530 Subject: [PATCH 0390/1328] dt-bindings: display: msm: qcm2290: Add Shikra MDSS Shikra reuses the same MDSS/DPU 6.5 hardware as QCM2290. Extend the existing qcm2290 bindings to cover Shikra by adding fallback compatible chains for MDSS, DPU and DSI controller nodes rather than introducing a separate binding file. Signed-off-by: Nabige Aala Reviewed-by: Dmitry Baryshkov Reviewed-by: Krzysztof Kozlowski Reviewed-by: Loic Poulain Patchwork: https://patchwork.freedesktop.org/patch/731152/ Link: https://lore.kernel.org/r/20260608-shikra-display-v4-1-88a846afdd5d@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- .../display/msm/dsi-controller-main.yaml | 4 ++++ .../display/msm/qcom,qcm2290-dpu.yaml | 6 +++++- .../display/msm/qcom,qcm2290-mdss.yaml | 19 ++++++++++++------- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/Documentation/devicetree/bindings/display/msm/dsi-controller-main.yaml b/Documentation/devicetree/bindings/display/msm/dsi-controller-main.yaml index dbc0613e427e..fd0834d09ad6 100644 --- a/Documentation/devicetree/bindings/display/msm/dsi-controller-main.yaml +++ b/Documentation/devicetree/bindings/display/msm/dsi-controller-main.yaml @@ -47,6 +47,10 @@ properties: - qcom,sm8650-dsi-ctrl - qcom,sm8750-dsi-ctrl - const: qcom,mdss-dsi-ctrl + - items: + - const: qcom,shikra-dsi-ctrl + - const: qcom,qcm2290-dsi-ctrl + - const: qcom,mdss-dsi-ctrl - items: - enum: - qcom,qcs8300-dsi-ctrl diff --git a/Documentation/devicetree/bindings/display/msm/qcom,qcm2290-dpu.yaml b/Documentation/devicetree/bindings/display/msm/qcom,qcm2290-dpu.yaml index be6cd8adb3b6..034d3df8d247 100644 --- a/Documentation/devicetree/bindings/display/msm/qcom,qcm2290-dpu.yaml +++ b/Documentation/devicetree/bindings/display/msm/qcom,qcm2290-dpu.yaml @@ -13,7 +13,11 @@ $ref: /schemas/display/msm/dpu-common.yaml# properties: compatible: - const: qcom,qcm2290-dpu + oneOf: + - const: qcom,qcm2290-dpu + - items: + - const: qcom,shikra-dpu + - const: qcom,qcm2290-dpu reg: items: diff --git a/Documentation/devicetree/bindings/display/msm/qcom,qcm2290-mdss.yaml b/Documentation/devicetree/bindings/display/msm/qcom,qcm2290-mdss.yaml index bb09ecd1a5b4..49a7b5c4c678 100644 --- a/Documentation/devicetree/bindings/display/msm/qcom,qcm2290-mdss.yaml +++ b/Documentation/devicetree/bindings/display/msm/qcom,qcm2290-mdss.yaml @@ -4,7 +4,7 @@ $id: http://devicetree.org/schemas/display/msm/qcom,qcm2290-mdss.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Qualcomm QCM220 Display MDSS +title: Qualcomm QCM2290 and Shikra Display MDSS maintainers: - Loic Poulain @@ -12,13 +12,18 @@ maintainers: description: Device tree bindings for MSM Mobile Display Subsystem(MDSS) that encapsulates sub-blocks like DPU display controller and DSI. Device tree bindings of MDSS - are mentioned for QCM2290 target. + are mentioned for QCM2290 and Shikra targets. Shikra uses the same MDSS/DPU/DSI + hardware as QCM2290 (DPU 6.5) and shares the same register layout. $ref: /schemas/display/msm/mdss-common.yaml# properties: compatible: - const: qcom,qcm2290-mdss + oneOf: + - const: qcom,qcm2290-mdss + - items: + - const: qcom,shikra-mdss + - const: qcom,qcm2290-mdss clocks: items: @@ -52,7 +57,8 @@ patternProperties: properties: compatible: - const: qcom,qcm2290-dpu + contains: + const: qcom,qcm2290-dpu "^dsi@[0-9a-f]+$": type: object @@ -60,9 +66,8 @@ patternProperties: properties: compatible: - items: - - const: qcom,qcm2290-dsi-ctrl - - const: qcom,mdss-dsi-ctrl + contains: + const: qcom,qcm2290-dsi-ctrl "^phy@[0-9a-f]+$": type: object From d858770e86e75bf78429b9f5210b7556ef431033 Mon Sep 17 00:00:00 2001 From: Kumar Anurag Date: Tue, 16 Jun 2026 08:12:52 -0700 Subject: [PATCH 0391/1328] drm/msm/dp: return 0 from audio_prepare when cable is disconnected PipeWire treats a non-zero return from prepare as fatal, marking the DP audio device as a dummy sink when the cable is unplugged. The active_stream_cnt guard already prevents any unclocked hardware access, so return success instead of -EINVAL when the link is not active. Signed-off-by: Kumar Anurag Suggested-by: Srinivas Kandagatla Reviewed-by: Dmitry Baryshkov # same behaviour Patchwork: https://patchwork.freedesktop.org/patch/733663/ Link: https://lore.kernel.org/r/20260616151252.3599089-2-kumar.singh@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_audio.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_audio.c b/drivers/gpu/drm/msm/dp/dp_audio.c index 41018e82efa1..df222943ae57 100644 --- a/drivers/gpu/drm/msm/dp/dp_audio.c +++ b/drivers/gpu/drm/msm/dp/dp_audio.c @@ -284,10 +284,8 @@ int msm_dp_audio_prepare(struct drm_bridge *bridge, * such cases check for connection status and bail out if not * connected. */ - if (!msm_dp_display->power_on) { - rc = -EINVAL; + if (!msm_dp_display->power_on) goto end; - } audio = msm_dp_audio_get_data(msm_dp_display); if (IS_ERR(audio)) { From 4f2b89b0341126ddddcfcf8a5a9895d226930e73 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Thu, 18 Jun 2026 16:11:32 +0200 Subject: [PATCH 0392/1328] drm/msm: Do not declare msm_framebuffer_init() as static Declare msm_framebuffer_init() in msm_drv.h and remove the static qualifier. The function will be required in msm_fbdev.c after inlining msm_alloc_stolen_fb(). Also move msm_framebuffer_init() before msm_framebuffer_create(), so that it can later be made static again. Prepares msm's fbdev emulation for using client buffers. Signed-off-by: Thomas Zimmermann Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/733883/ Link: https://lore.kernel.org/r/20260618141249.151338-2-tzimmermann@suse.de Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/msm_drv.h | 4 ++ drivers/gpu/drm/msm/msm_fb.c | 72 +++++++++++++++++------------------ 2 files changed, 39 insertions(+), 37 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h index 3787db8770ad..7dccf0713b02 100644 --- a/drivers/gpu/drm/msm/msm_drv.h +++ b/drivers/gpu/drm/msm/msm_drv.h @@ -258,6 +258,10 @@ void msm_framebuffer_cleanup(struct drm_framebuffer *fb, bool needed_dirtyfb); uint32_t msm_framebuffer_iova(struct drm_framebuffer *fb, int plane); struct drm_gem_object *msm_framebuffer_bo(struct drm_framebuffer *fb, int plane); const struct msm_format *msm_framebuffer_format(struct drm_framebuffer *fb); +struct drm_framebuffer *msm_framebuffer_init(struct drm_device *dev, + const struct drm_format_info *info, + const struct drm_mode_fb_cmd2 *mode_cmd, + struct drm_gem_object **bos); struct drm_framebuffer *msm_framebuffer_create(struct drm_device *dev, struct drm_file *file, const struct drm_format_info *info, const struct drm_mode_fb_cmd2 *mode_cmd); diff --git a/drivers/gpu/drm/msm/msm_fb.c b/drivers/gpu/drm/msm/msm_fb.c index ee17a47f31c4..c98710569a2e 100644 --- a/drivers/gpu/drm/msm/msm_fb.c +++ b/drivers/gpu/drm/msm/msm_fb.c @@ -29,10 +29,6 @@ struct msm_framebuffer { }; #define to_msm_framebuffer(x) container_of(x, struct msm_framebuffer, base) -static struct drm_framebuffer *msm_framebuffer_init(struct drm_device *dev, - const struct drm_format_info *info, - const struct drm_mode_fb_cmd2 *mode_cmd, struct drm_gem_object **bos); - static int msm_framebuffer_dirtyfb(struct drm_framebuffer *fb, struct drm_file *file_priv, unsigned int flags, unsigned int color, struct drm_clip_rect *clips, @@ -139,39 +135,10 @@ const struct msm_format *msm_framebuffer_format(struct drm_framebuffer *fb) return msm_fb->format; } -struct drm_framebuffer *msm_framebuffer_create(struct drm_device *dev, - struct drm_file *file, const struct drm_format_info *info, - const struct drm_mode_fb_cmd2 *mode_cmd) -{ - struct drm_gem_object *bos[4] = {0}; - struct drm_framebuffer *fb; - int ret, i, n = info->num_planes; - - for (i = 0; i < n; i++) { - bos[i] = drm_gem_object_lookup(file, mode_cmd->handles[i]); - if (!bos[i]) { - ret = -ENXIO; - goto out_unref; - } - } - - fb = msm_framebuffer_init(dev, info, mode_cmd, bos); - if (IS_ERR(fb)) { - ret = PTR_ERR(fb); - goto out_unref; - } - - return fb; - -out_unref: - for (i = 0; i < n; i++) - drm_gem_object_put(bos[i]); - return ERR_PTR(ret); -} - -static struct drm_framebuffer *msm_framebuffer_init(struct drm_device *dev, - const struct drm_format_info *info, - const struct drm_mode_fb_cmd2 *mode_cmd, struct drm_gem_object **bos) +struct drm_framebuffer *msm_framebuffer_init(struct drm_device *dev, + const struct drm_format_info *info, + const struct drm_mode_fb_cmd2 *mode_cmd, + struct drm_gem_object **bos) { struct msm_drm_private *priv = dev->dev_private; struct msm_kms *kms = priv->kms; @@ -251,6 +218,37 @@ static struct drm_framebuffer *msm_framebuffer_init(struct drm_device *dev, return ERR_PTR(ret); } +struct drm_framebuffer *msm_framebuffer_create(struct drm_device *dev, + struct drm_file *file, + const struct drm_format_info *info, + const struct drm_mode_fb_cmd2 *mode_cmd) +{ + struct drm_gem_object *bos[4] = {0}; + struct drm_framebuffer *fb; + int ret, i, n = info->num_planes; + + for (i = 0; i < n; i++) { + bos[i] = drm_gem_object_lookup(file, mode_cmd->handles[i]); + if (!bos[i]) { + ret = -ENXIO; + goto out_unref; + } + } + + fb = msm_framebuffer_init(dev, info, mode_cmd, bos); + if (IS_ERR(fb)) { + ret = PTR_ERR(fb); + goto out_unref; + } + + return fb; + +out_unref: + for (i = 0; i < n; i++) + drm_gem_object_put(bos[i]); + return ERR_PTR(ret); +} + struct drm_framebuffer * msm_alloc_stolen_fb(struct drm_device *dev, int w, int h, int p, uint32_t format) { From 0cfd468c995644a512c3fe82f391814f7ea30e77 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Thu, 18 Jun 2026 16:11:33 +0200 Subject: [PATCH 0393/1328] drm/msm: fbdev: Inline msm_alloc_stolen_fb() Inline msm_alloc_stolen_fb() into its only caller. This is necessary for converting fbdev emulation to use client buffers. There are some minor changes: - Handle errors for the non-stolen BO in the respective branch. - Fill mode_cmd right before using it with msm_framebuffer_init(). Both will later be replaced with client-buffer interfaces. - Set the modifier[0] to DRM_FORMAT_MOD_LINEAR. No functional change. - Integrate the error handling with the existing clean-up. Signed-off-by: Thomas Zimmermann Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/733884/ Link: https://lore.kernel.org/r/20260618141249.151338-3-tzimmermann@suse.de Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/msm_drv.h | 2 -- drivers/gpu/drm/msm/msm_fb.c | 46 --------------------------------- drivers/gpu/drm/msm/msm_fbdev.c | 46 ++++++++++++++++++++++++++------- 3 files changed, 37 insertions(+), 57 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h index 7dccf0713b02..01a64a32b843 100644 --- a/drivers/gpu/drm/msm/msm_drv.h +++ b/drivers/gpu/drm/msm/msm_drv.h @@ -265,8 +265,6 @@ struct drm_framebuffer *msm_framebuffer_init(struct drm_device *dev, struct drm_framebuffer *msm_framebuffer_create(struct drm_device *dev, struct drm_file *file, const struct drm_format_info *info, const struct drm_mode_fb_cmd2 *mode_cmd); -struct drm_framebuffer * msm_alloc_stolen_fb(struct drm_device *dev, - int w, int h, int p, uint32_t format); #ifdef CONFIG_DRM_MSM_KMS_FBDEV int msm_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, diff --git a/drivers/gpu/drm/msm/msm_fb.c b/drivers/gpu/drm/msm/msm_fb.c index c98710569a2e..dec550e6cf75 100644 --- a/drivers/gpu/drm/msm/msm_fb.c +++ b/drivers/gpu/drm/msm/msm_fb.c @@ -6,7 +6,6 @@ #include #include -#include #include #include #include @@ -248,48 +247,3 @@ struct drm_framebuffer *msm_framebuffer_create(struct drm_device *dev, drm_gem_object_put(bos[i]); return ERR_PTR(ret); } - -struct drm_framebuffer * -msm_alloc_stolen_fb(struct drm_device *dev, int w, int h, int p, uint32_t format) -{ - struct drm_mode_fb_cmd2 mode_cmd = { - .pixel_format = format, - .width = w, - .height = h, - .pitches = { p }, - }; - struct drm_gem_object *bo; - struct drm_framebuffer *fb; - int size; - - /* allocate backing bo */ - size = mode_cmd.pitches[0] * mode_cmd.height; - DBG("allocating %d bytes for fb %d", size, dev->primary->index); - bo = msm_gem_new(dev, size, MSM_BO_SCANOUT | MSM_BO_WC | MSM_BO_STOLEN, NULL); - if (IS_ERR(bo)) { - dev_warn(dev->dev, "could not allocate stolen bo\n"); - /* try regular bo: */ - bo = msm_gem_new(dev, size, MSM_BO_SCANOUT | MSM_BO_WC, NULL); - } - if (IS_ERR(bo)) { - DRM_DEV_ERROR(dev->dev, "failed to allocate buffer object\n"); - return ERR_CAST(bo); - } - - msm_gem_object_set_name(bo, "stolenfb"); - - fb = msm_framebuffer_init(dev, - drm_get_format_info(dev, mode_cmd.pixel_format, - mode_cmd.modifier[0]), - &mode_cmd, &bo); - if (IS_ERR(fb)) { - DRM_DEV_ERROR(dev->dev, "failed to allocate fb\n"); - /* note: if fb creation failed, we can't rely on fb destroy - * to unref the bo: - */ - drm_gem_object_put(bo); - return ERR_CAST(fb); - } - - return fb; -} diff --git a/drivers/gpu/drm/msm/msm_fbdev.c b/drivers/gpu/drm/msm/msm_fbdev.c index fd19995b12b5..f834a595e55e 100644 --- a/drivers/gpu/drm/msm/msm_fbdev.c +++ b/drivers/gpu/drm/msm/msm_fbdev.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -92,11 +93,13 @@ int msm_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, struct drm_device *dev = helper->dev; struct msm_drm_private *priv = dev->dev_private; struct fb_info *fbi = helper->info; + struct drm_mode_fb_cmd2 mode_cmd = { }; struct drm_framebuffer *fb = NULL; struct drm_gem_object *bo; uint64_t paddr; uint32_t format; int ret, pitch; + int size; format = drm_mode_legacy_fb_format(sizes->surface_bpp, sizes->surface_depth); @@ -105,15 +108,38 @@ int msm_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, sizes->fb_width, sizes->fb_height); pitch = align_pitch(sizes->surface_width, sizes->surface_bpp); - fb = msm_alloc_stolen_fb(dev, sizes->surface_width, - sizes->surface_height, pitch, format); - if (IS_ERR(fb)) { - DRM_DEV_ERROR(dev->dev, "failed to allocate fb\n"); - return PTR_ERR(fb); + /* allocate backing bo */ + size = pitch * sizes->surface_height; + DBG("allocating %d bytes for fb %d", size, dev->primary->index); + bo = msm_gem_new(dev, size, MSM_BO_SCANOUT | MSM_BO_WC | MSM_BO_STOLEN, NULL); + if (IS_ERR(bo)) { + dev_warn(dev->dev, "could not allocate stolen bo\n"); + /* try regular bo: */ + bo = msm_gem_new(dev, size, MSM_BO_SCANOUT | MSM_BO_WC, NULL); + if (IS_ERR(bo)) { + DRM_DEV_ERROR(dev->dev, "failed to allocate buffer object\n"); + return PTR_ERR(bo); + } } - bo = msm_framebuffer_bo(fb, 0); + msm_gem_object_set_name(bo, "stolenfb"); + + mode_cmd.pixel_format = format; + mode_cmd.width = sizes->surface_width; + mode_cmd.height = sizes->surface_height; + mode_cmd.pitches[0] = pitch; + mode_cmd.modifier[0] = DRM_FORMAT_MOD_LINEAR; + + fb = msm_framebuffer_init(dev, + drm_get_format_info(dev, mode_cmd.pixel_format, + mode_cmd.modifier[0]), + &mode_cmd, &bo); + if (IS_ERR(fb)) { + DRM_DEV_ERROR(dev->dev, "failed to allocate fb\n"); + ret = PTR_ERR(fb); + goto err_drm_gem_object_put; + } /* * NOTE: if we can be guaranteed to be able to map buffer @@ -123,7 +149,7 @@ int msm_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, ret = msm_gem_get_and_pin_iova(bo, priv->kms->vm, &paddr); if (ret) { DRM_DEV_ERROR(dev->dev, "failed to get buffer obj iova: %d\n", ret); - goto fail; + goto err_drm_framebuffer_remove; } DBG("fbi=%p, dev=%p", fbi, dev); @@ -138,7 +164,7 @@ int msm_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, fbi->screen_buffer = msm_gem_get_vaddr(bo); if (IS_ERR(fbi->screen_buffer)) { ret = PTR_ERR(fbi->screen_buffer); - goto fail; + goto err_drm_framebuffer_remove; } fbi->screen_size = bo->size; fbi->fix.smem_start = paddr; @@ -149,7 +175,9 @@ int msm_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, return 0; -fail: +err_drm_framebuffer_remove: drm_framebuffer_remove(fb); +err_drm_gem_object_put: + drm_gem_object_put(bo); return ret; } From e78d1bc9e4d062d1c95cd262ffe0c6fd66c40e1f Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Thu, 18 Jun 2026 16:11:34 +0200 Subject: [PATCH 0394/1328] drm/msm: fbdev: Fix error reporting Replace deprecated error reporting in msm_fbdev_driver_fbdev_probe(). Use drm_warn() and drm_err() instead. Signed-off-by: Thomas Zimmermann Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/733887/ Link: https://lore.kernel.org/r/20260618141249.151338-4-tzimmermann@suse.de Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/msm_fbdev.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_fbdev.c b/drivers/gpu/drm/msm/msm_fbdev.c index f834a595e55e..6af08b7b8082 100644 --- a/drivers/gpu/drm/msm/msm_fbdev.c +++ b/drivers/gpu/drm/msm/msm_fbdev.c @@ -114,11 +114,11 @@ int msm_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, DBG("allocating %d bytes for fb %d", size, dev->primary->index); bo = msm_gem_new(dev, size, MSM_BO_SCANOUT | MSM_BO_WC | MSM_BO_STOLEN, NULL); if (IS_ERR(bo)) { - dev_warn(dev->dev, "could not allocate stolen bo\n"); + drm_warn(dev, "could not allocate stolen bo\n"); /* try regular bo: */ bo = msm_gem_new(dev, size, MSM_BO_SCANOUT | MSM_BO_WC, NULL); if (IS_ERR(bo)) { - DRM_DEV_ERROR(dev->dev, "failed to allocate buffer object\n"); + drm_err(dev, "failed to allocate buffer object\n"); return PTR_ERR(bo); } } @@ -136,7 +136,7 @@ int msm_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, mode_cmd.modifier[0]), &mode_cmd, &bo); if (IS_ERR(fb)) { - DRM_DEV_ERROR(dev->dev, "failed to allocate fb\n"); + drm_err(dev, "failed to allocate fb\n"); ret = PTR_ERR(fb); goto err_drm_gem_object_put; } @@ -148,7 +148,7 @@ int msm_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, */ ret = msm_gem_get_and_pin_iova(bo, priv->kms->vm, &paddr); if (ret) { - DRM_DEV_ERROR(dev->dev, "failed to get buffer obj iova: %d\n", ret); + drm_err(dev, "failed to get buffer obj iova: %d\n", ret); goto err_drm_framebuffer_remove; } From 6cf3fb14f33ea73ee4a05fe4c406a6623c72d353 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Thu, 18 Jun 2026 16:11:35 +0200 Subject: [PATCH 0395/1328] drm/msm: fbdev: Calculate buffer geometry with format helpers Replace the geometry and size calculation in msm's fbdev emulation with DRM format helpers. This consists of a 4CC lookup from the fbdev parameters, format lookup, pitch calculation and size calculation. Then allocate the GEM buffer object for the framebuffer memory from the calculated size. Explicitly align the size of the allocated GEM buffer object to full pages. The contained memory is the framebuffer memory as seen by fbdev. The page alignment is required for mmap. v2: - clarify the page alignment of the buffer size (Dmitry) Signed-off-by: Thomas Zimmermann Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/733885/ Link: https://lore.kernel.org/r/20260618141249.151338-5-tzimmermann@suse.de Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/msm_drv.h | 7 ------- drivers/gpu/drm/msm/msm_fbdev.c | 26 ++++++++++++-------------- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h index 01a64a32b843..b21f8971dfa1 100644 --- a/drivers/gpu/drm/msm/msm_drv.h +++ b/drivers/gpu/drm/msm/msm_drv.h @@ -505,13 +505,6 @@ void msm_hrtimer_work_init(struct msm_hrtimer_work *work, #define DBG(fmt, ...) DRM_DEBUG_DRIVER(fmt"\n", ##__VA_ARGS__) #define VERB(fmt, ...) if (0) DRM_DEBUG_DRIVER(fmt"\n", ##__VA_ARGS__) -static inline int align_pitch(int width, int bpp) -{ - int bytespp = (bpp + 7) / 8; - /* adreno needs pitch aligned to 32 pixels: */ - return bytespp * ALIGN(width, 32); -} - /* for the generated headers: */ #define INVALID_IDX(idx) ({BUG(); 0;}) #define fui(x) ({BUG(); 0;}) diff --git a/drivers/gpu/drm/msm/msm_fbdev.c b/drivers/gpu/drm/msm/msm_fbdev.c index 6af08b7b8082..35b26830afb4 100644 --- a/drivers/gpu/drm/msm/msm_fbdev.c +++ b/drivers/gpu/drm/msm/msm_fbdev.c @@ -95,23 +95,25 @@ int msm_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, struct fb_info *fbi = helper->info; struct drm_mode_fb_cmd2 mode_cmd = { }; struct drm_framebuffer *fb = NULL; + const struct drm_format_info *format; + u32 fourcc, pitch; + u64 size; struct drm_gem_object *bo; uint64_t paddr; - uint32_t format; - int ret, pitch; - int size; - - format = drm_mode_legacy_fb_format(sizes->surface_bpp, sizes->surface_depth); + int ret; DBG("create fbdev: %dx%d@%d (%dx%d)", sizes->surface_width, sizes->surface_height, sizes->surface_bpp, sizes->fb_width, sizes->fb_height); - pitch = align_pitch(sizes->surface_width, sizes->surface_bpp); + fourcc = drm_mode_legacy_fb_format(sizes->surface_bpp, sizes->surface_depth); + format = drm_get_format_info(dev, fourcc, DRM_FORMAT_MOD_LINEAR); + /* adreno needs pitch aligned to 32 pixels: */ + pitch = drm_format_info_min_pitch(format, 0, ALIGN(sizes->surface_width, 32)); + size = ALIGN(pitch * sizes->surface_height, PAGE_SIZE); /* allocate backing bo */ - size = pitch * sizes->surface_height; - DBG("allocating %d bytes for fb %d", size, dev->primary->index); + DBG("allocating %llu bytes for fb %d", size, dev->primary->index); bo = msm_gem_new(dev, size, MSM_BO_SCANOUT | MSM_BO_WC | MSM_BO_STOLEN, NULL); if (IS_ERR(bo)) { drm_warn(dev, "could not allocate stolen bo\n"); @@ -125,16 +127,12 @@ int msm_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, msm_gem_object_set_name(bo, "stolenfb"); - mode_cmd.pixel_format = format; + mode_cmd.pixel_format = fourcc; mode_cmd.width = sizes->surface_width; mode_cmd.height = sizes->surface_height; mode_cmd.pitches[0] = pitch; - mode_cmd.modifier[0] = DRM_FORMAT_MOD_LINEAR; - fb = msm_framebuffer_init(dev, - drm_get_format_info(dev, mode_cmd.pixel_format, - mode_cmd.modifier[0]), - &mode_cmd, &bo); + fb = msm_framebuffer_init(dev, format, &mode_cmd, &bo); if (IS_ERR(fb)) { drm_err(dev, "failed to allocate fb\n"); ret = PTR_ERR(fb); From eb714953c44122fa4d9cb74cf98f22b46d3a5941 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Thu, 18 Jun 2026 16:11:36 +0200 Subject: [PATCH 0396/1328] drm/msm: fbdev: Use a DRM client buffer Replace the internal DRM framebuffer with a DRM client buffer. The client buffer allocates the DRM framebuffer on a file and also uses GEM object handles via the regular ADDFB2 interfaces. Using client-buffer interfaces unifies framebuffer allocation for DRM clients in user space and msm's internal fbdev emulation. It also simplifies the clean-up side of the fbdev emulation. Signed-off-by: Thomas Zimmermann Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/733891/ Link: https://lore.kernel.org/r/20260618141249.151338-6-tzimmermann@suse.de [DB: fixed error handling in msm_fbdev_driver_fbdev_probe] Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/msm_fbdev.c | 56 +++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_fbdev.c b/drivers/gpu/drm/msm/msm_fbdev.c index 35b26830afb4..89ca9da3e1f2 100644 --- a/drivers/gpu/drm/msm/msm_fbdev.c +++ b/drivers/gpu/drm/msm/msm_fbdev.c @@ -41,17 +41,15 @@ static int msm_fbdev_mmap(struct fb_info *info, struct vm_area_struct *vma) static void msm_fbdev_fb_destroy(struct fb_info *info) { struct drm_fb_helper *helper = (struct drm_fb_helper *)info->par; - struct drm_framebuffer *fb = helper->fb; - struct drm_gem_object *bo = msm_framebuffer_bo(fb, 0); + struct drm_gem_object *bo = msm_framebuffer_bo(helper->fb, 0); DBG(); drm_fb_helper_fini(helper); - /* this will free the backing object */ msm_gem_put_vaddr(bo); - drm_framebuffer_remove(fb); + drm_client_buffer_delete(helper->buffer); drm_client_release(&helper->client); } @@ -90,15 +88,16 @@ static const struct drm_fb_helper_funcs msm_fbdev_helper_funcs = { int msm_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, struct drm_fb_helper_surface_size *sizes) { - struct drm_device *dev = helper->dev; + struct drm_client_dev *client = &helper->client; + struct drm_device *dev = client->dev; + struct drm_file *file = client->file; struct msm_drm_private *priv = dev->dev_private; struct fb_info *fbi = helper->info; - struct drm_mode_fb_cmd2 mode_cmd = { }; - struct drm_framebuffer *fb = NULL; const struct drm_format_info *format; - u32 fourcc, pitch; + u32 fourcc, pitch, handle; u64 size; struct drm_gem_object *bo; + struct drm_client_buffer *buffer; uint64_t paddr; int ret; @@ -127,16 +126,15 @@ int msm_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, msm_gem_object_set_name(bo, "stolenfb"); - mode_cmd.pixel_format = fourcc; - mode_cmd.width = sizes->surface_width; - mode_cmd.height = sizes->surface_height; - mode_cmd.pitches[0] = pitch; - - fb = msm_framebuffer_init(dev, format, &mode_cmd, &bo); - if (IS_ERR(fb)) { - drm_err(dev, "failed to allocate fb\n"); - ret = PTR_ERR(fb); + ret = drm_gem_handle_create(file, bo, &handle); + if (ret) goto err_drm_gem_object_put; + + buffer = drm_client_buffer_create(client, sizes->surface_width, sizes->surface_height, + fourcc, handle, pitch); + if (IS_ERR(buffer)) { + ret = PTR_ERR(buffer); + goto err_drm_gem_handle_delete; } /* @@ -147,13 +145,14 @@ int msm_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, ret = msm_gem_get_and_pin_iova(bo, priv->kms->vm, &paddr); if (ret) { drm_err(dev, "failed to get buffer obj iova: %d\n", ret); - goto err_drm_framebuffer_remove; + goto err_drm_client_buffer_delete; } DBG("fbi=%p, dev=%p", fbi, dev); helper->funcs = &msm_fbdev_helper_funcs; - helper->fb = fb; + helper->buffer = buffer; + helper->fb = buffer->fb; fbi->fbops = &msm_fb_ops; @@ -162,19 +161,30 @@ int msm_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, fbi->screen_buffer = msm_gem_get_vaddr(bo); if (IS_ERR(fbi->screen_buffer)) { ret = PTR_ERR(fbi->screen_buffer); - goto err_drm_framebuffer_remove; + goto err_msm_gem_unpin; } fbi->screen_size = bo->size; fbi->fix.smem_start = paddr; fbi->fix.smem_len = bo->size; DBG("par=%p, %dx%d", fbi->par, fbi->var.xres, fbi->var.yres); - DBG("allocated %dx%d fb", fb->width, fb->height); + DBG("allocated %dx%d fb", buffer->fb->width, buffer->fb->height); + + /* The handle is only needed for creating the framebuffer. */ + drm_gem_handle_delete(file, handle); + + /* The framebuffer still holds a reference on the GEM object. */ + drm_gem_object_put(bo); return 0; -err_drm_framebuffer_remove: - drm_framebuffer_remove(fb); +err_msm_gem_unpin: + msm_gem_unpin_iova(bo, priv->kms->vm); + msm_gem_vma_put(bo); +err_drm_client_buffer_delete: + drm_client_buffer_delete(buffer); +err_drm_gem_handle_delete: + drm_gem_handle_delete(file, handle); err_drm_gem_object_put: drm_gem_object_put(bo); return ret; From 1a2bfb1cf7dff01646657606f5a8bddb8b3ea868 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Thu, 18 Jun 2026 16:11:37 +0200 Subject: [PATCH 0397/1328] drm/msm: Make msm_framebuffer_init() an internal interface again The only caller of msm_framebuffer_init() is msm_framebuffer_create() from the same source file. Declare the former as static. Signed-off-by: Thomas Zimmermann Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/733889/ Link: https://lore.kernel.org/r/20260618141249.151338-7-tzimmermann@suse.de Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/msm_drv.h | 4 ---- drivers/gpu/drm/msm/msm_fb.c | 8 ++++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h index b21f8971dfa1..2a3ce6afca1c 100644 --- a/drivers/gpu/drm/msm/msm_drv.h +++ b/drivers/gpu/drm/msm/msm_drv.h @@ -258,10 +258,6 @@ void msm_framebuffer_cleanup(struct drm_framebuffer *fb, bool needed_dirtyfb); uint32_t msm_framebuffer_iova(struct drm_framebuffer *fb, int plane); struct drm_gem_object *msm_framebuffer_bo(struct drm_framebuffer *fb, int plane); const struct msm_format *msm_framebuffer_format(struct drm_framebuffer *fb); -struct drm_framebuffer *msm_framebuffer_init(struct drm_device *dev, - const struct drm_format_info *info, - const struct drm_mode_fb_cmd2 *mode_cmd, - struct drm_gem_object **bos); struct drm_framebuffer *msm_framebuffer_create(struct drm_device *dev, struct drm_file *file, const struct drm_format_info *info, const struct drm_mode_fb_cmd2 *mode_cmd); diff --git a/drivers/gpu/drm/msm/msm_fb.c b/drivers/gpu/drm/msm/msm_fb.c index dec550e6cf75..60c108d35d2a 100644 --- a/drivers/gpu/drm/msm/msm_fb.c +++ b/drivers/gpu/drm/msm/msm_fb.c @@ -134,10 +134,10 @@ const struct msm_format *msm_framebuffer_format(struct drm_framebuffer *fb) return msm_fb->format; } -struct drm_framebuffer *msm_framebuffer_init(struct drm_device *dev, - const struct drm_format_info *info, - const struct drm_mode_fb_cmd2 *mode_cmd, - struct drm_gem_object **bos) +static struct drm_framebuffer * +msm_framebuffer_init(struct drm_device *dev, const struct drm_format_info *info, + const struct drm_mode_fb_cmd2 *mode_cmd, + struct drm_gem_object **bos) { struct msm_drm_private *priv = dev->dev_private; struct msm_kms *kms = priv->kms; From eecba818a962b74f569cef5f388f41d7a889122f Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 2 Jul 2026 11:36:40 +0200 Subject: [PATCH 0398/1328] drm/msm/hdmi_bridge: Simplify register bit updates Simplify reister updates (read, apply mask, write) with a wrapper to make code more obvious and avoid possible errors of reading and writing to different registers. Reviewed-by: Dmitry Baryshkov Signed-off-by: Krzysztof Kozlowski Patchwork: https://patchwork.freedesktop.org/patch/737201/ Link: https://lore.kernel.org/r/20260702-drm-msm-hdmi-cleanup-v2-1-a4a4f0e8895b@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/hdmi/hdmi.h | 19 +++++ drivers/gpu/drm/msm/hdmi/hdmi_bridge.c | 98 +++++++++++--------------- 2 files changed, 60 insertions(+), 57 deletions(-) diff --git a/drivers/gpu/drm/msm/hdmi/hdmi.h b/drivers/gpu/drm/msm/hdmi/hdmi.h index 49433f7727c3..436d4f9fe346 100644 --- a/drivers/gpu/drm/msm/hdmi/hdmi.h +++ b/drivers/gpu/drm/msm/hdmi/hdmi.h @@ -112,6 +112,25 @@ static inline u32 hdmi_read(struct hdmi *hdmi, u32 reg) return readl(hdmi->mmio + reg); } +static inline void hdmi_clear_bits(struct hdmi *hdmi, u32 reg, u32 mask) +{ + u32 val; + + val = hdmi_read(hdmi, reg); + val &= ~mask; + hdmi_write(hdmi, reg, val); +} + +static inline void hdmi_update_bits(struct hdmi *hdmi, u32 reg, u32 mask, u32 data) +{ + u32 val; + + val = hdmi_read(hdmi, reg); + val &= ~mask; + val |= data & mask; + hdmi_write(hdmi, reg, val); +} + static inline u32 hdmi_qfprom_read(struct hdmi *hdmi, u32 reg) { return readl(hdmi->qfprom_mmio + reg); diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c b/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c index 7abb9243dba5..157f19bd90b4 100644 --- a/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c +++ b/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c @@ -58,16 +58,13 @@ static int msm_hdmi_bridge_clear_avi_infoframe(struct drm_bridge *bridge) { struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge); struct hdmi *hdmi = hdmi_bridge->hdmi; - u32 val; - val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL0); - val &= ~(HDMI_INFOFRAME_CTRL0_AVI_SEND | - HDMI_INFOFRAME_CTRL0_AVI_CONT); - hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL0, val); + hdmi_clear_bits(hdmi, REG_HDMI_INFOFRAME_CTRL0, + HDMI_INFOFRAME_CTRL0_AVI_SEND | + HDMI_INFOFRAME_CTRL0_AVI_CONT); - val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL1); - val &= ~HDMI_INFOFRAME_CTRL1_AVI_INFO_LINE__MASK; - hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL1, val); + hdmi_clear_bits(hdmi, REG_HDMI_INFOFRAME_CTRL1, + HDMI_INFOFRAME_CTRL1_AVI_INFO_LINE__MASK); return 0; } @@ -76,18 +73,15 @@ static int msm_hdmi_bridge_clear_audio_infoframe(struct drm_bridge *bridge) { struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge); struct hdmi *hdmi = hdmi_bridge->hdmi; - u32 val; - val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL0); - val &= ~(HDMI_INFOFRAME_CTRL0_AUDIO_INFO_SEND | - HDMI_INFOFRAME_CTRL0_AUDIO_INFO_CONT | - HDMI_INFOFRAME_CTRL0_AUDIO_INFO_SOURCE | - HDMI_INFOFRAME_CTRL0_AUDIO_INFO_UPDATE); - hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL0, val); + hdmi_clear_bits(hdmi, REG_HDMI_INFOFRAME_CTRL0, + HDMI_INFOFRAME_CTRL0_AUDIO_INFO_SEND | + HDMI_INFOFRAME_CTRL0_AUDIO_INFO_CONT | + HDMI_INFOFRAME_CTRL0_AUDIO_INFO_SOURCE | + HDMI_INFOFRAME_CTRL0_AUDIO_INFO_UPDATE); - val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL1); - val &= ~HDMI_INFOFRAME_CTRL1_AUDIO_INFO_LINE__MASK; - hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL1, val); + hdmi_clear_bits(hdmi, REG_HDMI_INFOFRAME_CTRL1, + HDMI_INFOFRAME_CTRL1_AUDIO_INFO_LINE__MASK); return 0; } @@ -96,13 +90,11 @@ static int msm_hdmi_bridge_clear_spd_infoframe(struct drm_bridge *bridge) { struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge); struct hdmi *hdmi = hdmi_bridge->hdmi; - u32 val; - val = hdmi_read(hdmi, REG_HDMI_GEN_PKT_CTRL); - val &= ~(HDMI_GEN_PKT_CTRL_GENERIC1_SEND | - HDMI_GEN_PKT_CTRL_GENERIC1_CONT | - HDMI_GEN_PKT_CTRL_GENERIC1_LINE__MASK); - hdmi_write(hdmi, REG_HDMI_GEN_PKT_CTRL, val); + hdmi_clear_bits(hdmi, REG_HDMI_GEN_PKT_CTRL, + HDMI_GEN_PKT_CTRL_GENERIC1_SEND | + HDMI_GEN_PKT_CTRL_GENERIC1_CONT | + HDMI_GEN_PKT_CTRL_GENERIC1_LINE__MASK); return 0; } @@ -111,14 +103,12 @@ static int msm_hdmi_bridge_clear_hdmi_infoframe(struct drm_bridge *bridge) { struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge); struct hdmi *hdmi = hdmi_bridge->hdmi; - u32 val; - val = hdmi_read(hdmi, REG_HDMI_GEN_PKT_CTRL); - val &= ~(HDMI_GEN_PKT_CTRL_GENERIC0_SEND | - HDMI_GEN_PKT_CTRL_GENERIC0_CONT | - HDMI_GEN_PKT_CTRL_GENERIC0_UPDATE | - HDMI_GEN_PKT_CTRL_GENERIC0_LINE__MASK); - hdmi_write(hdmi, REG_HDMI_GEN_PKT_CTRL, val); + hdmi_clear_bits(hdmi, REG_HDMI_GEN_PKT_CTRL, + HDMI_GEN_PKT_CTRL_GENERIC0_SEND | + HDMI_GEN_PKT_CTRL_GENERIC0_CONT | + HDMI_GEN_PKT_CTRL_GENERIC0_UPDATE | + HDMI_GEN_PKT_CTRL_GENERIC0_LINE__MASK); return 0; } @@ -129,7 +119,6 @@ static int msm_hdmi_bridge_write_avi_infoframe(struct drm_bridge *bridge, struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge); struct hdmi *hdmi = hdmi_bridge->hdmi; u32 buf[4] = {}; - u32 val; int i; if (len != HDMI_INFOFRAME_SIZE(AVI) || len - 3 > sizeof(buf)) { @@ -153,15 +142,13 @@ static int msm_hdmi_bridge_write_avi_infoframe(struct drm_bridge *bridge, for (i = 0; i < ARRAY_SIZE(buf); i++) hdmi_write(hdmi, REG_HDMI_AVI_INFO(i), buf[i]); - val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL0); - val |= HDMI_INFOFRAME_CTRL0_AVI_SEND | - HDMI_INFOFRAME_CTRL0_AVI_CONT; - hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL0, val); + hdmi_update_bits(hdmi, REG_HDMI_INFOFRAME_CTRL0, + HDMI_INFOFRAME_CTRL0_AVI_SEND | HDMI_INFOFRAME_CTRL0_AVI_CONT, + HDMI_INFOFRAME_CTRL0_AVI_SEND | HDMI_INFOFRAME_CTRL0_AVI_CONT); - val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL1); - val &= ~HDMI_INFOFRAME_CTRL1_AVI_INFO_LINE__MASK; - val |= HDMI_INFOFRAME_CTRL1_AVI_INFO_LINE(AVI_IFRAME_LINE_NUMBER); - hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL1, val); + hdmi_update_bits(hdmi, REG_HDMI_INFOFRAME_CTRL1, + HDMI_INFOFRAME_CTRL1_AVI_INFO_LINE__MASK, + HDMI_INFOFRAME_CTRL1_AVI_INFO_LINE(AVI_IFRAME_LINE_NUMBER)); return 0; } @@ -193,12 +180,11 @@ static int msm_hdmi_bridge_write_audio_infoframe(struct drm_bridge *bridge, buffer[9] << 16 | buffer[10] << 24); - val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL0); - val |= HDMI_INFOFRAME_CTRL0_AUDIO_INFO_SEND | - HDMI_INFOFRAME_CTRL0_AUDIO_INFO_CONT | - HDMI_INFOFRAME_CTRL0_AUDIO_INFO_SOURCE | - HDMI_INFOFRAME_CTRL0_AUDIO_INFO_UPDATE; - hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL0, val); + val = HDMI_INFOFRAME_CTRL0_AUDIO_INFO_SEND | + HDMI_INFOFRAME_CTRL0_AUDIO_INFO_CONT | + HDMI_INFOFRAME_CTRL0_AUDIO_INFO_SOURCE | + HDMI_INFOFRAME_CTRL0_AUDIO_INFO_UPDATE; + hdmi_update_bits(hdmi, REG_HDMI_INFOFRAME_CTRL0, val, val); return 0; } @@ -231,11 +217,10 @@ static int msm_hdmi_bridge_write_spd_infoframe(struct drm_bridge *bridge, for (i = 0; i < ARRAY_SIZE(buf); i++) hdmi_write(hdmi, REG_HDMI_GENERIC1(i), buf[i]); - val = hdmi_read(hdmi, REG_HDMI_GEN_PKT_CTRL); - val |= HDMI_GEN_PKT_CTRL_GENERIC1_SEND | - HDMI_GEN_PKT_CTRL_GENERIC1_CONT | - HDMI_GEN_PKT_CTRL_GENERIC1_LINE(SPD_IFRAME_LINE_NUMBER); - hdmi_write(hdmi, REG_HDMI_GEN_PKT_CTRL, val); + val = HDMI_GEN_PKT_CTRL_GENERIC1_SEND | + HDMI_GEN_PKT_CTRL_GENERIC1_CONT | + HDMI_GEN_PKT_CTRL_GENERIC1_LINE(SPD_IFRAME_LINE_NUMBER); + hdmi_update_bits(hdmi, REG_HDMI_GEN_PKT_CTRL, val, val); return 0; } @@ -269,12 +254,11 @@ static int msm_hdmi_bridge_write_hdmi_infoframe(struct drm_bridge *bridge, for (i = 0; i < ARRAY_SIZE(buf); i++) hdmi_write(hdmi, REG_HDMI_GENERIC0(i), buf[i]); - val = hdmi_read(hdmi, REG_HDMI_GEN_PKT_CTRL); - val |= HDMI_GEN_PKT_CTRL_GENERIC0_SEND | - HDMI_GEN_PKT_CTRL_GENERIC0_CONT | - HDMI_GEN_PKT_CTRL_GENERIC0_UPDATE | - HDMI_GEN_PKT_CTRL_GENERIC0_LINE(VENSPEC_IFRAME_LINE_NUMBER); - hdmi_write(hdmi, REG_HDMI_GEN_PKT_CTRL, val); + val = HDMI_GEN_PKT_CTRL_GENERIC0_SEND | + HDMI_GEN_PKT_CTRL_GENERIC0_CONT | + HDMI_GEN_PKT_CTRL_GENERIC0_UPDATE | + HDMI_GEN_PKT_CTRL_GENERIC0_LINE(VENSPEC_IFRAME_LINE_NUMBER); + hdmi_update_bits(hdmi, REG_HDMI_GEN_PKT_CTRL, val, val); return 0; } From c5f6b1abca25ab00f9ad99bfeff5c45b1557ad2c Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 2 Jul 2026 11:36:41 +0200 Subject: [PATCH 0399/1328] drm/msm/hdmi_hdcp: Simplify register bit updates Simplify reister updates (read, apply mask, write) with a wrapper to make code more obvious and avoid possible errors of reading and writing to different registers. Reviewed-by: Dmitry Baryshkov Signed-off-by: Krzysztof Kozlowski Patchwork: https://patchwork.freedesktop.org/patch/737203/ Link: https://lore.kernel.org/r/20260702-drm-msm-hdmi-cleanup-v2-2-a4a4f0e8895b@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/hdmi/hdmi_hdcp.c | 85 +++++++++------------------- 1 file changed, 28 insertions(+), 57 deletions(-) diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_hdcp.c b/drivers/gpu/drm/msm/hdmi/hdmi_hdcp.c index 8fb5497aac9f..7862bd67d154 100644 --- a/drivers/gpu/drm/msm/hdmi/hdmi_hdcp.c +++ b/drivers/gpu/drm/msm/hdmi/hdmi_hdcp.c @@ -306,9 +306,9 @@ static int msm_reset_hdcp_ddc_failures(struct hdmi_hdcp_ctrl *hdcp_ctrl) HDMI_HDCP_DDC_CTRL_0_DISABLE); /* ACK the Failure to Clear it */ - reg_val = hdmi_read(hdmi, REG_HDMI_HDCP_DDC_CTRL_1); - reg_val |= HDMI_HDCP_DDC_CTRL_1_FAILED_ACK; - hdmi_write(hdmi, REG_HDMI_HDCP_DDC_CTRL_1, reg_val); + hdmi_update_bits(hdmi, REG_HDMI_HDCP_DDC_CTRL_1, + HDMI_HDCP_DDC_CTRL_1_FAILED_ACK, + HDMI_HDCP_DDC_CTRL_1_FAILED_ACK); /* Check if the FAILURE got Cleared */ reg_val = hdmi_read(hdmi, REG_HDMI_HDCP_DDC_STATUS); @@ -324,28 +324,22 @@ static int msm_reset_hdcp_ddc_failures(struct hdmi_hdcp_ctrl *hdcp_ctrl) DBG("Before: HDMI_DDC_SW_STATUS=0x%08x", hdmi_read(hdmi, REG_HDMI_DDC_SW_STATUS)); /* Reset HDMI DDC software status */ - reg_val = hdmi_read(hdmi, REG_HDMI_DDC_CTRL); - reg_val |= HDMI_DDC_CTRL_SW_STATUS_RESET; - hdmi_write(hdmi, REG_HDMI_DDC_CTRL, reg_val); + hdmi_update_bits(hdmi, REG_HDMI_DDC_CTRL, HDMI_DDC_CTRL_SW_STATUS_RESET, + HDMI_DDC_CTRL_SW_STATUS_RESET); rc = msm_hdmi_hdcp_msleep(hdcp_ctrl, 20, AUTH_ABORT_EV); - reg_val = hdmi_read(hdmi, REG_HDMI_DDC_CTRL); - reg_val &= ~HDMI_DDC_CTRL_SW_STATUS_RESET; - hdmi_write(hdmi, REG_HDMI_DDC_CTRL, reg_val); + hdmi_clear_bits(hdmi, REG_HDMI_DDC_CTRL, HDMI_DDC_CTRL_SW_STATUS_RESET); /* Reset HDMI DDC Controller */ - reg_val = hdmi_read(hdmi, REG_HDMI_DDC_CTRL); - reg_val |= HDMI_DDC_CTRL_SOFT_RESET; - hdmi_write(hdmi, REG_HDMI_DDC_CTRL, reg_val); + hdmi_update_bits(hdmi, REG_HDMI_DDC_CTRL, HDMI_DDC_CTRL_SOFT_RESET, + HDMI_DDC_CTRL_SOFT_RESET); /* If previous msleep is aborted, skip this msleep */ if (!rc) rc = msm_hdmi_hdcp_msleep(hdcp_ctrl, 20, AUTH_ABORT_EV); - reg_val = hdmi_read(hdmi, REG_HDMI_DDC_CTRL); - reg_val &= ~HDMI_DDC_CTRL_SOFT_RESET; - hdmi_write(hdmi, REG_HDMI_DDC_CTRL, reg_val); + hdmi_clear_bits(hdmi, REG_HDMI_DDC_CTRL, HDMI_DDC_CTRL_SOFT_RESET); DBG("After: HDMI_DDC_SW_STATUS=0x%08x", hdmi_read(hdmi, REG_HDMI_DDC_SW_STATUS)); } @@ -399,7 +393,6 @@ static void msm_hdmi_hdcp_reauth_work(struct work_struct *work) struct hdmi_hdcp_ctrl, hdcp_reauth_work); struct hdmi *hdmi = hdcp_ctrl->hdmi; unsigned long flags; - u32 reg_val; DBG("HDCP REAUTH WORK"); /* @@ -409,9 +402,7 @@ static void msm_hdmi_hdcp_reauth_work(struct work_struct *work) * AN1_READY bits in HDMI_HDCP_LINK0_STATUS register */ spin_lock_irqsave(&hdmi->reg_lock, flags); - reg_val = hdmi_read(hdmi, REG_HDMI_HPD_CTRL); - reg_val &= ~HDMI_HPD_CTRL_ENABLE; - hdmi_write(hdmi, REG_HDMI_HPD_CTRL, reg_val); + hdmi_clear_bits(hdmi, REG_HDMI_HPD_CTRL, HDMI_HPD_CTRL_ENABLE); /* Disable HDCP interrupts */ hdmi_write(hdmi, REG_HDMI_HDCP_INT_CTRL, 0); @@ -431,9 +422,8 @@ static void msm_hdmi_hdcp_reauth_work(struct work_struct *work) /* Enable HPD circuitry */ spin_lock_irqsave(&hdmi->reg_lock, flags); - reg_val = hdmi_read(hdmi, REG_HDMI_HPD_CTRL); - reg_val |= HDMI_HPD_CTRL_ENABLE; - hdmi_write(hdmi, REG_HDMI_HPD_CTRL, reg_val); + hdmi_update_bits(hdmi, REG_HDMI_HPD_CTRL, HDMI_HPD_CTRL_ENABLE, + HDMI_HPD_CTRL_ENABLE); spin_unlock_irqrestore(&hdmi->reg_lock, flags); /* @@ -456,7 +446,6 @@ static int msm_hdmi_hdcp_auth_prepare(struct hdmi_hdcp_ctrl *hdcp_ctrl) { struct hdmi *hdmi = hdcp_ctrl->hdmi; u32 link0_status; - u32 reg_val; unsigned long flags; int rc; @@ -472,14 +461,11 @@ static int msm_hdmi_hdcp_auth_prepare(struct hdmi_hdcp_ctrl *hdcp_ctrl) spin_lock_irqsave(&hdmi->reg_lock, flags); /* disable HDMI Encrypt */ - reg_val = hdmi_read(hdmi, REG_HDMI_CTRL); - reg_val &= ~HDMI_CTRL_ENCRYPTED; - hdmi_write(hdmi, REG_HDMI_CTRL, reg_val); + hdmi_clear_bits(hdmi, REG_HDMI_CTRL, HDMI_CTRL_ENCRYPTED); /* Enabling Software DDC */ - reg_val = hdmi_read(hdmi, REG_HDMI_DDC_ARBITRATION); - reg_val &= ~HDMI_DDC_ARBITRATION_HW_ARBITRATION; - hdmi_write(hdmi, REG_HDMI_DDC_ARBITRATION, reg_val); + hdmi_clear_bits(hdmi, REG_HDMI_DDC_ARBITRATION, + HDMI_DDC_ARBITRATION_HW_ARBITRATION); spin_unlock_irqrestore(&hdmi->reg_lock, flags); /* @@ -498,9 +484,8 @@ static int msm_hdmi_hdcp_auth_prepare(struct hdmi_hdcp_ctrl *hdcp_ctrl) hdmi_write(hdmi, REG_HDMI_HDCP_ENTROPY_CTRL1, 0xF00DFACE); /* Disable the RngCipher state */ - reg_val = hdmi_read(hdmi, REG_HDMI_HDCP_DEBUG_CTRL); - reg_val &= ~HDMI_HDCP_DEBUG_CTRL_RNG_CIPHER; - hdmi_write(hdmi, REG_HDMI_HDCP_DEBUG_CTRL, reg_val); + hdmi_clear_bits(hdmi, REG_HDMI_HDCP_DEBUG_CTRL, + HDMI_HDCP_DEBUG_CTRL_RNG_CIPHER); DBG("HDCP_DEBUG_CTRL=0x%08x", hdmi_read(hdmi, REG_HDMI_HDCP_DEBUG_CTRL)); @@ -537,15 +522,12 @@ static int msm_hdmi_hdcp_auth_prepare(struct hdmi_hdcp_ctrl *hdcp_ctrl) static void msm_hdmi_hdcp_auth_fail(struct hdmi_hdcp_ctrl *hdcp_ctrl) { struct hdmi *hdmi = hdcp_ctrl->hdmi; - u32 reg_val; unsigned long flags; DBG("hdcp auth failed, queue reauth work"); /* clear HDMI Encrypt */ spin_lock_irqsave(&hdmi->reg_lock, flags); - reg_val = hdmi_read(hdmi, REG_HDMI_CTRL); - reg_val &= ~HDMI_CTRL_ENCRYPTED; - hdmi_write(hdmi, REG_HDMI_CTRL, reg_val); + hdmi_clear_bits(hdmi, REG_HDMI_CTRL, HDMI_CTRL_ENCRYPTED); spin_unlock_irqrestore(&hdmi->reg_lock, flags); hdcp_ctrl->hdcp_state = HDCP_STATE_AUTH_FAILED; @@ -555,7 +537,6 @@ static void msm_hdmi_hdcp_auth_fail(struct hdmi_hdcp_ctrl *hdcp_ctrl) static void msm_hdmi_hdcp_auth_done(struct hdmi_hdcp_ctrl *hdcp_ctrl) { struct hdmi *hdmi = hdcp_ctrl->hdmi; - u32 reg_val; unsigned long flags; /* @@ -563,16 +544,15 @@ static void msm_hdmi_hdcp_auth_done(struct hdmi_hdcp_ctrl *hdcp_ctrl) * there is no Arbitration between software and hardware for DDC */ spin_lock_irqsave(&hdmi->reg_lock, flags); - reg_val = hdmi_read(hdmi, REG_HDMI_DDC_ARBITRATION); - reg_val |= HDMI_DDC_ARBITRATION_HW_ARBITRATION; - hdmi_write(hdmi, REG_HDMI_DDC_ARBITRATION, reg_val); + hdmi_update_bits(hdmi, REG_HDMI_DDC_ARBITRATION, + HDMI_DDC_ARBITRATION_HW_ARBITRATION, + HDMI_DDC_ARBITRATION_HW_ARBITRATION); spin_unlock_irqrestore(&hdmi->reg_lock, flags); /* enable HDMI Encrypt */ spin_lock_irqsave(&hdmi->reg_lock, flags); - reg_val = hdmi_read(hdmi, REG_HDMI_CTRL); - reg_val |= HDMI_CTRL_ENCRYPTED; - hdmi_write(hdmi, REG_HDMI_CTRL, reg_val); + hdmi_update_bits(hdmi, REG_HDMI_CTRL, HDMI_CTRL_ENCRYPTED, + HDMI_CTRL_ENCRYPTED); spin_unlock_irqrestore(&hdmi->reg_lock, flags); hdcp_ctrl->hdcp_state = HDCP_STATE_AUTHENTICATED; @@ -1304,7 +1284,6 @@ static void msm_hdmi_hdcp_auth_work(struct work_struct *work) void msm_hdmi_hdcp_on(struct hdmi_hdcp_ctrl *hdcp_ctrl) { struct hdmi *hdmi = hdcp_ctrl->hdmi; - u32 reg_val; unsigned long flags; if ((HDCP_STATE_INACTIVE != hdcp_ctrl->hdcp_state) || @@ -1315,9 +1294,7 @@ void msm_hdmi_hdcp_on(struct hdmi_hdcp_ctrl *hdcp_ctrl) /* clear HDMI Encrypt */ spin_lock_irqsave(&hdmi->reg_lock, flags); - reg_val = hdmi_read(hdmi, REG_HDMI_CTRL); - reg_val &= ~HDMI_CTRL_ENCRYPTED; - hdmi_write(hdmi, REG_HDMI_CTRL, reg_val); + hdmi_clear_bits(hdmi, REG_HDMI_CTRL, HDMI_CTRL_ENCRYPTED); spin_unlock_irqrestore(&hdmi->reg_lock, flags); hdcp_ctrl->auth_event = 0; @@ -1330,7 +1307,6 @@ void msm_hdmi_hdcp_off(struct hdmi_hdcp_ctrl *hdcp_ctrl) { struct hdmi *hdmi = hdcp_ctrl->hdmi; unsigned long flags; - u32 reg_val; if ((HDCP_STATE_INACTIVE == hdcp_ctrl->hdcp_state) || (HDCP_STATE_NO_AKSV == hdcp_ctrl->hdcp_state)) { @@ -1345,9 +1321,7 @@ void msm_hdmi_hdcp_off(struct hdmi_hdcp_ctrl *hdcp_ctrl) * AN1_READY bits in HDMI_HDCP_LINK0_STATUS register */ spin_lock_irqsave(&hdmi->reg_lock, flags); - reg_val = hdmi_read(hdmi, REG_HDMI_HPD_CTRL); - reg_val &= ~HDMI_HPD_CTRL_ENABLE; - hdmi_write(hdmi, REG_HDMI_HPD_CTRL, reg_val); + hdmi_clear_bits(hdmi, REG_HDMI_HPD_CTRL, HDMI_HPD_CTRL_ENABLE); /* * Disable HDCP interrupts. @@ -1375,14 +1349,11 @@ void msm_hdmi_hdcp_off(struct hdmi_hdcp_ctrl *hdcp_ctrl) hdmi_write(hdmi, REG_HDMI_HDCP_CTRL, 0); spin_lock_irqsave(&hdmi->reg_lock, flags); - reg_val = hdmi_read(hdmi, REG_HDMI_CTRL); - reg_val &= ~HDMI_CTRL_ENCRYPTED; - hdmi_write(hdmi, REG_HDMI_CTRL, reg_val); + hdmi_clear_bits(hdmi, REG_HDMI_CTRL, HDMI_CTRL_ENCRYPTED); /* Enable HPD circuitry */ - reg_val = hdmi_read(hdmi, REG_HDMI_HPD_CTRL); - reg_val |= HDMI_HPD_CTRL_ENABLE; - hdmi_write(hdmi, REG_HDMI_HPD_CTRL, reg_val); + hdmi_update_bits(hdmi, REG_HDMI_HPD_CTRL, HDMI_HPD_CTRL_ENABLE, + HDMI_HPD_CTRL_ENABLE); spin_unlock_irqrestore(&hdmi->reg_lock, flags); hdcp_ctrl->hdcp_state = HDCP_STATE_INACTIVE; From 8834f5494ab5e169b1c0fb4cbcb5464fd81d1ed0 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Mon, 20 Jul 2026 15:56:08 +0800 Subject: [PATCH 0400/1328] drm/msm/dpu: Fix DMA SSPP REC block offsets on DPU v13 On DPU v13, the DMA SSPP REC0 and REC1 blocks are located at offsets 0x1000 and 0x3000 from the SSPP common base. The existing DMA SSPP sub-block descriptor does not initialize sspp_rec0_blk and sspp_rec1_blk, causing REC register accesses to be performed at offset 0 instead of the corresponding REC block. As a result, DMA SSPP pipes are not programmed correctly and fail to produce output. Introduce a DPU v13 specific DMA SSPP descriptor with the correct REC block offsets and use it for all DMA SSPPs in the Kaanapali catalog. Signed-off-by: Yongxing Mou Fixes: 83fe2cd56b1d ("drm/msm/dpu: Add support for Kaanapali DPU") Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/741230/ Link: https://lore.kernel.org/r/20260720-dpu-v13-dma-sspp-rec-fix-v1-1-10d69b4875e7@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- .../drm/msm/disp/dpu1/catalog/dpu_13_0_kaanapali.h | 12 ++++++------ drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.c | 12 ++++++++++++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/msm/disp/dpu1/catalog/dpu_13_0_kaanapali.h b/drivers/gpu/drm/msm/disp/dpu1/catalog/dpu_13_0_kaanapali.h index 06da1583fb1e..85f455a9f29c 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/catalog/dpu_13_0_kaanapali.h +++ b/drivers/gpu/drm/msm/disp/dpu1/catalog/dpu_13_0_kaanapali.h @@ -86,42 +86,42 @@ static const struct dpu_sspp_cfg kaanapali_sspp[] = { .name = "sspp_8", .id = SSPP_DMA0, .base = 0x97000, .len = 0x84, .features = DMA_SDM845_MASK_SDMA, - .sblk = &dpu_dma_sblk, + .sblk = &dpu_dma_sblk_v13, .xin_id = 1, .type = SSPP_TYPE_DMA, }, { .name = "sspp_9", .id = SSPP_DMA1, .base = 0xa0000, .len = 0x84, .features = DMA_SDM845_MASK_SDMA, - .sblk = &dpu_dma_sblk, + .sblk = &dpu_dma_sblk_v13, .xin_id = 5, .type = SSPP_TYPE_DMA, }, { .name = "sspp_10", .id = SSPP_DMA2, .base = 0xa9000, .len = 0x84, .features = DMA_SDM845_MASK_SDMA, - .sblk = &dpu_dma_sblk, + .sblk = &dpu_dma_sblk_v13, .xin_id = 9, .type = SSPP_TYPE_DMA, }, { .name = "sspp_11", .id = SSPP_DMA3, .base = 0xb2000, .len = 0x84, .features = DMA_SDM845_MASK_SDMA, - .sblk = &dpu_dma_sblk, + .sblk = &dpu_dma_sblk_v13, .xin_id = 13, .type = SSPP_TYPE_DMA, }, { .name = "sspp_12", .id = SSPP_DMA4, .base = 0xbb000, .len = 0x84, .features = DMA_CURSOR_SDM845_MASK_SDMA, - .sblk = &dpu_dma_sblk, + .sblk = &dpu_dma_sblk_v13, .xin_id = 14, .type = SSPP_TYPE_DMA, }, { .name = "sspp_13", .id = SSPP_DMA5, .base = 0xc4000, .len = 0x84, .features = DMA_CURSOR_SDM845_MASK_SDMA, - .sblk = &dpu_dma_sblk, + .sblk = &dpu_dma_sblk_v13, .xin_id = 15, .type = SSPP_TYPE_DMA, }, diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.c index 2e10add84fd7..9a993cdfab85 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.c +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.c @@ -303,6 +303,16 @@ static const u32 wb2_formats_rgb_yuv[] = { .num_formats = ARRAY_SIZE(plane_formats), \ } +#define _DMA_SBLK_V13() \ + { \ + .sspp_rec0_blk = {.name = "sspp_rec0", \ + .base = 0x1000, .len = 0x180,}, \ + .sspp_rec1_blk = {.name = "sspp_rec1", \ + .base = 0x3000, .len = 0x180,}, \ + .format_list = plane_formats, \ + .num_formats = ARRAY_SIZE(plane_formats), \ + } + static const struct dpu_rotation_cfg dpu_rot_sc7280_cfg_v2 = { .rot_maxheight = 1088, .rot_num_formats = ARRAY_SIZE(rotation_v2_formats), @@ -353,6 +363,8 @@ static const struct dpu_sspp_sub_blks dpu_rgb_sblk = _RGB_SBLK(); static const struct dpu_sspp_sub_blks dpu_dma_sblk = _DMA_SBLK(); +static const struct dpu_sspp_sub_blks dpu_dma_sblk_v13 = _DMA_SBLK_V13(); + /************************************************************* * MIXER sub blocks config *************************************************************/ From 93c125e4ea98fb25f927ba5a334d85845127d667 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Thu, 23 Jul 2026 12:52:12 +0300 Subject: [PATCH 0401/1328] drm/msm: don't tear down KMS twice when KMS init fails When priv->kms_init() (mdp4_kms_init() / mdp5_kms_init()) fails partway through, both display drivers already tear their KMS state down via mdp4_destroy() / mdp5_kms_destroy() before returning the error. The common error path in msm_drm_init() then runs msm_drm_uninit() -> msm_drm_kms_uninit(), which tries to destroy the very same KMS a second time, which causes a use-after-free crash. Bring MDP4/MDP5 in line with the DPU driver whose dpu_kms_init() doesn't perform error cleanup on the failure. Let the common path own the cleanup, instead of freeing the KMS from their error paths. The crash trace for the reference: __lock_acquire from lock_acquire (kernel/locking/lockdep.c:5906 kernel/locking/lockdep.c:5863) lock_acquire from touch_wq_lockdep_map (kernel/workqueue.c:4094 (discriminator 1)) touch_wq_lockdep_map from __flush_workqueue (kernel/workqueue.c:4136) __flush_workqueue from msm_drm_kms_uninit (drivers/gpu/drm/msm/msm_kms.c:243 (discriminator 33)) msm_drm_kms_uninit from msm_drm_uninit (drivers/gpu/drm/msm/msm_drv.c:93) msm_drm_uninit from msm_drm_init (drivers/gpu/drm/msm/msm_drv.c:184) msm_drm_init from try_to_bring_up_aggregate_device (drivers/base/component.c:249 drivers/base/component.c:227) try_to_bring_up_aggregate_device from __component_add (drivers/base/component.c:269 drivers/base/component.c:748) __component_add from dsi_host_attach (drivers/gpu/drm/msm/dsi/dsi_host.c:1739) dsi_host_attach from mipi_dsi_attach (drivers/gpu/drm/drm_mipi_dsi.c:383) mipi_dsi_attach from sharp_nt_panel_probe (drivers/gpu/drm/panel/panel-sharp-ls043t1le01.c:247) Fixes: 506efcba3129 ("drm/msm: carve out KMS code from msm_drv.c") Signed-off-by: Dmitry Baryshkov Reviewed-by: Konrad Dybcio Patchwork: https://patchwork.freedesktop.org/patch/742068/ Link: https://lore.kernel.org/r/20260723-msm-fix-crash-v1-1-78fb4721c2d9@oss.qualcomm.com --- drivers/gpu/drm/msm/disp/mdp4/mdp4_kms.c | 22 ++++++++-------------- drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c | 11 +++-------- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/drivers/gpu/drm/msm/disp/mdp4/mdp4_kms.c b/drivers/gpu/drm/msm/disp/mdp4/mdp4_kms.c index c289dff78cd5..9b1d1982e683 100644 --- a/drivers/gpu/drm/msm/disp/mdp4/mdp4_kms.c +++ b/drivers/gpu/drm/msm/disp/mdp4/mdp4_kms.c @@ -398,7 +398,7 @@ static int mdp4_kms_init(struct drm_device *dev) ret = mdp_kms_init(&mdp4_kms->base, &kms_funcs); if (ret) { DRM_DEV_ERROR(dev->dev, "failed to init kms\n"); - goto fail; + return ret; } kms = priv->kms; @@ -409,7 +409,7 @@ static int mdp4_kms_init(struct drm_device *dev) ret = regulator_enable(mdp4_kms->vdd); if (ret) { DRM_DEV_ERROR(dev->dev, "failed to enable regulator vdd: %d\n", ret); - goto fail; + return ret; } } @@ -421,7 +421,7 @@ static int mdp4_kms_init(struct drm_device *dev) DRM_DEV_ERROR(dev->dev, "unexpected MDP version: v%d.%d\n", major, minor); ret = -ENXIO; - goto fail; + return ret; } mdp4_kms->rev = minor; @@ -430,7 +430,7 @@ static int mdp4_kms_init(struct drm_device *dev) if (!mdp4_kms->lut_clk) { DRM_DEV_ERROR(dev->dev, "failed to get lut_clk\n"); ret = -ENODEV; - goto fail; + return ret; } clk_set_rate(mdp4_kms->lut_clk, max_clk); } @@ -452,7 +452,7 @@ static int mdp4_kms_init(struct drm_device *dev) vm = msm_kms_init_vm(mdp4_kms->dev, NULL); if (IS_ERR(vm)) { ret = PTR_ERR(vm); - goto fail; + return ret; } kms->vm = vm; @@ -460,7 +460,7 @@ static int mdp4_kms_init(struct drm_device *dev) ret = modeset_init(mdp4_kms); if (ret) { DRM_DEV_ERROR(dev->dev, "modeset_init failed: %d\n", ret); - goto fail; + return ret; } mdp4_kms->blank_cursor_bo = msm_gem_new(dev, SZ_16K, MSM_BO_WC | MSM_BO_SCANOUT, NULL); @@ -468,14 +468,14 @@ static int mdp4_kms_init(struct drm_device *dev) ret = PTR_ERR(mdp4_kms->blank_cursor_bo); DRM_DEV_ERROR(dev->dev, "could not allocate blank-cursor bo: %d\n", ret); mdp4_kms->blank_cursor_bo = NULL; - goto fail; + return ret; } ret = msm_gem_get_and_pin_iova(mdp4_kms->blank_cursor_bo, kms->vm, &mdp4_kms->blank_cursor_iova); if (ret) { DRM_DEV_ERROR(dev->dev, "could not pin blank-cursor bo: %d\n", ret); - goto fail; + return ret; } dev->mode_config.min_width = 0; @@ -484,12 +484,6 @@ static int mdp4_kms_init(struct drm_device *dev) dev->mode_config.max_height = 2048; return 0; - -fail: - if (kms) - mdp4_destroy(kms); - - return ret; } static const struct dev_pm_ops mdp4_pm_ops = { diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c index 0a004ab9fc85..3934cd060b27 100644 --- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c @@ -517,7 +517,7 @@ static int mdp5_kms_init(struct drm_device *dev) ret = mdp_kms_init(&mdp5_kms->base, &kms_funcs); if (ret) { DRM_DEV_ERROR(&pdev->dev, "failed to init kms\n"); - goto fail; + return ret; } config = mdp5_cfg_get_config(mdp5_kms->cfg); @@ -540,7 +540,7 @@ static int mdp5_kms_init(struct drm_device *dev) vm = msm_kms_init_vm(mdp5_kms->dev, pdev->dev.parent); if (IS_ERR(vm)) { ret = PTR_ERR(vm); - goto fail; + return ret; } kms->vm = vm; @@ -550,7 +550,7 @@ static int mdp5_kms_init(struct drm_device *dev) ret = modeset_init(mdp5_kms); if (ret) { DRM_DEV_ERROR(&pdev->dev, "modeset_init failed: %d\n", ret); - goto fail; + return ret; } dev->mode_config.min_width = 0; @@ -562,11 +562,6 @@ static int mdp5_kms_init(struct drm_device *dev) dev->vblank_disable_immediate = true; return 0; -fail: - if (kms) - mdp5_kms_destroy(kms); - - return ret; } static void mdp5_destroy(struct mdp5_kms *mdp5_kms) From 684f95fb4e9ad10aac39fbb1fa7592a59d7f54ea Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Wed, 22 Jul 2026 09:36:16 +0300 Subject: [PATCH 0402/1328] drm/msm/dp: reject YUV420-only modes without VSC SDP support DP conveys YUV 420 colorimetry through a VSC SDP. A sink that advertises a mode as YUV-420-only therefore cannot be driven at all unless the panel supports VSC SDP, yet msm_dp_bridge_mode_valid() only used the VSC SDP capability to decide whether to halve the pixel clock, otherwise letting such modes through to be validated (and possibly accepted) at the full RGB clock the sink cannot display. Reject 420-only modes with MODE_NO_420 when the panel does not support VSC SDP. With those modes filtered out, being a 420-only mode implies VSC SDP support, so the YUV-420 test reduces to drm_mode_is_420_only(): drop msm_dp_is_yuv_420_enabled() and call the DRM helper directly at its two callers (the DPU encoder already has the connector from the atomic state). Fixes: df9cf852ca30 ("drm/msm/dp: account for widebus and yuv420 during mode validation") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/741713/ Link: https://lore.kernel.org/r/20260722-drm-msm-display-interface-v1-1-368c10fe62fd@oss.qualcomm.com --- drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c | 3 +-- drivers/gpu/drm/msm/dp/dp_display.c | 28 +++++++++------------ drivers/gpu/drm/msm/msm_drv.h | 8 ------ 3 files changed, 13 insertions(+), 26 deletions(-) diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c index 778e231d4967..1f20695f81e3 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c @@ -710,8 +710,7 @@ void dpu_encoder_update_topology(struct drm_encoder *drm_enc, if (fb && MSM_FORMAT_IS_YUV(msm_framebuffer_format(fb))) topology->num_cdm++; } else if (disp_info->intf_type == INTF_DP) { - if (msm_dp_is_yuv_420_enabled(priv->kms->dp[disp_info->h_tile_instance[0]], - adj_mode)) + if (drm_mode_is_420_only(&connector->display_info, adj_mode)) topology->num_cdm++; } } diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index dc6f33809ca5..e0c44eef3aba 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -698,6 +698,7 @@ enum drm_mode_status msm_dp_bridge_mode_valid(struct drm_bridge *bridge, u32 mode_rate_khz = 0, supported_rate_khz = 0, mode_bpp = 0; struct msm_dp *dp; int mode_pclk_khz = mode->clock; + bool is_yuv_420; dp = to_dp_bridge(bridge)->msm_dp_display; @@ -709,9 +710,16 @@ enum drm_mode_status msm_dp_bridge_mode_valid(struct drm_bridge *bridge, msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); link_info = &msm_dp_display->panel->link_info; - if ((drm_mode_is_420_only(&dp->connector->display_info, mode) && - msm_dp_display->panel->vsc_sdp_supported) || - msm_dp_wide_bus_available(dp)) + is_yuv_420 = drm_mode_is_420_only(&dp->connector->display_info, mode); + + /* + * YUV 420 is carried over DP by signalling the colorimetry through a + * VSC SDP, so a 420-only mode cannot be driven without VSC SDP support. + */ + if (is_yuv_420 && !msm_dp_display->panel->vsc_sdp_supported) + return MODE_NO_420; + + if (is_yuv_420 || msm_dp_wide_bus_available(dp)) mode_pclk_khz /= 2; if (mode_pclk_khz > DP_MAX_PIXEL_CLK_KHZ) @@ -1277,22 +1285,10 @@ void __exit msm_dp_unregister(void) platform_driver_unregister(&msm_dp_display_driver); } -bool msm_dp_is_yuv_420_enabled(const struct msm_dp *msm_dp_display, - const struct drm_display_mode *mode) -{ - struct msm_dp_display_private *dp; - const struct drm_display_info *info; - - dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); - info = &msm_dp_display->connector->display_info; - - return dp->panel->vsc_sdp_supported && drm_mode_is_420_only(info, mode); -} - bool msm_dp_needs_periph_flush(const struct msm_dp *msm_dp_display, const struct drm_display_mode *mode) { - return msm_dp_is_yuv_420_enabled(msm_dp_display, mode); + return drm_mode_is_420_only(&msm_dp_display->connector->display_info, mode); } bool msm_dp_wide_bus_available(const struct msm_dp *msm_dp_display) diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h index 2a3ce6afca1c..eb4bbae8557b 100644 --- a/drivers/gpu/drm/msm/msm_drv.h +++ b/drivers/gpu/drm/msm/msm_drv.h @@ -354,8 +354,6 @@ void __exit msm_dp_unregister(void); int msm_dp_modeset_init(struct msm_dp *dp_display, struct drm_device *dev, struct drm_encoder *encoder, bool yuv_supported); void msm_dp_snapshot(struct msm_disp_state *disp_state, struct msm_dp *dp_display); -bool msm_dp_is_yuv_420_enabled(const struct msm_dp *dp_display, - const struct drm_display_mode *mode); bool msm_dp_needs_periph_flush(const struct msm_dp *dp_display, const struct drm_display_mode *mode); bool msm_dp_wide_bus_available(const struct msm_dp *dp_display); @@ -380,12 +378,6 @@ static inline void msm_dp_snapshot(struct msm_disp_state *disp_state, struct msm { } -static inline bool msm_dp_is_yuv_420_enabled(const struct msm_dp *dp_display, - const struct drm_display_mode *mode) -{ - return false; -} - static inline bool msm_dp_needs_periph_flush(const struct msm_dp *dp_display, const struct drm_display_mode *mode) { From bd926e62d355879133452bc3889447f8e89757f2 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Wed, 22 Jul 2026 09:36:30 +0300 Subject: [PATCH 0403/1328] drm/msm/dp: do not reject wide-bus modes while a YUV420 mode is active msm_dp_bridge_mode_valid() halves the candidate mode's pixel clock when the sink either uses YUV 420 output or drives the wide bus, so that modes relying on those to stay under DP_MAX_PIXEL_CLK_KHZ are accepted. The wide bus part is queried through msm_dp_wide_bus_available(), which returns false whenever the currently committed mode uses YUV 420 output: it inspects the stored msm_dp_mode.out_fmt_is_yuv_420 of the active mode, not the mode being validated. Consequently, while a YUV 420 mode is active, an RGB mode that needs the wide bus to fit under DP_MAX_PIXEL_CLK_KHZ has its pixel clock left un-halved and is wrongly rejected as MODE_CLOCK_HIGH. The candidate mode's YUV 420 status is already evaluated as is_yuv_420, and the wide bus is disabled precisely for YUV 420 output, so halving the pixel clock for either case is equivalent to halving it when the candidate is YUV 420 or the controller supports the wide bus. Test wide_bus_supported directly, so the decision no longer depends on the format of the active mode. Fixes: df9cf852ca30 ("drm/msm/dp: account for widebus and yuv420 during mode validation") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/741740/ Link: https://lore.kernel.org/r/20260722-drm-msm-display-interface-v1-15-368c10fe62fd@oss.qualcomm.com --- drivers/gpu/drm/msm/dp/dp_display.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index e0c44eef3aba..79e2b171e269 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -719,7 +719,7 @@ enum drm_mode_status msm_dp_bridge_mode_valid(struct drm_bridge *bridge, if (is_yuv_420 && !msm_dp_display->panel->vsc_sdp_supported) return MODE_NO_420; - if (is_yuv_420 || msm_dp_wide_bus_available(dp)) + if (is_yuv_420 || msm_dp_display->wide_bus_supported) mode_pclk_khz /= 2; if (mode_pclk_khz > DP_MAX_PIXEL_CLK_KHZ) From db6f98b9515f09cc345949b1002d6d71c0bef473 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Tue, 28 Jul 2026 18:21:30 +0800 Subject: [PATCH 0404/1328] drm/msm/dp: remove cached drm_edid from panel The cached drm_edid seems unnecessary here. Use the drm_edid pointer directly in the plug stage instead of caching it. Remove the cached drm_edid and the corresponding oneliner to simplify the code. Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/742727/ Link: https://lore.kernel.org/r/20260728-dp_mstclean-v9-1-f7779fce10f4@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_display.c | 28 +++++++++----- drivers/gpu/drm/msm/dp/dp_panel.c | 59 +++-------------------------- drivers/gpu/drm/msm/dp/dp_panel.h | 13 ++----- 3 files changed, 27 insertions(+), 73 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 79e2b171e269..ba3804a12a57 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -269,6 +269,7 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp) const struct drm_display_info *info = &connector->display_info; int rc = 0; u8 dpcd[DP_RECEIVER_CAP_SIZE]; + const struct drm_edid *drm_edid = NULL; rc = drm_dp_read_dpcd_caps(dp->aux, dpcd); if (rc) @@ -276,10 +277,20 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp) dp->link->lttpr_count = msm_dp_display_lttpr_init(dp, dpcd); - rc = msm_dp_panel_read_sink_caps(dp->panel, connector); + rc = msm_dp_panel_read_link_caps(dp->panel, connector); if (rc) goto end; + drm_edid = drm_edid_read_ddc(connector, &dp->aux->ddc); + drm_edid_connector_update(connector, drm_edid); + + if (!drm_edid) { + DRM_ERROR("panel edid read failed\n"); + /* check edid read fail is due to unplug */ + if (!msm_dp_aux_is_link_connected(dp->aux)) + return -ETIMEDOUT; + } + msm_dp_link_process_request(dp->link); if (!dp->msm_dp_display.is_edp) @@ -291,7 +302,7 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp) dp->msm_dp_display.psr_supported = dp->panel->psr_cap.version && psr_enabled; dp->audio_supported = info->has_audio; - msm_dp_panel_handle_sink_request(dp->panel); + msm_dp_panel_handle_sink_request(dp->panel, drm_edid); /* * set sink to normal operation mode -- D0 @@ -302,6 +313,7 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp) msm_dp_link_reset_phy_params_vx_px(dp->link); end: + drm_edid_free(drm_edid); return rc; } @@ -453,7 +465,7 @@ static int msm_dp_hpd_unplug_handle(struct msm_dp_display_private *dp) /* Don't forget modes for eDP */ if (!dp->msm_dp_display.is_edp) - msm_dp_panel_unplugged(dp->panel, dp->msm_dp_display.connector); + drm_edid_connector_update(dp->msm_dp_display.connector, NULL); /* triggered by irq_hdp with sink_count = 0 */ if (dp->link->sink_count == 0) @@ -515,7 +527,6 @@ static int msm_dp_irq_hpd_handle(struct msm_dp_display_private *dp) static void msm_dp_display_deinit_sub_modules(struct msm_dp_display_private *dp) { msm_dp_audio_put(dp->audio); - msm_dp_panel_put(dp->panel); msm_dp_aux_put(dp->aux); } @@ -566,7 +577,7 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp) rc = PTR_ERR(dp->ctrl); DRM_ERROR("failed to initialize ctrl, rc = %d\n", rc); dp->ctrl = NULL; - goto error_ctrl; + goto error_link; } dp->audio = msm_dp_audio_get(dp->msm_dp_display.pdev, dp->link_base); @@ -574,13 +585,11 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp) rc = PTR_ERR(dp->audio); pr_err("failed to initialize audio, rc = %d\n", rc); dp->audio = NULL; - goto error_ctrl; + goto error_link; } return rc; -error_ctrl: - msm_dp_panel_put(dp->panel); error_link: msm_dp_aux_put(dp->aux); error: @@ -752,8 +761,7 @@ int msm_dp_display_get_modes(struct msm_dp *dp) msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); - return msm_dp_panel_get_modes(msm_dp_display->panel, - dp->connector); + return drm_edid_connector_add_modes(msm_dp_display->panel->connector); } bool msm_dp_display_check_video_test(struct msm_dp *dp) diff --git a/drivers/gpu/drm/msm/dp/dp_panel.c b/drivers/gpu/drm/msm/dp/dp_panel.c index 5b4954e7cb74..bde4a772d22c 100644 --- a/drivers/gpu/drm/msm/dp/dp_panel.c +++ b/drivers/gpu/drm/msm/dp/dp_panel.c @@ -232,8 +232,8 @@ static u32 msm_dp_panel_get_supported_bpp(struct msm_dp_panel *msm_dp_panel, return min_supported_bpp; } -int msm_dp_panel_read_sink_caps(struct msm_dp_panel *msm_dp_panel, - struct drm_connector *connector) +int msm_dp_panel_read_link_caps(struct msm_dp_panel *msm_dp_panel, + struct drm_connector *connector) { int rc, bw_code; int count; @@ -271,36 +271,9 @@ int msm_dp_panel_read_sink_caps(struct msm_dp_panel *msm_dp_panel, rc = drm_dp_read_downstream_info(panel->aux, msm_dp_panel->dpcd, msm_dp_panel->downstream_ports); - if (rc) - return rc; - - drm_edid_free(msm_dp_panel->drm_edid); - - msm_dp_panel->drm_edid = drm_edid_read_ddc(connector, &panel->aux->ddc); - - drm_edid_connector_update(connector, msm_dp_panel->drm_edid); - - if (!msm_dp_panel->drm_edid) { - DRM_ERROR("panel edid read failed\n"); - /* check edid read fail is due to unplug */ - if (!msm_dp_aux_is_link_connected(panel->aux)) { - rc = -ETIMEDOUT; - goto end; - } - } - -end: return rc; } -void msm_dp_panel_unplugged(struct msm_dp_panel *msm_dp_panel, - struct drm_connector *connector) -{ - drm_edid_connector_update(connector, NULL); - drm_edid_free(msm_dp_panel->drm_edid); - msm_dp_panel->drm_edid = NULL; -} - u32 msm_dp_panel_get_mode_bpp(struct msm_dp_panel *msm_dp_panel, u32 mode_edid_bpp, u32 mode_pclk_khz) { @@ -324,22 +297,6 @@ u32 msm_dp_panel_get_mode_bpp(struct msm_dp_panel *msm_dp_panel, return bpp; } -int msm_dp_panel_get_modes(struct msm_dp_panel *msm_dp_panel, - struct drm_connector *connector) -{ - if (!msm_dp_panel) { - DRM_ERROR("invalid input\n"); - return -EINVAL; - } - - if (msm_dp_panel->drm_edid) { - drm_edid_connector_update(connector, msm_dp_panel->drm_edid); - return drm_edid_connector_add_modes(connector); - } - - return 0; -} - static u8 msm_dp_panel_get_edid_checksum(const struct edid *edid) { edid += edid->extensions; @@ -347,7 +304,8 @@ static u8 msm_dp_panel_get_edid_checksum(const struct edid *edid) return edid->checksum; } -void msm_dp_panel_handle_sink_request(struct msm_dp_panel *msm_dp_panel) +void msm_dp_panel_handle_sink_request(struct msm_dp_panel *msm_dp_panel, + const struct drm_edid *drm_edid) { struct msm_dp_panel_private *panel; @@ -360,7 +318,7 @@ void msm_dp_panel_handle_sink_request(struct msm_dp_panel *msm_dp_panel) if (panel->link->sink_request & DP_TEST_LINK_EDID_READ) { /* FIXME: get rid of drm_edid_raw() */ - const struct edid *edid = drm_edid_raw(msm_dp_panel->drm_edid); + const struct edid *edid = drm_edid_raw(drm_edid); u8 checksum; if (edid) @@ -757,10 +715,3 @@ struct msm_dp_panel *msm_dp_panel_get(struct device *dev, struct drm_dp_aux *aux return msm_dp_panel; } -void msm_dp_panel_put(struct msm_dp_panel *msm_dp_panel) -{ - if (!msm_dp_panel) - return; - - drm_edid_free(msm_dp_panel->drm_edid); -} diff --git a/drivers/gpu/drm/msm/dp/dp_panel.h b/drivers/gpu/drm/msm/dp/dp_panel.h index 9173e90a5053..53b7b4463551 100644 --- a/drivers/gpu/drm/msm/dp/dp_panel.h +++ b/drivers/gpu/drm/msm/dp/dp_panel.h @@ -33,7 +33,6 @@ struct msm_dp_panel { u8 downstream_ports[DP_MAX_DOWNSTREAM_PORTS]; struct msm_dp_link_info link_info; - const struct drm_edid *drm_edid; struct drm_connector *connector; struct msm_dp_display_mode msm_dp_mode; struct msm_dp_panel_psr psr_cap; @@ -47,15 +46,12 @@ struct msm_dp_panel { int msm_dp_panel_init_panel_info(struct msm_dp_panel *msm_dp_panel); int msm_dp_panel_deinit(struct msm_dp_panel *msm_dp_panel); int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en); -int msm_dp_panel_read_sink_caps(struct msm_dp_panel *msm_dp_panel, - struct drm_connector *connector); -void msm_dp_panel_unplugged(struct msm_dp_panel *msm_dp_panel, - struct drm_connector *connector); +int msm_dp_panel_read_link_caps(struct msm_dp_panel *msm_dp_panel, + struct drm_connector *connector); u32 msm_dp_panel_get_mode_bpp(struct msm_dp_panel *msm_dp_panel, u32 mode_max_bpp, u32 mode_pclk_khz); -int msm_dp_panel_get_modes(struct msm_dp_panel *msm_dp_panel, - struct drm_connector *connector); -void msm_dp_panel_handle_sink_request(struct msm_dp_panel *msm_dp_panel); +void msm_dp_panel_handle_sink_request(struct msm_dp_panel *msm_dp_panel, + const struct drm_edid *drm_edid); void msm_dp_panel_tpg_config(struct msm_dp_panel *msm_dp_panel, bool enable); void msm_dp_panel_clear_dsc_dto(struct msm_dp_panel *msm_dp_panel); @@ -94,5 +90,4 @@ struct msm_dp_panel *msm_dp_panel_get(struct device *dev, struct drm_dp_aux *aux struct msm_dp_link *link, void __iomem *link_base, void __iomem *p0_base); -void msm_dp_panel_put(struct msm_dp_panel *msm_dp_panel); #endif /* _DP_PANEL_H_ */ From cd07d635884018b25573617fdd20924b0bf0af28 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Tue, 28 Jul 2026 18:21:31 +0800 Subject: [PATCH 0405/1328] drm/msm/dp: drop deprecated .mode_set() and use .atomic_pre_enable The bridge .mode_set() callback is deprecated. Remove it and move the mode setup logic to .atomic_pre_enable(), where the adjusted_mode is available from the atomic CRTC state. .atomic_pre_enable() is used rather than .atomic_enable() because the DPU encoder's .atomic_enable() reads the output mode's YUV420 / wide bus state through the msm_display callbacks, and it runs after all bridges' .atomic_pre_enable() but before their .atomic_enable(). Programming the mode from the DP bridge's .atomic_enable() would leave the encoder reading the previously committed mode's state. Drop msm_dp_mode from msm_dp_display_private and store the mode directly in the panel, as it was only used as a temporary cache. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Assisted-by: Claude:claude-opus-4-8 [DB: moved to atomic_pre_enable] Patchwork: https://patchwork.freedesktop.org/patch/742729/ Link: https://lore.kernel.org/r/20260728-dp_mstclean-v9-2-f7779fce10f4@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_display.c | 103 +++++++++++++--------------- drivers/gpu/drm/msm/dp/dp_drm.c | 4 +- drivers/gpu/drm/msm/dp/dp_drm.h | 5 +- 3 files changed, 51 insertions(+), 61 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index ba3804a12a57..3ff7360a6833 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -63,7 +63,6 @@ struct msm_dp_display_private { struct msm_dp_panel *panel; struct msm_dp_ctrl *ctrl; - struct msm_dp_display_mode msm_dp_mode; struct msm_dp msm_dp_display; /* wait for audio signaling */ @@ -597,16 +596,33 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp) } static int msm_dp_display_set_mode(struct msm_dp *msm_dp_display, - struct msm_dp_display_mode *mode) + const struct drm_display_mode *adjusted_mode, + struct msm_dp_panel *msm_dp_panel) { struct msm_dp_display_private *dp; + u32 bpp; dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); - drm_mode_copy(&dp->panel->msm_dp_mode.drm_mode, &mode->drm_mode); - dp->panel->msm_dp_mode.bpp = mode->bpp; - dp->panel->msm_dp_mode.out_fmt_is_yuv_420 = mode->out_fmt_is_yuv_420; - msm_dp_panel_init_panel_info(dp->panel); + drm_mode_copy(&msm_dp_panel->msm_dp_mode.drm_mode, adjusted_mode); + if (msm_dp_display_check_video_test(msm_dp_display)) + bpp = msm_dp_display_get_test_bpp(msm_dp_display); + else + bpp = msm_dp_panel->connector->display_info.bpc * 3; + + msm_dp_panel->msm_dp_mode.bpp = bpp ? bpp : 24; /* Default bpp */ + msm_dp_panel->msm_dp_mode.v_active_low = + !!(adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC); + msm_dp_panel->msm_dp_mode.h_active_low = + !!(adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC); + msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420 = + drm_mode_is_420_only(&msm_dp_panel->connector->display_info, adjusted_mode) && + msm_dp_panel->vsc_sdp_supported; + msm_dp_panel_init_panel_info(msm_dp_panel); + + /* populate wide_bus_support to different layers */ + dp->ctrl->wide_bus_en = + msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420 ? false : dp->wide_bus_supported; return 0; } @@ -1305,7 +1321,7 @@ bool msm_dp_wide_bus_available(const struct msm_dp *msm_dp_display) dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); - if (dp->msm_dp_mode.out_fmt_is_yuv_420) + if (dp->panel->msm_dp_mode.out_fmt_is_yuv_420) return false; return dp->wide_bus_supported; @@ -1356,6 +1372,30 @@ int msm_dp_modeset_init(struct msm_dp *msm_dp_display, struct drm_device *dev, return 0; } +void msm_dp_bridge_atomic_pre_enable(struct drm_bridge *drm_bridge, + struct drm_atomic_commit *state) +{ + struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(drm_bridge); + struct msm_dp *dp = msm_dp_bridge->msm_dp_display; + struct msm_dp_display_private *msm_dp_display; + struct drm_crtc *crtc; + struct drm_crtc_state *crtc_state; + + msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); + + crtc = drm_atomic_get_new_crtc_for_encoder(state, drm_bridge->encoder); + if (!crtc) + return; + crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + + /* + * The DPU encoder's .atomic_enable() reads the mode's YUV420 / wide bus + * state and runs before the bridge's .atomic_enable(), so the mode must + * be programmed here, in .atomic_pre_enable(). + */ + msm_dp_display_set_mode(dp, &crtc_state->adjusted_mode, msm_dp_display->panel); +} + void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge, struct drm_atomic_commit *state) { @@ -1366,10 +1406,6 @@ void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge, bool force_link_train = false; msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); - if (!msm_dp_display->msm_dp_mode.drm_mode.clock) { - DRM_ERROR("invalid params\n"); - return; - } if (dp->is_edp) msm_dp_hpd_plug_handle(msm_dp_display); @@ -1382,12 +1418,6 @@ void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge, if (msm_dp_display->link->sink_count == 0) return; - rc = msm_dp_display_set_mode(dp, &msm_dp_display->msm_dp_mode); - if (rc) { - DRM_ERROR("Failed to perform a mode set, rc=%d\n", rc); - return; - } - if (!dp->power_on) { msm_dp_display_host_phy_init(msm_dp_display); force_link_train = true; @@ -1442,45 +1472,6 @@ void msm_dp_bridge_atomic_post_disable(struct drm_bridge *drm_bridge, pm_runtime_put_sync(&dp->pdev->dev); } -void msm_dp_bridge_mode_set(struct drm_bridge *drm_bridge, - const struct drm_display_mode *mode, - const struct drm_display_mode *adjusted_mode) -{ - struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(drm_bridge); - struct msm_dp *dp = msm_dp_bridge->msm_dp_display; - struct msm_dp_display_private *msm_dp_display; - struct msm_dp_panel *msm_dp_panel; - - msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); - msm_dp_panel = msm_dp_display->panel; - - memset(&msm_dp_display->msm_dp_mode, 0x0, sizeof(struct msm_dp_display_mode)); - - if (msm_dp_display_check_video_test(dp)) - msm_dp_display->msm_dp_mode.bpp = msm_dp_display_get_test_bpp(dp); - else /* Default num_components per pixel = 3 */ - msm_dp_display->msm_dp_mode.bpp = dp->connector->display_info.bpc * 3; - - if (!msm_dp_display->msm_dp_mode.bpp) - msm_dp_display->msm_dp_mode.bpp = 24; /* Default bpp */ - - drm_mode_copy(&msm_dp_display->msm_dp_mode.drm_mode, adjusted_mode); - - msm_dp_display->msm_dp_mode.v_active_low = - !!(msm_dp_display->msm_dp_mode.drm_mode.flags & DRM_MODE_FLAG_NVSYNC); - - msm_dp_display->msm_dp_mode.h_active_low = - !!(msm_dp_display->msm_dp_mode.drm_mode.flags & DRM_MODE_FLAG_NHSYNC); - - msm_dp_display->msm_dp_mode.out_fmt_is_yuv_420 = - drm_mode_is_420_only(&dp->connector->display_info, adjusted_mode) && - msm_dp_panel->vsc_sdp_supported; - - /* populate wide_bus_support to different layers */ - msm_dp_display->ctrl->wide_bus_en = - msm_dp_display->msm_dp_mode.out_fmt_is_yuv_420 ? false : msm_dp_display->wide_bus_supported; -} - void msm_dp_bridge_hpd_enable(struct drm_bridge *bridge) { struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(bridge); diff --git a/drivers/gpu/drm/msm/dp/dp_drm.c b/drivers/gpu/drm/msm/dp/dp_drm.c index 06881bfd6181..794a0df60414 100644 --- a/drivers/gpu/drm/msm/dp/dp_drm.c +++ b/drivers/gpu/drm/msm/dp/dp_drm.c @@ -53,10 +53,10 @@ static const struct drm_bridge_funcs msm_dp_bridge_ops = { .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_pre_enable = msm_dp_bridge_atomic_pre_enable, .atomic_enable = msm_dp_bridge_atomic_enable, .atomic_disable = msm_dp_bridge_atomic_disable, .atomic_post_disable = msm_dp_bridge_atomic_post_disable, - .mode_set = msm_dp_bridge_mode_set, .mode_valid = msm_dp_bridge_mode_valid, .get_modes = msm_dp_bridge_get_modes, .detect = msm_dp_bridge_detect, @@ -230,10 +230,10 @@ static void msm_edp_bridge_debugfs_init(struct drm_bridge *bridge, struct dentry } static const struct drm_bridge_funcs msm_edp_bridge_ops = { + .atomic_pre_enable = msm_dp_bridge_atomic_pre_enable, .atomic_enable = msm_edp_bridge_atomic_enable, .atomic_disable = msm_edp_bridge_atomic_disable, .atomic_post_disable = msm_edp_bridge_atomic_post_disable, - .mode_set = msm_dp_bridge_mode_set, .mode_valid = msm_edp_bridge_mode_valid, .atomic_create_state = drm_atomic_helper_bridge_create_state, .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, diff --git a/drivers/gpu/drm/msm/dp/dp_drm.h b/drivers/gpu/drm/msm/dp/dp_drm.h index 041aa026ae2e..4f733d8118b1 100644 --- a/drivers/gpu/drm/msm/dp/dp_drm.h +++ b/drivers/gpu/drm/msm/dp/dp_drm.h @@ -27,6 +27,8 @@ int msm_dp_bridge_init(struct msm_dp *msm_dp_display, struct drm_device *dev, enum drm_connector_status msm_dp_bridge_detect(struct drm_bridge *bridge, struct drm_connector *connector); +void msm_dp_bridge_atomic_pre_enable(struct drm_bridge *drm_bridge, + struct drm_atomic_commit *state); void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge, struct drm_atomic_commit *state); void msm_dp_bridge_atomic_disable(struct drm_bridge *drm_bridge, @@ -36,9 +38,6 @@ void msm_dp_bridge_atomic_post_disable(struct drm_bridge *drm_bridge, enum drm_mode_status msm_dp_bridge_mode_valid(struct drm_bridge *bridge, const struct drm_display_info *info, const struct drm_display_mode *mode); -void msm_dp_bridge_mode_set(struct drm_bridge *drm_bridge, - const struct drm_display_mode *mode, - const struct drm_display_mode *adjusted_mode); void msm_dp_bridge_hpd_enable(struct drm_bridge *bridge); void msm_dp_bridge_hpd_disable(struct drm_bridge *bridge); void msm_dp_bridge_hpd_notify(struct drm_bridge *bridge, From 1c96c88ec172d3d7e0b46c00f0b3dc0361436fdb Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Tue, 28 Jul 2026 18:21:32 +0800 Subject: [PATCH 0406/1328] drm/msm/dp: move mode setup into msm_dp_panel_init_panel_info() The display layer directly assigns msm_dp_panel mode fields (bpp, sync polarity, yuv420 flag) instead of letting the panel manage its own state. Pass adjusted_mode and bpp as parameters to msm_dp_panel_init_panel_info() and move the assignments inside it. Suggested-by: Dmitry Baryshkov Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/742731/ Link: https://lore.kernel.org/r/20260728-dp_mstclean-v9-3-f7779fce10f4@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_display.c | 11 +---------- drivers/gpu/drm/msm/dp/dp_panel.c | 18 +++++++++++++++--- drivers/gpu/drm/msm/dp/dp_panel.h | 4 +++- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 3ff7360a6833..cc94ce9635b7 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -604,21 +604,12 @@ static int msm_dp_display_set_mode(struct msm_dp *msm_dp_display, dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); - drm_mode_copy(&msm_dp_panel->msm_dp_mode.drm_mode, adjusted_mode); if (msm_dp_display_check_video_test(msm_dp_display)) bpp = msm_dp_display_get_test_bpp(msm_dp_display); else bpp = msm_dp_panel->connector->display_info.bpc * 3; - msm_dp_panel->msm_dp_mode.bpp = bpp ? bpp : 24; /* Default bpp */ - msm_dp_panel->msm_dp_mode.v_active_low = - !!(adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC); - msm_dp_panel->msm_dp_mode.h_active_low = - !!(adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC); - msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420 = - drm_mode_is_420_only(&msm_dp_panel->connector->display_info, adjusted_mode) && - msm_dp_panel->vsc_sdp_supported; - msm_dp_panel_init_panel_info(msm_dp_panel); + msm_dp_panel_init_panel_info(msm_dp_panel, adjusted_mode, bpp ? bpp : 24); /* populate wide_bus_support to different layers */ dp->ctrl->wide_bus_en = diff --git a/drivers/gpu/drm/msm/dp/dp_panel.c b/drivers/gpu/drm/msm/dp/dp_panel.c index bde4a772d22c..e76dad0f6663 100644 --- a/drivers/gpu/drm/msm/dp/dp_panel.c +++ b/drivers/gpu/drm/msm/dp/dp_panel.c @@ -647,15 +647,27 @@ int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en) return 0; } -int msm_dp_panel_init_panel_info(struct msm_dp_panel *msm_dp_panel) +int msm_dp_panel_init_panel_info(struct msm_dp_panel *msm_dp_panel, + const struct drm_display_mode *adjusted_mode, + u32 bpp) { struct drm_display_mode *drm_mode; struct msm_dp_panel_private *panel; - drm_mode = &msm_dp_panel->msm_dp_mode.drm_mode; - panel = container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel); + drm_mode_copy(&msm_dp_panel->msm_dp_mode.drm_mode, adjusted_mode); + msm_dp_panel->msm_dp_mode.bpp = bpp; + msm_dp_panel->msm_dp_mode.v_active_low = + !!(adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC); + msm_dp_panel->msm_dp_mode.h_active_low = + !!(adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC); + msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420 = + drm_mode_is_420_only(&msm_dp_panel->connector->display_info, adjusted_mode) && + msm_dp_panel->vsc_sdp_supported; + + drm_mode = &msm_dp_panel->msm_dp_mode.drm_mode; + /* * print resolution info as this is a result * of user initiated action of cable connection diff --git a/drivers/gpu/drm/msm/dp/dp_panel.h b/drivers/gpu/drm/msm/dp/dp_panel.h index 53b7b4463551..4519ac374220 100644 --- a/drivers/gpu/drm/msm/dp/dp_panel.h +++ b/drivers/gpu/drm/msm/dp/dp_panel.h @@ -43,7 +43,9 @@ struct msm_dp_panel { u32 max_bw_code; }; -int msm_dp_panel_init_panel_info(struct msm_dp_panel *msm_dp_panel); +int msm_dp_panel_init_panel_info(struct msm_dp_panel *msm_dp_panel, + const struct drm_display_mode *adjusted_mode, + u32 bpp); int msm_dp_panel_deinit(struct msm_dp_panel *msm_dp_panel); int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en); int msm_dp_panel_read_link_caps(struct msm_dp_panel *msm_dp_panel, From 7de83c21291ff861a66c602ddcaf45ff8f43f547 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Tue, 28 Jul 2026 18:21:33 +0800 Subject: [PATCH 0407/1328] drm/msm/dp: split msm_dp_ctrl_config_ctrl() into link parts and stream parts The DP_CONFIGURATION_CTRL register contains both link-level and stream-specific fields. Currently, msm_dp_ctrl_config_ctrl() configures all of them together. Separate the configuration into link parts and stream parts to support MST. Clear the stream-specific fields before OR-ing new values in the stream path to avoid bit accumulation across repeated calls. Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/742733/ Link: https://lore.kernel.org/r/20260728-dp_mstclean-v9-4-f7779fce10f4@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_ctrl.c | 51 ++++++++++++++++++++++---------- drivers/gpu/drm/msm/dp/dp_reg.h | 2 ++ 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index 86ef8c89ad44..7c0649d5318f 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -388,26 +388,48 @@ void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl) drm_dbg_dp(ctrl->drm_dev, "mainlink off\n"); } -static void msm_dp_ctrl_config_ctrl(struct msm_dp_ctrl_private *ctrl) +static void msm_dp_ctrl_config_ctrl_streams(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *msm_dp_panel) { u32 config = 0, tbd; + + /* + * RMW: called from atomic_enable(), serialized by the DRM atomic framework. + * Clear stream-specific fields before OR-ing to avoid bit accumulation. + */ + config = msm_dp_read_link(ctrl, REG_DP_CONFIGURATION_CTRL); + config &= ~(DP_CONFIGURATION_CTRL_RGB_YUV_MASK | + DP_CONFIGURATION_CTRL_BPC_MASK | + DP_CONFIGURATION_CTRL_SEND_VSC); + + if (msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420) + config |= DP_CONFIGURATION_CTRL_RGB_YUV; /* YUV420 */ + + tbd = msm_dp_link_get_test_bits_depth(ctrl->link, + msm_dp_panel->msm_dp_mode.bpp); + + config |= tbd << DP_CONFIGURATION_CTRL_BPC_SHIFT; + + if (msm_dp_panel->psr_cap.version) + config |= DP_CONFIGURATION_CTRL_SEND_VSC; + + drm_dbg_dp(ctrl->drm_dev, "stream DP_CONFIGURATION_CTRL=0x%x\n", config); + + msm_dp_write_link(ctrl, REG_DP_CONFIGURATION_CTRL, config); +} + +static void msm_dp_ctrl_config_ctrl_link(struct msm_dp_ctrl_private *ctrl) +{ + u32 config = 0; const u8 *dpcd = ctrl->panel->dpcd; /* Default-> LSCLK DIV: 1/4 LCLK */ config |= (2 << DP_CONFIGURATION_CTRL_LSCLK_DIV_SHIFT); - if (ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420) - config |= DP_CONFIGURATION_CTRL_RGB_YUV; /* YUV420 */ - /* Scrambler reset enable */ if (drm_dp_alternate_scrambler_reset_cap(dpcd)) config |= DP_CONFIGURATION_CTRL_ASSR; - tbd = msm_dp_link_get_test_bits_depth(ctrl->link, - ctrl->panel->msm_dp_mode.bpp); - - config |= tbd << DP_CONFIGURATION_CTRL_BPC_SHIFT; - /* Num of Lanes */ config |= ((ctrl->link->link_params.num_lanes - 1) << DP_CONFIGURATION_CTRL_NUM_OF_LANES_SHIFT); @@ -421,10 +443,7 @@ static void msm_dp_ctrl_config_ctrl(struct msm_dp_ctrl_private *ctrl) config |= DP_CONFIGURATION_CTRL_STATIC_DYNAMIC_CN; config |= DP_CONFIGURATION_CTRL_SYNC_ASYNC_CLK; - if (ctrl->panel->psr_cap.version) - config |= DP_CONFIGURATION_CTRL_SEND_VSC; - - drm_dbg_dp(ctrl->drm_dev, "DP_CONFIGURATION_CTRL=0x%x\n", config); + drm_dbg_dp(ctrl->drm_dev, "link DP_CONFIGURATION_CTRL=0x%x\n", config); msm_dp_write_link(ctrl, REG_DP_CONFIGURATION_CTRL, config); } @@ -450,7 +469,8 @@ static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl msm_dp_ctrl_lane_mapping(ctrl); msm_dp_setup_peripheral_flush(ctrl); - msm_dp_ctrl_config_ctrl(ctrl); + msm_dp_ctrl_config_ctrl_link(ctrl); + msm_dp_ctrl_config_ctrl_streams(ctrl, ctrl->panel); test_bits_depth = msm_dp_link_get_test_bits_depth(ctrl->link, ctrl->panel->msm_dp_mode.bpp); colorimetry_cfg = msm_dp_link_get_colorimetry_config(ctrl->link); @@ -1628,7 +1648,8 @@ static int msm_dp_ctrl_link_train(struct msm_dp_ctrl_private *ctrl, u8 assr; struct msm_dp_link_info link_info = {0}; - msm_dp_ctrl_config_ctrl(ctrl); + msm_dp_ctrl_config_ctrl_link(ctrl); + msm_dp_ctrl_config_ctrl_streams(ctrl, ctrl->panel); link_info.num_lanes = ctrl->link->link_params.num_lanes; link_info.rate = ctrl->link->link_params.rate; diff --git a/drivers/gpu/drm/msm/dp/dp_reg.h b/drivers/gpu/drm/msm/dp/dp_reg.h index 3689642b7fc0..dda4b642c81b 100644 --- a/drivers/gpu/drm/msm/dp/dp_reg.h +++ b/drivers/gpu/drm/msm/dp/dp_reg.h @@ -149,8 +149,10 @@ #define DP_CONFIGURATION_CTRL_ENHANCED_FRAMING (0x00000040) #define DP_CONFIGURATION_CTRL_SEND_VSC (0x00000080) #define DP_CONFIGURATION_CTRL_BPC (0x00000100) +#define DP_CONFIGURATION_CTRL_BPC_MASK GENMASK(9, 8) #define DP_CONFIGURATION_CTRL_ASSR (0x00000400) #define DP_CONFIGURATION_CTRL_RGB_YUV (0x00000800) +#define DP_CONFIGURATION_CTRL_RGB_YUV_MASK GENMASK(12, 11) #define DP_CONFIGURATION_CTRL_LSCLK_DIV (0x00002000) #define DP_CONFIGURATION_CTRL_NUM_OF_LANES_SHIFT (0x04) #define DP_CONFIGURATION_CTRL_BPC_SHIFT (0x08) From 3a1edd5ac0226f06aef31b492c56b09f876acf5b Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Tue, 28 Jul 2026 18:21:34 +0800 Subject: [PATCH 0408/1328] drm/msm/dp: extract MISC1_MISC0 configuration into a separate function Refactor the MISC1_MISC0 register configuration into a standalone helper function to support MST. Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/742735/ Link: https://lore.kernel.org/r/20260728-dp_mstclean-v9-5-f7779fce10f4@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_ctrl.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index 7c0649d5318f..c557e0f01dc6 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -462,17 +462,13 @@ static void msm_dp_ctrl_lane_mapping(struct msm_dp_ctrl_private *ctrl) ln_mapping); } -static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl) +static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *msm_dp_panel) { u32 colorimetry_cfg, test_bits_depth, misc_val; - msm_dp_ctrl_lane_mapping(ctrl); - msm_dp_setup_peripheral_flush(ctrl); - - msm_dp_ctrl_config_ctrl_link(ctrl); - msm_dp_ctrl_config_ctrl_streams(ctrl, ctrl->panel); - - test_bits_depth = msm_dp_link_get_test_bits_depth(ctrl->link, ctrl->panel->msm_dp_mode.bpp); + test_bits_depth = msm_dp_link_get_test_bits_depth(ctrl->link, + msm_dp_panel->msm_dp_mode.bpp); colorimetry_cfg = msm_dp_link_get_colorimetry_config(ctrl->link); misc_val = msm_dp_read_link(ctrl, REG_DP_MISC1_MISC0); @@ -486,6 +482,17 @@ static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl drm_dbg_dp(ctrl->drm_dev, "misc settings = 0x%x\n", misc_val); msm_dp_write_link(ctrl, REG_DP_MISC1_MISC0, misc_val); +} + +static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl) +{ + msm_dp_ctrl_lane_mapping(ctrl); + msm_dp_setup_peripheral_flush(ctrl); + + msm_dp_ctrl_config_ctrl_link(ctrl); + msm_dp_ctrl_config_ctrl_streams(ctrl, ctrl->panel); + + msm_dp_ctrl_config_misc1_misc0(ctrl, ctrl->panel); msm_dp_panel_timing_cfg(ctrl->panel, ctrl->msm_dp_ctrl.wide_bus_en); } From 0a19867dfb8dc4491737e64987566fc2c5962778 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Tue, 28 Jul 2026 18:21:35 +0800 Subject: [PATCH 0409/1328] drm/msm/dp: split link setup from source params msm_dp_ctrl_configure_source_params() should only handle stream-related configuration. Move the link setup out of it so MST can program link and stream settings separately. Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/742737/ Link: https://lore.kernel.org/r/20260728-dp_mstclean-v9-6-f7779fce10f4@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_ctrl.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index c557e0f01dc6..f343bd6c2b81 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -486,10 +486,6 @@ static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl, static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl) { - msm_dp_ctrl_lane_mapping(ctrl); - msm_dp_setup_peripheral_flush(ctrl); - - msm_dp_ctrl_config_ctrl_link(ctrl); msm_dp_ctrl_config_ctrl_streams(ctrl, ctrl->panel); msm_dp_ctrl_config_misc1_misc0(ctrl, ctrl->panel); @@ -2556,6 +2552,10 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train */ reinit_completion(&ctrl->video_comp); + msm_dp_ctrl_lane_mapping(ctrl); + msm_dp_setup_peripheral_flush(ctrl); + msm_dp_ctrl_config_ctrl_link(ctrl); + msm_dp_ctrl_configure_source_params(ctrl); msm_dp_ctrl_config_msa(ctrl, From 38e4b121497923b0f1a9636bf947af730f38bb3b Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Tue, 28 Jul 2026 18:21:36 +0800 Subject: [PATCH 0410/1328] drm/msm/dp: move the pixel clock control to its own API Enable/Disable of DP pixel clock happens in multiple code paths leading to code duplication. Move it into individual helpers so that the helpers can be called wherever necessary. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/742738/ Link: https://lore.kernel.org/r/20260728-dp_mstclean-v9-7-f7779fce10f4@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_ctrl.c | 80 +++++++++++++++++--------------- 1 file changed, 42 insertions(+), 38 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index f343bd6c2b81..068d4f8b673f 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -2176,6 +2176,41 @@ static bool msm_dp_ctrl_send_phy_test_pattern(struct msm_dp_ctrl_private *ctrl) return success; } +static int msm_dp_ctrl_on_pixel_clk(struct msm_dp_ctrl_private *ctrl, unsigned long pixel_rate) +{ + int ret; + + ret = clk_set_rate(ctrl->pixel_clk, pixel_rate * 1000); + if (ret) { + DRM_ERROR("Failed to set pixel clock rate. ret=%d\n", ret); + return ret; + } + + if (WARN_ON_ONCE(ctrl->stream_clks_on)) + return 0; + + ret = clk_prepare_enable(ctrl->pixel_clk); + if (ret) { + DRM_ERROR("Failed to start pixel clocks. ret=%d\n", ret); + return ret; + } + ctrl->stream_clks_on = true; + + return ret; +} + +static void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl) +{ + struct msm_dp_ctrl_private *ctrl; + + ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); + + if (ctrl->stream_clks_on) { + clk_disable_unprepare(ctrl->pixel_clk); + ctrl->stream_clks_on = false; + } +} + static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl) { int ret; @@ -2201,22 +2236,9 @@ static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl } pixel_rate = ctrl->panel->msm_dp_mode.drm_mode.clock; - ret = clk_set_rate(ctrl->pixel_clk, pixel_rate * 1000); - if (ret) { - DRM_ERROR("Failed to set pixel clock rate. ret=%d\n", ret); + ret = msm_dp_ctrl_on_pixel_clk(ctrl, pixel_rate); + if (ret) return ret; - } - - if (ctrl->stream_clks_on) { - drm_dbg_dp(ctrl->drm_dev, "pixel clks already enabled\n"); - } else { - ret = clk_prepare_enable(ctrl->pixel_clk); - if (ret) { - DRM_ERROR("Failed to start pixel clocks. ret=%d\n", ret); - return ret; - } - ctrl->stream_clks_on = true; - } msm_dp_ctrl_send_phy_test_pattern(ctrl); @@ -2519,26 +2541,13 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train ret = msm_dp_ctrl_enable_mainlink_clocks(ctrl); if (ret) { DRM_ERROR("Failed to start link clocks. ret=%d\n", ret); - goto end; + return ret; } } - ret = clk_set_rate(ctrl->pixel_clk, pixel_rate * 1000); - if (ret) { - DRM_ERROR("Failed to set pixel clock rate. ret=%d\n", ret); - goto end; - } - - if (ctrl->stream_clks_on) { - drm_dbg_dp(ctrl->drm_dev, "pixel clks already enabled\n"); - } else { - ret = clk_prepare_enable(ctrl->pixel_clk); - if (ret) { - DRM_ERROR("Failed to start pixel clocks. ret=%d\n", ret); - goto end; - } - ctrl->stream_clks_on = true; - } + ret = msm_dp_ctrl_on_pixel_clk(ctrl, pixel_rate); + if (ret) + return ret; if (force_link_train || !msm_dp_ctrl_channel_eq_ok(ctrl)) msm_dp_ctrl_link_retrain(ctrl); @@ -2577,7 +2586,6 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train drm_dbg_dp(ctrl->drm_dev, "mainlink %s\n", mainlink_ready ? "READY" : "NOT READY"); -end: return ret; } @@ -2625,11 +2633,7 @@ void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl) msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl); - if (ctrl->stream_clks_on) { - clk_disable_unprepare(ctrl->pixel_clk); - ctrl->stream_clks_on = false; - } - + msm_dp_ctrl_off_pixel_clk(msm_dp_ctrl); dev_pm_opp_set_rate(ctrl->dev, 0); msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl); From ca16db3d18cfe2d3e45a2cb3f3f67f2b19685a02 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Tue, 28 Jul 2026 18:21:37 +0800 Subject: [PATCH 0411/1328] drm/msm/dp: break up dp_display_enable into two parts dp_display_enable() currently re-trains the link if needed and then enables the pixel clock, programs the controller to start sending the pixel stream. Split these two parts into prepare/enable APIs, to support MST bridges_enable insert the MST payloads funcs between enable stream_clks and program register. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/742740/ Link: https://lore.kernel.org/r/20260728-dp_mstclean-v9-8-f7779fce10f4@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_ctrl.c | 48 ++++++++----- drivers/gpu/drm/msm/dp/dp_ctrl.h | 3 +- drivers/gpu/drm/msm/dp/dp_display.c | 105 ++++++++++++++++++---------- 3 files changed, 103 insertions(+), 53 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index 068d4f8b673f..43094dda3434 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -2511,27 +2511,19 @@ static void msm_dp_ctrl_config_msa(struct msm_dp_ctrl_private *ctrl, msm_dp_write_link(ctrl, REG_DP_SOFTWARE_NVID, nvid); } -int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train) +int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train) { int ret = 0; - bool mainlink_ready = false; struct msm_dp_ctrl_private *ctrl; - unsigned long pixel_rate; - unsigned long pixel_rate_orig; if (!msm_dp_ctrl) return -EINVAL; ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); - pixel_rate = pixel_rate_orig = ctrl->panel->msm_dp_mode.drm_mode.clock; - - if (msm_dp_ctrl->wide_bus_en || ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420) - pixel_rate >>= 1; - - drm_dbg_dp(ctrl->drm_dev, "rate=%d, num_lanes=%d, pixel_rate=%lu\n", - ctrl->link->link_params.rate, - ctrl->link->link_params.num_lanes, pixel_rate); + drm_dbg_dp(ctrl->drm_dev, "rate=%d, num_lanes=%d\n", + ctrl->link->link_params.rate, + ctrl->link->link_params.num_lanes); drm_dbg_dp(ctrl->drm_dev, "core_clk_on=%d link_clk_on=%d stream_clk_on=%d\n", @@ -2545,16 +2537,40 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train } } - ret = msm_dp_ctrl_on_pixel_clk(ctrl, pixel_rate); - if (ret) - return ret; - if (force_link_train || !msm_dp_ctrl_channel_eq_ok(ctrl)) msm_dp_ctrl_link_retrain(ctrl); /* stop txing train pattern to end link training */ msm_dp_ctrl_clear_training_pattern(ctrl, DP_PHY_DPRX); + return ret; +} + +int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl) +{ + int ret = 0; + bool mainlink_ready = false; + struct msm_dp_ctrl_private *ctrl; + unsigned long pixel_rate; + unsigned long pixel_rate_orig; + + if (!msm_dp_ctrl) + return -EINVAL; + + ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); + + pixel_rate_orig = ctrl->panel->msm_dp_mode.drm_mode.clock; + pixel_rate = pixel_rate_orig; + + if (msm_dp_ctrl->wide_bus_en || ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420) + pixel_rate >>= 1; + + drm_dbg_dp(ctrl->drm_dev, "pixel_rate=%lu\n", pixel_rate); + + ret = msm_dp_ctrl_on_pixel_clk(ctrl, pixel_rate); + if (ret) + return ret; + /* * Set up transfer unit values and set controller state to send * video. diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h index f68bee62713f..1497f1a8fc2f 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.h +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h @@ -17,7 +17,8 @@ struct msm_dp_ctrl { struct phy; int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl); -int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train); +int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl); +int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train); void msm_dp_ctrl_off_link_stream(struct msm_dp_ctrl *msm_dp_ctrl); void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl); void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl); diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index cc94ce9635b7..e2b4f0276737 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -617,7 +617,42 @@ static int msm_dp_display_set_mode(struct msm_dp *msm_dp_display, return 0; } -static int msm_dp_display_enable(struct msm_dp_display_private *dp, bool force_link_train) +static int msm_dp_display_prepare_link(struct msm_dp_display_private *dp) +{ + struct msm_dp *msm_dp_display = &dp->msm_dp_display; + int rc = 0; + bool force_link_train = false; + + drm_dbg_dp(dp->drm_dev, "sink_count=%d\n", dp->link->sink_count); + + if (msm_dp_display->is_edp) + msm_dp_hpd_plug_handle(dp); + + rc = pm_runtime_resume_and_get(&msm_dp_display->pdev->dev); + if (rc) { + DRM_ERROR("failed to pm_runtime_resume\n"); + return rc; + } + + if (dp->link->sink_count == 0) + return -ENOTCONN; + + if (!msm_dp_display->power_on) { + msm_dp_display_host_phy_init(dp); + force_link_train = true; + } + + rc = msm_dp_ctrl_on_link(dp->ctrl); + if (rc) { + DRM_ERROR("Failed link training (rc=%d)\n", rc); + // TODO: schedule drm_connector_set_link_status_property() + return rc; + } + + return msm_dp_ctrl_prepare_stream_on(dp->ctrl, force_link_train); +} + +static int msm_dp_display_enable(struct msm_dp_display_private *dp) { int rc = 0; struct msm_dp *msm_dp_display = &dp->msm_dp_display; @@ -628,7 +663,7 @@ static int msm_dp_display_enable(struct msm_dp_display_private *dp, bool force_l return 0; } - rc = msm_dp_ctrl_on_stream(dp->ctrl, force_link_train); + rc = msm_dp_ctrl_on_stream(dp->ctrl); if (!rc) msm_dp_display->power_on = true; @@ -658,13 +693,10 @@ static int msm_dp_display_post_enable(struct msm_dp *msm_dp_display) return 0; } -static int msm_dp_display_disable(struct msm_dp_display_private *dp) +static void msm_dp_display_audio_notify_disable(struct msm_dp_display_private *dp) { struct msm_dp *msm_dp_display = &dp->msm_dp_display; - if (!msm_dp_display->power_on) - return 0; - /* wait only if audio was enabled */ if (msm_dp_display->audio_enabled) { /* signal the disconnect event */ @@ -675,6 +707,14 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp) } msm_dp_display->audio_enabled = false; +} + +static int msm_dp_display_disable(struct msm_dp_display_private *dp) +{ + struct msm_dp *msm_dp_display = &dp->msm_dp_display; + + if (!msm_dp_display->power_on) + return 0; if (dp->link->sink_count == 0) { /* @@ -1391,45 +1431,29 @@ void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge, struct drm_atomic_commit *state) { struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(drm_bridge); - struct msm_dp *dp = msm_dp_bridge->msm_dp_display; + struct msm_dp *msm_dp_display = msm_dp_bridge->msm_dp_display; int rc = 0; - struct msm_dp_display_private *msm_dp_display; - bool force_link_train = false; + struct msm_dp_display_private *dp; - msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); + dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); - if (dp->is_edp) - msm_dp_hpd_plug_handle(msm_dp_display); - - if (pm_runtime_resume_and_get(&dp->pdev->dev)) { - DRM_ERROR("failed to pm_runtime_resume\n"); - return; - } - - if (msm_dp_display->link->sink_count == 0) - return; - - if (!dp->power_on) { - msm_dp_display_host_phy_init(msm_dp_display); - force_link_train = true; - } - - rc = msm_dp_ctrl_on_link(msm_dp_display->ctrl); + rc = msm_dp_display_prepare_link(dp); if (rc) { - DRM_ERROR("Failed link training (rc=%d)\n", rc); - // TODO: schedule drm_connector_set_link_status_property() + DRM_ERROR("DP display prepare failed, rc=%d\n", rc); return; } - msm_dp_display_enable(msm_dp_display, force_link_train); + rc = msm_dp_display_enable(dp); + if (rc) + DRM_ERROR("DP display enable failed, rc=%d\n", rc); - rc = msm_dp_display_post_enable(dp); + rc = msm_dp_display_post_enable(msm_dp_display); if (rc) { DRM_ERROR("DP display post enable failed, rc=%d\n", rc); - msm_dp_display_disable(msm_dp_display); + msm_dp_display_disable(dp); } - drm_dbg_dp(dp->drm_dev, "type=%d Done\n", dp->connector_type); + drm_dbg_dp(msm_dp_display->drm_dev, "type=%d Done\n", msm_dp_display->connector_type); } void msm_dp_bridge_atomic_disable(struct drm_bridge *drm_bridge, @@ -1444,6 +1468,15 @@ void msm_dp_bridge_atomic_disable(struct drm_bridge *drm_bridge, msm_dp_ctrl_push_idle(msm_dp_display->ctrl); } +static void msm_dp_display_unprepare(struct msm_dp_display_private *dp) +{ + struct msm_dp *msm_dp_display = &dp->msm_dp_display; + + pm_runtime_put_sync(&msm_dp_display->pdev->dev); + + drm_dbg_dp(dp->drm_dev, "type=%d Done\n", msm_dp_display->connector_type); +} + void msm_dp_bridge_atomic_post_disable(struct drm_bridge *drm_bridge, struct drm_atomic_commit *state) { @@ -1456,11 +1489,11 @@ void msm_dp_bridge_atomic_post_disable(struct drm_bridge *drm_bridge, if (dp->is_edp) msm_dp_hpd_unplug_handle(msm_dp_display); + msm_dp_display_audio_notify_disable(msm_dp_display); + msm_dp_display_disable(msm_dp_display); - drm_dbg_dp(dp->drm_dev, "type=%d Done\n", dp->connector_type); - - pm_runtime_put_sync(&dp->pdev->dev); + msm_dp_display_unprepare(msm_dp_display); } void msm_dp_bridge_hpd_enable(struct drm_bridge *bridge) From 477d23a69569720be64bd79cafae01b3b569db2e Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Tue, 28 Jul 2026 18:21:38 +0800 Subject: [PATCH 0412/1328] drm/msm/dp: re-arrange dp_display_disable() into functional parts dp_display_disable() handles special case of when monitor is disconnected from the dongle while the dongle stays connected thereby needing a separate function dp_ctrl_off_link_stream() for this. However with a slight rework this can still be handled by keeping common paths same for regular and special case. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/742742/ Link: https://lore.kernel.org/r/20260728-dp_mstclean-v9-9-f7779fce10f4@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_ctrl.c | 19 +------------------ drivers/gpu/drm/msm/dp/dp_ctrl.h | 2 +- drivers/gpu/drm/msm/dp/dp_display.c | 10 +++++++++- 3 files changed, 11 insertions(+), 20 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index 43094dda3434..272ac3b25b13 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -2605,7 +2605,7 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl) return ret; } -void msm_dp_ctrl_off_link_stream(struct msm_dp_ctrl *msm_dp_ctrl) +void msm_dp_ctrl_reinit_phy(struct msm_dp_ctrl *msm_dp_ctrl) { struct msm_dp_ctrl_private *ctrl; struct phy *phy; @@ -2613,23 +2613,6 @@ void msm_dp_ctrl_off_link_stream(struct msm_dp_ctrl *msm_dp_ctrl) ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); phy = ctrl->phy; - msm_dp_panel_disable_vsc_sdp(ctrl->panel); - - /* set dongle to D3 (power off) mode */ - msm_dp_link_psm_config(ctrl->link, &ctrl->panel->link_info, true); - - msm_dp_ctrl_mainlink_disable(ctrl); - - if (ctrl->stream_clks_on) { - clk_disable_unprepare(ctrl->pixel_clk); - ctrl->stream_clks_on = false; - } - - dev_pm_opp_set_rate(ctrl->dev, 0); - msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl); - - phy_power_off(phy); - /* aux channel down, reinit phy */ phy_exit(phy); phy_init(phy); diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h index 1497f1a8fc2f..5d615f50d13b 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.h +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h @@ -19,7 +19,6 @@ struct phy; int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl); int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl); int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train); -void msm_dp_ctrl_off_link_stream(struct msm_dp_ctrl *msm_dp_ctrl); void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl); void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl); irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl); @@ -46,4 +45,5 @@ void msm_dp_ctrl_core_clk_disable(struct msm_dp_ctrl *msm_dp_ctrl); void msm_dp_ctrl_enable_irq(struct msm_dp_ctrl *msm_dp_ctrl); void msm_dp_ctrl_disable_irq(struct msm_dp_ctrl *msm_dp_ctrl); +void msm_dp_ctrl_reinit_phy(struct msm_dp_ctrl *msm_dp_ctrl); #endif /* _DP_CTRL_H_ */ diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index e2b4f0276737..f8f8f3889ea5 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -716,12 +716,20 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp) if (!msm_dp_display->power_on) return 0; + msm_dp_panel_disable_vsc_sdp(dp->panel); + + /* dongle is still connected but sinks are disconnected */ if (dp->link->sink_count == 0) { /* * irq_hpd with sink_count = 0 * hdmi unplugged out of dongle */ - msm_dp_ctrl_off_link_stream(dp->ctrl); + + /* set dongle to D3 (power off) mode */ + msm_dp_link_psm_config(dp->link, &dp->panel->link_info, true); + msm_dp_ctrl_off(dp->ctrl); + /* re-init the PHY so that we can listen to Dongle disconnect */ + msm_dp_ctrl_reinit_phy(dp->ctrl); } else { /* * unplugged interrupt From 2f0e61b6c592ded91a0a3544872c98b69bf33fba Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Tue, 28 Jul 2026 18:21:39 +0800 Subject: [PATCH 0413/1328] drm/msm/dp: allow dp_ctrl stream APIs to use any panel passed to it With MST, multiple sinks share a single DP controller, so a cached panel in msm_dp_ctrl_private can no longer represent the per-stream sink. Drop the cache and pass panel explicitly to all stream-related dp_ctrl APIs. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/742745/ Link: https://lore.kernel.org/r/20260728-dp_mstclean-v9-10-f7779fce10f4@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_ctrl.c | 201 ++++++++++++++++------------ drivers/gpu/drm/msm/dp/dp_ctrl.h | 28 ++-- drivers/gpu/drm/msm/dp/dp_display.c | 26 ++-- 3 files changed, 143 insertions(+), 112 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index 272ac3b25b13..9bed5a2fd2e7 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -114,7 +114,6 @@ struct msm_dp_ctrl_private { struct drm_device *drm_dev; struct device *dev; struct drm_dp_aux *aux; - struct msm_dp_panel *panel; struct msm_dp_link *link; void __iomem *ahb_base; void __iomem *link_base; @@ -202,7 +201,8 @@ static int msm_dp_aux_link_configure(struct drm_dp_aux *aux, /* * NOTE: resetting DP controller will also clear any pending HPD related interrupts */ -void msm_dp_ctrl_reset(struct msm_dp_ctrl *msm_dp_ctrl) +void msm_dp_ctrl_reset(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel) { struct msm_dp_ctrl_private *ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); @@ -217,10 +217,9 @@ void msm_dp_ctrl_reset(struct msm_dp_ctrl *msm_dp_ctrl) sw_reset &= ~DP_SW_RESET; msm_dp_write_ahb(ctrl, REG_DP_SW_RESET, sw_reset); - if (!ctrl->hw_revision) { + if (!ctrl->hw_revision) ctrl->hw_revision = msm_dp_read_ahb(ctrl, REG_DP_HW_VERSION); - ctrl->panel->hw_revision = ctrl->hw_revision; - } + panel->hw_revision = ctrl->hw_revision; } static u32 msm_dp_ctrl_get_aux_interrupt(struct msm_dp_ctrl_private *ctrl) @@ -418,10 +417,11 @@ static void msm_dp_ctrl_config_ctrl_streams(struct msm_dp_ctrl_private *ctrl, msm_dp_write_link(ctrl, REG_DP_CONFIGURATION_CTRL, config); } -static void msm_dp_ctrl_config_ctrl_link(struct msm_dp_ctrl_private *ctrl) +static void msm_dp_ctrl_config_ctrl_link(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel) { u32 config = 0; - const u8 *dpcd = ctrl->panel->dpcd; + const u8 *dpcd = panel->dpcd; /* Default-> LSCLK DIV: 1/4 LCLK */ config |= (2 << DP_CONFIGURATION_CTRL_LSCLK_DIV_SHIFT); @@ -484,13 +484,14 @@ static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl, msm_dp_write_link(ctrl, REG_DP_MISC1_MISC0, misc_val); } -static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl) +static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel) { - msm_dp_ctrl_config_ctrl_streams(ctrl, ctrl->panel); + msm_dp_ctrl_config_ctrl_streams(ctrl, panel); - msm_dp_ctrl_config_misc1_misc0(ctrl, ctrl->panel); + msm_dp_ctrl_config_misc1_misc0(ctrl, panel); - msm_dp_panel_timing_cfg(ctrl->panel, ctrl->msm_dp_ctrl.wide_bus_en); + msm_dp_panel_timing_cfg(panel, ctrl->msm_dp_ctrl.wide_bus_en); } /* @@ -1260,20 +1261,21 @@ static void _dp_ctrl_calc_tu(struct msm_dp_ctrl_private *ctrl, } static void msm_dp_ctrl_calc_tu_parameters(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel, struct msm_dp_vc_tu_mapping_table *tu_table) { struct msm_dp_tu_calc_input in; - struct drm_display_mode *drm_mode; + const struct drm_display_mode *drm_mode; - drm_mode = &ctrl->panel->msm_dp_mode.drm_mode; + drm_mode = &panel->msm_dp_mode.drm_mode; in.lclk = ctrl->link->link_params.rate / 1000; in.pclk_khz = drm_mode->clock; in.hactive = drm_mode->hdisplay; in.hporch = drm_mode->htotal - drm_mode->hdisplay; in.nlanes = ctrl->link->link_params.num_lanes; - in.bpp = ctrl->panel->msm_dp_mode.bpp; - in.pixel_enc = ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420 ? 420 : 444; + in.bpp = panel->msm_dp_mode.bpp; + in.pixel_enc = panel->msm_dp_mode.out_fmt_is_yuv_420 ? 420 : 444; in.dsc_en = 0; in.async_en = 0; in.fec_en = 0; @@ -1283,14 +1285,15 @@ static void msm_dp_ctrl_calc_tu_parameters(struct msm_dp_ctrl_private *ctrl, _dp_ctrl_calc_tu(ctrl, &in, tu_table); } -static void msm_dp_ctrl_setup_tr_unit(struct msm_dp_ctrl_private *ctrl) +static void msm_dp_ctrl_setup_tr_unit(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel) { u32 msm_dp_tu = 0x0; u32 valid_boundary = 0x0; u32 valid_boundary2 = 0x0; struct msm_dp_vc_tu_mapping_table tu_calc_table; - msm_dp_ctrl_calc_tu_parameters(ctrl, &tu_calc_table); + msm_dp_ctrl_calc_tu_parameters(ctrl, panel, &tu_calc_table); msm_dp_tu |= tu_calc_table.tu_size_minus1; valid_boundary |= tu_calc_table.valid_boundary_link; @@ -1442,6 +1445,7 @@ static int msm_dp_ctrl_set_pattern_state_bit(struct msm_dp_ctrl_private *ctrl, } static int msm_dp_ctrl_link_train_1(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel, int *training_step, enum drm_dp_phy dp_phy) { int delay_us; @@ -1450,7 +1454,7 @@ static int msm_dp_ctrl_link_train_1(struct msm_dp_ctrl_private *ctrl, int const maximum_retries = 4; delay_us = drm_dp_read_clock_recovery_delay(ctrl->aux, - ctrl->panel->dpcd, dp_phy, false); + panel->dpcd, dp_phy, false); msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0); @@ -1536,14 +1540,15 @@ static int msm_dp_ctrl_link_rate_down_shift(struct msm_dp_ctrl_private *ctrl) return ret; } -static int msm_dp_ctrl_link_lane_down_shift(struct msm_dp_ctrl_private *ctrl) +static int msm_dp_ctrl_link_lane_down_shift(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel) { if (ctrl->link->link_params.num_lanes == 1) return -1; ctrl->link->link_params.num_lanes /= 2; - ctrl->link->link_params.rate = ctrl->panel->link_info.rate; + ctrl->link->link_params.rate = panel->link_info.rate; ctrl->link->phy_params.p_level = 0; ctrl->link->phy_params.v_level = 0; @@ -1552,6 +1557,7 @@ static int msm_dp_ctrl_link_lane_down_shift(struct msm_dp_ctrl_private *ctrl) } static void msm_dp_ctrl_clear_training_pattern(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel, enum drm_dp_phy dp_phy) { int delay_us; @@ -1559,11 +1565,12 @@ static void msm_dp_ctrl_clear_training_pattern(struct msm_dp_ctrl_private *ctrl, msm_dp_ctrl_train_pattern_set(ctrl, DP_TRAINING_PATTERN_DISABLE, dp_phy); delay_us = drm_dp_read_channel_eq_delay(ctrl->aux, - ctrl->panel->dpcd, dp_phy, false); + panel->dpcd, dp_phy, false); fsleep(delay_us); } static int msm_dp_ctrl_link_train_2(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel, int *training_step, enum drm_dp_phy dp_phy) { int delay_us; @@ -1574,16 +1581,16 @@ static int msm_dp_ctrl_link_train_2(struct msm_dp_ctrl_private *ctrl, u8 link_status[DP_LINK_STATUS_SIZE]; delay_us = drm_dp_read_channel_eq_delay(ctrl->aux, - ctrl->panel->dpcd, dp_phy, false); + panel->dpcd, dp_phy, false); msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0); *training_step = DP_TRAINING_2; - if (drm_dp_tps4_supported(ctrl->panel->dpcd)) { + if (drm_dp_tps4_supported(panel->dpcd)) { pattern = DP_TRAINING_PATTERN_4; state_ctrl_bit = 4; - } else if (drm_dp_tps3_supported(ctrl->panel->dpcd)) { + } else if (drm_dp_tps3_supported(panel->dpcd)) { pattern = DP_TRAINING_PATTERN_3; state_ctrl_bit = 3; } else { @@ -1620,18 +1627,19 @@ static int msm_dp_ctrl_link_train_2(struct msm_dp_ctrl_private *ctrl, } static int msm_dp_ctrl_link_train_1_2(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel, int *training_step, enum drm_dp_phy dp_phy) { int ret; - ret = msm_dp_ctrl_link_train_1(ctrl, training_step, dp_phy); + ret = msm_dp_ctrl_link_train_1(ctrl, panel, training_step, dp_phy); if (ret) { DRM_ERROR("link training #1 on phy %d failed. ret=%d\n", dp_phy, ret); return ret; } drm_dbg_dp(ctrl->drm_dev, "link training #1 on phy %d successful\n", dp_phy); - ret = msm_dp_ctrl_link_train_2(ctrl, training_step, dp_phy); + ret = msm_dp_ctrl_link_train_2(ctrl, panel, training_step, dp_phy); if (ret) { DRM_ERROR("link training #2 on phy %d failed. ret=%d\n", dp_phy, ret); return ret; @@ -1642,17 +1650,18 @@ static int msm_dp_ctrl_link_train_1_2(struct msm_dp_ctrl_private *ctrl, } static int msm_dp_ctrl_link_train(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel, int *training_step) { int i; int ret = 0; - const u8 *dpcd = ctrl->panel->dpcd; + const u8 *dpcd = panel->dpcd; u8 encoding[] = { 0, DP_SET_ANSI_8B10B }; u8 assr; struct msm_dp_link_info link_info = {0}; - msm_dp_ctrl_config_ctrl_link(ctrl); - msm_dp_ctrl_config_ctrl_streams(ctrl, ctrl->panel); + msm_dp_ctrl_config_ctrl_link(ctrl, panel); + msm_dp_ctrl_config_ctrl_streams(ctrl, panel); link_info.num_lanes = ctrl->link->link_params.num_lanes; link_info.rate = ctrl->link->link_params.rate; @@ -1675,8 +1684,8 @@ static int msm_dp_ctrl_link_train(struct msm_dp_ctrl_private *ctrl, for (i = ctrl->link->lttpr_count - 1; i >= 0; i--) { enum drm_dp_phy dp_phy = DP_PHY_LTTPR(i); - ret = msm_dp_ctrl_link_train_1_2(ctrl, training_step, dp_phy); - msm_dp_ctrl_clear_training_pattern(ctrl, dp_phy); + ret = msm_dp_ctrl_link_train_1_2(ctrl, panel, training_step, dp_phy); + msm_dp_ctrl_clear_training_pattern(ctrl, panel, dp_phy); if (ret) break; @@ -1687,7 +1696,7 @@ static int msm_dp_ctrl_link_train(struct msm_dp_ctrl_private *ctrl, goto end; } - ret = msm_dp_ctrl_link_train_1_2(ctrl, training_step, DP_PHY_DPRX); + ret = msm_dp_ctrl_link_train_1_2(ctrl, panel, training_step, DP_PHY_DPRX); if (ret) { DRM_ERROR("link training on sink failed. ret=%d\n", ret); goto end; @@ -1700,6 +1709,7 @@ static int msm_dp_ctrl_link_train(struct msm_dp_ctrl_private *ctrl, } static int msm_dp_ctrl_setup_main_link(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel, int *training_step) { int ret = 0; @@ -1715,7 +1725,7 @@ static int msm_dp_ctrl_setup_main_link(struct msm_dp_ctrl_private *ctrl, * a link training pattern, we have to first do soft reset. */ - ret = msm_dp_ctrl_link_train(ctrl, training_step); + ret = msm_dp_ctrl_link_train(ctrl, panel, training_step); return ret; } @@ -1814,11 +1824,12 @@ static void msm_dp_ctrl_link_clk_disable(struct msm_dp_ctrl *msm_dp_ctrl) str_on_off(ctrl->core_clks_on)); } -static int msm_dp_ctrl_enable_mainlink_clocks(struct msm_dp_ctrl_private *ctrl) +static int msm_dp_ctrl_enable_mainlink_clocks(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel) { int ret = 0; struct phy *phy = ctrl->phy; - const u8 *dpcd = ctrl->panel->dpcd; + const u8 *dpcd = panel->dpcd; ctrl->phy_opts.dp.lanes = ctrl->link->link_params.num_lanes; ctrl->phy_opts.dp.link_rate = ctrl->link->link_params.rate / 100; @@ -1870,13 +1881,14 @@ static void msm_dp_ctrl_psr_exit(struct msm_dp_ctrl_private *ctrl) msm_dp_write_link(ctrl, REG_PSR_CMD, cmd); } -void msm_dp_ctrl_config_psr(struct msm_dp_ctrl *msm_dp_ctrl) +void msm_dp_ctrl_config_psr(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel) { struct msm_dp_ctrl_private *ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); u32 cfg; - if (!ctrl->panel->psr_cap.version) + if (!panel->psr_cap.version) return; /* enable PSR1 function */ @@ -1891,12 +1903,13 @@ void msm_dp_ctrl_config_psr(struct msm_dp_ctrl *msm_dp_ctrl) drm_dp_dpcd_write(ctrl->aux, DP_PSR_EN_CFG, &cfg, 1); } -void msm_dp_ctrl_set_psr(struct msm_dp_ctrl *msm_dp_ctrl, bool enter) +void msm_dp_ctrl_set_psr(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel, bool enter) { struct msm_dp_ctrl_private *ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); - if (!ctrl->panel->psr_cap.version) + if (!panel->psr_cap.version) return; /* @@ -1966,7 +1979,8 @@ void msm_dp_ctrl_phy_exit(struct msm_dp_ctrl *msm_dp_ctrl) phy_exit(phy); } -static int msm_dp_ctrl_reinitialize_mainlink(struct msm_dp_ctrl_private *ctrl) +static int msm_dp_ctrl_reinitialize_mainlink(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel) { struct phy *phy = ctrl->phy; int ret = 0; @@ -1987,7 +2001,7 @@ static int msm_dp_ctrl_reinitialize_mainlink(struct msm_dp_ctrl_private *ctrl) /* hw recommended delay before re-enabling clocks */ msleep(20); - ret = msm_dp_ctrl_enable_mainlink_clocks(ctrl); + ret = msm_dp_ctrl_enable_mainlink_clocks(ctrl, panel); if (ret) { DRM_ERROR("Failed to enable mainlink clks. ret=%d\n", ret); return ret; @@ -1996,7 +2010,8 @@ static int msm_dp_ctrl_reinitialize_mainlink(struct msm_dp_ctrl_private *ctrl) return ret; } -static int msm_dp_ctrl_deinitialize_mainlink(struct msm_dp_ctrl_private *ctrl) +static int msm_dp_ctrl_deinitialize_mainlink(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel) { struct phy *phy; @@ -2004,7 +2019,7 @@ static int msm_dp_ctrl_deinitialize_mainlink(struct msm_dp_ctrl_private *ctrl) msm_dp_ctrl_mainlink_disable(ctrl); - msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl); + msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl, panel); dev_pm_opp_set_rate(ctrl->dev, 0); msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl); @@ -2018,7 +2033,8 @@ static int msm_dp_ctrl_deinitialize_mainlink(struct msm_dp_ctrl_private *ctrl) return 0; } -static int msm_dp_ctrl_link_maintenance(struct msm_dp_ctrl_private *ctrl) +static int msm_dp_ctrl_link_maintenance(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel) { int ret = 0; int training_step = DP_TRAINING_NONE; @@ -2028,11 +2044,11 @@ static int msm_dp_ctrl_link_maintenance(struct msm_dp_ctrl_private *ctrl) ctrl->link->phy_params.p_level = 0; ctrl->link->phy_params.v_level = 0; - ret = msm_dp_ctrl_setup_main_link(ctrl, &training_step); + ret = msm_dp_ctrl_setup_main_link(ctrl, panel, &training_step); if (ret) goto end; - msm_dp_ctrl_clear_training_pattern(ctrl, DP_PHY_DPRX); + msm_dp_ctrl_clear_training_pattern(ctrl, panel, DP_PHY_DPRX); msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO); @@ -2211,7 +2227,8 @@ static void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl) } } -static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl) +static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel) { int ret; unsigned long pixel_rate; @@ -2227,15 +2244,15 @@ static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl * running. Add the global reset just before disabling the * link clocks and core clocks. */ - msm_dp_ctrl_off(&ctrl->msm_dp_ctrl); + msm_dp_ctrl_off(&ctrl->msm_dp_ctrl, panel); - ret = msm_dp_ctrl_on_link(&ctrl->msm_dp_ctrl); + ret = msm_dp_ctrl_on_link(&ctrl->msm_dp_ctrl, panel); if (ret) { DRM_ERROR("failed to enable DP link controller\n"); return ret; } - pixel_rate = ctrl->panel->msm_dp_mode.drm_mode.clock; + pixel_rate = panel->msm_dp_mode.drm_mode.clock; ret = msm_dp_ctrl_on_pixel_clk(ctrl, pixel_rate); if (ret) return ret; @@ -2245,7 +2262,8 @@ static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl return 0; } -void msm_dp_ctrl_handle_sink_request(struct msm_dp_ctrl *msm_dp_ctrl) +void msm_dp_ctrl_handle_sink_request(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel) { struct msm_dp_ctrl_private *ctrl; u32 sink_request = 0x0; @@ -2260,14 +2278,14 @@ void msm_dp_ctrl_handle_sink_request(struct msm_dp_ctrl *msm_dp_ctrl) if (sink_request & DP_TEST_LINK_PHY_TEST_PATTERN) { drm_dbg_dp(ctrl->drm_dev, "PHY_TEST_PATTERN request\n"); - if (msm_dp_ctrl_process_phy_test_request(ctrl)) { + if (msm_dp_ctrl_process_phy_test_request(ctrl, panel)) { DRM_ERROR("process phy_test_req failed\n"); return; } } if (sink_request & DP_LINK_STATUS_UPDATED) { - if (msm_dp_ctrl_link_maintenance(ctrl)) { + if (msm_dp_ctrl_link_maintenance(ctrl, panel)) { DRM_ERROR("LM failed: TEST_LINK_TRAINING\n"); return; } @@ -2275,7 +2293,7 @@ void msm_dp_ctrl_handle_sink_request(struct msm_dp_ctrl *msm_dp_ctrl) if (sink_request & DP_TEST_LINK_TRAINING) { msm_dp_link_send_test_response(ctrl->link); - if (msm_dp_ctrl_link_maintenance(ctrl)) { + if (msm_dp_ctrl_link_maintenance(ctrl, panel)) { DRM_ERROR("LM failed: TEST_LINK_TRAINING\n"); return; } @@ -2311,7 +2329,8 @@ static bool msm_dp_ctrl_channel_eq_ok(struct msm_dp_ctrl_private *ctrl) return drm_dp_channel_eq_ok(link_status, num_lanes); } -int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl) +int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel) { int rc = 0; struct msm_dp_ctrl_private *ctrl; @@ -2327,8 +2346,8 @@ int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl) ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); - rate = ctrl->panel->link_info.rate; - pixel_rate = ctrl->panel->msm_dp_mode.drm_mode.clock; + rate = panel->link_info.rate; + pixel_rate = panel->msm_dp_mode.drm_mode.clock; msm_dp_ctrl_core_clk_enable(&ctrl->msm_dp_ctrl); @@ -2340,8 +2359,8 @@ int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl) } else { ctrl->link->link_params.rate = rate; ctrl->link->link_params.num_lanes = - ctrl->panel->link_info.num_lanes; - if (ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420) + panel->link_info.num_lanes; + if (panel->msm_dp_mode.out_fmt_is_yuv_420) pixel_rate >>= 1; } @@ -2349,13 +2368,13 @@ int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl) ctrl->link->link_params.rate, ctrl->link->link_params.num_lanes, pixel_rate); - rc = msm_dp_ctrl_enable_mainlink_clocks(ctrl); + rc = msm_dp_ctrl_enable_mainlink_clocks(ctrl, panel); if (rc) return rc; while (--link_train_max_retries) { training_step = DP_TRAINING_NONE; - rc = msm_dp_ctrl_setup_main_link(ctrl, &training_step); + rc = msm_dp_ctrl_setup_main_link(ctrl, panel, &training_step); if (rc == 0) { /* training completed successfully */ break; @@ -2374,7 +2393,7 @@ int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl) * some lanes are ready, * reduce lane number */ - rc = msm_dp_ctrl_link_lane_down_shift(ctrl); + rc = msm_dp_ctrl_link_lane_down_shift(ctrl, panel); if (rc < 0) { /* lane == 1 already */ /* end with failure */ break; @@ -2395,7 +2414,7 @@ int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl) ctrl->link->link_params.num_lanes)) rc = msm_dp_ctrl_link_rate_down_shift(ctrl); else - rc = msm_dp_ctrl_link_lane_down_shift(ctrl); + rc = msm_dp_ctrl_link_lane_down_shift(ctrl, panel); if (rc < 0) { /* end with failure */ @@ -2403,10 +2422,10 @@ int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl) } /* stop link training before start re training */ - msm_dp_ctrl_clear_training_pattern(ctrl, DP_PHY_DPRX); + msm_dp_ctrl_clear_training_pattern(ctrl, panel, DP_PHY_DPRX); } - rc = msm_dp_ctrl_reinitialize_mainlink(ctrl); + rc = msm_dp_ctrl_reinitialize_mainlink(ctrl, panel); if (rc) { DRM_ERROR("Failed to reinitialize mainlink. rc=%d\n", rc); break; @@ -2427,20 +2446,21 @@ int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl) * link training failed * end txing train pattern here */ - msm_dp_ctrl_clear_training_pattern(ctrl, DP_PHY_DPRX); + msm_dp_ctrl_clear_training_pattern(ctrl, panel, DP_PHY_DPRX); - msm_dp_ctrl_deinitialize_mainlink(ctrl); + msm_dp_ctrl_deinitialize_mainlink(ctrl, panel); rc = -ECONNRESET; } return rc; } -static int msm_dp_ctrl_link_retrain(struct msm_dp_ctrl_private *ctrl) +static int msm_dp_ctrl_link_retrain(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel) { int training_step = DP_TRAINING_NONE; - return msm_dp_ctrl_setup_main_link(ctrl, &training_step); + return msm_dp_ctrl_setup_main_link(ctrl, panel, &training_step); } static void msm_dp_ctrl_config_msa(struct msm_dp_ctrl_private *ctrl, @@ -2511,7 +2531,9 @@ static void msm_dp_ctrl_config_msa(struct msm_dp_ctrl_private *ctrl, msm_dp_write_link(ctrl, REG_DP_SOFTWARE_NVID, nvid); } -int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train) +int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel, + bool force_link_train) { int ret = 0; struct msm_dp_ctrl_private *ctrl; @@ -2530,7 +2552,7 @@ int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_li ctrl->core_clks_on, ctrl->link_clks_on, ctrl->stream_clks_on); if (!ctrl->link_clks_on) { /* link clk is off */ - ret = msm_dp_ctrl_enable_mainlink_clocks(ctrl); + ret = msm_dp_ctrl_enable_mainlink_clocks(ctrl, panel); if (ret) { DRM_ERROR("Failed to start link clocks. ret=%d\n", ret); return ret; @@ -2538,15 +2560,15 @@ int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_li } if (force_link_train || !msm_dp_ctrl_channel_eq_ok(ctrl)) - msm_dp_ctrl_link_retrain(ctrl); + msm_dp_ctrl_link_retrain(ctrl, panel); /* stop txing train pattern to end link training */ - msm_dp_ctrl_clear_training_pattern(ctrl, DP_PHY_DPRX); + msm_dp_ctrl_clear_training_pattern(ctrl, panel, DP_PHY_DPRX); return ret; } -int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl) +int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *panel) { int ret = 0; bool mainlink_ready = false; @@ -2559,10 +2581,10 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl) ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); - pixel_rate_orig = ctrl->panel->msm_dp_mode.drm_mode.clock; + pixel_rate_orig = panel->msm_dp_mode.drm_mode.clock; pixel_rate = pixel_rate_orig; - if (msm_dp_ctrl->wide_bus_en || ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420) + if (msm_dp_ctrl->wide_bus_en || panel->msm_dp_mode.out_fmt_is_yuv_420) pixel_rate >>= 1; drm_dbg_dp(ctrl->drm_dev, "pixel_rate=%lu\n", pixel_rate); @@ -2579,18 +2601,18 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl) msm_dp_ctrl_lane_mapping(ctrl); msm_dp_setup_peripheral_flush(ctrl); - msm_dp_ctrl_config_ctrl_link(ctrl); + msm_dp_ctrl_config_ctrl_link(ctrl, panel); - msm_dp_ctrl_configure_source_params(ctrl); + msm_dp_ctrl_configure_source_params(ctrl, panel); msm_dp_ctrl_config_msa(ctrl, ctrl->link->link_params.rate, pixel_rate_orig, - ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420); + panel->msm_dp_mode.out_fmt_is_yuv_420); - msm_dp_panel_clear_dsc_dto(ctrl->panel); + msm_dp_panel_clear_dsc_dto(panel); - msm_dp_ctrl_setup_tr_unit(ctrl); + msm_dp_ctrl_setup_tr_unit(ctrl, panel); msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO); @@ -2618,7 +2640,8 @@ void msm_dp_ctrl_reinit_phy(struct msm_dp_ctrl *msm_dp_ctrl) phy_init(phy); } -void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl) +void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel) { struct msm_dp_ctrl_private *ctrl; struct phy *phy; @@ -2626,11 +2649,11 @@ void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl) ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); phy = ctrl->phy; - msm_dp_panel_disable_vsc_sdp(ctrl->panel); + msm_dp_panel_disable_vsc_sdp(panel); msm_dp_ctrl_mainlink_disable(ctrl); - msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl); + msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl, panel); msm_dp_ctrl_off_pixel_clk(msm_dp_ctrl); dev_pm_opp_set_rate(ctrl->dev, 0); @@ -2639,7 +2662,8 @@ void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl) phy_power_off(phy); } -irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl) +irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel) { struct msm_dp_ctrl_private *ctrl; u32 isr; @@ -2650,7 +2674,7 @@ irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl) ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); - if (ctrl->panel->psr_cap.version) { + if (panel->psr_cap.version) { isr = msm_dp_ctrl_get_psr_interrupt(ctrl); if (isr) @@ -2739,7 +2763,7 @@ static int msm_dp_ctrl_clk_init(struct msm_dp_ctrl *msm_dp_ctrl) } struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev, struct msm_dp_link *link, - struct msm_dp_panel *panel, struct drm_dp_aux *aux, + struct drm_dp_aux *aux, struct phy *phy, void __iomem *ahb_base, void __iomem *link_base) @@ -2747,7 +2771,7 @@ struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev, struct msm_dp_link *link struct msm_dp_ctrl_private *ctrl; int ret; - if (!dev || !panel || !aux || !link) { + if (!dev || !aux || !link) { DRM_ERROR("invalid input\n"); return ERR_PTR(-EINVAL); } @@ -2775,7 +2799,6 @@ struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev, struct msm_dp_link *link init_completion(&ctrl->video_comp); /* in parameters */ - ctrl->panel = panel; ctrl->aux = aux; ctrl->link = link; ctrl->dev = dev; diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h index 5d615f50d13b..00b430392a52 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.h +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h @@ -16,28 +16,36 @@ struct msm_dp_ctrl { struct phy; -int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl); -int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl); -int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train); -void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl); +int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel); +int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *panel); +int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel, + bool force_link_train); +void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel); void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl); -irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl); -void msm_dp_ctrl_handle_sink_request(struct msm_dp_ctrl *msm_dp_ctrl); +irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel); +void msm_dp_ctrl_handle_sink_request(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel); struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev, struct msm_dp_link *link, - struct msm_dp_panel *panel, struct drm_dp_aux *aux, struct phy *phy, void __iomem *ahb_base, void __iomem *link_base); -void msm_dp_ctrl_reset(struct msm_dp_ctrl *msm_dp_ctrl); +void msm_dp_ctrl_reset(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel); void msm_dp_ctrl_phy_init(struct msm_dp_ctrl *msm_dp_ctrl); void msm_dp_ctrl_phy_exit(struct msm_dp_ctrl *msm_dp_ctrl); void msm_dp_ctrl_irq_phy_exit(struct msm_dp_ctrl *msm_dp_ctrl); -void msm_dp_ctrl_set_psr(struct msm_dp_ctrl *msm_dp_ctrl, bool enable); -void msm_dp_ctrl_config_psr(struct msm_dp_ctrl *msm_dp_ctrl); +void msm_dp_ctrl_set_psr(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel, bool enable); +void msm_dp_ctrl_config_psr(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel); int msm_dp_ctrl_core_clk_enable(struct msm_dp_ctrl *msm_dp_ctrl); void msm_dp_ctrl_core_clk_disable(struct msm_dp_ctrl *msm_dp_ctrl); diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index f8f8f3889ea5..26810b8c3fff 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -359,7 +359,7 @@ static void msm_dp_display_host_init(struct msm_dp_display_private *dp) dp->phy_initialized); msm_dp_ctrl_core_clk_enable(dp->ctrl); - msm_dp_ctrl_reset(dp->ctrl); + msm_dp_ctrl_reset(dp->ctrl, dp->panel); msm_dp_ctrl_enable_irq(dp->ctrl); msm_dp_aux_init(dp->aux); dp->core_initialized = true; @@ -371,7 +371,7 @@ static void msm_dp_display_host_deinit(struct msm_dp_display_private *dp) dp->msm_dp_display.connector_type, dp->core_initialized, dp->phy_initialized); - msm_dp_ctrl_reset(dp->ctrl); + msm_dp_ctrl_reset(dp->ctrl, dp->panel); msm_dp_ctrl_disable_irq(dp->ctrl); msm_dp_aux_deinit(dp->aux); msm_dp_ctrl_core_clk_disable(dp->ctrl); @@ -392,7 +392,7 @@ static int msm_dp_display_handle_irq_hpd(struct msm_dp_display_private *dp) drm_dbg_dp(dp->drm_dev, "%d\n", sink_request); - msm_dp_ctrl_handle_sink_request(dp->ctrl); + msm_dp_ctrl_handle_sink_request(dp->ctrl, dp->panel); if (sink_request & DP_TEST_LINK_VIDEO_PATTERN) msm_dp_display_handle_video_request(dp); @@ -570,8 +570,8 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp) goto error_link; } - dp->ctrl = msm_dp_ctrl_get(dev, dp->link, dp->panel, dp->aux, - phy, dp->ahb_base, dp->link_base); + dp->ctrl = msm_dp_ctrl_get(dev, dp->link, dp->aux, + phy, dp->ahb_base, dp->link_base); if (IS_ERR(dp->ctrl)) { rc = PTR_ERR(dp->ctrl); DRM_ERROR("failed to initialize ctrl, rc = %d\n", rc); @@ -642,14 +642,14 @@ static int msm_dp_display_prepare_link(struct msm_dp_display_private *dp) force_link_train = true; } - rc = msm_dp_ctrl_on_link(dp->ctrl); + rc = msm_dp_ctrl_on_link(dp->ctrl, dp->panel); if (rc) { DRM_ERROR("Failed link training (rc=%d)\n", rc); // TODO: schedule drm_connector_set_link_status_property() return rc; } - return msm_dp_ctrl_prepare_stream_on(dp->ctrl, force_link_train); + return msm_dp_ctrl_prepare_stream_on(dp->ctrl, dp->panel, force_link_train); } static int msm_dp_display_enable(struct msm_dp_display_private *dp) @@ -663,7 +663,7 @@ static int msm_dp_display_enable(struct msm_dp_display_private *dp) return 0; } - rc = msm_dp_ctrl_on_stream(dp->ctrl); + rc = msm_dp_ctrl_on_stream(dp->ctrl, dp->panel); if (!rc) msm_dp_display->power_on = true; @@ -688,7 +688,7 @@ static int msm_dp_display_post_enable(struct msm_dp *msm_dp_display) msm_dp_display_handle_plugged_change(msm_dp_display, true); if (msm_dp_display->psr_supported) - msm_dp_ctrl_config_psr(dp->ctrl); + msm_dp_ctrl_config_psr(dp->ctrl, dp->panel); return 0; } @@ -727,7 +727,7 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp) /* set dongle to D3 (power off) mode */ msm_dp_link_psm_config(dp->link, &dp->panel->link_info, true); - msm_dp_ctrl_off(dp->ctrl); + msm_dp_ctrl_off(dp->ctrl, dp->panel); /* re-init the PHY so that we can listen to Dongle disconnect */ msm_dp_ctrl_reinit_phy(dp->ctrl); } else { @@ -735,7 +735,7 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp) * unplugged interrupt * dongle unplugged out of DUT */ - msm_dp_ctrl_off(dp->ctrl); + msm_dp_ctrl_off(dp->ctrl, dp->panel); msm_dp_display_host_phy_exit(dp); } @@ -879,7 +879,7 @@ void msm_dp_display_set_psr(struct msm_dp *msm_dp_display, bool enter) } dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); - msm_dp_ctrl_set_psr(dp->ctrl, enter); + msm_dp_ctrl_set_psr(dp->ctrl, dp->panel, enter); } /** @@ -989,7 +989,7 @@ static irqreturn_t msm_dp_display_irq_handler(int irq, void *dev_id) } /* DP controller isr */ - ret |= msm_dp_ctrl_isr(dp->ctrl); + ret |= msm_dp_ctrl_isr(dp->ctrl, dp->panel); return ret; } From a57a81926277e9d054b0a8af034bb6a7ccdcfbf7 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Tue, 28 Jul 2026 18:21:40 +0800 Subject: [PATCH 0414/1328] drm/msm/dp: split dp_ctrl_off() into stream and link parts Split dp_ctrl_off() into stream and link parts so that for MST cases we can control the link and pixel parts separately. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/742746/ Link: https://lore.kernel.org/r/20260728-dp_mstclean-v9-11-f7779fce10f4@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_ctrl.c | 10 +++++----- drivers/gpu/drm/msm/dp/dp_ctrl.h | 5 +++-- drivers/gpu/drm/msm/dp/dp_display.c | 7 ++++--- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index 9bed5a2fd2e7..5d896593771e 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -2215,7 +2215,7 @@ static int msm_dp_ctrl_on_pixel_clk(struct msm_dp_ctrl_private *ctrl, unsigned l return ret; } -static void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl) +void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl) { struct msm_dp_ctrl_private *ctrl; @@ -2244,7 +2244,8 @@ static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl * running. Add the global reset just before disabling the * link clocks and core clocks. */ - msm_dp_ctrl_off(&ctrl->msm_dp_ctrl, panel); + msm_dp_ctrl_off_pixel_clk(&ctrl->msm_dp_ctrl); + msm_dp_ctrl_off_link(&ctrl->msm_dp_ctrl, panel); ret = msm_dp_ctrl_on_link(&ctrl->msm_dp_ctrl, panel); if (ret) { @@ -2640,8 +2641,8 @@ void msm_dp_ctrl_reinit_phy(struct msm_dp_ctrl *msm_dp_ctrl) phy_init(phy); } -void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl, - struct msm_dp_panel *panel) +void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel) { struct msm_dp_ctrl_private *ctrl; struct phy *phy; @@ -2655,7 +2656,6 @@ void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl, msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl, panel); - msm_dp_ctrl_off_pixel_clk(msm_dp_ctrl); dev_pm_opp_set_rate(ctrl->dev, 0); msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl); diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h index 00b430392a52..5902cf7e746a 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.h +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h @@ -22,8 +22,9 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel * int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *panel, bool force_link_train); -void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl, - struct msm_dp_panel *panel); +void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel); +void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl); void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl); irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *panel); diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 26810b8c3fff..44f4c2312fc3 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -727,15 +727,16 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp) /* set dongle to D3 (power off) mode */ msm_dp_link_psm_config(dp->link, &dp->panel->link_info, true); - msm_dp_ctrl_off(dp->ctrl, dp->panel); - /* re-init the PHY so that we can listen to Dongle disconnect */ + msm_dp_ctrl_off_pixel_clk(dp->ctrl); + msm_dp_ctrl_off_link(dp->ctrl, dp->panel); msm_dp_ctrl_reinit_phy(dp->ctrl); } else { /* * unplugged interrupt * dongle unplugged out of DUT */ - msm_dp_ctrl_off(dp->ctrl, dp->panel); + msm_dp_ctrl_off_pixel_clk(dp->ctrl); + msm_dp_ctrl_off_link(dp->ctrl, dp->panel); msm_dp_display_host_phy_exit(dp); } From 894a6e16abbec9a523078dbd5f4701fe3c11c283 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Tue, 28 Jul 2026 18:21:41 +0800 Subject: [PATCH 0415/1328] drm/msm/dp: simplify link and clock disable sequence Move the common disable steps out of the sink_count check to make the flow easier to follow. No functional change intended. Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/742749/ Link: https://lore.kernel.org/r/20260728-dp_mstclean-v9-12-f7779fce10f4@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_display.c | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 44f4c2312fc3..b4e1feead765 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -718,27 +718,19 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp) msm_dp_panel_disable_vsc_sdp(dp->panel); - /* dongle is still connected but sinks are disconnected */ - if (dp->link->sink_count == 0) { - /* - * irq_hpd with sink_count = 0 - * hdmi unplugged out of dongle - */ + msm_dp_ctrl_off_pixel_clk(dp->ctrl); - /* set dongle to D3 (power off) mode */ + /* dongle is still connected but sinks are disconnected */ + if (dp->link->sink_count == 0) msm_dp_link_psm_config(dp->link, &dp->panel->link_info, true); - msm_dp_ctrl_off_pixel_clk(dp->ctrl); - msm_dp_ctrl_off_link(dp->ctrl, dp->panel); + + msm_dp_ctrl_off_link(dp->ctrl, dp->panel); + + if (dp->link->sink_count == 0) + /* re-init the PHY so that we can listen to Dongle disconnect */ msm_dp_ctrl_reinit_phy(dp->ctrl); - } else { - /* - * unplugged interrupt - * dongle unplugged out of DUT - */ - msm_dp_ctrl_off_pixel_clk(dp->ctrl); - msm_dp_ctrl_off_link(dp->ctrl, dp->panel); + else msm_dp_display_host_phy_exit(dp); - } msm_dp_display->power_on = false; From e8d91d17f73bf3ba314bbc2953dc3f9551b48770 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Tue, 28 Jul 2026 18:21:42 +0800 Subject: [PATCH 0416/1328] drm/msm/dp: make bridge helpers use dp_display to allow re-use dp_bridge helpers take drm_bridge as an input and extract the dp_display object to be used in the dp_display module. Rather than doing it in a roundabout way, directly pass the dp_display object to these helpers so that the MST bridge can also re-use the same helpers. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/742750/ Link: https://lore.kernel.org/r/20260728-dp_mstclean-v9-13-f7779fce10f4@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_display.c | 41 +++++++++--------------- drivers/gpu/drm/msm/dp/dp_display.h | 9 ++++++ drivers/gpu/drm/msm/dp/dp_drm.c | 48 ++++++++++++++++++++++++++++- drivers/gpu/drm/msm/dp/dp_drm.h | 11 ------- 4 files changed, 70 insertions(+), 39 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index b4e1feead765..66990493917f 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -740,25 +740,22 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp) /** * msm_dp_bridge_mode_valid - callback to determine if specified mode is valid - * @bridge: Pointer to drm bridge structure + * @dp: Pointer to dp display structure * @info: display info * @mode: Pointer to drm mode structure * Returns: Validity status for specified mode */ -enum drm_mode_status msm_dp_bridge_mode_valid(struct drm_bridge *bridge, - const struct drm_display_info *info, - const struct drm_display_mode *mode) +enum drm_mode_status msm_dp_display_mode_valid(struct msm_dp *dp, + const struct drm_display_info *info, + const struct drm_display_mode *mode) { const u32 num_components = 3, default_bpp = 24; struct msm_dp_display_private *msm_dp_display; struct msm_dp_link_info *link_info; u32 mode_rate_khz = 0, supported_rate_khz = 0, mode_bpp = 0; - struct msm_dp *dp; int mode_pclk_khz = mode->clock; bool is_yuv_420; - dp = to_dp_bridge(bridge)->msm_dp_display; - if (!dp || !mode_pclk_khz || !dp->connector) { DRM_ERROR("invalid params\n"); return -EINVAL; @@ -1404,18 +1401,16 @@ int msm_dp_modeset_init(struct msm_dp *msm_dp_display, struct drm_device *dev, return 0; } -void msm_dp_bridge_atomic_pre_enable(struct drm_bridge *drm_bridge, - struct drm_atomic_commit *state) +void msm_dp_display_atomic_pre_enable(struct msm_dp *msm_dp_display, + struct drm_atomic_commit *state) { - struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(drm_bridge); - struct msm_dp *dp = msm_dp_bridge->msm_dp_display; - struct msm_dp_display_private *msm_dp_display; + struct msm_dp_display_private *dp; struct drm_crtc *crtc; struct drm_crtc_state *crtc_state; - msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); + dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); - crtc = drm_atomic_get_new_crtc_for_encoder(state, drm_bridge->encoder); + crtc = drm_atomic_get_new_crtc_for_encoder(state, msm_dp_display->bridge->encoder); if (!crtc) return; crtc_state = drm_atomic_get_new_crtc_state(state, crtc); @@ -1425,14 +1420,12 @@ void msm_dp_bridge_atomic_pre_enable(struct drm_bridge *drm_bridge, * state and runs before the bridge's .atomic_enable(), so the mode must * be programmed here, in .atomic_pre_enable(). */ - msm_dp_display_set_mode(dp, &crtc_state->adjusted_mode, msm_dp_display->panel); + msm_dp_display_set_mode(msm_dp_display, &crtc_state->adjusted_mode, dp->panel); } -void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge, - struct drm_atomic_commit *state) +void msm_dp_display_atomic_enable(struct msm_dp *msm_dp_display, + struct drm_atomic_commit *state) { - struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(drm_bridge); - struct msm_dp *msm_dp_display = msm_dp_bridge->msm_dp_display; int rc = 0; struct msm_dp_display_private *dp; @@ -1457,11 +1450,8 @@ void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge, drm_dbg_dp(msm_dp_display->drm_dev, "type=%d Done\n", msm_dp_display->connector_type); } -void msm_dp_bridge_atomic_disable(struct drm_bridge *drm_bridge, - struct drm_atomic_commit *state) +void msm_dp_display_atomic_disable(struct msm_dp *dp) { - struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(drm_bridge); - struct msm_dp *dp = msm_dp_bridge->msm_dp_display; struct msm_dp_display_private *msm_dp_display; msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); @@ -1478,11 +1468,8 @@ static void msm_dp_display_unprepare(struct msm_dp_display_private *dp) drm_dbg_dp(dp->drm_dev, "type=%d Done\n", msm_dp_display->connector_type); } -void msm_dp_bridge_atomic_post_disable(struct drm_bridge *drm_bridge, - struct drm_atomic_commit *state) +void msm_dp_display_atomic_post_disable(struct msm_dp *dp) { - struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(drm_bridge); - struct msm_dp *dp = msm_dp_bridge->msm_dp_display; struct msm_dp_display_private *msm_dp_display; msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h index 0b65e16c790d..83117e7619f3 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.h +++ b/drivers/gpu/drm/msm/dp/dp_display.h @@ -33,5 +33,14 @@ void msm_dp_display_signal_audio_start(struct msm_dp *msm_dp_display); void msm_dp_display_signal_audio_complete(struct msm_dp *msm_dp_display); void msm_dp_display_set_psr(struct msm_dp *dp, bool enter); void msm_dp_display_debugfs_init(struct msm_dp *msm_dp_display, struct dentry *dentry, bool is_edp); +void msm_dp_display_atomic_post_disable(struct msm_dp *dp_display); +void msm_dp_display_atomic_disable(struct msm_dp *dp_display); +void msm_dp_display_atomic_pre_enable(struct msm_dp *dp_display, + struct drm_atomic_commit *state); +void msm_dp_display_atomic_enable(struct msm_dp *dp_display, + struct drm_atomic_commit *state); +enum drm_mode_status msm_dp_display_mode_valid(struct msm_dp *dp, + const struct drm_display_info *info, + const struct drm_display_mode *mode); #endif /* _DP_DISPLAY_H_ */ diff --git a/drivers/gpu/drm/msm/dp/dp_drm.c b/drivers/gpu/drm/msm/dp/dp_drm.c index 794a0df60414..c1e4147bfef1 100644 --- a/drivers/gpu/drm/msm/dp/dp_drm.c +++ b/drivers/gpu/drm/msm/dp/dp_drm.c @@ -49,6 +49,52 @@ static void msm_dp_bridge_debugfs_init(struct drm_bridge *bridge, struct dentry msm_dp_display_debugfs_init(dp, root, false); } +static void msm_dp_bridge_atomic_pre_enable(struct drm_bridge *drm_bridge, + struct drm_atomic_commit *state) +{ + struct msm_dp_bridge *dp_bridge = to_dp_bridge(drm_bridge); + struct msm_dp *dp = dp_bridge->msm_dp_display; + + msm_dp_display_atomic_pre_enable(dp, state); +} + +static void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge, + struct drm_atomic_commit *state) +{ + struct msm_dp_bridge *dp_bridge = to_dp_bridge(drm_bridge); + struct msm_dp *dp = dp_bridge->msm_dp_display; + + msm_dp_display_atomic_enable(dp, state); +} + +static void msm_dp_bridge_atomic_disable(struct drm_bridge *drm_bridge, + struct drm_atomic_commit *state) +{ + struct msm_dp_bridge *dp_bridge = to_dp_bridge(drm_bridge); + struct msm_dp *dp = dp_bridge->msm_dp_display; + + msm_dp_display_atomic_disable(dp); +} + +static void msm_dp_bridge_atomic_post_disable(struct drm_bridge *drm_bridge, + struct drm_atomic_commit *state) +{ + struct msm_dp_bridge *dp_bridge = to_dp_bridge(drm_bridge); + struct msm_dp *dp = dp_bridge->msm_dp_display; + + msm_dp_display_atomic_post_disable(dp); +} + +static enum drm_mode_status msm_dp_bridge_mode_valid(struct drm_bridge *drm_bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + struct msm_dp_bridge *dp_bridge = to_dp_bridge(drm_bridge); + struct msm_dp *dp = dp_bridge->msm_dp_display; + + return msm_dp_display_mode_valid(dp, info, mode); +} + static const struct drm_bridge_funcs msm_dp_bridge_ops = { .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, @@ -116,7 +162,7 @@ static void msm_edp_bridge_atomic_enable(struct drm_bridge *drm_bridge, return; } - msm_dp_bridge_atomic_enable(drm_bridge, state); + msm_dp_display_atomic_enable(dp, state); } static void msm_edp_bridge_atomic_disable(struct drm_bridge *drm_bridge, diff --git a/drivers/gpu/drm/msm/dp/dp_drm.h b/drivers/gpu/drm/msm/dp/dp_drm.h index 4f733d8118b1..da412c788503 100644 --- a/drivers/gpu/drm/msm/dp/dp_drm.h +++ b/drivers/gpu/drm/msm/dp/dp_drm.h @@ -27,17 +27,6 @@ int msm_dp_bridge_init(struct msm_dp *msm_dp_display, struct drm_device *dev, enum drm_connector_status msm_dp_bridge_detect(struct drm_bridge *bridge, struct drm_connector *connector); -void msm_dp_bridge_atomic_pre_enable(struct drm_bridge *drm_bridge, - struct drm_atomic_commit *state); -void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge, - struct drm_atomic_commit *state); -void msm_dp_bridge_atomic_disable(struct drm_bridge *drm_bridge, - struct drm_atomic_commit *state); -void msm_dp_bridge_atomic_post_disable(struct drm_bridge *drm_bridge, - struct drm_atomic_commit *state); -enum drm_mode_status msm_dp_bridge_mode_valid(struct drm_bridge *bridge, - const struct drm_display_info *info, - const struct drm_display_mode *mode); void msm_dp_bridge_hpd_enable(struct drm_bridge *bridge); void msm_dp_bridge_hpd_disable(struct drm_bridge *bridge); void msm_dp_bridge_hpd_notify(struct drm_bridge *bridge, From c16ba6ba3da4672713dd809d5b9031f1583848fa Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Tue, 28 Jul 2026 18:21:43 +0800 Subject: [PATCH 0417/1328] drm/msm/dp: pass panel to display enable/disable helpers Pass struct msm_dp_panel to the display enable/disable helpers to make them easier to reuse for MST stream handling. Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/742752/ Link: https://lore.kernel.org/r/20260728-dp_mstclean-v9-14-f7779fce10f4@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_display.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 66990493917f..bc646d172abe 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -652,7 +652,8 @@ static int msm_dp_display_prepare_link(struct msm_dp_display_private *dp) return msm_dp_ctrl_prepare_stream_on(dp->ctrl, dp->panel, force_link_train); } -static int msm_dp_display_enable(struct msm_dp_display_private *dp) +static int msm_dp_display_enable(struct msm_dp_display_private *dp, + struct msm_dp_panel *msm_dp_panel) { int rc = 0; struct msm_dp *msm_dp_display = &dp->msm_dp_display; @@ -663,7 +664,7 @@ static int msm_dp_display_enable(struct msm_dp_display_private *dp) return 0; } - rc = msm_dp_ctrl_on_stream(dp->ctrl, dp->panel); + rc = msm_dp_ctrl_on_stream(dp->ctrl, msm_dp_panel); if (!rc) msm_dp_display->power_on = true; @@ -709,22 +710,23 @@ static void msm_dp_display_audio_notify_disable(struct msm_dp_display_private *d msm_dp_display->audio_enabled = false; } -static int msm_dp_display_disable(struct msm_dp_display_private *dp) +static int msm_dp_display_disable(struct msm_dp_display_private *dp, + struct msm_dp_panel *msm_dp_panel) { struct msm_dp *msm_dp_display = &dp->msm_dp_display; if (!msm_dp_display->power_on) return 0; - msm_dp_panel_disable_vsc_sdp(dp->panel); + msm_dp_panel_disable_vsc_sdp(msm_dp_panel); msm_dp_ctrl_off_pixel_clk(dp->ctrl); /* dongle is still connected but sinks are disconnected */ if (dp->link->sink_count == 0) - msm_dp_link_psm_config(dp->link, &dp->panel->link_info, true); + msm_dp_link_psm_config(dp->link, &msm_dp_panel->link_info, true); - msm_dp_ctrl_off_link(dp->ctrl, dp->panel); + msm_dp_ctrl_off_link(dp->ctrl, msm_dp_panel); if (dp->link->sink_count == 0) /* re-init the PHY so that we can listen to Dongle disconnect */ @@ -739,7 +741,7 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp) } /** - * msm_dp_bridge_mode_valid - callback to determine if specified mode is valid + * msm_dp_display_mode_valid - callback to determine if specified mode is valid * @dp: Pointer to dp display structure * @info: display info * @mode: Pointer to drm mode structure @@ -1437,14 +1439,14 @@ void msm_dp_display_atomic_enable(struct msm_dp *msm_dp_display, return; } - rc = msm_dp_display_enable(dp); + rc = msm_dp_display_enable(dp, dp->panel); if (rc) DRM_ERROR("DP display enable failed, rc=%d\n", rc); rc = msm_dp_display_post_enable(msm_dp_display); if (rc) { DRM_ERROR("DP display post enable failed, rc=%d\n", rc); - msm_dp_display_disable(dp); + msm_dp_display_disable(dp, dp->panel); } drm_dbg_dp(msm_dp_display->drm_dev, "type=%d Done\n", msm_dp_display->connector_type); @@ -1479,7 +1481,7 @@ void msm_dp_display_atomic_post_disable(struct msm_dp *dp) msm_dp_display_audio_notify_disable(msm_dp_display); - msm_dp_display_disable(msm_dp_display); + msm_dp_display_disable(msm_dp_display, msm_dp_display->panel); msm_dp_display_unprepare(msm_dp_display); } From 811c38907eab0f66c22c5e5708e6f8eab14d76fa Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Tue, 28 Jul 2026 15:20:45 +0200 Subject: [PATCH 0418/1328] drm/msm/dpu: Drop sneaky dev_pm_opp_set_rate(0) dev_pm_opp_set_rate(0) removes the vote specified in required-opps but does not actually park the clock, making it run without the necessary power backing. Prevent that from happening when _dpu_core_perf_get_core_clk_rate() returns 0. Fixes: 25fdd5933e4c ("drm/msm: Add SDM845 DPU support") Signed-off-by: Konrad Dybcio Patchwork: https://patchwork.freedesktop.org/patch/742779/ Link: https://lore.kernel.org/r/20260728-topic-dpu_power-v1-1-e7783b859a70@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/disp/dpu1/dpu_core_perf.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_core_perf.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_core_perf.c index 2ff255d7795e..fea173e37464 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_core_perf.c +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_core_perf.c @@ -394,6 +394,10 @@ int dpu_core_perf_crtc_update(struct drm_crtc *crtc, trace_dpu_core_perf_update_clk(kms->dev, !crtc->enabled, clk_rate); + /* If we're going offline, PM callbacks will disable the clocks instead */ + if (!clk_rate) + return 0; + clk_rate = min(clk_rate, kms->perf.max_core_clk_rate); ret = dev_pm_opp_set_rate(&kms->pdev->dev, clk_rate); if (ret) { From cebfa9909e27ec7b7cbaec25ee5516cf886baa39 Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Tue, 28 Jul 2026 15:20:46 +0200 Subject: [PATCH 0419/1328] drm/msm/dp: Drop dev_pm_opp_set_rate(0) dev_pm_opp_set_rate(0) removes the vote specified in required-opps but does not actually park the clock, making it run without the necessary power backing. Drop the explicit calls to it. Fixes: c943b4948b58 ("drm/msm/dp: add displayPort driver support") Signed-off-by: Konrad Dybcio Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/742781/ Link: https://lore.kernel.org/r/20260728-topic-dpu_power-v1-2-e7783b859a70@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_ctrl.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index 5d896593771e..59070f399e2d 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -1988,13 +1988,12 @@ static int msm_dp_ctrl_reinitialize_mainlink(struct msm_dp_ctrl_private *ctrl, msm_dp_ctrl_mainlink_disable(ctrl); ctrl->phy_opts.dp.lanes = ctrl->link->link_params.num_lanes; phy_configure(phy, &ctrl->phy_opts); + /* * Disable and re-enable the mainlink clock since the * link clock might have been adjusted as part of the * link maintenance. */ - dev_pm_opp_set_rate(ctrl->dev, 0); - msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl); phy_power_off(phy); @@ -2021,7 +2020,6 @@ static int msm_dp_ctrl_deinitialize_mainlink(struct msm_dp_ctrl_private *ctrl, msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl, panel); - dev_pm_opp_set_rate(ctrl->dev, 0); msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl); phy_power_off(phy); @@ -2656,7 +2654,6 @@ void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl, msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl, panel); - dev_pm_opp_set_rate(ctrl->dev, 0); msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl); phy_power_off(phy); From 06b7ba206561619bb34116f49e0ef26b867ce3aa Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Tue, 28 Jul 2026 15:20:47 +0200 Subject: [PATCH 0420/1328] drm/msm/dsi: Drop dev_pm_opp_set_rate(0) dev_pm_opp_set_rate(0) removes the vote specified in required-opps but does not actually park the clock, making it run without the necessary power backing. Drop the explicit call to it. Every call site of ops->link_clk_disable() is followed by pm_runtime_put(), so the power vote will be rescinded if deemed safe. Fixes: 32d3e0feccfe ("drm/msm: dsi: Use OPP API to set clk/perf state") Signed-off-by: Konrad Dybcio Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/742783/ Link: https://lore.kernel.org/r/20260728-topic-dpu_power-v1-3-e7783b859a70@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dsi/dsi_host.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/gpu/drm/msm/dsi/dsi_host.c b/drivers/gpu/drm/msm/dsi/dsi_host.c index 59125d342fcb..7e4e3718b536 100644 --- a/drivers/gpu/drm/msm/dsi/dsi_host.c +++ b/drivers/gpu/drm/msm/dsi/dsi_host.c @@ -550,8 +550,6 @@ int dsi_link_clk_enable_v2(struct msm_dsi_host *msm_host) void dsi_link_clk_disable_6g(struct msm_dsi_host *msm_host) { - /* Drop the performance state vote */ - dev_pm_opp_set_rate(&msm_host->pdev->dev, 0); clk_disable_unprepare(msm_host->esc_clk); clk_disable_unprepare(msm_host->pixel_clk); clk_disable_unprepare(msm_host->byte_intf_clk); From 140b13475302601368c0cf4e193e66126a49feb3 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Thu, 30 Jul 2026 19:04:56 +0300 Subject: [PATCH 0421/1328] drm/msm: detach the ARM DMA mapping before attaching our own domain On ARM32 with CONFIG_ARM_DMA_USE_IOMMU, arch_setup_dma_ops() creates a dma_iommu_mapping for every IOMMU-backed device and attaches its domain to the device's IOMMU group. That domain is neither the group's default nor its blocking domain, so when msm_iommu_new() later attaches the domain the driver manages itself, __iommu_attach_group() refuses it: if (group->domain && group->domain != group->default_domain && group->domain != group->blocking_domain) return -EBUSY; Both the GPU and the display controller are hit by this on apq8064 (IFC6410), leaving the board with no GPU and no display: adreno 4300000.gpu: failed to load adreno gpu adreno 4300000.gpu: probe with driver adreno failed with error -16 mdp4 5100000.display-controller: [drm:msm_drm_kms_init] *ERROR* failed to load kms mdp4 5100000.display-controller: adev bind failed: -16 Other ARM32 DRM drivers that manage their own domains (tegra, rockchip, exynos) drop the arch mapping first. Do the same in msm_iommu_new(), which both the display and the GPU paths go through. With this the GPU and the KMS device both initialise: [drm] Initialized msm 1.13.0 for 4300000.gpu on minor 0 [drm] Initialized msm-kms 1.13.0 for 5100000.display-controller on minor 1 Assisted-by: Claude:claude-opus-5 Reviewed-by: Konrad Dybcio Signed-off-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/743333/ Link: https://lore.kernel.org/r/20260730-fix-qcom-smmu-v2-3-18e0daf2d836@oss.qualcomm.com --- drivers/gpu/drm/msm/msm_iommu.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/drivers/gpu/drm/msm/msm_iommu.c b/drivers/gpu/drm/msm/msm_iommu.c index 897e42c8d5c8..da6782fca6bd 100644 --- a/drivers/gpu/drm/msm/msm_iommu.c +++ b/drivers/gpu/drm/msm/msm_iommu.c @@ -7,6 +7,15 @@ #include #include #include + +#if defined(CONFIG_ARM_DMA_USE_IOMMU) +#include +#else +#define arm_iommu_detach_device(...) ({ }) +#define arm_iommu_release_mapping(...) ({ }) +#define to_dma_iommu_mapping(dev) NULL +#endif + #include "msm_drv.h" #include "msm_gpu_trace.h" #include "msm_mmu.h" @@ -749,6 +758,19 @@ struct msm_mmu *msm_iommu_new(struct device *dev, unsigned long quirks) mutex_init(&iommu->init_lock); + /* + * ARM32 attaches a DMA mapping domain to every IOMMU-backed device, + * which would make attaching our own domain fail with -EBUSY. + */ + if (IS_ENABLED(CONFIG_ARM_DMA_USE_IOMMU)) { + struct dma_iommu_mapping *mapping = to_dma_iommu_mapping(dev); + + if (mapping) { + arm_iommu_detach_device(dev); + arm_iommu_release_mapping(mapping); + } + } + ret = iommu_attach_device(iommu->domain, dev); if (ret) { iommu_domain_free(domain); From b0c2e98d291b9db03dcbaddd38d3a33428b098a8 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sat, 1 Aug 2026 00:15:52 -0700 Subject: [PATCH 0422/1328] apparmor: fix implicit declaration of function 'decompress_zstd' When CONFIG_ZSTD_DECOMPRESS is not enabled, and neither CONFIG_SECURITY_APPARMOR_EXPORT_BINARY nor CONFIG_SECURITY_APPARMOR_COMPRESSED_POLICY are enabled. The build will fail with implicit declaration of function 'decompress_zstd' because there is not an appropriate stub function, for when the zstd decompression isn't enabled. In addition fix compress_min, and compress_max to be conditional on CONFIG_SECURITY_APPARMOR_EXPORT_BINARY, as they are used with the exported policy. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202608010834.9yIVzhG2-lkp@intel.com/ Fixes: 1c5f27e845e84 ("apparmor: Fix build failure when ZSTD_DECOMPRESS is not enabled") Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 4309555b5541..2ae7404526d1 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -583,6 +583,14 @@ static struct aa_loaddata *aa_get_data_from_compressed(const char __user *userbu } #else +static int decompress_zstd(char *src __always_unused, + size_t slen __always_unused, + char *dst __always_unused, + size_t dlen __always_unused) +{ + return -EINVAL; +} + static struct aa_loaddata *aa_get_data_from_compressed(const char __user *userbuf __always_unused, size_t buffer_size __always_unused, loff_t *pos __always_unused, @@ -1450,6 +1458,12 @@ static int seq_ns_name_show(struct seq_file *seq, void *v) return 0; } +SEQ_NS_FOPS(stacked); +SEQ_NS_FOPS(nsstacked); +SEQ_NS_FOPS(level); +SEQ_NS_FOPS(name); + +#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY static int seq_ns_compress_min_show(struct seq_file *seq, void *v) { seq_printf(seq, "%d\n", AA_MIN_CLEVEL); @@ -1462,16 +1476,11 @@ static int seq_ns_compress_max_show(struct seq_file *seq, void *v) return 0; } -SEQ_NS_FOPS(stacked); -SEQ_NS_FOPS(nsstacked); -SEQ_NS_FOPS(level); -SEQ_NS_FOPS(name); SEQ_NS_FOPS(compress_min); SEQ_NS_FOPS(compress_max); /* policy/raw_data/ * file ops */ -#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY #define SEQ_RAWDATA_FOPS(NAME) \ static int seq_rawdata_ ##NAME ##_open(struct inode *inode, struct file *file)\ { \ @@ -2681,7 +2690,7 @@ static struct aa_sfs_entry aa_sfs_entry_apparmor[] = { AA_SFS_FILE_FOPS(".ns_level", 0444, &seq_ns_level_fops), AA_SFS_FILE_FOPS(".ns_name", 0444, &seq_ns_name_fops), AA_SFS_FILE_FOPS("profiles", 0444, &aa_sfs_profiles_fops), -#ifdef CONFIG_SECURITY_APPARMOR_COMPRESSED_POLICY +#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY AA_SFS_FILE_FOPS("raw_data_compression_level_min", 0444, &seq_ns_compress_min_fops), AA_SFS_FILE_FOPS("raw_data_compression_level_max", 0444, &seq_ns_compress_max_fops), #endif From efede141dc86279e663ac8c28f61c19cf7e36c84 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sun, 2 Aug 2026 23:16:21 -0700 Subject: [PATCH 0423/1328] apparmor: Fix warning: 'decompress_zstd' defined but not used decompress_zstd() is used in two cases: - CONFIG SECURITY_APPARMOR_COMPRESSED_POLICY: which allows for compressed policy to be loaded - CONFIG_SECURITY_APPARMOR_EXPORT_BINARY: which allows introspecting loaded policy, that is stored in compressed form until it is needed. When neither of these are selected there is no need for decpress_zstd(), which results in the decompressed_zstd defined but not used message. Only define decmpress_zstd() if either of those config options are enabled. The stub routine is not needed because all calling code is gated by one of those config options. Fixes: 1c5f27e845e84 ("apparmor: Fix build failure when ZSTD_DECOMPRESS is not enabled") Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 2ae7404526d1..eaee8dc300fa 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -484,7 +484,8 @@ static struct aa_loaddata *aa_simple_write_to_buffer(const char __user *userbuf, return data; } -#ifdef CONFIG_SECURITY_APPARMOR_COMPRESSED_POLICY +#if defined(CONFIG_SECURITY_APPARMOR_COMPRESSED_POLICY) || \ + defined(CONFIG_SECURITY_APPARMOR_EXPORT_BINARY) static int decompress_zstd(char *src, size_t slen, char *dst, size_t dlen) { if (slen < dlen) { @@ -519,7 +520,10 @@ static int decompress_zstd(char *src, size_t slen, char *dst, size_t dlen) memcpy(dst, src, slen); return 0; } +#endif + +#ifdef CONFIG_SECURITY_APPARMOR_COMPRESSED_POLICY /** * aa_get_data_from_compressed - common routine for getting compressed policy * from user and get both compressed and uncompressed version. @@ -531,7 +535,6 @@ static int decompress_zstd(char *src, size_t slen, char *dst, size_t dlen) * Returns: kernel buffer containing copy of user buffer data or an * ERR_PTR on failure. */ - static struct aa_loaddata *aa_get_data_from_compressed(const char __user *userbuf, size_t buffer_size, loff_t *pos, @@ -583,14 +586,6 @@ static struct aa_loaddata *aa_get_data_from_compressed(const char __user *userbu } #else -static int decompress_zstd(char *src __always_unused, - size_t slen __always_unused, - char *dst __always_unused, - size_t dlen __always_unused) -{ - return -EINVAL; -} - static struct aa_loaddata *aa_get_data_from_compressed(const char __user *userbuf __always_unused, size_t buffer_size __always_unused, loff_t *pos __always_unused, @@ -599,6 +594,7 @@ static struct aa_loaddata *aa_get_data_from_compressed(const char __user *userbu return ERR_PTR(-EINVAL); } #endif /* CONFIG_SECURITY_APPARMOR_COMPRESSED_POLICY */ + struct aa_user_hdr { uint8_t version; uint8_t compress_level; From a54ffce4637acb0db8e695188a6c7f99f14c3576 Mon Sep 17 00:00:00 2001 From: Zhan Xusheng Date: Thu, 16 Jul 2026 10:59:08 +0800 Subject: [PATCH 0424/1328] f2fs: use the mount idmap for the owner check in f2fs_xattr_advise_set() f2fs_xattr_advise_set() calls inode_owner_or_capable() with &nop_mnt_idmap before allowing the "system.advise" xattr to be set, instead of the idmap that the VFS passes to the ->set() handler. f2fs supports idmapped mounts, so on such a mount this checks the caller's fsuid against the unmapped on-disk owner rather than the mapped owner: the actual owner can be wrongly denied with -EPERM and an unrelated caller wrongly allowed. Pass the handler's idmap instead. Fixes: 01beba7957a2 ("fs: port inode_owner_or_capable() to mnt_idmap") Cc: stable@vger.kernel.org Signed-off-by: Zhan Xusheng Reviewed-by: Chao Yu Acked-by: Christian Brauner (Amutable) Signed-off-by: Jaegeuk Kim --- fs/f2fs/xattr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/f2fs/xattr.c b/fs/f2fs/xattr.c index ed33e5110f2a..6728d1488cad 100644 --- a/fs/f2fs/xattr.c +++ b/fs/f2fs/xattr.c @@ -119,7 +119,7 @@ static int f2fs_xattr_advise_set(const struct xattr_handler *handler, unsigned char old_advise = F2FS_I(inode)->i_advise; unsigned char new_advise; - if (!inode_owner_or_capable(&nop_mnt_idmap, inode)) + if (!inode_owner_or_capable(idmap, inode)) return -EPERM; if (value == NULL) return -EINVAL; From e533889fc26aea0cd83c90327063f272061dd820 Mon Sep 17 00:00:00 2001 From: Wenjie Qi Date: Tue, 30 Jun 2026 11:17:00 +0800 Subject: [PATCH 0425/1328] f2fs: validate MOVE_RANGE destination size F2FS_IOC_MOVE_RANGE checks the source range, but not the destination end before updating i_size. A source hole can expose this: __clone_blkaddrs() skips NULL_ADDR entries and returns success, so the caller can still extend the destination inode with unchecked pos_out + len. Reject destination overflow and use inode_newsize_ok() before extending the destination inode. Fixes: 4dd6f977fc77 ("f2fs: support an ioctl to move a range of data blocks") Cc: stable@kernel.org Assisted-by: Codex:gpt-5.5 Signed-off-by: Wenjie Qi Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/file.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index c54897a25981..170c485eafaa 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -3127,8 +3127,9 @@ static int f2fs_move_file_range(struct file *file_in, loff_t pos_in, struct inode *dst = file_inode(file_out); struct f2fs_sb_info *sbi = F2FS_I_SB(src); struct f2fs_lock_context lc; - size_t olen = len, dst_max_i_size = 0; - size_t dst_osize; + size_t olen = len; + loff_t dst_max_i_size = 0; + loff_t dst_osize, dst_end; int ret; if (file_in->f_path.mnt != file_out->f_path.mnt || @@ -3185,8 +3186,15 @@ static int f2fs_move_file_range(struct file *file_in, loff_t pos_in, } dst_osize = dst->i_size; - if (pos_out + olen > dst->i_size) - dst_max_i_size = pos_out + olen; + if (olen > LLONG_MAX - pos_out) + goto out_unlock; + dst_end = pos_out + olen; + if (dst_end > dst->i_size) { + ret = inode_newsize_ok(dst, dst_end); + if (ret) + goto out_unlock; + dst_max_i_size = dst_end; + } /* verify the end result is block aligned */ if (!IS_ALIGNED(pos_in, F2FS_BLKSIZE) || From 01027b2fcb74dade59fb833b51023f6593b6a9a2 Mon Sep 17 00:00:00 2001 From: Wenjie Qi Date: Tue, 30 Jun 2026 16:23:30 +0800 Subject: [PATCH 0426/1328] f2fs: limit recovery filename logging to stored length F2FS stores recovery filenames as a length plus a fixed-size i_name buffer. The buffer is not NUL-terminated, but recover_inode() and recover_dentry() print it with %s. For a 255-byte filename, recovery logging can read past i_name into the following raw inode fields. Print the name with a precision bounded by i_namelen and F2FS_NAME_LEN. Fixes: f356fe0cba0e ("f2fs: add debug msgs in the recovery routine") Cc: stable@kernel.org Assisted-by: Codex:gpt-5.5 Signed-off-by: Wenjie Qi Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/recovery.c | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/fs/f2fs/recovery.c b/fs/f2fs/recovery.c index 89af8407b667..3fecfdbd5958 100644 --- a/fs/f2fs/recovery.c +++ b/fs/f2fs/recovery.c @@ -158,6 +158,22 @@ static int init_recovered_filename(const struct inode *dir, return 0; } +static const char *recover_printable_name(struct inode *inode, + struct f2fs_inode *raw, + int *name_len) +{ + static const char encrypted_name[] = ""; + + if (file_enc_name(inode)) { + *name_len = sizeof(encrypted_name) - 1; + return encrypted_name; + } + + *name_len = min_t(unsigned int, le32_to_cpu(raw->i_namelen), + F2FS_NAME_LEN); + return raw->i_name; +} + static int recover_dentry(struct inode *inode, struct folio *ifolio, struct list_head *dir_list) { @@ -170,7 +186,8 @@ static int recover_dentry(struct inode *inode, struct folio *ifolio, struct inode *dir, *einode; struct fsync_inode_entry *entry; int err = 0; - char *name; + const char *name; + int name_len; entry = get_fsync_inode(dir_list, pino); if (!entry) { @@ -229,12 +246,9 @@ static int recover_dentry(struct inode *inode, struct folio *ifolio, out_put: f2fs_folio_put(folio, false); out: - if (file_enc_name(inode)) - name = ""; - else - name = raw_inode->i_name; - f2fs_notice(F2FS_I_SB(inode), "%s: ino = %x, name = %s, dir = %llu, err = %d", - __func__, ino_of_node(ifolio), name, + name = recover_printable_name(inode, raw_inode, &name_len); + f2fs_notice(F2FS_I_SB(inode), "%s: ino = %x, name = %.*s, dir = %llu, err = %d", + __func__, ino_of_node(ifolio), name_len, name, IS_ERR(dir) ? 0 : dir->i_ino, err); return err; } @@ -282,7 +296,8 @@ static int recover_inode(struct inode *inode, struct folio *folio) { struct f2fs_inode *raw = F2FS_INODE(folio); struct f2fs_inode_info *fi = F2FS_I(inode); - char *name; + const char *name; + int name_len; int err; inode->i_mode = le16_to_cpu(raw->i_mode); @@ -331,13 +346,11 @@ static int recover_inode(struct inode *inode, struct folio *folio) f2fs_mark_inode_dirty_sync(inode, true); - if (file_enc_name(inode)) - name = ""; - else - name = F2FS_INODE(folio)->i_name; + name = recover_printable_name(inode, raw, &name_len); - f2fs_notice(F2FS_I_SB(inode), "recover_inode: ino = %x, name = %s, inline = %x", - ino_of_node(folio), name, raw->i_inline); + f2fs_notice(F2FS_I_SB(inode), "%s: ino = %x, name = %.*s, inline = %x", + __func__, ino_of_node(folio), name_len, name, + raw->i_inline); return 0; } From 85171332742e741ccd6f401c69b6e0d698119e72 Mon Sep 17 00:00:00 2001 From: Wenjie Qi Date: Mon, 27 Jul 2026 21:04:29 +0800 Subject: [PATCH 0427/1328] f2fs: only redirty pinned folios in redirty_blocks redirty_blocks() pins folios with read_cache_folio() and then walks the same range again with filemap_lock_folio() to redirty them and drop the references it took. Commit 5951fee46bef ("f2fs: Use a folio in redirty_blocks()") changed the second pass to a do/while loop. If read_cache_folio() fails before anything is pinned, page_idx does not advance but the cleanup loop still runs once. If readahead has already populated the failed folio in page cache, that extra iteration finds it and folio_put_refs(folio, 2) drops one reference too many. Later drop_caches or reclaim can then report "BUG: Bad page state". Only redirty the range that was pinned successfully. Fixes: 5951fee46bef ("f2fs: Use a folio in redirty_blocks()") Cc: stable@kernel.org Assisted-by: Codex:gpt-5.5 Signed-off-by: Wenjie Qi Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/file.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index 170c485eafaa..2773c6ce7ec1 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -4487,7 +4487,7 @@ static int redirty_blocks(struct inode *inode, pgoff_t page_idx, int len) page_idx = folio_next_index(folio); } while (page_len < len); - do { + while (redirty_idx < page_idx) { folio = filemap_lock_folio(mapping, redirty_idx); /* It will never fail, when folio has pinned above */ @@ -4500,7 +4500,7 @@ static int redirty_blocks(struct inode *inode, pgoff_t page_idx, int len) redirty_idx = folio_next_index(folio); folio_unlock(folio); folio_put_refs(folio, 2); - } while (redirty_idx < page_idx); + } return ret; } From f8a4108800254d6f7b2755515fbbd9d0caac561e Mon Sep 17 00:00:00 2001 From: Wenjie Qi Date: Mon, 27 Jul 2026 15:49:10 +0800 Subject: [PATCH 0428/1328] f2fs: return writeback error from collapse range f2fs_collapse_range() writes back pages moved by f2fs_do_collapse(), but ignores the return value. If writeback fails, the ioctl can still truncate page cache, shrink blocks, and report success. Return the error before truncating page cache or updating the file size. Fixes: b4ace3370324 ("f2fs: support FALLOC_FL_COLLAPSE_RANGE") Cc: stable@kernel.org Assisted-by: Codex:gpt-5.5 Signed-off-by: Wenjie Qi Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/file.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index 2773c6ce7ec1..c19f0de271a9 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -1623,11 +1623,14 @@ static int f2fs_collapse_range(struct inode *inode, loff_t offset, loff_t len) /* write out all moved pages, if possible */ filemap_invalidate_lock(inode->i_mapping); - filemap_write_and_wait_range(inode->i_mapping, offset, LLONG_MAX); + ret = filemap_write_and_wait_range(inode->i_mapping, offset, LLONG_MAX); + if (ret) + goto out_unlock; truncate_pagecache(inode, offset); new_size = i_size_read(inode) - len; ret = f2fs_truncate_blocks(inode, new_size, true); +out_unlock: filemap_invalidate_unlock(inode->i_mapping); if (!ret) f2fs_i_size_write(inode, new_size); From 28c1ef094e7c86977d9bf570dc0362fc54e36437 Mon Sep 17 00:00:00 2001 From: Hao-Qun Huang Date: Wed, 8 Jul 2026 14:54:39 +0800 Subject: [PATCH 0429/1328] f2fs: reject overlapping move range after len expansion F2FS_IOC_MOVE_RANGE treats a zero length as a request to move data from pos_in to EOF. However, the same-file overlap check runs before that expansion, so a request with len == 0 bypasses the overlap rejection added for same-file moves. For example, with a four-block file, moving from block 0 to block 1 with len == 0 is accepted by the old check because pos_in + len is still pos_in at that point. The code then expands len to cover the rest of the file and calls __exchange_data_block() on overlapping source and destination ranges in the same inode, which is the data-corruption case the overlap check was meant to reject. Move the overlap check after the source range has been validated and len == 0 has been expanded, so it sees the effective length. This is a no-op for non-zero len (the value is unchanged there) and keeps the existing early return for identical positions. Fixes: d95fd91c1ac1 ("f2fs: exclude special cases for f2fs_move_file_range") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-fable-5 Signed-off-by: Hao-Qun Huang Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/file.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index c19f0de271a9..f0b74984b658 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -3154,8 +3154,6 @@ static int f2fs_move_file_range(struct file *file_in, loff_t pos_in, if (src == dst) { if (pos_in == pos_out) return 0; - if (pos_out > pos_in && pos_out < pos_in + len) - return -EINVAL; } inode_lock(src); @@ -3181,6 +3179,8 @@ static int f2fs_move_file_range(struct file *file_in, loff_t pos_in, goto out_unlock; if (len == 0) olen = len = src->i_size - pos_in; + if (src == dst && pos_out > pos_in && pos_out < pos_in + len) + goto out_unlock; if (pos_in + len == src->i_size) len = ALIGN(src->i_size, F2FS_BLKSIZE) - pos_in; if (len == 0) { From 1b4db09b7f10895dc1a0bc32704c7de05188fb1d Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Tue, 23 Jun 2026 20:48:23 +0800 Subject: [PATCH 0430/1328] f2fs: fix to avoid move_range and defragment on device_alias file It's forbidden to migrate blocks of device alias file. Cc: stable@kernel.org Fixes: 128d333f0dff ("f2fs: introduce device aliasing file") Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/file.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index f0b74984b658..b99d9cdf9ba7 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -3092,6 +3092,9 @@ static int f2fs_ioc_defragment(struct file *filp, unsigned long arg) if (f2fs_readonly(sbi->sb)) return -EROFS; + if (IS_DEVICE_ALIASING(inode)) + return -EOPNOTSUPP; + if (copy_from_user(&range, (struct f2fs_defragment __user *)arg, sizeof(range))) return -EFAULT; @@ -3145,7 +3148,8 @@ static int f2fs_move_file_range(struct file *file_in, loff_t pos_in, if (!S_ISREG(src->i_mode) || !S_ISREG(dst->i_mode)) return -EINVAL; - if (IS_ENCRYPTED(src) || IS_ENCRYPTED(dst)) + if (IS_ENCRYPTED(src) || IS_ENCRYPTED(dst) || + IS_DEVICE_ALIASING(src) || IS_DEVICE_ALIASING(dst)) return -EOPNOTSUPP; if (pos_out < 0 || pos_in < 0) From 575f6f8ef2abe8552af96e42c65d70964db53563 Mon Sep 17 00:00:00 2001 From: Zhan Xusheng Date: Tue, 23 Jun 2026 15:26:41 +0800 Subject: [PATCH 0431/1328] f2fs: don't drop the top folio order in the f2fs_iostat tracepoint The f2fs_iostat tracepoint stores the per-order read folio counts in a fixed-size array and prints a fixed number of buckets, both hardcoded to 11. The sysfs iostat accounting array is instead sized by NR_PAGE_ORDERS (= MAX_PAGE_ORDER + 1), which is not always 11: arm64 16K pages -> MAX_PAGE_ORDER 11 -> NR_PAGE_ORDERS 12 arm64 64K pages -> MAX_PAGE_ORDER 13 -> NR_PAGE_ORDERS 14 f2fs enables large folios for immutable, non-compressed files, and the read folio order is bounded by MAX_PAGECACHE_ORDER, i.e. min(MAX_XAS_ORDER, PREFERRED_MAX_PAGECACHE_ORDER). With THP enabled this reaches order 11 on 16K/64K base-page kernels (MAX_XAS_ORDER caps it at 11). So an order-11 read folio is possible there and is accounted into index 11 of the array. On those configurations the sysfs file reports the order-11 count correctly, but the tracepoint silently drops it: the memcpy is capped at min(NR_PAGE_ORDERS, 11), so index 11 is never copied and the trace disagrees with sysfs. There is no memory-safety issue, only the order-11 bucket missing from the trace; 4K-page kernels (NR_PAGE_ORDERS == 11, max order <= 9) are unaffected. Size the array and the printed buckets by a ceiling that covers the largest possible NR_PAGE_ORDERS (14) with headroom, and add a BUILD_BUG_ON() so any future growth of NR_PAGE_ORDERS fails the build loudly instead of silently truncating again. The human-readable "order=count" output is preserved. Fixes: cb8ff3ead9a3 ("f2fs: add page-order information for large folio reads in iostat") Cc: stable@vger.kernel.org Signed-off-by: Zhan Xusheng Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/iostat.c | 6 ++++++ include/trace/events/f2fs.h | 20 ++++++++++++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/fs/f2fs/iostat.c b/fs/f2fs/iostat.c index ae265e3e9b2c..12d4e18a6a50 100644 --- a/fs/f2fs/iostat.c +++ b/fs/f2fs/iostat.c @@ -332,6 +332,12 @@ void f2fs_destroy_iostat_processing(void) int f2fs_init_iostat(struct f2fs_sb_info *sbi) { + /* + * The f2fs_iostat tracepoint emits a fixed number of read folio order + * buckets; make sure every order fits so none is silently dropped. + */ + BUILD_BUG_ON(NR_PAGE_ORDERS > F2FS_IOSTAT_RD_FOLIO_ORDERS); + /* init iostat info */ spin_lock_init(&sbi->iostat_lock); spin_lock_init(&sbi->iostat_lat_lock); diff --git a/include/trace/events/f2fs.h b/include/trace/events/f2fs.h index 270c1a2c24c4..1dd9fc5afc46 100644 --- a/include/trace/events/f2fs.h +++ b/include/trace/events/f2fs.h @@ -2114,6 +2114,14 @@ DEFINE_EVENT(f2fs_zip_end, f2fs_decompress_pages_end, ); #ifdef CONFIG_F2FS_IOSTAT +/* + * Number of read folio order buckets emitted by the f2fs_iostat tracepoint. + * TP_printk() cannot loop, so the field count is fixed here and must be >= + * the largest possible NR_PAGE_ORDERS (14 on arm64 with 64K pages). The + * BUILD_BUG_ON() in f2fs_update_read_folio_count() enforces this. + */ +#define F2FS_IOSTAT_RD_FOLIO_ORDERS 16 + TRACE_EVENT(f2fs_iostat, TP_PROTO(struct f2fs_sb_info *sbi, unsigned long long *iostat, @@ -2151,7 +2159,7 @@ TRACE_EVENT(f2fs_iostat, __field(unsigned long long, fs_mrio) __field(unsigned long long, fs_discard) __field(unsigned long long, fs_reset_zone) - __array(unsigned long long, read_folio_count, 11) + __array(unsigned long long, read_folio_count, F2FS_IOSTAT_RD_FOLIO_ORDERS) ), TP_fast_assign( @@ -2186,7 +2194,8 @@ TRACE_EVENT(f2fs_iostat, __entry->fs_reset_zone = iostat[FS_ZONE_RESET_IO]; memset(__entry->read_folio_count, 0, sizeof(__entry->read_folio_count)); memcpy(__entry->read_folio_count, read_folio_count, - sizeof(unsigned long long) * min_t(int, NR_PAGE_ORDERS, 11)); + sizeof(unsigned long long) * + min_t(int, NR_PAGE_ORDERS, F2FS_IOSTAT_RD_FOLIO_ORDERS)); ), TP_printk("dev = (%d,%d), " @@ -2201,7 +2210,8 @@ TRACE_EVENT(f2fs_iostat, "fs [data=%llu, (gc_data=%llu, cdata=%llu), " "node=%llu, meta=%llu], " "read_folio_count [0=%llu, 1=%llu, 2=%llu, 3=%llu, 4=%llu, " - "5=%llu, 6=%llu, 7=%llu, 8=%llu, 9=%llu, 10=%llu]", + "5=%llu, 6=%llu, 7=%llu, 8=%llu, 9=%llu, 10=%llu, 11=%llu, " + "12=%llu, 13=%llu, 14=%llu, 15=%llu]", show_dev(__entry->dev), __entry->app_wio, __entry->app_dio, __entry->app_bio, __entry->app_mio, __entry->app_bcdio, __entry->app_mcdio, __entry->fs_dio, __entry->fs_cdio, @@ -2218,7 +2228,9 @@ TRACE_EVENT(f2fs_iostat, __entry->read_folio_count[4], __entry->read_folio_count[5], __entry->read_folio_count[6], __entry->read_folio_count[7], __entry->read_folio_count[8], __entry->read_folio_count[9], - __entry->read_folio_count[10]) + __entry->read_folio_count[10], __entry->read_folio_count[11], + __entry->read_folio_count[12], __entry->read_folio_count[13], + __entry->read_folio_count[14], __entry->read_folio_count[15]) ); #ifndef __F2FS_IOSTAT_LATENCY_TYPE From 9ec09d5f4b317a417c8655c14056f70cbe71eb6c Mon Sep 17 00:00:00 2001 From: Joanne Chang Date: Tue, 23 Jun 2026 06:34:28 +0000 Subject: [PATCH 0432/1328] f2fs: dirty directory inodes on mtime/ctime update Xfstests generic/547 sometimes fail with mismatched directory metadata before and after a power failure. This happens because when a directory entry is added, renamed, or deleted, its mtime and ctime are updated and the inode is marked dirty via f2fs_mark_inode_dirty_sync(dir, sync=false). The sync=false flag means the dirty inode is not added to the global DIRTY_META list. Therefore, subsequent checkpoints skip flushing these updated directory blocks, causing directory timestamps to revert to stale values after a sudden power failure. Address this by changing the dirtying parameter to sync=true during directory entry mutations and renames. This forces F2FS to immediately queue the updated directory blocks on the global DIRTY_META list, ensuring timestamps are committed to checkpoints. Fixes: 7c45729a4d6d ("f2fs: keep dirty inodes selectively for checkpoint") Cc: stable@vger.kernel.org Signed-off-by: Joanne Chang Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/dir.c | 6 +++--- fs/f2fs/inline.c | 2 +- fs/f2fs/namei.c | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/fs/f2fs/dir.c b/fs/f2fs/dir.c index 0562ad211351..fd0e2cd31a81 100644 --- a/fs/f2fs/dir.c +++ b/fs/f2fs/dir.c @@ -461,7 +461,7 @@ void f2fs_set_link(struct inode *dir, struct f2fs_dir_entry *de, folio_mark_dirty(folio); inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir)); - f2fs_mark_inode_dirty_sync(dir, false); + f2fs_mark_inode_dirty_sync(dir, true); f2fs_folio_put(folio, true); } @@ -616,7 +616,7 @@ void f2fs_update_parent_metadata(struct inode *dir, struct inode *inode, clear_inode_flag(inode, FI_NEW_INODE); } inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir)); - f2fs_mark_inode_dirty_sync(dir, false); + f2fs_mark_inode_dirty_sync(dir, true); if (F2FS_I(dir)->i_current_depth != current_depth) f2fs_i_depth_write(dir, current_depth); @@ -928,7 +928,7 @@ void f2fs_delete_entry(struct f2fs_dir_entry *dentry, struct folio *folio, f2fs_folio_put(folio, true); inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir)); - f2fs_mark_inode_dirty_sync(dir, false); + f2fs_mark_inode_dirty_sync(dir, true); if (inode) f2fs_drop_nlink(dir, inode); diff --git a/fs/f2fs/inline.c b/fs/f2fs/inline.c index e2f7bedf1552..aec06fb4fd76 100644 --- a/fs/f2fs/inline.c +++ b/fs/f2fs/inline.c @@ -732,7 +732,7 @@ void f2fs_delete_inline_entry(struct f2fs_dir_entry *dentry, f2fs_folio_put(folio, true); inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir)); - f2fs_mark_inode_dirty_sync(dir, false); + f2fs_mark_inode_dirty_sync(dir, true); if (inode) f2fs_drop_nlink(dir, inode); diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c index cac03b8e91a1..7ffdf23cea5e 100644 --- a/fs/f2fs/namei.c +++ b/fs/f2fs/namei.c @@ -1076,7 +1076,7 @@ static int f2fs_rename(struct mnt_idmap *idmap, struct inode *old_dir, f2fs_up_write(&F2FS_I(old_inode)->i_sem); inode_set_ctime_current(old_inode); - f2fs_mark_inode_dirty_sync(old_inode, false); + f2fs_mark_inode_dirty_sync(old_inode, true); f2fs_delete_entry(old_entry, old_folio, old_dir, NULL); old_folio = NULL; @@ -1246,7 +1246,7 @@ static int f2fs_cross_rename(struct inode *old_dir, struct dentry *old_dentry, f2fs_i_links_write(old_dir, old_nlink > 0); f2fs_up_write(&F2FS_I(old_dir)->i_sem); } - f2fs_mark_inode_dirty_sync(old_dir, false); + f2fs_mark_inode_dirty_sync(old_dir, true); /* update directory entry info of new dir inode */ f2fs_set_link(new_dir, new_entry, new_folio, old_inode); @@ -1265,7 +1265,7 @@ static int f2fs_cross_rename(struct inode *old_dir, struct dentry *old_dentry, f2fs_i_links_write(new_dir, new_nlink > 0); f2fs_up_write(&F2FS_I(new_dir)->i_sem); } - f2fs_mark_inode_dirty_sync(new_dir, false); + f2fs_mark_inode_dirty_sync(new_dir, true); if (F2FS_OPTION(sbi).fsync_mode == FSYNC_MODE_STRICT) { f2fs_add_ino_entry(sbi, old_dir->i_ino, TRANS_DIR_INO); From 0f046e45afb783bbf8c5a2eb9abe1f84db270203 Mon Sep 17 00:00:00 2001 From: Zhan Xusheng Date: Fri, 26 Jun 2026 11:07:28 +0800 Subject: [PATCH 0433/1328] f2fs: Remove unused curseg_segno() and curseg_alloc_type() The only callers of curseg_segno() and curseg_alloc_type() were removed by commit 5a4fed7cd97a ("f2fs: simplify do_checkpoint"); both helpers have been unused since then. Being static inline functions they do not trigger -Wunused-function, so the dead code has gone unnoticed. Remove them. No functional change. Signed-off-by: Zhan Xusheng Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/segment.h | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/fs/f2fs/segment.h b/fs/f2fs/segment.h index b0c06b3580b4..33a2257da1e6 100644 --- a/fs/f2fs/segment.h +++ b/fs/f2fs/segment.h @@ -796,20 +796,6 @@ F2FS_IPU_POLICY(F2FS_IPU_ASYNC); F2FS_IPU_POLICY(F2FS_IPU_NOCACHE); F2FS_IPU_POLICY(F2FS_IPU_HONOR_OPU_WRITE); -static inline unsigned int curseg_segno(struct f2fs_sb_info *sbi, - int type) -{ - struct curseg_info *curseg = CURSEG_I(sbi, type); - return curseg->segno; -} - -static inline unsigned char curseg_alloc_type(struct f2fs_sb_info *sbi, - int type) -{ - struct curseg_info *curseg = CURSEG_I(sbi, type); - return curseg->alloc_type; -} - static inline bool valid_main_segno(struct f2fs_sb_info *sbi, unsigned int segno) { From 6dc2b804a042d2ff0fdf4c8e3db78669c36b688d Mon Sep 17 00:00:00 2001 From: Wenjie Qi Date: Mon, 3 Aug 2026 19:41:53 +0800 Subject: [PATCH 0434/1328] f2fs: reject invalid recovered filename lengths Recovery uses raw_inode->i_namelen directly when rebuilding fsynced dentries. A zero-length name uses no dentry slots, so recovery can report success without recreating the dentry. Treat zero-length and oversized recovered names as corruption, mark NEED_FSCK, and stop recovery with -EFSCORRUPTED. Signed-off-by: Wenjie Qi Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/recovery.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/fs/f2fs/recovery.c b/fs/f2fs/recovery.c index 3fecfdbd5958..aaa5227739c8 100644 --- a/fs/f2fs/recovery.c +++ b/fs/f2fs/recovery.c @@ -116,18 +116,26 @@ static void del_fsync_inode(struct fsync_inode_entry *entry, int drop) } static int init_recovered_filename(const struct inode *dir, + struct inode *inode, struct f2fs_inode *raw_inode, struct f2fs_filename *fname, struct qstr *usr_fname) { + struct f2fs_sb_info *sbi = F2FS_I_SB(inode); int err; memset(fname, 0, sizeof(*fname)); fname->disk_name.len = le32_to_cpu(raw_inode->i_namelen); fname->disk_name.name = raw_inode->i_name; - if (WARN_ON(fname->disk_name.len > F2FS_NAME_LEN)) - return -ENAMETOOLONG; + if (unlikely(!fname->disk_name.len || + fname->disk_name.len > F2FS_NAME_LEN)) { + f2fs_err(sbi, "invalid recovered filename length %u for ino %llu", + fname->disk_name.len, inode->i_ino); + set_sbi_flag(sbi, SBI_NEED_FSCK); + f2fs_handle_error(sbi, ERROR_CORRUPTED_INODE); + return -EFSCORRUPTED; + } if (!IS_ENCRYPTED(dir)) { usr_fname->name = fname->disk_name.name; @@ -201,7 +209,7 @@ static int recover_dentry(struct inode *inode, struct folio *ifolio, } dir = entry->inode; - err = init_recovered_filename(dir, raw_inode, &fname, &usr_fname); + err = init_recovered_filename(dir, inode, raw_inode, &fname, &usr_fname); if (err) goto out; retry: From 3de6b80941152a384ee1b9cf88ac1c9dd4eec6dd Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Mon, 3 Aug 2026 09:52:22 -0700 Subject: [PATCH 0435/1328] f2fs: Run f2fs_write_end_io() asynchronously The bio_for_each_segment_all() loop can take more than 10 ms for a large bio on an ARM little core. This is too much for interrupt context. Hence perform the write bio completion work asynchronously if a bio is large and if f2fs_write_end_io() is called from atomic context. This patch reduces the time spent in f2fs_write_end_io() from about 10 ms to about 150 microseconds on an Arm Cortex-A520 core if the max_atc_write_bio_size parameter is changed to 16384. Signed-off-by: Bart Van Assche Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- Documentation/ABI/testing/sysfs-fs-f2fs | 10 ++++++++++ fs/f2fs/data.c | 23 +++++++++++++++++++++-- fs/f2fs/f2fs.h | 2 ++ fs/f2fs/super.c | 1 + fs/f2fs/sysfs.c | 2 ++ 5 files changed, 36 insertions(+), 2 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-fs-f2fs b/Documentation/ABI/testing/sysfs-fs-f2fs index 1b58c029abd0..f4e6a7415cde 100644 --- a/Documentation/ABI/testing/sysfs-fs-f2fs +++ b/Documentation/ABI/testing/sysfs-fs-f2fs @@ -1002,3 +1002,13 @@ Description: It can be used to tune priority of f2fs critical task, e.g. f2fs_ck threads, limitation as below: - it requires user has CAP_SYS_NICE capability. - the range is [100, 139], by default the value is 120. + +What: /sys/fs/f2fs//max_atc_write_bio_size +Date: June 2026 +Contact: Bart Van Assche +Description: Every time a write operation completes f2fs_write_end_io() is + called. This function may be called from an atomic context, + e.g. from inside an interrupt handler. This attribute controls + the maximum size of a write bio that is completed in atomic + (atc) context. The default value for this attribute is UINT_MAX + which means that this functionality is disabled by default. diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c index 042ed8ad9cc3..ef2a567acdac 100644 --- a/fs/f2fs/data.c +++ b/fs/f2fs/data.c @@ -411,13 +411,32 @@ static void f2fs_write_end_bio(struct bio *bio) bio_put(bio); } -static void f2fs_write_end_io(struct bio *bio) +static void f2fs_write_end_io_work(struct work_struct *work) { - iostat_update_and_unbind_ctx(bio); + struct bio *bio = &container_of(work, struct f2fs_bio, work)->bio; f2fs_write_end_bio(bio); } +static void f2fs_write_end_io(struct bio *bio) +{ + struct f2fs_sb_info *sbi; + + iostat_update_and_unbind_ctx(bio); + + sbi = bio->bi_private; + + if (in_atomic() && bio->bi_iter.bi_size > sbi->max_atc_write_bio_size) { + struct work_struct *w; + + w = &container_of(bio, struct f2fs_bio, bio)->work; + INIT_WORK(w, f2fs_write_end_io_work); + queue_work(sbi->wq, w); + } else { + f2fs_write_end_bio(bio); + } +} + #ifdef CONFIG_BLK_DEV_ZONED static void f2fs_zone_write_end_io(struct bio *bio) { diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index 8011bbdf2c68..8e2fb0bda467 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -1800,6 +1800,8 @@ struct f2fs_sb_info { struct f2fs_sm_info *sm_info; /* segment manager */ /* for bio operations */ + /* Largest write bio size completed in atomic context (atc). */ + u32 max_atc_write_bio_size; struct f2fs_bio_info *write_io[NR_PAGE_TYPE]; /* for write bios */ /* keep migration IO order for LFS mode */ struct f2fs_rwsem io_order_lock; diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c index 90a47cf86378..25309e6d4156 100644 --- a/fs/f2fs/super.c +++ b/fs/f2fs/super.c @@ -5068,6 +5068,7 @@ static int f2fs_fill_super(struct super_block *sb, struct fs_context *fc) sb->s_fs_info = sbi; sbi->raw_super = raw_super; + sbi->max_atc_write_bio_size = UINT_MAX; INIT_WORK(&sbi->s_error_work, f2fs_record_error_work); memcpy(sbi->errors, raw_super->s_errors, MAX_F2FS_ERRORS); diff --git a/fs/f2fs/sysfs.c b/fs/f2fs/sysfs.c index be92c05a5420..d9f81edca04a 100644 --- a/fs/f2fs/sysfs.c +++ b/fs/f2fs/sysfs.c @@ -1266,6 +1266,7 @@ F2FS_SBI_RW_ATTR(gc_idle_interval, interval_time[GC_TIME]); F2FS_SBI_RW_ATTR(umount_discard_timeout, interval_time[UMOUNT_DISCARD_TIMEOUT]); F2FS_SBI_RW_ATTR(gc_pin_file_thresh, gc_pin_file_threshold); F2FS_SBI_RW_ATTR(gc_reclaimed_segments, gc_reclaimed_segs); +F2FS_SBI_RW_ATTR(max_atc_write_bio_size, max_atc_write_bio_size); F2FS_SBI_GENERAL_RW_ATTR(max_victim_search); F2FS_SBI_GENERAL_RW_ATTR(migration_granularity); F2FS_SBI_GENERAL_RW_ATTR(migration_window_granularity); @@ -1509,6 +1510,7 @@ static struct attribute *f2fs_attrs[] = { ATTR_LIST(seq_file_ra_mul), ATTR_LIST(gc_segment_mode), ATTR_LIST(gc_reclaimed_segments), + ATTR_LIST(max_atc_write_bio_size), ATTR_LIST(max_fragment_chunk), ATTR_LIST(max_fragment_hole), ATTR_LIST(current_atomic_write), From c7d44572a85ae1b897e0bf95357439ee75ddbcc7 Mon Sep 17 00:00:00 2001 From: Imran Shaik Date: Sat, 18 Jul 2026 18:26:22 +0530 Subject: [PATCH 0436/1328] clk: qcom: Add support for Qualcomm GPU Clock Controller on Shikra The Qualcomm Shikra GPU clock controller is similar to QCM2290 GPUCC hardware block, with minor differences. Hence add support for Shikra GPUCC by extending the QCM2290 GPUCC driver. Reviewed-by: Konrad Dybcio Reviewed-by: Dmitry Baryshkov Signed-off-by: Imran Shaik Link: https://lore.kernel.org/r/20260718-shikra-dispcc-gpucc-v6-15-62703e05ef0f@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gpucc-qcm2290.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/clk/qcom/gpucc-qcm2290.c b/drivers/clk/qcom/gpucc-qcm2290.c index 9b61c066949c..7012b1ae80e6 100644 --- a/drivers/clk/qcom/gpucc-qcm2290.c +++ b/drivers/clk/qcom/gpucc-qcm2290.c @@ -134,6 +134,17 @@ static const struct freq_tbl ftbl_gpu_cc_gx_gfx3d_clk_src[] = { { } }; +static const struct freq_tbl ftbl_gpu_cc_gx_gfx3d_clk_src_shikra[] = { + F(355200000, P_GPU_CC_PLL0_OUT_AUX, 2, 0, 0), + F(537600000, P_GPU_CC_PLL0_OUT_AUX, 2, 0, 0), + F(672000000, P_GPU_CC_PLL0_OUT_AUX2, 2, 0, 0), + F(844800000, P_GPU_CC_PLL0_OUT_AUX2, 2, 0, 0), + F(921600000, P_GPU_CC_PLL0_OUT_AUX2, 2, 0, 0), + F(1017600000, P_GPU_CC_PLL0_OUT_AUX2, 2, 0, 0), + F(1142400000, P_GPU_CC_PLL0_OUT_AUX2, 2, 0, 0), + { } +}; + static struct clk_rcg2 gpu_cc_gx_gfx3d_clk_src = { .cmd_rcgr = 0x101c, .mnd_width = 0, @@ -372,12 +383,16 @@ static const struct qcom_cc_desc gpu_cc_qcm2290_desc = { static const struct of_device_id gpu_cc_qcm2290_match_table[] = { { .compatible = "qcom,qcm2290-gpucc" }, + { .compatible = "qcom,shikra-gpucc" }, { } }; MODULE_DEVICE_TABLE(of, gpu_cc_qcm2290_match_table); static int gpu_cc_qcm2290_probe(struct platform_device *pdev) { + if (device_is_compatible(&pdev->dev, "qcom,shikra-gpucc")) + gpu_cc_gx_gfx3d_clk_src.freq_tbl = ftbl_gpu_cc_gx_gfx3d_clk_src_shikra; + return qcom_cc_probe(pdev, &gpu_cc_qcm2290_desc); } From 5cb33b00c8fbb6e8f1fa3d281c3036d5f7c7c41f Mon Sep 17 00:00:00 2001 From: Wenjie Qi Date: Tue, 4 Aug 2026 09:48:48 +0800 Subject: [PATCH 0437/1328] f2fs: avoid NULL checkpoint thread access in sysfs checkpoint_merge can be enabled even when no checkpoint merge thread is running. A read-only mount is one case: f2fs does not start f2fs_issue_ckpt there, but ckpt_thread_ioprio is still writable through sysfs. The ckpt_thread_ioprio store path updates the saved ioprio value and, when checkpoint_merge is enabled, calls set_task_ioprio() for the checkpoint thread. If cprc->f2fs_issue_ckpt is NULL, that dereferences a NULL task pointer. Protect ckpt_thread_ioprio sysfs writes with s_umount as well, so the checkpoint thread cannot disappear under the store path while updating its ioprio. Fixes: e65920661708 ("f2fs: add ckpt_thread_ioprio sysfs node") Cc: stable@kernel.org Signed-off-by: Wenjie Qi Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/sysfs.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/fs/f2fs/sysfs.c b/fs/f2fs/sysfs.c index d9f81edca04a..c976549ff1bc 100644 --- a/fs/f2fs/sysfs.c +++ b/fs/f2fs/sysfs.c @@ -557,7 +557,7 @@ static ssize_t __sbi_store(struct f2fs_attr *a, return -EINVAL; cprc->ckpt_thread_ioprio = IOPRIO_PRIO_VALUE(class, level); - if (test_opt(sbi, MERGE_CHECKPOINT)) { + if (cprc->f2fs_issue_ckpt) { ret = set_task_ioprio(cprc->f2fs_issue_ckpt, cprc->ckpt_thread_ioprio); if (ret) @@ -1007,13 +1007,14 @@ static ssize_t f2fs_sbi_store(struct f2fs_attr *a, ssize_t ret; bool gc_entry = (!strcmp(a->attr.name, "gc_urgent") || a->struct_type == GC_THREAD); + bool thread_entry = !strcmp(a->attr.name, "ckpt_thread_ioprio"); - if (gc_entry) { + if (gc_entry || thread_entry) { if (!down_read_trylock(&sbi->sb->s_umount)) return -EAGAIN; } ret = __sbi_store(a, sbi, buf, count); - if (gc_entry) + if (gc_entry || thread_entry) up_read(&sbi->sb->s_umount); return ret; From 8e4692c6c165e81b2cbb847d8da4b45a53483b33 Mon Sep 17 00:00:00 2001 From: Wenjie Qi Date: Tue, 4 Aug 2026 09:48:49 +0800 Subject: [PATCH 0438/1328] f2fs: protect critical_task_priority updates with s_umount The sysfs store path already takes s_umount for GC thread control entries, and ckpt_thread_ioprio is covered as well. critical_task_priority also updates checkpoint or GC kthread scheduling state, but it is not covered by that serialization. It can race with remount or teardown paths that are stopping those threads. Protect critical_task_priority sysfs writes with s_umount too. Fixes: 52190933c37a ("f2fs: sysfs: introduce critical_task_priority") Cc: stable@kernel.org Signed-off-by: Wenjie Qi Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/sysfs.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/f2fs/sysfs.c b/fs/f2fs/sysfs.c index c976549ff1bc..3201e2185fea 100644 --- a/fs/f2fs/sysfs.c +++ b/fs/f2fs/sysfs.c @@ -1007,7 +1007,8 @@ static ssize_t f2fs_sbi_store(struct f2fs_attr *a, ssize_t ret; bool gc_entry = (!strcmp(a->attr.name, "gc_urgent") || a->struct_type == GC_THREAD); - bool thread_entry = !strcmp(a->attr.name, "ckpt_thread_ioprio"); + bool thread_entry = !strcmp(a->attr.name, "ckpt_thread_ioprio") || + !strcmp(a->attr.name, "critical_task_priority"); if (gc_entry || thread_entry) { if (!down_read_trylock(&sbi->sb->s_umount)) From 0f9af07ecc1ab486038373db6ae0436c5d674b19 Mon Sep 17 00:00:00 2001 From: Chen Changcheng Date: Tue, 4 Aug 2026 08:54:02 +0800 Subject: [PATCH 0439/1328] f2fs: fix valid block count leak on data block allocation failure In __allocate_data_block(), when allocating a new data block (dn->data_blkaddr == NULL_ADDR), inc_valid_block_count() is called first to increment total_valid_block_count and i_blocks. If the subsequent f2fs_allocate_data_block() fails, the function returns the error directly without rolling back the already-incremented block counts, causing a permanent leak. Fix this by calling dec_valid_block_count() to undo the increment before returning the error. The condition old_blkaddr == NULL_ADDR precisely identifies the case where inc_valid_block_count() was called. Fixes: 7d009e048d7c ("f2fs: fix to handle segment allocation failure correctly") Cc: Reviewed-by: Chao Yu Signed-off-by: Chen Changcheng Signed-off-by: Jaegeuk Kim --- fs/f2fs/data.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c index ef2a567acdac..30e8084da313 100644 --- a/fs/f2fs/data.c +++ b/fs/f2fs/data.c @@ -1552,8 +1552,11 @@ static int __allocate_data_block(struct dnode_of_data *dn, int seg_type) old_blkaddr = dn->data_blkaddr; err = f2fs_allocate_data_block(sbi, NULL, old_blkaddr, &dn->data_blkaddr, &sum, seg_type, NULL); - if (err) + if (err) { + if (old_blkaddr == NULL_ADDR) + dec_valid_block_count(sbi, dn->inode, count); return err; + } if (GET_SEGNO(sbi, old_blkaddr) != NULL_SEGNO) f2fs_invalidate_internal_cache(sbi, old_blkaddr, 1); From ad46c907d7d9975a285c1e89a4adde652eaa93f5 Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Fri, 31 Jul 2026 11:18:49 +0800 Subject: [PATCH 0440/1328] batman-adv: fix stale receive device on merged fragments Fragment reassembly reuses the skb from the highest-numbered buffered fragment as the merged packet. When that fragment was received on a hard interface which is deleted before the chain completes, the merged skb can re-enter the receive path with a stale skb->dev and skb_iif. batadv_batman_skb_recv() passes such merged packets through the normal receive handlers again. DAT and bridge loop avoidance both derive the ARP header length from skb->dev, so they can dereference the freed net_device before the packet reaches the local mesh interface. Refresh the receive device metadata from the current receive device before running the packet handlers. This keeps internally reinjected merged fragments consistent with the normal receive path after hard interface teardown. Fixes: 610bfc6bc99b ("batman-adv: Receive fragmented packets and merge") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Zhiling Zou Signed-off-by: Sven Eckelmann --- net/batman-adv/main.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/batman-adv/main.c b/net/batman-adv/main.c index 77597171d637..d89d44706269 100644 --- a/net/batman-adv/main.c +++ b/net/batman-adv/main.c @@ -492,6 +492,10 @@ int batadv_batman_skb_recv(struct sk_buff *skb, struct net_device *dev, if (!skb) goto err_put; + /* Merged fragments re-enter here with reused skb metadata. */ + skb->dev = dev; + skb->skb_iif = dev->ifindex; + /* packet should hold at least type and version */ if (unlikely(!pskb_may_pull(skb, 2))) goto err_free; From 08645ab95768b88e2ff85a89211994651710465b Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Fri, 3 Jul 2026 00:30:53 +0200 Subject: [PATCH 0441/1328] batman-adv: bla: avoid CRC corruption due to parallel claim add batadv_bla_add_claim() is used to add claims and modify the backbone of claims for CLAIM frames from remote backbones and local packets. When it handles a claim, it needs to either * add the new claim's CRC to the backbone CRC * remove the already existing claim's CRC from the old backbone and add it to the new backbone But when the "new" claim code was running in parallel to the "change backbone" code, it can happen that the CRC was invalid because the backbone_gw of the claim was changed twice in the "new" claim code path: * CPU0 creates the claim for gateway A and publishes it in the claim hash. The crc16 of the address has not yet been added to A's crc at this point. * CPU1 processes a claim frame of gateway B for the same client, finds the just published claim, and performs the ownership change: it switches the pointer to B, removes the crc16 from A's crc - which never contained it - and adds it to B's crc. * CPU0 continues behind the creation branch, unconditionally switches the pointer back to A without compensating B's crc (its remove_crc is false for the creation path), and finally adds the crc16 to A's crc The CRC is then wrong for both: * claim belongs to A: but CRC is not part of backbone A's CRC * claim doesn't belong to B: CRC is still part of backbone B's CRC This wrong CRC is never recomputated from the stored claims. For local backbone claims, this can also not recovered using syncs. To avoid this, split the functionality in clear separate parts: * new claim which always adds claim CRC to the backbone CRC (but never changes the already set backbone_gw of the claim back) * update of existing claim which automatically changes the backbone_gw entry and only updates both backbone CRCs when there was an actual change Cc: stable@vger.kernel.org Reported-by: Sashiko Fixes: 23721387c409 ("batman-adv: add basic bridge loop avoidance code") Signed-off-by: Sven Eckelmann --- net/batman-adv/bridge_loop_avoidance.c | 65 ++++++++++++++++---------- 1 file changed, 40 insertions(+), 25 deletions(-) diff --git a/net/batman-adv/bridge_loop_avoidance.c b/net/batman-adv/bridge_loop_avoidance.c index 94e074235e15..a3530cc90c95 100644 --- a/net/batman-adv/bridge_loop_avoidance.c +++ b/net/batman-adv/bridge_loop_avoidance.c @@ -695,12 +695,14 @@ static void batadv_bla_add_claim(struct batadv_priv *bat_priv, struct batadv_bla_backbone_gw *old_backbone_gw; struct batadv_bla_claim search_claim; struct batadv_bla_claim *claim; - bool remove_crc = false; int hash_added; + u16 claim_crc; + bool changed; ether_addr_copy(search_claim.addr, mac); search_claim.vid = vid; claim = batadv_claim_hash_find(bat_priv, &search_claim); + claim_crc = crc16(0, mac, ETH_ALEN); /* create a new claim entry if it does not exist yet. */ if (!claim) { @@ -732,43 +734,56 @@ static void batadv_bla_add_claim(struct batadv_priv *bat_priv, kfree(claim); return; } - } else { - WRITE_ONCE(claim->lasttime, jiffies); - if (claim->backbone_gw == backbone_gw) - /* no need to register a new backbone */ - goto claim_free_ref; + spin_lock_bh(&backbone_gw->crc_lock); + backbone_gw->crc ^= claim_crc; + spin_unlock_bh(&backbone_gw->crc_lock); + + WRITE_ONCE(backbone_gw->lasttime, jiffies); + + batadv_claim_put(claim); + return; + } + + WRITE_ONCE(claim->lasttime, jiffies); + + /* replace backbone_gw atomically and adjust reference counters */ + spin_lock_bh(&claim->backbone_lock); + if (claim->backbone_gw != backbone_gw) { + changed = true; + + old_backbone_gw = claim->backbone_gw; + kref_get(&backbone_gw->refcount); + claim->backbone_gw = backbone_gw; + } else { + old_backbone_gw = NULL; + changed = false; + } + spin_unlock_bh(&claim->backbone_lock); + + if (changed) { batadv_dbg(BATADV_DBG_BLA, bat_priv, "%s(): changing ownership for %pM, vid %d to gw %pM\n", __func__, mac, batadv_print_vid(vid), backbone_gw->orig); - remove_crc = true; + /* add claim address to new backbone_gw */ + spin_lock_bh(&backbone_gw->crc_lock); + backbone_gw->crc ^= claim_crc; + spin_unlock_bh(&backbone_gw->crc_lock); + + WRITE_ONCE(backbone_gw->lasttime, jiffies); } - /* replace backbone_gw atomically and adjust reference counters */ - spin_lock_bh(&claim->backbone_lock); - old_backbone_gw = claim->backbone_gw; - kref_get(&backbone_gw->refcount); - claim->backbone_gw = backbone_gw; - spin_unlock_bh(&claim->backbone_lock); - - if (remove_crc) { + if (old_backbone_gw) { /* remove claim address from old backbone_gw */ spin_lock_bh(&old_backbone_gw->crc_lock); - old_backbone_gw->crc ^= crc16(0, claim->addr, ETH_ALEN); + old_backbone_gw->crc ^= claim_crc; spin_unlock_bh(&old_backbone_gw->crc_lock); + + batadv_backbone_gw_put(old_backbone_gw); } - batadv_backbone_gw_put(old_backbone_gw); - - /* add claim address to new backbone_gw */ - spin_lock_bh(&backbone_gw->crc_lock); - backbone_gw->crc ^= crc16(0, claim->addr, ETH_ALEN); - spin_unlock_bh(&backbone_gw->crc_lock); - WRITE_ONCE(backbone_gw->lasttime, jiffies); - -claim_free_ref: batadv_claim_put(claim); } From 89f3502ff6878798be96461b2eebd64ba3c3874c Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Sun, 5 Jul 2026 22:21:50 +0200 Subject: [PATCH 0442/1328] batman-adv: bla: prevent CRC corruptions after claim flush When batadv_bla_del_backbone_claims() tried to remove all claims of a backbone, it sets the CRC to 0. It assumes that the it had the last reference of the claims because batadv_claim_release() (which runs after the last reference was released), is XORing the crc16 of the claim address with the backbone CRC. If there would be a parallel holder of any of these references, it could happen that the backbone CRC is (0 ^ crc16(delayed_released_claim)). Which is the wrong starting point for the new claims it may receive when the remote answers the claim request from batadv_bla_send_request(). This reinitializations can be completely dropped to avoid this problem. batadv_claim_release() will take care of fixing the backbone CRC. Cc: stable@vger.kernel.org Fixes: 23721387c409 ("batman-adv: add basic bridge loop avoidance code") Signed-off-by: Sven Eckelmann --- net/batman-adv/bridge_loop_avoidance.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/net/batman-adv/bridge_loop_avoidance.c b/net/batman-adv/bridge_loop_avoidance.c index a3530cc90c95..3194ccdbce60 100644 --- a/net/batman-adv/bridge_loop_avoidance.c +++ b/net/batman-adv/bridge_loop_avoidance.c @@ -325,11 +325,6 @@ batadv_bla_del_backbone_claims(struct batadv_bla_backbone_gw *backbone_gw) } spin_unlock_bh(list_lock); } - - /* all claims gone, initialize CRC */ - spin_lock_bh(&backbone_gw->crc_lock); - backbone_gw->crc = BATADV_BLA_CRC_INIT; - spin_unlock_bh(&backbone_gw->crc_lock); } /** From 0121afa52cdb88cfb4d5d7bd126a23a9100121d8 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Mon, 6 Jul 2026 19:46:37 +0200 Subject: [PATCH 0443/1328] batman-adv: dat: avoid unaligned fault in IP extraction Independent of the alignment of the ARP packet in the SKB, either the batadv_arp_ip_src or the batadv_arp_ip_dst will have an unaligned access (on HW without native unaligned read support). Use get_unaligned() to handle this properly on all architectures. Cc: stable@vger.kernel.org Reported-by: Sashiko Fixes: 5c3a0e553593 ("batman-adv: Distributed ARP Table - add ARP parsing functions") Signed-off-by: Sven Eckelmann --- net/batman-adv/distributed-arp-table.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/net/batman-adv/distributed-arp-table.c b/net/batman-adv/distributed-arp-table.c index 0d5a9cb0affe..76fd23db926c 100644 --- a/net/batman-adv/distributed-arp-table.c +++ b/net/batman-adv/distributed-arp-table.c @@ -316,7 +316,10 @@ static u8 *batadv_arp_hw_src(struct sk_buff *skb, int hdr_size) */ static __be32 batadv_arp_ip_src(struct sk_buff *skb, int hdr_size) { - return *(__force __be32 *)(batadv_arp_hw_src(skb, hdr_size) + ETH_ALEN); + u8 *src = batadv_arp_hw_src(skb, hdr_size) + ETH_ALEN; + __be32 *ip = (__force __be32 *)src; + + return get_unaligned(ip); } /** @@ -341,8 +344,9 @@ static u8 *batadv_arp_hw_dst(struct sk_buff *skb, int hdr_size) static __be32 batadv_arp_ip_dst(struct sk_buff *skb, int hdr_size) { u8 *dst = batadv_arp_hw_src(skb, hdr_size) + ETH_ALEN * 2 + 4; + __be32 *ip = (__force __be32 *)dst; - return *(__force __be32 *)dst; + return get_unaligned(ip); } /** From e6de568d3eda3e3c01c868fabd7a9535d5ee4a73 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Mon, 6 Jul 2026 18:36:47 +0200 Subject: [PATCH 0444/1328] batman-adv: dat: atomically update mac addresses When a MAC address is updated in batadv_dat_entry_add(), it is done using a simple copy function. A parallel reader might only see parts of this update. In worst case, the reader is transporting the half updated MAC address over the network or is creating an ARP response using it - poisoning the ARP cache. atomic64_t can be used to store the 48 bit of a mac address. A reader will then either see the old mac address or the new one - never a mixture of both. Cc: stable@vger.kernel.org Reported-by: Sashiko Fixes: 2f1dfbe18507 ("batman-adv: Distributed ARP Table - implement local storage") Signed-off-by: Sven Eckelmann --- net/batman-adv/distributed-arp-table.c | 58 +++++++++++++++++--------- net/batman-adv/types.h | 2 +- 2 files changed, 40 insertions(+), 20 deletions(-) diff --git a/net/batman-adv/distributed-arp-table.c b/net/batman-adv/distributed-arp-table.c index 76fd23db926c..011cfdc47fab 100644 --- a/net/batman-adv/distributed-arp-table.c +++ b/net/batman-adv/distributed-arp-table.c @@ -444,18 +444,19 @@ batadv_dat_entry_hash_find(struct batadv_priv *bat_priv, __be32 ip, static void batadv_dat_entry_add(struct batadv_priv *bat_priv, __be32 ip, u8 *mac_addr, unsigned short vid) { + u64 u64_mac = ether_addr_to_u64(mac_addr); struct batadv_dat_entry *dat_entry; int hash_added; dat_entry = batadv_dat_entry_hash_find(bat_priv, ip, vid); /* if this entry is already known, just update it */ if (dat_entry) { - if (!batadv_compare_eth(dat_entry->mac_addr, mac_addr)) - ether_addr_copy(dat_entry->mac_addr, mac_addr); + atomic64_set(&dat_entry->mac_addr, u64_mac); + dat_entry->last_update = jiffies; batadv_dbg(BATADV_DBG_DAT, bat_priv, "Entry updated: %pI4 %pM (vid: %d)\n", - &dat_entry->ip, dat_entry->mac_addr, + &dat_entry->ip, mac_addr, batadv_print_vid(vid)); goto out; } @@ -466,7 +467,7 @@ static void batadv_dat_entry_add(struct batadv_priv *bat_priv, __be32 ip, dat_entry->ip = ip; dat_entry->vid = vid; - ether_addr_copy(dat_entry->mac_addr, mac_addr); + atomic64_set(&dat_entry->mac_addr, u64_mac); dat_entry->last_update = jiffies; kref_init(&dat_entry->refcount); @@ -482,7 +483,7 @@ static void batadv_dat_entry_add(struct batadv_priv *bat_priv, __be32 ip, } batadv_dbg(BATADV_DBG_DAT, bat_priv, "New entry added: %pI4 %pM (vid: %d)\n", - &dat_entry->ip, dat_entry->mac_addr, batadv_print_vid(vid)); + &dat_entry->ip, mac_addr, batadv_print_vid(vid)); out: batadv_dat_entry_put(dat_entry); @@ -939,6 +940,8 @@ batadv_dat_cache_dump_entry(struct sk_buff *msg, u32 portid, struct netlink_callback *cb, struct batadv_dat_entry *dat_entry) { + u8 mac[ETH_ALEN]; + u64 u64_mac; int msecs; void *hdr; @@ -951,11 +954,12 @@ batadv_dat_cache_dump_entry(struct sk_buff *msg, u32 portid, genl_dump_check_consistent(cb, hdr); msecs = jiffies_to_msecs(jiffies - dat_entry->last_update); + u64_mac = atomic64_read(&dat_entry->mac_addr); + u64_to_ether_addr(u64_mac, mac); if (nla_put_in_addr(msg, BATADV_ATTR_DAT_CACHE_IP4ADDRESS, dat_entry->ip) || - nla_put(msg, BATADV_ATTR_DAT_CACHE_HWADDRESS, ETH_ALEN, - dat_entry->mac_addr) || + nla_put(msg, BATADV_ATTR_DAT_CACHE_HWADDRESS, ETH_ALEN, mac) || nla_put_u16(msg, BATADV_ATTR_DAT_CACHE_VID, dat_entry->vid) || nla_put_u32(msg, BATADV_ATTR_LAST_SEEN_MSECS, msecs)) { genlmsg_cancel(msg, hdr); @@ -1235,10 +1239,12 @@ bool batadv_dat_snoop_outgoing_arp_request(struct batadv_priv *bat_priv, struct sk_buff *skb_new; unsigned short vid; bool ret = false; + u8 mac[ETH_ALEN]; int hdr_size = 0; __be32 ip_dst; __be32 ip_src; u16 type = 0; + u64 u64_mac; u8 *hw_src; if (!READ_ONCE(bat_priv->distributed_arp_table)) @@ -1267,6 +1273,9 @@ bool batadv_dat_snoop_outgoing_arp_request(struct batadv_priv *bat_priv, dat_entry = batadv_dat_entry_hash_find(bat_priv, ip_dst, vid); if (dat_entry) { + u64_mac = atomic64_read(&dat_entry->mac_addr); + u64_to_ether_addr(u64_mac, mac); + /* If the ARP request is destined for a local client the local * client will answer itself. DAT would only generate a * duplicate packet. @@ -1275,7 +1284,7 @@ bool batadv_dat_snoop_outgoing_arp_request(struct batadv_priv *bat_priv, * additional DAT answer may trigger kernel warnings about * a packet coming from the wrong port. */ - if (batadv_is_my_client(bat_priv, dat_entry->mac_addr, vid)) { + if (batadv_is_my_client(bat_priv, mac, vid)) { ret = true; goto out; } @@ -1285,18 +1294,16 @@ bool batadv_dat_snoop_outgoing_arp_request(struct batadv_priv *bat_priv, * the backbone gws belonging to our backbone has claimed the * destination. */ - if (!batadv_bla_check_claim(bat_priv, - dat_entry->mac_addr, vid)) { + if (!batadv_bla_check_claim(bat_priv, mac, vid)) { batadv_dbg(BATADV_DBG_DAT, bat_priv, "Device %pM claimed by another backbone gw. Don't send ARP reply!", - dat_entry->mac_addr); + mac); ret = true; goto out; } skb_new = batadv_dat_arp_create_reply(bat_priv, ip_dst, ip_src, - dat_entry->mac_addr, - hw_src, vid); + mac, hw_src, vid); if (!skb_new) goto out; @@ -1340,8 +1347,10 @@ bool batadv_dat_snoop_incoming_arp_request(struct batadv_priv *bat_priv, struct sk_buff *skb_new; unsigned short vid; bool ret = false; + u8 mac[ETH_ALEN]; __be32 ip_src; __be32 ip_dst; + u64 u64_mac; u8 *hw_src; u16 type; int err; @@ -1371,8 +1380,11 @@ bool batadv_dat_snoop_incoming_arp_request(struct batadv_priv *bat_priv, if (!dat_entry) goto out; + u64_mac = atomic64_read(&dat_entry->mac_addr); + u64_to_ether_addr(u64_mac, mac); + skb_new = batadv_dat_arp_create_reply(bat_priv, ip_dst, ip_src, - dat_entry->mac_addr, hw_src, vid); + mac, hw_src, vid); if (!skb_new) goto out; @@ -1464,8 +1476,10 @@ bool batadv_dat_snoop_incoming_arp_reply(struct batadv_priv *bat_priv, struct batadv_dat_entry *dat_entry = NULL; bool dropped = false; unsigned short vid; + u8 mac[ETH_ALEN]; __be32 ip_src; __be32 ip_dst; + u64 u64_mac; u8 *hw_src; u8 *hw_dst; u16 type; @@ -1497,11 +1511,17 @@ bool batadv_dat_snoop_incoming_arp_reply(struct batadv_priv *bat_priv, * this frame would lead to doubled receive of an ARP reply. */ dat_entry = batadv_dat_entry_hash_find(bat_priv, ip_src, vid); - if (dat_entry && batadv_compare_eth(hw_src, dat_entry->mac_addr)) { - batadv_dbg(BATADV_DBG_DAT, bat_priv, "Doubled ARP reply removed: ARP MSG = [src: %pM-%pI4 dst: %pM-%pI4]; dat_entry: %pM-%pI4\n", - hw_src, &ip_src, hw_dst, &ip_dst, - dat_entry->mac_addr, &dat_entry->ip); - dropped = true; + if (dat_entry) { + u64_mac = atomic64_read(&dat_entry->mac_addr); + u64_to_ether_addr(u64_mac, mac); + + if (batadv_compare_eth(hw_src, mac)) { + batadv_dbg(BATADV_DBG_DAT, bat_priv, + "Doubled ARP reply removed: ARP MSG = [src: %pM-%pI4 dst: %pM-%pI4]; dat_entry: %pM-%pI4\n", + hw_src, &ip_src, hw_dst, &ip_dst, + mac, &dat_entry->ip); + dropped = true; + } } /* Update our internal cache with both the IP addresses the node got diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h index 42b631573512..9bdc5a3e799e 100644 --- a/net/batman-adv/types.h +++ b/net/batman-adv/types.h @@ -2176,7 +2176,7 @@ struct batadv_dat_entry { __be32 ip; /** @mac_addr: the MAC address associated to the stored IPv4 */ - u8 mac_addr[ETH_ALEN]; + atomic64_t mac_addr; /** @vid: the vlan ID associated to this entry */ unsigned short vid; From 7aedb59b80993c912ab45ce24386a2775150962b Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Thu, 9 Jul 2026 20:44:24 +0200 Subject: [PATCH 0445/1328] batman-adv: fix TX priority extraction for BATADV_FORW_MCAST batadv_mcast_forw_mode_by_count() pushs the skb->data for BATADV_FORW_MCAST forwarding via batadv_mcast_forw_mcsend(). But the batadv_skb_set_priority() expects the ethernet header directly before (skb->data + offset). With the moved skb->data, just some random data would be accessed to get the priority data. Move the batadv_skb_set_priority() before the decision about the handling multicast packets and potential header modifications. Cc: stable@vger.kernel.org Fixes: 90039133221e ("batman-adv: mcast: implement multicast packet generation") Signed-off-by: Sven Eckelmann --- net/batman-adv/mesh-interface.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/net/batman-adv/mesh-interface.c b/net/batman-adv/mesh-interface.c index 8e55b61dd2a6..e202088cf1fd 100644 --- a/net/batman-adv/mesh-interface.c +++ b/net/batman-adv/mesh-interface.c @@ -305,6 +305,8 @@ static netdev_tx_t batadv_interface_tx(struct sk_buff *skb, if (batadv_compare_eth(ethhdr->h_dest, ectp_addr)) goto dropped; + batadv_skb_set_priority(skb, 0); + gw_mode = READ_ONCE(bat_priv->gw.mode); if (is_multicast_ether_addr(ethhdr->h_dest)) { /* if gw mode is off, broadcast every packet */ @@ -338,6 +340,9 @@ static netdev_tx_t batadv_interface_tx(struct sk_buff *skb, send: if (do_bcast && !is_broadcast_ether_addr(ethhdr->h_dest)) { + /* WARNING batadv_mcast_forw_mode might add more headers + * in front of the skb. and might even reallocate the skb + */ forw_mode = batadv_mcast_forw_mode(bat_priv, skb, vid, &mcast_is_routable); switch (forw_mode) { @@ -355,8 +360,6 @@ static netdev_tx_t batadv_interface_tx(struct sk_buff *skb, } } - batadv_skb_set_priority(skb, 0); - /* ethernet packet should be broadcasted */ if (do_bcast) { primary_if = batadv_primary_if_get_selected(bat_priv); From 82bf207f48ebb7a38157f1d91dac884fc9b8cfd8 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Thu, 9 Jul 2026 21:17:08 +0200 Subject: [PATCH 0446/1328] batman-adv: mcast: ensure unshared skb for multicast packets When a packet is transmitted via a batman-adv interface and has already enough room for the header then nothing will make sure that the skbuff is unshared. But it is not allowed to modify a currently shared skbuff. Always make sure that the pskb_expand_head() is not only called for a too small header but also for shared skbuffs. Cc: stable@vger.kernel.org Fixes: 90039133221e ("batman-adv: mcast: implement multicast packet generation") Signed-off-by: Sven Eckelmann --- net/batman-adv/multicast_forw.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/net/batman-adv/multicast_forw.c b/net/batman-adv/multicast_forw.c index bae2a8110976..3927d28dcbb8 100644 --- a/net/batman-adv/multicast_forw.c +++ b/net/batman-adv/multicast_forw.c @@ -1105,8 +1105,7 @@ static int batadv_mcast_forw_expand_head(struct batadv_priv *bat_priv, return -EINVAL; } - if (skb_headroom(skb) < hdr_size && - pskb_expand_head(skb, hdr_size, 0, GFP_ATOMIC) < 0) + if (skb_cow(skb, hdr_size) < 0) return -ENOMEM; return 0; From 6a30a59e2660afd03c975f1b8eae6a2301161197 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Thu, 9 Jul 2026 21:26:13 +0200 Subject: [PATCH 0447/1328] batman-adv: mcast: linearize skbuff for packet generation batadv_mcast_forw_packet() and batadv_mcast_forw_scrape() is not only called (indirectly) by the unsharing+linearizing batadv_recv_mcast_packet() handler. When it is called (indirectly) by batadv_mcast_forw_mcsend() then it will be unshared but not linearized. The SKB_LINEAR_ASSERT() can therefore cause a fatal BUG(). The linearization should happen during the expansion of the head because the scrape function can be hit already during the initial batadv_mcast_forw_mode() selection code: * batadv_interface_tx * batadv_mcast_forw_mode * batadv_mcast_forw_mode_by_count() * batadv_mcast_forw_push() -> calls batadv_mcast_forw_expand_head() before everything else * batadv_mcast_forw_push_tvlvs() * batadv_mcast_forw_push_dests() * batadv_mcast_forw_push_adjust_padding() * batadv_mcast_forw_scrape() Cc: stable@vger.kernel.org Reported-by: Sashiko Fixes: 90039133221e ("batman-adv: mcast: implement multicast packet generation") Signed-off-by: Sven Eckelmann --- net/batman-adv/multicast_forw.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/batman-adv/multicast_forw.c b/net/batman-adv/multicast_forw.c index 3927d28dcbb8..ac97cd8b81dc 100644 --- a/net/batman-adv/multicast_forw.c +++ b/net/batman-adv/multicast_forw.c @@ -1108,6 +1108,10 @@ static int batadv_mcast_forw_expand_head(struct batadv_priv *bat_priv, if (skb_cow(skb, hdr_size) < 0) return -ENOMEM; + /* batadv_mcast_forw_scrape() + batadv_mcast_forw_packet() require linearized skb */ + if (skb_linearize(skb) < 0) + return -ENOMEM; + return 0; } From 8d128c932bced74e3b1625ba3d7c78ef122a88a7 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Wed, 22 Jul 2026 12:08:09 +0200 Subject: [PATCH 0448/1328] batman-adv: bla: fix freeing of claims on meshif deletion When the mesh interface is getting deleted, then batadv_bla_del_backbone_claims() (via batadv_bla_purge_backbone_gw()) could make sure that all claims gets removed. But this function is only executed when bat_priv->bla.claim_hash is not NULL. And since batadv_bla_free() is always setting it to NULL before it is (indirectly) called, it was never actually executed. But the batadv_bla_purge_claims() -> batadv_handle_unclaim() is at the moment too fragile because the BLA code is not handling the rehashing in batadv_bla_update_orig_address(). The stored backbone address doesn't have to be the one actually used for the hash bucket selection during the initial adding of the backbone. The batadv_handle_unclaim() can therefore fail to find the respective backbone for the unclaim and then stop the deletion. But the actual backbone_gw object is not needed for the unclaim because all relevant information is always provided by the caller. And the check for the existence of the backbone_gw doesn't provide any additional security check for the deletion of a claim. Cc: stable@kernel.org Fixes: 23721387c409 ("batman-adv: add basic bridge loop avoidance code") Signed-off-by: Sven Eckelmann --- net/batman-adv/bridge_loop_avoidance.c | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/net/batman-adv/bridge_loop_avoidance.c b/net/batman-adv/bridge_loop_avoidance.c index 3194ccdbce60..f635da4b8ca6 100644 --- a/net/batman-adv/bridge_loop_avoidance.c +++ b/net/batman-adv/bridge_loop_avoidance.c @@ -950,26 +950,18 @@ static bool batadv_handle_unclaim(struct batadv_priv *bat_priv, const u8 *backbone_addr, const u8 *claim_addr, unsigned short vid) { - struct batadv_bla_backbone_gw *backbone_gw; - /* unclaim in any case if it is our own */ if (primary_if && batadv_compare_eth(backbone_addr, primary_if->net_dev->dev_addr)) batadv_bla_send_claim(bat_priv, claim_addr, vid, BATADV_CLAIM_TYPE_UNCLAIM); - backbone_gw = batadv_backbone_hash_find(bat_priv, backbone_addr, vid); - - if (!backbone_gw) - return true; - /* this must be an UNCLAIM frame */ batadv_dbg(BATADV_DBG_BLA, bat_priv, "%s(): UNCLAIM %pM on vid %d (sent by %pM)...\n", __func__, - claim_addr, batadv_print_vid(vid), backbone_gw->orig); + claim_addr, batadv_print_vid(vid), backbone_addr); batadv_bla_del_claim(bat_priv, claim_addr, vid); - batadv_backbone_gw_put(backbone_gw); return true; } From 7e8ee82e69fde9d589272ec5e6f702358903be1f Mon Sep 17 00:00:00 2001 From: Ethan Nelson-Moore Date: Sun, 14 Jun 2026 02:45:38 +0100 Subject: [PATCH 0449/1328] ARM: 9477/1: Disable broken eBPF JIT on the Risc PC The eBPF JIT unconditionally generates ldrh/strh instructions, which do not function correctly on the Risc PC because its bus is unable to signal half-word accesses. Work around this issue by disabling the eBPF JIT when building for ARMv3 (the Risc PC is the only currently supported machine whose kernel is built for ARMv3). Comments from Ethan Nelson-Moore: From LKML: https://lore.kernel.org/all/CAD++jL=0qYGoygUwGEXQL7C_ROnC7kfpRv8RA+H5tNWwYu+pQA@mail.gmail.com/ The commit message has been updated slightly relative to the version on LKML to clarify that the Risc PC is not actually ARMv3. Fixes: 39c13c204bb1 ("arm: eBPF JIT compiler") Cc: stable@vger.kernel.org Signed-off-by: Ethan Nelson-Moore Reviewed-by: Linus Walleij Signed-off-by: Russell King --- arch/arm/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 9187240a02db..b8aa3fcce107 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -98,7 +98,7 @@ config ARM select HAVE_ARCH_TRACEHOOK select HAVE_ARCH_TRANSPARENT_HUGEPAGE if ARM_LPAE select HAVE_ARM_SMCCC if CPU_V7 - select HAVE_EBPF_JIT if !CPU_ENDIAN_BE32 + select HAVE_EBPF_JIT if !CPU_ENDIAN_BE32 && !CPU_32v3 select HAVE_CONTEXT_TRACKING_USER select HAVE_C_RECORDMCOUNT select HAVE_BUILDTIME_MCOUNT_SORT From 8ed9bff906cf8036531d1559f10e82733a52b41f Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sat, 4 Jul 2026 21:15:55 +0100 Subject: [PATCH 0450/1328] ARM: 9481/2: breakpoint: CFI breakpoints only on demand This removes the stub hw_breakpoint_cfi_handler() from ARM, making it not steal breakpoint type 0x03 (ARM_ENTRY_CFI_BREAKPOINT) unless CFI is actively used in the kernel. When not instrumenting with CFI, or when a breakpoint is issued in userspace, we fall through to return 1 from hw_breakpoint_pending() "unhandled fault" so userspace can make use of this breakpoint. Tested with LKDTM and this command line: echo CFI_FORWARD_PROTO > /sys/kernel/debug/provoke-crash/DIRECT still works as expected. Closes: https://lore.kernel.org/lkml/kJqktbpLphg_Pk5I5SPptgTLjl3E3eq5mN5UzCslyFj7Q1Irp-wDid4mj5eQVd2iZtRGXgeZd8goq195EkXdjyt864YMc8mVb2B9NGH91NQ=@protonmail.com/ Fixes: c3f89986fde7 ("ARM: 9391/2: hw_breakpoint: Handle CFI breakpoints") Reported-by: slipher Suggested-by: Mark Rutland Signed-off-by: Linus Walleij Signed-off-by: Russell King --- arch/arm/kernel/hw_breakpoint.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/arch/arm/kernel/hw_breakpoint.c b/arch/arm/kernel/hw_breakpoint.c index cd4b34c96e35..38feb30dfb5f 100644 --- a/arch/arm/kernel/hw_breakpoint.c +++ b/arch/arm/kernel/hw_breakpoint.c @@ -929,10 +929,6 @@ static void hw_breakpoint_cfi_handler(struct pt_regs *regs) break; } } -#else -static void hw_breakpoint_cfi_handler(struct pt_regs *regs) -{ -} #endif /* @@ -964,9 +960,14 @@ static int hw_breakpoint_pending(unsigned long addr, unsigned int fsr, case ARM_ENTRY_SYNC_WATCHPOINT: watchpoint_handler(addr, fsr, regs); break; +#ifdef CONFIG_CFI case ARM_ENTRY_CFI_BREAKPOINT: - hw_breakpoint_cfi_handler(regs); + if (user_mode(regs)) + ret = 1; /* Don't handle userspace BKPT */ + else + hw_breakpoint_cfi_handler(regs); break; +#endif default: ret = 1; /* Unhandled fault. */ } From 8a58a41100ea377e978d99600ec24a9bd0273662 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Fri, 24 Jul 2026 08:05:31 +0100 Subject: [PATCH 0451/1328] ARM: 9483/1: select HAVE_POSIX_CPU_TIMERS_TASK_WORK Commit c6e61c06d606 ("ARM: 9463/1: Allow to enable RT") enabled PREEMPT_RT on ARM but did not select HAVE_POSIX_CPU_TIMERS_TASK_WORK. This leaves CONFIG_POSIX_CPU_TIMERS_TASK_WORK disabled, so CPU timers expire in hard IRQ context. On PREEMPT_RT this makes run_posix_cpu_timers() take the sleeping sighand->siglock: BUG: sleeping function called from invalid context at spinlock_rt.c:48 rt_spin_lock from lock_task_sighand lock_task_sighand from run_posix_cpu_timers run_posix_cpu_timers from update_process_times ARM handles TIF_NOTIFY_RESUME on all return-to-user paths, including v7-M. ARM32 KVM host support was removed by commit 541ad0150ca4 ("arm: Remove 32bit KVM host support"), so the select need not be conditional on KVM. Select it to defer POSIX CPU timer expiry to task context. Reproduced with setrlimit(RLIMIT_CPU, ...) and a busy loop. The same path is used by setitimer(ITIMER_PROF or ITIMER_VIRTUAL) and POSIX CPU timers created with timer_create(). Assisted-by: Claude:claude-fable-5 Fixes: c6e61c06d606 ("ARM: 9463/1: Allow to enable RT") Signed-off-by: Karl Mehltretter Reviewed-by: Arnd Bergmann Reviewed-by: Sebastian Andrzej Siewior Signed-off-by: Russell King --- arch/arm/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index b8aa3fcce107..bcb53bc9b931 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -133,6 +133,7 @@ config ARM select HAVE_PERF_EVENTS select HAVE_PERF_REGS select HAVE_PERF_USER_STACK_DUMP + select HAVE_POSIX_CPU_TIMERS_TASK_WORK select MMU_GATHER_RCU_TABLE_FREE if SMP && ARM_LPAE select HAVE_REGS_AND_STACK_ACCESS_API select HAVE_RSEQ From e79ca91165d4fd18549c536abdb86101e889052f Mon Sep 17 00:00:00 2001 From: Xie Yuanbin Date: Tue, 28 Jul 2026 03:03:22 +0100 Subject: [PATCH 0452/1328] ARM: 9484/1: enable interrupts when unhandled user faults are triggered PREEMPT_RT requires interrupts to be enabled when sending signals. When do_DataAbort()/do_PrefetchAbort() triggers unhandled user faults, that is `inf->fn()` return a non-zero value, and the interrupts are not enabled within the hook function, force_sig_fault() will be called with interrupts disabled. This can be triggered by user programs executing the bkpt instruction, with kernel config CONFIG_PERF_EVENTS=n. Enable interrupts in do_DataAbort()/do_PrefetchAbort() when unhandled user faults are triggered to fix the issue. Fixes: c6e61c06d606 ("ARM: 9463/1: Allow to enable RT") Link: https://lore.kernel.org/20260629123349.134224-1-xieyuanbin1@huawei.com Suggested-by: Russell King Reviewed-by: Sebastian Andrzej Siewior Reviewed-by: Linus Walleij Signed-off-by: Xie Yuanbin Signed-off-by: Russell King --- arch/arm/mm/fault.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/arch/arm/mm/fault.c b/arch/arm/mm/fault.c index e62cc4be5adf..c68677503532 100644 --- a/arch/arm/mm/fault.c +++ b/arch/arm/mm/fault.c @@ -633,6 +633,9 @@ do_DataAbort(unsigned long addr, unsigned int fsr, struct pt_regs *regs) if (!inf->fn(addr, fsr & ~FSR_LNX_PF, regs)) return; + if (likely(user_mode(regs))) + local_irq_enable(); + pr_alert("8<--- cut here ---\n"); pr_alert("Unhandled fault: %s (0x%03x) at 0x%08lx\n", inf->name, fsr, addr); @@ -663,6 +666,9 @@ do_PrefetchAbort(unsigned long addr, unsigned int ifsr, struct pt_regs *regs) if (!inf->fn(addr, ifsr | FSR_LNX_PF, regs)) return; + if (likely(user_mode(regs))) + local_irq_enable(); + pr_alert("8<--- cut here ---\n"); pr_alert("Unhandled prefetch abort: %s (0x%03x) at 0x%08lx\n", inf->name, ifsr, addr); From 1039bffd6ae9c75b42b7d148d6c1106134107b66 Mon Sep 17 00:00:00 2001 From: Xie Yuanbin Date: Tue, 28 Jul 2026 03:16:42 +0100 Subject: [PATCH 0453/1328] ARM: 9485/1: mm: acquire mmap write lock around show_pte() for user faults When CONFIG_DEBUG_USER=y, and cmdline "user_debug=31" is set, a user fault may trigger show_pte() without any lock. If another thread in the same process concurrently calls munmap(), the page table pages may be freed while show_pte() is still traversing them, causing a use-after-free in show_pte(). If CONFIG_ARM_LPAE=y, this may cause a kernel panic if the pages table of PMD are freed when show_pte() is running. Acquire mmap_write_lock() around show_pte() for user faults to fix the contention. For user faults, additionally restrict that show_pte() is called only when the addr is a user-space address (addr < TASK_SIZE). This is because the lock of tsk->mm only protects the virtual memory of user address space, furthermore, dumping the page tables of a kernel-space address for user faults is unnecessary and may have security implications. Keep everything unchanged for kernel faults, because the kernel is already in the "oops" state, acquiring a lock may risk a deadlock. Co-developed-by: Qi Xi Fixes: 6d021b724481 ("ARM: dump pgd, pmd and pte states on unhandled data abort faults") Link: https://lore.kernel.org/20260716014022.2823-1-xieyuanbin1@huawei.com Acked-by: Lorenzo Stoakes (ARM) Reviewed-by: Linus Walleij Signed-off-by: Qi Xi Signed-off-by: Xie Yuanbin Signed-off-by: Russell King --- arch/arm/mm/fault.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/arch/arm/mm/fault.c b/arch/arm/mm/fault.c index c68677503532..0a09d4ff7718 100644 --- a/arch/arm/mm/fault.c +++ b/arch/arm/mm/fault.c @@ -181,7 +181,11 @@ __do_user_fault(unsigned long addr, unsigned int fsr, unsigned int sig, pr_err("8<--- cut here ---\n"); pr_err("%s: unhandled page fault (%d) at 0x%08lx, code 0x%03x\n", tsk->comm, sig, addr, fsr); - show_pte(KERN_ERR, tsk->mm, addr); + if (likely(addr < TASK_SIZE)) { + mmap_write_lock(tsk->mm); + show_pte(KERN_ERR, tsk->mm, addr); + mmap_write_unlock(tsk->mm); + } show_regs(regs); } #endif @@ -639,7 +643,15 @@ do_DataAbort(unsigned long addr, unsigned int fsr, struct pt_regs *regs) pr_alert("8<--- cut here ---\n"); pr_alert("Unhandled fault: %s (0x%03x) at 0x%08lx\n", inf->name, fsr, addr); - show_pte(KERN_ALERT, current->mm, addr); + if (likely(user_mode(regs))) { + if (addr < TASK_SIZE) { + mmap_write_lock(current->mm); + show_pte(KERN_ALERT, current->mm, addr); + mmap_write_unlock(current->mm); + } + } else { + show_pte(KERN_ALERT, current->mm, addr); + } arm_notify_die("", regs, inf->sig, inf->code, (void __user *)addr, fsr, 0); From 88656a46dfa3ecd0c878a0dc6916a1f399818334 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Wed, 5 Aug 2026 17:20:49 +0200 Subject: [PATCH 0454/1328] dt-bindings: clock: Document Renesas R-Car X5H Clock Pulse Generator Document support for the Renesas R-Car X5H Clock Pulse Generator, and add binding definitions for a limited initial set of clocks. Signed-off-by: Geert Uytterhoeven Reviewed-by: Marek Vasut Tested-by: Marek Vasut Acked-by: Conor Dooley Link: https://patch.msgid.link/7a33c99816c850faeec5c17532811faf78959a1d.1785941595.git.geert+renesas@glider.be --- .../bindings/clock/renesas,r8a78000-cpg.yaml | 55 +++++++++++++++++++ .../dt-bindings/clock/renesas,r8a78000-cpg.h | 14 +++++ 2 files changed, 69 insertions(+) create mode 100644 Documentation/devicetree/bindings/clock/renesas,r8a78000-cpg.yaml create mode 100644 include/dt-bindings/clock/renesas,r8a78000-cpg.h diff --git a/Documentation/devicetree/bindings/clock/renesas,r8a78000-cpg.yaml b/Documentation/devicetree/bindings/clock/renesas,r8a78000-cpg.yaml new file mode 100644 index 000000000000..4e23bba33438 --- /dev/null +++ b/Documentation/devicetree/bindings/clock/renesas,r8a78000-cpg.yaml @@ -0,0 +1,55 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/clock/renesas,r8a78000-cpg.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Renesas R-Car X5H Clock Pulse Generator + +maintainers: + - Geert Uytterhoeven + +description: + The R-Car X5H Clock Pulse Generator (CLK CONTROL) consists of oscillators, + PLL circuits, clock dividers and clock control circuits. It provides various + clocks for other modules. + +properties: + compatible: + const: renesas,r8a78000-cpg + + reg: + maxItems: 1 + + clocks: + maxItems: 2 + + clock-names: + items: + - const: extal + - const: extalr + + '#clock-cells': + description: + The single clock specifier cell must be the clock number, as defined in + . + const: 1 + +required: + - compatible + - reg + - clocks + - clock-names + - '#clock-cells' + +additionalProperties: false + +examples: + - | + clock-controller@c1320000 { + compatible = "renesas,r8a78000-cpg"; + reg = <0xc1320000 0x10000>; + clocks = <&extal_clk>, <&extalr_clk>; + clock-names = "extal", "extalr"; + #clock-cells = <1>; + }; diff --git a/include/dt-bindings/clock/renesas,r8a78000-cpg.h b/include/dt-bindings/clock/renesas,r8a78000-cpg.h new file mode 100644 index 000000000000..7fd09a3fb353 --- /dev/null +++ b/include/dt-bindings/clock/renesas,r8a78000-cpg.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ +/* + * Copyright (C) 2026 Glider bv + */ +#ifndef __DT_BINDINGS_CLOCK_RENESAS_R8A78000_CPG_H__ +#define __DT_BINDINGS_CLOCK_RENESAS_R8A78000_CPG_H__ + +/* R-Car X5H Clock Pulse Generator Clocks */ + +#define R8A78000_CPG_SGASYNCD4_PERW_BUS 0 +#define R8A78000_CPG_SGASYNCD8_PERW_BUS 1 +#define R8A78000_CPG_SGASYNCD16_PERW_BUS 2 + +#endif /* __DT_BINDINGS_CLOCK_RENESAS_R8A78000_CPG_H__ */ From 055a410f7c9f7efa2b5524ad837d6cca78dd0b81 Mon Sep 17 00:00:00 2001 From: Ethan Nelson-Moore Date: Sun, 14 Jun 2026 02:56:01 +0100 Subject: [PATCH 0455/1328] ARM: 9478/1: Remove references to removed CONFIG_CPU_ARM92x_CPU_IDLE options Several assembly files in arch/arm/mm contain comments referring to CONFIG_CPU_ARM92x_CPU_IDLE options, which have not existed in the kernel since 2.4.21. Remove them. Discovered while searching for CONFIG_* symbols referenced in code but not defined in any Kconfig file. Reviewed-by: Linus Walleij Signed-off-by: Ethan Nelson-Moore Signed-off-by: Russell King --- arch/arm/mm/proc-arm920.S | 2 -- arch/arm/mm/proc-arm922.S | 2 -- arch/arm/mm/proc-arm925.S | 2 -- arch/arm/mm/proc-arm926.S | 2 -- 4 files changed, 8 deletions(-) diff --git a/arch/arm/mm/proc-arm920.S b/arch/arm/mm/proc-arm920.S index 4727f4b5b6e8..0326067c6c75 100644 --- a/arch/arm/mm/proc-arm920.S +++ b/arch/arm/mm/proc-arm920.S @@ -8,8 +8,6 @@ * * These are the low level assembler for performing cache and TLB * functions on the arm920. - * - * CONFIG_CPU_ARM920_CPU_IDLE -> nohlt */ #include #include diff --git a/arch/arm/mm/proc-arm922.S b/arch/arm/mm/proc-arm922.S index 5a4a3f4f2683..3fe6fdf0d325 100644 --- a/arch/arm/mm/proc-arm922.S +++ b/arch/arm/mm/proc-arm922.S @@ -9,8 +9,6 @@ * * These are the low level assembler for performing cache and TLB * functions on the arm922. - * - * CONFIG_CPU_ARM922_CPU_IDLE -> nohlt */ #include #include diff --git a/arch/arm/mm/proc-arm925.S b/arch/arm/mm/proc-arm925.S index 1c4830afe1d3..2d15467e4a08 100644 --- a/arch/arm/mm/proc-arm925.S +++ b/arch/arm/mm/proc-arm925.S @@ -15,8 +15,6 @@ * These are the low level assembler for performing cache and TLB * functions on the arm925. * - * CONFIG_CPU_ARM925_CPU_IDLE -> nohlt - * * Some additional notes based on deciphering the TI TRM on OMAP-5910: * * NOTE1: The TI925T Configuration Register bit "D-cache clean and flush diff --git a/arch/arm/mm/proc-arm926.S b/arch/arm/mm/proc-arm926.S index a09cc3e02efd..d94aa8199452 100644 --- a/arch/arm/mm/proc-arm926.S +++ b/arch/arm/mm/proc-arm926.S @@ -8,8 +8,6 @@ * * These are the low level assembler for performing cache and TLB * functions on the arm926. - * - * CONFIG_CPU_ARM926_CPU_IDLE -> nohlt */ #include #include From bb3b2cfeb206f5b10b859e134651b54120e3f530 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Tue, 30 Jun 2026 18:05:56 +0100 Subject: [PATCH 0456/1328] ARM: 9480/1: entry: expand comment in __switch_to As per discussion between the developers in the mail thread linked, expand the comment in __switch_to so that readers of the code understand what is going on. Suggested-by: Mark Rutland Acked-by: Mark Rutland Signed-off-by: Linus Walleij > Signed-off-by: Russell King --- arch/arm/kernel/entry-armv.S | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/arch/arm/kernel/entry-armv.S b/arch/arm/kernel/entry-armv.S index ef6a657c8d13..b7faefe5bce7 100644 --- a/arch/arm/kernel/entry-armv.S +++ b/arch/arm/kernel/entry-armv.S @@ -557,9 +557,16 @@ ENTRY(__switch_to) ldmia r4, {r4 - sl, fp, ip, lr} @ Load all regs saved previously #ifdef CONFIG_VMAP_STACK @ - @ Do a dummy read from the new stack while running from the old one so - @ that we can rely on do_translation_fault() to fix up any stale PMD - @ entries covering the vmalloc region. + @ For a non-lazy mm switch, check_vmalloc_seq() has ensured that + @ that the active mm's page tables have mappings for the prev + @ task's stack and the next task's stack. + @ + @ For a lazy mm switch the active mm's page tables have mappings + @ for the prev task's stack but might not have mappings for the + @ new task's stack. Do a dummy read from the new stack while + @ running from the old stack so that we can rely on + @ do_translation_fault() to populate missing PMD entries covering the + @ new task's stack in the old task's page tables. @ ldr r2, [ip] #ifdef CONFIG_KASAN_VMALLOC From 713e4d54ff7369b8118f91e98a008220d60b2a5f Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 29 Jul 2026 07:35:48 -0600 Subject: [PATCH 0457/1328] dt-bindings: clock: rockchip: Add RV1106 CRU support Add the clock binding header and schema for the Rockchip RV1106 clock and reset unit. The clock IDs are numbered continuously from 0, in the same way as the RV1103B. The header also covers the MMC drive and sample phase clocks, which the CRU provides using control registers in the GRF region. The RV1103 is a package variant of the RV1106 and uses the same CRU. Signed-off-by: Simon Glass Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260729133609.3465563-2-sjg@chromium.org Signed-off-by: Heiko Stuebner --- .../bindings/clock/rockchip,rv1106-cru.yaml | 60 ++++ .../dt-bindings/clock/rockchip,rv1106-cru.h | 306 ++++++++++++++++++ 2 files changed, 366 insertions(+) create mode 100644 Documentation/devicetree/bindings/clock/rockchip,rv1106-cru.yaml create mode 100644 include/dt-bindings/clock/rockchip,rv1106-cru.h diff --git a/Documentation/devicetree/bindings/clock/rockchip,rv1106-cru.yaml b/Documentation/devicetree/bindings/clock/rockchip,rv1106-cru.yaml new file mode 100644 index 000000000000..b99dce9ff935 --- /dev/null +++ b/Documentation/devicetree/bindings/clock/rockchip,rv1106-cru.yaml @@ -0,0 +1,60 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/clock/rockchip,rv1106-cru.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Rockchip RV1106 Clock and Reset Unit + +maintainers: + - Simon Glass + - Heiko Stuebner + +description: + The RV1106 clock controller generates the clock and also implements a + reset controller for SoC peripherals. + +properties: + compatible: + const: rockchip,rv1106-cru + + reg: + maxItems: 1 + + "#clock-cells": + const: 1 + + "#reset-cells": + const: 1 + + clocks: + maxItems: 1 + + clock-names: + const: xin24m + + rockchip,grf: + $ref: /schemas/types.yaml#/definitions/phandle + description: + Phandle to the syscon managing the "general register files" (GRF). + If missing, pll rates are not changeable due to the missing pll + lock status, and the MMC drive and sample phase clocks, whose + control registers live in the GRF region, are not registered. + +required: + - compatible + - reg + - "#clock-cells" + - "#reset-cells" + +additionalProperties: false + +examples: + - | + clock-controller@ff3b0000 { + compatible = "rockchip,rv1106-cru"; + reg = <0xff3b0000 0x20000>; + rockchip,grf = <&grf>; + #clock-cells = <1>; + #reset-cells = <1>; + }; diff --git a/include/dt-bindings/clock/rockchip,rv1106-cru.h b/include/dt-bindings/clock/rockchip,rv1106-cru.h new file mode 100644 index 000000000000..80244988e139 --- /dev/null +++ b/include/dt-bindings/clock/rockchip,rv1106-cru.h @@ -0,0 +1,306 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */ +/* + * Copyright (c) 2022 Rockchip Electronics Co. Ltd. + * Author: Elaine Zhang + */ + +#ifndef _DT_BINDINGS_CLK_ROCKCHIP_RV1106_H +#define _DT_BINDINGS_CLK_ROCKCHIP_RV1106_H + +/* pll clocks */ +#define PLL_APLL 0 +#define PLL_DPLL 1 +#define PLL_CPLL 2 +#define PLL_GPLL 3 +#define ARMCLK 4 + +/* clk (clocks) */ +#define PCLK_DDRPHY 5 +#define PCLK_DDR_ROOT 6 +#define PCLK_DDRMON 7 +#define CLK_TIMER_DDRMON 8 +#define PCLK_DDRC 9 +#define PCLK_DFICTRL 10 +#define ACLK_DDR_ROOT 11 +#define ACLK_SYS_SHRM 12 +#define HCLK_NPU_ROOT 13 +#define ACLK_NPU_ROOT 14 +#define PCLK_NPU_ROOT 15 +#define HCLK_RKNN 16 +#define ACLK_RKNN 17 +#define PCLK_ACODEC 18 +#define MCLK_ACODEC_TX 19 +#define CLK_CORE_CRYPTO 20 +#define CLK_PKA_CRYPTO 21 +#define ACLK_CRYPTO 22 +#define HCLK_CRYPTO 23 +#define ACLK_DECOM 24 +#define PCLK_DECOM 25 +#define DCLK_DECOM 26 +#define ACLK_DMAC 27 +#define PCLK_DSM 28 +#define MCLK_DSM 29 +#define CCLK_SRC_EMMC 30 +#define HCLK_EMMC 31 +#define PCLK_GPIO4 32 +#define DBCLK_GPIO4 33 +#define PCLK_I2C0 34 +#define CLK_I2C0 35 +#define PCLK_I2C2 36 +#define CLK_I2C2 37 +#define PCLK_I2C3 38 +#define CLK_I2C3 39 +#define PCLK_I2C4 40 +#define CLK_I2C4 41 +#define HCLK_I2S0 42 +#define PCLK_DFT2APB 43 +#define HCLK_IVE 44 +#define ACLK_IVE 45 +#define PCLK_PWM0_PERI 46 +#define CLK_PWM0_PERI 47 +#define CLK_CAPTURE_PWM0_PERI 48 +#define PCLK_PERI_ROOT 49 +#define ACLK_PERI_ROOT 50 +#define HCLK_PERI_ROOT 51 +#define CLK_TIMER_ROOT 52 +#define ACLK_BUS_ROOT 53 +#define HCLK_SFC 54 +#define SCLK_SFC 55 +#define PCLK_UART0 56 +#define CLK_PVTM_CORE 57 +#define PCLK_UART1 58 +#define CLK_CORE_MCU_RTC 59 +#define PCLK_PWM1_PERI 60 +#define CLK_PWM1_PERI 61 +#define CLK_CAPTURE_PWM1_PERI 62 +#define PCLK_PWM2_PERI 63 +#define CLK_PWM2_PERI 64 +#define CLK_CAPTURE_PWM2_PERI 65 +#define HCLK_BOOTROM 66 +#define HCLK_SAI 67 +#define MCLK_SAI 68 +#define PCLK_SARADC 69 +#define CLK_SARADC 70 +#define PCLK_SPI1 71 +#define CLK_SPI1 72 +#define PCLK_STIMER 73 +#define CLK_STIMER0 74 +#define CLK_STIMER1 75 +#define PCLK_TIMER 76 +#define CLK_TIMER0 77 +#define CLK_TIMER1 78 +#define CLK_TIMER2 79 +#define CLK_TIMER3 80 +#define CLK_TIMER4 81 +#define CLK_TIMER5 82 +#define HCLK_TRNG_NS 83 +#define HCLK_TRNG_S 84 +#define PCLK_UART2 85 +#define HCLK_CPU 86 +#define PCLK_UART3 87 +#define CLK_CORE_MCU 88 +#define PCLK_UART4 89 +#define PCLK_DDR_HWLP 90 +#define PCLK_UART5 91 +#define ACLK_USBOTG 92 +#define CLK_REF_USBOTG 93 +#define CLK_UTMI_USBOTG 94 +#define PCLK_USBPHY 95 +#define CLK_REF_USBPHY 96 +#define PCLK_WDT_NS 97 +#define TCLK_WDT_NS 98 +#define PCLK_WDT_S 99 +#define TCLK_WDT_S 100 +#define CLK_DDR_FAIL_SAFE 101 +#define XIN_OSC0_DIV 102 +#define CLK_DEEPSLOW 103 +#define PCLK_PMU_GPIO0 104 +#define DBCLK_PMU_GPIO0 105 +#define CLK_PMU 106 +#define PCLK_PMU 107 +#define PCLK_PMU_HP_TIMER 108 +#define CLK_PMU_HP_TIMER 109 +#define CLK_PMU_32K_HP_TIMER 110 +#define PCLK_I2C1 111 +#define CLK_I2C1 112 +#define PCLK_PMU_IOC 113 +#define PCLK_PMU_MAILBOX 114 +#define CLK_PMU_MCU 115 +#define CLK_PMU_MCU_RTC 116 +#define CLK_PMU_MCU_JTAG 117 +#define CLK_PVTM_PMU 118 +#define PCLK_PVTM_PMU 119 +#define CLK_REFOUT 120 +#define CLK_100M_PMU 121 +#define PCLK_PMU_ROOT 122 +#define HCLK_PMU_ROOT 123 +#define HCLK_PMU_SRAM 124 +#define PCLK_PMU_WDT 125 +#define TCLK_PMU_WDT 126 +#define CLK_DFICTRL 127 +#define CLK_DDRMON 128 +#define CLK_DDR_PHY 129 +#define ACLK_DDRC 130 +#define CLK_CORE_DDRC_SRC 131 +#define CLK_CORE_DDRC 132 +#define CLK_50M_SRC 133 +#define CLK_100M_SRC 134 +#define CLK_150M_SRC 135 +#define CLK_200M_SRC 136 +#define CLK_250M_SRC 137 +#define CLK_300M_SRC 138 +#define CLK_339M_SRC 139 +#define CLK_400M_SRC 140 +#define CLK_450M_SRC 141 +#define CLK_500M_SRC 142 +#define CLK_I2S0_8CH_TX_SRC 143 +#define CLK_I2S0_8CH_TX_FRAC 144 +#define CLK_I2S0_8CH_TX 145 +#define CLK_I2S0_8CH_RX_SRC 146 +#define CLK_I2S0_8CH_RX_FRAC 147 +#define CLK_I2S0_8CH_RX 148 +#define I2S0_8CH_MCLKOUT 149 +#define MCLK_I2S0_8CH_RX 150 +#define MCLK_I2S0_8CH_TX 151 +#define CLK_REF_MIPI0_SRC 152 +#define CLK_REF_MIPI0_FRAC 153 +#define CLK_REF_MIPI0_OUT 154 +#define CLK_REF_MIPI1_SRC 155 +#define CLK_REF_MIPI1_FRAC 156 +#define MCLK_REF_MIPI0 157 +#define MCLK_REF_MIPI1 158 +#define CLK_REF_MIPI0 159 +#define CLK_REF_MIPI1 160 +#define CLK_UART0_SRC 161 +#define CLK_UART0_FRAC 162 +#define CLK_UART0 163 +#define SCLK_UART0 164 +#define CLK_UART1_SRC 165 +#define CLK_UART1_FRAC 166 +#define CLK_UART1 167 +#define SCLK_UART1 168 +#define CLK_UART2_SRC 169 +#define CLK_UART2_FRAC 170 +#define CLK_UART2 171 +#define SCLK_UART2 172 +#define CLK_UART3_SRC 173 +#define CLK_UART3_FRAC 174 +#define CLK_UART3 175 +#define SCLK_UART3 176 +#define CLK_UART4_SRC 177 +#define CLK_UART4_FRAC 178 +#define CLK_UART4 179 +#define SCLK_UART4 180 +#define CLK_UART5_SRC 181 +#define CLK_UART5_FRAC 182 +#define CLK_UART5 183 +#define SCLK_UART5 184 +#define CLK_VICAP_M0_SRC 185 +#define CLK_VICAP_M0_FRAC 186 +#define CLK_VICAP_M0 187 +#define SCLK_VICAP_M0 188 +#define CLK_VICAP_M1_SRC 189 +#define CLK_VICAP_M1_FRAC 190 +#define CLK_VICAP_M1 191 +#define SCLK_VICAP_M1 192 +#define DCLK_VOP_SRC 193 +#define PCLK_CRU 194 +#define PCLK_TOP_ROOT 195 +#define PCLK_SPI0 196 +#define CLK_SPI0 197 +#define SCLK_IN_SPI0 198 +#define CLK_UART_DETN_FLT 199 +#define HCLK_VEPU 200 +#define ACLK_VEPU 201 +#define CLK_CORE_VEPU 202 +#define CLK_CORE_VEPU_DVBM 203 +#define PCLK_GPIO1 204 +#define DBCLK_GPIO1 205 +#define HCLK_VEPU_PP 206 +#define ACLK_VEPU_PP 207 +#define HCLK_VEPU_ROOT 208 +#define ACLK_VEPU_COM_ROOT 209 +#define ACLK_VEPU_ROOT 210 +#define PCLK_VEPU_ROOT 211 +#define PCLK_VICAP_VEPU 212 +#define PCLK_CSIHOST0 213 +#define CLK_RXBYTECLKHS_0 214 +#define PCLK_CSIHOST1 215 +#define CLK_RXBYTECLKHS_1 216 +#define PCLK_GPIO3 217 +#define DBCLK_GPIO3 218 +#define HCLK_ISP3P2 219 +#define ACLK_ISP3P2 220 +#define CLK_CORE_ISP3P2 221 +#define PCLK_MIPICSIPHY 222 +#define CCLK_SRC_SDMMC 223 +#define HCLK_SDMMC 224 +#define CLK_SDMMC_DETN_FLT 225 +#define HCLK_VI_ROOT 226 +#define ACLK_VI_ROOT 227 +#define PCLK_VI_ROOT 228 +#define PCLK_VI_RTC_ROOT 229 +#define PCLK_VI_RTC_TEST 230 +#define PCLK_VI_RTC_PHY 231 +#define DCLK_VICAP 232 +#define PCLK_VICAP 233 +#define ACLK_VICAP 234 +#define HCLK_VICAP 235 +#define I0CLK_VICAP 236 +#define I1CLK_VICAP 237 +#define RX0PCLK_VICAP 238 +#define RX1PCLK_VICAP 239 +#define ISP0CLK_VICAP 240 +#define PCLK_GPIO2 241 +#define DBCLK_GPIO2 242 +#define ACLK_MAC 243 +#define PCLK_MAC 244 +#define CLK_GMAC0_50M_O 245 +#define CLK_GMAC0_TX_50M_O 246 +#define CLK_GMAC0_REF_50M 247 +#define CLK_GMAC0_TX_50M 248 +#define CLK_GMAC0_RX_50M 249 +#define ACLK_MAC_ROOT 250 +#define CLK_MACPHY 251 +#define CLK_OTPC_ARB 252 +#define PCLK_OTPC_NS 253 +#define CLK_SBPI_OTPC_NS 254 +#define CLK_USER_OTPC_NS 255 +#define PCLK_OTPC_S 256 +#define CLK_SBPI_OTPC_S 257 +#define CLK_USER_OTPC_S 258 +#define PCLK_OTP_MASK 259 +#define CLK_PMC_OTP 260 +#define HCLK_RGA2E 261 +#define ACLK_RGA2E 262 +#define CLK_CORE_RGA2E 263 +#define CCLK_SRC_SDIO 264 +#define HCLK_SDIO 265 +#define PCLK_TSADC 266 +#define CLK_TSADC 267 +#define CLK_TSADC_TSEN 268 +#define ACLK_VO_ROOT 269 +#define HCLK_VO_ROOT 270 +#define PCLK_VO_ROOT 271 +#define ACLK_VOP_ROOT 272 +#define HCLK_VOP 273 +#define DCLK_VOP 274 +#define ACLK_VOP 275 +#define CLK_RTC_32K 276 +#define PCLK_MAILBOX 277 + +/* mmc phase clocks */ +#define SCLK_EMMC_DRV 278 +#define SCLK_EMMC_SAMPLE 279 +#define SCLK_SDMMC_DRV 280 +#define SCLK_SDMMC_SAMPLE 281 +#define SCLK_SDIO_DRV 282 +#define SCLK_SDIO_SAMPLE 283 + +/* internal clocks */ +#define XIN_OSC0_HALF 284 +#define PCLK_DBG 285 +#define PCLK_CPU_ROOT 286 +#define CLK_RTC32K_FRAC 287 + +#endif From b520d056f2619972ad09ced2985b534bba4f9599 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 29 Jul 2026 07:35:49 -0600 Subject: [PATCH 0458/1328] clk: rockchip: Add clock controller for the RV1106 Add the clock and reset controller driver for the Rockchip RV1106, based on the vendor kernel from the Luckfox Pico SDK [1] at commit 824b817f8 (a Linux 5.10.160 kernel tree), adapted to the upstream clock framework. The MMC drive and sample phase clocks have their control registers in the GRF region. The CRU provides these clocks itself, using the syscon referenced by the rockchip,grf property as an auxiliary regmap, in the same way as the RK3528. The RV1103 is a package variant of the RV1106 and uses the same CRU. [1] https://github.com/LuckfoxTECH/luckfox-pico Signed-off-by: Simon Glass Link: https://patch.msgid.link/20260729133609.3465563-3-sjg@chromium.org Signed-off-by: Heiko Stuebner --- drivers/clk/rockchip/Kconfig | 7 + drivers/clk/rockchip/Makefile | 1 + drivers/clk/rockchip/clk-rv1106.c | 1099 +++++++++++++++++++++++++++++ 3 files changed, 1107 insertions(+) create mode 100644 drivers/clk/rockchip/clk-rv1106.c diff --git a/drivers/clk/rockchip/Kconfig b/drivers/clk/rockchip/Kconfig index 85133498f013..4b55f48d72c2 100644 --- a/drivers/clk/rockchip/Kconfig +++ b/drivers/clk/rockchip/Kconfig @@ -23,6 +23,13 @@ config CLK_RV1103B help Build the driver for RV1103B Clock Driver. +config CLK_RV1106 + bool "Rockchip RV1106 clock controller support" + depends on ARM || COMPILE_TEST + default y + help + Build the driver for RV1106 Clock Driver. + config CLK_RV110X bool "Rockchip RV110x clock controller support" depends on ARM || COMPILE_TEST diff --git a/drivers/clk/rockchip/Makefile b/drivers/clk/rockchip/Makefile index 7c984ee006c6..554b7eb83dc9 100644 --- a/drivers/clk/rockchip/Makefile +++ b/drivers/clk/rockchip/Makefile @@ -19,6 +19,7 @@ clk-rockchip-$(CONFIG_RESET_CONTROLLER) += softrst.o obj-$(CONFIG_CLK_PX30) += clk-px30.o obj-$(CONFIG_CLK_RV1103B) += clk-rv1103b.o +obj-$(CONFIG_CLK_RV1106) += clk-rv1106.o obj-$(CONFIG_CLK_RV110X) += clk-rv1108.o obj-$(CONFIG_CLK_RV1126) += clk-rv1126.o obj-$(CONFIG_CLK_RV1126B) += clk-rv1126b.o rst-rv1126b.o diff --git a/drivers/clk/rockchip/clk-rv1106.c b/drivers/clk/rockchip/clk-rv1106.c new file mode 100644 index 000000000000..3f26573e6cb7 --- /dev/null +++ b/drivers/clk/rockchip/clk-rv1106.c @@ -0,0 +1,1099 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2022 Rockchip Electronics Co. Ltd. + * Author: Elaine Zhang + */ + +#include +#include +#include +#include +#include +#include "clk.h" + +/* + * Register offsets inside the CRU region. The RV1106 CRU is split into + * several sub-blocks, one per power domain, all accessed through a + * single register window. + */ +#define RV1106_TOPCRU_BASE 0x10000 +#define RV1106_PERICRU_BASE 0x12000 +#define RV1106_VICRU_BASE 0x14000 +#define RV1106_NPUCRU_BASE 0x16000 +#define RV1106_CORECRU_BASE 0x18000 +#define RV1106_VEPUCRU_BASE 0x1a000 +#define RV1106_VOCRU_BASE 0x1c000 +#define RV1106_DDRCRU_BASE 0x1e000 +#define RV1106_SUBDDRCRU_BASE 0x1f000 + +#define RV1106_PMUCLKSEL_CON(x) ((x) * 0x4 + 0x300) +#define RV1106_PMUCLKGATE_CON(x) ((x) * 0x4 + 0x800) +#define RV1106_PLL_CON(x) ((x) * 0x4 + RV1106_TOPCRU_BASE) +#define RV1106_MODE_CON (0x280 + RV1106_TOPCRU_BASE) +#define RV1106_CLKSEL_CON(x) ((x) * 0x4 + 0x300 + RV1106_TOPCRU_BASE) +#define RV1106_CLKGATE_CON(x) ((x) * 0x4 + 0x800 + RV1106_TOPCRU_BASE) +#define RV1106_GLB_SRST_FST (0xc08 + RV1106_TOPCRU_BASE) +#define RV1106_PERICLKSEL_CON(x) ((x) * 0x4 + 0x300 + RV1106_PERICRU_BASE) +#define RV1106_PERICLKGATE_CON(x) ((x) * 0x4 + 0x800 + RV1106_PERICRU_BASE) +#define RV1106_VICLKSEL_CON(x) ((x) * 0x4 + 0x300 + RV1106_VICRU_BASE) +#define RV1106_VICLKGATE_CON(x) ((x) * 0x4 + 0x800 + RV1106_VICRU_BASE) +#define RV1106_NPUCLKSEL_CON(x) ((x) * 0x4 + 0x300 + RV1106_NPUCRU_BASE) +#define RV1106_NPUCLKGATE_CON(x) ((x) * 0x4 + 0x800 + RV1106_NPUCRU_BASE) +#define RV1106_CORECLKSEL_CON(x) ((x) * 0x4 + 0x300 + RV1106_CORECRU_BASE) +#define RV1106_CORECLKGATE_CON(x) ((x) * 0x4 + 0x800 + RV1106_CORECRU_BASE) +#define RV1106_VEPUCLKSEL_CON(x) ((x) * 0x4 + 0x300 + RV1106_VEPUCRU_BASE) +#define RV1106_VEPUCLKGATE_CON(x) ((x) * 0x4 + 0x800 + RV1106_VEPUCRU_BASE) +#define RV1106_VOCLKSEL_CON(x) ((x) * 0x4 + 0x300 + RV1106_VOCRU_BASE) +#define RV1106_VOCLKGATE_CON(x) ((x) * 0x4 + 0x800 + RV1106_VOCRU_BASE) +#define RV1106_DDRCLKSEL_CON(x) ((x) * 0x4 + 0x300 + RV1106_DDRCRU_BASE) +#define RV1106_DDRCLKGATE_CON(x) ((x) * 0x4 + 0x800 + RV1106_DDRCRU_BASE) +#define RV1106_SUBDDRCLKSEL_CON(x) ((x) * 0x4 + 0x300 + RV1106_SUBDDRCRU_BASE) +#define RV1106_SUBDDRCLKGATE_CON(x) ((x) * 0x4 + 0x800 + RV1106_SUBDDRCRU_BASE) +#define RV1106_SUBDDRMODE_CON (0x280 + RV1106_SUBDDRCRU_BASE) + +/* Register offsets inside the GRF region for the MMC phase clocks */ +#define RV1106_VI_GRF_BASE 0x50000 +#define RV1106_VO_GRF_BASE 0x60000 + +#define RV1106_EMMC_CON0 0x20 +#define RV1106_EMMC_CON1 0x24 +#define RV1106_SDMMC_CON0 (0x4 + RV1106_VI_GRF_BASE) +#define RV1106_SDMMC_CON1 (0x8 + RV1106_VI_GRF_BASE) +#define RV1106_SDIO_CON0 (0x1c + RV1106_VO_GRF_BASE) +#define RV1106_SDIO_CON1 (0x20 + RV1106_VO_GRF_BASE) + +#define CRU_PVTPLL0_CON0_L 0x11000 +#define CRU_PVTPLL0_CON0_H 0x11004 +#define CRU_PVTPLL0_CON1_L 0x11008 +#define CRU_PVTPLL0_CON2_H 0x11014 + +#define CRU_PVTPLL1_CON0_L 0x11030 +#define CRU_PVTPLL1_CON0_H 0x11034 +#define CRU_PVTPLL1_CON1_L 0x11038 +#define CRU_PVTPLL1_CON2_H 0x11044 + +#define RV1106_GRF_SOC_STATUS0 0x10 +#define CPU_PVTPLL_CON0_L 0x40000 +#define CPU_PVTPLL_CON0_H 0x40004 + +#define PVTPLL_RING_SEL_MASK 0x7 +#define PVTPLL_RING_SEL_SHIFT 8 +#define PVTPLL_EN_MASK 0x3 +#define PVTPLL_EN_SHIFT 0 +#define PVTPLL_LENGTH_SEL_MASK 0x7f +#define PVTPLL_LENGTH_SEL_SHIFT 0 + +#define CPU_CLK_PATH_BASE RV1106_CORECLKSEL_CON(0) +#define CPU_PVTPLL_PATH_CORE (BIT(12) | BIT(28)) + +#define RV1106_FRAC_MAX_PRATE 1200000000 + +enum rv1106_plls { + apll, dpll, cpll, gpll, +}; + +static struct rockchip_pll_rate_table rv1106_pll_rates[] = { + /* _mhz, _refdiv, _fbdiv, _postdiv1, _postdiv2, _dsmpd, _frac */ + RK3036_PLL_RATE(1608000000, 1, 67, 1, 1, 1, 0), + RK3036_PLL_RATE(1600000000, 3, 200, 1, 1, 1, 0), + RK3036_PLL_RATE(1584000000, 1, 132, 2, 1, 1, 0), + RK3036_PLL_RATE(1560000000, 1, 130, 2, 1, 1, 0), + RK3036_PLL_RATE(1536000000, 1, 128, 2, 1, 1, 0), + RK3036_PLL_RATE(1512000000, 1, 126, 2, 1, 1, 0), + RK3036_PLL_RATE(1488000000, 1, 124, 2, 1, 1, 0), + RK3036_PLL_RATE(1464000000, 1, 122, 2, 1, 1, 0), + RK3036_PLL_RATE(1440000000, 1, 120, 2, 1, 1, 0), + RK3036_PLL_RATE(1416000000, 1, 118, 2, 1, 1, 0), + RK3036_PLL_RATE(1400000000, 3, 350, 2, 1, 1, 0), + RK3036_PLL_RATE(1392000000, 1, 116, 2, 1, 1, 0), + RK3036_PLL_RATE(1368000000, 1, 114, 2, 1, 1, 0), + RK3036_PLL_RATE(1344000000, 1, 112, 2, 1, 1, 0), + RK3036_PLL_RATE(1320000000, 1, 110, 2, 1, 1, 0), + RK3036_PLL_RATE(1296000000, 1, 108, 2, 1, 1, 0), + RK3036_PLL_RATE(1272000000, 1, 106, 2, 1, 1, 0), + RK3036_PLL_RATE(1248000000, 1, 104, 2, 1, 1, 0), + RK3036_PLL_RATE(1200000000, 1, 100, 2, 1, 1, 0), + RK3036_PLL_RATE(1188000000, 1, 99, 2, 1, 1, 0), + RK3036_PLL_RATE(1104000000, 1, 92, 2, 1, 1, 0), + RK3036_PLL_RATE(1100000000, 3, 275, 2, 1, 1, 0), + RK3036_PLL_RATE(1008000000, 1, 84, 2, 1, 1, 0), + RK3036_PLL_RATE(1000000000, 3, 250, 2, 1, 1, 0), + RK3036_PLL_RATE(993484800, 1, 124, 3, 1, 0, 3113851), + RK3036_PLL_RATE(984000000, 1, 82, 2, 1, 1, 0), + RK3036_PLL_RATE(983040000, 1, 81, 2, 1, 0, 15435038), + RK3036_PLL_RATE(960000000, 1, 80, 2, 1, 1, 0), + RK3036_PLL_RATE(936000000, 1, 78, 2, 1, 1, 0), + RK3036_PLL_RATE(912000000, 1, 76, 2, 1, 1, 0), + RK3036_PLL_RATE(900000000, 1, 75, 2, 1, 1, 0), + RK3036_PLL_RATE(888000000, 1, 74, 2, 1, 1, 0), + RK3036_PLL_RATE(864000000, 1, 72, 2, 1, 1, 0), + RK3036_PLL_RATE(840000000, 1, 70, 2, 1, 1, 0), + RK3036_PLL_RATE(816000000, 1, 68, 2, 1, 1, 0), + RK3036_PLL_RATE(800000000, 3, 200, 2, 1, 1, 0), + RK3036_PLL_RATE(700000000, 3, 350, 4, 1, 1, 0), + RK3036_PLL_RATE(696000000, 1, 116, 4, 1, 1, 0), + RK3036_PLL_RATE(624000000, 1, 104, 4, 1, 1, 0), + RK3036_PLL_RATE(600000000, 1, 100, 4, 1, 1, 0), + RK3036_PLL_RATE(594000000, 1, 99, 4, 1, 1, 0), + RK3036_PLL_RATE(504000000, 1, 84, 4, 1, 1, 0), + RK3036_PLL_RATE(500000000, 1, 125, 6, 1, 1, 0), + RK3036_PLL_RATE(496742400, 1, 124, 6, 1, 0, 3113851), + RK3036_PLL_RATE(491520000, 1, 40, 2, 1, 0, 16106127), + RK3036_PLL_RATE(408000000, 1, 68, 2, 2, 1, 0), + RK3036_PLL_RATE(312000000, 1, 78, 6, 1, 1, 0), + RK3036_PLL_RATE(216000000, 1, 72, 4, 2, 1, 0), + RK3036_PLL_RATE(96000000, 1, 96, 6, 4, 1, 0), + { /* sentinel */ }, +}; + +#define RV1106_DIV_ACLK_CORE_MASK 0x1f +#define RV1106_DIV_ACLK_CORE_SHIFT 7 +#define RV1106_DIV_PCLK_DBG_MASK 0x1f +#define RV1106_DIV_PCLK_DBG_SHIFT 0 +#define RV1106_CORE_SEL_MASK 0x3 +#define RV1106_CORE_SEL_SHIFT 5 + +#define RV1106_CLKSEL0(_aclk_core) \ +{ \ + .reg = RV1106_CORECLKSEL_CON(0), \ + .val = HIWORD_UPDATE(_aclk_core, RV1106_DIV_ACLK_CORE_MASK, \ + RV1106_DIV_ACLK_CORE_SHIFT), \ +} + +#define RV1106_CLKSEL1(_pclk_dbg) \ +{ \ + .reg = RV1106_CORECLKSEL_CON(1), \ + .val = HIWORD_UPDATE(_pclk_dbg, RV1106_DIV_PCLK_DBG_MASK, \ + RV1106_DIV_PCLK_DBG_SHIFT), \ +} + +#define RV1106_CLKSEL2(_is_pvtpll) \ +{ \ + .reg = RV1106_CORECLKSEL_CON(0), \ + .val = HIWORD_UPDATE(_is_pvtpll, RV1106_CORE_SEL_MASK, \ + RV1106_CORE_SEL_SHIFT), \ +} + +#define RV1106_CPUCLK_RATE(_prate, _aclk_core, _pclk_dbg, _is_pvtpll) \ +{ \ + .prate = _prate, \ + .divs = { \ + RV1106_CLKSEL0(_aclk_core), \ + RV1106_CLKSEL1(_pclk_dbg), \ + }, \ + .post_muxs = { \ + RV1106_CLKSEL2(_is_pvtpll), \ + }, \ +} + +static struct rockchip_cpuclk_rate_table rv1106_cpuclk_rates[] __initdata = { + RV1106_CPUCLK_RATE(1608000000, 3, 7, 1), + RV1106_CPUCLK_RATE(1584000000, 3, 7, 1), + RV1106_CPUCLK_RATE(1560000000, 3, 7, 1), + RV1106_CPUCLK_RATE(1536000000, 3, 7, 1), + RV1106_CPUCLK_RATE(1512000000, 3, 7, 1), + RV1106_CPUCLK_RATE(1488000000, 2, 5, 1), + RV1106_CPUCLK_RATE(1464000000, 2, 5, 1), + RV1106_CPUCLK_RATE(1440000000, 2, 5, 1), + RV1106_CPUCLK_RATE(1416000000, 2, 5, 1), + RV1106_CPUCLK_RATE(1392000000, 2, 5, 1), + RV1106_CPUCLK_RATE(1368000000, 2, 5, 1), + RV1106_CPUCLK_RATE(1344000000, 2, 5, 1), + RV1106_CPUCLK_RATE(1320000000, 2, 5, 1), + RV1106_CPUCLK_RATE(1296000000, 2, 5, 1), + RV1106_CPUCLK_RATE(1272000000, 2, 5, 1), + RV1106_CPUCLK_RATE(1248000000, 2, 5, 1), + RV1106_CPUCLK_RATE(1224000000, 2, 5, 1), + RV1106_CPUCLK_RATE(1200000000, 2, 5, 1), + RV1106_CPUCLK_RATE(1104000000, 2, 5, 1), + RV1106_CPUCLK_RATE(1096000000, 2, 5, 1), + RV1106_CPUCLK_RATE(1008000000, 1, 5, 1), + RV1106_CPUCLK_RATE(912000000, 1, 5, 1), + RV1106_CPUCLK_RATE(816000000, 1, 3, 1), + RV1106_CPUCLK_RATE(696000000, 1, 3, 0), + RV1106_CPUCLK_RATE(600000000, 1, 3, 0), + RV1106_CPUCLK_RATE(408000000, 1, 1, 0), + RV1106_CPUCLK_RATE(312000000, 1, 1, 0), + RV1106_CPUCLK_RATE(216000000, 1, 1, 0), + RV1106_CPUCLK_RATE(96000000, 1, 1, 0), +}; + +static const struct rockchip_cpuclk_reg_data rv1106_cpuclk_data = { + .core_reg[0] = RV1106_CORECLKSEL_CON(0), + .div_core_shift[0] = 0, + .div_core_mask[0] = 0x1f, + .num_cores = 1, + .mux_core_alt = 2, + .mux_core_main = 0, + .mux_core_shift = 5, + .mux_core_mask = 0x3, +}; + +PNAME(mux_pll_p) = { "xin24m" }; +PNAME(mux_armclk_p) = { "apll", "cpu_pvtpll", "gpll" }; +PNAME(mux_24m_32k_p) = { "xin24m", "clk_rtc_32k" }; +PNAME(mux_gpll_cpll_p) = { "gpll", "cpll" }; +PNAME(mux_gpll_24m_p) = { "gpll", "xin24m" }; +PNAME(mux_100m_50m_24m_p) = { "clk_100m_src", "clk_50m_src", "xin24m" }; +PNAME(mux_150m_100m_50m_24m_p) = { "clk_150m_src", "clk_100m_src", "clk_50m_src", "xin24m" }; +PNAME(mux_500m_300m_100m_24m_p) = { "clk_500m_src", "clk_300m_src", "clk_100m_src", "xin24m" }; +PNAME(mux_400m_300m_pvtpll0_pvtpll1_p) = { "clk_400m_src", "clk_300m_src", "clk_pvtpll_0", "clk_pvtpll_1" }; +PNAME(mux_500m_300m_pvtpll0_pvtpll1_p) = { "clk_500m_src", "clk_300m_src", "clk_pvtpll_0", "clk_pvtpll_1" }; +PNAME(mux_339m_200m_pvtpll0_pvtpll1_p) = { "clk_339m_src", "clk_200m_src", "clk_pvtpll_0", "clk_pvtpll_1" }; +PNAME(mux_400m_200m_100m_24m_p) = { "clk_400m_src", "clk_200m_src", "clk_100m_src", "xin24m" }; +PNAME(mux_200m_100m_50m_24m_p) = { "clk_200m_src", "clk_100m_src", "clk_50m_src", "xin24m" }; +PNAME(mux_300m_200m_100m_24m_p) = { "clk_300m_src", "clk_200m_src", "clk_100m_src", "xin24m" }; +PNAME(mux_500m_300m_200m_24m_p) = { "clk_500m_src", "clk_300m_src", "clk_200m_src", "xin24m" }; +PNAME(mux_50m_24m_p) = { "clk_50m_src", "xin24m" }; +PNAME(mux_400m_24m_p) = { "clk_400m_src", "xin24m" }; +PNAME(clk_rtc32k_pmu_p) = { "clk_rtc32k_frac", "xin32k", "clk_pvtm_32k" }; +PNAME(mux_200m_100m_24m_32k_p) = { "clk_200m_src", "clk_100m_src", "xin24m", "clk_rtc_32k" }; +PNAME(mux_100m_pmu_24m_p) = { "clk_100m_pmu", "xin24m" }; +PNAME(mux_200m_100m_24m_p) = { "clk_200m_src", "clk_100m_pmu", "xin24m" }; +PNAME(mux_339m_200m_100m_24m_p) = { "clk_339m_src", "clk_200m_src", "clk_100m_pmu", "xin24m" }; +PNAME(mux_dpll_300m_p) = { "dpll", "clk_300m_src" }; +PNAME(clk_i2s0_8ch_tx_p) = { "clk_i2s0_8ch_tx_src", "clk_i2s0_8ch_tx_frac", "i2s0_mclkin", "xin_osc0_half" }; +PNAME(clk_i2s0_8ch_rx_p) = { "clk_i2s0_8ch_rx_src", "clk_i2s0_8ch_rx_frac", "i2s0_mclkin", "xin_osc0_half" }; +PNAME(i2s0_8ch_mclkout_p) = { "mclk_i2s0_8ch_tx", "mclk_i2s0_8ch_rx", "xin_osc0_half" }; +PNAME(clk_ref_mipi0_p) = { "clk_ref_mipi0_src", "clk_ref_mipi0_frac", "xin24m" }; +PNAME(clk_ref_mipi1_p) = { "clk_ref_mipi1_src", "clk_ref_mipi1_frac", "xin24m" }; +PNAME(clk_uart0_p) = { "clk_uart0_src", "clk_uart0_frac", "xin24m" }; +PNAME(clk_uart1_p) = { "clk_uart1_src", "clk_uart1_frac", "xin24m" }; +PNAME(clk_uart2_p) = { "clk_uart2_src", "clk_uart2_frac", "xin24m" }; +PNAME(clk_uart3_p) = { "clk_uart3_src", "clk_uart3_frac", "xin24m" }; +PNAME(clk_uart4_p) = { "clk_uart4_src", "clk_uart4_frac", "xin24m" }; +PNAME(clk_uart5_p) = { "clk_uart5_src", "clk_uart5_frac", "xin24m" }; +PNAME(clk_vicap_m0_p) = { "clk_vicap_m0_src", "clk_vicap_m0_frac", "xin24m" }; +PNAME(clk_vicap_m1_p) = { "clk_vicap_m1_src", "clk_vicap_m1_frac", "xin24m" }; + +static struct rockchip_pll_clock rv1106_pll_clks[] __initdata = { + [apll] = PLL(pll_rk3328, PLL_APLL, "apll", mux_pll_p, + CLK_IGNORE_UNUSED, RV1106_PLL_CON(0), + RV1106_MODE_CON, 0, 10, 0, rv1106_pll_rates), + [cpll] = PLL(pll_rk3328, PLL_CPLL, "cpll", mux_pll_p, + 0, RV1106_PLL_CON(8), + RV1106_MODE_CON, 2, 10, 0, rv1106_pll_rates), + [dpll] = PLL(pll_rk3328, PLL_DPLL, "dpll", mux_pll_p, + CLK_IGNORE_UNUSED, RV1106_PLL_CON(16), + RV1106_SUBDDRMODE_CON, 0, 10, 0, NULL), + [gpll] = PLL(pll_rk3328, PLL_GPLL, "gpll", mux_pll_p, + 0, RV1106_PLL_CON(24), + RV1106_MODE_CON, 4, 10, 0, rv1106_pll_rates), +}; + +#define MFLAGS CLK_MUX_HIWORD_MASK +#define DFLAGS CLK_DIVIDER_HIWORD_MASK +#define GFLAGS (CLK_GATE_HIWORD_MASK | CLK_GATE_SET_TO_DISABLE) + +static struct rockchip_clk_branch rv1106_rtc32k_pmu_fracmux __initdata = + MUX(CLK_RTC_32K, "clk_rtc_32k", clk_rtc32k_pmu_p, CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT, + RV1106_PMUCLKSEL_CON(0), 6, 2, MFLAGS); + +static struct rockchip_clk_branch rv1106_i2s0_8ch_tx_fracmux __initdata = + MUX(CLK_I2S0_8CH_TX, "clk_i2s0_8ch_tx", clk_i2s0_8ch_tx_p, CLK_SET_RATE_PARENT, + RV1106_CLKSEL_CON(19), 0, 2, MFLAGS); + +static struct rockchip_clk_branch rv1106_i2s0_8ch_rx_fracmux __initdata = + MUX(CLK_I2S0_8CH_RX, "clk_i2s0_8ch_rx", clk_i2s0_8ch_rx_p, CLK_SET_RATE_PARENT, + RV1106_CLKSEL_CON(21), 0, 2, MFLAGS); + +static struct rockchip_clk_branch rv1106_clk_ref_mipi0_fracmux __initdata = + MUX(CLK_REF_MIPI0, "clk_ref_mipi0", clk_ref_mipi0_p, CLK_SET_RATE_PARENT, + RV1106_CLKSEL_CON(27), 0, 2, MFLAGS); + +static struct rockchip_clk_branch rv1106_clk_ref_mipi1_fracmux __initdata = + MUX(CLK_REF_MIPI1, "clk_ref_mipi1", clk_ref_mipi1_p, CLK_SET_RATE_PARENT, + RV1106_CLKSEL_CON(29), 0, 2, MFLAGS); + +static struct rockchip_clk_branch rv1106_clk_uart0_fracmux __initdata = + MUX(CLK_UART0, "clk_uart0", clk_uart0_p, CLK_SET_RATE_PARENT, + RV1106_CLKSEL_CON(7), 0, 2, MFLAGS); + +static struct rockchip_clk_branch rv1106_clk_uart1_fracmux __initdata = + MUX(CLK_UART1, "clk_uart1", clk_uart1_p, CLK_SET_RATE_PARENT, + RV1106_CLKSEL_CON(9), 0, 2, MFLAGS); + +static struct rockchip_clk_branch rv1106_clk_uart2_fracmux __initdata = + MUX(CLK_UART2, "clk_uart2", clk_uart2_p, CLK_SET_RATE_PARENT, + RV1106_CLKSEL_CON(11), 0, 2, MFLAGS); + +static struct rockchip_clk_branch rv1106_clk_uart3_fracmux __initdata = + MUX(CLK_UART3, "clk_uart3", clk_uart3_p, CLK_SET_RATE_PARENT, + RV1106_CLKSEL_CON(13), 0, 2, MFLAGS); + +static struct rockchip_clk_branch rv1106_clk_uart4_fracmux __initdata = + MUX(CLK_UART4, "clk_uart4", clk_uart4_p, CLK_SET_RATE_PARENT, + RV1106_CLKSEL_CON(15), 0, 2, MFLAGS); + +static struct rockchip_clk_branch rv1106_clk_uart5_fracmux __initdata = + MUX(CLK_UART5, "clk_uart5", clk_uart5_p, CLK_SET_RATE_PARENT, + RV1106_CLKSEL_CON(17), 0, 2, MFLAGS); + +static struct rockchip_clk_branch rv1106_clk_vicap_m0_fracmux __initdata = + MUX(CLK_VICAP_M0, "clk_vicap_m0", clk_vicap_m0_p, CLK_SET_RATE_PARENT, + RV1106_CLKSEL_CON(31), 0, 2, MFLAGS); + +static struct rockchip_clk_branch rv1106_clk_vicap_m1_fracmux __initdata = + MUX(CLK_VICAP_M1, "clk_vicap_m1", clk_vicap_m1_p, CLK_SET_RATE_PARENT, + RV1106_CLKSEL_CON(33), 0, 2, MFLAGS); + +static struct rockchip_clk_branch rv1106_clk_branches[] __initdata = { + + FACTOR(XIN_OSC0_HALF, "xin_osc0_half", "xin24m", 0, 1, 2), + + /* PD_CORE */ + GATE(CLK_PVTM_CORE, "clk_pvtm_core", "xin24m", 0, + RV1106_CORECLKGATE_CON(0), 14, GFLAGS), + GATE(CLK_CORE_MCU_RTC, "clk_core_mcu_rtc", "xin24m", 0, + RV1106_CORECLKGATE_CON(1), 6, GFLAGS), + COMPOSITE(HCLK_CPU, "hclk_cpu", mux_gpll_24m_p, CLK_IS_CRITICAL, + RV1106_CORECLKSEL_CON(2), 5, 1, MFLAGS, 0, 5, DFLAGS, + RV1106_CORECLKGATE_CON(0), 12, GFLAGS), + COMPOSITE(CLK_CORE_MCU, "clk_core_mcu", mux_gpll_24m_p, 0, + RV1106_CORECLKSEL_CON(3), 11, 1, MFLAGS, 6, 5, DFLAGS, + RV1106_CORECLKGATE_CON(1), 1, GFLAGS), + COMPOSITE_NOMUX(PCLK_DBG, "pclk_dbg", "armclk", CLK_IS_CRITICAL, + RV1106_CORECLKSEL_CON(1), 0, 5, DFLAGS | CLK_DIVIDER_READ_ONLY, + RV1106_CORECLKGATE_CON(0), 6, GFLAGS), + GATE(PCLK_CPU_ROOT, "pclk_cpu_root", "pclk_dbg", CLK_IS_CRITICAL, + RV1106_CORECLKGATE_CON(0), 10, GFLAGS), + GATE(PCLK_MAILBOX, "pclk_mailbox", "pclk_cpu_root", 0, + RV1106_CORECLKGATE_CON(1), 8, GFLAGS), + + /* PD _TOP */ + COMPOSITE(CLK_50M_SRC, "clk_50m_src", mux_gpll_cpll_p, CLK_IS_CRITICAL, + RV1106_CLKSEL_CON(0), 5, 1, MFLAGS, 0, 5, DFLAGS, + RV1106_CLKGATE_CON(0), 1, GFLAGS), + COMPOSITE(CLK_100M_SRC, "clk_100m_src", mux_gpll_cpll_p, CLK_IS_CRITICAL, + RV1106_CLKSEL_CON(0), 11, 1, MFLAGS, 6, 5, DFLAGS, + RV1106_CLKGATE_CON(0), 2, GFLAGS), + COMPOSITE(CLK_150M_SRC, "clk_150m_src", mux_gpll_cpll_p, CLK_IS_CRITICAL, + RV1106_CLKSEL_CON(1), 5, 1, MFLAGS, 0, 5, DFLAGS, + RV1106_CLKGATE_CON(0), 3, GFLAGS), + COMPOSITE(CLK_200M_SRC, "clk_200m_src", mux_gpll_cpll_p, CLK_IS_CRITICAL, + RV1106_CLKSEL_CON(1), 11, 1, MFLAGS, 6, 5, DFLAGS, + RV1106_CLKGATE_CON(0), 4, GFLAGS), + COMPOSITE(CLK_250M_SRC, "clk_250m_src", mux_gpll_cpll_p, CLK_IS_CRITICAL, + RV1106_CLKSEL_CON(2), 5, 1, MFLAGS, 0, 5, DFLAGS, + RV1106_CLKGATE_CON(0), 5, GFLAGS), + COMPOSITE(CLK_300M_SRC, "clk_300m_src", mux_gpll_cpll_p, CLK_IS_CRITICAL, + RV1106_CLKSEL_CON(2), 11, 1, MFLAGS, 6, 5, DFLAGS, + RV1106_CLKGATE_CON(0), 6, GFLAGS), + COMPOSITE_HALFDIV(CLK_339M_SRC, "clk_339m_src", mux_gpll_cpll_p, CLK_IS_CRITICAL, + RV1106_CLKSEL_CON(3), 5, 1, MFLAGS, 0, 5, DFLAGS, + RV1106_CLKGATE_CON(0), 7, GFLAGS), + COMPOSITE(CLK_400M_SRC, "clk_400m_src", mux_gpll_cpll_p, CLK_IS_CRITICAL, + RV1106_CLKSEL_CON(3), 11, 1, MFLAGS, 6, 5, DFLAGS, + RV1106_CLKGATE_CON(0), 8, GFLAGS), + COMPOSITE_HALFDIV(CLK_450M_SRC, "clk_450m_src", mux_gpll_cpll_p, CLK_IS_CRITICAL, + RV1106_CLKSEL_CON(4), 5, 1, MFLAGS, 0, 5, DFLAGS, + RV1106_CLKGATE_CON(0), 9, GFLAGS), + COMPOSITE(CLK_500M_SRC, "clk_500m_src", mux_gpll_cpll_p, CLK_IS_CRITICAL, + RV1106_CLKSEL_CON(4), 11, 1, MFLAGS, 6, 5, DFLAGS, + RV1106_CLKGATE_CON(0), 10, GFLAGS), + + COMPOSITE_NODIV(PCLK_TOP_ROOT, "pclk_top_root", mux_100m_50m_24m_p, CLK_IS_CRITICAL, + RV1106_CLKSEL_CON(24), 5, 2, MFLAGS, + RV1106_CLKGATE_CON(2), 9, GFLAGS), + + COMPOSITE(CLK_I2S0_8CH_TX_SRC, "clk_i2s0_8ch_tx_src", mux_gpll_cpll_p, 0, + RV1106_CLKSEL_CON(17), 7, 1, MFLAGS, 2, 5, DFLAGS, + RV1106_CLKGATE_CON(1), 13, GFLAGS), + COMPOSITE_FRACMUX(CLK_I2S0_8CH_TX_FRAC, "clk_i2s0_8ch_tx_frac", "clk_i2s0_8ch_tx_src", CLK_SET_RATE_PARENT, + RV1106_CLKSEL_CON(18), 0, + RV1106_CLKGATE_CON(1), 14, GFLAGS, + &rv1106_i2s0_8ch_tx_fracmux), + GATE(MCLK_I2S0_8CH_TX, "mclk_i2s0_8ch_tx", "clk_i2s0_8ch_tx", 0, + RV1106_CLKGATE_CON(1), 15, GFLAGS), + COMPOSITE(CLK_I2S0_8CH_RX_SRC, "clk_i2s0_8ch_rx_src", mux_gpll_cpll_p, 0, + RV1106_CLKSEL_CON(19), 7, 1, MFLAGS, 2, 5, DFLAGS, + RV1106_CLKGATE_CON(2), 0, GFLAGS), + COMPOSITE_FRACMUX(CLK_I2S0_8CH_RX_FRAC, "clk_i2s0_8ch_rx_frac", "clk_i2s0_8ch_rx_src", CLK_SET_RATE_PARENT, + RV1106_CLKSEL_CON(20), 0, + RV1106_CLKGATE_CON(2), 1, GFLAGS, + &rv1106_i2s0_8ch_rx_fracmux), + GATE(MCLK_I2S0_8CH_RX, "mclk_i2s0_8ch_rx", "clk_i2s0_8ch_rx", 0, + RV1106_CLKGATE_CON(2), 2, GFLAGS), + MUX(I2S0_8CH_MCLKOUT, "i2s0_8ch_mclkout", i2s0_8ch_mclkout_p, CLK_SET_RATE_PARENT, + RV1106_CLKSEL_CON(21), 2, 2, MFLAGS), + COMPOSITE(CLK_REF_MIPI0_SRC, "clk_ref_mipi0_src", mux_gpll_cpll_p, 0, + RV1106_CLKSEL_CON(25), 7, 1, MFLAGS, 2, 5, DFLAGS, + RV1106_CLKGATE_CON(3), 4, GFLAGS), + COMPOSITE_FRACMUX(CLK_REF_MIPI0_FRAC, "clk_ref_mipi0_frac", "clk_ref_mipi0_src", CLK_SET_RATE_PARENT, + RV1106_CLKSEL_CON(26), 0, + RV1106_CLKGATE_CON(3), 5, GFLAGS, + &rv1106_clk_ref_mipi0_fracmux), + GATE(MCLK_REF_MIPI0, "mclk_ref_mipi0", "clk_ref_mipi0", 0, + RV1106_CLKGATE_CON(3), 6, GFLAGS), + COMPOSITE(CLK_REF_MIPI1_SRC, "clk_ref_mipi1_src", mux_gpll_cpll_p, 0, + RV1106_CLKSEL_CON(27), 7, 1, MFLAGS, 2, 5, DFLAGS, + RV1106_CLKGATE_CON(3), 7, GFLAGS), + COMPOSITE_FRACMUX(CLK_REF_MIPI1_FRAC, "clk_ref_mipi1_frac", "clk_ref_mipi1_src", CLK_SET_RATE_PARENT, + RV1106_CLKSEL_CON(28), 0, + RV1106_CLKGATE_CON(3), 8, GFLAGS, + &rv1106_clk_ref_mipi1_fracmux), + GATE(MCLK_REF_MIPI1, "mclk_ref_mipi1", "clk_ref_mipi1", 0, + RV1106_CLKGATE_CON(3), 9, GFLAGS), + COMPOSITE(CLK_UART0_SRC, "clk_uart0_src", mux_gpll_cpll_p, 0, + RV1106_CLKSEL_CON(5), 5, 1, MFLAGS, 0, 5, DFLAGS, + RV1106_CLKGATE_CON(0), 11, GFLAGS), + COMPOSITE_FRACMUX(CLK_UART0_FRAC, "clk_uart0_frac", "clk_uart0_src", CLK_SET_RATE_PARENT, + RV1106_CLKSEL_CON(6), 0, + RV1106_CLKGATE_CON(0), 12, GFLAGS, + &rv1106_clk_uart0_fracmux), + GATE(SCLK_UART0, "sclk_uart0", "clk_uart0", 0, + RV1106_CLKGATE_CON(0), 13, GFLAGS), + COMPOSITE(CLK_UART1_SRC, "clk_uart1_src", mux_gpll_cpll_p, 0, + RV1106_CLKSEL_CON(7), 7, 1, MFLAGS, 2, 5, DFLAGS, + RV1106_CLKGATE_CON(0), 14, GFLAGS), + COMPOSITE_FRACMUX(CLK_UART1_FRAC, "clk_uart1_frac", "clk_uart1_src", CLK_SET_RATE_PARENT, + RV1106_CLKSEL_CON(8), 0, + RV1106_CLKGATE_CON(0), 15, GFLAGS, + &rv1106_clk_uart1_fracmux), + GATE(SCLK_UART1, "sclk_uart1", "clk_uart1", 0, + RV1106_CLKGATE_CON(1), 0, GFLAGS), + COMPOSITE(CLK_UART2_SRC, "clk_uart2_src", mux_gpll_cpll_p, 0, + RV1106_CLKSEL_CON(9), 7, 1, MFLAGS, 2, 5, DFLAGS, + RV1106_CLKGATE_CON(1), 1, GFLAGS), + COMPOSITE_FRACMUX(CLK_UART2_FRAC, "clk_uart2_frac", "clk_uart2_src", CLK_SET_RATE_PARENT, + RV1106_CLKSEL_CON(10), 0, + RV1106_CLKGATE_CON(1), 2, GFLAGS, + &rv1106_clk_uart2_fracmux), + GATE(SCLK_UART2, "sclk_uart2", "clk_uart2", 0, + RV1106_CLKGATE_CON(1), 3, GFLAGS), + COMPOSITE(CLK_UART3_SRC, "clk_uart3_src", mux_gpll_cpll_p, 0, + RV1106_CLKSEL_CON(11), 7, 1, MFLAGS, 2, 5, DFLAGS, + RV1106_CLKGATE_CON(1), 4, GFLAGS), + COMPOSITE_FRACMUX(CLK_UART3_FRAC, "clk_uart3_frac", "clk_uart3_src", CLK_SET_RATE_PARENT, + RV1106_CLKSEL_CON(12), 0, + RV1106_CLKGATE_CON(1), 5, GFLAGS, + &rv1106_clk_uart3_fracmux), + GATE(SCLK_UART3, "sclk_uart3", "clk_uart3", 0, + RV1106_CLKGATE_CON(1), 6, GFLAGS), + COMPOSITE(CLK_UART4_SRC, "clk_uart4_src", mux_gpll_cpll_p, 0, + RV1106_CLKSEL_CON(13), 7, 1, MFLAGS, 2, 5, DFLAGS, + RV1106_CLKGATE_CON(1), 7, GFLAGS), + COMPOSITE_FRACMUX(CLK_UART4_FRAC, "clk_uart4_frac", "clk_uart4_src", CLK_SET_RATE_PARENT, + RV1106_CLKSEL_CON(14), 0, + RV1106_CLKGATE_CON(1), 8, GFLAGS, + &rv1106_clk_uart4_fracmux), + GATE(SCLK_UART4, "sclk_uart4", "clk_uart4", 0, + RV1106_CLKGATE_CON(1), 9, GFLAGS), + COMPOSITE(CLK_UART5_SRC, "clk_uart5_src", mux_gpll_cpll_p, 0, + RV1106_CLKSEL_CON(15), 7, 1, MFLAGS, 2, 5, DFLAGS, + RV1106_CLKGATE_CON(1), 10, GFLAGS), + COMPOSITE_FRACMUX(CLK_UART5_FRAC, "clk_uart5_frac", "clk_uart5_src", CLK_SET_RATE_PARENT, + RV1106_CLKSEL_CON(16), 0, + RV1106_CLKGATE_CON(1), 11, GFLAGS, + &rv1106_clk_uart5_fracmux), + GATE(SCLK_UART5, "sclk_uart5", "clk_uart5", 0, + RV1106_CLKGATE_CON(1), 12, GFLAGS), + COMPOSITE(CLK_VICAP_M0_SRC, "clk_vicap_m0_src", mux_gpll_cpll_p, 0, + RV1106_CLKSEL_CON(29), 7, 1, MFLAGS, 2, 5, DFLAGS, + RV1106_CLKGATE_CON(3), 10, GFLAGS), + COMPOSITE_FRACMUX(CLK_VICAP_M0_FRAC, "clk_vicap_m0_frac", "clk_vicap_m0_src", CLK_SET_RATE_PARENT, + RV1106_CLKSEL_CON(30), 0, + RV1106_CLKGATE_CON(3), 11, GFLAGS, + &rv1106_clk_vicap_m0_fracmux), + GATE(SCLK_VICAP_M0, "sclk_vicap_m0", "clk_vicap_m0", 0, + RV1106_CLKGATE_CON(3), 12, GFLAGS), + COMPOSITE(CLK_VICAP_M1_SRC, "clk_vicap_m1_src", mux_gpll_cpll_p, 0, + RV1106_CLKSEL_CON(31), 7, 1, MFLAGS, 2, 5, DFLAGS, + RV1106_CLKGATE_CON(3), 13, GFLAGS), + COMPOSITE_FRACMUX(CLK_VICAP_M1_FRAC, "clk_vicap_m1_frac", "clk_vicap_m1_src", CLK_SET_RATE_PARENT, + RV1106_CLKSEL_CON(32), 0, + RV1106_CLKGATE_CON(3), 14, GFLAGS, + &rv1106_clk_vicap_m1_fracmux), + GATE(SCLK_VICAP_M1, "sclk_vicap_m1", "clk_vicap_m1", 0, + RV1106_CLKGATE_CON(3), 15, GFLAGS), + COMPOSITE(DCLK_VOP_SRC, "dclk_vop_src", mux_gpll_cpll_p, 0, + RV1106_CLKSEL_CON(23), 8, 1, MFLAGS, 3, 5, DFLAGS, + RV1106_CLKGATE_CON(2), 6, GFLAGS), + + /* PD_DDR */ + COMPOSITE_NODIV(PCLK_DDR_ROOT, "pclk_ddr_root", mux_100m_50m_24m_p, CLK_IS_CRITICAL, + RV1106_DDRCLKSEL_CON(0), 0, 2, MFLAGS, + RV1106_DDRCLKGATE_CON(0), 0, GFLAGS), + COMPOSITE_NODIV(ACLK_DDR_ROOT, "aclk_ddr_root", mux_500m_300m_100m_24m_p, CLK_IS_CRITICAL, + RV1106_DDRCLKSEL_CON(0), 8, 2, MFLAGS, + RV1106_DDRCLKGATE_CON(0), 12, GFLAGS), + GATE(PCLK_DDRPHY, "pclk_ddrphy", "pclk_ddr_root", CLK_IGNORE_UNUSED, + RV1106_DDRCLKGATE_CON(1), 3, GFLAGS), + GATE(PCLK_DDR_HWLP, "pclk_ddr_hwlp", "pclk_ddr_root", CLK_IGNORE_UNUSED, + RV1106_DDRCLKGATE_CON(1), 2, GFLAGS), + GATE(PCLK_DDRMON, "pclk_ddrmon", "pclk_ddr_root", 0, + RV1106_DDRCLKGATE_CON(0), 7, GFLAGS), + GATE(CLK_TIMER_DDRMON, "clk_timer_ddrmon", "xin24m", 0, + RV1106_DDRCLKGATE_CON(0), 8, GFLAGS), + GATE(PCLK_DDRC, "pclk_ddrc", "pclk_ddr_root", CLK_IGNORE_UNUSED, + RV1106_DDRCLKGATE_CON(0), 5, GFLAGS), + GATE(PCLK_DFICTRL, "pclk_dfictrl", "pclk_ddr_root", CLK_IS_CRITICAL, + RV1106_DDRCLKGATE_CON(0), 11, GFLAGS), + GATE(ACLK_SYS_SHRM, "aclk_sys_shrm", "aclk_ddr_root", CLK_IS_CRITICAL, + RV1106_DDRCLKGATE_CON(0), 13, GFLAGS), + + /* PD_NPU */ + COMPOSITE_NODIV(HCLK_NPU_ROOT, "hclk_npu_root", mux_150m_100m_50m_24m_p, CLK_IS_CRITICAL, + RV1106_NPUCLKSEL_CON(0), 0, 2, MFLAGS, + RV1106_NPUCLKGATE_CON(0), 0, GFLAGS), + COMPOSITE_NODIV(ACLK_NPU_ROOT, "aclk_npu_root", mux_500m_300m_pvtpll0_pvtpll1_p, CLK_IS_CRITICAL, + RV1106_NPUCLKSEL_CON(0), 2, 2, MFLAGS, + RV1106_NPUCLKGATE_CON(0), 1, GFLAGS), + COMPOSITE_NODIV(PCLK_NPU_ROOT, "pclk_npu_root", mux_100m_50m_24m_p, CLK_IS_CRITICAL, + RV1106_NPUCLKSEL_CON(0), 4, 2, MFLAGS, + RV1106_NPUCLKGATE_CON(0), 2, GFLAGS), + GATE(HCLK_RKNN, "hclk_rknn", "hclk_npu_root", 0, + RV1106_NPUCLKGATE_CON(0), 9, GFLAGS), + GATE(ACLK_RKNN, "aclk_rknn", "aclk_npu_root", 0, + RV1106_NPUCLKGATE_CON(0), 10, GFLAGS), + + /* PD_PERI */ + COMPOSITE_NODIV(PCLK_PERI_ROOT, "pclk_peri_root", mux_100m_50m_24m_p, CLK_IS_CRITICAL, + RV1106_PERICLKSEL_CON(1), 0, 2, MFLAGS, + RV1106_PERICLKGATE_CON(0), 0, GFLAGS), + COMPOSITE_NODIV(ACLK_PERI_ROOT, "aclk_peri_root", mux_400m_200m_100m_24m_p, CLK_IS_CRITICAL, + RV1106_PERICLKSEL_CON(1), 2, 2, MFLAGS, + RV1106_PERICLKGATE_CON(0), 1, GFLAGS), + COMPOSITE_NODIV(HCLK_PERI_ROOT, "hclk_peri_root", mux_200m_100m_50m_24m_p, CLK_IS_CRITICAL, + RV1106_PERICLKSEL_CON(1), 4, 2, MFLAGS, + RV1106_PERICLKGATE_CON(0), 2, GFLAGS), + COMPOSITE_NODIV(ACLK_BUS_ROOT, "aclk_bus_root", mux_300m_200m_100m_24m_p, CLK_IS_CRITICAL, + RV1106_PERICLKSEL_CON(9), 0, 2, MFLAGS, + RV1106_PERICLKGATE_CON(6), 8, GFLAGS), + GATE(PCLK_ACODEC, "pclk_acodec", "pclk_peri_root", 0, + RV1106_PERICLKGATE_CON(6), 3, GFLAGS), + COMPOSITE_NOMUX(MCLK_ACODEC_TX, "mclk_acodec_tx", "mclk_i2s0_8ch_tx", 0, + RV1106_PERICLKSEL_CON(8), 0, 8, DFLAGS, + RV1106_PERICLKGATE_CON(6), 4, GFLAGS), + COMPOSITE_NODIV(CLK_CORE_CRYPTO, "clk_core_crypto", mux_300m_200m_100m_24m_p, 0, + RV1106_PERICLKSEL_CON(6), 5, 2, MFLAGS, + RV1106_PERICLKGATE_CON(3), 11, GFLAGS), + COMPOSITE_NODIV(CLK_PKA_CRYPTO, "clk_pka_crypto", mux_300m_200m_100m_24m_p, 0, + RV1106_PERICLKSEL_CON(6), 7, 2, MFLAGS, + RV1106_PERICLKGATE_CON(3), 12, GFLAGS), + GATE(ACLK_CRYPTO, "aclk_crypto", "aclk_bus_root", 0, + RV1106_PERICLKGATE_CON(3), 13, GFLAGS), + GATE(HCLK_CRYPTO, "hclk_crypto", "hclk_peri_root", 0, + RV1106_PERICLKGATE_CON(3), 14, GFLAGS), + GATE(ACLK_DECOM, "aclk_decom", "aclk_peri_root", 0, + RV1106_PERICLKGATE_CON(5), 9, GFLAGS), + GATE(PCLK_DECOM, "pclk_decom", "pclk_peri_root", 0, + RV1106_PERICLKGATE_CON(5), 10, GFLAGS), + COMPOSITE_NODIV(DCLK_DECOM, "dclk_decom", mux_400m_200m_100m_24m_p, 0, + RV1106_PERICLKSEL_CON(7), 14, 2, MFLAGS, + RV1106_PERICLKGATE_CON(5), 11, GFLAGS), + GATE(ACLK_DMAC, "aclk_dmac", "aclk_bus_root", 0, + RV1106_PERICLKGATE_CON(5), 8, GFLAGS), + GATE(PCLK_DSM, "pclk_dsm", "pclk_peri_root", 0, + RV1106_PERICLKGATE_CON(6), 2, GFLAGS), + GATE(MCLK_DSM, "mclk_dsm", "mclk_i2s0_8ch_tx", 0, + RV1106_PERICLKGATE_CON(6), 1, GFLAGS), + COMPOSITE(CCLK_SRC_EMMC, "cclk_src_emmc", mux_400m_24m_p, 0, + RV1106_PERICLKSEL_CON(7), 6, 1, MFLAGS, 0, 6, DFLAGS, + RV1106_PERICLKGATE_CON(4), 12, GFLAGS), + GATE(HCLK_EMMC, "hclk_emmc", "hclk_peri_root", 0, + RV1106_PERICLKGATE_CON(4), 13, GFLAGS), + GATE(PCLK_GPIO4, "pclk_gpio4", "pclk_peri_root", 0, + RV1106_PERICLKGATE_CON(2), 0, GFLAGS), + GATE(DBCLK_GPIO4, "dbclk_gpio4", "xin24m", 0, + RV1106_PERICLKGATE_CON(2), 1, GFLAGS), + GATE(PCLK_I2C0, "pclk_i2c0", "pclk_peri_root", 0, + RV1106_PERICLKGATE_CON(1), 6, GFLAGS), + COMPOSITE_NODIV(CLK_I2C0, "clk_i2c0", mux_200m_100m_50m_24m_p, 0, + RV1106_PERICLKSEL_CON(1), 8, 2, MFLAGS, + RV1106_PERICLKGATE_CON(1), 7, GFLAGS), + GATE(PCLK_I2C2, "pclk_i2c2", "pclk_peri_root", 0, + RV1106_PERICLKGATE_CON(1), 10, GFLAGS), + COMPOSITE_NODIV(CLK_I2C2, "clk_i2c2", mux_200m_100m_50m_24m_p, 0, + RV1106_PERICLKSEL_CON(1), 12, 2, MFLAGS, + RV1106_PERICLKGATE_CON(1), 11, GFLAGS), + GATE(PCLK_I2C3, "pclk_i2c3", "pclk_peri_root", 0, + RV1106_PERICLKGATE_CON(1), 12, GFLAGS), + COMPOSITE_NODIV(CLK_I2C3, "clk_i2c3", mux_200m_100m_50m_24m_p, 0, + RV1106_PERICLKSEL_CON(1), 14, 2, MFLAGS, + RV1106_PERICLKGATE_CON(1), 13, GFLAGS), + GATE(PCLK_I2C4, "pclk_i2c4", "pclk_peri_root", 0, + RV1106_PERICLKGATE_CON(1), 14, GFLAGS), + COMPOSITE_NODIV(CLK_I2C4, "clk_i2c4", mux_200m_100m_50m_24m_p, 0, + RV1106_PERICLKSEL_CON(2), 0, 2, MFLAGS, + RV1106_PERICLKGATE_CON(1), 15, GFLAGS), + GATE(HCLK_I2S0, "hclk_i2s0", "hclk_peri_root", 0, + RV1106_PERICLKGATE_CON(6), 0, GFLAGS), + GATE(PCLK_DFT2APB, "pclk_dft2apb", "pclk_peri_root", CLK_IGNORE_UNUSED, + RV1106_PERICLKGATE_CON(6), 7, GFLAGS), + GATE(HCLK_IVE, "hclk_ive", "hclk_peri_root", 0, + RV1106_PERICLKGATE_CON(6), 9, GFLAGS), + GATE(ACLK_IVE, "aclk_ive", "aclk_peri_root", 0, + RV1106_PERICLKGATE_CON(6), 10, GFLAGS), + GATE(PCLK_PWM0_PERI, "pclk_pwm0_peri", "pclk_peri_root", 0, + RV1106_PERICLKGATE_CON(7), 3, GFLAGS), + COMPOSITE_NODIV(CLK_PWM0_PERI, "clk_pwm0_peri", mux_100m_50m_24m_p, 0, + RV1106_PERICLKSEL_CON(11), 0, 2, MFLAGS, + RV1106_PERICLKGATE_CON(7), 4, GFLAGS), + GATE(CLK_CAPTURE_PWM0_PERI, "clk_capture_pwm0_peri", "xin24m", 0, + RV1106_PERICLKGATE_CON(7), 5, GFLAGS), + GATE(CLK_TIMER_ROOT, "clk_timer_root", "xin24m", 0, + RV1106_PERICLKGATE_CON(0), 3, GFLAGS), + GATE(HCLK_SFC, "hclk_sfc", "hclk_peri_root", 0, + RV1106_PERICLKGATE_CON(4), 14, GFLAGS), + COMPOSITE(SCLK_SFC, "sclk_sfc", mux_500m_300m_200m_24m_p, 0, + RV1106_PERICLKSEL_CON(7), 12, 2, MFLAGS, 7, 5, DFLAGS, + RV1106_PERICLKGATE_CON(5), 0, GFLAGS), + GATE(PCLK_UART0, "pclk_uart0", "pclk_peri_root", 0, + RV1106_PERICLKGATE_CON(6), 11, GFLAGS), + GATE(PCLK_UART1, "pclk_uart1", "pclk_peri_root", 0, + RV1106_PERICLKGATE_CON(6), 15, GFLAGS), + GATE(PCLK_PWM1_PERI, "pclk_pwm1_peri", "pclk_peri_root", 0, + RV1106_PERICLKGATE_CON(3), 15, GFLAGS), + COMPOSITE_NODIV(CLK_PWM1_PERI, "clk_pwm1_peri", mux_100m_50m_24m_p, 0, + RV1106_PERICLKSEL_CON(6), 9, 2, MFLAGS, + RV1106_PERICLKGATE_CON(4), 0, GFLAGS), + GATE(CLK_CAPTURE_PWM1_PERI, "clk_capture_pwm1_peri", "xin24m", 0, + RV1106_PERICLKGATE_CON(4), 1, GFLAGS), + GATE(PCLK_PWM2_PERI, "pclk_pwm2_peri", "pclk_peri_root", 0, + RV1106_PERICLKGATE_CON(4), 2, GFLAGS), + COMPOSITE_NODIV(CLK_PWM2_PERI, "clk_pwm2_peri", mux_100m_50m_24m_p, 0, + RV1106_PERICLKSEL_CON(6), 11, 2, MFLAGS, + RV1106_PERICLKGATE_CON(4), 3, GFLAGS), + GATE(CLK_CAPTURE_PWM2_PERI, "clk_capture_pwm2_peri", "xin24m", 0, + RV1106_PERICLKGATE_CON(4), 4, GFLAGS), + GATE(HCLK_BOOTROM, "hclk_bootrom", "hclk_peri_root", 0, + RV1106_PERICLKGATE_CON(0), 7, GFLAGS), + GATE(HCLK_SAI, "hclk_sai", "hclk_peri_root", 0, + RV1106_PERICLKGATE_CON(5), 13, GFLAGS), + GATE(MCLK_SAI, "mclk_sai", "mclk_i2s0_8ch_tx", 0, + RV1106_PERICLKGATE_CON(5), 14, GFLAGS), + GATE(PCLK_SARADC, "pclk_saradc", "pclk_peri_root", 0, + RV1106_PERICLKGATE_CON(3), 3, GFLAGS), + COMPOSITE_NOMUX(CLK_SARADC, "clk_saradc", "xin24m", 0, + RV1106_PERICLKSEL_CON(6), 0, 3, DFLAGS, + RV1106_PERICLKGATE_CON(3), 4, GFLAGS), + GATE(PCLK_SPI1, "pclk_spi1", "pclk_peri_root", 0, + RV1106_PERICLKGATE_CON(3), 6, GFLAGS), + COMPOSITE_NODIV(CLK_SPI1, "clk_spi1", mux_200m_100m_50m_24m_p, 0, + RV1106_PERICLKSEL_CON(6), 3, 2, MFLAGS, + RV1106_PERICLKGATE_CON(3), 7, GFLAGS), + GATE(PCLK_STIMER, "pclk_stimer", "pclk_peri_root", 0, + RV1106_PERICLKGATE_CON(0), 15, GFLAGS), + GATE(CLK_STIMER0, "clk_stimer0", "clk_timer_root", 0, + RV1106_PERICLKGATE_CON(1), 0, GFLAGS), + GATE(CLK_STIMER1, "clk_stimer1", "clk_timer_root", 0, + RV1106_PERICLKGATE_CON(1), 1, GFLAGS), + GATE(PCLK_TIMER, "pclk_timer", "pclk_peri_root", 0, + RV1106_PERICLKGATE_CON(0), 8, GFLAGS), + GATE(CLK_TIMER0, "clk_timer0", "clk_timer_root", 0, + RV1106_PERICLKGATE_CON(0), 9, GFLAGS), + GATE(CLK_TIMER1, "clk_timer1", "clk_timer_root", 0, + RV1106_PERICLKGATE_CON(0), 10, GFLAGS), + GATE(CLK_TIMER2, "clk_timer2", "clk_timer_root", 0, + RV1106_PERICLKGATE_CON(0), 11, GFLAGS), + GATE(CLK_TIMER3, "clk_timer3", "clk_timer_root", 0, + RV1106_PERICLKGATE_CON(0), 12, GFLAGS), + GATE(CLK_TIMER4, "clk_timer4", "clk_timer_root", 0, + RV1106_PERICLKGATE_CON(0), 13, GFLAGS), + GATE(CLK_TIMER5, "clk_timer5", "clk_timer_root", 0, + RV1106_PERICLKGATE_CON(0), 14, GFLAGS), + GATE(HCLK_TRNG_NS, "hclk_trng_ns", "hclk_peri_root", 0, + RV1106_PERICLKGATE_CON(3), 9, GFLAGS), + GATE(HCLK_TRNG_S, "hclk_trng_s", "hclk_peri_root", 0, + RV1106_PERICLKGATE_CON(3), 10, GFLAGS), + GATE(PCLK_UART2, "pclk_uart2", "pclk_peri_root", 0, + RV1106_PERICLKGATE_CON(2), 3, GFLAGS), + GATE(PCLK_UART3, "pclk_uart3", "pclk_peri_root", 0, + RV1106_PERICLKGATE_CON(2), 7, GFLAGS), + GATE(PCLK_UART4, "pclk_uart4", "pclk_peri_root", 0, + RV1106_PERICLKGATE_CON(2), 11, GFLAGS), + GATE(PCLK_UART5, "pclk_uart5", "pclk_peri_root", 0, + RV1106_PERICLKGATE_CON(2), 15, GFLAGS), + GATE(ACLK_USBOTG, "aclk_usbotg", "aclk_bus_root", 0, + RV1106_PERICLKGATE_CON(4), 7, GFLAGS), + GATE(CLK_REF_USBOTG, "clk_ref_usbotg", "xin24m", 0, + RV1106_PERICLKGATE_CON(4), 8, GFLAGS), + GATE(PCLK_USBPHY, "pclk_usbphy", "pclk_peri_root", 0, + RV1106_PERICLKGATE_CON(5), 1, GFLAGS), + GATE(CLK_REF_USBPHY, "clk_ref_usbphy", "xin24m", 0, + RV1106_PERICLKGATE_CON(5), 2, GFLAGS), + GATE(PCLK_WDT_NS, "pclk_wdt_ns", "pclk_peri_root", 0, + RV1106_PERICLKGATE_CON(1), 2, GFLAGS), + GATE(TCLK_WDT_NS, "tclk_wdt_ns", "xin24m", 0, + RV1106_PERICLKGATE_CON(1), 3, GFLAGS), + GATE(PCLK_WDT_S, "pclk_wdt_s", "pclk_peri_root", 0, + RV1106_PERICLKGATE_CON(1), 4, GFLAGS), + GATE(TCLK_WDT_S, "tclk_wdt_s", "xin24m", 0, + RV1106_PERICLKGATE_CON(1), 5, GFLAGS), + + /* PD_PMU */ + COMPOSITE_FRACMUX(CLK_RTC32K_FRAC, "clk_rtc32k_frac", "xin24m", CLK_IGNORE_UNUSED, + RV1106_PMUCLKSEL_CON(6), 0, + RV1106_PMUCLKGATE_CON(1), 14, GFLAGS, + &rv1106_rtc32k_pmu_fracmux), + DIV(CLK_100M_PMU, "clk_100m_pmu", "clk_200m_src", 0, + RV1106_PMUCLKSEL_CON(0), 0, 3, DFLAGS), + COMPOSITE_NODIV(PCLK_PMU_ROOT, "pclk_pmu_root", mux_100m_pmu_24m_p, CLK_IS_CRITICAL, + RV1106_PMUCLKSEL_CON(0), 3, 1, MFLAGS, + RV1106_PMUCLKGATE_CON(0), 1, GFLAGS), + COMPOSITE_NODIV(HCLK_PMU_ROOT, "hclk_pmu_root", mux_200m_100m_24m_p, CLK_IS_CRITICAL, + RV1106_PMUCLKSEL_CON(0), 4, 2, MFLAGS, + RV1106_PMUCLKGATE_CON(0), 2, GFLAGS), + GATE(CLK_PMU, "clk_pmu", "xin24m", CLK_IS_CRITICAL, + RV1106_PMUCLKGATE_CON(1), 0, GFLAGS), + GATE(PCLK_PMU, "pclk_pmu", "pclk_pmu_root", CLK_IS_CRITICAL, + RV1106_PMUCLKGATE_CON(1), 1, GFLAGS), + GATE(CLK_DDR_FAIL_SAFE, "clk_ddr_fail_safe", "clk_pmu", 0, + RV1106_PMUCLKGATE_CON(1), 15, GFLAGS), + GATE(PCLK_PMU_GPIO0, "pclk_pmu_gpio0", "pclk_pmu_root", 0, + RV1106_PMUCLKGATE_CON(1), 2, GFLAGS), + COMPOSITE_NODIV(DBCLK_PMU_GPIO0, "dbclk_pmu_gpio0", mux_24m_32k_p, 0, + RV1106_PMUCLKSEL_CON(0), 15, 1, MFLAGS, + RV1106_PMUCLKGATE_CON(1), 3, GFLAGS), + GATE(PCLK_I2C1, "pclk_i2c1", "pclk_pmu_root", 0, + RV1106_PMUCLKGATE_CON(0), 3, GFLAGS), + COMPOSITE_NODIV(CLK_I2C1, "clk_i2c1", mux_200m_100m_24m_32k_p, 0, + RV1106_PMUCLKSEL_CON(0), 6, 2, MFLAGS, + RV1106_PMUCLKGATE_CON(0), 4, GFLAGS), + GATE(PCLK_PMU_MAILBOX, "pclk_pmu_mailbox", "pclk_pmu_root", 0, + RV1106_PMUCLKGATE_CON(2), 10, GFLAGS), + GATE(CLK_PMU_MCU, "clk_pmu_mcu", "hclk_pmu_root", 0, + RV1106_PMUCLKGATE_CON(0), 9, GFLAGS), + GATE(CLK_PMU_MCU_RTC, "clk_pmu_mcu_rtc", "xin24m", 0, + RV1106_PMUCLKGATE_CON(0), 13, GFLAGS), + COMPOSITE_NOMUX(CLK_PVTM_PMU, "clk_pvtm_pmu", "xin24m", 0, + RV1106_PMUCLKSEL_CON(1), 0, 5, DFLAGS, + RV1106_PMUCLKGATE_CON(1), 4, GFLAGS), + GATE(PCLK_PVTM_PMU, "pclk_pvtm_pmu", "pclk_pmu_root", 0, + RV1106_PMUCLKGATE_CON(1), 5, GFLAGS), + GATE(CLK_REFOUT, "clk_refout", "xin24m", 0, + RV1106_PMUCLKGATE_CON(2), 13, GFLAGS), + GATE(HCLK_PMU_SRAM, "hclk_pmu_sram", "hclk_pmu_root", CLK_IGNORE_UNUSED, + RV1106_PMUCLKGATE_CON(0), 8, GFLAGS), + GATE(PCLK_PMU_WDT, "pclk_pmu_wdt", "pclk_pmu_root", 0, + RV1106_PMUCLKGATE_CON(2), 8, GFLAGS), + COMPOSITE_NODIV(TCLK_PMU_WDT, "tclk_pmu_wdt", mux_24m_32k_p, 0, + RV1106_PMUCLKSEL_CON(7), 2, 1, MFLAGS, + RV1106_PMUCLKGATE_CON(2), 9, GFLAGS), + + /* PD_SUBDDR */ + COMPOSITE(CLK_CORE_DDRC_SRC, "clk_core_ddrc_src", mux_dpll_300m_p, CLK_IGNORE_UNUSED, + RV1106_SUBDDRCLKSEL_CON(0), 5, 1, MFLAGS, 0, 5, DFLAGS, + RV1106_SUBDDRCLKGATE_CON(0), 2, GFLAGS), + GATE(CLK_DFICTRL, "clk_dfictrl", "clk_core_ddrc_src", CLK_IGNORE_UNUSED, + RV1106_SUBDDRCLKGATE_CON(0), 5, GFLAGS), + GATE(CLK_DDRMON, "clk_ddrmon", "clk_core_ddrc_src", CLK_IGNORE_UNUSED, + RV1106_SUBDDRCLKGATE_CON(0), 4, GFLAGS), + GATE(CLK_DDR_PHY, "clk_ddr_phy", "clk_core_ddrc_src", CLK_IGNORE_UNUSED, + RV1106_SUBDDRCLKGATE_CON(0), 6, GFLAGS), + GATE(ACLK_DDRC, "aclk_ddrc", "clk_core_ddrc_src", CLK_IS_CRITICAL, + RV1106_SUBDDRCLKGATE_CON(0), 1, GFLAGS), + GATE(CLK_CORE_DDRC, "clk_core_ddrc", "clk_core_ddrc_src", CLK_IS_CRITICAL, + RV1106_SUBDDRCLKGATE_CON(0), 3, GFLAGS), + + /* PD_VEPU */ + COMPOSITE_NODIV(HCLK_VEPU_ROOT, "hclk_vepu_root", mux_200m_100m_50m_24m_p, CLK_IS_CRITICAL, + RV1106_VEPUCLKSEL_CON(0), 0, 2, MFLAGS, + RV1106_VEPUCLKGATE_CON(0), 0, GFLAGS), + COMPOSITE_NODIV(ACLK_VEPU_COM_ROOT, "aclk_vepu_com_root", mux_400m_200m_100m_24m_p, CLK_IS_CRITICAL, + RV1106_VEPUCLKSEL_CON(0), 2, 2, MFLAGS, + RV1106_VEPUCLKGATE_CON(0), 1, GFLAGS), + COMPOSITE_NODIV(ACLK_VEPU_ROOT, "aclk_vepu_root", mux_300m_200m_100m_24m_p, CLK_IS_CRITICAL, + RV1106_VEPUCLKSEL_CON(0), 4, 2, MFLAGS, + RV1106_VEPUCLKGATE_CON(0), 2, GFLAGS), + COMPOSITE_NODIV(PCLK_VEPU_ROOT, "pclk_vepu_root", mux_100m_50m_24m_p, CLK_IS_CRITICAL, + RV1106_VEPUCLKSEL_CON(0), 6, 2, MFLAGS, + RV1106_VEPUCLKGATE_CON(0), 3, GFLAGS), + GATE(PCLK_SPI0, "pclk_spi0", "pclk_vepu_root", 0, + RV1106_VEPUCLKGATE_CON(1), 2, GFLAGS), + COMPOSITE_NODIV(CLK_SPI0, "clk_spi0", mux_200m_100m_50m_24m_p, 0, + RV1106_VEPUCLKSEL_CON(0), 12, 2, MFLAGS, + RV1106_VEPUCLKGATE_CON(1), 3, GFLAGS), + GATE(CLK_UART_DETN_FLT, "clk_uart_detn_flt", "xin24m", 0, + RV1106_VEPUCLKGATE_CON(1), 8, GFLAGS), + GATE(HCLK_VEPU, "hclk_vepu", "hclk_vepu_root", 0, + RV1106_VEPUCLKGATE_CON(0), 8, GFLAGS), + GATE(ACLK_VEPU, "aclk_vepu", "aclk_vepu_root", 0, + RV1106_VEPUCLKGATE_CON(0), 9, GFLAGS), + COMPOSITE_NODIV(CLK_CORE_VEPU, "clk_core_vepu", mux_400m_300m_pvtpll0_pvtpll1_p, 0, + RV1106_VEPUCLKSEL_CON(0), 8, 2, MFLAGS, + RV1106_VEPUCLKGATE_CON(0), 10, GFLAGS), + COMPOSITE_NODIV(CLK_CORE_VEPU_DVBM, "clk_core_vepu_dvbm", mux_200m_100m_50m_24m_p, 0, + RV1106_VEPUCLKSEL_CON(0), 10, 2, MFLAGS, + RV1106_VEPUCLKGATE_CON(0), 13, GFLAGS), + GATE(PCLK_GPIO1, "pclk_gpio1", "pclk_vepu_root", 0, + RV1106_VEPUCLKGATE_CON(0), 15, GFLAGS), + GATE(DBCLK_GPIO1, "dbclk_gpio1", "xin24m", 0, + RV1106_VEPUCLKGATE_CON(1), 0, GFLAGS), + GATE(HCLK_VEPU_PP, "hclk_vepu_pp", "hclk_vepu_root", 0, + RV1106_VEPUCLKGATE_CON(0), 11, GFLAGS), + GATE(ACLK_VEPU_PP, "aclk_vepu_pp", "aclk_vepu_root", 0, + RV1106_VEPUCLKGATE_CON(0), 12, GFLAGS), + + /* PD_VI */ + COMPOSITE_NODIV(HCLK_VI_ROOT, "hclk_vi_root", mux_150m_100m_50m_24m_p, CLK_IS_CRITICAL, + RV1106_VICLKSEL_CON(0), 0, 2, MFLAGS, + RV1106_VICLKGATE_CON(0), 0, GFLAGS), + COMPOSITE_NODIV(ACLK_VI_ROOT, "aclk_vi_root", mux_339m_200m_100m_24m_p, CLK_IS_CRITICAL, + RV1106_VICLKSEL_CON(0), 2, 2, MFLAGS, + RV1106_VICLKGATE_CON(0), 1, GFLAGS), + COMPOSITE_NODIV(PCLK_VI_ROOT, "pclk_vi_root", mux_150m_100m_50m_24m_p, CLK_IS_CRITICAL, + RV1106_VICLKSEL_CON(0), 4, 2, MFLAGS, + RV1106_VICLKGATE_CON(0), 2, GFLAGS), + COMPOSITE_NODIV(PCLK_VI_RTC_ROOT, "pclk_vi_rtc_root", mux_50m_24m_p, 0, + RV1106_VICLKSEL_CON(0), 6, 1, MFLAGS, + RV1106_VICLKGATE_CON(0), 3, GFLAGS), + + GATE(PCLK_CSIHOST0, "pclk_csihost0", "pclk_vi_root", 0, + RV1106_VICLKGATE_CON(1), 3, GFLAGS), + GATE(PCLK_CSIHOST1, "pclk_csihost1", "pclk_vi_root", 0, + RV1106_VICLKGATE_CON(1), 5, GFLAGS), + GATE(PCLK_GPIO3, "pclk_gpio3", "pclk_vi_root", 0, + RV1106_VICLKGATE_CON(1), 15, GFLAGS), + GATE(DBCLK_GPIO3, "dbclk_gpio3", "xin24m", 0, + RV1106_VICLKGATE_CON(2), 0, GFLAGS), + GATE(HCLK_ISP3P2, "hclk_isp3p2", "hclk_vi_root", 0, + RV1106_VICLKGATE_CON(0), 7, GFLAGS), + GATE(ACLK_ISP3P2, "aclk_isp3p2", "aclk_vi_root", 0, + RV1106_VICLKGATE_CON(0), 8, GFLAGS), + COMPOSITE_NODIV(CLK_CORE_ISP3P2, "clk_core_isp3p2", mux_339m_200m_pvtpll0_pvtpll1_p, 0, + RV1106_VICLKSEL_CON(0), 7, 2, MFLAGS, + RV1106_VICLKGATE_CON(0), 9, GFLAGS), + GATE(PCLK_MIPICSIPHY, "pclk_mipicsiphy", "pclk_vi_root", 0, + RV1106_VICLKGATE_CON(1), 14, GFLAGS), + COMPOSITE(CCLK_SRC_SDMMC, "cclk_src_sdmmc", mux_400m_24m_p, 0, + RV1106_VICLKSEL_CON(1), 14, 1, MFLAGS, 8, 6, DFLAGS, + RV1106_VICLKGATE_CON(1), 11, GFLAGS), + GATE(HCLK_SDMMC, "hclk_sdmmc", "hclk_vi_root", 0, + RV1106_VICLKGATE_CON(1), 12, GFLAGS), + GATE(CLK_SDMMC_DETN_FLT, "clk_sdmmc_detn_flt", "xin24m", 0, + RV1106_VICLKGATE_CON(1), 13, GFLAGS), + GATE(PCLK_VI_RTC_TEST, "pclk_vi_rtc_test", "pclk_vi_rtc_root", 0, + RV1106_VICLKGATE_CON(2), 5, GFLAGS), + GATE(PCLK_VI_RTC_PHY, "pclk_vi_rtc_phy", "pclk_vi_rtc_root", 0, + RV1106_VICLKGATE_CON(2), 6, GFLAGS), + COMPOSITE_NODIV(DCLK_VICAP, "dclk_vicap", mux_339m_200m_100m_24m_p, 0, + RV1106_VICLKSEL_CON(0), 9, 2, MFLAGS, + RV1106_VICLKGATE_CON(0), 10, GFLAGS), + GATE(ACLK_VICAP, "aclk_vicap", "aclk_vi_root", 0, + RV1106_VICLKGATE_CON(0), 12, GFLAGS), + GATE(HCLK_VICAP, "hclk_vicap", "hclk_vi_root", 0, + RV1106_VICLKGATE_CON(0), 13, GFLAGS), + + /* PD_VO */ + COMPOSITE_NODIV(ACLK_MAC_ROOT, "aclk_mac_root", mux_300m_200m_100m_24m_p, 0, + RV1106_VOCLKSEL_CON(1), 12, 2, MFLAGS, + RV1106_VOCLKGATE_CON(1), 4, GFLAGS), + COMPOSITE_NODIV(ACLK_VO_ROOT, "aclk_vo_root", mux_400m_200m_100m_24m_p, CLK_IS_CRITICAL, + RV1106_VOCLKSEL_CON(0), 0, 2, MFLAGS, + RV1106_VOCLKGATE_CON(0), 0, GFLAGS), + COMPOSITE_NODIV(HCLK_VO_ROOT, "hclk_vo_root", mux_200m_100m_50m_24m_p, CLK_IS_CRITICAL, + RV1106_VOCLKSEL_CON(0), 2, 2, MFLAGS, + RV1106_VOCLKGATE_CON(0), 1, GFLAGS), + COMPOSITE_NODIV(PCLK_VO_ROOT, "pclk_vo_root", mux_150m_100m_50m_24m_p, CLK_IS_CRITICAL, + RV1106_VOCLKSEL_CON(0), 4, 2, MFLAGS, + RV1106_VOCLKGATE_CON(0), 2, GFLAGS), + COMPOSITE_NODIV(ACLK_VOP_ROOT, "aclk_vop_root", mux_300m_200m_100m_24m_p, 0, + RV1106_VOCLKSEL_CON(1), 10, 2, MFLAGS, + RV1106_VOCLKGATE_CON(0), 11, GFLAGS), + + GATE(PCLK_GPIO2, "pclk_gpio2", "pclk_vo_root", 0, + RV1106_VOCLKGATE_CON(3), 0, GFLAGS), + GATE(DBCLK_GPIO2, "dbclk_gpio2", "xin24m", 0, + RV1106_VOCLKGATE_CON(3), 1, GFLAGS), + GATE(ACLK_MAC, "aclk_mac", "aclk_mac_root", 0, + RV1106_VOCLKGATE_CON(1), 8, GFLAGS), + GATE(PCLK_MAC, "pclk_mac", "pclk_vo_root", 0, + RV1106_VOCLKGATE_CON(1), 9, GFLAGS), + FACTOR(CLK_GMAC0_50M_O, "clk_gmac0_50m_o", "clk_50m_src", 0, 1, 1), + FACTOR(CLK_GMAC0_REF_50M, "clk_gmac0_ref_50m", "clk_gmac0_50m_o", 0, 1, 1), + DIV(CLK_GMAC0_TX_50M_O, "clk_gmac0_tx_50m_o", "clk_gmac0_50m_o", 0, + RV1106_VOCLKSEL_CON(2), 1, 6, DFLAGS), + GATE(CLK_MACPHY, "clk_macphy", "xin24m", 0, + RV1106_VOCLKGATE_CON(2), 13, GFLAGS), + GATE(CLK_OTPC_ARB, "clk_otpc_arb", "xin24m", 0, + RV1106_VOCLKGATE_CON(2), 11, GFLAGS), + GATE(PCLK_OTPC_NS, "pclk_otpc_ns", "pclk_vo_root", 0, + RV1106_VOCLKGATE_CON(2), 3, GFLAGS), + GATE(CLK_SBPI_OTPC_NS, "clk_sbpi_otpc_ns", "xin24m", 0, + RV1106_VOCLKGATE_CON(2), 5, GFLAGS), + COMPOSITE_NOMUX(CLK_USER_OTPC_NS, "clk_user_otpc_ns", "xin24m", 0, + RV1106_VOCLKSEL_CON(3), 10, 3, DFLAGS, + RV1106_VOCLKGATE_CON(2), 6, GFLAGS), + GATE(PCLK_OTPC_S, "pclk_otpc_s", "pclk_vo_root", 0, + RV1106_VOCLKGATE_CON(2), 7, GFLAGS), + GATE(CLK_SBPI_OTPC_S, "clk_sbpi_otpc_s", "xin24m", 0, + RV1106_VOCLKGATE_CON(2), 9, GFLAGS), + COMPOSITE_NOMUX(CLK_USER_OTPC_S, "clk_user_otpc_s", "xin24m", 0, + RV1106_VOCLKSEL_CON(3), 13, 3, DFLAGS, + RV1106_VOCLKGATE_CON(2), 10, GFLAGS), + GATE(PCLK_OTP_MASK, "pclk_otp_mask", "pclk_vo_root", 0, + RV1106_VOCLKGATE_CON(2), 14, GFLAGS), + GATE(CLK_PMC_OTP, "clk_pmc_otp", "clk_sbpi_otpc_s", 0, + RV1106_VOCLKGATE_CON(2), 15, GFLAGS), + GATE(HCLK_RGA2E, "hclk_rga2e", "hclk_vo_root", 0, + RV1106_VOCLKGATE_CON(0), 7, GFLAGS), + GATE(ACLK_RGA2E, "aclk_rga2e", "aclk_vo_root", 0, + RV1106_VOCLKGATE_CON(0), 8, GFLAGS), + COMPOSITE_NODIV(CLK_CORE_RGA2E, "clk_core_rga2e", mux_400m_200m_100m_24m_p, 0, + RV1106_VOCLKSEL_CON(1), 8, 2, MFLAGS, + RV1106_VOCLKGATE_CON(0), 9, GFLAGS), + COMPOSITE(CCLK_SRC_SDIO, "cclk_src_sdio", mux_400m_24m_p, 0, + RV1106_VOCLKSEL_CON(2), 13, 1, MFLAGS, 7, 6, DFLAGS, + RV1106_VOCLKGATE_CON(1), 14, GFLAGS), + GATE(HCLK_SDIO, "hclk_sdio", "hclk_vo_root", 0, + RV1106_VOCLKGATE_CON(1), 15, GFLAGS), + GATE(PCLK_TSADC, "pclk_tsadc", "pclk_vo_root", 0, + RV1106_VOCLKGATE_CON(2), 0, GFLAGS), + COMPOSITE_NOMUX(CLK_TSADC, "clk_tsadc", "xin24m", 0, + RV1106_VOCLKSEL_CON(3), 0, 5, DFLAGS, + RV1106_VOCLKGATE_CON(2), 1, GFLAGS), + COMPOSITE_NOMUX(CLK_TSADC_TSEN, "clk_tsadc_tsen", "xin24m", 0, + RV1106_VOCLKSEL_CON(3), 5, 5, DFLAGS, + RV1106_VOCLKGATE_CON(2), 2, GFLAGS), + GATE(HCLK_VOP, "hclk_vop", "hclk_vo_root", 0, + RV1106_VOCLKGATE_CON(0), 13, GFLAGS), + GATE(DCLK_VOP, "dclk_vop", "dclk_vop_src", 0, + RV1106_VOCLKGATE_CON(0), 14, GFLAGS), + GATE(ACLK_VOP, "aclk_vop", "aclk_vop_root", 0, + RV1106_VOCLKGATE_CON(0), 15, GFLAGS), + + /* IO CLK */ + GATE(RX0PCLK_VICAP, "rx0pclk_vicap", "rx0pclk_vicap_io", 0, + RV1106_VICLKGATE_CON(1), 0, GFLAGS), + GATE(RX1PCLK_VICAP, "rx1pclk_vicap", "rx1pclk_vicap_io", 0, + RV1106_VICLKGATE_CON(1), 1, GFLAGS), + GATE(ISP0CLK_VICAP, "isp0clk_vicap", "isp0clk_vicap_io", 0, + RV1106_VICLKGATE_CON(1), 2, GFLAGS), + GATE(I0CLK_VICAP, "i0clk_vicap", "i0clk_vicap_io", 0, + RV1106_VICLKGATE_CON(0), 14, GFLAGS), + GATE(I1CLK_VICAP, "i1clk_vicap", "i1clk_vicap_io", 0, + RV1106_VICLKGATE_CON(0), 15, GFLAGS), + GATE(PCLK_VICAP, "pclk_vicap", "pclk_vicap_io", 0, + RV1106_VICLKGATE_CON(0), 11, GFLAGS), + GATE(CLK_RXBYTECLKHS_0, "clk_rxbyteclkhs_0", "clk_rxbyteclkhs_0_io", 0, + RV1106_VICLKGATE_CON(1), 4, GFLAGS), + GATE(CLK_RXBYTECLKHS_1, "clk_rxbyteclkhs_1", "clk_rxbyteclkhs_1_io", 0, + RV1106_VICLKGATE_CON(1), 6, GFLAGS), + + GATE(PCLK_VICAP_VEPU, "pclk_vicap_vepu", "pclk_vicap_vepu_io", 0, + RV1106_VEPUCLKGATE_CON(0), 14, GFLAGS), + GATE(SCLK_IN_SPI0, "sclk_in_spi0", "sclk_in_spi0_io", 0, + RV1106_VEPUCLKGATE_CON(1), 4, GFLAGS), + + GATE(CLK_UTMI_USBOTG, "clk_utmi_usbotg", "clk_utmi_usbotg_io", 0, + RV1106_PERICLKGATE_CON(4), 9, GFLAGS), +}; + +static struct rockchip_clk_branch rv1106_grf_clk_branches[] __initdata = { + MMC_GRF(SCLK_EMMC_DRV, "emmc_drv", "cclk_src_emmc", + RV1106_EMMC_CON0, 1, grf_type_sys), + MMC_GRF(SCLK_EMMC_SAMPLE, "emmc_sample", "cclk_src_emmc", + RV1106_EMMC_CON1, 1, grf_type_sys), + MMC_GRF(SCLK_SDMMC_DRV, "sdmmc_drv", "cclk_src_sdmmc", + RV1106_SDMMC_CON0, 1, grf_type_sys), + MMC_GRF(SCLK_SDMMC_SAMPLE, "sdmmc_sample", "cclk_src_sdmmc", + RV1106_SDMMC_CON1, 1, grf_type_sys), + MMC_GRF(SCLK_SDIO_DRV, "sdio_drv", "cclk_src_sdio", + RV1106_SDIO_CON0, 1, grf_type_sys), + MMC_GRF(SCLK_SDIO_SAMPLE, "sdio_sample", "cclk_src_sdio", + RV1106_SDIO_CON1, 1, grf_type_sys), +}; + +static void __init rv1106_pvtpll_init(struct rockchip_clk_provider *ctx) +{ + /* set pvtpll ref clk mux */ + writel_relaxed(CPU_PVTPLL_PATH_CORE, ctx->reg_base + CPU_CLK_PATH_BASE); + + if (!IS_ERR(ctx->grf)) { + regmap_write(ctx->grf, CPU_PVTPLL_CON0_H, + HIWORD_UPDATE(0x7, PVTPLL_LENGTH_SEL_MASK, + PVTPLL_LENGTH_SEL_SHIFT)); + regmap_write(ctx->grf, CPU_PVTPLL_CON0_L, + HIWORD_UPDATE(0x1, PVTPLL_RING_SEL_MASK, + PVTPLL_RING_SEL_SHIFT)); + regmap_write(ctx->grf, CPU_PVTPLL_CON0_L, + HIWORD_UPDATE(0x3, PVTPLL_EN_MASK, + PVTPLL_EN_SHIFT)); + } + + writel_relaxed(0x007f0000, ctx->reg_base + CRU_PVTPLL0_CON0_H); + writel_relaxed(0xffff0018, ctx->reg_base + CRU_PVTPLL0_CON1_L); + writel_relaxed(0xffff0004, ctx->reg_base + CRU_PVTPLL0_CON2_H); + writel_relaxed(0x00030003, ctx->reg_base + CRU_PVTPLL0_CON0_L); + + writel_relaxed(0x007f0000, ctx->reg_base + CRU_PVTPLL1_CON0_H); + writel_relaxed(0xffff0018, ctx->reg_base + CRU_PVTPLL1_CON1_L); + writel_relaxed(0xffff0004, ctx->reg_base + CRU_PVTPLL1_CON2_H); + writel_relaxed(0x00030003, ctx->reg_base + CRU_PVTPLL1_CON0_L); +} + +static int __init clk_rv1106_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct rockchip_clk_provider *ctx; + unsigned long clk_nr; + void __iomem *reg_base; + int ret; + + clk_nr = rockchip_clk_find_max_clk_id(rv1106_clk_branches, + ARRAY_SIZE(rv1106_clk_branches)) + 1; + clk_nr = max(clk_nr, + rockchip_clk_find_max_clk_id(rv1106_grf_clk_branches, + ARRAY_SIZE(rv1106_grf_clk_branches)) + 1); + reg_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(reg_base)) + return dev_err_probe(dev, PTR_ERR(reg_base), + "could not map cru region"); + + ctx = rockchip_clk_init(np, reg_base, clk_nr); + if (IS_ERR(ctx)) + return dev_err_probe(dev, PTR_ERR(ctx), + "rockchip clk init failed"); + + rv1106_pvtpll_init(ctx); + + rockchip_clk_register_plls(ctx, rv1106_pll_clks, + ARRAY_SIZE(rv1106_pll_clks), + RV1106_GRF_SOC_STATUS0); + + rockchip_clk_register_armclk(ctx, ARMCLK, "armclk", + mux_armclk_p, ARRAY_SIZE(mux_armclk_p), + &rv1106_cpuclk_data, rv1106_cpuclk_rates, + ARRAY_SIZE(rv1106_cpuclk_rates)); + + rockchip_clk_register_branches(ctx, rv1106_clk_branches, + ARRAY_SIZE(rv1106_clk_branches)); + + /* + * The MMC drive and sample phase clocks have their control + * registers in the GRF region, so they can only be provided when + * the rockchip,grf property points at a valid syscon. + */ + if (!IS_ERR(ctx->grf)) { + ret = rockchip_clk_add_grf(ctx, ctx->grf, grf_type_sys); + if (ret) + dev_err(dev, "failed to add grf: %d\n", ret); + else + rockchip_clk_register_branches(ctx, + rv1106_grf_clk_branches, + ARRAY_SIZE(rv1106_grf_clk_branches)); + } + + rockchip_register_restart_notifier(ctx, RV1106_GLB_SRST_FST, NULL); + + rockchip_clk_of_add_provider(np, ctx); + + return 0; +} + +static const struct of_device_id clk_rv1106_match_table[] = { + { .compatible = "rockchip,rv1106-cru" }, + { /* end */ } +}; + +static struct platform_driver clk_rv1106_driver = { + .driver = { + .name = "clk-rv1106", + .of_match_table = clk_rv1106_match_table, + .suppress_bind_attrs = true, + }, +}; +builtin_platform_driver_probe(clk_rv1106_driver, clk_rv1106_probe); From 586ff159ec02533c17dd928641529cb0b52e9c62 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Wed, 5 Aug 2026 17:45:09 +0400 Subject: [PATCH 0459/1328] clk: rockchip: rk3576: fix source muxes for SPI0..SPI4 The TRM defines available source muxes for SPI0..SPI4 as - b00: clk_gpll_div6_src - b01: clk_gpll_div8_src - b10: clk_cpll_div10_src - b11: clk_xin_osc0_func Which doesn't match what the current clock driver implements, making it impossible to derive some SPI clock rates such as 37.125 MHz (which requires clk_gpll_div8_src as the source mux). Add a correct mux definition per TRM and point SPI0..SPI4 clocks at it. Fixes: cc40f5baa91b ("clk: rockchip: Add clock controller for the RK3576") Signed-off-by: Alexey Charkov Link: https://patch.msgid.link/20260805-rk3576-spi-clk-v1-1-2f040d0d163b@flipper.net Signed-off-by: Heiko Stuebner --- drivers/clk/rockchip/clk-rk3576.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/clk/rockchip/clk-rk3576.c b/drivers/clk/rockchip/clk-rk3576.c index 63f229e73a45..2cdd667f4004 100644 --- a/drivers/clk/rockchip/clk-rk3576.c +++ b/drivers/clk/rockchip/clk-rk3576.c @@ -315,6 +315,7 @@ PNAME(mux_100m_24m_lclk0_p) = { "clk_cpll_div10", "xin24m", "lclk_asrc_src_0" } PNAME(mux_100m_24m_lclk1_p) = { "clk_cpll_div10", "xin24m", "lclk_asrc_src_1" }; PNAME(mux_150m_100m_50m_24m_p) = { "clk_gpll_div8", "clk_cpll_div10", "clk_cpll_div20", "xin24m" }; PNAME(mux_200m_100m_50m_24m_p) = { "clk_gpll_div6", "clk_cpll_div10", "clk_cpll_div20", "xin24m" }; +PNAME(mux_200m_150m_100m_24m_p) = { "clk_gpll_div6", "clk_gpll_div8", "clk_cpll_div10", "xin24m" }; PNAME(mux_400m_200m_100m_24m_p) = { "clk_gpll_div3", "clk_gpll_div6", "clk_cpll_div10", "xin24m" }; PNAME(mux_500m_250m_100m_24m_p) = { "clk_cpll_div2", "clk_cpll_div4", "clk_cpll_div10", "xin24m" }; PNAME(mux_600m_400m_300m_24m_p) = { "clk_gpll_div2", "clk_gpll_div3", "clk_gpll_div4", "xin24m" }; @@ -706,19 +707,19 @@ static struct rockchip_clk_branch rk3576_clk_branches[] __initdata = { RK3576_CLKGATE_CON(16), 0, GFLAGS), GATE(PCLK_SPI4, "pclk_spi4", "pclk_bus_root", 0, RK3576_CLKGATE_CON(16), 1, GFLAGS), - COMPOSITE_NODIV(CLK_SPI0, "clk_spi0", mux_200m_100m_50m_24m_p, 0, + COMPOSITE_NODIV(CLK_SPI0, "clk_spi0", mux_200m_150m_100m_24m_p, 0, RK3576_CLKSEL_CON(70), 13, 2, MFLAGS, RK3576_CLKGATE_CON(16), 2, GFLAGS), - COMPOSITE_NODIV(CLK_SPI1, "clk_spi1", mux_200m_100m_50m_24m_p, 0, + COMPOSITE_NODIV(CLK_SPI1, "clk_spi1", mux_200m_150m_100m_24m_p, 0, RK3576_CLKSEL_CON(71), 0, 2, MFLAGS, RK3576_CLKGATE_CON(16), 3, GFLAGS), - COMPOSITE_NODIV(CLK_SPI2, "clk_spi2", mux_200m_100m_50m_24m_p, 0, + COMPOSITE_NODIV(CLK_SPI2, "clk_spi2", mux_200m_150m_100m_24m_p, 0, RK3576_CLKSEL_CON(71), 2, 2, MFLAGS, RK3576_CLKGATE_CON(16), 4, GFLAGS), - COMPOSITE_NODIV(CLK_SPI3, "clk_spi3", mux_200m_100m_50m_24m_p, 0, + COMPOSITE_NODIV(CLK_SPI3, "clk_spi3", mux_200m_150m_100m_24m_p, 0, RK3576_CLKSEL_CON(71), 4, 2, MFLAGS, RK3576_CLKGATE_CON(16), 5, GFLAGS), - COMPOSITE_NODIV(CLK_SPI4, "clk_spi4", mux_200m_100m_50m_24m_p, 0, + COMPOSITE_NODIV(CLK_SPI4, "clk_spi4", mux_200m_150m_100m_24m_p, 0, RK3576_CLKSEL_CON(71), 6, 2, MFLAGS, RK3576_CLKGATE_CON(16), 6, GFLAGS), GATE(PCLK_WDT0, "pclk_wdt0", "pclk_bus_root", 0, From 026d7aeadf27291f961893165edb6079a7078f6f Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Wed, 5 Aug 2026 12:20:08 +0000 Subject: [PATCH 0460/1328] f2fs: fix to return -EFSCORRUPTED in f2fs_get_node_info() correctly Otherwise, it will cache wrong nat info in cache. Cc: stable@kernel.org Fixes: 3cb396a2c790 ("f2fs: fix to do sanity check on nat entry of quota inode") Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/node.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/f2fs/node.c b/fs/f2fs/node.c index 38917e4a7319..9775fa90636a 100644 --- a/fs/f2fs/node.c +++ b/fs/f2fs/node.c @@ -660,6 +660,7 @@ int f2fs_get_node_info(struct f2fs_sb_info *sbi, nid_t nid, __builtin_return_address(0), ni->ino, ni->nid, ni->blk_addr, ni->version, ni->flag); f2fs_handle_error(sbi, ERROR_INCONSISTENT_NAT); + return -EFSCORRUPTED; } /* cache nat entry */ From 7173538c41d6046f1a92d1f92bc6d0c7b7e314a2 Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Wed, 5 Aug 2026 12:20:50 +0000 Subject: [PATCH 0461/1328] f2fs: avoid unnecessary shrink in f2fs_shrink_scan() In f2fs_shrink_scan(), let's check if we have already shrinked enough number of memory before calling f2fs_shrink_read_extent_tree(). Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/shrinker.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/f2fs/shrinker.c b/fs/f2fs/shrinker.c index b88babcf6ab4..4f6bf5926de4 100644 --- a/fs/f2fs/shrinker.c +++ b/fs/f2fs/shrinker.c @@ -109,7 +109,8 @@ unsigned long f2fs_shrink_scan(struct shrinker *shrink, freed += f2fs_shrink_age_extent_tree(sbi, nr >> 2); /* shrink read extent cache entries */ - freed += f2fs_shrink_read_extent_tree(sbi, nr >> 2); + if (freed < nr) + freed += f2fs_shrink_read_extent_tree(sbi, nr >> 2); /* shrink clean nat cache entries */ if (freed < nr) From 5b86eab84ac8e9289b5afc52ef88ab18ba5bacab Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Wed, 5 Aug 2026 12:29:20 +0000 Subject: [PATCH 0462/1328] f2fs: fix to clear dirty flag on folio in error path If node block is corrupted due to chksum mismatch or inconsistent footer info, it needs to drop clear flag of node folio, in order to persist inconsistent node data to storage. Cc: stable@kernel.org Fixes: b42b179bda9f ("f2fs: fix to do checksum even if inode page is uptodate") Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/node.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/f2fs/node.c b/fs/f2fs/node.c index 9775fa90636a..a98855b8cc5e 100644 --- a/fs/f2fs/node.c +++ b/fs/f2fs/node.c @@ -1618,7 +1618,7 @@ static struct folio *__get_node_folio(struct f2fs_sb_info *sbi, pgoff_t nid, if (!err) return folio; out_err: - folio_clear_uptodate(folio); + clear_node_folio_dirty(folio); out_put_err: /* ENOENT comes from read_node_folio which is not an error. */ if (err != -ENOENT) From 0fb9083a6e56f688aeeccfc7ef7eeba586e57dc6 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Wed, 5 Aug 2026 17:20:51 +0200 Subject: [PATCH 0463/1328] clk: renesas: Add R-Car X5H CPG driver Add a minimal Clock Pulse Generator driver for the R-Car X5H (R8A78000) SoC. For now this supports just the few fixed-rate clocks that are needed by the current minimal DTS. Signed-off-by: Geert Uytterhoeven Reviewed-by: Marek Vasut Link: https://patch.msgid.link/3f981753722de1ba871667be63460e89d299d635.1785941595.git.geert+renesas@glider.be --- drivers/clk/renesas/Kconfig | 4 + drivers/clk/renesas/Makefile | 1 + drivers/clk/renesas/r8a78000-cpg.c | 166 +++++++++++++++++++++++++++++ 3 files changed, 171 insertions(+) create mode 100644 drivers/clk/renesas/r8a78000-cpg.c diff --git a/drivers/clk/renesas/Kconfig b/drivers/clk/renesas/Kconfig index 5c0238e878b7..17b95a2e96fb 100644 --- a/drivers/clk/renesas/Kconfig +++ b/drivers/clk/renesas/Kconfig @@ -34,6 +34,7 @@ config CLK_RENESAS select CLK_R8A779F0 if ARCH_R8A779F0 select CLK_R8A779G0 if ARCH_R8A779G0 select CLK_R8A779H0 if ARCH_R8A779H0 + select CLK_R8A78000 if ARCH_R8A78000 select CLK_R9A06G032 if ARCH_R9A06G032 select CLK_R9A07G043 if ARCH_R9A07G043 select CLK_R9A07G044 if ARCH_R9A07G044 @@ -176,6 +177,9 @@ config CLK_R8A779H0 bool "R-Car V4M clock support" if COMPILE_TEST select CLK_RCAR_GEN4_CPG +config CLK_R8A78000 + bool "R-Car X5H clock support" if COMPILE_TEST + config CLK_R9A06G032 bool "RZ/N1D clock support" if COMPILE_TEST diff --git a/drivers/clk/renesas/Makefile b/drivers/clk/renesas/Makefile index ac790e56034b..658f8aac5127 100644 --- a/drivers/clk/renesas/Makefile +++ b/drivers/clk/renesas/Makefile @@ -31,6 +31,7 @@ obj-$(CONFIG_CLK_R8A779A0) += r8a779a0-cpg-mssr.o obj-$(CONFIG_CLK_R8A779F0) += r8a779f0-cpg-mssr.o obj-$(CONFIG_CLK_R8A779G0) += r8a779g0-cpg-mssr.o obj-$(CONFIG_CLK_R8A779H0) += r8a779h0-cpg-mssr.o +obj-$(CONFIG_CLK_R8A78000) += r8a78000-cpg.o obj-$(CONFIG_CLK_R9A06G032) += r9a06g032-clocks.o obj-$(CONFIG_CLK_R9A07G043) += r9a07g043-cpg.o obj-$(CONFIG_CLK_R9A07G044) += r9a07g044-cpg.o diff --git a/drivers/clk/renesas/r8a78000-cpg.c b/drivers/clk/renesas/r8a78000-cpg.c new file mode 100644 index 000000000000..7946f9d0b423 --- /dev/null +++ b/drivers/clk/renesas/r8a78000-cpg.c @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * R-Car X5H Clock Pulse Generator + * + * Copyright (C) 2026 Glider bv + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +struct clk_map { + int dt_id; /* DT binding clock ID or -1 sentinel */ + u32 fw_id; /* FIXED_CLK() ID */ +}; + +enum fixed_clk { + FIXED_CLK_66M, + FIXED_CLK_266M, + NUM_FIXED_CLKS +}; + +static const unsigned long fixed_clk_rates[NUM_FIXED_CLKS] = { + [FIXED_CLK_66M] = 66666000, + [FIXED_CLK_266M] = 266660000, +}; + +#define FIXED_CLK(rate) FIXED_CLK_ ## rate + +/** + * struct r8a78000_cpg_priv - Clock Pulse Generator Private Data + * + * @dev: CPG device + * @map: Mapping from DT clock IDs to fixed-rate clocks + * @fixed_hws: Fixed rate clocks + */ +struct r8a78000_cpg_priv { + struct device *dev; + const struct clk_map *map; + struct clk_hw *fixed_hws[NUM_FIXED_CLKS]; +}; + +static const struct clk_map *clk_map_find(const struct clk_map *map, u32 id) +{ + if (!map) + return NULL; + + for (; map->dt_id >= 0; map++) { + if (map->dt_id == id) + return map; + } + + return NULL; +} + +static struct clk_hw *r8a78000_clk_get(struct of_phandle_args *spec, + void *data) +{ + struct r8a78000_cpg_priv *priv = data; + struct device *dev = priv->dev; + const struct clk_map *map; + struct clk_hw *hw; + u32 id; + + if (spec->args_count != 1) + return ERR_PTR(-EINVAL); + + id = spec->args[0]; + + map = clk_map_find(priv->map, id); + if (!map) { + dev_err(dev, "Unknown clock %u\n", id); + return ERR_PTR(-ENOENT); + } + + dev_dbg(dev, "Mapping DT clock %u to fixed clock %u\n", id, map->fw_id); + + hw = priv->fixed_hws[map->fw_id]; + + dev_dbg(dev, "clock %u is %s at %lu Hz\n", id, clk_hw_get_name(hw), + clk_hw_get_rate(hw)); + + return hw; +} + +static int register_fixed_clks(struct r8a78000_cpg_priv *priv) +{ + struct device *dev = priv->dev; + unsigned long rate; + struct clk_hw *hw; + const char *name; + + for (unsigned int i = 0; i < ARRAY_SIZE(fixed_clk_rates); i++) { + rate = fixed_clk_rates[i]; + name = devm_kasprintf(dev, GFP_KERNEL, "cpg-%lu", rate); + if (!name) + return -ENOMEM; + + hw = devm_clk_hw_register_fixed_rate(dev, name, NULL, 0, rate); + if (IS_ERR(hw)) + return PTR_ERR(hw); + + priv->fixed_hws[i] = hw; + } + + return 0; +} + +static int r8a78000_cpg_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct r8a78000_cpg_priv *priv; + const struct clk_map *map; + int ret; + + map = of_device_get_match_data(dev); + if (!map) + return -ENODEV; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + priv->map = map; + + ret = register_fixed_clks(priv); + if (ret) + return ret; + + return devm_of_clk_add_hw_provider(dev, r8a78000_clk_get, priv); +} + +static const struct clk_map r8a78000_cpg_default[] = { + { R8A78000_CPG_SGASYNCD4_PERW_BUS, FIXED_CLK(266M) }, + { R8A78000_CPG_SGASYNCD16_PERW_BUS, FIXED_CLK(66M) }, + { -1 } +}; + +static const struct of_device_id r8a78000_cpg_match[] = { + { + .compatible = "renesas,r8a78000-cpg", + .data = &r8a78000_cpg_default, + }, + { /* sentinel */ } +}; + +static struct platform_driver r8a78000_cpg_driver = { + .probe = r8a78000_cpg_probe, + .driver = { + .name = "r8a78000-cpg", + .of_match_table = r8a78000_cpg_match, + .suppress_bind_attrs = true, + }, +}; + +builtin_platform_driver(r8a78000_cpg_driver) + +MODULE_DESCRIPTION("R-Car X5H CPG Driver"); From d97a98f434af98c1a46f5dc98b0f861187c52f63 Mon Sep 17 00:00:00 2001 From: Mikhail Lukianchikov Date: Fri, 17 Jul 2026 18:10:16 +0600 Subject: [PATCH 0464/1328] mfd: viperboard: Fix native fields type in structures as little-endian Sparse reports several warnings about incorrect type in assignment (different base types) when building the i2c-viperboard.c driver: warning: incorrect type in assignment (different base types) expected unsigned short [usertype] addr got restricted __le16 [usertype] Signed-off-by: Mikhail Lukianchikov Link: https://patch.msgid.link/20260717121015.11666-2-avermoal@gmail.com Signed-off-by: Lee Jones --- include/linux/mfd/viperboard.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/linux/mfd/viperboard.h b/include/linux/mfd/viperboard.h index 0557667fe544..5761ebdc81b3 100644 --- a/include/linux/mfd/viperboard.h +++ b/include/linux/mfd/viperboard.h @@ -47,7 +47,7 @@ struct vprbrd_i2c_write_hdr { u8 cmd; - u16 addr; + __le16 addr; u8 len1; u8 len2; u8 last; @@ -57,15 +57,15 @@ struct vprbrd_i2c_write_hdr { struct vprbrd_i2c_read_hdr { u8 cmd; - u16 addr; + __le16 addr; u8 len0; u8 len1; u8 len2; u8 len3; u8 len4; u8 len5; - u16 tf1; /* transfer 1 length */ - u16 tf2; /* transfer 2 length */ + __le16 tf1; /* transfer 1 length */ + __le16 tf2; /* transfer 2 length */ } __packed; struct vprbrd_i2c_status { @@ -89,7 +89,7 @@ struct vprbrd_i2c_addr_msg { u8 cmd; u8 addr; u8 unknown1; - u16 len; + __le16 len; u8 unknown2; u8 unknown3; } __packed; From 83feedd9d83c0c5199f98c72df0a6196b4aefb4d Mon Sep 17 00:00:00 2001 From: Abdun Nihaal Date: Mon, 20 Jul 2026 17:08:32 +0530 Subject: [PATCH 0465/1328] mfd: sm501: Fix potential memory leaks during remove The memory allocated for struct sm501_devdata in sm501_pci_probe() and sm501_plat_probe() is not freed by the corresponding remove functions sm501_pci_remove() and sm501_plat_remove(). Fix that by adding a call to kfree(). Fixes: b6d6454fdb66 ("[PATCH] mfd: SM501 core driver") Cc: stable@vger.kernel.org Signed-off-by: Abdun Nihaal Link: https://patch.msgid.link/20260720113836.73133-1-nihaal@cse.iitm.ac.in Signed-off-by: Lee Jones --- drivers/mfd/sm501.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/mfd/sm501.c b/drivers/mfd/sm501.c index b5bda477ebfc..77888965d91e 100644 --- a/drivers/mfd/sm501.c +++ b/drivers/mfd/sm501.c @@ -1627,6 +1627,7 @@ static void sm501_pci_remove(struct pci_dev *dev) release_mem_region(sm->io_res->start, 0x100); pci_disable_device(dev); + kfree(sm); } static void sm501_plat_remove(struct platform_device *dev) @@ -1637,6 +1638,7 @@ static void sm501_plat_remove(struct platform_device *dev) iounmap(sm->regs); release_mem_region(sm->io_res->start, 0x100); + kfree(sm); } static const struct pci_device_id sm501_pci_tbl[] = { From 0be718b5451bd83865d6e2a8d750ca7886c4772a Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Mon, 20 Jul 2026 19:55:23 +0800 Subject: [PATCH 0466/1328] mfd: rave-sp: validate received frame payload lengths A received RAVE-SP frame contains protocol data followed by a variant-specific one- or two-byte checksum. rave_sp_receive_frame() derives a checksum pointer before proving that the frame contains the checksum, then passes the checksum-inclusive length to handlers that index the command, acknowledgment ID and event-data bytes or derive a reply payload length. Name those protocol field offsets, prove the checksum extent before deriving the protocol-data length, pass only that data length to the handlers, and require the complete event or reply prefix before consuming it. Fixes: 538ee27290fa ("mfd: Add driver for RAVE Supervisory Processor") Signed-off-by: Pengpeng Hou Link: https://lore.kernel.org/all/20260706092337.78754-1-pengpeng@iscas.ac.cn/ Link: https://patch.msgid.link/20260720115523.99956-1-pengpeng@iscas.ac.cn Signed-off-by: Lee Jones --- drivers/mfd/rave-sp.c | 64 +++++++++++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/drivers/mfd/rave-sp.c b/drivers/mfd/rave-sp.c index c1b78d127a26..05d26d92df03 100644 --- a/drivers/mfd/rave-sp.c +++ b/drivers/mfd/rave-sp.c @@ -63,6 +63,12 @@ #define RAVE_SP_TX_BUFFER_SIZE \ (RAVE_SP_STX_ETX_SIZE + 2 * RAVE_SP_RX_BUFFER_SIZE) +enum rave_sp_frame_offset { + RAVE_SP_FRAME_CODE_OFFSET, + RAVE_SP_FRAME_ACK_ID_OFFSET, + RAVE_SP_FRAME_DATA_OFFSET, +}; + /** * enum rave_sp_deframer_state - Possible state for de-framer * @@ -352,7 +358,7 @@ int rave_sp_exec(struct rave_sp *sp, int command, ret = 0; u8 ackid; - command = sp->variant->cmd.translate(data[0]); + command = sp->variant->cmd.translate(data[RAVE_SP_FRAME_CODE_OFFSET]); if (command < 0) return command; @@ -366,8 +372,8 @@ int rave_sp_exec(struct rave_sp *sp, sp->reply = &reply; mutex_unlock(&sp->reply_lock); - data[0] = command; - data[1] = ackid; + data[RAVE_SP_FRAME_CODE_OFFSET] = command; + data[RAVE_SP_FRAME_ACK_ID_OFFSET] = ackid; rave_sp_write(sp, data, data_size); @@ -388,16 +394,23 @@ EXPORT_SYMBOL_GPL(rave_sp_exec); static void rave_sp_receive_event(struct rave_sp *sp, const unsigned char *data, size_t length) { - u8 cmd[] = { - [0] = rave_sp_reply_code(data[0]), - [1] = data[1], - }; + unsigned long action; + u8 cmd[RAVE_SP_FRAME_DATA_OFFSET]; + + if (length < RAVE_SP_FRAME_DATA_OFFSET + 1) { + dev_warn(&sp->serdev->dev, "Dropping short event frame\n"); + return; + } + + cmd[RAVE_SP_FRAME_CODE_OFFSET] = + rave_sp_reply_code(data[RAVE_SP_FRAME_CODE_OFFSET]); + cmd[RAVE_SP_FRAME_ACK_ID_OFFSET] = data[RAVE_SP_FRAME_ACK_ID_OFFSET]; rave_sp_write(sp, cmd, sizeof(cmd)); - blocking_notifier_call_chain(&sp->event_notifier_list, - rave_sp_action_pack(data[0], data[2]), - NULL); + action = rave_sp_action_pack(data[RAVE_SP_FRAME_CODE_OFFSET], + data[RAVE_SP_FRAME_DATA_OFFSET]); + blocking_notifier_call_chain(&sp->event_notifier_list, action, NULL); } static void rave_sp_receive_reply(struct rave_sp *sp, @@ -405,27 +418,35 @@ static void rave_sp_receive_reply(struct rave_sp *sp, { struct device *dev = &sp->serdev->dev; struct rave_sp_reply *reply; - const size_t payload_length = length - 2; + size_t payload_length; + + if (length < RAVE_SP_FRAME_DATA_OFFSET) { + dev_warn(dev, "Dropping short reply frame\n"); + return; + } + payload_length = length - RAVE_SP_FRAME_DATA_OFFSET; mutex_lock(&sp->reply_lock); reply = sp->reply; if (reply) { - if (reply->code == data[0] && reply->ackid == data[1] && + if (reply->code == data[RAVE_SP_FRAME_CODE_OFFSET] && + reply->ackid == data[RAVE_SP_FRAME_ACK_ID_OFFSET] && payload_length >= reply->length) { /* * We are relying on memcpy(dst, src, 0) to be a no-op * when handling commands that have a no-payload reply */ - memcpy(reply->data, &data[2], reply->length); + memcpy(reply->data, &data[RAVE_SP_FRAME_DATA_OFFSET], + reply->length); complete(&reply->received); sp->reply = NULL; } else { dev_err(dev, "Ignoring incorrect reply\n"); dev_dbg(dev, "Code: expected = 0x%08x received = 0x%08x\n", - reply->code, data[0]); + reply->code, data[RAVE_SP_FRAME_CODE_OFFSET]); dev_dbg(dev, "ACK ID: expected = 0x%08x received = 0x%08x\n", - reply->ackid, data[1]); + reply->ackid, data[RAVE_SP_FRAME_ACK_ID_OFFSET]); dev_dbg(dev, "Length: expected = %zu received = %zu\n", reply->length, payload_length); } @@ -439,10 +460,10 @@ static void rave_sp_receive_frame(struct rave_sp *sp, size_t length) { const size_t checksum_length = sp->variant->checksum->length; - const size_t payload_length = length - checksum_length; - const u8 *crc_reported = &data[payload_length]; struct device *dev = &sp->serdev->dev; u8 crc_calculated[RAVE_SP_CHECKSUM_SIZE]; + const u8 *crc_reported; + size_t payload_length; if (unlikely(checksum_length > sizeof(crc_calculated))) { dev_warn(dev, "Checksum too long, dropping\n"); @@ -457,6 +478,9 @@ static void rave_sp_receive_frame(struct rave_sp *sp, return; } + payload_length = length - checksum_length; + crc_reported = &data[payload_length]; + sp->variant->checksum->subroutine(data, payload_length, crc_calculated); @@ -465,10 +489,10 @@ static void rave_sp_receive_frame(struct rave_sp *sp, return; } - if (rave_sp_id_is_event(data[0])) - rave_sp_receive_event(sp, data, length); + if (rave_sp_id_is_event(data[RAVE_SP_FRAME_CODE_OFFSET])) + rave_sp_receive_event(sp, data, payload_length); else - rave_sp_receive_reply(sp, data, length); + rave_sp_receive_reply(sp, data, payload_length); } static size_t rave_sp_receive_buf(struct serdev_device *serdev, From 08ea045e0b82cbcadb7a2efc43a23561489a00f2 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Mon, 20 Jul 2026 19:54:23 +0800 Subject: [PATCH 0467/1328] mfd: iqs62x: Reject zero-length firmware records struct iqs62x_fw_rec includes the first data byte in its fixed-size header, so the parser advances by len - 1 bytes after that header. A zero len makes the size_t cursor update move back by one byte, so the next record overlaps the current record instead of following a valid declared extent. Reject zero-length records and express the remaining-size check without an offset addition. Fixes: 4d9cf7df8d35 ("mfd: Add support for Azoteq IQS620A/621/622/624/625") Signed-off-by: Pengpeng Hou Link: https://lore.kernel.org/all/20260706091034.75865-1-pengpeng@iscas.ac.cn/ Link: https://patch.msgid.link/20260720115423.94994-1-pengpeng@iscas.ac.cn Signed-off-by: Lee Jones --- drivers/mfd/iqs62x.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mfd/iqs62x.c b/drivers/mfd/iqs62x.c index ee017617d1d1..412ae7777f72 100644 --- a/drivers/mfd/iqs62x.c +++ b/drivers/mfd/iqs62x.c @@ -237,7 +237,7 @@ static int iqs62x_firmware_parse(struct iqs62x_core *iqs62x, fw_rec = (struct iqs62x_fw_rec *)(fw->data + pos); pos += sizeof(*fw_rec); - if (pos + fw_rec->len - 1 > fw->size) { + if (!fw_rec->len || fw_rec->len - 1 > fw->size - pos) { ret = -EINVAL; break; } From f5071235a1621fc8c8f6c513252028b0858faa9c Mon Sep 17 00:00:00 2001 From: Fenglin Wu Date: Tue, 4 Aug 2026 23:18:13 -0700 Subject: [PATCH 0468/1328] dt-bindings: mfd: qcom,spmi-pmic: Document haptics device Some of the Qualcomm SPMI PMIC has haptics device in it, add it in the device list. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Fenglin Wu Link: https://patch.msgid.link/20260804-qcom-spmi-haptics-v5-2-77128ebbdd2d@oss.qualcomm.com Signed-off-by: Lee Jones --- Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml b/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml index 809be2756a0c..072c75a9c377 100644 --- a/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml +++ b/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml @@ -166,6 +166,10 @@ patternProperties: type: object $ref: /schemas/pinctrl/qcom,pmic-gpio.yaml# + "^haptics@[0-9a-f]+$": + type: object + $ref: /schemas/input/qcom,spmi-haptics.yaml# + "^led-controller@[0-9a-f]+$": type: object $ref: /schemas/leds/qcom,spmi-flash-led.yaml# From 0a29aa605286bcd01632eb6b61f59b9053312347 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Sun, 19 Jul 2026 15:00:32 +0200 Subject: [PATCH 0469/1328] mfd: macsmc: Fix key count endianness annotation SMC firmware returns the value of the #KEY key in big-endian unlike most other keys. Reading it through apple_smc_read_u32() into a plain u32 and then converting with be32_to_cpu() makes sparse complain: drivers/mfd/macsmc.c:462:26: sparse: cast to restricted __be32 Read the raw value into a __be32 using apple_smc_read() instead. Fixes: e038d985c982 ("mfd: Add Apple Silicon System Management Controller") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202607181046.OANjIoqR-lkp@intel.com/ Signed-off-by: Sven Peter Reviewed-by: Janne Grunau Reviewed-by: Joshua Peisach Link: https://patch.msgid.link/20260719-b4-macsmc-be32-fix-v1-1-c7b1936307fa@kernel.org Signed-off-by: Lee Jones --- drivers/mfd/macsmc.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/mfd/macsmc.c b/drivers/mfd/macsmc.c index 358feec2d088..514cba7dc897 100644 --- a/drivers/mfd/macsmc.c +++ b/drivers/mfd/macsmc.c @@ -410,7 +410,7 @@ static int apple_smc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct apple_smc *smc; - u32 count; + __be32 count; int ret; smc = devm_kzalloc(dev, sizeof(*smc), GFP_KERNEL); @@ -461,8 +461,10 @@ static int apple_smc_probe(struct platform_device *pdev) dev_set_drvdata(&pdev->dev, smc); BLOCKING_INIT_NOTIFIER_HEAD(&smc->event_handlers); - ret = apple_smc_read_u32(smc, SMC_KEY(#KEY), &count); - if (ret) + ret = apple_smc_read(smc, SMC_KEY(#KEY), &count, sizeof(count)); + if (ret >= 0 && ret != sizeof(count)) + ret = -EINVAL; + if (ret < 0) return dev_err_probe(smc->dev, ret, "Failed to get key count"); smc->key_count = be32_to_cpu(count); From 95231a92250ec6d04b295b912ea42135875f6c53 Mon Sep 17 00:00:00 2001 From: Eduard Bostina Date: Thu, 23 Jul 2026 10:06:02 +0000 Subject: [PATCH 0470/1328] dt-bindings: leds: backlight: Convert TPS65217 to DT schema Convert the Texas Instruments TPS65217 bindings to DT schema. During the conversion, the following updates were made: - Documented the 'regulators', 'charger' and 'pwrbutton' child nodes, which are used by the boards but were missing from the old txt binding. - Documented the 'interrupts', 'interrupt-controller', '#interrupt-cells' and 'ti,pmic-shutdown-controller' properties, which are used by the am335x boards and read by the driver. The regulator/tps65217.txt binding documented the same 'ti,tps65217' compatible and is superseded by this schema, so remove it as well. Signed-off-by: Eduard Bostina Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260723100605.628882-5-egbostina@gmail.com Signed-off-by: Lee Jones --- .../bindings/leds/backlight/ti,tps65217.yaml | 170 ++++++++++++++++++ .../leds/backlight/tps65217-backlight.txt | 27 --- .../bindings/regulator/tps65217.txt | 78 -------- 3 files changed, 170 insertions(+), 105 deletions(-) create mode 100644 Documentation/devicetree/bindings/leds/backlight/ti,tps65217.yaml delete mode 100644 Documentation/devicetree/bindings/leds/backlight/tps65217-backlight.txt delete mode 100644 Documentation/devicetree/bindings/regulator/tps65217.txt diff --git a/Documentation/devicetree/bindings/leds/backlight/ti,tps65217.yaml b/Documentation/devicetree/bindings/leds/backlight/ti,tps65217.yaml new file mode 100644 index 000000000000..4d2f4dbd2d52 --- /dev/null +++ b/Documentation/devicetree/bindings/leds/backlight/ti,tps65217.yaml @@ -0,0 +1,170 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/leds/backlight/ti,tps65217.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: TPS65217 family of regulators + +maintainers: + - Eduard Bostina + +description: + The TPS65217 chip contains a boost converter and current sinks which can be + used to drive LEDs for use as backlights. + +properties: + compatible: + const: ti,tps65217 + + reg: + maxItems: 1 + description: I2C slave address + + interrupts: + maxItems: 1 + + interrupt-controller: true + + "#interrupt-cells": + const: 1 + + ti,pmic-shutdown-controller: + type: boolean + description: + Set the PMIC to shutdown on PWR_EN toggle. + + backlight: + type: object + additionalProperties: false + description: + Node for specifying WLED1 and WLED2 lines in TPS65217. + + properties: + isel: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [1, 2] + description: | + Selection bit. Valid values: + 1 - ISEL1 (low-level) + 2 - ISEL2 (high-level) + + fdim: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [100, 200, 500, 1000] + description: + PWM dimming frequency in Hz. + + default-brightness: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 100 + + required: + - isel + - fdim + - default-brightness + + regulators: + type: object + additionalProperties: false + description: + List of child nodes that specify the regulator initialization data. + Not all regulators for the given device need to be present. + + patternProperties: + "^(dcdc[1-3]|ldo[1-4])$": + type: object + $ref: /schemas/regulator/regulator.yaml# + unevaluatedProperties: false + + charger: + $ref: /schemas/power/supply/tps65217-charger.yaml# + unevaluatedProperties: false + + pwrbutton: + $ref: /schemas/input/ti,tps65217-pwrbutton.yaml# + unevaluatedProperties: false + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + pmic@24 { + compatible = "ti,tps65217"; + reg = <0x24>; + interrupt-controller; + #interrupt-cells = <1>; + ti,pmic-shutdown-controller; + + backlight { + isel = <1>; /* 1 - ISET1, 2 ISET2 */ + fdim = <100>; /* TPS65217_BL_FDIM_100HZ */ + default-brightness = <50>; + }; + + charger { + compatible = "ti,tps65217-charger"; + interrupts = <0>, <1>; + interrupt-names = "USB", "AC"; + }; + + pwrbutton { + compatible = "ti,tps65217-pwrbutton"; + interrupts = <2>; + }; + + regulators { + dcdc1 { + regulator-name = "vdds_dpr"; + regulator-always-on; + }; + + dcdc2 { + regulator-name = "vdd_mpu"; + regulator-min-microvolt = <925000>; + regulator-max-microvolt = <1351500>; + regulator-boot-on; + regulator-always-on; + }; + + dcdc3 { + regulator-name = "vdd_core"; + regulator-min-microvolt = <925000>; + regulator-max-microvolt = <1150000>; + regulator-boot-on; + regulator-always-on; + }; + + ldo1 { + regulator-name = "vio,vrtc,vdds"; + regulator-always-on; + }; + + ldo2 { + regulator-name = "vdd_3v3aux"; + regulator-always-on; + }; + + ldo3 { + regulator-name = "vdd_1v8"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + }; + + ldo4 { + regulator-name = "vdd_3v3a"; + regulator-always-on; + }; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/leds/backlight/tps65217-backlight.txt b/Documentation/devicetree/bindings/leds/backlight/tps65217-backlight.txt deleted file mode 100644 index 5fb9279ac287..000000000000 --- a/Documentation/devicetree/bindings/leds/backlight/tps65217-backlight.txt +++ /dev/null @@ -1,27 +0,0 @@ -TPS65217 family of regulators - -The TPS65217 chip contains a boost converter and current sinks which can be -used to drive LEDs for use as backlights. - -Required properties: -- compatible: "ti,tps65217" -- reg: I2C slave address -- backlight: node for specifying WLED1 and WLED2 lines in TPS65217 -- isel: selection bit, valid values: 1 for ISEL1 (low-level) and 2 for ISEL2 (high-level) -- fdim: PWM dimming frequency, valid values: 100, 200, 500, 1000 -- default-brightness: valid values: 0-100 - -Each regulator is defined using the standard binding for regulators. - -Example: - - tps: tps@24 { - reg = <0x24>; - compatible = "ti,tps65217"; - backlight { - isel = <1>; /* 1 - ISET1, 2 ISET2 */ - fdim = <100>; /* TPS65217_BL_FDIM_100HZ */ - default-brightness = <50>; - }; - }; - diff --git a/Documentation/devicetree/bindings/regulator/tps65217.txt b/Documentation/devicetree/bindings/regulator/tps65217.txt deleted file mode 100644 index 4f05d208c95c..000000000000 --- a/Documentation/devicetree/bindings/regulator/tps65217.txt +++ /dev/null @@ -1,78 +0,0 @@ -TPS65217 family of regulators - -Required properties: -- compatible: "ti,tps65217" -- reg: I2C slave address -- regulators: list of regulators provided by this controller, must be named - after their hardware counterparts: dcdc[1-3] and ldo[1-4] -- regulators: This is the list of child nodes that specify the regulator - initialization data for defined regulators. Not all regulators for the given - device need to be present. The definition for each of these nodes is defined - using the standard binding for regulators found at - Documentation/devicetree/bindings/regulator/regulator.txt. - -Optional properties: -- ti,pmic-shutdown-controller: Telling the PMIC to shutdown on PWR_EN toggle. - - The valid names for regulators are: - tps65217: dcdc1, dcdc2, dcdc3, ldo1, ldo2, ldo3 and ldo4 - -Each regulator is defined using the standard binding for regulators. - -Example: - - tps: tps@24 { - compatible = "ti,tps65217"; - ti,pmic-shutdown-controller; - - regulators { - dcdc1_reg: dcdc1 { - regulator-min-microvolt = <900000>; - regulator-max-microvolt = <1800000>; - regulator-boot-on; - regulator-always-on; - }; - - dcdc2_reg: dcdc2 { - regulator-min-microvolt = <900000>; - regulator-max-microvolt = <3300000>; - regulator-boot-on; - regulator-always-on; - }; - - dcdc3_reg: dcc3 { - regulator-min-microvolt = <900000>; - regulator-max-microvolt = <1500000>; - regulator-boot-on; - regulator-always-on; - }; - - ldo1_reg: ldo1 { - regulator-min-microvolt = <1000000>; - regulator-max-microvolt = <3300000>; - regulator-boot-on; - regulator-always-on; - }; - - ldo2_reg: ldo2 { - regulator-min-microvolt = <900000>; - regulator-max-microvolt = <3300000>; - regulator-boot-on; - regulator-always-on; - }; - - ldo3_reg: ldo3 { - regulator-min-microvolt = <1800000>; - regulator-max-microvolt = <3300000>; - regulator-boot-on; - regulator-always-on; - }; - - ldo4_reg: ldo4 { - regulator-min-microvolt = <1800000>; - regulator-max-microvolt = <3300000>; - regulator-boot-on; - regulator-always-on; - }; - }; - }; From 942901eeda934f1bebf2605a781155e9d6bc7f6e Mon Sep 17 00:00:00 2001 From: Steve Dunnagan Date: Fri, 24 Jul 2026 14:04:12 -0400 Subject: [PATCH 0471/1328] leds: gpio: Clear error pointers for skipped LEDs gpio_led_get_gpiod() returns an error pointer when a platform-data LED's GPIO is unavailable. gpio_led_probe() skips registration in that case, but leaves the error pointer in led_dat->gpiod. The skipped entry remains included in priv->num_leds. During shutdown, gpio_led_shutdown() walks those entries and passes the error pointer to gpio_led_set(), producing: gpiod_set_value: invalid GPIO (errorpointer: -ENOENT) Clear led_dat->gpiod before skipping the LED so skipped entries do not retain error-valued descriptors. Fixes: 45d4c6de4e49 ("leds: gpio: Try to lookup gpiod from device") Suggested-by: Lee Jones Assisted-by: ChatGPT:GPT-5.5-Thinking Signed-off-by: Steve Dunnagan Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260724180412.43150-1-sdunnaga@redhat.com Signed-off-by: Lee Jones --- drivers/leds/leds-gpio.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/leds/leds-gpio.c b/drivers/leds/leds-gpio.c index 9cbcf7e40a15..df7be79dbf24 100644 --- a/drivers/leds/leds-gpio.c +++ b/drivers/leds/leds-gpio.c @@ -296,6 +296,7 @@ static int gpio_led_probe(struct platform_device *pdev) if (IS_ERR(led_dat->gpiod)) { dev_info(dev, "Skipping unavailable LED gpio %s\n", template->name); + led_dat->gpiod = NULL; continue; } From b765b508bdd5501386ceae6f96a70ddd45675f9e Mon Sep 17 00:00:00 2001 From: Loic Poulain Date: Mon, 27 Jul 2026 16:54:33 +0200 Subject: [PATCH 0472/1328] dt-bindings: leds: nxp,pca963x: Fix reg maximum for pca9635 The pca9635 supports 16 LED channels, unlike the pca9634 which only supports 8. The allOf conditional grouped both chips under a single else branch capping reg at a maximum of 7. Give pca9634 its own if/then block and set maximum: 15 unconditionally in the top-level led@ node, making it the default for pca9635 and any future compatible. Also tighten the node name regex from [0-9a-f]+ to [0-9a-f] to match the single-digit hardware limit. Acked-by: Conor Dooley Signed-off-by: Loic Poulain Link: https://patch.msgid.link/20260727-monza-leds-v8-1-6e7e93d44dba@oss.qualcomm.com Signed-off-by: Lee Jones --- .../devicetree/bindings/leds/nxp,pca963x.yaml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/leds/nxp,pca963x.yaml b/Documentation/devicetree/bindings/leds/nxp,pca963x.yaml index 938d0e48fe51..4fda602912f7 100644 --- a/Documentation/devicetree/bindings/leds/nxp,pca963x.yaml +++ b/Documentation/devicetree/bindings/leds/nxp,pca963x.yaml @@ -62,14 +62,14 @@ properties: open-drain, newer chips to totem pole). patternProperties: - "^led@[0-9a-f]+$": + "^led@[0-9a-f]$": type: object $ref: common.yaml# unevaluatedProperties: false properties: reg: - minimum: 0 + maximum: 15 required: - reg @@ -88,7 +88,13 @@ allOf: properties: reg: maximum: 3 - else: + - if: + properties: + compatible: + contains: + enum: + - nxp,pca9634 + then: patternProperties: "^led@[0-9a-f]+$": properties: From 160c78e69c3b2a8be44de28cb27f3f04d85a7235 Mon Sep 17 00:00:00 2001 From: Loic Poulain Date: Mon, 27 Jul 2026 16:54:34 +0200 Subject: [PATCH 0473/1328] dt-bindings: leds: nxp,pca963x: Add multicolor LED support Add support for grouping individual PCA963x channels into a multicolor LED by introducing a multi-led@N node pattern. This follows the convention established by other multicolor LED drivers such as kinetic,ktd202x. This is necessary to support and model hardware setups where multiple PWM channels drive a single physical RGB LED. Acked-by: Conor Dooley Signed-off-by: Loic Poulain Link: https://patch.msgid.link/20260727-monza-leds-v8-2-6e7e93d44dba@oss.qualcomm.com Signed-off-by: Lee Jones --- .../devicetree/bindings/leds/nxp,pca963x.yaml | 96 ++++++++++++++++++- 1 file changed, 94 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/leds/nxp,pca963x.yaml b/Documentation/devicetree/bindings/leds/nxp,pca963x.yaml index 4fda602912f7..a16ca11f630b 100644 --- a/Documentation/devicetree/bindings/leds/nxp,pca963x.yaml +++ b/Documentation/devicetree/bindings/leds/nxp,pca963x.yaml @@ -74,6 +74,40 @@ patternProperties: required: - reg + "^multi-led@[0-9a-f]$": + type: object + $ref: leds-class-multicolor.yaml# + unevaluatedProperties: false + + properties: + reg: + maximum: 15 + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + + patternProperties: + "^led@[0-9a-f]$": + type: object + $ref: common.yaml# + unevaluatedProperties: false + + properties: + reg: + maximum: 15 + + required: + - reg + - color + + required: + - reg + - "#address-cells" + - "#size-cells" + allOf: - if: properties: @@ -84,10 +118,16 @@ allOf: - nxp,pca9633 then: patternProperties: - "^led@[0-9a-f]+$": + "^.*led@[0-9a-f]$": properties: reg: maximum: 3 + "^multi-led@[0-9a-f]$": + patternProperties: + "^led@[0-9a-f]$": + properties: + reg: + maximum: 3 - if: properties: compatible: @@ -96,10 +136,16 @@ allOf: - nxp,pca9634 then: patternProperties: - "^led@[0-9a-f]+$": + "^.*led@[0-9a-f]$": properties: reg: maximum: 7 + "^multi-led@[0-9a-f]$": + patternProperties: + "^led@[0-9a-f]$": + properties: + reg: + maximum: 7 additionalProperties: false @@ -143,4 +189,50 @@ examples: }; }; + - | + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + led-controller@62 { + compatible = "nxp,pca9633"; + reg = <0x62>; + #address-cells = <1>; + #size-cells = <0>; + + /* Three channels controlling one RGB LED */ + multi-led@0 { + reg = <0>; + color = ; + function = LED_FUNCTION_STATUS; + #address-cells = <1>; + #size-cells = <0>; + + led@0 { + reg = <0>; + color = ; + }; + + led@1 { + reg = <1>; + color = ; + }; + + led@2 { + reg = <2>; + color = ; + }; + }; + + /* Remaining channel used as a plain white LED */ + led@3 { + reg = <3>; + color = ; + function = LED_FUNCTION_STATUS; + }; + }; + }; + ... From cd5d8dcd4ad756749667f3e73ac87927d0f080e9 Mon Sep 17 00:00:00 2001 From: Loic Poulain Date: Mon, 27 Jul 2026 16:54:35 +0200 Subject: [PATCH 0474/1328] leds: pca963x: Add multicolor LED class support Allow grouping of individual PCA963x PWM channels into a single multicolor LED device by adding support for the LED multicolor class. A child node with sub-children is treated as a multicolor group, others are treated as single leds, keeping full backwards compatibility. Signed-off-by: Loic Poulain Link: https://patch.msgid.link/20260727-monza-leds-v8-3-6e7e93d44dba@oss.qualcomm.com Signed-off-by: Lee Jones --- drivers/leds/Kconfig | 1 + drivers/leds/leds-pca963x.c | 193 ++++++++++++++++++++++++++++-------- 2 files changed, 150 insertions(+), 44 deletions(-) diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 31b1e3ff094c..d0d3182236be 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -596,6 +596,7 @@ config LEDS_PCA963X tristate "LED support for PCA963x I2C chip" depends on LEDS_CLASS depends on I2C + select LEDS_CLASS_MULTICOLOR help This option enables support for LEDs connected to the PCA963x LED driver chip accessed via the I2C bus. Supported diff --git a/drivers/leds/leds-pca963x.c b/drivers/leds/leds-pca963x.c index e3a81c60ee27..605cf7497e4c 100644 --- a/drivers/leds/leds-pca963x.c +++ b/drivers/leds/leds-pca963x.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -101,8 +102,11 @@ struct pca963x; struct pca963x_led { struct pca963x *chip; struct led_classdev led_cdev; + struct led_classdev_mc mc_cdev; + struct mc_subled subleds[4]; int led_num; /* 0 .. 15 potentially */ bool blinking; + bool is_mc; u8 gdc; u8 gfrq; }; @@ -115,7 +119,7 @@ struct pca963x { struct pca963x_led leds[]; }; -static int pca963x_brightness(struct pca963x_led *led, +static int pca963x_brightness(struct pca963x_led *led, unsigned int led_num, enum led_brightness brightness) { struct i2c_client *client = led->chip->client; @@ -124,8 +128,8 @@ static int pca963x_brightness(struct pca963x_led *led, int shift; int ret; - ledout_addr = chipdef->ledout_base + (led->led_num / 4); - shift = 2 * (led->led_num % 4); + ledout_addr = chipdef->ledout_base + (led_num / 4); + shift = 2 * (led_num % 4); mask = 0x3 << shift; ledout = i2c_smbus_read_byte_data(client, ledout_addr); @@ -135,7 +139,7 @@ static int pca963x_brightness(struct pca963x_led *led, val = (ledout & ~mask) | (PCA963X_LED_GRP_PWM << shift); ret = i2c_smbus_write_byte_data(client, PCA963X_PWM_BASE + - led->led_num, + led_num, LED_FULL); } else { val = (ledout & ~mask) | (PCA963X_LED_ON << shift); @@ -150,7 +154,7 @@ static int pca963x_brightness(struct pca963x_led *led, default: ret = i2c_smbus_write_byte_data(client, PCA963X_PWM_BASE + - led->led_num, + led_num, brightness); if (ret < 0) return ret; @@ -199,20 +203,24 @@ static void pca963x_blink(struct pca963x_led *led) led->blinking = true; } -static int pca963x_power_state(struct pca963x_led *led) +static void pca963x_track_power_state(struct pca963x_led *led, unsigned int led_num, + enum led_brightness brightness) +{ + unsigned long *leds_on = &led->chip->leds_on; + + if (brightness) + set_bit(led_num, leds_on); + else + clear_bit(led_num, leds_on); +} + +static int pca963x_sync_power_state(struct pca963x_led *led, unsigned long cached_leds) { struct i2c_client *client = led->chip->client; - unsigned long *leds_on = &led->chip->leds_on; - unsigned long cached_leds = *leds_on; - if (led->led_cdev.brightness) - set_bit(led->led_num, leds_on); - else - clear_bit(led->led_num, leds_on); - - if (!(*leds_on) != !cached_leds) + if (!led->chip->leds_on != !cached_leds) return i2c_smbus_write_byte_data(client, PCA963X_MODE1, - *leds_on ? 0 : BIT(4)); + led->chip->leds_on ? 0 : BIT(4)); return 0; } @@ -221,22 +229,60 @@ static int pca963x_led_set(struct led_classdev *led_cdev, enum led_brightness value) { struct pca963x_led *led; + unsigned long cached_leds; int ret; led = container_of(led_cdev, struct pca963x_led, led_cdev); mutex_lock(&led->chip->mutex); - ret = pca963x_brightness(led, value); - if (ret < 0) + cached_leds = led->chip->leds_on; + ret = pca963x_brightness(led, led->led_num, value); + if (ret) goto unlock; - ret = pca963x_power_state(led); + + pca963x_track_power_state(led, led->led_num, value); + ret = pca963x_sync_power_state(led, cached_leds); unlock: mutex_unlock(&led->chip->mutex); return ret; } +static int pca963x_led_mc_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev); + struct pca963x_led *led = container_of(mc_cdev, struct pca963x_led, mc_cdev); + unsigned long cached_leds; + int ret = 0, sync_ret; + + led_mc_calc_color_components(mc_cdev, value); + + guard(mutex)(&led->chip->mutex); + + cached_leds = led->chip->leds_on; + for (unsigned int i = 0; i < mc_cdev->num_colors; i++) { + unsigned int channel = mc_cdev->subled_info[i].channel; + + ret = pca963x_brightness(led, channel, + mc_cdev->subled_info[i].brightness); + if (ret) + break; + + pca963x_track_power_state(led, channel, + mc_cdev->subled_info[i].brightness); + } + + /* + * Some channels may already have been updated before the error, so + * still sync the global on/off state to reflect what actually changed. + */ + sync_ret = pca963x_sync_power_state(led, cached_leds); + + return ret ? : sync_ret; +} + static unsigned int pca963x_period_scale(struct pca963x_led *led, unsigned int val) { @@ -300,6 +346,81 @@ static int pca963x_blink_set(struct led_classdev *led_cdev, return 0; } +static int pca963x_parse_mc_subleds(struct device *dev, struct pca963x_led *led, + struct fwnode_handle *fwnode, + const struct pca963x_chipdef *chipdef) +{ + unsigned int num_colors = 0; + int ret; + + fwnode_for_each_child_node_scoped(fwnode, sub) { + u32 color, subreg; + + if (num_colors >= ARRAY_SIZE(led->subleds)) + return dev_err_probe(dev, -EINVAL, "Too many LEDs for node %pfw\n", fwnode); + + ret = fwnode_property_read_u32(sub, "reg", &subreg); + if (ret) + return dev_err_probe(dev, ret, "Missing 'reg' for sub-LED %pfw\n", sub); + if (subreg >= chipdef->n_leds) + return dev_err_probe(dev, -EINVAL, "Invalid 'reg' for sub-LED %pfw\n", sub); + + ret = fwnode_property_read_u32(sub, "color", &color); + if (ret) + return dev_err_probe(dev, ret, "Missing 'color' for sub-LED %pfw\n", sub); + + led->subleds[num_colors].channel = subreg; + led->subleds[num_colors].color_index = color; + led->subleds[num_colors].intensity = LED_FULL; + num_colors++; + } + + led->mc_cdev.subled_info = led->subleds; + led->mc_cdev.num_colors = num_colors; + led->mc_cdev.led_cdev.max_brightness = LED_FULL; + led->mc_cdev.led_cdev.brightness_set_blocking = pca963x_led_mc_set; + + return 0; +} + +static int pca963x_register_led(struct device *dev, struct pca963x_led *led, + u32 reg, struct fwnode_handle *fwnode, + const struct pca963x_chipdef *chipdef, + bool hw_blink) +{ + struct i2c_client *client = led->chip->client; + struct led_init_data init_data = {}; + char label[32]; + int ret; + + led->led_num = reg; + + /* A node with sub-children groups several channels into a multicolor LED. */ + led->is_mc = fwnode_get_child_node_count(fwnode) > 0; + + if (led->is_mc) { + ret = pca963x_parse_mc_subleds(dev, led, fwnode, chipdef); + if (ret) + return ret; + } else { + led->led_cdev.brightness_set_blocking = pca963x_led_set; + if (hw_blink) + led->led_cdev.blink_set = pca963x_blink_set; + } + + init_data.fwnode = fwnode; + /* Keep the legacy device name to preserve existing sysfs LED names. */ + init_data.devicename = "pca963x"; + snprintf(label, sizeof(label), "%d:%.2x:%u", client->adapter->nr, client->addr, reg); + init_data.default_label = label; + + if (led->is_mc) + return devm_led_classdev_multicolor_register_ext(dev, &led->mc_cdev, + &init_data); + + return devm_led_classdev_register_ext(dev, &led->led_cdev, &init_data); +} + static int pca963x_register_leds(struct i2c_client *client, struct pca963x *chip) { @@ -338,37 +459,21 @@ static int pca963x_register_leds(struct i2c_client *client, return ret; device_for_each_child_node_scoped(dev, child) { - struct led_init_data init_data = {}; - char default_label[32]; - ret = fwnode_property_read_u32(child, "reg", ®); - if (ret || reg >= chipdef->n_leds) { - dev_err(dev, "Invalid 'reg' property for node %pfw\n", - child); - return -EINVAL; - } + if (ret) + return dev_err_probe(dev, ret, + "Missing 'reg' property for node %pfw\n", child); + if (reg >= chipdef->n_leds) + return dev_err_probe(dev, -EINVAL, + "Invalid 'reg' property for node %pfw\n", child); - led->led_num = reg; led->chip = chip; - led->led_cdev.brightness_set_blocking = pca963x_led_set; - if (hw_blink) - led->led_cdev.blink_set = pca963x_blink_set; led->blinking = false; - init_data.fwnode = child; - /* for backwards compatibility */ - init_data.devicename = "pca963x"; - snprintf(default_label, sizeof(default_label), "%d:%.2x:%u", - client->adapter->nr, client->addr, reg); - init_data.default_label = default_label; - - ret = devm_led_classdev_register_ext(dev, &led->led_cdev, - &init_data); - if (ret) { - dev_err(dev, "Failed to register LED for node %pfw\n", - child); - return ret; - } + ret = pca963x_register_led(dev, led, reg, child, chipdef, hw_blink); + if (ret) + return dev_err_probe(dev, ret, "Failed to register LED for node %pfw\n", + child); ++led; } From e36f8825616b73a09cd2308f2644aa12d7fb6db0 Mon Sep 17 00:00:00 2001 From: Ninad Naik Date: Wed, 29 Jul 2026 00:09:45 +0530 Subject: [PATCH 0475/1328] dt-bindings: leds: lacie,ns2-leds: Convert to DT schema Convert lacie,ns2-leds to DT schema. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Ninad Naik Closes: https://lore.kernel.org/r/202607231542.6ovJrInT-lkp@intel.com/ Link: https://patch.msgid.link/20260728183945.12008-1-ninadnaik07@gmail.com Signed-off-by: Lee Jones --- .../bindings/leds/lacie,ns2-leds.yaml | 85 +++++++++++++++++++ .../devicetree/bindings/leds/leds-ns2.txt | 35 -------- 2 files changed, 85 insertions(+), 35 deletions(-) create mode 100644 Documentation/devicetree/bindings/leds/lacie,ns2-leds.yaml delete mode 100644 Documentation/devicetree/bindings/leds/leds-ns2.txt diff --git a/Documentation/devicetree/bindings/leds/lacie,ns2-leds.yaml b/Documentation/devicetree/bindings/leds/lacie,ns2-leds.yaml new file mode 100644 index 000000000000..2138c75c0413 --- /dev/null +++ b/Documentation/devicetree/bindings/leds/lacie,ns2-leds.yaml @@ -0,0 +1,85 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/leds/lacie,ns2-leds.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Dual-GPIO LEDs found on Network Space v2 (and parents) + +maintainers: + - Simon Guinot + +description: + The Network Space v2 dual-GPIO LED is wired to a CPLD. Three different LED + modes are available, off, on and SATA activity blinking. The LED modes are + controlled through two GPIOs (command and slow), each combination of values + for the command/slow GPIOs corresponds to a LED mode. + +properties: + compatible: + const: lacie,ns2-leds + +additionalProperties: + type: object + $ref: common.yaml# + unevaluatedProperties: false + + description: Each child node represents a single LED + + properties: + cmd-gpio: + maxItems: 1 + description: GPIO connected to the command LED output + + slow-gpio: + maxItems: 1 + description: GPIO connected to the slow LED output + + num-modes: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + Number of entries in modes-map. + + modes-map: + $ref: /schemas/types.yaml#/definitions/uint32-matrix + description: + A mapping between LED modes (off, on or SATA activity blinking) and + the corresponding cmd-gpio/slow-gpio values. All the GPIO values + combinations should be given in order to avoid having an unknown + mode at driver probe time. + + items: + items: + - description: LED mode + enum: [0, 1, 2] + - description: Command GPIO level + enum: [0, 1] + - description: Slow GPIO level + enum: [0, 1] + + required: + - cmd-gpio + - slow-gpio + - modes-map + +required: + - compatible + +examples: + - | + #include + + led-controller { + compatible = "lacie,ns2-leds"; + + led-0 { + label = "ns2:blue:sata"; + slow-gpio = <&gpio0 29 0>; + cmd-gpio = <&gpio0 30 0>; + modes-map = ; + }; + }; +... diff --git a/Documentation/devicetree/bindings/leds/leds-ns2.txt b/Documentation/devicetree/bindings/leds/leds-ns2.txt deleted file mode 100644 index 9f81258a5b6e..000000000000 --- a/Documentation/devicetree/bindings/leds/leds-ns2.txt +++ /dev/null @@ -1,35 +0,0 @@ -Binding for dual-GPIO LED found on Network Space v2 (and parents). - -Required properties: -- compatible: "lacie,ns2-leds". - -Each LED is represented as a sub-node of the ns2-leds device. - -Required sub-node properties: -- cmd-gpio: Command LED GPIO. See OF device-tree GPIO specification. -- slow-gpio: Slow LED GPIO. See OF device-tree GPIO specification. -- modes-map: A mapping between LED modes (off, on or SATA activity blinking) and - the corresponding cmd-gpio/slow-gpio values. All the GPIO values combinations - should be given in order to avoid having an unknown mode at driver probe time. - -Optional sub-node properties: -- label: Name for this LED. If omitted, the label is taken from the node name. -- linux,default-trigger: Trigger assigned to the LED. - -Example: - -#include - -ns2-leds { - compatible = "lacie,ns2-leds"; - - blue-sata { - label = "ns2:blue:sata"; - slow-gpio = <&gpio0 29 0>; - cmd-gpio = <&gpio0 30 0>; - modes-map = ; - }; -}; From 4496593f4d69b119e1be861cb9e42347eeb24f05 Mon Sep 17 00:00:00 2001 From: Kathiravan Thirumoorthy Date: Mon, 27 Jul 2026 14:14:12 +0530 Subject: [PATCH 0476/1328] dt-bindings: mfd: qcom,tcsr: Document the IPQ9650 TCSR block Document the TCSR block found on the Qualcomm's IPQ9650 SoC. Signed-off-by: Kathiravan Thirumoorthy Acked-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260727-ipq9650_tcsr_binding-v1-1-931ac0e056d0@oss.qualcomm.com Signed-off-by: Lee Jones --- Documentation/devicetree/bindings/mfd/qcom,tcsr.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/mfd/qcom,tcsr.yaml b/Documentation/devicetree/bindings/mfd/qcom,tcsr.yaml index 1297f2ba914f..c3889f974ed3 100644 --- a/Documentation/devicetree/bindings/mfd/qcom,tcsr.yaml +++ b/Documentation/devicetree/bindings/mfd/qcom,tcsr.yaml @@ -53,6 +53,7 @@ properties: - qcom,tcsr-ipq8064 - qcom,tcsr-ipq8074 - qcom,tcsr-ipq9574 + - qcom,tcsr-ipq9650 - qcom,tcsr-mdm9615 - qcom,tcsr-msm8226 - qcom,tcsr-msm8660 From 485d3f5760d8feb8a5d842218c9cb980a78cc34d Mon Sep 17 00:00:00 2001 From: Oleg Nesterov Date: Sun, 5 Jul 2026 18:52:45 +0200 Subject: [PATCH 0477/1328] apparmor: use SEND_SIG_NOINFO instead of NULL in aa_audit() SEND_SIG_NOINFO is defined as ((struct kernel_siginfo *) 0), so passing NULL works, but: - this works "by accident" and looks as if the caller doesn't understand the signal sending API. - more importantly, this hides the usage of SEND_SIG_NOINFO from grep, and this is really bad. Signed-off-by: Oleg Nesterov Acked-by: Georgia Garcia Signed-off-by: John Johansen --- security/apparmor/audit.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c index 4a60b6fda75f..15e42e96b163 100644 --- a/security/apparmor/audit.c +++ b/security/apparmor/audit.c @@ -192,7 +192,7 @@ int aa_audit(int type, struct aa_profile *profile, aa_audit_msg(type, ad, cb); if (ad->type == AUDIT_APPARMOR_KILL) - (void)send_sig_info(profile->signal, NULL, + send_sig_info(profile->signal, SEND_SIG_NOINFO, ad->common.type == LSM_AUDIT_DATA_TASK && ad->common.u.tsk ? ad->common.u.tsk : current); From 3f4ae5fab613dca01d6a2a8210dd832e009fcf47 Mon Sep 17 00:00:00 2001 From: Jann Horn Date: Thu, 6 Aug 2026 17:55:02 +0200 Subject: [PATCH 0478/1328] apparmor: fix cred UAF caused by begin_current_label_crit_section() AppArmor's begin_current_label_crit_section() is a scary function called from lots of LSM hooks (in particular VFS/socket-related ones) that checks if the label referenced by the current creds is marked FLAG_STALE, and if so, attempts to use aa_replace_current_label() to replace the creds with an updated version that uses a new label. The first problem with this is that it would directly lead to UAF of `struct cred` if anything in the kernel takes a pointer to the current creds and accesses these past a security hook invocation that replaces creds, like so: ``` const struct cred *cred = current_cred(); alloc_file_pseudo(...); uid_t uid = cred->euid; ``` I don't know if anything in the kernel actually does this, but I think it is very surprising that this pattern could lead to UAF. The second problem is that things go wrong when aa_replace_current_label() runs with overridden credentials. aa_replace_current_label() bails out if `current_cred() != current_real_cred()` (mirroring the check in proc_pid_attr_write()), but this check can't actually reliably detect overridden credentials because the overridden creds can be the same as the objective creds. So in approximately the following scenario, things go wrong: 1. task begins with (as both objective and subjective creds), with refcount=2 2. task grabs an extra reference on for overriding 3. task calls override_creds(), which returns a pointer to the old subjective creds () 4. task enters AppArmor LSM hook 5. AppArmor checks that objective/subjective creds are equal 6. AppArmor replaces both cred pointers with and drops 2 refs on 7. task leaves AppArmor LSM hook 8. task calls revert_creds() 9. now task->cred is while task->real_cred is , but the task_struct logically holds two references to 10. another task drops the extra reference on that was used for overriding, refcount drops to 0 11. now task->real_cred points to freed creds At this point, any access to current_cred() will be UAF. I have a test case where I run aa-disable on a profile while a process using that profile is blocked on splice() from a FUSE passthrough file into a full pipe; after the profile update, the pipe becomes empty, splice() resumes, the credentials go out of sync, and a subsequent getuid() syscall results in a KASAN UAF splat. To fix this, instead of directly replacing creds, do it via task_work that will run at the end of the current syscall. (The point in time at which the cred replacement happens should have no correctness impact; it is just a performance optimization to avoid unnecessarily touching the refcount of the new label.) Note that AppArmor still performs direct cred replacements in the sb_pivotroot LSM hook after this change, and that direct cred replacements can still happen in VFS ->write() callbacks via proc_pid_attr_write(). There are two options for what to do with aa_dup_task_ctx(): Either explicitly reset new->label_replacement_pending after the entire aa_task_ctx has been copied, or switch to manually copying members over. I am switching to manually copying members over because that should make bugs more obvious. Cc: stable@vger.kernel.org Fixes: c75afcd153f6 ("AppArmor: contexts used in attaching policy to system objects") Signed-off-by: Jann Horn Signed-off-by: John Johansen --- security/apparmor/include/cred.h | 6 +----- security/apparmor/include/task.h | 15 +++++++++++---- security/apparmor/task.c | 27 +++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/security/apparmor/include/cred.h b/security/apparmor/include/cred.h index 2b6098149b15..0e8b67159f56 100644 --- a/security/apparmor/include/cred.h +++ b/security/apparmor/include/cred.h @@ -222,13 +222,9 @@ static inline struct aa_label *begin_current_label_crit_section(void) { struct aa_label *label = aa_current_raw_label(); - might_sleep(); - if (label_is_stale(label)) { label = aa_get_newest_label(label); - if (aa_replace_current_label(label) == 0) - /* task cred will keep the reference */ - aa_put_label(label); + aa_schedule_stale_label_replacement(); } return label; diff --git a/security/apparmor/include/task.h b/security/apparmor/include/task.h index 017d8b06b8f2..a8030ed78ff2 100644 --- a/security/apparmor/include/task.h +++ b/security/apparmor/include/task.h @@ -26,15 +26,22 @@ static inline struct aa_task_ctx *task_ctx(struct task_struct *task) * @onexec: profile to transition to on next exec (MAY BE NULL) * @previous: profile the task may return to (MAY BE NULL) * @token: magic value the task must know for returning to @previous_profile + * @label_replacement_tw: for aa_schedule_stale_label_replacement() + * @label_replacement_pending: is @label_replacement_tw pending? + * + * When changing this, check if aa_dup_task_ctx() needs to be updated. */ struct aa_task_ctx { struct aa_label *nnp; struct aa_label *onexec; struct aa_label *previous; u64 token; + struct callback_head label_replacement_tw; + bool label_replacement_pending; }; int aa_replace_current_label(struct aa_label *label); +void aa_schedule_stale_label_replacement(void); void aa_set_current_onexec(struct aa_label *label, bool stack); int aa_set_current_hat(struct aa_label *label, u64 token); int aa_restore_previous_label(u64 cookie); @@ -61,10 +68,10 @@ static inline void aa_free_task_ctx(struct aa_task_ctx *ctx) static inline void aa_dup_task_ctx(struct aa_task_ctx *new, const struct aa_task_ctx *old) { - *new = *old; - aa_get_label(new->nnp); - aa_get_label(new->previous); - aa_get_label(new->onexec); + new->nnp = aa_get_label(old->nnp); + new->onexec = aa_get_label(old->onexec); + new->previous = aa_get_label(old->previous); + new->token = old->token; } /** diff --git a/security/apparmor/task.c b/security/apparmor/task.c index b9fb3738124e..e16ff4130bc2 100644 --- a/security/apparmor/task.c +++ b/security/apparmor/task.c @@ -14,6 +14,7 @@ #include #include +#include #include "include/path.h" #include "include/audit.h" @@ -89,6 +90,32 @@ int aa_replace_current_label(struct aa_label *label) return 0; } +static void aa_replace_stale_label_tw_func(struct callback_head *tw) +{ + struct aa_task_ctx *ctx = task_ctx(current); + struct aa_label *label; + + ctx->label_replacement_pending = false; + label = aa_current_raw_label(); + if (!label_is_stale(label)) + return; + label = aa_get_newest_label(label); + aa_replace_current_label(label); + aa_put_label(label); +} + +/* replace the current task's stale label on syscall return */ +void aa_schedule_stale_label_replacement(void) +{ + struct aa_task_ctx *ctx = task_ctx(current); + + if (ctx->label_replacement_pending) + return; + init_task_work(&ctx->label_replacement_tw, aa_replace_stale_label_tw_func); + if (task_work_add(current, &ctx->label_replacement_tw, TWA_RESUME) == 0) + ctx->label_replacement_pending = true; +} + /** * aa_set_current_onexec - set the tasks change_profile to happen onexec From 5d901d7d1002a60cf03266d13fe76ce303cc254c Mon Sep 17 00:00:00 2001 From: Satya Priya Kakitapalli Date: Wed, 22 Jul 2026 16:14:41 +0530 Subject: [PATCH 0479/1328] dt-bindings: thermal: Add Qualcomm MBG thermal monitor support Add bindings for the Qualcomm MBG (Master Bandgap) temperature alarm peripheral found on the PM8775 PMIC. Unlike the existing SPMI temp alarm peripheral, the MBG peripheral supports both hot and cold threshold monitoring across two programmable levels (LVL1 and LVL2), with interrupt status reported via a fault status register over SPMI. Signed-off-by: Satya Priya Kakitapalli Co-developed-by: Sachin Gupta Signed-off-by: Sachin Gupta Signed-off-by: Daniel Lezcano Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260722-spmi-mbg-driver-v3-1-ef73064f2789@oss.qualcomm.com --- .../bindings/mfd/qcom,spmi-pmic.yaml | 4 ++ .../bindings/thermal/qcom,pm8775-mbg-tm.yaml | 72 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 Documentation/devicetree/bindings/thermal/qcom,pm8775-mbg-tm.yaml diff --git a/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml b/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml index 644c42b5e2e5..11db2b3f9a6b 100644 --- a/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml +++ b/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml @@ -193,6 +193,10 @@ patternProperties: type: object $ref: /schemas/thermal/qcom,spmi-temp-alarm.yaml# + "^temperature-sensor@[0-9a-f]+$": + type: object + $ref: /schemas/thermal/qcom,pm8775-mbg-tm.yaml# + "^typec@[0-9a-f]+$": type: object $ref: /schemas/usb/qcom,pmic-typec.yaml# diff --git a/Documentation/devicetree/bindings/thermal/qcom,pm8775-mbg-tm.yaml b/Documentation/devicetree/bindings/thermal/qcom,pm8775-mbg-tm.yaml new file mode 100644 index 000000000000..2e084d040625 --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/qcom,pm8775-mbg-tm.yaml @@ -0,0 +1,72 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/thermal/qcom,pm8775-mbg-tm.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm's SPMI PMIC MBG Thermal Monitoring + +maintainers: + - Jishnu Prakash + - Kamal Wadhwa + +description: + Qualcomm's MBG(Master Bandgap) temperature alarm monitors the die + temperature and generates an interrupt if the PMIC die temperature is + over a set of programmable temperature thresholds. It allows monitoring + for both hot and cold, LVL1 and LVL2 thresholds, which makes it different + from the existing temp alarm peripheral. The interrupt comes over SPMI + and the MBG's fault status register gives details to understand whether + it is a hot/cold and LVL1/LVL2 violation. + +properties: + compatible: + const: qcom,pm8775-mbg-tm + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + io-channels: + items: + - description: ADC channel, which reports chip die temperature. + + io-channel-names: + items: + - const: thermal + + '#thermal-sensor-cells': + const: 0 + +required: + - compatible + - reg + - interrupts + - io-channels + - io-channel-names + +allOf: + - $ref: thermal-sensor.yaml# + +unevaluatedProperties: false + +examples: + - | + #include + + pmic { + #address-cells = <1>; + #size-cells = <0>; + + temperature-sensor@d700 { + compatible = "qcom,pm8775-mbg-tm"; + reg = <0xd700>; + interrupts = <0x1 0xd7 0x0 IRQ_TYPE_EDGE_RISING>; + io-channels = <&pm8775_adc 0x3>; + io-channel-names = "thermal"; + #thermal-sensor-cells = <0>; + }; + }; +... From c3dce117333c0e2fbdb35228d4960442d2b1ef54 Mon Sep 17 00:00:00 2001 From: Satya Priya Kakitapalli Date: Wed, 22 Jul 2026 16:14:42 +0530 Subject: [PATCH 0480/1328] thermal/drivers/qcom: Add support for Qualcomm MBG thermal monitoring Add a driver for the Qualcomm MBG (Master Bandgap) thermal monitoring device. It monitors PMIC die temperature in hardware and raises an interrupt when a programmed threshold is crossed. MBG hardware supports two upper-threshold levels. Currently, only the level-1 threshold is supported by the driver. Temperature is read from the associated ADC channel, and when a level-1 upper-threshold violation occurs, the hardware generates an interrupt over SPMI. The driver notifies the thermal framework accordingly. Signed-off-by: Satya Priya Kakitapalli Co-developed-by: Sachin Gupta Signed-off-by: Sachin Gupta Signed-off-by: Daniel Lezcano Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260722-spmi-mbg-driver-v3-2-ef73064f2789@oss.qualcomm.com --- drivers/thermal/qcom/Kconfig | 14 ++ drivers/thermal/qcom/Makefile | 1 + drivers/thermal/qcom/qcom-spmi-mbg-tm.c | 256 ++++++++++++++++++++++++ 3 files changed, 271 insertions(+) create mode 100644 drivers/thermal/qcom/qcom-spmi-mbg-tm.c diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig index a6bb01082ec6..ecfd1d166e53 100644 --- a/drivers/thermal/qcom/Kconfig +++ b/drivers/thermal/qcom/Kconfig @@ -21,6 +21,20 @@ config QCOM_SPMI_ADC_TM5 Thermal client sets threshold temperature for both warm and cool and gets updated when a threshold is reached. +config QCOM_SPMI_MBG_TM + tristate "Qualcomm SPMI PMIC MBG Temperature monitor" + depends on QCOM_SPMI_ADC5_GEN3 + select REGMAP_SPMI + help + This enables Qualcomm PMIC MBG (Master Bandgap) thermal monitor. + + The MBG block monitors PMIC die temperature in hardware and raises an + interrupt when the programmed threshold is crossed. + + Temperature is read from the associated ADC channel, and threshold + interrupts are forwarded to the thermal framework as trip-violation + events. Current support handles a single LVL1 upper (hot) trip. + config QCOM_SPMI_TEMP_ALARM tristate "Qualcomm SPMI PMIC Temperature Alarm" depends on OF && SPMI && IIO diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile index 0fa2512042e7..1bec2746b98d 100644 --- a/drivers/thermal/qcom/Makefile +++ b/drivers/thermal/qcom/Makefile @@ -4,5 +4,6 @@ obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o qcom_tsens-y += tsens.o tsens-v2.o tsens-v1.o tsens-v0_1.o \ tsens-8960.o obj-$(CONFIG_QCOM_SPMI_ADC_TM5) += qcom-spmi-adc-tm5.o +obj-$(CONFIG_QCOM_SPMI_MBG_TM) += qcom-spmi-mbg-tm.o obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM) += qcom-spmi-temp-alarm.o obj-$(CONFIG_QCOM_LMH) += lmh.o diff --git a/drivers/thermal/qcom/qcom-spmi-mbg-tm.c b/drivers/thermal/qcom/qcom-spmi-mbg-tm.c new file mode 100644 index 000000000000..fa2f10002253 --- /dev/null +++ b/drivers/thermal/qcom/qcom-spmi-mbg-tm.c @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define MBG_TEMP_MON2_FAULT_STATUS 0x50 + +#define MON_FAULT_STATUS_MASK GENMASK(7, 4) +#define MON_FAULT_LVL1_UPR 0x5 + +#define MON2_LVL1_UP_THRESH 0x59 + +#define MBG_TEMP_MON2_MISC_CFG 0x5f +#define MON2_UP_THRESH_EN BIT(1) + +#define MBG_TEMP_STEP_MV 8 +#define MBG_TEMP_DEFAULT_TEMP_MV 600 +#define MBG_TEMP_CONSTANT 1000 +#define MBG_MIN_TRIP_TEMP 25000 +#define MBG_MAX_SUPPORTED_TEMP 160000 + +/** + * struct mbg_tm_chip - MBG thermal monitor device data. + * @map: regmap for accessing MBG thermal registers. + * @dev: mbg_tm_chip device. + * @tz_dev: thermal zone device registered with the thermal framework. + * @lock: mbg_tm_chip lock for set trip temperature. + * @base: base register offset for this MBG instance + * @irq: interrupt line used to signal threshold events + * @last_temp: last measured temperature. + * @last_thres_crossed: indicates whether the last interrupt crossed a threshold + * @adc: IIO ADC channel used for temperature sensing + */ +struct mbg_tm_chip { + struct regmap *map; + struct device *dev; + struct thermal_zone_device *tz_dev; + struct mutex lock; + unsigned int base; + int irq; + int last_temp; + bool last_thres_crossed; + struct iio_channel *adc; +}; + +/** + * struct mbg_map_table - temperature to voltage mapping entry + * @min_temp: minimum temperature supported by this mapping entry + * @vtemp0: reference voltage or ADC code corresponding to the temperature + * @tc: temperature coefficient used for conversion calculations + */ +struct mbg_map_table { + int min_temp; + int vtemp0; + int tc; +}; + +static const struct mbg_map_table map_table[] = { + { -60000, 4337, 1967 }, + { -40000, 4731, 1964 }, + { -20000, 5124, 1957 }, + { 0, 5515, 1949 }, + { 20000, 5905, 1940 }, + { 40000, 6293, 1930 }, + { 60000, 6679, 1921 }, + { 80000, 7064, 1910 }, + { 100000, 7446, 1896 }, + { 120000, 7825, 1878 }, + { 140000, 8201, 1859 }, +}; + +static int mbg_tm_get_temp(struct thermal_zone_device *tz, int *temp) +{ + struct mbg_tm_chip *chip = thermal_zone_device_priv(tz); + int ret, milli_celsius; + + scoped_guard(mutex, &chip->lock) { + if (chip->last_thres_crossed) { + dev_dbg(chip->dev, "last_temp: %d\n", chip->last_temp); + chip->last_thres_crossed = false; + *temp = chip->last_temp; + return 0; + } + } + + ret = iio_read_channel_processed(chip->adc, &milli_celsius); + if (ret < 0) { + dev_err(chip->dev, "Failed to read iio channel with %d\n", ret); + return ret; + } + + *temp = milli_celsius; + + return 0; +} + +static int temp_to_vtemp_mv(int temp) +{ + int idx, vtemp, tc = 0, t0 = 0, vtemp0 = 0; + + for (idx = 0; idx < ARRAY_SIZE(map_table); idx++) + if (temp >= map_table[idx].min_temp && + temp < (map_table[idx].min_temp + 20000)) { + tc = map_table[idx].tc; + t0 = map_table[idx].min_temp; + vtemp0 = map_table[idx].vtemp0; + break; + } + + /* + * Formula to calculate vtemp(mV) from a given temp + * vtemp = (temp - minT) * tc + vtemp0 + * tc, t0 and vtemp0 values are mentioned in the map_table array. + */ + vtemp = ((temp - t0) * tc + vtemp0 * 100000) / 1000000; + + /* step size is 8mV */ + return abs(vtemp - MBG_TEMP_DEFAULT_TEMP_MV) / MBG_TEMP_STEP_MV; +} + +static int mbg_tm_set_trip_temp(struct thermal_zone_device *tz, int low_temp, + int temp) +{ + struct mbg_tm_chip *chip = thermal_zone_device_priv(tz); + int ret = 0; + + guard(mutex)(&chip->lock); + + /* The HW has a limitation that the trip set must be above 25C */ + if (temp > MBG_MIN_TRIP_TEMP && temp < MBG_MAX_SUPPORTED_TEMP) { + ret = regmap_write(chip->map, chip->base + MON2_LVL1_UP_THRESH, + temp_to_vtemp_mv(temp)); + if (ret < 0) + return ret; + + ret = regmap_set_bits(chip->map, chip->base + MBG_TEMP_MON2_MISC_CFG, + MON2_UP_THRESH_EN); + if (ret < 0) + return ret; + } else { + dev_err(chip->dev, "Set trip b/w 25C and 160C\n"); + ret = regmap_clear_bits(chip->map, chip->base + MBG_TEMP_MON2_MISC_CFG, + MON2_UP_THRESH_EN); + return -ERANGE; + } + + /* + * Configure the last_temp one degree higher, to ensure the + * violated temp is returned to thermal framework when it reads + * temperature for the first time after the violation happens. + * This is needed to account for the inaccuracy in the conversion + * formula used which leads to the thermal framework setting back + * the same thresholds in case the temperature it reads does not + * show violation. + */ + chip->last_temp = temp + MBG_TEMP_CONSTANT; + + return ret; +} + +static const struct thermal_zone_device_ops mbg_tm_ops = { + .get_temp = mbg_tm_get_temp, + .set_trips = mbg_tm_set_trip_temp, +}; + +static irqreturn_t mbg_tm_isr(int irq, void *data) +{ + struct mbg_tm_chip *chip = data; + int ret, val; + + scoped_guard(mutex, &chip->lock) { + ret = regmap_read(chip->map, chip->base + MBG_TEMP_MON2_FAULT_STATUS, &val); + if (ret < 0) + return IRQ_HANDLED; + if (FIELD_GET(MON_FAULT_STATUS_MASK, val) == MON_FAULT_LVL1_UPR) + chip->last_thres_crossed = true; + } + + if (FIELD_GET(MON_FAULT_STATUS_MASK, val) == MON_FAULT_LVL1_UPR) { + dev_dbg(chip->dev, "Notifying Thermal, fault status=%d\n", val); + thermal_zone_device_update(chip->tz_dev, THERMAL_TRIP_VIOLATED); + } else { + dev_dbg(chip->dev, "Lvl1 upper threshold not violated, ignoring interrupt\n"); + } + + return IRQ_HANDLED; +} + +static int mbg_tm_probe(struct platform_device *pdev) +{ + struct mbg_tm_chip *chip; + struct device_node *node = pdev->dev.of_node; + u32 res; + int ret; + + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->dev = &pdev->dev; + + mutex_init(&chip->lock); + + chip->map = dev_get_regmap(pdev->dev.parent, NULL); + if (!chip->map) + return -ENXIO; + + ret = device_property_read_u32(chip->dev, "reg", &res); + if (ret < 0) + return dev_err_probe(chip->dev, ret, "Couldn't read reg property\n"); + + chip->base = res; + + chip->irq = platform_get_irq(pdev, 0); + if (chip->irq < 0) + return dev_err_probe(chip->dev, chip->irq, "Failed to get irq\n"); + + chip->adc = devm_iio_channel_get(&pdev->dev, "thermal"); + if (IS_ERR(chip->adc)) + return dev_err_probe(chip->dev, PTR_ERR(chip->adc), "Failed to get adc channel\n"); + + chip->tz_dev = devm_thermal_of_zone_register(chip->dev, 0, chip, &mbg_tm_ops); + if (IS_ERR(chip->tz_dev)) + return dev_err_probe(chip->dev, PTR_ERR(chip->tz_dev), + "Failed to register sensor\n"); + + return devm_request_threaded_irq(&pdev->dev, chip->irq, NULL, mbg_tm_isr, IRQF_ONESHOT, + node->name, chip); +} + +static const struct of_device_id mbg_tm_match_table[] = { + { .compatible = "qcom,pm8775-mbg-tm" }, + { } +}; +MODULE_DEVICE_TABLE(of, mbg_tm_match_table); + +static struct platform_driver mbg_tm_driver = { + .driver = { + .name = "qcom-spmi-mbg-tm", + .of_match_table = mbg_tm_match_table, + }, + .probe = mbg_tm_probe, +}; +module_platform_driver(mbg_tm_driver); + +MODULE_DESCRIPTION("PMIC MBG Temperature monitor driver"); +MODULE_LICENSE("GPL"); From 83f98a48f22c36bdbae0f5e07e07b26be44816eb Mon Sep 17 00:00:00 2001 From: Haritha S K Date: Fri, 19 Jun 2026 15:50:59 +0530 Subject: [PATCH 0481/1328] dt-bindings: thermal: qcom-tsens: Document the Maili Temperature Sensor Document the Temperature Sensor (TSENS) on the Qualcomm Maili SoC. Acked-by: Krzysztof Kozlowski Signed-off-by: Haritha S K Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260619-b4-maili-upstream-3-v2-1-e54516c37022@oss.qualcomm.com --- 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 f0efaa8349ee..5a8f7673e730 100644 --- a/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml +++ b/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml @@ -58,6 +58,7 @@ properties: - qcom,glymur-tsens - qcom,hawi-tsens - qcom,kaanapali-tsens + - qcom,maili-tsens - qcom,milos-tsens - qcom,nord-tsens - qcom,msm8953-tsens From 9cbf780428ca1102377a228febd5fc4164ea6512 Mon Sep 17 00:00:00 2001 From: Pei Xiao Date: Thu, 16 Jul 2026 15:04:19 +0800 Subject: [PATCH 0482/1328] thermal/drivers/spacemit/k1: Add shutdown action and reorder registration order Add a devm action to clean hardware interrupts, sampling, and control registers on driver unbind, mirroring what k1_tsensor_init() sets up. Reorder the registration order within probe(): register the thermal zones first, then request the IRQ, and register the shutdown action last. On removal, the hardware interrupt is disabled first, then the IRQ is released, and finally the thermal zones are released. This avoids the IRQ thread accessing an already unregistered thermal zone during devres cleanup. Signed-off-by: Pei Xiao Signed-off-by: Daniel Lezcano Reviewed-by: Troy Mitchell Link: https://patch.msgid.link/1967d2bcc8fede6fbd25fc8eee07f2873fb41472.1784184867.git.xiaopei01@kylinos.cn --- drivers/thermal/spacemit/k1_tsensor.c | 75 ++++++++++++++++++++++----- 1 file changed, 61 insertions(+), 14 deletions(-) diff --git a/drivers/thermal/spacemit/k1_tsensor.c b/drivers/thermal/spacemit/k1_tsensor.c index 79222d233129..ab12e2ec8ae4 100644 --- a/drivers/thermal/spacemit/k1_tsensor.c +++ b/drivers/thermal/spacemit/k1_tsensor.c @@ -199,6 +199,39 @@ static irqreturn_t k1_tsensor_irq_thread(int irq, void *data) return IRQ_HANDLED; } +static void k1_tsensor_shutdown(struct k1_tsensor *ts) +{ + u32 val; + + /* Disable all interrupts */ + writel(0xffffffff, ts->base + K1_TSENSOR_INT_EN_REG); + + /* Disable all sensors */ + val = readl(ts->base + K1_TSENSOR_EN_REG); + val &= ~K1_TSENSOR_EN_ALL; + writel(val, ts->base + K1_TSENSOR_EN_REG); + + /* Clear the sampling configuration set by k1_tsensor_init() */ + val = readl(ts->base + K1_TSENSOR_TIME_REG); + val &= ~(K1_TSENSOR_TIME_FILTER_PERIOD | + K1_TSENSOR_TIME_ADC_CNT_RST | + K1_TSENSOR_TIME_WAIT_REF_CNT); + writel(val, ts->base + K1_TSENSOR_TIME_REG); + + /* Clear the control bits configured by k1_tsensor_init() */ + val = readl(ts->base + K1_TSENSOR_PCTRL_REG); + val &= ~(K1_TSENSOR_PCTRL_RAW_SEL | + K1_TSENSOR_PCTRL_TEMP_MODE | + K1_TSENSOR_PCTRL_HW_AUTO_MODE | + K1_TSENSOR_PCTRL_ENABLE); + writel(val, ts->base + K1_TSENSOR_PCTRL_REG); +} + +static void k1_tsensor_shutdown_action(void *data) +{ + k1_tsensor_shutdown(data); +} + static int k1_tsensor_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -229,34 +262,48 @@ static int k1_tsensor_probe(struct platform_device *pdev) k1_tsensor_init(ts); - irq = platform_get_irq(pdev, 0); - if (irq < 0) - return irq; - - ret = devm_request_threaded_irq(dev, irq, NULL, - k1_tsensor_irq_thread, - IRQF_ONESHOT, "k1_tsensor", ts); - if (ret < 0) - return ret; - for (i = 0; i < MAX_SENSOR_NUMBER; ++i) { ts->ch[i].id = i; ts->ch[i].ts = ts; ts->ch[i].tzd = devm_thermal_of_zone_register(dev, i, ts->ch + i, &k1_tsensor_ops); - if (IS_ERR(ts->ch[i].tzd)) - return PTR_ERR(ts->ch[i].tzd); + if (IS_ERR(ts->ch[i].tzd)) { + ret = PTR_ERR(ts->ch[i].tzd); + goto err_shutdown; + } /* Attach sysfs hwmon attributes for userspace monitoring */ ret = devm_thermal_add_hwmon_sysfs(dev, ts->ch[i].tzd); if (ret) dev_warn(dev, "Failed to add hwmon sysfs attributes\n"); - - k1_tsensor_enable_irq(ts->ch + i); } + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + ret = irq; + goto err_shutdown; + } + + ret = devm_request_threaded_irq(dev, irq, NULL, + k1_tsensor_irq_thread, + IRQF_ONESHOT, "k1_tsensor", ts); + if (ret < 0) + goto err_shutdown; + + ret = devm_add_action_or_reset(dev, k1_tsensor_shutdown_action, ts); + if (ret) + return ret; + + /* Enable interrupts only after all zones and the handler are ready */ + for (i = 0; i < MAX_SENSOR_NUMBER; ++i) + k1_tsensor_enable_irq(ts->ch + i); + platform_set_drvdata(pdev, ts); return 0; + +err_shutdown: + k1_tsensor_shutdown(ts); + return ret; } static const struct of_device_id k1_tsensor_dt_ids[] = { From c4c40502363510d4179088babc327e4c904e3f79 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Tue, 4 Aug 2026 23:37:15 +0200 Subject: [PATCH 0483/1328] thermal/of: Fix trivial enabled typo Fix trivial typo, s@enabled@enable@. No functional change. Signed-off-by: Marek Vasut Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260804213738.287818-1-marek.vasut+renesas@mailbox.org --- drivers/thermal/thermal_of.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thermal/thermal_of.c b/drivers/thermal/thermal_of.c index 100fd8a0c8ce..0217a49b08ae 100644 --- a/drivers/thermal/thermal_of.c +++ b/drivers/thermal/thermal_of.c @@ -444,7 +444,7 @@ static struct thermal_zone_device *thermal_of_zone_register(struct device_node * ret = thermal_zone_device_enable(tz); if (ret) { - pr_err("Failed to enabled thermal zone '%s', id=%d: %d\n", + pr_err("Failed to enable thermal zone '%s', id=%d: %d\n", tz->type, tz->id, ret); thermal_of_zone_unregister(tz); return ERR_PTR(ret); From 7680af9184f1c9b8b22182c34c9d5282eebf3a97 Mon Sep 17 00:00:00 2001 From: Jishnu Prakash Date: Wed, 22 Jul 2026 16:56:37 +0530 Subject: [PATCH 0484/1328] iio: adc: qcom-spmi-adc5-gen3: Remove an unnecessary print devm_request_threaded_irq() internally prints an error message using dev_err_probe() in case of any errors. Remove the error print in the devm_request_threaded_irq() failure path as it is not needed. Suggested-by: Andy Shevchenko Signed-off-by: Jishnu Prakash Signed-off-by: Daniel Lezcano Reviewed-by: Andy Shevchenko Reviewed-by: Jonathan Cameron Reviewed-by: Maxwell Doose Reviewed-by: Joshua Crofts Link: https://patch.msgid.link/20260722-gen3_adc_tm-v4-1-011981f756c8@oss.qualcomm.com --- drivers/iio/adc/qcom-spmi-adc5-gen3.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/iio/adc/qcom-spmi-adc5-gen3.c b/drivers/iio/adc/qcom-spmi-adc5-gen3.c index c56b650fd8c0..2eaebf6b34e8 100644 --- a/drivers/iio/adc/qcom-spmi-adc5-gen3.c +++ b/drivers/iio/adc/qcom-spmi-adc5-gen3.c @@ -821,9 +821,7 @@ static int adc5_gen3_probe(struct platform_device *pdev) adc->dev_data.base[ADC5_GEN3_VADC_SDAM].irq_name, adc); if (ret) - return dev_err_probe(dev, ret, - "Failed to request SDAM%d irq\n", - ADC5_GEN3_VADC_SDAM); + return ret; ret = adc5_get_fw_data(adc); if (ret) From b39f9b4b21df871005a59e2aea814644896d0dd3 Mon Sep 17 00:00:00 2001 From: Jishnu Prakash Date: Wed, 22 Jul 2026 16:56:38 +0530 Subject: [PATCH 0485/1328] iio: adc: qcom-spmi-adc5-gen3: Share SDAM0 IRQ with ADC_TM auxiliary driver The SDAM0 IRQ can be triggered for both EOC (end of conversion) events for immediate ADC reads done in this driver and for threshold violation events, based on ADC_TM thresholds configured from the auxiliary ADC_TM driver on TM channels on the first SDAM. At present, this interrupt is handled only in the ISR in the main ADC driver. When the ISR is triggered for an ADC_TM event, this driver notifies the ADC_TM driver by calling a notifier callback exposed from it for this purpose. To simplify the interrupt handling in both drivers, share the interrupt between the drivers. With this, ADC_TM interrupts on SDAM0 will be handled directly in the ADC_TM driver, so remove the notifier callback and all TM interrupt handling in the main ADC ISR. Signed-off-by: Jishnu Prakash Signed-off-by: Daniel Lezcano Reviewed-by: Jonathan Cameron Link: https://patch.msgid.link/20260722-gen3_adc_tm-v4-2-011981f756c8@oss.qualcomm.com --- drivers/iio/adc/qcom-spmi-adc5-gen3.c | 65 +++++-------------- include/linux/iio/adc/qcom-adc5-gen3-common.h | 2 - 2 files changed, 18 insertions(+), 49 deletions(-) diff --git a/drivers/iio/adc/qcom-spmi-adc5-gen3.c b/drivers/iio/adc/qcom-spmi-adc5-gen3.c index 2eaebf6b34e8..c68c6c5f6aca 100644 --- a/drivers/iio/adc/qcom-spmi-adc5-gen3.c +++ b/drivers/iio/adc/qcom-spmi-adc5-gen3.c @@ -55,9 +55,6 @@ struct adc5_channel_prop { * requests from multiple clients. * @data: software configuration data. * @n_tm_channels: number of ADC channels used for TM measurements. - * @handler: TM callback to be called for threshold violation interrupt - * on first SDAM. - * @tm_aux: pointer to auxiliary TM device. */ struct adc5_chip { struct device *dev; @@ -69,8 +66,6 @@ struct adc5_chip { struct mutex lock; const struct adc5_data *data; unsigned int n_tm_channels; - void (*handler)(struct auxiliary_device *tm_aux); - struct auxiliary_device *tm_aux; }; int adc5_gen3_read(struct adc5_device_data *adc, unsigned int sdam_index, @@ -286,23 +281,21 @@ static irqreturn_t adc5_gen3_isr(int irq, void *dev_id) { struct adc5_chip *adc = dev_id; struct device *dev = adc->dev; - struct auxiliary_device *adev; u8 status, eoc_status, val; - u8 tm_status[2]; int ret; ret = adc5_gen3_read(&adc->dev_data, ADC5_GEN3_VADC_SDAM, ADC5_GEN3_STATUS1, &status, sizeof(status)); if (ret) { dev_err(dev, "adc read status1 failed with %d\n", ret); - return IRQ_HANDLED; + return IRQ_NONE; } ret = adc5_gen3_read(&adc->dev_data, ADC5_GEN3_VADC_SDAM, ADC5_GEN3_EOC_STS, &eoc_status, sizeof(eoc_status)); if (ret) { dev_err(dev, "adc read eoc status failed with %d\n", ret); - return IRQ_HANDLED; + return IRQ_NONE; } if (status & ADC5_GEN3_STATUS1_CONV_FAULT) { @@ -315,30 +308,13 @@ static irqreturn_t adc5_gen3_isr(int irq, void *dev_id) return IRQ_HANDLED; } + dev_dbg(dev, "Interrupt status:%#x, EOC status:%#x\n", status, eoc_status); + /* CHAN0 is the preconfigured channel for immediate conversion */ - if (eoc_status & ADC5_GEN3_EOC_CHAN_0) - complete(&adc->complete); - - ret = adc5_gen3_read(&adc->dev_data, ADC5_GEN3_VADC_SDAM, - ADC5_GEN3_TM_HIGH_STS, tm_status, sizeof(tm_status)); - if (ret) { - dev_err(dev, "adc read TM status failed with %d\n", ret); - return IRQ_HANDLED; - } - - dev_dbg(dev, "Interrupt status:%#x, EOC status:%#x, high:%#x, low:%#x\n", - status, eoc_status, tm_status[0], tm_status[1]); - - if (tm_status[0] || tm_status[1]) { - adev = adc->tm_aux; - if (!adev || !adev->dev.driver) { - dev_err(dev, "adc_tm auxiliary device not initialized\n"); - return IRQ_HANDLED; - } - - adc->handler(adev); - } + if (!(eoc_status & ADC5_GEN3_EOC_CHAN_0)) + return IRQ_NONE; + complete(&adc->complete); return IRQ_HANDLED; } @@ -683,8 +659,6 @@ static int adc5_gen3_add_aux_tm_device(struct adc5_chip *adc) if (ret) return ret; - adc->tm_aux = &aux_device->aux_dev; - return 0; } @@ -740,16 +714,6 @@ int adc5_gen3_therm_code_to_temp(struct device *dev, } EXPORT_SYMBOL_NS_GPL(adc5_gen3_therm_code_to_temp, "QCOM_SPMI_ADC5_GEN3"); -void adc5_gen3_register_tm_event_notifier(struct device *dev, - void (*handler)(struct auxiliary_device *)) -{ - struct iio_dev *indio_dev = dev_get_drvdata(dev->parent); - struct adc5_chip *adc = iio_priv(indio_dev); - - adc->handler = handler; -} -EXPORT_SYMBOL_NS_GPL(adc5_gen3_register_tm_event_notifier, "QCOM_SPMI_ADC5_GEN3"); - static int adc5_gen3_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -816,10 +780,17 @@ static int adc5_gen3_probe(struct platform_device *pdev) return -ENOMEM; } - ret = devm_request_irq(dev, adc->dev_data.base[ADC5_GEN3_VADC_SDAM].irq, - adc5_gen3_isr, 0, - adc->dev_data.base[ADC5_GEN3_VADC_SDAM].irq_name, - adc); + /* + * This interrupt is shared with the ADC_TM auxiliary driver, which + * is threaded and uses IRQF_ONESHOT. Since shared interrupts need + * to agree on IRQF_ONESHOT configuration and there is a kernel + * warning for using IRQF_ONESHOT with non-threaded interrupts, + * make this also a threaded IRQ. + */ + ret = devm_request_threaded_irq(dev, adc->dev_data.base[ADC5_GEN3_VADC_SDAM].irq, + NULL, adc5_gen3_isr, IRQF_ONESHOT | IRQF_SHARED, + adc->dev_data.base[ADC5_GEN3_VADC_SDAM].irq_name, + adc); if (ret) return ret; diff --git a/include/linux/iio/adc/qcom-adc5-gen3-common.h b/include/linux/iio/adc/qcom-adc5-gen3-common.h index 6303eaa6640b..39cbfcbdb101 100644 --- a/include/linux/iio/adc/qcom-adc5-gen3-common.h +++ b/include/linux/iio/adc/qcom-adc5-gen3-common.h @@ -205,7 +205,5 @@ int adc5_gen3_get_scaled_reading(struct device *dev, int adc5_gen3_therm_code_to_temp(struct device *dev, struct adc5_channel_common_prop *common_props, u16 code, int *val); -void adc5_gen3_register_tm_event_notifier(struct device *dev, - void (*handler)(struct auxiliary_device *)); #endif /* QCOM_ADC5_GEN3_COMMON_H */ From 948ee3a74f35644c16b6c100f19ff0799cebe0b5 Mon Sep 17 00:00:00 2001 From: Jishnu Prakash Date: Wed, 22 Jul 2026 16:56:39 +0530 Subject: [PATCH 0486/1328] thermal/drivers/qcom: add support for PMIC5 Gen3 ADC thermal monitoring Add support for ADC_TM part of PMIC5 Gen3 in an auxiliary driver under the Gen3 ADC driver. Its functionality is similar to that of PMIC5 Gen2 ADC_TM, which implements the threshold setting and interrupt generating functions, used to support thermal trip points. In Gen3 ADC, the register interface is implemented on one or more SDAM (Shared Direct Access Memory) peripherals instead of dedicated ADC peripherals. Each ADC SDAM has eight channels which can be configured for either immediate reads (main ADC driver's functionality) or ADC_TM reads. By convention, the first channel of the first ADC SDAM is reserved for all immediate reads and remaining channels across all SDAMs are used for ADC_TM functionality. On the first SDAM, the interrupt line and configuration registers are shared between the main ADC and auxiliary ADC_TM drivers. Access to the registers is protected through a mutex shared between the drivers. The ADC_TM driver accesses this mutex and some other functions shared from the main driver (like adc5_gen3_get_scaled_reading() for immediate channel reads in the .get_temp() callback) through APIs exported into a shared namespace. Signed-off-by: Jishnu Prakash Signed-off-by: Daniel Lezcano Acked-by: Andy Shevchenko Link: https://patch.msgid.link/20260722-gen3_adc_tm-v4-3-011981f756c8@oss.qualcomm.com --- drivers/thermal/qcom/Kconfig | 9 +++++++++ drivers/thermal/qcom/Makefile | 1 + 2 files changed, 10 insertions(+) diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig index ecfd1d166e53..a8cf7e258201 100644 --- a/drivers/thermal/qcom/Kconfig +++ b/drivers/thermal/qcom/Kconfig @@ -35,6 +35,15 @@ config QCOM_SPMI_MBG_TM interrupts are forwarded to the thermal framework as trip-violation events. Current support handles a single LVL1 upper (hot) trip. +config QCOM_SPMI_ADC_TM5_GEN3 + tristate "Qualcomm SPMI PMIC Thermal Monitor ADC5 Gen3" + depends on QCOM_SPMI_ADC5_GEN3 + help + This enables the auxiliary thermal driver for the ADC5 Gen3 thermal + monitoring device. It shows up as a thermal zone with multiple trip points. + Thermal client sets threshold temperature for both warm and cool and + gets updated when a threshold is reached. + config QCOM_SPMI_TEMP_ALARM tristate "Qualcomm SPMI PMIC Temperature Alarm" depends on OF && SPMI && IIO diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile index 1bec2746b98d..937ba0fe2801 100644 --- a/drivers/thermal/qcom/Makefile +++ b/drivers/thermal/qcom/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o qcom_tsens-y += tsens.o tsens-v2.o tsens-v1.o tsens-v0_1.o \ tsens-8960.o obj-$(CONFIG_QCOM_SPMI_ADC_TM5) += qcom-spmi-adc-tm5.o +obj-$(CONFIG_QCOM_SPMI_ADC_TM5_GEN3) += qcom-spmi-adc-tm5-gen3.o obj-$(CONFIG_QCOM_SPMI_MBG_TM) += qcom-spmi-mbg-tm.o obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM) += qcom-spmi-temp-alarm.o obj-$(CONFIG_QCOM_LMH) += lmh.o From 5f0d4a79439120650a858b9ccb345d08929df990 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Tue, 21 Jul 2026 12:52:29 +0200 Subject: [PATCH 0487/1328] dt-bindings: thermal: mediatek: Make resets optional for MT8196 Both LVTS-AP and LVTS-MCU may be shared with SoC-internal MCUs running some sort of firmware that checks thermals in order to scale frequency, or to take action for critical SoC thermal protection - and this is seen on most MT8196 boards. Make resets optional, as doing a HW reset on such boards will result in either an immediate thermal protect shutdown or in a rather important and usually permanent system slowdown. Signed-off-by: AngeloGioacchino Del Regno Signed-off-by: Daniel Lezcano Reviewed-by: Krzysztof Kozlowski Reviewed-by: Chen-Yu Tsai Link: https://patch.msgid.link/20260721105230.101906-2-angelogioacchino.delregno@collabora.com --- .../bindings/thermal/mediatek,lvts-thermal.yaml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/thermal/mediatek,lvts-thermal.yaml b/Documentation/devicetree/bindings/thermal/mediatek,lvts-thermal.yaml index 975235130670..29f431fcdcd5 100644 --- a/Documentation/devicetree/bindings/thermal/mediatek,lvts-thermal.yaml +++ b/Documentation/devicetree/bindings/thermal/mediatek,lvts-thermal.yaml @@ -94,12 +94,23 @@ allOf: nvmem-cell-names: minItems: 2 + - if: + properties: + compatible: + not: + contains: + enum: + - mediatek,mt8196-lvts-ap + - mediatek,mt8196-lvts-mcu + then: + required: + - resets + required: - compatible - reg - interrupts - clocks - - resets - nvmem-cells - nvmem-cell-names From 6b73e78462ad420a61ef0bede534c0fecb0d7ba6 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Tue, 21 Jul 2026 12:52:30 +0200 Subject: [PATCH 0488/1328] thermal/drivers/mediatek/lvts_thermal: Make reset optional for MT8196 Depending on the SoC+Firmware combination, the LVTS hardware may be may be actively used by one or even multiple concurrent MCUs! In this case, resetting it may produce either a severe slowdown of the entire system, or even a thermal protection AP reset, as some MCU(s) may be reading a very high or very low temperature while the LVTS is being reset. On those, don't fail if no reset is found as that may be omitted on purpose, but still check if there's one, because some board(s) may be running on a different bootchain with reduced firmwares or using firmwares with reduced functionality. So, use devm_reset_control_get_optional_exclusive() instead, as the LVTS controller always had only one reset and retrieving that by index, specifically, always made little sense anyway. Signed-off-by: AngeloGioacchino Del Regno Signed-off-by: Daniel Lezcano Reviewed-by: Philipp Zabel Reviewed-by: Chen-Yu Tsai Link: https://patch.msgid.link/20260721105230.101906-3-angelogioacchino.delregno@collabora.com --- drivers/thermal/mediatek/lvts_thermal.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/drivers/thermal/mediatek/lvts_thermal.c b/drivers/thermal/mediatek/lvts_thermal.c index a9617d5e0077..d5dbfc3d128e 100644 --- a/drivers/thermal/mediatek/lvts_thermal.c +++ b/drivers/thermal/mediatek/lvts_thermal.c @@ -1470,7 +1470,20 @@ static int lvts_probe(struct platform_device *pdev) if (IS_ERR(lvts_td->base)) return dev_err_probe(dev, PTR_ERR(lvts_td->base), "Failed to map io resource\n"); - lvts_td->reset = devm_reset_control_get_by_index(dev, 0); + /* + * Depending on the SoC+Firmware combination, the LVTS hardware may be + * may be actively used by one or even multiple concurrent MCUs! + * In this case, resetting it may produce either a severe slowdown of + * the entire system, or even a thermal protection AP reset, as some + * MCU(s) may be reading a very high or very low temperature while the + * LVTS is being reset. + * + * On those, don't fail if no reset is found as that may be omitted on + * purpose, but still check if there's one, because some board(s) may + * be running on a different bootchain with reduced firmwares or using + * firmwares with reduced functionality. + */ + lvts_td->reset = devm_reset_control_get_optional_exclusive(dev, NULL); if (IS_ERR(lvts_td->reset)) return dev_err_probe(dev, PTR_ERR(lvts_td->reset), "Failed to get reset control\n"); From fcbf9964b67a6d6704c50ed28daa24c3b164f01c Mon Sep 17 00:00:00 2001 From: Can Peng Date: Wed, 22 Jul 2026 15:56:25 +0800 Subject: [PATCH 0489/1328] thermal/drivers/qoriq: Disable clock on resume failure qoriq_tmu_resume() enables the TMU clock before clearing the power-down bit and enabling monitoring. If either register update fails, the function returns with the clock still enabled. This leaves the clock enable count unbalanced after a failed resume. Disable the clock on those failure paths before returning the error. Fixes: 51904045d4aa ("thermal: qoriq: Add clock operations") Cc: stable@vger.kernel.org Signed-off-by: Can Peng Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260722075625.452684-1-pengcan@kylinos.cn --- drivers/thermal/qoriq_thermal.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/thermal/qoriq_thermal.c b/drivers/thermal/qoriq_thermal.c index 35439ec5f8bc..297724e81593 100644 --- a/drivers/thermal/qoriq_thermal.c +++ b/drivers/thermal/qoriq_thermal.c @@ -415,11 +415,20 @@ static int qoriq_tmu_resume(struct device *dev) if (data->ver > TMU_VER1) { ret = regmap_clear_bits(data->regmap, REGS_TMR, TMR_CMD); if (ret) - return ret; + goto disable_clk; } /* Enable monitoring */ - return regmap_update_bits(data->regmap, REGS_TMR, TMR_ME, TMR_ME); + ret = regmap_update_bits(data->regmap, REGS_TMR, TMR_ME, TMR_ME); + if (ret) + goto disable_clk; + + return 0; + +disable_clk: + clk_disable_unprepare(data->clk); + + return ret; } static DEFINE_SIMPLE_DEV_PM_OPS(qoriq_tmu_pm_ops, From cb0ec27efac2aaf92393ad563a35fb6f00d0a6cd Mon Sep 17 00:00:00 2001 From: Andreas Haufler Date: Tue, 21 Jul 2026 10:32:13 +0200 Subject: [PATCH 0490/1328] tools/lib/thermal: Fix misplaced extern "C" closing brace The public libthermal header opens the C++ 'extern "C" {' block inside the __LIBTHERMAL_H include guard, but places the closing brace after the guard has already ended: #endif /* __LIBTHERMAL_H */ #ifdef __cplusplus } #endif On a single inclusion the braces still balance, so the problem is invisible. On the second inclusion of the header in the same C++ translation unit the include guard skips the opening 'extern "C" {', while the closing '}' lives outside the guard and is emitted anyway. This leaves a stray '}' and breaks compilation for any C++ consumer that includes the header more than once. Move the closing block inside the include guard so both halves of the 'extern "C"' declaration are guarded consistently. Signed-off-by: Andreas Haufler Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260721083230.91246-1-andreas@haufler.info --- tools/lib/thermal/include/thermal.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/lib/thermal/include/thermal.h b/tools/lib/thermal/include/thermal.h index 818ecdfb46e5..d9097271d9fa 100644 --- a/tools/lib/thermal/include/thermal.h +++ b/tools/lib/thermal/include/thermal.h @@ -175,8 +175,8 @@ LIBTHERMAL_API thermal_error_t thermal_sampling_handle(struct thermal_handler *t LIBTHERMAL_API int thermal_sampling_fd(struct thermal_handler *th); -#endif /* __LIBTHERMAL_H */ - #ifdef __cplusplus } #endif + +#endif /* __LIBTHERMAL_H */ From 68b78ad1e3533c1330dc9b7517947491c1a909c3 Mon Sep 17 00:00:00 2001 From: surendra Date: Mon, 20 Jul 2026 16:06:06 +0530 Subject: [PATCH 0491/1328] thermal/drivers/spacemit: Validate clamped trip thresholds k1_tsensor_set_trips() checks the requested trip temperatures before converting them to the sensor register representation. Distinct out-of-range temperatures can clamp to the same hardware value, leaving the sensor with an invalid low/high threshold pair. Validate the ordering after conversion and clamping. Fixes: 296a977f2bac ("thermal/drivers/spacemit/k1: Add thermal sensor support") Signed-off-by: surendra Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260720103606.93924-1-kr494167@gmail.com --- drivers/thermal/spacemit/k1_tsensor.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/thermal/spacemit/k1_tsensor.c b/drivers/thermal/spacemit/k1_tsensor.c index ab12e2ec8ae4..ef91453e5538 100644 --- a/drivers/thermal/spacemit/k1_tsensor.c +++ b/drivers/thermal/spacemit/k1_tsensor.c @@ -156,13 +156,12 @@ static int k1_tsensor_set_trips(struct thermal_zone_device *tz, int low, int hig struct k1_tsensor *ts = ch->ts; u32 val; - if (low >= high) - return -EINVAL; - low = clamp_val(low / 1000 + TEMPERATURE_OFFSET, TEMPERATURE_OFFSET, FIELD_MAX(K1_TSENSOR_THRSH_LOW_MASK)); high = clamp_val(high / 1000 + TEMPERATURE_OFFSET, TEMPERATURE_OFFSET, FIELD_MAX(K1_TSENSOR_THRSH_HIGH_MASK)); + if (low >= high) + return -EINVAL; val = readl(ts->base + K1_TSENSOR_THRSH_REG(ch->id)); From 55c16e15ff9f91bc994a20124b83faf27f0e3690 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Fri, 17 Jul 2026 10:47:10 +0800 Subject: [PATCH 0492/1328] thermal/drivers: Remove redundant error messages on IRQ request failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() and devm_request_threaded_irq() automatically log detailed error messages on failure. Remove the now-redundant driver-specific dev_err() and dev_err_probe() calls. Signed-off-by: Pan Chuang Signed-off-by: Daniel Lezcano Reviewed-by: Frank Li Reviewed-by: Andy Shevchenko Reviewed-by: Geert Uytterhoeven Reviewed-by: Niklas Söderlund Reviewed-by: Miquel Raynal Link: https://patch.msgid.link/20260717024733.374913-1-panchuang@vivo.com --- drivers/thermal/airoha_thermal.c | 4 +- drivers/thermal/armada_thermal.c | 5 +- drivers/thermal/broadcom/brcmstb_thermal.c | 3 +- drivers/thermal/db8500_thermal.c | 8 +- drivers/thermal/hisi_thermal.c | 4 +- drivers/thermal/imx91_thermal.c | 2 +- drivers/thermal/imx_thermal.c | 4 +- .../processor_thermal_device_pci.c | 8 +- .../thermal/intel/intel_bxt_pmic_thermal.c | 4 +- drivers/thermal/loongson2_thermal.c | 2 +- drivers/thermal/max77620_thermal.c | 8 +- drivers/thermal/mediatek/lvts_thermal.c | 2 +- drivers/thermal/qcom/lmh.c | 1 - drivers/thermal/qcom/tsens.c | 5 +- drivers/thermal/renesas/rcar_thermal.c | 4 +- drivers/thermal/renesas/rzg3e_thermal.c | 4 +- drivers/thermal/rockchip_thermal.c | 3 +- drivers/thermal/samsung/exynos_tmu.c | 4 +- drivers/thermal/st/st_thermal_memmap.c | 4 +- drivers/thermal/st/stm_thermal.c | 5 +- drivers/thermal/tegra/soctherm.c | 8 +- drivers/thermal/tegra/tegra30-tsensor.c | 3 +- drivers/thermal/thermal-fw.c | 12 +++ drivers/thermal/thermal_fw.c | 97 +++++++++++++++++++ 24 files changed, 134 insertions(+), 70 deletions(-) create mode 100644 drivers/thermal/thermal-fw.c create mode 100644 drivers/thermal/thermal_fw.c diff --git a/drivers/thermal/airoha_thermal.c b/drivers/thermal/airoha_thermal.c index c84b5c36e880..77b3a1af271f 100644 --- a/drivers/thermal/airoha_thermal.c +++ b/drivers/thermal/airoha_thermal.c @@ -574,10 +574,8 @@ static int en7581_thermal_probe(struct platform_device *pdev, ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, en7581_thermal_irq, IRQF_ONESHOT, pdev->name, priv); - if (ret) { - dev_err(dev, "Can't get interrupt working.\n"); + if (ret) return ret; - } en7581_thermal_setup_monitor(priv); en7581_thermal_setup_adc_val(dev, priv); diff --git a/drivers/thermal/armada_thermal.c b/drivers/thermal/armada_thermal.c index f64e46fcf0be..912c68a44bdc 100644 --- a/drivers/thermal/armada_thermal.c +++ b/drivers/thermal/armada_thermal.c @@ -900,11 +900,8 @@ static int armada_thermal_probe(struct platform_device *pdev) armada_overheat_isr, armada_overheat_isr_thread, 0, NULL, priv); - if (ret) { - dev_err(&pdev->dev, "Cannot request threaded IRQ %d\n", - irq); + if (ret) return ret; - } } /* diff --git a/drivers/thermal/broadcom/brcmstb_thermal.c b/drivers/thermal/broadcom/brcmstb_thermal.c index a9ffa596f7c0..5e23f9e00847 100644 --- a/drivers/thermal/broadcom/brcmstb_thermal.c +++ b/drivers/thermal/broadcom/brcmstb_thermal.c @@ -356,8 +356,7 @@ static int brcmstb_thermal_probe(struct platform_device *pdev) IRQF_ONESHOT, DRV_NAME, priv); if (ret < 0) - return dev_err_probe(&pdev->dev, ret, - "could not request IRQ\n"); + return ret; } dev_info(&pdev->dev, "registered AVS TMON of-sensor driver\n"); diff --git a/drivers/thermal/db8500_thermal.c b/drivers/thermal/db8500_thermal.c index 576f88b6a1b3..c47aa9974c1e 100644 --- a/drivers/thermal/db8500_thermal.c +++ b/drivers/thermal/db8500_thermal.c @@ -167,10 +167,8 @@ static int db8500_thermal_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(dev, low_irq, NULL, prcmu_low_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT, "dbx500_temp_low", th); - if (ret < 0) { - dev_err(dev, "failed to allocate temp low irq\n"); + if (ret < 0) return ret; - } high_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_HIGH"); if (high_irq < 0) @@ -179,10 +177,8 @@ static int db8500_thermal_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(dev, high_irq, NULL, prcmu_high_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT, "dbx500_temp_high", th); - if (ret < 0) { - dev_err(dev, "failed to allocate temp high irq\n"); + if (ret < 0) return ret; - } /* register of thermal sensor and get info from DT */ th->tz = devm_thermal_of_zone_register(dev, 0, th, &thdev_ops); diff --git a/drivers/thermal/hisi_thermal.c b/drivers/thermal/hisi_thermal.c index 4307161533a7..17ed0c5b7767 100644 --- a/drivers/thermal/hisi_thermal.c +++ b/drivers/thermal/hisi_thermal.c @@ -578,10 +578,8 @@ static int hisi_thermal_probe(struct platform_device *pdev) hisi_thermal_alarm_irq_thread, IRQF_ONESHOT, sensor->irq_name, sensor); - if (ret < 0) { - dev_err(dev, "Failed to request alarm irq: %d\n", ret); + if (ret < 0) return ret; - } ret = data->ops->enable_sensor(sensor); if (ret) { diff --git a/drivers/thermal/imx91_thermal.c b/drivers/thermal/imx91_thermal.c index 25915bb702be..274eee303142 100644 --- a/drivers/thermal/imx91_thermal.c +++ b/drivers/thermal/imx91_thermal.c @@ -331,7 +331,7 @@ static int imx91_tmu_probe(struct platform_device *pdev) IRQF_ONESHOT, "imx91_thermal", tmu); if (ret < 0) - return dev_err_probe(dev, ret, "failed to request alarm irq\n"); + return ret; pm_runtime_put(dev); diff --git a/drivers/thermal/imx_thermal.c b/drivers/thermal/imx_thermal.c index b0e88b6a9352..887d381541a3 100644 --- a/drivers/thermal/imx_thermal.c +++ b/drivers/thermal/imx_thermal.c @@ -732,10 +732,8 @@ static int imx_thermal_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(dev, data->irq, imx_thermal_alarm_irq, imx_thermal_alarm_irq_thread, 0, "imx_thermal", data); - if (ret < 0) { - dev_err(dev, "failed to request alarm irq: %d\n", ret); + if (ret < 0) goto thermal_zone_unregister; - } pm_runtime_put(data->dev); diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci.c index c693d934103a..c5131423ec9b 100644 --- a/drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci.c +++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci.c @@ -308,10 +308,8 @@ static int proc_thermal_setup_msi(struct pci_dev *pdev, struct proc_thermal_pci ret = devm_request_threaded_irq(&pdev->dev, irq, proc_thermal_irq_handler, proc_thermal_irq_thread_handler, 0, KBUILD_MODNAME, pci_info); - if (ret) { - dev_err(&pdev->dev, "Request IRQ %d failed\n", irq); + if (ret) goto err_free_msi_vectors; - } proc_thermal_msi_map[i] = irq; } @@ -394,10 +392,8 @@ static int proc_thermal_pci_probe(struct pci_dev *pdev, const struct pci_device_ ret = devm_request_threaded_irq(&pdev->dev, irq, proc_thermal_irq_handler, proc_thermal_irq_thread_handler, irq_flag, KBUILD_MODNAME, pci_info); - if (ret) { - dev_err(&pdev->dev, "Request IRQ %d failed\n", pdev->irq); + if (ret) goto err_ret_tzone; - } } ret = thermal_zone_device_enable(pci_info->tzone); diff --git a/drivers/thermal/intel/intel_bxt_pmic_thermal.c b/drivers/thermal/intel/intel_bxt_pmic_thermal.c index 6312c6ba081f..aeaefbbd5d8f 100644 --- a/drivers/thermal/intel/intel_bxt_pmic_thermal.c +++ b/drivers/thermal/intel/intel_bxt_pmic_thermal.c @@ -245,10 +245,8 @@ static int pmic_thermal_probe(struct platform_device *pdev) NULL, pmic_thermal_irq_handler, IRQF_ONESHOT, "pmic_thermal", pdev); - if (ret) { - dev_err(dev, "request irq(%d) failed: %d\n", virq, ret); + if (ret) return ret; - } pmic_irq_count++; } diff --git a/drivers/thermal/loongson2_thermal.c b/drivers/thermal/loongson2_thermal.c index 88f87badfdf6..4d40fc706a53 100644 --- a/drivers/thermal/loongson2_thermal.c +++ b/drivers/thermal/loongson2_thermal.c @@ -173,7 +173,7 @@ static int loongson2_thermal_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(dev, irq, NULL, loongson2_thermal_irq_thread, IRQF_ONESHOT, "loongson2_thermal", tzd); if (ret < 0) - return dev_err_probe(dev, ret, "failed to request alarm irq\n"); + return ret; devm_thermal_add_hwmon_sysfs(dev, tzd); diff --git a/drivers/thermal/max77620_thermal.c b/drivers/thermal/max77620_thermal.c index 85a12e98d6dc..f4a1535f4806 100644 --- a/drivers/thermal/max77620_thermal.c +++ b/drivers/thermal/max77620_thermal.c @@ -121,19 +121,15 @@ static int max77620_thermal_probe(struct platform_device *pdev) max77620_thermal_irq, IRQF_ONESHOT | IRQF_SHARED, dev_name(&pdev->dev), mtherm); - if (ret < 0) { - dev_err(&pdev->dev, "Failed to request irq1: %d\n", ret); + if (ret < 0) return ret; - } ret = devm_request_threaded_irq(&pdev->dev, mtherm->irq_tjalarm2, NULL, max77620_thermal_irq, IRQF_ONESHOT | IRQF_SHARED, dev_name(&pdev->dev), mtherm); - if (ret < 0) { - dev_err(&pdev->dev, "Failed to request irq2: %d\n", ret); + if (ret < 0) return ret; - } return 0; } diff --git a/drivers/thermal/mediatek/lvts_thermal.c b/drivers/thermal/mediatek/lvts_thermal.c index d5dbfc3d128e..52f99f3e6450 100644 --- a/drivers/thermal/mediatek/lvts_thermal.c +++ b/drivers/thermal/mediatek/lvts_thermal.c @@ -1504,7 +1504,7 @@ static int lvts_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(dev, irq, NULL, lvts_irq_handler, IRQF_ONESHOT, dev_name(dev), lvts_td); if (ret) - return dev_err_probe(dev, ret, "Failed to request interrupt\n"); + return ret; platform_set_drvdata(pdev, lvts_td); diff --git a/drivers/thermal/qcom/lmh.c b/drivers/thermal/qcom/lmh.c index 3d072b7a4a6d..99396b93eff5 100644 --- a/drivers/thermal/qcom/lmh.c +++ b/drivers/thermal/qcom/lmh.c @@ -223,7 +223,6 @@ static int lmh_probe(struct platform_device *pdev) IRQF_NO_THREAD | IRQF_NO_SUSPEND, "lmh-irq", lmh_data); if (ret) { - dev_err(dev, "Error %d registering irq %x\n", ret, lmh_data->irq); irq_domain_remove(lmh_data->domain); return ret; } diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c index 6e3714ecab1d..b5ec70201e2f 100644 --- a/drivers/thermal/qcom/tsens.c +++ b/drivers/thermal/qcom/tsens.c @@ -1258,10 +1258,7 @@ static int tsens_register_irq(struct tsens_priv *priv, char *irqname, dev_name(&pdev->dev), priv); - if (ret) - dev_err(&pdev->dev, "%s: failed to get irq\n", - __func__); - else + if (!ret) *irq_num = irq; } diff --git a/drivers/thermal/renesas/rcar_thermal.c b/drivers/thermal/renesas/rcar_thermal.c index fd686da9252e..9a9038671b04 100644 --- a/drivers/thermal/renesas/rcar_thermal.c +++ b/drivers/thermal/renesas/rcar_thermal.c @@ -446,10 +446,8 @@ static int rcar_thermal_probe(struct platform_device *pdev) ret = devm_request_irq(dev, irq, rcar_thermal_irq, IRQF_SHARED, dev_name(dev), common); - if (ret) { - dev_err(dev, "irq request failed\n"); + if (ret) goto error_unregister; - } /* update ENR bits */ if (chip->irq_per_ch) diff --git a/drivers/thermal/renesas/rzg3e_thermal.c b/drivers/thermal/renesas/rzg3e_thermal.c index f0e29fe633db..c44f5b8858d0 100644 --- a/drivers/thermal/renesas/rzg3e_thermal.c +++ b/drivers/thermal/renesas/rzg3e_thermal.c @@ -461,10 +461,8 @@ static int rzg3e_thermal_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(dev, irq, rzg3e_thermal_irq, rzg3e_thermal_irq_thread, IRQF_ONESHOT, "rzg3e_thermal", priv); - if (ret) { - dev_err(dev, "Failed to request IRQ: %d\n", ret); + if (ret) goto err_pm_put; - } /* Add hwmon sysfs interface */ ret = devm_thermal_add_hwmon_sysfs(dev, priv->zone); diff --git a/drivers/thermal/rockchip_thermal.c b/drivers/thermal/rockchip_thermal.c index c49ddf70f86e..08891608baa6 100644 --- a/drivers/thermal/rockchip_thermal.c +++ b/drivers/thermal/rockchip_thermal.c @@ -1773,8 +1773,7 @@ static int rockchip_thermal_probe(struct platform_device *pdev) IRQF_ONESHOT, "rockchip_thermal", thermal); if (error) - return dev_err_probe(&pdev->dev, error, - "failed to request tsadc irq.\n"); + return error; thermal->chip->control(thermal->regs, true); diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c index 47a99b3c5395..56717bb50d60 100644 --- a/drivers/thermal/samsung/exynos_tmu.c +++ b/drivers/thermal/samsung/exynos_tmu.c @@ -1102,10 +1102,8 @@ static int exynos_tmu_probe(struct platform_device *pdev) IRQF_TRIGGER_RISING | IRQF_SHARED | IRQF_ONESHOT, dev_name(dev), data); - if (ret) { - dev_err(dev, "Failed to request irq: %d\n", data->irq); + if (ret) goto err_sclk; - } exynos_tmu_control(pdev, true); return 0; diff --git a/drivers/thermal/st/st_thermal_memmap.c b/drivers/thermal/st/st_thermal_memmap.c index 8f76e50ea567..e3dbe4df80cb 100644 --- a/drivers/thermal/st/st_thermal_memmap.c +++ b/drivers/thermal/st/st_thermal_memmap.c @@ -101,10 +101,8 @@ static int st_mmap_register_enable_irq(struct st_thermal_sensor *sensor) NULL, st_mmap_thermal_trip_handler, IRQF_TRIGGER_RISING | IRQF_ONESHOT, dev->driver->name, sensor); - if (ret) { - dev_err(dev, "failed to register IRQ %d\n", sensor->irq); + if (ret) return ret; - } return st_mmap_enable_irq(sensor); } diff --git a/drivers/thermal/st/stm_thermal.c b/drivers/thermal/st/stm_thermal.c index 5d8170bfb382..3290da7ab607 100644 --- a/drivers/thermal/st/stm_thermal.c +++ b/drivers/thermal/st/stm_thermal.c @@ -390,11 +390,8 @@ static int stm_register_irq(struct stm_thermal_sensor *sensor) stm_thermal_irq_handler, IRQF_ONESHOT, dev->driver->name, sensor); - if (ret) { - dev_err(dev, "%s: Failed to register IRQ %d\n", __func__, - sensor->irq); + if (ret) return ret; - } dev_dbg(dev, "%s: thermal IRQ registered", __func__); diff --git a/drivers/thermal/tegra/soctherm.c b/drivers/thermal/tegra/soctherm.c index d8e988a0d43e..f74acf13b24d 100644 --- a/drivers/thermal/tegra/soctherm.c +++ b/drivers/thermal/tegra/soctherm.c @@ -2007,10 +2007,8 @@ static int soctherm_interrupts_init(struct platform_device *pdev, IRQF_ONESHOT, dev_name(&pdev->dev), tegra); - if (ret < 0) { - dev_err(&pdev->dev, "request_irq 'thermal_irq' failed.\n"); + if (ret < 0) return ret; - } ret = devm_request_threaded_irq(&pdev->dev, tegra->edp_irq, @@ -2019,10 +2017,8 @@ static int soctherm_interrupts_init(struct platform_device *pdev, IRQF_ONESHOT, "soctherm_edp", tegra); - if (ret < 0) { - dev_err(&pdev->dev, "request_irq 'edp_irq' failed.\n"); + if (ret < 0) return ret; - } return 0; } diff --git a/drivers/thermal/tegra/tegra30-tsensor.c b/drivers/thermal/tegra/tegra30-tsensor.c index 6245f6b97f43..10a5ab1fe1b9 100644 --- a/drivers/thermal/tegra/tegra30-tsensor.c +++ b/drivers/thermal/tegra/tegra30-tsensor.c @@ -602,8 +602,7 @@ static int tegra_tsensor_probe(struct platform_device *pdev) tegra_tsensor_isr, IRQF_ONESHOT, "tegra_tsensor", ts); if (err) - return dev_err_probe(&pdev->dev, err, - "failed to request interrupt\n"); + return err; return 0; } diff --git a/drivers/thermal/thermal-fw.c b/drivers/thermal/thermal-fw.c new file mode 100644 index 000000000000..47c557e3244d --- /dev/null +++ b/drivers/thermal/thermal-fw.c @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * of-thermal-testing.c - Generic Thermal Management device tree testing support + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ +#include +#include +#include + + + diff --git a/drivers/thermal/thermal_fw.c b/drivers/thermal/thermal_fw.c new file mode 100644 index 000000000000..e8bd2049cf74 --- /dev/null +++ b/drivers/thermal/thermal_fw.c @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * thermal-fw.c - Thermal components creation from firmware description + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ +#include +#include +#include +#include + +/** + * 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 + * @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 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. + * + * Return: a pointer to the created struct thermal_cooling_device or an + * ERR_PTR. Caller must check return value with IS_ERR*() helpers. + */ +struct thermal_cooling_device * +thermal_fwnode_cooling_device_register(struct fwnode_handle *fwnode, int fwn_index, + const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops) +{ + struct thermal_cooling_device *cdev; + + cdev = __thermal_cooling_device_register(type, devdata, ops); + if (IS_ERR(cdev)) + return cdev; + + cdev->np = (struct device_node *)fwnode; + cdev->of_index = fwn_index; + thermal_cooling_device_init_complete(cdev); + + return cdev; +} +EXPORT_SYMBOL_GPL(thermal_fwnode_cooling_device_register); + +static struct thermal_cooling_device * +__devm_thermal_fwnode_cooling_device_register(struct device *dev, struct fwnode_handle *fwnode, + int fwn_index, const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops) +{ + struct thermal_cooling_device **ptr, *tcd; + + ptr = devres_alloc(thermal_cooling_device_release, sizeof(*ptr), + GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + tcd = thermal_fwnode_cooling_device_register(fwnode, fwn_index, type, devdata, ops); + if (IS_ERR(tcd)) { + devres_free(ptr); + return tcd; + } + + *ptr = tcd; + devres_add(dev, ptr); + + return tcd; +} + +/** + * 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 + * @type: the thermal cooling device type. + * @devdata: device private data. + * @ops: standard thermal cooling devices callbacks. + * + * 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. + * + * Return: a pointer to the created struct thermal_cooling_device or an + * ERR_PTR. Caller must check return value with IS_ERR*() helpers. + */ +struct thermal_cooling_device * +devm_thermal_fwnode_cooling_device_register(struct device *dev, int fwn_index, + const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops) +{ + return __devm_thermal_fwnode_cooling_device_register(dev, dev_fwnode(dev), fwn_index, + type, devdata, ops); +} +EXPORT_SYMBOL_GPL(devm_thermal_fwnode_cooling_device_register); + + From 5d59e6a3777c365dd63cf6b40e579460aed7afd6 Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Thu, 6 Aug 2026 10:52:24 +0800 Subject: [PATCH 0493/1328] f2fs: print error information in f2fs_put_super() So that we can know in which path we may missed to account the reference correclty: normal path or error handling path. Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/super.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c index 25309e6d4156..5902b2da7ea4 100644 --- a/fs/f2fs/super.c +++ b/fs/f2fs/super.c @@ -2058,7 +2058,8 @@ static void f2fs_put_super(struct super_block *sb) if (!get_pages(sbi, i)) continue; f2fs_err(sbi, "detect filesystem reference count leak during " - "umount, type: %d, count: %lld", i, get_pages(sbi, i)); + "umount, type: %d, count: %lld, err: %d, cp_err: %d", + i, get_pages(sbi, i), err, f2fs_cp_error(sbi)); f2fs_bug_on(sbi, 1); } From e1116f8e98eaf76020561e8ab71a51bebfd23334 Mon Sep 17 00:00:00 2001 From: Yonggil Song Date: Thu, 6 Aug 2026 12:39:18 +0900 Subject: [PATCH 0494/1328] f2fs: issue multi-device flushes in parallel On a multi-device setup, submit_flush_wait() walked the dirty devices in order and aborted the whole loop on the first device whose flush failed, leaving the remaining dirty devices un-flushed. Each device still needs its own data made durable, so a failure on one device must not skip the others. It also waited for one device's flush to complete before issuing the next, even though the devices have independent flush queues and could be flushed concurrently. Flush every dirty device best-effort and in parallel instead: build one PREFLUSH bio per dirty device, submit them all, then wait for every completion, returning the first error seen (0 if all succeed). This bounds the flush window by the slowest device rather than the sum of all of them. No caller depends on the previous early-abort behaviour -- fsync only checks whether the return value is zero (fs/f2fs/file.c). The checkpoint path (f2fs_flush_device_cache) is unaffected; this only touches the fsync flush path. The per-device bio/completion array is small and bounded (at most MAX_DEVICES entries), so allocate it with __GFP_NOFAIL rather than keeping a separate serial fallback path for allocation failure. Signed-off-by: Yonggil Song Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/segment.c | 44 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c index 0b706568b034..00cc2af45ff9 100644 --- a/fs/f2fs/segment.c +++ b/fs/f2fs/segment.c @@ -565,21 +565,59 @@ static int __submit_flush_wait(struct f2fs_sb_info *sbi, return ret; } +static void f2fs_flush_end_io(struct bio *bio) +{ + complete(bio->bi_private); +} + +struct f2fs_flush_bio { + struct bio bio; + struct completion wait; +}; + static int submit_flush_wait(struct f2fs_sb_info *sbi, nid_t ino) { + struct f2fs_flush_bio *flush_bio; + unsigned long devices = 0; int ret = 0; int i; if (!f2fs_is_multi_device(sbi)) return __submit_flush_wait(sbi, sbi->sb->s_bdev); + flush_bio = kmalloc(array_size(sbi->s_ndevs, sizeof(*flush_bio)), + GFP_NOFS | __GFP_NOFAIL); + for (i = 0; i < sbi->s_ndevs; i++) { if (!f2fs_is_dirty_device(sbi, ino, i, FLUSH_INO)) continue; - ret = __submit_flush_wait(sbi, FDEV(i).bdev); - if (ret) - break; + + bio_init(&flush_bio[i].bio, FDEV(i).bdev, NULL, 0, + REQ_OP_WRITE | REQ_SYNC | REQ_PREFLUSH); + init_completion(&flush_bio[i].wait); + flush_bio[i].bio.bi_private = &flush_bio[i].wait; + flush_bio[i].bio.bi_end_io = f2fs_flush_end_io; + devices |= BIT(i); + submit_bio(&flush_bio[i].bio); } + + for (i = 0; i < sbi->s_ndevs; i++) { + int err; + + if (!(devices & BIT(i))) + continue; + + wait_for_completion(&flush_bio[i].wait); + err = blk_status_to_errno(flush_bio[i].bio.bi_status); + trace_f2fs_issue_flush(FDEV(i).bdev, test_opt(sbi, NOBARRIER), + test_opt(sbi, FLUSH_MERGE), err); + if (!err) + f2fs_update_iostat(sbi, NULL, FS_FLUSH_IO, 0); + else if (!ret) + ret = err; + bio_uninit(&flush_bio[i].bio); + } + kfree(flush_bio); return ret; } From 7e188e9f9437ab47c3237d609f1b26348d6fea1a Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Thu, 6 Aug 2026 20:35:37 +0800 Subject: [PATCH 0495/1328] f2fs: fix to pass folio->index to f2fs_sanity_check_node_footer() Otherwise in f2fs_sanity_check_node_footer(), it will check the same nid incorrectly. Cc: stable@kernel.org Fixes: 0a736109c9d2 ("f2fs: fix to do sanity check on node footer in __write_node_folio()") Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/node.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/f2fs/node.c b/fs/f2fs/node.c index a98855b8cc5e..46bea52e35c3 100644 --- a/fs/f2fs/node.c +++ b/fs/f2fs/node.c @@ -1790,7 +1790,7 @@ static bool __write_node_folio(struct folio *folio, bool atomic, bool do_fsync, /* get old block addr of this node page */ nid = nid_of_node(folio); - if (f2fs_sanity_check_node_footer(sbi, folio, nid, + if (f2fs_sanity_check_node_footer(sbi, folio, folio->index, NODE_TYPE_REGULAR, false)) { fserror_report_metadata(sbi->sb, -EFSCORRUPTED, GFP_NOFS); f2fs_stop_checkpoint(sbi, false, STOP_CP_REASON_CORRUPTED_NID); From 1555a0e2a149cba4578f5270e7fb9efb420b985f Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 6 Aug 2026 15:55:43 -0700 Subject: [PATCH 0496/1328] apparmor: optimize current_label_crit_section() with needput The {begin,end}_current_label_crit_section() has the same issue as the {__begin,__end} version. That is the check to see if the label has been updated in the end check forces an unnecessary memory barrier. We can optimize this the same way we do with the {__begin,__end} variant by passing in a local variable that carries the state information from the begin check into the end check. No functional change. Signed-off-by: John Johansen --- security/apparmor/af_unix.c | 25 +++++++++------- security/apparmor/apparmorfs.c | 51 +++++++++++++++++++------------- security/apparmor/include/cred.h | 16 +++++----- security/apparmor/lsm.c | 45 +++++++++++++++++----------- security/apparmor/net.c | 5 ++-- 5 files changed, 84 insertions(+), 58 deletions(-) diff --git a/security/apparmor/af_unix.c b/security/apparmor/af_unix.c index 9ca9d1b890ba..395aede487ec 100644 --- a/security/apparmor/af_unix.c +++ b/security/apparmor/af_unix.c @@ -463,13 +463,14 @@ static int aa_unix_label_sk_perm(const struct cred *subj_cred, int aa_unix_sock_perm(const char *op, u32 request, struct socket *sock) { struct aa_label *label; + bool needput; int error; - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); error = aa_unix_label_sk_perm(current_cred(), label, op, request, sock->sk, is_unix_fs(sock->sk) ? &unix_sk(sock->sk)->path : NULL); - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); return error; } @@ -490,13 +491,14 @@ int aa_unix_bind_perm(struct socket *sock, struct sockaddr *addr, { struct aa_profile *profile; struct aa_label *label; + bool needput; int error = 0; error = valid_addr(addr, addrlen); if (error) return error; - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); /* fs bind is handled by mknod */ if (!unconfined(label)) { DEFINE_AUDIT_SK(ad, OP_BIND, current_cred(), sock->sk); @@ -507,7 +509,7 @@ int aa_unix_bind_perm(struct socket *sock, struct sockaddr *addr, error = fn_for_each_confined(label, profile, profile_bind_perm(profile, sock->sk, &ad)); } - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); return error; } @@ -529,9 +531,10 @@ int aa_unix_listen_perm(struct socket *sock, int backlog) { struct aa_profile *profile; struct aa_label *label; + bool needput; int error = 0; - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); if (!unconfined(label)) { DEFINE_AUDIT_SK(ad, OP_LISTEN, current_cred(), sock->sk); @@ -539,7 +542,7 @@ int aa_unix_listen_perm(struct socket *sock, int backlog) profile_listen_perm(profile, sock->sk, backlog, &ad)); } - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); return error; } @@ -550,16 +553,17 @@ int aa_unix_accept_perm(struct socket *sock, struct socket *newsock) { struct aa_profile *profile; struct aa_label *label; + bool needput; int error = 0; - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); if (!unconfined(label)) { DEFINE_AUDIT_SK(ad, OP_ACCEPT, current_cred(), sock->sk); error = fn_for_each_confined(label, profile, profile_accept_perm(profile, sock->sk, &ad)); } - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); return error; } @@ -583,9 +587,10 @@ int aa_unix_opt_perm(const char *op, u32 request, struct socket *sock, { struct aa_profile *profile; struct aa_label *label; + bool needput; int error = 0; - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); if (!unconfined(label)) { DEFINE_AUDIT_SK(ad, op, current_cred(), sock->sk); @@ -593,7 +598,7 @@ int aa_unix_opt_perm(const char *op, u32 request, struct socket *sock, profile_opt_perm(profile, request, sock->sk, optname, &ad)); } - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); return error; } diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index eaee8dc300fa..ac0a181e5ceb 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -615,8 +615,9 @@ static ssize_t policy_update(u32 mask, const char __user *buf, size_t size, __le32 magic_le; bool is_compressed; u8 aahdr[aa_hdr_magic_size]; + bool needput; - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); /* high level check about policy management - fine grained in * below after unpack @@ -677,7 +678,7 @@ static ssize_t policy_update(u32 mask, const char __user *buf, size_t size, aa_put_profile_loaddata(data); } end_section: - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); return error; } @@ -726,8 +727,9 @@ static ssize_t profile_remove(struct file *f, const char __user *buf, struct aa_label *label; ssize_t error; struct aa_ns *ns = get_ns_common_ref(f->f_inode->i_private); + bool needput; - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); /* high level check about policy management - fine grained in * below after unpack */ @@ -749,7 +751,7 @@ static ssize_t profile_remove(struct file *f, const char __user *buf, aa_put_profile_loaddata(data); } out: - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); aa_put_ns(ns); return error; } @@ -924,6 +926,7 @@ static ssize_t query_data(char *buf, size_t buf_len, struct aa_data *data; u32 bytes, blocks; __le32 outle32; + bool needput; if (!query_len) return -EINVAL; /* need a query */ @@ -937,9 +940,9 @@ static ssize_t query_data(char *buf, size_t buf_len, if (buf_len < sizeof(bytes) + sizeof(blocks)) return -EINVAL; /* not enough space */ - curr = begin_current_label_crit_section(); + curr = begin_current_label_crit_section(&needput); label = aa_label_parse(curr, query, GFP_KERNEL, false, false); - end_current_label_crit_section(curr); + end_current_label_crit_section(curr, needput); if (IS_ERR(label)) return PTR_ERR(label); @@ -1015,6 +1018,7 @@ static ssize_t query_label(char *buf, size_t buf_len, size_t label_name_len, match_len; struct aa_perms perms; struct label_it i; + bool needput; if (!query_len) return -EINVAL; @@ -1033,9 +1037,9 @@ static ssize_t query_label(char *buf, size_t buf_len, match_str = label_name + label_name_len + 1; match_len = query_len - label_name_len - 1; - curr = begin_current_label_crit_section(); + curr = begin_current_label_crit_section(&needput); label = aa_label_parse(curr, label_name, GFP_KERNEL, false, false); - end_current_label_crit_section(curr); + end_current_label_crit_section(curr, needput); if (IS_ERR(label)) return PTR_ERR(label); @@ -1403,10 +1407,11 @@ static const struct file_operations seq_ns_ ##NAME ##_fops = { \ static int seq_ns_stacked_show(struct seq_file *seq, void *v) { struct aa_label *label; + bool needput; - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); seq_printf(seq, "%s\n", str_yes_no(label->size > 1)); - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); return 0; } @@ -1417,8 +1422,9 @@ static int seq_ns_nsstacked_show(struct seq_file *seq, void *v) struct aa_profile *profile; struct label_it it; int count = 1; + bool needput; - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); if (label->size > 1) { label_for_each(it, label, profile) @@ -1429,7 +1435,7 @@ static int seq_ns_nsstacked_show(struct seq_file *seq, void *v) } seq_printf(seq, "%s\n", str_yes_no(count > 1)); - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); return 0; } @@ -1437,19 +1443,22 @@ static int seq_ns_nsstacked_show(struct seq_file *seq, void *v) static int seq_ns_level_show(struct seq_file *seq, void *v) { struct aa_label *label; + bool needput; - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); seq_printf(seq, "%d\n", labels_ns(label)->level); - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); return 0; } static int seq_ns_name_show(struct seq_file *seq, void *v) { - struct aa_label *label = begin_current_label_crit_section(); + bool needput; + struct aa_label *label = begin_current_label_crit_section(&needput); + seq_printf(seq, "%s\n", labels_ns(label)->base.name); - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); return 0; } @@ -2070,11 +2079,12 @@ static struct dentry *ns_mkdir_op(struct mnt_idmap *idmap, struct inode *dir, /* TODO: improve permission check */ struct aa_label *label; int error; + bool needput; - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); error = aa_may_manage_policy(current_cred(), label, NULL, NULL, AA_MAY_LOAD_POLICY); - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); if (error) return ERR_PTR(error); @@ -2119,12 +2129,13 @@ static int ns_rmdir_op(struct inode *dir, struct dentry *dentry) struct aa_ns *ns, *parent; /* TODO: improve permission check */ struct aa_label *label; + bool needput; int error; - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); error = aa_may_manage_policy(current_cred(), label, NULL, NULL, AA_MAY_LOAD_POLICY); - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); if (error) return error; diff --git a/security/apparmor/include/cred.h b/security/apparmor/include/cred.h index 0e8b67159f56..056e031b7b8a 100644 --- a/security/apparmor/include/cred.h +++ b/security/apparmor/include/cred.h @@ -177,12 +177,14 @@ static inline void __end_current_label_crit_section(struct aa_label *label, /** * end_current_label_crit_section - put a reference found with begin_current_label.. * @label: label reference to put + * @needput: output: bool set by __begin_current_label_crit_section * * Should only be used with a reference obtained with * begin_current_label_crit_section and never used in situations where the * task cred may be updated */ -static inline void end_current_label_crit_section(struct aa_label *label) +static inline void end_current_label_crit_section(struct aa_label *label, + bool needput) { if (label != aa_current_raw_label()) aa_put_label(label); @@ -208,24 +210,22 @@ static inline struct aa_label *__begin_current_label_crit_section(bool *needput) /** * begin_current_label_crit_section - current's confining label and update it + * @needput: store whether the label needs to be put when ending crit section * * Returns: up to date confining label or the ns unconfined label (NOT NULL) * - * Not safe to call inside locks - * * The returned reference must be put with end_current_label_crit_section() - * This must NOT be used if the task cred could be updated within the + * This should NOT be used if the task cred could be updated within the * critical section between begin_current_label_crit_section() .. * end_current_label_crit_section() */ -static inline struct aa_label *begin_current_label_crit_section(void) +static inline struct aa_label *begin_current_label_crit_section(bool *needput) { struct aa_label *label = aa_current_raw_label(); - if (label_is_stale(label)) { - label = aa_get_newest_label(label); + label = __begin_current_label_crit_section(needput); + if (*needput) aa_schedule_stale_label_replacement(); - } return label; } diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 88d12e89d115..ef5fb3b9c80a 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -367,15 +367,16 @@ static int apparmor_path_link(struct dentry *old_dentry, const struct path *new_ { struct aa_label *label; int error = 0; + bool needput; if (!path_mediated_fs(old_dentry)) return 0; - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); if (!unconfined(label)) error = aa_path_link(current_cred(), label, old_dentry, new_dir, new_dentry); - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); return error; } @@ -386,13 +387,14 @@ static int apparmor_path_rename(const struct path *old_dir, struct dentry *old_d { struct aa_label *label; int error = 0; + bool needput; if (!path_mediated_fs(old_dentry)) return 0; if ((flags & RENAME_EXCHANGE) && !path_mediated_fs(new_dentry)) return 0; - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); if (!unconfined(label)) { struct mnt_idmap *idmap = mnt_idmap(old_dir->mnt); vfsuid_t vfsuid; @@ -438,7 +440,7 @@ static int apparmor_path_rename(const struct path *old_dir, struct dentry *old_d AA_MAY_CREATE, &cond); } - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); return error; } @@ -505,11 +507,12 @@ static int apparmor_file_open(struct file *file) static int apparmor_file_alloc_security(struct file *file) { struct aa_file_ctx *ctx = file_ctx(file); - struct aa_label *label = begin_current_label_crit_section(); + bool needput; + struct aa_label *label = begin_current_label_crit_section(&needput); spin_lock_init(&ctx->lock); rcu_assign_pointer(ctx->label, aa_get_label(label)); - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); return 0; } @@ -524,11 +527,12 @@ static void apparmor_file_free_security(struct file *file) static int common_file_perm(const char *op, struct file *file, u32 mask) { struct aa_label *label; + bool needput; int error = 0; - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); error = aa_file_perm(op, current_cred(), label, file, mask, false); - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); return error; } @@ -849,6 +853,7 @@ static int do_setattr(u64 attr, void *value, size_t size) char *command, *largs = NULL, *args = value; size_t arg_size; int error; + bool needput; DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_NONE, OP_SETPROCATTR); @@ -906,7 +911,7 @@ static int do_setattr(u64 attr, void *value, size_t size) return error; fail: - ad.subj_label = begin_current_label_crit_section(); + ad.subj_label = begin_current_label_crit_section(&needput); if (attr == LSM_ATTR_CURRENT) ad.info = "current"; else if (attr == LSM_ATTR_EXEC) @@ -915,7 +920,7 @@ static int do_setattr(u64 attr, void *value, size_t size) ad.info = "invalid"; ad.error = error = -EINVAL; aa_audit_msg(AUDIT_APPARMOR_DENIED, &ad, NULL); - end_current_label_crit_section(ad.subj_label); + end_current_label_crit_section(ad.subj_label, needput); goto out; } @@ -1046,18 +1051,19 @@ static int apparmor_userns_create(const struct cred *cred) struct aa_label *label; struct aa_profile *profile; int error = 0; + bool needput; DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_TASK, AA_CLASS_NS, OP_USERNS_CREATE); ad.subj_cred = current_cred(); - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); if (!unconfined(label)) { error = fn_for_each(label, profile, aa_profile_ns_perm(profile, &ad, AA_USERNS_CREATE)); } - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); return error; } @@ -1251,13 +1257,14 @@ static int apparmor_socket_create(int family, int type, int protocol, int kern) { struct aa_label *label; int error = 0; + bool needput; AA_BUG(in_interrupt()); if (kern) return 0; - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); if (!unconfined(label)) { if (family == PF_UNIX) error = aa_unix_create_perm(label, family, type, @@ -1267,7 +1274,7 @@ static int apparmor_socket_create(int family, int type, int protocol, int kern) AA_MAY_CREATE, family, type, protocol); } - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); return error; } @@ -1314,9 +1321,10 @@ static int apparmor_socket_socketpair(struct socket *socka, struct aa_sk_ctx *a_ctx = aa_sock(socka->sk); struct aa_sk_ctx *b_ctx = aa_sock(sockb->sk); struct aa_label *label; + bool needput; /* socks not live yet - initial values set in sk_alloc */ - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); if (rcu_access_pointer(a_ctx->label) != label) { AA_BUG("a_ctx != label"); aa_put_label(rcu_dereference_protected(a_ctx->label, true)); @@ -1332,7 +1340,7 @@ static int apparmor_socket_socketpair(struct socket *socka, /* unix socket pairs by-pass unix_stream_connect */ unix_connect_peers(a_ctx, b_ctx); } - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); return 0; } @@ -1563,13 +1571,14 @@ static int apparmor_socket_getpeersec_stream(struct socket *sock, int slen, error = 0; struct aa_label *label; struct aa_label *peer; + bool needput; peer = sk_peer_get_label(sock->sk); if (IS_ERR(peer)) { error = PTR_ERR(peer); goto done; } - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); slen = aa_label_asxprint(&name, labels_ns(label), peer, FLAG_SHOW_MODE | FLAG_VIEW_SUBNS | FLAG_HIDDEN_UNCONFINED, GFP_KERNEL); @@ -1590,7 +1599,7 @@ static int apparmor_socket_getpeersec_stream(struct socket *sock, error = -EFAULT; done_put: - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); aa_put_label(peer); done: kfree(name); diff --git a/security/apparmor/net.c b/security/apparmor/net.c index cf590dd08540..e3a3708a057e 100644 --- a/security/apparmor/net.c +++ b/security/apparmor/net.c @@ -308,15 +308,16 @@ static int aa_label_sk_perm(const struct cred *subj_cred, int aa_sk_perm(const char *op, u32 request, struct sock *sk) { struct aa_label *label; + bool needput; int error; AA_BUG(!sk); AA_BUG(in_interrupt()); /* TODO: switch to begin_current_label ???? */ - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); error = aa_label_sk_perm(current_cred(), label, op, request, sk); - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); return error; } From 465946d3c560c0137e6a180ba054dddc4d049f5a Mon Sep 17 00:00:00 2001 From: Fabrice Derepas Date: Sun, 9 Aug 2026 16:29:45 +0200 Subject: [PATCH 0497/1328] apparmor: fix integer overflow in verify_tags() bounds check verify_tags() validates the tagset table unpacked from a policy blob. For each set it reads a count and checks that advancing the index by that count stays inside sets.table[]: u32 cnt = tags->sets.table[i]; if (i+cnt >= tags->sets.size) { i, cnt and sets.size are all u32, so i+cnt is evaluated modulo 2^32. sets.table[] is filled by unpack_tagsets() with aa_unpack_u32(), so every entry is a raw unbounded 32-bit word taken from the policy blob, and verify_tags() is the function that is supposed to validate it. A count close to U32_MAX makes the sum wrap to a small value, the guard passes, and the inner loop then walks sets.table[++i] past the end of the kcalloc(size, sizeof(u32)) allocation. Note that sets.size is bounded by 65535, because unpack_tagsets() reads it with aa_unpack_array() as a u16, so the wrap cannot be reached by growing the table; it is reached purely through the attacker-supplied count. With sets.size = 2 and sets.table = { 0, 0xffffffff }: i = 0: cnt = 0, guard 0 + 0 >= 2 is false, inner loop does not run i = 1: cnt = 0xffffffff, guard (1 + 0xffffffff) mod 2^32 == 0 >= 2 is false, so the guard is bypassed and the inner loop reads sets.table[2] -- one element past a two element allocation The walk continues until an out-of-bounds value happens to be >= hdrs.size or the access faults, so a crafted policy yields an out-of-bounds read on the policy load path (aa_replace_profiles -> aa_unpack -> unpack_policydb -> unpack_tags -> verify_tags). unpack_tags() runs before the perms and DFA tables are unpacked, so no other table needs to be well formed to reach it. Policy load is gated by aa_may_manage_policy(), which checks CAP_MAC_ADMIN relative to the subject's own user namespace rather than the init user namespace, so with the default unprivileged_userns_apparmor_policy=1 the path is reachable from an unprivileged task in a matched-level nested namespace, not only by a globally privileged one. Perform the addition in u64 so that it cannot wrap, restoring the intended i + cnt < sets.size guarantee. Fixes: 3d28e2397af7 ("apparmor: add support loading per permission tagging") Signed-off-by: Fabrice Derepas Signed-off-by: John Johansen --- security/apparmor/policy_unpack.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 5969d78f16af..6b3b77dcbd69 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -731,7 +731,7 @@ static bool verify_tags(struct aa_tags_struct *tags, const char **info) /* count followed by count indexes into hdrs */ u32 cnt = tags->sets.table[i]; - if (i+cnt >= tags->sets.size) { + if ((u64)i + cnt >= tags->sets.size) { AA_DEBUG(DEBUG_UNPACK, "tagset too large %d+%d > sets.table[%d]", i, cnt, tags->sets.size); From fd22370226a0d8109045d0831fd5aaadee921693 Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Sun, 7 Jun 2026 07:06:46 -0700 Subject: [PATCH 0498/1328] SUNRPC: check rpc_sockaddr2uaddr() return value in rpcb_register_inet4/6 rpcb_register_inet4() and rpcb_register_inet6() store the result of rpc_sockaddr2uaddr() into map->r_addr without checking it for NULL. rpc_sockaddr2uaddr() returns NULL when its final kstrdup() fails, and the unchecked NULL is then carried into the synchronous RPCBPROC_SET encode path: rpcb_register_call() -> rpc_call_sync() -> rpcb_enc_getaddr() -> encode_rpcb_string(), whose first statement is strlen(string), dereferencing NULL and oopsing the kernel. The crash reproduces under failslab on v6.12; with KASAN the NULL dereference surfaces as a fault on the shadow of address zero: Oops: general protection fault, probably for non-canonical address 0xdffffc0000000000 [#1] PREEMPT SMP KASAN RIP: 0010:strlen (lib/string.c:409) Call Trace: encode_rpcb_string (net/sunrpc/rpcb_clnt.c:890) rpcb_enc_getaddr (net/sunrpc/rpcb_clnt.c:910) rpcauth_wrap_req_encode (net/sunrpc/auth.c:745) call_encode (net/sunrpc/clnt.c:1966) __rpc_execute (net/sunrpc/sched.c:952) rpc_run_task (net/sunrpc/clnt.c:1243) rpc_call_sync (net/sunrpc/clnt.c:1272) rpcb_v4_register (net/sunrpc/rpcb_clnt.c:500) svc_generic_rpcbind_set nfsd_rpcbind_set svc_register svc_setup_socket svc_addsock write_ports nfsctl_transaction_write vfs_write The crash is reachable when an in-kernel RPC service (nfsd, lockd, nfs-callback) registers with the local rpcbind under enough memory pressure for the small GFP_KERNEL kstrdup() in rpc_sockaddr2uaddr() to fail. The asynchronous getport path already handles this exact failure mode by returning -ENOMEM; only the two register helpers omit the check. Mirror that handling: bail out with -ENOMEM when rpc_sockaddr2uaddr() returns NULL, before the address is fed into the encoder. Fixes: d77385f23830 ("SUNRPC: Fix rpc_sockaddr2uaddr") Reported-by: Xiang Mei Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Weiming Shi Reviewed-by: Jeff Layton Signed-off-by: Trond Myklebust --- net/sunrpc/rpcb_clnt.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/sunrpc/rpcb_clnt.c b/net/sunrpc/rpcb_clnt.c index 6aa372188c86..4c0b7fefee4e 100644 --- a/net/sunrpc/rpcb_clnt.c +++ b/net/sunrpc/rpcb_clnt.c @@ -490,6 +490,8 @@ static int rpcb_register_inet4(struct sunrpc_net *sn, int result; map->r_addr = rpc_sockaddr2uaddr(sap, GFP_KERNEL); + if (!map->r_addr) + return -ENOMEM; msg->rpc_proc = &rpcb_procedures4[RPCBPROC_UNSET]; if (port != 0) { @@ -516,6 +518,8 @@ static int rpcb_register_inet6(struct sunrpc_net *sn, int result; map->r_addr = rpc_sockaddr2uaddr(sap, GFP_KERNEL); + if (!map->r_addr) + return -ENOMEM; msg->rpc_proc = &rpcb_procedures4[RPCBPROC_UNSET]; if (port != 0) { From 33930840b5f0a79f826e7c69dc6cd78f72a67481 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Thu, 11 Jun 2026 13:35:56 +0800 Subject: [PATCH 0499/1328] sunrpc: xprtsock: annotate shared socket callbacks with READ_ONCE/WRITE_ONCE xprtsock replaces and restores sk->sk_data_ready and sk->sk_write_space on live sockets with plain stores, and xs_udp_do_set_buffer_size() invokes sk->sk_write_space via a plain load. These callback pointers are shared with generic socket and protocol paths that may read or invoke them concurrently, so xprtsock needs the same READ_ONCE()/WRITE_ONCE() callback visibility contract that the validated 4022 family applied elsewhere. When SUNRPC takes over an AF_LOCAL, UDP, or TCP socket and later restores the lower-socket callbacks during teardown, another CPU may still hold an earlier callback snapshot. The plain replace/restore pattern leaves the same visibility hole as the validated 4022 family, so a stale snapshot can still invoke xs_data_ready() or xs_udp_write_space() after the live callback fields have already been restored to the lower-socket handlers. Use WRITE_ONCE() for the shared sk_data_ready and sk_write_space stores in xs_local_finish_connecting(), xs_udp_finish_connecting(), xs_tcp_finish_connecting(), and xs_restore_old_callbacks(). Use READ_ONCE() for the direct sk_write_space invocation in xs_udp_do_set_buffer_size(). This matches the required callback visibility contract while leaving adjacent sk_state_change and sk_error_report handling unchanged. Fixes: a246b0105bbd ("[PATCH] RPC: introduce client-side transport switch") Signed-off-by: Runyu Xiao Signed-off-by: Trond Myklebust --- net/sunrpc/xprtsock.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/net/sunrpc/xprtsock.c b/net/sunrpc/xprtsock.c index 359407aae03e..d735e6ec7e37 100644 --- a/net/sunrpc/xprtsock.c +++ b/net/sunrpc/xprtsock.c @@ -1202,9 +1202,9 @@ static void xs_save_old_callbacks(struct sock_xprt *transport, struct sock *sk) static void xs_restore_old_callbacks(struct sock_xprt *transport, struct sock *sk) { - sk->sk_data_ready = transport->old_data_ready; + WRITE_ONCE(sk->sk_data_ready, transport->old_data_ready); sk->sk_state_change = transport->old_state_change; - sk->sk_write_space = transport->old_write_space; + WRITE_ONCE(sk->sk_write_space, transport->old_write_space); sk->sk_error_report = transport->old_error_report; } @@ -1664,6 +1664,7 @@ static void xs_udp_do_set_buffer_size(struct rpc_xprt *xprt) { struct sock_xprt *transport = container_of(xprt, struct sock_xprt, xprt); struct sock *sk = transport->inet; + void (*write_space)(struct sock *sock); if (transport->rcvsize) { sk->sk_userlocks |= SOCK_RCVBUF_LOCK; @@ -1672,7 +1673,8 @@ static void xs_udp_do_set_buffer_size(struct rpc_xprt *xprt) if (transport->sndsize) { sk->sk_userlocks |= SOCK_SNDBUF_LOCK; sk->sk_sndbuf = transport->sndsize * xprt->max_reqs * 2; - sk->sk_write_space(sk); + write_space = READ_ONCE(sk->sk_write_space); + write_space(sk); } } @@ -1988,8 +1990,8 @@ static int xs_local_finish_connecting(struct rpc_xprt *xprt, xs_save_old_callbacks(transport, sk); sk->sk_user_data = xprt; - sk->sk_data_ready = xs_data_ready; - sk->sk_write_space = xs_udp_write_space; + WRITE_ONCE(sk->sk_data_ready, xs_data_ready); + WRITE_ONCE(sk->sk_write_space, xs_udp_write_space); sk->sk_state_change = xs_local_state_change; sk->sk_error_report = xs_error_report; sk->sk_use_task_frag = false; @@ -2191,8 +2193,8 @@ static void xs_udp_finish_connecting(struct rpc_xprt *xprt, struct socket *sock) xs_save_old_callbacks(transport, sk); sk->sk_user_data = xprt; - sk->sk_data_ready = xs_data_ready; - sk->sk_write_space = xs_udp_write_space; + WRITE_ONCE(sk->sk_data_ready, xs_data_ready); + WRITE_ONCE(sk->sk_write_space, xs_udp_write_space); sk->sk_use_task_frag = false; xprt_set_connected(xprt); @@ -2378,9 +2380,9 @@ static int xs_tcp_finish_connecting(struct rpc_xprt *xprt, struct socket *sock) xs_save_old_callbacks(transport, sk); sk->sk_user_data = xprt; - sk->sk_data_ready = xs_data_ready; + WRITE_ONCE(sk->sk_data_ready, xs_data_ready); sk->sk_state_change = xs_tcp_state_change; - sk->sk_write_space = xs_tcp_write_space; + WRITE_ONCE(sk->sk_write_space, xs_tcp_write_space); sk->sk_error_report = xs_error_report; sk->sk_use_task_frag = false; From 220af23d863995091f0edeb1e6aa0945b3db8b37 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Wed, 17 Jun 2026 08:52:57 -0400 Subject: [PATCH 0500/1328] NFS: Return a delegation the client fails to record When an NFS server grants a delegation in an OPEN reply, nfs_inode_set_delegation() records it on the client. However, three of its error flows return without sending DELEGRETURN. A delegation can be relinquished only by DELEGRETURN (RFC 8881 Section 20.2.4), so dropping one silently leaves the server believing the client still holds it. If the server happens to recall that delegation, the client answers CB_RECALL with NFS4ERR_BADHANDLE because it has no record of the stateid. The server revokes the delegation and moves it onto its cl_revoked list, because the client never sends the FREE_STATEID that would drain it. Every subsequent SEQUENCE reply then carries SEQ4_STATUS_RECALLABLE_STATE_REVOKED, and the client's state manager loops issuing TEST_STATEID across its delegations without ever clearing the condition. The window is easy to reach now that a server offers a write delegation on any write OPEN: a delegation recalled for one opener races a re-open that the server answers with a fresh write delegation. Instead of dropping it, hand the delegation back during these error flows. Fixes: ade04647dd56 ("NFSv4: Ensure we honour NFS_DELEGATION_RETURNING in nfs_inode_set_delegation()") Signed-off-by: Chuck Lever Signed-off-by: Trond Myklebust --- fs/nfs/delegation.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/fs/nfs/delegation.c b/fs/nfs/delegation.c index 9546d2195c25..284437fa3f87 100644 --- a/fs/nfs/delegation.c +++ b/fs/nfs/delegation.c @@ -447,11 +447,14 @@ int nfs_inode_set_delegation(struct inode *inode, const struct cred *cred, struct nfs_inode *nfsi = NFS_I(inode); struct nfs_delegation *delegation, *old_delegation; struct nfs_delegation *freeme = NULL; + bool orphaned = false; int status = 0; delegation = kmalloc_obj(*delegation, GFP_KERNEL_ACCOUNT); - if (delegation == NULL) + if (delegation == NULL) { + nfs4_proc_delegreturn(inode, cred, stateid, NULL, 0); return -ENOMEM; + } nfs4_stateid_copy(&delegation->stateid, stateid); refcount_set(&delegation->refcount, 1); delegation->type = type; @@ -500,11 +503,15 @@ int nfs_inode_set_delegation(struct inode *inode, const struct cred *cred, goto out; } if (test_and_set_bit(NFS_DELEGATION_RETURNING, - &old_delegation->flags)) + &old_delegation->flags)) { + orphaned = true; goto out; + } } - if (!nfs_detach_delegations_locked(nfsi, old_delegation, clp)) + if (!nfs_detach_delegations_locked(nfsi, old_delegation, clp)) { + orphaned = true; goto out; + } freeme = old_delegation; add_new: /* @@ -539,8 +546,11 @@ int nfs_inode_set_delegation(struct inode *inode, const struct cred *cred, nfs_update_delegated_mtime(inode); out: spin_unlock(&clp->cl_lock); - if (delegation != NULL) + if (delegation != NULL) { + if (orphaned) + nfs_do_return_delegation(inode, delegation, 0); __nfs_free_delegation(delegation); + } if (freeme != NULL) { nfs_do_return_delegation(inode, freeme, 0); nfs_mark_delegation_revoked(server, freeme); From 9f1e40193eef7f047e6b77cfb4b4cafdecd7a123 Mon Sep 17 00:00:00 2001 From: Hyunwoo Kim Date: Mon, 10 Aug 2026 18:51:33 +0900 Subject: [PATCH 0501/1328] apparmor: fix out-of-bounds write when null terminating a label vec aa_vec_unique() null terminates at vec[n - dups] when VEC_FLAG_TERMINATE is passed. If the components are all distinct no duplicates are dropped, dups is 0 and the terminator goes to vec[n], so the caller has to provide room for n + 1 entries. aa_label_strn_parse() sets up its vector with vec_setup(profile, vec, len, gfp) and then calls aa_vec_unique(vec, len, VEC_FLAG_TERMINATE), but vec_setup() does not reserve the terminator entry. Up to LOCAL_VEC_ENTRIES it uses the local array of LOCAL_VEC_ENTRIES pointers, above that it allocates exactly len pointers. The terminator therefore lands one entry past the end of the local array when len is LOCAL_VEC_ENTRIES, and one entry past the end of the allocation when len is larger. len comes from the number of "//&" separated components in the label name and label_count_strn_entries() does not bound it. An unprivileged task reaches the parse by writing to /proc/self/attr/apparmor/current or through lsm_set_self_attr(2), both of which go through do_setattr(), and the name is parsed before the change_profile permission is checked. The query_label() path behind the securityfs .access file, which is mode 0666, performs no permission check at all. Every component has to resolve to a loaded profile, so a system with policy loaded is required. The other two VEC_FLAG_TERMINATE users work on a label vec that aa_label_alloc() has already sized with "+ 1 for null terminator entry on vec". Reserve the same entry in vec_setup() and DEFINE_VEC(). Passing len + 1 from the caller instead would move len == LOCAL_VEC_ENTRIES out of the local array and into kzalloc(). Fixes: f1bd904175e8 ("apparmor: add the base fns() for domain labels") Cc: stable@vger.kernel.org Signed-off-by: Hyunwoo Kim Signed-off-by: John Johansen --- security/apparmor/include/label.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/security/apparmor/include/label.h b/security/apparmor/include/label.h index b5a722a47fd2..37cb135de323 100644 --- a/security/apparmor/include/label.h +++ b/security/apparmor/include/label.h @@ -23,7 +23,7 @@ struct aa_ruleset; #define LOCAL_VEC_ENTRIES 8 #define DEFINE_VEC(T, V) \ - struct aa_ ## T *(_ ## V ## _localtmp)[LOCAL_VEC_ENTRIES]; \ + struct aa_ ## T *(_ ## V ## _localtmp)[LOCAL_VEC_ENTRIES + 1]; \ struct aa_ ## T **(V) #define vec_setup(T, V, N, GFP) \ @@ -31,10 +31,10 @@ struct aa_ruleset; if ((N) <= LOCAL_VEC_ENTRIES) { \ typeof(N) i; \ (V) = (_ ## V ## _localtmp); \ - for (i = 0; i < (N); i++) \ + for (i = 0; i <= (N); i++) \ (V)[i] = NULL; \ } else \ - (V) = kzalloc(sizeof(struct aa_ ## T *) * (N), (GFP)); \ + (V) = kzalloc_objs(struct aa_ ## T *, (N) + 1, (GFP)); \ (V) ? 0 : -ENOMEM; \ }) From eae3faf210bdc69181be717ce90437eadc6b3c80 Mon Sep 17 00:00:00 2001 From: Daeho Jeong Date: Wed, 5 Aug 2026 18:48:13 -0700 Subject: [PATCH 0502/1328] f2fs: support dynamic reserve/release for device aliasing This patch adds a dynamic management feature to the existing device aliasing functionality. It allows users to dynamically reserve or release specific devices from the filesystem's free pool at runtime through new ioctls. To support this, three new ioctls are introduced: - F2FS_IOC_RESERVE_DEV_ALIAS: This reclaims the space occupied by a device aliasing file. It first performs a capacity check, resets GC victim information for the target range, marks the segments as in-use to prevent new allocations, and then triggers GC to migrate existing valid data out of the range. Finally, it reserves these blocks in the SIT to effectively exclude the device from the usable capacity. - F2FS_IOC_RELEASE_DEV_ALIAS: This releases the reserved space of a previously reserved device aliasing file. It truncates the blocks associated with the file, which makes them available for general filesystem allocation again. - F2FS_IOC_GET_DEV_ALIAS_STATUS: This retrieves the current aliasing status of a device aliasing file, returning whether the file is released (inactive alias) or reserved (active alias, with blocks fully allocated on the device). Signed-off-by: Daeho Jeong Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- Documentation/filesystems/f2fs.rst | 35 ++++ fs/f2fs/data.c | 4 +- fs/f2fs/extent_cache.c | 9 + fs/f2fs/f2fs.h | 19 +- fs/f2fs/file.c | 273 ++++++++++++++++++++++++++++- fs/f2fs/gc.c | 30 ++-- fs/f2fs/namei.c | 14 ++ fs/f2fs/segment.c | 178 +++++++++++++------ fs/f2fs/segment.h | 22 +++ fs/f2fs/super.c | 36 ++++ include/uapi/linux/f2fs.h | 7 + 11 files changed, 558 insertions(+), 69 deletions(-) diff --git a/Documentation/filesystems/f2fs.rst b/Documentation/filesystems/f2fs.rst index 8c4a14ae444f..1a5fd4afe609 100644 --- a/Documentation/filesystems/f2fs.rst +++ b/Documentation/filesystems/f2fs.rst @@ -1045,6 +1045,41 @@ So, the key idea is, user can do any file operations on /dev/vdc, and reclaim the space after the use, while the space is counted as /data. That doesn't require modifying partition size and filesystem format. +Dynamic Device Aliasing Management +---------------------------------- + +In addition to static device aliasing by deleting the aliasing file, F2FS +supports dynamic management of device aliasing. This mechanism allows the system +to dynamically transition partition ownership between F2FS userdata and external +entities (e.g., zRAM, raw partition) based on system requirements without +deleting the master aliasing file or requiring unmount/remount. + +The master aliasing file is created during the initial format of the file system +and remains as a persistent control entity (ioctl gateway) in the root directory. + +- Partition Reservation (In-service to Aliased) + When a specific partition needs to be dedicated to external services (e.g., zRAM), + a user can reserve the device alias range via ioctl. The kernel resets GC victim + information for the target range, marks segments as in-use to prevent new + allocations, and triggers forced GC to migrate existing valid data out of the + range. Finally, it reserves these blocks in the SIT to effectively exclude the + device from the usable capacity. + +- Partition Release (Aliased to In-service) + When external usage concludes, the space is reclaimed not by deleting the file, + but through the release ioctl. The kernel truncates blocks associated with + the file, releasing them back to general filesystem allocation. + +.. code-block:: + + # f2fs_io dev_alias release /mnt/f2fs/vdc.file + # df -h + /dev/vdb 64G 753M 64G 2% /mnt/f2fs + + # f2fs_io dev_alias reserve /mnt/f2fs/vdc.file + # df -h + /dev/vdb 64G 33G 32G 52% /mnt/f2fs + Per-file Read-Only Large Folio Support -------------------------------------- diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c index 30e8084da313..c219ea76a3a7 100644 --- a/fs/f2fs/data.c +++ b/fs/f2fs/data.c @@ -1270,7 +1270,7 @@ int f2fs_reserve_new_blocks(struct dnode_of_data *dn, blkcnt_t count) if (unlikely(is_inode_flag_set(dn->inode, FI_NO_ALLOC))) return -EPERM; - err = inc_valid_block_count(sbi, dn->inode, &count, true); + err = inc_valid_block_count(sbi, dn->inode, &count, true, false); if (unlikely(err)) return err; @@ -1543,7 +1543,7 @@ static int __allocate_data_block(struct dnode_of_data *dn, int seg_type) dn->data_blkaddr = f2fs_data_blkaddr(dn); if (dn->data_blkaddr == NULL_ADDR) { - err = inc_valid_block_count(sbi, dn->inode, &count, true); + err = inc_valid_block_count(sbi, dn->inode, &count, true, false); if (unlikely(err)) return err; } diff --git a/fs/f2fs/extent_cache.c b/fs/f2fs/extent_cache.c index 61f6b9714366..37cf9fa8d537 100644 --- a/fs/f2fs/extent_cache.c +++ b/fs/f2fs/extent_cache.c @@ -17,6 +17,7 @@ #include "f2fs.h" #include "node.h" +#include "segment.h" #include bool sanity_check_extent_cache(struct inode *inode, struct folio *ifolio) @@ -62,6 +63,14 @@ bool sanity_check_extent_cache(struct inode *inode, struct folio *ifolio) __func__, inode->i_ino, ei.blk, ei.fofs, ei.len); return false; } + + if ((GET_SEGOFF_FROM_SEG0(sbi, ei.blk) % BLKS_PER_SEC(sbi)) || + (ei.len % BLKS_PER_SEC(sbi))) { + f2fs_warn(sbi, "%s: device alias inode (ino=%llx)'s extent info [%u, %u, %u] is not aligned to section size %u", + __func__, inode->i_ino, ei.blk, ei.fofs, ei.len, + BLKS_PER_SEC(sbi)); + return false; + } return true; } diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index 8e2fb0bda467..a380b8be8819 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -1404,6 +1404,8 @@ struct f2fs_dev_info { unsigned int total_segments; block_t start_blk; block_t end_blk; + bool has_alias; + bool is_reserving; #ifdef CONFIG_BLK_DEV_ZONED unsigned int nr_blkz; /* Total number of zones */ unsigned long *blkz_seq; /* Bitmap indicating sequential zones */ @@ -1884,6 +1886,7 @@ struct f2fs_sb_info { block_t last_valid_block_count; /* for recovery */ block_t reserved_blocks; /* configurable reserved blocks */ block_t current_reserved_blocks; /* current reserved blocks */ + block_t alias_reserved_blocks; /* reserved blocks for device alias */ /* Additional tracking for no checkpoint mode */ block_t unusable_block_count; /* # of blocks saved by last cp */ @@ -2586,7 +2589,8 @@ static inline unsigned int get_available_block_count(struct f2fs_sb_info *sbi, block_t avail_user_block_count; avail_user_block_count = sbi->user_block_count - - sbi->current_reserved_blocks; + sbi->current_reserved_blocks - + sbi->alias_reserved_blocks; if (test_opt(sbi, RESERVE_ROOT) && !__allow_reserved_root(sbi, inode, cap)) avail_user_block_count -= F2FS_OPTION(sbi).root_reserved_blocks; @@ -2603,7 +2607,8 @@ static inline unsigned int get_available_block_count(struct f2fs_sb_info *sbi, static inline void f2fs_i_blocks_write(struct inode *, block_t, bool, bool); static inline int inc_valid_block_count(struct f2fs_sb_info *sbi, - struct inode *inode, blkcnt_t *count, bool partial) + struct inode *inode, blkcnt_t *count, + bool partial, bool alias_reserved) { long long diff = 0, release = 0; block_t avail_user_block_count; @@ -2626,10 +2631,16 @@ static inline int inc_valid_block_count(struct f2fs_sb_info *sbi, spin_lock(&sbi->stat_lock); + if (alias_reserved) + sbi->alias_reserved_blocks -= *count; + avail_user_block_count = get_available_block_count(sbi, inode, true); diff = (long long)sbi->total_valid_block_count + *count - avail_user_block_count; if (unlikely(diff > 0)) { + if (alias_reserved) + sbi->alias_reserved_blocks += *count; + if (!partial) { spin_unlock(&sbi->stat_lock); release = *count; @@ -4037,6 +4048,8 @@ int f2fs_flush_device_cache(struct f2fs_sb_info *sbi); void f2fs_destroy_flush_cmd_control(struct f2fs_sb_info *sbi, bool free); void f2fs_invalidate_blocks(struct f2fs_sb_info *sbi, block_t addr, unsigned int len); +void f2fs_reserve_device_alias(struct f2fs_sb_info *sbi, block_t addr, + unsigned int len); bool f2fs_is_checkpointed_data(struct f2fs_sb_info *sbi, block_t blkaddr); int f2fs_start_discard_thread(struct f2fs_sb_info *sbi); void f2fs_drop_discard_cmd(struct f2fs_sb_info *sbi); @@ -4258,6 +4271,8 @@ void f2fs_build_gc_manager(struct f2fs_sb_info *sbi); int f2fs_gc_range(struct f2fs_sb_info *sbi, unsigned int start_seg, unsigned int end_seg, bool dry_run, unsigned int dry_run_sections); +void f2fs_reset_gc_victim_resource(struct f2fs_sb_info *sbi, + unsigned int start, unsigned int end); int f2fs_resize_fs(struct file *filp, __u64 block_count); int __init f2fs_create_garbage_collection_cache(void); void f2fs_destroy_garbage_collection_cache(void); diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index b99d9cdf9ba7..56529a82e027 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -813,13 +813,19 @@ int f2fs_do_truncate_blocks(struct inode *inode, u64 from, bool lock) if (IS_DEVICE_ALIASING(inode)) { struct extent_tree *et = F2FS_I(inode)->extent_tree[EX_READ]; - struct extent_info ei = et->largest; + struct extent_info ei; + + read_lock(&et->lock); + ei = et->largest; + read_unlock(&et->lock); f2fs_invalidate_blocks(sbi, ei.blk, ei.len); dec_valid_block_count(sbi, inode, ei.len); f2fs_update_time(sbi, REQ_TIME); + f2fs_drop_extent_tree(inode); + f2fs_folio_put(ifolio, true); goto out; } @@ -1100,8 +1106,9 @@ int f2fs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, if ((attr->ia_valid & ATTR_SIZE)) { if (mapping_large_folio_support(inode->i_mapping)) return -EOPNOTSUPP; - if (!f2fs_is_compress_backend_ready(inode) || - IS_DEVICE_ALIASING(inode)) + if (IS_DEVICE_ALIASING(inode)) + return -EPERM; + if (!f2fs_is_compress_backend_ready(inode)) return -EOPNOTSUPP; if (is_inode_flag_set(inode, FI_COMPRESS_RELEASED) && !IS_ALIGNED(attr->ia_size, @@ -2139,6 +2146,9 @@ static int f2fs_setflags_common(struct inode *inode, u32 iflags, u32 mask) if (IS_NOQUOTA(inode)) return -EPERM; + if (IS_DEVICE_ALIASING(inode)) + return -EPERM; + if ((iflags ^ masked_flags) & F2FS_CASEFOLD_FL) { if (!f2fs_sb_has_casefold(F2FS_I_SB(inode))) return -EOPNOTSUPP; @@ -2687,6 +2697,17 @@ static int f2fs_ioc_get_encryption_policy(struct file *filp, unsigned long arg) return fscrypt_ioctl_get_policy(filp, (void __user *)arg); } +static int f2fs_ioc_get_dev_alias_status(struct file *filp, unsigned long arg) +{ + struct inode *inode = file_inode(filp); + + if (!IS_DEVICE_ALIASING(inode)) + return -EINVAL; + + return put_user(F2FS_HAS_BLOCKS(inode) ? F2FS_DEV_ALIAS_STATUS_RESERVED : + F2FS_DEV_ALIAS_STATUS_RELEASED, (u32 __user *)arg); +} + static int f2fs_ioc_get_encryption_pwsalt(struct file *filp, unsigned long arg) { struct inode *inode = file_inode(filp); @@ -3637,6 +3658,241 @@ static int f2fs_ioc_get_dev_alias_file(struct file *filp, unsigned long arg) (u32 __user *)arg); } +static bool f2fs_get_dev_alias_extent(struct f2fs_sb_info *sbi, + struct dentry *dentry, + struct extent_info *ei) +{ + int i; + + for (i = 1; i < sbi->s_ndevs; i++) { + char *name = strrchr(FDEV(i).path, '/'); + + name = name ? name + 1 : FDEV(i).path; + if (strcmp(name, dentry->d_name.name)) + continue; + + ei->blk = FDEV(i).start_blk; + ei->len = FDEV(i).total_segments << sbi->log_blocks_per_seg; + ei->fofs = 0; + return true; + } + return false; +} + +static int f2fs_ioc_reserve_dev_alias(struct file *filp) +{ + struct inode *inode = file_inode(filp); + struct f2fs_sb_info *sbi = F2FS_I_SB(inode); + struct extent_tree *et = F2FS_I(inode)->extent_tree[EX_READ]; + struct extent_info ei; + struct cp_control cpc = { CP_SYNC, 0, 0, 0 }; + struct f2fs_lock_context lc, glc; + blkcnt_t count; + unsigned int start, end; + int type, err; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (unlikely(is_sbi_flag_set(sbi, SBI_CP_DISABLED))) + return -EINVAL; + + err = mnt_want_write_file(filp); + if (err) + return err; + + inode_lock(inode); + + if (!IS_DEVICE_ALIASING(inode)) { + err = -EINVAL; + goto out_inode_unlock; + } + + if (F2FS_HAS_BLOCKS(inode)) { + err = 0; + goto out_inode_unlock; + } + + if (!f2fs_get_dev_alias_extent(sbi, filp->f_path.dentry, &ei)) { + f2fs_warn(sbi, "device alias file (%s, ino=%llu) has no matching device", + filp->f_path.dentry->d_name.name, + (unsigned long long)inode->i_ino); + set_sbi_flag(sbi, SBI_NEED_FSCK); + f2fs_handle_error(sbi, ERROR_CORRUPTED_INODE); + err = -EFSCORRUPTED; + goto out_inode_unlock; + } + + spin_lock(&sbi->stat_lock); + if (sbi->total_valid_block_count + ei.len > + get_available_block_count(sbi, inode, true)) { + spin_unlock(&sbi->stat_lock); + err = -ENOSPC; + goto out_inode_unlock; + } + sbi->alias_reserved_blocks += ei.len; + spin_unlock(&sbi->stat_lock); + + spin_lock(&FREE_I(sbi)->segmap_lock); + FDEV(f2fs_target_device_index(sbi, ei.blk)).is_reserving = true; + spin_unlock(&FREE_I(sbi)->segmap_lock); + + start = GET_SEGNO(sbi, ei.blk); + end = GET_SEGNO(sbi, ei.blk + ei.len - 1); + + /* Acquire gc_lock for victim reset, curseg resize, and range GC */ + f2fs_down_write_trace(&sbi->gc_lock, &glc); + + /* Reset the victim information to prevent GC from targeting the range */ + f2fs_reset_gc_victim_resource(sbi, start, end); + + /* Move out cursegs from the target range */ + for (type = CURSEG_HOT_DATA; type < NR_CURSEG_PERSIST_TYPE; type++) { + err = f2fs_allocate_segment_for_resize(sbi, type, start, end); + if (err) + goto out_gc_unlock; + } + + f2fs_lock_op(sbi, &lc); + + if (unlikely(is_sbi_flag_set(sbi, SBI_CP_DISABLED))) { + err = -EINVAL; + f2fs_unlock_op(sbi, &lc); + goto out_gc_unlock; + } + + /* do GC to move out valid blocks in the range all at once! */ + err = f2fs_gc_range(sbi, start, end, false, 0); + if (err) { + f2fs_unlock_op(sbi, &lc); + goto out_gc_unlock; + } + + count = ei.len; + err = inc_valid_block_count(sbi, inode, &count, false, true); + if (err) { + f2fs_unlock_op(sbi, &lc); + goto out_gc_unlock; + } + + write_lock(&et->lock); + et->largest = ei; + write_unlock(&et->lock); + clear_inode_flag(inode, FI_NO_EXTENT); + + f2fs_reserve_device_alias(sbi, ei.blk, ei.len); + + i_size_write(inode, (loff_t)ei.len << sbi->log_blocksize); + f2fs_update_inode_page(inode); + + spin_lock(&FREE_I(sbi)->segmap_lock); + FDEV(f2fs_target_device_index(sbi, ei.blk)).is_reserving = false; + spin_unlock(&FREE_I(sbi)->segmap_lock); + + f2fs_unlock_op(sbi, &lc); + f2fs_up_write_trace(&sbi->gc_lock, &glc); + + inode_unlock(inode); + mnt_drop_write_file(filp); + + return f2fs_write_checkpoint(sbi, &cpc); + +out_gc_unlock: + spin_lock(&sbi->stat_lock); + sbi->alias_reserved_blocks -= ei.len; + spin_unlock(&sbi->stat_lock); + + spin_lock(&FREE_I(sbi)->segmap_lock); + FDEV(f2fs_target_device_index(sbi, ei.blk)).is_reserving = false; + spin_unlock(&FREE_I(sbi)->segmap_lock); + f2fs_up_write_trace(&sbi->gc_lock, &glc); + +out_inode_unlock: + inode_unlock(inode); + mnt_drop_write_file(filp); + return err; +} + +static int f2fs_ioc_release_dev_alias(struct file *filp) +{ + struct inode *inode = file_inode(filp); + struct f2fs_sb_info *sbi = F2FS_I_SB(inode); + struct extent_tree *et = F2FS_I(inode)->extent_tree[EX_READ]; + struct extent_info ei = {0, }; + struct cp_control cpc = { CP_SYNC, 0, 0, 0 }; + struct f2fs_lock_context lc, glc; + int err; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (unlikely(is_sbi_flag_set(sbi, SBI_CP_DISABLED))) + return -EINVAL; + + err = mnt_want_write_file(filp); + if (err) + return err; + + inode_lock(inode); + + if (!IS_DEVICE_ALIASING(inode)) { + err = -EINVAL; + goto out_inode_unlock; + } + + if (!F2FS_HAS_BLOCKS(inode)) { + err = 0; + goto out_inode_unlock; + } + + err = filemap_write_and_wait(inode->i_mapping); + if (err) + goto out_inode_unlock; + + read_lock(&et->lock); + ei = et->largest; + read_unlock(&et->lock); + + f2fs_down_write_trace(&sbi->gc_lock, &glc); + f2fs_lock_op(sbi, &lc); + + if (unlikely(is_sbi_flag_set(sbi, SBI_CP_DISABLED))) { + err = -EINVAL; + f2fs_unlock_op(sbi, &lc); + f2fs_up_write_trace(&sbi->gc_lock, &glc); + goto out_inode_unlock; + } + + filemap_invalidate_lock(inode->i_mapping); + truncate_setsize(inode, 0); + + err = f2fs_truncate_blocks(inode, 0, false); + if (err) + i_size_write(inode, (loff_t)ei.len << sbi->log_blocksize); + filemap_invalidate_unlock(inode->i_mapping); + + if (err) { + f2fs_unlock_op(sbi, &lc); + f2fs_up_write_trace(&sbi->gc_lock, &glc); + goto out_inode_unlock; + } + + f2fs_update_inode_page(inode); + + f2fs_unlock_op(sbi, &lc); + f2fs_up_write_trace(&sbi->gc_lock, &glc); + + inode_unlock(inode); + mnt_drop_write_file(filp); + + return f2fs_write_checkpoint(sbi, &cpc); + +out_inode_unlock: + inode_unlock(inode); + mnt_drop_write_file(filp); + return err; +} + static int f2fs_ioc_io_prio(struct file *filp, unsigned long arg) { struct inode *inode = file_inode(filp); @@ -4062,7 +4318,7 @@ static int reserve_compress_blocks(struct dnode_of_data *dn, pgoff_t count, } ret = inc_valid_block_count(sbi, dn->inode, - &to_reserved, false); + &to_reserved, false, false); if (unlikely(ret)) return ret; @@ -4763,8 +5019,14 @@ static long __f2fs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) return f2fs_ioc_compress_file(filp); case F2FS_IOC_GET_DEV_ALIAS_FILE: return f2fs_ioc_get_dev_alias_file(filp, arg); + case F2FS_IOC_GET_DEV_ALIAS_STATUS: + return f2fs_ioc_get_dev_alias_status(filp, arg); case F2FS_IOC_IO_PRIO: return f2fs_ioc_io_prio(filp, arg); + case F2FS_IOC_RESERVE_DEV_ALIAS: + return f2fs_ioc_reserve_dev_alias(filp); + case F2FS_IOC_RELEASE_DEV_ALIAS: + return f2fs_ioc_release_dev_alias(filp); default: return -ENOTTY; } @@ -5551,7 +5813,10 @@ long f2fs_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) case F2FS_IOC_DECOMPRESS_FILE: case F2FS_IOC_COMPRESS_FILE: case F2FS_IOC_GET_DEV_ALIAS_FILE: + case F2FS_IOC_GET_DEV_ALIAS_STATUS: case F2FS_IOC_IO_PRIO: + case F2FS_IOC_RESERVE_DEV_ALIAS: + case F2FS_IOC_RELEASE_DEV_ALIAS: break; default: return -ENOIOCTLCMD; diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c index d04633f872ef..51fc8c69eb25 100644 --- a/fs/f2fs/gc.c +++ b/fs/f2fs/gc.c @@ -2191,29 +2191,37 @@ int f2fs_gc_range(struct f2fs_sb_info *sbi, return 0; } +void f2fs_reset_gc_victim_resource(struct f2fs_sb_info *sbi, + unsigned int start, unsigned int end) +{ + int i; + + mutex_lock(&DIRTY_I(sbi)->seglist_lock); + for (i = 0; i < MAX_GC_POLICY; i++) + if (SIT_I(sbi)->last_victim[i] >= start && + SIT_I(sbi)->last_victim[i] <= end) + SIT_I(sbi)->last_victim[i] = 0; + + for (i = BG_GC; i <= FG_GC; i++) + if (sbi->next_victim_seg[i] >= start && + sbi->next_victim_seg[i] <= end) + sbi->next_victim_seg[i] = NULL_SEGNO; + mutex_unlock(&DIRTY_I(sbi)->seglist_lock); +} + static int free_segment_range(struct f2fs_sb_info *sbi, unsigned int secs, bool dry_run) { unsigned int next_inuse, start, end; struct cp_control cpc = { CP_RESIZE, 0, 0, 0 }; - int gc_mode, gc_type; int err = 0; int type; - /* Force block allocation for GC */ MAIN_SECS(sbi) -= secs; start = MAIN_SECS(sbi) * SEGS_PER_SEC(sbi); end = MAIN_SEGS(sbi) - 1; - mutex_lock(&DIRTY_I(sbi)->seglist_lock); - for (gc_mode = 0; gc_mode < MAX_GC_POLICY; gc_mode++) - if (SIT_I(sbi)->last_victim[gc_mode] >= start) - SIT_I(sbi)->last_victim[gc_mode] = 0; - - for (gc_type = BG_GC; gc_type <= FG_GC; gc_type++) - if (sbi->next_victim_seg[gc_type] >= start) - sbi->next_victim_seg[gc_type] = NULL_SEGNO; - mutex_unlock(&DIRTY_I(sbi)->seglist_lock); + f2fs_reset_gc_victim_resource(sbi, start, end); /* Move out cursegs from the target range */ for (type = CURSEG_HOT_DATA; type < NR_CURSEG_PERSIST_TYPE; type++) { diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c index 7ffdf23cea5e..784f636244e1 100644 --- a/fs/f2fs/namei.c +++ b/fs/f2fs/namei.c @@ -425,6 +425,9 @@ static int f2fs_link(struct dentry *old_dentry, struct inode *dir, if (!f2fs_is_checkpoint_ready(sbi)) return -ENOSPC; + if (IS_DEVICE_ALIASING(inode)) + return -EPERM; + err = fscrypt_prepare_link(old_dentry, dir, dentry); if (err) return err; @@ -568,6 +571,9 @@ static int f2fs_unlink(struct inode *dir, struct dentry *dentry) trace_f2fs_unlink_enter(dir, dentry); + if (IS_DEVICE_ALIASING(inode)) + return -EPERM; + if (unlikely(f2fs_cp_error(sbi))) { err = -EIO; goto out; @@ -946,6 +952,9 @@ static int f2fs_rename(struct mnt_idmap *idmap, struct inode *old_dir, bool old_is_dir = S_ISDIR(old_inode->i_mode); int err; + if (IS_DEVICE_ALIASING(old_inode)) + return -EPERM; + if (unlikely(f2fs_cp_error(sbi))) return -EIO; if (!f2fs_is_checkpoint_ready(sbi)) @@ -1016,6 +1025,8 @@ static int f2fs_rename(struct mnt_idmap *idmap, struct inode *old_dir, } if (new_inode) { + if (IS_DEVICE_ALIASING(new_inode)) + return -EPERM; err = -ENOTEMPTY; if (old_is_dir && !f2fs_empty_dir(new_inode)) @@ -1143,6 +1154,9 @@ static int f2fs_cross_rename(struct inode *old_dir, struct dentry *old_dentry, int old_nlink = 0, new_nlink = 0; int err; + if (IS_DEVICE_ALIASING(old_inode) || IS_DEVICE_ALIASING(new_inode)) + return -EPERM; + if (unlikely(f2fs_cp_error(sbi))) return -EIO; if (!f2fs_is_checkpoint_ready(sbi)) diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c index 00cc2af45ff9..b81245ddd490 100644 --- a/fs/f2fs/segment.c +++ b/fs/f2fs/segment.c @@ -261,7 +261,7 @@ static int __replace_atomic_write_block(struct inode *inode, pgoff_t index, } else { blkcnt_t count = 1; - err = inc_valid_block_count(sbi, inode, &count, true); + err = inc_valid_block_count(sbi, inode, &count, true, false); if (err) { f2fs_put_dnode(&dn); return err; @@ -2539,35 +2539,42 @@ static int update_sit_entry_for_alloc(struct f2fs_sb_info *sbi, struct seg_entry unsigned int segno, block_t blkaddr, unsigned int offset, int del) { bool exist; + int del_count = del; + int i; - exist = f2fs_test_and_set_bit(offset, se->cur_valid_map); - if (unlikely(exist)) { - f2fs_err(sbi, "Bitmap was wrongly set, blk:%u", blkaddr); - f2fs_bug_on(sbi, 1); - se->valid_blocks--; - del = 0; - } + f2fs_bug_on(sbi, GET_SEGNO(sbi, blkaddr) != GET_SEGNO(sbi, blkaddr + del_count - 1)); - if (f2fs_block_unit_discard(sbi) && - !f2fs_test_and_set_bit(offset, se->discard_map)) - sbi->discard_blks--; - - /* - * SSR should never reuse block which is checkpointed - * or newly invalidated. - */ - if (!is_sbi_flag_set(sbi, SBI_CP_DISABLED)) { - if (!f2fs_test_and_set_bit(offset, se->ckpt_valid_map)) { - se->ckpt_valid_blocks++; - if (__is_large_section(sbi)) - get_sec_entry(sbi, segno)->ckpt_valid_blocks++; + for (i = 0; i < del_count; i++) { + exist = f2fs_test_and_set_bit(offset + i, se->cur_valid_map); + if (unlikely(exist)) { + f2fs_err(sbi, "Bitmap was wrongly set, blk:%u", blkaddr + i); + f2fs_bug_on(sbi, 1); + se->valid_blocks--; + del -= 1; + continue; } - } - if (!f2fs_test_bit(offset, se->ckpt_valid_map)) { - se->ckpt_valid_blocks += del; - if (__is_large_section(sbi)) - get_sec_entry(sbi, segno)->ckpt_valid_blocks += del; + if (f2fs_block_unit_discard(sbi) && + !f2fs_test_and_set_bit(offset + i, se->discard_map)) + sbi->discard_blks--; + + /* + * SSR should never reuse block which is checkpointed + * or newly invalidated. + */ + if (!is_sbi_flag_set(sbi, SBI_CP_DISABLED)) { + if (!f2fs_test_and_set_bit(offset + i, se->ckpt_valid_map)) { + se->ckpt_valid_blocks++; + if (__is_large_section(sbi)) + get_sec_entry(sbi, segno)->ckpt_valid_blocks++; + } + } + + if (!f2fs_test_bit(offset + i, se->ckpt_valid_map)) { + se->ckpt_valid_blocks += 1; + if (__is_large_section(sbi)) + get_sec_entry(sbi, segno)->ckpt_valid_blocks += 1; + } } if (__is_large_section(sbi)) @@ -2622,9 +2629,14 @@ void f2fs_invalidate_blocks(struct f2fs_sb_info *sbi, block_t addr, unsigned int segno = GET_SEGNO(sbi, addr); struct sit_info *sit_i = SIT_I(sbi); block_t addr_start = addr, addr_end = addr + len - 1; - unsigned int seg_num = GET_SEGNO(sbi, addr_end) - segno + 1; + unsigned int seg_num; unsigned int i = 1, max_blocks = sbi->blocks_per_seg, cnt; + if (len == 0) + return; + + seg_num = GET_SEGNO(sbi, addr_end) - segno + 1; + f2fs_bug_on(sbi, addr == NULL_ADDR); if (addr == NEW_ADDR || addr == COMPRESS_ADDR) return; @@ -2657,6 +2669,52 @@ void f2fs_invalidate_blocks(struct f2fs_sb_info *sbi, block_t addr, up_write(&sit_i->sentry_lock); } +void f2fs_reserve_device_alias(struct f2fs_sb_info *sbi, block_t addr, + unsigned int len) +{ + unsigned int segno = GET_SEGNO(sbi, addr); + struct sit_info *sit_i = SIT_I(sbi); + block_t addr_start = addr, addr_end = addr + len - 1; + unsigned int seg_num; + unsigned int i = 1, max_blocks = sbi->blocks_per_seg, cnt; + + if (len == 0) + return; + + seg_num = GET_SEGNO(sbi, addr_end) - segno + 1; + + down_write(&sit_i->sentry_lock); + + if (seg_num == 1) + cnt = len; + else + cnt = max_blocks - GET_BLKOFF_FROM_SEG0(sbi, addr); + + do { + update_segment_mtime(sbi, addr_start, 0); + update_sit_entry(sbi, addr_start, cnt); + __set_test_and_inuse(sbi, segno); + + /* Remove the segment from PRE (prefree) to prevent checkpoint from freeing it! */ + mutex_lock(&DIRTY_I(sbi)->seglist_lock); + if (test_and_clear_bit(segno, DIRTY_I(sbi)->dirty_segmap[PRE])) + DIRTY_I(sbi)->nr_dirty[PRE]--; + mutex_unlock(&DIRTY_I(sbi)->seglist_lock); + + /* add it into dirty seglist */ + locate_dirty_segment(sbi, segno); + + /* update @addr_start and @cnt and @segno */ + addr_start = START_BLOCK(sbi, ++segno); + if (++i == seg_num) + cnt = GET_BLKOFF_FROM_SEG0(sbi, addr_end) + 1; + else + cnt = max_blocks; + } while (i <= seg_num); + + up_write(&sit_i->sentry_lock); +} + bool f2fs_is_checkpointed_data(struct f2fs_sb_info *sbi, block_t blkaddr) { struct sit_info *sit_i = SIT_I(sbi); @@ -2795,8 +2853,13 @@ static int is_next_segment_free(struct f2fs_sb_info *sbi, unsigned int segno = curseg->segno + 1; struct free_segmap_info *free_i = FREE_I(sbi); - if (segno < MAIN_SEGS(sbi) && segno % SEGS_PER_SEC(sbi)) + if (segno < MAIN_SEGS(sbi) && segno % SEGS_PER_SEC(sbi)) { + int devi = f2fs_target_device_index(sbi, START_BLOCK(sbi, segno)); + + if (f2fs_dev_is_reserving(sbi, devi)) + return 0; return !test_bit(segno, free_i->free_segmap); + } return 0; } @@ -2815,7 +2878,8 @@ static int get_new_segment(struct f2fs_sb_info *sbi, unsigned int alloc_policy = sbi->allocate_section_policy; unsigned int alloc_hint = sbi->allocate_section_hint; bool init = true; - int i; + bool looped = false; + int i, devi; int ret = 0; spin_lock(&free_i->segmap_lock); @@ -2828,8 +2892,13 @@ static int get_new_segment(struct f2fs_sb_info *sbi, if (!new_sec && ((*newseg + 1) % SEGS_PER_SEC(sbi))) { segno = find_next_zero_bit(free_i->free_segmap, GET_SEG_FROM_SEC(sbi, hint + 1), *newseg + 1); - if (segno < GET_SEG_FROM_SEC(sbi, hint + 1)) + if (segno < GET_SEG_FROM_SEC(sbi, hint + 1)) { + devi = f2fs_target_device_index(sbi, START_BLOCK(sbi, segno)); + + if (f2fs_dev_is_alloc_blocked(sbi, devi, pinning)) + goto find_other_zone; goto got_it; + } } #ifdef CONFIG_BLK_DEV_ZONED @@ -2865,33 +2934,42 @@ static int get_new_segment(struct f2fs_sb_info *sbi, find_other_zone: secno = find_next_zero_bit(free_i->free_secmap, MAIN_SECS(sbi), hint); -#ifdef CONFIG_BLK_DEV_ZONED - if (secno >= MAIN_SECS(sbi) && f2fs_sb_has_blkzoned(sbi)) { - /* Write only to sequential zones */ - if (sbi->blkzone_alloc_policy == BLKZONE_ALLOC_ONLY_SEQ) { - hint = GET_SEC_FROM_SEG(sbi, sbi->first_seq_zone_segno); - secno = find_next_zero_bit(free_i->free_secmap, MAIN_SECS(sbi), hint); - } else - secno = find_first_zero_bit(free_i->free_secmap, - MAIN_SECS(sbi)); - if (secno >= MAIN_SECS(sbi)) { - ret = -ENOSPC; - f2fs_bug_on(sbi, 1); - goto out_unlock; - } - } -#endif - if (secno >= MAIN_SECS(sbi)) { - secno = find_first_zero_bit(free_i->free_secmap, - MAIN_SECS(sbi)); - if (secno >= MAIN_SECS(sbi)) { + if (looped) { ret = -ENOSPC; f2fs_bug_on(sbi, !pinning); goto out_unlock; } + hint = 0; +#ifdef CONFIG_BLK_DEV_ZONED + /* Write only to sequential zones */ + if (f2fs_sb_has_blkzoned(sbi) && + sbi->blkzone_alloc_policy == BLKZONE_ALLOC_ONLY_SEQ) + hint = GET_SEC_FROM_SEG(sbi, sbi->first_seq_zone_segno); +#endif + looped = true; + goto find_other_zone; } + segno = GET_SEG_FROM_SEC(sbi, secno); + + devi = f2fs_target_device_index(sbi, START_BLOCK(sbi, segno)); + + if (f2fs_dev_is_alloc_blocked(sbi, devi, pinning)) { + while (devi < sbi->s_ndevs && + f2fs_dev_is_alloc_blocked(sbi, devi, pinning)) { + unsigned int end_segno = GET_SEGNO(sbi, FDEV(devi).end_blk); + + hint = GET_SEC_FROM_SEG(sbi, end_segno) + 1; + devi++; + } + goto find_other_zone; + } + + if (sec_usage_check(sbi, secno)) { + hint = secno + 1; + goto find_other_zone; + } zoneno = GET_ZONE_FROM_SEC(sbi, secno); /* give up on finding another zone */ diff --git a/fs/f2fs/segment.h b/fs/f2fs/segment.h index 33a2257da1e6..db1079169a23 100644 --- a/fs/f2fs/segment.h +++ b/fs/f2fs/segment.h @@ -940,10 +940,32 @@ static inline block_t sum_blk_addr(struct f2fs_sb_info *sbi, int base, int type) - (base + 1) + type; } +static inline bool f2fs_dev_is_reserving(struct f2fs_sb_info *sbi, int devi) +{ + if (!f2fs_sb_has_device_alias(sbi)) + return false; + return FDEV(devi).is_reserving; +} + +static inline bool f2fs_dev_is_alloc_blocked(struct f2fs_sb_info *sbi, + int devi, bool pinning) +{ + if (!f2fs_sb_has_device_alias(sbi)) + return false; + return (pinning && FDEV(devi).has_alias) || FDEV(devi).is_reserving; +} + static inline bool sec_usage_check(struct f2fs_sb_info *sbi, unsigned int secno) { if (is_cursec(sbi, secno) || (sbi->cur_victim_sec == secno)) return true; + if (f2fs_sb_has_device_alias(sbi)) { + block_t start_blk = START_BLOCK(sbi, GET_SEG_FROM_SEC(sbi, secno)); + int devi = f2fs_target_device_index(sbi, start_blk); + + if (f2fs_dev_is_reserving(sbi, devi)) + return true; + } return false; } diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c index 5902b2da7ea4..ed9e0ba9cf0b 100644 --- a/fs/f2fs/super.c +++ b/fs/f2fs/super.c @@ -5005,6 +5005,39 @@ static void f2fs_tuning_parameters(struct f2fs_sb_info *sbi) sbi->readdir_ra = true; } +static void f2fs_restore_device_alias(struct f2fs_sb_info *sbi) +{ + struct inode *root = d_inode(sbi->sb->s_root); + struct f2fs_dir_entry *de; + struct folio *folio; + int i; + + if (!f2fs_sb_has_device_alias(sbi)) + return; + + for (i = 1; i < sbi->s_ndevs; i++) { + char *name = strrchr(FDEV(i).path, '/'); + struct inode *inode; + struct qstr qstr; + + name = name ? name + 1 : FDEV(i).path; + qstr.name = name; + qstr.len = strlen(name); + + de = f2fs_find_entry(root, &qstr, &folio); + if (!de) + continue; + + inode = f2fs_iget(sbi->sb, le32_to_cpu(de->ino)); + if (!IS_ERR(inode)) { + if (IS_DEVICE_ALIASING(inode)) + FDEV(i).has_alias = true; + iput(inode); + } + f2fs_folio_put(folio, 0); + } +} + static int f2fs_fill_super(struct super_block *sb, struct fs_context *fc) { struct f2fs_fs_context *ctx = fc->fs_private; @@ -5209,6 +5242,7 @@ static int f2fs_fill_super(struct super_block *sb, struct fs_context *fc) sbi->last_valid_block_count = sbi->total_valid_block_count; sbi->reserved_blocks = 0; sbi->current_reserved_blocks = 0; + sbi->alias_reserved_blocks = 0; limit_reserve_root(sbi); adjust_unusable_cap_perc(sbi); @@ -5436,6 +5470,8 @@ static int f2fs_fill_super(struct super_block *sb, struct fs_context *fc) f2fs_update_time(sbi, REQ_TIME); clear_sbi_flag(sbi, SBI_CP_DISABLED_QUICK); + f2fs_restore_device_alias(sbi); + sbi->umount_lock_holder = NULL; return 0; diff --git a/include/uapi/linux/f2fs.h b/include/uapi/linux/f2fs.h index 795e26258355..4409ada2fecb 100644 --- a/include/uapi/linux/f2fs.h +++ b/include/uapi/linux/f2fs.h @@ -45,6 +45,9 @@ #define F2FS_IOC_START_ATOMIC_REPLACE _IO(F2FS_IOCTL_MAGIC, 25) #define F2FS_IOC_GET_DEV_ALIAS_FILE _IOR(F2FS_IOCTL_MAGIC, 26, __u32) #define F2FS_IOC_IO_PRIO _IOW(F2FS_IOCTL_MAGIC, 27, __u32) +#define F2FS_IOC_RESERVE_DEV_ALIAS _IO(F2FS_IOCTL_MAGIC, 28) +#define F2FS_IOC_RELEASE_DEV_ALIAS _IO(F2FS_IOCTL_MAGIC, 29) +#define F2FS_IOC_GET_DEV_ALIAS_STATUS _IOR(F2FS_IOCTL_MAGIC, 30, __u32) /* * should be same as XFS_IOC_GOINGDOWN. @@ -70,6 +73,10 @@ enum { F2FS_IOPRIO_MAX, }; +/* for F2FS_IOC_GET_DEV_ALIAS_STATUS */ +#define F2FS_DEV_ALIAS_STATUS_RELEASED 0 +#define F2FS_DEV_ALIAS_STATUS_RESERVED 1 + struct f2fs_gc_range { __u32 sync; __u64 start; From 0d7477640f44851a0bcb82d220e2f451fa67864e Mon Sep 17 00:00:00 2001 From: Jaegeuk Kim Date: Fri, 7 Aug 2026 21:31:11 +0000 Subject: [PATCH 0503/1328] f2fs: refactor f2fs_evict_inode having three major parts 1. f2fs_pre_evict_inode() : drop all in-memory structures 2. f2fs_delete_inode() : truncate inode blocks, if it was unlinked. 3. f2fs_post_evict_inode() : update inode records for future access Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/inode.c | 132 +++++++++++++++++++++++++++++------------------- 1 file changed, 79 insertions(+), 53 deletions(-) diff --git a/fs/f2fs/inode.c b/fs/f2fs/inode.c index c95e0b126da4..553b1e338aa1 100644 --- a/fs/f2fs/inode.c +++ b/fs/f2fs/inode.c @@ -855,15 +855,12 @@ void f2fs_remove_donate_inode(struct inode *inode) } /* - * Called at the last iput() if i_nlink is zero + * Return true, if we shouldn't go through post_evict_inode. */ -void f2fs_evict_inode(struct inode *inode) +static bool f2fs_pre_evict_inode(struct inode *inode) { struct f2fs_sb_info *sbi = F2FS_I_SB(inode); struct f2fs_inode_info *fi = F2FS_I(inode); - nid_t xnid = fi->i_xattr_nid; - int err = 0; - bool freeze_protected = false; f2fs_abort_atomic_write(inode, true); @@ -883,13 +880,13 @@ void f2fs_evict_inode(struct inode *inode) truncate_inode_pages_final(&inode->i_data); if ((inode->i_nlink || is_bad_inode(inode)) && - test_opt(sbi, COMPRESS_CACHE) && f2fs_compressed_file(inode)) + test_opt(sbi, COMPRESS_CACHE) && f2fs_compressed_file(inode)) f2fs_invalidate_compress_pages(sbi, inode->i_ino); if (inode->i_ino == F2FS_NODE_INO(sbi) || - inode->i_ino == F2FS_META_INO(sbi) || - inode->i_ino == F2FS_COMPRESS_INO(sbi)) - goto out_clear; + inode->i_ino == F2FS_META_INO(sbi) || + inode->i_ino == F2FS_COMPRESS_INO(sbi)) + return true; f2fs_bug_on(sbi, get_dirty_pages(inode)); f2fs_remove_dirty_inode(inode); @@ -898,14 +895,18 @@ void f2fs_evict_inode(struct inode *inode) if (!IS_DEVICE_ALIASING(inode)) f2fs_destroy_extent_tree(inode); - if (inode->i_nlink || is_bad_inode(inode)) - goto no_delete; + return false; +} - err = f2fs_dquot_initialize(inode); - if (err) { - err = 0; +static void f2fs_delete_inode(struct inode *inode) +{ + struct f2fs_sb_info *sbi = F2FS_I_SB(inode); + bool freeze_protected = false; + struct f2fs_lock_context lc; + int err = 0; + + if (f2fs_dquot_initialize(inode)) set_sbi_flag(sbi, SBI_QUOTA_NEED_REPAIR); - } f2fs_remove_ino_entry(sbi, inode->i_ino, APPEND_INO); f2fs_remove_ino_entry(sbi, inode->i_ino, UPDATE_INO); @@ -924,30 +925,30 @@ void f2fs_evict_inode(struct inode *inode) if (time_to_inject(sbi, FAULT_EVICT_INODE)) err = -EIO; - if (!err) { - struct f2fs_lock_context lc; + if (err) + goto error_check; - f2fs_lock_op(sbi, &lc); - err = f2fs_remove_inode_page(inode); - f2fs_unlock_op(sbi, &lc); - if (err == -ENOENT) { - err = 0; + f2fs_lock_op(sbi, &lc); + err = f2fs_remove_inode_page(inode); + f2fs_unlock_op(sbi, &lc); - /* - * in fuzzed image, another node may has the same - * block address as inode's, if it was truncated - * previously, truncation of inode node will fail. - */ - if (is_inode_flag_set(inode, FI_DIRTY_INODE)) { - f2fs_warn(F2FS_I_SB(inode), - "f2fs_evict_inode: inconsistent node id, ino:%llu", - inode->i_ino); - f2fs_inode_synced(inode); - set_sbi_flag(sbi, SBI_NEED_FSCK); - } + if (err == -ENOENT) { + err = 0; + + /* + * in fuzzed image, another node may has the same + * block address as inode's, if it was truncated + * previously, truncation of inode node will fail. + */ + if (is_inode_flag_set(inode, FI_DIRTY_INODE)) { + f2fs_warn(F2FS_I_SB(inode), + "f2fs_evict_inode: inconsistent node id, ino:%llu", + inode->i_ino); + f2fs_inode_synced(inode); + set_sbi_flag(sbi, SBI_NEED_FSCK); } } - +error_check: /* give more chances, if ENOMEM case */ if (err == -ENOMEM) { err = 0; @@ -957,27 +958,37 @@ void f2fs_evict_inode(struct inode *inode) if (IS_DEVICE_ALIASING(inode)) f2fs_destroy_extent_tree(inode); - if (err) { - f2fs_update_inode_page(inode); - if (dquot_initialize_needed(inode)) - set_sbi_flag(sbi, SBI_QUOTA_NEED_REPAIR); + if (!err) + goto unfreeze_out; - /* - * If both f2fs_truncate() and f2fs_update_inode_page() failed - * due to fuzzed corrupted inode, call f2fs_inode_synced() to - * avoid triggering later f2fs_bug_on(). - */ - if (is_inode_flag_set(inode, FI_DIRTY_INODE)) { - f2fs_warn(sbi, - "f2fs_evict_inode: inode is dirty, ino:%llu", - inode->i_ino); - f2fs_inode_synced(inode); - set_sbi_flag(sbi, SBI_NEED_FSCK); - } + f2fs_update_inode_page(inode); + + if (dquot_initialize_needed(inode)) + set_sbi_flag(sbi, SBI_QUOTA_NEED_REPAIR); + + /* + * If both f2fs_truncate() and f2fs_update_inode_page() failed + * due to fuzzed corrupted inode, call f2fs_inode_synced() to + * avoid triggering later f2fs_bug_on(). + */ + if (is_inode_flag_set(inode, FI_DIRTY_INODE)) { + f2fs_warn(sbi, + "f2fs_evict_inode: inode is dirty, ino:%llu", + inode->i_ino); + f2fs_inode_synced(inode); + set_sbi_flag(sbi, SBI_NEED_FSCK); } +unfreeze_out: if (freeze_protected) sb_end_intwrite(inode->i_sb); -no_delete: +} + +static void f2fs_post_evict_inode(struct inode *inode) +{ + struct f2fs_sb_info *sbi = F2FS_I_SB(inode); + struct f2fs_inode_info *fi = F2FS_I(inode); + nid_t xnid = fi->i_xattr_nid; + dquot_drop(inode); stat_dec_inline_xattr(inode); @@ -1019,7 +1030,22 @@ void f2fs_evict_inode(struct inode *inode) * In that case, f2fs_check_nid_range() is enough to give a clue. */ } -out_clear: +} + +/* + * Called at the last iput() if i_nlink is zero + */ +void f2fs_evict_inode(struct inode *inode) +{ + if (f2fs_pre_evict_inode(inode)) + goto clear_out; + + if (!inode->i_nlink && !is_bad_inode(inode)) + f2fs_delete_inode(inode); + + f2fs_post_evict_inode(inode); + +clear_out: fscrypt_put_encryption_info(inode); clear_inode(inode); } From 1bc94d09e1a94fe3f94cce025e0799dea24f2888 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 1 Jul 2026 05:54:20 -0700 Subject: [PATCH 0504/1328] apparmor: fix error handling for copy_from_user in policy_update copy_from_user does not return an error code and the check should be setting the error code. Fixes: 8b236f99edf8 ("apparmor: Initial support for compressed policies") Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index ac0a181e5ceb..22317817d1f5 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -552,10 +552,10 @@ static struct aa_loaddata *aa_get_data_from_compressed(const char __user *userbu *compressed_data = kvmalloc(buffer_size, GFP_KERNEL); if (!*compressed_data) return ERR_PTR(-ENOMEM); - error = copy_from_user(*compressed_data, userbuf, buffer_size); - if (error) + if (copy_from_user(*compressed_data, userbuf, buffer_size)) { + error = -EFAULT; goto fail; - + } error = zstd_get_frame_header(&header, *compressed_data, buffer_size); if (error || header.frameContentSize == ZSTD_CONTENTSIZE_UNKNOWN || header.frameContentSize == ZSTD_CONTENTSIZE_ERROR) { From 7965b1f3940ae21209ad095fcf2df46c0a99df2d Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sun, 17 Jun 2018 03:56:25 -0700 Subject: [PATCH 0505/1328] apparmor: make MEDIATES_AF_UNIX its own fn Hide the functionality of determinig unix mediation behind its own fn so it is easier to adjust the test in the future as it has different requirements than the other socket mediation. Signed-off-by: John Johansen --- security/apparmor/af_unix.c | 14 +++++++------- security/apparmor/file.c | 2 +- security/apparmor/include/policy.h | 6 +++++- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/security/apparmor/af_unix.c b/security/apparmor/af_unix.c index 395aede487ec..84250c0b9c78 100644 --- a/security/apparmor/af_unix.c +++ b/security/apparmor/af_unix.c @@ -209,7 +209,7 @@ static int profile_create_perm(struct aa_profile *profile, int family, AA_BUG(!profile); AA_BUG(profile_unconfined(profile)); - state = RULE_MEDIATES_v9NET(rules); + state = RULE_MEDIATES_UNIX(rules); if (state) { state = aa_match_to_prot(rules->policy, state, AA_MAY_CREATE, PF_UNIX, type, protocol, NULL, @@ -235,7 +235,7 @@ static int profile_sk_perm(struct aa_profile *profile, AA_BUG(!sk); AA_BUG(profile_unconfined(profile)); - state = RULE_MEDIATES_v9NET(rules); + state = RULE_MEDIATES_UNIX(rules); if (state) { if (is_unix_fs(sk)) return unix_fs_perm(ad->op, request, ad->subj_cred, @@ -264,7 +264,7 @@ static int profile_bind_perm(struct aa_profile *profile, struct sock *sk, AA_BUG(!ad); AA_BUG(profile_unconfined(profile)); - state = RULE_MEDIATES_v9NET(rules); + state = RULE_MEDIATES_UNIX(rules); if (state) { if (is_unix_addr_fs(ad->net.addr, ad->net.addrlen)) /* under v7-9 fs hook handles bind */ @@ -295,7 +295,7 @@ static int profile_listen_perm(struct aa_profile *profile, struct sock *sk, AA_BUG(!ad); AA_BUG(profile_unconfined(profile)); - state = RULE_MEDIATES_v9NET(rules); + state = RULE_MEDIATES_UNIX(rules); if (state) { __be16 b = cpu_to_be16(backlog); @@ -332,7 +332,7 @@ static int profile_accept_perm(struct aa_profile *profile, AA_BUG(!ad); AA_BUG(profile_unconfined(profile)); - state = RULE_MEDIATES_v9NET(rules); + state = RULE_MEDIATES_UNIX(rules); if (state) { if (is_unix_fs(sk)) return unix_fs_perm(ad->op, AA_MAY_ACCEPT, @@ -362,7 +362,7 @@ static int profile_opt_perm(struct aa_profile *profile, u32 request, AA_BUG(!ad); AA_BUG(profile_unconfined(profile)); - state = RULE_MEDIATES_v9NET(rules); + state = RULE_MEDIATES_UNIX(rules); if (state) { __be16 b = cpu_to_be16(optname); if (is_unix_fs(sk)) @@ -403,7 +403,7 @@ static int profile_peer_perm(struct aa_profile *profile, u32 request, AA_BUG(!peer_label); AA_BUG(!ad); - state = RULE_MEDIATES_v9NET(rules); + state = RULE_MEDIATES_UNIX(rules); if (state) { struct aa_profile *peerp; diff --git a/security/apparmor/file.c b/security/apparmor/file.c index c9d55fe1086f..e67ca65b7bd9 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -232,7 +232,7 @@ int __aa_path_perm(const char *op, const struct cred *subj_cred, int e = 0; if (profile_unconfined(profile) || - ((flags & PATH_SOCK_COND) && !RULE_MEDIATES_v9NET(rules))) + ((flags & PATH_SOCK_COND) && !RULE_MEDIATES_UNIX(rules))) return 0; aa_str_perms(rules->file, rules->file->start[AA_CLASS_FILE], name, cond, perms); diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index 240b2eba7687..f530436e2b9d 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -351,10 +351,14 @@ static inline aa_state_t RULE_MEDIATES_NET(struct aa_ruleset *rules) /* fallback and check v7/8 if v9 is NOT mediated */ if (!state) state = RULE_MEDIATES(rules, AA_CLASS_NET); - return state; } +static inline aa_state_t RULE_MEDIATES_UNIX(struct aa_ruleset *rules) +{ + return RULE_MEDIATES_v9NET(rules); +} + void aa_compute_profile_mediates(struct aa_profile *profile); static inline bool profile_mediates(struct aa_profile *profile, From 4f0cafcb2850a08d9081eea3ac8bec17b9a9881c Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sat, 9 Oct 2021 02:30:41 -0700 Subject: [PATCH 0506/1328] apparmor: refactor network sock mediation in preparation for inet mediation Refactor network mediation, introducing the stub code for the fine grained inet mediation. This is a preparatory step and does not change mediation. Signed-off-by: John Johansen --- security/apparmor/Makefile | 2 +- security/apparmor/af_inet.c | 81 +++++++++++++++++++++++++++++ security/apparmor/include/af_inet.h | 36 +++++++++++++ security/apparmor/include/net.h | 7 +-- security/apparmor/lsm.c | 66 +++++++++++++++++++---- security/apparmor/net.c | 20 ++++--- 6 files changed, 190 insertions(+), 22 deletions(-) create mode 100644 security/apparmor/af_inet.c create mode 100644 security/apparmor/include/af_inet.h diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index 12fb419714c0..8aed0d7ed6f5 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -6,7 +6,7 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o task.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ resource.o secid.o file.o policy_ns.o label.o mount.o net.o \ - policy_compat.o af_unix.o + policy_compat.o af_unix.o af_inet.o apparmor-$(CONFIG_SECURITY_APPARMOR_HASH) += crypto.o obj-$(CONFIG_SECURITY_APPARMOR_KUNIT_TEST) += apparmor_policy_unpack_test.o diff --git a/security/apparmor/af_inet.c b/security/apparmor/af_inet.c new file mode 100644 index 000000000000..780c905b029f --- /dev/null +++ b/security/apparmor/af_inet.c @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * AppArmor security module + * + * This file contains AppArmor inet fine grained mediation + * + * Copyright 2024 Canonical Ltd. + * + */ + +#include + +#include "include/audit.h" +#include "include/af_inet.h" +#include "include/apparmor.h" +#include "include/file.h" +#include "include/label.h" +#include "include/net.h" +#include "include/path.h" +#include "include/policy.h" +#include "include/cred.h" + + +/* no kernel_t early bailout */ +/* NOTE: already lifted label_mediates into lsm.c */ +int aa_inet_create_perm(struct aa_label *label, int family, int type, + int protocol) +{ + return aa_af_perm(current_cred(), label, OP_CREATE, + AA_MAY_CREATE, family, type, + protocol); +} + +int aa_inet_bind_perm(struct socket *sock, struct sockaddr *addr, + int addrlen) +{ + return aa_sk_perm(OP_BIND, AA_MAY_BIND, sock->sk); +} + +int aa_inet_connect_perm(struct socket *sock, struct sockaddr *addr, + int addrlen) +{ + return aa_sk_perm(OP_CONNECT, AA_MAY_CONNECT, sock->sk); +} + +int aa_inet_listen_perm(struct socket *sock, int backlog) +{ + return aa_sk_perm(OP_LISTEN, AA_MAY_LISTEN, sock->sk); +} + +/* ability of sock to connect, not peer address binding */ +int aa_inet_accept_perm(struct socket *sock, struct socket *newsock) +{ + return aa_sk_perm(OP_ACCEPT, AA_MAY_ACCEPT, sock->sk); +} + +/* sendmsg, recvmsg. */ +int aa_inet_msg_perm(const char *op, u32 request, struct socket *sock, + struct msghdr *msg, int size) +{ + return aa_sk_perm(op, request, sock->sk); +} + +/* getopt, setopt */ +int aa_inet_opt_perm(const char *op, u32 request, struct socket *sock, + int level, int optname) +{ + return aa_sk_perm(op, request, sock->sk); +} + +/* revaliation, get/set attr/getsockname/peername */ +int aa_inet_sock_perm(const char *op, u32 request, struct socket *sock) +{ + return aa_sk_perm(op, request, sock->sk); +} + +int aa_inet_file_perm(const struct cred *subj_cred, struct aa_label *label, + const char *op, u32 request, struct socket *sock) +{ + return aa_label_sk_perm(subj_cred, label, op, request, sock->sk); +} diff --git a/security/apparmor/include/af_inet.h b/security/apparmor/include/af_inet.h new file mode 100644 index 000000000000..601ab44bdfe1 --- /dev/null +++ b/security/apparmor/include/af_inet.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * AppArmor security module + * + * This file contains AppArmor inet/inet6 fine grained mediation + * + * Copyright 2024 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ +#ifndef __AA_AF_INET_H +#define __AA_AF_INET_H + +#include "label.h" + +int aa_inet_sock_perm(const char *op, u32 request, struct socket *sock); +int aa_inet_create_perm(struct aa_label *label, int family, int type, + int protocol); +int aa_inet_bind_perm(struct socket *sock, struct sockaddr *address, + int addrlen); +int aa_inet_connect_perm(struct socket *sock, struct sockaddr *address, + int addrlen); +int aa_inet_listen_perm(struct socket *sock, int backlog); +int aa_inet_accept_perm(struct socket *sock, struct socket *newsock); +int aa_inet_msg_perm(const char *op, u32 request, struct socket *sock, + struct msghdr *msg, int size); +int aa_inet_opt_perm(const char *op, u32 request, struct socket *sock, int level, + int optname); +int aa_inet_file_perm(const struct cred *subj_cred, + struct aa_label *label, const char *op, u32 request, + struct socket *sock); + +#endif /* __AA_AF_INET_H */ diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h index 0d0b0ce42723..2efc5417d1ba 100644 --- a/security/apparmor/include/net.h +++ b/security/apparmor/include/net.h @@ -102,13 +102,14 @@ int aa_af_perm(const struct cred *subj_cred, struct aa_label *label, static inline int aa_profile_af_sk_perm(struct aa_profile *profile, struct apparmor_audit_data *ad, u32 request, - struct sock *sk) + const struct sock *sk) { return aa_profile_af_perm(profile, ad, request, sk->sk_family, sk->sk_type, sk->sk_protocol); } -int aa_sk_perm(const char *op, u32 request, struct sock *sk); - +int aa_sk_perm(const char *op, u32 request, const struct sock *sk); +int aa_label_sk_perm(const struct cred *subj_cred, struct aa_label *label, + const char *op, u32 request, const struct sock *sk); int aa_sock_file_perm(const struct cred *subj_cred, struct aa_label *label, const char *op, u32 request, struct file *file); diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index ef5fb3b9c80a..65db1e5460bf 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -28,6 +28,7 @@ #include #include "include/af_unix.h" +#include "include/af_inet.h" #include "include/apparmor.h" #include "include/apparmorfs.h" #include "include/audit.h" @@ -1266,13 +1267,21 @@ static int apparmor_socket_create(int family, int type, int protocol, int kern) label = begin_current_label_crit_section(&needput); if (!unconfined(label)) { - if (family == PF_UNIX) + switch (family) { + case PF_UNIX: error = aa_unix_create_perm(label, family, type, protocol); - else + break; + case PF_INET: + case PF_INET6: + error = aa_inet_create_perm(label, family, type, + protocol); + break; + default: error = aa_af_perm(current_cred(), label, OP_CREATE, AA_MAY_CREATE, family, type, protocol); + } } end_current_label_crit_section(label, needput); @@ -1367,8 +1376,13 @@ static int apparmor_socket_bind(struct socket *sock, AA_BUG(!address); AA_BUG(in_interrupt()); - if (sock->sk->sk_family == PF_UNIX) + switch (sock->sk->sk_family) { + case PF_UNIX: return aa_unix_bind_perm(sock, address, addrlen); + case PF_INET: + case PF_INET6: + return aa_inet_bind_perm(sock, address, addrlen); + } return aa_sk_perm(OP_BIND, AA_MAY_BIND, sock->sk); } @@ -1381,8 +1395,13 @@ static int apparmor_socket_connect(struct socket *sock, AA_BUG(in_interrupt()); /* PF_UNIX goes through unix_stream_connect && unix_may_send */ - if (sock->sk->sk_family == PF_UNIX) + switch (sock->sk->sk_family) { + case PF_UNIX: return 0; + case PF_INET: + case PF_INET6: + return aa_inet_connect_perm(sock, address, addrlen); + } return aa_sk_perm(OP_CONNECT, AA_MAY_CONNECT, sock->sk); } @@ -1392,8 +1411,13 @@ static int apparmor_socket_listen(struct socket *sock, int backlog) AA_BUG(!sock->sk); AA_BUG(in_interrupt()); - if (sock->sk->sk_family == PF_UNIX) + switch (sock->sk->sk_family) { + case PF_UNIX: return aa_unix_listen_perm(sock, backlog); + case PF_INET: + case PF_INET6: + return aa_inet_listen_perm(sock, backlog); + } return aa_sk_perm(OP_LISTEN, AA_MAY_LISTEN, sock->sk); } @@ -1408,8 +1432,13 @@ static int apparmor_socket_accept(struct socket *sock, struct socket *newsock) AA_BUG(!newsock); AA_BUG(in_interrupt()); - if (sock->sk->sk_family == PF_UNIX) + switch (sock->sk->sk_family) { + case PF_UNIX: return aa_unix_accept_perm(sock, newsock); + case PF_INET: + case PF_INET6: + return aa_inet_accept_perm(sock, newsock); + } return aa_sk_perm(OP_ACCEPT, AA_MAY_ACCEPT, sock->sk); } @@ -1422,8 +1451,14 @@ static int aa_sock_msg_perm(const char *op, u32 request, struct socket *sock, AA_BUG(in_interrupt()); /* PF_UNIX goes through unix_may_send */ - if (sock->sk->sk_family == PF_UNIX) + switch (sock->sk->sk_family) { + case PF_UNIX: return 0; + case PF_INET: + case PF_INET6: + return aa_inet_msg_perm(op, request, sock, msg, size); + } + return aa_sk_perm(op, request, sock->sk); } @@ -1442,7 +1477,8 @@ static int apparmor_socket_sendmsg(struct socket *sock, (sk_is_tcp(sock->sk) || (sk_is_inet(sock->sk) && sock->sk->sk_type == SOCK_STREAM && sock->sk->sk_protocol == IPPROTO_MPTCP))) - error = aa_sk_perm(OP_CONNECT, AA_MAY_CONNECT, sock->sk); + error = aa_sock_msg_perm(OP_CONNECT, AA_MAY_CONNECT, sock, + msg, size); return error; } @@ -1460,8 +1496,13 @@ static int aa_sock_perm(const char *op, u32 request, struct socket *sock) AA_BUG(!sock->sk); AA_BUG(in_interrupt()); - if (sock->sk->sk_family == PF_UNIX) + switch (sock->sk->sk_family) { + case PF_UNIX: return aa_unix_sock_perm(op, request, sock); + case PF_INET: + case PF_INET6: + return aa_inet_sock_perm(op, request, sock); + } return aa_sk_perm(op, request, sock->sk); } @@ -1483,8 +1524,13 @@ static int aa_sock_opt_perm(const char *op, u32 request, struct socket *sock, AA_BUG(!sock->sk); AA_BUG(in_interrupt()); - if (sock->sk->sk_family == PF_UNIX) + switch (sock->sk->sk_family) { + case PF_UNIX: return aa_unix_opt_perm(op, request, sock, level, optname); + case PF_INET: + case PF_INET6: + return aa_inet_opt_perm(op, request, sock, level, optname); + } return aa_sk_perm(op, request, sock->sk); } diff --git a/security/apparmor/net.c b/security/apparmor/net.c index e3a3708a057e..678d1417efd6 100644 --- a/security/apparmor/net.c +++ b/security/apparmor/net.c @@ -9,6 +9,7 @@ */ #include "include/af_unix.h" +#include "include/af_inet.h" #include "include/apparmor.h" #include "include/audit.h" #include "include/cred.h" @@ -133,12 +134,12 @@ void audit_net_cb(struct audit_buffer *ab, void *va) audit_log_format(ab, " protocol=%d", ad->net.protocol); if (ad->request & NET_PERMS_MASK) { - audit_log_format(ab, " requested_mask="); + audit_log_format(ab, " requested="); aa_audit_perm_mask(ab, ad->request, NULL, 0, net_mask_names, NET_PERMS_MASK); if (ad->denied & NET_PERMS_MASK) { - audit_log_format(ab, " denied_mask="); + audit_log_format(ab, " denied="); aa_audit_perm_mask(ab, ad->denied, NULL, 0, net_mask_names, NET_PERMS_MASK); } @@ -282,10 +283,8 @@ int aa_af_perm(const struct cred *subj_cred, struct aa_label *label, type, protocol)); } -static int aa_label_sk_perm(const struct cred *subj_cred, - struct aa_label *label, - const char *op, u32 request, - struct sock *sk) +int aa_label_sk_perm(const struct cred *subj_cred, struct aa_label *label, + const char *op, u32 request, const struct sock *sk) { struct aa_sk_ctx *ctx = aa_sock(sk); int error = 0; @@ -305,7 +304,7 @@ static int aa_label_sk_perm(const struct cred *subj_cred, return error; } -int aa_sk_perm(const char *op, u32 request, struct sock *sk) +int aa_sk_perm(const char *op, u32 request, const struct sock *sk) { struct aa_label *label; bool needput; @@ -334,8 +333,13 @@ int aa_sock_file_perm(const struct cred *subj_cred, struct aa_label *label, if (!sock || !sock->sk) return 0; - if (sock->sk->sk_family == PF_UNIX) + switch (sock->sk->sk_family) { + case PF_UNIX: return aa_unix_file_perm(subj_cred, label, op, request, file); + case PF_INET: + case PF_INET6: + return aa_inet_file_perm(subj_cred, label, op, request, sock); + } return aa_label_sk_perm(subj_cred, label, op, request, sock->sk); } From 89ac6aa752ec8b7b43d8d88a3e7719d1868d582a Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 30 Jun 2026 10:42:06 -0700 Subject: [PATCH 0507/1328] apparmor: push inet mediation into profile callbacks, and improve auditing Continue preparing for fine grained inet mediation by setting up the stacked mediation callback. This lifts address mapping and audit context setup out of the stacking loop and pushing the mediation down into the callback fn. While this patch sets up the structure for fine grained mediation it does not change mediation and the callback fns only call the default mediation that will be used when fine grained inet mediation is not available. Signed-off-by: John Johansen --- security/apparmor/af_inet.c | 508 +++++++++++++++++++++++++++++++++++- security/apparmor/audit.c | 2 +- 2 files changed, 497 insertions(+), 13 deletions(-) diff --git a/security/apparmor/af_inet.c b/security/apparmor/af_inet.c index 780c905b029f..516c9016537c 100644 --- a/security/apparmor/af_inet.c +++ b/security/apparmor/af_inet.c @@ -21,61 +21,545 @@ #include "include/cred.h" +static inline aa_state_t RULE_MEDIATES_SK(struct aa_ruleset *rules, + const struct sock *sk) +{ + return RULE_MEDIATES_NET(rules); +} + + +enum addr_type { + ADDR_LOCAL = 0, + ADDR_LOCAL_PRIV = 1, + ADDR_REMOTE = 2, +}; + +struct match_addr { + const char *addrp; + enum addr_type addrtype; + int len; + __be16 port; +}; + +struct stored_match_addr { + union { + struct sockaddr addr; + struct sockaddr_in addr4; + struct sockaddr_in6 addr6; + }; + int addrlen; + struct match_addr maddr; +}; + +static void set_ad_create(struct apparmor_audit_data *ad, + int family, int type, int protocol) +{ + ad->common.u.net->family = family; + ad->net.type = type; + ad->net.protocol = protocol; +} + +static int set_ad_addr(struct apparmor_audit_data *ad, + u16 family, bool source, struct match_addr *maddr) +{ + ad->common.u.net->family = family; + + if (source) { + ad->common.u.net->sport = maddr->port; + if (maddr->addrp) { + if (family == AF_INET) + /* ad.u.net->v4info.saddr = addr4->sin_addr.s_addr; */ + ad->common.u.net->v4info.saddr = *(__be32 *)maddr->addrp; + else + /* ad.u.net->v4info.saddr = addr6->sin6_addr.s6_addr; */ + ad->common.u.net->v6info.saddr = *(struct in6_addr *)maddr->addrp; + } + } else { + ad->common.u.net->dport = maddr->port; + if (maddr->addrp) { + if (family == AF_INET) + /* ad.u.net->v4info.saddr = addr4->sin_addr.s_addr; */ + ad->common.u.net->v4info.daddr = *(__be32 *)maddr->addrp; + else + /* ad.u.net->v4info.saddr = addr6->sin6_addr.s6_addr; */ + ad->common.u.net->v6info.daddr = *(struct in6_addr *)maddr->addrp; + } + } + return 0; +} + +/* returns 0 on success +* raw_port - if set raw_port (protocol) when SOCK_RAW */ +static int map_addr(struct sockaddr *addr, int addrlen, u16 raw_port, + enum addr_type addrtype, struct match_addr *maddr, + struct apparmor_audit_data *ad) +{ + struct sockaddr_in *addr4 = NULL; + struct sockaddr_in6 *addr6 = NULL; + + AA_BUG(!addr); + AA_BUG(!maddr); + + maddr->addrtype = addrtype; + if (!addr || addrlen < offsetofend(struct sockaddr, sa_family)) { + maddr->addrp = NULL; + maddr->port = 0; + maddr->len = 0; + return 0; + } + + /* + * its possibly to have sk->sk_family == PF_INET6 and + * addr->sa_family == AF_INET. sk_family is used for socket + * mediation, sa_family for when we have address ... + */ + switch (addr->sa_family) { + case AF_INET: + addr4 = (struct sockaddr_in *)addr; + if (addrlen < sizeof(struct sockaddr_in)) + return -EINVAL; + maddr->port = addr4->sin_port; + maddr->addrp = (char *)&addr4->sin_addr.s_addr; + maddr->len = 4; + break; + case AF_INET6: + addr6 = (struct sockaddr_in6 *)addr; + if (addrlen < SIN6_LEN_RFC2133) + return -EINVAL; + maddr->port = addr6->sin6_port; + maddr->addrp = (char *)&addr6->sin6_addr.s6_addr; + maddr->len = 16; + break; + default: + return -EAFNOSUPPORT; + } + /* per ip spec, && sk->sk_type == SOCK_RAW*/ + if (raw_port && addrtype != ADDR_REMOTE) + maddr->port = htons(raw_port); + if (ad) + set_ad_addr(ad, addr->sa_family, addrtype != ADDR_REMOTE, maddr); + + return 0; +} + +/* -ENOTCONN if not connected */ +static int map_sock_addr(struct socket *sock, enum addr_type addrtype, + struct stored_match_addr *maddr, + struct apparmor_audit_data *ad) +{ + /* do we need early bailout for !family ... */ + maddr->addrlen = sock->ops->getname(sock, (struct sockaddr *) &maddr->addr, addrtype != ADDR_REMOTE ? 0 : 1); + if (maddr->addrlen == -ENOTCONN) { + maddr->addrlen = 0; + return map_addr(NULL, 0, 0, addrtype, &maddr->maddr, ad); + } else if (maddr->addrlen < 0) + return maddr->addrlen; + return map_addr(&maddr->addr, maddr->addrlen, 0, addrtype, + &maddr->maddr, ad); +} + +/* TODO: combine with connect map addr */ +/* TODO: raw_port */ +static int bind_map_addr(const struct sock *sk, struct sockaddr *addr, + int addrlen, + struct match_addr *maddr, + struct apparmor_audit_data *ad) +{ + struct sockaddr_in *addr4 = NULL; + struct sockaddr_in6 *addr6 = NULL; + u16 family; + + AA_BUG(!sk); + AA_BUG(!addr); + AA_BUG(!maddr); + + if (addrlen < offsetofend(struct sockaddr, sa_family)) + return -EINVAL; + + maddr->addrtype = ADDR_LOCAL; + /* + * its possibly to have sk->sk_family == PF_INET6 and + * addr->sa_family == AF_INET. sk_family is used for socket + * mediation, sa_family for when we have address ... + */ + family = addr->sa_family; + switch (addr->sa_family) { + case AF_UNSPEC: + if (sk->sk_family == PF_INET6) { + /* Length check from inet6_bind_sk() */ + if (addrlen < SIN6_LEN_RFC2133) + return -EINVAL; + /* Family check from __inet6_bind() */ + return -EAFNOSUPPORT; + } + /* see __inet_bind(), we only want to allow + * AF_UNSPEC if the address is INADDR_ANY + */ + addr4 = (struct sockaddr_in *)addr; + if (addr4->sin_addr.s_addr != htonl(INADDR_ANY)) + return -EAFNOSUPPORT; + family = AF_INET; + fallthrough; + case AF_INET: + addr4 = (struct sockaddr_in *)addr; + if (addrlen < sizeof(struct sockaddr_in)) + return -EINVAL; + maddr->port = addr4->sin_port; + maddr->addrp = (char *)&addr4->sin_addr.s_addr; + maddr->len = 4; + break; + case AF_INET6: + addr6 = (struct sockaddr_in6 *)addr; + if (addrlen < SIN6_LEN_RFC2133) + return -EINVAL; + maddr->port = addr6->sin6_port; + maddr->addrp = (char *)&addr6->sin6_addr.s6_addr; + maddr->len = 16; + break; + default: + return -EAFNOSUPPORT; + } + + if (ad) + set_ad_addr(ad, family, true, maddr); + + return 0; +} + + +static inline int profile_sk_perm(struct aa_profile *profile, u32 request, + const struct sock *sk, + struct match_addr *maddr, + struct apparmor_audit_data *ad) +{ + AA_BUG(!profile); + AA_BUG(!sk); + + return aa_profile_af_sk_perm(profile, ad, request, sk); +} + +/* no kernel_t bailout */ +static int profile_create_perm(struct aa_profile *profile, int family, + int type, int protocol, + struct apparmor_audit_data *ad) +{ + AA_BUG(!profile); + + return aa_profile_af_perm(profile, ad, AA_MAY_CREATE, family, type, + protocol); +} + + +/* sendmsg/rcvmsg/connect */ +static int profile_remote_perm(struct aa_profile *profile, + const struct sock *sk, + u32 request, struct match_addr *raddr, + struct match_addr *laddr, + struct apparmor_audit_data *ad) +{ + AA_BUG(!profile); + AA_BUG(!sk); + AA_BUG(!raddr); + AA_BUG(!laddr); + AA_BUG(sk->sk_family != PF_INET && sk->sk_family != PF_INET6, + "family=%d", sk->sk_family); + + return aa_profile_af_sk_perm(profile, ad, request, sk); +} + +static int profile_bind_perm(struct aa_profile *profile, + const struct sock *sk, + struct match_addr *maddr, + struct apparmor_audit_data *ad) +{ + return aa_profile_af_sk_perm(profile, ad, AA_MAY_BIND, sk); + +} + +static int profile_listen_perm(struct aa_profile *profile, + const struct sock *sk, + struct match_addr *maddr, int backlog, + struct apparmor_audit_data *ad) +{ + AA_BUG(!profile); + AA_BUG(!sk); + AA_BUG(!maddr); + AA_BUG(sk->sk_family != PF_INET && sk->sk_family != PF_INET6, + "family=%d", sk->sk_family); + + return aa_profile_af_sk_perm(profile, ad, AA_MAY_LISTEN, sk); +} + +static inline int profile_accept_perm(struct aa_profile *profile, + const struct sock *sk, + struct match_addr *maddr, + const struct sock *newsk, + struct apparmor_audit_data *ad) +{ + AA_BUG(!profile); + AA_BUG(!sk); + /* AA_BUG(!newsk); newsk can be null here, since not using atm ... */ + AA_BUG(!maddr); + AA_BUG(sk->sk_family != PF_INET && sk->sk_family != PF_INET6, + "family=%d", sk->sk_family); + + return aa_profile_af_sk_perm(profile, ad, AA_MAY_ACCEPT, sk); +} + +/* getopt/setopt */ +static int profile_opt_perm(struct aa_profile *profile, u32 request, + const struct sock *sk, struct match_addr *maddr, + int level, int optname, + struct apparmor_audit_data *ad) +{ + AA_BUG(!profile); + AA_BUG(!sk); + AA_BUG(!maddr); + AA_BUG(sk->sk_family != PF_INET && sk->sk_family != PF_INET6, + "family=%d", sk->sk_family); + + return aa_profile_af_sk_perm(profile, ad, request, sk); +} + +/* ---------------------------------------------------------------------- */ + +// TODO: cleanup init to use recursion, so we can have N init fns, in 1 macro +// TODO: lift DEFINE_AUDIT out of macro into init fn??? + +/* no kernel_t bailout */ +#define label_sk_has_perm2(CRED, LABEL, SOCKSK, OP, REQUEST, PROFILE, AAD, XXXX, YYYY, CALLBACKFN) \ +({ \ + int __EERROR = 0; \ + if (label_mediates(LABEL, AA_CLASS_NET)) { \ + struct aa_profile *PROFILE; \ + DEFINE_AUDIT_SK(AAD, OP, CRED, SOCKSK); \ + (AAD).subj_cred = (CRED); \ + (AAD).request = (REQUEST); \ + __EERROR = (XXXX); \ + if (__EERROR == 0) { \ + __EERROR = (YYYY); \ + if (__EERROR == 0) { \ + __EERROR = fn_for_each(LABEL, PROFILE, \ + (CALLBACKFN)); \ + } \ + } \ + } \ + __EERROR; \ +}) + +/* no kernel_t bailout */ +#define label_sk_has_perm(CRED, LABEL, SOCKSK, OP, REQUEST, PROFILE, AAD, CALLBACKFN) \ + label_sk_has_perm2(CRED, LABEL, SOCKSK, OP, REQUEST, PROFILE, AAD, \ + 0, 0, CALLBACKFN) + +/* no kernel_t bailout */ +#define label_sk_has_perm1(CRED, LABEL, SOCKSK, OP, REQUEST, PROFILE, AAD, XXXX, CALLBACKFN) \ + label_sk_has_perm2(CRED, LABEL, SOCKSK, OP, REQUEST, PROFILE, AAD, \ + XXXX, 0, CALLBACKFN) + + +/* Early bailout for kernel_t - 2 init args before callback */ +#define sk_has_perm2(SOCKSK, OP, REQUEST, PROFILE, AAD, XXXXY, YYYYX, CALLBACKFN) \ +({ \ + struct aa_label *__label; \ + struct aa_sk_ctx *__ctx = aa_sock(SOCKSK); \ + int __ERROR = 0; \ + bool __needput; \ + if (rcu_access_pointer(__ctx->label) != kernel_t) { \ + \ + __label = begin_current_label_crit_section(&__needput); \ + __ERROR = label_sk_has_perm2(current_cred(), __label, SOCKSK, OP, REQUEST, PROFILE, AAD, XXXXY, YYYYX, CALLBACKFN); \ + end_current_label_crit_section(__label, __needput); \ + } \ + __ERROR; \ +}) + +/* Early bailout for kernel_t - no init args before callback */ +#define sk_has_perm(SOCKSK, OP, REQUEST, PROFILE, AAD, CALLBACKFN) \ + sk_has_perm2(SOCKSK, OP, REQUEST, PROFILE, AAD, 0, 0, CALLBACKFN) + + +/* Early bailout for kernel_t - 1 init arg before callback */ +#define sk_has_perm1(SOCKSK, OP, REQUEST, PROFILE, AAD, XXXXY, CALLBACKFN) \ + sk_has_perm2(SOCKSK, OP, REQUEST, PROFILE, AAD, XXXXY, 0, CALLBACKFN) + + + /* no kernel_t early bailout */ /* NOTE: already lifted label_mediates into lsm.c */ int aa_inet_create_perm(struct aa_label *label, int family, int type, int protocol) { - return aa_af_perm(current_cred(), label, OP_CREATE, - AA_MAY_CREATE, family, type, - protocol); + struct aa_profile *profile; + int error = 0; + DEFINE_AUDIT_NET(ad, OP_CREATE, current_cred(), NULL, family, type, + protocol); + + ad.subj_cred = current_cred(); + set_ad_create(&ad, family, type, protocol); + error = fn_for_each(label, profile, + profile_create_perm(profile, family, type, + protocol, &ad)); + + return error; } int aa_inet_bind_perm(struct socket *sock, struct sockaddr *addr, int addrlen) { - return aa_sk_perm(OP_BIND, AA_MAY_BIND, sock->sk); + struct match_addr maddr; + + return sk_has_perm1(sock->sk, OP_BIND, AA_MAY_BIND, profile, ad, + bind_map_addr(sock->sk, addr, addrlen, &maddr, + &ad), + profile_bind_perm(profile, sock->sk, &maddr, &ad)); } int aa_inet_connect_perm(struct socket *sock, struct sockaddr *addr, int addrlen) { - return aa_sk_perm(OP_CONNECT, AA_MAY_CONNECT, sock->sk); + struct stored_match_addr laddr; + struct match_addr raddr; + + /* disconnect socket */ + if (addrlen < offsetofend(struct sockaddr, sa_family)) + return -EINVAL; + if (addr->sa_family == AF_UNSPEC) + return 0; + + /* do we need early bailout for !family ... */ + return sk_has_perm2(sock->sk, OP_CONNECT, AA_MAY_CONNECT, profile, ad, + map_sock_addr(sock, ADDR_LOCAL, &laddr, &ad), + map_addr(addr, addrlen, 0, ADDR_REMOTE, &raddr, + &ad), + profile_remote_perm(profile, sock->sk, + AA_MAY_CONNECT, &raddr, + &laddr.maddr, &ad)); } int aa_inet_listen_perm(struct socket *sock, int backlog) { - return aa_sk_perm(OP_LISTEN, AA_MAY_LISTEN, sock->sk); + struct stored_match_addr maddr; + + /* do we need early bailout for !family ... */ + return sk_has_perm1(sock->sk, OP_LISTEN, AA_MAY_LISTEN, profile, ad, + map_sock_addr(sock, ADDR_LOCAL, &maddr, &ad), + profile_listen_perm(profile, sock->sk, &maddr.maddr, + backlog, &ad)); } /* ability of sock to connect, not peer address binding */ int aa_inet_accept_perm(struct socket *sock, struct socket *newsock) { - return aa_sk_perm(OP_ACCEPT, AA_MAY_ACCEPT, sock->sk); + struct stored_match_addr maddr; + int error; + + error = sk_has_perm1(sock->sk, OP_ACCEPT, AA_MAY_ACCEPT, profile, ad, + map_sock_addr(sock, ADDR_LOCAL, &maddr, &ad), + profile_accept_perm(profile, sock->sk, + &maddr.maddr, + newsock->sk, &ad)); + + /* selinux updates inode - need to investigate this more */ + return error; } /* sendmsg, recvmsg. */ int aa_inet_msg_perm(const char *op, u32 request, struct socket *sock, struct msghdr *msg, int size) { - return aa_sk_perm(op, request, sock->sk); + struct stored_match_addr laddr; + struct match_addr raddr; + + /* do we need early bailout for !family ... */ + return sk_has_perm2(sock->sk, op, request, profile, ad, + map_sock_addr(sock, ADDR_LOCAL, &laddr, &ad), + map_addr(msg->msg_name, msg->msg_namelen, 0, + ADDR_REMOTE, &raddr, &ad), + profile_remote_perm(profile, sock->sk, request, + &raddr, &laddr.maddr, &ad)); } /* getopt, setopt */ int aa_inet_opt_perm(const char *op, u32 request, struct socket *sock, int level, int optname) { - return aa_sk_perm(op, request, sock->sk); + struct stored_match_addr maddr; + + return sk_has_perm1(sock->sk, op, request, profile, ad, + map_sock_addr(sock, ADDR_LOCAL, &maddr, &ad), + profile_opt_perm(profile, request, sock->sk, + &maddr.maddr, level, optname, &ad)); } -/* revaliation, get/set attr/getsockname/peername */ +static int inet_label_sock_perm(const struct cred *cred, struct aa_label *label, + const char *op, u32 request, + struct socket *sock) +{ + struct stored_match_addr maddr; + + return label_sk_has_perm1(cred, label, sock->sk, op, request, profile, + ad, + map_sock_addr(sock, ADDR_LOCAL, &maddr, &ad), + profile_sk_perm(profile, request, sock->sk, + &maddr.maddr, &ad)); +} + +/* revalidation, get/set attr/getsockname/peername */ int aa_inet_sock_perm(const char *op, u32 request, struct socket *sock) { - return aa_sk_perm(op, request, sock->sk); + struct aa_sk_ctx *ctx = aa_sock(sock->sk); + struct aa_label *label; + bool needput; + int error; + + if (rcu_access_pointer(ctx->label) == kernel_t) + return 0; + + label = begin_current_label_crit_section(&needput); + error = inet_label_sock_perm(current_cred(), label, op, request, sock); + end_current_label_crit_section(label, needput); + + return error; } int aa_inet_file_perm(const struct cred *subj_cred, struct aa_label *label, const char *op, u32 request, struct socket *sock) { - return aa_label_sk_perm(subj_cred, label, op, request, sock->sk); + u32 sk_req = request & ~NET_PEER_MASK; + struct stored_match_addr laddr; + const struct sock *sk = sock->sk; + int error = 0; + + AA_BUG(!label); + AA_BUG(!sock); + AA_BUG(!sock->sk); + AA_BUG(sk->sk_family != PF_INET && sk->sk_family != PF_INET6, + "family=%d", sk->sk_family); + + /* access to the local sock */ + error = label_sk_has_perm1(subj_cred, label, sock->sk, op, request, + profile, ad, + map_sock_addr(sock, ADDR_LOCAL, &laddr, &ad), + profile_sk_perm(profile, sk_req, sock->sk, &laddr.maddr, + &ad)); + + if (!error) { + struct stored_match_addr raddr; + + /* TODO: have ad here: instead of in CB so we do have to redo */ + error = map_sock_addr(sock, ADDR_REMOTE, &raddr, NULL); + if (!error && raddr.maddr.addrp) { + error = label_sk_has_perm1(subj_cred, label, sock->sk, + op, request, profile, ad, + set_ad_addr(&ad, raddr.addr.sa_family, + false, &raddr.maddr), + profile_remote_perm(profile, sock->sk, + request, + &raddr.maddr, + &laddr.maddr, &ad)); + } + } + + return error; } diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c index 15e42e96b163..f22dca9b0cc3 100644 --- a/security/apparmor/audit.c +++ b/security/apparmor/audit.c @@ -52,7 +52,7 @@ static const char *const aa_class_names[] = { "unknown", "unknown", "net", - "unknown", + "netv9", "label", "posix_mqueue", "io_uring", From 1af45bf33b3c11fcc0ea0813ebfd68d0df342e48 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 29 Jun 2026 16:58:57 -0700 Subject: [PATCH 0508/1328] apparmor: refactor network socket mediation to support compatibility Make it easier for distros to support the network backwards compat patch by refactoring the code to minimize the changes needed. Signed-off-by: John Johansen --- security/apparmor/net.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/security/apparmor/net.c b/security/apparmor/net.c index 678d1417efd6..4d4e9e389080 100644 --- a/security/apparmor/net.c +++ b/security/apparmor/net.c @@ -262,14 +262,15 @@ int aa_profile_af_perm(struct aa_profile *profile, AA_BUG(type < 0 || type >= SOCK_MAX); AA_BUG(profile_unconfined(profile)); - if (profile_unconfined(profile)) - return 0; state = RULE_MEDIATES_NET(rules); - if (!state) - return 0; - state = aa_match_to_prot(rules->policy, state, request, family, type, - protocol, &p, &ad->info); - return aa_do_perms(profile, rules->policy, state, request, p, ad); + if (state) { + state = aa_match_to_prot(rules->policy, state, request, family, + type, protocol, &p, &ad->info); + return aa_do_perms(profile, rules->policy, state, request, p, + ad); + } /* else */ + + return 0; } int aa_af_perm(const struct cred *subj_cred, struct aa_label *label, @@ -298,7 +299,8 @@ int aa_label_sk_perm(const struct cred *subj_cred, struct aa_label *label, ad.subj_cred = subj_cred; error = fn_for_each_confined(label, profile, - aa_profile_af_sk_perm(profile, &ad, request, sk)); + aa_profile_af_perm(profile, &ad, request, sk->sk_family, + sk->sk_type, sk->sk_protocol)); } return error; From a7bc8ccbd279216b0d6eb265423f49d93a7882d6 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 7 Jan 2026 21:37:41 -0800 Subject: [PATCH 0509/1328] apparmor: move netfilter functions next to the LSM network operations Make sure all the network mediation hooks are next to each other in a logical block. This just makes it easier to read/understand the network mediation code. Reviewed-by: Georgia Garcia Signed-off-by: John Johansen --- security/apparmor/lsm.c | 90 +++++++++++++++++++++-------------------- 1 file changed, 46 insertions(+), 44 deletions(-) diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 65db1e5460bf..9d28cbe396fc 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -1707,6 +1707,51 @@ static int apparmor_inet_conn_request(const struct sock *sk, struct sk_buff *skb } #endif +#if defined(CONFIG_NETFILTER) && defined(CONFIG_NETWORK_SECMARK) +static unsigned int apparmor_ip_postroute(void *priv, + struct sk_buff *skb, + const struct nf_hook_state *state) +{ + struct aa_sk_ctx *ctx; + struct sock *sk; + int error; + + if (!skb->secmark) + return NF_ACCEPT; + + sk = skb_to_full_sk(skb); + if (sk == NULL) + return NF_ACCEPT; + + ctx = aa_sock(sk); + rcu_read_lock(); + error = apparmor_secmark_check(rcu_dereference(ctx->label), OP_SENDMSG, + AA_MAY_SEND, skb->secmark, sk); + rcu_read_unlock(); + if (!error) + return NF_ACCEPT; + + return NF_DROP_ERR(-ECONNREFUSED); +} + +static const struct nf_hook_ops apparmor_nf_ops[] = { + { + .hook = apparmor_ip_postroute, + .pf = NFPROTO_IPV4, + .hooknum = NF_INET_POST_ROUTING, + .priority = NF_IP_PRI_SELINUX_FIRST, + }, +#if IS_ENABLED(CONFIG_IPV6) + { + .hook = apparmor_ip_postroute, + .pf = NFPROTO_IPV6, + .hooknum = NF_INET_POST_ROUTING, + .priority = NF_IP6_PRI_SELINUX_FIRST, + }, +#endif +}; +#endif + /* * The cred blob is a pointer to, not an instance of, an aa_label. */ @@ -2424,51 +2469,8 @@ static inline int apparmor_init_sysctl(void) } #endif /* CONFIG_SYSCTL */ + #if defined(CONFIG_NETFILTER) && defined(CONFIG_NETWORK_SECMARK) -static unsigned int apparmor_ip_postroute(void *priv, - struct sk_buff *skb, - const struct nf_hook_state *state) -{ - struct aa_sk_ctx *ctx; - struct sock *sk; - int error; - - if (!skb->secmark) - return NF_ACCEPT; - - sk = skb_to_full_sk(skb); - if (sk == NULL) - return NF_ACCEPT; - - ctx = aa_sock(sk); - rcu_read_lock(); - error = apparmor_secmark_check(rcu_dereference(ctx->label), OP_SENDMSG, - AA_MAY_SEND, skb->secmark, sk); - rcu_read_unlock(); - if (!error) - return NF_ACCEPT; - - return NF_DROP_ERR(-ECONNREFUSED); - -} - -static const struct nf_hook_ops apparmor_nf_ops[] = { - { - .hook = apparmor_ip_postroute, - .pf = NFPROTO_IPV4, - .hooknum = NF_INET_POST_ROUTING, - .priority = NF_IP_PRI_SELINUX_FIRST, - }, -#if IS_ENABLED(CONFIG_IPV6) - { - .hook = apparmor_ip_postroute, - .pf = NFPROTO_IPV6, - .hooknum = NF_INET_POST_ROUTING, - .priority = NF_IP6_PRI_SELINUX_FIRST, - }, -#endif -}; - static int __net_init apparmor_nf_register(struct net *net) { return nf_register_net_hooks(net, apparmor_nf_ops, From 27908a4356f02f06ca4d5d25f456091aa44167d2 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 9 Feb 2026 03:31:35 -0800 Subject: [PATCH 0510/1328] apparmor: move sock_rcv_skb() next to inet_conn_request both of these fns are using ifdef CONFIG_NETWORK_SECMARK and related to AppArmor's secmark based mediation, so move them together. Reviewed-by: Georgia Garcia Signed-off-by: John Johansen --- security/apparmor/lsm.c | 69 ++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 36 deletions(-) diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 9d28cbe396fc..9c5dcaf2fec3 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -1553,42 +1553,6 @@ static int apparmor_socket_shutdown(struct socket *sock, int how) return aa_sock_perm(OP_SHUTDOWN, AA_MAY_SHUTDOWN, sock); } -#ifdef CONFIG_NETWORK_SECMARK -/** - * apparmor_socket_sock_rcv_skb - check perms before associating skb to sk - * @sk: sk to associate @skb with - * @skb: skb to check for perms - * - * Note: can not sleep may be called with locks held - * - * don't want protocol specific in __skb_recv_datagram() - * to deny an incoming connection socket_sock_rcv_skb() - */ -static int apparmor_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) -{ - struct aa_sk_ctx *ctx = aa_sock(sk); - int error; - - if (!skb->secmark) - return 0; - - /* - * If reach here before socket_post_create hook is called, in which - * case label is null, drop the packet. - */ - if (!rcu_access_pointer(ctx->label)) - return -EACCES; - - rcu_read_lock(); - error = apparmor_secmark_check(rcu_dereference(ctx->label), OP_RECVMSG, - AA_MAY_RECEIVE, skb->secmark, sk); - rcu_read_unlock(); - - return error; -} -#endif - - static struct aa_label *sk_peer_get_label(struct sock *sk) { struct aa_sk_ctx *ctx = aa_sock(sk); @@ -1689,6 +1653,39 @@ static void apparmor_sock_graft(struct sock *sk, struct socket *parent) } #ifdef CONFIG_NETWORK_SECMARK +/** + * apparmor_socket_sock_rcv_skb - check perms before associating skb to sk + * @sk: sk to associate @skb with + * @skb: skb to check for perms + * + * Note: can not sleep may be called with locks held + * + * don't want protocol specific in __skb_recv_datagram() + * to deny an incoming connection socket_sock_rcv_skb() + */ +static int apparmor_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) +{ + struct aa_sk_ctx *ctx = aa_sock(sk); + int error; + + if (!skb->secmark) + return 0; + + /* + * If reach here before socket_post_create hook is called, in which + * case label is null, drop the packet. + */ + if (!rcu_access_pointer(ctx->label)) + return -EACCES; + + rcu_read_lock(); + error = apparmor_secmark_check(rcu_dereference(ctx->label), OP_RECVMSG, + AA_MAY_RECEIVE, skb->secmark, sk); + rcu_read_unlock(); + + return error; +} + static int apparmor_inet_conn_request(const struct sock *sk, struct sk_buff *skb, struct request_sock *req) { From ecd4e67a28dd5ff7cdc4f91dc0562704035dffcc Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 30 Jun 2026 19:29:44 -0700 Subject: [PATCH 0511/1328] apparmor: reserve mediation class for packet mediation Packet mediation is going to be added in the future, reserve a class for it. Signed-off-by: John Johansen --- security/apparmor/audit.c | 2 +- security/apparmor/include/apparmor.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c index f22dca9b0cc3..c32828f55bcc 100644 --- a/security/apparmor/audit.c +++ b/security/apparmor/audit.c @@ -67,7 +67,7 @@ static const char *const aa_class_names[] = { "unknown", "unknown", "unknown", - "unknown", + "netv9_packet", "X", "dbus", }; diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h index cc6e3df1bc62..cb06b2f55d49 100644 --- a/security/apparmor/include/apparmor.h +++ b/security/apparmor/include/apparmor.h @@ -36,6 +36,7 @@ #define AA_CLASS_NS 21 #define AA_CLASS_IO_URING 22 +#define AA_CLASS_NETV9_SKB 30 #define AA_CLASS_X 31 #define AA_CLASS_DBUS 32 From 08c2f7c8d4b1434cfae006f3daf4d1bce330b57b Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 8 May 2026 22:30:07 -0700 Subject: [PATCH 0512/1328] apparmor: fix unconfined user namespace restriction forced stack If a task is already confined by a stack the unprivileged transition restriction on unconfined is not correctly, applied. This results in an escape if two transitions through an unconfined profile can be executed. Fix this by pushing the check into the per profile label build. The check will always be done against unconfined and result in a stack of just the unconfined component when necessary. Fixes: 2d9da9b188b8 ("apparmor: allow restricting unprivileged change_profile") Signed-off-by: John Johansen --- security/apparmor/domain.c | 80 +++++++++++++++++++++++++------------- 1 file changed, 54 insertions(+), 26 deletions(-) diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 4154964cdac5..bb2216f17bd9 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -875,6 +875,52 @@ static struct aa_label *label_merge_wrap(struct aa_label *a, struct aa_label *b, return label; } +static bool is_profile_priv_restricted_to_stack(const struct cred *subj_cred, + struct aa_profile *profile) +{ + if (profile_unconfined(profile) && profile == profile->ns->unconfined && + aa_unprivileged_unconfined_restricted && + /* cap_capable returns false (0) if true, hence true here means + * doesn't have capability and the stack will be restricted + */ + cap_capable(current_cred(), &init_user_ns, CAP_MAC_OVERRIDE, + CAP_OPT_NOAUDIT)) + return true; + return false; +} + +static const char *stack_msg = "change_profile unprivileged unconfined converted to stacking"; + +static struct aa_label *priv_restricted_transition(const struct cred *subj_cred, + struct aa_profile *profile, + const char *op, u32 request, + const char *name, + struct aa_label *transition, + gfp_t gfp) +{ + if (!is_profile_priv_restricted_to_stack(subj_cred, profile)) + return aa_get_newest_label(transition); + + /* transition allowed but only via stack */ + struct aa_label *target = label_merge_wrap(&profile->label, + transition, gfp); + if (IS_ERR_OR_NULL(target)) + return target; + + /* doing this here is less than optimal but good enough until the + * fs mediation rework lands + */ + struct aa_perms perms = { + .allow = request, + .audit = request, + }; + aa_audit_file(subj_cred, profile, &perms, op, + request, name, NULL, target, + subj_cred->euid, stack_msg, 0); + + return target; +} + static struct aa_label *handle_onexec(const struct cred *subj_cred, struct aa_label *label, struct aa_label *onexec, bool stack, @@ -903,7 +949,10 @@ static struct aa_label *handle_onexec(const struct cred *subj_cred, new = fn_label_build_in_scope(label, profile, GFP_KERNEL, stack ? label_merge_wrap(&profile->label, onexec, GFP_KERNEL) - : aa_get_newest_label(onexec), + : priv_restricted_transition(subj_cred, profile, + OP_CHANGE_ONEXEC, AA_MAY_ONEXEC, + bprm->filename, onexec, + GFP_KERNEL), profile_transition(subj_cred, profile, bprm, buffer, cond, unsafe)); AA_BUG(!new); @@ -1407,8 +1456,6 @@ static int change_profile_perms_wrapper(const char *op, const char *name, return error; } -static const char *stack_msg = "change_profile unprivileged unconfined converted to stacking"; - /** * aa_change_profile - perform a one-way profile transition * @fqname: name of profile may include namespace (NOT NULL) @@ -1468,28 +1515,6 @@ int aa_change_profile(const char *fqname, int flags) op = OP_CHANGE_PROFILE; } - /* This should move to a per profile test. Requires pushing build - * into callback - */ - if (!stack && unconfined(label) && - label == &labels_ns(label)->unconfined->label && - aa_unprivileged_unconfined_restricted && - /* TODO: refactor so this check is a fn */ - cap_capable(current_cred(), &init_user_ns, CAP_MAC_OVERRIDE, - CAP_OPT_NOAUDIT)) { - /* regardless of the request in this case apparmor - * stacks against unconfined so admin set policy can't be - * by-passed - */ - stack = true; - perms.audit = request; - (void) fn_for_each_in_scope(label, profile, - aa_audit_file(subj_cred, profile, &perms, op, - request, auditname, NULL, target, - GLOBAL_ROOT_UID, stack_msg, 0)); - perms.audit = 0; - } - if (*fqname == '&') { stack = true; /* don't have label_parse() do stacking */ @@ -1560,7 +1585,10 @@ int aa_change_profile(const char *fqname, int flags) /* stacking is always a subset, so only check the nonstack case */ if (!stack) { new = fn_label_build_in_scope(label, profile, GFP_KERNEL, - aa_get_label(target), + priv_restricted_transition(subj_cred, profile, + op, request, + auditname, target, + GFP_KERNEL), aa_get_label(&profile->label)); AA_BUG(!new); if (IS_ERR(new)) From 812aa0aa67d50c251d9788f1d225438e747507f0 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sun, 25 Jun 2023 01:48:32 -0700 Subject: [PATCH 0513/1328] apparmor: refactor xattr attachment, to take the file path The full bprm does not need to be passed into xattrs_match, so only pass in the path, and propagate the change backup the call stack until bprm is actually needed. Reviewed-by: Georgia Garcia Signed-off-by: John Johansen --- security/apparmor/domain.c | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index bb2216f17bd9..3b80db6663ad 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -304,28 +304,31 @@ static int change_profile_perms(struct aa_profile *profile, /** * aa_xattrs_match - check whether a file matches the xattrs defined in profile - * @bprm: binprm struct for the process to validate + * @path: path for file being matched (NOT NULL) * @profile: profile to match against (NOT NULL) * @state: state to start match in * * Returns: number of extended attributes that matched, or < 0 on error */ -static int aa_xattrs_match(const struct linux_binprm *bprm, +static int aa_xattrs_match(const struct path *path, struct aa_profile *profile, aa_state_t state) { + AA_BUG(!path); + AA_BUG(!profile); + int i; struct dentry *d; char *value = NULL; struct aa_attachment *attach = &profile->attach; int size, value_size = 0, ret = attach->xattr_count; - if (!bprm || !attach->xattr_count) + if (!attach->xattr_count) return 0; might_sleep(); /* transition from exec match to xattr set */ state = aa_dfa_outofband_transition(attach->xmatch->dfa, state); - d = bprm->file->f_path.dentry; + d = path->dentry; for (i = 0; i < attach->xattr_count; i++) { size = vfs_getxattr_alloc(&nop_mnt_idmap, d, attach->xattrs[i], @@ -373,7 +376,7 @@ static int aa_xattrs_match(const struct linux_binprm *bprm, /** * find_attach - do attachment search for unconfined processes - * @bprm: binprm structure of transitioning task + * @path: path of file in question (NOT NULL) * @ns: the current namespace (NOT NULL) * @head: profile list to walk (NOT NULL) * @name: to match against (NOT NULL) @@ -388,7 +391,7 @@ static int aa_xattrs_match(const struct linux_binprm *bprm, * * Returns: label or NULL if no match found */ -static struct aa_label *find_attach(const struct linux_binprm *bprm, +static struct aa_label *find_attach(const struct path *path, struct aa_ns *ns, struct list_head *head, const char *name, const char **info) { @@ -396,6 +399,7 @@ static struct aa_label *find_attach(const struct linux_binprm *bprm, bool conflict = false; struct aa_profile *profile, *candidate = NULL; + AA_BUG(!path); AA_BUG(!name); AA_BUG(!head); @@ -435,13 +439,13 @@ static struct aa_label *find_attach(const struct linux_binprm *bprm, if (count < candidate_len) continue; - if (bprm && attach->xattr_count) { + if (attach->xattr_count) { long rev = READ_ONCE(ns->revision); if (!aa_get_profile_not0(profile)) goto restart; rcu_read_unlock(); - ret = aa_xattrs_match(bprm, profile, + ret = aa_xattrs_match(path, profile, state); rcu_read_lock(); aa_put_profile(profile); @@ -557,7 +561,7 @@ struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex, /** * x_to_label - get target label for a given xindex * @profile: current profile (NOT NULL) - * @bprm: binprm structure of transitioning task + * @path: path of file in question * @name: name to lookup (NOT NULL) * @xindex: index into x transition table * @lookupname: returns: name used in lookup if one was specified (NOT NULL) @@ -568,7 +572,7 @@ struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex, * Returns: refcounted label or NULL if not found available */ static struct aa_label *x_to_label(struct aa_profile *profile, - const struct linux_binprm *bprm, + const struct path *path, const char *name, u32 xindex, const char **lookupname, const char **info) @@ -599,11 +603,11 @@ static struct aa_label *x_to_label(struct aa_profile *profile, case AA_X_NAME: if (xindex & AA_X_CHILD) /* released by caller */ - new = find_attach(bprm, ns, &profile->base.profiles, + new = find_attach(path, ns, &profile->base.profiles, name, info); else /* released by caller */ - new = find_attach(bprm, ns, &ns->base.profiles, + new = find_attach(path, ns, &ns->base.profiles, name, info); *lookupname = name; break; @@ -691,7 +695,7 @@ static struct aa_label *profile_transition(const struct cred *subj_cred, } if (profile_unconfined(profile)) { - new = find_attach(bprm, profile->ns, + new = find_attach(&bprm->file->f_path, profile->ns, &profile->ns->base.profiles, name, &info); /* info set -> something unusual that we should report * Currently this is only conflicting attachments, but other @@ -721,8 +725,8 @@ static struct aa_label *profile_transition(const struct cred *subj_cred, state = aa_str_perms(rules->file, state, name, cond, &perms); if (perms.allow & MAY_EXEC) { /* exec permission determine how to transition */ - new = x_to_label(profile, bprm, name, perms.xindex, &target, - &info); + new = x_to_label(profile, &bprm->file->f_path, name, + perms.xindex, &target, &info); if (new && new->proxy == profile->label.proxy && info) { /* Force audit on conflicting attachment fallback * Because perms is never used again after this audit From c37e23f84a361aeb319edd60021604336c8cc57a Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 18 Apr 2024 23:10:21 -0700 Subject: [PATCH 0514/1328] apparmor: fix race condition in label replacement label replacement can result in the need for locking on two separate trees. Currently this is done by locking the tree to remove and then the tree to add to. For compound labels the race can result in the old label proxy pointing to the the new label that lost the race and that was not inserted in to the new tree. This does not break mediation, but it does result in a task that will not update its profile correctly on future mediation, and that will leak its refcount due to a circular reference in its proxy, resulting in a memory leak. Signed-off-by: John Johansen --- security/apparmor/label.c | 60 +++++++++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 12 deletions(-) diff --git a/security/apparmor/label.c b/security/apparmor/label.c index a165cadf8249..32efef2f617a 100644 --- a/security/apparmor/label.c +++ b/security/apparmor/label.c @@ -796,6 +796,44 @@ bool aa_label_remove(struct aa_label *label) return res; } +enum ls_lock_class { + AA_LS_LOCK_FIRST, + AA_LS_LOCK_SECOND, +}; + +#define write_lock_irqsave_nested(L, F, SC) write_lock_irqsave(L, F) + +static void ns_ls_double_lock(struct aa_ns *ns1, struct aa_ns *ns2, + unsigned long *flags) +{ + if (likely(ns1 == ns2)) { + write_lock_irqsave(&ns1->labels.lock, *flags); + return; + } + + /* ordered by namespace hierarchy (walked in nesting order in + * labels_update. If at the same level by address order + */ + if ((ns1->level > ns2->level) || + (ns1->level == ns2->level && ns1 > ns2)) + swap(ns1, ns2); + + write_lock_irqsave_nested(&ns1->labels.lock, *flags, AA_LS_LOCK_FIRST); + write_lock_nested(&ns2->labels.lock, AA_LS_LOCK_SECOND); +} + +static void ns_ls_double_unlock(struct aa_ns *ns1, struct aa_ns *ns2, + unsigned long flags) +{ + if (likely(ns1 == ns2)) { + write_unlock_irqrestore(&ns1->labels.lock, flags); + return; + } + /* order doesn't matter on unlock, except flags restore must be last */ + write_unlock(&ns2->labels.lock); + write_unlock_irqrestore(&ns1->labels.lock, flags); +} + /** * aa_label_replace - replace a label @old with a new version @new * @old: label to replace @@ -803,36 +841,34 @@ bool aa_label_remove(struct aa_label *label) * * Returns: true if @old was in tree and replaced * else @old was not in tree, and @new was not inserted + * + * replacement can involve two different labelsets so has to be + * handled very careful, as a double lock may be required. */ bool aa_label_replace(struct aa_label *old, struct aa_label *new) { + struct aa_ns *ons = labels_ns(old); + struct aa_ns *nns = labels_ns(new); unsigned long flags; bool res; - if (name_is_shared(old, new) && labels_ns(old) == labels_ns(new)) { - write_lock_irqsave(&labels_set(old)->lock, flags); + ns_ls_double_lock(ons, nns, &flags); + if (ons == nns && name_is_shared(old, new)) { if (old->proxy != new->proxy) __proxy_share(old, new); else __aa_proxy_redirect(old, new); res = __label_replace(old, new); - write_unlock_irqrestore(&labels_set(old)->lock, flags); } else { struct aa_label *l; - struct aa_labelset *ls = labels_set(old); - write_lock_irqsave(&ls->lock, flags); + /* will redirect old proxy to new */ res = __label_remove(old, new); - if (labels_ns(old) != labels_ns(new)) { - write_unlock_irqrestore(&ls->lock, flags); - ls = labels_set(new); - write_lock_irqsave(&ls->lock, flags); - } - l = __label_insert(ls, new, true); + l = __label_insert(&nns->labels, new, true); res = (l == new); - write_unlock_irqrestore(&ls->lock, flags); aa_put_label(l); } + ns_ls_double_unlock(ons, nns, flags); return res; } From ef6f394039973716dc61fb82fc51243fb467caa9 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 23 Jul 2026 15:41:38 -0700 Subject: [PATCH 0515/1328] apparmor: make table entry count last enum for static tables Instead of keeping an external define for the various tables indexed by an enum, make the size the last entry of the enum so the table size will get updated correctly with changes to the enum. Reviewed-by: Georgia Garcia Signed-off-by: John Johansen --- security/apparmor/include/audit.h | 4 ++-- security/apparmor/include/policy.h | 2 +- security/apparmor/lsm.c | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index aa00b34404f9..75ae2cc96d16 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -21,13 +21,13 @@ #include "label.h" extern const char *const audit_mode_names[]; -#define AUDIT_MAX_INDEX 5 enum audit_mode { AUDIT_NORMAL, /* follow normal auditing of accesses */ AUDIT_QUIET_DENIED, /* quiet all denied access messages */ AUDIT_QUIET, /* quiet all messages */ AUDIT_NOQUIET, /* do not quiet audit messages */ - AUDIT_ALL /* audit all accesses */ + AUDIT_ALL, /* audit all accesses */ + AUDIT_MODE_NAMES_COUNT /* Must be last entry */ }; enum audit_type { diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index f530436e2b9d..eae9db08fb93 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -36,7 +36,6 @@ extern int unprivileged_userns_apparmor_policy; extern int aa_unprivileged_unconfined_restricted; extern const char *const aa_profile_mode_names[]; -#define APPARMOR_MODE_NAMES_MAX_INDEX 4 #define PROFILE_MODE(_profile, _mode) \ ((aa_g_profile_mode == (_mode)) || \ @@ -75,6 +74,7 @@ enum profile_mode { APPARMOR_KILL, /* kill task on access violation */ APPARMOR_UNCONFINED, /* profile set to unconfined */ APPARMOR_USER, /* modified complain mode to userspace */ + PROFILE_MODE_NAMES_COUNT /* Must be last entry */ }; diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 9c5dcaf2fec3..5e21dd78d608 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -2190,7 +2190,7 @@ static int param_set_audit(const char *val, const struct kernel_param *kp) if (apparmor_initialized && !aa_current_policy_admin_capable(NULL)) return -EPERM; - i = match_string(audit_mode_names, AUDIT_MAX_INDEX, val); + i = match_string(audit_mode_names, AUDIT_MODE_NAMES_COUNT, val); if (i < 0) return -EINVAL; @@ -2218,7 +2218,7 @@ static int param_set_mode(const char *val, const struct kernel_param *kp) if (apparmor_initialized && !aa_current_policy_admin_capable(NULL)) return -EPERM; - i = match_string(aa_profile_mode_names, APPARMOR_MODE_NAMES_MAX_INDEX, + i = match_string(aa_profile_mode_names, PROFILE_MODE_NAMES_COUNT, val); if (i < 0) return -EINVAL; From 1bd606110942d5014c9eee967c7222862936a20d Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 23 Jul 2026 15:44:13 -0700 Subject: [PATCH 0516/1328] apparmor: fix error debug output in fn_label_build checking PTR_ERROR() is not correct to just determine if any error occured, instead use the IS_ERR macro and also output the PTR_ERR as part of the debug message. Signed-off-by: John Johansen --- security/apparmor/include/lib.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/security/apparmor/include/lib.h b/security/apparmor/include/lib.h index e3c8cb044a90..d59eee5603a4 100644 --- a/security/apparmor/include/lib.h +++ b/security/apparmor/include/lib.h @@ -338,8 +338,8 @@ __do_cleanup: \ __new_ = (FN); \ } \ __done: \ - if (PTR_ERR(__new_)) \ - AA_DEBUG(DEBUG_LABEL, "label build failed\n"); \ + if (IS_ERR(__new_)) \ + AA_DEBUG(DEBUG_LABEL, "label build failed %ld\n", PTR_ERR(__new_)); \ (__new_); \ }) From a3ed5d43f786bdce14cd9eeeb7ca7ab695c82948 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 23 Jul 2026 15:46:45 -0700 Subject: [PATCH 0517/1328] apparmor: mark static tables and structs as read only static tables, and structs that are initialized as part of their data section or during init should be read only to protect against accidental or malicous changes. Signed-off-by: John Johansen --- security/apparmor/file.c | 2 +- security/apparmor/include/file.h | 2 +- security/apparmor/include/match.h | 2 +- security/apparmor/include/perms.h | 4 ++-- security/apparmor/lib.c | 15 ++++++++------- security/apparmor/lsm.c | 8 ++++---- security/apparmor/match.c | 6 +++--- security/apparmor/mount.c | 3 ++- 8 files changed, 22 insertions(+), 20 deletions(-) diff --git a/security/apparmor/file.c b/security/apparmor/file.c index e67ca65b7bd9..d5264af92f07 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -93,7 +93,7 @@ static void file_audit_cb(struct audit_buffer *ab, void *va) * Returns: %0 or error on failure */ int aa_audit_file(const struct cred *subj_cred, - struct aa_profile *profile, struct aa_perms *perms, + struct aa_profile *profile, const struct aa_perms *perms, const char *op, u32 request, const char *name, const char *target, struct aa_label *tlabel, kuid_t ouid, const char *info, int error) diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h index ef60f99bc5ae..1614c07fc53e 100644 --- a/security/apparmor/include/file.h +++ b/security/apparmor/include/file.h @@ -72,7 +72,7 @@ struct path_cond { #define COMBINED_PERM_MASK(X) ((X).allow | (X).audit | (X).quiet | (X).kill) int aa_audit_file(const struct cred *cred, - struct aa_profile *profile, struct aa_perms *perms, + struct aa_profile *profile, const struct aa_perms *perms, const char *op, u32 request, const char *name, const char *target, struct aa_label *tlabel, kuid_t ouid, const char *info, int error); diff --git a/security/apparmor/include/match.h b/security/apparmor/include/match.h index 7accb1c39849..094f6b998ea8 100644 --- a/security/apparmor/include/match.h +++ b/security/apparmor/include/match.h @@ -125,7 +125,7 @@ static inline size_t table_size(size_t len, size_t el_size) #define aa_state_t unsigned int -struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags); +struct aa_dfa *aa_dfa_unpack(const void *blob, size_t size, int flags); aa_state_t aa_dfa_match_len(struct aa_dfa *dfa, aa_state_t start, const char *str, int len); aa_state_t aa_dfa_match(struct aa_dfa *dfa, aa_state_t start, diff --git a/security/apparmor/include/perms.h b/security/apparmor/include/perms.h index 37a3781b99a0..bbab28ed7abb 100644 --- a/security/apparmor/include/perms.h +++ b/security/apparmor/include/perms.h @@ -96,8 +96,8 @@ struct aa_perms { #define AA_INDEX_NONE 0 #define ALL_PERMS_MASK 0xffffffff -extern struct aa_perms nullperms; -extern struct aa_perms allperms; +extern const struct aa_perms nullperms; +extern const struct aa_perms allperms; /** * aa_perms_accum_raw - accumulate perms with out masking off overlapping perms diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index e41ff57798b2..39f824c65d88 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -20,8 +20,8 @@ #include "include/perms.h" #include "include/policy.h" -struct aa_perms nullperms; -struct aa_perms allperms = { .allow = ALL_PERMS_MASK, +const struct aa_perms nullperms; +const struct aa_perms allperms = { .allow = ALL_PERMS_MASK, .quiet = ALL_PERMS_MASK, .hide = ALL_PERMS_MASK }; @@ -30,7 +30,7 @@ struct val_table_ent { int value; }; -static struct val_table_ent debug_values_table[] = { +static const struct val_table_ent debug_values_table[] = { { "N", DEBUG_NONE }, { "none", DEBUG_NONE }, { "n", DEBUG_NONE }, @@ -49,10 +49,11 @@ static struct val_table_ent debug_values_table[] = { { NULL, 0 } }; -static struct val_table_ent *val_table_find_ent(struct val_table_ent *table, - const char *name, size_t len) +static const struct val_table_ent * +val_table_find_ent(const struct val_table_ent *table, + const char *name, size_t len) { - struct val_table_ent *entry; + const struct val_table_ent *entry; for (entry = table; entry->str != NULL; entry++) { if (strncmp(entry->str, name, len) == 0 && @@ -64,7 +65,7 @@ static struct val_table_ent *val_table_find_ent(struct val_table_ent *table, int aa_parse_debug_params(const char *str) { - struct val_table_ent *ent; + const struct val_table_ent *ent; const char *next; int val = 0; diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 5e21dd78d608..d502ad0ac26f 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -2500,16 +2500,16 @@ static int __init apparmor_nf_ip_init(void) } #endif -static char nulldfa_src[] __aligned(8) = { +static const char nulldfa_src[] __aligned(8) = { #include "nulldfa.in" }; -static struct aa_dfa *nulldfa; +static struct aa_dfa *nulldfa __ro_after_init; static char stacksplitdfa_src[] __aligned(8) = { #include "stacksplitdfa.in" }; -struct aa_dfa *stacksplitdfa; -struct aa_policydb *nullpdb; +struct aa_dfa *stacksplitdfa __ro_after_init; +struct aa_policydb *nullpdb __ro_after_init; static int __init aa_setup_dfa_engine(void) { diff --git a/security/apparmor/match.c b/security/apparmor/match.c index d43ff34d705c..52668134da9d 100644 --- a/security/apparmor/match.c +++ b/security/apparmor/match.c @@ -31,7 +31,7 @@ * * NOTE: must be freed by kvfree (not kfree) */ -static struct table_header *unpack_table(char *blob, size_t bsize) +static struct table_header *unpack_table(const char *blob, size_t bsize) { struct table_header *table = ERR_PTR(-EPROTO); struct table_header th; @@ -312,11 +312,11 @@ static struct table_header *remap_data16_to_data32(struct table_header *old) * * Returns: an unpacked dfa ready for matching or ERR_PTR on failure */ -struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags) +struct aa_dfa *aa_dfa_unpack(const void *blob, size_t size, int flags) { int hsize; int error = -ENOMEM; - char *data = blob; + const char *data = blob; struct table_header *table = NULL; struct aa_dfa *dfa = kzalloc_obj(struct aa_dfa); if (!dfa) diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c index 2f5d918832c1..8402d29ad524 100644 --- a/security/apparmor/mount.c +++ b/security/apparmor/mount.c @@ -136,7 +136,8 @@ static int audit_mount(const struct cred *subj_cred, const char *name, const char *src_name, const char *type, const char *trans, unsigned long flags, const void *data, u32 request, - struct aa_perms *perms, const char *info, int error) + const struct aa_perms *perms, const char *info, + int error) { int audit_type = AUDIT_APPARMOR_AUTO; DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_MOUNT, op); From 97dd3f51bf0cb111a2c1cdef7ecca48e31223cb7 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sun, 3 Aug 2025 20:35:10 -0700 Subject: [PATCH 0518/1328] apparmor: add audit mode to provide a mechanism to silence complain messages Complain messages can be very noisy and fill the logs quickly. Allow complain (allow) messages to be silenced separate from denied messages. Signed-off-by: John Johansen --- security/apparmor/audit.c | 1 + security/apparmor/file.c | 11 +++++++++-- security/apparmor/include/audit.h | 1 + security/apparmor/lib.c | 19 +++++++++++-------- 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c index c32828f55bcc..97d8151b5ff6 100644 --- a/security/apparmor/audit.c +++ b/security/apparmor/audit.c @@ -20,6 +20,7 @@ const char *const audit_mode_names[] = { "normal", "quiet_denied", + "quiet.allowed", "quiet", "noquiet", "all" diff --git a/security/apparmor/file.c b/security/apparmor/file.c index d5264af92f07..3e74b613db32 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -98,6 +98,8 @@ int aa_audit_file(const struct cred *subj_cred, const char *target, struct aa_label *tlabel, kuid_t ouid, const char *info, int error) { + u32 quiet = perms->quiet; + u32 complain = perms->complain; int type = AUDIT_APPARMOR_AUTO; DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_TASK, AA_CLASS_FILE, op); @@ -112,6 +114,8 @@ int aa_audit_file(const struct cred *subj_cred, ad.error = error; ad.common.u.tsk = NULL; + if (COMPLAIN_MODE(profile)) + complain |= ~(perms->allow | perms->deny); if (likely(!ad.error)) { u32 mask = perms->audit; @@ -132,11 +136,14 @@ int aa_audit_file(const struct cred *subj_cred, if (ad.request & perms->kill) type = AUDIT_APPARMOR_KILL; + if (AUDIT_MODE(profile) == AUDIT_QUIET_ALLOWED) + quiet |= complain | perms->allow; + /* quiet known rejects, assumes quiet and kill do not overlap */ - if ((ad.request & perms->quiet) && + if ((ad.request & quiet) && AUDIT_MODE(profile) != AUDIT_NOQUIET && AUDIT_MODE(profile) != AUDIT_ALL) - ad.request &= ~perms->quiet; + ad.request &= ~quiet; if (!ad.request) return ad.error; diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index 75ae2cc96d16..da95b5569d68 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -24,6 +24,7 @@ extern const char *const audit_mode_names[]; enum audit_mode { AUDIT_NORMAL, /* follow normal auditing of accesses */ AUDIT_QUIET_DENIED, /* quiet all denied access messages */ + AUDIT_QUIET_ALLOWED, /* quiet all allowed access messages */ AUDIT_QUIET, /* quiet all messages */ AUDIT_NOQUIET, /* do not quiet audit messages */ AUDIT_ALL, /* audit all accesses */ diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index 39f824c65d88..0c5c51c326fa 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -363,6 +363,13 @@ void aa_audit_perm_mask(struct audit_buffer *ab, u32 mask, const char *chrs, */ void aa_apply_modes_to_perms(struct aa_profile *profile, struct aa_perms *perms) { + if (KILL_MODE(profile)) + perms->kill = ~perms->allow; + else if (COMPLAIN_MODE(profile)) + perms->complain |= ~(perms->allow | perms->deny); + else if (USER_MODE(profile)) + perms->prompt |= ~(perms->allow | perms->deny); + switch (AUDIT_MODE(profile)) { case AUDIT_ALL: perms->audit = ALL_PERMS_MASK; @@ -374,16 +381,12 @@ void aa_apply_modes_to_perms(struct aa_profile *profile, struct aa_perms *perms) perms->audit = 0; fallthrough; case AUDIT_QUIET_DENIED: - perms->quiet = ALL_PERMS_MASK; + perms->quiet |= ~perms->allow; + break; + case AUDIT_QUIET_ALLOWED: + perms->quiet |= perms->complain | perms->allow; break; } - - if (KILL_MODE(profile)) - perms->kill = ALL_PERMS_MASK; - else if (COMPLAIN_MODE(profile)) - perms->complain = ALL_PERMS_MASK; - else if (USER_MODE(profile)) - perms->prompt = ALL_PERMS_MASK; } void aa_profile_match_label(struct aa_profile *profile, From b9f2181b90ec047488dcb4fb1c631d417aff07e0 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 28 Jul 2026 13:14:49 -0700 Subject: [PATCH 0519/1328] apparmor: fix auditing of mount binary data AppArmor only mediates non-binary mount data, and should only audit the mount data if it is non-binary. Signed-off-by: John Johansen --- security/apparmor/mount.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c index 8402d29ad524..537d22eb0303 100644 --- a/security/apparmor/mount.c +++ b/security/apparmor/mount.c @@ -345,8 +345,8 @@ static int match_mnt_path_str(const struct cred *subj_cred, audit: return audit_mount(subj_cred, profile, OP_MOUNT, mntpnt, devname, - type, NULL, - flags, data, AA_MAY_MOUNT, &perms, info, error); + type, NULL, flags, !binary ? data : NULL, + AA_MAY_MOUNT, &perms, info, error); } /** From 6c27cd23bd43c8adb1af6a96abfeee13b10da48d Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 27 Jul 2026 05:36:53 -0700 Subject: [PATCH 0520/1328] apparmor: refactory mount to use check_perms Move the mount permissions check to use the common backend aa_check_perms() to check permissions. This will make it so caching, audit, complain, logic can be handled consistently in a single place. Signed-off-by: John Johansen --- security/apparmor/audit.c | 33 ++++ security/apparmor/include/audit.h | 5 + security/apparmor/lib.c | 12 +- security/apparmor/mount.c | 262 ++++++++++++------------------ 4 files changed, 144 insertions(+), 168 deletions(-) diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c index 97d8151b5ff6..5752bb93aaaa 100644 --- a/security/apparmor/audit.c +++ b/security/apparmor/audit.c @@ -140,6 +140,17 @@ static void audit_pre(struct audit_buffer *ab, void *va) } } +int aa_select_audit_type(u32 denied, const struct aa_perms *perms) +{ + if (likely(!denied)) + return AUDIT_APPARMOR_AUDIT; + else if (denied & perms->kill) + return AUDIT_APPARMOR_KILL; + else if (denied == (denied & perms->complain)) + return AUDIT_APPARMOR_ALLOWED; + return AUDIT_APPARMOR_DENIED; +} + /** * aa_audit_msg - Log a message to the audit subsystem * @type: audit type for the message @@ -153,6 +164,28 @@ void aa_audit_msg(int type, struct apparmor_audit_data *ad, common_lsm_audit(&ad->common, audit_pre, cb); } +int aa_audit_perm_error(struct aa_label *label, u32 request, int error, + struct apparmor_audit_data *ad, + void (*cb)(struct audit_buffer *, void *)) +{ + int type = aa_select_audit_type(request, &nullperms); + + if (ad) { + struct aa_profile *profile; + struct label_it i; + + ad->request = request; + ad->denied = request; + ad->error = error; + label_for_each_confined(i, label, profile) { + ad->subj_label = &profile->label; + aa_audit_msg(type, ad, cb); + } + } + + return error; +} + /** * aa_audit - Log a profile based audit event to the audit subsystem * @type: audit type for the message diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index da95b5569d68..987ed4441fd3 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -184,6 +184,8 @@ struct apparmor_audit_data { .common.apparmor_audit_data = &NAME, \ }; +int aa_select_audit_type(u32 denied, const struct aa_perms *perms); + void aa_audit_msg(int type, struct apparmor_audit_data *ad, void (*cb) (struct audit_buffer *, void *)); int aa_audit(int type, struct aa_profile *profile, @@ -197,6 +199,9 @@ int aa_audit(int type, struct aa_profile *profile, (AD)->error; \ }) +int aa_audit_perm_error(struct aa_label *label, u32 request, int error, + struct apparmor_audit_data *ad, + void (*cb)(struct audit_buffer *, void *)); static inline int complain_error(int error) { diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index 0c5c51c326fa..5d33252204fe 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -425,7 +425,7 @@ int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms, u32 request, struct apparmor_audit_data *ad, void (*cb)(struct audit_buffer *, void *)) { - int type, error; + int error; u32 denied = request & (~perms->allow | perms->deny); if (likely(!denied)) { @@ -434,18 +434,10 @@ int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms, if (!request || !ad) return 0; - type = AUDIT_APPARMOR_AUDIT; error = 0; } else { error = -EACCES; - if (denied & perms->kill) - type = AUDIT_APPARMOR_KILL; - else if (denied == (denied & perms->complain)) - type = AUDIT_APPARMOR_ALLOWED; - else - type = AUDIT_APPARMOR_DENIED; - if (denied == (denied & perms->hide)) error = -ENOENT; @@ -454,6 +446,8 @@ int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms, return error; } + int type = aa_select_audit_type(denied, perms); + if (ad) { ad->subj_label = &profile->label; ad->request = request; diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c index 537d22eb0303..5aaa4f878d92 100644 --- a/security/apparmor/mount.c +++ b/security/apparmor/mount.c @@ -24,6 +24,10 @@ #include "include/policy.h" +#define DEFINE_AUDIT_MOUNT(NAME, OP, CRED) \ + DEFINE_AUDIT_DATA(NAME, LSM_AUDIT_DATA_NONE, AA_CLASS_MOUNT, OP);\ + NAME.subj_cred = (CRED) + static void audit_mnt_flags(struct audit_buffer *ab, unsigned long flags) { if (flags & MS_RDONLY) @@ -113,78 +117,6 @@ static void audit_cb(struct audit_buffer *ab, void *va) } } -/** - * audit_mount - handle the auditing of mount operations - * @subj_cred: cred of the subject - * @profile: the profile being enforced (NOT NULL) - * @op: operation being mediated (NOT NULL) - * @name: name of object being mediated (MAYBE NULL) - * @src_name: src_name of object being mediated (MAYBE_NULL) - * @type: type of filesystem (MAYBE_NULL) - * @trans: name of trans (MAYBE NULL) - * @flags: filesystem independent mount flags - * @data: filesystem mount flags - * @request: permissions requested - * @perms: the permissions computed for the request (NOT NULL) - * @info: extra information message (MAYBE NULL) - * @error: 0 if operation allowed else failure error code - * - * Returns: %0 or error on failure - */ -static int audit_mount(const struct cred *subj_cred, - struct aa_profile *profile, const char *op, - const char *name, const char *src_name, - const char *type, const char *trans, - unsigned long flags, const void *data, u32 request, - const struct aa_perms *perms, const char *info, - int error) -{ - int audit_type = AUDIT_APPARMOR_AUTO; - DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_MOUNT, op); - - if (likely(!error)) { - u32 mask = perms->audit; - - if (unlikely(AUDIT_MODE(profile) == AUDIT_ALL)) - mask = 0xffff; - - /* mask off perms that are not being force audited */ - request &= mask; - - if (likely(!request)) - return 0; - audit_type = AUDIT_APPARMOR_AUDIT; - } else { - /* only report permissions that were denied */ - request = request & ~perms->allow; - - if (request & perms->kill) - audit_type = AUDIT_APPARMOR_KILL; - - /* quiet known rejects, assumes quiet and kill do not overlap */ - if ((request & perms->quiet) && - AUDIT_MODE(profile) != AUDIT_NOQUIET && - AUDIT_MODE(profile) != AUDIT_ALL) - request &= ~perms->quiet; - - if (!request) - return error; - } - - ad.subj_cred = subj_cred; - ad.name = name; - ad.mnt.src_name = src_name; - ad.mnt.type = type; - ad.mnt.trans = trans; - ad.mnt.flags = flags; - if (data && (perms->audit & AA_AUDIT_DATA)) - ad.mnt.data = data; - ad.info = info; - ad.error = error; - - return aa_audit(audit_type, profile, &ad, audit_cb); -} - /** * match_mnt_flags - Do an ordered match on mount flags * @dfa: dfa to match against @@ -290,7 +222,6 @@ static int path_flags(struct aa_profile *profile, const struct path *path) /** * match_mnt_path_str - handle path matching for mount - * @subj_cred: cred of confined subject * @profile: the confining profile * @mntpath: for the mntpnt (NOT NULL) * @buffer: buffer to be used to lookup mntpath @@ -300,18 +231,19 @@ static int path_flags(struct aa_profile *profile, const struct path *path) * @data: fs mount data (MAYBE NULL) * @binary: whether @data is binary * @devinfo: error str if (IS_ERR(@devname)) + * @ad: apparmor audit data structure * * Returns: 0 on success else error */ -static int match_mnt_path_str(const struct cred *subj_cred, - struct aa_profile *profile, +static int match_mnt_path_str(struct aa_profile *profile, const struct path *mntpath, char *buffer, const char *devname, const char *type, unsigned long flags, void *data, bool binary, - const char *devinfo) + const char *devinfo, + struct apparmor_audit_data *ad) { struct aa_perms perms = { }; - const char *mntpnt = NULL, *info = NULL; + const char *mntpnt = NULL; struct aa_ruleset *rules = profile->label.rules[0]; int pos, error; @@ -322,36 +254,37 @@ static int match_mnt_path_str(const struct cred *subj_cred, if (!RULE_MEDIATES(rules, AA_CLASS_MOUNT)) return 0; + ad->mnt.type = type; + error = aa_path_name(mntpath, path_flags(profile, mntpath), buffer, - &mntpnt, &info, profile->disconnected); + &mntpnt, &ad->info, profile->disconnected); if (error) - goto audit; + return aa_audit_perm_error(&profile->label, AA_MAY_MOUNT, + error, ad, audit_cb); + ad->name = mntpnt; + if (IS_ERR(devname)) { error = PTR_ERR(devname); - devname = NULL; - info = devinfo; - goto audit; + ad->info = devinfo; + return aa_audit_perm_error(&profile->label, AA_MAY_MOUNT, + error, ad, audit_cb); } + ad->mnt.src_name = devname; - error = -EACCES; pos = do_match_mnt(rules->policy, rules->policy->start[AA_CLASS_MOUNT], mntpnt, devname, type, flags, data, binary, &perms); - if (pos) { - info = mnt_info_table[pos]; - goto audit; - } - error = 0; + if (pos) + ad->info = mnt_info_table[pos]; -audit: - return audit_mount(subj_cred, profile, OP_MOUNT, mntpnt, devname, - type, NULL, flags, !binary ? data : NULL, - AA_MAY_MOUNT, &perms, info, error); + aa_apply_modes_to_perms(profile, &perms); + if (data && !binary && (perms.audit & AA_AUDIT_DATA)) + ad->mnt.data = data; + return aa_check_perms(profile, &perms, AA_MAY_MOUNT, ad, audit_cb); } /** * match_mnt - handle path matching for mount - * @subj_cred: cred of the subject * @profile: the confining profile * @path: for the mntpnt (NOT NULL) * @buffer: buffer to be used to lookup mntpath @@ -361,14 +294,14 @@ static int match_mnt_path_str(const struct cred *subj_cred, * @flags: mount flags to match * @data: fs mount data (MAYBE NULL) * @binary: whether @data is binary + * @ad: apparmor audit data structure * * Returns: 0 on success else error */ -static int match_mnt(const struct cred *subj_cred, - struct aa_profile *profile, const struct path *path, +static int match_mnt(struct aa_profile *profile, const struct path *path, char *buffer, const struct path *devpath, char *devbuffer, const char *type, unsigned long flags, void *data, - bool binary) + bool binary, struct apparmor_audit_data *ad) { const char *devname = NULL, *info = NULL; struct aa_ruleset *rules = profile->label.rules[0]; @@ -388,8 +321,8 @@ static int match_mnt(const struct cred *subj_cred, devname = ERR_PTR(error); } - return match_mnt_path_str(subj_cred, profile, path, buffer, devname, - type, flags, data, binary, info); + return match_mnt_path_str(profile, path, buffer, devname, + type, flags, data, binary, info, ad); } int aa_remount(const struct cred *subj_cred, @@ -400,6 +333,8 @@ int aa_remount(const struct cred *subj_cred, char *buffer = NULL; bool binary; int error; + DEFINE_AUDIT_MOUNT(ad, OP_MOUNT, subj_cred); + ad.mnt.flags = flags; AA_BUG(!label); AA_BUG(!path); @@ -410,9 +345,8 @@ int aa_remount(const struct cred *subj_cred, if (!buffer) return -ENOMEM; error = fn_for_each_confined(label, profile, - match_mnt(subj_cred, profile, path, buffer, NULL, - NULL, NULL, - flags, data, binary)); + match_mnt(profile, path, buffer, NULL, NULL, NULL, + flags, data, binary, &ad)); aa_put_buffer(buffer); return error; @@ -426,6 +360,7 @@ int aa_bind_mount(const struct cred *subj_cred, char *buffer = NULL, *old_buffer = NULL; struct path old_path; int error; + DEFINE_AUDIT_MOUNT(ad, OP_MOUNT, subj_cred); AA_BUG(!label); AA_BUG(!path); @@ -434,10 +369,12 @@ int aa_bind_mount(const struct cred *subj_cred, return -EINVAL; flags &= MS_REC | MS_BIND; + ad.mnt.flags = flags; error = kern_path(dev_name, LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT, &old_path); if (error) - return error; + return aa_audit_perm_error(label, AA_MAY_MOUNT, error, &ad, + audit_cb); buffer = aa_get_buffer(false); old_buffer = aa_get_buffer(false); @@ -446,8 +383,8 @@ int aa_bind_mount(const struct cred *subj_cred, goto out; error = fn_for_each_confined(label, profile, - match_mnt(subj_cred, profile, path, buffer, &old_path, - old_buffer, NULL, flags, NULL, false)); + match_mnt(profile, path, buffer, &old_path, + old_buffer, NULL, flags, NULL, false, &ad)); out: aa_put_buffer(buffer); aa_put_buffer(old_buffer); @@ -463,6 +400,7 @@ int aa_mount_change_type(const struct cred *subj_cred, struct aa_profile *profile; char *buffer = NULL; int error; + DEFINE_AUDIT_MOUNT(ad, OP_MOUNT, subj_cred); AA_BUG(!label); AA_BUG(!path); @@ -470,14 +408,14 @@ int aa_mount_change_type(const struct cred *subj_cred, /* These are the flags allowed by do_change_type() */ flags &= (MS_REC | MS_SILENT | MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE); + ad.mnt.flags = flags; buffer = aa_get_buffer(false); if (!buffer) return -ENOMEM; error = fn_for_each_confined(label, profile, - match_mnt(subj_cred, profile, path, buffer, NULL, - NULL, NULL, - flags, NULL, false)); + match_mnt(profile, path, buffer, NULL, NULL, NULL, + flags, NULL, false, &ad)); aa_put_buffer(buffer); return error; @@ -490,6 +428,8 @@ int aa_move_mount(const struct cred *subj_cred, struct aa_profile *profile; char *to_buffer = NULL, *from_buffer = NULL; int error; + DEFINE_AUDIT_MOUNT(ad, OP_MOUNT, subj_cred); + ad.mnt.flags = MS_MOVE; AA_BUG(!label); AA_BUG(!from_path); @@ -505,9 +445,9 @@ int aa_move_mount(const struct cred *subj_cred, /* moving a mount detached from the namespace */ from_path = NULL; error = fn_for_each_confined(label, profile, - match_mnt(subj_cred, profile, to_path, to_buffer, - from_path, from_buffer, - NULL, MS_MOVE, NULL, false)); + match_mnt(profile, to_path, to_buffer, from_path, + from_buffer, NULL, MS_MOVE, NULL, false, + &ad)); out: aa_put_buffer(to_buffer); aa_put_buffer(from_buffer); @@ -543,6 +483,8 @@ int aa_new_mount(const struct cred *subj_cred, struct aa_label *label, int error; int requires_dev = 0; struct path tmp_path, *dev_path = NULL; + DEFINE_AUDIT_MOUNT(ad, OP_MOUNT, subj_cred); + ad.mnt.flags = flags; AA_BUG(!label); AA_BUG(!path); @@ -580,14 +522,14 @@ int aa_new_mount(const struct cred *subj_cred, struct aa_label *label, goto out; } error = fn_for_each_confined(label, profile, - match_mnt(subj_cred, profile, path, buffer, - dev_path, dev_buffer, - type, flags, data, binary)); + match_mnt(profile, path, buffer, dev_path, + dev_buffer, type, flags, data, + binary, &ad)); } else { error = fn_for_each_confined(label, profile, - match_mnt_path_str(subj_cred, profile, path, - buffer, dev_name, - type, flags, data, binary, NULL)); + match_mnt_path_str(profile, path, buffer, + dev_name, type, flags, data, + binary, NULL, &ad)); } out: @@ -599,13 +541,12 @@ int aa_new_mount(const struct cred *subj_cred, struct aa_label *label, return error; } -static int profile_umount(const struct cred *subj_cred, - struct aa_profile *profile, const struct path *path, - char *buffer) +static int profile_umount(struct aa_profile *profile, const struct path *path, + char *buffer, struct apparmor_audit_data *ad) { struct aa_ruleset *rules = profile->label.rules[0]; struct aa_perms perms = { }; - const char *name = NULL, *info = NULL; + const char *name = NULL; aa_state_t state; int error; @@ -615,22 +556,23 @@ static int profile_umount(const struct cred *subj_cred, if (!RULE_MEDIATES(rules, AA_CLASS_MOUNT)) return 0; + /* TODO: lift path_name, need to separate profile path_flags from + * the lookup + */ error = aa_path_name(path, path_flags(profile, path), buffer, &name, - &info, profile->disconnected); + &ad->info, profile->disconnected); if (error) - goto audit; + return aa_audit_perm_error(&profile->label, AA_MAY_UMOUNT, + error, ad, audit_cb); + ad->name = name; state = aa_dfa_match(rules->policy->dfa, rules->policy->start[AA_CLASS_MOUNT], name); perms = *aa_lookup_perms(rules->policy, state); - if (AA_MAY_UMOUNT & ~perms.allow) - error = -EACCES; -audit: - return audit_mount(subj_cred, profile, OP_UMOUNT, name, NULL, NULL, - NULL, 0, NULL, - AA_MAY_UMOUNT, &perms, info, error); + aa_apply_modes_to_perms(profile, &perms); + return aa_check_perms(profile, &perms, AA_MAY_UMOUNT, ad, audit_cb); } int aa_umount(const struct cred *subj_cred, struct aa_label *label, @@ -640,6 +582,7 @@ int aa_umount(const struct cred *subj_cred, struct aa_label *label, char *buffer = NULL; int error; struct path path = { .mnt = mnt, .dentry = mnt->mnt_root }; + DEFINE_AUDIT_MOUNT(ad, OP_UMOUNT, subj_cred); AA_BUG(!label); AA_BUG(!mnt); @@ -649,7 +592,7 @@ int aa_umount(const struct cred *subj_cred, struct aa_label *label, return -ENOMEM; error = fn_for_each_confined(label, profile, - profile_umount(subj_cred, profile, &path, buffer)); + profile_umount(profile, &path, buffer, &ad)); aa_put_buffer(buffer); return error; @@ -659,16 +602,15 @@ int aa_umount(const struct cred *subj_cred, struct aa_label *label, * * Returns: label for transition or ERR_PTR. Does not return NULL */ -static struct aa_label *build_pivotroot(const struct cred *subj_cred, - struct aa_profile *profile, +static struct aa_label *build_pivotroot(struct aa_profile *profile, const struct path *new_path, char *new_buffer, const struct path *old_path, - char *old_buffer) + char *old_buffer, + struct apparmor_audit_data *ad) { struct aa_ruleset *rules = profile->label.rules[0]; - const char *old_name, *new_name = NULL, *info = NULL; - const char *trans_name = NULL; + const char *old_name, *new_name = NULL; struct aa_perms perms = { }; aa_state_t state; int error; @@ -682,36 +624,40 @@ static struct aa_label *build_pivotroot(const struct cred *subj_cred, return aa_get_newest_label(&profile->label); error = aa_path_name(old_path, path_flags(profile, old_path), - old_buffer, &old_name, &info, + old_buffer, &old_name, &ad->info, profile->disconnected); if (error) - goto audit; + goto err; + ad->mnt.src_name = old_name; error = aa_path_name(new_path, path_flags(profile, new_path), - new_buffer, &new_name, &info, + new_buffer, &new_name, &ad->info, profile->disconnected); if (error) - goto audit; + goto err; + ad->name = new_name; - error = -EACCES; state = aa_dfa_match(rules->policy->dfa, rules->policy->start[AA_CLASS_MOUNT], new_name); state = aa_dfa_null_transition(rules->policy->dfa, state); state = aa_dfa_match(rules->policy->dfa, state, old_name); perms = *aa_lookup_perms(rules->policy, state); + /* todo: allow pivotroot to specify a transition other than profile */ + ad->mnt.trans = profile->label.hname; - if (AA_MAY_PIVOTROOT & perms.allow) - error = 0; + aa_apply_modes_to_perms(profile, &perms); + error = aa_check_perms(profile, &perms, AA_MAY_PIVOTROOT, ad, audit_cb); -audit: - error = audit_mount(subj_cred, profile, OP_PIVOTROOT, new_name, - old_name, - NULL, trans_name, 0, NULL, AA_MAY_PIVOTROOT, - &perms, info, error); +out: if (error) return ERR_PTR(error); return aa_get_newest_label(&profile->label); + +err: + error = aa_audit_perm_error(&profile->label, AA_MAY_PIVOTROOT, error, + ad, audit_cb); + goto out; } int aa_pivotroot(const struct cred *subj_cred, struct aa_label *label, @@ -720,8 +666,9 @@ int aa_pivotroot(const struct cred *subj_cred, struct aa_label *label, { struct aa_profile *profile; struct aa_label *target = NULL; - char *old_buffer = NULL, *new_buffer = NULL, *info = NULL; + char *old_buffer = NULL, *new_buffer = NULL; int error; + DEFINE_AUDIT_MOUNT(ad, OP_PIVOTROOT, subj_cred); AA_BUG(!label); AA_BUG(!old_path); @@ -733,9 +680,8 @@ int aa_pivotroot(const struct cred *subj_cred, struct aa_label *label, if (!old_buffer || !new_buffer) goto out; target = fn_label_build(label, profile, GFP_KERNEL, - build_pivotroot(subj_cred, profile, new_path, - new_buffer, - old_path, old_buffer)); + build_pivotroot(profile, new_path, new_buffer, + old_path, old_buffer, &ad)); AA_BUG(!target); if (!IS_ERR(target)) { error = aa_replace_current_label(target); @@ -743,7 +689,7 @@ int aa_pivotroot(const struct cred *subj_cred, struct aa_label *label, goto fail; aa_put_label(target); } else - /* already audited error */ + /* already audited error in build_pivotroot */ error = PTR_ERR(target); out: aa_put_buffer(old_buffer); @@ -752,14 +698,12 @@ int aa_pivotroot(const struct cred *subj_cred, struct aa_label *label, return error; fail: - /* TODO: add back in auditing of new_name and old_name */ - error = fn_for_each(label, profile, - audit_mount(subj_cred, profile, OP_PIVOTROOT, - NULL /*new_name */, - NULL /* old_name */, - NULL, NULL, - 0, target->hname, AA_MAY_PIVOTROOT, &nullperms, info, - error)); + /* TODO: add back in auditing of new_name and old_name, + * needs lifting of name lookup out of profile cb + */ + ad.mnt.trans = target->hname; + error = aa_audit_perm_error(label, AA_MAY_PIVOTROOT, error, &ad, + audit_cb); aa_put_label(target); goto out; } From ecafacef162fc7b79b49391084b04fb04ea64f37 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 28 Jul 2026 13:26:55 -0700 Subject: [PATCH 0521/1328] apparmor: drop use of _confined variant for iteration In preparation for allowing unconfined to be replaced and mediate select rules drop use of the _confined variant for iteration in the mount code. The RULE_MEDIATES() check will continue to allow unconfined profile mediation to be skipped unless it is replaced and is given mount rules. Signed-off-by: John Johansen --- security/apparmor/mount.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c index 5aaa4f878d92..8b920164cd88 100644 --- a/security/apparmor/mount.c +++ b/security/apparmor/mount.c @@ -344,7 +344,7 @@ int aa_remount(const struct cred *subj_cred, buffer = aa_get_buffer(false); if (!buffer) return -ENOMEM; - error = fn_for_each_confined(label, profile, + error = fn_for_each(label, profile, match_mnt(profile, path, buffer, NULL, NULL, NULL, flags, data, binary, &ad)); aa_put_buffer(buffer); @@ -382,7 +382,7 @@ int aa_bind_mount(const struct cred *subj_cred, if (!buffer || !old_buffer) goto out; - error = fn_for_each_confined(label, profile, + error = fn_for_each(label, profile, match_mnt(profile, path, buffer, &old_path, old_buffer, NULL, flags, NULL, false, &ad)); out: @@ -413,7 +413,7 @@ int aa_mount_change_type(const struct cred *subj_cred, buffer = aa_get_buffer(false); if (!buffer) return -ENOMEM; - error = fn_for_each_confined(label, profile, + error = fn_for_each(label, profile, match_mnt(profile, path, buffer, NULL, NULL, NULL, flags, NULL, false, &ad)); aa_put_buffer(buffer); @@ -444,7 +444,7 @@ int aa_move_mount(const struct cred *subj_cred, if (!our_mnt(from_path->mnt)) /* moving a mount detached from the namespace */ from_path = NULL; - error = fn_for_each_confined(label, profile, + error = fn_for_each(label, profile, match_mnt(profile, to_path, to_buffer, from_path, from_buffer, NULL, MS_MOVE, NULL, false, &ad)); @@ -521,12 +521,12 @@ int aa_new_mount(const struct cred *subj_cred, struct aa_label *label, error = -ENOMEM; goto out; } - error = fn_for_each_confined(label, profile, + error = fn_for_each(label, profile, match_mnt(profile, path, buffer, dev_path, dev_buffer, type, flags, data, binary, &ad)); } else { - error = fn_for_each_confined(label, profile, + error = fn_for_each(label, profile, match_mnt_path_str(profile, path, buffer, dev_name, type, flags, data, binary, NULL, &ad)); @@ -591,7 +591,7 @@ int aa_umount(const struct cred *subj_cred, struct aa_label *label, if (!buffer) return -ENOMEM; - error = fn_for_each_confined(label, profile, + error = fn_for_each(label, profile, profile_umount(profile, &path, buffer, &ad)); aa_put_buffer(buffer); From e3bc982007fe5e704c6a7f5a189502ece056f68d Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 28 Jul 2026 13:57:10 -0700 Subject: [PATCH 0522/1328] apparmor: constify aa_perms parameters that are read-only Several functions take a struct aa_perms * argument that is only ever read from and never modified through the pointer. Mark those parameters const struct aa_perms * to document intent and let the compiler enforce that the permission set is not mutated. The converted functions are: - aa_check_perms() - aa_do_perms() - do_perms() (af_inet) - match_label() (af_unix) - verify_perm() - aa_perms_accum() / aa_perms_accum_raw() (@addend only) No functional change. Signed-off-by: John Johansen Assisted-by: Claude:claude-opus-4.8 --- security/apparmor/af_unix.c | 2 +- security/apparmor/include/net.h | 2 +- security/apparmor/include/perms.h | 11 ++++++----- security/apparmor/lib.c | 2 +- security/apparmor/net.c | 2 +- security/apparmor/policy_unpack.c | 2 +- 6 files changed, 11 insertions(+), 10 deletions(-) diff --git a/security/apparmor/af_unix.c b/security/apparmor/af_unix.c index 84250c0b9c78..ba1488767d36 100644 --- a/security/apparmor/af_unix.c +++ b/security/apparmor/af_unix.c @@ -173,7 +173,7 @@ static aa_state_t match_to_peer(struct aa_policydb *policy, aa_state_t state, static aa_state_t match_label(struct aa_profile *profile, struct aa_ruleset *rule, aa_state_t state, u32 request, struct aa_profile *peer, - struct aa_perms *p, + const struct aa_perms *p, struct apparmor_audit_data *ad) { AA_BUG(!profile); diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h index 2efc5417d1ba..375341929cb6 100644 --- a/security/apparmor/include/net.h +++ b/security/apparmor/include/net.h @@ -86,7 +86,7 @@ extern struct aa_sfs_entry aa_sfs_entry_network[]; extern struct aa_sfs_entry aa_sfs_entry_networkv9[]; int aa_do_perms(struct aa_profile *profile, struct aa_policydb *policy, - aa_state_t state, u32 request, struct aa_perms *p, + aa_state_t state, u32 request, const struct aa_perms *p, struct apparmor_audit_data *ad); /* passing in state returned by XXX_mediates_AF() */ aa_state_t aa_match_to_prot(struct aa_policydb *policy, aa_state_t state, diff --git a/security/apparmor/include/perms.h b/security/apparmor/include/perms.h index bbab28ed7abb..65ca3f30b4c2 100644 --- a/security/apparmor/include/perms.h +++ b/security/apparmor/include/perms.h @@ -105,7 +105,7 @@ extern const struct aa_perms allperms; * @addend: perms struct to add to @accum */ static inline void aa_perms_accum_raw(struct aa_perms *accum, - struct aa_perms *addend) + const struct aa_perms *addend) { accum->deny |= addend->deny; accum->allow &= addend->allow & ~addend->deny; @@ -132,7 +132,7 @@ static inline void aa_perms_accum_raw(struct aa_perms *accum, * @addend: perms struct to add to @accum */ static inline void aa_perms_accum(struct aa_perms *accum, - struct aa_perms *addend) + const struct aa_perms *addend) { accum->deny |= addend->deny; accum->allow &= addend->allow & ~accum->deny; @@ -208,12 +208,13 @@ void aa_audit_perm_mask(struct audit_buffer *ab, u32 mask, const char *chrs, u32 chrsmask, const char * const *names, u32 namesmask); void aa_apply_modes_to_perms(struct aa_profile *profile, struct aa_perms *perms); -void aa_perms_accum(struct aa_perms *accum, struct aa_perms *addend); -void aa_perms_accum_raw(struct aa_perms *accum, struct aa_perms *addend); +void aa_perms_accum(struct aa_perms *accum, const struct aa_perms *addend); +void aa_perms_accum_raw(struct aa_perms *accum, const struct aa_perms *addend); void aa_profile_match_label(struct aa_profile *profile, struct aa_ruleset *rules, struct aa_label *label, int type, u32 request, struct aa_perms *perms); -int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms, +int aa_check_perms(struct aa_profile *profile, const struct aa_perms *perms, u32 request, struct apparmor_audit_data *ad, void (*cb)(struct audit_buffer *, void *)); #endif /* __AA_PERM_H */ + diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index 5d33252204fe..e6401f291541 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -421,7 +421,7 @@ void aa_profile_match_label(struct aa_profile *profile, * error code will indicate whether there was an explicit deny * with a positive value. */ -int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms, +int aa_check_perms(struct aa_profile *profile, const struct aa_perms *perms, u32 request, struct apparmor_audit_data *ad, void (*cb)(struct audit_buffer *, void *)) { diff --git a/security/apparmor/net.c b/security/apparmor/net.c index 4d4e9e389080..2e58a3686863 100644 --- a/security/apparmor/net.c +++ b/security/apparmor/net.c @@ -167,7 +167,7 @@ void audit_net_cb(struct audit_buffer *ab, void *va) /* standard permission lookup pattern - supports early bailout */ int aa_do_perms(struct aa_profile *profile, struct aa_policydb *policy, aa_state_t state, u32 request, - struct aa_perms *p, struct apparmor_audit_data *ad) + const struct aa_perms *p, struct apparmor_audit_data *ad) { struct aa_perms perms; diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 6b3b77dcbd69..8dc940176be1 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -1493,7 +1493,7 @@ static bool verify_dfa_accept_index(struct aa_dfa *dfa, int table_size) return true; } -static bool verify_perm(struct aa_perms *perm) +static bool verify_perm(const struct aa_perms *perm) { /* TODO: allow option to just force the perms into a valid state */ if (perm->allow & perm->deny) From 08066bcdeed037a99c47f7c8456cf30ee3055048 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 28 Jul 2026 14:50:38 -0700 Subject: [PATCH 0523/1328] apparmor: constify aa_profile parameters on read-only compute paths A number of functions take a struct aa_profile * argument that is only ever read from: they compute DFA matches or apply the profile's mode flags without modifying the profile, taking a reference on it, or touching its embedded label. Mark those parameters const struct aa_profile * to document intent and let the compiler enforce it. The converted functions are the permission "compute" path plus a few pure readers: - aa_apply_modes_to_perms(), aa_profile_match_label() - AUDIT_MODE() - aa_label_match() and its match_component()/label_compound_match()/ label_components_match() helpers (label.c) - match_component()/label_compound_match()/label_components_match()/ label_match()/change_profile_perms()/aa_xattrs_match() (domain.c) - match_iface()/match_addr_iface()/match_addr_iface_label()/ skb_match_to_sk()/skb_match_to_cmd() (af_inet.c) - aa_profile_capget(), path_flags(), profile_query_cb() The remaining aa_profile * parameters cannot be made const: the audit path stores &profile->label into the owned, refcounted apparmor_audit_data.subj_label/peer fields, and the domain/lifecycle paths take references on the profile's embedded label (aa_get_label()/aa_get_newest_label()/aa_get_profile()) or write profile fields. No functional change. Signed-off-by: John Johansen Assisted-by: Claude:claude-opus-4.8 --- security/apparmor/apparmorfs.c | 3 ++- security/apparmor/capability.c | 2 +- security/apparmor/domain.c | 16 ++++++++-------- security/apparmor/include/capability.h | 2 +- security/apparmor/include/label.h | 2 +- security/apparmor/include/perms.h | 4 ++-- security/apparmor/include/policy.h | 2 +- security/apparmor/label.c | 10 +++++----- security/apparmor/lib.c | 5 +++-- security/apparmor/mount.c | 2 +- 10 files changed, 25 insertions(+), 23 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 22317817d1f5..5b42140e12e8 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -856,7 +856,8 @@ static const struct file_operations aa_fs_ns_revision_fops = { .release = ns_revision_release, }; -static void profile_query_cb(struct aa_profile *profile, struct aa_perms *perms, +static void profile_query_cb(const struct aa_profile *profile, + struct aa_perms *perms, const char *match_str, size_t match_len) { struct aa_ruleset *rules = profile->label.rules[0]; diff --git a/security/apparmor/capability.c b/security/apparmor/capability.c index b9ea6bc45c1a..2f3c6c84358d 100644 --- a/security/apparmor/capability.c +++ b/security/apparmor/capability.c @@ -191,7 +191,7 @@ int aa_capable(const struct cred *subj_cred, struct aa_label *label, return error; } -kernel_cap_t aa_profile_capget(struct aa_profile *profile) +kernel_cap_t aa_profile_capget(const struct aa_profile *profile) { struct aa_ruleset *rules = profile->label.rules[0]; aa_state_t state; diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 3b80db6663ad..e20d177a4357 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -91,8 +91,8 @@ static int may_change_ptraced_domain(const struct cred *to_cred, * If a subns profile is not to be matched should be prescreened with * visibility test. */ -static inline aa_state_t match_component(struct aa_profile *profile, - struct aa_profile *tp, +static inline aa_state_t match_component(const struct aa_profile *profile, + const struct aa_profile *tp, bool stack, aa_state_t state) { struct aa_ruleset *rules = profile->label.rules[0]; @@ -127,7 +127,7 @@ static inline aa_state_t match_component(struct aa_profile *profile, * @perms should be preinitialized with allperms OR a previous permission * check to be stacked. */ -static int label_compound_match(struct aa_profile *profile, +static int label_compound_match(const struct aa_profile *profile, struct aa_label *label, bool stack, aa_state_t state, bool inview, u32 request, struct aa_perms *perms) @@ -189,7 +189,7 @@ static int label_compound_match(struct aa_profile *profile, * @perms should be preinitialized with allperms OR a previous permission * check to be stacked. */ -static int label_components_match(struct aa_profile *profile, +static int label_components_match(const struct aa_profile *profile, struct aa_label *label, bool stack, aa_state_t start, bool inview, u32 request, struct aa_perms *perms) @@ -253,7 +253,7 @@ static int label_components_match(struct aa_profile *profile, * * Returns: the state the match finished in, may be the none matching state */ -static int label_match(struct aa_profile *profile, struct aa_label *label, +static int label_match(const struct aa_profile *profile, struct aa_label *label, bool stack, aa_state_t state, bool inview, u32 request, struct aa_perms *perms) { @@ -287,7 +287,7 @@ static int label_match(struct aa_profile *profile, struct aa_label *label, * currently only matches full label A//&B//&C or individual components A, B, C * not arbitrary combinations. Eg. A//&B, C */ -static int change_profile_perms(struct aa_profile *profile, +static int change_profile_perms(const struct aa_profile *profile, struct aa_label *target, bool stack, u32 request, aa_state_t start, struct aa_perms *perms) @@ -311,7 +311,7 @@ static int change_profile_perms(struct aa_profile *profile, * Returns: number of extended attributes that matched, or < 0 on error */ static int aa_xattrs_match(const struct path *path, - struct aa_profile *profile, aa_state_t state) + const struct aa_profile *profile, aa_state_t state) { AA_BUG(!path); AA_BUG(!profile); @@ -319,7 +319,7 @@ static int aa_xattrs_match(const struct path *path, int i; struct dentry *d; char *value = NULL; - struct aa_attachment *attach = &profile->attach; + const struct aa_attachment *attach = &profile->attach; int size, value_size = 0, ret = attach->xattr_count; if (!attach->xattr_count) diff --git a/security/apparmor/include/capability.h b/security/apparmor/include/capability.h index 3fb8b8f2182a..89a9c75d8f44 100644 --- a/security/apparmor/include/capability.h +++ b/security/apparmor/include/capability.h @@ -37,7 +37,7 @@ struct aa_caps { extern struct aa_sfs_entry aa_sfs_entry_caps[]; -kernel_cap_t aa_profile_capget(struct aa_profile *profile); +kernel_cap_t aa_profile_capget(const struct aa_profile *profile); int aa_capable(const struct cred *subj_cred, struct aa_label *label, int cap, unsigned int opts); diff --git a/security/apparmor/include/label.h b/security/apparmor/include/label.h index 37cb135de323..eb2240173ba3 100644 --- a/security/apparmor/include/label.h +++ b/security/apparmor/include/label.h @@ -342,7 +342,7 @@ static inline const char *aa_label_str_split(const char *str) struct aa_perms; struct aa_ruleset; -int aa_label_match(struct aa_profile *profile, struct aa_ruleset *rules, +int aa_label_match(const struct aa_profile *profile, struct aa_ruleset *rules, struct aa_label *label, aa_state_t state, bool subns, u32 request, struct aa_perms *perms); diff --git a/security/apparmor/include/perms.h b/security/apparmor/include/perms.h index 65ca3f30b4c2..ee25eb8e78e4 100644 --- a/security/apparmor/include/perms.h +++ b/security/apparmor/include/perms.h @@ -206,11 +206,11 @@ void aa_audit_perm_names(struct audit_buffer *ab, const char * const *names, u32 mask); void aa_audit_perm_mask(struct audit_buffer *ab, u32 mask, const char *chrs, u32 chrsmask, const char * const *names, u32 namesmask); -void aa_apply_modes_to_perms(struct aa_profile *profile, +void aa_apply_modes_to_perms(const struct aa_profile *profile, struct aa_perms *perms); void aa_perms_accum(struct aa_perms *accum, const struct aa_perms *addend); void aa_perms_accum_raw(struct aa_perms *accum, const struct aa_perms *addend); -void aa_profile_match_label(struct aa_profile *profile, +void aa_profile_match_label(const struct aa_profile *profile, struct aa_ruleset *rules, struct aa_label *label, int type, u32 request, struct aa_perms *perms); int aa_check_perms(struct aa_profile *profile, const struct aa_perms *perms, diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index eae9db08fb93..23b9f0e5df3c 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -433,7 +433,7 @@ static inline void aa_put_profile(struct aa_profile *p) kref_put(&p->label.count.count, aa_label_kref); } -static inline int AUDIT_MODE(struct aa_profile *profile) +static inline int AUDIT_MODE(const struct aa_profile *profile) { if (aa_g_audit != AUDIT_NORMAL) return aa_g_audit; diff --git a/security/apparmor/label.c b/security/apparmor/label.c index 32efef2f617a..c95488b0b55c 100644 --- a/security/apparmor/label.c +++ b/security/apparmor/label.c @@ -1290,9 +1290,9 @@ struct aa_label *aa_label_merge(struct aa_label *a, struct aa_label *b, * If a subns profile is not to be matched should be prescreened with * visibility test. */ -static inline aa_state_t match_component(struct aa_profile *profile, +static inline aa_state_t match_component(const struct aa_profile *profile, struct aa_ruleset *rules, - struct aa_profile *tp, + const struct aa_profile *tp, aa_state_t state) { const char *ns_name; @@ -1324,7 +1324,7 @@ static inline aa_state_t match_component(struct aa_profile *profile, * @perms should be preinitialized with allperms OR a previous permission * check to be stacked. */ -static int label_compound_match(struct aa_profile *profile, +static int label_compound_match(const struct aa_profile *profile, struct aa_ruleset *rules, struct aa_label *label, aa_state_t state, bool inview, u32 request, @@ -1380,7 +1380,7 @@ static int label_compound_match(struct aa_profile *profile, * @perms should be preinitialized with allperms OR a previous permission * check to be stacked. */ -static int label_components_match(struct aa_profile *profile, +static int label_components_match(const struct aa_profile *profile, struct aa_ruleset *rules, struct aa_label *label, aa_state_t start, bool inview, u32 request, @@ -1439,7 +1439,7 @@ static int label_components_match(struct aa_profile *profile, * * Returns: the state the match finished in, may be the none matching state */ -int aa_label_match(struct aa_profile *profile, struct aa_ruleset *rules, +int aa_label_match(const struct aa_profile *profile, struct aa_ruleset *rules, struct aa_label *label, aa_state_t state, bool inview, u32 request, struct aa_perms *perms) { diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index e6401f291541..e97bb7240e0e 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -361,7 +361,8 @@ void aa_audit_perm_mask(struct audit_buffer *ab, u32 mask, const char *chrs, * * TODO: split into profile and ns based flags for when accumulating perms */ -void aa_apply_modes_to_perms(struct aa_profile *profile, struct aa_perms *perms) +void aa_apply_modes_to_perms(const struct aa_profile *profile, + struct aa_perms *perms) { if (KILL_MODE(profile)) perms->kill = ~perms->allow; @@ -389,7 +390,7 @@ void aa_apply_modes_to_perms(struct aa_profile *profile, struct aa_perms *perms) } } -void aa_profile_match_label(struct aa_profile *profile, +void aa_profile_match_label(const struct aa_profile *profile, struct aa_ruleset *rules, struct aa_label *label, int type, u32 request, struct aa_perms *perms) diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c index 8b920164cd88..84273a3ab150 100644 --- a/security/apparmor/mount.c +++ b/security/apparmor/mount.c @@ -211,7 +211,7 @@ static int do_match_mnt(struct aa_policydb *policy, aa_state_t start, } -static int path_flags(struct aa_profile *profile, const struct path *path) +static int path_flags(const struct aa_profile *profile, const struct path *path) { AA_BUG(!profile); AA_BUG(!path); From 01a915e8239b7e806605a4ed1c93aab35528a452 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 29 Jul 2026 05:42:32 -0700 Subject: [PATCH 0524/1328] apparmor: constify aa_dfa parameters on read-only compute paths Most uses of aa_dfa a read-only walking of the dfa. Have the compiler enforce this. Signed-off-by: John Johansen --- security/apparmor/af_unix.c | 2 +- security/apparmor/include/lib.h | 2 +- security/apparmor/include/match.h | 16 +++++++++------- security/apparmor/match.c | 29 ++++++++++++++++------------- security/apparmor/mount.c | 2 +- security/apparmor/net.c | 2 +- security/apparmor/policy_compat.c | 14 +++++++------- security/apparmor/policy_unpack.c | 2 +- 8 files changed, 37 insertions(+), 32 deletions(-) diff --git a/security/apparmor/af_unix.c b/security/apparmor/af_unix.c index ba1488767d36..b908e744818c 100644 --- a/security/apparmor/af_unix.c +++ b/security/apparmor/af_unix.c @@ -67,7 +67,7 @@ static int unix_fs_perm(const char *op, u32 mask, const struct cred *subj_cred, #define SHUTDOWN_ADDR "\x03" /* path addr is shutdown and cleared */ #define FS_ADDR "/" /* path addr in fs */ -static aa_state_t match_addr(struct aa_dfa *dfa, aa_state_t state, +static aa_state_t match_addr(const struct aa_dfa *dfa, aa_state_t state, struct sockaddr_un *addr, int addrlen) { if (addr) diff --git a/security/apparmor/include/lib.h b/security/apparmor/include/lib.h index d59eee5603a4..475dd71fbe40 100644 --- a/security/apparmor/include/lib.h +++ b/security/apparmor/include/lib.h @@ -136,7 +136,7 @@ static inline bool aa_strneq(const char *str, const char *sub, int len) * character which is not used in standard matching and is only * used to separate pairs. */ -static inline aa_state_t aa_dfa_null_transition(struct aa_dfa *dfa, +static inline aa_state_t aa_dfa_null_transition(const struct aa_dfa *dfa, aa_state_t start) { /* the null transition only needs the string's null terminator byte */ diff --git a/security/apparmor/include/match.h b/security/apparmor/include/match.h index 094f6b998ea8..f7bd7855f1bd 100644 --- a/security/apparmor/include/match.h +++ b/security/apparmor/include/match.h @@ -126,15 +126,17 @@ static inline size_t table_size(size_t len, size_t el_size) #define aa_state_t unsigned int struct aa_dfa *aa_dfa_unpack(const void *blob, size_t size, int flags); -aa_state_t aa_dfa_match_len(struct aa_dfa *dfa, aa_state_t start, +aa_state_t aa_dfa_match_len(const struct aa_dfa *dfa, aa_state_t start, const char *str, int len); -aa_state_t aa_dfa_match(struct aa_dfa *dfa, aa_state_t start, +aa_state_t aa_dfa_match(const struct aa_dfa *dfa, aa_state_t start, const char *str); -aa_state_t aa_dfa_next(struct aa_dfa *dfa, aa_state_t state, const char c); -aa_state_t aa_dfa_outofband_transition(struct aa_dfa *dfa, aa_state_t state); -aa_state_t aa_dfa_match_until(struct aa_dfa *dfa, aa_state_t start, +aa_state_t aa_dfa_next(const struct aa_dfa *dfa, aa_state_t state, + const char c); +aa_state_t aa_dfa_outofband_transition(const struct aa_dfa *dfa, + aa_state_t state); +aa_state_t aa_dfa_match_until(const struct aa_dfa *dfa, aa_state_t start, const char *str, const char **retpos); -aa_state_t aa_dfa_matchn_until(struct aa_dfa *dfa, aa_state_t start, +aa_state_t aa_dfa_matchn_until(const struct aa_dfa *dfa, aa_state_t start, const char *str, int n, const char **retpos); void aa_dfa_free_kref(struct kref *kref); @@ -152,7 +154,7 @@ struct match_workbuf N = { \ .len = 0, \ } -aa_state_t aa_dfa_leftmatch(struct aa_dfa *dfa, aa_state_t start, +aa_state_t aa_dfa_leftmatch(const struct aa_dfa *dfa, aa_state_t start, const char *str, unsigned int *count); /** diff --git a/security/apparmor/match.c b/security/apparmor/match.c index 52668134da9d..7713484f6a36 100644 --- a/security/apparmor/match.c +++ b/security/apparmor/match.c @@ -151,7 +151,7 @@ static int verify_table_headers(struct table_header **tables, int flags) * * Returns: %0 else error code on failure to verify */ -static int verify_dfa(struct aa_dfa *dfa) +static int verify_dfa(const struct aa_dfa *dfa) { size_t i, state_count, trans_count; int error = -EPROTO; @@ -467,7 +467,7 @@ do { \ * * Returns: final state reached after input is consumed */ -aa_state_t aa_dfa_match_len(struct aa_dfa *dfa, aa_state_t start, +aa_state_t aa_dfa_match_len(const struct aa_dfa *dfa, aa_state_t start, const char *str, int len) { u32 *def = DEFAULT_TABLE(dfa); @@ -512,7 +512,8 @@ aa_state_t aa_dfa_match_len(struct aa_dfa *dfa, aa_state_t start, * * Returns: final state reached after input is consumed */ -aa_state_t aa_dfa_match(struct aa_dfa *dfa, aa_state_t start, const char *str) +aa_state_t aa_dfa_match(const struct aa_dfa *dfa, aa_state_t start, + const char *str) { u32 *def = DEFAULT_TABLE(dfa); u32 *base = BASE_TABLE(dfa); @@ -555,7 +556,8 @@ aa_state_t aa_dfa_match(struct aa_dfa *dfa, aa_state_t start, const char *str) * * Returns: state reach after input @c */ -aa_state_t aa_dfa_next(struct aa_dfa *dfa, aa_state_t state, const char c) +aa_state_t aa_dfa_next(const struct aa_dfa *dfa, aa_state_t state, + const char c) { u32 *def = DEFAULT_TABLE(dfa); u32 *base = BASE_TABLE(dfa); @@ -573,7 +575,8 @@ aa_state_t aa_dfa_next(struct aa_dfa *dfa, aa_state_t state, const char c) return state; } -aa_state_t aa_dfa_outofband_transition(struct aa_dfa *dfa, aa_state_t state) +aa_state_t aa_dfa_outofband_transition(const struct aa_dfa *dfa, + aa_state_t state) { u32 *def = DEFAULT_TABLE(dfa); u32 *base = BASE_TABLE(dfa); @@ -603,8 +606,8 @@ aa_state_t aa_dfa_outofband_transition(struct aa_dfa *dfa, aa_state_t state) * * Returns: final state reached after input is consumed */ -aa_state_t aa_dfa_match_until(struct aa_dfa *dfa, aa_state_t start, - const char *str, const char **retpos) +aa_state_t aa_dfa_match_until(const struct aa_dfa *dfa, aa_state_t start, + const char *str, const char **retpos) { u32 *def = DEFAULT_TABLE(dfa); u32 *base = BASE_TABLE(dfa); @@ -664,8 +667,8 @@ aa_state_t aa_dfa_match_until(struct aa_dfa *dfa, aa_state_t start, * * Returns: final state reached after input is consumed */ -aa_state_t aa_dfa_matchn_until(struct aa_dfa *dfa, aa_state_t start, - const char *str, int n, const char **retpos) +aa_state_t aa_dfa_matchn_until(const struct aa_dfa *dfa, aa_state_t start, + const char *str, int n, const char **retpos) { u32 *def = DEFAULT_TABLE(dfa); u32 *base = BASE_TABLE(dfa); @@ -740,9 +743,9 @@ static bool is_loop(struct match_workbuf *wb, aa_state_t state, return false; } -static aa_state_t leftmatch_fb(struct aa_dfa *dfa, aa_state_t start, - const char *str, struct match_workbuf *wb, - unsigned int *count) +static aa_state_t leftmatch_fb(const struct aa_dfa *dfa, aa_state_t start, + const char *str, struct match_workbuf *wb, + unsigned int *count) { u32 *def = DEFAULT_TABLE(dfa); u32 *base = BASE_TABLE(dfa); @@ -821,7 +824,7 @@ static aa_state_t leftmatch_fb(struct aa_dfa *dfa, aa_state_t start, * * Returns: final state reached after input is consumed */ -aa_state_t aa_dfa_leftmatch(struct aa_dfa *dfa, aa_state_t start, +aa_state_t aa_dfa_leftmatch(const struct aa_dfa *dfa, aa_state_t start, const char *str, unsigned int *count) { DEFINE_MATCH_WB(wb); diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c index 84273a3ab150..4ed7b9136beb 100644 --- a/security/apparmor/mount.c +++ b/security/apparmor/mount.c @@ -129,7 +129,7 @@ static void audit_cb(struct audit_buffer *ab, void *va) * * Returns: next state after flags match */ -static aa_state_t match_mnt_flags(struct aa_dfa *dfa, aa_state_t state, +static aa_state_t match_mnt_flags(const struct aa_dfa *dfa, aa_state_t state, unsigned long flags) { unsigned int i; diff --git a/security/apparmor/net.c b/security/apparmor/net.c index 2e58a3686863..a333e6aff926 100644 --- a/security/apparmor/net.c +++ b/security/apparmor/net.c @@ -199,7 +199,7 @@ static struct aa_perms *early_match(struct aa_policydb *policy, return p; } -static aa_state_t aa_dfa_match_be16(struct aa_dfa *dfa, aa_state_t state, +static aa_state_t aa_dfa_match_be16(const struct aa_dfa *dfa, aa_state_t state, u16 data) { __be16 buffer = cpu_to_be16(data); diff --git a/security/apparmor/policy_compat.c b/security/apparmor/policy_compat.c index 5fc16d56fbf4..94e4b781d33c 100644 --- a/security/apparmor/policy_compat.c +++ b/security/apparmor/policy_compat.c @@ -97,7 +97,7 @@ static u32 map_old_perms(u32 old) return new; } -static void compute_fperms_allow(struct aa_perms *perms, struct aa_dfa *dfa, +static void compute_fperms_allow(struct aa_perms *perms, const struct aa_dfa *dfa, aa_state_t state) { perms->allow |= AA_MAY_GETATTR; @@ -109,7 +109,7 @@ static void compute_fperms_allow(struct aa_perms *perms, struct aa_dfa *dfa, perms->allow |= AA_MAY_ONEXEC; } -static struct aa_perms compute_fperms_user(struct aa_dfa *dfa, +static struct aa_perms compute_fperms_user(const struct aa_dfa *dfa, aa_state_t state) { struct aa_perms perms = { }; @@ -124,7 +124,7 @@ static struct aa_perms compute_fperms_user(struct aa_dfa *dfa, return perms; } -static struct aa_perms compute_fperms_other(struct aa_dfa *dfa, +static struct aa_perms compute_fperms_other(const struct aa_dfa *dfa, aa_state_t state) { struct aa_perms perms = { }; @@ -147,7 +147,7 @@ static struct aa_perms compute_fperms_other(struct aa_dfa *dfa, * * Returns: remapped perm table */ -static struct aa_perms *compute_fperms(struct aa_dfa *dfa, +static struct aa_perms *compute_fperms(const struct aa_dfa *dfa, u32 *size) { aa_state_t state; @@ -171,7 +171,7 @@ static struct aa_perms *compute_fperms(struct aa_dfa *dfa, return table; } -static struct aa_perms *compute_xmatch_perms(struct aa_dfa *xmatch, +static struct aa_perms *compute_xmatch_perms(const struct aa_dfa *xmatch, u32 *size) { struct aa_perms *perms; @@ -207,7 +207,7 @@ static u32 map_xbits(u32 x) ((x & 0x7e) << 9); } -static struct aa_perms compute_perms_entry(struct aa_dfa *dfa, +static struct aa_perms compute_perms_entry(const struct aa_dfa *dfa, aa_state_t state, u32 version) { @@ -246,7 +246,7 @@ static struct aa_perms compute_perms_entry(struct aa_dfa *dfa, return perms; } -static struct aa_perms *compute_perms(struct aa_dfa *dfa, u32 version, +static struct aa_perms *compute_perms(const struct aa_dfa *dfa, u32 version, u32 *size) { unsigned int state; diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 8dc940176be1..f1fc48e72d0e 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -1483,7 +1483,7 @@ static int verify_header(struct aa_ext *e, int required, const char **ns) * @dfa: the dfa to check accept indexes are in range * @table_size: the permission table size the indexes should be within */ -static bool verify_dfa_accept_index(struct aa_dfa *dfa, int table_size) +static bool verify_dfa_accept_index(const struct aa_dfa *dfa, int table_size) { int i; for (i = 0; i < dfa->tables[YYTD_ID_ACCEPT]->td_lolen; i++) { From fbedf6d92e544648a48c054bfc6d494a4866ec14 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 4 Aug 2026 12:02:24 -0700 Subject: [PATCH 0525/1328] apparmor: constify aa_label parameters on read-only query helpers Several label helpers only read from their struct aa_label * arguments: they compare labels, test subset relationships, or check the mediation bitmask, all via direct field/index access. Mark those parameters const struct aa_label * to document intent and let the compiler enforce that the label is not modified. The converted functions are: - label_mediates(), label_mediates_safe() - aa_label_cmp() (and its vec_cmp() helper) - __aa_label_next_not_in_set(), aa_label_is_subset(), aa_label_is_unconfined_subset() - __aa_subj_label_is_cached() - aa_label_next_confined(), aa_label_next_in_merge() These all access the label through direct indexing or manual iterators rather than the label_for_each()/fn_for_each() macros, which are not const-correct and so gate the majority of the remaining label consumers (the print, match, and permission-check paths) from being constified. No functional change. Signed-off-by: John Johansen Assisted-by: Claude:claude-opus-4.8 --- security/apparmor/include/label.h | 23 ++++++++++++----------- security/apparmor/label.c | 20 +++++++++++--------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/security/apparmor/include/label.h b/security/apparmor/include/label.h index eb2240173ba3..2c869b7dec97 100644 --- a/security/apparmor/include/label.h +++ b/security/apparmor/include/label.h @@ -165,7 +165,7 @@ do { \ #define labels_profile(X) ((X)->vec[(X)->size - 1]) -int aa_label_next_confined(struct aa_label *l, int i); +int aa_label_next_confined(const struct aa_label *l, int i); /* for each profile in a label */ #define label_for_each(I, L, P) \ @@ -246,12 +246,12 @@ int aa_label_next_confined(struct aa_label *l, int i); #define fn_for_each_not_in_set(L1, L2, P, FN) \ fn_for_each2_XXX((L1), (L2), P, FN, _not_in_set) -static inline bool label_mediates(struct aa_label *L, unsigned char C) +static inline bool label_mediates(const struct aa_label *L, unsigned char C) { return (L)->mediates & (((u64) 1) << (C)); } -static inline bool label_mediates_safe(struct aa_label *L, unsigned char C) +static inline bool label_mediates_safe(const struct aa_label *L, unsigned char C) { if (C > AA_CLASS_LAST) return false; @@ -268,11 +268,12 @@ void aa_label_kref(struct kref *kref); bool aa_label_init(struct aa_label *label, int size, gfp_t gfp); struct aa_label *aa_label_alloc(int size, struct aa_proxy *proxy, gfp_t gfp); -bool aa_label_is_subset(struct aa_label *set, struct aa_label *sub); -bool aa_label_is_unconfined_subset(struct aa_label *set, struct aa_label *sub); +bool aa_label_is_subset(const struct aa_label *set, const struct aa_label *sub); +bool aa_label_is_unconfined_subset(const struct aa_label *set, + const struct aa_label *sub); struct aa_profile *__aa_label_next_not_in_set(struct label_it *I, - struct aa_label *set, - struct aa_label *sub); + const struct aa_label *set, + const struct aa_label *sub); bool aa_label_remove(struct aa_label *label); struct aa_label *aa_label_insert(struct aa_labelset *ls, struct aa_label *l); bool aa_label_replace(struct aa_label *old, struct aa_label *new); @@ -280,8 +281,8 @@ bool aa_label_make_newest(struct aa_labelset *ls, struct aa_label *old, struct aa_label *new); struct aa_profile *aa_label_next_in_merge(struct label_it *I, - struct aa_label *a, - struct aa_label *b); + const struct aa_label *a, + const struct aa_label *b); struct aa_label *aa_label_find_merge(struct aa_label *a, struct aa_label *b); struct aa_label *aa_label_merge(struct aa_label *a, struct aa_label *b, gfp_t gfp); @@ -462,8 +463,8 @@ static inline void aa_put_label(struct aa_label *l) } /* wrapper fn to indicate semantics of the check */ -static inline bool __aa_subj_label_is_cached(struct aa_label *subj_label, - struct aa_label *obj_label) +static inline bool __aa_subj_label_is_cached(const struct aa_label *subj_label, + const struct aa_label *obj_label) { return aa_label_is_subset(obj_label, subj_label); } diff --git a/security/apparmor/label.c b/security/apparmor/label.c index c95488b0b55c..82742a471055 100644 --- a/security/apparmor/label.c +++ b/security/apparmor/label.c @@ -165,7 +165,8 @@ static int profile_cmp(struct aa_profile *a, struct aa_profile *b) * ==0 if @a == @b * >0 if @a > @b */ -static int vec_cmp(struct aa_profile **a, int an, struct aa_profile **b, int bn) +static int vec_cmp(struct aa_profile * const *a, int an, + struct aa_profile * const *b, int bn) { int i; @@ -473,7 +474,7 @@ struct aa_label *aa_label_alloc(int size, struct aa_proxy *proxy, gfp_t gfp) * ==0 if a == b * >0 if a > b */ -static int label_cmp(struct aa_label *a, struct aa_label *b) +static int label_cmp(const struct aa_label *a, const struct aa_label *b) { AA_BUG(!b); @@ -484,7 +485,7 @@ static int label_cmp(struct aa_label *a, struct aa_label *b) } /* helper fn for label_for_each_confined */ -int aa_label_next_confined(struct aa_label *label, int i) +int aa_label_next_confined(const struct aa_label *label, int i) { AA_BUG(!label); AA_BUG(i < 0); @@ -507,8 +508,8 @@ int aa_label_next_confined(struct aa_label *label, int i) * else NULL if @sub is a subset of @set */ struct aa_profile *__aa_label_next_not_in_set(struct label_it *I, - struct aa_label *set, - struct aa_label *sub) + const struct aa_label *set, + const struct aa_label *sub) { AA_BUG(!set); AA_BUG(!I); @@ -544,7 +545,7 @@ struct aa_profile *__aa_label_next_not_in_set(struct label_it *I, * Returns: true if @sub is subset of @set * else false */ -bool aa_label_is_subset(struct aa_label *set, struct aa_label *sub) +bool aa_label_is_subset(const struct aa_label *set, const struct aa_label *sub) { struct label_it i = { }; @@ -571,7 +572,8 @@ bool aa_label_is_subset(struct aa_label *set, struct aa_label *sub) * Returns: true if @sub is special_subset of @set * else false */ -bool aa_label_is_unconfined_subset(struct aa_label *set, struct aa_label *sub) +bool aa_label_is_unconfined_subset(const struct aa_label *set, + const struct aa_label *sub) { struct label_it i = { }; struct aa_profile *p; @@ -991,8 +993,8 @@ struct aa_label *aa_label_insert(struct aa_labelset *ls, struct aa_label *label) * else null if no more profiles */ struct aa_profile *aa_label_next_in_merge(struct label_it *I, - struct aa_label *a, - struct aa_label *b) + const struct aa_label *a, + const struct aa_label *b) { AA_BUG(!a); AA_BUG(!b); From 43e214ede664d5d59859e5173cb93659d2559b17 Mon Sep 17 00:00:00 2001 From: Greg Ungerer Date: Tue, 7 Jul 2026 23:01:02 +1000 Subject: [PATCH 0526/1328] m68k: defconfig: add config for M52358EVB board Add a default configuration for a basic M5235 based EVB board. The SoC has been supported for a long time but there is no default configuration. Create one to improve build and test coverage. Signed-off-by: Greg Ungerer --- arch/m68k/configs/m5235evb_defconfig | 52 ++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 arch/m68k/configs/m5235evb_defconfig diff --git a/arch/m68k/configs/m5235evb_defconfig b/arch/m68k/configs/m5235evb_defconfig new file mode 100644 index 000000000000..fcac4ccd332d --- /dev/null +++ b/arch/m68k/configs/m5235evb_defconfig @@ -0,0 +1,52 @@ +CONFIG_LOG_BUF_SHIFT=14 +CONFIG_EXPERT=y +# CONFIG_FUTEX is not set +# CONFIG_EPOLL is not set +# CONFIG_SIGNALFD is not set +# CONFIG_TIMERFD is not set +# CONFIG_EVENTFD is not set +# CONFIG_AIO is not set +# CONFIG_KALLSYMS is not set +# CONFIG_MMU is not set +CONFIG_M523x=y +CONFIG_M5235EVB=y +CONFIG_RAMBASE=0x00000000 +CONFIG_RAMSIZE=0x01000000 +CONFIG_VECTORBASE=0x00000000 +CONFIG_IPSBAR=0x40000000 +CONFIG_KERNELBASE=0x00020000 +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_BINFMT_FLAT=y +# CONFIG_VM_EVENT_COUNTERS is not set +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_INET=y +# CONFIG_INET_DIAG is not set +# CONFIG_IPV6 is not set +# CONFIG_FW_LOADER is not set +CONFIG_MTD=y +CONFIG_MTD_BLOCK=y +CONFIG_MTD_RAM=y +CONFIG_MTD_UCLINUX=y +CONFIG_BLK_DEV_RAM=y +CONFIG_NETDEVICES=y +CONFIG_FEC=y +# CONFIG_INPUT is not set +# CONFIG_SERIO is not set +# CONFIG_VT is not set +# CONFIG_UNIX98_PTYS is not set +CONFIG_SERIAL_MCF=y +CONFIG_SERIAL_MCF_CONSOLE=y +# CONFIG_HWMON is not set +# CONFIG_USB_SUPPORT is not set +CONFIG_EXT2_FS=y +# CONFIG_FILE_LOCKING is not set +# CONFIG_DNOTIFY is not set +CONFIG_ROMFS_FS=y +CONFIG_ROMFS_BACKED_BY_MTD=y +# CONFIG_NETWORK_FILESYSTEMS is not set +# CONFIG_SLUB_DEBUG is not set +CONFIG_BOOTPARAM=y +CONFIG_BOOTPARAM_STRING="root=/dev/mtdblock0" From ab3e29ac591d69a51120b1113c65b60707ba7bd0 Mon Sep 17 00:00:00 2001 From: Greg Ungerer Date: Tue, 7 Jul 2026 23:02:08 +1000 Subject: [PATCH 0527/1328] m68k: defconfig: add config for M5282EVB board Add a default configuration for a basic M5282 based EVB board. The SoC has been supported for a long time but there is no default configuration. Create one to improve build and test coverage. Signed-off-by: Greg Ungerer --- arch/m68k/configs/m5282evb_defconfig | 51 ++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 arch/m68k/configs/m5282evb_defconfig diff --git a/arch/m68k/configs/m5282evb_defconfig b/arch/m68k/configs/m5282evb_defconfig new file mode 100644 index 000000000000..85040dc3e45e --- /dev/null +++ b/arch/m68k/configs/m5282evb_defconfig @@ -0,0 +1,51 @@ +CONFIG_LOG_BUF_SHIFT=14 +CONFIG_EXPERT=y +# CONFIG_FUTEX is not set +# CONFIG_EPOLL is not set +# CONFIG_SIGNALFD is not set +# CONFIG_TIMERFD is not set +# CONFIG_EVENTFD is not set +# CONFIG_AIO is not set +# CONFIG_KALLSYMS is not set +# CONFIG_MMU is not set +CONFIG_M528x=y +CONFIG_RAMBASE=0x00000000 +CONFIG_RAMSIZE=0x00800000 +CONFIG_VECTORBASE=0x00000000 +CONFIG_IPSBAR=0x40000000 +CONFIG_KERNELBASE=0x00020000 +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_BINFMT_FLAT=y +# CONFIG_VM_EVENT_COUNTERS is not set +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_INET=y +# CONFIG_INET_DIAG is not set +# CONFIG_IPV6 is not set +# CONFIG_FW_LOADER is not set +CONFIG_MTD=y +CONFIG_MTD_BLOCK=y +CONFIG_MTD_RAM=y +CONFIG_MTD_UCLINUX=y +CONFIG_BLK_DEV_RAM=y +CONFIG_NETDEVICES=y +CONFIG_FEC=y +# CONFIG_INPUT is not set +# CONFIG_SERIO is not set +# CONFIG_VT is not set +# CONFIG_UNIX98_PTYS is not set +CONFIG_SERIAL_MCF=y +CONFIG_SERIAL_MCF_CONSOLE=y +# CONFIG_HWMON is not set +# CONFIG_USB_SUPPORT is not set +CONFIG_EXT2_FS=y +# CONFIG_FILE_LOCKING is not set +# CONFIG_DNOTIFY is not set +CONFIG_ROMFS_FS=y +CONFIG_ROMFS_BACKED_BY_MTD=y +# CONFIG_NETWORK_FILESYSTEMS is not set +# CONFIG_SLUB_DEBUG is not set +CONFIG_BOOTPARAM=y +CONFIG_BOOTPARAM_STRING="root=/dev/mtdblock0" From b47eeaa48ee5f28213056a749cb02ebdcb1d42bd Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 10 Jul 2026 23:19:47 +0200 Subject: [PATCH 0528/1328] m68k/coldfire: replace linux/gpio.h inclusions linux/gpio.h should no longer be used, convert these instead to linux/gpio/legacy.h for coldfire. Acked-by: Bartosz Golaszewski Signed-off-by: Arnd Bergmann Signed-off-by: Greg Ungerer --- arch/m68k/coldfire/device.c | 2 +- arch/m68k/include/asm/mcfgpio.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/m68k/coldfire/device.c b/arch/m68k/coldfire/device.c index 1420bae0964f..9a0258acd998 100644 --- a/arch/m68k/coldfire/device.c +++ b/arch/m68k/coldfire/device.c @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/arch/m68k/include/asm/mcfgpio.h b/arch/m68k/include/asm/mcfgpio.h index 7103cfa4edb6..29726aa40eb6 100644 --- a/arch/m68k/include/asm/mcfgpio.h +++ b/arch/m68k/include/asm/mcfgpio.h @@ -16,7 +16,7 @@ int __mcfgpio_request(unsigned gpio); void __mcfgpio_free(unsigned gpio); #ifdef CONFIG_GPIOLIB -#include +#include #else /* our alternate 'gpiolib' functions */ From de0dab22cbf6943d0f12f3b1e2eb1bbdd807f039 Mon Sep 17 00:00:00 2001 From: Jean-Michel Hautbois Date: Wed, 17 Jun 2026 21:17:16 +0200 Subject: [PATCH 0529/1328] m68k: coldfire/5441x: register mcf-rcm-reset platform device Add SoC-level registration of the mcf-rcm-reset platform device so the power_on_reason sysfs attribute is created on every MCF5441x board without per-board boilerplate. Signed-off-by: Jean-Michel Hautbois Signed-off-by: Greg Ungerer --- arch/m68k/coldfire/m5441x.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/arch/m68k/coldfire/m5441x.c b/arch/m68k/coldfire/m5441x.c index 5b5e09ecf487..ad3d0021578f 100644 --- a/arch/m68k/coldfire/m5441x.c +++ b/arch/m68k/coldfire/m5441x.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -242,6 +243,30 @@ static void __init m5441x_fec_init(void) mcf_write8(0x03, MCFGPIO_PAR_FEC); } +/* + * Reset Controller Module status register. Exposed to userspace as + * /sys/devices/platform/mcf-rcm-reset/power_on_reason by the mcf-rcm-reset + * driver (drivers/power/reset/mcf-rcm-reset.c). + */ +static struct resource m5441x_rcm_resource[] = { + { + .start = MCF_RSR, + .end = MCF_RSR, + .flags = IORESOURCE_MEM, + }, +}; + +static int __init m5441x_rcm_init(void) +{ + struct platform_device *pdev; + + pdev = platform_device_register_simple("mcf-rcm-reset", -1, + m5441x_rcm_resource, + ARRAY_SIZE(m5441x_rcm_resource)); + return PTR_ERR_OR_ZERO(pdev); +} +arch_initcall(m5441x_rcm_init); + void __init config_BSP(char *commandp, int size) { m5441x_clk_init(); From 314c9e476ffcc77289c25d354c611aa6d1209784 Mon Sep 17 00:00:00 2001 From: Jaegeuk Kim Date: Mon, 3 Aug 2026 20:32:30 +0000 Subject: [PATCH 0530/1328] f2fs: call __add_ino_entry out of the eviction path The f2fs_evict_inode() can be called during the direct reclaim path, but __add_ino_entry requires allocating some memory. Since we don't need to do that in that context, let's migrate it in other workqueue context. Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/checkpoint.c | 11 ++++++ fs/f2fs/data.c | 13 ++++++- fs/f2fs/f2fs.h | 4 ++ fs/f2fs/inode.c | 92 +++++++++++++++++++++++++++++++++++++++++--- fs/f2fs/super.c | 9 ++++- 5 files changed, 122 insertions(+), 7 deletions(-) diff --git a/fs/f2fs/checkpoint.c b/fs/f2fs/checkpoint.c index 064f5b537423..4413eccb5ecb 100644 --- a/fs/f2fs/checkpoint.c +++ b/fs/f2fs/checkpoint.c @@ -766,6 +766,15 @@ static void __remove_ino_entry(struct f2fs_sb_info *sbi, nid_t ino, int type) spin_unlock(&im->ino_lock); } +static void f2fs_wait_for_inode_record(struct f2fs_sb_info *sbi, int mode) +{ + if (mode != APPEND_INO && mode != UPDATE_INO) + return; + + /* Let's wait for some pending updates for APPEND_INO and UPDATE_INO. */ + flush_workqueue(sbi->evict_wq); +} + void f2fs_add_ino_entry(struct f2fs_sb_info *sbi, nid_t ino, int type) { /* add new dirty ino entry into list */ @@ -798,6 +807,8 @@ void f2fs_release_ino_entry(struct f2fs_sb_info *sbi, bool all) for (i = all ? ORPHAN_INO : APPEND_INO; i < MAX_INO_ENTRY; i++) { struct inode_management *im = &sbi->im[i]; + f2fs_wait_for_inode_record(sbi, i); + spin_lock(&im->ino_lock); list_for_each_entry_safe(e, tmp, &im->ino_list, list) { list_del(&e->list); diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c index c219ea76a3a7..6ae0eb37d20f 100644 --- a/fs/f2fs/data.c +++ b/fs/f2fs/data.c @@ -4558,13 +4558,24 @@ int f2fs_init_wq(struct f2fs_sb_info *sbi) { sbi->wq = alloc_workqueue("f2fs_wq", WQ_UNBOUND | WQ_HIGHPRI, num_online_cpus()); - return sbi->wq ? 0 : -ENOMEM; + if (!sbi->wq) + return -ENOMEM; + + sbi->evict_wq = alloc_workqueue("f2fs_evict_wq", + WQ_UNBOUND | WQ_HIGHPRI, num_online_cpus()); + if (!sbi->evict_wq) { + destroy_workqueue(sbi->wq); + return -ENOMEM; + } + return 0; } void f2fs_destroy_wq(struct f2fs_sb_info *sbi) { if (sbi->wq) destroy_workqueue(sbi->wq); + if (sbi->evict_wq) + destroy_workqueue(sbi->evict_wq); } int __init f2fs_init_bio_entry_cache(void) diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index a380b8be8819..7fd3a181af61 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -2013,6 +2013,8 @@ struct f2fs_sb_info { struct workqueue_struct *wq; /* bio completion workqueue */ + struct workqueue_struct *evict_wq; /* inode eviction workqueue */ + /* * If we are in irq context, let's update error information into * on-disk superblock in the work. @@ -3872,6 +3874,8 @@ int f2fs_write_inode(struct inode *inode, struct writeback_control *wbc); void f2fs_remove_donate_inode(struct inode *inode); void f2fs_evict_inode(struct inode *inode); void f2fs_handle_failed_inode(struct inode *inode, struct f2fs_lock_context *lc); +int f2fs_init_evict_inode_work(void); +void f2fs_destroy_evict_inode_work(void); /* * namei.c diff --git a/fs/f2fs/inode.c b/fs/f2fs/inode.c index 553b1e338aa1..bac1e360d966 100644 --- a/fs/f2fs/inode.c +++ b/fs/f2fs/inode.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -24,6 +25,18 @@ extern const struct address_space_operations f2fs_compress_aops; #endif +#define NUM_PREALLOC_EVICT_INODE_WORK 8 + +static struct kmem_cache *evict_inode_work_cache; +static mempool_t *evict_inode_work_pool; + +struct evict_inode_work { + struct work_struct work; + struct f2fs_sb_info *sbi; + nid_t ino; + unsigned int add_ino_entry_bits; +}; + void f2fs_mark_inode_dirty_sync(struct inode *inode, bool sync) { if (is_inode_flag_set(inode, FI_NEW_INODE)) @@ -637,6 +650,9 @@ struct inode *f2fs_iget(struct super_block *sb, unsigned long ino) inode->i_fop = &f2fs_dir_operations; inode->i_mapping->a_ops = &f2fs_dblock_aops; mapping_set_gfp_mask(inode->i_mapping, GFP_NOFS); + + /* Let's prepare APPEND/UPDATE_INO before future access. */ + flush_workqueue(sbi->evict_wq); } else if (S_ISLNK(inode->i_mode)) { if (file_is_encrypt(inode)) inode->i_op = &f2fs_encrypted_symlink_inode_operations; @@ -854,6 +870,25 @@ void f2fs_remove_donate_inode(struct inode *inode) spin_unlock(&sbi->inode_lock[DONATE_INODE]); } +static void f2fs_record_inode_state(struct f2fs_sb_info *sbi, nid_t ino, + unsigned int bits) +{ + if (bits & BIT(APPEND_INO)) + f2fs_add_ino_entry(sbi, ino, APPEND_INO); + if (bits & BIT(UPDATE_INO)) + f2fs_add_ino_entry(sbi, ino, UPDATE_INO); +} + +static void f2fs_evict_inode_work(struct work_struct *work) +{ + struct evict_inode_work *ew = + container_of(work, struct evict_inode_work, work); + + f2fs_record_inode_state(ew->sbi, ew->ino, ew->add_ino_entry_bits); + + mempool_free(ew, evict_inode_work_pool); +} + /* * Return true, if we shouldn't go through post_evict_inode. */ @@ -988,6 +1023,7 @@ static void f2fs_post_evict_inode(struct inode *inode) struct f2fs_sb_info *sbi = F2FS_I_SB(inode); struct f2fs_inode_info *fi = F2FS_I(inode); nid_t xnid = fi->i_xattr_nid; + unsigned int record_bits = 0; dquot_drop(inode); @@ -1014,12 +1050,32 @@ static void f2fs_post_evict_inode(struct inode *inode) inode->i_ino); if (xnid) invalidate_mapping_pages(NODE_MAPPING(sbi), xnid, xnid); - if (inode->i_nlink) { - if (is_inode_flag_set(inode, FI_APPEND_WRITE)) - f2fs_add_ino_entry(sbi, inode->i_ino, APPEND_INO); - if (is_inode_flag_set(inode, FI_UPDATE_WRITE)) - f2fs_add_ino_entry(sbi, inode->i_ino, UPDATE_INO); + + if (!inode->i_nlink) + goto skip_record; + + if (is_inode_flag_set(inode, FI_APPEND_WRITE)) + record_bits = BIT(APPEND_INO); + if (is_inode_flag_set(inode, FI_UPDATE_WRITE)) + record_bits = BIT(UPDATE_INO); + + if (!record_bits) + goto skip_record; + + /* Let's do this in workqueue out of the direct reclaim path. */ + if (current_is_kswapd()) { + f2fs_record_inode_state(sbi, inode->i_ino, record_bits); + } else { + struct evict_inode_work *ew = + mempool_alloc(evict_inode_work_pool, GFP_NOFS); + + ew->sbi = sbi; + ew->ino = inode->i_ino; + ew->add_ino_entry_bits = record_bits; + INIT_WORK(&ew->work, f2fs_evict_inode_work); + queue_work(sbi->evict_wq, &ew->work); } +skip_record: if (is_inode_flag_set(inode, FI_FREE_NID)) { f2fs_alloc_nid_failed(sbi, inode->i_ino); clear_inode_flag(inode, FI_FREE_NID); @@ -1105,3 +1161,29 @@ void f2fs_handle_failed_inode(struct inode *inode, struct f2fs_lock_context *lc) /* iput will drop the inode object */ iput(inode); } + +int __init f2fs_init_evict_inode_work(void) +{ + evict_inode_work_cache = + kmem_cache_create("f2fs_evict_inode_work", + sizeof(struct evict_inode_work), 0, 0, NULL); + if (!evict_inode_work_cache) + goto fail; + evict_inode_work_pool = + mempool_create_slab_pool(NUM_PREALLOC_EVICT_INODE_WORK, + evict_inode_work_cache); + if (!evict_inode_work_pool) + goto fail_free_cache; + return 0; + +fail_free_cache: + kmem_cache_destroy(evict_inode_work_cache); +fail: + return -ENOMEM; +} + +void f2fs_destroy_evict_inode_work(void) +{ + mempool_destroy(evict_inode_work_pool); + kmem_cache_destroy(evict_inode_work_cache); +} diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c index ed9e0ba9cf0b..fa4a7a7288d5 100644 --- a/fs/f2fs/super.c +++ b/fs/f2fs/super.c @@ -5739,10 +5739,16 @@ static int __init init_f2fs_fs(void) err = f2fs_init_xattr_cache(); if (err) goto free_casefold_cache; - err = register_filesystem(&f2fs_fs_type); + err = f2fs_init_evict_inode_work(); if (err) goto free_xattr_cache; + err = register_filesystem(&f2fs_fs_type); + if (err) + goto free_evict_inode_cache; return 0; + +free_evict_inode_cache: + f2fs_destroy_evict_inode_work(); free_xattr_cache: f2fs_destroy_xattr_cache(); free_casefold_cache: @@ -5785,6 +5791,7 @@ static int __init init_f2fs_fs(void) static void __exit exit_f2fs_fs(void) { unregister_filesystem(&f2fs_fs_type); + f2fs_destroy_evict_inode_work(); f2fs_destroy_xattr_cache(); f2fs_destroy_casefold_cache(); f2fs_destroy_compress_cache(); From f06711486c12c1fbb3ea9c0c595ca244b111891f Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Wed, 29 Jul 2026 10:20:42 -0700 Subject: [PATCH 0531/1328] ubifs: debug.h: fix kernel-doc struct prototypes Use the "struct" keyword to describe structs in kernel-doc format. This avoids kernel-doc warnings: Warning: fs/ubifs/debug.h:75 cannot understand function prototype: 'struct ubifs_debug_info' Warning: fs/ubifs/debug.h:130 cannot understand function prototype: 'struct ubifs_global_debug_info' Signed-off-by: Randy Dunlap Signed-off-by: Richard Weinberger --- fs/ubifs/debug.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/ubifs/debug.h b/fs/ubifs/debug.h index d425861e6b82..48cd64eb2d01 100644 --- a/fs/ubifs/debug.h +++ b/fs/ubifs/debug.h @@ -26,7 +26,7 @@ typedef int (*dbg_znode_callback)(struct ubifs_info *c, #define UBIFS_DFS_DIR_LEN (3 + 1 + 2 + 3 + 1) /** - * ubifs_debug_info - per-FS debugging information. + * struct ubifs_debug_info - per-FS debugging information. * @old_zroot: old index root - used by 'dbg_check_old_index()' * @old_zroot_level: old index root level - used by 'dbg_check_old_index()' * @old_zroot_sqnum: old index root sqnum - used by 'dbg_check_old_index()' @@ -119,7 +119,7 @@ struct ubifs_debug_info { }; /** - * ubifs_global_debug_info - global (not per-FS) UBIFS debugging information. + * struct ubifs_global_debug_info - global (not per-FS) UBIFS debugging information. * * @chk_gen: if general extra checks are enabled * @chk_index: if index xtra checks are enabled From abd6591fb5cc1490ab2cf5d9ff513a6677fa57b2 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Wed, 29 Jul 2026 10:20:43 -0700 Subject: [PATCH 0532/1328] ubifs: key.h: use correct function parameter name Use @hash as the function parameter name to prevent kernel-doc warnings: Warning: fs/ubifs/key.h:42 function parameter 'hash' not described in 'key_mask_hash' Warning: fs/ubifs/key.h:42 Excess function parameter 'val' description in 'key_mask_hash' Signed-off-by: Randy Dunlap Reviewed-by: Zhihao Cheng Signed-off-by: Richard Weinberger --- fs/ubifs/key.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/ubifs/key.h b/fs/ubifs/key.h index 8142d9d6fe5d..71497c1b4753 100644 --- a/fs/ubifs/key.h +++ b/fs/ubifs/key.h @@ -33,7 +33,7 @@ /** * key_mask_hash - mask a valid hash value. - * @val: value to be masked + * @hash: value to be masked * * We use hash values as offset in directories, so values %0 and %1 are * reserved for "." and "..". %2 is reserved for "end of readdir" marker. This From 4e5e02340a18183b0f8ad633da1516cad92ee43e Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Wed, 29 Jul 2026 10:20:44 -0700 Subject: [PATCH 0533/1328] ubifs: ubifs.h: clean up kernel-doc comments - use the struct keyword when describing a struct in kernel-doc format. - add or correct missing struct members @leaf, @eof, and @superblock_need_write. - add Returns: clauses for 4 function descriptions. to prevent kernel-doc warnings: Warning: fs/ubifs/ubifs.h:755 struct member 'leaf' not described in 'ubifs_zbranch' Warning: fs/ubifs/ubifs.h:814 struct member 'eof' not described in 'bu_info' Warning: fs/ubifs/ubifs.h:814 Excess struct member 'oef' description in 'bu_info' Warning: fs/ubifs/ubifs.h:992 cannot understand function prototype: 'struct ubifs_stats_info' Warning: fs/ubifs/ubifs.h:1513 struct member 'superblock_need_write' not described in 'ubifs_info' Warning: fs/ubifs/ubifs.h:1597 No description found for return value of 'ubifs_check_hash' Warning: fs/ubifs/ubifs.h:1612 No description found for return value of 'ubifs_check_hmac' Warning: fs/ubifs/ubifs.h:1653 No description found for return value of 'ubifs_branch_hash' Warning: fs/ubifs/ubifs.h:1703 No description found for return value of 'ubifs_auth_node_sz' Signed-off-by: Randy Dunlap Reviewed-by: Zhihao Cheng Signed-off-by: Richard Weinberger --- fs/ubifs/ubifs.h | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/fs/ubifs/ubifs.h b/fs/ubifs/ubifs.h index b62a154c7bd4..00db0d19a85e 100644 --- a/fs/ubifs/ubifs.h +++ b/fs/ubifs/ubifs.h @@ -738,6 +738,7 @@ struct ubifs_jhead { * struct ubifs_zbranch - key/coordinate/length branch stored in znodes. * @key: key * @znode: znode address in memory + * @leaf: leaf node * @lnum: LEB number of the target node (indexing node or data node) * @offs: target node offset within @lnum * @len: target node length @@ -801,7 +802,7 @@ struct ubifs_znode { * @gc_seq: GC sequence number to detect races with GC * @cnt: number of data nodes for bulk read * @blk_cnt: number of data blocks including holes - * @oef: end of file reached + * @eof: end of file reached */ struct bu_info { union ubifs_key key; @@ -985,7 +986,7 @@ struct ubifs_budg_info { }; /** - * ubifs_stats_info - per-FS statistics information. + * struct ubifs_stats_info - per-FS statistics information. * @magic_errors: number of bad magic numbers (will be reset with a new mount). * @node_errors: number of bad nodes (will be reset with a new mount). * @crc_errors: number of bad crcs (will be reset with a new mount). @@ -1051,6 +1052,7 @@ struct ubifs_debug_info; * @rw_incompat: the media is not R/W compatible * @assert_action: action to take when a ubifs_assert() fails * @authenticated: flag indigating the FS is mounted in authenticated mode + * @superblock_need_write: superblock node needs to be written * * @tnc_mutex: protects the Tree Node Cache (TNC), @zroot, @cnext, @enext, and * @calc_idx_sz @@ -1588,8 +1590,9 @@ int ubifs_prepare_auth_node(struct ubifs_info *c, void *node, * @expected: first hash * @got: second hash * - * Compare two hashes @expected and @got. Returns 0 when they are equal, a - * negative error code otherwise. + * Compare two hashes @expected and @got. + * + * Returns: 0 when they are equal, a negative error code otherwise. */ static inline int ubifs_check_hash(const struct ubifs_info *c, const u8 *expected, const u8 *got) @@ -1603,8 +1606,9 @@ static inline int ubifs_check_hash(const struct ubifs_info *c, * @expected: first HMAC * @got: second HMAC * - * Compare two hashes @expected and @got. Returns 0 when they are equal, a - * negative error code otherwise. + * Compare two hashes @expected and @got. + * + * Returns: 0 when they are equal, a negative error code otherwise. */ static inline int ubifs_check_hmac(const struct ubifs_info *c, const u8 *expected, const u8 *got) @@ -1644,7 +1648,7 @@ static inline void ubifs_exit_authentication(struct ubifs_info *c) * @c: UBIFS file-system description object * @br: branch to get the hash from * - * This returns a pointer to the hash of a branch. Since the key already is a + * Returns: a pointer to the hash of a branch. Since the key already is a * dynamically sized object we cannot use a struct member here. */ static inline u8 *ubifs_branch_hash(struct ubifs_info *c, @@ -1694,7 +1698,7 @@ static inline int ubifs_node_verify_hmac(const struct ubifs_info *c, * ubifs_auth_node_sz - returns the size of an authentication node * @c: UBIFS file-system description object * - * This function returns the size of an authentication node which can + * Returns: the size of an authentication node which can * be 0 for unauthenticated filesystems or the real size of an auth node * authentication is enabled. */ From 420477a84f1e52babfe60a43dbdd0988ed956845 Mon Sep 17 00:00:00 2001 From: Zhihao Cheng Date: Sat, 4 Jul 2026 16:30:40 +0800 Subject: [PATCH 0534/1328] UBI: Preserve torture flag when rescheduling failed erasures In __erase_worker(), when ubi_sync_erase() returns -EINTR, -ENOMEM, -EAGAIN, or -EBUSY, the physical eraseblock is rescheduled for erasure via schedule_erase() with the torture flag hardcoded to 0. This unconditionally drops the torture request. If the error occurred before torture_peb() completed (e.g., -ENOMEM from the ec_hdr allocation in ubi_sync_erase(), errors from self_check_not_bad()/nor_erase_prepare() in ubi_io_sync_erase(), or torture_peb() failing mid-test because its internal do_sync_erase()/ ubi_io_read()/ubi_io_write() returned one of the above error codes), the torture test was never finished. Such a PEB may reach the free pool without being tortured, defeating the purpose of the torture test for detecting marginally-bad eraseblocks. If the error occurred after torture_peb() succeeded (the final do_sync_erase() in ubi_io_sync_erase() or ubi_io_write_ec_hdr() in ubi_sync_erase() failed), the torture has already been done and need not be repeated. Pass the torture flag by pointer to ubi_sync_erase() and ubi_io_sync_erase(), and clear it to zero once torture_peb() has completed successfully. __erase_worker() then forwards the updated wl_wrk->torture to schedule_erase(): torture already done is dropped, torture not completed is preserved across the reschedule. Fixes: 784c145444e7 ("UBI: fix error handling in erase worker") Signed-off-by: Zhihao Cheng Signed-off-by: Richard Weinberger --- drivers/mtd/ubi/attach.c | 4 ++-- drivers/mtd/ubi/fastmap.c | 6 +++--- drivers/mtd/ubi/io.c | 11 +++++++---- drivers/mtd/ubi/ubi.h | 4 ++-- drivers/mtd/ubi/wl.c | 15 +++++++++------ 5 files changed, 23 insertions(+), 17 deletions(-) diff --git a/drivers/mtd/ubi/attach.c b/drivers/mtd/ubi/attach.c index 0fa115cbf3ad..0ce7ff7400d3 100644 --- a/drivers/mtd/ubi/attach.c +++ b/drivers/mtd/ubi/attach.c @@ -771,7 +771,7 @@ void ubi_remove_av(struct ubi_attach_info *ai, struct ubi_ainf_volume *av) static int early_erase_peb(struct ubi_device *ubi, const struct ubi_attach_info *ai, int pnum, int ec) { - int err; + int err, torture = 0; struct ubi_ec_hdr *ec_hdr; if ((long long)ec >= UBI_MAX_ERASECOUNTER) { @@ -790,7 +790,7 @@ static int early_erase_peb(struct ubi_device *ubi, ec_hdr->ec = cpu_to_be64(ec); - err = ubi_io_sync_erase(ubi, pnum, 0); + err = ubi_io_sync_erase(ubi, pnum, &torture); if (err < 0) goto out_free; diff --git a/drivers/mtd/ubi/fastmap.c b/drivers/mtd/ubi/fastmap.c index 3bce1b4d8464..98cc698d9c34 100644 --- a/drivers/mtd/ubi/fastmap.c +++ b/drivers/mtd/ubi/fastmap.c @@ -1488,7 +1488,7 @@ static void return_fm_pebs(struct ubi_device *ubi, */ int ubi_update_fastmap(struct ubi_device *ubi) { - int ret, i, j; + int ret, i, j, torture = 0; struct ubi_fastmap_layout *new_fm, *old_fm; struct ubi_wl_entry *tmp_e; @@ -1526,7 +1526,7 @@ int ubi_update_fastmap(struct ubi_device *ubi) if (!tmp_e) { if (old_fm && old_fm->e[i]) { - ret = ubi_sync_erase(ubi, old_fm->e[i], 0); + ret = ubi_sync_erase(ubi, old_fm->e[i], &torture); if (ret < 0) { ubi_err(ubi, "could not erase old fastmap PEB"); @@ -1578,7 +1578,7 @@ int ubi_update_fastmap(struct ubi_device *ubi) if (old_fm) { /* no fresh anchor PEB was found, reuse the old one */ if (!tmp_e) { - ret = ubi_sync_erase(ubi, old_fm->e[0], 0); + ret = ubi_sync_erase(ubi, old_fm->e[0], &torture); if (ret < 0) { ubi_err(ubi, "could not erase old anchor PEB"); diff --git a/drivers/mtd/ubi/io.c b/drivers/mtd/ubi/io.c index 915eb64cb001..d70d1313ad4e 100644 --- a/drivers/mtd/ubi/io.c +++ b/drivers/mtd/ubi/io.c @@ -519,8 +519,10 @@ static int nor_erase_prepare(struct ubi_device *ubi, int pnum) /** * ubi_io_sync_erase - synchronously erase a physical eraseblock. * @ubi: UBI device description object - * @pnum: physical eraseblock number to erase - * @torture: if this physical eraseblock has to be tortured + * @pnum: the physical eraseblock number to erase + * @torture: if this physical eraseblock has to be tortured; cleared to zero + * once the torture test has completed successfully so that a retry + * of the erase does not torture the physical eraseblock again * * This function synchronously erases physical eraseblock @pnum. If @torture * flag is not zero, the physical eraseblock is checked by means of writing @@ -532,7 +534,7 @@ static int nor_erase_prepare(struct ubi_device *ubi, int pnum) * codes in case of other errors. Note, %-EIO means that the physical * eraseblock is bad. */ -int ubi_io_sync_erase(struct ubi_device *ubi, int pnum, int torture) +int ubi_io_sync_erase(struct ubi_device *ubi, int pnum, int *torture) { int err, ret = 0; @@ -560,10 +562,11 @@ int ubi_io_sync_erase(struct ubi_device *ubi, int pnum, int torture) return err; } - if (torture) { + if (*torture) { ret = torture_peb(ubi, pnum); if (ret < 0) return ret; + *torture = 0; } err = do_sync_erase(ubi, pnum); diff --git a/drivers/mtd/ubi/ubi.h b/drivers/mtd/ubi/ubi.h index af466cd83ae0..80b762892922 100644 --- a/drivers/mtd/ubi/ubi.h +++ b/drivers/mtd/ubi/ubi.h @@ -901,7 +901,7 @@ int self_check_eba(struct ubi_device *ubi, struct ubi_attach_info *ai_fastmap, struct ubi_attach_info *ai_scan); /* wl.c */ -int ubi_sync_erase(struct ubi_device *ubi, struct ubi_wl_entry *e, int torture); +int ubi_sync_erase(struct ubi_device *ubi, struct ubi_wl_entry *e, int *torture); int ubi_wl_get_peb(struct ubi_device *ubi); int ubi_wl_put_peb(struct ubi_device *ubi, int vol_id, int lnum, int pnum, int torture); @@ -923,7 +923,7 @@ int ubi_io_read(const struct ubi_device *ubi, void *buf, int pnum, int offset, int len); int ubi_io_write(struct ubi_device *ubi, const void *buf, int pnum, int offset, int len); -int ubi_io_sync_erase(struct ubi_device *ubi, int pnum, int torture); +int ubi_io_sync_erase(struct ubi_device *ubi, int pnum, int *torture); int ubi_io_is_bad(const struct ubi_device *ubi, int pnum); int ubi_io_mark_bad(const struct ubi_device *ubi, int pnum); int ubi_io_read_ec_hdr(struct ubi_device *ubi, int pnum, diff --git a/drivers/mtd/ubi/wl.c b/drivers/mtd/ubi/wl.c index e3705db8e570..edfab98f7064 100644 --- a/drivers/mtd/ubi/wl.c +++ b/drivers/mtd/ubi/wl.c @@ -443,12 +443,14 @@ static int prot_queue_del(struct ubi_device *ubi, int pnum) * ubi_sync_erase - synchronously erase a physical eraseblock. * @ubi: UBI device description object * @e: the physical eraseblock to erase - * @torture: if the physical eraseblock has to be tortured + * @torture: if the physical eraseblock has to be tortured; cleared to zero + * once the torture test has completed successfully so that a retry + * of the erase does not torture the physical eraseblock again * * This function returns zero in case of success and a negative error code in * case of failure. */ -int ubi_sync_erase(struct ubi_device *ubi, struct ubi_wl_entry *e, int torture) +int ubi_sync_erase(struct ubi_device *ubi, struct ubi_wl_entry *e, int *torture) { int err; struct ubi_ec_hdr *ec_hdr; @@ -1113,7 +1115,7 @@ static int __erase_worker(struct ubi_device *ubi, struct ubi_work *wl_wrk) dbg_wl("erase PEB %d EC %d LEB %d:%d", pnum, e->ec, wl_wrk->vol_id, wl_wrk->lnum); - err = ubi_sync_erase(ubi, e, wl_wrk->torture); + err = ubi_sync_erase(ubi, e, &wl_wrk->torture); if (!err) { spin_lock(&ubi->wl_lock); @@ -1150,7 +1152,8 @@ static int __erase_worker(struct ubi_device *ubi, struct ubi_work *wl_wrk) int err1; /* Re-schedule the LEB for erasure */ - err1 = schedule_erase(ubi, e, vol_id, lnum, 0, true); + err1 = schedule_erase(ubi, e, vol_id, lnum, wl_wrk->torture, + true); if (err1) { spin_lock(&ubi->wl_lock); wl_entry_destroy(ubi, e); @@ -1757,7 +1760,7 @@ static void shutdown_work(struct ubi_device *ubi) static int erase_aeb(struct ubi_device *ubi, struct ubi_ainf_peb *aeb, bool sync) { struct ubi_wl_entry *e; - int err; + int err, torture = 0; e = kmem_cache_alloc(ubi_wl_entry_slab, GFP_KERNEL); if (!e) @@ -1768,7 +1771,7 @@ static int erase_aeb(struct ubi_device *ubi, struct ubi_ainf_peb *aeb, bool sync ubi->lookuptbl[e->pnum] = e; if (sync) { - err = ubi_sync_erase(ubi, e, false); + err = ubi_sync_erase(ubi, e, &torture); if (err) goto out_free; From be04fce9945aac2a843f5d16725391eb80d948ef Mon Sep 17 00:00:00 2001 From: Zhihao Cheng Date: Sat, 4 Jul 2026 16:30:41 +0800 Subject: [PATCH 0535/1328] UBI: fastmap: Pass to_be_tortured when reusing old fastmap PEBs In ubi_update_fastmap(), when no fresh PEB is available to replace an old fastmap PEB, the old PEB is reused by erasing it synchronously via ubi_sync_erase() with the torture flag hardcoded to zero. However, old_fm->to_be_tortured[] may be non-zero: it is set during fastmap scanning when ubi_io_read_data()/ubi_io_read_ec_hdr() reports UBI_IO_BITFLIPS, meaning the PEB showed signs of bit decay and should be tortured to verify it is still reliable. When the old PEB is instead returned to the WL sub-system via ubi_wl_put_fm_peb(), the to_be_tortured flag is honored. The synchronous reuse path is inconsistent: it silently skips the torture test for PEBs that exhibited bit-flips. Fix it by passing &old_fm->to_be_tortured[i] to ubi_sync_erase(). Fixes: dbb7d2a88d2a ("UBI: Add fastmap core") Signed-off-by: Zhihao Cheng Signed-off-by: Richard Weinberger --- drivers/mtd/ubi/fastmap.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/mtd/ubi/fastmap.c b/drivers/mtd/ubi/fastmap.c index 98cc698d9c34..3f9fb7c181ff 100644 --- a/drivers/mtd/ubi/fastmap.c +++ b/drivers/mtd/ubi/fastmap.c @@ -1488,7 +1488,7 @@ static void return_fm_pebs(struct ubi_device *ubi, */ int ubi_update_fastmap(struct ubi_device *ubi) { - int ret, i, j, torture = 0; + int ret, i, j; struct ubi_fastmap_layout *new_fm, *old_fm; struct ubi_wl_entry *tmp_e; @@ -1526,7 +1526,8 @@ int ubi_update_fastmap(struct ubi_device *ubi) if (!tmp_e) { if (old_fm && old_fm->e[i]) { - ret = ubi_sync_erase(ubi, old_fm->e[i], &torture); + ret = ubi_sync_erase(ubi, old_fm->e[i], + &old_fm->to_be_tortured[i]); if (ret < 0) { ubi_err(ubi, "could not erase old fastmap PEB"); @@ -1578,7 +1579,8 @@ int ubi_update_fastmap(struct ubi_device *ubi) if (old_fm) { /* no fresh anchor PEB was found, reuse the old one */ if (!tmp_e) { - ret = ubi_sync_erase(ubi, old_fm->e[0], &torture); + ret = ubi_sync_erase(ubi, old_fm->e[0], + &old_fm->to_be_tortured[0]); if (ret < 0) { ubi_err(ubi, "could not erase old anchor PEB"); From 95d27c1708bb6e8823c8e7c623f9abc2a91bf4bf Mon Sep 17 00:00:00 2001 From: Ibrahim Hashimov Date: Fri, 24 Jul 2026 09:43:27 +0200 Subject: [PATCH 0536/1328] ubifs: fix out-of-bounds read in signature length check ubifs_sb_verify_signature() bounds the on-disk ubifs_sig_node->len field before handing the signature payload to verify_pkcs7_signature(), but the check has the wrong sign: if (le32_to_cpu(signode->len) > snod->len + sizeof(struct ubifs_sig_node)) The signature bytes start sizeof(struct ubifs_sig_node) (UBIFS_SIG_NODE_SZ, 64 bytes) into the node, so the payload is at most snod->len - sizeof(struct ubifs_sig_node) bytes long. Adding the header size instead of subtracting it accepts a declared length up to 2 * UBIFS_SIG_NODE_SZ larger than the node actually holds -- past the end of c->sbuf, which is vmalloc(c->leb_size). verify_pkcs7_signature() -> pkcs7_parse_message() -> asn1_ber_decoder() is then handed that inflated length and reads beyond the allocation while walking the DER headers. The node length comes straight from the mounted image, so a crafted signed UBIFS image reaches this via ubifs_read_superblock() before the signature is cryptographically checked. snod->len is guaranteed to be >= UBIFS_SIG_NODE_SZ by the node scanner (c->ranges[UBIFS_SIG_NODE].min_len == UBIFS_SIG_NODE_SZ), so the corrected subtraction cannot underflow. Legitimately signed images are unaffected: a correct superblock never declares a signature longer than the node it is embedded in. Fixes: 817aa094842d ("ubifs: support offline signed images") Cc: stable@vger.kernel.org Signed-off-by: Ibrahim Hashimov Assisted-by: AuditCode-AI:2026.07 Reviewed-by: Richard Weinberger Reviewed-by: Zhihao Cheng Signed-off-by: Richard Weinberger --- fs/ubifs/auth.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/ubifs/auth.c b/fs/ubifs/auth.c index a4a0158f712d..1f770795ee70 100644 --- a/fs/ubifs/auth.c +++ b/fs/ubifs/auth.c @@ -217,7 +217,7 @@ int ubifs_sb_verify_signature(struct ubifs_info *c, signode = snod->node; - if (le32_to_cpu(signode->len) > snod->len + sizeof(struct ubifs_sig_node)) { + if (le32_to_cpu(signode->len) > snod->len - sizeof(struct ubifs_sig_node)) { ubifs_err(c, "invalid signature len %d", le32_to_cpu(signode->len)); err = -EINVAL; goto out_destroy; From 5b0a6b554e12a97f9771a9a9f4ea1f5457373c73 Mon Sep 17 00:00:00 2001 From: Yuho Choi Date: Mon, 8 Jun 2026 00:06:39 -0400 Subject: [PATCH 0537/1328] ubi: Fix rollback for explicit UBI device numbers ubi_init_attach() rolls back module initialization failures by scanning ubi_devices[0..i-1], where i is the mtd= parameter index. That assumes the parameter index matches the UBI device number. That assumption is not true when mtd= specifies an explicit ubi_num. A successfully attached device can be stored at a higher ubi_devices[] slot, and a later failure can miss it during rollback. Scan the full ubi_devices[] array and detach by the actual array index, matching the way UBI devices are stored. Fixes: 83ff59a06663 ("UBI: support ubi_num on mtd.ubi command line") Signed-off-by: Yuho Choi Reviewed-by: Zhihao Cheng Signed-off-by: Richard Weinberger --- drivers/mtd/ubi/build.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/mtd/ubi/build.c b/drivers/mtd/ubi/build.c index 674ad87809df..90c5c83d90e3 100644 --- a/drivers/mtd/ubi/build.c +++ b/drivers/mtd/ubi/build.c @@ -1317,10 +1317,10 @@ static int __init ubi_init_attach(void) return 0; out_detach: - for (k = 0; k < i; k++) + for (k = 0; k < UBI_MAX_DEVICES; k++) if (ubi_devices[k]) { mutex_lock(&ubi_devices_mutex); - ubi_detach_mtd_dev(ubi_devices[k]->ubi_num, 1); + ubi_detach_mtd_dev(k, 1); mutex_unlock(&ubi_devices_mutex); } return err; From 31dd710cd84d5dd63c49f640d3a9f36c9699ca95 Mon Sep 17 00:00:00 2001 From: Yuho Choi Date: Fri, 7 Aug 2026 16:45:50 -0400 Subject: [PATCH 0538/1328] mtd: ubi: Release device reference on busy detach ubi_detach_mtd_dev() obtains a device reference through ubi_get_device() before checking whether the UBI device is busy. The busy return path drops ubi->ref_count but leaves the device reference held, so the device object cannot be released after a later detach. Drop the device reference before returning -EBUSY. Fixes: 7e84c961b2eb ("mtd: ubi: introduce pre-removal notification for UBI volumes") Signed-off-by: Yuho Choi Signed-off-by: Richard Weinberger --- drivers/mtd/ubi/build.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mtd/ubi/build.c b/drivers/mtd/ubi/build.c index 90c5c83d90e3..1997bb70b702 100644 --- a/drivers/mtd/ubi/build.c +++ b/drivers/mtd/ubi/build.c @@ -1105,6 +1105,7 @@ int ubi_detach_mtd_dev(int ubi_num, int anyway) ubi->ref_count -= 1; if (ubi->ref_count) { if (!anyway) { + put_device(&ubi->dev); spin_unlock(&ubi_devices_lock); return -EBUSY; } From ff7937d14f5333ec7e6fb50d00a152d7974f4105 Mon Sep 17 00:00:00 2001 From: Ran Hongyun Date: Thu, 23 Jul 2026 10:02:32 +0800 Subject: [PATCH 0539/1328] UBI: fix two issues in the ubi.mtd MODULE_PARM_DESC Fix two issues introduced before: - The parameter format string was missing the enable_fm and need_resv_pool tokens introduced in an earlier commit. - The bad-block reservation note was misplaced after Example 5 instead of after Example 3. It was misplaced due to an earlier patch. Fixes: 83ff59a06663 ("UBI: support ubi_num on mtd.ubi command line") Signed-off-by: Ran Hongyun Reviewed-by: Zhihao Cheng Signed-off-by: Richard Weinberger --- drivers/mtd/ubi/build.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/mtd/ubi/build.c b/drivers/mtd/ubi/build.c index 1997bb70b702..456090a12f52 100644 --- a/drivers/mtd/ubi/build.c +++ b/drivers/mtd/ubi/build.c @@ -1575,7 +1575,7 @@ static int ubi_mtd_param_parse(const char *val, const struct kernel_param *kp) } module_param_call(mtd, ubi_mtd_param_parse, NULL, NULL, 0400); -MODULE_PARM_DESC(mtd, "MTD devices to attach. Parameter format: mtd=[,[,max_beb_per1024[,ubi_num]]].\n" +MODULE_PARM_DESC(mtd, "MTD devices to attach. Parameter format: mtd=[,[,max_beb_per1024[,ubi_num[,enable_fm[,need_resv_pool]]]]].\n" "Multiple \"mtd\" parameters may be specified.\n" "MTD devices may be specified by their number, name, or path to the MTD character device node.\n" "Optional \"vid_hdr_offs\" parameter specifies UBI VID header position to be used by UBI. (default value if 0)\n" @@ -1588,9 +1588,9 @@ MODULE_PARM_DESC(mtd, "MTD devices to attach. Parameter format: mtd= Date: Thu, 23 Jul 2026 10:02:33 +0800 Subject: [PATCH 0540/1328] UBI: support per-device wear-leveling threshold The UBI wear-leveling threshold (CONFIG_MTD_UBI_WL_THRESHOLD) is currently a compile-time constant shared by all UBI devices. When a single kernel image must support multiple NAND flashes with different erase lifetimes, one global threshold cannot suit all devices. Add a per-device configurable wl_threshold parameter: - UAPI: add __s32 wl_threshold to struct ubi_attach_req, carved from the existing padding. - Module parameter: ubi.mtd gains a new optional token "wl_threshold": ubi.mtd=0,0,0,0,0,0,256 ubi.mtd=1,0,0,0,0,0,4096 0 means "use the kernel default", the accepted range is 2-65536. Signed-off-by: Ran Hongyun Reviewed-by: Zhihao Cheng Signed-off-by: Richard Weinberger --- drivers/mtd/ubi/build.c | 43 ++++++++++++++++++++++++++++++------ drivers/mtd/ubi/cdev.c | 2 +- drivers/mtd/ubi/fastmap-wl.c | 8 +++---- drivers/mtd/ubi/ubi.h | 21 +++++++++++++++++- drivers/mtd/ubi/wl.c | 39 ++++++++------------------------ include/uapi/mtd/ubi-user.h | 10 ++++++++- 6 files changed, 79 insertions(+), 44 deletions(-) diff --git a/drivers/mtd/ubi/build.c b/drivers/mtd/ubi/build.c index 456090a12f52..910e6b0ff5e7 100644 --- a/drivers/mtd/ubi/build.c +++ b/drivers/mtd/ubi/build.c @@ -36,7 +36,7 @@ #define MTD_PARAM_LEN_MAX 64 /* Maximum number of comma-separated items in the 'mtd=' parameter */ -#define MTD_PARAM_MAX_COUNT 6 +#define MTD_PARAM_MAX_COUNT 7 /* Maximum value for the number of bad PEBs per 1024 PEBs */ #define MAX_MTD_UBI_BEB_LIMIT 768 @@ -56,6 +56,7 @@ * @max_beb_per1024: maximum expected number of bad PEBs per 1024 PEBs * @enable_fm: enable fastmap when value is non-zero * @need_resv_pool: reserve pool->max_size pebs when value is none-zero + * @wl_threshold: wear-leveling threshold, 0 means use CONFIG_MTD_UBI_WL_THRESHOLD */ struct mtd_dev_param { char name[MTD_PARAM_LEN_MAX]; @@ -64,6 +65,7 @@ struct mtd_dev_param { int max_beb_per1024; int enable_fm; int need_resv_pool; + int wl_threshold; }; /* Numbers of elements set in the @mtd_dev_param array */ @@ -832,6 +834,8 @@ static int autoresize(struct ubi_device *ubi, int vol_id) * @max_beb_per1024: maximum expected number of bad PEB per 1024 PEBs * @disable_fm: whether disable fastmap * @need_resv_pool: whether reserve pebs to fill fm_pool + * @wl_threshold: wear-leveling threshold for this UBI device; 0 means use + * %CONFIG_MTD_UBI_WL_THRESHOLD; accepted range is 2-65536 * * This function attaches MTD device @mtd_dev to UBI and assign @ubi_num number * to the newly created UBI device, unless @ubi_num is %UBI_DEV_NUM_AUTO, in @@ -848,7 +852,7 @@ static int autoresize(struct ubi_device *ubi, int vol_id) */ int ubi_attach_mtd_dev(struct mtd_info *mtd, int ubi_num, int vid_hdr_offset, int max_beb_per1024, bool disable_fm, - bool need_resv_pool) + bool need_resv_pool, int wl_threshold) { struct ubi_device *ubi; int i, err; @@ -859,6 +863,15 @@ int ubi_attach_mtd_dev(struct mtd_info *mtd, int ubi_num, if (!max_beb_per1024) max_beb_per1024 = CONFIG_MTD_UBI_BEB_LIMIT; + if (!wl_threshold) + wl_threshold = CONFIG_MTD_UBI_WL_THRESHOLD; + + if (wl_threshold < 2 || wl_threshold > 65536) { + pr_err("ubi: bad wear-leveling threshold %d\n", + wl_threshold); + return -EINVAL; + } + /* * Check if we already have the same MTD device attached. * @@ -944,6 +957,8 @@ int ubi_attach_mtd_dev(struct mtd_info *mtd, int ubi_num, ubi->ubi_num = ubi_num; ubi->vid_hdr_offset = vid_hdr_offset; ubi->autoresize_vol_id = -1; + ubi->wl_threshold = wl_threshold; + ubi->wl_free_max_diff = wl_threshold * 2; #ifdef CONFIG_MTD_UBI_FASTMAP ubi->fm_pool.used = ubi->fm_pool.size = 0; @@ -1044,7 +1059,7 @@ int ubi_attach_mtd_dev(struct mtd_info *mtd, int ubi_num, ubi->vol_count - UBI_INT_VOL_COUNT, UBI_INT_VOL_COUNT, ubi->vtbl_slots); ubi_msg(ubi, "max/mean erase counter: %d/%d, WL threshold: %d, image sequence number: %u", - ubi->max_ec, ubi->mean_ec, CONFIG_MTD_UBI_WL_THRESHOLD, + ubi->max_ec, ubi->mean_ec, ubi->wl_threshold, ubi->image_seq); ubi_msg(ubi, "available PEBs: %d, total reserved PEBs: %d, PEBs reserved for bad PEB handling: %d", ubi->avail_pebs, ubi->rsvd_pebs, ubi->beb_rsvd_pebs); @@ -1248,7 +1263,7 @@ static void ubi_notify_add(struct mtd_info *mtd) /* called while holding mtd_table_mutex */ mutex_lock_nested(&ubi_devices_mutex, SINGLE_DEPTH_NESTING); - err = ubi_attach_mtd_dev(mtd, UBI_DEV_NUM_AUTO, 0, 0, false, false); + err = ubi_attach_mtd_dev(mtd, UBI_DEV_NUM_AUTO, 0, 0, false, false, 0); mutex_unlock(&ubi_devices_mutex); if (err < 0) __put_mtd_device(mtd); @@ -1290,7 +1305,8 @@ static int __init ubi_init_attach(void) err = ubi_attach_mtd_dev(mtd, p->ubi_num, p->vid_hdr_offs, p->max_beb_per1024, p->enable_fm == 0, - p->need_resv_pool != 0); + p->need_resv_pool != 0, + p->wl_threshold); mutex_unlock(&ubi_devices_mutex); if (err < 0) { pr_err("UBI error: cannot attach mtd%d\n", @@ -1570,12 +1586,24 @@ static int ubi_mtd_param_parse(const char *val, const struct kernel_param *kp) } else p->need_resv_pool = 0; + token = tokens[6]; + if (token) { + int err = kstrtoint(token, 10, &p->wl_threshold); + + if (err) { + pr_err("UBI error: bad value for wl_threshold parameter: %s\n", + token); + return -EINVAL; + } + } else + p->wl_threshold = 0; + mtd_devs += 1; return 0; } module_param_call(mtd, ubi_mtd_param_parse, NULL, NULL, 0400); -MODULE_PARM_DESC(mtd, "MTD devices to attach. Parameter format: mtd=[,[,max_beb_per1024[,ubi_num[,enable_fm[,need_resv_pool]]]]].\n" +MODULE_PARM_DESC(mtd, "MTD devices to attach. Parameter format: mtd=[,[,max_beb_per1024[,ubi_num[,enable_fm[,need_resv_pool[,wl_threshold]]]]]].\n" "Multiple \"mtd\" parameters may be specified.\n" "MTD devices may be specified by their number, name, or path to the MTD character device node.\n" "Optional \"vid_hdr_offs\" parameter specifies UBI VID header position to be used by UBI. (default value if 0)\n" @@ -1590,7 +1618,8 @@ MODULE_PARM_DESC(mtd, "MTD devices to attach. Parameter format: mtd=free, WL_FREE_MAX_DIFF, + e = find_wl_entry(ubi, &ubi->free, ubi->wl_free_max_diff, !can_fill_pools(ubi, left_free)); self_check_in_wl_tree(ubi, e, &ubi->free); rb_erase(&e->u.rb, &ubi->free); @@ -392,18 +392,18 @@ static bool need_wear_leveling(struct ubi_device *ubi) if (!e) { if (!ubi->free.rb_node) return false; - e = find_wl_entry(ubi, &ubi->free, WL_FREE_MAX_DIFF, 0); + e = find_wl_entry(ubi, &ubi->free, ubi->wl_free_max_diff, 0); ec = e->ec; } else { ec = e->ec; if (ubi->free.rb_node) { - e = find_wl_entry(ubi, &ubi->free, WL_FREE_MAX_DIFF, 0); + e = find_wl_entry(ubi, &ubi->free, ubi->wl_free_max_diff, 0); ec = max(ec, e->ec); } } e = rb_entry(rb_first(&ubi->used), struct ubi_wl_entry, u.rb); - return ec - e->ec >= UBI_WL_THRESHOLD; + return ec - e->ec >= ubi->wl_threshold; } /* get_peb_for_wl - returns a PEB to be used internally by the WL sub-system. diff --git a/drivers/mtd/ubi/ubi.h b/drivers/mtd/ubi/ubi.h index 80b762892922..8a9ac60ff2f4 100644 --- a/drivers/mtd/ubi/ubi.h +++ b/drivers/mtd/ubi/ubi.h @@ -516,6 +516,22 @@ struct ubi_debug_info { * @bgt_thread: background thread description object * @thread_enabled: if the background thread is enabled * @bgt_name: background thread name + * @wl_threshold: Maximum difference between two erase counters. If this + * threshold is exceeded, the WL sub-system starts moving + * data from used physical eraseblocks with low erase + * counter to free physical eraseblocks with high erase counter. + * @wl_free_max_diff: When a physical eraseblock is moved, the WL sub-system + * has to pick the target physical eraseblock to move to. + * The simplest way would be just to pick the one with the + * highest erase counter. But in certain workloads this + * could lead to an unlimited wear of one or few physical + * eraseblock. Indeed, imagine a situation when the picked + * physical eraseblock is constantly erased after the + * data is written to it. So, we have a constant which + * limits the highest erase counter of the free physical + * eraseblock to pick. Namely, the WL sub-system does not + * pick eraseblocks with erase counter greater than the + * lowest erase counter plus @wl_free_max_diff. * * @flash_size: underlying MTD device size (in bytes) * @peb_count: count of physical eraseblocks on the MTD device @@ -623,6 +639,8 @@ struct ubi_device { struct task_struct *bgt_thread; int thread_enabled; char bgt_name[sizeof(UBI_BGT_NAME_PATTERN)+2]; + int wl_threshold; + int wl_free_max_diff; /* I/O sub-system's stuff */ long long flash_size; @@ -938,7 +956,8 @@ int ubi_io_write_vid_hdr(struct ubi_device *ubi, int pnum, /* build.c */ int ubi_attach_mtd_dev(struct mtd_info *mtd, int ubi_num, int vid_hdr_offset, int max_beb_per1024, - bool disable_fm, bool need_resv_pool); + bool disable_fm, bool need_resv_pool, + int wl_threshold); int ubi_detach_mtd_dev(int ubi_num, int anyway); struct ubi_device *ubi_get_device(int ubi_num); void ubi_put_device(struct ubi_device *ubi); diff --git a/drivers/mtd/ubi/wl.c b/drivers/mtd/ubi/wl.c index edfab98f7064..5a0db928a90e 100644 --- a/drivers/mtd/ubi/wl.c +++ b/drivers/mtd/ubi/wl.c @@ -95,27 +95,6 @@ /* Number of physical eraseblocks reserved for wear-leveling purposes */ #define WL_RESERVED_PEBS 1 -/* - * Maximum difference between two erase counters. If this threshold is - * exceeded, the WL sub-system starts moving data from used physical - * eraseblocks with low erase counter to free physical eraseblocks with high - * erase counter. - */ -#define UBI_WL_THRESHOLD CONFIG_MTD_UBI_WL_THRESHOLD - -/* - * When a physical eraseblock is moved, the WL sub-system has to pick the target - * physical eraseblock to move to. The simplest way would be just to pick the - * one with the highest erase counter. But in certain workloads this could lead - * to an unlimited wear of one or few physical eraseblock. Indeed, imagine a - * situation when the picked physical eraseblock is constantly erased after the - * data is written to it. So, we have a constant which limits the highest erase - * counter of the free physical eraseblock to pick. Namely, the WL sub-system - * does not pick eraseblocks with erase counter greater than the lowest erase - * counter plus %WL_FREE_MAX_DIFF. - */ -#define WL_FREE_MAX_DIFF (2*UBI_WL_THRESHOLD) - /* * Maximum number of consecutive background thread failures which is enough to * switch to read-only mode. @@ -358,7 +337,7 @@ static struct ubi_wl_entry *find_wl_entry(struct ubi_device *ubi, * * This function looks for a wear leveling entry with medium erase counter, * but not greater or equivalent than the lowest erase counter plus - * %WL_FREE_MAX_DIFF/2. + * @ubi->wl_free_max_diff/2. */ static struct ubi_wl_entry *find_mean_wl_entry(struct ubi_device *ubi, struct rb_root *root) @@ -368,7 +347,7 @@ static struct ubi_wl_entry *find_mean_wl_entry(struct ubi_device *ubi, first = rb_entry(rb_first(root), struct ubi_wl_entry, u.rb); last = rb_entry(rb_last(root), struct ubi_wl_entry, u.rb); - if (last->ec - first->ec < WL_FREE_MAX_DIFF) { + if (last->ec - first->ec < ubi->wl_free_max_diff) { e = rb_entry(root->rb_node, struct ubi_wl_entry, u.rb); /* @@ -379,7 +358,7 @@ static struct ubi_wl_entry *find_mean_wl_entry(struct ubi_device *ubi, */ e = may_reserve_for_fm(ubi, e, root); } else - e = find_wl_entry(ubi, root, WL_FREE_MAX_DIFF/2, 0); + e = find_wl_entry(ubi, root, ubi->wl_free_max_diff/2, 0); return e; } @@ -708,7 +687,7 @@ static int wear_leveling_worker(struct ubi_device *ubi, struct ubi_work *wrk, #ifdef CONFIG_MTD_UBI_FASTMAP e1 = find_anchor_wl_entry(&ubi->used); if (e1 && ubi->fm_anchor && - (ubi->fm_anchor->ec - e1->ec >= UBI_WL_THRESHOLD)) { + (ubi->fm_anchor->ec - e1->ec >= ubi->wl_threshold)) { ubi->fm_do_produce_anchor = 1; /* * fm_anchor is no longer considered a good anchor. @@ -745,7 +724,7 @@ static int wear_leveling_worker(struct ubi_device *ubi, struct ubi_work *wrk, if (!e2) goto out_cancel; - if (!(e2->ec - e1->ec >= UBI_WL_THRESHOLD)) { + if (!(e2->ec - e1->ec >= ubi->wl_threshold)) { dbg_wl("no WL needed: min used EC %d, max free EC %d", e1->ec, e2->ec); @@ -1058,12 +1037,12 @@ static int ensure_wear_leveling(struct ubi_device *ubi, int nested) * We schedule wear-leveling only if the difference between the * lowest erase counter of used physical eraseblocks and a high * erase counter of free physical eraseblocks is greater than - * %UBI_WL_THRESHOLD. + * @ubi->wl_threshold. */ e1 = rb_entry(rb_first(&ubi->used), struct ubi_wl_entry, u.rb); - e2 = find_wl_entry(ubi, &ubi->free, WL_FREE_MAX_DIFF, 0); + e2 = find_wl_entry(ubi, &ubi->free, ubi->wl_free_max_diff, 0); - if (!(e2->ec - e1->ec >= UBI_WL_THRESHOLD)) + if (!(e2->ec - e1->ec >= ubi->wl_threshold)) goto out_unlock; #endif dbg_wl("schedule wear-leveling"); @@ -2093,7 +2072,7 @@ static struct ubi_wl_entry *get_peb_for_wl(struct ubi_device *ubi) { struct ubi_wl_entry *e; - e = find_wl_entry(ubi, &ubi->free, WL_FREE_MAX_DIFF, 0); + e = find_wl_entry(ubi, &ubi->free, ubi->wl_free_max_diff, 0); self_check_in_wl_tree(ubi, e, &ubi->free); ubi->free_count--; ubi_assert(ubi->free_count >= 0); diff --git a/include/uapi/mtd/ubi-user.h b/include/uapi/mtd/ubi-user.h index aa872a41ffb9..3538e11b5175 100644 --- a/include/uapi/mtd/ubi-user.h +++ b/include/uapi/mtd/ubi-user.h @@ -289,6 +289,13 @@ enum { * If @disable_fm is not zero, ubi doesn't create new fastmap even the module * param 'fm_autoconvert' is set, and existed old fastmap will be destroyed * after doing full scanning. + * + * The @wl_threshold defines the maximum difference between the highest and the + * lowest erase counter value of eraseblocks of this UBI device. When this + * threshold is exceeded, UBI starts performing wear leveling by means of + * moving data from eraseblock with low erase counter to eraseblocks with high + * erase counter. If @wl_threshold is zero, the default kernel value of + * %CONFIG_MTD_UBI_WL_THRESHOLD is used. The accepted range is 2-65536. */ struct ubi_attach_req { __s32 ubi_num; @@ -297,7 +304,8 @@ struct ubi_attach_req { __s16 max_beb_per1024; __s8 disable_fm; __s8 need_resv_pool; - __s8 padding[8]; + __s32 wl_threshold; + __s8 padding[4]; }; /* From da3abd0445245077a1ac45b97ee0a59f8d300d31 Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Fri, 12 Jun 2026 16:52:51 -0500 Subject: [PATCH 0541/1328] clk: at91: Read "reg" with helper The "reg" property is an address-sized DT cell property. The AT91 compat clock parser only uses a small bus id from it, but reading it with the u8 helper does not match the property encoding. Use of_property_read_reg() so the code goes through the helper for "reg" properties, then keep the existing range check before passing the bus id to the clock registration code. Assisted-by: Codex:gpt-5-5 Signed-off-by: Rob Herring (Arm) Reviewed-by: Brian Masney Link: https://patch.msgid.link/20260612215251.1888345-1-robh@kernel.org Signed-off-by: Claudiu Beznea --- drivers/clk/at91/dt-compat.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/clk/at91/dt-compat.c b/drivers/clk/at91/dt-compat.c index 5d543e807843..dad26dd5e71d 100644 --- a/drivers/clk/at91/dt-compat.c +++ b/drivers/clk/at91/dt-compat.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -217,7 +218,7 @@ CLK_OF_DECLARE(of_sama5d4_clk_h32mx_setup, "atmel,sama5d4-clk-h32mx", static void __init of_sama5d2_clk_i2s_mux_setup(struct device_node *np) { struct regmap *regmap_sfr; - u8 bus_id; + u64 bus_id; const char *parent_names[2]; struct device_node *i2s_mux_np; struct clk_hw *hw; @@ -228,7 +229,7 @@ static void __init of_sama5d2_clk_i2s_mux_setup(struct device_node *np) return; for_each_child_of_node(np, i2s_mux_np) { - if (of_property_read_u8(i2s_mux_np, "reg", &bus_id)) + if (of_property_read_reg(i2s_mux_np, 0, &bus_id, NULL)) continue; if (bus_id > I2S_BUS_NR) From 47504742cea7878ebd1bf1491bbed923df6b90b1 Mon Sep 17 00:00:00 2001 From: Ali Ahmet Memis Date: Sun, 2 Aug 2026 13:53:07 +0000 Subject: [PATCH 0542/1328] mfd: qnap-mcu: keep the reply buffer alive past a command timeout qnap_mcu_exec() publishes an on-stack buffer to the receive path: unsigned char rx[QNAP_MCU_RX_BUFFER_SIZE]; ... reply->data = rx; reply->length = length; and qnap_mcu_receive_buf() writes into it from the serdev receive path, which runs out of flush_to_ldisc() and is not serialized against qnap_mcu_exec() at all. bus_lock cannot cover it, because qnap_mcu_exec() holds that mutex across wait_for_completion_timeout(). On a timeout qnap_mcu_exec() returns with reply->data still pointing at its own frame. A reply that arrives late, or an unsolicited message from the MCU, is then written into a stack frame that has been left, corrupting whatever runs next on that stack. The same applies when qnap_mcu_write() fails, since that path returns without touching the reply state either. Move the receive buffer into struct qnap_mcu. It is 37 bytes and the structure is devm_kzalloc()ed, so it lives as long as the driver, and a late write lands in memory that is still valid and is reinitialized by the next command. bus_lock keeps commands from sharing it. This deliberately does not clear reply->data or reply->length on the timeout path. Doing so races with qnap_mcu_receive_buf(), which reads both after its if (!reply->length) return size; check: clearing reply->data gives a NULL dereference, and clearing reply->length alone removes the reply->received == reply->length exit condition, so the copy loop runs until the uart chunk is consumed and overruns the buffer. Leaving both set keeps the write bounded by reply->length, which qnap_mcu_exec() has already checked against sizeof(mcu->rx). Fixes: 998f70d1806b ("mfd: Add base driver for qnap-mcu devices") Cc: stable@vger.kernel.org Signed-off-by: Ali Ahmet Memis Link: https://lore.kernel.org/all/20260802132012.537B81F000E9@smtp.kernel.org/ Link: https://patch.msgid.link/20260802135307.31380-1-ali@iusegentoo.com Signed-off-by: Lee Jones --- drivers/mfd/qnap-mcu.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/mfd/qnap-mcu.c b/drivers/mfd/qnap-mcu.c index 8de974ddac3e..93a3dd93404d 100644 --- a/drivers/mfd/qnap-mcu.c +++ b/drivers/mfd/qnap-mcu.c @@ -56,6 +56,7 @@ struct qnap_mcu_reply { * @reply: Reply data structure * @variant: Device variant specific information * @version: MCU firmware version + * @rx: Receive buffer the reply is assembled in */ struct qnap_mcu { struct serdev_device *serdev; @@ -63,6 +64,7 @@ struct qnap_mcu { struct qnap_mcu_reply reply; const struct qnap_mcu_variant *variant; u8 version[QNAP_MCU_VERSION_LEN]; + u8 rx[QNAP_MCU_RX_BUFFER_SIZE]; }; /* @@ -214,19 +216,18 @@ int qnap_mcu_exec(struct qnap_mcu *mcu, const u8 *cmd_data, size_t cmd_data_size, u8 *reply_data, size_t reply_data_size) { - unsigned char rx[QNAP_MCU_RX_BUFFER_SIZE]; size_t length = reply_data_size + QNAP_MCU_CHECKSUM_SIZE; struct qnap_mcu_reply *reply = &mcu->reply; int ret = 0; - if (length > sizeof(rx)) { + if (length > sizeof(mcu->rx)) { dev_err(&mcu->serdev->dev, "expected data too big for receive buffer"); return -EINVAL; } guard(mutex)(&mcu->bus_lock); - reply->data = rx; + reply->data = mcu->rx; reply->length = length; reply->received = 0; reinit_completion(&reply->done); @@ -242,15 +243,15 @@ int qnap_mcu_exec(struct qnap_mcu *mcu, return -ETIMEDOUT; } - if (!qnap_mcu_verify_checksum(rx, reply->received)) { + if (!qnap_mcu_verify_checksum(mcu->rx, reply->received)) { dev_err(&mcu->serdev->dev, "Invalid Checksum received from controller\n"); return -EPROTO; } - if (qnap_mcu_reply_is_any_error(mcu, rx, reply->received)) + if (qnap_mcu_reply_is_any_error(mcu, mcu->rx, reply->received)) return -EPROTO; - memcpy(reply_data, rx, reply_data_size); + memcpy(reply_data, mcu->rx, reply_data_size); return 0; } From d86dadc6feeaa86b6a29db5509bfe8c65b66c7b2 Mon Sep 17 00:00:00 2001 From: Pinkesh Vaghela Date: Tue, 4 Aug 2026 16:14:27 +0530 Subject: [PATCH 0543/1328] dt-bindings: mfd: syscon: Add ESWIN EIC7700 compatible Document ESWIN EIC7700 SoC compatible for syscon registers. Signed-off-by: Pinkesh Vaghela Acked-by: Conor Dooley Link: https://patch.msgid.link/20260804104431.1391839-5-pinkesh.vaghela@einfochips.com Signed-off-by: Lee Jones --- Documentation/devicetree/bindings/mfd/syscon.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/mfd/syscon.yaml b/Documentation/devicetree/bindings/mfd/syscon.yaml index fe882d3f828b..8e24c6be2fdd 100644 --- a/Documentation/devicetree/bindings/mfd/syscon.yaml +++ b/Documentation/devicetree/bindings/mfd/syscon.yaml @@ -59,6 +59,7 @@ properties: - cirrus,ep7209-syscon3 - cnxt,cx92755-uc - econet,en751221-chip-scu + - eswin,eic7700-syscfg - freecom,fsg-cs2-system-controller - fsl,imx93-aonmix-ns-syscfg - fsl,imx93-wakeupmix-syscfg From 2b02aa1b547a57e1e7aad1364a57520cf4ac49cf Mon Sep 17 00:00:00 2001 From: Binbin Zhou Date: Tue, 4 Aug 2026 20:43:56 +0800 Subject: [PATCH 0544/1328] dt-bindings: mfd: syscon: Allow syscon compatible for Loongson-2K0300 chip id The Loongson-2K0300 SoC exposes its chip ID registers through a syscon interface. Add the specific compatible `loongson,ls2k0300-chipid-syscon` to the allowed list of syscon bindings so that it can be referenced from the thermal node via a phandle. Signed-off-by: Binbin Zhou Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/b76405a88b0cded91ba370139d7223fe2d6953df.1785829933.git.zhoubinbin@loongson.cn Signed-off-by: Lee Jones --- Documentation/devicetree/bindings/mfd/syscon.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/mfd/syscon.yaml b/Documentation/devicetree/bindings/mfd/syscon.yaml index 8e24c6be2fdd..945a168b613d 100644 --- a/Documentation/devicetree/bindings/mfd/syscon.yaml +++ b/Documentation/devicetree/bindings/mfd/syscon.yaml @@ -74,6 +74,7 @@ properties: - hpe,gxp-sysreg - loongson,ls1b-syscon - loongson,ls1c-syscon + - loongson,ls2k0300-chipid-syscon - lsi,axxia-syscon - marvell,armada-3700-cpu-misc - marvell,armada-3700-nb-pm From 9d0e4b1ae5b045a2c92b0b9a1c3b268191c219d9 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Wed, 5 Aug 2026 13:01:09 +0100 Subject: [PATCH 0545/1328] mfd: cs42l43: Fix regmap defaults ordering The regmap defaults should be ordered as binary search is done on the array. A few registers were added in the wrong places, move these to be in register address order. Fixes: a6fe20d67dc7 ("mfd: cs42l43: Add support for the B variant") Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20260805120109.4024451-1-ckeepax@opensource.cirrus.com Signed-off-by: Lee Jones --- drivers/mfd/cs42l43.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/mfd/cs42l43.c b/drivers/mfd/cs42l43.c index 33479ddd539a..4212ebcca60b 100644 --- a/drivers/mfd/cs42l43.c +++ b/drivers/mfd/cs42l43.c @@ -115,14 +115,14 @@ const struct reg_default cs42l43_reg_default[CS42L43_N_DEFAULTS] = { { CS42L43_DECIM_HPF_WNF_CTRL2, 0x00000001 }, { CS42L43_DECIM_HPF_WNF_CTRL3, 0x00000001 }, { CS42L43_DECIM_HPF_WNF_CTRL4, 0x00000001 }, - { CS42L43B_DECIM_HPF_WNF_CTRL5, 0x00000001 }, - { CS42L43B_DECIM_HPF_WNF_CTRL6, 0x00000001 }, { CS42L43_DMIC_PDM_CTRL, 0x00000000 }, { CS42L43_DECIM_VOL_CTRL_CH1_CH2, 0x20122012 }, { CS42L43_DECIM_VOL_CTRL_CH3_CH4, 0x20122012 }, { CS42L43B_DECIM_VOL_CTRL_CH1_CH2, 0x20122012 }, { CS42L43B_DECIM_VOL_CTRL_CH3_CH4, 0x20122012 }, { CS42L43B_DECIM_VOL_CTRL_CH5_CH6, 0x20122012 }, + { CS42L43B_DECIM_HPF_WNF_CTRL5, 0x00000001 }, + { CS42L43B_DECIM_HPF_WNF_CTRL6, 0x00000001 }, { CS42L43_INTP_VOLUME_CTRL1, 0x00000180 }, { CS42L43_INTP_VOLUME_CTRL2, 0x00000180 }, { CS42L43_AMP1_2_VOL_RAMP, 0x00000022 }, @@ -160,10 +160,10 @@ const struct reg_default cs42l43_reg_default[CS42L43_N_DEFAULTS] = { { CS42L43_SWIRE_DP2_CH2_INPUT, 0x00000000 }, { CS42L43_SWIRE_DP3_CH1_INPUT, 0x00000000 }, { CS42L43_SWIRE_DP3_CH2_INPUT, 0x00000000 }, - { CS42L43B_SWIRE_DP3_CH3_INPUT, 0x00000000 }, - { CS42L43B_SWIRE_DP3_CH4_INPUT, 0x00000000 }, { CS42L43_SWIRE_DP4_CH1_INPUT, 0x00000000 }, { CS42L43_SWIRE_DP4_CH2_INPUT, 0x00000000 }, + { CS42L43B_SWIRE_DP3_CH3_INPUT, 0x00000000 }, + { CS42L43B_SWIRE_DP3_CH4_INPUT, 0x00000000 }, { CS42L43B_SWIRE_DP4_CH3_INPUT, 0x00000000 }, { CS42L43B_SWIRE_DP4_CH4_INPUT, 0x00000000 }, { CS42L43_ASRC_INT1_INPUT1, 0x00000000 }, @@ -178,14 +178,10 @@ const struct reg_default cs42l43_reg_default[CS42L43_N_DEFAULTS] = { { CS42L43_ISRC1INT2_INPUT1, 0x00000000 }, { CS42L43_ISRC1DEC1_INPUT1, 0x00000000 }, { CS42L43_ISRC1DEC2_INPUT1, 0x00000000 }, - { CS42L43B_ISRC1DEC3_INPUT1, 0x00000000 }, - { CS42L43B_ISRC1DEC4_INPUT1, 0x00000000 }, { CS42L43_ISRC2INT1_INPUT1, 0x00000000 }, { CS42L43_ISRC2INT2_INPUT1, 0x00000000 }, { CS42L43_ISRC2DEC1_INPUT1, 0x00000000 }, { CS42L43_ISRC2DEC2_INPUT1, 0x00000000 }, - { CS42L43B_ISRC2DEC3_INPUT1, 0x00000000 }, - { CS42L43B_ISRC2DEC4_INPUT1, 0x00000000 }, { CS42L43_EQ1MIX_INPUT1, 0x00800000 }, { CS42L43_EQ1MIX_INPUT2, 0x00800000 }, { CS42L43_EQ1MIX_INPUT3, 0x00800000 }, @@ -212,6 +208,10 @@ const struct reg_default cs42l43_reg_default[CS42L43_N_DEFAULTS] = { { CS42L43_AMP4MIX_INPUT2, 0x00800000 }, { CS42L43_AMP4MIX_INPUT3, 0x00800000 }, { CS42L43_AMP4MIX_INPUT4, 0x00800000 }, + { CS42L43B_ISRC1DEC3_INPUT1, 0x00000000 }, + { CS42L43B_ISRC1DEC4_INPUT1, 0x00000000 }, + { CS42L43B_ISRC2DEC3_INPUT1, 0x00000000 }, + { CS42L43B_ISRC2DEC4_INPUT1, 0x00000000 }, { CS42L43_ASRC_INT_ENABLES, 0x00000100 }, { CS42L43_ASRC_DEC_ENABLES, 0x00000100 }, { CS42L43_PDNCNTL, 0x00000000 }, From f8cca63a0a4f3475199d9ab7f86782bdfeb0744d Mon Sep 17 00:00:00 2001 From: Andreas Kemnade Date: Thu, 6 Aug 2026 13:51:36 +0200 Subject: [PATCH 0546/1328] leds: is31fl319x: Modernize registration Use _ext version to have properties parsed to avoid needing to parse them in the driver itself. More modern properties are recognized and the LEDs can be referenced via phandle. Due to the maximum current mechanics, LEDs are not registered right in the first iteration over the nodes. Signed-off-by: Andreas Kemnade Link: https://patch.msgid.link/20260806-led-modern-v4-1-eed715baab2e@kemnade.info Signed-off-by: Lee Jones --- drivers/leds/leds-is31fl319x.c | 61 +++++++++++++++++----------------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/drivers/leds/leds-is31fl319x.c b/drivers/leds/leds-is31fl319x.c index 80f38dba0fba..83cb769d8cb4 100644 --- a/drivers/leds/leds-is31fl319x.c +++ b/drivers/leds/leds-is31fl319x.c @@ -98,7 +98,7 @@ struct is31fl319x_chip { struct is31fl319x_chip *chip; struct led_classdev cdev; u32 max_microamp; - bool configured; + struct fwnode_handle *fwnode; } leds[IS31FL319X_MAX_LEDS]; }; @@ -363,31 +363,17 @@ static const struct of_device_id of_is31fl319x_match[] = { }; MODULE_DEVICE_TABLE(of, of_is31fl319x_match); -static int is31fl319x_parse_child_fw(const struct device *dev, - const struct fwnode_handle *child, - struct is31fl319x_led *led, - struct is31fl319x_chip *is31) +static void is31_free_fwnode(void *data) { - struct led_classdev *cdev = &led->cdev; - int ret; + struct is31fl319x_chip *is31 = data; + int i; - if (fwnode_property_read_string(child, "label", &cdev->name)) - cdev->name = fwnode_get_name(child); + for (i = 0; i < is31->cdef->num_leds; i++) { + if (is31->leds[i].fwnode) + fwnode_handle_put(is31->leds[i].fwnode); - ret = fwnode_property_read_string(child, "linux,default-trigger", &cdev->default_trigger); - if (ret < 0 && ret != -EINVAL) /* is optional */ - return ret; - - led->max_microamp = is31->cdef->current_default; - ret = fwnode_property_read_u32(child, "led-max-microamp", &led->max_microamp); - if (!ret) { - if (led->max_microamp < is31->cdef->current_min) - return -EINVAL; /* not supported */ - led->max_microamp = min(led->max_microamp, - is31->cdef->current_max); + is31->leds[i].fwnode = NULL; } - - return 0; } static int is31fl319x_parse_fw(struct device *dev, struct is31fl319x_chip *is31) @@ -402,6 +388,12 @@ static int is31fl319x_parse_fw(struct device *dev, struct is31fl319x_chip *is31) "Failed to get shutdown gpio\n"); is31->cdef = device_get_match_data(dev); + if (!is31->cdef) + return -ENODEV; + + ret = devm_add_action_or_reset(dev, is31_free_fwnode, is31); + if (ret) + return ret; count = 0; device_for_each_child_node_scoped(dev, child) @@ -427,14 +419,20 @@ static int is31fl319x_parse_fw(struct device *dev, struct is31fl319x_chip *is31) led = &is31->leds[reg - 1]; - if (led->configured) + if (led->fwnode) return dev_err_probe(dev, -EINVAL, "led %u is already configured\n", reg); - ret = is31fl319x_parse_child_fw(dev, child, led, is31); - if (ret) - return dev_err_probe(dev, ret, "led %u DT parsing failed\n", reg); + led->max_microamp = is31->cdef->current_default; + ret = fwnode_property_read_u32(child, "led-max-microamp", &led->max_microamp); + if (!ret) { + if (led->max_microamp < is31->cdef->current_min) + return dev_err_probe(dev, -EINVAL, "invalid maximum current\n"); - led->configured = true; + led->max_microamp = min(led->max_microamp, + is31->cdef->current_max); + } + + led->fwnode = fwnode_handle_get(child); } is31->audio_gain_db = 0; @@ -531,7 +529,7 @@ static int is31fl319x_probe(struct i2c_client *client) */ aggregated_led_microamp = is31->cdef->current_max; for (i = 0; i < is31->cdef->num_leds; i++) - if (is31->leds[i].configured && + if (is31->leds[i].fwnode && is31->leds[i].max_microamp < aggregated_led_microamp) aggregated_led_microamp = is31->leds[i].max_microamp; @@ -545,14 +543,17 @@ static int is31fl319x_probe(struct i2c_client *client) for (i = 0; i < is31->cdef->num_leds; i++) { struct is31fl319x_led *led = &is31->leds[i]; + struct led_init_data init_data = {}; - if (!led->configured) + if (!led->fwnode) continue; + init_data.fwnode = led->fwnode; + led->chip = is31; led->cdev.brightness_set_blocking = is31->cdef->brightness_set; - err = devm_led_classdev_register(&client->dev, &led->cdev); + err = devm_led_classdev_register_ext(&client->dev, &led->cdev, &init_data); if (err < 0) return err; } From daf120e464386f750b3870dc04d5bc6a786f0000 Mon Sep 17 00:00:00 2001 From: Junjie Cao Date: Tue, 4 Aug 2026 11:02:53 +0800 Subject: [PATCH 0547/1328] backlight: aw99706: Fix DT property names to match binding The driver reads four tuning properties without the unit suffixes that the binding mandates: "awinic,sw-freq" instead of "awinic,sw-freq-hz", "awinic,sw-ilmt" instead of "awinic,sw-ilmt-microamp", "awinic,iled-max" instead of "awinic,iled-max-microamp", and "awinic,uvlo-thres" instead of "awinic,uvlo-thres-microvolt". As a result, device_property_read_u32() never finds these properties in a binding-conformant device tree and silently falls back to the compiled-in defaults for switching frequency, switching current limit, max LED current, and UVLO threshold. Fix by aligning the property name strings in aw99706_dt_props[] with the binding. No value/range changes are needed since both sides already use the same units and enumerations. Fixes: 147b38a5ad06 ("backlight: aw99706: Add support for Awinic AW99706 backlight") Cc: stable@vger.kernel.org Signed-off-by: Junjie Cao Reviewed-by: Daniel Thompson (RISCstar) Link: https://patch.msgid.link/20260804030255.1934470-2-junjie.cao@intel.com Signed-off-by: Lee Jones --- drivers/video/backlight/aw99706.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/video/backlight/aw99706.c b/drivers/video/backlight/aw99706.c index 18299faf06ad..e130f164303a 100644 --- a/drivers/video/backlight/aw99706.c +++ b/drivers/video/backlight/aw99706.c @@ -130,23 +130,23 @@ static const struct aw99706_dt_prop aw99706_dt_props[] = { AW99706_CFG0_REG, AW99706_DIM_MODE_MASK, 1, }, { - "awinic,sw-freq", aw99706_dt_property_lookup, + "awinic,sw-freq-hz", aw99706_dt_property_lookup, aw99706_sw_freq_tbl, ARRAY_SIZE(aw99706_sw_freq_tbl), AW99706_CFG1_REG, AW99706_SW_FREQ_MASK, 750000, }, { - "awinic,sw-ilmt", aw99706_dt_property_lookup, + "awinic,sw-ilmt-microamp", aw99706_dt_property_lookup, aw99706_sw_ilmt_tbl, ARRAY_SIZE(aw99706_sw_ilmt_tbl), AW99706_CFG1_REG, AW99706_SW_ILMT_MASK, 3000000, }, { - "awinic,iled-max", aw99706_dt_property_iled_max_convert, + "awinic,iled-max-microamp", aw99706_dt_property_iled_max_convert, NULL, 0, AW99706_CFG2_REG, AW99706_ILED_MAX_MASK, 20000, }, { - "awinic,uvlo-thres", aw99706_dt_property_lookup, + "awinic,uvlo-thres-microvolt", aw99706_dt_property_lookup, aw99706_ulvo_thres_tbl, ARRAY_SIZE(aw99706_ulvo_thres_tbl), AW99706_CFG2_REG, AW99706_UVLOSEL_MASK, 2200000, }, From 3d142b19b09ea62d6e9a33d46d1b858b3ad05707 Mon Sep 17 00:00:00 2001 From: Junjie Cao Date: Tue, 4 Aug 2026 11:02:54 +0800 Subject: [PATCH 0548/1328] backlight: aw99706: Validate all DT property values consistently The lookup helpers for dim-mode and ramp-ctl take a shortcut when lookup_tbl is NULL: they accept any u32 value without range-checking and return success unconditionally. Out-of-range values get silently truncated by regmap_update_bits instead of triggering the dev_warn + default-fallback path that the other properties use. Add a field-width check for the NULL-table case so that values exceeding the register field maximum are rejected the same way a table-lookup miss is. The switching frequency table has a second hole: reserved slots use 0 as their marker, so "awinic,sw-freq-hz = <0>" matches slot 0 and programs a reserved encoding. Make the reserved marker U32_MAX and skip such slots during lookup. While here, also switch the error returns to -EINVAL for consistency. Fixes: 147b38a5ad06 ("backlight: aw99706: Add support for Awinic AW99706 backlight") Signed-off-by: Junjie Cao Reviewed-by: Daniel Thompson (RISCstar) Link: https://patch.msgid.link/20260804030255.1934470-3-junjie.cao@intel.com Signed-off-by: Lee Jones --- drivers/video/backlight/aw99706.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/video/backlight/aw99706.c b/drivers/video/backlight/aw99706.c index e130f164303a..6ec49b6cb14c 100644 --- a/drivers/video/backlight/aw99706.c +++ b/drivers/video/backlight/aw99706.c @@ -60,7 +60,7 @@ #define AW99706_MTPLDOSEL_REG 0x1E #define AW99706_MTPRUN_REG 0x1F -#define RESV 0 +#define RESV U32_MAX /* Boost switching frequency table, in Hz */ static const u32 aw99706_sw_freq_tbl[] = { @@ -94,17 +94,19 @@ static int aw99706_dt_property_lookup(const struct aw99706_dt_prop *prop, int i; if (!prop->lookup_tbl) { + if (dt_val > (prop->mask >> __ffs(prop->mask))) + return -EINVAL; *val = dt_val; return 0; } for (i = 0; i < prop->tbl_size; i++) - if (prop->lookup_tbl[i] == dt_val) + if (prop->lookup_tbl[i] != RESV && prop->lookup_tbl[i] == dt_val) break; *val = i; - return i == prop->tbl_size ? -1 : 0; + return i == prop->tbl_size ? -EINVAL : 0; } #define MIN_ILED_MAX 5000 @@ -116,11 +118,14 @@ aw99706_dt_property_iled_max_convert(const struct aw99706_dt_prop *prop, u32 dt_val, u8 *val) { if (dt_val > MAX_ILED_MAX || dt_val < MIN_ILED_MAX) - return -1; + return -EINVAL; + + if ((dt_val - MIN_ILED_MAX) % STEP_ILED_MAX) + return -EINVAL; *val = (dt_val - MIN_ILED_MAX) / STEP_ILED_MAX; - return (dt_val - MIN_ILED_MAX) % STEP_ILED_MAX; + return 0; } static const struct aw99706_dt_prop aw99706_dt_props[] = { From fbf8b5d251e8b4e1b01c591be313c63cb9e1526a Mon Sep 17 00:00:00 2001 From: Junjie Cao Date: Tue, 4 Aug 2026 11:02:55 +0800 Subject: [PATCH 0549/1328] backlight: aw99706: Honor the core blank state in update_status() update_status() passes props.brightness straight to the hardware and ignores the power/blank state tracked by the core. Writing 4 to the bl_power sysfs attribute or blanking the framebuffer therefore leaves the backlight lit. Use backlight_get_brightness(), which returns 0 while the device is blanked. Fixes: 147b38a5ad06 ("backlight: aw99706: Add support for Awinic AW99706 backlight") Cc: stable@vger.kernel.org Signed-off-by: Junjie Cao Reviewed-by: Daniel Thompson (RISCstar) Link: https://patch.msgid.link/20260804030255.1934470-4-junjie.cao@intel.com Signed-off-by: Lee Jones --- drivers/video/backlight/aw99706.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/video/backlight/aw99706.c b/drivers/video/backlight/aw99706.c index 6ec49b6cb14c..c4a66b59ecf1 100644 --- a/drivers/video/backlight/aw99706.c +++ b/drivers/video/backlight/aw99706.c @@ -340,7 +340,7 @@ static int aw99706_bl_update_status(struct backlight_device *bl) { struct aw99706_device *aw = bl_get_data(bl); - return aw99706_update_brightness(aw, bl->props.brightness); + return aw99706_update_brightness(aw, backlight_get_brightness(bl)); } static const struct backlight_ops aw99706_bl_ops = { From 5859fa79c7d85d0c242a9a1ffb40bab9732d4ef1 Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Sun, 19 Jul 2026 03:49:02 +0100 Subject: [PATCH 0550/1328] backlight: ktd2801: Fix unmet dependency on GPIOLIB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LEDS_EXPRESSWIRE depends on GPIOLIB, so its selector, BACKLIGHT_KTD2801 also needs to ensure GPIOLIB is enabled. Otherwise: WARNING: unmet direct dependencies detected for LEDS_EXPRESSWIRE Depends on [n]: NEW_LEDS [=n] && GPIOLIB [=n] Selected by [y]: - BACKLIGHT_KTD2801 [=y] && HAS_IOMEM [=y] && BACKLIGHT_CLASS_DEVICE [=y] This unmet dependency was found by kconfirm, a static analysis tool for Kconfig. Fixes: d95963e309bc ("backlight: ktd2801: Depend on GPIOLIB") Signed-off-by: Julian Braha Reviewed-by: Daniel Thompson (RISCstar) Acked-by: Duje Mihanović Tested-by: Randy Dunlap Acked-by: Randy Dunlap Link: https://patch.msgid.link/20260719024902.151710-1-julianbraha@gmail.com Signed-off-by: Lee Jones --- drivers/video/backlight/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig index 7c66b8840d88..dbf4ca23a9b6 100644 --- a/drivers/video/backlight/Kconfig +++ b/drivers/video/backlight/Kconfig @@ -193,7 +193,7 @@ config BACKLIGHT_KTD253 config BACKLIGHT_KTD2801 tristate "Backlight Driver for Kinetic KTD2801" - depends on GPIOLIB || COMPILE_TEST + depends on GPIOLIB select LEDS_EXPRESSWIRE help Say Y to enable the backlight driver for the Kinetic KTD2801 1-wire From 0bf739c3cd22b17af7c31c3d8d0777e1e6574429 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Wed, 22 Jul 2026 14:57:08 +0800 Subject: [PATCH 0551/1328] backlight: qcom-wled: Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_threaded_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() calls. Signed-off-by: Pan Chuang Reviewed-by: Daniel Thompson (RISCstar) Link: https://patch.msgid.link/20260722065709.346072-1-panchuang@vivo.com Signed-off-by: Lee Jones --- drivers/video/backlight/qcom-wled.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/video/backlight/qcom-wled.c b/drivers/video/backlight/qcom-wled.c index 8054e4787725..650dd95f06ef 100644 --- a/drivers/video/backlight/qcom-wled.c +++ b/drivers/video/backlight/qcom-wled.c @@ -1639,8 +1639,6 @@ static int wled_configure_ovp_irq(struct wled *wled, wled_ovp_irq_handler, IRQF_ONESHOT, "wled_ovp_irq", wled); if (rc < 0) { - dev_err(wled->dev, "Unable to request ovp_irq (err:%d)\n", - rc); wled->ovp_irq = 0; return 0; } From cf1a12e0804515e0ed8b3f50c1c32f7f425bd660 Mon Sep 17 00:00:00 2001 From: Levente Szajko Date: Wed, 5 Aug 2026 13:51:39 +0000 Subject: [PATCH 0552/1328] backlight: Use sysfs_emit() instead of sprintf() Replace sprintf() with sysfs_emit() in the sysfs show callbacks of backlight.c and lcd.c, as recommended by Documentation/filesystems/sysfs.rst. No functional change intended. Signed-off-by: Levente Szajko Reviewed-by: Daniel Thompson (RISCstar) Link: https://patch.msgid.link/20260805135027.43890-1-raedrimhun@proton.me Signed-off-by: Lee Jones --- drivers/video/backlight/backlight.c | 16 ++++++++-------- drivers/video/backlight/lcd.c | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/drivers/video/backlight/backlight.c b/drivers/video/backlight/backlight.c index a22d0bbb6e63..fc9bb303e8c5 100644 --- a/drivers/video/backlight/backlight.c +++ b/drivers/video/backlight/backlight.c @@ -139,7 +139,7 @@ static ssize_t bl_power_show(struct device *dev, struct device_attribute *attr, { struct backlight_device *bd = to_backlight_device(dev); - return sprintf(buf, "%d\n", bd->props.power); + return sysfs_emit(buf, "%d\n", bd->props.power); } static ssize_t bl_power_store(struct device *dev, struct device_attribute *attr, @@ -180,7 +180,7 @@ static ssize_t brightness_show(struct device *dev, { struct backlight_device *bd = to_backlight_device(dev); - return sprintf(buf, "%d\n", bd->props.brightness); + return sysfs_emit(buf, "%d\n", bd->props.brightness); } int backlight_device_set_brightness(struct backlight_device *bd, @@ -228,7 +228,7 @@ static ssize_t type_show(struct device *dev, struct device_attribute *attr, { struct backlight_device *bd = to_backlight_device(dev); - return sprintf(buf, "%s\n", backlight_types[bd->props.type]); + return sysfs_emit(buf, "%s\n", backlight_types[bd->props.type]); } static DEVICE_ATTR_RO(type); @@ -237,7 +237,7 @@ static ssize_t max_brightness_show(struct device *dev, { struct backlight_device *bd = to_backlight_device(dev); - return sprintf(buf, "%d\n", bd->props.max_brightness); + return sysfs_emit(buf, "%d\n", bd->props.max_brightness); } static DEVICE_ATTR_RO(max_brightness); @@ -251,9 +251,9 @@ static ssize_t actual_brightness_show(struct device *dev, if (bd->ops && bd->ops->get_brightness) { rc = bd->ops->get_brightness(bd); if (rc >= 0) - rc = sprintf(buf, "%d\n", rc); + rc = sysfs_emit(buf, "%d\n", rc); } else { - rc = sprintf(buf, "%d\n", bd->props.brightness); + rc = sysfs_emit(buf, "%d\n", bd->props.brightness); } mutex_unlock(&bd->ops_lock); @@ -267,9 +267,9 @@ static ssize_t scale_show(struct device *dev, struct backlight_device *bd = to_backlight_device(dev); if (WARN_ON(bd->props.scale > BACKLIGHT_SCALE_NON_LINEAR)) - return sprintf(buf, "unknown\n"); + return sysfs_emit(buf, "unknown\n"); - return sprintf(buf, "%s\n", backlight_scale_types[bd->props.scale]); + return sysfs_emit(buf, "%s\n", backlight_scale_types[bd->props.scale]); } static DEVICE_ATTR_RO(scale); diff --git a/drivers/video/backlight/lcd.c b/drivers/video/backlight/lcd.c index e918f57608ef..461e393837d3 100644 --- a/drivers/video/backlight/lcd.c +++ b/drivers/video/backlight/lcd.c @@ -77,7 +77,7 @@ static ssize_t lcd_power_show(struct device *dev, struct device_attribute *attr, mutex_lock(&ld->ops_lock); if (ld->ops && ld->ops->get_power) - rc = sprintf(buf, "%d\n", ld->ops->get_power(ld)); + rc = sysfs_emit(buf, "%d\n", ld->ops->get_power(ld)); else rc = -ENXIO; mutex_unlock(&ld->ops_lock); @@ -118,7 +118,7 @@ static ssize_t contrast_show(struct device *dev, mutex_lock(&ld->ops_lock); if (ld->ops && ld->ops->get_contrast) - rc = sprintf(buf, "%d\n", ld->ops->get_contrast(ld)); + rc = sysfs_emit(buf, "%d\n", ld->ops->get_contrast(ld)); mutex_unlock(&ld->ops_lock); return rc; @@ -154,7 +154,7 @@ static ssize_t max_contrast_show(struct device *dev, { struct lcd_device *ld = to_lcd_device(dev); - return sprintf(buf, "%d\n", ld->props.max_contrast); + return sysfs_emit(buf, "%d\n", ld->props.max_contrast); } static DEVICE_ATTR_RO(max_contrast); From b2205d3cfd6c76fd5c5443ee9fdb498cfb0e1c66 Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Tue, 11 Aug 2026 15:16:36 +0800 Subject: [PATCH 0553/1328] f2fs: fix to off-by-one issue in f2fs_zero_post_eof_page() Otherwise, it will drop one more page after new_size which is not necessary. Cc: stable@kernel.org Fixes: ba8dac350faf ("f2fs: fix to zero post-eof page") Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/file.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index 56529a82e027..9dbe509926b2 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -50,7 +50,7 @@ static void f2fs_zero_post_eof_page(struct inode *inode, if (lock) filemap_invalidate_lock(inode->i_mapping); /* zero or drop pages only in range of [old_size, new_size] */ - truncate_inode_pages_range(inode->i_mapping, old_size, new_size); + truncate_inode_pages_range(inode->i_mapping, old_size, new_size - 1); if (lock) filemap_invalidate_unlock(inode->i_mapping); } From 5eced87b7d19dbc76ebdddaf322046f9ac582fcb Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Tue, 11 Aug 2026 15:16:37 +0800 Subject: [PATCH 0554/1328] f2fs: fix to zero post-EOF data when extending file size generic/794 4s ... - output mismatch (see /share/git/fstests/results//generic/794.out.bad) --- tests/generic/794.out 2026-06-12 08:46:32.766426241 +0800 +++ /share/git/fstests/results//generic/794.out.bad 2026-07-05 18:32:55.000000000 +0800 @@ -1,4 +1,16 @@ QA output created by 794 append_write +FAIL: non-zero data in gap [4080,4096) after shutdown+remount +000000 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a >ZZZZZZZZZZZZZZZZ< +* +001000 truncate_up ... (Run 'diff -u /share/git/fstests/tests/generic/794.out /share/git/fstests/results//generic/794.out.bad' to see the entire diff) Ran: generic/794 Failures: generic/794 Failed 1 of 1 tests Steps of generic/794: 1. write 4096 bytes to file w/ 0x5a 2. use fiemap to get PBA of first block in file 3. truncate file to 4080 4. umount; write 4096 bytes to file w/ 0x5a directly via PBA; mount 5. extend filesize via a) append 4096 from offset 4096, or b) truncate 8192, or c) fallocate 4096 from offset 4096 6. verify the gap is zeroed in memory [4080,4096) 7. sync range 4096 from offset 4096; shutdown -f (flush meta before shutdown) 8. umount; mount; verify [4080,4096) is zeroed or not. When extending file size (e.g. via truncate, fallocate, or write) across an unaligned EOF boundary, we need to ensure that post-EOF data in the partial page is zeroed out in pagecache and marked dirty, then writeback the cache to persist zeroed data before committing inode w/ updated i_size. This help to prevent stale disk data beyond the previous EOF from being exposed after remounting or crash recovery. Since f2fs is a LFS filesystem, we only support direct write via PBA in pinfile, and pinfile has section-aligned filesize, so in Android, there should no problem, but for other usage in different environment, let's fix this w/ fsync_mode=strict mount option. Cc: stable@kernel.org Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/file.c | 100 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 84 insertions(+), 16 deletions(-) diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index 9dbe509926b2..76316e537f65 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -36,16 +36,52 @@ #include #include -static void f2fs_zero_post_eof_page(struct inode *inode, - loff_t new_size, bool lock) +static int fill_zero(struct inode *inode, pgoff_t index, + loff_t start, loff_t len); + +static int do_zero_post_eof_page(struct inode *inode, loff_t new_size) { loff_t old_size = i_size_read(inode); + unsigned int offset, len; + pgoff_t index; + int err; + + offset = old_size & (PAGE_SIZE - 1); + + if (!offset) + return 0; + + len = min_t(loff_t, PAGE_SIZE - offset, new_size - old_size); + index = old_size >> PAGE_SHIFT; + + if (f2fs_has_inline_data(inode)) { + /* data post eof should be always zero */ + if (new_size <= MAX_INLINE_DATA(inode)) + return 0; + err = f2fs_convert_inline_inode(inode); + if (err) + return err; + } + + err = fill_zero(inode, index, offset, len); + if (err) + return err; + return filemap_write_and_wait_range(inode->i_mapping, + old_size, old_size + len - 1); +} + +static int f2fs_zero_post_eof_page(struct inode *inode, + loff_t new_size, bool lock, bool writeback) +{ + loff_t old_size = i_size_read(inode); + bool strict = + F2FS_OPTION(F2FS_I_SB(inode)).fsync_mode == FSYNC_MODE_STRICT; if (old_size >= new_size) - return; + return 0; - if (mapping_empty(inode->i_mapping)) - return; + if (!strict && mapping_empty(inode->i_mapping)) + return 0; if (lock) filemap_invalidate_lock(inode->i_mapping); @@ -53,6 +89,16 @@ static void f2fs_zero_post_eof_page(struct inode *inode, truncate_inode_pages_range(inode->i_mapping, old_size, new_size - 1); if (lock) filemap_invalidate_unlock(inode->i_mapping); + + if (!writeback || !strict) + return 0; + /* + * In fsync_mode=strict, when we expand an unaligned EOF size, we + * should zero post EOF data and writeback the data immediately, + * so that it can avoid exposing stale data after metadata flush + * and POR. + */ + return do_zero_post_eof_page(inode, new_size); } static vm_fault_t f2fs_filemap_fault(struct vm_fault *vmf) @@ -132,7 +178,10 @@ static vm_fault_t f2fs_vm_page_mkwrite(struct vm_fault *vmf) f2fs_bug_on(sbi, f2fs_has_inline_data(inode)); - f2fs_zero_post_eof_page(inode, (folio->index + 1) << PAGE_SHIFT, true); + err = f2fs_zero_post_eof_page(inode, + (folio->index + 1) << PAGE_SHIFT, true, false); + if (err) + goto out_pagefault; file_update_time(vmf->vma->vm_file); filemap_invalidate_lock_shared(inode->i_mapping); @@ -189,7 +238,7 @@ static vm_fault_t f2fs_vm_page_mkwrite(struct vm_fault *vmf) out_sem: filemap_invalidate_unlock_shared(inode->i_mapping); - +out_pagefault: sb_end_pagefault(inode->i_sb); out: ret = vmf_fs_error(err); @@ -1182,8 +1231,12 @@ int f2fs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, f2fs_down_write(&fi->i_gc_rwsem[WRITE]); filemap_invalidate_lock(inode->i_mapping); - if (attr->ia_size > old_size) - f2fs_zero_post_eof_page(inode, attr->ia_size, false); + if (attr->ia_size > old_size) { + err = f2fs_zero_post_eof_page(inode, + attr->ia_size, false, true); + if (err) + goto err_out; + } truncate_setsize(inode, attr->ia_size); if (attr->ia_size <= old_size) @@ -1192,6 +1245,7 @@ int f2fs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, * do not trim all blocks after i_size if target size is * larger than i_size. */ +err_out: filemap_invalidate_unlock(inode->i_mapping); f2fs_up_write(&fi->i_gc_rwsem[WRITE]); if (err) @@ -1303,7 +1357,9 @@ static int f2fs_punch_hole(struct inode *inode, loff_t offset, loff_t len) if (ret) return ret; - f2fs_zero_post_eof_page(inode, offset + len, true); + ret = f2fs_zero_post_eof_page(inode, offset + len, true, false); + if (ret) + return ret; pg_start = ((unsigned long long) offset) >> PAGE_SHIFT; pg_end = ((unsigned long long) offset + len) >> PAGE_SHIFT; @@ -1590,7 +1646,9 @@ static int f2fs_do_collapse(struct inode *inode, loff_t offset, loff_t len) f2fs_down_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]); filemap_invalidate_lock(inode->i_mapping); - f2fs_zero_post_eof_page(inode, offset + len, false); + ret = f2fs_zero_post_eof_page(inode, offset + len, false, false); + if (ret) + goto out_unlock; f2fs_lock_op(sbi, &lc); f2fs_drop_extent_tree(inode); @@ -1598,6 +1656,7 @@ static int f2fs_do_collapse(struct inode *inode, loff_t offset, loff_t len) ret = __exchange_data_block(inode, inode, end, start, nrpages - end, true); f2fs_unlock_op(sbi, &lc); +out_unlock: filemap_invalidate_unlock(inode->i_mapping); f2fs_up_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]); return ret; @@ -1719,7 +1778,9 @@ static int f2fs_zero_range(struct inode *inode, loff_t offset, loff_t len, if (ret) return ret; - f2fs_zero_post_eof_page(inode, offset + len, true); + ret = f2fs_zero_post_eof_page(inode, offset + len, true, false); + if (ret) + return ret; pg_start = ((unsigned long long) offset) >> PAGE_SHIFT; pg_end = ((unsigned long long) offset + len) >> PAGE_SHIFT; @@ -1854,7 +1915,9 @@ static int f2fs_insert_range(struct inode *inode, loff_t offset, loff_t len) f2fs_down_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]); filemap_invalidate_lock(mapping); - f2fs_zero_post_eof_page(inode, offset + len, false); + ret = f2fs_zero_post_eof_page(inode, offset + len, false, false); + if (ret) + goto out_unlock; truncate_pagecache(inode, offset); while (!ret && idx > pg_start) { @@ -1872,6 +1935,7 @@ static int f2fs_insert_range(struct inode *inode, loff_t offset, loff_t len) idx + delta, nr, false); f2fs_unlock_op(sbi, &lc); } +out_unlock: filemap_invalidate_unlock(mapping); f2fs_up_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]); if (ret) @@ -1914,7 +1978,9 @@ static int f2fs_expand_inode_data(struct inode *inode, loff_t offset, if (err) return err; - f2fs_zero_post_eof_page(inode, offset + len, true); + err = f2fs_zero_post_eof_page(inode, offset + len, true, true); + if (err) + return err; f2fs_balance_fs(sbi, true); @@ -5285,8 +5351,10 @@ static ssize_t f2fs_write_checks(struct kiocb *iocb, struct iov_iter *from) if (err) return err; - f2fs_zero_post_eof_page(inode, - iocb->ki_pos + iov_iter_count(from), true); + err = f2fs_zero_post_eof_page(inode, + iocb->ki_pos + iov_iter_count(from), true, true); + if (err) + return err; return count; } From d3915a0a356e58b52ed05f4f22a90b02b663688a Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Sun, 9 Aug 2026 09:26:38 +0800 Subject: [PATCH 0555/1328] f2fs: unify add/remove ino entry API for all ino types - Call f2fs_add_ino_entry() and f2fs_remove_ino_entry() for ORPHAN_INO - introduce __f2fs_add_ino_entry() to wrap __add_ino_entry(), so that both f2fs_add_ino_entry() and f2fs_set_dirty_device() will call __f2fs_add_ino_entry(). So, after this change: add delete lookup ORPHAN_INO f2fs_add_ino_entry f2fs_remove_ino_entry N/A FLUSH_INO f2fs_set_dirty_device f2fs_remove_ino_entry f2fs_is_dirty_device APPEND_INO f2fs_add_ino_entry f2fs_remove_ino_entry f2fs_exist_written_data UPDATA_INO f2fs_add_ino_entry f2fs_remove_ino_entry f2fs_exist_written_data TRANS_DIR_INO f2fs_add_ino_entry N/A f2fs_exist_written_data XATTR_DIR_INO f2fs_add_ino_entry N/A f2fs_exist_written_data Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/checkpoint.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/fs/f2fs/checkpoint.c b/fs/f2fs/checkpoint.c index 4413eccb5ecb..eb2f955b1e2a 100644 --- a/fs/f2fs/checkpoint.c +++ b/fs/f2fs/checkpoint.c @@ -775,10 +775,16 @@ static void f2fs_wait_for_inode_record(struct f2fs_sb_info *sbi, int mode) flush_workqueue(sbi->evict_wq); } -void f2fs_add_ino_entry(struct f2fs_sb_info *sbi, nid_t ino, int type) +static void __f2fs_add_ino_entry(struct f2fs_sb_info *sbi, nid_t ino, + unsigned int devidx, int type) { /* add new dirty ino entry into list */ - __add_ino_entry(sbi, ino, 0, type); + __add_ino_entry(sbi, ino, devidx, type); +} + +void f2fs_add_ino_entry(struct f2fs_sb_info *sbi, nid_t ino, int type) +{ + __f2fs_add_ino_entry(sbi, ino, 0, type); } void f2fs_remove_ino_entry(struct f2fs_sb_info *sbi, nid_t ino, int type) @@ -823,7 +829,7 @@ void f2fs_release_ino_entry(struct f2fs_sb_info *sbi, bool all) void f2fs_set_dirty_device(struct f2fs_sb_info *sbi, nid_t ino, unsigned int devidx, int type) { - __add_ino_entry(sbi, ino, devidx, type); + __f2fs_add_ino_entry(sbi, ino, devidx, type); } bool f2fs_is_dirty_device(struct f2fs_sb_info *sbi, nid_t ino, @@ -875,14 +881,14 @@ void f2fs_release_orphan_inode(struct f2fs_sb_info *sbi) void f2fs_add_orphan_inode(struct inode *inode) { /* add new orphan ino entry into list */ - __add_ino_entry(F2FS_I_SB(inode), inode->i_ino, 0, ORPHAN_INO); + f2fs_add_ino_entry(F2FS_I_SB(inode), inode->i_ino, ORPHAN_INO); f2fs_update_inode_page(inode); } void f2fs_remove_orphan_inode(struct f2fs_sb_info *sbi, nid_t ino) { /* remove orphan entry from orphan list */ - __remove_ino_entry(sbi, ino, ORPHAN_INO); + f2fs_remove_ino_entry(sbi, ino, ORPHAN_INO); } static int recover_orphan_inode(struct f2fs_sb_info *sbi, nid_t ino) From b6c46ab0bdee90c238e96ea4a74972118c97900d Mon Sep 17 00:00:00 2001 From: Yuho Choi Date: Sun, 2 Aug 2026 21:55:50 -0400 Subject: [PATCH 0556/1328] ipmi: Remove all sysfs files on registration failure ipmi_add_smi() creates the nr_users and nr_msgs files before trying to create the maintenance_mode file. If that last creation fails, the error path removes only nr_users before dropping the final reference to the interface. Remove nr_msgs as well so no sysfs attribute embedded in the freed interface remains registered. Fixes: 627118470fcc ("ipmi: Add a maintenance mode sysfs file") Cc: stable@vger.kernel.org # 6.18 Signed-off-by: Yuho Choi Message-ID: <20260803015550.618808-1-dbgh9129@gmail.com> Signed-off-by: Corey Minyard --- drivers/char/ipmi/ipmi_msghandler.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/char/ipmi/ipmi_msghandler.c b/drivers/char/ipmi/ipmi_msghandler.c index 8d9f2e647d9b..6ff9a15cced8 100644 --- a/drivers/char/ipmi/ipmi_msghandler.c +++ b/drivers/char/ipmi/ipmi_msghandler.c @@ -3740,6 +3740,7 @@ int ipmi_add_smi(struct module *owner, sysfs_attr_init(&intf->maintenance_mode_devattr.attr); rv = device_create_file(intf->si_dev, &intf->maintenance_mode_devattr); if (rv) { + device_remove_file(intf->si_dev, &intf->nr_msgs_devattr); device_remove_file(intf->si_dev, &intf->nr_users_devattr); goto out_err_bmc_reg; } From 18e633dd9674c8bf118ea8583bb5a531edc2728a Mon Sep 17 00:00:00 2001 From: Michail Tatas Date: Fri, 7 Aug 2026 01:30:20 +0300 Subject: [PATCH 0557/1328] ipmi: Fix leak in __ipmi_bmc_register In case that ida_alloc(&ipmi_bmc_ida,...) succeeds and then platform_device_register() fails, ipmi_bmc_ida is leaked. Fix by freeing the error path Signed-off-by: Michail Tatas Message-ID: Signed-off-by: Corey Minyard --- drivers/char/ipmi/ipmi_msghandler.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/char/ipmi/ipmi_msghandler.c b/drivers/char/ipmi/ipmi_msghandler.c index 6ff9a15cced8..7634dff99f41 100644 --- a/drivers/char/ipmi/ipmi_msghandler.c +++ b/drivers/char/ipmi/ipmi_msghandler.c @@ -3301,6 +3301,7 @@ static int __ipmi_bmc_register(struct ipmi_smi *intf, list_del(&intf->bmc_link); mutex_unlock(&bmc->dyn_mutex); intf->bmc = &intf->tmp_bmc; + ida_free(&ipmi_bmc_ida, bmc->pdev.id); put_device(&bmc->pdev.dev); goto out; } From eee1ea58c81ecb4d05c7267c05da73544386f3d9 Mon Sep 17 00:00:00 2001 From: "Pawel Zalewski (The Capable Hub)" Date: Fri, 7 Aug 2026 12:26:10 +0100 Subject: [PATCH 0558/1328] char: ipmi: 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 we are 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) Message-ID: <20260807-acpi-char-v1-3-742c450254dd@thegoodpenguin.co.uk> Signed-off-by: Corey Minyard --- drivers/char/ipmi/ipmb_dev_int.c | 4 ++-- drivers/char/ipmi/ipmi_si_platform.c | 4 ++-- drivers/char/ipmi/ipmi_ssif.c | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/char/ipmi/ipmb_dev_int.c b/drivers/char/ipmi/ipmb_dev_int.c index e4c50d9ae3e1..eae31ec92382 100644 --- a/drivers/char/ipmi/ipmb_dev_int.c +++ b/drivers/char/ipmi/ipmb_dev_int.c @@ -361,8 +361,8 @@ MODULE_DEVICE_TABLE(i2c, ipmb_id); #ifdef CONFIG_ACPI static const struct acpi_device_id acpi_ipmb_id[] = { - { "IPMB0001", 0 }, - {}, + { .id = "IPMB0001" }, + { } }; MODULE_DEVICE_TABLE(acpi, acpi_ipmb_id); #endif diff --git a/drivers/char/ipmi/ipmi_si_platform.c b/drivers/char/ipmi/ipmi_si_platform.c index bdc481ce1302..fa221cbb4b3b 100644 --- a/drivers/char/ipmi/ipmi_si_platform.c +++ b/drivers/char/ipmi/ipmi_si_platform.c @@ -387,8 +387,8 @@ static int acpi_ipmi_probe(struct platform_device *pdev) } static const struct acpi_device_id acpi_ipmi_match[] = { - { "IPI0001", 0 }, - { }, + { .id = "IPI0001" }, + { } }; MODULE_DEVICE_TABLE(acpi, acpi_ipmi_match); #else diff --git a/drivers/char/ipmi/ipmi_ssif.c b/drivers/char/ipmi/ipmi_ssif.c index 07f1d2327bb7..2361103c5edc 100644 --- a/drivers/char/ipmi/ipmi_ssif.c +++ b/drivers/char/ipmi/ipmi_ssif.c @@ -2057,8 +2057,8 @@ static unsigned short *ssif_address_list(void) #ifdef CONFIG_ACPI static const struct acpi_device_id ssif_acpi_match[] = { - { "IPI0001", 0 }, - { }, + { .id = "IPI0001" }, + { } }; MODULE_DEVICE_TABLE(acpi, ssif_acpi_match); #endif From 431e735e9bb6b7fd8e2ea9d83f0627bb5c5687ee Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Tue, 11 Aug 2026 14:39:51 -0700 Subject: [PATCH 0559/1328] scsi: qla2xxx: Fix size_t format specifier in qla29xx_process_rd_image() After commit c3930ec119cb ("scsi: qla2xxx: Add FC operational firmware load for 29xx"), there is a warning due to an incorrect format specifier for a 'size_t' variable when building for 32-bit platforms, for which 'size_t' is 'unsigned int': drivers/scsi/qla2xxx/qla_init.c: In function 'qla29xx_process_rd_image': drivers/scsi/qla2xxx/qla_init.c:9272:74: error: format '%lx' expects argument of type 'long unsigned int', but argument 6 has type 'size_t' {aka 'unsigned int'} [-Werror=format=] 9272 | "TIM section too large (0x%x bytes, ring 0x%lx bytes).\n", | ~~^ | | | long unsigned int | %x 9273 | section_size, 9274 | req->length * qla_req_entry_size(ha)); | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | | | size_t {aka unsigned int} cc1: all warnings being treated as errors Use '%zx', the proper 'size_t' format specifier, to clear up the warning. Fixes: c3930ec119cb ("scsi: qla2xxx: Add FC operational firmware load for 29xx") Signed-off-by: Nathan Chancellor Reviewed-by: Bart Van Assche Link: https://patch.msgid.link/20260811-scsi-qla2xxxx-qla_init-wformat-v1-1-50760021914f@kernel.org Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/qla2xxx/qla_init.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/scsi/qla2xxx/qla_init.c b/drivers/scsi/qla2xxx/qla_init.c index 2b9a9c672ec6..900cd141928e 100644 --- a/drivers/scsi/qla2xxx/qla_init.c +++ b/drivers/scsi/qla2xxx/qla_init.c @@ -9269,7 +9269,7 @@ static int qla29xx_process_rd_image(struct scsi_qla_host *vha, if (section == TIM) { if (section_size > req->length * qla_req_entry_size(ha)) { ql_log(ql_log_fatal, vha, 0x0098, - "TIM section too large (0x%x bytes, ring 0x%lx bytes).\n", + "TIM section too large (0x%x bytes, ring 0x%zx bytes).\n", section_size, req->length * qla_req_entry_size(ha)); return QLA_FUNCTION_FAILED; From b9f679dfe629004b593f018df33b330d799bcee4 Mon Sep 17 00:00:00 2001 From: Chandrakanth Patil Date: Sat, 8 Aug 2026 20:40:10 +0530 Subject: [PATCH 0560/1328] scsi: mpt3sas: Avoid freeing unallocated PCIe SGL buffers _base_release_memory_pools() unconditionally frees every ioc->pcie_sg_lookup[] entry, including ones the setup loop never allocated after a partial failure, causing a "bad dma" warning on debug kernels or a NULL pointer dereference otherwise. Fixes: dbec4c9040ed ("scsi: mpt3sas: lockless command submission") Reported-by: Laurence Oberman Signed-off-by: Chandrakanth Patil Link: https://patch.msgid.link/20260808151010.185603-1-chandrakanth.patil@broadcom.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/mpt3sas/mpt3sas_base.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/scsi/mpt3sas/mpt3sas_base.c b/drivers/scsi/mpt3sas/mpt3sas_base.c index 791a3c5fbf44..fed7aeffec58 100644 --- a/drivers/scsi/mpt3sas/mpt3sas_base.c +++ b/drivers/scsi/mpt3sas/mpt3sas_base.c @@ -5870,6 +5870,8 @@ _base_release_memory_pools(struct MPT3SAS_ADAPTER *ioc) if (ioc->pcie_sgl_dma_pool) { for (i = 0; i < ioc->scsiio_depth; i++) { + if (!ioc->pcie_sg_lookup[i].pcie_sgl) + continue; dma_pool_free(ioc->pcie_sgl_dma_pool, ioc->pcie_sg_lookup[i].pcie_sgl, ioc->pcie_sg_lookup[i].pcie_sgl_dma); From 4c2128c1a399318859d9eea0b74613d90f4bcb66 Mon Sep 17 00:00:00 2001 From: Xingui Yang Date: Tue, 11 Aug 2026 12:03:33 +0800 Subject: [PATCH 0561/1328] scsi: libsas: Add sas_ex_phy_to_dev() helper Add sas_ex_phy_to_dev() to return any device type attached to an expander phy, and refactor sas_ex_to_ata() to use it. No functional changes intended. Signed-off-by: Xingui Yang Reviewed-by: John Garry Link: https://patch.msgid.link/20260811040334.4184911-2-yangxingui@huawei.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/libsas/sas_expander.c | 13 +++++++++---- drivers/scsi/libsas/sas_internal.h | 1 + 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/scsi/libsas/sas_expander.c b/drivers/scsi/libsas/sas_expander.c index f471ab464a78..a5c5327cd0dd 100644 --- a/drivers/scsi/libsas/sas_expander.c +++ b/drivers/scsi/libsas/sas_expander.c @@ -345,11 +345,10 @@ static void sas_set_ex_phy(struct domain_device *dev, int phy_id, SAS_ADDR(phy->attached_sas_addr), type); } -/* check if we have an existing attached ata device on this expander phy */ -struct domain_device *sas_ex_to_ata(struct domain_device *ex_dev, int phy_id) +/* Return the domain device attached to an expander phy */ +struct domain_device *sas_ex_phy_to_dev(struct domain_device *ex_dev, int phy_id) { struct ex_phy *ex_phy = &ex_dev->ex_dev.ex_phy[phy_id]; - struct domain_device *dev; struct sas_rphy *rphy; if (!ex_phy->port) @@ -359,7 +358,13 @@ struct domain_device *sas_ex_to_ata(struct domain_device *ex_dev, int phy_id) if (!rphy) return NULL; - dev = sas_find_dev_by_rphy(rphy); + return sas_find_dev_by_rphy(rphy); +} + +/* Check if we have an existing attached ata device on this expander phy */ +struct domain_device *sas_ex_to_ata(struct domain_device *ex_dev, int phy_id) +{ + struct domain_device *dev = sas_ex_phy_to_dev(ex_dev, phy_id); if (dev && dev_is_sata(dev)) return dev; diff --git a/drivers/scsi/libsas/sas_internal.h b/drivers/scsi/libsas/sas_internal.h index 7dce0f587149..f5c75ab10dc4 100644 --- a/drivers/scsi/libsas/sas_internal.h +++ b/drivers/scsi/libsas/sas_internal.h @@ -91,6 +91,7 @@ int sas_smp_get_phy_events(struct sas_phy *phy); void sas_device_set_phy(struct domain_device *dev, struct sas_port *port); struct domain_device *sas_find_dev_by_rphy(struct sas_rphy *rphy); +struct domain_device *sas_ex_phy_to_dev(struct domain_device *ex_dev, int phy_id); struct domain_device *sas_ex_to_ata(struct domain_device *ex_dev, int phy_id); int sas_ex_phy_discover(struct domain_device *dev, int single); int sas_get_report_phy_sata(struct domain_device *dev, int phy_id, From db441dcb8c1452279956b7ea4e394b5ce8ce77f1 Mon Sep 17 00:00:00 2001 From: Xingui Yang Date: Tue, 11 Aug 2026 12:03:34 +0800 Subject: [PATCH 0562/1328] scsi: libsas: Add linkrate and sas_addr change detection in rediscover Introduce sas_dev_is_flutter() and sas_rediscover_ex_phy() to improve flutter and device replace detection during rediscovery. sas_dev_is_flutter() calls sas_ex_phy_discover() before looking up the child device via sas_ex_phy_to_dev(), ensuring the PHY state is always updated and avoiding use-after-free since the child device pointer is obtained after the sleeping SMP request completes. Add validation for linkrate and sas_addr changes. When the SAS address changes, phy->attached_sas_addr is restored to the original address before returning false, so sas_unregister_devs_sas_addr() can properly match and unregister the old device. The sas_addr check is ordered before the linkrate check to avoid skipping the restoration when both change simultaneously. sas_rediscover_ex_phy() uses the async discovery pattern (sas_discover_event) instead of the synchronous sas_discover_new() to ensure proper ordering between device unregistration and rediscovery, avoiding sysfs_warn_dup() errors. Signed-off-by: Xingui Yang Suggested-by: John Garry Link: https://patch.msgid.link/20260811040334.4184911-3-yangxingui@huawei.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/libsas/sas_expander.c | 85 ++++++++++++++++++++++++------ 1 file changed, 70 insertions(+), 15 deletions(-) diff --git a/drivers/scsi/libsas/sas_expander.c b/drivers/scsi/libsas/sas_expander.c index a5c5327cd0dd..811c9eb4fef1 100644 --- a/drivers/scsi/libsas/sas_expander.c +++ b/drivers/scsi/libsas/sas_expander.c @@ -1963,6 +1963,72 @@ static bool dev_type_flutter(enum sas_device_type new, enum sas_device_type old) return false; } +static void sas_rediscover_ex_phy(struct domain_device *dev, int phy_id, + bool last) +{ + struct expander_device *ex = &dev->ex_dev; + struct ex_phy *phy = &ex->ex_phy[phy_id]; + + phy->phy_change_count = -1; + ex->ex_change_count = -1; + sas_unregister_devs_sas_addr(dev, phy_id, last); + sas_discover_event(dev->port, DISCE_REVALIDATE_DOMAIN); +} + +static bool sas_dev_is_flutter(struct domain_device *dev, int phy_id, + u8 *sas_addr, enum sas_device_type type) +{ + struct expander_device *ex = &dev->ex_dev; + struct ex_phy *phy = &ex->ex_phy[phy_id]; + struct domain_device *child_dev; + char *action = ""; + int res; + + if (SAS_ADDR(sas_addr) != SAS_ADDR(phy->attached_sas_addr) || + !dev_type_flutter(type, phy->attached_dev_type)) + return false; + + res = sas_ex_phy_discover(dev, phy_id); + if (res) + return false; + + child_dev = sas_ex_phy_to_dev(dev, phy_id); + if (!child_dev) + goto out; + + if (dev_is_sata(child_dev) && + phy->attached_dev_type == SAS_SATA_PENDING) { + action = ", needs recovery"; + goto out; + } + + if (SAS_ADDR(child_dev->sas_addr) != SAS_ADDR(phy->attached_sas_addr)) { + pr_info("ex %016llx phy%02d sas_addr changed from %016llx to %016llx\n", + SAS_ADDR(dev->sas_addr), phy_id, + SAS_ADDR(child_dev->sas_addr), + SAS_ADDR(phy->attached_sas_addr)); + /* + * Device unregistering relies on address matching. Restore + * attached_sas_addr back to the original address so that the old + * device can be unregistered later + */ + memcpy(phy->attached_sas_addr, child_dev->sas_addr, SAS_ADDR_SIZE); + return false; + } + + if (child_dev->linkrate != phy->linkrate) { + pr_info("ex %016llx phy%02d linkrate changed from %d to %d\n", + SAS_ADDR(dev->sas_addr), phy_id, + child_dev->linkrate, phy->linkrate); + return false; + } + +out: + pr_debug("ex %016llx phy%02d broadcast flutter%s\n", + SAS_ADDR(dev->sas_addr), phy_id, action); + return true; +} + static int sas_rediscover_dev(struct domain_device *dev, int phy_id, bool last, int sibling) { @@ -2016,27 +2082,16 @@ static int sas_rediscover_dev(struct domain_device *dev, int phy_id, if (res == 0) sas_set_ex_phy(dev, phy_id, disc_resp); goto out_free_resp; - } else if (SAS_ADDR(sas_addr) == SAS_ADDR(phy->attached_sas_addr) && - dev_type_flutter(type, phy->attached_dev_type)) { - struct domain_device *ata_dev = sas_ex_to_ata(dev, phy_id); - char *action = ""; - - sas_ex_phy_discover(dev, phy_id); - - if (ata_dev && phy->attached_dev_type == SAS_SATA_PENDING) - action = ", needs recovery"; - pr_debug("ex %016llx phy%02d broadcast flutter%s\n", - SAS_ADDR(dev->sas_addr), phy_id, action); - goto out_free_resp; } + if (sas_dev_is_flutter(dev, phy_id, sas_addr, type)) + goto out_free_resp; + /* we always have to delete the old device when we went here */ pr_info("ex %016llx phy%02d replace %016llx\n", SAS_ADDR(dev->sas_addr), phy_id, SAS_ADDR(phy->attached_sas_addr)); - sas_unregister_devs_sas_addr(dev, phy_id, last); - - res = sas_discover_new(dev, phy_id); + sas_rediscover_ex_phy(dev, phy_id, last); out_free_resp: kfree(disc_resp); return res; From 720d8b2f2457f4962ca353dd90395e2660d9a10d Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 7 Aug 2026 15:49:46 -0700 Subject: [PATCH 0563/1328] scsi: ufs: core: Set task state before io_schedule_timeout() Set the task state to TASK_UNINTERRUPTIBLE before calling io_schedule_timeout() in ufshcd_wait_for_pending_cmds(). Without setting the task state, io_schedule_timeout() returns immediately because the task state remains TASK_RUNNING. This results in a busy loop that wastes CPU cycles. Fixes: 2000bc309703 ("scsi: ufs: core: Reduce the clock scaling latency") Reviewed-by: Peter Wang Reported-by: Sashiko Signed-off-by: Bart Van Assche Link: https://patch.msgid.link/8fe4526ce272811b28e99048b42358dd8f7c48af.1786142946.git.bvanassche@acm.org Signed-off-by: Martin K. Petersen (Oracle) --- drivers/ufs/core/ufshcd.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c index a51e071916cf..2c5d8e0e696e 100644 --- a/drivers/ufs/core/ufshcd.c +++ b/drivers/ufs/core/ufshcd.c @@ -1317,6 +1317,7 @@ static int ufshcd_wait_for_pending_cmds(struct ufs_hba *hba, break; } + __set_current_state(TASK_UNINTERRUPTIBLE); io_schedule_timeout(msecs_to_jiffies(20)); if (ktime_to_us(ktime_sub(ktime_get(), start)) > wait_timeout_us) { From ff5d552022b35f7475bdc538173b3ceb95d55d85 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 7 Aug 2026 15:49:47 -0700 Subject: [PATCH 0564/1328] scsi: ufs: core: Enable context analysis Annotate functions that modify the state of a synchronization object. Remove the struct semaphore annotations because lock context annotations are not supported for semaphores. Reviewed-by: Peter Wang Signed-off-by: Bart Van Assche Link: https://patch.msgid.link/3c975386a5bcb939f8a2a0d47fd621f234321a9e.1786142946.git.bvanassche@acm.org Signed-off-by: Martin K. Petersen (Oracle) --- drivers/ufs/core/Makefile | 2 ++ drivers/ufs/core/ufs-debugfs.c | 8 ++++++-- drivers/ufs/core/ufshcd.c | 14 ++++++++++++++ drivers/ufs/host/Makefile | 2 ++ 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/drivers/ufs/core/Makefile b/drivers/ufs/core/Makefile index ce7d16d2cf35..67ab9ffbdf5d 100644 --- a/drivers/ufs/core/Makefile +++ b/drivers/ufs/core/Makefile @@ -1,5 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 +CONTEXT_ANALYSIS := y + obj-$(CONFIG_SCSI_UFSHCD) += ufshcd-core.o ufshcd-core-y += ufshcd.o ufs-sysfs.o ufs-mcq.o ufs-txeq.o ufshcd-core-$(CONFIG_RPMB) += ufs-rpmb.o diff --git a/drivers/ufs/core/ufs-debugfs.c b/drivers/ufs/core/ufs-debugfs.c index be527209540d..ed1de70e2ec1 100644 --- a/drivers/ufs/core/ufs-debugfs.c +++ b/drivers/ufs/core/ufs-debugfs.c @@ -65,8 +65,10 @@ static int ee_usr_mask_get(void *data, u64 *val) return 0; } +token_context_lock(ufs_debugfs); + static int ufs_debugfs_get_user_access(struct ufs_hba *hba) -__acquires(&hba->host_sem) + __cond_acquires(0, ufs_debugfs) { down(&hba->host_sem); if (!ufshcd_is_user_access_allowed(hba)) { @@ -74,14 +76,16 @@ __acquires(&hba->host_sem) return -EBUSY; } ufshcd_rpm_get_sync(hba); + __acquire(ufs_debugfs); return 0; } static void ufs_debugfs_put_user_access(struct ufs_hba *hba) -__releases(&hba->host_sem) + __releases(ufs_debugfs) { ufshcd_rpm_put_sync(hba); up(&hba->host_sem); + __release(ufs_debugfs); } static int ee_usr_mask_set(void *data, u64 val) diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c index 2c5d8e0e696e..67745d2796d0 100644 --- a/drivers/ufs/core/ufshcd.c +++ b/drivers/ufs/core/ufshcd.c @@ -1354,6 +1354,8 @@ static int ufshcd_wait_for_pending_cmds(struct ufs_hba *hba, * On failure, all acquired locks are released and the tagset is unquiesced. */ int ufshcd_pause_command_processing(struct ufs_hba *hba, u64 timeout_us) + __cond_acquires(0, &hba->host->scan_mutex) + __cond_acquires(0, &hba->clk_scaling_lock) { int ret = 0; @@ -1378,6 +1380,8 @@ int ufshcd_pause_command_processing(struct ufs_hba *hba, u64 timeout_us) * This function resumes command submissions. */ void ufshcd_resume_command_processing(struct ufs_hba *hba) + __releases(&hba->clk_scaling_lock) + __releases(&hba->host->scan_mutex) { up_write(&hba->clk_scaling_lock); blk_mq_unquiesce_tagset(&hba->host->tag_set); @@ -1447,6 +1451,9 @@ static int ufshcd_scale_gear(struct ufs_hba *hba, u32 target_gear, bool scale_up * Return: 0 upon success; -EBUSY upon timeout. */ static int ufshcd_clock_scaling_prepare(struct ufs_hba *hba, u64 timeout_us) + __cond_acquires(0, &hba->host->scan_mutex) + __cond_acquires(0, &hba->wb_mutex) + __cond_acquires(0, &hba->clk_scaling_lock) { int ret = 0; /* @@ -1476,6 +1483,9 @@ static int ufshcd_clock_scaling_prepare(struct ufs_hba *hba, u64 timeout_us) } static void ufshcd_clock_scaling_unprepare(struct ufs_hba *hba, int err) + __releases(&hba->clk_scaling_lock) + __releases(&hba->wb_mutex) + __releases(&hba->host->scan_mutex) { up_write(&hba->clk_scaling_lock); mutex_unlock(&hba->wb_mutex); @@ -3305,6 +3315,8 @@ ufshcd_dev_cmd_completion(struct ufs_hba *hba, struct ufshcd_lrb *lrbp) } static void ufshcd_dev_man_lock(struct ufs_hba *hba) + __acquires(&hba->dev_cmd.lock) + __acquires_shared(&hba->clk_scaling_lock) { ufshcd_hold(hba); mutex_lock(&hba->dev_cmd.lock); @@ -3312,6 +3324,8 @@ static void ufshcd_dev_man_lock(struct ufs_hba *hba) } static void ufshcd_dev_man_unlock(struct ufs_hba *hba) + __releases_shared(&hba->clk_scaling_lock) + __releases(&hba->dev_cmd.lock) { up_read(&hba->clk_scaling_lock); mutex_unlock(&hba->dev_cmd.lock); diff --git a/drivers/ufs/host/Makefile b/drivers/ufs/host/Makefile index 65d8bb23ab7b..7d8db67eb23c 100644 --- a/drivers/ufs/host/Makefile +++ b/drivers/ufs/host/Makefile @@ -1,5 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 +CONTEXT_ANALYSIS := y + obj-$(CONFIG_SCSI_UFS_DWC_TC_PCI) += tc-dwc-g210-pci.o ufshcd-dwc.o tc-dwc-g210.o obj-$(CONFIG_SCSI_UFS_DWC_TC_PLATFORM) += tc-dwc-g210-pltfrm.o ufshcd-dwc.o tc-dwc-g210.o obj-$(CONFIG_SCSI_UFS_CDNS_PLATFORM) += cdns-pltfrm.o From e70647b25a641b9f5602558839182d036b512de5 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 7 Aug 2026 15:49:48 -0700 Subject: [PATCH 0565/1328] scsi: core: Pass the SCSI host pointer directly to scanning functions In the functions scsi_probe_and_add_lun(), scsi_sequential_lun_scan(), scsi_report_lun_scan() and __scsi_scan_target() the SCSI host pointer is derived from the SCSI target pointer. Pass the SCSI host pointer directly. This patch prepares for enabling context analysis. With this patch applied, context annotations can refer to the SCSI host pointer directly, e.g. __must_hold(&shost->scan_mutex). Without this patch, the following annotation would have to be used: __must_hold(&dev_to_shost(starget->dev.parent)->scan_mutex) Additionally, in code that locks shost->scan_mutex, the following would have to be added to help the compiler understand that shost == dev_to_shost(starget->dev.parent): __assume_ctx_lock(&dev_to_shost(starget->dev.parent)->scan_mutex); __assume_ctx_lock() statements should be avoided if there is a good alternative. Hence this patch. No functionality has been changed. Reviewed-by: John Garry Signed-off-by: Bart Van Assche Link: https://patch.msgid.link/49d2fc5fae5cb5dca2536818155581c73f39c883.1786142946.git.bvanassche@acm.org Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/scsi_scan.c | 47 ++++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/drivers/scsi/scsi_scan.c b/drivers/scsi/scsi_scan.c index e27da038603a..d7e0e9393194 100644 --- a/drivers/scsi/scsi_scan.c +++ b/drivers/scsi/scsi_scan.c @@ -1169,6 +1169,7 @@ static unsigned char *scsi_inq_str(unsigned char *buf, unsigned char *inq, /** * scsi_probe_and_add_lun - probe a LUN, if a LUN is found add it + * @shost: SCSI host pointer * @starget: pointer to target device structure * @lun: LUN of target device * @bflagsp: store bflags here if not NULL @@ -1188,7 +1189,8 @@ static unsigned char *scsi_inq_str(unsigned char *buf, unsigned char *inq, * attached at the LUN * - SCSI_SCAN_LUN_PRESENT: a new scsi_device was allocated and initialized **/ -static int scsi_probe_and_add_lun(struct scsi_target *starget, +static int scsi_probe_and_add_lun(struct Scsi_Host *shost, + struct scsi_target *starget, u64 lun, blist_flags_t *bflagsp, struct scsi_device **sdevp, enum scsi_scan_mode rescan, @@ -1198,7 +1200,6 @@ static int scsi_probe_and_add_lun(struct scsi_target *starget, unsigned char *result; blist_flags_t bflags; int res = SCSI_SCAN_NO_RESPONSE, result_len = 256; - struct Scsi_Host *shost = dev_to_shost(starget->dev.parent); /* * The rescan flag is used as an optimization, the first scan of a @@ -1334,6 +1335,7 @@ static int scsi_probe_and_add_lun(struct scsi_target *starget, /** * scsi_sequential_lun_scan - sequentially scan a SCSI target + * @shost: SCSI host pointer * @starget: pointer to target structure to scan * @bflags: black/white list flag for LUN 0 * @scsi_level: Which version of the standard does this device adhere to @@ -1346,13 +1348,13 @@ static int scsi_probe_and_add_lun(struct scsi_target *starget, * * Modifies sdevscan->lun. **/ -static void scsi_sequential_lun_scan(struct scsi_target *starget, +static void scsi_sequential_lun_scan(struct Scsi_Host *shost, + struct scsi_target *starget, blist_flags_t bflags, int scsi_level, enum scsi_scan_mode rescan) { uint max_dev_lun; u64 sparse_lun, lun; - struct Scsi_Host *shost = dev_to_shost(starget->dev.parent); SCSI_LOG_SCAN_BUS(3, starget_printk(KERN_INFO, starget, "scsi scan: Sequential scan\n")); @@ -1412,14 +1414,15 @@ static void scsi_sequential_lun_scan(struct scsi_target *starget, * sparse_lun. */ for (lun = 1; lun < max_dev_lun; ++lun) - if ((scsi_probe_and_add_lun(starget, lun, NULL, NULL, rescan, - NULL) != SCSI_SCAN_LUN_PRESENT) && + if (scsi_probe_and_add_lun(shost, starget, lun, NULL, NULL, + rescan, NULL) != SCSI_SCAN_LUN_PRESENT && !sparse_lun) return; } /** * scsi_report_lun_scan - Scan using SCSI REPORT LUN results + * @shost: SCSI host pointer * @starget: which target * @bflags: Zero or a mix of BLIST_NOLUN, BLIST_REPORTLUN2, or BLIST_NOREPORTLUN * @rescan: nonzero if we can skip code only needed on first scan @@ -1438,8 +1441,9 @@ static void scsi_sequential_lun_scan(struct scsi_target *starget, * 0: scan completed (or no memory, so further scanning is futile) * 1: could not scan with REPORT LUN **/ -static int scsi_report_lun_scan(struct scsi_target *starget, blist_flags_t bflags, - enum scsi_scan_mode rescan) +static int scsi_report_lun_scan(struct Scsi_Host *shost, + struct scsi_target *starget, blist_flags_t bflags, + enum scsi_scan_mode rescan) { unsigned char scsi_cmd[MAX_COMMAND_SIZE]; unsigned int length; @@ -1448,7 +1452,6 @@ static int scsi_report_lun_scan(struct scsi_target *starget, blist_flags_t bflag int result; struct scsi_lun *lunp, *lun_data; struct scsi_device *sdev; - struct Scsi_Host *shost = dev_to_shost(&starget->dev); struct scsi_failure failure_defs[] = { { .sense = UNIT_ATTENTION, @@ -1594,7 +1597,7 @@ static int scsi_report_lun_scan(struct scsi_target *starget, blist_flags_t bflag } else { int res; - res = scsi_probe_and_add_lun(starget, + res = scsi_probe_and_add_lun(shost, starget, lun, NULL, NULL, rescan, NULL); if (res == SCSI_SCAN_NO_RESPONSE) { /* @@ -1641,7 +1644,7 @@ struct scsi_device *__scsi_add_device(struct Scsi_Host *shost, uint channel, scsi_complete_async_scans(); if (scsi_host_scan_allowed(shost) && scsi_autopm_get_host(shost) == 0) { - scsi_probe_and_add_lun(starget, lun, NULL, &sdev, + scsi_probe_and_add_lun(shost, starget, lun, NULL, &sdev, SCSI_SCAN_RESCAN, hostdata); scsi_autopm_put_host(shost); } @@ -1763,10 +1766,10 @@ int scsi_rescan_device(struct scsi_device *sdev) } EXPORT_SYMBOL(scsi_rescan_device); -static void __scsi_scan_target(struct device *parent, unsigned int channel, - unsigned int id, u64 lun, enum scsi_scan_mode rescan) +static void __scsi_scan_target(struct Scsi_Host *shost, struct device *parent, + unsigned int channel, unsigned int id, u64 lun, + enum scsi_scan_mode rescan) { - struct Scsi_Host *shost = dev_to_shost(parent); blist_flags_t bflags = 0; int res; struct scsi_target *starget; @@ -1786,7 +1789,8 @@ static void __scsi_scan_target(struct device *parent, unsigned int channel, /* * Scan for a specific host/chan/id/lun. */ - scsi_probe_and_add_lun(starget, lun, NULL, NULL, rescan, NULL); + scsi_probe_and_add_lun(shost, starget, lun, NULL, NULL, rescan, + NULL); goto out_reap; } @@ -1794,14 +1798,15 @@ static void __scsi_scan_target(struct device *parent, unsigned int channel, * Scan LUN 0, if there is some response, scan further. Ideally, we * would not configure LUN 0 until all LUNs are scanned. */ - res = scsi_probe_and_add_lun(starget, 0, &bflags, NULL, rescan, NULL); + res = scsi_probe_and_add_lun(shost, starget, 0, &bflags, NULL, rescan, + NULL); if (res == SCSI_SCAN_LUN_PRESENT || res == SCSI_SCAN_TARGET_PRESENT) { - if (scsi_report_lun_scan(starget, bflags, rescan) != 0) + if (scsi_report_lun_scan(shost, starget, bflags, rescan) != 0) /* * The REPORT LUN did not scan the target, * do a sequential scan. */ - scsi_sequential_lun_scan(starget, bflags, + scsi_sequential_lun_scan(shost, starget, bflags, starget->scsi_level, rescan); } @@ -1851,7 +1856,7 @@ void scsi_scan_target(struct device *parent, unsigned int channel, scsi_complete_async_scans(); if (scsi_host_scan_allowed(shost) && scsi_autopm_get_host(shost) == 0) { - __scsi_scan_target(parent, channel, id, lun, rescan); + __scsi_scan_target(shost, parent, channel, id, lun, rescan); scsi_autopm_put_host(shost); } mutex_unlock(&shost->scan_mutex); @@ -1882,11 +1887,11 @@ static void scsi_scan_channel(struct Scsi_Host *shost, unsigned int channel, order_id = shost->max_id - id - 1; else order_id = id; - __scsi_scan_target(&shost->shost_gendev, channel, + __scsi_scan_target(shost, &shost->shost_gendev, channel, order_id, lun, rescan); } else - __scsi_scan_target(&shost->shost_gendev, channel, + __scsi_scan_target(shost, &shost->shost_gendev, channel, id, lun, rescan); } From 09982efcc07e739c7e4ac6089e3e521132388b53 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 7 Aug 2026 15:49:49 -0700 Subject: [PATCH 0566/1328] scsi: core: Add lock context annotations Document which functions expect that shost->scan_mutex is held. Reviewed-by: John Garry Signed-off-by: Bart Van Assche Link: https://patch.msgid.link/ad5ca37acf8c933a12830c0811c293af54c87573.1786142946.git.bvanassche@acm.org Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/scsi_scan.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/scsi/scsi_scan.c b/drivers/scsi/scsi_scan.c index d7e0e9393194..3b82e80e807a 100644 --- a/drivers/scsi/scsi_scan.c +++ b/drivers/scsi/scsi_scan.c @@ -1195,6 +1195,7 @@ static int scsi_probe_and_add_lun(struct Scsi_Host *shost, struct scsi_device **sdevp, enum scsi_scan_mode rescan, void *hostdata) + __must_hold(&shost->scan_mutex) { struct scsi_device *sdev; unsigned char *result; @@ -1352,6 +1353,7 @@ static void scsi_sequential_lun_scan(struct Scsi_Host *shost, struct scsi_target *starget, blist_flags_t bflags, int scsi_level, enum scsi_scan_mode rescan) + __must_hold(&shost->scan_mutex) { uint max_dev_lun; u64 sparse_lun, lun; @@ -1444,6 +1446,7 @@ static void scsi_sequential_lun_scan(struct Scsi_Host *shost, static int scsi_report_lun_scan(struct Scsi_Host *shost, struct scsi_target *starget, blist_flags_t bflags, enum scsi_scan_mode rescan) + __must_hold(&shost->scan_mutex) { unsigned char scsi_cmd[MAX_COMMAND_SIZE]; unsigned int length; @@ -1769,6 +1772,7 @@ EXPORT_SYMBOL(scsi_rescan_device); static void __scsi_scan_target(struct Scsi_Host *shost, struct device *parent, unsigned int channel, unsigned int id, u64 lun, enum scsi_scan_mode rescan) + __must_hold(&shost->scan_mutex) { blist_flags_t bflags = 0; int res; @@ -1866,6 +1870,7 @@ EXPORT_SYMBOL(scsi_scan_target); static void scsi_scan_channel(struct Scsi_Host *shost, unsigned int channel, unsigned int id, u64 lun, enum scsi_scan_mode rescan) + __must_hold(&shost->scan_mutex) { uint order_id; From 4c461ee2b2a5a7c327fe092b41de1fcc002adc01 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 7 Aug 2026 15:49:50 -0700 Subject: [PATCH 0567/1328] scsi: core: Protect host state changes with the host lock Some but not all SCSI host state changes are protected with the SCSI host lock. Annotate the SCSI host state with __guarded_by(host_lock) and protect all SCSI host state changes with the SCSI host lock. This patch prevents that KCSAN complains about data races when accessing the SCSI host state. Reported-by: Jianzhou Zhao Closes: https://lore.kernel.org/all/36d59d0e.6db0.19cdbeee01b.Coremail.luckd0g@163.com/ Signed-off-by: Bart Van Assche Reviewed-by: John Garry Link: https://patch.msgid.link/681e4a5260c182feb5fc1d96f0d43c62c21dc6c9.1786142946.git.bvanassche@acm.org Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/hosts.c | 19 ++++++++++-------- drivers/scsi/leapraid/leapraid_func.c | 2 +- drivers/scsi/leapraid/leapraid_os.c | 2 +- drivers/scsi/megaraid/megaraid_sas_base.c | 2 +- drivers/scsi/mpi3mr/mpi3mr_os.c | 2 +- drivers/scsi/mpt3sas/mpt3sas_scsih.c | 2 +- drivers/scsi/qla4xxx/ql4_os.c | 6 ++---- drivers/scsi/scsi_lib.c | 3 +-- drivers/scsi/scsi_sysfs.c | 7 ++++--- include/scsi/scsi_host.h | 24 ++++++++++++++++------- 10 files changed, 40 insertions(+), 29 deletions(-) diff --git a/drivers/scsi/hosts.c b/drivers/scsi/hosts.c index d512080268af..9610dd7aa85f 100644 --- a/drivers/scsi/hosts.c +++ b/drivers/scsi/hosts.c @@ -73,8 +73,9 @@ static struct class shost_class = { * transition is illegal. **/ int scsi_host_set_state(struct Scsi_Host *shost, enum scsi_host_state state) + __must_hold(shost->host_lock) { - enum scsi_host_state oldstate = shost->shost_state; + enum scsi_host_state oldstate = READ_ONCE(shost->shost_state); if (state == oldstate) return 0; @@ -145,7 +146,7 @@ int scsi_host_set_state(struct Scsi_Host *shost, enum scsi_host_state state) } break; } - shost->shost_state = state; + WRITE_ONCE(shost->shost_state, state); return 0; illegal: @@ -276,7 +277,8 @@ int scsi_add_host_with_dma(struct Scsi_Host *shost, struct device *dev, if (error) goto out_disable_runtime_pm; - scsi_host_set_state(shost, SHOST_RUNNING); + scoped_guard(spinlock_irq, shost->host_lock) + scsi_host_set_state(shost, SHOST_RUNNING); get_device(shost->shost_gendev.parent); device_enable_async_suspend(&shost->shost_dev); @@ -350,6 +352,7 @@ EXPORT_SYMBOL(scsi_add_host_with_dma); static void scsi_host_dev_release(struct device *dev) { struct Scsi_Host *shost = dev_to_shost(dev); + enum scsi_host_state state = scsi_get_host_state(shost); struct device *parent = dev->parent; /* Wait for functions invoked through call_rcu(&scmd->rcu, ...) */ @@ -362,7 +365,7 @@ static void scsi_host_dev_release(struct device *dev) if (shost->work_q) destroy_workqueue(shost->work_q); - if (shost->shost_state == SHOST_CREATED) { + if (state == SHOST_CREATED) { /* * Free the shost_dev device name and remove the proc host dir * here if scsi_host_{alloc,put}() have been called but neither @@ -378,7 +381,7 @@ static void scsi_host_dev_release(struct device *dev) ida_free(&host_index_ida, shost->host_no); - if (shost->shost_state != SHOST_CREATED) + if (state != SHOST_CREATED) put_device(parent); kfree(shost); } @@ -411,8 +414,8 @@ struct Scsi_Host *scsi_host_alloc(const struct scsi_host_template *sht, int priv return NULL; shost->host_lock = &shost->default_lock; - spin_lock_init(shost->host_lock); - shost->shost_state = SHOST_CREATED; + scoped_guard(spinlock_init, shost->host_lock) + shost->shost_state = SHOST_CREATED; INIT_LIST_HEAD(&shost->__devices); INIT_LIST_HEAD(&shost->__targets); INIT_LIST_HEAD(&shost->eh_abort_list); @@ -598,7 +601,7 @@ EXPORT_SYMBOL(scsi_host_lookup); **/ struct Scsi_Host *scsi_host_get(struct Scsi_Host *shost) { - if ((shost->shost_state == SHOST_DEL) || + if (scsi_get_host_state(shost) == SHOST_DEL || !get_device(&shost->shost_gendev)) return NULL; return shost; diff --git a/drivers/scsi/leapraid/leapraid_func.c b/drivers/scsi/leapraid/leapraid_func.c index 3e1aeceab994..089d0810bd13 100644 --- a/drivers/scsi/leapraid/leapraid_func.c +++ b/drivers/scsi/leapraid/leapraid_func.c @@ -37,7 +37,7 @@ static noinline bool leapraid_shost_in_recovery(struct Scsi_Host *shost) { enum scsi_host_state state; - state = READ_ONCE(shost->shost_state); + state = scsi_get_host_state(shost); return state == SHOST_RECOVERY || state == SHOST_CANCEL_RECOVERY || state == SHOST_DEL_RECOVERY || diff --git a/drivers/scsi/leapraid/leapraid_os.c b/drivers/scsi/leapraid/leapraid_os.c index 2f3d4ac7490c..a8e1c9f33896 100644 --- a/drivers/scsi/leapraid/leapraid_os.c +++ b/drivers/scsi/leapraid/leapraid_os.c @@ -808,7 +808,7 @@ static bool leapraid_should_queuecommand(struct leapraid_adapter *adapter, goto no_connect; if (sdev_priv->block && - scmd->device->host->shost_state == SHOST_RECOVERY && + scsi_get_host_state(scmd->device->host) == SHOST_RECOVERY && scmd->cmnd[0] == TEST_UNIT_READY) { scsi_build_sense(scmd, 0, UNIT_ATTENTION, LEAPRAID_SCSI_ASC_POWER_ON_RESET, diff --git a/drivers/scsi/megaraid/megaraid_sas_base.c b/drivers/scsi/megaraid/megaraid_sas_base.c index ecd365d78ae3..f0152b043e18 100644 --- a/drivers/scsi/megaraid/megaraid_sas_base.c +++ b/drivers/scsi/megaraid/megaraid_sas_base.c @@ -3072,7 +3072,7 @@ static int megasas_reset_bus_host(struct scsi_cmnd *scmd) scmd_printk(KERN_INFO, scmd, "SCSI host state: %d SCSI host busy: %d FW outstanding: %d\n", - scmd->device->host->shost_state, + scsi_get_host_state(scmd->device->host), scsi_host_busy(scmd->device->host), atomic_read(&instance->fw_outstanding)); /* diff --git a/drivers/scsi/mpi3mr/mpi3mr_os.c b/drivers/scsi/mpi3mr/mpi3mr_os.c index 402d1f35d214..f80a21ec161b 100644 --- a/drivers/scsi/mpi3mr/mpi3mr_os.c +++ b/drivers/scsi/mpi3mr/mpi3mr_os.c @@ -5172,7 +5172,7 @@ static enum scsi_qc_status mpi3mr_qcmd(struct Scsi_Host *shost, /* Avoid error handling escalation when device is removed or blocked */ - if (scmd->device->host->shost_state == SHOST_RECOVERY && + if (scsi_get_host_state(scmd->device->host) == SHOST_RECOVERY && scmd->cmnd[0] == TEST_UNIT_READY && (stgt_priv_data->dev_removed || (dev_handle == MPI3MR_INVALID_DEV_HANDLE))) { scsi_build_sense(scmd, 0, UNIT_ATTENTION, 0x29, 0x07); diff --git a/drivers/scsi/mpt3sas/mpt3sas_scsih.c b/drivers/scsi/mpt3sas/mpt3sas_scsih.c index dea78688cc9b..0e12009a87f6 100644 --- a/drivers/scsi/mpt3sas/mpt3sas_scsih.c +++ b/drivers/scsi/mpt3sas/mpt3sas_scsih.c @@ -5472,7 +5472,7 @@ static enum scsi_qc_status scsih_qcmd(struct Scsi_Host *shost, * Avoid error handling escallation when device is disconnected */ if (handle == MPT3SAS_INVALID_DEVICE_HANDLE || sas_device_priv_data->block) { - if (scmd->device->host->shost_state == SHOST_RECOVERY && + if (scsi_get_host_state(scmd->device->host) == SHOST_RECOVERY && scmd->cmnd[0] == TEST_UNIT_READY) { scsi_build_sense(scmd, 0, UNIT_ATTENTION, 0x29, 0x07); scsi_done(scmd); diff --git a/drivers/scsi/qla4xxx/ql4_os.c b/drivers/scsi/qla4xxx/ql4_os.c index d598ab4126f8..c9d9fc7c81fb 100644 --- a/drivers/scsi/qla4xxx/ql4_os.c +++ b/drivers/scsi/qla4xxx/ql4_os.c @@ -9411,11 +9411,9 @@ static int qla4xxx_eh_target_reset(struct scsi_cmnd *cmd) * This routine finds that if reset host is called in EH * scenario or from some application like sg_reset **/ -static int qla4xxx_is_eh_active(struct Scsi_Host *shost) +static bool qla4xxx_is_eh_active(struct Scsi_Host *shost) { - if (shost->shost_state == SHOST_RECOVERY) - return 1; - return 0; + return scsi_get_host_state(shost) == SHOST_RECOVERY; } /** diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c index 686cef2406b3..2e07b686090f 100644 --- a/drivers/scsi/scsi_lib.c +++ b/drivers/scsi/scsi_lib.c @@ -1663,10 +1663,9 @@ static enum scsi_qc_status scsi_dispatch_cmd(struct scsi_cmnd *cmd) goto done; } - if (unlikely(host->shost_state == SHOST_DEL)) { + if (unlikely(scsi_get_host_state(host) == SHOST_DEL)) { cmd->result = (DID_NO_CONNECT << 16); goto done; - } trace_scsi_dispatch_cmd_start(cmd); diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c index dfc3559e7e04..9480432f650b 100644 --- a/drivers/scsi/scsi_sysfs.c +++ b/drivers/scsi/scsi_sysfs.c @@ -214,8 +214,9 @@ store_shost_state(struct device *dev, struct device_attribute *attr, if (!state) return -EINVAL; - if (scsi_host_set_state(shost, state)) - return -EINVAL; + scoped_guard(spinlock_irq, shost->host_lock) + if (scsi_host_set_state(shost, state)) + return -EINVAL; return count; } @@ -223,7 +224,7 @@ static ssize_t show_shost_state(struct device *dev, struct device_attribute *attr, char *buf) { struct Scsi_Host *shost = class_to_shost(dev); - const char *name = scsi_host_state_name(shost->shost_state); + const char *name = scsi_host_state_name(scsi_get_host_state(shost)); if (!name) return -EINVAL; diff --git a/include/scsi/scsi_host.h b/include/scsi/scsi_host.h index 7e2011830ba4..c9754771bf29 100644 --- a/include/scsi/scsi_host.h +++ b/include/scsi/scsi_host.h @@ -2,6 +2,7 @@ #ifndef _SCSI_SCSI_HOST_H #define _SCSI_SCSI_HOST_H +#include #include #include #include @@ -727,7 +728,7 @@ struct Scsi_Host { unsigned int irq; - enum scsi_host_state shost_state; + enum scsi_host_state shost_state __guarded_by(host_lock); /* ldm bits */ struct device shost_gendev, shost_dev; @@ -785,11 +786,18 @@ static inline struct Scsi_Host *dev_to_shost(struct device *dev) return container_of(dev, struct Scsi_Host, shost_gendev); } +static inline enum scsi_host_state scsi_get_host_state(struct Scsi_Host *shost) +{ + return context_unsafe(READ_ONCE(shost->shost_state)); +} + static inline int scsi_host_in_recovery(struct Scsi_Host *shost) { - return shost->shost_state == SHOST_RECOVERY || - shost->shost_state == SHOST_CANCEL_RECOVERY || - shost->shost_state == SHOST_DEL_RECOVERY || + enum scsi_host_state state = scsi_get_host_state(shost); + + return state == SHOST_RECOVERY || + state == SHOST_CANCEL_RECOVERY || + state == SHOST_DEL_RECOVERY || shost->tmf_in_progress; } @@ -835,8 +843,9 @@ static inline struct device *scsi_get_device(struct Scsi_Host *shost) **/ static inline int scsi_host_scan_allowed(struct Scsi_Host *shost) { - return shost->shost_state == SHOST_RUNNING || - shost->shost_state == SHOST_RECOVERY; + enum scsi_host_state state = scsi_get_host_state(shost); + + return state == SHOST_RUNNING || state == SHOST_RECOVERY; } extern void scsi_unblock_requests(struct Scsi_Host *); @@ -940,6 +949,7 @@ static inline unsigned char scsi_host_get_guard(struct Scsi_Host *shost) return shost->prot_guard_type; } -extern int scsi_host_set_state(struct Scsi_Host *, enum scsi_host_state); +int scsi_host_set_state(struct Scsi_Host *shost, enum scsi_host_state state) + __must_hold(shost->host_lock); #endif /* _SCSI_SCSI_HOST_H */ From fb0fc67db96292a65539e63f536c6f586490c78b Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 7 Aug 2026 15:49:51 -0700 Subject: [PATCH 0568/1328] scsi: core: Enable context analysis Enable context analysis for those SCSI core files that build without triggering any context analysis warnings. Signed-off-by: Bart Van Assche Reviewed-by: John Garry Link: https://patch.msgid.link/2576d2f7e3530b721b5050ac6d25c413037d7e7e.1786142946.git.bvanassche@acm.org Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/Makefile | 22 ++++++++++++++++++++++ drivers/scsi/device_handler/Makefile | 3 +++ 2 files changed, 25 insertions(+) diff --git a/drivers/scsi/Makefile b/drivers/scsi/Makefile index 098f35219e7d..72eb395ccf1d 100644 --- a/drivers/scsi/Makefile +++ b/drivers/scsi/Makefile @@ -14,6 +14,28 @@ # satisfy certain initialization assumptions in the SCSI layer. # *!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*! +CONTEXT_ANALYSIS_constants.o := y +CONTEXT_ANALYSIS_scsi.o := y +CONTEXT_ANALYSIS_scsi_common.o := y +CONTEXT_ANALYSIS_scsi_devinfo.o := y +CONTEXT_ANALYSIS_scsi_dh.o := y +CONTEXT_ANALYSIS_scsi_error.o := y +CONTEXT_ANALYSIS_scsi_ioctl.o := y +CONTEXT_ANALYSIS_scsi_lib.o := y +CONTEXT_ANALYSIS_scsi_lib_dma.o := y +CONTEXT_ANALYSIS_scsi_logging.o := y +CONTEXT_ANALYSIS_scsi_netlink.o := y +CONTEXT_ANALYSIS_scsi_pm.o := y +CONTEXT_ANALYSIS_scsi_proc.o := y +CONTEXT_ANALYSIS_scsi_scan.o := y +CONTEXT_ANALYSIS_scsi_sysfs.o := y +CONTEXT_ANALYSIS_scsi_trace.o := y +CONTEXT_ANALYSIS_scsicam.o := y +CONTEXT_ANALYSIS_sd.o := y +CONTEXT_ANALYSIS_sd_dif.o := y +CONTEXT_ANALYSIS_sd_zbc.o := y +CONTEXT_ANALYSIS_sr.o := y +CONTEXT_ANALYSIS_sr_ioctl.o := y CFLAGS_aha152x.o = -DAHA152X_STAT -DAUTOCONF diff --git a/drivers/scsi/device_handler/Makefile b/drivers/scsi/device_handler/Makefile index 0a603aefd2bb..5aa282a63e24 100644 --- a/drivers/scsi/device_handler/Makefile +++ b/drivers/scsi/device_handler/Makefile @@ -2,6 +2,9 @@ # # SCSI Device Handler # + +CONTEXT_ANALYSIS := y + obj-$(CONFIG_SCSI_DH_RDAC) += scsi_dh_rdac.o obj-$(CONFIG_SCSI_DH_HP_SW) += scsi_dh_hp_sw.o obj-$(CONFIG_SCSI_DH_EMC) += scsi_dh_emc.o From 5a03dbfd670092a6960de433005aae1734c18f1f Mon Sep 17 00:00:00 2001 From: Ian Bridges Date: Wed, 29 Jul 2026 09:46:13 -0500 Subject: [PATCH 0569/1328] scsi: lpfc: Replace strlcat() with seq_buf in lpfc_info() In preparation for removing the strlcat() API[1], replace its uses in lpfc_info(). The function accumulates a variable number of optional fragments, which is what seq_buf is for. The intermediate tmp buffer and the per fragment overflow checks become unnecessary. seq_buf is memory safe by construction and silently truncates in the same way as the replaced pattern. The old code passed phba->ModelDesc as the format string of the first scnprintf() call. The model description comes from adapter VPD data. seq_buf_printf() takes a format string, so the replacement prints it through "%s". A model description containing conversion specifiers is no longer interpreted. Link: https://github.com/KSPP/linux/issues/370 [1] Signed-off-by: Ian Bridges Link: https://patch.msgid.link/20260729144617.1388646-2-icb@fastmail.org Reviewed-by: Nigel Kirkland Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/lpfc/lpfc_scsi.c | 49 +++++++++++------------------------ 1 file changed, 15 insertions(+), 34 deletions(-) diff --git a/drivers/scsi/lpfc/lpfc_scsi.c b/drivers/scsi/lpfc/lpfc_scsi.c index f2cab134af7f..8a795c65e3c3 100644 --- a/drivers/scsi/lpfc/lpfc_scsi.c +++ b/drivers/scsi/lpfc/lpfc_scsi.c @@ -21,6 +21,7 @@ * included with this package. * *******************************************************************/ #include +#include #include #include #include @@ -5103,57 +5104,37 @@ lpfc_info(struct Scsi_Host *host) struct lpfc_hba *phba = vport->phba; int link_speed = 0; static char lpfcinfobuf[384]; - char tmp[384] = {0}; + struct seq_buf s; memset(lpfcinfobuf, 0, sizeof(lpfcinfobuf)); + seq_buf_init(&s, lpfcinfobuf, sizeof(lpfcinfobuf)); if (phba && phba->pcidev){ /* Model Description */ - scnprintf(tmp, sizeof(tmp), phba->ModelDesc); - if (strlcat(lpfcinfobuf, tmp, sizeof(lpfcinfobuf)) >= - sizeof(lpfcinfobuf)) - goto buffer_done; + seq_buf_printf(&s, "%s", phba->ModelDesc); /* PCI Info */ - scnprintf(tmp, sizeof(tmp), - " on PCI bus %02x device %02x irq %d", - phba->pcidev->bus->number, phba->pcidev->devfn, - phba->pcidev->irq); - if (strlcat(lpfcinfobuf, tmp, sizeof(lpfcinfobuf)) >= - sizeof(lpfcinfobuf)) - goto buffer_done; + seq_buf_printf(&s, " on PCI bus %02x device %02x irq %d", + phba->pcidev->bus->number, phba->pcidev->devfn, + phba->pcidev->irq); /* Port Number */ - if (phba->Port[0]) { - scnprintf(tmp, sizeof(tmp), " port %s", phba->Port); - if (strlcat(lpfcinfobuf, tmp, sizeof(lpfcinfobuf)) >= - sizeof(lpfcinfobuf)) - goto buffer_done; - } + if (phba->Port[0]) + seq_buf_printf(&s, " port %s", phba->Port); /* Link Speed */ link_speed = lpfc_sli_port_speed_get(phba); - if (link_speed != 0) { - scnprintf(tmp, sizeof(tmp), - " Logical Link Speed: %d Mbps", link_speed); - if (strlcat(lpfcinfobuf, tmp, sizeof(lpfcinfobuf)) >= - sizeof(lpfcinfobuf)) - goto buffer_done; - } + if (link_speed != 0) + seq_buf_printf(&s, " Logical Link Speed: %d Mbps", + link_speed); /* Support for BSG ioctls */ - scnprintf(tmp, sizeof(tmp), " BSG"); - if (strlcat(lpfcinfobuf, tmp, sizeof(lpfcinfobuf)) >= - sizeof(lpfcinfobuf)) - goto buffer_done; + seq_buf_printf(&s, " BSG"); /* PCI resettable */ - if (!lpfc_check_pci_resettable(phba)) { - scnprintf(tmp, sizeof(tmp), " PCI resettable"); - strlcat(lpfcinfobuf, tmp, sizeof(lpfcinfobuf)); - } + if (!lpfc_check_pci_resettable(phba)) + seq_buf_printf(&s, " PCI resettable"); } -buffer_done: return lpfcinfobuf; } From 07f46a9f8964b91d56951ef7f74c551af0eddeac Mon Sep 17 00:00:00 2001 From: Ian Bridges Date: Wed, 29 Jul 2026 09:46:14 -0500 Subject: [PATCH 0570/1328] scsi: lpfc: Replace strlcat() with scnprintf() in lpfc_vport_symbolic_node_name() In preparation for removing the strlcat() API[1], replace its uses in lpfc_vport_symbolic_node_name(). The function builds five unconditional fragments, so one scnprintf() call composes the whole string. The intermediate tmp buffer and the per fragment overflow checks become unnecessary. scnprintf() truncates at the buffer size and returns the number of bytes it wrote, which equals the length that the removed strnlen() call computed. The old code capped every fragment at MAXHOSTNAMELEN bytes before appending it, independently of the room left in the destination. The replacement formats each fragment directly into the destination, so a fragment longer than MAXHOSTNAMELEN is no longer truncated when the destination has room for it. Link: https://github.com/KSPP/linux/issues/370 [1] Signed-off-by: Ian Bridges Link: https://patch.msgid.link/20260729144617.1388646-3-icb@fastmail.org Reviewed-by: Nigel Kirkland Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/lpfc/lpfc_ct.c | 29 +++++------------------------ 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/drivers/scsi/lpfc/lpfc_ct.c b/drivers/scsi/lpfc/lpfc_ct.c index c7853e7fe071..0734ab3be3e3 100644 --- a/drivers/scsi/lpfc/lpfc_ct.c +++ b/drivers/scsi/lpfc/lpfc_ct.c @@ -1823,34 +1823,15 @@ lpfc_vport_symbolic_node_name(struct lpfc_vport *vport, char *symbol, size_t size) { char fwrev[FW_REV_STR_SIZE] = {0}; - char tmp[MAXHOSTNAMELEN] = {0}; - - memset(symbol, 0, size); - - scnprintf(tmp, sizeof(tmp), "Emulex %s", vport->phba->ModelName); - if (strlcat(symbol, tmp, size) >= size) - goto buffer_done; lpfc_decode_firmware_rev(vport->phba, fwrev, 0); - scnprintf(tmp, sizeof(tmp), " FV%s", fwrev); - if (strlcat(symbol, tmp, size) >= size) - goto buffer_done; - - scnprintf(tmp, sizeof(tmp), " DV%s", lpfc_release_version); - if (strlcat(symbol, tmp, size) >= size) - goto buffer_done; - - scnprintf(tmp, sizeof(tmp), " HN:%s", vport->phba->os_host_name); - if (strlcat(symbol, tmp, size) >= size) - goto buffer_done; + memset(symbol, 0, size); /* Note :- OS name is "Linux" */ - scnprintf(tmp, sizeof(tmp), " OS:%s", init_utsname()->sysname); - strlcat(symbol, tmp, size); - -buffer_done: - return strnlen(symbol, size); - + return scnprintf(symbol, size, "Emulex %s FV%s DV%s HN:%s OS:%s", + vport->phba->ModelName, fwrev, + lpfc_release_version, vport->phba->os_host_name, + init_utsname()->sysname); } static uint32_t From 22d4cbf6f7509f0aab58d60d488a0082916a2fb0 Mon Sep 17 00:00:00 2001 From: Ian Bridges Date: Wed, 29 Jul 2026 09:46:15 -0500 Subject: [PATCH 0571/1328] scsi: lpfc: Replace strlcat() with seq_buf in lpfc_rx_monitor_report() In preparation for removing the strlcat() API[1], replace its use in lpfc_rx_monitor_report(). The function accumulates one line per ring entry, which is what seq_buf is for. seq_buf tracks the write position, so the per entry strlen() rescans of the destination are gone. Each record is still formatted into the tmp buffer. seq_buf_puts() appends it only when it fits whole, so the output keeps ending at the last complete record. The loop still stops on overflow without consuming the current entry, and the returned count and the ring head keep their old meaning. The produced bytes are unchanged. Link: https://github.com/KSPP/linux/issues/370 [1] Signed-off-by: Ian Bridges Link: https://patch.msgid.link/20260729144617.1388646-4-icb@fastmail.org Reviewed-by: Nigel Kirkland Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/lpfc/lpfc_sli.c | 43 ++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/drivers/scsi/lpfc/lpfc_sli.c b/drivers/scsi/lpfc/lpfc_sli.c index 62a30a92b792..cfa4371169f1 100644 --- a/drivers/scsi/lpfc/lpfc_sli.c +++ b/drivers/scsi/lpfc/lpfc_sli.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -8109,17 +8110,18 @@ u32 lpfc_rx_monitor_report(struct lpfc_hba *phba, u32 cnt = 0; char tmp[DBG_LOG_STR_SZ] = {0}; bool log_to_kmsg = (!buf || !buf_len) ? true : false; + struct seq_buf s; if (!log_to_kmsg) { /* clear the buffer to be sure */ memset(buf, 0, buf_len); - scnprintf(buf, buf_len, "\t%-16s%-16s%-16s%-16s%-8s%-8s%-8s" - "%-8s%-8s%-8s%-16s\n", - "MaxBPI", "Tot_Data_CMF", - "Tot_Data_Cmd", "Tot_Data_Cmpl", - "Lat(us)", "Avg_IO", "Max_IO", "Bsy", - "IO_cnt", "Info", "BWutil(ms)"); + seq_buf_init(&s, buf, buf_len); + seq_buf_printf(&s, "\t%-16s%-16s%-16s%-16s%-8s%-8s%-8s%-8s%-8s%-8s%-16s\n", + "MaxBPI", "Tot_Data_CMF", + "Tot_Data_Cmd", "Tot_Data_Cmpl", + "Lat(us)", "Avg_IO", "Max_IO", "Bsy", + "IO_cnt", "Info", "BWutil(ms)"); } /* Needs to be _irq because record is called from timer interrupt @@ -8131,24 +8133,27 @@ u32 lpfc_rx_monitor_report(struct lpfc_hba *phba, /* Read out this entry's data. */ if (!log_to_kmsg) { - /* If !log_to_kmsg, then store to buf. */ + /* + * Drop a record whole if it does not fit, without + * consuming its ring entry. + */ scnprintf(tmp, sizeof(tmp), - "%03d:\t%-16llu%-16llu%-16llu%-16llu%-8llu" - "%-8llu%-8llu%-8u%-8u%-8u%u(%u)\n", - *head_idx, entry->max_bytes_per_interval, - entry->cmf_bytes, entry->total_bytes, - entry->rcv_bytes, entry->avg_io_latency, - entry->avg_io_size, entry->max_read_cnt, + "%03d:\t%-16llu%-16llu%-16llu%-16llu%-8llu%-8llu%-8llu%-8u%-8u%-8u%u(%u)\n", + *head_idx, + entry->max_bytes_per_interval, + entry->cmf_bytes, + entry->total_bytes, + entry->rcv_bytes, + entry->avg_io_latency, + entry->avg_io_size, + entry->max_read_cnt, entry->cmf_busy, entry->io_cnt, - entry->cmf_info, entry->timer_utilization, + entry->cmf_info, + entry->timer_utilization, entry->timer_interval); - /* Check for buffer overflow */ - if ((strlen(buf) + strlen(tmp)) >= buf_len) + if (seq_buf_puts(&s, tmp) < 0) break; - - /* Append entry's data to buffer */ - strlcat(buf, tmp, buf_len); } else { lpfc_printf_log(phba, KERN_INFO, LOG_CGN_MGMT, "4410 %02u: MBPI %llu Xmit %llu " From 4832a60e0a7818400afffa392b74776d084ddf4a Mon Sep 17 00:00:00 2001 From: Ian Bridges Date: Wed, 29 Jul 2026 09:46:16 -0500 Subject: [PATCH 0572/1328] scsi: lpfc: Replace strlcat() with seq_buf in the debugfs dump helpers In preparation for removing the strlcat() API[1], replace its uses in lpfc_debugfs_multixripools_data(), lpfc_debugfs_scsistat_data() and lpfc_debugfs_hdwqstat_data(). Each helper accumulates a variable number of lines into the debugfs buffer, which is what seq_buf is for. The intermediate tmp buffers and the per fragment overflow checks become unnecessary. Once a seq_buf overflows, later writes to it do nothing, so dropping the early exits does not change the produced bytes. Each loop that appends keeps one seq_buf_has_overflowed() exit, so a full buffer stops the iteration. lpfc_debugfs_multixripools_data() and lpfc_debugfs_hdwqstat_data() append to whatever the buffer already holds, so their seq_buf is anchored at the current end of the string. All three helpers keep returning strnlen() because seq_buf_used() reports the full buffer size after an overflow. Link: https://github.com/KSPP/linux/issues/370 [1] Signed-off-by: Ian Bridges Link: https://patch.msgid.link/20260729144617.1388646-5-icb@fastmail.org Reviewed-by: Nigel Kirkland Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/lpfc/lpfc_debugfs.c | 198 +++++++++++++------------------ 1 file changed, 85 insertions(+), 113 deletions(-) diff --git a/drivers/scsi/lpfc/lpfc_debugfs.c b/drivers/scsi/lpfc/lpfc_debugfs.c index 052023fc1733..30e3cc050aaa 100644 --- a/drivers/scsi/lpfc/lpfc_debugfs.c +++ b/drivers/scsi/lpfc/lpfc_debugfs.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -462,7 +463,8 @@ lpfc_debugfs_multixripools_data(struct lpfc_hba *phba, char *buf, int size) struct lpfc_pvt_pool *pvt_pool; struct lpfc_pbl_pool *pbl_pool; u32 txcmplq_cnt; - char tmp[LPFC_DEBUG_OUT_LINE_SZ] = {0}; + size_t used; + struct seq_buf s; if (phba->sli_rev != LPFC_SLI_REV4) return 0; @@ -475,6 +477,9 @@ lpfc_debugfs_multixripools_data(struct lpfc_hba *phba, char *buf, int size) return i; } + used = strnlen(buf, size); + seq_buf_init(&s, buf + used, size - used); + /* * Pbl: Current number of free XRIs in public pool * Pvt: Current number of free XRIs in private pool @@ -484,10 +489,8 @@ lpfc_debugfs_multixripools_data(struct lpfc_hba *phba, char *buf, int size) * pbl_empty: Incremented by 1 when all pbl_pool are empty during * IO submission */ - scnprintf(tmp, sizeof(tmp), - "HWQ: Pbl Pvt Busy HWM | pvt_empty pbl_empty "); - if (strlcat(buf, tmp, size) >= size) - return strnlen(buf, size); + seq_buf_printf(&s, + "HWQ: Pbl Pvt Busy HWM | pvt_empty pbl_empty "); #ifdef LPFC_MXP_STAT /* @@ -501,25 +504,17 @@ lpfc_debugfs_multixripools_data(struct lpfc_hba *phba, char *buf, int size) * othPbl_hit: Incremented by 1 if successfully get a batch of XRI from * other pbl_pool */ - scnprintf(tmp, sizeof(tmp), - "MAXH above_lmt below_lmt locPbl_hit othPbl_hit"); - if (strlcat(buf, tmp, size) >= size) - return strnlen(buf, size); + seq_buf_printf(&s, "MAXH above_lmt below_lmt locPbl_hit othPbl_hit"); /* * sPbl: snapshot of Pbl 15 sec after stat gets cleared * sPvt: snapshot of Pvt 15 sec after stat gets cleared * sBusy: snapshot of Busy 15 sec after stat gets cleared */ - scnprintf(tmp, sizeof(tmp), - " | sPbl sPvt sBusy"); - if (strlcat(buf, tmp, size) >= size) - return strnlen(buf, size); + seq_buf_printf(&s, " | sPbl sPvt sBusy"); #endif - scnprintf(tmp, sizeof(tmp), "\n"); - if (strlcat(buf, tmp, size) >= size) - return strnlen(buf, size); + seq_buf_printf(&s, "\n"); hwq_count = phba->cfg_hdw_queue; for (i = 0; i < hwq_count; i++) { @@ -531,36 +526,32 @@ lpfc_debugfs_multixripools_data(struct lpfc_hba *phba, char *buf, int size) pvt_pool = &multixri_pool->pvt_pool; txcmplq_cnt = qp->io_wq->pring->txcmplq_cnt; - scnprintf(tmp, sizeof(tmp), - "%03d: %4d %4d %4d %4d | %10d %10d ", - i, pbl_pool->count, pvt_pool->count, - txcmplq_cnt, pvt_pool->high_watermark, - qp->empty_io_bufs, multixri_pool->pbl_empty_count); - if (strlcat(buf, tmp, size) >= size) - break; + seq_buf_printf(&s, + "%03d: %4d %4d %4d %4d | %10d %10d ", + i, pbl_pool->count, pvt_pool->count, + txcmplq_cnt, pvt_pool->high_watermark, + qp->empty_io_bufs, + multixri_pool->pbl_empty_count); #ifdef LPFC_MXP_STAT - scnprintf(tmp, sizeof(tmp), - "%4d %10d %10d %10d %10d", - multixri_pool->stat_max_hwm, - multixri_pool->above_limit_count, - multixri_pool->below_limit_count, - multixri_pool->local_pbl_hit_count, - multixri_pool->other_pbl_hit_count); - if (strlcat(buf, tmp, size) >= size) - break; + seq_buf_printf(&s, + "%4d %10d %10d %10d %10d", + multixri_pool->stat_max_hwm, + multixri_pool->above_limit_count, + multixri_pool->below_limit_count, + multixri_pool->local_pbl_hit_count, + multixri_pool->other_pbl_hit_count); - scnprintf(tmp, sizeof(tmp), - " | %4d %4d %5d", - multixri_pool->stat_pbl_count, - multixri_pool->stat_pvt_count, - multixri_pool->stat_busy_count); - if (strlcat(buf, tmp, size) >= size) - break; + seq_buf_printf(&s, + " | %4d %4d %5d", + multixri_pool->stat_pbl_count, + multixri_pool->stat_pvt_count, + multixri_pool->stat_busy_count); #endif - scnprintf(tmp, sizeof(tmp), "\n"); - if (strlcat(buf, tmp, size) >= size) + seq_buf_printf(&s, "\n"); + + if (seq_buf_has_overflowed(&s)) break; } return strnlen(buf, size); @@ -1261,13 +1252,14 @@ lpfc_debugfs_scsistat_data(struct lpfc_vport *vport, char *buf, int size) u64 data1, data2, data3; u64 tot, totin, totout; int i; - char tmp[LPFC_MAX_SCSI_INFO_TMP_LEN] = {0}; + struct seq_buf s; if (!(vport->cfg_enable_fc4_type & LPFC_ENABLE_FCP) || (phba->sli_rev != LPFC_SLI_REV4)) return 0; - scnprintf(buf, size, "SCSI HDWQ Statistics\n"); + seq_buf_init(&s, buf, size); + seq_buf_printf(&s, "SCSI HDWQ Statistics\n"); totin = 0; totout = 0; @@ -1280,21 +1272,18 @@ lpfc_debugfs_scsistat_data(struct lpfc_vport *vport, char *buf, int size) data3 = cstat->control_requests; totout += (data1 + data2 + data3); - scnprintf(tmp, sizeof(tmp), "HDWQ (%d): Rd %016llx Wr %016llx " - "IO %016llx ", i, data1, data2, data3); - if (strlcat(buf, tmp, size) >= size) - goto buffer_done; + seq_buf_printf(&s, "HDWQ (%d): Rd %016llx Wr %016llx IO %016llx ", + i, data1, data2, data3); - scnprintf(tmp, sizeof(tmp), "Cmpl %016llx OutIO %016llx\n", - tot, ((data1 + data2 + data3) - tot)); - if (strlcat(buf, tmp, size) >= size) - goto buffer_done; + seq_buf_printf(&s, "Cmpl %016llx OutIO %016llx\n", + tot, ((data1 + data2 + data3) - tot)); + + if (seq_buf_has_overflowed(&s)) + break; } - scnprintf(tmp, sizeof(tmp), "Total FCP Cmpl %016llx Issue %016llx " - "OutIO %016llx\n", totin, totout, totout - totin); - strlcat(buf, tmp, size); + seq_buf_printf(&s, "Total FCP Cmpl %016llx Issue %016llx OutIO %016llx\n", + totin, totout, totout - totin); -buffer_done: len = strnlen(buf, size); return len; @@ -1704,28 +1693,23 @@ lpfc_debugfs_hdwqstat_data(struct lpfc_vport *vport, char *buf, int size) uint32_t tot_xmt; uint32_t tot_rcv; uint32_t tot_cmpl; - char tmp[LPFC_MAX_SCSI_INFO_TMP_LEN] = {0}; + size_t used = strnlen(buf, size); + struct seq_buf s; - scnprintf(tmp, sizeof(tmp), "HDWQ Stats:\n\n"); - if (strlcat(buf, tmp, size) >= size) - goto buffer_done; + seq_buf_init(&s, buf + used, size - used); - scnprintf(tmp, sizeof(tmp), "(NVME Accounting: %s) ", - (phba->hdwqstat_on & - (LPFC_CHECK_NVME_IO | LPFC_CHECK_NVMET_IO) ? - "Enabled" : "Disabled")); - if (strlcat(buf, tmp, size) >= size) - goto buffer_done; + seq_buf_printf(&s, "HDWQ Stats:\n\n"); - scnprintf(tmp, sizeof(tmp), "(SCSI Accounting: %s) ", - (phba->hdwqstat_on & LPFC_CHECK_SCSI_IO ? - "Enabled" : "Disabled")); - if (strlcat(buf, tmp, size) >= size) - goto buffer_done; + seq_buf_printf(&s, "(NVME Accounting: %s) ", + (phba->hdwqstat_on & + (LPFC_CHECK_NVME_IO | LPFC_CHECK_NVMET_IO) ? + "Enabled" : "Disabled")); - scnprintf(tmp, sizeof(tmp), "\n\n"); - if (strlcat(buf, tmp, size) >= size) - goto buffer_done; + seq_buf_printf(&s, "(SCSI Accounting: %s) ", + (phba->hdwqstat_on & LPFC_CHECK_SCSI_IO ? + "Enabled" : "Disabled")); + + seq_buf_printf(&s, "\n\n"); for (i = 0; i < phba->cfg_hdw_queue; i++) { tot_rcv = 0; @@ -1744,62 +1728,50 @@ lpfc_debugfs_hdwqstat_data(struct lpfc_vport *vport, char *buf, int size) !c_stat->rcv_io) continue; - if (!tot_xmt && !tot_cmpl && !tot_rcv) { - /* Print HDWQ string only the first time */ - scnprintf(tmp, sizeof(tmp), "[HDWQ %d]:\t", i); - if (strlcat(buf, tmp, size) >= size) - goto buffer_done; - } + /* Print HDWQ string only the first time */ + if (!tot_xmt && !tot_cmpl && !tot_rcv) + seq_buf_printf(&s, "[HDWQ %d]:\t", i); tot_xmt += c_stat->xmt_io; tot_cmpl += c_stat->cmpl_io; if (phba->nvmet_support) tot_rcv += c_stat->rcv_io; - scnprintf(tmp, sizeof(tmp), "| [CPU %d]: ", j); - if (strlcat(buf, tmp, size) >= size) - goto buffer_done; + seq_buf_printf(&s, "| [CPU %d]: ", j); - if (phba->nvmet_support) { - scnprintf(tmp, sizeof(tmp), - "XMT 0x%x CMPL 0x%x RCV 0x%x |", - c_stat->xmt_io, c_stat->cmpl_io, - c_stat->rcv_io); - if (strlcat(buf, tmp, size) >= size) - goto buffer_done; - } else { - scnprintf(tmp, sizeof(tmp), - "XMT 0x%x CMPL 0x%x |", - c_stat->xmt_io, c_stat->cmpl_io); - if (strlcat(buf, tmp, size) >= size) - goto buffer_done; - } + if (phba->nvmet_support) + seq_buf_printf(&s, + "XMT 0x%x CMPL 0x%x RCV 0x%x |", + c_stat->xmt_io, c_stat->cmpl_io, + c_stat->rcv_io); + else + seq_buf_printf(&s, + "XMT 0x%x CMPL 0x%x |", + c_stat->xmt_io, c_stat->cmpl_io); + + if (seq_buf_has_overflowed(&s)) + break; } + if (seq_buf_has_overflowed(&s)) + break; + /* Check if nothing to display */ if (!tot_xmt && !tot_cmpl && !tot_rcv) continue; - scnprintf(tmp, sizeof(tmp), "\t->\t[HDWQ Total: "); - if (strlcat(buf, tmp, size) >= size) - goto buffer_done; + seq_buf_printf(&s, "\t->\t[HDWQ Total: "); - if (phba->nvmet_support) { - scnprintf(tmp, sizeof(tmp), - "XMT 0x%x CMPL 0x%x RCV 0x%x]\n\n", - tot_xmt, tot_cmpl, tot_rcv); - if (strlcat(buf, tmp, size) >= size) - goto buffer_done; - } else { - scnprintf(tmp, sizeof(tmp), - "XMT 0x%x CMPL 0x%x]\n\n", - tot_xmt, tot_cmpl); - if (strlcat(buf, tmp, size) >= size) - goto buffer_done; - } + if (phba->nvmet_support) + seq_buf_printf(&s, + "XMT 0x%x CMPL 0x%x RCV 0x%x]\n\n", + tot_xmt, tot_cmpl, tot_rcv); + else + seq_buf_printf(&s, + "XMT 0x%x CMPL 0x%x]\n\n", + tot_xmt, tot_cmpl); } -buffer_done: len = strnlen(buf, size); return len; } From 36b6dcb2b7463b290d63c83d82bec8065a1a93de Mon Sep 17 00:00:00 2001 From: Ian Bridges Date: Wed, 29 Jul 2026 09:46:17 -0500 Subject: [PATCH 0573/1328] scsi: lpfc: Replace strlcat() with sysfs_emit_at() in the sysfs show functions In preparation for removing the strlcat() API[1], replace its uses in lpfc_cmf_info_show(), lpfc_nvme_info_show() and lpfc_scsi_stat_show(). The three functions build sysfs attribute output, and sysfs_emit_at() is the designated helper for that. The single write paths become sysfs_emit(), the offset zero form of the same helper. Each intermediate tmp buffer and its per fragment overflow check become unnecessary. Once the page is full, sysfs_emit_at() writes nothing more, so dropping the early exits does not change the produced bytes. Each loop that appends keeps one exit, so a full page stops the iteration. In lpfc_nvme_info_show() the exit also releases the fc_nodes_list_lock as it did before. The unlock_buf_done label loses its last user and is removed. The old code capped every fragment at LPFC_MAX_INFO_TMP_LEN or LPFC_MAX_SCSI_INFO_TMP_LEN bytes before appending it. The replacement formats each fragment directly into the page, so a fragment longer than its old tmp buffer is no longer truncated when the page has room for it. Both macros lose their last user and are removed. The running length that sysfs_emit_at() maintains equals the length that the removed strnlen() calls computed, so the "Could be more info" overflow markers keep their trigger condition. Link: https://github.com/KSPP/linux/issues/370 [1] Signed-off-by: Ian Bridges Link: https://patch.msgid.link/20260729144617.1388646-6-icb@fastmail.org Reviewed-by: Nigel Kirkland Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/lpfc/lpfc_attr.c | 533 ++++++++++++++-------------------- drivers/scsi/lpfc/lpfc_scsi.h | 3 - 2 files changed, 219 insertions(+), 317 deletions(-) diff --git a/drivers/scsi/lpfc/lpfc_attr.c b/drivers/scsi/lpfc/lpfc_attr.c index f4e8164b94ab..bb5c84f0b787 100644 --- a/drivers/scsi/lpfc/lpfc_attr.c +++ b/drivers/scsi/lpfc/lpfc_attr.c @@ -57,7 +57,6 @@ #define LPFC_MIN_DEVLOSS_TMO 1 #define LPFC_MAX_DEVLOSS_TMO 255 -#define LPFC_MAX_INFO_TMP_LEN 100 #define LPFC_INFO_MORE_STR "\nCould be more info...\n" /* * Write key size should be multiple of 4. If write key is changed @@ -126,133 +125,121 @@ lpfc_cmf_info_show(struct device *dev, struct device_attribute *attr, int len = 0; int cpu; u64 rcv, total; - char tmp[LPFC_MAX_INFO_TMP_LEN] = {0}; if (phba->cgn_i) cp = (struct lpfc_cgn_info *)phba->cgn_i->virt; - scnprintf(tmp, sizeof(tmp), - "Congestion Mgmt Info: E2Eattr %d Ver %d " - "CMF %d cnt %d\n", - phba->sli4_hba.pc_sli4_params.mi_cap, - cp ? cp->cgn_info_version : 0, - phba->sli4_hba.pc_sli4_params.cmf, phba->cmf_timer_cnt); - - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len = strnlen(buf, PAGE_SIZE); + len += sysfs_emit_at(buf, len, + "Congestion Mgmt Info: E2Eattr %d Ver %d CMF %d cnt %d\n", + phba->sli4_hba.pc_sli4_params.mi_cap, + cp ? cp->cgn_info_version : 0, + phba->sli4_hba.pc_sli4_params.cmf, + phba->cmf_timer_cnt); if (!phba->sli4_hba.pc_sli4_params.cmf) goto buffer_done; switch (phba->cgn_init_reg_signal) { case EDC_CG_SIG_WARN_ONLY: - scnprintf(tmp, sizeof(tmp), - "Register: Init: Signal:WARN "); + len += sysfs_emit_at(buf, len, + "Register: Init: Signal:WARN "); break; case EDC_CG_SIG_WARN_ALARM: - scnprintf(tmp, sizeof(tmp), - "Register: Init: Signal:WARN|ALARM "); + len += sysfs_emit_at(buf, len, + "Register: Init: Signal:WARN|ALARM "); break; default: - scnprintf(tmp, sizeof(tmp), - "Register: Init: Signal:NONE "); + len += sysfs_emit_at(buf, len, + "Register: Init: Signal:NONE "); break; } - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; switch (phba->cgn_init_reg_fpin) { case LPFC_CGN_FPIN_WARN: - scnprintf(tmp, sizeof(tmp), - "FPIN:WARN\n"); + len += sysfs_emit_at(buf, len, "FPIN:WARN\n"); break; case LPFC_CGN_FPIN_ALARM: - scnprintf(tmp, sizeof(tmp), - "FPIN:ALARM\n"); + len += sysfs_emit_at(buf, len, "FPIN:ALARM\n"); break; case LPFC_CGN_FPIN_BOTH: - scnprintf(tmp, sizeof(tmp), - "FPIN:WARN|ALARM\n"); + len += sysfs_emit_at(buf, len, "FPIN:WARN|ALARM\n"); break; default: - scnprintf(tmp, sizeof(tmp), - "FPIN:NONE\n"); + len += sysfs_emit_at(buf, len, "FPIN:NONE\n"); break; } - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; switch (phba->cgn_reg_signal) { case EDC_CG_SIG_WARN_ONLY: - scnprintf(tmp, sizeof(tmp), - " Current: Signal:WARN "); + len += sysfs_emit_at(buf, len, + " Current: Signal:WARN "); break; case EDC_CG_SIG_WARN_ALARM: - scnprintf(tmp, sizeof(tmp), - " Current: Signal:WARN|ALARM "); + len += sysfs_emit_at(buf, len, + " Current: Signal:WARN|ALARM "); break; default: - scnprintf(tmp, sizeof(tmp), - " Current: Signal:NONE "); + len += sysfs_emit_at(buf, len, + " Current: Signal:NONE "); break; } - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; switch (phba->cgn_reg_fpin) { case LPFC_CGN_FPIN_WARN: - scnprintf(tmp, sizeof(tmp), - "FPIN:WARN ACQEcnt:%d\n", phba->cgn_acqe_cnt); + len += sysfs_emit_at(buf, len, + "FPIN:WARN ACQEcnt:%d\n", + phba->cgn_acqe_cnt); break; case LPFC_CGN_FPIN_ALARM: - scnprintf(tmp, sizeof(tmp), - "FPIN:ALARM ACQEcnt:%d\n", phba->cgn_acqe_cnt); + len += sysfs_emit_at(buf, len, + "FPIN:ALARM ACQEcnt:%d\n", + phba->cgn_acqe_cnt); break; case LPFC_CGN_FPIN_BOTH: - scnprintf(tmp, sizeof(tmp), - "FPIN:WARN|ALARM ACQEcnt:%d\n", phba->cgn_acqe_cnt); + len += sysfs_emit_at(buf, len, + "FPIN:WARN|ALARM ACQEcnt:%d\n", + phba->cgn_acqe_cnt); break; default: - scnprintf(tmp, sizeof(tmp), - "FPIN:NONE ACQEcnt:%d\n", phba->cgn_acqe_cnt); + len += sysfs_emit_at(buf, len, + "FPIN:NONE ACQEcnt:%d\n", + phba->cgn_acqe_cnt); break; } - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; if (phba->cmf_active_mode != phba->cgn_p.cgn_param_mode) { switch (phba->cmf_active_mode) { case LPFC_CFG_OFF: - scnprintf(tmp, sizeof(tmp), "Active: Mode:Off\n"); + len += sysfs_emit_at(buf, len, "Active: Mode:Off\n"); break; case LPFC_CFG_MANAGED: - scnprintf(tmp, sizeof(tmp), "Active: Mode:Managed\n"); + len += sysfs_emit_at(buf, len, + "Active: Mode:Managed\n"); break; case LPFC_CFG_MONITOR: - scnprintf(tmp, sizeof(tmp), "Active: Mode:Monitor\n"); + len += sysfs_emit_at(buf, len, + "Active: Mode:Monitor\n"); break; default: - scnprintf(tmp, sizeof(tmp), "Active: Mode:Unknown\n"); + len += sysfs_emit_at(buf, len, + "Active: Mode:Unknown\n"); } - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; } switch (phba->cgn_p.cgn_param_mode) { case LPFC_CFG_OFF: - scnprintf(tmp, sizeof(tmp), "Config: Mode:Off "); + len += sysfs_emit_at(buf, len, "Config: Mode:Off "); break; case LPFC_CFG_MANAGED: - scnprintf(tmp, sizeof(tmp), "Config: Mode:Managed "); + len += sysfs_emit_at(buf, len, "Config: Mode:Managed "); break; case LPFC_CFG_MONITOR: - scnprintf(tmp, sizeof(tmp), "Config: Mode:Monitor "); + len += sysfs_emit_at(buf, len, "Config: Mode:Monitor "); break; default: - scnprintf(tmp, sizeof(tmp), "Config: Mode:Unknown "); + len += sysfs_emit_at(buf, len, "Config: Mode:Unknown "); } - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; total = 0; rcv = 0; @@ -262,24 +249,18 @@ lpfc_cmf_info_show(struct device *dev, struct device_attribute *attr, rcv += atomic64_read(&cgs->rcv_bytes); } - scnprintf(tmp, sizeof(tmp), - "IObusy:%d Info:%d Bytes: Rcv:x%llx Total:x%llx\n", - atomic_read(&phba->cmf_busy), - phba->cmf_active_info, rcv, total); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "IObusy:%d Info:%d Bytes: Rcv:x%llx Total:x%llx\n", + atomic_read(&phba->cmf_busy), + phba->cmf_active_info, rcv, total); - scnprintf(tmp, sizeof(tmp), - "Port_speed:%d Link_byte_cnt:%ld " - "Max_byte_per_interval:%ld\n", - lpfc_sli_port_speed_get(phba), - (unsigned long)phba->cmf_link_byte_count, - (unsigned long)phba->cmf_max_bytes_per_interval); - strlcat(buf, tmp, PAGE_SIZE); + len += sysfs_emit_at(buf, len, + "Port_speed:%d Link_byte_cnt:%ld Max_byte_per_interval:%ld\n", + lpfc_sli_port_speed_get(phba), + (unsigned long)phba->cmf_link_byte_count, + (unsigned long)phba->cmf_max_bytes_per_interval); buffer_done: - len = strnlen(buf, PAGE_SIZE); - if (unlikely(len >= (PAGE_SIZE - 1))) { lpfc_printf_log(phba, KERN_INFO, LOG_CGN_MGMT, "6312 Catching potential buffer " @@ -480,17 +461,19 @@ lpfc_nvme_info_show(struct device *dev, struct device_attribute *attr, char *statep; int i; int len = 0; - char tmp[LPFC_MAX_INFO_TMP_LEN] = {0}; if (!(vport->cfg_enable_fc4_type & LPFC_ENABLE_NVME)) { - len = scnprintf(buf, PAGE_SIZE, "NVME Disabled\n"); + len = sysfs_emit(buf, "NVME Disabled\n"); return len; } + + len = strnlen(buf, PAGE_SIZE); + if (phba->nvmet_support) { if (!phba->targetport) { - len = scnprintf(buf, PAGE_SIZE, - "NVME Target: x%llx is not allocated\n", - wwn_to_u64(vport->fc_portname.u.wwn)); + len = sysfs_emit(buf, + "NVME Target: x%llx is not allocated\n", + wwn_to_u64(vport->fc_portname.u.wwn)); return len; } /* Port state is only one of two values for now. */ @@ -498,167 +481,131 @@ lpfc_nvme_info_show(struct device *dev, struct device_attribute *attr, statep = "REGISTERED"; else statep = "INIT"; - scnprintf(tmp, sizeof(tmp), - "NVME Target Enabled State %s\n", - statep); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "NVME Target Enabled State %s\n", + statep); - scnprintf(tmp, sizeof(tmp), - "%s%d WWPN x%llx WWNN x%llx DID x%06x\n", - "NVME Target: lpfc", - phba->brd_no, - wwn_to_u64(vport->fc_portname.u.wwn), - wwn_to_u64(vport->fc_nodename.u.wwn), - phba->targetport->port_id); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "%s%d WWPN x%llx WWNN x%llx DID x%06x\n", + "NVME Target: lpfc", + phba->brd_no, + wwn_to_u64(vport->fc_portname.u.wwn), + wwn_to_u64(vport->fc_nodename.u.wwn), + phba->targetport->port_id); - if (strlcat(buf, "\nNVME Target: Statistics\n", PAGE_SIZE) - >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, "\nNVME Target: Statistics\n"); tgtp = (struct lpfc_nvmet_tgtport *)phba->targetport->private; - scnprintf(tmp, sizeof(tmp), - "LS: Rcv %08x Drop %08x Abort %08x\n", - atomic_read(&tgtp->rcv_ls_req_in), - atomic_read(&tgtp->rcv_ls_req_drop), - atomic_read(&tgtp->xmt_ls_abort)); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "LS: Rcv %08x Drop %08x Abort %08x\n", + atomic_read(&tgtp->rcv_ls_req_in), + atomic_read(&tgtp->rcv_ls_req_drop), + atomic_read(&tgtp->xmt_ls_abort)); if (atomic_read(&tgtp->rcv_ls_req_in) != atomic_read(&tgtp->rcv_ls_req_out)) { - scnprintf(tmp, sizeof(tmp), - "Rcv LS: in %08x != out %08x\n", - atomic_read(&tgtp->rcv_ls_req_in), - atomic_read(&tgtp->rcv_ls_req_out)); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "Rcv LS: in %08x != out %08x\n", + atomic_read(&tgtp->rcv_ls_req_in), + atomic_read(&tgtp->rcv_ls_req_out)); } - scnprintf(tmp, sizeof(tmp), - "LS: Xmt %08x Drop %08x Cmpl %08x\n", - atomic_read(&tgtp->xmt_ls_rsp), - atomic_read(&tgtp->xmt_ls_drop), - atomic_read(&tgtp->xmt_ls_rsp_cmpl)); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "LS: Xmt %08x Drop %08x Cmpl %08x\n", + atomic_read(&tgtp->xmt_ls_rsp), + atomic_read(&tgtp->xmt_ls_drop), + atomic_read(&tgtp->xmt_ls_rsp_cmpl)); - scnprintf(tmp, sizeof(tmp), - "LS: RSP Abort %08x xb %08x Err %08x\n", - atomic_read(&tgtp->xmt_ls_rsp_aborted), - atomic_read(&tgtp->xmt_ls_rsp_xb_set), - atomic_read(&tgtp->xmt_ls_rsp_error)); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "LS: RSP Abort %08x xb %08x Err %08x\n", + atomic_read(&tgtp->xmt_ls_rsp_aborted), + atomic_read(&tgtp->xmt_ls_rsp_xb_set), + atomic_read(&tgtp->xmt_ls_rsp_error)); - scnprintf(tmp, sizeof(tmp), - "FCP: Rcv %08x Defer %08x Release %08x " - "Drop %08x\n", - atomic_read(&tgtp->rcv_fcp_cmd_in), - atomic_read(&tgtp->rcv_fcp_cmd_defer), - atomic_read(&tgtp->xmt_fcp_release), - atomic_read(&tgtp->rcv_fcp_cmd_drop)); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "FCP: Rcv %08x Defer %08x Release %08x Drop %08x\n", + atomic_read(&tgtp->rcv_fcp_cmd_in), + atomic_read(&tgtp->rcv_fcp_cmd_defer), + atomic_read(&tgtp->xmt_fcp_release), + atomic_read(&tgtp->rcv_fcp_cmd_drop)); if (atomic_read(&tgtp->rcv_fcp_cmd_in) != atomic_read(&tgtp->rcv_fcp_cmd_out)) { - scnprintf(tmp, sizeof(tmp), - "Rcv FCP: in %08x != out %08x\n", - atomic_read(&tgtp->rcv_fcp_cmd_in), - atomic_read(&tgtp->rcv_fcp_cmd_out)); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "Rcv FCP: in %08x != out %08x\n", + atomic_read(&tgtp->rcv_fcp_cmd_in), + atomic_read(&tgtp->rcv_fcp_cmd_out)); } - scnprintf(tmp, sizeof(tmp), - "FCP Rsp: RD %08x rsp %08x WR %08x rsp %08x " - "drop %08x\n", - atomic_read(&tgtp->xmt_fcp_read), - atomic_read(&tgtp->xmt_fcp_read_rsp), - atomic_read(&tgtp->xmt_fcp_write), - atomic_read(&tgtp->xmt_fcp_rsp), - atomic_read(&tgtp->xmt_fcp_drop)); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "FCP Rsp: RD %08x rsp %08x WR %08x rsp %08x drop %08x\n", + atomic_read(&tgtp->xmt_fcp_read), + atomic_read(&tgtp->xmt_fcp_read_rsp), + atomic_read(&tgtp->xmt_fcp_write), + atomic_read(&tgtp->xmt_fcp_rsp), + atomic_read(&tgtp->xmt_fcp_drop)); - scnprintf(tmp, sizeof(tmp), - "FCP Rsp Cmpl: %08x err %08x drop %08x\n", - atomic_read(&tgtp->xmt_fcp_rsp_cmpl), - atomic_read(&tgtp->xmt_fcp_rsp_error), - atomic_read(&tgtp->xmt_fcp_rsp_drop)); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "FCP Rsp Cmpl: %08x err %08x drop %08x\n", + atomic_read(&tgtp->xmt_fcp_rsp_cmpl), + atomic_read(&tgtp->xmt_fcp_rsp_error), + atomic_read(&tgtp->xmt_fcp_rsp_drop)); - scnprintf(tmp, sizeof(tmp), - "FCP Rsp Abort: %08x xb %08x xricqe %08x\n", - atomic_read(&tgtp->xmt_fcp_rsp_aborted), - atomic_read(&tgtp->xmt_fcp_rsp_xb_set), - atomic_read(&tgtp->xmt_fcp_xri_abort_cqe)); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "FCP Rsp Abort: %08x xb %08x xricqe %08x\n", + atomic_read(&tgtp->xmt_fcp_rsp_aborted), + atomic_read(&tgtp->xmt_fcp_rsp_xb_set), + atomic_read(&tgtp->xmt_fcp_xri_abort_cqe)); - scnprintf(tmp, sizeof(tmp), - "ABORT: Xmt %08x Cmpl %08x\n", - atomic_read(&tgtp->xmt_fcp_abort), - atomic_read(&tgtp->xmt_fcp_abort_cmpl)); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "ABORT: Xmt %08x Cmpl %08x\n", + atomic_read(&tgtp->xmt_fcp_abort), + atomic_read(&tgtp->xmt_fcp_abort_cmpl)); - scnprintf(tmp, sizeof(tmp), - "ABORT: Sol %08x Usol %08x Err %08x Cmpl %08x\n", - atomic_read(&tgtp->xmt_abort_sol), - atomic_read(&tgtp->xmt_abort_unsol), - atomic_read(&tgtp->xmt_abort_rsp), - atomic_read(&tgtp->xmt_abort_rsp_error)); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "ABORT: Sol %08x Usol %08x Err %08x Cmpl %08x\n", + atomic_read(&tgtp->xmt_abort_sol), + atomic_read(&tgtp->xmt_abort_unsol), + atomic_read(&tgtp->xmt_abort_rsp), + atomic_read(&tgtp->xmt_abort_rsp_error)); - scnprintf(tmp, sizeof(tmp), - "DELAY: ctx %08x fod %08x wqfull %08x\n", - atomic_read(&tgtp->defer_ctx), - atomic_read(&tgtp->defer_fod), - atomic_read(&tgtp->defer_wqfull)); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "DELAY: ctx %08x fod %08x wqfull %08x\n", + atomic_read(&tgtp->defer_ctx), + atomic_read(&tgtp->defer_fod), + atomic_read(&tgtp->defer_wqfull)); /* Calculate outstanding IOs */ tot = atomic_read(&tgtp->rcv_fcp_cmd_drop); tot += atomic_read(&tgtp->xmt_fcp_release); tot = atomic_read(&tgtp->rcv_fcp_cmd_in) - tot; - scnprintf(tmp, sizeof(tmp), - "IO_CTX: %08x WAIT: cur %08x tot %08x\n" - "CTX Outstanding %08llx\n\n", - phba->sli4_hba.nvmet_xri_cnt, - phba->sli4_hba.nvmet_io_wait_cnt, - phba->sli4_hba.nvmet_io_wait_total, - tot); - strlcat(buf, tmp, PAGE_SIZE); + len += sysfs_emit_at(buf, len, + "IO_CTX: %08x WAIT: cur %08x tot %08x\n" + "CTX Outstanding %08llx\n\n", + phba->sli4_hba.nvmet_xri_cnt, + phba->sli4_hba.nvmet_io_wait_cnt, + phba->sli4_hba.nvmet_io_wait_total, + tot); goto buffer_done; } localport = vport->localport; if (!localport) { - len = scnprintf(buf, PAGE_SIZE, - "NVME Initiator x%llx is not allocated\n", - wwn_to_u64(vport->fc_portname.u.wwn)); + len = sysfs_emit(buf, + "NVME Initiator x%llx is not allocated\n", + wwn_to_u64(vport->fc_portname.u.wwn)); return len; } lport = (struct lpfc_nvme_lport *)localport->private; - if (strlcat(buf, "\nNVME Initiator Enabled\n", PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, "\nNVME Initiator Enabled\n"); - scnprintf(tmp, sizeof(tmp), - "XRI Dist lpfc%d Total %d IO %d ELS %d\n", - phba->brd_no, - phba->sli4_hba.max_cfg_param.max_xri, - phba->sli4_hba.io_xri_max, - lpfc_sli4_get_els_iocb_cnt(phba)); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "XRI Dist lpfc%d Total %d IO %d ELS %d\n", + phba->brd_no, + phba->sli4_hba.max_cfg_param.max_xri, + phba->sli4_hba.io_xri_max, + lpfc_sli4_get_els_iocb_cnt(phba)); /* Port state is only one of two values for now. */ if (localport->port_id) @@ -666,15 +613,13 @@ lpfc_nvme_info_show(struct device *dev, struct device_attribute *attr, else statep = "UNKNOWN "; - scnprintf(tmp, sizeof(tmp), - "%s%d WWPN x%llx WWNN x%llx DID x%06x %s\n", - "NVME LPORT lpfc", - phba->brd_no, - wwn_to_u64(vport->fc_portname.u.wwn), - wwn_to_u64(vport->fc_nodename.u.wwn), - localport->port_id, statep); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "%s%d WWPN x%llx WWNN x%llx DID x%06x %s\n", + "NVME LPORT lpfc", + phba->brd_no, + wwn_to_u64(vport->fc_portname.u.wwn), + wwn_to_u64(vport->fc_nodename.u.wwn), + localport->port_id, statep); spin_lock_irqsave(&vport->fc_nodes_list_lock, iflags); @@ -702,77 +647,55 @@ lpfc_nvme_info_show(struct device *dev, struct device_attribute *attr, } /* Tab in to show lport ownership. */ - if (strlcat(buf, "NVME RPORT ", PAGE_SIZE) >= PAGE_SIZE) - goto unlock_buf_done; - if (phba->brd_no >= 10) { - if (strlcat(buf, " ", PAGE_SIZE) >= PAGE_SIZE) - goto unlock_buf_done; - } + len += sysfs_emit_at(buf, len, "NVME RPORT "); + if (phba->brd_no >= 10) + len += sysfs_emit_at(buf, len, " "); - scnprintf(tmp, sizeof(tmp), "WWPN x%llx ", - nrport->port_name); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto unlock_buf_done; + len += sysfs_emit_at(buf, len, "WWPN x%llx ", + nrport->port_name); - scnprintf(tmp, sizeof(tmp), "WWNN x%llx ", - nrport->node_name); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto unlock_buf_done; + len += sysfs_emit_at(buf, len, "WWNN x%llx ", + nrport->node_name); - scnprintf(tmp, sizeof(tmp), "DID x%06x ", - nrport->port_id); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto unlock_buf_done; + len += sysfs_emit_at(buf, len, "DID x%06x ", + nrport->port_id); /* An NVME rport can have multiple roles. */ - if (nrport->port_role & FC_PORT_ROLE_NVME_INITIATOR) { - if (strlcat(buf, "INITIATOR ", PAGE_SIZE) >= PAGE_SIZE) - goto unlock_buf_done; - } - if (nrport->port_role & FC_PORT_ROLE_NVME_TARGET) { - if (strlcat(buf, "TARGET ", PAGE_SIZE) >= PAGE_SIZE) - goto unlock_buf_done; - } - if (nrport->port_role & FC_PORT_ROLE_NVME_DISCOVERY) { - if (strlcat(buf, "DISCSRVC ", PAGE_SIZE) >= PAGE_SIZE) - goto unlock_buf_done; - } + if (nrport->port_role & FC_PORT_ROLE_NVME_INITIATOR) + len += sysfs_emit_at(buf, len, "INITIATOR "); + if (nrport->port_role & FC_PORT_ROLE_NVME_TARGET) + len += sysfs_emit_at(buf, len, "TARGET "); + if (nrport->port_role & FC_PORT_ROLE_NVME_DISCOVERY) + len += sysfs_emit_at(buf, len, "DISCSRVC "); if (nrport->port_role & ~(FC_PORT_ROLE_NVME_INITIATOR | FC_PORT_ROLE_NVME_TARGET | - FC_PORT_ROLE_NVME_DISCOVERY)) { - scnprintf(tmp, sizeof(tmp), "UNKNOWN ROLE x%x", - nrport->port_role); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto unlock_buf_done; - } + FC_PORT_ROLE_NVME_DISCOVERY)) + len += sysfs_emit_at(buf, len, "UNKNOWN ROLE x%x", + nrport->port_role); - scnprintf(tmp, sizeof(tmp), "%s\n", statep); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto unlock_buf_done; + len += sysfs_emit_at(buf, len, "%s\n", statep); + + if (len >= PAGE_SIZE - 1) + break; } spin_unlock_irqrestore(&vport->fc_nodes_list_lock, iflags); if (!lport) goto buffer_done; - if (strlcat(buf, "\nNVME Statistics\n", PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, "\nNVME Statistics\n"); - scnprintf(tmp, sizeof(tmp), - "LS: Xmt %010x Cmpl %010x Abort %08x\n", - atomic_read(&lport->fc4NvmeLsRequests), - atomic_read(&lport->fc4NvmeLsCmpls), - atomic_read(&lport->xmt_ls_abort)); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "LS: Xmt %010x Cmpl %010x Abort %08x\n", + atomic_read(&lport->fc4NvmeLsRequests), + atomic_read(&lport->fc4NvmeLsCmpls), + atomic_read(&lport->xmt_ls_abort)); - scnprintf(tmp, sizeof(tmp), - "LS XMIT: Err %08x CMPL: xb %08x Err %08x\n", - atomic_read(&lport->xmt_ls_err), - atomic_read(&lport->cmpl_ls_xb), - atomic_read(&lport->cmpl_ls_err)); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "LS XMIT: Err %08x CMPL: xb %08x Err %08x\n", + atomic_read(&lport->xmt_ls_err), + atomic_read(&lport->cmpl_ls_xb), + atomic_read(&lport->cmpl_ls_err)); totin = 0; totout = 0; @@ -785,40 +708,25 @@ lpfc_nvme_info_show(struct device *dev, struct device_attribute *attr, data3 = cstat->control_requests; totout += (data1 + data2 + data3); } - scnprintf(tmp, sizeof(tmp), - "Total FCP Cmpl %016llx Issue %016llx " - "OutIO %016llx\n", - totin, totout, totout - totin); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "Total FCP Cmpl %016llx Issue %016llx OutIO %016llx\n", + totin, totout, totout - totin); - scnprintf(tmp, sizeof(tmp), - "\tabort %08x noxri %08x nondlp %08x qdepth %08x " - "wqerr %08x err %08x\n", - atomic_read(&lport->xmt_fcp_abort), - atomic_read(&lport->xmt_fcp_noxri), - atomic_read(&lport->xmt_fcp_bad_ndlp), - atomic_read(&lport->xmt_fcp_qdepth), - atomic_read(&lport->xmt_fcp_wqerr), - atomic_read(&lport->xmt_fcp_err)); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "\tabort %08x noxri %08x nondlp %08x qdepth %08x wqerr %08x err %08x\n", + atomic_read(&lport->xmt_fcp_abort), + atomic_read(&lport->xmt_fcp_noxri), + atomic_read(&lport->xmt_fcp_bad_ndlp), + atomic_read(&lport->xmt_fcp_qdepth), + atomic_read(&lport->xmt_fcp_wqerr), + atomic_read(&lport->xmt_fcp_err)); - scnprintf(tmp, sizeof(tmp), - "FCP CMPL: xb %08x Err %08x\n", - atomic_read(&lport->cmpl_fcp_xb), - atomic_read(&lport->cmpl_fcp_err)); - strlcat(buf, tmp, PAGE_SIZE); - - /* host_lock is already unlocked. */ - goto buffer_done; - - unlock_buf_done: - spin_unlock_irqrestore(&vport->fc_nodes_list_lock, iflags); + len += sysfs_emit_at(buf, len, + "FCP CMPL: xb %08x Err %08x\n", + atomic_read(&lport->cmpl_fcp_xb), + atomic_read(&lport->cmpl_fcp_err)); buffer_done: - len = strnlen(buf, PAGE_SIZE); - if (unlikely(len >= (PAGE_SIZE - 1))) { lpfc_printf_log(phba, KERN_INFO, LOG_NVME, "6314 Catching potential buffer " @@ -844,13 +752,12 @@ lpfc_scsi_stat_show(struct device *dev, struct device_attribute *attr, u64 data1, data2, data3; u64 tot, totin, totout; int i; - char tmp[LPFC_MAX_SCSI_INFO_TMP_LEN] = {0}; if (!(vport->cfg_enable_fc4_type & LPFC_ENABLE_FCP) || (phba->sli_rev != LPFC_SLI_REV4)) return 0; - scnprintf(buf, PAGE_SIZE, "SCSI HDWQ Statistics\n"); + len = sysfs_emit(buf, "SCSI HDWQ Statistics\n"); totin = 0; totout = 0; @@ -863,22 +770,20 @@ lpfc_scsi_stat_show(struct device *dev, struct device_attribute *attr, data3 = cstat->control_requests; totout += (data1 + data2 + data3); - scnprintf(tmp, sizeof(tmp), "HDWQ (%d): Rd %016llx Wr %016llx " - "IO %016llx ", i, data1, data2, data3); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "HDWQ (%d): Rd %016llx Wr %016llx IO %016llx ", + i, data1, data2, data3); - scnprintf(tmp, sizeof(tmp), "Cmpl %016llx OutIO %016llx\n", - tot, ((data1 + data2 + data3) - tot)); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "Cmpl %016llx OutIO %016llx\n", + tot, ((data1 + data2 + data3) - tot)); + + if (len >= PAGE_SIZE - 1) + break; } - scnprintf(tmp, sizeof(tmp), "Total FCP Cmpl %016llx Issue %016llx " - "OutIO %016llx\n", totin, totout, totout - totin); - strlcat(buf, tmp, PAGE_SIZE); - -buffer_done: - len = strnlen(buf, PAGE_SIZE); + len += sysfs_emit_at(buf, len, + "Total FCP Cmpl %016llx Issue %016llx OutIO %016llx\n", + totin, totout, totout - totin); return len; } diff --git a/drivers/scsi/lpfc/lpfc_scsi.h b/drivers/scsi/lpfc/lpfc_scsi.h index a05d203e4777..3e0937cf3ff3 100644 --- a/drivers/scsi/lpfc/lpfc_scsi.h +++ b/drivers/scsi/lpfc/lpfc_scsi.h @@ -158,6 +158,3 @@ struct fcp_cmnd32 { #define TXRDY_PAYLOAD_LEN 12 -/* For sysfs/debugfs tmp string max len */ -#define LPFC_MAX_SCSI_INFO_TMP_LEN 79 - From 53cb675d72064f9f97667fbc0a8775f24263362d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Thu, 6 Aug 2026 15:00:50 +0200 Subject: [PATCH 0574/1328] clk: samsung: Don't include MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All users of drivers/clk/samsung/clk.h live in drivers/clk/samsung (see git grep samsung/clk.h ). All these are only using of_device_id from the set of structures defined by this include (see { git grep -l clk\\.h drivers/clk/samsung/; echo drivers/clk/samsung/clk.h; } | xargs grep -oh -E "\<(acpi_device_id|amba_id|ap_device_id|apr_device_id|auxiliary_device_id|bcma_device_id|ccw_device_id|cdx_device_id|coreboot_device_id|css_device_id|dfl_device_id|dmi_(device|system)_id|eisa_device_id|fsl_mc_device_id|hda_device_id|hid_device_id|hv_vmbus_device_id|i2c_device_id|i3c_device_id|ieee1394_device_id|input_device_id|ipack_device_id|isapnp_device_id|ishtp_device_id|mcb_device_id|mdio_device_id|mei_cl_device_id|mhi_device_id|mips_cdmm_device_id|of_device_id|parisc_device_id|pci_device_id|pci_epf_device_id|pcmcia_device_id|platform_device_id|pnp_(card_)?device_id|rio_device_id|rpmsg_device_id|sdio_device_id|sdw_device_id|serio_device_id|slim_device_id|spi_device_id|spmi_device_id|ssam_device_id|ssb_device_id|tb_service_id|tee_client_device_id|typec_device_id|ulpi_device_id|usb_device_id|vchiq_device_id|vio_device_id|virtio_device_id|wmi_device_id|x86_(cpu|device)_id|zorro_device_id|cpu_feature)\>" | sort | uniq -c ). So can be replaced by to get rid of another user of the former header. Signed-off-by: Uwe Kleine-König (The Capable Hub) Reviewed-by: Brian Masney Signed-off-by: Stephen Boyd --- drivers/clk/samsung/clk.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/samsung/clk.h b/drivers/clk/samsung/clk.h index be733eba08a2..3b8d51e9b73c 100644 --- a/drivers/clk/samsung/clk.h +++ b/drivers/clk/samsung/clk.h @@ -11,7 +11,7 @@ #define __SAMSUNG_CLK_H #include -#include +#include #include #include "clk-pll.h" #include "clk-cpu.h" From 271e90eb5f9ff34951647e5ed33c1775eebcca50 Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Fri, 31 Jul 2026 12:42:32 -0300 Subject: [PATCH 0575/1328] drm: use drm_warn() in validate_blend_mode_for_alpha_formats() Commit 860e748bddcc ("drm: ensure blend mode supported if pixel format with alpha exposed") introduced a WARN() to let driver developers know that a previously valid behavior should now be changed. But WARN() should not be used for that, as it's a kernel warning report mechanism for conditions that are not expected to happen. It also produces a stack trace. Instead, a simple warning-level log message should have been used, as drivers were expected to trigger the condition. This is causing problems for fuzzers, as they may stop when encountering a "BUG:" or "WARNING:" in the logs. Replace WARN() with drm_warn() in this function, avoiding these issues. Fixes: 860e748bddcc ("drm: ensure blend mode supported if pixel format with alpha exposed") Signed-off-by: Leandro Ribeiro Reviewed-by: Daniel Stone Link: https://patch.msgid.link/20260731154232.37020-2-leandro.ribeiro@collabora.com Signed-off-by: Daniel Stone --- drivers/gpu/drm/drm_mode_config.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/drm_mode_config.c b/drivers/gpu/drm/drm_mode_config.c index 3bcc7bf0900c..366f6d821242 100644 --- a/drivers/gpu/drm/drm_mode_config.c +++ b/drivers/gpu/drm/drm_mode_config.c @@ -869,8 +869,9 @@ static void validate_blend_mode_for_alpha_formats(struct drm_plane *plane) for (i = 0; i < plane->format_count; i++) { fmt = drm_format_info(plane->format_types[i]); if (fmt->has_alpha) { - WARN(1, "[PLANE:%d:%s] pixel format with alpha exposed but blend mode not setup", - plane->base.id, plane->name); + drm_warn(plane->dev, + "[PLANE:%d:%s] pixel format with alpha exposed but blend mode not setup. Please fix.\n", + plane->base.id, plane->name); break; } } From 9e409f1dff7841634e4b101111d6427f979c0aac Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Sun, 9 Aug 2026 07:43:55 +0800 Subject: [PATCH 0576/1328] ACPI: battery: Protect all properties with a separated mutex The acpi_battery_get_property() callback calls acpi_battery_get_state() without any lock held. On some devices, it happens that the property cache has expired before a uevent reaches userspace, triggering simultaneous attempts to evaluate _BST. See [1] for an analysis to sysrq stacktraces on one of the these devices. In a few cases, including when the AML is sleeping or acquiring a mutex, ACPICA drops the namespace and interpreter locks and allows the evaluation of _BST to start while another task is still evaluating it. This could somehow confuse the interpreter and lead to chaos in AML mutexes on some devices, see [2] for an example. Not holding the lock is also prone to race conditions, for example: CPU0 | CPU1 acpi_battery_get_property() | acpi_battery_get_state() | [update_time expired] | extract_package() | acpi_battery_get_property() battery->update_time = jiffies | acpi_battery_get_state() kfree() | [up to date] | [read capacity_now] [fix capacity_now due to quirk] | where CPU1 gets raw capacity_now before CPU0 fixes it to a meaningful value. The existing mutex update_lock is not applicapable for acpi_battery_get_property(), as some code path could call or wait for acpi_battery_get_property() while holding update_lock. Therefore, introduce a mutex called property_lock to protect all accesses to battery properties, so that acpi_battery_get_property() can take the advantage of the mutex and synchronize itself. With the mutex, acpi_battery_get_state() are synchronized in all code paths calling it, and its cache mechanism can always clamp the frequency of _BST evaluations according to cache_time. The helper function acpi_battery_handle_discharging() for quirky devices has to be inlined due to the change, as the mutex must be unlocked before calling the expensive power_supply_is_system_supplied() helper function. Fixes: 86bfd21a0baf ("ACPI: battery: Drop redundant locking") Reported-by: Rick Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221065#c85 [1] Reported-by: Avraham Hollander Tested-by: Avraham Hollander Closes: https://lore.kernel.org/linux-acpi/CAP1mzZReJCn6df5DwEPu-JCQUyr=Pu1cg5xKCMttWZkHCQtVmQ@mail.gmail.com [2] Signed-off-by: Rong Zhang Cc: All applicable Link: https://patch.msgid.link/20260809-b4-acpi-battery-notification-v5-1-788d54fa2e35@rong.moe Signed-off-by: Rafael J. Wysocki --- drivers/acpi/battery.c | 147 ++++++++++++++++++++++++++++------------- 1 file changed, 101 insertions(+), 46 deletions(-) diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c index 0084f308b790..670853ec3a4d 100644 --- a/drivers/acpi/battery.c +++ b/drivers/acpi/battery.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -105,6 +106,9 @@ struct acpi_battery { struct delayed_work acpi_notif_dwork; struct notifier_block pm_nb; struct list_head list; + unsigned long flags; + + struct mutex property_lock; /* Protects properties below. */ unsigned long update_time; int revision; int rate_now; @@ -131,7 +135,6 @@ struct acpi_battery { char oem_info[MAX_STRING_LENGTH]; int state; int power_unit; - unsigned long flags; }; #define to_acpi_battery(x) power_supply_get_drvdata(x) @@ -189,20 +192,6 @@ static bool acpi_battery_is_degraded(struct acpi_battery *battery) battery->full_charge_capacity < battery->design_capacity; } -static int acpi_battery_handle_discharging(struct acpi_battery *battery) -{ - /* - * Some devices wrongly report discharging if the battery's charge level - * was above the device's start charging threshold atm the AC adapter - * was plugged in and the device thus did not start a new charge cycle. - */ - if ((battery_ac_is_broken || power_supply_is_system_supplied()) && - battery->rate_now == 0) - return POWER_SUPPLY_STATUS_NOT_CHARGING; - - return POWER_SUPPLY_STATUS_DISCHARGING; -} - static int acpi_battery_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) @@ -210,15 +199,41 @@ static int acpi_battery_get_property(struct power_supply *psy, int full_capacity = ACPI_BATTERY_VALUE_UNKNOWN, ret = 0; struct acpi_battery *battery = to_acpi_battery(psy); - if (acpi_battery_present(battery)) { - /* run battery update only if it is present */ - acpi_battery_get_state(battery); - } else if (psp != POWER_SUPPLY_PROP_PRESENT) - return -ENODEV; + /* run battery update only if it is present */ + if (!acpi_battery_present(battery)) { + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 0; + return 0; + default: + return -ENODEV; + } + } + + mutex_lock(&battery->property_lock); + + acpi_battery_get_state(battery); + switch (psp) { case POWER_SUPPLY_PROP_STATUS: + /* + * Some devices wrongly report discharging if the battery's charge level + * was above the device's start charging threshold atm the AC adapter + * was plugged in and the device thus did not start a new charge cycle. + */ if (battery->state & ACPI_BATTERY_STATE_DISCHARGING) - val->intval = acpi_battery_handle_discharging(battery); + if (battery->rate_now != 0) { + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + } else if (battery_ac_is_broken) { + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + } else { + mutex_unlock(&battery->property_lock); + + val->intval = power_supply_is_system_supplied() + ? POWER_SUPPLY_STATUS_NOT_CHARGING + : POWER_SUPPLY_STATUS_DISCHARGING; + return 0; + } else if (battery->state & ACPI_BATTERY_STATE_CHARGING) /* Check the rate and capacity to validate the status. */ if (!acpi_battery_is_full(battery) || @@ -321,6 +336,8 @@ static int acpi_battery_get_property(struct power_supply *psy, default: ret = -EINVAL; } + + mutex_unlock(&battery->property_lock); return ret; } @@ -556,6 +573,8 @@ static int acpi_battery_get_info(struct acpi_battery *battery) int use_bix; int result = -ENODEV; + lockdep_assert_held(&battery->property_lock); + if (!acpi_battery_present(battery)) return 0; @@ -595,6 +614,8 @@ static int acpi_battery_get_state(struct acpi_battery *battery) acpi_status status = 0; struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + lockdep_assert_held(&battery->property_lock); + if (!acpi_battery_present(battery)) return 0; @@ -648,6 +669,8 @@ static int acpi_battery_set_alarm(struct acpi_battery *battery) { acpi_status status = 0; + lockdep_assert_held(&battery->property_lock); + if (!acpi_battery_present(battery) || !test_bit(ACPI_BATTERY_ALARM_PRESENT, &battery->flags)) return -ENODEV; @@ -665,6 +688,8 @@ static int acpi_battery_set_alarm(struct acpi_battery *battery) static int acpi_battery_init_alarm(struct acpi_battery *battery) { + lockdep_assert_held(&battery->property_lock); + /* See if alarms are supported, and if so, set default */ if (!acpi_has_method(battery->device->handle, "_BTP")) { clear_bit(ACPI_BATTERY_ALARM_PRESENT, &battery->flags); @@ -682,6 +707,8 @@ static ssize_t acpi_battery_alarm_show(struct device *dev, { struct acpi_battery *battery = to_acpi_battery(dev_get_drvdata(dev)); + guard(mutex)(&battery->property_lock); + return sysfs_emit(buf, "%d\n", battery->alarm * 1000); } @@ -697,6 +724,8 @@ static ssize_t acpi_battery_alarm_store(struct device *dev, if (err) return err; + guard(mutex)(&battery->property_lock); + battery->alarm = x / 1000; if (acpi_battery_present(battery)) acpi_battery_set_alarm(battery); @@ -881,12 +910,17 @@ static int sysfs_add_battery(struct acpi_battery *battery) .no_wakeup_source = true, }; bool full_cap_broken = false; + int power_unit; - if (!ACPI_BATTERY_CAPACITY_VALID(battery->full_charge_capacity) && - !ACPI_BATTERY_CAPACITY_VALID(battery->design_capacity)) - full_cap_broken = true; + scoped_guard(mutex, &battery->property_lock) { + power_unit = battery->power_unit; - if (battery->power_unit == ACPI_BATTERY_POWER_UNIT_MA) { + if (!ACPI_BATTERY_CAPACITY_VALID(battery->full_charge_capacity) && + !ACPI_BATTERY_CAPACITY_VALID(battery->design_capacity)) + full_cap_broken = true; + } + + if (power_unit == ACPI_BATTERY_POWER_UNIT_MA) { if (full_cap_broken) { battery->bat_desc.properties = charge_battery_full_cap_broken_props; @@ -940,6 +974,9 @@ static void sysfs_remove_battery(struct acpi_battery *battery) static void find_battery(const struct dmi_header *dm, void *private) { struct acpi_battery *battery = (struct acpi_battery *)private; + + lockdep_assert_held(&battery->property_lock); + /* Note: the hardcoded offsets below have been extracted from * the source code of dmidecode. */ @@ -971,6 +1008,8 @@ static void find_battery(const struct dmi_header *dm, void *private) */ static void acpi_battery_quirks(struct acpi_battery *battery) { + lockdep_assert_held(&battery->property_lock); + if (test_bit(ACPI_BATTERY_QUIRK_PERCENTAGE_CAPACITY, &battery->flags)) return; @@ -1023,30 +1062,38 @@ static void acpi_battery_quirks(struct acpi_battery *battery) static int acpi_battery_update(struct acpi_battery *battery, bool resume) { int result = acpi_battery_get_status(battery); + bool wakeup; if (result) return result; if (!acpi_battery_present(battery)) { sysfs_remove_battery(battery); - battery->update_time = 0; + scoped_guard(mutex, &battery->property_lock) + battery->update_time = 0; return 0; } if (resume) return 0; - if (!battery->update_time) { - result = acpi_battery_get_info(battery); + scoped_guard(mutex, &battery->property_lock) { + if (!battery->update_time) { + result = acpi_battery_get_info(battery); + if (result) + return result; + acpi_battery_init_alarm(battery); + } + + result = acpi_battery_get_state(battery); if (result) return result; - acpi_battery_init_alarm(battery); - } + acpi_battery_quirks(battery); - result = acpi_battery_get_state(battery); - if (result) - return result; - acpi_battery_quirks(battery); + wakeup = ((battery->state & ACPI_BATTERY_STATE_CRITICAL) || + (test_bit(ACPI_BATTERY_ALARM_PRESENT, &battery->flags) && + (battery->capacity_now <= battery->alarm))); + } if (!battery->bat) { result = sysfs_add_battery(battery); @@ -1058,9 +1105,7 @@ static int acpi_battery_update(struct acpi_battery *battery, bool resume) * Wakeup the system if battery is critical low * or lower than the alarm level */ - if ((battery->state & ACPI_BATTERY_STATE_CRITICAL) || - (test_bit(ACPI_BATTERY_ALARM_PRESENT, &battery->flags) && - (battery->capacity_now <= battery->alarm))) + if (wakeup) acpi_pm_wakeup_event(battery->phys_dev); return result; @@ -1073,12 +1118,14 @@ static void acpi_battery_refresh(struct acpi_battery *battery) if (!battery->bat) return; - power_unit = battery->power_unit; + scoped_guard(mutex, &battery->property_lock) { + power_unit = battery->power_unit; - acpi_battery_get_info(battery); + acpi_battery_get_info(battery); - if (power_unit == battery->power_unit) - return; + if (power_unit == battery->power_unit) + return; + } /* The battery has changed its reporting units. */ sysfs_remove_battery(battery); @@ -1170,17 +1217,21 @@ static int battery_notify(struct notifier_block *nb, } else { int result; - result = acpi_battery_get_info(battery); - if (result) - return result; + scoped_guard(mutex, &battery->property_lock) { + result = acpi_battery_get_info(battery); + if (result) + return result; + } result = sysfs_add_battery(battery); if (result) return result; } - acpi_battery_init_alarm(battery); - acpi_battery_get_state(battery); + scoped_guard(mutex, &battery->property_lock) { + acpi_battery_init_alarm(battery); + acpi_battery_get_state(battery); + } } return 0; @@ -1345,6 +1396,10 @@ static int acpi_battery_probe(struct platform_device *pdev) if (result) return result; + result = devm_mutex_init(&pdev->dev, &battery->property_lock); + if (result) + return result; + if (acpi_has_method(battery->device->handle, "_BIX")) set_bit(ACPI_BATTERY_XINFO_PRESENT, &battery->flags); From a414485ebc2aa50907d0ce97cde2b1a353696897 Mon Sep 17 00:00:00 2001 From: Hongyan Xu Date: Sat, 8 Aug 2026 16:59:42 +0800 Subject: [PATCH 0577/1328] ACPI: scan: fix bus ID cleanup on device_add() failures When device_add() fails after acpi_device_set_name() has allocated an instance ID and a new acpi_device_bus_id has been linked into acpi_bus_id_list, the rollback path only removes wakeup_list and detaches the ACPI handle data. That leaves the bus-ID bookkeeping behind and keeps the allocated instance number consumed. Move the bus-ID cleanup and wakeup-list removal into a single helper. Use it from both the normal device teardown path and the device_add() rollback path. The wakeup list node is initialized before registration, so it can be deleted without checking whether the device is wakeup- capable like in the original teardown path. Fixes: d783156ea384 ("ACPI / scan: Define non-empty device removal handler") Signed-off-by: Hongyan Xu [ rjw: Rename acpi_device_del_list() to acpi_device_cleanup() ] [ rjw: Subject and changelog edits ] Link: https://patch.msgid.link/20260808085943.526-1-getshell@seu.edu.cn Signed-off-by: Rafael J. Wysocki --- drivers/acpi/scan.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 9a7ac2eb9ce0..f8450f7ea097 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -524,12 +524,10 @@ static void acpi_device_release(struct device *dev) kfree(acpi_dev); } -static void acpi_device_del(struct acpi_device *device) +static void acpi_device_cleanup(struct acpi_device *device) { struct acpi_device_bus_id *acpi_device_bus_id; - mutex_lock(&acpi_device_lock); - list_for_each_entry(acpi_device_bus_id, &acpi_bus_id_list, node) if (!strcmp(acpi_device_bus_id->bus_id, acpi_device_hid(device))) { @@ -544,6 +542,13 @@ static void acpi_device_del(struct acpi_device *device) } list_del(&device->wakeup_list); +} + +static void acpi_device_del(struct acpi_device *device) +{ + mutex_lock(&acpi_device_lock); + + acpi_device_cleanup(device); mutex_unlock(&acpi_device_lock); @@ -803,7 +808,7 @@ int acpi_device_add(struct acpi_device *device) err: mutex_lock(&acpi_device_lock); - list_del(&device->wakeup_list); + acpi_device_cleanup(device); err_unlock: mutex_unlock(&acpi_device_lock); From 72530e1f72b0515a73fd88292254d04fecf03649 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 12 Aug 2026 14:41:44 +0200 Subject: [PATCH 0578/1328] ACPI: bus: Introduce acpi_bus_get_primary_device() The function used for obtaining the first "physical" device for which the given ACPI one is the ACPI companion, acpi_get_first_physical_node(), may return a stale device pointer (mostly in theory) because acpi_unbind_one() may run as a whole after dropping the ACPI device's physical_node_lock in acpi_get_first_physical_node() and before it returns. The last reference to the "physical" device may be dropped then before the pointer to it is returned to the caller. If that happens and the acpi_get_first_physical_node() caller invokes get_device() on the pointer obtained from it, which is done by the majority of its callers, a use-after-free will occur. To prepare for addressing this problem, introduce a new function for getting the first "physical" device associated with the given ACPI one (the "primary physical device") that will also reference count the device in question before returning a pointer to it. Make that new function and acpi_get_first_physical_node() share the physical node list lookup code. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/2843318.mvXUDI8C0e@rafael.j.wysocki --- drivers/acpi/bus.c | 54 +++++++++++++++++++++++++++++------------ include/acpi/acpi_bus.h | 6 +++++ 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/drivers/acpi/bus.c b/drivers/acpi/bus.c index c1876f145ae4..ce50ce7a50da 100644 --- a/drivers/acpi/bus.c +++ b/drivers/acpi/bus.c @@ -774,30 +774,52 @@ static int __init acpi_setup_sb_notify_handler(void) Device Matching -------------------------------------------------------------------------- */ + +static struct device *primary_physical_device(struct acpi_device *adev) +{ + struct acpi_device_physical_node *pn; + + pn = list_first_entry_or_null(&adev->physical_node_list, + struct acpi_device_physical_node, node); + if (pn) + return pn->dev; + + return NULL; +} + /** - * acpi_get_first_physical_node - Get first physical node of an ACPI device + * acpi_bus_get_primary_device - Get first physical device for a given ACPI one + * @adev: ACPI device to get the first physical device for. + * + * Find the first physical device for which @adev is the ACPI companion and + * reference count it if present. + * + * Return: Pointer to the first physical counterpart of @adev or NULL if there + * are none. Callers are responsible for invoking put_device() on the returned + * device. + */ +struct device *acpi_bus_get_primary_device(struct acpi_device *adev) +{ + if (!adev) + return NULL; + + guard(mutex)(&adev->physical_node_lock); + + return get_device(primary_physical_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) { - struct mutex *physical_node_lock = &adev->physical_node_lock; - struct device *phys_dev; + guard(mutex)(&adev->physical_node_lock); - mutex_lock(physical_node_lock); - if (list_empty(&adev->physical_node_list)) { - phys_dev = NULL; - } else { - const struct acpi_device_physical_node *node; - - node = list_first_entry(&adev->physical_node_list, - struct acpi_device_physical_node, node); - - phys_dev = node->dev; - } - mutex_unlock(physical_node_lock); - return phys_dev; + return primary_physical_device(adev); } EXPORT_SYMBOL_GPL(acpi_get_first_physical_node); diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index 32cac3a6f362..eaf13f6ac2d3 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -650,6 +650,7 @@ int acpi_scan_add_handler(struct acpi_scan_handler *handler); int acpi_bus_scan(acpi_handle handle); void acpi_bus_trim(struct acpi_device *start); acpi_status acpi_bus_get_ejd(acpi_handle handle, acpi_handle * ejd); +struct device *acpi_bus_get_primary_device(struct acpi_device *adev); int acpi_match_device_ids(struct acpi_device *device, const struct acpi_device_id *ids); void acpi_set_modalias(struct acpi_device *adev, const char *default_id, @@ -952,6 +953,11 @@ 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 struct device *acpi_bus_get_primary_device(struct acpi_device *adev) +{ + return NULL; +} + static inline bool acpi_of_match_device(const struct acpi_device *adev, const struct of_device_id *of_match_table, const struct of_device_id **of_id) From a9ba4dd2f18bf3f439d9ef0d8f375f90360ba1bd Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 10 Aug 2026 13:38:08 +0200 Subject: [PATCH 0579/1328] ACPI: platform: Use acpi_bus_get_primary_device() The acpi_get_first_physical_node() usage in acpi_platform_fill_resource() and acpi_create_platform_device() is generally unsafe because in theory the device returned by it may be freed at any time [1]. It is also inefficient because acpi_get_first_physical_node() is called multiple times for the same argument which can be avoided. Address these issues by using acpi_bus_get_primary_device() instead of acpi_get_first_physical_node() and adjusting the code to call it just once at the beginning of and acpi_create_platform_device() and drop the device reference acquired by it upon the return from that function. Fixes: 3b95bd160547 ("ACPI: introduce a function to find the first physical device") Fixes: a252d881c558 ("ACPI / platform: Pay attention to parent device's resources") Link: https://sashiko.dev/#/patchset/12955541.O9o76ZdvQC%40rafael.j.wysocki [1] Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/3436112.aeNJFYEL58@rafael.j.wysocki --- drivers/acpi/acpi_platform.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/drivers/acpi/acpi_platform.c b/drivers/acpi/acpi_platform.c index 373c94de7590..fffdd4f011b2 100644 --- a/drivers/acpi/acpi_platform.c +++ b/drivers/acpi/acpi_platform.c @@ -102,18 +102,15 @@ static unsigned int acpi_platform_adjust_resources(struct acpi_device *adev, return count; } -static void acpi_platform_fill_resource(struct acpi_device *adev, - const struct resource *src, struct resource *dest) +static void acpi_platform_fill_resource(struct device *parent, + const struct resource *src, + struct resource *dest) { - struct device *parent; - *dest = *src; - /* * If the device has parent we need to take its resources into * account as well because this device might consume part of those. */ - parent = acpi_get_first_physical_node(acpi_dev_parent(adev)); if (parent && dev_is_pci(parent)) dest->parent = pci_find_resource(to_pci_dev(parent), dest); } @@ -141,7 +138,8 @@ static unsigned int acpi_platform_resource_count(struct acpi_resource *ares, voi struct platform_device *acpi_create_platform_device(struct acpi_device *adev, const struct property_entry *properties) { - struct acpi_device *parent = acpi_dev_parent(adev); + struct acpi_device *p = acpi_dev_parent(adev); + struct device *parent __free(put_device) = acpi_bus_get_primary_device(p); struct platform_device *pdev = NULL; struct platform_device_info pdevinfo; const struct acpi_device_id *match; @@ -187,7 +185,7 @@ struct platform_device *acpi_create_platform_device(struct acpi_device *adev, rentry->res, resources, count); - acpi_platform_fill_resource(adev, rentry->res, + acpi_platform_fill_resource(parent, rentry->res, &resources[count++]); } acpi_dev_free_resource_list(&resource_list); @@ -200,7 +198,7 @@ struct platform_device *acpi_create_platform_device(struct acpi_device *adev, * attached to it, that physical device should be the parent of the * platform device we are about to create. */ - pdevinfo.parent = parent ? acpi_get_first_physical_node(parent) : NULL; + pdevinfo.parent = parent; pdevinfo.name = dev_name(&adev->dev); pdevinfo.id = PLATFORM_DEVID_NONE; pdevinfo.res = resources; From 87ee7d704f69f303c86b2dcf617e33764371fe7b Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 10 Aug 2026 13:40:18 +0200 Subject: [PATCH 0580/1328] ACPI: scan: Use acpi_bus_get_primary_device() The acpi_get_first_physical_node() usage in acpi_create_video_bus_device() is generally unsafe because in theory the device returned by it may be freed at any time. Address this issues by using acpi_bus_get_primary_device() instead of acpi_get_first_physical_node() and dropping the device reference acquired by it after registering the child. Fixes: 6ab3532b4c98 ("ACPI: video: Switch over to auxiliary bus type") Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/10906414.nUPlyArG6x@rafael.j.wysocki --- drivers/acpi/scan.c | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 8515e1892643..cf25d9d880e4 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -2204,29 +2204,27 @@ static void acpi_create_video_bus_device(struct acpi_device *adev, struct auxiliary_device *aux_dev; static unsigned int aux_dev_id; + struct device *phys_parent __free(put_device) = acpi_bus_get_primary_device(parent); + if (!phys_parent) + return; + aux_dev = kzalloc_obj(*aux_dev); if (!aux_dev) return; aux_dev->id = aux_dev_id++; aux_dev->name = "video_bus"; - aux_dev->dev.parent = acpi_get_first_physical_node(parent); - if (!aux_dev->dev.parent) - goto err; - + aux_dev->dev.parent = phys_parent; aux_dev->dev.release = acpi_video_bus_device_release; - if (auxiliary_device_init(aux_dev)) - goto err; + if (auxiliary_device_init(aux_dev)) { + kfree(aux_dev); + return; + } ACPI_COMPANION_SET(&aux_dev->dev, adev); if (__auxiliary_device_add(aux_dev, "acpi")) auxiliary_device_uninit(aux_dev); - - return; - -err: - kfree(aux_dev); } struct acpi_scan_system_dev { From 4226911d72960b24ec427c61d795916e027f528e Mon Sep 17 00:00:00 2001 From: Hongnan Li Date: Thu, 13 Aug 2026 14:30:04 +0800 Subject: [PATCH 0581/1328] ACPI: APD: Convert fixed clock rates to use HZ_PER_MHZ Use HZ_PER_MHZ multiplier for fixed_clk_rate values to improve readability. Signed-off-by: Hongnan Li Suggested-by: Andy Shevchenko Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20260813063005.42925-1-clarke.li@hj-micro.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpi_apd.c | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/drivers/acpi/acpi_apd.c b/drivers/acpi/acpi_apd.c index 008bd0552cb7..275027ebd01f 100644 --- a/drivers/acpi/acpi_apd.c +++ b/drivers/acpi/acpi_apd.c @@ -14,6 +14,7 @@ #include #include #include +#include #include "internal.h" @@ -110,17 +111,17 @@ static int fch_misc_setup(struct apd_private_data *pdata) static const struct apd_device_desc cz_i2c_desc = { .setup = acpi_apd_setup, - .fixed_clk_rate = 133000000, + .fixed_clk_rate = 133 * HZ_PER_MHZ, }; static const struct apd_device_desc wt_i2c_desc = { .setup = acpi_apd_setup, - .fixed_clk_rate = 150000000, + .fixed_clk_rate = 150 * HZ_PER_MHZ, }; static const struct apd_device_desc wt_i3c_desc = { .setup = acpi_apd_setup, - .fixed_clk_rate = 125000000, + .fixed_clk_rate = 125 * HZ_PER_MHZ, }; static struct property_entry uart_properties[] = { @@ -132,7 +133,7 @@ static struct property_entry uart_properties[] = { static const struct apd_device_desc cz_uart_desc = { .setup = acpi_apd_setup, - .fixed_clk_rate = 48000000, + .fixed_clk_rate = 48 * HZ_PER_MHZ, .properties = uart_properties, }; @@ -144,52 +145,52 @@ static const struct apd_device_desc fch_misc_desc = { #ifdef CONFIG_ARM64 static const struct apd_device_desc xgene_i2c_desc = { .setup = acpi_apd_setup, - .fixed_clk_rate = 100000000, + .fixed_clk_rate = 100 * HZ_PER_MHZ, }; static const struct apd_device_desc vulcan_spi_desc = { .setup = acpi_apd_setup, - .fixed_clk_rate = 133000000, + .fixed_clk_rate = 133 * HZ_PER_MHZ, }; static const struct apd_device_desc hip07_i2c_desc = { .setup = acpi_apd_setup, - .fixed_clk_rate = 200000000, + .fixed_clk_rate = 200 * HZ_PER_MHZ, }; static const struct apd_device_desc hip08_i2c_desc = { .setup = acpi_apd_setup, - .fixed_clk_rate = 250000000, + .fixed_clk_rate = 250 * HZ_PER_MHZ, }; static const struct apd_device_desc hip08_lite_i2c_desc = { .setup = acpi_apd_setup, - .fixed_clk_rate = 125000000, + .fixed_clk_rate = 125 * HZ_PER_MHZ, }; static const struct apd_device_desc thunderx2_i2c_desc = { .setup = acpi_apd_setup, - .fixed_clk_rate = 125000000, + .fixed_clk_rate = 125 * HZ_PER_MHZ, }; static const struct apd_device_desc nxp_i2c_desc = { .setup = acpi_apd_setup, - .fixed_clk_rate = 350000000, + .fixed_clk_rate = 350 * HZ_PER_MHZ, }; static const struct apd_device_desc hip08_spi_desc = { .setup = acpi_apd_setup, - .fixed_clk_rate = 250000000, + .fixed_clk_rate = 250 * HZ_PER_MHZ, }; static const struct apd_device_desc leca_spi_desc = { .setup = acpi_apd_setup, - .fixed_clk_rate = 400000000, + .fixed_clk_rate = 400 * HZ_PER_MHZ, }; static const struct apd_device_desc leca_i2c_desc = { .setup = acpi_apd_setup, - .fixed_clk_rate = 250000000, + .fixed_clk_rate = 250 * HZ_PER_MHZ, }; #endif /* CONFIG_ARM64 */ From 6d2d4627485595c3d09c77eeee94972abf362e8b Mon Sep 17 00:00:00 2001 From: Xiangyang Yu Date: Thu, 13 Aug 2026 14:40:25 +0800 Subject: [PATCH 0582/1328] ACPI: APD: Add clock frequency for HJMC01 I2C controller I2C clock frequency for HJMC01 is 200MHz, define a new ACPI HID for it. Signed-off-by: Xiangyang Yu Signed-off-by: Hongnan Li Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20260813064025.45242-1-clarke.li@hj-micro.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpi_apd.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/acpi/acpi_apd.c b/drivers/acpi/acpi_apd.c index 275027ebd01f..e7366fcb76ee 100644 --- a/drivers/acpi/acpi_apd.c +++ b/drivers/acpi/acpi_apd.c @@ -193,6 +193,11 @@ static const struct apd_device_desc leca_i2c_desc = { .fixed_clk_rate = 250 * HZ_PER_MHZ, }; +static const struct apd_device_desc hjmc_i2c_desc = { + .setup = acpi_apd_setup, + .fixed_clk_rate = 200 * HZ_PER_MHZ, +}; + #endif /* CONFIG_ARM64 */ #endif @@ -263,6 +268,7 @@ static const struct acpi_device_id acpi_apd_device_ids[] = { { "HISI02A2", APD_ADDR(hip08_i2c_desc) }, { "HISI02A3", APD_ADDR(hip08_lite_i2c_desc) }, { "HISI0173", APD_ADDR(hip08_spi_desc) }, + { "HJMC3001", APD_ADDR(hjmc_i2c_desc) }, { "LECA0002", APD_ADDR(leca_spi_desc) }, { "LECA0003", APD_ADDR(leca_i2c_desc) }, { "NXP0001", APD_ADDR(nxp_i2c_desc) }, From 6620f5e8c11c4f7e41222a86f5c97150cc5f84a5 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sat, 1 Aug 2026 21:09:50 +0200 Subject: [PATCH 0583/1328] openrisc: 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 Signed-off-by: Stafford Horne --- arch/openrisc/mm/tlb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/openrisc/mm/tlb.c b/arch/openrisc/mm/tlb.c index 3115f2e4f864..2ee00bf301a4 100644 --- a/arch/openrisc/mm/tlb.c +++ b/arch/openrisc/mm/tlb.c @@ -74,11 +74,11 @@ void local_flush_tlb_all(void) #define flush_dtlb_page_eir(addr) mtspr(SPR_DTLBEIR, addr) #define flush_dtlb_page_no_eir(addr) \ - mtspr_off(SPR_DTLBMR_BASE(0), DTLB_OFFSET(addr), 0); + mtspr_off(SPR_DTLBMR_BASE(0), DTLB_OFFSET(addr), 0) #define flush_itlb_page_eir(addr) mtspr(SPR_ITLBEIR, addr) #define flush_itlb_page_no_eir(addr) \ - mtspr_off(SPR_ITLBMR_BASE(0), ITLB_OFFSET(addr), 0); + mtspr_off(SPR_ITLBMR_BASE(0), ITLB_OFFSET(addr), 0) void local_flush_tlb_page(struct vm_area_struct *vma, unsigned long addr) { From 3bff8f8e95fdc6ad19c8a1a8f87029094747e4bf Mon Sep 17 00:00:00 2001 From: Hui Su Date: Thu, 6 Aug 2026 22:23:04 +0800 Subject: [PATCH 0584/1328] cpufreq: schedutil: Fix rate limit overflow rate_limit_us is an unsigned int, while NSEC_PER_USEC is defined as 1000L. On 32-bit systems, the multiplication is therefore performed using 32-bit unsigned arithmetic before the result is assigned to freq_update_delay_ns. For example, writing 4294968 to rate_limit_us wraps the delay from 4294968000 ns to 704 ns. This makes schedutil update far more often than configured. Add sugov_update_rate_limit_us() to widen rate_limit_us to s64 before converting it to nanoseconds. Use the helper when updating the tunable through sysfs and when starting the governor, so both paths perform the conversion without overflow. Fixes: 9bdcb44e391d ("cpufreq: schedutil: New governor based on scheduler utilization data") Signed-off-by: Hui Su Reviewed-by: Zhongqiu Han Cc: All applicable Link: https://patch.msgid.link/20260806142304.1761454-1-sh_def@163.com Signed-off-by: Rafael J. Wysocki --- kernel/sched/cpufreq_schedutil.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/kernel/sched/cpufreq_schedutil.c b/kernel/sched/cpufreq_schedutil.c index a1782755efcc..49ccd6f1c185 100644 --- a/kernel/sched/cpufreq_schedutil.c +++ b/kernel/sched/cpufreq_schedutil.c @@ -62,6 +62,17 @@ static DEFINE_PER_CPU(struct sugov_cpu, sugov_cpu); /************************ Governor internals ***********************/ +static void sugov_update_rate_limit_us(struct sugov_policy *sg_policy) +{ + /* + * Cast rate_limit_us before multiplication to force 64-bit arithmetic. + * Otherwise, on 32-bit platforms, both operands are converted to + * 32-bit unsigned long and the multiplication may overflow. + */ + sg_policy->freq_update_delay_ns = + (s64)sg_policy->tunables->rate_limit_us * NSEC_PER_USEC; +} + static bool sugov_should_update_freq(struct sugov_policy *sg_policy, u64 time) { s64 delta_ns; @@ -608,7 +619,7 @@ rate_limit_us_store(struct gov_attr_set *attr_set, const char *buf, size_t count tunables->rate_limit_us = rate_limit_us; list_for_each_entry(sg_policy, &attr_set->policy_list, tunables_hook) - sg_policy->freq_update_delay_ns = rate_limit_us * NSEC_PER_USEC; + sugov_update_rate_limit_us(sg_policy); return count; } @@ -850,7 +861,7 @@ static int sugov_start(struct cpufreq_policy *policy) void (*uu)(struct update_util_data *data, u64 time, unsigned int flags); unsigned int cpu; - sg_policy->freq_update_delay_ns = sg_policy->tunables->rate_limit_us * NSEC_PER_USEC; + sugov_update_rate_limit_us(sg_policy); sg_policy->last_freq_update_time = 0; sg_policy->next_freq = 0; sg_policy->work_in_progress = false; From c659fa329ea25baa6f8ab01e73d9bc925911c4b2 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 5 Aug 2026 14:15:31 +0200 Subject: [PATCH 0585/1328] cpufreq: intel_pstate: Consolidate HWP P-states initialization After previous changes, intel_pstate_hybrid_hwp_adjust() does not do much and its name and kerneldoc comment (which is not really necessary because the function is static) have become a bit confusing. Moreover, the initialization of P-states on systems with HWP enabled is divided between it and a direct conditional statement branch in intel_pstate_get_cpu_pstates() which is not super-easy to follow. Address this by introducing intel_pstate_get_hwp_pstates() for the entire HWP-specific initialization of P-states and moving the code from intel_pstate_hybrid_hwp_adjust() into it along with some HWP-related code from intel_pstate_get_cpu_pstates(). No intentional functional impact. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/6021518.DvuYhMxLoT@rafael.j.wysocki --- drivers/cpufreq/intel_pstate.c | 113 ++++++++++++++++----------------- 1 file changed, 54 insertions(+), 59 deletions(-) diff --git a/drivers/cpufreq/intel_pstate.c b/drivers/cpufreq/intel_pstate.c index 7ce5981cbae5..8a068fe8320a 100644 --- a/drivers/cpufreq/intel_pstate.c +++ b/drivers/cpufreq/intel_pstate.c @@ -555,47 +555,6 @@ static int intel_pstate_freq_to_hwp(struct cpudata *cpu, int freq) return intel_pstate_freq_to_hwp_rel(cpu, freq, CPUFREQ_RELATION_L); } -/** - * intel_pstate_hybrid_hwp_adjust - Calibrate HWP performance levels. - * @cpu: Target CPU. - * - * On hybrid processors, HWP may expose more performance levels than there are - * P-states accessible through the PERF_CTL interface. If that happens, the - * scaling factor between HWP performance levels and CPU frequency will be less - * than the scaling factor between P-state values and CPU frequency. - * - * In that case, adjust the CPU parameters used in computations accordingly. - */ -static void intel_pstate_hybrid_hwp_adjust(struct cpudata *cpu) -{ - int perf_ctl_max_phys = cpu->pstate.max_pstate_physical; - int perf_ctl_scaling = cpu->pstate.perf_ctl_scaling; - int perf_ctl_turbo = pstate_funcs.get_turbo(cpu->cpu); - int scaling = cpu->pstate.scaling; - int freq; - - pr_debug("CPU%d: PERF_CTL max_phys = %d\n", cpu->cpu, perf_ctl_max_phys); - pr_debug("CPU%d: PERF_CTL turbo = %d\n", cpu->cpu, perf_ctl_turbo); - pr_debug("CPU%d: PERF_CTL scaling = %d\n", cpu->cpu, perf_ctl_scaling); - pr_debug("CPU%d: HWP_CAP guaranteed = %d\n", cpu->cpu, cpu->pstate.max_pstate); - pr_debug("CPU%d: HWP_CAP highest = %d\n", cpu->cpu, cpu->pstate.turbo_pstate); - pr_debug("CPU%d: HWP-to-frequency scaling factor: %d\n", cpu->cpu, scaling); - - if (scaling == perf_ctl_scaling) - return; - - hwp_is_hybrid = true; - - freq = perf_ctl_max_phys * perf_ctl_scaling; - cpu->pstate.max_pstate_physical = intel_pstate_freq_to_hwp(cpu, freq); - - /* - * Cast the min P-state value retrieved via pstate_funcs.get_min() to - * the effective range of HWP performance levels. - */ - cpu->pstate.min_pstate = intel_pstate_freq_to_hwp(cpu, cpu->pstate.min_freq); -} - static bool turbo_is_disabled(void) { u64 misc_en; @@ -2293,34 +2252,70 @@ static int hwp_get_cpu_scaling(int cpu) return intel_pstate_cppc_get_scaling(cpu); } +static void intel_pstate_get_hwp_pstates(struct cpudata *cpu) +{ + int perf_ctl_max_phys = cpu->pstate.max_pstate_physical; + int perf_ctl_scaling = cpu->pstate.perf_ctl_scaling; + int perf_ctl_turbo = cpu->pstate.turbo_pstate; + int cpuid = cpu->cpu; + + __intel_pstate_get_hwp_cap(cpu); + + if (!pstate_funcs.get_cpu_scaling) + return; + + pr_debug("CPU%d: PERF_CTL max_phys = %d\n", cpuid, perf_ctl_max_phys); + pr_debug("CPU%d: PERF_CTL turbo = %d\n", cpuid, perf_ctl_turbo); + pr_debug("CPU%d: PERF_CTL scaling = %d\n", cpuid, perf_ctl_scaling); + pr_debug("CPU%d: PERF_CTL min = %d\n", cpuid, cpu->pstate.min_pstate); + pr_debug("CPU%d: HWP_CAP guaranteed = %d\n", cpuid, cpu->pstate.max_pstate); + pr_debug("CPU%d: HWP_CAP highest = %d\n", cpuid, cpu->pstate.turbo_pstate); + + cpu->pstate.scaling = pstate_funcs.get_cpu_scaling(cpuid); + + pr_debug("CPU%d: HWP-to-frequency scaling = %d\n", cpuid, cpu->pstate.scaling); + + /* + * On hybrid processors, HWP may expose more performance levels than + * there are P-states accessible through the PERF_CTL interface. If + * that happens, the scaling between HWP performance levels and CPU + * frequency will be less than the scaling between P-state values and + * CPU frequency. In that case, update the maximum physical non-turbo + * performance level accordingly. + */ + if (cpu->pstate.scaling != perf_ctl_scaling) { + int freq; + + freq = perf_ctl_max_phys * perf_ctl_scaling; + cpu->pstate.max_pstate_physical = intel_pstate_freq_to_hwp(cpu, freq); + + freq = cpu->pstate.min_freq; + cpu->pstate.min_pstate = intel_pstate_freq_to_hwp(cpu, freq); + + hwp_is_hybrid = true; + } + /* + * If the CPU is going online for the first time and it was offline + * initially, asym capacity scaling may need to be updated. + */ + hybrid_update_capacity(cpu); +} + static void intel_pstate_get_cpu_pstates(struct cpudata *cpu) { int perf_ctl_scaling = pstate_funcs.get_scaling(); cpu->pstate.max_pstate_physical = pstate_funcs.get_max_physical(cpu->cpu); + cpu->pstate.turbo_pstate = pstate_funcs.get_turbo(cpu->cpu); cpu->pstate.min_pstate = pstate_funcs.get_min(cpu->cpu); cpu->pstate.min_freq = cpu->pstate.min_pstate * perf_ctl_scaling; cpu->pstate.perf_ctl_scaling = perf_ctl_scaling; + cpu->pstate.scaling = perf_ctl_scaling; - if (hwp_active) { - __intel_pstate_get_hwp_cap(cpu); - - if (pstate_funcs.get_cpu_scaling) { - cpu->pstate.scaling = pstate_funcs.get_cpu_scaling(cpu->cpu); - intel_pstate_hybrid_hwp_adjust(cpu); - } else { - cpu->pstate.scaling = perf_ctl_scaling; - } - /* - * If the CPU is going online for the first time and it was - * offline initially, asym capacity scaling needs to be updated. - */ - hybrid_update_capacity(cpu); - } else { - cpu->pstate.scaling = perf_ctl_scaling; + if (hwp_active) + intel_pstate_get_hwp_pstates(cpu); + else cpu->pstate.max_pstate = pstate_funcs.get_max(cpu->cpu); - cpu->pstate.turbo_pstate = pstate_funcs.get_turbo(cpu->cpu); - } intel_pstate_update_freq_limits(cpu); From d657aa1b8ed345eeb24e33967de2655547993fce Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 5 Aug 2026 14:18:11 +0200 Subject: [PATCH 0586/1328] cpufreq: intel_pstate: Avoid using DESIRED_PERF when DEC is enabled In principle, the desired performance level can be set in MSR_HWP_REQUEST to indicate to the processor what performance level the OS would like the given CPU to run at, but if the Dynamic Efficiency Control (DEC) feature is enabled in the processor, doing so may result in confusing the processor firmware. It is then better to let the processor firmware figure out the most suitable performance level by itself. Accordingly, make intel_pstate always set the desired performance level to zero (which means "no preference") when running on a platform with DEC enabled. Signed-off-by: Rafael J. Wysocki Acked-by: Srinivas Pandruvada Link: https://patch.msgid.link/4758098.LvFx2qVVIh@rafael.j.wysocki --- drivers/cpufreq/intel_pstate.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/cpufreq/intel_pstate.c b/drivers/cpufreq/intel_pstate.c index 8a068fe8320a..ceb340f7a110 100644 --- a/drivers/cpufreq/intel_pstate.c +++ b/drivers/cpufreq/intel_pstate.c @@ -303,6 +303,7 @@ static bool per_cpu_limits __ro_after_init; static bool hwp_forced __ro_after_init; static bool hwp_boost __read_mostly; static bool hwp_is_hybrid; +static u32 hwp_desired_mask __read_mostly = ~0U; static struct cpufreq_driver *intel_pstate_driver __read_mostly; @@ -3150,7 +3151,7 @@ static void intel_cpufreq_hwp_update(struct cpudata *cpu, u32 min, u32 max, value |= HWP_MAX_PERF(max); value &= ~HWP_DESIRED_PERF(~0L); - value |= HWP_DESIRED_PERF(desired); + value |= HWP_DESIRED_PERF(desired & hwp_desired_mask); if (value == prev) return; @@ -3760,9 +3761,9 @@ static bool hwp_check_epp(void) static bool hwp_check_dec(void) { - u64 power_ctl; + u64 power_ctl = 0; - rdmsrq(MSR_IA32_POWER_CTL, power_ctl); + rdmsrq_safe(MSR_IA32_POWER_CTL, &power_ctl); return !!(power_ctl & BIT(POWER_CTL_DEC_ENABLE)); } @@ -3787,6 +3788,7 @@ static int __init intel_pstate_init(void) id = x86_match_cpu(hwp_support_ids); if (id) { bool epp_present = hwp_check_epp(); + bool dec_present = hwp_check_dec(); /* * If HWP is enabled already, there is no choice but to deal @@ -3798,7 +3800,7 @@ static int __init intel_pstate_init(void) no_hwp = 0; } else if (no_load) { return -ENODEV; - } else if (!epp_present && !hwp_check_dec()) { + } else if (!epp_present && !dec_present) { /* * Avoid enabling HWP for processors without EPP support * unless the Dynamic Efficiency Control (DEC) enable @@ -3820,6 +3822,9 @@ static int __init intel_pstate_init(void) if (!default_driver) default_driver = &intel_pstate; + if (dec_present) + hwp_desired_mask = 0; + if (!id->driver_data) pstate_funcs.get_cpu_scaling = hwp_get_cpu_scaling; From 4ec11f14d1d6fdda787d991b142537be7841d395 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 12 Aug 2026 02:27:15 -0700 Subject: [PATCH 0587/1328] apparmor: fix deadlock in complain-mode change_hat The use of change_hat when in complain mode can cause a deadlock when the hat doesn't exist and a new learning profile is created for the missing profile. This is because change_hat() has taken the lock to search the hat list and creating the new learning profile needs to take the lock to add it to the list. From the bug report: Originally found in 7.0.0 in LTS ubuntu 26.04 with pam_apparmor + su in complain mode set to change hats. Then verified in newest available vanilla kernel I've compiled to see if still present: 7.2-rc7 vanilla -> affected checked also some other kernels: 6.18.44 vanilla -> affected 6.12.95 with debian patches -> unaffected On systems without bug (for example 6.12.95 debian) it just prints: aa_change_hat rc=0 On systems with bug, the executable always hangs, prints nothing and becomes unkillable. (And once stuck this way, it will cause any further hat changes to also cause the changing process to get stuck) Then in syslog you can find hint about cause: kernel: INFO: task hat:3409 blocked for more than 483 seconds. kernel: Not tainted 7.2.0-rc7 #1 kernel: "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message. kernel: task:hat state:D stack:0 pid:3409 tgid:3409 ppid:2605 task_flags:0x400000 flags:0x00080800 kernel: Call Trace: kernel: kernel: __schedule+0x48f/0xfe0 kernel: schedule+0x27/0xa0 kernel: schedule_preempt_disabled+0x15/0x30 kernel: __mutex_lock.constprop.0+0x569/0xa10 kernel: aa_new_learning_profile+0x15f/0x210 kernel: build_change_hat+0x19f/0x3b0 kernel: change_hat.isra.0+0x5dd/0xd60 kernel: aa_change_hat+0x2f3/0x710 kernel: aa_setprocattr_changehat+0x121/0x1f0 kernel: do_setattr+0x28c/0x340 kernel: apparmor_setselfattr+0x20/0x50 kernel: security_setselfattr+0xf6/0x110 kernel: __x64_sys_lsm_set_self_attr+0x53/0x90 kernel: do_syscall_64+0xdd/0x5e0 kernel: ? __mod_memcg_lruvec_state+0xfd/0x260 kernel: ? lruvec_stat_mod_folio+0x8d/0xd0 kernel: ? __folio_mod_stat+0x2d/0x90 kernel: ? map_anon_folio_pte_nopf+0xd1/0x1f0 kernel: ? do_anonymous_page+0x184/0xa10 kernel: ? __handle_mm_fault+0x805/0x870 kernel: ? count_memcg_events+0xef/0x230 kernel: ? handle_mm_fault+0x1f0/0x2f0 kernel: ? do_user_addr_fault+0x2bb/0x7b0 kernel: ? do_syscall_64+0x94/0x5e0 kernel: ? exc_page_fault+0x75/0x160 kernel: entry_SYSCALL_64_after_hwframe+0x76/0x7e kernel: RIP: 0033:0x7f815e134c8d kernel: RSP: 002b:00007fff6df94ea8 EFLAGS: 00000246 ORIG_RAX: 00000000000001cc kernel: RAX: ffffffffffffffda RBX: 0000556d8c81d040 RCX: 00007f815e134c8d kernel: RDX: 0000000000000046 RSI: 0000556d8c81d040 RDI: 0000000000000064 kernel: RBP: 00007fff6df94ef0 R08: 00007f815e212ac8 R09: 000000000000000c kernel: R10: 0000000000000000 R11: 0000000000000246 R12: 0000556d8c81d010 kernel: R13: 0000000000000026 R14: 0000000000000046 R15: 0000000000000064 kernel: kernel: INFO: task hat:3409 is blocked on a mutex likely owned by task hat:3409. To fix the issue, lift the locking out of the core of aa_new_learning_profile(), introduce a wrapper function that takes the lock where needed, and have build_change_hat() call the core function that no longer takes the lock. In addition fix 4 other issues introduced by commit 32e92764d6f8d ("apparmor: grab ns lock and refresh when looking up changehat child profiles") - aa_get_profile_rcu() was replaced-by: aa_get_profile without the accompanying rcu_dereference_protected() - an extra aa_get_label(label) was introduced at the start of change_hat() without an accompanying aa_put_label() causing a reference count leak. - a reference count leak was introduced in the label_is_stale(label) case, where the newest profile would be leaked instead of the label passed to the function. - a potential UAF when the lookup walks up the tree with new_ns != ns the new label reference is put, and then used for the next lookup. The mutex_lock, will block replacement, and removal in the locked ns. However there are two cases where putting the reference can result in the label being freed even with the lock held. 1. the label does not have a list reference (possible for temporary or special profiles) in which case the put can trigger the cleanup. 2. the new label reference is in a different namespace, which does not have a lock held on it. This extends case 1 to also include replacement, and removal that could be occurring in the namespace new is in. Reported-by: Martin Petricek Link: https://lists.ubuntu.com/archives/apparmor/2026-August/014907.html Fixes: 32e92764d6f8d ("apparmor: grab ns lock and refresh when looking up changehat child profiles") Signed-off-by: John Johansen --- security/apparmor/domain.c | 26 +++++++++++++++++--------- security/apparmor/include/policy.h | 3 +++ security/apparmor/policy.c | 22 +++++++++++++++++----- 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index e20d177a4357..af9e8431e3ea 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -1138,8 +1138,8 @@ static struct aa_label *build_change_hat(const struct cred *subj_cred, if (!hat) { error = -ENOENT; if (COMPLAIN_MODE(profile)) { - hat = aa_new_learning_profile(profile, true, name, - GFP_KERNEL); + hat = __aa_new_learning_profile(profile, true, name, + GFP_KERNEL); if (!hat) { info = "failed null profile create"; error = -ENOMEM; @@ -1177,6 +1177,7 @@ static struct aa_label *change_hat(const struct cred *subj_cred, bool sibling = false; const char *name, *info = NULL; int i, error; + bool needput = false; AA_BUG(!label); AA_BUG(!hats); @@ -1189,7 +1190,6 @@ static struct aa_label *change_hat(const struct cred *subj_cred, * the profiles and label, we can rely on the namespaces being live * and avoid incrementing their refcounts while grabbing the lock. */ - label = aa_get_label(label); ns = labels_ns(label); retry: @@ -1197,15 +1197,19 @@ static struct aa_label *change_hat(const struct cred *subj_cred, if (label_is_stale(label)) { new = aa_get_newest_label(label); new_ns = labels_ns(new); + + if (needput) + /* aa_put_label() is safe to call when under lock */ + aa_put_label(label); + label = new; + needput = true; + /* check if replaced with label in parent ns, and lock there */ if (new_ns != ns) { - aa_put_label(new); mutex_unlock(&ns->lock); ns = new_ns; - label = new; + /* retry will bottom out at the root of the tree */ goto retry; } - aa_put_label(label); - label = new; } if (PROFILE_IS_HAT(labels_profile(label))) @@ -1216,7 +1220,8 @@ static struct aa_label *change_hat(const struct cred *subj_cred, name = hats[i]; label_for_each_in_scope(it, labels_ns(label), label, profile) { if (sibling && PROFILE_IS_HAT(profile)) { - root = aa_get_profile(profile->parent); + root = aa_get_profile(rcu_dereference_protected(profile->parent, + mutex_is_locked(&ns->lock))); } else if (!sibling && !PROFILE_IS_HAT(profile)) { root = aa_get_profile(profile); } else { /* conflicting change type */ @@ -1277,6 +1282,8 @@ static struct aa_label *change_hat(const struct cred *subj_cred, } } mutex_unlock(&ns->lock); + if (needput) + aa_put_label(label); return ERR_PTR(error); build: @@ -1287,7 +1294,8 @@ static struct aa_label *change_hat(const struct cred *subj_cred, mutex_unlock(&ns->lock); AA_BUG(!new); /* return new label or error ptr */ - + if (needput) + aa_put_label(label); return new; } diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index 23b9f0e5df3c..0dcbf8cf1029 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -294,6 +294,9 @@ struct aa_profile *aa_alloc_profile(const char *name, struct aa_proxy *proxy, gfp_t gfp); struct aa_profile *aa_alloc_null(struct aa_profile *parent, const char *name, gfp_t gfp); +struct aa_profile *__aa_new_learning_profile(struct aa_profile *parent, + bool hat, const char *base, + gfp_t gfp); struct aa_profile *aa_new_learning_profile(struct aa_profile *parent, bool hat, const char *base, gfp_t gfp); void aa_free_profile(struct aa_profile *profile); diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index f6f1b72d7c3d..397f9ff78aeb 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -720,7 +720,7 @@ struct aa_profile *aa_alloc_null(struct aa_profile *parent, const char *name, } /** - * aa_new_learning_profile - create or find a null-X learning profile + * __aa_new_learning_profile - create or find a null-X learning profile * @parent: profile that caused this profile to be created (NOT NULL) * @hat: true if the null- learning profile is a hat * @base: name to base the null profile off of @@ -737,8 +737,9 @@ struct aa_profile *aa_alloc_null(struct aa_profile *parent, const char *name, * * Returns: new refcounted profile else NULL on failure */ -struct aa_profile *aa_new_learning_profile(struct aa_profile *parent, bool hat, - const char *base, gfp_t gfp) +struct aa_profile *__aa_new_learning_profile(struct aa_profile *parent, + bool hat, const char *base, + gfp_t gfp) { struct aa_profile *p, *profile; const char *bname; @@ -746,6 +747,7 @@ struct aa_profile *aa_new_learning_profile(struct aa_profile *parent, bool hat, size_t name_sz; AA_BUG(!parent); + AA_BUG(!mutex_is_locked(&parent->ns->lock)); if (base) { name_sz = strlen(parent->base.hname) + 8 + strlen(base); @@ -779,7 +781,6 @@ struct aa_profile *aa_new_learning_profile(struct aa_profile *parent, bool hat, if (hat) profile->label.flags |= FLAG_HAT; - mutex_lock_nested(&profile->ns->lock, profile->ns->level); p = __find_child(&parent->base.profiles, bname); if (p) { aa_free_profile(profile); @@ -787,7 +788,6 @@ struct aa_profile *aa_new_learning_profile(struct aa_profile *parent, bool hat, } else { __add_profile(&parent->base.profiles, profile); } - mutex_unlock(&profile->ns->lock); /* refcount released by caller */ out: @@ -801,6 +801,18 @@ struct aa_profile *aa_new_learning_profile(struct aa_profile *parent, bool hat, return NULL; } +struct aa_profile *aa_new_learning_profile(struct aa_profile *parent, bool hat, + const char *base, gfp_t gfp) +{ + struct aa_profile *profile; + + mutex_lock_nested(&parent->ns->lock, parent->ns->level); + profile = __aa_new_learning_profile(parent, hat, base, gfp); + mutex_unlock(&parent->ns->lock); + + return profile; +} + /** * replacement_allowed - test to see if replacement is allowed * @profile: profile to test if it can be replaced (MAYBE NULL) From 0fda52de8bbd4ca9a852c8a7ef6536cf82bd71fd Mon Sep 17 00:00:00 2001 From: Steffen Klassert Date: Mon, 17 Aug 2026 07:17:58 +0200 Subject: [PATCH 0588/1328] Revert "esp: do not unref managed frag pages in esp_ssg_unref()" This reverts commit 21697720ff43b8dfa25b8e8d9ca7f56f4597fc80. The patch does not fix the issue completely, so revert for now and wait for an updated version. Signed-off-by: Steffen Klassert --- net/ipv4/esp4.c | 7 ------- net/ipv6/esp6.c | 7 ------- 2 files changed, 14 deletions(-) diff --git a/net/ipv4/esp4.c b/net/ipv4/esp4.c index fa1710e27e50..dfc81ee969ae 100644 --- a/net/ipv4/esp4.c +++ b/net/ipv4/esp4.c @@ -104,13 +104,6 @@ static void esp_ssg_unref(struct xfrm_state *x, void *tmp, struct sk_buff *skb, struct aead_request *req; struct scatterlist *sg; - /* Managed frags are owned by the zerocopy ubuf; the skb holds no - * per-frag page reference, so we must not drop one here. Mirrors - * the SKBFL_MANAGED_FRAG_REFS handling in skb_release_data(). - */ - if (skb_zcopy_managed(skb)) - return; - if (x->props.flags & XFRM_STATE_ESN) extralen += sizeof(struct esp_output_extra); diff --git a/net/ipv6/esp6.c b/net/ipv6/esp6.c index 7d216b9c59f0..296b57926abb 100644 --- a/net/ipv6/esp6.c +++ b/net/ipv6/esp6.c @@ -121,13 +121,6 @@ static void esp_ssg_unref(struct xfrm_state *x, void *tmp, struct sk_buff *skb, struct aead_request *req; struct scatterlist *sg; - /* Managed frags are owned by the zerocopy ubuf; the skb holds no - * per-frag page reference, so we must not drop one here. Mirrors - * the SKBFL_MANAGED_FRAG_REFS handling in skb_release_data(). - */ - if (skb_zcopy_managed(skb)) - return; - if (x->props.flags & XFRM_STATE_ESN) extralen += sizeof(struct esp_output_extra); From bb23acfd05f0399f33dae000df2ef4a88787b7d0 Mon Sep 17 00:00:00 2001 From: Abhijit Gangurde Date: Fri, 17 Apr 2026 14:43:53 +0530 Subject: [PATCH 0589/1328] net: ionic: Fetch RCQ sign bit from firmware Read the rcq_sign_bit from the RDMA LIF identity reported by firmware. Signed-off-by: Abhijit Gangurde --- drivers/net/ethernet/pensando/ionic/ionic_if.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/pensando/ionic/ionic_if.h b/drivers/net/ethernet/pensando/ionic/ionic_if.h index 0a201422d0c5..3506f20a6718 100644 --- a/drivers/net/ethernet/pensando/ionic/ionic_if.h +++ b/drivers/net/ethernet/pensando/ionic/ionic_if.h @@ -555,6 +555,8 @@ enum ionic_lif_rdma_cap_stats { * @rdma.eq_qtype: RDMA Event Qtype * @rdma.stats_type: Supported statistics type * (enum ionic_lif_rdma_cap_stats) + * @rdma.rsvd: Reserved byte + * @rdma.rcq_sign_bit: RCQ sign bit * @rdma.rsvd1: Reserved byte(s) * @words: word access to struct contents */ @@ -600,7 +602,9 @@ union ionic_lif_identity { struct ionic_lif_logical_qtype cq_qtype; struct ionic_lif_logical_qtype eq_qtype; __le16 stats_type; - u8 rsvd1[162]; + u8 rsvd; + u8 rcq_sign_bit; + u8 rsvd1[160]; } __packed rdma; } __packed; __le32 words[478]; From 61461050da42401b484d03e0fdac02878d235fe6 Mon Sep 17 00:00:00 2001 From: Arnaud Bonnet Date: Mon, 22 Jun 2026 19:55:10 +0200 Subject: [PATCH 0590/1328] nfs: replace atomic bitops sequence with clear_and_wake_up_bit helper Commit 8236b0ae31c83 ("bdi: wake up concurrent wb_shutdown() callers.") introduces the clear_and_wake_up_bit() helper as a wrapper for the common clear -> barrier -> wake up bitops sequence. Use the helper in nfs_clear_invalid_mapping as inode.c already relies on functions from and to homogenize with other subsystems. Suggested-by: Agatha Isabelle Moreira Link: https://kernelnewbies.org/Beginner%20Cleanup%20and%20Refactor%20Tasks%20by%20Agatha%20Isabelle%20Moreira#task_007 Fixes: d529ef83c355 ("NFS: fix the handling of NFS_INO_INVALID_DATA flag in nfs_revalidate_mapping") Signed-off-by: Arnaud Bonnet Signed-off-by: Trond Myklebust --- fs/nfs/inode.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c index 5bcd4027d203..e538736bb165 100644 --- a/fs/nfs/inode.c +++ b/fs/nfs/inode.c @@ -1531,9 +1531,7 @@ int nfs_clear_invalid_mapping(struct address_space *mapping) ret = nfs_invalidate_mapping(inode, mapping); trace_nfs_invalidate_mapping_exit(inode, ret); - clear_bit_unlock(NFS_INO_INVALIDATING, bitlock); - smp_mb__after_atomic(); - wake_up_bit(bitlock, NFS_INO_INVALIDATING); + clear_and_wake_up_bit(NFS_INO_INVALIDATING, bitlock); out: return ret; } From 187bfc974eefa9e5d88a0b4ee9d08ae8fe485df4 Mon Sep 17 00:00:00 2001 From: Arnaud Bonnet Date: Mon, 22 Jun 2026 19:55:11 +0200 Subject: [PATCH 0591/1328] nfs: refactor pNFS functions using clear_and_wake_up_bit Commit 8236b0ae31c83 ("bdi: wake up concurrent wb_shutdown() callers.") introduces the clear_and_wake_up_bit() helper as a wrapper for the common clear -> barrier -> wake up bitops sequence. The file pnfs.c has several helpers with identical contents. Thus they are replaced with the more recent clean_and_wake_up_bit() global helper which describes accurately its effects at the call and still specifies the cleared bit. This also homogenizes the code with other subsystems. Since the helpers are no longer used after this, they can be safely removed. Suggested-by: Agatha Isabelle Moreira Link: https://kernelnewbies.org/Beginner%20Cleanup%20and%20Refactor%20Tasks%20by%20Agatha%20Isabelle%20Moreira#task_007 Fixes: d67ae825a59d ("pnfs/flexfiles: Add the FlexFile Layout Driver") Signed-off-by: Arnaud Bonnet Signed-off-by: Trond Myklebust --- fs/nfs/pnfs.c | 35 ++++++++++------------------------- 1 file changed, 10 insertions(+), 25 deletions(-) diff --git a/fs/nfs/pnfs.c b/fs/nfs/pnfs.c index 7715e2bd5871..99c50a1fde2b 100644 --- a/fs/nfs/pnfs.c +++ b/fs/nfs/pnfs.c @@ -2100,15 +2100,6 @@ static bool pnfs_is_first_layoutget(struct pnfs_layout_hdr *lo) return test_bit(NFS_LAYOUT_FIRST_LAYOUTGET, &lo->plh_flags); } -static void pnfs_clear_first_layoutget(struct pnfs_layout_hdr *lo) -{ - unsigned long *bitlock = &lo->plh_flags; - - clear_bit_unlock(NFS_LAYOUT_FIRST_LAYOUTGET, bitlock); - smp_mb__after_atomic(); - wake_up_bit(bitlock, NFS_LAYOUT_FIRST_LAYOUTGET); -} - static void _add_to_server_list(struct pnfs_layout_hdr *lo, struct nfs_server *server) { @@ -2284,7 +2275,8 @@ pnfs_update_layout(struct inode *ino, iomode, lo, lseg, PNFS_UPDATE_LAYOUT_INVALID_OPEN); nfs4_schedule_stateid_recovery(server, ctx->state); - pnfs_clear_first_layoutget(lo); + clear_and_wake_up_bit(NFS_LAYOUT_FIRST_LAYOUTGET, + &lo->plh_flags); pnfs_put_layout_hdr(lo); goto lookup_again; } @@ -2353,7 +2345,8 @@ pnfs_update_layout(struct inode *ino, if (!exception.retry) goto out_put_layout_hdr; if (first) - pnfs_clear_first_layoutget(lo); + clear_and_wake_up_bit(NFS_LAYOUT_FIRST_LAYOUTGET, + &lo->plh_flags); trace_pnfs_update_layout(ino, pos, count, iomode, lo, lseg, PNFS_UPDATE_LAYOUT_RETRY); pnfs_put_layout_hdr(lo); @@ -2365,7 +2358,7 @@ pnfs_update_layout(struct inode *ino, out_put_layout_hdr: if (first) - pnfs_clear_first_layoutget(lo); + clear_and_wake_up_bit(NFS_LAYOUT_FIRST_LAYOUTGET, &lo->plh_flags); trace_pnfs_update_layout(ino, pos, count, iomode, lo, lseg, PNFS_UPDATE_LAYOUT_EXIT); pnfs_put_layout_hdr(lo); @@ -2457,7 +2450,7 @@ static void _lgopen_prepare_attached(struct nfs4_opendata *data, lgp = pnfs_alloc_init_layoutget_args(ino, ctx, ¤t_stateid, &rng, nfs_io_gfp_mask()); if (!lgp) { - pnfs_clear_first_layoutget(lo); + clear_and_wake_up_bit(NFS_LAYOUT_FIRST_LAYOUTGET, &lo->plh_flags); nfs_layoutget_end(lo); pnfs_put_layout_hdr(lo); return; @@ -2561,7 +2554,8 @@ void nfs4_lgopen_release(struct nfs4_layoutget *lgp) { if (lgp != NULL) { if (lgp->lo) { - pnfs_clear_first_layoutget(lgp->lo); + clear_and_wake_up_bit(NFS_LAYOUT_FIRST_LAYOUTGET, + &lgp->lo->plh_flags); nfs_layoutget_end(lgp->lo); } pnfs_layoutget_free(lgp); @@ -3273,15 +3267,6 @@ pnfs_generic_pg_readpages(struct nfs_pageio_descriptor *desc) } EXPORT_SYMBOL_GPL(pnfs_generic_pg_readpages); -static void pnfs_clear_layoutcommitting(struct inode *inode) -{ - unsigned long *bitlock = &NFS_I(inode)->flags; - - clear_bit_unlock(NFS_INO_LAYOUTCOMMITTING, bitlock); - smp_mb__after_atomic(); - wake_up_bit(bitlock, NFS_INO_LAYOUTCOMMITTING); -} - /* * There can be multiple RW segments. */ @@ -3306,7 +3291,7 @@ static void pnfs_list_write_lseg_done(struct inode *inode, struct list_head *lis pnfs_put_lseg(lseg); } - pnfs_clear_layoutcommitting(inode); + clear_and_wake_up_bit(NFS_INO_LAYOUTCOMMITTING, &NFS_I(inode)->flags); } void pnfs_set_lo_fail(struct pnfs_layout_segment *lseg) @@ -3446,7 +3431,7 @@ pnfs_layoutcommit_inode(struct inode *inode, bool sync) spin_unlock(&inode->i_lock); kfree(data); clear_layoutcommitting: - pnfs_clear_layoutcommitting(inode); + clear_and_wake_up_bit(NFS_INO_LAYOUTCOMMITTING, &NFS_I(inode)->flags); goto out; } EXPORT_SYMBOL_GPL(pnfs_layoutcommit_inode); From d05c2007b3d84ccba11dc6e9cb3202768cc72f14 Mon Sep 17 00:00:00 2001 From: Ruoyu Wang Date: Wed, 24 Jun 2026 11:58:58 +0800 Subject: [PATCH 0592/1328] NFSv4: remove callback IDR entry on client allocation failure nfs4_alloc_client() allocates an NFSv4.0 callback identifier before it finishes setting up the client. If any later initialization step fails, the error path frees the nfs_client directly with nfs_free_client(). That bypasses nfs_put_client(), which is where the callback IDR entry is removed during normal teardown. A failed allocation can therefore leave cb_ident_idr pointing at a freed nfs_client. A later NFSv4.0 callback lookup by cb_ident would find the stale pointer and take a reference to it. Make the callback IDR removal helper callable by the allocation failure path, and remove the callback identifier before freeing the client. This was found by a local static-analysis checker for publish-before-free lifetime bugs and confirmed by manual inspection. Fixes: f4eecd5da342 ("NFS implement v4.0 callback_ident") Signed-off-by: Ruoyu Wang Signed-off-by: Trond Myklebust --- fs/nfs/client.c | 14 +++++++++++++- fs/nfs/internal.h | 1 + fs/nfs/nfs4client.c | 1 + 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/fs/nfs/client.c b/fs/nfs/client.c index 4dcb91ab3039..60386330aeec 100644 --- a/fs/nfs/client.c +++ b/fs/nfs/client.c @@ -215,10 +215,22 @@ static void nfs_cb_idr_remove_locked(struct nfs_client *clp) { struct nfs_net *nn = net_generic(clp->cl_net, nfs_net_id); - if (clp->cl_cb_ident) + if (clp->cl_cb_ident) { idr_remove(&nn->cb_ident_idr, clp->cl_cb_ident); + clp->cl_cb_ident = 0; + } } +void nfs_cb_idr_remove(struct nfs_client *clp) +{ + struct nfs_net *nn = net_generic(clp->cl_net, nfs_net_id); + + spin_lock(&nn->nfs_client_lock); + nfs_cb_idr_remove_locked(clp); + spin_unlock(&nn->nfs_client_lock); +} +EXPORT_SYMBOL_GPL(nfs_cb_idr_remove); + static void pnfs_init_server(struct nfs_server *server) { rpc_init_wait_queue(&server->roc_rpcwaitq, "pNFS ROC"); diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h index e4533f583632..864fa092bcea 100644 --- a/fs/nfs/internal.h +++ b/fs/nfs/internal.h @@ -225,6 +225,7 @@ void nfs_server_copy_userdata(struct nfs_server *, struct nfs_server *); extern void nfs_put_client(struct nfs_client *); extern void nfs_free_client(struct nfs_client *); +void nfs_cb_idr_remove(struct nfs_client *clp); extern struct nfs_client *nfs4_find_client_ident(struct net *, int); extern struct nfs_client * nfs4_find_client_sessionid(struct net *, const struct sockaddr *, diff --git a/fs/nfs/nfs4client.c b/fs/nfs/nfs4client.c index 71c271a1700a..aff019d2842d 100644 --- a/fs/nfs/nfs4client.c +++ b/fs/nfs/nfs4client.c @@ -261,6 +261,7 @@ struct nfs_client *nfs4_alloc_client(const struct nfs_client_initdata *cl_init) return clp; error: + nfs_cb_idr_remove(clp); nfs_free_client(clp); return ERR_PTR(err); } From c056f817e4200fb18079d5052c273a22f191ff0a Mon Sep 17 00:00:00 2001 From: ZhangGuoDong Date: Thu, 25 Jun 2026 11:20:38 +0800 Subject: [PATCH 0593/1328] pnfs/blocklayout: Fix device leaks on parse failure bl_parse_concat() and bl_parse_stripe() allocate a child device array and then parse each child in turn. If parsing a child fails, the failed child is not counted in nr_children and the parent may be left with a children array that bl_free_device() will not release when nr_children is zero. Release the failed child and the already parsed children before returning the error. Also make bl_free_device() release the child array whenever the children pointer is set, so that partially initialised concat or stripe devices are cleaned up correctly. bl_parse_scsi() can also fail after assigning d->bdev_file and dropping the file reference. Clear the pointer after fput() so that an outer cleanup path does not put it again. Fixes: 5c83746a0cf2 ("pnfs/blocklayout: in-kernel GETDEVICEINFO XDR parsing") Signed-off-by: ZhangGuoDong Signed-off-by: Trond Myklebust --- fs/nfs/blocklayout/dev.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/fs/nfs/blocklayout/dev.c b/fs/nfs/blocklayout/dev.c index bb35f88501ce..db4bb0a6283e 100644 --- a/fs/nfs/blocklayout/dev.c +++ b/fs/nfs/blocklayout/dev.c @@ -85,15 +85,17 @@ bl_free_device(struct pnfs_block_dev *dev) { bl_unregister_dev(dev); - if (dev->nr_children) { + if (dev->children) { int i; for (i = 0; i < dev->nr_children; i++) bl_free_device(&dev->children[i]); kfree(dev->children); - } else { - if (dev->bdev_file) - fput(dev->bdev_file); + dev->children = NULL; + dev->nr_children = 0; + } else if (dev->bdev_file) { + fput(dev->bdev_file); + dev->bdev_file = NULL; } } @@ -437,6 +439,7 @@ bl_parse_scsi(struct nfs_server *server, struct pnfs_block_dev *d, out_blkdev_put: fput(d->bdev_file); + d->bdev_file = NULL; return error; } @@ -472,8 +475,11 @@ bl_parse_concat(struct nfs_server *server, struct pnfs_block_dev *d, for (i = 0; i < v->concat.volumes_count; i++) { ret = bl_parse_deviceid(server, &d->children[i], volumes, v->concat.volumes[i], gfp_mask); - if (ret) + if (ret) { + bl_free_device(&d->children[i]); + bl_free_device(d); return ret; + } d->nr_children++; d->children[i].start += len; @@ -501,8 +507,11 @@ bl_parse_stripe(struct nfs_server *server, struct pnfs_block_dev *d, for (i = 0; i < v->stripe.volumes_count; i++) { ret = bl_parse_deviceid(server, &d->children[i], volumes, v->stripe.volumes[i], gfp_mask); - if (ret) + if (ret) { + bl_free_device(&d->children[i]); + bl_free_device(d); return ret; + } d->nr_children++; len += d->children[i].len; From ba0f097418c7d58cbcfaaab5ddb46e2b9576974c Mon Sep 17 00:00:00 2001 From: Benjamin Coddington Date: Thu, 25 Jun 2026 08:05:48 -0400 Subject: [PATCH 0594/1328] pNFS: report clora_changed in the cb_layoutrecall_file tracepoint A CB_LAYOUTRECALL carries the clora_changed flag (RFC 8881, Section 20.3.3), which tells the client whether the server is changing the layout (and therefore whether the client should flush modified data to the storage devices before returning, or stop writing to them and go through the metadata server). The client decodes this into cbl_layoutchanged, but it is otherwise invisible. Give nfs4_cb_layoutrecall_file its own event definition and report clora_changed, so the intent of a recall can be observed in a trace. Signed-off-by: Benjamin Coddington Signed-off-by: Trond Myklebust --- fs/nfs/callback_proc.c | 2 +- fs/nfs/nfs4trace.h | 55 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/fs/nfs/callback_proc.c b/fs/nfs/callback_proc.c index 10f2354ba304..f5cf76d36367 100644 --- a/fs/nfs/callback_proc.c +++ b/fs/nfs/callback_proc.c @@ -317,7 +317,7 @@ static u32 initiate_file_draining(struct nfs_client *clp, nfs_iput_and_deactive(ino); out_noput: trace_nfs4_cb_layoutrecall_file(clp, &args->cbl_fh, ino, - &args->cbl_stateid, -rv); + &args->cbl_stateid, args->cbl_layoutchanged, -rv); return rv; } diff --git a/fs/nfs/nfs4trace.h b/fs/nfs/nfs4trace.h index 1ed677810d9d..e679507eccb6 100644 --- a/fs/nfs/nfs4trace.h +++ b/fs/nfs/nfs4trace.h @@ -1515,7 +1515,60 @@ DECLARE_EVENT_CLASS(nfs4_inode_stateid_callback_event, ), \ TP_ARGS(clp, fhandle, inode, stateid, error)) DEFINE_NFS4_INODE_STATEID_CALLBACK_EVENT(nfs4_cb_recall); -DEFINE_NFS4_INODE_STATEID_CALLBACK_EVENT(nfs4_cb_layoutrecall_file); + +TRACE_EVENT(nfs4_cb_layoutrecall_file, + TP_PROTO( + const struct nfs_client *clp, + const struct nfs_fh *fhandle, + const struct inode *inode, + const nfs4_stateid *stateid, + unsigned int changed, + int error + ), + + TP_ARGS(clp, fhandle, inode, stateid, changed, error), + + TP_STRUCT__entry( + __field(unsigned long, error) + __field(dev_t, dev) + __field(u32, fhandle) + __field(u64, fileid) + __string(dstaddr, clp ? clp->cl_hostname : "unknown") + __field(int, stateid_seq) + __field(u32, stateid_hash) + __field(unsigned int, changed) + ), + + TP_fast_assign( + __entry->error = error < 0 ? -error : 0; + __entry->fhandle = nfs_fhandle_hash(fhandle); + if (!IS_ERR_OR_NULL(inode)) { + __entry->fileid = inode->i_ino; + __entry->dev = inode->i_sb->s_dev; + } else { + __entry->fileid = 0; + __entry->dev = 0; + } + __assign_str(dstaddr); + __entry->stateid_seq = + be32_to_cpu(stateid->seqid); + __entry->stateid_hash = + nfs_stateid_hash(stateid); + __entry->changed = changed; + ), + + TP_printk( + "error=%ld (%s) fileid=%02x:%02x:%llu fhandle=0x%08x " + "stateid=%d:0x%08x dstaddr=%s clora_changed=%u", + -__entry->error, + show_nfs4_status(__entry->error), + MAJOR(__entry->dev), MINOR(__entry->dev), + (unsigned long long)__entry->fileid, + __entry->fhandle, + __entry->stateid_seq, __entry->stateid_hash, + __get_str(dstaddr), __entry->changed + ) +); #define show_stateid_type(type) \ __print_symbolic(type, \ From aceaa5991bdf744e9c3fa1e5ad73405b1801eaab Mon Sep 17 00:00:00 2001 From: Benjamin Coddington Date: Thu, 25 Jun 2026 08:05:49 -0400 Subject: [PATCH 0595/1328] pNFS: honor clora_changed when recalling a layout When the metadata server recalls a layout with clora_changed FALSE, the layout is not changing and the client may complete its modified writes to the storage devices before returning the layout (RFC 8881, Section 20.3.3). Only when clora_changed is TRUE -- the server is restriping, or a storage device has failed -- should the client stop writing to the storage devices and redirect through the metadata server. Since commit b739a5bd9d9f ("NFSv4/flexfiles: Cancel I/O if the layout is recalled or revoked") the client cancels in-flight I/O on every recall, regardless of clora_changed. For an unchanged recall this abandons writes whose data may already have reached the storage device; such a write can then land after the LAYOUTRETURN, which the server sees as a write without a layout. Pass the recall's clora_changed value through pnfs_mark_matching_lsegs_return() and only cancel in-flight I/O when the layout is actually changing. When it is not, the existing deferred return path waits for the in-flight writes to drain before sending the LAYOUTRETURN. Other callers, which are tearing down or returning the layout for their own reasons, continue to cancel as before. Signed-off-by: Benjamin Coddington Signed-off-by: Trond Myklebust --- fs/nfs/callback_proc.c | 3 ++- fs/nfs/pnfs.c | 22 +++++++++++++--------- fs/nfs/pnfs.h | 2 +- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/fs/nfs/callback_proc.c b/fs/nfs/callback_proc.c index f5cf76d36367..3fb10c8e4271 100644 --- a/fs/nfs/callback_proc.c +++ b/fs/nfs/callback_proc.c @@ -291,7 +291,8 @@ static u32 initiate_file_draining(struct nfs_client *clp, pnfs_set_layout_stateid(lo, &args->cbl_stateid, NULL, true); switch (pnfs_mark_matching_lsegs_return(lo, &free_me_list, &args->cbl_range, - be32_to_cpu(args->cbl_stateid.seqid))) { + be32_to_cpu(args->cbl_stateid.seqid), + args->cbl_layoutchanged)) { case 0: case -EBUSY: /* There are layout segments that need to be returned */ diff --git a/fs/nfs/pnfs.c b/fs/nfs/pnfs.c index 99c50a1fde2b..9a08fd076e0a 100644 --- a/fs/nfs/pnfs.c +++ b/fs/nfs/pnfs.c @@ -432,7 +432,8 @@ bool nfs4_layout_refresh_old_stateid(nfs4_stateid *dst, goto out; } /* Try to update the seqid to the most recent */ - err = pnfs_mark_matching_lsegs_return(lo, &head, &range, 0); + err = pnfs_mark_matching_lsegs_return(lo, &head, &range, 0, + true); if (err != -EBUSY) { dst->seqid = lo->plh_stateid.seqid; *dst_range = range; @@ -486,7 +487,7 @@ static int pnfs_mark_layout_stateid_return(struct pnfs_layout_hdr *lo, .length = NFS4_MAX_UINT64, }; - return pnfs_mark_matching_lsegs_return(lo, lseg_list, &range, seq); + return pnfs_mark_matching_lsegs_return(lo, lseg_list, &range, seq, true); } static int @@ -524,7 +525,7 @@ pnfs_layout_io_set_failed(struct pnfs_layout_hdr *lo, u32 iomode) spin_lock(&inode->i_lock); pnfs_layout_set_fail_bit(lo, pnfs_iomode_to_fail_bit(iomode)); - pnfs_mark_matching_lsegs_return(lo, &head, &range, 0); + pnfs_mark_matching_lsegs_return(lo, &head, &range, 0, true); spin_unlock(&inode->i_lock); pnfs_free_lseg_list(&head); dprintk("%s Setting layout IOMODE_%s fail bit\n", __func__, @@ -1461,7 +1462,7 @@ _pnfs_return_layout(struct inode *ino) } valid_layout = pnfs_layout_is_valid(lo); pnfs_clear_layoutcommit(ino, &tmp_list); - pnfs_mark_matching_lsegs_return(lo, &tmp_list, &range, 0); + pnfs_mark_matching_lsegs_return(lo, &tmp_list, &range, 0, true); /* Don't send a LAYOUTRETURN if list was initially empty */ @@ -2615,7 +2616,7 @@ pnfs_layout_process(struct nfs4_layoutget *lgp) .iomode = IOMODE_ANY, .length = NFS4_MAX_UINT64, }; - pnfs_mark_matching_lsegs_return(lo, &free_me, &range, 0); + pnfs_mark_matching_lsegs_return(lo, &free_me, &range, 0, true); goto out_forget; } else { /* We have a completely new layout */ @@ -2646,6 +2647,7 @@ pnfs_layout_process(struct nfs4_layoutget *lgp) * @tmp_list: list header to be used with pnfs_free_lseg_list() * @return_range: describe layout segment ranges to be returned * @seq: stateid seqid to match + * @cancel_io: signal io be cancelled * * This function is mainly intended for use by layoutrecall. It attempts * to free the layout segment immediately, or else to mark it for return @@ -2660,7 +2662,7 @@ int pnfs_mark_matching_lsegs_return(struct pnfs_layout_hdr *lo, struct list_head *tmp_list, const struct pnfs_layout_range *return_range, - u32 seq) + u32 seq, bool cancel_io) { struct pnfs_layout_segment *lseg, *next; struct nfs_server *server = NFS_SERVER(lo->plh_inode); @@ -2686,7 +2688,8 @@ pnfs_mark_matching_lsegs_return(struct pnfs_layout_hdr *lo, continue; remaining++; set_bit(NFS_LSEG_LAYOUTRETURN, &lseg->pls_flags); - pnfs_lseg_cancel_io(server, lseg); + if (cancel_io) + pnfs_lseg_cancel_io(server, lseg); } if (remaining) { @@ -2721,7 +2724,8 @@ pnfs_mark_layout_for_return(struct inode *inode, * segments at hand when sending layoutreturn. See pnfs_put_lseg() * for how it works. */ - if (pnfs_mark_matching_lsegs_return(lo, &lo->plh_return_segs, range, 0) != -EBUSY) { + if (pnfs_mark_matching_lsegs_return(lo, &lo->plh_return_segs, range, 0, + true) != -EBUSY) { const struct cred *cred; nfs4_stateid stateid; enum pnfs_iomode iomode; @@ -2836,7 +2840,7 @@ static int pnfs_layout_return_unused_byserver(struct nfs_server *server, pnfs_get_layout_hdr(lo); pnfs_set_plh_return_info(lo, range->iomode, 0); if (pnfs_mark_matching_lsegs_return(lo, &lo->plh_return_segs, - range, 0) != 0 || + range, 0, true) != 0 || !pnfs_prepare_layoutreturn(lo, &stateid, &cred, &iomode)) { spin_unlock(&inode->i_lock); rcu_read_unlock(); diff --git a/fs/nfs/pnfs.h b/fs/nfs/pnfs.h index eb39859c216c..673c2b244978 100644 --- a/fs/nfs/pnfs.h +++ b/fs/nfs/pnfs.h @@ -300,7 +300,7 @@ int pnfs_mark_matching_lsegs_invalid(struct pnfs_layout_hdr *lo, int pnfs_mark_matching_lsegs_return(struct pnfs_layout_hdr *lo, struct list_head *tmp_list, const struct pnfs_layout_range *recall_range, - u32 seq); + u32 seq, bool cancel_io); int pnfs_mark_layout_stateid_invalid(struct pnfs_layout_hdr *lo, struct list_head *lseg_list); bool pnfs_roc(struct inode *ino, struct nfs4_layoutreturn_args *args, From 4a013b0e881e0466cc2c6a5518d2f6cda3a4f19f Mon Sep 17 00:00:00 2001 From: Benjamin Coddington Date: Thu, 25 Jun 2026 08:05:50 -0400 Subject: [PATCH 0596/1328] NFSv4/flexfiles: report cancelled I/O as a layout error When a layout is recalled or revoked the client cancels its in-flight I/O so the layout can be returned. The metadata server needs to learn that this I/O to the storage device did not complete, so that it can reconcile the affected mirror instance (or, if none remains, take other action). The cancellation completed with -EAGAIN, which ff_layout_io_track_ds_error() does not recognise: it fell through the switch and recorded nothing, so no error was reported to the server. -EAGAIN is overloaded in the RPC layer, so rather than key the reporting on it, cancel the I/O with -ECANCELED and map that to NFS4ERR_NXIO in ff_layout_io_track_ds_error() -- the status the client already reports for the transport errors that leave an in-flight write incomplete. The cancelled I/O is then reported to the server via LAYOUTERROR / LAYOUTRETURN. Unlike a genuine transport error, though, we aborted the I/O ourselves and have no evidence the device is at fault, so once the error is recorded we skip marking the device unreachable and forcing a further layout return. The retry disposition is unchanged from the original -EAGAIN cancellation: both NFS4ERR_NXIO and -ECANCELED are no-ops in ff_layout_async_handle_error(), which still resets the I/O to pNFS (or the MDS), so it is re-driven as before. Signed-off-by: Benjamin Coddington Signed-off-by: Trond Myklebust --- fs/nfs/flexfilelayout/flexfilelayout.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/fs/nfs/flexfilelayout/flexfilelayout.c b/fs/nfs/flexfilelayout/flexfilelayout.c index c4aa995026f6..c8072f333236 100644 --- a/fs/nfs/flexfilelayout/flexfilelayout.c +++ b/fs/nfs/flexfilelayout/flexfilelayout.c @@ -1543,6 +1543,17 @@ static void ff_layout_io_track_ds_error(struct pnfs_layout_segment *lseg, case -EACCES: *op_status = status = NFS4ERR_ACCESS; break; + case -ECANCELED: + /* + * In-flight I/O we cancelled to return a recalled or + * revoked layout. Report it as a failure to reach the + * device (NFS4ERR_NXIO), like the transport errors + * above, so the server can reconcile the affected mirror + * instance. We aborted the I/O ourselves rather than + * observe the device fail, so don't condemn it below. + */ + *op_status = status = NFS4ERR_NXIO; + break; default: return; } @@ -1553,6 +1564,15 @@ static void ff_layout_io_track_ds_error(struct pnfs_layout_segment *lseg, mirror, dss_id, offset, length, status, opnum, nfs_io_gfp_mask()); + /* + * I/O we cancelled ourselves to return a recalled or revoked layout + * is reported above so the server can reconcile the mirror, but we + * have no evidence the device is at fault: don't mark it unreachable + * or force a return. + */ + if (error == -ECANCELED) + goto out; + switch (status) { case NFS4ERR_DELAY: case NFS4ERR_GRACE: @@ -1572,6 +1592,7 @@ static void ff_layout_io_track_ds_error(struct pnfs_layout_segment *lseg, lseg); } +out: dprintk("%s: err %d op %d status %u\n", __func__, err, opnum, status); } @@ -2462,7 +2483,7 @@ static void ff_layout_cancel_io(struct pnfs_layout_segment *lseg) clnt = ds_clp->cl_rpcclient; if (!clnt) continue; - if (!rpc_cancel_tasks(clnt, -EAGAIN, + if (!rpc_cancel_tasks(clnt, -ECANCELED, ff_layout_match_io, lseg)) continue; rpc_clnt_disconnect(clnt); From 86ff1842795b3e51f526460b1d701d48fdee49b8 Mon Sep 17 00:00:00 2001 From: Tom Haynes Date: Mon, 27 Jul 2026 17:09:38 -0400 Subject: [PATCH 0597/1328] nfs4.2: add UNCACHEABLE_FILE_DATA attribute support Recognize the NFSv4.2 per-file UNCACHEABLE_FILE_DATA attribute (attr 87, draft-ietf-nfsv4-uncacheable-files): decode it via GETATTR, track per- exported-filesystem support, and record on the inode whether a regular file's data must not be cached. Acting on the attribute (opening such files O_DIRECT) is done by a subsequent change. If the NFSv4 server reports a regular file's UNCACHEABLE_FILE_DATA as true, it indicates the file's data must not be cached; the client records this in NFS_I(inode)->uncacheable_file_data for use by the I/O paths. The UNCACHEABLE_FILE_DATA attribute applies only to regular files (NF4REG); per the draft a server MUST reject a query of it on any other object type with NFS4ERR_INVAL. A subsequent commit gates the client accordingly. Link: https://datatracker.ietf.org/doc/draft-ietf-nfsv4-uncacheable-files/ Signed-off-by: Tom Haynes [snitzer: adapt Tom's original code focused on metadata for ABE] Co-developed-by: Mike Snitzer Signed-off-by: Mike Snitzer Signed-off-by: Mike Snitzer Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Trond Myklebust --- fs/nfs/inode.c | 22 +++++++++++++++++++--- fs/nfs/nfs4proc.c | 15 ++++++++++++--- fs/nfs/nfs4trace.h | 3 ++- fs/nfs/nfs4xdr.c | 35 ++++++++++++++++++++++++++++++++++- fs/nfs/nfstrace.h | 3 ++- include/linux/nfs4.h | 9 +++++++++ include/linux/nfs_fs.h | 3 +++ include/linux/nfs_xdr.h | 8 +++++++- 8 files changed, 88 insertions(+), 10 deletions(-) diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c index e538736bb165..e98b1f755e95 100644 --- a/fs/nfs/inode.c +++ b/fs/nfs/inode.c @@ -507,6 +507,7 @@ nfs_fhget(struct super_block *sb, struct nfs_fh *fh, struct nfs_fattr *fattr) inode->i_blocks = 0; nfsi->write_io = 0; nfsi->read_io = 0; + nfsi->uncacheable_file_data = false; nfsi->read_cache_jiffies = fattr->time_start; nfsi->attr_gencount = fattr->gencount; @@ -561,6 +562,11 @@ nfs_fhget(struct super_block *sb, struct nfs_fh *fh, struct nfs_fattr *fattr) } else if (fattr_supported & NFS_ATTR_FATTR_SPACE_USED && fattr->size != 0) nfs_set_cache_invalid(inode, NFS_INO_INVALID_BLOCKS); + if (fattr->valid & NFS_ATTR_FATTR_UNCACHEABLE_FILE_DATA) + nfsi->uncacheable_file_data = + fattr->aux_flags & NFS_AUX_UNCACHEABLE_FILE_DATA; + else if (fattr_supported & NFS_ATTR_FATTR_UNCACHEABLE_FILE_DATA) + nfs_set_cache_invalid(inode, NFS_INO_INVALID_UNCACHEABLE_FILE_DATA); nfs_setsecurity(inode, fattr); @@ -1973,7 +1979,8 @@ static int nfs_inode_finish_partial_attr_update(const struct nfs_fattr *fattr, NFS_INO_INVALID_ATIME | NFS_INO_INVALID_CTIME | NFS_INO_INVALID_MTIME | NFS_INO_INVALID_SIZE | NFS_INO_INVALID_BLOCKS | NFS_INO_INVALID_OTHER | - NFS_INO_INVALID_NLINK | NFS_INO_INVALID_BTIME; + NFS_INO_INVALID_NLINK | NFS_INO_INVALID_BTIME | + NFS_INO_INVALID_UNCACHEABLE_FILE_DATA; unsigned long cache_validity = NFS_I(inode)->cache_validity; enum nfs4_change_attr_type ctype = NFS_SERVER(inode)->change_attr_type; @@ -2295,7 +2302,8 @@ static int nfs_update_inode(struct inode *inode, struct nfs_fattr *fattr) nfsi->cache_validity &= ~(NFS_INO_INVALID_ATTR | NFS_INO_INVALID_ATIME | NFS_INO_REVAL_FORCED - | NFS_INO_INVALID_BLOCKS); + | NFS_INO_INVALID_BLOCKS + | NFS_INO_INVALID_UNCACHEABLE_FILE_DATA); /* Do atomic weak cache consistency updates */ nfs_wcc_update_inode(inode, fattr); @@ -2335,7 +2343,8 @@ static int nfs_update_inode(struct inode *inode, struct nfs_fattr *fattr) | NFS_INO_INVALID_NLINK | NFS_INO_INVALID_MODE | NFS_INO_INVALID_OTHER - | NFS_INO_INVALID_BTIME; + | NFS_INO_INVALID_BTIME + | NFS_INO_INVALID_UNCACHEABLE_FILE_DATA; if (S_ISDIR(inode->i_mode)) nfs_force_lookup_revalidate(inode); attr_changed = true; @@ -2459,6 +2468,13 @@ static int nfs_update_inode(struct inode *inode, struct nfs_fattr *fattr) nfsi->cache_validity |= save_cache_validity & NFS_INO_INVALID_BLOCKS; + if (fattr->valid & NFS_ATTR_FATTR_UNCACHEABLE_FILE_DATA) + nfsi->uncacheable_file_data = + fattr->aux_flags & NFS_AUX_UNCACHEABLE_FILE_DATA; + else if (fattr_supported & NFS_ATTR_FATTR_UNCACHEABLE_FILE_DATA) + nfsi->cache_validity |= + save_cache_validity & NFS_INO_INVALID_UNCACHEABLE_FILE_DATA; + /* Update attrtimeo value if we're out of the unstable period */ if (attr_changed) { nfs_inc_stats(inode, NFSIOS_ATTRINVALIDATE); diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c index 5709c6fea85b..59f6a38bfb5c 100644 --- a/fs/nfs/nfs4proc.c +++ b/fs/nfs/nfs4proc.c @@ -225,8 +225,9 @@ const u32 nfs4_fattr_bitmap[3] = { | FATTR4_WORD1_TIME_METADATA | FATTR4_WORD1_TIME_MODIFY | FATTR4_WORD1_MOUNTED_ON_FILEID, + FATTR4_WORD2_UNCACHEABLE_FILE_DATA #ifdef CONFIG_NFS_V4_SECURITY_LABEL - FATTR4_WORD2_SECURITY_LABEL + | FATTR4_WORD2_SECURITY_LABEL #endif }; @@ -250,6 +251,7 @@ static const u32 nfs4_pnfs_open_bitmap[3] = { #ifdef CONFIG_NFS_V4_SECURITY_LABEL | FATTR4_WORD2_SECURITY_LABEL #endif + | FATTR4_WORD2_UNCACHEABLE_FILE_DATA }; static const u32 nfs4_open_noattr_bitmap[3] = { @@ -327,6 +329,9 @@ static void nfs4_bitmap_copy_adjust(__u32 *dst, const __u32 *src, if (!(cache_validity & NFS_INO_INVALID_BTIME)) dst[1] &= ~FATTR4_WORD1_TIME_CREATE; + if (!(cache_validity & NFS_INO_INVALID_UNCACHEABLE_FILE_DATA)) + dst[2] &= ~FATTR4_WORD2_UNCACHEABLE_FILE_DATA; + if (nfs_have_delegated_mtime(inode)) { if (!(cache_validity & NFS_INO_INVALID_ATIME)) dst[1] &= ~(FATTR4_WORD1_TIME_ACCESS|FATTR4_WORD1_TIME_ACCESS_SET); @@ -1238,7 +1243,7 @@ nfs4_update_changeattr_locked(struct inode *inode, NFS_INO_INVALID_SIZE | NFS_INO_INVALID_OTHER | NFS_INO_INVALID_BLOCKS | NFS_INO_INVALID_NLINK | NFS_INO_INVALID_MODE | NFS_INO_INVALID_BTIME | - NFS_INO_INVALID_XATTR; + NFS_INO_INVALID_XATTR | NFS_INO_INVALID_UNCACHEABLE_FILE_DATA; nfsi->attrtimeo = NFS_MINATTRTIMEO(inode); } nfsi->attrtimeo_timestamp = jiffies; @@ -3857,7 +3862,7 @@ static void nfs4_close_context(struct nfs_open_context *ctx, int is_sync) #define FATTR4_WORD1_NFS40_MASK (2*FATTR4_WORD1_MOUNTED_ON_FILEID - 1UL) #define FATTR4_WORD2_NFS41_MASK (2*FATTR4_WORD2_SUPPATTR_EXCLCREAT - 1UL) -#define FATTR4_WORD2_NFS42_MASK (2*FATTR4_WORD2_OPEN_ARGUMENTS - 1UL) +#define FATTR4_WORD2_NFS42_MASK (2*FATTR4_WORD2_UNCACHEABLE_FILE_DATA - 1UL) #define FATTR4_WORD2_NFS42_TIME_DELEG_MASK \ (FATTR4_WORD2_TIME_DELEG_MODIFY|FATTR4_WORD2_TIME_DELEG_ACCESS) @@ -3981,6 +3986,8 @@ static int _nfs4_server_capabilities(struct nfs_server *server, struct nfs_fh *f memcpy(server->attr_bitmask_nl, res.attr_bitmask, sizeof(server->attr_bitmask)); server->attr_bitmask_nl[2] &= ~FATTR4_WORD2_SECURITY_LABEL; + if (!(res.attr_bitmask[2] & FATTR4_WORD2_UNCACHEABLE_FILE_DATA)) + server->fattr_valid &= ~NFS_ATTR_FATTR_UNCACHEABLE_FILE_DATA; if (res.open_caps.oa_share_access_want[0] & NFS4_SHARE_WANT_OPEN_XOR_DELEGATION) @@ -5809,6 +5816,8 @@ void nfs4_bitmask_set(__u32 bitmask[], const __u32 src[], bitmask[1] |= FATTR4_WORD1_SPACE_USED; if (cache_validity & NFS_INO_INVALID_BTIME) bitmask[1] |= FATTR4_WORD1_TIME_CREATE; + if (cache_validity & NFS_INO_INVALID_UNCACHEABLE_FILE_DATA) + bitmask[2] |= FATTR4_WORD2_UNCACHEABLE_FILE_DATA; if (cache_validity & NFS_INO_INVALID_SIZE) bitmask[0] |= FATTR4_WORD0_SIZE; diff --git a/fs/nfs/nfs4trace.h b/fs/nfs/nfs4trace.h index e679507eccb6..b5c89eeef2bc 100644 --- a/fs/nfs/nfs4trace.h +++ b/fs/nfs/nfs4trace.h @@ -33,7 +33,8 @@ { NFS_ATTR_FATTR_CHANGE, "CHANGE" }, \ { NFS_ATTR_FATTR_OWNER_NAME, "OWNER_NAME" }, \ { NFS_ATTR_FATTR_GROUP_NAME, "GROUP_NAME" }, \ - { NFS_ATTR_FATTR_BTIME, "BTIME" }) + { NFS_ATTR_FATTR_BTIME, "BTIME" }, \ + { NFS_ATTR_FATTR_UNCACHEABLE_FILE_DATA, "UNCACHEABLE_FILE_DATA" }) DECLARE_EVENT_CLASS(nfs4_clientid_event, TP_PROTO( diff --git a/fs/nfs/nfs4xdr.c b/fs/nfs/nfs4xdr.c index c23c2eee1b5c..fc049ce4ba8a 100644 --- a/fs/nfs/nfs4xdr.c +++ b/fs/nfs/nfs4xdr.c @@ -120,7 +120,8 @@ static int decode_layoutget(struct xdr_stream *xdr, struct rpc_rqst *req, 3*nfstime4_maxsz + \ nfs4_owner_maxsz + \ nfs4_group_maxsz + nfs4_label_maxsz + \ - decode_mdsthreshold_maxsz)) + decode_mdsthreshold_maxsz + \ + 1)) /* uncacheable_file_data */ #define nfs4_fattr_maxsz (nfs4_fattr_bitmap_maxsz + \ nfs4_fattr_value_maxsz) #define decode_getattr_maxsz (op_decode_hdr_maxsz + nfs4_fattr_maxsz) @@ -4380,6 +4381,30 @@ static int decode_attr_open_arguments(struct xdr_stream *xdr, uint32_t *bitmap, return 0; } +static int decode_attr_uncacheable_file_data(struct xdr_stream *xdr, uint32_t *bitmap, + uint32_t *res, uint64_t *flags) +{ + int status = 0; + __be32 *p; + + if (unlikely(bitmap[2] & (FATTR4_WORD2_UNCACHEABLE_FILE_DATA - 1U))) + return -EIO; + if (likely(bitmap[2] & FATTR4_WORD2_UNCACHEABLE_FILE_DATA)) { + p = xdr_inline_decode(xdr, 4); + if (unlikely(!p)) + return -EIO; + if (be32_to_cpup(p)) + *res |= NFS_AUX_UNCACHEABLE_FILE_DATA; + else + *res &= ~NFS_AUX_UNCACHEABLE_FILE_DATA; + bitmap[2] &= ~FATTR4_WORD2_UNCACHEABLE_FILE_DATA; + *flags |= NFS_ATTR_FATTR_UNCACHEABLE_FILE_DATA; + } + dprintk("%s: uncacheable_file_data: =%s\n", __func__, + (*res & NFS_AUX_UNCACHEABLE_FILE_DATA) == 0 ? "false" : "true"); + return status; +} + static int verify_attr_len(struct xdr_stream *xdr, unsigned int savep, uint32_t attrlen) { unsigned int attrwords = XDR_QUADLEN(attrlen); @@ -4725,6 +4750,8 @@ static int decode_getfattr_attrs(struct xdr_stream *xdr, uint32_t *bitmap, uint32_t type; int32_t err; + fattr->aux_flags = 0; + status = decode_attr_type(xdr, bitmap, &type); if (status < 0) goto xdr_error; @@ -4843,6 +4870,12 @@ static int decode_getfattr_attrs(struct xdr_stream *xdr, uint32_t *bitmap, goto xdr_error; fattr->valid |= status; + status = decode_attr_uncacheable_file_data(xdr, bitmap, &fattr->aux_flags, + &fattr->valid); + if (status < 0) + goto xdr_error; + + status = 0; xdr_error: dprintk("%s: xdr returned %d\n", __func__, -status); return status; diff --git a/fs/nfs/nfstrace.h b/fs/nfs/nfstrace.h index 4ada21f4eebd..b15c1732c869 100644 --- a/fs/nfs/nfstrace.h +++ b/fs/nfs/nfstrace.h @@ -33,7 +33,8 @@ { NFS_INO_INVALID_XATTR, "INVALID_XATTR" }, \ { NFS_INO_INVALID_NLINK, "INVALID_NLINK" }, \ { NFS_INO_INVALID_MODE, "INVALID_MODE" }, \ - { NFS_INO_INVALID_BTIME, "INVALID_BTIME" }) + { NFS_INO_INVALID_BTIME, "INVALID_BTIME" }, \ + { NFS_INO_INVALID_UNCACHEABLE_FILE_DATA, "INVALID_UNCACHEABLE_FILE_DATA" }) #define nfs_show_nfsi_flags(v) \ __print_flags(v, "|", \ diff --git a/include/linux/nfs4.h b/include/linux/nfs4.h index d87be1f25273..9015bb6dc2f2 100644 --- a/include/linux/nfs4.h +++ b/include/linux/nfs4.h @@ -516,6 +516,14 @@ enum { FATTR4_XATTR_SUPPORT = 82, }; +/* + * Symbol name and value are from draft-ietf-nfsv4-uncacheable-files + * Section 7. "XDR for Uncacheable Attribute" + */ +enum { + FATTR4_UNCACHEABLE_FILE_DATA = 87, +}; + /* * The following internal definitions enable processing the above * attribute bits within 32-bit word boundaries. @@ -602,6 +610,7 @@ enum { #define FATTR4_WORD2_ACL_TRUEFORM_SCOPE BIT(FATTR4_ACL_TRUEFORM_SCOPE - 64) #define FATTR4_WORD2_POSIX_DEFAULT_ACL BIT(FATTR4_POSIX_DEFAULT_ACL - 64) #define FATTR4_WORD2_POSIX_ACCESS_ACL BIT(FATTR4_POSIX_ACCESS_ACL - 64) +#define FATTR4_WORD2_UNCACHEABLE_FILE_DATA BIT(FATTR4_UNCACHEABLE_FILE_DATA - 64) /* MDS threshold bitmap bits */ #define THRESHOLD_RD (1UL << 0) diff --git a/include/linux/nfs_fs.h b/include/linux/nfs_fs.h index ec17e602c979..8552a0d778d9 100644 --- a/include/linux/nfs_fs.h +++ b/include/linux/nfs_fs.h @@ -162,6 +162,8 @@ struct nfs_inode { struct timespec64 btime; + bool uncacheable_file_data : 1; + /* * read_cache_jiffies is when we started read-caching this inode. * attrtimeo is for how long the cached information is assumed @@ -319,6 +321,7 @@ struct nfs4_copy_state { #define NFS_INO_INVALID_NLINK BIT(16) /* cached nlinks is invalid */ #define NFS_INO_INVALID_MODE BIT(17) /* cached mode is invalid */ #define NFS_INO_INVALID_BTIME BIT(18) /* cached btime is invalid */ +#define NFS_INO_INVALID_UNCACHEABLE_FILE_DATA BIT(19) /* cached uncacheable_file_data is invalid */ #define NFS_INO_INVALID_ATTR (NFS_INO_INVALID_CHANGE \ | NFS_INO_INVALID_CTIME \ diff --git a/include/linux/nfs_xdr.h b/include/linux/nfs_xdr.h index 11c5b31cfc7d..2e1987ac403d 100644 --- a/include/linux/nfs_xdr.h +++ b/include/linux/nfs_xdr.h @@ -17,6 +17,9 @@ #define NFS_BITMASK_SZ 3 +/* aux_flags in nfs_fattr */ +#define NFS_AUX_UNCACHEABLE_FILE_DATA BIT(0) + struct nfs4_string { unsigned int len; char *data; @@ -68,6 +71,7 @@ struct nfs_fattr { struct timespec64 mtime; struct timespec64 ctime; struct timespec64 btime; + __u32 aux_flags; /* NFSv4 auxiliary flags bitfield */ __u64 change_attr; /* NFSv4 change attribute */ __u64 pre_change_attr;/* pre-op NFSv4 change attribute */ __u64 pre_size; /* pre_op_attr.size */ @@ -108,6 +112,7 @@ struct nfs_fattr { #define NFS_ATTR_FATTR_GROUP_NAME BIT_ULL(24) #define NFS_ATTR_FATTR_V4_SECURITY_LABEL BIT_ULL(25) #define NFS_ATTR_FATTR_BTIME BIT_ULL(26) +#define NFS_ATTR_FATTR_UNCACHEABLE_FILE_DATA BIT_ULL(27) #define NFS_ATTR_FATTR (NFS_ATTR_FATTR_TYPE \ | NFS_ATTR_FATTR_MODE \ @@ -129,7 +134,8 @@ struct nfs_fattr { #define NFS_ATTR_FATTR_V4 (NFS_ATTR_FATTR \ | NFS_ATTR_FATTR_SPACE_USED \ | NFS_ATTR_FATTR_BTIME \ - | NFS_ATTR_FATTR_V4_SECURITY_LABEL) + | NFS_ATTR_FATTR_V4_SECURITY_LABEL \ + | NFS_ATTR_FATTR_UNCACHEABLE_FILE_DATA) /* * Maximal number of supported layout drivers. From 6f36ce30983e2b7865cd1eaf3618fb65a0a78c37 Mon Sep 17 00:00:00 2001 From: Mike Snitzer Date: Mon, 27 Jul 2026 17:09:39 -0400 Subject: [PATCH 0598/1328] nfs4.2: request UNCACHEABLE_FILE_DATA only for regular files The UNCACHEABLE_FILE_DATA attribute applies only to regular files (NF4REG); per draft-ietf-nfsv4-uncacheable-files a server MUST reject a query of it on any other object type with NFS4ERR_INVAL. The previous commit decodes and tracks the attribute but does not gate it: the bit rides in the per-server attribute bitmask (server->attr_bitmask) and in the generic getattr request bitmap (nfs4_fattr_bitmap), so it would be requested for non-regular objects too -- e.g. a plain directory GETATTR, a LOOKUP that resolves to a directory, or a CREATE (which only ever makes non-regular objects). A strict server would fail those compounds. Gate the client accordingly: - Only set NFS_INO_INVALID_UNCACHEABLE_FILE_DATA on regular-file inodes. In particular, drop it from nfs4_update_changeattr_locked()'s force-revalidation aggregation: that helper only ever runs on directory inodes (its callers update a directory's change information after OPEN-create, REMOVE, RENAME and LINK), so it was setting the file-only bit on directories. - Gate the request by object type at the choke point nfs4_bitmap_copy_adjust(), which clears FATTR4_WORD2_UNCACHEABLE_FILE_DATA unless the target inode is a regular file (a NULL inode -- unknown object type -- clears it too). This already covers GETATTR, SETATTR and LINK; route LOOKUP, LOOKUPP and CREATE through it as well. - Type-gate nfs4_bitmask_set(), which translates NFS_INO_INVALID_UNCACHEABLE_FILE_DATA into a request for attr 87 in the getattr attached to WRITE, CLOSE and DELEGRETURN. WRITE and CLOSE only ever pass regular files, but DELEGRETURN passes whatever object held the delegation -- with directory delegation support that includes directories -- so request attr 87 there only for S_ISREG inodes. The bit is kept in server->attr_bitmask (it is server-supported, and OPEN still requests it via its regular-file-only open_bitmap), so no bespoke per-data-file bitmask plumbing is needed. The remaining getattr-bearing compounds are already safe: ACCESS and LAYOUTCOMMIT use server->cache_consistency_bitmask (no word2 attributes); READDIR does not encode the bit; and LOOKUP_ROOT, FSINFO, STATFS and PATHCONF use fixed bitmaps without it. Signed-off-by: Mike Snitzer Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Trond Myklebust --- fs/nfs/inode.c | 6 ++++-- fs/nfs/nfs4proc.c | 46 +++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c index e98b1f755e95..3022454f7698 100644 --- a/fs/nfs/inode.c +++ b/fs/nfs/inode.c @@ -565,7 +565,8 @@ nfs_fhget(struct super_block *sb, struct nfs_fh *fh, struct nfs_fattr *fattr) if (fattr->valid & NFS_ATTR_FATTR_UNCACHEABLE_FILE_DATA) nfsi->uncacheable_file_data = fattr->aux_flags & NFS_AUX_UNCACHEABLE_FILE_DATA; - else if (fattr_supported & NFS_ATTR_FATTR_UNCACHEABLE_FILE_DATA) + else if (S_ISREG(inode->i_mode) && + (fattr_supported & NFS_ATTR_FATTR_UNCACHEABLE_FILE_DATA)) nfs_set_cache_invalid(inode, NFS_INO_INVALID_UNCACHEABLE_FILE_DATA); nfs_setsecurity(inode, fattr); @@ -2471,7 +2472,8 @@ static int nfs_update_inode(struct inode *inode, struct nfs_fattr *fattr) if (fattr->valid & NFS_ATTR_FATTR_UNCACHEABLE_FILE_DATA) nfsi->uncacheable_file_data = fattr->aux_flags & NFS_AUX_UNCACHEABLE_FILE_DATA; - else if (fattr_supported & NFS_ATTR_FATTR_UNCACHEABLE_FILE_DATA) + else if (S_ISREG(inode->i_mode) && + (fattr_supported & NFS_ATTR_FATTR_UNCACHEABLE_FILE_DATA)) nfsi->cache_validity |= save_cache_validity & NFS_INO_INVALID_UNCACHEABLE_FILE_DATA; diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c index 59f6a38bfb5c..62fc7034e0ac 100644 --- a/fs/nfs/nfs4proc.c +++ b/fs/nfs/nfs4proc.c @@ -308,6 +308,15 @@ static void nfs4_bitmap_copy_adjust(__u32 *dst, const __u32 *src, unsigned long cache_validity; memcpy(dst, src, NFS4_BITMASK_SZ*sizeof(*dst)); + /* + * The uncacheable_file_data attribute applies only to regular files + * (NF4REG); a server must reject a query of it on any other object + * type with NFS4ERR_INVAL. Never request it unless the target is + * known to be a regular file (callers with an unknown object type, + * e.g. LOOKUP, pass a NULL inode). + */ + if (!inode || !S_ISREG(inode->i_mode)) + dst[2] &= ~FATTR4_WORD2_UNCACHEABLE_FILE_DATA; if (!inode || !nfs_have_read_or_write_delegation(inode)) return; @@ -1243,7 +1252,7 @@ nfs4_update_changeattr_locked(struct inode *inode, NFS_INO_INVALID_SIZE | NFS_INO_INVALID_OTHER | NFS_INO_INVALID_BLOCKS | NFS_INO_INVALID_NLINK | NFS_INO_INVALID_MODE | NFS_INO_INVALID_BTIME | - NFS_INO_INVALID_XATTR | NFS_INO_INVALID_UNCACHEABLE_FILE_DATA; + NFS_INO_INVALID_XATTR; nfsi->attrtimeo = NFS_MINATTRTIMEO(inode); } nfsi->attrtimeo_timestamp = jiffies; @@ -4598,6 +4607,7 @@ static int _nfs4_proc_lookup(struct rpc_clnt *clnt, struct inode *dir, .rpc_resp = &res, }; unsigned short task_flags = 0; + __u32 bitmask[NFS4_BITMASK_SZ]; if (nfs_server_capable(dir, NFS_CAP_MOVEABLE)) task_flags = RPC_TASK_MOVEABLE; @@ -4606,7 +4616,13 @@ static int _nfs4_proc_lookup(struct rpc_clnt *clnt, struct inode *dir, if (nfs_lookup_is_soft_revalidate(dentry)) task_flags |= RPC_TASK_TIMEOUT; - args.bitmask = nfs4_bitmask(server, fattr->label); + /* + * The looked-up object's type is unknown here, so gate out the + * regular-file-only uncacheable_file_data attribute (NULL inode). + */ + nfs4_bitmap_copy_adjust(bitmask, nfs4_bitmask(server, fattr->label), + NULL, 0); + args.bitmask = bitmask; nfs_fattr_init(fattr); @@ -4720,13 +4736,20 @@ static int _nfs4_proc_lookupp(struct inode *inode, .rpc_resp = &res, }; unsigned short task_flags = 0; + __u32 bitmask[NFS4_BITMASK_SZ]; if (server->flags & NFS_MOUNT_SOFTREVAL) task_flags |= RPC_TASK_TIMEOUT; if (server->caps & NFS_CAP_MOVEABLE) task_flags |= RPC_TASK_MOVEABLE; - args.bitmask = nfs4_bitmask(server, fattr->label); + /* + * The looked-up object's type is unknown here, so gate out the + * regular-file-only uncacheable_file_data attribute (NULL inode). + */ + nfs4_bitmap_copy_adjust(bitmask, nfs4_bitmask(server, fattr->label), + NULL, 0); + args.bitmask = bitmask; nfs_fattr_init(fattr); nfs4_init_sequence(server->nfs_client, &args.seq_args, &res.seq_res, 0, 0); @@ -5141,6 +5164,7 @@ struct nfs4_createdata { struct nfs4_create_res res; struct nfs_fh fh; struct nfs_fattr fattr; + u32 bitmask[NFS4_BITMASK_SZ]; }; static struct nfs4_createdata *nfs4_alloc_createdata(struct inode *dir, @@ -5164,7 +5188,14 @@ static struct nfs4_createdata *nfs4_alloc_createdata(struct inode *dir, data->arg.name = name; data->arg.attrs = sattr; data->arg.ftype = ftype; - data->arg.bitmask = nfs4_bitmask(server, data->fattr.label); + /* + * CREATE only makes non-regular objects, so gate out the + * regular-file-only uncacheable_file_data attribute (NULL inode). + */ + nfs4_bitmap_copy_adjust(data->bitmask, + nfs4_bitmask(server, data->fattr.label), + NULL, 0); + data->arg.bitmask = data->bitmask; data->arg.umask = current_umask(); data->res.server = server; data->res.fh = &data->fh; @@ -5816,7 +5847,12 @@ void nfs4_bitmask_set(__u32 bitmask[], const __u32 src[], bitmask[1] |= FATTR4_WORD1_SPACE_USED; if (cache_validity & NFS_INO_INVALID_BTIME) bitmask[1] |= FATTR4_WORD1_TIME_CREATE; - if (cache_validity & NFS_INO_INVALID_UNCACHEABLE_FILE_DATA) + /* + * uncacheable_file_data (attr 87) applies only to regular files; a + * directory can reach here via DELEGRETURN of a directory delegation. + */ + if ((cache_validity & NFS_INO_INVALID_UNCACHEABLE_FILE_DATA) && + S_ISREG(inode->i_mode)) bitmask[2] |= FATTR4_WORD2_UNCACHEABLE_FILE_DATA; if (cache_validity & NFS_INO_INVALID_SIZE) From b4dd7f81592287c9b4070e6669732d8770269303 Mon Sep 17 00:00:00 2001 From: Mike Snitzer Date: Mon, 27 Jul 2026 17:09:40 -0400 Subject: [PATCH 0599/1328] nfs4.2: open UNCACHEABLE_FILE_DATA files with O_DIRECT Honor the per-file UNCACHEABLE_FILE_DATA attribute by transparently opening such regular files with O_DIRECT, so reads and writes bypass the page cache as the attribute requires, without the application having to request O_DIRECT itself. This follows the model the specification describes: the attribute is "similar in intent to O_DIRECT" and clients "retain flexibility in how they satisfy the requirements" (draft-ietf-nfsv4-uncacheable-files Section 4.4, "Relationship to Direct I/O"), and its Implementation Status (Section 6) describes a prototype Linux client that "treats the attribute as an indication to use O_DIRECT-like behavior for file access". Introduce an NFS_CONTEXT_O_DIRECT open-context flag: nfs4_atomic_open() sets it when the resolved inode has uncacheable_file_data set (and the open is not O_APPEND), and the open paths nfs_atomic_open() and nfs4_file_open() apply O_DIRECT to the file when the flag is set. The I/O mode is thus selected at open time and is not changed for an already-open file: a later change to the attribute takes effect on the next open. The specification permits this -- a client that has already opened a file MAY continue with its existing caching behavior and apply the updated attribute to subsequent operations (Section 5). The delegation interaction in Section 4.3 was considered: it permits read caching to remain when another NFSv4.2 mechanism, such as a delegation, already ensures a consistent view of the file. That relaxation is optional ("may remain appropriate") and read-only -- it does not relax write-behind suppression (Section 4.1) or the WRITE durability invariant (Section 4.2). This implementation deliberately does not take it: an uncacheable file is opened O_DIRECT regardless of any delegation held, which is compliant (read caching is simply suppressed more aggressively than the Section 4.3 minimum) and avoids decoupling read vs write caching behind a single open flag. Relaxing reads under a delegation is left as a possible future optimization. Section 6 observes the benefit holds "for applications that issue well-formed I/O requests". That alignment caveat does not constrain the Linux NFS client's over-the-wire path: the client readily issues misaligned I/O using O_DIRECT over SunRPC to the remote NFS server. The only place a fallback from O_DIRECT to buffered I/O for misaligned I/O applies is NFS LOCALIO (fs/nfs/localio.c), which detects non-DIO-aligned I/O and falls back internally; that path is unaffected by this change. Link: https://datatracker.ietf.org/doc/draft-ietf-nfsv4-uncacheable-files/ Signed-off-by: Mike Snitzer Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Trond Myklebust --- fs/nfs/dir.c | 4 ++++ fs/nfs/nfs4file.c | 2 ++ fs/nfs/nfs4proc.c | 10 ++++++++++ include/linux/nfs_fs.h | 1 + 4 files changed, 17 insertions(+) diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index c7caffb31935..03c8e83f5913 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -2208,6 +2208,10 @@ int nfs_atomic_open(struct inode *dir, struct dentry *dentry, goto out; } file->f_mode |= FMODE_CAN_ODIRECT; + if (test_bit(NFS_CONTEXT_O_DIRECT, &ctx->flags)) { + file->f_flags |= O_DIRECT; + open_flags |= O_DIRECT; + } err = nfs_finish_open(ctx, ctx->dentry, file, open_flags); trace_nfs_atomic_open_exit(dir, ctx, open_flags, err); diff --git a/fs/nfs/nfs4file.c b/fs/nfs/nfs4file.c index be40e126c539..6401f6363f75 100644 --- a/fs/nfs/nfs4file.c +++ b/fs/nfs/nfs4file.c @@ -91,6 +91,8 @@ nfs4_file_open(struct inode *inode, struct file *filp) nfs_fscache_open_file(inode, filp); err = 0; filp->f_mode |= FMODE_CAN_ODIRECT; + if (test_bit(NFS_CONTEXT_O_DIRECT, &ctx->flags)) + filp->f_flags |= O_DIRECT; out_put_ctx: put_nfs_open_context(ctx); diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c index 62fc7034e0ac..b79d121ea069 100644 --- a/fs/nfs/nfs4proc.c +++ b/fs/nfs/nfs4proc.c @@ -3853,6 +3853,16 @@ nfs4_atomic_open(struct inode *dir, struct nfs_open_context *ctx, if (IS_ERR(state)) return ERR_CAST(state); + + /* + * Use O_DIRECT if file was marked as Uncacheable, see: + * https://datatracker.ietf.org/doc/draft-ietf-nfsv4-uncacheable-files/ + */ + if (!(open_flags & O_DIRECT) && NFS_I(state->inode)->uncacheable_file_data) { + if (!(open_flags & O_APPEND)) + set_bit(NFS_CONTEXT_O_DIRECT, &ctx->flags); + } + return state->inode; } diff --git a/include/linux/nfs_fs.h b/include/linux/nfs_fs.h index 8552a0d778d9..48b806aa3a2f 100644 --- a/include/linux/nfs_fs.h +++ b/include/linux/nfs_fs.h @@ -110,6 +110,7 @@ struct nfs_open_context { #define NFS_CONTEXT_UNLOCK (3) #define NFS_CONTEXT_FILE_OPEN (4) #define NFS_CONTEXT_WRITE_SYNC (5) +#define NFS_CONTEXT_O_DIRECT (6) struct nfs4_threshold *mdsthreshold; struct list_head list; From 59075fb8b7887b4149e73e3d5aee2ceeeb87d287 Mon Sep 17 00:00:00 2001 From: Jiangshan Yi Date: Thu, 2 Jul 2026 09:50:14 +0800 Subject: [PATCH 0600/1328] NFS: fix folio dereference before NULL check in nfs_inode_remove_request() nfs_inode_remove_request() obtains the folio for the head request via nfs_page_to_folio(), which returns NULL when the PG_FOLIO flag is not set on req->wb_head. The presence of the "if (likely(folio))" check shows the code already assumes folio can be NULL. However, folio was dereferenced before that check: folio = nfs_page_to_folio(req->wb_head); mapping = folio->mapping; /* deref */ spin_lock(&mapping->i_private_lock); if (likely(folio)) { /* too late */ folio->mapping is read (and mapping->i_private_lock is taken, and folio_end_dropbehind(folio) is called outside the check) before folio is validated, so a NULL folio would crash before the guard is ever reached, rendering the check useless. Move the folio->mapping read, the i_private_lock section and the folio_end_dropbehind() call inside the "if (likely(folio))" block so the folio is only dereferenced after it has been confirmed non-NULL. The behaviour is unchanged when folio is non-NULL. Signed-off-by: Jiangshan Yi Signed-off-by: Trond Myklebust --- fs/nfs/write.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/fs/nfs/write.c b/fs/nfs/write.c index d2b03ceaeb4f..ec4e0d6c829c 100644 --- a/fs/nfs/write.c +++ b/fs/nfs/write.c @@ -739,17 +739,18 @@ static void nfs_inode_remove_request(struct nfs_page *req) nfs_page_group_lock(req); if (nfs_page_group_sync_on_bit_locked(req, PG_REMOVE)) { struct folio *folio = nfs_page_to_folio(req->wb_head); - struct address_space *mapping = folio->mapping; - spin_lock(&mapping->i_private_lock); if (likely(folio)) { + struct address_space *mapping = folio->mapping; + + spin_lock(&mapping->i_private_lock); folio->private = NULL; folio_clear_private(folio); clear_bit(PG_MAPPED, &req->wb_head->wb_flags); - } - spin_unlock(&mapping->i_private_lock); + spin_unlock(&mapping->i_private_lock); - folio_end_dropbehind(folio); + folio_end_dropbehind(folio); + } } nfs_page_group_unlock(req); From 3265f1998ae9a9282a8a6ca95467d9572d6ebb82 Mon Sep 17 00:00:00 2001 From: ZhangGuoDong Date: Fri, 3 Jul 2026 14:54:48 +0800 Subject: [PATCH 0601/1328] NFS: Verify symlink inode before caching target nfs_symlink() copies the symlink target into a folio before issuing the SYMLINK RPC. After a successful reply, it caches that folio in the instantiated inode mapping and assumes that the dentry now names a symlink. If the dentry is instantiated with a non-symlink inode, the raw symlink target folio can be inserted into the wrong mapping. When that inode is a directory, reclaim or unmount later calls nfs_readdir_clear_array() through nfs_dir_aops and interprets the symlink target as a readdir cache array, which can lead to invalid kfree() calls. A vmcore from a 4.19-based kernel showed the crash when reclaiming a directory mapping on unmount: Stack trace: nfs_readdir_clear_array+0x4d/0x70 [nfs] page_cache_free_page.isra.35+0x1a/0x90 delete_from_page_cache_batch+0x1cf/0x2c0 truncate_inode_pages_range+0x24d/0x910 [...] nfs_evict_inode+0x15/0x30 [nfs] evict+0x115/0x2b0 dispose_list+0x48/0x60 evict_inodes+0x16c/0x1b0 generic_shutdown_super+0x3f/0x120 nfs_kill_super+0x1b/0x40 [nfs] deactivate_locked_super+0x3f/0x70 cleanup_mnt+0x3b/0x80 The current code still has the same unchecked cache insertion pattern, so it may be susceptible to the same failure mode. Verify that the instantiated inode is a symlink before caching the target folio. If the type is wrong, drop the suspect dentry and skip the cache insertion while preserving the successful SYMLINK result. Co-developed-by: Jackie Liu Signed-off-by: Jackie Liu Signed-off-by: ZhangGuoDong Signed-off-by: Trond Myklebust --- fs/nfs/dir.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index 03c8e83f5913..c4a0a93b24e4 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -2673,6 +2673,12 @@ int nfs_symlink(struct mnt_idmap *idmap, struct inode *dir, return error; } + if (unlikely(!d_is_symlink(dentry))) { + d_drop(dentry); + folio_put(folio); + return 0; + } + nfs_set_verifier(dentry, nfs_save_change_attribute(dir)); /* From 68c03755577ac90e31e38c7ec787301cf6be5331 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Sun, 5 Jul 2026 00:42:17 +0800 Subject: [PATCH 0602/1328] NFS: Fix delayed delegation return list handling The delayed delegation return handling added a separate delegations_delayed list to keep delegations whose return needs to be retried later. The delayed list is then spliced back to delegations_return by nfs_server_clear_delayed_delegations(), which also causes the state manager to retry the delegation return. However, nfs_end_delegation_return() still moves delayed delegations to delegations_return instead of delegations_delayed. As a result, the new delayed list is never populated, nfs_server_clear_delayed_delegations() always returns false, and NFS4CLNT_DELEGRETURN is not set again to drive a retry. Move delayed delegations to delegations_delayed so that the delayed return path can splice them back to delegations_return and schedule the retry as intended. Fixes: 4039fbedcbcb ("NFS: fix delayed delegation return handling") Signed-off-by: Guangshuo Li Signed-off-by: Trond Myklebust --- fs/nfs/delegation.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/nfs/delegation.c b/fs/nfs/delegation.c index 284437fa3f87..ab3e441464a6 100644 --- a/fs/nfs/delegation.c +++ b/fs/nfs/delegation.c @@ -604,7 +604,7 @@ static int nfs_end_delegation_return(struct inode *inode, spin_lock(&server->delegations_lock); if (list_empty(&delegation->entry)) refcount_inc(&delegation->refcount); - list_move_tail(&delegation->entry, &server->delegations_return); + list_move_tail(&delegation->entry, &server->delegations_delayed); spin_unlock(&server->delegations_lock); set_bit(NFS4CLNT_DELEGRETURN_DELAYED, &server->nfs_client->cl_state); abort: From da729ddd4a1bc7c9f119bf7dfcc2c173b887cafa Mon Sep 17 00:00:00 2001 From: Mike Snitzer Date: Mon, 6 Jul 2026 12:05:47 -0400 Subject: [PATCH 0603/1328] NFS/localio: issue IO inline when not in a memory-reclaim context Every LOCALIO read and write is currently bounced through the dedicated !WQ_MEM_RECLAIM nfslocaliod_workqueue. That bounce is only actually required when the submitting context is a memory-reclaim context: LOCALIO issues IO directly into a stacked local filesystem (e.g. XFS) which may in turn flush its own !WQ_MEM_RECLAIM workqueue. Doing that from a WQ_MEM_RECLAIM worker (most importantly writeback's wb_workfn on bdi_wq) or an explicit PF_MEMALLOC reclaim task trips check_flush_dependency() and risks a forward-progress deadlock, which is why commit b9f5dd57f4a5 ("nfs/localio: use dedicated workqueues for filesystem read and write") introduced the intermediate workqueue. Outside of reclaim context -- ordinary application/task submission such as O_DIRECT or fsync-driven writeback -- the workqueue hop buys nothing and merely adds a context switch and scheduling latency per IO while discarding the NFS client's inherent application-context parallelism. Add current_is_workqueue_mem_reclaim(), which reports whether %current is a WQ_MEM_RECLAIM worker using the same predicate check_flush_dependency() warns on. Use it, together with the PF_MEMALLOC check, in the new nfs_local_defer_io() helper to decide per-IO whether nfs_local_do_read() and nfs_local_do_write() must defer to nfslocaliod_workqueue or may issue the IO inline. Buffered writeback continues to bounce (wb_workfn is a WQ_MEM_RECLAIM worker); O_DIRECT and app-context submission now run inline. Running nfs_local_call_write() inline is safe: it already saves and restores current->flags around the PF_LOCAL_THROTTLE|PF_MEMALLOC_NOIO it sets and scopes the file opener's creds. The async O_DIRECT completion path is likewise unaffected: when the underlying filesystem returns -EIOCBQUEUED, the kiocb ki_complete callback (nfs_local_read_aio_complete / nfs_local_write_aio_complete) can run in bottom-half context and so must still defer the pgio completion (nfs_local_pgio_release -> rpc_call_done) to nfsiod_workqueue via nfs_local_pgio_aio_complete(). That completion hop is independent of how the IO was submitted, and this change leaves it as-is; only the submission side stops unconditionally hopping through nfslocaliod_workqueue. Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Mike Snitzer Signed-off-by: Trond Myklebust --- fs/nfs/localio.c | 33 +++++++++++++++++++++++++++++++-- include/linux/workqueue.h | 1 + kernel/workqueue.c | 24 ++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/fs/nfs/localio.c b/fs/nfs/localio.c index e55c5977fcc3..d3e480888eb1 100644 --- a/fs/nfs/localio.c +++ b/fs/nfs/localio.c @@ -699,6 +699,29 @@ static void nfs_local_call_read(struct work_struct *work) } } +/* + * Decide whether LOCALIO must defer submission to the dedicated + * !WQ_MEM_RECLAIM nfslocaliod_workqueue rather than issue the IO inline. + * + * LOCALIO issues IO directly into a stacked local filesystem (e.g. XFS), + * which may in turn flush its own !WQ_MEM_RECLAIM workqueue. Doing so from a + * memory-reclaim context -- either a WQ_MEM_RECLAIM worker (most importantly + * writeback's wb_workfn running on bdi_wq) or an explicit reclaim task + * (PF_MEMALLOC) -- would trip check_flush_dependency() and risks a + * forward-progress deadlock; see commit b9f5dd57f4a5 ("nfs/localio: use + * dedicated workqueues for filesystem read and write"). In that case defer + * to nfslocaliod_workqueue. + * + * Otherwise (ordinary application/task context, e.g. O_DIRECT or fsync-driven + * submission) issue the IO inline: this preserves the NFS client's inherent + * application-context parallelism and avoids the per-IO workqueue hop. + */ +static inline bool nfs_local_defer_io(void) +{ + return (current->flags & PF_MEMALLOC) || + current_is_workqueue_mem_reclaim(); +} + static void nfs_local_do_read(struct nfs_local_kiocb *iocb, const struct rpc_call_ops *call_ops) { @@ -711,7 +734,10 @@ static void nfs_local_do_read(struct nfs_local_kiocb *iocb, hdr->res.eof = false; INIT_WORK(&iocb->work, nfs_local_call_read); - queue_work(nfslocaliod_workqueue, &iocb->work); + if (nfs_local_defer_io()) + queue_work(nfslocaliod_workqueue, &iocb->work); + else + nfs_local_call_read(&iocb->work); } static void @@ -929,7 +955,10 @@ static void nfs_local_do_write(struct nfs_local_kiocb *iocb, nfs_set_local_verifier(hdr->inode, hdr->res.verf, hdr->args.stable); INIT_WORK(&iocb->work, nfs_local_call_write); - queue_work(nfslocaliod_workqueue, &iocb->work); + if (nfs_local_defer_io()) + queue_work(nfslocaliod_workqueue, &iocb->work); + else + nfs_local_call_write(&iocb->work); } static struct nfs_local_kiocb * diff --git a/include/linux/workqueue.h b/include/linux/workqueue.h index a283766a192a..c8a36423cb34 100644 --- a/include/linux/workqueue.h +++ b/include/linux/workqueue.h @@ -661,6 +661,7 @@ extern void workqueue_set_min_active(struct workqueue_struct *wq, int min_active); extern struct work_struct *current_work(void); extern bool current_is_workqueue_rescuer(void); +extern bool current_is_workqueue_mem_reclaim(void); extern bool workqueue_congested(int cpu, struct workqueue_struct *wq); extern unsigned int work_busy(struct work_struct *work); extern __printf(1, 2) void set_worker_desc(const char *fmt, ...); diff --git a/kernel/workqueue.c b/kernel/workqueue.c index 78068ae8f28a..7bb41bec621f 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -6215,6 +6215,30 @@ bool current_is_workqueue_rescuer(void) return worker && worker->rescue_wq; } +/** + * current_is_workqueue_mem_reclaim - is %current a %WQ_MEM_RECLAIM worker? + * + * Determine whether %current is a workqueue worker executing on a workqueue + * created with %WQ_MEM_RECLAIM. This mirrors the condition that + * check_flush_dependency() warns on: flushing (or otherwise waiting on) a + * !WQ_MEM_RECLAIM workqueue from such a context breaks the forward-progress + * guarantee and can deadlock. Callers that may recurse into such a flush -- + * e.g. NFS LOCALIO submitting into a stacked filesystem that flushes its own + * !WQ_MEM_RECLAIM workqueue -- can use this to decide whether they must defer + * the work to a !WQ_MEM_RECLAIM workqueue rather than run it inline. + * + * Return: %true if %current is a %WQ_MEM_RECLAIM worker. %false otherwise. + */ +bool current_is_workqueue_mem_reclaim(void) +{ + struct worker *worker = current_wq_worker(); + + return worker && + ((worker->current_pwq->wq->flags & + (WQ_MEM_RECLAIM | __WQ_LEGACY)) == WQ_MEM_RECLAIM); +} +EXPORT_SYMBOL_GPL(current_is_workqueue_mem_reclaim); + /** * workqueue_congested - test whether a workqueue is congested * @cpu: CPU in question From b10c63dcf27a8dcb5468f2b4a360d07fe93029a0 Mon Sep 17 00:00:00 2001 From: Mike Snitzer Date: Mon, 6 Jul 2026 12:05:48 -0400 Subject: [PATCH 0604/1328] NFS/localio: remove dead FLUSH_SYNC handling from nfs_local_commit nfs_local_commit() is reached only through nfs_initiate_commit(), and every path that supplies its "how" argument has already cleared FLUSH_SYNC: __nfs_commit_inode() strips it (how &= ~FLUSH_SYNC) before dispatch and does its own waiting via wait_on_commit(), while the O_DIRECT path passes how=0. filelayout issues its DS commit with a NULL localio, so it never enters nfs_local_commit() at all. The FLUSH_SYNC branch has therefore been dead since it was introduced with commit 70ba381e1a43 ("nfs: add LOCALIO support"). Remove the never-taken FLUSH_SYNC branch along with the completion plumbing it was the sole user of: the struct nfs_local_fsync_ctx::done member, its initialization, and the complete() call in nfs_local_fsync_work(). With the branch gone the "how" parameter is unused, so drop it from nfs_local_commit() and its callers. No functional change. Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Mike Snitzer Signed-off-by: Trond Myklebust --- fs/nfs/internal.h | 4 ++-- fs/nfs/localio.c | 15 ++------------- fs/nfs/write.c | 2 +- 3 files changed, 5 insertions(+), 16 deletions(-) diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h index 864fa092bcea..9ddf0192a0b9 100644 --- a/fs/nfs/internal.h +++ b/fs/nfs/internal.h @@ -480,7 +480,7 @@ extern int nfs_local_doio(struct nfs_client *, const struct rpc_call_ops *); extern int nfs_local_commit(struct nfsd_file *, struct nfs_commit_data *, - const struct rpc_call_ops *, int); + const struct rpc_call_ops *); extern bool nfs_server_is_local(const struct nfs_client *clp); #else /* CONFIG_NFS_LOCALIO */ @@ -502,7 +502,7 @@ static inline int nfs_local_doio(struct nfs_client *clp, } static inline int nfs_local_commit(struct nfsd_file *localio, struct nfs_commit_data *data, - const struct rpc_call_ops *call_ops, int how) + const struct rpc_call_ops *call_ops) { return -EINVAL; } diff --git a/fs/nfs/localio.c b/fs/nfs/localio.c index d3e480888eb1..acbc2bddcf81 100644 --- a/fs/nfs/localio.c +++ b/fs/nfs/localio.c @@ -52,7 +52,6 @@ struct nfs_local_fsync_ctx { struct nfsd_file *localio; struct nfs_commit_data *data; struct work_struct work; - struct completion *done; }; static bool localio_enabled __read_mostly = true; @@ -1100,8 +1099,6 @@ nfs_local_fsync_work(struct work_struct *work) status = nfs_local_run_commit(nfs_to->nfsd_file_file(ctx->localio), ctx->data); nfs_local_commit_done(ctx->data, status); - if (ctx->done != NULL) - complete(ctx->done); nfs_local_fsync_ctx_free(ctx); current->flags = old_flags; @@ -1117,14 +1114,13 @@ nfs_local_fsync_ctx_alloc(struct nfs_commit_data *data, ctx->localio = localio; ctx->data = data; INIT_WORK(&ctx->work, nfs_local_fsync_work); - ctx->done = NULL; } return ctx; } int nfs_local_commit(struct nfsd_file *localio, struct nfs_commit_data *data, - const struct rpc_call_ops *call_ops, int how) + const struct rpc_call_ops *call_ops) { struct nfs_local_fsync_ctx *ctx; @@ -1136,14 +1132,7 @@ int nfs_local_commit(struct nfsd_file *localio, } nfs_local_init_commit(data, call_ops); - - if (how & FLUSH_SYNC) { - DECLARE_COMPLETION_ONSTACK(done); - ctx->done = &done; - queue_work(nfslocaliod_workqueue, &ctx->work); - wait_for_completion(&done); - } else - queue_work(nfslocaliod_workqueue, &ctx->work); + queue_work(nfslocaliod_workqueue, &ctx->work); return 0; } diff --git a/fs/nfs/write.c b/fs/nfs/write.c index ec4e0d6c829c..623e7ef1f73d 100644 --- a/fs/nfs/write.c +++ b/fs/nfs/write.c @@ -1665,7 +1665,7 @@ int nfs_initiate_commit(struct rpc_clnt *clnt, struct nfs_commit_data *data, dprintk("NFS: initiated commit call\n"); if (localio) - return nfs_local_commit(localio, data, call_ops, how); + return nfs_local_commit(localio, data, call_ops); task = rpc_run_task(&task_setup_data); if (IS_ERR(task)) From 3e05a62a97b15dfaf9b14875d68084003828fb74 Mon Sep 17 00:00:00 2001 From: Mike Snitzer Date: Mon, 6 Jul 2026 12:05:49 -0400 Subject: [PATCH 0605/1328] NFS/localio: issue commit inline when not in a memory-reclaim context Extend the memory-reclaim-context test used for LOCALIO reads and writes to the commit (fsync) path. As with data IO, bouncing every commit through the dedicated !WQ_MEM_RECLAIM nfslocaliod_workqueue is only required when the submitting context is a memory-reclaim context: nfs_local_run_commit() calls vfs_fsync_range(), which may flush the underlying filesystem's own !WQ_MEM_RECLAIM workqueue, and doing so from a WQ_MEM_RECLAIM worker or a PF_MEMALLOC task trips check_flush_dependency(). The writeback path does exercise this: nfs_write_inode() (the ->write_inode super_op) runs under wb_workfn on the WQ_MEM_RECLAIM bdi_wq and reaches nfs_local_commit() via __nfs_commit_inode(), so that case must keep deferring. Application-context commits -- fsync (nfs_file_fsync), O_DIRECT (nfs_direct), and copy/clone (nfs42) -- are not in a reclaim context and now run the fsync inline via nfs_local_defer_io(), avoiding the per-commit workqueue hop. Completion (nfs_commit_release_pages -> nfs_commit_end) then runs synchronously in the submitting context; higher layers already cope with this, as __nfs_commit_inode() dispatches the commit async and waits for it separately via wait_on_commit(). Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Mike Snitzer Signed-off-by: Trond Myklebust --- fs/nfs/localio.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/fs/nfs/localio.c b/fs/nfs/localio.c index acbc2bddcf81..f42b6112a613 100644 --- a/fs/nfs/localio.c +++ b/fs/nfs/localio.c @@ -1132,7 +1132,19 @@ int nfs_local_commit(struct nfsd_file *localio, } nfs_local_init_commit(data, call_ops); - queue_work(nfslocaliod_workqueue, &ctx->work); + + /* + * Run the commit (fsync) inline when not in a memory-reclaim context, + * rather than bouncing through nfslocaliod_workqueue; see + * nfs_local_defer_io(). Completion (nfs_commit_release_pages -> + * nfs_commit_end) then runs synchronously, which higher layers cope + * with: __nfs_commit_inode() dispatches async and waits via + * wait_on_commit(). + */ + if (nfs_local_defer_io()) + queue_work(nfslocaliod_workqueue, &ctx->work); + else + nfs_local_fsync_work(&ctx->work); return 0; } From 932a8cf6abb2b2f8677b79153a823108d8861fe2 Mon Sep 17 00:00:00 2001 From: Luxiao Xu Date: Tue, 7 Jul 2026 13:20:47 +0800 Subject: [PATCH 0606/1328] sunrpc: fix use-after-free in __rpc_clnt_handle_event and __rpc_clnt_remove_pipedir Normal client creation goes through rpc_setup_pipedir(), which records clnt->pipefs_sb, but the mount-event path in __rpc_clnt_handle_event() calls rpc_setup_pipedir_sb() directly and never refreshes that field. The umount path also removes the directory without clearing clnt->pipefs_sb. After a late pipefs mount or any remount, rpc_clnt_remove_pipedir() compares the current superblock against a stale pipefs_sb pointer and skips cleanup, leaving pipefs dentries whose inode private data still points at a freed rpc_clnt, leading to a potential use-after-free during subsequent rpc_info_open() or rpc_show_info() calls. Fix this by properly updating clnt->pipefs_sb upon mount events and clearing it during unmount or failure paths. Fixes: bfca5fb4e97c ("SUNRPC: Fix RPC client cleaned up the freed pipefs dentries") Cc: stable@vger.kernel.org Reported-by: Yuan Tan Reported-by: Xin Liu Reviewed-by: Ren Wei Assisted-by: Codex:gpt-5.4 Signed-off-by: Luxiao Xu Signed-off-by: Trond Myklebust --- net/sunrpc/clnt.c | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/net/sunrpc/clnt.c b/net/sunrpc/clnt.c index efa26899bc7d..6cedc824cf82 100644 --- a/net/sunrpc/clnt.c +++ b/net/sunrpc/clnt.c @@ -96,7 +96,10 @@ static void rpc_unregister_client(struct rpc_clnt *clnt) static void __rpc_clnt_remove_pipedir(struct rpc_clnt *clnt) { - rpc_remove_client_dir(clnt); + if (clnt->pipefs_sb) { + rpc_remove_client_dir(clnt); + clnt->pipefs_sb = NULL; + } } static void rpc_clnt_remove_pipedir(struct rpc_clnt *clnt) @@ -177,19 +180,28 @@ static int rpc_clnt_skip_event(struct rpc_clnt *clnt, unsigned long event) } static int __rpc_clnt_handle_event(struct rpc_clnt *clnt, unsigned long event, - struct super_block *sb) + struct super_block *sb) { + int err = 0; + switch (event) { case RPC_PIPEFS_MOUNT: - return rpc_setup_pipedir_sb(sb, clnt); + clnt->pipefs_sb = sb; + err = rpc_setup_pipedir_sb(sb, clnt); + if (err) + clnt->pipefs_sb = NULL; + break; case RPC_PIPEFS_UMOUNT: - __rpc_clnt_remove_pipedir(clnt); + if (clnt->pipefs_sb == sb) { + __rpc_clnt_remove_pipedir(clnt); + clnt->pipefs_sb = NULL; + } break; default: printk(KERN_ERR "%s: unknown event: %ld\n", __func__, event); return -ENOTSUPP; } - return 0; + return err; } static int __rpc_pipefs_event(struct rpc_clnt *clnt, unsigned long event, From 8cb1ce7aa0e8ac30e55f5bccfb80125f2e43e84a Mon Sep 17 00:00:00 2001 From: Jia Zhu Date: Tue, 7 Jul 2026 21:36:23 +0800 Subject: [PATCH 0607/1328] NFSv4: pin the superblock for active state owners NFSv4 open state can outlive the file and dentry that created it. This was observed in production when NFSv4 state recovery, such as after a server reboot or lease expiration, raced with unmount. The race requires recovery to hold an open state reference while the last open file is closed and the filesystem is unmounted, allowing the superblock's active reference to drop to zero between refcount_inc(&state->count) and nfs4_put_open_state(): state manager umount nfs4_run_state_manager() nfs4_do_reclaim() nfs4_reclaim_open_state() refcount_inc(&state->count) ... close last file generic_shutdown_super() "Busy inodes after unmount" nfs_free_server() nfs4_put_open_state() iput(inode) evict() nfs_clear_inode() nfs_zap_acl_cache() The "VFS: Busy inodes after unmount" warning is the visible symptom of that lifetime mismatch: superblock teardown proceeds even though the NFS open state still pins an inode. After umount has freed the server, the state manager can then run nfs4_put_open_state() for the last open-state reference. The resulting iput(inode) can evict an NFS inode with freed server data, causing crashes at nfs_zap_acl_cache(). This can be reproduced by delaying the reclaim path before nfs4_put_open_state(), then closing the last file and unmounting the NFS mount. Pin the superblock while a state owner is active, and drop the pin when the owner becomes idle again, so the NFS server stays alive until all open state associated with the owner has been released. Assisted-by: Codex:GPT-5 Signed-off-by: Jia Zhu Signed-off-by: Trond Myklebust --- fs/nfs/nfs4state.c | 40 +++++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/fs/nfs/nfs4state.c b/fs/nfs/nfs4state.c index 305a772e5497..a5dec0473e22 100644 --- a/fs/nfs/nfs4state.c +++ b/fs/nfs/nfs4state.c @@ -351,6 +351,26 @@ const struct cred *nfs4_get_clid_cred(struct nfs_client *clp) return cred; } +static bool +nfs4_get_state_owner_active_locked(struct nfs4_state_owner *sp) +{ + struct nfs_server *server = sp->so_server; + + /* + * A counted state owner may dereference so_server until the final + * nfs4_put_state_owner(). Pin the superblock when reviving an idle + * owner so umount cannot free the server underneath it. + */ + if (atomic_read(&sp->so_count) == 0) { + if (!nfs_sb_active(server->super)) + return false; + if (!list_empty(&sp->so_lru)) + list_del_init(&sp->so_lru); + } + atomic_inc(&sp->so_count); + return true; +} + static struct nfs4_state_owner * nfs4_find_state_owner_locked(struct nfs_server *server, const struct cred *cred) { @@ -369,9 +389,8 @@ nfs4_find_state_owner_locked(struct nfs_server *server, const struct cred *cred) else if (cmp > 0) p = &parent->rb_right; else { - if (!list_empty(&sp->so_lru)) - list_del_init(&sp->so_lru); - atomic_inc(&sp->so_count); + if (!nfs4_get_state_owner_active_locked(sp)) + return NULL; return sp; } } @@ -397,9 +416,8 @@ nfs4_insert_state_owner_locked(struct nfs4_state_owner *new) else if (cmp > 0) p = &parent->rb_right; else { - if (!list_empty(&sp->so_lru)) - list_del_init(&sp->so_lru); - atomic_inc(&sp->so_count); + if (!nfs4_get_state_owner_active_locked(sp)) + return NULL; return sp; } } @@ -449,6 +467,10 @@ nfs4_alloc_state_owner(struct nfs_server *server, sp = kzalloc_obj(*sp, gfp_flags); if (!sp) return NULL; + if (!nfs_sb_active(server->super)) { + kfree(sp); + return NULL; + } sp->so_seqid.owner_id = atomic64_inc_return(&server->owner_ctr); sp->so_server = server; sp->so_cred = get_cred(cred); @@ -534,8 +556,10 @@ struct nfs4_state_owner *nfs4_get_state_owner(struct nfs_server *server, spin_lock(&clp->cl_lock); sp = nfs4_insert_state_owner_locked(new); spin_unlock(&clp->cl_lock); - if (sp != new) + if (sp != new) { nfs4_free_state_owner(new); + nfs_sb_deactive(server->super); + } out: nfs4_gc_state_owners(server); return sp; @@ -557,6 +581,7 @@ void nfs4_put_state_owner(struct nfs4_state_owner *sp) { struct nfs_server *server = sp->so_server; struct nfs_client *clp = server->nfs_client; + struct super_block *sb = server->super; if (!atomic_dec_and_lock(&sp->so_count, &clp->cl_lock)) return; @@ -564,6 +589,7 @@ void nfs4_put_state_owner(struct nfs4_state_owner *sp) sp->so_expires = jiffies; list_add_tail(&sp->so_lru, &server->state_owners_lru); spin_unlock(&clp->cl_lock); + nfs_sb_deactive(sb); } /** From 2b03ebbf8d5e8f6af4ecd6c65375232dd1ec32cc Mon Sep 17 00:00:00 2001 From: Jeuk Kim Date: Wed, 8 Jul 2026 16:44:32 +0900 Subject: [PATCH 0608/1328] NFSv4/flexfiles: fix NULL dereference for NFSv4.0 data servers flexfiles accepts NFSv4.0 data servers, but two NFSv4 code paths assume the data server client has a session. Unlike NFSv4.1+, an NFSv4.0 client has no session (clp->cl_session is NULL; it uses clp->cl_slot_tbl), so I/O to a v4.0 flexfiles DS oopses: - nfs4_init_ds_session() dereferences clp->cl_session->session_state while seeding the DS lease. It also only seeds cl_lease_time when NFS4_SESSION_INITING is set; without a session that never happens, so cl_lease_time stays 0 and nfs4_renew_state() busy-loops, requeuing every 5 seconds. Seed the lease whenever there is no session and return before touching session state. - ff_layout_async_handle_error_v4() dereferences clp->cl_session->fc_slot_table on every DS I/O error. Fall back to the v4.0 transport slot table (clp->cl_slot_tbl) when there is no session. Fixes: a7878ca14008 ("nfs: flexfilelayout: remove v3-only data server limitation") Signed-off-by: Jeuk Kim Signed-off-by: Trond Myklebust --- fs/nfs/flexfilelayout/flexfilelayout.c | 3 ++- fs/nfs/nfs4session.c | 16 +++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/fs/nfs/flexfilelayout/flexfilelayout.c b/fs/nfs/flexfilelayout/flexfilelayout.c index c8072f333236..7fe8b91fa47c 100644 --- a/fs/nfs/flexfilelayout/flexfilelayout.c +++ b/fs/nfs/flexfilelayout/flexfilelayout.c @@ -1322,7 +1322,8 @@ static int ff_layout_async_handle_error_v4(struct rpc_task *task, struct pnfs_layout_hdr *lo = lseg->pls_layout; struct inode *inode = lo->plh_inode; struct nfs4_deviceid_node *devid = FF_LAYOUT_DEVID_NODE(lseg, idx, dss_id); - struct nfs4_slot_table *tbl = &clp->cl_session->fc_slot_table; + struct nfs4_slot_table *tbl = nfs4_has_session(clp) ? + &clp->cl_session->fc_slot_table : clp->cl_slot_tbl; switch (op_status) { case NFS4_OK: diff --git a/fs/nfs/nfs4session.c b/fs/nfs/nfs4session.c index 5c128957a0a4..993f0db7cf5e 100644 --- a/fs/nfs/nfs4session.c +++ b/fs/nfs/nfs4session.c @@ -632,16 +632,22 @@ int nfs4_init_ds_session(struct nfs_client *clp, unsigned long lease_time) int ret; spin_lock(&clp->cl_lock); - if (test_and_clear_bit(NFS4_SESSION_INITING, &session->session_state)) { - /* - * Do not set NFS_CS_CHECK_LEASE_TIME instead set the - * DS lease to be equal to the MDS lease. - */ + /* + * Do not set NFS_CS_CHECK_LEASE_TIME instead set the + * DS lease to be equal to the MDS lease. + * + * A v4.0 DS has no session, so seed the lease every time. + */ + if (!session || + test_and_clear_bit(NFS4_SESSION_INITING, &session->session_state)) { clp->cl_lease_time = lease_time; clp->cl_last_renewal = jiffies; } spin_unlock(&clp->cl_lock); + if (!session) + return 0; + ret = nfs41_check_session_ready(clp); if (ret) return ret; From 92a885576fbcd145cf3cdfa73d33ca79d7ef81a4 Mon Sep 17 00:00:00 2001 From: Jeuk Kim Date: Wed, 8 Jul 2026 16:44:33 +0900 Subject: [PATCH 0609/1328] NFSv4/flexfiles: support loosely coupled data servers A flexfiles storage device is tightly coupled to the MDS only when the decoded ds_versions[0].tightly_coupled flag is set (RFC 8435, sections 2.3 and 4.1). The client currently ignores that flag and treats every data server as tightly coupled, which breaks I/O to loosely coupled DSes. Two things force that assumption on an NFSv4.1+ DS: 1) nfs4_set_ds_client() always sets NFS_CS_PNFS on the new client, so EXCHANGE_ID is sent with EXCHGID4_FLAG_USE_PNFS_DS. 2) nfs4_init_ds_session() then calls is_ds_client() and returns -ENODEV if the reply does not carry EXCHGID4_FLAG_USE_PNFS_DS. A loosely coupled DS is just a normal NFS server and does not act in the pNFS DS role, so the client must not require it to advertise that role. Thread the ds_versions[0].tightly_coupled flag from the flexfiles driver down to the DS connect path. When it is false, skip both the NFS_CS_PNFS flag and the is_ds_client() check. The file layout driver always passes true because NFSv4.1 file layout data servers use the pNFS DS role. Signed-off-by: Jeuk Kim Signed-off-by: Trond Myklebust --- fs/nfs/filelayout/filelayoutdev.c | 2 +- fs/nfs/flexfilelayout/flexfilelayoutdev.c | 3 ++- fs/nfs/internal.h | 3 ++- fs/nfs/nfs4client.c | 5 +++-- fs/nfs/nfs4session.c | 5 +++-- fs/nfs/nfs4session.h | 3 ++- fs/nfs/pnfs.h | 3 ++- fs/nfs/pnfs_nfs.c | 14 +++++++++----- 8 files changed, 24 insertions(+), 14 deletions(-) diff --git a/fs/nfs/filelayout/filelayoutdev.c b/fs/nfs/filelayout/filelayoutdev.c index 7226989ee4d5..d06d303fdcc3 100644 --- a/fs/nfs/filelayout/filelayoutdev.c +++ b/fs/nfs/filelayout/filelayoutdev.c @@ -280,7 +280,7 @@ nfs4_fl_prepare_ds(struct pnfs_layout_segment *lseg, u32 ds_idx) status = nfs4_pnfs_ds_connect(s, ds, devid, dataserver_timeo, dataserver_retrans, 4, - s->nfs_client->cl_minorversion); + s->nfs_client->cl_minorversion, true); if (status) { nfs4_mark_deviceid_unavailable(devid); ret = NULL; diff --git a/fs/nfs/flexfilelayout/flexfilelayoutdev.c b/fs/nfs/flexfilelayout/flexfilelayoutdev.c index 1109462a9699..8be5c730e101 100644 --- a/fs/nfs/flexfilelayout/flexfilelayoutdev.c +++ b/fs/nfs/flexfilelayout/flexfilelayoutdev.c @@ -399,7 +399,8 @@ nfs4_ff_layout_prepare_ds(struct pnfs_layout_segment *lseg, status = nfs4_pnfs_ds_connect(s, ds, &mirror->dss[dss_id].mirror_ds->id_node, dataserver_timeo, dataserver_retrans, mirror->dss[dss_id].mirror_ds->ds_versions[0].version, - mirror->dss[dss_id].mirror_ds->ds_versions[0].minor_version); + mirror->dss[dss_id].mirror_ds->ds_versions[0].minor_version, + mirror->dss[dss_id].mirror_ds->ds_versions[0].tightly_coupled); /* connect success, check rsize/wsize limit */ if (!status) { diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h index 9ddf0192a0b9..8656ea6af887 100644 --- a/fs/nfs/internal.h +++ b/fs/nfs/internal.h @@ -251,7 +251,8 @@ extern struct nfs_client *nfs4_set_ds_client(struct nfs_server *mds_srv, int ds_addrlen, int ds_proto, unsigned int ds_timeo, unsigned int ds_retrans, - u32 minor_version); + u32 minor_version, + bool tightly_coupled); extern struct rpc_clnt *nfs4_find_or_create_ds_client(struct nfs_client *, struct inode *); extern void nfs4_session_limit_rwsize(struct nfs_server *server); diff --git a/fs/nfs/nfs4client.c b/fs/nfs/nfs4client.c index aff019d2842d..d06bfe317890 100644 --- a/fs/nfs/nfs4client.c +++ b/fs/nfs/nfs4client.c @@ -792,7 +792,7 @@ static int nfs4_set_client(struct nfs_server *server, struct nfs_client *nfs4_set_ds_client(struct nfs_server *mds_srv, const struct sockaddr_storage *ds_addr, int ds_addrlen, int ds_proto, unsigned int ds_timeo, unsigned int ds_retrans, - u32 minor_version) + u32 minor_version, bool tightly_coupled) { struct rpc_timeout ds_timeout; struct nfs_client *mds_clp = mds_srv->nfs_client; @@ -839,7 +839,8 @@ struct nfs_client *nfs4_set_ds_client(struct nfs_server *mds_srv, if (test_bit(NFS_CS_NETUNREACH_FATAL, &mds_clp->cl_flags)) __set_bit(NFS_CS_NETUNREACH_FATAL, &cl_init.init_flags); - __set_bit(NFS_CS_PNFS, &cl_init.init_flags); + if (tightly_coupled) + __set_bit(NFS_CS_PNFS, &cl_init.init_flags); cl_init.max_connect = NFS_MAX_TRANSPORTS; /* * Set an authflavor equual to the MDS value. Use the MDS nfs_client diff --git a/fs/nfs/nfs4session.c b/fs/nfs/nfs4session.c index 993f0db7cf5e..175390e5b93f 100644 --- a/fs/nfs/nfs4session.c +++ b/fs/nfs/nfs4session.c @@ -626,7 +626,8 @@ int nfs4_init_session(struct nfs_client *clp) return nfs41_check_session_ready(clp); } -int nfs4_init_ds_session(struct nfs_client *clp, unsigned long lease_time) +int nfs4_init_ds_session(struct nfs_client *clp, unsigned long lease_time, + bool tightly_coupled) { struct nfs4_session *session = clp->cl_session; int ret; @@ -652,7 +653,7 @@ int nfs4_init_ds_session(struct nfs_client *clp, unsigned long lease_time) if (ret) return ret; /* Test for the DS role */ - if (!is_ds_client(clp)) + if (tightly_coupled && !is_ds_client(clp)) return -ENODEV; return 0; } diff --git a/fs/nfs/nfs4session.h b/fs/nfs/nfs4session.h index d2569f599977..ee2f4baf16a1 100644 --- a/fs/nfs/nfs4session.h +++ b/fs/nfs/nfs4session.h @@ -122,7 +122,8 @@ extern int nfs4_setup_session_slot_tables(struct nfs4_session *ses); extern struct nfs4_session *nfs4_alloc_session(struct nfs_client *clp); extern void nfs4_destroy_session(struct nfs4_session *session); extern int nfs4_init_session(struct nfs_client *clp); -extern int nfs4_init_ds_session(struct nfs_client *, unsigned long); +extern int nfs4_init_ds_session(struct nfs_client *clp, unsigned long lease_time, + bool tightly_coupled); /* * Determine if sessions are in use. diff --git a/fs/nfs/pnfs.h b/fs/nfs/pnfs.h index 673c2b244978..bab81f769636 100644 --- a/fs/nfs/pnfs.h +++ b/fs/nfs/pnfs.h @@ -421,7 +421,8 @@ struct nfs4_pnfs_ds *nfs4_pnfs_ds_add(const struct net *net, void nfs4_pnfs_v3_ds_connect_unload(void); int nfs4_pnfs_ds_connect(struct nfs_server *mds_srv, struct nfs4_pnfs_ds *ds, struct nfs4_deviceid_node *devid, unsigned int timeo, - unsigned int retrans, u32 version, u32 minor_version); + unsigned int retrans, u32 version, u32 minor_version, + bool tightly_coupled); struct nfs4_pnfs_ds_addr *nfs4_decode_mp_ds_addr(struct net *net, struct xdr_stream *xdr, gfp_t gfp_flags); diff --git a/fs/nfs/pnfs_nfs.c b/fs/nfs/pnfs_nfs.c index 648c95b78eea..b539e1a44d26 100644 --- a/fs/nfs/pnfs_nfs.c +++ b/fs/nfs/pnfs_nfs.c @@ -881,7 +881,8 @@ static int _nfs4_pnfs_v4_ds_connect(struct nfs_server *mds_srv, struct nfs4_pnfs_ds *ds, unsigned int timeo, unsigned int retrans, - u32 minor_version) + u32 minor_version, + bool tightly_coupled) { struct nfs_client *clp = ERR_PTR(-EIO); struct nfs_client *mds_clp = mds_srv->nfs_client; @@ -971,12 +972,14 @@ static int _nfs4_pnfs_v4_ds_connect(struct nfs_server *mds_srv, clp = nfs4_set_ds_client(mds_srv, &da->da_addr, da->da_addrlen, ds_proto, - timeo, retrans, minor_version); + timeo, retrans, minor_version, + tightly_coupled); if (IS_ERR(clp)) continue; status = nfs4_init_ds_session(clp, - mds_srv->nfs_client->cl_lease_time); + mds_srv->nfs_client->cl_lease_time, + tightly_coupled); if (status) { nfs_put_client(clp); clp = ERR_PTR(-EIO); @@ -1004,7 +1007,8 @@ static int _nfs4_pnfs_v4_ds_connect(struct nfs_server *mds_srv, */ int nfs4_pnfs_ds_connect(struct nfs_server *mds_srv, struct nfs4_pnfs_ds *ds, struct nfs4_deviceid_node *devid, unsigned int timeo, - unsigned int retrans, u32 version, u32 minor_version) + unsigned int retrans, u32 version, u32 minor_version, + bool tightly_coupled) { int err; @@ -1027,7 +1031,7 @@ int nfs4_pnfs_ds_connect(struct nfs_server *mds_srv, struct nfs4_pnfs_ds *ds, break; case 4: err = _nfs4_pnfs_v4_ds_connect(mds_srv, ds, timeo, retrans, - minor_version); + minor_version, tightly_coupled); break; default: dprintk("%s: unsupported DS version %d\n", __func__, version); From 2092f5b38f88be306140c77aeeeb43fc1adacacc Mon Sep 17 00:00:00 2001 From: Nate Prodromou Date: Tue, 14 Jul 2026 18:58:46 +0000 Subject: [PATCH 0610/1328] NFS: fix delegation_hash_table leak when nfs4_server_common_setup() fails nfs4_server_common_setup() allocates server->delegation_hash_table first, but server->destroy - the only path that frees the table via nfs4_destroy_server() - is not assigned until the very end of the function. If any intermediate step fails (the is_ds_only_client() check, nfs4_init_session(), nfs4_get_rootfh(), or nfs_probe_server()), the function returns with server->destroy still NULL, so the caller's nfs_free_server() skips the destroy callback and the hash table is leaked (4 KiB per attempt with the default delegation watermark). This is trivially reachable from userspace: every failed NFSv4 mount leaks one allocation. A client that persistently retries a mount that cannot succeed leaks kernel memory without bound. Observed in production where a Longhorn backup poller retried mount.nfs4 against an NFSv3-only server roughly 10 times per second, leaking ~3.4 GiB of unreclaimable slab (kmalloc-rnd-13-4k) per day; the node accumulated 12 GiB of leaked slab before the source was identified via the kmem:kmalloc tracepoint (call_site=nfs4_delegation_hash_alloc). Reproducer: # server exports NFSv3 only (or export path absent for v4) while :; do mount -t nfs4 :/missing /mnt; done # watch SUnreclaim in /proc/meminfo grow 4 KiB per iteration Free the table on the error paths between the allocation and the assignment of server->destroy. Fixes: f5b3108e6a14 ("NFS: use a hash table for delegation lookup") Cc: stable@vger.kernel.org Signed-off-by: Nate Prodromou Reviewed-by: Christoph Hellwig Signed-off-by: Trond Myklebust --- fs/nfs/nfs4client.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/fs/nfs/nfs4client.c b/fs/nfs/nfs4client.c index d06bfe317890..b661f446ea49 100644 --- a/fs/nfs/nfs4client.c +++ b/fs/nfs/nfs4client.c @@ -917,20 +917,22 @@ static int nfs4_server_common_setup(struct nfs_server *server, return error; /* data servers support only a subset of NFSv4.1 */ - if (is_ds_only_client(server->nfs_client)) - return -EPROTONOSUPPORT; + if (is_ds_only_client(server->nfs_client)) { + error = -EPROTONOSUPPORT; + goto out_free_delegation_hash; + } /* We must ensure the session is initialised first */ error = nfs4_init_session(server->nfs_client); if (error < 0) - return error; + goto out_free_delegation_hash; nfs_server_set_init_caps(server); /* Probe the root fh to retrieve its FSID and filehandle */ error = nfs4_get_rootfh(server, mntfh, auth_probe); if (error < 0) - return error; + goto out_free_delegation_hash; dprintk("Server FSID: %llx:%llx\n", (unsigned long long) server->fsid.major, @@ -939,7 +941,7 @@ static int nfs4_server_common_setup(struct nfs_server *server, error = nfs_probe_server(server, mntfh); if (error < 0) - return error; + goto out_free_delegation_hash; nfs4_session_limit_rwsize(server); nfs4_session_limit_xasize(server); @@ -951,6 +953,11 @@ static int nfs4_server_common_setup(struct nfs_server *server, server->mount_time = jiffies; server->destroy = nfs4_destroy_server; return 0; + +out_free_delegation_hash: + kfree(server->delegation_hash_table); + server->delegation_hash_table = NULL; + return error; } /* From 4c7fc129db061c7daab841c4f3c342d894832362 Mon Sep 17 00:00:00 2001 From: Shuangpeng Bai Date: Fri, 17 Jul 2026 13:28:09 -0400 Subject: [PATCH 0611/1328] lockd: fix NULL dereference on lockowner allocation failure nlmclnt_locks_init_private() installs NLM file lock operations even when nlmclnt_find_lockowner() fails to allocate a lockowner. nlmclnt_proc() then returns -ENOMEM, but the VFS still tears down the partially initialized file_lock and calls locks_release_private(). That invokes nlmclnt_locks_release_private(), which dereferences fl->fl_u.nfs_fl.owner and crashes because the owner was never installed. Clear fl_ops before attempting to initialize the NLM private state, and install the NLM lock operations only after a lockowner has been allocated successfully. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Shuangpeng Bai Signed-off-by: Trond Myklebust --- fs/lockd/clntproc.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fs/lockd/clntproc.c b/fs/lockd/clntproc.c index f06faf577cea..f8018bfe9c64 100644 --- a/fs/lockd/clntproc.c +++ b/fs/lockd/clntproc.c @@ -487,9 +487,12 @@ static const struct file_lock_operations nlmclnt_lock_ops = { static void nlmclnt_locks_init_private(struct file_lock *fl, struct nlm_host *host) { fl->fl_u.nfs_fl.state = 0; + fl->fl_ops = NULL; fl->fl_u.nfs_fl.owner = nlmclnt_find_lockowner(host, fl->c.flc_owner); INIT_LIST_HEAD(&fl->fl_u.nfs_fl.list); + if (!fl->fl_u.nfs_fl.owner) + return; fl->fl_ops = &nlmclnt_lock_ops; } From 468e458ffde907ba19acd2102ca1fbb8f6fbede2 Mon Sep 17 00:00:00 2001 From: Zhansong Gao Date: Thu, 23 Jul 2026 04:00:59 +0800 Subject: [PATCH 0612/1328] NFSv4: Fix incorrect argument passed to nfs4_delete_lease() in nfs4_add_lease() When nfs4_add_lease() races with a delegation return, it calls nfs4_delete_lease() to clean up. Previously, it passed priv, which can legitimately be NULL. Passing a NULL priv eventually leads to a NULL pointer dereference in generic_setlease(). Fixes: e93a5e9306a5 ("NFSv4: Add support for application leases underpinned by a delegation") Signed-off-by: Zhansong Gao Signed-off-by: Trond Myklebust --- fs/nfs/nfs4proc.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c index b79d121ea069..04b1987115d5 100644 --- a/fs/nfs/nfs4proc.c +++ b/fs/nfs/nfs4proc.c @@ -7817,6 +7817,7 @@ static int nfs4_add_lease(struct file *file, int arg, struct file_lease **lease, { struct inode *inode = file_inode(file); fmode_t type = arg == F_RDLCK ? FMODE_READ : FMODE_WRITE; + fl_owner_t owner = (*lease)->c.flc_owner; int ret; /* No delegation, no lease */ @@ -7826,7 +7827,8 @@ static int nfs4_add_lease(struct file *file, int arg, struct file_lease **lease, if (ret || nfs4_have_delegation(inode, type, 0)) return ret; /* We raced with a delegation return */ - nfs4_delete_lease(file, priv); + dprintk("%s: raced with a delegation return\n", __func__); + nfs4_delete_lease(file, &owner); return -EAGAIN; } From a89dd597458848b463d284b15e42a8078beeb046 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Jean?= Date: Sun, 9 Aug 2026 17:07:48 +0000 Subject: [PATCH 0613/1328] SUNRPC: wait for in-flight client TLS handshake callback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit xs_tls_handshake_sync() gives xs_tls_handshake_done() a reference to the lower transport before submitting the handshake request. On timeout or signal, the synchronous waiter drops that reference after calling tls_handshake_cancel(). handshake_req_cancel() returns false when handshake_complete() has already marked the request complete. In that case the completion callback can still be running, so dropping the callback-owned reference in the waiter can free the lower transport before xs_tls_handshake_done() stores xprt_err or drops its own reference. If cancellation loses to completion, wait until xs_tls_handshake_done() signals handshake_done and let the callback release its reference. This mirrors the server-side handshake lifetime handling and keeps the timeout or signal return value unchanged. Fixes: 75eb6af7acdf ("SUNRPC: Add a TCP-with-TLS RPC transport class") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5 Signed-off-by: Jérémy Jean Reviewed-by: Chuck Lever Signed-off-by: Trond Myklebust --- net/sunrpc/xprtsock.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/net/sunrpc/xprtsock.c b/net/sunrpc/xprtsock.c index d735e6ec7e37..7f60723fa64d 100644 --- a/net/sunrpc/xprtsock.c +++ b/net/sunrpc/xprtsock.c @@ -2650,7 +2650,17 @@ static int xs_tls_handshake_sync(struct rpc_xprt *lower_xprt, struct xprtsec_par rc = wait_for_completion_interruptible_timeout(&lower_transport->handshake_done, XS_TLS_HANDSHAKE_TO); if (rc <= 0) { - tls_handshake_cancel(sk); + if (!tls_handshake_cancel(sk)) { + /* + * Cancellation lost to handshake_complete(): the + * callback still owns its xprt reference and is in + * flight. Wait for it to finish before returning. + */ + wait_for_completion(&lower_transport->handshake_done); + if (rc == 0) + rc = -ETIMEDOUT; + goto out; + } if (rc == 0) rc = -ETIMEDOUT; goto out_put_xprt; From 10f307e525a1783570a39eb9ac146d45f4f16b3e Mon Sep 17 00:00:00 2001 From: Michael Nemanov Date: Thu, 6 Aug 2026 13:13:58 +0000 Subject: [PATCH 0614/1328] nfs: fix ENXIO on O_CREAT open of existing symlink over NFSv3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When open(2) is called with O_CREAT on a path that already exists as a symlink, over an NFSv3 mount with a cold dcache, the kernel returns ENXIO instead of following the symlink to its target. Reproducer script (MNT is an NFSv3 mount, kernel is 7.1-rc6): MNT=/mnt/export ln -sf /tmp/target $MNT/mylink echo 3 | sudo tee /proc/sys/vm/drop_caches # cold dcache python3 - <<'EOF' import os fd = os.open('/mnt/export/mylink', os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o666) os.close(fd) EOF Expected: success (follow symlink, open target) Actual: OSError: [Errno 6] No such device or address The bug does not trigger when the dcache is warm (e.g. after a prior stat(2)), because lookup_open() then finds a positive dentry and skips atomic_open entirely, leaving symlink resolution to the VFS. Root cause: nfs_atomic_open_v23(), registered as inode->i_op->atomic_open for NFSv3, handles O_CREAT by sending a CREATE UNCHECKED RPC. As implemented in nfsd3_create_file() (fs/nfsd/nfs3proc.c) and as required by RFC 1813 (3.3.8), when the name already exists as a non-regular file the server returns NFS3_OK with the existing object's file handle rather than NFS3ERR_EXIST causing nfs_do_create() to return 0 with the dentry now pointing to a symlink. The code then unconditionally calls finish_open(), which dispatches through inode->i_fop->open(). Symlink inodes never have i_fop set — the VFS initialises it to &no_open_fops because POSIX requires open(2) to follow symlinks, never open them directly. no_open() returns -ENXIO. Fix: After nfs_do_create() succeeds, verify the returned inode is a regular file before calling finish_open(). If the object is not regular, return finish_no_open(file, NULL) so the VFS follows the symlink through the normal open path. NULL is passed because nfs_do_create() instantiates the inode on the dentry already owned by the caller; passing dentry back would cause atomic_open() to dput() it a second time. !S_ISREG() is used rather than S_ISLNK() to cover any other non-regular types a server might return. Changes in v2: - Pass NULL to finish_no_open() per Trond's feedback. Fixes: 7c6c5249f061 ("NFS: add atomic_open for NFSv3 to handle O_TRUNC correctly.") Link: https://lore.kernel.org/linux-nfs/20260614122911.3485467-1-michael.nemanov@vastdata.com/ (v1) Signed-off-by: Michael Nemanov Tested-by: Michael Nemanov [trond.myklebust@hammerspace.com: use d_is_reg() to catch negative dentries] Signed-off-by: Trond Myklebust --- fs/nfs/dir.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index c4a0a93b24e4..854967857338 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -2323,6 +2323,13 @@ int nfs_atomic_open_v23(struct inode *dir, struct dentry *dentry, if (open_flags & O_CREAT) { error = nfs_do_create(dir, dentry, mode, open_flags); if (!error) { + /* With UNCHECKED mode, a server may return NFS3_OK for + * a pre-existing non-regular file (e.g. a symlink). + * Let the VFS handle it; calling finish_open() would + * hit no_open() and return -ENXIO. + */ + if (!d_is_reg(dentry)) + return finish_no_open(file, NULL); file->f_mode |= FMODE_CREATED; return finish_open(file, dentry, NULL); } else if (error != -EEXIST || open_flags & O_EXCL) From 5d7ebde44469086af6c3486e0bbed2d8b5c522f1 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sat, 1 Aug 2026 21:55:41 +0200 Subject: [PATCH 0615/1328] dt-bindings: clock: Correct white-space style Correct a few white-space issues, like double space after '=' or before bracket '{' characters, which will be flagged by dt-check-style. No functional changes. Signed-off-by: Krzysztof Kozlowski Reviewed-by: Brian Masney Acked-by: Rob Herring (Arm) Signed-off-by: Stephen Boyd --- .../bindings/clock/mediatek,pericfg.yaml | 2 +- .../bindings/clock/st,stm32mp21-rcc.yaml | 140 +++++++-------- .../bindings/clock/st,stm32mp25-rcc.yaml | 160 +++++++++--------- .../bindings/clock/xlnx,clocking-wizard.yaml | 2 +- 4 files changed, 152 insertions(+), 152 deletions(-) diff --git a/Documentation/devicetree/bindings/clock/mediatek,pericfg.yaml b/Documentation/devicetree/bindings/clock/mediatek,pericfg.yaml index b98cf45efe2f..f0c8df4d10c8 100644 --- a/Documentation/devicetree/bindings/clock/mediatek,pericfg.yaml +++ b/Documentation/devicetree/bindings/clock/mediatek,pericfg.yaml @@ -65,7 +65,7 @@ examples: - | pericfg@10003000 { - compatible = "mediatek,mt7623-pericfg", "mediatek,mt2701-pericfg", "syscon"; + compatible = "mediatek,mt7623-pericfg", "mediatek,mt2701-pericfg", "syscon"; reg = <0x10003000 0x1000>; #clock-cells = <1>; #reset-cells = <1>; diff --git a/Documentation/devicetree/bindings/clock/st,stm32mp21-rcc.yaml b/Documentation/devicetree/bindings/clock/st,stm32mp21-rcc.yaml index 4368063c6709..e4d64d4b63c6 100644 --- a/Documentation/devicetree/bindings/clock/st,stm32mp21-rcc.yaml +++ b/Documentation/devicetree/bindings/clock/st,stm32mp21-rcc.yaml @@ -125,75 +125,75 @@ examples: reg = <0x44200000 0x10000>; #clock-cells = <1>; #reset-cells = <1>; - clocks = <&scmi_clk CK_SCMI_HSE>, - <&scmi_clk CK_SCMI_HSI>, - <&scmi_clk CK_SCMI_MSI>, - <&scmi_clk CK_SCMI_LSE>, - <&scmi_clk CK_SCMI_LSI>, - <&scmi_clk CK_SCMI_HSE_DIV2>, - <&scmi_clk CK_SCMI_ICN_HS_MCU>, - <&scmi_clk CK_SCMI_ICN_LS_MCU>, - <&scmi_clk CK_SCMI_ICN_SDMMC>, - <&scmi_clk CK_SCMI_ICN_DDR>, - <&scmi_clk CK_SCMI_ICN_DISPLAY>, - <&scmi_clk CK_SCMI_ICN_HSL>, - <&scmi_clk CK_SCMI_ICN_NIC>, - <&scmi_clk CK_SCMI_FLEXGEN_07>, - <&scmi_clk CK_SCMI_FLEXGEN_08>, - <&scmi_clk CK_SCMI_FLEXGEN_09>, - <&scmi_clk CK_SCMI_FLEXGEN_10>, - <&scmi_clk CK_SCMI_FLEXGEN_11>, - <&scmi_clk CK_SCMI_FLEXGEN_12>, - <&scmi_clk CK_SCMI_FLEXGEN_13>, - <&scmi_clk CK_SCMI_FLEXGEN_14>, - <&scmi_clk CK_SCMI_FLEXGEN_16>, - <&scmi_clk CK_SCMI_FLEXGEN_17>, - <&scmi_clk CK_SCMI_FLEXGEN_18>, - <&scmi_clk CK_SCMI_FLEXGEN_19>, - <&scmi_clk CK_SCMI_FLEXGEN_20>, - <&scmi_clk CK_SCMI_FLEXGEN_21>, - <&scmi_clk CK_SCMI_FLEXGEN_22>, - <&scmi_clk CK_SCMI_FLEXGEN_23>, - <&scmi_clk CK_SCMI_FLEXGEN_24>, - <&scmi_clk CK_SCMI_FLEXGEN_25>, - <&scmi_clk CK_SCMI_FLEXGEN_26>, - <&scmi_clk CK_SCMI_FLEXGEN_27>, - <&scmi_clk CK_SCMI_FLEXGEN_29>, - <&scmi_clk CK_SCMI_FLEXGEN_30>, - <&scmi_clk CK_SCMI_FLEXGEN_31>, - <&scmi_clk CK_SCMI_FLEXGEN_33>, - <&scmi_clk CK_SCMI_FLEXGEN_36>, - <&scmi_clk CK_SCMI_FLEXGEN_37>, - <&scmi_clk CK_SCMI_FLEXGEN_38>, - <&scmi_clk CK_SCMI_FLEXGEN_39>, - <&scmi_clk CK_SCMI_FLEXGEN_40>, - <&scmi_clk CK_SCMI_FLEXGEN_41>, - <&scmi_clk CK_SCMI_FLEXGEN_42>, - <&scmi_clk CK_SCMI_FLEXGEN_43>, - <&scmi_clk CK_SCMI_FLEXGEN_44>, - <&scmi_clk CK_SCMI_FLEXGEN_45>, - <&scmi_clk CK_SCMI_FLEXGEN_46>, - <&scmi_clk CK_SCMI_FLEXGEN_47>, - <&scmi_clk CK_SCMI_FLEXGEN_48>, - <&scmi_clk CK_SCMI_FLEXGEN_50>, - <&scmi_clk CK_SCMI_FLEXGEN_51>, - <&scmi_clk CK_SCMI_FLEXGEN_52>, - <&scmi_clk CK_SCMI_FLEXGEN_53>, - <&scmi_clk CK_SCMI_FLEXGEN_54>, - <&scmi_clk CK_SCMI_FLEXGEN_55>, - <&scmi_clk CK_SCMI_FLEXGEN_56>, - <&scmi_clk CK_SCMI_FLEXGEN_57>, - <&scmi_clk CK_SCMI_FLEXGEN_58>, - <&scmi_clk CK_SCMI_FLEXGEN_61>, - <&scmi_clk CK_SCMI_FLEXGEN_62>, - <&scmi_clk CK_SCMI_FLEXGEN_63>, - <&scmi_clk CK_SCMI_ICN_APB1>, - <&scmi_clk CK_SCMI_ICN_APB2>, - <&scmi_clk CK_SCMI_ICN_APB3>, - <&scmi_clk CK_SCMI_ICN_APB4>, - <&scmi_clk CK_SCMI_ICN_APB5>, - <&scmi_clk CK_SCMI_ICN_APBDBG>, - <&scmi_clk CK_SCMI_TIMG1>, - <&scmi_clk CK_SCMI_TIMG2>; + clocks = <&scmi_clk CK_SCMI_HSE>, + <&scmi_clk CK_SCMI_HSI>, + <&scmi_clk CK_SCMI_MSI>, + <&scmi_clk CK_SCMI_LSE>, + <&scmi_clk CK_SCMI_LSI>, + <&scmi_clk CK_SCMI_HSE_DIV2>, + <&scmi_clk CK_SCMI_ICN_HS_MCU>, + <&scmi_clk CK_SCMI_ICN_LS_MCU>, + <&scmi_clk CK_SCMI_ICN_SDMMC>, + <&scmi_clk CK_SCMI_ICN_DDR>, + <&scmi_clk CK_SCMI_ICN_DISPLAY>, + <&scmi_clk CK_SCMI_ICN_HSL>, + <&scmi_clk CK_SCMI_ICN_NIC>, + <&scmi_clk CK_SCMI_FLEXGEN_07>, + <&scmi_clk CK_SCMI_FLEXGEN_08>, + <&scmi_clk CK_SCMI_FLEXGEN_09>, + <&scmi_clk CK_SCMI_FLEXGEN_10>, + <&scmi_clk CK_SCMI_FLEXGEN_11>, + <&scmi_clk CK_SCMI_FLEXGEN_12>, + <&scmi_clk CK_SCMI_FLEXGEN_13>, + <&scmi_clk CK_SCMI_FLEXGEN_14>, + <&scmi_clk CK_SCMI_FLEXGEN_16>, + <&scmi_clk CK_SCMI_FLEXGEN_17>, + <&scmi_clk CK_SCMI_FLEXGEN_18>, + <&scmi_clk CK_SCMI_FLEXGEN_19>, + <&scmi_clk CK_SCMI_FLEXGEN_20>, + <&scmi_clk CK_SCMI_FLEXGEN_21>, + <&scmi_clk CK_SCMI_FLEXGEN_22>, + <&scmi_clk CK_SCMI_FLEXGEN_23>, + <&scmi_clk CK_SCMI_FLEXGEN_24>, + <&scmi_clk CK_SCMI_FLEXGEN_25>, + <&scmi_clk CK_SCMI_FLEXGEN_26>, + <&scmi_clk CK_SCMI_FLEXGEN_27>, + <&scmi_clk CK_SCMI_FLEXGEN_29>, + <&scmi_clk CK_SCMI_FLEXGEN_30>, + <&scmi_clk CK_SCMI_FLEXGEN_31>, + <&scmi_clk CK_SCMI_FLEXGEN_33>, + <&scmi_clk CK_SCMI_FLEXGEN_36>, + <&scmi_clk CK_SCMI_FLEXGEN_37>, + <&scmi_clk CK_SCMI_FLEXGEN_38>, + <&scmi_clk CK_SCMI_FLEXGEN_39>, + <&scmi_clk CK_SCMI_FLEXGEN_40>, + <&scmi_clk CK_SCMI_FLEXGEN_41>, + <&scmi_clk CK_SCMI_FLEXGEN_42>, + <&scmi_clk CK_SCMI_FLEXGEN_43>, + <&scmi_clk CK_SCMI_FLEXGEN_44>, + <&scmi_clk CK_SCMI_FLEXGEN_45>, + <&scmi_clk CK_SCMI_FLEXGEN_46>, + <&scmi_clk CK_SCMI_FLEXGEN_47>, + <&scmi_clk CK_SCMI_FLEXGEN_48>, + <&scmi_clk CK_SCMI_FLEXGEN_50>, + <&scmi_clk CK_SCMI_FLEXGEN_51>, + <&scmi_clk CK_SCMI_FLEXGEN_52>, + <&scmi_clk CK_SCMI_FLEXGEN_53>, + <&scmi_clk CK_SCMI_FLEXGEN_54>, + <&scmi_clk CK_SCMI_FLEXGEN_55>, + <&scmi_clk CK_SCMI_FLEXGEN_56>, + <&scmi_clk CK_SCMI_FLEXGEN_57>, + <&scmi_clk CK_SCMI_FLEXGEN_58>, + <&scmi_clk CK_SCMI_FLEXGEN_61>, + <&scmi_clk CK_SCMI_FLEXGEN_62>, + <&scmi_clk CK_SCMI_FLEXGEN_63>, + <&scmi_clk CK_SCMI_ICN_APB1>, + <&scmi_clk CK_SCMI_ICN_APB2>, + <&scmi_clk CK_SCMI_ICN_APB3>, + <&scmi_clk CK_SCMI_ICN_APB4>, + <&scmi_clk CK_SCMI_ICN_APB5>, + <&scmi_clk CK_SCMI_ICN_APBDBG>, + <&scmi_clk CK_SCMI_TIMG1>, + <&scmi_clk CK_SCMI_TIMG2>; }; ... diff --git a/Documentation/devicetree/bindings/clock/st,stm32mp25-rcc.yaml b/Documentation/devicetree/bindings/clock/st,stm32mp25-rcc.yaml index 1e3b5d218bb0..3b7925d2c175 100644 --- a/Documentation/devicetree/bindings/clock/st,stm32mp25-rcc.yaml +++ b/Documentation/devicetree/bindings/clock/st,stm32mp25-rcc.yaml @@ -135,85 +135,85 @@ examples: reg = <0x44200000 0x10000>; #clock-cells = <1>; #reset-cells = <1>; - clocks = <&scmi_clk CK_SCMI_HSE>, - <&scmi_clk CK_SCMI_HSI>, - <&scmi_clk CK_SCMI_MSI>, - <&scmi_clk CK_SCMI_LSE>, - <&scmi_clk CK_SCMI_LSI>, - <&scmi_clk CK_SCMI_HSE_DIV2>, - <&scmi_clk CK_SCMI_ICN_HS_MCU>, - <&scmi_clk CK_SCMI_ICN_LS_MCU>, - <&scmi_clk CK_SCMI_ICN_SDMMC>, - <&scmi_clk CK_SCMI_ICN_DDR>, - <&scmi_clk CK_SCMI_ICN_DISPLAY>, - <&scmi_clk CK_SCMI_ICN_HSL>, - <&scmi_clk CK_SCMI_ICN_NIC>, - <&scmi_clk CK_SCMI_ICN_VID>, - <&scmi_clk CK_SCMI_FLEXGEN_07>, - <&scmi_clk CK_SCMI_FLEXGEN_08>, - <&scmi_clk CK_SCMI_FLEXGEN_09>, - <&scmi_clk CK_SCMI_FLEXGEN_10>, - <&scmi_clk CK_SCMI_FLEXGEN_11>, - <&scmi_clk CK_SCMI_FLEXGEN_12>, - <&scmi_clk CK_SCMI_FLEXGEN_13>, - <&scmi_clk CK_SCMI_FLEXGEN_14>, - <&scmi_clk CK_SCMI_FLEXGEN_15>, - <&scmi_clk CK_SCMI_FLEXGEN_16>, - <&scmi_clk CK_SCMI_FLEXGEN_17>, - <&scmi_clk CK_SCMI_FLEXGEN_18>, - <&scmi_clk CK_SCMI_FLEXGEN_19>, - <&scmi_clk CK_SCMI_FLEXGEN_20>, - <&scmi_clk CK_SCMI_FLEXGEN_21>, - <&scmi_clk CK_SCMI_FLEXGEN_22>, - <&scmi_clk CK_SCMI_FLEXGEN_23>, - <&scmi_clk CK_SCMI_FLEXGEN_24>, - <&scmi_clk CK_SCMI_FLEXGEN_25>, - <&scmi_clk CK_SCMI_FLEXGEN_26>, - <&scmi_clk CK_SCMI_FLEXGEN_27>, - <&scmi_clk CK_SCMI_FLEXGEN_28>, - <&scmi_clk CK_SCMI_FLEXGEN_29>, - <&scmi_clk CK_SCMI_FLEXGEN_30>, - <&scmi_clk CK_SCMI_FLEXGEN_31>, - <&scmi_clk CK_SCMI_FLEXGEN_32>, - <&scmi_clk CK_SCMI_FLEXGEN_33>, - <&scmi_clk CK_SCMI_FLEXGEN_34>, - <&scmi_clk CK_SCMI_FLEXGEN_35>, - <&scmi_clk CK_SCMI_FLEXGEN_36>, - <&scmi_clk CK_SCMI_FLEXGEN_37>, - <&scmi_clk CK_SCMI_FLEXGEN_38>, - <&scmi_clk CK_SCMI_FLEXGEN_39>, - <&scmi_clk CK_SCMI_FLEXGEN_40>, - <&scmi_clk CK_SCMI_FLEXGEN_41>, - <&scmi_clk CK_SCMI_FLEXGEN_42>, - <&scmi_clk CK_SCMI_FLEXGEN_43>, - <&scmi_clk CK_SCMI_FLEXGEN_44>, - <&scmi_clk CK_SCMI_FLEXGEN_45>, - <&scmi_clk CK_SCMI_FLEXGEN_46>, - <&scmi_clk CK_SCMI_FLEXGEN_47>, - <&scmi_clk CK_SCMI_FLEXGEN_48>, - <&scmi_clk CK_SCMI_FLEXGEN_49>, - <&scmi_clk CK_SCMI_FLEXGEN_50>, - <&scmi_clk CK_SCMI_FLEXGEN_51>, - <&scmi_clk CK_SCMI_FLEXGEN_52>, - <&scmi_clk CK_SCMI_FLEXGEN_53>, - <&scmi_clk CK_SCMI_FLEXGEN_54>, - <&scmi_clk CK_SCMI_FLEXGEN_55>, - <&scmi_clk CK_SCMI_FLEXGEN_56>, - <&scmi_clk CK_SCMI_FLEXGEN_57>, - <&scmi_clk CK_SCMI_FLEXGEN_58>, - <&scmi_clk CK_SCMI_FLEXGEN_59>, - <&scmi_clk CK_SCMI_FLEXGEN_60>, - <&scmi_clk CK_SCMI_FLEXGEN_61>, - <&scmi_clk CK_SCMI_FLEXGEN_62>, - <&scmi_clk CK_SCMI_FLEXGEN_63>, - <&scmi_clk CK_SCMI_ICN_APB1>, - <&scmi_clk CK_SCMI_ICN_APB2>, - <&scmi_clk CK_SCMI_ICN_APB3>, - <&scmi_clk CK_SCMI_ICN_APB4>, - <&scmi_clk CK_SCMI_ICN_APBDBG>, - <&scmi_clk CK_SCMI_TIMG1>, - <&scmi_clk CK_SCMI_TIMG2>, - <&scmi_clk CK_SCMI_PLL3>, - <&clk_dsi_txbyte>; + clocks = <&scmi_clk CK_SCMI_HSE>, + <&scmi_clk CK_SCMI_HSI>, + <&scmi_clk CK_SCMI_MSI>, + <&scmi_clk CK_SCMI_LSE>, + <&scmi_clk CK_SCMI_LSI>, + <&scmi_clk CK_SCMI_HSE_DIV2>, + <&scmi_clk CK_SCMI_ICN_HS_MCU>, + <&scmi_clk CK_SCMI_ICN_LS_MCU>, + <&scmi_clk CK_SCMI_ICN_SDMMC>, + <&scmi_clk CK_SCMI_ICN_DDR>, + <&scmi_clk CK_SCMI_ICN_DISPLAY>, + <&scmi_clk CK_SCMI_ICN_HSL>, + <&scmi_clk CK_SCMI_ICN_NIC>, + <&scmi_clk CK_SCMI_ICN_VID>, + <&scmi_clk CK_SCMI_FLEXGEN_07>, + <&scmi_clk CK_SCMI_FLEXGEN_08>, + <&scmi_clk CK_SCMI_FLEXGEN_09>, + <&scmi_clk CK_SCMI_FLEXGEN_10>, + <&scmi_clk CK_SCMI_FLEXGEN_11>, + <&scmi_clk CK_SCMI_FLEXGEN_12>, + <&scmi_clk CK_SCMI_FLEXGEN_13>, + <&scmi_clk CK_SCMI_FLEXGEN_14>, + <&scmi_clk CK_SCMI_FLEXGEN_15>, + <&scmi_clk CK_SCMI_FLEXGEN_16>, + <&scmi_clk CK_SCMI_FLEXGEN_17>, + <&scmi_clk CK_SCMI_FLEXGEN_18>, + <&scmi_clk CK_SCMI_FLEXGEN_19>, + <&scmi_clk CK_SCMI_FLEXGEN_20>, + <&scmi_clk CK_SCMI_FLEXGEN_21>, + <&scmi_clk CK_SCMI_FLEXGEN_22>, + <&scmi_clk CK_SCMI_FLEXGEN_23>, + <&scmi_clk CK_SCMI_FLEXGEN_24>, + <&scmi_clk CK_SCMI_FLEXGEN_25>, + <&scmi_clk CK_SCMI_FLEXGEN_26>, + <&scmi_clk CK_SCMI_FLEXGEN_27>, + <&scmi_clk CK_SCMI_FLEXGEN_28>, + <&scmi_clk CK_SCMI_FLEXGEN_29>, + <&scmi_clk CK_SCMI_FLEXGEN_30>, + <&scmi_clk CK_SCMI_FLEXGEN_31>, + <&scmi_clk CK_SCMI_FLEXGEN_32>, + <&scmi_clk CK_SCMI_FLEXGEN_33>, + <&scmi_clk CK_SCMI_FLEXGEN_34>, + <&scmi_clk CK_SCMI_FLEXGEN_35>, + <&scmi_clk CK_SCMI_FLEXGEN_36>, + <&scmi_clk CK_SCMI_FLEXGEN_37>, + <&scmi_clk CK_SCMI_FLEXGEN_38>, + <&scmi_clk CK_SCMI_FLEXGEN_39>, + <&scmi_clk CK_SCMI_FLEXGEN_40>, + <&scmi_clk CK_SCMI_FLEXGEN_41>, + <&scmi_clk CK_SCMI_FLEXGEN_42>, + <&scmi_clk CK_SCMI_FLEXGEN_43>, + <&scmi_clk CK_SCMI_FLEXGEN_44>, + <&scmi_clk CK_SCMI_FLEXGEN_45>, + <&scmi_clk CK_SCMI_FLEXGEN_46>, + <&scmi_clk CK_SCMI_FLEXGEN_47>, + <&scmi_clk CK_SCMI_FLEXGEN_48>, + <&scmi_clk CK_SCMI_FLEXGEN_49>, + <&scmi_clk CK_SCMI_FLEXGEN_50>, + <&scmi_clk CK_SCMI_FLEXGEN_51>, + <&scmi_clk CK_SCMI_FLEXGEN_52>, + <&scmi_clk CK_SCMI_FLEXGEN_53>, + <&scmi_clk CK_SCMI_FLEXGEN_54>, + <&scmi_clk CK_SCMI_FLEXGEN_55>, + <&scmi_clk CK_SCMI_FLEXGEN_56>, + <&scmi_clk CK_SCMI_FLEXGEN_57>, + <&scmi_clk CK_SCMI_FLEXGEN_58>, + <&scmi_clk CK_SCMI_FLEXGEN_59>, + <&scmi_clk CK_SCMI_FLEXGEN_60>, + <&scmi_clk CK_SCMI_FLEXGEN_61>, + <&scmi_clk CK_SCMI_FLEXGEN_62>, + <&scmi_clk CK_SCMI_FLEXGEN_63>, + <&scmi_clk CK_SCMI_ICN_APB1>, + <&scmi_clk CK_SCMI_ICN_APB2>, + <&scmi_clk CK_SCMI_ICN_APB3>, + <&scmi_clk CK_SCMI_ICN_APB4>, + <&scmi_clk CK_SCMI_ICN_APBDBG>, + <&scmi_clk CK_SCMI_TIMG1>, + <&scmi_clk CK_SCMI_TIMG2>, + <&scmi_clk CK_SCMI_PLL3>, + <&clk_dsi_txbyte>; }; ... diff --git a/Documentation/devicetree/bindings/clock/xlnx,clocking-wizard.yaml b/Documentation/devicetree/bindings/clock/xlnx,clocking-wizard.yaml index b497c28e8094..3f311f90294e 100644 --- a/Documentation/devicetree/bindings/clock/xlnx,clocking-wizard.yaml +++ b/Documentation/devicetree/bindings/clock/xlnx,clocking-wizard.yaml @@ -70,7 +70,7 @@ additionalProperties: false examples: - | - clock-controller@b0000000 { + clock-controller@b0000000 { compatible = "xlnx,clocking-wizard"; reg = <0xb0000000 0x10000>; #clock-cells = <1>; From 3a6deb47cdd47e50b25779ad8321a7aaf20e9d23 Mon Sep 17 00:00:00 2001 From: Bhargav Joshi Date: Tue, 11 Aug 2026 00:24:59 +0530 Subject: [PATCH 0616/1328] dt-bindings: clock: ti,clockdomain: Convert to DT schema Convert TI clockdomain to yaml DT schema. Drop '#clock-cells' from the required list as this binding doesn't define a new clock binding type, it is used to group existing clock nodes under hardware hierarchy. Most existing dts omit '#clock-cells'. Update the reference to the old legacy text binding in the description of bindings/clock/ti/ti,gate-clock.yaml to point to the new YAML file. Reviewed-by: Rob Herring (Arm) Signed-off-by: Bhargav Joshi Signed-off-by: Stephen Boyd --- .../bindings/clock/ti/clockdomain.txt | 25 ----------- .../bindings/clock/ti/ti,clockdomain.yaml | 44 +++++++++++++++++++ .../bindings/clock/ti/ti,gate-clock.yaml | 2 +- 3 files changed, 45 insertions(+), 26 deletions(-) delete mode 100644 Documentation/devicetree/bindings/clock/ti/clockdomain.txt create mode 100644 Documentation/devicetree/bindings/clock/ti/ti,clockdomain.yaml diff --git a/Documentation/devicetree/bindings/clock/ti/clockdomain.txt b/Documentation/devicetree/bindings/clock/ti/clockdomain.txt deleted file mode 100644 index edf0b5d42768..000000000000 --- a/Documentation/devicetree/bindings/clock/ti/clockdomain.txt +++ /dev/null @@ -1,25 +0,0 @@ -Binding for Texas Instruments clockdomain. - -This binding uses the common clock binding[1] in consumer role. -Every clock on TI SoC belongs to one clockdomain, but software -only needs this information for specific clocks which require -their parent clockdomain to be controlled when the clock is -enabled/disabled. This binding doesn't define a new clock -binding type, it is used to group existing clock nodes under -hardware hierarchy. - -[1] Documentation/devicetree/bindings/clock/clock-bindings.txt - -Required properties: -- compatible : shall be "ti,clockdomain" -- #clock-cells : from common clock binding; shall be set to 0. -- clocks : link phandles of clocks within this domain - -Optional properties: -- clock-output-names : from common clock binding. - -Examples: - dss_clkdm: dss_clkdm { - compatible = "ti,clockdomain"; - clocks = <&dss1_alwon_fck_3430es2>, <&dss_ick_3430es2>; - }; diff --git a/Documentation/devicetree/bindings/clock/ti/ti,clockdomain.yaml b/Documentation/devicetree/bindings/clock/ti/ti,clockdomain.yaml new file mode 100644 index 000000000000..95746bfdced4 --- /dev/null +++ b/Documentation/devicetree/bindings/clock/ti/ti,clockdomain.yaml @@ -0,0 +1,44 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/clock/ti/ti,clockdomain.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments clockdomain + +maintainers: + - Tero Kristo + +description: + This binding uses the common clock binding in consumer role. Every clock on TI + SoC belongs to one clockdomain, but software only needs this information for + specific clocks which require their parent clockdomain to be controlled when + the clock is enabled/disabled. This binding doesn't define a new clock binding + type, it is used to group existing clock nodes under hardware hierarchy. + +properties: + compatible: + const: ti,clockdomain + + "#clock-cells": + const: 0 + + clocks: + minItems: 1 + maxItems: 64 + + clock-output-names: + maxItems: 1 + +required: + - compatible + - clocks + +additionalProperties: false + +examples: + - | + dss_clkdm { + compatible = "ti,clockdomain"; + clocks = <&dss1_alwon_fck_3430es2>, <&dss_ick_3430es2>; + }; diff --git a/Documentation/devicetree/bindings/clock/ti/ti,gate-clock.yaml b/Documentation/devicetree/bindings/clock/ti/ti,gate-clock.yaml index eaa727ab0d7f..438e190d1067 100644 --- a/Documentation/devicetree/bindings/clock/ti/ti,gate-clock.yaml +++ b/Documentation/devicetree/bindings/clock/ti/ti,gate-clock.yaml @@ -19,7 +19,7 @@ description: | that is used. [1] Documentation/devicetree/bindings/clock/gpio-gate-clock.yaml - [2] Documentation/devicetree/bindings/clock/ti/clockdomain.txt + [2] Documentation/devicetree/bindings/clock/ti/ti,clockdomain.yaml properties: compatible: From 3cf908dd13968e099163ed0db236443b7bdec835 Mon Sep 17 00:00:00 2001 From: Babanpreet Singh Date: Mon, 3 Aug 2026 06:31:26 +0000 Subject: [PATCH 0617/1328] clk: zynq: pll: Fix kernel-doc after determine_rate() conversion Commit 1547747b55d5 ("clk: zynq: pll: convert from round_rate() to determine_rate()") replaced zynq_pll_round_rate() with zynq_pll_determine_rate() but left its kernel-doc block behind, so the block still names the old function and documents the removed @rate and @prate parameters instead of @req. Retitle the block and describe @req. The Return: line described a frequency and referenced the removed @rate; the function returns 0 unconditionally and the rounded rate comes back in @req. No functional changes. Assisted-by: Claude:claude-opus-5 [kernel-doc] Signed-off-by: Babanpreet Singh Reviewed-by: Michal Simek Reviewed-by: Brian Masney Signed-off-by: Stephen Boyd --- drivers/clk/zynq/pll.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/clk/zynq/pll.c b/drivers/clk/zynq/pll.c index 44c609378364..fe90b50e1545 100644 --- a/drivers/clk/zynq/pll.c +++ b/drivers/clk/zynq/pll.c @@ -42,11 +42,11 @@ struct zynq_pll { #define PLL_FBDIV_MAX 66 /** - * zynq_pll_round_rate() - Round a clock frequency + * zynq_pll_determine_rate() - Round a clock frequency * @hw: Handle between common and hardware-specific interfaces - * @rate: Desired clock frequency - * @prate: Clock frequency of parent clock - * Return: frequency closest to @rate the hardware can generate. + * @req: Clock rate request, updated with the frequency closest to the + * requested one that the hardware can generate + * Return: 0 always */ static int zynq_pll_determine_rate(struct clk_hw *hw, struct clk_rate_request *req) From 16f212563f86646a07ca97b334e1c7d97d3ee6fd Mon Sep 17 00:00:00 2001 From: Eduard Bostina Date: Thu, 30 Jul 2026 11:30:19 +0300 Subject: [PATCH 0618/1328] dt-bindings: clock: ti: Convert APLL clock to DT schema Convert the Texas Instruments APLL clock bindings to DT schema. Signed-off-by: Eduard Bostina Signed-off-by: Stephen Boyd --- .../devicetree/bindings/clock/ti/apll.txt | 43 --------- .../bindings/clock/ti/ti,apll-clock.yaml | 89 +++++++++++++++++++ 2 files changed, 89 insertions(+), 43 deletions(-) delete mode 100644 Documentation/devicetree/bindings/clock/ti/apll.txt create mode 100644 Documentation/devicetree/bindings/clock/ti/ti,apll-clock.yaml diff --git a/Documentation/devicetree/bindings/clock/ti/apll.txt b/Documentation/devicetree/bindings/clock/ti/apll.txt deleted file mode 100644 index bbd505c1199d..000000000000 --- a/Documentation/devicetree/bindings/clock/ti/apll.txt +++ /dev/null @@ -1,43 +0,0 @@ -Binding for Texas Instruments APLL clock. - -This binding uses the common clock binding[1]. It assumes a -register-mapped APLL with usually two selectable input clocks -(reference clock and bypass clock), with analog phase locked -loop logic for multiplying the input clock to a desired output -clock. This clock also typically supports different operation -modes (locked, low power stop etc.) APLL mostly behaves like -a subtype of a DPLL [2], although a simplified one at that. - -[1] Documentation/devicetree/bindings/clock/clock-bindings.txt -[2] Documentation/devicetree/bindings/clock/ti/dpll.txt - -Required properties: -- compatible : shall be "ti,dra7-apll-clock" or "ti,omap2-apll-clock" -- #clock-cells : from common clock binding; shall be set to 0. -- clocks : link phandles of parent clocks (clk-ref and clk-bypass) -- reg : address and length of the register set for controlling the APLL. - It contains the information of registers in the following order: - "control" - contains the control register offset - "idlest" - contains the idlest register offset - "autoidle" - contains the autoidle register offset (OMAP2 only) -- ti,clock-frequency : static clock frequency for the clock (OMAP2 only) -- ti,idlest-shift : bit-shift for the idlest field (OMAP2 only) -- ti,bit-shift : bit-shift for enable and autoidle fields (OMAP2 only) - -Examples: - apll_pcie_ck: apll_pcie_ck { - #clock-cells = <0>; - clocks = <&apll_pcie_in_clk_mux>, <&dpll_pcie_ref_ck>; - reg = <0x021c>, <0x0220>; - compatible = "ti,dra7-apll-clock"; - }; - - apll96_ck: apll96_ck { - #clock-cells = <0>; - compatible = "ti,omap2-apll-clock"; - clocks = <&sys_ck>; - ti,bit-shift = <2>; - ti,idlest-shift = <8>; - ti,clock-frequency = <96000000>; - reg = <0x0500>, <0x0530>, <0x0520>; - }; diff --git a/Documentation/devicetree/bindings/clock/ti/ti,apll-clock.yaml b/Documentation/devicetree/bindings/clock/ti/ti,apll-clock.yaml new file mode 100644 index 000000000000..e14a76254ce7 --- /dev/null +++ b/Documentation/devicetree/bindings/clock/ti/ti,apll-clock.yaml @@ -0,0 +1,89 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/clock/ti/ti,apll-clock.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments APLL clock + +maintainers: + - Eduard Bostina + +allOf: + - $ref: /schemas/clock/clock.yaml# + +description: + This binding assumes a register-mapped APLL with usually two selectable + input clocks (reference clock and bypass clock), with analog phase locked + loop logic for multiplying the input clock to a desired output clock. + This clock also typically supports different operation modes (locked, low + power stop etc.) APLL mostly behaves like a subtype of a DPLL, although + a simplified one at that. + +properties: + compatible: + enum: + - ti,dra7-apll-clock + - ti,omap2-apll-clock + + "#clock-cells": + const: 0 + + clocks: + minItems: 1 + maxItems: 2 + description: Link phandles of parent clocks (clk-ref and clk-bypass) + + reg: + minItems: 2 + maxItems: 3 + description: | + Address and length of the register set for controlling the APLL. + It contains the information of registers in the following order: + "control" - contains the control register offset + "idlest" - contains the idlest register offset + "autoidle" - contains the autoidle register offset (OMAP2 only) + + ti,clock-frequency: + $ref: /schemas/types.yaml#/definitions/uint32 + description: static clock frequency for the clock (OMAP2 only) + + ti,idlest-shift: + $ref: /schemas/types.yaml#/definitions/uint32 + description: bit-shift for the idlest field (OMAP2 only) + + ti,bit-shift: + $ref: /schemas/types.yaml#/definitions/uint32 + description: bit-shift for enable and autoidle fields (OMAP2 only) + +required: + - compatible + - "#clock-cells" + - clocks + - reg + +unevaluatedProperties: false + +examples: + - | + bus { + #address-cells = <1>; + #size-cells = <0>; + + clock@21c { + #clock-cells = <0>; + compatible = "ti,dra7-apll-clock"; + clocks = <&apll_pcie_in_clk_mux>, <&dpll_pcie_ref_ck>; + reg = <0x021c>, <0x0220>; + }; + + clock@500 { + #clock-cells = <0>; + compatible = "ti,omap2-apll-clock"; + clocks = <&sys_ck>; + ti,bit-shift = <2>; + ti,idlest-shift = <8>; + ti,clock-frequency = <96000000>; + reg = <0x0500>, <0x0530>, <0x0520>; + }; + }; From 368500fcb3679af69b244e394a35be47a799a725 Mon Sep 17 00:00:00 2001 From: Bhargav Joshi Date: Sun, 26 Jul 2026 01:34:08 +0530 Subject: [PATCH 0619/1328] dt-bindings: clock: ti,keystone-gate: Convert to DT schema Convert Texas Instruments keystone PSC clock binding to DT schema. The vendor-specific 'domain-id' property is intentionally kept without a "ti," prefix to preserve existing ABI since driver and existing board file use property without "ti," prefix. Also update the reference to this binding in ti,keystone-rproc.txt to point at the new filename. Signed-off-by: Bhargav Joshi Reviewed-by: Rob Herring (Arm) Signed-off-by: Stephen Boyd --- .../bindings/clock/keystone-gate.txt | 27 --------- .../bindings/clock/ti,keystone,psc-clock.yaml | 60 +++++++++++++++++++ .../bindings/remoteproc/ti,keystone-rproc.txt | 2 +- 3 files changed, 61 insertions(+), 28 deletions(-) delete mode 100644 Documentation/devicetree/bindings/clock/keystone-gate.txt create mode 100644 Documentation/devicetree/bindings/clock/ti,keystone,psc-clock.yaml diff --git a/Documentation/devicetree/bindings/clock/keystone-gate.txt b/Documentation/devicetree/bindings/clock/keystone-gate.txt deleted file mode 100644 index 43f6fb6c9392..000000000000 --- a/Documentation/devicetree/bindings/clock/keystone-gate.txt +++ /dev/null @@ -1,27 +0,0 @@ -Binding for Keystone gate control driver which uses PSC controller IP. - -This binding uses the common clock binding[1]. - -[1] Documentation/devicetree/bindings/clock/clock-bindings.txt - -Required properties: -- compatible : shall be "ti,keystone,psc-clock". -- #clock-cells : from common clock binding; shall be set to 0. -- clocks : parent clock phandle -- reg : psc control and domain address address space -- reg-names : psc control and domain registers -- domain-id : psc domain id needed to check the transition state register - -Optional properties: -- clock-output-names : From common clock binding to override the - default output clock name -Example: - clkusb: clkusb { - #clock-cells = <0>; - compatible = "ti,keystone,psc-clock"; - clocks = <&chipclk16>; - clock-output-names = "usb"; - reg = <0x02350008 0xb00>, <0x02350000 0x400>; - reg-names = "control", "domain"; - domain-id = <0>; - }; diff --git a/Documentation/devicetree/bindings/clock/ti,keystone,psc-clock.yaml b/Documentation/devicetree/bindings/clock/ti,keystone,psc-clock.yaml new file mode 100644 index 000000000000..ed42adb27832 --- /dev/null +++ b/Documentation/devicetree/bindings/clock/ti,keystone,psc-clock.yaml @@ -0,0 +1,60 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/clock/ti,keystone,psc-clock.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: TI Keystone PSC clock + +maintainers: + - Santosh Shilimkar + - Murali Karicheri + +properties: + compatible: + const: ti,keystone,psc-clock + + reg: + items: + - description: PSC control + - description: Domain + + reg-names: + items: + - const: control + - const: domain + + domain-id: + $ref: /schemas/types.yaml#/definitions/uint32 + description: PSC domain id needed to check the transition state register + + clocks: + maxItems: 1 + + clock-output-names: + maxItems: 1 + + "#clock-cells": + const: 0 + +required: + - compatible + - reg + - reg-names + - domain-id + - clocks + - "#clock-cells" + +additionalProperties: false + +examples: + - | + clkusb@2350008 { + compatible = "ti,keystone,psc-clock"; + reg = <0x02350008 0xb00>, <0x02350000 0x400>; + reg-names = "control", "domain"; + domain-id = <0>; + clocks = <&chipclk16>; + clock-output-names = "usb"; + #clock-cells = <0>; + }; diff --git a/Documentation/devicetree/bindings/remoteproc/ti,keystone-rproc.txt b/Documentation/devicetree/bindings/remoteproc/ti,keystone-rproc.txt index 91f0a3b0c0b2..847552133633 100644 --- a/Documentation/devicetree/bindings/remoteproc/ti,keystone-rproc.txt +++ b/Documentation/devicetree/bindings/remoteproc/ti,keystone-rproc.txt @@ -75,7 +75,7 @@ SoCs only: - clocks: Should contain the device's input clock, and should be defined as per the bindings in, - Documentation/devicetree/bindings/clock/keystone-gate.txt + Documentation/devicetree/bindings/clock/ti,keystone,psc-clock.yaml The following are mandatory properties for Keystone 2 66AK2G SoCs only: From 0d4d262c1664365e17e0a5ba2ab79f4db484b44e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Onur=20=C3=96zkan?= Date: Sat, 1 Aug 2026 14:16:34 +0300 Subject: [PATCH 0620/1328] clk: devres: fix cleanup in devm_clk_get_optional_enabled_with_rate() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit devm_clk_get_optional_enabled_with_rate() registers its cleanup action before setting the clock rate. If setting the rate fails, it attempts to disable and unprepare a clock that was never enabled. This issue was spotted while reviewing "rust: clk: add devres-managed clks" [1]. Register the cleanup action only after successfully preparing and enabling the clock. [1]: https://lore.kernel.org/rust-for-linux/20260706-clk-type-state-v5-3-67c5f326a16c@collabora.com Fixes: 9934a1bd45b2 ("clk: provide devm_clk_get_optional_enabled_with_rate()") Signed-off-by: Onur Özkan Reviewed-by: Brian Masney Signed-off-by: Stephen Boyd --- drivers/clk/clk-devres.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/clk/clk-devres.c b/drivers/clk/clk-devres.c index 49d4782991eb..4cc3863bd7da 100644 --- a/drivers/clk/clk-devres.c +++ b/drivers/clk/clk-devres.c @@ -99,6 +99,11 @@ struct clk *devm_clk_get_optional_enabled(struct device *dev, const char *id) } EXPORT_SYMBOL_GPL(devm_clk_get_optional_enabled); +static void devm_clk_disable_unprepare(void *data) +{ + clk_disable_unprepare(data); +} + struct clk *devm_clk_get_optional_enabled_with_rate(struct device *dev, const char *id, unsigned long rate) @@ -106,8 +111,7 @@ struct clk *devm_clk_get_optional_enabled_with_rate(struct device *dev, struct clk *clk; int ret; - clk = __devm_clk_get(dev, id, clk_get_optional, NULL, - clk_disable_unprepare); + clk = devm_clk_get_optional(dev, id); if (IS_ERR(clk)) return ERR_CAST(clk); @@ -119,6 +123,10 @@ struct clk *devm_clk_get_optional_enabled_with_rate(struct device *dev, if (ret) goto out_put_clk; + ret = devm_add_action_or_reset(dev, devm_clk_disable_unprepare, clk); + if (ret) + goto out_put_clk; + return clk; out_put_clk: From 667f420c09f1417c9f07798e32432c901915209d Mon Sep 17 00:00:00 2001 From: Mathieu Dubois-Briand Date: Mon, 27 Jul 2026 09:41:40 +0200 Subject: [PATCH 0621/1328] clk: ti: mux: resolve parent clocks by DT index, not by name Resolve parent clocks by their index into the device tree "clocks" property rather than matching names as strings. Name-based matching is fragile because a clock's "clock-output-names" value in its provider node can differ from the name used to reference it in a consumer node, and because names must be globally unique across all clock providers. On AM335x, this caused broken clock trees where some clocks failed to enable because their parents could not be found. Replace of_clk_parent_fill() with a clk_parent_data array that sets .index to the array position. Fixes: ec7aa25fa483 ("ARM: dts: Use clock-output-names for am3") Signed-off-by: Mathieu Dubois-Briand Reviewed-by: Brian Masney Signed-off-by: Stephen Boyd --- drivers/clk/ti/mux.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/drivers/clk/ti/mux.c b/drivers/clk/ti/mux.c index d6a0ccfd81db..ded4432f7528 100644 --- a/drivers/clk/ti/mux.c +++ b/drivers/clk/ti/mux.c @@ -119,7 +119,7 @@ const struct clk_ops ti_clk_mux_ops = { }; static struct clk *_register_mux(struct device_node *node, const char *name, - const char * const *parent_names, + const struct clk_parent_data *parent_data, u8 num_parents, unsigned long flags, struct clk_omap_reg *reg, u8 shift, u32 mask, s8 latch, u8 clk_mux_flags, u32 *table) @@ -136,7 +136,7 @@ static struct clk *_register_mux(struct device_node *node, const char *name, init.name = name; init.ops = &ti_clk_mux_ops; init.flags = flags; - init.parent_names = parent_names; + init.parent_data = parent_data; init.num_parents = num_parents; /* struct clk_mux assignments */ @@ -167,24 +167,26 @@ static void of_mux_clk_setup(struct device_node *node) struct clk *clk; struct clk_omap_reg reg; unsigned int num_parents; - const char **parent_names; + struct clk_parent_data *parent_data; const char *name; u8 clk_mux_flags = 0; u32 mask = 0; u32 shift = 0; s32 latch = -EINVAL; u32 flags = CLK_SET_RATE_NO_REPARENT; + int i; num_parents = of_clk_get_parent_count(node); if (num_parents < 2) { pr_err("mux-clock %pOFn must have parents\n", node); return; } - parent_names = kcalloc(num_parents, sizeof(char *), GFP_KERNEL); - if (!parent_names) - goto cleanup; + parent_data = kcalloc(num_parents, sizeof(*parent_data), GFP_KERNEL); + if (!parent_data) + return; - of_clk_parent_fill(node, parent_names, num_parents); + for (i = 0; i < num_parents; i++) + parent_data[i].index = i; if (ti_clk_get_reg_addr(node, 0, ®)) goto cleanup; @@ -207,7 +209,7 @@ static void of_mux_clk_setup(struct device_node *node) mask = (1 << fls(mask)) - 1; name = ti_dt_clk_name(node); - clk = _register_mux(node, name, parent_names, num_parents, + clk = _register_mux(node, name, parent_data, num_parents, flags, ®, shift, mask, latch, clk_mux_flags, NULL); @@ -215,7 +217,7 @@ static void of_mux_clk_setup(struct device_node *node) of_clk_add_provider(node, of_clk_src_simple_get, clk); cleanup: - kfree(parent_names); + kfree(parent_data); } CLK_OF_DECLARE(mux_clk, "ti,mux-clock", of_mux_clk_setup); From fe3dd92ac54a043ab79ff06623530b0c9611593c Mon Sep 17 00:00:00 2001 From: Mathieu Dubois-Briand Date: Mon, 27 Jul 2026 09:41:41 +0200 Subject: [PATCH 0622/1328] clk: ti: composite: resolve parent clocks by DT index, not by name Resolve parent clocks by their index into the device tree "clocks" property rather than matching names as strings. This makes it consistent with other parts of the same driver. Signed-off-by: Mathieu Dubois-Briand Reviewed-by: Brian Masney Signed-off-by: Stephen Boyd --- drivers/clk/ti/composite.c | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/drivers/clk/ti/composite.c b/drivers/clk/ti/composite.c index c379bbdae25a..01eae8995254 100644 --- a/drivers/clk/ti/composite.c +++ b/drivers/clk/ti/composite.c @@ -52,7 +52,7 @@ static const struct clk_ops ti_composite_gate_ops = { struct component_clk { int num_parents; - const char **parent_names; + struct clk_parent_data *parent_data; struct device_node *node; int type; struct clk_hw *hw; @@ -116,7 +116,7 @@ static void __init _register_composite(void *user, struct clk_hw_omap_comp *cclk = to_clk_hw_comp(hw); struct component_clk *comp; int num_parents = 0; - const char **parent_names = NULL; + struct clk_parent_data *parent_data = NULL; const char *name; int i; int ret; @@ -155,7 +155,7 @@ static void __init _register_composite(void *user, continue; if (comp->num_parents) { num_parents = comp->num_parents; - parent_names = comp->parent_names; + parent_data = comp->parent_data; break; } } @@ -166,8 +166,8 @@ static void __init _register_composite(void *user, } name = ti_dt_clk_name(node); - clk = clk_register_composite(NULL, name, - parent_names, num_parents, + clk = clk_register_composite_pdata(NULL, name, + parent_data, num_parents, _get_hw(cclk, CLK_COMPONENT_TYPE_MUX), &ti_clk_mux_ops, _get_hw(cclk, CLK_COMPONENT_TYPE_DIVIDER), @@ -190,7 +190,7 @@ static void __init _register_composite(void *user, if (!cclk->comp_clks[i]) continue; list_del(&cclk->comp_clks[i]->link); - kfree(cclk->comp_clks[i]->parent_names); + kfree(cclk->comp_clks[i]->parent_data); kfree(cclk->comp_clks[i]); } @@ -237,8 +237,9 @@ int __init ti_clk_add_component(struct device_node *node, struct clk_hw *hw, int type) { unsigned int num_parents; - const char **parent_names; + struct clk_parent_data *parent_data; struct component_clk *clk; + unsigned int i; num_parents = of_clk_get_parent_count(node); @@ -247,20 +248,21 @@ int __init ti_clk_add_component(struct device_node *node, struct clk_hw *hw, return -EINVAL; } - parent_names = kcalloc(num_parents, sizeof(char *), GFP_KERNEL); - if (!parent_names) + parent_data = kcalloc(num_parents, sizeof(*parent_data), GFP_KERNEL); + if (!parent_data) return -ENOMEM; - of_clk_parent_fill(node, parent_names, num_parents); + for (i = 0; i < num_parents; i++) + parent_data[i].index = i; clk = kzalloc_obj(*clk); if (!clk) { - kfree(parent_names); + kfree(parent_data); return -ENOMEM; } clk->num_parents = num_parents; - clk->parent_names = parent_names; + clk->parent_data = parent_data; clk->hw = hw; clk->node = node; clk->type = type; From be8e785db92c1cb448943b908900b324c93b5b08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Wed, 29 Jul 2026 14:16:40 +0200 Subject: [PATCH 0623/1328] Drop Michael Turquette's clk maintainer entry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mike wasn't publically active for the clk subsystem since years. Stop pretending that there is a maintainer team caring for clk. Signed-off-by: Uwe Kleine-König Acked-by: Michael Turquette Signed-off-by: Stephen Boyd --- CREDITS | 4 ++++ MAINTAINERS | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CREDITS b/CREDITS index 84793a967a0b..c8a67ab8efe5 100644 --- a/CREDITS +++ b/CREDITS @@ -4085,6 +4085,10 @@ S: 4226 Landgreen Street S: Rockville, Maryland 20853 S: USA +N: Michael Turquette +E: mturquette@baylibre.com +D: common clk maintenance + N: Stephen Tweedie E: sct@redhat.com P: 1024/E7A417AD E2 FE A4 20 34 EC ED FC 7D 7E 67 8D E0 31 D1 69 diff --git a/MAINTAINERS b/MAINTAINERS index d15f20dbc92d..6654ed8a17ad 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6447,7 +6447,6 @@ F: include/linux/comedi/ F: include/uapi/linux/comedi.h COMMON CLK FRAMEWORK -M: Michael Turquette M: Stephen Boyd R: Brian Masney L: linux-clk@vger.kernel.org From 8bc7ceadce1f362a8b0e88075ef3d926b286c01c Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Fri, 14 Aug 2026 18:27:49 +0200 Subject: [PATCH 0624/1328] MAINTAINERS: Add Brian Masney and Jerome Brunet as co-maintainers for clk subsystem With Michael Turquette stepping down as a clk co-maintainer, this subsystem is big enough that it needs multiple people to keep the clk community healthy. Add Brian and Jerome as co-maintainers. Link: https://lore.kernel.org/linux-clk/anW-jTldkWSJK3wl@redhat.com/T/#m0ae01b8dd23c452d82f34b61844a8305977a53a4 Acked-by: Michael Turquette Signed-off-by: Brian Masney Signed-off-by: Jerome Brunet Signed-off-by: Stephen Boyd --- MAINTAINERS | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 6654ed8a17ad..5b0c1c4d7804 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6448,7 +6448,8 @@ F: include/uapi/linux/comedi.h COMMON CLK FRAMEWORK M: Stephen Boyd -R: Brian Masney +M: Brian Masney +M: Jerome Brunet L: linux-clk@vger.kernel.org S: Maintained Q: http://patchwork.kernel.org/project/linux-clk/list/ From 44f7b876b7c6c7b3a219b7a810d3d9548df21540 Mon Sep 17 00:00:00 2001 From: Mario Rugiero Date: Sat, 15 Aug 2026 20:22:29 -0300 Subject: [PATCH 0625/1328] regulator: tps65185: wait for the IC to wake before the first I2C access The probe drives WAKEUP high with GPIOD_OUT_HIGH, enables the input supply, and then issues a regmap_update_bits() on INT_EN2 with no wait anywhere in between. The TPS65185 data sheet (TI SLVSAQ8G, February 2011, revised September 2017), section 7.6 "Timing Requirements: Data Transmission", Figure 2 "Power-Up and Power-Down Timing Diagram", footnote 1, requires 1.8 ms minimum between the WAKEUP rising edge and the IC being ready to accept an I2C transaction. The driver calls no delay function anywhere, and nothing on that path is guaranteed to take that long. On an RK3026 board the first transfer was NAKed: tps65185 0-0068: error -ENXIO: failed to enable temp irq -ENXIO here is rk3x_i2c reporting REG_INT_NAKRCV. Probe failed, so vposneg never registered and its consumer deferred indefinitely. Wait after the supply rather than at the gpiod_get(). Figure 2 times that 1.8 ms from a WAKEUP edge with VIN already up, whereas this driver asserts WAKEUP before enabling vin-supply; the data sheet does not describe that order, so on a board whose vin-supply is a switched regulator that was off, a wait placed at the gpiod_get() could elapse before the part had power. Waiting from whichever of the two comes last satisfies the specified case and is the conservative choice in the other. It also goes above the interrupt request rather than immediately before the INT_EN2 write, because that write is not necessarily the first transfer: tps65185_irq_thread() reads INT1 and INT2, and it is requested with IRQF_TRIGGER_LOW, so it can run as soon as the handler is installed. Tested on an ONYX BOOX C67ML (RK3026): the chip now probes, all three regulators register and the EPD controller comes up. Fixes: b0fc1e770194 ("regulator: Add TPS65185 driver") Signed-off-by: Mario Rugiero Link: https://patch.msgid.link/20260815232229.68474-1-mrugiero@gmail.com Signed-off-by: Mark Brown --- drivers/regulator/tps65185.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/regulator/tps65185.c b/drivers/regulator/tps65185.c index 1f13e4156cab..6a3130177e7a 100644 --- a/drivers/regulator/tps65185.c +++ b/drivers/regulator/tps65185.c @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -375,6 +376,11 @@ static int tps65185_probe(struct i2c_client *client) return dev_err_probe(&client->dev, ret, "failed to get vin regulator\n"); + // TPS65185x PMIC for E Ink Vizplex Enabled Electronic Paper Display Chapter 7.6 Figure 2: + // "Minimum delay time between WAKEUP rising edge and IC ready to accept I2C transaction." + // https://www.ti.com/lit/ds/symlink/tps65185.pdf + usleep_range(1800, 3000); + data->dev = &client->dev; i2c_set_clientdata(client, data); From 3d318fe4e99a627aa3c2980699e2e9f13ad18516 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 13 Aug 2026 10:09:47 +0300 Subject: [PATCH 0626/1328] drm/xe: tests: fix error message in xe_migrate_sanity_test() This is supposed to print the error code but there is a copy and paste bug so it prints "bo" instead of "err". Fixes: dd08ebf6c352 ("drm/xe: Introduce a new DRM driver for Intel GPUs") Signed-off-by: Dan Carpenter Link: https://patch.msgid.link/an1tu0z3T-qX1ogn@stanley.mountain Signed-off-by: Rodrigo Vivi (cherry picked from commit 28a4198c52a1468fc1b620a9837557ea1dc1766d) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/tests/xe_migrate.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpu/drm/xe/tests/xe_migrate.c b/drivers/gpu/drm/xe/tests/xe_migrate.c index 3c1be809be82..f10d9513747b 100644 --- a/drivers/gpu/drm/xe/tests/xe_migrate.c +++ b/drivers/gpu/drm/xe/tests/xe_migrate.c @@ -198,8 +198,7 @@ static void xe_migrate_sanity_test(struct xe_migrate *m, struct kunit *test, err = xe_bo_vmap(bo); if (err) { - KUNIT_FAIL(test, "Failed to vmap our pagetables: %li\n", - PTR_ERR(bo)); + KUNIT_FAIL(test, "Failed to vmap our pagetables: %d\n", err); return; } From fa487f56efba6344aef67d871688f0908fe08af3 Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Wed, 12 Aug 2026 12:17:54 +0000 Subject: [PATCH 0627/1328] f2fs: fix to reclaim space in f2fs_allocate_pinning_section() It needs to trigger checkpoint to free space reclaimed by f2fs_gc_range(), otherwise, fallocate() on pinfile will fail easily even there is slash space in conventional zone. [Testcase] nullblk_create.sh 512 2 1024 1024 mkfs.f2fs /dev/nullb0 -f -m mount /dev/nullb0 /mnt/f2fs/ touch /mnt/f2fs/pinfile f2fs_io pinfile set /mnt/f2fs/pinfile mkdir /mnt/f2fs/dir/ for((i=0;i<3934;i++)) do { dd if=/dev/zero of=/mnt/f2fs/dir/$i bs=1M count=1;} done sync for((i=0;i<3934;i+=2)) do { rm /mnt/f2fs/dir/$i;} done for((i=0;i<1950;i++)) do { rm /mnt/f2fs/dir/$i;} done sync f2fs_io fallocate 0 0 $((1024*1024*1024)) /mnt/f2fs/pinfile sync stat /mnt/f2fs/pinfile f2fs_io fiemap 0 $((1024*1024*1024)) /mnt/f2fs/pinfile [Before] fallocate failed: Resource temporarily unavailable File: /mnt/f2fs/pinfile Size: 109051904 Blocks: 213208 IO Block: 4096 regular file Device: 250,0 Inode: 4 Links: 1 Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2026-08-12 20:04:02.264000000 +0800 Modify: 2026-08-12 20:04:26.784000000 +0800 Change: 2026-08-12 20:04:26.784000000 +0800 Birth: - root@localhost:~# root@localhost:~# root@localhost:~# root@localhost:~# f2fs_io fiemap 0 $((1024*1024*1024)) /mnt/f2fs/pinfile Fiemap: offset = 0 len = 1073741824 logical addr. physical addr. length flags 0 0000000000000000 0000000002e00000 0000000000200000 00001000 1 0000000000200000 000000002dc00000 0000000000400000 00001000 2 0000000000600000 000000002e400000 0000000000600000 00001000 3 0000000000c00000 000000007a400000 0000000005c00000 00001001 [After] File: /mnt/f2fs/pinfile Size: 1073741824 Blocks: 2099216 IO Block: 4096 regular file Device: 250,0 Inode: 4 Links: 1 Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2026-08-12 19:47:49.428000000 +0800 Modify: 2026-08-12 19:49:06.808000000 +0800 Change: 2026-08-12 19:49:06.808000000 +0800 Birth: - Fiemap: offset = 0 len = 1073741824 logical addr. physical addr. length flags 0 0000000000000000 0000000002e00000 0000000000200000 00001000 1 0000000000200000 000000003aa00000 0000000000400000 00001000 2 0000000000600000 000000003b400000 0000000000200000 00001000 3 0000000000800000 000000007a200000 0000000005e00000 00001000 4 0000000006600000 0000000002800000 0000000000200000 00001000 5 0000000006800000 0000000003200000 0000000000400000 00001000 6 0000000006c00000 0000000003000000 0000000000200000 00001000 7 0000000006e00000 0000000003600000 0000000037200000 00001000 8 000000003e000000 000000003b200000 0000000000200000 00001000 9 000000003e200000 000000003a800000 0000000000200000 00001000 10 000000003e400000 000000003ae00000 0000000000400000 00001000 11 000000003e800000 000000003b600000 0000000001800000 00001001 Cc: stable@kernel.org Fixes: 9703d69d9d15 ("f2fs: support file pinning for zoned devices") Cc: Daeho Jeong Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/segment.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c index b81245ddd490..7bc779c8e935 100644 --- a/fs/f2fs/segment.c +++ b/fs/f2fs/segment.c @@ -3477,10 +3477,13 @@ int f2fs_allocate_pinning_section(struct f2fs_sb_info *sbi) err = f2fs_gc_range(sbi, 0, sbi->first_seq_zone_segno - 1, true, ZONED_PIN_SEC_REQUIRED_COUNT); f2fs_up_write_trace(&sbi->gc_lock, &lc); - - gc_required = false; - if (!err) + if (err) + return err; + err = f2fs_sync_fs(sbi->sb, 1); + if (!err) { + gc_required = false; goto retry; + } } return err; From 5d49025a4e596c4c9ac0c519ef9f9a2c91396856 Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Wed, 12 Aug 2026 12:17:55 +0000 Subject: [PATCH 0628/1328] f2fs: fix to shrink gc_lock coverage in f2fs_gc_range() In f2fs_allocate_pinning_section(), we will hold gc_lock before calling f2fs_gc_range() to migrate section in conventional zone, we may suffer worse case because we may need to traverse and migrate multiple sections if we failed to move blocks in section due to lot of reasons: ENOMEM, fail to migrate block of pinfile, racing on i_gc_rwsem. To avoid hold gc_lock for long time to block checkpoint, let's hold the lock and only try to migrate one section. Cc: Daeho Jeong Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/f2fs.h | 2 +- fs/f2fs/file.c | 2 +- fs/f2fs/gc.c | 34 +++++++++++++++++++++++++--------- fs/f2fs/segment.c | 4 +--- 4 files changed, 28 insertions(+), 14 deletions(-) diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index 7fd3a181af61..3e69b94e9465 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -4274,7 +4274,7 @@ int f2fs_gc(struct f2fs_sb_info *sbi, struct f2fs_gc_control *gc_control); void f2fs_build_gc_manager(struct f2fs_sb_info *sbi); int f2fs_gc_range(struct f2fs_sb_info *sbi, unsigned int start_seg, unsigned int end_seg, - bool dry_run, unsigned int dry_run_sections); + bool dry_run, unsigned int dry_run_sections, bool lock); void f2fs_reset_gc_victim_resource(struct f2fs_sb_info *sbi, unsigned int start, unsigned int end); int f2fs_resize_fs(struct file *filp, __u64 block_count); diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index 76316e537f65..a54b3ab52f1a 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -3828,7 +3828,7 @@ static int f2fs_ioc_reserve_dev_alias(struct file *filp) } /* do GC to move out valid blocks in the range all at once! */ - err = f2fs_gc_range(sbi, start, end, false, 0); + err = f2fs_gc_range(sbi, start, end, false, 0, false); if (err) { f2fs_unlock_op(sbi, &lc); goto out_gc_unlock; diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c index 51fc8c69eb25..2f97a7a98517 100644 --- a/fs/f2fs/gc.c +++ b/fs/f2fs/gc.c @@ -2150,8 +2150,9 @@ void f2fs_build_gc_manager(struct f2fs_sb_info *sbi) int f2fs_gc_range(struct f2fs_sb_info *sbi, unsigned int start_seg, unsigned int end_seg, - bool dry_run, unsigned int dry_run_sections) + bool dry_run, unsigned int dry_run_sections, bool lock) { + struct f2fs_lock_context lc; unsigned int segno; unsigned int gc_secs = dry_run_sections; @@ -2164,28 +2165,43 @@ int f2fs_gc_range(struct f2fs_sb_info *sbi, .ilist = LIST_HEAD_INIT(gc_list.ilist), .iroot = RADIX_TREE_INIT(gc_list.iroot, GFP_NOFS), }; + int err = 0; + + if (lock) + f2fs_down_write_trace(&sbi->gc_lock, &lc); /* * avoid migrating empty section, as it can be allocated by * log in parallel. */ if (!get_valid_blocks(sbi, segno, true)) - continue; + goto next; if (is_cursec(sbi, GET_SEC_FROM_SEG(sbi, segno))) - continue; + goto next; do_garbage_collect(sbi, segno, &gc_list, FG_GC, true, false); put_gc_inode(&gc_list); - if (!dry_run && get_valid_blocks(sbi, segno, true)) - return -EAGAIN; + if (!dry_run && get_valid_blocks(sbi, segno, true)) { + err = -EAGAIN; + goto next; + } if (dry_run && dry_run_sections && - !get_valid_blocks(sbi, segno, true) && --gc_secs == 0) - break; + !get_valid_blocks(sbi, segno, true)) { + --gc_secs; + goto next; + } if (fatal_signal_pending(current)) - return -ERESTARTSYS; + err = -ERESTARTSYS; +next: + if (lock) + f2fs_up_write_trace(&sbi->gc_lock, &lc); + if (err) + return err; + if (dry_run && dry_run_sections && !gc_secs) + return 0; } return 0; @@ -2231,7 +2247,7 @@ static int free_segment_range(struct f2fs_sb_info *sbi, } /* do GC to move out valid blocks in the range */ - err = f2fs_gc_range(sbi, start, end, dry_run, 0); + err = f2fs_gc_range(sbi, start, end, dry_run, 0, false); if (err || dry_run) goto out; diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c index 7bc779c8e935..caa46c866443 100644 --- a/fs/f2fs/segment.c +++ b/fs/f2fs/segment.c @@ -3473,10 +3473,8 @@ int f2fs_allocate_pinning_section(struct f2fs_sb_info *sbi) f2fs_unlock_op(sbi, &lc); if (f2fs_sb_has_blkzoned(sbi) && err == -EAGAIN && gc_required) { - f2fs_down_write_trace(&sbi->gc_lock, &lc); err = f2fs_gc_range(sbi, 0, sbi->first_seq_zone_segno - 1, - true, ZONED_PIN_SEC_REQUIRED_COUNT); - f2fs_up_write_trace(&sbi->gc_lock, &lc); + true, ZONED_PIN_SEC_REQUIRED_COUNT, true); if (err) return err; err = f2fs_sync_fs(sbi->sb, 1); From 6da62bbde487d4678702ef004a911cd1c7a5c746 Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Wed, 12 Aug 2026 12:20:07 +0000 Subject: [PATCH 0629/1328] f2fs: cleanup w/ f2fs_need_rand_{blk, seg, seg_blk} No logic changes. Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/f2fs.h | 13 +++++++++++-- fs/f2fs/gc.c | 2 +- fs/f2fs/segment.c | 8 ++++---- fs/f2fs/super.c | 4 ++-- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index 3e69b94e9465..2c439ad4f36a 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -4142,10 +4142,19 @@ static inline struct inode *fio_inode(struct f2fs_io_info *fio) #define MIN_FRAGMENT_SIZE 1 #define MAX_FRAGMENT_SIZE 512 +static inline bool f2fs_need_rand_blk(struct f2fs_sb_info *sbi) +{ + return F2FS_OPTION(sbi).fs_mode == FS_MODE_FRAGMENT_BLK; +} + static inline bool f2fs_need_rand_seg(struct f2fs_sb_info *sbi) { - return F2FS_OPTION(sbi).fs_mode == FS_MODE_FRAGMENT_SEG || - F2FS_OPTION(sbi).fs_mode == FS_MODE_FRAGMENT_BLK; + return F2FS_OPTION(sbi).fs_mode == FS_MODE_FRAGMENT_SEG; +} + +static inline bool f2fs_need_rand_seg_blk(struct f2fs_sb_info *sbi) +{ + return f2fs_need_rand_blk(sbi) || f2fs_need_rand_seg(sbi); } /* diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c index 2f97a7a98517..e6758adc5da1 100644 --- a/fs/f2fs/gc.c +++ b/fs/f2fs/gc.c @@ -310,7 +310,7 @@ static void select_policy(struct f2fs_sb_info *sbi, int gc_type, p->max_search = sbi->max_victim_search; /* let's select beginning hot/small space first. */ - if (f2fs_need_rand_seg(sbi)) { + if (f2fs_need_rand_seg_blk(sbi)) { p->offset = get_random_u32_below(MAIN_SECS(sbi) * SEGS_PER_SEC(sbi)); SIT_I(sbi)->last_victim[p->gc_mode] = p->offset; diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c index caa46c866443..76c6f6fe503a 100644 --- a/fs/f2fs/segment.c +++ b/fs/f2fs/segment.c @@ -3052,7 +3052,7 @@ static unsigned int __get_next_segno(struct f2fs_sb_info *sbi, int type) sanity_check_seg_type(sbi, seg_type); if (__is_large_section(sbi)) { - if (f2fs_need_rand_seg(sbi)) { + if (f2fs_need_rand_seg_blk(sbi)) { unsigned int hint = GET_SEC_FROM_SEG(sbi, curseg->segno); if (GET_SEC_FROM_SEG(sbi, curseg->segno + 1) != hint) @@ -3061,7 +3061,7 @@ static unsigned int __get_next_segno(struct f2fs_sb_info *sbi, int type) GET_SEG_FROM_SEC(sbi, hint + 1) - 1); } return curseg->segno; - } else if (f2fs_need_rand_seg(sbi)) { + } else if (f2fs_need_rand_seg_blk(sbi)) { return get_random_u32_below(MAIN_SECS(sbi) * SEGS_PER_SEC(sbi)); } @@ -3117,7 +3117,7 @@ static int new_curseg(struct f2fs_sb_info *sbi, int type, bool new_sec) curseg->next_segno = segno; reset_curseg(sbi, type, 1); curseg->alloc_type = LFS; - if (F2FS_OPTION(sbi).fs_mode == FS_MODE_FRAGMENT_BLK) + if (f2fs_need_rand_blk(sbi)) curseg->fragment_remained_chunk = get_random_u32_inclusive(1, sbi->max_fragment_chunk); return 0; @@ -3936,7 +3936,7 @@ int f2fs_allocate_data_block(struct f2fs_sb_info *sbi, struct folio *folio, curseg->next_blkoff = f2fs_find_next_ssr_block(sbi, curseg); } else { curseg->next_blkoff++; - if (F2FS_OPTION(sbi).fs_mode == FS_MODE_FRAGMENT_BLK) + if (f2fs_need_rand_blk(sbi)) f2fs_randomize_chunk(sbi, curseg); } if (curseg->next_blkoff >= f2fs_usable_blks_in_seg(sbi, curseg->segno)) diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c index fa4a7a7288d5..8dd656a7d3dc 100644 --- a/fs/f2fs/super.c +++ b/fs/f2fs/super.c @@ -2457,9 +2457,9 @@ static int f2fs_show_options(struct seq_file *seq, struct dentry *root) seq_puts(seq, "adaptive"); else if (F2FS_OPTION(sbi).fs_mode == FS_MODE_LFS) seq_puts(seq, "lfs"); - else if (F2FS_OPTION(sbi).fs_mode == FS_MODE_FRAGMENT_SEG) + else if (f2fs_need_rand_seg(sbi)) seq_puts(seq, "fragment:segment"); - else if (F2FS_OPTION(sbi).fs_mode == FS_MODE_FRAGMENT_BLK) + else if (f2fs_need_rand_blk(sbi)) seq_puts(seq, "fragment:block"); seq_printf(seq, ",active_logs=%u", F2FS_OPTION(sbi).active_logs); if (test_opt(sbi, RESERVE_ROOT) || test_opt(sbi, RESERVE_NODE)) From 0a1703eba23707e3b2edfa2a2329352e7abc0ffa Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Wed, 12 Aug 2026 12:20:08 +0000 Subject: [PATCH 0630/1328] f2fs: fix to avoid pinfile fragment on fragment:{block, segment} mode pinfile fallocate() conflicts w/ mode=fragment:{block,segment} mount option, result in fragment blocks in pinfile, it violate semantics of pinfile introduced in commit f5a53edcf01e ("f2fs: support aligned pinned file"). mkfs.f2fs -f /dev/vdb mount -t f2fs -o mode=fragment:block /dev/vdb /mnt/f2fs/ dd if=/dev/zero of=/mnt/f2fs/file bs=1M count=3900 sync touch /mnt/f2fs/pinfile f2fs_io pinfile set /mnt/f2fs/pinfile f2fs_io fallocate 0 0 $((1024*1024*16)) /mnt/f2fs/pinfile sync f2fs_io fiemap 0 $((1024*1024*16)) /mnt/f2fs/pinfile [Before] fallocate failed: No space left on device Fiemap: offset = 0 len = 16777216 logical addr. physical addr. length flags 0 0000000000000000 00000000d7200000 0000000000004000 00001000 1 0000000000004000 00000000d7207000 0000000000001000 00001000 2 0000000000005000 00000000d720c000 0000000000002000 00001000 3 0000000000007000 00000000d7211000 0000000000001000 00001000 4 0000000000008000 00000000d7214000 0000000000001000 00001000 5 0000000000009000 00000000d7218000 0000000000001000 00001000 6 000000000000a000 00000000d721d000 0000000000001000 00001000 7 000000000000b000 00000000d721f000 0000000000004000 00001000 ... 96 00000000000f1000 00000000d73e9000 0000000000004000 00001000 97 00000000000f5000 00000000d73f1000 0000000000003000 00001000 98 00000000000f8000 00000000d73f5000 0000000000004000 00001000 99 00000000000fc000 00000000d73fa000 0000000000001000 00001000 100 00000000000fd000 00000000d73ff000 0000000000001000 00001001 [After] fallocated a file: i_size=16777216, i_blocks=32808 Fiemap: offset = 0 len = 16777216 logical addr. physical addr. length flags 0 0000000000000000 0000000018a00000 0000000000400000 00001000 1 0000000000400000 0000000019000000 0000000000400000 00001000 2 0000000000800000 0000000032400000 0000000000200000 00001000 3 0000000000a00000 0000000038000000 0000000000200000 00001000 4 0000000000c00000 0000000039c00000 0000000000200000 00001000 5 0000000000e00000 0000000044c00000 0000000000200000 00001001 Let's ignore mode=fragment:{block,segment} mount option while fallocate() on pinfile. Fixes: 6691d940b0e0 ("f2fs: introduce fragment allocation mode mount option") Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/f2fs.h | 15 +++++++++++---- fs/f2fs/gc.c | 2 +- fs/f2fs/segment.c | 8 ++++---- fs/f2fs/super.c | 4 ++-- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index 2c439ad4f36a..c925b9207a37 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -4142,19 +4142,26 @@ static inline struct inode *fio_inode(struct f2fs_io_info *fio) #define MIN_FRAGMENT_SIZE 1 #define MAX_FRAGMENT_SIZE 512 -static inline bool f2fs_need_rand_blk(struct f2fs_sb_info *sbi) +static inline bool f2fs_need_rand_blk(struct f2fs_sb_info *sbi, + enum log_type type) { + if (type == CURSEG_COLD_DATA_PINNED) + return false; return F2FS_OPTION(sbi).fs_mode == FS_MODE_FRAGMENT_BLK; } -static inline bool f2fs_need_rand_seg(struct f2fs_sb_info *sbi) +static inline bool f2fs_need_rand_seg(struct f2fs_sb_info *sbi, + enum log_type type) { + if (type == CURSEG_COLD_DATA_PINNED) + return false; return F2FS_OPTION(sbi).fs_mode == FS_MODE_FRAGMENT_SEG; } -static inline bool f2fs_need_rand_seg_blk(struct f2fs_sb_info *sbi) +static inline bool f2fs_need_rand_seg_blk(struct f2fs_sb_info *sbi, + enum log_type type) { - return f2fs_need_rand_blk(sbi) || f2fs_need_rand_seg(sbi); + return f2fs_need_rand_blk(sbi, type) || f2fs_need_rand_seg(sbi, type); } /* diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c index e6758adc5da1..c4da2f31805b 100644 --- a/fs/f2fs/gc.c +++ b/fs/f2fs/gc.c @@ -310,7 +310,7 @@ static void select_policy(struct f2fs_sb_info *sbi, int gc_type, p->max_search = sbi->max_victim_search; /* let's select beginning hot/small space first. */ - if (f2fs_need_rand_seg_blk(sbi)) { + if (f2fs_need_rand_seg_blk(sbi, type)) { p->offset = get_random_u32_below(MAIN_SECS(sbi) * SEGS_PER_SEC(sbi)); SIT_I(sbi)->last_victim[p->gc_mode] = p->offset; diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c index 76c6f6fe503a..56decf9c691c 100644 --- a/fs/f2fs/segment.c +++ b/fs/f2fs/segment.c @@ -3052,7 +3052,7 @@ static unsigned int __get_next_segno(struct f2fs_sb_info *sbi, int type) sanity_check_seg_type(sbi, seg_type); if (__is_large_section(sbi)) { - if (f2fs_need_rand_seg_blk(sbi)) { + if (f2fs_need_rand_seg_blk(sbi, type)) { unsigned int hint = GET_SEC_FROM_SEG(sbi, curseg->segno); if (GET_SEC_FROM_SEG(sbi, curseg->segno + 1) != hint) @@ -3061,7 +3061,7 @@ static unsigned int __get_next_segno(struct f2fs_sb_info *sbi, int type) GET_SEG_FROM_SEC(sbi, hint + 1) - 1); } return curseg->segno; - } else if (f2fs_need_rand_seg_blk(sbi)) { + } else if (f2fs_need_rand_seg_blk(sbi, type)) { return get_random_u32_below(MAIN_SECS(sbi) * SEGS_PER_SEC(sbi)); } @@ -3117,7 +3117,7 @@ static int new_curseg(struct f2fs_sb_info *sbi, int type, bool new_sec) curseg->next_segno = segno; reset_curseg(sbi, type, 1); curseg->alloc_type = LFS; - if (f2fs_need_rand_blk(sbi)) + if (f2fs_need_rand_blk(sbi, type)) curseg->fragment_remained_chunk = get_random_u32_inclusive(1, sbi->max_fragment_chunk); return 0; @@ -3936,7 +3936,7 @@ int f2fs_allocate_data_block(struct f2fs_sb_info *sbi, struct folio *folio, curseg->next_blkoff = f2fs_find_next_ssr_block(sbi, curseg); } else { curseg->next_blkoff++; - if (f2fs_need_rand_blk(sbi)) + if (f2fs_need_rand_blk(sbi, type)) f2fs_randomize_chunk(sbi, curseg); } if (curseg->next_blkoff >= f2fs_usable_blks_in_seg(sbi, curseg->segno)) diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c index 8dd656a7d3dc..0c8f60b7242f 100644 --- a/fs/f2fs/super.c +++ b/fs/f2fs/super.c @@ -2457,9 +2457,9 @@ static int f2fs_show_options(struct seq_file *seq, struct dentry *root) seq_puts(seq, "adaptive"); else if (F2FS_OPTION(sbi).fs_mode == FS_MODE_LFS) seq_puts(seq, "lfs"); - else if (f2fs_need_rand_seg(sbi)) + else if (f2fs_need_rand_seg(sbi, NO_CHECK_TYPE)) seq_puts(seq, "fragment:segment"); - else if (f2fs_need_rand_blk(sbi)) + else if (f2fs_need_rand_blk(sbi, NO_CHECK_TYPE)) seq_puts(seq, "fragment:block"); seq_printf(seq, ",active_logs=%u", F2FS_OPTION(sbi).active_logs); if (test_opt(sbi, RESERVE_ROOT) || test_opt(sbi, RESERVE_NODE)) From 112e447d17f78d08485b3076a8baa1dfb9794bd8 Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Mon, 3 Aug 2026 09:11:23 +0800 Subject: [PATCH 0631/1328] netfilter: validate L4 headers after userspace packet writes NFQUEUE and nft_payload can hand packet data modified by userspace back to the stack. Recent restrictions keep link and network headers stable, but transport header fields can still be changed. A packet can therefore keep the same network header and conntrack entry while changing the transport header layout. For TCP, increasing doff can make later helper or NAT code use a different transport-header base than the parser used, and can make offsets point past skb->tail. Extend NFQUEUE payload validation to check the final L4 protocol and known base headers after IPv4 options or IPv6 extension headers. Reject packets whose L4 protocol no longer matches an attached non-template conntrack entry, and reject IP fragments that already have such a conntrack entry before trying to validate transport headers. Unknown L4 protocols are left to their normal protocol handlers. For nft payload writes, reject transport-header stores that overlap TCP doff. nft_nh_write_ok() already rejects network-header protocol changes, so keeping doff stable prevents nft payload writes from changing the TCP header length underneath conntrack and helper users. This patch is a follow up to commit df07998dfd40 ("netfilter: nftables: restrict linklayer and network header writes") and commit 54f34607d184 ("netfilter: nfnetlink_queue: restrict writes to network header"). Reported-by: Vega Signed-off-by: Zhiling Zou Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nfnetlink_queue.c | 72 +++++++++++++++++++++++++++++++-- net/netfilter/nft_payload.c | 13 ++++++ 2 files changed, 82 insertions(+), 3 deletions(-) diff --git a/net/netfilter/nfnetlink_queue.c b/net/netfilter/nfnetlink_queue.c index b8aaf39cb4d8..c727668b0c5b 100644 --- a/net/netfilter/nfnetlink_queue.c +++ b/net/netfilter/nfnetlink_queue.c @@ -28,10 +28,17 @@ #include #include #include +#include +#include +#include #include +#include #include #include #include +#include +#include +#include #include #include #include @@ -1206,10 +1213,62 @@ static bool nfqnl_validate_ipopts(const struct iphdr *iph_new, return memcmp(iph_new + 1, ip_hdr(e->skb) + 1, ihl - sizeof(*iph_orig)) == 0; } +static bool nfqnl_validate_l4(const u8 *data, unsigned int data_len, + const struct nf_queue_entry *e, u8 proto, + bool fragment) +{ +#if IS_ENABLED(CONFIG_NF_CONNTRACK) + enum ip_conntrack_info ctinfo; + const struct nf_conn *ct; + + ct = nf_ct_get(e->skb, &ctinfo); + if (ct && !nf_ct_is_template(ct)) { + if (fragment || nf_ct_protonum(ct) != proto) + return false; + } +#endif + + if (fragment) + return true; + + switch (proto) { + case IPPROTO_TCP: { + const struct tcphdr *th = (const struct tcphdr *)data; + unsigned int thlen; + + if (data_len < sizeof(*th)) + return false; + + thlen = __tcp_hdrlen(th); + if (thlen < sizeof(*th) || data_len < thlen) + return false; + + return true; + } + case IPPROTO_UDP: + return data_len >= sizeof(struct udphdr); + case IPPROTO_ICMP: + return data_len >= sizeof(struct icmphdr); + case IPPROTO_ICMPV6: + return data_len >= sizeof(struct icmp6hdr); + case IPPROTO_SCTP: + return data_len >= sizeof(struct sctphdr); + case IPPROTO_GRE: + return data_len >= sizeof(struct gre_base_hdr); + case IPPROTO_AH: + return data_len >= sizeof(struct ip_auth_hdr); + case IPPROTO_ESP: + return data_len >= sizeof(struct ip_esp_hdr); + } + + return true; +} + static bool nfqnl_validate_ip4(const struct iphdr *iph, unsigned int data_len, const struct nf_queue_entry *e) { unsigned int ihl; + bool fragment; if (data_len < sizeof(*iph)) return false; @@ -1226,10 +1285,14 @@ static bool nfqnl_validate_ip4(const struct iphdr *iph, unsigned int data_len, if (ntohs(iph->tot_len) != data_len) return false; + fragment = iph->frag_off & htons(IP_MF | IP_OFFSET); + /* support for ipopts mangling would require * recompile + skb transport header update. */ - return nfqnl_validate_ipopts(iph, e); + return nfqnl_validate_ipopts(iph, e) && + nfqnl_validate_l4((const u8 *)iph + ihl, data_len - ihl, e, + iph->protocol, fragment); } static bool nfqnl_validate_one_exthdr(const u8 *data, @@ -1273,6 +1336,7 @@ static bool nfqnl_validate_exthdr(const struct ipv6hdr *ip6_new, const u8 *data = (const u8 *)ip6_new; u8 orig_nexthdr = ip6_orig->nexthdr; u8 new_nexthdr = ip6_new->nexthdr; + bool fragment = false; if (new_nexthdr != orig_nexthdr) return false; @@ -1286,7 +1350,8 @@ static bool nfqnl_validate_exthdr(const struct ipv6hdr *ip6_new, int hdrlen; if (orig_nexthdr == NEXTHDR_NONE) - return true; + return nfqnl_validate_l4(data, data_len, e, + new_nexthdr, fragment); if (unlikely(exthdr_cnt++ >= IP6_MAX_EXT_HDRS_CNT)) return false; @@ -1297,6 +1362,7 @@ static bool nfqnl_validate_exthdr(const struct ipv6hdr *ip6_new, switch (orig_nexthdr) { case NEXTHDR_FRAGMENT: + fragment = true; hdrlen = sizeof(struct frag_hdr); break; case NEXTHDR_AUTH: @@ -1323,7 +1389,7 @@ static bool nfqnl_validate_exthdr(const struct ipv6hdr *ip6_new, data += hdrlen; } - return true; + return nfqnl_validate_l4(data, data_len, e, new_nexthdr, fragment); } static bool nfqnl_validate_ip6(const struct ipv6hdr *ip6, unsigned int data_len, diff --git a/net/netfilter/nft_payload.c b/net/netfilter/nft_payload.c index 8a4472fd77d9..e315d35f73d4 100644 --- a/net/netfilter/nft_payload.c +++ b/net/netfilter/nft_payload.c @@ -1067,6 +1067,17 @@ static bool nft_payload_csum_write_ok(const struct nft_pktinfo *pkt, return false; } +static bool nft_th_write_ok(const struct nft_pktinfo *pkt, + const struct nft_payload_set *priv) +{ + unsigned int doff = offsetof(struct tcphdr, ack_seq) + sizeof(__be32); + + if (pkt->tprot != IPPROTO_TCP) + return true; + + return priv->offset > doff || priv->offset + priv->len <= doff; +} + static void nft_payload_set_eval(const struct nft_expr *expr, struct nft_regs *regs, const struct nft_pktinfo *pkt) @@ -1105,6 +1116,8 @@ static void nft_payload_set_eval(const struct nft_expr *expr, case NFT_PAYLOAD_TRANSPORT_HEADER: if (!(pkt->flags & NFT_PKTINFO_L4PROTO) || pkt->fragoff) goto err; + if (!nft_th_write_ok(pkt, priv)) + goto err; offset = nft_thoff(pkt); break; case NFT_PAYLOAD_INNER_HEADER: From e80456d79ec83b00a6bd44c377684b1632ccc3c7 Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Fri, 7 Aug 2026 02:28:08 +0200 Subject: [PATCH 0632/1328] netfilter: ipset: remove need to allocate memory on delete operations Allocating mem via GFP_ATOMIC on delete is problematic, delete operations should always succeed. Do in-place substitution: When /cidr reaches 0 count (no more elements in the range), move ranges stored later in the array forward and keep the count 0 ones at the end. INIT_CIDR() can then check count == 0 without a need to search next element in the array. To avoid problems on weakly ordered architectures, pack the structure so it is only 32bit wide, then use READ/WRITE_ONCE to store both cidr and count. atomically. Also update comments to mention the possible presence of ignored 0-count-0-cidr structures at the end and need for seqcount. seqcount is used to restart. This avoids bogus range misses. Given: [0]: /29 [1]: /24 cpu1 reads slot 0. then, right after, cpu2 removes /29. count drops to 0, so it updates array to: [0], /24, [1], /0 (count 0). cpu1 then skips /28: slot 0 was already visited, but slot 1 already replaced. Note that mtype_add() doesn't check mtype_add_cidr() return value. Doing this here is useless noise as this code is extensively rewritten in the rhashtable replacement patch. Assisted-by: Claude:claude-sonnet-5 Fixes: 8e5fd2a55e24 ("netfilter: ipset: rework cidr bookkeeping") Signed-off-by: Florian Westphal Signed-off-by: Pablo Neira Ayuso --- net/netfilter/ipset/ip_set_hash_gen.h | 174 +++++++++++++------ net/netfilter/ipset/ip_set_hash_netiface.c | 1 - net/netfilter/ipset/ip_set_hash_netportnet.c | 1 - 3 files changed, 124 insertions(+), 52 deletions(-) diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h index f00c82acd7f0..80ca523f304b 100644 --- a/net/netfilter/ipset/ip_set_hash_gen.h +++ b/net/netfilter/ipset/ip_set_hash_gen.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -98,14 +99,34 @@ struct htable { #define IPSET_NET_COUNT 1 #endif -/* Book-keeping of the prefixes added to the set */ +/** + * struct net_prefix - Representation of a network prefix. + * @cidr: The CIDR prefix length. + * @count: Number of occurrences. + */ struct net_prefix { - u8 cidr; /* the cidr value */ - u32 count; /* number of elements of this cidr */ + u32 cidr:8; + u32 count:24; }; +#define CIDR_MAX_COUNT ((1 << 24) - 1) + +/** + * struct net_prefixes - A collection of network prefixes. + * @rcu: RCU head + * @seq: Sequence counter guarding in-place reordering of @nets + * @len: Number of entries in the array. + * @nets: Array of net_prefix structures (sorted by CIDR descending). + * + * @nets entries are updated in place under @set's lock. A single entry's + * cidr/count pair is always updated atomically via READ_ONCE()/WRITE_ONCE(), + * but removing an entry also shifts every following entry down by one slot. + * Lockless readers that scan the whole array (i.e. more than a single + * indexed slot) must use @seq to detect and retry across such a shift. + */ struct net_prefixes { struct rcu_head rcu; + seqcount_spinlock_t seq; u8 len; struct net_prefix nets[] __counted_by(len); }; @@ -143,8 +164,11 @@ htable_size(u8 hbits) #endif #define INIT_CIDR(n, host_mask) ({ \ - const struct net_prefixes *__n = rcu_dereference(n); \ - DCIDR_PUT((__n)->len ? (__n)->nets[0].cidr : host_mask);\ + const struct net_prefixes *__n = rcu_dereference(n); \ + struct net_prefix __p = \ + __n->len ? READ_ONCE(__n->nets[0]) \ + : (struct net_prefix){}; \ + DCIDR_PUT(__p.count ? __p.cidr : host_mask); \ }) #endif /* IP_SET_HASH_WITH_NETS */ @@ -318,27 +342,43 @@ struct mtype_resize_ad { }; #ifdef IP_SET_HASH_WITH_NETS -/* Network cidr size book keeping when the hash stores different - * sized networks. cidr == real cidr + 1 to support /0. +/** + * mtype_add_cidr - Add a CIDR entry to hash table bookkeeping + * @set: Pointer to the ip_set + * @h: Pointer to the htype + * @cidr: The CIDR prefix length + * @n: The index of the net_prefix array to add @cidr to + * + * Performs an update if @cidr is found, otherwise performs COW-style + * allocation and replacement via RCU. + * + * Return: 0 on success, negative error code on failure. */ static int mtype_add_cidr(struct ip_set *set, struct htype *h, u8 cidr, u8 n) { - struct net_prefixes *nets, *tmp; int i, j, found, len = 0, ret = 0; + struct net_prefixes *nets, *tmp; + struct net_prefix np; spin_lock_bh(&set->lock); nets = __ipset_dereference(h->rnets[n]); /* Add in increasing prefix order, so larger cidr first */ for (i = 0, found = -1; i < nets->len; i++) { - if (nets->nets[i].count) + np = READ_ONCE(nets->nets[i]); + if (np.count) len++; if (found != -1) { continue; - } else if (nets->nets[i].cidr < cidr) { + } else if (np.cidr < cidr) { found = i; - } else if (nets->nets[i].cidr == cidr) { - nets->nets[i].count++; + } else if (np.cidr == cidr) { + if (np.count < CIDR_MAX_COUNT) { + np.count++; + WRITE_ONCE(nets->nets[i], np); + } else { + ret = -EOVERFLOW; + } goto unlock; } } @@ -350,6 +390,7 @@ mtype_add_cidr(struct ip_set *set, struct htype *h, u8 cidr, u8 n) } tmp->len = len; + seqcount_spinlock_init(&tmp->seq, &set->lock); for (i = 0, j = 0; i < nets->len; i++) { if (i == found) { tmp->nets[j].cidr = cidr; @@ -371,42 +412,60 @@ mtype_add_cidr(struct ip_set *set, struct htype *h, u8 cidr, u8 n) return ret; } +/** + * mtype_del_cidr - Remove CIDR entry and maintain array integrity. + * @set: Pointer to the ip_set. + * @h: Pointer to the htype. + * @cidr: The CIDR prefix length. + * @n: The index of the net_prefix array to remove @cidr from + * + * If CIDR entry count falls to 0, this function performs a "shift-left" + * operation on all following elements. This ensures that the array remains + * contiguous and maintains its descending order by CIDR. The vacated slot + * at the end of the array is zeroed out (cidr=0, count=0). + */ static void mtype_del_cidr(struct ip_set *set, struct htype *h, u8 cidr, u8 n) { - struct net_prefixes *nets, *tmp; - u8 i, j, len = 0; + struct net_prefixes *nets; + struct net_prefix np; int found; + u8 i, j; + + BUILD_BUG_ON(sizeof(struct net_prefix) != sizeof(u32)); spin_lock_bh(&set->lock); nets = __ipset_dereference(h->rnets[n]); for (i = 0, found = -1; i < nets->len; i++) { - if (nets->nets[i].count) - len++; - if (nets->nets[i].cidr == cidr) + np = READ_ONCE(nets->nets[i]); + if (np.count && np.cidr == cidr) { + np.count--; found = i; + break; + } } if (unlikely(found == -1)) goto unlock; - nets->nets[found].count--; - if (nets->nets[found].count) + if (np.count) { + WRITE_ONCE(nets->nets[found], np); goto unlock; - len--; - tmp = kzalloc_flex(*tmp, nets, len, GFP_ATOMIC); - if (!tmp) - /* Leave a hole */ - goto unlock; - - tmp->len = len; - for (i = 0, j = 0; i < nets->len; i++) { - if (!nets->nets[i].count || i == found) - continue; - tmp->nets[j].cidr = nets->nets[i].cidr; - tmp->nets[j++].count = nets->nets[i].count; } - rcu_assign_pointer(h->rnets[n], tmp); - kfree_rcu(nets, rcu); + + write_seqcount_begin(&nets->seq); + for (i = 0, j = 0; i < nets->len; i++) { + if (i == found) + continue; + + np = READ_ONCE(nets->nets[i]); + if (i != j) + WRITE_ONCE(nets->nets[j], np); + j++; + } + + while (j < nets->len) + WRITE_ONCE(nets->nets[j++], (struct net_prefix){}); + write_seqcount_end(&nets->seq); unlock: spin_unlock_bh(&set->lock); } @@ -451,7 +510,7 @@ mtype_flush(struct ip_set *set) { struct htype *h = set->data; #ifdef IP_SET_HASH_WITH_NETS - struct net_prefixes *nets, *tmp; + struct net_prefixes *nets; #endif struct htable *t; struct hbucket *n; @@ -477,17 +536,15 @@ mtype_flush(struct ip_set *set) } #ifdef IP_SET_HASH_WITH_NETS for (i = 0; i < IPSET_NET_COUNT; i++) { - nets = ipset_dereference_nfnl(h->rnets[i]); - tmp = kzalloc_obj(*tmp, GFP_ATOMIC); - if (!tmp) { - u8 j; + u8 j; - for (j = 0; j < nets->len; j++) - nets->nets[j].count = 0; - } else { - rcu_assign_pointer(h->rnets[i], tmp); - kfree_rcu(nets, rcu); - } + spin_lock_bh(&set->lock); + nets = ipset_dereference_nfnl(h->rnets[i]); + write_seqcount_begin(&nets->seq); + for (j = 0; j < nets->len; j++) + WRITE_ONCE(nets->nets[j], (struct net_prefix){}); + write_seqcount_end(&nets->seq); + spin_unlock_bh(&set->lock); } #endif } @@ -1253,31 +1310,41 @@ mtype_test_cidrs(struct ip_set *set, struct mtype_elem *d, #if IPSET_NET_COUNT == 2 struct net_prefixes *nets1; struct mtype_elem orig = *d; + unsigned int seq1; int ret, i, j, k; #else int ret, i, j; #endif - u32 key, multi = 0; + unsigned int seq0; + u32 key, multi; u8 pos; pr_debug("test by nets\n"); rcu_read_lock_bh(); +retry: + multi = 0; nets0 = rcu_dereference_bh(h->rnets[0]); + seq0 = read_seqcount_begin(&nets0->seq); #if IPSET_NET_COUNT == 2 nets1 = rcu_dereference_bh(h->rnets[1]); + seq1 = read_seqcount_begin(&nets1->seq); #endif for (j = 0; j < nets0->len && !multi; j++) { - if (!nets0->nets[j].count) + struct net_prefix p0 = READ_ONCE(nets0->nets[j]); + + if (!p0.count) continue; #if IPSET_NET_COUNT == 2 mtype_data_reset_elem(d, &orig); - mtype_data_netmask(d, nets0->nets[j].cidr, false); + mtype_data_netmask(d, p0.cidr, false); for (k = 0; k < nets1->len && !multi; k++) { - if (!nets1->nets[k].count) + struct net_prefix p1 = READ_ONCE(nets1->nets[k]); + + if (!p1.count) continue; - mtype_data_netmask(d, nets1->nets[k].cidr, true); + mtype_data_netmask(d, p1.cidr, true); #else - mtype_data_netmask(d, nets0->nets[j].cidr); + mtype_data_netmask(d, p0.cidr); #endif key = HKEY(d, h->initval, t->htable_bits); n = rcu_dereference_bh(hbucket(t, key)); @@ -1304,6 +1371,12 @@ mtype_test_cidrs(struct ip_set *set, struct mtype_elem *d, } ret = 0; unlock: + if (read_seqcount_retry(&nets0->seq, seq0)) + goto retry; +#if IPSET_NET_COUNT == 2 + if (read_seqcount_retry(&nets1->seq, seq1)) + goto retry; +#endif rcu_read_unlock_bh(); return ret; } @@ -1707,6 +1780,7 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set, kfree(rcu_dereference_raw(h->rnets[--i])); goto free_hregion; } + seqcount_spinlock_init(&nets->seq, &set->lock); RCU_INIT_POINTER(h->rnets[i], nets); } #endif diff --git a/net/netfilter/ipset/ip_set_hash_netiface.c b/net/netfilter/ipset/ip_set_hash_netiface.c index b44b95f766b7..b602cc43565d 100644 --- a/net/netfilter/ipset/ip_set_hash_netiface.c +++ b/net/netfilter/ipset/ip_set_hash_netiface.c @@ -38,7 +38,6 @@ MODULE_ALIAS("ip_set_hash:net,iface"); #define HTYPE hash_netiface #define IP_SET_HASH_WITH_NETS #define IP_SET_HASH_WITH_MULTI -#define IP_SET_HASH_WITH_NET0 #define STRSCPY(a, b) strscpy(a, b, IFNAMSIZ) diff --git a/net/netfilter/ipset/ip_set_hash_netportnet.c b/net/netfilter/ipset/ip_set_hash_netportnet.c index 6291532be7a5..61af1ce27127 100644 --- a/net/netfilter/ipset/ip_set_hash_netportnet.c +++ b/net/netfilter/ipset/ip_set_hash_netportnet.c @@ -36,7 +36,6 @@ MODULE_ALIAS("ip_set_hash:net,port,net"); #define IP_SET_HASH_WITH_PROTO #define IP_SET_HASH_WITH_NETS #define IPSET_NET_COUNT 2 -#define IP_SET_HASH_WITH_NET0 /* IPv4 variant */ From 7904b94768e983bcb2be34a8d6d1f3450f5b838b Mon Sep 17 00:00:00 2001 From: Fourie Zhang Date: Mon, 10 Aug 2026 19:35:01 +0800 Subject: [PATCH 0633/1328] netfilter: nf_tables: don't queue packet path object notifications All file:line references below are against v7.2-rc4 (ac5b0e5651b1). The trace was captured on 7.2.0-rc6-kasan72rc6 (075b74841bd0), where the same lines apply. nft_obj_notify() is exported and reached from the packet path. Its only in-tree caller is nft_quota_obj_eval() (net/netfilter/nft_quota.c:68), which notifies with GFP_ATOMIC while evaluating a rule for a transiting packet, holding no mutex. Since commit 67cc570edaa0 ("netfilter: nf_tables: coalesce multiple notifications into one skbuff") that notification is no longer sent immediately. __nft_obj_notify() queues it onto nft_net->notify_list via nft_notify_enqueue() (net/netfilter/nf_tables_api.c:1211), which is a bare list_add_tail(). notify_list has no lock of its own (include/net/netfilter/nf_tables.h:1951), it is serialised by commit_mutex: the six other enqueue sites all run inside a netlink transaction, and the drain in nft_commit_notify() (net/netfilter/nf_tables_api.c:10746) does list_del() + kfree_skb() from nf_tables_commit() with commit_mutex held. Sending packets through a chain that references a depleted quota object therefore races an unlocked list_add_tail() against list_del() + kfree_skb() on another CPU. The WRITE_ONCE(prev->next, new) in __list_add() then stores through an sk_buff that has already been freed: BUG: KASAN: slab-use-after-free in __nft_obj_notify+0x2c5/0x2d0 Write of size 8 at addr ff110001047183c0 by task poc/76 CPU: 0 UID: 1000 PID: 76 Comm: poc Tainted: G W 7.2.0-rc6-kasan72rc6 #4 Call Trace: __nft_obj_notify (include/linux/list.h:164 include/linux/list.h:191 net/netfilter/nf_tables_api.c:1211 net/netfilter/nf_tables_api.c:8743) nft_quota_obj_eval (net/netfilter/nft_quota.c:68) nft_do_chain_inet nf_hook_slow __ip_local_out ip_push_pending_frames udp_send_skb udp_sendmsg __x64_sys_sendto Allocated by task 77: __alloc_skb (net/core/skbuff.c:704) __nft_obj_notify (include/net/netlink.h:1055 net/netfilter/nf_tables_api.c:8731) nft_quota_obj_eval (net/netfilter/nft_quota.c:68) nft_do_chain Freed by task 79: nf_tables_commit (include/linux/skbuff.h:1332 net/netfilter/nf_tables_api.c:10759 net/netfilter/nf_tables_api.c:11185) nfnetlink_rcv_batch (net/netfilter/nfnetlink.c:574) netlink_unicast netlink_sendmsg The buggy address belongs to the cache skbuff_head_cache of size 232 Queueing from the packet path is wrong even leaving the race aside: notify_list is only drained by nft_commit_notify() from nf_tables_commit() (:11185), so a notification enqueued outside a transaction is not sent until some later netlink batch commits, if one ever does. The gfp argument that nft_obj_notify() still takes is a leftover of the pre-67cc570edaa0 behaviour, where this path called nfnetlink_send() directly. Restore that: split the message construction out into nft_obj_notify_alloc() and let each caller decide what to do with the skb. nft_obj_notify(), the exported one reached from the packet path, sends it straight away; nf_tables_obj_notify(), which runs under commit_mutex, keeps queueing it, so transaction notifications are still coalesced. Fixes: 67cc570edaa0 ("netfilter: nf_tables: coalesce multiple notifications into one skbuff") Cc: stable@kernel.org Reported-by: TencentOS Corvus AI Assisted-by: tencentos-corvus-ai:kimi-k3 Signed-off-by: Fourie Zhang Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_tables_api.c | 36 ++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index af357f6c5070..3a7c8f7a6304 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -8715,18 +8715,17 @@ static int nf_tables_delobj(struct sk_buff *skb, const struct nfnl_info *info, return nft_delobj(&ctx, obj); } -static void -__nft_obj_notify(struct net *net, const struct nft_table *table, - struct nft_object *obj, u32 portid, u32 seq, int event, - u16 flags, int family, int report, gfp_t gfp) +static struct sk_buff * +nft_obj_notify_alloc(struct net *net, const struct nft_table *table, + struct nft_object *obj, u32 portid, u32 seq, int event, + u16 flags, int family, int report, gfp_t gfp) { - struct nftables_pernet *nft_net = nft_pernet(net); struct sk_buff *skb; int err; if (!report && !nfnetlink_has_listeners(net, NFNLGRP_NFTABLES)) - return; + return NULL; skb = nlmsg_new(NLMSG_GOODSIZE, gfp); if (skb == NULL) @@ -8740,10 +8739,10 @@ __nft_obj_notify(struct net *net, const struct nft_table *table, goto err; } - nft_notify_enqueue(skb, report, &nft_net->notify_list); - return; + return skb; err: nfnetlink_set_err(net, portid, NFNLGRP_NFTABLES, -ENOBUFS); + return NULL; } void nft_obj_notify(struct net *net, const struct nft_table *table, @@ -8752,6 +8751,7 @@ void nft_obj_notify(struct net *net, const struct nft_table *table, { char *buf = kasprintf(gfp, "%s:%u", table->name, nft_base_seq(net)); + struct sk_buff *skb; audit_log_nfcfg(buf, family, @@ -8762,17 +8762,27 @@ void nft_obj_notify(struct net *net, const struct nft_table *table, gfp); kfree(buf); - __nft_obj_notify(net, table, obj, portid, seq, event, - flags, family, report, gfp); + /* Called from the packet path, holding no mutex: notify_list is + * serialised by commit_mutex, so send this notification directly. + */ + skb = nft_obj_notify_alloc(net, table, obj, portid, seq, event, + flags, family, report, gfp); + if (skb) + nfnetlink_send(skb, net, portid, NFNLGRP_NFTABLES, report, gfp); } EXPORT_SYMBOL_GPL(nft_obj_notify); static void nf_tables_obj_notify(const struct nft_ctx *ctx, struct nft_object *obj, int event) { - __nft_obj_notify(ctx->net, ctx->table, obj, ctx->portid, - ctx->seq, event, ctx->flags, ctx->family, - ctx->report, GFP_KERNEL); + struct nftables_pernet *nft_net = nft_pernet(ctx->net); + struct sk_buff *skb; + + skb = nft_obj_notify_alloc(ctx->net, ctx->table, obj, ctx->portid, + ctx->seq, event, ctx->flags, ctx->family, + ctx->report, GFP_KERNEL); + if (skb) + nft_notify_enqueue(skb, ctx->report, &nft_net->notify_list); } /* From 5fc04d4648f4e699e0df7982e37bcdec23daf39c Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Wed, 12 Aug 2026 10:00:29 +0200 Subject: [PATCH 0634/1328] netfilter: nf_conntrack_expect: consolidate check for insertion of dead expectation Consolidate the check for buggy expectations with DEAD flag on insertion, which is called both by nf_ct_expect_related() and nf_ct_expect_related_pair(). Fixes: e765c95faa10 ("netfilter: nf_conntrack_expect: bail out on insert dead expectations") Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_conntrack_expect.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/net/netfilter/nf_conntrack_expect.c b/net/netfilter/nf_conntrack_expect.c index f1f0c582db5d..06242c86e1dc 100644 --- a/net/netfilter/nf_conntrack_expect.c +++ b/net/netfilter/nf_conntrack_expect.c @@ -477,6 +477,11 @@ static inline int __nf_ct_expect_check(struct nf_conntrack_expect *expect, lockdep_nfct_expect_lock_held(); + if (expect->flags & NF_CT_EXPECT_DEAD) { + DEBUG_NET_WARN_ON_ONCE(1); + return -EINVAL; + } + h = nf_ct_expect_dst_hash(net, &expect->tuple); hlist_for_each_entry_safe(i, next, &nf_ct_expect_hash[h], hnode) { if (nf_ct_exp_is_expired(i)) { @@ -528,12 +533,6 @@ int nf_ct_expect_related_report(struct nf_conntrack_expect *expect, int ret; spin_lock_bh(&nf_conntrack_expect_lock); - if (expect->flags & NF_CT_EXPECT_DEAD) { - DEBUG_NET_WARN_ON_ONCE(1); - ret = -EINVAL; - goto out; - } - master_help = nfct_help(expect->master); if (!master_help) { ret = -ESHUTDOWN; From 322371b09058ad10d0282a0e7ec8eaa764e6aaac Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Wed, 12 Aug 2026 10:03:38 +0200 Subject: [PATCH 0635/1328] netfilter: ctnetlink: do not expose expectation DEAD flag Expose expectation flags included in the NF_CT_EXPECT_MASK bitmask only. The DEAD flag is internal, do not expose it. Fixes: b8b09dc2bf35 ("netfilter: nf_conntrack_expect: use conntrack GC to reap expectations") Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_conntrack_netlink.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c index fc3f60099af3..9b4e29557ec3 100644 --- a/net/netfilter/nf_conntrack_netlink.c +++ b/net/netfilter/nf_conntrack_netlink.c @@ -3042,7 +3042,7 @@ ctnetlink_exp_dump_expect(struct sk_buff *skb, #endif if (nla_put_be32(skb, CTA_EXPECT_TIMEOUT, htonl(timeout)) || nla_put_be32(skb, CTA_EXPECT_ID, nf_expect_get_id(exp)) || - nla_put_be32(skb, CTA_EXPECT_FLAGS, htonl(exp->flags)) || + nla_put_be32(skb, CTA_EXPECT_FLAGS, htonl(exp->flags & NF_CT_EXPECT_MASK)) || nla_put_be32(skb, CTA_EXPECT_CLASS, htonl(exp->class))) goto nla_put_failure; From b343ededb3f961dd44b16d148b7ada697fe80c95 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Thu, 13 Aug 2026 15:00:35 +0200 Subject: [PATCH 0636/1328] netfilter: nf_tables: move set_update_list to nftables per-netns This list is used to invoke the set .commit and .abort ops for the rbtree and pipapo to run GC on expired elements and replace the current datastructure view by the clone. For the rbtree, this also rebuild the datapath b-search array. From abort path, remove the set from the update_list if it is already bound to rule, then the rule itself takes care of releasing the set and its elements, otherwise, memleak is possible because set ops .abort only deals with removing the set data structure, not the elements. This is a preparation patch to call set .commit before processing the transaction list for the rbtree, no functional changes are intended. Signed-off-by: Pablo Neira Ayuso --- include/net/netfilter/nf_tables.h | 1 + net/netfilter/nf_tables_api.c | 49 ++++++++++--------------------- 2 files changed, 16 insertions(+), 34 deletions(-) diff --git a/include/net/netfilter/nf_tables.h b/include/net/netfilter/nf_tables.h index 3be612145c13..238f6ecb90e9 100644 --- a/include/net/netfilter/nf_tables.h +++ b/include/net/netfilter/nf_tables.h @@ -1949,6 +1949,7 @@ struct nftables_pernet { struct list_head binding_list; struct list_head module_list; struct list_head notify_list; + struct list_head set_update_list; struct mutex commit_mutex; u64 table_handle; u64 tstamp; diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index 3a7c8f7a6304..b51ba77b5151 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -595,10 +595,15 @@ static void nft_trans_commit_list_add_tail(struct net *net, struct nft_trans *tr static void nft_trans_commit_list_add_elem(struct net *net, struct nft_trans *trans) { struct nftables_pernet *nft_net = nft_pernet(net); + struct nft_trans_elem *te; WARN_ON_ONCE(trans->msg_type != NFT_MSG_NEWSETELEM && trans->msg_type != NFT_MSG_DELSETELEM); + te = nft_trans_container_elem(trans); + if (te->set->ops->commit && list_empty(&te->set->pending_update)) + list_add_tail(&te->set->pending_update, &nft_net->set_update_list); + if (nft_trans_try_collapse(nft_net, trans)) { kfree(trans); return; @@ -10858,11 +10863,11 @@ static void nf_tables_commit_audit_log(struct list_head *adl, u32 generation) } } -static void nft_set_commit_update(struct list_head *set_update_list) +static void nft_set_commit_update(struct nftables_pernet *nft_net) { struct nft_set *set, *next; - list_for_each_entry_safe(set, next, set_update_list, pending_update) { + list_for_each_entry_safe(set, next, &nft_net->set_update_list, pending_update) { list_del_init(&set->pending_update); if (!set->ops->commit || set->dead) @@ -10895,7 +10900,6 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb) struct nft_trans_binding *trans_binding; struct nft_trans *trans, *next; unsigned int base_seq, gc_seq; - LIST_HEAD(set_update_list); struct nft_trans_elem *te; struct nft_chain *chain; struct nft_table *table; @@ -11101,27 +11105,13 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb) break; case NFT_MSG_NEWSETELEM: te = nft_trans_container_elem(trans); - nft_trans_elems_add(&ctx, te); - - if (te->set->ops->commit && - list_empty(&te->set->pending_update)) { - list_add_tail(&te->set->pending_update, - &set_update_list); - } nft_trans_destroy(trans); break; case NFT_MSG_DELSETELEM: case NFT_MSG_DESTROYSETELEM: te = nft_trans_container_elem(trans); - nft_trans_elems_remove(&ctx, te); - - if (te->set->ops->commit && - list_empty(&te->set->pending_update)) { - list_add_tail(&te->set->pending_update, - &set_update_list); - } break; case NFT_MSG_NEWOBJ: if (nft_trans_obj_update(trans)) { @@ -11190,7 +11180,7 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb) } } - nft_set_commit_update(&set_update_list); + nft_set_commit_update(nft_net); nft_commit_notify(net, NETLINK_CB(skb).portid); nf_tables_gen_notify(net, skb, NFT_MSG_NEWGEN); @@ -11257,11 +11247,11 @@ static void nf_tables_abort_release(struct nft_trans *trans) kfree(trans); } -static void nft_set_abort_update(struct list_head *set_update_list) +static void nft_set_abort_update(struct nftables_pernet *nft_net) { struct nft_set *set, *next; - list_for_each_entry_safe(set, next, set_update_list, pending_update) { + list_for_each_entry_safe(set, next, &nft_net->set_update_list, pending_update) { list_del_init(&set->pending_update); if (!set->ops->abort) @@ -11396,33 +11386,22 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) nft_trans_destroy(trans); break; case NFT_MSG_NEWSETELEM: + te = nft_trans_container_elem(trans); if (nft_trans_elem_set_bound(trans)) { + list_del_init(&te->set->pending_update); nft_trans_destroy(trans); break; } - te = nft_trans_container_elem(trans); if (!nft_trans_elems_new_abort(&ctx, te)) { nft_trans_destroy(trans); break; } - - if (te->set->ops->abort && - list_empty(&te->set->pending_update)) { - list_add_tail(&te->set->pending_update, - &set_update_list); - } break; case NFT_MSG_DELSETELEM: case NFT_MSG_DESTROYSETELEM: te = nft_trans_container_elem(trans); nft_trans_elems_destroy_abort(&ctx, te); - - if (te->set->ops->abort && - list_empty(&te->set->pending_update)) { - list_add_tail(&te->set->pending_update, - &set_update_list); - } nft_trans_destroy(trans); break; case NFT_MSG_NEWOBJ: @@ -11468,7 +11447,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) WARN_ON_ONCE(!list_empty(&nft_net->commit_set_list)); - nft_set_abort_update(&set_update_list); + nft_set_abort_update(nft_net); synchronize_rcu(); @@ -12152,6 +12131,7 @@ static int __net_init nf_tables_init_net(struct net *net) INIT_LIST_HEAD(&nft_net->binding_list); INIT_LIST_HEAD(&nft_net->module_list); INIT_LIST_HEAD(&nft_net->notify_list); + INIT_LIST_HEAD(&nft_net->set_update_list); mutex_init(&nft_net->commit_mutex); net->nft.base_seq = 1; nft_net->gc_seq = 0; @@ -12196,6 +12176,7 @@ static void __net_exit nf_tables_exit_net(struct net *net) WARN_ON_ONCE(!list_empty(&nft_net->module_list)); WARN_ON_ONCE(!list_empty(&nft_net->notify_list)); WARN_ON_ONCE(!list_empty(&nft_net->destroy_list)); + WARN_ON_ONCE(!list_empty(&nft_net->set_update_list)); } static void nf_tables_exit_batch(struct list_head *net_exit_list) From 1e3b9e1c77fe262c6999c50f6f23c20f96faf5ce Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Thu, 13 Aug 2026 15:00:36 +0200 Subject: [PATCH 0637/1328] netfilter: nf_tables: call set ops .commit when building new ruleset blob The rbtree set only builds the b-search array after the new ruleset has been published through set ops .commit. This exposes an empty set for a short time span which results in a bogus mismatch for the following batch: destroy table ip x table ip x { ... } The same problem also affects the pipapo set backend which also provides a set ops .commit interface too. This patch moves the set ops .commit call right before building and publishing the chain blob. The commit path now performs an early handling of the DELSETELEM command to remove stale elements from the clone before it is published via rcu. Note that DELSETELEM notifications are still delivered in order. NEWSETELEM commands are handled after the set is published, since this clears the previous genbit to 1 to prepare the element for the next control plane transaction. This comes at the cost of one extra iteration over the transaction list. Suggested-by: Florian Westphal Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_tables_api.c | 62 ++++++++++++++++++++++++++++------- 1 file changed, 51 insertions(+), 11 deletions(-) diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index b51ba77b5151..c112ecc4fca3 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -7191,7 +7191,30 @@ static void nft_setelem_remove(const struct net *net, } static void nft_trans_elems_remove(const struct nft_ctx *ctx, - const struct nft_trans_elem *te) + const struct nft_trans_elem *te, + bool notify) +{ + int i; + + for (i = 0; i < te->nelems; i++) { + WARN_ON_ONCE(te->elems[i].update); + + if (notify) { + nf_tables_setelem_notify(ctx, te->set, + te->elems[i].priv, + te->nft_trans.msg_type); + } + + nft_setelem_remove(ctx->net, te->set, te->elems[i].priv); + if (!nft_setelem_is_catchall(te->set, te->elems[i].priv)) { + atomic_dec(&te->set->nelems); + te->set->ndeact--; + } + } +} + +static void nft_trans_elems_remove_notify(const struct nft_ctx *ctx, + const struct nft_trans_elem *te) { int i; @@ -7201,12 +7224,6 @@ static void nft_trans_elems_remove(const struct nft_ctx *ctx, nf_tables_setelem_notify(ctx, te->set, te->elems[i].priv, te->nft_trans.msg_type); - - nft_setelem_remove(ctx->net, te->set, te->elems[i].priv); - if (!nft_setelem_is_catchall(te->set, te->elems[i].priv)) { - atomic_dec(&te->set->nelems); - te->set->ndeact--; - } } } @@ -10863,9 +10880,29 @@ static void nf_tables_commit_audit_log(struct list_head *adl, u32 generation) } } -static void nft_set_commit_update(struct nftables_pernet *nft_net) +static void nft_set_commit_update(struct nft_ctx *ctx, + struct nftables_pernet *nft_net) { struct nft_set *set, *next; + struct nft_trans_elem *te; + struct nft_trans *trans; + + if (list_empty(&nft_net->set_update_list)) + return; + + list_for_each_entry(trans, &nft_net->commit_list, list) { + nft_ctx_update(ctx, trans); + + switch (trans->msg_type) { + case NFT_MSG_DELSETELEM: + te = nft_trans_container_elem(trans); + if (!te->set->ops->commit) + break; + + nft_trans_elems_remove(ctx, te, false); + break; + } + } list_for_each_entry_safe(set, next, &nft_net->set_update_list, pending_update) { list_del_init(&set->pending_update); @@ -10974,6 +11011,8 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb) } /* step 2. Make rules_gen_X visible to packet path */ + nft_set_commit_update(&ctx, nft_net); + list_for_each_entry(table, &nft_net->tables, list) { list_for_each_entry(chain, &table->chains, list) nf_tables_commit_chain(net, chain); @@ -11111,7 +11150,10 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb) case NFT_MSG_DELSETELEM: case NFT_MSG_DESTROYSETELEM: te = nft_trans_container_elem(trans); - nft_trans_elems_remove(&ctx, te); + if (te->set->ops->commit) + nft_trans_elems_remove_notify(&ctx, te); + else + nft_trans_elems_remove(&ctx, te, true); break; case NFT_MSG_NEWOBJ: if (nft_trans_obj_update(trans)) { @@ -11180,8 +11222,6 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb) } } - nft_set_commit_update(nft_net); - nft_commit_notify(net, NETLINK_CB(skb).portid); nf_tables_gen_notify(net, skb, NFT_MSG_NEWGEN); nf_tables_commit_audit_log(&adl, nft_base_seq(net)); From e625a9477d12baaff4025c5f9989184a907ea8fc Mon Sep 17 00:00:00 2001 From: Joas Antonio dos Santos Date: Thu, 13 Aug 2026 15:34:24 -0300 Subject: [PATCH 0638/1328] ipvs: fix integer overflow in ftp helper port/address parsing ip_vs_ftp_get_addrport() accumulates decimal digits into a __u16 (hport) and into unsigned char (p[]) without checking for overflow. A crafted FTP PASV/EPSV response with an over-long port or address octet wraps the value, so the helper configures the data connection with a truncated port/address. The netfilter conntrack FTP helper had the same defect, fixed in commit 2b413fc689ba ("netfilter: nf_conntrack_ftp: avoid u16 overflows"). Apply the equivalent fix here: widen the port accumulator to u32 and reject values above 65535, and reject address octets above 255. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Joas Antonio dos Santos Acked-by: Julian Anastasov Signed-off-by: Pablo Neira Ayuso --- net/netfilter/ipvs/ip_vs_ftp.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/net/netfilter/ipvs/ip_vs_ftp.c b/net/netfilter/ipvs/ip_vs_ftp.c index b315c608fda4..9e3e005a8263 100644 --- a/net/netfilter/ipvs/ip_vs_ftp.c +++ b/net/netfilter/ipvs/ip_vs_ftp.c @@ -102,7 +102,7 @@ static int ip_vs_ftp_get_addrport(char *data, char *data_limit, char *s, c; unsigned char p[6]; char edelim; - __u16 hport; + __u32 hport; int i = 0; if (data_limit - data < plen) { @@ -144,7 +144,11 @@ static int ip_vs_ftp_get_addrport(char *data, char *data_limit, return -1; c = *data; if (isdigit(c)) { - p[i] = p[i]*10 + c - '0'; + unsigned int val = p[i] * 10 + c - '0'; + + if (val > 255) + return -1; + p[i] = val; } else if (c == ',' && i < 5) { i++; p[i] = 0; @@ -222,6 +226,8 @@ static int ip_vs_ftp_get_addrport(char *data, char *data_limit, if (!isdigit(*s)) break; hport = hport * 10 + *s - '0'; + if (hport > 65535) + return -1; } if (s == data_limit || !hport || *s != edelim) return -1; From 4e9442ce551ebd84b52ad649df721e2dc28af95a Mon Sep 17 00:00:00 2001 From: Zihan Xi Date: Mon, 17 Aug 2026 19:09:56 +0000 Subject: [PATCH 0639/1328] xfrm: bound nat keepalive state collection The v1 nat keepalive fix allocates a GFP_ATOMIC object for every state while collecting references for phase two. This makes the worker's temporary memory use depend on the number of states and lets -ENOMEM abort the scan. Replace the allocated list with a fixed-size batch. When the batch is full, return a private walk status so xfrm_state_walk() leaves a cursor; drain the references after the walk releases xfrm_state_lock and resume from the cursor. This bounds temporary memory use and avoids the allocation failure path. The v1 fix also moved nat_keepalive_send() out of the walk callback. Keep the phase-two drain BH-disabled, as required by local_lock_nested_bh() used by the keepalive sockets. Fixes: 763fe700b7c5 ("xfrm: avoid lock inversion in nat keepalive work") Cc: stable@vger.kernel.org Cc: Eyal Birger Reported-by: Vega Assisted-by: Codex:gpt-5.4 Signed-off-by: Zihan Xi Signed-off-by: Steffen Klassert --- net/xfrm/xfrm_nat_keepalive.c | 48 ++++++++++++++++------------------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/net/xfrm/xfrm_nat_keepalive.c b/net/xfrm/xfrm_nat_keepalive.c index 8679c68c10a1..5cd6d43164db 100644 --- a/net/xfrm/xfrm_nat_keepalive.c +++ b/net/xfrm/xfrm_nat_keepalive.c @@ -155,32 +155,30 @@ static void nat_keepalive_send(struct nat_keepalive *ka) } } -struct nat_keepalive_work_ctx { - struct list_head states; - time64_t next_run; - time64_t now; +enum { + NAT_KEEPALIVE_BATCH_SIZE = 16, + NAT_KEEPALIVE_BATCH_FULL = 1, }; -struct nat_keepalive_state { - struct list_head list; - struct xfrm_state *x; +struct nat_keepalive_work_ctx { + struct xfrm_state *batch[NAT_KEEPALIVE_BATCH_SIZE]; + unsigned int nr; + time64_t next_run; + time64_t now; }; static int nat_keepalive_work_collect(struct xfrm_state *x, int count, void *ptr) { struct nat_keepalive_work_ctx *ctx = ptr; - struct nat_keepalive_state *state; if (!READ_ONCE(x->nat_keepalive_interval)) return 0; - state = kmalloc_obj(*state, GFP_ATOMIC); - if (!state) - return -ENOMEM; + if (ctx->nr == ARRAY_SIZE(ctx->batch)) + return NAT_KEEPALIVE_BATCH_FULL; xfrm_state_hold(x); - state->x = x; - list_add_tail(&state->list, &ctx->states); + ctx->batch[ctx->nr++] = x; return 0; } @@ -226,29 +224,27 @@ static void nat_keepalive_work_single(struct xfrm_state *x, static void nat_keepalive_work(struct work_struct *work) { - struct nat_keepalive_state *state, *tmp; struct nat_keepalive_work_ctx ctx; struct xfrm_state_walk walk; struct net *net; - int err; + int err, i; - INIT_LIST_HEAD(&ctx.states); ctx.next_run = 0; ctx.now = ktime_get_real_seconds(); net = container_of(work, struct net, xfrm.nat_keepalive_work.work); xfrm_state_walk_init(&walk, IPPROTO_ESP, NULL); - err = xfrm_state_walk(net, &walk, nat_keepalive_work_collect, &ctx); + do { + ctx.nr = 0; + err = xfrm_state_walk(net, &walk, nat_keepalive_work_collect, &ctx); + local_bh_disable(); + for (i = 0; i < ctx.nr; i++) { + nat_keepalive_work_single(ctx.batch[i], &ctx); + xfrm_state_put(ctx.batch[i]); + } + local_bh_enable(); + } while (err == NAT_KEEPALIVE_BATCH_FULL); xfrm_state_walk_done(&walk, net); - list_for_each_entry_safe(state, tmp, &ctx.states, list) { - nat_keepalive_work_single(state->x, &ctx); - xfrm_state_put(state->x); - kfree(state); - } - if (err == -ENOMEM) { - schedule_delayed_work(&net->xfrm.nat_keepalive_work, 0); - return; - } if (ctx.next_run) schedule_delayed_work(&net->xfrm.nat_keepalive_work, (ctx.next_run - ctx.now) * HZ); From cebe359188013aa54e3afd06cc6461c6ebbfb6d3 Mon Sep 17 00:00:00 2001 From: Jishnu Prakash Date: Tue, 11 Aug 2026 16:54:26 +0200 Subject: [PATCH 0640/1328] thermal/drivers/qcom: Fix missing spmi adc tm5 gen3 file Add missing file resulting from a manual application of the change below after fixing a conflict in the Makefile. Fixes: 948ee3a74f35 ("thermal/drivers/qcom: add support for PMIC5 Gen3 ADC thermal monitoring") Signed-off-by: Jishnu Prakash Signed-off-by: Daniel Lezcano Reviewed-by: Dmitry Baryshkov Link: https://patch.msgid.link/20260811145427.3089426-1-daniel.lezcano@kernel.org --- drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c | 434 ++++++++++++++++++ 1 file changed, 434 insertions(+) create mode 100644 drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c diff --git a/drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c b/drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c new file mode 100644 index 000000000000..9cf552d36868 --- /dev/null +++ b/drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c @@ -0,0 +1,434 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../thermal_hwmon.h" + +#define ADC_TM5_GEN3_CONFIG_REGS 12 + +struct device; +struct adc_tm5_gen3_chip; + +/** + * struct adc_tm5_gen3_channel_props - ADC_TM channel structure + * @common_props: structure with common ADC channel properties. + * @chip: ADC TM device. + * @tzd: pointer to thermal device corresponding to TM channel. + * @sdam_index: SDAM on which this TM channel lies. + * @timer: time period of recurring TM measurement. + * @tm_chan_index: TM channel number used. + * @high_thr_en: TM high threshold crossing detection enabled. + * @low_thr_en: TM low threshold crossing detection enabled. + */ +struct adc_tm5_gen3_channel_props { + struct adc5_channel_common_prop common_props; + struct adc_tm5_gen3_chip *chip; + struct thermal_zone_device *tzd; + unsigned int sdam_index; + unsigned int timer; + unsigned int tm_chan_index; + bool high_thr_en; + bool low_thr_en; +}; + +/** + * struct adc_tm5_gen3_chip - ADC Thermal Monitoring device structure + * @dev_data: Top-level ADC device data. + * @chan_props: Array of ADC_TM channel structures. + * @dev: SPMI ADC5 Gen3 device. + * @nchannels: number of TM channels allocated + */ +struct adc_tm5_gen3_chip { + struct adc5_device_data *dev_data; + struct adc_tm5_gen3_channel_props *chan_props; + struct device *dev; + unsigned int nchannels; +}; + +DEFINE_GUARD(adc5_gen3, struct adc_tm5_gen3_chip *, + adc5_gen3_mutex_lock(_T->dev), adc5_gen3_mutex_unlock(_T->dev)) + +static int get_sdam_from_irq(struct adc_tm5_gen3_chip *adc_tm5, int irq) +{ + for (int i = 0; i < adc_tm5->dev_data->num_sdams; i++) { + if (adc_tm5->dev_data->base[i].irq == irq) + return i; + } + return -ENOENT; +} + +static irqreturn_t adctm5_gen3_isr(int irq, void *dev_id) +{ + struct adc_tm5_gen3_chip *adc_tm5 = dev_id; + int ret, sdam_num; + u8 tm_status[2]; + u8 status, val; + + sdam_num = get_sdam_from_irq(adc_tm5, irq); + if (sdam_num < 0) + return IRQ_NONE; + + ret = adc5_gen3_read(adc_tm5->dev_data, sdam_num, ADC5_GEN3_STATUS1, + &status, sizeof(status)); + if (ret) + return IRQ_NONE; + + if (status & ADC5_GEN3_STATUS1_CONV_FAULT) { + val = ADC5_GEN3_CONV_ERR_CLR_REQ; + adc5_gen3_status_clear(adc_tm5->dev_data, sdam_num, + ADC5_GEN3_CONV_ERR_CLR, &val, 1); + return IRQ_HANDLED; + } + + ret = adc5_gen3_read(adc_tm5->dev_data, sdam_num, ADC5_GEN3_TM_HIGH_STS, + tm_status, sizeof(tm_status)); + if (ret) + return IRQ_NONE; + + if (tm_status[0] || tm_status[1]) + return IRQ_WAKE_THREAD; + + return IRQ_NONE; +} + +static irqreturn_t adctm5_gen3_isr_thread(int irq, void *dev_id) +{ + struct adc_tm5_gen3_chip *adc_tm5 = dev_id; + u8 tm_status[2]; + int sdam_index; + + sdam_index = get_sdam_from_irq(adc_tm5, irq); + if (sdam_index < 0) + return IRQ_NONE; + + scoped_guard(adc5_gen3, adc_tm5) { + int ret; + + ret = adc5_gen3_read(adc_tm5->dev_data, sdam_index, ADC5_GEN3_TM_HIGH_STS, + tm_status, sizeof(tm_status)); + if (ret) + return IRQ_NONE; + + ret = adc5_gen3_status_clear(adc_tm5->dev_data, sdam_index, + ADC5_GEN3_TM_HIGH_STS_CLR, tm_status, + sizeof(tm_status)); + if (ret) + return IRQ_NONE; + } + + for (int i = 0; i < adc_tm5->nchannels; i++) { + struct adc_tm5_gen3_channel_props *chan_prop = &adc_tm5->chan_props[i]; + int offset = chan_prop->tm_chan_index; + bool upper_set, lower_set; + + if (chan_prop->sdam_index != sdam_index) + continue; + + upper_set = ((tm_status[0] & BIT(offset)) && chan_prop->high_thr_en); + lower_set = ((tm_status[1] & BIT(offset)) && chan_prop->low_thr_en); + + if (!(upper_set || lower_set)) + continue; + + thermal_zone_device_update(chan_prop->tzd, THERMAL_TRIP_VIOLATED); + } + + return IRQ_HANDLED; +} + +static int adc_tm5_gen3_get_temp(struct thermal_zone_device *tz, int *temp) +{ + struct adc_tm5_gen3_channel_props *prop = thermal_zone_device_priv(tz); + struct adc_tm5_gen3_chip *adc_tm5; + + if (!prop || !prop->chip) + return -EINVAL; + + adc_tm5 = prop->chip; + + return adc5_gen3_get_scaled_reading(adc_tm5->dev, &prop->common_props, temp); +} + +static int adc_tm5_gen3_disable_channel(struct adc_tm5_gen3_channel_props *prop) +{ + struct adc_tm5_gen3_chip *adc_tm5 = prop->chip; + int ret; + u8 val; + + prop->high_thr_en = false; + prop->low_thr_en = false; + + ret = adc5_gen3_poll_wait_hs(adc_tm5->dev_data, prop->sdam_index); + if (ret) + return ret; + + val = BIT(prop->tm_chan_index); + ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, + ADC5_GEN3_TM_HIGH_STS_CLR, &val, sizeof(val)); + if (ret) + return ret; + + ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, + ADC5_GEN3_TM_LOW_STS_CLR, &val, sizeof(val)); + if (ret) + return ret; + + val = MEAS_INT_DISABLE; + ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, + ADC5_GEN3_TIMER_SEL, &val, sizeof(val)); + if (ret) + return ret; + + /* To indicate there is an actual conversion request */ + val = ADC5_GEN3_CHAN_CONV_REQ | prop->tm_chan_index; + ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, + ADC5_GEN3_PERPH_CH, &val, sizeof(val)); + if (ret) + return ret; + + val = ADC5_GEN3_CONV_REQ_REQ; + return adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, + ADC5_GEN3_CONV_REQ, &val, sizeof(val)); +} + +static int adc_tm5_gen3_configure(struct adc_tm5_gen3_channel_props *prop, + int low_temp, int high_temp) +{ + struct adc_tm5_gen3_chip *adc_tm5 = prop->chip; + u8 buf[ADC_TM5_GEN3_CONFIG_REGS]; + u8 conv_req; + u16 adc_code; + int ret; + + ret = adc5_gen3_poll_wait_hs(adc_tm5->dev_data, prop->sdam_index); + if (ret < 0) + return ret; + + ret = adc5_gen3_read(adc_tm5->dev_data, prop->sdam_index, + ADC5_GEN3_SID, buf, sizeof(buf)); + if (ret < 0) + return ret; + + /* Write SID */ + buf[0] = FIELD_PREP(ADC5_GEN3_SID_MASK, prop->common_props.sid); + + /* Select TM channel and indicate there is an actual conversion request */ + buf[1] = ADC5_GEN3_CHAN_CONV_REQ | prop->tm_chan_index; + + buf[2] = prop->timer; + + /* Digital param selection */ + adc5_gen3_update_dig_param(&prop->common_props, &buf[3]); + + /* Update fast average sample value */ + buf[4] = FIELD_PREP(ADC5_GEN3_FAST_AVG_CTL_SAMPLES_MASK, + prop->common_props.avg_samples) | ADC5_GEN3_FAST_AVG_CTL_EN; + + /* Select ADC channel */ + buf[5] = prop->common_props.channel; + + /* Select HW settle delay for channel */ + buf[6] = FIELD_PREP(ADC5_GEN3_HW_SETTLE_DELAY_MASK, + prop->common_props.hw_settle_time_us); + + buf[7] = 0; + + /* High temperature corresponds to low voltage threshold */ + prop->low_thr_en = (high_temp != INT_MAX); + if (prop->low_thr_en) { + adc_code = qcom_adc_tm5_gen2_temp_res_scale(high_temp); + put_unaligned_le16(adc_code, &buf[8]); + buf[7] |= ADC5_GEN3_LOW_THR_INT_EN; + } + + /* Low temperature corresponds to high voltage threshold */ + prop->high_thr_en = (low_temp != -INT_MAX); + if (prop->high_thr_en) { + adc_code = qcom_adc_tm5_gen2_temp_res_scale(low_temp); + put_unaligned_le16(adc_code, &buf[10]); + buf[7] |= ADC5_GEN3_HIGH_THR_INT_EN; + } + + ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, ADC5_GEN3_SID, + buf, sizeof(buf)); + if (ret < 0) + return ret; + + conv_req = ADC5_GEN3_CONV_REQ_REQ; + return adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, + ADC5_GEN3_CONV_REQ, &conv_req, sizeof(conv_req)); +} + +static int adc_tm5_gen3_set_trip_temp(struct thermal_zone_device *tz, + int low_temp, int high_temp) +{ + struct adc_tm5_gen3_channel_props *prop = thermal_zone_device_priv(tz); + struct adc_tm5_gen3_chip *adc_tm5; + + if (!prop || !prop->chip) + return -EINVAL; + + adc_tm5 = prop->chip; + + dev_dbg(adc_tm5->dev, "channel:%s, low_temp(mdegC):%d, high_temp(mdegC):%d\n", + prop->common_props.label, low_temp, high_temp); + + guard(adc5_gen3)(adc_tm5); + + return adc_tm5_gen3_configure(prop, low_temp, high_temp); +} + +static const struct thermal_zone_device_ops adc_tm_ops = { + .get_temp = adc_tm5_gen3_get_temp, + .set_trips = adc_tm5_gen3_set_trip_temp, +}; + +static int adc_tm5_register_tzd(struct adc_tm5_gen3_chip *adc_tm5) +{ + struct thermal_zone_device *tzd; + unsigned int channel; + int ret; + + for (int i = 0; i < adc_tm5->nchannels; i++) { + channel = ADC5_GEN3_V_CHAN(adc_tm5->chan_props[i].common_props); + tzd = devm_thermal_of_zone_register(adc_tm5->dev, channel, + &adc_tm5->chan_props[i], + &adc_tm_ops); + if (IS_ERR(tzd)) { + if (PTR_ERR(tzd) == -ENODEV) { + dev_dbg(adc_tm5->dev, + "thermal sensor on channel %d is not used\n", + channel); + continue; + } + return PTR_ERR(tzd); + } + adc_tm5->chan_props[i].tzd = tzd; + ret = devm_thermal_add_hwmon_sysfs(adc_tm5->dev, tzd); + if (ret) + return ret; + } + + return 0; +} + +static void adc5_gen3_disable(void *data) +{ + struct adc_tm5_gen3_chip *adc_tm5 = data; + + guard(adc5_gen3)(adc_tm5); + + /* Disable all available TM channels */ + for (int i = 0; i < adc_tm5->nchannels; i++) + adc_tm5_gen3_disable_channel(&adc_tm5->chan_props[i]); +} + +static int adc_tm5_probe(struct auxiliary_device *aux_dev, + const struct auxiliary_device_id *id) +{ + struct adc_tm5_gen3_chip *adc_tm5; + struct tm5_aux_dev_wrapper *aux_dev_wrapper; + struct device *dev = &aux_dev->dev; + int ret; + + adc_tm5 = devm_kzalloc(dev, sizeof(*adc_tm5), GFP_KERNEL); + if (!adc_tm5) + return -ENOMEM; + + aux_dev_wrapper = container_of(aux_dev, struct tm5_aux_dev_wrapper, aux_dev); + + adc_tm5->dev = dev; + adc_tm5->dev_data = aux_dev_wrapper->dev_data; + adc_tm5->nchannels = aux_dev_wrapper->n_tm_channels; + adc_tm5->chan_props = devm_kcalloc(dev, aux_dev_wrapper->n_tm_channels, + sizeof(*adc_tm5->chan_props), GFP_KERNEL); + if (!adc_tm5->chan_props) + return -ENOMEM; + + for (int i = 0; i < adc_tm5->nchannels; i++) { + /* + * Since the first channel of the first SDAM is reserved for + * immediate ADC conversions, TM channel count must start from + * the channel just after it. The variable tm_count is used to + * calculate SDAM and TM channel index on that SDAM correctly + * for each TM channel. + */ + int tm_count = i + 1; + + adc_tm5->chan_props[i].common_props = aux_dev_wrapper->tm_props[i]; + adc_tm5->chan_props[i].timer = MEAS_INT_1S; + adc_tm5->chan_props[i].sdam_index = tm_count / 8; + adc_tm5->chan_props[i].tm_chan_index = tm_count % 8; + adc_tm5->chan_props[i].chip = adc_tm5; + } + + /* + * ADC_TM channels are enabled in the loop in adc_tm5_register_tzd() as + * part of the set_trips calls during thermal zone registration. This + * action is to disable them all in case of probe failure. + */ + ret = devm_add_action(dev, adc5_gen3_disable, adc_tm5); + if (ret) + return ret; + + ret = adc_tm5_register_tzd(adc_tm5); + if (ret) + return ret; + + for (int i = 0; i < adc_tm5->dev_data->num_sdams; i++) { + u32 irq_flags = IRQF_ONESHOT; + + /* + * First SDAM's interrupt is shared between main ADC driver and + * auxiliary TM driver, so its flags must include IRQF_SHARED. + * This is not needed for other SDAMs as they will be used only + * for TM functionality. + */ + if (i == 0) + irq_flags |= IRQF_SHARED; + + ret = devm_request_threaded_irq(dev, + adc_tm5->dev_data->base[i].irq, + adctm5_gen3_isr, + adctm5_gen3_isr_thread, + irq_flags, + adc_tm5->dev_data->base[i].irq_name, + adc_tm5); + if (ret < 0) + return ret; + } + + return 0; +} + +static const struct auxiliary_device_id adctm5_auxiliary_id_table[] = { + { .name = "qcom_spmi_adc5_gen3.adc5_tm_gen3" }, + { } +}; +MODULE_DEVICE_TABLE(auxiliary, adctm5_auxiliary_id_table); + +static struct auxiliary_driver adctm5gen3_auxiliary_driver = { + .id_table = adctm5_auxiliary_id_table, + .probe = adc_tm5_probe, +}; +module_auxiliary_driver(adctm5gen3_auxiliary_driver); + +MODULE_DESCRIPTION("SPMI PMIC Thermal Monitor ADC driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("QCOM_SPMI_ADC5_GEN3"); From c5f7c0d35c552cbaf1f0630a9731a04f7fe03948 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Tue, 11 Aug 2026 11:49:35 +0200 Subject: [PATCH 0641/1328] thermal/drivers/qcom/spm mbg tm: Fix missing bitfield header Add missing bitfield header leading to the error: >> drivers/thermal/qcom/qcom-spmi-mbg-tm.c:184:21: error: implicit declaration of function 'FIELD_GET' [-Wimplicit-function-declaration] 184 | if (FIELD_GET(MON_FAULT_STATUS_MASK, val) == MON_FAULT_LVL1_UPR) | ^~~~~~~~~ Fixes: c3dce117333c ("thermal/drivers/qcom: Add support for Qualcomm MBG thermal monitoring") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202608080800.RfxKb9uR-lkp@intel.com/ Signed-off-by: Daniel Lezcano Reviewed-by: Dmitry Baryshkov Link: https://patch.msgid.link/20260811094935.2941313-1-daniel.lezcano@kernel.org --- drivers/thermal/qcom/qcom-spmi-mbg-tm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thermal/qcom/qcom-spmi-mbg-tm.c b/drivers/thermal/qcom/qcom-spmi-mbg-tm.c index fa2f10002253..0492d5eeca01 100644 --- a/drivers/thermal/qcom/qcom-spmi-mbg-tm.c +++ b/drivers/thermal/qcom/qcom-spmi-mbg-tm.c @@ -2,7 +2,7 @@ /* * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ - +#include #include #include #include From b115930d716defc3a7daa2bc2ae2465d864b7114 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Tue, 11 Aug 2026 11:47:47 +0200 Subject: [PATCH 0642/1328] thermal/drivers/armada: Fix missing bitfields include MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the missing include leading to the error: error: implicit declaration of function ‘FIELD_GET’ [-Werror=implicit-function-declaration] 184 | if (FIELD_GET(MON_FAULT_STATUS_MASK, val) == MON_FAULT_LVL1_UPR) | ^~~~~~~~~ cc1: all warnings being treated as errors Fixes: cbe31d5ce498 ("thermal/drivers/armada: Use bitfield and bitmask macros") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202608082242.drjXuzsN-lkp@intel.com/ Signed-off-by: Daniel Lezcano Reviewed-by: Miquel Raynal Link: https://patch.msgid.link/20260811094747.2940616-1-daniel.lezcano@kernel.org --- drivers/thermal/armada_thermal.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/thermal/armada_thermal.c b/drivers/thermal/armada_thermal.c index 912c68a44bdc..ff63b4f2b977 100644 --- a/drivers/thermal/armada_thermal.c +++ b/drivers/thermal/armada_thermal.c @@ -4,6 +4,7 @@ * * Copyright (C) 2013 Marvell */ +#include #include #include #include From dd0ae9a2e19a24c532c9aa80a44f572b4907bcdf Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Tue, 18 Aug 2026 01:46:29 +0000 Subject: [PATCH 0643/1328] ASoC: codec: max98926: fixup invert setup mask invert might have both WCI/BCI mask, but regmap_update_bits() masks BCI only. It needs both mask. Fixup it. static int max98926_dai_set_fmt(...) { ... switch (fmt & SND_SOC_DAIFMT_INV_MASK) { case SND_SOC_DAIFMT_NB_NF: break; case SND_SOC_DAIFMT_NB_IF: => invert = MAX98926_DAI_WCI_MASK; break; ^^^^^^^^^^^^^^^^^^^^^ case SND_SOC_DAIFMT_IB_NF: => invert = MAX98926_DAI_BCI_MASK; break; ^^^^^^^^^^^^^^^^^^^^^ case SND_SOC_DAIFMT_IB_IF: => invert = MAX98926_DAI_BCI_MASK | MAX98926_DAI_WCI_MASK; break; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ default: ... } ... regmap_update_bits(max98926->regmap, MAX98926_FORMAT, MAX98926_DAI_BCI_MASK, invert); ... ^^^^^^^^^^^^^^^^^^^^^ } Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87wltomc97.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/max98926.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/max98926.c b/sound/soc/codecs/max98926.c index 5305e1f9d97f..51a30c182167 100644 --- a/sound/soc/codecs/max98926.c +++ b/sound/soc/codecs/max98926.c @@ -360,7 +360,7 @@ static int max98926_dai_set_fmt(struct snd_soc_dai *codec_dai, regmap_write(max98926->regmap, MAX98926_FORMAT, MAX98926_DAI_DLY_MASK); regmap_update_bits(max98926->regmap, MAX98926_FORMAT, - MAX98926_DAI_BCI_MASK, invert); + MAX98926_DAI_BCI_MASK | MAX98926_DAI_WCI_MASK, invert); return 0; } From 046173b98de316211b81d9909cde81ca582604a8 Mon Sep 17 00:00:00 2001 From: Jack Yu Date: Tue, 18 Aug 2026 10:30:18 +0800 Subject: [PATCH 0644/1328] ASoC: sdw_utils: fix double put_device() on aggregated amps In aggregation mode with two identical amplifiers, a stress test that repeatedly triggers card unbind/rebind can hit a NULL pointer dereference during the exit path. ctx->amp_dev1 / ctx->amp_dev2 are single shared resources, so the release must be safe against being called more than once. Clear each pointer after put_device() so a second invocation becomes a no-op, this could address NULL pointer dereference issue. Signed-off-by: Jack Yu Link: https://patch.msgid.link/20260818023018.2564212-1-jack.yu@realtek.com Signed-off-by: Mark Brown --- sound/soc/sdw_utils/soc_sdw_rt_amp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/sdw_utils/soc_sdw_rt_amp.c b/sound/soc/sdw_utils/soc_sdw_rt_amp.c index 4e9b08cb653d..81d2cbac0ea3 100644 --- a/sound/soc/sdw_utils/soc_sdw_rt_amp.c +++ b/sound/soc/sdw_utils/soc_sdw_rt_amp.c @@ -252,11 +252,13 @@ int asoc_sdw_rt_amp_exit(struct snd_soc_card *card, struct snd_soc_dai_link *dai if (ctx->amp_dev1) { device_remove_software_node(ctx->amp_dev1); put_device(ctx->amp_dev1); + ctx->amp_dev1 = NULL; } if (ctx->amp_dev2) { device_remove_software_node(ctx->amp_dev2); put_device(ctx->amp_dev2); + ctx->amp_dev2 = NULL; } return 0; From ed98f8e27a93dae225a99ac41ad48f967fd19842 Mon Sep 17 00:00:00 2001 From: Michal Clapinski Date: Mon, 10 Aug 2026 09:48:51 +0200 Subject: [PATCH 0645/1328] ipmi:si: Add async init to ipmi_si Added a new config option to allow offloading individual calls to try_smi_init() using workqueue. Saves 100ms on my system. Signed-off-by: Michal Clapinski Message-ID: <20260810074851.306979-1-mclapinski@google.com> Signed-off-by: Corey Minyard --- drivers/char/ipmi/Kconfig | 9 ++++ drivers/char/ipmi/ipmi_si_intf.c | 79 +++++++++++++++++++++++++------- 2 files changed, 72 insertions(+), 16 deletions(-) diff --git a/drivers/char/ipmi/Kconfig b/drivers/char/ipmi/Kconfig index 669f76000197..538a7d3c65bf 100644 --- a/drivers/char/ipmi/Kconfig +++ b/drivers/char/ipmi/Kconfig @@ -67,6 +67,15 @@ config IPMI_SI Currently, only KCS and SMIC are supported. If you are using IPMI, you should probably say "y" here. +config IPMI_SI_ASYNC_INIT + bool 'Asynchronous initialization of IPMI System Interface' + depends on IPMI_SI + default n + help + Offloads individual SMI inits. It speeds up the boot time. + It also introduces a very small risk that something else might fail + if it depends on synchronous IPMI init. + config IPMI_SSIF tristate 'IPMI SMBus handler (SSIF)' depends on I2C diff --git a/drivers/char/ipmi/ipmi_si_intf.c b/drivers/char/ipmi/ipmi_si_intf.c index 9a9d12be9bf7..6b95a7581328 100644 --- a/drivers/char/ipmi/ipmi_si_intf.c +++ b/drivers/char/ipmi/ipmi_si_intf.c @@ -39,6 +39,7 @@ #include #include #include +#include #include "ipmi_si.h" #include "ipmi_si_sm.h" #include @@ -252,6 +253,8 @@ struct smi_info { struct task_struct *thread; + struct work_struct init_work; + struct list_head link; }; @@ -272,6 +275,7 @@ static bool unload_when_empty = true; static int try_smi_init(struct smi_info *smi); static void cleanup_one_si(struct smi_info *smi_info); static void cleanup_ipmi_si(void); +static void smi_init_work_fn(struct work_struct *work); #ifdef DEBUG_TIMING void debug_timestamp(struct smi_info *smi_info, char *msg) @@ -1970,6 +1974,7 @@ int ipmi_si_add_smi(struct si_sm_io *io) if (!new_smi) return -ENOMEM; spin_lock_init(&new_smi->si_lock); + INIT_WORK(&new_smi->init_work, smi_init_work_fn); new_smi->io = *io; @@ -1982,7 +1987,12 @@ int ipmi_si_add_smi(struct si_sm_io *io) dev_info(dup->io.dev, "Removing SMBIOS-specified %s state machine in favor of ACPI\n", si_to_str[new_smi->io.si_info->type]); + list_del(&dup->link); + mutex_unlock(&smi_infos_lock); + cleanup_one_si(dup); + + mutex_lock(&smi_infos_lock); } else { dev_info(new_smi->io.dev, "%s-specified %s state machine: duplicate\n", @@ -2000,8 +2010,12 @@ int ipmi_si_add_smi(struct si_sm_io *io) list_add_tail(&new_smi->link, &smi_infos); - if (initialized) - rv = try_smi_init(new_smi); + if (initialized) { + if (IS_ENABLED(CONFIG_IPMI_SI_ASYNC_INIT)) + queue_work(system_dfl_wq, &new_smi->init_work); + else + rv = try_smi_init(new_smi); + } out_err: mutex_unlock(&smi_infos_lock); return rv; @@ -2174,6 +2188,15 @@ static bool __init ipmi_smi_info_same(struct smi_info *e1, struct smi_info *e2) e1->io.addr_data == e2->io.addr_data); } +static void smi_init_work_fn(struct work_struct *work) +{ + struct smi_info *smi = container_of(work, struct smi_info, init_work); + + mutex_lock(&smi_infos_lock); + try_smi_init(smi); + mutex_unlock(&smi_infos_lock); +} + static int __init init_ipmi_si(void) { struct smi_info *e, *e2; @@ -2219,8 +2242,12 @@ static int __init init_ipmi_si(void) break; } } - if (!dup) - try_smi_init(e); + if (!dup) { + if (IS_ENABLED(CONFIG_IPMI_SI_ASYNC_INIT)) + queue_work(system_unbound_wq, &e->init_work); + else + try_smi_init(e); + } } /* @@ -2253,8 +2280,12 @@ static int __init init_ipmi_si(void) break; } } - if (!dup) - try_smi_init(e); + if (!dup) { + if (IS_ENABLED(CONFIG_IPMI_SI_ASYNC_INIT)) + queue_work(system_unbound_wq, &e->init_work); + else + try_smi_init(e); + } } initialized = true; @@ -2344,31 +2375,36 @@ static void shutdown_smi(void *send_info) } /* - * Must be called with smi_infos_lock held, to serialize the - * smi_info->intf check. + * Must be called with smi_info unlinked from smi_infos and smi_infos_lock released. */ static void cleanup_one_si(struct smi_info *smi_info) { if (!smi_info) return; - list_del(&smi_info->link); + if (IS_ENABLED(CONFIG_IPMI_SI_ASYNC_INIT)) + cancel_work_sync(&smi_info->init_work); + ipmi_unregister_smi(smi_info->intf); kfree(smi_info); } void ipmi_si_remove_by_dev(struct device *dev) { - struct smi_info *e; + struct smi_info *e = NULL, *tmp; mutex_lock(&smi_infos_lock); - list_for_each_entry(e, &smi_infos, link) { - if (e->io.dev == dev) { - cleanup_one_si(e); + list_for_each_entry(tmp, &smi_infos, link) { + if (tmp->io.dev == dev) { + e = tmp; + list_del(&e->link); break; } } mutex_unlock(&smi_infos_lock); + + if (e) + cleanup_one_si(e); } struct device *ipmi_si_remove_by_data(int addr_space, enum si_type si_type, @@ -2377,6 +2413,7 @@ struct device *ipmi_si_remove_by_data(int addr_space, enum si_type si_type, /* remove */ struct smi_info *e, *tmp_e; struct device *dev = NULL; + LIST_HEAD(to_clean); mutex_lock(&smi_infos_lock); list_for_each_entry_safe(e, tmp_e, &smi_infos, link) { @@ -2386,17 +2423,23 @@ struct device *ipmi_si_remove_by_data(int addr_space, enum si_type si_type, continue; if (e->io.addr_data == addr) { dev = get_device(e->io.dev); - cleanup_one_si(e); + list_move_tail(&e->link, &to_clean); } } mutex_unlock(&smi_infos_lock); + list_for_each_entry_safe(e, tmp_e, &to_clean, link) { + list_del(&e->link); + cleanup_one_si(e); + } + return dev; } static void cleanup_ipmi_si(void) { struct smi_info *e, *tmp_e; + LIST_HEAD(to_clean); if (!initialized) return; @@ -2410,10 +2453,14 @@ static void cleanup_ipmi_si(void) ipmi_si_platform_shutdown(); mutex_lock(&smi_infos_lock); - list_for_each_entry_safe(e, tmp_e, &smi_infos, link) - cleanup_one_si(e); + list_splice_init(&smi_infos, &to_clean); mutex_unlock(&smi_infos_lock); + list_for_each_entry_safe(e, tmp_e, &to_clean, link) { + list_del(&e->link); + cleanup_one_si(e); + } + ipmi_si_hardcode_exit(); ipmi_si_hotmod_exit(); } From c41ac86802fc0a22a886915a43bcad2e8d482b02 Mon Sep 17 00:00:00 2001 From: Prasad Kumpatla Date: Tue, 18 Aug 2026 14:16:52 +0530 Subject: [PATCH 0646/1328] ASoC: dt-bindings: qcom,q6dsp-lpass-ports: Rename QAIF clock IDs The QAIF clock IDs were introduced with a LAPSS prefix typo. Rename them to use the existing LPASS naming convention and update all corresponding in-tree users. Since these IDs are new, rename them directly instead of keeping backward-compatible aliases. Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260804070307.117119-1-prasad.kumpatla@oss.qualcomm.com Signed-off-by: Prasad Kumpatla Link: https://patch.msgid.link/20260818084655.3240284-2-prasad.kumpatla@oss.qualcomm.com Signed-off-by: Mark Brown --- .../sound/qcom,q6dsp-lpass-ports.h | 56 +++++++++---------- sound/soc/qcom/qdsp6/q6prm-clocks.c | 56 +++++++++---------- sound/soc/qcom/qdsp6/q6prm.h | 56 +++++++++---------- 3 files changed, 84 insertions(+), 84 deletions(-) diff --git a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h index 2e879a3c09ae..3a99703dbc4a 100644 --- a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h +++ b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h @@ -237,34 +237,34 @@ /* Clock ID for RX CORE MCLK2 2X MCLK */ #define LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK 70 -#define LAPSS_CLK_ID_QAIF_IF0_IBIT 71 -#define LAPSS_CLK_ID_QAIF_IF0_EBIT 72 -#define LAPSS_CLK_ID_QAIF_IF1_IBIT 73 -#define LAPSS_CLK_ID_QAIF_IF1_EBIT 74 -#define LAPSS_CLK_ID_QAIF_IF2_IBIT 75 -#define LAPSS_CLK_ID_QAIF_IF2_EBIT 76 -#define LAPSS_CLK_ID_QAIF_IF3_IBIT 77 -#define LAPSS_CLK_ID_QAIF_IF3_EBIT 78 -#define LAPSS_CLK_ID_QAIF_IF4_IBIT 79 -#define LAPSS_CLK_ID_QAIF_IF4_EBIT 80 -#define LAPSS_CLK_ID_QAIF_IF5_IBIT 81 -#define LAPSS_CLK_ID_QAIF_IF5_EBIT 82 -#define LAPSS_CLK_ID_QAIF_IF6_IBIT 83 -#define LAPSS_CLK_ID_QAIF_IF6_EBIT 84 -#define LAPSS_CLK_ID_QAIF_IF7_IBIT 85 -#define LAPSS_CLK_ID_QAIF_IF7_EBIT 86 -#define LAPSS_CLK_ID_QAIF_IF8_IBIT 87 -#define LAPSS_CLK_ID_QAIF_IF8_EBIT 88 -#define LAPSS_CLK_ID_QAIF_IF9_IBIT 89 -#define LAPSS_CLK_ID_QAIF_IF9_EBIT 90 -#define LAPSS_CLK_ID_QAIF_IF10_IBIT 91 -#define LAPSS_CLK_ID_QAIF_IF10_EBIT 92 -#define LAPSS_CLK_ID_QAIF_IF11_IBIT 93 -#define LAPSS_CLK_ID_QAIF_IF11_EBIT 94 -#define LAPSS_CLK_ID_QAIF_IF12_IBIT 95 -#define LAPSS_CLK_ID_QAIF_IF12_EBIT 96 -#define LAPSS_CLK_ID_VA_QAIF_IF0_IBIT 97 -#define LAPSS_CLK_ID_VA_QAIF_IF0_EBIT 98 +#define LPASS_CLK_ID_QAIF_IF0_IBIT 71 +#define LPASS_CLK_ID_QAIF_IF0_EBIT 72 +#define LPASS_CLK_ID_QAIF_IF1_IBIT 73 +#define LPASS_CLK_ID_QAIF_IF1_EBIT 74 +#define LPASS_CLK_ID_QAIF_IF2_IBIT 75 +#define LPASS_CLK_ID_QAIF_IF2_EBIT 76 +#define LPASS_CLK_ID_QAIF_IF3_IBIT 77 +#define LPASS_CLK_ID_QAIF_IF3_EBIT 78 +#define LPASS_CLK_ID_QAIF_IF4_IBIT 79 +#define LPASS_CLK_ID_QAIF_IF4_EBIT 80 +#define LPASS_CLK_ID_QAIF_IF5_IBIT 81 +#define LPASS_CLK_ID_QAIF_IF5_EBIT 82 +#define LPASS_CLK_ID_QAIF_IF6_IBIT 83 +#define LPASS_CLK_ID_QAIF_IF6_EBIT 84 +#define LPASS_CLK_ID_QAIF_IF7_IBIT 85 +#define LPASS_CLK_ID_QAIF_IF7_EBIT 86 +#define LPASS_CLK_ID_QAIF_IF8_IBIT 87 +#define LPASS_CLK_ID_QAIF_IF8_EBIT 88 +#define LPASS_CLK_ID_QAIF_IF9_IBIT 89 +#define LPASS_CLK_ID_QAIF_IF9_EBIT 90 +#define LPASS_CLK_ID_QAIF_IF10_IBIT 91 +#define LPASS_CLK_ID_QAIF_IF10_EBIT 92 +#define LPASS_CLK_ID_QAIF_IF11_IBIT 93 +#define LPASS_CLK_ID_QAIF_IF11_EBIT 94 +#define LPASS_CLK_ID_QAIF_IF12_IBIT 95 +#define LPASS_CLK_ID_QAIF_IF12_EBIT 96 +#define LPASS_CLK_ID_VA_QAIF_IF0_IBIT 97 +#define LPASS_CLK_ID_VA_QAIF_IF0_EBIT 98 #define LPASS_HW_AVTIMER_VOTE 101 #define LPASS_HW_MACRO_VOTE 102 diff --git a/sound/soc/qcom/qdsp6/q6prm-clocks.c b/sound/soc/qcom/qdsp6/q6prm-clocks.c index f613e2aee75e..b4f9abbe02cb 100644 --- a/sound/soc/qcom/qdsp6/q6prm-clocks.c +++ b/sound/soc/qcom/qdsp6/q6prm-clocks.c @@ -64,34 +64,34 @@ static const struct q6dsp_clk_init q6prm_clks[] = { Q6PRM_CLK(LPASS_CLK_ID_WSA2_CORE_TX_MCLK), Q6PRM_CLK(LPASS_CLK_ID_WSA2_CORE_TX_2X_MCLK), Q6PRM_CLK(LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF0_IBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF0_EBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF1_IBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF1_EBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF2_IBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF2_EBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF3_IBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF3_EBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF4_IBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF4_EBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF5_IBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF5_EBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF6_IBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF6_EBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF7_IBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF7_EBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF8_IBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF8_EBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF9_IBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF9_EBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF10_IBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF10_EBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF11_IBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF11_EBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF12_IBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF12_EBIT), - Q6PRM_CLK(LAPSS_CLK_ID_VA_QAIF_IF0_IBIT), - Q6PRM_CLK(LAPSS_CLK_ID_VA_QAIF_IF0_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF0_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF0_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF1_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF1_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF2_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF2_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF3_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF3_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF4_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF4_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF5_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF5_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF6_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF6_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF7_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF7_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF8_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF8_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF9_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF9_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF10_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF10_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF11_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF11_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF12_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF12_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_VA_QAIF_IF0_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_VA_QAIF_IF0_EBIT), Q6DSP_VOTE_CLK(LPASS_HW_MACRO_VOTE, Q6PRM_HW_CORE_ID_LPASS, "LPASS_HW_MACRO"), Q6DSP_VOTE_CLK(LPASS_HW_DCODEC_VOTE, Q6PRM_HW_CORE_ID_DCODEC, diff --git a/sound/soc/qcom/qdsp6/q6prm.h b/sound/soc/qcom/qdsp6/q6prm.h index cca77cd92bc1..cf619ef6561a 100644 --- a/sound/soc/qcom/qdsp6/q6prm.h +++ b/sound/soc/qcom/qdsp6/q6prm.h @@ -97,34 +97,34 @@ /* Clock ID for RX CORE MCLK2 2X MCLK */ #define Q6PRM_LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK 0x318 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF0_IBIT 0x500 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF0_EBIT 0x501 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF1_IBIT 0x502 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF1_EBIT 0x503 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF2_IBIT 0x504 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF2_EBIT 0x505 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF3_IBIT 0x506 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF3_EBIT 0x507 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF4_IBIT 0x508 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF4_EBIT 0x509 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF5_IBIT 0x50A -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF5_EBIT 0x50B -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF6_IBIT 0x50C -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF6_EBIT 0x50D -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF7_IBIT 0x50E -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF7_EBIT 0x50F -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF8_IBIT 0x510 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF8_EBIT 0x511 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF9_IBIT 0x512 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF9_EBIT 0x513 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF10_IBIT 0x514 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF10_EBIT 0x515 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF11_IBIT 0x516 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF11_EBIT 0x517 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF12_IBIT 0x518 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF12_EBIT 0x519 -#define Q6PRM_LAPSS_CLK_ID_VA_QAIF_IF0_IBIT 0x550 -#define Q6PRM_LAPSS_CLK_ID_VA_QAIF_IF0_EBIT 0x551 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF0_IBIT 0x500 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF0_EBIT 0x501 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF1_IBIT 0x502 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF1_EBIT 0x503 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF2_IBIT 0x504 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF2_EBIT 0x505 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF3_IBIT 0x506 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF3_EBIT 0x507 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF4_IBIT 0x508 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF4_EBIT 0x509 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF5_IBIT 0x50A +#define Q6PRM_LPASS_CLK_ID_QAIF_IF5_EBIT 0x50B +#define Q6PRM_LPASS_CLK_ID_QAIF_IF6_IBIT 0x50C +#define Q6PRM_LPASS_CLK_ID_QAIF_IF6_EBIT 0x50D +#define Q6PRM_LPASS_CLK_ID_QAIF_IF7_IBIT 0x50E +#define Q6PRM_LPASS_CLK_ID_QAIF_IF7_EBIT 0x50F +#define Q6PRM_LPASS_CLK_ID_QAIF_IF8_IBIT 0x510 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF8_EBIT 0x511 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF9_IBIT 0x512 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF9_EBIT 0x513 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF10_IBIT 0x514 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF10_EBIT 0x515 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF11_IBIT 0x516 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF11_EBIT 0x517 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF12_IBIT 0x518 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF12_EBIT 0x519 +#define Q6PRM_LPASS_CLK_ID_VA_QAIF_IF0_IBIT 0x550 +#define Q6PRM_LPASS_CLK_ID_VA_QAIF_IF0_EBIT 0x551 #define Q6PRM_LPASS_CLK_SRC_INTERNAL 1 #define Q6PRM_LPASS_CLK_ROOT_DEFAULT 0 From 9c9fb79f9769ba9042086817ee57aa4eef8b8a97 Mon Sep 17 00:00:00 2001 From: Prasad Kumpatla Date: Tue, 18 Aug 2026 14:16:53 +0530 Subject: [PATCH 0647/1328] ASoC: qcom: qdsp6: Zero-initialize AudioReach module config q6apm_dai_prepare() and q6apm_dai_compr_set_params() build an AudioReach module config on the stack and populate only the fields needed for the stream type. Audio IF media-format setup treats zero slot_mask, nslots_per_frame and slot_width as a request to use the topology defaults. Leave those fields uninitialized and random stack data can override the topology values sent to the DSP. Initialize the whole config structure so omitted fields reliably retain their zero/default meaning. Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260804070307.117119-1-prasad.kumpatla@oss.qualcomm.com Signed-off-by: Prasad Kumpatla Link: https://patch.msgid.link/20260818084655.3240284-3-prasad.kumpatla@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/q6apm-dai.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/qcom/qdsp6/q6apm-dai.c b/sound/soc/qcom/qdsp6/q6apm-dai.c index bf1f872a09f4..033197bdd870 100644 --- a/sound/soc/qcom/qdsp6/q6apm-dai.c +++ b/sound/soc/qcom/qdsp6/q6apm-dai.c @@ -216,7 +216,7 @@ static int q6apm_dai_prepare(struct snd_soc_component *component, { struct snd_pcm_runtime *runtime = substream->runtime; struct q6apm_dai_rtd *prtd = runtime->private_data; - struct audioreach_module_config cfg; + struct audioreach_module_config cfg = {}; struct device *dev = component->dev; struct q6apm_dai_data *pdata; int ret; @@ -814,7 +814,7 @@ static int q6apm_dai_compr_set_params(struct snd_soc_component *component, struct snd_compr_runtime *runtime = stream->runtime; struct q6apm_dai_rtd *prtd = runtime->private_data; struct q6apm_dai_data *pdata; - struct audioreach_module_config cfg; + struct audioreach_module_config cfg = {}; struct snd_codec *codec = ¶ms->codec; int dir = stream->direction; int ret; From 0a9e00d5ebdfcf460902f463e765f737d3fe935e Mon Sep 17 00:00:00 2001 From: Prasad Kumpatla Date: Tue, 18 Aug 2026 14:16:54 +0530 Subject: [PATCH 0648/1328] ASoC: qcom: common: Distinguish missing and invalid TDM slot configuration qcom_snd_parse_dai_tdm_slots() uses -EINVAL for both missing DAI-link TDM configuration and malformed TDM slot properties. As a result, qcom_snd_apply_dai_tdm_slots() silently ignores configuration errors. Return -ENOENT for missing DAI-link configuration and preserve -EINVAL for malformed TDM slot properties. Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260804070307.117119-1-prasad.kumpatla@oss.qualcomm.com Signed-off-by: Prasad Kumpatla Link: https://patch.msgid.link/20260818084655.3240284-4-prasad.kumpatla@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/qcom/common.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sound/soc/qcom/common.c b/sound/soc/qcom/common.c index d231024206db..d9f256d51973 100644 --- a/sound/soc/qcom/common.c +++ b/sound/soc/qcom/common.c @@ -103,14 +103,14 @@ static int qcom_snd_parse_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd, int ret; if (!link_np) - return -EINVAL; + return -ENOENT; struct device_node *cpu_np __free(device_node) = of_get_child_by_name(link_np, "cpu"); struct device_node *codec_np __free(device_node) = of_get_child_by_name(link_np, "codec"); if (!cpu_np || !codec_np) - return -EINVAL; + return -ENOENT; ret = qcom_snd_parse_tdm_slot(cpu_np, cpu_cfg); if (ret) @@ -172,7 +172,7 @@ int qcom_snd_apply_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd) ret = qcom_snd_get_dai_tdm_slots(rtd, &cpu_cfg, &codec_cfg); if (ret) - return ret == -EINVAL ? 0 : ret; + return ret == -ENOENT ? 0 : ret; return qcom_snd_apply_dai_tdm_slots_cfg(rtd, &cpu_cfg, &codec_cfg); } From 8593dc5f052e791748eaa76397ad95b9e32edac3 Mon Sep 17 00:00:00 2001 From: Prasad Kumpatla Date: Tue, 18 Aug 2026 14:16:55 +0530 Subject: [PATCH 0649/1328] ASoC: qcom: sc8280xp: Fix TDM hw_params error handling Treat -ENOENT from TDM slot parsing as the optional "configuration not present" case and continue to propagate real configuration errors. Also ignore -ENOTSUPP from optional DAI format and codec sysclk callbacks, apply codec_dai_fmt to codec DAIs on TDM links, and use a signed bclk_freq variable so errors from snd_soc_tdm_params_to_bclk() are handled correctly. Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260804070307.117119-1-prasad.kumpatla@oss.qualcomm.com Signed-off-by: Prasad Kumpatla Link: https://patch.msgid.link/20260818084655.3240284-5-prasad.kumpatla@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/qcom/sc8280xp.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/sound/soc/qcom/sc8280xp.c b/sound/soc/qcom/sc8280xp.c index 1322601659d4..4d48e1012cd4 100644 --- a/sound/soc/qcom/sc8280xp.c +++ b/sound/soc/qcom/sc8280xp.c @@ -115,33 +115,42 @@ static int sc8280xp_tdm_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *codec_dai; struct qcom_snd_tdm_slot_cfg cpu_cfg; struct qcom_snd_tdm_slot_cfg codec_cfg; - unsigned int bclk_freq; + int bclk_freq; int ret; int i; ret = qcom_snd_get_dai_tdm_slots(rtd, &cpu_cfg, &codec_cfg); if (ret) - return ret == -EINVAL ? 0 : ret; + return ret == -ENOENT ? 0 : ret; if (!cpu_cfg.slots) return 0; ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP); - if (ret) + if (ret && ret != -ENOTSUPP) return ret; + if (data->priv->codec_dai_fmt) { + for_each_rtd_codec_dais(rtd, i, codec_dai) { + ret = snd_soc_dai_set_fmt(codec_dai, + data->priv->codec_dai_fmt); + if (ret && ret != -ENOTSUPP) + return ret; + } + } + ret = qcom_snd_apply_dai_tdm_slots_cfg(rtd, &cpu_cfg, &codec_cfg); if (ret) return ret; bclk_freq = snd_soc_tdm_params_to_bclk(params, cpu_cfg.slot_width, cpu_cfg.slots, 1); - if (!bclk_freq) + if (bclk_freq <= 0) return -EINVAL; if (data->priv->mi2s_bclk_enable) { ret = snd_soc_dai_set_sysclk(cpu_dai, LPAIF_MI2S_BCLK, bclk_freq, SND_SOC_CLOCK_IN); - if (ret) { + if (ret && ret != -ENOTSUPP) { dev_err(rtd->dev, "%s: failed to set cpu sysclk: %d\n", __func__, ret); return ret; @@ -152,7 +161,7 @@ static int sc8280xp_tdm_hw_params(struct snd_pcm_substream *substream, for_each_rtd_codec_dais(rtd, i, codec_dai) { ret = snd_soc_dai_set_sysclk(codec_dai, 0, bclk_freq, SND_SOC_CLOCK_IN); - if (ret) { + if (ret && ret != -ENOTSUPP) { dev_err(rtd->dev, "%s: failed to set codec sysclk on %s: %d\n", __func__, codec_dai->name, ret); return ret; From 523ee23d4957f3e088e97d1d03ab9a8fc333d72a Mon Sep 17 00:00:00 2001 From: Binbin Zhou Date: Tue, 18 Aug 2026 17:20:52 +0800 Subject: [PATCH 0650/1328] ASoC: loongson: Fix naming of machine event and DAPM widgets Rename `tegra_machine_event` to `loongson_asoc_machine_event` and `loongson_aosc_dapm_widgets` to `loongson_asoc_dapm_widgets` to accurately reflect the hardware platform. Update all references accordingly. No functional change is introduced. Signed-off-by: Binbin Zhou Reviewed-by: Keguang Zhang Reviewed-by: Huacai Chen Link: https://patch.msgid.link/20260818092052.3596347-1-zhoubinbin@loongson.cn Signed-off-by: Mark Brown --- sound/soc/loongson/loongson_card.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/sound/soc/loongson/loongson_card.c b/sound/soc/loongson/loongson_card.c index 25cd12eab4b1..6422cc1703b6 100644 --- a/sound/soc/loongson/loongson_card.c +++ b/sound/soc/loongson/loongson_card.c @@ -55,8 +55,8 @@ static const struct loongson_card_config ls2k0300_dl2k0300b_card_config = { .add_dapm_routes = true, }; -static int tegra_machine_event(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *k, int event) +static int loongson_asoc_machine_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) { struct snd_soc_card *card = snd_soc_dapm_to_card(w->dapm); struct loongson_card_data *priv = snd_soc_card_get_drvdata(card); @@ -72,9 +72,9 @@ static int tegra_machine_event(struct snd_soc_dapm_widget *w, return 0; } -static const struct snd_soc_dapm_widget loongson_aosc_dapm_widgets[] = { - SND_SOC_DAPM_HP("Headphone", tegra_machine_event), - SND_SOC_DAPM_SPK("Speaker", tegra_machine_event), +static const struct snd_soc_dapm_widget loongson_asoc_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone", loongson_asoc_machine_event), + SND_SOC_DAPM_SPK("Speaker", loongson_asoc_machine_event), }; /* Headphones Jack */ @@ -314,8 +314,8 @@ static int loongson_asoc_card_probe(struct platform_device *pdev) card->num_links = ARRAY_SIZE(loongson_dai_links); if (ls_priv->cfg->add_dapm_widgets) { - card->dapm_widgets = loongson_aosc_dapm_widgets; - card->num_dapm_widgets = ARRAY_SIZE(loongson_aosc_dapm_widgets); + card->dapm_widgets = loongson_asoc_dapm_widgets; + card->num_dapm_widgets = ARRAY_SIZE(loongson_asoc_dapm_widgets); } snd_soc_card_set_drvdata(card, ls_priv); From ae84a2536577057e97f23f75a202e26d0e86cf01 Mon Sep 17 00:00:00 2001 From: Corey Minyard Date: Tue, 18 Aug 2026 12:49:52 -0500 Subject: [PATCH 0651/1328] ipmi:msghandler: Cancel work cleanly on an error If an error occurs during startup of an IPMI interface, it may have scheduled work to run. The work needs to be canceled before the interface can be freed. Reported-by: Nilay Shroff Closes: https://sourceforge.net/p/openipmi/mailman/message/59375605/ Fixes: 62cd145453d5 ("ipmi:msghandler: Handle error returns from the SMI sender") Cc: stable@vger.kernel.org # 7.0 Tested-by: Nilay Shroff Signed-off-by: Corey Minyard --- drivers/char/ipmi/ipmi_msghandler.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/char/ipmi/ipmi_msghandler.c b/drivers/char/ipmi/ipmi_msghandler.c index 7634dff99f41..c73e9def59a6 100644 --- a/drivers/char/ipmi/ipmi_msghandler.c +++ b/drivers/char/ipmi/ipmi_msghandler.c @@ -3766,6 +3766,7 @@ int ipmi_add_smi(struct module *owner, list_del(&intf->link); mutex_unlock(&ipmi_interfaces_mutex); mutex_unlock(&smi_watchers_mutex); + cancel_work_sync(&intf->smi_work); kref_put(&intf->refcount, intf_free); return rv; From 6c97817e20598e5473094e0e38d1f51f1cf4dfff Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Tue, 18 Aug 2026 22:47:17 +0800 Subject: [PATCH 0652/1328] ALSA: pcxhr: initialize mutexes before requesting threaded IRQ pcxhr_probe() requests pcxhr_threaded_irq() before initializing mgr->lock, even though the threaded handler takes that mutex. Initialize the manager locks before request_threaded_irq() so an early interrupt cannot run against uninitialized mutex state during probe. Fixes: 9bef72bdb26e ("ALSA: pcxhr: Use nonatomic PCM ops") Cc: stable@vger.kernel.org Signed-off-by: Runyu Xiao Link: https://patch.msgid.link/20260818144717.2269918-1-runyu.xiao@seu.edu.cn Signed-off-by: Takashi Iwai --- sound/pci/pcxhr/pcxhr.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/sound/pci/pcxhr/pcxhr.c b/sound/pci/pcxhr/pcxhr.c index 25a640304430..60cc0721d0a6 100644 --- a/sound/pci/pcxhr/pcxhr.c +++ b/sound/pci/pcxhr/pcxhr.c @@ -1535,6 +1535,13 @@ static int pcxhr_probe(struct pci_dev *pci, mgr->pci = pci; mgr->irq = -1; + /* ISR lock */ + mutex_init(&mgr->lock); + mutex_init(&mgr->msg_lock); + + /* init setup mutex*/ + mutex_init(&mgr->setup_mutex); + if (request_threaded_irq(pci->irq, pcxhr_interrupt, pcxhr_threaded_irq, IRQF_SHARED, KBUILD_MODNAME, mgr)) { @@ -1548,13 +1555,6 @@ static int pcxhr_probe(struct pci_dev *pci, "Digigram at 0x%lx & 0x%lx, 0x%lx irq %i", mgr->port[0], mgr->port[1], mgr->port[2], mgr->irq); - /* ISR lock */ - mutex_init(&mgr->lock); - mutex_init(&mgr->msg_lock); - - /* init setup mutex*/ - mutex_init(&mgr->setup_mutex); - mgr->prmh = kmalloc(sizeof(*mgr->prmh) + sizeof(u32) * (PCXHR_SIZE_MAX_LONG_STATUS - PCXHR_SIZE_MAX_STATUS), From 352cef030f9036a658e6eb03d6bb85fa19c7c103 Mon Sep 17 00:00:00 2001 From: Chad Talbott Date: Tue, 18 Aug 2026 14:32:37 -1000 Subject: [PATCH 0653/1328] ALSA: usb-audio: Add MIN_MUTE quirk for Creative Sound Blaster Play! 3 The Creative Sound Blaster Play! 3 (041e:324d) exposes a Feature Unit volume control with a range of -44.00 dB .. 0.00 dB over raw values 0..88, but raw value 0 is in fact a hard mute rather than -44.00 dB. Tested on hardware. Signed-off-by: Chad Talbott Link: https://patch.msgid.link/20260819003237.452807-1-chad.talbott@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index eb1750def067..f4e1eac639b9 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2219,6 +2219,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_MIXER_GET_CUR_BROKEN), DEVICE_FLG(0x041e, 0x3000, /* Creative SB Extigy */ QUIRK_FLAG_IGNORE_CTL_ERROR), + DEVICE_FLG(0x041e, 0x324d, /* Creative Sound Blaster Play! 3 */ + QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), DEVICE_FLG(0x041e, 0x4080, /* Creative Live Cam VF0610 */ QUIRK_FLAG_GET_SAMPLE_RATE), DEVICE_FLG(0x045e, 0x083c, /* MS USB Link headset */ From 9c48a53685040bb0de45a640b34055cbbfc69d4f Mon Sep 17 00:00:00 2001 From: Yi Ding Date: Mon, 1 Jun 2026 20:51:35 -0700 Subject: [PATCH 0654/1328] rtc: pcf8563: fix clock provider leak on unbind pcf8563_clkout_register_clk() registers the CLKOUT clock provider with of_clk_add_provider(), but nothing ever unwinds it: there is no of_clk_del_provider() call and the driver has no remove callback. Each of_clk_add_provider() allocates a struct of_clk_provider, takes a reference on the OF node and adds an entry to the global of_clk_providers list, none of which is released when the device is unbound. Every bind/unbind (or module reload) therefore leaks a provider structure and an of_node reference. The clock itself is already device-managed (devm_clk_register()); only the provider registration was not. Use devm_of_clk_add_hw_provider() so the provider is removed automatically on unbind. Tie it to the parent i2c device, whose OF node carries the #clock-cells and clock-output-names properties (the RTC class device has no OF node of its own). Fixes: a39a6405d5f9 ("rtc: pcf8563: add CLKOUT to common clock framework") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Yi Ding Link: https://patch.msgid.link/20260602035135.62264-1-yi.s.ding@gmail.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-pcf8563.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/rtc/rtc-pcf8563.c b/drivers/rtc/rtc-pcf8563.c index 81d13733b1e9..a90dc940474b 100644 --- a/drivers/rtc/rtc-pcf8563.c +++ b/drivers/rtc/rtc-pcf8563.c @@ -449,7 +449,9 @@ static struct clk *pcf8563_clkout_register_clk(struct pcf8563 *pcf8563) clk = devm_clk_register(&pcf8563->rtc->dev, &pcf8563->clkout_hw); if (!IS_ERR(clk)) - of_clk_add_provider(node, of_clk_src_simple_get, clk); + devm_of_clk_add_hw_provider(pcf8563->rtc->dev.parent, + of_clk_hw_simple_get, + &pcf8563->clkout_hw); return clk; } From c52feb4365396b6a881b5e8a95540517ffabb3b7 Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Wed, 5 Aug 2026 11:44:54 +0800 Subject: [PATCH 0655/1328] drm/amdgpu: Disable runtime PM for externally attached dGPUs pci_is_thunderbolt_attached() requires an upstream PCI bridge with is_thunderbolt set from an Intel Thunderbolt VSEC. This does not cover the affected ASM4242 USB4 PCI hierarchy: 00:02.2 \- 0f:00.0 [1b21:2421] +- 10:01.0 [1b21:2423] -> 45:00.0 -> 46:00.0 | -> 47:00.0 -> 48:00.0 -> 49:00.0 [1002:7590] \- 10:03.0 -> 76:00.0 [1b21:2425] USB4 Host Router The host router is outside the GPU upstream bridge chain, leaving no ancestor with is_thunderbolt set. PCI core propagates DEVICE_REMOVABLE below the external-facing PCIe tunnel. Disable Runtime PM when either pci_is_thunderbolt_attached() or dev_is_removable() is true. Cc: stable@vger.kernel.org Signed-off-by: Yang Wang Reviewed-by: Candice Li Reviewed-by: Kenneth Feng Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index 520fd59036d5..57c8ce814931 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -609,6 +609,13 @@ void amdgpu_device_detect_runtime_pm_mode(struct amdgpu_device *adev) int bamaco_support; adev->pm.rpm_mode = AMDGPU_RUNPM_NONE; + if (pci_is_thunderbolt_attached(adev->pdev) || + dev_is_removable(&adev->pdev->dev)) { + dev_info(adev->dev, + "Runtime PM disabled for externally attached device\n"); + return; + } + bamaco_support = amdgpu_device_supports_baco(adev); switch (amdgpu_runtime_pm) { From ef5fcf2a6c320676bf8be2dadac93d9023b468b7 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Sat, 8 Aug 2026 20:09:34 +0800 Subject: [PATCH 0656/1328] drm/amdgpu: fix autosuspend cleanup during removal amdgpu_pci_probe() calls pm_runtime_use_autosuspend(), but amdgpu_pci_remove() does not call the matching pm_runtime_dont_use_autosuspend(). If the autosuspend delay is set to a negative value while autosuspend is enabled, the runtime PM core increments usage_count to prevent runtime suspend. Without calling pm_runtime_dont_use_autosuspend() during teardown, this reference is not dropped and usage_count remains unbalanced. The documentation for pm_runtime_use_autosuspend() also notes that it is important to undo it with pm_runtime_dont_use_autosuspend() at driver exit time, unless runtime PM was initially enabled with devm_pm_runtime_enable(). Add the missing pm_runtime_dont_use_autosuspend() call to the remove path. This issue was found by manual code inspection. Fixes: d38ceaf99ed0 ("drm/amdgpu: add core driver (v4)") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260808120934.2813010-1-lgs201920130244@gmail.com Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c index 0ab380ca7e64..5c33c19fd9bc 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c @@ -2557,6 +2557,7 @@ amdgpu_pci_remove(struct pci_dev *pdev) if (adev->pm.rpm_mode != AMDGPU_RUNPM_NONE) { pm_runtime_get_sync(dev->dev); pm_runtime_forbid(dev->dev); + pm_runtime_dont_use_autosuspend(dev->dev); } amdgpu_driver_unload_kms(dev); From ffdb7a8104f51d552dea4b319c8ce5169f63e724 Mon Sep 17 00:00:00 2001 From: Zhu Lingshan Date: Wed, 22 Jul 2026 18:13:44 +0800 Subject: [PATCH 0657/1328] drm/amdgpu: ensure all userq VAs mapped before restore amdgpu_userq_buffer_vas_mapped() checks whether all VAs of a queue are mapped before restoring it. So that HW won't access any invalid addresses. Currently, this function assumes all VAs are mapped if any VA of a queue has been mapped, which is wrong. This commit fixes this problem by examining all VAs of a queue and reporting false if any of them is not mapped. Signed-off-by: Zhu Lingshan Reviewed-by: Sunil Khatri Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index bcfbd7213dd6..04639f894903 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -287,22 +287,24 @@ static bool amdgpu_userq_buffer_va_mapped(struct amdgpu_vm *vm, u64 addr) static bool amdgpu_userq_buffer_vas_mapped(struct amdgpu_usermode_queue *queue) { - int i, r = 0; + int i; + bool mapped; for (i = 0; i < ARRAY_SIZE(queue->userq_vas.va_array); i++) { if (!queue->userq_vas.va_array[i]) continue; - r += amdgpu_userq_buffer_va_mapped(queue->vm, + + mapped = amdgpu_userq_buffer_va_mapped(queue->vm, queue->userq_vas.va_array[i]); dev_dbg(queue->userq_mgr->adev->dev, "validate the userq mapping:%p va:%llx r:%d\n", - queue, queue->userq_vas.va_array[i], r); + queue, queue->userq_vas.va_array[i], mapped); + + if (!mapped) + return false; } - if (r != 0) - return true; - - return false; + return true; } From 3438e964c916eb1a51f9f3ac018758e44abe5539 Mon Sep 17 00:00:00 2001 From: Sunil Khatri Date: Thu, 13 Aug 2026 13:59:07 +0530 Subject: [PATCH 0658/1328] drm/amdgpu/userq: ignore duplicate BO locks in userq signal ioctl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit amdgpu_userq_signal_ioctl() calls drm_exec_init() without DRM_EXEC_IGNORE_DUPLICATES. When the same GEM object appears more than once across the read/write BO handle lists submitted by userspace, drm_exec_lock_obj() returns -EALREADY the second time it locks that object, which aborts the ioctl instead of treating the repeat as a no-op. Add DRM_EXEC_IGNORE_DUPLICATES so duplicate objects are silently skipped on the second lock attempt, matching the intended semantics of locking a set of (possibly overlapping) BOs before publishing a fence on them. Signed-off-by: Sunil Khatri Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c index 7e80442ec3e5..cd0bd016a24d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c @@ -537,7 +537,7 @@ int amdgpu_userq_signal_ioctl(struct drm_device *dev, void *data, * amdgpu_userq_ensure_ev_fence() can't be called while holding the resv * locks. */ - drm_exec_init(&exec, DRM_EXEC_INTERRUPTIBLE_WAIT, + drm_exec_init(&exec, DRM_EXEC_INTERRUPTIBLE_WAIT | DRM_EXEC_IGNORE_DUPLICATES, (num_read_bo_handles + num_write_bo_handles)); drm_exec_until_all_locked(&exec) { From 2411e499385c7df58afedb59238817579bc2168b Mon Sep 17 00:00:00 2001 From: Sunil Khatri Date: Thu, 13 Aug 2026 14:02:09 +0530 Subject: [PATCH 0659/1328] drm/amdgpu/userq: ignore duplicate BO locks when counting wait fences MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit amdgpu_userq_wait_count_fences() calls drm_exec_init() without DRM_EXEC_IGNORE_DUPLICATES. When the same GEM object appears more than once across the read/write BO handle lists submitted by userspace, drm_exec_lock_obj() returns -EALREADY the second time it locks that object, which aborts the fence-counting pass instead of treating the repeat as a no-op. Add DRM_EXEC_IGNORE_DUPLICATES so duplicate objects are silently skipped on the second lock attempt. Signed-off-by: Sunil Khatri Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c index cd0bd016a24d..981cb1dd5dda 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c @@ -643,7 +643,7 @@ amdgpu_userq_wait_count_fences(struct drm_file *filp, /* TODO: It is actually not necessary to lock them */ num_read_bo_handles = wait_info->num_bo_read_handles; num_write_bo_handles = wait_info->num_bo_write_handles; - drm_exec_init(&exec, DRM_EXEC_INTERRUPTIBLE_WAIT, + drm_exec_init(&exec, DRM_EXEC_INTERRUPTIBLE_WAIT | DRM_EXEC_IGNORE_DUPLICATES, num_read_bo_handles + num_write_bo_handles); drm_exec_until_all_locked(&exec) { From 5827c7ad72b6826d9b90be15af139dd2287e3ca3 Mon Sep 17 00:00:00 2001 From: Sunil Khatri Date: Thu, 13 Aug 2026 14:02:35 +0530 Subject: [PATCH 0660/1328] drm/amdgpu/userq: ignore duplicate BO locks when returning wait fence info MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit amdgpu_userq_wait_return_fence_info() calls drm_exec_init() without DRM_EXEC_IGNORE_DUPLICATES. When the same GEM object appears more than once across the read/write BO handle lists submitted by userspace, drm_exec_lock_obj() returns -EALREADY the second time it locks that object, which aborts the fence resolution pass instead of treating the repeat as a no-op. Add DRM_EXEC_IGNORE_DUPLICATES so duplicate objects are silently skipped on the second lock attempt. Signed-off-by: Sunil Khatri Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c index 981cb1dd5dda..a33dbe978798 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c @@ -778,7 +778,7 @@ amdgpu_userq_wait_return_fence_info(struct drm_device *dev, struct drm_file *fil /* Lock all the GEM objects */ num_read_bo_handles = wait_info->num_bo_read_handles; num_write_bo_handles = wait_info->num_bo_write_handles; - drm_exec_init(&exec, DRM_EXEC_INTERRUPTIBLE_WAIT, + drm_exec_init(&exec, DRM_EXEC_INTERRUPTIBLE_WAIT | DRM_EXEC_IGNORE_DUPLICATES, num_read_bo_handles + num_write_bo_handles); drm_exec_until_all_locked(&exec) { From 6fd83a1c2cdea48c396f600795217fbdfb8124f6 Mon Sep 17 00:00:00 2001 From: Akhmed Zhitaev Date: Thu, 13 Aug 2026 22:09:59 +0500 Subject: [PATCH 0661/1328] drm/amd/display: Scale custom brightness curve from full range Custom brightness curves use an 8-bit input signal. After exporting the full PWM range to userspace, the curve normalizer still divides requests by the physical PWM span. On panels with a nonzero minimum PWM level, this can produce a curve input greater than 255 and send an invalid backlight level to DC. Scale the userspace [0..max] range to the curve's [0..255] range instead. This retains the full advertised range and keeps the reverse readback conversion unchanged. Fixes: 8dbd72cb7900 ("drm/amd/display: Export full brightness range to userspace") Cc: stable@vger.kernel.org Signed-off-by: Akhmed Zhitaev Reviewed-by: Mario Limonciello (AMD) (Move to amdgpu_dm_backlight.c) Link: https://patch.msgid.link/20260813170959.22073-1-zhitaevakh@gmail.com Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c index b66ca60e697d..e61bbc310f33 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c @@ -106,10 +106,10 @@ int get_brightness_range(const struct amdgpu_dm_backlight_caps *caps, } EXPORT_IF_KUNIT(get_brightness_range); -/* Rescale from [min..max] to [0..AMDGPU_MAX_BL_LEVEL] */ -static inline u32 scale_input_to_fw(int min, int max, u64 input) +/* Rescale userspace [0..max] to the firmware curve's [0..255]. */ +static inline u32 scale_input_to_fw(int max, u64 input) { - return DIV_ROUND_CLOSEST_ULL(input * AMDGPU_MAX_BL_LEVEL, max - min); + return DIV_ROUND_CLOSEST_ULL(input * AMDGPU_MAX_BL_LEVEL, max); } /* Rescale from [0..AMDGPU_MAX_BL_LEVEL] to [min..max] */ @@ -123,7 +123,7 @@ void convert_custom_brightness(const struct amdgpu_dm_backlight_caps *caps, unsigned int min, unsigned int max, uint32_t *user_brightness) { - u32 brightness = scale_input_to_fw(min, max, *user_brightness); + u32 brightness = scale_input_to_fw(max, *user_brightness); u8 lower_signal, upper_signal, upper_lum, lower_lum, lum; int left, right; From 59db985bc99e0589536a2116b93b0a6c45df41ab Mon Sep 17 00:00:00 2001 From: Mukul Joshi Date: Thu, 13 Aug 2026 11:02:24 -0400 Subject: [PATCH 0662/1328] drm/amdgpu: fix sysfs ip base addr for 64bit in standalone mode In standalone mode the ip_discovery sysfs tree is built from a verbatim copy of the discovery binary taken before reg_base_init() collapses the 64bit base addresses in place. Decoding as 32bit there yields interleaved zeros. Decode base_address_64[] in that case; keep reading the already collapsed adev->discovery.bin as-is otherwise. Fixes: 402e04f11ff7 ("drm/amdgpu: Export ip_discovery sysfs on probe failure") Cc: stable@vger.kernel.org Signed-off-by: Mukul Joshi Acked-by: Alex Deucher Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c index a404d8aa13ee..164e85b66e2d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c @@ -1307,8 +1307,19 @@ static int amdgpu_discovery_sysfs_ips(struct amdgpu_device *adev, ip_hw_instance->num_instance); ip_hw_instance->num_base_addresses = ip->num_base_address; - for (kk = 0; kk < ip_hw_instance->num_base_addresses; kk++) - ip_hw_instance->base_addr[kk] = ip->base_address[kk]; + for (kk = 0; kk < ip_hw_instance->num_base_addresses; kk++) { + /* + * Standalone mode uses a raw copy of the discovery + * binary; decode 64-bit addresses here. The shared + * bin is already collapsed to 32-bit in place. + */ + if (reg_base_64 && ip_top->standalone_mode) + ip_hw_instance->base_addr[kk] = + lower_32_bits(le64_to_cpu(ip->base_address_64[kk])) & 0x3FFFFFFF; + else + ip_hw_instance->base_addr[kk] = + le32_to_cpu(ip->base_address[kk]); + } kobject_init(&ip_hw_instance->kobj, &ip_hw_instance_ktype); ip_hw_instance->kobj.kset = &ip_hw_id->hw_id_kset; From 48dc279c3010ac8f91b1845b2abb3a1e9943a0f5 Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Thu, 13 Aug 2026 12:27:53 +0800 Subject: [PATCH 0663/1328] drm/amdgpu: force complete the MES ring fences on reset The MES scheduler ring has no drm scheduler (no_scheduler = true), so it is skipped by the force-completion loop in amdgpu_device_pre_asic_reset(). It uses a polling fence whose hw value lives in wb (GTT) memory and survives a MODE1 reset, while fence_drv.sync_seq keeps advancing for every packet. When the reset is triggered because MES itself stopped responding, the timed-out packets advance sync_seq past the last hw fence value MES wrote. After resume the first MES submission polls forever on a seq that is never written back, failing the resume and wedging the box on a second reset: amdgpu: MES ring buffer is full. amdgpu: *ERROR* ring gfx_0.0.0 test failed (-110) amdgpu: resume of IP block failed -110 amdgpu: GPU reset end with ret = -110 Force complete the MES scheduler ring fences together with the scheduler rings so their hw fence is realigned to sync_seq. v2: cover all XCCs (one scheduler ring each), not just mes.ring[0]. Cc: stable@vger.kernel.org Signed-off-by: Jesse Zhang Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index 57c8ce814931..be4c74491554 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -5043,6 +5043,19 @@ int amdgpu_device_pre_asic_reset(struct amdgpu_device *adev, amdgpu_fence_driver_force_completion(ring, fence); } + /* + * MES scheduler rings have no drm scheduler, so they are missed by the + * loop above. Realign their polling fence too (one per XCC), otherwise the + * first post-reset submission polls forever on a stale seq. sched.ready is + * only set while the driver owns the ring. + */ + for (i = 0; i < AMDGPU_MAX_MES_INST_PIPES; i++) { + struct amdgpu_ring *mes_ring = &adev->mes.ring[i]; + + if (mes_ring->fence_drv.initialized && mes_ring->sched.ready) + amdgpu_fence_driver_force_completion(mes_ring, fence); + } + amdgpu_fence_driver_isr_toggle(adev, false); r = amdgpu_reset_prepare_hwcontext(adev, reset_context); From 556488b08638aad240bdb524cebf22fa7569843c Mon Sep 17 00:00:00 2001 From: Zhu Lingshan Date: Thu, 13 Aug 2026 17:36:53 +0800 Subject: [PATCH 0664/1328] drm/amdgpu: validate rptr and wptr of a userq rptr and wptr of a userq are 8 bytes aligned, and may not placed on a page boundary. This commit checks whether rptr and wptr are 8 bytes aligned, and expectes 8 bytes when validates rptr/wptr VA. With above changes, this commit fixes an regression in amdgpu_userq_input_va_validate, where end_addr is caculated by: check_add_overflow(start_addr, expected_size - 1, &end_addr). Wptr and rptr are very likely not to be page aligned, when validating rptr and wptr, if they are located in the last mapped page(or only one page is mapped) and expected_size is PAGE_SIZE, end_addr will exceed the last mapped page, means (end_addr >> AMDGPU_GPU_PAGE_SHIFT) > va_map->last, and causing an -EINVAL, even it is a valid VA. Signed-off-by: Zhu Lingshan Acked-by: Alex Deucher Fixes: c0122bf2ccb1 ("drm/amdgpu: fix userq VA validation for sub-page buffers") Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index 04639f894903..17cc48d87c4d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -702,10 +702,10 @@ amdgpu_userq_create(struct drm_file *filp, union drm_amdgpu_userq *args) args->in.queue_size, &queue->userq_vas.va.queue_rb) || amdgpu_userq_input_va_validate(adev, queue, args->in.rptr_va, - AMDGPU_GPU_PAGE_SIZE, + sizeof(u64), &queue->userq_vas.va.rptr) || amdgpu_userq_input_va_validate(adev, queue, args->in.wptr_va, - AMDGPU_GPU_PAGE_SIZE, + sizeof(u64), &queue->userq_vas.va.wptr)) { r = -EINVAL; amdgpu_bo_unreserve(fpriv->vm.root.bo); @@ -850,6 +850,12 @@ static int amdgpu_userq_input_args_validate(struct drm_device *dev, drm_file_err(filp, "invalidate userq queue rptr or wptr\n"); return -EINVAL; } + + if (!IS_ALIGNED(args->in.wptr_va, sizeof(u64)) || + !IS_ALIGNED(args->in.rptr_va, sizeof(u64))) { + drm_file_err(filp, "user queue rptr or wptr is not 8-byte aligned\n"); + return -EINVAL; + } break; case AMDGPU_USERQ_OP_FREE: if (args->in.ip_type || From fd65d1742992361fc2201ecb4e43411e6e417fcb Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Thu, 13 Aug 2026 12:28:04 +0800 Subject: [PATCH 0665/1328] drm/amdgpu: force complete the KIQ ring fences on reset Like the MES scheduler ring, the KIQ ring sets no_scheduler = true and uses a polling fence, so it is skipped by the force-completion loop in amdgpu_device_pre_asic_reset(). Its hw fence value lives in wb (GTT) memory and survives a MODE1 reset while fence_drv.sync_seq keeps advancing, so after a reset the first KIQ submission can poll forever on a seq that is never written back. Force complete the KIQ ring fences too so their hw fence is realigned to sync_seq. Cc: stable@vger.kernel.org Reviewed-by: Alex Deucher Suggested-by: Alex Deucher Signed-off-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index be4c74491554..0b7cdea4b9e0 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -5056,6 +5056,18 @@ int amdgpu_device_pre_asic_reset(struct amdgpu_device *adev, amdgpu_fence_driver_force_completion(mes_ring, fence); } + /* + * KIQ rings are polling-fence/no_scheduler like MES, so realign their + * fence too (one ring per XCC), otherwise the first post-reset KIQ + * submission polls forever on a stale seq. + */ + for (i = 0; i < AMDGPU_MAX_GC_INSTANCES; i++) { + struct amdgpu_ring *kiq_ring = &adev->gfx.kiq[i].ring; + + if (kiq_ring->fence_drv.initialized && kiq_ring->sched.ready) + amdgpu_fence_driver_force_completion(kiq_ring, fence); + } + amdgpu_fence_driver_isr_toggle(adev, false); r = amdgpu_reset_prepare_hwcontext(adev, reset_context); From 8587d48d694da5aca580f92461658ec14470592b Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Wed, 12 Aug 2026 11:08:40 +0800 Subject: [PATCH 0666/1328] drm/amdgpu: check thunderbolt before switcheroo registration Introduce a helper to consolidate the vga_switcheroo registration condition used by the init and fini paths. Keep the explicit pci_is_thunderbolt_attached() check, as dev_is_removable() does not provide equivalent coverage for Thunderbolt-attached GPUs. This ensures such devices remain excluded from switcheroo registration while preserving the existing PX and Apple gmux handling. Cc: stable@vger.kernel.org Signed-off-by: Yang Wang Reviewed-by: Kenneth Feng Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index 0b7cdea4b9e0..ca385ed15bc6 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -3739,6 +3739,14 @@ static void amdgpu_device_sys_interface_fini(struct amdgpu_device *adev) amdgpu_ptl_sysfs_fini(adev); } +static bool +amdgpu_device_should_register_switcheroo(struct amdgpu_device *adev, bool px) +{ + return !pci_is_thunderbolt_attached(adev->pdev) && + (px || (!dev_is_removable(&adev->pdev->dev) && + apple_gmux_detect(NULL, NULL))); +} + /** * amdgpu_device_init - initialize the driver * @@ -4189,8 +4197,7 @@ int amdgpu_device_init(struct amdgpu_device *adev, px = amdgpu_device_supports_px(adev); - if (px || (!dev_is_removable(&adev->pdev->dev) && - apple_gmux_detect(NULL, NULL))) + if (amdgpu_device_should_register_switcheroo(adev, px)) vga_switcheroo_register_client(adev->pdev, &amdgpu_switcheroo_ops, px); @@ -4355,8 +4362,7 @@ void amdgpu_device_fini_sw(struct amdgpu_device *adev) px = amdgpu_device_supports_px(adev); - if (px || (!dev_is_removable(&adev->pdev->dev) && - apple_gmux_detect(NULL, NULL))) + if (amdgpu_device_should_register_switcheroo(adev, px)) vga_switcheroo_unregister_client(adev->pdev); if (px) From c675dea86a000e9550077c5bca97c6431786d1b7 Mon Sep 17 00:00:00 2001 From: Pierre-Eric Pelloux-Prayer Date: Mon, 15 Jun 2026 13:48:23 +0200 Subject: [PATCH 0667/1328] drm/amdgpu: delay ttm buffer func enablement on xgmi When amdgpu_init_minimal_xgmi is used, SDMA engines init is delayed so amdgpu_ttm_enable_buffer_funcs must be called later. Without this, the check for num_buffer_funcs_scheds will fail and using ttm buffer funcs later will fail. Given that amdgpu_ttm_enable_buffer_funcs is a no-op if amdgpu_in_reset() returns true, the call has to occur after the reset lock is dropped. Cc: stable@vger.kernel.org Fixes: e4029f7a9474 ("drm/amdgpu: only use working sdma schedulers for ttm") Signed-off-by: Pierre-Eric Pelloux-Prayer Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 8 +++++--- drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c | 3 +++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index ca385ed15bc6..b61c641b5be4 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -2529,7 +2529,11 @@ static int amdgpu_device_ip_init(struct amdgpu_device *adev) if (r) goto init_failed; - amdgpu_ttm_enable_buffer_funcs(adev); + /* If SDMA is not brought up during hwini, the ttm buffer funcs enablement + * is delayed after reset-on-init completes. + */ + if (amdgpu_ip_member_of_hwini(adev, AMD_IP_BLOCK_TYPE_SDMA)) + amdgpu_ttm_enable_buffer_funcs(adev); /* Don't init kfd if whole hive need to be reset during init */ if (adev->init_lvl->level != AMDGPU_INIT_LEVEL_MINIMAL_XGMI) { @@ -5174,8 +5178,6 @@ int amdgpu_device_reinit_after_reset(struct amdgpu_reset_context *reset_context) if (r) goto out; - amdgpu_ttm_enable_buffer_funcs(tmp_adev); - r = amdgpu_device_ip_resume_phase3(tmp_adev); if (r) goto out; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c index 2725230aa5e3..45e31b3daf06 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c @@ -1380,6 +1380,9 @@ static void amdgpu_xgmi_reset_on_init_work(struct work_struct *work) amdgpu_device_unlock_reset_domain(tmp_adev->reset_domain); list_for_each_entry(tmp_adev, &hive->device_list, gmc.xgmi.head) { + /* Enable ttm buffers funcs after the reset lock has been dropped. */ + amdgpu_ttm_enable_buffer_funcs(tmp_adev); + r = amdgpu_ras_init_badpage_info(tmp_adev); if (r && r != -EHWPOISON) dev_err(tmp_adev->dev, From 8fce9b0f93e222451d3f586c129c7b9f53a53fd2 Mon Sep 17 00:00:00 2001 From: Zhu Lingshan Date: Fri, 14 Aug 2026 14:51:32 +0800 Subject: [PATCH 0668/1328] amdkfd: let profile_lock_device return an int other than uint32 profile_lock_device() may return negive error code, so the type of the return value should be int, not uint32 Signed-off-by: Zhu Lingshan Reviewed-by: Felix Kuehling Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_chardev.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c index 309510e23315..6fd18488d5cf 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c @@ -3329,7 +3329,7 @@ static int kfd_ioctl_create_process(struct file *filep, struct kfd_process *p, v return 0; } -static inline uint32_t profile_lock_device(struct kfd_process *p, +static inline int profile_lock_device(struct kfd_process *p, uint32_t gpu_id, uint32_t op) { struct kfd_process_device *pdd; From 5c082f4cd17601e2357c1c4a686a85a53411c3d0 Mon Sep 17 00:00:00 2001 From: Zhu Lingshan Date: Fri, 14 Aug 2026 16:37:01 +0800 Subject: [PATCH 0669/1328] drm/amdgpu: fix hang and race in userq destroy When a queue is hung, the hang_detect_work is the only way to recover it. However in amdgpu_userq_destroy(), the hang_detect_work is cancelled too early, resulting in amdgpu_userq_wait_for_last_fence() may never return, leaving an uninterruptible dma_fence_wait() hang there. To fix this problem, this commit moves the cancelling of hang_detect_work after amdgpu_userq_wait_for_last_fence(), and it has to be before the unmap helper, because hang_detect_work resets the queue, so it races with amdgpu_userq_unmap_helper() for MES operations and queue state. This commit splits amdgpu_userq_cleanup() into two parts: 1) amdgpu_userq_detach_doorbell(), which detaches the queue from userq_doorbell_xa. This has to be called before the cancel, otherwise the IRQ handlers (for example amdgpu_userq_process_fence_irq) can re-schedule the hang_detect_work and the cancel is not final. 2) amdgpu_userq_fence_driver_free(), this has to be called after the unmap helper, because it can release the seq64 slot that the GPU writes fence values to. Only one cancel_delayed_work_sync(&queue->hang_detect_work) is needed, so other redundancies are removed. Signed-off-by: Zhu Lingshan Acked-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index 17cc48d87c4d..24adad7be251 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -418,19 +418,12 @@ static void amdgpu_userq_wait_for_last_fence(struct amdgpu_usermode_queue *queue dma_fence_wait(f, false); } -static void amdgpu_userq_cleanup(struct amdgpu_usermode_queue *queue) +static void amdgpu_userq_detach_doorbell(struct amdgpu_usermode_queue *queue) { - struct amdgpu_userq_mgr *uq_mgr = queue->userq_mgr; - struct amdgpu_device *adev = uq_mgr->adev; + struct amdgpu_device *adev = queue->userq_mgr->adev; - /* Wait for mode-1 reset to complete */ down_read(&adev->reset_domain->sem); - - /* Use interrupt-safe locking since IRQ handlers may access these XArrays */ xa_erase_irq(&adev->userq_doorbell_xa, queue->doorbell_index); - amdgpu_userq_fence_driver_free(queue); - queue->fence_drv = NULL; - up_read(&adev->reset_domain->sem); } @@ -551,18 +544,19 @@ amdgpu_userq_destroy(struct amdgpu_userq_mgr *uq_mgr, struct amdgpu_usermode_que cancel_delayed_work_sync(&uq_mgr->resume_work); - /* Cancel any pending hang detection work and cleanup */ - cancel_delayed_work_sync(&queue->hang_detect_work); - mutex_lock(&uq_mgr->userq_mutex); amdgpu_userq_wait_for_last_fence(queue); + amdgpu_userq_detach_doorbell(queue); + cancel_delayed_work_sync(&queue->hang_detect_work); + #if defined(CONFIG_DEBUG_FS) debugfs_remove_recursive(queue->debugfs_queue); #endif r = amdgpu_userq_unmap_helper(queue); atomic_dec(&uq_mgr->userq_count[queue->queue_type]); - amdgpu_userq_cleanup(queue); + amdgpu_userq_fence_driver_free(queue); + queue->fence_drv = NULL; mutex_unlock(&uq_mgr->userq_mutex); /* @@ -574,7 +568,6 @@ amdgpu_userq_destroy(struct amdgpu_userq_mgr *uq_mgr, struct amdgpu_usermode_que if (r) queue_work(adev->reset_domain->wq, &uq_mgr->reset_work); - cancel_delayed_work_sync(&queue->hang_detect_work); uq_funcs->mqd_destroy(queue); queue->userq_mgr = NULL; @@ -748,7 +741,7 @@ amdgpu_userq_create(struct drm_file *filp, union drm_amdgpu_userq *args) ((queue->queue_type != AMDGPU_HW_IP_GFX) && (queue->queue_type != AMDGPU_HW_IP_COMPUTE))) { /* Serialize the map against an in-progress GPU reset (MES is - * unresponsive during recovery), matching amdgpu_userq_cleanup(). + * unresponsive during recovery), matching amdgpu_userq_detach_doorbell(). */ down_read(&adev->reset_domain->sem); r = amdgpu_userq_map_helper(queue); From 275c3332585bbabcefad109a6978cc0cbecf2008 Mon Sep 17 00:00:00 2001 From: Gilles Risch Date: Mon, 17 Aug 2026 02:43:26 +0200 Subject: [PATCH 0670/1328] drm/radeon: fix internal display on iMac11, 1 (RV770/DCE3.1) The Apple iMac11,1 (27-inch, Late 2009) uses a Mobility Radeon HD 4850 (RV770/DCE3.1) with a 2560x1440 internal panel on an internal DisplayPort path. Without this fix the display stays dark under KMS. This machine suffers from the same issue as iMac10,1 and iMac11,2: Apple routes the internal display through Link B of the DIG encoder instead of Link A. Add iMac11,1 to the existing DMI quirk and move the Apple-specific encoder assignment into its own block, independent of the DCE version check. Additionally, the 2560x1440 panel requires RADEON_PLL_USE_FRAC_FB_DIV and ATOM_ENCODER_CMD_DP_VIDEO_ON, limited to iMac11,1 via dmi_match() to avoid affecting other boards. Reviewed-by: Lukas Wunner Signed-off-by: Gilles Risch Signed-off-by: Alex Deucher --- drivers/gpu/drm/radeon/atombios_crtc.c | 5 ++++- drivers/gpu/drm/radeon/atombios_encoders.c | 23 ++++++++++++---------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/radeon/atombios_crtc.c b/drivers/gpu/drm/radeon/atombios_crtc.c index 2fc0334e0d6c..075eba2d47f3 100644 --- a/drivers/gpu/drm/radeon/atombios_crtc.c +++ b/drivers/gpu/drm/radeon/atombios_crtc.c @@ -24,6 +24,8 @@ * Alex Deucher */ +#include + #include #include #include @@ -594,7 +596,8 @@ static u32 atombios_adjust_pll(struct drm_crtc *crtc, if (((rdev->family == CHIP_RS780) || (rdev->family == CHIP_RS880)) && !radeon_crtc->ss_enabled) radeon_crtc->pll_flags |= RADEON_PLL_USE_FRAC_FB_DIV; - if (ASIC_IS_DCE32(rdev) && mode->clock > 165000) + if ((ASIC_IS_DCE32(rdev) || dmi_match(DMI_PRODUCT_NAME, "iMac11,1")) + && mode->clock > 165000) radeon_crtc->pll_flags |= RADEON_PLL_USE_FRAC_FB_DIV; } else { radeon_crtc->pll_flags |= RADEON_PLL_LEGACY; diff --git a/drivers/gpu/drm/radeon/atombios_encoders.c b/drivers/gpu/drm/radeon/atombios_encoders.c index 5cfd8fcfa5e8..8b3f8303a967 100644 --- a/drivers/gpu/drm/radeon/atombios_encoders.c +++ b/drivers/gpu/drm/radeon/atombios_encoders.c @@ -1707,7 +1707,7 @@ radeon_atom_encoder_dpms_dig(struct drm_encoder *encoder, int mode) if (ENCODER_MODE_IS_DP(atombios_get_encoder_mode(encoder)) && connector) { /* DP_SET_POWER_D0 is set in radeon_dp_link_train */ radeon_dp_link_train(encoder, connector); - if (ASIC_IS_DCE4(rdev)) + if (ASIC_IS_DCE4(rdev) || dmi_match(DMI_PRODUCT_NAME, "iMac11,1")) atombios_dig_encoder_setup(encoder, ATOM_ENCODER_CMD_DP_VIDEO_ON, 0); } if (radeon_encoder->devices & (ATOM_DEVICE_LCD_SUPPORT)) { @@ -2123,17 +2123,20 @@ int radeon_atom_pick_dig_encoder(struct drm_encoder *encoder, int fe_idx) } /* - * On DCE32 any encoder can drive any block so usually just use crtc id, - * but Apple thinks different at least on iMac10,1 and iMac11,2, so there use linkb, - * otherwise the internal eDP panel will stay dark. + * Apple routes the internal eDP panel through Link B of the DIG encoder + * instead of Link A on the iMac10,1, iMac11,1 and iMac11,2. + * Use linkb to avoid a dark display. */ - if (ASIC_IS_DCE32(rdev)) { - if (dmi_match(DMI_PRODUCT_NAME, "iMac10,1") || - dmi_match(DMI_PRODUCT_NAME, "iMac11,2")) - enc_idx = (dig->linkb) ? 1 : 0; - else - enc_idx = radeon_crtc->crtc_id; + if (dmi_match(DMI_PRODUCT_NAME, "iMac10,1") || + dmi_match(DMI_PRODUCT_NAME, "iMac11,1") || + dmi_match(DMI_PRODUCT_NAME, "iMac11,2")) { + enc_idx = (dig->linkb) ? 1 : 0; + goto assigned; + } + /* on DCE32 and encoder can driver any block so just crtc id */ + if (ASIC_IS_DCE32(rdev)) { + enc_idx = radeon_crtc->crtc_id; goto assigned; } From 4d7390530853eb7befda9cc786e4c86e8ad7ac9e Mon Sep 17 00:00:00 2001 From: "David (Ming Qiang) Wu" Date: Fri, 7 Aug 2026 15:12:14 -0400 Subject: [PATCH 0671/1328] drm/amdgpu/vcn: fix integer overflow in dec_msg buffer count check If the supplied msg[2] (num_buffers) is 0x3FFFFFFF, the expression 6 + num_buffers * 4 wraps to 2 and the bounds check passes, letting the parser loop far past the end of the message BO. Triggering it additionally requires a ~4GiB mapping so that msg[1] survives the earlier "header does not fit in BO" check. Rewrite the test in division form, which is overflow-free by construction. Also update the message to reflect that msg is invalid. Fixes: b193019860d6 ("drm/amdgpu/vcn3: Prevent OOB reads when parsing dec msg") Fixes: 0a78f2bac142 ("drm/amdgpu/vcn4: Prevent OOB reads when parsing dec msg") Cc: stable@vger.kernel.org Signed-off-by: David (Ming Qiang) Wu Reviewed-by: Leo Liu Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/vcn_v3_0.c | 10 +++++++--- drivers/gpu/drm/amd/amdgpu/vcn_v4_0.c | 10 +++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/vcn_v3_0.c b/drivers/gpu/drm/amd/amdgpu/vcn_v3_0.c index 81bba3ec2a93..00d8f35846f2 100644 --- a/drivers/gpu/drm/amd/amdgpu/vcn_v3_0.c +++ b/drivers/gpu/drm/amd/amdgpu/vcn_v3_0.c @@ -1964,9 +1964,13 @@ static int vcn_v3_0_dec_msg(struct amdgpu_cs_parser *p, struct amdgpu_job *job, len_dw = msg[1] / 4; num_buffers = msg[2]; - /* Verify that all indices fit within the claimed length. Each index is 4 DWORDs */ - if (num_buffers > len_dw || 6 + num_buffers * 4 > len_dw) { - DRM_ERROR("VCN message has too many buffers!\n"); + /* Verify that all indices fit within the claimed length. + * There are 6 dwords in the header before the first buffer. + * Each buffer has 4 dwords. Any trailing dwords after the + * last buffer are ignored. + */ + if (len_dw < 6 || num_buffers > (len_dw - 6) / 4) { + DRM_ERROR("Invalid VCN message!\n"); r = -EINVAL; goto out; } diff --git a/drivers/gpu/drm/amd/amdgpu/vcn_v4_0.c b/drivers/gpu/drm/amd/amdgpu/vcn_v4_0.c index 0cce78b205a8..c2ddf3cb368f 100644 --- a/drivers/gpu/drm/amd/amdgpu/vcn_v4_0.c +++ b/drivers/gpu/drm/amd/amdgpu/vcn_v4_0.c @@ -1880,9 +1880,13 @@ static int vcn_v4_0_dec_msg(struct amdgpu_cs_parser *p, struct amdgpu_job *job, len_dw = msg[1] / 4; num_buffers = msg[2]; - /* Verify that all indices fit within the claimed length. Each index is 4 DWORDs */ - if (num_buffers > len_dw || 6 + num_buffers * 4 > len_dw) { - DRM_ERROR("VCN message has too many buffers!\n"); + /* Verify that all indices fit within the claimed length. + * There are 6 dwords in the header before the first buffer. + * Each buffer has 4 dwords. Any trailing dwords after the + * last buffer are ignored. + */ + if (len_dw < 6 || num_buffers > (len_dw - 6) / 4) { + DRM_ERROR("Invalid VCN message!\n"); r = -EINVAL; goto out; } From 0e4ef0ead600e367b4dd431daa97a345a5ff8a86 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Fri, 14 Aug 2026 11:39:16 -0400 Subject: [PATCH 0672/1328] drm/amdgpu: handle pipeline sync without a VM fence If we end up emitting a VM fence keep pipeline sync associated with that fence. If not, emit them as part of the IB fence. v2: fix need_pipe_sync handling v3: simplify the function Cc: David Rosca Fixes: cb1e657ccac8 ("drm/amdgpu: handle GDS and SPM without a VM fence") Reviewed-by: David Rosca Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ib.c | 6 +++++- drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c | 27 +++++++++++++------------- drivers/gpu/drm/amd/amdgpu/amdgpu_vm.h | 2 +- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ib.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ib.c index da4dc489e80b..360e6f00cb7c 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ib.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ib.c @@ -222,7 +222,7 @@ int amdgpu_ib_schedule(struct amdgpu_ring *ring, unsigned int num_ibs, vm_af = job->hw_vm_fence; /* VM sequence */ vm_af->ib_wptr = ring->wptr; - amdgpu_vm_flush(ring, job, need_pipe_sync, &emit_spm_needed, + amdgpu_vm_flush(ring, job, &need_pipe_sync, &emit_spm_needed, &emit_gds_needed); vm_af->ib_dw_size = amdgpu_ring_get_dw_distance(ring, vm_af->ib_wptr, ring->wptr); @@ -235,6 +235,10 @@ int amdgpu_ib_schedule(struct amdgpu_ring *ring, unsigned int num_ibs, if (ring->funcs->insert_start) ring->funcs->insert_start(ring); + /* this may have been handled by amdgpu_vm_flush */ + if (need_pipe_sync) + amdgpu_ring_emit_pipeline_sync(ring); + if (emit_spm_needed) adev->gfx.rlc.funcs->update_spm_vmid(adev, ring->xcc_id, ring, job->vmid); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c index 71050a86bcc3..f6c5de63eae6 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c @@ -772,7 +772,7 @@ bool amdgpu_vm_need_pipeline_sync(struct amdgpu_ring *ring, * Emit a VM flush when it is necessary. */ void amdgpu_vm_flush(struct amdgpu_ring *ring, struct amdgpu_job *job, - bool need_pipe_sync, bool *emit_spm_needed, + bool *need_pipe_sync, bool *emit_spm_needed, bool *emit_gds_needed) { struct amdgpu_device *adev = ring->adev; @@ -827,8 +827,7 @@ void amdgpu_vm_flush(struct amdgpu_ring *ring, struct amdgpu_job *job, if (gds_switch_needed && emit_fence) *emit_gds_needed = false; - if (!vm_flush_needed && !gds_switch_needed && !need_pipe_sync && - !cleaner_shader_needed && !spm_update_needed) + if (!emit_fence) return; amdgpu_ring_ib_begin(ring); @@ -847,8 +846,10 @@ void amdgpu_vm_flush(struct amdgpu_ring *ring, struct amdgpu_job *job, patch = amdgpu_ring_init_cond_exec(ring, ring->cond_exe_gpu_addr); - if (need_pipe_sync) + if (*need_pipe_sync) { amdgpu_ring_emit_pipeline_sync(ring); + *need_pipe_sync = false; + } if (cleaner_shader_needed) ring->funcs->emit_cleaner_shader(ring); @@ -861,21 +862,19 @@ void amdgpu_vm_flush(struct amdgpu_ring *ring, struct amdgpu_job *job, if (pasid_mapping_needed) amdgpu_gmc_emit_pasid_mapping(ring, job->vmid, job->pasid); - if (emit_fence) { - if (spm_update_needed) - adev->gfx.rlc.funcs->update_spm_vmid(adev, ring->xcc_id, ring, job->vmid); + if (spm_update_needed) + adev->gfx.rlc.funcs->update_spm_vmid(adev, ring->xcc_id, ring, job->vmid); - if (gds_switch_needed) - amdgpu_ring_emit_gds_switch(ring, job->vmid, job->gds_base, + if (gds_switch_needed) + amdgpu_ring_emit_gds_switch(ring, job->vmid, job->gds_base, job->gds_size, job->gws_base, job->gws_size, job->oa_base, job->oa_size); - amdgpu_fence_emit(ring, job->hw_vm_fence, 0); - fence = &job->hw_vm_fence->base; - /* get a ref for the job */ - dma_fence_get(fence); - } + amdgpu_fence_emit(ring, job->hw_vm_fence, 0); + fence = &job->hw_vm_fence->base; + /* get a ref for the job */ + dma_fence_get(fence); if (vm_flush_needed) { mutex_lock(&id_mgr->lock); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.h index 7f2ba728e3ed..d32183cd9e0f 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.h @@ -512,7 +512,7 @@ int amdgpu_vm_validate(struct amdgpu_device *adev, struct amdgpu_vm *vm, int (*callback)(void *p, struct amdgpu_bo *bo), void *param); void amdgpu_vm_flush(struct amdgpu_ring *ring, struct amdgpu_job *job, - bool need_pipe_sync, bool *emit_spm_needed, + bool *need_pipe_sync, bool *emit_spm_needed, bool *emit_gds_needed); int amdgpu_vm_update_pdes(struct amdgpu_device *adev, struct amdgpu_vm *vm, bool immediate); From 4f40873f8a4107df2b9c8e68c947c4fd0cd519d2 Mon Sep 17 00:00:00 2001 From: Harry Wentland Date: Tue, 4 Aug 2026 17:04:04 -0400 Subject: [PATCH 0673/1328] drm/amd/display: avoid divide-by-zero in __is_lut_linear() __is_lut_linear() computes the expected value of each entry with expected = i * MAX_DRM_LUT_VALUE / (size - 1); If it is ever called with a single-entry LUT, size - 1 is zero and the kernel takes a divide error (#DE). A LUT with fewer than two entries cannot describe a linear mapping anyway, so return false early instead of dividing by zero. Fixes: 086247a4b2fb ("drm/amd/display: Use 4096 lut entries") Cc: stable@vger.kernel.org Signed-off-by: Harry Wentland Reviewed-by: Melissa Wen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c index 7b68c6846039..26e5c89375a5 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c @@ -471,6 +471,12 @@ bool __is_lut_linear(const struct drm_color_lut *lut, uint32_t size) uint32_t expected; int delta; + /* A LUT with fewer than two entries can't be interpolated and would + * divide by zero below (size - 1); it can't be treated as linear. + */ + if (size < 2) + return false; + for (i = 0; i < size; i++) { /* All color values should equal */ if ((lut[i].red != lut[i].green) || (lut[i].green != lut[i].blue)) From e4c3ab59021e7c146a84b6671f0d530972bd58b4 Mon Sep 17 00:00:00 2001 From: Harry Wentland Date: Tue, 4 Aug 2026 17:04:05 -0400 Subject: [PATCH 0674/1328] drm/amd/display: validate plane degamma LUT size for private color prop Unlike the CRTC degamma path, which is guarded by amdgpu_dm_verify_lut_sizes(), the per-plane degamma LUT size was never validated before use. __set_dm_plane_degamma() passed the user-supplied size straight into __is_lut_linear() and, for a non-linear LUT, into __set_input_tf() -> __drm_lut_to_dc_gamma(), the latter always iterating MAX_COLOR_LUT_ENTRIES entries regardless of the actual LUT size. A malformed AMD_PLANE_DEGAMMA_LUT blob (e.g. a single entry) could thus trigger a divide-by-zero in __is_lut_linear() or an out-of-bounds read in __drm_lut_to_dc_gamma(). Reject any plane degamma LUT whose size does not match MAX_COLOR_LUT_ENTRIES, mirroring the invariant the code already asserts a few lines below (and which the CRTC path enforces). The AMD_PLANE_DEGAMMA_LUT property is only exposed on builds with AMD_PRIVATE_COLOR defined. Fixes: 980f8710075a ("drm/amd/display: add plane degamma TF and LUT support") Cc: stable@vger.kernel.org Signed-off-by: Harry Wentland Reviewed-by: Melissa Wen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c index 26e5c89375a5..d55dc06167a8 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c @@ -1495,6 +1495,13 @@ __set_dm_plane_degamma(struct drm_plane_state *plane_state, degamma_lut = __extract_blob_lut(dm_plane_state->degamma_lut, °amma_size); + if (degamma_lut && degamma_size != MAX_COLOR_LUT_ENTRIES) { + drm_dbg(plane_state->state->dev, + "Invalid Plane Degamma LUT size. Should be %u but got %u.\n", + MAX_COLOR_LUT_ENTRIES, degamma_size); + return -EINVAL; + } + has_degamma_lut = degamma_lut && !__is_lut_linear(degamma_lut, degamma_size); From 6fd1b9225de1b09cd8dd79e1ccba8d84b4e94036 Mon Sep 17 00:00:00 2001 From: Andrey Golovko Date: Thu, 13 Aug 2026 19:40:00 +0300 Subject: [PATCH 0675/1328] ASoC: sdw_utils: prepare the stream again when resuming A peripheral loses its Data Port configuration when the controller is power-gated during system suspend, so the ports have to be prepared again before the stream can be enabled. That happens on its own when userspace calls snd_pcm_prepare() after SNDRV_PCM_STATE_SUSPENDED, but an application is also allowed to restart the stream with SNDRV_PCM_IOCTL_RESUME on a card that advertises SNDRV_PCM_INFO_RESUME, as the AMD ACP platforms do. That path only reaches the trigger callback, sdw_enable_stream() writes the channels of ports that were never prepared, and playback silently produces nothing: the PCM keeps running, no error is reported anywhere, and the speakers stay quiet until the stream is torn down and set up again. Prepare the stream on SNDRV_PCM_TRIGGER_RESUME, before enabling it. The SoundWire core expects exactly this: sdw_prepare_stream() accepts a disabled stream and then reapplies the bus parameters without recomputing them, which it documents as the resume case. Signed-off-by: Andrey Golovko Link: https://patch.msgid.link/20260813194000.10412-2-andrey.golovko@gmail.com Signed-off-by: Mark Brown --- sound/soc/sdw_utils/soc_sdw_utils.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c index 8a07ba2a29e5..3e091e78d8c2 100644 --- a/sound/soc/sdw_utils/soc_sdw_utils.c +++ b/sound/soc/sdw_utils/soc_sdw_utils.c @@ -1510,9 +1510,24 @@ int asoc_sdw_trigger(struct snd_pcm_substream *substream, int cmd) } switch (cmd) { + case SNDRV_PCM_TRIGGER_RESUME: + /* + * The peripherals lose their port configuration when the + * controller is power-gated during system suspend, and an + * application that restarts the stream with + * SNDRV_PCM_IOCTL_RESUME - which platforms advertising + * SNDRV_PCM_INFO_RESUME allow - never goes through + * .prepare() again. Prepare the stream here so that the + * ports are reprogrammed before they are enabled; + * sdw_prepare_stream() reapplies the parameters without + * recomputing them when the stream is disabled. + */ + ret = sdw_prepare_stream(sdw_stream); + if (ret) + break; + fallthrough; case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - case SNDRV_PCM_TRIGGER_RESUME: ret = sdw_enable_stream(sdw_stream); break; From 119046319e773ff86b98e3ab67623443f8481d7b Mon Sep 17 00:00:00 2001 From: Andrey Golovko Date: Thu, 13 Aug 2026 19:40:00 +0300 Subject: [PATCH 0676/1328] ASoC: tas2783-sdw: power the Function up before preparing the port A Data Port cannot complete channel preparation while the SDCA Function is powered down: the peripheral raises the channel's bit in DPn_PrepareStatus and never clears it. tas_sdw_hw_params() takes care of that for a stream that is being set up, and the retry loop there says so - "ensure power on so that port prepare succeeds". Port preparation, however, also happens on a stream that is merely re-prepared, without hw_params() running again. That is what userspace does after a suspend in which the peripheral lost power: snd_pcm_prepare() reaches .prepare and sdw_prepare_stream(), the port is prepared afresh, but PDE23 is still at the PS3 reset default because nothing wrote it since the device came back. The result is silence with no error anywhere. The codec sets simple_ch_prep_sm, so sdw_prep_deprep_slave_port() skips the NOT_PREPARED poll, and a port that never prepares is indistinguishable from a healthy one. Power the Function up in the PRE_PREP callback, immediately before the PrepareCtrl write it already performs, so that preparation has what it needs on every path that prepares a port. Measured on an ASUS ProArt PX13 (AMD ACP7.0, two TAS2783): after s2idle with ~100 s of S0i3 residency, DPn_PrepareStatus stays at the channel mask and there is no audio; writing PDE23 PS0 and re-issuing the prepare clears it within 1 ms and audio returns. Signed-off-by: Andrey Golovko Link: https://patch.msgid.link/20260813194000.10412-3-andrey.golovko@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/tas2783-sdw.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/sound/soc/codecs/tas2783-sdw.c b/sound/soc/codecs/tas2783-sdw.c index c217da5fccdf..d32d8f441651 100644 --- a/sound/soc/codecs/tas2783-sdw.c +++ b/sound/soc/codecs/tas2783-sdw.c @@ -1259,6 +1259,7 @@ static int tas_port_prep(struct sdw_slave *slave, struct sdw_prepare_ch *prep_ch enum sdw_port_prep_ops pre_ops) { struct device *dev = &slave->dev; + struct tas2783_prv *tas_dev = dev_get_drvdata(dev); struct sdw_dpn_prop *dpn_prop; u32 addr; int ret; @@ -1270,6 +1271,25 @@ static int tas_port_prep(struct sdw_slave *slave, struct sdw_prepare_ch *prep_ch addr = SDW_DPN_PREPARECTRL(prep_ch->num); switch (pre_ops) { case SDW_OPS_PORT_PRE_PREP: + /* + * The Function has to be powered before the port can complete + * channel preparation. hw_params() does that when a stream is + * set up, but a stream that is only re-prepared - as it is + * after the peripheral lost power in S0i3 - does not go + * through hw_params() again, and the peripheral is back at its + * PS3 reset default. Power it up here, where it is needed. + */ + scoped_guard(mutex, &tas_dev->pde_lock) + ret = regmap_write(tas_dev->regmap, + SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PDE23, + TAS2783_SDCA_CTL_REQ_POW_STATE, 0), + TAS2783_SDCA_POW_STATE_ON); + if (ret) { + dev_err(dev, "power up failed for port %d, err=%d\n", + prep_ch->num, ret); + return ret; + } + ret = sdw_write_no_pm(slave, addr, prep_ch->ch_mask); if (ret) dev_err(dev, "prep failed for port %d, err=%d\n", From 092755367e1a5fcf0f5405e9425e86907199dfce Mon Sep 17 00:00:00 2001 From: Surendra Singh Chouhan Date: Fri, 24 Jul 2026 19:28:03 +0530 Subject: [PATCH 0677/1328] rtc: spacemit: handle regmap_test_bits() error return p1_rtc_read_time() called if (!regmap_test_bits(regmap, RTC_CTRL, RTC_EN)) to check if the RTC was enabled. regmap_test_bits() returns 1 if the bit is set, 0 if not set, and a negative error code (e.g. -EIO) if reading the control register fails. Using !regmap_test_bits(...) evaluates a negative error code as boolean false, causing I2C/regmap read failures to be ignored and incorrectly proceeding to read time registers from a failing device. Fix this by capturing the return value of regmap_test_bits() and returning the error code if negative, or -EINVAL if the RTC is disabled. Fixes: a6de182daa2b ("rtc: spacemit: support the SpacemiT P1 RTC") Reviewed-by: Alex Elder Signed-off-by: Surendra Singh Chouhan Link: https://patch.msgid.link/20260724135803.81223-1-kr494167@gmail.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-spacemit-p1.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/rtc/rtc-spacemit-p1.c b/drivers/rtc/rtc-spacemit-p1.c index 43ab62494bb4..1de7bd995d29 100644 --- a/drivers/rtc/rtc-spacemit-p1.c +++ b/drivers/rtc/rtc-spacemit-p1.c @@ -57,8 +57,9 @@ static int p1_rtc_read_time(struct device *dev, struct rtc_time *t) u8 time[6]; int ret; - if (!regmap_test_bits(regmap, RTC_CTRL, RTC_EN)) - return -EINVAL; /* RTC is disabled */ + ret = regmap_test_bits(regmap, RTC_CTRL, RTC_EN); + if (ret <= 0) + return ret ?: -EINVAL; /* RTC is disabled or error */ ret = regmap_bulk_read(regmap, RTC_TIME, time, sizeof(time)); if (ret) From 2943ec93ee6fb4683b73e66ac516b62df091e41b Mon Sep 17 00:00:00 2001 From: Jack Lee Date: Fri, 12 Jun 2026 16:35:34 -0600 Subject: [PATCH 0678/1328] rtc: ds1343: replace symbolic permissions with octal Symbolic permissions S_IRUGO and S_IWUSR are deprecated in favor of octal permissions. Replace S_IRUGO|S_IWUSR with 0644 and S_IRUGO with 0444. Signed-off-by: Jack Lee Link: https://patch.msgid.link/20260612223534.77412-1-skunkolee@gmail.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-ds1343.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/rtc/rtc-ds1343.c b/drivers/rtc/rtc-ds1343.c index aa9500791b7e..54f6af7886a8 100644 --- a/drivers/rtc/rtc-ds1343.c +++ b/drivers/rtc/rtc-ds1343.c @@ -120,7 +120,7 @@ static ssize_t ds1343_store_glitchfilter(struct device *dev, return count; } -static DEVICE_ATTR(glitch_filter, S_IRUGO | S_IWUSR, ds1343_show_glitchfilter, +static DEVICE_ATTR(glitch_filter, 0644, ds1343_show_glitchfilter, ds1343_store_glitchfilter); static int ds1343_nvram_write(void *priv, unsigned int off, void *val, @@ -183,7 +183,7 @@ static ssize_t ds1343_show_tricklecharger(struct device *dev, return sprintf(buf, "%s %s\n", diodes, resistors); } -static DEVICE_ATTR(trickle_charger, S_IRUGO, ds1343_show_tricklecharger, NULL); +static DEVICE_ATTR(trickle_charger, 0444, ds1343_show_tricklecharger, NULL); static struct attribute *ds1343_attrs[] = { &dev_attr_glitch_filter.attr, From 5904c4109554582ce8ded6a4dcfede48b21dbe4d Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 24 Jun 2026 13:55:24 +0800 Subject: [PATCH 0679/1328] rtc: zynqmp: Return optional clock lookup errors devm_clk_get_optional() returns NULL when the optional clock is absent, but returns an ERR_PTR when the clock provider lookup fails. Probe currently keeps the ERR_PTR and then passes it to clk_get_rate(). Return the lookup error instead. A truly absent optional clock still reaches the existing calibration fallback through clk_get_rate(NULL). Signed-off-by: Pengpeng Hou Fixes: 07dcc6f9c762 ("rtc: zynqmp: Add calibration set and get support") Reviewed-by: Michal Simek Link: https://patch.msgid.link/20260624055524.38522-1-pengpeng@iscas.ac.cn Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-zynqmp.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/rtc/rtc-zynqmp.c b/drivers/rtc/rtc-zynqmp.c index 2ae54804b87a..5bcb7536e973 100644 --- a/drivers/rtc/rtc-zynqmp.c +++ b/drivers/rtc/rtc-zynqmp.c @@ -334,10 +334,9 @@ static int xlnx_rtc_probe(struct platform_device *pdev) /* Getting the rtc info */ xrtcdev->rtc_clk = devm_clk_get_optional(&pdev->dev, "rtc"); - if (IS_ERR(xrtcdev->rtc_clk)) { - if (PTR_ERR(xrtcdev->rtc_clk) != -EPROBE_DEFER) - dev_warn(&pdev->dev, "Device clock not found.\n"); - } + if (IS_ERR(xrtcdev->rtc_clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(xrtcdev->rtc_clk), + "Failed to get rtc clock\n"); xrtcdev->freq = clk_get_rate(xrtcdev->rtc_clk); if (!xrtcdev->freq) { ret = of_property_read_u32(pdev->dev.of_node, "calibration", From f1daef2834042f2d45e120f55a74bd76a711e5c2 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sat, 4 Jul 2026 20:20:03 +0800 Subject: [PATCH 0680/1328] rtc: s32g: add missing MODULE_DEVICE_TABLE() The driver has an OF match table wired to .of_match_table, but does not export the table with MODULE_DEVICE_TABLE(). Add the missing MODULE_DEVICE_TABLE(of, ...) entry so module alias information is generated for OF based module autoloading. This is a source-level fix. It does not claim dynamic hardware reproduction; the evidence is the driver-owned match table, its use by the platform driver, and the missing module alias publication. Signed-off-by: Pengpeng Hou Reviewed-by: Matthias Brugger Reviewed-by: Ciprian Marian Costea Link: https://patch.msgid.link/20260704122003.70108-1-pengpeng@iscas.ac.cn Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-s32g.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/rtc/rtc-s32g.c b/drivers/rtc/rtc-s32g.c index 3a0818e972eb..86716192d10f 100644 --- a/drivers/rtc/rtc-s32g.c +++ b/drivers/rtc/rtc-s32g.c @@ -366,6 +366,7 @@ static const struct of_device_id rtc_dt_ids[] = { { .compatible = "nxp,s32g2-rtc", .data = &rtc_s32g2_data }, { /* sentinel */ }, }; +MODULE_DEVICE_TABLE(of, rtc_dt_ids); static DEFINE_SIMPLE_DEV_PM_OPS(s32g_rtc_pm_ops, s32g_rtc_suspend, s32g_rtc_resume); From 402c89d28fecd28652a4bfe997f08f6544836381 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sat, 4 Jul 2026 20:17:34 +0800 Subject: [PATCH 0681/1328] rtc: brcmstb-waketimer: add missing MODULE_DEVICE_TABLE() The driver has an OF match table wired to .of_match_table, but does not export the table with MODULE_DEVICE_TABLE(). Add the missing MODULE_DEVICE_TABLE(of, ...) entry so module alias information is generated for OF based module autoloading. This is a source-level fix. It does not claim dynamic hardware reproduction; the evidence is the driver-owned match table, its use by the platform driver, and the missing module alias publication. Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260704121734.54941-1-pengpeng@iscas.ac.cn Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-brcmstb-waketimer.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/rtc/rtc-brcmstb-waketimer.c b/drivers/rtc/rtc-brcmstb-waketimer.c index fb47c32ab5ff..55c15aad42cf 100644 --- a/drivers/rtc/rtc-brcmstb-waketimer.c +++ b/drivers/rtc/rtc-brcmstb-waketimer.c @@ -413,6 +413,7 @@ static const __maybe_unused struct of_device_id brcmstb_waketmr_of_match[] = { { .compatible = "brcm,brcmstb-waketimer" }, { /* sentinel */ }, }; +MODULE_DEVICE_TABLE(of, brcmstb_waketmr_of_match); static struct platform_driver brcmstb_waketmr_driver = { .probe = brcmstb_waketmr_probe, From 7490b8356e52e09c196645912f2c054f79aa5f3a Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sat, 4 Jul 2026 23:25:28 +0800 Subject: [PATCH 0682/1328] rtc: ab-eoz9: add missing MODULE_DEVICE_TABLE() The driver has a match table for the i2c bus wired into its driver structure, but the table is not exported with MODULE_DEVICE_TABLE(). Add the missing MODULE_DEVICE_TABLE() entry so module alias information is generated for automatic module loading. This is a source-level fix. It does not claim dynamic hardware reproduction; the evidence is the driver-owned match table, its use by the driver registration structure, and the missing module alias publication. Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260704152528.53258-1-pengpeng@iscas.ac.cn Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-ab-eoz9.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/rtc/rtc-ab-eoz9.c b/drivers/rtc/rtc-ab-eoz9.c index b75f4f665076..eb6d267c2ec3 100644 --- a/drivers/rtc/rtc-ab-eoz9.c +++ b/drivers/rtc/rtc-ab-eoz9.c @@ -549,6 +549,7 @@ static const struct i2c_device_id abeoz9_id[] = { { .name = "abeoz9" }, { } }; +MODULE_DEVICE_TABLE(i2c, abeoz9_id); static struct i2c_driver abeoz9_driver = { .driver = { From bcad3667a33750905fbb7470de5727bf8e37186d Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Thu, 25 Jun 2026 15:02:00 +0200 Subject: [PATCH 0683/1328] rtc: mv: fix potential race condition Since the driver allocates the IRQ using devm_request_irq(), this means the IRQ is going to be automatically unregistered by devres after mv_rtc_remove() returns. However, mv_rtc_remove() explicitly disables the hardware clock before devres teardown happens so the interrupt handler may run while the clock is disabled leading to a possible bus hang when accessing registers. Link: https://patch.msgid.link/20260625130202.1621692-1-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-mv.c | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/drivers/rtc/rtc-mv.c b/drivers/rtc/rtc-mv.c index f88976fd6d5d..c0f1bcd838e3 100644 --- a/drivers/rtc/rtc-mv.c +++ b/drivers/rtc/rtc-mv.c @@ -219,17 +219,15 @@ static int __init mv_rtc_probe(struct platform_device *pdev) if (IS_ERR(pdata->ioaddr)) return PTR_ERR(pdata->ioaddr); - pdata->clk = devm_clk_get(&pdev->dev, NULL); - /* Not all SoCs require a clock.*/ - if (!IS_ERR(pdata->clk)) - clk_prepare_enable(pdata->clk); + pdata->clk = devm_clk_get_optional_prepared(&pdev->dev, NULL); + if (IS_ERR(pdata->clk)) + return PTR_ERR(pdata->clk); /* make sure the 24 hour mode is enabled */ rtc_time = readl(pdata->ioaddr + RTC_TIME_REG_OFFS); if (rtc_time & RTC_HOURS_12H_MODE) { dev_err(&pdev->dev, "12 Hour mode is enabled but not supported.\n"); - ret = -EINVAL; - goto out; + return -EINVAL; } /* make sure it is actually functional */ @@ -238,8 +236,7 @@ static int __init mv_rtc_probe(struct platform_device *pdev) rtc_time = readl(pdata->ioaddr + RTC_TIME_REG_OFFS); if (rtc_time == 0x01000000) { dev_err(&pdev->dev, "internal RTC not ticking\n"); - ret = -ENODEV; - goto out; + return -ENODEV; } } @@ -249,8 +246,7 @@ static int __init mv_rtc_probe(struct platform_device *pdev) pdata->rtc = devm_rtc_allocate_device(&pdev->dev); if (IS_ERR(pdata->rtc)) { - ret = PTR_ERR(pdata->rtc); - goto out; + return PTR_ERR(pdata->rtc); } if (pdata->irq >= 0) { @@ -275,9 +271,6 @@ static int __init mv_rtc_probe(struct platform_device *pdev) ret = devm_rtc_register_device(pdata->rtc); if (!ret) return 0; -out: - if (!IS_ERR(pdata->clk)) - clk_disable_unprepare(pdata->clk); return ret; } @@ -288,9 +281,6 @@ static void __exit mv_rtc_remove(struct platform_device *pdev) if (pdata->irq >= 0) device_init_wakeup(&pdev->dev, false); - - if (!IS_ERR(pdata->clk)) - clk_disable_unprepare(pdata->clk); } #ifdef CONFIG_OF From 12b1843fee1fa7282b800a56faa360ee945630d1 Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Thu, 25 Jun 2026 15:02:01 +0200 Subject: [PATCH 0684/1328] rtc: mv: remove mv_rtc_remove Use devm_device_init_wakeup() so we can avoid having to explicitly teardown of module removal. Link: https://patch.msgid.link/20260625130202.1621692-2-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-mv.c | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/drivers/rtc/rtc-mv.c b/drivers/rtc/rtc-mv.c index c0f1bcd838e3..db64c459ee19 100644 --- a/drivers/rtc/rtc-mv.c +++ b/drivers/rtc/rtc-mv.c @@ -260,7 +260,7 @@ static int __init mv_rtc_probe(struct platform_device *pdev) } if (pdata->irq >= 0) - device_init_wakeup(&pdev->dev, true); + devm_device_init_wakeup(&pdev->dev); else clear_bit(RTC_FEATURE_ALARM, pdata->rtc->features); @@ -275,14 +275,6 @@ static int __init mv_rtc_probe(struct platform_device *pdev) return ret; } -static void __exit mv_rtc_remove(struct platform_device *pdev) -{ - struct rtc_plat_data *pdata = platform_get_drvdata(pdev); - - if (pdata->irq >= 0) - device_init_wakeup(&pdev->dev, false); -} - #ifdef CONFIG_OF static const struct of_device_id rtc_mv_of_match_table[] = { { .compatible = "marvell,orion-rtc", }, @@ -313,14 +305,7 @@ static int mv_rtc_resume(struct device *dev) static SIMPLE_DEV_PM_OPS(mv_rtc_pm_ops, mv_rtc_suspend, mv_rtc_resume); -/* - * mv_rtc_remove() lives in .exit.text. For drivers registered via - * module_platform_driver_probe() this is ok because they cannot get unbound at - * runtime. So mark the driver struct with __refdata to prevent modpost - * triggering a section mismatch warning. - */ -static struct platform_driver mv_rtc_driver __refdata = { - .remove = __exit_p(mv_rtc_remove), +static struct platform_driver mv_rtc_driver = { .driver = { .name = "rtc-mv", .of_match_table = of_match_ptr(rtc_mv_of_match_table), From da5e8713aee51dd73310bccf23d28446b39e7b0e Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Mon, 13 Jul 2026 17:22:59 -0700 Subject: [PATCH 0685/1328] rtc: st-lpc: get IRQ via platform_get_irq() Replace irq_of_parse_and_map() with platform_get_irq(), which resolves the interrupt from pdev->dev.of_node directly and returns a positive IRQ or a negative errno (it never returns 0). Propagate the error on failure instead of the previous open-coded "IRQ missing or invalid" / -EINVAL message, so -EPROBE_DEFER is handled correctly. The rtc->irq field is a signed short, so the negative error code is preserved. np is still used by of_property_read_u32() for "st,lpc-mode", so it is not removed. Built for ARM (multi_v7_defconfig + CONFIG_RTC_DRV_ST_LPC) with LLVM=1; drivers/rtc/rtc-st-lpc.o compiles cleanly. Assisted-by: opencode:hy3-free Signed-off-by: Rosen Penev Reviewed-by: Patrice Chotard Link: https://patch.msgid.link/20260714002259.1392655-1-rosenp@gmail.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-st-lpc.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/rtc/rtc-st-lpc.c b/drivers/rtc/rtc-st-lpc.c index c6d4522411b3..ae79ddf24f37 100644 --- a/drivers/rtc/rtc-st-lpc.c +++ b/drivers/rtc/rtc-st-lpc.c @@ -18,7 +18,6 @@ #include #include #include -#include #include #include @@ -212,11 +211,9 @@ static int st_rtc_probe(struct platform_device *pdev) if (IS_ERR(rtc->ioaddr)) return PTR_ERR(rtc->ioaddr); - rtc->irq = irq_of_parse_and_map(np, 0); - if (!rtc->irq) { - dev_err(&pdev->dev, "IRQ missing or invalid\n"); - return -EINVAL; - } + rtc->irq = platform_get_irq(pdev, 0); + if (rtc->irq < 0) + return rtc->irq; ret = devm_request_irq(&pdev->dev, rtc->irq, st_rtc_handler, IRQF_NO_AUTOEN, pdev->name, rtc); From 315d11a89147724528d867268382812913bd2ead Mon Sep 17 00:00:00 2001 From: Bhargav Joshi Date: Tue, 4 Aug 2026 22:35:08 +0530 Subject: [PATCH 0686/1328] dt-bindings: rtc: ti,omap-rtc: Convert to DT schema Convert the Texas Instruments OMAP Real Time Clock (RTC) binding from the legacy text format to the DT schema. Mark 'ti,hwmods' as deprecated as it is no longer used, it is kept to support legacy boards. Signed-off-by: Bhargav Joshi Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260804-ti-omap-rtc-v3-4-ba3bbd8af570@gmail.com Signed-off-by: Alexandre Belloni --- .../devicetree/bindings/rtc/rtc-omap.txt | 53 ------- .../devicetree/bindings/rtc/ti,omap-rtc.yaml | 140 ++++++++++++++++++ 2 files changed, 140 insertions(+), 53 deletions(-) delete mode 100644 Documentation/devicetree/bindings/rtc/rtc-omap.txt create mode 100644 Documentation/devicetree/bindings/rtc/ti,omap-rtc.yaml diff --git a/Documentation/devicetree/bindings/rtc/rtc-omap.txt b/Documentation/devicetree/bindings/rtc/rtc-omap.txt deleted file mode 100644 index 062ebb14cecf..000000000000 --- a/Documentation/devicetree/bindings/rtc/rtc-omap.txt +++ /dev/null @@ -1,53 +0,0 @@ -TI Real Time Clock - -Required properties: -- compatible: - - "ti,da830-rtc" - for RTC IP used similar to that on DA8xx SoC family. - - "ti,am3352-rtc" - for RTC IP used similar to that on AM335x SoC family. - This RTC IP has special WAKE-EN Register to enable - Wakeup generation for event Alarm. It can also be - used to control an external PMIC via the - pmic_power_en pin. - - "ti,am4372-rtc" - for RTC IP used similar to that on AM437X SoC family. -- reg: Address range of rtc register set -- interrupts: rtc timer, alarm interrupts in order - -Optional properties: -- system-power-controller: whether the rtc is controlling the system power - through pmic_power_en -- clocks: Any internal or external clocks feeding in to rtc -- clock-names: Corresponding names of the clocks -- pinctrl-0: a phandle pointing to the pin settings for the device -- pinctrl-names: should be "default" - -Optional subnodes: -- generic pinctrl node - -Required pinctrl subnodes properties: -- pins - Names of ext_wakeup pins to configure - -Optional pinctrl subnodes properties: -- input-enable - Enables ext_wakeup -- ti,active-high - Set input active high (by default active low) - -Example: - -rtc@1c23000 { - compatible = "ti,da830-rtc"; - reg = <0x23000 0x1000>; - interrupts = <19 - 19>; - interrupt-parent = <&intc>; - system-power-controller; - clocks = <&clk_32k_rtc>, <&clk_32768_ck>; - clock-names = "ext-clk", "int-clk"; - - pinctrl-0 = <&ext_wakeup>; - pinctrl-names = "default"; - - ext_wakeup: ext-wakeup { - pins = "ext_wakeup0"; - input-enable; - ti,active-high; - }; -}; diff --git a/Documentation/devicetree/bindings/rtc/ti,omap-rtc.yaml b/Documentation/devicetree/bindings/rtc/ti,omap-rtc.yaml new file mode 100644 index 000000000000..02b3c23cf435 --- /dev/null +++ b/Documentation/devicetree/bindings/rtc/ti,omap-rtc.yaml @@ -0,0 +1,140 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/rtc/ti,omap-rtc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments OMAP Real Time Clock + +maintainers: + - Keerthy + - Afzal Mohammed + +description: + The OMAP RTC is a year/month/day/hours/minutes/seconds BCD clock with + century-range alarm matching, driven by the 32kHz clock. + +properties: + compatible: + oneOf: + - const: ti,da830-rtc + - items: + - const: ti,am3352-rtc + - const: ti,da830-rtc + - items: + - const: ti,am4372-rtc + - const: ti,am3352-rtc + - const: ti,da830-rtc + + reg: + maxItems: 1 + + interrupts: + minItems: 2 + maxItems: 2 + + system-power-controller: + type: boolean + description: + Indicates that this RTC controls system power via the pmic_power_en pin. + + clocks: + minItems: 1 + maxItems: 2 + + clock-names: + minItems: 1 + items: + - enum: [ext-clk, int-clk] + - const: int-clk + + pinctrl-0: + description: + Phandle to pin configuration for the external wakeup pins. + + pinctrl-names: + minItems: 1 + + ti,hwmods: + $ref: /schemas/types.yaml#/definitions/string + description: + Name of the hwmod associated with the RTC. + const: rtc + deprecated: true + +patternProperties: + "^ext-wakeup(-[0-9]+)?$": + type: object + + description: + Child node describing external wakeup pin configuration. + + properties: + pins: + pattern: '^ext_wakeup[0-3]$' + + input-enable: + type: boolean + description: + Enables the external wakeup input on the selected pin. + + ti,active-high: + type: boolean + description: + Sets the wakeup input polarity to active high. By default the + input is active low. + + required: + - pins + + additionalProperties: false + +required: + - compatible + - reg + - interrupts + +allOf: + - $ref: rtc.yaml# + - if: + not: + properties: + compatible: + contains: + const: ti,am3352-rtc + then: + properties: + system-power-controller: false + patternProperties: + "^ext-wakeup(-[0-9]+)?$": false + +unevaluatedProperties: false + +examples: + - | + rtc@23000 { + compatible = "ti,da830-rtc"; + reg = <0x23000 0x1000>; + interrupts = <19>, <19>; + clocks = <&clk_32768_ck>; + clock-names = "int-clk"; + }; + + - | + rtc@0 { + compatible = "ti,am3352-rtc", "ti,da830-rtc"; + reg = <0x0 0x1000>; + interrupts = <75>, <76>; + system-power-controller; + clocks = <&clk_32k_rtc>, <&clk_32768_ck>; + clock-names = "ext-clk", "int-clk"; + + pinctrl-0 = <&ext_wakeup>; + pinctrl-names = "default"; + + ext-wakeup { + pins = "ext_wakeup0"; + input-enable; + ti,active-high; + }; + }; From 1190db8edcd91309e64d8212c1ca8b90a4ed732d Mon Sep 17 00:00:00 2001 From: Udaya Kiran Challa Date: Fri, 3 Jul 2026 16:34:42 +0530 Subject: [PATCH 0687/1328] dt-bindings: rtc: microchip,pic32mzda-rtc: Convert to DT schema Convert Microchip PIC32 Real Time Clock and Calendar devicetree binding from legacy text format to DT schema. Signed-off-by: Udaya Kiran Challa Acked-by: Conor Dooley Link: https://patch.msgid.link/20260703110442.205026-1-challauday369@gmail.com Signed-off-by: Alexandre Belloni --- .../bindings/rtc/microchip,pic32-rtc.txt | 21 -------- .../bindings/rtc/microchip,pic32mzda-rtc.yaml | 50 +++++++++++++++++++ 2 files changed, 50 insertions(+), 21 deletions(-) delete mode 100644 Documentation/devicetree/bindings/rtc/microchip,pic32-rtc.txt create mode 100644 Documentation/devicetree/bindings/rtc/microchip,pic32mzda-rtc.yaml diff --git a/Documentation/devicetree/bindings/rtc/microchip,pic32-rtc.txt b/Documentation/devicetree/bindings/rtc/microchip,pic32-rtc.txt deleted file mode 100644 index 180b7144bfcc..000000000000 --- a/Documentation/devicetree/bindings/rtc/microchip,pic32-rtc.txt +++ /dev/null @@ -1,21 +0,0 @@ -* Microchip PIC32 Real Time Clock and Calendar - -The RTCC keeps time in hours, minutes, and seconds, and one half second. It -provides a calendar in weekday, date, month, and year. It also provides a -configurable alarm. - -Required properties: -- compatible: should be: "microchip,pic32mzda-rtc" -- reg: physical base address of the controller and length of memory mapped - region. -- interrupts: RTC alarm/event interrupt -- clocks: clock phandle - -Example: - - rtc: rtc@1f8c0000 { - compatible = "microchip,pic32mzda-rtc"; - reg = <0x1f8c0000 0x60>; - interrupts = <166 IRQ_TYPE_EDGE_RISING>; - clocks = <&PBCLK6>; - }; diff --git a/Documentation/devicetree/bindings/rtc/microchip,pic32mzda-rtc.yaml b/Documentation/devicetree/bindings/rtc/microchip,pic32mzda-rtc.yaml new file mode 100644 index 000000000000..481ee28c06e3 --- /dev/null +++ b/Documentation/devicetree/bindings/rtc/microchip,pic32mzda-rtc.yaml @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/rtc/microchip,pic32mzda-rtc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Microchip PIC32 Real Time Clock and Calendar + +maintainers: + - Alexandre Belloni + +description: | + The Microchip PIC32 Real Time Clock and Calendar (RTCC) keeps time in hours, + minutes, seconds, and one half second. It also provides a calendar with + weekday, date, month, and year, along with a configurable alarm. + +allOf: + - $ref: rtc.yaml# + +properties: + compatible: + const: microchip,pic32mzda-rtc + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + maxItems: 1 + +required: + - compatible + - reg + - interrupts + - clocks + +additionalProperties: false + +examples: + - | + #include + + rtc@1f8c0000 { + compatible = "microchip,pic32mzda-rtc"; + reg = <0x1f8c0000 0x60>; + interrupts = <166 IRQ_TYPE_EDGE_RISING>; + clocks = <&PBCLK6>; + }; From 745ca1b959d9f08cbfd15891aa74ef4e3e847952 Mon Sep 17 00:00:00 2001 From: Teja Sai Charan Bellamkonda Date: Fri, 10 Jul 2026 03:49:44 +0530 Subject: [PATCH 0688/1328] dt-bindings: rtc: Convert rtc-cmos binding to YAML Convert the rtc-cmos devicetree bindings to dt schema. The original text binding documents only the motorola,mc146818 compatible. Existing in-tree Devicetree sources also use the intel,ce4100-rtc compatible together with the motorola,mc146818 fallback, but this was not documented. Document the Intel variant in the schema so that these existing configurations are accepted during schema validation. Signed-off-by: Teja Sai Charan Bellamkonda Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260709221944.159244-1-tejaasaye@gmail.com Signed-off-by: Alexandre Belloni --- .../devicetree/bindings/rtc/rtc-cmos.txt | 27 -------- .../devicetree/bindings/rtc/rtc-cmos.yaml | 63 +++++++++++++++++++ 2 files changed, 63 insertions(+), 27 deletions(-) delete mode 100644 Documentation/devicetree/bindings/rtc/rtc-cmos.txt create mode 100644 Documentation/devicetree/bindings/rtc/rtc-cmos.yaml diff --git a/Documentation/devicetree/bindings/rtc/rtc-cmos.txt b/Documentation/devicetree/bindings/rtc/rtc-cmos.txt deleted file mode 100644 index 7d7b5f6bda65..000000000000 --- a/Documentation/devicetree/bindings/rtc/rtc-cmos.txt +++ /dev/null @@ -1,27 +0,0 @@ - Motorola mc146818 compatible RTC -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Required properties: - - compatible : "motorola,mc146818" - - reg : should contain registers location and length. - -Optional properties: - - interrupts : should contain interrupt. - - ctrl-reg : Contains the initial value of the control register also - called "Register B". - - freq-reg : Contains the initial value of the frequency register also - called "Register A". - -"Register A" and "B" are usually initialized by the firmware (BIOS for -instance). If this is not done, it can be performed by the driver. - -ISA Example: - - rtc@70 { - compatible = "motorola,mc146818"; - interrupts = <8 3>; - interrupt-parent = <&ioapic1>; - ctrl-reg = <2>; - freq-reg = <0x26>; - reg = <1 0x70 2>; - }; diff --git a/Documentation/devicetree/bindings/rtc/rtc-cmos.yaml b/Documentation/devicetree/bindings/rtc/rtc-cmos.yaml new file mode 100644 index 000000000000..e37927e9916c --- /dev/null +++ b/Documentation/devicetree/bindings/rtc/rtc-cmos.yaml @@ -0,0 +1,63 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/rtc/rtc-cmos.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Motorola mc146818 compatible RTC + +maintainers: + - Alexandre Belloni + +properties: + compatible: + oneOf: + - const: motorola,mc146818 + + - items: + - const: intel,ce4100-rtc + - const: motorola,mc146818 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + ctrl-reg: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + Initial value of the control register + (also known as Register B). + + freq-reg: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + Initial value of the frequency register + (also known as Register A). + +required: + - compatible + - reg + +allOf: + - $ref: rtc.yaml# + +unevaluatedProperties: false + +examples: + - | + bus { + #address-cells = <2>; + #size-cells = <1>; + + rtc@1,70 { + compatible = "motorola,mc146818"; + reg = <0x1 0x70 0x2>; + + interrupts = <8 3>; + + ctrl-reg = <2>; + freq-reg = <0x26>; + }; + }; From 4d31434fea516f10ccff35db4a21773780f08cee Mon Sep 17 00:00:00 2001 From: Eduard Bostina Date: Sun, 19 Jul 2026 14:10:07 +0000 Subject: [PATCH 0689/1328] dt-bindings: rtc: Convert TI Palmas RTC to DT schema Convert the Texas Instruments Palmas RTC controller bindings to DT schema. As part of the conversion, declare 'wakeup-source: true'. This documents the Palmas PMIC's capability to wake the system. Signed-off-by: Eduard Bostina Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260719141008.3562347-2-egbostina@gmail.com Signed-off-by: Alexandre Belloni --- .../devicetree/bindings/rtc/rtc-palmas.txt | 32 ----------- .../bindings/rtc/ti,palmas-rtc.yaml | 57 +++++++++++++++++++ 2 files changed, 57 insertions(+), 32 deletions(-) delete mode 100644 Documentation/devicetree/bindings/rtc/rtc-palmas.txt create mode 100644 Documentation/devicetree/bindings/rtc/ti,palmas-rtc.yaml diff --git a/Documentation/devicetree/bindings/rtc/rtc-palmas.txt b/Documentation/devicetree/bindings/rtc/rtc-palmas.txt deleted file mode 100644 index c6cf37758a77..000000000000 --- a/Documentation/devicetree/bindings/rtc/rtc-palmas.txt +++ /dev/null @@ -1,32 +0,0 @@ -Palmas RTC controller bindings - -Required properties: -- compatible: - - "ti,palmas-rtc" for palma series of the RTC controller -- interrupts: Interrupt number of RTC submodule on device. - -Optional properties: - -- ti,backup-battery-chargeable: The Palmas series device like TPS65913 or - TPS80036 supports the backup battery for powering the RTC when main - battery is removed or in very low power state. The backup battery - can be chargeable or non-chargeable. This flag will tells whether - battery is chargeable or not. If charging battery then driver can - enable the charging. -- ti,backup-battery-charge-high-current: Enable high current charging in - backup battery. Device supports the < 100uA and > 100uA charging. - The high current will be > 100uA. Absence of this property will - charge battery to lower current i.e. < 100uA. - -Example: - palmas: tps65913@58 { - ... - palmas_rtc: rtc { - compatible = "ti,palmas-rtc"; - interrupt-parent = <&palmas>; - interrupts = <8 0>; - ti,backup-battery-chargeable; - ti,backup-battery-charge-high-current; - }; - ... - }; diff --git a/Documentation/devicetree/bindings/rtc/ti,palmas-rtc.yaml b/Documentation/devicetree/bindings/rtc/ti,palmas-rtc.yaml new file mode 100644 index 000000000000..ac64f0589c84 --- /dev/null +++ b/Documentation/devicetree/bindings/rtc/ti,palmas-rtc.yaml @@ -0,0 +1,57 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/rtc/ti,palmas-rtc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments Palmas RTC + +maintainers: + - Eduard Bostina + +allOf: + - $ref: /schemas/rtc/rtc.yaml# + +properties: + compatible: + const: ti,palmas-rtc + + interrupts: + maxItems: 1 + + wakeup-source: true + + ti,backup-battery-chargeable: + type: boolean + description: + The backup battery can be chargeable or non-chargeable. This flag + indicates whether the battery is chargeable. If present, the driver + can enable charging. + + ti,backup-battery-charge-high-current: + type: boolean + description: + Enable high current charging in the backup battery. + Device supports the < 100uA and > 100uA charging. The high current will + be > 100uA. Absence of this property will charge battery to lower + current i.e. < 100uA. + +required: + - compatible + - interrupts + +unevaluatedProperties: false + +examples: + - | + pmic { + #address-cells = <1>; + #size-cells = <0>; + + rtc { + compatible = "ti,palmas-rtc"; + interrupts = <8 0>; + ti,backup-battery-chargeable; + ti,backup-battery-charge-high-current; + }; + }; From b1407862fb57c6455a04fa5d0d02b58496a38cc9 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Fri, 24 Jul 2026 03:45:31 +0900 Subject: [PATCH 0690/1328] rtc: pcf2127: remove conditional return with no effect Both branches of the check return the same value, so the check has no effect. Remove it and return the value directly. This is the result of running the Coccinelle script from scripts/coccinelle/misc/cond_return_no_effect.cocci. Signed-off-by: Sang-Heon Jeon Reviewed-by: Bruno Thomsen Link: https://patch.msgid.link/20260723184538.3888637-30-ekffu200098@gmail.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-pcf2127.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/rtc/rtc-pcf2127.c b/drivers/rtc/rtc-pcf2127.c index 1995e9f2756d..ff2023908f5a 100644 --- a/drivers/rtc/rtc-pcf2127.c +++ b/drivers/rtc/rtc-pcf2127.c @@ -1184,12 +1184,7 @@ static int pcf2127_configure_interrupt_pins(struct device *dev) if (ret) return ret; - ret = regmap_write(pcf2127->regmap, - PCF2131_REG_INT_A_MASK2, 0); - if (ret) - return ret; - - return ret; + return regmap_write(pcf2127->regmap, PCF2131_REG_INT_A_MASK2, 0); } static int pcf2127_probe(struct device *dev, struct regmap *regmap, From 878d93aaa596a861dbc8d71f7c38e62a2e805f01 Mon Sep 17 00:00:00 2001 From: Robert Leussler Date: Tue, 21 Jul 2026 08:19:07 +0000 Subject: [PATCH 0691/1328] rtc: ds1307: fix RX8130 wakeup alarm WADA bit for day-of-month mode The RX8130 wakeup alarm never fired when set via /sys/class/rtc/rtc0/wakealarm. The root cause is that the WADA bit (bit 3) in the Extension register (0x1c) was never set before programming the alarm registers. Per the RX8130 datasheet: WADA=0 - Week alarm: register 0x19 is compared against day-of-week WADA=1 - Day alarm: register 0x19 is compared against day-of-month rx8130_set_alarm() always writes a BCD day-of-month value to alarm register 0x19, so WADA must be 1. With WADA=0 the hardware matched the day-of-month value (e.g. 15) as a day-of-week index, which is always out of range (valid weekdays are 0-6), so the alarm interrupt was never asserted. Fix by setting the WADA bit in rx8130_set_alarm() before writing the Extension register back to the device. This is consistent with rx8130_read_alarm(), which decodes the same register under the same assumption (24-hour and day-of-month mode). Signed-off-by: Robert Leussler Link: https://patch.msgid.link/20260721081907.3518648-1-robert.leussler@leica-geosystems.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-ds1307.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/rtc/rtc-ds1307.c b/drivers/rtc/rtc-ds1307.c index 0707ded5368b..6f3dcb6483b9 100644 --- a/drivers/rtc/rtc-ds1307.c +++ b/drivers/rtc/rtc-ds1307.c @@ -662,7 +662,7 @@ static int rx8130_set_alarm(struct device *dev, struct rtc_wkalrm *t) if (ret < 0) return ret; - ctl[0] &= RX8130_REG_EXTENSION_WADA; + ctl[0] |= RX8130_REG_EXTENSION_WADA; ctl[1] &= ~RX8130_REG_FLAG_AF; ctl[2] &= ~RX8130_REG_CONTROL0_AIE; From ca45cfa74370644d371b552bef57938c19e3c80c Mon Sep 17 00:00:00 2001 From: Linkai Gong Date: Fri, 31 Jul 2026 16:04:58 +0800 Subject: [PATCH 0692/1328] rtc: gamecube: check return value of devm_rtc_register_device() gamecube_rtc_probe() ignored the return value of devm_rtc_register_device() and always returned success. Propagate the error so probe fails when RTC registration fails. Fixes: 86559400b3ef ("rtc: gamecube: Add a RTC driver for the GameCube, Wii and Wii U") Signed-off-by: Linkai Gong Link: https://patch.msgid.link/20260731080458.417532-1-gonglinkai@kylinos.cn Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-gamecube.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/rtc/rtc-gamecube.c b/drivers/rtc/rtc-gamecube.c index 045d5d45ab4b..889028cecf4a 100644 --- a/drivers/rtc/rtc-gamecube.c +++ b/drivers/rtc/rtc-gamecube.c @@ -355,9 +355,7 @@ static int gamecube_rtc_probe(struct platform_device *pdev) rtc->range_max = U32_MAX; rtc->ops = &gamecube_rtc_ops; - devm_rtc_register_device(rtc); - - return 0; + return devm_rtc_register_device(rtc); } static const struct of_device_id gamecube_rtc_of_match[] = { From 3ae1674975d48fb6d343d03ff7961abd6904af1a Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Wed, 19 Aug 2026 21:05:15 +0200 Subject: [PATCH 0693/1328] clk: ti: Make sure clk_init_data is fully initialized The clk_init_data structure contains several mutually-exclusive members for different methods to specify the possible parents of a clock, prompting drivers to initialize only the members they need. However, not initializing all members may cause subtle issues, which are only exposed when CONFIG_INIT_STACK_ALL_PATTERN or CONFIG_INIT_STACK_NONE is enabled. _register_mux() fills in init.parent_data, and assumes that init.parent_names is NULL. However, the latter is uninitialized, and thus may cause a crash. Make sure all members are fully initialized, to fix such bugs, and to avoid future breakage when converting drivers to a different method for specifying the parents. Fixes: 667f420c09f1417c ("clk: ti: mux: resolve parent clocks by DT index, not by name") Closes: https://lore.kernel.org/CAMuHMdU3yVqoyHC4eNF2NuYo8wy+6ODLoYat4R71X99Mxc_=kw@mail.gmail.com Signed-off-by: Geert Uytterhoeven Reviewed-by: Brian Masney Reviewed-by: Mathieu Dubois-Briand Signed-off-by: Stephen Boyd --- drivers/clk/ti/adpll.c | 4 ++-- drivers/clk/ti/divider.c | 2 +- drivers/clk/ti/mux.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/clk/ti/adpll.c b/drivers/clk/ti/adpll.c index e305fcbac647..8885d28face5 100644 --- a/drivers/clk/ti/adpll.c +++ b/drivers/clk/ti/adpll.c @@ -483,7 +483,7 @@ static const struct clk_ops ti_adpll_ops = { static int ti_adpll_init_dco(struct ti_adpll_data *d) { - struct clk_init_data init; + struct clk_init_data init = {}; struct clk *clock; const char *postfix; int width, err; @@ -576,7 +576,7 @@ static int ti_adpll_init_clkout(struct ti_adpll_data *d, struct clk *clk1) { struct ti_adpll_clkout_data *co; - struct clk_init_data init; + struct clk_init_data init = {}; struct clk_ops *ops; const char *parent_names[2]; const char *child_name; diff --git a/drivers/clk/ti/divider.c b/drivers/clk/ti/divider.c index af923b8cb0ed..3b438c2d68aa 100644 --- a/drivers/clk/ti/divider.c +++ b/drivers/clk/ti/divider.c @@ -311,7 +311,7 @@ static struct clk *_register_divider(struct device_node *node, u32 flags, struct clk_omap_divider *div) { - struct clk_init_data init; + struct clk_init_data init = {}; const char *parent_name; const char *name; diff --git a/drivers/clk/ti/mux.c b/drivers/clk/ti/mux.c index ded4432f7528..0fef60e82107 100644 --- a/drivers/clk/ti/mux.c +++ b/drivers/clk/ti/mux.c @@ -124,9 +124,9 @@ static struct clk *_register_mux(struct device_node *node, const char *name, struct clk_omap_reg *reg, u8 shift, u32 mask, s8 latch, u8 clk_mux_flags, u32 *table) { + struct clk_init_data init = {}; struct clk_omap_mux *mux; struct clk *clk; - struct clk_init_data init; /* allocate the mux */ mux = kzalloc_obj(*mux); From 39c0e6c844a14945040cca4ccf6997792ab764c4 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Wed, 19 Aug 2026 21:05:16 +0200 Subject: [PATCH 0694/1328] clk: visconti: Make sure clk_init_data is fully initialized The clk_init_data structure contains several mutually-exclusive members for different methods to specify the possible parents of a clock, prompting drivers to initialize only the members they need. However, not initializing all members may cause subtle issues, which are only exposed when CONFIG_INIT_STACK_ALL_PATTERN or CONFIG_INIT_STACK_NONE is enabled. visconti_clk_register_gate() fills in init.parent_data, and assumes that init.parent_names is NULL. However, the latter in uninitialized, and thus may cause a crash. Make sure all members are fully initialized, to fix such bugs, and to avoid future breakage when converting drivers to a different method for specifying the parents. Fixes: b4cbe606dc3674b2 ("clk: visconti: Add support common clock driver and reset driver") Signed-off-by: Geert Uytterhoeven Reviewed-by: Brian Masney Reviewed-by: Nobuhiro Iwamatsu Signed-off-by: Stephen Boyd --- drivers/clk/visconti/clkc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/visconti/clkc.c b/drivers/clk/visconti/clkc.c index d0b193b5d0b3..4018d1298880 100644 --- a/drivers/clk/visconti/clkc.c +++ b/drivers/clk/visconti/clkc.c @@ -81,9 +81,9 @@ static struct clk_hw *visconti_clk_register_gate(struct device *dev, u8 rs_idx, spinlock_t *lock) { + struct clk_init_data init = {}; struct visconti_clk_gate *gate; struct clk_parent_data *pdata; - struct clk_init_data init; struct clk_hw *hw; int ret; From fbfa013eeac299ecc015cb14fa40e382a23fb489 Mon Sep 17 00:00:00 2001 From: Pedro Kopper Date: Fri, 14 Aug 2026 10:22:13 +0100 Subject: [PATCH 0695/1328] clk: microchip: mpfs: fix regmap_update_bits() mask/val order mpfs_cfg_clk_set_rate() passes the mask and value arguments to regmap_update_bits() in the wrong order. The resulting write becomes reg = orig_reg | val, causing bits to not be cleared if the clock divider changes. Pass the arguments in the correct order so the divider field is updated as intended. Fixes: c6f2dddfa7f9 ("clk: microchip: mpfs: use regmap for clocks") Signed-off-by: Pedro Kopper Reviewed-by: Conor Dooley Cc: stable@vger.kernel.org Signed-off-by: Stephen Boyd --- drivers/clk/microchip/clk-mpfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/microchip/clk-mpfs.c b/drivers/clk/microchip/clk-mpfs.c index ee58304913ef..7f044775aea8 100644 --- a/drivers/clk/microchip/clk-mpfs.c +++ b/drivers/clk/microchip/clk-mpfs.c @@ -285,7 +285,7 @@ static int mpfs_cfg_clk_set_rate(struct clk_hw *hw, unsigned long rate, unsigned mask = clk_div_mask(cfg->width) << cfg->shift; val = divider_setting << cfg->shift; - regmap_update_bits(cfg->map, cfg->map_offset, val, mask); + regmap_update_bits(cfg->map, cfg->map_offset, mask, val); return 0; } From d736eba9c453fedce664fdf592c8b71ecff1932b Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Thu, 20 Aug 2026 09:41:17 +0800 Subject: [PATCH 0696/1328] ALSA: ice1712: Fix the card leak at probe error with the auto-cleanup snd_ice1712_probe() performs multiple initialization steps after snd_card_new(), but directly returns on failures from later steps without releasing the ALSA card, causing resource leaks when probing fails. Use snd_devm_card_new() together with scope-based cleanup via __free(snd_card_unref), and clear the card pointer after successful registration to keep it alive. Fixes: ca642da4b33d ("ALSA: ice1712: Allocate resources with device-managed APIs") Suggested-by: Takashi Iwai Signed-off-by: Haotian Zhang Link: https://patch.msgid.link/20260820014117.14044-1-vulab@iscas.ac.cn Signed-off-by: Takashi Iwai --- sound/pci/ice1712/ice1712.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sound/pci/ice1712/ice1712.c b/sound/pci/ice1712/ice1712.c index 7d1a357ed90d..d6bfd8fb20e6 100644 --- a/sound/pci/ice1712/ice1712.c +++ b/sound/pci/ice1712/ice1712.c @@ -2523,7 +2523,7 @@ static int snd_ice1712_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_unref) = NULL; struct snd_ice1712 *ice; int pcm_dev = 0, err; const struct snd_ice1712_card_info * const *tbl, *c; @@ -2535,8 +2535,8 @@ static int snd_ice1712_probe(struct pci_dev *pci, return -ENOENT; } - err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, - sizeof(*ice), &card); + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + sizeof(*ice), &card); if (err < 0) return err; ice = card->private_data; @@ -2640,6 +2640,7 @@ static int snd_ice1712_probe(struct pci_dev *pci, if (err < 0) return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } From e711ebfadc9a9b7ee9fcb6b5c8c6b1bfc0cc4a87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Habil=20Eren=20T=C3=BCrker?= Date: Wed, 19 Aug 2026 20:08:59 +0300 Subject: [PATCH 0697/1328] ALSA: hda/realtek: Add quirk for HP Laptop 15-fd0039nt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch adds a quirk for the HP Laptop 15-fd0039nt (SSID 103c:8bb6) to fix the micmute LED functionality. The micmute LED is controlled via GPIO 0 with active-low polarity. Tested-by: Habil Eren Türker Signed-off-by: Habil Eren Türker Link: https://patch.msgid.link/20260819170919.3172-1-habilerenturker@hotmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index cae327e1d90d..e3adcd114a36 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -1370,6 +1370,16 @@ static void alc269_fixup_hp_gpio_led(struct hda_codec *codec, alc_fixup_hp_gpio_led(codec, action, 0x08, 0x10); } +static void alc236_fixup_hp_micmute_led_only(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) + spec->micmute_led_polarity = 1; + alc_fixup_hp_gpio_led(codec, action, 0x00, 0x01); +} + static void alc285_fixup_hp_gpio_led(struct hda_codec *codec, const struct hda_fixup *fix, int action) { @@ -4152,6 +4162,7 @@ enum { ALC236_FIXUP_HP_GPIO_LED, ALC236_FIXUP_HP_MUTE_LED, ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF, + ALC236_FIXUP_HP_MICMUTE_LED_ONLY, ALC236_FIXUP_LENOVO_INV_DMIC, ALC298_FIXUP_SAMSUNG_AMP, ALC298_FIXUP_SAMSUNG_AMP_V2_2_AMPS, @@ -5913,6 +5924,10 @@ static const struct hda_fixup alc269_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc236_fixup_hp_mute_led_micmute_gpio, }, + [ALC236_FIXUP_HP_MICMUTE_LED_ONLY] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc236_fixup_hp_micmute_led_only, + }, [ALC236_FIXUP_LENOVO_INV_DMIC] = { .type = HDA_FIXUP_FUNC, .v.func = alc_fixup_inv_dmic, @@ -7448,6 +7463,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8b97, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), SND_PCI_QUIRK(0x103c, 0x8bb3, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8bb4, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8bb6, "HP Laptop 15-fd0039nt", ALC236_FIXUP_HP_MICMUTE_LED_ONLY), SND_PCI_QUIRK(0x103c, 0x8bbe, "HP Victus 16-r0xxx (MB 8BBE)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8bc8, "HP Victus 15-fa1xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8bcd, "HP Omen 16-xd0xxx", ALC245_FIXUP_HP_MUTE_LED_V1_COEFBIT), From 2b366b833f52a37f70be845892c1f5fe1d993051 Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Thu, 20 Aug 2026 15:37:44 +0800 Subject: [PATCH 0698/1328] ALSA: hda/conexant: Simplify headset plugin type handling Both the CTIA and OMTP branches of cx_process_headset_plugin() write the same value to the headset-mic pin, differing only in the debug message. Merge the two branches into a single one, leaving the codec write unchanged. No functional change. Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20260820073744.521680-1-zhangheng@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/codecs/conexant.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/sound/hda/codecs/conexant.c b/sound/hda/codecs/conexant.c index c517334b56fd..8e950c796209 100644 --- a/sound/hda/codecs/conexant.c +++ b/sound/hda/codecs/conexant.c @@ -223,11 +223,9 @@ static void cx_process_headset_plugin(struct hda_codec *codec) count++; } while (count < 3); val = snd_hda_codec_read(codec, 0x1c, 0, 0xcb0, 0x0); - if (val & 0x800) { - codec_dbg(codec, "headset plugin, type is CTIA\n"); - snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24); - } else if (val & 0x400) { - codec_dbg(codec, "headset plugin, type is OMTP\n"); + if (val & 0xc00) { + codec_dbg(codec, "headset plugin, type is %s\n", + val & 0x800 ? "CTIA" : "OMTP"); snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24); } else { codec_dbg(codec, "headphone plugin\n"); From b2702908ee23ef31bfcf241a2e07ace0eb76bd71 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Wed, 19 Aug 2026 21:05:19 +0200 Subject: [PATCH 0699/1328] spi: amlogic-spisg: Make sure clk_init_data is fully initialized The clk_init_data structure contains several mutually-exclusive members for different methods to specify the possible parents of a clock, prompting drivers to initialize only the members they need. However, not initializing all members may cause subtle issues, which are only exposed when CONFIG_INIT_STACK_ALL_PATTERN or CONFIG_INIT_STACK_NONE is enabled. aml_spisg_clk_init() fills in init.parent_data, and assumes that init.parent_names is NULL. However, the latter in uninitialized, and thus may cause a crash. Make sure all members are fully initialized, to fix such bugs, and to avoid future breakage when converting drivers to a different method for specifying the parents. Fixes: cef9991e04aed330 ("spi: Add Amlogic SPISG driver") Signed-off-by: Geert Uytterhoeven Reviewed-by: Brian Masney Reviewed-by: Xianwei Zhao Link: https://patch.msgid.link/9fb35ae0aedb7a6db0db6c78a8193c7602dd9d44.1787165329.git.geert+renesas@glider.be Signed-off-by: Mark Brown --- drivers/spi/spi-amlogic-spisg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/spi-amlogic-spisg.c b/drivers/spi/spi-amlogic-spisg.c index afc8af04638d..9049a87e9d0f 100644 --- a/drivers/spi/spi-amlogic-spisg.c +++ b/drivers/spi/spi-amlogic-spisg.c @@ -636,7 +636,7 @@ static int aml_spisg_target_abort(struct spi_controller *ctlr) static int aml_spisg_clk_init(struct spisg_device *spisg, void __iomem *base) { struct device *dev = &spisg->pdev->dev; - struct clk_init_data init; + struct clk_init_data init = {}; struct clk_divider *div; struct clk_div_table *tbl; char name[32]; From a9c2f0d401fd1e11ce59e4243946a59bd818e8c5 Mon Sep 17 00:00:00 2001 From: Li Jun Date: Thu, 20 Aug 2026 17:23:51 +0800 Subject: [PATCH 0700/1328] spi: loongson: pm: add .freeze .poweroff .thaw .restore after execute s4, the spi error, [ 1104.754246][ 4] [ T1] tpm_tis_spi spi-SMO0768:00: SPI transfer failed: -110 [ 1104.761503][ 4] [ T1] spi_master spi1: failed to transfer one message from queue [ 1104.769201][ 4] [ T1] spi_master spi1: noqueue transfer failed [ 1104.776344][ 4] [ T1] tpm_tis_spi spi-SMO0768:00: SPI transfer failed: -110 [ 1104.783609][ 4] [ T1] spi_master spi1: failed to transfer one message from queue [ 1104.791308][ 4] [ T1] spi_master spi1: noqueue transfer failed [ 1104.797446][ 4] [ T1] gttadd tpm_chip_start1 ret = -110 and in s4 the loongson_spi_resume&suspend are not called at all. use DEFINE_SIMPLE_DEV_PM_OPS() add .freeze .poweroff .thaw .restore, after s4 the spi communication is normal. Signed-off-by: Li Jun Link: https://patch.msgid.link/20260820092351.101605-1-lijun01@kylinos.cn Signed-off-by: Mark Brown --- drivers/spi/spi-loongson-core.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/spi/spi-loongson-core.c b/drivers/spi/spi-loongson-core.c index 30b6a7176ce9..6ba7c56b1bdd 100644 --- a/drivers/spi/spi-loongson-core.c +++ b/drivers/spi/spi-loongson-core.c @@ -269,10 +269,8 @@ static int loongson_spi_resume(struct device *dev) return 0; } -const struct dev_pm_ops loongson_spi_dev_pm_ops = { - .suspend = loongson_spi_suspend, - .resume = loongson_spi_resume, -}; +DEFINE_SIMPLE_DEV_PM_OPS(loongson_spi_dev_pm_ops, loongson_spi_suspend, loongson_spi_resume); + EXPORT_SYMBOL_NS_GPL(loongson_spi_dev_pm_ops, "SPI_LOONGSON_CORE"); MODULE_DESCRIPTION("Loongson SPI core driver"); From dfb8bc1d286fd97b7cf47e3af5df84cfb068d214 Mon Sep 17 00:00:00 2001 From: Jack Yu Date: Thu, 20 Aug 2026 17:24:15 +0800 Subject: [PATCH 0701/1328] ASoC: rt712-sdca: Pull high JD2 to prevent from entering TV mode Add a patch setting to prevent ALC712 from entering TV mode which may cause unexpected issues. Signed-off-by: Jack Yu Link: https://patch.msgid.link/20260820092415.2979641-1-jack.yu@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt712-sdca.c | 1 + sound/soc/codecs/rt712-sdca.h | 1 + 2 files changed, 2 insertions(+) diff --git a/sound/soc/codecs/rt712-sdca.c b/sound/soc/codecs/rt712-sdca.c index 2218f9918ae3..13574513b181 100644 --- a/sound/soc/codecs/rt712-sdca.c +++ b/sound/soc/codecs/rt712-sdca.c @@ -1779,6 +1779,7 @@ static void rt712_sdca_vb_io_init(struct rt712_sdca_priv *rt712) dev_dbg(dev, "%s jack/mic/amp func_status=0x%x, 0x%x, 0x%x\n", __func__, jack_func_status, mic_func_status, amp_func_status); + rt712_sdca_index_write(rt712, RT712_VENDOR_REG, RT712_JD_CTL3, 0x7778); /* DMIC */ if ((mic_func_status & FUNCTION_NEEDS_INITIALIZATION) || (!rt712->first_hw_init)) { rt712_sdca_index_write(rt712, RT712_VENDOR_HDA_CTL, RT712_DMIC2_FU_IT_FLOAT_CTL, 0x1526); diff --git a/sound/soc/codecs/rt712-sdca.h b/sound/soc/codecs/rt712-sdca.h index 7ab7d5feb50a..46740281a5c1 100644 --- a/sound/soc/codecs/rt712-sdca.h +++ b/sound/soc/codecs/rt712-sdca.h @@ -74,6 +74,7 @@ struct rt712_dmic_kctrl_priv { #define RT712_JD_PRODUCT_NUM 0x00 #define RT712_ANALOG_BIAS_CTL3 0x04 #define RT712_JD_CTL1 0x09 +#define RT712_JD_CTL3 0x0b #define RT712_IO_CTL 0x0c #define RT712_LDO2_3_CTL1 0x0e #define RT712_PARA_VERB_CTL 0x1a From 13087ad7817e6e5f210064518bfc4d6d58a9c2f3 Mon Sep 17 00:00:00 2001 From: Nitin Gote Date: Fri, 14 Aug 2026 13:11:07 +0530 Subject: [PATCH 0702/1328] drm/xe: don't WARN on kernel job timeout when device already wedged igt@xe_wedged@wedged-at-any-timeout wedges the device in mode 2 (UPON_ANY_HANG_NO_RESET) and then rebinds the driver. During unbind, a GSC proxy kernel submission can still time out; with the device wedged and the GuC CT stopped it can never complete, so its kernel job times out. Tile0: GT1: Kernel-submitted job timed out WARNING: drivers/gpu/drm/xe/xe_guc_submit.c:... at guc_exec_queue_timedout_job() Workqueue: gt-ordered-wq drm_sched_job_timedout Killed queues skip guc_submit_hint_wedged(), leaving 'wedged' false even though the device is already wedged. The timeout handler then treats the kernel queue timeout as unexpected and taints the kernel. Honour an already-wedged device even for killed queues so the expected teardown timeout no longer trips the WARN. Fixes: 5a2f117a80c2 ("drm/xe: Do not wedge device on killed exec queues") Cc: Matthew Brost Signed-off-by: Nitin Gote Reviewed-by: Tejas Upadhyay Link: https://patch.msgid.link/20260814074106.92670-2-nitin.r.gote@intel.com Signed-off-by: Tejas Upadhyay (cherry picked from commit a1c1dbd0f047bb05de6aaf6abe9103031179bf19) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_guc_submit.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_guc_submit.c b/drivers/gpu/drm/xe/xe_guc_submit.c index 8aaed4fd13ea..864859bd0ccc 100644 --- a/drivers/gpu/drm/xe/xe_guc_submit.c +++ b/drivers/gpu/drm/xe/xe_guc_submit.c @@ -1559,8 +1559,14 @@ guc_exec_queue_timedout_job(struct drm_sched_job *drm_job) if (!skip_timeout_check && !check_timeout(q, job)) goto rearm; + /* + * Killed queues must not newly wedge the device, but preserve an + * already-wedged state to avoid warning on teardown timeouts. + */ if (!exec_queue_killed(q)) wedged = guc_submit_hint_wedged(exec_queue_to_guc(q)); + else + wedged = xe_device_wedged(xe); set_exec_queue_banned(q); From c73cb5b7c159246dd572277c668851a56e516019 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Thu, 20 Aug 2026 19:27:49 +0800 Subject: [PATCH 0703/1328] ALSA: mtpav: shut down output timer before card teardown snd_mtpav_output_timer() rearms chip->timer while holding chip->spinlock and accesses the card-private mtpav state. snd_mtpav_free() currently takes the same lock and calls timer_delete() when the timer is active. This only removes a pending timer; it does not wait for a callback that is already running and does not prevent the callback from rearming the timer. A callback running on another CPU can therefore continue after snd_mtpav_free() releases the lock and access the card-private state while the card is being torn down. It can also rearm the timer after timer_delete() has returned. Call timer_shutdown_sync() without holding chip->spinlock. This waits for any running callback to finish and prevents further rearming before the card-private mtpav state is released. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Runyu Xiao Link: https://patch.msgid.link/20260820112749.9195-1-runyu.xiao@seu.edu.cn Signed-off-by: Takashi Iwai --- sound/drivers/mtpav.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sound/drivers/mtpav.c b/sound/drivers/mtpav.c index d31eadf4be5f..1ed6d4f0cb62 100644 --- a/sound/drivers/mtpav.c +++ b/sound/drivers/mtpav.c @@ -642,9 +642,7 @@ static void snd_mtpav_free(struct snd_card *card) { struct mtpav *crd = card->private_data; - guard(spinlock_irqsave)(&crd->spinlock); - if (crd->istimer > 0) - snd_mtpav_remove_output_timer(crd); + timer_shutdown_sync(&crd->timer); } /* From 41d60cbfde10b9f01ae6e2d3195463fbad6e54a8 Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Thu, 20 Aug 2026 21:06:43 +0800 Subject: [PATCH 0704/1328] ALSA: hda/realtek: Fix bass speaker DAC routing for Lenovo Yoga Pro 9 16IAH10 The Lenovo Yoga Pro 9 16IAH10 (PCI SSID 17aa:3846, codec SSID 17aa:3920) uses a TAS2781 smart amplifier driven via the ALC287 codec. On this machine the bass speaker pin (NID 0x17) is by default routed to DAC 0x03, which has no volume amplifier capability, resulting in the right-side woofer being barely audible while only the tweeter plays. The Windows driver configures NID 0x17 to DAC 0x02, which is shared with the main speaker and carries proper amp control. Currently this machine has no PCI SSID quirk, so it falls through to the codec SSID fallback and matches the existing 0x17aa:0x3920 entry intended for the Yoga S990-16, applying only the TAS2781 I2C init without the DAC routing fix. Add a dedicated quirk for PCI SSID 17aa:3846 that forces NID 0x17 to DAC 0x02 via alc285_fixup_speaker2_to_dac1() and chains to ALC287_FIXUP_TXNW2781_I2C so the TAS2781 amplifier initialization is preserved. Link: https://bugzilla.kernel.org/show_bug.cgi?id=220540 Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20260820130644.76093-1-zhangheng@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index e3adcd114a36..dd57e0b00a85 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -4308,6 +4308,7 @@ enum { ALC287_FIXUP_LENOVO_LEGION_AW88399, ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN_HEADSET, ALC285_LENOVO_DAC_RENAME, + ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1, }; /* A special fixup for Lenovo C940 and Yoga Duet 7; @@ -7025,6 +7026,12 @@ static const struct hda_fixup alc269_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc285_lenovo_dac_rename, }, + [ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_speaker2_to_dac1, + .chained = true, + .chain_id = ALC287_FIXUP_TXNW2781_I2C, + }, }; static const struct hda_quirk alc269_fixup_tbl[] = { @@ -8047,6 +8054,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x3834, "Lenovo IdeaPad Slim 9i 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x383d, "Legion Y9000X 2019", ALC285_FIXUP_LEGION_Y9000X_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x3843, "Lenovo Yoga 9i / Yoga Book 9i", ALC287_FIXUP_LENOVO_YOGA_BOOK_9I), + SND_PCI_QUIRK(0x17aa, 0x3846, "Lenovo Yoga Pro 9 16IAH10", ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1), /* Yoga Pro 7 14IMH9 shares PCI SSID 17aa:3847 with Legion 7 16ACHG6; * use codec SSID to distinguish them */ From 7e77c09e23dae1ff8bd8598c3c96f9db6862eddd Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Thu, 20 Aug 2026 21:06:44 +0800 Subject: [PATCH 0705/1328] ALSA: hda/realtek: Reorder quirk entries for Lenovo Legion Pro 7 16ARX8H Reorder the HDA_CODEC_QUIRK entries for 0x17aa:0x38a7 and 0x17aa:0x38a8 so that the AMD variant (TAS2781) is matched before the Intel variant's PCI SSID (0x386f) which uses a different amplifier. This ensures the correct fixup (ALC287_FIXUP_TAS2781_I2C) is applied to the AMD model, restoring internal speaker functionality. Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20260820130644.76093-2-zhangheng@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index dd57e0b00a85..fe553db5b52f 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -8074,8 +8074,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = { HDA_CODEC_QUIRK(0x17aa, 0x386a, "Lenovo Yoga 7 16IAP7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), HDA_CODEC_QUIRK(0x17aa, 0x386e, "Legion Y9000X 2022 IAH7", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x386e, "Yoga Pro 7 14ARP8", ALC285_FIXUP_YOGA_SPEAKER2_TO_DAC1), - HDA_CODEC_QUIRK(0x17aa, 0x38a8, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ HDA_CODEC_QUIRK(0x17aa, 0x38a7, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ + HDA_CODEC_QUIRK(0x17aa, 0x38a8, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ SND_PCI_QUIRK(0x17aa, 0x386f, "Legion Pro 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x3870, "Lenovo Yoga 7 14ARB7", ALC287_FIXUP_YOGA7_14ARB7_I2C), SND_PCI_QUIRK(0x17aa, 0x3874, "Legion 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), From e37b2abca80473e106176e41712a369fd2f72117 Mon Sep 17 00:00:00 2001 From: "Cen Zhang (Microsoft)" Date: Thu, 13 Aug 2026 17:53:28 -0400 Subject: [PATCH 0706/1328] xsk: fix NULL pointer dereference in __xsk_rcv() In the __xsk_rcv() multi-buffer path, xsk_buff_alloc() is called in a loop without checking its return value. xsk_buff_can_alloc() only counts fill queue entries without validating their addresses, so it can succeed while xsk_buff_alloc() rejects all remaining entries and returns NULL. Oops: general protection fault, probably for non-canonical address 0xdffffc0000000000 KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007] RIP: 0010:__xsk_rcv+0x426/0xc20 (net/xdp/xsk.c:350) Call Trace: xsk_generic_rcv+0x26d/0x5f0 xdp_do_generic_redirect+0x3c5/0xcf0 do_xdp_generic+0x92f/0xe70 __netif_receive_skb_core.constprop.0+0xf7e/0x2b30 Fix this with a two-stage transaction. First allocate and stage all buffers required for the packet, recycling all staged buffers with xsk_buff_free() if any allocation fails. Only after this stage succeeds, copy the data, reserve the RX descriptors, and release the buffers in an error-free loop. Fixes: 804627751b42 ("xsk: add support for AF_XDP multi-buffer on Rx path") Reported-by: AutonomousCodeSecurity@microsoft.com Signed-off-by: Cen Zhang (Microsoft) Reviewed-by: Simon Horman Reviewed-by: Jason Xing Link: https://patch.msgid.link/20260813215328.99311-1-blbllhy@gmail.com Signed-off-by: David S. Miller Signed-off-by: Jakub Kicinski --- net/xdp/xsk.c | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/net/xdp/xsk.c b/net/xdp/xsk.c index 7855ee09c4b6..33475b180ea6 100644 --- a/net/xdp/xsk.c +++ b/net/xdp/xsk.c @@ -298,9 +298,11 @@ static int __xsk_rcv(struct xdp_sock *xs, struct xdp_buff *xdp, u32 len) u32 frame_size = __xsk_pool_get_rx_frame_size(xs->pool); void *copy_from = xsk_copy_xdp_start(xdp), *copy_to; u32 from_len, meta_len, rem, num_desc; - struct xdp_buff_xsk *xskb; + struct xdp_buff_xsk *xskb, *tmp; struct xdp_buff *xsk_xdp; + LIST_HEAD(xsk_buffs); skb_frag_t *frag; + u32 i; from_len = xdp->data_end - copy_from; meta_len = xdp->data - copy_from; @@ -343,23 +345,45 @@ static int __xsk_rcv(struct xdp_sock *xs, struct xdp_buff *xdp, u32 len) frag = &sinfo->frags[0]; } + for (i = 0; i < num_desc; i++) { + xsk_xdp = xsk_buff_alloc(xs->pool); + if (!xsk_xdp) + goto err_alloc; + + xskb = container_of(xsk_xdp, struct xdp_buff_xsk, xdp); + if (unlikely(!list_empty(&xskb->list_node))) + goto err_alloc; + + list_add_tail(&xskb->list_node, &xsk_buffs); + } + do { u32 to_len = frame_size + meta_len; u32 copied; - xsk_xdp = xsk_buff_alloc(xs->pool); + xskb = list_first_entry(&xsk_buffs, struct xdp_buff_xsk, + list_node); + list_del_init(&xskb->list_node); + xsk_xdp = &xskb->xdp; copy_to = xsk_xdp->data - meta_len; copied = xsk_copy_xdp(copy_to, ©_from, to_len, &from_len, &frag, rem); rem -= copied; - xskb = container_of(xsk_xdp, struct xdp_buff_xsk, xdp); __xsk_rcv_zc_safe(xs, xskb, copied - meta_len, rem ? XDP_PKT_CONTD : 0); meta_len = 0; } while (rem); return 0; + +err_alloc: + list_for_each_entry_safe(xskb, tmp, &xsk_buffs, list_node) { + list_del_init(&xskb->list_node); + xsk_buff_free(&xskb->xdp); + } + xs->rx_dropped++; + return -ENOMEM; } static bool xsk_tx_writeable(struct xdp_sock *xs) From b74a072d8fb71d3c9ffba4a17d5943e63266fb38 Mon Sep 17 00:00:00 2001 From: Ruoyu Wang Date: Fri, 14 Aug 2026 21:40:53 +0800 Subject: [PATCH 0707/1328] net: bridge: Reject descending VLAN tunnel ranges A pair of descending VLAN and tunnel IDs can pass the tunnel range span check. The VLAN subtraction produces a negative int, which is converted to unsigned when compared with the u32 tunnel ID subtraction. It can therefore equal the wrapped tunnel ID delta. The range loop then performs no iterations. Since the batched notification handling added a post-loop error check, this leaves err uninitialized and makes the request's return value unpredictable. Reject descending VLAN ranges before comparing the spans. Valid ascending and single-entry ranges remain unchanged, while malformed descending ranges consistently return -EINVAL. This issue was found by a static analysis checker and confirmed by manual source review. Fixes: 94339443686b ("net: bridge: notify on vlan tunnel changes done via the old api") Signed-off-by: Ruoyu Wang Acked-by: Nikolay Aleksandrov Link: https://patch.msgid.link/20260814134053.1387275-1-ruoyuw560@gmail.com Signed-off-by: David S. Miller Signed-off-by: Jakub Kicinski --- net/bridge/br_netlink_tunnel.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/bridge/br_netlink_tunnel.c b/net/bridge/br_netlink_tunnel.c index a713668ea34f..e7eceab5b515 100644 --- a/net/bridge/br_netlink_tunnel.c +++ b/net/bridge/br_netlink_tunnel.c @@ -302,7 +302,8 @@ int br_process_vlan_tunnel_info(const struct net_bridge *br, if (!(tinfo_last->flags & BRIDGE_VLAN_INFO_RANGE_BEGIN)) return -EINVAL; - if ((tinfo_curr->vid - tinfo_last->vid) != + if (tinfo_curr->vid < tinfo_last->vid || + (tinfo_curr->vid - tinfo_last->vid) != (tinfo_curr->tunid - tinfo_last->tunid)) return -EINVAL; t = tinfo_last->tunid; From 3cbfd627ee720f3d2460d2cbe2fe9e4130240db6 Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Sat, 15 Aug 2026 06:03:02 +0200 Subject: [PATCH 0708/1328] net: ipa: fix stalled modem TX queue after runtime resume ipa_start_xmit() unconditionally stops the TX queue before calling pm_runtime_get(), relying on the wake scheduled by runtime resume (ipa_modem_wake_queue_work()) to restart it once power is ACTIVE. But that work is queued from within the runtime resume callback, before the device's power state reaches RPM_ACTIVE, so it can run while the device is still RPM_RESUMING. The wake is then consumed too early: the transmit it restarts stops the queue again, pm_runtime_get() returns -EINPROGRESS without arranging any future wake (deferred_resume exists only for RPM_SUSPENDING), and after the resume completes nothing is left to wake the queue. Transmit stalls permanently: packets pile up in the qdisc behind the stopped queue, the device runtime-suspends, and since the netdev registers no ndo_tx_timeout the watchdog never fires. Observed on SM7635 (Fairphone 6) as the cellular data path going permanently deaf within hours, RX included, since nothing resumes the suspended endpoints. Close the window by making the wake work wait for the resume to complete (pm_runtime_get_sync()) before waking the queue. Every queue stop is then guaranteed a later wake that happens while power is ACTIVE; a transmit racing a new suspend/resume cycle re-schedules the work. If the device could not be resumed, wake the queue anyway so pending packets are dropped by the transmit path rather than stranded. The STARTED power flag used to narrow this window: a wake running before the transmit path's stop suppressed that stop, but only once, as the flag was cleared by the first stop it absorbed. Removing the flag made a single transmit during an in-flight resume sufficient to strand the queue, which is the form observed. With an accelerated reproducer (autosuspend delay shortened to 5 ms, ~20 packets/s of TX), an unpatched kernel stalled three times in 230 s / 4380 packets; with this patch the same test ran 3601 s / 70298 packets without a stall. Fixes: 688de12f080f ("net: ipa: kill the STARTED IPA power flag") Cc: stable@vger.kernel.org Signed-off-by: Jorijn van der Graaf Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260815040302.653650-1-jorijnvdgraaf@catcrafts.net Signed-off-by: David S. Miller Signed-off-by: Jakub Kicinski --- drivers/net/ipa/ipa_modem.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/drivers/net/ipa/ipa_modem.c b/drivers/net/ipa/ipa_modem.c index 9b136f6b8b4a..d84c1dbd3b1a 100644 --- a/drivers/net/ipa/ipa_modem.c +++ b/drivers/net/ipa/ipa_modem.c @@ -266,13 +266,29 @@ void ipa_modem_suspend(struct net_device *netdev) * the modem. We can't enable the queue directly in ipa_modem_resume() * because transmits restart the instant the queue is awakened; but the * device power state won't be ACTIVE until *after* ipa_modem_resume() - * returns. + * returns. A transmit restarted before that would stop the queue + * again and get -EINPROGRESS from pm_runtime_get(), and with this + * work having already run, nothing would ever wake the queue again. + * So wait for the resume to complete before waking the queue. */ static void ipa_modem_wake_queue_work(struct work_struct *work) { struct ipa_priv *priv = container_of(work, struct ipa_priv, work); + struct device *dev = priv->ipa->dev; + int ret; + ret = pm_runtime_get_sync(dev); + + /* Wake the queue even if the device could not be resumed, so + * that pending packets are dropped by the transmit path rather + * than stranded behind a stopped queue. + */ netif_wake_queue(priv->tx->netdev); + + if (ret < 0) + pm_runtime_put_noidle(dev); + else + (void)pm_runtime_put_autosuspend(dev); } /** ipa_modem_resume() - resume callback for runtime_pm From 47cdab0d51aaa9bd85f8e4904585bd5bd4df4488 Mon Sep 17 00:00:00 2001 From: Yuyang Huang Date: Sat, 15 Aug 2026 17:46:51 +0900 Subject: [PATCH 0709/1328] ipv6: use RCU iterator to dump route exceptions rt6_nh_dump_exceptions() uses hlist_for_each_entry() to iterate over RCU-protected exception lists. The caller holds rcu_read_lock(), but does not hold rt6_exception_lock, so rt6_insert_exception() can concurrently add an entry with hlist_add_head_rcu(). KCSAN reports this race (irrelevant details omitted): ================================================================== BUG: KCSAN: data-race in rt6_insert_exception / rt6_nh_dump_exceptions write (marked) to 0xffff8a7c44c59620 of 8 bytes by interrupt on cpu 5: rt6_insert_exception+0x3bb/0x760 __ip6_rt_update_pmtu+0x4fe/0x750 ip6_sk_update_pmtu+0x19a/0x3b0 udpv6_err+0x3ff/0x800 icmpv6_notify+0x1e1/0x440 icmpv6_rcv+0x8c0/0xab0 ip6_protocol_deliver_rcu+0x616/0x840 ip6_input_finish+0xb9/0x160 ... entry_SYSCALL_64_after_hwframe+0x77/0x7f read to 0xffff8a7c44c59620 of 8 bytes by task 549 on cpu 14: rt6_nh_dump_exceptions+0xb3/0x260 rt6_dump_route+0x53e/0x5f0 fib6_dump_node+0x6d/0xf0 fib6_walk_continue+0x290/0x2d0 fib6_dump_table+0x28d/0x360 inet6_dump_fib+0x37d/0x620 rtnl_dumpit+0x7b/0xd0 netlink_dump+0x3ae/0x7e0 ... entry_SYSCALL_64_after_hwframe+0x77/0x7f 4 locks held by dumper/549: ... #1: (rcu_read_lock){....}-{1:3}, at: inet6_dump_fib+0x88/0x620 #2: (&tb->tb6_lock){+.-.}-{3:3}, at: fib6_dump_table+0x1e9/0x360 #3: (rcu_read_lock){....}-{1:3}, at: rt6_dump_route+0x483/0x5f0 value changed: 0xffff8a7c44e05700 -> 0xffff8a7c45d60100 Reported by Kernel Concurrency Sanitizer on: CPU: 14 UID: 0 PID: 549 Comm: dumper Not tainted 7.2.0-rc7-virtme #38 PREEMPT(lazy) ... Use hlist_for_each_entry_rcu() to safely iterate over the exception list. Fixes: 1e47b4837f3b ("ipv6: Dump route exceptions if requested") Cc: stable@vger.kernel.org Signed-off-by: Yuyang Huang Reviewed-by: Stefano Brivio Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260815084651.69477-1-sigefriedhyy@gmail.com Signed-off-by: David S. Miller Signed-off-by: Jakub Kicinski --- net/ipv6/route.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/ipv6/route.c b/net/ipv6/route.c index 16dfac54a259..526535387512 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -6046,7 +6046,7 @@ static int rt6_nh_dump_exceptions(struct fib6_nh *nh, void *arg) return 0; for (i = 0; i < FIB6_EXCEPTION_BUCKET_SIZE; i++) { - hlist_for_each_entry(rt6_ex, &bucket->chain, hlist) { + hlist_for_each_entry_rcu(rt6_ex, &bucket->chain, hlist) { if (w->skip) { w->skip--; continue; From dc948f8b384a516ac5c471eb9382959f838e02f4 Mon Sep 17 00:00:00 2001 From: Peixin Xie Date: Thu, 20 Aug 2026 13:56:27 +0800 Subject: [PATCH 0710/1328] ACPI: scan: Defer device power initialization acpi_bus_get_power_flags() initializes the device power state while the ACPI device object is being created, before checking whether the device is ready for enumeration. If enumeration is deferred, acpi_bus_attach() clears the initialized and power_manageable flags. When the dependency is later satisfied, acpi_bus_init_power() is called again and takes additional references to the power resources used by the device. These references prevent the resources from being turned off when the device enters D3. This issue was reproduced on a SpacemiT K3 RISC-V Pico-ITX. The affected device uses a power resource through _PR0 and has an automatically derived dependency on its interrupt controller. The initial power initialization acquires a power resource reference. The device is then deferred, but that reference is not dropped. When the dependency becomes available, power initialization acquires another reference. Consequently, entering D3 only drops the reference count from 2 to 1 and _OFF is not evaluated: [ 0.314611] ACPI Debug: "I2P2 _STA" [ 0.318260] ACPI: \_SB_.I2P2: ACPI: PM: Power resource is on [ 0.323998] ACPI: \_SB_.I2P2: New power resource [ 0.382108] ACPI Debug: "I2P2 _STA" [ 0.478964] ACPI Debug: "I2P2 _ON" [ 0.482498] ACPI: \_SB_.I2P2: ACPI: PM: Power resource turned on [ 0.488597] ACPI Debug: "I2C2, PS0" [ 0.863170] ACPI: \_SB_.I2P2: ACPI: PM: Power resource already on [ 0.873686] ACPI Debug: "I2C2, PS0" [ 2.416055] ACPI Debug: "I2C2, PS3" [ 2.423397] ACPI: \_SB_.I2P2: ACPI: PM: Power resource still in use To address this, remove the early acpi_bus_init_power() call and leave regular ACPI device objects uninitialized until acpi_bus_attach() runs after the device is ready for enumeration. Power resource objects are initialized through acpi_add_power_resource() and do not require the generic initialization in acpi_bus_attach(), so mark them as initialized there. After the change, device power state initialization is deferred until its dependency is met. Since no reference is acquired before then, the power resource left on by firmware is turned off as unused after the namespace scan. Once the dependency is met, the resource is turned on once for the device and is turned off normally when the device later enters D3: [ 0.314628] ACPI Debug: "I2P2 _STA" [ 0.318277] ACPI: \_SB_.I2P2: ACPI: PM: Power resource is on [ 0.324016] ACPI: \_SB_.I2P2: New power resource [ 0.382118] ACPI Debug: "I2P2 _STA" [ 0.496116] ACPI: \_SB_.I2P2: ACPI: PM: Turning OFF [ 0.501081] ACPI Debug: "I2P2 _OFF" [ 0.504705] ACPI: \_SB_.I2P2: ACPI: PM: Power resource turned off [ 1.415899] ACPI Debug: "I2P2 _ON" [ 1.418866] ACPI: \_SB_.I2P2: ACPI: PM: Power resource turned on [ 1.424947] ACPI Debug: "I2C2, PS0" [ 2.647655] ACPI Debug: "I2C2, PS3" [ 2.654856] ACPI Debug: "I2P2 _OFF" [ 2.654866] ACPI: \_SB_.I2P2: ACPI: PM: Power resource turned off This also avoids powering up devices before their dependencies are available. Signed-off-by: Peixin Xie [ rjw: Changelog edits ] Link: https://patch.msgid.link/20260820-acpi-power-resource-ref-fix-v2-1-29818173ea13@linux.spacemit.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/power.c | 1 + drivers/acpi/scan.c | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/acpi/power.c b/drivers/acpi/power.c index d4131c184be8..4f1479103bfe 100644 --- a/drivers/acpi/power.c +++ b/drivers/acpi/power.c @@ -955,6 +955,7 @@ struct acpi_device *acpi_add_power_resource(acpi_handle handle) INIT_LIST_HEAD(&resource->dependents); device->power.state = ACPI_STATE_UNKNOWN; device->flags.match_driver = true; + device->flags.initialized = true; /* Evaluate the object to get the system level and resource order. */ status = acpi_evaluate_object(handle, NULL, NULL, &buffer); diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index f8450f7ea097..cd7ab27ecdce 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -1146,9 +1146,6 @@ static void acpi_bus_get_power_flags(struct acpi_device *device) if (!list_empty(&device->power.states[ACPI_STATE_D3_HOT].resources)) device->power.states[ACPI_STATE_D3_COLD].flags.valid = 1; } - - if (acpi_bus_init_power(device)) - device->flags.power_manageable = 0; } static void acpi_bus_get_flags(struct acpi_device *device) @@ -1827,7 +1824,6 @@ void acpi_init_device_object(struct acpi_device *device, acpi_handle handle, acpi_init_properties(device); acpi_bus_get_flags(device); device->flags.match_driver = false; - device->flags.initialized = true; device->flags.enumeration_by_parent = acpi_device_enumeration_by_parent(device); acpi_device_clear_enumerated(device); From ec518a7c4ba13ea0b94a50cc79584e6d578e4791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Lebrun?= Date: Thu, 20 Aug 2026 19:08:20 +0200 Subject: [PATCH 0711/1328] net: macb: drop CONFIG_OF #if block MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix -Wimplicit-function-declaration error on CONFIG_OF=n builds: drivers/net/ethernet/cadence/macb_main.c: In function ‘macb_probe’: drivers/net/ethernet/cadence/macb_main.c:5951:15: error: implicit declaration of function ‘macb_alloc_tieoff’ [...] 5951 | err = macb_alloc_tieoff(bp); | ^~~~~~~~~~~~~~~~~ drivers/net/ethernet/cadence/macb_main.c:5973:9: error: implicit declaration of function ‘macb_free_tieoff’ [...] 5973 | macb_free_tieoff(bp); | ^~~~~~~~~~~~~~~~ Error got introduced because functions are mistakenly declared in a `#if defined(CONFIG_OF)` block. Instead of moving functions around, avoid any future mistake and drop the block entirely. Change the module content slightly on CONFIG_OF=n. Previously match tables were ignored. Now they appear in the resulting build. This is considered trivial in size by most and is the common case: ⟩ 18 out of 254 OF net drivers reference CONFIG_OF ⟩ rg -lF 'MODULE_DEVICE_TABLE(of,' drivers/net/ | tee /tmp/a | wc -l 254 ⟩ xargs -a /tmp/a rg -l CONFIG_OF | wc -l 18 Tangent: no, of_match_ptr() does not imply that the compiler can optimize out match tables, because MODULE_DEVICE_TABLE(of, ...) unconditionally puts the match tables in the binary. It is only meant to avoid undefined declaration issues when match tables are hidden behind a #ifdef, as was done before. We therefore drop the macro call. Fixes: 5262eab9462a ("net: macb: allocate tieoff descriptor once across device lifetime") Reported-by: Nathan Chancellor Signed-off-by: Théo Lebrun Reviewed-by: Nicolai Buchwitz Acked-by: Conor Dooley Link: https://patch.msgid.link/20260820-macb-fix-x86-v1-1-b2e7c902104e@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cadence/macb_main.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index 1476bce77f34..76ee4f506033 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -4926,7 +4926,6 @@ static const struct macb_usrio_config at91_default_usrio = { .clken = MACB_BIT(CLKEN), }; -#if defined(CONFIG_OF) /* 1518 rounded up */ #define AT91ETHER_MAX_RBUFF_SZ 0x600 /* max number of receive buffers */ @@ -5754,7 +5753,6 @@ static const struct of_device_id macb_dt_ids[] = { { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, macb_dt_ids); -#endif /* CONFIG_OF */ static const struct macb_config default_gem_config = { .caps = MACB_CAPS_GIGABIT_MODE_AVAILABLE | @@ -6267,7 +6265,7 @@ static struct platform_driver macb_driver = { .remove = macb_remove, .driver = { .name = "macb", - .of_match_table = of_match_ptr(macb_dt_ids), + .of_match_table = macb_dt_ids, .pm = &macb_pm_ops, }, .shutdown = macb_shutdown, From 2640e64195948a601430d230c9864f5426574cde Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Sat, 15 Aug 2026 15:03:36 +0800 Subject: [PATCH 0712/1328] net: advertise TCP MSS from the configured MTU, not the learned PMTU The MSS a host puts in its SYN tells the peer how big a segment it may send us. Right now we can shrink it with a PMTU we learned on our own send path, which is the wrong direction entirely. On asymmetric paths this bites - think DSR load balancers, where the request side goes through a smaller-MTU overlay. We learn a small PMTU going out, then advertise a small MSS, and the peer stays capped for the whole connection even though its path back to us is wide. MSS only shows up in the SYN and never grows back. On symmetric paths we lose nothing by dropping it either: the peer runs its own PMTU discovery and usually already knows the real path MTU. So work out the advertised MSS from the configured route or device MTU and ignore the learned PMTU. Our send side is unchanged, still clamped by tcp_current_mss(). Add ip_dst_mtu_configured()/ip6_dst_mtu_configured() and use them from the two default_advmss() paths. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Fixes: 164a5e7ad531 ("ipv4: ipv4_default_advmss() should use route mtu") Cc: stable@vger.kernel.org Signed-off-by: Jiayuan Chen Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260815070413.294559-1-jiayuan.chen@linux.dev Signed-off-by: Jakub Kicinski --- include/net/ip.h | 25 +++++++++++++++++++++++++ include/net/ip6_route.h | 37 +++++++++++++++++++++++++++++++++++++ net/ipv4/route.c | 4 ++-- net/ipv6/route.c | 2 +- 4 files changed, 65 insertions(+), 3 deletions(-) diff --git a/include/net/ip.h b/include/net/ip.h index 7f2fe1a8401b..a8f57b4f4aa2 100644 --- a/include/net/ip.h +++ b/include/net/ip.h @@ -506,6 +506,31 @@ static inline unsigned int ip_dst_mtu_maybe_forward(const struct dst_entry *dst, return res; } +/* Configured/administrative MTU of a route, for advertising the TCP MSS. + * + * Unlike ip_dst_mtu_maybe_forward(), this deliberately ignores the + * ICMP-learned path MTU (rt->rt_pmtu). The advertised MSS bounds what the + * peer may send to us and must reflect our receive capability (the device or + * route-configured MTU), not a path MTU learned on the reverse (send) + * direction, which may not apply to the peer->us path and outlives the fnhe + * for the whole connection. See RFC 2923 section 2.3 and the comment above + * tcp_advertise_mss(). + */ +static inline unsigned int ip_dst_mtu_configured(const struct dst_entry *dst) +{ + unsigned int mtu, res; + + rcu_read_lock(); + mtu = dst_metric_raw(dst, RTAX_MTU); + if (!mtu) + mtu = READ_ONCE(dst_dev_rcu(dst)->mtu); + mtu = min_t(unsigned int, mtu, IP_MAX_MTU); + res = mtu - lwtunnel_headroom(dst->lwtstate, mtu); + rcu_read_unlock(); + + return res; +} + static inline unsigned int ip_skb_dst_mtu(struct sock *sk, const struct sk_buff *skb) { diff --git a/include/net/ip6_route.h b/include/net/ip6_route.h index cc045705862d..c69f1c871922 100644 --- a/include/net/ip6_route.h +++ b/include/net/ip6_route.h @@ -387,6 +387,43 @@ static inline unsigned int ip6_dst_mtu_maybe_forward(const struct dst_entry *dst return mtu - lwtunnel_headroom(dst->lwtstate, mtu); } +/* Configured/administrative MTU of a route, for advertising the TCP MSS. + * + * Unlike ip6_dst_mtu_maybe_forward(), this ignores any ICMPv6-learned path + * MTU (which is kept on the RTF_CACHE exception route) and returns the MTU of + * the underlying route (fib6_pmtu) or the egress device. The advertised MSS + * bounds what the peer may send to us and must reflect our receive + * capability, not a path MTU learned on the reverse (send) direction. See + * RFC 2923 section 2.3 and the comment above tcp_advertise_mss(). + */ +static inline unsigned int ip6_dst_mtu_configured(const struct dst_entry *dst) +{ + const struct rt6_info *rt = dst_rt6_info(dst); + const struct fib6_info *from; + struct inet6_dev *idev; + unsigned int mtu = 0; + + rcu_read_lock(); + /* IPv6 keeps the learned PMTU and the configured MTU in the same + * RTAX_MTU slot: the learned value sits on this (possibly RTF_CACHE) + * dst, the configured one on the underlying route. Reach the latter + * via ->from (fib6_pmtu), populated by ip6_route_info_create(). + */ + from = rcu_dereference(rt->from); + if (from) + mtu = from->fib6_pmtu; + if (!mtu) { + mtu = IPV6_MIN_MTU; + idev = __in6_dev_get(dst_dev_rcu(dst)); + if (idev) + mtu = max_t(unsigned int, mtu, READ_ONCE(idev->cnf.mtu6)); + } + rcu_read_unlock(); + + mtu = min_t(unsigned int, mtu, IP6_MAX_MTU); + return mtu - lwtunnel_headroom(dst->lwtstate, mtu); +} + u32 ip6_mtu_from_fib6(const struct fib6_result *res, const struct in6_addr *daddr, const struct in6_addr *saddr); diff --git a/net/ipv4/route.c b/net/ipv4/route.c index 604cc51dfd9b..37674d76f90f 100644 --- a/net/ipv4/route.c +++ b/net/ipv4/route.c @@ -1363,8 +1363,8 @@ static unsigned int ipv4_default_advmss(const struct dst_entry *dst) rcu_read_lock(); net = dst_dev_net_rcu(dst); - advmss = max_t(unsigned int, ipv4_mtu(dst) - header_size, - net->ipv4.ip_rt_min_advmss); + advmss = max_t(unsigned int, ip_dst_mtu_configured(dst) - header_size, + net->ipv4.ip_rt_min_advmss); rcu_read_unlock(); return min(advmss, IPV4_MAX_PMTU - header_size); diff --git a/net/ipv6/route.c b/net/ipv6/route.c index 526535387512..93b4168044d9 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -3261,7 +3261,7 @@ void ip6_sk_redirect(struct sk_buff *skb, struct sock *sk) static unsigned int ip6_default_advmss(const struct dst_entry *dst) { - unsigned int mtu = dst6_mtu(dst); + unsigned int mtu = ip6_dst_mtu_configured(dst); struct net *net; mtu -= sizeof(struct ipv6hdr) + sizeof(struct tcphdr); From e5c8e301b497930e25cc4085fbc7ea2223b37030 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Sat, 15 Aug 2026 15:15:08 +0800 Subject: [PATCH 0713/1328] selftests: net: packetdrill: add tests for advertised MSS with PMTU exceptions Add packetdrill tests for IPv4 and IPv6 to verify that the advertised MSS in SYN-ACK is derived from the configured interface/route MTU, and is not shrunk by learned Path MTU exceptions from previous outbound connections. Signed-off-by: Eric Dumazet Signed-off-by: Jiayuan Chen Link: https://patch.msgid.link/20260815071532.301908-1-jiayuan.chen@linux.dev Signed-off-by: Jakub Kicinski --- .../net/packetdrill/tcp_advmss_pmtu_ipv4.pkt | 67 +++++++++++++++++++ .../net/packetdrill/tcp_advmss_pmtu_ipv6.pkt | 67 +++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 tools/testing/selftests/net/packetdrill/tcp_advmss_pmtu_ipv4.pkt create mode 100644 tools/testing/selftests/net/packetdrill/tcp_advmss_pmtu_ipv6.pkt diff --git a/tools/testing/selftests/net/packetdrill/tcp_advmss_pmtu_ipv4.pkt b/tools/testing/selftests/net/packetdrill/tcp_advmss_pmtu_ipv4.pkt new file mode 100644 index 000000000000..f2ef931b77a1 --- /dev/null +++ b/tools/testing/selftests/net/packetdrill/tcp_advmss_pmtu_ipv4.pkt @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Test that IPv4 advertised MSS in SYN-ACK is derived from the configured +// interface MTU (1500 -> MSS 1460), not the ICMP-learned Path MTU. + +--ip_version=ipv4 + +`./defaults.sh +ethtool -K tun0 tso off +` + +// +// Connection 1: Learn PMTU exception (MTU 1200 -> MSS 1160) +// + 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 + +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 + +0 bind(3, ..., ...) = 0 + +0 listen(3, 1) = 0 + + +0 < S 0:0(0) win 65535 + +0 > S. 0:0(0) ack 1 + +.1 < . 1:1(0) ack 1 win 257 + +0 accept(3, ..., ...) = 4 + +// Send a full 1460-byte segment + +0 write(4, ..., 1460) = 1460 + +0 > P. 1:1461(1460) ack 1 + +// ICMP Fragmentation Needed arrives indicating next-hop MTU 1200 + +0 < icmp unreachable frag_needed mtu 1200 [1:1461(1460)] + +// Local host retransmits using the learned MTU 1200 (MSS = 1200 - 40 = 1160) + +0 > . 1:1161(1160) ack 1 + +0 > P. 1161:1461(300) ack 1 + +0 < R 1:1(0) ack 1461 win 0 + +// Close connection 1 and listener + +0 close(4) = 0 + +0 close(3) = 0 + +// +// Connection 2: New connection from the same peer +// + +0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 + +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 + +0 bind(3, ..., ...) = 0 + +0 listen(3, 1) = 0 + + +0 < S 0:0(0) win 65535 + +// Verify: SYN-ACK MUST advertise configured MSS 1460, NOT the learned PMTU MSS 1160 + +0 > S. 0:0(0) ack 1 + +0 < . 1:1(0) ack 1 win 257 + +0 accept(3, ..., ...) = 4 + +// Verify: Outgoing transmit MSS is still constrained by the learned PMTU 1200 + +0 write(4, ..., 1460) = 1460 + +0 > . 1:1161(1160) ack 1 + +0 > P. 1161:1461(300) ack 1 + +0 < . 1:1(0) ack 1461 win 257 + +// Clean up + +0 close(4) = 0 + +0 > F. 1461:1461(0) ack 1 + +0 < F. 1:1(0) ack 1462 win 257 + +0 > . 1462:1462(0) ack 2 + +0 close(3) = 0 diff --git a/tools/testing/selftests/net/packetdrill/tcp_advmss_pmtu_ipv6.pkt b/tools/testing/selftests/net/packetdrill/tcp_advmss_pmtu_ipv6.pkt new file mode 100644 index 000000000000..c7638b11a815 --- /dev/null +++ b/tools/testing/selftests/net/packetdrill/tcp_advmss_pmtu_ipv6.pkt @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Test that IPv6 advertised MSS in SYN-ACK is derived from the configured +// interface MTU (1520 -> MSS 1460), not the ICMPv6-learned Path MTU. + +--ip_version=ipv6 + +`./defaults.sh +ethtool -K tun0 tso off +` + +// +// Connection 1: Learn PMTU exception (MTU 1280 -> MSS 1220) +// + 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 + +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 + +0 bind(3, ..., ...) = 0 + +0 listen(3, 1) = 0 + + +0 < S 0:0(0) win 65535 + +0 > S. 0:0(0) ack 1 + +.1 < . 1:1(0) ack 1 win 257 + +0 accept(3, ..., ...) = 4 + +// Send a full 1460-byte segment + +0 write(4, ..., 1460) = 1460 + +0 > P. 1:1461(1460) ack 1 + +// ICMPv6 Packet Too Big arrives indicating next-hop MTU 1280 + +0 < icmp packet_too_big mtu 1280 [1:1461(1460)] + +// Local host retransmits using the learned MTU 1280 (MSS = 1280 - 40 - 20 = 1220) + +0 > . 1:1221(1220) ack 1 + +0 > P. 1221:1461(240) ack 1 + +0 < R 1:1(0) ack 1461 win 0 + +// Close connection 1 and listener + +0 close(4) = 0 + +0 close(3) = 0 + +// +// Connection 2: New connection from the same peer +// + +0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 + +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 + +0 bind(3, ..., ...) = 0 + +0 listen(3, 1) = 0 + + +0 < S 0:0(0) win 65535 + +// Verify: SYN-ACK MUST advertise configured MSS 1460, NOT the learned PMTU MSS 1220 + +0 > S. 0:0(0) ack 1 + +0 < . 1:1(0) ack 1 win 257 + +0 accept(3, ..., ...) = 4 + +// Verify: Outgoing transmit MSS is still constrained by the learned PMTU 1280 + +0 write(4, ..., 1460) = 1460 + +0 > . 1:1221(1220) ack 1 + +0 > P. 1221:1461(240) ack 1 + +0 < . 1:1(0) ack 1461 win 257 + +// Clean up + +0 close(4) = 0 + +0 > F. 1461:1461(0) ack 1 + +0 < F. 1:1(0) ack 1462 win 257 + +0 > . 1462:1462(0) ack 2 + +0 close(3) = 0 From 8e2efb3f45a5c6f7c8f68685c1c4709040aa2fa0 Mon Sep 17 00:00:00 2001 From: Victor Nogueira Date: Sun, 16 Aug 2026 17:13:27 -0300 Subject: [PATCH 0714/1328] net/sched: add get_fill_size callbacks for actions missing them Several tc actions - act_police, act_bpf, act_pedit, act_ife, act_sample, act_ct, act_ctinfo and act_tunnel_key among them - provide no get_fill_size() callback, so tcf_action_fill_size() falls back to tcf_action_shared_attrs_size() which does not account for the action-specific netlink attributes emitted inside TCA_ACT_OPTIONS by their dump functions. When an RTM_NEWACTION request with NLM_F_ECHO (or an RTNLGRP_TC listener) creates several actions, tcf_add_notify_msg() allocates the echo skb from this underestimated size. When this happens, the act_api code fails to add all of the fields to the netlink message and, thus, fails to send it. Issue is that, when that happens, this failure doesn't stop the action instances from being added. So any user watching these events will be under the false impression that no actions were created at all. For example, act_pedit overruns with 32 actions of four munge keys each, act_police with 32 policers once the optional rate/peakrate/result/avrate attributes are present. To fix this, add the missing get_fill_size callbacks returning the worst-case size of each action's dump attributes, following the pattern used by act_gact/act_skbedit/act_vlan. Also widen the TCA_GACT_TM accounting in tcf_action_shared_attrs_size() to nla_total_size_64bit(), since actions dump their tcf_t with nla_put_64bit(), which may be preceded by an NLA_PAD attribute. Note: We only provided fixes for the actions we reproduced this bug with as of today. We can send a separate hardening patch for the remaining actions to net-next later. The other pre-existing issues, pointed out by Clashiko [1], will be fixed in upcoming patches. [1] https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260810164357.1653956-1-victor%40mojatatu.com Fixes: 4e76e75d6aba ("net sched actions: calculate add/delete event message size") Reported-by: Vega Acked-by: Jamal Hadi Salim Signed-off-by: Victor Nogueira Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260816201327.2435335-1-victor@mojatatu.com Signed-off-by: Jakub Kicinski --- net/sched/act_api.c | 5 ++- net/sched/act_bpf.c | 26 +++++++++++++ net/sched/act_ct.c | 46 ++++++++++++++++++++++ net/sched/act_ctinfo.c | 11 ++++++ net/sched/act_ife.c | 23 +++++++++++ net/sched/act_pedit.c | 24 ++++++++++++ net/sched/act_police.c | 12 ++++++ net/sched/act_sample.c | 9 +++++ net/sched/act_tunnel_key.c | 80 ++++++++++++++++++++++++++++++++++++++ 9 files changed, 235 insertions(+), 1 deletion(-) diff --git a/net/sched/act_api.c b/net/sched/act_api.c index 600b7804befd..b4415d358c91 100644 --- a/net/sched/act_api.c +++ b/net/sched/act_api.c @@ -452,7 +452,10 @@ static size_t tcf_action_shared_attrs_size(const struct tc_action *act) /* TCA_STATS_QUEUE */ + nla_total_size_64bit(sizeof(struct gnet_stats_queue)) + nla_total_size(0) /* TCA_ACT_OPTIONS nested */ - + nla_total_size(sizeof(struct tcf_t)); /* TCA_GACT_TM */ + /* TCA_GACT_TM; actions dump their tcf_t with nla_put_64bit(), + * which may emit an extra NLA_PAD attribute. + */ + + nla_total_size_64bit(sizeof(struct tcf_t)); } static size_t tcf_action_full_attrs_size(size_t sz) diff --git a/net/sched/act_bpf.c b/net/sched/act_bpf.c index 09d46e195e33..06d8f78b7368 100644 --- a/net/sched/act_bpf.c +++ b/net/sched/act_bpf.c @@ -389,6 +389,31 @@ static void tcf_bpf_cleanup(struct tc_action *act) tcf_bpf_cfg_cleanup(&tmp); } +static size_t tcf_bpf_get_fill_size(const struct tc_action *act) +{ + struct tcf_bpf *prog = to_bpf(act); + size_t size = nla_total_size(sizeof(struct tc_act_bpf)); + + /* bpf_ops and bpf_num_ops are published as separate stores under + * tcf_lock, so take it here as tcf_bpf_dump() does. + */ + spin_lock_bh(&prog->tcf_lock); + if (tcf_bpf_is_ebpf(prog)) { + /* TCA_ACT_BPF_NAME */ + size += nla_total_size(ACT_BPF_NAME_LEN + 1); + size += nla_total_size(sizeof(u32)); /* TCA_ACT_BPF_ID */ + size += nla_total_size(BPF_TAG_SIZE); /* TCA_ACT_BPF_TAG */ + } else { + size += nla_total_size(sizeof(u16)); /* TCA_ACT_BPF_OPS_LEN */ + /* TCA_ACT_BPF_OPS */ + size += nla_total_size(prog->bpf_num_ops * + sizeof(struct sock_filter)); + } + spin_unlock_bh(&prog->tcf_lock); + + return size; +} + static struct tc_action_ops act_bpf_ops __read_mostly = { .kind = "bpf", .id = TCA_ID_BPF, @@ -397,6 +422,7 @@ static struct tc_action_ops act_bpf_ops __read_mostly = { .dump = tcf_bpf_dump, .cleanup = tcf_bpf_cleanup, .init = tcf_bpf_init, + .get_fill_size = tcf_bpf_get_fill_size, .size = sizeof(struct tcf_bpf), }; MODULE_ALIAS_NET_ACT("bpf"); diff --git a/net/sched/act_ct.c b/net/sched/act_ct.c index dc0b5724785d..9080cb386c16 100644 --- a/net/sched/act_ct.c +++ b/net/sched/act_ct.c @@ -1657,6 +1657,51 @@ static int tcf_ct_offload_act_setup(struct tc_action *act, void *entry_data, return 0; } +static size_t tcf_ct_get_fill_size(const struct tc_action *act) +{ + const struct tcf_ct_params *p; + size_t size; + + size = nla_total_size(sizeof(struct tc_ct)) /* TCA_CT_PARMS */ + + nla_total_size(sizeof(u16)); /* TCA_CT_ACTION */ + + rcu_read_lock(); + p = rcu_dereference(to_ct(act)->params); + + if (p->ct_action & TCA_CT_ACT_CLEAR) + goto out; + + /* TCA_CT_MARK, TCA_CT_MARK_MASK */ + if (IS_ENABLED(CONFIG_NF_CONNTRACK_MARK)) + size += nla_total_size(sizeof(p->mark)) + + nla_total_size(sizeof(p->mark_mask)); + + /* TCA_CT_LABELS, TCA_CT_LABELS_MASK */ + if (IS_ENABLED(CONFIG_NF_CONNTRACK_LABELS)) + size += nla_total_size(sizeof(p->labels)) + + nla_total_size(sizeof(p->labels_mask)); + + if (IS_ENABLED(CONFIG_NF_CONNTRACK_ZONES)) + size += nla_total_size(sizeof(p->zone)); /* TCA_CT_ZONE */ + + if (p->ct_action & TCA_CT_ACT_NAT) + /* TCA_CT_NAT_IPV6_{MIN,MAX}, the larger of the two address + * variants, plus TCA_CT_NAT_PORT_{MIN,MAX}. + */ + size += 2 * nla_total_size(sizeof(struct in6_addr)) + + 2 * nla_total_size(sizeof(__be16)); + + /* TCA_CT_HELPER_{NAME,FAMILY,PROTO} */ + if (p->helper) + size += nla_total_size(NF_CT_HELPER_NAME_LEN) + + nla_total_size(sizeof(u8)) + + nla_total_size(sizeof(u8)); +out: + rcu_read_unlock(); + + return size; +} + static struct tc_action_ops act_ct_ops = { .kind = "ct", .id = TCA_ID_CT, @@ -1666,6 +1711,7 @@ static struct tc_action_ops act_ct_ops = { .init = tcf_ct_init, .cleanup = tcf_ct_cleanup, .stats_update = tcf_stats_update, + .get_fill_size = tcf_ct_get_fill_size, .offload_act_setup = tcf_ct_offload_act_setup, .size = sizeof(struct tcf_ct), }; diff --git a/net/sched/act_ctinfo.c b/net/sched/act_ctinfo.c index 1886ffd2ca95..fced4b1094af 100644 --- a/net/sched/act_ctinfo.c +++ b/net/sched/act_ctinfo.c @@ -356,6 +356,16 @@ static void tcf_ctinfo_cleanup(struct tc_action *a) kfree_rcu(cp, rcu); } +static size_t tcf_ctinfo_get_fill_size(const struct tc_action *act) +{ + return nla_total_size(sizeof(struct tc_ctinfo)) /* TCA_CTINFO_ACT */ + + nla_total_size(sizeof(u16)) /* TCA_CTINFO_ZONE */ + /* TCA_CTINFO_PARMS_{DSCP_MASK,DSCP_STATEMASK,CPMARK_MASK} */ + + 3 * nla_total_size(sizeof(u32)) + /* TCA_CTINFO_STATS_{DSCP_SET,DSCP_ERROR,CPMARK_SET} */ + + 3 * nla_total_size_64bit(sizeof(u64)); +} + static struct tc_action_ops act_ctinfo_ops = { .kind = "ctinfo", .id = TCA_ID_CTINFO, @@ -364,6 +374,7 @@ static struct tc_action_ops act_ctinfo_ops = { .dump = tcf_ctinfo_dump, .init = tcf_ctinfo_init, .cleanup= tcf_ctinfo_cleanup, + .get_fill_size = tcf_ctinfo_get_fill_size, .size = sizeof(struct tcf_ctinfo), }; MODULE_ALIAS_NET_ACT("ctinfo"); diff --git a/net/sched/act_ife.c b/net/sched/act_ife.c index 065228026c58..ff2b16e35b9b 100644 --- a/net/sched/act_ife.c +++ b/net/sched/act_ife.c @@ -878,6 +878,28 @@ TC_INDIRECT_SCOPE int tcf_ife_act(struct sk_buff *skb, return tcf_ife_decode(skb, a, res); } +static size_t tcf_ife_get_fill_size(const struct tc_action *act) +{ + struct tcf_ife_info *ife = to_ife(act); + const struct tcf_ife_params *p; + struct tcf_meta_info *e; + size_t size = nla_total_size(sizeof(struct tc_ife)) /* TCA_IFE_PARMS */ + + nla_total_size(ETH_ALEN) /* TCA_IFE_DMAC */ + + nla_total_size(ETH_ALEN) /* TCA_IFE_SMAC */ + + nla_total_size(2) /* TCA_IFE_TYPE */ + + nla_total_size(0); /* TCA_IFE_METALST */ + + rcu_read_lock(); + p = rcu_dereference(ife->params); + if (p) { + list_for_each_entry_rcu(e, &p->metalist, metalist) + size += nla_total_size(sizeof(u32)); + } + rcu_read_unlock(); + + return size; +} + static struct tc_action_ops act_ife_ops = { .kind = "ife", .id = TCA_ID_IFE, @@ -886,6 +908,7 @@ static struct tc_action_ops act_ife_ops = { .dump = tcf_ife_dump, .cleanup = tcf_ife_cleanup, .init = tcf_ife_init, + .get_fill_size = tcf_ife_get_fill_size, .size = sizeof(struct tcf_ife_info), }; MODULE_ALIAS_NET_ACT("ife"); diff --git a/net/sched/act_pedit.c b/net/sched/act_pedit.c index d4d47a9921f4..99d7e36510bd 100644 --- a/net/sched/act_pedit.c +++ b/net/sched/act_pedit.c @@ -626,6 +626,29 @@ static int tcf_pedit_offload_act_setup(struct tc_action *act, void *entry_data, return 0; } +static size_t tcf_pedit_get_fill_size(const struct tc_action *act) +{ + const struct tcf_pedit_parms *parms; + size_t size; + + rcu_read_lock(); + parms = rcu_dereference(to_pedit(act)->parms); + size = nla_total_size(struct_size_t(struct tc_pedit, keys, + parms->tcfp_nkeys)); + if (parms->tcfp_keys_ex) { + /* TCA_PEDIT_KEYS_EX, holding one TCA_PEDIT_KEY_EX nest with a + * HTYPE and a CMD attribute per key. + */ + size += nla_total_size(0) + + parms->tcfp_nkeys * (nla_total_size(0) + + nla_total_size(sizeof(u16)) + + nla_total_size(sizeof(u16))); + } + rcu_read_unlock(); + + return size; +} + static struct tc_action_ops act_pedit_ops = { .kind = "pedit", .id = TCA_ID_PEDIT, @@ -635,6 +658,7 @@ static struct tc_action_ops act_pedit_ops = { .dump = tcf_pedit_dump, .cleanup = tcf_pedit_cleanup, .init = tcf_pedit_init, + .get_fill_size = tcf_pedit_get_fill_size, .offload_act_setup = tcf_pedit_offload_act_setup, .size = sizeof(struct tcf_pedit), }; diff --git a/net/sched/act_police.c b/net/sched/act_police.c index ce08f6840ef7..3f8147f37549 100644 --- a/net/sched/act_police.c +++ b/net/sched/act_police.c @@ -490,6 +490,17 @@ static int tcf_police_offload_act_setup(struct tc_action *act, void *entry_data, return 0; } +static size_t tcf_police_get_fill_size(const struct tc_action *act) +{ + return nla_total_size(sizeof(struct tc_police)) /* TCA_POLICE_TBF */ + + nla_total_size_64bit(sizeof(u64)) /* TCA_POLICE_RATE64 */ + + nla_total_size_64bit(sizeof(u64)) /* TCA_POLICE_PEAKRATE64 */ + + nla_total_size_64bit(sizeof(u64)) /* TCA_POLICE_PKTRATE64 */ + + nla_total_size_64bit(sizeof(u64)) /* TCA_POLICE_PKTBURST64 */ + + nla_total_size(sizeof(u32)) /* TCA_POLICE_RESULT */ + + nla_total_size(sizeof(u32)); /* TCA_POLICE_AVRATE */ +} + MODULE_AUTHOR("Alexey Kuznetsov"); MODULE_DESCRIPTION("Policing actions"); MODULE_LICENSE("GPL"); @@ -503,6 +514,7 @@ static struct tc_action_ops act_police_ops = { .dump = tcf_police_dump, .init = tcf_police_init, .cleanup = tcf_police_cleanup, + .get_fill_size = tcf_police_get_fill_size, .offload_act_setup = tcf_police_offload_act_setup, .size = sizeof(struct tcf_police), }; diff --git a/net/sched/act_sample.c b/net/sched/act_sample.c index 2ceb4d141b71..44319a159b55 100644 --- a/net/sched/act_sample.c +++ b/net/sched/act_sample.c @@ -315,6 +315,14 @@ static int tcf_sample_offload_act_setup(struct tc_action *act, void *entry_data, return 0; } +static size_t tcf_sample_get_fill_size(const struct tc_action *act) +{ + return nla_total_size(sizeof(struct tc_sample)) /* TCA_SAMPLE_PARMS */ + + nla_total_size(sizeof(u32)) /* TCA_SAMPLE_RATE */ + + nla_total_size(sizeof(u32)) /* TCA_SAMPLE_TRUNC_SIZE */ + + nla_total_size(sizeof(u32)); /* TCA_SAMPLE_PSAMPLE_GROUP */ +} + static struct tc_action_ops act_sample_ops = { .kind = "sample", .id = TCA_ID_SAMPLE, @@ -324,6 +332,7 @@ static struct tc_action_ops act_sample_ops = { .dump = tcf_sample_dump, .init = tcf_sample_init, .cleanup = tcf_sample_cleanup, + .get_fill_size = tcf_sample_get_fill_size, .get_psample_group = tcf_sample_get_group, .offload_act_setup = tcf_sample_offload_act_setup, .size = sizeof(struct tcf_sample), diff --git a/net/sched/act_tunnel_key.c b/net/sched/act_tunnel_key.c index b14807761d82..ff401ace4f3d 100644 --- a/net/sched/act_tunnel_key.c +++ b/net/sched/act_tunnel_key.c @@ -835,6 +835,85 @@ static int tcf_tunnel_key_offload_act_setup(struct tc_action *act, return 0; } +static size_t +tunnel_key_geneve_opts_fill_size(const struct ip_tunnel_info *info) +{ + const u8 *src = ip_tunnel_info_opts(info); + int len = info->options_len; + size_t size = 0; + + while (len > 0) { + const struct geneve_opt *opt = (const struct geneve_opt *)src; + + /* TCA_TUNNEL_KEY_ENC_OPT_GENEVE_{CLASS,TYPE,DATA} */ + size += nla_total_size(2) + + nla_total_size(1) + + nla_total_size(opt->length * 4); + + len -= sizeof(struct geneve_opt) + opt->length * 4; + src += sizeof(struct geneve_opt) + opt->length * 4; + } + + return size; +} + +static size_t tunnel_key_opts_fill_size(const struct ip_tunnel_info *info) +{ + size_t size; + + if (!info->options_len) + return 0; + + /* TCA_TUNNEL_KEY_ENC_OPTS and the per-protocol nest inside it */ + size = nla_total_size(0) + nla_total_size(0); + + if (test_bit(IP_TUNNEL_GENEVE_OPT_BIT, info->key.tun_flags)) { + size += tunnel_key_geneve_opts_fill_size(info); + } else if (test_bit(IP_TUNNEL_VXLAN_OPT_BIT, info->key.tun_flags)) { + /* TCA_TUNNEL_KEY_ENC_OPT_VXLAN_GBP */ + size += nla_total_size(sizeof(u32)); + } else if (test_bit(IP_TUNNEL_ERSPAN_OPT_BIT, info->key.tun_flags)) { + /* TCA_TUNNEL_KEY_ENC_OPT_ERSPAN_{VER,INDEX,DIR,HWID} */ + size += nla_total_size(sizeof(u8)) + + nla_total_size(sizeof(__be32)) + + nla_total_size(sizeof(u8)) + + nla_total_size(sizeof(u8)); + } + + return size; +} + +static size_t tunnel_key_get_fill_size(const struct tc_action *act) +{ + struct tcf_tunnel_key *t = to_tunnel_key(act); + const struct tcf_tunnel_key_params *params; + /* TCA_TUNNEL_KEY_PARMS */ + size_t size = nla_total_size(sizeof(struct tc_tunnel_key)); + + rcu_read_lock(); + params = rcu_dereference(t->params); + if (params->tcft_action == TCA_TUNNEL_KEY_ACT_SET) { + const struct ip_tunnel_info *info = + ¶ms->tcft_enc_metadata->u.tun_info; + + /* In dump order: TCA_TUNNEL_KEY_ENC_KEY_ID, the IPv6 address + * pair (larger than the IPv4 one), ..._ENC_DST_PORT, + * ..._NO_CSUM, ..._NO_FRAG, the options and ..._ENC_{TOS,TTL}. + */ + size += nla_total_size(sizeof(__be32)) + + 2 * nla_total_size(sizeof(struct in6_addr)) + + nla_total_size(sizeof(__be16)) + + nla_total_size(sizeof(u8)) + + nla_total_size(0) + + tunnel_key_opts_fill_size(info) + + nla_total_size(sizeof(u8)) + + nla_total_size(sizeof(u8)); + } + rcu_read_unlock(); + + return size; +} + static struct tc_action_ops act_tunnel_key_ops = { .kind = "tunnel_key", .id = TCA_ID_TUNNEL_KEY, @@ -843,6 +922,7 @@ static struct tc_action_ops act_tunnel_key_ops = { .dump = tunnel_key_dump, .init = tunnel_key_init, .cleanup = tunnel_key_release, + .get_fill_size = tunnel_key_get_fill_size, .offload_act_setup = tcf_tunnel_key_offload_act_setup, .size = sizeof(struct tcf_tunnel_key), }; From da4471557f279d0f56605158a625bb6e49ef7d41 Mon Sep 17 00:00:00 2001 From: Hyunwoo Kim Date: Mon, 17 Aug 2026 06:28:42 +0900 Subject: [PATCH 0715/1328] net/tcp-ao: fix use-after-free of current_key on reconnect to another peer tcp_inbound_ao_hash() is called before bh_lock_sock_nested() is taken, with only rcu_read_lock() held. On the fast path for established sockets, if the rnext_keyid sent by the peer differs from current_key->sndid, the key the peer asked for is looked up and stored in current_key. The lookup is inside the RCU read side, but current_key outlives it. When the socket is disconnected and connect() is called again for another peer, tcp_ao_connect_init() unlinks every key that does not match the new peer and frees it with call_rcu(). If current_key points at such a key, it is cleared to NULL. The fast path reads sk_state only once on entry, so a softirq that got into it while the socket was still established can update current_key after that loop has already run. The update is inside the RCU read side, so it comes before the call_rcu() callback, and once the callback frees the key, current_key is left pointing at freed memory. The next transmission picks that pointer up in tcp_get_current_key(). tcp_ao_transmit_skb() then reads the traffic key from the freed object, which is the use-after-free. Wait for one grace period before unlinking, and only if a key is going to be removed. By the time tcp_connect() runs the socket is already in TCP_SYN_SENT, and TCP_AO_ESTABLISHED does not contain TCPF_SYN_SENT, so a softirq entering after the wait cannot reach the fast path, and the ones already in it have finished. The existing NULL handling in the loop is then enough. Fixes: 0a3a809089eb ("net/tcp: Verify inbound TCP-AO signed segments") Cc: stable@vger.kernel.org Signed-off-by: Hyunwoo Kim Reviewed-by: Simon Horman Acked-by: Paolo Abeni Link: https://patch.msgid.link/aoIriv3pHDgII2YR@v4bel Signed-off-by: Jakub Kicinski --- net/ipv4/tcp_ao.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/net/ipv4/tcp_ao.c b/net/ipv4/tcp_ao.c index e4ec60a33496..5094267447ae 100644 --- a/net/ipv4/tcp_ao.c +++ b/net/ipv4/tcp_ao.c @@ -1160,6 +1160,15 @@ void tcp_ao_connect_init(struct sock *sk) l3index = l3mdev_master_ifindex_by_index(sock_net(sk), sk->sk_bound_dev_if); + hlist_for_each_entry(key, &ao_info->head, node) { + if (tcp_ao_key_cmp(key, l3index, addr, key->prefixlen, + family, -1, -1)) { + /* pairs with tcp_inbound_ao_hash() */ + synchronize_rcu(); + break; + } + } + hlist_for_each_entry_safe(key, next, &ao_info->head, node) { if (!tcp_ao_key_cmp(key, l3index, addr, key->prefixlen, family, -1, -1)) continue; From f826df95332c07380206dbd54178b6eefb311aba Mon Sep 17 00:00:00 2001 From: Andrea Mayer Date: Mon, 17 Aug 2026 15:26:44 +0200 Subject: [PATCH 0716/1328] ipv6: rpl: fix NULL dereference of idev in ipv6_rpl_srh_rcv() ipv6_rpl_srh_rcv() dereferences idev from __in6_dev_get() without a NULL check when reading idev->cnf.rpl_seg_enabled. When the device's MTU drops below IPV6_MIN_MTU, addrconf_ifdown() clears dev->ip6_ptr through RCU_INIT_POINTER(). A packet that passed the idev check in ip6_rcv_core() can then reach ipv6_rpl_srh_rcv() with dev->ip6_ptr already NULL. Reproduced by flooding the receiving interface with ping6 traffic while flapping its MTU between 1500 and 1200: BUG: KASAN: null-ptr-deref in ipv6_rpl_srh_rcv+0xb3/0x1070 Read of size 4 at addr 00000000000006b4 by task ping6/394 CPU: 2 UID: 0 PID: 394 Comm: ping6 Not tainted 7.2.0-rc7-micro-vm-dev-00095-g24ef02f934ee #240 PREEMPT(full) Call Trace: kasan_report+0xc6/0x100 ipv6_rpl_srh_rcv+0xb3/0x1070 ip6_protocol_deliver_rcu+0x759/0x9a0 ip6_input_finish+0xa8/0x1b0 ip6_input+0xe1/0x490 ipv6_rcv+0x33d/0x460 __netif_receive_skb_one_core+0xd6/0x130 process_backlog+0x2cc/0xa00 __napi_poll.constprop.0+0x56/0x270 net_rx_action+0x327/0x730 handle_softirqs+0x11e/0x630 do_softirq+0xb3/0xf0 Both ipv6_rpl_srh_rcv() and ipv6_srh_rcv() are called only from ipv6_rthdr_rcv(), which already has an idev lookup. Fix the NULL dereference on the RPL path by checking idev in ipv6_rthdr_rcv(), before it calls either function. The callees take idev as an argument and no longer call __in6_dev_get(), so the packet is now dropped in one place, with SKB_DROP_REASON_IPV6DISABLED on both paths. Fixes: 8610c7c6e3bd ("net: ipv6: add support for rpl sr exthdr") Cc: stable@vger.kernel.org Signed-off-by: Andrea Mayer Tested-by: Xiang Mei Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260817132644.2223-1-andrea.mayer@uniroma2.it Signed-off-by: Jakub Kicinski --- net/ipv6/exthdrs.c | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c index 9c677eb1d1a6..51941ad656a3 100644 --- a/net/ipv6/exthdrs.c +++ b/net/ipv6/exthdrs.c @@ -368,23 +368,16 @@ static void seg6_update_csum(struct sk_buff *skb) (__be32 *)addr); } -static int ipv6_srh_rcv(struct sk_buff *skb) +static int ipv6_srh_rcv(struct sk_buff *skb, struct inet6_dev *idev) { struct inet6_skb_parm *opt = IP6CB(skb); struct net *net = dev_net(skb->dev); struct ipv6_sr_hdr *hdr; - struct inet6_dev *idev; struct in6_addr *addr; int accept_seg6; hdr = (struct ipv6_sr_hdr *)skb_transport_header(skb); - idev = __in6_dev_get(skb->dev); - if (!idev) { - kfree_skb(skb); - return -1; - } - accept_seg6 = min(READ_ONCE(net->ipv6.devconf_all->seg6_enabled), READ_ONCE(idev->cnf.seg6_enabled)); @@ -485,12 +478,11 @@ static int ipv6_srh_rcv(struct sk_buff *skb) return -1; } -static int ipv6_rpl_srh_rcv(struct sk_buff *skb) +static int ipv6_rpl_srh_rcv(struct sk_buff *skb, struct inet6_dev *idev) { struct ipv6_rpl_sr_hdr *hdr, *ohdr, *chdr; struct inet6_skb_parm *opt = IP6CB(skb); struct net *net = dev_net(skb->dev); - struct inet6_dev *idev; struct ipv6hdr *oldhdr; unsigned int chdr_len; unsigned char *buf; @@ -499,8 +491,6 @@ static int ipv6_rpl_srh_rcv(struct sk_buff *skb) u64 n = 0; u32 r; - idev = __in6_dev_get(skb->dev); - accept_rpl_seg = min(READ_ONCE(net->ipv6.devconf_all->rpl_seg_enabled), READ_ONCE(idev->cnf.rpl_seg_enabled)); if (!accept_rpl_seg) { @@ -689,10 +679,14 @@ static int ipv6_rthdr_rcv(struct sk_buff *skb) switch (hdr->type) { case IPV6_SRCRT_TYPE_4: /* segment routing */ - return ipv6_srh_rcv(skb); + if (!idev) + goto disabled; + return ipv6_srh_rcv(skb, idev); case IPV6_SRCRT_TYPE_3: /* rpl segment routing */ - return ipv6_rpl_srh_rcv(skb); + if (!idev) + goto disabled; + return ipv6_rpl_srh_rcv(skb, idev); default: break; } @@ -837,6 +831,10 @@ static int ipv6_rthdr_rcv(struct sk_buff *skb) icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, (&hdr->type) - skb_network_header(skb)); return -1; + +disabled: + kfree_skb_reason(skb, SKB_DROP_REASON_IPV6DISABLED); + return -1; } static const struct inet6_protocol rthdr_protocol = { From c5ae83ee02c04487b1be1d143b791ec2caca888e Mon Sep 17 00:00:00 2001 From: Fan Ye Date: Sat, 15 Aug 2026 10:21:52 +0000 Subject: [PATCH 0717/1328] net: thunderbolt: Count delivered packets in rx_packets and rx_bytes tbnet_poll() increments rx_packets once per received frame because that is the NAPI work unit, and then adds the same number to stats.rx_packets. An skb is handed to the stack only when the last frame of a packet arrives, so once the MTU exceeds TBNET_MAX_PAYLOAD_SIZE the statistic reports frames. tx_packets is bumped once per skb, so the two ends of a link disagree: at MTU 65330 the receiver reports 16 times the packets its sender sent. rx_bytes has the matching problem: frames of a packet that is later dropped mid-assembly are already accounted, so it does not correspond to rx_packets as documented. Account for both where the packet is completed, and leave the NAPI work counter alone. Fixes: e69b6c02b4c3 ("net: Add support for networking over Thunderbolt cable") Signed-off-by: Fan Ye Reviewed-by: Simon Horman Acked-by: Mika Westerberg Link: https://patch.msgid.link/20260815-tbnet-rx-stats-v1-1-8da375c2cd09@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/thunderbolt/main.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/net/thunderbolt/main.c b/drivers/net/thunderbolt/main.c index 2a1728621887..82cef20092b8 100644 --- a/drivers/net/thunderbolt/main.c +++ b/drivers/net/thunderbolt/main.c @@ -904,9 +904,11 @@ static int tbnet_poll(struct napi_struct *napi, int budget) le32_to_cpu(net->rx_hdr.frame_count) - 1; rx_packets++; - net->stats.rx_bytes += frame_size; if (last) { + /* Before eth_type_trans() pulls the Ethernet header. */ + net->stats.rx_packets++; + net->stats.rx_bytes += skb->len; skb->protocol = eth_type_trans(skb, net->dev); trace_tbnet_rx_skb(skb); napi_gro_receive(&net->napi, skb); @@ -914,8 +916,6 @@ static int tbnet_poll(struct napi_struct *napi, int budget) } } - net->stats.rx_packets += rx_packets; - if (cleaned_count) tbnet_alloc_rx_buffers(net, cleaned_count); From ff0f9b7aa1c756e24a8023eaf684e93774c64194 Mon Sep 17 00:00:00 2001 From: Mark Bloch Date: Sun, 16 Aug 2026 09:50:13 +0300 Subject: [PATCH 0718/1328] net/mlx5: E-Switch, use state lock for vport state changes Protect vport admin state modifications and vport iteration with the eswitch state_lock mutex to ensure proper serialization of concurrent vport state changes. Currently, calls to mlx5_modify_vport_admin_state() and loops iterating over eswitch vports can race with each other, potentially leading to inconsistent vport state. Fix this by acquiring esw->state_lock Fixes: 7d0314b11cdd ("net/mlx5e: Modify uplink state on interface up/down") Signed-off-by: Mark Bloch Reviewed-by: Shay Drori Reviewed-by: Or Har-Toov Signed-off-by: Tariq Toukan Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260816065015.3280733-2-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/mellanox/mlx5/core/en_rep.c | 20 ++++++++++++++----- .../mellanox/mlx5/core/esw/adj_vport.c | 2 ++ .../mellanox/mlx5/core/eswitch_offloads.c | 4 ++++ .../net/ethernet/mellanox/mlx5/core/lag/lag.c | 2 ++ .../net/ethernet/mellanox/mlx5/core/vport.c | 8 ++++++++ 5 files changed, 31 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c b/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c index 603051ab1eaa..ce765692fd19 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c @@ -689,11 +689,13 @@ static int mlx5e_rep_open(struct net_device *dev) if (err) goto unlock; + mutex_lock(&rep->esw->state_lock); if (!mlx5_modify_vport_admin_state(priv->mdev, MLX5_VPORT_STATE_OP_MOD_ESW_VPORT, rep->vport, 1, MLX5_VPORT_ADMIN_STATE_UP)) netif_carrier_on(dev); + mutex_unlock(&rep->esw->state_lock); unlock: mutex_unlock(&priv->state_lock); @@ -708,10 +710,12 @@ static int mlx5e_rep_close(struct net_device *dev) int ret; mutex_lock(&priv->state_lock); + mutex_lock(&rep->esw->state_lock); mlx5_modify_vport_admin_state(priv->mdev, MLX5_VPORT_STATE_OP_MOD_ESW_VPORT, rep->vport, 1, MLX5_VPORT_ADMIN_STATE_DOWN); + mutex_unlock(&rep->esw->state_lock); ret = mlx5e_close_locked(dev); mutex_unlock(&priv->state_lock); return ret; @@ -783,22 +787,25 @@ static int mlx5e_rep_change_carrier(struct net_device *dev, bool new_carrier) struct mlx5e_priv *priv = netdev_priv(dev); struct mlx5e_rep_priv *rpriv = priv->ppriv; struct mlx5_eswitch_rep *rep = rpriv->rep; - int err; + int err = 0; + mutex_lock(&rep->esw->state_lock); if (new_carrier) { err = mlx5_modify_vport_admin_state(priv->mdev, MLX5_VPORT_STATE_OP_MOD_ESW_VPORT, rep->vport, 1, MLX5_VPORT_ADMIN_STATE_UP); if (err) - return err; + goto unlock; netif_carrier_on(dev); } else { err = mlx5_modify_vport_admin_state(priv->mdev, MLX5_VPORT_STATE_OP_MOD_ESW_VPORT, rep->vport, 1, MLX5_VPORT_ADMIN_STATE_DOWN); if (err) - return err; + goto unlock; netif_carrier_off(dev); } - return 0; +unlock: + mutex_unlock(&rep->esw->state_lock); + return err; } static const struct net_device_ops mlx5e_netdev_ops_rep = { @@ -1339,9 +1346,12 @@ static int mlx5e_uplink_rep_enable(struct mlx5e_priv *priv) mlx5e_rep_tc_enable(priv); - if (MLX5_CAP_GEN(mdev, uplink_follow)) + if (MLX5_CAP_GEN(mdev, uplink_follow)) { + mutex_lock(&mdev->priv.eswitch->state_lock); mlx5_modify_vport_admin_state(mdev, MLX5_VPORT_STATE_OP_MOD_UPLINK, 0, 0, MLX5_VPORT_ADMIN_STATE_AUTO); + mutex_unlock(&mdev->priv.eswitch->state_lock); + } mlx5_lag_add_netdev(mdev, netdev); priv->events_nb.notifier_call = uplink_rep_async_event; mlx5_notifier_register(mdev, &priv->events_nb); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/adj_vport.c b/drivers/net/ethernet/mellanox/mlx5/core/esw/adj_vport.c index ca249b50f830..3624c680a861 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/adj_vport.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/adj_vport.c @@ -9,6 +9,8 @@ int mlx5_esw_adj_vport_modify(struct mlx5_core_dev *dev, u16 vport, { u32 in[MLX5_ST_SZ_DW(modify_vport_state_in)] = {}; + lockdep_assert_held(&dev->priv.eswitch->state_lock); + MLX5_SET(modify_vport_state_in, in, opcode, MLX5_CMD_OP_MODIFY_VPORT_STATE); MLX5_SET(modify_vport_state_in, in, op_mod, diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c b/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c index 247e5d85ec36..d603e294ee0e 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c @@ -2567,6 +2567,7 @@ static void mlx5_esw_fdb_active(struct mlx5_eswitch *esw) mlx5_esw_fdb_drop_destroy(esw); mlx5_mpfs_enable(esw->dev); + mutex_lock(&esw->state_lock); mlx5_esw_for_each_vf_vport(esw, i, vport, U16_MAX) { if (!vport->adjacent) continue; @@ -2574,6 +2575,7 @@ static void mlx5_esw_fdb_active(struct mlx5_eswitch *esw) vport->vport); mlx5_esw_adj_vport_modify(esw->dev, vport->vport, true); } + mutex_unlock(&esw->state_lock); esw->offloads_inactive = false; esw_warn(esw->dev, "MPFS/FDB active\n"); @@ -2587,6 +2589,7 @@ static void mlx5_esw_fdb_inactive(struct mlx5_eswitch *esw) mlx5_mpfs_disable(esw->dev); mlx5_esw_fdb_drop_create(esw); + mutex_lock(&esw->state_lock); mlx5_esw_for_each_vf_vport(esw, i, vport, U16_MAX) { if (!vport->adjacent) continue; @@ -2595,6 +2598,7 @@ static void mlx5_esw_fdb_inactive(struct mlx5_eswitch *esw) mlx5_esw_adj_vport_modify(esw->dev, vport->vport, false); } + mutex_unlock(&esw->state_lock); esw->offloads_inactive = true; esw_warn(esw->dev, "MPFS/FDB inactive\n"); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c b/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c index 28d16fdc3f06..2285c889c215 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c @@ -1471,6 +1471,7 @@ static void mlx5_lag_modify_device_vports_speed(struct mlx5_core_dev *mdev, if (!MLX5_CAP_ESW(mdev, esw_vport_state_max_tx_speed)) return; + mutex_lock(&esw->state_lock); mlx5_esw_for_each_vport(esw, i, vport) { if (!vport) continue; @@ -1490,6 +1491,7 @@ static void mlx5_lag_modify_device_vports_speed(struct mlx5_core_dev *mdev, "Failed to set vport %d speed %d, err=%d\n", vport->vport, speed, ret); } + mutex_unlock(&esw->state_lock); } void mlx5_lag_set_vports_agg_speed(struct mlx5_lag *ldev) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/vport.c b/drivers/net/ethernet/mellanox/mlx5/core/vport.c index 3d86510af615..01ae383d300a 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/vport.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/vport.c @@ -89,6 +89,10 @@ int mlx5_modify_vport_admin_state(struct mlx5_core_dev *mdev, u8 opmod, { u32 in[MLX5_ST_SZ_DW(modify_vport_state_in)] = {}; +#ifdef CONFIG_MLX5_ESWITCH + lockdep_assert_held(&mdev->priv.eswitch->state_lock); +#endif + MLX5_SET(modify_vport_state_in, in, opcode, MLX5_CMD_OP_MODIFY_VPORT_STATE); MLX5_SET(modify_vport_state_in, in, op_mod, opmod); @@ -106,6 +110,10 @@ int mlx5_modify_vport_max_tx_speed(struct mlx5_core_dev *mdev, u8 opmod, u8 admin_state; int err; +#ifdef CONFIG_MLX5_ESWITCH + lockdep_assert_held(&mdev->priv.eswitch->state_lock); +#endif + err = mlx5_query_vport_admin_state(mdev, opmod, vport, other_vport, &admin_state); if (err) From 20f11b5cfa429ba3a2b8ef463d47e820687b4d2e Mon Sep 17 00:00:00 2001 From: Or Har-Toov Date: Sun, 16 Aug 2026 09:50:14 +0300 Subject: [PATCH 0719/1328] net/mlx5: Move vport DOWN state check out of mlx5_query_vport_max_tx_speed() mlx5_query_vport_max_tx_speed() was introduced to serve the query_port_speed path, which uses max_tx_speed == 0 when port is down. This is incorrect for callers that need the actual configured speed regardless of vport state, such as modify-vport-state helpers that must preserve the speed across state transitions. Move this logic to the caller function in the verb flow and let mlx5_query_vport_max_tx_speed() return the raw firmware value unconditionally. Fixes: aaecff5e13cd ("RDMA/mlx5: Implement query_port_speed callback") Signed-off-by: Or Har-Toov Reviewed-by: Shay Drori Signed-off-by: Tariq Toukan Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260816065015.3280733-3-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/infiniband/hw/mlx5/main.c | 5 +++-- drivers/net/ethernet/mellanox/mlx5/core/vport.c | 12 ++++-------- include/linux/mlx5/vport.h | 3 ++- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/drivers/infiniband/hw/mlx5/main.c b/drivers/infiniband/hw/mlx5/main.c index 02809114fc79..794b869579ad 100644 --- a/drivers/infiniband/hw/mlx5/main.c +++ b/drivers/infiniband/hw/mlx5/main.c @@ -1631,14 +1631,15 @@ static int mlx5_ib_query_port_speed_from_vport(struct mlx5_core_dev *mdev, u32 port_num) { u32 max_tx_speed; + u8 vport_state; int err; err = mlx5_query_vport_max_tx_speed(mdev, op_mod, vport, other_vport, - &max_tx_speed); + &max_tx_speed, &vport_state); if (err) return err; - if (max_tx_speed == 0) + if (vport_state == VPORT_STATE_DOWN || max_tx_speed == 0) /* Value 0 indicates field not supported, fallback */ return mlx5_ib_query_port_speed_from_port(dev, port_num, speed); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/vport.c b/drivers/net/ethernet/mellanox/mlx5/core/vport.c index 01ae383d300a..78f3d15b599d 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/vport.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/vport.c @@ -131,11 +131,11 @@ int mlx5_modify_vport_max_tx_speed(struct mlx5_core_dev *mdev, u8 opmod, } int mlx5_query_vport_max_tx_speed(struct mlx5_core_dev *mdev, u8 op_mod, - u16 vport, u8 other_vport, u32 *max_tx_speed) + u16 vport, u8 other_vport, + u32 *max_tx_speed, u8 *state) { u32 out[MLX5_ST_SZ_DW(query_vport_state_out)] = {}; u32 in[MLX5_ST_SZ_DW(query_vport_state_in)] = {}; - u32 state; int err; MLX5_SET(query_vport_state_in, in, opcode, @@ -148,13 +148,9 @@ int mlx5_query_vport_max_tx_speed(struct mlx5_core_dev *mdev, u8 op_mod, if (err) return err; - state = MLX5_GET(query_vport_state_out, out, state); - if (state == VPORT_STATE_DOWN) { - *max_tx_speed = 0; - return 0; - } - *max_tx_speed = MLX5_GET(query_vport_state_out, out, max_tx_speed); + if (state) + *state = MLX5_GET(query_vport_state_out, out, state); return 0; } EXPORT_SYMBOL_GPL(mlx5_query_vport_max_tx_speed); diff --git a/include/linux/mlx5/vport.h b/include/linux/mlx5/vport.h index 577168a4ca0c..57c6b5bacedb 100644 --- a/include/linux/mlx5/vport.h +++ b/include/linux/mlx5/vport.h @@ -61,7 +61,8 @@ u8 mlx5_query_vport_state(struct mlx5_core_dev *mdev, u8 opmod, u16 vport); int mlx5_modify_vport_admin_state(struct mlx5_core_dev *mdev, u8 opmod, u16 vport, u8 other_vport, u8 state); int mlx5_query_vport_max_tx_speed(struct mlx5_core_dev *mdev, u8 op_mod, - u16 vport, u8 other_vport, u32 *max_tx_speed); + u16 vport, u8 other_vport, + u32 *max_tx_speed, u8 *state); int mlx5_modify_vport_max_tx_speed(struct mlx5_core_dev *mdev, u8 opmod, u16 vport, u8 other_vport, u16 max_tx_speed); int mlx5_query_nic_vport_mac_address(struct mlx5_core_dev *mdev, From ad0ae7aefa7a6ede28a549d8dac87456d87ef6d4 Mon Sep 17 00:00:00 2001 From: Or Har-Toov Date: Sun, 16 Aug 2026 09:50:15 +0300 Subject: [PATCH 0720/1328] net/mlx5: E-Switch, preserve max tx speed on vport state modification When modifying vport state, the firmware interprets a zero in the max tx speed field as an intentional reset, which can overwrite previously set values. This patch attempts to fix this by querying the current max tx speed from firmware before modifying the vport state and passing it back in the modification command. If the query fails, fall back to the cached agg_max_tx_speed value to avoid inadvertently resetting the speed. Fixes: 50f1d188c580 ("net/mlx5: Propagate LAG effective max_tx_speed to vports") Signed-off-by: Or Har-Toov Reviewed-by: Mark Bloch Reviewed-by: Shay Drori Signed-off-by: Tariq Toukan Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260816065015.3280733-4-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../mellanox/mlx5/core/esw/adj_vport.c | 20 ++++++++++++++++ .../net/ethernet/mellanox/mlx5/core/vport.c | 24 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/adj_vport.c b/drivers/net/ethernet/mellanox/mlx5/core/esw/adj_vport.c index 3624c680a861..7950b82d8b8a 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/adj_vport.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/adj_vport.c @@ -11,6 +11,26 @@ int mlx5_esw_adj_vport_modify(struct mlx5_core_dev *dev, u16 vport, lockdep_assert_held(&dev->priv.eswitch->state_lock); + if (MLX5_CAP_ESW(dev, esw_vport_state_max_tx_speed)) { + u8 op_mod = MLX5_VPORT_STATE_OP_MOD_ESW_VPORT; + struct mlx5_vport *esw_vport; + u32 speed = 0; + int err; + + err = mlx5_query_vport_max_tx_speed(dev, op_mod, vport, + true, &speed, NULL); + if (err) { + esw_vport = mlx5_eswitch_get_vport(dev->priv.eswitch, + vport); + speed = IS_ERR(esw_vport) ? 0 : + esw_vport->agg_max_tx_speed; + mlx5_core_dbg(dev, + "Failed to query vport %d max tx speed, err=%d, using cached %u\n", + vport, err, speed); + } + MLX5_SET(modify_vport_state_in, in, max_tx_speed, speed); + } + MLX5_SET(modify_vport_state_in, in, opcode, MLX5_CMD_OP_MODIFY_VPORT_STATE); MLX5_SET(modify_vport_state_in, in, op_mod, diff --git a/drivers/net/ethernet/mellanox/mlx5/core/vport.c b/drivers/net/ethernet/mellanox/mlx5/core/vport.c index 78f3d15b599d..5774ec117428 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/vport.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/vport.c @@ -93,6 +93,30 @@ int mlx5_modify_vport_admin_state(struct mlx5_core_dev *mdev, u8 opmod, lockdep_assert_held(&mdev->priv.eswitch->state_lock); #endif + if (MLX5_CAP_ESW(mdev, esw_vport_state_max_tx_speed) && + opmod == MLX5_VPORT_STATE_OP_MOD_ESW_VPORT && + vport != MLX5_VPORT_UPLINK) { + u32 speed = 0; + int err; + + err = mlx5_query_vport_max_tx_speed(mdev, opmod, vport, + other_vport, &speed, NULL); + if (err) { +#ifdef CONFIG_MLX5_ESWITCH + struct mlx5_vport *esw_vport; + + esw_vport = mlx5_eswitch_get_vport(mdev->priv.eswitch, + vport); + speed = IS_ERR(esw_vport) ? 0 : + esw_vport->agg_max_tx_speed; +#endif + mlx5_core_dbg(mdev, + "Failed to query vport %d max tx speed, err=%d, using cached %u\n", + vport, err, speed); + } + MLX5_SET(modify_vport_state_in, in, max_tx_speed, speed); + } + MLX5_SET(modify_vport_state_in, in, opcode, MLX5_CMD_OP_MODIFY_VPORT_STATE); MLX5_SET(modify_vport_state_in, in, op_mod, opmod); From 9393f1d656a79693e0c123ff7bc7c5c0f708046d Mon Sep 17 00:00:00 2001 From: Marek Czernohous Date: Sat, 15 Aug 2026 21:54:38 +0200 Subject: [PATCH 0721/1328] forcedeth: fix off-by-one when saving/restoring non-PCI config space nv_suspend() and nv_resume() walk the non-PCI configuration space with for (i = 0; i <= np->register_size/sizeof(u32); i++) which runs one iteration too many. saved_config_space is declared as u32 saved_config_space[NV_PCI_REGSZ_MAX/4]; and NV_PCI_REGSZ_VER3 is equal to NV_PCI_REGSZ_MAX (0x604), so on a VER3 device register_size/sizeof(u32) is exactly the array length and the last iteration addresses one element past the end. The element it lands on is np->name_rx[0..3]: saved_config_space[] is followed immediately by char name_rx[IFNAMSIZ + 3], and char needs no padding. Nothing observable is corrupted by that, because nv_request_irq() rewrites name_rx with sprintf() before it is ever passed to request_irq(). The bug is the out-of-bounds access itself, which UBSAN reports and which CONFIG_UBSAN_TRAP=y turns into a trap that aborts the running kernel code, plus an MMIO read and, on resume, an MMIO writel() to base + 0x604, one dword past the range the driver mapped: np->base = ioremap(addr, np->register_size); VER1 and VER2 devices stay inside the array, but they too get the stray read and the stray write one dword past their own window. Caught by UBSAN on an Apple Macmini3,1 (MCP79) during a deep S3 cycle. The splat below is trimmed: the build path in the file name, the CPU and taint lines, the Workqueue line, the "?" hint frames, and the frames below device_suspend are all cut. The kernel was tainted, with an out-of-tree nouveau and CPU_OUT_OF_SPEC; forcedeth itself was the stock module. UBSAN: array-index-out-of-bounds in drivers/net/ethernet/nvidia/forcedeth.c:6225:25 index 385 is out of range for type 'u32 [385]' Call Trace: dump_stack_lvl+0x5d/0x80 ubsan_epilogue+0x5/0x2b __ubsan_handle_out_of_bounds.cold+0x54/0x59 __this_module+0xe398c/0xe9010 [forcedeth] pci_pm_suspend+0x80/0x170 dpm_run_callback+0x51/0x160 device_suspend+0x1a2/0x4a0 ... Both loops are hit. UBSAN reports each source location only once per module load (__ubsan_handle_out_of_bounds() calls suppress_report(), which does test_and_set_bit(REPORTED_BIT, ...) on the struct source_location), so the two splats land in the first S3 cycle after the module is loaded and later cycles are silent even though the access still runs off the end every time. In that first cycle line 6225 is reported from pci_pm_suspend and line 6240 from pci_pm_resume. The same off-by-one was fixed in nv_get_regs() by commit ba9aa134287f ("forcedeth: fix buffer overflow") in 2012; these two loops were missed. The suspend and resume side was reported on LKML in September 2013 by Marc Weber, with the same analysis and the same one-character fix, but the patch was attached rather than sent inline and the thread ended there. Use < instead of <=, which saves and restores exactly register_size bytes. Fixes: 1a1ca86158ee ("[netdrvr] forcedeth: save/restore device configuration space") Cc: stable@vger.kernel.org Signed-off-by: Marek Czernohous Reviewed-by: Simon Horman Reviewed-by: Zhu Yanjun Link: https://patch.msgid.link/178682367885.3748309.10595890901761762683@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/nvidia/forcedeth.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/nvidia/forcedeth.c b/drivers/net/ethernet/nvidia/forcedeth.c index 58d3e55def48..dc804e111564 100644 --- a/drivers/net/ethernet/nvidia/forcedeth.c +++ b/drivers/net/ethernet/nvidia/forcedeth.c @@ -6221,7 +6221,7 @@ static int nv_suspend(struct device *device) netif_device_detach(dev); /* save non-pci configuration space */ - for (i = 0; i <= np->register_size/sizeof(u32); i++) + for (i = 0; i < np->register_size/sizeof(u32); i++) np->saved_config_space[i] = readl(base + i*sizeof(u32)); return 0; @@ -6236,7 +6236,7 @@ static int nv_resume(struct device *device) int i, rc = 0; /* restore non-pci configuration space */ - for (i = 0; i <= np->register_size/sizeof(u32); i++) + for (i = 0; i < np->register_size/sizeof(u32); i++) writel(np->saved_config_space[i], base+i*sizeof(u32)); if (np->driver_data & DEV_NEED_MSI_FIX) From cfa9178ce2e5b4f2e2a9e8b577a6cff841665aa0 Mon Sep 17 00:00:00 2001 From: Marek Czernohous Date: Sat, 15 Aug 2026 21:54:38 +0200 Subject: [PATCH 0722/1328] forcedeth: stop the tx_timeout register dump past the requested window nv_tx_timeout() dumps the register window in rows of eight dwords: for (i = 0; i <= np->register_size; i += 32) { netdev_info(dev, "%3x: %08x ... %08x\n", i, readl(base + i + 0), ..., readl(base + i + 28)); The loop bound only checks the row's starting offset, so the final row reads a full 32 bytes from a position that is below the end of the window but too close to it. base is mapped with exactly that length: np->base = ioremap(addr, np->register_size); so the tail of that row is read from beyond the length the driver asked for. Per variant, the last iteration reads past register_size by: NV_PCI_REGSZ_VER1 (0x270): row 0x260 reads to 0x27f, 16 bytes over NV_PCI_REGSZ_VER2 (0x2d4): row 0x2c0 reads to 0x2df, 12 bytes over NV_PCI_REGSZ_VER3 (0x604): row 0x600 reads to 0x61f, 28 bytes over This happens on every supported device, not just one of them. Note that it is not a consequence of the sizes being odd: with i <= register_size the offending row is reached whatever the size, and a size that were a multiple of 32 would overrun by a full row rather than by a remainder. To be precise about the severity: the reads stay inside the BAR. Memory BAR sizes are powers of two, the driver only accepts a region with pci_resource_len() >= register_size (forcedeth.c:5757-5762), and the next power of two at or above each register_size already covers the offending row: 0x400 for 0x270 and 0x2d4, 0x800 for 0x604. ioremap() also rounds the mapped length up to page granularity, so the reads land inside the mapping the CPU has as well. What they leave is the window the driver asked for, not the BAR and not the mapping. That is still a driver reading registers it did not ask for, and it is trivial to avoid, but nobody should expect a fault from it. Changing <= to < is not enough: register_size is a length and every size above is larger than its last row start, so i still reaches the offending row. Check that the whole row fits instead. The trade-off is that a partial trailing row is no longer dumped: 16 bytes for VER1, 20 for VER2, 4 for VER3. That seemed preferable to reading outside the requested window, and to open-coding a second, narrower dump for the remainder in what is a debug-only path. Extending the dump to cover the tail can be done on top if anyone misses those registers. Only reachable with the debug_tx_timeout module parameter, which defaults to false. It has not been observed at runtime: forcing a genuine TX timeout on the reference machine is not something I can do safely, so this rests on the arithmetic above and on a build test, not on a reproduction. UBSAN does not catch it either, since these are MMIO reads rather than an array access. It was found by reading the function while fixing the saved_config_space off-by-one in nv_suspend() and nv_resume(). The dump was introduced with a fixed 0x400 bound while ioremap() mapped only NV_PCI_REGSZ (0x270), so it read about 0x190 bytes too far from the start. Commit 86a0f04387bf ("[PATCH] forcedeth: fix initialization") later replaced 0x400 with np->register_size, which shrank the overrun to the remainder but did not remove it. Fixes: c2dba06dae7d ("[PATCH] forcedeth: rewritten tx irq handling") Signed-off-by: Marek Czernohous Reviewed-by: Simon Horman Reviewed-by: Zhu Yanjun Link: https://patch.msgid.link/178682367886.3748309.6978554332066826294@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/nvidia/forcedeth.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/nvidia/forcedeth.c b/drivers/net/ethernet/nvidia/forcedeth.c index dc804e111564..f0218a0eab5c 100644 --- a/drivers/net/ethernet/nvidia/forcedeth.c +++ b/drivers/net/ethernet/nvidia/forcedeth.c @@ -2740,7 +2740,7 @@ static void nv_tx_timeout(struct net_device *dev, unsigned int txqueue) netdev_info(dev, "Ring at %lx\n", (unsigned long)np->ring_addr); netdev_info(dev, "Dumping tx registers\n"); - for (i = 0; i <= np->register_size; i += 32) { + for (i = 0; i + 32 <= np->register_size; i += 32) { netdev_info(dev, "%3x: %08x %08x %08x %08x " "%08x %08x %08x %08x\n", From 6b9eaa61ff2df63c6eb35d5cd025e2cef0861d76 Mon Sep 17 00:00:00 2001 From: Ruoyu Wang Date: Sat, 15 Aug 2026 23:17:37 +0800 Subject: [PATCH 0723/1328] net: ipa: balance runtime PM reference on remove error ipa_remove() takes a runtime PM reference before accessing IPA hardware during teardown. If a concurrent modem start or stop keeps ipa_modem_stop() busy across both attempts, the callback intentionally returns without releasing the remaining resources because proceeding with teardown could crash. That return also skips the matching pm_runtime_put_noidle(), leaving the callback's usage-count reference held. Drop only this runtime PM reference before returning. pm_runtime_put_noidle() does not request an idle transition, so the hardware and resources retained on this exceptional path remain untouched while the usage count stays balanced. This issue was found by a static analysis checker and confirmed by manual source review. Fixes: 923a6b698447 ("net: ipa: get clock in ipa_probe()") Signed-off-by: Ruoyu Wang Reviewed-by: Alex Elder Link: https://patch.msgid.link/20260815151737.3758320-1-ruoyuw560@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ipa/ipa_main.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/net/ipa/ipa_main.c b/drivers/net/ipa/ipa_main.c index 3cd9e44680e9..14ac2d2faf7c 100644 --- a/drivers/net/ipa/ipa_main.c +++ b/drivers/net/ipa/ipa_main.c @@ -972,12 +972,12 @@ static void ipa_remove(struct platform_device *pdev) } if (ret) { /* - * Not cleaning up here properly might also yield a - * crash later on. As the device is still unregistered - * in this case, this might even yield a crash later on. + * Continuing teardown after failing to stop the modem + * could crash, so leave the remaining resources allocated. */ dev_err(dev, "Failed to stop modem (%pe), leaking resources\n", ERR_PTR(ret)); + pm_runtime_put_noidle(dev); return; } From 408da1df18116c971c3392e21e50586688cd3fbf Mon Sep 17 00:00:00 2001 From: Aldo Ariel Panzardo Date: Wed, 12 Aug 2026 23:21:02 -0300 Subject: [PATCH 0724/1328] net: mctp: hold a reference to the route device in mctp_route_lookup() mctp_route_lookup() uses rt->dev without holding a reference on it. mctp_route_lookup_single() returns the route under RCU only, so the route's device can be torn down concurrently: mctp_dev_put() drops the last reference and synchronously kfree()s mdev->addrs. mctp_dev_saddr() then reads rt->dev->addrs[0], giving a use-after-free reachable by an unprivileged local AF_MCTP user on the receive/forwarding path (no CAP_NET_RAW required): BUG: KASAN: slab-use-after-free in mctp_route_lookup Read of size 1 at addr ... by task mctp_uaf/... mctp_route_lookup mctp_pkttype_receive Freed by task ...: kfree mctp_dev_put mctp_dev_notify In the same window mctp_dst_from_route() -> mctp_dev_hold() also increments a refcount that has already reached zero ("refcount_t: addition on 0 ... mctp_dev_hold"). This reintroduces the use-after-free class of CVE-2023-3439: the source address lookup was moved ahead of the point where the destination takes its device reference. Take a reference with refcount_inc_not_zero() before touching rt->dev, skip a device that is already dead, and drop the reference once the destination has taken its own. Fixes: 22cb45afd221 ("net: mctp: perform source address lookups when we populate our dst") Cc: stable@vger.kernel.org Signed-off-by: Aldo Ariel Panzardo Link: https://patch.msgid.link/20260813022102.2792032-1-qwe.aldo@gmail.com Signed-off-by: Jakub Kicinski --- net/mctp/route.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/net/mctp/route.c b/net/mctp/route.c index 1f3dccbb7aed..b19c63a5691a 100644 --- a/net/mctp/route.c +++ b/net/mctp/route.c @@ -998,14 +998,29 @@ int mctp_route_lookup(struct net *net, unsigned int dnet, mtu = mtu ?: rt->mtu; if (rt->dst_type == MCTP_ROUTE_DIRECT) { - mctp_eid_t saddr = mctp_dev_saddr(rt->dev); + mctp_eid_t saddr; + + /* rt->dev may be going away concurrently: its last + * reference is dropped in mctp_dev_put(), which frees + * mdev->addrs that mctp_dev_saddr() reads, and + * mctp_dst_from_route() takes a reference on it. Pin + * it before use, and skip a device that is already + * dead rather than resurrecting it. + */ + if (!refcount_inc_not_zero(&rt->dev->refs)) + break; + + saddr = mctp_dev_saddr(rt->dev); /* cannot do gateway-ed routes without a src */ - if (saddr == MCTP_ADDR_NULL && depth != 0) + if (saddr == MCTP_ADDR_NULL && depth != 0) { + mctp_dev_put(rt->dev); break; + } if (dst) mctp_dst_from_route(dst, daddr, saddr, mtu, rt); + mctp_dev_put(rt->dev); rc = 0; break; From ced45be0073a8a31b30b4a7f68cd3a15734515de Mon Sep 17 00:00:00 2001 From: Anirudh Prasad Date: Sat, 15 Aug 2026 01:36:23 +0530 Subject: [PATCH 0725/1328] ACPI: pfr_update: fix stack buffer overflow in query_capability() query_capability() copies four ACPI buffer objects returned by the firmware _DSM into fixed-size u8[16] fields in struct pfru_update_cap_info using memcpy with the firmware-supplied length: memcpy(&cap_hdr->code_type, elements[CAP_CODE_TYPE_IDX].buffer.pointer, elements[CAP_CODE_TYPE_IDX].buffer.length); The same pattern repeats for drv_type, platform_id, and oem_id. If the firmware returns buffer.length > 16 for any of these fields, memcpy writes past the destination array. struct pfru_update_cap_info is stack-allocated in pfru_ioctl(). Confirmed with KASAN on 7.2-rc6: three stack-out-of-bounds reports are generated when a DSM returns 64-byte buffers, with writes reaching 44 bytes past the end of cap_hdr's [64, 156) frame window into adjacent stack redzones. Introduce a helper pointer to out_obj->package.elements and use it to validate each buffer length against its destination field size before copying, returning -EINVAL if the firmware supplies an oversized buffer. Fixes: 0db89fa243e5 ("ACPI: Introduce Platform Firmware Runtime Update device driver") Cc: All applicable Signed-off-by: Anirudh Prasad Link: https://patch.msgid.link/1a001e1fee9.637da6dc3533246.238498880682901704@a0rg.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/pfr_update.c | 45 +++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/drivers/acpi/pfr_update.c b/drivers/acpi/pfr_update.c index 6283105bb0e8..9afd2c52fdbd 100644 --- a/drivers/acpi/pfr_update.c +++ b/drivers/acpi/pfr_update.c @@ -120,7 +120,7 @@ static int query_capability(struct pfru_update_cap_info *cap_hdr, struct pfru_device *pfru_dev) { acpi_handle handle = ACPI_HANDLE(pfru_dev->parent_dev); - union acpi_object *out_obj; + union acpi_object *out_obj, *elem; int ret = -EINVAL; out_obj = acpi_evaluate_dsm_typed(handle, &pfru_guid, @@ -150,7 +150,9 @@ static int query_capability(struct pfru_update_cap_info *cap_hdr, goto free_acpi_buffer; } - cap_hdr->status = out_obj->package.elements[CAP_STATUS_IDX].integer.value; + elem = out_obj->package.elements; + + cap_hdr->status = elem[CAP_STATUS_IDX].integer.value; if (cap_hdr->status != DSM_SUCCEED) { ret = -EBUSY; dev_dbg(pfru_dev->parent_dev, "Query cap Error Status:%d\n", @@ -158,29 +160,30 @@ static int query_capability(struct pfru_update_cap_info *cap_hdr, goto free_acpi_buffer; } - cap_hdr->update_cap = out_obj->package.elements[CAP_UPDATE_IDX].integer.value; + if (elem[CAP_CODE_TYPE_IDX].buffer.length > sizeof(cap_hdr->code_type) || + elem[CAP_DRV_TYPE_IDX].buffer.length > sizeof(cap_hdr->drv_type) || + elem[CAP_PLAT_ID_IDX].buffer.length > sizeof(cap_hdr->platform_id) || + elem[CAP_OEM_ID_IDX].buffer.length > sizeof(cap_hdr->oem_id)) + goto free_acpi_buffer; + + cap_hdr->update_cap = elem[CAP_UPDATE_IDX].integer.value; memcpy(&cap_hdr->code_type, - out_obj->package.elements[CAP_CODE_TYPE_IDX].buffer.pointer, - out_obj->package.elements[CAP_CODE_TYPE_IDX].buffer.length); - cap_hdr->fw_version = - out_obj->package.elements[CAP_FW_VER_IDX].integer.value; - cap_hdr->code_rt_version = - out_obj->package.elements[CAP_CODE_RT_VER_IDX].integer.value; + elem[CAP_CODE_TYPE_IDX].buffer.pointer, + elem[CAP_CODE_TYPE_IDX].buffer.length); + cap_hdr->fw_version = elem[CAP_FW_VER_IDX].integer.value; + cap_hdr->code_rt_version = elem[CAP_CODE_RT_VER_IDX].integer.value; memcpy(&cap_hdr->drv_type, - out_obj->package.elements[CAP_DRV_TYPE_IDX].buffer.pointer, - out_obj->package.elements[CAP_DRV_TYPE_IDX].buffer.length); - cap_hdr->drv_rt_version = - out_obj->package.elements[CAP_DRV_RT_VER_IDX].integer.value; - cap_hdr->drv_svn = - out_obj->package.elements[CAP_DRV_SVN_IDX].integer.value; + elem[CAP_DRV_TYPE_IDX].buffer.pointer, + elem[CAP_DRV_TYPE_IDX].buffer.length); + cap_hdr->drv_rt_version = elem[CAP_DRV_RT_VER_IDX].integer.value; + cap_hdr->drv_svn = elem[CAP_DRV_SVN_IDX].integer.value; memcpy(&cap_hdr->platform_id, - out_obj->package.elements[CAP_PLAT_ID_IDX].buffer.pointer, - out_obj->package.elements[CAP_PLAT_ID_IDX].buffer.length); + elem[CAP_PLAT_ID_IDX].buffer.pointer, + elem[CAP_PLAT_ID_IDX].buffer.length); memcpy(&cap_hdr->oem_id, - out_obj->package.elements[CAP_OEM_ID_IDX].buffer.pointer, - out_obj->package.elements[CAP_OEM_ID_IDX].buffer.length); - cap_hdr->oem_info_len = - out_obj->package.elements[CAP_OEM_INFO_IDX].buffer.length; + elem[CAP_OEM_ID_IDX].buffer.pointer, + elem[CAP_OEM_ID_IDX].buffer.length); + cap_hdr->oem_info_len = elem[CAP_OEM_INFO_IDX].buffer.length; ret = 0; From 07e98a4d5e9c292eae97c9cc5ab0937384e48492 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Mon, 17 Aug 2026 08:25:11 +0000 Subject: [PATCH 0726/1328] netdevsim: update queue NAPI association on queue reset In netdevsim, receive queues (struct nsim_rq) embed their own struct napi_struct. When queue reset is performed (e.g. via queue_reset debugfs), nsim_queue_start() swaps in a newly allocated struct nsim_rq, and nsim_queue_mem_free() later deletes and frees the old one. However, nsim_queue_start() failed to update the queue-to-NAPI mapping via netif_queue_set_napi(). As a result, dev->_rx[idx].napi continued to point to the old NAPI struct. After the old queue was freed, a subsequent queue dump via Netlink (NETDEV_CMD_QUEUE_GET) triggered a KASAN slab-use-after-free read in nla_put_napi_id() when accessing rxq->napi->napi_id. Fix this by calling netif_queue_set_napi() in nsim_queue_start() to associate the new NAPI with the RX queue, and clear the association with netif_queue_set_napi(..., NULL) in nsim_del_napi() during teardown. Fixes: 5bc8e8dbef27 ("netdevsim: add queue management API support") Reported-by: syzbot+483a6efbc4882c1201ee@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/6a82c3d4.f7a79266.2f965f.0024.GAE@google.com/T/#u Signed-off-by: Eric Dumazet Reviewed-by: Vadim Fedorenko Link: https://patch.msgid.link/20260817082511.2300402-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- drivers/net/netdevsim/netdev.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/netdevsim/netdev.c b/drivers/net/netdevsim/netdev.c index 4e9d7e10b527..b4a99f3ceac6 100644 --- a/drivers/net/netdevsim/netdev.c +++ b/drivers/net/netdevsim/netdev.c @@ -529,6 +529,7 @@ static void nsim_del_napi(struct netdevsim *ns) for (i = 0; i < dev->num_rx_queues; i++) { struct nsim_rq *rq = ns->rq[i]; + netif_queue_set_napi(dev, i, NETDEV_QUEUE_TYPE_RX, NULL); napi_disable_locked(&rq->napi); __netif_napi_del_locked(&rq->napi); } @@ -826,6 +827,7 @@ nsim_queue_start(struct net_device *dev, struct netdev_queue_config *qcfg, } ns->rq[idx] = qmem->rq; + netif_queue_set_napi(dev, idx, NETDEV_QUEUE_TYPE_RX, &ns->rq[idx]->napi); napi_enable_locked(&ns->rq[idx]->napi); return 0; From d2c26c2911dd1a363c488add4fb63eb5f0f28f87 Mon Sep 17 00:00:00 2001 From: "Cen Zhang (Microsoft)" Date: Sun, 16 Aug 2026 21:32:37 -0400 Subject: [PATCH 0727/1328] ipv6: avoid divide by zero in rt6_multipath_rebalance rt6_multipath_rebalance() calculates the total eligible nexthop weight in one pass and programs upper bounds in a second pass. Since RTM_NEWROUTE is RTNL-free, a concurrent ignore_routes_with_linkdown update can make the first pass return zero while the second sees an eligible nexthop, causing rt6_upper_bound_set() to divide by zero. UBSAN: division-overflow in net/ipv6/route.c:4845:17 Oops: divide error: 0000 [#1] SMP KASAN NOPTI rt6_upper_bound_set() net/ipv6/route.c:4845 rt6_multipath_rebalance() fib6_add_rt2node() ip6_route_multipath_add() inet6_rtm_newroute() Skip upper-bound calculation when the first pass reports a zero total. This respects the lock-free performance considerations here and solves insecure scenarios. Fixes: bd11ff421d36 ("ipv6: Get rid of RTNL for SIOCDELRT and RTM_DELROUTE.") Reported-by: AutonomousCodeSecurity@microsoft.com Reported-by: Xiang Mei (Microsoft) Reported-by: Cen Zhang (Microsoft) Signed-off-by: Cen Zhang (Microsoft) Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260817013237.2797-1-blbllhy@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv6/route.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/ipv6/route.c b/net/ipv6/route.c index 93b4168044d9..6a40c5074543 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -4852,7 +4852,7 @@ static void rt6_upper_bound_set(struct fib6_info *rt, int *weight, int total) { int upper_bound = -1; - if (!rt6_is_dead(rt)) { + if (total && !rt6_is_dead(rt)) { *weight += rt->fib6_nh->fib_nh_weight; upper_bound = DIV_ROUND_CLOSEST_ULL((u64) (*weight) << 31, total) - 1; From d2796ffe38cb4155afe0eab23636295b096c27a5 Mon Sep 17 00:00:00 2001 From: Jiangshan Yi Date: Sat, 15 Aug 2026 20:21:49 +0800 Subject: [PATCH 0728/1328] bnx2x: fix double free in bnx2x_init_firmware() error path bnx2x_init_firmware() frees bp->init_ops, bp->init_data and bp->init_ops_offsets in its error path without setting them to NULL. The cleanup function bnx2x_release_firmware() frees the same three pointers unconditionally, so if init_firmware fails and release_firmware is later called (e.g. from __bnx2x_remove or through the function state machine), all three are freed a second time. Set each pointer to NULL after kfree() in the error path so that the subsequent kfree(NULL) in bnx2x_release_firmware() is a safe no-op. Fixes: 94a78b79cb5f ("bnx2x: Separated FW from the source.") Cc: stable@vger.kernel.org Signed-off-by: Jiangshan Yi Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260815122149.951215-1-yijiangshan@kylinos.cn Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnx2x/bnx2x_main.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/ethernet/broadcom/bnx2x/bnx2x_main.c b/drivers/net/ethernet/broadcom/bnx2x/bnx2x_main.c index 208a894d6190..39eb6ab5f805 100644 --- a/drivers/net/ethernet/broadcom/bnx2x/bnx2x_main.c +++ b/drivers/net/ethernet/broadcom/bnx2x/bnx2x_main.c @@ -13473,10 +13473,13 @@ static int bnx2x_init_firmware(struct bnx2x *bp) iro_alloc_err: kfree(bp->init_ops_offsets); + bp->init_ops_offsets = NULL; init_offsets_alloc_err: kfree(bp->init_ops); + bp->init_ops = NULL; init_ops_alloc_err: kfree(bp->init_data); + bp->init_data = NULL; request_firmware_exit: release_firmware(bp->firmware); bp->firmware = NULL; From 45ccfec7caab044e0954fae6a296710d21aa466f Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 19 Aug 2026 22:12:34 +0200 Subject: [PATCH 0729/1328] ACPI: Add Bob Moore to CREDITS To me, Bob is a silent hero. He had been driving the development and maintenance of the ACPI Component Architecture (ACPICA) project for over 2 decades and while he was not vocal or otherwise visible too much, he was focused on improving the code delivered by him to a community reaching far beyond the Linux kernel. Bob retired from Intel earlier this year after over 40 years of continuous service and departed from software development as far as I know, and he is missed already. The kernel depends on Bob's contributions quite a bit, so he deserves a CREDITS entry. Signed-off-by: Rafael J. Wysocki Reviewed-by: Armin Wolf Link: https://patch.msgid.link/3711645.iIbC2pHGDl@rafael.j.wysocki --- CREDITS | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CREDITS b/CREDITS index 91c51c14e993..f4579ee78638 100644 --- a/CREDITS +++ b/CREDITS @@ -2906,6 +2906,10 @@ N: Thomas Molina E: tmolina@cablespeed.com D: bug fixes, documentation, minor hackery +N: Bob Moore +E: robert.moore@intel.com +D: ACPI Component Architecture (ACPICA) + N: Paul Moore E: paul@paul-moore.com W: https://www.paul-moore.com From 500919d75d5bbc0b45cc486615181ee502a49c31 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 19 Aug 2026 22:13:13 +0200 Subject: [PATCH 0730/1328] ACPI: Update MAINTAINERS entry for ACPICA Update the MAINTAINERS entry for ACPICA after recent changes in the upstream ACPICA project. Signed-off-by: Rafael J. Wysocki Reviewed-by: Armin Wolf Link: https://patch.msgid.link/3356863.5fSG56mABF@rafael.j.wysocki --- MAINTAINERS | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index b7094a616afd..67d0dd2dff26 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -314,15 +314,15 @@ F: drivers/firmware/efi/cper* ACPI COMPONENT ARCHITECTURE (ACPICA) M: "Rafael J. Wysocki" -M: Saket Dumbre +M: Maciej Wieczor-Retman +M: Pawel Chmielewski L: linux-acpi@vger.kernel.org L: acpica-devel@lists.linux.dev S: Supported W: https://acpica.org/ -W: https://github.com/acpica/acpica/ +W: https://github.com/open-acpica/acpica/ Q: https://patchwork.kernel.org/project/linux-acpi/list/ B: https://bugzilla.kernel.org -B: https://bugs.acpica.org T: git git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm F: drivers/acpi/acpica/ F: include/acpi/ From a6fa4d6e38d6d57986957de990d1feab663ea3c7 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 19 Aug 2026 22:14:10 +0200 Subject: [PATCH 0731/1328] ACPI: Update upstream ACPICA repository URL in documentation Update the URL of the upstream ACPICA repository after recent changes in the upstream ACPICA project. Signed-off-by: Rafael J. Wysocki Reviewed-by: Armin Wolf Link: https://patch.msgid.link/1867400.VLH7GnMWUR@rafael.j.wysocki --- Documentation/driver-api/acpi/linuxized-acpica.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Documentation/driver-api/acpi/linuxized-acpica.rst b/Documentation/driver-api/acpi/linuxized-acpica.rst index cc234353d2c4..317a68ed3b66 100644 --- a/Documentation/driver-api/acpi/linuxized-acpica.rst +++ b/Documentation/driver-api/acpi/linuxized-acpica.rst @@ -117,7 +117,7 @@ ACPICA Release ============== The ACPICA project maintains its code base at the following repository URL: -https://github.com/acpica/acpica.git. As a rule, a release is made every +https://github.com/open-acpica/acpica.git. As a rule, a release is made every month. As the coding style adopted by the ACPICA project is not acceptable by @@ -173,7 +173,7 @@ illustrated in the following figure:: utility located in source/tools/acpisrc folder and a number of scripts located in generate/linux folder. B. acpica / master - "master" branch of the git repository at - . + . C. linux-pm / linux-next - "linux-next" branch of the git repository at . D. linux / master - "master" branch of the git repository at @@ -244,7 +244,7 @@ before they become available from the ACPICA release process. Then the gen-patch.sh command can help to cherry-pick an ACPICA commit from the ACPICA local repository:: - $ git clone https://github.com/acpica/acpica + $ git clone https://github.com/open-acpica/acpica $ cd acpica $ generate/linux/gen-patch.sh -u [commit ID] @@ -259,7 +259,7 @@ before they become available from the ACPICA release process. You can generate the ACPICA release series yourself and rebase your code on top of the generated ACPICA release patches:: - $ git clone https://github.com/acpica/acpica + $ git clone https://github.com/open-acpica/acpica $ cd acpica $ generate/linux/make-patches.sh -u [commit ID] @@ -273,7 +273,7 @@ before they become available from the ACPICA release process. If you have local copies of both Linux and upstream ACPICA, you can generate a diff file indicating the state of the current divergences:: - # git clone https://github.com/acpica/acpica + # git clone https://github.com/open-acpica/acpica # git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git # cd acpica # generate/linux/divergence.sh -s ../linux From f85dc137aabf357bf3d9fe4c9712121039d83798 Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Mon, 17 Aug 2026 23:08:15 +0900 Subject: [PATCH 0732/1328] net: add missing ref_tracker_dir_exit() to net_passive_dec() I found that trying to read /sys/kernel/debug/ref_tracker/* causes NULL pointer dereference crash when alloc_netdev_mqs() via unshare() returned NULL, for commit 9ba74e6c9e9d ("net: add networking namespace refcount tracker") added ref_tracker_dir_exit(&net->refcnt_tracker) to only __put_net() path whereas commit 65b584f53611 ("ref_tracker: automatically register a file in debugfs for a ref_tracker_dir") added ref_tracker_dir_debugfs() to ref_tracker_dir_init() path. Since preinit_net() calls ref_tracker_dir_init(&net->refcnt_tracker) and ref_tracker_dir_init(&net->notrefcnt_tracker), we need to make sure that both ref_tracker_dir_exit(&net->refcnt_tracker) and ref_tracker_dir_exit(&net->notrefcnt_tracker) are called before net_passive_dec() schedules for kmem_cache_free() via net_complete_free(). ref_tracker_dir_exit(&net->refcnt_tracker) is called via put_net() when ns_ref_put() returned true. But put_net() is not called when copy_net_ns() fails. Therefore, call ref_tracker_dir_exit() from net_passive_dec() if put_net() is not yet called. Link: https://sashiko.dev/#/patchset/b06ce35d-e7bc-47a5-8e0a-e82be7e4dd08%40I-love.SAKURA.ne.jp Fixes: 9ba74e6c9e9d ("net: add networking namespace refcount tracker") Reviewed-by: Eric Dumazet Signed-off-by: Tetsuo Handa Link: https://patch.msgid.link/64254d80-9248-466c-8108-95f43bd71117@I-love.SAKURA.ne.jp Signed-off-by: Jakub Kicinski --- net/core/net_namespace.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c index a91d2b58aadd..9166f467293e 100644 --- a/net/core/net_namespace.c +++ b/net/core/net_namespace.c @@ -536,8 +536,12 @@ void net_passive_dec(struct net *net) if (refcount_dec_and_test(&net->passive)) { kfree(rcu_access_pointer(net->gen)); +#ifdef CONFIG_REF_TRACKER /* There should not be any trackers left there. */ ref_tracker_dir_exit(&net->notrefcnt_tracker); + if (!net->refcnt_tracker.dead) + ref_tracker_dir_exit(&net->refcnt_tracker); +#endif /* Wait for an extra rcu_barrier() before final free. */ llist_add(&net->defer_free_list, &defer_free_list); From 5e8076e4e4124dae75a3e080ddc20404700d7585 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 16 Aug 2026 13:21:09 +0800 Subject: [PATCH 0733/1328] net: qlcnic: validate unified ROM sections before loading The unified ROM parser reads directory, product, and data-descriptor fields from the firmware file. Existing validation forms table and data ends with unchecked additions and multiplications. Malformed values can wrap before they are compared with the firmware size. The parser also dereferences typed pointers at firmware-controlled offsets. Valid descriptor extents alone are insufficient for the consumers. The loader reads a fixed-size bootloader regardless of its declared size, the version parser assumes a 17-byte tail, and a partial final firmware word is read as a full u64. A truncated image can therefore make the driver read beyond the firmware allocation during validation or loading. Replace the pointer-returning parser with bounded range helpers. Validate table entry sizes, descriptor indices, section ranges, the fixed bootloader load length, and the version tail before exposing any section. Read all file fields with unaligned little-endian accessors and assemble a partial final word from only the bytes that remain. Apply the same range checks to the legacy image before reading its fixed fields. Fixes: af19b49152bd ("qlcnic: Qlogic ethernet driver for CNA devices") Signed-off-by: Pengpeng Hou Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260816052109.4607-1-pengpeng@iscas.ac.cn Signed-off-by: Jakub Kicinski --- .../net/ethernet/qlogic/qlcnic/qlcnic_init.c | 467 ++++++++++-------- 1 file changed, 259 insertions(+), 208 deletions(-) diff --git a/drivers/net/ethernet/qlogic/qlcnic/qlcnic_init.c b/drivers/net/ethernet/qlogic/qlcnic/qlcnic_init.c index 9192c5ad5a16..c6bdf1b6ac9d 100644 --- a/drivers/net/ethernet/qlogic/qlcnic/qlcnic_init.c +++ b/drivers/net/ethernet/qlogic/qlcnic/qlcnic_init.c @@ -6,6 +6,7 @@ #include "qlcnic.h" #include "qlcnic_hw.h" +#include struct crb_addr_pair { u32 addr; @@ -740,159 +741,212 @@ qlcnic_has_mn(struct qlcnic_adapter *adapter) return 0; } -static -struct uni_table_desc *qlcnic_get_table_desc(const u8 *unirom, int section) +#define FILEHEADER_SIZE (14 * 4) +#define QLCNIC_UNI_DIR_TYPE_OFF (8 * sizeof(__le32)) +#define QLCNIC_UNI_DIR_ENTRY_MIN_SIZE (9 * sizeof(__le32)) +#define QLCNIC_UNI_PRODUCT_ENTRY_MIN_SIZE \ + ((QLCNIC_UNI_FIRMWARE_IDX_OFF + 1) * sizeof(__le32)) +#define QLCNIC_UNI_VERSION_TAIL_SIZE 17 +#define QLCNIC_UNI_BOOTLD_SIZE \ + (QLCNIC_IMAGE_START - QLCNIC_BOOTLD_START) + +struct qlcnic_uni_data { + u32 offset; + u32 size; +}; + +static bool qlcnic_rom_range_valid(size_t size, size_t offset, size_t len) { - u32 i, entries; - struct uni_table_desc *directory = (struct uni_table_desc *) &unirom[0]; - entries = le32_to_cpu(directory->num_entries); - - for (i = 0; i < entries; i++) { - - u32 offs = le32_to_cpu(directory->findex) + - i * le32_to_cpu(directory->entry_size); - u32 tab_type = le32_to_cpu(*((__le32 *)&unirom[offs] + 8)); - - if (tab_type == section) - return (struct uni_table_desc *) &unirom[offs]; - } - - return NULL; + return offset <= size && len <= size - offset; } -#define FILEHEADER_SIZE (14 * 4) +static bool qlcnic_rom_table_valid(size_t size, u32 offset, u32 entries, + u32 entry_size, u32 min_entry_size) +{ + if (entry_size < min_entry_size || offset > size) + return false; + + return entries <= (size - offset) / entry_size; +} + +static int qlcnic_get_directory(struct qlcnic_adapter *adapter, + size_t *offset, u32 *entries, u32 *entry_size) +{ + const struct firmware *fw = adapter->fw; + const u8 *directory = fw->data; + + if (fw->size < FILEHEADER_SIZE) + return -EINVAL; + + *offset = get_unaligned_le32(directory + + offsetof(struct uni_table_desc, findex)); + *entries = get_unaligned_le32(directory + + offsetof(struct uni_table_desc, num_entries)); + *entry_size = get_unaligned_le32(directory + + offsetof(struct uni_table_desc, entry_size)); + + if (!qlcnic_rom_table_valid(fw->size, *offset, *entries, *entry_size, + QLCNIC_UNI_DIR_ENTRY_MIN_SIZE)) + return -EINVAL; + + return 0; +} + +static int qlcnic_get_table_desc(struct qlcnic_adapter *adapter, int section, + size_t *desc_offset) +{ + const u8 *unirom = adapter->fw->data; + size_t directory_offset; + u32 entries, entry_size; + size_t i; + int ret; + + ret = qlcnic_get_directory(adapter, &directory_offset, &entries, + &entry_size); + if (ret) + return ret; + + for (i = 0; i < entries; i++) { + size_t offset = directory_offset + i * entry_size; + u32 table_type; + + table_type = get_unaligned_le32(unirom + offset + + QLCNIC_UNI_DIR_TYPE_OFF); + if (table_type == section) { + *desc_offset = offset; + return 0; + } + } + + return -ENOENT; +} static int qlcnic_validate_header(struct qlcnic_adapter *adapter) { - const u8 *unirom = adapter->fw->data; - struct uni_table_desc *directory = (struct uni_table_desc *) &unirom[0]; - u32 entries, entry_size, tab_size, fw_file_size; + u32 entries, entry_size; + size_t offset; - fw_file_size = adapter->fw->size; + return qlcnic_get_directory(adapter, &offset, &entries, &entry_size); +} - if (fw_file_size < FILEHEADER_SIZE) +static int qlcnic_get_data_desc(struct qlcnic_adapter *adapter, u32 section, + u32 index_offset, struct qlcnic_uni_data *data) +{ + size_t table_desc_offset, table_offset, desc_offset; + const struct firmware *fw = adapter->fw; + const u8 *unirom = fw->data; + size_t product_index_offset; + u32 entries, entry_size, idx; + int ret; + + product_index_offset = adapter->file_prd_off + + (size_t)index_offset * sizeof(__le32); + if (!qlcnic_rom_range_valid(fw->size, product_index_offset, + sizeof(__le32))) return -EINVAL; - entries = le32_to_cpu(directory->num_entries); - entry_size = le32_to_cpu(directory->entry_size); - tab_size = le32_to_cpu(directory->findex) + (entries * entry_size); + idx = get_unaligned_le32(unirom + product_index_offset); + ret = qlcnic_get_table_desc(adapter, section, &table_desc_offset); + if (ret) + return ret; - if (fw_file_size < tab_size) + table_offset = get_unaligned_le32(unirom + table_desc_offset + + offsetof(struct uni_table_desc, findex)); + entries = get_unaligned_le32(unirom + table_desc_offset + + offsetof(struct uni_table_desc, num_entries)); + entry_size = get_unaligned_le32(unirom + table_desc_offset + + offsetof(struct uni_table_desc, entry_size)); + if (!qlcnic_rom_table_valid(fw->size, table_offset, entries, + entry_size, sizeof(struct uni_data_desc)) || + idx >= entries) + return -EINVAL; + + desc_offset = table_offset + (size_t)idx * entry_size; + data->offset = get_unaligned_le32(unirom + desc_offset + + offsetof(struct uni_data_desc, findex)); + data->size = get_unaligned_le32(unirom + desc_offset + + offsetof(struct uni_data_desc, size)); + + if (!qlcnic_rom_range_valid(fw->size, data->offset, data->size)) return -EINVAL; return 0; } -static int -qlcnic_validate_bootld(struct qlcnic_adapter *adapter) +static int qlcnic_validate_bootld(struct qlcnic_adapter *adapter) { - struct uni_table_desc *tab_desc; - struct uni_data_desc *descr; - u32 offs, tab_size, data_size, idx; - const u8 *unirom = adapter->fw->data; - __le32 temp; + struct qlcnic_uni_data data; + int ret; - temp = *((__le32 *)&unirom[adapter->file_prd_off] + - QLCNIC_UNI_BOOTLD_IDX_OFF); - idx = le32_to_cpu(temp); - tab_desc = qlcnic_get_table_desc(unirom, QLCNIC_UNI_DIR_SECT_BOOTLD); + ret = qlcnic_get_data_desc(adapter, QLCNIC_UNI_DIR_SECT_BOOTLD, + QLCNIC_UNI_BOOTLD_IDX_OFF, &data); + if (ret) + return ret; - if (!tab_desc) - return -EINVAL; - - tab_size = le32_to_cpu(tab_desc->findex) + - le32_to_cpu(tab_desc->entry_size) * (idx + 1); - - if (adapter->fw->size < tab_size) - return -EINVAL; - - offs = le32_to_cpu(tab_desc->findex) + - le32_to_cpu(tab_desc->entry_size) * idx; - descr = (struct uni_data_desc *)&unirom[offs]; - - data_size = le32_to_cpu(descr->findex) + le32_to_cpu(descr->size); - - if (adapter->fw->size < data_size) - return -EINVAL; - - return 0; + return data.size < QLCNIC_UNI_BOOTLD_SIZE ? -EINVAL : 0; } -static int -qlcnic_validate_fw(struct qlcnic_adapter *adapter) +static int qlcnic_validate_fw(struct qlcnic_adapter *adapter) { - struct uni_table_desc *tab_desc; - struct uni_data_desc *descr; - const u8 *unirom = adapter->fw->data; - u32 offs, tab_size, data_size, idx; - __le32 temp; + struct qlcnic_uni_data data; + int ret; - temp = *((__le32 *)&unirom[adapter->file_prd_off] + - QLCNIC_UNI_FIRMWARE_IDX_OFF); - idx = le32_to_cpu(temp); - tab_desc = qlcnic_get_table_desc(unirom, QLCNIC_UNI_DIR_SECT_FW); + ret = qlcnic_get_data_desc(adapter, QLCNIC_UNI_DIR_SECT_FW, + QLCNIC_UNI_FIRMWARE_IDX_OFF, &data); + if (ret) + return ret; - if (!tab_desc) - return -EINVAL; - - tab_size = le32_to_cpu(tab_desc->findex) + - le32_to_cpu(tab_desc->entry_size) * (idx + 1); - - if (adapter->fw->size < tab_size) - return -EINVAL; - - offs = le32_to_cpu(tab_desc->findex) + - le32_to_cpu(tab_desc->entry_size) * idx; - descr = (struct uni_data_desc *)&unirom[offs]; - data_size = le32_to_cpu(descr->findex) + le32_to_cpu(descr->size); - - if (adapter->fw->size < data_size) - return -EINVAL; - - return 0; + return data.size < QLCNIC_UNI_VERSION_TAIL_SIZE ? -EINVAL : 0; } static int qlcnic_validate_product_offs(struct qlcnic_adapter *adapter) { - struct uni_table_desc *ptab_descr; + size_t table_desc_offset, table_offset; const u8 *unirom = adapter->fw->data; int mn_present = qlcnic_has_mn(adapter); - u32 entries, entry_size, tab_size, i; - __le32 temp; + u32 entries, entry_size; + size_t i; + int ret; - ptab_descr = qlcnic_get_table_desc(unirom, - QLCNIC_UNI_DIR_SECT_PRODUCT_TBL); - if (!ptab_descr) - return -EINVAL; + ret = qlcnic_get_table_desc(adapter, QLCNIC_UNI_DIR_SECT_PRODUCT_TBL, + &table_desc_offset); + if (ret) + return ret; - entries = le32_to_cpu(ptab_descr->num_entries); - entry_size = le32_to_cpu(ptab_descr->entry_size); - tab_size = le32_to_cpu(ptab_descr->findex) + (entries * entry_size); - - if (adapter->fw->size < tab_size) + table_offset = get_unaligned_le32(unirom + table_desc_offset + + offsetof(struct uni_table_desc, findex)); + entries = get_unaligned_le32(unirom + table_desc_offset + + offsetof(struct uni_table_desc, num_entries)); + entry_size = get_unaligned_le32(unirom + table_desc_offset + + offsetof(struct uni_table_desc, entry_size)); + if (!qlcnic_rom_table_valid(adapter->fw->size, table_offset, entries, + entry_size, + QLCNIC_UNI_PRODUCT_ENTRY_MIN_SIZE)) return -EINVAL; nomn: for (i = 0; i < entries; i++) { - - u32 flags, file_chiprev, offs; + size_t offset = table_offset + i * entry_size; u8 chiprev = adapter->ahw->revision_id; + u32 flags, file_chiprev; u32 flagbit; - offs = le32_to_cpu(ptab_descr->findex) + - i * le32_to_cpu(ptab_descr->entry_size); - temp = *((__le32 *)&unirom[offs] + QLCNIC_UNI_FLAGS_OFF); - flags = le32_to_cpu(temp); - temp = *((__le32 *)&unirom[offs] + QLCNIC_UNI_CHIP_REV_OFF); - file_chiprev = le32_to_cpu(temp); + flags = get_unaligned_le32(unirom + offset + + QLCNIC_UNI_FLAGS_OFF * sizeof(__le32)); + file_chiprev = get_unaligned_le32(unirom + offset + + QLCNIC_UNI_CHIP_REV_OFF * + sizeof(__le32)); flagbit = mn_present ? 1 : 2; if ((chiprev == file_chiprev) && ((1ULL << flagbit) & flags)) { - adapter->file_prd_off = offs; + if (offset > U32_MAX) + return -EINVAL; + + adapter->file_prd_off = offset; return 0; } } @@ -933,93 +987,81 @@ qlcnic_validate_unified_romimage(struct qlcnic_adapter *adapter) return 0; } -static -struct uni_data_desc *qlcnic_get_data_desc(struct qlcnic_adapter *adapter, - u32 section, u32 idx_offset) -{ - const u8 *unirom = adapter->fw->data; - struct uni_table_desc *tab_desc; - u32 offs, idx; - __le32 temp; - - temp = *((__le32 *)&unirom[adapter->file_prd_off] + idx_offset); - idx = le32_to_cpu(temp); - - tab_desc = qlcnic_get_table_desc(unirom, section); - - if (tab_desc == NULL) - return NULL; - - offs = le32_to_cpu(tab_desc->findex) + - le32_to_cpu(tab_desc->entry_size) * idx; - - return (struct uni_data_desc *)&unirom[offs]; -} - -static u8 * -qlcnic_get_bootld_offs(struct qlcnic_adapter *adapter) +static int qlcnic_get_bootld_data(struct qlcnic_adapter *adapter, + const u8 **bootld) { u32 offs = QLCNIC_BOOTLD_START; - struct uni_data_desc *data_desc; + struct qlcnic_uni_data data; + int ret; - data_desc = qlcnic_get_data_desc(adapter, QLCNIC_UNI_DIR_SECT_BOOTLD, - QLCNIC_UNI_BOOTLD_IDX_OFF); + if (adapter->ahw->fw_type == QLCNIC_UNIFIED_ROMIMAGE) { + ret = qlcnic_get_data_desc(adapter, QLCNIC_UNI_DIR_SECT_BOOTLD, + QLCNIC_UNI_BOOTLD_IDX_OFF, &data); + if (ret || data.size < QLCNIC_UNI_BOOTLD_SIZE) + return -EINVAL; + offs = data.offset; + } else if (!qlcnic_rom_range_valid(adapter->fw->size, offs, + QLCNIC_UNI_BOOTLD_SIZE)) { + return -EINVAL; + } - if (adapter->ahw->fw_type == QLCNIC_UNIFIED_ROMIMAGE) - offs = le32_to_cpu(data_desc->findex); - - return (u8 *)&adapter->fw->data[offs]; + *bootld = adapter->fw->data + offs; + return 0; } -static u8 * -qlcnic_get_fw_offs(struct qlcnic_adapter *adapter) +static int qlcnic_get_fw_data(struct qlcnic_adapter *adapter, + const u8 **image, u32 *image_size) { u32 offs = QLCNIC_IMAGE_START; - struct uni_data_desc *data_desc; + struct qlcnic_uni_data data; + int ret; - data_desc = qlcnic_get_data_desc(adapter, QLCNIC_UNI_DIR_SECT_FW, - QLCNIC_UNI_FIRMWARE_IDX_OFF); - if (adapter->ahw->fw_type == QLCNIC_UNIFIED_ROMIMAGE) - offs = le32_to_cpu(data_desc->findex); + if (adapter->ahw->fw_type == QLCNIC_UNIFIED_ROMIMAGE) { + ret = qlcnic_get_data_desc(adapter, QLCNIC_UNI_DIR_SECT_FW, + QLCNIC_UNI_FIRMWARE_IDX_OFF, &data); + if (ret) + return ret; + offs = data.offset; + *image_size = data.size; + } else { + if (!qlcnic_rom_range_valid(adapter->fw->size, + QLCNIC_FW_SIZE_OFFSET, + sizeof(__le32))) + return -EINVAL; + *image_size = get_unaligned_le32(adapter->fw->data + + QLCNIC_FW_SIZE_OFFSET); + } - return (u8 *)&adapter->fw->data[offs]; -} + if (!qlcnic_rom_range_valid(adapter->fw->size, offs, *image_size)) + return -EINVAL; -static u32 qlcnic_get_fw_size(struct qlcnic_adapter *adapter) -{ - struct uni_data_desc *data_desc; - const u8 *unirom = adapter->fw->data; - - data_desc = qlcnic_get_data_desc(adapter, QLCNIC_UNI_DIR_SECT_FW, - QLCNIC_UNI_FIRMWARE_IDX_OFF); - - if (adapter->ahw->fw_type == QLCNIC_UNIFIED_ROMIMAGE) - return le32_to_cpu(data_desc->size); - else - return le32_to_cpu(*(__le32 *)&unirom[QLCNIC_FW_SIZE_OFFSET]); + *image = adapter->fw->data + offs; + return 0; } static u32 qlcnic_get_fw_version(struct qlcnic_adapter *adapter) { - struct uni_data_desc *fw_data_desc; + char ver_str[QLCNIC_UNI_VERSION_TAIL_SIZE + 1]; const struct firmware *fw = adapter->fw; + struct qlcnic_uni_data data; u32 major, minor, sub; - __le32 version_offset; - const u8 *ver_str; int i, ret; - if (adapter->ahw->fw_type != QLCNIC_UNIFIED_ROMIMAGE) { - version_offset = *(__le32 *)&fw->data[QLCNIC_FW_VERSION_OFFSET]; - return le32_to_cpu(version_offset); - } + if (adapter->ahw->fw_type != QLCNIC_UNIFIED_ROMIMAGE) + return get_unaligned_le32(fw->data + QLCNIC_FW_VERSION_OFFSET); - fw_data_desc = qlcnic_get_data_desc(adapter, QLCNIC_UNI_DIR_SECT_FW, - QLCNIC_UNI_FIRMWARE_IDX_OFF); - ver_str = fw->data + le32_to_cpu(fw_data_desc->findex) + - le32_to_cpu(fw_data_desc->size) - 17; + ret = qlcnic_get_data_desc(adapter, QLCNIC_UNI_DIR_SECT_FW, + QLCNIC_UNI_FIRMWARE_IDX_OFF, &data); + if (ret || data.size < QLCNIC_UNI_VERSION_TAIL_SIZE) + return 0; + + memcpy(ver_str, fw->data + data.offset + data.size - + QLCNIC_UNI_VERSION_TAIL_SIZE, + QLCNIC_UNI_VERSION_TAIL_SIZE); + ver_str[QLCNIC_UNI_VERSION_TAIL_SIZE] = '\0'; for (i = 0; i < 12; i++) { - if (!strncmp(&ver_str[i], "REV=", 4)) { + if (!strncmp(ver_str + i, "REV=", 4)) { ret = sscanf(&ver_str[i+4], "%u.%u.%u ", &major, &minor, &sub); if (ret != 3) @@ -1034,18 +1076,15 @@ static u32 qlcnic_get_fw_version(struct qlcnic_adapter *adapter) static u32 qlcnic_get_bios_version(struct qlcnic_adapter *adapter) { - const struct firmware *fw = adapter->fw; u32 bios_ver, prd_off = adapter->file_prd_off; - u8 *version_offset; - __le32 temp; + const struct firmware *fw = adapter->fw; - if (adapter->ahw->fw_type != QLCNIC_UNIFIED_ROMIMAGE) { - version_offset = (u8 *)&fw->data[QLCNIC_BIOS_VERSION_OFFSET]; - return le32_to_cpu(*(__le32 *)version_offset); - } + if (adapter->ahw->fw_type != QLCNIC_UNIFIED_ROMIMAGE) + return get_unaligned_le32(fw->data + QLCNIC_BIOS_VERSION_OFFSET); - temp = *((__le32 *)(&fw->data[prd_off]) + QLCNIC_UNI_BIOS_VERSION_OFF); - bios_ver = le32_to_cpu(temp); + bios_ver = get_unaligned_le32(fw->data + prd_off + + QLCNIC_UNI_BIOS_VERSION_OFF * + sizeof(__le32)); return (bios_ver << 16) + ((bios_ver >> 8) & 0xff00) + (bios_ver >> 24); } @@ -1106,24 +1145,26 @@ static const char *fw_name[] = { int qlcnic_load_firmware(struct qlcnic_adapter *adapter) { - __le64 *ptr64; - u32 i, flashaddr, size; const struct firmware *fw = adapter->fw; struct pci_dev *pdev = adapter->pdev; + const u8 *bootld, *image; + u32 i, flashaddr, image_size; + int ret; dev_info(&pdev->dev, "loading firmware from %s\n", fw_name[adapter->ahw->fw_type]); if (fw) { + u32 words, remainder; u64 data; - size = (QLCNIC_IMAGE_START - QLCNIC_BOOTLD_START) / 8; - - ptr64 = (__le64 *)qlcnic_get_bootld_offs(adapter); + ret = qlcnic_get_bootld_data(adapter, &bootld); + if (ret) + return ret; flashaddr = QLCNIC_BOOTLD_START; - for (i = 0; i < size; i++) { - data = le64_to_cpu(ptr64[i]); + for (i = 0; i < QLCNIC_UNI_BOOTLD_SIZE / sizeof(u64); i++) { + data = get_unaligned_le64(bootld + i * sizeof(u64)); if (qlcnic_pci_mem_write_2M(adapter, flashaddr, data)) return -EIO; @@ -1131,13 +1172,15 @@ qlcnic_load_firmware(struct qlcnic_adapter *adapter) flashaddr += 8; } - size = qlcnic_get_fw_size(adapter) / 8; - - ptr64 = (__le64 *)qlcnic_get_fw_offs(adapter); + ret = qlcnic_get_fw_data(adapter, &image, &image_size); + if (ret) + return ret; + words = image_size / sizeof(u64); + remainder = image_size % sizeof(u64); flashaddr = QLCNIC_IMAGE_START; - for (i = 0; i < size; i++) { - data = le64_to_cpu(ptr64[i]); + for (i = 0; i < words; i++) { + data = get_unaligned_le64(image + i * sizeof(u64)); if (qlcnic_pci_mem_write_2M(adapter, flashaddr, data)) @@ -1146,9 +1189,11 @@ qlcnic_load_firmware(struct qlcnic_adapter *adapter) flashaddr += 8; } - size = qlcnic_get_fw_size(adapter) % 8; - if (size) { - data = le64_to_cpu(ptr64[i]); + if (remainder) { + __le64 tail = 0; + + memcpy(&tail, image + words * sizeof(u64), remainder); + data = le64_to_cpu(tail); if (qlcnic_pci_mem_write_2M(adapter, flashaddr, data)) @@ -1156,10 +1201,9 @@ qlcnic_load_firmware(struct qlcnic_adapter *adapter) } } else { - u64 data; - u32 hi, lo; - int ret; struct qlcnic_flt_entry bootld_entry; + u32 hi, lo, size; + u64 data; ret = qlcnic_get_flt_entry(adapter, QLCNIC_BOOTLD_REGION, &bootld_entry); @@ -1200,26 +1244,33 @@ qlcnic_load_firmware(struct qlcnic_adapter *adapter) static int qlcnic_validate_firmware(struct qlcnic_adapter *adapter) { - u32 val; - u32 ver, bios, min_size; - struct pci_dev *pdev = adapter->pdev; const struct firmware *fw = adapter->fw; + struct pci_dev *pdev = adapter->pdev; u8 fw_type = adapter->ahw->fw_type; + u32 ver, bios, min_size; + const u8 *data; + u32 data_size; + u32 val; + + if (fw_type == QLCNIC_UNIFIED_ROMIMAGE) + min_size = QLCNIC_UNI_FW_MIN_SIZE; + else + min_size = QLCNIC_FW_MIN_SIZE; + + if (fw->size < min_size) + return -EINVAL; if (fw_type == QLCNIC_UNIFIED_ROMIMAGE) { if (qlcnic_validate_unified_romimage(adapter)) return -EINVAL; - - min_size = QLCNIC_UNI_FW_MIN_SIZE; } else { - val = le32_to_cpu(*(__le32 *)&fw->data[QLCNIC_FW_MAGIC_OFFSET]); + val = get_unaligned_le32(fw->data + QLCNIC_FW_MAGIC_OFFSET); if (val != QLCNIC_BDINFO_MAGIC) return -EINVAL; - - min_size = QLCNIC_FW_MIN_SIZE; } - if (fw->size < min_size) + if (qlcnic_get_bootld_data(adapter, &data) || + qlcnic_get_fw_data(adapter, &data, &data_size)) return -EINVAL; val = qlcnic_get_fw_version(adapter); From b878dfdd12d7a5b8722a78d35e313506140ca3d9 Mon Sep 17 00:00:00 2001 From: Harshit Varu Date: Sat, 15 Aug 2026 17:22:05 +0530 Subject: [PATCH 0734/1328] mptcp: fix uninitialized local_id in syncookie MP_JOIN reconstruction mptcp_token_join_cookie_init_state() restores remote_nonce, local_nonce, backup, join_id, token and msk from the saved cookie entry when rebuilding the request socket for a MP_JOIN 4th-ACK handled under SYN cookies, but it does not restore local_id, even though the SYN path saved it. subflow_ulp_clone() then reads that uninitialized field and stores it as the joined subflow's address-ID. Because the request-sock slab is SLAB_TYPESAFE_BY_RCU and not zeroed on allocation, the value is the stale byte of a previously freed request socket, which an off-path peer can influence by sending concurrent MP_JOIN SYNs. This corrupts the path manager's id-based subflow bookkeeping for the connection. Restore subflow_req->local_id from the cookie entry, as done for the other fields. Fixes: 9466a1ccebbe ("mptcp: enable JOIN requests even if cookies are in use") Cc: stable@vger.kernel.org Signed-off-by: Harshit Varu Reviewed-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260815115205.197151-1-harshitvaru666@gmail.com Signed-off-by: Jakub Kicinski --- net/mptcp/syncookies.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/mptcp/syncookies.c b/net/mptcp/syncookies.c index 7f22526346a7..b5cac5701122 100644 --- a/net/mptcp/syncookies.c +++ b/net/mptcp/syncookies.c @@ -118,6 +118,7 @@ bool mptcp_token_join_cookie_init_state(struct mptcp_subflow_request_sock *subfl subflow_req->local_nonce = e->local_nonce; subflow_req->backup = e->backup; subflow_req->remote_id = e->join_id; + subflow_req->local_id = e->local_id; subflow_req->token = e->token; subflow_req->msk = msk; spin_unlock_bh(&join_entry_locks[i]); From 235b42b5860189eb8c27c36435ad932cae65a734 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Tue, 18 Aug 2026 17:27:55 +0000 Subject: [PATCH 0735/1328] ip6mr: do not clone dst in ip6mr_cache_report() IPv6 input attaches a non-refcounted (NOREF) dst to skbs under RCU. When an ingress multicast packet misses MFC lookup, ip6mr_cache_unresolved() places the skb onto the unresolved queue, escaping the receive-side RCU grace period. If the underlying route is deleted and freed, and the MFC queue is later resolved with a wrong parent interface, ip6_mr_forward() invokes ip6mr_cache_report(..., MRT6MSG_WRONGMIF), which executes dst_clone(skb_dst(pkt)) on the freed dst entry, triggering a slab use-after-free. Report packets queued to mroute6_sk (a raw socket) and netlink notifications do not require an attached dst entry. Fix this by: 1. Removing dst_clone() in ip6mr_cache_report() and ensuring report skbs do not hold a dst. 2. Dropping skb_dst before queuing unresolved skbs in ip6mr_cache_unresolved(), matching the fact that multicast forwarding resolves outgoing routes anew via ip6_route_output(). Fixes: 67f415dd2906 ("ipv6: convert rx data path to not take refcnt on dst") Reported-by: Zero Day Initiative Signed-off-by: Eric Dumazet Reviewed-by: Hangbin Liu Link: https://patch.msgid.link/20260818172755.4083692-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/ipv6/ip6mr.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/ipv6/ip6mr.c b/net/ipv6/ip6mr.c index 604a58838901..3f2ed9b77deb 100644 --- a/net/ipv6/ip6mr.c +++ b/net/ipv6/ip6mr.c @@ -1162,10 +1162,10 @@ static int ip6mr_cache_report(const struct mr_table *mrt, struct sk_buff *pkt, msg->im6_src = ipv6_hdr(pkt)->saddr; msg->im6_dst = ipv6_hdr(pkt)->daddr; - skb_dst_set(skb, dst_clone(skb_dst(pkt))); skb->ip_summed = CHECKSUM_UNNECESSARY; } + skb_dst_drop(skb); mrt6msg_netlink_event(mrt, skb); /* Deliver to user space multicast routing algorithms */ @@ -1246,6 +1246,7 @@ static int ip6mr_cache_unresolved(struct mr_table *mrt, mifi_t mifi, skb->skb_iif = dev->ifindex; } + skb_dst_drop(skb); skb_queue_tail(&c->_c.mfc_un.unres.unresolved, skb); spin_unlock_bh(&mfc_unres_lock); From 2ee66e9487172fcd189bc52a767c30dad7141c09 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Tue, 18 Aug 2026 15:12:13 +0000 Subject: [PATCH 0736/1328] inetpeer: randomize RB-tree node comparison using SipHash The inetpeer rate limiting system stores peer entries in a Red-Black tree keyed deterministically on the remote IP address. Because tree lookups walk the RB-tree using standard lexicographical comparisons (inetpeer_addr_cmp), an off-path adversary can predict the exact topology of the tree and the sequence of nodes traversed during lookups (the gc_stack candidate list). By combining deterministic tree traversal with aggressive garbage collection (triggered when tree size exceeds inet_peer_threshold), an attacker can selectively force the eviction of targeted inet_peer nodes. When an evicted node is subsequently re-created upon receiving a new packet, its rate-limiting token bucket (rate_tokens, rate_last) is reset to full capacity. This creates a side-channel primitive allowing off-path attackers to bypass IP-keyed ICMP rate limits and infer open UDP ports (similar to SAD DNS style attacks). Mitigate this by randomizing the RB-tree node comparison logic using SipHash with a secret key (inetpeer_hash_key) initialized via net_get_random_once(). Nodes are ordered in the tree by SipHash(addr, key) rather than raw IP addresses. Because the secret key is unknown to external entities, the tree layout and lookup traversal paths are unpredictable to off-path adversaries, breaking the deterministic eviction gadget. Cache the computed 64-bit SipHash (hash) in struct inet_peer and compute the target hash (dhash) once at the beginning of inet_getpeer() to avoid recomputing SipHash at every step of the RB-tree walk. Fixes: b145425f269a ("inetpeer: remove AVL implementation in favor of RB tree") Reported-by: Michael Blunt Suggested-by: Michael Blunt Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260818151213.3953963-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- include/net/inetpeer.h | 4 ++++ net/ipv4/inetpeer.c | 38 +++++++++++++++++++++++++++++++++++--- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/include/net/inetpeer.h b/include/net/inetpeer.h index f475757daafb..414e9adf4c51 100644 --- a/include/net/inetpeer.h +++ b/include/net/inetpeer.h @@ -35,6 +35,7 @@ struct inetpeer_addr { struct inet_peer { struct rb_node rb_node; + u64 hash; struct inetpeer_addr daddr; u32 metrics[RTAX_MAX]; @@ -125,6 +126,9 @@ static inline int inetpeer_addr_cmp(const struct inetpeer_addr *a, { int i, n; + if (a->family != b->family) + return a->family < b->family ? -1 : 1; + if (a->family == AF_INET) n = sizeof(a->a4) / sizeof(u32); else diff --git a/net/ipv4/inetpeer.c b/net/ipv4/inetpeer.c index 5b957a831e7c..adf6dc8a95b4 100644 --- a/net/ipv4/inetpeer.c +++ b/net/ipv4/inetpeer.c @@ -21,6 +21,7 @@ #include #include #include +#include /* * Theory of operations. @@ -52,6 +53,34 @@ */ static struct kmem_cache *peer_cachep __ro_after_init; +static siphash_aligned_key_t inetpeer_hash_key __read_mostly; + +static u64 inetpeer_addr_hash(const struct inetpeer_addr *a) +{ + net_get_random_once(&inetpeer_hash_key, sizeof(inetpeer_hash_key)); + + if (a->family == AF_INET) + return siphash_2u32((__force u32)a->a4.addr, a->a4.vif, + &inetpeer_hash_key); + + return siphash_4u32((__force u32)a->a6.s6_addr32[0], + (__force u32)a->a6.s6_addr32[1], + (__force u32)a->a6.s6_addr32[2], + (__force u32)a->a6.s6_addr32[3], + &inetpeer_hash_key); +} + +static int inetpeer_entry_cmp(u64 dhash, + const struct inetpeer_addr *daddr, + const struct inet_peer *p) +{ + if (dhash < p->hash) + return -1; + if (dhash > p->hash) + return 1; + + return inetpeer_addr_cmp(daddr, &p->daddr); +} void inet_peer_base_init(struct inet_peer_base *bp) { @@ -84,6 +113,7 @@ void __init inet_initpeers(void) /* Called with rcu_read_lock() or base->lock held */ static struct inet_peer *lookup(const struct inetpeer_addr *daddr, + u64 dhash, struct inet_peer_base *base, unsigned int seq, struct inet_peer *gc_stack[], @@ -105,7 +135,7 @@ static struct inet_peer *lookup(const struct inetpeer_addr *daddr, break; parent = next; p = rb_entry(parent, struct inet_peer, rb_node); - cmp = inetpeer_addr_cmp(daddr, &p->daddr); + cmp = inetpeer_entry_cmp(dhash, daddr, p); if (cmp == 0) { now = jiffies; if (READ_ONCE(p->dtime) != now) @@ -170,6 +200,7 @@ struct inet_peer *inet_getpeer(struct inet_peer_base *base, const struct inetpeer_addr *daddr) { struct inet_peer *p, *gc_stack[PEER_MAX_GC]; + u64 dhash = inetpeer_addr_hash(daddr); struct rb_node **pp, *parent; unsigned int gc_cnt, seq; @@ -177,7 +208,7 @@ struct inet_peer *inet_getpeer(struct inet_peer_base *base, * Because of a concurrent writer, we might not find an existing entry. */ seq = read_seqbegin(&base->lock); - p = lookup(daddr, base, seq, NULL, &gc_cnt, &parent, &pp); + p = lookup(daddr, dhash, base, seq, NULL, &gc_cnt, &parent, &pp); /* Make sure tree was not modified during our lookup. */ if (p && !read_seqretry(&base->lock, seq)) @@ -190,11 +221,12 @@ struct inet_peer *inet_getpeer(struct inet_peer_base *base, write_seqlock_bh(&base->lock); gc_cnt = 0; - p = lookup(daddr, base, seq, gc_stack, &gc_cnt, &parent, &pp); + p = lookup(daddr, dhash, base, seq, gc_stack, &gc_cnt, &parent, &pp); if (!p) { p = kmem_cache_alloc(peer_cachep, GFP_ATOMIC); if (p) { p->daddr = *daddr; + p->hash = dhash; p->dtime = (__u32)jiffies; refcount_set(&p->refcnt, 1); atomic_set(&p->rid, 0); From 44930446dde45a7a90fe1446fa38eb0e2c561646 Mon Sep 17 00:00:00 2001 From: Kyle Zeng Date: Mon, 17 Aug 2026 08:58:38 +0000 Subject: [PATCH 0737/1328] ipv6: seg6: clear IPv4 control block on IPIP decapsulation End.DX4 and End.DT4 decapsulate an IPv4 packet through decap_and_validate() and send it directly to IPv4 routing. The inner packet therefore bypasses ip_rcv_core(), which normally clears IPCB before IPv4 interprets skb->cb. The skb instead retains IP6CB data from the outer packet. IP6CB and IPCB use the same skb->cb storage, so IP6CB(skb)->lastopt overlaps IPCB(skb)->opt.optlen and srr, while IP6CB(skb)->nhoff overlaps rr and ts. The sender can make the stale optlen byte nonzero with a valid outer extension-header chain. The reproducers put an eight-byte Destination Options header immediately after the 40-byte IPv6 header and before the Segment Routing Header. ipv6_destopt_rcv() records the sender-controlled Destination Options offset in both lastopt and nhoff, setting them to 40. On the reproduced little-endian x86-64 kernel, IPv4 therefore sees optlen = 40 and rr = 40. Both tcp_v4_save_options() and __ip_options_echo() skip option copying when optlen is zero. Here optlen is 40, so the TCP SYN path allocates room for 40 bytes of option data and calls __ip_options_echo(). The stale rr value makes that function read inner packet byte 41 as the Record Route option length. The reproducers set that sender-controlled byte to 255, so __ip_options_echo() copies 255 bytes into the 40-byte option-data area. Separate End.DX4 and End.DT4 reproducers on the unpatched v7.2-rc5 kernel both produced: BUG: KASAN: slab-out-of-bounds in __ip_options_echo() Write of size 255 The relevant End.DX4 call path is: __ip_options_echo tcp_v4_route_req tcp_conn_request tcp_v4_conn_request tcp_rcv_state_process tcp_v4_do_rcv tcp_v4_rcv ip_protocol_deliver_rcu ip_local_deliver_finish ip_local_deliver input_action_end_dx4_finish input_action_end_dx4 The relevant End.DT4 call path is: __ip_options_echo tcp_v4_route_req tcp_conn_request tcp_v4_conn_request tcp_rcv_state_process tcp_v4_do_rcv tcp_v4_rcv ip_protocol_deliver_rcu ip_local_deliver_finish ip_local_deliver input_action_end_dt4 tcp_v4_save_options() is inlined into the tcp_v4_route_req() path, so it does not appear as a separate frame. When decap_and_validate() handles IPPROTO_IPIP, save the ingress interface from IP6CB, clear IPCB, and restore the saved value. Doing this in the common decapsulation path covers End.DX4, End.DT4, and End.DT46's IPv4 arm. Use IP6CB(skb)->iif rather than skb->skb_iif. These actions run after l3mdev processing, which can replace skb_iif with the L3 master; IP6CB iif still records the receiving interface set at IPv6 ingress. Fixes: 891ef8dd2a8d ("ipv6: sr: implement additional seg6local actions") Cc: stable@vger.kernel.org Suggested-by: Andrea Mayer Signed-off-by: Kyle Zeng Co-developed-by: David Lee Signed-off-by: David Lee Reviewed-by: Andrea Mayer Link: https://patch.msgid.link/20260817085839.946321-1-david.lee@trailofbits.com Signed-off-by: Jakub Kicinski --- net/ipv6/seg6_local.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/net/ipv6/seg6_local.c b/net/ipv6/seg6_local.c index 2b41e4c0dddd..95ea0b62729a 100644 --- a/net/ipv6/seg6_local.c +++ b/net/ipv6/seg6_local.c @@ -256,6 +256,13 @@ static bool decap_and_validate(struct sk_buff *skb, int proto) if (iptunnel_pull_offloads(skb)) return false; + if (proto == IPPROTO_IPIP) { + int iif = IP6CB(skb)->iif; + + memset(IPCB(skb), 0, sizeof(*IPCB(skb))); + IPCB(skb)->iif = iif; + } + return true; } From f12c2de4f542e3220e17e0606f492110064f04cb Mon Sep 17 00:00:00 2001 From: Kyle Zeng Date: Mon, 17 Aug 2026 08:49:54 +0000 Subject: [PATCH 0738/1328] batman-adv: reject unrepresentable multicast TVLV offsets The network and transport header fields in struct sk_buff are 16-bit offsets from skb->head, and U16_MAX is reserved as the unset transport header value. batadv_tvlv_call_handler() sets both fields from a received multicast TVLV without checking whether the TVLV end is representable. If the end offset exceeds the field's range, skb_set_transport_header() truncates it so that the transport header precedes the network header. The negative difference is then returned by skb_network_header_len() as a large u32. batadv_mcast_forw_packet() consequently accepts an oversized multicast tracker and accesses memory beyond the skb data. Add skb_set_transport_header_careful(), an offset-aware counterpart to skb_reset_transport_header_careful(), which validates the final head-relative offset before assigning it. Use the new helper in batadv_tvlv_call_handler() and reject unrepresentable TVLVs before setting the network header. Fixes: 07afe1ba288c ("batman-adv: mcast: implement multicast packet reception and forwarding") Cc: stable@vger.kernel.org Signed-off-by: Kyle Zeng Co-developed-by: David Lee Signed-off-by: David Lee Acked-by: Sven Eckelmann Link: https://patch.msgid.link/20260817084955.944189-1-david.lee@trailofbits.com Signed-off-by: Jakub Kicinski --- include/linux/skbuff.h | 24 ++++++++++++++++++++++++ net/batman-adv/tvlv.c | 5 ++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h index 95184183180f..5522716df8ff 100644 --- a/include/linux/skbuff.h +++ b/include/linux/skbuff.h @@ -3126,6 +3126,30 @@ static inline void skb_set_transport_header(struct sk_buff *skb, skb->transport_header += offset; } +/** + * skb_set_transport_header_careful - conditionally set transport header + * @skb: buffer to alter + * @offset: offset to add to skb->data + * + * Hardened version of skb_set_transport_header(). + * + * Returns: true if the operation was a success. + */ +static inline bool __must_check +skb_set_transport_header_careful(struct sk_buff *skb, const int offset) +{ + long thoff = skb->data - skb->head + offset; + + if (unlikely(thoff != (typeof(skb->transport_header))thoff)) + return false; + + if (unlikely(thoff == (typeof(skb->transport_header))~0U)) + return false; + + skb->transport_header = thoff; + return true; +} + static inline unsigned char *skb_network_header(const struct sk_buff *skb) { return skb->head + skb->network_header; diff --git a/net/batman-adv/tvlv.c b/net/batman-adv/tvlv.c index de907c07fa15..93492d393c34 100644 --- a/net/batman-adv/tvlv.c +++ b/net/batman-adv/tvlv.c @@ -438,8 +438,11 @@ static int batadv_tvlv_call_handler(struct batadv_priv *bat_priv, return NET_RX_SUCCESS; tvlv_offset = (unsigned char *)tvlv_value - skb->data; + if (!skb_set_transport_header_careful(skb, + tvlv_offset + tvlv_value_len)) + return -EINVAL; + skb_set_network_header(skb, tvlv_offset); - skb_set_transport_header(skb, tvlv_offset + tvlv_value_len); return tvlv_handler->mcast_handler(bat_priv, skb); } From d302d7109fb9c553bcd96adad7fa2918316c4cc2 Mon Sep 17 00:00:00 2001 From: Zhan Xusheng Date: Tue, 18 Aug 2026 10:37:04 +0800 Subject: [PATCH 0739/1328] ethtool: remove unused __ETHTOOL_LINK_MODE_MASK_NWORDS From: Zhan Xusheng Added by commit f625aa9be8c1 ("ethtool: provide link mode information with LINKMODES_GET request") and never used. The same count is computed as __ETHTOOL_LINK_MODE_MASK_NU32 in net/ethtool/ioctl.c. Signed-off-by: Zhan Xusheng Link: https://patch.msgid.link/20260818023704.125721-1-zhanxusheng@xiaomi.com Signed-off-by: Jakub Kicinski --- include/linux/ethtool_netlink.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/include/linux/ethtool_netlink.h b/include/linux/ethtool_netlink.h index 39254b2726c0..15881f34ef66 100644 --- a/include/linux/ethtool_netlink.h +++ b/include/linux/ethtool_netlink.h @@ -7,9 +7,6 @@ #include #include -#define __ETHTOOL_LINK_MODE_MASK_NWORDS \ - DIV_ROUND_UP(__ETHTOOL_LINK_MODE_MASK_NBITS, 32) - #define ETHTOOL_PAUSE_STAT_CNT (__ETHTOOL_A_PAUSE_STAT_CNT - \ ETHTOOL_A_PAUSE_STAT_TX_FRAMES) From 447cbe95ebb95392b5d8f6a01c0556826919ce23 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Tue, 11 Aug 2026 08:52:46 +0000 Subject: [PATCH 0740/1328] vlan: fix skb_under_panic and races when toggling HW VLAN offload Toggling hardware VLAN TX offload (NETIF_F_HW_VLAN_CTAG_TX or NETIF_F_HW_VLAN_STAG_TX) on a lower device invokes vlan_transfer_features(), which dynamically changed vlandev->hard_header_len. This causes two issues: 1. Lockless TX paths (e.g. packet_snd in af_packet.c, ip6_finish_output2) read dev->hard_header_len without holding RTNL lock. Mutating hard_header_len dynamically under RTNL creates a data race where upper layers reserve insufficient headroom based on a stale hard_header_len, resulting in skb_under_panic when vlan_dev_hard_header() is called. 2. In addition, vlan_transfer_features() updated hard_header_len without updating header_ops, causing a mismatch between allocated headroom and header creation. Always setting dev->hard_header_len = real_dev->hard_header_len and dev->needed_headroom = real_dev->needed_headroom + VLAN_HLEN unconditionally ensures: - dev->hard_header_len remains 100% static and immutable at real_dev->hard_header_len, eliminating all dynamic runtime updates and data races on hard_header_len. - Upper layers allocating skbs via LL_RESERVED_SPACE() will always reserve sufficient headroom for software VLAN tag insertion (real_dev->hard_header_len + real_dev->needed_headroom + VLAN_HLEN). - vlandev inherits real_dev->needed_tailroom so underlying trailer/padding/ICV requirements are honored. - AF_PACKET SOCK_RAW network header offsets remain correctly aligned at real_dev->hard_header_len. - vlan_header_ops is used unconditionally. Note to stable teams: Make sure to backport these commits: e16e960d55a4 ("ipvlan: inherit needed_headroom and needed_tailroom from phy_dev") cef51860becd ("macvlan: inherit needed_headroom and needed_tailroom from lowerdev") Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: Tangxin Xie Closes: https://lore.kernel.org/netdev/99d678ae-c7b2-4b44-b534-b8320679deb3@h-partners.com/ Cc: # 3.19: e16e960d55a4: ipvlan: inherit needed_headroom and needed_tailroom from phy_dev Cc: # 3.19: cef51860becd: macvlan: inherit needed_headroom and needed_tailroom from lowerdev Cc: # 3.19 Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260811085246.2267779-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/8021q/vlan_dev.c | 39 +++++++-------------------------------- 1 file changed, 7 insertions(+), 32 deletions(-) diff --git a/net/8021q/vlan_dev.c b/net/8021q/vlan_dev.c index ec2569b3f8da..2859cbac3f26 100644 --- a/net/8021q/vlan_dev.c +++ b/net/8021q/vlan_dev.c @@ -502,26 +502,6 @@ static const struct header_ops vlan_header_ops = { .parse_protocol = vlan_parse_protocol, }; -static int vlan_passthru_hard_header(struct sk_buff *skb, struct net_device *dev, - unsigned short type, - const void *daddr, const void *saddr, - unsigned int len) -{ - struct vlan_dev_priv *vlan = vlan_dev_priv(dev); - struct net_device *real_dev = vlan->real_dev; - - if (saddr == NULL) - saddr = dev->dev_addr; - - return dev_hard_header(skb, real_dev, type, daddr, saddr, len); -} - -static const struct header_ops vlan_passthru_header_ops = { - .create = vlan_passthru_hard_header, - .parse = eth_header_parse, - .parse_protocol = vlan_parse_protocol, -}; - static const struct device_type vlan_type = { .name = "vlan", }; @@ -580,14 +560,10 @@ static int vlan_dev_init(struct net_device *dev) dev->fcoe_ddp_xid = real_dev->fcoe_ddp_xid; #endif - dev->needed_headroom = real_dev->needed_headroom; - if (vlan_hw_offload_capable(real_dev->features, vlan->vlan_proto)) { - dev->header_ops = &vlan_passthru_header_ops; - dev->hard_header_len = real_dev->hard_header_len; - } else { - dev->header_ops = &vlan_header_ops; - dev->hard_header_len = real_dev->hard_header_len + VLAN_HLEN; - } + dev->needed_headroom = real_dev->needed_headroom + VLAN_HLEN; + dev->needed_tailroom = real_dev->needed_tailroom; + dev->header_ops = &vlan_header_ops; + dev->hard_header_len = real_dev->hard_header_len; dev->netdev_ops = &vlan_netdev_ops; @@ -1029,10 +1005,9 @@ static void vlan_transfer_features(struct net_device *dev, netif_inherit_tso_max(vlandev, dev); - if (vlan_hw_offload_capable(dev->features, vlan->vlan_proto)) - vlandev->hard_header_len = dev->hard_header_len; - else - vlandev->hard_header_len = dev->hard_header_len + VLAN_HLEN; + vlandev->needed_headroom = dev->needed_headroom + VLAN_HLEN; + vlandev->needed_tailroom = dev->needed_tailroom; + vlandev->hard_header_len = dev->hard_header_len; #if IS_ENABLED(CONFIG_FCOE) vlandev->fcoe_ddp_xid = dev->fcoe_ddp_xid; From 992cc9f94ca924089a506ba9b327caa9af797529 Mon Sep 17 00:00:00 2001 From: Kyle Zeng Date: Sun, 16 Aug 2026 16:56:46 -0700 Subject: [PATCH 0741/1328] net/packet: defer vmalloc TX_RING free until skbs finish AF_PACKET TX_RING skbs keep a raw pointer to their ring frame. The skb page references preserve page-backed ring blocks after pg_vec is freed, but they do not preserve a vmalloc mapping. tpacket_destruct_skb() currently drops the pending reference before writing the timestamp and TP_STATUS_AVAILABLE to the frame. Move the decrement after those stores. The smp_wmb() in __packet_set_status() orders the frame stores before the decrement. Also recheck pending TX frames under pg_vec_lock before non-closing ring replacement, so a racing send cannot add a pending skb between the initial check and the ring swap. Ring allocation can produce a mixture of page-backed and vmalloc-backed blocks. Allocate deferred-work storage during TX ring setup when the first vmalloc-backed block is encountered, and keep its pointer in the pg_vec allocation header. If allocation fails, return -ENOMEM from ring setup. On socket close, a non-NULL pointer identifies a vmalloc-backed vector without a scan. If TX skbs remain, defer the whole vector to system_long_wq. After pg_vec is detached, a late destructor can skip the pending decrement. Use socket write-memory accounting as the deferred lifetime gate instead: an skb remains charged through its final sock_wfree(), after all ring-frame accesses. The delayed work retains a socket reference and reschedules itself until no TX skbs remain. Move pending_refcnt release to packet_sock_destruct() so late skb destructors and deferred cleanup can safely use it after packet_release(). Page-backed teardown remains synchronous, and no lock is added to the TX completion hot path. Fixes: b013840810c2 ("packet: use percpu mmap tx frame pending refcount") Cc: stable@vger.kernel.org Link: https://lore.kernel.org/netdev/20260721015824.45829-1-kylebot@openai.com/ Suggested-by: Eric Dumazet Suggested-by: Willem de Bruijn Reviewed-by: Willem de Bruijn Signed-off-by: Kyle Zeng Link: https://patch.msgid.link/20260816235646.76500-1-kylebot@openai.com Signed-off-by: Jakub Kicinski --- net/packet/af_packet.c | 96 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 87 insertions(+), 9 deletions(-) diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c index 1168bd6b09cd..b22cda322136 100644 --- a/net/packet/af_packet.c +++ b/net/packet/af_packet.c @@ -88,6 +88,7 @@ #include #include #include +#include #ifdef CONFIG_INET #include #endif @@ -1341,6 +1342,8 @@ static void packet_sock_destruct(struct sock *sk) WARN_ON(atomic_read(&sk->sk_rmem_alloc)); WARN_ON(refcount_read(&sk->sk_wmem_alloc)); + packet_free_pending(pkt_sk(sk)); + if (!sock_flag(sk, SOCK_DEAD)) { pr_err("Attempt to release alive packet socket: %p\n", sk); return; @@ -2534,11 +2537,11 @@ static void tpacket_destruct_skb(struct sk_buff *skb) __u32 ts; ph = skb_zcopy_get_nouarg(skb); - packet_dec_pending(&po->tx_ring); ts = __packet_set_timestamp(po, ph, skb); __packet_set_status(po, ph, TP_STATUS_AVAILABLE | ts); + packet_dec_pending(&po->tx_ring); complete(&po->skb_completion); } @@ -3207,7 +3210,6 @@ static int packet_release(struct socket *sock) /* Purge queues */ skb_queue_purge(&sk->sk_receive_queue); - packet_free_pending(po); sock_put(sk); return 0; @@ -4370,11 +4372,26 @@ static const struct vm_operations_struct packet_mmap_ops = { .close = packet_mm_close, }; +struct packet_pg_vec { + struct packet_pg_vec_free *deferred; + unsigned int order; + unsigned int len; + struct pgv pg_vec[] __counted_by(len); +}; + +struct packet_pg_vec_free { + struct delayed_work work; + struct sock *sk; + struct packet_pg_vec *vec; +}; + static void free_pg_vec(struct pgv *pg_vec, unsigned int order, unsigned int len) { + struct packet_pg_vec *vec; int i; + vec = container_of_const(pg_vec, struct packet_pg_vec, pg_vec[0]); for (i = 0; i < len; i++) { if (likely(pg_vec[i].buffer)) { if (is_vmalloc_addr(pg_vec[i].buffer)) @@ -4385,7 +4402,46 @@ static void free_pg_vec(struct pgv *pg_vec, unsigned int order, pg_vec[i].buffer = NULL; } } - kfree(pg_vec); + kfree(vec->deferred); + kfree(vec); +} + +static void packet_free_pg_vec_work(struct work_struct *work) +{ + struct packet_pg_vec_free *deferred; + struct packet_pg_vec *vec; + struct sock *sk; + + deferred = container_of_const(to_delayed_work(work), + struct packet_pg_vec_free, work); + vec = deferred->vec; + sk = deferred->sk; + if (sk_wmem_alloc_get(sk)) { + queue_delayed_work(system_long_wq, &deferred->work, 1); + return; + } + + free_pg_vec(vec->pg_vec, vec->order, vec->len); + sock_put(sk); +} + +static void packet_free_tx_ring(struct sock *sk, struct pgv *pg_vec, + unsigned int order, unsigned int len) +{ + struct packet_pg_vec_free *deferred; + struct packet_pg_vec *vec; + + vec = container_of_const(pg_vec, struct packet_pg_vec, pg_vec[0]); + deferred = vec->deferred; + if (!deferred || !sk_wmem_alloc_get(sk)) { + free_pg_vec(pg_vec, order, len); + return; + } + + /* A detached ring's pending count can miss late skb destructors. */ + deferred->sk = sk; + sock_hold(sk); + queue_delayed_work(system_long_wq, &deferred->work, 0); } static char *alloc_one_pg_vec_page(unsigned long order) @@ -4413,20 +4469,35 @@ static char *alloc_one_pg_vec_page(unsigned long order) return NULL; } -static struct pgv *alloc_pg_vec(struct tpacket_req *req, int order) +static struct pgv *alloc_pg_vec(struct tpacket_req *req, int order, bool tx_ring) { unsigned int block_nr = req->tp_block_nr; + struct packet_pg_vec *vec; struct pgv *pg_vec; int i; - pg_vec = kzalloc_objs(struct pgv, block_nr, GFP_KERNEL | __GFP_NOWARN); - if (unlikely(!pg_vec)) - goto out; + vec = kzalloc_flex(*vec, pg_vec, block_nr, GFP_KERNEL | __GFP_NOWARN); + if (unlikely(!vec)) + return NULL; + vec->order = order; + vec->len = block_nr; + pg_vec = vec->pg_vec; for (i = 0; i < block_nr; i++) { pg_vec[i].buffer = alloc_one_pg_vec_page(order); if (unlikely(!pg_vec[i].buffer)) goto out_free_pgvec; + + if (tx_ring && !vec->deferred && + is_vmalloc_addr(pg_vec[i].buffer)) { + vec->deferred = kzalloc_obj(*vec->deferred, + GFP_KERNEL | __GFP_NOWARN); + if (!vec->deferred) + goto out_free_pgvec; + vec->deferred->vec = vec; + INIT_DELAYED_WORK(&vec->deferred->work, + packet_free_pg_vec_work); + } } out: @@ -4509,7 +4580,7 @@ static int packet_set_ring(struct sock *sk, union tpacket_req_u *req_u, err = -ENOMEM; order = get_order(req->tp_block_size); - pg_vec = alloc_pg_vec(req, order); + pg_vec = alloc_pg_vec(req, order, tx_ring); if (unlikely(!pg_vec)) goto out; switch (po->tp_version) { @@ -4561,6 +4632,9 @@ static int packet_set_ring(struct sock *sk, union tpacket_req_u *req_u, err = -EBUSY; mutex_lock(&po->pg_vec_lock); if (closing || atomic_long_read(&po->mapped) == 0) { + if (tx_ring && !closing && packet_read_pending(rb)) + goto out_unlock; + err = 0; spin_lock_bh(&rb_queue->lock); swap(rb->pg_vec, pg_vec); @@ -4582,6 +4656,7 @@ static int packet_set_ring(struct sock *sk, union tpacket_req_u *req_u, pr_err("packet_mmap: vma is busy: %ld\n", atomic_long_read(&po->mapped)); } +out_unlock: mutex_unlock(&po->pg_vec_lock); spin_lock(&po->bind_lock); @@ -4603,7 +4678,10 @@ static int packet_set_ring(struct sock *sk, union tpacket_req_u *req_u, out_free_pg_vec: if (pg_vec) { bitmap_free(rx_owner_map); - free_pg_vec(pg_vec, order, req->tp_block_nr); + if (tx_ring && closing) + packet_free_tx_ring(sk, pg_vec, order, req->tp_block_nr); + else + free_pg_vec(pg_vec, order, req->tp_block_nr); } out: return err; From 68d8c6532659168430e489bb659c0d41de7d75fe Mon Sep 17 00:00:00 2001 From: Mina Almasry Date: Fri, 14 Aug 2026 19:13:30 +0000 Subject: [PATCH 0742/1328] net: core: propagate unreadable flag in skb_zerocopy skb_zerocopy() fails to propagate the unreadable flag when copying unreadable fragments, causing target skbs to appear as readable memory. This patch fixes the flag propagation. Additionally, it returns -EFAULT if readable fragments are mixed with unreadable fragments during extraction, and returns -EFAULT in openvswitch queue_userspace_packet(). Fixes: 65249feb6b3d ("net: add support for skbs with unreadable frags") Cc: Stanislav Fomichev Cc: Bobby Eshleman Cc: Florian Westphal Cc: Aaron Conole Cc: Eelco Chaudron Cc: Willem de Bruijn Signed-off-by: Mina Almasry Reviewed-by: Pavel Begunkov Reviewed-by: Ilya Maximets Link: https://patch.msgid.link/20260814191336.187243-1-almasrymina@google.com Signed-off-by: Jakub Kicinski --- net/core/skbuff.c | 13 ++++++++++++- net/openvswitch/datapath.c | 3 +++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/net/core/skbuff.c b/net/core/skbuff.c index c82a1472a5ea..d4382b68d56e 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -3870,7 +3870,8 @@ EXPORT_SYMBOL_GPL(skb_zerocopy_headlen); * Return value: * 0: everything is OK * -ENOMEM: couldn't orphan frags of @from due to lack of memory - * -EFAULT: skb_copy_bits() found some problem with skb geometry + * -EFAULT: skb_copy_bits() found some problem with skb geometry, or readable head + * payload would be mixed with unreadable frags. */ int skb_zerocopy(struct sk_buff *to, struct sk_buff *from, int len, int hlen) @@ -3905,10 +3906,17 @@ skb_zerocopy(struct sk_buff *to, struct sk_buff *from, int len, int hlen) } } + if (!skb_frags_readable(from) && j > 0 && len) { + put_page(virt_to_head_page(from->head)); + return -EFAULT; + } + skb_len_add(to, len + plen); if (unlikely(skb_orphan_frags(from, GFP_ATOMIC))) { skb_tx_error(from); + if (j > 0) + put_page(virt_to_head_page(from->head)); return -ENOMEM; } skb_zerocopy_clone(to, from, GFP_ATOMIC); @@ -3928,6 +3936,9 @@ skb_zerocopy(struct sk_buff *to, struct sk_buff *from, int len, int hlen) } skb_shinfo(to)->nr_frags = j; + if (i > 0 && from->unreadable) + to->unreadable = 1; + return 0; } EXPORT_SYMBOL_GPL(skb_zerocopy); diff --git a/net/openvswitch/datapath.c b/net/openvswitch/datapath.c index 69999f9cc44c..f2d5b5ab38de 100644 --- a/net/openvswitch/datapath.c +++ b/net/openvswitch/datapath.c @@ -467,6 +467,9 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb, if (!dp_ifindex) return -ENODEV; + if (!skb_frags_readable(skb)) + return -EFAULT; + if (skb_vlan_tag_present(skb)) { nskb = skb_clone(skb, GFP_ATOMIC); if (!nskb) From d9c56501c72fdac937e8b770f31655c46832f1a5 Mon Sep 17 00:00:00 2001 From: Mina Almasry Date: Fri, 14 Aug 2026 19:13:31 +0000 Subject: [PATCH 0743/1328] net: tcp: block mixing readable and unreadable frags Protect tcp_sendmsg_locked() from mistakenly mixing readable and unreadable page fragments in the same SKB. Check that the devmem binding matches the existing SKB's readability. If a mismatch is detected, avoid collapsing and create a new segment. Fixes: bd61848900bff ("net: devmem: Implement TX path") Suggested-by: Eric Dumazet Cc: Pavel Begunkov Cc: Stanislav Fomichev Cc: Bobby Eshleman Signed-off-by: Mina Almasry Link: https://patch.msgid.link/20260814191336.187243-2-almasrymina@google.com Signed-off-by: Jakub Kicinski --- net/ipv4/tcp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c index 455441f1b694..b4237d0e994d 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c @@ -1240,7 +1240,8 @@ int tcp_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t size) trace_tcp_sendmsg_locked(sk, msg, skb, size_goal); - if (copy <= 0 || !tcp_skb_can_collapse_to(skb)) { + if (copy <= 0 || !tcp_skb_can_collapse_to(skb) || + unlikely(skb_frags_readable(skb) != !binding)) { bool first_skb; new_segment: From 4c660ee8c809637909f4f7eb1017f7b9401c75c4 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Tue, 18 Aug 2026 05:59:27 -0400 Subject: [PATCH 0744/1328] net: sched: fix 32-bit backlog wrap in gred, bfifo and plug enqueue gred_enqueue(), bfifo_enqueue() and plug_enqueue() admit a packet when the current backlog plus the packet length fits within the queue limit: sch->qstats.backlog + qdisc_pkt_len(skb) <= sch->limit (gred default VQ) gred_backlog+qdisc_pkt_len(skb) <= q->limit (gred configured VQ) sch->qstats.backlog + qdisc_pkt_len(skb) <= sch->limit (bfifo) sch->qstats.backlog + skb->len <= q->limit (plug) sch->qstats.backlog and q->backlog are u32, and qdisc_pkt_len()/skb->len are unsigned int, so all sums are computed in 32 bits and wrap at 2^32. Once the true backlog exceeds 4 GiB the wrapped sum becomes small and admission keeps succeeding, so the queue grows without bound and the kernel can be driven to OOM. Promote the sums to u64 so admission stops once the true backlog exceeds the limit. The limit is u32, so the bounded queue stays below 2^32 and the stored u32 backlog never wraps. The bug can only be reproduced as root (albeit with ridiculous setup): attach a gred (or bfifo/plug) qdisc with a limit near 4 GiB, leaving the default VQ unconfigured (for gred), and drive >4 GiB of queued traffic (e.g. via a size table / stab to inflate qdisc_pkt_len, or sustained high-rate traffic). The u32 backlog+len sum wraps at 2^32, admission keeps succeeding, and the queue grows unboundedly to OOM. Fixes: a3eb95f891d6 ("net_sched: gred: add TCA_GRED_LIMIT attribute") Reported-by: vega@nebusec.ai Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260818095927.15901-1-jhs@mojatatu.com Signed-off-by: Jakub Kicinski --- net/sched/sch_fifo.c | 2 +- net/sched/sch_gred.c | 4 ++-- net/sched/sch_plug.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/net/sched/sch_fifo.c b/net/sched/sch_fifo.c index e6bfd39ff339..1b6388d50967 100644 --- a/net/sched/sch_fifo.c +++ b/net/sched/sch_fifo.c @@ -19,7 +19,7 @@ static int bfifo_enqueue(struct sk_buff *skb, struct Qdisc *sch, struct sk_buff **to_free) { - if (likely(sch->qstats.backlog + qdisc_pkt_len(skb) <= + if (likely((u64)sch->qstats.backlog + qdisc_pkt_len(skb) <= READ_ONCE(sch->limit))) return qdisc_enqueue_tail(skb, sch); diff --git a/net/sched/sch_gred.c b/net/sched/sch_gred.c index fcc1a4c03636..f04f425c6c44 100644 --- a/net/sched/sch_gred.c +++ b/net/sched/sch_gred.c @@ -179,7 +179,7 @@ static int gred_enqueue(struct sk_buff *skb, struct Qdisc *sch, * if no default DP has been configured. This * allows for DP flows to be left untouched. */ - if (likely(sch->qstats.backlog + qdisc_pkt_len(skb) <= + if (likely((u64)sch->qstats.backlog + qdisc_pkt_len(skb) <= sch->limit)) return qdisc_enqueue_tail(skb, sch); else @@ -244,7 +244,7 @@ static int gred_enqueue(struct sk_buff *skb, struct Qdisc *sch, break; } - if (gred_backlog(t, q, sch) + qdisc_pkt_len(skb) <= q->limit) { + if ((u64)gred_backlog(t, q, sch) + qdisc_pkt_len(skb) <= q->limit) { q->backlog += qdisc_pkt_len(skb); return qdisc_enqueue_tail(skb, sch); } diff --git a/net/sched/sch_plug.c b/net/sched/sch_plug.c index cefb65201e17..b60ddfee6a68 100644 --- a/net/sched/sch_plug.c +++ b/net/sched/sch_plug.c @@ -89,7 +89,7 @@ static int plug_enqueue(struct sk_buff *skb, struct Qdisc *sch, { struct plug_sched_data *q = qdisc_priv(sch); - if (likely(sch->qstats.backlog + skb->len <= q->limit)) { + if (likely((u64)sch->qstats.backlog + skb->len <= q->limit)) { if (!q->unplug_indefinite) q->pkts_current_epoch++; return qdisc_enqueue_tail(skb, sch); From 25b863cd6d12ec196115ff7a5422c69995443466 Mon Sep 17 00:00:00 2001 From: Triet Hoang Date: Tue, 18 Aug 2026 13:27:39 +0000 Subject: [PATCH 0745/1328] tools: ynl: handle calloc failure in ynl_ntf_parse Check the return value of calloc() before dereferencing the allocated response structure in ynl_ntf_parse(). Signed-off-by: Triet Hoang Link: https://patch.msgid.link/20260818132739.469624-1-triet.hoang.dev@gmail.com Signed-off-by: Jakub Kicinski --- tools/net/ynl/lib/ynl.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/net/ynl/lib/ynl.c b/tools/net/ynl/lib/ynl.c index 2bcd781111d7..af101544c31a 100644 --- a/tools/net/ynl/lib/ynl.c +++ b/tools/net/ynl/lib/ynl.c @@ -889,6 +889,9 @@ static int ynl_ntf_parse(struct ynl_sock *ys, const struct nlmsghdr *nlh) return YNL_PARSE_CB_ERROR; rsp = calloc(1, info->alloc_sz); + if (!rsp) + return YNL_PARSE_CB_ERROR; + rsp->free = info->free; yarg.data = rsp->data; yarg.rsp_policy = info->policy; From 03a9d10ecf71f54b2af8020935f2033d4a132be5 Mon Sep 17 00:00:00 2001 From: Hyunwoo Kim Date: Wed, 19 Aug 2026 10:38:37 +0900 Subject: [PATCH 0746/1328] sctp: drop a chunk if its transport was removed sctp_rcv() resolves the transport once per packet and leaves it in chunk->transport. The lookup reference, or the one sctp_add_backlog() takes if the socket is owned by userspace, keeps it around until the chunk has been processed. An authenticated ASCONF DEL-IP can remove it in the meantime. sctp_assoc_rm_peer() takes the transport out of the association and calls sctp_transport_free(), which tags it dead and drops the reference the association held. There is a window on both paths: the packet can sit on the socket backlog, and on the direct path the lookup completes before bh_lock_sock(). The DATA chunk in that packet puts the removed transport back into asoc->peer.last_data_from. Once the packet is done that reference goes away and the transport is freed by RCU, so the next delayed SACK carries the pointer into the SACK chunk and sctp_outq_select_transport() reads the freed transport's state. Drop the chunk in sctp_inq_push(), next to the existing rcvr->dead check. Both paths reach it with the association's socket lock held. The peer retransmits it. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Hyunwoo Kim Acked-by: Xin Long Link: https://patch.msgid.link/aoUJHQmxL0LFIMCw@v4bel Signed-off-by: Jakub Kicinski --- net/sctp/inqueue.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/net/sctp/inqueue.c b/net/sctp/inqueue.c index a024c0843247..5f988b3a8814 100644 --- a/net/sctp/inqueue.c +++ b/net/sctp/inqueue.c @@ -71,8 +71,11 @@ void sctp_inq_free(struct sctp_inq *queue) */ void sctp_inq_push(struct sctp_inq *q, struct sctp_chunk *chunk) { - /* Directly call the packet handling routine. */ - if (chunk->rcvr->dead) { + /* Directly call the packet handling routine. Drop the chunk if the + * receiver or the transport it was looked up on is gone. + */ + if (chunk->rcvr->dead || + (chunk->transport && chunk->transport->dead)) { sctp_chunk_free(chunk); return; } From 8197c180052fe5ee644e91b31d98328bea8a31ed Mon Sep 17 00:00:00 2001 From: Stefan Wahren Date: Tue, 18 Aug 2026 15:59:58 +0200 Subject: [PATCH 0747/1328] docs: oa-tc6-framework: Fix link to specification Current link for 10BASE-T1x MAC-PHY Serial Interface Specification doesn't work - it returns 404. Update the link to the working one. Signed-off-by: Stefan Wahren Acked-by: Randy Dunlap Link: https://patch.msgid.link/20260818135958.17311-1-wahrenst@gmx.net Signed-off-by: Jakub Kicinski --- Documentation/networking/oa-tc6-framework.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/networking/oa-tc6-framework.rst b/Documentation/networking/oa-tc6-framework.rst index 013824078cea..0feda2f9d840 100644 --- a/Documentation/networking/oa-tc6-framework.rst +++ b/Documentation/networking/oa-tc6-framework.rst @@ -77,7 +77,7 @@ Reference 10BASE-T1x MAC-PHY Serial Interface Specification, -Link: https://opensig.org/download/document/OPEN_Alliance_10BASET1x_MAC-PHY_Serial_Interface_V1.1.pdf +Link: https://opensig.org/wp-content/uploads/2023/12/OPEN_Alliance_10BASET1x_MAC-PHY_Serial_Interface_V1.1.pdf Hardware Architecture --------------------- From 256496397287334a19ed80ec7be92bffcae76b9d Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Mon, 17 Aug 2026 14:35:16 +0900 Subject: [PATCH 0748/1328] NTB: ntb_transport: Recycle TX entries before client callbacks ntb_tx_copy_callback() invokes the client callback before returning the entry to tx_free_q. The callback may wake a stopped client queue, only for the next enqueue to find no local entry and return -EBUSY. The window is narrow, but the retry is unnecessary. Save the callback data and length, then return the entry to tx_free_q before invoking the client. A completion callback then means both the client buffer and transport entry are ready for reuse. Fixes: fce8a7bb5b4b ("PCI-Express Non-Transparent Bridge Support") Cc: stable@vger.kernel.org Signed-off-by: Koichiro Den Reviewed-by: Dave Jiang Link: https://patch.msgid.link/20260817053519.4135287-2-den@valinux.co.jp Signed-off-by: Jakub Kicinski --- drivers/ntb/ntb_transport.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/drivers/ntb/ntb_transport.c b/drivers/ntb/ntb_transport.c index f59f926d4bfa..e92b96f4afad 100644 --- a/drivers/ntb/ntb_transport.c +++ b/drivers/ntb/ntb_transport.c @@ -1719,9 +1719,16 @@ static void ntb_transport_rxc_db(unsigned long data) static void ntb_tx_copy_callback(void *data, const struct dmaengine_result *res) { + struct ntb_payload_header __iomem *hdr; struct ntb_queue_entry *entry = data; - struct ntb_transport_qp *qp = entry->qp; - struct ntb_payload_header __iomem *hdr = entry->tx_hdr; + struct ntb_transport_qp *qp; + unsigned int len; + void *cb_data; + + qp = entry->qp; + hdr = entry->tx_hdr; + cb_data = entry->cb_data; + len = entry->len; /* we need to check DMA results if we are using DMA */ if (res) { @@ -1768,15 +1775,13 @@ static void ntb_tx_copy_callback(void *data, * "link down" or similar. Since no payload is being sent in these * cases, there is nothing to add to the completion queue. */ - if (entry->len > 0) { - qp->tx_bytes += entry->len; - - if (qp->tx_handler) - qp->tx_handler(qp, qp->cb_data, entry->cb_data, - entry->len); - } + if (len > 0) + qp->tx_bytes += len; ntb_list_add(&qp->ntb_tx_free_q_lock, &entry->entry, &qp->tx_free_q); + + if (len > 0 && qp->tx_handler) + qp->tx_handler(qp, qp->cb_data, cb_data, len); } static void ntb_memcpy_tx_on_stack(struct ntb_queue_entry *entry, void __iomem *offset) From 8aaa47351db0f93a5c5297fbafdfa8bc75e8ae49 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Mon, 17 Aug 2026 14:35:17 +0900 Subject: [PATCH 0749/1328] net: ntb_netdev: Fix TX busy and drop handling Currently, ntb_netdev returns NETDEV_TX_BUSY for every enqueue error. It also increments the drop and error counters while leaving the skb owned by the qdisc, and may return BUSY with the subqueue still awake. Retrying a permanent error cannot succeed either. The unconditional BUSY return and premature accounting date back to the initial driver. The error-path queue stop was later removed without changing that return value. The current flow-control code includes a resource check, but ntb_netdev does not honor its result before enqueue. Honor the resource check before enqueue. For -EAGAIN and -EBUSY, stop the subqueue, arm the existing reaper timer, and return BUSY without touching the skb. For other errors, free the skb, increment tx_dropped, and return NETDEV_TX_OK. Fixes: 548c237c0a99 ("net: Add support for NTB virtual ethernet device") Fixes: d723485cb4ca ("ntb_netdev: remove tx timeout") Fixes: e74bfeedad08 ("NTB: Add flow control to the ntb_netdev") Cc: stable@vger.kernel.org Signed-off-by: Koichiro Den Reviewed-by: Dave Jiang Link: https://patch.msgid.link/20260817053519.4135287-3-den@valinux.co.jp Signed-off-by: Jakub Kicinski --- drivers/net/ntb_netdev.c | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/drivers/net/ntb_netdev.c b/drivers/net/ntb_netdev.c index 029a4a532a10..02b35cf53a62 100644 --- a/drivers/net/ntb_netdev.c +++ b/drivers/net/ntb_netdev.c @@ -199,8 +199,10 @@ static int __ntb_netdev_maybe_stop_tx(struct net_device *netdev, static int ntb_netdev_maybe_stop_tx(struct net_device *ndev, struct ntb_netdev_queue *q, int size) { - if (__netif_subqueue_stopped(ndev, q->qid) || - (ntb_transport_tx_free_entry(q->qp) >= size)) + if (__netif_subqueue_stopped(ndev, q->qid)) + return -EBUSY; + + if (ntb_transport_tx_free_entry(q->qp) >= size) return 0; return __ntb_netdev_maybe_stop_tx(ndev, q, size); @@ -256,21 +258,30 @@ static netdev_tx_t ntb_netdev_start_xmit(struct sk_buff *skb, q = &dev->queues[qid]; - ntb_netdev_maybe_stop_tx(ndev, q, tx_stop); + if (unlikely(ntb_netdev_maybe_stop_tx(ndev, q, tx_stop))) + return NETDEV_TX_BUSY; rc = ntb_transport_tx_enqueue(q->qp, skb, skb->data, skb->len); - if (rc) - goto err; + if (rc) { + if (rc == -EAGAIN || rc == -EBUSY) { + netif_stop_subqueue(ndev, q->qid); + mod_timer(&q->tx_timer, + jiffies + usecs_to_jiffies(tx_time)); + return NETDEV_TX_BUSY; + } + + goto drop; + } /* check for next submit */ ntb_netdev_maybe_stop_tx(ndev, q, tx_stop); return NETDEV_TX_OK; -err: +drop: + dev_kfree_skb_any(skb); ndev->stats.tx_dropped++; - ndev->stats.tx_errors++; - return NETDEV_TX_BUSY; + return NETDEV_TX_OK; } static void ntb_netdev_tx_timer(struct timer_list *t) From 873ce713fef5dde0939220f04f3484ec86a16fba Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Mon, 17 Aug 2026 14:35:18 +0900 Subject: [PATCH 0750/1328] NTB: ntb_transport: Fail TX enqueue when the QP link is down Commit f195a1a6fe41 ("ntb: Drop packets when qp link is down") meant to make ntb_transport_tx_enqueue() drop packets submitted while the QP link is down, but it only returns 0 without consuming the packet. Zero means success by this function's contract, so ntb_netdev reports NETDEV_TX_OK and forgets the skb: nothing queued it, nothing frees it, and it leaks, one skb for every transmit racing a link-down. Return -ENOLINK instead, restoring the contract that a non-zero return leaves the buffer owned by the caller. With the preceding patch, ntb_netdev frees the skb on non-retryable enqueue failures and returns NETDEV_TX_OK, so a packet racing with link-down is dropped without leaking or entering a busy retry loop. Fixes: f195a1a6fe41 ("ntb: Drop packets when qp link is down") Cc: stable@vger.kernel.org Signed-off-by: Koichiro Den Reviewed-by: Dave Jiang Link: https://patch.msgid.link/20260817053519.4135287-4-den@valinux.co.jp Signed-off-by: Jakub Kicinski --- drivers/ntb/ntb_transport.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/ntb/ntb_transport.c b/drivers/ntb/ntb_transport.c index e92b96f4afad..4bdd81edcb87 100644 --- a/drivers/ntb/ntb_transport.c +++ b/drivers/ntb/ntb_transport.c @@ -2353,9 +2353,8 @@ int ntb_transport_tx_enqueue(struct ntb_transport_qp *qp, void *cb, void *data, if (!qp || !len) return -EINVAL; - /* If the qp link is down already, just ignore. */ if (!qp->link_is_up) - return 0; + return -ENOLINK; entry = ntb_list_rm(&qp->ntb_tx_free_q_lock, &qp->tx_free_q); if (!entry) { From a4f2387db6f1cc2f03abba7f3a6807ad61e26ff7 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Mon, 17 Aug 2026 14:35:19 +0900 Subject: [PATCH 0751/1328] NTB: ntb_transport: Reject oversized TX buffers ntb_process_tx() handles an oversized buffer by calling tx_handler() with a NULL data pointer and returning success. ntb_netdev therefore neither frees the skb in its completion callback nor takes its enqueue error path, leaking it. Reject oversized buffers in ntb_transport_tx_enqueue() before acquiring a queue entry and return -EMSGSIZE. The caller retains ownership of the buffer, and the preceding netdev patch frees the skb when enqueue returns this permanent error. Fixes: fce8a7bb5b4b ("PCI-Express Non-Transparent Bridge Support") Cc: stable@vger.kernel.org Signed-off-by: Koichiro Den Reviewed-by: Dave Jiang Link: https://patch.msgid.link/20260817053519.4135287-5-den@valinux.co.jp Signed-off-by: Jakub Kicinski --- drivers/ntb/ntb_transport.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/drivers/ntb/ntb_transport.c b/drivers/ntb/ntb_transport.c index 4bdd81edcb87..f9caa1a653c5 100644 --- a/drivers/ntb/ntb_transport.c +++ b/drivers/ntb/ntb_transport.c @@ -1955,15 +1955,6 @@ static int ntb_process_tx(struct ntb_transport_qp *qp, return -EAGAIN; } - if (entry->len > qp->tx_max_frame - sizeof(struct ntb_payload_header)) { - if (qp->tx_handler) - qp->tx_handler(qp, qp->cb_data, NULL, -EIO); - - ntb_list_add(&qp->ntb_tx_free_q_lock, &entry->entry, - &qp->tx_free_q); - return 0; - } - ntb_async_tx(qp, entry); qp->tx_pkts++; @@ -2356,6 +2347,9 @@ int ntb_transport_tx_enqueue(struct ntb_transport_qp *qp, void *cb, void *data, if (!qp->link_is_up) return -ENOLINK; + if (len > qp->tx_max_frame - sizeof(struct ntb_payload_header)) + return -EMSGSIZE; + entry = ntb_list_rm(&qp->ntb_tx_free_q_lock, &qp->tx_free_q); if (!entry) { qp->tx_err_no_buf++; From f2849b1fd059ec9b3281b771e6ac5aad9feee851 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Mon, 17 Aug 2026 23:30:03 +0200 Subject: [PATCH 0752/1328] net: phylink: correctly validate returned PCS in phylink_inband_caps In phylink_inband_caps(), the PCS returned by mac_select_pcs is only checked if NULL but mac_select_pcs can also return an error pointer. This can cause a kernel panic as phylink_pcs_inband_caps() only checks if passed PCS is not NULL and directly dereference ops from the phylink_pcs struct. Use the IS_ERR_OR_NULL macro to address both case where the returned PCS can be NULL or an error pointer and prevent a kernel panic. Cc: stable@vger.kernel.org Fixes: df874f9e52c3 ("net: phylink: add pcs_inband_caps() method") Signed-off-by: Christian Marangi Link: https://patch.msgid.link/20260817213009.13924-1-ansuelsmth@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/phy/phylink.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index 5b8e956902fb..3ec3bb439109 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -964,7 +964,7 @@ static unsigned int phylink_inband_caps(struct phylink *pl, return 0; pcs = pl->mac_ops->mac_select_pcs(pl->config, interface); - if (!pcs) + if (IS_ERR_OR_NULL(pcs)) return 0; return phylink_pcs_inband_caps(pcs, interface); From a8b842366f8ff469fe0fc700ba53770ded847ea9 Mon Sep 17 00:00:00 2001 From: "Li, Yifan" Date: Fri, 14 Aug 2026 11:10:08 +0800 Subject: [PATCH 0753/1328] powercap: intel_rapl: Sign-extend the PMU delta on counter wraparound The RAPL PMU misreports energy when the hardware energy counter overflows and wraps back to zero. perf event counts are defined to increase monotonically, but a single wraparound makes the PMU event count jump backwards by nearly the full counter range, and consumers that take the difference of two reads in unsigned arithmetic then underflow and report an absurd value. On a Panther Lake system (energy unit 61.035 uJ, counter range 262144 J) the package counter wraps every ~2.9 hours at 25 W, and turbostat prints one bogus sample per wraparound, per domain: PkgTmp PkgWatt CorWatt GFXWatt RAMWatt SysWatt 44 24.97 16.30 3.90 1.87 2145386370.35 43 2145240612.10 16.13 4.02 1.91 40.46 The RAPL energy counters are 32-bit wide on every register interface: MSR, MMIO and TPMI all describe ENERGY_COUNTER with a GENMASK(31, 0) mask. rapl_read_data_raw() applies that mask, so event_read_counter() returns the counter zero-extended in a u64. rapl_event_update() then computes delta = new_raw_count - prev_raw_count; without reducing the result modulo 2^32. While the counter does not wrap this is correct, but once the hardware counter wraps, new_raw_count < prev_raw_count and delta becomes (true_delta - 2^32), a large negative value. Declaring delta as s64 only makes that value representable; it does not correct it. That bogus delta is scaled and added to event->count, which is where the backwards jump comes from. Fix it the way arch/x86/events/rapl.c has done since the RAPL PMU was first introduced: shift both values up so that the 64-bit subtraction reduces modulo 2^32, then shift the difference back down with an arithmetic shift to sign-extend it. This is correct as long as at most one wraparound happens between two updates, which the existing overflow hrtimer already guarantees: its period is half of the counter range at the 200 W reference used in rapl_package_add_pmu_locked(). The problem has been present since the powercap RAPL PMU was added, but only affected TPMI RAPL until commit 748d6ba43afd ("powercap: intel_rapl: Enable MSR-based RAPL PMU support") routed MSR RAPL through the same PMU, which exposed it on client platforms such as Panther Lake. Fixes: 575024a8aa7c ("powercap: intel_rapl: Introduce APIs for PMU support") Reported-by: Jyoti, Anand B Signed-off-by: Li, Yifan Signed-off-by: Gao Jianfeng Tested-by: Jyoti, Anand B Acked-by: Srinivas Pandruvada Reviewed-by: Kuppuswamy Sathyanarayanan Link: https://patch.msgid.link/20260814031008.750911-1-yifan2.li@intel.com Signed-off-by: Rafael J. Wysocki --- drivers/powercap/intel_rapl_common.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/powercap/intel_rapl_common.c b/drivers/powercap/intel_rapl_common.c index 1006d183d508..6b7d11a0abc5 100644 --- a/drivers/powercap/intel_rapl_common.c +++ b/drivers/powercap/intel_rapl_common.c @@ -32,6 +32,9 @@ #define ENERGY_STATUS_MASK GENMASK(31, 0) +/* Width of the RAPL energy counters, see the *_ENERGY_STATUS_MASK defines */ +#define RAPL_CNTR_WIDTH 32 + #define POWER_UNIT_OFFSET 0x00 #define POWER_UNIT_MASK GENMASK(3, 0) @@ -1227,6 +1230,7 @@ static u64 rapl_event_update(struct perf_event *event) struct rapl_package_pmu_data *data = event_to_pmu_data(event); u64 prev_raw_count, new_raw_count; s64 delta, sdelta; + int shift = 64 - RAPL_CNTR_WIDTH; /* * Follow the generic code to drain hwc->prev_count. @@ -1243,8 +1247,13 @@ static u64 rapl_event_update(struct perf_event *event) * Now we have the new raw value and have updated the prev * timestamp already. We can now calculate the elapsed delta * (event-)time and add that to the generic event. + * + * Careful, the counter is narrower than u64 and is not + * sign-extended above its physical width. Shift both values up + * so that the subtraction wraps, then shift the result back down. */ - delta = new_raw_count - prev_raw_count; + delta = (new_raw_count << shift) - (prev_raw_count << shift); + delta >>= shift; /* * Scale delta to smallest unit (2^-32) From 036322025d6e440cb75fc6fecbba9a16b271a2ae Mon Sep 17 00:00:00 2001 From: Mahanta Jambigi Date: Tue, 18 Aug 2026 09:31:07 +0200 Subject: [PATCH 0754/1328] net/smc: free stashed qentry before overwrite in REQ_ADD_LINK to ADD_LINK transition When smc_llc_event_handler() transitions the local LLC flow from SMC_LLC_FLOW_REQ_ADD_LINK to SMC_LLC_FLOW_ADD_LINK on arrival of an ADD_LINK request, it calls smc_llc_flow_qentry_set() unconditionally: if (lgr->llc_flow_lcl.type == SMC_LLC_FLOW_REQ_ADD_LINK) { lgr->llc_flow_lcl.type = SMC_LLC_FLOW_ADD_LINK; smc_llc_flow_qentry_set(&lgr->llc_flow_lcl, qentry); ... } A CONFIRM_LINK or ADD_LINK_CONT arriving while flow->type is SMC_LLC_FLOW_REQ_ADD_LINK is stashed into flow->qentry via the SMC_LLC_CONFIRM_LINK / SMC_LLC_ADD_LINK_CONT handler (which stores into flow->qentry for any non-NONE flow type). When the subsequent ADD_LINK arrives, the REQ_ADD_LINK branch overwrites flow->qentry with the new pointer without first freeing the stashed allocation, leaking one kmalloc object. The stashed entry has no consumer: smc_llc_wait() is only called from llc_add_link_work, which is not yet scheduled while the flow type remains REQ_ADD_LINK. No waiter is sleeping on llc_msg_waiter at this point. It is safe to unconditionally free any stashed qentry before the overwrite. Call smc_llc_flow_qentry_del() before smc_llc_flow_qentry_set() in the REQ_ADD_LINK branch. smc_llc_flow_qentry_del() already checks flow->qentry before freeing, so the normal path where no entry is stashed is a no-op. Fixes: b4ba4652b3f8 ("net/smc: extend LLC layer for SMC-Rv2") Reviewed-by: Hidayath Khan Signed-off-by: Mahanta Jambigi Link: https://patch.msgid.link/20260818073107.466506-1-mjambigi@linux.ibm.com Signed-off-by: Jakub Kicinski --- net/smc/smc_llc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/smc/smc_llc.c b/net/smc/smc_llc.c index aa6d83af55ed..c7f0222aff8d 100644 --- a/net/smc/smc_llc.c +++ b/net/smc/smc_llc.c @@ -1911,6 +1911,8 @@ static void smc_llc_event_handler(struct smc_llc_qentry *qentry) if (lgr->llc_flow_lcl.type == SMC_LLC_FLOW_REQ_ADD_LINK) { /* server started add_link processing */ + /* free any qentry stashed in REQ_ADD_LINK state */ + smc_llc_flow_qentry_del(&lgr->llc_flow_lcl); lgr->llc_flow_lcl.type = SMC_LLC_FLOW_ADD_LINK; smc_llc_flow_qentry_set(&lgr->llc_flow_lcl, qentry); From 5ee0ceddc7785c6dcf4a8107fef01f0414a354f4 Mon Sep 17 00:00:00 2001 From: Mahanta Jambigi Date: Tue, 18 Aug 2026 09:39:43 +0200 Subject: [PATCH 0755/1328] net/smc: free pending qentry in smc_llc_flow_stop() before memset smc_llc_flow_stop() resets a flow struct with a blind memset: spin_lock_bh(&lgr->llc_flow_lock); memset(flow, 0, sizeof(*flow)); flow->type = SMC_LLC_FLOW_NONE; spin_unlock_bh(&lgr->llc_flow_lock); If flow->qentry is non-NULL at this point the pointer is overwritten without the allocation being freed, leaking one kmalloc object. A late-arriving duplicate CONFIRM_LINK or ADD_LINK_CONT message can set flow->qentry after the legitimate message has been consumed by the waiter via smc_llc_flow_qentry_clr() (which NULLs the pointer but leaves flow->type non-zero) but before the flow completes and smc_llc_flow_stop() runs. In that window the duplicate is stashed into flow->qentry, and then lost when smc_llc_flow_stop() zeros the struct. Call smc_llc_flow_qentry_del() inside the lock before the memset. smc_llc_flow_qentry_del() already checks flow->qentry before freeing, so the normal case where no entry is pending is a no-op. Fixes: 555da9af827d ("net/smc: add event-based llc_flow framework") Reviewed-by: Hidayath Khan Signed-off-by: Mahanta Jambigi Link: https://patch.msgid.link/20260818073943.1108383-1-mjambigi@linux.ibm.com Signed-off-by: Jakub Kicinski --- net/smc/smc_llc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/smc/smc_llc.c b/net/smc/smc_llc.c index c7f0222aff8d..cef2c1df3ab9 100644 --- a/net/smc/smc_llc.c +++ b/net/smc/smc_llc.c @@ -320,6 +320,7 @@ int smc_llc_flow_initiate(struct smc_link_group *lgr, void smc_llc_flow_stop(struct smc_link_group *lgr, struct smc_llc_flow *flow) { spin_lock_bh(&lgr->llc_flow_lock); + smc_llc_flow_qentry_del(flow); memset(flow, 0, sizeof(*flow)); flow->type = SMC_LLC_FLOW_NONE; spin_unlock_bh(&lgr->llc_flow_lock); From cb258d651d747a7f7063d40f145418bee562ceaf Mon Sep 17 00:00:00 2001 From: Shibo Zhu <3499129952@qq.com> Date: Wed, 19 Aug 2026 00:18:39 +0800 Subject: [PATCH 0756/1328] PM: sleep: Unblock runtime PM when device prepare fails device_prepare() blocks runtime PM for a device with runtime PM disabled before invoking its system-sleep ->prepare() callback. For a device that has never enabled runtime PM, this changes dev->power.last_status from RPM_INVALID to RPM_BLOCKED. If the callback returns an error, dpm_prepare() does not move the device to dpm_prepared_list. Consequently, the recovery path through dpm_complete() never calls device_complete() for the failing device. The error path drops the runtime PM usage reference, but does not clear RPM_BLOCKED. A later legitimate pm_runtime_enable() then reports: Attempt to enable runtime PM when it is blocked before clearing the stale state. Call pm_runtime_unblock() on the prepare error path before dropping the runtime PM reference, matching the cleanup performed by device_complete(). The issue was reproduced with a platform test device whose ->prepare() callback returns -EIO while runtime PM has never been enabled. Before the fix, last_status remained RPM_BLOCKED after the failed suspend and the first pm_runtime_enable() produced the warning above. With the fix, last_status is restored to RPM_INVALID and the warning is absent. Fixes: 3e5eee147b7b ("PM: Block enabling of runtime PM during system suspend") Cc: All applicable Signed-off-by: Shibo Zhu <3499129952@qq.com> Link: https://patch.msgid.link/tencent_C5AC0A02FC01F700E764F8C2E3ECE4F41009@qq.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 184dc4b3b938..e130da428141 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -2248,6 +2248,7 @@ static int device_prepare(struct device *dev, pm_message_t state) if (ret < 0) { suspend_report_result(dev, callback, ret); + pm_runtime_unblock(dev); pm_runtime_put(dev); return ret; } From 57549ab9079122991dfa0f8248ca00e101e8e69b Mon Sep 17 00:00:00 2001 From: Nikolay Aleksandrov Date: Tue, 18 Aug 2026 18:07:55 +0300 Subject: [PATCH 0757/1328] net: bridge: arp/nd proxy: fix reading neigh ha Currently neigh ha address is read directly, but that can result in torn/partial reads if the neigh is being updated. Use neigh_ha_snapshot to take a stable snapshot of the address. Fixes: 057658cb33fb ("bridge: suppress arp pkts on BR_NEIGH_SUPPRESS ports") Fixes: ed842faeb2bd ("bridge: suppress nd pkts on BR_NEIGH_SUPPRESS ports") Signed-off-by: Nikolay Aleksandrov Reviewed-by: Petr Machata Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260818150756.890025-2-razor@blackwall.org Signed-off-by: Jakub Kicinski --- net/bridge/br_arp_nd_proxy.c | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/net/bridge/br_arp_nd_proxy.c b/net/bridge/br_arp_nd_proxy.c index b6e5a86b6a92..87a40e2f1c50 100644 --- a/net/bridge/br_arp_nd_proxy.c +++ b/net/bridge/br_arp_nd_proxy.c @@ -196,13 +196,15 @@ void br_do_proxy_suppress_arp(struct sk_buff *skb, struct net_bridge *br, n = neigh_lookup(&arp_tbl, &tip, vlandev); if (n) { struct net_bridge_fdb_entry *f; + u8 ha[ETH_ALEN] __aligned(2); if (!(READ_ONCE(n->nud_state) & NUD_VALID)) { neigh_release(n); return; } - f = br_fdb_find_rcu(br, n->ha, vid); + neigh_ha_snapshot(ha, n, n->dev); + f = br_fdb_find_rcu(br, ha, vid); if (f) { const struct net_bridge_port *dst = READ_ONCE(f->dst); bool replied = false; @@ -212,10 +214,10 @@ void br_do_proxy_suppress_arp(struct sk_buff *skb, struct net_bridge *br, br_is_neigh_suppress_enabled(dst, vid)) { if (!vid) br_arp_send(br, p, skb->dev, sip, tip, - sha, n->ha, sha, 0, 0); + sha, ha, sha, 0, 0); else br_arp_send(br, p, skb->dev, sip, tip, - sha, n->ha, sha, + sha, ha, sha, skb->vlan_proto, skb_vlan_tag_get(skb)); replied = true; @@ -250,7 +252,7 @@ struct nd_msg *br_is_nd_neigh_msg(struct sk_buff *skb) } static void br_nd_send(struct net_bridge *br, struct net_bridge_port *p, - struct sk_buff *request, struct neighbour *n, + struct sk_buff *request, struct neighbour *n, u8 *ha, __be16 vlan_proto, u16 vlan_tci) { struct net_device *dev = request->dev; @@ -309,7 +311,7 @@ static void br_nd_send(struct net_bridge *br, struct net_bridge_port *p, ipv6_eth_mc_map(&in6addr_linklocal_allnodes, eth_hdr(reply)->h_dest); else ether_addr_copy(eth_hdr(reply)->h_dest, daddr); - ether_addr_copy(eth_hdr(reply)->h_source, n->ha); + ether_addr_copy(eth_hdr(reply)->h_source, ha); eth_hdr(reply)->h_proto = htons(ETH_P_IPV6); reply->protocol = htons(ETH_P_IPV6); @@ -339,7 +341,7 @@ static void br_nd_send(struct net_bridge *br, struct net_bridge_port *p, na->icmph.icmp6_override = 1; na->icmph.icmp6_solicited = dad ? 0 : 1; na->target = ns->target; - ether_addr_copy(&na->opt[2], n->ha); + ether_addr_copy(&na->opt[2], ha); na->opt[0] = ND_OPT_TARGET_LL_ADDR; na->opt[1] = na_olen >> 3; @@ -368,7 +370,7 @@ static void br_nd_send(struct net_bridge *br, struct net_bridge_port *p, __vlan_hwaccel_put_tag(reply, vlan_proto, vlan_tci); netdev_dbg(dev, "nd send dev %s dst %pI6 dst_hw %pM src %pI6 src_hw %pM\n", - dev->name, &pip6->daddr, daddr, &pip6->saddr, n->ha); + dev->name, &pip6->daddr, daddr, &pip6->saddr, ha); if (p) { dev_queue_xmit(reply); @@ -471,24 +473,26 @@ void br_do_suppress_nd(struct sk_buff *skb, struct net_bridge *br, n = neigh_lookup(&nd_tbl, &msg->target, vlandev); if (n) { struct net_bridge_fdb_entry *f; + u8 ha[ETH_ALEN] __aligned(2); if (!(READ_ONCE(n->nud_state) & NUD_VALID)) { neigh_release(n); return; } - f = br_fdb_find_rcu(br, n->ha, vid); + neigh_ha_snapshot(ha, n, n->dev); + f = br_fdb_find_rcu(br, ha, vid); if (f) { const struct net_bridge_port *dst = READ_ONCE(f->dst); bool replied = false; if (br_is_neigh_suppress_enabled(dst, vid)) { if (vid != 0) - br_nd_send(br, p, skb, n, + br_nd_send(br, p, skb, n, ha, skb->vlan_proto, skb_vlan_tag_get(skb)); else - br_nd_send(br, p, skb, n, 0, 0); + br_nd_send(br, p, skb, n, ha, 0, 0); replied = true; } From b824059a673b2283e78c7aae2c7d257aad7f0e1d Mon Sep 17 00:00:00 2001 From: Nikolay Aleksandrov Date: Tue, 18 Aug 2026 18:07:56 +0300 Subject: [PATCH 0758/1328] vxlan: fix reading neigh ha Currently arp/neigh_reduce read neigh ha directly which can lead to partial reads while the neigh is being updated. Use neigh_ha_snapshot to take a stable snapshot of the address similar to route_shortcircuit which already does the right thing. Fixes: e4f67addf158 ("add DOVE extensions for VXLAN") Fixes: f564f45c4518 ("vxlan: add ipv6 proxy support") Signed-off-by: Nikolay Aleksandrov Reviewed-by: Petr Machata Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260818150756.890025-3-razor@blackwall.org Signed-off-by: Jakub Kicinski --- drivers/net/vxlan/vxlan_core.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c index ac88d1c85bea..459f19f7071e 100644 --- a/drivers/net/vxlan/vxlan_core.c +++ b/drivers/net/vxlan/vxlan_core.c @@ -1881,6 +1881,7 @@ static int arp_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni) if (n) { struct vxlan_rdst *rdst = NULL; + u8 ha[ETH_ALEN] __aligned(2); struct vxlan_fdb *f; struct sk_buff *reply; @@ -1889,8 +1890,10 @@ static int arp_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni) goto out; } + neigh_ha_snapshot(ha, n, n->dev); + rcu_read_lock(); - f = vxlan_find_mac_tx(vxlan, n->ha, vni); + f = vxlan_find_mac_tx(vxlan, ha, vni); if (f) rdst = first_remote_rcu(f); if (rdst && vxlan_addr_any(&rdst->remote_ip)) { @@ -1902,7 +1905,7 @@ static int arp_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni) rcu_read_unlock(); reply = arp_create(ARPOP_REPLY, ETH_P_ARP, sip, dev, tip, sha, - n->ha, sha); + ha, sha); neigh_release(n); @@ -1935,7 +1938,8 @@ static int arp_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni) #if IS_ENABLED(CONFIG_IPV6) static struct sk_buff *vxlan_na_create(struct sk_buff *request, - struct neighbour *n, bool isrouter) + struct neighbour *n, u8 *ha, + bool isrouter) { struct net_device *dev = request->dev; struct sk_buff *reply; @@ -1981,7 +1985,7 @@ static struct sk_buff *vxlan_na_create(struct sk_buff *request, /* Ethernet header */ ether_addr_copy(eth_hdr(reply)->h_dest, daddr); - ether_addr_copy(eth_hdr(reply)->h_source, n->ha); + ether_addr_copy(eth_hdr(reply)->h_source, ha); eth_hdr(reply)->h_proto = htons(ETH_P_IPV6); reply->protocol = htons(ETH_P_IPV6); @@ -2010,7 +2014,7 @@ static struct sk_buff *vxlan_na_create(struct sk_buff *request, na->icmph.icmp6_override = 1; na->icmph.icmp6_solicited = 1; na->target = ns->target; - ether_addr_copy(&na->opt[2], n->ha); + ether_addr_copy(&na->opt[2], ha); na->opt[0] = ND_OPT_TARGET_LL_ADDR; na->opt[1] = na_olen >> 3; @@ -2051,6 +2055,7 @@ static int neigh_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni) if (n) { struct vxlan_rdst *rdst = NULL; + u8 ha[ETH_ALEN] __aligned(2); struct vxlan_fdb *f; struct sk_buff *reply; @@ -2059,7 +2064,8 @@ static int neigh_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni) goto out; } - f = vxlan_find_mac_tx(vxlan, n->ha, vni); + neigh_ha_snapshot(ha, n, n->dev); + f = vxlan_find_mac_tx(vxlan, ha, vni); if (f) rdst = first_remote_rcu(f); if (rdst && vxlan_addr_any(&rdst->remote_ip)) { @@ -2068,7 +2074,7 @@ static int neigh_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni) goto out; } - reply = vxlan_na_create(skb, n, + reply = vxlan_na_create(skb, n, ha, !!(f ? f->flags & NTF_ROUTER : 0)); neigh_release(n); From 746fc0787f616da418ffc04a110296fe95d53491 Mon Sep 17 00:00:00 2001 From: Mehrdad Afshari Date: Sat, 15 Aug 2026 21:04:26 -0700 Subject: [PATCH 0759/1328] net: usb: cdc_ncm: add Apple MacBook Pro USB product ID 0x1902 The cdc_devs[] quirk table special-cases the Mac CDC-NCM private interface personality only for USB product ID 0x1905. Some MacBook Pro models (e.g. M1 Max) connected over a USB4/Thunderbolt 3/4 cable to a host whose Thunderbolt controller lacks PCIe tunneling support (no NHI function, USB4-only mode) present themselves with product ID 0x1902 instead, using the same descriptor layout as 0x1905: a Communications control interface with zero endpoints (no interrupt/status endpoint) paired with a CDC Data interface, at interface numbers 0 and 2. Because 0x1902 is unmatched, these devices fall through to the generic cdc_ncm_info driver_info, which sets FLAG_LINK_INTR and therefore requires an interrupt endpoint on the control interface. Apple's private NCM interface never provides one, so cdc_ncm_bind() fails outright: cdc_ncm 2-1:1.0: bind() failure cdc_ncm 2-1:1.2: bind() failure and no network device is created, breaking Ethernet-over-USB4 between the Mac and any USB4 host lacking Thunderbolt PCIe tunneling. Add matching entries for 0x1902 alongside the existing 0x1905 ones, reusing apple_private_interface_info as with the other Mac ID. Signed-off-by: Mehrdad Afshari Signed-off-by: Jakub Kicinski --- drivers/net/usb/cdc_ncm.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/usb/cdc_ncm.c b/drivers/net/usb/cdc_ncm.c index 0223a172851e..35db38cb3e4a 100644 --- a/drivers/net/usb/cdc_ncm.c +++ b/drivers/net/usb/cdc_ncm.c @@ -2013,6 +2013,12 @@ static const struct usb_device_id cdc_devs[] = { }, /* Mac */ + { USB_DEVICE_INTERFACE_NUMBER(0x05ac, 0x1902, 0), + .driver_info = (unsigned long)&apple_private_interface_info, + }, + { USB_DEVICE_INTERFACE_NUMBER(0x05ac, 0x1902, 2), + .driver_info = (unsigned long)&apple_private_interface_info, + }, { USB_DEVICE_INTERFACE_NUMBER(0x05ac, 0x1905, 0), .driver_info = (unsigned long)&apple_private_interface_info, }, From 8fa4804fe62ca4155a2d8fc2789d630376cbf2fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Jean?= Date: Thu, 20 Aug 2026 20:53:29 +0000 Subject: [PATCH 0760/1328] NFSv4.1: zero referring call lists before decoding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit decode_cb_sequence_args() allocates csa_rclists with kmalloc_objs(), so each referring_call_list starts uninitialized. decode_rc_list() assigns rcl_refcalls only when rcl_nrefcalls is nonzero. A valid list with zero referring calls therefore leaves the pointer uninitialized, and nfs4_callback_sequence() later passes stale slab contents to kfree(). Allocate csa_rclists with kzalloc_objs() so every rcl_refcalls member is NULL from the beginning, including valid empty referring call lists. Fixes: 4aece6a19cf7 ("nfs41: cb_sequence xdr implementation") Assisted-by: Codex:gpt-5 Signed-off-by: Jérémy Jean Signed-off-by: Trond Myklebust --- fs/nfs/callback_xdr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/nfs/callback_xdr.c b/fs/nfs/callback_xdr.c index 4382baddc9ee..a6406707ede6 100644 --- a/fs/nfs/callback_xdr.c +++ b/fs/nfs/callback_xdr.c @@ -417,7 +417,7 @@ static __be32 decode_cb_sequence_args(struct svc_rqst *rqstp, args->csa_nrclists = ntohl(*p++); args->csa_rclists = NULL; if (args->csa_nrclists) { - args->csa_rclists = kmalloc_objs(*args->csa_rclists, + args->csa_rclists = kzalloc_objs(*args->csa_rclists, args->csa_nrclists); if (unlikely(args->csa_rclists == NULL)) return htonl(NFS4ERR_RESOURCE); From 20358201777496fd0bb7b4336fcb4d3fc13cad28 Mon Sep 17 00:00:00 2001 From: Tim Menninger Date: Wed, 19 Aug 2026 12:41:21 +0000 Subject: [PATCH 0761/1328] pNFS: Fix EBUSY check in pnfs_layout_need_return Commit 41d0a8ead9720 ("NFSv4/pnfs: Add support for the PNFS_LAYOUT_FILE_BULK_RETURN flag") replaced pnfs_layout_segments_returnable() in pnfs_layout_need_return() with a direct call to pnfs_mark_layout_stateid_return(). The old helper checked the return value against -EBUSY, but the replacement compares against EBUSY. Since pnfs_mark_layout_stateid_return() returns negative errno values, the -EBUSY case is never detected. Fix the comparison in pnfs_layout_need_return() to check against -EBUSY. Fixes: 41d0a8ead9720 ("NFSv4/pnfs: Add support for the PNFS_LAYOUT_FILE_BULK_RETURN flag") Cc: stable@vger.kernel.org Signed-off-by: Tim Menninger Signed-off-by: Trond Myklebust --- fs/nfs/pnfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/nfs/pnfs.c b/fs/nfs/pnfs.c index 9a08fd076e0a..f0379dacf197 100644 --- a/fs/nfs/pnfs.c +++ b/fs/nfs/pnfs.c @@ -1390,7 +1390,7 @@ pnfs_layout_need_return(struct pnfs_layout_hdr *lo) return false; return pnfs_mark_layout_stateid_return(lo, &lo->plh_return_segs, lo->plh_return_iomode, - lo->plh_return_seq) != EBUSY; + lo->plh_return_seq) != -EBUSY; } static void pnfs_layoutreturn_before_put_layout_hdr(struct pnfs_layout_hdr *lo) From c75ef2137e749f2673f0617cfdaae53b2bb7195a Mon Sep 17 00:00:00 2001 From: Junrui Luo Date: Sun, 16 Aug 2026 16:01:27 +0800 Subject: [PATCH 0762/1328] NFSv4.2: fix LAYOUTSTATS send buffer exhaustion encode_layoutstats_maxsz budgets XDR_QUADLEN(PNFS_LAYOUTSTATS_MAXSIZE), i.e. 256 bytes, for the layoutupdate4 body written by the layout driver. The flexfiles record can exceed that. ff_layout_encode_ff_layoutupdate() emits, per data server, a netaddr4, an nfs_fh4, two ff_io_latency4, an nfstime4 and a bool. A data server whose filehandle is NFS_MAXFHSIZE bytes long already accounts for 132 of those bytes, and the two ff_io_latency4 at 64 bytes each, the nfstime4 and the bool add a further 144, so the body passes 256 bytes before the netaddr4 is encoded at all. encode_layoutstats() additionally writes the deviceid4 and the layoutupdate4 lou_type word, neither of which the macro accounts for. The filehandle and the address are both chosen by the server, through LAYOUTGET and GETDEVICEINFO, so it can drive the encoder past the end of the send buffer. xdr_reserve_space() returns NULL once that happens, and the two ff_layout_encode_io_latency() calls run with dss_info->mirror->lock held, so a NULL return there leaves the lock permanently held. Raise PNFS_LAYOUTSTATS_MAXSIZE to 384 so that the record fits inside the reservation. Fixes: 27c430644369 ("pnfs/flexfiles: encode LAYOUTSTATS flexfiles specific data") Reported-by: Yuhao Jiang Assisted-by: Claude:claude-opus-5 Signed-off-by: Junrui Luo Signed-off-by: Trond Myklebust --- include/linux/nfs_xdr.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/nfs_xdr.h b/include/linux/nfs_xdr.h index 2e1987ac403d..7ed8fdb930d6 100644 --- a/include/linux/nfs_xdr.h +++ b/include/linux/nfs_xdr.h @@ -360,7 +360,7 @@ struct nfs4_layoutreturn { struct nfs4_xdr_opaque_data ld_private; }; -#define PNFS_LAYOUTSTATS_MAXSIZE 256 +#define PNFS_LAYOUTSTATS_MAXSIZE 384 struct nfs42_layoutstat_args; struct nfs42_layoutstat_devinfo; From 13c23c5cb977f66390795437fd3837887ce1fd75 Mon Sep 17 00:00:00 2001 From: Junrui Luo Date: Sun, 16 Aug 2026 16:01:28 +0800 Subject: [PATCH 0763/1328] NFSv4/pnfs: key the data server cache on the NFS version nfs4_pnfs_ds_add() keys the per-net data server cache on the multipath address set alone, and struct nfs4_pnfs_ds records no version. That suffices for the files layout driver, which always connects with version 4, but flexfiles takes its version tuple from GETDEVICEINFO per device, and one address can legitimately serve both NFSv3 and NFSv4. Two deviceids on one address with different ds_versions[0].version therefore share a single nfs4_pnfs_ds, and whichever mirror connects first pins ds_clp to its own version. The other one is handed that client anyway, so it selects rpc_call_ops for a version the connection does not speak, and the mismatched sequence-slot handling dereferences NULL. Add the version to the cache key so the two cannot alias, giving each version its own nfs4_pnfs_ds and connection while both mirrors stay usable. Only the major version is compared, since that is what selects rpc_call_ops and rpc_ops; v4.0 and v4.1 keep sharing a client. The files layout driver passes the 4 it already hardcodes at connect time. Fixes: d67ae825a59d ("pnfs/flexfiles: Add the FlexFile Layout Driver") Reported-by: Yuhao Jiang Assisted-by: Claude:claude-opus-5 Signed-off-by: Junrui Luo Signed-off-by: Trond Myklebust --- fs/nfs/filelayout/filelayoutdev.c | 3 ++- fs/nfs/flexfilelayout/flexfilelayoutdev.c | 3 ++- fs/nfs/pnfs.h | 3 ++- fs/nfs/pnfs_nfs.c | 14 +++++++++----- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/fs/nfs/filelayout/filelayoutdev.c b/fs/nfs/filelayout/filelayoutdev.c index d06d303fdcc3..88bc79ec3459 100644 --- a/fs/nfs/filelayout/filelayoutdev.c +++ b/fs/nfs/filelayout/filelayoutdev.c @@ -170,7 +170,8 @@ nfs4_fl_alloc_deviceid_node(struct nfs_server *server, struct pnfs_device *pdev, goto out_err_free_deviceid; } - dsaddr->ds_list[i] = nfs4_pnfs_ds_add(net, &dsaddrs, gfp_flags); + dsaddr->ds_list[i] = nfs4_pnfs_ds_add(net, &dsaddrs, 4, + gfp_flags); if (!dsaddr->ds_list[i]) goto out_err_drain_dsaddrs; trace_fl_getdevinfo(server, &pdev->dev_id, dsaddr->ds_list[i]->ds_remotestr); diff --git a/fs/nfs/flexfilelayout/flexfilelayoutdev.c b/fs/nfs/flexfilelayout/flexfilelayoutdev.c index 8be5c730e101..5c0216bd5fce 100644 --- a/fs/nfs/flexfilelayout/flexfilelayoutdev.c +++ b/fs/nfs/flexfilelayout/flexfilelayoutdev.c @@ -153,7 +153,8 @@ nfs4_ff_alloc_deviceid_node(struct nfs_server *server, struct pnfs_device *pdev, new_ds->ds_versions = ds_versions; new_ds->ds_versions_cnt = version_count; - new_ds->ds = nfs4_pnfs_ds_add(net, &dsaddrs, gfp_flags); + new_ds->ds = nfs4_pnfs_ds_add(net, &dsaddrs, ds_versions[0].version, + gfp_flags); if (!new_ds->ds) goto out_err_drain_dsaddrs; diff --git a/fs/nfs/pnfs.h b/fs/nfs/pnfs.h index bab81f769636..70d20f779678 100644 --- a/fs/nfs/pnfs.h +++ b/fs/nfs/pnfs.h @@ -63,6 +63,7 @@ struct nfs4_pnfs_ds { const struct net *ds_net; struct nfs_client *ds_clp; refcount_t ds_count; + u32 ds_version; /* cache key, with ds_addrs */ unsigned long ds_state; #define NFS4DS_CONNECTING 0 /* ds is establishing connection */ }; @@ -417,7 +418,7 @@ void pnfs_generic_write_commit_done(struct rpc_task *task, void *data); void nfs4_pnfs_ds_put(struct nfs4_pnfs_ds *ds); struct nfs4_pnfs_ds *nfs4_pnfs_ds_add(const struct net *net, struct list_head *dsaddrs, - gfp_t gfp_flags); + u32 version, gfp_t gfp_flags); void nfs4_pnfs_v3_ds_connect_unload(void); int nfs4_pnfs_ds_connect(struct nfs_server *mds_srv, struct nfs4_pnfs_ds *ds, struct nfs4_deviceid_node *devid, unsigned int timeo, diff --git a/fs/nfs/pnfs_nfs.c b/fs/nfs/pnfs_nfs.c index b539e1a44d26..93d63f75a355 100644 --- a/fs/nfs/pnfs_nfs.c +++ b/fs/nfs/pnfs_nfs.c @@ -603,15 +603,17 @@ _same_data_server_addrs_locked(const struct list_head *dsaddrs1, } /* - * Lookup DS by addresses. nfs4_ds_cache_lock is held + * Lookup DS by addresses and NFS version. nfs4_ds_cache_lock is held */ static struct nfs4_pnfs_ds * -_data_server_lookup_locked(const struct nfs_net *nn, const struct list_head *dsaddrs) +_data_server_lookup_locked(const struct nfs_net *nn, + const struct list_head *dsaddrs, u32 version) { struct nfs4_pnfs_ds *ds; list_for_each_entry(ds, &nn->nfs4_data_server_cache, ds_node) - if (_same_data_server_addrs_locked(&ds->ds_addrs, dsaddrs)) + if (ds->ds_version == version && + _same_data_server_addrs_locked(&ds->ds_addrs, dsaddrs)) return ds; return NULL; } @@ -719,7 +721,8 @@ nfs4_pnfs_remotestr(struct list_head *dsaddrs, gfp_t gfp_flags) * uncached and return cached struct nfs4_pnfs_ds. */ struct nfs4_pnfs_ds * -nfs4_pnfs_ds_add(const struct net *net, struct list_head *dsaddrs, gfp_t gfp_flags) +nfs4_pnfs_ds_add(const struct net *net, struct list_head *dsaddrs, u32 version, + gfp_t gfp_flags) { struct nfs_net *nn = net_generic(net, nfs_net_id); struct nfs4_pnfs_ds *tmp_ds, *ds = NULL; @@ -738,7 +741,7 @@ nfs4_pnfs_ds_add(const struct net *net, struct list_head *dsaddrs, gfp_t gfp_fla remotestr = nfs4_pnfs_remotestr(dsaddrs, gfp_flags); spin_lock(&nn->nfs4_data_server_lock); - tmp_ds = _data_server_lookup_locked(nn, dsaddrs); + tmp_ds = _data_server_lookup_locked(nn, dsaddrs, version); if (tmp_ds == NULL) { INIT_LIST_HEAD(&ds->ds_addrs); list_splice_init(dsaddrs, &ds->ds_addrs); @@ -747,6 +750,7 @@ nfs4_pnfs_ds_add(const struct net *net, struct list_head *dsaddrs, gfp_t gfp_fla INIT_LIST_HEAD(&ds->ds_node); ds->ds_net = net; ds->ds_clp = NULL; + ds->ds_version = version; list_add(&ds->ds_node, &nn->nfs4_data_server_cache); dprintk("%s add new data server %s\n", __func__, ds->ds_remotestr); From ee5a386cfe60f3f8286de16a9db8e1a08f0bc124 Mon Sep 17 00:00:00 2001 From: Junrui Luo Date: Sun, 16 Aug 2026 16:01:29 +0800 Subject: [PATCH 0764/1328] NFSv4.1: fix layout segment leak on the pnfs_layout_process() forget path When the server returns a new layout stateid while a valid one is still held, pnfs_layout_process() calls pnfs_mark_matching_lsegs_return() on the on-stack free_me list and jumps to out_forget. Segments whose reference count drops to zero are unlinked from lo->plh_segs and moved to free_me by mark_lseg_invalid(); for an idle cached segment the layout header holds the only reference, so this happens on the first decrement. out_forget never drains free_me -- only the success path calls pnfs_free_lseg_list(). Commit 814b84971388 ("pNFS/NFSv4: Fix a layout segment leak in pnfs_layout_process()") added the drain; commit 08bd8dbe8882 ("pNFS/NFSv4: Try to return invalid layout in pnfs_layout_process()") removed it while switching the destination to lo->plh_return_segs, which is drained elsewhere. Commit fb700ef02676 ("NFSv4.1: Simplify layout return in pnfs_layout_process()") switched the destination back to free_me without restoring the drain. Restore the pnfs_free_lseg_list() call. Fixes: fb700ef02676 ("NFSv4.1: Simplify layout return in pnfs_layout_process()") Reported-by: Yuhao Jiang Assisted-by: Claude:claude-opus-5 Cc: stable@vger.kernel.org Signed-off-by: Junrui Luo Signed-off-by: Trond Myklebust --- fs/nfs/pnfs.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/nfs/pnfs.c b/fs/nfs/pnfs.c index f0379dacf197..4f9c0f639014 100644 --- a/fs/nfs/pnfs.c +++ b/fs/nfs/pnfs.c @@ -2638,6 +2638,7 @@ pnfs_layout_process(struct nfs4_layoutget *lgp) spin_unlock(&ino->i_lock); lseg->pls_layout = lo; NFS_SERVER(ino)->pnfs_curr_ld->free_lseg(lseg); + pnfs_free_lseg_list(&free_me); return ERR_PTR(-EAGAIN); } From 258c95150c26a30db1896eacd3bdef26314f2ca1 Mon Sep 17 00:00:00 2001 From: Daeho Jeong Date: Thu, 20 Aug 2026 09:43:40 -0700 Subject: [PATCH 0765/1328] f2fs: avoid setting SBI_NEED_FSCK on transient resize failure When free_segment_range() fails in f2fs_resize_fs(), no on-disk superblock or filesystem metadata has been modified yet, and free_segment_range() safely restores all in-memory counters before returning. However, the current error recovery path unconditionally sets the SBI_NEED_FSCK flag and prints a scary error message on any error, forcing an unnecessary and time-consuming fsck.f2fs repair on the subsequent mount/reboot. Fix this by separating the error recovery path with a dedicated recover_user_blocks label to bypass setting SBI_NEED_FSCK on free_segment_range() failures. Signed-off-by: Daeho Jeong Signed-off-by: Sunmin Jeong Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/gc.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c index c4da2f31805b..3215630c353d 100644 --- a/fs/f2fs/gc.c +++ b/fs/f2fs/gc.c @@ -2440,7 +2440,7 @@ int f2fs_resize_fs(struct file *filp, __u64 block_count) set_sbi_flag(sbi, SBI_IS_RESIZEFS); err = free_segment_range(sbi, secs, false); if (err) - goto recover_out; + goto recover_user_blocks; update_sb_metadata(sbi, -secs); @@ -2462,11 +2462,14 @@ int f2fs_resize_fs(struct file *filp, __u64 block_count) f2fs_commit_super(sbi, false); } recover_out: - clear_sbi_flag(sbi, SBI_IS_RESIZEFS); if (err) { + f2fs_bug_on(sbi, err == -EAGAIN); set_sbi_flag(sbi, SBI_NEED_FSCK); f2fs_err(sbi, "resize_fs failed, should run fsck to repair!"); - + } +recover_user_blocks: + clear_sbi_flag(sbi, SBI_IS_RESIZEFS); + if (err) { spin_lock(&sbi->stat_lock); sbi->user_block_count += shrunk_blocks; spin_unlock(&sbi->stat_lock); From 8ec06f50ddd8d201bd7e55b896ae28ed9d4cb7d1 Mon Sep 17 00:00:00 2001 From: Daeho Jeong Date: Wed, 19 Aug 2026 11:06:35 -0700 Subject: [PATCH 0766/1328] f2fs: fix to migrate all curseg types during free_segment_range In free_segment_range(), the curseg evacuation loop only iterates up to NR_CURSEG_PERSIST_TYPE (0..5), missing non-persistent in-memory curseg types such as CURSEG_COLD_DATA_PINNED and CURSEG_ALL_DATA_ATGC. Even though these in-memory curseg types are not saved in the on-disk checkpoint header, they still occupy active physical segments at runtime. If an active in-memory curseg happens to be allocated within the segment range being truncated during filesystem shrink, failing to evacuate it will cause subsequent writes to the curseg attempting out-of-bounds I/O on the truncated storage range. Fix this by expanding the curseg evacuation loop upper bound to NR_CURSEG_TYPE to ensure all active curseg types are safely migrated out of the target range. Fixes: d0b9e42ab615 ("f2fs: introduce inmem curseg") Cc: stable@vger.kernel.org Signed-off-by: Daeho Jeong Signed-off-by: Sunmin Jeong Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/gc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c index 3215630c353d..5917ee917d87 100644 --- a/fs/f2fs/gc.c +++ b/fs/f2fs/gc.c @@ -2240,7 +2240,7 @@ static int free_segment_range(struct f2fs_sb_info *sbi, f2fs_reset_gc_victim_resource(sbi, start, end); /* Move out cursegs from the target range */ - for (type = CURSEG_HOT_DATA; type < NR_CURSEG_PERSIST_TYPE; type++) { + for (type = CURSEG_HOT_DATA; type < NR_CURSEG_TYPE; type++) { err = f2fs_allocate_segment_for_resize(sbi, type, start, end); if (err) goto out; From 0f448bb3767ef6119f5cdeabcae3f10d6e75aed6 Mon Sep 17 00:00:00 2001 From: Zhan Xusheng Date: Tue, 18 Aug 2026 22:55:35 +0800 Subject: [PATCH 0767/1328] f2fs: fix i_size when pinned fallocate partially fails From: Zhan Xusheng Commit 4275b59673eb ("f2fs: fix to round down start offset of fallocate for pin file") moved the allocation loop's start down to a section boundary, but the error path still converts @expanded against @pg_start, which holds the unrounded start. @pg_start exists for that conversion: commit 88f2cfc5fa90 ("f2fs: fix to update last i_size if fallocate partially succeeds") added it as an immutable base because map.m_lblk moves every round. Each round now maps exactly sec_blks blocks starting from rounddown(pg_start, sec_blks), so pg_start + expanded overshoots the last allocated block by pg_start % sec_blks, and a partial failure leaves i_size covering a tail that was never allocated. Nothing corrects that afterwards either, since file_dont_truncate() has already cleared FADVISE_TRUNC_BIT. It needs a start offset that is not section aligned plus a fallocate that hits ENOSPC partway, so the error path runs with expanded > 0. On an 80 MiB image with 2 MiB sections: truncate -s 80M img mkfs.f2fs -s 1 -f img mount -o loop img /mnt touch /mnt/pinned f2fs_io pinfile set /mnt/pinned # 2093056 = block 511, so pg_start % sec_blks = 511 f2fs_io fallocate 0 2093056 536870912 /mnt/pinned stat -c %s /mnt/pinned filefrag -v /mnt/pinned The last extent ends at block 10737 either way. Before, i_size is 46075904, block 11249, so 511 blocks of it were never allocated, and filefrag does not mark the last extent eof. After, i_size is 43982848, block 10738, and eof is back. A kernel from before that commit also shows no overshoot. Keep @pg_start pointing at where allocation actually begins. Fixes: 4275b59673eb ("f2fs: fix to round down start offset of fallocate for pin file") Cc: stable@vger.kernel.org Signed-off-by: Zhan Xusheng Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/file.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index a54b3ab52f1a..d82be8c1502a 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -2001,8 +2001,9 @@ static int f2fs_expand_inode_data(struct inode *inode, loff_t offset, block_t sec_len; if (map.m_lblk % sec_blks) { - map.m_lblk = rounddown(map.m_lblk, sec_blks); - map.m_len = pg_end - map.m_lblk; + pg_start = rounddown(map.m_lblk, sec_blks); + map.m_lblk = pg_start; + map.m_len = pg_end - pg_start; if (off_end) map.m_len++; } From 9a9ee7408a1f8271bd1978baff2dd09457054ef4 Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Wed, 19 Aug 2026 09:31:07 +0800 Subject: [PATCH 0768/1328] f2fs: reduce memory footprint of ino management Currently, ino entries for APPEND_INO, UPDATE_INO, TRANS_DIR_INO, and XATTR_DIR_INO allocate a 'struct ino_entry' slab object and attach it to both a list and a radix tree solely for existence checks via f2fs_exist_written_data(). Since these ino types only track binary existence status, we can embed the information directly into radix tree value entries as a bitmap: - The Linux radix tree/XArray supports in-place value entries via xa_mk_value() / xa_to_value(), which tag the least significant bit to store an unallocated integer value of BITS_PER_XA_VALUE bits (BITS_PER_LONG - 1) directly in the slot pointer. - For each inode, (ino / BITS_PER_XA_VALUE) serves as the radix tree slot index, and (ino % BITS_PER_XA_VALUE) is used as the bit offset within the slot's bitmap. For example, when tracking ino = 7: - Before: Allocate a 'struct ino_entry' ({ .ino = 7 }), insert its pointer into the radix tree at index = 7, and link it to im->ino_list. - After: Compute slot_index = 7 / BITS_PER_XA_VALUE (index 0) and bit_offset = 7 % BITS_PER_XA_VALUE (bit 7), then set bit 7 in the value entry via xa_mk_value(bitmap) at index 0, without allocating a slab object or linking to a list. Additionally: - In-place slot updates are performed via radix_tree_replace_slot(), and slots are deleted with radix_tree_delete() once the bitmap is zeroed. - Reorder the ino list enum so ORPHAN_INO and FLUSH_INO (which still require struct ino_entry and list traversal) remain separated, while bitmap-based trees are torn down using xa_destroy(). This eliminates 'struct ino_entry' slab allocations and linked-list tracking for these ino types, significantly reducing memory consumption. Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/checkpoint.c | 99 ++++++++++++++++++++++++++++++++++++++++---- fs/f2fs/f2fs.h | 6 ++- 2 files changed, 95 insertions(+), 10 deletions(-) diff --git a/fs/f2fs/checkpoint.c b/fs/f2fs/checkpoint.c index eb2f955b1e2a..c73999e39a39 100644 --- a/fs/f2fs/checkpoint.c +++ b/fs/f2fs/checkpoint.c @@ -766,6 +766,64 @@ static void __remove_ino_entry(struct f2fs_sb_info *sbi, nid_t ino, int type) spin_unlock(&im->ino_lock); } +static void __set_ino_bitmap(struct f2fs_sb_info *sbi, nid_t ino, int type) +{ + struct inode_management *im = &sbi->im[type]; + unsigned long index = INO_SLOT_INDEX(ino); + unsigned int ofs = INO_BIT_OFFSET(ino); + void **slot, *entry; + unsigned long bitmap = 0; + int ret; + + ret = radix_tree_preload(GFP_NOFS | __GFP_NOFAIL); + f2fs_bug_on(sbi, ret); + + spin_lock(&im->ino_lock); + slot = radix_tree_lookup_slot(&im->ino_root, index); + if (slot) { + entry = radix_tree_deref_slot_protected(slot, &im->ino_lock); + bitmap = xa_to_value(entry); + if (!(bitmap & (1UL << ofs))) { + bitmap |= (1UL << ofs); + entry = xa_mk_value(bitmap); + radix_tree_replace_slot(&im->ino_root, slot, entry); + } + } else { + bitmap |= (1UL << ofs); + entry = xa_mk_value(bitmap); + if (unlikely(radix_tree_insert(&im->ino_root, index, entry))) + f2fs_bug_on(sbi, 1); + } + spin_unlock(&im->ino_lock); + radix_tree_preload_end(); +} + +static void __clear_ino_bitmap(struct f2fs_sb_info *sbi, nid_t ino, int type) +{ + struct inode_management *im = &sbi->im[type]; + unsigned long index = INO_SLOT_INDEX(ino); + unsigned int ofs = INO_BIT_OFFSET(ino); + void **slot, *entry; + unsigned long bitmap; + + spin_lock(&im->ino_lock); + slot = radix_tree_lookup_slot(&im->ino_root, index); + if (slot) { + entry = radix_tree_deref_slot_protected(slot, &im->ino_lock); + bitmap = xa_to_value(entry); + if (bitmap & (1UL << ofs)) + bitmap &= ~(1UL << ofs); + + if (bitmap) { + entry = xa_mk_value(bitmap); + radix_tree_replace_slot(&im->ino_root, slot, entry); + } else { + radix_tree_delete(&im->ino_root, index); + } + } + spin_unlock(&im->ino_lock); +} + static void f2fs_wait_for_inode_record(struct f2fs_sb_info *sbi, int mode) { if (mode != APPEND_INO && mode != UPDATE_INO) @@ -778,8 +836,10 @@ static void f2fs_wait_for_inode_record(struct f2fs_sb_info *sbi, int mode) static void __f2fs_add_ino_entry(struct f2fs_sb_info *sbi, nid_t ino, unsigned int devidx, int type) { - /* add new dirty ino entry into list */ - __add_ino_entry(sbi, ino, devidx, type); + if (type <= FLUSH_INO) + __add_ino_entry(sbi, ino, devidx, type); + else + __set_ino_bitmap(sbi, ino, type); } void f2fs_add_ino_entry(struct f2fs_sb_info *sbi, nid_t ino, int type) @@ -789,20 +849,33 @@ void f2fs_add_ino_entry(struct f2fs_sb_info *sbi, nid_t ino, int type) void f2fs_remove_ino_entry(struct f2fs_sb_info *sbi, nid_t ino, int type) { - /* remove dirty ino entry from list */ - __remove_ino_entry(sbi, ino, type); + if (type <= FLUSH_INO) + __remove_ino_entry(sbi, ino, type); + else + __clear_ino_bitmap(sbi, ino, type); } -/* mode should be APPEND_INO, UPDATE_INO or TRANS_DIR_INO */ +/* mode should be APPEND_INO, UPDATE_INO, TRANS_DIR_INO and XATTR_DIR_INO */ bool f2fs_exist_written_data(struct f2fs_sb_info *sbi, nid_t ino, int mode) { struct inode_management *im = &sbi->im[mode]; - struct ino_entry *e; + unsigned long index = INO_SLOT_INDEX(ino); + unsigned int ofs = INO_BIT_OFFSET(ino); + void *entry; + unsigned long bitmap; + + f2fs_bug_on(sbi, mode <= FLUSH_INO); spin_lock(&im->ino_lock); - e = radix_tree_lookup(&im->ino_root, ino); + entry = radix_tree_lookup(&im->ino_root, index); + if (!entry) { + spin_unlock(&im->ino_lock); + return false; + } + bitmap = xa_to_value(entry); spin_unlock(&im->ino_lock); - return e ? true : false; + + return bitmap & (1UL << ofs); } void f2fs_release_ino_entry(struct f2fs_sb_info *sbi, bool all) @@ -810,7 +883,7 @@ void f2fs_release_ino_entry(struct f2fs_sb_info *sbi, bool all) struct ino_entry *e, *tmp; int i; - for (i = all ? ORPHAN_INO : APPEND_INO; i < MAX_INO_ENTRY; i++) { + for (i = all ? ORPHAN_INO : FLUSH_INO; i <= FLUSH_INO; i++) { struct inode_management *im = &sbi->im[i]; f2fs_wait_for_inode_record(sbi, i); @@ -824,6 +897,14 @@ void f2fs_release_ino_entry(struct f2fs_sb_info *sbi, bool all) } spin_unlock(&im->ino_lock); } + + for (i = APPEND_INO; i < MAX_INO_ENTRY; i++) { + struct inode_management *im = &sbi->im[i]; + + spin_lock(&im->ino_lock); + xa_destroy(&im->ino_root); + spin_unlock(&im->ino_lock); + } } void f2fs_set_dirty_device(struct f2fs_sb_info *sbi, nid_t ino, diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index c925b9207a37..1b96d8718c5c 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -388,14 +388,18 @@ enum { /* for the list of ino */ enum { ORPHAN_INO, /* for orphan ino list */ + FLUSH_INO, /* for multiple device flushing */ APPEND_INO, /* for append ino list */ UPDATE_INO, /* for update ino list */ TRANS_DIR_INO, /* for transactions dir ino list */ XATTR_DIR_INO, /* for xattr updated dir ino list */ - FLUSH_INO, /* for multiple device flushing */ MAX_INO_ENTRY, /* max. list */ }; +#define INO_BITS_PER_SLOT BITS_PER_XA_VALUE +#define INO_SLOT_INDEX(ino) ((ino) / INO_BITS_PER_SLOT) +#define INO_BIT_OFFSET(ino) ((ino) % INO_BITS_PER_SLOT) + struct ino_entry { struct list_head list; /* list head */ nid_t ino; /* inode number */ From 78acddfde75177a27000f076e3e828743e38877e Mon Sep 17 00:00:00 2001 From: Cosmo Chou Date: Sat, 18 Jul 2026 03:37:05 +0800 Subject: [PATCH 0769/1328] rtc: pcf85363: Add error checking to regmap calls in probe() The probe() function ignores errors returned by regmap operations. If an I2C transport error occurs (e.g., -ENXIO), the driver continues probing and may register a non-functional RTC device. Propagate errors from all unchecked regmap calls in probe() using dev_err_probe(). Fixes: fd9a6a13949a ("rtc: pcf85363: add support for the quartz-load-femtofarads property") Signed-off-by: Cosmo Chou Link: https://lore.kernel.org/linux-rtc/20260716125142.1801599-1-chou.cosmo@gmail.com/ Link: https://patch.msgid.link/20260717193705.2003175-1-chou.cosmo@gmail.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-pcf85363.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/drivers/rtc/rtc-pcf85363.c b/drivers/rtc/rtc-pcf85363.c index 540042b9eec8..ccc7834e5759 100644 --- a/drivers/rtc/rtc-pcf85363.c +++ b/drivers/rtc/rtc-pcf85363.c @@ -426,8 +426,8 @@ static int pcf85363_probe(struct i2c_client *client) err = pcf85363_load_capacitance(pcf85363, client->dev.of_node); if (err < 0) - dev_warn(&client->dev, "failed to set xtal load capacitance: %d", - err); + return dev_err_probe(&client->dev, err, + "failed to set xtal load capacitance\n"); pcf85363->rtc->ops = &rtc_ops; pcf85363->rtc->range_min = RTC_TIMESTAMP_BEGIN_2000; @@ -436,9 +436,16 @@ static int pcf85363_probe(struct i2c_client *client) wakeup_source = device_property_read_bool(&client->dev, "wakeup-source"); if (client->irq > 0 || wakeup_source) { - regmap_write(pcf85363->regmap, CTRL_FLAGS, 0); - regmap_update_bits(pcf85363->regmap, CTRL_PIN_IO, - PIN_IO_INTAPM, PIN_IO_INTA_OUT); + err = regmap_write(pcf85363->regmap, CTRL_FLAGS, 0); + if (err) + return dev_err_probe(&client->dev, err, + "failed to clear flags\n"); + + err = regmap_update_bits(pcf85363->regmap, CTRL_PIN_IO, + PIN_IO_INTAPM, PIN_IO_INTA_OUT); + if (err) + return dev_err_probe(&client->dev, err, + "failed to set interrupt pin mode\n"); } if (client->irq > 0) { From 7617cc05df28dcae967cca109de74084321eaa62 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 20 Aug 2026 21:11:14 +0200 Subject: [PATCH 0770/1328] ACPI: scan: Do not combine resources that overlap completely Commit f234fdaae1ca ("ACPI: scan: Avoid registering platform devices with resource overlaps") attempted to avoid platform device registration errors due to overlaps of resources of the same type returned by the same _CRS object in the ACPI tables. It did that by combining two or more overlapping resources into one, but it went too far and also caused resources that overlap completely to be combined which broke the arm-cmn driver that expects two MMIO resources to be present for each device it binds to and it expects those two resources to overlap completely. Address this issue by adding checks for completely overlapping resources to acpi_platform_adjust_resources() and add a comment explaining what is done there. Fixes: f234fdaae1ca ("ACPI: scan: Avoid registering platform devices with resource overlaps") Reported-by: Nathan Chancellor Tested-by: Nathan Chancellor Closes: https://lore.kernel.org/linux-acpi/20260819003752.GA3063251@ax162/ Signed-off-by: Rafael J. Wysocki Reviewed-by: Jarkko Sakkinen Link: https://patch.msgid.link/12955564.O9o76ZdvQC@rafael.j.wysocki --- drivers/acpi/acpi_platform.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/acpi/acpi_platform.c b/drivers/acpi/acpi_platform.c index 373c94de7590..e3c77a65f61c 100644 --- a/drivers/acpi/acpi_platform.c +++ b/drivers/acpi/acpi_platform.c @@ -85,7 +85,13 @@ static unsigned int acpi_platform_adjust_resources(struct acpi_device *adev, for (i = 0; i < count; ) { struct resource *res = &resources[i]; - if (resource_type(new_res) != resource_type(res) || + /* + * Look for overlaps of resources of the same type that would + * cause resource insertion to fail down the road. + */ + if (__resource_contains_unbound(res, new_res) || + __resource_contains_unbound(new_res, res) || + resource_type(new_res) != resource_type(res) || !resource_union(new_res, res, new_res)) { i++; continue; From 53dd1c1de851280c71385f49a24715e5615c926c Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Fri, 21 Aug 2026 16:37:31 +0100 Subject: [PATCH 0771/1328] ASoC: tegra: Fix the I2S enable default value Commit 4b05ccb17f92 ("regcache: Sort the local copy of an unsorted reg_defaults array") exposed an issue in the Tegra I2S driver where the register default for the TEGRA210_I2S_ENABLE is specified as 1, but the hardware default is actually 0. After this commit was added the I2S driver is no longer working and so fix this by correcting the default value for this register and explicitly configuring the I2S_ENABLE register when runtime resuming the I2S device. The I2S_ENABLE register offset is different on Tegra264 devices than other Tegra devices and so add a 'enable_reg' variable to the SoC data structure to specify the offset for different SoC devices. Fixes: c0bfa98349d1 ("ASoC: tegra: Add Tegra210 based I2S driver") Cc: stable@vger.kernel.org Signed-off-by: Jon Hunter Link: https://patch.msgid.link/20260821153734.158426-2-jonathanh@nvidia.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra210_i2s.c | 20 +++++++++++++++++--- sound/soc/tegra/tegra210_i2s.h | 1 + 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/sound/soc/tegra/tegra210_i2s.c b/sound/soc/tegra/tegra210_i2s.c index 84506576437d..79a2f898ea28 100644 --- a/sound/soc/tegra/tegra210_i2s.c +++ b/sound/soc/tegra/tegra210_i2s.c @@ -23,7 +23,7 @@ static const struct reg_default tegra210_i2s_reg_defaults[] = { { TEGRA210_I2S_RX_CIF_CTRL, 0x00007700 }, { TEGRA210_I2S_TX_INT_MASK, 0x00000003 }, { TEGRA210_I2S_TX_CIF_CTRL, 0x00007700 }, - { TEGRA210_I2S_ENABLE, 0x1 }, + { TEGRA210_I2S_ENABLE, 0x0 }, { TEGRA210_I2S_CG, 0x1 }, { TEGRA210_I2S_TIMING, 0x0000001f }, /* @@ -42,7 +42,7 @@ static const struct reg_default tegra264_i2s_reg_defaults[] = { { TEGRA264_I2S_TX_INT_MASK, 0x00000003 }, { TEGRA264_I2S_TX_CIF_CTRL, 0x00003f00 }, { TEGRA264_I2S_TX_FIFO_RD_ACCESS_MODE, 0x1 }, - { TEGRA264_I2S_ENABLE, 0x1 }, + { TEGRA264_I2S_ENABLE, 0x0 }, { TEGRA264_I2S_CG, 0x1 }, { TEGRA264_I2S_TIMING, 0x0000001f }, }; @@ -201,9 +201,21 @@ static int tegra210_i2s_runtime_resume(struct device *dev) } regcache_cache_only(i2s->regmap, false); - regcache_sync(i2s->regmap); + err = regcache_sync(i2s->regmap); + if (err) + goto err; + + err = regmap_write(i2s->regmap, i2s->soc_data->enable_reg, I2S_EN); + if (err) + goto err; return 0; + +err: + regcache_cache_only(i2s->regmap, true); + clk_disable_unprepare(i2s->clk_i2s); + + return err; } static void tegra210_i2s_set_data_offset(struct tegra210_i2s *i2s, @@ -1133,6 +1145,7 @@ static const struct tegra_i2s_soc_data soc_data_tegra210 = { .regmap_conf = &tegra210_regmap_conf, .i2s_cmpnt = &tegra210_i2s_cmpnt, .max_ch = TEGRA210_I2S_MAX_CHANNEL, + .enable_reg = TEGRA210_I2S_ENABLE, .tx_offset = TEGRA210_I2S_TX_OFFSET, .i2s_ctrl_offset = TEGRA210_I2S_CTRL_OFFSET, .fsync_width_mask = I2S_CTRL_FSYNC_WIDTH_MASK, @@ -1144,6 +1157,7 @@ static const struct tegra_i2s_soc_data soc_data_tegra264 = { .regmap_conf = &tegra264_regmap_conf, .i2s_cmpnt = &tegra264_i2s_cmpnt, .max_ch = TEGRA264_I2S_MAX_CHANNEL, + .enable_reg = TEGRA264_I2S_ENABLE, .tx_offset = TEGRA264_I2S_TX_OFFSET, .i2s_ctrl_offset = TEGRA264_I2S_CTRL_OFFSET, .fsync_width_mask = TEGRA264_I2S_CTRL_FSYNC_WIDTH_MASK, diff --git a/sound/soc/tegra/tegra210_i2s.h b/sound/soc/tegra/tegra210_i2s.h index 42be2137342c..82292f96ab36 100644 --- a/sound/soc/tegra/tegra210_i2s.h +++ b/sound/soc/tegra/tegra210_i2s.h @@ -150,6 +150,7 @@ struct tegra_i2s_soc_data { const struct regmap_config *regmap_conf; const struct snd_soc_component_driver *i2s_cmpnt; unsigned int max_ch; + unsigned int enable_reg; unsigned int tx_offset; unsigned int i2s_ctrl_offset; unsigned int fsync_width_mask; From 5442b8093a2f94ecd4696b3875194be09e2676c5 Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Fri, 21 Aug 2026 16:37:32 +0100 Subject: [PATCH 0772/1328] ASoC: tegra: Fix the MIXER enable default value Commit 4b05ccb17f92 ("regcache: Sort the local copy of an unsorted reg_defaults array") exposed an issue in the Tegra MIXER driver where the register default for the TEGRA210_MIXER_ENABLE is specified as 1, but the hardware default is actually 0. After this commit was added the MIXER driver is no longer working and so fix this by correcting the default value for this register and explicitly configuring the MIXER_ENABLE register when runtime resuming the MIXER device. Fixes: 05bb3d5ec64a ("ASoC: tegra: Add Tegra210 based Mixer driver") Cc: stable@vger.kernel.org Signed-off-by: Jon Hunter Link: https://patch.msgid.link/20260821153734.158426-3-jonathanh@nvidia.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra210_mixer.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/sound/soc/tegra/tegra210_mixer.c b/sound/soc/tegra/tegra210_mixer.c index 8eb4e54b954b..96259a6cbc59 100644 --- a/sound/soc/tegra/tegra210_mixer.c +++ b/sound/soc/tegra/tegra210_mixer.c @@ -57,7 +57,7 @@ static const struct reg_default tegra210_mixer_reg_defaults[] = { MIXER_TX_REG_DEFAULTS(3), MIXER_TX_REG_DEFAULTS(4), - { TEGRA210_MIXER_ENABLE, 0x1 }, + { TEGRA210_MIXER_ENABLE, 0x0 }, { TEGRA210_MIXER_CG, 0x00000001}, { TEGRA210_MIXER_GAIN_CFG_RAM_CTRL, 0x00004000}, { TEGRA210_MIXER_PEAKM_RAM_CTRL, 0x00004000}, @@ -86,11 +86,15 @@ static int tegra210_mixer_runtime_suspend(struct device *dev) static int tegra210_mixer_runtime_resume(struct device *dev) { struct tegra210_mixer *mixer = dev_get_drvdata(dev); + int err; regcache_cache_only(mixer->regmap, false); - regcache_sync(mixer->regmap); + err = regcache_sync(mixer->regmap); + if (err) + return err; - return 0; + return regmap_write(mixer->regmap, TEGRA210_MIXER_ENABLE, + TEGRA210_MIXER_EN); } static int tegra210_mixer_write_ram(struct tegra210_mixer *mixer, From 18d48bfd1bc178da7cfc7a1be3eaa1c8cb10f05c Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Fri, 21 Aug 2026 16:37:33 +0100 Subject: [PATCH 0773/1328] ASoC: tegra: Sort ADMAIF register defaults Commit 4b05ccb17f92 ("regcache: Sort the local copy of an unsorted reg_defaults array") exposed an issue in the Tegra ADMAIF driver and now the following warning is observed: tegra210-admaif 290f000.admaif: Driver needs fixing: Unsorted reg_defaults, sorting the copy This warning occurs because register defaults in the structures tegra186_admaif_reg_defaults and tegra264_admaif_reg_defaults are not specified in ascending order which is required by regmap. Fix this by sorting the register defaults according to their address. Note that in order to do this it is necessary to split the macro ADMAIF_REG_DEFAULTS into separate RX and TX macros to the RX and TX registers. Fixes: f74028e159bb ("ASoC: tegra: Add Tegra210 based ADMAIF driver") Cc: stable@vger.kernel.org Signed-off-by: Jon Hunter Link: https://patch.msgid.link/20260821153734.158426-4-jonathanh@nvidia.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra210_admaif.c | 210 ++++++++++++++++++++---------- 1 file changed, 139 insertions(+), 71 deletions(-) diff --git a/sound/soc/tegra/tegra210_admaif.c b/sound/soc/tegra/tegra210_admaif.c index 7299c6bfcf15..87e898cc35e4 100644 --- a/sound/soc/tegra/tegra210_admaif.c +++ b/sound/soc/tegra/tegra210_admaif.c @@ -26,94 +26,162 @@ #define CH_RX_REG(reg, id) CH_REG(admaif->soc_data->rx_base, reg, id) -#define REG_DEFAULTS(id, rx_ctrl, tx_ctrl, tx_base, rx_base, cif_ctrl) \ +#define RX_REG_DEFAULTS(id, rx_ctrl, rx_base, cif_ctrl) \ { CH_REG(rx_base, TEGRA_ADMAIF_RX_INT_MASK, id), 0x00000001 }, \ { CH_REG(rx_base, TEGRA_ADMAIF_CH_ACIF_RX_CTRL, id), cif_ctrl }, \ - { CH_REG(rx_base, TEGRA_ADMAIF_RX_FIFO_CTRL, id), rx_ctrl }, \ + { CH_REG(rx_base, TEGRA_ADMAIF_RX_FIFO_CTRL, id), rx_ctrl } + +#define TX_REG_DEFAULTS(id, tx_ctrl, tx_base, cif_ctrl) \ { CH_REG(tx_base, TEGRA_ADMAIF_TX_INT_MASK, id), 0x00000001 }, \ { CH_REG(tx_base, TEGRA_ADMAIF_CH_ACIF_TX_CTRL, id), cif_ctrl }, \ { CH_REG(tx_base, TEGRA_ADMAIF_TX_FIFO_CTRL, id), tx_ctrl } -#define ADMAIF_REG_DEFAULTS(id, chip) \ - REG_DEFAULTS((id) - 1, \ +#define ADMAIF_RX_REG_DEFAULTS(id, chip) \ + RX_REG_DEFAULTS((id) - 1, \ chip ## _ADMAIF_RX ## id ## _FIFO_CTRL_REG_DEFAULT, \ - chip ## _ADMAIF_TX ## id ## _FIFO_CTRL_REG_DEFAULT, \ - chip ## _ADMAIF_TX_BASE, \ chip ## _ADMAIF_RX_BASE, \ chip ## _ADMAIF_CIF_REG_DEFAULT) +#define ADMAIF_TX_REG_DEFAULTS(id, chip) \ + TX_REG_DEFAULTS((id) - 1, \ + chip ## _ADMAIF_TX ## id ## _FIFO_CTRL_REG_DEFAULT, \ + chip ## _ADMAIF_TX_BASE, \ + chip ## _ADMAIF_CIF_REG_DEFAULT) + static const struct reg_default tegra186_admaif_reg_defaults[] = { - {(TEGRA_ADMAIF_GLOBAL_CG_0 + TEGRA186_ADMAIF_GLOBAL_BASE), 0x00000003}, - ADMAIF_REG_DEFAULTS(1, TEGRA186), - ADMAIF_REG_DEFAULTS(2, TEGRA186), - ADMAIF_REG_DEFAULTS(3, TEGRA186), - ADMAIF_REG_DEFAULTS(4, TEGRA186), - ADMAIF_REG_DEFAULTS(5, TEGRA186), - ADMAIF_REG_DEFAULTS(6, TEGRA186), - ADMAIF_REG_DEFAULTS(7, TEGRA186), - ADMAIF_REG_DEFAULTS(8, TEGRA186), - ADMAIF_REG_DEFAULTS(9, TEGRA186), - ADMAIF_REG_DEFAULTS(10, TEGRA186), - ADMAIF_REG_DEFAULTS(11, TEGRA186), - ADMAIF_REG_DEFAULTS(12, TEGRA186), - ADMAIF_REG_DEFAULTS(13, TEGRA186), - ADMAIF_REG_DEFAULTS(14, TEGRA186), - ADMAIF_REG_DEFAULTS(15, TEGRA186), - ADMAIF_REG_DEFAULTS(16, TEGRA186), - ADMAIF_REG_DEFAULTS(17, TEGRA186), - ADMAIF_REG_DEFAULTS(18, TEGRA186), - ADMAIF_REG_DEFAULTS(19, TEGRA186), - ADMAIF_REG_DEFAULTS(20, TEGRA186) + ADMAIF_RX_REG_DEFAULTS(1, TEGRA186), + ADMAIF_RX_REG_DEFAULTS(2, TEGRA186), + ADMAIF_RX_REG_DEFAULTS(3, TEGRA186), + ADMAIF_RX_REG_DEFAULTS(4, TEGRA186), + ADMAIF_RX_REG_DEFAULTS(5, TEGRA186), + ADMAIF_RX_REG_DEFAULTS(6, TEGRA186), + ADMAIF_RX_REG_DEFAULTS(7, TEGRA186), + ADMAIF_RX_REG_DEFAULTS(8, TEGRA186), + ADMAIF_RX_REG_DEFAULTS(9, TEGRA186), + ADMAIF_RX_REG_DEFAULTS(10, TEGRA186), + ADMAIF_RX_REG_DEFAULTS(11, TEGRA186), + ADMAIF_RX_REG_DEFAULTS(12, TEGRA186), + ADMAIF_RX_REG_DEFAULTS(13, TEGRA186), + ADMAIF_RX_REG_DEFAULTS(14, TEGRA186), + ADMAIF_RX_REG_DEFAULTS(15, TEGRA186), + ADMAIF_RX_REG_DEFAULTS(16, TEGRA186), + ADMAIF_RX_REG_DEFAULTS(17, TEGRA186), + ADMAIF_RX_REG_DEFAULTS(18, TEGRA186), + ADMAIF_RX_REG_DEFAULTS(19, TEGRA186), + ADMAIF_RX_REG_DEFAULTS(20, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(1, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(2, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(3, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(4, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(5, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(6, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(7, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(8, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(9, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(10, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(11, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(12, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(13, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(14, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(15, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(16, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(17, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(18, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(19, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(20, TEGRA186), + {(TEGRA_ADMAIF_GLOBAL_CG_0 + TEGRA186_ADMAIF_GLOBAL_BASE), 0x00000003} }; static const struct reg_default tegra210_admaif_reg_defaults[] = { - {(TEGRA_ADMAIF_GLOBAL_CG_0 + TEGRA210_ADMAIF_GLOBAL_BASE), 0x00000003}, - ADMAIF_REG_DEFAULTS(1, TEGRA210), - ADMAIF_REG_DEFAULTS(2, TEGRA210), - ADMAIF_REG_DEFAULTS(3, TEGRA210), - ADMAIF_REG_DEFAULTS(4, TEGRA210), - ADMAIF_REG_DEFAULTS(5, TEGRA210), - ADMAIF_REG_DEFAULTS(6, TEGRA210), - ADMAIF_REG_DEFAULTS(7, TEGRA210), - ADMAIF_REG_DEFAULTS(8, TEGRA210), - ADMAIF_REG_DEFAULTS(9, TEGRA210), - ADMAIF_REG_DEFAULTS(10, TEGRA210) + ADMAIF_RX_REG_DEFAULTS(1, TEGRA210), + ADMAIF_RX_REG_DEFAULTS(2, TEGRA210), + ADMAIF_RX_REG_DEFAULTS(3, TEGRA210), + ADMAIF_RX_REG_DEFAULTS(4, TEGRA210), + ADMAIF_RX_REG_DEFAULTS(5, TEGRA210), + ADMAIF_RX_REG_DEFAULTS(6, TEGRA210), + ADMAIF_RX_REG_DEFAULTS(7, TEGRA210), + ADMAIF_RX_REG_DEFAULTS(8, TEGRA210), + ADMAIF_RX_REG_DEFAULTS(9, TEGRA210), + ADMAIF_RX_REG_DEFAULTS(10, TEGRA210), + ADMAIF_TX_REG_DEFAULTS(1, TEGRA210), + ADMAIF_TX_REG_DEFAULTS(2, TEGRA210), + ADMAIF_TX_REG_DEFAULTS(3, TEGRA210), + ADMAIF_TX_REG_DEFAULTS(4, TEGRA210), + ADMAIF_TX_REG_DEFAULTS(5, TEGRA210), + ADMAIF_TX_REG_DEFAULTS(6, TEGRA210), + ADMAIF_TX_REG_DEFAULTS(7, TEGRA210), + ADMAIF_TX_REG_DEFAULTS(8, TEGRA210), + ADMAIF_TX_REG_DEFAULTS(9, TEGRA210), + ADMAIF_TX_REG_DEFAULTS(10, TEGRA210), + {(TEGRA_ADMAIF_GLOBAL_CG_0 + TEGRA210_ADMAIF_GLOBAL_BASE), 0x00000003} }; static const struct reg_default tegra264_admaif_reg_defaults[] = { - {(TEGRA_ADMAIF_GLOBAL_CG_0 + TEGRA264_ADMAIF_GLOBAL_BASE), 0x00000003}, - ADMAIF_REG_DEFAULTS(1, TEGRA264), - ADMAIF_REG_DEFAULTS(2, TEGRA264), - ADMAIF_REG_DEFAULTS(3, TEGRA264), - ADMAIF_REG_DEFAULTS(4, TEGRA264), - ADMAIF_REG_DEFAULTS(5, TEGRA264), - ADMAIF_REG_DEFAULTS(6, TEGRA264), - ADMAIF_REG_DEFAULTS(7, TEGRA264), - ADMAIF_REG_DEFAULTS(8, TEGRA264), - ADMAIF_REG_DEFAULTS(9, TEGRA264), - ADMAIF_REG_DEFAULTS(10, TEGRA264), - ADMAIF_REG_DEFAULTS(11, TEGRA264), - ADMAIF_REG_DEFAULTS(12, TEGRA264), - ADMAIF_REG_DEFAULTS(13, TEGRA264), - ADMAIF_REG_DEFAULTS(14, TEGRA264), - ADMAIF_REG_DEFAULTS(15, TEGRA264), - ADMAIF_REG_DEFAULTS(16, TEGRA264), - ADMAIF_REG_DEFAULTS(17, TEGRA264), - ADMAIF_REG_DEFAULTS(18, TEGRA264), - ADMAIF_REG_DEFAULTS(19, TEGRA264), - ADMAIF_REG_DEFAULTS(20, TEGRA264), - ADMAIF_REG_DEFAULTS(21, TEGRA264), - ADMAIF_REG_DEFAULTS(22, TEGRA264), - ADMAIF_REG_DEFAULTS(23, TEGRA264), - ADMAIF_REG_DEFAULTS(24, TEGRA264), - ADMAIF_REG_DEFAULTS(25, TEGRA264), - ADMAIF_REG_DEFAULTS(26, TEGRA264), - ADMAIF_REG_DEFAULTS(27, TEGRA264), - ADMAIF_REG_DEFAULTS(28, TEGRA264), - ADMAIF_REG_DEFAULTS(29, TEGRA264), - ADMAIF_REG_DEFAULTS(30, TEGRA264), - ADMAIF_REG_DEFAULTS(31, TEGRA264), - ADMAIF_REG_DEFAULTS(32, TEGRA264) + ADMAIF_RX_REG_DEFAULTS(1, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(2, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(3, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(4, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(5, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(6, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(7, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(8, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(9, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(10, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(11, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(12, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(13, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(14, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(15, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(16, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(17, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(18, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(19, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(20, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(21, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(22, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(23, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(24, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(25, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(26, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(27, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(28, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(29, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(30, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(31, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(32, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(1, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(2, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(3, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(4, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(5, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(6, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(7, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(8, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(9, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(10, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(11, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(12, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(13, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(14, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(15, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(16, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(17, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(18, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(19, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(20, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(21, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(22, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(23, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(24, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(25, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(26, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(27, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(28, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(29, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(30, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(31, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(32, TEGRA264), + {(TEGRA_ADMAIF_GLOBAL_CG_0 + TEGRA264_ADMAIF_GLOBAL_BASE), 0x00000003} }; static bool tegra_admaif_wr_reg(struct device *dev, unsigned int reg) From abb5bed6f48d54d3b82724a430fd462579ecd876 Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Fri, 21 Aug 2026 16:37:34 +0100 Subject: [PATCH 0774/1328] ASoC: tegra: Sort MBDRC register defaults Commit 4b05ccb17f92 ("regcache: Sort the local copy of an unsorted reg_defaults array") exposed an issue with the Tegra MBDRC driver and now the following warning is observed: tegra210-ope 2908000.processing-engine: Driver needs fixing: Unsorted reg_defaults, sorting the copy This warning occurs because register defaults in the structure tegra210_mbdrc_reg_defaults are not specified in ascending order which is required by regmap. Fix this by sorting the register defaults according to their address. Note that in order to do this it is necessary to replace the macro MBDRC_FILTER_REG_DEFAULTS with a per register macro MBDRC_FILTER_PARAM_REG_DEFAULTS. Fixes: 7358a803c778 ("ASoC: tegra: Add Tegra210 based OPE driver") Cc: stable@vger.kernel.org Signed-off-by: Jon Hunter Link: https://patch.msgid.link/20260821153734.158426-5-jonathanh@nvidia.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra210_mbdrc.c | 42 ++++++++++++++++---------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/sound/soc/tegra/tegra210_mbdrc.c b/sound/soc/tegra/tegra210_mbdrc.c index f5d4a93dd6f1..1d78b4c1cc88 100644 --- a/sound/soc/tegra/tegra210_mbdrc.c +++ b/sound/soc/tegra/tegra210_mbdrc.c @@ -20,33 +20,33 @@ #define MBDRC_FILTER_REG(reg, id) \ ((reg) + ((id) * TEGRA210_MBDRC_FILTER_PARAM_STRIDE)) -#define MBDRC_FILTER_REG_DEFAULTS(id) \ - { MBDRC_FILTER_REG(TEGRA210_MBDRC_IIR_CFG, id), 0x00000005}, \ - { MBDRC_FILTER_REG(TEGRA210_MBDRC_IN_ATTACK, id), 0x3e48590c}, \ - { MBDRC_FILTER_REG(TEGRA210_MBDRC_IN_RELEASE, id), 0x08414e9f}, \ - { MBDRC_FILTER_REG(TEGRA210_MBDRC_FAST_ATTACK, id), 0x7fffffff}, \ - { MBDRC_FILTER_REG(TEGRA210_MBDRC_IN_THRESHOLD, id), 0x06145082}, \ - { MBDRC_FILTER_REG(TEGRA210_MBDRC_OUT_THRESHOLD, id), 0x060d379b}, \ - { MBDRC_FILTER_REG(TEGRA210_MBDRC_RATIO_1ST, id), 0x0000a000}, \ - { MBDRC_FILTER_REG(TEGRA210_MBDRC_RATIO_2ND, id), 0x00002000}, \ - { MBDRC_FILTER_REG(TEGRA210_MBDRC_RATIO_3RD, id), 0x00000b33}, \ - { MBDRC_FILTER_REG(TEGRA210_MBDRC_RATIO_4TH, id), 0x00000800}, \ - { MBDRC_FILTER_REG(TEGRA210_MBDRC_RATIO_5TH, id), 0x0000019a}, \ - { MBDRC_FILTER_REG(TEGRA210_MBDRC_MAKEUP_GAIN, id), 0x00000002}, \ - { MBDRC_FILTER_REG(TEGRA210_MBDRC_INIT_GAIN, id), 0x00066666}, \ - { MBDRC_FILTER_REG(TEGRA210_MBDRC_GAIN_ATTACK, id), 0x00d9ba0e}, \ - { MBDRC_FILTER_REG(TEGRA210_MBDRC_GAIN_RELEASE, id), 0x3e48590c}, \ - { MBDRC_FILTER_REG(TEGRA210_MBDRC_FAST_RELEASE, id), 0x7ffff26a}, \ - { MBDRC_FILTER_REG(TEGRA210_MBDRC_CFG_RAM_CTRL, id), 0x4000} +#define MBDRC_FILTER_PARAM_REG_DEFAULTS(reg, val) \ + { MBDRC_FILTER_REG(reg, 0), val}, \ + { MBDRC_FILTER_REG(reg, 1), val}, \ + { MBDRC_FILTER_REG(reg, 2), val} static const struct reg_default tegra210_mbdrc_reg_defaults[] = { { TEGRA210_MBDRC_CFG, 0x0030de51}, { TEGRA210_MBDRC_CHANNEL_MASK, 0x00000003}, { TEGRA210_MBDRC_FAST_FACTOR, 0x30000800}, - MBDRC_FILTER_REG_DEFAULTS(0), - MBDRC_FILTER_REG_DEFAULTS(1), - MBDRC_FILTER_REG_DEFAULTS(2), + MBDRC_FILTER_PARAM_REG_DEFAULTS(TEGRA210_MBDRC_IIR_CFG, 0x00000005), + MBDRC_FILTER_PARAM_REG_DEFAULTS(TEGRA210_MBDRC_IN_ATTACK, 0x3e48590c), + MBDRC_FILTER_PARAM_REG_DEFAULTS(TEGRA210_MBDRC_IN_RELEASE, 0x08414e9f), + MBDRC_FILTER_PARAM_REG_DEFAULTS(TEGRA210_MBDRC_FAST_ATTACK, 0x7fffffff), + MBDRC_FILTER_PARAM_REG_DEFAULTS(TEGRA210_MBDRC_IN_THRESHOLD, 0x06145082), + MBDRC_FILTER_PARAM_REG_DEFAULTS(TEGRA210_MBDRC_OUT_THRESHOLD, 0x060d379b), + MBDRC_FILTER_PARAM_REG_DEFAULTS(TEGRA210_MBDRC_RATIO_1ST, 0x0000a000), + MBDRC_FILTER_PARAM_REG_DEFAULTS(TEGRA210_MBDRC_RATIO_2ND, 0x00002000), + MBDRC_FILTER_PARAM_REG_DEFAULTS(TEGRA210_MBDRC_RATIO_3RD, 0x00000b33), + MBDRC_FILTER_PARAM_REG_DEFAULTS(TEGRA210_MBDRC_RATIO_4TH, 0x00000800), + MBDRC_FILTER_PARAM_REG_DEFAULTS(TEGRA210_MBDRC_RATIO_5TH, 0x0000019a), + MBDRC_FILTER_PARAM_REG_DEFAULTS(TEGRA210_MBDRC_MAKEUP_GAIN, 0x00000002), + MBDRC_FILTER_PARAM_REG_DEFAULTS(TEGRA210_MBDRC_INIT_GAIN, 0x00066666), + MBDRC_FILTER_PARAM_REG_DEFAULTS(TEGRA210_MBDRC_GAIN_ATTACK, 0x00d9ba0e), + MBDRC_FILTER_PARAM_REG_DEFAULTS(TEGRA210_MBDRC_GAIN_RELEASE, 0x3e48590c), + MBDRC_FILTER_PARAM_REG_DEFAULTS(TEGRA210_MBDRC_FAST_RELEASE, 0x7ffff26a), + MBDRC_FILTER_PARAM_REG_DEFAULTS(TEGRA210_MBDRC_CFG_RAM_CTRL, 0x4000), }; /* Default MBDRC parameters */ From 4e15e89faac9f308baeb01f46c13a051814d2449 Mon Sep 17 00:00:00 2001 From: Joe Damato Date: Wed, 19 Aug 2026 16:32:11 -0700 Subject: [PATCH 0775/1328] net: bnxt: ring the doorbell when SW USO exits early When a burst of packets is handed down to the driver, the driver defers the doorbell to the end by setting txr->kick_pending = 1. The normal TX path handles this, but the SW USO path can miss it if it returns early. If bnxt_sw_udp_gso_xmit runs but returns early with NETDEV_TX_BUSY and txr->kick_pending was previously set to 1, then the TX queue can stall because the driver wrote some BDs but never wrote the doorbell. The device won't know to do the TX which would generate the completion that would wake the queue back up. Simplify bnxt_sw_udp_gso_xmit to set txr->kick_pending in its success case and check the flag on return. The added check after bnxt_sw_udp_gso_xmit returns ensures that any pending doorbells are written handling both successful USO and any early returns, which prevents the TX queue stall mentioned above. This TX queue stall was observed on a production system with a netdev TX watchdog informing about the queue stall. Fixes: cc5d90667db8 ("net: bnxt: Implement software USO") Cc: stable@vger.kernel.org Signed-off-by: Joe Damato Reviewed-by: Michael Chan Link: https://patch.msgid.link/20260819233213.3673149-1-joe@dama.to Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 10 ++++++++-- drivers/net/ethernet/broadcom/bnxt/bnxt_gso.c | 4 +--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index 9377bf675981..d3cb25abb632 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -485,6 +485,7 @@ static netdev_tx_t bnxt_start_xmit(struct sk_buff *skb, struct net_device *dev) struct bnxt_sw_tx_bd *tx_buf; __le32 lflags = 0; skb_frag_t *frag; + netdev_tx_t ret; i = skb_get_queue_mapping(skb); if (unlikely(i >= bp->tx_nr_rings)) { @@ -510,8 +511,13 @@ static netdev_tx_t bnxt_start_xmit(struct sk_buff *skb, struct net_device *dev) #endif if (skb_is_gso(skb) && (skb_shinfo(skb)->gso_type & SKB_GSO_UDP_L4) && - !(bp->flags & BNXT_FLAG_UDP_GSO_CAP)) - return bnxt_sw_udp_gso_xmit(bp, txr, txq, skb); + !(bp->flags & BNXT_FLAG_UDP_GSO_CAP)) { + ret = bnxt_sw_udp_gso_xmit(bp, txr, txq, skb); + if (txr->kick_pending) + bnxt_txr_db_kick(bp, txr, txr->tx_prod); + + return ret; + } free_size = bnxt_tx_avail(bp, txr); if (unlikely(free_size < skb_shinfo(skb)->nr_frags + 2)) { diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.c b/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.c index f317f60414e8..f7e18bea0fb8 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.c @@ -223,9 +223,7 @@ netdev_tx_t bnxt_sw_udp_gso_xmit(struct bnxt *bp, netdev_tx_sent_queue(txq, skb->len); WRITE_ONCE(txr->tx_prod, prod); - /* Sync BDs before doorbell */ - wmb(); - bnxt_db_write(bp, &txr->tx_db, prod); + txr->kick_pending = 1; if (unlikely(bnxt_tx_avail(bp, txr) <= bp->tx_wake_thresh)) netif_txq_try_stop(txq, bnxt_tx_avail(bp, txr), From 415125669c2ddc773c579a60df108f75712dfa83 Mon Sep 17 00:00:00 2001 From: Robin Everaars Date: Mon, 17 Aug 2026 14:14:17 +0000 Subject: [PATCH 0776/1328] ACPI: button: Add DMI quirk for Razer Blade Pro 17 early 2020 lid switch The lid switch reports "close" but can miss the matching "open", leaving _LID closed after resume. systemd-logind then suspends the system again roughly every 35 seconds. Reading the embedded controller's PSTA byte while _LID is stale shows that bit 0x04 is set, which the DSDT treats as open. The DSDT returns the cached LIDS byte from _LID. Its wake path aborts in RTEC on an unhandled SystemCMOS region before copying PSTA to LIDS. Initialize the lid state to open on resume, matching the existing quirk for the Razer Blade Stealth 13 late 2019. With button.lid_init_state=open, a physical close suspended once and resume reported open without another suspend. Signed-off-by: Robin Everaars Link: https://patch.msgid.link/20260817141414.213075-1-robineveraars@pm.me Signed-off-by: Rafael J. Wysocki --- drivers/acpi/button.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/acpi/button.c b/drivers/acpi/button.c index 3836ee75dd66..cdbb1023a8ee 100644 --- a/drivers/acpi/button.c +++ b/drivers/acpi/button.c @@ -133,6 +133,17 @@ static const struct dmi_system_id dmi_lid_quirks[] = { }, .driver_data = (void *)(long)ACPI_BUTTON_LID_INIT_OPEN, }, + { + /* + * Razer Blade Pro 17 early 2020, notification of the LID device + * only happens on close, not on open and _LID keeps returning closed. + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Razer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Blade Pro 17 (Early 2020) - RZ09-0329"), + }, + .driver_data = (void *)(long)ACPI_BUTTON_LID_INIT_OPEN, + }, { /* * Samsung galaxybook2 ,initial _LID device notification returns From 9d4843ac81d15be27e0b03392a99a8bd88a2d86a Mon Sep 17 00:00:00 2001 From: Niranjan H Y Date: Thu, 20 Aug 2026 17:21:12 +0530 Subject: [PATCH 0777/1328] ASoC: sdw_utils: Set component_name for tac5xx2 codec family * With component_name set in the codec_info_list DAI entries, "amixer -D hw: info" includes the 'spk' identifier for each tac5xx2 speaker component. This allows userspace to identify the speaker codec at runtime and enables UCM card matching for the tac5xx2 family. * Set component_name for the speaker, mic, and UAJ DAI entries for all tac5xx2 family variants. Signed-off-by: Niranjan H Y Link: https://patch.msgid.link/20260820115114.1203-1-niranjan.hy@ti.com Signed-off-by: Mark Brown --- sound/soc/sdw_utils/soc_sdw_utils.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c index 3e091e78d8c2..a66dcc02fb59 100644 --- a/sound/soc/sdw_utils/soc_sdw_utils.c +++ b/sound/soc/sdw_utils/soc_sdw_utils.c @@ -81,6 +81,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { /* speaker */ .direction = {true, false}, .dai_name = "tac5xx2-aif1", + .component_name = "tac5572", .dai_type = SOC_SDW_DAI_TYPE_AMP, .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, .init = asoc_sdw_ti_amp_init, @@ -94,6 +95,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { /* mic */ .direction = {false, true}, .dai_name = "tac5xx2-aif2", + .component_name = "tac5572", .dai_type = SOC_SDW_DAI_TYPE_MIC, .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, .rtd_init = asoc_sdw_ti_dmic_rtd_init, @@ -102,6 +104,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { /* UAJ */ .direction = {true, true}, .dai_name = "tac5xx2-aif3", + .component_name = "tac5572", .dai_type = SOC_SDW_DAI_TYPE_JACK, .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, .controls = generic_jack_controls, @@ -122,6 +125,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { /* speaker with IV sense feedback */ .direction = {true, true}, .dai_name = "tac5xx2-aif1", + .component_name = "tac5672", .dai_type = SOC_SDW_DAI_TYPE_AMP, .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, .init = asoc_sdw_ti_amp_init, @@ -135,6 +139,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { /* mic */ .direction = {false, true}, .dai_name = "tac5xx2-aif2", + .component_name = "tac5672", .dai_type = SOC_SDW_DAI_TYPE_MIC, .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, .rtd_init = asoc_sdw_ti_dmic_rtd_init, @@ -143,6 +148,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { /* UAJ */ .direction = {true, true}, .dai_name = "tac5xx2-aif3", + .component_name = "tac5672", .dai_type = SOC_SDW_DAI_TYPE_JACK, .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, .controls = generic_jack_controls, @@ -163,6 +169,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { /* speaker with echo reference feedback */ .direction = {true, true}, .dai_name = "tac5xx2-aif1", + .component_name = "tac5682", .dai_type = SOC_SDW_DAI_TYPE_AMP, .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, .init = asoc_sdw_ti_amp_init, @@ -176,6 +183,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { /* mic */ .direction = {false, true}, .dai_name = "tac5xx2-aif2", + .component_name = "tac5682", .dai_type = SOC_SDW_DAI_TYPE_MIC, .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, .rtd_init = asoc_sdw_ti_dmic_rtd_init, @@ -184,6 +192,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { /* UAJ */ .direction = {true, true}, .dai_name = "tac5xx2-aif3", + .component_name = "tac5682", .dai_type = SOC_SDW_DAI_TYPE_JACK, .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, .controls = generic_jack_controls, @@ -203,6 +212,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { { .direction = {true, false}, .dai_name = "tac5xx2-aif1", + .component_name = "tas2883", .dai_type = SOC_SDW_DAI_TYPE_AMP, .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, .init = asoc_sdw_ti_amp_init, @@ -216,6 +226,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { /* mic */ .direction = {false, true}, .dai_name = "tac5xx2-aif2", + .component_name = "tas2883", .dai_type = SOC_SDW_DAI_TYPE_MIC, .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, .rtd_init = asoc_sdw_ti_dmic_rtd_init, From 04145998fa20f321fd7090089c7f86dba3d36a13 Mon Sep 17 00:00:00 2001 From: Niranjan H Y Date: Thu, 20 Aug 2026 17:21:13 +0530 Subject: [PATCH 0778/1328] ASoC: tac5xx2-sdw: Rename gain and volume macros Rename gain and volume macros to match the hardware intent. Currently, both TAC_SDCA_CHANNEL_GAIN and TAC_SDCA_CHANNEL_VOLUME are defined as 0x02. Keep only TAC_SDCA_CHANNEL_VOLUME and reposition TAC_SDCA_CHANNEL_GAIN to 0x0B. Remove the now-redundant TAC_SDCA_MASTER_GAIN alias (also 0x0B), replacing its single use with TAC_SDCA_CHANNEL_GAIN. Signed-off-by: Niranjan H Y Link: https://patch.msgid.link/20260820115114.1203-2-niranjan.hy@ti.com Signed-off-by: Mark Brown --- sound/soc/codecs/tac5xx2-sdw.c | 48 +++++++++++++++++----------------- sound/soc/codecs/tac5xx2.h | 5 ++-- 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/sound/soc/codecs/tac5xx2-sdw.c b/sound/soc/codecs/tac5xx2-sdw.c index c062065a807d..065e7d456240 100644 --- a/sound/soc/codecs/tac5xx2-sdw.c +++ b/sound/soc/codecs/tac5xx2-sdw.c @@ -123,9 +123,9 @@ static const DECLARE_TLV_DB_SCALE(tac5xx2_dvc_tlv, -7200, 50, 0); #define TAC_DOUBLE_Q78_TLV(name, func_id, ent_id) \ SDCA_DOUBLE_Q78_TLV(name, \ SDW_SDCA_CTL(TAC_FUNCTION_ID_##func_id, TAC_SDCA_ENT_##ent_id, \ - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), \ + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT), \ SDW_SDCA_CTL(TAC_FUNCTION_ID_##func_id, TAC_SDCA_ENT_##ent_id, \ - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), \ + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT), \ TAC_DVC_MIN, TAC_DVC_MAX, TAC_DVC_STEP, tac5xx2_dvc_tlv) struct tac5xx2_prv { @@ -385,33 +385,33 @@ static const struct reg_default tac_reg_default[] = { static const struct reg_sequence tac_spk_seq[] = { REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU21, - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), 0), + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT), 0), REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU21, - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), 0), + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT), 0), REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23, - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), 0), + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT), 0), REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23, - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), 0), + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT), 0), }; static const struct reg_sequence tac_sm_seq[] = { REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU113, - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), 0), + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT), 0), REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU113, - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), 0), + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT), 0), REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU11, - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), 0), + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT), 0), REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU11, - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), 0), + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT), 0), }; static const struct reg_sequence tac_uaj_seq[] = { REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU41, - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), 0), + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT), 0), REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU41, - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), 0), + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT), 0), REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU36, - TAC_SDCA_CHANNEL_GAIN, TAC_JACK_MONO_CS), 0), + TAC_SDCA_CHANNEL_VOLUME, TAC_JACK_MONO_CS), 0), }; static bool tac_volatile_reg(struct device *dev, unsigned int reg) @@ -468,25 +468,25 @@ static int tac_sdca_mbq_size(struct device *dev, unsigned int reg) case SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU21, TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT): case SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23, - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT): + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT): case SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23, - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT): + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT): case SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23, - TAC_SDCA_MASTER_GAIN, 0): + TAC_SDCA_CHANNEL_GAIN, 0): case SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU113, - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT): + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT): case SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU113, - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT): + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT): case SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU11, - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT): + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT): case SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU11, - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT): + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT): case SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU41, - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT): + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT): case SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU41, - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT): + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT): case SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU36, - TAC_SDCA_CHANNEL_GAIN, TAC_JACK_MONO_CS): + TAC_SDCA_CHANNEL_VOLUME, TAC_JACK_MONO_CS): return 2; default: @@ -531,7 +531,7 @@ static const struct snd_kcontrol_new tac_uaj_controls[] = { TAC_DOUBLE_Q78_TLV("UAJ Playback Volume", UAJ, FU41), SDCA_SINGLE_Q78_TLV("UAJ Capture Volume", SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU36, - TAC_SDCA_CHANNEL_GAIN, TAC_JACK_MONO_CS), + TAC_SDCA_CHANNEL_VOLUME, TAC_JACK_MONO_CS), TAC_DVC_MIN, TAC_DVC_MAX, TAC_DVC_STEP, tac5xx2_dvc_tlv), }; diff --git a/sound/soc/codecs/tac5xx2.h b/sound/soc/codecs/tac5xx2.h index eed8e6cf3498..a84c5ca515a7 100644 --- a/sound/soc/codecs/tac5xx2.h +++ b/sound/soc/codecs/tac5xx2.h @@ -174,14 +174,13 @@ #define TAC_SDCA_ENT_SAPU29 0x35 /* Control selector definitions */ -#define TAC_SDCA_MASTER_GAIN 0x0B #define TAC_SDCA_MASTER_MUTE 0x01 #define TAC_SDCA_CHANNEL_MUTE 0x01 -#define TAC_SDCA_CHANNEL_GAIN 0x02 +#define TAC_SDCA_CHANNEL_VOLUME 0x02 #define TAC_SDCA_POSTURENUMBER 0x10 #define TAC_SDCA_REQUESTED_PS 0x01 #define TAC_SDCA_ACTUAL_PS 0x10 -#define TAC_SDCA_CHANNEL_VOLUME 0x02 +#define TAC_SDCA_CHANNEL_GAIN 0x0B /* 2. smart mic function */ #define TAC_FUNCTION_ID_SM 0x2 From cf82dd2183cb0c224816f7d23d32a7a05be4858b Mon Sep 17 00:00:00 2001 From: Niranjan H Y Date: Thu, 20 Aug 2026 17:21:14 +0530 Subject: [PATCH 0779/1328] ASoC: tac5xx2-sdw: update default regmap registers * At boot time, when the registers are accessed by asoc layer while trying to enable a DAPM path, while the device is still being attached, -EBUSY error will be thown by regmap_read and regmap_udpate_bits. Because the regmap will be in cache only mode. Add default values for these registers so that, the cached values are returned instead of error. * Also remove writing default values once again for first attach. Signed-off-by: Niranjan H Y Link: https://patch.msgid.link/20260820115114.1203-3-niranjan.hy@ti.com Signed-off-by: Mark Brown --- sound/soc/codecs/tac5xx2-sdw.c | 88 ++++++++++++++++++---------------- 1 file changed, 46 insertions(+), 42 deletions(-) diff --git a/sound/soc/codecs/tac5xx2-sdw.c b/sound/soc/codecs/tac5xx2-sdw.c index 065e7d456240..a5f654cde69f 100644 --- a/sound/soc/codecs/tac5xx2-sdw.c +++ b/sound/soc/codecs/tac5xx2-sdw.c @@ -55,6 +55,12 @@ #define TAC_XU_BYPASS_REG(func, xu) \ SDW_SDCA_CTL(TAC_FUNCTION_ID_##func, TAC_SDCA_ENT_##xu, \ TAC_SDCA_CTL_XU_BYPASS, 0) +#define TAC_VOLUME_REG(func, fu, ch) \ + SDW_SDCA_CTL(TAC_FUNCTION_ID_##func, TAC_SDCA_ENT_##fu, \ + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_##ch) +#define TAC_GAIN_REG(func, fu, ch) \ + SDW_SDCA_CTL(TAC_FUNCTION_ID_##func, TAC_SDCA_ENT_##fu, \ + TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_##ch) /* mute registers */ #define FU21_L_MUTE_REG TAC_MUTE_REG(SA, FU21, LEFT) @@ -381,6 +387,46 @@ static const struct reg_default tac_reg_default[] = { {TAC_REG_SDW(0, 1, 0x71), 0x5}, {TAC_REG_SDW(0, 1, 0x72), 0x6}, {TAC_REG_SDW(0, 1, 0x73), 0x7}, + /* SA */ + {TAC_MUTE_REG(SA, FU21, LEFT), 0x1}, + {TAC_MUTE_REG(SA, FU21, RIGHT), 0x1}, + {TAC_VOLUME_REG(SA, FU21, LEFT), 0x9c00}, + {TAC_VOLUME_REG(SA, FU21, RIGHT), 0x9c00}, + {TAC_MUTE_REG(SA, FU23, LEFT), 0x1}, + {TAC_MUTE_REG(SA, FU23, RIGHT), 0x1}, + {TAC_GAIN_REG(SA, FU23, LEFT), 0x0}, + {TAC_GAIN_REG(SA, FU23, RIGHT), 0x0}, + /* SM */ + {TAC_USAGE_REG(SM, IT11), 0x0}, + {TAC_USAGE_REG(SM, OT113), 0x0}, + {TAC_MUTE_REG(SM, FU113, LEFT), 0x1}, + {TAC_MUTE_REG(SM, FU113, RIGHT), 0x1}, + {TAC_GAIN_REG(SM, FU113, LEFT), 0x0}, + {TAC_GAIN_REG(SM, FU113, RIGHT), 0x0}, + {TAC_MUTE_REG(SM, FU11, LEFT), 0x1}, + {TAC_MUTE_REG(SM, FU11, RIGHT), 0x1}, + {TAC_GAIN_REG(SM, FU11, LEFT), 0x0}, + {TAC_GAIN_REG(SM, FU11, RIGHT), 0x0}, + {TAC_XU_BYPASS_REG(SM, XU12), 0x1}, + {SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_CS113, + TAC_SDCA_CTL_CS_SAMP_RATE_IDX, 0), 0x0}, + /* UAJ */ + {TAC_USAGE_REG(UAJ, IT33), 0x0}, + {TAC_USAGE_REG(UAJ, IT41), 0x0}, + {TAC_USAGE_REG(UAJ, OT36), 0x0}, + {TAC_USAGE_REG(UAJ, OT45), 0x0}, + {TAC_MUTE_REG(UAJ, FU41, LEFT), 0x1}, + {TAC_MUTE_REG(UAJ, FU41, RIGHT), 0x1}, + {TAC_VOLUME_REG(UAJ, FU41, LEFT), 0x0}, + {TAC_VOLUME_REG(UAJ, FU41, RIGHT), 0x0}, + {TAC_MUTE_REG(UAJ, FU36, RIGHT), 0x1}, + {SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU36, + TAC_SDCA_CHANNEL_VOLUME, TAC_JACK_MONO_CS), 0x0}, + {TAC_XU_BYPASS_REG(UAJ, XU42), 0x0}, + {SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_CS36, + TAC_SDCA_CTL_CS_SAMP_RATE_IDX, 0), 0x0}, + {SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_CS41, + TAC_SDCA_CTL_CS_SAMP_RATE_IDX, 0), 0x0}, }; static const struct reg_sequence tac_spk_seq[] = { @@ -388,30 +434,6 @@ static const struct reg_sequence tac_spk_seq[] = { TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT), 0), REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU21, TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT), 0), - REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23, - TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT), 0), - REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23, - TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT), 0), -}; - -static const struct reg_sequence tac_sm_seq[] = { - REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU113, - TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT), 0), - REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU113, - TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT), 0), - REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU11, - TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT), 0), - REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU11, - TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT), 0), -}; - -static const struct reg_sequence tac_uaj_seq[] = { - REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU41, - TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT), 0), - REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU41, - TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT), 0), - REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU36, - TAC_SDCA_CHANNEL_VOLUME, TAC_JACK_MONO_CS), 0), }; static bool tac_volatile_reg(struct device *dev, unsigned int reg) @@ -1761,16 +1783,6 @@ static int tac_io_init(struct device *dev, struct sdw_slave *slave, bool first) goto io_init_err; } dev_dbg(dev, "smartmic init done\n"); - - if (first) { - ret = regmap_multi_reg_write(tac_dev->regmap, tac_sm_seq, - ARRAY_SIZE(tac_sm_seq)); - if (ret) { - dev_err(tac_dev->dev, - "init writes failed, err=%d", ret); - goto io_init_err; - } - } } if (tac_dev->uaj_func_data) { @@ -1783,14 +1795,6 @@ static int tac_io_init(struct device *dev, struct sdw_slave *slave, bool first) dev_dbg(dev, "uaj init done\n"); if (first) { - ret = regmap_multi_reg_write(tac_dev->regmap, tac_uaj_seq, - ARRAY_SIZE(tac_uaj_seq)); - if (ret) { - dev_err(tac_dev->dev, - "init writes failed, err=%d", ret); - goto io_init_err; - } - if (tac_dev->hs_jack) { ret = tac5xx2_jack_init(tac_dev); if (ret) { From 72cc574aa3791aa4f05d247b4e884cdbd135819a Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Fri, 21 Aug 2026 18:54:10 +0100 Subject: [PATCH 0780/1328] ASoC: fix unmet dependencies on PPC_BESTCOMM and SND_SOC_AC97_BUS SND_MPC52xx_SOC_PCM030 and SND_MPC52xx_SOC_EFIKA both select SND_SOC_MPC5200_AC97 without ensuring its dependency on PPC_BESTCOMM is met. This causes unmet dependencies such as: WARNING: unmet direct dependencies detected for SND_SOC_MPC5200_AC97 Depends on [n]: SOUND [=m] && SND [=m] && SND_SOC [=m] && SND_POWERPC_SOC [=m] && PPC_BESTCOMM [=n] Selected by [m]: - SND_MPC52xx_SOC_PCM030 [=m] && SOUND [=m] && SND [=m] && SND_SOC [=m] && SND_POWERPC_SOC [=m] && PPC_MPC5200_SIMPLE [=y] In v1, Rosen pointed out that the import of "mpc5200_dma.h" is actually unnecessary, and Arnd suggested that the 'select' of SND_SOC_MPC5200_AC97 be changed to 'depends on'. This resolves all 3 unmet dependencies. These unmet dependency bugs were detected by kconfirm, a static analysis tool for Kconfig. Fixes: a9262c4fd404 ("ASoC: Support for AC97 on Phytec pmc030 base board.") Fixes: 6ffee43ecf8b ("ASoC: Fabric bindings for STAC9766 on the Efika") Suggested-by: Arnd Bergmann Suggested-by: Rosen Penev Signed-off-by: Julian Braha Link: https://patch.msgid.link/20260821175410.179631-1-julianbraha@gmail.com Signed-off-by: Mark Brown --- sound/soc/fsl/Kconfig | 4 ++-- sound/soc/fsl/efika-audio-fabric.c | 2 -- sound/soc/fsl/pcm030-audio-fabric.c | 2 -- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 8ae59c094878..04940879dfd8 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -244,7 +244,7 @@ config SND_SOC_MPC5200_AC97 config SND_MPC52xx_SOC_PCM030 tristate "SoC AC97 Audio support for Phytec pcm030 and WM9712" depends on PPC_MPC5200_SIMPLE - select SND_SOC_MPC5200_AC97 + depends on SND_SOC_MPC5200_AC97 select SND_SOC_WM9712 help Say Y if you want to add support for sound on the Phytec pcm030 @@ -253,7 +253,7 @@ config SND_MPC52xx_SOC_PCM030 config SND_MPC52xx_SOC_EFIKA tristate "SoC AC97 Audio support for bbplan Efika and STAC9766" depends on PPC_EFIKA - select SND_SOC_MPC5200_AC97 + depends on SND_SOC_MPC5200_AC97 select SND_SOC_STAC9766 help Say Y if you want to add support for sound on the Efika. diff --git a/sound/soc/fsl/efika-audio-fabric.c b/sound/soc/fsl/efika-audio-fabric.c index 4d3266d9cbb7..e9c728bae8b9 100644 --- a/sound/soc/fsl/efika-audio-fabric.c +++ b/sound/soc/fsl/efika-audio-fabric.c @@ -22,8 +22,6 @@ #include #include -#include "mpc5200_dma.h" - #define DRV_NAME "efika-audio-fabric" SND_SOC_DAILINK_DEFS(analog, diff --git a/sound/soc/fsl/pcm030-audio-fabric.c b/sound/soc/fsl/pcm030-audio-fabric.c index 5542c4ee6d12..a7a5dba83a2f 100644 --- a/sound/soc/fsl/pcm030-audio-fabric.c +++ b/sound/soc/fsl/pcm030-audio-fabric.c @@ -13,8 +13,6 @@ #include -#include "mpc5200_dma.h" - #define DRV_NAME "pcm030-audio-fabric" struct pcm030_audio_data { From 660ee6792edfe498e35ec17b1b34bb6704e74f4f Mon Sep 17 00:00:00 2001 From: Fenglin Wu Date: Thu, 6 Aug 2026 23:21:40 -0700 Subject: [PATCH 0781/1328] dt-bindings: input: Add Qualcomm SPMI PMIC haptics Add binding document for the haptics module inside Qualcomm PMIC PMIH0108. Assisted-by: Claude:claude-4-8-opus Reviewed-by: Krzysztof Kozlowski Signed-off-by: Fenglin Wu Link: https://patch.msgid.link/20260806-qcom-spmi-haptics-v6-1-19fcecd5204f@oss.qualcomm.com [robh: Drop example] Signed-off-by: Rob Herring (Arm) --- .../bindings/input/qcom,spmi-haptics.yaml | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 Documentation/devicetree/bindings/input/qcom,spmi-haptics.yaml diff --git a/Documentation/devicetree/bindings/input/qcom,spmi-haptics.yaml b/Documentation/devicetree/bindings/input/qcom,spmi-haptics.yaml new file mode 100644 index 000000000000..e6cdaed9ce5b --- /dev/null +++ b/Documentation/devicetree/bindings/input/qcom,spmi-haptics.yaml @@ -0,0 +1,116 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/input/qcom,spmi-haptics.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Haptics device inside Qualcomm Technologies, Inc. PMIC + +maintainers: + - Fenglin Wu + +description: | + Certain Qualcomm PMICs integrate a haptics module, such as the HAP530_HV haptics + module in the PMIH0108 PMIC, which drives an LRA (Linear Resonant Actuator) with + an output voltage up to 10 V. Several play modes are supported in HAP530_HV: + + DIRECT_PLAY: The hardware outputs sinusoidal waveforms whose period is + defined by qcom,lra-period-us and whose peak voltage is defined by + qcom,vmax-microvolt. The driving amplitude can be scaled in the range + [0, 255] via a single register byte. Hardware-based LRA auto-resonance + tracking is enabled by default in this mode, allowing the haptics engine + to follow the actual resonant frequency of the LRA and update the driving + period accordingly to achieve stronger vibration magnitude. + + FIFO: The hardware can play an arbitrary waveform composed of a sequence + of 8-bit samples at a configurable play rate. Samples are pre-filled + into the internal FIFO memory of the haptics module and continuously + replenished via the FIFO-empty IRQ until all samples have been played. + An 8K-byte FIFO memory bank is available in the HAP530_HV haptics module, + shared between the FIFO and PAT_MEM play modes. The memory partition + between the two modes is configurable via registers, and FIFO mode always + uses the 1st partition starting from offset 0. + + PAT_MEM: This mode is very similar to FIFO streaming mode but without the + data refilling capability. It is designed mainly for short, latency-critical + vibrations. The memory space for PAT_MEM mode must be reserved for dedicated + usage, and the waveform data should be preloaded and remain unchanged + thereafter. The haptics module can play the waveform data from the memory + region specified by the PAT_MEM play start address and length registers. + + In either FIFO mode or PAT_MEM mode, the following play rates are supported: + -- 0(T_LRA): each FIFO byte drives one full sinusoidal cycle with the + period defined in qcom,lra-period-us. + -- 1/2/3(T_LRA_DIV_2/4/8): each FIFO byte drives a half/quarter/eighth + sinusoidal cycle with the period defined in qcom,lra-period-us. + -- 4/5/6(T_LRA_X_2/4/8): each FIFO byte drives 2/4/8 sinusoidal cycles + with the period defined in qcom,lra-period-us. + -- 8/9/10/11/12/13(8KHz/16KHz/24KHz/32KHz/44.1KHz/48KHz): the FIFO + data is treated as PCM samples and drives the output with an + arbitrarily shaped waveform. This mode is typically used to define + custom driving waveforms for specific vibration effects such as fast + attack, crisp brake, etc. + + The drive voltage in FIFO or PAT_MEM mode can exceed the value defined in + qcom,vmax-microvolt to achieve a special vibration effect, but the waveform + must be short enough to prevent the LRA from being damaged by operating at + an overvoltage. + + Also, hardware-based LRA auto-resonance tracking is normally disabled in + FIFO or PAT_MEM mode, as these modes are intended to drive arbitrary + waveforms that may not follow the resonant frequency; autonomous hardware + resonance correction would interfere with the intended output. + +properties: + compatible: + items: + - const: qcom,pmih0108-haptics + - const: qcom,spmi-haptics + + reg: + items: + - description: HAP_CFG module base address + - description: HAP_PTN module base address + + reg-names: + items: + - const: cfg + - const: ptn + + interrupts: + maxItems: 1 + + interrupt-names: + items: + - const: fifo-empty + + qcom,vmax-microvolt: + description: + Maximum allowed output driving voltage in microvolts, must be a multiple + of 50,000 uV. This is the peak driving voltage in DIRECT_PLAY mode, + which outputs sinusoidal waveforms. The value should be equal to the + square root of 2 times the Vrms voltage of the LRA. + minimum: 50000 + maximum: 10000000 + multipleOf: 50000 + + qcom,lra-period-us: + description: + LRA actuator initial resonance period in microseconds + (1,000,000 / resonant_freq_hz). Used to configure T_LRA-based play + rates and the auto-resonance zero-crossing window. It could be also used + as the initial period if the LRA wants to be driven off resonance. + minimum: 5 + maximum: 20475 + multipleOf: 5 + +required: + - compatible + - reg + - reg-names + - interrupts + - interrupt-names + - qcom,vmax-microvolt + - qcom,lra-period-us + +additionalProperties: false From 46d4246d8dcde75aff707976cddc546f22184cac Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Mon, 17 Aug 2026 11:19:16 +0000 Subject: [PATCH 0782/1328] f2fs: use f2fs_{down, up}_(read, write}_trace() for nat_tree_lock Under heavy workloads or during background GC/fallocate operations, nat_tree_lock can experience high lock contention between background readers (e.g. f2fs_get_node_info() in gc_data_segment) and writers (e.g. flush_nat_entries, set_node_addr, shrinker). [375067.327986][T13777] schedule+0x4c/0x114 [375067.327997][T13777] f2fs_get_node_info+0x438/0x5c4 [375067.328002][T13777] f2fs_get_inode_page+0x1e0/0x3f0 [375067.328013][T13777] f2fs_iget+0x88/0x1180 [375067.328024][T13777] f2fs_lookup+0x168/0x3a8 [375067.328035][T13777] path_openat+0xa28/0x1b04 [375067.328046][T13777] do_filp_open+0xac/0x130 [375067.328056][T13777] do_sys_openat2+0x140/0x21c [375067.328066][T13777] __arm64_sys_openat+0x70/0x9c [375067.330299][T13777] schedule+0x4c/0x114 [375067.330310][T13777] schedule_preempt_disabled+0x24/0x40 [375067.330321][T13777] rwsem_down_write_slowpath+0x3b4/0x9d0 [375067.330332][T13777] down_write+0x98/0x170 [375067.330343][T13777] set_node_addr+0x74/0x4b4 [375067.330354][T13777] f2fs_new_node_page+0xb0/0x280 [375067.330444][T13777] f2fs_new_inode_page+0x3c/0x64 [375067.330455][T13777] f2fs_init_inode_metadata+0x4c/0x47c [375067.330461][T13777] f2fs_add_regular_entry+0x258/0x5b8 [375067.330471][T13777] f2fs_add_dentry+0x100/0x158 [375067.330476][T13777] f2fs_do_add_link+0x84/0x140 [375067.330487][T13777] f2fs_create+0xec/0x250 [375067.331759][T13777] schedule+0x4c/0x114 [375067.331770][T13777] f2fs_down_read+0x9c/0xc4 [375067.331781][T13777] f2fs_need_inode_block_update+0x20/0x10c [375067.331792][T13777] f2fs_do_sync_file+0x478/0x830 [375067.331802][T13777] f2fs_sync_file+0x2c/0x40 This patch converts nat_tree_lock to use the f2fs_{down,up}_{read,write}_trace infrastructure. Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- Documentation/ABI/testing/sysfs-fs-f2fs | 1 + fs/f2fs/checkpoint.c | 1 + fs/f2fs/f2fs.h | 1 + fs/f2fs/node.c | 71 ++++++++++++++----------- include/trace/events/f2fs.h | 3 +- 5 files changed, 46 insertions(+), 31 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-fs-f2fs b/Documentation/ABI/testing/sysfs-fs-f2fs index f4e6a7415cde..85194e4c7f01 100644 --- a/Documentation/ABI/testing/sysfs-fs-f2fs +++ b/Documentation/ABI/testing/sysfs-fs-f2fs @@ -986,6 +986,7 @@ Description: This sysfs entry can be used to enable/disable to adjust priority f 0x00000008 gc_lock 0x00000010 cp_global 0x00000020 io_rwsem + 0x00000040 nat_tree_lock ========== ================== What: /sys/fs/f2fs//lock_duration_priority diff --git a/fs/f2fs/checkpoint.c b/fs/f2fs/checkpoint.c index c73999e39a39..4b59f30ef45d 100644 --- a/fs/f2fs/checkpoint.c +++ b/fs/f2fs/checkpoint.c @@ -107,6 +107,7 @@ static bool need_uplift_priority(struct f2fs_rwsem *sem, bool is_write) case LOCK_NAME_GC_LOCK: case LOCK_NAME_CP_GLOBAL: case LOCK_NAME_IO_RWSEM: + case LOCK_NAME_NAT_TREE_LOCK: return true; default: f2fs_bug_on(sem->sbi, 1); diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index 1b96d8718c5c..a1f5f375045a 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -193,6 +193,7 @@ enum f2fs_lock_name { LOCK_NAME_GC_LOCK, LOCK_NAME_CP_GLOBAL, LOCK_NAME_IO_RWSEM, + LOCK_NAME_NAT_TREE_LOCK, LOCK_NAME_MAX, }; diff --git a/fs/f2fs/node.c b/fs/f2fs/node.c index 46bea52e35c3..968e5ed38816 100644 --- a/fs/f2fs/node.c +++ b/fs/f2fs/node.c @@ -401,15 +401,16 @@ bool f2fs_need_dentry_mark(struct f2fs_sb_info *sbi, nid_t nid) struct f2fs_nm_info *nm_i = NM_I(sbi); struct nat_entry *e; bool need = false; + struct f2fs_lock_context lc; - f2fs_down_read(&nm_i->nat_tree_lock); + f2fs_down_read_trace(&nm_i->nat_tree_lock, &lc); e = __lookup_nat_cache(nm_i, nid, false); if (e) { if (!get_nat_flag(e, IS_CHECKPOINTED) && !get_nat_flag(e, HAS_FSYNCED_INODE)) need = true; } - f2fs_up_read(&nm_i->nat_tree_lock); + f2fs_up_read_trace(&nm_i->nat_tree_lock, &lc); return need; } @@ -418,12 +419,13 @@ bool f2fs_is_checkpointed_node(struct f2fs_sb_info *sbi, nid_t nid) struct f2fs_nm_info *nm_i = NM_I(sbi); struct nat_entry *e; bool is_cp = true; + struct f2fs_lock_context lc; - f2fs_down_read(&nm_i->nat_tree_lock); + f2fs_down_read_trace(&nm_i->nat_tree_lock, &lc); e = __lookup_nat_cache(nm_i, nid, false); if (e && !get_nat_flag(e, IS_CHECKPOINTED)) is_cp = false; - f2fs_up_read(&nm_i->nat_tree_lock); + f2fs_up_read_trace(&nm_i->nat_tree_lock, &lc); return is_cp; } @@ -432,16 +434,16 @@ bool f2fs_need_inode_block_update(struct f2fs_sb_info *sbi, nid_t ino) struct f2fs_nm_info *nm_i = NM_I(sbi); struct nat_entry *e; bool need_update = true; - struct f2fs_lock_context lc; + struct f2fs_lock_context lc, nlc; f2fs_down_read_trace(&sbi->node_write, &lc); - f2fs_down_read(&nm_i->nat_tree_lock); + f2fs_down_read_trace(&nm_i->nat_tree_lock, &nlc); e = __lookup_nat_cache(nm_i, ino, false); if (e && get_nat_flag(e, HAS_LAST_FSYNC) && (get_nat_flag(e, IS_CHECKPOINTED) || get_nat_flag(e, HAS_FSYNCED_INODE))) need_update = false; - f2fs_up_read(&nm_i->nat_tree_lock); + f2fs_up_read_trace(&nm_i->nat_tree_lock, &nlc); f2fs_up_read_trace(&sbi->node_write, &lc); return need_update; } @@ -452,6 +454,7 @@ static void cache_nat_entry(struct f2fs_sb_info *sbi, nid_t nid, { struct f2fs_nm_info *nm_i = NM_I(sbi); struct nat_entry *new, *e; + struct f2fs_lock_context lc; /* Let's mitigate lock contention of nat_tree_lock during checkpoint */ if (f2fs_rwsem_is_locked(&sbi->cp_global_sem)) @@ -461,7 +464,7 @@ static void cache_nat_entry(struct f2fs_sb_info *sbi, nid_t nid, if (!new) return; - f2fs_down_write(&nm_i->nat_tree_lock); + f2fs_down_write_trace(&nm_i->nat_tree_lock, &lc); e = __lookup_nat_cache(nm_i, nid, false); if (!e) e = __init_nat_entry(nm_i, new, ne, false, false); @@ -470,7 +473,7 @@ static void cache_nat_entry(struct f2fs_sb_info *sbi, nid_t nid, nat_get_blkaddr(e) != le32_to_cpu(ne->block_addr) || nat_get_version(e) != ne->version); - f2fs_up_write(&nm_i->nat_tree_lock); + f2fs_up_write_trace(&nm_i->nat_tree_lock, &lc); if (e != new) __free_nat_entry(new); } @@ -482,8 +485,9 @@ static void set_node_addr(struct f2fs_sb_info *sbi, struct node_info *ni, struct nat_entry *e; struct nat_entry *new = __alloc_nat_entry(sbi, ni->nid, true); bool init_dirty = false; + struct f2fs_lock_context lc; - f2fs_down_write(&nm_i->nat_tree_lock); + f2fs_down_write_trace(&nm_i->nat_tree_lock, &lc); e = __lookup_nat_cache(nm_i, ni->nid, true); if (!e) { init_dirty = true; @@ -533,15 +537,16 @@ static void set_node_addr(struct f2fs_sb_info *sbi, struct node_info *ni, set_nat_flag(e, HAS_FSYNCED_INODE, true); set_nat_flag(e, HAS_LAST_FSYNC, fsync_done); } - f2fs_up_write(&nm_i->nat_tree_lock); + f2fs_up_write_trace(&nm_i->nat_tree_lock, &lc); } int f2fs_try_to_free_nats(struct f2fs_sb_info *sbi, int nr_shrink) { struct f2fs_nm_info *nm_i = NM_I(sbi); int nr = nr_shrink; + struct f2fs_lock_context lc; - if (!f2fs_down_write_trylock(&nm_i->nat_tree_lock)) + if (!f2fs_down_write_trylock_trace(&nm_i->nat_tree_lock, &lc)) return 0; spin_lock(&nm_i->nat_list_lock); @@ -563,7 +568,7 @@ int f2fs_try_to_free_nats(struct f2fs_sb_info *sbi, int nr_shrink) } spin_unlock(&nm_i->nat_list_lock); - f2fs_up_write(&nm_i->nat_tree_lock); + f2fs_up_write_trace(&nm_i->nat_tree_lock, &lc); return nr - nr_shrink; } @@ -581,18 +586,19 @@ int f2fs_get_node_info(struct f2fs_sb_info *sbi, nid_t nid, pgoff_t index; int i; bool need_cache = true; + struct f2fs_lock_context lc; ni->flag = 0; ni->nid = nid; retry: /* Check nat cache */ - f2fs_down_read(&nm_i->nat_tree_lock); + f2fs_down_read_trace(&nm_i->nat_tree_lock, &lc); e = __lookup_nat_cache(nm_i, nid, false); if (e) { ni->ino = nat_get_ino(e); ni->blk_addr = nat_get_blkaddr(e); ni->version = nat_get_version(e); - f2fs_up_read(&nm_i->nat_tree_lock); + f2fs_up_read_trace(&nm_i->nat_tree_lock, &lc); if (IS_ENABLED(CONFIG_F2FS_CHECK_FS)) { need_cache = false; goto sanity_check; @@ -610,7 +616,7 @@ int f2fs_get_node_info(struct f2fs_sb_info *sbi, nid_t nid, down_read(&curseg->journal_rwsem); } else if (f2fs_rwsem_is_contended(&nm_i->nat_tree_lock) || !down_read_trylock(&curseg->journal_rwsem)) { - f2fs_up_read(&nm_i->nat_tree_lock); + f2fs_up_read_trace(&nm_i->nat_tree_lock, &lc); goto retry; } @@ -621,13 +627,13 @@ int f2fs_get_node_info(struct f2fs_sb_info *sbi, nid_t nid, } up_read(&curseg->journal_rwsem); if (i >= 0) { - f2fs_up_read(&nm_i->nat_tree_lock); + f2fs_up_read_trace(&nm_i->nat_tree_lock, &lc); goto sanity_check; } /* Fill node_info from nat page */ index = current_nat_addr(sbi, nid); - f2fs_up_read(&nm_i->nat_tree_lock); + f2fs_up_read_trace(&nm_i->nat_tree_lock, &lc); folio = f2fs_get_meta_folio(sbi, index); if (IS_ERR(folio)) @@ -2567,8 +2573,9 @@ static void scan_free_nid_bits(struct f2fs_sb_info *sbi) struct f2fs_nm_info *nm_i = NM_I(sbi); unsigned int i, idx; nid_t nid; + struct f2fs_lock_context lc; - f2fs_down_read(&nm_i->nat_tree_lock); + f2fs_down_read_trace(&nm_i->nat_tree_lock, &lc); for (i = 0; i < nm_i->nat_blocks; i++) { if (!test_bit_le(i, nm_i->nat_block_bitmap)) @@ -2591,7 +2598,7 @@ static void scan_free_nid_bits(struct f2fs_sb_info *sbi) out: scan_curseg_cache(sbi); - f2fs_up_read(&nm_i->nat_tree_lock); + f2fs_up_read_trace(&nm_i->nat_tree_lock, &lc); } static int __f2fs_build_free_nids(struct f2fs_sb_info *sbi, @@ -2600,6 +2607,7 @@ static int __f2fs_build_free_nids(struct f2fs_sb_info *sbi, struct f2fs_nm_info *nm_i = NM_I(sbi); int i = 0, ret; nid_t nid = nm_i->next_scan_nid; + struct f2fs_lock_context lc; if (unlikely(nid >= nm_i->max_nid)) nid = 0; @@ -2626,7 +2634,7 @@ static int __f2fs_build_free_nids(struct f2fs_sb_info *sbi, f2fs_ra_meta_pages(sbi, NAT_BLOCK_OFFSET(nid), FREE_NID_PAGES, META_NAT, true); - f2fs_down_read(&nm_i->nat_tree_lock); + f2fs_down_read_trace(&nm_i->nat_tree_lock, &lc); while (1) { if (!test_bit_le(NAT_BLOCK_OFFSET(nid), @@ -2642,7 +2650,7 @@ static int __f2fs_build_free_nids(struct f2fs_sb_info *sbi, } if (ret) { - f2fs_up_read(&nm_i->nat_tree_lock); + f2fs_up_read_trace(&nm_i->nat_tree_lock, &lc); if (ret == -EFSCORRUPTED) { f2fs_err(sbi, "NAT is corrupt, run fsck to fix it"); @@ -2669,7 +2677,7 @@ static int __f2fs_build_free_nids(struct f2fs_sb_info *sbi, /* find free nids from current sum_pages */ scan_curseg_cache(sbi); - f2fs_up_read(&nm_i->nat_tree_lock); + f2fs_up_read_trace(&nm_i->nat_tree_lock, &lc); f2fs_ra_meta_pages(sbi, NAT_BLOCK_OFFSET(nm_i->next_scan_nid), nm_i->ra_nid_pages, META_NAT, false); @@ -3206,21 +3214,22 @@ int f2fs_flush_nat_entries(struct f2fs_sb_info *sbi, struct cp_control *cpc) nid_t set_idx = 0; LIST_HEAD(sets); int err = 0; + struct f2fs_lock_context lc; /* * during unmount, let's flush nat_bits before checking * nat_cnt[DIRTY_NAT]. */ if (enabled_nat_bits(sbi, cpc)) { - f2fs_down_write(&nm_i->nat_tree_lock); + f2fs_down_write_trace(&nm_i->nat_tree_lock, &lc); remove_nats_in_journal(sbi); - f2fs_up_write(&nm_i->nat_tree_lock); + f2fs_up_write_trace(&nm_i->nat_tree_lock, &lc); } if (!nm_i->nat_cnt[DIRTY_NAT]) return 0; - f2fs_down_write(&nm_i->nat_tree_lock); + f2fs_down_write_trace(&nm_i->nat_tree_lock, &lc); /* * if there are no enough space in journal to store dirty nat @@ -3261,7 +3270,7 @@ int f2fs_flush_nat_entries(struct f2fs_sb_info *sbi, struct cp_control *cpc) break; } - f2fs_up_write(&nm_i->nat_tree_lock); + f2fs_up_write_trace(&nm_i->nat_tree_lock, &lc); /* Allow dirty nats by node block allocation in write_begin */ return err; @@ -3380,7 +3389,8 @@ static int init_node_manager(struct f2fs_sb_info *sbi) mutex_init(&nm_i->build_lock); spin_lock_init(&nm_i->nid_list_lock); - init_f2fs_rwsem(&nm_i->nat_tree_lock); + init_f2fs_rwsem_trace(&nm_i->nat_tree_lock, sbi, + LOCK_NAME_NAT_TREE_LOCK); nm_i->next_scan_nid = le32_to_cpu(sbi->ckpt->next_free_nid); nm_i->bitmap_size = __bitmap_size(sbi, NAT_BITMAP); @@ -3472,6 +3482,7 @@ void f2fs_destroy_node_manager(struct f2fs_sb_info *sbi) struct nat_entry_set **setvec = (struct nat_entry_set **)vec; nid_t nid = 0; unsigned int found; + struct f2fs_lock_context lc; if (!nm_i) return; @@ -3490,7 +3501,7 @@ void f2fs_destroy_node_manager(struct f2fs_sb_info *sbi) spin_unlock(&nm_i->nid_list_lock); /* destroy nat cache */ - f2fs_down_write(&nm_i->nat_tree_lock); + f2fs_down_write_trace(&nm_i->nat_tree_lock, &lc); while ((found = __gang_lookup_nat_cache(nm_i, nid, NAT_VEC_SIZE, natvec))) { unsigned idx; @@ -3521,7 +3532,7 @@ void f2fs_destroy_node_manager(struct f2fs_sb_info *sbi) kmem_cache_free(nat_entry_set_slab, setvec[idx]); } } - f2fs_up_write(&nm_i->nat_tree_lock); + f2fs_up_write_trace(&nm_i->nat_tree_lock, &lc); kvfree(nm_i->nat_block_bitmap); if (nm_i->free_nid_bitmap) { diff --git a/include/trace/events/f2fs.h b/include/trace/events/f2fs.h index 1dd9fc5afc46..d53be932df01 100644 --- a/include/trace/events/f2fs.h +++ b/include/trace/events/f2fs.h @@ -191,7 +191,8 @@ TRACE_DEFINE_ENUM(CP_PHASE_FINISH_CHECKPOINT); { LOCK_NAME_NODE_WRITE, "node_write" }, \ { LOCK_NAME_GC_LOCK, "gc_lock" }, \ { LOCK_NAME_CP_GLOBAL, "cp_global" }, \ - { LOCK_NAME_IO_RWSEM, "io_rwsem" }) + { LOCK_NAME_IO_RWSEM, "io_rwsem" }, \ + { LOCK_NAME_NAT_TREE_LOCK, "nat_tree_lock" }) struct f2fs_sb_info; struct f2fs_io_info; From 2b8704b6a8b2896ccad1f5941d9a3e2c5031a470 Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Mon, 17 Aug 2026 02:39:27 +0000 Subject: [PATCH 0783/1328] f2fs: fix to reset all pinned status during fggc Otherwise, the pinned status may affect latter flow of fggc. Cc: stable@kernel.org Fixes: 9703d69d9d15 ("f2fs: support file pinning for zoned devices") Cc: Daeho Jeong Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/gc.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c index 5917ee917d87..0c17038fcfd7 100644 --- a/fs/f2fs/gc.c +++ b/fs/f2fs/gc.c @@ -2183,6 +2183,9 @@ int f2fs_gc_range(struct f2fs_sb_info *sbi, do_garbage_collect(sbi, segno, &gc_list, FG_GC, true, false); put_gc_inode(&gc_list); + /* reset all pinned status during fggc */ + f2fs_unpin_all_sections(sbi, true); + if (!dry_run && get_valid_blocks(sbi, segno, true)) { err = -EAGAIN; goto next; From 11d56d7a8e60d7ee4969b56403da700745aea1af Mon Sep 17 00:00:00 2001 From: Daeho Jeong Date: Fri, 21 Aug 2026 07:17:42 -0700 Subject: [PATCH 0784/1328] f2fs: fix error handling on device alias check in rename and unlink In f2fs_rename() and f2fs_unlink(), directly returning -EPERM when encountering a device aliasing file bypasses the cleanup path. Fix this by setting err to -EPERM and jumping to the proper cleanup labels (out_dir and out) instead of returning immediately. Reported-by: Christophe JAILLET Signed-off-by: Daeho Jeong Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/namei.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c index 784f636244e1..b9b15c5d28de 100644 --- a/fs/f2fs/namei.c +++ b/fs/f2fs/namei.c @@ -571,8 +571,10 @@ static int f2fs_unlink(struct inode *dir, struct dentry *dentry) trace_f2fs_unlink_enter(dir, dentry); - if (IS_DEVICE_ALIASING(inode)) - return -EPERM; + if (IS_DEVICE_ALIASING(inode)) { + err = -EPERM; + goto out; + } if (unlikely(f2fs_cp_error(sbi))) { err = -EIO; @@ -1025,8 +1027,10 @@ static int f2fs_rename(struct mnt_idmap *idmap, struct inode *old_dir, } if (new_inode) { - if (IS_DEVICE_ALIASING(new_inode)) - return -EPERM; + if (IS_DEVICE_ALIASING(new_inode)) { + err = -EPERM; + goto out_dir; + } err = -ENOTEMPTY; if (old_is_dir && !f2fs_empty_dir(new_inode)) From a2c73a7a677afdaa8b16d775188f9ef5cfbfd8b2 Mon Sep 17 00:00:00 2001 From: Wenjie Qi Date: Mon, 10 Aug 2026 21:38:32 +0800 Subject: [PATCH 0785/1328] f2fs: return symlink writeback errors F2FS writes long symlink data with page_symlink() and then flushes the symlink mapping to reduce the chance of exposing a broken symlink. That flush result is currently ignored. If the writeback fails, symlink() still returns success even though the symlink is not durable and the same operation can already surface -EIO through syncfs(). Return the writeback error to userspace and skip the dirsync flush once the symlink data flush has failed. Fixes: d0cae97cb600 ("f2fs: flush symlink path to avoid broken symlink after POR") Cc: stable@kernel.org Signed-off-by: Wenjie Qi Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/namei.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c index b9b15c5d28de..a0e902895dd3 100644 --- a/fs/f2fs/namei.c +++ b/fs/f2fs/namei.c @@ -715,15 +715,16 @@ static int f2fs_symlink(struct mnt_idmap *idmap, struct inode *dir, * performance regression. */ if (!err) { - filemap_write_and_wait_range(inode->i_mapping, 0, - disk_link.len - 1); + err = filemap_write_and_wait_range(inode->i_mapping, 0, + disk_link.len - 1); - if (IS_DIRSYNC(dir)) + if (!err && IS_DIRSYNC(dir)) f2fs_sync_fs(sbi->sb, 1); - } else { - f2fs_unlink(dir, dentry); } + if (err) + f2fs_unlink(dir, dentry); + f2fs_balance_fs(sbi, true); goto out_free_encrypted_link; From dafb84f092a6387748b2df4c8f647d46873bc1a0 Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Wed, 12 Aug 2026 12:20:43 +0000 Subject: [PATCH 0786/1328] f2fs: fix to propagate error from f2fs_sync_fs() So that caller can detect any failure from f2fs_sync_fs(). Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/namei.c | 51 ++++++++++++++++++++++++++++++++++--------------- fs/f2fs/super.c | 4 +++- 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c index a0e902895dd3..7d9f8e92de06 100644 --- a/fs/f2fs/namei.c +++ b/fs/f2fs/namei.c @@ -402,8 +402,11 @@ static int f2fs_create(struct mnt_idmap *idmap, struct inode *dir, d_instantiate_new(dentry, inode); - if (IS_DIRSYNC(dir)) - f2fs_sync_fs(sbi->sb, 1); + if (IS_DIRSYNC(dir)) { + err = f2fs_sync_fs(sbi->sb, 1); + if (err) + return err; + } f2fs_balance_fs(sbi, true); return 0; @@ -455,8 +458,11 @@ static int f2fs_link(struct dentry *old_dentry, struct inode *dir, d_instantiate(dentry, inode); - if (IS_DIRSYNC(dir)) - f2fs_sync_fs(sbi->sb, 1); + if (IS_DIRSYNC(dir)) { + err = f2fs_sync_fs(sbi->sb, 1); + if (err) + return err; + } return 0; out: clear_inode_flag(inode, FI_INC_LINK); @@ -626,8 +632,11 @@ static int f2fs_unlink(struct inode *dir, struct dentry *dentry) if (IS_ENABLED(CONFIG_UNICODE) && IS_CASEFOLDED(dir)) d_invalidate(dentry); - if (IS_DIRSYNC(dir)) - f2fs_sync_fs(sbi->sb, 1); + if (IS_DIRSYNC(dir)) { + err = f2fs_sync_fs(sbi->sb, 1); + if (err) + goto out; + } goto out; corrupted: @@ -719,7 +728,7 @@ static int f2fs_symlink(struct mnt_idmap *idmap, struct inode *dir, disk_link.len - 1); if (!err && IS_DIRSYNC(dir)) - f2fs_sync_fs(sbi->sb, 1); + err = f2fs_sync_fs(sbi->sb, 1); } if (err) @@ -771,8 +780,11 @@ static struct dentry *f2fs_mkdir(struct mnt_idmap *idmap, struct inode *dir, d_instantiate_new(dentry, inode); - if (IS_DIRSYNC(dir)) - f2fs_sync_fs(sbi->sb, 1); + if (IS_DIRSYNC(dir)) { + err = f2fs_sync_fs(sbi->sb, 1); + if (err) + return ERR_PTR(err); + } f2fs_balance_fs(sbi, true); return NULL; @@ -826,8 +838,11 @@ static int f2fs_mknod(struct mnt_idmap *idmap, struct inode *dir, d_instantiate_new(dentry, inode); - if (IS_DIRSYNC(dir)) - f2fs_sync_fs(sbi->sb, 1); + if (IS_DIRSYNC(dir)) { + err = f2fs_sync_fs(sbi->sb, 1); + if (err) + return err; + } f2fs_balance_fs(sbi, true); return 0; @@ -1126,8 +1141,11 @@ static int f2fs_rename(struct mnt_idmap *idmap, struct inode *old_dir, f2fs_unlock_op(sbi, &lc); - if (IS_DIRSYNC(old_dir) || IS_DIRSYNC(new_dir)) - f2fs_sync_fs(sbi->sb, 1); + if (IS_DIRSYNC(old_dir) || IS_DIRSYNC(new_dir)) { + err = f2fs_sync_fs(sbi->sb, 1); + if (err) + return err; + } f2fs_update_time(sbi, REQ_TIME); return 0; @@ -1293,8 +1311,11 @@ static int f2fs_cross_rename(struct inode *old_dir, struct dentry *old_dentry, f2fs_unlock_op(sbi, &lc); - if (IS_DIRSYNC(old_dir) || IS_DIRSYNC(new_dir)) - f2fs_sync_fs(sbi->sb, 1); + if (IS_DIRSYNC(old_dir) || IS_DIRSYNC(new_dir)) { + err = f2fs_sync_fs(sbi->sb, 1); + if (err) + return err; + } f2fs_update_time(sbi, REQ_TIME); return 0; diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c index 0c8f60b7242f..3bdb0f891c35 100644 --- a/fs/f2fs/super.c +++ b/fs/f2fs/super.c @@ -2960,7 +2960,9 @@ static int __f2fs_remount(struct fs_context *fc, struct super_block *sb) set_sbi_flag(sbi, SBI_IS_DIRTY); set_sbi_flag(sbi, SBI_IS_CLOSE); - f2fs_sync_fs(sb, 1); + err = f2fs_sync_fs(sb, 1); + if (err) + goto restore_gc; clear_sbi_flag(sbi, SBI_IS_CLOSE); } From a9a01be2834a529cbd490ccbab02643f0c1735f2 Mon Sep 17 00:00:00 2001 From: Vincent Donnefort Date: Fri, 7 Aug 2026 09:54:23 +0100 Subject: [PATCH 0787/1328] tracing: Fix logged instance name on creation failure When boot instance creation fails, the kernel incorrectly logs "(null)" as the instance name because strsep() consumes curr_str entirely during parsing. Print the properly parsed name variable instead. And while at it log the error code. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260807085423.4175161-1-vdonnefort@google.com Fixes: cb1f98c5e574 ("tracing: Add creation of instances at boot command line") Acked-by: Masami Hiramatsu (Google) Signed-off-by: Vincent Donnefort Signed-off-by: Steven Rostedt --- kernel/trace/trace.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index 89dc1c0ebb90..740c5f358b75 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -9726,7 +9726,8 @@ __init static void enable_instances(void) tr = trace_array_create_systems(name, NULL, addr, size); if (IS_ERR(tr)) { - pr_warn("Tracing: Failed to create instance buffer %s\n", curr_str); + pr_warn("Tracing: Failed to create instance buffer '%s' (%ld)\n", name, + PTR_ERR(tr)); continue; } From 622d698df4239fef3e0eb51fe59f4198f957f28a Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Thu, 20 Aug 2026 21:45:12 -0700 Subject: [PATCH 0788/1328] bnxt_en: Fix call to hardware monitoring event handler The first parameter of hwmon_notify_event() is supposed to be the hardware monitoring device. The bnxt driver calls it with the platform device as first parameter instead. This API break results in undefined behavior and may result in a crash. Pass the hardware monitoring device as parameter instead to fix the problem. Fixes: a19b4801457b0 ("bnxt_en: Event handler for Thermal event") Signed-off-by: Guenter Roeck Reviewed-by: Kalesh AP Reviewed-by: Vadim Fedorenko Link: https://patch.msgid.link/20260821044512.663941-1-linux@roeck-us.net Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnxt/bnxt_hwmon.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt_hwmon.c b/drivers/net/ethernet/broadcom/bnxt/bnxt_hwmon.c index de3427c6c6aa..faed0cf726e1 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt_hwmon.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt_hwmon.c @@ -40,7 +40,7 @@ void bnxt_hwmon_notify_event(struct bnxt *bp) return; } - hwmon_notify_event(&bp->pdev->dev, hwmon_temp, attr, 0); + hwmon_notify_event(bp->hwmon_dev, hwmon_temp, attr, 0); } static int bnxt_hwrm_temp_query(struct bnxt *bp, u8 *temp) From a70859cf31214e546cb73da7142f190138dae9ab Mon Sep 17 00:00:00 2001 From: Thomas Walsh Date: Thu, 20 Aug 2026 18:05:44 -0400 Subject: [PATCH 0789/1328] bnxt_en: Gate TPH enablement behind BNXT_SUPPORTS_QUEUE_API check In bnxt_request_irq(), pcie_enable_tph() is called unconditionally to enable PCIe TPH when setting up interrupts. If the NIC hardware or firmware capabilities do not support queue ops, attempting to enable TPH during bnxt_request_irq() is unnecessary. As a result a flood of "RX queue restart failed: err=-95" messages is seen upon boot. Older NICs (pre-Thor / BCM57414) do not support TPH or queue management. TPH requires queue management to restart the queue. NICs that support queue management (with updated FW) all support TPH. Gate the call to pcie_enable_tph() and setting of bp->tph_mode behind BNXT_SUPPORTS_QUEUE_API(bp) to ensure TPH is only initialized on devices capable of supporting queue ops. This prevents a guaranteed -EOPNOTSUPP error from occurring due to NULL operations. Fixes: c214410c47d6 ("bnxt_en: Add TPH support in BNXT driver") Suggested-by: Michal Schmidt Signed-off-by: Thomas Walsh Reviewed-by: Michael Chan Reviewed-by: Pavan Chebbi Link: https://patch.msgid.link/20260820220544.1240879-1-thwalsh@redhat.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index d3cb25abb632..9c2cc50276a5 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -11952,9 +11952,11 @@ static int bnxt_request_irq(struct bnxt *bp) #endif /* Enable TPH support as part of IRQ request */ - rc = pcie_enable_tph(bp->pdev, PCI_TPH_ST_IV_MODE); - if (!rc) - bp->tph_mode = PCI_TPH_ST_IV_MODE; + if (BNXT_SUPPORTS_QUEUE_API(bp)) { + rc = pcie_enable_tph(bp->pdev, PCI_TPH_ST_IV_MODE); + if (!rc) + bp->tph_mode = PCI_TPH_ST_IV_MODE; + } for (i = 0, j = 0; i < bp->cp_nr_rings; i++) { struct cpumask *cpu_mask = bp->ring_cpu_mask[i]; From 777dbc9914b2f003f1d44af80c7a4a395c5961b2 Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Fri, 21 Aug 2026 11:24:49 +0800 Subject: [PATCH 0790/1328] ptp: netc: fix period truncation and potential divide-by-zero in PEROUT The max_period bound in net_timer_enable_perout() was computed as: max_period = (u64)NETC_TMR_DEFAULT_FIPER + integral_period; which exceeds U32_MAX when integral_period > 0 (e.g. 0x100000002 for the default 333333333 Hz clock). A period_ns that passes this check but exceeds U32_MAX is then silently truncated when stored into the u32 struct netc_pp::period field. A truncated value of zero can reach netc_timer_set_perout_alarm(), where the local u32 period variable would also be 0, causing a divide-by-zero in roundup_u64(delta, period) whenever the stime < min_time branch is taken (which always happens for a start time of {0, 0}). Additionally, netc_timer_enable_periodic_pulse() and netc_timer_enable_fiper() both compute: fiper = pp->period - integral_period; A zero pp->period results in an unsigned wraparound to 0xFFFFFFFD, mis-programming the FIPER hardware register. Fix all three issues by capping max_period at NETC_TMR_DEFAULT_FIPER (0xFFFFFFFF). This ensures that any period_ns passing the range check fits in a u32 without truncation, so the stored value is always valid and non-zero. The accepted range is reduced by integral_period ns (typically only a few nanoseconds), which is negligible in practice. Fixes: 671e266835b8 ("ptp: netc: add periodic pulse output support") Signed-off-by: Wei Fang Reviewed-by: Abel Vesa Link: https://patch.msgid.link/20260821032449.1235065-1-wei.fang@oss.nxp.com Signed-off-by: Jakub Kicinski --- drivers/ptp/ptp_netc.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/ptp/ptp_netc.c b/drivers/ptp/ptp_netc.c index 59db08e189e6..19ca99d80e95 100644 --- a/drivers/ptp/ptp_netc.c +++ b/drivers/ptp/ptp_netc.c @@ -440,7 +440,7 @@ static int net_timer_enable_perout(struct netc_timer *priv, } if (on) { - u64 period_ns, gclk_period, max_period, min_period; + u64 period_ns, gclk_period, min_period; struct timespec64 period, stime; u32 integral_period; int alarm_id; @@ -450,12 +450,12 @@ static int net_timer_enable_perout(struct netc_timer *priv, period_ns = timespec64_to_ns(&period); integral_period = netc_timer_get_integral_period(priv); - max_period = (u64)NETC_TMR_DEFAULT_FIPER + integral_period; gclk_period = netc_timer_get_gclk_period(priv); min_period = gclk_period * 4 + integral_period; - if (period_ns > max_period || period_ns < min_period) { - dev_err(dev, "The period range is %llu ~ %llu\n", - min_period, max_period); + if (period_ns > NETC_TMR_DEFAULT_FIPER || + period_ns < min_period) { + dev_err(dev, "The period range is %llu ~ %lu\n", + min_period, NETC_TMR_DEFAULT_FIPER); err = -EINVAL; goto unlock_spinlock; } From 1beb81947eb486716af80db7a584f9e9fef7e003 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Wed, 19 Aug 2026 10:37:33 -0400 Subject: [PATCH 0791/1328] net/sched: account classifier filter allocations to memcg Allocations in the tc classifier *_change() paths (filter objects, per-CPU counters, and per-filter aux data) use plain GFP_KERNEL without __GFP_ACCOUNT, allowing unprivileged users to pin kernel memory outside memcg charging. The shared tcf_exts_init_ex() action array allocation in cls_api.c was also uncharged; this patch closes it along with the per-classifier filter-object/percpu/aux allocations that remain unaccounted. Add GFP_KERNEL_ACCOUNT to: - the shared tcf_exts_init_ex() action array (cls_api.c), common to every filter of every classifier (32 pointers, 256 bytes); - the filter-object, per-CPU-counter, and per-filter aux allocations in cls_basic, cls_bpf, cls_cgroup, cls_flow, cls_flower, cls_fw, cls_matchall, cls_route and cls_u32; - the u32_init_knode() replace-path knode allocation (cls_u32.c), which allocates the same struct tc_u_knode + sel.keys on every replace of an existing knode and was missed by the create-path-only conversion. Also fix the cls_basic error path: basic_change() inserts fnew into the IDR before allocating the per-CPU counter. If alloc_percpu() fails the errout path kfree'd fnew without idr_remove, leaving a dangling pointer in the IDR. With GFP_KERNEL_ACCOUNT the percpu alloc becomes failable on demand (memcg at memory.max), making the dead path attacker-reachable and burning the handle permanently. Add the idr_remove on the percpu failure path, matching the basic_set_parms failure-path pattern. Note: vega@nebusec.ai provided a poc for basic_cls, but it was easy to extend to the other classifiers. Conditions to recreate the bug: - CONFIG_NET_SCHED, CONFIG_NET_CLS_* (the classifier being used), CONFIG_NET_CLS_ACT, CONFIG_MEMCG, CONFIG_USER_NS, CONFIG_NET_NS. - Unprivileged user in a fresh user+network namespace (unshare -Urn), or root with CAP_NET_ADMIN. - Create a large number of tc filters (e.g. tc filter add dev lo ingress ... ...) while watching a memcg-limited cgroup: system slab grows far faster than memory.current, pinning kernel memory outside memcg charging. Fixes: 0da974f4f303 ("[NET]: Conversions from kmalloc+memset to k(z|c)alloc.") Reported-by: vega@nebusec.ai Signed-off-by: Jamal Hadi Salim Reviewed-by: Breno Leitao Link: https://patch.msgid.link/20260819143733.57538-1-jhs@mojatatu.com Signed-off-by: Jakub Kicinski --- net/sched/cls_api.c | 3 ++- net/sched/cls_basic.c | 6 ++++-- net/sched/cls_bpf.c | 6 +++--- net/sched/cls_cgroup.c | 2 +- net/sched/cls_flow.c | 2 +- net/sched/cls_flower.c | 4 ++-- net/sched/cls_fw.c | 4 ++-- net/sched/cls_matchall.c | 4 ++-- net/sched/cls_route.c | 4 ++-- net/sched/cls_u32.c | 11 ++++++----- 10 files changed, 25 insertions(+), 21 deletions(-) diff --git a/net/sched/cls_api.c b/net/sched/cls_api.c index 3271963c945d..9966766661d5 100644 --- a/net/sched/cls_api.c +++ b/net/sched/cls_api.c @@ -3372,7 +3372,8 @@ int tcf_exts_init_ex(struct tcf_exts *exts, struct net *net, int action, * This reference might be taken later from tcf_exts_get_net(). */ exts->net = net; - exts->actions = kzalloc_objs(struct tc_action *, TCA_ACT_MAX_PRIO); + exts->actions = kzalloc_objs(struct tc_action *, TCA_ACT_MAX_PRIO, + GFP_KERNEL_ACCOUNT); if (!exts->actions) return -ENOMEM; #endif diff --git a/net/sched/cls_basic.c b/net/sched/cls_basic.c index 492cd9ce8d46..e2a94ba9fba7 100644 --- a/net/sched/cls_basic.c +++ b/net/sched/cls_basic.c @@ -193,7 +193,7 @@ static int basic_change(struct net *net, struct sk_buff *in_skb, return -EINVAL; } - fnew = kzalloc_obj(*fnew); + fnew = kzalloc_obj(*fnew, GFP_KERNEL_ACCOUNT); if (!fnew) return -ENOBUFS; @@ -212,9 +212,11 @@ static int basic_change(struct net *net, struct sk_buff *in_skb, if (err) goto errout; fnew->handle = handle; - fnew->pf = alloc_percpu(struct tc_basic_pcnt); + fnew->pf = alloc_percpu_gfp(struct tc_basic_pcnt, GFP_KERNEL_ACCOUNT); if (!fnew->pf) { err = -ENOMEM; + if (!fold) + idr_remove(&head->handle_idr, fnew->handle); goto errout; } diff --git a/net/sched/cls_bpf.c b/net/sched/cls_bpf.c index 6d19155becc8..188cf0f949dd 100644 --- a/net/sched/cls_bpf.c +++ b/net/sched/cls_bpf.c @@ -352,7 +352,7 @@ static int cls_bpf_prog_from_ops(struct nlattr **tb, struct cls_bpf_prog *prog) if (bpf_size != nla_len(tb[TCA_BPF_OPS])) return -EINVAL; - bpf_ops = kmemdup(nla_data(tb[TCA_BPF_OPS]), bpf_size, GFP_KERNEL); + bpf_ops = kmemdup(nla_data(tb[TCA_BPF_OPS]), bpf_size, GFP_KERNEL_ACCOUNT); if (bpf_ops == NULL) return -ENOMEM; @@ -403,7 +403,7 @@ static int cls_bpf_prog_from_efd(struct nlattr **tb, struct cls_bpf_prog *prog, } if (tb[TCA_BPF_NAME]) { - name = nla_memdup(tb[TCA_BPF_NAME], GFP_KERNEL); + name = nla_memdup(tb[TCA_BPF_NAME], GFP_KERNEL_ACCOUNT); if (!name) { bpf_prog_put(fp); return -ENOMEM; @@ -443,7 +443,7 @@ static int cls_bpf_change(struct net *net, struct sk_buff *in_skb, if (ret < 0) return ret; - prog = kzalloc_obj(*prog); + prog = kzalloc_obj(*prog, GFP_KERNEL_ACCOUNT); if (!prog) return -ENOBUFS; diff --git a/net/sched/cls_cgroup.c b/net/sched/cls_cgroup.c index 680a5c308094..210fd9fd26d8 100644 --- a/net/sched/cls_cgroup.c +++ b/net/sched/cls_cgroup.c @@ -95,7 +95,7 @@ static int cls_cgroup_change(struct net *net, struct sk_buff *in_skb, if (head && handle != head->handle) return -ENOENT; - new = kzalloc_obj(*head); + new = kzalloc_obj(*head, GFP_KERNEL_ACCOUNT); if (!new) return -ENOBUFS; diff --git a/net/sched/cls_flow.c b/net/sched/cls_flow.c index 356c68ebc389..a9ac3acf6eda 100644 --- a/net/sched/cls_flow.c +++ b/net/sched/cls_flow.c @@ -438,7 +438,7 @@ static int flow_change(struct net *net, struct sk_buff *in_skb, return -EOPNOTSUPP; } - fnew = kzalloc_obj(*fnew); + fnew = kzalloc_obj(*fnew, GFP_KERNEL_ACCOUNT); if (!fnew) return -ENOBUFS; diff --git a/net/sched/cls_flower.c b/net/sched/cls_flower.c index 88f8a32fab2b..0e275b58151c 100644 --- a/net/sched/cls_flower.c +++ b/net/sched/cls_flower.c @@ -2233,7 +2233,7 @@ static struct fl_flow_mask *fl_create_new_mask(struct cls_fl_head *head, struct fl_flow_mask *newmask; int err; - newmask = kzalloc_obj(*newmask); + newmask = kzalloc_obj(*newmask, GFP_KERNEL_ACCOUNT); if (!newmask) return ERR_PTR(-ENOMEM); @@ -2394,7 +2394,7 @@ static int fl_change(struct net *net, struct sk_buff *in_skb, goto errout_tb; } - fnew = kzalloc_obj(*fnew); + fnew = kzalloc_obj(*fnew, GFP_KERNEL_ACCOUNT); if (!fnew) { err = -ENOBUFS; goto errout_tb; diff --git a/net/sched/cls_fw.c b/net/sched/cls_fw.c index 646a730dca93..a462b262719c 100644 --- a/net/sched/cls_fw.c +++ b/net/sched/cls_fw.c @@ -276,7 +276,7 @@ static int fw_change(struct net *net, struct sk_buff *in_skb, if (f->id != handle && handle) return -EINVAL; - fnew = kzalloc_obj(struct fw_filter); + fnew = kzalloc_obj(struct fw_filter, GFP_KERNEL_ACCOUNT); if (!fnew) return -ENOBUFS; @@ -330,7 +330,7 @@ static int fw_change(struct net *net, struct sk_buff *in_skb, rcu_assign_pointer(tp->root, head); } - f = kzalloc_obj(struct fw_filter); + f = kzalloc_obj(struct fw_filter, GFP_KERNEL_ACCOUNT); if (f == NULL) return -ENOBUFS; diff --git a/net/sched/cls_matchall.c b/net/sched/cls_matchall.c index 6f126872c14a..c14899b935bf 100644 --- a/net/sched/cls_matchall.c +++ b/net/sched/cls_matchall.c @@ -189,7 +189,7 @@ static int mall_change(struct net *net, struct sk_buff *in_skb, return -EINVAL; } - new = kzalloc_obj(*new); + new = kzalloc_obj(*new, GFP_KERNEL_ACCOUNT); if (!new) return -ENOBUFS; @@ -201,7 +201,7 @@ static int mall_change(struct net *net, struct sk_buff *in_skb, handle = 1; new->handle = handle; new->flags = userflags; - new->pf = alloc_percpu(struct tc_matchall_pcnt); + new->pf = alloc_percpu_gfp(struct tc_matchall_pcnt, GFP_KERNEL_ACCOUNT); if (!new->pf) { err = -ENOMEM; goto err_alloc_percpu; diff --git a/net/sched/cls_route.c b/net/sched/cls_route.c index eded7aacd3f7..0d1324c90583 100644 --- a/net/sched/cls_route.c +++ b/net/sched/cls_route.c @@ -455,7 +455,7 @@ static int route4_set_parms(struct net *net, struct tcf_proto *tp, h1 = to_hash(nhandle); b = rtnl_dereference(head->table[h1]); if (!b) { - b = kzalloc_obj(struct route4_bucket); + b = kzalloc_obj(struct route4_bucket, GFP_KERNEL_ACCOUNT); if (b == NULL) return -ENOBUFS; @@ -524,7 +524,7 @@ static int route4_change(struct net *net, struct sk_buff *in_skb, return -EINVAL; err = -ENOBUFS; - f = kzalloc_obj(struct route4_filter); + f = kzalloc_obj(struct route4_filter, GFP_KERNEL_ACCOUNT); if (!f) goto errout; diff --git a/net/sched/cls_u32.c b/net/sched/cls_u32.c index c297d7dbcf91..ac6d0fa5a40e 100644 --- a/net/sched/cls_u32.c +++ b/net/sched/cls_u32.c @@ -825,7 +825,7 @@ static struct tc_u_knode *u32_init_knode(struct net *net, struct tcf_proto *tp, struct tc_u32_sel *s = &n->sel; struct tc_u_knode *new; - new = kzalloc_flex(*new, sel.keys, s->nkeys); + new = kzalloc_flex(*new, sel.keys, s->nkeys, GFP_KERNEL_ACCOUNT); if (!new) return NULL; @@ -1114,15 +1114,16 @@ static int u32_change(struct net *net, struct sk_buff *in_skb, goto erridr; } - n = kzalloc_flex(*n, sel.keys, s->nkeys); + n = kzalloc_flex(*n, sel.keys, s->nkeys, GFP_KERNEL_ACCOUNT); if (n == NULL) { err = -ENOBUFS; goto erridr; } #ifdef CONFIG_CLS_U32_PERF - n->pf = __alloc_percpu(struct_size(n->pf, kcnts, s->nkeys), - __alignof__(struct tc_u32_pcnt)); + n->pf = __alloc_percpu_gfp(struct_size(n->pf, kcnts, s->nkeys), + __alignof__(struct tc_u32_pcnt), + GFP_KERNEL_ACCOUNT); if (!n->pf) { err = -ENOBUFS; goto errfree; @@ -1144,7 +1145,7 @@ static int u32_change(struct net *net, struct sk_buff *in_skb, goto errout; #ifdef CONFIG_CLS_U32_MARK - n->pcpu_success = alloc_percpu(u32); + n->pcpu_success = alloc_percpu_gfp(u32, GFP_KERNEL_ACCOUNT); if (!n->pcpu_success) { err = -ENOMEM; goto errout; From 6776efe4a52f289a3fc18f8adf19b035a7d8e1bb Mon Sep 17 00:00:00 2001 From: Anton Danilov Date: Wed, 19 Aug 2026 13:43:39 +0300 Subject: [PATCH 0792/1328] ipip: fix skb leak in collect_md mode when metadata_dst allocation fails In collect_md mode ipip_tunnel_rcv() returns 0 without freeing the skb when ip_tun_rx_dst() fails to allocate the metadata_dst. ipip_rcv() and mplsip_rcv() are registered as xfrm_tunnel handlers, so tunnel4_rcv() and tunnelmpls4_rcv() read the zero return as "the packet has been consumed" and do not free it either. The skb is leaked. The other tunnel drivers all dispose of the packet at this point: ip6_tunnel.c jumps to its drop label, ip_gre.c and ip6_gre.c return PACKET_REJECT, which makes gre_rcv() free the skb. Only ipip returns 0. Jump to the existing drop label instead. It frees the skb and still returns 0, so the packet keeps being reported as consumed, which is what we want here: the outer header has already been pulled, and neither the remaining handlers nor an ICMP unreachable have any use for it. Triggering this needs an ipip or mplsip tunnel in collect_md mode and an atomic allocation failure, which is why it has gone unnoticed. Fixes: cfc7381b3002 ("ip_tunnel: add collect_md mode to IPIP tunnel") Cc: stable@vger.kernel.org Signed-off-by: Anton Danilov Reviewed-by: Fernando Fernandez Mancera Link: https://patch.msgid.link/20260819104338.432631-2-littlesmilingcloud@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv4/ipip.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/ipv4/ipip.c b/net/ipv4/ipip.c index 1630325c77d3..f684baf8e58f 100644 --- a/net/ipv4/ipip.c +++ b/net/ipv4/ipip.c @@ -248,7 +248,7 @@ static int ipip_tunnel_rcv(struct sk_buff *skb, u8 ipproto) tun_dst = ip_tun_rx_dst(skb, flags, 0, 0); if (!tun_dst) - return 0; + goto drop; ip_tunnel_md_udp_encap(skb, &tun_dst->u.tun_info); } skb_reset_mac_header(skb); From 870a9e42ecc6fe1b8c25d87af043cb0d9c178fe1 Mon Sep 17 00:00:00 2001 From: Yong Wang Date: Wed, 19 Aug 2026 23:22:04 +0800 Subject: [PATCH 0793/1328] tcp: clamp route advmss to TCP_MIN_MSS tcp_select_initial_window() assumes that callers never pass an MSS smaller than 1, but route-derived advmss values can violate that assumption. A too-small explicit RTAX_ADVMSS is one way to get there, but it is not the only one. The same divide-by-zero can also be reached through the "default advmss" path when RTAX_ADVMSS is left at 0 and the effective advmss is later driven down by route MTU and min_adv_mss. Introduce a tcp_dst_advmss() helper that clamps route advmss to TCP_MIN_MSS before TCP consumes it, and use it in the TCP paths that derive advmss from dst metrics. This keeps the effective MSS from dropping to zero before tcp_select_initial_window() rounds the receive window. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Yong Wang Signed-off-by: Ren Wei Link: https://patch.msgid.link/251eaf8277fa7c66364c9815c5da01662d269181.1787074852.git.edragain@163.com Signed-off-by: Jakub Kicinski --- include/net/tcp.h | 5 +++++ net/ipv4/tcp_ipv4.c | 2 +- net/ipv4/tcp_minisocks.c | 2 +- net/ipv4/tcp_output.c | 6 +++--- net/ipv6/tcp_ipv6.c | 2 +- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/include/net/tcp.h b/include/net/tcp.h index 2c5b889530b5..670c20876f26 100644 --- a/include/net/tcp.h +++ b/include/net/tcp.h @@ -1782,6 +1782,11 @@ static inline int tcp_full_space(const struct sock *sk) return tcp_win_from_space(sk, READ_ONCE(sk->sk_rcvbuf)); } +static inline u32 tcp_dst_advmss(const struct dst_entry *dst) +{ + return max_t(u32, dst_metric_advmss(dst), TCP_MIN_MSS); +} + static inline void __tcp_adjust_rcv_ssthresh(struct sock *sk, u32 new_ssthresh) { int unused_mem = sk_unused_reserved_mem(sk); diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c index 190c7af4cf92..9f053eb8b46e 100644 --- a/net/ipv4/tcp_ipv4.c +++ b/net/ipv4/tcp_ipv4.c @@ -1736,7 +1736,7 @@ struct sock *tcp_v4_syn_recv_sock(const struct sock *sk, struct sk_buff *skb, tcp_ca_openreq_child(newsk, dst); tcp_sync_mss(newsk, dst4_mtu(dst)); - newtp->advmss = tcp_mss_clamp(tcp_sk(sk), dst_metric_advmss(dst)); + newtp->advmss = tcp_mss_clamp(tcp_sk(sk), tcp_dst_advmss(dst)); tcp_initialize_rcv_mss(newsk); diff --git a/net/ipv4/tcp_minisocks.c b/net/ipv4/tcp_minisocks.c index 12254e6eb2f3..f3fa0b18eda0 100644 --- a/net/ipv4/tcp_minisocks.c +++ b/net/ipv4/tcp_minisocks.c @@ -440,7 +440,7 @@ void tcp_openreq_init_rwin(struct request_sock *req, u32 rcv_wnd; int mss; - mss = tcp_mss_clamp(tp, dst_metric_advmss(dst)); + mss = tcp_mss_clamp(tp, tcp_dst_advmss(dst)); window_clamp = READ_ONCE(tp->window_clamp); /* Set this up on the first call only */ req->rsk_window_clamp = window_clamp ? : dst_metric(dst, RTAX_WINDOW); diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c index fcaa04e65189..f2709d585edb 100644 --- a/net/ipv4/tcp_output.c +++ b/net/ipv4/tcp_output.c @@ -143,7 +143,7 @@ static __u16 tcp_advertise_mss(struct sock *sk) int mss = tp->advmss; if (dst) { - unsigned int metric = dst_metric_advmss(dst); + unsigned int metric = tcp_dst_advmss(dst); if (metric < mss) { mss = metric; @@ -3972,7 +3972,7 @@ struct sk_buff *tcp_make_synack(const struct sock *sk, struct dst_entry *dst, } skb_dst_set(skb, dst); - mss = tcp_mss_clamp(tp, dst_metric_advmss(dst)); + mss = tcp_mss_clamp(tp, tcp_dst_advmss(dst)); memset(&opts, 0, sizeof(opts)); now = tcp_clock_ns(); @@ -4128,7 +4128,7 @@ static void tcp_connect_init(struct sock *sk) if (!tp->window_clamp) WRITE_ONCE(tp->window_clamp, dst_metric(dst, RTAX_WINDOW)); - tp->advmss = tcp_mss_clamp(tp, dst_metric_advmss(dst)); + tp->advmss = tcp_mss_clamp(tp, tcp_dst_advmss(dst)); tcp_initialize_rcv_mss(sk); diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c index 9e9155b1b3aa..df9c29eb5c1f 100644 --- a/net/ipv6/tcp_ipv6.c +++ b/net/ipv6/tcp_ipv6.c @@ -1487,7 +1487,7 @@ static struct sock *tcp_v6_syn_recv_sock(const struct sock *sk, struct sk_buff * tcp_ca_openreq_child(newsk, dst); tcp_sync_mss(newsk, dst6_mtu(dst)); - newtp->advmss = tcp_mss_clamp(tcp_sk(sk), dst_metric_advmss(dst)); + newtp->advmss = tcp_mss_clamp(tcp_sk(sk), tcp_dst_advmss(dst)); tcp_initialize_rcv_mss(newsk); From 82e15be2d8b9efa6fb1750528d9b6f40e6a8eea7 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Thu, 20 Aug 2026 02:25:38 +0900 Subject: [PATCH 0794/1328] net: ntb_netdev: Avoid double-accounting netif_rx() drops netif_rx() already accounts packets it drops in the core rx_dropped counter. ntb_netdev counts them again as both errors and drops. Leave netif_rx() drops to the core. Count the packet and bytes unconditionally since it was received successfully by the driver. Fixes: 548c237c0a99 ("net: Add support for NTB virtual ethernet device") Cc: stable@vger.kernel.org Suggested-by: Jakub Kicinski Signed-off-by: Koichiro Den Link: https://patch.msgid.link/20260819172539.1450821-2-den@valinux.co.jp Signed-off-by: Jakub Kicinski --- drivers/net/ntb_netdev.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/drivers/net/ntb_netdev.c b/drivers/net/ntb_netdev.c index 02b35cf53a62..66405f400509 100644 --- a/drivers/net/ntb_netdev.c +++ b/drivers/net/ntb_netdev.c @@ -155,13 +155,9 @@ static void ntb_netdev_rx_handler(struct ntb_transport_qp *qp, void *qp_data, skb->ip_summed = CHECKSUM_NONE; skb_record_rx_queue(skb, q->qid); - if (netif_rx(skb) == NET_RX_DROP) { - ndev->stats.rx_errors++; - ndev->stats.rx_dropped++; - } else { - ndev->stats.rx_packets++; - ndev->stats.rx_bytes += len; - } + netif_rx(skb); + ndev->stats.rx_packets++; + ndev->stats.rx_bytes += len; skb = new_skb; From 31ded341c375bb2faac1d77ab0012a732ba3e2a6 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Thu, 20 Aug 2026 02:25:39 +0900 Subject: [PATCH 0795/1328] net: ntb_netdev: Count packets dropped on RX refill failure When replacement skb allocation fails, ntb_netdev drops a packet that was received successfully and requeues the original buffer. The drop is counted, but rx_packets and rx_bytes are not. Count every good packet before allocating its replacement. Fixes: d2121faf133a ("NTB: ntb_netdev: Preserve RX queue depth on allocation failure") Cc: stable@vger.kernel.org Signed-off-by: Koichiro Den Link: https://patch.msgid.link/20260819172539.1450821-3-den@valinux.co.jp Signed-off-by: Jakub Kicinski --- drivers/net/ntb_netdev.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/net/ntb_netdev.c b/drivers/net/ntb_netdev.c index 66405f400509..9c171697e762 100644 --- a/drivers/net/ntb_netdev.c +++ b/drivers/net/ntb_netdev.c @@ -144,6 +144,9 @@ static void ntb_netdev_rx_handler(struct ntb_transport_qp *qp, void *qp_data, goto enqueue_again; } + ndev->stats.rx_packets++; + ndev->stats.rx_bytes += len; + new_skb = netdev_alloc_skb(ndev, ndev->mtu + ETH_HLEN); if (!new_skb) { ndev->stats.rx_dropped++; @@ -156,8 +159,6 @@ static void ntb_netdev_rx_handler(struct ntb_transport_qp *qp, void *qp_data, skb_record_rx_queue(skb, q->qid); netif_rx(skb); - ndev->stats.rx_packets++; - ndev->stats.rx_bytes += len; skb = new_skb; From 71283aaa6c65b3cec84caf1dc78560985737641f Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Tue, 18 Aug 2026 23:45:15 +0800 Subject: [PATCH 0796/1328] xdp: fix zero-copy frame layout xdp_convert_zc_to_xdp_frame() clones an XSK packet into an order-0 page and advertises PAGE_SIZE as its frame size. It allows the copied frame to occupy the page tail needed by skb_shared_info and records zero headroom even when metadata separates the frame header from packet data. An AF_XDP zero-copy packet redirected through cpumap can therefore make the skb overlap skb_shared_info or place it beyond the allocated page. Limit the copied layout to SKB_WITH_OVERHEAD(PAGE_SIZE) and include the metadata length in frame headroom. Redirect callers already handle a NULL conversion result. BUG: KASAN: slab-out-of-bounds in skb_gro_receive Write of size 4 at addr ffff88800cf37004 by task cpumap/1/map:1/146 Call Trace: skb_gro_receive (net/core/gro.c:174) udp_gro_receive (net/ipv4/udp_offload.c:812) inet_gro_receive (net/ipv4/af_inet.c:1539) dev_gro_receive (net/core/gro.c:515) gro_receive_skb (net/core/gro.c:633) cpu_map_kthread_run (kernel/bpf/cpumap.c:395) kthread (kernel/kthread.c:436) ret_from_fork (arch/x86/kernel/process.c:164) ret_from_fork_asm (arch/x86/entry/entry_64.S:255) Kernel panic - not syncing: KASAN: panic_on_warn set ... Fixes: b0d1beeff2a9 ("xdp: implement convert_to_xdp_frame for MEM_TYPE_ZERO_COPY") Cc: stable@vger.kernel.org Reported-by: Xiang Mei Signed-off-by: Weiming Shi Link: https://patch.msgid.link/20260818154516.793517-1-bestswngs@gmail.com Signed-off-by: Jakub Kicinski --- net/core/xdp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/core/xdp.c b/net/core/xdp.c index 0194e69da339..1d679e8fd649 100644 --- a/net/core/xdp.c +++ b/net/core/xdp.c @@ -585,7 +585,7 @@ struct xdp_frame *xdp_convert_zc_to_xdp_frame(struct xdp_buff *xdp) xdp->data - xdp->data_meta; totsize = xdp->data_end - xdp->data + metasize; - if (sizeof(*xdpf) + totsize > PAGE_SIZE) + if (sizeof(*xdpf) + totsize > SKB_WITH_OVERHEAD(PAGE_SIZE)) return NULL; page = dev_alloc_page(); @@ -602,7 +602,7 @@ struct xdp_frame *xdp_convert_zc_to_xdp_frame(struct xdp_buff *xdp) xdpf->data = addr + metasize; xdpf->len = totsize - metasize; - xdpf->headroom = 0; + xdpf->headroom = metasize; xdpf->metasize = metasize; xdpf->frame_sz = PAGE_SIZE; xdpf->mem_type = MEM_TYPE_PAGE_ORDER0; From 498386b6d402737db1e2eeed4c385acbf0ef9e34 Mon Sep 17 00:00:00 2001 From: Qing Ming Date: Tue, 18 Aug 2026 23:00:00 +0800 Subject: [PATCH 0797/1328] gtp: serialize PDP context updates PDP contexts can be deleted through GTP_CMD_DELPDP or while the GTP network device is being unregistered. The latter is serialized by RTNL, but the generic-netlink delete path only holds RCU. Running both paths concurrently can therefore make both paths delete the same PDP context. The issue was found through static analysis and reproduced on a KASAN-enabled kernel by a simple two-thread program racing GTP_CMD_DELPDP against RTM_DELLINK: Oops: general protection fault, probably for non-canonical address KASAN: maybe wild-memory-access in range [0xdead000000000120-0xdead000000000127] RIP: gtp_genl_del_pdp+0x1c1/0x420 [gtp] RBP: dead000000000122 The second deletion dereferenced the poisoned hlist pprev pointer. Serialize gtp_pdp_add(), gtp_genl_del_pdp(), and gtp_dellink() with a shared mutex. Keep the mutex held until the final use of a PDP context in the NEWPDP path, and keep the RCU read-side section around the complete PDP context use in the DELPDP path. Fixes: 459aa660eb1d ("gtp: add initial driver for datapath of GPRS Tunneling Protocol (GTP-U)") Cc: stable@vger.kernel.org Signed-off-by: Qing Ming Link: https://patch.msgid.link/20260818150000.7670-1-a0yami@mailbox.org Signed-off-by: Jakub Kicinski --- drivers/net/gtp.c | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/drivers/net/gtp.c b/drivers/net/gtp.c index 9a12cc53da00..2f6e77cce385 100644 --- a/drivers/net/gtp.c +++ b/drivers/net/gtp.c @@ -12,6 +12,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include +#include #include #include #include @@ -108,6 +109,7 @@ struct gtp_net { }; static u32 gtp_h_initval; +static DEFINE_MUTEX(gtp_pdp_lock); static struct genl_family gtp_genl_family; @@ -151,7 +153,8 @@ static struct pdp_ctx *gtp0_pdp_find(struct gtp_dev *gtp, u64 tid, u16 family) head = >p->tid_hash[gtp0_hashfn(tid) % gtp->hash_size]; - hlist_for_each_entry_rcu(pdp, head, hlist_tid) { + hlist_for_each_entry_rcu(pdp, head, hlist_tid, + lockdep_is_held(>p_pdp_lock)) { if (pdp->af == family && pdp->gtp_version == GTP_V0 && pdp->u.v0.tid == tid) @@ -168,7 +171,8 @@ static struct pdp_ctx *gtp1_pdp_find(struct gtp_dev *gtp, u32 tid, u16 family) head = >p->tid_hash[gtp1u_hashfn(tid) % gtp->hash_size]; - hlist_for_each_entry_rcu(pdp, head, hlist_tid) { + hlist_for_each_entry_rcu(pdp, head, hlist_tid, + lockdep_is_held(>p_pdp_lock)) { if (pdp->af == family && pdp->gtp_version == GTP_V1 && pdp->u.v1.i_tei == tid) @@ -185,7 +189,8 @@ static struct pdp_ctx *ipv4_pdp_find(struct gtp_dev *gtp, __be32 ms_addr) head = >p->addr_hash[ipv4_hashfn(ms_addr) % gtp->hash_size]; - hlist_for_each_entry_rcu(pdp, head, hlist_addr) { + hlist_for_each_entry_rcu(pdp, head, hlist_addr, + lockdep_is_held(>p_pdp_lock)) { if (pdp->af == AF_INET && pdp->ms.addr.s_addr == ms_addr) return pdp; @@ -220,7 +225,8 @@ static struct pdp_ctx *ipv6_pdp_find(struct gtp_dev *gtp, head = >p->addr_hash[ipv6_hashfn(ms_addr) % gtp->hash_size]; - hlist_for_each_entry_rcu(pdp, head, hlist_addr) { + hlist_for_each_entry_rcu(pdp, head, hlist_addr, + lockdep_is_held(>p_pdp_lock)) { if (pdp->af == AF_INET6 && ipv6_pdp_addr_equal(&pdp->ms.addr6, ms_addr)) return pdp; @@ -1555,9 +1561,11 @@ static void gtp_dellink(struct net_device *dev, struct list_head *head) struct pdp_ctx *pctx; int i; + mutex_lock(>p_pdp_lock); for (i = 0; i < gtp->hash_size; i++) hlist_for_each_entry_safe(pctx, next, >p->tid_hash[i], hlist_tid) pdp_context_delete(pctx); + mutex_unlock(>p_pdp_lock); list_del(>p->list); unregister_netdevice_queue(dev, head); @@ -2053,6 +2061,7 @@ static int gtp_genl_new_pdp(struct sk_buff *skb, struct genl_info *info) goto out_unlock; } + mutex_lock(>p_pdp_lock); pctx = gtp_pdp_add(gtp, sk, info); if (IS_ERR(pctx)) { err = PTR_ERR(pctx); @@ -2060,6 +2069,7 @@ static int gtp_genl_new_pdp(struct sk_buff *skb, struct genl_info *info) gtp_tunnel_notify(pctx, GTP_CMD_NEWPDP, GFP_KERNEL); err = 0; } + mutex_unlock(>p_pdp_lock); out_unlock: rtnl_unlock(); @@ -2134,6 +2144,8 @@ static int gtp_genl_del_pdp(struct sk_buff *skb, struct genl_info *info) if (!info->attrs[GTPA_VERSION]) return -EINVAL; + mutex_lock(>p_pdp_lock); + rcu_read_lock(); pctx = gtp_find_pdp(sock_net(skb->sk), info->attrs); @@ -2154,6 +2166,7 @@ static int gtp_genl_del_pdp(struct sk_buff *skb, struct genl_info *info) out_unlock: rcu_read_unlock(); + mutex_unlock(>p_pdp_lock); return err; } From b7adcc56fd3db4f5ddaf8c01069d26136d61e5c8 Mon Sep 17 00:00:00 2001 From: Daniel Machon Date: Mon, 17 Aug 2026 17:41:58 +0200 Subject: [PATCH 0798/1328] net: microchip: vcap: use port number instead of netdev name for debugfs sparx5_vcap_init() runs before sparx5_register_netdevs() in probe, and its debugfs setup calls vcap_port_debugfs() for every port using netdev_name(ndev) as the debugfs file name. At that point the netdevs have only been allocated, not registered, so dev->name still holds the "eth%d" template and netdev_name() returns "(unnamed net_device)". Every port tries to create the same file under vcaps/, producing a flood of warnings at boot: debugfs: '(unnamed net_device)' already exists in 'vcaps' debugfs: '(unnamed net_device)' already exists in 'vcaps' ... Add vcap_port_debugfs_portno(), a variant of vcap_port_debugfs() that takes the port's stable hardware port number and uses "p%u" as the debugfs file name instead of netdev_name(ndev). This makes the file name independent of registration order; the file still stores and later dereferences the netdev itself, same as before. sparx5 already reports the same "p%d" string via ndo_get_phys_port_name(), so the debugfs name now matches that. Only sparx5 (and lan969x, which shares this code) is switched to the new function. lan966x keeps calling vcap_port_debugfs() unchanged, so this fix does not rename any of its existing debugfs files. Fixes: b8909aad5b8d ("net: sparx5: move netdev and notifier block registration to probe") Signed-off-by: Daniel Machon Link: https://patch.msgid.link/20260817-misc-fixes-sparx5-lan969x-v3-1-c7c7fef723a8@microchip.com Signed-off-by: Jakub Kicinski --- .../microchip/sparx5/sparx5_vcap_impl.c | 5 +++-- .../microchip/vcap/vcap_api_debugfs.c | 21 +++++++++++++++++++ .../microchip/vcap/vcap_api_debugfs.h | 14 +++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_impl.c b/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_impl.c index cf332de6bf73..2dee2ce19fce 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_impl.c +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_vcap_impl.c @@ -2077,8 +2077,9 @@ int sparx5_vcap_init(struct sparx5 *sparx5) dir = vcap_debugfs(sparx5->dev, sparx5->debugfs_root, ctrl); for (idx = 0; idx < consts->n_ports; ++idx) if (sparx5->ports[idx]) - vcap_port_debugfs(sparx5->dev, dir, ctrl, - sparx5->ports[idx]->ndev); + vcap_port_debugfs_portno(sparx5->dev, dir, ctrl, + sparx5->ports[idx]->ndev, + sparx5->ports[idx]->portno); return err; } diff --git a/drivers/net/ethernet/microchip/vcap/vcap_api_debugfs.c b/drivers/net/ethernet/microchip/vcap/vcap_api_debugfs.c index e0c65c7ab23e..476f7496a9d4 100644 --- a/drivers/net/ethernet/microchip/vcap/vcap_api_debugfs.c +++ b/drivers/net/ethernet/microchip/vcap/vcap_api_debugfs.c @@ -400,6 +400,27 @@ void vcap_port_debugfs(struct device *dev, struct dentry *parent, } EXPORT_SYMBOL_GPL(vcap_port_debugfs); +void vcap_port_debugfs_portno(struct device *dev, + struct dentry *parent, + struct vcap_control *vctrl, + struct net_device *ndev, + unsigned int portno) +{ + struct vcap_port_debugfs_info *info; + char name[16]; + + info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); + if (!info) + return; + + info->vctrl = vctrl; + info->ndev = ndev; + + snprintf(name, sizeof(name), "p%u", portno); + debugfs_create_file(name, 0444, parent, info, &vcap_port_debugfs_fops); +} +EXPORT_SYMBOL_GPL(vcap_port_debugfs_portno); + /* Show the full VCAP instance data (rules with all fields) */ static int vcap_debugfs_show(struct seq_file *m, void *unused) { diff --git a/drivers/net/ethernet/microchip/vcap/vcap_api_debugfs.h b/drivers/net/ethernet/microchip/vcap/vcap_api_debugfs.h index 9f2c59b5f6f5..7dc6e3411a4d 100644 --- a/drivers/net/ethernet/microchip/vcap/vcap_api_debugfs.h +++ b/drivers/net/ethernet/microchip/vcap/vcap_api_debugfs.h @@ -18,6 +18,12 @@ void vcap_port_debugfs(struct device *dev, struct dentry *parent, struct vcap_control *vctrl, struct net_device *ndev); +void vcap_port_debugfs_portno(struct device *dev, + struct dentry *parent, + struct vcap_control *vctrl, + struct net_device *ndev, + unsigned int portno); + /* Create a debugFS entry for a vcap instance */ struct dentry *vcap_debugfs(struct device *dev, struct dentry *parent, struct vcap_control *vctrl); @@ -30,6 +36,14 @@ static inline void vcap_port_debugfs(struct device *dev, struct dentry *parent, { } +static inline void vcap_port_debugfs_portno(struct device *dev, + struct dentry *parent, + struct vcap_control *vctrl, + struct net_device *ndev, + unsigned int portno) +{ +} + static inline struct dentry *vcap_debugfs(struct device *dev, struct dentry *parent, struct vcap_control *vctrl) From b62793a7baeea9cf20209c9fd2e333311aaf3b8d Mon Sep 17 00:00:00 2001 From: Daniel Machon Date: Mon, 17 Aug 2026 17:41:59 +0200 Subject: [PATCH 0799/1328] net: sparx5: fix sleep in atomic context in MAC table access sparx5_set_rx_mode() runs with netif_addr_lock_bh held and iterates dev->mc via __dev_mc_sync(), which per address calls sparx5_mc_sync() / sparx5_mc_unsync() -> sparx5_mact_learn() / sparx5_mact_forget(). These take sparx5->lock, a mutex, and then poll the MAC access command register with readx_poll_timeout(). A mutex may block, which is not allowed from atomic context. Convert the driver to the new .ndo_set_rx_mode_async callback introduced in commit 3554b4345d85 ("net: introduce ndo_set_rx_mode_async and netdev_rx_mode_work"). The async callback is invoked from process context, so the mutex and sleeping completion poll can remain. Observed with CONFIG_PROVE_LOCKING, CONFIG_DEBUG_SPINLOCK, CONFIG_DEBUG_MUTEXES and CONFIG_DEBUG_ATOMIC_SLEEP enabled: BUG: sleeping function called from invalid context at kernel/locking/mutex.c:591 in_atomic(): 1, irqs_disabled(): 0, non_block: 0, pid: 217, name: ip preempt_count: 201, expected: 0 Call trace: __might_resched+0x144/0x248 __might_sleep+0x48/0x7c __mutex_lock+0x74/0x850 mutex_lock_nested+0x24/0x30 sparx5_mact_learn+0x78/0x100 sparx5_mc_sync+0x40/0x54 __hw_addr_sync_dev+0xc4/0x170 sparx5_set_rx_mode+0x4c/0x58 __dev_set_rx_mode+0x64/0xa4 __dev_open+0x1ec/0x26c Fixes: d6fce5141929 ("net: sparx5: add switching support") Signed-off-by: Daniel Machon Link: https://patch.msgid.link/20260817-misc-fixes-sparx5-lan969x-v3-2-c7c7fef723a8@microchip.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/microchip/sparx5/sparx5_netdev.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_netdev.c b/drivers/net/ethernet/microchip/sparx5/sparx5_netdev.c index 1d34af78166a..4dedf5d92641 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_netdev.c +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_netdev.c @@ -162,13 +162,18 @@ static int sparx5_port_stop(struct net_device *ndev) return 0; } -static void sparx5_set_rx_mode(struct net_device *dev) +static int sparx5_set_rx_mode(struct net_device *dev, + struct netdev_hw_addr_list *uc, + struct netdev_hw_addr_list *mc) { struct sparx5_port *port = netdev_priv(dev); struct sparx5 *sparx5 = port->sparx5; if (!test_bit(port->portno, sparx5->bridge_mask)) - __dev_mc_sync(dev, sparx5_mc_sync, sparx5_mc_unsync); + return __hw_addr_sync_dev(mc, dev, sparx5_mc_sync, + sparx5_mc_unsync); + + return 0; } static int sparx5_port_get_phys_port_name(struct net_device *dev, @@ -249,7 +254,7 @@ static const struct net_device_ops sparx5_port_netdev_ops = { .ndo_open = sparx5_port_open, .ndo_stop = sparx5_port_stop, .ndo_start_xmit = sparx5_port_xmit_impl, - .ndo_set_rx_mode = sparx5_set_rx_mode, + .ndo_set_rx_mode_async = sparx5_set_rx_mode, .ndo_get_phys_port_name = sparx5_port_get_phys_port_name, .ndo_set_mac_address = sparx5_set_mac_address, .ndo_validate_addr = eth_validate_addr, From 7bf29145d7a9564162c6b18f8d23760e141e2d15 Mon Sep 17 00:00:00 2001 From: Jiawen Wu Date: Tue, 18 Aug 2026 10:30:26 +0800 Subject: [PATCH 0800/1328] net: txgbe: fix MISC interrupt unmasking in non-MSI-X mode and device shutdown In txgbe_misc_irq_thread_fn(), the driver unmasks the miscellaneous interrupt at the end of the handler using TXGBE_INTR_MISC(wx) (which resolves to BIT(wx->num_q_vectors)). While this is correct for MSI-X mode, it is incorrect for legacy INTx or single MSI modes. Due to hardware behavior, the WX_PX_MISC_IVAR register is completely ignored by the hardware when MSI-X is disabled. In non-MSI-X mode, the hardware forcibly merges all interrupt causes (both Queue and MISC) into a single bit: BIT(0) of the interrupt register. Unconditionally unmasking TXGBE_INTR_MISC(wx) (e.g., BIT(1)) in non-MSI-X mode means the actual MISC interrupt bit (BIT(0)) is not unmasked promptly at the end of the MISC thread. Instead, it remains masked until NAPI completes its polling and unmasks the shared BIT(0). This delays the assertion of subsequent MISC interrupts, preventing timely handling of events like link state changes. Fix this by explicitly checking `pdev->msix_enabled` and falling back to BIT(0) as the interrupt mask for the MISC cause when MSI-X is disabled. Additionally, unconditionally unmasking the interrupt at the end of the thread introduces a race condition during device teardown. Guarding the wx_intr_enable() call with a check for the WX_STATE_DOWN bit, to prevent re-arming the interrupt during device shutdown. Fixes: e37546ad1f9b ("net: wangxun: revert the adjustment of the IRQ vector sequence") Signed-off-by: Jiawen Wu Reviewed-by: Simon Horman Link: https://patch.msgid.link/56A53978B83EEDE9+20260818023026.6631-1-jiawenwu@trustnetic.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/wangxun/txgbe/txgbe_irq.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_irq.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_irq.c index 8746318ad3bc..5ad1ff7c3ce9 100644 --- a/drivers/net/ethernet/wangxun/txgbe/txgbe_irq.c +++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_irq.c @@ -164,6 +164,7 @@ static irqreturn_t txgbe_misc_irq_thread_fn(int irq, void *data) struct wx *wx = txgbe->wx; unsigned int nhandled = 0; unsigned int sub_irq; + u64 misc_mask; u32 eicr; eicr = txgbe->eicr; @@ -183,7 +184,9 @@ static irqreturn_t txgbe_misc_irq_thread_fn(int irq, void *data) nhandled++; } - wx_intr_enable(wx, TXGBE_INTR_MISC(wx)); + misc_mask = wx->pdev->msix_enabled ? TXGBE_INTR_MISC(wx) : BIT(0); + if (!test_bit(WX_STATE_DOWN, wx->state)) + wx_intr_enable(wx, misc_mask); return (nhandled > 0 ? IRQ_HANDLED : IRQ_NONE); } From 06aa3d26327f24edd039ff249672fdf6f2ba5695 Mon Sep 17 00:00:00 2001 From: Vaibhav Nagare Date: Tue, 18 Aug 2026 13:03:09 +0530 Subject: [PATCH 0801/1328] qede: Fix NULL pointer dereference in TPA fragment processing Under memory pressure, the qede driver encounters NULL pointer dereferences when processing TPA continuation fragments. Commit 8a8633978b84 ("qede: Add build_skb() support.") accidentally dropped the assignment of tpa_info->buffer.data in qede_tpa_start(). When memory pressure causes an SKB allocation failure in qede_tpa_start(), the driver sets tpa_start_fail = true and attempts to recycle the physical page later in qede_tpa_end() via qede_reuse_page(). However, because buffer.data was left uninitialized (NULL), qede_reuse_page() pushes a "ghost" BD (valid DMA mapping but NULL data pointer) back into the active Rx ring. The next time the hardware uses this ring slot, it passes a NULL page to qede_fill_frag_skb(), causing a kernel panic. Example crash from production system: BUG: unable to handle kernel NULL pointer dereference at 0x8 RIP: qede_fill_frag_skb+0x96/0x430 [qede] Call Trace: qede_rx_int+0xb06/0x1de0 qede_poll+0x2f4/0x6c0 __napi_poll+0x2d/0x130 Fix the root cause by restoring the tpa_info->buffer.data assignment in qede_tpa_start(), ensuring valid pages are correctly tracked and recycled. Additionally, update the stale comment for struct qede_agg_info::buffer to reflect its current usage. Fixes: 8a8633978b84 ("qede: Add build_skb() support.") Cc: stable@vger.kernel.org Signed-off-by: Vaibhav Nagare Link: https://patch.msgid.link/20260818073309.2266072-1-vnagare@redhat.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/qlogic/qede/qede.h | 8 ++++---- drivers/net/ethernet/qlogic/qede/qede_fp.c | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/qlogic/qede/qede.h b/drivers/net/ethernet/qlogic/qede/qede.h index 042a75f34060..0e7a0c2c1765 100644 --- a/drivers/net/ethernet/qlogic/qede/qede.h +++ b/drivers/net/ethernet/qlogic/qede/qede.h @@ -303,10 +303,10 @@ enum qede_agg_state { }; struct qede_agg_info { - /* rx_buf is a data buffer that can be placed / consumed from rx bd - * chain. It has two purposes: We will preallocate the data buffer - * for each aggregation when we open the interface and will place this - * buffer on the rx-bd-ring when we receive TPA_START. We don't want + /* buffer is used to retain the Rx consumer descriptor when a TPA + * session starts. If the SKB allocation fails during TPA_START, + * we use this saved buffer to safely recycle the physical page + * back into the rx-bd-ring via qede_reuse_page(). We don't want * to be in a state where allocation fails, as we can't reuse the * consumer buffer in the rx-chain since FW may still be writing to it * (since header needs to be modified for TPA). diff --git a/drivers/net/ethernet/qlogic/qede/qede_fp.c b/drivers/net/ethernet/qlogic/qede/qede_fp.c index c11e0d8f98aa..a02624f6d6d5 100644 --- a/drivers/net/ethernet/qlogic/qede/qede_fp.c +++ b/drivers/net/ethernet/qlogic/qede/qede_fp.c @@ -850,6 +850,7 @@ static void qede_tpa_start(struct qede_dev *edev, pad, false); tpa_info->buffer.page_offset = sw_rx_data_cons->page_offset; tpa_info->buffer.mapping = sw_rx_data_cons->mapping; + tpa_info->buffer.data = sw_rx_data_cons->data; if (unlikely(!tpa_info->skb)) { DP_NOTICE(edev, "Failed to allocate SKB for gro\n"); From f05516dd7b865666ea7d67e90d0edb588ae9ad18 Mon Sep 17 00:00:00 2001 From: Jiawen Wu Date: Tue, 18 Aug 2026 15:47:21 +0800 Subject: [PATCH 0802/1328] net: libwx: fix concurrent bitmap overwrite in PTP setup In wx_ptp_set_timestamp_mode(), the driver copies the global `wx->flags` bitmap to a local variable, modifies the PTP-related bits, and then writes the entire bitmap back using memcpy(). This Read-Copy-Update pattern is unsafe and introduces a critical race condition. Other asynchronous contexts (such as Tx timeout routines or GPIO IRQ handlers) update individual bits in `wx->flags` concurrently using atomic bitops like set_bit() or clear_bit(). The memcpy() write-back can silently overwrite and drop these concurrent changes, potentially causing the driver to miss critical module reset or PCIe recovery requests. Fix this by removing the local bitmap copy. Instead, evaluate the intended PTP flag states locally and apply them directly to `wx->flags` using atomic set_bit() and clear_bit() operations only after the hardware is successfully configured. Fixes: 06e75161b9d4 ("net: wangxun: Add support for PTP clock") Signed-off-by: Jiawen Wu Reviewed-by: Vadim Fedorenko Link: https://patch.msgid.link/6C7EC12D69217315+20260818074721.45536-1-jiawenwu@trustnetic.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/wangxun/libwx/wx_ptp.c | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ptp.c b/drivers/net/ethernet/wangxun/libwx/wx_ptp.c index 3eea647c4742..4708e7f3958f 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_ptp.c +++ b/drivers/net/ethernet/wangxun/libwx/wx_ptp.c @@ -558,13 +558,11 @@ static int wx_ptp_set_timestamp_mode(struct wx *wx, { u32 tsync_tx_ctl = WX_TSC_1588_CTL_ENABLED; u32 tsync_rx_ctl = WX_PSR_1588_CTL_ENABLED; - DECLARE_BITMAP(flags, WX_PF_FLAGS_NBITS); u32 tsync_rx_mtrl = PTP_EV_PORT << 16; + bool rx_tstamp = false; bool is_l2 = false; u32 regval; - memcpy(flags, wx->flags, sizeof(wx->flags)); - switch (config->tx_type) { case HWTSTAMP_TX_OFF: tsync_tx_ctl = 0; @@ -579,20 +577,16 @@ static int wx_ptp_set_timestamp_mode(struct wx *wx, case HWTSTAMP_FILTER_NONE: tsync_rx_ctl = 0; tsync_rx_mtrl = 0; - clear_bit(WX_FLAG_RX_HWTSTAMP_ENABLED, flags); - clear_bit(WX_FLAG_RX_HWTSTAMP_IN_REGISTER, flags); break; case HWTSTAMP_FILTER_PTP_V1_L4_SYNC: tsync_rx_ctl |= WX_PSR_1588_CTL_TYPE_L4_V1; tsync_rx_mtrl |= WX_PSR_1588_MSG_V1_SYNC; - set_bit(WX_FLAG_RX_HWTSTAMP_ENABLED, flags); - set_bit(WX_FLAG_RX_HWTSTAMP_IN_REGISTER, flags); + rx_tstamp = true; break; case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ: tsync_rx_ctl |= WX_PSR_1588_CTL_TYPE_L4_V1; tsync_rx_mtrl |= WX_PSR_1588_MSG_V1_DELAY_REQ; - set_bit(WX_FLAG_RX_HWTSTAMP_ENABLED, flags); - set_bit(WX_FLAG_RX_HWTSTAMP_IN_REGISTER, flags); + rx_tstamp = true; break; case HWTSTAMP_FILTER_PTP_V2_EVENT: case HWTSTAMP_FILTER_PTP_V2_L2_EVENT: @@ -605,9 +599,8 @@ static int wx_ptp_set_timestamp_mode(struct wx *wx, case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ: tsync_rx_ctl |= WX_PSR_1588_CTL_TYPE_EVENT_V2; is_l2 = true; + rx_tstamp = true; config->rx_filter = HWTSTAMP_FILTER_PTP_V2_EVENT; - set_bit(WX_FLAG_RX_HWTSTAMP_ENABLED, flags); - set_bit(WX_FLAG_RX_HWTSTAMP_IN_REGISTER, flags); break; default: /* register PSR_1588_MSG must be set in order to do V1 packets, @@ -646,7 +639,8 @@ static int wx_ptp_set_timestamp_mode(struct wx *wx, WX_WRITE_FLUSH(wx); /* configure adapter flags only when HW is actually configured */ - memcpy(wx->flags, flags, sizeof(wx->flags)); + assign_bit(WX_FLAG_RX_HWTSTAMP_ENABLED, wx->flags, rx_tstamp); + assign_bit(WX_FLAG_RX_HWTSTAMP_IN_REGISTER, wx->flags, rx_tstamp); /* clear TX/RX timestamp state, just to be sure */ wx_ptp_clear_tx_timestamp(wx); From a42a459ef0e54cb0c4b3e43e21cb0e658e664f64 Mon Sep 17 00:00:00 2001 From: Yehyeong Lee Date: Wed, 19 Aug 2026 11:33:04 +0900 Subject: [PATCH 0803/1328] net/smc: fix use-after-free of the LLC qentry in smc_llc_srv_add_link() smc_llc_srv_add_link() keeps add_llc pointing into the queue entry: add_llc = &qentry->msg.add_link; smc_llc.c:1482 ... smc_llc_save_add_link_info(link_new, add_llc); smc_llc.c:1494 smc_llc_flow_qentry_del(&lgr->llc_flow_lcl); smc_llc.c:1495 ... u8 *llc_msg = smc_link_shared_v2_rxbuf(link) ? (u8 *)lgr->wr_rx_buf_v2 : (u8 *)add_llc; smc_llc.c:1504 smc_llc_save_add_link_rkeys(link, link_new, llc_msg); smc_llc.c:1506 smc_llc_flow_qentry_del() kfree()s the entry, so on a link without a shared v2 receive buffer the pointer handed to smc_llc_save_add_link_rkeys() is already freed. Before the Fixes: commit that branch always used lgr->wr_rx_buf_v2 and add_llc was not used after the free. Reproduced on an unpatched tree over rxe, with KASAN, kasan_multi_shot and a link forced to max_recv_sge == 1: the entry is freed and read by the same call, and the freeing frame is smc_llc_srv_add_link() itself. [ 2.523161] BUG: KASAN: slab-use-after-free in smc_llc_save_add_link_rkeys+0x333/0x350 [ 2.523499] Read of size 2 at addr ffff8880052194de by task kworker/0:1/11 [ 2.523789] [ 2.523862] CPU: 0 UID: 0 PID: 11 Comm: kworker/0:1 Not tainted 7.2.0-rc5-p0-g2c9dd296545d #35 PREEMPT(lazy) [ 2.523865] Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 [ 2.523866] Workqueue: smc_hs_wq smc_listen_work [ 2.523869] Call Trace: [ 2.523870] [ 2.523871] dump_stack_lvl+0x53/0x70 [ 2.523872] print_report+0xd0/0x630 [ 2.523874] ? __pfx__raw_spin_lock_irqsave+0x10/0x10 [ 2.523876] ? smc_llc_save_add_link_rkeys+0x333/0x350 [ 2.523878] kasan_report+0xce/0x100 [ 2.523879] ? smc_llc_save_add_link_rkeys+0x333/0x350 [ 2.523881] smc_llc_save_add_link_rkeys+0x333/0x350 [ 2.523883] ? smcr_buf_reg_lgr+0x2a4/0x660 [ 2.523885] smc_llc_srv_add_link+0xaa2/0x1e50 [ 2.523888] ? _printk+0xba/0xf0 [ 2.523897] ? __pfx_smc_llc_srv_add_link+0x10/0x10 [ 2.523899] ? down_write+0xb0/0x130 [ 2.523903] ? __pfx_down_write+0x10/0x10 [ 2.523905] smc_listen_work+0x489e/0x4d00 [ 2.523907] ? kmem_cache_free+0x1c6/0x3a0 [ 2.523911] ? __pfx_smc_listen_work+0x10/0x10 [ 2.523913] ? release_sock+0x148/0x1d0 [ 2.523915] ? smc_tcp_listen_work+0xb4f/0xfc0 [ 2.523917] ? _raw_spin_lock_irq+0x80/0xe0 [ 2.523918] ? __pfx__raw_spin_lock_irq+0x10/0x10 [ 2.523920] process_one_work+0x633/0x1030 [ 2.523922] ? assign_work+0x11d/0x370 [ 2.523924] worker_thread+0x45b/0xd10 [ 2.523926] ? __pfx_worker_thread+0x10/0x10 [ 2.523928] ? __pfx_worker_thread+0x10/0x10 [ 2.523929] kthread+0x2c6/0x3b0 [ 2.523931] ? recalc_sigpending+0x15c/0x1e0 [ 2.523934] ? __pfx_kthread+0x10/0x10 [ 2.523935] ret_from_fork+0x36e/0x5a0 [ 2.523937] ? __pfx_ret_from_fork+0x10/0x10 [ 2.523938] ? __switch_to+0x572/0xdd0 [ 2.523943] ? __pfx_kthread+0x10/0x10 [ 2.523944] ret_from_fork_asm+0x1a/0x30 [ 2.523947] [ 2.523948] [ 2.531253] Allocated by task 48: [ 2.531399] kasan_save_stack+0x33/0x60 [ 2.531570] kasan_save_track+0x14/0x30 [ 2.531737] __kasan_kmalloc+0x8f/0xa0 [ 2.531905] __kmalloc_cache_noprof+0x158/0x370 [ 2.532100] smc_llc_enqueue+0x72/0x560 [ 2.532268] smc_wr_rx_tasklet_fn+0x474/0xa80 [ 2.532491] tasklet_action_common+0x20f/0x8a0 [ 2.532714] handle_softirqs+0x18e/0x590 [ 2.532886] do_softirq+0x3b/0x60 [ 2.533036] __local_bh_enable_ip+0x61/0x70 [ 2.533221] __alloc_skb+0x732/0x890 [ 2.533384] rxe_init_packet+0x16b/0x4f0 [ 2.533567] prepare_ack_packet+0xb8/0x830 [ 2.533760] rxe_receiver+0x495/0x96e0 [ 2.533933] do_work+0x144/0x470 [ 2.534078] process_one_work+0x633/0x1030 [ 2.534257] worker_thread+0x45b/0xd10 [ 2.534424] kthread+0x2c6/0x3b0 [ 2.534569] ret_from_fork+0x36e/0x5a0 [ 2.534737] ret_from_fork_asm+0x1a/0x30 [ 2.534907] [ 2.534980] Freed by task 11: [ 2.535112] kasan_save_stack+0x33/0x60 [ 2.535279] kasan_save_track+0x14/0x30 [ 2.535444] kasan_save_free_info+0x3b/0x60 [ 2.535625] __kasan_slab_free+0x43/0x70 [ 2.535798] kfree+0x121/0x380 [ 2.535935] smc_llc_srv_add_link+0x9a8/0x1e50 [ 2.536128] smc_listen_work+0x489e/0x4d00 [ 2.536305] process_one_work+0x633/0x1030 [ 2.536482] worker_thread+0x45b/0xd10 [ 2.536652] kthread+0x2c6/0x3b0 [ 2.536794] ret_from_fork+0x36e/0x5a0 [ 2.536958] ret_from_fork_asm+0x1a/0x30 [ 2.537133] [ 2.537205] The buggy address belongs to the object at ffff888005219480 [ 2.537205] which belongs to the cache kmalloc-96 of size 96 [ 2.537719] The buggy address is located 94 bytes inside of [ 2.537719] freed 96-byte region [ffff888005219480, ffff8880052194e0) [ 2.538216] [ 2.538289] The buggy address belongs to the physical page: [ 2.538524] page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x5219 [ 2.538857] flags: 0x100000000000000(node=0|zone=1) [ 2.539066] page_type: f5(slab) [ 2.539210] raw: 0100000000000000 ffff888001041280 dead000000000122 0000000000000000 [ 2.539534] raw: 0000000000000000 0000000000200020 00000000f5000000 0000000000000000 [ 2.539863] page dumped because: kasan: bad access detected [ 2.540098] [ 2.540170] Memory state around the buggy address: [ 2.540379] ffff888005219380: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [ 2.540684] ffff888005219400: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [ 2.540988] >ffff888005219480: fa fb fb fb fb fb fb fb fb fb fb fb fc fc fc fc [ 2.541291] ^ [ 2.541548] ffff888005219500: 00 00 00 00 00 00 00 00 00 fc fc fc fc fc fc fc [ 2.541857] ffff888005219580: 00 00 00 00 00 00 00 00 00 fc fc fc fc fc fc fc The offset is past the 72-byte queue entry because the out-of-bounds read fixed by the next patch is on the same line; what this patch removes is the free at smc_llc_srv_add_link+0x9a8 happening before the read at +0xaa2. Detach the entry instead of freeing it there, and free it at the single exit label. The reject path has to detach as well, otherwise it would be freed twice. This changes only the lifetime of the entry. The same read still runs past its end until the next two patches bound it, so a backport wants all three. Fixes: 27ef6a9981fe ("net/smc: support SMC-R V2 for rdma devices with max_recv_sge equals to 1") Cc: stable@vger.kernel.org Reviewed-by: Sidraya Jayagond Signed-off-by: Yehyeong Lee Reviewed-by: Breno Leitao Link: https://patch.msgid.link/20260819023306.644849-2-yhlee@isslab.korea.ac.kr Signed-off-by: Jakub Kicinski --- net/smc/smc_llc.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/net/smc/smc_llc.c b/net/smc/smc_llc.c index cef2c1df3ab9..de367510813b 100644 --- a/net/smc/smc_llc.c +++ b/net/smc/smc_llc.c @@ -1482,7 +1482,7 @@ int smc_llc_srv_add_link(struct smc_link *link, } add_llc = &qentry->msg.add_link; if (add_llc->hd.flags & SMC_LLC_FLAG_ADD_LNK_REJ) { - smc_llc_flow_qentry_del(&lgr->llc_flow_lcl); + smc_llc_flow_qentry_clr(&lgr->llc_flow_lcl); rc = -ENOLINK; goto out_err; } @@ -1493,7 +1493,8 @@ int smc_llc_srv_add_link(struct smc_link *link, lgr_new_t = SMC_LGR_ASYMMETRIC_PEER; } smc_llc_save_add_link_info(link_new, add_llc); - smc_llc_flow_qentry_del(&lgr->llc_flow_lcl); + /* add_llc still points into qentry, so only detach it here */ + smc_llc_flow_qentry_clr(&lgr->llc_flow_lcl); rc = smc_ib_ready_link(link_new); if (rc) @@ -1513,14 +1514,14 @@ int smc_llc_srv_add_link(struct smc_link *link, rc = smc_llc_srv_conf_link(link, link_new, lgr_new_t); if (rc) goto out_err; - kfree(ini); - return 0; + goto out; out_err: if (link_new) { link_new->state = SMC_LNK_INACTIVE; smcr_link_clear(link_new, false); } out: + kfree(qentry); kfree(ini); if (send_req_add_link_resp) smc_llc_send_req_add_link_response(req_qentry); From 2d1e7c5aaa3326e95e2058457f172ca99a9a4577 Mon Sep 17 00:00:00 2001 From: Yehyeong Lee Date: Wed, 19 Aug 2026 11:33:05 +0900 Subject: [PATCH 0804/1328] net/smc: bound the peer rkey counts in SMC-Rv2 LLC messages On a link whose device has max_recv_sge == 1 there is no shared v2 receive buffer, and smc_llc_save_add_link_rkeys() takes the v2 extension from 44 bytes past the start of the queue entry's inline message: ext = (struct smc_llc_msg_add_link_v2_ext *)(llc_msg + SMC_WR_TX_SIZE); The entry is a 72-byte allocation and the extension starts at offset 68, so ext->num_rkeys at offset 94 is already past it. This happens on every SMC-Rv2 link addition, whatever the peer sends: [ 2.490065] BUG: KASAN: slab-out-of-bounds in smc_llc_save_add_link_rkeys+0x333/0x350 [ 2.490431] Read of size 2 at addr ffff8880056406de by task smctest/106 [ 2.490709] [ 2.490792] CPU: 0 UID: 0 PID: 106 Comm: smctest Not tainted 7.2.0-rc5-p1-g77a5d9d9c99f #32 PREEMPT(lazy) [ 2.490795] Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 [ 2.490798] Call Trace: [ 2.490803] [ 2.490805] dump_stack_lvl+0x53/0x70 [ 2.490810] print_report+0xd0/0x630 [ 2.490828] ? __pfx__raw_spin_lock_irqsave+0x10/0x10 [ 2.490832] ? smc_llc_save_add_link_rkeys+0x333/0x350 [ 2.490834] kasan_report+0xce/0x100 [ 2.490836] ? smc_llc_save_add_link_rkeys+0x333/0x350 [ 2.490837] smc_llc_save_add_link_rkeys+0x333/0x350 [ 2.490839] ? smcr_buf_map_lgr+0x1bf/0x2b0 [ 2.490844] smc_llc_cli_add_link+0xca7/0x1e80 [ 2.490848] ? smc_llc_wait+0x355/0x810 [ 2.490850] ? __pfx_smc_llc_wait+0x10/0x10 [ 2.490851] ? __pfx_smc_llc_cli_add_link+0x10/0x10 [ 2.490853] ? __pfx_autoremove_wake_function+0x10/0x10 [ 2.490863] __smc_connect+0x3f5c/0x4980 [ 2.490873] ? __pfx_kernel_connect+0x10/0x10 [ 2.490888] ? __pfx___smc_connect+0x10/0x10 [ 2.490891] ? release_sock+0x148/0x1d0 [ 2.490894] smc_connect+0x42c/0x580 [ 2.490896] __sys_connect+0xfc/0x130 [ 2.490898] ? __pfx___sys_connect+0x10/0x10 [ 2.490900] ? handle_mm_fault+0x1a1/0x430 [ 2.490908] __x64_sys_connect+0x6d/0xb0 [ 2.490909] ? fpregs_assert_state_consistent+0x56/0xe0 [ 2.490917] do_syscall_64+0xf9/0x540 [ 2.490921] entry_SYSCALL_64_after_hwframe+0x77/0x7f [ 2.490924] RIP: 0033:0x421bb4 [ 2.490927] Code: ff f7 d8 64 89 01 48 83 c8 ff c3 66 2e 0f 1f 84 00 00 00 00 00 90 f3 0f 1e fa 80 3d ad 34 09 00 00 74 13 b8 2a 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 4c c3 0f 1f 00 55 48 89 e5 48 83 ec 10 89 55 [ 2.490929] RSP: 002b:00007ffd473b01a8 EFLAGS: 00000202 ORIG_RAX: 000000000000002a [ 2.490935] RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 0000000000421bb4 [ 2.490936] RDX: 0000000000000010 RSI: 00007ffd473b01d0 RDI: 0000000000000003 [ 2.490937] RBP: 0000000000003930 R08: 0000000000000004 R09: 0000000000000000 [ 2.490938] R10: 00007ffd473b0f98 R11: 0000000000000202 R12: 0000000000000006 [ 2.490939] R13: 00007ffd473b0f87 R14: 0000000000000003 R15: 00007ffd473b0f90 [ 2.490940] [ 2.490941] [ 2.499545] Allocated by task 44: [ 2.499693] kasan_save_stack+0x33/0x60 [ 2.499860] kasan_save_track+0x14/0x30 [ 2.500026] __kasan_kmalloc+0x8f/0xa0 [ 2.500190] __kmalloc_cache_noprof+0x158/0x370 [ 2.500393] smc_llc_enqueue+0x72/0x560 [ 2.500559] smc_wr_rx_tasklet_fn+0x474/0xa80 [ 2.500747] tasklet_action_common+0x20f/0x8a0 [ 2.500945] handle_softirqs+0x18e/0x590 [ 2.501115] do_softirq+0x3b/0x60 [ 2.501266] __local_bh_enable_ip+0x61/0x70 [ 2.501446] __alloc_skb+0x732/0x890 [ 2.501604] rxe_init_packet+0x16b/0x4f0 [ 2.501783] prepare_ack_packet+0xb8/0x830 [ 2.501962] rxe_receiver+0x495/0x96e0 [ 2.502125] do_work+0x144/0x470 [ 2.502269] process_one_work+0x633/0x1030 [ 2.502450] worker_thread+0x45b/0xd10 [ 2.502617] kthread+0x2c6/0x3b0 [ 2.502762] ret_from_fork+0x36e/0x5a0 [ 2.502925] ret_from_fork_asm+0x1a/0x30 [ 2.503103] [ 2.503177] The buggy address belongs to the object at ffff888005640680 [ 2.503177] which belongs to the cache kmalloc-96 of size 96 [ 2.503692] The buggy address is located 22 bytes to the right of [ 2.503692] allocated 72-byte region [ffff888005640680, ffff8880056406c8) [ 2.504227] [ 2.504300] The buggy address belongs to the physical page: [ 2.504535] page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x5640 [ 2.504865] flags: 0x100000000000000(node=0|zone=1) [ 2.505076] page_type: f5(slab) [ 2.505221] raw: 0100000000000000 ffff888001041280 dead000000000122 0000000000000000 [ 2.505544] raw: 0000000000000000 0000000000200020 00000000f5000000 0000000000000000 [ 2.505867] page dumped because: kasan: bad access detected [ 2.506102] [ 2.506176] Memory state around the buggy address: [ 2.506380] ffff888005640580: 00 00 00 00 00 00 00 00 00 fc fc fc fc fc fc fc [ 2.506683] ffff888005640600: 00 00 00 00 00 00 00 00 00 fc fc fc fc fc fc fc [ 2.506987] >ffff888005640680: 00 00 00 00 00 00 00 00 00 fc fc fc fc fc fc fc [ 2.507291] ^ [ 2.507548] ffff888005640700: 00 00 00 00 00 00 00 00 00 fc fc fc fc fc fc fc [ 2.507850] ffff888005640780: 00 00 00 00 00 00 00 00 00 fc fc fc fc fc fc fc Whatever that read finds then bounds the ext->rt[] loop, so a peer that declares 255 rkeys reads much further. smc_llc_rmt_delete_rkey() has the same shape for llcv2->rkey[]. Bound both loops by the buffer they read from, and skip the extension altogether when there is no shared v2 receive buffer. The extension does arrive on the link, but smc_llc_enqueue() copies only sizeof(union smc_llc_msg) into the queue entry, so what that code read past the 44 inline bytes was heap and not peer data. Fixes: 27ef6a9981fe ("net/smc: support SMC-R V2 for rdma devices with max_recv_sge equals to 1") Cc: stable@vger.kernel.org Reviewed-by: Sidraya Jayagond Signed-off-by: Yehyeong Lee Link: https://patch.msgid.link/20260819023306.644849-3-yhlee@isslab.korea.ac.kr Signed-off-by: Jakub Kicinski --- net/smc/smc_llc.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/net/smc/smc_llc.c b/net/smc/smc_llc.c index de367510813b..a27406e09e24 100644 --- a/net/smc/smc_llc.c +++ b/net/smc/smc_llc.c @@ -1001,13 +1001,21 @@ static void smc_llc_save_add_link_rkeys(struct smc_link *link, struct smc_link *link_new, u8 *llc_msg) { + const u32 rt_off = offsetof(struct smc_llc_msg_add_link_v2_ext, rt); struct smc_llc_msg_add_link_v2_ext *ext; struct smc_link_group *lgr = link->lgr; int max, i; + /* Without a shared v2 receive buffer the extension is not copied + * into the queue entry, so not even ext->num_rkeys is there. + */ + if (!smc_link_shared_v2_rxbuf(link)) + return; ext = (struct smc_llc_msg_add_link_v2_ext *)(llc_msg + SMC_WR_TX_SIZE); max = min_t(u8, ext->num_rkeys, SMC_LLC_RKEYS_PER_MSG_V2); + max = min_t(u32, max, (SMC_WR_BUF_V2_SIZE - SMC_WR_TX_SIZE - rt_off) / + sizeof(ext->rt[0])); down_write(&lgr->rmbs_lock); for (i = 0; i < max; i++) { smc_rtoken_set(lgr, link->link_idx, link_new->link_idx, @@ -1812,17 +1820,25 @@ static void smc_llc_rmt_delete_rkey(struct smc_link_group *lgr) link = qentry->link; if (lgr->smc_version == SMC_V2) { + const u32 rkey_off = + offsetof(struct smc_llc_msg_delete_rkey_v2, rkey); struct smc_llc_msg_delete_rkey_v2 *llcv2; + u32 buf_len; if (smc_link_shared_v2_rxbuf(link)) { memcpy(lgr->wr_rx_buf_v2, llc, sizeof(*llc)); llcv2 = (struct smc_llc_msg_delete_rkey_v2 *)lgr->wr_rx_buf_v2; + buf_len = SMC_WR_BUF_V2_SIZE; } else { llcv2 = (struct smc_llc_msg_delete_rkey_v2 *)llc; + buf_len = sizeof(qentry->msg); } llcv2->num_inval_rkeys = 0; max = min_t(u8, llcv2->num_rkeys, SMC_LLC_RKEYS_PER_MSG_V2); + /* bound by the buffer llcv2 points at */ + max = min_t(u32, max, (buf_len - rkey_off) / + sizeof(llcv2->rkey[0])); for (i = 0; i < max; i++) { if (smc_rtoken_delete(link, llcv2->rkey[i])) llcv2->num_inval_rkeys++; From 8d3c1ab82c11d4fadebf817a825fd221b3e197ea Mon Sep 17 00:00:00 2001 From: Yehyeong Lee Date: Wed, 19 Aug 2026 11:33:06 +0900 Subject: [PATCH 0805/1328] net/smc: carry oversized SMC-Rv2 LLC messages in the queue entry smc_llc_rmt_delete_rkey() and smc_llc_save_add_link_rkeys() read the part of a v2 message that does not fit into the 44-byte union smc_llc_msg, and both bound themselves by the size of the buffer it landed in, not by what arrived. On a link with a shared v2 receive buffer a 44-byte DELETE_RKEY_V2 declaring 255 rkeys reaches rkey[9..254] in whatever an earlier message left in lgr->wr_rx_buf_v2, and passes each of them to smc_rtoken_delete(). One of those 255 matched a registered rtoken and deleted it. An ADD_LINK on such a link installs up to 255 rtokens from the same bytes. Copy the tail into the queue entry, so its length is the length of the message that arrived, and declare the rkeys that fit inline as a member of the union instead of reaching them through a cast. The same DELETE_RKEY_V2 now processes the 9 rkeys it carries. The copy is limited to the longest tail the two functions can read, so the peer does not pick the size of the entry. The bound the previous patch placed on links without a shared v2 receive buffer is no longer needed. Fixes: 27ef6a9981fe ("net/smc: support SMC-R V2 for rdma devices with max_recv_sge equals to 1") Cc: stable@vger.kernel.org Suggested-by: D. Wythe Reviewed-by: Sidraya Jayagond Signed-off-by: Yehyeong Lee Link: https://patch.msgid.link/20260819023306.644849-4-yhlee@isslab.korea.ac.kr Signed-off-by: Jakub Kicinski --- net/smc/smc_llc.c | 123 ++++++++++++++++++++++++++++++++-------------- net/smc/smc_wr.c | 6 +-- 2 files changed, 90 insertions(+), 39 deletions(-) diff --git a/net/smc/smc_llc.c b/net/smc/smc_llc.c index a27406e09e24..c9ccd8480369 100644 --- a/net/smc/smc_llc.c +++ b/net/smc/smc_llc.c @@ -157,6 +157,7 @@ struct smc_llc_msg_confirm_rkey { /* type 0x06 */ }; #define SMC_LLC_DEL_RKEY_MAX 8 +#define SMC_LLC_DEL_RKEY_V2_INLINE 9 #define SMC_LLC_FLAG_RKEY_RETRY 0x10 #define SMC_LLC_FLAG_RKEY_NEG 0x20 @@ -177,6 +178,15 @@ struct smc_llc_msg_delete_rkey_v2 { /* type 0x29 */ __be32 rkey[]; }; +/* the leading rkeys of a DELETE_RKEY_V2 fit into union smc_llc_msg */ +struct smc_llc_msg_delete_rkey_v2_inline { /* type 0x29 */ + struct smc_llc_hdr hd; + u8 num_rkeys; + u8 num_inval_rkeys; + u8 reserved[2]; + __be32 rkey[SMC_LLC_DEL_RKEY_V2_INLINE]; +}; + union smc_llc_msg { struct smc_llc_msg_confirm_link confirm_link; struct smc_llc_msg_add_link add_link; @@ -186,6 +196,7 @@ union smc_llc_msg { struct smc_llc_msg_confirm_rkey confirm_rkey; struct smc_llc_msg_delete_rkey delete_rkey; + struct smc_llc_msg_delete_rkey_v2_inline delete_rkey_v2; struct smc_llc_msg_test_link test_link; struct { @@ -194,15 +205,25 @@ union smc_llc_msg { } raw; }; +static_assert(SMC_LLC_DEL_RKEY_V2_INLINE == + (sizeof(union smc_llc_msg) - + offsetof(struct smc_llc_msg_delete_rkey_v2, rkey)) / + sizeof(__be32)); +static_assert(offsetof(struct smc_llc_msg_delete_rkey_v2_inline, rkey) == + offsetof(struct smc_llc_msg_delete_rkey_v2, rkey)); + #define SMC_LLC_FLAG_RESP 0x80 struct smc_llc_qentry { struct list_head list; struct smc_link *link; + u16 body_len; union smc_llc_msg msg; + u8 body[] __counted_by(body_len); }; -static void smc_llc_enqueue(struct smc_link *link, union smc_llc_msg *llc); +static void smc_llc_enqueue(struct smc_link *link, union smc_llc_msg *llc, + u32 byte_len); struct smc_llc_qentry *smc_llc_flow_qentry_clr(struct smc_llc_flow *flow) { @@ -999,22 +1020,19 @@ static int smc_llc_cli_conf_link(struct smc_link *link, static void smc_llc_save_add_link_rkeys(struct smc_link *link, struct smc_link *link_new, - u8 *llc_msg) + struct smc_llc_qentry *qentry) { const u32 rt_off = offsetof(struct smc_llc_msg_add_link_v2_ext, rt); struct smc_llc_msg_add_link_v2_ext *ext; struct smc_link_group *lgr = link->lgr; int max, i; - /* Without a shared v2 receive buffer the extension is not copied - * into the queue entry, so not even ext->num_rkeys is there. - */ - if (!smc_link_shared_v2_rxbuf(link)) + /* the rkey count itself is only there if enough bytes arrived */ + if (qentry->body_len < rt_off) return; - ext = (struct smc_llc_msg_add_link_v2_ext *)(llc_msg + - SMC_WR_TX_SIZE); + ext = (struct smc_llc_msg_add_link_v2_ext *)qentry->body; max = min_t(u8, ext->num_rkeys, SMC_LLC_RKEYS_PER_MSG_V2); - max = min_t(u32, max, (SMC_WR_BUF_V2_SIZE - SMC_WR_TX_SIZE - rt_off) / + max = min_t(u32, max, (qentry->body_len - rt_off) / sizeof(ext->rt[0])); down_write(&lgr->rmbs_lock); for (i = 0; i < max; i++) { @@ -1108,9 +1126,7 @@ int smc_llc_cli_add_link(struct smc_link *link, struct smc_llc_qentry *qentry) if (rc) goto out_clear_lnk; if (lgr->smc_version == SMC_V2) { - u8 *llc_msg = smc_link_shared_v2_rxbuf(link) ? - (u8 *)lgr->wr_rx_buf_v2 : (u8 *)llc; - smc_llc_save_add_link_rkeys(link, lnk_new, llc_msg); + smc_llc_save_add_link_rkeys(link, lnk_new, qentry); } else { rc = smc_llc_cli_rkey_exchange(link, lnk_new); if (rc) { @@ -1511,9 +1527,7 @@ int smc_llc_srv_add_link(struct smc_link *link, if (rc) goto out_err; if (lgr->smc_version == SMC_V2) { - u8 *llc_msg = smc_link_shared_v2_rxbuf(link) ? - (u8 *)lgr->wr_rx_buf_v2 : (u8 *)add_llc; - smc_llc_save_add_link_rkeys(link, link_new, llc_msg); + smc_llc_save_add_link_rkeys(link, link_new, qentry); } else { rc = smc_llc_srv_rkey_exchange(link, link_new); if (rc) @@ -1562,7 +1576,8 @@ void smc_llc_add_link_local(struct smc_link *link) add_llc.hd.common.llc_type = SMC_LLC_ADD_LINK; smc_llc_init_msg_hdr(&add_llc.hd, link->lgr, sizeof(add_llc)); /* no dev and port needed */ - smc_llc_enqueue(link, (union smc_llc_msg *)&add_llc); + smc_llc_enqueue(link, (union smc_llc_msg *)&add_llc, + sizeof(union smc_llc_msg)); } /* worker to process an add link message */ @@ -1598,7 +1613,8 @@ void smc_llc_srv_delete_link_local(struct smc_link *link, u8 del_link_id) del_llc.link_num = del_link_id; del_llc.reason = htonl(SMC_LLC_DEL_LOST_PATH); del_llc.hd.flags |= SMC_LLC_FLAG_DEL_LINK_ORDERLY; - smc_llc_enqueue(link, (union smc_llc_msg *)&del_llc); + smc_llc_enqueue(link, (union smc_llc_msg *)&del_llc, + sizeof(union smc_llc_msg)); } static void smc_llc_process_cli_delete_link(struct smc_link_group *lgr) @@ -1820,27 +1836,28 @@ static void smc_llc_rmt_delete_rkey(struct smc_link_group *lgr) link = qentry->link; if (lgr->smc_version == SMC_V2) { - const u32 rkey_off = - offsetof(struct smc_llc_msg_delete_rkey_v2, rkey); - struct smc_llc_msg_delete_rkey_v2 *llcv2; - u32 buf_len; + struct smc_llc_msg_delete_rkey_v2_inline *llcv2; - if (smc_link_shared_v2_rxbuf(link)) { - memcpy(lgr->wr_rx_buf_v2, llc, sizeof(*llc)); - llcv2 = (struct smc_llc_msg_delete_rkey_v2 *)lgr->wr_rx_buf_v2; - buf_len = SMC_WR_BUF_V2_SIZE; - } else { - llcv2 = (struct smc_llc_msg_delete_rkey_v2 *)llc; - buf_len = sizeof(qentry->msg); - } + /* The leading SMC_LLC_DEL_RKEY_V2_INLINE rkeys are declared in + * the message itself, any further ones were received into + * qentry->body. + */ + llcv2 = &qentry->msg.delete_rkey_v2; llcv2->num_inval_rkeys = 0; max = min_t(u8, llcv2->num_rkeys, SMC_LLC_RKEYS_PER_MSG_V2); - /* bound by the buffer llcv2 points at */ - max = min_t(u32, max, (buf_len - rkey_off) / - sizeof(llcv2->rkey[0])); + max = min_t(u32, max, SMC_LLC_DEL_RKEY_V2_INLINE + + qentry->body_len / sizeof(__be32)); for (i = 0; i < max; i++) { - if (smc_rtoken_delete(link, llcv2->rkey[i])) + __be32 rkey; + + if (i < SMC_LLC_DEL_RKEY_V2_INLINE) + rkey = llcv2->rkey[i]; + else + memcpy(&rkey, qentry->body + + (i - SMC_LLC_DEL_RKEY_V2_INLINE) * + sizeof(rkey), sizeof(rkey)); + if (smc_rtoken_delete(link, rkey)) llcv2->num_inval_rkeys++; } memset(&llc->rkey[0], 0, sizeof(llc->rkey)); @@ -2084,18 +2101,52 @@ static void smc_llc_rx_response(struct smc_link *link, wake_up(&link->lgr->llc_msg_waiter); } -static void smc_llc_enqueue(struct smc_link *link, union smc_llc_msg *llc) +/* the longest tail either reader of qentry->body can use */ +static u32 smc_llc_max_body_len(union smc_llc_msg *llc) +{ + switch (llc->raw.hdr.common.llc_type) { + case SMC_LLC_ADD_LINK: + return offsetof(struct smc_llc_msg_add_link_v2_ext, rt) + + SMC_LLC_RKEYS_PER_MSG_V2 * + sizeof(struct smc_llc_msg_add_link_cont_rt); + case SMC_LLC_DELETE_RKEY: + return (SMC_LLC_RKEYS_PER_MSG_V2 - + SMC_LLC_DEL_RKEY_V2_INLINE) * sizeof(__be32); + default: + return 0; + } +} + +static void smc_llc_enqueue(struct smc_link *link, union smc_llc_msg *llc, + u32 byte_len) { struct smc_link_group *lgr = link->lgr; struct smc_llc_qentry *qentry; unsigned long flags; + u16 body_len = 0; - qentry = kmalloc_obj(*qentry, GFP_ATOMIC); + /* V2 messages can be longer than the inline union smc_llc_msg. Carry + * the remainder in the qentry itself, so that its lifetime and its + * length match the message the peer actually sent. + */ + if (lgr->smc_version == SMC_V2 && byte_len > SMC_WR_TX_SIZE) + body_len = min_t(u32, byte_len, SMC_WR_BUF_V2_SIZE) - + SMC_WR_TX_SIZE; + body_len = min_t(u32, body_len, smc_llc_max_body_len(llc)); + + qentry = kmalloc_flex(*qentry, body, body_len, GFP_ATOMIC); if (!qentry) return; + qentry->body_len = body_len; qentry->link = link; INIT_LIST_HEAD(&qentry->list); memcpy(&qentry->msg, llc, sizeof(union smc_llc_msg)); + if (body_len) { + u8 *src = smc_link_shared_v2_rxbuf(link) ? + (u8 *)lgr->wr_rx_buf_v2 : (u8 *)llc; + + memcpy(qentry->body, src + SMC_WR_TX_SIZE, body_len); + } /* process responses immediately */ if ((llc->raw.hdr.flags & SMC_LLC_FLAG_RESP) && @@ -2127,7 +2178,7 @@ static void smc_llc_rx_handler(struct ib_wc *wc, void *buf) return; /* invalid message */ } - smc_llc_enqueue(link, llc); + smc_llc_enqueue(link, llc, wc->byte_len); } /***************************** worker, utils *********************************/ diff --git a/net/smc/smc_wr.c b/net/smc/smc_wr.c index 59c92b46945c..97ba46893b17 100644 --- a/net/smc/smc_wr.c +++ b/net/smc/smc_wr.c @@ -602,9 +602,9 @@ static void smc_wr_init_sge(struct smc_link *lnk) /* With SMC-Rv2 there can be messages larger than SMC_WR_TX_SIZE. * Each ib_recv_wr gets 2 sges, the second one is a spillover buffer - * and the same buffer for all sges. When a larger message arrived then - * the content of the first small sge is copied to the beginning of - * the larger spillover buffer, allowing easy data mapping. + * and the same buffer for all sges. The spillover sge starts at + * SMC_WR_TX_SIZE, so the leading bytes of that buffer are never + * written. */ for (i = 0; i < lnk->wr_rx_cnt; i++) { int x = i * lnk->wr_rx_sge_cnt; From 039f248a6cc1f4dec895c001de2c600842022e58 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Fri, 21 Aug 2026 01:41:25 +0800 Subject: [PATCH 0806/1328] net: page_pool: Remove zone/policy GFP flags when allocating XArray entries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Net drivers request GFP flags according to both the current context and the device constraints, but the XArray entry itself is by no mean used by the device. Passing though device constraints to XArray allocation is a bug and will be warned and fixed up by slab, e.g.: Unexpected gfp: 0x4 (GFP_DMA32). Fixing up to gfp: 0x82820 (GFP_ATOMIC|__GFP_NOWARN|__GFP_NOMEMALLOC). Fix your code! CPU: 2 UID: 0 PID: 1071629 Comm: kworker/u80:1 Not tainted 7.2.0-rc7+ #1 PREEMPT(lazy) Hardware name: LENOVO 21Q4/LNVNB161216, BIOS PXCN27WW 10/20/2025 Workqueue: mt76 mt792x_pm_wake_work [mt792x_lib] Call Trace: dump_stack_lvl+0x6e/0x90 kmalloc_fix_flags+0x4d/0x6a refill_objects+0x10a/0x330 __pcs_replace_empty_main+0x292/0x5c0 kmem_cache_alloc_lru_noprof+0x4c2/0x680 ? __xas_nomem+0x3a/0x120 __xas_nomem+0x3a/0x120 __xa_alloc+0xd4/0x190 page_pool_dma_map+0xef/0x400 __page_pool_alloc_netmems_slow+0xed/0x480 ? lock_release+0x280/0x490 page_pool_alloc_frag_netmem+0xe0/0x3a0 page_pool_alloc_frag+0xe/0x20 mt76_dma_rx_fill_buf+0x1f6/0x580 [mt76] mt76_dma_rx_reset+0x1cf/0x230 [mt76] mt792x_wpdma_reset+0x183/0x1b0 [mt792x_lib] mt792x_wpdma_reinit_cond+0x5e/0xa0 [mt792x_lib] mt792xe_mcu_drv_pmctrl+0x28/0x60 [mt792x_lib] mt792x_mcu_drv_pmctrl+0x3e/0x90 [mt792x_lib] mt792x_pm_wake_work+0x2d/0x1d0 [mt792x_lib] ? process_one_work+0x20e/0x600 process_one_work+0x230/0x600 ? process_one_work+0x256/0x600 worker_thread+0x1ec/0x3c0 ? rescuer_thread+0x610/0x610 kthread+0xf2/0x130 ? kthread_affine_node+0x140/0x140 ret_from_fork+0x2a5/0x380 ? kthread_affine_node+0x140/0x140 ret_from_fork_asm+0x11/0x20 Currently mt76 and stmmac may allocate page pool pages with GFP_DMA32. Fix it by removing zone/policy GFP flags when allocating XArray entries. This is inspired by commit 96d578088085 ("iommu/dma: Use the gfp parameter in __iommu_dma_alloc_noncontiguous()"). Fixes: ee62ce7a1d90 ("page_pool: Track DMA-mapped pages and unmap them when destroying the pool") Signed-off-by: Rong Zhang Reviewed-by: Toke Høiland-Jørgensen Link: https://patch.msgid.link/20260821-page-pool-xa-drop-dma32-v1-1-6eab295c3478@rong.moe Signed-off-by: Jakub Kicinski --- net/core/page_pool.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/net/core/page_pool.c b/net/core/page_pool.c index 50ee550fef73..8f8956fb061b 100644 --- a/net/core/page_pool.c +++ b/net/core/page_pool.c @@ -484,6 +484,13 @@ static int page_pool_register_dma_index(struct page_pool *pool, if (unlikely(!PP_DMA_INDEX_BITS)) goto out; + /* + * Drivers request GFP flags according to both the current context and + * the device constraints, but the XArray entry itself is by no mean + * used by the device, so remove zone/policy flags. + */ + gfp &= ~(__GFP_DMA | __GFP_DMA32 | __GFP_HIGHMEM | __GFP_COMP); + if (in_softirq()) err = xa_alloc(&pool->dma_mapped, &id, netmem_to_page(netmem), PP_DMA_INDEX_LIMIT, gfp); From 137b8ae233cabe7fc77cce86f5ba33bf1b369e26 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 20 Aug 2026 08:06:25 -0400 Subject: [PATCH 0807/1328] net_sched: sch_fq: fix pacing delay underflow with pacing offload When pacing offload is enabled (q->offload_horizon > 0), FQ can dequeue packets early (now < f->time_next_packet). In this case, the drift calculation (now - f->time_next_packet) underflows to a large unsigned value. min(len/2, now - f->time_next_packet) then evaluates to len/2, incorrectly halving the pacing delay for the next packet. Fix this by only applying drift compensation if now > f->time_next_packet. This bug was triggered when flow_max_rate was set on the qdisc or for non EDT packets (packets with a zero skb->tstamp). Fixes: f26080d47007 ("net_sched: sch_fq: add the ability to offload pacing") Reported-by: Willem de Bruijn Closes: https://lore.kernel.org/netdev/CANn89iK6O7ujR9zCJzd04MNLQoDi3mA+HWsR-hgQWYzLS3gZfw@mail.gmail.com/ Signed-off-by: Eric Dumazet Signed-off-by: Willem de Bruijn Link: https://patch.msgid.link/20260820120706.1995449-1-willemdebruijn.kernel@gmail.com Signed-off-by: Jakub Kicinski --- net/sched/sch_fq.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/net/sched/sch_fq.c b/net/sched/sch_fq.c index 7cae082a9847..4b5f6d896c6d 100644 --- a/net/sched/sch_fq.c +++ b/net/sched/sch_fq.c @@ -828,8 +828,12 @@ static struct sk_buff *fq_dequeue(struct Qdisc *sch) * f->time_next_packet was set when prior packet was sent, * and current time (@now) can be too late by tens of us. */ - if (f->time_next_packet) - len -= min(len/2, now - f->time_next_packet); + if (f->time_next_packet) { + s64 drift = now - f->time_next_packet; + + if (drift > 0) + len -= min_t(u64, len / 2, drift); + } f->time_next_packet = now + len; } out: From 11efd7963dac9b036a7c9a435ab0e621188910e8 Mon Sep 17 00:00:00 2001 From: Jian Shen Date: Thu, 20 Aug 2026 20:43:46 +0800 Subject: [PATCH 0808/1328] net: hibmcge: fix page_pool DMA direction mismatch The driver memsets the RX buffer page head to zero before submitting it to hardware, then calls dma_sync_single_for_device() with DMA_TO_DEVICE. This sync direction does not match the pool dma_dir which is DMA_FROM_DEVICE, violating the DMA API contract that the sync direction must match the mapping direction. On swiotlb platforms the mismatch can cause incorrect bounce-buffer behaviour, and CONFIG_DMA_API_DEBUG emits a warning. Switch the page_pool dma_dir to DMA_BIDIRECTIONAL so that the CPU-to-device memset sync becomes legal. Fixes: c30595917585 ("net: hibmcge: add support for pagepool on rx") Signed-off-by: Jian Shen Signed-off-by: Jijie Shao Link: https://patch.msgid.link/20260820124346.4097115-1-shaojijie@huawei.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/hisilicon/hibmcge/hbg_txrx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/hisilicon/hibmcge/hbg_txrx.c b/drivers/net/ethernet/hisilicon/hibmcge/hbg_txrx.c index 4382af937e2e..207ada721dd9 100644 --- a/drivers/net/ethernet/hisilicon/hibmcge/hbg_txrx.c +++ b/drivers/net/ethernet/hisilicon/hibmcge/hbg_txrx.c @@ -554,7 +554,7 @@ static int hbg_ring_page_pool_init(struct hbg_priv *priv, struct hbg_ring *ring) .nid = dev_to_node(&priv->pdev->dev), .dev = &priv->pdev->dev, .napi = &ring->napi, - .dma_dir = DMA_FROM_DEVICE, + .dma_dir = DMA_BIDIRECTIONAL, .offset = 0, .max_len = hbg_get_page_size(ring), }; From 7cbfb180945ce529608e4d4e24a6d483699fab1e Mon Sep 17 00:00:00 2001 From: Giuseppe Piscitelli Date: Thu, 20 Aug 2026 17:45:03 +0200 Subject: [PATCH 0809/1328] net/sched: sch_cake: fix autorate reconfiguration throttling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CAKE's autorate-ingress path intends to limit shaper reconfiguration to once per 250 ms, but last_reconfig_time is only checked and never updated. Since the field stays zero, every qualifying capacity-estimate window can call cake_reconfigure(), causing avoidable rate churn and scheduler work under bursty traffic. Store the current timestamp when autorate actually reconfigures the qdisc so the guard enforces the intended interval. Fixes: 7298de9cd725 ("sch_cake: Add ingress mode") Signed-off-by: Giuseppe Piscitelli Acked-by: Toke Høiland-Jørgensen Link: https://patch.msgid.link/20260820154503.892214-1-ooonea@gmail.com Signed-off-by: Jakub Kicinski --- net/sched/sch_cake.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/sched/sch_cake.c b/net/sched/sch_cake.c index f25f60978631..dc93267029e7 100644 --- a/net/sched/sch_cake.c +++ b/net/sched/sch_cake.c @@ -1907,6 +1907,7 @@ static s32 cake_enqueue(struct sk_buff *skb, struct Qdisc *sch, ktime_add_ms(q->last_reconfig_time, 250))) { q->config->rate_bps = (q->avg_peak_bandwidth * 15) >> 4; + q->last_reconfig_time = now; cake_reconfigure(sch); } } From a7318172aa332a161fb9618286e64454c827f8fd Mon Sep 17 00:00:00 2001 From: Hui Su Date: Sun, 16 Aug 2026 18:04:29 +0800 Subject: [PATCH 0810/1328] tracing: Fix use-after-free with same-name named triggers When two hist triggers on different events are registered with the same name=, the second one reuses the first as named_data. Both are added to tr->hist_vars by save_hist_vars() during event_hist_trigger_parse(), because save_hist_vars() is called before event_trigger_register() while the named reuse is only detected later, in hist_register_trigger(). In the named-data branch hist_register_trigger() then frees the second histogram's hist_data via destroy_hist_data(), but never removes its tr->hist_vars list entry, leaving a dangling pointer and leaking the trace_array reference it holds. A later hist trigger that references a variable makes find_var_file() walk tr->hist_vars and dereference the freed hist_data. The bug is reproducible from userspace by writing three hist triggers to tracefs: cd /sys/kernel/tracing echo 'hist:keys=common_pid:x=common_pid:name=mh' > events/sched/sched_switch/trigger echo 'hist:keys=common_pid:x=common_pid:name=mh' > events/sched/sched_process_fork/trigger echo 'hist:keys=common_pid:vals=$x' > events/sched/sched_process_exit/trigger The third write panics the kernel: BUG: KASAN: slab-use-after-free in find_var_file.part.0+0x272/0x290 Read of size 8 at addr ffff888001f8a0e0 by task sh/1 CPU: 1 UID: 0 PID: 1 Comm: sh Tainted: G D N Call Trace: find_var_file.part.0 find_event_var parse_atom parse_expr __create_val_field event_hist_trigger_parse trigger_process_regex event_trigger_write vfs_write ksys_write do_syscall_64 entry_SYSCALL_64_after_hwframe Allocated by task 1: event_hist_trigger_parse Freed by task 1: hist_register_trigger+0x618/0xa30 event_hist_trigger_parse The buggy address belongs to freed 2048-byte region Oops: general protection fault ... RIP: find_var_file.part.0 Kernel panic - not syncing: Attempted to kill init! exitcode=0x0000000b Fix by removing the hist_data from tr->hist_vars and releasing the trace_array reference in the named-data branch of hist_register_trigger() before freeing the hist_data. Cc: stable@vger.kernel.org Fixes: 6f86bdeab633 ("tracing: Fix bad hist from corrupting named_triggers list") Link: https://patch.msgid.link/20260816100427.33642-3-sh_def@163.com Signed-off-by: Hui Su Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_hist.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index 893bd8b0e48a..963e0d6b61fd 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -6661,8 +6661,10 @@ static int hist_register_trigger(char *glob, tracing_set_filter_buffering(file->tr, true); } - if (named_data) + if (named_data) { + remove_hist_vars(hist_data); destroy_hist_data(hist_data); + } out: return ret; } From 649bc7df3e5d7be6f7996a95084037dbf3cad1e5 Mon Sep 17 00:00:00 2001 From: Hui Su Date: Mon, 17 Aug 2026 20:06:44 +0800 Subject: [PATCH 0811/1328] tracing: Fix crash passing ERR_PTR to kthread_stop() event_test_stuff() calls kthread_run() and unconditionally passes the returned task_struct pointer to kthread_stop(). kthread_run() returns an error pointer such as ERR_PTR(-ENOMEM) when kthread creation fails, for example under memory pressure during the boot-time event self-test. kthread_stop() then dereferences the invalid pointer, crashing the kernel. Check the result of kthread_run() before passing it to kthread_stop(). Use WARN_ON() so that a failure to create the self-test thread does not go unnoticed, matching the ring-buffer self-test fix in commit 91542863abad ("ring-buffer: Fix crash passing ERR_PTR to kthread_stop()"). Cc: stable@vger.kernel.org Fixes: e6187007d6c3 ("tracing/events: add startup tests for events") Link: https://patch.msgid.link/20260817120642.668375-3-sh_def@163.com Signed-off-by: Hui Su Signed-off-by: Steven Rostedt --- kernel/trace/trace_events.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c index 9f8f2d02276c..1d39eaf6a0f7 100644 --- a/kernel/trace/trace_events.c +++ b/kernel/trace/trace_events.c @@ -5019,6 +5019,8 @@ static __init void event_test_stuff(void) struct task_struct *test_thread; test_thread = kthread_run(event_test_thread, NULL, "test-events"); + if (WARN_ON(IS_ERR(test_thread))) + return; msleep(1); kthread_stop(test_thread); } From 372f8534244d632ad5118e8a87a11291b01712d3 Mon Sep 17 00:00:00 2001 From: Deepanshu Kartikey Date: Mon, 17 Aug 2026 19:36:55 +0530 Subject: [PATCH 0812/1328] tracing: Fix use-after-free in trace_pipe read on sub-buffer order change Writing to buffer_subbuf_size_kb calls ring_buffer_subbuf_order_set(), which frees every sub-buffer of the ring buffer, including the reader page, and replaces them with newly allocated ones. Readers of trace_pipe hold pointers into those pages. ring_buffer_peek() looks up an event under cpu_buffer->reader_lock but returns the event pointer after dropping the lock, and peek_next_entry() then calls ring_buffer_event_length() and ring_buffer_event_data() on it. If the sub-buffer order is changed in that window, the reader dereferences freed memory: BUG: KASAN: use-after-free in ring_buffer_peek+0x3e0/0x430 Read of size 1 at addr ffff88802a4cf010 by task syz-executor989/6002 Freed by: free_buffer_page kernel/trace/ring_buffer.c:398 [inline] ring_buffer_subbuf_order_set+0x1325/0x18e0 kernel/trace/ring_buffer.c:7444 buffer_subbuf_size_write+0x182/0x280 kernel/trace/trace.c:8221 Take trace_access_lock(RING_BUFFER_ALL_CPUS) around the order change. This is the lock trace_pipe readers already hold across their entire peek-and-print loop, so the swap can no longer race with a reader that is dereferencing a peeked event. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260817140655.5694-1-kartikey406@gmail.com Fixes: f9b94daa542a ("ring-buffer: Set new size of the ring buffer sub page") Reported-by: syzbot+685955db58555575fdd2@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=685955db58555575fdd2 Tested-by: syzbot+685955db58555575fdd2@syzkaller.appspotmail.com Reviewed-by: Bradley Morgan Signed-off-by: Deepanshu Kartikey Signed-off-by: Steven Rostedt --- kernel/trace/trace.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index 740c5f358b75..60c87977f1e3 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -8214,6 +8214,8 @@ buffer_subbuf_size_write(struct file *filp, const char __user *ubuf, /* Do not allow tracing while changing the order of the ring buffer */ tracing_stop_tr(tr); + trace_access_lock(RING_BUFFER_ALL_CPUS); + old_order = ring_buffer_subbuf_order_get(tr->array_buffer.buffer); if (old_order == order) goto out; @@ -8253,6 +8255,7 @@ buffer_subbuf_size_write(struct file *filp, const char __user *ubuf, #endif (*ppos)++; out: + trace_access_unlock(RING_BUFFER_ALL_CPUS); if (ret) cnt = ret; tracing_start_tr(tr); From 1fcccdc20a76b4639b00124cf2a379b837bd2bf7 Mon Sep 17 00:00:00 2001 From: Csaba Budai Date: Fri, 21 Aug 2026 12:25:14 +0200 Subject: [PATCH 0813/1328] ALSA: hda/realtek: Fix ALC700 audio on Intel Hades Canyon On Intel Hades Canyon systems with an ALC700 codec, analog audio can become unusable after rebooting from Windows into Linux. The affected system uses codec subsystem ID 8086:2073. In the broken state, Realtek coefficient 0x1b has value 0x4a4b. Setting bit 0x0400 changes it to 0x4e4b and immediately restores normal analog playback. The BIOS also sets this bit after disabling and re-enabling onboard audio, while Windows may leave it cleared. Linux currently does not restore it during codec initialization. Add a machine-specific fixup for Intel Hades Canyon which sets only bit 0x0400 of coefficient 0x1b during HDA_FIXUP_ACT_INIT. The fix has been verified across Windows -> Linux reboots on an Intel NUC8i7HNB with an ALC700 codec. Signed-off-by: Csaba Budai Link: https://patch.msgid.link/20260821102514.11194-1-ilyr73@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index fe553db5b52f..5c4ea24e9651 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -4099,6 +4099,7 @@ enum { ALC225_FIXUP_DELL_WYSE_MIC_NO_PRESENCE, ALC225_FIXUP_S3_POP_NOISE, ALC700_FIXUP_INTEL_REFERENCE, + ALC700_FIXUP_INTEL_HADES_CANYON, ALC274_FIXUP_DELL_BIND_DACS, ALC274_FIXUP_DELL_AIO_LINEOUT_VERB, ALC298_FIXUP_TPT470_DOCK_FIX, @@ -4345,6 +4346,19 @@ static void alc287_fixup_lenovo_yoga_book_9i(struct hda_codec *codec, __snd_hda_apply_fixup(codec, id, action, 0); } +static void alc700_fixup_intel_hades_canyon(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + /* + * Windows may leave coef 0x1b bit 0x0400 cleared, causing broken + * analog playback after rebooting into Linux. Restore the bit + * during codec initialization. + */ + if (action == HDA_FIXUP_ACT_INIT) + alc_update_coef_idx(codec, 0x1b, 0x0400, 0x0400); +} + static const struct hda_fixup alc269_fixups[] = { [ALC298_FIXUP_RAZER_BLADE16_2025_PINS] = { .type = HDA_FIXUP_PINS, @@ -5423,6 +5437,10 @@ static const struct hda_fixup alc269_fixups[] = { {} } }, + [ALC700_FIXUP_INTEL_HADES_CANYON] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc700_fixup_intel_hades_canyon, + }, [ALC274_FIXUP_DELL_BIND_DACS] = { .type = HDA_FIXUP_FUNC, .v.func = alc274_fixup_bind_dacs, @@ -8254,6 +8272,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x2782, 0xa128, "Positivo N15RPE-S", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), SND_PCI_QUIRK(0x2782, 0xa212, "Lunnen Ground 14", ALC269VC_FIXUP_LUNNEN_GROUND_14), SND_PCI_QUIRK(0x7017, 0x2014, "Star Labs StarFighter", ALC233_FIXUP_STARLABS_STARFIGHTER), + SND_PCI_QUIRK(0x8086, 0x2073, "Intel NUC 8 Hades Canyon", + ALC700_FIXUP_INTEL_HADES_CANYON), SND_PCI_QUIRK(0x8086, 0x2074, "Intel NUC 8", ALC233_FIXUP_INTEL_NUC8_DMIC), SND_PCI_QUIRK(0x8086, 0x2080, "Intel NUC 8 Rugged", ALC256_FIXUP_INTEL_NUC8_RUGGED), SND_PCI_QUIRK(0x8086, 0x2081, "Intel NUC 10", ALC256_FIXUP_INTEL_NUC10), From af79b081ce332a3cb0e5c11b7666c6af5d5f4cbc Mon Sep 17 00:00:00 2001 From: Edward Blair Date: Sat, 22 Aug 2026 12:41:10 +0100 Subject: [PATCH 0814/1328] ALSA: usb-audio: Add quirk flags for SMSL USB DAC The SMSL USB DAC with USB ID 152a:85dd produces an audible pop when snd-usb-audio performs its redundant probe-time interface setup. It also pops when returning from runtime suspend. Skip the probe-time interface setup and disable runtime autosuspend for this device. Both flags have been verified through the quirk_flags module parameter. Keep QUIRK_FLAG_DSD_RAW in the device entry because the exact match takes precedence over the generic 0x152a vendor entry from which the device currently inherits that flag. Signed-off-by: Edward Blair Link: https://patch.msgid.link/20260822114110.1069541-1-edward.blair@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index f4e1eac639b9..0dc5695160eb 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2395,6 +2395,9 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_IFB_SILENCE_ON_EMPTY), DEVICE_FLG(0x13e5, 0x0001, /* Serato Phono */ QUIRK_FLAG_IGNORE_CTL_ERROR), + DEVICE_FLG(0x152a, 0x85dd, /* SMSL USB DAC */ + QUIRK_FLAG_DSD_RAW | QUIRK_FLAG_DISABLE_AUTOSUSPEND | + QUIRK_FLAG_SKIP_IFACE_SETUP), DEVICE_FLG(0x152a, 0x880a, /* NeuralDSP Quad Cortex */ 0), /* Doesn't have the vendor quirk which would otherwise apply */ DEVICE_FLG(0x1532, 0x055e, /* Razer Nommo V2 X */ From f3d14f0a8dc30eb9755ecd24c853e6eff882fb80 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Sun, 23 Aug 2026 02:52:28 +0800 Subject: [PATCH 0815/1328] ALSA: doc: usb-audio: Add doc for QUIRK_FLAG_ALWAYS_SET_RATE QUIRK_FLAG_ALWAYS_SET_RATE was introduced into usb-audio before without appropriate documentation, so add it. There is an odd colon in its comments, so remove it too. As this is often forgotten, also add a reminder to the end of the enumeration. Fixes: 786f91da8535 ("ALSA: usb-audio: add QUIRK_FLAG_ALWAYS_SET_RATE for Mackie DLZ Creator XS") Signed-off-by: Rong Zhang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260823-uac-demote-sticky-check-v1-1-a8cbe572edff@rong.moe --- Documentation/sound/alsa-configuration.rst | 5 +++++ sound/usb/usbaudio.h | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Documentation/sound/alsa-configuration.rst b/Documentation/sound/alsa-configuration.rst index b2171472e6cb..71733e807a7c 100644 --- a/Documentation/sound/alsa-configuration.rst +++ b/Documentation/sound/alsa-configuration.rst @@ -2406,6 +2406,11 @@ quirk_flags works around it by enforcing the fixed max URBs (12) instead of the dynamic calculation from the buffer size, and passing the `URB_ISO_ASAP` URB flag. + * bit 32: ``always_set_rate`` + Issue SET_CUR for the sample rate even when the clock already reports + the requested rate. A device advertising a single rate is otherwise + never sent the request at all, and some require it before streaming + will start. This module supports multiple devices, autoprobe and hotplugging. diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h index c49709d7ad25..abc2d7e579de 100644 --- a/sound/usb/usbaudio.h +++ b/sound/usb/usbaudio.h @@ -260,7 +260,7 @@ extern bool snd_usb_skip_validation; * to insufficient buffer depth combined with xHCI scheduling variability. * The larger buffer (MAX_URBS = 12, ~64ms) absorbs system scheduling * jitter during boot, while URB_ISO_ASAP ensures consistent xHCI scheduling. - * QUIRK_FLAG_ALWAYS_SET_RATE: + * QUIRK_FLAG_ALWAYS_SET_RATE * Issue SET_CUR for the sample rate even when the clock already reports the * requested rate. A device advertising a single rate is otherwise never sent * the request at all, and some require it before streaming will start. @@ -300,7 +300,7 @@ enum { QUIRK_TYPE_MIXER_GET_CUR_BROKEN = 30, QUIRK_TYPE_PLAYBACK_URB_FIXUP = 31, QUIRK_TYPE_ALWAYS_SET_RATE = 32, -/* Please also edit snd_usb_audio_quirk_flag_names */ +/* Please also edit snd_usb_audio_quirk_flag_names and alsa-configuration.rst */ }; #define QUIRK_FLAG(x) BIT_U64(QUIRK_TYPE_ ## x) From 3c87a903a6820a0789bbab61681dd570d6030260 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Sun, 23 Aug 2026 02:52:29 +0800 Subject: [PATCH 0816/1328] ALSA: usb-audio: Demote the severity of sticky GET_CUR Currently, a mixer is disabled when its GET_CUR is sticky, causing userspace to fall back to soft mixers, unless QUIRK_FLAG_MIXER_GET_CUR_BROKEN is set. This leads to issues on some wireless headphones with broken GET_CUR but effective SET_CUR, which use poorly-performed lossy codecs and are prone to audible distortion at low volume. They have to set the quirk flag to reeanble the mixer. Considering that users can always opt into soft mixers if they need it, i.e., when SET_CUR is stubbed, demote the severity of sticky GET_CUR by marking GET_CUR as broken and only provide mixer values from the cache. The mixer itself is still registered. Signed-off-by: Rong Zhang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260823-uac-demote-sticky-check-v1-2-a8cbe572edff@rong.moe --- sound/usb/mixer.c | 76 +++++++++++++---------------------------------- 1 file changed, 21 insertions(+), 55 deletions(-) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 5de182181ede..3aa9a9c12557 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -1237,21 +1237,27 @@ static void init_cur_mix_raw(struct usb_mixer_elem_info *cval, int ch, int idx) } /* - * Additional checks for sticky mixers + * Additional checks for sticky GET_CUR * - * Some devices' volume control mixers are sticky, which accept SET_CUR but - * do absolutely nothing. + * Some devices' volume control mixers have sticky GET_CUR, which implies either + * stubbed SET_CUR or broken GET_CUR. For the former case, the mixer accepts + * SET_CUR but do absolutely nothing, so falling back to soft mixer is the only + * way to control the volume. For the latter case, the mixer has effective + * SET_CUR despite GET_CUR being constant, and the mixer is usable as long as we + * always provide mixer value from the ceche. * * Check the return values of GET_CUR with different SET_CUR values. Consider - * the mixer as sticky if GET_CUR always returns a constant value. + * GET_CUR as sticky if GET_CUR always returns a constant value. * - * Some devices have effective SET_CUR despite GET_CUR being constant. Do not - * consider the mixer as sticky if a quirk flag indicates that. + * Unfortunately, we can't distinguish between stubbed SET_CUR and broken + * GET_CUR with simple read-back tests. Disabling the mixer regardless and + * forcing userspace to use soft mixer instead can lead to audible distortion at + * low volume on some wireless headphones, probably due to their poorly- + * performed lossy codec. * - * Gate the registration of sticky mixers to prevent confusing userspace, so - * that they won't cause ineffective volume control. However, for mixers with - * effective SET_CUR but broken GET_CUR, the registration can continue normally - * but further GET_CUR requests will be gated. + * Instead, mark GET_CUR as broken regardless and only provide mixer value from + * the cache. Users may opt into soft mixer in userspace audio stack if they + * need it. */ static int check_sticky_volume_control(struct usb_mixer_elem_info *cval, int channel, int saved) @@ -1271,24 +1277,13 @@ static int check_sticky_volume_control(struct usb_mixer_elem_info *cval, return 0; } - if (cval->head.mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_GET_CUR_BROKEN) { - usb_audio_info(cval->head.mixer->chip, - "%d:%d: broken mixer GET_CUR (%d/%d/%d => %d)\n", - cval->head.id, mixer_ctrl_intf(cval->head.mixer), - cval->min, cval->max, cval->res, saved); - - cval->get_cur_broken = 1; - return -ENXIO; - } - - usb_audio_err(cval->head.mixer->chip, - "%d:%d: sticky mixer values (%d/%d/%d => %d), disabling\n", - cval->head.id, mixer_ctrl_intf(cval->head.mixer), - cval->min, cval->max, cval->res, saved); usb_audio_info(cval->head.mixer->chip, - "check MIXER_GET_CUR_BROKEN if you believe the mixer is non-sticky"); + "%d:%d: broken mixer GET_CUR (%d/%d/%d => %d)\n", + cval->head.id, mixer_ctrl_intf(cval->head.mixer), + cval->min, cval->max, cval->res, saved); - return -ENODEV; + cval->get_cur_broken = 1; + return -ENXIO; } /* @@ -1385,8 +1380,6 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, goto no_checks; ret = check_sticky_volume_control(cval, minchn, saved); - if (ret == -ENODEV) - goto sticky; if (ret) goto no_checks; @@ -1455,33 +1448,6 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, } return 0; - -sticky: - /* - * It makes no sense to restore the saved value for a sticky mixer, - * since setting any value is a no-op. - * - * However, in some rare cases, SET_CUR is effective despite GET_CUR - * always returns a constant value. These mixers are not sticky, but - * there's no way to distinguish them. Without any additional - * information, the best thing we can do is to set the mixer value to - * the maximum before bailing out, so that a soft mixer can still reach - * the maximum hardware volume if the mixer turns out to be non-sticky. - * Meanwhile, all channels must be synchronized to prevent imbalance - * volume. - */ - if (!cval->cmask) { - snd_usb_set_cur_mix_value(cval, 0, 0, cval->max); - } else { - idx = 0; - for (i = 0; i < MAX_CHANNELS; i++) { - if (cval->cmask & BIT(i)) { - snd_usb_set_cur_mix_value(cval, i + 1, idx, cval->max); - idx++; - } - } - } - return ret; } #define get_min_max(cval, def) get_min_max_with_quirks(cval, def, NULL) From 9e8a6fc3b1a5ee7e3ccbd11ba7d5bfeb7eaf8ebf Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Sun, 23 Aug 2026 02:52:30 +0800 Subject: [PATCH 0817/1328] ALSA: usb-audio: Reverse MIXER_GET_CUR_BROKEN as MIXER_GET_CUR_OK The default behavior of sticky check now becomes what QUIRK_FLAG_MIXER_GET_CUR_BROKEN originally does, so the quirk flag is no longer needed. On some devices, whether their GET_CUR being sticky depends on whether hotpluggable components are present. When the hotpluggable components are missing on probe, their GET_CUR behavior is classified as broken. Therefore, reverse QUIRK_FLAG_MIXER_GET_CUR_BROKEN as QUIRK_FLAG_MIXER_GET_CUR_OK, so that it can be set to prevent the heuristics from gating GET_CUR. Note that even if the quirk flag is set, init_cur_mix_raw() should still initialize the mixer value to cval->min, otherwise restoring the bogus saved value on the first channel could lead to unbalanced channels. Signed-off-by: Rong Zhang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260823-uac-demote-sticky-check-v1-3-a8cbe572edff@rong.moe --- Documentation/sound/alsa-configuration.rst | 20 ++++++++------------ sound/usb/mixer.c | 8 ++++++++ sound/usb/quirks.c | 16 +++------------- sound/usb/usbaudio.h | 20 +++++++------------- 4 files changed, 26 insertions(+), 38 deletions(-) diff --git a/Documentation/sound/alsa-configuration.rst b/Documentation/sound/alsa-configuration.rst index 71733e807a7c..e500ec84e3ef 100644 --- a/Documentation/sound/alsa-configuration.rst +++ b/Documentation/sound/alsa-configuration.rst @@ -2316,6 +2316,9 @@ quirk_flags applies the ``mixer_playback_min_mute`` flag and clears the ``ignore_ctl_error`` flag for the device 1234:abcd, and applies the ``skip_sample_rate`` flag for all devices. + New quirk flags may replace old ones by reusing the latter's bits, so the + new usage is preferred. Despite that, depending on the order of probing is + fragile, so it'd better migrate to the new usage anyway. * bit 0: ``get_sample_rate`` Skip reading sample rate for devices @@ -2389,18 +2392,11 @@ quirk_flags from snd_usb_handle_sync_urb. Instead fall through and enqueue a packet_info containing only size-0 packets, so the OUT ring keeps moving (emits silence). Needed by Behringer Flow 8 (1397:050c). - * bit 30: ``mixer_get_cur_broken`` - Some mixers are sticky, which means that setting their current volume - is a no-op, and reading the current volume returns a constant value. - The sticky check disables these mixers to prevent confusing userspace. - However, some devices do have a tunable volume despite the reported - current volume being constant. As the sticky check can't distinguish - between the two categories, setting this flag tells that the device - should fall into the second category when GET_CUR returns a constant - value, resulting in the sticky check being non-fatal and only - disabling GET_CUR instead of the whole mixer. The current volume will - then be provided by the internal cache that stores the last set - volume + * bit 30: ``mixer_get_cur_ok`` + On some devices, whether their GET_CUR being sticky depends on whether + hotpluggable components are present. When the hotpluggable components + are missing on probe, their GET_CUR behavior is classified as broken. + Set the flag to prevent the heuristics from gating GET_CUR. * bit 31: ``playback_urb_fixup`` Some devices show the stuttering at playback, and this quirk works around it by enforcing the fixed max URBs (12) instead of diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 3aa9a9c12557..ecaa8bc08d7c 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -1447,6 +1447,14 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, } } + /* + * When GET_CUR is sticky, the saved value is bogus, so mixer values set + * by the sanity checks must be discarded through init_cur_mix_raw(). + * After that, we can clear the flag as per QUIRK_FLAG_MIXER_GET_CUR_OK. + */ + if (cval->head.mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_GET_CUR_OK) + cval->get_cur_broken = 0; + return 0; } diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 0dc5695160eb..2f5276cf7339 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2215,8 +2215,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), DEVICE_FLG(0x03f0, 0x654a, /* HP 320 FHD Webcam */ QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), - DEVICE_FLG(0x040b, 0x0897, /* Weltrend Semiconductor, sold as Redragon H510-PRO Wireless headset */ - QUIRK_FLAG_MIXER_GET_CUR_BROKEN), DEVICE_FLG(0x041e, 0x3000, /* Creative SB Extigy */ QUIRK_FLAG_IGNORE_CTL_ERROR), DEVICE_FLG(0x041e, 0x324d, /* Creative Sound Blaster Play! 3 */ @@ -2258,8 +2256,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { DEVICE_FLG(0x046d, 0x0a8f, /* Logitech H390 headset */ QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), - DEVICE_FLG(0x046d, 0x0af7, /* Logitech PRO X 2 LIGHTSPEED */ - QUIRK_FLAG_MIXER_GET_CUR_BROKEN), DEVICE_FLG(0x0499, 0x1506, /* Yamaha THR5 */ QUIRK_FLAG_GENERIC_IMPLICIT_FB), DEVICE_FLG(0x0499, 0x1509, /* Steinberg UR22 */ @@ -2331,9 +2327,9 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { DEVICE_FLG(0x0763, 0x2031, /* M-Audio Fast Track C600 */ QUIRK_FLAG_GENERIC_IMPLICIT_FB), DEVICE_FLG(0x0763, 0x2080, /* M-Audio Fast Track Ultra */ - QUIRK_FLAG_MIXER_GET_CUR_BROKEN | QUIRK_FLAG_GENERIC_IMPLICIT_FB), + QUIRK_FLAG_GENERIC_IMPLICIT_FB), DEVICE_FLG(0x0763, 0x2081, /* M-Audio Fast Track Ultra */ - QUIRK_FLAG_MIXER_GET_CUR_BROKEN | QUIRK_FLAG_GENERIC_IMPLICIT_FB), + QUIRK_FLAG_GENERIC_IMPLICIT_FB), DEVICE_FLG(0x07fd, 0x000b, /* MOTU M Series 2nd hardware revision */ QUIRK_FLAG_CTL_MSG_DELAY_1M), DEVICE_FLG(0x08bb, 0x2702, /* LineX FM Transmitter */ @@ -2372,8 +2368,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_SHARE_MEDIA_DEVICE | QUIRK_FLAG_ALIGN_TRANSFER), DEVICE_FLG(0x1038, 0x1294, /* SteelSeries Arctis Pro Wireless */ QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), - DEVICE_FLG(0x1038, 0x2232, /* SteelSeries Arctis Nova 5 */ - QUIRK_FLAG_MIXER_GET_CUR_BROKEN), DEVICE_FLG(0x1101, 0x0003, /* Audioengine D1 */ QUIRK_FLAG_GET_SAMPLE_RATE), DEVICE_FLG(0x12d1, 0x3a07, /* HUAWEI USB-C HEADSET */ @@ -2381,8 +2375,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), DEVICE_FLG(0x1224, 0x2a25, /* Jieli Technology USB PHY 2.0 */ QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), - DEVICE_FLG(0x1377, 0x6004, /* Sennheiser MOMENTUM 3 */ - QUIRK_FLAG_MIXER_GET_CUR_BROKEN), DEVICE_FLG(0x1395, 0x740a, /* Sennheiser DECT */ QUIRK_FLAG_GET_SAMPLE_RATE), DEVICE_FLG(0x1397, 0x0507, /* Behringer UMC202HD */ @@ -2510,8 +2502,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_CTL_MSG_DELAY_1M), DEVICE_FLG(0x2d99, 0x0026, /* HECATE G2 GAMING HEADSET */ QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), - DEVICE_FLG(0x2d99, 0xa024, /* Edifier MF200 */ - QUIRK_FLAG_MIXER_GET_CUR_BROKEN), DEVICE_FLG(0x2fc6, 0xf06b, /* MOONDROP Moonriver2 Ti */ QUIRK_FLAG_CTL_MSG_DELAY), DEVICE_FLG(0x2fc6, 0xf0b5, /* iBasso DC-Elite */ @@ -2652,7 +2642,7 @@ static const char *const snd_usb_audio_quirk_flag_names[] = { QUIRK_STRING_ENTRY(MIXER_PLAYBACK_LINEAR_VOL), QUIRK_STRING_ENTRY(MIXER_CAPTURE_LINEAR_VOL), QUIRK_STRING_ENTRY(IFB_SILENCE_ON_EMPTY), - QUIRK_STRING_ENTRY(MIXER_GET_CUR_BROKEN), + QUIRK_STRING_ENTRY(MIXER_GET_CUR_OK), QUIRK_STRING_ENTRY(PLAYBACK_URB_FIXUP), QUIRK_STRING_ENTRY(ALWAYS_SET_RATE), NULL diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h index abc2d7e579de..24436473931d 100644 --- a/sound/usb/usbaudio.h +++ b/sound/usb/usbaudio.h @@ -243,17 +243,11 @@ extern bool snd_usb_skip_validation; * from snd_usb_handle_sync_urb. Instead fall through and enqueue a * packet_info containing only size-0 packets, so the OUT ring keeps * moving (emits silence). Needed by Behringer Flow 8 (1397:050c). - * QUIRK_FLAG_MIXER_GET_CUR_BROKEN - * Some mixers are sticky, which means that setting their current volume is a - * no-op, and reading the current volume returns a constant value. The sticky - * check disables these mixers to prevent confusing userspace. However, some - * devices do have a tunable volume despite the reported current volume being - * constant. As the sticky check can't distinguish between the two categories, - * setting this flag tells that the device should fall into the second - * category when GET_CUR returns a constant value, resulting in the sticky - * check being non-fatal and only disabling GET_CUR instead of the whole mixer. - * The current volume will then be provided by the internal cache that stores - * the last set volume + * QUIRK_FLAG_MIXER_GET_CUR_OK + * On some devices, whether their GET_CUR being sticky depends on whether + * hotpluggable components are present. When the hotpluggable components are + * missing on probe, their GET_CUR behavior is classified as broken. Set the + * flag to prevent the heuristics from gating GET_CUR. * QUIRK_FLAG_PLAYBACK_URB_FIXUP * Set URB_ISO_ASAP flag for isochronous URBs and force nurbs to MAX_URBS. * This is needed for devices that exhibit boot-time audio stuttering due @@ -297,7 +291,7 @@ enum { QUIRK_TYPE_MIXER_PLAYBACK_LINEAR_VOL = 27, QUIRK_TYPE_MIXER_CAPTURE_LINEAR_VOL = 28, QUIRK_TYPE_IFB_SILENCE_ON_EMPTY = 29, - QUIRK_TYPE_MIXER_GET_CUR_BROKEN = 30, + QUIRK_TYPE_MIXER_GET_CUR_OK = 30, QUIRK_TYPE_PLAYBACK_URB_FIXUP = 31, QUIRK_TYPE_ALWAYS_SET_RATE = 32, /* Please also edit snd_usb_audio_quirk_flag_names and alsa-configuration.rst */ @@ -335,7 +329,7 @@ enum { #define QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL QUIRK_FLAG(MIXER_PLAYBACK_LINEAR_VOL) #define QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL QUIRK_FLAG(MIXER_CAPTURE_LINEAR_VOL) #define QUIRK_FLAG_IFB_SILENCE_ON_EMPTY QUIRK_FLAG(IFB_SILENCE_ON_EMPTY) -#define QUIRK_FLAG_MIXER_GET_CUR_BROKEN QUIRK_FLAG(MIXER_GET_CUR_BROKEN) +#define QUIRK_FLAG_MIXER_GET_CUR_OK QUIRK_FLAG(MIXER_GET_CUR_OK) #define QUIRK_FLAG_PLAYBACK_URB_FIXUP QUIRK_FLAG(PLAYBACK_URB_FIXUP) #define QUIRK_FLAG_ALWAYS_SET_RATE QUIRK_FLAG(ALWAYS_SET_RATE) From 3ac761b613c744aa22e269d31801053e38bb4968 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Sun, 23 Aug 2026 02:52:31 +0800 Subject: [PATCH 0818/1328] ALSA: usb-audio: Add quirk flags for Logitech PRO X Wireless The Logitech PRO X Wireless is a wireless headset with a hotpluggable microphone. Its Playback mixer's GET_CUR somehow becomes broken when the microphone is detached, so set QUIRK_FLAG_MIXER_GET_CUR_OK to prevent the mixer behavior from depending on whether the microphone is attached. Meanwhile, the Playback mixer's minimum value doesn't work properly, thus set QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE to prevent userspace audio stack from using the minimum mixer value to tune volume (setting it when muted is OK). Reported-by: Alexander Niemeyer Closes: https://msgid.link/6262cbbd-d1f2-4c9d-a1c7-9c5d12636f4b@gmx.de Closes: https://msgid.link/7984832b-86f6-4934-bfc0-1ed70218973a@gmx.de Signed-off-by: Rong Zhang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260823-uac-demote-sticky-check-v1-4-a8cbe572edff@rong.moe --- sound/usb/quirks.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 2f5276cf7339..5c544bd88728 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2256,6 +2256,9 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { DEVICE_FLG(0x046d, 0x0a8f, /* Logitech H390 headset */ QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), + DEVICE_FLG(0x046d, 0x0aba, /* Logitech PRO X Wireless */ + QUIRK_FLAG_MIXER_GET_CUR_OK | + QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), DEVICE_FLG(0x0499, 0x1506, /* Yamaha THR5 */ QUIRK_FLAG_GENERIC_IMPLICIT_FB), DEVICE_FLG(0x0499, 0x1509, /* Steinberg UR22 */ From 17056a36d276a740f09dca723de1bc6aa3eb4ef9 Mon Sep 17 00:00:00 2001 From: Will Porter Date: Sat, 22 Aug 2026 20:53:53 -0500 Subject: [PATCH 0819/1328] ALSA: usb-audio: Add iD14 MkI to monitor-mix volume quirk Add the iD14 MkI USB ID to the existing audient_id14_map. This names feature unit 12 Monitor Mix Playback Volume. Live meter A/B testing on MkI USB ID 2708:0002 reproduced the same asymmetric attenuation seen on MkII. At ALSA value 104, the right channel was 23.07 dB below the left. At 127, both channels were equal. The misleading Speaker Playback Volume name causes PipeWire to adopt FU12 as the stream master. Reuse the existing map to keep the control available while preventing that selection. Hardware-tested on Bazzite 7.2.0-ogc4.1.fc44.x86_64 with this change backported. ALSA exposed Monitor Mix Playback Volume after module load and USB reconnect. PipeWire volume changes from 100% to 35% and back left all four FU12 values at 127. Channel-isolated left/right playback remained balanced after reconnect. Build-tested with CONFIG_SND_USB_AUDIO=m using the focused sound/usb target. Assisted-by: Codex:gpt-5 Signed-off-by: Will Porter Link: https://patch.msgid.link/20260823015353.686080-1-mrwillporter@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/mixer_maps.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c index 8046d5987d5b..ace4ccad8f51 100644 --- a/sound/usb/mixer_maps.c +++ b/sound/usb/mixer_maps.c @@ -505,13 +505,13 @@ static const struct usbmix_connector_map gigabyte_b450_connector_map[] = { {} }; -/* Audient iD14: FU 12 advertises Volume on only 4 of its 6 logical channels - * and sits on the monitor mixer branch, but it is traced through to the - * Speaker output terminal and gets named "Speaker Playback Volume". Userspace - * then adopts it as the stream's hardware volume, and any setting below 0 dB - * attenuates some channels but not others (20 dB imbalance at 80%). Give it a - * non-standard name so that it is no longer taken for the stream's master - * volume, while remaining reachable for anyone who wants the monitor gain. +/* Audient iD14 MkI and MkII: FU 12 sits on the monitor mixer branch but is + * traced through to the Speaker output terminal, so it is named "Speaker + * Playback Volume". On MkII it controls only 4 of 6 playback channels. MkI + * testing found asymmetric attenuation within the main stereo pair. Userspace + * adopts this control as the stream's hardware volume, causing imbalance below + * 0 dB. Give it a non-standard name so that userspace no longer treats it as + * the stream master, while keeping the monitor gain reachable. */ static const struct usbmix_name_map audient_id14_map[] = { { 12, "Monitor Mix Playback" }, /* FU, partial coverage */ @@ -602,7 +602,12 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { .map = maya44_map, }, { - /* Audient iD14 */ + /* Audient iD14 MkI */ + .id = USB_ID(0x2708, 0x0002), + .map = audient_id14_map, + }, + { + /* Audient iD14 MkII */ .id = USB_ID(0x2708, 0x0008), .map = audient_id14_map, }, From 1ffb5be3e4be37a8f8d88861d3e1a1a1539b4c97 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 21 Aug 2026 22:10:16 +0100 Subject: [PATCH 0820/1328] dt-bindings: rtc: renesas,rzn1-rtc: Add RZ/T2H and RZ/N2H support Add compatible strings for the RTC block found on the Renesas RZ/T2H (R9A09G077) and RZ/N2H (R9A09G087) SoCs. These SoCs integrate a closely related variant of the RZ/N1 RTC IP. Unlike RZ/N1, they do not implement the RTCA0SUBU register. This is not a limitation for Linux support, as these registers are not used when the RTC operates in "scmp" clock mode, which is required on RZ/T2H and RZ/N2H due to their 195.3 kHz input clock. The RZ/T2H RTC variant also supports a 1Hz output signal on the RTCAT1HZ pin, controlled by the RTCA0CTL1[RTCA01HZE] bit. This bit is marked as reserved in the RZ/N1 hardware manual. Update the binding schema to require the additional clock inputs used by these SoCs. Signed-off-by: Lad Prabhakar Acked-by: Conor Dooley Reviewed-by: Wolfram Sang Link: https://patch.msgid.link/20260821211032.13554-2-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Alexandre Belloni --- .../bindings/rtc/renesas,rzn1-rtc.yaml | 35 +++++++++++++++---- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/Documentation/devicetree/bindings/rtc/renesas,rzn1-rtc.yaml b/Documentation/devicetree/bindings/rtc/renesas,rzn1-rtc.yaml index 1860f0e4c31a..ea7b039a91e7 100644 --- a/Documentation/devicetree/bindings/rtc/renesas,rzn1-rtc.yaml +++ b/Documentation/devicetree/bindings/rtc/renesas,rzn1-rtc.yaml @@ -9,15 +9,19 @@ title: Renesas RZ/N1 SoCs Real-Time Clock maintainers: - Miquel Raynal -allOf: - - $ref: rtc.yaml# - properties: compatible: - items: - - enum: - - renesas,r9a06g032-rtc - - const: renesas,rzn1-rtc + oneOf: + - items: + - enum: + - renesas,r9a06g032-rtc + - const: renesas,rzn1-rtc + + - const: renesas,r9a09g077-rtc + + - items: + - const: renesas,r9a09g087-rtc + - const: renesas,r9a09g077-rtc reg: maxItems: 1 @@ -54,6 +58,23 @@ required: - clock-names - power-domains +allOf: + - $ref: rtc.yaml# + + - if: + properties: + compatible: + contains: + enum: + - renesas,r9a09g077-rtc + - renesas,r9a09g087-rtc + then: + properties: + clocks: + minItems: 2 + clock-names: + minItems: 2 + unevaluatedProperties: false examples: From 708546aa39560a11cf44c7ba99492c8395a6c2fb Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 21 Aug 2026 22:10:17 +0100 Subject: [PATCH 0821/1328] rtc: rzn1: Handle EPROBE_DEFER for optional pps interrupt Check for -EPROBE_DEFER from platform_get_irq_byname_optional() and handle the deferred probe request properly. Although the "pps" interrupt is optional, an error code of -EPROBE_DEFER indicates that the interrupt subsystem is not yet ready. Intercept this specific error condition, assign it to the return value, and jump to the dis_runtime_pm label to avoid ignoring a valid probe deferral. Fixes: eea7791e00f33 ("rtc: rzn1: implement one-second accuracy for alarms") Cc: stable@vger.kernel.org Signed-off-by: Lad Prabhakar Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Link: https://patch.msgid.link/20260821211032.13554-3-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rzn1.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/rtc/rtc-rzn1.c b/drivers/rtc/rtc-rzn1.c index c4ed43735457..f81d691c8b9a 100644 --- a/drivers/rtc/rtc-rzn1.c +++ b/drivers/rtc/rtc-rzn1.c @@ -465,6 +465,10 @@ static int rzn1_rtc_probe(struct platform_device *pdev) } irq = platform_get_irq_byname_optional(pdev, "pps"); + if (irq == -EPROBE_DEFER) { + ret = irq; + goto dis_runtime_pm; + } if (irq >= 0) ret = devm_request_irq(&pdev->dev, irq, rzn1_rtc_1s_irq, 0, "RZN1 RTC 1s", rtc); From 022a2839a52006531804a8db55d3228084400b48 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 21 Aug 2026 22:10:18 +0100 Subject: [PATCH 0822/1328] rtc: rzn1: Fix weekday underflow when alarm crosses month boundary rzn1_rtc_set_alarm() calculates the alarm weekday from the difference between the alarm day and the current day of the month. When the alarm crosses a month boundary, this difference can become negative. Since days_ahead is unsigned, it underflows and results in an incorrect weekday being programmed into RZN1_RTC_ALW. The RTC core already provides a fully populated struct rtc_time for the alarm, including the correct tm_wday. Use tm->tm_wday directly instead of recalculating the weekday from the day-of-month. This avoids the underflow and ensures alarms scheduled across a month boundary use the correct weekday. Fixes: b5ad1bf00d2c4 ("rtc: rzn1: Add alarm support") Cc: stable@vger.kernel.org Signed-off-by: Lad Prabhakar Suggested-by: Wolfram Sang Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Link: https://patch.msgid.link/20260821211032.13554-4-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rzn1.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/rtc/rtc-rzn1.c b/drivers/rtc/rtc-rzn1.c index f81d691c8b9a..56284a4320ae 100644 --- a/drivers/rtc/rtc-rzn1.c +++ b/drivers/rtc/rtc-rzn1.c @@ -261,7 +261,6 @@ static int rzn1_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) struct rzn1_rtc *rtc = dev_get_drvdata(dev); struct rtc_time *tm = &alrm->time, tm_now; unsigned long alarm, farest; - unsigned int days_ahead, wday; int ret; ret = rzn1_rtc_read_time(dev, &tm_now); @@ -274,13 +273,9 @@ static int rzn1_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) if (time_after(alarm, farest)) return -ERANGE; - /* Convert alarm day into week day */ - days_ahead = tm->tm_mday - tm_now.tm_mday; - wday = (tm_now.tm_wday + days_ahead) % 7; - writel(bin2bcd(tm->tm_min), rtc->base + RZN1_RTC_ALM); writel(bin2bcd(tm->tm_hour), rtc->base + RZN1_RTC_ALH); - writel(BIT(wday), rtc->base + RZN1_RTC_ALW); + writel(BIT(tm->tm_wday), rtc->base + RZN1_RTC_ALW); rtc->tm_alarm = alrm->time; From 457b5dbce31209e65e1184716ed3af59cb1c0372 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 21 Aug 2026 22:10:19 +0100 Subject: [PATCH 0823/1328] rtc: rzn1: Handle unset alarm weekday in rzn1_rtc_read_alarm RZN1_RTC_ALW is a weekday bitmask where bit N represents weekday N. When no alarm has been configured, the register has its power-on-reset value of zero. rzn1_rtc_read_alarm() uses fls() to convert the weekday bitmask into a weekday number. When RZN1_RTC_ALW is zero, fls(0) returns zero and fls(wday) - 1 evaluates to -1. This invalid weekday is then used to calculate the alarm date and can either leave tm_wday set to -1 or produce a fabricated alarm date. Treat a zero RZN1_RTC_ALW value as an unset alarm weekday and return without calculating the alarm date. Move reading RZN1_RTC_CTL1 before this check so that alrm->enabled is updated for both configured and unconfigured alarms. Fixes: b5ad1bf00d2c4 ("rtc: rzn1: Add alarm support") Cc: stable@vger.kernel.org Signed-off-by: Lad Prabhakar Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Link: https://patch.msgid.link/20260821211032.13554-5-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rzn1.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/rtc/rtc-rzn1.c b/drivers/rtc/rtc-rzn1.c index 56284a4320ae..d4cba0d415b6 100644 --- a/drivers/rtc/rtc-rzn1.c +++ b/drivers/rtc/rtc-rzn1.c @@ -235,13 +235,24 @@ static int rzn1_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) if (ret) return ret; + ctl1 = readl(rtc->base + RZN1_RTC_CTL1); + alrm->enabled = !!(ctl1 & (RZN1_RTC_CTL1_ALME | RZN1_RTC_CTL1_1SE)); + min = readl(rtc->base + RZN1_RTC_ALM); hour = readl(rtc->base + RZN1_RTC_ALH); - wday = readl(rtc->base + RZN1_RTC_ALW); tm->tm_sec = 0; tm->tm_min = bcd2bin(min); tm->tm_hour = bcd2bin(hour); + + /* + * If wday is zero, no bit is set in RZN1_RTC_ALW. This is the + * register's power-on reset value. + */ + wday = readl(rtc->base + RZN1_RTC_ALW); + if (!wday) + return 0; + delta_days = ((fls(wday) - 1) - tm->tm_wday + 7) % 7; tm->tm_wday = fls(wday) - 1; @@ -250,9 +261,6 @@ static int rzn1_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) rtc_time64_to_tm(alarm, tm); } - ctl1 = readl(rtc->base + RZN1_RTC_CTL1); - alrm->enabled = !!(ctl1 & (RZN1_RTC_CTL1_ALME | RZN1_RTC_CTL1_1SE)); - return 0; } From c3e735e9f62022354fc63dbf193cb5614188a33b Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 21 Aug 2026 22:10:20 +0100 Subject: [PATCH 0824/1328] rtc: rzn1: Fix alarm range check truncation on 32-bit systems alarm and farest were declared as unsigned long, but rtc_tm_to_time64() returns time64_t (s64). On 32-bit systems where unsigned long is 32 bits, the assignment silently truncates the upper 32 bits of the timestamp. Fix by declaring alarm and farest as time64_t and replacing time_after() with a direct signed comparison, which is correct for time64_t values that will never realistically overflow. Signed-off-by: Lad Prabhakar Reviewed-by: Geert Uytterhoeven Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Link: https://patch.msgid.link/20260821211032.13554-6-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rzn1.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/rtc/rtc-rzn1.c b/drivers/rtc/rtc-rzn1.c index d4cba0d415b6..f3268655fd37 100644 --- a/drivers/rtc/rtc-rzn1.c +++ b/drivers/rtc/rtc-rzn1.c @@ -268,7 +268,7 @@ static int rzn1_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) { struct rzn1_rtc *rtc = dev_get_drvdata(dev); struct rtc_time *tm = &alrm->time, tm_now; - unsigned long alarm, farest; + time64_t alarm, farest; int ret; ret = rzn1_rtc_read_time(dev, &tm_now); @@ -278,7 +278,7 @@ static int rzn1_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) /* We cannot set alarms more than one week ahead */ farest = rtc_tm_to_time64(&tm_now) + rtc->rtcdev->alarm_offset_max; alarm = rtc_tm_to_time64(tm); - if (time_after(alarm, farest)) + if (alarm > farest) return -ERANGE; writel(bin2bcd(tm->tm_min), rtc->base + RZN1_RTC_ALM); From 51458d5b0a1cfb1b6013400abc95aadf16ed2a57 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 21 Aug 2026 22:10:21 +0100 Subject: [PATCH 0825/1328] rtc: rzn1: Disable alarm interrupt before reprogramming alarm registers rzn1_rtc_set_alarm() updates RZN1_RTC_ALM, RZN1_RTC_ALH and RZN1_RTC_ALW using separate MMIO writes without first disabling the alarm interrupt. If a previous alarm is still enabled, the interrupt can fire while the alarm registers contain a mixture of old and newly written values. Fix this by disabling the alarm interrupt before reprogramming ALM, ALH and ALW with a call to rzn1_rtc_alarm_irq_enable(). Fixes: b5ad1bf00d2c4 ("rtc: rzn1: Add alarm support") Cc: stable@vger.kernel.org Signed-off-by: Lad Prabhakar Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Link: https://patch.msgid.link/20260821211032.13554-7-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rzn1.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/rtc/rtc-rzn1.c b/drivers/rtc/rtc-rzn1.c index f3268655fd37..42e57bf0b4aa 100644 --- a/drivers/rtc/rtc-rzn1.c +++ b/drivers/rtc/rtc-rzn1.c @@ -281,6 +281,11 @@ static int rzn1_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) if (alarm > farest) return -ERANGE; + /* Disable alarm interrupts before reprogramming the alarm. */ + ret = rzn1_rtc_alarm_irq_enable(dev, 0); + if (ret) + return ret; + writel(bin2bcd(tm->tm_min), rtc->base + RZN1_RTC_ALM); writel(bin2bcd(tm->tm_hour), rtc->base + RZN1_RTC_ALH); writel(BIT(tm->tm_wday), rtc->base + RZN1_RTC_ALW); From cd54d8f09d9b61c67e7fc662f4e4e2ffd65a54f2 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 21 Aug 2026 22:10:22 +0100 Subject: [PATCH 0826/1328] rtc: rzn1: Fix malformed MODULE_AUTHOR string Fix a malformed MODULE_AUTHOR macro in the rtc-rzn1 driver where a missing closing angle bracket on the second author entry creates an invalid format. Correct it to the standard "Name " format. Signed-off-by: Lad Prabhakar Reviewed-by: Geert Uytterhoeven Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Link: https://patch.msgid.link/20260821211032.13554-8-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rzn1.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/rtc/rtc-rzn1.c b/drivers/rtc/rtc-rzn1.c index 42e57bf0b4aa..12c52003a7dc 100644 --- a/drivers/rtc/rtc-rzn1.c +++ b/drivers/rtc/rtc-rzn1.c @@ -525,6 +525,6 @@ static struct platform_driver rzn1_rtc_driver = { module_platform_driver(rzn1_rtc_driver); MODULE_AUTHOR("Michel Pollet "); -MODULE_AUTHOR("Miquel Raynal "); MODULE_DESCRIPTION("RZ/N1 RTC driver"); MODULE_LICENSE("GPL"); From 960987abe58d175b94869fb7aca72d7b931b584f Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 21 Aug 2026 22:10:23 +0100 Subject: [PATCH 0827/1328] rtc: Kconfig: Broaden RTC_DRV_RZN1 dependency to ARCH_RENESAS Replace the ARCH_RZN1 dependency with ARCH_RENESAS for the RTC_DRV_RZN1 config option to make the driver available across both ARM32 and ARM64 Renesas architectures. The newer RZ/T2H and RZ/N2H ARM64 SoCs integrate a closely related variant of the RTC IP block found on the RZ/N1 SoCs. Update the build dependency and expand the Kconfig help text to allow this driver to be selected for these additional platforms. Signed-off-by: Lad Prabhakar Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Link: https://patch.msgid.link/20260821211032.13554-9-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Alexandre Belloni --- drivers/rtc/Kconfig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 01def8231873..d23a0fbe8d89 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -1635,10 +1635,11 @@ config RTC_DRV_RS5C313 config RTC_DRV_RZN1 tristate "Renesas RZ/N1 RTC" - depends on ARCH_RZN1 || COMPILE_TEST + depends on ARCH_RENESAS || COMPILE_TEST depends on OF && HAS_IOMEM help - If you say yes here you get support for the Renesas RZ/N1 RTC. + If you say yes here you get support for the RTC initially found on + Renesas RZ/N1 SoCs. config RTC_DRV_GENERIC tristate "Generic RTC support" From ea0ef2e7f83be647be114d8cfaf701a5146eaa90 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 21 Aug 2026 22:10:24 +0100 Subject: [PATCH 0828/1328] rtc: rzn1: Use pm_runtime_put_sync() pm_runtime_put() may trigger the idle check after pm_runtime_disable() is run as part of devm_pm_runtime_enable()'s cleanup action, leaving runtime PM active. Use pm_runtime_put_sync() to ensure the idle check runs synchronously. Signed-off-by: Lad Prabhakar Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Link: https://patch.msgid.link/20260821211032.13554-10-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rzn1.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/rtc/rtc-rzn1.c b/drivers/rtc/rtc-rzn1.c index 12c52003a7dc..84ec8dc397e5 100644 --- a/drivers/rtc/rtc-rzn1.c +++ b/drivers/rtc/rtc-rzn1.c @@ -493,7 +493,7 @@ static int rzn1_rtc_probe(struct platform_device *pdev) return 0; dis_runtime_pm: - pm_runtime_put(&pdev->dev); + pm_runtime_put_sync(&pdev->dev); return ret; } @@ -505,7 +505,7 @@ static void rzn1_rtc_remove(struct platform_device *pdev) /* Disable all interrupts */ writel(0, rtc->base + RZN1_RTC_CTL1); - pm_runtime_put(&pdev->dev); + pm_runtime_put_sync(&pdev->dev); } static const struct of_device_id rzn1_rtc_of_match[] = { From 6e0d7d480f2df83dba82e2fa8f6cd453e9a8326f Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 21 Aug 2026 22:10:25 +0100 Subject: [PATCH 0829/1328] rtc: rzn1: Replace remove callback with devm_add_action_or_reset() Simplify the driver teardown by registering a managed cleanup action with devm_add_action_or_reset(). This eliminates the explicit probe error path and allows the .remove() callback to be dropped. Signed-off-by: Lad Prabhakar Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Link: https://patch.msgid.link/20260821211032.13554-11-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rzn1.c | 56 +++++++++++++++++------------------------- 1 file changed, 23 insertions(+), 33 deletions(-) diff --git a/drivers/rtc/rtc-rzn1.c b/drivers/rtc/rtc-rzn1.c index 84ec8dc397e5..f90785827945 100644 --- a/drivers/rtc/rtc-rzn1.c +++ b/drivers/rtc/rtc-rzn1.c @@ -385,6 +385,17 @@ static const struct rtc_class_ops rzn1_rtc_ops_scmp = { .alarm_irq_enable = rzn1_rtc_alarm_irq_enable, }; +static void rzn1_rtc_disable_hardware(void *data) +{ + struct device *dev = data; + struct rzn1_rtc *rtc = dev_get_drvdata(dev); + + /* Disable all interrupts */ + writel(0, rtc->base + RZN1_RTC_CTL1); + + pm_runtime_put_sync(dev); +} + static int rzn1_rtc_probe(struct platform_device *pdev) { struct rzn1_rtc *rtc; @@ -422,18 +433,19 @@ static int rzn1_rtc_probe(struct platform_device *pdev) if (ret < 0) return ret; + ret = devm_add_action_or_reset(&pdev->dev, rzn1_rtc_disable_hardware, &pdev->dev); + if (ret) + return ret; + /* Only switch to scmp if we have an xtal clock with a valid rate and != 32768 */ xtal = devm_clk_get_optional(&pdev->dev, "xtal"); if (IS_ERR(xtal)) { - ret = PTR_ERR(xtal); - goto dis_runtime_pm; + return PTR_ERR(xtal); } else if (xtal) { rate = clk_get_rate(xtal); - if (rate < 32000 || rate > BIT(22)) { - ret = -EOPNOTSUPP; - goto dis_runtime_pm; - } + if (rate < 32000 || rate > BIT(22)) + return -EOPNOTSUPP; if (rate != 32768) scmp_val = RZN1_RTC_CTL0_SLSB_SCMP; @@ -446,7 +458,7 @@ static int rzn1_rtc_probe(struct platform_device *pdev) ret = readl_poll_timeout(rtc->base + RZN1_RTC_CTL0, val, !(val & RZN1_RTC_CTL0_CEST), 62, 123); if (ret) - goto dis_runtime_pm; + return ret; /* Set desired modes leaving the controller disabled */ writel(RZN1_RTC_CTL0_AMPM | scmp_val, rtc->base + RZN1_RTC_CTL0); @@ -469,14 +481,12 @@ static int rzn1_rtc_probe(struct platform_device *pdev) ret = devm_request_irq(&pdev->dev, irq, rzn1_rtc_alarm_irq, 0, "RZN1 RTC Alarm", rtc); if (ret) { dev_err(&pdev->dev, "RTC alarm interrupt not available\n"); - goto dis_runtime_pm; + return ret; } irq = platform_get_irq_byname_optional(pdev, "pps"); - if (irq == -EPROBE_DEFER) { - ret = irq; - goto dis_runtime_pm; - } + if (irq == -EPROBE_DEFER) + return irq; if (irq >= 0) ret = devm_request_irq(&pdev->dev, irq, rzn1_rtc_1s_irq, 0, "RZN1 RTC 1s", rtc); @@ -486,26 +496,7 @@ static int rzn1_rtc_probe(struct platform_device *pdev) dev_warn(&pdev->dev, "RTC pps interrupt not available. Alarm has only minute accuracy\n"); } - ret = devm_rtc_register_device(rtc->rtcdev); - if (ret) - goto dis_runtime_pm; - - return 0; - -dis_runtime_pm: - pm_runtime_put_sync(&pdev->dev); - - return ret; -} - -static void rzn1_rtc_remove(struct platform_device *pdev) -{ - struct rzn1_rtc *rtc = platform_get_drvdata(pdev); - - /* Disable all interrupts */ - writel(0, rtc->base + RZN1_RTC_CTL1); - - pm_runtime_put_sync(&pdev->dev); + return devm_rtc_register_device(rtc->rtcdev); } static const struct of_device_id rzn1_rtc_of_match[] = { @@ -516,7 +507,6 @@ MODULE_DEVICE_TABLE(of, rzn1_rtc_of_match); static struct platform_driver rzn1_rtc_driver = { .probe = rzn1_rtc_probe, - .remove = rzn1_rtc_remove, .driver = { .name = "rzn1-rtc", .of_match_table = rzn1_rtc_of_match, From afb8972b9e36cd519bc30fb63081fe9ae96adec2 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 21 Aug 2026 22:10:26 +0100 Subject: [PATCH 0830/1328] rtc: rzn1: Dynamically calculate synchronization delay based on clock rate Replace the hardcoded hardware synchronization delays with a calculated time window derived from the operating sub-clock frequency. The driver currently hardcodes microsecond ranges assuming a fixed sub-clock frequency of 32.768 kHz. Newer SoC variants, such as the RZ/T2H, drive this hardware block using a much faster clock rate (~195.3 kHz). Hardcoding these wait windows forces faster blocks to over-sleep, introducing unnecessary delays during clock initialization and register configuration. Calculate the duration of the required clock cycles in microseconds based on the runtime clock rate, and store this value in the driver private structure to adjust the usleep_range() and readl_poll_timeout() boundaries dynamically. Signed-off-by: Lad Prabhakar Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Link: https://patch.msgid.link/20260821211032.13554-12-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rzn1.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/rtc/rtc-rzn1.c b/drivers/rtc/rtc-rzn1.c index f90785827945..4e62f7bc2e34 100644 --- a/drivers/rtc/rtc-rzn1.c +++ b/drivers/rtc/rtc-rzn1.c @@ -70,6 +70,7 @@ struct rzn1_rtc { */ spinlock_t ctl1_access_lock; struct rtc_time tm_alarm; + unsigned long sync_time; }; static void rzn1_rtc_get_time_snapshot(struct rzn1_rtc *rtc, struct rtc_time *tm) @@ -120,8 +121,8 @@ static int rzn1_rtc_set_time(struct device *dev, struct rtc_time *tm) /* Hold the counter if it was counting up */ writel(RZN1_RTC_CTL2_WAIT, rtc->base + RZN1_RTC_CTL2); - /* Wait for the counter to stop: two 32k clock cycles */ - usleep_range(61, 100); + /* Wait 2-4 RTC_PCLK clock cycles for the counter to stop */ + usleep_range(rtc->sync_time, rtc->sync_time * 2); ret = readl_poll_timeout(rtc->base + RZN1_RTC_CTL2, val, val & RZN1_RTC_CTL2_WST, 0, 100); if (ret) @@ -398,10 +399,10 @@ static void rzn1_rtc_disable_hardware(void *data) static int rzn1_rtc_probe(struct platform_device *pdev) { + unsigned long rate = 32768; struct rzn1_rtc *rtc; u32 val, scmp_val = 0; struct clk *xtal; - unsigned long rate; int irq, ret; rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL); @@ -451,12 +452,16 @@ static int rzn1_rtc_probe(struct platform_device *pdev) scmp_val = RZN1_RTC_CTL0_SLSB_SCMP; } + /* Calculate the duration of two RTC_PCLK clock cycles */ + rtc->sync_time = DIV_ROUND_UP(2 * USEC_PER_SEC, rate); + /* Disable controller during SUBU/SCMP setup */ val = readl(rtc->base + RZN1_RTC_CTL0) & ~RZN1_RTC_CTL0_CE; writel(val, rtc->base + RZN1_RTC_CTL0); - /* Wait 2-4 32k clock cycles for the disabled controller */ + /* Wait 2-4 RTC_PCLK clock cycles for the disabled controller to stop */ ret = readl_poll_timeout(rtc->base + RZN1_RTC_CTL0, val, - !(val & RZN1_RTC_CTL0_CEST), 62, 123); + !(val & RZN1_RTC_CTL0_CEST), rtc->sync_time, + rtc->sync_time * 2); if (ret) return ret; From b587001387c16b3a75b8ed9399feee157ad3d3b4 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 21 Aug 2026 22:10:27 +0100 Subject: [PATCH 0831/1328] rtc: rzn1: Use temporary variable for struct device Use a temporary variable for the struct device pointers to avoid dereferencing. Signed-off-by: Lad Prabhakar Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Link: https://patch.msgid.link/20260821211032.13554-13-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rzn1.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/drivers/rtc/rtc-rzn1.c b/drivers/rtc/rtc-rzn1.c index 4e62f7bc2e34..7598efc31e09 100644 --- a/drivers/rtc/rtc-rzn1.c +++ b/drivers/rtc/rtc-rzn1.c @@ -399,13 +399,14 @@ static void rzn1_rtc_disable_hardware(void *data) static int rzn1_rtc_probe(struct platform_device *pdev) { + struct device *dev = &pdev->dev; unsigned long rate = 32768; struct rzn1_rtc *rtc; u32 val, scmp_val = 0; struct clk *xtal; int irq, ret; - rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL); + rtc = devm_kzalloc(dev, sizeof(*rtc), GFP_KERNEL); if (!rtc) return -ENOMEM; @@ -413,13 +414,13 @@ static int rzn1_rtc_probe(struct platform_device *pdev) rtc->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(rtc->base)) - return dev_err_probe(&pdev->dev, PTR_ERR(rtc->base), "Missing reg\n"); + return dev_err_probe(dev, PTR_ERR(rtc->base), "Missing reg\n"); irq = platform_get_irq_byname(pdev, "alarm"); if (irq < 0) return irq; - rtc->rtcdev = devm_rtc_allocate_device(&pdev->dev); + rtc->rtcdev = devm_rtc_allocate_device(dev); if (IS_ERR(rtc->rtcdev)) return PTR_ERR(rtc->rtcdev); @@ -427,19 +428,19 @@ static int rzn1_rtc_probe(struct platform_device *pdev) rtc->rtcdev->range_max = RTC_TIMESTAMP_END_2099; rtc->rtcdev->alarm_offset_max = 7 * 86400; - ret = devm_pm_runtime_enable(&pdev->dev); + ret = devm_pm_runtime_enable(dev); if (ret < 0) return ret; - ret = pm_runtime_resume_and_get(&pdev->dev); + ret = pm_runtime_resume_and_get(dev); if (ret < 0) return ret; - ret = devm_add_action_or_reset(&pdev->dev, rzn1_rtc_disable_hardware, &pdev->dev); + ret = devm_add_action_or_reset(dev, rzn1_rtc_disable_hardware, dev); if (ret) return ret; /* Only switch to scmp if we have an xtal clock with a valid rate and != 32768 */ - xtal = devm_clk_get_optional(&pdev->dev, "xtal"); + xtal = devm_clk_get_optional(dev, "xtal"); if (IS_ERR(xtal)) { return PTR_ERR(xtal); } else if (xtal) { @@ -483,9 +484,9 @@ static int rzn1_rtc_probe(struct platform_device *pdev) spin_lock_init(&rtc->ctl1_access_lock); - ret = devm_request_irq(&pdev->dev, irq, rzn1_rtc_alarm_irq, 0, "RZN1 RTC Alarm", rtc); + ret = devm_request_irq(dev, irq, rzn1_rtc_alarm_irq, 0, "RZN1 RTC Alarm", rtc); if (ret) { - dev_err(&pdev->dev, "RTC alarm interrupt not available\n"); + dev_err(dev, "RTC alarm interrupt not available\n"); return ret; } @@ -493,12 +494,12 @@ static int rzn1_rtc_probe(struct platform_device *pdev) if (irq == -EPROBE_DEFER) return irq; if (irq >= 0) - ret = devm_request_irq(&pdev->dev, irq, rzn1_rtc_1s_irq, 0, "RZN1 RTC 1s", rtc); + ret = devm_request_irq(dev, irq, rzn1_rtc_1s_irq, 0, "RZN1 RTC 1s", rtc); if (irq < 0 || ret) { set_bit(RTC_FEATURE_ALARM_RES_MINUTE, rtc->rtcdev->features); clear_bit(RTC_FEATURE_UPDATE_INTERRUPT, rtc->rtcdev->features); - dev_warn(&pdev->dev, "RTC pps interrupt not available. Alarm has only minute accuracy\n"); + dev_warn(dev, "RTC pps interrupt not available. Alarm has only minute accuracy\n"); } return devm_rtc_register_device(rtc->rtcdev); From 0e1ae8c654f05d813457bee308f864acea7b5cc1 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 21 Aug 2026 22:10:28 +0100 Subject: [PATCH 0832/1328] rtc: rzn1: Consistently use dev_err_probe() Use dev_err_probe() in the IRQ request error path to make error handling consistent with the rest of rzn1_rtc_probe(). Signed-off-by: Lad Prabhakar Reviewed-by: Geert Uytterhoeven Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Link: https://patch.msgid.link/20260821211032.13554-14-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rzn1.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/rtc/rtc-rzn1.c b/drivers/rtc/rtc-rzn1.c index 7598efc31e09..9fc93afee1e5 100644 --- a/drivers/rtc/rtc-rzn1.c +++ b/drivers/rtc/rtc-rzn1.c @@ -485,10 +485,8 @@ static int rzn1_rtc_probe(struct platform_device *pdev) spin_lock_init(&rtc->ctl1_access_lock); ret = devm_request_irq(dev, irq, rzn1_rtc_alarm_irq, 0, "RZN1 RTC Alarm", rtc); - if (ret) { - dev_err(dev, "RTC alarm interrupt not available\n"); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "RTC alarm interrupt not available\n"); irq = platform_get_irq_byname_optional(pdev, "pps"); if (irq == -EPROBE_DEFER) From 5ae93269d3e51db730ca3ee96f8833e4d3befd95 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 21 Aug 2026 22:10:29 +0100 Subject: [PATCH 0833/1328] rtc: rzn1: use FIELD_PREP/FIELD_GET and GENMASK for register access Replace open-coded shift and mask operations with the bitfield API. Note that the weekday field is changed from an explicit 0x0f mask to an 8-bit field definition, matching the hardware manual. This does not change behaviour, as valid weekday values cannot exceed 7. Signed-off-by: Lad Prabhakar Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Link: https://patch.msgid.link/20260821211032.13554-15-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rzn1.c | 50 +++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/drivers/rtc/rtc-rzn1.c b/drivers/rtc/rtc-rzn1.c index 9fc93afee1e5..33c7ef5c1eba 100644 --- a/drivers/rtc/rtc-rzn1.c +++ b/drivers/rtc/rtc-rzn1.c @@ -12,6 +12,8 @@ */ #include +#include +#include #include #include #include @@ -39,14 +41,18 @@ #define RZN1_RTC_CTL2_STOPPED (RZN1_RTC_CTL2_WAIT | RZN1_RTC_CTL2_WST) #define RZN1_RTC_TIME 0x30 -#define RZN1_RTC_TIME_MIN_SHIFT 8 -#define RZN1_RTC_TIME_HOUR_SHIFT 16 +#define RZN1_RTC_TIME_SEC GENMASK(7, 0) +#define RZN1_RTC_TIME_MIN GENMASK(15, 8) +#define RZN1_RTC_TIME_HOUR GENMASK(23, 16) + #define RZN1_RTC_CAL 0x34 -#define RZN1_RTC_CAL_DAY_SHIFT 8 -#define RZN1_RTC_CAL_MON_SHIFT 16 -#define RZN1_RTC_CAL_YEAR_SHIFT 24 +#define RZN1_RTC_CAL_WDAY GENMASK(7, 0) +#define RZN1_RTC_CAL_DAY GENMASK(15, 8) +#define RZN1_RTC_CAL_MON GENMASK(23, 16) +#define RZN1_RTC_CAL_YEAR GENMASK(31, 24) #define RZN1_RTC_SUBU 0x38 +#define RZN1_RTC_SUBU_RTCA0FX GENMASK(5, 0) #define RZN1_RTC_SUBU_DEV BIT(7) #define RZN1_RTC_SUBU_DECR BIT(6) @@ -78,15 +84,15 @@ static void rzn1_rtc_get_time_snapshot(struct rzn1_rtc *rtc, struct rtc_time *tm u32 val; val = readl(rtc->base + RZN1_RTC_TIMEC); - tm->tm_sec = bcd2bin(val); - tm->tm_min = bcd2bin(val >> RZN1_RTC_TIME_MIN_SHIFT); - tm->tm_hour = bcd2bin(val >> RZN1_RTC_TIME_HOUR_SHIFT); + tm->tm_sec = bcd2bin(FIELD_GET(RZN1_RTC_TIME_SEC, val)); + tm->tm_min = bcd2bin(FIELD_GET(RZN1_RTC_TIME_MIN, val)); + tm->tm_hour = bcd2bin(FIELD_GET(RZN1_RTC_TIME_HOUR, val)); val = readl(rtc->base + RZN1_RTC_CALC); - tm->tm_wday = val & 0x0f; - tm->tm_mday = bcd2bin(val >> RZN1_RTC_CAL_DAY_SHIFT); - tm->tm_mon = bcd2bin(val >> RZN1_RTC_CAL_MON_SHIFT) - 1; - tm->tm_year = bcd2bin(val >> RZN1_RTC_CAL_YEAR_SHIFT) + 100; + tm->tm_wday = FIELD_GET(RZN1_RTC_CAL_WDAY, val); + tm->tm_mday = bcd2bin(FIELD_GET(RZN1_RTC_CAL_DAY, val)); + tm->tm_mon = bcd2bin(FIELD_GET(RZN1_RTC_CAL_MON, val)) - 1; + tm->tm_year = bcd2bin(FIELD_GET(RZN1_RTC_CAL_YEAR, val)) + 100; } static int rzn1_rtc_read_time(struct device *dev, struct rtc_time *tm) @@ -129,15 +135,15 @@ static int rzn1_rtc_set_time(struct device *dev, struct rtc_time *tm) return ret; } - val = bin2bcd(tm->tm_sec); - val |= bin2bcd(tm->tm_min) << RZN1_RTC_TIME_MIN_SHIFT; - val |= bin2bcd(tm->tm_hour) << RZN1_RTC_TIME_HOUR_SHIFT; + val = FIELD_PREP(RZN1_RTC_TIME_SEC, bin2bcd(tm->tm_sec)) | + FIELD_PREP(RZN1_RTC_TIME_MIN, bin2bcd(tm->tm_min)) | + FIELD_PREP(RZN1_RTC_TIME_HOUR, bin2bcd(tm->tm_hour)); writel(val, rtc->base + RZN1_RTC_TIME); - val = tm->tm_wday; - val |= bin2bcd(tm->tm_mday) << RZN1_RTC_CAL_DAY_SHIFT; - val |= bin2bcd(tm->tm_mon + 1) << RZN1_RTC_CAL_MON_SHIFT; - val |= bin2bcd(tm->tm_year - 100) << RZN1_RTC_CAL_YEAR_SHIFT; + val = FIELD_PREP(RZN1_RTC_CAL_WDAY, tm->tm_wday) | + FIELD_PREP(RZN1_RTC_CAL_DAY, bin2bcd(tm->tm_mday)) | + FIELD_PREP(RZN1_RTC_CAL_MON, bin2bcd(tm->tm_mon + 1)) | + FIELD_PREP(RZN1_RTC_CAL_YEAR, bin2bcd(tm->tm_year - 100)); writel(val, rtc->base + RZN1_RTC_CAL); writel(0, rtc->base + RZN1_RTC_CTL2); @@ -308,12 +314,12 @@ static int rzn1_rtc_read_offset(struct device *dev, long *offset) val = readl(rtc->base + RZN1_RTC_SUBU); ppb_per_step = val & RZN1_RTC_SUBU_DEV ? 1017 : 3051; subtract = val & RZN1_RTC_SUBU_DECR; - val &= 0x3F; + val = FIELD_GET(RZN1_RTC_SUBU_RTCA0FX, val); if (!val) *offset = 0; else if (subtract) - *offset = -(((~val) & 0x3F) + 1) * ppb_per_step; + *offset = -(((~val) & RZN1_RTC_SUBU_RTCA0FX) + 1) * ppb_per_step; else *offset = (val - 1) * ppb_per_step; @@ -355,7 +361,7 @@ static int rzn1_rtc_set_offset(struct device *dev, long offset) subu |= steps + 1; } else { subu |= RZN1_RTC_SUBU_DECR; - subu |= (~(-steps - 1)) & 0x3F; + subu |= (~(-steps - 1)) & RZN1_RTC_SUBU_RTCA0FX; } ret = readl_poll_timeout(rtc->base + RZN1_RTC_CTL2, ctl2, From 38a3a3b09958d9f2d2a2c72b90a0437cbe8618bf Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 21 Aug 2026 22:10:30 +0100 Subject: [PATCH 0834/1328] rtc: rzn1: Add OF match data to gate SUBU register access The RZ/N1 RTC driver selects SCMP mode only when an optional xtal clock is provided at a valid rate other than 32768 Hz. Without an xtal clock, or when it runs at 32768 Hz, the driver uses SUBU mode. However, the RTCA0SUBU register used by SUBU mode is not present on all SoCs that integrate a similar variant of the RTC block. Allowing SUBU mode on those variants would expose RTC offset operations that access a non-existent register. Add OF match data to describe whether the RTC supports the SUBU register. Reject probe with -EOPNOTSUPP when SUBU mode would be selected on a variant without SUBU support. Signed-off-by: Lad Prabhakar Suggested-by: Wolfram Sang Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Link: https://patch.msgid.link/20260821211032.13554-16-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rzn1.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/drivers/rtc/rtc-rzn1.c b/drivers/rtc/rtc-rzn1.c index 33c7ef5c1eba..8c3dbbc34717 100644 --- a/drivers/rtc/rtc-rzn1.c +++ b/drivers/rtc/rtc-rzn1.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -66,6 +67,10 @@ #define RZN1_RTC_TIMEC 0x68 #define RZN1_RTC_CALC 0x6c +struct rzn1_rtc_data { + bool has_subu; +}; + struct rzn1_rtc { struct rtc_device *rtcdev; void __iomem *base; @@ -405,6 +410,7 @@ static void rzn1_rtc_disable_hardware(void *data) static int rzn1_rtc_probe(struct platform_device *pdev) { + const struct rzn1_rtc_data *data; struct device *dev = &pdev->dev; unsigned long rate = 32768; struct rzn1_rtc *rtc; @@ -412,6 +418,10 @@ static int rzn1_rtc_probe(struct platform_device *pdev) struct clk *xtal; int irq, ret; + data = of_device_get_match_data(dev); + if (!data) + return -ENODEV; + rtc = devm_kzalloc(dev, sizeof(*rtc), GFP_KERNEL); if (!rtc) return -ENOMEM; @@ -455,8 +465,12 @@ static int rzn1_rtc_probe(struct platform_device *pdev) if (rate < 32000 || rate > BIT(22)) return -EOPNOTSUPP; - if (rate != 32768) + if (rate != 32768 || !data->has_subu) scmp_val = RZN1_RTC_CTL0_SLSB_SCMP; + } else if (!data->has_subu) { + /* xtal is NULL here */ + return dev_err_probe(dev, -EOPNOTSUPP, + "No valid XTAL provided and SUBU mode not supported\n"); } /* Calculate the duration of two RTC_PCLK clock cycles */ @@ -509,8 +523,12 @@ static int rzn1_rtc_probe(struct platform_device *pdev) return devm_rtc_register_device(rtc->rtcdev); } +static const struct rzn1_rtc_data rzn1_rtc_rzn1_data = { + .has_subu = true, +}; + static const struct of_device_id rzn1_rtc_of_match[] = { - { .compatible = "renesas,rzn1-rtc" }, + { .compatible = "renesas,rzn1-rtc", .data = &rzn1_rtc_rzn1_data }, {}, }; MODULE_DEVICE_TABLE(of, rzn1_rtc_of_match); From 8af6331c6e7aaec3453b8a158bedb44b271314d7 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 21 Aug 2026 22:10:31 +0100 Subject: [PATCH 0835/1328] rtc: rzn1: Drop trailing comma from OF match table sentinel Drop the trailing comma from the final empty entry in the RZN1 RTC OF match table and mark it explicitly as the sentinel. Signed-off-by: Lad Prabhakar Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Link: https://patch.msgid.link/20260821211032.13554-17-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rzn1.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/rtc/rtc-rzn1.c b/drivers/rtc/rtc-rzn1.c index 8c3dbbc34717..906d03c65df3 100644 --- a/drivers/rtc/rtc-rzn1.c +++ b/drivers/rtc/rtc-rzn1.c @@ -529,7 +529,7 @@ static const struct rzn1_rtc_data rzn1_rtc_rzn1_data = { static const struct of_device_id rzn1_rtc_of_match[] = { { .compatible = "renesas,rzn1-rtc", .data = &rzn1_rtc_rzn1_data }, - {}, + { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, rzn1_rtc_of_match); From b418fa370ea69fc5824e70b63d9f4c93249762e7 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 21 Aug 2026 22:10:32 +0100 Subject: [PATCH 0836/1328] rtc: rzn1: Add support for Renesas RZ/T2H and RZ/N2H SoCs Add support for the RTC block found on the Renesas RZ/T2H (R9A09G077) and RZ/N2H (R9A09G087) SoCs. These SoCs integrate a closely related variant of the RZ/N1 RTC IP, but do not implement the RTCA0SUBU register. Use variant-specific match data to disable SUBU support for these SoCs. Signed-off-by: Lad Prabhakar Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Link: https://patch.msgid.link/20260821211032.13554-18-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rzn1.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/rtc/rtc-rzn1.c b/drivers/rtc/rtc-rzn1.c index 906d03c65df3..1e1676a7f3cc 100644 --- a/drivers/rtc/rtc-rzn1.c +++ b/drivers/rtc/rtc-rzn1.c @@ -523,11 +523,16 @@ static int rzn1_rtc_probe(struct platform_device *pdev) return devm_rtc_register_device(rtc->rtcdev); } +static const struct rzn1_rtc_data rzn1_rtc_rzt2h_data = { + .has_subu = false, +}; + static const struct rzn1_rtc_data rzn1_rtc_rzn1_data = { .has_subu = true, }; static const struct of_device_id rzn1_rtc_of_match[] = { + { .compatible = "renesas,r9a09g077-rtc", .data = &rzn1_rtc_rzt2h_data }, { .compatible = "renesas,rzn1-rtc", .data = &rzn1_rtc_rzn1_data }, { /* sentinel */ } }; From a5c7d35e2fd3e24c411816c91f8f6cc78e652e0c Mon Sep 17 00:00:00 2001 From: Nikolai Grlica Date: Mon, 10 Aug 2026 15:03:35 +0000 Subject: [PATCH 0837/1328] rust: kernel: list: fix incorrect pop_back example comment The example uses pop_back(), but the accompanying comment says pop_front(). Update the comment to match the example. Signed-off-by: Nikolai Grlica Cc: stable@vger.kernel.org Fixes: bf87a41b85d6 ("rust: list: Add an example for `ListLinksSelfPtr` usage") Link: https://patch.msgid.link/20260810150322.61809-1-nikolai@nikolaigrlica.dev Signed-off-by: Miguel Ojeda --- rust/kernel/list.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/kernel/list.rs b/rust/kernel/list.rs index 406e3a028c55..0f367264ee2e 100644 --- a/rust/kernel/list.rs +++ b/rust/kernel/list.rs @@ -249,7 +249,7 @@ /// assert_eq!(list.iter().count(), 3); /// } /// -/// // Pop the items from the list using `pop_front()` and verify the content. +/// // Pop the items from the list using `pop_back()` and verify the content. /// { /// assert_eq!(list.pop_back().ok_or(EINVAL)?.value.foo(), ("a", 15)); /// assert_eq!(list.pop_back().ok_or(EINVAL)?.value.foo(), ("a", 32)); From 993f235c4af49d0e912cc28abe6d733531600237 Mon Sep 17 00:00:00 2001 From: Miguel Ojeda Date: Wed, 5 Aug 2026 16:45:24 +0200 Subject: [PATCH 0838/1328] objtool/rust: add one more `noreturn` Rust function When the pointer formatting series [1] is applied and KUnit tests are enabled, `objtool` would report an error with any of our supported Rust versions. For instance, with Rust 1.97.1: rust/kernel.o: error: objtool: _R..._4core3fmt7Pointer3fmtB7_() falls through to next function _R..._4core7convert5AsRefNtB5_4BStrE6as_ref() Or, with Rust 1.85.0: rust/kernel.o: error: objtool: _R..._4core3fmt7Pointer3fmtB7_() falls through to next function _R..._4core3ffi5c_str4CStrENtNtBS_3fmt7Display3fmtB7_() This happens due to calls to the `noreturn` symbol: core::str::slice_error_fail Thus add the mangled one to the list so that `objtool` knows it is actually `noreturn`. See commit 56d680dd23c3 ("objtool/rust: list `noreturn` Rust functions") for more details. [ While the series [1] mentioned above was not applied in a way that triggered this, Gary found another case [2]: I am seeing this function causing issue on my 1.85.1 -Os build, although the error message is different this time: rust/kernel.o: warning: objtool: _R..._5range9RangeFromjEE5indexCsldbe0TGTmNx_6kernel+0x1d: stack state mismatch: cfa1=4+8 cfa2=4+16 Disassembly shows that the function call before the warning location is a call to _4core3str16slice_error_fail, and objtool thought it can return (which will end up with a unbalanced stack if it does return). Thus apply it. - Miguel ] Cc: stable@vger.kernel.org # Needed in 6.12.y and later (Rust is pinned in older LTSs). Cc: Josh Poimboeuf Cc: Peter Zijlstra Reported-by: Alice Ryhl Link: https://lore.kernel.org/rust-for-linux/anLxXvCKBcAVf91_@google.com/ Link: https://lore.kernel.org/rust-for-linux/20260706-hashedptr-v13-0-377a07f2f78d@kylinos.cn/ [1] Link: https://lore.kernel.org/rust-for-linux/DKS7U9UHY6XR.2JC0EBIPCI06Z@garyguo.net/ [2] Link: https://patch.msgid.link/20260805144524.233362-1-ojeda@kernel.org Signed-off-by: Miguel Ojeda --- tools/objtool/check.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 87db9f4ed9e2..3ab5b9f1c6a4 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -195,6 +195,7 @@ static bool is_rust_noreturn(const struct symbol *func) return str_ends_with(func->name, "_4core3num20from_str_radix_panic") || str_ends_with(func->name, "_4core3num22from_ascii_radix_panic") || str_ends_with(func->name, "_4core3num28from_ascii_bytes_radix_panic") || + str_ends_with(func->name, "_4core3str16slice_error_fail") || str_ends_with(func->name, "_4core5sliceSp15copy_from_slice17len_mismatch_fail") || str_ends_with(func->name, "_4core6option13expect_failed") || str_ends_with(func->name, "_4core6option13unwrap_failed") || From dea1bf38143f8505747c19b953ef12fb809d5d77 Mon Sep 17 00:00:00 2001 From: FUJITA Tomonori Date: Sun, 9 Aug 2026 22:48:58 +0900 Subject: [PATCH 0839/1328] rust: jump_label: skip arch-specific asm in `testlib` builds Running `make rusttest` with `ARCH=` set to an architecture other than the host's may fail in the future, e.g. `ARCH=arm64` on an x86_64 host: error: alignment must be a power of 2 --> rust/kernel/jump_label.rs:51:13 | 51 | include!(concat!(env!("OBJTREE"), "/rust/kernel/generated_arch_static_branch_asm.rs")); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: instantiated into assembly here --> :3:10 | 3 | .align 3 | ^ The reason is that `rusttest` builds the kernel crate as a host library: it passes the `CONFIG_*` cfgs of the configured architecture, but not `--target`, so code generation happens for the host. `arch_static_branch!` then selects the arch-specific inline asm arm based on CONFIG_*, and the host assembler rejects it. This does not happen with the current master because `arch_static_branch!` has no user inside the kernel crate itself yet, but fix it now to avoid surprises later. Signed-off-by: FUJITA Tomonori Link: https://patch.msgid.link/20260809134858.1219036-1-tomo@flapping.org [ Reworded slightly to clarify it "may fail in the future". - Miguel ] Signed-off-by: Miguel Ojeda --- rust/kernel/jump_label.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/rust/kernel/jump_label.rs b/rust/kernel/jump_label.rs index 4e974c768dbd..f54cedcb6fd5 100644 --- a/rust/kernel/jump_label.rs +++ b/rust/kernel/jump_label.rs @@ -44,6 +44,7 @@ macro_rules! static_branch_unlikely { #[macro_export] #[doc(hidden)] +#[cfg(not(testlib))] #[cfg(CONFIG_JUMP_LABEL)] macro_rules! arch_static_branch { ($key:path, $keytyp:ty, $field:ident, $branch:expr) => {'my_label: { @@ -61,6 +62,17 @@ macro_rules! arch_static_branch { }}; } +#[macro_export] +#[doc(hidden)] +#[cfg(testlib)] +#[cfg(CONFIG_JUMP_LABEL)] +macro_rules! arch_static_branch { + ($key:path, $keytyp:ty, $field:ident, $branch:expr) => { + // The asm falls through until patched, which never happens on the host. + false + }; +} + #[cfg(CONFIG_JUMP_LABEL)] pub use arch_static_branch; From 29b03d1de967a177176d12811d970cac7882afcf Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Thu, 20 Aug 2026 14:57:32 +0100 Subject: [PATCH 0840/1328] rust: cfi: disable function merging if CFI is enabled In Rust doc tests, there is a dummy `__module_firmware_test_init` function generated by the example in `module_firmware!`'s documentation, which just returns zero. Many other documentation examples generate functions that produce zero. LKP test robot reports [1] a `Flags::zeroed` instance; my local reproduction has a `Bounded::new::<0>`. LLVM's MergeFunctionsPass incorrectly merges functions with different KCFI types, causing `__module_firmware_test_init` to be merged into one of the zero-returning functions. As module init is invoked via an indirect function call, KCFI is checked and this produces a KCFI failure. I've reported this bug to upstream LLVM [2]; in the meantime, disable function merging if CFI is enabled. No separate treatment is needed for CONFIG_RUST_INLINE_HELPERS, as Clang does not enable function merging by default. [ LLVM already has a pending PR: https://github.com/llvm/llvm-project/pull/217665 which solves the issue. In addition, I asked upstream Rust if the unstable `-Zmerge-functions=disabled` flag will remain around: https://rust-lang.zulipchat.com/#narrow/channel/425075-rust-for-linux/topic/.60-Zmerge-functions.3Ddisabled.60/ and it does indeed look like that will be the case. - Miguel ] Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-lkp/202608201017.100a4511-lkp@intel.com [1] Link: https://github.com/llvm/llvm-project/issues/217629 [2] Signed-off-by: Gary Guo Cc: stable@vger.kernel.org Fixes: ca627e636551 ("rust: cfi: add support for CFI_CLANG with Rust") Reviewed-by: Sami Tolvanen Link: https://patch.msgid.link/20260820135733.37121-1-gary@kernel.org [ Fixed typos as discussed. Reworded slightly for other typos. - Miguel ] Signed-off-by: Miguel Ojeda --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 130926fa592e..c2344755a1bb 100644 --- a/Makefile +++ b/Makefile @@ -1118,7 +1118,8 @@ endif ifdef CONFIG_RUST # Always pass -Zsanitizer-cfi-normalize-integers as CONFIG_RUST selects # CONFIG_CFI_ICALL_NORMALIZE_INTEGERS. - RUSTC_FLAGS_CFI := -Zsanitizer=kcfi -Zsanitizer-cfi-normalize-integers + # Disable function merging as LLVM incorrectly merges functions with different KCFI types. + RUSTC_FLAGS_CFI := -Zsanitizer=kcfi -Zsanitizer-cfi-normalize-integers -Zmerge-functions=disabled KBUILD_RUSTFLAGS += $(RUSTC_FLAGS_CFI) export RUSTC_FLAGS_CFI endif From 0bff7711c19ba05ff3b686554c5a2d503c3e9797 Mon Sep 17 00:00:00 2001 From: Miguel Ojeda Date: Sun, 16 Aug 2026 15:32:32 +0200 Subject: [PATCH 0841/1328] kbuild: rust: preserve unreachable traps with inline helpers When `CONFIG_RUST_INLINE_HELPERS` is enabled, it is possible to hit `objtool` warnings like: vmlinux.o: warning: objtool: _R..._4cmdq12CommandToGsp4init() falls through to next function _R..._4core5array4iter8IntoIterRShKj3_EEEBa_() `rustc` normally emits traps for unreachable paths. However, under `CONFIG_RUST_INLINE_HELPERS=y`, `rustc` emits LLVM bitcode and Clang performs final code generation after the helper bitcode is linked, but Clang does not trap unreachable IR by default. In turn, this means `objtool` follows compiler-generated impossible Rust `enum` paths through alignment padding into the next function, resulting in fallthrough warnings. Thus pass the LLVM `trap-unreachable` option to the final Clang invocation and suppress traps immediately after `noreturn` calls, which `objtool` already recognizes as dead ends. The combination of both flags makes it match `rustc`'s behavior. Rust 1.85.0 (the minimum supported one) supports LLVM >= 18, and both flags are available in LLVM 18. Assisted-by: LLM Cc: Gary Guo Cc: Boqun Feng Cc: Alice Ryhl Cc: Matthew Maurer Cc: Josh Poimboeuf Cc: Peter Zijlstra Cc: stable@vger.kernel.org Fixes: 3a2486cc1da5 ("kbuild: rust: provide an option to inline C helpers into Rust") Acked-by: Gary Guo Link: https://patch.msgid.link/20260816133233.197500-1-ojeda@kernel.org Signed-off-by: Miguel Ojeda --- Makefile | 10 ++++++++++ rust/Makefile | 3 ++- scripts/Makefile.build | 3 ++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index c2344755a1bb..960f296e4a4d 100644 --- a/Makefile +++ b/Makefile @@ -1083,6 +1083,16 @@ endif export CC_FLAGS_SCS endif +ifdef CONFIG_RUST_INLINE_HELPERS +# `rustc` normally emits traps for unreachable paths during code generation. +# With inline helpers, Clang performs code generation from the linked bitcode +# instead, so request the same behavior explicitly. Otherwise `objtool` may +# follow an impossible Rust path into the next function. +CC_FLAGS_RUST_INLINE_HELPERS := -mllvm -trap-unreachable \ + -mllvm -no-trap-after-noreturn +export CC_FLAGS_RUST_INLINE_HELPERS +endif + ifdef CONFIG_LTO_CLANG ifdef CONFIG_LTO_CLANG_FULL CC_FLAGS_LTO := -flto diff --git a/rust/Makefile b/rust/Makefile index fbe0accc51a3..f871d94f6af2 100644 --- a/rust/Makefile +++ b/rust/Makefile @@ -644,7 +644,8 @@ quiet_cmd_rustc_library = $(if $(skip_clippy),RUSTC,$(RUSTC_OR_CLIPPY_QUIET)) L -Zunstable-options \ $(if $(link_helper),;$(LLVM_LINK) --internalize --suppress-warnings $(patsubst %.o,%.bc,$@) \ $(obj)/helpers/helpers$(if $(part-of-module),_module).bc -o $(patsubst %.o,%.m.bc,$@); \ - $(CC) $(CLANG_FLAGS) $(KBUILD_CFLAGS) -Wno-override-module -c $(patsubst %.o,%.m.bc,$@) -o $@ \ + $(CC) $(CLANG_FLAGS) $(KBUILD_CFLAGS) \ + $(CC_FLAGS_RUST_INLINE_HELPERS) -Wno-override-module -c $(patsubst %.o,%.m.bc,$@) -o $@ \ $(cmd_ld_single)) \ $(if $(rustc_objcopy),;$(OBJCOPY) $(rustc_objcopy) $@) \ $(cmd_objtool) diff --git a/scripts/Makefile.build b/scripts/Makefile.build index a48209591dee..0ac326e43fea 100644 --- a/scripts/Makefile.build +++ b/scripts/Makefile.build @@ -347,7 +347,8 @@ quiet_cmd_rustc_o_rs = $(RUSTC_OR_CLIPPY_QUIET) $(quiet_modtag) $@ cmd_rustc_o_rs = $(rust_common_cmd) --emit=$(if $(CONFIG_RUST_INLINE_HELPERS),llvm-bc=$(patsubst %.o,%.bc,$@),obj=$@) $< \ $(if $(CONFIG_RUST_INLINE_HELPERS),;$(LLVM_LINK) --internalize --suppress-warnings $(patsubst %.o,%.bc,$@) \ $(objtree)/rust/helpers/helpers$(if $(part-of-module),_module).bc -o $(patsubst %.o,%.m.bc,$@); \ - $(CC) $(CLANG_FLAGS) $(KBUILD_CFLAGS) -Wno-override-module -c $(patsubst %.o,%.m.bc,$@) -o $@ \ + $(CC) $(CLANG_FLAGS) $(KBUILD_CFLAGS) \ + $(CC_FLAGS_RUST_INLINE_HELPERS) -Wno-override-module -c $(patsubst %.o,%.m.bc,$@) -o $@ \ $(cmd_ld_single)) \ $(cmd_objtool) From 5febf432df1cfa5b25d99b54c32103fafbdd0eb9 Mon Sep 17 00:00:00 2001 From: Miguel Ojeda Date: Sun, 16 Aug 2026 15:32:33 +0200 Subject: [PATCH 0842/1328] kbuild: rust: keep Rust objects out of Clang LTO with inline helpers Under `CONFIG_LTO_CLANG` + `CONFIG_RUST_INLINE_HELPERS`, one may hit `objtool` errors such as: vmlinux.o: error: objtool: _R..._3Gsp4boot+0xd6a: can't find jump dest instruction at .text._R..._3Gsp4boot+0x1dfd The reason is that in such builds, the Clang invocation that compiles the combined Rust plus helpers bitcode emits LLVM bitcode (again) -- the final code generation happens in the linker's LTO step, which the `-mllvm` trap options passed to Clang do not reach. This, in turn, means that unreachable traps are missing, and the impossible paths do not merely fallthrough to the next symbol, but past the end of their own section, since LTO builds place each function in its own section. Thus filter `CC_FLAGS_LTO` out of the Clang invocation, so that it always emits machine code directly, with the traps in place. Assisted-by: LLM Cc: Gary Guo Cc: Boqun Feng Cc: Alice Ryhl Cc: Matthew Maurer Cc: Josh Poimboeuf Cc: Peter Zijlstra Cc: stable@vger.kernel.org Fixes: 3a2486cc1da5 ("kbuild: rust: provide an option to inline C helpers into Rust") Acked-by: Gary Guo Link: https://patch.msgid.link/20260816133233.197500-2-ojeda@kernel.org Signed-off-by: Miguel Ojeda --- rust/Makefile | 2 +- scripts/Makefile.build | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/Makefile b/rust/Makefile index f871d94f6af2..3afaad4a4a3a 100644 --- a/rust/Makefile +++ b/rust/Makefile @@ -644,7 +644,7 @@ quiet_cmd_rustc_library = $(if $(skip_clippy),RUSTC,$(RUSTC_OR_CLIPPY_QUIET)) L -Zunstable-options \ $(if $(link_helper),;$(LLVM_LINK) --internalize --suppress-warnings $(patsubst %.o,%.bc,$@) \ $(obj)/helpers/helpers$(if $(part-of-module),_module).bc -o $(patsubst %.o,%.m.bc,$@); \ - $(CC) $(CLANG_FLAGS) $(KBUILD_CFLAGS) \ + $(CC) $(CLANG_FLAGS) $(filter-out $(CC_FLAGS_LTO),$(KBUILD_CFLAGS)) \ $(CC_FLAGS_RUST_INLINE_HELPERS) -Wno-override-module -c $(patsubst %.o,%.m.bc,$@) -o $@ \ $(cmd_ld_single)) \ $(if $(rustc_objcopy),;$(OBJCOPY) $(rustc_objcopy) $@) \ diff --git a/scripts/Makefile.build b/scripts/Makefile.build index 0ac326e43fea..4349108e75e1 100644 --- a/scripts/Makefile.build +++ b/scripts/Makefile.build @@ -347,7 +347,7 @@ quiet_cmd_rustc_o_rs = $(RUSTC_OR_CLIPPY_QUIET) $(quiet_modtag) $@ cmd_rustc_o_rs = $(rust_common_cmd) --emit=$(if $(CONFIG_RUST_INLINE_HELPERS),llvm-bc=$(patsubst %.o,%.bc,$@),obj=$@) $< \ $(if $(CONFIG_RUST_INLINE_HELPERS),;$(LLVM_LINK) --internalize --suppress-warnings $(patsubst %.o,%.bc,$@) \ $(objtree)/rust/helpers/helpers$(if $(part-of-module),_module).bc -o $(patsubst %.o,%.m.bc,$@); \ - $(CC) $(CLANG_FLAGS) $(KBUILD_CFLAGS) \ + $(CC) $(CLANG_FLAGS) $(filter-out $(CC_FLAGS_LTO),$(KBUILD_CFLAGS)) \ $(CC_FLAGS_RUST_INLINE_HELPERS) -Wno-override-module -c $(patsubst %.o,%.m.bc,$@) -o $@ \ $(cmd_ld_single)) \ $(cmd_objtool) From db005bdaa5ff4ea52095aa213507134dcaff42cc Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 21 Aug 2026 12:09:02 +0200 Subject: [PATCH 0843/1328] rtc: hym8563: Make sure clk_init_data is fully initialized The clk_init_data structure contains several mutually-exclusive members for different methods to specify the possible parents of a clock, prompting drivers to initialize only the members they need. However, not initializing all members may cause subtle issues, which are only exposed when CONFIG_INIT_STACK_ALL_PATTERN or CONFIG_INIT_STACK_NONE is enabled. Make sure all members are fully initialized, to avoid such bugs, and to prevent future breakage when converting drivers to a different method for specifying the parents. Signed-off-by: Geert Uytterhoeven Link: https://patch.msgid.link/657f2b3a871074087aee0b7a70bf527ab0f48da8.1787241693.git.geert+renesas@glider.be Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-hym8563.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/rtc/rtc-hym8563.c b/drivers/rtc/rtc-hym8563.c index 3156aa5f2d9f..1d52cc3def02 100644 --- a/drivers/rtc/rtc-hym8563.c +++ b/drivers/rtc/rtc-hym8563.c @@ -376,8 +376,8 @@ static struct clk *hym8563_clkout_register_clk(struct hym8563 *hym8563) { struct i2c_client *client = hym8563->client; struct device_node *node = client->dev.of_node; + struct clk_init_data init = {}; struct clk *clk; - struct clk_init_data init; int ret; ret = i2c_smbus_write_byte_data(client, HYM8563_CLKOUT, From bdcedfee8b87067dbc907e9e94310c5be4311133 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 21 Aug 2026 12:09:03 +0200 Subject: [PATCH 0844/1328] rtc: m41t80: Make sure clk_init_data is fully initialized The clk_init_data structure contains several mutually-exclusive members for different methods to specify the possible parents of a clock, prompting drivers to initialize only the members they need. However, not initializing all members may cause subtle issues, which are only exposed when CONFIG_INIT_STACK_ALL_PATTERN or CONFIG_INIT_STACK_NONE is enabled. Make sure all members are fully initialized, to avoid such bugs, and to prevent future breakage when converting drivers to a different method for specifying the parents. Signed-off-by: Geert Uytterhoeven Link: https://patch.msgid.link/c5290fb9a28b45d6bcec724d5897f8bcbf96ea50.1787241693.git.geert+renesas@glider.be Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-m41t80.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/rtc/rtc-m41t80.c b/drivers/rtc/rtc-m41t80.c index 3c8c379392c1..ec86ca3f2c4c 100644 --- a/drivers/rtc/rtc-m41t80.c +++ b/drivers/rtc/rtc-m41t80.c @@ -574,8 +574,8 @@ static struct clk *m41t80_sqw_register_clk(struct m41t80_data *m41t80) struct i2c_client *client = m41t80->client; struct device_node *node = client->dev.of_node; struct device_node *fixed_clock; + struct clk_init_data init = {}; struct clk *clk; - struct clk_init_data init; int ret; fixed_clock = of_get_child_by_name(node, "clock"); From 096c7ef4ef6670e64ebfc22f8989cb48f560fe10 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 21 Aug 2026 12:09:04 +0200 Subject: [PATCH 0845/1328] rtc: nct3018y: Make sure clk_init_data is fully initialized The clk_init_data structure contains several mutually-exclusive members for different methods to specify the possible parents of a clock, prompting drivers to initialize only the members they need. However, not initializing all members may cause subtle issues, which are only exposed when CONFIG_INIT_STACK_ALL_PATTERN or CONFIG_INIT_STACK_NONE is enabled. Make sure all members are fully initialized, to avoid such bugs, and to prevent future breakage when converting drivers to a different method for specifying the parents. Signed-off-by: Geert Uytterhoeven Link: https://patch.msgid.link/6dba18b645aeea279a67f4625d5ec48037c76f0c.1787241693.git.geert+renesas@glider.be Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-nct3018y.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/rtc/rtc-nct3018y.c b/drivers/rtc/rtc-nct3018y.c index 700a395fad3a..2f7ad57057a4 100644 --- a/drivers/rtc/rtc-nct3018y.c +++ b/drivers/rtc/rtc-nct3018y.c @@ -459,8 +459,8 @@ static struct clk *nct3018y_clkout_register_clk(struct nct3018y *nct3018y) { struct i2c_client *client = nct3018y->client; struct device_node *node = client->dev.of_node; + struct clk_init_data init = {}; struct clk *clk; - struct clk_init_data init; init.name = "nct3018y-clkout"; init.ops = &nct3018y_clkout_ops; From 5ef3d51a3aaae642f8e5515a2c67c17f6b7cb01c Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 21 Aug 2026 12:09:05 +0200 Subject: [PATCH 0846/1328] rtc: philips: Make sure clk_init_data is fully initialized The clk_init_data structure contains several mutually-exclusive members for different methods to specify the possible parents of a clock, prompting drivers to initialize only the members they need. However, not initializing all members may cause subtle issues, which are only exposed when CONFIG_INIT_STACK_ALL_PATTERN or CONFIG_INIT_STACK_NONE is enabled. Make sure all members are fully initialized, to avoid such bugs, and to prevent future breakage when converting drivers to a different method for specifying the parents. Signed-off-by: Geert Uytterhoeven Link: https://patch.msgid.link/8281eaf605069aac2211d83233e11285b8e8ca84.1787241693.git.geert+renesas@glider.be Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-pcf85063.c | 2 +- drivers/rtc/rtc-pcf8563.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/rtc/rtc-pcf85063.c b/drivers/rtc/rtc-pcf85063.c index 01e209d88f5f..8cb9ffc73f6d 100644 --- a/drivers/rtc/rtc-pcf85063.c +++ b/drivers/rtc/rtc-pcf85063.c @@ -498,7 +498,7 @@ static const struct clk_ops pcf85063_clkout_ops = { static struct clk *pcf85063_clkout_register_clk(struct pcf85063 *pcf85063) { struct clk *clk; - struct clk_init_data init; + struct clk_init_data init = {}; struct device_node *node = pcf85063->rtc->dev.parent->of_node; struct device_node *fixed_clock; diff --git a/drivers/rtc/rtc-pcf8563.c b/drivers/rtc/rtc-pcf8563.c index a90dc940474b..7eebdb058067 100644 --- a/drivers/rtc/rtc-pcf8563.c +++ b/drivers/rtc/rtc-pcf8563.c @@ -425,7 +425,7 @@ static const struct clk_ops pcf8563_clkout_ops = { static struct clk *pcf8563_clkout_register_clk(struct pcf8563 *pcf8563) { struct device_node *node = pcf8563->rtc->dev.parent->of_node; - struct clk_init_data init; + struct clk_init_data init = {}; struct clk *clk; int ret; From a12eac458814f70ed9b0b2e3293b7478c7032eaa Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 21 Aug 2026 12:09:06 +0200 Subject: [PATCH 0847/1328] rtc: microcrystal: Make sure clk_init_data is fully initialized The clk_init_data structure contains several mutually-exclusive members for different methods to specify the possible parents of a clock, prompting drivers to initialize only the members they need. However, not initializing all members may cause subtle issues, which are only exposed when CONFIG_INIT_STACK_ALL_PATTERN or CONFIG_INIT_STACK_NONE is enabled. Make sure all members are fully initialized, to avoid such bugs, and to prevent future breakage when converting drivers to a different method for specifying the parents. Signed-off-by: Geert Uytterhoeven Link: https://patch.msgid.link/6e7b5258ff73e93fe23dd83cf56f806ee60fa7a9.1787241693.git.geert+renesas@glider.be Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rv3028.c | 2 +- drivers/rtc/rtc-rv3032.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/rtc/rtc-rv3028.c b/drivers/rtc/rtc-rv3028.c index d96f6bb68850..d25e33d0835e 100644 --- a/drivers/rtc/rtc-rv3028.c +++ b/drivers/rtc/rtc-rv3028.c @@ -816,7 +816,7 @@ static int rv3028_clkout_register_clk(struct rv3028_data *rv3028, { int ret; struct clk *clk; - struct clk_init_data init; + struct clk_init_data init = {}; struct device_node *node = client->dev.of_node; ret = regmap_update_bits(rv3028->regmap, RV3028_STATUS, diff --git a/drivers/rtc/rtc-rv3032.c b/drivers/rtc/rtc-rv3032.c index 6bafdec637ae..687faaef42b2 100644 --- a/drivers/rtc/rtc-rv3032.c +++ b/drivers/rtc/rtc-rv3032.c @@ -752,7 +752,7 @@ static int rv3032_clkout_register_clk(struct rv3032_data *rv3032, { int ret; struct clk *clk; - struct clk_init_data init; + struct clk_init_data init = {}; struct device_node *node = client->dev.of_node; ret = regmap_update_bits(rv3032->regmap, RV3032_TLSB, RV3032_TLSB_CLKF, 0); From bf2fe566a8cbd3671b816223eec8fadd18ec8d77 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Sat, 25 Jul 2026 20:32:40 +0900 Subject: [PATCH 0848/1328] coccinelle: double_lock: improve performance when no double lock exists The 'balanced' rule collects the locks that are taken and released under the same condition, to prevent them from being reported as a double lock. It runs on every file that contains a lock call. To avoid this, collect the double-lock candidates first, so that 'balanced' runs only when one exists. The report then excludes what 'balanced' found. Every double lock that can be reported is also a candidate, so the same reports are made as before and the output does not change. A report-mode run over every .c file in the tree produces identical output. Signed-off-by: Sang-Heon Jeon Signed-off-by: Julia Lawall --- scripts/coccinelle/locks/double_lock.cocci | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/scripts/coccinelle/locks/double_lock.cocci b/scripts/coccinelle/locks/double_lock.cocci index 619cfc714409..381060849a7b 100644 --- a/scripts/coccinelle/locks/double_lock.cocci +++ b/scripts/coccinelle/locks/double_lock.cocci @@ -38,7 +38,20 @@ write_lock@p1 write_trylock@p1 ) (E1@p,...); -@balanced@ +@r_candidate exists@ +expression x <= locked.E1; +expression locked.E1; +expression E2; +identifier lock; +position locked.p,p1,p2; +@@ + +lock@p1 (E1@p,...); +... when != E1 + when != \(x = E2\|&x\) +lock@p2 (E1,...); + +@balanced depends on r_candidate@ position p1 != locked.p1; position locked.p; identifier lock,unlock; From b32cf68d78a1f2a05797ec2a969b80a9472f45a7 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Sat, 25 Jul 2026 20:32:41 +0900 Subject: [PATCH 0849/1328] coccinelle: misc: minmax: improve performance when no candidate exists The rules that report an opencoded min() or max() search every function body, even when the file contains nothing to find. To avoid this, collect the candidates first and run the search only when one exists. A candidate is any conditional expression whose condition is a comparison. Every opencoded min() or max() is also a candidate, so the same opportunities are reported as before and the output does not change. A report-mode run over every .c file in the tree produces identical output. Signed-off-by: Sang-Heon Jeon Signed-off-by: Julia Lawall --- scripts/coccinelle/misc/minmax.cocci | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/scripts/coccinelle/misc/minmax.cocci b/scripts/coccinelle/misc/minmax.cocci index ca4830ae3042..93c074b9439f 100644 --- a/scripts/coccinelle/misc/minmax.cocci +++ b/scripts/coccinelle/misc/minmax.cocci @@ -17,7 +17,21 @@ virtual org virtual context virtual patch -@rmax depends on !patch@ +@max_candidate@ +expression E1, E2, E3, E4; +binary operator cmp = {>, >=}; +@@ + + (E1 cmp E2 ? E3 : E4) + +@min_candidate@ +expression E1, E2, E3, E4; +binary operator cmp = {<, <=}; +@@ + + (E1 cmp E2 ? E3 : E4) + +@rmax depends on !patch && max_candidate@ identifier func; expression x, y; binary operator cmp = {>, >=}; @@ -51,7 +65,7 @@ func(...) } // Ignore errcode returns. -@errcode@ +@errcode depends on min_candidate@ position p; identifier func; expression x; @@ -65,7 +79,7 @@ func(...) ...> } -@rmin depends on !patch@ +@rmin depends on !patch && min_candidate@ identifier func; expression x, y; binary operator cmp = {<, <=}; @@ -98,7 +112,7 @@ func(...) ...> } -@pmax depends on patch@ +@pmax depends on patch && max_candidate@ identifier func; expression x, y; binary operator cmp = {>=, >}; @@ -131,7 +145,7 @@ func(...) ...> } -@pmin depends on patch@ +@pmin depends on patch && min_candidate@ identifier func; expression x, y; binary operator cmp = {<=, <}; From fd0a63f086b3f2f45c49b1378f6c2ac7a542db19 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sun, 26 Jul 2026 16:01:38 +0200 Subject: [PATCH 0850/1328] coccinelle: misc: minmax: drop unneeded parentheses The outer parentheses don't matter when just matching an returning a line. Eliminating them reduces the complexity of the pattern to match and gives a small performance improvement in the non-patch cases. Signed-off-by: Julia Lawall --- scripts/coccinelle/misc/minmax.cocci | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/coccinelle/misc/minmax.cocci b/scripts/coccinelle/misc/minmax.cocci index 93c074b9439f..905dd2292609 100644 --- a/scripts/coccinelle/misc/minmax.cocci +++ b/scripts/coccinelle/misc/minmax.cocci @@ -22,14 +22,14 @@ expression E1, E2, E3, E4; binary operator cmp = {>, >=}; @@ - (E1 cmp E2 ? E3 : E4) + E1 cmp E2 ? E3 : E4 @min_candidate@ expression E1, E2, E3, E4; binary operator cmp = {<, <=}; @@ - (E1 cmp E2 ? E3 : E4) + E1 cmp E2 ? E3 : E4 @rmax depends on !patch && max_candidate@ identifier func; @@ -41,7 +41,7 @@ position p; func(...) { <... -* ((x) cmp@p (y) ? (x) : (y)) +* (x) cmp@p (y) ? (x) : (y) ...> } @@ -89,7 +89,7 @@ position p != errcode.p; func(...) { <... -* ((x) cmp@p (y) ? (x) : (y)) +* (x) cmp@p (y) ? (x) : (y) ...> } From af8613c863a3160b7fb6fd09cbf5b56e37330d80 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sun, 26 Jul 2026 16:08:56 +0200 Subject: [PATCH 0851/1328] coccinelle: misc: minmax: check for the presence of if cases As done previously for the ternary command, check that a file contains the min or max if pattern before applying the minif and maxif rules. Signed-off-by: Julia Lawall --- scripts/coccinelle/misc/minmax.cocci | 32 ++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/scripts/coccinelle/misc/minmax.cocci b/scripts/coccinelle/misc/minmax.cocci index 905dd2292609..f14e608240b6 100644 --- a/scripts/coccinelle/misc/minmax.cocci +++ b/scripts/coccinelle/misc/minmax.cocci @@ -45,7 +45,19 @@ func(...) ...> } -@rmaxif depends on !patch@ +@maxif_candidate@ +expression x, y; +expression max_val; +binary operator cmp = {>, >=}; +@@ + +if ((x) cmp (y)) { + max_val = (x); +} else { + max_val = (y); +} + +@rmaxif depends on !patch && maxif_candidate@ identifier func; expression x, y; expression max_val; @@ -93,7 +105,19 @@ func(...) ...> } -@rminif depends on !patch@ +@minif_candidate@ +expression x, y; +expression min_val; +binary operator cmp = {<, <=}; +@@ + +if ((x) cmp (y)) { + min_val = (x); +} else { + min_val = (y); +} + +@rminif depends on !patch && minif_candidate@ identifier func; expression x, y; expression min_val; @@ -126,7 +150,7 @@ func(...) ...> } -@pmaxif depends on patch@ +@pmaxif depends on patch && maxif_candidate@ identifier func; expression x, y; expression max_val; @@ -160,7 +184,7 @@ func(...) ...> } -@pminif depends on patch@ +@pminif depends on patch && maxif_candidate@ identifier func; expression x, y; expression min_val; From 4fc2656db0c8026139f1634d557ef66e4b383765 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sun, 26 Jul 2026 17:07:54 +0200 Subject: [PATCH 0852/1328] coccinelle: misc: minmax: avoid unhelpful isomorphisms Avoid isomorphisms that introduce comparisons with 0 that do not occur in code. Signed-off-by: Julia Lawall --- scripts/coccinelle/misc/minmax.cocci | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/scripts/coccinelle/misc/minmax.cocci b/scripts/coccinelle/misc/minmax.cocci index f14e608240b6..b83d4a01e47a 100644 --- a/scripts/coccinelle/misc/minmax.cocci +++ b/scripts/coccinelle/misc/minmax.cocci @@ -17,21 +17,21 @@ virtual org virtual context virtual patch -@max_candidate@ +@max_candidate disable not_int1, not_int2, neg_if_exp@ expression E1, E2, E3, E4; binary operator cmp = {>, >=}; @@ E1 cmp E2 ? E3 : E4 -@min_candidate@ +@min_candidate disable not_int1, not_int2, neg_if_exp@ expression E1, E2, E3, E4; binary operator cmp = {<, <=}; @@ E1 cmp E2 ? E3 : E4 -@rmax depends on !patch && max_candidate@ +@rmax depends on !patch && max_candidate disable not_int1, not_int2, neg_if_exp@ identifier func; expression x, y; binary operator cmp = {>, >=}; @@ -45,7 +45,7 @@ func(...) ...> } -@maxif_candidate@ +@maxif_candidate disable not_int1, not_int2, neg_if@ expression x, y; expression max_val; binary operator cmp = {>, >=}; @@ -57,7 +57,7 @@ if ((x) cmp (y)) { max_val = (y); } -@rmaxif depends on !patch && maxif_candidate@ +@rmaxif depends on !patch && maxif_candidate disable not_int1, not_int2, neg_if@ identifier func; expression x, y; expression max_val; @@ -77,7 +77,7 @@ func(...) } // Ignore errcode returns. -@errcode depends on min_candidate@ +@errcode depends on min_candidate disable not_int1, not_int2, neg_if_exp@ position p; identifier func; expression x; @@ -91,7 +91,7 @@ func(...) ...> } -@rmin depends on !patch && min_candidate@ +@rmin depends on !patch && min_candidate disable not_int1, not_int2, neg_if_exp@ identifier func; expression x, y; binary operator cmp = {<, <=}; @@ -105,7 +105,7 @@ func(...) ...> } -@minif_candidate@ +@minif_candidate disable not_int1, not_int2, neg_if@ expression x, y; expression min_val; binary operator cmp = {<, <=}; @@ -117,7 +117,7 @@ if ((x) cmp (y)) { min_val = (y); } -@rminif depends on !patch && minif_candidate@ +@rminif depends on !patch && minif_candidate disable not_int1, not_int2, neg_if@ identifier func; expression x, y; expression min_val; @@ -136,7 +136,7 @@ func(...) ...> } -@pmax depends on patch && max_candidate@ +@pmax depends on patch && max_candidate disable not_int1, not_int2, neg_if_exp@ identifier func; expression x, y; binary operator cmp = {>=, >}; @@ -150,7 +150,7 @@ func(...) ...> } -@pmaxif depends on patch && maxif_candidate@ +@pmaxif depends on patch && maxif_candidate disable not_int1, not_int2, neg_if@ identifier func; expression x, y; expression max_val; @@ -169,7 +169,7 @@ func(...) ...> } -@pmin depends on patch && min_candidate@ +@pmin depends on patch && min_candidate disable not_int1, not_int2, neg_if_exp@ identifier func; expression x, y; binary operator cmp = {<=, <}; @@ -184,7 +184,7 @@ func(...) ...> } -@pminif depends on patch && maxif_candidate@ +@pminif depends on patch && minif_candidate disable not_int1, not_int2, neg_if@ identifier func; expression x, y; expression min_val; From 5adb3698bbe8f2053afbe046dee8eb184ac78478 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=9B=B8=E6=B5=A6=E5=BD=B0=20/=20AIURA=EF=BC=8CAKIRA?= Date: Thu, 30 Jul 2026 02:38:40 +0000 Subject: [PATCH 0853/1328] coccinelle: update Coccinelle website URL The old Coccinelle project URL is no longer available. Replace it with the current Coccinelle homepage already referenced by Documentation/dev-tools/coccinelle.rst. Signed-off-by: Akira Aiura Signed-off-by: Julia Lawall --- Documentation/process/4.Coding.rst | 3 ++- scripts/coccicheck | 4 ++-- scripts/nsdeps | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Documentation/process/4.Coding.rst b/Documentation/process/4.Coding.rst index c0f57d0c4f73..c23b9e48ce49 100644 --- a/Documentation/process/4.Coding.rst +++ b/Documentation/process/4.Coding.rst @@ -312,7 +312,8 @@ be found at https://sparse.wiki.kernel.org/index.php/Main_Page if your distributor does not package it); it can then be run on the code by adding "C=1" to your make command. -The "Coccinelle" tool (http://coccinelle.lip6.fr/) is able to find a wide +The "Coccinelle" tool +(https://coccinelle.gitlabpages.inria.fr/website) is able to find a wide variety of potential coding problems; it can also propose fixes for those problems. Quite a few "semantic patches" for the kernel have been packaged under the scripts/coccinelle directory; running "make coccicheck" will run diff --git a/scripts/coccicheck b/scripts/coccicheck index 8dd766009de1..a0a0e0f72bc7 100755 --- a/scripts/coccicheck +++ b/scripts/coccicheck @@ -11,7 +11,7 @@ DIR="$(dirname $(readlink -f $0))/.." SPATCH="`which ${SPATCH:=spatch}`" if [ ! -x "$SPATCH" ]; then - echo 'spatch is part of the Coccinelle project and is available at http://coccinelle.lip6.fr/' + echo 'spatch is part of the Coccinelle project and is available at https://coccinelle.gitlabpages.inria.fr/website' exit 1 fi @@ -229,7 +229,7 @@ coccinelle () { echo " in $FILE." echo '' echo ' More information about semantic patching is available at' - echo ' http://coccinelle.lip6.fr/' + echo ' https://coccinelle.gitlabpages.inria.fr/website' echo '' if [ "`sed -ne 's|^//#||p' $COCCI`" ] ; then diff --git a/scripts/nsdeps b/scripts/nsdeps index a3372166ac01..d4b5ebcc2016 100644 --- a/scripts/nsdeps +++ b/scripts/nsdeps @@ -8,7 +8,7 @@ SPATCH_REQ_VERSION="1.0.4" DIR="$(dirname $(readlink -f $0))/.." SPATCH="`which ${SPATCH:=spatch}`" if [ ! -x "$SPATCH" ]; then - echo 'spatch is part of the Coccinelle project and is available at http://coccinelle.lip6.fr/' + echo 'spatch is part of the Coccinelle project and is available at https://coccinelle.gitlabpages.inria.fr/website' exit 1 fi From b64acacd5429ff3fe7c38cb3cf959bbd3a9fb30f Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sun, 2 Aug 2026 21:20:51 +0200 Subject: [PATCH 0854/1328] coccinelle: api: check for macro context A cast on a call to an allocation function that is the body of a macro can be useful, as it ensures tha the macro is used for allocating objects of the right type. Add two new rules to ignore the macro case. Suggested-by: Steven Rostedt Signed-off-by: Julia Lawall --- scripts/coccinelle/api/alloc/alloc_cast.cocci | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/scripts/coccinelle/api/alloc/alloc_cast.cocci b/scripts/coccinelle/api/alloc/alloc_cast.cocci index f6f0ccdb6409..958c914f8ba6 100644 --- a/scripts/coccinelle/api/alloc/alloc_cast.cocci +++ b/scripts/coccinelle/api/alloc/alloc_cast.cocci @@ -20,6 +20,24 @@ virtual patch virtual org virtual report +@m1@ +identifier i; +expression e; +type T; +position p1; +@@ + +#define i (T@p1 *)e + +@m2@ +identifier i; +expression e; +type T; +position p2; +@@ + +#define i(...) (T@p2 *)e + @initialize:python@ @@ import re @@ -28,9 +46,10 @@ m = re.compile(pattern) @r1 depends on context || patch@ type T; +position p != {m1.p1,m2.p2}; @@ - (T *) + (T@p *) \(kmalloc\|kzalloc\|kcalloc\|kmem_cache_alloc\|kmem_cache_zalloc\| kmem_cache_alloc_node\|kmalloc_node\|kzalloc_node\|vmalloc\|vzalloc\| dma_alloc_coherent\|devm_kmalloc\|devm_kzalloc\| @@ -90,7 +109,7 @@ type r1.T; @r2 depends on org || report@ type T; -position p; +position p != {m1.p1,m2.p2}; @@ (T@p *) From a53cbd149844cbde80f6adfc7f4233b8f230f664 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Mon, 27 Jul 2026 22:52:44 +0900 Subject: [PATCH 0855/1328] coccinelle: mini_lock: improve performance when searching loops The 'looped' rule collects the returns inside a for loop to prevent 'err' from reporting them. It searches every for loop in the file, and on files with large loop bodies the search explodes. For example, kernel/bpf/verifier.c runs for over 200 seconds, almost entirely in 'looped' according to --profile. Since the kernel .cocciconfig sets a 200 second timeout, coccicheck silently skips the file. To avoid this, collect the candidate returns first, so that 'looped' checks only those positions. 'err' then excludes what 'looped' found. Every return that 'err' can report is also a candidate, so the same returns are excluded as before and the output does not change. A report-mode run over every .c file in the tree produces identical output. So verifier.c now finishes well within the timeout, in a few seconds. Signed-off-by: Sang-Heon Jeon Signed-off-by: Julia Lawall --- scripts/coccinelle/locks/mini_lock.cocci | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/scripts/coccinelle/locks/mini_lock.cocci b/scripts/coccinelle/locks/mini_lock.cocci index 71065d8a5d54..c65241c895ff 100644 --- a/scripts/coccinelle/locks/mini_lock.cocci +++ b/scripts/coccinelle/locks/mini_lock.cocci @@ -53,11 +53,31 @@ spin_lock_irq@p1 spin_lock_irqsave@p1 ) (E1@p,...); -@looped@ +@err_candidate exists@ +expression E1; +position prelocked.p; +position up != prelocked.p1; +position rc; +identifier lock,unlock; +@@ + +lock(E1@p,...); +... when != E1 + when any +if (...) { + ... when != E1 + return@rc ...; +} +... when != E1 + when any +unlock@up(E1,...); + +@looped exists@ +position err_candidate.rc; position r; @@ -for(...;...;...) { <+... return@r ...; ...+> } +for(...;...;...) { <+... return@rc@r ...; ...+> } @err exists@ expression E1; From 707f8e0ddf3f730c15e5c18b346d4258ddb46868 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Sun, 9 Aug 2026 13:53:34 +0900 Subject: [PATCH 0856/1328] coccinelle: misc: struct_size: drop unneeded parentheses The outer parentheses and the single-branch disjunction don't matter when just matching and reporting a line. Eliminating them reduces the complexity of the pattern to match and gives a performance improvement in the non-patch cases, like the previous minmax change. Signed-off-by: Sang-Heon Jeon Signed-off-by: Julia Lawall --- scripts/coccinelle/misc/struct_size.cocci | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/scripts/coccinelle/misc/struct_size.cocci b/scripts/coccinelle/misc/struct_size.cocci index 9b02c37438e4..406884d68d43 100644 --- a/scripts/coccinelle/misc/struct_size.cocci +++ b/scripts/coccinelle/misc/struct_size.cocci @@ -29,9 +29,7 @@ f expression E1, E2; identifier m; @@ -( -* (sizeof(*E1) + (E2 * sizeof(*E1->m))) -) +* sizeof(*E1) + (E2 * sizeof(*E1->m)) //---------------------------------------------------------- // For patch mode @@ -55,9 +53,7 @@ expression E1, E2; identifier m; position p; @@ -( - (sizeof(*E1)@p + (E2 * sizeof(*E1->m))) -) + sizeof(*E1)@p + (E2 * sizeof(*E1->m)) @script:python depends on org@ p << r.p; From 8c59f5c467aff29d9d8088e07b6ca34926f6151d Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sun, 9 Aug 2026 18:43:36 +0200 Subject: [PATCH 0857/1328] scripts: coccinelle: devm_free: reduce false positives False positives could be introduced due to allocations using the new _obj functions. Add these to the "safe" rule accordingly. False positives could also be introduced when the same variable name has two possible types. Incorporate type information to avoid reporting this case This does lead to false negatives when no type information is available. Signed-off-by: Julia Lawall Reported-by: Ricardo Ribalda --- scripts/coccinelle/free/devm_free.cocci | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/scripts/coccinelle/free/devm_free.cocci b/scripts/coccinelle/free/devm_free.cocci index 0880729badbc..947d7e685655 100644 --- a/scripts/coccinelle/free/devm_free.cocci +++ b/scripts/coccinelle/free/devm_free.cocci @@ -26,7 +26,8 @@ virtual report virtual context @r depends on context || org || report@ -expression x; +type T; +T x; @@ ( @@ -56,18 +57,26 @@ expression x; ) @safe depends on context || org || report exists@ -expression x; +r.T x; position p; @@ ( x = kmalloc(...) +| + x = kmalloc_obj(...) +| + x = kmalloc_objs(...) | x = kvasprintf(...) | x = kasprintf(...) | x = kzalloc(...) +| + x = kzalloc_obj(...) +| + x = kzalloc_objs(...) | x = kmalloc_array(...) | @@ -105,7 +114,7 @@ position p; ) @pb@ -expression r.x; +r.T r.x; position p != safe.p; @@ From cfeffda899903bfaf5244dab3596f585511af814 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Mon, 24 Aug 2026 01:18:13 +0900 Subject: [PATCH 0858/1328] coccinelle: remove obsolete pci_free_consistent.cocci pci_alloc_consistent() and pci_free_consistent() were removed by commit 7968778914e5 ("PCI: Remove the deprecated "pci-dma-compat.h" API"). So remove the obsolete script. Signed-off-by: Sang-Heon Jeon Signed-off-by: Julia Lawall --- .../coccinelle/free/pci_free_consistent.cocci | 53 ------------------- 1 file changed, 53 deletions(-) delete mode 100644 scripts/coccinelle/free/pci_free_consistent.cocci diff --git a/scripts/coccinelle/free/pci_free_consistent.cocci b/scripts/coccinelle/free/pci_free_consistent.cocci deleted file mode 100644 index e062b9ba09ff..000000000000 --- a/scripts/coccinelle/free/pci_free_consistent.cocci +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/// Find missing pci_free_consistent for every pci_alloc_consistent. -/// -// Confidence: Moderate -// Copyright: (C) 2013 Petr Strnad. -// URL: https://coccinelle.gitlabpages.inria.fr/website -// Keywords: pci_free_consistent, pci_alloc_consistent -// Options: --no-includes --include-headers - -virtual report -virtual org - -@search@ -local idexpression id; -expression x,y,z,e; -position p1,p2; -type T; -@@ - -id = pci_alloc_consistent@p1(x,y,&z) -... when != e = id -if (id == NULL || ...) { ... return ...; } -... when != pci_free_consistent(x,y,id,z) - when != if (id) { ... pci_free_consistent(x,y,id,z) ... } - when != if (y) { ... pci_free_consistent(x,y,id,z) ... } - when != e = (T)id - when exists -( -return 0; -| -return 1; -| -return id; -| -return@p2 ...; -) - -@script:python depends on report@ -p1 << search.p1; -p2 << search.p2; -@@ - -msg = "ERROR: missing pci_free_consistent; pci_alloc_consistent on line %s and return without freeing on line %s" % (p1[0].line,p2[0].line) -coccilib.report.print_report(p2[0],msg) - -@script:python depends on org@ -p1 << search.p1; -p2 << search.p2; -@@ - -msg = "ERROR: missing pci_free_consistent; pci_alloc_consistent on line %s and return without freeing on line %s" % (p1[0].line,p2[0].line) -cocci.print_main(msg,p1) -cocci.print_secs("",p2) From 2bd30b8dc75b823dd7bbef9d7bc68e1fd7ecee5c Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Mon, 24 Aug 2026 01:18:14 +0900 Subject: [PATCH 0859/1328] coccinelle: alloc_cast: drop removed allocators - pci_alloc_consistent() and pci_zalloc_consistent() were removed by commit 7968778914e5 ("PCI: Remove the deprecated "pci-dma-compat.h" API") - kmem_alloc() was removed by commit f078d4ea8276 ("xfs: convert kmem_alloc() to kmalloc()") - kmem_zalloc() was removed by commit 10634530f7ba ("xfs: convert kmem_zalloc() to kzalloc()") - kmem_zone_alloc() and kmem_zone_zalloc() were removed by commit bae633a4a283 ("xfs: remove xfs_zone_{alloc,zalloc} helpers") So drop them from the rules. No functional change. Signed-off-by: Sang-Heon Jeon Signed-off-by: Julia Lawall --- scripts/coccinelle/api/alloc/alloc_cast.cocci | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/scripts/coccinelle/api/alloc/alloc_cast.cocci b/scripts/coccinelle/api/alloc/alloc_cast.cocci index 958c914f8ba6..a4b83cc8ea01 100644 --- a/scripts/coccinelle/api/alloc/alloc_cast.cocci +++ b/scripts/coccinelle/api/alloc/alloc_cast.cocci @@ -53,9 +53,8 @@ position p != {m1.p1,m2.p2}; \(kmalloc\|kzalloc\|kcalloc\|kmem_cache_alloc\|kmem_cache_zalloc\| kmem_cache_alloc_node\|kmalloc_node\|kzalloc_node\|vmalloc\|vzalloc\| dma_alloc_coherent\|devm_kmalloc\|devm_kzalloc\| - kvmalloc\|kvzalloc\|kvmalloc_node\|kvzalloc_node\|pci_alloc_consistent\| - pci_zalloc_consistent\|kmem_alloc\|kmem_zalloc\|kmem_zone_alloc\| - kmem_zone_zalloc\|vmalloc_node\|vzalloc_node\)(...) + kvmalloc\|kvzalloc\|kvmalloc_node\|kvzalloc_node\| + vmalloc_node\|vzalloc_node\)(...) //---------------------------------------------------------- // For context mode @@ -76,9 +75,8 @@ type r1.T; \(kmalloc\|kzalloc\|kcalloc\|kmem_cache_alloc\|kmem_cache_zalloc\| kmem_cache_alloc_node\|kmalloc_node\|kzalloc_node\|vmalloc\|vzalloc\| dma_alloc_coherent\|devm_kmalloc\|devm_kzalloc\| - kvmalloc\|kvzalloc\|kvmalloc_node\|kvzalloc_node\|pci_alloc_consistent\| - pci_zalloc_consistent\|kmem_alloc\|kmem_zalloc\|kmem_zone_alloc\| - kmem_zone_zalloc\|vmalloc_node\|vzalloc_node\)(...) + kvmalloc\|kvzalloc\|kvmalloc_node\|kvzalloc_node\| + vmalloc_node\|vzalloc_node\)(...) //---------------------------------------------------------- // For patch mode @@ -99,9 +97,8 @@ type r1.T; \(kmalloc\|kzalloc\|kcalloc\|kmem_cache_alloc\|kmem_cache_zalloc\| kmem_cache_alloc_node\|kmalloc_node\|kzalloc_node\|vmalloc\|vzalloc\| dma_alloc_coherent\|devm_kmalloc\|devm_kzalloc\| - kvmalloc\|kvzalloc\|kvmalloc_node\|kvzalloc_node\|pci_alloc_consistent\| - pci_zalloc_consistent\|kmem_alloc\|kmem_zalloc\|kmem_zone_alloc\| - kmem_zone_zalloc\|vmalloc_node\|vzalloc_node\)(...) + kvmalloc\|kvzalloc\|kvmalloc_node\|kvzalloc_node\| + vmalloc_node\|vzalloc_node\)(...) //---------------------------------------------------------- // For org and report mode @@ -116,9 +113,8 @@ position p != {m1.p1,m2.p2}; \(kmalloc\|kzalloc\|kcalloc\|kmem_cache_alloc\|kmem_cache_zalloc\| kmem_cache_alloc_node\|kmalloc_node\|kzalloc_node\|vmalloc\|vzalloc\| dma_alloc_coherent\|devm_kmalloc\|devm_kzalloc\| - kvmalloc\|kvzalloc\|kvmalloc_node\|kvzalloc_node\|pci_alloc_consistent\| - pci_zalloc_consistent\|kmem_alloc\|kmem_zalloc\|kmem_zone_alloc\| - kmem_zone_zalloc\|vmalloc_node\|vzalloc_node\)(...) + kvmalloc\|kvzalloc\|kvmalloc_node\|kvzalloc_node\| + vmalloc_node\|vzalloc_node\)(...) @script:python depends on org@ p << r2.p; From 0319b42e1e28b845a3092082fa1e1ff9043f833e Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Mon, 24 Aug 2026 01:18:15 +0900 Subject: [PATCH 0860/1328] coccinelle: zalloc-simple: drop the kmem_alloc rules - kmem_alloc() was removed by commit f078d4ea8276 ("xfs: convert kmem_alloc() to kmalloc()") - kmem_zalloc() was removed by commit 10634530f7ba ("xfs: convert kmem_zalloc() to kzalloc()") So drop the kmem_alloc rules. No functional change. Signed-off-by: Sang-Heon Jeon Signed-off-by: Julia Lawall --- .../coccinelle/api/alloc/zalloc-simple.cocci | 41 +------------------ 1 file changed, 1 insertion(+), 40 deletions(-) diff --git a/scripts/coccinelle/api/alloc/zalloc-simple.cocci b/scripts/coccinelle/api/alloc/zalloc-simple.cocci index d66c45356691..ae1d39e71e31 100644 --- a/scripts/coccinelle/api/alloc/zalloc-simple.cocci +++ b/scripts/coccinelle/api/alloc/zalloc-simple.cocci @@ -35,7 +35,7 @@ statement S; @@ * x = (T)\(kmalloc(E1, ...)\|vmalloc(E1)\|dma_alloc_coherent(...,E1,...)\| - kmalloc_node(E1, ...)\|kmem_cache_alloc(...)\|kmem_alloc(E1, ...)\| + kmalloc_node(E1, ...)\|kmem_cache_alloc(...)\| devm_kmalloc(...,E1,...)\|kvmalloc(E1, ...)\|kvmalloc_node(E1,...)\); if ((x==NULL) || ...) S * memset((T2)x,0,E1); @@ -88,15 +88,6 @@ statement S; - x = (T)kmem_cache_alloc(E3,E4); + x = (T)kmem_cache_zalloc(E3,E4); | -- x = kmem_alloc(E1,E2); -+ x = kmem_zalloc(E1,E2); -| -- x = (T *)kmem_alloc(E1,E2); -+ x = kmem_zalloc(E1,E2); -| -- x = (T)kmem_alloc(E1,E2); -+ x = (T)kmem_zalloc(E1,E2); -| - x = devm_kmalloc(E2,E1,E3); + x = devm_kzalloc(E2,E1,E3); | @@ -290,36 +281,6 @@ x << r4.x; msg="WARNING: kmem_cache_zalloc should be used for %s, instead of kmem_cache_alloc/memset" % (x) coccilib.report.print_report(p[0], msg) -//----------------------------------------------------------------- -@r5 depends on org || report@ -type T, T2; -expression x; -expression E1,E2; -statement S; -position p; -@@ - - x = (T)kmem_alloc@p(E1,E2); - if ((x==NULL) || ...) S - memset((T2)x,0,E1); - -@script:python depends on org@ -p << r5.p; -x << r5.x; -@@ - -msg="%s" % (x) -msg_safe=msg.replace("[","@(").replace("]",")") -coccilib.org.print_todo(p[0], msg_safe) - -@script:python depends on report@ -p << r5.p; -x << r5.x; -@@ - -msg="WARNING: kmem_zalloc should be used for %s, instead of kmem_alloc/memset" % (x) -coccilib.report.print_report(p[0], msg) - //----------------------------------------------------------------- @r6 depends on org || report@ type T, T2; From e1fc6e70f36e93eede63df85d942011a3f96a764 Mon Sep 17 00:00:00 2001 From: Hardik Garg Date: Mon, 27 Jul 2026 21:11:01 +0000 Subject: [PATCH 0861/1328] Drivers: hv: Use meaningful errnos for hypercall status codes Commit 3817854ba892 ("hyperv: Log hypercall status codes as strings") converted hv_result_to_errno() from a switch to a table and added status codes used for string logging. Statuses without an existing specific mapping were assigned the generic -EIO fallback even when a more specific errno was available. Map HV_STATUS_ACCESS_DENIED and HV_STATUS_OPERATION_DENIED to -EACCES, HV_STATUS_UNKNOWN_PROPERTY and HV_STATUS_PROPERTY_VALUE_OUT_OF_RANGE to -EINVAL, and HV_STATUS_PROCESSOR_FEATURE_NOT_SUPPORTED to -EOPNOTSUPP. This lets callers distinguish permission, argument, and capability failures from generic I/O errors. The table conversion also added duplicate HV_STATUS_INVALID_LP_INDEX and HV_STATUS_INVALID_REGISTER_VALUE entries. Remove the later -EIO entries, which are unreachable because find_hv_status_info() returns the first match. Signed-off-by: Hardik Garg Reviewed-by: Naman Jain Signed-off-by: Wei Liu --- drivers/hv/hv_common.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/drivers/hv/hv_common.c b/drivers/hv/hv_common.c index 6b67ac616789..31256cb22b39 100644 --- a/drivers/hv/hv_common.c +++ b/drivers/hv/hv_common.c @@ -787,11 +787,11 @@ static const struct hv_status_info hv_status_infos[] = { _STATUS_INFO(HV_STATUS_INVALID_HYPERCALL_INPUT, -EINVAL), _STATUS_INFO(HV_STATUS_INVALID_ALIGNMENT, -EIO), _STATUS_INFO(HV_STATUS_INVALID_PARAMETER, -EINVAL), - _STATUS_INFO(HV_STATUS_ACCESS_DENIED, -EIO), + _STATUS_INFO(HV_STATUS_ACCESS_DENIED, -EACCES), _STATUS_INFO(HV_STATUS_INVALID_PARTITION_STATE, -EIO), - _STATUS_INFO(HV_STATUS_OPERATION_DENIED, -EIO), - _STATUS_INFO(HV_STATUS_UNKNOWN_PROPERTY, -EIO), - _STATUS_INFO(HV_STATUS_PROPERTY_VALUE_OUT_OF_RANGE, -EIO), + _STATUS_INFO(HV_STATUS_OPERATION_DENIED, -EACCES), + _STATUS_INFO(HV_STATUS_UNKNOWN_PROPERTY, -EINVAL), + _STATUS_INFO(HV_STATUS_PROPERTY_VALUE_OUT_OF_RANGE, -EINVAL), _STATUS_INFO(HV_STATUS_INSUFFICIENT_MEMORY, -ENOMEM), _STATUS_INFO(HV_STATUS_INSUFFICIENT_CONTIGUOUS_MEMORY, -ENOMEM), _STATUS_INFO(HV_STATUS_INSUFFICIENT_ROOT_MEMORY, -ENOMEM), @@ -805,11 +805,9 @@ static const struct hv_status_info hv_status_infos[] = { _STATUS_INFO(HV_STATUS_NOT_ACKNOWLEDGED, -EIO), _STATUS_INFO(HV_STATUS_INVALID_VP_STATE, -EIO), _STATUS_INFO(HV_STATUS_NO_RESOURCES, -EIO), - _STATUS_INFO(HV_STATUS_PROCESSOR_FEATURE_NOT_SUPPORTED, -EIO), + _STATUS_INFO(HV_STATUS_PROCESSOR_FEATURE_NOT_SUPPORTED, -EOPNOTSUPP), _STATUS_INFO(HV_STATUS_INVALID_LP_INDEX, -EINVAL), _STATUS_INFO(HV_STATUS_INVALID_REGISTER_VALUE, -EINVAL), - _STATUS_INFO(HV_STATUS_INVALID_LP_INDEX, -EIO), - _STATUS_INFO(HV_STATUS_INVALID_REGISTER_VALUE, -EIO), _STATUS_INFO(HV_STATUS_OPERATION_FAILED, -EIO), _STATUS_INFO(HV_STATUS_TIME_OUT, -EIO), _STATUS_INFO(HV_STATUS_CALL_PENDING, -EIO), From 86bdd16e8f390d51bae9e77a4bc4164ca2f580fe Mon Sep 17 00:00:00 2001 From: Naman Jain Date: Mon, 10 Aug 2026 09:07:13 +0000 Subject: [PATCH 0862/1328] PCI: hv: Set irq_retrigger callback for the Hyper-V PCI MSI irqchip The Hyper-V vPCI MSI irqchip never installs an irq_retrigger() callback. On CPU hot-unplug fixup_irqs() migrates the interrupts which are affine to the outgoing CPU to a new target. If an interrupt still has its pending bit set in the outgoing CPU's IRR at that point, fixup_irqs() resends it on the new target through the irqchip's irq_retrigger() callback. As the Hyper-V PCI/MSI chip does not provide that callback, the pending interrupt is silently dropped, which can result in lost interrupts, stalls and "No irq handler for vector" messages during CPU hotplug. Install irq_chip_retrigger_hierarchy() as the irq_retrigger() callback for the Hyper-V PCI/MSI irqchip, so that a pending interrupt is resent on its new target CPU via the parent x86 vector domain. Fixes: 4daace0d8ce85 ("PCI: hv: Add paravirtual PCI front-end for Microsoft Hyper-V VMs") Cc: stable@vger.kernel.org Suggested-by: Long Li Suggested-by: Thomas Gleixner Reviewed-by: Aditya Garg Reviewed-by: Shradha Gupta Signed-off-by: Naman Jain Reviewed-by: Michael Kelley Signed-off-by: Wei Liu --- drivers/pci/controller/pci-hyperv.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/pci/controller/pci-hyperv.c b/drivers/pci/controller/pci-hyperv.c index cfc8fa403dad..89816a2bd7cd 100644 --- a/drivers/pci/controller/pci-hyperv.c +++ b/drivers/pci/controller/pci-hyperv.c @@ -2119,6 +2119,7 @@ static bool hv_pcie_init_dev_msi_info(struct device *dev, struct irq_domain *dom info->ops->msi_prepare = hv_msi_prepare; chip->irq_set_affinity = irq_chip_set_affinity_parent; + chip->irq_retrigger = irq_chip_retrigger_hierarchy; if (IS_ENABLED(CONFIG_X86)) chip->flags |= IRQCHIP_MOVE_DEFERRED; From 769bcbee07a45856e0b603ee23243737a2f9ca2a Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Wed, 29 Jul 2026 17:50:19 -0700 Subject: [PATCH 0863/1328] x86/hyperv: reserve more vectors Microsoft Hypervisor delivers three vectors to the NT HAL running in the root partition and refuses to map a device interrupt to any of them when interrupt remapping is not available in the system. As of writing, the nested MSHV setup has no interrupt remapping capability. The three vectors are: HAL_NT_APC_VECTOR 0x1F HAL_NT_DPC_VECTOR 0x2F HAL_NT_CLOCK_IPI_VECTOR 0xD2 0x1F is below FIRST_EXTERNAL_VECTOR so the vector allocator never hands it out, but 0x2F and 0xD2 are both inside the allocatable range and are handed out once enough vectors are in use. Mapping such an interrupt then fails with HV_STATUS_INVALID_PARAMETER, and the interrupt is never delivered. Reserve all three next to the hypervisor debug vectors that are already kept out of the allocator's hands. Reviewed-by: Michael Kelley Signed-off-by: Wei Liu --- arch/x86/kernel/cpu/mshyperv.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/arch/x86/kernel/cpu/mshyperv.c b/arch/x86/kernel/cpu/mshyperv.c index 185d4f677ec0..229c7377a980 100644 --- a/arch/x86/kernel/cpu/mshyperv.c +++ b/arch/x86/kernel/cpu/mshyperv.c @@ -502,17 +502,32 @@ static void hv_reserve_irq_vectors(void) #define HYPERV_DBG_ASSERT_VECTOR 0x2C #define HYPERV_DBG_SERVICE_VECTOR 0x2D + /* + * The hypervisor delivers these three to the NT HAL and refuses to + * map a device interrupt to any of them. + * + * The hypervisor will provide a hint in the future when these + * vectors become available to use. + */ + #define HAL_NT_APC_VECTOR 0x1F + #define HAL_NT_DPC_VECTOR 0x2F + #define HAL_NT_CLOCK_IPI_VECTOR 0xD2 + if (cpu_feature_enabled(X86_FEATURE_FRED)) return; if (test_and_set_bit(HYPERV_DBG_ASSERT_VECTOR, system_vectors) || test_and_set_bit(HYPERV_DBG_SERVICE_VECTOR, system_vectors) || - test_and_set_bit(HYPERV_DBG_FASTFAIL_VECTOR, system_vectors)) + test_and_set_bit(HYPERV_DBG_FASTFAIL_VECTOR, system_vectors) || + test_and_set_bit(HAL_NT_APC_VECTOR, system_vectors) || + test_and_set_bit(HAL_NT_DPC_VECTOR, system_vectors) || + test_and_set_bit(HAL_NT_CLOCK_IPI_VECTOR, system_vectors)) BUG(); - pr_info("Hyper-V: reserve vectors: 0x%x 0x%x 0x%x\n", + pr_info("Hyper-V: reserve vectors: 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x\n", HYPERV_DBG_ASSERT_VECTOR, HYPERV_DBG_SERVICE_VECTOR, - HYPERV_DBG_FASTFAIL_VECTOR); + HYPERV_DBG_FASTFAIL_VECTOR, HAL_NT_APC_VECTOR, + HAL_NT_DPC_VECTOR, HAL_NT_CLOCK_IPI_VECTOR); } static void __init ms_hyperv_init_platform(void) From cee0d90bceae1dee3bcc70f8d6b2ceb5b87deb42 Mon Sep 17 00:00:00 2001 From: Michael Kelley Date: Wed, 12 Aug 2026 07:52:23 -0700 Subject: [PATCH 0864/1328] Drivers: hv: vmbus: Skip VMBus module cleanup for non-nested root partition The VMBus module initialization function, hv_acpi_init(), currently does nothing when running in the root partition and root is not nested in another VM. But the initialization function reports success, so the VMBus module is indeed loaded. VMBus functionality is not actually needed, but the VMBus module must be loaded so that hv_vmbus_exists() can answer correctly. Furthermore, the mshv_root dependency on the VMBus module is needed as described in the commit message for 840b740a35bf ("mshv: Add conditional VMBus dependency"). Loading the VMBus module without actually initializing it causes failures if the module should later be unloaded. The module unload code tries to clean up things that were never initialized, resulting in memory faults and a panic. Fix this by having VMBus module exit function perform the same check for non-nested root partition, and do nothing in such a case, just like hv_acpi_init(). In the long run, the code that manages the Hyper-V provided SynIC should be refactored to better coordinate the requirements of root partition scenarios and normal VM scenarios, and to hopefully remove the hv_vmbus_exists() dependnecy between mshv_root and VMBus modules. Preventing the current unload failure scenario is an expediency until such a refactoring is done. Reported-by: Sashiko Closes: https://lore.kernel.org/linux-hyperv/20260721154943.A09BD1F00A3D@smtp.kernel.org/ Fixes: 7e279d78664aa ("Drivers: hv: vmbus: skip VMBus initialization if Linux is root") Signed-off-by: Michael Kelley Reviewed-by: Easwar Hariharan Signed-off-by: Wei Liu --- drivers/hv/vmbus_drv.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index 6824bd7cb3c4..18ee549d9880 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c @@ -2982,6 +2982,13 @@ static int __init hv_acpi_init(void) return -ENODEV; if (hv_root_partition() && !hv_nested) + /* + * A non-nested root partition does not need VMBus client + * functionality. However, the mshv_root module may have + * a dependency on the VMBus module as described in + * commit 840b740a35bf. Return success so the module + * loads even though no VMBus initialization is done. + */ return 0; /* @@ -3030,6 +3037,14 @@ static void __exit vmbus_exit(void) { int cpu; + if (hv_root_partition() && !hv_nested) + /* + * If a non-nested root partition loaded the VMBus module, + * hv_acpi_init() did not do any VMBus initialization. + * There's nothing to clean up, so just return. + */ + return; + unregister_syscore(&hv_synic_syscore); hv_remove_kexec_handler(); From 8f1da3536b8f8420988fe6e6ae15d58afa2ba2d8 Mon Sep 17 00:00:00 2001 From: Kameron Carr Date: Tue, 11 Aug 2026 09:04:45 -0700 Subject: [PATCH 0865/1328] Drivers: hv: vmbus: add vmbus_establish_gpadl_caller_decrypted() Add a new vmbus_establish_gpadl_caller_decrypted() for callers that want to decrypt their own buffers. Add a new hv_gpadl_type, HV_GPADL_BUFFER_DECRYPTED, to communicate the decryption status of the buffer. No functional change for existing callers. Signed-off-by: Kameron Carr Reviewed-by: Michael Kelley Signed-off-by: Wei Liu --- drivers/hv/channel.c | 27 +++++++++++++++++++++++++-- include/linux/hyperv.h | 8 +++++++- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c index 6821f225248b..4782f5070bba 100644 --- a/drivers/hv/channel.c +++ b/drivers/hv/channel.c @@ -40,6 +40,7 @@ static inline u32 hv_gpadl_size(enum hv_gpadl_type type, u32 size) { switch (type) { case HV_GPADL_BUFFER: + case HV_GPADL_BUFFER_DECRYPTED: return size; case HV_GPADL_RING: /* The size of a ringbuffer must be page-aligned */ @@ -100,6 +101,7 @@ static inline u64 hv_gpadl_hvpfn(enum hv_gpadl_type type, void *kbuffer, switch (type) { case HV_GPADL_BUFFER: + case HV_GPADL_BUFFER_DECRYPTED: break; case HV_GPADL_RING: if (i == 0) @@ -460,7 +462,8 @@ static int __vmbus_establish_gpadl(struct vmbus_channel *channel, } gpadl->decrypted = !((channel->co_external_memory && type == HV_GPADL_BUFFER) || - (channel->co_ring_buffer && type == HV_GPADL_RING)); + (channel->co_ring_buffer && type == HV_GPADL_RING) || + (type == HV_GPADL_BUFFER_DECRYPTED)); if (gpadl->decrypted) { /* * The "decrypted" flag being true assumes that set_memory_decrypted() succeeds. @@ -575,7 +578,7 @@ static int __vmbus_establish_gpadl(struct vmbus_channel *channel, * @channel: a channel * @kbuffer: from kmalloc or vmalloc * @size: page-size multiple - * @gpadl_handle: some funky thing + * @gpadl: output gpadl */ int vmbus_establish_gpadl(struct vmbus_channel *channel, void *kbuffer, u32 size, struct vmbus_gpadl *gpadl) @@ -585,6 +588,26 @@ int vmbus_establish_gpadl(struct vmbus_channel *channel, void *kbuffer, } EXPORT_SYMBOL_GPL(vmbus_establish_gpadl); +/* + * vmbus_establish_gpadl_caller_decrypted - Establish a GPADL for a buffer + * that has already been decrypted by the caller. + * + * @channel: a channel + * @kbuffer: from kmalloc or vmalloc; must already be decrypted by the caller + * @size: page-size multiple + * @gpadl: output gpadl + * + * The caller is responsible for re-encrypting the buffer before freeing it. + */ +int vmbus_establish_gpadl_caller_decrypted(struct vmbus_channel *channel, + void *kbuffer, u32 size, + struct vmbus_gpadl *gpadl) +{ + return __vmbus_establish_gpadl(channel, HV_GPADL_BUFFER_DECRYPTED, + kbuffer, size, 0U, gpadl); +} +EXPORT_SYMBOL_GPL(vmbus_establish_gpadl_caller_decrypted); + /** * request_arr_init - Allocates memory for the requestor array. Each slot * keeps track of the next available slot in the array. Initially, each diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index a2b484679eb4..08b927fab610 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -70,7 +70,8 @@ */ enum hv_gpadl_type { HV_GPADL_BUFFER, - HV_GPADL_RING + HV_GPADL_RING, + HV_GPADL_BUFFER_DECRYPTED }; /* Single-page buffer */ @@ -1205,6 +1206,11 @@ extern int vmbus_establish_gpadl(struct vmbus_channel *channel, u32 size, struct vmbus_gpadl *gpadl); +extern int vmbus_establish_gpadl_caller_decrypted(struct vmbus_channel *channel, + void *kbuffer, + u32 size, + struct vmbus_gpadl *gpadl); + extern int vmbus_teardown_gpadl(struct vmbus_channel *channel, struct vmbus_gpadl *gpadl); From 73fe42af955a24b6cc792b9a4867e809196f05ed Mon Sep 17 00:00:00 2001 From: Kameron Carr Date: Tue, 11 Aug 2026 09:04:46 -0700 Subject: [PATCH 0866/1328] Drivers: hv: vmbus: Add vmbus_alloc_buffer()/vmbus_free_buffer() for CoCo VMs On CoCo VMs without confidential VMBus, the netvsc send and receive buffers must be made host-visible by decrypting them. These buffers are vmalloc'ed, but set_memory_decrypted()/encrypted() do not work on vmalloc'ed memory. This use case is (so far) unique to netvsc, so solve it locally rather than changing the set_memory() or allocation APIs. Add vmbus_alloc_buffer()/vmbus_free_buffer() to the VMBus core. When the guest's isolation model requires it, allocate the buffer as a list of physically-contiguous chunks via alloc_pages_node(), starting at MAX_PAGE_ORDER and falling back to smaller orders so the allocation still succeeds under memory fragmentation. Each chunk is decrypted in place via set_memory_decrypted() on its direct-map address, and the chunks are then stitched into a single virtually-contiguous range with vmap(). Buffers that do not need decryption keep using vzalloc(). To free the buffer, vmbus_free_buffer() calls vunmap() on the range then re-encrypts and frees each chunk individually; any chunk that fails re-encryption is leaked to prevent accidentally freeing decrypted memory. This approach minimizes scattering of decrypted 4 KiB pages through the kernel direct map and the resulting shattering of large page mappings. Signed-off-by: Kameron Carr Reviewed-by: Michael Kelley Signed-off-by: Wei Liu --- drivers/hv/channel.c | 155 +++++++++++++++++++++++++++++++++++++++++ include/linux/hyperv.h | 7 ++ 2 files changed, 162 insertions(+) diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c index 4782f5070bba..f4370617deac 100644 --- a/drivers/hv/channel.c +++ b/drivers/hv/channel.c @@ -13,11 +13,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -608,6 +610,159 @@ int vmbus_establish_gpadl_caller_decrypted(struct vmbus_channel *channel, } EXPORT_SYMBOL_GPL(vmbus_establish_gpadl_caller_decrypted); +/** + * vmbus_free_buffer - release a buffer allocated by vmbus_alloc_buffer(). + * + * @addr: buffer address, or NULL if none was allocated (e.g. cleanup from a + * failed allocation) + * @chunks: chunks array from vmbus_alloc_buffer(), or NULL + * @chunk_cnt: number of entries in @chunks + * + * When @chunks is NULL the buffer is a plain vzalloc() allocation. + * + * Otherwise tear down the vmap, and for each chunk re-encrypt and free + * the underlying pages. Any chunk that cannot be re-encrypted is leaked. + */ +void vmbus_free_buffer(void *addr, struct page **chunks, u32 chunk_cnt) +{ + u32 i; + + if (!chunks) { + vfree(addr); + return; + } + + vunmap(addr); + + for (i = 0; i < chunk_cnt; i++) { + unsigned long vaddr = + (unsigned long)page_address(chunks[i]); + unsigned int order = folio_order(page_folio(chunks[i])); + + if (set_memory_encrypted(vaddr, 1U << order)) + continue; + __free_pages(chunks[i], order); + } + + kvfree(chunks); +} +EXPORT_SYMBOL_GPL(vmbus_free_buffer); + +/** + * vmbus_alloc_buffer - allocate a host-visible, virtually-contiguous buffer. + * + * @channel: the channel the buffer will be attached to + * @size: requested buffer size in bytes (will be rounded up to PAGE_SIZE) + * @chunks_out: on success, set to the array of underlying chunks, or NULL when + * the buffer was allocated with vzalloc() + * @chunk_cnt_out: on success, set to the number of chunks + * + * Buffers not requiring decryption are allocated with vzalloc(). + * + * Buffers requiring decryption are allocated as a series of + * physically-contiguous chunks, starting at MAX_PAGE_ORDER and falling back to + * smaller orders on allocation failure. Each chunk is transitioned to + * host-visible via set_memory_decrypted() on its direct-map address, then all + * chunks are combined into a virtually-contiguous range via vmap(). + * + * Return: the buffer's virtual address, or NULL on failure. + */ +void *vmbus_alloc_buffer(struct vmbus_channel *channel, + u32 size, + struct page ***chunks_out, + u32 *chunk_cnt_out) +{ + unsigned long nr_pages = PFN_UP(size); + unsigned long remaining = nr_pages; + unsigned long page_idx = 0; + struct page **chunks = NULL; + struct page **pages = NULL; + int order = MAX_PAGE_ORDER; + u32 chunk_cnt = 0; + void *addr; + u32 i; + int ret; + + *chunks_out = NULL; + *chunk_cnt_out = 0; + + if (!nr_pages) + return NULL; + + /* If the buffer does not need to be decrypted, just use vzalloc() */ + if (!hv_is_isolation_supported() || channel->co_external_memory) + return vzalloc(nr_pages << PAGE_SHIFT); + + /* Worst case: every chunk is a single page. */ + chunks = kvmalloc_array(nr_pages, sizeof(*chunks), + GFP_KERNEL | __GFP_ZERO); + if (!chunks) + goto err; + + pages = kvmalloc_array(nr_pages, sizeof(*pages), GFP_KERNEL); + if (!pages) + goto err; + + while (remaining) { + struct page *page; + gfp_t gfp; + + order = min(order, ilog2(remaining)); + + /* + * Use __GFP_NORETRY | __GFP_NOWARN to avoid OOM-killing, + * but try harder at order 0 since that is the final + * fallback. + * __GFP_COMP stores order information in the page folio. + */ + gfp = GFP_KERNEL | __GFP_ZERO; + if (order) + gfp |= __GFP_COMP | __GFP_NORETRY | __GFP_NOWARN; + + page = alloc_pages_node(cpu_to_node(channel->target_cpu), + gfp, order); + if (!page) { + if (!order--) + goto err; + continue; + } + + ret = set_memory_decrypted((unsigned long)page_address(page), + 1U << order); + if (ret) { + /* + * set_memory_decrypted() failed; the page state is + * unknown so it must be leaked rather than freed. + */ + goto err; + } + + chunks[chunk_cnt++] = page; + + for (i = 0; i < (1U << order); i++) + pages[page_idx++] = page + i; + + remaining -= 1U << order; + } + + addr = vmap(pages, nr_pages, VM_MAP, pgprot_decrypted(PAGE_KERNEL)); + if (!addr) + goto err; + + memset(addr, 0, nr_pages << PAGE_SHIFT); + + kvfree(pages); + *chunks_out = chunks; + *chunk_cnt_out = chunk_cnt; + return addr; + +err: + kvfree(pages); + vmbus_free_buffer(NULL, chunks, chunk_cnt); + return NULL; +} +EXPORT_SYMBOL_GPL(vmbus_alloc_buffer); + /** * request_arr_init - Allocates memory for the requestor array. Each slot * keeps track of the next available slot in the array. Initially, each diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 08b927fab610..a97be78653e8 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -1214,6 +1214,13 @@ extern int vmbus_establish_gpadl_caller_decrypted(struct vmbus_channel *channel, extern int vmbus_teardown_gpadl(struct vmbus_channel *channel, struct vmbus_gpadl *gpadl); +extern void *vmbus_alloc_buffer(struct vmbus_channel *channel, + u32 size, + struct page ***chunks_out, + u32 *chunk_cnt_out); + +extern void vmbus_free_buffer(void *addr, struct page **chunks, u32 chunk_cnt); + void vmbus_reset_channel_cb(struct vmbus_channel *channel); extern int vmbus_recvpacket(struct vmbus_channel *channel, From f85e1cc5ecbbbb18ac68639f667c9de49b3ca986 Mon Sep 17 00:00:00 2001 From: Kameron Carr Date: Tue, 11 Aug 2026 09:04:47 -0700 Subject: [PATCH 0867/1328] hv_netvsc: Allocate send/receive buffers using vmbus_alloc_buffer() On CoCo VMs without confidential VMBus, the netvsc send and receive buffers must be made host-visible by decrypting them. These buffers are vmalloc'ed, but set_memory_decrypted()/encrypted() do not work on vmalloc'ed memory. This use case is (so far) unique to netvsc, so solve it locally rather than changing the set_memory() or allocation APIs. Use vmbus_alloc_buffer() to allocate the send and receive buffers, which will make them host-visible. Store the list of memory chunks in the netvsc_device struct so they can be individually freed later. Use vmbus_establish_gpadl_caller_decrypted() so there is no attempt to decrypt the virtual address. Appropriately free the buffers with vmbus_free_buffer(). Because vunmap() and set_memory_encrypted() must run in process context, replace the rcu_head/call_rcu() pair used to defer free_netvsc_device() with rcu_work/queue_rcu_work(). This also fixes a small race condition where the buffers may be accessed while being re-encrypted by moving the re-encryption after the RCU grace period. Signed-off-by: Kameron Carr Reviewed-by: Michael Kelley Signed-off-by: Wei Liu --- drivers/net/hyperv/hyperv_net.h | 8 ++- drivers/net/hyperv/netvsc.c | 103 ++++++++++++++++++++++---------- drivers/net/hyperv/netvsc_drv.c | 6 ++ 3 files changed, 83 insertions(+), 34 deletions(-) diff --git a/drivers/net/hyperv/hyperv_net.h b/drivers/net/hyperv/hyperv_net.h index 7397c693f984..4841367fdab2 100644 --- a/drivers/net/hyperv/hyperv_net.h +++ b/drivers/net/hyperv/hyperv_net.h @@ -220,6 +220,8 @@ struct net_device_context; extern u32 netvsc_ring_bytes; +int netvsc_workqueue_init(void); +void netvsc_workqueue_destroy(void); struct netvsc_device *netvsc_device_add(struct hv_device *device, const struct netvsc_device_info *info); int netvsc_alloc_recv_comp_ring(struct netvsc_device *net_device, u32 q_idx); @@ -1158,6 +1160,8 @@ struct netvsc_device { /* Receive buffer allocated by us but manages by NetVSP */ void *recv_buf; u32 recv_buf_size; /* allocated bytes */ + struct page **recv_buf_chunks; + u32 recv_buf_chunk_cnt; struct vmbus_gpadl recv_buf_gpadl_handle; u32 recv_section_cnt; u32 recv_section_size; @@ -1166,6 +1170,8 @@ struct netvsc_device { /* Send buffer allocated by us */ void *send_buf; u32 send_buf_size; + struct page **send_buf_chunks; + u32 send_buf_chunk_cnt; struct vmbus_gpadl send_buf_gpadl_handle; u32 send_section_cnt; u32 send_section_size; @@ -1193,7 +1199,7 @@ struct netvsc_device { struct netvsc_channel chan_table[VRSS_CHANNEL_MAX]; - struct rcu_head rcu; + struct rcu_work rwork; }; /* NdisInitialize message */ diff --git a/drivers/net/hyperv/netvsc.c b/drivers/net/hyperv/netvsc.c index 4d319c50955e..3b5d263cc0ce 100644 --- a/drivers/net/hyperv/netvsc.c +++ b/drivers/net/hyperv/netvsc.c @@ -29,6 +29,8 @@ #include "hyperv_net.h" #include "netvsc_trace.h" +static struct workqueue_struct *netvsc_wq; + /* * Switch the data path from the synthetic interface to the VF * interface. @@ -126,6 +128,47 @@ static void netvsc_subchan_work(struct work_struct *w) rtnl_unlock(); } +static void __free_netvsc_device(struct netvsc_device *nvdev) +{ + int i; + + kfree(nvdev->extension); + + vmbus_free_buffer(nvdev->recv_buf, nvdev->recv_buf_chunks, + nvdev->recv_buf_chunk_cnt); + vmbus_free_buffer(nvdev->send_buf, nvdev->send_buf_chunks, + nvdev->send_buf_chunk_cnt); + bitmap_free(nvdev->send_section_map); + + for (i = 0; i < VRSS_CHANNEL_MAX; i++) { + xdp_rxq_info_unreg(&nvdev->chan_table[i].xdp_rxq); + kfree(nvdev->chan_table[i].recv_buf); + vfree(nvdev->chan_table[i].mrc.slots); + } + + kfree(nvdev); +} + +static void free_netvsc_device(struct work_struct *w) +{ + struct rcu_work *rwork = to_rcu_work(w); + + __free_netvsc_device(container_of(rwork, struct netvsc_device, rwork)); +} + +int netvsc_workqueue_init(void) +{ + netvsc_wq = alloc_workqueue("hv_netvsc", WQ_UNBOUND, 0); + + return netvsc_wq ? 0 : -ENOMEM; +} + +void netvsc_workqueue_destroy(void) +{ + rcu_barrier(); + destroy_workqueue(netvsc_wq); +} + static struct netvsc_device *alloc_net_device(void) { struct netvsc_device *net_device; @@ -144,36 +187,18 @@ static struct netvsc_device *alloc_net_device(void) init_completion(&net_device->channel_init_wait); init_waitqueue_head(&net_device->subchan_open); INIT_WORK(&net_device->subchan_work, netvsc_subchan_work); + INIT_RCU_WORK(&net_device->rwork, free_netvsc_device); return net_device; } -static void free_netvsc_device(struct rcu_head *head) -{ - struct netvsc_device *nvdev - = container_of(head, struct netvsc_device, rcu); - int i; - - kfree(nvdev->extension); - - if (!nvdev->recv_buf_gpadl_handle.decrypted) - vfree(nvdev->recv_buf); - if (!nvdev->send_buf_gpadl_handle.decrypted) - vfree(nvdev->send_buf); - bitmap_free(nvdev->send_section_map); - - for (i = 0; i < VRSS_CHANNEL_MAX; i++) { - xdp_rxq_info_unreg(&nvdev->chan_table[i].xdp_rxq); - kfree(nvdev->chan_table[i].recv_buf); - vfree(nvdev->chan_table[i].mrc.slots); - } - - kfree(nvdev); -} - static void free_netvsc_device_rcu(struct netvsc_device *nvdev) { - call_rcu(&nvdev->rcu, free_netvsc_device); + /* + * Defer the actual free to process context: vunmap() and + * set_memory_encrypted() cannot run from RCU softirq context. + */ + queue_rcu_work(netvsc_wq, &nvdev->rwork); } static void netvsc_revoke_recv_buf(struct hv_device *device, @@ -352,7 +377,10 @@ static int netvsc_init_buf(struct hv_device *device, buf_size = min_t(unsigned int, buf_size, NETVSC_RECEIVE_BUFFER_SIZE_LEGACY); - net_device->recv_buf = vzalloc(buf_size); + net_device->recv_buf = + vmbus_alloc_buffer(device->channel, buf_size, + &net_device->recv_buf_chunks, + &net_device->recv_buf_chunk_cnt); if (!net_device->recv_buf) { netdev_err(ndev, "unable to allocate receive buffer of size %u\n", @@ -368,9 +396,10 @@ static int netvsc_init_buf(struct hv_device *device, * channel. Note: This call uses the vmbus connection rather * than the channel to establish the gpadl handle. */ - ret = vmbus_establish_gpadl(device->channel, net_device->recv_buf, - buf_size, - &net_device->recv_buf_gpadl_handle); + ret = vmbus_establish_gpadl_caller_decrypted(device->channel, + net_device->recv_buf, + buf_size, + &net_device->recv_buf_gpadl_handle); if (ret != 0) { netdev_err(ndev, "unable to establish receive buffer's gpadl\n"); @@ -458,7 +487,10 @@ static int netvsc_init_buf(struct hv_device *device, buf_size = device_info->send_sections * device_info->send_section_size; buf_size = round_up(buf_size, PAGE_SIZE); - net_device->send_buf = vzalloc(buf_size); + net_device->send_buf = + vmbus_alloc_buffer(device->channel, buf_size, + &net_device->send_buf_chunks, + &net_device->send_buf_chunk_cnt); if (!net_device->send_buf) { netdev_err(ndev, "unable to allocate send buffer of size %u\n", buf_size); @@ -471,9 +503,10 @@ static int netvsc_init_buf(struct hv_device *device, * channel. Note: This call uses the vmbus connection rather * than the channel to establish the gpadl handle. */ - ret = vmbus_establish_gpadl(device->channel, net_device->send_buf, - buf_size, - &net_device->send_buf_gpadl_handle); + ret = vmbus_establish_gpadl_caller_decrypted(device->channel, + net_device->send_buf, + buf_size, + &net_device->send_buf_gpadl_handle); if (ret != 0) { netdev_err(ndev, "unable to establish send buffer's gpadl\n"); @@ -1874,7 +1907,11 @@ struct netvsc_device *netvsc_device_add(struct hv_device *device, netif_napi_del(&net_device->chan_table[0].napi); cleanup2: - free_netvsc_device(&net_device->rcu); + /* + * net_device was never published, so we don't need to wait for an + * RCU grace period -- call the free routine synchronously. + */ + __free_netvsc_device(net_device); return ERR_PTR(ret); } diff --git a/drivers/net/hyperv/netvsc_drv.c b/drivers/net/hyperv/netvsc_drv.c index ee5ab5ceb2be..1d43c73fd73f 100644 --- a/drivers/net/hyperv/netvsc_drv.c +++ b/drivers/net/hyperv/netvsc_drv.c @@ -2867,12 +2867,17 @@ static void __exit netvsc_drv_exit(void) { unregister_netdevice_notifier(&netvsc_netdev_notifier); vmbus_driver_unregister(&netvsc_drv); + netvsc_workqueue_destroy(); } static int __init netvsc_drv_init(void) { int ret; + ret = netvsc_workqueue_init(); + if (ret) + return ret; + if (ring_size < RING_SIZE_MIN) { ring_size = RING_SIZE_MIN; pr_info("Increased ring_size to %u (min allowed)\n", @@ -2890,6 +2895,7 @@ static int __init netvsc_drv_init(void) err_vmbus_reg: unregister_netdevice_notifier(&netvsc_netdev_notifier); + netvsc_workqueue_destroy(); return ret; } From a9a05e801efffe3a2f949fef2a83f855e733d6e1 Mon Sep 17 00:00:00 2001 From: Michael Kelley Date: Wed, 5 Aug 2026 13:37:46 -0700 Subject: [PATCH 0868/1328] Drivers: hv: Remove support for WS2012/2012R2 & Win8/8.1 version of Hyper-V Linux code for running as a Hyper-V guest includes special cases for running on Hyper-V in WS2012/2012R2 and Windows 8/8.1. These versions were initially released 14 years ago, and official support ended in 2023 (unless a customer has contracted for extended security updates). Given the release of subsequent versions with improved functionality, there's no need to continue to support the latest Linux kernels on these versions of Hyper-V. If someone is running Linux on one of these older Hyper-V versions and doesn't want to upgrade, they can continue to do so as presumably they don't want upgrade the Linux version either. Simplify Linux code by removing special cases for running on these old versions of Hyper-V. Remove the negotiation of the VMBus protocol versions for WS2012/Win8, and remove special case code based on those VMBus protocol versions. Also update the balloon and snapshot drivers to no longer negotiate driver-specific protocol versions for these older Hyper-V versions, and remove any related special cases. Signed-off-by: Michael Kelley Signed-off-by: Wei Liu --- drivers/hv/channel_mgmt.c | 8 +++----- drivers/hv/connection.c | 11 +++++++---- drivers/hv/hv_balloon.c | 36 +++++++++++++++--------------------- drivers/hv/hv_snapshot.c | 5 ----- include/linux/hyperv.h | 5 ++--- 5 files changed, 27 insertions(+), 38 deletions(-) diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index 89d214dda360..a044fd3b3c4e 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -929,12 +929,10 @@ static void vmbus_unload_response(struct vmbus_channel_message_header *hdr) void vmbus_initiate_unload(bool crash) { struct vmbus_channel_message_header hdr; + enum vmbus_connect_state old_state; - if (xchg(&vmbus_connection.conn_state, DISCONNECTED) == DISCONNECTED) - return; - - /* Pre-Win2012R2 hosts don't support reconnect */ - if (vmbus_proto_version < VERSION_WIN8_1) + old_state = xchg(&vmbus_connection.conn_state, DISCONNECTED); + if (old_state == DISCONNECTED || old_state == CONNECTING) return; reinit_completion(&vmbus_connection.unload_event); diff --git a/drivers/hv/connection.c b/drivers/hv/connection.c index 0fd50d4cb573..1ab3581b096a 100644 --- a/drivers/hv/connection.c +++ b/drivers/hv/connection.c @@ -47,7 +47,9 @@ EXPORT_SYMBOL_GPL(vmbus_proto_version); /* * Table of VMBus versions listed from newest to oldest. - * VERSION_WIN7 and VERSION_WS2008 are no longer supported in + * VERSION_WIN7,VERSION_WS2008, VERSION_WIN8 (which is + * Windows Server 2012) and VERSION_WIN8_1 (which is + * Windows Server 2012 R2) are no longer supported in * Linux guests and are not listed. */ static __u32 vmbus_versions[] = { @@ -57,9 +59,7 @@ static __u32 vmbus_versions[] = { VERSION_WIN10_V5_1, VERSION_WIN10_V5, VERSION_WIN10_V4_1, - VERSION_WIN10, - VERSION_WIN8_1, - VERSION_WIN8 + VERSION_WIN10 }; /* @@ -304,6 +304,9 @@ int vmbus_connect(void) for (i = 0; ; i++) { if (i == ARRAY_SIZE(vmbus_versions)) { ret = -EDOM; + pr_err("Hyper-V host does not support VMBus version %d.%d or higher;\n\ + the host may be an older version no longer supported by Linux\n", + vmbus_versions[i-1] >> 16, vmbus_versions[i-1] & 0xFFFF); goto cleanup; } diff --git a/drivers/hv/hv_balloon.c b/drivers/hv/hv_balloon.c index 42ce27be344d..9cba97e81111 100644 --- a/drivers/hv/hv_balloon.c +++ b/drivers/hv/hv_balloon.c @@ -58,6 +58,10 @@ #define DYNMEM_MAJOR_VERSION(Version) ((__u32)(Version) >> 16) #define DYNMEM_MINOR_VERSION(Version) ((__u32)(Version) & 0xff) +/* + * VERSION_1 and VERSION_2 are retained for the historical record, + * but are no longer supported in Linux guests. + */ enum { DYNMEM_PROTOCOL_VERSION_1 = DYNMEM_MAKE_VERSION(0, 3), DYNMEM_PROTOCOL_VERSION_2 = DYNMEM_MAKE_VERSION(1, 0), @@ -65,9 +69,7 @@ enum { DYNMEM_PROTOCOL_VERSION_WIN7 = DYNMEM_PROTOCOL_VERSION_1, DYNMEM_PROTOCOL_VERSION_WIN8 = DYNMEM_PROTOCOL_VERSION_2, - DYNMEM_PROTOCOL_VERSION_WIN10 = DYNMEM_PROTOCOL_VERSION_3, - - DYNMEM_PROTOCOL_VERSION_CURRENT = DYNMEM_PROTOCOL_VERSION_WIN10 + DYNMEM_PROTOCOL_VERSION_WIN10 = DYNMEM_PROTOCOL_VERSION_3 }; /* @@ -1434,19 +1436,9 @@ static void version_resp(struct hv_dynmem_device *dm, version_req.version.version = dm->next_version; dm->version = version_req.version.version; - /* - * Set the next version to try in case current version fails. - * Win7 protocol ought to be the last one to try. - */ - switch (version_req.version.version) { - case DYNMEM_PROTOCOL_VERSION_WIN8: - dm->next_version = DYNMEM_PROTOCOL_VERSION_WIN7; - version_req.is_last_attempt = 0; - break; - default: - dm->next_version = 0; - version_req.is_last_attempt = 1; - } + /* Set the next version to try in case current version fails. */ + dm->next_version = 0; + version_req.is_last_attempt = 1; ret = vmbus_sendpacket(dm->dev->channel, &version_req, sizeof(struct dm_version_request), @@ -1735,16 +1727,18 @@ static int balloon_connect_vsp(struct hv_device *dev) /* * Initiate the hand shake with the host and negotiate - * a version that the host can support. We start with the - * highest version number and go down if the host cannot - * support it. + * a version that the host can support. The mechanism is in place + * to start with the highest version number and go down if the host + * cannot support it. But currently we only try the WIN10 version + * since support for older Hyper-V versions has been removed from + * Linux. */ memset(&version_req, 0, sizeof(struct dm_version_request)); version_req.hdr.type = DM_VERSION_REQUEST; version_req.hdr.size = sizeof(struct dm_version_request); version_req.hdr.trans_id = atomic_inc_return(&trans_id); version_req.version.version = DYNMEM_PROTOCOL_VERSION_WIN10; - version_req.is_last_attempt = 0; + version_req.is_last_attempt = 1; dm_device.version = version_req.version.version; ret = vmbus_sendpacket(dev->channel, &version_req, @@ -1964,7 +1958,7 @@ static int balloon_probe(struct hv_device *dev, #endif dm_device.dev = dev; dm_device.state = DM_INITIALIZING; - dm_device.next_version = DYNMEM_PROTOCOL_VERSION_WIN8; + dm_device.next_version = 0; init_completion(&dm_device.host_event); init_completion(&dm_device.config_event); INIT_LIST_HEAD(&dm_device.ha_region_list); diff --git a/drivers/hv/hv_snapshot.c b/drivers/hv/hv_snapshot.c index 506871aeacf0..847dc69e8250 100644 --- a/drivers/hv/hv_snapshot.c +++ b/drivers/hv/hv_snapshot.c @@ -372,11 +372,6 @@ static void vss_on_reset(void) int hv_vss_init(struct hv_util_service *srv) { - if (vmbus_proto_version < VERSION_WIN8_1) { - pr_warn("Integration service 'Backup (volume snapshot)'" - " not supported on this host version.\n"); - return -ENOTSUPP; - } recv_buffer = srv->recv_buffer; vss_transaction.recv_channel = srv->channel; vss_transaction.recv_channel->max_pkt_size = VSS_MAX_PKT_SIZE; diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index a97be78653e8..9e109d91aa14 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -261,9 +261,8 @@ static inline u32 hv_get_avail_to_write_percent( * 5 . 2 (Windows Server 2019, RS5) * 5 . 3 (Windows Server 2022) * - * The WS2008 and WIN7 versions are listed here for - * completeness but are no longer supported in the - * Linux kernel. + * The WS2008, WIN7, WIN8, and WIN8_1 versions are listed here for + * completeness but are no longer supported in the Linux kernel. */ #define VMBUS_MAKE_VERSION(MAJ, MIN) ((((u32)MAJ) << 16) | (MIN)) From 73da9973b353abf2fed5038d38fa403fc6944339 Mon Sep 17 00:00:00 2001 From: Michael Kelley Date: Wed, 5 Aug 2026 13:37:47 -0700 Subject: [PATCH 0869/1328] hv_sock: Remove check for old Hyper-V hosts With the overall removal of Linux support for running on Hyper-V hosts earlier than WS2016 and Windows 10, the check for such versions during hv_sock initialization is no longer necessary. Remove the check. Signed-off-by: Michael Kelley Signed-off-by: Wei Liu --- net/vmw_vsock/hyperv_transport.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/net/vmw_vsock/hyperv_transport.c b/net/vmw_vsock/hyperv_transport.c index 5405c2680dec..855b15971c35 100644 --- a/net/vmw_vsock/hyperv_transport.c +++ b/net/vmw_vsock/hyperv_transport.c @@ -961,9 +961,6 @@ static int __init hvs_init(void) { int ret; - if (vmbus_proto_version < VERSION_WIN10) - return -ENODEV; - ret = vmbus_driver_register(&hvs_drv); if (ret != 0) return ret; From 6b8345035d9de59b46c439bd117a4bec53e66bdc Mon Sep 17 00:00:00 2001 From: Michael Kelley Date: Wed, 5 Aug 2026 13:37:48 -0700 Subject: [PATCH 0870/1328] hv_netvsc: Remove GPADL teardown special case for old Hyper-V hosts With the overall removal of Linux support for running on Hyper-V hosts earlier than WS2016 and Windows 10, the special case handling of GPADL teardown is no longer necessary. Remove it. Signed-off-by: Michael Kelley Signed-off-by: Wei Liu --- drivers/net/hyperv/netvsc.c | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/drivers/net/hyperv/netvsc.c b/drivers/net/hyperv/netvsc.c index 3b5d263cc0ce..5cd084e5696c 100644 --- a/drivers/net/hyperv/netvsc.c +++ b/drivers/net/hyperv/netvsc.c @@ -728,17 +728,8 @@ void netvsc_device_remove(struct hv_device *device) = rtnl_dereference(net_device_ctx->nvdev); int i; - /* - * Revoke receive buffer. If host is pre-Win2016 then tear down - * receive buffer GPADL. Do the same for send buffer. - */ netvsc_revoke_recv_buf(device, net_device, ndev); - if (vmbus_proto_version < VERSION_WIN10) - netvsc_teardown_recv_gpadl(device, net_device, ndev); - netvsc_revoke_send_buf(device, net_device, ndev); - if (vmbus_proto_version < VERSION_WIN10) - netvsc_teardown_send_gpadl(device, net_device, ndev); RCU_INIT_POINTER(net_device_ctx->nvdev, NULL); @@ -766,14 +757,9 @@ void netvsc_device_remove(struct hv_device *device) /* Now, we can close the channel safely */ vmbus_close(device->channel); - /* - * If host is Win2016 or higher then we do the GPADL tear down - * here after VMBus is closed. - */ - if (vmbus_proto_version >= VERSION_WIN10) { - netvsc_teardown_recv_gpadl(device, net_device, ndev); - netvsc_teardown_send_gpadl(device, net_device, ndev); - } + /* Must do the GPADL teardown after channel is closed */ + netvsc_teardown_recv_gpadl(device, net_device, ndev); + netvsc_teardown_send_gpadl(device, net_device, ndev); /* Release all resources */ free_netvsc_device_rcu(net_device); From 54cf8154186f0267f084a46d21752d5973a25f62 Mon Sep 17 00:00:00 2001 From: Michael Kelley Date: Wed, 5 Aug 2026 13:37:50 -0700 Subject: [PATCH 0871/1328] scsi: storvsc: Remove support for storvsc protocol of old Hyper-V hosts With the overall removal of Linux support for running on Hyper-V hosts earlier than WS2016 and Windows 10, it's no longer necessary to support older storvsc protocols. Remove the support and associated special case code. However, support for the protocol for Windows Server 2012R2 and Win8.1 (protocol version 6.0) is *not* removed because it is activiely used by HvLite in the paravisor for Azure VMs. Signed-off-by: Michael Kelley Signed-off-by: Wei Liu --- drivers/scsi/storvsc_drv.c | 53 +++++++------------------------------- 1 file changed, 10 insertions(+), 43 deletions(-) diff --git a/drivers/scsi/storvsc_drv.c b/drivers/scsi/storvsc_drv.c index 571ea549152b..095fce47c5f1 100644 --- a/drivers/scsi/storvsc_drv.c +++ b/drivers/scsi/storvsc_drv.c @@ -47,9 +47,15 @@ * V1 RC < 2008/1/31: 1.0 * V1 RC > 2008/1/31: 2.0 * Win7: 4.2 - * Win8: 5.1 - * Win8.1: 6.0 - * Win10: 6.2 + * Win8/WS2012: 5.1 + * Win8.1/WS2012R2: 6.0 (also for HvLite paravisor in Azure) + * Win10/WS2016: 6.2 + * + * Protocol versions earlier than Win8.1 are no longer supported since + * Win8.1/WS2012R2 and earlier hosts are no longer supported by Linux. + * But protocol version 6.0 is retained since it is used by the HvLite + * paravisor in Azure. The #define's for the earlier versions remain + * for the historical record. */ #define VMSTOR_PROTO_VERSION(MAJOR_, MINOR_) ((((MAJOR_) & 0xff) << 8) | \ @@ -218,7 +224,6 @@ struct vmscsi_request { static const int protocol_version[] = { VMSTOR_PROTO_VERSION_WIN10, VMSTOR_PROTO_VERSION_WIN8_1, - VMSTOR_PROTO_VERSION_WIN8, }; @@ -1601,13 +1606,12 @@ static int storvsc_sdev_configure(struct scsi_device *sdevice, sdevice->no_write_same = 1; /* - * If the host is WIN8 or WIN8 R2, claim conformance to SPC-3 + * If the host is WIN8 R2, claim conformance to SPC-3 * if the device is a MSFT virtual device. If the host is * WIN10 or newer, allow write_same. */ if (!strncmp(sdevice->vendor, "Msft", 4)) { switch (vmstor_proto_version) { - case VMSTOR_PROTO_VERSION_WIN8: case VMSTOR_PROTO_VERSION_WIN8_1: sdevice->scsi_level = SCSI_SPC_3; break; @@ -1703,28 +1707,6 @@ static enum scsi_timeout_action storvsc_eh_timed_out(struct scsi_cmnd *scmnd) return SCSI_EH_RESET_TIMER; } -static bool storvsc_scsi_cmd_ok(struct scsi_cmnd *scmnd) -{ - bool allowed = true; - u8 scsi_op = scmnd->cmnd[0]; - - switch (scsi_op) { - /* the host does not handle WRITE_SAME, log accident usage */ - case WRITE_SAME: - /* - * smartd sends this command and the host does not handle - * this. So, don't send it. - */ - case SET_WINDOW: - set_host_byte(scmnd, DID_ERROR); - allowed = false; - break; - default: - break; - } - return allowed; -} - static enum scsi_qc_status storvsc_queuecommand(struct Scsi_Host *host, struct scsi_cmnd *scmnd) { @@ -1738,21 +1720,6 @@ static enum scsi_qc_status storvsc_queuecommand(struct Scsi_Host *host, u32 payload_sz; u32 length; - if (vmstor_proto_version <= VMSTOR_PROTO_VERSION_WIN8) { - /* - * On legacy hosts filter unimplemented commands. - * Future hosts are expected to correctly handle - * unsupported commands. Furthermore, it is - * possible that some of the currently - * unsupported commands maybe supported in - * future versions of the host. - */ - if (!storvsc_scsi_cmd_ok(scmnd)) { - scsi_done(scmnd); - return 0; - } - } - /* Setup the cmd request */ cmd_request->cmd = scmnd; From be0cfab740e58b70047ef6e7e3d578f00ed5d258 Mon Sep 17 00:00:00 2001 From: Michael Kelley Date: Wed, 5 Aug 2026 13:37:51 -0700 Subject: [PATCH 0872/1328] clocksource: hyper-v: Remove support for stimer interrupts in message mode In Hyper-V versions prior to WS2016/Win10, Hyper-V synthetic timers interrupt the guest by delivering a message that is initially handled by the Linux VMBus driver. Starting with WS2016/Win10, Hyper-V can deliver stimer interrupts directly to an assigned interrupt vector without involving the VMBus driver. This is called "Direct Mode". With the overall removal of Linux support for running on Hyper-V hosts earlier than WS2016 and Windows 10, it's no longer necessary to support the legacy message-based delivery. Remove that delivery mechanism and always use Direct Mode. If for some reason, the Hyper-V host does not enumerate Direct Mode, output an error message but continue to run using the LAPIC timer instead of an stimer. With these changes, the VMBus driver no longer calls the stimer interrupt service routine. This removal has a broader benefit in unblocking the disentangling of VMBus code and stimer code, as they should be independent of each other. The final disentangling will come as a follow-on patch set. Signed-off-by: Michael Kelley Signed-off-by: Wei Liu --- arch/x86/hyperv/hv_init.c | 23 ++--- arch/x86/kernel/cpu/mshyperv.c | 4 +- drivers/clocksource/hyperv_timer.c | 150 ++++------------------------- drivers/hv/hv.c | 4 - drivers/hv/vmbus_drv.c | 10 +- include/clocksource/hyperv_timer.h | 6 -- 6 files changed, 35 insertions(+), 162 deletions(-) diff --git a/arch/x86/hyperv/hv_init.c b/arch/x86/hyperv/hv_init.c index 55a8b6de2865..0b4a1c0b0b16 100644 --- a/arch/x86/hyperv/hv_init.c +++ b/arch/x86/hyperv/hv_init.c @@ -171,8 +171,7 @@ static int hv_cpu_init(unsigned int cpu) } /* Allow Hyper-V stimer vector to be injected from Hypervisor. */ - if (ms_hyperv.misc_features & HV_STIMER_DIRECT_MODE_AVAILABLE) - apic_update_vector(cpu, HYPERV_STIMER0_VECTOR, true); + apic_update_vector(cpu, HYPERV_STIMER0_VECTOR, true); return hyperv_init_ghcb(); } @@ -281,8 +280,7 @@ static int hv_cpu_die(unsigned int cpu) *ghcb_va = NULL; } - if (ms_hyperv.misc_features & HV_STIMER_DIRECT_MODE_AVAILABLE) - apic_update_vector(cpu, HYPERV_STIMER0_VECTOR, false); + apic_update_vector(cpu, HYPERV_STIMER0_VECTOR, false); hv_common_cpu_die(cpu); @@ -425,15 +423,18 @@ static void (* __initdata old_setup_percpu_clockev)(void); static void __init hv_stimer_setup_percpu_clockev(void) { - /* - * Ignore any errors in setting up stimer clockevents - * as we can run with the LAPIC timer as a fallback. - */ - (void)hv_stimer_alloc(false); + int ret; /* - * Still register the LAPIC timer, because the direct-mode STIMER is - * not supported by old versions of Hyper-V. This also allows users + * Continue afters errors in setting up stimer clockevents + * as we can run with the LAPIC timer as a fallback. + */ + ret = hv_stimer_alloc(false); + if (ret) + pr_warn("stimer setup failed with error %d\n", ret); + + /* + * Still register the LAPIC timer to allows users * to switch to LAPIC timer via /sys, if they want to. */ if (old_setup_percpu_clockev) diff --git a/arch/x86/kernel/cpu/mshyperv.c b/arch/x86/kernel/cpu/mshyperv.c index 229c7377a980..b4af7c0a70ac 100644 --- a/arch/x86/kernel/cpu/mshyperv.c +++ b/arch/x86/kernel/cpu/mshyperv.c @@ -731,9 +731,7 @@ static void __init ms_hyperv_init_platform(void) } /* Install system interrupt handler for stimer0 */ - if (ms_hyperv.misc_features & HV_STIMER_DIRECT_MODE_AVAILABLE) { - sysvec_install(HYPERV_STIMER0_VECTOR, sysvec_hyperv_stimer0); - } + sysvec_install(HYPERV_STIMER0_VECTOR, sysvec_hyperv_stimer0); # ifdef CONFIG_SMP smp_ops.smp_prepare_boot_cpu = hv_smp_prepare_boot_cpu; diff --git a/drivers/clocksource/hyperv_timer.c b/drivers/clocksource/hyperv_timer.c index df567795d175..dddfff458ebf 100644 --- a/drivers/clocksource/hyperv_timer.c +++ b/drivers/clocksource/hyperv_timer.c @@ -31,44 +31,20 @@ static struct clock_event_device __percpu *hv_clock_event; /* Note: offset can hold negative values after hibernation. */ static u64 hv_sched_clock_offset __read_mostly; -/* - * If false, we're using the old mechanism for stimer0 interrupts - * where it sends a VMbus message when it expires. The old - * mechanism is used when running on older versions of Hyper-V - * that don't support Direct Mode. While Hyper-V provides - * four stimer's per CPU, Linux uses only stimer0. - * - * Because Direct Mode does not require processing a VMbus - * message, stimer interrupts can be enabled earlier in the - * process of booting a CPU, and consistent with when timer - * interrupts are enabled for other clocksource drivers. - * However, for legacy versions of Hyper-V when Direct Mode - * is not enabled, setting up stimer interrupts must be - * delayed until VMbus is initialized and can process the - * interrupt message. - */ -static bool direct_mode_enabled; - static int stimer0_irq = -1; -static int stimer0_message_sint; static __maybe_unused DEFINE_PER_CPU(long, stimer0_evt); -/* - * Common code for stimer0 interrupts coming via Direct Mode or - * as a VMbus message. - */ -void hv_stimer0_isr(void) +static void hv_stimer0_isr(void) { struct clock_event_device *ce; ce = this_cpu_ptr(hv_clock_event); ce->event_handler(ce); } -EXPORT_SYMBOL_GPL(hv_stimer0_isr); /* * stimer0 interrupt handler for architectures that support - * per-cpu interrupts, which also implies Direct Mode. + * per-cpu interrupts */ static irqreturn_t __maybe_unused hv_stimer0_percpu_isr(int irq, void *dev_id) { @@ -91,7 +67,7 @@ static int hv_ce_shutdown(struct clock_event_device *evt) { hv_set_msr(HV_MSR_STIMER0_COUNT, 0); hv_set_msr(HV_MSR_STIMER0_CONFIG, 0); - if (direct_mode_enabled && stimer0_irq >= 0) + if (stimer0_irq >= 0) disable_percpu_irq(stimer0_irq); return 0; @@ -104,23 +80,16 @@ static int hv_ce_set_oneshot(struct clock_event_device *evt) timer_cfg.as_uint64 = 0; timer_cfg.enable = 1; timer_cfg.auto_enable = 1; - if (direct_mode_enabled) { - /* - * When it expires, the timer will directly interrupt - * on the specified hardware vector/IRQ. - */ - timer_cfg.direct_mode = 1; - timer_cfg.apic_vector = HYPERV_STIMER0_VECTOR; - if (stimer0_irq >= 0) - enable_percpu_irq(stimer0_irq, IRQ_TYPE_NONE); - } else { - /* - * When it expires, the timer will generate a VMbus message, - * to be handled by the normal VMbus interrupt handler. - */ - timer_cfg.direct_mode = 0; - timer_cfg.sintx = stimer0_message_sint; - } + + /* + * When it expires, the timer will directly interrupt + * on the specified hardware vector/IRQ. + */ + timer_cfg.direct_mode = 1; + timer_cfg.apic_vector = HYPERV_STIMER0_VECTOR; + if (stimer0_irq >= 0) + enable_percpu_irq(stimer0_irq, IRQ_TYPE_NONE); + hv_set_msr(HV_MSR_STIMER0_CONFIG, timer_cfg.as_uint64); return 0; } @@ -175,25 +144,8 @@ int hv_stimer_cleanup(unsigned int cpu) if (!hv_clock_event) return 0; - /* - * In the legacy case where Direct Mode is not enabled - * (which can only be on x86/64), stimer cleanup happens - * relatively early in the CPU offlining process. We - * must unbind the stimer-based clockevent device so - * that the LAPIC timer can take over until clockevents - * are no longer needed in the offlining process. Note - * that clockevents_unbind_device() eventually calls - * hv_ce_shutdown(). - * - * The unbind should not be done when Direct Mode is - * enabled because we may be on an architecture where - * there are no other clockevent devices to fallback to. - */ ce = per_cpu_ptr(hv_clock_event, cpu); - if (direct_mode_enabled) - hv_ce_shutdown(ce); - else - clockevents_unbind_device(ce, cpu); + hv_ce_shutdown(ce); return 0; } @@ -268,23 +220,14 @@ int hv_stimer_alloc(bool have_percpu_irqs) * Hyper-V on x86. In that case, return as error as Linux will use a * clockevent based on emulated LAPIC timer hardware. */ - if (!(ms_hyperv.features & HV_MSR_SYNTIMER_AVAILABLE)) + if (!(ms_hyperv.features & HV_MSR_SYNTIMER_AVAILABLE) || + !(ms_hyperv.misc_features & HV_STIMER_DIRECT_MODE_AVAILABLE)) return -EINVAL; hv_clock_event = alloc_percpu(struct clock_event_device); if (!hv_clock_event) return -ENOMEM; - direct_mode_enabled = ms_hyperv.misc_features & - HV_STIMER_DIRECT_MODE_AVAILABLE; - - /* - * If Direct Mode isn't enabled, the remainder of the initialization - * is done later by hv_stimer_legacy_init() - */ - if (!direct_mode_enabled) - return 0; - if (have_percpu_irqs) { ret = hv_setup_stimer0_irq(); if (ret) @@ -293,11 +236,6 @@ int hv_stimer_alloc(bool have_percpu_irqs) hv_setup_stimer0_handler(hv_stimer0_isr); } - /* - * Since we are in Direct Mode, stimer initialization - * can be done now with a CPUHP value in the same range - * as other clockevent devices. - */ ret = cpuhp_setup_state(CPUHP_AP_HYPERV_TIMER_STARTING, "clockevents/hyperv/stimer:starting", hv_stimer_init, hv_stimer_cleanup); @@ -314,67 +252,19 @@ int hv_stimer_alloc(bool have_percpu_irqs) } EXPORT_SYMBOL_GPL(hv_stimer_alloc); -/* - * hv_stimer_legacy_init -- Called from the VMbus driver to handle - * the case when Direct Mode is not enabled, and the stimer - * must be initialized late in the CPU onlining process. - * - */ -void hv_stimer_legacy_init(unsigned int cpu, int sint) -{ - if (direct_mode_enabled) - return; - - /* - * This function gets called by each vCPU, so setting the - * global stimer_message_sint value each time is conceptually - * not ideal, but the value passed in is always the same and - * it avoids introducing yet another interface into this - * clocksource driver just to set the sint in the legacy case. - */ - stimer0_message_sint = sint; - (void)hv_stimer_init(cpu); -} -EXPORT_SYMBOL_GPL(hv_stimer_legacy_init); - -/* - * hv_stimer_legacy_cleanup -- Called from the VMbus driver to - * handle the case when Direct Mode is not enabled, and the - * stimer must be cleaned up early in the CPU offlining - * process. - */ -void hv_stimer_legacy_cleanup(unsigned int cpu) -{ - if (direct_mode_enabled) - return; - (void)hv_stimer_cleanup(cpu); -} -EXPORT_SYMBOL_GPL(hv_stimer_legacy_cleanup); - /* * Do a global cleanup of clockevents for the cases of kexec and * vmbus exit */ void hv_stimer_global_cleanup(void) { - int cpu; - - /* - * hv_stime_legacy_cleanup() will stop the stimer if Direct - * Mode is not enabled, and fallback to the LAPIC timer. - */ - for_each_present_cpu(cpu) { - hv_stimer_legacy_cleanup(cpu); - } - if (!hv_clock_event) return; - if (direct_mode_enabled) { - cpuhp_remove_state(CPUHP_AP_HYPERV_TIMER_STARTING); - hv_remove_stimer0_irq(); - stimer0_irq = -1; - } + cpuhp_remove_state(CPUHP_AP_HYPERV_TIMER_STARTING); + hv_remove_stimer0_irq(); + stimer0_irq = -1; + free_percpu(hv_clock_event); hv_clock_event = NULL; diff --git a/drivers/hv/hv.c b/drivers/hv/hv.c index ef4b1b03395d..fe50090dcc01 100644 --- a/drivers/hv/hv.c +++ b/drivers/hv/hv.c @@ -399,8 +399,6 @@ int hv_synic_init(unsigned int cpu) else hv_hyp_synic_enable_interrupts(); - hv_stimer_legacy_init(cpu, VMBUS_MESSAGE_SINT); - return 0; } @@ -630,8 +628,6 @@ int hv_synic_cleanup(unsigned int cpu) return -EBUSY; always_cleanup: - hv_stimer_legacy_cleanup(cpu); - /* * First, disable the event and message pages * used for communicating with the host, and then diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index 18ee549d9880..5ebdbe24b5a1 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c @@ -1320,14 +1320,8 @@ static void vmbus_message_sched(struct hv_per_cpu_context *hv_cpu, void *message msg = (struct hv_message *)message_page_addr + VMBUS_MESSAGE_SINT; /* Check if there are actual msgs to be processed */ - if (msg->header.message_type != HVMSG_NONE) { - if (msg->header.message_type == HVMSG_TIMER_EXPIRED) { - hv_stimer0_isr(); - vmbus_signal_eom(msg, HVMSG_TIMER_EXPIRED); - } else { - tasklet_schedule(&hv_cpu->msg_dpc); - } - } + if (msg->header.message_type != HVMSG_NONE) + tasklet_schedule(&hv_cpu->msg_dpc); } static void __vmbus_isr(void) diff --git a/include/clocksource/hyperv_timer.h b/include/clocksource/hyperv_timer.h index d48dd4176fd3..8d3befb7e667 100644 --- a/include/clocksource/hyperv_timer.h +++ b/include/clocksource/hyperv_timer.h @@ -27,10 +27,7 @@ /* Routines called by the VMbus driver */ extern int hv_stimer_alloc(bool have_percpu_irqs); extern int hv_stimer_cleanup(unsigned int cpu); -extern void hv_stimer_legacy_init(unsigned int cpu, int sint); -extern void hv_stimer_legacy_cleanup(unsigned int cpu); extern void hv_stimer_global_cleanup(void); -extern void hv_stimer0_isr(void); extern void hv_init_clocksource(void); extern void hv_remap_tsc_clocksource(void); @@ -107,10 +104,7 @@ hv_read_tsc_page_tsc(const struct ms_hyperv_tsc_page *tsc_pg, u64 *cur_tsc, u64 } static inline int hv_stimer_cleanup(unsigned int cpu) { return 0; } -static inline void hv_stimer_legacy_init(unsigned int cpu, int sint) {} -static inline void hv_stimer_legacy_cleanup(unsigned int cpu) {} static inline void hv_stimer_global_cleanup(void) {} -static inline void hv_stimer0_isr(void) {} #endif /* CONFIG_HYPERV_TIMER */ From d34a88f53a7ab0a4d84fbff137c9077701e95b06 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 14 Aug 2026 16:20:08 +0000 Subject: [PATCH 0873/1328] scsi: core: Enable context analysis for hosts.o Enable compiler-based context analysis for drivers/scsi/hosts.c by setting CONTEXT_ANALYSIS_hosts.o := y in drivers/scsi/Makefile. The SCSI host management code in hosts.c now has the necessary lock context annotations (such as __must_hold(shost->host_lock) on scsi_host_set_state) and conforms to compile-time lock checking rules. It builds cleanly without triggering any context analysis warnings. Enable context analysis for hosts.o so that lock correctness and context safety invariants for SCSI host operations are verified at compile time when CONFIG_WARN_CONTEXT_ANALYSIS is enabled. Fixes: fb0fc67db962 ("scsi: core: Enable context analysis") Reported-by: John Garry Signed-off-by: Bart Van Assche Reviewed-by: John Garry Link: https://patch.msgid.link/3e1c3c0ca9307e2581cf4b96cf3fcdae35202255.1786724393.git.bvanassche@acm.org Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/scsi/Makefile b/drivers/scsi/Makefile index 72eb395ccf1d..533623382eca 100644 --- a/drivers/scsi/Makefile +++ b/drivers/scsi/Makefile @@ -15,6 +15,7 @@ # *!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*! CONTEXT_ANALYSIS_constants.o := y +CONTEXT_ANALYSIS_hosts.o := y CONTEXT_ANALYSIS_scsi.o := y CONTEXT_ANALYSIS_scsi_common.o := y CONTEXT_ANALYSIS_scsi_devinfo.o := y From b79b88b655a84187aed12d773566dabf5ab72ed6 Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Mon, 17 Aug 2026 12:04:55 -0700 Subject: [PATCH 0874/1328] scsi: ibmvfc: Fix use of uninitialized rport in ibmvfc_do_work() After commit 696d1cc2aaa2 ("scsi: ibmvfc: process NVMe/FC rports in work thread"), clang warns (or errors with CONFIG_WERROR=y / W=e): drivers/scsi/ibmvscsi/ibmvfc-core.c:6154:15: error: variable 'rport' is uninitialized when used here [-Werror,-Wuninitialized] 6154 | } else if (rport && tgt->action == IBMVFC_TGT_ACTION_DEL_AND_LOGOUT_RPORT) { | ^~~~~ The check for rport is unnecessary in this block, it was accidentally included from copying and pasting. Remove it to clear up the warning. Fixes: 696d1cc2aaa2 ("scsi: ibmvfc: process NVMe/FC rports in work thread") Suggested-by: Tyrel Datwyler Link: https://lore.kernel.org/6ccbe8c5-beb6-483f-bfa4-c2d3819ad5f2@linux.ibm.com/ Signed-off-by: Nathan Chancellor Acked-by: Tyrel Datwyler Link: https://patch.msgid.link/20260817-ibmvscsi-rport-wuninitialized-v1-1-0fdfb27a5f01@kernel.org Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/ibmvscsi/ibmvfc-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c index 93b9f699c2e5..b3bc3ce872d6 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc-core.c +++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c @@ -6151,7 +6151,7 @@ static void ibmvfc_do_work(struct ibmvfc_host *vhost) timer_delete_sync(&tgt->timer); kref_put(&tgt->kref, ibmvfc_release_tgt); return; - } else if (rport && tgt->action == IBMVFC_TGT_ACTION_DEL_AND_LOGOUT_RPORT) { + } else if (tgt->action == IBMVFC_TGT_ACTION_DEL_AND_LOGOUT_RPORT) { tgt_dbg(tgt, "Deleting NVMe rport with outstanding I/O\n"); nvme_rport = tgt->nvme_remote_port; ibmvfc_set_tgt_action(tgt, IBMVFC_TGT_ACTION_LOGOUT_DELETED_RPORT); From ff9365a4c9991cca68fd48c7729808f2fdae2036 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 13 Aug 2026 10:08:27 +0300 Subject: [PATCH 0875/1328] scsi: qla2xxx: Fix an error code in qla_get_tmf() Negative -EIO was intended instead of positive EIO. The caller, doesn't care so this doesn't affect runtime. It's just a cleanup. Signed-off-by: Dan Carpenter Link: https://patch.msgid.link/an1taxANE_4_vzJT@stanley.mountain Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/qla2xxx/qla_init.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/scsi/qla2xxx/qla_init.c b/drivers/scsi/qla2xxx/qla_init.c index 900cd141928e..bb0cc71de37b 100644 --- a/drivers/scsi/qla2xxx/qla_init.c +++ b/drivers/scsi/qla2xxx/qla_init.c @@ -2302,7 +2302,7 @@ int qla_get_tmf(struct tmf_arg *arg) if (TMF_NOT_READY(fcport)) { ql_log(ql_log_warn, vha, 0x802c, "Unable to acquire TM resource due to disruption.\n"); - rc = EIO; + rc = -EIO; break; } if (ha->active_tmf < MAX_ACTIVE_TMF && From 11e48f5201fd86ffa038809dd9c41144d43ee2e4 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 13 Aug 2026 10:09:53 +0300 Subject: [PATCH 0876/1328] scsi: qla2xxx: Fix an loop timeout test This loop timeout with "retries" set to -1, not 0. Fix the test for failure. Fixes: 7ec0effd30bb ("[SCSI] qla2xxx: Add support for ISP8044.") Signed-off-by: Dan Carpenter Link: https://patch.msgid.link/an1twcxTYSFkkUTA@stanley.mountain Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/qla2xxx/qla_nx2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/scsi/qla2xxx/qla_nx2.c b/drivers/scsi/qla2xxx/qla_nx2.c index 41ff6fbdb933..04d7ab6ad037 100644 --- a/drivers/scsi/qla2xxx/qla_nx2.c +++ b/drivers/scsi/qla2xxx/qla_nx2.c @@ -3507,7 +3507,7 @@ qla8044_poll_flash_status_reg(struct scsi_qla_host *vha) msleep(QLA8044_FLASH_STATUS_REG_POLL_DELAY); } - if (!retries) + if (retries == -1) ret_val = QLA_FUNCTION_FAILED; return ret_val; From 0ec418204f23f0a1dfff79d5f6721080ec006042 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 13 Aug 2026 10:09:32 +0300 Subject: [PATCH 0877/1328] scsi: lpfc: Remove unnnecessary NULL check The "evt_dat" variale is non-NULL at this point so there is no need to check. Delete the check and pull the code in a tab. Signed-off-by: Dan Carpenter Reviewed-by: Paul Ely Link: https://patch.msgid.link/an1trOAUeQmYEus_@stanley.mountain Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/lpfc/lpfc_bsg.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/scsi/lpfc/lpfc_bsg.c b/drivers/scsi/lpfc/lpfc_bsg.c index c95165905483..7354ae9ba8e5 100644 --- a/drivers/scsi/lpfc/lpfc_bsg.c +++ b/drivers/scsi/lpfc/lpfc_bsg.c @@ -1329,10 +1329,8 @@ lpfc_bsg_hba_get_event(struct bsg_job *job) else bsg_reply->reply_payload_rcv_len = 0; - if (evt_dat) { - kfree(evt_dat->data); - kfree(evt_dat); - } + kfree(evt_dat->data); + kfree(evt_dat); spin_lock_irqsave(&phba->ct_ev_lock, flags); lpfc_bsg_event_unref(evt); From 970f69b6bf71562df5afcefb89c77b4e971d68de Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Fri, 14 Aug 2026 11:38:44 +0800 Subject: [PATCH 0878/1328] scsi: leapraid: Balance host references for firmware log VMAs leapraid_fw_mmap() keeps the Scsi_Host reference obtained while looking up the adapter for the lifetime of the initial VMA. The VMA close callback drops that reference. The open callback is also invoked when a VMA is duplicated or split, but it only increments mmap_refcnt. Since every corresponding close callback drops a host reference, cloning the mapping can release the host while another VMA still refers to the adapter. Take a host device reference for every VMA open and release the lookup reference once the initial mapping has acquired its own reference. Use get_device() because a VMA can be cloned after the host enters SHOST_DEL; an existing VMA still pins the host at that point and open cannot fail. Fixes: 5597088c9e79 ("scsi: leapraid: Add new SCSI driver") Signed-off-by: Linmao Li Reviewed-by: Dongdong Hao Link: https://patch.msgid.link/20260814033845.2971706-2-lilinmao@kylinos.cn Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/leapraid/leapraid_app.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/scsi/leapraid/leapraid_app.c b/drivers/scsi/leapraid/leapraid_app.c index 742f19c07fcb..841027ce2501 100644 --- a/drivers/scsi/leapraid/leapraid_app.c +++ b/drivers/scsi/leapraid/leapraid_app.c @@ -697,6 +697,7 @@ static void leapraid_fw_mmap_open(struct vm_area_struct *vma) if (!adapter) return; + get_device(&adapter->shost->shost_gendev); atomic_inc(&adapter->fw_log_desc.mmap_refcnt); } @@ -767,7 +768,6 @@ static int leapraid_fw_mmap(struct file *filp, struct vm_area_struct *vma) vma->vm_private_data = adapter; vma->vm_ops = &leapraid_fw_mmap_vm_ops; leapraid_fw_mmap_open(vma); - adapter = NULL; rc = 0; out_put: From 00b7c8d4ce441aa9ac704840332ba4738e1c6d51 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Fri, 14 Aug 2026 11:38:45 +0800 Subject: [PATCH 0879/1328] scsi: leapraid: Serialize firmware log mmap with teardown leapraid_fw_log_exit() waits for mmap_refcnt to reach zero before it frees the firmware log buffer. leapraid_fw_mmap() checks host_removing, but it does not increment mmap_refcnt until after dma_mmap_coherent() succeeds and the VMA open callback runs. Removal can set host_removing and observe a zero mmap_refcnt between the check and the VMA open. It can then free the coherent buffer while the mmap path is still establishing a userspace mapping of it. Claim a temporary mmap reference while looking up the adapter under leapraid_adapter_lock. Removal deletes the adapter from the same locked list after setting host_removing, so a mapping is either rejected or included in the count that removal waits for. Drop the temporary reference on the common exit path, after a successful VMA open has acquired the reference covering the VMA lifetime. Fixes: 5597088c9e79 ("scsi: leapraid: Add new SCSI driver") Signed-off-by: Linmao Li Reviewed-by: Dongdong Hao Link: https://patch.msgid.link/20260814033845.2971706-3-lilinmao@kylinos.cn Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/leapraid/leapraid_app.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/scsi/leapraid/leapraid_app.c b/drivers/scsi/leapraid/leapraid_app.c index 841027ce2501..84eef50947ae 100644 --- a/drivers/scsi/leapraid/leapraid_app.c +++ b/drivers/scsi/leapraid/leapraid_app.c @@ -171,7 +171,8 @@ static int leapraid_ctl_validate_sge_offset(struct leapraid_adapter *adapter, return 0; } -static struct leapraid_adapter *leapraid_ctl_lookup_adapter(int adapter_id) +static struct leapraid_adapter *leapraid_ctl_lookup_adapter(int adapter_id, + bool track_mmap) { struct leapraid_adapter *adapter; struct Scsi_Host *shost; @@ -184,6 +185,8 @@ static struct leapraid_adapter *leapraid_ctl_lookup_adapter(int adapter_id) shost = adapter->shost; if (!shost || !scsi_host_get(shost)) break; + if (track_mmap) + atomic_inc(&adapter->fw_log_desc.mmap_refcnt); spin_unlock(&leapraid_adapter_lock); return adapter; } @@ -589,7 +592,7 @@ static int leapraid_ctl_ioctl_main(struct file *file, unsigned int cmd, return -EFAULT; } - adapter = leapraid_ctl_lookup_adapter(ioctl_header.adapter_id); + adapter = leapraid_ctl_lookup_adapter(ioctl_header.adapter_id, false); if (!adapter) return -EFAULT; @@ -728,7 +731,7 @@ static int leapraid_fw_mmap(struct file *filp, struct vm_area_struct *vma) length = vma->vm_end - vma->vm_start; - adapter = leapraid_ctl_lookup_adapter(adapter_id); + adapter = leapraid_ctl_lookup_adapter(adapter_id, true); if (!adapter) { pr_err("%s: No adapter found!\n", __func__); return -EINVAL; @@ -771,6 +774,9 @@ static int leapraid_fw_mmap(struct file *filp, struct vm_area_struct *vma) rc = 0; out_put: + if (adapter && + atomic_dec_and_test(&adapter->fw_log_desc.mmap_refcnt)) + wake_up(&adapter->fw_log_desc.mmap_waitq); leapraid_ctl_put_adapter(adapter); return rc; } From 46f861d300e87283f81faf9ee377d1e73682903d Mon Sep 17 00:00:00 2001 From: Dongdong Hao Date: Fri, 14 Aug 2026 17:05:26 +0800 Subject: [PATCH 0880/1328] scsi: leapraid: Standardize NCQ priority sysfs attributes Replace the earlier LeapRAID ncq_cmd_prio_enable attribute with the standard sas_ncq_prio_supported and sas_ncq_prio_enable names documented in Documentation/ABI/testing/sysfs-block-device, and rename the per-device NCQ priority state to match. The earlier ncq_cmd_prio_enable name has not yet been established as part of a released userspace ABI, so no compatibility alias is needed. For LeapRAID, sas_ncq_prio_enable is backed by the driver's per-device NCQ priority state and controls whether RT-class I/O requests are issued with command priority on supported SATA devices. Update leapraid.rst to describe the standard attribute names and paths, and clean up the surrounding RST text for consistency with kernel documentation style. Also switch the capability check from open-coded VPD page 0x89 parsing to sas_ata_ncq_prio_supported(), use kstrtobool() for the enable path, and expose the NCQ priority attributes only for SATA devices using LeapRAID's target-private SAS device state. Reviewed-by: Damien Le Moal Signed-off-by: Dongdong Hao Reviewed-by: Hannes Reinecke Link: https://patch.msgid.link/20260814090526.395704-1-doubled@leap-io-kernel.com Signed-off-by: Martin K. Petersen (Oracle) --- Documentation/scsi/leapraid.rst | 14 +++-- drivers/scsi/leapraid/leapraid_func.h | 4 +- drivers/scsi/leapraid/leapraid_os.c | 87 +++++++++++++++++---------- 3 files changed, 68 insertions(+), 37 deletions(-) diff --git a/Documentation/scsi/leapraid.rst b/Documentation/scsi/leapraid.rst index 99930ce2b8d0..d36ff627d9f0 100644 --- a/Documentation/scsi/leapraid.rst +++ b/Documentation/scsi/leapraid.rst @@ -22,6 +22,7 @@ Supported devices Features ======== + - PCIe Gen4 x8 host interface - Support for SAS and SATA devices - RAID levels: 0, 1, 10, 5, 50, 6, 60 @@ -50,16 +51,20 @@ LeapRAID specific disk attributes :: - /sys/class/scsi_disk/host:bus:target:lun/device/sas_device_handle - /sys/class/scsi_disk/host:bus:target:lun/device/ncq_cmd_prio_enable + /sys/block//device/sas_device_handle + /sys/block//device/sas_ncq_prio_supported + /sys/block//device/sas_ncq_prio_enable The read-only attribute "sas_device_handle" represents the disk's device handle, which is a unique identifier maintained by the firmware. -This attribute "ncq_cmd_prio_enable" controls NCQ command priority. A value +The read-only attribute "sas_ncq_prio_supported" reports whether a SATA +device supports NCQ command priority. + +The attribute "sas_ncq_prio_enable" controls NCQ command priority. A value of 0 disables NCQ priority handling for RT-priority I/O. Writing 1 enables NCQ priority handling when the device reports support for the feature through -VPD page 0x89. Unsupported devices keep the effective state at 0. +VPD page 0x89. Writes to unsupported devices fail with an error. LeapRAID module parameters ========================== @@ -100,6 +105,7 @@ in io_uring poll mode. The default value is 0. File Location ============= + The driver source is located at: ``drivers/scsi/leapraid/`` diff --git a/drivers/scsi/leapraid/leapraid_func.h b/drivers/scsi/leapraid/leapraid_func.h index 923596211aa1..4c0b9ca728d8 100644 --- a/drivers/scsi/leapraid/leapraid_func.h +++ b/drivers/scsi/leapraid/leapraid_func.h @@ -1064,7 +1064,7 @@ struct leapraid_starget_priv { * @starget_priv: Associated target private data. * @lun: Logical Unit Number. * @flg: Flags. - * @ncq_cmd_prio_enable: Enables NCQ command priority for RT I/O. + * @ncq_prio_enable: Enables NCQ command priority for RT I/O. * @block: Block flag. * @deleted: Deletion flag. * @sep: SEP flag. @@ -1073,7 +1073,7 @@ struct leapraid_sdev_priv { struct leapraid_starget_priv *starget_priv; unsigned int lun; u32 flg; - u8 ncq_cmd_prio_enable; + u8 ncq_prio_enable; u8 block; u8 deleted; u8 sep; diff --git a/drivers/scsi/leapraid/leapraid_os.c b/drivers/scsi/leapraid/leapraid_os.c index a8e1c9f33896..ee3242779dfd 100644 --- a/drivers/scsi/leapraid/leapraid_os.c +++ b/drivers/scsi/leapraid/leapraid_os.c @@ -859,7 +859,7 @@ static u32 build_scsiio_req_control(struct scsi_cmnd *scmd, control |= LEAPRAID_SCSIIO_CTRL_SIMPLEQ; - if (sdev_priv->ncq_cmd_prio_enable && + if (sdev_priv->ncq_prio_enable && (IOPRIO_PRIO_CLASS(req_get_ioprio(scsi_cmd_to_rq(scmd))) == IOPRIO_CLASS_RT)) control |= LEAPRAID_SCSIIO_CTRL_CMDPRI; @@ -1854,7 +1854,16 @@ static ssize_t sas_device_handle_show(struct device *dev, sas_device_priv_data->starget_priv->hdl); } -static ssize_t ncq_cmd_prio_enable_show(struct device *dev, +static ssize_t sas_ncq_prio_supported_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct scsi_device *sdev = to_scsi_device(dev); + + return sysfs_emit(buf, "%d\n", sas_ata_ncq_prio_supported(sdev)); +} + +static ssize_t sas_ncq_prio_enable_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -1867,19 +1876,16 @@ static ssize_t ncq_cmd_prio_enable_show(struct device *dev, return -EINVAL; } - return sysfs_emit(buf, "%d\n", - sas_device_priv_data->ncq_cmd_prio_enable); + return sysfs_emit(buf, "%d\n", sas_device_priv_data->ncq_prio_enable); } -static ssize_t ncq_cmd_prio_enable_store(struct device *dev, +static ssize_t sas_ncq_prio_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct scsi_device *sdev = to_scsi_device(dev); struct leapraid_sdev_priv *sas_device_priv_data = sdev->hostdata; - struct scsi_vpd *vpd_pg89; - int ncq_cmd_prio_enable; - bool ncq_supported; + bool enable; if (!sas_device_priv_data) { dev_err(&sdev->sdev_gendev, @@ -1887,44 +1893,63 @@ static ssize_t ncq_cmd_prio_enable_store(struct device *dev, return -EINVAL; } - if (kstrtoint(buf, 0, &ncq_cmd_prio_enable)) + if (kstrtobool(buf, &enable)) return -EINVAL; - if (ncq_cmd_prio_enable != 0 && ncq_cmd_prio_enable != 1) { - dev_err(&sdev->sdev_gendev, - "%s: Invalid NCQ cmd prio %d (0/1 only)\n", - __func__, ncq_cmd_prio_enable); + if (!sas_ata_ncq_prio_supported(sdev)) return -EINVAL; - } - rcu_read_lock(); - vpd_pg89 = rcu_dereference(sdev->vpd_pg89); - if (!vpd_pg89 || vpd_pg89->len < LEAPRAID_VPD_PG89_MIN_LEN) { - rcu_read_unlock(); - return -EINVAL; - } - - ncq_supported = (vpd_pg89->data[LEAPRAID_VPD_PG89_NCQ_BYTE_IDX] >> - LEAPRAID_VPD_PG89_NCQ_BIT_SHIFT) & - LEAPRAID_VPD_PG89_NCQ_BIT_MASK; - rcu_read_unlock(); - if (ncq_supported) - sas_device_priv_data->ncq_cmd_prio_enable = - ncq_cmd_prio_enable; + sas_device_priv_data->ncq_prio_enable = enable; return count; } static DEVICE_ATTR_RO(sas_device_handle); +static DEVICE_ATTR_RO(sas_ncq_prio_supported); +static DEVICE_ATTR_RW(sas_ncq_prio_enable); -static DEVICE_ATTR_RW(ncq_cmd_prio_enable); +static bool leapraid_sdev_is_sata(struct scsi_device *sdev) +{ + struct scsi_target *starget = sdev->sdev_target; + struct leapraid_starget_priv *starget_priv = starget->hostdata; + struct leapraid_sas_dev *sas_dev; + + if (!starget_priv) + return false; + + sas_dev = starget_priv->sas_dev; + return sas_dev && (sas_dev->dev_info & LEAPRAID_DEVTYP_SATA_DEV); +} static struct attribute *leapraid_sdev_attrs[] = { &dev_attr_sas_device_handle.attr, - &dev_attr_ncq_cmd_prio_enable.attr, + &dev_attr_sas_ncq_prio_supported.attr, + &dev_attr_sas_ncq_prio_enable.attr, NULL, }; -ATTRIBUTE_GROUPS(leapraid_sdev); +static umode_t leapraid_sdev_attr_is_visible(struct kobject *kobj, + struct attribute *attr, int i) +{ + struct device *dev = kobj_to_dev(kobj); + struct scsi_device *sdev = to_scsi_device(dev); + + if (attr == &dev_attr_sas_ncq_prio_supported.attr || + attr == &dev_attr_sas_ncq_prio_enable.attr) + if (!leapraid_sdev_is_sata(sdev)) + return 0; + + return attr->mode; +} + +static const struct attribute_group leapraid_sdev_attr_group = { + .attrs = leapraid_sdev_attrs, + .is_visible = leapraid_sdev_attr_is_visible, +}; + +static const struct attribute_group *leapraid_sdev_groups[] = { + &leapraid_sdev_attr_group, + NULL, +}; static struct scsi_host_template leapraid_driver_template = { .module = THIS_MODULE, From 764587d7d76846716a568dea7ae48be1c50ecc18 Mon Sep 17 00:00:00 2001 From: Finn Thain Date: Tue, 18 Aug 2026 11:00:24 +1000 Subject: [PATCH 0881/1328] scsi: MAINTAINERS: Leave the cumana_1 and oak drivers to the RISCPC maintainers The NCR5380 entry in MAINTAINERS includes drivers/scsi/arm/cumana_1.c and drivers/scsi/arm/oak.c. However, those two files are also covered by the drivers/scsi/arm/ pathname in the ARM/RISCPC entry. The latter entry is more effective than the former because, AIUI, neither Michael nor I have access to the necessary hardware. IMHO, such access is a pre-requisite for the 'maintainer' role for device drivers. To work on these particular drivers would require an old GCC compiler, having support for -march=armv3m, which is a problem for contributors. Cc: Michael Schmitz Cc: Russell King Cc: Arnd Bergmann Signed-off-by: Finn Thain Acked-by: Michael Schmitz Link: https://patch.msgid.link/935b08c0fb292888c06c2233570331f2ccadcd53.1787014824.git.fthain@linux-m68k.org Signed-off-by: Martin K. Petersen (Oracle) --- MAINTAINERS | 2 -- 1 file changed, 2 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index a8a58f059c49..95fd1a961cd5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18576,8 +18576,6 @@ L: linux-scsi@vger.kernel.org S: Maintained F: Documentation/scsi/g_NCR5380.rst F: drivers/scsi/NCR5380.* -F: drivers/scsi/arm/cumana_1.c -F: drivers/scsi/arm/oak.c F: drivers/scsi/atari_scsi.* F: drivers/scsi/dmx3191d.c F: drivers/scsi/g_NCR5380.* From df125bd16280b19835bfa2eef6504b505790f1f6 Mon Sep 17 00:00:00 2001 From: "Martin K. Petersen (Oracle)" Date: Sun, 23 Aug 2026 22:08:18 -0400 Subject: [PATCH 0882/1328] scsi: MAINTAINERS: Update my email address Use my kernel.org address for Linux development. Signed-off-by: Martin K. Petersen (Oracle) --- .mailmap | 3 +++ MAINTAINERS | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.mailmap b/.mailmap index 23eb9a4b04f4..e484232b32a1 100644 --- a/.mailmap +++ b/.mailmap @@ -553,6 +553,9 @@ Mark Brown Mark Starovoytov Markus Schneider-Pargmann Mark Yao +Martin K. Petersen +Martin K. Petersen +Martin K. Petersen Martin Kepplinger-Novakovic Martyna Szapar-Mudlaw Mathieu Othacehe diff --git a/MAINTAINERS b/MAINTAINERS index 95fd1a961cd5..9c768040658d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -24291,7 +24291,7 @@ F: include/scsi/sg.h SCSI SUBSYSTEM M: "James E.J. Bottomley" -M: "Martin K. Petersen" +M: "Martin K. Petersen" L: linux-scsi@vger.kernel.org S: Maintained Q: https://patchwork.kernel.org/project/linux-scsi/list/ @@ -24321,7 +24321,7 @@ F: drivers/target/target_core_user.c F: include/uapi/linux/target_core_user.h SCSI TARGET SUBSYSTEM -M: "Martin K. Petersen" +M: "Martin K. Petersen" L: linux-scsi@vger.kernel.org L: target-devel@vger.kernel.org S: Supported From 12e67eb89eb2b9516685c744d3f7de0a2d1bd701 Mon Sep 17 00:00:00 2001 From: Chen Changcheng Date: Mon, 27 Jul 2026 15:34:38 +0800 Subject: [PATCH 0883/1328] scsi: snic: Fix SCSI host leak on workqueue allocation failure In snic_add_host(), if scsi_add_host() succeeds but alloc_ordered_workqueue() fails, the function returns -ENOMEM with shost->work_q left as NULL. The caller's error path then calls snic_del_host(), which returns early when !shost->work_q without calling scsi_remove_host(). The Scsi_Host remains registered in sysfs as a zombie device even after the probe has failed. This causes: - The leaked host remains visible in /sys/class/scsi_host/ after probe failure, with state "running". - Subsequent SCSI host numbering is permanently shifted (the leaked host ID from ida_alloc() is never reclaimed). - Memory leak: the Scsi_Host allocation can never be freed because device_add() took a reference that can only be released by device_del() inside scsi_remove_host(). Fix by adding scsi_remove_host() in the workqueue allocation failure path inside snic_add_host(), undoing the successful scsi_add_host() before returning the error. This is cleaner than modifying snic_del_host() because snic_del_host() is called from a shared error label that also serves paths where snic_add_host() was never invoked. Reproducer (requires no real SNIC hardware): - Build CONFIG_SCSI_SNIC=y (built-in) - Add snic.test_mode=1 snic.inject_wq_fail=1 to kernel cmdline - Boot with a PCI device matching the snic driver (e.g. QEMU edu device, PCI ID 0x1234:0x11e8, temporarily added to the driver's PCI ID table) Before the fix: # /sys/class/scsi_host/ contains a zombie host0: $ cat /sys/class/scsi_host/host0/proc_name snic_scsi $ cat /sys/class/scsi_host/host0/state running # ata_piix gets host1, host2 (host0 stuck): scsi host1: ata_piix scsi host2: ata_piix After the fix: # host0 is properly freed and reused by ata_piix: scsi host0: ata_piix scsi host1: ata_piix # No zombie host in /sys/class/scsi_host/ Signed-off-by: Chen Changcheng Acked-by: Narsimhulu Musini Link: https://patch.msgid.link/20260727073438.209673-1-chenchangcheng@kylinos.cn Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/snic/snic_main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/scsi/snic/snic_main.c b/drivers/scsi/snic/snic_main.c index 82953e6a0915..cd638b4a4d7b 100644 --- a/drivers/scsi/snic/snic_main.c +++ b/drivers/scsi/snic/snic_main.c @@ -305,6 +305,7 @@ snic_add_host(struct Scsi_Host *shost, struct pci_dev *pdev) if (!shost->work_q) { SNIC_HOST_ERR(shost, "Failed to Create ScsiHost wq.\n"); + scsi_remove_host(shost); ret = -ENOMEM; } From 1035a8f63bae28e498b0e7b5ac91d749844a7158 Mon Sep 17 00:00:00 2001 From: Marouane El Moufid Date: Sun, 23 Aug 2026 13:55:48 +0000 Subject: [PATCH 0884/1328] ALSA: usb-audio: fix OOB write in snd_usbmidi_novation_output() snd_usbmidi_novation_output() lays out a two-byte header at transfer_buffer[0..1] and passes &transfer_buffer[2] together with a length of ep->max_transfer - 2 to snd_rawmidi_transmit(): count = snd_rawmidi_transmit(ep->ports[0].substream, &transfer_buffer[2], ep->max_transfer - 2); ep->max_transfer comes from the output endpoint's wMaxPacketSize via usb_maxpacket(). A malformed or malicious device can advertise a bulk OUT endpoint with a wMaxPacketSize of 1 - the USB core only clamps this value downwards - so ep->max_transfer becomes 1 and the count argument becomes -1. snd_rawmidi_transmit() passes the negative count on to __snd_rawmidi_transmit_peek(), where "if (count1 > count) count1 = count" leaves count1 negative; get_aligned_size() keeps it negative for a byte-stream substream, so the following memcpy(buffer, ..., count1) runs with a (size_t)-1 length and writes far past the transfer buffer, which was allocated with usb_alloc_coherent(ep->max_transfer). This is the same class of bug that was fixed for snd_usbmidi_akai_output() in commit 0970274613fb ("ALSA: usb-audio: fix OOB write in snd_usbmidi_akai_output()"); the novation output routine was left unguarded. Bail out when the endpoint cannot hold the two-byte header plus at least one payload byte. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Marouane El Moufid Link: https://patch.msgid.link/178749334830.543645.13722252148340572274@espilon.net Signed-off-by: Takashi Iwai --- sound/usb/midi.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/usb/midi.c b/sound/usb/midi.c index f8996416c3be..8a9bc37f0b6e 100644 --- a/sound/usb/midi.c +++ b/sound/usb/midi.c @@ -875,6 +875,8 @@ static void snd_usbmidi_novation_output(struct snd_usb_midi_out_endpoint *ep, if (!ep->ports[0].active) return; + if (ep->max_transfer < 3) + return; transfer_buffer = urb->transfer_buffer; count = snd_rawmidi_transmit(ep->ports[0].substream, &transfer_buffer[2], From e72d5659a2606056a0c34af212b46a3275a55bbf Mon Sep 17 00:00:00 2001 From: Eckhart Mohr Date: Fri, 21 Aug 2026 16:44:34 +0200 Subject: [PATCH 0885/1328] ALSA: hda/realtek: Add quirk for TongFang XxAF5xxx Fix microphone detection on built in headphone jack for some devices Signed-off-by: Eckhart Mohr Cc: stable@vger.kernel.org Signed-off-by: Werner Sembach Link: https://patch.msgid.link/20260821144437.27233-1-wse@tuxedocomputers.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 5c4ea24e9651..b08d25fb0e17 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -8239,6 +8239,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1d05, 0x3031, "TongFang X6AR55xU", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1d05, 0x3033, "TongFang X6SP45xU", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1d05, 0x3034, "TongFang X6KK45xU", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1d05, 0x30ba, "TongFang XxAF5xxx", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1d17, 0x3288, "Haier Boyue G42", ALC269VC_FIXUP_ACER_VCOPPERBOX_PINS), SND_PCI_QUIRK(0x1d72, 0x1602, "RedmiBook", ALC255_FIXUP_XIAOMI_HEADSET_MIC), SND_PCI_QUIRK(0x1d72, 0x1701, "XiaomiNotebook Pro", ALC298_FIXUP_DELL1_MIC_NO_PRESENCE), From 916b61abba586d950a4d8264e2841cc5ccfa4df1 Mon Sep 17 00:00:00 2001 From: Sumeet Pawnikar Date: Sat, 22 Aug 2026 15:16:57 +0530 Subject: [PATCH 0886/1328] powercap: intel_rapl: Fix kernel panic during PMU unbind rapl_package_add_pmu() fails internally at perf_pmu_register(), and rapl_pmu_update() leaves the global rapl_pmu.pmu structure zero-initialized via memset and returns an error. But any previously probed packages retain has_pmu = true. When the driver is subsequently unbound or removed, rapl_package_remove_pmu_locked() sees has_pmu == true and unconditionally calls perf_pmu_unregister(&rapl_pmu.pmu) on the zeroed-out structure. This attempts a list_del_rcu() on a NULL list head, immediately causing a kernel panic. Fix this by checking if the PMU is actually registered before attempting to unregister it. Signed-off-by: Sumeet Pawnikar Reviewed-by: Abel Vesa [ rjw: Added empty line after the new conditional ] Link: https://patch.msgid.link/20260822094657.12489-1-sumeet4linux@gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/powercap/intel_rapl_common.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/powercap/intel_rapl_common.c b/drivers/powercap/intel_rapl_common.c index 6b7d11a0abc5..0c743a86df76 100644 --- a/drivers/powercap/intel_rapl_common.c +++ b/drivers/powercap/intel_rapl_common.c @@ -1661,7 +1661,9 @@ void rapl_package_remove_pmu_locked(struct rapl_package *rp) return; } - perf_pmu_unregister(&rapl_pmu.pmu); + if (rapl_pmu.registered) + perf_pmu_unregister(&rapl_pmu.pmu); + memset(&rapl_pmu, 0, sizeof(struct rapl_pmu)); } EXPORT_SYMBOL_NS_GPL(rapl_package_remove_pmu_locked, "INTEL_RAPL"); From 46094a7708b7945cb7eba9eb887e3ea9757440a7 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Mon, 24 Aug 2026 12:49:10 +0200 Subject: [PATCH 0887/1328] locking: Revert switching guards to _irq_{disable,enable}() Revert commit 1b0866874833 ("locking: Switch to _irq_{disable,enable}() variants in cleanup guards"). While the guards are properly nested, not all wrapped code is nice, as already highlighted by that fair.c hunk. Syzbot found another instance of this pattern in posix_timer_delete(), which does spin_unlock_irq()+spin_lock_irq() inside scoped_guard(spinlock_irq). Combined with this patch, that goes sideways most spectacular. Undo this until we've developed stronger tools / debug for such issues. Fixes: 1b0866874833 ("locking: Switch to _irq_{disable,enable}() variants in cleanup guards") Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260824105523.GA4121620%40noisy.programming.kicks-ass.net --- include/linux/spinlock.h | 26 ++++++++++++++------------ kernel/sched/fair.c | 12 ++++++------ 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/include/linux/spinlock.h b/include/linux/spinlock.h index 799a8f7d2741..3d405cc4c121 100644 --- a/include/linux/spinlock.h +++ b/include/linux/spinlock.h @@ -572,12 +572,12 @@ DECLARE_LOCK_GUARD_1_ATTRS(raw_spinlock_nested, __acquires(_T), __releases(*(raw #define class_raw_spinlock_nested_constructor(_T) WITH_LOCK_GUARD_1_ATTRS(raw_spinlock_nested, _T) DEFINE_LOCK_GUARD_1(raw_spinlock_irq, raw_spinlock_t, - raw_spin_lock_irq_disable(_T->lock), - raw_spin_unlock_irq_enable(_T->lock)) + raw_spin_lock_irq(_T->lock), + raw_spin_unlock_irq(_T->lock)) DECLARE_LOCK_GUARD_1_ATTRS(raw_spinlock_irq, __acquires(_T), __releases(*(raw_spinlock_t **)_T)) #define class_raw_spinlock_irq_constructor(_T) WITH_LOCK_GUARD_1_ATTRS(raw_spinlock_irq, _T) -DEFINE_LOCK_GUARD_1_COND(raw_spinlock_irq, _try, raw_spin_trylock_irq_disable(_T->lock)) +DEFINE_LOCK_GUARD_1_COND(raw_spinlock_irq, _try, raw_spin_trylock_irq(_T->lock)) DECLARE_LOCK_GUARD_1_ATTRS(raw_spinlock_irq_try, __acquires(_T), __releases(*(raw_spinlock_t **)_T)) #define class_raw_spinlock_irq_try_constructor(_T) WITH_LOCK_GUARD_1_ATTRS(raw_spinlock_irq_try, _T) @@ -592,13 +592,14 @@ DECLARE_LOCK_GUARD_1_ATTRS(raw_spinlock_bh_try, __acquires(_T), __releases(*(raw #define class_raw_spinlock_bh_try_constructor(_T) WITH_LOCK_GUARD_1_ATTRS(raw_spinlock_bh_try, _T) DEFINE_LOCK_GUARD_1(raw_spinlock_irqsave, raw_spinlock_t, - raw_spin_lock_irq_disable(_T->lock), - raw_spin_unlock_irq_enable(_T->lock)) + raw_spin_lock_irqsave(_T->lock, _T->flags), + raw_spin_unlock_irqrestore(_T->lock, _T->flags), + unsigned long flags) DECLARE_LOCK_GUARD_1_ATTRS(raw_spinlock_irqsave, __acquires(_T), __releases(*(raw_spinlock_t **)_T)) #define class_raw_spinlock_irqsave_constructor(_T) WITH_LOCK_GUARD_1_ATTRS(raw_spinlock_irqsave, _T) DEFINE_LOCK_GUARD_1_COND(raw_spinlock_irqsave, _try, - raw_spin_trylock_irq_disable(_T->lock)) + raw_spin_trylock_irqsave(_T->lock, _T->flags)) DECLARE_LOCK_GUARD_1_ATTRS(raw_spinlock_irqsave_try, __acquires(_T), __releases(*(raw_spinlock_t **)_T)) #define class_raw_spinlock_irqsave_try_constructor(_T) WITH_LOCK_GUARD_1_ATTRS(raw_spinlock_irqsave_try, _T) @@ -617,13 +618,13 @@ DECLARE_LOCK_GUARD_1_ATTRS(spinlock_try, __acquires(_T), __releases(*(spinlock_t #define class_spinlock_try_constructor(_T) WITH_LOCK_GUARD_1_ATTRS(spinlock_try, _T) DEFINE_LOCK_GUARD_1(spinlock_irq, spinlock_t, - spin_lock_irq_disable(_T->lock), - spin_unlock_irq_enable(_T->lock)) + spin_lock_irq(_T->lock), + spin_unlock_irq(_T->lock)) DECLARE_LOCK_GUARD_1_ATTRS(spinlock_irq, __acquires(_T), __releases(*(spinlock_t **)_T)) #define class_spinlock_irq_constructor(_T) WITH_LOCK_GUARD_1_ATTRS(spinlock_irq, _T) DEFINE_LOCK_GUARD_1_COND(spinlock_irq, _try, - spin_trylock_irq_disable(_T->lock)) + spin_trylock_irq(_T->lock)) DECLARE_LOCK_GUARD_1_ATTRS(spinlock_irq_try, __acquires(_T), __releases(*(spinlock_t **)_T)) #define class_spinlock_irq_try_constructor(_T) WITH_LOCK_GUARD_1_ATTRS(spinlock_irq_try, _T) @@ -639,13 +640,14 @@ DECLARE_LOCK_GUARD_1_ATTRS(spinlock_bh_try, __acquires(_T), __releases(*(spinloc #define class_spinlock_bh_try_constructor(_T) WITH_LOCK_GUARD_1_ATTRS(spinlock_bh_try, _T) DEFINE_LOCK_GUARD_1(spinlock_irqsave, spinlock_t, - spin_lock_irq_disable(_T->lock), - spin_unlock_irq_enable(_T->lock)) + spin_lock_irqsave(_T->lock, _T->flags), + spin_unlock_irqrestore(_T->lock, _T->flags), + unsigned long flags) DECLARE_LOCK_GUARD_1_ATTRS(spinlock_irqsave, __acquires(_T), __releases(*(spinlock_t **)_T)) #define class_spinlock_irqsave_constructor(_T) WITH_LOCK_GUARD_1_ATTRS(spinlock_irqsave, _T) DEFINE_LOCK_GUARD_1_COND(spinlock_irqsave, _try, - spin_trylock_irq_disable(_T->lock)) + spin_trylock_irqsave(_T->lock, _T->flags)) DECLARE_LOCK_GUARD_1_ATTRS(spinlock_irqsave_try, __acquires(_T), __releases(*(spinlock_t **)_T)) #define class_spinlock_irqsave_try_constructor(_T) WITH_LOCK_GUARD_1_ATTRS(spinlock_irqsave_try, _T) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index 6d881e530f89..8dff37059faf 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -7253,7 +7253,7 @@ static bool distribute_cfs_runtime(struct cfs_bandwidth *cfs_b) * period the timer is deactivated until scheduling resumes; cfs_b->idle is * used to track this state. */ -static int do_sched_cfs_period_timer(struct cfs_bandwidth *cfs_b, int overrun) +static int do_sched_cfs_period_timer(struct cfs_bandwidth *cfs_b, int overrun, unsigned long flags) __must_hold(&cfs_b->lock) { int throttled; @@ -7288,10 +7288,10 @@ static int do_sched_cfs_period_timer(struct cfs_bandwidth *cfs_b, int overrun) * This check is repeated as we release cfs_b->lock while we unthrottle. */ while (throttled && cfs_b->runtime > 0) { - raw_spin_unlock_irq_enable(&cfs_b->lock); + raw_spin_unlock_irqrestore(&cfs_b->lock, flags); /* we can't nest cfs_b->lock while distributing bandwidth */ throttled = distribute_cfs_runtime(cfs_b); - raw_spin_lock_irq_disable(&cfs_b->lock); + raw_spin_lock_irqsave(&cfs_b->lock, flags); } /* @@ -7399,7 +7399,7 @@ static __always_inline void return_cfs_rq_runtime(struct cfs_rq *cfs_rq) static void do_sched_cfs_slack_timer(struct cfs_bandwidth *cfs_b) { /* confirm we're still not at a refresh boundary */ - scoped_guard(raw_spinlock_irq, &cfs_b->lock) { + scoped_guard(raw_spinlock_irqsave, &cfs_b->lock) { u64 runtime = 0, slice = sched_cfs_bandwidth_slice(); cfs_b->slack_started = false; @@ -7484,14 +7484,14 @@ static enum hrtimer_restart sched_cfs_period_timer(struct hrtimer *timer) int idle = 0; int count = 0; - guard(raw_spinlock_irq)(&cfs_b->lock); + CLASS(raw_spinlock_irqsave, cfsb_guard)(&cfs_b->lock); for (;;) { overrun = hrtimer_forward_now(timer, cfs_b->period); if (!overrun) break; - idle = do_sched_cfs_period_timer(cfs_b, overrun); + idle = do_sched_cfs_period_timer(cfs_b, overrun, cfsb_guard.flags); if (++count > 3) { u64 new, old = ktime_to_ns(cfs_b->period); From 7dd6343fc127935425a81a8f1989907996c2868d Mon Sep 17 00:00:00 2001 From: Younes Akhouayri Date: Sat, 22 Aug 2026 10:44:47 +0200 Subject: [PATCH 0888/1328] rust: num: restrict bool conversion to unsigned Bounded From turns true into 1. A signed Bounded with N = 1 can hold only -1 and 0. The current implementation can therefore create a value that breaks Bounded's invariant. Deref relies on that invariant and calls unreachable_unchecked() when it is broken, so safe Rust can reach undefined behavior. The other primitive conversions require the source and destination to have the same signedness. Treat bool as an unsigned one-bit value and allow conversions between bool and Bounded only when the backing integer type is unsigned. Fixes: 01e345e82ec3 ("rust: num: add Bounded integer wrapping type") Closes: https://lore.kernel.org/rust-for-linux/OzuVxu0--J-9@younes.io/ Cc: stable@vger.kernel.org Suggested-by: Alexandre Courbot Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Younes Akhouayri Reviewed-by: Alexandre Courbot Link: https://patch.msgid.link/20260822-fix-rust-bounded-from-bool-submit-v4-1-aa780bfe7f30@younes.io Signed-off-by: Miguel Ojeda --- rust/kernel/num/bounded.rs | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/rust/kernel/num/bounded.rs b/rust/kernel/num/bounded.rs index d192610a687d..2a2b0a4bca5e 100644 --- a/rust/kernel/num/bounded.rs +++ b/rust/kernel/num/bounded.rs @@ -13,7 +13,10 @@ }; use kernel::{ - num::Integer, + num::{ + Integer, + Unsigned, // + }, prelude::*, // }; @@ -174,13 +177,16 @@ fn fits_within(value: T, num_bits: u32) -> bool { /// // `u8` (regardless of the passed value). /// // let _ = Bounded::::from(10u8); /// -/// // Booleans can be converted into single-bit `Bounded`s. +/// // Booleans can be converted into unsigned `Bounded`s. /// /// let v = Bounded::::from(false); /// assert_eq!(v.get(), 0); /// /// let v = Bounded::::from(true); /// assert_eq!(v.get(), 1); +/// +/// // This does not build because `i8` is signed. +/// // let _ = Bounded::::from(true); /// ``` /// /// Infallible conversions from a [`Bounded`] to a primitive integer are also supported, and @@ -203,12 +209,16 @@ fn fits_within(value: T, num_bits: u32) -> bool { /// let _v = Bounded::::new::<10>(); /// // assert_eq!(u8::from(_v), 10); /// -/// // Single-bit `Bounded`s can be converted into a boolean. +/// // Unsigned single-bit `Bounded`s can be converted into a boolean. /// let v = Bounded::::new::<1>(); /// assert_eq!(bool::from(v), true); /// /// let v = Bounded::::new::<0>(); /// assert_eq!(bool::from(v), false); +/// +/// // This does not build because `i8` is signed. +/// // let v = Bounded::::new::<-1>(); +/// // let _ = bool::from(v); /// ``` /// /// Fallible conversions from any primitive integer to any [`Bounded`] are also supported using the @@ -1109,31 +1119,33 @@ fn from(value: Bounded) -> $type { i8 i16 i32 i64 isize ); -// Single-bit `Bounded`s can be converted from/to a boolean. +// Unsigned single-bit `Bounded`s can be converted to a boolean. impl From> for bool where - T: Integer + Zeroable, + T: Integer + Zeroable, { fn from(value: Bounded) -> Self { value.get() != Zeroable::zeroed() } } +// Booleans can be converted to unsigned `Bounded`s. + impl From for Bounded where - T: Integer + From, + T: Integer + From, { fn from(value: bool) -> Self { - // SAFETY: A boolean can be represented using a single bit, and thus fits within any - // integer type for any `N` > 0. + // SAFETY: A boolean is represented by `0` or `1`, so it fits within any valid unsigned + // `Bounded` width. unsafe { Self::__new(T::from(value)) } } } impl Bounded where - T: Integer + Zeroable, + T: Integer + Zeroable, { /// Converts this [`Bounded`] into a [`bool`]. /// From eb049bdbf2b98d103d93daef44a5e1a0164d01eb Mon Sep 17 00:00:00 2001 From: Anthony Iliopoulos Date: Thu, 20 Aug 2026 14:36:51 +0200 Subject: [PATCH 0889/1328] xtensa: remove unused setup_profiling_timer function setup_profiling_timer() is not used by any code at this point. Since a default weak implementation exists, there is no need to keep this arch-specific definition around. Remove it along with the now-redundant profile header includes. Signed-off-by: Anthony Iliopoulos Message-ID: <20260820123711.142005-1-ailiop@suse.com> Signed-off-by: Max Filippov --- arch/xtensa/kernel/smp.c | 7 ------- arch/xtensa/kernel/time.c | 1 - 2 files changed, 8 deletions(-) diff --git a/arch/xtensa/kernel/smp.c b/arch/xtensa/kernel/smp.c index 94a23f100726..e4c25d0c2158 100644 --- a/arch/xtensa/kernel/smp.c +++ b/arch/xtensa/kernel/smp.c @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include @@ -459,12 +458,6 @@ void show_ipi_list(struct seq_file *p, int prec) } } -int setup_profiling_timer(unsigned int multiplier) -{ - pr_debug("setup_profiling_timer %d\n", multiplier); - return 0; -} - /* TLB flush functions */ struct flush_data { diff --git a/arch/xtensa/kernel/time.c b/arch/xtensa/kernel/time.c index 1c3dfea843ec..09fda6a3fe3b 100644 --- a/arch/xtensa/kernel/time.c +++ b/arch/xtensa/kernel/time.c @@ -23,7 +23,6 @@ #include #include #include -#include #include #include #include From c9e17e381e021536a9f0fe36f4c9c693d6c0f27c Mon Sep 17 00:00:00 2001 From: Daniel Palmer Date: Mon, 13 Jul 2026 22:39:19 +0900 Subject: [PATCH 0890/1328] rtc: msc313: Select by default on MSTARV7 All of the ARCH_MSTARV7 chips have this RTC so it's more likely ARCH_MSTARV7 wants it than doesn't. At the same time fix up the indent on the depends on line that looks like it got converted from a tab to spaces at some point. Signed-off-by: Daniel Palmer Link: https://patch.msgid.link/20260713133919.1356748-1-daniel@thingy.jp Signed-off-by: Alexandre Belloni --- drivers/rtc/Kconfig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index d23a0fbe8d89..05b9233b9418 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -2107,7 +2107,8 @@ config RTC_DRV_MACSMC config RTC_DRV_MSC313 tristate "MStar MSC313 RTC" - depends on ARCH_MSTARV7 || COMPILE_TEST + depends on ARCH_MSTARV7 || COMPILE_TEST + default ARCH_MSTARV7 help If you say yes here you get support for the Mstar MSC313e On-Chip Real Time Clock. From 60e73c07b3fc12958a63de77b482d18bda413a6a Mon Sep 17 00:00:00 2001 From: Shuming Fan Date: Mon, 24 Aug 2026 18:39:36 +0800 Subject: [PATCH 0891/1328] ASoC: rt766: add RT766/RT767 VA1 device IDs This patch adds the RT766/RT767 VA1 device IDs, which support SoundWire v1.3. Signed-off-by: Shuming Fan Link: https://patch.msgid.link/20260824103936.3524080-1-shumingf@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt766-sdca-sdw.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/codecs/rt766-sdca-sdw.c b/sound/soc/codecs/rt766-sdca-sdw.c index 5d60c3bcc6cd..8e76bf725ccc 100644 --- a/sound/soc/codecs/rt766-sdca-sdw.c +++ b/sound/soc/codecs/rt766-sdca-sdw.c @@ -233,6 +233,8 @@ static void rt766_sdca_sdw_remove(struct sdw_slave *slave) static const struct sdw_device_id rt766_sdca_id[] = { SDW_SLAVE_ENTRY_EXT(0x025d, 0x766, 0x3, 0x1, 0), SDW_SLAVE_ENTRY_EXT(0x025d, 0x767, 0x3, 0x1, 0), + SDW_SLAVE_ENTRY_EXT(0x025d, 0x766, 0x4, 0x1, 0), + SDW_SLAVE_ENTRY_EXT(0x025d, 0x767, 0x4, 0x1, 0), {}, }; MODULE_DEVICE_TABLE(sdw, rt766_sdca_id); From 902caade3cfd60f99bf71b355e7c86344bd831e5 Mon Sep 17 00:00:00 2001 From: Wei-Lin Chang Date: Fri, 14 Aug 2026 23:24:57 +0100 Subject: [PATCH 0892/1328] arm64: ptdump: Make note_page_flush() range aware note_page_flush() calls note_page() with addr == 0 and level == -1 to dump the last row of a ptdump. addr == 0 (1 << 64 wrapped around) renders a huge region with enormous size for address spaces with IA bits < 64. For example the stage-2 page tables and the EFI runtime page table. More importantly, the last region of the address space and everything after the address space up to 1 << 64 are merged into one row of output. If the last region within the address space is valid, it will appear to remain valid up to 1 << 64 with the same attributes. Currently only the EFI runtime ptdump is affected by this, but KVM will soon fix its stage-2 ptdump by using note_page_flush(). Here is an example of an EFI runtime ptdump (last row): 0x0000008000000000-0x0000000000000000 17179868672G PGD With this patch: 0x0000008000000000-0x0001000000000000 261632G PGD To fix this, cache the end address of a ptdump in ptdump_pg_state so note_page_flush() can call the final note_page() with the correct end address. Fixes: 9d80448ac92b ("efi/arm64: Add debugfs node to dump UEFI runtime page tables") Signed-off-by: Wei-Lin Chang Reviewed-by: Marc Zyngier Signed-off-by: Will Deacon --- arch/arm64/include/asm/ptdump.h | 2 ++ arch/arm64/mm/ptdump.c | 14 +++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/arch/arm64/include/asm/ptdump.h b/arch/arm64/include/asm/ptdump.h index 5b374a6ab34a..1b743de7d89e 100644 --- a/arch/arm64/include/asm/ptdump.h +++ b/arch/arm64/include/asm/ptdump.h @@ -52,6 +52,8 @@ struct ptdump_pg_state { const struct addr_marker *marker; const struct mm_struct *mm; unsigned long start_address; + /* exclusive end, ULONG_MAX represents an end at 1 << 64 */ + unsigned long end_address; int level; ptval_t current_prot; bool check_wx; diff --git a/arch/arm64/mm/ptdump.c b/arch/arm64/mm/ptdump.c index 1c20144700d7..eab400e744d9 100644 --- a/arch/arm64/mm/ptdump.c +++ b/arch/arm64/mm/ptdump.c @@ -278,9 +278,19 @@ void note_page_pgd(struct ptdump_state *pt_st, unsigned long addr, pgd_t pgd) void note_page_flush(struct ptdump_state *pt_st) { + struct ptdump_pg_state *st = container_of(pt_st, struct ptdump_pg_state, ptdump); + unsigned long end = st->end_address; pte_t pte_zero = {0}; - note_page(pt_st, 0, -1, pte_val(pte_zero)); + /* + * Address spaces that end at 1 << 64 have end_address == ULONG_MAX, + * but note_page() expects the exclusive end. In this case adjust end + * to the wraparound value 0. + */ + if (end == ULONG_MAX) + end = 0; + + note_page(pt_st, end, -1, pte_val(pte_zero)); } static void arm64_ptdump_walk_pgd(struct ptdump_state *st, struct mm_struct *mm) @@ -303,6 +313,7 @@ void ptdump_walk(struct seq_file *s, struct ptdump_info *info) .marker = info->markers, .mm = info->mm, .pg_level = &kernel_pg_levels[0], + .end_address = end, .level = -1, .ptdump = { .note_page_pte = note_page_pte, @@ -344,6 +355,7 @@ bool ptdump_check_wx(void) { -1, NULL}, }, .pg_level = &kernel_pg_levels[0], + .end_address = ~0UL, .level = -1, .check_wx = true, .ptdump = { From beca1b97a21d81eae9cbd267ecc44b5d2193e60a Mon Sep 17 00:00:00 2001 From: Wei-Lin Chang Date: Fri, 14 Aug 2026 23:24:58 +0100 Subject: [PATCH 0893/1328] KVM: arm64: ptdump: Flush the last region Currently the stage-2 ptdump calls note_page() at each leaf entry visit. This simply misses the output of the last region, because note_page() only dumps output when it detects a change in level/prot, or when the walk enters a next marker section. The last region in the guest IPA space with the same level/prot is not dumped since there is no change after it. Call note_page_flush() to dump the final region. note_page_flush() uses ptdump_pg_state.end_address to call the final note_page(), so also provide the end address. Also change the second marker's start address to ULONG_MAX so we never cross it. This avoids dumping redundant marker names (which are NULL), and advancing beyond the end of the marker array. Fixes: 7c4f73548ed1 ("KVM: arm64: Register ptdump with debugfs on guest creation") Reported-by: Sashiko AI Closes: https://lore.kernel.org/kvmarm/20260630122758.891011F00A3A@smtp.kernel.org/ Reviewed-by: Dev Jain Tested-by: Dev Jain Signed-off-by: Wei-Lin Chang Reviewed-by: Marc Zyngier Signed-off-by: Will Deacon --- arch/arm64/kvm/ptdump.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/arch/arm64/kvm/ptdump.c b/arch/arm64/kvm/ptdump.c index c9140e22abcf..69899797dbad 100644 --- a/arch/arm64/kvm/ptdump.c +++ b/arch/arm64/kvm/ptdump.c @@ -130,7 +130,7 @@ static struct kvm_ptdump_guest_state *kvm_ptdump_parser_create(struct kvm_s2_mmu } st->ipa_marker[0].name = "Guest IPA"; - st->ipa_marker[1].start_address = BIT(pgtable->ia_bits); + st->ipa_marker[1].start_address = ULONG_MAX; st->mmu = mmu; return st; @@ -148,18 +148,21 @@ static int kvm_ptdump_guest_show(struct seq_file *m, void *unused) .flags = KVM_PGTABLE_WALK_LEAF, }; + guard(write_lock)(&kvm->mmu_lock); st->parser_state = (struct ptdump_pg_state) { .marker = &st->ipa_marker[0], + .end_address = BIT(mmu->pgt->ia_bits), .level = -1, .pg_level = &st->level[0], .seq = m, }; - write_lock(&kvm->mmu_lock); ret = kvm_pgtable_walk(mmu->pgt, 0, BIT(mmu->pgt->ia_bits), &walker); - write_unlock(&kvm->mmu_lock); + if (ret) + return ret; + note_page_flush(&st->parser_state.ptdump); - return ret; + return 0; } static int kvm_ptdump_guest_open(struct inode *m, struct file *file) From 1476cca098f6d3a553fcec6fe9b7d86e15c00b59 Mon Sep 17 00:00:00 2001 From: Alexander Graf Date: Fri, 21 Aug 2026 22:42:52 +0000 Subject: [PATCH 0894/1328] dma-contiguous: fix truncation of numa_cma / cma_pernuma sizes >= 2G numa_cma=0:4G reserves nothing at all. dma_numa_cma_reserve() copies the requested size into a local int before handing it to cma_declare_contiguous_nid(), so 0x100000000 truncates to zero and the loop skips the node silently. Both parameters are documented in kernel-parameters.txt as nn[MG], so that is the syntax the documentation invites. Which bits survive decides what a request turns into: 4G, 8G and 16G reserve nothing, 2G, 3G and 6G sign-extend into a size the allocator rejects with a warning, and 5G quietly reserves 1G. It reaches further than those parameters. On a CMA_SIZE_PERNUMA kernel with no per-node parameter, dma_numa_cma_reserve() takes the per-node size from the default area, so a plain cma=4G on a multi-node machine feeds that size through the same local and loses every per-node area. numa_cma_size[] and pernuma_size_bytes are both phys_addr_t, so use it for the local too, and give early_numa_cma() separate variables for the node id and the size while in there. Fixes: d5cae2261b86 ("dma-contiguous: simplify numa cma area handling") Cc: stable@vger.kernel.org Assisted-by: Kiro:claude-opus-5 Signed-off-by: Alexander Graf Reviewed-by: Feng Tang Link: https://lore.kernel.org/r/20260821224252.70640-1-graf@amazon.com Signed-off-by: Marek Szyprowski --- kernel/dma/contiguous.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/kernel/dma/contiguous.c b/kernel/dma/contiguous.c index f754079a287d..66093460584e 100644 --- a/kernel/dma/contiguous.c +++ b/kernel/dma/contiguous.c @@ -140,21 +140,22 @@ static bool numa_cma_configured __initdata; static int __init early_numa_cma(char *p) { int nid, count = 0; - unsigned long tmp; + unsigned long node; + phys_addr_t size; char *s = p; while (*s) { - if (sscanf(s, "%lu%n", &tmp, &count) != 1) + if (sscanf(s, "%lu%n", &node, &count) != 1) break; if (s[count] == ':') { - if (tmp >= MAX_NUMNODES) + if (node >= MAX_NUMNODES) break; - nid = array_index_nospec(tmp, MAX_NUMNODES); + nid = array_index_nospec(node, MAX_NUMNODES); s += count + 1; - tmp = memparse(s, &s); - numa_cma_size[nid] = tmp; + size = memparse(s, &s); + numa_cma_size[nid] = size; if (*s == ',') s++; @@ -207,9 +208,10 @@ static void __init dma_numa_cma_reserve(void) pernuma_size_bytes = cma_get_size(dma_contiguous_default_area); for_each_node(nid) { - int size, ret; + phys_addr_t size; char name[CMA_MAX_NAME]; struct cma **cma; + int ret; if (!node_online(nid)) { if (pernuma_size_bytes || numa_cma_size[nid]) From b8f070ac3167595069feb1f794c127b805115645 Mon Sep 17 00:00:00 2001 From: Vladimir Murzin Date: Wed, 19 Aug 2026 15:48:59 +0100 Subject: [PATCH 0895/1328] arm64: process: Fix context switching MTE store-only tag check SCTLR_EL1.TCSO0 is set when user opt-in for MTE store-only tag check mode. However, it is not part of SCTLR_USER_MASK which imply that on context switch we never clear SCTLR_EL1.TCSO0, so we are leaking that setting into another task. Fix that by including SCTLR_EL1_TCSO0_MASK into SCTLR_USER_MASK Fixes: 4d51ff5bba00 ("arm64/kernel: Support store-only mte tag check") Signed-off-by: Vladimir Murzin Reviewed-by: Catalin Marinas Reviewed-by: Yeoreum Yun Signed-off-by: Will Deacon --- arch/arm64/include/asm/processor.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/include/asm/processor.h b/arch/arm64/include/asm/processor.h index c2a627f39314..6dfbcacd9ba0 100644 --- a/arch/arm64/include/asm/processor.h +++ b/arch/arm64/include/asm/processor.h @@ -264,7 +264,7 @@ static inline void task_set_sve_vl_onexec(struct task_struct *task, #define SCTLR_USER_MASK \ (SCTLR_ELx_ENIA | SCTLR_ELx_ENIB | SCTLR_ELx_ENDA | SCTLR_ELx_ENDB | \ - SCTLR_EL1_TCF0_MASK) + SCTLR_EL1_TCF0_MASK | SCTLR_EL1_TCSO0_MASK) static inline void arch_thread_struct_whitelist(unsigned long *offset, unsigned long *size) From 3b2c5d35cf4398edf47c4a3ad076838654a30015 Mon Sep 17 00:00:00 2001 From: Seongjae Jeong Date: Mon, 24 Aug 2026 02:32:31 +0000 Subject: [PATCH 0896/1328] f2fs: use adjusted write range after f2fs_write_checks() generic_write_checks() in f2fs_write_checks() can adjust iocb->ki_pos for append writes and truncate the iterator to limit the number of bytes to write. In f2fs_file_write_iter(), the pinned-file overwrite check currently uses the position and count saved before f2fs_write_checks(), so it can check a range different from the actual write range. The forced buffered I/O cleanup also uses orig_pos saved before f2fs_write_checks(). For O_APPEND writes, this can make the cleanup flush and invalidate the wrong page cache range. Move the pinned-file overwrite check after f2fs_write_checks() and use the adjusted iocb->ki_pos and iov_iter_count(from). Also save the adjusted write position and use it for the forced buffered I/O cleanup. Fixes: 3fdd89b452c2 ("f2fs: prevent writing without fallocate() for pinned files") Fixes: 92318f20d703 ("f2fs: preserve direct write semantics when buffering is forced") Signed-off-by: Seongjae Jeong Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/file.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index d82be8c1502a..d440231b8cb9 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -5641,9 +5641,8 @@ static ssize_t f2fs_file_write_iter(struct kiocb *iocb, struct iov_iter *from) bool dio; bool may_need_sync = true; int preallocated; - const loff_t pos = iocb->ki_pos; - const ssize_t count = iov_iter_count(from); ssize_t ret; + loff_t bufio_start_pos; if (unlikely(f2fs_cp_error(F2FS_I_SB(inode)))) { ret = -EIO; @@ -5664,15 +5663,17 @@ static ssize_t f2fs_file_write_iter(struct kiocb *iocb, struct iov_iter *from) inode_lock(inode); } + ret = f2fs_write_checks(iocb, from); + if (ret <= 0) + goto out_unlock; + if (f2fs_is_pinned_file(inode) && - !f2fs_overwrite_io(inode, pos, count)) { + !f2fs_overwrite_io(inode, iocb->ki_pos, iov_iter_count(from))) { ret = -EIO; goto out_unlock; } - ret = f2fs_write_checks(iocb, from); - if (ret <= 0) - goto out_unlock; + bufio_start_pos = iocb->ki_pos; /* Determine whether we will do a direct write or a buffered write. */ dio = f2fs_should_use_dio(inode, iocb, from); @@ -5727,8 +5728,8 @@ static ssize_t f2fs_file_write_iter(struct kiocb *iocb, struct iov_iter *from) */ if (ret > 0 && !dio && (iocb->ki_flags & IOCB_DIRECT)) f2fs_flush_buffered_write(iocb->ki_filp->f_mapping, - orig_pos, - orig_pos + ret - 1); + bufio_start_pos, + bufio_start_pos + ret - 1); return ret; } From cb19774faa57c51efa189d8b8606aeabccebc53b Mon Sep 17 00:00:00 2001 From: Ibrahim Abdelkader Date: Tue, 11 Aug 2026 10:37:29 +0200 Subject: [PATCH 0897/1328] Bluetooth: hci_sync: Clear HCI_CMD_PENDING when dropping the last request A synchronous HCI command that never receives a response leaves HCI_CMD_PENDING set: hci_req_cmd_complete() is the only place that clears it, and it only runs when a response matching the last command sent arrives. hci_send_cmd_sync() populates hdev->req_skb only when the flag transitions from clear to set, while hci_dev_open_sync() and hci_dev_close_sync() drop req_skb without clearing the flag. After a timeout followed by either, the two disagree: the flag claims a request is outstanding while req_skb is NULL. Subsequent synchronous commands are then sent with no req_skb, so hci_event_packet() has nothing to match an arriving event against, and the caller times out even though the controller answered. Commands answered by Command Complete recover on their own, since hci_req_cmd_complete() clears the flag as a side effect. Drivers using __hci_cmd_sync_ev() with a custom event do not, because a vendor event never reaches that path. On a WCN3988 (hci_qca over UART) this makes a controller firmware hang unrecoverable: the driver injects a hardware error and re-runs qca_setup(), qca_read_soc_version() waits for HCI_EV_VENDOR, the reply arrives within 4 ms and is discarded, and every retry fails the same way. The adapter is left down until the driver is unbound and rebound, or power is removed. Clear the flag wherever the last request is dropped, restoring the invariant that req_skb is non-NULL exactly when HCI_CMD_PENDING is set. Verified on hardware by forcing a command timeout: without this change setup fails on every attempt, with it setup succeeds on the first. Fixes: 2615fd9a7c25 ("Bluetooth: hci_sync: Fix overwriting request callback") Cc: stable@vger.kernel.org Signed-off-by: Ibrahim Abdelkader Signed-off-by: Hans de Goede Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_sync.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index b5897545d795..4d6ab5d39e94 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -5448,6 +5448,7 @@ int hci_dev_open_sync(struct hci_dev *hdev) if (hdev->req_skb) { kfree_skb(hdev->req_skb); hdev->req_skb = NULL; + hci_dev_clear_flag(hdev, HCI_CMD_PENDING); } clear_bit(HCI_RUNNING, &hdev->flags); @@ -5632,6 +5633,7 @@ int hci_dev_close_sync(struct hci_dev *hdev) if (hdev->req_skb) { kfree_skb(hdev->req_skb); hdev->req_skb = NULL; + hci_dev_clear_flag(hdev, HCI_CMD_PENDING); } clear_bit(HCI_RUNNING, &hdev->flags); From bbc2de7fa5b42ee024ae82cd848252152d219a4e Mon Sep 17 00:00:00 2001 From: Akiyoshi Kurita Date: Thu, 30 Jul 2026 22:00:30 +0900 Subject: [PATCH 0898/1328] docs/ja_JP: translate submitting-patches.rst (tag usage) Translate the Acked-by:, Cc:, and Co-developed-by: section into Japanese. Signed-off-by: Akiyoshi Kurita Acked-by: Akira Yokosawa Signed-off-by: Jonathan Corbet Message-ID: <20260730130030.1078990-1-weibu@redadmin.org> --- .../ja_JP/process/submitting-patches.rst | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/Documentation/translations/ja_JP/process/submitting-patches.rst b/Documentation/translations/ja_JP/process/submitting-patches.rst index 22d54b663051..797b70b97fa7 100644 --- a/Documentation/translations/ja_JP/process/submitting-patches.rst +++ b/Documentation/translations/ja_JP/process/submitting-patches.rst @@ -479,3 +479,97 @@ revert にも ``Signed-off-by:`` を含める必要があります。 関与せず、その取り扱いや転送を行った人によるものです。SoB の連鎖は、 パッチがメンテナーを経て最終的に Linus へ届いた実際の経路を反映する 必要があります。最初の SoB は、単独の主要作者であることを示します。 + + +Acked-by:、Cc:、Co-developed-by: を使用する場合 +------------------------------------------------ + +``Signed-off-by:`` タグは、署名者がパッチの開発に関与したか、 +そのパッチの送付経路に関与したことを示します。 + +パッチの準備や取り扱いに直接関与していない人が、そのパッチへの +承認を表明し、記録に残したい場合は、パッチの変更履歴に +``Acked-by:`` 行を追加するよう依頼できます。 + +``Acked-by:`` は、何らかの形で影響を受けるコードに責任を持つ人、 +またはそのコードに関与する人が使用することを意図しています。 +最も一般的なのは、パッチの作成にも転送にも関与していない +メンテナーが使用する場合です。 + +``Acked-by:`` は、変更されるコードの元の作者など、その分野の +知識を持つ人、カーネル uAPI パッチをユーザー空間側からレビューする人、 +または機能の主要な利用者など、その他の利害関係者も使用できます。 +このような場合は、必要に応じて意味を明確にするために +``# Suffix`` を追加すると便利です:: + + Acked-by: The Stakeholder # As primary user + +``Acked-by:`` は ``Signed-off-by:`` ほど正式なものではありません。 +これは、承認した人が少なくともパッチをレビューし、受け入れる意思を +示したことの記録です。そのため、パッチをマージする人が、 +「はい、問題なさそうです」という返答を手動で ``Acked-by:`` に +変換することがあります。ただし、通常は明示的な承認を求める方が +適切です。 + +``Acked-by:`` は ``Reviewed-by:`` よりも正式なものではありません。 +たとえば、メンテナーはパッチが取り込まれることに同意していても、 +``Reviewed-by:`` を付ける場合ほど十分にはレビューしていないことを +示すために使用できます。同様に、主要な利用者はパッチの技術的な +レビューを行っていなくても、全体的な方針、機能、または +ユーザー向けインターフェースに満足している場合があります。 + +``Acked-by:`` は、必ずしもパッチ全体への同意を示すものでは +ありません。たとえば、パッチが複数のサブシステムに影響し、 +そのうち一つのサブシステムのメンテナーから ``Acked-by:`` が +付けられている場合、通常は、そのメンテナーが担当するコードに +影響する部分だけへの同意を示します。ここでは状況に応じた判断が +必要です。疑問がある場合は、メーリングリストアーカイブにある +元の議論を参照してください。この場合も、意味を明確にするために +``# Suffix`` を使用できます。 + +ある人がパッチにコメントする機会を得たものの、コメントしなかった +場合は、必要に応じてパッチに ``Cc:`` タグを追加できます。このタグは、 +関心を持つ可能性のある人が議論に含まれていたことを記録します。 +なお、これは、名前を記載される本人の明示的な許可なしに使用できる +可能性のある三つのタグのうちの一つです。詳細については、後述の +「人をタグ付けするには許可が必要」を参照してください。 + +``Co-developed-by:`` は、パッチが複数の開発者によって共同で作成された +ことを示します。複数の人が一つのパッチを共同で作成した場合に、 +``From:`` タグで示される作者に加えて、共同作者の貢献を明記するために +使用します。 + +``Co-developed-by:`` は作者であることを示すため、各 +``Co-developed-by:`` の直後には、対応する共同作者の +``Signed-off-by:`` を必ず記載しなければなりません。通常の sign-off +手続きが適用されます。つまり、作者が ``From:`` と +``Co-developed-by:`` のどちらで示されているかにかかわらず、 +``Signed-off-by:`` タグの順序は、可能な限りパッチの時系列上の +経緯を反映する必要があります。特に、最後の ``Signed-off-by:`` は、 +必ずパッチを提出する開発者のものでなければなりません。 + +なお、``From:`` タグで示される作者が、メールヘッダーの From 行に +記載された人物およびメールアドレスと同じ場合、``From:`` タグは +省略できます。 + +``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 + +``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 From b40a680ea4efe4ef2addada63e304a94d19500d1 Mon Sep 17 00:00:00 2001 From: Jenson Johnathon Reggentin Date: Wed, 19 Aug 2026 21:23:59 +0200 Subject: [PATCH 0899/1328] docs: block: fix dead http link in blk-mq.rst The current link http://kernel.dk/blk-mq.pdf leads to a 404 error page. https://www.kernel.dk/blk-mq.pdf leads to the correct document. v2 to add the missing www. prefix in the actual file change. Signed-off-by: Jenson Johnathon Reggentin Acked-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260819192422.42189-1-mail@jregg.de> --- Documentation/block/blk-mq.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/block/blk-mq.rst b/Documentation/block/blk-mq.rst index fc06761b6ea9..2798761efe0d 100644 --- a/Documentation/block/blk-mq.rst +++ b/Documentation/block/blk-mq.rst @@ -139,7 +139,7 @@ completed. Further reading --------------- -- `Linux Block IO: Introducing Multi-queue SSD Access on Multi-core Systems `_ +- `Linux Block IO: Introducing Multi-queue SSD Access on Multi-core Systems `_ - `NOOP scheduler `_ From 60763c2f834dbeb6c30064f845109358e775e64b Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Wed, 19 Aug 2026 15:40:39 +0200 Subject: [PATCH 0900/1328] docs: threat-model: fix /dev/kmsg reference Fix a '/dev/kmsg' reference which got replaced with '/proc/kmsg' (which is not accessible to regular users). Fixes: a03ef333fbd6 ("Documentation: security-bugs: explain what is and is not a security bug") Signed-off-by: Johan Hovold Acked-by: Willy Tarreau Signed-off-by: Jonathan Corbet Message-ID: <20260819134039.286793-1-johan@kernel.org> --- Documentation/process/threat-model.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/process/threat-model.rst b/Documentation/process/threat-model.rst index 9dd8011dde82..3ec35682d479 100644 --- a/Documentation/process/threat-model.rst +++ b/Documentation/process/threat-model.rst @@ -84,7 +84,7 @@ possibilities of user namespaces are not covered in this document. The kernel also offers a lot of troubleshooting and debugging facilities, which can constitute attack vectors when placed in wrong hands. While some of them are designed to be accessible to regular local users with a low risk (e.g. -kernel logs via ``/proc/kmsg``), some would expose enough information to +kernel logs via ``/dev/kmsg``), some would expose enough information to represent a risk in most places and the decision to expose them is under the administrator's responsibility (perf events, traces), and others are not designed to be accessed by non-privileged users (e.g. debugfs). Access to these From f7f11bdddababc9947138001a2f5c80d2cccf1a7 Mon Sep 17 00:00:00 2001 From: Federico Vaga Date: Sun, 16 Aug 2026 19:39:28 +0000 Subject: [PATCH 0901/1328] doc:it_IT: align Italian documentation in process This commit updates the Italian translation in `process/` following these changes: commit 44abc8fcbff2 ("Documentation: process: Arbitrarily bump kernel major version number") commit ba2457109d5b ("Documentation: process: Also mention Sasha Levin as stable tree maintainer") commit 5ce70894f6ca ("Doc: correct spelling and wording mistakes") commit 46298375477b ("linux-next: update maintainer info.") commit 43e9076a00b1 ("docs: Fix conflicting contributor identity info") commit b27f9e8079bf ("docs: remove Documentation/dontdiff") commit 9734b3e753ad ("docs: 5.Posting: mentioned Suggested-by: tag") commit 4e6b7141d169 ("docs: clarify rules wrt tagging other people") commit 944df7a31452 ("docs: update the guidance for Link: tags") commit 0a83293322fd ("doc: development-process: add notice on testing") commit a037699da0a1 ("docs: Add debugging section to process") commit 78d979db6cef ("docs: add AI Coding Assistants documentation") commit a66437c27979 ("Documentation: Provide guidelines for tool-generated content") commit a592a36e4937 ("Documentation: use a source-read extension for the index link boilerplate") commit 102606402f4f ("Documentation: Project continuity") commit a03ef333fbd6 ("Documentation: security-bugs: explain what is and is not a security bug") commit d0b343605f1b ("kernel-docs: Add new section for Rust learning materials") commit 57937eac1f78 ("kernel-docs: Add book to process/kernel-docs.rst") commit d8c949c577b5 ("docs/licensing: Clarify wording about "GPL" and "Proprietary"") commit ebf1bafd0907 ("LICENSES: Explicitly allow SPDX-FileCopyrightText") commit 9fa7153c31a3 ("rust: conclude the Rust experiment") commit 47cb33cedf47 ("docs: clarify wording in programming-language.rst") commit 45a92c0b91d7 ("docs: maintainers: add SPDX license to the file") commit 2932ba8d9c99 ("slab: Introduce kmalloc_obj() and family") commit e4c8b46b924e ("slab: Introduce kmalloc_flex() and family") commit d957b4184aee ("Documentation: deprecated.rst: kmalloc-family: mark argument as optional") commit 120a64c8021d ("Documentation: process: fix brackets") commit 079a028d6327 ("string: Remove strncpy() from the kernel") commit e551bd4109d2 ("Documentation: remove :kyb: tags") commit 4971ca2007e3 ("docs: process: email-client: add Thunderbird "Toggle Line Wrap" extension") commit f44a29784f68 ("Documentation: update maintainer-pgp-guide for latest best practices") commit 273aa250f138 ("Documentation: Improve wording on requirements for a free Nitrokey") commit 6c5c07bc8589 ("docs: process: maintainer-pgp-guide: update kernel.org docs link") commit 54857c52452a ("docs: maintainer-pgp-guide.rst: add a reference for kernel.org sign") commit a556bd882b94 ("docs: align with scripts/syscall.tbl migration") commit e5b1c0fa4ff2 ("Documentation: Remove :manpage: from non-existing man pages") commit 78a00cac1e96 ("docs: fix 're-use' -> 'reuse' in documentation") commit ec6fd28baf61 ("docs: remove unneeded maintainer_handbooks_main label") commit 8eae6da5f56c ("docs: auto-generate maintainer entry profile links") commit bda185c30593 ("docs: maintainers_include: Only show main entry for profiles") commit 2783096fb1dd ("docs: submit-checklist: Expand on build tests against different word sizes") commit fb12098d8ee4 ("docs: submit-checklist: Allow creating cross-references for ABI README") commit 5a63f0369bda ("docs/.../submit-checklist: Use Documentation/admin-guide/abi.rst for cross-ref of README") commit e5880f95a979 ("docs: process: discourage pointless boilerplate kdoc") commit 2c62e2e874d1 ("coding-style: fix verb typo") commit 197bbebd2581 ("docs: Update documentation to avoid mentioning of kernel.h") commit eba6ffd126cd ("docs: kdoc: move kernel-doc to tools/docs") commit 7c6d969d5349 ("Documentation: adopt new coding style of type-aware kmalloc-family") commit 323fa4b9608b ("Documentation: Fix syntax of kmalloc_objs example in coding style doc") commit d49172bbd7eb ("Documentation: clarify the expected collaboration with security bugs reporters") commit 3a68841d1d9b ("Documentation: smooth the text flow in the security bug reporting process") commit ceddb2c001d9 ("Documentation: insist on the plain-text requirement for security reports") commit f2b1cbef1536 ("Documentation: minor updates to the security contacts") commit a72b832a4823 ("Documentation: explain how to find maintainers addresses for security reports") commit 496fa1befba1 ("Documentation: clarify the mandatory and desirable info for security reports") commit f387e2e2b9d3 ("Documentation: fix two typos in latest update to the security report howto") commit aed3c3346765 ("Documentation: security-bugs: do not systematically Cc the security team") commit 4bf85afb9f3e ("Documentation: security-bugs: clarify requirements for AI-assisted reports") commit 561458db0d6b ("docs: security-bugs: add a link to the threat-model documentation") commit 5f5e7344322f ("kbuild: generate offset range data for builtin modules") commit 41047d53bcff ("docs:process:changes: fix version command for btrfs-progs") commit 82a1978d0fdc ("kheaders: use 'tar' instead of 'cpio' for copying files") commit d2b239099cf0 ("docs: changes: update Sphinx minimal version to 3.4.3") commit 5e25b972a22b ("docs: changes: update Python minimal version") commit 118c40b7b503 ("kbuild: require gcc-8 and binutils-2.30") commit 28d51df0dbaa ("Documentation: update binutils-2.30 version reference") commit fc6edeea53f4 ("docs: Remove reiserfsprogs from dependencies.") commit bc20c56e98e0 ("docs: changes: better document Python needs") commit 20c098928356 ("kbuild: Bump minimum version of LLVM for building the kernel to 15.0.0") commit 903922cfa0e6 ("lib/Kconfig.debug: Set the minimum required pahole version to v1.22") commit 8913632998fc ("Documentation: Fix typos and grammatical errors") commit c99fcb58501e ("docs: Fix an erroneous reference to sphinx.rst") commit d8a224f519c6 ("docs: changes/ver_linux: fix entries and add several tools") commit ece7e57afd51 ("docs: changes.rst and ver_linux: sort the lists") commit f32fb9c58a5b ("rust: bump Rust minimum supported version to 1.85.0 (Debian Trixie)") commit c3a00a3f31ff ("rust: bump `bindgen` minimum supported version to 0.71.1 (Debian Trixie)") commit ce3267a39a92 ("kbuild: Bump minimum version of LLVM for building the kernel to 17.0.1") commit 2c1ccd9a1d78 ("docs: changes.rst: restore pahole 1.26 minimum (regressed by sort)") commit 3f997cbf676b ("docs: process: submitting-patches: split canonical patch format section") commit 6356f18f09dc ("Align git commit ID abbreviation guidelines and checks") commit cd9123eeb224 ("docs: submitting-patches: clarify Acked-by and introduce "# Suffix"") commit 25fb101385f7 ("docs: submitting-patches: clarify difference between Acked-by and Reviewed-by") commit 08c035da54a3 ("docs: submitting-patches: clarify that signers may use their discretion on tags") commit 95767a592dc9 ("docs: submitting-patches: document the format for affiliation") commit dc896f853e1a ("docs: submitting-patches: adjust Fixes definition slightly") commit 22014a230093 ("Documentation/process: submitting-patches: fix typo in "were do"") commit e36a7b1e1734 ("docs: submitting-patches: Clarify that removal of Acks needs explanation too") commit 8a12e3fbf2c3 ("docs: submitting-patches: suggest adding previous version links") commit 6252e5c1c20e ("docs: add an Assisted-by mention to submitting-patches.rst") commit 48c3876a6a6f ("docs: submitting-patches: Clarify that "reviewer" is a person") commit 83f71fbc66fb ("docs: submitting-patches: Fix section structure around DCO") Signed-off-by: Federico Vaga Signed-off-by: Jonathan Corbet Message-ID: <20260816193906.68808-1-federico.vaga@vaga.pv.it> --- .../translations/it_IT/process/1.Intro.rst | 14 +- .../translations/it_IT/process/2.Process.rst | 57 +++-- .../translations/it_IT/process/5.Posting.rst | 34 ++- .../it_IT/process/7.AdvancedTopics.rst | 2 +- .../it_IT/process/adding-syscalls.rst | 94 ++++++++ .../it_IT/process/coding-style.rst | 33 +-- .../translations/it_IT/process/deprecated.rst | 83 +++++-- .../it_IT/process/email-clients.rst | 9 +- .../translations/it_IT/process/index.rst | 8 + .../it_IT/process/license-rules.rst | 16 +- .../it_IT/process/maintainer-handbooks.rst | 15 +- .../it_IT/process/maintainer-pgp-guide.rst | 170 +++++++------- .../it_IT/process/maintainers.rst | 2 + .../it_IT/process/programming-language.rst | 6 +- .../it_IT/process/submit-checklist.rst | 9 +- .../it_IT/process/submitting-patches.rst | 212 ++++++++++++------ 16 files changed, 519 insertions(+), 245 deletions(-) diff --git a/Documentation/translations/it_IT/process/1.Intro.rst b/Documentation/translations/it_IT/process/1.Intro.rst index c1be6dc398a7..03b61ccf5882 100644 --- a/Documentation/translations/it_IT/process/1.Intro.rst +++ b/Documentation/translations/it_IT/process/1.Intro.rst @@ -279,13 +279,13 @@ una versione 3 della licenza GPL nel prossimo futuro. È imperativo che tutto il codice che contribuisce al kernel sia legittimamente software libero. Per questa ragione, un codice proveniente da un contributore -anonimo (o sotto pseudonimo) non verrà accettato. È richiesto a tutti i -contributori di firmare il proprio codice, attestando così che quest'ultimo -può essere distribuito insieme al kernel sotto la licenza GPL. Il codice che -non è stato licenziato come software libero dal proprio creatore, o che -potrebbe creare problemi di copyright per il kernel (come il codice derivante -da processi di ingegneria inversa senza le opportune tutele), non può essere -diffuso. +la cui identità non è nota, o da un contributore anonimo, non verrà accettato. +È richiesto a tutti i contributori di firmare il proprio codice, attestando +così che quest'ultimo può essere distribuito insieme al kernel sotto la licenza +GPL. Il codice che non è stato licenziato come software libero dal proprio +creatore, o che potrebbe creare problemi di copyright per il kernel (come il +codice derivante da processi di ingegneria inversa senza le opportune tutele), +non può essere diffuso. Domande relative a questioni legate al copyright sono frequenti nelle liste di discussione dedicate allo sviluppo di Linux. Tali quesiti, normalmente, diff --git a/Documentation/translations/it_IT/process/2.Process.rst b/Documentation/translations/it_IT/process/2.Process.rst index 6262c3908665..5da70c6f39a7 100644 --- a/Documentation/translations/it_IT/process/2.Process.rst +++ b/Documentation/translations/it_IT/process/2.Process.rst @@ -18,25 +18,22 @@ processo si svolge per poter esserne parte attiva. Il quadro d'insieme ------------------- -Gli sviluppatori kernel utilizzano un calendario di rilascio generico, dove -ogni due o tre mesi viene effettuata un rilascio importante del kernel. -I rilasci più recenti sono stati: +Il kernel Linux utilizza un modello di sviluppo a rilascio continuo, +vagamente basato sul tempo. Un nuovo rilascio principale del kernel (che +chiameremo, come esempio, 9.x) [1]_ avviene ogni due o tre mesi, e porta +con sé nuove funzionalità, modifiche interne alle API e molto altro. Un +tipico rilascio può contenere circa 13.000 gruppi di modifiche che toccano +diverse centinaia di migliaia di righe di codice. I rilasci più recenti, +assieme alle rispettive date, si possono trovare su `Wikipedia +`_. - ====== ================= - 5.0 3 marzo, 2019 - 5.1 5 maggio, 2019 - 5.2 7 luglio, 2019 - 5.3 15 settembre, 2019 - 5.4 24 novembre, 2019 - 5.5 6 gennaio, 2020 - ====== ================= - -Ciascun rilascio 5.x è un importante rilascio del kernel con nuove -funzionalità, modifiche interne dell'API, e molto altro. Un tipico -rilascio contiene quasi 13,000 gruppi di modifiche con ulteriori -modifiche a parecchie migliaia di linee di codice. La 5.x. è pertanto la -linea di confine nello sviluppo del kernel Linux; il kernel utilizza un sistema -di sviluppo continuo che integra costantemente nuove importanti modifiche. +.. [1] A rigor di termini, il kernel Linux non utilizza uno schema di + numerazione semantica delle versioni (semantic versioning), bensì + la coppia 9.x identifica la versione del rilascio principale come + numero intero. Per ogni rilascio, x viene incrementato, mentre + 9 viene incrementato solo quando x è ritenuto sufficientemente + grande (per esempio, Linux 5.0 è stato rilasciato dopo Linux + 4.20). Viene seguita una disciplina abbastanza lineare per l'inclusione delle patch di ogni rilascio. All'inizio di ogni ciclo di sviluppo, la @@ -55,8 +52,8 @@ verrà descritto dettagliatamente più avanti). La finestra di inclusione resta attiva approssimativamente per due settimane. Al termine di questo periodo, Linus Torvald dichiarerà che la finestra è chiusa e rilascerà il primo degli "rc" del kernel. -Per il kernel che è destinato ad essere 5.6, per esempio, il rilascio -che emerge al termine della finestra d'inclusione si chiamerà 5.6-rc1. +Per il kernel che è destinato ad essere 9.x, per esempio, il rilascio +che emerge al termine della finestra d'inclusione si chiamerà 9.x-rc1. Questo rilascio indica che il momento di aggiungere nuovi componenti è passato, e che è iniziato il periodo di stabilizzazione del prossimo kernel. @@ -109,17 +106,19 @@ tipo di perfezione difficilmente viene raggiunta; esistono troppe variabili in un progetto di questa portata. Arriva un punto dove ritardare il rilascio finale peggiora la situazione; la quantità di modifiche in attesa della prossima finestra di inclusione crescerà enormemente, creando ancor più -regressioni al giro successivo. Quindi molti kernel 5.x escono con una +regressioni al giro successivo. Quindi molti kernel escono con una manciata di regressioni delle quali, si spera, nessuna è grave. Una volta che un rilascio stabile è fatto, il suo costante mantenimento è -affidato al "squadra stabilità", attualmente composta da Greg Kroah-Hartman. -Questa squadra rilascia occasionalmente degli aggiornamenti relativi al -rilascio stabile usando la numerazione 5.x.y. Per essere presa in -considerazione per un rilascio d'aggiornamento, una modifica deve: -(1) correggere un baco importante (2) essere già inserita nel ramo principale -per il prossimo sviluppo del kernel. Solitamente, passato il loro rilascio -iniziale, i kernel ricevono aggiornamenti per più di un ciclo di sviluppo. +affidato alla "squadra stabilità", attualmente composta da Greg Kroah-Hartman +e Sasha Levin. Questa squadra rilascia occasionalmente degli aggiornamenti +relativi al rilascio stabile usando la numerazione 9.x.y. + +Per essere presa in considerazione per un rilascio d'aggiornamento, una +modifica deve: (1) correggere un baco importante (2) essere già inserita nel +ramo principale per il prossimo sviluppo del kernel. Solitamente, passato il +loro rilascio iniziale, i kernel ricevono aggiornamenti per più di un ciclo di +sviluppo. Quindi, per esempio, la storia del kernel 5.2 appare così (anno 2019): ============== =============================== @@ -314,7 +313,7 @@ the moment) all'indirizzo: frustrante; ci sono buone probabilità che non compili nemmeno. I sorgenti principali per il prossimo ciclo d'integrazione delle patch -è linux-next, gestito da Stephen Rothwell. I sorgenti linux-next sono, per +è linux-next, gestito da Mark Brown. I sorgenti linux-next sono, per definizione, un'istantanea di come dovrà apparire il ramo principale dopo che la prossima finestra di inclusione si chiuderà. I linux-next sono annunciati sulla lista di discussione linux-kernel e linux-next nel momento in cui diff --git a/Documentation/translations/it_IT/process/5.Posting.rst b/Documentation/translations/it_IT/process/5.Posting.rst index 3b9b4db6fb9a..c4cf311e09b9 100644 --- a/Documentation/translations/it_IT/process/5.Posting.rst +++ b/Documentation/translations/it_IT/process/5.Posting.rst @@ -48,7 +48,14 @@ l'invio delle patch alla comunità di sviluppo. Queste cose includono: - Verificare il codice fino al massimo che vi è consentito. Usate gli strumenti di debug del kernel, assicuratevi che il kernel compili con tutte le più ragionevoli combinazioni d'opzioni, usate cross-compilatori - per compilare il codice per differenti architetture, eccetera. + per compilare il codice per differenti architetture, eccetera. Aggiungete + dei test, preferibilmente usando un framework di test già esistente come + KUnit, e includeteli come un elemento separato della vostra serie (per + maggiori informazioni sulle serie di patch, vedere la sezione successiva). + Da notare che questo può essere obbligatorio quando si toccano alcuni + sottosistemi. Per esempio, le funzioni di libreria (che risiedono in + lib/) sono usate estensivamente quasi ovunque, e ci si aspetta che siano + testate adeguatamente. - Assicuratevi che il vostro codice sia conforme alla linee guida del kernel sullo stile del codice. @@ -224,10 +231,9 @@ implementate dalla patch:: Link: https://example.com/somewhere.html optional-other-stuff -Alcuni manutentori aggiungono quest'etichetta alla patch per fare riferimento -alla più recente discussione pubblica. A volte questo è fatto automaticamente da -alcuni strumenti come b4 or un *hook* git come quello descritto qui -'Documentation/translations/it_IT/maintainer/configure-git.rst' +Come indicato dal "Chief Penguin" (soprannome di Linus Torvalds), un'etichetta +Link: dovrebbe essere aggiunta ad un commit solo se conduce a informazioni +utili che non si trovano già nel commit stesso. Se il collegamento indirizza verso un rapporto su un baco risolto dalla patch, @@ -284,13 +290,23 @@ Le etichette in uso più comuni sono: Se esiste un rapporto disponibile sul web, allora L'etichetta dovrebbe essere seguita da un collegamento al suddetto rapporto. + - Suggested-by: indica che l'idea della patch è stata suggerita dalla persona + menzionata, ed assicura che le venga dato credito per l'idea. Questo, si + spera, la inviterà ad aiutarci ancora in futuro. + - Cc: la persona menzionata ha ricevuto una copia della patch ed ha avuto l'opportunità di commentarla. -State attenti ad aggiungere queste etichette alla vostra patch: solo "Cc:" può -essere aggiunta senza il permesso esplicito della persona menzionata. Il più -delle volte anche Reported-by: va bene, ma è sempre meglio chiedere specialmente -se il baco è stato riportato in una comunicazione privata. +State attenti ad aggiungere le suddette etichette alla vostra patch: tutte, +tranne Cc:, Reported-by: e Suggested-by:, richiedono il permesso esplicito +della persona menzionata. Per queste tre è sufficiente un permesso implicito, +se la persona ha contribuito al kernel Linux usando quel nome e quell'indirizzo +email secondo gli archivi di lore o la cronologia dei commit -- e, nel caso di +Reported-by: e Suggested-by:, se la segnalazione o il suggerimento sono +avvenuti pubblicamente. Da notare che bugzilla.kernel.org è, in questo senso, +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. Inviare la modifica ------------------- diff --git a/Documentation/translations/it_IT/process/7.AdvancedTopics.rst b/Documentation/translations/it_IT/process/7.AdvancedTopics.rst index b3d8b62f3b57..592de7de4d99 100644 --- a/Documentation/translations/it_IT/process/7.AdvancedTopics.rst +++ b/Documentation/translations/it_IT/process/7.AdvancedTopics.rst @@ -60,7 +60,7 @@ Quando sarete in grado di creare rami git che siano guardabili da altri, vi servirà, ovviamente, un server dal quale sia possibile attingere le vostre modifiche. Se avete un server accessibile da Internet, configurarlo per eseguire git-daemon è relativamente semplice . Altrimenti, iniziano a -svilupparsi piattaforme che offrono spazi pubblici, e gratuiti (Github, +svilupparsi piattaforme che offrono spazi pubblici, e gratuiti (GitHub, per esempio). Gli sviluppatori permanenti possono ottenere un account su kernel.org, ma non è proprio facile da ottenere; per maggiori informazioni consultate la pagina web https://kernel.org/faq/. diff --git a/Documentation/translations/it_IT/process/adding-syscalls.rst b/Documentation/translations/it_IT/process/adding-syscalls.rst index c4ed6dbf5f05..a21c9af1e2cb 100644 --- a/Documentation/translations/it_IT/process/adding-syscalls.rst +++ b/Documentation/translations/it_IT/process/adding-syscalls.rst @@ -278,6 +278,56 @@ Per riassumere, vi serve un *commit* che includa: - *stub* di ripiego in ``kernel/sys_ni.c`` +.. _it_syscall_generic_6_11: + +Dalla versione 6.11 +~~~~~~~~~~~~~~~~~~~~ + +A partire dalla versione 6.11 del kernel, l'implementazione generica delle +chiamate di sistema per le seguenti architetture non richiede più modifiche +a ``include/uapi/asm-generic/unistd.h``: + + - arc + - arm64 + - csky + - hexagon + - loongarch + - nios2 + - openrisc + - riscv + +Al suo posto, dovete aggiornare ``scripts/syscall.tbl`` e, se necessario, +modificare ``arch/*/kernel/Makefile.syscalls``. + +Dato che ``scripts/syscall.tbl`` funge da tabella comune delle chiamate di +sistema condivisa fra più architetture, in questa tabella è richiesto un +nuovo elemento:: + + 468 common xyzzy sys_xyzzy + +Da notare che l'aggiunta di un elemento a ``scripts/syscall.tbl`` con l'ABI +"common" influisce anche su tutte le architetture che condividono questa +tabella. Per modifiche più limitate o specifiche di un'architettura, +considerate l'uso di un'ABI specifica per l'architettura, o la definizione +di una nuova. + +Se viene introdotta una nuova ABI, per esempio ``xyz``, andranno fatti i +corrispondenti aggiornamenti anche in ``arch/*/kernel/Makefile.syscalls``:: + + syscall_abis_{32,64} += xyz (...) + +Per riassumere, vi serve un *commit* che includa: + + - un'opzione ``CONFIG`` per la nuova funzione, normalmente in + ``init/Kconfig`` + - ``SYSCALL_DEFINEn(xyzzy, ...)`` per il punto d'accesso + - il corrispondente prototipo in ``include/linux/syscalls.h`` + - un nuovo elemento in ``scripts/syscall.tbl`` + - (se necessario) aggiornamenti al Makefile in + ``arch/*/kernel/Makefile.syscalls`` + - *stub* di ripiego in ``kernel/sys_ni.c`` + + Implementazione delle chiamate di sistema x86 --------------------------------------------- @@ -396,6 +446,47 @@ Riassumendo, vi serve: - una voce ``__SC_COMP``, e non ``__SYSCALL``, in ``include/uapi/asm-generic/unistd.h`` + +Dalla versione 6.11 +~~~~~~~~~~~~~~~~~~~~ + +Questo si applica a tutte le architetture elencate in +:ref:`Dalla versione 6.11` sotto "Implementazione +di chiamate di sistema generiche", eccetto arm64. Vedere +:ref:`Chiamate di sistema compatibili (arm64)` per maggiori +informazioni. + +Dovete estendere la voce in ``scripts/syscall.tbl`` con una colonna +aggiuntiva per indicare che un programma in spazio utente a 32-bit in +esecuzione su un kernel a 64-bit deve invocare il punto d'accesso +*compatibile*:: + + 468 common xyzzy sys_xyzzy compat_sys_xyzzy + +Riassumendo, vi serve: + + - un ``COMPAT_SYSCALL_DEFINEn(xyzzy, ...)`` per il punto d'accesso + *compatibile* + - il corrispondente prototipo in ``include/linux/compat.h`` + - la modifica della voce in ``scripts/syscall.tbl`` per includere una + colonna "compat" aggiuntiva + - (se necessario) una struttura di mappatura a 32-bit in + ``include/linux/compat.h`` + + +.. _it_compat_arm64: + +Chiamate di sistema compatibili (arm64) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Su arm64 esiste una tabella delle chiamate di sistema dedicata per le +chiamate di sistema compatibili rivolte allo spazio utente a 32-bit +(AArch32): ``arch/arm64/tools/syscall_32.tbl``. Dovete aggiungere una riga +aggiuntiva a questa tabella specificando il punto d'accesso *compatibile*:: + + 468 common xyzzy sys_xyzzy compat_sys_xyzzy + + Compatibilità delle chiamate di sistema (x86) --------------------------------------------- @@ -641,3 +732,6 @@ Riferimenti e fonti - Raccomandazioni da Linus Torvalds che le chiamate di sistema x32 dovrebbero favorire la compatibilità con le versioni a 64-bit piuttosto che quelle a 32-bit: https://lore.kernel.org/r/CA+55aFxfmwfB7jbbrXxa=K7VBYPfAvmu3XOkGrLbB1UFjX1+Ew@mail.gmail.com + - Serie di patch che revisiona l'infrastruttura della tabella delle chiamate + di sistema per usare scripts/syscall.tbl su più architetture: + https://lore.kernel.org/lkml/20240704143611.2979589-1-arnd@kernel.org diff --git a/Documentation/translations/it_IT/process/coding-style.rst b/Documentation/translations/it_IT/process/coding-style.rst index 2a499412a2e3..b0cf138249c2 100644 --- a/Documentation/translations/it_IT/process/coding-style.rst +++ b/Documentation/translations/it_IT/process/coding-style.rst @@ -604,8 +604,11 @@ il PERCHÉ. Per favore, quando commentate una funzione dell'API del kernel usate il formato kernel-doc. Per maggiori dettagli, leggete i file in -:ref::ref:`Documentation/translations/it_IT/doc-guide/ ` e in -``script/kernel-doc``. +:ref:`Documentation/translations/it_IT/doc-guide/ ` e in +``tools/docs/kernel-doc``. Da notare che il pericolo di commentare troppo +si applica anche ai commenti kernel-doc. Non aggiungete kernel-doc +superfluo che si limita a ripetere quanto già ovvio dalla firma della +funzione. Lo stile preferito per i commenti più lunghi (multi-riga) è: @@ -935,7 +938,7 @@ racchiusa in #ifdef, potete usare printk(KERN_DEBUG ...). --------------------- Il kernel fornisce i seguenti assegnatori ad uso generico: -kmalloc(), kzalloc(), kmalloc_array(), kcalloc(), vmalloc(), e vzalloc(). +kmalloc(), kzalloc(), kmalloc_objs(), kzalloc_objs(), vmalloc(), e vzalloc(). Per maggiori informazioni, consultate la documentazione dell'API: :ref:`Documentation/translations/it_IT/core-api/memory-allocation.rst ` @@ -957,13 +960,13 @@ Il modo preferito per assegnare un vettore è il seguente: .. code-block:: c - p = kmalloc_array(n, sizeof(...), ...); + p = kmalloc_objs(*p, n, ...); Il modo preferito per assegnare un vettore a zero è il seguente: .. code-block:: c - p = kcalloc(n, sizeof(...), ...); + p = kzalloc_objs(*p, n, ...); Entrambe verificano la condizione di overflow per la dimensione d'assegnamento n * sizeof(...), se accade ritorneranno NULL. @@ -1068,15 +1071,17 @@ può migliorare la leggibilità. 18) Non reinventate le macro del kernel --------------------------------------- -Il file di intestazione include/linux/kernel.h contiene un certo numero -di macro che dovreste usare piuttosto che implementarne una qualche variante. -Per esempio, se dovete calcolare la lunghezza di un vettore, sfruttate la -macro: +Ci sono molti file d'intestazione in include/linux/ che contengono un certo +numero di macro che dovreste usare piuttosto che implementarne una qualche +variante. Per esempio, se dovete calcolare la lunghezza di un vettore, +sfruttate la macro: .. code-block:: c #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) +che è definita in array_size.h. + Analogamente, se dovete calcolare la dimensione di un qualche campo di una struttura, usate @@ -1084,10 +1089,12 @@ struttura, usate #define sizeof_field(t, f) (sizeof(((t*)0)->f)) -Ci sono anche le macro min() e max() che, se vi serve, effettuano un controllo -rigido sui tipi. Sentitevi liberi di leggere attentamente questo file -d'intestazione per scoprire cos'altro è stato definito che non dovreste -reinventare nel vostro codice. +che è definita in stddef.h. + +Ci sono anche le macro min() e max(), definite in minmax.h, che, se vi +serve, effettuano un controllo rigido sui tipi. Sentitevi liberi di +leggere attentamente questi file d'intestazione per scoprire cos'altro è +stato definito che non dovreste reinventare nel vostro codice. 19) Linee di configurazione degli editor e altre schifezze ----------------------------------------------------------- diff --git a/Documentation/translations/it_IT/process/deprecated.rst b/Documentation/translations/it_IT/process/deprecated.rst index d4ab76e9be49..64b7bf40057f 100644 --- a/Documentation/translations/it_IT/process/deprecated.rst +++ b/Documentation/translations/it_IT/process/deprecated.rst @@ -141,26 +141,37 @@ ritorno di strcpy(). La funzione strscpy() non ritorna un puntatore alla destinazione, ma un contatore dei byte non NUL copiati (oppure un errno negativo se la stringa è stata troncata). -strncpy() su stringe terminate con NUL --------------------------------------- -L'utilizzo di strncpy() non fornisce alcuna garanzia sul fatto che -il buffer di destinazione verrà terminato con il carattere NUL. Questo -potrebbe portare a diversi overflow di lettura o altri malfunzionamenti -causati, appunto, dalla mancanza del terminatore. Questa estende la -terminazione nel buffer di destinazione quando la stringa d'origine è più -corta; questo potrebbe portare ad una penalizzazione delle prestazioni per -chi usa solo stringe terminate. La versione sicura da usare è -strscpy(), tuttavia va prestata attenzione a tutti quei casi dove -viene usato il valore di ritorno di strncpy(). La funzione strscpy() -non ritorna un puntatore alla destinazione, ma un contatore dei byte -non NUL copiati (oppure un errno negativo se la stringa è stata -troncata). Tutti i casi che necessitano di estendere la -terminazione con NUL dovrebbero usare strscpy_pad(). +strncpy() +--------- +La funzione strncpy() è stata rimossa dal kernel. Tutti i chiamanti che +la usavano sono stati migrati verso alternative più sicure. -Se il chiamate no usa stringhe terminate con NUL, allore strncpy() -può continuare ad essere usata, ma i buffer di destinazione devono essere -marchiati con l'attributo `__nonstring `_ -per evitare avvisi durante la compilazione. +strncpy() non garantiva che il buffer di destinazione venisse terminato +con il carattere NUL, causando overflow di lettura lineari e altri +malfunzionamenti. Inoltre estendeva incondizionatamente la terminazione +NUL nel buffer di destinazione, il che era una penalizzazione delle +prestazioni superflua per i chiamanti che usavano solo stringhe +terminate con NUL. A causa dei suoi vari comportamenti, si trattava di +un'API ambigua per determinare quale fosse la reale intenzione +dell'autore per la copia. + +I sostituti di strncpy() sono: + +- strscpy(), quando la destinazione deve essere terminata con NUL. +- strscpy_pad(), quando la destinazione deve essere terminata con NUL + ed estesa con zeri (per esempio, strutture che attraversano i confini + dei privilegi). +- memtostr(), per destinazioni terminate con NUL a partire da sorgenti + a larghezza fissa non terminate con NUL (con l'attributo + `__nonstring` sulla sorgente). +- memtostr_pad(), per lo stesso caso, ma con estensione tramite zeri. +- strtomem(), per destinazioni a larghezza fissa non terminate con NUL, + con l'attributo `__nonstring` sulla destinazione. +- strtomem_pad(), per destinazioni non terminate con NUL che + necessitano anche di estensione tramite zeri. +- memcpy_and_pad(), per copie limitate da sorgenti potenzialmente non + terminate, quando la dimensione della destinazione è un valore + determinato a runtime. strlcpy() --------- @@ -407,3 +418,37 @@ La macro di supporto dev'essere usata:: DECLARE_FLEX_ARRAY(struct type2, two); }; }; + +Assegnazioni kmalloc con codice esplicito per oggetti struct +-------------------------------------------------------------- +Eseguire assegnazioni con codice esplicito per le allocazioni della +famiglia kmalloc() impedisce al kernel (e al compilatore) di poter +esaminare il tipo della variabile a cui viene fatta l'assegnazione, il +che limita ogni possibile introspezione utile per l'allineamento, per +l'overflow, o per un ulteriore irrobustimento. Le macro della famiglia +kmalloc_obj() forniscono questa introspezione, e possono essere usate +per i più comuni schemi di codice per l'allocazione di un singolo +oggetto, di un vettore di oggetti, o di un oggetto con un array +flessibile. Per esempio, queste assegnazioni con codice esplicito:: + + ptr = kmalloc(sizeof(*ptr), gfp); + ptr = kzalloc(sizeof(*ptr), gfp); + ptr = kmalloc_array(count, sizeof(*ptr), gfp); + ptr = kcalloc(count, sizeof(*ptr), gfp); + ptr = kmalloc(struct_size(ptr, flex_member, count), gfp); + ptr = kmalloc(sizeof(struct foo), gfp); + +diventano, rispettivamente:: + + ptr = kmalloc_obj(*ptr [, gfp] ); + ptr = kzalloc_obj(*ptr [, gfp] ); + ptr = kmalloc_objs(*ptr, count [, gfp] ); + ptr = kzalloc_objs(*ptr, count [, gfp] ); + ptr = kmalloc_flex(*ptr, flex_member, count [, gfp] ); + __auto_type ptr = kmalloc_obj(struct foo [, gfp] ); + +L'argomento gfp è opzionale, e il suo valore predefinito è GFP_KERNEL. +Se `ptr->flex_member` è annotato con __counted_by(), l'allocazione +fallirà automaticamente se `count` è più grande del valore massimo +rappresentabile che può essere memorizzato nel membro contatore +associato a `flex_member`. diff --git a/Documentation/translations/it_IT/process/email-clients.rst b/Documentation/translations/it_IT/process/email-clients.rst index 9f8fe8abab4a..677b6715b8b1 100644 --- a/Documentation/translations/it_IT/process/email-clients.rst +++ b/Documentation/translations/it_IT/process/email-clients.rst @@ -335,7 +335,14 @@ Per rendere l'editor interno un po' più sensato, fate così: - impostate ``mailnews.send_plaintext_flowed`` a ``false`` - - impostate ``mailnews.wraplength`` da ``72`` a ``0`` + - impostate ``mailnews.wraplength`` da ``72`` a ``0`` **oppure** installate + l'estensione "Toggle Line Wrap" + + https://github.com/jan-kiszka/togglelinewrap + + https://addons.thunderbird.net/thunderbird/addon/toggle-line-wrap + + per controllare questo registro al volo. - Non scrivete messaggi HTML! Andate sulla finestra principale ed aprite la schermata :menuselection:`Menu principale-->Impostazioni account-->nome@unserver.ovunque-->Composizioni e indirizzi`. diff --git a/Documentation/translations/it_IT/process/index.rst b/Documentation/translations/it_IT/process/index.rst index 5a5214f5fd72..dd88c1140cf2 100644 --- a/Documentation/translations/it_IT/process/index.rst +++ b/Documentation/translations/it_IT/process/index.rst @@ -63,6 +63,7 @@ della comunità del kernel (e oltre). .. toctree:: :maxdepth: 1 + license-rules code-of-conduct kernel-enforcement-statement kernel-driver-statement @@ -78,6 +79,11 @@ con riguardo. I documenti che seguono descrivono le nostre politiche riguardo al trattamento di alcune classi particolari di bachi: le regressioni e i problemi di sicurezza. +.. toctree:: + :maxdepth: 1 + + security-bugs + Informazioni per i manutentori ------------------------------ @@ -86,6 +92,7 @@ Come trovare le persone che accetteranno le vostre modifiche. .. toctree:: :maxdepth: 1 + maintainer-handbooks maintainers Altri documenti @@ -98,6 +105,7 @@ degli sviluppatori: :maxdepth: 1 kernel-docs + deprecated .. only:: subproject and html diff --git a/Documentation/translations/it_IT/process/license-rules.rst b/Documentation/translations/it_IT/process/license-rules.rst index 4cd87a3a7bf9..50e49146d66c 100644 --- a/Documentation/translations/it_IT/process/license-rules.rst +++ b/Documentation/translations/it_IT/process/license-rules.rst @@ -75,7 +75,10 @@ Sintassi degli identificatori di licenza possibile di un file che possa contenere commenti. Per la maggior parte dei file questa è la prima riga, fanno eccezione gli script che richiedono come prima riga '#!PATH_TO_INTERPRETER'. Per questi script l'identificativo - SPDX finisce nella seconda riga. + di licenza SPDX finisce nella seconda riga. + + Alla riga dell'identificativo di licenza possono seguire, se lo si + desidera, una o più righe SPDX-FileCopyrightText. | @@ -486,10 +489,13 @@ _`MODULE_LICENSE` file sorgenti. "Proprietary" Questo modulo è rilasciato con licenza - proprietaria. Questa stringa è solo per i - moduli proprietari di terze parti e non può - essere usata per quelli che risiedono nei - sorgenti del kernel. I moduli etichettati in + proprietaria. "Proprietary" va inteso + unicamente come "la licenza non è compatibile + con la GPLv2". Questa stringa è solo per i + moduli di terze parti non compatibili con la + GPLv2 e non può essere usata per quelli che + risiedono nei sorgenti del kernel. I moduli + etichettati in questo modo stanno contaminando il kernel e gli viene assegnato un flag 'P'; quando vengono caricati, il caricatore di moduli del diff --git a/Documentation/translations/it_IT/process/maintainer-handbooks.rst b/Documentation/translations/it_IT/process/maintainer-handbooks.rst index d840145bcceb..cc901809bbfa 100644 --- a/Documentation/translations/it_IT/process/maintainer-handbooks.rst +++ b/Documentation/translations/it_IT/process/maintainer-handbooks.rst @@ -5,8 +5,6 @@ :Original: Documentation/process/maintainer-handbooks.rst :Translator: Federico Vaga -.. _it_maintainer_handbooks_main: - Note sul processo di sviluppo dei sottosistemi e dei sorgenti dei manutentori ============================================================================= @@ -15,10 +13,13 @@ sviluppo dedicate ai sottosistemi che vanno ad integrare quelle più generali descritte in :ref:`Documentation/translations/it_IT/process `. -Indice: +Per gli sviluppatori, qui di seguito sono elencate tutte le guide specifiche +per i sottosistemi conosciute. Se il sottosistema al quale state contribuendo +non ha una guida elencata qui, è lecito chiedere chiarimenti sulle domande +sollevate in Documentation/maintainer/maintainer-entry-profile.rst. -.. toctree:: - :numbered: - :maxdepth: 2 +Per i manutentori, si consiglia di documentare ulteriori requisiti ed +aspettative se le sottomissioni trascurano sistematicamente specifici criteri +di sottomissione. Vedere Documentation/maintainer/maintainer-entry-profile.rst. - maintainer-tip +.. maintainers-profile-toc:: diff --git a/Documentation/translations/it_IT/process/maintainer-pgp-guide.rst b/Documentation/translations/it_IT/process/maintainer-pgp-guide.rst index cdc43c4a9b0b..8a9c8bd8a62d 100644 --- a/Documentation/translations/it_IT/process/maintainer-pgp-guide.rst +++ b/Documentation/translations/it_IT/process/maintainer-pgp-guide.rst @@ -1,3 +1,5 @@ +.. SPDX-License-Identifier: GPL-2.0 + .. include:: ../disclaimer-ita.rst :Original: :ref:`Documentation/process/maintainer-pgp-guide.rst ` @@ -57,7 +59,7 @@ pratiche di sicurezza messe in atto. Il principio sopra indicato è la ragione per la quale è necessaria questa guida. Vogliamo essere sicuri che il riporre la fiducia negli sviluppatori -non sia fatto semplicemente per incolpare qualcun'altro per future falle di +non sia fatto unicamente per incolpare qualcun'altro per future falle di sicurezza. L'obiettivo è quello di fornire una serie di linee guida che gli sviluppatori possano seguire per creare un ambiente di lavoro sicuro e salvaguardare le chiavi PGP usate nello stabilire l'integrità del kernel Linux @@ -68,7 +70,7 @@ stesso. Strumenti PGP ============= -Usare GnuPG 2.2 o successivo +Usare GnuPG 2.4 o successivo ---------------------------- La vostra distribuzione potrebbe avere già installato GnuPG, dovete solo @@ -77,9 +79,10 @@ usate:: $ gpg --version | head -n1 -Se state utilizzando la version 2.2 o successiva, allora siete pronti a partire. -Se invece state usando una versione precedente, allora alcuni comandi elencati -in questa guida potrebbero non funzionare. +Se state utilizzando la versione 2.4 o successiva, allora siete pronti a +partire. Se invece state usando una versione precedente, allora si tratta +di una versione di GnuPG non più mantenuta, e alcuni comandi elencati in +questa guida potrebbero non funzionare. Configurare le opzioni di gpg-agent ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -214,12 +217,6 @@ possano ricevere la vostra nuova sottochiave:: $ gpg --send-key [fpr] -.. note:: Supporto ECC in GnuPG - - Tenete presente che se avete intenzione di usare un dispositivo che non - supporta chiavi ED25519 ECC, allora dovreste usare "nistp256" al posto di - "ed25519". Più avanti ci sono alcune raccomandazioni per i dispositivi. - Copia di riserva della chiave primaria per gestire il recupero da disastro -------------------------------------------------------------------------- @@ -227,7 +224,7 @@ Maggiori sono le firme di altri sviluppatori che vengono applicate alla vostra, maggiori saranno i motivi per avere una copia di riserva che non sia digitale, al fine di effettuare un recupero da disastro. -Il modo migliore per creare una copia fisica della vostra chiave privata è +Un buon modo per creare una copia fisica della vostra chiave privata è l'uso del programma ``paperkey``. Consultate ``man paperkey`` per maggiori dettagli sul formato dell'output ed i suoi punti di forza rispetto ad altre soluzioni. Paperkey dovrebbe essere già pacchettizzato per la maggior parte @@ -238,11 +235,11 @@ vostra chiave privata:: $ gpg --export-secret-key [fpr] | paperkey -o /tmp/key-backup.txt -Stampate il file (o fate un pipe direttamente verso lpr), poi prendete -una penna e scrivete la passphare sul margine del foglio. **Questo è -caldamente consigliato** perché la copia cartacea è comunque criptata con -la passphrase, e se mai doveste cambiarla non vi ricorderete qual'era al -momento della creazione di quella copia -- *garantito*. +Stampate il file, poi prendete una penna e scrivete la passphare sul +margine del foglio. **Questo è caldamente consigliato** perché la copia +cartacea è comunque criptata con la passphrase, e se mai doveste +cambiarla non vi ricorderete qual'era al momento della creazione di +quella copia -- *garantito*. Mettete la copia cartacea e la passphrase scritta a mano in una busta e mettetela in un posto sicuro e ben protetto, preferibilmente fuori casa, @@ -250,10 +247,9 @@ magari in una cassetta di sicurezza in banca. .. note:: - Probabilmente la vostra stampante non è più quello stupido dispositivo - connesso alla porta parallela, ma dato che il suo output è comunque - criptato con la passphrase, eseguire la stampa in un sistema "cloud" - moderno dovrebbe essere comunque relativamente sicuro. + La chiave è comunque criptata con la vostra passphrase, quindi + stampare anche con stampanti moderne "integrate nel cloud" dovrebbe + rimanere un'operazione relativamente sicura. Copia di riserva di tutta la cartella GnuPG ------------------------------------------- @@ -269,17 +265,17 @@ prontezza rispetto al recupero da disastro che abbiamo risolto con vostra chiave Certify -- ovvero quando fate modifiche alle vostre chiavi o firmate le chiavi di altre persone ad una conferenza o ad un gruppo d'incontro. -Incominciate con una piccola chiavetta di memoria USB (preferibilmente due) -che userete per le copie di riserva. Dovrete criptarle usando LUKS -- fate -riferimento alla documentazione della vostra distribuzione per capire come -fare. +Incominciate con un supporto di memoria esterno (preferibilmente due) che +userete per le copie di riserva. Dovrete creare su questo dispositivo una +partizione criptata usando LUKS -- fate riferimento alla documentazione +della vostra distribuzione per capire come fare. Per la passphrase di criptazione, potete usare la stessa della vostra chiave primaria. -Una volta che il processo di criptazione è finito, reinserite il disco USB ed -assicurativi che venga montato correttamente. Copiate interamente la cartella -``.gnugp`` nel disco criptato:: +Una volta che il processo di criptazione è finito, reinserite il vostro +dispositivo ed assicurativi che venga montato correttamente. Copiate +interamente la cartella ``.gnugp`` nel disco criptato:: $ cp -a ~/.gnupg /media/disk/foo/gnupg-backup @@ -288,11 +284,11 @@ Ora dovreste verificare che tutto continui a funzionare:: $ gpg --homedir=/media/disk/foo/gnupg-backup --list-key [fpr] Se non vedete errori, allora dovreste avere fatto tutto con successo. -Smontate il disco USB, etichettatelo per bene di modo da evitare di -distruggerne il contenuto non appena vi serve una chiavetta USB a caso, ed -infine mettetelo in un posto sicuro -- ma non troppo lontano, perché vi servirà -di tanto in tanto per modificare le identità, aggiungere o revocare -sottochiavi, o firmare le chiavi di altre persone. +Smontate il dispositivo, etichettatelo per bene di modo da evitare di +sovrascriverlo per errore, ed infine mettetelo in un posto sicuro -- ma non +troppo lontano, perché vi servirà di tanto in tanto per modificare le +identità, aggiungere o revocare sottochiavi, o firmare le chiavi di altre +persone. Togliete la chiave primaria dalla vostra home --------------------------------------------- @@ -321,7 +317,7 @@ cartella home e la si archivia su un dispositivo disconnesso. che stiamo per fare renderà la vostra chiave inutile se non avete delle copie di riserva utilizzabili! -Per prima cosa, identificate il keygrip della vostra chiave primaria:: +Per prima cosa, identificate il "keygrip" della vostra chiave primaria:: $ gpg --with-keygrip --list-key [fpr] @@ -346,7 +342,7 @@ ad un file nella cartella ``~/.gnupg``:: 2222000000000000000000000000000000000000.key 3333000000000000000000000000000000000000.key -Quello che dovrete fare è rimuovere il file .key che corrisponde al keygrip +È sufficiente rimuovere il file .key che corrisponde al keygrip della chiave primaria:: $ cd ~/.gnupg/private-keys-v1.d @@ -391,8 +387,9 @@ Inoltre, ogni volta che viene fatta un'operazione con GnuPG, le chiavi vengono caricate nella memoria di sistema e potrebbero essere rubate con l'uso di malware sofisticati (pensate a Meltdown e a Spectre). -Il miglior modo per proteggere le proprie chiave è di spostarle su un -dispositivo specializzato in grado di effettuare operazioni smartcard. +Un buon modo per proteggere completamente le vostre chiavi è di spostarle +su un dispositivo specializzato in grado di effettuare operazioni +smartcard. I benefici di una smartcard --------------------------- @@ -401,12 +398,13 @@ Una smartcard contiene un chip crittografico che è capace di immagazzinare le chiavi private ed effettuare operazioni crittografiche direttamente sulla carta stessa. Dato che la chiave non lascia mai la smartcard, il sistema operativo usato sul computer non sarà in grado di accedere alle chiavi. -Questo è molto diverso dai dischi USB criptati che abbiamo usato allo scopo di -avere una copia di riserva sicura -- quando il dispositivo USB è connesso e -montato, il sistema operativo potrà accedere al contenuto delle chiavi private. +Questo è molto diverso dal dispositivo di memoria criptato che abbiamo usato +allo scopo di avere una copia di riserva sicura -- quando quel dispositivo +è connesso e montato, il sistema operativo potrà accedere al contenuto +delle chiavi private. -L'uso di un disco USB criptato non può sostituire le funzioni di un dispositivo -capace di operazioni di tipo smartcard. +L'uso di un dispositivo di memoria esterno criptato non può sostituire le +funzioni di un dispositivo capace di operazioni di tipo smartcard. Dispositivi smartcard disponibili --------------------------------- @@ -417,28 +415,27 @@ implementi le funzionalità delle smartcard. Sul mercato ci sono diverse soluzioni disponibili: - `Nitrokey Start`_: è Open hardware e Free Software, è basata sul progetto - `GnuK`_ della FSIJ. Questo è uno dei pochi dispositivi a supportare le chiavi - ECC ED25519, ma offre meno funzionalità di sicurezza (come la resistenza - alla manomissione o alcuni attacchi ad un canale laterale). -- `Nitrokey Pro 2`_: è simile alla Nitrokey Start, ma è più resistente alla - manomissione e offre più funzionalità di sicurezza. La Pro 2 supporta la - crittografia ECC (NISTP). + `GnuK`_ della FSIJ. È una delle opzioni più economiche, ma offre meno + funzionalità di sicurezza (come la resistenza alla manomissione o alcuni + attacchi ad un canale laterale). +- `Nitrokey 3`_: è simile alla Nitrokey Start, ma è più resistente alla + manomissione, offre più funzionalità di sicurezza e diverse forme USB. + Supporta la crittografia ECC (ED25519 e NISTP). - `Yubikey 5`_: l'hardware e il software sono proprietari, ma è più economica - della Nitrokey Pro ed è venduta anche con porta USB-C il che è utile con i - computer portatili più recenti. In aggiunta, offre altre funzionalità di - sicurezza come FIDO, U2F, e ora supporta anche le chiavi ECC (NISTP) + della Nitrokey a parità di funzionalità. Supporta la crittografia ECC + (ED25519 e NISTP). La vostra scelta dipenderà dal costo, la disponibilità nella vostra regione, e sulla scelta fra dispositivi aperti e proprietari. .. note:: - Se siete nella lista MAINTAINERS o avete un profilo su kernel.org, allora - `potrete avere gratuitamente una Nitrokey Start`_ grazie alla fondazione - Linux. + Se siete elencati in una voce `M:` nel file MAINTAINERS o avete un + profilo su kernel.org, allora `potrete avere gratuitamente una + Nitrokey Start`_ grazie alla fondazione Linux. -.. _`Nitrokey Start`: https://shop.nitrokey.com/shop/product/nitrokey-start-6 -.. _`Nitrokey Pro 2`: https://shop.nitrokey.com/shop/product/nitrokey-pro-2-3 +.. _`Nitrokey Start`: https://www.nitrokey.com/products/nitrokeys +.. _`Nitrokey 3`: https://www.nitrokey.com/products/nitrokeys .. _`Yubikey 5`: https://www.yubico.com/product/yubikey-5-overview/ .. _Gnuk: https://www.fsij.org/doc-gnuk/ .. _`potrete avere gratuitamente una Nitrokey Start`: https://www.kernel.org/nitrokey-digital-tokens-for-kernel-developers.html @@ -474,7 +471,7 @@ dell'amministratore viene usato così raramente che è inevitabile dimenticarsel se non lo si annota. Tornando al nostro menu, potete impostare anche altri valori (come il nome, -il sesso, informazioni d'accesso, eccetera), ma non sono necessari e aggiunge +il genere, informazioni d'accesso, eccetera), ma non sono necessari e aggiunge altre informazioni sulla carta che potrebbero trapelare in caso di smarrimento. .. note:: @@ -636,7 +633,7 @@ eseguite:: Se per voi è più facile da memorizzare, potete anche utilizzare una data specifica (per esempio, il vostro compleanno o capodanno):: - $ gpg --quick-set-expire [fpr] 2025-07-01 + $ gpg --quick-set-expire [fpr] 2038-07-01 Ricordatevi di inviare l'aggiornamento ai keyserver:: @@ -676,8 +673,8 @@ storia completa del progetto, inclusi i suoi tag, i commit ed i rami. Tuttavia, con i centinaia di repositori clonati che ci sono in giro, come si fa a verificare che la loro copia di linux.git non è stata manomessa da qualcuno? -Oppure, cosa succede se viene scoperta una backdoor nel codice e la riga -"Autore" dice che sei stato tu, mentre tu sei abbastanza sicuro di +Oppure, cosa succede se viene scoperto del codice malevolo nel kernel e la +riga "Autore" dice che sei stato tu, mentre tu sei abbastanza sicuro di `non averci niente a che fare`_? Per risolvere entrambi i problemi, Git ha introdotto l'integrazione con PGP. @@ -732,9 +729,9 @@ Il merge conterrà qualcosa di simile:: # gpg: Signature made [...] # gpg: Good signature from [...] -Se state verificando il tag di qualcun altro, allora dovrete importare -la loro chiave PGP. Fate riferimento alla sezione ":ref:`it_verify_identities`" -che troverete più avanti. +Se state verificando il tag di qualcun altro, allora dovrete prima +importare la loro chiave PGP. Fate riferimento alla sezione +":ref:`it_verify_identities`" che troverete più avanti. Configurare git per firmare sempre i tag con annotazione ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -748,16 +745,17 @@ dovete impostare la seguente opzione globale:: Come usare commit firmati ------------------------- -Creare dei commit firmati è facile, ma è molto più difficile utilizzarli -nello sviluppo del kernel linux per via del fatto che ci si affida alle -liste di discussione e questo modo di procedere non mantiene le firme PGP -nei commit. In aggiunta, quando si usa *rebase* nel proprio repositorio -locale per allinearsi al kernel anche le proprie firme PGP verranno scartate. -Per questo motivo, la maggior parte degli sviluppatori del kernel non si -preoccupano troppo di firmare i propri commit ed ignoreranno quelli firmati -che si trovano in altri repositori usati per il proprio lavoro. +È anche possibile creare dei commit firmati, ma la loro utilità nello +sviluppo del kernel Linux è limitata. Il flusso di lavoro per contribuire +al kernel si basa sull'invio di patch, e la conversione dei commit in +patch non preserva le firme PGP dei commit. Inoltre, quando si esegue il +*rebase* del proprio repositorio su un ramo principale più recente, le +firme PGP dei commit verranno scartate. Per questo motivo, la maggior +parte degli sviluppatori del kernel non si preoccupano troppo di firmare +i propri commit ed ignoreranno quelli firmati che si trovano in altri +repositori usati per il proprio lavoro. -Tuttavia, se avete il vostro repositorio di lavoro disponibile al pubblico +Detto ciò, se avete il vostro repositorio di lavoro disponibile al pubblico su un qualche servizio di hosting git (kernel.org, infradead.org, ozlabs.org, o altri), allora la raccomandazione è di firmare tutti i vostri commit anche se gli sviluppatori non ne beneficeranno direttamente. @@ -769,17 +767,18 @@ Vi raccomandiamo di farlo per i seguenti motivi: esternamente che hanno firme PGP sui commit avranno un certo valore a questo scopo. 2. Se dovesse mai capitarvi di clonare il vostro repositorio locale (per - esempio dopo un danneggiamento del disco), la firma vi permetterà di - verificare l'integrità del repositorio prima di riprendere il lavoro. + esempio dopo aver reinstallato il vostro sistema), la firma vi + permetterà di verificare l'integrità del repositorio prima di + riprendere il lavoro. 3. Se qualcuno volesse usare *cherry-pick* sui vostri commit, allora la firma permetterà di verificare l'integrità dei commit prima di applicarli. Creare commit firmati ~~~~~~~~~~~~~~~~~~~~~ -Per creare un commit firmato, dovete solamente aggiungere l'opzione ``-S`` -al comando ``git commit`` (si usa la lettera maiuscola per evitare -conflitti con un'altra opzione):: +Per creare un commit firmato, aggiungete l'opzione ``-S`` al comando +``git commit`` (si usa la lettera maiuscola per evitare conflitti con +un'altra opzione):: $ git commit -S @@ -814,6 +813,11 @@ un'attestazione delle firme crittografiche (tipo DKIM): Installare e configurate patatt ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. note:: + + Se usate B4 per inviare le vostre patch, patatt è già installato ed + integrato nel vostro flusso di lavoro. + Lo strumento patatt è disponibile per diverse distribuzioni, dunque cercatelo prima lì. Oppure potete installarlo usano pypi "``pip install patatt``" @@ -855,7 +859,7 @@ esempio:: Come verificare l'identità degli sviluppatori del kernel ======================================================== -Firmare i tag e i commit è facile, ma come si fa a verificare che la chiave +Firmare tag e commit è semplice, ma come si fa a verificare che la chiave usata per firmare qualcosa appartenga davvero allo sviluppatore e non ad un impostore? @@ -892,7 +896,7 @@ Se avete un account kernel.org, al fine di rendere più utile l'uso di WKD da parte di altri sviluppatori del kernel, dovreste `aggiungere alla vostra chiave lo UID di kernel.org`_. -.. _`aggiungere alla vostra chiave lo UID di kernel.org`: https://korg.wiki.kernel.org/userdoc/mail#adding_a_kernelorg_uid_to_your_pgp_key +.. _`aggiungere alla vostra chiave lo UID di kernel.org`: https://korg.docs.kernel.org/mail.html#adding-a-kernel-org-uid-to-your-pgp-key Web of Trust (WOT) o Trust on First Use (TOFU) ---------------------------------------------- @@ -905,7 +909,7 @@ essere le entità di certificazione di cui dovreste fidarvi, PGP lascia la responsabilità ad ogni singolo utente. Sfortunatamente, solo poche persone capiscono come funziona la rete di fiducia. -Nonostante sia un importante aspetto della specifica OpenPGP, recentemente +Nonostante sia tuttora una parte importante della specifica OpenPGP, recentemente le versioni di GnuPG (2.2 e successive) hanno implementato un meccanisco alternativo chiamato "Trust on First Use" (TOFU). Potete pensare a TOFU come "ad un approccio all fidicia simile ad SSH". In SSH, la prima volta che vi @@ -915,8 +919,8 @@ SSH vi avviserà e si rifiuterà di connettersi, obbligandovi a prendere una decisione circa la fiducia che riponete nella nuova chiave. In modo simile, la prima volta che importate la chiave PGP di qualcuno, si assume sia valida. Se ad un certo punto GnuPG trova un'altra chiave con la stessa identità, -entrambe, la vecchia e la nuova, verranno segnate come invalide e dovrete -verificare manualmente quale tenere. +entrambe, la vecchia e la nuova, verranno segnate per la verifica e dovrete +controllare manualmente quale tenere. Vi raccomandiamo di usare il meccanisco TOFU+PGP (che è la nuova configurazione di base di GnuPG v2). Per farlo, aggiungete (o modificate) l'impostazione @@ -924,6 +928,8 @@ di base di GnuPG v2). Per farlo, aggiungete (o modificate) l'impostazione trust-model tofu+pgp +.. _it_kernel_org_trust_repository: + Usare il repositorio kernel.org per il web of trust --------------------------------------------------- diff --git a/Documentation/translations/it_IT/process/maintainers.rst b/Documentation/translations/it_IT/process/maintainers.rst index 3225f7c89fda..95916bc8e346 100644 --- a/Documentation/translations/it_IT/process/maintainers.rst +++ b/Documentation/translations/it_IT/process/maintainers.rst @@ -1,3 +1,5 @@ +.. SPDX-License-Identifier: GPL-2.0 + :Original: Documentation/process/maintainers.rst Lista dei manutentori e come inviare modifiche al kernel diff --git a/Documentation/translations/it_IT/process/programming-language.rst b/Documentation/translations/it_IT/process/programming-language.rst index 5bc5b9d42f31..623675f3b623 100644 --- a/Documentation/translations/it_IT/process/programming-language.rst +++ b/Documentation/translations/it_IT/process/programming-language.rst @@ -8,8 +8,8 @@ Linguaggio di programmazione ============================ -Il kernel è scritto nel linguaggio di programmazione C [it-c-language]_. -Più precisamente, il kernel viene compilato con ``gcc`` [it-gcc]_ usando +Il kernel Linux è scritto nel linguaggio di programmazione C [it-c-language]_. +Più precisamente, viene compilato con ``gcc`` [it-gcc]_ usando l'opzione ``-std=gnu11`` [it-gcc-c-dialect-options]_: il dialetto GNU dello standard ISO C11. Linux supporta anche ``clang`` [it-clang]_, leggete la documentazione @@ -42,7 +42,7 @@ Per maggiori informazioni consultate il file d'intestazione Rust ---- -Il kernel supporta sperimentalmente il linguaggio di programmazione Rust +Il kernel supporta il linguaggio di programmazione Rust [it-rust-language]_ abilitando l'opzione di configurazione ``CONFIG_RUST``. Il codice verrà compilato usando ``rustc`` [it-rustc]_ con l'opzione ``--edition=2021`` [it-rust-editions]_. Le edizioni Rust sono un modo per diff --git a/Documentation/translations/it_IT/process/submit-checklist.rst b/Documentation/translations/it_IT/process/submit-checklist.rst index c58d773fd297..229e219a6f9a 100644 --- a/Documentation/translations/it_IT/process/submit-checklist.rst +++ b/Documentation/translations/it_IT/process/submit-checklist.rst @@ -98,9 +98,12 @@ Compilare il codice e correggere i problemi 2) Compilare per diverse architetture di processore usando strumenti per la - cross-compilazione o altri. Una buona architettura per la verifica della - cross-compilazione è la ppc64 perché tende ad usare ``unsigned long`` per le - quantità a 64-bit. + cross-compilazione o altri. + Da notare che testare su architetture con diverse dimensioni delle parole + (32 e 64 bit) e diverso ordinamento dei byte (*big-* e *little-endian*) è + efficace nell'individuare vari problemi di portabilità dovuti ad + assunzioni errate sull'intervallo di valori rappresentabili, l'allineamento + dei dati, o l'ordinamento dei byte, fra le altre cose. 3) Il nuovo codice è stato compilato con ``gcc -W`` (usate ``make KCFLAGS=-W``). Questo genererà molti avvisi, ma è ottimo diff --git a/Documentation/translations/it_IT/process/submitting-patches.rst b/Documentation/translations/it_IT/process/submitting-patches.rst index 1cc4808139ce..a742610cb070 100644 --- a/Documentation/translations/it_IT/process/submitting-patches.rst +++ b/Documentation/translations/it_IT/process/submitting-patches.rst @@ -28,7 +28,7 @@ renderà la vostra vita di sviluppatore del kernel molto più semplice. I sorgenti di alcuni sottosistemi e manutentori contengono più informazioni riguardo al loro modo di lavorare ed aspettative. Consultate -:ref:`Documentation/translations/it_IT/process/maintainer-handbooks.rst ` +Documentation/translations/it_IT/process/maintainer-handbooks.rst Ottenere i sorgenti attuali --------------------------- @@ -162,8 +162,8 @@ proibiti. Se la vostra patch corregge un baco in un commit specifico, per esempio avete trovato un problema usando ``git bisect``, per favore usate l'etichetta -'Fixes:' indicando i primi 12 caratteri dell'identificativo SHA-1 seguiti -dalla riga riassuntiva. Per esempio:: +'Fixes:' indicando almeno i primi 12 caratteri dell'identificativo SHA-1 +seguiti dalla riga riassuntiva. Per esempio:: Fixes: e21d2170f366 ("video: remove unnecessary platform_set_drvdata()") @@ -444,12 +444,11 @@ delle patch che vengono inviate per e-mail. La firma è una semplice riga alla fine della descrizione della patch che certifica che l'avete scritta voi o che avete il diritto di pubblicarla come patch open-source. Le regole sono abbastanza semplici: se potete -certificare quanto segue: +certificare quanto segue:: -Il certificato d'origine dello sviluppatore 1.1 -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Il certificato d'origine dello sviluppatore 1.1 -Contribuendo a questo progetto, io certifico che: + Contribuendo a questo progetto, io certifico che: (a) Il contributo è stato creato interamente, o in parte, da me e che ho il diritto di inviarlo in accordo con la licenza open-source @@ -505,27 +504,48 @@ della patch ma desidera firmare e mettere agli atti la loro approvazione, allora queste persone possono chiedere di aggiungere al changelog della patch una riga Acked-by:. -Acked-by: viene spesso utilizzato dai manutentori del sottosistema in oggetto -quando quello stesso manutentore non ha contribuito né trasmesso la patch. +Acked-by: è pensata per essere usata da coloro che sono responsabili, o +comunque coinvolti, con il codice interessato in un modo o nell'altro. Più +comunemente, dal manutentore, quando quello stesso manutentore non ha +contribuito né trasmesso la patch. + +Acked-by: può anche essere usata da altre parti interessate, come persone con +conoscenza specifica dell'argomento (per esempio l'autore originale del +codice modificato), revisori lato spazio-utente per una patch che tocca la +uAPI del kernel, o utenti chiave di una funzionalità. Opzionalmente, in +questi casi, può essere utile aggiungere un "# Suffisso" per chiarirne il +significato:: + + Acked-by: The Stakeholder # Come utente primario Acked-by: non è formale come Signed-off-by:. Questo indica che la persona ha revisionato la patch e l'ha trovata accettabile. Per cui, a volte, chi integra le patch convertirà un "sì, mi sembra che vada bene" in un Acked-by: (ma tenete presente che solitamente è meglio chiedere esplicitamente). +Acked-by: è anche meno formale di Reviewed-by:. Per esempio, un manutentore +potrebbe usarla per indicare che è d'accordo con l'integrazione della patch, +pur non avendola revisionata con lo stesso livello d'approfondimento che +avrebbe richiesto un Reviewed-by:. Allo stesso modo, un utente chiave +potrebbe non aver effettuato una revisione tecnica della patch, ma potrebbe +comunque essere soddisfatto dell'approccio generale, della funzionalità o +dell'interfaccia rivolta all'utente. + Acked-by: non indica l'accettazione di un'intera patch. Per esempio, quando una patch ha effetti su diversi sottosistemi e ha un Acked-by: da un manutentore di uno di questi, significa che il manutentore accetta quella parte di codice relativa al sottosistema che mantiene. Qui dovremmo essere giudiziosi. Quando si hanno dei dubbi si dovrebbe far riferimento alla -discussione originale negli archivi della lista di discussione. +discussione originale negli archivi della lista di discussione. Anche in +questo caso si può usare un "# Suffisso" per chiarire. Se una persona ha avuto l'opportunità di commentare la patch, ma non lo ha -fatto, potete aggiungere l'etichetta ``Cc:`` alla patch. Questa è l'unica -etichetta che può essere aggiunta senza che la persona in questione faccia -alcunché - ma dovrebbe indicare che la persona ha ricevuto una copia della -patch. Questa etichetta documenta che terzi potenzialmente interessati sono -stati inclusi nella discussione. +fatto, potete aggiungere l'etichetta ``Cc:`` alla patch. Questa etichetta +documenta che terzi potenzialmente interessati sono stati inclusi nella +discussione. Da notare che questa è una delle sole tre etichette che +potreste poter usare senza il permesso esplicito della persona nominata (per +i dettagli, vedere più avanti "Etichettare le persone richiede un +permesso"). Co-developed-by: indica che la patch è stata cosviluppata da diversi sviluppatori; viene usato per assegnare più autori (in aggiunta a quello @@ -569,13 +589,14 @@ Utilizzare Reported-by:, Tested-by:, Reviewed-by:, Suggested-by: e Fixes: L'etichetta Reported-by da credito alle persone che trovano e riportano i bachi e si spera che questo possa ispirarli ad aiutarci nuovamente in futuro. -Rammentate che se il baco è stato riportato in privato, dovrete chiedere il -permesso prima di poter utilizzare l'etichetta Reported-by. Questa etichetta va -usata per i bachi, dunque non usatela per richieste di nuove funzionalità. -Questa etichetta dovrebbe essere seguita da quella Closes: con un indirizzo al -rapporto, a meno che questo non sia disponibile sul web. L'etichetta Link: può -essere usata in alternativa a Closes: se la patch corregge solo in parte il -problema riportato nel rapporto. +Questa etichetta va usata per i bachi, dunque non usatela per richieste di +nuove funzionalità. Questa etichetta dovrebbe essere seguita da quella Closes: +con un indirizzo al rapporto, a meno che questo non sia disponibile sul web. +L'etichetta Link: può essere usata in alternativa a Closes: se la patch +corregge solo in parte il problema riportato nel rapporto. Da notare che +l'etichetta Reported-by è una delle sole tre etichette che potreste poter +usare senza il permesso esplicito della persona nominata (per i dettagli, +vedere più avanti "Etichettare le persone richiede un permesso"). L'etichetta Tested-by: indica che la patch è stata verificata con successo (su un qualche sistema) dalla persona citata. Questa etichetta informa i @@ -584,12 +605,11 @@ persone che possano verificare il codice in futuro, e garantisce che queste stesse persone ricevano credito per il loro lavoro. Reviewed-by:, invece, indica che la patch è stata revisionata ed è stata -considerata accettabile in accordo con la dichiarazione dei revisori: +considerata accettabile in accordo con la dichiarazione dei revisori:: -Dichiarazione di svista dei revisori -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Dichiarazione di svista dei revisori -Offrendo la mia etichetta Reviewed-by, dichiaro quanto segue: + Offrendo la mia etichetta Reviewed-by, dichiaro quanto segue: (a) Ho effettuato una revisione tecnica di questa patch per valutarne l'adeguatezza ai fini dell'inclusione nel ramo principale del @@ -611,8 +631,9 @@ Offrendo la mia etichetta Reviewed-by, dichiaro quanto segue: L'etichetta Reviewed-by è la dichiarazione di un parere sulla bontà di una modifica che si ritiene appropriata e senza alcun problema tecnico -importante. Qualsiasi revisore interessato (quelli che lo hanno fatto) -possono offrire il proprio Reviewed-by per la patch. Questa etichetta serve +importante. Qualsiasi revisore interessato (che lo abbia fatto e che sia +una persona dall'identità nota) può offrire il proprio Reviewed-by per la +patch. Questa etichetta serve a dare credito ai revisori e a informare i manutentori sul livello di revisione che è stato fatto sulla patch. L'etichetta Reviewed-by, quando fornita da revisori conosciuti per la loro conoscenza sulla materia in oggetto e per la @@ -623,28 +644,63 @@ Quando si riceve una email sulla lista di discussione da un tester o un revisore, le etichette Tested-by o Reviewed-by devono essere aggiunte dall'autore quando invierà nuovamente la patch. Tuttavia, se la patch è cambiata in modo significativo, queste etichette potrebbero -non avere più senso e quindi andrebbero rimosse. Solitamente si tiene traccia -della rimozione nel changelog della patch (subito dopo il separatore '---'). +non avere più senso e quindi andrebbero rimosse. Solitamente si tiene +traccia della rimozione di un'etichetta Acked-by, Tested-by o Reviewed-by +nel changelog della patch, con una spiegazione, (subito dopo il +separatore '---'). L'etichetta Suggested-by: indica che l'idea della patch è stata suggerita -dalla persona nominata e le da credito. Tenete a mente che questa etichetta -non dovrebbe essere aggiunta senza un permesso esplicito, specialmente se -l'idea non è stata pubblicata in un forum pubblico. Detto ciò, dando credito -a chi ci fornisce delle idee, si spera di poterli ispirare ad aiutarci -nuovamente in futuro. +dalla persona nominata e le da credito: se diamo diligentemente credito a +chi ci fornisce delle idee, si spera di poterli ispirare ad aiutarci +nuovamente in futuro. Da notare che questa è una delle sole tre etichette +che potreste poter usare senza il permesso esplicito della persona +nominata (per i dettagli, vedere più avanti "Etichettare le persone +richiede un permesso"). -L'etichetta Fixes: indica che la patch corregge un problema in un commit -precedente. Serve a chiarire l'origine di un baco, il che aiuta la revisione -del baco stesso. Questa etichetta è di aiuto anche per i manutentori dei -kernel stabili al fine di capire quale kernel deve ricevere la correzione. -Questo è il modo suggerito per indicare che un baco è stato corretto nella -patch. Per maggiori dettagli leggete :ref:`it_describe_changes` +L'etichetta Fixes: indica che la patch corregge un baco in un commit +precedente. Serve a rendere facile determinare dove abbia avuto origine +un problema, il che può aiutare la revisione di una correzione. Questa +etichetta è di aiuto anche per i manutentori dei kernel stabili al fine +di capire quale kernel deve ricevere la correzione. Questo è il modo +suggerito per indicare che un baco è stato corretto nella patch. Per +maggiori dettagli leggete :ref:`it_describe_changes` Da notare che aggiungere un tag "Fixes:" non esime dalle regole previste per i kernel stabili, e nemmeno dalla necessità di aggiungere in copia conoscenza stable@vger.kernel.org su tutte le patch per suddetti kernel. +Infine, sebbene fornire le etichette sia gradito e generalmente molto +apprezzato, tenete presente che i firmatari (cioè chi sottomette e i +manutentori) potrebbero usare la propria discrezione nell'applicare le +etichette proposte. + +.. _it_tagging_people: + +Etichettare le persone richiede un permesso +-------------------------------------------- + +State attenti ad aggiungere le suddette etichette alla vostra patch: tutte, +tranne Cc:, Reported-by: e Suggested-by:, richiedono il permesso esplicito +della persona nominata. Per queste tre è sufficiente un permesso implicito, +se la persona ha contribuito al kernel Linux usando quel nome e quell'indirizzo +email secondo gli archivi di lore o la cronologia dei commit -- e, nel caso di +Reported-by: e Suggested-by:, se la segnalazione o il suggerimento sono +avvenuti pubblicamente. Da notare che bugzilla.kernel.org è, in questo senso, +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 @@ -656,6 +712,9 @@ potere usare il comando ``git format-patch`` per ottenere patch nel formato appropriato. Lo strumento non crea il testo necessario, per cui, leggete le seguenti istruzioni. +Oggetto +^^^^^^^ + L'oggetto di una patch canonica è la riga:: Subject: [PATCH 001/123] subsystem: summary phrase @@ -729,6 +788,9 @@ Un paio di esempi di oggetti:: Subject: [PATCH v2] sub/sys: Condensed patch summary Subject: [PATCH v2 M/N] sub/sys: Condensed patch summary +Riga From +^^^^^^^^^ + La riga ``from`` dev'essere la prima nel corpo del messaggio ed è nel formato: @@ -739,6 +801,15 @@ l'autore della patch. Se la riga ``from`` è mancante, allora per determinare l'autore da inserire nel changelog verrà usata la riga ``From`` nell'intestazione dell'email. +L'autore può indicare la propria affiliazione o lo sponsor del lavoro +aggiungendo il nome di un'organizzazione alle righe ``from`` e ``SoB``, +per esempio: + + From: Patch Author (Azienda) + +Corpo della spiegazione +^^^^^^^^^^^^^^^^^^^^^^^^ + Il corpo della spiegazione verrà incluso nel changelog permanente, per cui deve aver senso per un lettore esperto che è ha dimenticato i dettagli della discussione che hanno portato alla patch. L'inclusione di informazioni @@ -755,6 +826,33 @@ aggiungete solo quello che è necessario per far si che la vostra patch venga trovata. Come nella ``summary phrase``, è importante essere sia brevi che descrittivi. +.. _it_backtraces: + +Aggiungere i *backtrace* nei messaggi di commit +"""""""""""""""""""""""""""""""""""""""""""""""" + +I *backtrace* aiutano a documentare la sequenza di chiamate a funzione +che portano ad un problema. Tuttavia, non tutti i *backtrace* sono +davvero utili. Per esempio, le sequenze iniziali di avvio sono uniche +e ovvie. Copiare integralmente l'output di ``dmesg`` aggiunge tante +informazioni che distraggono dal vero problema (per esempio, i +marcatori temporali, la lista dei moduli, la lista dei registri, lo +stato dello stack). + +Quindi, per rendere utile un *backtrace* dovreste eliminare le +informazioni inutili, cosicché ci si possa focalizzare sul +problema. Ecco un esempio di un *backtrace* essenziale:: + + 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 + +Commento +^^^^^^^^ + La linea di demarcazione ``---`` serve essenzialmente a segnare dove finisce il messaggio di changelog. @@ -778,7 +876,10 @@ versione di una patch non sono parte del *chagelog* che viene incluso in git. Queste sono informazioni utili solo ai revisori. Se venissero messe sopra la riga, qualcuno dovrà fare del lavoro manuale per rimuoverle; cosa che invece viene fatta automaticamente quando vengono -messe correttamente oltre la riga.:: +messe correttamente oltre la riga. Se disponibili, si consiglia di +aggiungere anche i collegamenti alle versioni precedenti della patch +(per esempio, un collegamento all'archivio di lore.kernel.org) per +aiutare i revisori:: ... @@ -787,35 +888,14 @@ messe correttamente oltre la riga.:: 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+++-- ... Maggiori dettagli sul formato delle patch nei riferimenti qui di seguito. -.. _it_backtraces: - -Aggiungere i *backtrace* nei messaggi di commit -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -I *backtrace* aiutano a documentare la sequenza di chiamate a funzione -che portano ad un problema. Tuttavia, non tutti i *backtrace* sono -davvero utili. Per esempio, le sequenze iniziali di avvio sono uniche -e ovvie. Copiare integralmente l'output di ``dmesg`` aggiunge tante -informazioni che distraggono dal vero problema (per esempio, i -marcatori temporali, la lista dei moduli, la lista dei registri, lo -stato dello stack). - -Quindi, per rendere utile un *backtrace* dovreste eliminare le -informazioni inutili, cosicché ci si possa focalizzare sul -problema. Ecco un esempio di un *backtrace* essenziale:: - - 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 - .. _it_explicit_in_reply_to: Usare esplicitamente In-Reply-To nell'intestazione From dc6b7c771a963e20aedf4a21ffa22543b9837ba8 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Sat, 8 Aug 2026 13:15:32 +0800 Subject: [PATCH 0902/1328] Bluetooth: hci_bcm: fix usage_count leak when autosuspend_delay is negative bcm_request_irq() calls pm_runtime_use_autosuspend(), but bcm_close() does not call the matching pm_runtime_dont_use_autosuspend() when tearing down runtime PM. If the autosuspend delay is set to a negative value while autosuspend is enabled, the runtime PM core increments usage_count to prevent runtime suspend. Without calling pm_runtime_dont_use_autosuspend() during driver teardown, this reference is not dropped and usage_count remains unbalanced. Add the missing pm_runtime_dont_use_autosuspend() call before disabling runtime PM. This issue was found by manual code inspection. Fixes: e88ab30d3669 ("Bluetooth: hci_bcm: Add suspend/resume runtime PM functions") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/hci_bcm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/bluetooth/hci_bcm.c b/drivers/bluetooth/hci_bcm.c index 01da3fecb536..9a103db7e355 100644 --- a/drivers/bluetooth/hci_bcm.c +++ b/drivers/bluetooth/hci_bcm.c @@ -547,6 +547,7 @@ static int bcm_close(struct hci_uart *hu) if (IS_ENABLED(CONFIG_PM) && bdev->irq_acquired) { devm_free_irq(bdev->dev, bdev->irq, bdev); device_init_wakeup(bdev->dev, false); + pm_runtime_dont_use_autosuspend(bdev->dev); pm_runtime_disable(bdev->dev); } From 853a92b97ca547a7ddd9790ff90651b2fd943498 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Sat, 8 Aug 2026 13:26:54 +0800 Subject: [PATCH 0903/1328] Bluetooth: hci_h5: fix usage_count leak when autosuspend_delay is negative h5_btrtl_open() calls pm_runtime_use_autosuspend(), but h5_btrtl_close() does not call the matching pm_runtime_dont_use_autosuspend() when tearing down runtime PM. If the autosuspend delay is set to a negative value while autosuspend is enabled, the runtime PM core increments usage_count to prevent runtime suspend. Without calling pm_runtime_dont_use_autosuspend() during driver teardown, this reference is not dropped and usage_count remains unbalanced. Add the missing pm_runtime_dont_use_autosuspend() call before disabling runtime PM. This issue was found by manual code inspection. Fixes: d9dd833cf6d2 ("Bluetooth: hci_h5: Add runtime suspend") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/hci_h5.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/bluetooth/hci_h5.c b/drivers/bluetooth/hci_h5.c index 60b90f1e11fc..b1999e14aade 100644 --- a/drivers/bluetooth/hci_h5.c +++ b/drivers/bluetooth/hci_h5.c @@ -1023,8 +1023,10 @@ static void h5_btrtl_open(struct h5 *h5) static void h5_btrtl_close(struct h5 *h5) { - if (!test_bit(H5_WAKEUP_DISABLE, &h5->flags)) + if (!test_bit(H5_WAKEUP_DISABLE, &h5->flags)) { + pm_runtime_dont_use_autosuspend(&h5->hu->serdev->dev); pm_runtime_disable(&h5->hu->serdev->dev); + } gpiod_set_value_cansleep(h5->device_wake_gpio, 0); gpiod_set_value_cansleep(h5->enable_gpio, 0); From c7e9a8cb6918656884a0757c92465075c7555ffa Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Sat, 8 Aug 2026 13:30:57 +0800 Subject: [PATCH 0904/1328] Bluetooth: hci_intel: fix usage_count leak when autosuspend_delay is negative intel_set_power() calls pm_runtime_use_autosuspend() when powering on the device, but the power-off path does not call the matching pm_runtime_dont_use_autosuspend() before disabling runtime PM. If the autosuspend delay is set to a negative value while autosuspend is enabled, the runtime PM core increments usage_count to prevent runtime suspend. Without calling pm_runtime_dont_use_autosuspend() during teardown, this reference is not dropped and usage_count remains unbalanced. Add the missing pm_runtime_dont_use_autosuspend() call before disabling runtime PM. This issue was found by manual code inspection. Fixes: 74cdad37cd24 ("Bluetooth: hci_intel: Add runtime PM support") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/hci_intel.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/bluetooth/hci_intel.c b/drivers/bluetooth/hci_intel.c index ecf597f3e201..d10ce7a0ba3e 100644 --- a/drivers/bluetooth/hci_intel.c +++ b/drivers/bluetooth/hci_intel.c @@ -345,6 +345,7 @@ static int intel_set_power(struct hci_uart *hu, bool powered) devm_free_irq(&idev->pdev->dev, idev->irq, idev); device_wakeup_disable(&idev->pdev->dev); + pm_runtime_dont_use_autosuspend(&idev->pdev->dev); pm_runtime_disable(&idev->pdev->dev); } } From ca2c4c26498643f421d35ffe258fafbd3ed461c3 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 9 Aug 2026 20:42:41 +0300 Subject: [PATCH 0905/1328] Bluetooth: L2CAP: access chan->conn safely in get/setsockopt Since commit b66774b48dd9 ("Bluetooth: L2CAP: Fix UAF in channel timeout by holding conn ref") l2cap_chan::conn has held reference and remains non-NULL also after the corresponding hci_conn is deleted. In this state accessing various fields eg. hci_conn::hdev is invalid, which leads to KASAN crash in l2cap_sock_setsockopt() access of conn->hcon->hdev. Check l2cap_chan::conn.hcon corresponds to an alive hci_conn before trying to use it in l2cap_sock.c. Hold l2cap_chan_lock() in getsockopt/setsockopt to ensure it stays alive, and to avoid data races in l2cap_chan fields. Fixes: b66774b48dd9 ("Bluetooth: L2CAP: Fix UAF in channel timeout by holding conn ref") Reported-by: syzbot+b106284c2a0b7bc80cf9@syzkaller.appspotmail.com Link: https://syzkaller.appspot.com/bug?extid=b106284c2a0b7bc80cf9 Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/l2cap_sock.c | 64 ++++++++++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 16 deletions(-) diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c index 735167f73f31..cca6201f9cdb 100644 --- a/net/bluetooth/l2cap_sock.c +++ b/net/bluetooth/l2cap_sock.c @@ -436,11 +436,26 @@ static int l2cap_get_mode(struct l2cap_chan *chan) return -EINVAL; } +static struct l2cap_conn *l2cap_chan_conn(struct l2cap_chan *chan) +{ + lockdep_assert_held(&chan->lock); + + /* l2cap_conn_del() sets FLAG_DEL while holding chan->lock before + * conn->hcon is deleted. If not set and conn is non-NULL, conn->hcon + * remains alive during this chan->lock critical section. + */ + if (test_bit(FLAG_DEL, &chan->flags)) + return NULL; + + return chan->conn; +} + static int l2cap_sock_getsockopt_old(struct socket *sock, int optname, sockopt_t *sopt) { struct sock *sk = sock->sk; struct l2cap_chan *chan = l2cap_pi(sk)->chan; + struct l2cap_conn *conn; struct l2cap_options opts; struct l2cap_conninfo cinfo; int err = 0; @@ -451,6 +466,7 @@ static int l2cap_sock_getsockopt_old(struct socket *sock, int optname, len = sopt->optlen; + l2cap_chan_lock(chan); lock_sock(sk); switch (optname) { @@ -537,9 +553,15 @@ static int l2cap_sock_getsockopt_old(struct socket *sock, int optname, break; } + conn = l2cap_chan_conn(chan); + if (!conn) { + err = -ENOTCONN; + break; + } + memset(&cinfo, 0, sizeof(cinfo)); - cinfo.hci_handle = chan->conn->hcon->handle; - memcpy(cinfo.dev_class, chan->conn->hcon->dev_class, 3); + cinfo.hci_handle = conn->hcon->handle; + memcpy(cinfo.dev_class, conn->hcon->dev_class, 3); len = min(len, sizeof(cinfo)); if (copy_to_iter(&cinfo, len, &sopt->iter_out) != len) @@ -553,6 +575,8 @@ static int l2cap_sock_getsockopt_old(struct socket *sock, int optname, } release_sock(sk); + l2cap_chan_unlock(chan); + return err; } @@ -561,6 +585,7 @@ static int l2cap_sock_getsockopt(struct socket *sock, int level, int optname, { struct sock *sk = sock->sk; struct l2cap_chan *chan = l2cap_pi(sk)->chan; + struct l2cap_conn *conn; struct bt_security sec; struct bt_power pwr; int len, mode, err = 0; @@ -578,6 +603,7 @@ static int l2cap_sock_getsockopt(struct socket *sock, int level, int optname, len = sopt->optlen; + l2cap_chan_lock(chan); lock_sock(sk); switch (optname) { @@ -589,12 +615,14 @@ static int l2cap_sock_getsockopt(struct socket *sock, int level, int optname, break; } + conn = l2cap_chan_conn(chan); + memset(&sec, 0, sizeof(sec)); - if (chan->conn) { - sec.level = chan->conn->hcon->sec_level; + if (conn) { + sec.level = conn->hcon->sec_level; if (sk->sk_state == BT_CONNECTED) - sec.key_size = chan->conn->hcon->enc_key_size; + sec.key_size = conn->hcon->enc_key_size; } else { sec.level = chan->sec_level; } @@ -678,12 +706,14 @@ static int l2cap_sock_getsockopt(struct socket *sock, int level, int optname, break; case BT_PHY: - if (sk->sk_state != BT_CONNECTED) { + conn = l2cap_chan_conn(chan); + + if (sk->sk_state != BT_CONNECTED || !conn) { err = -ENOTCONN; break; } - opt = hci_conn_get_phy(chan->conn->hcon); + opt = hci_conn_get_phy(conn->hcon); if (copy_to_iter(&opt, sizeof(opt), &sopt->iter_out) != sizeof(opt)) @@ -719,6 +749,7 @@ static int l2cap_sock_getsockopt(struct socket *sock, int level, int optname, } release_sock(sk); + l2cap_chan_unlock(chan); return err; } @@ -749,6 +780,7 @@ static int l2cap_sock_setsockopt_old(struct socket *sock, int optname, BT_DBG("sk %p", sk); + l2cap_chan_lock(chan); lock_sock(sk); switch (optname) { @@ -850,6 +882,7 @@ static int l2cap_sock_setsockopt_old(struct socket *sock, int optname, } release_sock(sk); + l2cap_chan_unlock(chan); return err; } @@ -913,6 +946,7 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname, if (level != SOL_BLUETOOTH) return -ENOPROTOOPT; + l2cap_chan_lock(chan); lock_sock(sk); switch (optname) { @@ -938,11 +972,10 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname, chan->sec_level = sec.level; - if (!chan->conn) + conn = l2cap_chan_conn(chan); + if (!conn) break; - conn = chan->conn; - /* change security for LE channels */ if (chan->scid == L2CAP_CID_ATT) { if (smp_conn_security(conn->hcon, sec.level)) { @@ -997,7 +1030,8 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname, } if (opt == BT_FLUSHABLE_OFF) { - conn = chan->conn; + conn = l2cap_chan_conn(chan); + /* proceed further only when we have l2cap_conn and No Flush support in the LM */ if (!conn || !lmp_no_flush_capable(conn->hcon->hdev)) { @@ -1083,7 +1117,8 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname, break; case BT_PHY: - if (sk->sk_state != BT_CONNECTED) { + conn = l2cap_chan_conn(chan); + if (sk->sk_state != BT_CONNECTED || !conn) { err = -ENOTCONN; break; } @@ -1093,10 +1128,6 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname, if (err) break; - if (!chan->conn) - break; - - conn = chan->conn; err = hci_conn_set_phy(conn->hcon, phys); break; @@ -1139,6 +1170,7 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname, } release_sock(sk); + l2cap_chan_unlock(chan); return err; } From d4bfa78fd67929b62b02013c107973e0c5b7aa9a Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 9 Aug 2026 01:06:05 +0300 Subject: [PATCH 0906/1328] Bluetooth: L2CAP: reject accept queue add unless BT_LISTEN New sk should not be added to parent socket accept queue after last l2cap_sock_cleanup_listen() has run in l2cap_sock_teardown_cb() and state set to BT_CLOSED, as that can result to UAF on dereferencing the dangling parent reference. l2cap_sock_new_connection_cb() may race with parent l2cap_chan teardown, due to chan->state accessed without consistent locking: [Task 1] [Task 2] l2cap_sock_release(parent) l2cap_connect l2cap_sock_shutdown pchan = l2cap_global_chan_by_psm l2cap_chan_lock(pchan) l2cap_chan_close l2cap_sock_teardown_cb pchan->state = BT_CLOSED l2cap_chan_unlock(pchan) ------> l2cap_chan_lock(pchan) l2cap_new_connection l2cap_sock_new_connection_cb l2cap_chan_lock(pchan) <-------- l2cap_chan_unlock(pchan) l2cap_sock_kill(parent) /* bt_sk(sk)->parent dangling */ Fix by adding check for sk_state == BT_LISTEN after acquiring sk lock in l2cap_sock_new_connection_cb(). Add lock_sock() around sk_state writes where missing, to avoid data races. Although the data races on pchan->state should be fixed too, this defensive sk_state check probably makes sense in any case. Fixes: 2ff1a41a912d ("Bluetooth: L2CAP: Fix null-ptr-deref in l2cap_sock_state_change_cb()") Reported-by: syzbot+9265e754091c2d27ea29@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=9265e754091c2d27ea29 Signed-off-by: Pauli Virtanen Reported-by: syzbot+9265e754091c2d27ea29@syzkaller.appspotmail.com Tested-by: syzbot+9265e754091c2d27ea29@syzkaller.appspotmail.com Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/l2cap_sock.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c index cca6201f9cdb..8bf35bc8126f 100644 --- a/net/bluetooth/l2cap_sock.c +++ b/net/bluetooth/l2cap_sock.c @@ -1600,6 +1600,11 @@ static int l2cap_sock_new_connection_cb(struct l2cap_chan *chan, lock_sock(parent); + if (parent->sk_state != BT_LISTEN) { + release_sock(parent); + return -EINVAL; + } + /* Check for backlog size */ if (sk_acceptq_is_full(parent)) { BT_DBG("backlog full %d", parent->sk_ack_backlog); @@ -1763,10 +1768,14 @@ static void l2cap_sock_state_change_cb(struct l2cap_chan *chan, int state, if (!sk) return; + lock_sock(sk); + sk->sk_state = state; if (err) sk->sk_err = err; + + release_sock(sk); } static struct sk_buff *l2cap_sock_alloc_skb_cb(struct l2cap_chan *chan, @@ -1842,6 +1851,8 @@ static void l2cap_sock_resume_cb(struct l2cap_chan *chan) if (!sk) return; + lock_sock(sk); + if (test_and_clear_bit(FLAG_PENDING_SECURITY, &chan->flags)) { sk->sk_state = BT_CONNECTED; chan->state = BT_CONNECTED; @@ -1849,6 +1860,8 @@ static void l2cap_sock_resume_cb(struct l2cap_chan *chan) clear_bit(BT_SK_SUSPEND, &bt_sk(sk)->flags); sk->sk_state_change(sk); + + release_sock(sk); } static void l2cap_sock_set_shutdown_cb(struct l2cap_chan *chan) From 59eecbe2f2f38d8f3e1104bd11da97f9a2c58998 Mon Sep 17 00:00:00 2001 From: Pavel Shpakovskiy Date: Sat, 8 Aug 2026 19:31:11 +0300 Subject: [PATCH 0907/1328] Bluetooth: mgmt: fix 'hdev->discovery.uuids' NULL dereference 'uuid_count' member of struct 'discovery_state' is assigned and read without any locks, so there is a chance of situation when uuid_count != 0, but uuids is NULL and there will be NULL pointer dereference. Possible race: 'hci_update_passive_scan_sync' 'hci_discovery_filter_clear' hdev->discovery.uuid_count = 0; <----------------------preempted-----------------------------> 'start_service_discovery' // Set uuid_count to value != 0 hdev->discovery.uuid_count = uuid_count; hdev->discovery.uuids = kmemdup(...); <----------------------preempted-----------------------------> spin_lock(&hdev->discovery.lock); kfree(hdev->discovery.uuids); hdev->discovery.uuids = NULL; spin_unlock(&hdev->discovery.lock); Now uuids == NULL and uuid_count != 0. So 'mgmt_device_found' -> 'is_filter_match' -> 'eir_has_uuids' receives non consistent discovery state, where NULL dereference of uuids happens. To fix it let's add discovery.lock around every read/write of uuid_count, uuids pair of struct members. It is also important to assign uuid_count value only after success kmemdup() allocation in start_service_discovery(), otherwise uuids is NULL, because kmemdup failed, but uuid_count is already assigned to non zero value. The following panic happens: [ ] ------------[ cut here ]------------ [ ] Unable to handle kernel NULL pointer dereference at virtual address 0000000000000000 [ ] Internal error: Oops: 0000000096000006 [#1] PREEMPT SMP [ ] CPU: 0 PID: 15056 Comm: kworker/u9:2 [ ] Workqueue: hci0 hci_rx_work [ ] pstate: 10400009 (nzcV daif +PAN -UAO -TCO -DIT -SSBS BTYPE=--) [ ] pc : eir_has_uuids+0x2d8/0x590 [ ] lr : is_filter_match+0x258/0x320 ... [ ] Call trace: [ ] eir_has_uuids+0x2d8/0x590 [ ] is_filter_match+0x258/0x320 [ ] mgmt_device_found+0x5b0/0xafc [ ] process_adv_report.part.0+0x8c8/0xf14 [ ] hci_le_adv_report_evt+0x338/0x3f0 [ ] hci_le_meta_evt+0x1f0/0x4c8 [ ] hci_event_packet+0x440/0xc9c [ ] hci_rx_work+0x44c/0xaf8 [ ] process_one_work+0x54c/0x103c [ ] worker_thread+0x6c4/0x10c4 [ ] kthread+0x274/0x2ec [ ] ret_from_fork+0x10/0x20 [ ] Code: 14000004 91004021 eb14003f 54000180 (f9400024) [ ] ---[ end trace 0000000000000000 ]--- Fixes: 2935e556850e ("Bluetooth: hci_sync: fix double free in 'hci_discovery_filter_clear()'") Signed-off-by: Pavel Shpakovskiy Signed-off-by: Luiz Augusto von Dentz --- include/net/bluetooth/hci_core.h | 2 +- net/bluetooth/mgmt.c | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index e07418a5adce..4105c446ca98 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -935,9 +935,9 @@ static inline void hci_discovery_filter_clear(struct hci_dev *hdev) hdev->discovery.result_filtering = false; hdev->discovery.report_invalid_rssi = true; hdev->discovery.rssi = HCI_RSSI_INVALID; - hdev->discovery.uuid_count = 0; spin_lock(&hdev->discovery.lock); + hdev->discovery.uuid_count = 0; kfree(hdev->discovery.uuids); hdev->discovery.uuids = NULL; spin_unlock(&hdev->discovery.lock); diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index 860c086011b7..ac4864e56ec7 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -6171,6 +6171,7 @@ static int start_service_discovery(struct sock *sk, struct hci_dev *hdev, struct mgmt_pending_cmd *cmd; const u16 max_uuid_count = ((U16_MAX - sizeof(*cp)) / 16); u16 uuid_count, expected_len; + u8 (*uuids)[16] = NULL; u8 status; int err; @@ -6247,12 +6248,10 @@ static int start_service_discovery(struct sock *sk, struct hci_dev *hdev, hdev->discovery.result_filtering = true; hdev->discovery.type = cp->type; hdev->discovery.rssi = cp->rssi; - hdev->discovery.uuid_count = uuid_count; if (uuid_count > 0) { - hdev->discovery.uuids = kmemdup(cp->uuids, uuid_count * 16, - GFP_KERNEL); - if (!hdev->discovery.uuids) { + uuids = kmemdup(cp->uuids, uuid_count * sizeof(*uuids), GFP_KERNEL); + if (!uuids) { err = mgmt_cmd_complete(sk, hdev->id, MGMT_OP_START_SERVICE_DISCOVERY, MGMT_STATUS_FAILED, @@ -6262,6 +6261,11 @@ static int start_service_discovery(struct sock *sk, struct hci_dev *hdev, } } + spin_lock(&hdev->discovery.lock); + hdev->discovery.uuids = uuids; + hdev->discovery.uuid_count = uuid_count; + spin_unlock(&hdev->discovery.lock); + err = hci_cmd_sync_queue(hdev, start_discovery_sync, cmd, start_discovery_complete); if (err < 0) { @@ -10505,6 +10509,7 @@ static bool is_filter_match(struct hci_dev *hdev, s8 rssi, u8 *eir, !hci_test_quirk(hdev, HCI_QUIRK_STRICT_DUPLICATE_FILTER)))) return false; + spin_lock(&hdev->discovery.lock); if (hdev->discovery.uuid_count != 0) { /* If a list of UUIDs is provided in filter, results with no * matching UUID should be dropped. @@ -10513,9 +10518,12 @@ static bool is_filter_match(struct hci_dev *hdev, s8 rssi, u8 *eir, hdev->discovery.uuids) && !eir_has_uuids(scan_rsp, scan_rsp_len, hdev->discovery.uuid_count, - hdev->discovery.uuids)) + hdev->discovery.uuids)) { + spin_unlock(&hdev->discovery.lock); return false; + } } + spin_unlock(&hdev->discovery.lock); /* If duplicate filtering does not report RSSI changes, then restart * scanning to ensure updated result with updated RSSI values. From 66d6ef18548ae6d7dd452b84115fc82c0a73a4ea Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 8 Aug 2026 12:08:45 +0300 Subject: [PATCH 0908/1328] Bluetooth: L2CAP: fix race l2cap_sock_cleanup_listen() vs. put_chan For L2CAP sockets without owning sk->sk_socket, reading l2cap_pi(sk)->chan may race against concurrent l2cap_sock_kill() -> l2cap_sock_put_chan(). This excludes simultaneous proto_ops callbacks, but access in l2cap_sock_cleanup_listen() has unsafe lockless read. [Task 1] [Task 2 (hdev->workqueue)] l2cap_sock_release(parent) l2cap_disconn_cfm l2cap_sock_cleanup_listen l2cap_conn_del bt_accept_dequeue l2cap_chan_del lock_sock(sk) l2cap_sock_teardown_cb bt_accept_unlink bt_sk(sk)->parent = NULL release_sock(sk) ----------------> lock_sock(sk) parent = /* NULL */ lock_sock(sk) <--------------------- release_sock(sk) sock_set_flag(sk, SOCK_ZAPPED) l2cap_sock_close_cb l2cap_sock_kill(sk) l2cap_sock_put_chan chan = READ l2cap_pi(sk)->chan l2cap_pi(sk)->chan = NULL l2cap_chan_hold_unless_zero l2cap_put_chan(chan) kref_get_unless_zero(&chan->ref) Task 1 may observe NULL which causes null-ptr-deref. Fix the race by taking lock_sock() in l2cap_sock_kill() to synchronize with l2cap_sock_cleanup_listen(). hold_unless_zero() is not needed here, l2cap_pi(sk)->chan owns reference if it is non-NULL. Clarify code comments vs. locking. Fixes: 6fef032af009 ("Bluetooth: L2CAP: Fix use-after-free in l2cap_sock_new_connection_cb()") Reported-by: syzbot+e6382a2f53f5fc7453ac@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=e6382a2f53f5fc7453ac Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- include/net/bluetooth/l2cap.h | 5 +++++ net/bluetooth/l2cap_sock.c | 23 +++++++++++++---------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h index ef6ce1c20a4f..3d9a32094347 100644 --- a/include/net/bluetooth/l2cap.h +++ b/include/net/bluetooth/l2cap.h @@ -699,7 +699,12 @@ struct l2cap_rx_busy { struct l2cap_pinfo { struct bt_sock bt; + + /* With owning sk_socket chan may be read without lock, other access + * should hold lock_sock. + */ struct l2cap_chan *chan; + struct list_head rx_busy; }; diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c index 8bf35bc8126f..1194c37e466f 100644 --- a/net/bluetooth/l2cap_sock.c +++ b/net/bluetooth/l2cap_sock.c @@ -1344,7 +1344,12 @@ static void l2cap_sock_kill(struct sock *sk) BT_DBG("sk %p state %s", sk, state_to_string(sk->sk_state)); + /* Take lock to synchronize against access without owning sk->sk_socket, + * eg. in l2cap_sock_cleanup_listen(). proto_ops etc. don't need lock. + */ + lock_sock(sk); l2cap_sock_put_chan(sk); + release_sock(sk); /* Kill poor orphan */ sock_set_flag(sk, SOCK_DEAD); @@ -1548,14 +1553,10 @@ static void l2cap_sock_cleanup_listen(struct sock *parent) * establish sk_lock -> conn->lock and invert the established * conn->lock -> chan->lock -> sk_lock order (lockdep deadlock). * - * Instead, briefly take the child sk lock to fetch and pin its chan. - * l2cap_conn_del() reaches the chan free only via - * l2cap_chan_del() -> l2cap_sock_teardown_cb(), which itself takes - * the child sk lock; holding it across l2cap_chan_hold_unless_zero() - * therefore guarantees the chan cannot be freed while we read and - * pin it (hold_unless_zero() additionally skips a chan already past - * its last reference). We then drop the sk lock before taking - * chan->lock, so sk and chan locks are never held together. + * Instead, briefly take the child sk lock to synchronize vs. + * l2cap_sock_kill that puts l2cap_pi(sk)->chan. We then drop the sk + * lock before taking chan->lock, so sk and chan locks are never held + * together. * * Since we cannot call l2cap_chan_close() without conn->lock, * schedule l2cap_chan_timeout to close the channel; it already @@ -1565,10 +1566,12 @@ static void l2cap_sock_cleanup_listen(struct sock *parent) struct l2cap_chan *chan; lock_sock_nested(sk, L2CAP_NESTING_NORMAL); - chan = l2cap_chan_hold_unless_zero(l2cap_pi(sk)->chan); + chan = l2cap_pi(sk)->chan; + if (chan) + l2cap_chan_hold(chan); release_sock(sk); if (!chan) { - /* l2cap_conn_del() already tearing this child down */ + /* Already torn down */ sock_put(sk); continue; } From 59c3ee19ca88210bfc0e22ce59218091cb1a3c48 Mon Sep 17 00:00:00 2001 From: Ismail Tarim Date: Sat, 15 Aug 2026 14:56:23 +0300 Subject: [PATCH 0909/1328] Bluetooth: btmtk: Do not report success when subsys reset fails btmtk_usb_subsys_reset() validates the subsystem reset by reading the chip id back. When that read succeeds at the bus level but yields an id of zero, the reset has demonstrably not taken effect: the function logs "Can't get device id, subsys reset fail." and then returns the return value of btmtk_usb_id_get(), which in that case is zero, i.e. success. btusb_mtk_reset() returns that value unchanged, so its caller cannot tell a completed reset from a failed one. Return -ENODEV when the chip id reads back as zero, leaving the existing MT6639 exemption intact. Observed on an MT7902 [13d3:3579]. The path can be reached on demand by asking the controller for a coredump, since btmtk requests a reset once the dump completes: # echo 1 > /sys/class/bluetooth/hci0/device/coredump Bluetooth: hci0: Mediatek coredump end Bluetooth: hci0: Can't get device id, subsys reset fail. usb 3-10: reset high-speed USB device number 5 using xhci_hcd usb 3-10: device descriptor read/64, error -110 usb usb3-port10: attempt power cycle usb usb3-port10: unable to enumerate USB device The same sequence occurs unprompted when the controller firmware asserts on its own. Note that this corrects the error reporting only; it does not by itself make the controller recoverable in the case above. Fixes: 25b6d7593a3a ("Bluetooth: btmtk: introduce btmtk reset work") Signed-off-by: Ismail Tarim Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btmtk.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/bluetooth/btmtk.c b/drivers/bluetooth/btmtk.c index 66b346761043..dc702c0a6034 100644 --- a/drivers/bluetooth/btmtk.c +++ b/drivers/bluetooth/btmtk.c @@ -968,8 +968,10 @@ int btmtk_usb_subsys_reset(struct hci_dev *hdev, u32 dev_id) } err = btmtk_usb_id_get(hdev, 0x70010200, &val); - if (err || (!val && dev_id != 0x6639)) + if (err || (!val && dev_id != 0x6639)) { bt_dev_err(hdev, "Can't get device id, subsys reset fail."); + return err ? err : -ENODEV; + } return err; } From 21b50c24843b51f88ac4316dd470d2803da0c42f Mon Sep 17 00:00:00 2001 From: Ismail Tarim Date: Sat, 15 Aug 2026 14:56:24 +0300 Subject: [PATCH 0910/1328] Bluetooth: btmtk: Do not discard the subsystem reset timeout When the MTK_BT_RST_DONE poll times out, btmtk_usb_subsys_reset() logs "Reset timeout" and keeps the error in err, but err is then overwritten by the return value of the following btmtk_usb_id_get() call, so the timeout is never reported to the caller. Commit 25b6d7593a3a ("Bluetooth: btmtk: introduce btmtk reset work") discarded the return value of the chip id read, so the function returned the timeout error as intended. Commit 3dcb122b3064 ("Bluetooth: btusb: mediatek: return error for failed reg access") started assigning err at that call and silently dropped it. Keep the timeout in a separate variable and return it, restoring the original behaviour without changing the control flow. Fixes: 3dcb122b3064 ("Bluetooth: btusb: mediatek: return error for failed reg access") Signed-off-by: Ismail Tarim Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btmtk.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/bluetooth/btmtk.c b/drivers/bluetooth/btmtk.c index dc702c0a6034..c0ed51567ed4 100644 --- a/drivers/bluetooth/btmtk.c +++ b/drivers/bluetooth/btmtk.c @@ -860,6 +860,7 @@ static u32 btmtk_usb_reset_done(struct hci_dev *hdev) int btmtk_usb_subsys_reset(struct hci_dev *hdev, u32 dev_id) { + int reset_err = 0; u32 val; int err; @@ -958,8 +959,10 @@ int btmtk_usb_subsys_reset(struct hci_dev *hdev, u32 dev_id) err = readx_poll_timeout(btmtk_usb_reset_done, hdev, val, val & MTK_BT_RST_DONE, 20000, 1000000); - if (err < 0) + if (err < 0) { bt_dev_err(hdev, "Reset timeout"); + reset_err = err; + } if (dev_id == 0x7922) { err = btmtk_usb_uhw_reg_write(hdev, MTK_UDMA_INT_STA_BT, 0x000000FF); @@ -973,7 +976,7 @@ int btmtk_usb_subsys_reset(struct hci_dev *hdev, u32 dev_id) return err ? err : -ENODEV; } - return err; + return reset_err; } EXPORT_SYMBOL_GPL(btmtk_usb_subsys_reset); From 155e3003d1e614f85566b636973df7118e1b4851 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Mon, 17 Aug 2026 17:53:31 +0800 Subject: [PATCH 0911/1328] Bluetooth: btmtksdio: Take exclusive ownership of the SKB before TX btmtksdio_tx_packet() prepends the MediaTek SDIO header with skb_push() and writes into that space after only checking the headroom size. On a cloned SKB that headroom belongs to a buffer shared with the other owner, which the driver has no right to write to. Cloned SKBs do reach this path: hci_send_cmd_sync() keeps a clone of every HCI command in hdev->sent_cmd before handing the SKB to the driver, and l2cap_ertm_send() clones SKBs for retransmission. Replace the open-coded headroom check with skb_cow_head(), which both guarantees the headroom and reallocates a private buffer when the SKB is cloned. The cost is one reallocation and copy per cloned packet, the usual price of this pattern in network drivers. This has no observable effect on its own, as the driver only writes in front of skb->data where no other owner looks. It is a prerequisite for "Bluetooth: btmtksdio: Fix out-of-bounds DMA read in the TX path", which writes padding behind skb->tail, and carries the same Fixes: tag so that both are backported together. Fixes: 9aebfd4a2200 ("Bluetooth: mediatek: add support for MediaTek MT7663S and MT7668S SDIO devices") Signed-off-by: Chris Lu Assisted-by: Claude:claude-opus-5 Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btmtksdio.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/drivers/bluetooth/btmtksdio.c b/drivers/bluetooth/btmtksdio.c index 4e1012e90979..8052e24d503b 100644 --- a/drivers/bluetooth/btmtksdio.c +++ b/drivers/bluetooth/btmtksdio.c @@ -274,13 +274,12 @@ static int btmtksdio_tx_packet(struct btmtksdio_dev *bdev, struct mtkbtsdio_hdr *sdio_hdr; int err; - /* Make sure that there are enough rooms for SDIO header */ - if (unlikely(skb_headroom(skb) < sizeof(*sdio_hdr))) { - err = pskb_expand_head(skb, sizeof(*sdio_hdr), 0, - GFP_ATOMIC); - if (err < 0) - return err; - } + /* Make sure that the data buffer is not shared with anyone else and + * that there is enough room for the SDIO header + */ + err = skb_cow_head(skb, sizeof(*sdio_hdr)); + if (err < 0) + return err; /* Prepend MediaTek SDIO Specific Header */ skb_push(skb, sizeof(*sdio_hdr)); From fa0ad2d277c7adead61d1c22411c55cea6990c2a Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Mon, 17 Aug 2026 17:53:32 +0800 Subject: [PATCH 0912/1328] Bluetooth: btmtksdio: Fix out-of-bounds DMA read in the TX path btmtksdio_tx_packet() rounds the transfer size up to the SDIO block size of 256 bytes, but hands the host controller the SKB buffer as is: err = sdio_writesb(bdev->func, MTK_REG_CTDR, skb->data, round_up(skb->len, MTK_SDIO_BLOCK_SIZE)); Only skb->len bytes hold packet data, so the controller reads up to 255 bytes of uninitialised memory and sends it to the device over the SDIO bus. Depending on how much tailroom slack the SKB allocation happens to carry, that read can also extend past the end of the buffer. Compute the padded length up front, ensure the SKB has tailroom for it, and zero-fill the padding with skb_put_zero(). skb->len then covers the padding, so sdio_writesb() no longer needs to round up. byte_tx keeps counting the header and the payload only, and the error path restores the SKB so that the caller can requeue it. Writing behind skb->tail is only safe because the driver owns the buffer, which "Bluetooth: btmtksdio: Take exclusive ownership of the SKB before TX" ensures. Fixes: 9aebfd4a2200 ("Bluetooth: mediatek: add support for MediaTek MT7663S and MT7668S SDIO devices") Signed-off-by: Chris Lu Assisted-by: Claude:claude-opus-5 Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btmtksdio.c | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/drivers/bluetooth/btmtksdio.c b/drivers/bluetooth/btmtksdio.c index 8052e24d503b..94aa60d9cc20 100644 --- a/drivers/bluetooth/btmtksdio.c +++ b/drivers/bluetooth/btmtksdio.c @@ -272,6 +272,7 @@ static int btmtksdio_tx_packet(struct btmtksdio_dev *bdev, struct sk_buff *skb) { struct mtkbtsdio_hdr *sdio_hdr; + unsigned int len, pad_len; int err; /* Make sure that the data buffer is not shared with anyone else and @@ -281,6 +282,18 @@ static int btmtksdio_tx_packet(struct btmtksdio_dev *bdev, if (err < 0) return err; + /* The transfer is rounded up to the SDIO block size, so the buffer + * has to provide tailroom for the padding as well + */ + len = skb->len + sizeof(*sdio_hdr); + pad_len = round_up(len, MTK_SDIO_BLOCK_SIZE) - len; + + if (unlikely(skb_tailroom(skb) < pad_len)) { + err = pskb_expand_head(skb, 0, pad_len, GFP_ATOMIC); + if (err < 0) + return err; + } + /* Prepend MediaTek SDIO Specific Header */ skb_push(skb, sizeof(*sdio_hdr)); @@ -289,19 +302,22 @@ static int btmtksdio_tx_packet(struct btmtksdio_dev *bdev, sdio_hdr->reserved = cpu_to_le16(0); sdio_hdr->bt_type = hci_skb_pkt_type(skb); - clear_bit(BTMTKSDIO_HW_TX_READY, &bdev->tx_state); - err = sdio_writesb(bdev->func, MTK_REG_CTDR, skb->data, - round_up(skb->len, MTK_SDIO_BLOCK_SIZE)); - if (err < 0) - goto err_skb_pull; + /* Zero the padding so that no uninitialised memory is sent out */ + skb_put_zero(skb, pad_len); - bdev->hdev->stat.byte_tx += skb->len; + clear_bit(BTMTKSDIO_HW_TX_READY, &bdev->tx_state); + err = sdio_writesb(bdev->func, MTK_REG_CTDR, skb->data, skb->len); + if (err < 0) + goto err_skb_restore; + + bdev->hdev->stat.byte_tx += len; kfree_skb(skb); return 0; -err_skb_pull: +err_skb_restore: + skb_trim(skb, len); skb_pull(skb, sizeof(*sdio_hdr)); return err; From 3b8f8d632381fdeb4c4f54fd516d49d4f513b9aa Mon Sep 17 00:00:00 2001 From: Sherry Sun Date: Mon, 17 Aug 2026 10:27:39 +0800 Subject: [PATCH 0913/1328] Bluetooth: btnxpuart: Check remote M.2 connector availability before pwrseq The current code uses of_graph_is_present() to decide whether to enter the pwrseq path. However, of_graph_is_present() only checks for the structural presence of a port/ports sub-node and does not check the status property. This causes problems when a DT overlay disables the remote M.2 connector node (e.g., switching from PCIe WiFi to SDIO WiFi): the port node still exists, so of_graph_is_present() returns true, but the pwrseq provider never registers because the connector is disabled, leading to an infinite -EPROBE_DEFER loop. Replace of_graph_is_present() with a new helper that traverses the OF graph to the remote port parent (the M.2 connector node) and checks of_device_is_available(). When the remote connector is disabled, the pwrseq path is skipped, allowing the BT driver to fall through to the direct bluetooth child node path. Fixes: e48e332d84d8 ("Bluetooth: btnxpuart: Add M.2 Bluetooth device support using pwrseq") Signed-off-by: Sherry Sun Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btnxpuart.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/drivers/bluetooth/btnxpuart.c b/drivers/bluetooth/btnxpuart.c index 81cdd8da5636..e2b8f7997e4e 100644 --- a/drivers/bluetooth/btnxpuart.c +++ b/drivers/bluetooth/btnxpuart.c @@ -1809,6 +1809,28 @@ static void nxp_coredump_notify(struct hci_dev *hdev, int state) kobject_uevent_env(&serdev->dev.kobj, KOBJ_CHANGE, envp); } +/* + * Check if the remote M.2 connector device linked via OF graph is present + * and available. This is used to determine whether the pwrseq path should + * be taken. When the remote connector node is disabled (e.g., by a DT + * overlay switching from PCIe WiFi to SDIO WiFi), the pwrseq path is + * skipped, allowing the BT driver to use a direct bluetooth child node + * instead. + */ +static bool nxp_m2_connector_is_available(struct device *dev) +{ + struct device_node *ep __free(device_node) = + of_graph_get_next_endpoint(dev_of_node(dev), NULL); + + if (!ep) + return false; + + struct device_node *remote __free(device_node) = + of_graph_get_remote_port_parent(ep); + + return remote && of_device_is_available(remote); +} + static int nxp_serdev_probe(struct serdev_device *serdev) { struct hci_dev *hdev; @@ -1863,7 +1885,7 @@ static int nxp_serdev_probe(struct serdev_device *serdev) return err; } - if (of_graph_is_present(dev_of_node(&serdev->ctrl->dev))) { + if (nxp_m2_connector_is_available(&serdev->ctrl->dev)) { struct pwrseq_desc *pwrseq; pwrseq = pwrseq_get(&serdev->ctrl->dev, "uart"); From 5b9b68abb5fa548478e20ee38a0ef5c18e9cf4b7 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 16 Aug 2026 11:59:01 +0300 Subject: [PATCH 0914/1328] Bluetooth: hci_sync: add conditional locking annotations Add context analysis annotations to functions doing conditional locking, to suppress analysis warnings. Fixes: cdc36db204ff ("Bluetooth: hci_sync: Fix advertising data UAFs") Tested-by: Nathan Chancellor # build Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_sync.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index 4d6ab5d39e94..007b8db76283 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -1287,6 +1287,7 @@ hci_set_ext_adv_params_sync(struct hci_dev *hdev, u8 instance, } static int hci_set_ext_adv_data_sync(struct hci_dev *hdev, u8 instance) + __context_unsafe(/* conditional locking */) { DEFINE_FLEX(struct hci_cp_le_set_ext_adv_data, pdu, data, length, HCI_MAX_EXT_AD_LENGTH); @@ -1375,6 +1376,7 @@ int hci_update_adv_data_sync(struct hci_dev *hdev, u8 instance) } int hci_setup_ext_adv_instance_sync(struct hci_dev *hdev, u8 instance) + __context_unsafe(/* conditional locking */) { struct hci_cp_le_set_ext_adv_params cp; struct hci_rp_le_set_ext_adv_params rp; @@ -1535,6 +1537,7 @@ int hci_setup_ext_adv_instance_sync(struct hci_dev *hdev, u8 instance) } static int hci_set_ext_scan_rsp_data_sync(struct hci_dev *hdev, u8 instance) + __context_unsafe(/* conditional locking */) { DEFINE_FLEX(struct hci_cp_le_set_ext_scan_rsp_data, pdu, data, length, HCI_MAX_EXT_AD_LENGTH); @@ -1588,6 +1591,7 @@ static int hci_set_ext_scan_rsp_data_sync(struct hci_dev *hdev, u8 instance) } static int __hci_set_scan_rsp_data_sync(struct hci_dev *hdev, u8 instance) + __context_unsafe(/* conditional locking */) { struct hci_cp_le_set_scan_rsp_data cp; u8 len; @@ -1729,6 +1733,7 @@ static int hci_set_per_adv_params_sync(struct hci_dev *hdev, u8 instance, } static int hci_set_per_adv_data_sync(struct hci_dev *hdev, u8 instance) + __context_unsafe(/* conditional locking */) { DEFINE_FLEX(struct hci_cp_le_set_per_adv_data, pdu, data, length, HCI_MAX_PER_AD_LENGTH); From e5bbf81def17930d0450ecf174a439eb07a09d46 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sat, 15 Aug 2026 10:29:34 -0700 Subject: [PATCH 0915/1328] docs: kernel-parameters: add CPU_FREQ, CPU_IDLE build options CPU_FREQ and CPU_IDLE are used in descriptions of some kernel parameters so add their descriptions to the build options list. Signed-off-by: Randy Dunlap Acked-by: Rafael J. Wysocki (Intel) Signed-off-by: Jonathan Corbet Message-ID: <20260815172934.3604009-1-rdunlap@infradead.org> --- Documentation/admin-guide/kernel-parameters.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index f29a6e6a97c6..37c645f36a57 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -8,6 +8,8 @@ ARM64 ARM64 architecture is enabled. CLK Common clock infrastructure is enabled. CMA Contiguous Memory Area support is enabled. + CPU_FREQ CPU frequency scaling is enabled. + CPU_IDLE CPU idle time management is enabled. DRM Direct Rendering Management support is enabled. DYNAMIC_DEBUG Build in debug messages and enable them at runtime EARLY Parameter processed too early to be embedded in initrd. From 060fa7592bdc043a93b6b7870f5b8551206d315d Mon Sep 17 00:00:00 2001 From: Ali Ahmet Memis Date: Fri, 14 Aug 2026 18:28:48 +0000 Subject: [PATCH 0916/1328] Bluetooth: btnxpuart: Validate the FW dump header length nxp_process_fw_dump() pulls the ACL header off the frame and then reads seq_num and buf_len from a struct nxp_fw_dump_hdr placed at skb->data, without checking that the ACL payload is long enough to contain it. h4_recv_buf() collects HCI_ACL_HDR_SIZE bytes of header followed by the number of payload bytes named in that header, so skb->len is 4 + dlen with dlen supplied by the controller and possibly smaller than the 8 byte dump header, or zero. A short frame with connection handle 0xfff therefore reads both fields from beyond the received data. Beyond the read itself, buf_len is what terminates a dump: a value of zero makes the driver call hci_devcd_complete() and reset the controller, so a truncated frame can end a dump early. Use skb_pull_data() to validate and pull the FW dump header before accessing its fields. Warn and reject the chunk if the header is truncated. Fixes: 998e447f443f ("Bluetooth: btnxpuart: Add support for HCI coredump feature") Signed-off-by: Ali Ahmet Memis Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btnxpuart.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/bluetooth/btnxpuart.c b/drivers/bluetooth/btnxpuart.c index e2b8f7997e4e..f2bbe6e462aa 100644 --- a/drivers/bluetooth/btnxpuart.c +++ b/drivers/bluetooth/btnxpuart.c @@ -1359,12 +1359,21 @@ static int nxp_process_fw_dump(struct hci_dev *hdev, struct sk_buff *skb) { struct hci_acl_hdr *acl_hdr = (struct hci_acl_hdr *)skb_pull_data(skb, sizeof(*acl_hdr)); - struct nxp_fw_dump_hdr *fw_dump_hdr = (struct nxp_fw_dump_hdr *)skb->data; + struct nxp_fw_dump_hdr *fw_dump_hdr; struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); - __u16 seq_num = __le16_to_cpu(fw_dump_hdr->seq_num); - __u16 buf_len = __le16_to_cpu(fw_dump_hdr->buf_len); + __u16 seq_num; + __u16 buf_len; int err; + fw_dump_hdr = skb_pull_data(skb, sizeof(*fw_dump_hdr)); + if (!fw_dump_hdr) { + bt_dev_warn(hdev, "FW dump: invalid or corrupt fw dump chunk"); + goto free_skb; + } + + seq_num = __le16_to_cpu(fw_dump_hdr->seq_num); + buf_len = __le16_to_cpu(fw_dump_hdr->buf_len); + if (seq_num == 0x0001) { if (test_and_set_bit(BTNXPUART_FW_DUMP_IN_PROGRESS, &nxpdev->tx_state)) { bt_dev_err(hdev, "FW dump already in progress"); From 4beb198bc59b242404a47c21990bc84165052c8a Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Sat, 15 Aug 2026 15:24:19 +0900 Subject: [PATCH 0917/1328] Bluetooth: eir: Fix OOB read in eir_get_service_data() eir_get_service_data() walks the advertising data for a Service Data field with a matching UUID. On a mismatch it advances: eir += dlen; eir_len -= dlen; eir_get_data() reports dlen as the field's data length, but the field spans dlen + 2 bytes once its length and type bytes count, and more when non-Service-Data fields were skipped to reach it. The pointer lands correctly on the next field. eir_len does not, and the shortfall compounds across fields until eir_get_data() reads the length and type bytes of a "field" past the end of the buffer. For an ISO broadcast sink that buffer is hcon->le_per_adv_data[], filled from the periodic advertising reports of a remote broadcaster. A PA payload packed with mismatching Service Data fields walks off the array into the rest of struct hci_conn. A drifted field that matches the BAA UUID puts those bytes in iso_pi(sk)->base, where user space reads them back with getsockopt(BT_ISO_BASE). Recompute eir_len from the end of the buffer each iteration. Fixes: 8f9ae5b3ae80 ("Bluetooth: eir: Add helpers for managing service data") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Signed-off-by: HyeongJun An Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/eir.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/bluetooth/eir.c b/net/bluetooth/eir.c index 1de5f9df6eec..a55696820b22 100644 --- a/net/bluetooth/eir.c +++ b/net/bluetooth/eir.c @@ -369,6 +369,7 @@ u8 eir_create_scan_rsp(struct hci_dev *hdev, u8 instance, u8 *ptr) void *eir_get_service_data(u8 *eir, size_t eir_len, u16 uuid, size_t *len) { + const u8 *eir_end = eir + eir_len; size_t dlen; while ((eir = eir_get_data(eir, eir_len, EIR_SERVICE_DATA, &dlen))) { @@ -381,7 +382,7 @@ void *eir_get_service_data(u8 *eir, size_t eir_len, u16 uuid, size_t *len) } eir += dlen; - eir_len -= dlen; + eir_len = eir_end - eir; } return NULL; From aec6a8d80e3da0ab5c9303a0281fd06d077f8716 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Tue, 18 Aug 2026 10:49:34 +0100 Subject: [PATCH 0918/1328] Bluetooth: hci_bcm4377: Ignore reserved PHY in ext adv reports on BCM4378 Commit ed2a2ef16a6b ("Bluetooth: Add quirk to ignore reserved PHY bits in LE Extended Adv Report") added a quirk to handle creative use of the reserved bits in the PHY fields for 4388 controllers in Apple silicon. I observed the same issue with the BCM4378 Bluetooth controller (14e4:5f69, rev 05) on an Apple MacBook Pro (13-inch, M2, 2022): > HCI Event: LE Meta Event (0x3e) plen 51 LE Extended Advertising Report (0x0d) Num reports: 1 Entry 0 Event type: 0x2513 Props: 0x0013 Connectable Scannable Use legacy advertising PDUs Data status: Complete Reserved (0x2500) Legacy PDU Type: Reserved (0x2513) Address type: Random (0x01) Address: EA:C1:82:F0:24:C6 (Static) Primary PHY: Reserved Secondary PHY: No packets SID: no ADI field (0xff) TX power: 127 dBm RSSI: -57 dBm (0xc7) Periodic advertising interval: 0.00 msec (0x0000) Direct address type: Public (0x00) Direct address: 00:00:00:00:00:00 (OUI 00-00-00) Data length: 25 This results in the firmware rejecting connection attempts with "Unsupported Feature or Parameter Value" (0x11). Fix the issue by using the same quirk for BCM4378 devices too. I tested this locally and confirmed that the issue is resolved. This was observed when attempting to connect a Kinesis Advantage 360 keyboard to the MacBook. Assisted-by: Claude:claude-fable-5 Fixes: 2e7ed5f5e69b ("Bluetooth: hci_sync: Use advertised PHYs on hci_le_ext_create_conn_sync") Cc: stable@vger.kernel.org Signed-off-by: Lorenzo Stoakes (ARM) Reviewed-by: Sven Peter Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/hci_bcm4377.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/bluetooth/hci_bcm4377.c b/drivers/bluetooth/hci_bcm4377.c index 925d0a635945..66d49b471544 100644 --- a/drivers/bluetooth/hci_bcm4377.c +++ b/drivers/bluetooth/hci_bcm4377.c @@ -2490,6 +2490,7 @@ static const struct bcm4377_hw bcm4377_hw_variants[] = { .has_bar0_core2_window2 = true, .broken_mws_transport_config = true, .broken_le_coded = true, + .broken_le_ext_adv_report_phy = true, .send_calibration = bcm4378_send_calibration, .send_ptb = bcm4378_send_ptb, }, From ed5fb41d3b6b6e665e7f97fd54bd1f9531c7477f Mon Sep 17 00:00:00 2001 From: Valentin Kindschi Date: Tue, 18 Aug 2026 15:29:34 +0200 Subject: [PATCH 0919/1328] Bluetooth: hci_conn: re-enable advertising only for peripheral role hci_le_conn_failed() unconditionally calls hci_enable_advertising(), although its own comment states advertising should be re-enabled only when the failed attempt was made as a peripheral. hci_le_conn_failed() is reached from hci_conn_failed() for every failed LE connection, including outgoing central connections. For a central attempt this enable is redundant: hci_le_create_conn_sync() already restores advertising via hci_resume_advertising_sync() in its done: block. Because hci_enable_advertising() only queues the work on cmd_sync_work, it runs *after* that resume has already succeeded and set HCI_LE_ADV. The resulting HCI sequence, captured on a BCM43455 (no LE Extended Advertising, so legacy advertising is used): LE Create Connection Status Success ... 13.8 s, peer never answers ... LE Set Advertising Parameters (0x2006) Success <- done: resume, LE Set Advertising Enable (0x200a) Success HCI_LE_ADV set LE Create Connection Cancel (0x200e) Success LE Connection Complete Unknown Conn Id LE Set Advertising Parameters (0x2006) Command Disallowed (0x0c) The last command is the queued enable from hci_le_conn_failed() running as a second hci_enable_advertising_sync() pass. It clears HCI_LE_ADV (hci_sync.c, "Clear the HCI_LE_ADV bit temporarily"), then sends LE Set Advertising Parameters while the controller is still advertising, which the controller correctly rejects with Command Disallowed. The disable-first call at the top of hci_enable_advertising_sync() cannot prevent this: hci_disable_advertising_sync() returns early without sending anything when HCI_LE_ADV is clear, so it is a no-op exactly when the flag is wrong. hci_enable_advertising_sync() then returns without sending LE Set Advertising Enable, so HCI_LE_ADV is never set again. The legacy software rotation loop re-arms hci_schedule_adv_instance_sync() every HCI_DEFAULT_ADV_DURATION (2 s), and its "already advertising" shortcut tests HCI_LE_ADV, which can no longer become true. The command is therefore retried every 2 s indefinitely: Bluetooth: hci0: Opcode 0x2006 failed: -16 Observed on a gateway as 5326 occurrences over 3 hours, ending only when bluetoothd was restarted. Connection attempts that succeed do not call hci_le_conn_failed() and never trigger this. Add the role test the comment already describes. Both other hci_enable_advertising() call sites reached from a failed/closed LE connection (hci_cs_disconnect() and hci_disconn_complete_evt()) already guard on conn->role == HCI_ROLE_SLAVE; this one was missed. Reproducing needs legacy advertising (ext_adv_capable() false, so the software rotation loop is used), simultaneous peripheral advertising and outgoing central connects, and a central connect that times out rather than failing fast. The Fixes tag points at the commit that introduced the advertising restart into this path for the directed-advertising (peripheral) case; the role test that the later commit 0b1db38ca26b ("Bluetooth: Fix check for direct advertising") added to the sibling paths was never applied here. Fixes: 3c857757ef6e ("Bluetooth: Add directed advertising support through connect()") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 btmon Signed-off-by: Valentin Kindschi Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_conn.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index 19b7629b1cc1..8de98af2fb58 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -1391,7 +1391,8 @@ static void hci_le_conn_failed(struct hci_conn *conn, u8 status) /* Enable advertising in case this was a failed connection * attempt as a peripheral. */ - hci_enable_advertising(hdev); + if (conn->role == HCI_ROLE_SLAVE) + hci_enable_advertising(hdev); } /* This function requires the caller holds hdev->lock */ From 941929abe5feaed672b9a52e330e547d333240c6 Mon Sep 17 00:00:00 2001 From: Valentin Kindschi Date: Tue, 18 Aug 2026 15:29:35 +0200 Subject: [PATCH 0920/1328] Bluetooth: hci_event: clear HCI_LE_ADV only on a created connection le_conn_complete_evt() clears HCI_LE_ADV before looking at the event status, on the premise stated in its comment that all controllers stop advertising when a connection is created. That premise only holds when a connection was actually created. On a non-zero status none was, and the controller is still advertising: after the host issues LE Create Connection Cancel the event arrives with Unknown Connection Identifier (0x02), and a connection timeout behaves the same way. Clearing the flag there leaves the host believing advertising is off while the controller has it on. It is also wrong for extended advertising, where several sets can be advertising at once. hci_cc_le_set_ext_adv_enable() is careful about this - on disabling one set it walks hdev->adv_instances and only clears HCI_LE_ADV once no instance is still enabled. The unconditional clear here discards that bookkeeping, so one set connecting drops the flag while the others keep advertising. The direction of the error matters. A flag left set is self-correcting: hci_disable_advertising_sync() sends LE Set Advertising Enable(0) and the command complete puts the state back. A flag left clear is not, because that same function returns early without sending anything while the flag is clear: - LE Set Advertising Parameters is then sent to a controller that is still advertising, and is correctly rejected with Command Disallowed (0x0c); - hci_enable_advertising_sync() returns at that point, before the LE Set Advertising Enable that would set HCI_LE_ADV again. On a controller without LE Extended Advertising that is reachable from here: hci_schedule_adv_instance_sync() re-arms adv_instance_expire every HCI_DEFAULT_ADV_DURATION (2 s) and its "already advertising" shortcut tests HCI_LE_ADV, which can no longer become true, so the parameter write is retried for as long as advertising is configured: Bluetooth: hci0: Opcode 0x2006 failed: -16 Only clear the flag when a connection was established. Note this is not on its own sufficient to stop that retry loop - the redundant enable queued by hci_le_conn_failed() clears HCI_LE_ADV itself and recreates the same mismatch, which patch 1 addresses. This patch fixes the event handler reporting a state the controller is not in. Verified on the affected device (BCM43455, legacy advertising only) with this patch and patch 1 applied. A 221 s btmon capture with an out-of-range peer at -90 dBm contains two outgoing connection attempts that the host cancelled, each producing exactly the event this patch changes: < LE Set Advertising Parameters 0x2006 Success < LE Set Advertising Enable 0x200a Success < LE Create Connection Cancel 0x200e Success > LE Connection Complete Unknown Connection Identifier (0x02), central Nothing follows either one; the next command is an unrelated scan restart 70 ms later. Over the whole capture: 7 LE Set Advertising Parameters sent, all Success; 10 LE Set Advertising Enable, all Success; no Command Disallowed of any opcode, and no 2 s cadence anywhere. Two central connections to other peers completed normally afterwards, with feature exchange and a connection parameter update, so advertising was still live across the cancelled attempts. The extended advertising case above is a code argument, not a measurement: this controller has no LE Extended Advertising, so that path is not exercised by the capture. Fixes: fbd96c151cdc ("Bluetooth: Fix clearing HCI_LE_ADV for LE connections") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 btmon Signed-off-by: Valentin Kindschi Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_event.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index 3eb1eaf6e6a0..2f5e21ff9752 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -5763,10 +5763,11 @@ static void le_conn_complete_evt(struct hci_dev *hdev, u8 status, hci_dev_lock(hdev); hci_store_wake_reason(hdev, bdaddr, bdaddr_type); - /* All controllers implicitly stop advertising in the event of a - * connection, so ensure that the state bit is cleared. + /* Advertising stops when a connection is created. On a failed + * connection it keeps running, so leave the state bit alone. */ - hci_dev_clear_flag(hdev, HCI_LE_ADV); + if (!status) + hci_dev_clear_flag(hdev, HCI_LE_ADV); /* Check for existing connection: * From f5afdff569a09d1cb8cf19826199d024725576cb Mon Sep 17 00:00:00 2001 From: Xin Chen Date: Wed, 19 Aug 2026 21:53:21 +0800 Subject: [PATCH 0921/1328] Bluetooth: hci_core: use skb_get() instead of skb_clone() for req_skb BT enable fails intermittently with -ETIMEDOUT (-110). The kernel log shows the HCI Read Local Version command was sent and the firmware replied with status 0x00 (logged by hci_req_cmd_complete() BT_DBG), but the waiter in __hci_cmd_sync_sk() never woke up and timed out after 10 s: bluetooth hci0: Opcode 0xfc00 // __hci_cmd_sync_sk bluetooth hci0: opcode 0xfc00 plen 1 // hci_cmd_sync_add bluetooth hci0: skb len 4 // hci_cmd_sync_alloc bluetooth hci0: length 1 // hci_req_sync_run Bluetooth: hci0 cmd_cnt 1 cmd queued 1 // hci_cmd_work Bluetooth: hci0 type 1 len 4 // hci_send_frame Bluetooth: opcode 0xfc00 status 0x00 // hci_req_cmd_complete <-- req_skb NULL: req_complete_skb not set, hci_cmd_sync_complete() never called, req_status stays HCI_REQ_PEND --> <-- 10 s later: wait_event_interruptible_timeout expires --> bluetooth hci0: end: err -110 // __hci_cmd_sync_sk The root cause is that hci_send_cmd_sync() clones the sent command into hdev->req_skb so that hci_req_cmd_complete() can locate the registered completion callback. Under memory pressure this skb_clone() fails, leaving hdev->req_skb NULL. The firmware reply is received and processed, but hci_req_cmd_complete() finds NULL req_skb, so hci_cmd_sync_complete() is never called, req_status stays HCI_REQ_PEND, and the waiter times out with -ETIMEDOUT. req_skb is only used to read bt_cb(skb)->hci callbacks and opcode -- it is never modified. Replace skb_clone() with skb_get(), which simply increments the reference count of hdev->sent_cmd without allocating new memory and therefore cannot fail. This issue was first observed as a use-after-free in ttyport_close() when ttyport_open() failed, which was investigated in an earlier patch series [1]. That investigation led to the discovery of the true root cause described above. [1] https://lore.kernel.org/all/20250430111617.1151390-1-quic_cxin@quicinc.com/ Fixes: 2615fd9a7c25 ("Bluetooth: hci_sync: Fix overwriting request callback") Cc: stable@vger.kernel.org Signed-off-by: Xin Chen Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 509c820a693d..35a1be57e386 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -4093,7 +4093,7 @@ static int hci_send_cmd_sync(struct hci_dev *hdev, struct sk_buff *skb) if (READ_ONCE(hdev->req_status) == HCI_REQ_PEND && !hci_dev_test_and_set_flag(hdev, HCI_CMD_PENDING)) { kfree_skb(hdev->req_skb); - hdev->req_skb = skb_clone(hdev->sent_cmd, GFP_KERNEL); + hdev->req_skb = skb_get(hdev->sent_cmd); } return err; From 94509f50d696bff745fe26457ce421cea8525bee Mon Sep 17 00:00:00 2001 From: "Guilherme G. Piccoli" Date: Wed, 19 Aug 2026 15:31:30 -0300 Subject: [PATCH 0922/1328] docs: panic: Disclaimer about console verbosity when using panic_print with pstore Users of panic_print / panic_sys_info are usually aware of the potentially huge amount of information displayed with such options - their goal by using them is indeed to dump more information during panic! What might come unnoticed at first is the impact that the console_verbose() call on panic path has, specially in cases that users rely on pstore as a means to collect the panic logs. Recent experience shows that dumping all tasks in an ARM64 system (with qcom_geni tty driver) gets a 2 minute delay on panic time due to writing such information to console. But in that case, pstore was used, so there was no need for console printing, and suppressing that reduced the full time to less than 0.5 second. The option "printk.console_no_auto_verbose" aims exactly at this use case: avoid automatically increasing the loglevel during panic path. So, let's officially document this to avoid users wasting their time either on long panic printing or debugging the panic delays. While at it, improve the readability of the panic_print text, by adding some line breaks. Suggested-by: Petr Mladek Reviewed-by: Bradley Morgan Reviewed-by: Feng Tang Signed-off-by: Guilherme G. Piccoli Reviewed-by: Petr Mladek Signed-off-by: Jonathan Corbet Message-ID: <20260819183419.2203903-1-gpiccoli@igalia.com> --- .../admin-guide/kernel-parameters.txt | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 37c645f36a57..63e65e3ee85b 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -4859,6 +4859,7 @@ Kernel parameters panic_print= Bitmask for printing system info when panic happens. User can chose combination of the following bits: + bit 0: print all tasks info bit 1: print system memory info bit 2: print timer info @@ -4867,10 +4868,16 @@ Kernel parameters bit 5: replay all kernel messages on consoles at the end of panic bit 6: print all CPUs backtrace (if available in the arch) bit 7: print only tasks in uninterruptible (blocked) state - *Be aware* that this option may print a _lot_ of lines, - so there are risks of losing older messages in the log. - Use this option carefully, maybe worth to setup a - bigger log buffer with "log_buf_len" along with this. + + *Be aware* that this option may print a _lot_ of lines. + + There is an increased risk of losing older messages in + the log. Maybe worth to setup a bigger log buffer with + "log_buf_len" along with this. + + Also consider using "printk.console_no_auto_verbose=Y" + if using this along with pstore, to avoid extra delays + due to increased console verbosity during panic. panic_sys_info= A comma separated list of extra information to be dumped on panic. @@ -4885,7 +4892,9 @@ Kernel parameters all_bt: print all CPUs backtrace (if available in the arch) blocked_tasks: print only tasks in uninterruptible (blocked) state - This is a human readable alternative to the 'panic_print' option. + This is a human readable alternative to the 'panic_print' + option above. Please be aware of the caveats using this, + check the 'panic_print' parameter for more details. panic_console_replay When panic happens, replay all kernel messages on From 560bef609fa5992745929e8d7d458b9d88dd2830 Mon Sep 17 00:00:00 2001 From: Hang Nan <2122295973@qq.com> Date: Wed, 19 Aug 2026 08:57:58 +0800 Subject: [PATCH 0923/1328] Bluetooth: ISO: fix use-after-free of listener socket in iso_conn_ready iso_conn_ready() looks up the BIS listener socket with iso_get_sock(), which takes a reference, and then, without re-checking its state, creates a child socket from it: parent = iso_get_sock(hdev, ...); if (!parent) return; lock_sock(parent); sk = iso_sock_alloc(sock_net(parent), NULL, BTPROTO_ISO, ...); ... iso_chan_add(conn, sk, parent); ... release_sock(parent); sock_put(parent); If the listener socket is closed concurrently, between iso_get_sock() and lock_sock(), the reference taken by iso_get_sock() may be the last one: the close path drops the link-list reference, and once iso_conn_ready() drops its own reference at the end of the function the socket is freed. The child socket, however, is already linked to the freed parent, and a later disconnect of the child runs iso_chan_del() -> bt_accept_unlink(), which dereferences the dangling parent pointer into the freed accept queue (a use-after-free). The same dangling pointer is also dereferenced through parent->***() in iso_chan_del(). Fix it the same way the connected (non-BIS) path was fixed in commit 0d255e63fcf3 ("Bluetooth: ISO: hold sk properly in iso_conn_ready"): after taking the socket lock, re-check that the parent is still a listening, alive socket, and bail out otherwise. Fixes: ccf74f2390d60 ("Bluetooth: Add BTPROTO_ISO socket type") Cc: stable@vger.kernel.org Signed-off-by: Hang Nan <2122295973@qq.com> Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/iso.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index aa2ce78f56a2..75bfd5938b2e 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -2277,6 +2277,14 @@ static void iso_conn_ready(struct iso_conn *conn) lock_sock(parent); + /* The listener may have been closed concurrently. */ + if (parent->sk_state != BT_LISTEN || + sock_flag(parent, SOCK_ZAPPED)) { + release_sock(parent); + sock_put(parent); + return; + } + sk = iso_sock_alloc(sock_net(parent), NULL, BTPROTO_ISO, GFP_ATOMIC, 0); if (!sk) { From 44c98fd082eafd49d55a8a4077ff488175b2fe24 Mon Sep 17 00:00:00 2001 From: Hyunwoo Kim Date: Fri, 20 Mar 2026 00:14:58 +0900 Subject: [PATCH 0924/1328] Bluetooth: RFCOMM: Validate MTU in rfcomm_apply_pn() to prevent infinite loop rfcomm_apply_pn() accepts the MTU value from a remote PN (Parameter Negotiation) frame without checking for zero. When the remote peer sends an MTU of zero, d->mtu is set to 0. This causes the sendmsg path to enter an infinite loop when fragmenting data, as each fragment has size == min_t(size_t, len, 0) == 0, so the remaining length never decreases. The infinite allocation of zero-length skbs exhausts all system memory. Fix by clamping d->mtu to RFCOMM_DEFAULT_MTU when the negotiated value is zero, consistent with the initial value assigned in rfcomm_dlc_alloc(). Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Hyunwoo Kim Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/rfcomm/core.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/bluetooth/rfcomm/core.c b/net/bluetooth/rfcomm/core.c index 9cdfea666a2c..0e496b85e6ce 100644 --- a/net/bluetooth/rfcomm/core.c +++ b/net/bluetooth/rfcomm/core.c @@ -1455,6 +1455,10 @@ static int rfcomm_apply_pn(struct rfcomm_dlc *d, int cr, struct rfcomm_pn *pn) d->mtu = __le16_to_cpu(pn->mtu); + /* MTU 0 causes an infinite loop when fragmenting in sendmsg */ + if (!d->mtu) + d->mtu = RFCOMM_DEFAULT_MTU; + if (cr && d->mtu > s->mtu) d->mtu = s->mtu; From a9355799343e10014f2acfd4b6844d2335ecafea Mon Sep 17 00:00:00 2001 From: Gongwei Li Date: Fri, 21 Aug 2026 10:45:55 +0800 Subject: [PATCH 0925/1328] Bluetooth: hci_uart: Fix false success return in hci_uart_setup() When reading the local version information for vendor detection fails, the error is only printed and 0 is returned, which masks the setup failure from the HCI core. Return PTR_ERR(skb) instead. Fixes: fb2ce8d11f039 ("Bluetooth: hci_uart: Add support for vendor detection flag") Fixes: 82f5169bf3d3b ("Bluetooth: hci_uart: add serdev driver support library") Cc: stable@vger.kernel.org Signed-off-by: Gongwei Li Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/hci_ldisc.c | 2 +- drivers/bluetooth/hci_serdev.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/bluetooth/hci_ldisc.c b/drivers/bluetooth/hci_ldisc.c index 58f5504a336e..697e32122cc8 100644 --- a/drivers/bluetooth/hci_ldisc.c +++ b/drivers/bluetooth/hci_ldisc.c @@ -457,7 +457,7 @@ static int hci_uart_setup(struct hci_dev *hdev) if (IS_ERR(skb)) { BT_ERR("%s: Reading local version information failed (%ld)", hdev->name, PTR_ERR(skb)); - return 0; + return PTR_ERR(skb); } if (skb->len != sizeof(*ver)) { diff --git a/drivers/bluetooth/hci_serdev.c b/drivers/bluetooth/hci_serdev.c index 593d9cefbbf9..d2eaf2f12aa2 100644 --- a/drivers/bluetooth/hci_serdev.c +++ b/drivers/bluetooth/hci_serdev.c @@ -221,7 +221,7 @@ static int hci_uart_setup(struct hci_dev *hdev) if (IS_ERR(skb)) { bt_dev_err(hdev, "Reading local version info failed (%ld)", PTR_ERR(skb)); - return 0; + return PTR_ERR(skb); } if (skb->len != sizeof(*ver)) From ca0583c24661749508a0979189c254388a685559 Mon Sep 17 00:00:00 2001 From: Junjie Cao Date: Mon, 24 Aug 2026 13:32:27 +0800 Subject: [PATCH 0926/1328] Bluetooth: btusb: limit RTL8761B BROKEN_EXT_SCAN quirk to 0bda:a728 Commit 5ead2063611a ("Bluetooth: btrtl: fix RTL8761B/BU broken LE extended scan") set HCI_QUIRK_BROKEN_EXT_SCAN for every CHIP_ID_8761B device to cure repeated 0x2042 failures on an 0bda:a728 dongle. The brokenness is per-dongle, not per-chip: on a TP-Link UB500 (2357:0604, RTL8761BU, fw 0xdfc6d922) extended scan works, and the legacy scan path the quirk forces is what is broken -- LE Set Scan Enable (0x200c) times out with -110 about 30 s after firmware load, btusb resets the device, and the adapter re-enumerates in an endless loop (382 firmware reloads in one boot). 7.1.8, which predates the stable backport, runs clean on this unit; 7.1.9 loops. Move the quirk from btrtl's chip-wide switch to a btusb device-table flag on the USB id the original fix was verified against. Other 8761B dongles return to their earlier long-standing behaviour. Link: https://bugzilla.redhat.com/show_bug.cgi?id=2521504 Fixes: 5ead2063611a ("Bluetooth: btrtl: fix RTL8761B/BU broken LE extended scan") Cc: stable@vger.kernel.org Signed-off-by: Junjie Cao Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btrtl.c | 13 ------------- drivers/bluetooth/btusb.c | 8 ++++++++ 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/drivers/bluetooth/btrtl.c b/drivers/bluetooth/btrtl.c index 7f54d2d2d13a..03fa9409e3ee 100644 --- a/drivers/bluetooth/btrtl.c +++ b/drivers/bluetooth/btrtl.c @@ -1343,19 +1343,6 @@ void btrtl_set_quirks(struct hci_dev *hdev, struct btrtl_device_info *btrtl_dev) if (!btrtl_dev->ic_info) return; - switch (btrtl_dev->project_id) { - case CHIP_ID_8761B: - /* RTL8761B/BU reports HCI version 5.1 but does not support - * the LE Extended Scan commands (Opcode 0x2042), causing - * repeated -EBUSY failures when BlueZ attempts extended - * scanning while a connection is active. - */ - hci_set_quirk(hdev, HCI_QUIRK_BROKEN_EXT_SCAN); - break; - default: - break; - } - switch (btrtl_dev->ic_info->lmp_subver) { case RTL_ROM_LMP_8703B: /* 8723CS reports two pages for local ext features, diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index be82bbbc1b5c..d70a3e7a13f5 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -67,6 +67,7 @@ static struct usb_driver btusb_driver; #define BTUSB_INTEL_NO_WBS_SUPPORT BIT(26) #define BTUSB_ACTIONS_SEMI BIT(27) #define BTUSB_BARROT BIT(28) +#define BTUSB_BROKEN_EXT_SCAN BIT(29) static const struct usb_device_id btusb_table[] = { /* Generic Bluetooth USB device */ @@ -617,6 +618,10 @@ static const struct usb_device_id quirks_table[] = { { USB_DEVICE(0x0489, 0xe130), .driver_info = BTUSB_REALTEK | BTUSB_WIDEBAND_SPEECH }, + /* Realtek 8761BU Bluetooth devices */ + { USB_DEVICE(0x0bda, 0xa728), .driver_info = BTUSB_REALTEK | + BTUSB_BROKEN_EXT_SCAN }, + /* Realtek Bluetooth devices */ { USB_VENDOR_AND_INTERFACE_INFO(0x0bda, 0xe0, 0x01, 0x01), .driver_info = BTUSB_REALTEK }, @@ -4401,6 +4406,9 @@ static int btusb_probe(struct usb_interface *intf, if (id->driver_info & BTUSB_INVALID_LE_STATES) hci_set_quirk(hdev, HCI_QUIRK_BROKEN_LE_STATES); + if (id->driver_info & BTUSB_BROKEN_EXT_SCAN) + hci_set_quirk(hdev, HCI_QUIRK_BROKEN_EXT_SCAN); + if (id->driver_info & BTUSB_DIGIANSWER) { data->cmdreq_type = USB_TYPE_VENDOR; hci_set_quirk(hdev, HCI_QUIRK_RESET_ON_CLOSE); From 759c185d0bbdb131357408f50b8735e04ed3caff Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Sun, 23 Aug 2026 00:43:41 +0800 Subject: [PATCH 0927/1328] Bluetooth: RFCOMM: serialize security confirmation handling rfcomm_security_cfm() looks up a session on session_list and then walks its DLC list without holding rfcomm_mutex. Since RFCOMM session teardown uses rfcomm_mutex, krfcommd can close and free the same session and DLCs concurrently: hci_rx_work krfcommd ----------- --------- rfcomm_session_get() rfcomm_lock() rfcomm_session_close() rfcomm_dlc_unlink() rfcomm_session_del() kfree(s) rfcomm_unlock() walk s->dlcs The callback can then read a freed session list head and touch freed DLCs while updating their flags or timers. Serialize the session lookup and DLC traversal in rfcomm_security_cfm() with rfcomm_mutex. This matches the existing RFCOMM session lifetime rules and prevents concurrent rfcomm_session_del() / rfcomm_dlc_unlink() from tearing the objects down while the callback is using them. KASAN reported: BUG: KASAN: slab-use-after-free in rfcomm_security_cfm+0x41c/0x440 Read of size 8 at addr ffff888111fb3960 by task kworker/u17:1/89 Workqueue: hci0 hci_rx_work Call Trace: rfcomm_security_cfm+0x41c/0x440 hci_encrypt_cfm+0x139/0x590 hci_encrypt_change_evt+0x37b/0xc40 hci_event_packet+0x71b/0xb20 hci_rx_work+0x293/0x730 Allocated by task 69: rfcomm_session_add+0x9e/0x2f0 rfcomm_run+0x44b/0x41e0 Freed by task 69: kfree+0x131/0x3c0 rfcomm_session_del+0x188/0x220 rfcomm_run+0x1985/0x41e0 Fixes: 08c30aca9e698faddebd34f81e1196295f9dc063 ("Bluetooth: Remove RFCOMM session refcnt") Cc: stable@vger.kernel.org Signed-off-by: Chengfeng Ye Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/rfcomm/core.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/net/bluetooth/rfcomm/core.c b/net/bluetooth/rfcomm/core.c index 0e496b85e6ce..63fa0f542ccf 100644 --- a/net/bluetooth/rfcomm/core.c +++ b/net/bluetooth/rfcomm/core.c @@ -2217,9 +2217,13 @@ static void rfcomm_security_cfm(struct hci_conn *conn, u8 status, u8 encrypt) BT_DBG("conn %p status 0x%02x encrypt 0x%02x", conn, status, encrypt); + rfcomm_lock(); + s = rfcomm_session_get(&conn->hdev->bdaddr, &conn->dst); - if (!s) + if (!s) { + rfcomm_unlock(); return; + } list_for_each_entry_safe(d, n, &s->dlcs, list) { if (test_and_clear_bit(RFCOMM_SEC_PENDING, &d->flags)) { @@ -2251,6 +2255,8 @@ static void rfcomm_security_cfm(struct hci_conn *conn, u8 status, u8 encrypt) set_bit(RFCOMM_AUTH_REJECT, &d->flags); } + rfcomm_unlock(); + rfcomm_schedule(); } From 7e1e4047200fd7519f9bdfe8a001437715e618b4 Mon Sep 17 00:00:00 2001 From: Radek Podgorny Date: Mon, 24 Aug 2026 13:00:20 +0200 Subject: [PATCH 0928/1328] Bluetooth: do not leak an hci_conn when a second LE connect is rejected create_le_conn_complete() decides whether the failed connection is still pending by comparing it against hci_lookup_le_connect(), which returns the first LE connection in BT_CONNECT. That is the same connection only while at most one is pending. Two can be pending. Connections created on the passive scan path sit in BT_CONNECT with HCI_CONN_SCANNING set and are invisible to hci_lookup_le_connect() until hci_le_create_conn_sync() clears the flag when their command is issued, so the -EBUSY guard in hci_connect_le() does not prevent a second connection from being queued while the first is still on the scan path. Whenever two connections are in BT_CONNECT at once, the lookup may return one connection while create_le_conn_complete() is reporting the failure of the other; the early exit then drops the error and hci_conn_failed() never runs on the connection that failed. The controller also rejects a second HCI_OP_LE_CREATE_CONN issued while another connection creation is still outstanding, per Core Spec Vol 4, Part E. The spec calls for Command Disallowed there; the bcm43438 observed here answers with an LMP/LL error code instead, which bt_to_errno() maps to the -EPROTO (-71) in the log below. The leaked connection stays in BT_CONNECT forever, and because hci_connect_le() refuses to dial while hci_lookup_le_connect() finds anything, every subsequent attempt to reach any peer fails with -EBUSY and no command reaches the controller at all. Seen on a bcm43438 with two BLE peers polled on the same interval (state 5 is BT_CONNECT; both handles are UNSET ones, allocated from the ida above HCI_CONN_HANDLE_MAX): Bluetooth: hci1: Opcode 0x2013 failed: -71 # hcitool con < LE 14:9C:EF:03:68:81 handle 3840 state 5 lm CENTRAL < LE C4:D3:6A:8C:B5:38 handle 3841 state 5 lm CENTRAL A btmon capture across the next ten minutes of connect attempts contains no HCI_OP_LE_CREATE_CONN at all; outgoing LE connections do not recover until the adapter is reset. With this change the same scenario fails the rejected connection cleanly and further connects to both peers go through. Ask about the connection itself instead of about the device. Fixes: c9f73a2178c1 ("Bluetooth: hci_conn: Fix hci_connect_le_sync") Signed-off-by: Radek Podgorny Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_sync.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index 007b8db76283..ffd7b37e7401 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -7289,8 +7289,13 @@ static void create_le_conn_complete(struct hci_dev *hdev, void *data, int err) goto unlock; } - /* Check if connection is still pending */ - if (conn != hci_lookup_le_connect(hdev)) + /* Check if this connection is still pending. + * + * hci_lookup_le_connect() returns only the first LE connection + * in BT_CONNECT, which is not necessarily this one when two are + * pending at once, so ask the connection itself. + */ + if (conn->state != BT_CONNECT) goto unlock; /* Flush to make sure we send create conn cancel command if needed */ From ebe6674292fda9a58e6f3adffd6d277560857169 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Sat, 22 Aug 2026 23:06:19 +0800 Subject: [PATCH 0929/1328] Bluetooth: RFCOMM: serialize session teardown rfcomm_kill_listener() walks session_list and deletes every session without holding rfcomm_mutex, unlike the normal session processing and connect error paths. Under normal operation, an open RFCOMM socket pins rfcomm.ko, so rfcomm_kill_listener() does not run concurrently with rfcomm_dlc_open(). However, forced module unload via delete_module(O_TRUNC) can stop krfcommd while a failed connect is still unwinding. connect task forced unload / krfcommd ------------ ------------------------ rfcomm_lock() rfcomm_session_add() delete_module("rfcomm", O_TRUNC) rfcomm_kill_listener() fetch session from session_list kernel_connect() fails rfcomm_session_del() remove and free session rfcomm_session_del(session) The final call then reads the freed session and may corrupt the list. KASAN reported with mdelay() to enlarge critical window: BUG: KASAN: slab-use-after-free in rfcomm_run+0x3802/0x3f00 [rfcomm] Read of size 8 at addr ffff888111058d40 by task krfcommd/79 Tainted: [R]=FORCED_RMMOD Allocated by task 86: rfcomm_session_add+0xa1/0x300 [rfcomm] rfcomm_dlc_open+0x8b2/0xf30 [rfcomm] rfcomm_sock_connect+0x34c/0x530 [rfcomm] Freed by task 86: kfree+0x121/0x3c0 rfcomm_dlc_open+0xab7/0xf30 [rfcomm] rfcomm_sock_connect+0x34c/0x530 [rfcomm] Hold rfcomm_mutex across the teardown traversal so every reachable session_list walk uses the same serialization. Reviewed-by: Ali Ahmet Memis Tested-by: Ali Ahmet Memis Reviewed-by: Pauli Virtanen Signed-off-by: Chengfeng Ye Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/rfcomm/core.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/bluetooth/rfcomm/core.c b/net/bluetooth/rfcomm/core.c index 63fa0f542ccf..f7463f092283 100644 --- a/net/bluetooth/rfcomm/core.c +++ b/net/bluetooth/rfcomm/core.c @@ -2182,8 +2182,10 @@ static void rfcomm_kill_listener(void) BT_DBG(""); + rfcomm_lock(); list_for_each_entry_safe(s, n, &session_list, list) rfcomm_session_del(s); + rfcomm_unlock(); } static int rfcomm_run(void *unused) From c0ef04232f9fac083c3d8a77ce58ba32243e6734 Mon Sep 17 00:00:00 2001 From: Shiji Yang Date: Wed, 19 Aug 2026 21:07:07 +0800 Subject: [PATCH 0930/1328] net: ethernet: mtk_wed: increase WED v2 WDMA RESV_BUFF to 0x80 Change WDMA RESV_BUFF from 0x40 to 0x80 to avoid CDM TX FIFO overflow. Without this patch mt7986 and mt7981 may have WDMA TX hang issue. This patch was pulled from mtk-openwrt-feeds GPL open source project. Link: https://github.com/mediatek/mtk-openwrt-feeds/commit/07c87502e854b68b48544d101b6fe17ec059b97b Signed-off-by: Shiji Yang Reviewed-by: Simon Horman Acked-by: Lorenzo Bianconi Link: https://patch.msgid.link/OSZPR01MB779537889255E2F606E47EABBCA52@OSZPR01MB7795.jpnprd01.prod.outlook.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mediatek/mtk_wed.c | 5 +++++ drivers/net/ethernet/mediatek/mtk_wed_regs.h | 1 + 2 files changed, 6 insertions(+) diff --git a/drivers/net/ethernet/mediatek/mtk_wed.c b/drivers/net/ethernet/mediatek/mtk_wed.c index 10d9beaae372..53d3b7e00e1c 100644 --- a/drivers/net/ethernet/mediatek/mtk_wed.c +++ b/drivers/net/ethernet/mediatek/mtk_wed.c @@ -2071,6 +2071,11 @@ mtk_wed_dma_enable(struct mtk_wed_device *dev) wdma_set(dev, MTK_WDMA_GLO_CFG, MTK_WDMA_GLO_CFG_TX_DMA_EN); } + if (mtk_wed_is_v2(dev->hw)) + wdma_m32(dev, MTK_WDMA_GLO_CFG, + MTK_WDMA_GLO_CFG_RESV_BUFF, + FIELD_PREP(MTK_WDMA_GLO_CFG_RESV_BUFF, 0x80)); + wed_set(dev, MTK_WED_GLO_CFG, MTK_WED_GLO_CFG_TX_DMA_EN | MTK_WED_GLO_CFG_RX_DMA_EN); diff --git a/drivers/net/ethernet/mediatek/mtk_wed_regs.h b/drivers/net/ethernet/mediatek/mtk_wed_regs.h index c71190924816..e5f83100ded5 100644 --- a/drivers/net/ethernet/mediatek/mtk_wed_regs.h +++ b/drivers/net/ethernet/mediatek/mtk_wed_regs.h @@ -433,6 +433,7 @@ struct mtk_wdma_desc { #define MTK_WDMA_GLO_CFG_TX_DMA_BUSY BIT(1) #define MTK_WDMA_GLO_CFG_RX_DMA_EN BIT(2) #define MTK_WDMA_GLO_CFG_RX_DMA_BUSY BIT(3) +#define MTK_WDMA_GLO_CFG_RESV_BUFF GENMASK(23, 16) #define MTK_WDMA_GLO_CFG_RX_INFO3_PRERES BIT(26) #define MTK_WDMA_GLO_CFG_RX_INFO2_PRERES BIT(27) #define MTK_WDMA_GLO_CFG_RX_INFO1_PRERES BIT(28) From bd7d30bd1d3856d8354b5acef947d68ad1faf886 Mon Sep 17 00:00:00 2001 From: Stanislav Fomichev Date: Wed, 19 Aug 2026 09:05:33 -0700 Subject: [PATCH 0931/1328] xsk: align TX metadata layout across ABIs Add explicit padding before launch_time so xsk_tx_metadata has the same layout on 32-bit and 64-bit systems. On several architectures (csky, i386, nios2, m65k, openrisc, sh), the old native 32-bit layout put launch_time at offset 12 and had a natural size of 20 bytes. Using sizeof(struct xsk_tx_metadata) as tx_metadata_len was already rejected because the length must be a multiple of eight, so the straightforward use of the interface was broken on those ABIs. Userspace could still register a padded length of 24 bytes, though; mixing the old and new layouts then silently reads launch_time from the wrong offset and misprograms packet launch times. This intentionally replaces that incompatible layout because the affected architectures are unlikely to have any notable users. (x86_64 and arm64 have the most users and are _not_ affected) Fixes: ca4419f15abd ("xsk: Add launch time hardware offload support to XDP Tx metadata") Reviewed-by: Simon Horman Signed-off-by: Stanislav Fomichev Link: https://patch.msgid.link/20260819160535.1472459-2-sdf@fomichev.me Signed-off-by: Jakub Kicinski --- include/uapi/linux/if_xdp.h | 1 + tools/include/uapi/linux/if_xdp.h | 1 + 2 files changed, 2 insertions(+) diff --git a/include/uapi/linux/if_xdp.h b/include/uapi/linux/if_xdp.h index 23a062781468..50d67df78911 100644 --- a/include/uapi/linux/if_xdp.h +++ b/include/uapi/linux/if_xdp.h @@ -149,6 +149,7 @@ struct xsk_tx_metadata { __u16 csum_start; /* Offset from csum_start where checksum should be stored. */ __u16 csum_offset; + __u32 reserved; /* XDP_TXMD_FLAGS_LAUNCH_TIME */ /* Launch time in nanosecond against the PTP HW Clock */ diff --git a/tools/include/uapi/linux/if_xdp.h b/tools/include/uapi/linux/if_xdp.h index 23a062781468..50d67df78911 100644 --- a/tools/include/uapi/linux/if_xdp.h +++ b/tools/include/uapi/linux/if_xdp.h @@ -149,6 +149,7 @@ struct xsk_tx_metadata { __u16 csum_start; /* Offset from csum_start where checksum should be stored. */ __u16 csum_offset; + __u32 reserved; /* XDP_TXMD_FLAGS_LAUNCH_TIME */ /* Launch time in nanosecond against the PTP HW Clock */ From a6e4b9a6deb9362ef7a0706c70d674e92fe1411a Mon Sep 17 00:00:00 2001 From: Stanislav Fomichev Date: Wed, 19 Aug 2026 09:05:34 -0700 Subject: [PATCH 0932/1328] xsk: honor XDP_TX_METADATA in zero-copy path The zero-copy path reads TX metadata whenever the UMEM has metadata space, even if the descriptor does not set XDP_TX_METADATA. Pass descriptor options through the metadata helpers and ignore metadata unless the option is set. This does not fix the existing per-WQE metadata handling for mlx5 MPWQEs. Only the descriptor that starts a session passes through xsk_tx_metadata_request() and configures offload state shared by the batch. Metadata on descriptors joining an open session is therefore not validated and does not configure its requested offloads. In addition, a non-NULL metadata pointer from such a descriptor is treated as a timestamp completion request even when XDP_TXMD_FLAGS_TIMESTAMP is not set, so its metadata union can be overwritten with an unrequested timestamp. Fixing mixed metadata states within one MPWQE requires a separate change. Fixes: 48eb03dd2630 ("xsk: Add TX timestamp and TX checksum offload support") Reviewed-by: Alexander Lobakin Signed-off-by: Stanislav Fomichev Reviewed-by: Maciej Fijalkowski Reviewed-by: Aleksandr Loktionov Link: https://patch.msgid.link/20260819160535.1472459-3-sdf@fomichev.me Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/igc/igc_main.c | 3 ++- .../ethernet/mellanox/mlx5/core/en/xsk/tx.c | 2 +- .../net/ethernet/stmicro/stmmac/stmmac_main.c | 3 ++- include/net/libeth/xsk.h | 2 +- include/net/xdp_sock_drv.h | 22 +++++++++++-------- include/net/xsk_buff_pool.h | 3 ++- net/xdp/xsk_buff_pool.c | 7 ++++-- 7 files changed, 26 insertions(+), 16 deletions(-) diff --git a/drivers/net/ethernet/intel/igc/igc_main.c b/drivers/net/ethernet/intel/igc/igc_main.c index 8b9ce7cbfff9..1fb5f3cbe93c 100644 --- a/drivers/net/ethernet/intel/igc/igc_main.c +++ b/drivers/net/ethernet/intel/igc/igc_main.c @@ -3071,7 +3071,8 @@ static void igc_xdp_xmit_zc(struct igc_ring *ring) olinfo_status = xdp_desc.len << IGC_ADVTXD_PAYLEN_SHIFT; dma = xsk_buff_raw_get_dma(pool, xdp_desc.addr); - meta = xsk_buff_get_metadata(pool, xdp_desc.addr); + meta = xsk_buff_get_metadata(pool, xdp_desc.addr, + xdp_desc.options); xsk_buff_raw_dma_sync_for_device(pool, dma, xdp_desc.len); bi = &ring->tx_buffer_info[ntu]; diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/xsk/tx.c b/drivers/net/ethernet/mellanox/mlx5/core/en/xsk/tx.c index 3d19dad8f868..d194eeddd7b5 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/xsk/tx.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/xsk/tx.c @@ -99,7 +99,7 @@ bool mlx5e_xsk_tx(struct mlx5e_xdpsq *sq, unsigned int budget) xdptxd.dma_addr = xsk_buff_raw_get_dma(pool, desc.addr); xdptxd.data = xsk_buff_raw_get_data(pool, desc.addr); xdptxd.len = desc.len; - meta = xsk_buff_get_metadata(pool, desc.addr); + meta = xsk_buff_get_metadata(pool, desc.addr, desc.options); xsk_buff_raw_dma_sync_for_device(pool, xdptxd.dma_addr, xdptxd.len); diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index b2b7d0242dd3..efa35cfecc4f 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -2723,7 +2723,8 @@ static bool stmmac_xdp_xmit_zc(struct stmmac_priv *priv, u32 queue, u32 budget) tx_desc = stmmac_get_tx_desc(priv, tx_q, entry); dma_addr = xsk_buff_raw_get_dma(pool, xdp_desc.addr); - meta = xsk_buff_get_metadata(pool, xdp_desc.addr); + meta = xsk_buff_get_metadata(pool, xdp_desc.addr, + xdp_desc.options); xsk_buff_raw_dma_sync_for_device(pool, dma_addr, xdp_desc.len); /* To return XDP buffer to XSK pool, we simple call diff --git a/include/net/libeth/xsk.h b/include/net/libeth/xsk.h index 5dcc0d7f65b7..a452b7828ce4 100644 --- a/include/net/libeth/xsk.h +++ b/include/net/libeth/xsk.h @@ -196,7 +196,7 @@ __libeth_xsk_xmit_fill_buf_md(const struct xdp_desc *xdesc, struct libeth_xdp_tx_desc desc; struct xdp_desc_ctx ctx; - ctx = xsk_buff_raw_get_ctx(sq->pool, xdesc->addr); + ctx = xsk_buff_raw_get_ctx(sq->pool, xdesc->addr, xdesc->options); desc = (typeof(desc)){ .addr = ctx.dma, __libeth_xdp_tx_len(xdesc->len), diff --git a/include/net/xdp_sock_drv.h b/include/net/xdp_sock_drv.h index b344789f5df8..d94aeb506379 100644 --- a/include/net/xdp_sock_drv.h +++ b/include/net/xdp_sock_drv.h @@ -240,6 +240,7 @@ static inline void *xsk_buff_raw_get_data(struct xsk_buff_pool *pool, u64 addr) * xsk_buff_raw_get_ctx - get &xdp_desc context * @pool: XSk buff pool desc address belongs to * @addr: desc address (from userspace) + * @options: desc options (from userspace) * * Wrapper for xp_raw_get_ctx() to be used in drivers, see its kdoc for * details. @@ -248,9 +249,9 @@ static inline void *xsk_buff_raw_get_data(struct xsk_buff_pool *pool, u64 addr) * pointer, if it is present (initialized to %NULL otherwise). */ static inline struct xdp_desc_ctx -xsk_buff_raw_get_ctx(const struct xsk_buff_pool *pool, u64 addr) +xsk_buff_raw_get_ctx(const struct xsk_buff_pool *pool, u64 addr, u32 options) { - return xp_raw_get_ctx(pool, addr); + return xp_raw_get_ctx(pool, addr, options); } #define XDP_TXMD_FLAGS_VALID ( \ @@ -318,18 +319,20 @@ xsk_tx_metadata_request(const struct xsk_buff_pool *pool, } static inline struct xsk_tx_metadata * -__xsk_buff_get_metadata(const struct xsk_buff_pool *pool, void *data) +__xsk_buff_get_metadata(const struct xsk_buff_pool *pool, void *data, + unsigned int options) { - if (!pool->tx_metadata_len) + if (!pool->tx_metadata_len || !(options & XDP_TX_METADATA)) return NULL; return data - pool->tx_metadata_len; } static inline struct xsk_tx_metadata * -xsk_buff_get_metadata(struct xsk_buff_pool *pool, u64 addr) +xsk_buff_get_metadata(struct xsk_buff_pool *pool, u64 addr, u32 options) { - return __xsk_buff_get_metadata(pool, xp_raw_get_data(pool, addr)); + return __xsk_buff_get_metadata(pool, xp_raw_get_data(pool, addr), + options); } static inline void xsk_buff_dma_sync_for_cpu(struct xdp_buff *xdp) @@ -510,7 +513,7 @@ static inline void *xsk_buff_raw_get_data(struct xsk_buff_pool *pool, u64 addr) } static inline struct xdp_desc_ctx -xsk_buff_raw_get_ctx(const struct xsk_buff_pool *pool, u64 addr) +xsk_buff_raw_get_ctx(const struct xsk_buff_pool *pool, u64 addr, u32 options) { return (struct xdp_desc_ctx){ }; } @@ -530,13 +533,14 @@ xsk_tx_metadata_request(const struct xsk_buff_pool *pool, } static inline struct xsk_tx_metadata * -__xsk_buff_get_metadata(const struct xsk_buff_pool *pool, void *data) +__xsk_buff_get_metadata(const struct xsk_buff_pool *pool, void *data, + unsigned int options) { return NULL; } static inline struct xsk_tx_metadata * -xsk_buff_get_metadata(struct xsk_buff_pool *pool, u64 addr) +xsk_buff_get_metadata(struct xsk_buff_pool *pool, u64 addr, u32 options) { return NULL; } diff --git a/include/net/xsk_buff_pool.h b/include/net/xsk_buff_pool.h index 2bb1d122b1bc..a7df573784fd 100644 --- a/include/net/xsk_buff_pool.h +++ b/include/net/xsk_buff_pool.h @@ -154,7 +154,8 @@ struct xdp_desc_ctx { struct xsk_tx_metadata *meta; }; -struct xdp_desc_ctx xp_raw_get_ctx(const struct xsk_buff_pool *pool, u64 addr); +struct xdp_desc_ctx xp_raw_get_ctx(const struct xsk_buff_pool *pool, u64 addr, + u32 options); static inline dma_addr_t xp_get_dma(struct xdp_buff_xsk *xskb) { diff --git a/net/xdp/xsk_buff_pool.c b/net/xdp/xsk_buff_pool.c index 78c14f106395..9d2d94f1fb75 100644 --- a/net/xdp/xsk_buff_pool.c +++ b/net/xdp/xsk_buff_pool.c @@ -763,6 +763,7 @@ EXPORT_SYMBOL(xp_raw_get_dma); * xp_raw_get_ctx - get &xdp_desc context * @pool: XSk buff pool desc address belongs to * @addr: desc address (from userspace) + * @options: desc options (from userspace) * * Helper for getting desc's DMA address and metadata pointer, if present. * Saves one call on hotpath and double calculation of the actual address. @@ -771,14 +772,16 @@ EXPORT_SYMBOL(xp_raw_get_dma); * Return: new &xdp_desc_ctx struct containing desc's DMA address and metadata * pointer, if it is present (initialized to %NULL otherwise). */ -struct xdp_desc_ctx xp_raw_get_ctx(const struct xsk_buff_pool *pool, u64 addr) +struct xdp_desc_ctx xp_raw_get_ctx(const struct xsk_buff_pool *pool, u64 addr, + u32 options) { struct xdp_desc_ctx ret; addr = __xp_raw_get_addr(pool, addr); ret.dma = __xp_raw_get_dma(pool, addr); - ret.meta = __xsk_buff_get_metadata(pool, __xp_raw_get_data(pool, addr)); + ret.meta = __xsk_buff_get_metadata(pool, __xp_raw_get_data(pool, addr), + options); return ret; } From d989e22ae9802c52c56ad4284d0caf26696cf6ae Mon Sep 17 00:00:00 2001 From: "Cen Zhang (Microsoft)" Date: Wed, 19 Aug 2026 22:07:35 -0400 Subject: [PATCH 0933/1328] gtp: add synchronize_net() in gtp_newlink() error path to prevent use-after-free gtp_newlink()'s error path frees tid_hash and addr_hash without waiting for an RCU grace period after clearing sk_user_data. A concurrent gtp_encap_recv() in softirq may still hold the gtp_dev pointer obtained via rcu_dereference_sk_user_data() and access the freed memory. BUG: KASAN: slab-use-after-free in gtp0_pdp_find+0x1f6/0x200 (gtp.c:152) Call Trace: gtp0_pdp_find+0x1f6/0x200 gtp_encap_recv+0x527/0x24b0 udp_queue_rcv_one_skb+0x75f/0xc10 Add synchronize_net() before the kfree calls in out_hashtable, which covers all error paths from both gtp_encap_enable() and gtp_create_sockets(). Fixes: 459aa660eb1d8ce6 ("gtp: add initial driver for datapath of GPRS Tunneling Protocol (GTP-U)") Reported-by: AutonomousCodeSecurity@microsoft.com Reported-by: Xiang Mei (Microsoft) Reported-by: Cen Zhang (Microsoft) Signed-off-by: Cen Zhang (Microsoft) Link: https://patch.msgid.link/20260820020735.59474-1-blbllhy@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/gtp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/gtp.c b/drivers/net/gtp.c index 2f6e77cce385..298efc76a56b 100644 --- a/drivers/net/gtp.c +++ b/drivers/net/gtp.c @@ -1549,6 +1549,8 @@ static int gtp_newlink(struct net_device *dev, out_encap: gtp_encap_disable(gtp); out_hashtable: + /* Wait for RCU readers that may still reference this gtp_dev. */ + synchronize_net(); kfree(gtp->addr_hash); kfree(gtp->tid_hash); return err; From ec65631bd5ec251cdf67a4919fac7a3149a6e235 Mon Sep 17 00:00:00 2001 From: Anshumali Gaur Date: Thu, 20 Aug 2026 10:33:33 +0530 Subject: [PATCH 0934/1328] octeontx2-af: fix NULL deref in NIX TM tree debugfs read path rvu_dbg_nix_tm_tree_display() dereferences pfvf->sq_ctx without checking whether the SQ context has been allocated. Reading /sys/kernel/debug/octeontx2/nix/tm_tree for a NIX LF whose transmit queues are not set up triggers a kernel oops. Guard the read path the same way rvu_dbg_nix_tm_tree_write() already does and return -EINVAL with a seq_file message when sq_ctx is NULL. Fixes: b907194a5d5b ("octeontx2-af: Add debugfs support to dump NIX TM topology") Signed-off-by: Anshumali Gaur Signed-off-by: Ratheesh Kannoth Link: https://patch.msgid.link/20260820050333.2606095-1-rkannoth@marvell.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/marvell/octeontx2/af/rvu_debugfs.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_debugfs.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_debugfs.c index 3456313d3b3c..22ee99676879 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_debugfs.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_debugfs.c @@ -1697,6 +1697,12 @@ static int rvu_dbg_nix_tm_tree_display(struct seq_file *m, void *unused) return -EINVAL; pfvf = rvu_get_pfvf(rvu, pcifunc); + + if (!pfvf->sq_ctx) { + seq_printf(m, "SQ context is not initialized for pcifunc 0x%x\n", pcifunc); + return -EINVAL; + } + max_id = pfvf->sq_ctx->qsize; memset(&aq_req, 0, sizeof(struct nix_aq_enq_req)); From 4d5df98369c5f45710b786499f8bd7ffc3196433 Mon Sep 17 00:00:00 2001 From: Anshumali Gaur Date: Thu, 20 Aug 2026 11:24:51 +0530 Subject: [PATCH 0935/1328] octeontx2-af: fix out-of-bounds read setting MSI-X irq affinity rvu_register_interrupts() walks every MSI-X vector and uses strstr() to match "Mbox" or "FLR" in irq_name before pinning those interrupts to CPU 0. irq_name is a per-vector NAME_SIZE buffer, but not every slot is populated before this loop runs. strstr() keeps scanning until it finds a NUL terminator, so an uninitialized slot can trigger a KASAN slab-out-of-bounds read at boot when debug options are enabled. Use strnstr() with NAME_SIZE to bound the search within each vector's name buffer. Fixes: 4e527f1e5c15 ("octeontx2-af: npc: cn20k: Add new mailboxes for CN20K silicon") Signed-off-by: Anshumali Gaur Signed-off-by: Ratheesh Kannoth Link: https://patch.msgid.link/20260820055451.2642358-1-rkannoth@marvell.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/marvell/octeontx2/af/rvu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu.c index ffba56ee8a60..43062390aa4c 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu.c @@ -3331,8 +3331,8 @@ static int rvu_register_interrupts(struct rvu *rvu) goto fail; for (i = 0; i < rvu->num_vec; i++) { - if (strstr(&rvu->irq_name[i * NAME_SIZE], "Mbox") || - strstr(&rvu->irq_name[i * NAME_SIZE], "FLR")) + if (strnstr(&rvu->irq_name[i * NAME_SIZE], "Mbox", NAME_SIZE) || + strnstr(&rvu->irq_name[i * NAME_SIZE], "FLR", NAME_SIZE)) irq_set_affinity(pci_irq_vector(rvu->pdev, i), cpumask_of(0)); } From 3ba97ff4f873481d370bee7f7dfb87f8296af9be Mon Sep 17 00:00:00 2001 From: Anshumali Gaur Date: Thu, 20 Aug 2026 14:06:34 +0530 Subject: [PATCH 0936/1328] octeontx2-vf: fix workqueue and netdev race in probe/remove Initialize the VF workqueue before register_netdev() so ndo_set_rx_mode does not queue work on a NULL workqueue. Unregister the netdev before destroying the workqueue, and add proper probe error cleanup. Fixes: cbc100aa2205 ("octeontx2-nicvf: add ndo_set_rx_mode support for multicast & promisc") Signed-off-by: Nitin Shetty J Signed-off-by: Anshumali Gaur Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260820083634.1641740-1-nshettyj@marvell.com Signed-off-by: Jakub Kicinski --- .../ethernet/marvell/octeontx2/nic/otx2_vf.c | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c index b022f52c6845..fcdf891f90b5 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c +++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c @@ -737,16 +737,16 @@ static int otx2vf_probe(struct pci_dev *pdev, const struct pci_device_id *id) if (err) goto err_ptp_destroy; + err = otx2_vf_wq_init(vf); + if (err) + goto err_ipsec_clean; + err = register_netdev(netdev); if (err) { dev_err(dev, "Failed to register netdevice\n"); - goto err_ipsec_clean; + goto err_wq_destroy; } - err = otx2_vf_wq_init(vf); - if (err) - goto err_unreg_netdev; - otx2vf_set_ethtool_ops(netdev); err = otx2vf_mcam_flow_init(vf); @@ -789,6 +789,10 @@ static int otx2vf_probe(struct pci_dev *pdev, const struct pci_device_id *id) otx2_shutdown_tc(vf); err_unreg_netdev: unregister_netdev(netdev); +err_wq_destroy: + cancel_work_sync(&vf->reset_task); + cancel_work_sync(&vf->rx_mode_work); + destroy_workqueue(vf->otx2_wq); err_ipsec_clean: cn10k_ipsec_clean(vf); err_ptp_destroy: @@ -836,11 +840,13 @@ static void otx2vf_remove(struct pci_dev *pdev) } #endif - cancel_work_sync(&vf->reset_task); otx2_unregister_dl(vf); unregister_netdev(netdev); - if (vf->otx2_wq) + if (vf->otx2_wq) { + cancel_work_sync(&vf->reset_task); + cancel_work_sync(&vf->rx_mode_work); destroy_workqueue(vf->otx2_wq); + } cn10k_ipsec_clean(vf); otx2_ptp_destroy(vf); otx2_mcam_flow_del(vf); From 403f96c32c9e24600093d7d0c61c17daeedca957 Mon Sep 17 00:00:00 2001 From: Yuqi Xu Date: Fri, 21 Aug 2026 11:19:38 +0800 Subject: [PATCH 0937/1328] openvswitch: Fix CT limit teardown use-after-free Packet processing uses CT limit state under RCU, while netns teardown frees that state under ovs_mutex. The CT limit pointer was neither removed from readers nor protected by a grace period, allowing packet processing to dereference the freed state. An unprivileged user can trigger this bug from a user and network namespace, causing a slab-use-after-free in ovs_ct_execute() when the netns is torn down. Publish the CT limit pointer through RCU, remove it before teardown, and wait for readers before freeing its contents. Keep ovs_mutex around individual CT limit updates, and use the RCU read-side lock while GET traverses the RCU-protected limit lists. Netns teardown detaches the RCU-protected CT limit state in the pernet .pre_exit callback while holding ovs_mutex. The pernet core guarantees an RCU grace period between the .pre_exit and .exit callbacks, so the .exit callback completes the teardown without adding any extra synchronization. The netlink command handlers do not need NULL checks because the userspace netlink socket holds an active reference to its network namespace while a request is processed. The per-netns exit path therefore cannot run concurrently with SET, DEL, or GET for that socket's namespace. Fixes: 11efd5cb04a1 ("openvswitch: Support conntrack zone limit") Cc: stable@vger.kernel.org Reported-by: Vega Link: https://lore.kernel.org/all/cover.1784711445.git.xuyuqiabc@gmail.com Co-developed-by: Nan Li Signed-off-by: Nan Li Signed-off-by: Yuqi Xu Reviewed-by: Ren Wei Reviewed-by: Ilya Maximets Link: https://patch.msgid.link/288fbd5459d92b9dd0dcc6faf625f04819161ff3.1787280296.git.xuyuqiabc@gmail.com Signed-off-by: Jakub Kicinski --- net/openvswitch/conntrack.c | 120 ++++++++++++++++++++++++------------ net/openvswitch/conntrack.h | 6 +- net/openvswitch/datapath.c | 10 ++- net/openvswitch/datapath.h | 8 ++- 4 files changed, 98 insertions(+), 46 deletions(-) diff --git a/net/openvswitch/conntrack.c b/net/openvswitch/conntrack.c index 49eb2b0d234d..27115967e5d9 100644 --- a/net/openvswitch/conntrack.c +++ b/net/openvswitch/conntrack.c @@ -932,10 +932,14 @@ static int ovs_ct_check_limit(struct net *net, const struct ovs_conntrack_info *info) { struct ovs_net *ovs_net = net_generic(net, ovs_net_id); - const struct ovs_ct_limit_info *ct_limit_info = ovs_net->ct_limit_info; + const struct ovs_ct_limit_info *ct_limit_info; u32 per_zone_limit, connections; u32 conncount_key; + ct_limit_info = rcu_dereference(ovs_net->ct_limit_info); + if (!ct_limit_info) + return 0; + conncount_key = info->zone.id; per_zone_limit = ct_limit_get(ct_limit_info, info->zone.id); @@ -1579,40 +1583,55 @@ static void __ovs_ct_free_action(struct ovs_conntrack_info *ct_info) #if IS_ENABLED(CONFIG_NETFILTER_CONNCOUNT) static int ovs_ct_limit_init(struct net *net, struct ovs_net *ovs_net) { + struct ovs_ct_limit_info *info; int i, err; - ovs_net->ct_limit_info = kmalloc_obj(*ovs_net->ct_limit_info); - if (!ovs_net->ct_limit_info) + info = kmalloc_obj(*info); + if (!info) return -ENOMEM; - ovs_net->ct_limit_info->default_limit = OVS_CT_LIMIT_DEFAULT; - ovs_net->ct_limit_info->limits = + info->default_limit = OVS_CT_LIMIT_DEFAULT; + info->limits = kmalloc_objs(struct hlist_head, CT_LIMIT_HASH_BUCKETS); - if (!ovs_net->ct_limit_info->limits) { - kfree(ovs_net->ct_limit_info); + if (!info->limits) { + kfree(info); return -ENOMEM; } for (i = 0; i < CT_LIMIT_HASH_BUCKETS; i++) - INIT_HLIST_HEAD(&ovs_net->ct_limit_info->limits[i]); + INIT_HLIST_HEAD(&info->limits[i]); - ovs_net->ct_limit_info->data = nf_conncount_init(net, sizeof(u32)); + info->data = nf_conncount_init(net, sizeof(u32)); - if (IS_ERR(ovs_net->ct_limit_info->data)) { - err = PTR_ERR(ovs_net->ct_limit_info->data); - kfree(ovs_net->ct_limit_info->limits); - kfree(ovs_net->ct_limit_info); + if (IS_ERR(info->data)) { + err = PTR_ERR(info->data); + kfree(info->limits); + kfree(info); pr_err("openvswitch: failed to init nf_conncount %d\n", err); return err; } + rcu_assign_pointer(ovs_net->ct_limit_info, info); return 0; } -static void ovs_ct_limit_exit(struct net *net, struct ovs_net *ovs_net) +static void *ovs_ct_limit_exit_start(struct ovs_net *ovs_net) { - const struct ovs_ct_limit_info *info = ovs_net->ct_limit_info; + return rcu_replace_pointer(ovs_net->ct_limit_info, NULL, + lockdep_ovsl_is_held()); +} + +/* The CT limit state must be detached by ovs_ct_limit_exit_start() and an + * RCU grace period must elapse before this function runs. The pernet core + * guarantees the grace period between the .pre_exit and .exit callbacks. + */ +static void ovs_ct_limit_exit_finish(struct net *net, void *data) +{ + const struct ovs_ct_limit_info *info = data; int i; + if (!info) + return; + nf_conncount_destroy(net, info->data); for (i = 0; i < CT_LIMIT_HASH_BUCKETS; ++i) { struct hlist_head *head = &info->limits[i]; @@ -1620,7 +1639,7 @@ static void ovs_ct_limit_exit(struct net *net, struct ovs_net *ovs_net) struct hlist_node *next; hlist_for_each_entry_safe(ct_limit, next, head, hlist_node) - kfree_rcu(ct_limit, rcu); + kfree(ct_limit); } kfree(info->limits); kfree(info); @@ -1659,12 +1678,13 @@ static bool check_zone_id(int zone_id, u16 *pzone) return false; } -static int ovs_ct_limit_set_zone_limit(struct nlattr *nla_zone_limit, - struct ovs_ct_limit_info *info) +static int ovs_ct_limit_set_zone_limit(struct ovs_net *ovs_net, + struct nlattr *nla_zone_limit) { struct ovs_zone_limit *zone_limit; - int rem; + struct ovs_ct_limit_info *info; u16 zone; + int rem; rem = NLA_ALIGN(nla_len(nla_zone_limit)); zone_limit = (struct ovs_zone_limit *)nla_data(nla_zone_limit); @@ -1673,6 +1693,7 @@ static int ovs_ct_limit_set_zone_limit(struct nlattr *nla_zone_limit, if (unlikely(zone_limit->zone_id == OVS_ZONE_LIMIT_DEFAULT_ZONE)) { ovs_lock(); + info = ovsl_dereference(ovs_net->ct_limit_info); info->default_limit = zone_limit->limit; ovs_unlock(); } else if (unlikely(!check_zone_id( @@ -1689,6 +1710,7 @@ static int ovs_ct_limit_set_zone_limit(struct nlattr *nla_zone_limit, ct_limit->limit = zone_limit->limit; ovs_lock(); + info = ovsl_dereference(ovs_net->ct_limit_info); ct_limit_set(info, ct_limit); ovs_unlock(); } @@ -1703,12 +1725,13 @@ static int ovs_ct_limit_set_zone_limit(struct nlattr *nla_zone_limit, return 0; } -static int ovs_ct_limit_del_zone_limit(struct nlattr *nla_zone_limit, - struct ovs_ct_limit_info *info) +static int ovs_ct_limit_del_zone_limit(struct ovs_net *ovs_net, + struct nlattr *nla_zone_limit) { struct ovs_zone_limit *zone_limit; - int rem; + struct ovs_ct_limit_info *info; u16 zone; + int rem; rem = NLA_ALIGN(nla_len(nla_zone_limit)); zone_limit = (struct ovs_zone_limit *)nla_data(nla_zone_limit); @@ -1717,6 +1740,7 @@ static int ovs_ct_limit_del_zone_limit(struct nlattr *nla_zone_limit, if (unlikely(zone_limit->zone_id == OVS_ZONE_LIMIT_DEFAULT_ZONE)) { ovs_lock(); + info = ovsl_dereference(ovs_net->ct_limit_info); info->default_limit = OVS_CT_LIMIT_DEFAULT; ovs_unlock(); } else if (unlikely(!check_zone_id( @@ -1724,6 +1748,7 @@ static int ovs_ct_limit_del_zone_limit(struct nlattr *nla_zone_limit, OVS_NLERR(true, "zone id is out of range"); } else { ovs_lock(); + info = ovsl_dereference(ovs_net->ct_limit_info); ct_limit_del(info, zone); ovs_unlock(); } @@ -1767,6 +1792,7 @@ static int __ovs_ct_limit_get_zone_limit(struct net *net, return nla_put_nohdr(reply, sizeof(zone_limit), &zone_limit); } +/* Called with RCU read lock held. */ static int ovs_ct_limit_get_zone_limit(struct net *net, struct nlattr *nla_zone_limit, struct ovs_ct_limit_info *info, @@ -1790,12 +1816,10 @@ static int ovs_ct_limit_get_zone_limit(struct net *net, &zone))) { OVS_NLERR(true, "zone id is out of range"); } else { - rcu_read_lock(); limit = ct_limit_get(info, zone); err = __ovs_ct_limit_get_zone_limit( net, info->data, zone, limit, reply); - rcu_read_unlock(); if (err) return err; } @@ -1810,6 +1834,7 @@ static int ovs_ct_limit_get_zone_limit(struct net *net, return 0; } +/* Called with RCU read lock held. */ static int ovs_ct_limit_get_all_zone_limit(struct net *net, struct ovs_ct_limit_info *info, struct sk_buff *reply) @@ -1822,19 +1847,16 @@ static int ovs_ct_limit_get_all_zone_limit(struct net *net, if (err) return err; - rcu_read_lock(); for (i = 0; i < CT_LIMIT_HASH_BUCKETS; ++i) { head = &info->limits[i]; hlist_for_each_entry_rcu(ct_limit, head, hlist_node) { err = __ovs_ct_limit_get_zone_limit(net, info->data, ct_limit->zone, ct_limit->limit, reply); if (err) - goto exit_err; + return err; } } -exit_err: - rcu_read_unlock(); return err; } @@ -1844,7 +1866,6 @@ static int ovs_ct_limit_cmd_set(struct sk_buff *skb, struct genl_info *info) struct sk_buff *reply; struct ovs_header *ovs_reply_header; struct ovs_net *ovs_net = net_generic(sock_net(skb->sk), ovs_net_id); - struct ovs_ct_limit_info *ct_limit_info = ovs_net->ct_limit_info; int err; reply = ovs_ct_limit_cmd_reply_start(info, OVS_CT_LIMIT_CMD_SET, @@ -1857,8 +1878,8 @@ static int ovs_ct_limit_cmd_set(struct sk_buff *skb, struct genl_info *info) goto exit_err; } - err = ovs_ct_limit_set_zone_limit(a[OVS_CT_LIMIT_ATTR_ZONE_LIMIT], - ct_limit_info); + err = ovs_ct_limit_set_zone_limit(ovs_net, + a[OVS_CT_LIMIT_ATTR_ZONE_LIMIT]); if (err) goto exit_err; @@ -1878,7 +1899,6 @@ static int ovs_ct_limit_cmd_del(struct sk_buff *skb, struct genl_info *info) struct sk_buff *reply; struct ovs_header *ovs_reply_header; struct ovs_net *ovs_net = net_generic(sock_net(skb->sk), ovs_net_id); - struct ovs_ct_limit_info *ct_limit_info = ovs_net->ct_limit_info; int err; reply = ovs_ct_limit_cmd_reply_start(info, OVS_CT_LIMIT_CMD_DEL, @@ -1891,8 +1911,8 @@ static int ovs_ct_limit_cmd_del(struct sk_buff *skb, struct genl_info *info) goto exit_err; } - err = ovs_ct_limit_del_zone_limit(a[OVS_CT_LIMIT_ATTR_ZONE_LIMIT], - ct_limit_info); + err = ovs_ct_limit_del_zone_limit(ovs_net, + a[OVS_CT_LIMIT_ATTR_ZONE_LIMIT]); if (err) goto exit_err; @@ -1912,7 +1932,7 @@ static int ovs_ct_limit_cmd_get(struct sk_buff *skb, struct genl_info *info) struct ovs_header *ovs_reply_header; struct net *net = sock_net(skb->sk); struct ovs_net *ovs_net = net_generic(net, ovs_net_id); - struct ovs_ct_limit_info *ct_limit_info = ovs_net->ct_limit_info; + struct ovs_ct_limit_info *ct_limit_info; int err; reply = ovs_ct_limit_cmd_reply_start(info, OVS_CT_LIMIT_CMD_GET, @@ -1926,18 +1946,19 @@ static int ovs_ct_limit_cmd_get(struct sk_buff *skb, struct genl_info *info) goto exit_err; } + rcu_read_lock(); + ct_limit_info = rcu_dereference(ovs_net->ct_limit_info); if (a[OVS_CT_LIMIT_ATTR_ZONE_LIMIT]) { err = ovs_ct_limit_get_zone_limit( net, a[OVS_CT_LIMIT_ATTR_ZONE_LIMIT], ct_limit_info, reply); - if (err) - goto exit_err; } else { err = ovs_ct_limit_get_all_zone_limit(net, ct_limit_info, reply); - if (err) - goto exit_err; } + rcu_read_unlock(); + if (err) + goto exit_err; nla_nest_end(reply, nla_reply); genlmsg_end(reply, ovs_reply_header); @@ -2012,12 +2033,29 @@ int ovs_ct_init(struct net *net) return err; } -void ovs_ct_exit(struct net *net) +/* Must be called with ovs_mutex held. Detaches the RCU-protected + * ct_limit_info and stores it in ovs_net->ct_limit_exit_data for + * ovs_ct_exit_finish() to complete the teardown after an RCU grace period. + */ +void ovs_ct_exit_start(struct net *net __maybe_unused) +{ +#if IS_ENABLED(CONFIG_NETFILTER_CONNCOUNT) + struct ovs_net *ovs_net = net_generic(net, ovs_net_id); + + ovs_net->ct_limit_exit_data = ovs_ct_limit_exit_start(ovs_net); +#endif +} + +/* Completes the CT limit teardown. The pernet core guarantees an RCU + * grace period between detaching the state in ovs_ct_exit_start() and + * this call, so no RCU readers remain. + */ +void ovs_ct_exit_finish(struct net *net) { struct ovs_net *ovs_net = net_generic(net, ovs_net_id); #if IS_ENABLED(CONFIG_NETFILTER_CONNCOUNT) - ovs_ct_limit_exit(net, ovs_net); + ovs_ct_limit_exit_finish(net, ovs_net->ct_limit_exit_data); #endif if (ovs_net->xt_label) diff --git a/net/openvswitch/conntrack.h b/net/openvswitch/conntrack.h index 317e525c8a11..ab21d032fde3 100644 --- a/net/openvswitch/conntrack.h +++ b/net/openvswitch/conntrack.h @@ -14,7 +14,8 @@ enum ovs_key_attr; #if IS_ENABLED(CONFIG_NF_CONNTRACK) int ovs_ct_init(struct net *); -void ovs_ct_exit(struct net *); +void ovs_ct_exit_start(struct net *net); +void ovs_ct_exit_finish(struct net *net); bool ovs_ct_verify(struct net *, enum ovs_key_attr attr); int ovs_ct_copy_action(struct net *, const struct nlattr *, const struct sw_flow_key *, struct sw_flow_actions **, @@ -40,7 +41,8 @@ void ovs_ct_free_action(const struct nlattr *a); static inline int ovs_ct_init(struct net *net) { return 0; } -static inline void ovs_ct_exit(struct net *net) { } +static inline void ovs_ct_exit_start(struct net *net) { } +static inline void ovs_ct_exit_finish(struct net *net) { } static inline bool ovs_ct_verify(struct net *net, int attr) { diff --git a/net/openvswitch/datapath.c b/net/openvswitch/datapath.c index f2d5b5ab38de..100a2ce4d74d 100644 --- a/net/openvswitch/datapath.c +++ b/net/openvswitch/datapath.c @@ -2742,6 +2742,13 @@ static void __net_exit list_vports_from_net(struct net *net, struct net *dnet, } } +static void __net_exit ovs_pre_exit_net(struct net *dnet) +{ + ovs_lock(); + ovs_ct_exit_start(dnet); + ovs_unlock(); +} + static void __net_exit ovs_exit_net(struct net *dnet) { struct datapath *dp, *dp_next; @@ -2752,7 +2759,7 @@ static void __net_exit ovs_exit_net(struct net *dnet) ovs_lock(); - ovs_ct_exit(dnet); + ovs_ct_exit_finish(dnet); list_for_each_entry_safe(dp, dp_next, &ovs_net->dps, list_node) __dp_destroy(dp); @@ -2776,6 +2783,7 @@ static void __net_exit ovs_exit_net(struct net *dnet) static struct pernet_operations ovs_net_ops = { .init = ovs_init_net, + .pre_exit = ovs_pre_exit_net, .exit = ovs_exit_net, .id = &ovs_net_id, .size = sizeof(struct ovs_net), diff --git a/net/openvswitch/datapath.h b/net/openvswitch/datapath.h index 696640e88fa7..b2c2b8da12d4 100644 --- a/net/openvswitch/datapath.h +++ b/net/openvswitch/datapath.h @@ -164,7 +164,10 @@ struct dp_upcall_info { * Protected by genl_mutex. * @dp_notify_work: A work notifier to handle port unregistering. * @masks_rebalance: A work to periodically optimize flow table caches. - * @ct_limit_info: A hash table of conntrack zone connection limits. + * @ct_limit_info: Hash table of conntrack zone connection limits. Protected + * by RCU; updates and teardown are serialized by ovs_mutex. May be NULL during + * netns teardown. + * @ct_limit_exit_data: CT limit state detached at .pre_exit, freed at .exit. * @xt_label: Whether connlables are configured for the network or not. */ struct ovs_net { @@ -172,7 +175,8 @@ struct ovs_net { struct work_struct dp_notify_work; struct delayed_work masks_rebalance; #if IS_ENABLED(CONFIG_NETFILTER_CONNCOUNT) - struct ovs_ct_limit_info *ct_limit_info; + struct ovs_ct_limit_info __rcu *ct_limit_info; + struct ovs_ct_limit_info *ct_limit_exit_data; #endif bool xt_label; }; From 2d83aa65dc983368ce14f1a1daa05ca56d38ab5c Mon Sep 17 00:00:00 2001 From: Qingshuang Fu Date: Fri, 21 Aug 2026 11:14:41 +0800 Subject: [PATCH 0938/1328] selftests/net: fix kill() argument order and wrapper cleanup in fin_ack_lat sig_handler() passes its arguments to kill() in the wrong order: it sends signal number child_pid to PID SIGTERM (15) instead of sending SIGTERM to the client process. The call therefore always fails and the signal is never forwarded: when only the server process receives SIGTERM, the client keeps running its infinite connect loop as an orphan process. Swap the arguments so that the server forwards SIGTERM to the client. Guard the call with child_pid > 0: the client inherits the handler and sees child_pid == 0, and a plain argument swap would make it call kill(0, SIGTERM), signaling the whole process group instead of exiting quietly. Now that the server actually terminates the client before the wrapper script's cleanup runs, kill() may fail with ESRCH for the already-exited client. The script uses set -e, so make the kill tolerant to avoid aborting the EXIT trap and leaking temporary files. Signed-off-by: Qingshuang Fu Reviewed-by: Hangbin Liu Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260821031442.1124777-1-fffsqian@163.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/fin_ack_lat.c | 3 ++- tools/testing/selftests/net/fin_ack_lat.sh | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/net/fin_ack_lat.c b/tools/testing/selftests/net/fin_ack_lat.c index 4117332eb1a9..98044e6f9f43 100644 --- a/tools/testing/selftests/net/fin_ack_lat.c +++ b/tools/testing/selftests/net/fin_ack_lat.c @@ -103,7 +103,8 @@ static void server(int sock, struct sockaddr_in address) static void sig_handler(int signum) { - kill(SIGTERM, child_pid); + if (child_pid > 0) + kill(child_pid, SIGTERM); exit(0); } diff --git a/tools/testing/selftests/net/fin_ack_lat.sh b/tools/testing/selftests/net/fin_ack_lat.sh index a3ff6e0b2c7a..a8aa2238ab5c 100755 --- a/tools/testing/selftests/net/fin_ack_lat.sh +++ b/tools/testing/selftests/net/fin_ack_lat.sh @@ -9,7 +9,7 @@ set -e tmpfile=$(mktemp /tmp/fin_ack_latency.XXXX.log) cleanup() { - kill $(pidof fin_ack_lat) + kill $(pidof fin_ack_lat) 2>/dev/null || true rm -f $tmpfile } From 11e41444a3f6d854937672343a040607c219db0f Mon Sep 17 00:00:00 2001 From: Qingshuang Fu Date: Fri, 21 Aug 2026 11:14:42 +0800 Subject: [PATCH 0939/1328] selftests/net: check fork() return value in fin_ack_lat main() never checks fork() for failure. When fork() returns -1 (EAGAIN/ENOMEM/RLIMIT_NPROC), the !child_pid test is false and the process falls into server()'s infinite accept() loop with no client ever connecting, producing empty output. The wrapper script treats an empty log as a passing test, producing a false positive. Check fork() for failure with error(), as is done for every other syscall in this file. Signed-off-by: Qingshuang Fu Reviewed-by: Hangbin Liu Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260821031442.1124777-2-fffsqian@163.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/fin_ack_lat.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/testing/selftests/net/fin_ack_lat.c b/tools/testing/selftests/net/fin_ack_lat.c index 98044e6f9f43..4068f8e227cf 100644 --- a/tools/testing/selftests/net/fin_ack_lat.c +++ b/tools/testing/selftests/net/fin_ack_lat.c @@ -143,6 +143,8 @@ int main(int argc, char const *argv[]) fprintf(stderr, "server port: %d\n", ntohs(laddr.sin_port)); child_pid = fork(); + if (child_pid < 0) + error(-1, errno, "fork"); if (!child_pid) client(ntohs(laddr.sin_port)); else From a66734a1c5e36525ea07e9f4547fddc51e916de3 Mon Sep 17 00:00:00 2001 From: Xiang Mei Date: Thu, 20 Aug 2026 12:52:40 -0700 Subject: [PATCH 0940/1328] net: qualcomm: rmnet: restore skb->dev on deaggregated frames rmnet_map_deaggregate() allocates each sub-frame with alloc_skb() and leaves skb->dev NULL. __rmnet_map_ingress_handler() assigns skb->dev = ep->egress_dev only on the data path, but a MAP command frame is dispatched to rmnet_map_command() before that, so rmnet_map_send_ack() runs netif_tx_lock(skb->dev) on a NULL device. An unprivileged user reaches this by unsharing a user+net namespace, creating an rmnet link over a tap device with INGRESS_DEAGGREGATION and INGRESS_MAP_COMMANDS, and writing an aggregated frame carrying a flow-control command to the tap fd. Restore the assignment dropped by 378e25357ac7, so every skb leaving rmnet_map_deaggregate() has a valid device. BUG: KASAN: null-ptr-deref in _raw_spin_lock (kernel/locking/spinlock.c:158) Write of size 4 at addr 00000000000004b4 by task exploit/144 Call Trace: _raw_spin_lock (kernel/locking/spinlock.c:158) netif_tx_lock (net/sched/sch_generic.c:497) rmnet_map_command (drivers/net/ethernet/qualcomm/rmnet/rmnet_map_command.c:67) rmnet_rx_handler (drivers/net/ethernet/qualcomm/rmnet/rmnet_handlers.c:125) __netif_receive_skb_core.constprop.0 (net/core/dev.c:6103) ... __netif_receive_skb_one_core (net/core/dev.c:6214) netif_receive_skb (net/core/dev.c:6474) tun_get_user (drivers/net/tun.c:1966) tun_chr_write_iter (drivers/net/tun.c:2012) vfs_write (fs/read_write.c:687) ksys_write (fs/read_write.c:739) do_syscall_64 (arch/x86/entry/syscall_64.c:94) entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121) Kernel panic - not syncing: Fatal exception in interrupt Fixes: 378e25357ac7 ("net: qualcomm: rmnet: Remove unnecessary device assignment") Reported-by: co+4638111fe2a12980@bugs.sh Closes: https://lore.kernel.org/netdev/ijg79FFMfIvKJbivdJEKvTO90Q9dTvyBkJck@bugs.sh/T/#u Signed-off-by: Xiang Mei Reviewed-by: Subash Abhinov Kasiviswanathan Link: https://patch.msgid.link/20260820195240.1631458-1-xmei5@asu.edu Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/qualcomm/rmnet/rmnet_map_data.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_map_data.c b/drivers/net/ethernet/qualcomm/rmnet/rmnet_map_data.c index 305ae15ae8f3..e6f48dc9fe64 100644 --- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_map_data.c +++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_map_data.c @@ -394,6 +394,7 @@ struct sk_buff *rmnet_map_deaggregate(struct sk_buff *skb, if (!skbn) return NULL; + skbn->dev = skb->dev; skb_reserve(skbn, RMNET_MAP_DEAGGR_HEADROOM); skb_put(skbn, packet_len); memcpy(skbn->data, skb->data, packet_len); From af20e269f7459d2ce69887fdf2fad7caf986c865 Mon Sep 17 00:00:00 2001 From: Zihan Xi Date: Thu, 20 Aug 2026 18:40:28 +0000 Subject: [PATCH 0941/1328] net: l2tp: do not propagate multicast notification errors The tunnel create, tunnel modify, session create, and session modify netlink handlers send multicast notifications through helpers that can fail while allocating or encoding a message, or while multicasting it. For tunnel and session create/modify, a notification is sent after the live operation has completed. Returning a best-effort notification error as the command result can therefore report failure for an operation that already committed and can cause callers to retry and accumulate live objects. Keep sending notifications for listener visibility, but do not propagate their best-effort status as the command result. This also keeps the tunnel modify command consistent with the other notification-only paths. Fixes: 33f72e6f0c67 ("l2tp : multicast notification to the registered listeners") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Zihan Xi Reviewed-by: Simon Horman Link: https://patch.msgid.link/54f48e812ca0424c47ffdb9a8182180921f7e6b2.1787247008.git.zihanx@nebusec.ai Signed-off-by: Jakub Kicinski --- net/l2tp/l2tp_netlink.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/net/l2tp/l2tp_netlink.c b/net/l2tp/l2tp_netlink.c index c0c4d1ebc7a3..38aac59d052c 100644 --- a/net/l2tp/l2tp_netlink.c +++ b/net/l2tp/l2tp_netlink.c @@ -251,8 +251,8 @@ static int l2tp_nl_cmd_tunnel_create(struct sk_buff *skb, struct genl_info *info kfree(tunnel); goto out; } - ret = l2tp_tunnel_notify(&l2tp_nl_family, info, tunnel, - L2TP_CMD_TUNNEL_CREATE); + l2tp_tunnel_notify(&l2tp_nl_family, info, tunnel, + L2TP_CMD_TUNNEL_CREATE); l2tp_tunnel_put(tunnel); out: @@ -308,8 +308,8 @@ static int l2tp_nl_cmd_tunnel_modify(struct sk_buff *skb, struct genl_info *info goto out; } - ret = l2tp_tunnel_notify(&l2tp_nl_family, info, - tunnel, L2TP_CMD_TUNNEL_MODIFY); + l2tp_tunnel_notify(&l2tp_nl_family, info, + tunnel, L2TP_CMD_TUNNEL_MODIFY); l2tp_tunnel_put(tunnel); @@ -648,8 +648,8 @@ static int l2tp_nl_cmd_session_create(struct sk_buff *skb, struct genl_info *inf session = l2tp_session_get(net, tunnel->sock, tunnel->version, tunnel_id, session_id); if (session) { - ret = l2tp_session_notify(&l2tp_nl_family, info, session, - L2TP_CMD_SESSION_CREATE); + l2tp_session_notify(&l2tp_nl_family, info, session, + L2TP_CMD_SESSION_CREATE); l2tp_session_put(session); } } @@ -713,8 +713,8 @@ static int l2tp_nl_cmd_session_modify(struct sk_buff *skb, struct genl_info *inf if (info->attrs[L2TP_ATTR_RECV_TIMEOUT]) session->reorder_timeout = nla_get_msecs(info->attrs[L2TP_ATTR_RECV_TIMEOUT]); - ret = l2tp_session_notify(&l2tp_nl_family, info, - session, L2TP_CMD_SESSION_MODIFY); + l2tp_session_notify(&l2tp_nl_family, info, + session, L2TP_CMD_SESSION_MODIFY); l2tp_session_put(session); From 317fe168f4c0fc30e5c375c22d9c8911940aea9b Mon Sep 17 00:00:00 2001 From: Md Rabbani Date: Fri, 21 Aug 2026 11:57:18 +0600 Subject: [PATCH 0942/1328] net: stmmac: fix device node reference leaks in stmmac_mtl_setup() In stmmac_mtl_setup(), q_node is shared across the RX and TX queue parsing loops. When the RX queue loop breaks early because the number of parsed queues reaches plat->rx_queues_to_use, q_node retains an acquired reference count. If the error check passes (queue == plat->rx_queues_to_use), execution proceeds directly to the TX queue loop, where of_get_next_child() immediately overwrites q_node with the first TX child, permanently leaking the retained RX child device node reference. Switch both loops to for_each_child_of_node_scoped() so child node references are automatically dropped upon loop exit or early break, and remove the now-unnecessary function-scoped q_node variable and its manual of_node_put() at the exit label. Signed-off-by: Md Rabbani Link: https://patch.msgid.link/20260821055718.57-1-rabbanyhmm@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c index dc5f951a311d..6128ed1bd521 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c @@ -130,7 +130,6 @@ static struct stmmac_axi *stmmac_axi_setup(struct platform_device *pdev) static int stmmac_mtl_setup(struct platform_device *pdev, struct plat_stmmacenet_data *plat) { - struct device_node *q_node; struct device_node *rx_node; struct device_node *tx_node; u8 queue = 0; @@ -169,7 +168,7 @@ static int stmmac_mtl_setup(struct platform_device *pdev, plat->rx_sched_algorithm = MTL_RX_ALGORITHM_SP; /* Processing individual RX queue config */ - for_each_child_of_node(rx_node, q_node) { + for_each_child_of_node_scoped(rx_node, q_node) { if (queue >= plat->rx_queues_to_use) break; @@ -227,7 +226,7 @@ static int stmmac_mtl_setup(struct platform_device *pdev, queue = 0; /* Processing individual TX queue config */ - for_each_child_of_node(tx_node, q_node) { + for_each_child_of_node_scoped(tx_node, q_node) { if (queue >= plat->tx_queues_to_use) break; @@ -276,7 +275,6 @@ static int stmmac_mtl_setup(struct platform_device *pdev, out: of_node_put(rx_node); of_node_put(tx_node); - of_node_put(q_node); return ret; } From 719296c4aa8213d4ac8002e77d5956d436bc98d0 Mon Sep 17 00:00:00 2001 From: Hidayath Khan Date: Thu, 20 Aug 2026 16:47:29 +0200 Subject: [PATCH 0943/1328] net/smc: fix socket refcount leak in smc_switch_conns() smc_switch_conns() takes a reference on the SMC socket before dropping lgr->conns_lock, so the connection stays alive while the CDC slot is fetched: sock_hold(&smc->sk); read_unlock_bh(&lgr->conns_lock); /* pre-fetch buffer outside of send_lock, might sleep */ rc = smc_cdc_get_free_slot(conn, to_lnk, &wr_buf, NULL, &pend); if (rc) goto err_out; The err_out label only drops the wr_tx link reference, so this early exit returns without the matching sock_put(). The second error exit is not affected, because sock_put() has already run by then. A leaked sk_refcnt means the smc_sock is never destroyed. Its send and receive buffers stay allocated, and for a user socket the reference held on the network namespace is never released, so the netns can no longer be torn down. smc_cdc_get_free_slot() fails when the target link goes down or when the connection has been killed while the switch is in progress. Both are reachable during the link failover this function implements, so the leak is triggered by the same hardware events that make smc_switch_conns() run in the first place. Restructure so there is a single sock_put() covering both outcomes, instead of adding a second one to the error path. Fixes: 95f7f3e7dc6b ("net/smc: improved fix wait on already cleared link") Cc: stable@vger.kernel.org Reviewed-by: Mahanta Jambigi Reviewed-by: Breno Leitao Signed-off-by: Hidayath Khan Link: https://patch.msgid.link/20260820144729.1019399-1-hidayath@linux.ibm.com Signed-off-by: Jakub Kicinski --- net/smc/smc_core.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/net/smc/smc_core.c b/net/smc/smc_core.c index 181647982490..04aedd957543 100644 --- a/net/smc/smc_core.c +++ b/net/smc/smc_core.c @@ -1148,13 +1148,13 @@ struct smc_link *smc_switch_conns(struct smc_link_group *lgr, read_unlock_bh(&lgr->conns_lock); /* pre-fetch buffer outside of send_lock, might sleep */ rc = smc_cdc_get_free_slot(conn, to_lnk, &wr_buf, NULL, &pend); - if (rc) - goto err_out; - /* avoid race with smcr_tx_sndbuf_nonempty() */ - spin_lock_bh(&conn->send_lock); - smc_switch_link_and_count(conn, to_lnk); - rc = smc_switch_cursor(smc, pend, wr_buf); - spin_unlock_bh(&conn->send_lock); + if (!rc) { + /* avoid race with smcr_tx_sndbuf_nonempty() */ + spin_lock_bh(&conn->send_lock); + smc_switch_link_and_count(conn, to_lnk); + rc = smc_switch_cursor(smc, pend, wr_buf); + spin_unlock_bh(&conn->send_lock); + } sock_put(&smc->sk); if (rc) goto err_out; From db51a8658c11a82432b64999519a269c3aabb447 Mon Sep 17 00:00:00 2001 From: Hidayath Khan Date: Thu, 20 Aug 2026 09:46:41 +0200 Subject: [PATCH 0944/1328] net/smc: stop killed, freed and out_of_sync sharing a byte The three connection state flags are single-bit bitfields, so they occupy one byte of struct smc_connection and every store to one is a read-modify-write of the other two: u8 killed : 1; u8 freed : 1; u8 out_of_sync : 1; They are not written under a common lock. smc_cdc_msg_validate() sets out_of_sync from the receive tasklet, while smc_conn_kill() sets killed from process context under lock_sock(), and the receive path does not defer to the backlog when the socket is owned -- smc_cdc_msg_recv() takes only bh_lock_sock(). Give each flag its own byte so a store no longer touches its neighbours. All readers test them as booleans and are unchanged. struct smc_connection grows by two bytes. Fixes: b286a0651e44 ("net/smc: handle incoming CDC validation message") Cc: stable@vger.kernel.org Reviewed-by: Mahanta Jambigi Signed-off-by: Hidayath Khan Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260820074642.966856-2-hidayath@linux.ibm.com Signed-off-by: Jakub Kicinski --- net/smc/smc.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/net/smc/smc.h b/net/smc/smc.h index 52145df83f6e..427b6d63b993 100644 --- a/net/smc/smc.h +++ b/net/smc/smc.h @@ -277,9 +277,9 @@ struct smc_connection { * 0 for SMC-R, 32 for SMC-D */ u64 peer_token; /* SMC-D token of peer */ - u8 killed : 1; /* abnormal termination */ - u8 freed : 1; /* normal termination */ - u8 out_of_sync : 1; /* out of sync with peer */ + u8 killed; /* abnormal termination */ + u8 freed; /* normal termination */ + u8 out_of_sync; /* out of sync with peer */ }; struct smc_sock { /* smc sock container */ From c924884743e948e25625b7fbf3ee2a9325a204a7 Mon Sep 17 00:00:00 2001 From: Hidayath Khan Date: Thu, 20 Aug 2026 09:46:42 +0200 Subject: [PATCH 0945/1328] net/smc: fix use-after-free in smc_rx_pipe_buf_release() smc_rx_splice() hands RMB pages to a pipe and takes a socket reference per entry so the smc_sock stays alive until the reader finishes. The connection does not: a concurrent close runs smc_conn_free(), which releases the receive buffer back to the link group pool. smc_rx_pipe_buf_release() tests sk_state before taking the socket lock. The state can change between the test and the lock, and smc_rx_update_cons() then dereferences conn->rmb_desc and walks conn->lgr, which smc_conn_free() has already released. On the is_reg_err path smcr_buf_unuse() frees the descriptor outright, so this is a use-after-free. Take the socket lock first and test conn->freed instead. smc_conn_free() sets that flag before releasing anything, and every caller holds the socket lock. The two paths exclude each other: either the pipe release runs first with everything valid, or it sees the flag and skips the update. Fixes: 9014db202cb7 ("smc: add support for splice()") Cc: stable@vger.kernel.org Reviewed-by: Mahanta Jambigi Signed-off-by: Hidayath Khan Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260820074642.966856-3-hidayath@linux.ibm.com Signed-off-by: Jakub Kicinski --- net/smc/smc_rx.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/net/smc/smc_rx.c b/net/smc/smc_rx.c index 5c9e4d8b57de..197fddc6271f 100644 --- a/net/smc/smc_rx.c +++ b/net/smc/smc_rx.c @@ -115,16 +115,15 @@ static void smc_rx_pipe_buf_release(struct pipe_inode_info *pipe, struct pipe_buffer *buf) { struct smc_spd_priv *priv = (struct smc_spd_priv *)buf->private; + struct smc_connection *conn = &priv->smc->conn; struct smc_sock *smc = priv->smc; - struct smc_connection *conn; struct sock *sk = &smc->sk; - if (sk->sk_state == SMC_CLOSED || - sk->sk_state == SMC_PEERFINCLOSEWAIT || - sk->sk_state == SMC_APPFINCLOSEWAIT) - goto out; - conn = &smc->conn; lock_sock(sk); + if (conn->freed) { + release_sock(sk); + goto out; + } smc_rx_update_cons(smc, priv->len); release_sock(sk); if (atomic_sub_and_test(priv->len, &conn->splice_pending)) From 9edf8d4876e8a2471e66f54d36208eaff2858392 Mon Sep 17 00:00:00 2001 From: Naveen Mamindlapalli Date: Fri, 21 Aug 2026 11:24:45 +0530 Subject: [PATCH 0946/1328] octeontx2-af: Fix TL3/TL2 link config ENA clearing Clear and restore the ENA bit for each TL3/TL2 link entry during SMQ flush instead of repeatedly using the same link index. Fixes: 019aba04f08c ("octeontx2-af: Modify SMQ flush sequence to drop packets") Signed-off-by: Nitin Shetty J Signed-off-by: Naveen Mamindlapalli Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260821055445.2517568-1-nshettyj@marvell.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c index b81c47ea023b..153eb57bad06 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c @@ -2491,8 +2491,8 @@ static int nix_smq_flush(struct rvu *rvu, int blkaddr, int pf = rvu_get_pf(rvu->pdev, pcifunc); u8 cgx_id = 0, lmac_id = 0; u16 tl2_tl3_link_schq; - u8 link, link_level; u64 cfg, bmap = 0; + u8 link_level; if (!is_rvu_otx2(rvu)) { /* Skip SMQ flush if pkt count is zero */ @@ -2524,7 +2524,6 @@ static int nix_smq_flush(struct rvu *rvu, int blkaddr, link_level = rvu_read64(rvu, blkaddr, NIX_AF_PSE_CHANNEL_LEVEL) & 0x01 ? NIX_TXSCH_LVL_TL3 : NIX_TXSCH_LVL_TL2; tl2_tl3_link_schq = smq_flush_ctx->smq_tree_ctx[link_level].schq; - link = smq_flush_ctx->smq_tree_ctx[NIX_TXSCH_LVL_TL1].schq; /* SMQ set enqueue xoff */ cfg = rvu_read64(rvu, blkaddr, NIX_AF_SMQX_CFG(smq)); @@ -2534,13 +2533,13 @@ static int nix_smq_flush(struct rvu *rvu, int blkaddr, /* Clear all NIX_AF_TL3_TL2_LINK_CFG[ENA] for the TL3/TL2 queue */ for (i = 0; i < (rvu->hw->cgx_links + rvu->hw->lbk_links); i++) { cfg = rvu_read64(rvu, blkaddr, - NIX_AF_TL3_TL2X_LINKX_CFG(tl2_tl3_link_schq, link)); + NIX_AF_TL3_TL2X_LINKX_CFG(tl2_tl3_link_schq, i)); if (!(cfg & BIT_ULL(12))) continue; bmap |= BIT_ULL(i); cfg &= ~BIT_ULL(12); rvu_write64(rvu, blkaddr, - NIX_AF_TL3_TL2X_LINKX_CFG(tl2_tl3_link_schq, link), cfg); + NIX_AF_TL3_TL2X_LINKX_CFG(tl2_tl3_link_schq, i), cfg); } /* Do SMQ flush and set enqueue xoff */ @@ -2561,10 +2560,10 @@ static int nix_smq_flush(struct rvu *rvu, int blkaddr, if (!(bmap & BIT_ULL(i))) continue; cfg = rvu_read64(rvu, blkaddr, - NIX_AF_TL3_TL2X_LINKX_CFG(tl2_tl3_link_schq, link)); + NIX_AF_TL3_TL2X_LINKX_CFG(tl2_tl3_link_schq, i)); cfg |= BIT_ULL(12); rvu_write64(rvu, blkaddr, - NIX_AF_TL3_TL2X_LINKX_CFG(tl2_tl3_link_schq, link), cfg); + NIX_AF_TL3_TL2X_LINKX_CFG(tl2_tl3_link_schq, i), cfg); } /* clear XOFF on TL2s */ From 9e94d8e14ae45e3fda8c0919f745db3ed3715433 Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Fri, 21 Aug 2026 14:41:39 +0800 Subject: [PATCH 0947/1328] net: enetc: restore RX ring congestion mode after ring reconfiguration The RX ring congestion mode (CM) is only configured in the phylink .mac_link_up() callback enetc_pl_mac_link_up(), which sets the ENETC_RBMR_CM bit when tx_pause is enabled. This callback runs only when the link status changes. However, enetc_reconfigure() tears down and re-creates the RX BD rings at runtime without any link status change, for example when attaching or detaching an XDP program, or when enabling/disabling PTP RX hardware timestamping. The rings are rebuilt from a cleared RBMR, so the CM bit is lost. Since the link status does not change, enetc_pl_mac_link_up() is not called again and the CM bit is never restored. As a result, the ENETC MAC can no longer generate PAUSE frames on ingress congestion, and flow control stops working after such a reconfiguration. Track the desired CM state in a software flag ENETC_RXBDR_CM. Set or clear this flag in enetc_pl_mac_link_up() according to tx_pause. When the RX BD rings are (re)enabled, enetc_enable_rxbdr() consults this flag and restores the ENETC_RBMR_CM bit accordingly, so flow control survives ring reconfiguration even when the link status does not change. RBMR is now written as a whole word from enetc_enable_rxbdr() rather than by read-modify-write from several call sites. Serialize the remaining RBMR read-modify-write paths, the congestion mode update and the RX VLAN offload update, with the new si->gen_lock so they cannot race each other. Fixes: 5093406c784f ("net: enetc: implement ring reconfiguration procedure for PTP RX timestamping") Signed-off-by: Wei Fang Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260821064140.1315611-2-wei.fang@oss.nxp.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/freescale/enetc/enetc.c | 72 +++++++++++++++---- drivers/net/ethernet/freescale/enetc/enetc.h | 9 +++ .../net/ethernet/freescale/enetc/enetc_pf.c | 14 +--- 3 files changed, 68 insertions(+), 27 deletions(-) diff --git a/drivers/net/ethernet/freescale/enetc/enetc.c b/drivers/net/ethernet/freescale/enetc/enetc.c index 8e3f345dd9aa..80f0082f6c63 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc.c +++ b/drivers/net/ethernet/freescale/enetc/enetc.c @@ -81,6 +81,33 @@ void enetc_reset_mac_addr_filter(struct enetc_mac_filter *filter) } EXPORT_SYMBOL_GPL(enetc_reset_mac_addr_filter); +void enetc_set_congestion_mode(struct enetc_ndev_priv *priv, bool enable) +{ + struct enetc_si *si = priv->si; + struct enetc_hw *hw = &si->hw; + + spin_lock(&si->gen_lock); + + if (enable) + set_bit(ENETC_RXBDR_CM, &priv->flags); + else + clear_bit(ENETC_RXBDR_CM, &priv->flags); + + for (int i = 0; i < priv->num_rx_rings; i++) { + u32 old_rbmr = enetc_rxbdr_rd(hw, i, ENETC_RBMR); + u32 rbmr; + + rbmr = u32_replace_bits(old_rbmr, enable, ENETC_RBMR_CM); + if (rbmr == old_rbmr) + continue; + + enetc_rxbdr_wr(hw, i, ENETC_RBMR, rbmr); + } + + spin_unlock(&si->gen_lock); +} +EXPORT_SYMBOL_GPL(enetc_set_congestion_mode); + static int enetc_num_stack_tx_queues(struct enetc_ndev_priv *priv) { int num_tx_rings = priv->num_tx_rings; @@ -2632,7 +2659,6 @@ static void enetc_setup_rxbdr(struct enetc_hw *hw, struct enetc_bdr *rx_ring, bool extended) { int idx = rx_ring->index; - u32 rbmr = 0; enetc_rxbdr_wr(hw, idx, ENETC_RBBAR0, lower_32_bits(rx_ring->bd_dma_base)); @@ -2660,12 +2686,6 @@ static void enetc_setup_rxbdr(struct enetc_hw *hw, struct enetc_bdr *rx_ring, enetc_rxbdr_wr(hw, idx, ENETC_RBICR0, ENETC_RBICR0_ICEN | 0x1); rx_ring->ext_en = extended; - if (rx_ring->ext_en) - rbmr |= ENETC_RBMR_BDS; - - if (rx_ring->ndev->features & NETIF_F_HW_VLAN_CTAG_RX) - rbmr |= ENETC_RBMR_VTE; - rx_ring->rcir = hw->reg + ENETC_BDR(RX, idx, ENETC_RBCIR); rx_ring->idr = hw->reg + ENETC_SIRXIDR; @@ -2676,8 +2696,6 @@ static void enetc_setup_rxbdr(struct enetc_hw *hw, struct enetc_bdr *rx_ring, enetc_lock_mdio(); enetc_refill_rx_ring(rx_ring, enetc_bd_unused(rx_ring)); enetc_unlock_mdio(); - - enetc_rxbdr_wr(hw, idx, ENETC_RBMR, rbmr); } static void enetc_setup_bdrs(struct enetc_ndev_priv *priv, bool extended) @@ -2704,21 +2722,34 @@ static void enetc_enable_txbdr(struct enetc_hw *hw, struct enetc_bdr *tx_ring) static void enetc_enable_rxbdr(struct enetc_hw *hw, struct enetc_bdr *rx_ring) { + struct enetc_ndev_priv *priv = netdev_priv(rx_ring->ndev); int idx = rx_ring->index; - u32 rbmr; + u32 rbmr = ENETC_RBMR_EN; + + if (rx_ring->ext_en) + rbmr |= ENETC_RBMR_BDS; + + if (rx_ring->ndev->features & NETIF_F_HW_VLAN_CTAG_RX) + rbmr |= ENETC_RBMR_VTE; + + if (test_bit(ENETC_RXBDR_CM, &priv->flags)) + rbmr |= ENETC_RBMR_CM; - rbmr = enetc_rxbdr_rd(hw, idx, ENETC_RBMR); - rbmr |= ENETC_RBMR_EN; enetc_rxbdr_wr(hw, idx, ENETC_RBMR, rbmr); } static void enetc_enable_rx_bdrs(struct enetc_ndev_priv *priv) { - struct enetc_hw *hw = &priv->si->hw; + struct enetc_si *si = priv->si; + struct enetc_hw *hw = &si->hw; int i; + spin_lock(&si->gen_lock); + for (i = 0; i < priv->num_rx_rings; i++) enetc_enable_rxbdr(hw, priv->rx_ring[i]); + + spin_unlock(&si->gen_lock); } static void enetc_enable_tx_bdrs(struct enetc_ndev_priv *priv) @@ -2748,11 +2779,16 @@ static void enetc_disable_txbdr(struct enetc_hw *hw, struct enetc_bdr *rx_ring) static void enetc_disable_rx_bdrs(struct enetc_ndev_priv *priv) { - struct enetc_hw *hw = &priv->si->hw; + struct enetc_si *si = priv->si; + struct enetc_hw *hw = &si->hw; int i; + spin_lock(&si->gen_lock); + for (i = 0; i < priv->num_rx_rings; i++) enetc_disable_rxbdr(hw, priv->rx_ring[i]); + + spin_unlock(&si->gen_lock); } static void enetc_disable_tx_bdrs(struct enetc_ndev_priv *priv) @@ -3344,11 +3380,16 @@ EXPORT_SYMBOL_GPL(enetc_get_stats); static void enetc_enable_rxvlan(struct net_device *ndev, bool en) { struct enetc_ndev_priv *priv = netdev_priv(ndev); - struct enetc_hw *hw = &priv->si->hw; + struct enetc_si *si = priv->si; + struct enetc_hw *hw = &si->hw; int i; + spin_lock(&si->gen_lock); + for (i = 0; i < priv->num_rx_rings; i++) enetc_bdr_enable_rxvlan(hw, i, en); + + spin_unlock(&si->gen_lock); } static void enetc_enable_txvlan(struct net_device *ndev, bool en) @@ -3679,6 +3720,7 @@ int enetc_pci_probe(struct pci_dev *pdev, const char *name, int sizeof_priv) si = PTR_ALIGN(p, ENETC_SI_ALIGN); si->pad = (char *)si - (char *)p; + spin_lock_init(&si->gen_lock); pci_set_drvdata(pdev, si); si->pdev = pdev; diff --git a/drivers/net/ethernet/freescale/enetc/enetc.h b/drivers/net/ethernet/freescale/enetc/enetc.h index 8839cfb49bcf..d1e9d9130057 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc.h +++ b/drivers/net/ethernet/freescale/enetc/enetc.h @@ -309,6 +309,13 @@ struct enetc_si { struct net_device *ndev; /* back ref. */ + /* General-purpose lock serializing updates that must not race, + * e.g. read-modify-write of shared hardware registers and of + * selected priv->flags bits between the phylink link callbacks + * and the ring (re)configuration path. + */ + spinlock_t gen_lock; + union { struct enetc_cbdr cbd_ring; /* Only ENETC 1.0 */ struct ntmp_user ntmp_user; /* ENETC 4.1 and later */ @@ -417,6 +424,7 @@ enum enetc_active_offloads { enum enetc_flags_bit { ENETC_TX_ONESTEP_TSTAMP_IN_PROGRESS = 0, ENETC_TX_DOWN, + ENETC_RXBDR_CM, }; /* interrupt coalescing modes */ @@ -505,6 +513,7 @@ int enetc_get_driver_data(struct enetc_si *si); void enetc_add_mac_addr_ht_filter(struct enetc_mac_filter *filter, const unsigned char *addr); void enetc_reset_mac_addr_filter(struct enetc_mac_filter *filter); +void enetc_set_congestion_mode(struct enetc_ndev_priv *priv, bool enable); int enetc_open(struct net_device *ndev); int enetc_close(struct net_device *ndev); diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf.c b/drivers/net/ethernet/freescale/enetc/enetc_pf.c index a509929f89f2..55c07c528f22 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.c @@ -556,8 +556,7 @@ static void enetc_pl_mac_link_up(struct phylink_config *config, struct enetc_hw *hw = &pf->si->hw; struct enetc_si *si = pf->si; struct enetc_ndev_priv *priv; - u32 rbmr, cmd_cfg; - int idx; + u32 cmd_cfg; priv = netdev_priv(pf->si->ndev); @@ -569,16 +568,7 @@ static void enetc_pl_mac_link_up(struct phylink_config *config, enetc_force_rgmii_mac(si, speed, duplex); /* Flow control */ - for (idx = 0; idx < priv->num_rx_rings; idx++) { - rbmr = enetc_rxbdr_rd(hw, idx, ENETC_RBMR); - - if (tx_pause) - rbmr |= ENETC_RBMR_CM; - else - rbmr &= ~ENETC_RBMR_CM; - - enetc_rxbdr_wr(hw, idx, ENETC_RBMR, rbmr); - } + enetc_set_congestion_mode(priv, tx_pause); if (tx_pause) { /* When the port first enters congestion, send a PAUSE request From fa1a8457b8581725ae237efefb2358eddd7e05c9 Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Fri, 21 Aug 2026 14:41:40 +0800 Subject: [PATCH 0948/1328] net: enetc: restore RX ring congestion mode for ENETC v4 ENETC v4 has the same problem as ENETC v1: the RX BD ring congestion mode (CM) is only configured in the phylink .mac_link_up() callback, so it is cleared when enetc_reconfigure() rebuilds the RX BD rings at runtime (for example when enabling or disabling PTP RX hardware timestamping) without a link status change, and it is never restored. As a result, the MAC can no longer generate PAUSE frames on ingress congestion and flow control stops working. Fix it in the same way as ENETC v1. Track the desired CM state in the software flag ENETC_RXBDR_CM. Route enetc4_set_tx_pause() through the shared helper enetc_set_congestion_mode(), which sets or clears the flag according to tx_pause and updates the ENETC_RBMR_CM bit under si->gen_lock. When the RX BD rings are (re)enabled, enetc_enable_rxbdr() consults this flag and restores the CM bit accordingly, so flow control survives ring reconfiguration even when the link status does not change. Fixes: f5b9a1cde0a2 ("net: enetc: add PTP synchronization support for ENETC v4") Signed-off-by: Wei Fang Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260821064140.1315611-3-wei.fang@oss.nxp.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/freescale/enetc/enetc4_pf.c | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c index fcfbabb29d22..9bb1004548ab 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c @@ -718,22 +718,14 @@ static void enetc4_set_rx_pause(struct enetc_pf *pf, bool rx_pause) enetc_port_mac_wr(si, ENETC4_PM_CMD_CFG(0), val); } -static void enetc4_set_tx_pause(struct enetc_pf *pf, int num_rxbdr, bool tx_pause) +static void enetc4_set_tx_pause(struct enetc_pf *pf, bool tx_pause) { + struct enetc_ndev_priv *priv = netdev_priv(pf->si->ndev); u32 pause_off_thresh = 0, pause_on_thresh = 0; u32 init_quanta = 0, refresh_quanta = 0; struct enetc_hw *hw = &pf->si->hw; - u32 rbmr, old_rbmr; - int i; - for (i = 0; i < num_rxbdr; i++) { - old_rbmr = enetc_rxbdr_rd(hw, i, ENETC_RBMR); - rbmr = u32_replace_bits(old_rbmr, tx_pause ? 1 : 0, ENETC_RBMR_CM); - if (rbmr == old_rbmr) - continue; - - enetc_rxbdr_wr(hw, i, ENETC_RBMR, rbmr); - } + enetc_set_congestion_mode(priv, tx_pause); if (tx_pause) { /* When the port first enters congestion, send a PAUSE request @@ -898,7 +890,7 @@ static void enetc4_pl_mac_link_up(struct phylink_config *config, tx_pause = false; } - enetc4_set_tx_pause(pf, priv->num_rx_rings, tx_pause); + enetc4_set_tx_pause(pf, tx_pause); enetc4_set_rx_pause(pf, rx_pause); enetc4_mac_tx_enable(pf); enetc4_mac_rx_enable(pf); From 5c07193ebe4718f71752c11d30cf389fa7b4c870 Mon Sep 17 00:00:00 2001 From: Manush Prajwal Date: Fri, 21 Aug 2026 15:37:14 +0530 Subject: [PATCH 0949/1328] net: ethernet: renesas: rswitch: fix device_node refcount leak in rswitch_get_port_node() On an of_property_read_u32() failure, rswitch_get_port_node() set port to NULL and jumped to the out label before releasing the reference the for_each_available_child_of_node() iterator was holding on it. Once port was overwritten with NULL, that reference could never be released since out: only put "ports", the parent node. Rework the function around for_each_available_child_of_node_scoped() instead of adding a manual of_node_put(), so the iterator's reference is dropped automatically on every exit path. Since port is the function's return value, take an explicit reference with of_node_get() on the match before breaking out of the loop. Signed-off-by: Manush Prajwal Link: https://patch.msgid.link/6a882352.ee10049a.267d65.7a31@mx.google.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/renesas/rswitch_main.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/net/ethernet/renesas/rswitch_main.c b/drivers/net/ethernet/renesas/rswitch_main.c index 6fe964816322..755232994fcc 100644 --- a/drivers/net/ethernet/renesas/rswitch_main.c +++ b/drivers/net/ethernet/renesas/rswitch_main.c @@ -1303,7 +1303,8 @@ static int rswitch_etha_mii_write_c22(struct mii_bus *bus, int phyad, /* Call of_node_put(port) after done */ static struct device_node *rswitch_get_port_node(struct rswitch_device *rdev) { - struct device_node *ports, *port; + struct device_node *port = NULL; + struct device_node *ports; int err = 0; u32 index; @@ -1312,17 +1313,16 @@ static struct device_node *rswitch_get_port_node(struct rswitch_device *rdev) if (!ports) return NULL; - for_each_available_child_of_node(ports, port) { - err = of_property_read_u32(port, "reg", &index); - if (err < 0) { - port = NULL; - goto out; - } - if (index == rdev->etha->index) + for_each_available_child_of_node_scoped(ports, child) { + err = of_property_read_u32(child, "reg", &index); + if (err < 0) break; + if (index == rdev->etha->index) { + port = of_node_get(child); + break; + } } -out: of_node_put(ports); return port; From 5b483f7791b079bb97d411f1066652ff659207ff Mon Sep 17 00:00:00 2001 From: Victor Nogueira Date: Fri, 21 Aug 2026 13:40:31 -0300 Subject: [PATCH 0950/1328] net/sched: act_ife: Only operate on Ethernet frames act_ife encapsulates/decapsulates the original Ethernet header and uses skb->dev->hard_header_len as the length of that header. That is only correct for Ethernet devices: on a device where hard_header_len does not match the L2 header that was actually pulled (PPP reports PPP_HDRLEN while nothing is stripped on ingress), the ingress skb_push()/skb_pull() use the wrong length and can hit skb_under_panic when headroom is tight. IFE is Ethernet-only by design - it builds an outer ethhdr, rewrites h_source/h_dest/h_proto, and calls eth_type_trans() on decode - so instead of trying to make the offsets work for arbitrary link types, simply drop packets that do not carry an Ethernet header. Checking skb->dev->type alone is not enough. We have to cater for a corner case where mirred can redirect an skb from a non-Ethernet device to an Ethernet one, and skb->dev then says nothing about the framing the skb actually has: an skb redirected from ppp0 reaches the target's ingress hook with mac_len 0 and no Ethernet header at all. So at ingress also require mac_len to be ETH_HLEN. On egress mac_len is not maintained, so the device type is all we have; a bogus redirect there yields a malformed frame rather than an out-of-bounds push, and it would be malformed with or without IFE. That corner case is not theoretical - redirecting from ppp0 into a veth that has an ife encode action on its ingress hook panics without this patch: skbuff: skb_under_panic: len:98 put:14 head:ffff88800e410000 data:ffff88800e40fff5 tail:0x57 end:0x640 dev:veth3 kernel BUG at net/core/skbuff.c:214! Call Trace: skb_push (net/core/skbuff.c:224 net/core/skbuff.c:2657) tcf_ife_act (net/sched/act_ife.c:829 net/sched/act_ife.c:874) tc_run (net/core/dev.c:4463) netif_receive_skb (net/core/dev.c:6463 net/core/dev.c:6522) tcf_mirred_to_dev (net/sched/act_mirred.c:248 net/sched/act_mirred.c:328) tcf_mirred_act (net/sched/act_mirred.c:489) tc_run (net/core/dev.c:4463) process_backlog (net/core/dev.c:6728) With Ethernet framing guaranteed, use ETH_HLEN instead of hard_header_len. Fixes: 295a6e06d21e ("net/sched: act_ife: Change to use ife module") Reported-by: vega@nebusec.ai Acked-by: Jamal Hadi Salim Signed-off-by: Victor Nogueira Link: https://patch.msgid.link/20260821164031.32824-1-victor@mojatatu.com Signed-off-by: Jakub Kicinski --- net/ife/ife.c | 14 +++++++------- net/sched/act_ife.c | 32 ++++++++++++++++++++++++++++---- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/net/ife/ife.c b/net/ife/ife.c index 7a75947a31e3..2ddf725d3389 100644 --- a/net/ife/ife.c +++ b/net/ife/ife.c @@ -37,7 +37,7 @@ void *ife_encode(struct sk_buff *skb, u16 metalen) * where ORIGDATA = original ethernet header ... */ int hdrm = metalen + IFE_METAHDRLEN; - int total_push = hdrm + skb->dev->hard_header_len; + int total_push = hdrm + ETH_HLEN; struct ifeheadr *ifehdr; struct ethhdr *iethh; /* inner ether header */ int skboff = 0; @@ -50,9 +50,9 @@ void *ife_encode(struct sk_buff *skb, u16 metalen) iethh = (struct ethhdr *) skb->data; __skb_push(skb, total_push); - memcpy(skb->data, iethh, skb->dev->hard_header_len); + memcpy(skb->data, iethh, ETH_HLEN); skb_reset_mac_header(skb); - skboff += skb->dev->hard_header_len; + skboff += ETH_HLEN; /* total metadata length */ ifehdr = (struct ifeheadr *) (skb->data + skboff); @@ -69,12 +69,12 @@ void *ife_decode(struct sk_buff *skb, u16 *metalen) int total_pull; u16 ifehdrln; - if (!pskb_may_pull(skb, skb->dev->hard_header_len + IFE_METAHDRLEN)) + if (!pskb_may_pull(skb, ETH_HLEN + IFE_METAHDRLEN)) return NULL; - ifehdr = (struct ifeheadr *) (skb->data + skb->dev->hard_header_len); + ifehdr = (struct ifeheadr *)(skb->data + ETH_HLEN); ifehdrln = ntohs(ifehdr->metalen); - total_pull = skb->dev->hard_header_len + ifehdrln; + total_pull = ETH_HLEN + ifehdrln; if (unlikely(ifehdrln < 2)) return NULL; @@ -82,7 +82,7 @@ void *ife_decode(struct sk_buff *skb, u16 *metalen) if (unlikely(!pskb_may_pull(skb, total_pull + ETH_HLEN))) return NULL; - ifehdr = (struct ifeheadr *)(skb->data + skb->dev->hard_header_len); + ifehdr = (struct ifeheadr *)(skb->data + ETH_HLEN); skb_set_mac_header(skb, total_pull); __skb_pull(skb, total_pull); *metalen = ifehdrln - IFE_METAHDRLEN; diff --git a/net/sched/act_ife.c b/net/sched/act_ife.c index ff2b16e35b9b..9cea71fc1db3 100644 --- a/net/sched/act_ife.c +++ b/net/sched/act_ife.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -723,7 +724,7 @@ static int tcf_ife_decode(struct sk_buff *skb, const struct tc_action *a, tcf_lastuse_update(&ife->tcf_tm); if (skb_at_tc_ingress(skb)) - skb_push(skb, skb->dev->hard_header_len); + skb_push(skb, ETH_HLEN); tlv_data = ife_decode(skb, &metalen); if (unlikely(!tlv_data)) { @@ -795,7 +796,7 @@ static int tcf_ife_encode(struct sk_buff *skb, const struct tc_action *a, where ORIGDATA = original ethernet header ... */ u16 metalen = ife_get_sz(skb, p); - int hdrm = metalen + skb->dev->hard_header_len + IFE_METAHDRLEN; + int hdrm = metalen + ETH_HLEN + IFE_METAHDRLEN; unsigned int skboff = 0; int new_len = skb->len + hdrm; bool exceed_mtu = false; @@ -826,7 +827,7 @@ static int tcf_ife_encode(struct sk_buff *skb, const struct tc_action *a, } if (skb_at_tc_ingress(skb)) - skb_push(skb, skb->dev->hard_header_len); + skb_push(skb, ETH_HLEN); ife_meta = ife_encode(skb, metalen); if (!ife_meta) @@ -856,11 +857,27 @@ static int tcf_ife_encode(struct sk_buff *skb, const struct tc_action *a, oethh->h_proto = htons(p->eth_type); if (skb_at_tc_ingress(skb)) - skb_pull(skb, skb->dev->hard_header_len); + skb_pull(skb, ETH_HLEN); return action; } +/* IFE encapsulates the original Ethernet header and, on decode, expects to + * find one, so it can only ever work on skbs that carry one. Loopback carries + * Ethernet header as well, so it qualifies here. + * At ingress, also verify that the L2 header about to be pushed back really + * is an Ethernet header because the skb could've been redirected with mirred + * from a non-Ethernet device. + */ +static bool tcf_ife_is_eth_skb(const struct sk_buff *skb) +{ + if (skb->dev->type != ARPHRD_ETHER && + skb->dev->type != ARPHRD_LOOPBACK) + return false; + + return !skb_at_tc_ingress(skb) || skb->mac_len == ETH_HLEN; +} + TC_INDIRECT_SCOPE int tcf_ife_act(struct sk_buff *skb, const struct tc_action *a, struct tcf_result *res) @@ -869,6 +886,13 @@ TC_INDIRECT_SCOPE int tcf_ife_act(struct sk_buff *skb, struct tcf_ife_params *p; int ret; + if (unlikely(!tcf_ife_is_eth_skb(skb))) { + bstats_update(this_cpu_ptr(ife->common.cpu_bstats), skb); + tcf_lastuse_update(&ife->tcf_tm); + qstats_cpu_drop_inc(ife->common.cpu_qstats); + return TC_ACT_SHOT; + } + p = rcu_dereference_bh(ife->params); if (p->flags & IFE_ENCODE) { ret = tcf_ife_encode(skb, a, res, p); From 2c7493f980140a5c40eb4f98f97c557193a2c330 Mon Sep 17 00:00:00 2001 From: Long Li Date: Fri, 21 Aug 2026 11:37:36 -0700 Subject: [PATCH 0951/1328] net: mana: Cap MSI-X vectors to the device MSI-X table size mana_gd_query_max_resources() sizes gc->num_msix_usable from resp.max_msix and the CPU count, but never from the device MSI-X table. On a 1792 vCPU M-series VM that yields 1793 while the table has 1024 entries, and mana_gd_setup_remaining_irqs() then walks indices 1..1792, running off the end of the region mapped by msix_map_region(): BUG: unable to handle page fault for address: ff8e347f8b99800c RIP: 0010:msix_prepare_msi_desc+0x7a/0x90 RAX: 0000000000004000 RBX: ff4330cb164ea780 RCX: ff8e347f8b998000 Call Trace: __msi_domain_alloc_irqs+0x13a/0x440 msi_domain_alloc_irq_at+0x149/0x1b0 mana_gd_setup+0x351/0x890 mana_gd_probe+0x274/0x390 RAX is index 1024 * PCI_MSIX_ENTRY_SIZE, one entry past the table. msi_insert_desc() does range check the index, but only against the MSI domain hwsize, which matches the table only for devices on an MSI parent domain. With a global PCI/MSI domain hwsize is MSI_XA_DOMAIN_SIZE, so nothing bounds the request. Cap num_msix_usable with pci_msix_vec_count(). Fixes: 755391121038 ("net: mana: Allocate MSI-X vectors dynamically") Signed-off-by: Long Li Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260821183736.733296-1-longli@microsoft.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/microsoft/mana/gdma_main.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/drivers/net/ethernet/microsoft/mana/gdma_main.c b/drivers/net/ethernet/microsoft/mana/gdma_main.c index ed9af314e4ed..f92b2d0bf926 100644 --- a/drivers/net/ethernet/microsoft/mana/gdma_main.c +++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c @@ -182,6 +182,7 @@ static int mana_gd_query_max_resources(struct pci_dev *pdev) struct gdma_query_max_resources_resp resp = {}; struct gdma_general_req req = {}; unsigned int max_num_queues; + unsigned int msix_vec_count; u8 bm_hostmode; u16 num_ports; int err; @@ -218,6 +219,24 @@ static int mana_gd_query_max_resources(struct pci_dev *pdev) gc->num_msix_usable = min(resp.max_msix, num_online_cpus() + 1); } + /* MSI-X vectors are allocated by index into the device MSI-X table, so + * never ask for more than the table holds. It can be smaller than both + * resp.max_msix and the CPU count. + */ + err = pci_msix_vec_count(pdev); + if (err <= 0) { + dev_err(gc->dev, "Failed to query MSI-X table size: %d\n", err); + return err < 0 ? err : -ENOSPC; + } + msix_vec_count = err; + + if (gc->num_msix_usable > msix_vec_count) { + dev_info(gc->dev, + "Limiting MSI-X vectors from %u to table size %u\n", + gc->num_msix_usable, msix_vec_count); + gc->num_msix_usable = msix_vec_count; + } + if (gc->num_msix_usable <= 1) return -ENOSPC; From d4f484661961636eb90d287050959e613795f73a Mon Sep 17 00:00:00 2001 From: Allison Henderson Date: Fri, 21 Aug 2026 22:26:47 -0700 Subject: [PATCH 0952/1328] net/rds: use wq_has_sleeper() in rds_cong_map_updated() rds_cong_map_updated() runs after a peer's congestion map has been rewritten (by rds_tcp_cong_recv() and rds_ib_cong_recv(), or the clear-all in the loopback and IB send-completion paths). It bumps rds_cong_generation and then checks waitqueue_active() on map->m_waitq and on rds_poll_waitq to decide whether anyone needs waking. atomic_inc() carries no ordering and waitqueue_active() is a plain load, so nothing orders the map and generation stores before the wait queue reads. The waiters do the mirror image: rds_cong_wait() adds itself to m_waitq and then tests the port bit, and rds_poll() registers on rds_poll_waitq and then reads the generation. That is the store-buffering pattern described above waitqueue_active() in include/linux/wait.h - the updater can observe an empty wait queue while the waiter still observes the port as congested, and no wake-up is issued. rds_cong_wait() is an interruptible sleep with no timeout, so a sender blocked on a congested port stays blocked until the next congestion update from that peer arrives or a signal is delivered. A poll() waiter misses the map-updated notification the same way. Use wq_has_sleeper(), which is waitqueue_active() preceded by the required full barrier, as rds_tcp_state_change() already does for the same pattern. Fixes: 922cb17a5c81 ("RDS: Congestion-handling code") Signed-off-by: Allison Henderson Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260822052647.88318-1-achender@kernel.org Signed-off-by: Jakub Kicinski --- net/rds/cong.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/rds/cong.c b/net/rds/cong.c index 3133b91f9e69..f7634ce3ffc1 100644 --- a/net/rds/cong.c +++ b/net/rds/cong.c @@ -256,9 +256,9 @@ void rds_cong_map_updated(struct rds_cong_map *map, uint64_t portmask) map, &map->m_addr); rds_stats_inc(s_cong_update_received); atomic_inc(&rds_cong_generation); - if (waitqueue_active(&map->m_waitq)) + if (wq_has_sleeper(&map->m_waitq)) wake_up(&map->m_waitq); - if (waitqueue_active(&rds_poll_waitq)) + if (wq_has_sleeper(&rds_poll_waitq)) wake_up_all(&rds_poll_waitq); if (portmask && !list_empty(&rds_cong_monitor)) { From 80230a18c164a4b5bbc048fe2768b219ac17bc5a Mon Sep 17 00:00:00 2001 From: Alexandra Winter Date: Fri, 21 Aug 2026 14:55:01 +0200 Subject: [PATCH 0953/1328] net/iucv: filter frames in afiucv_hs_rcv() by ingress device afiucv_hs_rcv() selects a socket from iucv_sk_list by matching four 8-byte name fields in the transport header alone. No check is made against the net_device the frame arrived on. This can cause a frame arriving on any netdev to be delivered to an AF_IUCV socket. Three problems follow. First, a frame arriving over HiperSockets can be delivered to a socket bound to the classic z/VM IUCV transport, which has iucv->hs_dev == NULL. iucv_sock_bind() takes the classic path whenever the requested userid matches iucv_userid, even on a guest that also has a HiperSockets device carrying the same identifier. The child socket created by afiucv_hs_callback_syn() for such a match inherits hs_dev = NULL and transport = AF_IUCV_TRANS_HIPER, so the first send() on it returns -ENODEV. The socket delivered to accept() is unusable. Second, a frame arriving on one netdev can be delivered to a socket bound to a different IQD device. Which can lead to - Accept-queue exhaustion (DoS) - Attacker-controlled peer identity in the child socket - Data injection into existing sockets - Fabric noise on the IQD fabric, where bogus replies are sent - killing established connections Third, all AF_IUCV sockets live in init_net, as iucv_sock_alloc() calls sk_alloc(&init_net, ...). But even frames arriving on netdev devices in a namespace can be delivered to an IUCV socket. So a process in an unprivileged user and network namespace holding only the CAP_NET_RAW capability valid within that namespace can send a raw ETH_P_AF_IUCV frame on its own lo device and have it matched against init_net sockets. Fix all three by skipping any socket whose hs_dev does not match the ingress device. A classic z/VM IUCV socket has hs_dev == NULL; the ingress dev is never NULL, so classic sockets are skipped automatically. An unbound HIPER socket also has hs_dev == NULL and is skipped. A bound HIPER socket is only reachable from the exact IQD device it was bound to. Because hs_dev is always a device in init_net (iucv_sock_bind() scans for_each_netdev_rcu(&init_net, ...) exclusively), a frame whose ingress device belongs to another namespace never matches any socket. Note that AF_IUCV over HiperSockets provides no per-connection authentication: no sequence numbers, no TLS, no nonce. The four name fields identifying a connection are exchanged in plaintext on the shared HiperSockets segment (VCHID). Any host on the same HiperSockets segment could spoof any frame type against an existing connection. That is a protocol-level property unchanged by this patch. The fix reduces the attack surface to peers present on the same HiperSockets segment. Fixes: 3881ac441f64 ("af_iucv: add HiperSockets transport") Cc: stable@vger.kernel.org Co-developed-by: Bryam Vargas Signed-off-by: Alexandra Winter Link: https://patch.msgid.link/20260821125501.3718748-1-wintera@linux.ibm.com Signed-off-by: Jakub Kicinski --- net/iucv/af_iucv.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/iucv/af_iucv.c b/net/iucv/af_iucv.c index ea047bab65e7..4e5cc9da6e06 100644 --- a/net/iucv/af_iucv.c +++ b/net/iucv/af_iucv.c @@ -2079,6 +2079,8 @@ static int afiucv_hs_rcv(struct sk_buff *skb, struct net_device *dev, sk = NULL; read_lock(&iucv_sk_list.lock); sk_for_each(sk, &iucv_sk_list.head) { + if (iucv_sk(sk)->hs_dev != dev) + continue; if (trans_hdr->flags == AF_IUCV_FLAG_SYN) { if ((!memcmp(&iucv_sk(sk)->src_name, trans_hdr->destAppName, 8)) && From b09a0503c755b6609fad59a84cc7f05b6843a03c Mon Sep 17 00:00:00 2001 From: Suman Ghosh Date: Fri, 21 Aug 2026 16:25:35 +0530 Subject: [PATCH 0954/1328] octeontx2-pf: fix NULL deref of af_xdp_zc_qidx on rep setup af_xdp_zc_qidx tracks receive queues using AF_XDP zero-copy and is allocated during PF/VF probe. Representors and other non-AF_XDP paths leave the pointer NULL, but several call sites used test_bit() on it unconditionally. Switching to devlink eswitch mode creates representors and runs otx2_init_hw_resources(), which reaches otx2_pool_aq_init() and oopses when dereferencing the NULL bitmap. Add NULL checks before every af_xdp_zc_qidx test_bit() use in the RSS, ethtool, XSK, and pool init paths. Fixes: efabce290151 ("octeontx2-pf: AF_XDP zero copy receive support") Signed-off-by: Suman Ghosh Signed-off-by: Geetha sowjanya Signed-off-by: Ratheesh Kannoth Link: https://patch.msgid.link/20260821105536.2998765-1-rkannoth@marvell.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c | 6 ++++-- drivers/net/ethernet/marvell/octeontx2/nic/otx2_ethtool.c | 3 ++- drivers/net/ethernet/marvell/octeontx2/nic/otx2_xsk.c | 3 ++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c index ca73a94db794..175992188c18 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c +++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c @@ -333,7 +333,8 @@ int otx2_set_rss_table(struct otx2_nic *pfvf, int ctx_id, const u32 *ind_tbl) /* Get memory to put this msg */ for (idx = 0; idx < rss->rss_size; idx++) { /* Ignore the queue if AF_XDP zero copy is enabled */ - if (test_bit(ind_tbl[idx], pfvf->af_xdp_zc_qidx)) + if (pfvf->af_xdp_zc_qidx && + test_bit(ind_tbl[idx], pfvf->af_xdp_zc_qidx)) continue; aq = otx2_mbox_alloc_msg_nix_aq_enq(mbox); @@ -1509,7 +1510,8 @@ int otx2_pool_aq_init(struct otx2_nic *pfvf, u16 pool_id, if (type != AURA_NIX_RQ) return 0; - if (!test_bit(pool_id, pfvf->af_xdp_zc_qidx)) { + if (!pfvf->af_xdp_zc_qidx || + !test_bit(pool_id, pfvf->af_xdp_zc_qidx)) { pp_params.order = get_order(buf_size); pp_params.flags = PP_FLAG_DMA_MAP; pp_params.pool_size = min(OTX2_PAGE_POOL_SZ, numptrs); diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_ethtool.c b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_ethtool.c index a0340f3422bf..9bee1b91eeaa 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_ethtool.c +++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_ethtool.c @@ -939,7 +939,8 @@ static int otx2_get_rxfh(struct net_device *dev, for (idx = 0; idx < rss->rss_size; idx++) { /* Ignore if the rx queue is AF_XDP zero copy enabled */ - if (test_bit(rss->ind_tbl[idx], pfvf->af_xdp_zc_qidx)) + if (pfvf->af_xdp_zc_qidx && + test_bit(rss->ind_tbl[idx], pfvf->af_xdp_zc_qidx)) continue; indir[idx] = rss->ind_tbl[idx]; } diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_xsk.c b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_xsk.c index 7d67b4cbaf71..0e8a6a6486c4 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_xsk.c +++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_xsk.c @@ -193,7 +193,8 @@ int otx2_xsk_wakeup(struct net_device *dev, u32 queue_id, u32 flags) void otx2_attach_xsk_buff(struct otx2_nic *pfvf, struct otx2_snd_queue *sq, int qidx) { - if (test_bit(qidx, pfvf->af_xdp_zc_qidx)) + if (pfvf->af_xdp_zc_qidx && + test_bit(qidx, pfvf->af_xdp_zc_qidx)) sq->xsk_pool = xsk_get_pool_from_qid(pfvf->netdev, qidx); } From 3b11a77f69980932c3924054d66e565c9a135747 Mon Sep 17 00:00:00 2001 From: Sai Krishna Date: Fri, 21 Aug 2026 15:53:37 +0530 Subject: [PATCH 0955/1328] octeontx2-af: fix cn20k mailbox lifetime on repeated rvu_mbox_init() rvu_mbox_init() is called separately for AF-PF mailboxes during probe and for AF-VF mailboxes when SR-IOV is enabled. Each call used to allocate a new ng_rvu object, leaking the first allocation when the pointer was overwritten on the second call. Sharing one ng_rvu across both paths exposed several teardown bugs: the error path freed all cn20k mailbox DMA and kfree()d ng_rvu even when only the failing init type should be unwound, leaving live AF-PF mailbox memory in use after an AF-VF init failure. mutex_init() was also re-run on the AF-VF path while AF-PF mailbox handlers could still hold rvu->mbox_lock. Probe and SR-IOV failure paths did not release cn20k mailbox DMA either, since cleanup only happened in rvu_remove(). Allocate ng_rvu once with devm_kzalloc(), initialize mbox_lock in the same block, unwind only the mailbox memory for the failing init type, and free cn20k mailbox DMA from the probe and pci_enable_sriov() error paths. Fixes: e53ee4acb220 ("octeontx2-af: CN20k basic mbox operations and structures") Signed-off-by: Sai Krishna Signed-off-by: Ratheesh Kannoth Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260821102337.2989169-1-rkannoth@marvell.com Signed-off-by: Jakub Kicinski --- .../ethernet/marvell/octeontx2/af/cn20k/api.h | 1 + .../marvell/octeontx2/af/cn20k/mbox_init.c | 21 +++++++++- .../net/ethernet/marvell/octeontx2/af/rvu.c | 39 +++++++++++-------- 3 files changed, 42 insertions(+), 19 deletions(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/af/cn20k/api.h b/drivers/net/ethernet/marvell/octeontx2/af/cn20k/api.h index 4285b5d6a6a2..f36a1d5f236f 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/cn20k/api.h +++ b/drivers/net/ethernet/marvell/octeontx2/af/cn20k/api.h @@ -21,6 +21,7 @@ int cn20k_rvu_mbox_init(struct rvu *rvu, int type, int num); int cn20k_rvu_get_mbox_regions(struct rvu *rvu, void **mbox_addr, int num, int type, unsigned long *pf_bmap); void cn20k_free_mbox_memory(struct rvu *rvu); +void cn20k_free_mbox_memory_type(struct rvu *rvu, int type); int cn20k_register_afpf_mbox_intr(struct rvu *rvu); int cn20k_register_afvf_mbox_intr(struct rvu *rvu, int pf_vec_start); void cn20k_rvu_enable_mbox_intr(struct rvu *rvu); diff --git a/drivers/net/ethernet/marvell/octeontx2/af/cn20k/mbox_init.c b/drivers/net/ethernet/marvell/octeontx2/af/cn20k/mbox_init.c index 71401dec0d77..01f32adac599 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/cn20k/mbox_init.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/cn20k/mbox_init.c @@ -335,13 +335,30 @@ int cn20k_rvu_mbox_init(struct rvu *rvu, int type, int ndevs) return rvu_alloc_mbox_memory(rvu, type, ndevs, MBOX_SIZE); } +void cn20k_free_mbox_memory_type(struct rvu *rvu, int type) +{ + if (!is_cn20k(rvu->pdev) || !rvu->ng_rvu) + return; + + switch (type) { + case TYPE_AFPF: + qmem_free(rvu->dev, rvu->ng_rvu->pf_mbox_addr); + rvu->ng_rvu->pf_mbox_addr = NULL; + break; + case TYPE_AFVF: + qmem_free(rvu->dev, rvu->ng_rvu->vf_mbox_addr); + rvu->ng_rvu->vf_mbox_addr = NULL; + break; + } +} + void cn20k_free_mbox_memory(struct rvu *rvu) { if (!is_cn20k(rvu->pdev)) return; - qmem_free(rvu->dev, rvu->ng_rvu->pf_mbox_addr); - qmem_free(rvu->dev, rvu->ng_rvu->vf_mbox_addr); + cn20k_free_mbox_memory_type(rvu, TYPE_AFPF); + cn20k_free_mbox_memory_type(rvu, TYPE_AFVF); } void cn20k_rvu_disable_afvf_intr(struct rvu *rvu, int vfs) diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu.c index 43062390aa4c..74c041ab5280 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu.c @@ -2585,12 +2585,6 @@ static int rvu_mbox_init(struct rvu *rvu, struct mbox_wq_info *mw, if (!pf_bmap) return -ENOMEM; - ng_rvu_mbox = kzalloc_obj(*ng_rvu_mbox); - if (!ng_rvu_mbox) { - err = -ENOMEM; - goto free_bitmap; - } - /* RVU VFs */ if (type == TYPE_AFVF) bitmap_set(pf_bmap, 0, num); @@ -2604,15 +2598,22 @@ static int rvu_mbox_init(struct rvu *rvu, struct mbox_wq_info *mw, } } - rvu->ng_rvu = ng_rvu_mbox; + if (!rvu->ng_rvu) { + ng_rvu_mbox = devm_kzalloc(rvu->dev, sizeof(*ng_rvu_mbox), GFP_KERNEL); + if (!ng_rvu_mbox) { + err = -ENOMEM; + goto free_bitmap; + } - rvu->ng_rvu->rvu_mbox_ops = &rvu_mbox_ops; + rvu->ng_rvu = ng_rvu_mbox; + + rvu->ng_rvu->rvu_mbox_ops = &rvu_mbox_ops; + mutex_init(&rvu->mbox_lock); + } err = cn20k_rvu_mbox_init(rvu, type, num); if (err) - goto free_mem; - - mutex_init(&rvu->mbox_lock); + goto free_bitmap; mbox_regions = kcalloc(num, sizeof(void __iomem *), GFP_KERNEL); if (!mbox_regions) { @@ -2702,14 +2703,18 @@ static int rvu_mbox_init(struct rvu *rvu, struct mbox_wq_info *mw, free_regions: kfree(mbox_regions); free_qmem: - cn20k_free_mbox_memory(rvu); -free_mem: - kfree(rvu->ng_rvu); + cn20k_free_mbox_memory_type(rvu, type); free_bitmap: bitmap_free(pf_bmap); return err; } +static void rvu_free_cn20k_mbox_memory(struct rvu *rvu) +{ + if (is_cn20k(rvu->pdev)) + cn20k_free_mbox_memory(rvu); +} + static void rvu_mbox_destroy(struct mbox_wq_info *mw) { struct otx2_mbox *mbox = &mw->mbox; @@ -3519,6 +3524,7 @@ static int rvu_enable_sriov(struct rvu *rvu) if (err) { rvu_disable_afvf_intr(rvu); rvu_mbox_destroy(&rvu->afvf_wq_info); + cn20k_free_mbox_memory_type(rvu, TYPE_AFVF); return err; } @@ -3681,6 +3687,7 @@ static int rvu_probe(struct pci_dev *pdev, const struct pci_device_id *id) err_mbox: rvu_mbox_destroy(&rvu->afpf_wq_info); err_hwsetup: + rvu_free_cn20k_mbox_memory(rvu); rvu_cgx_exit(rvu); rvu_fwdata_exit(rvu); rvu_mcs_exit(rvu); @@ -3723,9 +3730,7 @@ static void rvu_remove(struct pci_dev *pdev) pci_set_drvdata(pdev, NULL); devm_kfree(&pdev->dev, rvu->hw); - if (is_cn20k(rvu->pdev)) - cn20k_free_mbox_memory(rvu); - kfree(rvu->ng_rvu); + rvu_free_cn20k_mbox_memory(rvu); devm_kfree(&pdev->dev, rvu); atomic_set(&device_bound, 0); } From 1704aaaf5d22bc765c168402350d191e24e245bc Mon Sep 17 00:00:00 2001 From: Deepanshu Kartikey Date: Mon, 24 Aug 2026 20:16:53 +0530 Subject: [PATCH 0956/1328] eventfs: Initialize ei->children and ei->list in init_ei() eventfs_create_dir() allocates the eventfs_inode and initializes it with init_ei(). But this does not initialize the eventfs_inode list_heads. If the eventfs_create_dir() fails due to memory pressure, it will call free_ei() before it initialized the lists, and that checks to make sure the eventfs_inode has no children. But because the list wasn't initialized, it will give a false warning. Fix it by moving the list initialization into init_ei(). Cc: stable@vger.kernel.org Fixes: 5790b1fb3d67 ("eventfs: Remove eventfs_file and just use eventfs_inode") Reported-by: syzbot+3ef80b4ed02226d04a06@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=3ef80b4ed02226d04a06 Link: https://patch.msgid.link/20260824144653.54044-1-kartikey406@gmail.com Signed-off-by: Deepanshu Kartikey [ Rewrote change log ] Signed-off-by: Steven Rostedt --- fs/tracefs/event_inode.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/fs/tracefs/event_inode.c b/fs/tracefs/event_inode.c index 604ba3e841d2..6e3513b13cfa 100644 --- a/fs/tracefs/event_inode.c +++ b/fs/tracefs/event_inode.c @@ -438,6 +438,8 @@ static inline struct eventfs_inode *init_ei(struct eventfs_inode *ei, const char if (!ei->name) return NULL; kref_init(&ei->kref); + INIT_LIST_HEAD(&ei->children); + INIT_LIST_HEAD(&ei->list); return ei; } @@ -729,8 +731,6 @@ struct eventfs_inode *eventfs_create_dir(const char *name, struct eventfs_inode ei->entries = entries; ei->nr_entries = size; ei->data = data; - INIT_LIST_HEAD(&ei->children); - INIT_LIST_HEAD(&ei->list); scoped_guard(mutex, &eventfs_mutex) { if (!parent->is_freed) @@ -802,9 +802,6 @@ struct eventfs_inode *eventfs_create_events_dir(const char *name, struct dentry ei->attr.uid = uid; ei->attr.gid = gid; - INIT_LIST_HEAD(&ei->children); - INIT_LIST_HEAD(&ei->list); - ti = get_tracefs(inode); ti->flags |= TRACEFS_EVENT_INODE; ti->private = ei; From 64f74d8f728877858372c52067e5c0c091f8db24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 19 Aug 2026 09:24:21 +0200 Subject: [PATCH 0957/1328] um: Use asm-generic/timex.h over the host architecture one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The removal of the architecture-specific asm/timex.h was meant to fall back to asm-generic/timex.h. However on UML, the host architecture headers are earlier on the include path and therefore asm/timex.h from the host architecture is used. On x86 that header in turn requires the generation of cpufeaturemasks.h, which is not set up for UML and the build fails. In file included from ../arch/x86/include/asm/tsc.h:9, from ../arch/x86/include/asm/timex.h:6, from ../include/linux/timex.h:67, from ../include/linux/time32.h:13, from ../include/linux/time.h:60, from ../include/linux/jiffies.h:10, from ../include/linux/ktime.h:25, from ../include/linux/timer.h:6, from ../include/linux/uprobes.h:18, from ../include/linux/mm_types.h:16, from ../include/linux/mmzone.h:22, from ../include/linux/gfp.h:7, from ../include/linux/slab.h:17, from ../include/linux/crypto.h:18, from ../arch/um/kernel/asm-offsets.c:6: arch/x86/include/asm/tsc.h: In function ‘get_cycles’: arch/um/include/asm/cpufeature.h:52:39: error: implicit declaration of function ‘DISABLED_MASK_BIT_SET’ [-Wimplicit-function-declaration] 52 | (__builtin_constant_p(bit) && DISABLED_MASK_BIT_SET(bit) ? 0 : _static_cpu_has(bit)) | ^~~~~~~~~~~~~~~~~~~~~ arch/x86/include/asm/tsc.h:79:14: note: in expansion of macro ‘cpu_feature_enabled’ 79 | if (!cpu_feature_enabled(X86_FEATURE_TSC)) | ^~~~~~~~~~~~~~~~~~~ arch/um/include/asm/cpufeature.h:115:17: error: implicit declaration of function ‘_static_cpu_has’ [-Wimplicit-function-declaration] 115 | _static_cpu_has(bit) \ | ^~~~~~~~~~~~~~~ ... The x86-specific timex.h also doesn't make sense on UML in general. Reintroduce the UML-specific asm/timex.h and add a comment to document its necessity. [ bp: Improve that comment to actually state why stuff is there. ] Fixes: 3ed403bbc967 ("treewide: Remove CLOCK_TICK_RATE") Signed-off-by: Thomas Weißschuh Signed-off-by: Borislav Petkov (AMD) Reviewed-by: David Gow Link: https://patch.msgid.link/20260819-uml-timex-fix-v1-1-f690b514fc9f@linutronix.de --- arch/um/include/asm/timex.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 arch/um/include/asm/timex.h diff --git a/arch/um/include/asm/timex.h b/arch/um/include/asm/timex.h new file mode 100644 index 000000000000..39e392ebc8c2 --- /dev/null +++ b/arch/um/include/asm/timex.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __UM_TIMEX_H +#define __UM_TIMEX_H + +/* + * Do not fall back to the host architecture header because latter likely + * includes facilities like cpu_feature_enabled() which are present only + * there. That would result in build breakages and/or efforts to "emulate" + * those facilities in UML. + */ +#include + +#endif From d230991493b521eeff39f32434fddcbcdb109eb0 Mon Sep 17 00:00:00 2001 From: Johannes Weiner Date: Mon, 29 Jun 2026 12:33:37 -0400 Subject: [PATCH 0958/1328] mm: mempolicy: fix automatic numa balancing for shmem Neha reports that mapped shmem aren't considered for NUMA balancing, noting convergence problems and bandwidth bottlenecking for cachelib based workloads on tiered memory systems. Looking at the code and going through the git history, this doesn't actually seem intentional: Commit fc3147245d19 ("mm: numa: Limit NUMA scanning to migrate-on-fault VMAs") added a vma_policy_mof() gate to task_numa_work() so VMAs whose policy lacks MPOL_F_MOF are skipped from NUMA balancing scans. The motivation was a real usecase: Oracle was pinning shared segments with mbind(MPOL_BIND) so trapping faults was both expensive and pointless. The handling of NULL from vm_ops->get_policy, however, treated "user explicitly opted out" the same as "user never specified anything." For VMAs whose shared policy is absent - the common case for shmem - the scan was disabled too. This issue is old. It probably hurts less in conventional NUMA. But it's very noticeable on tiered systems, where entire tmpfs workingsets can get stuck on lower-bandwidth memory. Fix this by having vma_policy_mof() use __get_vma_policy() directly, and thereby handle the fallback to task policy (-> preferred_node_policy() has MPOL_F_MOF per default). Every other consumer of vm_ops->get_policy already handles it this way, the scan-eligibility check was the outlier. This preserves Mel's intended fix: don't scan stuff the user explicitly pinned. But allow default policy vmas to participate in balancing. Link: https://lore.kernel.org/20260629163337.1264881-1-hannes@cmpxchg.org Fixes: fc3147245d19 ("mm: numa: Limit NUMA scanning to migrate-on-fault VMAs") Signed-off-by: Johannes Weiner Reported-by: Neha Gholkar Tested-by: Neha Gholkar Reviewed-by: Gregory Price Acked-by: David Hildenbrand (Arm) Acked-by: Balbir Singh Cc: Alistair Popple Cc: Byungchul Park Cc: "Huang, Ying" Cc: Joshua Hahn Cc: Matthew Brost Cc: Rakie Kim Cc: Zi Yan Cc: Signed-off-by: Andrew Morton --- mm/mempolicy.c | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/mm/mempolicy.c b/mm/mempolicy.c index 501e0b80d7da..5720f7f54d94 100644 --- a/mm/mempolicy.c +++ b/mm/mempolicy.c @@ -2060,24 +2060,15 @@ struct mempolicy *get_vma_policy(struct vm_area_struct *vma, bool vma_policy_mof(struct vm_area_struct *vma) { struct mempolicy *pol; + pgoff_t ilx; + bool mof; - if (vma->vm_ops && vma->vm_ops->get_policy) { - bool ret = false; - pgoff_t ilx; /* ignored here */ - - pol = vma->vm_ops->get_policy(vma, vma->vm_start, &ilx); - if (pol && (pol->flags & MPOL_F_MOF)) - ret = true; - mpol_cond_put(pol); - - return ret; - } - - pol = vma->vm_policy; + pol = __get_vma_policy(vma, vma->vm_start, &ilx); if (!pol) pol = get_task_policy(current); - - return pol->flags & MPOL_F_MOF; + mof = pol->flags & MPOL_F_MOF; + mpol_cond_put(pol); + return mof; } bool apply_policy_zone(struct mempolicy *policy, enum zone_type zone) From 644ad84870ab503f5619d21cf3d37efb134e82de Mon Sep 17 00:00:00 2001 From: Hajime Tazaki Date: Thu, 2 Jul 2026 10:25:46 +0900 Subject: [PATCH 0959/1328] mm: nommu: point to the write iterator upon split_vma When a user invokes munmap(2) on a partial region allocated by mmap(), the kernel may split the original region if necessary and shrink it to the correct size. At the beginning of vmi_shrink_vma(), the unused part is cleared; however, an assertion is triggered if the shrink occurs after split_vma(). This commit fixes the issue by correctly configuring the pointer to the iterator at the end of split_vma(). This bug was detected using the Linux Test Project (LTP) test linked below, running on a nommu UML (User-Mode Linux) environment (via an out-of-tree extension to UML). Here is a minimal reproducible chunk of code for this issue: void *addr; size_t pagesize = getpagesize(); addr = mmap(NULL, pagesize * 4, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); munmap(addr + pagesize * 1, pagesize); This is the console output with CONFIG_DEBUG_MAPLE_TREE=y. nommu: WARN at __mas_set_range:791 (1) MAS: tree=0000000091c23b08 enode=0000000065057663 (ma_active) Store Type: node_store [9/9] index=70af8000 last=ffffffffffffffff min=0 max=ffffffffffffffff sheaf=0000000000000000, request 0 depth=0, flags=0 maple_tree(0000000091c23b08) flags 307, height 1 root 0000000083394c06 0-ffffffffffffffff: node 0000000010c90bd6 depth 0 type 1 parent 0000000050e1ddf8 contents: 0000000000000000 707A 7FFF 00000000eb0ac2b5 707AFFFF 0000000000000000 7093FFFF 0000000045ead616 7095FFFF 0000000000000000 7096CFFF 000 00000681c7151 7096FFFF 0000000000000000 70AF3FFF 000000006c78b9e9 70AF4FFF 000000001914ab0b 70AF7FFF 00000000000 00000 FFFFFFFFFFFFFFFF 0000000000000000 0 0000000000000000 0 0000000000000000 0 0000000000000000 0 0000000000000 000 0 00000000bca8be4f 0-707a7fff: 0000000000000000 707a8000-707affff: 00000000eb0ac2b5 707b0000-7093ffff: 0000000000000000 70940000-7095ffff: 0000000045ead616 70960000-7096cfff: 0000000000000000 7096d000-7096ffff: 00000000681c7151 70970000-70af3fff: 0000000000000000 70af4000-70af4fff: 000000006c78b9e9 70af5000-70af7fff: 000000001914ab0b 70af8000-ffffffffffffffff: 0000000000000000 nommu: Pass: 796 Run:797 Link: https://github.com/linux-test-project/ltp/blob/master/testcases/kernel/syscalls/mseal/mseal02.c Link: https://lore.kernel.org/20260702012546.665383-1-thehajime@gmail.com Signed-off-by: Hajime Tazaki Cc: Jann Horn Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Pedro Falcato Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/nommu.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mm/nommu.c b/mm/nommu.c index 277f663e1c5b..498e01ee40b0 100644 --- a/mm/nommu.c +++ b/mm/nommu.c @@ -1393,6 +1393,10 @@ static int split_vma(struct vma_iterator *vmi, struct vm_area_struct *vma, setup_vma_to_mm(vma, mm); setup_vma_to_mm(new, mm); vma_iter_store_new(vmi, new); + + /* vmi should point lower address */ + if (new_below) + vma_next(vmi); mm->map_count++; return 0; From 9b5e4809806cb300cc163b26fa70dfd36e3577b3 Mon Sep 17 00:00:00 2001 From: Ethan Nelson-Moore Date: Wed, 10 Jun 2026 15:09:04 -0700 Subject: [PATCH 0960/1328] maple_tree: remove undocumented CONFIG_MAPLE_RCU_DISABLED macro consults the macro CONFIG_MAPLE_RCU_DISABLED to determine whether to disable the mt_in_rcu() function (by making it always return false). This macro is not reachable via Kconfig, despite its name, and is not documented anywhere. Remove it to avoid polluting the CONFIG_* namespace. Discovered while searching for CONFIG_* symbols referenced in code but not defined in any Kconfig file. Link: https://lore.kernel.org/20260610220905.99860-1-enelsonmoore@gmail.com Signed-off-by: Ethan Nelson-Moore Acked-by: SeongJae Park Reviewed-by: Liam Howlett Reviewed-by: Alice Ryhl Cc: Andrew Ballance Signed-off-by: Andrew Morton --- include/linux/maple_tree.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/include/linux/maple_tree.h b/include/linux/maple_tree.h index 4a5631906aff..1b3014377105 100644 --- a/include/linux/maple_tree.h +++ b/include/linux/maple_tree.h @@ -11,7 +11,6 @@ #include #include #include -/* #define CONFIG_MAPLE_RCU_DISABLED */ /* * Allocated nodes are mutable until they have been inserted into the tree, @@ -864,9 +863,6 @@ static inline void mt_init(struct maple_tree *mt) static inline bool mt_in_rcu(struct maple_tree *mt) { -#ifdef CONFIG_MAPLE_RCU_DISABLED - return false; -#endif return mt->ma_flags & MT_FLAGS_USE_RCU; } From 1eba458a54d073aa8bd62627ad0e9264ef0f51e3 Mon Sep 17 00:00:00 2001 From: Kaitao Cheng Date: Tue, 7 Jul 2026 17:03:31 +0800 Subject: [PATCH 0961/1328] mm/kconfig: drop redundant memory hotplug dependencies MHP_MEMMAP_ON_MEMORY is defined inside the MEMORY_HOTPLUG block, and MEMORY_HOTPLUG already depends on SPARSEMEM_VMEMMAP. Keep the explicit MEMORY_HOTPLUG dependency for local readability, but drop the redundant SPARSEMEM_VMEMMAP dependency. ZONE_DEVICE depends on MEMORY_HOTREMOVE, which depends on MEMORY_HOTPLUG. MEMORY_HOTPLUG in turn depends on SPARSEMEM_VMEMMAP. Drop the direct MEMORY_HOTPLUG and SPARSEMEM_VMEMMAP dependencies from ZONE_DEVICE. This does not change the set of valid configurations. Link: https://lore.kernel.org/20260707090331.52971-1-kaitao.cheng@linux.dev Signed-off-by: Kaitao Cheng Acked-by: David Hildenbrand (Arm) Acked-by: Muchun Song Reviewed-by: Lorenzo Stoakes Cc: Liam R. Howlett Cc: Michal Hocko Cc: Mike Rapoport Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/Kconfig | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mm/Kconfig b/mm/Kconfig index 3185500ce7b7..d28dde592de4 100644 --- a/mm/Kconfig +++ b/mm/Kconfig @@ -588,7 +588,7 @@ config MEMORY_HOTREMOVE config MHP_MEMMAP_ON_MEMORY def_bool y - depends on MEMORY_HOTPLUG && SPARSEMEM_VMEMMAP + depends on MEMORY_HOTPLUG depends on ARCH_MHP_MEMMAP_ON_MEMORY_ENABLE endif # MEMORY_HOTPLUG @@ -1227,9 +1227,7 @@ config ZONE_DMA32 config ZONE_DEVICE bool "Device memory (pmem, HMM, etc...) hotplug support" - depends on MEMORY_HOTPLUG depends on MEMORY_HOTREMOVE - depends on SPARSEMEM_VMEMMAP select XARRAY_MULTI help From 092836fedd82cdafc6e2085c4b7a7878bfa3ca1d Mon Sep 17 00:00:00 2001 From: "David Hildenbrand (Arm)" Date: Thu, 9 Jul 2026 10:13:34 +0530 Subject: [PATCH 0962/1328] mm: standardize printing for pgtable entries Bad page map reporting currently stores page table entry values in an unsigned long long and prints them with fixed 64-bit-oriented format strings. This is inconsistent across call sites and does not work well for architectures where page table entry values are not naturally represented as 64-bit values, such as 32-bit or 128-bit entries. Introduce a common helper to convert raw page table entry values into a fixed-width hexadecimal string based on the actual entry size. Use it for bad page map reporting and for dumping the page table walk in __print_bad_page_map_pgtable(). Pass page table entry values to the reporting path as raw bytes together with their size, instead of forcing them through an unsigned long long. It keeps the printed output consistent and avoids truncation or misleading formatting for non-64-bit page table entries. Link: https://lore.kernel.org/20260709044334.1741263-1-anshuman.khandual@arm.com Signed-off-by: David Hildenbrand (Arm) Co-developed-by: Anshuman Khandual Signed-off-by: Anshuman Khandual Cc: Andriy Shevchenko Cc: David Hildenbrand Cc: Hugh Dickins Cc: Matthew Wilcox (Oracle) Cc: Ryan Roberts Signed-off-by: Andrew Morton --- mm/memory.c | 102 ++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 79 insertions(+), 23 deletions(-) diff --git a/mm/memory.c b/mm/memory.c index be743a9c6606..d2f14ba2261c 100644 --- a/mm/memory.c +++ b/mm/memory.c @@ -519,9 +519,52 @@ static bool is_bad_page_map_ratelimited(void) return false; } +static void ptval_bytes_to_hex_str(char *buf, size_t buf_size, const void *entry, size_t entry_size) +{ + if (WARN_ON_ONCE(buf_size < entry_size * 2 + 1)) { + snprintf(buf, buf_size, "overflow"); + return; + } + + switch (entry_size) { + case sizeof(u32): + snprintf(buf, buf_size, "%08x", *(const u32 *)entry); + break; + case sizeof(u64): + snprintf(buf, buf_size, "%016llx", *(const u64 *)entry); + break; +#if defined(__SIZEOF_INT128__) + case sizeof(u128): + snprintf(buf, buf_size, "%016llx%016llx", + (unsigned long long)(*(const u128 *)entry >> 64), + (unsigned long long)*(const u128 *)entry); + break; +#endif + default: + snprintf(buf, buf_size, "unsupported"); + break; + } +} + +#define ptval_to_str(buf, val) \ + do { \ + auto __val = (val); \ + \ + ptval_bytes_to_hex_str((buf), sizeof(buf), &__val, sizeof(__val)); \ + } while (0) + +#if defined(__SIZEOF_INT128__) +#define PTVAL_STR_MAX (32 + 1) /* Max 128-bit value in hex + NUL */ +#else +#define PTVAL_STR_MAX (16 + 1) /* Max 64-bit value in hex + NUL */ +#endif + static void __print_bad_page_map_pgtable(struct mm_struct *mm, unsigned long addr) { - unsigned long long pgdv, p4dv, pudv, pmdv; + char pgd_str[PTVAL_STR_MAX]; + char p4d_str[PTVAL_STR_MAX]; + char pud_str[PTVAL_STR_MAX]; + char pmd_str[PTVAL_STR_MAX]; p4d_t p4d, *p4dp; pud_t pud, *pudp; pmd_t pmd, *pmdp; @@ -532,34 +575,34 @@ static void __print_bad_page_map_pgtable(struct mm_struct *mm, unsigned long add * see locking requirements for print_bad_page_map(). */ pgdp = pgd_offset(mm, addr); - pgdv = pgd_val(*pgdp); + ptval_to_str(pgd_str, pgd_val(*pgdp)); if (!pgd_present(*pgdp) || pgd_leaf(*pgdp)) { - pr_alert("pgd:%08llx\n", pgdv); + pr_alert("pgd:%s\n", pgd_str); return; } p4dp = p4d_offset(pgdp, addr); p4d = p4dp_get(p4dp); - p4dv = p4d_val(p4d); + ptval_to_str(p4d_str, p4d_val(p4d)); if (!p4d_present(p4d) || p4d_leaf(p4d)) { - pr_alert("pgd:%08llx p4d:%08llx\n", pgdv, p4dv); + pr_alert("pgd:%s p4d:%s\n", pgd_str, p4d_str); return; } pudp = pud_offset(p4dp, addr); pud = pudp_get(pudp); - pudv = pud_val(pud); + ptval_to_str(pud_str, pud_val(pud)); if (!pud_present(pud) || pud_leaf(pud)) { - pr_alert("pgd:%08llx p4d:%08llx pud:%08llx\n", pgdv, p4dv, pudv); + pr_alert("pgd:%s p4d:%s pud:%s\n", pgd_str, p4d_str, pud_str); return; } pmdp = pmd_offset(pudp, addr); pmd = pmdp_get(pmdp); - pmdv = pmd_val(pmd); + ptval_to_str(pmd_str, pmd_val(pmd)); /* * Dumping the PTE would be nice, but it's tricky with CONFIG_HIGHPTE, @@ -567,8 +610,7 @@ static void __print_bad_page_map_pgtable(struct mm_struct *mm, unsigned long add * doing another map would be bad. print_bad_page_map() should * already take care of printing the PTE. */ - pr_alert("pgd:%08llx p4d:%08llx pud:%08llx pmd:%08llx\n", pgdv, - p4dv, pudv, pmdv); + pr_alert("pgd:%s p4d:%s pud:%s pmd:%s\n", pgd_str, p4d_str, pud_str, pmd_str); } /* @@ -584,10 +626,11 @@ static void __print_bad_page_map_pgtable(struct mm_struct *mm, unsigned long add * page table lock. */ static void print_bad_page_map(struct vm_area_struct *vma, - unsigned long addr, unsigned long long entry, struct page *page, - enum pgtable_level level) + unsigned long addr, const void *entry, size_t entry_size, + struct page *page, enum pgtable_level level) { struct address_space *mapping; + char entry_str[PTVAL_STR_MAX]; pgoff_t index; if (is_bad_page_map_ratelimited()) @@ -596,8 +639,9 @@ static void print_bad_page_map(struct vm_area_struct *vma, mapping = vma->vm_file ? vma->vm_file->f_mapping : NULL; index = linear_page_index(vma, addr); - pr_alert("BUG: Bad page map in process %s %s:%08llx", current->comm, - pgtable_level_to_str(level), entry); + ptval_bytes_to_hex_str(entry_str, sizeof(entry_str), entry, entry_size); + pr_alert("BUG: Bad page map in process %s %s:%s", current->comm, + pgtable_level_to_str(level), entry_str); __print_bad_page_map_pgtable(vma->vm_mm, addr); if (page) dump_page(page, "bad page map"); @@ -627,8 +671,13 @@ static inline bool pgtable_level_has_pxx_special(enum pgtable_level level) } } -#define print_bad_pte(vma, addr, pte, page) \ - print_bad_page_map(vma, addr, pte_val(pte), page, PGTABLE_LEVEL_PTE) +static void print_bad_pte(struct vm_area_struct *vma, unsigned long addr, + pte_t pte, struct page *page) +{ + auto entry = pte_val(pte); + + print_bad_page_map(vma, addr, &entry, sizeof(entry), page, PGTABLE_LEVEL_PTE); +} /** * __vm_normal_page() - Get the "struct page" associated with a page table entry. @@ -636,8 +685,9 @@ static inline bool pgtable_level_has_pxx_special(enum pgtable_level level) * @addr: The address where the page table entry is mapped. * @pfn: The PFN stored in the page table entry. * @special: Whether the page table entry is marked "special". - * @level: The page table level for error reporting purposes only. * @entry: The page table entry value for error reporting purposes only. + * @entry_size: The size of @entry. + * @level: The page table level for error reporting purposes only. * * "Special" mappings do not wish to be associated with a "struct page" (either * it doesn't exist, or it exists but they don't want to touch it). In this @@ -697,7 +747,7 @@ static inline bool pgtable_level_has_pxx_special(enum pgtable_level level) */ static inline struct page *__vm_normal_page(struct vm_area_struct *vma, unsigned long addr, unsigned long pfn, bool special, - unsigned long long entry, enum pgtable_level level) + const void *entry, size_t entry_size, enum pgtable_level level) { if (pgtable_level_has_pxx_special(level)) { if (unlikely(special)) { @@ -710,7 +760,7 @@ static inline struct page *__vm_normal_page(struct vm_area_struct *vma, if (is_zero_pfn(pfn) || is_huge_zero_pfn(pfn)) return NULL; - print_bad_page_map(vma, addr, entry, NULL, level); + print_bad_page_map(vma, addr, entry, entry_size, NULL, level); return NULL; } /* @@ -741,7 +791,7 @@ static inline struct page *__vm_normal_page(struct vm_area_struct *vma, if (unlikely(pfn > highest_memmap_pfn)) { /* Corrupted page table entry. */ - print_bad_page_map(vma, addr, entry, NULL, level); + print_bad_page_map(vma, addr, entry, entry_size, NULL, level); return NULL; } /* @@ -767,8 +817,10 @@ static inline struct page *__vm_normal_page(struct vm_area_struct *vma, struct page *vm_normal_page(struct vm_area_struct *vma, unsigned long addr, pte_t pte) { + auto entry = pte_val(pte); + return __vm_normal_page(vma, addr, pte_pfn(pte), pte_special(pte), - pte_val(pte), PGTABLE_LEVEL_PTE); + &entry, sizeof(entry), PGTABLE_LEVEL_PTE); } /** @@ -809,8 +861,10 @@ struct folio *vm_normal_folio(struct vm_area_struct *vma, unsigned long addr, struct page *vm_normal_page_pmd(struct vm_area_struct *vma, unsigned long addr, pmd_t pmd) { + auto entry = pmd_val(pmd); + return __vm_normal_page(vma, addr, pmd_pfn(pmd), pmd_special(pmd), - pmd_val(pmd), PGTABLE_LEVEL_PMD); + &entry, sizeof(entry), PGTABLE_LEVEL_PMD); } /** @@ -850,8 +904,10 @@ struct folio *vm_normal_folio_pmd(struct vm_area_struct *vma, struct page *vm_normal_page_pud(struct vm_area_struct *vma, unsigned long addr, pud_t pud) { + auto entry = pud_val(pud); + return __vm_normal_page(vma, addr, pud_pfn(pud), pud_special(pud), - pud_val(pud), PGTABLE_LEVEL_PUD); + &entry, sizeof(entry), PGTABLE_LEVEL_PUD); } #endif From 0b4268ac77fa6e2c05fc8b90441b0832b211385d Mon Sep 17 00:00:00 2001 From: Kaitao Cheng Date: Sun, 12 Jul 2026 17:33:26 +0800 Subject: [PATCH 0963/1328] mm/kconfig: drop redundant dependency wrappers Some mm Kconfig entries repeat dependencies that are already expressed by their surrounding blocks or menus. The zsmalloc allocator options menu already depends on ZSMALLOC, so the outer if ZSMALLOC block does not add any extra constraint. MEMORY_HOTREMOVE and MHP_MEMMAP_ON_MEMORY are both inside the if MEMORY_HOTPLUG block, so their local depends on MEMORY_HOTPLUG entries are redundant. PTE_MARKER_UFFD_WP is the only entry under if USERFAULTFD. Move the USERFAULTFD dependency into the symbol itself and combine it with the architecture support dependency. This keeps the same visibility and defaults while avoiding duplicate dependency expressions. Link: https://lore.kernel.org/20260712093326.8313-1-kaitao.cheng@linux.dev Signed-off-by: Kaitao Cheng Suggested-by: Julian Braha Reviewed-by: Lorenzo Stoakes (ARM) Acked-by: David Hildenbrand (Arm) Cc: Liam R. Howlett Cc: Michal Hocko Cc: Mike Rapoport Cc: Muchun Song Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/Kconfig | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/mm/Kconfig b/mm/Kconfig index d28dde592de4..060190e12bce 100644 --- a/mm/Kconfig +++ b/mm/Kconfig @@ -125,8 +125,6 @@ config ZSWAP_COMPRESSOR_DEFAULT config ZSMALLOC tristate -if ZSMALLOC - menu "Zsmalloc allocator options" depends on ZSMALLOC @@ -161,8 +159,6 @@ config ZSMALLOC_CHAIN_SIZE endmenu -endif - menu "Slab allocator options" config SLUB @@ -583,12 +579,10 @@ endchoice config MEMORY_HOTREMOVE bool "Allow for memory hot remove" - depends on MEMORY_HOTPLUG select MIGRATION config MHP_MEMMAP_ON_MEMORY def_bool y - depends on MEMORY_HOTPLUG depends on ARCH_MHP_MEMMAP_ON_MEMORY_ENABLE endif # MEMORY_HOTPLUG @@ -1393,17 +1387,15 @@ menuconfig USERFAULTFD Enable the userfaultfd() system call that allows to intercept and handle page faults in userland. -if USERFAULTFD config PTE_MARKER_UFFD_WP bool "Userfaultfd write protection support for shmem/hugetlbfs" default y - depends on HAVE_ARCH_USERFAULTFD_WP + depends on USERFAULTFD && HAVE_ARCH_USERFAULTFD_WP help Allows to create marker PTEs for userfaultfd write protection purposes. It is required to enable userfaultfd write protection on file-backed memory types like shmem and hugetlbfs. -endif # USERFAULTFD # multi-gen LRU { config LRU_GEN From 0bd14001eb264247d565a5a44a71675df273640d Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Thu, 13 Aug 2026 18:32:18 +0100 Subject: [PATCH 0964/1328] mm/vma: introduce VMA anon page offset field and add helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Patch series "mm/rmap: index MAP_PRIVATE file-backed folios by anonymous pgoff", v5. In memory management we've managed to manufacture a great deal of confusion around the concept of anonymous memory. We have: 1. 'Pure anon' memory - anonymous VMAs whose folios are anonymous and swap-backed (thus for reclaim purposes, treated as anonymous). These are simple enough. 2. shmem - file-backed VMAs, file-backed folios (from rmap perspective) so present in the page cache and mapped by an address_space object, but whose folios are also swap-backed (thus treated as anonymous for reclaim purposes). 3. MAP_PRIVATE-mapped /dev/zero - a strange beast whose VMAs have vma->vm_file set, but which clears vma->vm_ops to satisfy vma_is_anonymous(), resulting in VMAs that were mmap()'d referencing a file, but are in every other sense anonymous, including the folios. 4. Other MAP_PRIVATE-file backed mappings - These possess file-backed VMAs and have file-backed folios until CoW'd, at which point those CoW'd folios are anonymous. This series fixes issue 3. In order for us to traverse VMAs using the reverse mapping, we require two fields - folio->mapping and folio->index. The first tells the rmap code where to look for VMAs, and the second tells it at which offset the folio starts within the referenced object. For anonymous folios, folio->mapping points at an anon_vma object. For file-backed folios, it points at an address_space. And: * For file-backed folios folio->index is simply the page offset of the start of the folio within the file. * For anonymous folios belonging to pure anon mappings, folio->index is equal to the anonymous page offset of the folio. * For anonymous folios belonging to file-backed mappings (i.e. CoW'd folios of a MAP_PRIVATE file-backed mapping), folio->index is equal to the file page offset. This series establishes a new anonymous page offset property of VMAs to allow us to map anonymous folios at their anonymous page offset, consistent with pure anon. The purpose of doing so is to lay the foundations for the scalable CoW work. This is necessary because scalable CoW looks in the maple tree for the VMA located at folio->index << PAGE_SHIFT, before falling back to looking up tracked remaps if necessary. The MAP_PRIVATE file-backed case means that folio indices will very often conflict with one another and this remap tracking becomes substantially more contended, and of course the fast path can never be used. This also makes it possible, in future, to unshare anonymously mapped folios with deep fork hierarchies on remap, eliminating the need for remap tracking in the vast majority of cases. Similar to page offset of pure anonymous VMAs, we update the anonymous page offset of unfaulted file-backed VMAs on remap, but do not once CoW'd (i.e. vma->anon_vma is non-NULL). Overall, there is little impact on mergeability, which remains exactly the same for pure anonymous and shared file-backed mappings, with the only impact being on MAP_PRIVATE-mapped file-backed mappings, which must now match on anonymous page offset as well as file page offset to be merged. To fail to merge like this would require CoW'ing the mapping, then finding another VMA with identical file and compatible page offset to remap next to. This is therefore very much an edge case that should have very little impact (and which scalable CoW may very well address in any case). This patch (of 16): Establish fields in vm_area_struct to store the anonymous page offset of VMAs. Initially, the anonymous page offset of a VMA is vma->vm_start >> PAGE_SHIFT. When a VMA is remapped to new_address its anonymous page offset is either updated to new_address >> PAGE_SHIFT if unfaulted or, if faulted, remains equal to the anonymous page offset it had when first faulted. Currently, anonymous folios belonging to CoW'd MAP_PRIVATE-mapped file-backed VMAs are tracked by their file offsets. By adding anonymous offset as a property of VMAs, we can now track them by their anonymous page offset instead. By tracking this, we provide the means by which to eliminate this inconsistency, and more importantly lay the foundations for future work for the scalable CoW anonymous rmap rework. This patch simply adds the fields and some simple helpers. Subsequent patches will update mm code to make use of these fields correctly. The fields chosen are packed in the VMA such that, for 64-bit kernel builds, no additional space is taken up. The first field is present on cacheline 0 containing key VMA fields, and the second on cacheline 3, which contains file-backed reverse mapping fields. Given the relative time spent accessing reverse mapping fields as well as updating them, there shouldn't be any performance impact here from false sharing. Update the VMA userland tests to account for this change. No callsites are updated yet, so no functional change intended. Link: https://lore.kernel.org/20260813-b4-scalable-cow-virt-pgoff-v5-0-c21581c0c3c8@kernel.org Link: https://lore.kernel.org/20260813-b4-scalable-cow-virt-pgoff-v5-1-c21581c0c3c8@kernel.org Signed-off-by: Lorenzo Stoakes (ARM) Acked-by: David Hildenbrand (Arm) Reviewed-by: Gregory Price (Meta) Reviewed-by: Xu Xin Cc: Adrian Hunter Cc: Alexander Deucher Cc: Alexander Gordeev Cc: Alexander Shishkin Cc: Alistair Popple Cc: Arnaldo Carvalho de Melo Cc: Arnd Bergmann Cc: Baolin Wang Cc: Baoquan He Cc: Barry Song Cc: Boris Brezillon Cc: Byungchul Park Cc: Chengming Zhou Cc: Chris Li Cc: Christan König Cc: Christian Borntraeger Cc: Claudio Imbrenda Cc: Dave Airlie Cc: Dev Jain Cc: Gerald Schaefer Cc: Greg Kroah-Hartman Cc: Harry Yoo Cc: Heiko Carstens Cc: Huang Ray Cc: "Huang, Ying" Cc: Ian Rogers Cc: Ingo Molnar Cc: James Clark Cc: Jan Kara Cc: Jann Horn Cc: Janosch Frank Cc: Jason Gunthorpe Cc: Jiri Olsa Cc: John Hubbard Cc: Joshua Hahn Cc: Kairui Song Cc: Kees Cook Cc: Kemeng Shi Cc: Lance Yang Cc: Liam R. Howlett Cc: Liviu Dudau Cc: Maarten Lankhorst Cc: Marc Rutland Cc: "Masami Hiramatsu (Google)" Cc: Matthew Auld Cc: Matthew Brost Cc: Matthew Wilcox (Oracle) Cc: Maxime Ripard Cc: Miaohe Lin Cc: Michal Hocko Cc: Mike Rapoport Cc: Muchun Song Cc: Namhyung kim Cc: Naoya Horiguchi Cc: Nhat Pham Cc: Nico Pache Cc: Oleg Nesterov Cc: Oscar Salvador Cc: Pedro Falcato Cc: Peter Xu Cc: Peter Zijlstra Cc: Rakie Kim Cc: Rik van Riel Cc: Rodrigo Vivi Cc: Ryan Roberts Cc: Steven Price Cc: Suren Baghdasaryan Cc: Sven Schnelle Cc: Thomas Hellström Cc: Thomas Zimemrmann Cc: Vasily Gorbik Cc: Vlastimil Babka Cc: xu xin Cc: Zi Yan Signed-off-by: Andrew Morton --- include/linux/mm.h | 59 +++++++++++++++++++++++++++++++++ include/linux/mm_types.h | 12 +++++++ mm/vma.h | 14 ++++++++ mm/vma_init.c | 1 + tools/testing/vma/include/dup.h | 26 +++++++++++++++ 5 files changed, 112 insertions(+) diff --git a/include/linux/mm.h b/include/linux/mm.h index 87feaa5a2b78..df78847f5f07 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -4393,6 +4393,65 @@ static inline pgoff_t vma_last_pgoff(const struct vm_area_struct *vma) return vma_end_pgoff(vma) - 1; } +/** + * vma_start_anon_pgoff() - Get the anonymous page offset of the start of @vma + * @vma: The VMA whose anonymous page offset is required. + * + * If unfaulted, then this is vma->vm_start >> PAGE_SHIFT, if faulted then the + * anonymous page offset at the time of first fault. + * + * If the VMA is anonymous, this returns the same value as vma_start_pgoff(). + * + * This value is used for tracking MAP_PRIVATE file-backed mappings by their + * anonymous page offset. + * + * Returns: The anonymous page offset of the start of @vma. + */ +static inline pgoff_t vma_start_anon_pgoff(const struct vm_area_struct *vma) +{ + pgoff_t pgoff = 0; + +#ifdef CONFIG_64BIT + pgoff += vma->__vm_anon_pgoff_hi; + pgoff <<= 32; +#endif + pgoff += vma->__vm_anon_pgoff_lo; + return pgoff; +} + +/** + * vma_end_anon_pgoff() - Get the anonymous page offset of the exclusive end of + * @vma. + * @vma: The VMA whose end anonymous page offset is required. + * + * This returns the anonymous exclusive end page offset of @vma, which is useful + * for expressing page offset ranges. + * + * See the description of vma_start_anon_pgoff() for a description of VMA + * anonymous page offsets. + * + * Returns: The exclusive end anonymous page offset of @vma. + */ +static inline pgoff_t vma_end_anon_pgoff(const struct vm_area_struct *vma) +{ + return vma_start_anon_pgoff(vma) + vma_pages(vma); +} + +/** + * vma_last_anon_pgoff() - Get the anonymous page offset of the last page in + * @vma. + * @vma: The VMA whose last anonymous page offset is required. + * + * See the description of vma_start_anon_pgoff() for a description of VMA + * anonymous page offsets. + * + * Returns: The last anonymous page offset of @vma. + */ +static inline pgoff_t vma_last_anon_pgoff(const struct vm_area_struct *vma) +{ + return vma_end_anon_pgoff(vma) - 1; +} + static inline unsigned long vma_desc_size(const struct vm_area_desc *desc) { return desc->end - desc->start; diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h index 939b5ea8c9e0..ebf0d912be7d 100644 --- a/include/linux/mm_types.h +++ b/include/linux/mm_types.h @@ -967,6 +967,11 @@ struct vm_area_struct { */ unsigned int vm_lock_seq; #endif + /* + * Low 32-bits of anonymous page offset. + * See vma_start_anon_pgoff() comment for details. + */ + unsigned int __vm_anon_pgoff_lo; /* * A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma * list, after a COW of one of the file pages. A MAP_SHARED vma @@ -1041,6 +1046,13 @@ struct vm_area_struct { #ifdef CONFIG_DEBUG_LOCK_ALLOC struct lockdep_map vmlock_dep_map; #endif +#endif +#ifdef CONFIG_64BIT + /* + * High 32-bits of anonymous page offset. + * See vma_start_anon_pgoff() comment for details. + */ + unsigned int __vm_anon_pgoff_hi; #endif /* * For areas with an address space and backing store, diff --git a/mm/vma.h b/mm/vma.h index 0bc7d521e976..54ed7c744e3b 100644 --- a/mm/vma.h +++ b/mm/vma.h @@ -283,6 +283,20 @@ static inline void vma_set_pgoff(struct vm_area_struct *vma, pgoff_t pgoff) vma->vm_pgoff = pgoff; } +static inline void __vma_set_anon_pgoff(struct vm_area_struct *vma, pgoff_t pgoff) +{ +#ifdef CONFIG_64BIT + vma->__vm_anon_pgoff_hi = pgoff >> 32; +#endif + vma->__vm_anon_pgoff_lo = pgoff & GENMASK(31, 0); +} + +static inline void vma_set_anon_pgoff(struct vm_area_struct *vma, pgoff_t pgoff) +{ + vma_assert_can_modify(vma); + __vma_set_anon_pgoff(vma, pgoff); +} + static inline void vma_add_pgoff(struct vm_area_struct *vma, pgoff_t delta) { vma_assert_can_modify(vma); diff --git a/mm/vma_init.c b/mm/vma_init.c index 715feee283f0..baa7e82f47e3 100644 --- a/mm/vma_init.c +++ b/mm/vma_init.c @@ -51,6 +51,7 @@ static void vm_area_init_from(const struct vm_area_struct *src, dest->vm_end = src->vm_end; dest->anon_vma = src->anon_vma; dest->vm_pgoff = vma_start_pgoff(src); + __vma_set_anon_pgoff(dest, vma_start_anon_pgoff(src)); dest->vm_file = src->vm_file; dest->vm_private_data = src->vm_private_data; vm_flags_init(dest, src->vm_flags); diff --git a/tools/testing/vma/include/dup.h b/tools/testing/vma/include/dup.h index cdeb53bbdd1b..17f94e5de569 100644 --- a/tools/testing/vma/include/dup.h +++ b/tools/testing/vma/include/dup.h @@ -577,6 +577,7 @@ struct vm_area_struct { */ unsigned int vm_lock_seq; #endif + unsigned int __vm_anon_pgoff_lo; /* * A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma @@ -612,6 +613,9 @@ struct vm_area_struct { #ifdef CONFIG_PER_VMA_LOCK /* Unstable RCU readers are allowed to read this. */ refcount_t vm_refcnt; +#endif +#ifdef CONFIG_64BIT + unsigned int __vm_anon_pgoff_hi; #endif /* * For areas with an address space and backing store, @@ -1320,6 +1324,28 @@ static inline pgoff_t vma_end_pgoff(const struct vm_area_struct *vma) return vma_start_pgoff(vma) + vma_pages(vma); } +static inline pgoff_t vma_start_anon_pgoff(const struct vm_area_struct *vma) +{ + pgoff_t pgoff = 0; + +#ifdef CONFIG_64BIT + pgoff += vma->__vm_anon_pgoff_hi; + pgoff <<= 32; +#endif + pgoff += vma->__vm_anon_pgoff_lo; + return pgoff; +} + +static inline pgoff_t vma_end_anon_pgoff(const struct vm_area_struct *vma) +{ + return vma_start_anon_pgoff(vma) + vma_pages(vma); +} + +static inline pgoff_t vma_last_anon_pgoff(const struct vm_area_struct *vma) +{ + return vma_end_anon_pgoff(vma) - 1; +} + static inline int vfs_mmap_prepare(struct file *file, struct vm_area_desc *desc) { return file->f_op->mmap_prepare(desc); From 51943a18ad4bd6ff8baea2da7b8cce2f86f1a959 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Thu, 13 Aug 2026 18:32:19 +0100 Subject: [PATCH 0965/1328] mm: provide vma_[flags_]is_cow_mapping() and remove is_cow_mapping() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All remaining callers of is_cow_mapping() are invoking it in the form of is_cow_mapping(vma->vm_flags) or an indirected version of this. Therefore, provide a helper - vma_is_cow_mapping() to directly test the VMA. Additionally provide a new helper vma_flags_is_cow_mapping() which performs the check using the new vma_flags_t type, and share this logic between vma_is_cow_mapping() and vma_desc_is_cow_mapping(). With these changes, no callers of is_cow_mapping() remain, so remove it. Also update the userland VMA tests to reflect the change. No functional change intended. [akpm@linux-foundation.org: fix kerneldoc comment typo, per Lorenzo] Link: https://lore.kernel.org/aob1goSSPH6sTN9y@gremlin Link: https://lore.kernel.org/20260813-b4-scalable-cow-virt-pgoff-v5-2-c21581c0c3c8@kernel.org Signed-off-by: Lorenzo Stoakes (ARM) Acked-by: David Hildenbrand (Arm) Cc: Adrian Hunter Cc: Alexander Deucher Cc: Alexander Gordeev Cc: Alexander Shishkin Cc: Alistair Popple Cc: Arnaldo Carvalho de Melo Cc: Arnd Bergmann Cc: Baolin Wang Cc: Baoquan He Cc: Barry Song Cc: Boris Brezillon Cc: Byungchul Park Cc: Chengming Zhou Cc: Chris Li Cc: Christan König Cc: Christian Borntraeger Cc: Claudio Imbrenda Cc: Dave Airlie Cc: Dev Jain Cc: Gerald Schaefer Cc: Greg Kroah-Hartman Cc: Gregory Price (Meta) Cc: Harry Yoo Cc: Heiko Carstens Cc: Huang Ray Cc: "Huang, Ying" Cc: Ian Rogers Cc: Ingo Molnar Cc: James Clark Cc: Jan Kara Cc: Jann Horn Cc: Janosch Frank Cc: Jason Gunthorpe Cc: Jiri Olsa Cc: John Hubbard Cc: Joshua Hahn Cc: Kairui Song Cc: Kees Cook Cc: Kemeng Shi Cc: Lance Yang Cc: Liam R. Howlett Cc: Liviu Dudau Cc: Maarten Lankhorst Cc: Marc Rutland Cc: "Masami Hiramatsu (Google)" Cc: Matthew Auld Cc: Matthew Brost Cc: Matthew Wilcox (Oracle) Cc: Maxime Ripard Cc: Miaohe Lin Cc: Michal Hocko Cc: Mike Rapoport Cc: Muchun Song Cc: Namhyung kim Cc: Naoya Horiguchi Cc: Nhat Pham Cc: Nico Pache Cc: Oleg Nesterov Cc: Oscar Salvador Cc: Pedro Falcato Cc: Peter Xu Cc: Peter Zijlstra Cc: Rakie Kim Cc: Rik van Riel Cc: Rodrigo Vivi Cc: Ryan Roberts Cc: Steven Price Cc: Suren Baghdasaryan Cc: Sven Schnelle Cc: Thomas Hellström Cc: Thomas Zimemrmann Cc: Vasily Gorbik Cc: Vlastimil Babka Cc: xu xin Cc: Zi Yan Signed-off-by: Andrew Morton --- arch/s390/mm/gmap_helpers.c | 2 +- drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c | 4 +- drivers/gpu/drm/drm_gem_shmem_helper.c | 2 +- drivers/gpu/drm/panthor/panthor_gem.c | 2 +- drivers/gpu/drm/ttm/ttm_bo_vm.c | 2 +- drivers/gpu/drm/xe/xe_device.c | 2 +- fs/proc/task_mmu.c | 2 +- include/linux/mm.h | 75 ++++++++++++++++++++++--- kernel/events/uprobes.c | 2 +- mm/gup.c | 2 +- mm/huge_memory.c | 8 +-- mm/hugetlb.c | 2 +- mm/internal.h | 2 +- mm/memory.c | 25 ++++----- mm/mempolicy.c | 2 +- tools/testing/vma/include/dup.h | 11 ++++ 16 files changed, 107 insertions(+), 38 deletions(-) diff --git a/arch/s390/mm/gmap_helpers.c b/arch/s390/mm/gmap_helpers.c index 4bf7c9012feb..cd5fded159c0 100644 --- a/arch/s390/mm/gmap_helpers.c +++ b/arch/s390/mm/gmap_helpers.c @@ -200,7 +200,7 @@ static int find_zeropage_pte_entry(pte_t *pte, unsigned long addr, * currently only works in COW mappings, which is also where * mm_forbids_zeropage() is checked. */ - if (!is_cow_mapping(walk->vma->vm_flags)) + if (!vma_is_cow_mapping(walk->vma)) return -EFAULT; *found_addr = addr; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c index 6a0699746fbc..0c7309080a7a 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c @@ -377,9 +377,9 @@ static int amdgpu_gem_object_mmap(struct drm_gem_object *obj, struct vm_area_str /* Workaround for Thunk bug creating PROT_NONE,MAP_PRIVATE mappings * for debugger access to invisible VRAM. Should have used MAP_SHARED * instead. Clearing VM_MAYWRITE prevents the mapping from ever - * becoming writable and makes is_cow_mapping(vm_flags) false. + * becoming writable and makes vma_is_cow_mapping(vma) false. */ - if (is_cow_mapping(vma->vm_flags) && + if (vma_is_cow_mapping(vma) && !(vma->vm_flags & VM_ACCESS_FLAGS)) vm_flags_clear(vma, VM_MAYWRITE); diff --git a/drivers/gpu/drm/drm_gem_shmem_helper.c b/drivers/gpu/drm/drm_gem_shmem_helper.c index 06d019d51d3e..177d0e0b9334 100644 --- a/drivers/gpu/drm/drm_gem_shmem_helper.c +++ b/drivers/gpu/drm/drm_gem_shmem_helper.c @@ -753,7 +753,7 @@ int drm_gem_shmem_mmap(struct drm_gem_shmem_object *shmem, struct vm_area_struct return ret; } - if (is_cow_mapping(vma->vm_flags)) + if (vma_is_cow_mapping(vma)) return -EINVAL; dma_resv_lock(shmem->base.resv, NULL); diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c index 770556353968..d2eec46f7abe 100644 --- a/drivers/gpu/drm/panthor/panthor_gem.c +++ b/drivers/gpu/drm/panthor/panthor_gem.c @@ -761,7 +761,7 @@ static int panthor_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *v return ret; } - if (is_cow_mapping(vma->vm_flags)) + if (vma_is_cow_mapping(vma)) return -EINVAL; if (!refcount_inc_not_zero(&bo->cmap.mmap_count)) { diff --git a/drivers/gpu/drm/ttm/ttm_bo_vm.c b/drivers/gpu/drm/ttm/ttm_bo_vm.c index 88babf435ac2..872bf444b1f0 100644 --- a/drivers/gpu/drm/ttm/ttm_bo_vm.c +++ b/drivers/gpu/drm/ttm/ttm_bo_vm.c @@ -489,7 +489,7 @@ static const struct vm_operations_struct ttm_bo_vm_ops = { int ttm_bo_mmap_obj(struct vm_area_struct *vma, struct ttm_buffer_object *bo) { /* Enforce no COW since would have really strange behavior with it. */ - if (is_cow_mapping(vma->vm_flags)) + if (vma_is_cow_mapping(vma)) return -EINVAL; drm_gem_object_get(&bo->base); diff --git a/drivers/gpu/drm/xe/xe_device.c b/drivers/gpu/drm/xe/xe_device.c index 9d119c95a569..de5fdf49d729 100644 --- a/drivers/gpu/drm/xe/xe_device.c +++ b/drivers/gpu/drm/xe/xe_device.c @@ -330,7 +330,7 @@ static int xe_pci_barrier_mmap(struct file *filp, if (vma->vm_end - vma->vm_start > SZ_4K) return -EINVAL; - if (is_cow_mapping(vma->vm_flags)) + if (vma_is_cow_mapping(vma)) return -EINVAL; if (vma->vm_flags & (VM_READ | VM_EXEC)) diff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c index 817e3e0f9194..5c54aebe2118 100644 --- a/fs/proc/task_mmu.c +++ b/fs/proc/task_mmu.c @@ -1693,7 +1693,7 @@ static inline bool pte_is_pinned(struct vm_area_struct *vma, unsigned long addr, if (!pte_write(pte)) return false; - if (!is_cow_mapping(vma->vm_flags)) + if (!vma_is_cow_mapping(vma)) return false; if (likely(!mm_flags_test(MMF_HAS_PINNED, vma->vm_mm))) return false; diff --git a/include/linux/mm.h b/include/linux/mm.h index df78847f5f07..20361b4344ea 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -2271,19 +2271,78 @@ void unpin_user_pages(struct page **pages, unsigned long npages); void unpin_user_folio(struct folio *folio, unsigned long npages); void unpin_folios(struct folio **folios, unsigned long nfolios); -static inline bool is_cow_mapping(vm_flags_t flags) +/** + * vma_flags_is_cow_mapping() - Do these VMA flags imply a CoW mapping? + * @flags: The VMA flags to check. + * + * Mappings which could be CoW'd (subject to Copy-On-Write faults) are + * described as CoW mappings. + * + * All mappings backed by anonymous folios (all anonymous mappings and most + * MAP_PRIVATE-file backed ranges) are CoW mappings. + * + * All other mappings (including all MAP_SHARED mappings) are non-CoW. + * + * The criteria are !VMA_SHARED_BIT, VMA_MAYWRITE_BIT. + * + * VMA_MAYWRITE_BIT is checked instead of VMA_WRITE_BIT to account for both + * future mprotect() calls which can render a read-only mapping writable, and + * GUP with FOLL_FORCE (e.g. ptrace) which can CoW a read-only mapping. + * + * - No anonymous mapping can ever clear VMA_MAYWRITE_BIT. + * + * - Writes to anonymous mappings do not immediately result in CoW faults but + * may do so after the process is forked or if a read is followed by a + * write. + * + * - Writes to MAP_PRIVATE file-backed mappings result in CoW faults and may + * do so again after fork. + * + * - MAP_SHARED mappings of a file opened read-only are transformed into + * VMA_MAYSHARE_BIT, !VMA_SHARED_BIT, !VMA_MAYWRITE_BIT mappings, so remain + * non-CoW. + * + * - Drivers may clear VMA_MAYWRITE_BIT but do so at mmap() time and cannot + * mark themselves anonymous. Having cleared this flag it is not valid for + * them to leave the VMA_WRITE_BIT flag set. + * + * As a consequence, the anonymous reverse mapping only tracks CoW mappings. + * + * Returns: true if the flags indicate a CoW mapping, otherwise false. + */ +static inline bool vma_flags_is_cow_mapping(const vma_flags_t *flags) { - return (flags & (VM_SHARED | VM_MAYWRITE)) == VM_MAYWRITE; -} - -static inline bool vma_desc_is_cow_mapping(struct vm_area_desc *desc) -{ - const vma_flags_t *flags = &desc->vma_flags; - return vma_flags_test(flags, VMA_MAYWRITE_BIT) && !vma_flags_test(flags, VMA_SHARED_BIT); } +/** + * vma_is_cow_mapping() - Is this VMA a CoW mapping? + * @vma: The VMA to check. + * + * See vma_flags_is_cow_mapping() for details. + * + * Returns: true if the VMA is a CoW mapping, otherwise false. + */ +static inline bool vma_is_cow_mapping(const struct vm_area_struct *vma) +{ + return vma_flags_is_cow_mapping(&vma->flags); +} + +/** + * vma_desc_is_cow_mapping() - Is this VMA descriptor a CoW mapping? + * @desc: The VMA descriptor to check. + * + * See vma_flags_is_cow_mapping() for details. + * + * Returns: true if the VMA descriptor describes a CoW mapping, otherwise + * false. + */ +static inline bool vma_desc_is_cow_mapping(struct vm_area_desc *desc) +{ + return vma_flags_is_cow_mapping(&desc->vma_flags); +} + #ifndef CONFIG_MMU static inline bool is_nommu_shared_mapping(vm_flags_t flags) { diff --git a/kernel/events/uprobes.c b/kernel/events/uprobes.c index ae2f3b9f8d50..eb0d11092fb3 100644 --- a/kernel/events/uprobes.c +++ b/kernel/events/uprobes.c @@ -513,7 +513,7 @@ int uprobe_write(struct arch_uprobe *auprobe, struct vm_area_struct *vma, uprobe = container_of(auprobe, struct uprobe, arch); - if (WARN_ON_ONCE(!is_cow_mapping(vma->vm_flags))) + if (WARN_ON_ONCE(!vma_is_cow_mapping(vma))) return -EINVAL; /* diff --git a/mm/gup.c b/mm/gup.c index 99902c15703b..8ea3de60e82d 100644 --- a/mm/gup.c +++ b/mm/gup.c @@ -1236,7 +1236,7 @@ static int check_vma_flags(struct vm_area_struct *vma, unsigned long gup_flags) * Anon pages in shared mappings are surprising: now * just reject it. */ - if (!is_cow_mapping(vm_flags)) + if (!vma_is_cow_mapping(vma)) return -EFAULT; } } else if (!(vm_flags & VM_READ)) { diff --git a/mm/huge_memory.c b/mm/huge_memory.c index 644d6905b49c..ff13b57d9d56 100644 --- a/mm/huge_memory.c +++ b/mm/huge_memory.c @@ -1681,7 +1681,7 @@ vm_fault_t vmf_insert_pfn_pmd(struct vm_fault *vmf, unsigned long pfn, BUG_ON(!(vma->vm_flags & (VM_PFNMAP|VM_MIXEDMAP))); BUG_ON((vma->vm_flags & (VM_PFNMAP|VM_MIXEDMAP)) == (VM_PFNMAP|VM_MIXEDMAP)); - BUG_ON((vma->vm_flags & VM_PFNMAP) && is_cow_mapping(vma->vm_flags)); + BUG_ON((vma->vm_flags & VM_PFNMAP) && vma_is_cow_mapping(vma)); pfnmap_setup_cachemode_pfn(pfn, &pgprot); @@ -1789,7 +1789,7 @@ vm_fault_t vmf_insert_pfn_pud(struct vm_fault *vmf, unsigned long pfn, BUG_ON(!(vma->vm_flags & (VM_PFNMAP|VM_MIXEDMAP))); BUG_ON((vma->vm_flags & (VM_PFNMAP|VM_MIXEDMAP)) == (VM_PFNMAP|VM_MIXEDMAP)); - BUG_ON((vma->vm_flags & VM_PFNMAP) && is_cow_mapping(vma->vm_flags)); + BUG_ON((vma->vm_flags & VM_PFNMAP) && vma_is_cow_mapping(vma)); pfnmap_setup_cachemode_pfn(pfn, &pgprot); @@ -1931,7 +1931,7 @@ int copy_huge_pmd(struct mm_struct *dst_mm, struct mm_struct *src_mm, * applied special bit, or we made the PRIVATE mapping be * able to wrongly write to the backend MMIO. */ - VM_WARN_ON_ONCE(is_cow_mapping(src_vma->vm_flags) && pmd_write(pmd)); + VM_WARN_ON_ONCE(vma_is_cow_mapping(src_vma) && pmd_write(pmd)); goto set_pmd; } @@ -2052,7 +2052,7 @@ int copy_huge_pud(struct mm_struct *dst_mm, struct mm_struct *src_mm, * TODO: once we support anonymous pages, use * folio_try_dup_anon_rmap_*() and split if duplicating fails. */ - if (is_cow_mapping(vma->vm_flags) && pud_write(pud)) { + if (vma_is_cow_mapping(vma) && pud_write(pud)) { pudp_set_wrprotect(src_mm, addr, src_pud); pud = pud_wrprotect(pud); } diff --git a/mm/hugetlb.c b/mm/hugetlb.c index 79e5c3b3e850..49bf325325c0 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -4898,7 +4898,7 @@ int copy_hugetlb_page_range(struct mm_struct *dst, struct mm_struct *src, pte_t *src_pte, *dst_pte, entry; struct folio *pte_folio; unsigned long addr; - bool cow = is_cow_mapping(src_vma->vm_flags); + bool cow = vma_is_cow_mapping(src_vma); struct hstate *h = hstate_vma(src_vma); unsigned long sz = huge_page_size(h); unsigned long npages = pages_per_huge_page(h); diff --git a/mm/internal.h b/mm/internal.h index 68db5abd0a4c..a75e7641ef49 100644 --- a/mm/internal.h +++ b/mm/internal.h @@ -1353,7 +1353,7 @@ static inline bool gup_must_unshare(struct vm_area_struct *vma, * ... because we only care about writable private ("COW") * mappings where we have to break COW early. */ - return is_cow_mapping(vma->vm_flags); + return vma_is_cow_mapping(vma); } /* Paired with a memory barrier in folio_try_share_anon_rmap_*(). */ diff --git a/mm/memory.c b/mm/memory.c index d2f14ba2261c..396d7b9059e6 100644 --- a/mm/memory.c +++ b/mm/memory.c @@ -780,7 +780,7 @@ static inline struct page *__vm_normal_page(struct vm_area_struct *vma, /* Only CoW'ed anon folios are "normal". */ if (pfn == index) return NULL; - if (!is_cow_mapping(vma->vm_flags)) + if (!vma_is_cow_mapping(vma)) return NULL; } } @@ -1002,7 +1002,6 @@ copy_nonpresent_pte(struct mm_struct *dst_mm, struct mm_struct *src_mm, pte_t *dst_pte, pte_t *src_pte, struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma, unsigned long addr, int *rss) { - vm_flags_t vm_flags = dst_vma->vm_flags; pte_t orig_pte = ptep_get(src_pte); softleaf_t entry = softleaf_from_pte(orig_pte); pte_t pte = orig_pte; @@ -1026,7 +1025,7 @@ copy_nonpresent_pte(struct mm_struct *dst_mm, struct mm_struct *src_mm, rss[mm_counter(folio)]++; if (!softleaf_is_migration_read(entry) && - is_cow_mapping(vm_flags)) { + vma_is_cow_mapping(dst_vma)) { /* * COW mappings require pages in both parent and child * to be set to read. A previously exclusive entry is @@ -1067,7 +1066,7 @@ copy_nonpresent_pte(struct mm_struct *dst_mm, struct mm_struct *src_mm, * save and restore device driver state). */ if (softleaf_is_device_private_write(entry) && - is_cow_mapping(vm_flags)) { + vma_is_cow_mapping(dst_vma)) { entry = make_readable_device_private_entry( swp_offset(entry)); pte = swp_entry_to_pte(entry); @@ -1082,7 +1081,7 @@ copy_nonpresent_pte(struct mm_struct *dst_mm, struct mm_struct *src_mm, * exclusive entries currently only support private writable * (ie. COW) mappings. */ - VM_BUG_ON(!is_cow_mapping(src_vma->vm_flags)); + VM_BUG_ON(!vma_is_cow_mapping(src_vma)); if (try_restore_exclusive_pte(src_vma, addr, src_pte, orig_pte)) return -EBUSY; return -ENOENT; @@ -1181,7 +1180,7 @@ static __always_inline void __copy_present_ptes(struct vm_area_struct *dst_vma, } /* If it's a COW mapping, write protect it both processes. */ - if (is_cow_mapping(src_vma->vm_flags) && writable) { + if (vma_is_cow_mapping(src_vma) && writable) { wrprotect_ptes(src_mm, addr, src_pte, nr); pte = pte_wrprotect(pte); } @@ -1602,9 +1601,9 @@ copy_page_range(struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma) * We need to invalidate the secondary MMU mappings only when * there could be a permission downgrade on the ptes of the * parent mm. And a permission downgrade will only happen if - * is_cow_mapping() returns true. + * vma_is_cow_mapping() returns true. */ - is_cow = is_cow_mapping(src_vma->vm_flags); + is_cow = vma_is_cow_mapping(src_vma); if (is_cow) { mmu_notifier_range_init(&range, MMU_NOTIFY_PROTECTION_PAGE, @@ -2437,7 +2436,7 @@ static bool vm_mixed_zeropage_allowed(struct vm_area_struct *vma) if (mm_forbids_zeropage(vma->vm_mm)) return false; /* zeropages in COW mappings are common and unproblematic. */ - if (is_cow_mapping(vma->vm_flags)) + if (vma_is_cow_mapping(vma)) return true; /* Mappings that do not allow for writable PTEs are unproblematic. */ if (!(vma->vm_flags & (VM_WRITE | VM_MAYWRITE))) @@ -2888,7 +2887,7 @@ vm_fault_t vmf_insert_pfn_prot(struct vm_area_struct *vma, unsigned long addr, BUG_ON(!(vma->vm_flags & (VM_PFNMAP|VM_MIXEDMAP))); BUG_ON((vma->vm_flags & (VM_PFNMAP|VM_MIXEDMAP)) == (VM_PFNMAP|VM_MIXEDMAP)); - BUG_ON((vma->vm_flags & VM_PFNMAP) && is_cow_mapping(vma->vm_flags)); + BUG_ON((vma->vm_flags & VM_PFNMAP) && vma_is_cow_mapping(vma)); BUG_ON((vma->vm_flags & VM_MIXEDMAP) && pfn_valid(pfn)); if (addr < vma->vm_start || addr >= vma->vm_end) @@ -3300,7 +3299,7 @@ static int remap_pfn_range_prepare_vma(struct vm_area_struct *vma, unsigned long size) { const unsigned long end = addr + PAGE_ALIGN(size); - const bool is_cow = is_cow_mapping(vma->vm_flags); + const bool is_cow = vma_is_cow_mapping(vma); int err; err = get_remap_pgoff(is_cow, addr, end, vma->vm_start, vma->vm_end, @@ -6800,7 +6799,7 @@ static vm_fault_t sanitize_fault_flags(struct vm_area_struct *vma, * FAULT_FLAG_UNSHARE only applies to COW mappings. Let's * just treat it like an ordinary read-fault otherwise. */ - if (!is_cow_mapping(vma->vm_flags)) + if (!vma_is_cow_mapping(vma)) *flags &= ~FAULT_FLAG_UNSHARE; } else if (*flags & FAULT_FLAG_WRITE) { /* Write faults on read-only mappings are impossible ... */ @@ -6808,7 +6807,7 @@ static vm_fault_t sanitize_fault_flags(struct vm_area_struct *vma, return VM_FAULT_SIGSEGV; /* ... and FOLL_FORCE only applies to COW mappings. */ if (WARN_ON_ONCE(!(vma->vm_flags & VM_WRITE) && - !is_cow_mapping(vma->vm_flags))) + !vma_is_cow_mapping(vma))) return VM_FAULT_SIGSEGV; } #ifdef CONFIG_PER_VMA_LOCK diff --git a/mm/mempolicy.c b/mm/mempolicy.c index 5720f7f54d94..3498a5651d50 100644 --- a/mm/mempolicy.c +++ b/mm/mempolicy.c @@ -844,7 +844,7 @@ bool folio_can_map_prot_numa(struct folio *folio, struct vm_area_struct *vma, return false; /* Also skip shared copy-on-write folios */ - if (is_cow_mapping(vma->vm_flags) && folio_maybe_mapped_shared(folio)) + if (vma_is_cow_mapping(vma) && folio_maybe_mapped_shared(folio)) return false; /* Folios are pinned and can't be migrated */ diff --git a/tools/testing/vma/include/dup.h b/tools/testing/vma/include/dup.h index 17f94e5de569..af2fd3f607b5 100644 --- a/tools/testing/vma/include/dup.h +++ b/tools/testing/vma/include/dup.h @@ -1162,6 +1162,17 @@ static inline bool vma_is_shared_maywrite(struct vm_area_struct *vma) return is_shared_maywrite(&vma->flags); } +static inline bool vma_flags_is_cow_mapping(const vma_flags_t *flags) +{ + return vma_flags_test(flags, VMA_MAYWRITE_BIT) && + !vma_flags_test(flags, VMA_SHARED_BIT); +} + +static inline bool vma_is_cow_mapping(const struct vm_area_struct *vma) +{ + return vma_flags_is_cow_mapping(&vma->flags); +} + static inline struct vm_area_struct *vma_next(struct vma_iterator *vmi) { /* From 7e6543d1f939eaaca008c13395e52cbd07605cb0 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Thu, 13 Aug 2026 18:32:20 +0100 Subject: [PATCH 0966/1328] mm: introduce linear_anon_page_index() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This function provides the anonymous equivalent of linear_page_index(), instead offsetting based on the anonymous page offset of the VMA. It is valid only for anonymous or MAP_PRIVATE file-backed mappings, in other words CoW mappings. For pure anon VMAs, this will be equal to linear_page_index(). Assert that both of these invariants are true in linear_anon_page_index() and implement the algorithm in __linear_anon_page_index(). Note that MAP_PRIVATE-/dev/zero mappings will satisfy vma_is_anonymous() but not fulfill this invariant, so when asserting this we check vma->vm_file to account for this. We do not update callsites yet, so no functional change intended. Also const-ify vma_is_anonymous() to make it compatible with the const-ified linear_anon_page_index(). While we're here, update linear_page_index() to be more succinct. VMA userland tests are also updated accordingly. Link: https://lore.kernel.org/20260813-b4-scalable-cow-virt-pgoff-v5-3-c21581c0c3c8@kernel.org Signed-off-by: Lorenzo Stoakes (ARM) Reviewed-by: Gregory Price (Meta) Acked-by: David Hildenbrand (Arm) Cc: Adrian Hunter Cc: Alexander Deucher Cc: Alexander Gordeev Cc: Alexander Shishkin Cc: Alistair Popple Cc: Arnaldo Carvalho de Melo Cc: Arnd Bergmann Cc: Baolin Wang Cc: Baoquan He Cc: Barry Song Cc: Boris Brezillon Cc: Byungchul Park Cc: Chengming Zhou Cc: Chris Li Cc: Christan König Cc: Christian Borntraeger Cc: Claudio Imbrenda Cc: Dave Airlie Cc: Dev Jain Cc: Gerald Schaefer Cc: Greg Kroah-Hartman Cc: Harry Yoo Cc: Heiko Carstens Cc: Huang Ray Cc: "Huang, Ying" Cc: Ian Rogers Cc: Ingo Molnar Cc: James Clark Cc: Jan Kara Cc: Jann Horn Cc: Janosch Frank Cc: Jason Gunthorpe Cc: Jiri Olsa Cc: John Hubbard Cc: Joshua Hahn Cc: Kairui Song Cc: Kees Cook Cc: Kemeng Shi Cc: Lance Yang Cc: Liam R. Howlett Cc: Liviu Dudau Cc: Maarten Lankhorst Cc: Marc Rutland Cc: "Masami Hiramatsu (Google)" Cc: Matthew Auld Cc: Matthew Brost Cc: Matthew Wilcox (Oracle) Cc: Maxime Ripard Cc: Miaohe Lin Cc: Michal Hocko Cc: Mike Rapoport Cc: Muchun Song Cc: Namhyung kim Cc: Naoya Horiguchi Cc: Nhat Pham Cc: Nico Pache Cc: Oleg Nesterov Cc: Oscar Salvador Cc: Pedro Falcato Cc: Peter Xu Cc: Peter Zijlstra Cc: Rakie Kim Cc: Rik van Riel Cc: Rodrigo Vivi Cc: Ryan Roberts Cc: Steven Price Cc: Suren Baghdasaryan Cc: Sven Schnelle Cc: Thomas Hellström Cc: Thomas Zimemrmann Cc: Vasily Gorbik Cc: Vlastimil Babka Cc: xu xin Cc: Zi Yan Signed-off-by: Andrew Morton --- include/linux/mm.h | 2 +- include/linux/pagemap.h | 40 ++++++++++++++++++++++++++++++--- tools/testing/vma/include/dup.h | 25 ++++++++++++++++++++- 3 files changed, 62 insertions(+), 5 deletions(-) diff --git a/include/linux/mm.h b/include/linux/mm.h index 20361b4344ea..dd09c438fa23 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -1556,7 +1556,7 @@ static inline void vma_desc_set_anonymous(struct vm_area_desc *desc) desc->vm_ops = NULL; } -static inline bool vma_is_anonymous(struct vm_area_struct *vma) +static inline bool vma_is_anonymous(const struct vm_area_struct *vma) { return !vma->vm_ops; } diff --git a/include/linux/pagemap.h b/include/linux/pagemap.h index c6fc783aaee5..0adfa6605653 100644 --- a/include/linux/pagemap.h +++ b/include/linux/pagemap.h @@ -1094,10 +1094,44 @@ static inline pgoff_t linear_page_delta(const struct vm_area_struct *vma, static inline pgoff_t linear_page_index(const struct vm_area_struct *vma, const unsigned long address) { - pgoff_t pgoff; + return linear_page_delta(vma, address) + vma_start_pgoff(vma); +} + +static inline pgoff_t __linear_anon_page_index(const struct vm_area_struct *vma, + const unsigned long address) +{ + return linear_page_delta(vma, address) + vma_start_anon_pgoff(vma); +} + +/** + * linear_anon_page_index() - Determine the absolute anonymous page offset of + * @address within @vma. + * @vma: An anonymous or MAP_PRIVATE file-backed VMA in which @address resides. + * @address: The address whose absolute page offset is required. + * + * This returns the anonymous page offset of @address, which is the page offset + * the address possessed at the time the VMA was first faulted. + * + * For anonymous mappings, this returns the same value as linear_page_index(). + * + * For MAP_PRIVATE file-backed mappings, this returns the anonymous page offset + * of @address, which is the page offset the address possessed at the time the + * VMA was first faulted. + * + * It is not valid to call this function for shared file-backed mappings. + * + * Returns: The absolute anonymous page offset of @address within @vma. + */ +static inline pgoff_t linear_anon_page_index(const struct vm_area_struct *vma, + const unsigned long address) +{ + const pgoff_t pgoff = __linear_anon_page_index(vma, address); + + VM_WARN_ON_ONCE(!vma_is_cow_mapping(vma)); + /* Account for MAP_PRIVATE-/dev/zero which is only semi-anonymous. */ + if (vma_is_anonymous(vma) && !vma->vm_file) + VM_WARN_ON_ONCE(pgoff != linear_page_index(vma, address)); - pgoff = linear_page_delta(vma, address); - pgoff += vma_start_pgoff(vma); return pgoff; } diff --git a/tools/testing/vma/include/dup.h b/tools/testing/vma/include/dup.h index af2fd3f607b5..4655aecffaf3 100644 --- a/tools/testing/vma/include/dup.h +++ b/tools/testing/vma/include/dup.h @@ -1428,7 +1428,7 @@ static inline void vma_iter_set(struct vma_iterator *vmi, unsigned long addr) mas_set(&vmi->mas, addr); } -static inline bool vma_is_anonymous(struct vm_area_struct *vma) +static inline bool vma_is_anonymous(const struct vm_area_struct *vma) { return !vma->vm_ops; } @@ -1621,3 +1621,26 @@ static inline pgprot_t vma_get_page_prot(const struct vm_area_struct *vma) { return vma_flags_to_page_prot(vma->flags); } + +static inline pgoff_t __linear_anon_page_index(const struct vm_area_struct *vma, + const unsigned long address) +{ + pgoff_t pgoff; + + pgoff = linear_page_delta(vma, address); + pgoff += vma_start_anon_pgoff(vma); + return pgoff; +} + +static inline pgoff_t linear_anon_page_index(const struct vm_area_struct *vma, + const unsigned long address) +{ + const pgoff_t pgoff = __linear_anon_page_index(vma, address); + + VM_WARN_ON_ONCE(!vma_is_cow_mapping(vma)); + /* Account for MAP_PRIVATE-/dev/zero which is only semi-anonymous. */ + if (vma_is_anonymous(vma) && !vma->vm_file) + VM_WARN_ON_ONCE(pgoff != linear_page_index(vma, address)); + + return pgoff; +} From c02fe674feaf3529b4d8c808c363aa1bcf955979 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Thu, 13 Aug 2026 18:32:21 +0100 Subject: [PATCH 0967/1328] mm: abstract vma_address() and introduce vma_anon_address() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce __vma_address() which abstracts the VMA start page offset field as pgoff_start, then update vma_address() to use it. Then introduce vma_anon_address() which does the equivalent of vma_address(), only using the anonymous page offset of the VMA rather than the file-backed one. Also add an assert to ensure that the function is not called for mappings which are file-backed but not MAP_PRIVATE to ensure it is only used in the correct places. This will be necessary for determining the address of a folio's index within a VMA when the folio belongs to a MAP_PRIVATE file-backed VMA but has been CoW'd, and thus is anonymous, once the anonymous VMA page offset field is used for the reverse mapping. No callers are updated, so no functional change intended. Link: https://lore.kernel.org/20260813-b4-scalable-cow-virt-pgoff-v5-4-c21581c0c3c8@kernel.org Signed-off-by: Lorenzo Stoakes (ARM) Acked-by: David Hildenbrand (Arm) Cc: Adrian Hunter Cc: Alexander Deucher Cc: Alexander Gordeev Cc: Alexander Shishkin Cc: Alistair Popple Cc: Arnaldo Carvalho de Melo Cc: Arnd Bergmann Cc: Baolin Wang Cc: Baoquan He Cc: Barry Song Cc: Boris Brezillon Cc: Byungchul Park Cc: Chengming Zhou Cc: Chris Li Cc: Christan König Cc: Christian Borntraeger Cc: Claudio Imbrenda Cc: Dave Airlie Cc: Dev Jain Cc: Gerald Schaefer Cc: Greg Kroah-Hartman Cc: Gregory Price (Meta) Cc: Harry Yoo Cc: Heiko Carstens Cc: Huang Ray Cc: "Huang, Ying" Cc: Ian Rogers Cc: Ingo Molnar Cc: James Clark Cc: Jan Kara Cc: Jann Horn Cc: Janosch Frank Cc: Jason Gunthorpe Cc: Jiri Olsa Cc: John Hubbard Cc: Joshua Hahn Cc: Kairui Song Cc: Kees Cook Cc: Kemeng Shi Cc: Lance Yang Cc: Liam R. Howlett Cc: Liviu Dudau Cc: Maarten Lankhorst Cc: Marc Rutland Cc: "Masami Hiramatsu (Google)" Cc: Matthew Auld Cc: Matthew Brost Cc: Matthew Wilcox (Oracle) Cc: Maxime Ripard Cc: Miaohe Lin Cc: Michal Hocko Cc: Mike Rapoport Cc: Muchun Song Cc: Namhyung kim Cc: Naoya Horiguchi Cc: Nhat Pham Cc: Nico Pache Cc: Oleg Nesterov Cc: Oscar Salvador Cc: Pedro Falcato Cc: Peter Xu Cc: Peter Zijlstra Cc: Rakie Kim Cc: Rik van Riel Cc: Rodrigo Vivi Cc: Ryan Roberts Cc: Steven Price Cc: Suren Baghdasaryan Cc: Sven Schnelle Cc: Thomas Hellström Cc: Thomas Zimemrmann Cc: Vasily Gorbik Cc: Vlastimil Babka Cc: xu xin Cc: Zi Yan Signed-off-by: Andrew Morton --- mm/internal.h | 49 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/mm/internal.h b/mm/internal.h index a75e7641ef49..761738d61fe0 100644 --- a/mm/internal.h +++ b/mm/internal.h @@ -1017,19 +1017,9 @@ void mlock_drain_remote(int cpu); extern pmd_t maybe_pmd_mkwrite(pmd_t pmd, struct vm_area_struct *vma); -/** - * vma_address - Find the virtual address a page range is mapped at - * @vma: The vma which maps this object. - * @pgoff: The page offset within its object. - * @nr_pages: The number of pages to consider. - * - * If any page in this range is mapped by this VMA, return the first address - * where any of these pages appear. Otherwise, return -EFAULT. - */ -static inline unsigned long vma_address(const struct vm_area_struct *vma, - pgoff_t pgoff, unsigned long nr_pages) +static inline unsigned long __vma_address(const struct vm_area_struct *vma, + pgoff_t pgoff, pgoff_t pgoff_start, unsigned long nr_pages) { - const pgoff_t pgoff_start = vma_start_pgoff(vma); unsigned long address; if (pgoff >= pgoff_start) { @@ -1047,6 +1037,41 @@ static inline unsigned long vma_address(const struct vm_area_struct *vma, return address; } +/** + * vma_address - Find the virtual address a page range is mapped at. + * @vma: The vma which maps this object. + * @pgoff: The page offset within its object. + * @nr_pages: The number of pages to consider. + * + * If any page in this range is mapped by this VMA, return the first address + * where any of these pages appear. Otherwise, return -EFAULT. + */ +static inline unsigned long vma_address(const struct vm_area_struct *vma, + pgoff_t pgoff, unsigned long nr_pages) +{ + return __vma_address(vma, pgoff, vma_start_pgoff(vma), nr_pages); +} + +/** + * vma_anon_address - Find the address an anonymous folio with index @pgoff_anon + * is mapped at. + * @vma: The vma which maps this object. + * @pgoff_anon: The anonymous page index belonging to the folio. + * @nr_pages: The number of pages to consider. + * + * This is only valid for anonymous or MAP_PRIVATE-mapped file-backed VMAs. + * + * Returns: If any page in this range is mapped by this VMA, return the first + * address where any of these pages appear. Otherwise, return -EFAULT. + */ +static inline unsigned long vma_anon_address(const struct vm_area_struct *vma, + pgoff_t pgoff_anon, unsigned long nr_pages) +{ + VM_WARN_ON_ONCE(!vma_is_cow_mapping(vma)); + + return __vma_address(vma, pgoff_anon, vma_start_anon_pgoff(vma), nr_pages); +} + /* * Then at what user virtual address will none of the range be found in vma? * Assumes that vma_address() already returned a good starting address. From 2a8de2d1d6a5f10251e89c77b547fd3243c8c3a8 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Thu, 13 Aug 2026 18:32:22 +0100 Subject: [PATCH 0968/1328] mm: update print_bad_page_map() to show anon index if appropriate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the VMA is a CoW mapping page offset may differ from anon page offset, indicating different positions in the relevant rmap trees. Update print_bad_page_map() to reflect that - if the mapping is non-CoW or the indexes match, then output only one index as before, otherwise output both with (file) or (anon) suffixes to reflect which is which. It's not possible to give only one index as there is no folio available to perform folio_test_anon() upon (the page table entry is bad so this is unavailable). This is potentially useful debugging information and matches the existing page offset provided. Use the raw __linear_anon_page_index() function so as to always output this value regardless of whether the mapping is file-backed or not and to avoid asserts that shouldn't apply here. Link: https://lore.kernel.org/20260813-b4-scalable-cow-virt-pgoff-v5-5-c21581c0c3c8@kernel.org Signed-off-by: Lorenzo Stoakes (ARM) Acked-by: David Hildenbrand (Arm) Reviewed-by: Gregory Price (Meta) Cc: Adrian Hunter Cc: Alexander Deucher Cc: Alexander Gordeev Cc: Alexander Shishkin Cc: Alistair Popple Cc: Arnaldo Carvalho de Melo Cc: Arnd Bergmann Cc: Baolin Wang Cc: Baoquan He Cc: Barry Song Cc: Boris Brezillon Cc: Byungchul Park Cc: Chengming Zhou Cc: Chris Li Cc: Christan König Cc: Christian Borntraeger Cc: Claudio Imbrenda Cc: Dave Airlie Cc: Dev Jain Cc: Gerald Schaefer Cc: Greg Kroah-Hartman Cc: Harry Yoo Cc: Heiko Carstens Cc: Huang Ray Cc: "Huang, Ying" Cc: Ian Rogers Cc: Ingo Molnar Cc: James Clark Cc: Jan Kara Cc: Jann Horn Cc: Janosch Frank Cc: Jason Gunthorpe Cc: Jiri Olsa Cc: John Hubbard Cc: Joshua Hahn Cc: Kairui Song Cc: Kees Cook Cc: Kemeng Shi Cc: Lance Yang Cc: Liam R. Howlett Cc: Liviu Dudau Cc: Maarten Lankhorst Cc: Marc Rutland Cc: "Masami Hiramatsu (Google)" Cc: Matthew Auld Cc: Matthew Brost Cc: Matthew Wilcox (Oracle) Cc: Maxime Ripard Cc: Miaohe Lin Cc: Michal Hocko Cc: Mike Rapoport Cc: Muchun Song Cc: Namhyung kim Cc: Naoya Horiguchi Cc: Nhat Pham Cc: Nico Pache Cc: Oleg Nesterov Cc: Oscar Salvador Cc: Pedro Falcato Cc: Peter Xu Cc: Peter Zijlstra Cc: Rakie Kim Cc: Rik van Riel Cc: Rodrigo Vivi Cc: Ryan Roberts Cc: Steven Price Cc: Suren Baghdasaryan Cc: Sven Schnelle Cc: Thomas Hellström Cc: Thomas Zimemrmann Cc: Vasily Gorbik Cc: Vlastimil Babka Cc: xu xin Cc: Zi Yan Signed-off-by: Andrew Morton --- mm/memory.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/mm/memory.c b/mm/memory.c index 396d7b9059e6..c54943302553 100644 --- a/mm/memory.c +++ b/mm/memory.c @@ -631,13 +631,14 @@ static void print_bad_page_map(struct vm_area_struct *vma, { struct address_space *mapping; char entry_str[PTVAL_STR_MAX]; - pgoff_t index; + pgoff_t index, anon_index; if (is_bad_page_map_ratelimited()) return; mapping = vma->vm_file ? vma->vm_file->f_mapping : NULL; index = linear_page_index(vma, addr); + anon_index = __linear_anon_page_index(vma, addr); ptval_bytes_to_hex_str(entry_str, sizeof(entry_str), entry, entry_size); pr_alert("BUG: Bad page map in process %s %s:%s", current->comm, @@ -645,8 +646,14 @@ static void print_bad_page_map(struct vm_area_struct *vma, __print_bad_page_map_pgtable(vma->vm_mm, addr); if (page) dump_page(page, "bad page map"); - pr_alert("addr:%px vm_flags:%08lx anon_vma:%px mapping:%px index:%lx\n", - (void *)addr, vma->vm_flags, vma->anon_vma, mapping, index); + pr_alert("addr:%px vm_flags:%08lx anon_vma:%px mapping:%px", + (void *)addr, vma->vm_flags, vma->anon_vma, mapping); + if (!vma_is_cow_mapping(vma) || index == anon_index) { + pr_cont(" index:%lx\n", index); + } else { + pr_cont(" index:%lx (file) %lx (anon)\n", index, anon_index); + } + pr_alert("file:%pD fault:%ps mmap:%ps mmap_prepare: %ps read_folio:%ps\n", vma->vm_file, vma->vm_ops ? vma->vm_ops->fault : NULL, From dba10745e9b4dcf2d8b6a0c44a23eec4f58365f0 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Thu, 13 Aug 2026 18:32:23 +0100 Subject: [PATCH 0969/1328] mm: introduce and use vma_filebacked_address() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In cases where we know that the VMA is file-backed, use vma_filebacked_address() rather than vma_address(). This lays the foundation for using the anonymous page offset via vma_anon_address(). Also add an assert to ensure that the VMA whose address is required is not anonymous. No functional change intended. Link: https://lore.kernel.org/20260813-b4-scalable-cow-virt-pgoff-v5-6-c21581c0c3c8@kernel.org Signed-off-by: Lorenzo Stoakes (ARM) Tested-by: syzbot@syzkaller.appspotmail.com Acked-by: David Hildenbrand (Arm) Cc: Adrian Hunter Cc: Alexander Deucher Cc: Alexander Gordeev Cc: Alexander Shishkin Cc: Alistair Popple Cc: Arnaldo Carvalho de Melo Cc: Arnd Bergmann Cc: Baolin Wang Cc: Baoquan He Cc: Barry Song Cc: Boris Brezillon Cc: Byungchul Park Cc: Chengming Zhou Cc: Chris Li Cc: Christan König Cc: Christian Borntraeger Cc: Claudio Imbrenda Cc: Dave Airlie Cc: Dev Jain Cc: Gerald Schaefer Cc: Greg Kroah-Hartman Cc: Gregory Price (Meta) Cc: Harry Yoo Cc: Heiko Carstens Cc: Huang Ray Cc: "Huang, Ying" Cc: Ian Rogers Cc: Ingo Molnar Cc: James Clark Cc: Jan Kara Cc: Jann Horn Cc: Janosch Frank Cc: Jason Gunthorpe Cc: Jiri Olsa Cc: John Hubbard Cc: Joshua Hahn Cc: Kairui Song Cc: Kees Cook Cc: Kemeng Shi Cc: Lance Yang Cc: Liam R. Howlett Cc: Liviu Dudau Cc: Maarten Lankhorst Cc: Marc Rutland Cc: "Masami Hiramatsu (Google)" Cc: Matthew Auld Cc: Matthew Brost Cc: Matthew Wilcox (Oracle) Cc: Maxime Ripard Cc: Miaohe Lin Cc: Michal Hocko Cc: Mike Rapoport Cc: Muchun Song Cc: Namhyung kim Cc: Naoya Horiguchi Cc: Nhat Pham Cc: Nico Pache Cc: Oleg Nesterov Cc: Oscar Salvador Cc: Pedro Falcato Cc: Peter Xu Cc: Peter Zijlstra Cc: Rakie Kim Cc: Rik van Riel Cc: Rodrigo Vivi Cc: Ryan Roberts Cc: Steven Price Cc: Suren Baghdasaryan Cc: Sven Schnelle Cc: Thomas Hellström Cc: Thomas Zimemrmann Cc: Vasily Gorbik Cc: Vlastimil Babka Cc: xu xin Cc: Zi Yan Signed-off-by: Andrew Morton --- mm/internal.h | 18 ++++++++++++++++++ mm/memory-failure.c | 4 ++-- mm/page_vma_mapped.c | 6 +++++- mm/rmap.c | 10 ++++++---- 4 files changed, 31 insertions(+), 7 deletions(-) diff --git a/mm/internal.h b/mm/internal.h index 761738d61fe0..f6475d77024d 100644 --- a/mm/internal.h +++ b/mm/internal.h @@ -1037,6 +1037,24 @@ static inline unsigned long __vma_address(const struct vm_area_struct *vma, return address; } +/** + * vma_filebacked_address - Find the virtual address a file-backed page range is + * mapped at. + * @vma: The vma which maps this object. + * @pgoff: The page offset within its object. + * @nr_pages: The number of pages to consider. + * + * Returns: If any page in this range is mapped by this VMA, return the first + * address where any of these pages appear. Otherwise, return -EFAULT. + */ +static inline unsigned long vma_filebacked_address(const struct vm_area_struct *vma, + pgoff_t pgoff, unsigned long nr_pages) +{ + VM_WARN_ON_ONCE(vma_is_anonymous(vma)); + + return __vma_address(vma, pgoff, vma_start_pgoff(vma), nr_pages); +} + /** * vma_address - Find the virtual address a page range is mapped at. * @vma: The vma which maps this object. diff --git a/mm/memory-failure.c b/mm/memory-failure.c index aaf14608b30e..a8b03e2920ba 100644 --- a/mm/memory-failure.c +++ b/mm/memory-failure.c @@ -620,7 +620,7 @@ static void add_to_kill_fsdax(struct task_struct *tsk, const struct page *p, struct vm_area_struct *vma, struct list_head *to_kill, pgoff_t pgoff) { - unsigned long addr = vma_address(vma, pgoff, 1); + unsigned long addr = vma_filebacked_address(vma, pgoff, 1); __add_to_kill(tsk, p, vma, to_kill, addr); } @@ -2265,7 +2265,7 @@ static void add_to_kill_pgoff(struct task_struct *tsk, } /* Check for pgoff not backed by struct page */ - tk->addr = vma_address(vma, pgoff, 1); + tk->addr = vma_filebacked_address(vma, pgoff, 1); tk->size_shift = PAGE_SHIFT; if (tk->addr == -EFAULT) diff --git a/mm/page_vma_mapped.c b/mm/page_vma_mapped.c index d7670ba4147b..081e483cc7bf 100644 --- a/mm/page_vma_mapped.c +++ b/mm/page_vma_mapped.c @@ -356,6 +356,7 @@ unsigned long page_mapped_in_vma(const struct page *page, struct vm_area_struct *vma) { const struct folio *folio = page_folio(page); + const pgoff_t pgoff = page_pgoff(folio, page); struct page_vma_mapped_walk pvmw = { .pfn = page_to_pfn(page), .nr_pages = 1, @@ -363,7 +364,10 @@ unsigned long page_mapped_in_vma(const struct page *page, .flags = PVMW_SYNC, }; - pvmw.address = vma_address(vma, page_pgoff(folio, page), 1); + if (folio_test_anon(folio)) + pvmw.address = vma_address(vma, pgoff, 1); + else + pvmw.address = vma_filebacked_address(vma, pgoff, 1); if (pvmw.address == -EFAULT) goto out; if (!page_vma_mapped_walk(&pvmw)) diff --git a/mm/rmap.c b/mm/rmap.c index 1f72d279ba68..bf618e4678d3 100644 --- a/mm/rmap.c +++ b/mm/rmap.c @@ -865,14 +865,15 @@ unsigned long page_address_in_vma(const struct folio *folio, if (!vma->anon_vma || !anon_vma || vma->anon_vma->root != anon_vma->root) return -EFAULT; + /* KSM folios don't reach here because of the !anon_vma check */ + return vma_address(vma, page_pgoff(folio, page), 1); } else if (!vma->vm_file) { return -EFAULT; } else if (vma->vm_file->f_mapping != folio->mapping) { return -EFAULT; } - /* KSM folios don't reach here because of the !anon_vma check */ - return vma_address(vma, page_pgoff(folio, page), 1); + return vma_filebacked_address(vma, page_pgoff(folio, page), 1); } /* @@ -1321,7 +1322,7 @@ int pfn_mkclean_range(unsigned long pfn, unsigned long nr_pages, pgoff_t pgoff, if (invalid_mkclean_vma(vma, NULL)) return 0; - pvmw.address = vma_address(vma, pgoff, nr_pages); + pvmw.address = vma_filebacked_address(vma, pgoff, nr_pages); VM_BUG_ON_VMA(pvmw.address == -EFAULT, vma); return page_vma_mkclean_one(&pvmw); @@ -3098,7 +3099,8 @@ static void __rmap_walk_file(struct folio *folio, struct address_space *mapping, } lookup: mapping_rmap_tree_foreach(vma, mapping, pgoff_start, pgoff_end) { - unsigned long address = vma_address(vma, pgoff_start, nr_pages); + unsigned long address = vma_filebacked_address(vma, pgoff_start, + nr_pages); VM_BUG_ON_VMA(address == -EFAULT, vma); cond_resched(); From 9998bc06d75bfbb17b8ff7f83183d133923c5829 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Thu, 13 Aug 2026 18:32:24 +0100 Subject: [PATCH 0970/1328] mm/vma: fix self-merge check in copy_vma() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The existing logic is very confusing so improve things. Firstly rename the confusing faulted_in_anon_vma variable to can_self_merge and update this when the page offset is updated. What is being checked for is a 'self-merge' - that is between the VMA being remapped and its prior VMA (remember that this is copy_vma() - if a non-MREMAP_DONTUNMAP remap the original VMA is only removed afterwards). This can happen if the VMA is moved immediately adjacent to itself, either before or after it: |----------------|----------------| | | | v | v |...............||---------------||...............| | new || old || new | |...............||---------------||---------------| In these cases the old VMA is simply expanded to cover the new range. It is also possible for the move to both self-merge and merge with a prior VMA if it is placed between a preceding VMA and its old self: |---------------| | | v | |---------------||...............||---------------| | prev || new || old | |---------------||...............||---------------| In this case, the old VMA is removed and 'prev' is expanded and replaces it. Since copy_vma_and_data() which calls copy_vma() intends to reference the old VMA after the merge, it must have this pointer updated. This kind of self-merge is not possible with a succeeding merge, as the merge always prefers to expand the preceding VMA if possible. copy_vma() accounts for this by explicitly checking to see if a self-merge occurred and updating the vmap pointer if so. However it incorrect did so even for a subsequent merge (this is simply a noop so it had no impact). So change this to only check for the case which matters - a backwards merge - and rearrange the parameters to make it clearer we're doing that - i.e. check new_vma->vm_start < old_vma_start (having already renamed vma_start to old_vma_start to make it clear this is the previous VMA). Also update the existing wall-of-text comment to be a lot clearer. While we're here, replace the VM_BUG_ON_VMA() with a VM_WARN_ON_ONCE_VMA() and update the VMA userland tests accordingly. No functional change intended. Link: https://lore.kernel.org/20260813-b4-scalable-cow-virt-pgoff-v5-7-c21581c0c3c8@kernel.org Signed-off-by: Lorenzo Stoakes (ARM) Acked-by: David Hildenbrand (Arm) Cc: Adrian Hunter Cc: Alexander Deucher Cc: Alexander Gordeev Cc: Alexander Shishkin Cc: Alistair Popple Cc: Arnaldo Carvalho de Melo Cc: Arnd Bergmann Cc: Baolin Wang Cc: Baoquan He Cc: Barry Song Cc: Boris Brezillon Cc: Byungchul Park Cc: Chengming Zhou Cc: Chris Li Cc: Christan König Cc: Christian Borntraeger Cc: Claudio Imbrenda Cc: Dave Airlie Cc: Dev Jain Cc: Gerald Schaefer Cc: Greg Kroah-Hartman Cc: Gregory Price (Meta) Cc: Harry Yoo Cc: Heiko Carstens Cc: Huang Ray Cc: "Huang, Ying" Cc: Ian Rogers Cc: Ingo Molnar Cc: James Clark Cc: Jan Kara Cc: Jann Horn Cc: Janosch Frank Cc: Jason Gunthorpe Cc: Jiri Olsa Cc: John Hubbard Cc: Joshua Hahn Cc: Kairui Song Cc: Kees Cook Cc: Kemeng Shi Cc: Lance Yang Cc: Liam R. Howlett Cc: Liviu Dudau Cc: Maarten Lankhorst Cc: Marc Rutland Cc: "Masami Hiramatsu (Google)" Cc: Matthew Auld Cc: Matthew Brost Cc: Matthew Wilcox (Oracle) Cc: Maxime Ripard Cc: Miaohe Lin Cc: Michal Hocko Cc: Mike Rapoport Cc: Muchun Song Cc: Namhyung kim Cc: Naoya Horiguchi Cc: Nhat Pham Cc: Nico Pache Cc: Oleg Nesterov Cc: Oscar Salvador Cc: Pedro Falcato Cc: Peter Xu Cc: Peter Zijlstra Cc: Rakie Kim Cc: Rik van Riel Cc: Rodrigo Vivi Cc: Ryan Roberts Cc: Steven Price Cc: Suren Baghdasaryan Cc: Sven Schnelle Cc: Thomas Hellström Cc: Thomas Zimemrmann Cc: Vasily Gorbik Cc: Vlastimil Babka Cc: xu xin Cc: Zi Yan Signed-off-by: Andrew Morton --- mm/vma.c | 35 +++++++++++++++----------------- tools/testing/vma/vma_internal.h | 1 + 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/mm/vma.c b/mm/vma.c index a325376e62ea..ecefc9e63070 100644 --- a/mm/vma.c +++ b/mm/vma.c @@ -1911,10 +1911,10 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap, bool *need_rmap_locks) { struct vm_area_struct *vma = *vmap; - unsigned long vma_start = vma->vm_start; + unsigned long old_vma_start = vma->vm_start; struct mm_struct *mm = vma->vm_mm; struct vm_area_struct *new_vma; - bool faulted_in_anon_vma = true; + bool can_self_merge = false; VMA_ITERATOR(vmi, mm, addr); VMG_VMA_STATE(vmg, &vmi, NULL, vma, addr, addr + len); @@ -1924,7 +1924,7 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap, */ if (unlikely(vma_is_anonymous(vma) && !vma->anon_vma)) { pgoff = addr >> PAGE_SHIFT; - faulted_in_anon_vma = false; + can_self_merge = true; } /* @@ -1944,24 +1944,21 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap, new_vma = vma_merge_copied_range(&vmg); if (new_vma) { - /* - * Source vma may have been merged into new_vma - */ - if (unlikely(vma_start >= new_vma->vm_start && - vma_start < new_vma->vm_end)) { + /* Self-merged and VMA replaced. */ + if (unlikely(new_vma->vm_start < old_vma_start && + new_vma->vm_end > old_vma_start)) { /* - * The only way we can get a vma_merge with - * self during an mremap is if the vma hasn't - * been faulted in yet and we were allowed to - * reset the dst vma->vm_pgoff to the - * destination address of the mremap to allow - * the merge to happen. mremap must change the - * vm_pgoff linearity between src and dst vmas - * (in turn preventing a vma_merge) to be - * safe. It is only safe to keep the vm_pgoff - * linear if there are no pages mapped yet. + * The only way a VMA can both self-merge and be + * replaced is if the remap places the new VMA + * immediately prior to its old self ('next') and + * immediately after another VMA ('prev') causing the + * next to be removed and prev to be expanded to cover + * the entire range. + * + * This should only be possible if the page offset was + * updated, i.e. the VMA is unfaulted. */ - VM_BUG_ON_VMA(faulted_in_anon_vma, new_vma); + VM_WARN_ON_ONCE_VMA(!can_self_merge, new_vma); *vmap = vma = new_vma; } *need_rmap_locks = diff --git a/tools/testing/vma/vma_internal.h b/tools/testing/vma/vma_internal.h index 4f6c5666ac07..8a48b231aa7a 100644 --- a/tools/testing/vma/vma_internal.h +++ b/tools/testing/vma/vma_internal.h @@ -53,6 +53,7 @@ typedef __bitwise unsigned int vm_fault_t; #define VM_WARN_ON(_expr) (WARN_ON(_expr)) #define VM_WARN_ON_ONCE(_expr) (WARN_ON_ONCE(_expr)) +#define VM_WARN_ON_ONCE_VMA(_expr, _vma) (WARN_ON_ONCE(_expr)) #define VM_WARN_ON_VMG(_expr, _vmg) (WARN_ON(_expr)) #define VM_BUG_ON(_expr) (BUG_ON(_expr)) #define VM_BUG_ON_VMA(_expr, _vma) (BUG_ON(_expr)) From 746b9e0a4777e8e883d70b4292a6c3d8c435712c Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Thu, 13 Aug 2026 18:32:25 +0100 Subject: [PATCH 0971/1328] tools/testing/vma: add tests for copy_vma() self-merge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Assert that a VMA can be moved backwards, forwards and between a preceding VMA and its old self. In the cases in which the VMA merges only with itself expect that to be achieved by expanding its old self, so assert that these function correctly. However in the case of a merge between a preceding VMA and itself the original VMA is removed, so assert that the preceding VMA replaces the one passed in as vmap and the merge is as expected. Link: https://lore.kernel.org/20260813-b4-scalable-cow-virt-pgoff-v5-8-c21581c0c3c8@kernel.org Signed-off-by: Lorenzo Stoakes (ARM) Acked-by: David Hildenbrand (Arm) Cc: Adrian Hunter Cc: Alexander Deucher Cc: Alexander Gordeev Cc: Alexander Shishkin Cc: Alistair Popple Cc: Arnaldo Carvalho de Melo Cc: Arnd Bergmann Cc: Baolin Wang Cc: Baoquan He Cc: Barry Song Cc: Boris Brezillon Cc: Byungchul Park Cc: Chengming Zhou Cc: Chris Li Cc: Christan König Cc: Christian Borntraeger Cc: Claudio Imbrenda Cc: Dave Airlie Cc: Dev Jain Cc: Gerald Schaefer Cc: Greg Kroah-Hartman Cc: Gregory Price (Meta) Cc: Harry Yoo Cc: Heiko Carstens Cc: Huang Ray Cc: "Huang, Ying" Cc: Ian Rogers Cc: Ingo Molnar Cc: James Clark Cc: Jan Kara Cc: Jann Horn Cc: Janosch Frank Cc: Jason Gunthorpe Cc: Jiri Olsa Cc: John Hubbard Cc: Joshua Hahn Cc: Kairui Song Cc: Kees Cook Cc: Kemeng Shi Cc: Lance Yang Cc: Liam R. Howlett Cc: Liviu Dudau Cc: Maarten Lankhorst Cc: Marc Rutland Cc: "Masami Hiramatsu (Google)" Cc: Matthew Auld Cc: Matthew Brost Cc: Matthew Wilcox (Oracle) Cc: Maxime Ripard Cc: Miaohe Lin Cc: Michal Hocko Cc: Mike Rapoport Cc: Muchun Song Cc: Namhyung kim Cc: Naoya Horiguchi Cc: Nhat Pham Cc: Nico Pache Cc: Oleg Nesterov Cc: Oscar Salvador Cc: Pedro Falcato Cc: Peter Xu Cc: Peter Zijlstra Cc: Rakie Kim Cc: Rik van Riel Cc: Rodrigo Vivi Cc: Ryan Roberts Cc: Steven Price Cc: Suren Baghdasaryan Cc: Sven Schnelle Cc: Thomas Hellström Cc: Thomas Zimemrmann Cc: Vasily Gorbik Cc: Vlastimil Babka Cc: xu xin Cc: Zi Yan Signed-off-by: Andrew Morton --- tools/testing/vma/tests/vma.c | 46 ++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/tools/testing/vma/tests/vma.c b/tools/testing/vma/tests/vma.c index 754a2da06321..0d40d7ba2181 100644 --- a/tools/testing/vma/tests/vma.c +++ b/tools/testing/vma/tests/vma.c @@ -33,7 +33,51 @@ static bool test_copy_vma(void) struct mm_struct mm = {}; bool need_locks = false; VMA_ITERATOR(vmi, &mm, 0); - struct vm_area_struct *vma, *vma_new, *vma_next; + struct vm_area_struct *vma, *vma_prev, *vma_new, *vma_next, *vma_orig; + + /* Move forwards, adjacent to old self - self-merge. */ + + vma = alloc_and_link_vma(&mm, 0x1000, 0x2000, 1, vma_flags); + vma_set_anonymous(vma); + vma_orig = vma; + vma_new = copy_vma(&vma, 0x2000, 0x1000, 1, &need_locks); + ASSERT_EQ(vma_new, vma_orig); + ASSERT_EQ(vma, vma_orig); + ASSERT_EQ(vma_new->vm_start, 0x1000); + ASSERT_EQ(vma_new->vm_end, 0x3000); + + cleanup_mm(&mm, &vmi); + + /* Move backwards, adjacent to old self - self-merge. */ + + vma = alloc_and_link_vma(&mm, 0x2000, 0x3000, 2, vma_flags); + vma_set_anonymous(vma); + vma_orig = vma; + vma_new = copy_vma(&vma, 0x1000, 0x1000, 2, &need_locks); + ASSERT_EQ(vma_new, vma_orig); + ASSERT_EQ(vma, vma_orig); + ASSERT_EQ(vma_new->vm_start, 0x1000); + ASSERT_EQ(vma_new->vm_end, 0x3000); + + cleanup_mm(&mm, &vmi); + + /* + * Move backwards between prior VMA and old self - self-merge and vma + * updated to a new VMA. + */ + + vma_prev = alloc_and_link_vma(&mm, 0x1000, 0x2000, 1, vma_flags); + vma_set_anonymous(vma_prev); + vma = alloc_and_link_vma(&mm, 0x3000, 0x4000, 3, vma_flags); + vma_set_anonymous(vma); + vma_orig = vma; + vma_new = copy_vma(&vma, 0x2000, 0x1000, 3, &need_locks); + ASSERT_NE(vma_new, vma_orig); + ASSERT_EQ(vma_new, vma); + ASSERT_EQ(vma_new->vm_start, 0x1000); + ASSERT_EQ(vma_new->vm_end, 0x4000); + + cleanup_mm(&mm, &vmi); /* Move backwards and do not merge. */ From 6a993c7fbc3e99431e148eb261c9b2e38525fce4 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Thu, 13 Aug 2026 18:32:26 +0100 Subject: [PATCH 0972/1328] mm: propagate VMA anonymous page offset on map, remap, split + merge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We must correctly update VMA anonymous page offset state on all VMA operations that would result in it changing, with special attention given to remapping. We cover most cases by simply updating vma_set_range() to do so (with a new anonymous page offset parameter), but also notably must update the merging and mapping logic to propagate this parameter correctly. The remap logic remains the same - we may update the anonymous page offset if the VMA is unfaulted, but now this applies to MAP_PRIVATE file-backed mappings too, so we update the code to reflect this. Note that we use __linear_anon_page_index() upon remap as the VMA may be shared, in order that we update the field consistently regardless of VMA type. Similarly, pass through anon page offset to the merge logic, updating the vma_merge_struct struct to propagate it, and also use __linear_anon_page_index() to obtain the anonymous page index so it can be safely used for both shared and MAP_PRIVATE file-backed mappings. In copy_vma(), the anonymous page offset is updated regardless of whether the mapping is a CoW mapping or not. This is both to keep the anonymous page offset consistent even for non-CoW mappings (it is set so should at least remain correct) and makes the logic cleaner. A self-merge however remains permitted only for mappings which can have a populated vma->anon_vma and do not require alignment on a separate file offset - that is pure anonymous VMAs, so only set can_self_merge if vma_is_anonymous(). Finally, we update insert_vm_struct() to correctly set the anonymous page offset on insertion of a VMA. We simply ensure state is correctly propagated here, so no functional changes are intended. Also update VMA userland tests to reflect this change. Link: https://lore.kernel.org/20260813-b4-scalable-cow-virt-pgoff-v5-9-c21581c0c3c8@kernel.org Signed-off-by: Lorenzo Stoakes (ARM) Acked-by: David Hildenbrand (Arm) Cc: Adrian Hunter Cc: Alexander Deucher Cc: Alexander Gordeev Cc: Alexander Shishkin Cc: Alistair Popple Cc: Arnaldo Carvalho de Melo Cc: Arnd Bergmann Cc: Baolin Wang Cc: Baoquan He Cc: Barry Song Cc: Boris Brezillon Cc: Byungchul Park Cc: Chengming Zhou Cc: Chris Li Cc: Christan König Cc: Christian Borntraeger Cc: Claudio Imbrenda Cc: Dave Airlie Cc: Dev Jain Cc: Gerald Schaefer Cc: Greg Kroah-Hartman Cc: Gregory Price (Meta) Cc: Harry Yoo Cc: Heiko Carstens Cc: Huang Ray Cc: "Huang, Ying" Cc: Ian Rogers Cc: Ingo Molnar Cc: James Clark Cc: Jan Kara Cc: Jann Horn Cc: Janosch Frank Cc: Jason Gunthorpe Cc: Jiri Olsa Cc: John Hubbard Cc: Joshua Hahn Cc: Kairui Song Cc: Kees Cook Cc: Kemeng Shi Cc: Lance Yang Cc: Liam R. Howlett Cc: Liviu Dudau Cc: Maarten Lankhorst Cc: Marc Rutland Cc: "Masami Hiramatsu (Google)" Cc: Matthew Auld Cc: Matthew Brost Cc: Matthew Wilcox (Oracle) Cc: Maxime Ripard Cc: Miaohe Lin Cc: Michal Hocko Cc: Mike Rapoport Cc: Muchun Song Cc: Namhyung kim Cc: Naoya Horiguchi Cc: Nhat Pham Cc: Nico Pache Cc: Oleg Nesterov Cc: Oscar Salvador Cc: Pedro Falcato Cc: Peter Xu Cc: Peter Zijlstra Cc: Rakie Kim Cc: Rik van Riel Cc: Rodrigo Vivi Cc: Ryan Roberts Cc: Steven Price Cc: Suren Baghdasaryan Cc: Sven Schnelle Cc: Thomas Hellström Cc: Thomas Zimemrmann Cc: Vasily Gorbik Cc: Vlastimil Babka Cc: xu xin Cc: Zi Yan Signed-off-by: Andrew Morton --- mm/mremap.c | 6 ++- mm/vma.c | 52 +++++++++++++++------- mm/vma.h | 79 ++++++++++++++++++++------------- mm/vma_exec.c | 2 +- tools/testing/vma/shared.c | 3 +- tools/testing/vma/tests/merge.c | 4 +- tools/testing/vma/tests/vma.c | 10 ++--- 7 files changed, 97 insertions(+), 59 deletions(-) diff --git a/mm/mremap.c b/mm/mremap.c index b64aa1f6e07e..9ea1707eafa5 100644 --- a/mm/mremap.c +++ b/mm/mremap.c @@ -1265,7 +1265,9 @@ static void unmap_source_vma(struct vma_remap_struct *vrm) static int copy_vma_and_data(struct vma_remap_struct *vrm, struct vm_area_struct **new_vma_ptr) { - const unsigned long new_pgoff = linear_page_index(vrm->vma, vrm->addr); + const pgoff_t new_pgoff = linear_page_index(vrm->vma, vrm->addr); + const pgoff_t new_anon_pgoff = + __linear_anon_page_index(vrm->vma, vrm->addr); struct vm_area_struct *vma = vrm->vma; struct vm_area_struct *new_vma; unsigned long moved_len; @@ -1273,7 +1275,7 @@ static int copy_vma_and_data(struct vma_remap_struct *vrm, PAGETABLE_MOVE(pmc, NULL, NULL, vrm->addr, vrm->new_addr, vrm->old_len); new_vma = copy_vma(&vma, vrm->new_addr, vrm->new_len, new_pgoff, - &pmc.need_rmap_locks); + new_anon_pgoff, &pmc.need_rmap_locks); if (!new_vma) { vrm_uncharge(vrm); *new_vma_ptr = NULL; diff --git a/mm/vma.c b/mm/vma.c index ecefc9e63070..e35b04ac12cb 100644 --- a/mm/vma.c +++ b/mm/vma.c @@ -18,6 +18,7 @@ struct mmap_state { unsigned long addr; unsigned long end; pgoff_t pgoff; + pgoff_t anon_pgoff; unsigned long pglen; union { vm_flags_t vm_flags; @@ -46,13 +47,14 @@ struct mmap_state { bool file_doesnt_need_get :1; }; -#define MMAP_STATE(name, mm_, vmi_, addr_, len_, pgoff_, vma_flags_, file_) \ +#define MMAP_STATE(name, mm_, vmi_, addr_, len_, pgoff_, anon_pgoff_, vma_flags_, file_) \ struct mmap_state name = { \ .mm = mm_, \ .vmi = vmi_, \ .addr = addr_, \ .end = (addr_) + (len_), \ .pgoff = pgoff_, \ + .anon_pgoff = anon_pgoff_, \ .pglen = PHYS_PFN(len_), \ .vma_flags = vma_flags_, \ .file = file_, \ @@ -67,6 +69,7 @@ struct mmap_state { .end = (map_)->end, \ .vma_flags = (map_)->vma_flags, \ .pgoff = (map_)->pgoff, \ + .anon_pgoff = (map_)->anon_pgoff, \ .file = (map_)->file, \ .prev = (map_)->prev, \ .middle = vma_, \ @@ -82,10 +85,11 @@ static void __vma_set_range(struct vm_area_struct *vma, unsigned long start, } static void vma_set_range(struct vm_area_struct *vma, unsigned long start, - unsigned long end, pgoff_t pgoff) + unsigned long end, pgoff_t pgoff, pgoff_t anon_pgoff) { __vma_set_range(vma, start, end); vma_set_pgoff(vma, pgoff); + vma_set_anon_pgoff(vma, anon_pgoff); } /* Was this VMA ever forked from a parent, i.e. maybe contains CoW mappings? */ @@ -812,7 +816,8 @@ static int commit_merge(struct vma_merge_struct *vmg) */ vma_adjust_trans_huge(vma, vmg->start, vmg->end, vmg->__adjust_middle_start ? vmg->middle : NULL); - vma_set_range(vma, vmg->start, vmg->end, vmg_start_pgoff(vmg)); + vma_set_range(vma, vmg->start, vmg->end, vmg_start_pgoff(vmg), + vmg_start_anon_pgoff(vmg)); vmg_adjust_set_range(vmg); vma_iter_store_overwrite(vmg->vmi, vmg->target); @@ -982,6 +987,7 @@ static __must_check struct vm_area_struct *vma_merge_existing_range( vmg->start = prev->vm_start; vmg->end = next->vm_end; vmg->pgoff = vma_start_pgoff(prev); + vmg->anon_pgoff = vma_start_anon_pgoff(prev); /* * We already ensured anon_vma compatibility above, so now it's @@ -1000,6 +1006,7 @@ static __must_check struct vm_area_struct *vma_merge_existing_range( */ vmg->start = prev->vm_start; vmg->pgoff = vma_start_pgoff(prev); + vmg->anon_pgoff = vma_start_anon_pgoff(prev); if (!vmg->__remove_middle) vmg->__adjust_middle_start = true; @@ -1022,12 +1029,14 @@ static __must_check struct vm_area_struct *vma_merge_existing_range( if (vmg->__remove_middle) { vmg->end = next->vm_end; vmg->pgoff = vma_start_pgoff(next) - pglen; + vmg->anon_pgoff = vma_start_anon_pgoff(next) - pglen; } else { /* We shrink middle and expand next. */ vmg->__adjust_next_start = true; vmg->start = middle->vm_start; vmg->end = start; vmg->pgoff = vma_start_pgoff(middle); + vmg->anon_pgoff = vma_start_anon_pgoff(middle); } err = dup_anon_vma(next, middle, &anon_dup); @@ -1137,6 +1146,7 @@ struct vm_area_struct *vma_merge_new_range(struct vma_merge_struct *vmg) vmg->start = prev->vm_start; vmg->target = prev; vmg->pgoff = vma_start_pgoff(prev); + vmg->anon_pgoff = vma_start_anon_pgoff(prev); /* * If this merge would result in removal of the next VMA but we @@ -1908,7 +1918,7 @@ static int vma_link(struct mm_struct *mm, struct vm_area_struct *vma) */ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap, unsigned long addr, unsigned long len, pgoff_t pgoff, - bool *need_rmap_locks) + pgoff_t anon_pgoff, bool *need_rmap_locks) { struct vm_area_struct *vma = *vmap; unsigned long old_vma_start = vma->vm_start; @@ -1919,12 +1929,16 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap, VMG_VMA_STATE(vmg, &vmi, NULL, vma, addr, addr + len); /* - * If anonymous vma has not yet been faulted, update new pgoff - * to match new location, to increase its chance of merging. + * If a vma has not yet been faulted, update its anonymous pgoff to + * match the new location to increase its chance of merging. */ - if (unlikely(vma_is_anonymous(vma) && !vma->anon_vma)) { - pgoff = addr >> PAGE_SHIFT; - can_self_merge = true; + if (!vma->anon_vma) { + anon_pgoff = addr >> PAGE_SHIFT; + + if (vma_is_anonymous(vma)) { + pgoff = anon_pgoff; + can_self_merge = true; + } } /* @@ -1940,6 +1954,7 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap, return NULL; /* should never get here */ vmg.pgoff = pgoff; + vmg.anon_pgoff = anon_pgoff; vmg.next = vma_iter_next_rewind(&vmi, NULL); new_vma = vma_merge_copied_range(&vmg); @@ -1955,8 +1970,8 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap, * next to be removed and prev to be expanded to cover * the entire range. * - * This should only be possible if the page offset was - * updated, i.e. the VMA is unfaulted. + * This should only be possible if the anonymous page + * offset was updated, i.e. the VMA is unfaulted. */ VM_WARN_ON_ONCE_VMA(!can_self_merge, new_vma); *vmap = vma = new_vma; @@ -1967,7 +1982,7 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap, new_vma = vm_area_dup(vma); if (!new_vma) goto out; - vma_set_range(new_vma, addr, addr + len, pgoff); + vma_set_range(new_vma, addr, addr + len, pgoff, anon_pgoff); if (vma_dup_policy(vma, new_vma)) goto out_free_vma; if (anon_vma_clone(new_vma, vma, VMA_OP_REMAP)) @@ -2609,7 +2624,7 @@ static int __mmap_new_vma(struct mmap_state *map, struct vm_area_struct **vmap, if (is_anon) vma_set_anonymous(vma); - vma_set_range(vma, map->addr, map->end, map->pgoff); + vma_set_range(vma, map->addr, map->end, map->pgoff, map->anon_pgoff); vma->flags = map->vma_flags; vma->vm_page_prot = map->page_prot; @@ -2798,7 +2813,8 @@ static unsigned long __mmap_region(struct file *file, unsigned long addr, struct vm_area_struct *vma = NULL; bool have_mmap_prepare = file && file->f_op->mmap_prepare; VMA_ITERATOR(vmi, mm, addr); - MMAP_STATE(map, mm, &vmi, addr, len, pgoff, vma_flags, file); + const pgoff_t anon_pgoff = addr >> PAGE_SHIFT; + MMAP_STATE(map, mm, &vmi, addr, len, pgoff, anon_pgoff, vma_flags, file); struct vm_area_desc desc = { .mm = mm, .file = file, @@ -2943,6 +2959,7 @@ int do_brk_flags(struct vma_iterator *vmi, struct vm_area_struct *vma, unsigned long addr, unsigned long len, vma_flags_t vma_flags) { struct mm_struct *mm = current->mm; + const pgoff_t pgoff = addr >> PAGE_SHIFT; /* * Check against address space limits by the changed size @@ -2967,7 +2984,7 @@ int do_brk_flags(struct vma_iterator *vmi, struct vm_area_struct *vma, * occur after forking, so the expand will only happen on new VMAs. */ if (vma && vma->vm_end == addr) { - VMG_STATE(vmg, mm, vmi, addr, addr + len, vma_flags, PHYS_PFN(addr)); + VMG_STATE(vmg, mm, vmi, addr, addr + len, vma_flags, pgoff, pgoff); vmg.prev = vma; /* vmi is positioned at prev, which this mode expects. */ @@ -2987,7 +3004,7 @@ int do_brk_flags(struct vma_iterator *vmi, struct vm_area_struct *vma, goto unacct_fail; vma_set_anonymous(vma); - vma_set_range(vma, addr, addr + len, addr >> PAGE_SHIFT); + vma_set_range(vma, addr, addr + len, pgoff, pgoff); vma->flags = vma_flags; vma->vm_page_prot = vm_get_page_prot(vma_flags_to_legacy(vma_flags)); vma_start_write(vma); @@ -3379,6 +3396,7 @@ int insert_vm_struct(struct mm_struct *mm, struct vm_area_struct *vma) WARN_ON_ONCE(vma->anon_vma); vma_set_pgoff(vma, vma->vm_start >> PAGE_SHIFT); } + vma_set_anon_pgoff(vma, vma->vm_start >> PAGE_SHIFT); if (vma_link(mm, vma)) { if (vma_test(vma, VMA_ACCOUNT_BIT)) @@ -3434,7 +3452,7 @@ struct vm_area_struct *__install_special_mapping( vma->vm_ops = ops; vma->vm_private_data = priv; - vma_set_range(vma, addr, addr + len, 0); + vma_set_range(vma, addr, addr + len, 0, addr >> PAGE_SHIFT); ret = insert_vm_struct(mm, vma); if (ret) diff --git a/mm/vma.h b/mm/vma.h index 54ed7c744e3b..024fabe63560 100644 --- a/mm/vma.h +++ b/mm/vma.h @@ -104,6 +104,7 @@ struct vma_merge_struct { unsigned long start; unsigned long end; pgoff_t pgoff; + pgoff_t anon_pgoff; union { /* Temporary while VMA flags are being converted. */ @@ -237,11 +238,6 @@ static inline bool vmg_nomem(struct vma_merge_struct *vmg) return vmg->state == VMA_MERGE_ERROR_NOMEM; } -static inline pgoff_t vmg_start_pgoff(const struct vma_merge_struct *vmg) -{ - return vmg->pgoff; -} - static inline pgoff_t vmg_pages(const struct vma_merge_struct *vmg) { const unsigned long size = vmg->end - vmg->start; @@ -249,6 +245,11 @@ static inline pgoff_t vmg_pages(const struct vma_merge_struct *vmg) return size >> PAGE_SHIFT; } +static inline pgoff_t vmg_start_pgoff(const struct vma_merge_struct *vmg) +{ + return vmg->pgoff; +} + static inline pgoff_t vmg_end_pgoff(const struct vma_merge_struct *vmg) { return vmg_start_pgoff(vmg) + vmg_pages(vmg); @@ -283,6 +284,16 @@ static inline void vma_set_pgoff(struct vm_area_struct *vma, pgoff_t pgoff) vma->vm_pgoff = pgoff; } +static inline pgoff_t vmg_start_anon_pgoff(const struct vma_merge_struct *vmg) +{ + return vmg->anon_pgoff; +} + +static inline pgoff_t vmg_end_anon_pgoff(const struct vma_merge_struct *vmg) +{ + return vmg_start_anon_pgoff(vmg) + vmg_pages(vmg); +} + static inline void __vma_set_anon_pgoff(struct vm_area_struct *vma, pgoff_t pgoff) { #ifdef CONFIG_64BIT @@ -301,42 +312,46 @@ static inline void vma_add_pgoff(struct vm_area_struct *vma, pgoff_t delta) { vma_assert_can_modify(vma); vma_set_pgoff(vma, vma_start_pgoff(vma) + delta); + vma_set_anon_pgoff(vma, vma_start_anon_pgoff(vma) + delta); } static inline void vma_sub_pgoff(struct vm_area_struct *vma, pgoff_t delta) { vma_assert_can_modify(vma); vma_set_pgoff(vma, vma_start_pgoff(vma) - delta); + vma_set_anon_pgoff(vma, vma_start_anon_pgoff(vma) - delta); } -#define VMG_STATE(name, mm_, vmi_, start_, end_, vma_flags_, pgoff_) \ - struct vma_merge_struct name = { \ - .mm = mm_, \ - .vmi = vmi_, \ - .start = start_, \ - .end = end_, \ - .vma_flags = vma_flags_, \ - .pgoff = pgoff_, \ - .state = VMA_MERGE_START, \ +#define VMG_STATE(name, mm_, vmi_, start_, end_, vma_flags_, pgoff_, anon_pgoff_) \ + struct vma_merge_struct name = { \ + .mm = mm_, \ + .vmi = vmi_, \ + .start = start_, \ + .end = end_, \ + .vma_flags = vma_flags_, \ + .pgoff = pgoff_, \ + .anon_pgoff = anon_pgoff_, \ + .state = VMA_MERGE_START, \ } -#define VMG_VMA_STATE(name, vmi_, prev_, vma_, start_, end_) \ - struct vma_merge_struct name = { \ - .mm = vma_->vm_mm, \ - .vmi = vmi_, \ - .prev = prev_, \ - .middle = vma_, \ - .next = NULL, \ - .start = start_, \ - .end = end_, \ - .vm_flags = vma_->vm_flags, \ - .pgoff = linear_page_index(vma_, start_), \ - .file = vma_->vm_file, \ - .anon_vma = vma_->anon_vma, \ - .policy = vma_policy(vma_), \ - .uffd_ctx = vma_->vm_userfaultfd_ctx, \ - .anon_name = anon_vma_name(vma_), \ - .state = VMA_MERGE_START, \ +#define VMG_VMA_STATE(name, vmi_, prev_, vma_, start_, end_) \ + struct vma_merge_struct name = { \ + .mm = vma_->vm_mm, \ + .vmi = vmi_, \ + .prev = prev_, \ + .middle = vma_, \ + .next = NULL, \ + .start = start_, \ + .end = end_, \ + .vm_flags = vma_->vm_flags, \ + .pgoff = linear_page_index(vma_, start_), \ + .anon_pgoff = __linear_anon_page_index(vma_, start_), \ + .file = vma_->vm_file, \ + .anon_vma = vma_->anon_vma, \ + .policy = vma_policy(vma_), \ + .uffd_ctx = vma_->vm_userfaultfd_ctx, \ + .anon_name = anon_vma_name(vma_), \ + .state = VMA_MERGE_START, \ } #ifdef CONFIG_DEBUG_VM_MAPLE_TREE @@ -520,7 +535,7 @@ void unlink_file_vma_batch_add(struct unlink_vma_file_batch *vb, struct vm_area_struct *copy_vma(struct vm_area_struct **vmap, unsigned long addr, unsigned long len, pgoff_t pgoff, - bool *need_rmap_locks); + pgoff_t anon_pgoff, bool *need_rmap_locks); struct anon_vma *find_mergeable_anon_vma(struct vm_area_struct *vma); diff --git a/mm/vma_exec.c b/mm/vma_exec.c index 7af1260689b9..586c52155942 100644 --- a/mm/vma_exec.c +++ b/mm/vma_exec.c @@ -41,7 +41,7 @@ int relocate_vma_down(struct vm_area_struct *vma, unsigned long shift) unsigned long new_end = old_end - shift; VMA_ITERATOR(vmi, mm, new_start); VMG_STATE(vmg, mm, &vmi, new_start, old_end, EMPTY_VMA_FLAGS, - vma_start_pgoff(vma)); + vma_start_pgoff(vma), vma_start_anon_pgoff(vma)); struct vm_area_struct *next; struct mmu_gather tlb; PAGETABLE_MOVE(pmc, vma, vma, old_start, new_start, length); diff --git a/tools/testing/vma/shared.c b/tools/testing/vma/shared.c index bea9ea6db02a..4a39c9d50489 100644 --- a/tools/testing/vma/shared.c +++ b/tools/testing/vma/shared.c @@ -23,7 +23,8 @@ struct vm_area_struct *alloc_vma(struct mm_struct *mm, vma->vm_start = start; vma->vm_end = end; - vma->vm_pgoff = pgoff; + vma_set_pgoff(vma, pgoff); + vma_set_anon_pgoff(vma, start >> PAGE_SHIFT); vma->flags = vma_flags; vma_assert_detached(vma); diff --git a/tools/testing/vma/tests/merge.c b/tools/testing/vma/tests/merge.c index e357accc8499..48418b82b01d 100644 --- a/tools/testing/vma/tests/merge.c +++ b/tools/testing/vma/tests/merge.c @@ -45,6 +45,7 @@ void vmg_set_range(struct vma_merge_struct *vmg, unsigned long start, vmg->start = start; vmg->end = end; vmg->pgoff = pgoff; + vmg->anon_pgoff = start >> PAGE_SHIFT; vmg->vma_flags = vma_flags; vmg->just_expand = false; @@ -108,6 +109,7 @@ static bool test_simple_merge(void) .end = 0x2000, .vma_flags = vma_flags, .pgoff = 1, + .anon_pgoff = 1, }; ASSERT_FALSE(attach_vma(&mm, vma_left)); @@ -1431,7 +1433,7 @@ static bool test_expand_only_mode(void) struct mm_struct mm = {}; VMA_ITERATOR(vmi, &mm, 0); struct vm_area_struct *vma_prev, *vma; - VMG_STATE(vmg, &mm, &vmi, 0x5000, 0x9000, vma_flags, 5); + VMG_STATE(vmg, &mm, &vmi, 0x5000, 0x9000, vma_flags, 5, 5); /* * Place a VMA prior to the one we're expanding so we assert that we do diff --git a/tools/testing/vma/tests/vma.c b/tools/testing/vma/tests/vma.c index 0d40d7ba2181..c8ef7b8cd46b 100644 --- a/tools/testing/vma/tests/vma.c +++ b/tools/testing/vma/tests/vma.c @@ -40,7 +40,7 @@ static bool test_copy_vma(void) vma = alloc_and_link_vma(&mm, 0x1000, 0x2000, 1, vma_flags); vma_set_anonymous(vma); vma_orig = vma; - vma_new = copy_vma(&vma, 0x2000, 0x1000, 1, &need_locks); + vma_new = copy_vma(&vma, 0x2000, 0x1000, 1, 1, &need_locks); ASSERT_EQ(vma_new, vma_orig); ASSERT_EQ(vma, vma_orig); ASSERT_EQ(vma_new->vm_start, 0x1000); @@ -53,7 +53,7 @@ static bool test_copy_vma(void) vma = alloc_and_link_vma(&mm, 0x2000, 0x3000, 2, vma_flags); vma_set_anonymous(vma); vma_orig = vma; - vma_new = copy_vma(&vma, 0x1000, 0x1000, 2, &need_locks); + vma_new = copy_vma(&vma, 0x1000, 0x1000, 2, 2, &need_locks); ASSERT_EQ(vma_new, vma_orig); ASSERT_EQ(vma, vma_orig); ASSERT_EQ(vma_new->vm_start, 0x1000); @@ -71,7 +71,7 @@ static bool test_copy_vma(void) vma = alloc_and_link_vma(&mm, 0x3000, 0x4000, 3, vma_flags); vma_set_anonymous(vma); vma_orig = vma; - vma_new = copy_vma(&vma, 0x2000, 0x1000, 3, &need_locks); + vma_new = copy_vma(&vma, 0x2000, 0x1000, 3, 3, &need_locks); ASSERT_NE(vma_new, vma_orig); ASSERT_EQ(vma_new, vma); ASSERT_EQ(vma_new->vm_start, 0x1000); @@ -82,7 +82,7 @@ static bool test_copy_vma(void) /* Move backwards and do not merge. */ vma = alloc_and_link_vma(&mm, 0x3000, 0x5000, 3, vma_flags); - vma_new = copy_vma(&vma, 0, 0x2000, 0, &need_locks); + vma_new = copy_vma(&vma, 0, 0x2000, 0, 3, &need_locks); ASSERT_NE(vma_new, vma); ASSERT_EQ(vma_new->vm_start, 0); ASSERT_EQ(vma_new->vm_end, 0x2000); @@ -95,7 +95,7 @@ static bool test_copy_vma(void) vma = alloc_and_link_vma(&mm, 0, 0x2000, 0, vma_flags); vma_next = alloc_and_link_vma(&mm, 0x6000, 0x8000, 6, vma_flags); - vma_new = copy_vma(&vma, 0x4000, 0x2000, 4, &need_locks); + vma_new = copy_vma(&vma, 0x4000, 0x2000, 4, 4, &need_locks); vma_assert_attached(vma_new); ASSERT_EQ(vma_new, vma_next); From 50c5f35a64aad82e3fd22e26f2b9c9c7395fa206 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Thu, 13 Aug 2026 18:32:27 +0100 Subject: [PATCH 0973/1328] mm/rmap: track whether the page VMA mapped pgoff is anonymous MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update the page_vma_mapped_walk structure to track whether the pgoff being tracked is an anonymous pgoff or not and update the comments to reflect this. This is necessary in order to determine the correct VMA page offset in vma_address_end() when pvmw->nr_pages > 1. Also document that pvmw->pgoff is meaningless for pvmw->nr_pages == 1 and for KSM. Do not set this field where pgoff is not specified. This is laying the groundwork for eventually using anonymous page offsets as the index for all anonymous folios. No functional change intended. Link: https://lore.kernel.org/20260813-b4-scalable-cow-virt-pgoff-v5-10-c21581c0c3c8@kernel.org Signed-off-by: Lorenzo Stoakes (ARM) Acked-by: David Hildenbrand (Arm) Cc: Adrian Hunter Cc: Alexander Deucher Cc: Alexander Gordeev Cc: Alexander Shishkin Cc: Alistair Popple Cc: Arnaldo Carvalho de Melo Cc: Arnd Bergmann Cc: Baolin Wang Cc: Baoquan He Cc: Barry Song Cc: Boris Brezillon Cc: Byungchul Park Cc: Chengming Zhou Cc: Chris Li Cc: Christan König Cc: Christian Borntraeger Cc: Claudio Imbrenda Cc: Dave Airlie Cc: Dev Jain Cc: Gerald Schaefer Cc: Greg Kroah-Hartman Cc: Gregory Price (Meta) Cc: Harry Yoo Cc: Heiko Carstens Cc: Huang Ray Cc: "Huang, Ying" Cc: Ian Rogers Cc: Ingo Molnar Cc: James Clark Cc: Jan Kara Cc: Jann Horn Cc: Janosch Frank Cc: Jason Gunthorpe Cc: Jiri Olsa Cc: John Hubbard Cc: Joshua Hahn Cc: Kairui Song Cc: Kees Cook Cc: Kemeng Shi Cc: Lance Yang Cc: Liam R. Howlett Cc: Liviu Dudau Cc: Maarten Lankhorst Cc: Marc Rutland Cc: "Masami Hiramatsu (Google)" Cc: Matthew Auld Cc: Matthew Brost Cc: Matthew Wilcox (Oracle) Cc: Maxime Ripard Cc: Miaohe Lin Cc: Michal Hocko Cc: Mike Rapoport Cc: Muchun Song Cc: Namhyung kim Cc: Naoya Horiguchi Cc: Nhat Pham Cc: Nico Pache Cc: Oleg Nesterov Cc: Oscar Salvador Cc: Pedro Falcato Cc: Peter Xu Cc: Peter Zijlstra Cc: Rakie Kim Cc: Rik van Riel Cc: Rodrigo Vivi Cc: Ryan Roberts Cc: Steven Price Cc: Suren Baghdasaryan Cc: Sven Schnelle Cc: Thomas Hellström Cc: Thomas Zimemrmann Cc: Vasily Gorbik Cc: Vlastimil Babka Cc: xu xin Cc: Zi Yan Signed-off-by: Andrew Morton --- include/linux/rmap.h | 4 +++- mm/rmap.c | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/include/linux/rmap.h b/include/linux/rmap.h index 8dc0871e5f00..0574537a355c 100644 --- a/include/linux/rmap.h +++ b/include/linux/rmap.h @@ -864,13 +864,14 @@ struct page *make_device_exclusive(struct mm_struct *mm, unsigned long addr, struct page_vma_mapped_walk { unsigned long pfn; unsigned long nr_pages; - pgoff_t pgoff; + pgoff_t pgoff; /* Only meaningful if nr_pages > 1 and not a KSM walk */ struct vm_area_struct *vma; unsigned long address; pmd_t *pmd; pte_t *pte; spinlock_t *ptl; unsigned int flags; + bool pgoff_is_anon : 1; }; #define DEFINE_FOLIO_VMA_WALK(name, _folio, _vma, _address, _flags) \ @@ -881,6 +882,7 @@ struct page_vma_mapped_walk { .vma = _vma, \ .address = _address, \ .flags = _flags, \ + .pgoff_is_anon = folio_test_anon(_folio), \ } static inline void page_vma_mapped_walk_done(struct page_vma_mapped_walk *pvmw) diff --git a/mm/rmap.c b/mm/rmap.c index bf618e4678d3..1b23ac709f45 100644 --- a/mm/rmap.c +++ b/mm/rmap.c @@ -1240,6 +1240,7 @@ static bool mapping_wrprotect_range_one(struct folio *folio, .vma = vma, .address = address, .flags = PVMW_SYNC, + .pgoff_is_anon = false, }; state->cleaned += page_vma_mkclean_one(&pvmw); @@ -1317,6 +1318,7 @@ int pfn_mkclean_range(unsigned long pfn, unsigned long nr_pages, pgoff_t pgoff, .pgoff = pgoff, .vma = vma, .flags = PVMW_SYNC, + .pgoff_is_anon = false, }; if (invalid_mkclean_vma(vma, NULL)) From e7006fbd608f490f1a687df9d724de4454f8e164 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Thu, 13 Aug 2026 18:32:28 +0100 Subject: [PATCH 0974/1328] mm: clean up vma_address_end() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vma_address_end() is a confusing function with a lot of moving parts so clean it up prior to extending it for anon page indexed mappings. Const-ify some variables and establish pgoff_vma_start and pgoff_end variables to clearly identify the page offset for the start of the VMA and the end of the page offset range specified by the page walk. This simplifies the function significantly and lays the groundwork for a future change to update this function to account for anonymously page indexed folios. No functional change intended. Link: https://lore.kernel.org/20260813-b4-scalable-cow-virt-pgoff-v5-11-c21581c0c3c8@kernel.org Signed-off-by: Lorenzo Stoakes (ARM) Acked-by: David Hildenbrand (Arm) Cc: Adrian Hunter Cc: Alexander Deucher Cc: Alexander Gordeev Cc: Alexander Shishkin Cc: Alistair Popple Cc: Arnaldo Carvalho de Melo Cc: Arnd Bergmann Cc: Baolin Wang Cc: Baoquan He Cc: Barry Song Cc: Boris Brezillon Cc: Byungchul Park Cc: Chengming Zhou Cc: Chris Li Cc: Christan König Cc: Christian Borntraeger Cc: Claudio Imbrenda Cc: Dave Airlie Cc: Dev Jain Cc: Gerald Schaefer Cc: Greg Kroah-Hartman Cc: Gregory Price (Meta) Cc: Harry Yoo Cc: Heiko Carstens Cc: Huang Ray Cc: "Huang, Ying" Cc: Ian Rogers Cc: Ingo Molnar Cc: James Clark Cc: Jan Kara Cc: Jann Horn Cc: Janosch Frank Cc: Jason Gunthorpe Cc: Jiri Olsa Cc: John Hubbard Cc: Joshua Hahn Cc: Kairui Song Cc: Kees Cook Cc: Kemeng Shi Cc: Lance Yang Cc: Liam R. Howlett Cc: Liviu Dudau Cc: Maarten Lankhorst Cc: Marc Rutland Cc: "Masami Hiramatsu (Google)" Cc: Matthew Auld Cc: Matthew Brost Cc: Matthew Wilcox (Oracle) Cc: Maxime Ripard Cc: Miaohe Lin Cc: Michal Hocko Cc: Mike Rapoport Cc: Muchun Song Cc: Namhyung kim Cc: Naoya Horiguchi Cc: Nhat Pham Cc: Nico Pache Cc: Oleg Nesterov Cc: Oscar Salvador Cc: Pedro Falcato Cc: Peter Xu Cc: Peter Zijlstra Cc: Rakie Kim Cc: Rik van Riel Cc: Rodrigo Vivi Cc: Ryan Roberts Cc: Steven Price Cc: Suren Baghdasaryan Cc: Sven Schnelle Cc: Thomas Hellström Cc: Thomas Zimemrmann Cc: Vasily Gorbik Cc: Vlastimil Babka Cc: xu xin Cc: Zi Yan Signed-off-by: Andrew Morton --- mm/internal.h | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/mm/internal.h b/mm/internal.h index f6475d77024d..a38b5f7896b2 100644 --- a/mm/internal.h +++ b/mm/internal.h @@ -1091,22 +1091,24 @@ static inline unsigned long vma_anon_address(const struct vm_area_struct *vma, } /* - * Then at what user virtual address will none of the range be found in vma? + * At what user virtual address will none of the range be found in vma? * Assumes that vma_address() already returned a good starting address. */ static inline unsigned long vma_address_end(struct page_vma_mapped_walk *pvmw) { - struct vm_area_struct *vma = pvmw->vma; - pgoff_t pgoff; + const pgoff_t pgoff_end = pvmw->pgoff + pvmw->nr_pages; + const struct vm_area_struct *vma = pvmw->vma; + pgoff_t pgoff_vma_start; unsigned long address; /* Common case, plus ->pgoff is invalid for KSM */ if (pvmw->nr_pages == 1) return pvmw->address + PAGE_SIZE; - pgoff = pvmw->pgoff + pvmw->nr_pages; + pgoff_vma_start = vma_start_pgoff(vma); + address = vma->vm_start + - ((pgoff - vma_start_pgoff(vma)) << PAGE_SHIFT); + ((pgoff_end - pgoff_vma_start) << PAGE_SHIFT); /* Check for address beyond vma (or wrapped through 0?) */ if (address < vma->vm_start || address > vma->vm_end) address = vma->vm_end; From 8e658ecc3be21e43573e6639af2d1c536bbfe67d Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Thu, 13 Aug 2026 18:32:29 +0100 Subject: [PATCH 0975/1328] mm/huge_memory: update remove_migration_pmd() to accept a folio MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This function does not need to accept a page and requiring it to is unnecessary and misleading. make_[writable, readable]_device_private_entry() must be passed a PMD-aligned PFN as they immediately used to obtain a softleaf PMD entry and the same argument applies to folio_add_[anon, file]_rmap_pmd(). While we are here, update a VM_BUG_ON() to a VM_WARN_ON_ONCE(). No functional change intended. Link: https://lore.kernel.org/20260813-b4-scalable-cow-virt-pgoff-v5-12-c21581c0c3c8@kernel.org Signed-off-by: Lorenzo Stoakes (ARM) Acked-by: David Hildenbrand (Arm) Cc: Adrian Hunter Cc: Alexander Deucher Cc: Alexander Gordeev Cc: Alexander Shishkin Cc: Alistair Popple Cc: Arnaldo Carvalho de Melo Cc: Arnd Bergmann Cc: Baolin Wang Cc: Baoquan He Cc: Barry Song Cc: Boris Brezillon Cc: Byungchul Park Cc: Chengming Zhou Cc: Chris Li Cc: Christan König Cc: Christian Borntraeger Cc: Claudio Imbrenda Cc: Dave Airlie Cc: Dev Jain Cc: Gerald Schaefer Cc: Greg Kroah-Hartman Cc: Gregory Price (Meta) Cc: Harry Yoo Cc: Heiko Carstens Cc: Huang Ray Cc: "Huang, Ying" Cc: Ian Rogers Cc: Ingo Molnar Cc: James Clark Cc: Jan Kara Cc: Jann Horn Cc: Janosch Frank Cc: Jason Gunthorpe Cc: Jiri Olsa Cc: John Hubbard Cc: Joshua Hahn Cc: Kairui Song Cc: Kees Cook Cc: Kemeng Shi Cc: Lance Yang Cc: Liam R. Howlett Cc: Liviu Dudau Cc: Maarten Lankhorst Cc: Marc Rutland Cc: "Masami Hiramatsu (Google)" Cc: Matthew Auld Cc: Matthew Brost Cc: Matthew Wilcox (Oracle) Cc: Maxime Ripard Cc: Miaohe Lin Cc: Michal Hocko Cc: Mike Rapoport Cc: Muchun Song Cc: Namhyung kim Cc: Naoya Horiguchi Cc: Nhat Pham Cc: Nico Pache Cc: Oleg Nesterov Cc: Oscar Salvador Cc: Pedro Falcato Cc: Peter Xu Cc: Peter Zijlstra Cc: Rakie Kim Cc: Rik van Riel Cc: Rodrigo Vivi Cc: Ryan Roberts Cc: Steven Price Cc: Suren Baghdasaryan Cc: Sven Schnelle Cc: Thomas Hellström Cc: Thomas Zimemrmann Cc: Vasily Gorbik Cc: Vlastimil Babka Cc: xu xin Cc: Zi Yan Signed-off-by: Andrew Morton --- include/linux/swapops.h | 6 +++--- mm/huge_memory.c | 16 +++++++--------- mm/migrate.c | 2 +- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/include/linux/swapops.h b/include/linux/swapops.h index c956bc445ee0..1f3ff3b93e16 100644 --- a/include/linux/swapops.h +++ b/include/linux/swapops.h @@ -325,8 +325,8 @@ struct page_vma_mapped_walk; extern int set_pmd_migration_entry(struct page_vma_mapped_walk *pvmw, struct page *page); -extern void remove_migration_pmd(struct page_vma_mapped_walk *pvmw, - struct page *new); +void remove_migration_pmd(struct page_vma_mapped_walk *pvmw, + struct folio *folio); extern void pmd_migration_entry_wait(struct mm_struct *mm, pmd_t *pmd); @@ -346,7 +346,7 @@ static inline int set_pmd_migration_entry(struct page_vma_mapped_walk *pvmw, } static inline void remove_migration_pmd(struct page_vma_mapped_walk *pvmw, - struct page *new) + struct folio *folio) { BUILD_BUG(); } diff --git a/mm/huge_memory.c b/mm/huge_memory.c index ff13b57d9d56..2822190daf2b 100644 --- a/mm/huge_memory.c +++ b/mm/huge_memory.c @@ -5077,9 +5077,8 @@ int set_pmd_migration_entry(struct page_vma_mapped_walk *pvmw, return 0; } -void remove_migration_pmd(struct page_vma_mapped_walk *pvmw, struct page *new) +void remove_migration_pmd(struct page_vma_mapped_walk *pvmw, struct folio *folio) { - struct folio *folio = page_folio(new); struct vm_area_struct *vma = pvmw->vma; struct mm_struct *mm = vma->vm_mm; unsigned long address = pvmw->address; @@ -5115,11 +5114,9 @@ void remove_migration_pmd(struct page_vma_mapped_walk *pvmw, struct page *new) swp_entry_t entry; if (pmd_write(pmde)) - entry = make_writable_device_private_entry( - page_to_pfn(new)); + entry = make_writable_device_private_entry(folio_pfn(folio)); else - entry = make_readable_device_private_entry( - page_to_pfn(new)); + entry = make_readable_device_private_entry(folio_pfn(folio)); pmde = softleaf_to_pmd(entry); if (pmd_swp_soft_dirty(*pvmw->pmd)) @@ -5134,11 +5131,12 @@ void remove_migration_pmd(struct page_vma_mapped_walk *pvmw, struct page *new) if (!softleaf_is_migration_read(entry)) rmap_flags |= RMAP_EXCLUSIVE; - folio_add_anon_rmap_pmd(folio, new, vma, haddr, rmap_flags); + folio_add_anon_rmap_pmd(folio, &folio->page, vma, haddr, rmap_flags); } else { - folio_add_file_rmap_pmd(folio, new, vma); + folio_add_file_rmap_pmd(folio, &folio->page, vma); } - VM_BUG_ON(pmd_write(pmde) && folio_test_anon(folio) && !PageAnonExclusive(new)); + VM_WARN_ON_ONCE(pmd_write(pmde) && folio_test_anon(folio) && + !PageAnonExclusive(&folio->page)); set_pmd_at(mm, haddr, pvmw->pmd, pmde); /* No need to invalidate - it was non-present before */ diff --git a/mm/migrate.c b/mm/migrate.c index 8aaafcea7bc1..9e32af3fe303 100644 --- a/mm/migrate.c +++ b/mm/migrate.c @@ -372,7 +372,7 @@ static bool remove_migration_pte(struct folio *folio, if (!pvmw.pte) { VM_BUG_ON_FOLIO(folio_test_hugetlb(folio) || !folio_test_pmd_mappable(folio), folio); - remove_migration_pmd(&pvmw, new); + remove_migration_pmd(&pvmw, folio); continue; } #endif From 5f653f8b7a3469524b1dd9fbd4b803baa34f14a7 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Thu, 13 Aug 2026 18:32:30 +0100 Subject: [PATCH 0976/1328] mm/migrate: calculate large folio page index using PFN MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rather than having to figure out the page index to use using linear_page_index(), calculate it using PFN. This is a more natural fit as the linear page index is immaterial to determining the folio page index. Derive the page index from the offset between migration entry PFN and folio PFN - pvmw.pfn (set via DEFINE_FOLIO_VMA_WALK() which uses folio_pfn() to obtain it). Additionally remove a not so useful comment and clean the code layout up. No functional change intended. Link: https://lore.kernel.org/20260813-b4-scalable-cow-virt-pgoff-v5-13-c21581c0c3c8@kernel.org Signed-off-by: Lorenzo Stoakes (ARM) Suggested-by: David Hildenbrand (Arm) Acked-by: David Hildenbrand (Arm) Cc: Adrian Hunter Cc: Alexander Deucher Cc: Alexander Gordeev Cc: Alexander Shishkin Cc: Alistair Popple Cc: Arnaldo Carvalho de Melo Cc: Arnd Bergmann Cc: Baolin Wang Cc: Baoquan He Cc: Barry Song Cc: Boris Brezillon Cc: Byungchul Park Cc: Chengming Zhou Cc: Chris Li Cc: Christan König Cc: Christian Borntraeger Cc: Claudio Imbrenda Cc: Dave Airlie Cc: Dev Jain Cc: Gerald Schaefer Cc: Greg Kroah-Hartman Cc: Gregory Price (Meta) Cc: Harry Yoo Cc: Heiko Carstens Cc: Huang Ray Cc: "Huang, Ying" Cc: Ian Rogers Cc: Ingo Molnar Cc: James Clark Cc: Jan Kara Cc: Jann Horn Cc: Janosch Frank Cc: Jason Gunthorpe Cc: Jiri Olsa Cc: John Hubbard Cc: Joshua Hahn Cc: Kairui Song Cc: Kees Cook Cc: Kemeng Shi Cc: Lance Yang Cc: Liam R. Howlett Cc: Liviu Dudau Cc: Maarten Lankhorst Cc: Marc Rutland Cc: "Masami Hiramatsu (Google)" Cc: Matthew Auld Cc: Matthew Brost Cc: Matthew Wilcox (Oracle) Cc: Maxime Ripard Cc: Miaohe Lin Cc: Michal Hocko Cc: Mike Rapoport Cc: Muchun Song Cc: Namhyung kim Cc: Naoya Horiguchi Cc: Nhat Pham Cc: Nico Pache Cc: Oleg Nesterov Cc: Oscar Salvador Cc: Pedro Falcato Cc: Peter Xu Cc: Peter Zijlstra Cc: Rakie Kim Cc: Rik van Riel Cc: Rodrigo Vivi Cc: Ryan Roberts Cc: Steven Price Cc: Suren Baghdasaryan Cc: Sven Schnelle Cc: Thomas Hellström Cc: Thomas Zimemrmann Cc: Vasily Gorbik Cc: Vlastimil Babka Cc: xu xin Cc: Zi Yan Signed-off-by: Andrew Morton --- mm/migrate.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/mm/migrate.c b/mm/migrate.c index 9e32af3fe303..15b45832bcfa 100644 --- a/mm/migrate.c +++ b/mm/migrate.c @@ -356,16 +356,11 @@ static bool remove_migration_pte(struct folio *folio, while (page_vma_mapped_walk(&pvmw)) { rmap_t rmap_flags = RMAP_NONE; - pte_t old_pte; - pte_t pte; + unsigned long idx = 0; softleaf_t entry; struct page *new; - unsigned long idx = 0; - - /* pgoff is invalid for ksm pages, but they are never large */ - if (folio_test_large(folio) && !folio_test_hugetlb(folio)) - idx = linear_page_index(vma, pvmw.address) - pvmw.pgoff; - new = folio_page(folio, idx); + pte_t old_pte; + pte_t pte; #ifdef CONFIG_ARCH_HAS_PMD_SOFTLEAVES /* PMD-mapped THP migration entry */ @@ -381,14 +376,18 @@ static bool remove_migration_pte(struct folio *folio, pvmw.pte); else old_pte = ptep_get(pvmw.pte); + + entry = softleaf_from_pte(old_pte); + if (folio_test_large(folio) && !folio_test_hugetlb(folio)) + idx = softleaf_to_pfn(entry) - pvmw.pfn; + if (rmap_walk_arg->map_unused_to_zeropage && try_to_map_unused_to_zeropage(&pvmw, folio, old_pte, idx)) continue; folio_get(folio); + new = folio_page(folio, idx); pte = mk_pte(new, READ_ONCE(vma->vm_page_prot)); - - entry = softleaf_from_pte(old_pte); if (!softleaf_is_migration_young(entry)) pte = pte_mkold(pte); if (folio_test_dirty(folio) && softleaf_is_migration_dirty(entry)) From 93c0c8dc87f6eb9f6ce71c4ac379bef88965192c Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Thu, 13 Aug 2026 18:32:31 +0100 Subject: [PATCH 0977/1328] mm/rmap: use anon pgoff to track MAP_PRIVATE file-backed anon folios MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently anonymous folios belonging to CoW'd MAP_PRIVATE file-backed mappings are indexed by their page offset within the file in which they were originally mapped. This differs from anonymous folios belonging to pure anon mappings which are indexed by their anonymous page offset (the address at which they'd belong in the VMA when first faulted). This change fixes this inconsistency, always indexing anonymous folios by their anonymous page offset regardless of the VMA to which they belong. The foundations have been laid such that we need only switch this functionality on such by: * Using linear_anon_page_index() in __folio_set_anon() to assign the folio's index to the anonymous linear index rather than the file-backed one. * Otherwise using linear_anon_page_index() in all instances where anonymous folios are being referenced or manipulated. * Replacing vma_address() with vma_filebacked_address() or vma_anon_address() as appropriate. * Updating the merging logic to check that anonymous page offsets are aligned as well as filebacked ones for MAP_PRIVATE file-backed VMAs, introducing needs_adjacent_anon_pgoff() to figure out when this is required. * Updating linear_folio_page_index() to invoke linear_anon_page_index() if the folio is anonymous. * Updating vma_address_end() to use the VMA's anonymous page offset when pvmw->pgoff is anonymous. * Correcting folio_within_range() to use anonymous page offset for anonymous folios. This will have no impact on merging of anonymous VMAs, whose page offset and anonymous page offset are identical, nor will it impact shared file-backed VMAs, which will continue to be merged based on the file-backed page offset. However, MAP_PRIVATE file-backed mappings must now be aligned on anonymous page offset as well. In most instances this should have no impact on merging of file-backed mappings, which are usually not merged all that often, let alone MAP_PRIVATE mapped ones, and rarely remapped and faulted before being moved back in place (the case in which a merge may now fail). One subtle impact of this change is in NUMA interleaving - since commit 88c91dc58582 ("mempolicy: migration attempt to match interleave nodes"), migration heuristically tries to maintain interleaving behaviour matching the policy using folio indices. When doing migration of CoW'd MAP_PRIVATE-file backed ranges, the 'base' upon which the interleaving behaviour is performed will vary for these ranges. However the commit notes that ranges spanning multiple VMAs will already cause varying bases, and that this is an acceptable approximation. It is very unlikely real world use-cases will be impacted by this (MAP_PRIVATE file-backed mappings are already an edge case), and all that will happen is that such ranges will cause interleaving to be rotated over the CoW'd range, with little to no impact. This commit lays the foundations for future scalable CoW work which needs to track some remaps, meaning that most remap tracking can be avoided, and in nearly all cases the anonymous page offset will be able to be used to quickly find the VMA in an mm. Note that the need_rmap_locks check doesn't need to be updated, as any remapping will offset both the anonymous and file-backed page offset, so it suffices to check only one. Link: https://lore.kernel.org/20260813-b4-scalable-cow-virt-pgoff-v5-14-c21581c0c3c8@kernel.org Signed-off-by: Lorenzo Stoakes (ARM) Acked-by: David Hildenbrand (Arm) Cc: Adrian Hunter Cc: Alexander Deucher Cc: Alexander Gordeev Cc: Alexander Shishkin Cc: Alistair Popple Cc: Arnaldo Carvalho de Melo Cc: Arnd Bergmann Cc: Baolin Wang Cc: Baoquan He Cc: Barry Song Cc: Boris Brezillon Cc: Byungchul Park Cc: Chengming Zhou Cc: Chris Li Cc: Christan König Cc: Christian Borntraeger Cc: Claudio Imbrenda Cc: Dave Airlie Cc: Dev Jain Cc: Gerald Schaefer Cc: Greg Kroah-Hartman Cc: Gregory Price (Meta) Cc: Harry Yoo Cc: Heiko Carstens Cc: Huang Ray Cc: "Huang, Ying" Cc: Ian Rogers Cc: Ingo Molnar Cc: James Clark Cc: Jan Kara Cc: Jann Horn Cc: Janosch Frank Cc: Jason Gunthorpe Cc: Jiri Olsa Cc: John Hubbard Cc: Joshua Hahn Cc: Kairui Song Cc: Kees Cook Cc: Kemeng Shi Cc: Lance Yang Cc: Liam R. Howlett Cc: Liviu Dudau Cc: Maarten Lankhorst Cc: Marc Rutland Cc: "Masami Hiramatsu (Google)" Cc: Matthew Auld Cc: Matthew Brost Cc: Matthew Wilcox (Oracle) Cc: Maxime Ripard Cc: Miaohe Lin Cc: Michal Hocko Cc: Mike Rapoport Cc: Muchun Song Cc: Namhyung kim Cc: Naoya Horiguchi Cc: Nhat Pham Cc: Nico Pache Cc: Oleg Nesterov Cc: Oscar Salvador Cc: Pedro Falcato Cc: Peter Xu Cc: Peter Zijlstra Cc: Rakie Kim Cc: Rik van Riel Cc: Rodrigo Vivi Cc: Ryan Roberts Cc: Steven Price Cc: Suren Baghdasaryan Cc: Sven Schnelle Cc: Thomas Hellström Cc: Thomas Zimemrmann Cc: Vasily Gorbik Cc: Vlastimil Babka Cc: xu xin Cc: Zi Yan Signed-off-by: Andrew Morton --- mm/huge_memory.c | 2 +- mm/internal.h | 27 ++++++++------------------- mm/interval_tree.c | 4 ++-- mm/ksm.c | 6 +++--- mm/page_vma_mapped.c | 2 +- mm/rmap.c | 12 ++++++------ mm/userfaultfd.c | 4 ++-- mm/vma.c | 32 +++++++++++++++++++++++++++++++- 8 files changed, 54 insertions(+), 35 deletions(-) diff --git a/mm/huge_memory.c b/mm/huge_memory.c index 2822190daf2b..a8174d1d3848 100644 --- a/mm/huge_memory.c +++ b/mm/huge_memory.c @@ -2930,7 +2930,7 @@ int move_pages_huge_pmd(struct mm_struct *mm, pmd_t *dst_pmd, pmd_t *src_pmd, pm } folio_move_anon_rmap(src_folio, dst_vma); - src_folio->index = linear_page_index(dst_vma, dst_addr); + src_folio->index = linear_anon_page_index(dst_vma, dst_addr); _dst_pmd = folio_mk_pmd(src_folio, dst_vma->vm_page_prot); /* Follow mremap() behavior and treat the entry dirty after the move */ diff --git a/mm/internal.h b/mm/internal.h index a38b5f7896b2..16750b130ec4 100644 --- a/mm/internal.h +++ b/mm/internal.h @@ -945,7 +945,8 @@ folio_within_range(struct folio *folio, struct vm_area_struct *vma, return false; pgoff_folio = folio_pgoff(folio); - pgoff_vma_start = vma_start_pgoff(vma); + pgoff_vma_start = folio_test_anon(folio) ? + vma_start_anon_pgoff(vma) : vma_start_pgoff(vma); if (start < vma->vm_start) start = vma->vm_start; @@ -1056,23 +1057,8 @@ static inline unsigned long vma_filebacked_address(const struct vm_area_struct * } /** - * vma_address - Find the virtual address a page range is mapped at. - * @vma: The vma which maps this object. - * @pgoff: The page offset within its object. - * @nr_pages: The number of pages to consider. - * - * If any page in this range is mapped by this VMA, return the first address - * where any of these pages appear. Otherwise, return -EFAULT. - */ -static inline unsigned long vma_address(const struct vm_area_struct *vma, - pgoff_t pgoff, unsigned long nr_pages) -{ - return __vma_address(vma, pgoff, vma_start_pgoff(vma), nr_pages); -} - -/** - * vma_anon_address - Find the address an anonymous folio with index @pgoff_anon - * is mapped at. + * vma_anon_address - Find the virtual address an anonymous page range is mapped + * at. * @vma: The vma which maps this object. * @pgoff_anon: The anonymous page index belonging to the folio. * @nr_pages: The number of pages to consider. @@ -1105,7 +1091,10 @@ static inline unsigned long vma_address_end(struct page_vma_mapped_walk *pvmw) if (pvmw->nr_pages == 1) return pvmw->address + PAGE_SIZE; - pgoff_vma_start = vma_start_pgoff(vma); + if (pvmw->pgoff_is_anon) + pgoff_vma_start = vma_start_anon_pgoff(vma); + else + pgoff_vma_start = vma_start_pgoff(vma); address = vma->vm_start + ((pgoff_end - pgoff_vma_start) << PAGE_SHIFT); diff --git a/mm/interval_tree.c b/mm/interval_tree.c index 3ae9e106d3af..7bbbf15cfbf0 100644 --- a/mm/interval_tree.c +++ b/mm/interval_tree.c @@ -83,12 +83,12 @@ mapping_rmap_tree_iter_next(struct vm_area_struct *vma, static pgoff_t avc_start_pgoff(struct anon_vma_chain *avc) { - return vma_start_pgoff(avc->vma); + return vma_start_anon_pgoff(avc->vma); } static pgoff_t avc_last_pgoff(struct anon_vma_chain *avc) { - return vma_last_pgoff(avc->vma); + return vma_last_anon_pgoff(avc->vma); } INTERVAL_TREE_DEFINE(struct anon_vma_chain, rb, pgoff_t, rb_subtree_last, diff --git a/mm/ksm.c b/mm/ksm.c index b4142746777e..b5854dc14a2e 100644 --- a/mm/ksm.c +++ b/mm/ksm.c @@ -1625,7 +1625,7 @@ static int try_to_merge_with_ksm_page(struct ksm_rmap_item *rmap_item, * stable_tree, break_cow() will clean it up. */ rmap_item->anon_vma = vma->anon_vma; - rmap_item->linear_page_index = linear_page_index(vma, rmap_item->address); + rmap_item->linear_page_index = linear_anon_page_index(vma, rmap_item->address); get_anon_vma(vma->anon_vma); out: mmap_read_unlock(mm); @@ -3152,7 +3152,7 @@ struct folio *ksm_might_need_to_copy(struct folio *folio, return folio; /* no need to copy it */ } else if (!anon_vma) { return folio; /* no need to copy it */ - } else if (folio->index == linear_page_index(vma, addr) && + } else if (folio->index == linear_anon_page_index(vma, addr) && anon_vma->root == vma->anon_vma->root) { return folio; /* still no need to copy it */ } @@ -3222,7 +3222,7 @@ void rmap_walk_ksm(struct folio *folio, struct rmap_walk_control *rwc) /* * Currently, KSM folios are always small folios, so it's * sufficient to search for a single page. We can simply use - * the linear_page_index of the original de-duplicate + * the linear_anon_page_index of the original de-duplicate * anonymous page that we remembered in the rmap_item while * de-duplicating. Note that mremap() always de-duplicates KSM * folios: so if there was mremap() in our parent or our child, diff --git a/mm/page_vma_mapped.c b/mm/page_vma_mapped.c index 081e483cc7bf..4e964545e5e8 100644 --- a/mm/page_vma_mapped.c +++ b/mm/page_vma_mapped.c @@ -365,7 +365,7 @@ unsigned long page_mapped_in_vma(const struct page *page, }; if (folio_test_anon(folio)) - pvmw.address = vma_address(vma, pgoff, 1); + pvmw.address = vma_anon_address(vma, pgoff, 1); else pvmw.address = vma_filebacked_address(vma, pgoff, 1); if (pvmw.address == -EFAULT) diff --git a/mm/rmap.c b/mm/rmap.c index 1b23ac709f45..34ceeb600111 100644 --- a/mm/rmap.c +++ b/mm/rmap.c @@ -866,7 +866,7 @@ unsigned long page_address_in_vma(const struct folio *folio, vma->anon_vma->root != anon_vma->root) return -EFAULT; /* KSM folios don't reach here because of the !anon_vma check */ - return vma_address(vma, page_pgoff(folio, page), 1); + return vma_anon_address(vma, page_pgoff(folio, page), 1); } else if (!vma->vm_file) { return -EFAULT; } else if (vma->vm_file->f_mapping != folio->mapping) { @@ -1485,7 +1485,7 @@ static void __folio_set_anon(struct folio *folio, struct vm_area_struct *vma, */ anon_vma = (void *) anon_vma + FOLIO_MAPPING_ANON; WRITE_ONCE(folio->mapping, (struct address_space *) anon_vma); - folio->index = linear_page_index(vma, address); + folio->index = linear_anon_page_index(vma, address); } /** @@ -1512,8 +1512,8 @@ static void __page_check_anon_rmap(const struct folio *folio, */ VM_BUG_ON_FOLIO(folio_anon_vma(folio)->root != vma->anon_vma->root, folio); - VM_BUG_ON_PAGE(page_pgoff(folio, page) != linear_page_index(vma, address), - page); + VM_BUG_ON_PAGE(page_pgoff(folio, page) != + linear_anon_page_index(vma, address), page); } static __always_inline void __folio_add_anon_rmap(struct folio *folio, @@ -3038,10 +3038,10 @@ static void rmap_walk_anon(struct folio *folio, pgoff_end = pgoff_start + folio_nr_pages(folio) - 1; anon_rmap_tree_foreach(avc, anon_vma, pgoff_start, pgoff_end) { struct vm_area_struct *vma = avc->vma; - unsigned long address = vma_address(vma, pgoff_start, + const unsigned long address = vma_anon_address(vma, pgoff_start, folio_nr_pages(folio)); - VM_BUG_ON_VMA(address == -EFAULT, vma); + VM_WARN_ON_ONCE_VMA(address == -EFAULT, vma); cond_resched(); if (rwc->invalid_vma && rwc->invalid_vma(vma, rwc->arg)) diff --git a/mm/userfaultfd.c b/mm/userfaultfd.c index de4cc2483562..23fb68fce000 100644 --- a/mm/userfaultfd.c +++ b/mm/userfaultfd.c @@ -1352,7 +1352,7 @@ static long move_present_ptes(struct mm_struct *mm, } folio_move_anon_rmap(src_folio, dst_vma); - src_folio->index = linear_page_index(dst_vma, dst_addr); + src_folio->index = linear_anon_page_index(dst_vma, dst_addr); orig_dst_pte = folio_mk_pte(src_folio, dst_vma->vm_page_prot); /* Set soft dirty bit so userspace can notice the pte was moved */ @@ -1428,7 +1428,7 @@ static int move_swap_pte(struct mm_struct *mm, struct vm_area_struct *dst_vma, */ if (src_folio) { folio_move_anon_rmap(src_folio, dst_vma); - src_folio->index = linear_page_index(dst_vma, dst_addr); + src_folio->index = linear_anon_page_index(dst_vma, dst_addr); } else { /* * Check if the swap entry is cached after acquiring the src_pte diff --git a/mm/vma.c b/mm/vma.c index e35b04ac12cb..35e7a64855fa 100644 --- a/mm/vma.c +++ b/mm/vma.c @@ -204,6 +204,25 @@ static void init_multi_vma_prep(struct vma_prepare *vp, vp->skip_vma_uprobe = true; } +/* + * Does this merge require that adjacent VMAs must have adjacent anonymous page + * offsets in addition to having adjacent vma->vm_pgoff? + * + * This is only required for MAP_PRIVATE-file backed mappings as the page offset + * for pure anonymous VMAs is equal to the anonymous page offset. + * + * Read-only shared mappings (with VMA_SHARED_BIT cleared) are always unfaulted + * so automatically have correct anonymous page offset (as it is always updated + * on remap). + * + * 'Special' mappings in the sense of VDSO, VVAR etc. have !file but would in + * any case not be candidates for merge nor be mergeable. + */ +static bool needs_adjacent_anon_pgoff(const struct vma_merge_struct *vmg) +{ + return vmg->file && vma_flags_is_cow_mapping(&vmg->vma_flags); +} + /* * Return true if we can merge this (vma_flags,anon_vma,file,vm_pgoff) * in front of (at a lower virtual address and file offset than) the vma. @@ -225,6 +244,9 @@ static bool can_vma_merge_before(struct vma_merge_struct *vmg) return false; if (vmg_end_pgoff(vmg) != vma_start_pgoff(vmg->next)) return false; + if (needs_adjacent_anon_pgoff(vmg) && + vmg_end_anon_pgoff(vmg) != vma_start_anon_pgoff(vmg->next)) + return false; return true; } @@ -245,6 +267,9 @@ static bool can_vma_merge_after(struct vma_merge_struct *vmg) return false; if (vma_end_pgoff(vmg->prev) != vmg_start_pgoff(vmg)) return false; + if (needs_adjacent_anon_pgoff(vmg) && + vma_end_anon_pgoff(vmg->prev) != vmg_start_anon_pgoff(vmg)) + return false; return true; } @@ -2048,7 +2073,12 @@ static int anon_vma_compatible(struct vm_area_struct *a, struct vm_area_struct * if (!vma_flags_empty(&diff)) return false; /* Page offset must align. */ - return vma_end_pgoff(a) == vma_start_pgoff(b); + if (vma_end_pgoff(a) != vma_start_pgoff(b)) + return false; + /* Only reached from anon path, so either MAP_PRIVATE file or anon. */ + if (vma_end_anon_pgoff(a) != vma_start_anon_pgoff(b)) + return false; + return true; } /* From 6b7460ad1af8b10cc6b629649979d9d0d844046e Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Thu, 13 Aug 2026 18:32:32 +0100 Subject: [PATCH 0978/1328] tools/testing/vma: expand VMA merge tests to assert anon pgoff MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now we have introduced the VMA anonymous page offset attribute and update it when VMAs are manipulated, update VMA merge tests to assert that the anonymous page offset is as expected. Also update instances where we could use vma_start_pgoff() to do so. Link: https://lore.kernel.org/20260813-b4-scalable-cow-virt-pgoff-v5-15-c21581c0c3c8@kernel.org Signed-off-by: Lorenzo Stoakes (ARM) Acked-by: David Hildenbrand (Arm) Cc: Adrian Hunter Cc: Alexander Deucher Cc: Alexander Gordeev Cc: Alexander Shishkin Cc: Alistair Popple Cc: Arnaldo Carvalho de Melo Cc: Arnd Bergmann Cc: Baolin Wang Cc: Baoquan He Cc: Barry Song Cc: Boris Brezillon Cc: Byungchul Park Cc: Chengming Zhou Cc: Chris Li Cc: Christan König Cc: Christian Borntraeger Cc: Claudio Imbrenda Cc: Dave Airlie Cc: Dev Jain Cc: Gerald Schaefer Cc: Greg Kroah-Hartman Cc: Gregory Price (Meta) Cc: Harry Yoo Cc: Heiko Carstens Cc: Huang Ray Cc: "Huang, Ying" Cc: Ian Rogers Cc: Ingo Molnar Cc: James Clark Cc: Jan Kara Cc: Jann Horn Cc: Janosch Frank Cc: Jason Gunthorpe Cc: Jiri Olsa Cc: John Hubbard Cc: Joshua Hahn Cc: Kairui Song Cc: Kees Cook Cc: Kemeng Shi Cc: Lance Yang Cc: Liam R. Howlett Cc: Liviu Dudau Cc: Maarten Lankhorst Cc: Marc Rutland Cc: "Masami Hiramatsu (Google)" Cc: Matthew Auld Cc: Matthew Brost Cc: Matthew Wilcox (Oracle) Cc: Maxime Ripard Cc: Miaohe Lin Cc: Michal Hocko Cc: Mike Rapoport Cc: Muchun Song Cc: Namhyung kim Cc: Naoya Horiguchi Cc: Nhat Pham Cc: Nico Pache Cc: Oleg Nesterov Cc: Oscar Salvador Cc: Pedro Falcato Cc: Peter Xu Cc: Peter Zijlstra Cc: Rakie Kim Cc: Rik van Riel Cc: Rodrigo Vivi Cc: Ryan Roberts Cc: Steven Price Cc: Suren Baghdasaryan Cc: Sven Schnelle Cc: Thomas Hellström Cc: Thomas Zimemrmann Cc: Vasily Gorbik Cc: Vlastimil Babka Cc: xu xin Cc: Zi Yan Signed-off-by: Andrew Morton --- tools/testing/vma/tests/merge.c | 45 ++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/tools/testing/vma/tests/merge.c b/tools/testing/vma/tests/merge.c index 48418b82b01d..acaab282939c 100644 --- a/tools/testing/vma/tests/merge.c +++ b/tools/testing/vma/tests/merge.c @@ -121,6 +121,7 @@ static bool test_simple_merge(void) ASSERT_EQ(vma->vm_start, 0); ASSERT_EQ(vma->vm_end, 0x3000); ASSERT_EQ(vma_start_pgoff(vma), 0); + ASSERT_EQ(vma_start_anon_pgoff(vma), 0); ASSERT_FLAGS_SAME_MASK(&vma->flags, vma_flags); detach_free_vma(vma); @@ -153,6 +154,7 @@ static bool test_simple_modify(void) ASSERT_EQ(vma->vm_start, 0x1000); ASSERT_EQ(vma->vm_end, 0x2000); ASSERT_EQ(vma_start_pgoff(vma), 1); + ASSERT_EQ(vma_start_anon_pgoff(vma), 1); /* * Now walk through the three split VMAs and make sure they are as @@ -165,6 +167,7 @@ static bool test_simple_modify(void) ASSERT_EQ(vma->vm_start, 0); ASSERT_EQ(vma->vm_end, 0x1000); ASSERT_EQ(vma_start_pgoff(vma), 0); + ASSERT_EQ(vma_start_anon_pgoff(vma), 0); detach_free_vma(vma); vma_iter_clear(&vmi); @@ -174,6 +177,7 @@ static bool test_simple_modify(void) ASSERT_EQ(vma->vm_start, 0x1000); ASSERT_EQ(vma->vm_end, 0x2000); ASSERT_EQ(vma_start_pgoff(vma), 1); + ASSERT_EQ(vma_start_anon_pgoff(vma), 1); detach_free_vma(vma); vma_iter_clear(&vmi); @@ -183,6 +187,7 @@ static bool test_simple_modify(void) ASSERT_EQ(vma->vm_start, 0x2000); ASSERT_EQ(vma->vm_end, 0x3000); ASSERT_EQ(vma_start_pgoff(vma), 2); + ASSERT_EQ(vma_start_anon_pgoff(vma), 2); detach_free_vma(vma); mtree_destroy(&mm.mm_mt); @@ -212,6 +217,7 @@ static bool test_simple_expand(void) ASSERT_EQ(vma->vm_start, 0); ASSERT_EQ(vma->vm_end, 0x3000); ASSERT_EQ(vma_start_pgoff(vma), 0); + ASSERT_EQ(vma_start_anon_pgoff(vma), 0); detach_free_vma(vma); mtree_destroy(&mm.mm_mt); @@ -234,6 +240,7 @@ static bool test_simple_shrink(void) ASSERT_EQ(vma->vm_start, 0); ASSERT_EQ(vma->vm_end, 0x1000); ASSERT_EQ(vma_start_pgoff(vma), 0); + ASSERT_EQ(vma_start_anon_pgoff(vma), 0); detach_free_vma(vma); mtree_destroy(&mm.mm_mt); @@ -346,6 +353,7 @@ static bool __test_merge_new(bool is_sticky, bool a_is_sticky, bool b_is_sticky, ASSERT_EQ(vma->vm_start, 0); ASSERT_EQ(vma->vm_end, 0x5000); ASSERT_EQ(vma_start_pgoff(vma), 0); + ASSERT_EQ(vma_start_anon_pgoff(vma), 0); ASSERT_EQ(vma->anon_vma, &dummy_anon_vma); ASSERT_TRUE(vma_write_started(vma)); ASSERT_EQ(mm.map_count, 3); @@ -367,6 +375,7 @@ static bool __test_merge_new(bool is_sticky, bool a_is_sticky, bool b_is_sticky, ASSERT_EQ(vma->vm_start, 0x6000); ASSERT_EQ(vma->vm_end, 0x9000); ASSERT_EQ(vma_start_pgoff(vma), 6); + ASSERT_EQ(vma_start_anon_pgoff(vma), 6); ASSERT_EQ(vma->anon_vma, &dummy_anon_vma); ASSERT_TRUE(vma_write_started(vma)); ASSERT_EQ(mm.map_count, 3); @@ -387,6 +396,7 @@ static bool __test_merge_new(bool is_sticky, bool a_is_sticky, bool b_is_sticky, ASSERT_EQ(vma->vm_start, 0); ASSERT_EQ(vma->vm_end, 0x9000); ASSERT_EQ(vma_start_pgoff(vma), 0); + ASSERT_EQ(vma_start_anon_pgoff(vma), 0); ASSERT_EQ(vma->anon_vma, &dummy_anon_vma); ASSERT_TRUE(vma_write_started(vma)); ASSERT_EQ(mm.map_count, 2); @@ -407,6 +417,7 @@ static bool __test_merge_new(bool is_sticky, bool a_is_sticky, bool b_is_sticky, ASSERT_EQ(vma->vm_start, 0xa000); ASSERT_EQ(vma->vm_end, 0xc000); ASSERT_EQ(vma_start_pgoff(vma), 0xa); + ASSERT_EQ(vma_start_anon_pgoff(vma), 0xa); ASSERT_EQ(vma->anon_vma, &dummy_anon_vma); ASSERT_TRUE(vma_write_started(vma)); ASSERT_EQ(mm.map_count, 2); @@ -426,6 +437,7 @@ static bool __test_merge_new(bool is_sticky, bool a_is_sticky, bool b_is_sticky, ASSERT_EQ(vma->vm_start, 0); ASSERT_EQ(vma->vm_end, 0xc000); ASSERT_EQ(vma_start_pgoff(vma), 0); + ASSERT_EQ(vma_start_anon_pgoff(vma), 0); ASSERT_EQ(vma->anon_vma, &dummy_anon_vma); ASSERT_TRUE(vma_write_started(vma)); ASSERT_EQ(mm.map_count, 1); @@ -446,6 +458,7 @@ static bool __test_merge_new(bool is_sticky, bool a_is_sticky, bool b_is_sticky, ASSERT_EQ(vma->vm_start, 0); ASSERT_EQ(vma->vm_end, 0xc000); ASSERT_EQ(vma_start_pgoff(vma), 0); + ASSERT_EQ(vma_start_anon_pgoff(vma), 0); ASSERT_EQ(vma->anon_vma, &dummy_anon_vma); detach_free_vma(vma); @@ -642,7 +655,8 @@ static bool test_vma_merge_with_close(void) ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS); ASSERT_EQ(vma_prev->vm_start, 0); ASSERT_EQ(vma_prev->vm_end, 0x5000); - ASSERT_EQ(vma_prev->vm_pgoff, 0); + ASSERT_EQ(vma_start_pgoff(vma_prev), 0); + ASSERT_EQ(vma_start_anon_pgoff(vma_prev), 0); ASSERT_EQ(cleanup_mm(&mm, &vmi), 2); @@ -753,7 +767,8 @@ static bool test_vma_merge_with_close(void) ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS); ASSERT_EQ(vma_prev->vm_start, 0); ASSERT_EQ(vma_prev->vm_end, 0x5000); - ASSERT_EQ(vma_prev->vm_pgoff, 0); + ASSERT_EQ(vma_start_pgoff(vma_prev), 0); + ASSERT_EQ(vma_start_anon_pgoff(vma_prev), 0); ASSERT_EQ(cleanup_mm(&mm, &vmi), 2); @@ -808,6 +823,7 @@ static bool test_vma_merge_new_with_close(void) ASSERT_EQ(vma->vm_start, 0); ASSERT_EQ(vma->vm_end, 0x5000); ASSERT_EQ(vma_start_pgoff(vma), 0); + ASSERT_EQ(vma_start_anon_pgoff(vma), 0); ASSERT_EQ(vma->vm_ops, &vm_ops); ASSERT_TRUE(vma_write_started(vma)); ASSERT_EQ(mm.map_count, 2); @@ -863,11 +879,13 @@ static bool __test_merge_existing(bool prev_is_sticky, bool middle_is_sticky, bo ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS); ASSERT_EQ(vma_next->vm_start, 0x3000); ASSERT_EQ(vma_next->vm_end, 0x9000); - ASSERT_EQ(vma_next->vm_pgoff, 3); + ASSERT_EQ(vma_start_pgoff(vma_next), 3); + ASSERT_EQ(vma_start_anon_pgoff(vma_next), 3); ASSERT_EQ(vma_next->anon_vma, &dummy_anon_vma); ASSERT_EQ(vma->vm_start, 0x2000); ASSERT_EQ(vma->vm_end, 0x3000); ASSERT_EQ(vma_start_pgoff(vma), 2); + ASSERT_EQ(vma_start_anon_pgoff(vma), 2); ASSERT_TRUE(vma_write_started(vma)); ASSERT_TRUE(vma_write_started(vma_next)); ASSERT_EQ(mm.map_count, 2); @@ -897,7 +915,8 @@ static bool __test_merge_existing(bool prev_is_sticky, bool middle_is_sticky, bo ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS); ASSERT_EQ(vma_next->vm_start, 0x2000); ASSERT_EQ(vma_next->vm_end, 0x9000); - ASSERT_EQ(vma_next->vm_pgoff, 2); + ASSERT_EQ(vma_start_pgoff(vma_next), 2); + ASSERT_EQ(vma_start_anon_pgoff(vma_next), 2); ASSERT_EQ(vma_next->anon_vma, &dummy_anon_vma); ASSERT_TRUE(vma_write_started(vma_next)); ASSERT_EQ(mm.map_count, 1); @@ -929,11 +948,13 @@ static bool __test_merge_existing(bool prev_is_sticky, bool middle_is_sticky, bo ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS); ASSERT_EQ(vma_prev->vm_start, 0); ASSERT_EQ(vma_prev->vm_end, 0x6000); - ASSERT_EQ(vma_prev->vm_pgoff, 0); + ASSERT_EQ(vma_start_pgoff(vma_prev), 0); + ASSERT_EQ(vma_start_anon_pgoff(vma_prev), 0); ASSERT_EQ(vma_prev->anon_vma, &dummy_anon_vma); ASSERT_EQ(vma->vm_start, 0x6000); ASSERT_EQ(vma->vm_end, 0x7000); ASSERT_EQ(vma_start_pgoff(vma), 6); + ASSERT_EQ(vma_start_anon_pgoff(vma), 6); ASSERT_TRUE(vma_write_started(vma_prev)); ASSERT_TRUE(vma_write_started(vma)); ASSERT_EQ(mm.map_count, 2); @@ -964,7 +985,8 @@ static bool __test_merge_existing(bool prev_is_sticky, bool middle_is_sticky, bo ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS); ASSERT_EQ(vma_prev->vm_start, 0); ASSERT_EQ(vma_prev->vm_end, 0x7000); - ASSERT_EQ(vma_prev->vm_pgoff, 0); + ASSERT_EQ(vma_start_pgoff(vma_prev), 0); + ASSERT_EQ(vma_start_anon_pgoff(vma_prev), 0); ASSERT_EQ(vma_prev->anon_vma, &dummy_anon_vma); ASSERT_TRUE(vma_write_started(vma_prev)); ASSERT_EQ(mm.map_count, 1); @@ -996,7 +1018,8 @@ static bool __test_merge_existing(bool prev_is_sticky, bool middle_is_sticky, bo ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS); ASSERT_EQ(vma_prev->vm_start, 0); ASSERT_EQ(vma_prev->vm_end, 0x9000); - ASSERT_EQ(vma_prev->vm_pgoff, 0); + ASSERT_EQ(vma_start_pgoff(vma_prev), 0); + ASSERT_EQ(vma_start_anon_pgoff(vma_prev), 0); ASSERT_EQ(vma_prev->anon_vma, &dummy_anon_vma); ASSERT_TRUE(vma_write_started(vma_prev)); ASSERT_EQ(mm.map_count, 1); @@ -1126,7 +1149,8 @@ static bool test_anon_vma_non_mergeable(void) ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS); ASSERT_EQ(vma_prev->vm_start, 0); ASSERT_EQ(vma_prev->vm_end, 0x7000); - ASSERT_EQ(vma_prev->vm_pgoff, 0); + ASSERT_EQ(vma_start_pgoff(vma_prev), 0); + ASSERT_EQ(vma_start_anon_pgoff(vma_prev), 0); ASSERT_TRUE(vma_write_started(vma_prev)); ASSERT_FALSE(vma_write_started(vma_next)); @@ -1157,7 +1181,8 @@ static bool test_anon_vma_non_mergeable(void) ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS); ASSERT_EQ(vma_prev->vm_start, 0); ASSERT_EQ(vma_prev->vm_end, 0x7000); - ASSERT_EQ(vma_prev->vm_pgoff, 0); + ASSERT_EQ(vma_start_pgoff(vma_prev), 0); + ASSERT_EQ(vma_start_anon_pgoff(vma_prev), 0); ASSERT_TRUE(vma_write_started(vma_prev)); ASSERT_FALSE(vma_write_started(vma_next)); @@ -1419,6 +1444,7 @@ static bool test_merge_extend(void) ASSERT_EQ(vma->vm_start, 0); ASSERT_EQ(vma->vm_end, 0x4000); ASSERT_EQ(vma_start_pgoff(vma), 0); + ASSERT_EQ(vma_start_anon_pgoff(vma), 0); ASSERT_TRUE(vma_write_started(vma)); ASSERT_EQ(mm.map_count, 1); @@ -1459,6 +1485,7 @@ static bool test_expand_only_mode(void) ASSERT_EQ(vma->vm_start, 0x3000); ASSERT_EQ(vma->vm_end, 0x9000); ASSERT_EQ(vma_start_pgoff(vma), 3); + ASSERT_EQ(vma_start_anon_pgoff(vma), 3); ASSERT_TRUE(vma_write_started(vma)); ASSERT_EQ(vma_iter_addr(&vmi), 0x3000); vma_assert_attached(vma); From fb580e196497738d98dbad5b8459913435b376e7 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Thu, 13 Aug 2026 18:32:33 +0100 Subject: [PATCH 0979/1328] tools/testing/selftests/mm: test anonymous page offset merge behaviour MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While maintaining anonymous page offsets for VMAs has no impact for most merge cases, it does impact MAP_PRIVATE-mapped file-backed mappings which happen to have matching page offset but not matching anonymous page offset. Assert this behaviour by attempting to map an unfaulted MAP_PRIVATE-memfd region with a faulted one with compatible file page offsets but incompatible anonymous page offsets. Link: https://lore.kernel.org/20260813-b4-scalable-cow-virt-pgoff-v5-16-c21581c0c3c8@kernel.org Signed-off-by: Lorenzo Stoakes (ARM) Acked-by: David Hildenbrand (Arm) Cc: Adrian Hunter Cc: Alexander Deucher Cc: Alexander Gordeev Cc: Alexander Shishkin Cc: Alistair Popple Cc: Arnaldo Carvalho de Melo Cc: Arnd Bergmann Cc: Baolin Wang Cc: Baoquan He Cc: Barry Song Cc: Boris Brezillon Cc: Byungchul Park Cc: Chengming Zhou Cc: Chris Li Cc: Christan König Cc: Christian Borntraeger Cc: Claudio Imbrenda Cc: Dave Airlie Cc: Dev Jain Cc: Gerald Schaefer Cc: Greg Kroah-Hartman Cc: Gregory Price (Meta) Cc: Harry Yoo Cc: Heiko Carstens Cc: Huang Ray Cc: "Huang, Ying" Cc: Ian Rogers Cc: Ingo Molnar Cc: James Clark Cc: Jan Kara Cc: Jann Horn Cc: Janosch Frank Cc: Jason Gunthorpe Cc: Jiri Olsa Cc: John Hubbard Cc: Joshua Hahn Cc: Kairui Song Cc: Kees Cook Cc: Kemeng Shi Cc: Lance Yang Cc: Liam R. Howlett Cc: Liviu Dudau Cc: Maarten Lankhorst Cc: Marc Rutland Cc: "Masami Hiramatsu (Google)" Cc: Matthew Auld Cc: Matthew Brost Cc: Matthew Wilcox (Oracle) Cc: Maxime Ripard Cc: Miaohe Lin Cc: Michal Hocko Cc: Mike Rapoport Cc: Muchun Song Cc: Namhyung kim Cc: Naoya Horiguchi Cc: Nhat Pham Cc: Nico Pache Cc: Oleg Nesterov Cc: Oscar Salvador Cc: Pedro Falcato Cc: Peter Xu Cc: Peter Zijlstra Cc: Rakie Kim Cc: Rik van Riel Cc: Rodrigo Vivi Cc: Ryan Roberts Cc: Steven Price Cc: Suren Baghdasaryan Cc: Sven Schnelle Cc: Thomas Hellström Cc: Thomas Zimemrmann Cc: Vasily Gorbik Cc: Vlastimil Babka Cc: xu xin Cc: Zi Yan Signed-off-by: Andrew Morton --- tools/testing/selftests/mm/merge.c | 57 ++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/tools/testing/selftests/mm/merge.c b/tools/testing/selftests/mm/merge.c index 519e5ac02db7..52b8727b6628 100644 --- a/tools/testing/selftests/mm/merge.c +++ b/tools/testing/selftests/mm/merge.c @@ -1305,6 +1305,63 @@ TEST_F(merge, merge_vmas_with_mseal) ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 2 * page_size); } +TEST_F(merge, anon_and_page_offset_mismatch_memfd) +{ + struct procmap_fd *procmap = &self->procmap; + unsigned int page_size = self->page_size; + char *carveout = self->carveout; + char *ptr, *ptr2; + int fd; + + /* Create a 10 page memfd descriptor. */ + fd = memfd_create("anon_page_offset_test", MFD_CLOEXEC); + ASSERT_NE(fd, -1); + ASSERT_EQ(ftruncate(fd, 10 * page_size), 0); + + /* Map a region using the memfd at page offset 0. */ + ptr = mmap(carveout, 5 * page_size, PROT_READ | PROT_WRITE, + MAP_FIXED | MAP_PRIVATE, fd, 0); + ASSERT_NE(ptr, MAP_FAILED); + + /* + * Map another separately and trigger a CoW fault at page offset 5: + * + * |-----------| |---------| + * | unfaulted | | faulted | + * |-----------| |---------| + */ + ptr2 = mmap(&carveout[10 * page_size], 5 * page_size, + PROT_READ | PROT_WRITE, MAP_FIXED | MAP_PRIVATE, + fd, 5 * page_size); + ASSERT_NE(ptr2, MAP_FAILED); + ptr2[0] = 'x'; + + /* + * Now move it in place: + * + * |----------| + * | | + * v | + * |-----------| |---------| + * | unfaulted | | faulted | + * |-----------| |---------| + * + * Because the anonymous page offset of the faulted region is now + * &carveout[10 * page_size], despite the two regions being mergeable + * due to file page offset, they are NOT mergeable due to anonymous + * page offset. + */ + ptr2 = sys_mremap(ptr2, 5 * page_size, 5 * page_size, + MREMAP_MAYMOVE | MREMAP_FIXED, + &carveout[5 * page_size]); + ASSERT_NE(ptr2, MAP_FAILED); + + /* Assert that they did not merge. */ + ASSERT_TRUE(find_vma_procmap(procmap, ptr)); + ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr); + ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 5 * page_size); +} + TEST_F(merge_with_fork, mremap_faulted_to_unfaulted_prev) { struct procmap_fd *procmap = &self->procmap; From 3541a2b06ecd78ba333188df04368dcf97273d6a Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Mon, 20 Jul 2026 06:23:45 -0700 Subject: [PATCH 0980/1328] mm/kmemleak: report RCU-tasks quiescent states during the scan kmemleak_scan() can run for ages on large debug kernels. It was causing some soft-lockups which I got fixed with commit 3175fcfec8b16baeb ("mm/kmemleak: avoid soft lockup when scanning task stacks") with our beloved cond_resched(). I've got the fix above deployed in the Meta fleet, and now I am seeing: INFO: rcu_tasks detected stalls on tasks: task:kmemleak state:R ... nvcsw: 274/274 holdout: 1 idle_cpu: -1/3 scan_block scan_gray_list kmemleak_scan and, worse, blocks the callers waiting on that grace period. Here a BPF struct_ops map free, which waits via synchronize_rcu_mult(call_rcu, call_rcu_tasks), is stuck long enough to also trip the hung task check: INFO: task kworker/...:bpf_map_free_deferred blocked for 122 seconds __wait_rcu_gp bpf_struct_ops_map_free Then I've learned that cond_resched() is not an RCU-tasks quiescent state, so, we need to use stronger primitives. Use cond_resched_tasks_rcu_qs() at the scan reschedule points so the scan reports an RCU-tasks quiescent state as it proceeds. Inspired by commit b96285e10aad ("tracing: Have osnoise_main() add a quiescent state for task rcu"). Link: https://lore.kernel.org/20260720-kmemleak_rcu_task-v1-1-5b460ade777d@debian.org Fixes: c4b28963fd79 ("mm/kmemleak: rely on rcu for task stack scanning") Signed-off-by: Breno Leitao Reviewed-by: Paul E. McKenney Reviewed-by: SJ Park Reviewed-by: Catalin Marinas Cc: Breno Leitao Cc: Puranjay Mohan Cc: Signed-off-by: Andrew Morton --- mm/kmemleak.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/mm/kmemleak.c b/mm/kmemleak.c index e96e9efd19b0..0a6045c857d6 100644 --- a/mm/kmemleak.c +++ b/mm/kmemleak.c @@ -1571,7 +1571,7 @@ static int scan_large_block(void *start, void *end) if (scan_block(start, next, NULL)) return 1; start = next; - cond_resched(); + cond_resched_tasks_rcu_qs(); } return 0; @@ -1608,7 +1608,7 @@ static void scan_object(struct kmemleak_object *object) scan_block(start, end, object); raw_spin_unlock_irqrestore(&object->lock, flags); - cond_resched(); + cond_resched_tasks_rcu_qs(); raw_spin_lock_irqsave(&object->lock, flags); if (!(object->flags & OBJECT_ALLOCATED)) break; @@ -1630,7 +1630,7 @@ static void scan_object(struct kmemleak_object *object) break; raw_spin_unlock_irqrestore(&object->lock, flags); - cond_resched(); + cond_resched_tasks_rcu_qs(); raw_spin_lock_irqsave(&object->lock, flags); } while (object->flags & OBJECT_ALLOCATED); } else { @@ -1658,7 +1658,7 @@ static void scan_gray_list(void) */ object = list_entry(gray_list.next, typeof(*object), gray_list); while (&object->gray_list != &gray_list) { - cond_resched(); + cond_resched_tasks_rcu_qs(); /* may add new objects to the list */ if (!scan_should_stop()) @@ -1693,7 +1693,7 @@ static void kmemleak_cond_resched(struct kmemleak_object *object) raw_spin_unlock_irq(&kmemleak_lock); rcu_read_unlock(); - cond_resched(); + cond_resched_tasks_rcu_qs(); rcu_read_lock(); raw_spin_lock_irq(&kmemleak_lock); @@ -1738,7 +1738,7 @@ static void kmemleak_scan_task_stacks(void) } put_task_struct(p); } - cond_resched(); + cond_resched_tasks_rcu_qs(); } while (pid && !stop); } @@ -1915,7 +1915,7 @@ static void kmemleak_scan(void) struct page *page = pfn_to_online_page(pfn); if (!(pfn & 63)) - cond_resched(); + cond_resched_tasks_rcu_qs(); if (!page) continue; From 3bf07ce8058bcab03ea1cb9cd11ec1d6b2ee5af0 Mon Sep 17 00:00:00 2001 From: Baolin Wang Date: Mon, 20 Jul 2026 19:12:00 +0800 Subject: [PATCH 0981/1328] mm: vmscan: convert folio_referenced() to use vma_flags_t Patch series "promote mapped executable folios after first usage for MGLRU", v4. Now MGLRU's protection of mapped executable file folios is less reliable. Follow the classical LRU's logic, promoting mapped executable file folios after their first usage to give executable code a better chance to stay in memory and improve workload performance (See patch 2 for more details). This patch (of 3): Replace use of the legacy vm_flags_t flags with vma_flags_t values for folio_referenced() and related logic. This is also a preparation for the following changes. No functional changes. Link: https://lore.kernel.org/cover.1784509721.git.baolin.wang@linux.alibaba.com Link: https://lore.kernel.org/2bd39e16ec19e3e3c4716aa9a1a25775c26cac57.1784509721.git.baolin.wang@linux.alibaba.com Signed-off-by: Baolin Wang Acked-by: David Hildenbrand (Arm) Acked-by: Johannes Weiner Reviewed-by: Barry Song Reviewed-by: Kairui Song Reviewed-by: Axel Rasmussen Cc: Harry Yoo Cc: Jann Horn Cc: Lance Yang Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Rik van Riel Cc: Shakeel Butt Cc: Vlastimil Babka Cc: Wei Xu Cc: Yuanchu Xie Signed-off-by: Andrew Morton --- include/linux/rmap.h | 7 +++---- mm/rmap.c | 19 +++++++++++-------- mm/vmscan.c | 14 +++++++------- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/include/linux/rmap.h b/include/linux/rmap.h index 0574537a355c..0b332770abee 100644 --- a/include/linux/rmap.h +++ b/include/linux/rmap.h @@ -843,7 +843,7 @@ static inline int folio_try_share_anon_rmap_pmd(struct folio *folio, * Called from mm/vmscan.c to handle paging out */ int folio_referenced(struct folio *, int is_locked, - struct mem_cgroup *memcg, vm_flags_t *vm_flags); + struct mem_cgroup *memcg, vma_flags_t *vma_flags); void try_to_migrate(struct folio *folio, enum ttu_flags flags); void try_to_unmap(struct folio *, enum ttu_flags flags); @@ -977,10 +977,9 @@ struct anon_vma *folio_lock_anon_vma_read(const struct folio *folio, #define anon_vma_prepare(vma) (0) static inline int folio_referenced(struct folio *folio, int is_locked, - struct mem_cgroup *memcg, - vm_flags_t *vm_flags) + struct mem_cgroup *memcg, vma_flags_t *vma_flags) { - *vm_flags = 0; + vma_flags_clear_all(vma_flags); return 0; } diff --git a/mm/rmap.c b/mm/rmap.c index 34ceeb600111..14f2f9b07572 100644 --- a/mm/rmap.c +++ b/mm/rmap.c @@ -908,7 +908,7 @@ pmd_t *mm_find_pmd(struct mm_struct *mm, unsigned long address) struct folio_referenced_arg { int mapcount; int referenced; - vm_flags_t vm_flags; + vma_flags_t vma_flags; struct mem_cgroup *memcg; }; @@ -927,7 +927,7 @@ static bool folio_referenced_one(struct folio *folio, address = pvmw.address; nr = 1; - if (vma->vm_flags & VM_LOCKED) { + if (vma_test(vma, VMA_LOCKED_BIT)) { ptes++; pra->mapcount--; @@ -948,7 +948,7 @@ static bool folio_referenced_one(struct folio *folio, /* Restore the mlock which got missed */ mlock_vma_folio(folio, vma); page_vma_mapped_walk_done(&pvmw); - pra->vm_flags |= VM_LOCKED; + vma_flags_set(&pra->vma_flags, VMA_LOCKED_BIT); return false; /* To break the loop */ } @@ -1016,8 +1016,11 @@ static bool folio_referenced_one(struct folio *folio, referenced++; if (referenced) { + vma_flags_t vma_flags = vma->flags; + pra->referenced++; - pra->vm_flags |= vma->vm_flags & ~VM_LOCKED; + vma_flags_clear(&vma_flags, VMA_LOCKED_BIT); + vma_flags_set_mask(&pra->vma_flags, vma_flags); } if (!pra->mapcount) @@ -1055,7 +1058,7 @@ static bool invalid_folio_referenced_vma(struct vm_area_struct *vma, void *arg) * @folio: The folio to test. * @is_locked: Caller holds lock on the folio. * @memcg: target memory cgroup - * @vm_flags: A combination of all the vma->vm_flags which referenced the folio. + * @vma_flags: A combination of all the vma->flags which referenced the folio. * * Quick test_and_clear_referenced for all mappings of a folio, * @@ -1063,7 +1066,7 @@ static bool invalid_folio_referenced_vma(struct vm_area_struct *vma, void *arg) * the function bailed out due to rmap lock contention. */ int folio_referenced(struct folio *folio, int is_locked, - struct mem_cgroup *memcg, vm_flags_t *vm_flags) + struct mem_cgroup *memcg, vma_flags_t *vma_flags) { bool we_locked = false; struct folio_referenced_arg pra = { @@ -1079,7 +1082,7 @@ int folio_referenced(struct folio *folio, int is_locked, }; VM_WARN_ON_ONCE_FOLIO(folio_is_zone_device(folio), folio); - *vm_flags = 0; + vma_flags_clear_all(vma_flags); if (!pra.mapcount) return 0; @@ -1093,7 +1096,7 @@ int folio_referenced(struct folio *folio, int is_locked, } rmap_walk(folio, &rwc); - *vm_flags = pra.vm_flags; + vma_flags_set_mask(vma_flags, pra.vma_flags); if (we_locked) folio_unlock(folio); diff --git a/mm/vmscan.c b/mm/vmscan.c index 8bd0bea62767..206213e56ec8 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -861,16 +861,16 @@ static enum folio_references folio_check_references(struct folio *folio, struct scan_control *sc) { int referenced_ptes, referenced_folio; - vm_flags_t vm_flags; + vma_flags_t vma_flags; referenced_ptes = folio_referenced(folio, 1, sc->target_mem_cgroup, - &vm_flags); + &vma_flags); /* * The supposedly reclaimable folio was found to be in a VM_LOCKED vma. * Let the folio, now marked Mlocked, be moved to the unevictable list. */ - if (vm_flags & VM_LOCKED) + if (vma_flags_test(&vma_flags, VMA_LOCKED_BIT)) return FOLIOREF_ACTIVATE; /* @@ -914,7 +914,7 @@ static enum folio_references folio_check_references(struct folio *folio, /* * Activate file-backed executable folios after first usage. */ - if ((vm_flags & VM_EXEC) && folio_is_file_lru(folio)) + if (vma_flags_test(&vma_flags, VMA_EXEC_BIT) && folio_is_file_lru(folio)) return FOLIOREF_ACTIVATE; return FOLIOREF_KEEP; @@ -2065,7 +2065,7 @@ static void shrink_active_list(unsigned long nr_to_scan, { unsigned long nr_taken; unsigned long nr_scanned; - vm_flags_t vm_flags; + vma_flags_t vma_flags; LIST_HEAD(l_hold); /* The folios which were snipped off */ LIST_HEAD(l_active); LIST_HEAD(l_inactive); @@ -2109,7 +2109,7 @@ static void shrink_active_list(unsigned long nr_to_scan, /* Referenced or rmap lock contention: rotate */ if (folio_referenced(folio, 0, sc->target_mem_cgroup, - &vm_flags) != 0) { + &vma_flags) != 0) { /* * Identify referenced, file-backed active folios and * give them one more trip around the active list. So @@ -2119,7 +2119,7 @@ static void shrink_active_list(unsigned long nr_to_scan, * IO, plus JVM can create lots of anon VM_EXEC folios, * so we ignore them here. */ - if ((vm_flags & VM_EXEC) && folio_is_file_lru(folio)) { + if (vma_flags_test(&vma_flags, VMA_EXEC_BIT) && folio_is_file_lru(folio)) { nr_rotated += folio_nr_pages(folio); list_add(&folio->lru, &l_active); continue; From b64727d264782a5441aa625eba60b3c53fd2842e Mon Sep 17 00:00:00 2001 From: Baolin Wang Date: Mon, 20 Jul 2026 19:12:01 +0800 Subject: [PATCH 0982/1328] mm: vmscan: add a helper to identify file-backed executable folios Add a helper to identify file-backed executable folios to avoid duplicate code. No functional changes. Link: https://lore.kernel.org/2ee74f9f98ac45a2f0db0ceb018e086bdb671d17.1784509721.git.baolin.wang@linux.alibaba.com Signed-off-by: Baolin Wang Acked-by: Johannes Weiner Reviewed-by: Axel Rasmussen Reviewed-by: David Hildenbrand (Arm) Reviewed-by: Kairui Song Reviewed-by: Barry Song Cc: Harry Yoo Cc: Jann Horn Cc: Lance Yang Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Rik van Riel Cc: Shakeel Butt Cc: Vlastimil Babka Cc: Wei Xu Cc: Yuanchu Xie Signed-off-by: Andrew Morton --- mm/vmscan.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/mm/vmscan.c b/mm/vmscan.c index 206213e56ec8..f2628b9187f8 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -268,6 +268,12 @@ static int sc_swappiness(struct scan_control *sc, struct mem_cgroup *memcg) } #endif +static inline bool is_exec_file_folio(const struct folio *folio, + const vma_flags_t *vma_flags) +{ + return vma_flags_test(vma_flags, VMA_EXEC_BIT) && folio_is_file_lru(folio); +} + static void set_task_reclaim_state(struct task_struct *task, struct reclaim_state *rs) { @@ -914,7 +920,7 @@ static enum folio_references folio_check_references(struct folio *folio, /* * Activate file-backed executable folios after first usage. */ - if (vma_flags_test(&vma_flags, VMA_EXEC_BIT) && folio_is_file_lru(folio)) + if (is_exec_file_folio(folio, &vma_flags)) return FOLIOREF_ACTIVATE; return FOLIOREF_KEEP; @@ -2119,7 +2125,7 @@ static void shrink_active_list(unsigned long nr_to_scan, * IO, plus JVM can create lots of anon VM_EXEC folios, * so we ignore them here. */ - if (vma_flags_test(&vma_flags, VMA_EXEC_BIT) && folio_is_file_lru(folio)) { + if (is_exec_file_folio(folio, &vma_flags)) { nr_rotated += folio_nr_pages(folio); list_add(&folio->lru, &l_active); continue; From 0ee06ee38aed1a9949510624a1b4abdc7e39815a Mon Sep 17 00:00:00 2001 From: Baolin Wang Date: Mon, 20 Jul 2026 19:12:02 +0800 Subject: [PATCH 0983/1328] mm: mglru: promote mapped executable folios after first usage Classical LRU protects mapped executable file folios through commit 8cab4754d24a0 ("vmscan: make mapped executable pages the first class citizen") and commit c909e99364c8 ("vmscan: activate executable pages after first usage"), giving executable code a better chance to stay in memory, avoiding IO thrashing and improving workload performance. However, MGLRU's protection of mapped executable file folios is less reliable. Although shrink_folio_list() checks references, the access flag of mapped executable file folios may have already been checked and cleared by lru_gen_look_around() or walk_mm(). Additionally, folio_update_gen() or lru_gen_set_refs() only sets the 'PG_referenced' flag for mapped executable file folios, which causes shrink_folio_list() to ignore the first usage of these mapped executable file folios and reclaim them easily. Follow the classical LRU's logic, promoting mapped executable file folios after their first usage in folio_update_gen() and lru_gen_set_refs(), giving executable code a better chance to stay in memory. On my 32-core Arm machine, with the memcg limit set to 2G, running 'make -j32' to build kernel showed some improvement in sys time. base patched 9248.543s 7861.579s Link: https://lore.kernel.org/f57d94b1d85bb3d620d89bb739128f0d929bf9c6.1784509721.git.baolin.wang@linux.alibaba.com Signed-off-by: Baolin Wang Acked-by: Johannes Weiner Reviewed-by: Axel Rasmussen Cc: Barry Song Cc: David Hildenbrand Cc: Harry Yoo Cc: Jann Horn Cc: Kairui Song Cc: Lance Yang Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Rik van Riel Cc: Shakeel Butt Cc: Vlastimil Babka Cc: Wei Xu Cc: Yuanchu Xie Signed-off-by: Andrew Morton --- mm/vmscan.c | 43 +++++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/mm/vmscan.c b/mm/vmscan.c index f2628b9187f8..d65ccea92756 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -841,10 +841,16 @@ enum folio_references { * with PG_active set. In contrast, the aging (page table walk) path uses * folio_update_gen(). */ -static bool lru_gen_set_refs(struct folio *folio) +static bool lru_gen_set_refs(struct folio *folio, const vma_flags_t *vma_flags) { /* see the comment on LRU_REFS_FLAGS */ if (!folio_test_referenced(folio) && !folio_test_workingset(folio)) { + /* Activate file-backed executable folios after first usage. */ + if (is_exec_file_folio(folio, vma_flags)) { + set_mask_bits(&folio->flags.f, LRU_REFS_FLAGS, BIT(PG_workingset)); + return true; + } + set_mask_bits(&folio->flags.f, LRU_REFS_MASK, BIT(PG_referenced)); return false; } @@ -857,7 +863,7 @@ static bool lru_gen_set_refs(struct folio *folio) return true; } #else -static bool lru_gen_set_refs(struct folio *folio) +static bool lru_gen_set_refs(struct folio *folio, const vma_flags_t *vma_flags) { return false; } @@ -892,7 +898,7 @@ static enum folio_references folio_check_references(struct folio *folio, if (!referenced_ptes) return FOLIOREF_RECLAIM; - return lru_gen_set_refs(folio) ? FOLIOREF_ACTIVATE : FOLIOREF_KEEP; + return lru_gen_set_refs(folio, &vma_flags) ? FOLIOREF_ACTIVATE : FOLIOREF_KEEP; } referenced_folio = folio_test_clear_referenced(folio); @@ -3208,14 +3214,19 @@ static bool positive_ctrl_err(struct ctrl_pos *sp, struct ctrl_pos *pv) ******************************************************************************/ /* promote pages accessed through page tables */ -static int folio_update_gen(struct folio *folio, int gen) +static int folio_update_gen(struct folio *folio, int gen, const vma_flags_t *vma_flags) { unsigned long new_flags, old_flags = READ_ONCE(folio->flags.f); VM_WARN_ON_ONCE(gen >= MAX_NR_GENS); - /* see the comment on LRU_REFS_FLAGS */ - if (!folio_test_referenced(folio) && !folio_test_workingset(folio)) { + /* + * See the comment on LRU_REFS_FLAGS, and activate file-backed + * executable folios after first usage to avoid typical IO + * thrashing from reclaiming. + */ + if (!folio_test_referenced(folio) && !folio_test_workingset(folio) && + !is_exec_file_folio(folio, vma_flags)) { set_mask_bits(&folio->flags.f, LRU_REFS_MASK, BIT(PG_referenced)); return -1; } @@ -3448,8 +3459,8 @@ static bool suitable_to_scan(int total, int young) return young * n >= total; } -static void walk_update_folio(struct lru_gen_mm_walk *walk, struct folio *folio, - int new_gen, bool dirty) +static void walk_update_folio(struct lru_gen_mm_walk *walk, struct vm_area_struct *vma, + struct folio *folio, int new_gen, bool dirty) { int old_gen; @@ -3462,10 +3473,10 @@ static void walk_update_folio(struct lru_gen_mm_walk *walk, struct folio *folio, folio_mark_dirty(folio); if (walk) { - old_gen = folio_update_gen(folio, new_gen); + old_gen = folio_update_gen(folio, new_gen, &vma->flags); if (old_gen >= 0 && old_gen != new_gen) update_batch_size(walk, folio, old_gen, new_gen); - } else if (lru_gen_set_refs(folio)) { + } else if (lru_gen_set_refs(folio, &vma->flags)) { old_gen = folio_lru_gen(folio); if (old_gen >= 0 && old_gen != new_gen) folio_activate(folio); @@ -3538,7 +3549,7 @@ static bool walk_pte_range(pmd_t *pmd, unsigned long start, unsigned long end, continue; if (last != folio) { - walk_update_folio(walk, last, gen, dirty); + walk_update_folio(walk, args->vma, last, gen, dirty); last = folio; dirty = false; @@ -3551,7 +3562,7 @@ static bool walk_pte_range(pmd_t *pmd, unsigned long start, unsigned long end, walk->mm_stats[MM_LEAF_YOUNG] += nr; } - walk_update_folio(walk, last, gen, dirty); + walk_update_folio(walk, args->vma, last, gen, dirty); last = NULL; if (i < PTRS_PER_PTE && get_next_vma(PMD_MASK, PAGE_SIZE, args, &start, &end)) @@ -3629,7 +3640,7 @@ static void walk_pmd_range_locked(pud_t *pud, unsigned long addr, struct vm_area goto next; if (last != folio) { - walk_update_folio(walk, last, gen, dirty); + walk_update_folio(walk, vma, last, gen, dirty); last = folio; dirty = false; @@ -3643,7 +3654,7 @@ static void walk_pmd_range_locked(pud_t *pud, unsigned long addr, struct vm_area i = i > MIN_LRU_BATCH ? 0 : find_next_bit(bitmap, MIN_LRU_BATCH, i) + 1; } while (i <= MIN_LRU_BATCH); - walk_update_folio(walk, last, gen, dirty); + walk_update_folio(walk, vma, last, gen, dirty); lazy_mmu_mode_disable(); spin_unlock(ptl); @@ -4278,7 +4289,7 @@ bool lru_gen_look_around(struct page_vma_mapped_walk *pvmw, unsigned int nr) continue; if (last != folio) { - walk_update_folio(walk, last, gen, dirty); + walk_update_folio(walk, vma, last, gen, dirty); last = folio; dirty = false; @@ -4290,7 +4301,7 @@ bool lru_gen_look_around(struct page_vma_mapped_walk *pvmw, unsigned int nr) young += nr; } - walk_update_folio(walk, last, gen, dirty); + walk_update_folio(walk, vma, last, gen, dirty); lazy_mmu_mode_disable(); From 1b7c8fe294a6bf913832e998f519029cad47dbcb Mon Sep 17 00:00:00 2001 From: Ridong Chen Date: Thu, 23 Jul 2026 11:24:33 +0800 Subject: [PATCH 0984/1328] memcg: move mem_cgroup_swappiness and vm_swappiness to mm/swap.h Patch series "mm: vmscan: fix node reclaim ignoring swappiness parameter", v4. The per-node proactive reclaim interface (/sys/devices/system/node/nodeX/reclaim) accepts a swappiness parameter, but it is silently ignored when CONFIG_MEMCG is disabled. The root cause is that sc_swappiness() has separate implementations for CONFIG_MEMCG and !CONFIG_MEMCG, and the latter never checks proactive_swappiness. Patch 1 moves mem_cgroup_swappiness() and vm_swappiness out of the public include/linux/swap.h into the mm-private mm/swap.h, and makes the helper handle both CONFIG_MEMCG and !CONFIG_MEMCG in a single inline function. This is a prerequisite for unifying sc_swappiness(). Patch 2 consolidates sc_swappiness() into a single definition that works regardless of CONFIG_MEMCG, fixing the node reclaim swappiness bug. This patch (of 2): The per-memcg swappiness knob is v1-only; v2 always uses global vm_swappiness and ignores the per-cgroup field. Both mem_cgroup_swappiness() and vm_swappiness are only used within mm/ (memcontrol.c, memcontrol-v1.c, vmscan.c), so move them out of the public include/linux/swap.h into the mm-private mm/swap.h. This keeps unrelated declarations out of include/linux/swap.h. Guard memcg->swappiness with CONFIG_MEMCG_V1 as well, so v2-only kernels drop the unused field. No functional change for v1; v2-only kernels drop the unused field. Link: https://lore.kernel.org/20260723032434.2016749-1-ridong.chen@linux.dev Link: https://lore.kernel.org/20260723032434.2016749-2-ridong.chen@linux.dev Signed-off-by: Ridong Chen Acked-by: Johannes Weiner Reviewed-by: Barry Song Reviewed-by: Song Hu Acked-by: Shakeel Butt Cc: Axel Rasmussen Cc: Baoquan He Cc: Chris Li Cc: David Hildenbrand Cc: Davidlohr Bueso Cc: Kairui Song Cc: Kemeng Shi Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Muchun Song Cc: Nhat Pham Cc: Roman Gushchin Cc: Wei Xu Cc: Yuanchu Xie Cc: Qi Zheng Signed-off-by: Andrew Morton --- include/linux/memcontrol.h | 4 ++-- include/linux/swap.h | 19 ------------------- mm/memcontrol.c | 4 ++-- mm/swap.h | 14 ++++++++++++++ 4 files changed, 18 insertions(+), 23 deletions(-) diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h index 8170bb8066a2..f619e24fc3bb 100644 --- a/include/linux/memcontrol.h +++ b/include/linux/memcontrol.h @@ -239,8 +239,6 @@ struct mem_cgroup { */ bool oom_group; - int swappiness; - /* memory.events and memory.events.local */ struct cgroup_file events_file; struct cgroup_file events_local_file; @@ -318,6 +316,8 @@ struct mem_cgroup { /* List of events which userspace want to receive */ struct list_head event_list; spinlock_t event_list_lock; + + int swappiness; #endif /* CONFIG_MEMCG_V1 */ struct mem_cgroup_per_node *nodeinfo[]; diff --git a/include/linux/swap.h b/include/linux/swap.h index 696ed01709c2..330a420fd6de 100644 --- a/include/linux/swap.h +++ b/include/linux/swap.h @@ -309,7 +309,6 @@ static inline bool lru_cache_disabled(void) } extern unsigned long shrink_all_memory(unsigned long nr_pages); -extern int vm_swappiness; long remove_mapping(struct address_space *mapping, struct folio *folio); #if defined(CONFIG_SYSFS) && defined(CONFIG_NUMA) @@ -468,25 +467,7 @@ static inline int add_swap_extent(struct swap_info_struct *sis, } #endif /* CONFIG_SWAP */ #ifdef CONFIG_MEMCG -static inline int mem_cgroup_swappiness(struct mem_cgroup *memcg) -{ - /* Cgroup2 doesn't have per-cgroup swappiness */ - if (cgroup_subsys_on_dfl(memory_cgrp_subsys)) - return READ_ONCE(vm_swappiness); - - /* root ? */ - if (mem_cgroup_disabled() || mem_cgroup_is_root(memcg)) - return READ_ONCE(vm_swappiness); - - return READ_ONCE(memcg->swappiness); -} - void lru_reparent_memcg(struct mem_cgroup *memcg, struct mem_cgroup *parent, int nid); -#else -static inline int mem_cgroup_swappiness(struct mem_cgroup *memcg) -{ - return READ_ONCE(vm_swappiness); -} #endif #if defined(CONFIG_SWAP) && defined(CONFIG_MEMCG) && defined(CONFIG_BLK_CGROUP) diff --git a/mm/memcontrol.c b/mm/memcontrol.c index 69b37f63a307..ec92cb40156d 100644 --- a/mm/memcontrol.c +++ b/mm/memcontrol.c @@ -63,6 +63,7 @@ #include #include #include "internal.h" +#include "swap.h" #include "swap_table.h" #include #include @@ -4176,11 +4177,10 @@ mem_cgroup_css_alloc(struct cgroup_subsys_state *parent_css) #endif page_counter_set_high(&memcg->swap, PAGE_COUNTER_MAX); if (parent) { - WRITE_ONCE(memcg->swappiness, mem_cgroup_swappiness(parent)); - page_counter_init(&memcg->memory, &parent->memory, memcg_on_dfl); page_counter_init(&memcg->swap, &parent->swap, false); #ifdef CONFIG_MEMCG_V1 + WRITE_ONCE(memcg->swappiness, mem_cgroup_swappiness(parent)); memcg->memory.track_failcnt = !memcg_on_dfl; memcg->memsw.track_failcnt = !memcg_on_dfl; WRITE_ONCE(memcg->oom_kill_disable, READ_ONCE(parent->oom_kill_disable)); diff --git a/mm/swap.h b/mm/swap.h index b51ad3071a73..2ccf8cf7f6c1 100644 --- a/mm/swap.h +++ b/mm/swap.h @@ -4,6 +4,8 @@ #include /* for atomic_long_t */ #include /* for PAGE_SHIFT */ +#include /* for mem_cgroup_swappiness() */ + struct mempolicy; struct swap_iocb; struct swap_memcg_table; @@ -76,6 +78,18 @@ enum swap_cluster_flags { CLUSTER_FLAG_MAX, }; +extern int vm_swappiness; + +static inline int mem_cgroup_swappiness(struct mem_cgroup *memcg) +{ +#ifdef CONFIG_MEMCG_V1 + if (!cgroup_subsys_on_dfl(memory_cgrp_subsys) && + !mem_cgroup_disabled() && !mem_cgroup_is_root(memcg)) + return READ_ONCE(memcg->swappiness); +#endif + return READ_ONCE(vm_swappiness); +} + #ifdef CONFIG_SWAP #include /* for swp_offset */ #include /* for bio_end_io_t */ From 8a905195850d383c0465ab5bdd3c91d94269b242 Mon Sep 17 00:00:00 2001 From: Ridong Chen Date: Thu, 23 Jul 2026 11:24:34 +0800 Subject: [PATCH 0985/1328] mm: vmscan: fix node reclaim ignoring swappiness parameter sc_swappiness() had two separate definitions depending on CONFIG_MEMCG. The !CONFIG_MEMCG variant simply returned vm_swappiness, ignoring the proactive_swappiness value passed through scan_control. This caused the swappiness parameter written to /sys/devices/system/node/nodeX/reclaim to have no effect when CONFIG_MEMCG is disabled. Fix this by consolidating sc_swappiness() into a single definition that checks sc->proactive_swappiness first, then falls back to mem_cgroup_swappiness() which already handles both CONFIG_MEMCG and !CONFIG_MEMCG. Before fix (swappiness=max ignored, mostly file pages reclaimed): # cat /proc/sys/vm/swappiness 60 # cat /proc/vmstat | grep pgsteal pgsteal_kswapd 0 pgsteal_direct 0 pgsteal_khugepaged 0 pgsteal_proactive 1840 pgsteal_anon 25 pgsteal_file 1815 # echo "64M swappiness=max" > /sys/devices/system/node/node0/reclaim # cat /proc/vmstat | grep pgsteal pgsteal_kswapd 0 pgsteal_direct 0 pgsteal_khugepaged 0 pgsteal_proactive 18013 pgsteal_anon 337 pgsteal_file 17676 After fix (swappiness=max honored, anon pages reclaimed as expected): # cat /proc/vmstat | grep pgsteal pgsteal_kswapd 0 pgsteal_direct 0 pgsteal_khugepaged 0 pgsteal_proactive 0 pgsteal_anon 0 pgsteal_file 0 # echo "64M swappiness=max" > /sys/devices/system/node/node0/reclaim # cat /proc/vmstat | grep pgsteal pgsteal_kswapd 0 pgsteal_direct 0 pgsteal_khugepaged 0 pgsteal_proactive 16283 pgsteal_anon 16283 pgsteal_file 0 Link: https://lore.kernel.org/20260723032434.2016749-3-ridong.chen@linux.dev Fixes: b980077899ea ("mm: introduce per-node proactive reclaim interface") Signed-off-by: Ridong Chen Acked-by: Johannes Weiner Reviewed-by: Barry Song Acked-by: Qi Zheng Tested-by: Song Hu Reviewed-by: Song Hu Acked-by: Shakeel Butt Cc: Axel Rasmussen Cc: Baoquan He Cc: Chris Li Cc: David Hildenbrand Cc: Davidlohr Bueso Cc: Kairui Song Cc: Kemeng Shi Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Muchun Song Cc: Nhat Pham Cc: Roman Gushchin Cc: Wei Xu Cc: Yuanchu Xie Cc: [6.17+] Signed-off-by: Andrew Morton --- mm/vmscan.c | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/mm/vmscan.c b/mm/vmscan.c index d65ccea92756..5bad065a38f1 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -199,6 +199,13 @@ struct scan_control { */ int vm_swappiness = 60; +static int sc_swappiness(struct scan_control *sc, struct mem_cgroup *memcg) +{ + if (sc->proactive && sc->proactive_swappiness) + return *sc->proactive_swappiness; + return mem_cgroup_swappiness(memcg); +} + #ifdef CONFIG_MEMCG /* Returns true for reclaim through cgroup limits or cgroup interfaces. */ @@ -239,13 +246,6 @@ static bool writeback_throttling_sane(struct scan_control *sc) #endif return false; } - -static int sc_swappiness(struct scan_control *sc, struct mem_cgroup *memcg) -{ - if (sc->proactive && sc->proactive_swappiness) - return *sc->proactive_swappiness; - return mem_cgroup_swappiness(memcg); -} #else static bool cgroup_reclaim(struct scan_control *sc) { @@ -261,11 +261,6 @@ static bool writeback_throttling_sane(struct scan_control *sc) { return true; } - -static int sc_swappiness(struct scan_control *sc, struct mem_cgroup *memcg) -{ - return READ_ONCE(vm_swappiness); -} #endif static inline bool is_exec_file_folio(const struct folio *folio, From a69797fb36452865252f10c8ac9ef6781d07e3d7 Mon Sep 17 00:00:00 2001 From: Usama Arif Date: Mon, 27 Jul 2026 09:23:23 -0700 Subject: [PATCH 0986/1328] mm/vmstat, mm/memcontrol: add _monotonic vmstat readers Patch series "mm/vmscan: reduce lru_lock contention via vmstat-derived scan-balance cost", v5. The anon/file scan balance heuristic in get_scan_count() is fed by two scalars in struct lruvec (anon_cost, file_cost) that every reclaim producer updates under lruvec->lru_lock. The cost-recording work itself is trivial, but it both contends for and contributes to contention on lru_lock - which is often a contention point on memory-pressured workloads. Specifically: - shrink_inactive_list() re-acquires lru_lock at function exit just to call lru_note_cost_unlock_irq(). - shrink_active_list() does the same after rotation accounting. - workingset_refault() takes folio_lruvec_lock_irq() purely to record the refault cost. - prepare_scan_control() snapshots anon_cost/file_cost under lru_lock. - lru_note_cost_unlock_irq() itself walks parent_lruvec() and re-acquires lru_lock on every ancestor, multiplying the cost of every update by memcg-hierarchy depth. This series removes those producer-side acquisitions entirely. The rotation inputs become per-LRU PGROTATE_{ANON,FILE} vmstat counters. NR_VMSCAN_WRITE already captures reclaim-driven pageout at writeout(); charge it through lruvec_stat_mod_folio() so it is available per lruvec and aggregated through the memcg hierarchy. Reclaim does not submit filesystem folios for writeback from this path, so pageout contributes only to anon cost. WORKINGSET_RESTORE_* already captures the refault input. PGROTATE_* are also useful independently of scan balancing. They are cumulative base-page events, not unique-page counts. Classic inactive reclaim records scan work that does not produce immediate reclaim or demotion, while active reclaim records referenced executable file folios retained on the active list. MGLRU records initially isolated pages that remain unreclaimed after its retry passes. Read alongside pgscan_* and pgsteal_*, their deltas identify which LRU type is consuming reclaim CPU without producing immediate yield. Unlike the existing pgrotated event, they do not imply a move to the inactive-list tail. prepare_scan_control() reads the raw cost signals without lru_lock: anon = PGROTATE_ANON + (NR_VMSCAN_WRITE + WORKINGSET_RESTORE_ANON) * SWAP_CLUSTER_MAX file = PGROTATE_FILE + WORKINGSET_RESTORE_FILE * SWAP_CLUSTER_MAX It folds the deltas into a per-lruvec accumulator. A dedicated per-lruvec cost_lock, not touched by isolate_lru_folios(), move_folios_to_lru(), or folio_add_lru(), serialises the accumulator RMW and the lrusize/4 halving check. Hierarchy aggregation is implicit in rstat propagation, so the parent_lruvec() walk and the lru_reparent_memcg() cost-splice both disappear. Moving accumulation and decay to the reclaim side also improves the cost model across reclaim gaps. With producer-side decay, events that happen while reclaim is idle still age each other before reclaim ever samples the costs. If a workload refaults a large anon set and then a smaller file set before reclaim runs again, the later file activity can age the earlier anon activity out of the cost model. The new scheme observes the whole between-reclaim delta and decays anon and file proportionally, so the scan-balance history better represents what happened since the last reclaim pass. Trade-offs: - Cost reads see rstat-aggregated values that can lag until periodic / reader-triggered flushing. - Per-lruvec footprint grows by 4 unsigned longs + a spinlock (a struct lru_cost { count, last_rotated, last_io } per side), which is a small cost. - NR_VMSCAN_WRITE now also updates the folio's lruvec/memcg stat, adding memcg stat accounting to the reclaim writeout path while preserving the existing node-level total. == Numbers == Tested on a 176-core, 256 GB host. The benchmark drives sustained swap-out/refault inside a tight memcg using vm-scalability/usemem: usemem -n 16 --prealloc --prefault --random $((256*1024*1024)) run inside a two-level memcg with memory.max=512M on the leaf (4 GB anon working set has to fit in 512 MB -> continuous shrink_inactive_list + workingset_refault). A 16 GB swap file is used. Measurement is a 30 s `perf lock record -a` window over otherwise-idle hardware. Workload rates are identical on both kernels (the bench drives the same memory pressure): baseline patched delta pgscan_direct / s 172,662 171,817 ~0% pgsteal_direct / s 67,162 66,306 ~0% workingset_refault_anon / s 40,696 39,830 ~0% perf lock contention (total wait per 30 s window): Lock Name Before After % change shrink_lruvec+0x770 722.84 ms 0 -100% (eliminated) (= lru_note_cost_unlock_irq) workingset_refault+0x167 385.26 ms 0 -100% (eliminated) (= lru_note_cost_refault) shrink_node+0x4ad 689.43 ms 26.95 ms -96% shrink_active_list 208.34 ms 15.97 ms -92% lru_add_drain_cpu+0x34 1.96 s 917.71 ms -53% Total LRU lock wait ~4.23 s ~1.66 s -61% The two specific contention sites the patch removes (shrink_lruvec+0x770 = lru_note_cost_unlock_irq; workingset_refault+0x167 = lru_note_cost_refault) are completely absent from the patched perf-lock-contention output. Secondary reductions in shrink_node, shrink_active_list, lru_add_drain_cpu and pgrefill/pgactivate look like knock-on effects from removing the cost-recording overhead and the parent_lruvec walk. The remaining ~1.66 s of LRU lock wait on the patched kernel is dominated by the per-CPU pagevec drain (lru_add_drain_cpu) and the main reclaim path in shrink_lruvec. The numbers above can be reproduced using the script in [1]. This patch (of 3): lruvec_page_state(), node_page_state(), and global_node_page_state() all clamp negative reads to zero on CONFIG_SMP so that a transient per-CPU delta skew presents as zero pages rather than as a garbage unsigned value. This is the right behaviour for non-monotonic page-count readers. It is however incorrect for callers that snapshot a monotonically- incremented event counter and compute a delta from two samples. Once the underlying signed long wraps past LONG_MAX, the clamped read drops to zero while the previously-recorded snapshot still holds the pre-wrap value; the unsigned subtraction then underflows into a ~2^31 spurious delta for 32-bit architecture and corrupts the caller's accumulator. Add non-clamping siblings that return the underlying state value cast to unsigned long: global_node_page_state_monotonic() node_page_state_monotonic() lruvec_page_state_monotonic() With both samples read via the _monotonic variant, unsigned modular subtraction stays correct across a signed-long wraparound as long as the true growth between two samples fits in unsigned long (< 2^32 on 32-bit, < 2^64 on 64-bit); the 32-bit bound is the practically-reachable one that motivates this helper. The variants are only safe for monotonically-incremented counters. Non-monotonic page-count readers must keep using the existing clamped helpers so transient negative reads still present as zero. This is a prerequisite for a later patch which replaces the producer-side anon_cost/file_cost accumulators with a read-side accumulator in prepare_scan_control() that samples monotonic per-LRU vmstat counters (PGROTATE_*, NR_VMSCAN_WRITE, WORKINGSET_RESTORE_*) via lruvec_page_state_monotonic() and folds their unsigned modular deltas into lruvec->cost[].count. Link: https://lore.kernel.org/20260727162550.2032-1-usama.arif@linux.dev Link: https://lore.kernel.org/20260727162550.2032-2-usama.arif@linux.dev Link: https://gist.github.com/uarif1/a4eb33a86c5b2d7bbc55b42f0956e884 [1] Signed-off-by: Usama Arif Acked-by: Johannes Weiner Acked-by: Shakeel Butt Acked-by: Vlastimil Babka (SUSE) Cc: Axel Rasmussen Cc: Baoquan He Cc: Chris Li Cc: David Hildenbrand Cc: David Rientjes Cc: Kairui Song Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Muchun Song Cc: Nhat Pham Cc: Roman Gushchin Cc: Suren Baghdasaryan Cc: Wei Xu Cc: Yuanchu Xie Signed-off-by: Andrew Morton --- include/linux/memcontrol.h | 8 ++++++++ include/linux/vmstat.h | 16 ++++++++++++++++ mm/memcontrol.c | 36 ++++++++++++++++++++++++++++++++++++ mm/vmstat.c | 11 +++++++++++ 4 files changed, 71 insertions(+) diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h index f619e24fc3bb..e78bc98ab229 100644 --- a/include/linux/memcontrol.h +++ b/include/linux/memcontrol.h @@ -947,6 +947,8 @@ unsigned long memcg_page_state_output(struct mem_cgroup *memcg, int item); bool memcg_stat_item_valid(int idx); bool memcg_vm_event_item_valid(enum vm_event_item idx); unsigned long lruvec_page_state(struct lruvec *lruvec, enum node_stat_item idx); +unsigned long lruvec_page_state_monotonic(struct lruvec *lruvec, + enum node_stat_item idx); unsigned long lruvec_page_state_local(struct lruvec *lruvec, enum node_stat_item idx); @@ -1399,6 +1401,12 @@ static inline unsigned long lruvec_page_state(struct lruvec *lruvec, return node_page_state(lruvec_pgdat(lruvec), idx); } +static inline unsigned long lruvec_page_state_monotonic(struct lruvec *lruvec, + enum node_stat_item idx) +{ + return node_page_state_monotonic(lruvec_pgdat(lruvec), idx); +} + static inline unsigned long lruvec_page_state_local(struct lruvec *lruvec, enum node_stat_item idx) { diff --git a/include/linux/vmstat.h b/include/linux/vmstat.h index 3c9c266cf782..fb8c76289e02 100644 --- a/include/linux/vmstat.h +++ b/include/linux/vmstat.h @@ -194,6 +194,19 @@ unsigned long global_node_page_state_pages(enum node_stat_item item) return x; } +/* + * Non-clamping variant of global_node_page_state() intended for callers that + * snapshot a monotonically-incremented counter and subtract two samples. + * Returns the raw wrapping value so that unsigned modular subtraction stays + * correct across a signed-long overflow (a real hazard on 32-bit) that the + * clamp in global_node_page_state() would otherwise turn into a huge spurious + * delta. Do NOT use for non-monotonic page-count reads. + */ +static inline unsigned long global_node_page_state_monotonic(enum node_stat_item item) +{ + return (unsigned long)atomic_long_read(&vm_node_stat[item]); +} + static inline unsigned long global_node_page_state(enum node_stat_item item) { VM_WARN_ON_ONCE(vmstat_item_in_bytes(item)); @@ -259,11 +272,14 @@ extern unsigned long node_page_state(struct pglist_data *pgdat, enum node_stat_item item); extern unsigned long node_page_state_pages(struct pglist_data *pgdat, enum node_stat_item item); +extern unsigned long node_page_state_monotonic(struct pglist_data *pgdat, + enum node_stat_item item); extern void fold_vm_numa_events(void); #else #define sum_zone_node_page_state(node, item) global_zone_page_state(item) #define node_page_state(node, item) global_node_page_state(item) #define node_page_state_pages(node, item) global_node_page_state_pages(item) +#define node_page_state_monotonic(node, item) global_node_page_state_monotonic(item) static inline void fold_vm_numa_events(void) { } diff --git a/mm/memcontrol.c b/mm/memcontrol.c index ec92cb40156d..d804f8d07581 100644 --- a/mm/memcontrol.c +++ b/mm/memcontrol.c @@ -508,6 +508,42 @@ unsigned long lruvec_page_state(struct lruvec *lruvec, enum node_stat_item idx) return x; } +/** + * lruvec_page_state_monotonic - non-clamping lruvec stat read for delta sampling + * @lruvec: the LRU vector to read from + * @idx: the node_stat_item to read + * + * Returns the raw state[idx] value cast to unsigned long, skipping the + * clamp-negative-to-zero step in lruvec_page_state(). Intended for callers + * that snapshot a monotonically-incremented counter and subtract two + * samples: unsigned modular arithmetic then yields the correct delta across + * a signed-long wraparound (a real hazard on 32-bit) that the clamp would + * otherwise turn into a huge spurious delta. + * + * Do NOT use for non-monotonic page-count reads where a transient negative + * reading from per-CPU delta skew must present as zero. + * + * XXX: This helper (and its node/global peers) exists because some + * monotonically-incremented event counters are stored in + * enum node_stat_item. + */ +unsigned long lruvec_page_state_monotonic(struct lruvec *lruvec, + enum node_stat_item idx) +{ + struct mem_cgroup_per_node *pn; + int i; + + if (mem_cgroup_disabled()) + return node_page_state_monotonic(lruvec_pgdat(lruvec), idx); + + i = memcg_stats_index(idx); + if (WARN_ONCE(BAD_STAT_IDX(i), "%s: missing stat item %d\n", __func__, idx)) + return 0; + + pn = container_of(lruvec, struct mem_cgroup_per_node, lruvec); + return (unsigned long)READ_ONCE(pn->lruvec_stats->state[i]); +} + unsigned long lruvec_page_state_local(struct lruvec *lruvec, enum node_stat_item idx) { diff --git a/mm/vmstat.c b/mm/vmstat.c index 3b5cb1031f72..507118474c03 100644 --- a/mm/vmstat.c +++ b/mm/vmstat.c @@ -1025,6 +1025,17 @@ unsigned long node_page_state(struct pglist_data *pgdat, return node_page_state_pages(pgdat, item); } + +/* + * Non-clamping variant of node_page_state() intended for callers that + * snapshot a monotonically-incremented counter and subtract two samples. + * See global_node_page_state_monotonic() for the rationale. + */ +unsigned long node_page_state_monotonic(struct pglist_data *pgdat, + enum node_stat_item item) +{ + return (unsigned long)atomic_long_read(&pgdat->vm_stat[item]); +} #endif /* From 1b089def0fb8833ec4b331b61908f29d6b491ccb Mon Sep 17 00:00:00 2001 From: Usama Arif Date: Mon, 27 Jul 2026 09:23:24 -0700 Subject: [PATCH 0987/1328] mm/vmscan: add pgrotate_anon and pgrotate_file vmstat counters Reclaim can spend substantial work on an LRU type without immediately reclaiming or demoting a corresponding amount of memory. Record this work in PGROTATE_ANON and PGROTATE_FILE. For classic LRU reclaim: - Inactive-list reclaim adds nr_scanned - nr_reclaimed to the corresponding anon/file counter when isolation succeeds. - Active-list reclaim adds referenced executable file folios that are retained on the active list to PGROTATE_FILE. Active anon reclaim does not contribute this component. For MGLRU, add the number of initially isolated pages that remain unreclaimed after both the initial and retry passes to the counter for the selected anon/file type. These counters are distinct from the existing pgrotated vm event. pgrotated records an actual move to the inactive-list tail, primarily after reclaim-marked writeback completes or failed invalidation leaves a folio for accelerated reclaim. PGROTATE_ANON and PGROTATE_FILE measure reclaim cost and do not imply that a folio moved to an LRU tail. A subsequent patch will consume these counters for anon/file scan balancing. Link: https://lore.kernel.org/20260727162550.2032-3-usama.arif@linux.dev Signed-off-by: Usama Arif Acked-by: Shakeel Butt Acked-by: Johannes Weiner Reviewed-by: Vlastimil Babka (SUSE) Cc: Axel Rasmussen Cc: Baoquan He Cc: Chris Li Cc: David Hildenbrand Cc: David Rientjes Cc: Kairui Song Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Muchun Song Cc: Nhat Pham Cc: Roman Gushchin Cc: Suren Baghdasaryan Cc: Wei Xu Cc: Yuanchu Xie Signed-off-by: Andrew Morton --- include/linux/mmzone.h | 2 ++ mm/memcontrol.c | 2 ++ mm/vmscan.c | 14 +++++++++++++- mm/vmstat.c | 2 ++ 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h index 0507193b3ae3..1cc5ea506b7c 100644 --- a/include/linux/mmzone.h +++ b/include/linux/mmzone.h @@ -323,6 +323,8 @@ enum node_stat_item { PGSCAN_PROACTIVE, PGSCAN_ANON, PGSCAN_FILE, + PGROTATE_ANON, + PGROTATE_FILE, PGREFILL, #ifdef CONFIG_HUGETLB_PAGE NR_HUGETLB, diff --git a/mm/memcontrol.c b/mm/memcontrol.c index d804f8d07581..011bb3b0346b 100644 --- a/mm/memcontrol.c +++ b/mm/memcontrol.c @@ -425,6 +425,8 @@ static const unsigned int memcg_node_stat_items[] = { PGSCAN_PROACTIVE, PGSCAN_ANON, PGSCAN_FILE, + PGROTATE_ANON, + PGROTATE_FILE, PGREFILL, #ifdef CONFIG_HUGETLB_PAGE NR_HUGETLB, diff --git a/mm/vmscan.c b/mm/vmscan.c index 5bad065a38f1..fa2c5cf577af 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -2038,6 +2038,9 @@ static unsigned long shrink_inactive_list(unsigned long nr_to_scan, item = PGSTEAL_KSWAPD + reclaimer_offset(sc); mod_lruvec_state(lruvec, item, nr_reclaimed); mod_lruvec_state(lruvec, PGSTEAL_ANON + file, nr_reclaimed); + if (nr_scanned > nr_reclaimed) + mod_lruvec_state(lruvec, PGROTATE_ANON + file, + nr_scanned - nr_reclaimed); lruvec_lock_irq(lruvec); lru_note_cost_unlock_irq(lruvec, file, stat.nr_pageout, @@ -2147,6 +2150,8 @@ static void shrink_active_list(unsigned long nr_to_scan, count_vm_events(PGDEACTIVATE, nr_deactivate); count_memcg_events(lruvec_memcg(lruvec), PGDEACTIVATE, nr_deactivate); mod_node_page_state(pgdat, NR_ISOLATED_ANON + file, -nr_taken); + if (nr_rotated) + mod_lruvec_state(lruvec, PGROTATE_ANON + file, nr_rotated); lruvec_lock_irq(lruvec); lru_note_cost_unlock_irq(lruvec, file, 0, nr_rotated); @@ -4828,7 +4833,8 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec, struct reclaim_stat stat; struct lru_gen_mm_walk *walk; int scanned, reclaimed; - int isolated = 0, type, type_scanned; + int isolated = 0, nr_isolated = 0, type, type_scanned; + unsigned long total_reclaimed = 0; bool skip_retry = false; struct mem_cgroup *memcg = lruvec_memcg(lruvec); struct pglist_data *pgdat = lruvec_pgdat(lruvec); @@ -4840,6 +4846,7 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec, scanned = isolate_folios(nr_to_scan, lruvec, sc, swappiness, &list, &isolated, &type, &type_scanned); + nr_isolated = isolated; /* Scanning may have emptied the oldest gen, flush it */ if (scanned) @@ -4852,6 +4859,7 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec, retry: reclaimed = shrink_folio_list(&list, pgdat, sc, &stat, false, memcg); sc->nr_reclaimed += reclaimed; + total_reclaimed += reclaimed; /* Retry pass is only meant for clean folios without new isolation */ if (isolated) handle_reclaim_writeback(isolated, pgdat, sc, &stat); @@ -4903,6 +4911,10 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec, goto retry; } + if (nr_isolated > total_reclaimed) + mod_lruvec_state(lruvec, PGROTATE_ANON + type, + nr_isolated - total_reclaimed); + return scanned; } diff --git a/mm/vmstat.c b/mm/vmstat.c index 507118474c03..7d6e61a01f51 100644 --- a/mm/vmstat.c +++ b/mm/vmstat.c @@ -1301,6 +1301,8 @@ const char * const vmstat_text[] = { [I(PGSCAN_PROACTIVE)] = "pgscan_proactive", [I(PGSCAN_ANON)] = "pgscan_anon", [I(PGSCAN_FILE)] = "pgscan_file", + [I(PGROTATE_ANON)] = "pgrotate_anon", + [I(PGROTATE_FILE)] = "pgrotate_file", [I(PGREFILL)] = "pgrefill", #ifdef CONFIG_HUGETLB_PAGE [I(NR_HUGETLB)] = "nr_hugetlb", From 7b9f4e5f81013bcb0d16bf54b349ee323ce0ac01 Mon Sep 17 00:00:00 2001 From: Usama Arif Date: Mon, 27 Jul 2026 09:23:25 -0700 Subject: [PATCH 0988/1328] mm/vmscan: reduce lru_lock contention via vmstat-derived scan-balance cost The anon/file scan balance in get_scan_count() is driven by two scalars in struct lruvec, anon_cost and file_cost, accumulated by every reclaim producer under lruvec->lru_lock. The acquisition sites for cost work specifically are: - shrink_inactive_list() re-takes lru_lock at function exit purely to call lru_note_cost_unlock_irq() with (nr_pageout, nr_scanned - nr_reclaimed). One acquisition per inactive shrink. - shrink_active_list() does the same with (0, nr_rotated). One acquisition per active shrink. - workingset_refault() takes the lock via folio_lruvec_lock_irq() purely to record the refault cost. One acquisition per refault. - prepare_scan_control() takes lru_lock just to snapshot the two scalars into sc->{anon,file}_cost. - lru_note_cost_unlock_irq() itself walks parent_lruvec and re-acquires lru_lock on each ancestor to propagate the update, adding O(memcg-depth) acquisitions per producer call. This hurts because lru_lock is already a heavy contention point on memory-heavy workloads: every isolate_lru_folios(), move_folios_to_lru() and folio_add_lru() takes it. The cost work itself is trivial (two scalar bumps and one comparison), but it contends with and causes contention for actual LRU manipulation. The parent_lruvec() walk also multiplies cost-update overhead by memcg hierarchy depth. The balance formula for anon and file, respectively, is this: cost = nr_io * SWAP_CLUSTER_MAX + nr_rotated Instead of recording cost and running averaging logic directly when these events occur, snapshot running vmstat counters once per reclaim cycle and derive the balance from event deltas since the last run. Use PGROTATE_* from the preceding patch for the rotation input. WORKINGSET_RESTORE_* and NR_VMSCAN_WRITE provide the remaining event counters. Charge NR_VMSCAN_WRITE through lruvec stats so all inputs can be sampled per lruvec and aggregated through the memcg hierarchy. This is overall cheaper and has fewer lock acquisition sites. Moving accumulation and decay to the reclaim side also improves the cost model across reclaim gaps. With producer-side decay, events that happen while reclaim is idle still age each other before reclaim ever samples the costs. If a workload refaults a large anon set and then a smaller file set before reclaim runs again, the later file activity can age the earlier anon activity out of the cost model. The new scheme observes the whole between-reclaim delta and decays anon and file proportionally, so the scan-balance history better represents what happened since the last reclaim pass. A dedicated per-lruvec spinlock, cost_lock, serialises the delta extraction, the cost->count update and the halving loop against concurrent reclaimers in the same memcg+node. NR_VMSCAN_WRITE is accounted at writeout(), so reclaim_stat.nr_pageout is no longer needed and is removed. memcg-v1's memory.stat anon_cost/file_cost is now sourced from cost[].count instead of the removed lruvec anon_cost/file_cost fields. The reported values only refresh when prepare_scan_control() runs and are bounded at ~lrusize/4 by the halving loop; the scan-balance signal they express is unchanged. Under pure MGLRU the scan-balance signal itself is not consumed (both prepare_scan_control() and get_scan_count() are short-circuited on the MGLRU paths, and MGLRU's own type/tier selection comes from read_ctrl_pos() on lrugen->{avg_refaulted,avg_total,refaulted,evicted}, not from anon_cost/file_cost). NR_VMSCAN_WRITE naturally covers writeout from either reclaim implementation. The preceding patch also bumps PGROTATE_{ANON,FILE} from evict_folios(), so rotation-driven reclaim work is accounted consistently across both implementations. Link: https://lore.kernel.org/20260727162550.2032-4-usama.arif@linux.dev Signed-off-by: Usama Arif Acked-by: Shakeel Butt Acked-by: Johannes Weiner Acked-by: Vlastimil Babka (SUSE) Cc: Axel Rasmussen Cc: Baoquan He Cc: Chris Li Cc: David Hildenbrand Cc: David Rientjes Cc: Kairui Song Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Muchun Song Cc: Nhat Pham Cc: Roman Gushchin Cc: Suren Baghdasaryan Cc: Wei Xu Cc: Yuanchu Xie Signed-off-by: Andrew Morton --- include/linux/mmzone.h | 13 +++++-- include/linux/vmstat.h | 1 - mm/folio.c | 69 ------------------------------------ mm/internal.h | 3 -- mm/memcontrol-v1.c | 4 +-- mm/memcontrol.c | 1 + mm/mmzone.c | 1 + mm/vmscan.c | 79 +++++++++++++++++++++++++++++++++++------- mm/workingset.c | 5 --- 9 files changed, 81 insertions(+), 95 deletions(-) diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h index 1cc5ea506b7c..158c1fba2393 100644 --- a/include/linux/mmzone.h +++ b/include/linux/mmzone.h @@ -757,6 +757,12 @@ void lru_gen_reparent_memcg(struct mem_cgroup *memcg, struct mem_cgroup *parent, #endif /* CONFIG_LRU_GEN */ +struct lru_cost { + unsigned long count; + unsigned long last_rotated; + unsigned long last_io; +}; + struct lruvec { struct list_head lists[NR_LRU_LISTS]; /* per lruvec lru_lock for memcg */ @@ -765,9 +771,12 @@ struct lruvec { * These track the cost of reclaiming one LRU - file or anon - * over the other. As the observed cost of reclaiming one LRU * increases, the reclaim scan balance tips toward the other. + * Updated and decayed at prepare_scan_control() time; cost_lock + * serialises that update. */ - unsigned long anon_cost; - unsigned long file_cost; + struct lru_cost cost[ANON_AND_FILE]; + /* Protects cost[]. */ + spinlock_t cost_lock; /* Non-resident age, driven by LRU movement */ atomic_long_t nonresident_age; /* Refaults at the time of last reclaim cycle */ diff --git a/include/linux/vmstat.h b/include/linux/vmstat.h index fb8c76289e02..5b31d8e7ae40 100644 --- a/include/linux/vmstat.h +++ b/include/linux/vmstat.h @@ -20,7 +20,6 @@ struct reclaim_stat { unsigned nr_congested; unsigned nr_writeback; unsigned nr_immediate; - unsigned nr_pageout; unsigned nr_activate[ANON_AND_FILE]; unsigned nr_ref_keep; unsigned nr_unmap_fail; diff --git a/mm/folio.c b/mm/folio.c index d2937600cf72..a9e328c3f21b 100644 --- a/mm/folio.c +++ b/mm/folio.c @@ -265,73 +265,6 @@ void folio_rotate_reclaimable(struct folio *folio) folio_batch_add_and_move(folio, lru_move_tail); } -void lru_note_cost_unlock_irq(struct lruvec *lruvec, bool file, - unsigned int nr_io, unsigned int nr_rotated) - __releases(lruvec->lru_lock) - __releases(rcu) -{ - unsigned long cost; - - /* - * Reflect the relative cost of incurring IO and spending CPU - * time on rotations. This doesn't attempt to make a precise - * comparison, it just says: if reloads are about comparable - * between the LRU lists, or rotations are overwhelmingly - * different between them, adjust scan balance for CPU work. - */ - cost = nr_io * SWAP_CLUSTER_MAX + nr_rotated; - if (!cost) { - spin_unlock_irq(&lruvec->lru_lock); - rcu_read_unlock(); - return; - } - - for (;;) { - unsigned long lrusize; - - /* Record cost event */ - if (file) - lruvec->file_cost += cost; - else - lruvec->anon_cost += cost; - - /* - * Decay previous events - * - * Because workloads change over time (and to avoid - * overflow) we keep these statistics as a floating - * average, which ends up weighing recent refaults - * more than old ones. - */ - lrusize = lruvec_page_state(lruvec, NR_INACTIVE_ANON) + - lruvec_page_state(lruvec, NR_ACTIVE_ANON) + - lruvec_page_state(lruvec, NR_INACTIVE_FILE) + - lruvec_page_state(lruvec, NR_ACTIVE_FILE); - - if (lruvec->file_cost + lruvec->anon_cost > lrusize / 4) { - lruvec->file_cost /= 2; - lruvec->anon_cost /= 2; - } - - spin_unlock_irq(&lruvec->lru_lock); - lruvec = parent_lruvec(lruvec); - if (!lruvec) { - rcu_read_unlock(); - break; - } - spin_lock_irq(&lruvec->lru_lock); - } -} - -void lru_note_cost_refault(struct folio *folio) -{ - struct lruvec *lruvec; - - lruvec = folio_lruvec_lock_irq(folio); - lru_note_cost_unlock_irq(lruvec, folio_is_file_lru(folio), - folio_nr_pages(folio), 0); -} - static void lru_activate(struct lruvec *lruvec, struct folio *folio) { long nr_pages = folio_nr_pages(folio); @@ -1162,8 +1095,6 @@ void lru_reparent_memcg(struct mem_cgroup *memcg, struct mem_cgroup *parent, int child_lruvec = mem_cgroup_lruvec(memcg, NODE_DATA(nid)); parent_lruvec = mem_cgroup_lruvec(parent, NODE_DATA(nid)); - parent_lruvec->anon_cost += child_lruvec->anon_cost; - parent_lruvec->file_cost += child_lruvec->file_cost; for_each_lru(lru) lruvec_reparent_lru(child_lruvec, parent_lruvec, lru, nid); diff --git a/mm/internal.h b/mm/internal.h index 16750b130ec4..07f60ca0b201 100644 --- a/mm/internal.h +++ b/mm/internal.h @@ -41,9 +41,6 @@ void workingset_refault(struct folio *folio, void *shadow); void workingset_activation(struct folio *folio); /* mm/folio.c */ -void lru_note_cost_unlock_irq(struct lruvec *lruvec, bool file, - unsigned int nr_io, unsigned int nr_rotated); -void lru_note_cost_refault(struct folio *folio); void folio_add_lru_vma(struct folio *folio, struct vm_area_struct *vma); static inline bool folio_may_be_lru_cached(struct folio *folio) diff --git a/mm/memcontrol-v1.c b/mm/memcontrol-v1.c index 2dc599484d00..835fc8e51184 100644 --- a/mm/memcontrol-v1.c +++ b/mm/memcontrol-v1.c @@ -2287,8 +2287,8 @@ void memcg1_stat_format(struct mem_cgroup *memcg, struct seq_buf *s) for_each_online_pgdat(pgdat) { mz = memcg->nodeinfo[pgdat->node_id]; - anon_cost += mz->lruvec.anon_cost; - file_cost += mz->lruvec.file_cost; + anon_cost += mz->lruvec.cost[WORKINGSET_ANON].count; + file_cost += mz->lruvec.cost[WORKINGSET_FILE].count; } seq_buf_printf(s, "anon_cost %lu\n", anon_cost); seq_buf_printf(s, "file_cost %lu\n", file_cost); diff --git a/mm/memcontrol.c b/mm/memcontrol.c index 011bb3b0346b..65057b59b097 100644 --- a/mm/memcontrol.c +++ b/mm/memcontrol.c @@ -399,6 +399,7 @@ static const unsigned int memcg_node_stat_items[] = { NR_SHMEM_THPS, NR_FILE_THPS, NR_ANON_THPS, + NR_VMSCAN_WRITE, NR_VMALLOC, NR_KERNEL_STACK_KB, NR_PAGETABLE, diff --git a/mm/mmzone.c b/mm/mmzone.c index 59dc3f2076a6..9cc9ef588580 100644 --- a/mm/mmzone.c +++ b/mm/mmzone.c @@ -79,6 +79,7 @@ void lruvec_init(struct lruvec *lruvec) memset(lruvec, 0, sizeof(struct lruvec)); spin_lock_init(&lruvec->lru_lock); + spin_lock_init(&lruvec->cost_lock); zswap_lruvec_state_init(lruvec); for_each_lru(lru) diff --git a/mm/vmscan.c b/mm/vmscan.c index fa2c5cf577af..3a6701143620 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -670,7 +670,7 @@ static pageout_t pageout(struct folio *folio, struct address_space *mapping, folio_clear_reclaim(folio); trace_mm_vmscan_write_folio(folio); - node_stat_add_folio(folio, NR_VMSCAN_WRITE); + lruvec_stat_mod_folio(folio, NR_VMSCAN_WRITE, folio_nr_pages(folio)); return PAGE_SUCCESS; } @@ -1413,8 +1413,6 @@ static unsigned int shrink_folio_list(struct list_head *folio_list, sc->nr_scanned -= (nr_pages - 1); nr_pages = 1; } - stat->nr_pageout += nr_pages; - if (folio_test_writeback(folio)) goto keep; if (folio_test_dirty(folio)) @@ -2042,9 +2040,6 @@ static unsigned long shrink_inactive_list(unsigned long nr_to_scan, mod_lruvec_state(lruvec, PGROTATE_ANON + file, nr_scanned - nr_reclaimed); - lruvec_lock_irq(lruvec); - lru_note_cost_unlock_irq(lruvec, file, stat.nr_pageout, - nr_scanned - nr_reclaimed); handle_reclaim_writeback(nr_taken, pgdat, sc, &stat); trace_mm_vmscan_lru_shrink_inactive(pgdat->node_id, nr_scanned, nr_reclaimed, &stat, sc->priority, file); @@ -2153,8 +2148,6 @@ static void shrink_active_list(unsigned long nr_to_scan, if (nr_rotated) mod_lruvec_state(lruvec, PGROTATE_ANON + file, nr_rotated); - lruvec_lock_irq(lruvec); - lru_note_cost_unlock_irq(lruvec, file, 0, nr_rotated); trace_mm_vmscan_lru_shrink_active(pgdat->node_id, nr_taken, nr_activate, nr_deactivate, nr_rotated, sc->priority, file); } @@ -2287,8 +2280,10 @@ enum scan_balance { static void prepare_scan_control(pg_data_t *pgdat, struct scan_control *sc) { - unsigned long file; + struct lru_cost *anon_cost, *file_cost; struct lruvec *target_lruvec; + unsigned long lrusize; + unsigned long file; if (lru_gen_enabled() && !lru_gen_switching()) return; @@ -2304,11 +2299,69 @@ static void prepare_scan_control(pg_data_t *pgdat, struct scan_control *sc) /* * Determine the scan balance between anon and file LRUs. + * + * The cost model is based on rotations, refaults and + * reclaim-driven writes (anon only) on each side. + * + * These event counters are monotonic, so each reclaim cycle + * the delta since the last scan is extracted and incorporated + * into a decaying average. This ensures currency, as workloads + * change over time, and avoids overflow in the calculations. + * + * Use lruvec_page_state_monotonic() so unsigned subtraction + * yields the correct delta across a signed-long wraparound of + * the underlying counter (a real hazard on 32-bit that the + * clamp in lruvec_page_state() would otherwise turn into a huge + * spurious delta). */ - spin_lock_irq(&target_lruvec->lru_lock); - sc->anon_cost = target_lruvec->anon_cost; - sc->file_cost = target_lruvec->file_cost; - spin_unlock_irq(&target_lruvec->lru_lock); + spin_lock(&target_lruvec->cost_lock); + + for (int f = 0; f <= 1; f++) { + struct lru_cost *cost = &target_lruvec->cost[f]; + unsigned long rotated, io, nr_rotated, nr_io; + + rotated = lruvec_page_state_monotonic(target_lruvec, + PGROTATE_ANON + f); + io = lruvec_page_state_monotonic(target_lruvec, + WORKINGSET_RESTORE_BASE + f); + if (f == WORKINGSET_ANON) + io += lruvec_page_state_monotonic(target_lruvec, + NR_VMSCAN_WRITE); + + nr_rotated = rotated - cost->last_rotated; + nr_io = io - cost->last_io; + + /* + * Reflect the relative cost of incurring IO and spending + * CPU time on rotations. This doesn't attempt to make a + * precise comparison, it just says: if reloads are about + * comparable between the LRU lists, or rotations are + * overwhelmingly different between them, adjust scan + * balance for CPU work. + */ + cost->count += nr_io * SWAP_CLUSTER_MAX + nr_rotated; + + cost->last_rotated = rotated; + cost->last_io = io; + } + + anon_cost = &target_lruvec->cost[WORKINGSET_ANON]; + file_cost = &target_lruvec->cost[WORKINGSET_FILE]; + + lrusize = lruvec_page_state(target_lruvec, NR_INACTIVE_ANON) + + lruvec_page_state(target_lruvec, NR_ACTIVE_ANON) + + lruvec_page_state(target_lruvec, NR_INACTIVE_FILE) + + lruvec_page_state(target_lruvec, NR_ACTIVE_FILE); + + while (anon_cost->count + file_cost->count > lrusize / 4) { + anon_cost->count /= 2; + file_cost->count /= 2; + } + + sc->anon_cost = anon_cost->count; + sc->file_cost = file_cost->count; + + spin_unlock(&target_lruvec->cost_lock); /* * Target desirable inactive:active list ratios for the anon diff --git a/mm/workingset.c b/mm/workingset.c index f351798e723a..7ac2b88c80ae 100644 --- a/mm/workingset.c +++ b/mm/workingset.c @@ -584,11 +584,6 @@ void workingset_refault(struct folio *folio, void *shadow) /* Folio was active prior to eviction */ if (workingset) { folio_set_workingset(folio); - /* - * XXX: Move to folio_add_lru() when it supports new vs - * putback - */ - lru_note_cost_refault(folio); mod_lruvec_state(lruvec, WORKINGSET_RESTORE_BASE + file, nr); } out: From dde75313eed0b014c437f48dd75c0308b592cbf9 Mon Sep 17 00:00:00 2001 From: Sergey Senozhatsky Date: Wed, 29 Jul 2026 13:57:35 +0900 Subject: [PATCH 0989/1328] zram: set default primary compressor in zram_destroy_comps() Patch series "zram: fix zram issues reported by sashiko". Sashiko drove by and reported [1] a couple of zram issues: a possible BUG_ON() in zlib code due to missing winbits range validation and one possible NULL-ptr dereference in zcomp. Both are low risk yet still worth fixing. This patch (of 2): zram_destroy_comps() resets all compressors and leaves them set to NULL, including the primary one, which is invalid device state, as now comp_algorithm_show()->strcmp() can be called on a NULL compressor. Set default primary compressor in zram_destroy_comps(). Link: https://lore.kernel.org/20260729045745.775973-2-senozhatsky@chromium.org Fixes: 486fd58af7ac ("zram: don't free statically defined names") Link: https://sashiko.dev/#/patchset/20260728092935.31139-1-haoqinhuang7@gmail.com [1] Signed-off-by: Sergey Senozhatsky Cc: Minchan Kim Cc: Haoqin Huang Cc: Signed-off-by: Andrew Morton --- drivers/block/zram/zram_drv.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/block/zram/zram_drv.c b/drivers/block/zram/zram_drv.c index 4bfe63a5225d..cfa98846ac48 100644 --- a/drivers/block/zram/zram_drv.c +++ b/drivers/block/zram/zram_drv.c @@ -2828,6 +2828,7 @@ static void zram_destroy_comps(struct zram *zram) zram->comp_algs[prio] = NULL; zram_comp_params_reset(zram); + comp_algorithm_set(zram, ZRAM_PRIMARY_COMP, default_compressor); } static void zram_reset_device(struct zram *zram) @@ -2845,8 +2846,6 @@ static void zram_reset_device(struct zram *zram) zram_destroy_comps(zram); memset(&zram->stats, 0, sizeof(zram->stats)); reset_bdev(zram); - - comp_algorithm_set(zram, ZRAM_PRIMARY_COMP, default_compressor); } static ssize_t disksize_store(struct device *dev, struct device_attribute *attr, From ec7607ac4717ff521c9c1e9d8271c26293345513 Mon Sep 17 00:00:00 2001 From: Sergey Senozhatsky Date: Wed, 29 Jul 2026 13:57:36 +0900 Subject: [PATCH 0990/1328] zram: validate deflate params We must validate user-supplied deflate winbits before we pass it to zlib_deflate_workspacesize(), which triggers BUG_ON() if winbits value is outside of valid ranges. Link: https://lore.kernel.org/20260729045745.775973-3-senozhatsky@chromium.org Fixes: dc75a0d93bd5 ("zram: support deflate-specific params") Link: https://sashiko.dev/#/patchset/20260728092935.31139-1-haoqinhuang7@gmail.com Signed-off-by: Sergey Senozhatsky Cc: Minchan Kim Cc: Haoqin Huang Cc: Signed-off-by: Andrew Morton --- drivers/block/zram/backend_deflate.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/block/zram/backend_deflate.c b/drivers/block/zram/backend_deflate.c index f92a52a720d1..b3f7d08b49d9 100644 --- a/drivers/block/zram/backend_deflate.c +++ b/drivers/block/zram/backend_deflate.c @@ -24,8 +24,16 @@ static int deflate_setup_params(struct zcomp_params *params) { if (params->level == ZCOMP_PARAM_NOT_SET) params->level = Z_DEFAULT_COMPRESSION; - if (params->deflate.winbits == ZCOMP_PARAM_NOT_SET) + if (params->deflate.winbits == ZCOMP_PARAM_NOT_SET) { params->deflate.winbits = DEFLATE_DEF_WINBITS; + } else { + s32 wb = params->deflate.winbits; + + if ((wb < -15 || wb > -9) && (wb < 9 || wb > 15)) { + pr_err("invalid deflate winbits: %d\n", wb); + return -EINVAL; + } + } return 0; } From 9477820c63cbf4d97114238f3d1ff10dfd6bee3f Mon Sep 17 00:00:00 2001 From: Guopeng Zhang Date: Fri, 24 Jul 2026 10:18:05 +0800 Subject: [PATCH 0991/1328] mm: memcg: stop reclaim when a limit update is superseded kernfs serializes file operations only per open file, so separate open files can update the same memory.high or memory.max file concurrently. Both handlers store the new limit before synchronous reclaim, but continue to use the writer's local target in the reclaim loop. If another writer raises or removes the limit, the first writer can continue reclaiming toward a stale target. For memory.max, this can leave the writer looping indefinitely once reclaim retries are exhausted. The OOM path sees sufficient margin under the current limit and returns true without killing, while the writer still compares usage against its stale target and records another OOM event. Check the current limit at the start of each reclaim iteration and stop if it no longer matches the writer's target. Reproducer: Populate a cgroup with anonymous memory and disable swapping. Lower memory.max from one open file, then restore it to "max" through another open file after the new limit becomes visible. Without the patch, the first writer remains blocked and repeatedly increments the OOM event counter. With the patch, it returns normally. This was not motivated by a reported production workload. We found it through automated randomized testing for our cgroup observability work and reduced it to the reproducer above. Link: https://lore.kernel.org/20260724021805.1234583-1-guopeng.zhang@linux.dev Fixes: 8c8c383c04f6 ("mm: memcontrol: try harder to set a new memory.high") Fixes: b6e6edcfa405 ("mm: memcontrol: reclaim and OOM kill when shrinking memory.max below usage") Signed-off-by: Guopeng Zhang Acked-by: Tao Cui Acked-by: Johannes Weiner Cc: Michal Hocko Cc: Muchun Song Cc: Roman Gushchin Cc: Shakeel Butt Cc: Signed-off-by: Andrew Morton --- mm/memcontrol.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mm/memcontrol.c b/mm/memcontrol.c index 65057b59b097..0dd847af69a2 100644 --- a/mm/memcontrol.c +++ b/mm/memcontrol.c @@ -4839,6 +4839,9 @@ static ssize_t memory_high_write(struct kernfs_open_file *of, unsigned long nr_pages = page_counter_read(&memcg->memory); unsigned long reclaimed; + if (high != READ_ONCE(memcg->memory.high)) + break; + if (nr_pages <= high) break; @@ -4894,6 +4897,9 @@ static ssize_t memory_max_write(struct kernfs_open_file *of, for (;;) { unsigned long nr_pages = page_counter_read(&memcg->memory); + if (max != READ_ONCE(memcg->memory.max)) + break; + if (nr_pages <= max) break; From f74e6dff5440f662bced614b723a7d8f2b05919d Mon Sep 17 00:00:00 2001 From: Sergey Senozhatsky Date: Thu, 30 Jul 2026 16:51:46 +0900 Subject: [PATCH 0992/1328] Documentation: zram: correct algo parameters configuration documentation zram has always reset all previously set parameters for the given algorithm in comp_params_store(). Make documentation more clear and explicitly state that all relevant/necessary parameters should be set in one configuration write. Link: https://lore.kernel.org/20260730075158.1339787-1-senozhatsky@chromium.org Signed-off-by: Sergey Senozhatsky Cc: Jonathan Corbet Cc: Minchan Kim Signed-off-by: Andrew Morton --- Documentation/admin-guide/blockdev/zram.rst | 40 ++++++++++++++++++--- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/Documentation/admin-guide/blockdev/zram.rst b/Documentation/admin-guide/blockdev/zram.rst index 2f6bbfd991fe..148b7cf3b924 100644 --- a/Documentation/admin-guide/blockdev/zram.rst +++ b/Documentation/admin-guide/blockdev/zram.rst @@ -109,14 +109,41 @@ path to the `dict` along with other parameters:: #pass path to pre-trained zstd dictionary echo "algo=zstd dict=/etc/dictionary" > /sys/block/zram0/algorithm_params - #same, but using algorithm priority - echo "priority=1 dict=/etc/dictionary" > \ - /sys/block/zram0/algorithm_params - #pass path to pre-trained zstd dictionary and compression level echo "algo=zstd level=8 dict=/etc/dictionary" > \ /sys/block/zram0/algorithm_params + #same, but using algorithm priority + echo "algo=zstd priority=1" > /sys/block/zram0/recomp_algorithm + echo "priority=1 dict=/etc/dictionary" > \ + /sys/block/zram0/algorithm_params + +Each write to `algorithm_params` replaces the entire set of parameters of +the corresponding algorithm, parameters that are not listed in the write +are reset to their default values. Configure all of the parameters of an +algorithm in one write:: + + #WRONG: the second write resets level back to its default value + echo "algo=zstd level=8" > /sys/block/zram0/algorithm_params + echo "algo=zstd dict=/etc/dictionary" > /sys/block/zram0/algorithm_params + + #RIGHT + echo "algo=zstd level=8 dict=/etc/dictionary" > \ + /sys/block/zram0/algorithm_params + +Select the compression algorithm before configuring its parameters. The +parameters of one algorithm are not necessarily valid for another one, so +changing the algorithm of a particular priority resets that priority's +parameters:: + + #WRONG: comp_algorithm write resets the previously configured level + echo "level=8" > /sys/block/zram0/algorithm_params + echo zstd > /sys/block/zram0/comp_algorithm + + #RIGHT + echo zstd > /sys/block/zram0/comp_algorithm + echo "algo=zstd level=8" > /sys/block/zram0/algorithm_params + Parameters are algorithm specific: not all algorithms support pre-trained dictionaries, not all algorithms support `level`. Furthermore, for certain algorithms `level` controls the compression level (the higher the value the @@ -124,6 +151,11 @@ better the compression ratio, it even can take negatives values for some algorithms), for other algorithms `level` is acceleration level (the higher the value the lower the compression ratio). +Parameters are handed over to the compression algorithm when the device is +initialised, hence invalid parameters (or parameters that the selected +algorithm does not support) are reported by the `disksize` write, and not +by the `algorithm_params` write that has configured them. + Set Disksize ============ From 6b0d1083364fc8e7cc2f7d1f93ee3ee78f4d52f7 Mon Sep 17 00:00:00 2001 From: Shakeel Butt Date: Tue, 28 Jul 2026 19:46:12 -0700 Subject: [PATCH 0993/1328] memcg: bypass the reclaim and oom killer for dying tasks once oom_reaper is done At Meta, we are seeing instances where an OOM killed job is stuck in the exit path for several hours. In one particular case, the job was stuck for more than 8 hours and I had to manually remove the memory.max limits to allow the process to exit. The job was a single process job and had ~55 GiB memory.max and zswap enabled. It had almost 0 anon in memory and ~111 GiB in zswap compressed to ~51 GiB zswap pool (i.e. almost all of memory.current was zswap). Nothing was left on the LRUs to reclaim. On further inspection, I observed ~20k threads of that process stuck with the following stack: [<0>] mem_cgroup_out_of_memory+0x4e/0xa0 [<0>] charge_memcg+0x8bf/0x990 [<0>] mem_cgroup_swapin_charge_folio+0x4e/0x80 [<0>] __read_swap_cache_async+0x10c/0x260 [<0>] swapin_readahead+0x116/0x3f0 [<0>] do_swap_page+0x13c/0x1ce0 [<0>] handle_mm_fault+0x61d/0x11f0 [<0>] do_user_addr_fault+0x3e7/0x6d0 [<0>] exc_page_fault+0x8f/0x110 [<0>] asm_exc_page_fault+0x22/0x30 [<0>] __get_user_8+0x14/0x20 [<0>] futex_cleanup+0x27/0x1c0 [<0>] futex_exit_release+0x47/0x60 [<0>] do_exit+0x107/0x940 [<0>] do_group_exit+0x81/0xa0 [<0>] get_signal+0x2b1/0x6e0 [<0>] arch_do_signal_or_restart+0x1a/0x1c0 [<0>] exit_to_user_mode_loop+0xa8/0x1c0 [<0>] do_syscall_64+0x152/0x250 [<0>] entry_SYSCALL_64_after_hwframe+0x4b/0x53 In addition the dmesg was filled with "Out of memory and no killable processes..." messages. I have no idea why oom reaper was not able to reap/unmap the process. My guess is that since oom reaper tries to acquire mmap_lock in read mode limited number of times and then gives up, there might be a thread of that process which had mmap_lock in write mode at that time. My initial suspicion was the futex_cleanup and kernel page fault causing infinite fault and charge retries but that was put to rest in previous discussions happened on similar problem [1]. My current theory is that it is just a simple slow serialization behind the oom_lock. Unlike page allocator, memcg charge code takes the oom_lock without the "try". Though memcg oom code uses mutex_lock_killable(), note that in the call stack get_signal() consumes SIGKILL (or sigdelset(SIGKILL)) before calling do_group_exit(). So this mutex_lock_killable() is just a mutex_lock() here. Therefore 10s of thousands of threads are waiting on oom_lock and one by one they get -EFAULT from get_user() in the futex cleanup code and bails out. Discussion from [1] led to commit a75ffa26122b ("memcg, oom: do not bypass oom killer for dying tasks") which routes dying tasks into the OOM path precisely so the oom_reaper can reap their mm and free the memory asynchronously. But the reaper is best-effort and one-shot: if it cannot take mmap_lock for read (e.g. a sibling thread holds it for write) it sets MMF_OOM_SKIP and never retries, leaving only the glacial oom_lock-serialized synchronous drain. Once MMF_OOM_SKIP is set there is no more asynchronous reclaim coming for the mm, so a dying task charging against it has nothing left to wait for: it frees its memory only once it finishes exiting. Running reclaim and the (no-victim) OOM killer for it is then pointless, and doing it for 10s of thousands of exiting threads is what serializes them behind oom_lock. So before reclaim, if current is an OOM victim whose reaper is done, fail the charge. Reproduced with 20k threads, each parking a robust futex head on its own zswapped page, OOM-group-killed while a sibling holds mmap_lock for write so the reaper gives up and sets MMF_OOM_SKIP. Tested on next-20260728 and baseline show ~90 seconds exit time while with the patch the exit time reduced to ~3 seconds. Link: https://lore.kernel.org/20260729024612.3369005-1-shakeel.butt@linux.dev Link: https://lore.kernel.org/7a4e5591f45df455e6a485fc5400989569d3d22d.camel@surriel.com/ [1] Signed-off-by: Shakeel Butt Acked-by: Johannes Weiner Acked-by: Michal Hocko Cc: David Rientjes Cc: Muchun Song Cc: Nhat Pham Cc: Rik van Riel Cc: Roman Gushchin Cc: Suren Baghdasaryan Cc: Signed-off-by: Andrew Morton --- mm/memcontrol.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/mm/memcontrol.c b/mm/memcontrol.c index 0dd847af69a2..1d3339520809 100644 --- a/mm/memcontrol.c +++ b/mm/memcontrol.c @@ -2693,6 +2693,19 @@ static int try_charge_memcg(struct mem_cgroup *memcg, gfp_t gfp_mask, if (!gfpflags_allow_blocking(gfp_mask)) goto nomem; + /* + * OOM victim still needs to charge memory to exit. OOM reaper should + * help but it might fail on mmap_lock contention. If the victim is a + * large thread group then all exiting threads might compete on oom_lock + * just to learn that there is nothing really killable anymore. Bail + * out early and fail the charge to expedite their exit. They are + * considered fully reclaimed by the oom reaper and they shouldn't + * contribute further charges. + */ + if (tsk_is_oom_victim(current) && + mm_flags_test(MMF_OOM_SKIP, current->signal->oom_mm)) + goto nomem; + __memcg_memory_event(mem_over_limit, MEMCG_MAX, allow_spinning); raised_max_event = true; From 62e39381b7804eb94a43bd1e0b0c216aad3e5f08 Mon Sep 17 00:00:00 2001 From: Alexander Gordeev Date: Mon, 20 Jul 2026 16:16:33 +0200 Subject: [PATCH 0994/1328] mm: use proper PTE accessor in move_ptes() Follow the pattern established by commit c33c794828f2 ("mm: ptep_get() conversion") and use the proper PTE accessor instead of a direct pointer dereference. Link: https://lore.kernel.org/20260720141633.501799-1-agordeev@linux.ibm.com Fixes: b36b701bbcd9 ("mm: expose abnormal new_pte during move_ptes") Signed-off-by: Alexander Gordeev Acked-by: David Hildenbrand (Arm) Cc: Ryan Roberts Signed-off-by: Andrew Morton --- mm/mremap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/mremap.c b/mm/mremap.c index 9ea1707eafa5..e8df5cdb0ac9 100644 --- a/mm/mremap.c +++ b/mm/mremap.c @@ -264,7 +264,7 @@ static int move_ptes(struct pagetable_move_control *pmc, for (; old_addr < old_end; old_ptep += nr_ptes, old_addr += nr_ptes * PAGE_SIZE, new_ptep += nr_ptes, new_addr += nr_ptes * PAGE_SIZE) { - VM_WARN_ON_ONCE(!pte_none(*new_ptep)); + VM_WARN_ON_ONCE(!pte_none(ptep_get(new_ptep))); nr_ptes = 1; max_nr_ptes = (old_end - old_addr) >> PAGE_SHIFT; From 5120b1e048d48596ffaec1a8412012a91adba73b Mon Sep 17 00:00:00 2001 From: Guillaume Morin Date: Tue, 28 Jul 2026 21:29:03 +0200 Subject: [PATCH 0995/1328] hugetlb: only adjust reservation during unmapping if mapcount is 0 Since df7a6d1f6405, __unmap_hugepage_range can adjust reservations. In the case of folio mapped in both a parent and a child, if the parent unmaps the range first, the reservation adjustment will result in an underflow of the reserved count. Once the child unmaps the range, the count is restored. Change __unmap_hugepage_range() to check the mapcount before adjusting the reservation. Link: https://lore.kernel.org/all/alEJkwn5VlTTH_ZX@bender.morinfr.org/ Link: https://lore.kernel.org/amkC_1Ya6OiUoiLZ@bender.morinfr.org Fixes: df7a6d1f6405 ("mm/hugetlb: restore the reservation if needed") Signed-off-by: Guillaume Morin Reviewed-by: Breno Leitao Reviewed-by: Rik van Riel Cc: Muchun Song Cc: David Hildenbrand Cc: Oscar Salvador Cc: Signed-off-by: Andrew Morton --- mm/hugetlb.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mm/hugetlb.c b/mm/hugetlb.c index 49bf325325c0..7d14511c20ad 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -5218,6 +5218,7 @@ void __unmap_hugepage_range(struct mmu_gather *tlb, struct vm_area_struct *vma, bool adjust_reservation; unsigned long last_addr_mask; + i_mmap_assert_write_locked(vma->vm_file->f_mapping); WARN_ON(!is_vm_hugetlb_page(vma)); BUG_ON(start & ~huge_page_mask(h)); BUG_ON(end & ~huge_page_mask(h)); @@ -5309,7 +5310,10 @@ void __unmap_hugepage_range(struct mmu_gather *tlb, struct vm_area_struct *vma, /* * Restore the reservation for anonymous page, otherwise the - * backing page could be stolen by someone. + * backing page could be stolen by someone. Restore only on the + * last unmap, otherwise the owner could empty its resv map + * while the folio is still mapped by a child. Note that holding + * i_mmap_lock_write is needed to check the number of mappings. * If there we are freeing a surplus, do not set the restore * reservation bit. */ @@ -5317,7 +5321,7 @@ void __unmap_hugepage_range(struct mmu_gather *tlb, struct vm_area_struct *vma, spin_lock_irq(&hugetlb_lock); if (!h->surplus_huge_pages && __vma_private_lock(vma) && - folio_test_anon(folio)) { + !folio_mapped(folio) && folio_test_anon(folio)) { folio_set_hugetlb_restore_reserve(folio); /* Reservation to be adjusted after the spin lock */ adjust_reservation = true; From 7a39f03bc9da3499c2423758f353ab72d23faa16 Mon Sep 17 00:00:00 2001 From: Pratyush Mallick Date: Fri, 31 Jul 2026 19:37:05 +0000 Subject: [PATCH 0996/1328] mm/page_reporting: add page_reporting_delay_ms module parameter Free page reporting currently hardcodes a 2-second interval between reports. This rigid delay cannot accommodate diverse guest workloads. This patch introduces a module parameter, page_reporting_delay_ms (default: 2000), allowing users to tune the reporting rate: - Lower values enable aggressive memory reclamation by returning unused pages to the host immediately. - Higher values help batch pages during spiky allocation/free churn, reducing hypercalls and nested page fault overheads. Setting the delay to 0 is safe and execution is strictly gated by: - reporting is only triggered by high-order page frees. - expensive hypercalls are bounded by a slot capacity watermark check before proceeding. Link: https://lore.kernel.org/20260731193705.2902728-1-pratmal@google.com Signed-off-by: Pratyush Mallick Reviewed-by: SJ Park Acked-by: Lorenzo Stoakes (ARM) Acked-by: David Hildenbrand (Arm) Cc: Anshuman Khandual Cc: Brendan Jackman Cc: Greg Thelen Cc: Johannes Weiner Cc: Jonathan Corbet Cc: Liam R. Howlett Cc: Michal Hocko Cc: Mike Rapoport Cc: SeongJae Park Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Zi Yan Signed-off-by: Andrew Morton --- .../admin-guide/kernel-parameters.txt | 6 ++++ mm/page_reporting.c | 28 ++++++++++++------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index b5493a7f8f22..364c2dce8e70 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -4810,6 +4810,12 @@ Kernel parameters Adjust the minimal page reporting order. The page reporting is disabled when it exceeds MAX_PAGE_ORDER. + page_reporting.page_reporting_delay_ms= + [KNL] Free page reporting delay in milliseconds + Format: + Adjust the delay in milliseconds between free page + reporting intervals. Default is 2000 (2 seconds). + panic= [KNL] Kernel behaviour on panic: delay timeout > 0: seconds before rebooting timeout = 0: wait forever diff --git a/mm/page_reporting.c b/mm/page_reporting.c index 1cce8729696e..de587be17801 100644 --- a/mm/page_reporting.c +++ b/mm/page_reporting.c @@ -48,7 +48,11 @@ MODULE_PARM_DESC(page_reporting_order, "Set page reporting order"); */ EXPORT_SYMBOL_GPL(page_reporting_order); -#define PAGE_REPORTING_DELAY (2 * HZ) +static unsigned int page_reporting_delay_ms = 2 * MSEC_PER_SEC; +module_param(page_reporting_delay_ms, uint, 0644); +MODULE_PARM_DESC(page_reporting_delay_ms, + "Set page reporting delay in milliseconds"); + static struct page_reporting_dev_info __rcu *pr_dev_info __read_mostly; enum { @@ -57,6 +61,13 @@ enum { PAGE_REPORTING_ACTIVE }; +/* schedule work for page reporting */ +static void page_reporting_schedule_work(struct page_reporting_dev_info *prdev) +{ + queue_delayed_work(system_freezable_wq, &prdev->work, + msecs_to_jiffies(page_reporting_delay_ms)); +} + /* request page reporting */ static void __page_reporting_request(struct page_reporting_dev_info *prdev) @@ -77,12 +88,10 @@ __page_reporting_request(struct page_reporting_dev_info *prdev) return; /* - * Delay the start of work to allow a sizable queue to build. For - * now we are limiting this to running no more than once every - * couple of seconds. + * Delay the start of work to allow a sizable queue to build. + * We limit this based on page_reporting_delay_ms. */ - queue_delayed_work(system_freezable_wq, &prdev->work, - PAGE_REPORTING_DELAY); + page_reporting_schedule_work(prdev); } /* notify prdev of free page reporting request */ @@ -337,13 +346,12 @@ static void page_reporting_process(struct work_struct *work) err_out: /* * If the state has reverted back to requested then there may be - * additional pages to be processed. We will defer for 2s to allow - * more pages to accumulate. + * additional pages to be processed. We will defer by + * page_reporting_delay_ms to allow more pages to accumulate. */ state = atomic_cmpxchg(&prdev->state, state, PAGE_REPORTING_IDLE); if (state == PAGE_REPORTING_REQUESTED) - queue_delayed_work(system_freezable_wq, &prdev->work, - PAGE_REPORTING_DELAY); + page_reporting_schedule_work(prdev); } static DEFINE_MUTEX(page_reporting_mutex); From 8380671909bfcdd44818abf6b93f82bd3669bc8c Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Sat, 1 Aug 2026 01:47:55 +0900 Subject: [PATCH 0997/1328] mm/sparse: correct init section annotations The !SPARSEMEM_EXTREME stub of sparse_index_init() has no annotation but the SPARSEMEM_EXTREME variant is __meminit. So mark the stub __meminit too. mminit_validate_memmodel_limits() is only called by memory_present(), which is __init. So mark it __init. sparse_usagebuf and sparse_usagebuf_end are only used by sparse_init_early_section(), sparse_usage_init() and sparse_usage_fini(), which are all __init. So mark them __initdata. Link: https://lore.kernel.org/20260731164758.1210668-1-ekffu200098@gmail.com Signed-off-by: Sang-Heon Jeon Reviewed-by: Andrew Morton Acked-by: David Hildenbrand (Arm) Acked-by: Mike Rapoport (Microsoft) Reviewed-by: Anshuman Khandual Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/sparse.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mm/sparse.c b/mm/sparse.c index 704a9dec2b9a..67fa192d4289 100644 --- a/mm/sparse.c +++ b/mm/sparse.c @@ -104,7 +104,7 @@ int __meminit sparse_index_init(unsigned long section_nr, int nid) return 0; } #else /* !SPARSEMEM_EXTREME */ -int sparse_index_init(unsigned long section_nr, int nid) +int __meminit sparse_index_init(unsigned long section_nr, int nid) { return 0; } @@ -127,7 +127,7 @@ static inline int sparse_early_nid(struct mem_section *section) } /* Validate the physical addressing limitations of the model */ -static void __meminit mminit_validate_memmodel_limits(unsigned long *start_pfn, +static void __init mminit_validate_memmodel_limits(unsigned long *start_pfn, unsigned long *end_pfn) { unsigned long max_sparsemem_pfn = (DIRECT_MAP_PHYSMEM_END + 1) >> PAGE_SHIFT; @@ -249,8 +249,8 @@ void __weak __meminit vmemmap_populate_print_last(void) { } -static void *sparse_usagebuf __meminitdata; -static void *sparse_usagebuf_end __meminitdata; +static void *sparse_usagebuf __initdata; +static void *sparse_usagebuf_end __initdata; /* * Helper function that is used for generic section initialization, and From 7822fa5f4f647d05d31eea3edd01382c6183e4cd Mon Sep 17 00:00:00 2001 From: Wilson Felipe Pereira Date: Fri, 31 Jul 2026 05:37:06 +0000 Subject: [PATCH 0998/1328] mm: zswap: drop list_lru param from zswap_lru_add() and _del() Since zswap_lru_add() and zswap_lru_del() are only called with the global zswap_list_lru, remove the redundant list_lru argument and use zswap_list_lru directly. Link: https://lore.kernel.org/20260731053721.1412304-1-wfelipe@google.com Signed-off-by: Wilson Felipe Pereira Acked-by: Johannes Weiner Acked-by: Yosry Ahmed Reviewed-by: SJ Park Acked-by: Nhat Pham Cc: Chengming Zhou Signed-off-by: Andrew Morton --- mm/zswap.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mm/zswap.c b/mm/zswap.c index 761cd699e0a3..a810524c7621 100644 --- a/mm/zswap.c +++ b/mm/zswap.c @@ -665,7 +665,7 @@ static inline int entry_to_nid(struct zswap_entry *entry) return page_to_nid(virt_to_page(entry)); } -static void zswap_lru_add(struct list_lru *list_lru, struct zswap_entry *entry) +static void zswap_lru_add(struct zswap_entry *entry) { int nid = entry_to_nid(entry); struct mem_cgroup *memcg; @@ -684,11 +684,11 @@ static void zswap_lru_add(struct list_lru *list_lru, struct zswap_entry *entry) rcu_read_lock(); memcg = mem_cgroup_from_entry(entry); /* will always succeed */ - list_lru_add(list_lru, &entry->lru, nid, memcg); + list_lru_add(&zswap_list_lru, &entry->lru, nid, memcg); rcu_read_unlock(); } -static void zswap_lru_del(struct list_lru *list_lru, struct zswap_entry *entry) +static void zswap_lru_del(struct zswap_entry *entry) { int nid = entry_to_nid(entry); struct mem_cgroup *memcg; @@ -696,7 +696,7 @@ static void zswap_lru_del(struct list_lru *list_lru, struct zswap_entry *entry) rcu_read_lock(); memcg = mem_cgroup_from_entry(entry); /* will always succeed */ - list_lru_del(list_lru, &entry->lru, nid, memcg); + list_lru_del(&zswap_list_lru, &entry->lru, nid, memcg); rcu_read_unlock(); } @@ -764,7 +764,7 @@ static void zswap_entry_cache_free(struct zswap_entry *entry) */ static void zswap_entry_free(struct zswap_entry *entry) { - zswap_lru_del(&zswap_list_lru, entry); + zswap_lru_del(entry); zs_free(entry->pool->zs_pool, entry->handle); zswap_pool_put(entry->pool); if (entry->objcg) { @@ -1461,7 +1461,7 @@ static bool zswap_store_page(struct page *page, entry->referenced = true; if (entry->length) { INIT_LIST_HEAD(&entry->lru); - zswap_lru_add(&zswap_list_lru, entry); + zswap_lru_add(entry); } return true; From 34a00895d032a414830d41106a09329ae6c251b6 Mon Sep 17 00:00:00 2001 From: Arvind Yadav Date: Tue, 28 Jul 2026 11:58:32 +0530 Subject: [PATCH 0999/1328] mm/migrate_device: clear stale mapping after freeing swapcache __migrate_device_pages() reads the folio mapping before calling folio_free_swap(). When folio_free_swap() succeeds, the folio is removed from the swap cache, but the saved mapping still points to swap_space. Passing the stale mapping to folio_migrate_mapping() makes it use the mapped-folio path for a folio that is no longer in swapcache. It can then operate on swap_space.i_pages with invalid reference accounting, eventually triggering a folio reference count BUG. After a successful split, nr still contains the number of pages in the original large folio, although each resulting page is now a separate order-0 folio. Reset nr to 1 so each split folio is processed separately, including its own swapcache removal and mapping lookup. Refresh the saved mapping after folio_free_swap() so the current folio state is used during migration. Link: https://lore.kernel.org/20260728062832.1107127-1-arvind.yadav@intel.com Fixes: df263d9a7dff ("mm/migrate_device: try to handle swapcache pages") Signed-off-by: Arvind Yadav Reviewed-by: Zi Yan Reviewed-by: Balbir Singh Cc: David Hildenbrand Cc: Matthew Brost Cc: Joshua Hahn Cc: Rakie Kim Cc: Byungchul Park Cc: Gregory Price Cc: Ying Huang Cc: Alistair Popple Cc: Signed-off-by: Andrew Morton --- mm/migrate_device.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/mm/migrate_device.c b/mm/migrate_device.c index 18d097c38853..9a346162c688 100644 --- a/mm/migrate_device.c +++ b/mm/migrate_device.c @@ -1193,6 +1193,13 @@ static void __migrate_device_pages(unsigned long *src_pfns, MIGRATE_PFN_COMPOUND); goto next; } + + /* + * reset nr so that only first after-split folio + * is processed below + */ + VM_WARN_ON_ONCE(folio_test_large(folio)); + nr = 1; } else if ((src_pfns[i] & MIGRATE_PFN_MIGRATE) && (dst_pfns[i] & MIGRATE_PFN_COMPOUND) && !(src_pfns[i] & MIGRATE_PFN_COMPOUND)) { @@ -1232,6 +1239,12 @@ static void __migrate_device_pages(unsigned long *src_pfns, folio = page_folio(migrate_pfn_to_page(src_pfns[i+j])); newfolio = page_folio(migrate_pfn_to_page(dst_pfns[i+j])); + /* + * folio_free_swap() removed the folio from the swap + * cache. Refresh the saved mapping before migration. + */ + mapping = folio_mapping(folio); + r = folio_migrate_mapping(mapping, newfolio, folio, extra_cnt); if (r) src_pfns[i+j] &= ~MIGRATE_PFN_MIGRATE; From c299a2285d9d8bda4da024455de65e3d00de6f17 Mon Sep 17 00:00:00 2001 From: Zi Yan Date: Tue, 4 Aug 2026 17:04:27 -0400 Subject: [PATCH 1000/1328] mm/huge_memory: use folio's memcg inside __folio_split() Patch series "Honor XA_FLAGS_ACCOUNT in xas_split_alloc() and charge to folio's memcg", v3. __GFP_ACCOUNT is needed for xarray node allocation accounting when XA_FLAGS_ACCOUNT is set. Commit 7b785645e8f13 ("mm: fix page cache convergence regression") fixed a workingset regression with it. xas_split_alloc() does not have it and needs to be fixed. In addition, based on Sashiko's review[1] and Johannes' confirmation[2], to charge the right memcg, folio's memcg needs to be active during folio split. Add that before adding __GFP_ACCOUNT. There is no workingset convergence regression related to missing __GFP_ACCOUNT in xas_split_alloc() and the impact to userspace should be minor. This patch (of 2): During a pagecache folio split, an xarray node allocation can happen and needs to charge at folio's memcg instead of folio split invoker's memcg, because for example folio split can happen during reclaim and reclaim's active memcg might not be folio's memcg. Switch to folio's memcg at the beginning and switch back afterwards. Link: https://lore.kernel.org/20260804-add-gfp_account-to-xas_split_alloc-v3-0-38cb3ff325c5@nvidia.com Link: https://lore.kernel.org/20260804-add-gfp_account-to-xas_split_alloc-v3-1-38cb3ff325c5@nvidia.com Link: https://sashiko.dev/#/patchset/20260727-add-gfp_account-to-xas_split_alloc-v1-1-9fae6bf64838%40nvidia.com?part=1 [1] Link: https://lore.kernel.org/all/amtcBZ-_QVRgCd6b@cmpxchg.org/ [2] Fixes: 6b24ca4a1a8d ("mm: Use multi-index entries in the page cache") Signed-off-by: Zi Yan Suggested-by: Johannes Weiner Reviewed-by: Baolin Wang Acked-by: Lorenzo Stoakes (ARM) Acked-by: Johannes Weiner Cc: Barry Song Cc: David Hildenbrand Cc: Dev Jain Cc: Lance Yang Cc: Liam R. Howlett Cc: Matthew Wilcox (Oracle) Cc: Ryan Roberts Cc: William Kucharski Cc: Signed-off-by: Andrew Morton --- mm/huge_memory.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/mm/huge_memory.c b/mm/huge_memory.c index a8174d1d3848..ced400f72d43 100644 --- a/mm/huge_memory.c +++ b/mm/huge_memory.c @@ -4105,34 +4105,42 @@ static int __folio_split(struct folio *folio, unsigned int new_order, XA_STATE(xas, &folio->mapping->i_pages, folio->index); struct folio *end_folio = folio_next(folio); bool is_anon = folio_test_anon(folio); + struct mem_cgroup *memcg, *old_memcg; struct address_space *mapping = NULL; struct anon_vma *anon_vma = NULL; int old_order = folio_order(folio); struct folio *new_folio, *next; int nr_shmem_dropped = 0; enum ttu_flags ttu_flags = 0; - int ret; pgoff_t end = 0; + int ret; VM_WARN_ON_ONCE_FOLIO(!folio_test_locked(folio), folio); VM_WARN_ON_ONCE_FOLIO(!folio_test_large(folio), folio); if (folio != page_folio(split_at) || folio != page_folio(lock_at)) { ret = -EINVAL; - goto out; + goto out_no_memcg; } if (new_order >= old_order) { ret = -EINVAL; - goto out; + goto out_no_memcg; } ret = folio_check_splittable(folio, new_order, split_type); if (ret) { VM_WARN_ONCE(ret == -EINVAL, "Tried to split an unsplittable folio"); - goto out; + goto out_no_memcg; } + /* + * switch to folio's memcg as xarray node allocation can happen and + * needs to charge to it. + */ + memcg = get_mem_cgroup_from_folio(folio); + old_memcg = set_active_memcg(memcg); + if (is_anon) { /* * The caller does not necessarily hold an mmap_lock that would @@ -4275,6 +4283,10 @@ static int __folio_split(struct folio *folio, unsigned int new_order, if (mapping) i_mmap_unlock_read(mapping); out: + /* restore to caller's old_memcg */ + set_active_memcg(old_memcg); + mem_cgroup_put(memcg); +out_no_memcg: xas_destroy(&xas); if (is_pmd_order(old_order)) count_vm_event(!ret ? THP_SPLIT_PAGE : THP_SPLIT_PAGE_FAILED); From 789763523fb43cdc328de5cb5dcd19240ccf90d8 Mon Sep 17 00:00:00 2001 From: Zi Yan Date: Tue, 4 Aug 2026 17:04:28 -0400 Subject: [PATCH 1001/1328] xarray: honor XA_FLAGS_ACCOUNT in xas_split_alloc() XArray operations that allocate xa_nodes, such as xas_nomem() and xas_alloc(), add __GFP_ACCOUNT when the array has XA_FLAGS_ACCOUNT set. This charges the allocated memory and avoids the workingset convergence issue described by commit 7b785645e8f13 ("mm: fix page cache convergence regression"). xas_split_alloc() does not add _GFP_ACCOUNT when XA_FLAGS_ACCOUNT is present. Fix it. Link: https://lore.kernel.org/20260804-add-gfp_account-to-xas_split_alloc-v3-2-38cb3ff325c5@nvidia.com Fixes: 6b24ca4a1a8d ("mm: Use multi-index entries in the page cache") Signed-off-by: Zi Yan Reviewed-by: Lorenzo Stoakes (ARM) Acked-by: Johannes Weiner Cc: Baolin Wang Cc: Barry Song Cc: David Hildenbrand Cc: Dev Jain Cc: Lance Yang Cc: Liam R. Howlett Cc: Matthew Wilcox (Oracle) Cc: Ryan Roberts Cc: William Kucharski Cc: Signed-off-by: Andrew Morton --- lib/xarray.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/xarray.c b/lib/xarray.c index 9a8b4916540c..bfe7bef80f34 100644 --- a/lib/xarray.c +++ b/lib/xarray.c @@ -1053,6 +1053,9 @@ void xas_split_alloc(struct xa_state *xas, void *entry, unsigned int order, if (xas->xa_shift + XA_CHUNK_SHIFT > order) return; + if (xas->xa->xa_flags & XA_FLAGS_ACCOUNT) + gfp |= __GFP_ACCOUNT; + do { struct xa_node *node; From 078e1a0fc41a42baaf113383b52dab8874c0b967 Mon Sep 17 00:00:00 2001 From: "David Hildenbrand (Arm)" Date: Fri, 31 Jul 2026 22:27:53 +0200 Subject: [PATCH 1002/1328] mm/gup: fix always draining LRU caches in collect_longterm_unpinnable_folios() folio_may_be_lru_cached() is currently only true for small folios, and for small folios FOLL_PIN adds GUP_PIN_COUNTING_BIAS references instead of 1 in try_grab_folio()/try_grab_folio_fast(). Consequently, our folio_ref_count(folio) != folio_expected_ref_count(folio) + 1 check in collect_longterm_unpinnable_folios() will currently always identify "reference mismatch" and first drain the local LRU cache to then drain the LRU cache on all CPUs, as collect_longterm_unpinnable_folios() is really called after pinning the folios with FOLL_PIN. Add a comment because the current code is not quite intuitive: we used to drain only to make sure the folio_isolate_lru() would succeed. But then we also started draining to make later migration more reliable. We'll refactor that code soon a bit, to also make it usable in other context where we really want to remove any references from LRU caches. Let's add CC stable, because having an easy way for excessive LRU cache draining on all CPUs does not sound right. In common scenarios we don't expect to ever have to drain. Link: https://lore.kernel.org/20260731-check_and_migrate_movable_folios-v1-1-e0002d7b791e@kernel.org Fixes: 98c6d259319e ("mm/gup: check ref_count instead of lru before migration") Fixes: a09a8a1fbb37 ("mm/gup: local lru_add_drain() to avoid lru_add_drain_all()") Signed-off-by: David Hildenbrand (Arm) Acked-by: Hugh Dickins Cc: Ackerley Tng Cc: Jason Gunthorpe Cc: John Hubbard Cc: Kiryl Shutsemau Cc: Peter Xu Cc: Signed-off-by: Andrew Morton --- mm/gup.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/mm/gup.c b/mm/gup.c index 8ea3de60e82d..bce275c7dbb6 100644 --- a/mm/gup.c +++ b/mm/gup.c @@ -2273,6 +2273,7 @@ static unsigned long collect_longterm_unpinnable_folios( for (folio = pofs_get_folio(pofs, i); folio; folio = pofs_next_folio(folio, pofs, &i)) { + const int pin_refs = folio_has_pincount(folio) ? 1 : GUP_PIN_COUNTING_BIAS; if (folio_is_longterm_pinnable(folio)) continue; @@ -2287,15 +2288,20 @@ static unsigned long collect_longterm_unpinnable_folios( continue; } + /* + * We drain not only to make the folio_isolate_lru() succeed, + * but also to remove any other folio references from LRU + * caches. + */ if (drained == 0 && folio_may_be_lru_cached(folio) && folio_ref_count(folio) != - folio_expected_ref_count(folio) + 1) { + folio_expected_ref_count(folio) + pin_refs) { lru_add_drain(); drained = 1; } if (drained == 1 && folio_may_be_lru_cached(folio) && folio_ref_count(folio) != - folio_expected_ref_count(folio) + 1) { + folio_expected_ref_count(folio) + pin_refs) { lru_add_drain_all(); drained = 2; } From fc6415a384f026f48b414a48588c0970c060679f Mon Sep 17 00:00:00 2001 From: "David Hildenbrand (Arm)" Date: Thu, 6 Aug 2026 20:09:06 +0200 Subject: [PATCH 1003/1328] mm/gup: factor out LRU cache draining for folio into lru_cache_drain_for_folio() KVM with guest_memfd wants to remove any folio references due to LRU caches, as it really must only allow to convert folios from shared to private when there are no unexpected folio references (e.g., from GUP references). So, to drive the refcount down, it needs a way to flush the LRU caches. Let's factor out what we have in lru_cache_drain_for_folio(). Document it, and also mention that concurrent folio (un)mapping might, in theory, miss detecting LRU cache references. Keep obtaining the expected refcount twice to minimize the possibility. For the current and future user that should work, and we don't really have a better alternative: we could detect if the mapcount changed, but it would still be racy and add more complexity with questionable benefit. Maybe there is a chance to avoid the draining entirely in the future, by avoiding extra references from the LRU cache: Hugh thinks there might be a way. But for the time being, this handling is unfortunately required. Make folio_may_be_lru_cached() accept a const pointer so lru_cache_drain_for_folio() can accept a const pointer as well. Link: https://lore.kernel.org/20260806-lru_cache_drain_for_folio-v1-1-c6287d295e99@kernel.org Signed-off-by: David Hildenbrand (Arm) Reviewed-by: Fuad Tabba Cc: Ackerley Tng Cc: Baoquan He Cc: Barry Song Cc: Chris Li Cc: Jason Gunthorpe Cc: John Hubbard Cc: Kairui Song Cc: Kemeng Shi Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Nhat Pham Cc: Peter Xu Cc: Sean Christopherson Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- include/linux/swap.h | 8 ++++++++ mm/folio.c | 46 ++++++++++++++++++++++++++++++++++++++++++++ mm/gup.c | 15 ++------------- mm/internal.h | 2 +- 4 files changed, 57 insertions(+), 14 deletions(-) diff --git a/include/linux/swap.h b/include/linux/swap.h index 330a420fd6de..b4b1c0a84c8b 100644 --- a/include/linux/swap.h +++ b/include/linux/swap.h @@ -298,6 +298,14 @@ void folio_add_lru(struct folio *folio); void folio_mark_accessed(struct folio *folio); void lru_add_drain_all(void); +enum lru_cache_drained { + LRU_CACHE_NOT_DRAINED, + LRU_CACHE_DRAINED, + LRU_CACHE_DRAINED_ALL, +}; +void lru_cache_drain_for_folio(const struct folio *folio, + unsigned int extra_refs, enum lru_cache_drained *drained); + /* linux/mm/folio-compat.c */ void mark_page_accessed(struct page *page); diff --git a/mm/folio.c b/mm/folio.c index a9e328c3f21b..59c477120b9a 100644 --- a/mm/folio.c +++ b/mm/folio.c @@ -881,6 +881,52 @@ void lru_add_drain_all(void) } #endif /* CONFIG_SMP */ +/** + * lru_cache_drain_for_folio() - drain LRU caches if the caches might hold + * folio references + * @folio: The folio. + * @extra_refs: Extra folio references held by the caller. + * @drained: Drain status for batch folio processing. + * + * Drain LRU caches if the caches might hold folio references. Start + * with a local LRU cache drain, to then drain LRU caches on all CPUs if + * local draining was insufficient. + * + * This function detects LRU cache references by comparing the folio refcount + * with the sum of the expected folio refcount + extra references held by the + * caller. Note that we cannot rely on PG_lru to reliably detect all LRU + * cache references, and there are rare scenarios (concurrent folio (un)mapping) + * where this function might miss detecting LRU cache references. + * + * If @drained is not NULL, the function will avoid re-draining LRU caches + * when processing multiple folios in a row. In that case, the variable + * @drained points at must be initialized to LRU_CACHE_NOT_DRAINED before + * the first invocation by the caller. + */ +void lru_cache_drain_for_folio(const struct folio *folio, + unsigned int extra_refs, enum lru_cache_drained *drained) +{ + if (!folio_may_be_lru_cached(folio)) + return; + + if (!drained || *drained == LRU_CACHE_NOT_DRAINED) { + if (folio_ref_count(folio) == + folio_expected_ref_count(folio) + extra_refs) + return; + lru_add_drain(); + if (drained) + *drained = LRU_CACHE_DRAINED; + } + if (!drained || *drained == LRU_CACHE_DRAINED) { + if (folio_ref_count(folio) == + folio_expected_ref_count(folio) + extra_refs) + return; + lru_add_drain_all(); + if (drained) + *drained = LRU_CACHE_DRAINED_ALL; + } +} + atomic_t lru_disable_count = ATOMIC_INIT(0); /* diff --git a/mm/gup.c b/mm/gup.c index bce275c7dbb6..eb898ea1ee22 100644 --- a/mm/gup.c +++ b/mm/gup.c @@ -2266,9 +2266,9 @@ static unsigned long collect_longterm_unpinnable_folios( struct list_head *movable_folio_list, struct pages_or_folios *pofs) { + enum lru_cache_drained drained = LRU_CACHE_NOT_DRAINED; unsigned long collected = 0; struct folio *folio; - int drained = 0; long i = 0; for (folio = pofs_get_folio(pofs, i); folio; @@ -2293,18 +2293,7 @@ static unsigned long collect_longterm_unpinnable_folios( * but also to remove any other folio references from LRU * caches. */ - if (drained == 0 && folio_may_be_lru_cached(folio) && - folio_ref_count(folio) != - folio_expected_ref_count(folio) + pin_refs) { - lru_add_drain(); - drained = 1; - } - if (drained == 1 && folio_may_be_lru_cached(folio) && - folio_ref_count(folio) != - folio_expected_ref_count(folio) + pin_refs) { - lru_add_drain_all(); - drained = 2; - } + lru_cache_drain_for_folio(folio, pin_refs, &drained); if (!folio_isolate_lru(folio)) continue; diff --git a/mm/internal.h b/mm/internal.h index 07f60ca0b201..38b1165212c9 100644 --- a/mm/internal.h +++ b/mm/internal.h @@ -43,7 +43,7 @@ void workingset_activation(struct folio *folio); /* mm/folio.c */ void folio_add_lru_vma(struct folio *folio, struct vm_area_struct *vma); -static inline bool folio_may_be_lru_cached(struct folio *folio) +static inline bool folio_may_be_lru_cached(const struct folio *folio) { /* * Holding PMD-sized folios in per-CPU LRU cache unbalances accounting. From 272b0d84b17f72f6396254dbaa6264f2f74a7997 Mon Sep 17 00:00:00 2001 From: Artem Lytkin Date: Sat, 1 Aug 2026 14:49:15 +0300 Subject: [PATCH 1004/1328] mm/vmalloc: make vm_struct.nr_pages an unsigned long vm_struct::nr_pages is an unsigned int, and the file keeps deriving byte counts from it as nr_pages << PAGE_SHIFT. A shift is evaluated in the type of its promoted left operand, so those are 32-bit arithmetic and wrap at 4 GiB of bytes, which is 2^20 pages. Every site depends on a cast being remembered; vmap() has one, two recent commits did not. vread_iter() then computes a size of zero for a 4 GiB VM_ALLOC area and /proc/kcore returns it as zeros while reporting a successful read, which drgn, crash or gdb cannot tell from real memory, and the vrealloc() grow-in-place check declines a request that would have fit. Widen the field so the class of bug goes away instead of one site at a time. Everything feeding or consuming it widens too: vm_area_alloc_pages() and its accumulators, nr_small_pages, new_nr_pages and old_nr_pages, the index range of vm_area_free_pages(), and three page indexes that were plain int. Five casts go. Two prints needed fixing as well, %u in vmalloc_dump_obj() and %d for the unsigned field in vmalloc_info_show(). No bug report behind this, I found it reading the code. The 4 GiB wrap needs only a machine with over 4 GiB of memory. Neither larger threshold is a practical concern: 2^32 pages, where the field itself truncates, is 16 TiB and beyond what hardware can populate, and 2^31, where the plain int indexes break, is 8 TiB and larger than anything in the tree asks for. The int *nr cursor in the mapping path is unchanged and is separate work. Users outside mm/vmalloc.c need no change either. Those handing the count to a narrower parameter cannot drive it near 2^31, and kho_preserve_vmalloc() stores it into a 32-bit ABI field that still receives the same low bits; above 2^32 pages the truncation just moves out of vm_struct into that store. sizeof(struct vm_struct) on x86-64 stays 72 bytes with CONFIG_HAVE_ARCH_HUGE_VMALLOC=n and goes from 72 to 80 with it enabled, both inside the kmalloc-96 bucket it already comes from. Link: https://lore.kernel.org/20260801114915.115224-1-iprintercanon@gmail.com Fixes: 0bca23804632 ("mm/vmalloc: use physical page count in vread_iter() for VM_ALLOC areas") Fixes: d57ac904ffdc ("mm/vmalloc: use physical page count for vrealloc() grow-in-place check") Signed-off-by: Artem Lytkin Suggested-by: Andrew Morton Reviewed-by: Uladzislau Rezki (Sony) Assisted-by: Claude:claude-fable-5 Cc: Matthew Wilcox (Oracle) Cc: Cc: Signed-off-by: Andrew Morton --- include/linux/vmalloc.h | 2 +- mm/vmalloc.c | 58 ++++++++++++++++++++--------------------- 2 files changed, 29 insertions(+), 31 deletions(-) diff --git a/include/linux/vmalloc.h b/include/linux/vmalloc.h index e4d8d0a9f30f..aed121d729b0 100644 --- a/include/linux/vmalloc.h +++ b/include/linux/vmalloc.h @@ -62,7 +62,7 @@ struct vm_struct { #ifdef CONFIG_HAVE_ARCH_HUGE_VMALLOC unsigned int page_order; #endif - unsigned int nr_pages; + unsigned long nr_pages; phys_addr_t phys_addr; const void *caller; unsigned long requested_size; diff --git a/mm/vmalloc.c b/mm/vmalloc.c index 5c0c2d0d6ae7..72d7f0d81c05 100644 --- a/mm/vmalloc.c +++ b/mm/vmalloc.c @@ -3361,7 +3361,7 @@ struct vm_struct *remove_vm_area(const void *addr) static inline void set_area_direct_map(const struct vm_struct *area, int (*set_direct_map)(struct page *page)) { - int i; + unsigned long i; /* HUGE_VMALLOC passes small pages to set_direct_map */ for (i = 0; i < area->nr_pages; i++) @@ -3377,7 +3377,7 @@ static void vm_reset_perms(struct vm_struct *area) unsigned long start = ULONG_MAX, end = 0; unsigned int page_order = vm_area_page_order(area); int flush_dmap = 0; - int i; + unsigned long i; /* * Find the start and end range of the direct mappings to make sure that @@ -3450,10 +3450,10 @@ void vfree_atomic(const void *addr) * Caller is responsible for unmapping (vunmap_range) and KASAN * poisoning before calling this. */ -static void vm_area_free_pages(struct vm_struct *vm, unsigned int start_idx, - unsigned int end_idx) +static void vm_area_free_pages(struct vm_struct *vm, unsigned long start_idx, + unsigned long end_idx) { - unsigned int i; + unsigned long i; if (!(vm->flags & VM_MAP_PUT_PAGES)) { for (i = start_idx; i < end_idx; i++) @@ -3665,12 +3665,12 @@ static inline gfp_t vmalloc_gfp_adjust(gfp_t flags, const bool large) return flags; } -static inline unsigned int +static inline unsigned long vm_area_alloc_pages(gfp_t gfp, int nid, - unsigned int order, unsigned int nr_pages, struct page **pages) + unsigned int order, unsigned long nr_pages, struct page **pages) { - unsigned int nr_allocated = 0; - unsigned int nr_remaining = nr_pages; + unsigned long nr_allocated = 0; + unsigned long nr_remaining = nr_pages; unsigned int max_attempt_order = MAX_PAGE_ORDER; struct page *page; int i; @@ -3718,7 +3718,7 @@ vm_area_alloc_pages(gfp_t gfp, int nid, if (!order) { while (nr_allocated < nr_pages) { unsigned int nr, nr_pages_request; - int i; + unsigned long i; /* * A maximum allowed request is hard-coded and is 100 @@ -3726,7 +3726,7 @@ vm_area_alloc_pages(gfp_t gfp, int nid, * long preemption off scenario in the bulk-allocator * so the range is [1:100]. */ - nr_pages_request = min(100U, nr_pages - nr_allocated); + nr_pages_request = min(100UL, nr_pages - nr_allocated); /* memory allocation should consider mempolicy, we can't * wrongly use nearest node when nid == NUMA_NO_NODE, @@ -3872,12 +3872,12 @@ static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask, unsigned long addr = (unsigned long)area->addr; unsigned long size = get_vm_area_size(area); unsigned long array_size; - unsigned int nr_small_pages = size >> PAGE_SHIFT; + unsigned long nr_small_pages = size >> PAGE_SHIFT; unsigned int page_order; unsigned int flags; int ret; - array_size = (unsigned long)nr_small_pages * sizeof(struct page *); + array_size = nr_small_pages * sizeof(struct page *); /* __GFP_NOFAIL and "noblock" flags are mutually exclusive. */ if (!gfpflags_allow_blocking(gfp_mask)) @@ -4375,7 +4375,7 @@ void *vrealloc_node_align_noprof(const void *p, size_t size, unsigned long align } if (size <= old_size) { - unsigned int new_nr_pages = PAGE_ALIGN(size) >> PAGE_SHIFT; + unsigned long new_nr_pages = PAGE_ALIGN(size) >> PAGE_SHIFT; /* Zero out "freed" memory, potentially for future realloc. */ if (want_init_on_free() || want_init_on_alloc(flags)) @@ -4404,7 +4404,7 @@ void *vrealloc_node_align_noprof(const void *p, size_t size, unsigned long align !(vm->flags & (VM_FLUSH_RESET_PERMS | VM_USERMAP)) && gfp_has_io_fs(flags)) { unsigned long addr = (unsigned long)kasan_reset_tag(p); - unsigned int old_nr_pages = vm->nr_pages; + unsigned long old_nr_pages = vm->nr_pages; /* * Use the node lock to synchronize with concurrent @@ -4417,16 +4417,13 @@ void *vrealloc_node_align_noprof(const void *p, size_t size, unsigned long align spin_unlock(&vn->busy.lock); /* Notify kmemleak of the reduced allocation size before unmapping. */ - kmemleak_free_part( - (void *)addr + ((unsigned long)new_nr_pages - << PAGE_SHIFT), - (unsigned long)(old_nr_pages - new_nr_pages) - << PAGE_SHIFT); + kmemleak_free_part((void *)addr + + (new_nr_pages << PAGE_SHIFT), + (old_nr_pages - new_nr_pages) + << PAGE_SHIFT); - vunmap_range(addr + ((unsigned long)new_nr_pages - << PAGE_SHIFT), - addr + ((unsigned long)old_nr_pages - << PAGE_SHIFT)); + vunmap_range(addr + (new_nr_pages << PAGE_SHIFT), + addr + (old_nr_pages << PAGE_SHIFT)); vm_area_free_pages(vm, new_nr_pages, old_nr_pages); } @@ -5250,7 +5247,7 @@ bool vmalloc_dump_obj(void *object) struct vmap_area *va; struct vmap_node *vn; unsigned long addr; - unsigned int nr_pages; + unsigned long nr_pages; addr = PAGE_ALIGN((unsigned long) object); vn = addr_to_node(addr); @@ -5270,7 +5267,7 @@ bool vmalloc_dump_obj(void *object) nr_pages = vm->nr_pages; spin_unlock(&vn->busy.lock); - pr_cont(" %u-page vmalloc region starting at %#lx allocated at %pS\n", + pr_cont(" %lu-page vmalloc region starting at %#lx allocated at %pS\n", nr_pages, addr, caller); return true; @@ -5288,16 +5285,17 @@ bool vmalloc_dump_obj(void *object) static void show_numa_info(struct seq_file *m, struct vm_struct *v, unsigned int *counters) { - unsigned int nr; unsigned int step = 1U << vm_area_page_order(v); + unsigned long i; + unsigned int nr; if (!counters) return; memset(counters, 0, nr_node_ids * sizeof(unsigned int)); - for (nr = 0; nr < v->nr_pages; nr += step) - counters[page_to_nid(v->pages[nr])] += step; + for (i = 0; i < v->nr_pages; i += step) + counters[page_to_nid(v->pages[i])] += step; for_each_node_state(nr, N_HIGH_MEMORY) if (counters[nr]) seq_printf(m, " N%u=%u", nr, counters[nr]); @@ -5355,7 +5353,7 @@ static int vmalloc_info_show(struct seq_file *m, void *p) seq_printf(m, " %pS", v->caller); if (v->nr_pages) - seq_printf(m, " pages=%d", v->nr_pages); + seq_printf(m, " pages=%lu", v->nr_pages); if (v->phys_addr) seq_printf(m, " phys=%pa", &v->phys_addr); From c310a8932a3107c9bc8f01d473e9d085f8aa9c98 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Mon, 3 Aug 2026 11:04:26 -0700 Subject: [PATCH 1005/1328] mm/swap: reject swapon() on filesystem-level encrypted files ext4 and f2fs don't prevent filesystem-level encrypted files from being set up directly as swap files. In this case, encryption is bypassed. No one should be doing this, vs. the methods of encrypted swap that actually do work (such as swapping to a dm-crypt device, or swapping to a loopback device on top of a filesystem-level encrypted file). Nevertheless, to prevent user error, make swapon() explicitly reject this case. Document this behavior in fscrypt.rst as well. Link: https://lore.kernel.org/20260803180426.3123-1-ebiggers@kernel.org Fixes: 9bd8212f981e ("ext4 crypto: add encryption policy and password salt support") Fixes: f424f664f0e8 ("f2fs crypto: add encryption policy and password salt support") Signed-off-by: Eric Biggers Reviewed-by: Baoquan He Reviewed-by: Muhammad Usama Anjum Reviewed-by: "Darrick J. Wong" Cc: Barry Song Cc: Chris Li Cc: Kairui Song Cc: Kemeng Shi Cc: Nhat Pham Cc: Signed-off-by: Andrew Morton --- Documentation/filesystems/fscrypt.rst | 4 ++++ mm/swapfile.c | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/Documentation/filesystems/fscrypt.rst b/Documentation/filesystems/fscrypt.rst index c0dd35f1af12..e4882b73120e 100644 --- a/Documentation/filesystems/fscrypt.rst +++ b/Documentation/filesystems/fscrypt.rst @@ -1238,6 +1238,10 @@ astute users may notice some differences in behavior: - DAX (Direct Access) is not supported on encrypted files. +- Encrypted files cannot be used directly as swap files. To swap to + an encrypted file, set up a loopback device on top of it. + Alternatively, encrypted swap can use a dm-crypt device. + - The maximum length of an encrypted symlink is 2 bytes shorter than the maximum length of an unencrypted symlink. For example, on an EXT4 filesystem with a 4K block size, unencrypted symlinks can be up diff --git a/mm/swapfile.c b/mm/swapfile.c index 4e07d457e261..d7f749ad60c2 100644 --- a/mm/swapfile.c +++ b/mm/swapfile.c @@ -3668,6 +3668,13 @@ SYSCALL_DEFINE2(swapon, const char __user *, specialfile, int, swap_flags) error = -EBUSY; goto bad_swap_unlock_inode; } + if (IS_ENCRYPTED(inode)) { + pr_warn_once( + "Filesystem-level encrypted swapfile '%s' is unsupported. Create a loop device over it, or use dm-crypt\n", + name->name); + error = -EINVAL; + goto bad_swap_unlock_inode; + } /* * The swap subsystem needs a major overhaul to support this. From 8282cb36d021835e6574c393df6a4aed1bf6ada3 Mon Sep 17 00:00:00 2001 From: Jianlin Shi Date: Thu, 6 Aug 2026 16:27:34 +0800 Subject: [PATCH 1006/1328] mm/page_alloc: only update lowmem_reserve_ratio on sysctl write lowmem_reserve_ratio_sysctl_handler() ignores the return value of proc_dointvec_minmax() and always calls setup_per_zone_lowmem_reserve(), even for read operations. Fix three issues: 1. Propagate errors from proc_dointvec_minmax() instead of always returning success. For example, writing non-integer garbage to the sysctl now returns an error instead of silently succeeding with unchanged values. 2. Only call setup_per_zone_lowmem_reserve() when the sysctl is actually written, matching the write-only refresh pattern of min_free_kbytes and watermark_scale_factor handlers. 3. On write, parse into a temporary ratio[] array and only copy into sysctl_lowmem_reserve_ratio[] and refresh derived state after the full vector is validated. This avoids leaving the ratio array partially updated while skipping setup when proc_dointvec_minmax() returns an error on a later element (suggested by Andrew Morton). Drop the manual "< 1 -> 0" sanitization loop and set .extra1 = SYSCTL_ZERO on the ctl_table entry so proc_dointvec_minmax() enforces the minimum on write; negative values now return -EINVAL instead of being silently coerced to 0 (suggested by Vlastimil Babka). [akpm@linux-foundation.org: add comment, per hannes] Link: https://lore.kernel.org/anSRGASe5FIrqwlg@cmpxchg.org Link: https://lore.kernel.org/linux-mm/tencent_FFD4F4D728AAE8A8AE0AF277A59854A29A06@qq.com/ Link: https://lore.kernel.org/tencent_A860C873956A52E26AD8D309A308A241BA08@qq.com Signed-off-by: Jianlin Shi Acked-by: Vlastimil Babka (SUSE) Cc: Brendan Jackman Cc: Johannes Weiner Cc: Michal Hocko Cc: Suren Baghdasaryan Cc: Zi Yan Cc: Joel Granados Signed-off-by: Andrew Morton --- mm/page_alloc.c | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/mm/page_alloc.c b/mm/page_alloc.c index 083cbcb5bdde..12fac9084c48 100644 --- a/mm/page_alloc.c +++ b/mm/page_alloc.c @@ -6853,8 +6853,8 @@ static int sysctl_min_slab_ratio_sysctl_handler(const struct ctl_table *table, i /* * lowmem_reserve_ratio_sysctl_handler - just a wrapper around - * proc_dointvec() so that we can call setup_per_zone_lowmem_reserve() - * whenever sysctl_lowmem_reserve_ratio changes. + * proc_dointvec_minmax() so that we can call + * setup_per_zone_lowmem_reserve() when the sysctl is written. * * The reserve ratio obviously has absolutely no relation with the * minimum watermarks. The lowmem reserve ratio can only make sense @@ -6863,16 +6863,27 @@ static int sysctl_min_slab_ratio_sysctl_handler(const struct ctl_table *table, i static int lowmem_reserve_ratio_sysctl_handler(const struct ctl_table *table, int write, void *buffer, size_t *length, loff_t *ppos) { - int i; + struct ctl_table tmp = *table; + int ratio[ARRAY_SIZE(sysctl_lowmem_reserve_ratio)]; + int rc; - proc_dointvec_minmax(table, write, buffer, length, ppos); + if (!write) + return proc_dointvec_minmax(table, write, buffer, length, ppos); - for (i = 0; i < MAX_NR_ZONES; i++) { - if (sysctl_lowmem_reserve_ratio[i] < 1) - sysctl_lowmem_reserve_ratio[i] = 0; - } + /* + * proc_dointvec_max() works incrementally. Use a buffer and only set + * the values if all of them parse cleanly. + */ + memcpy(ratio, sysctl_lowmem_reserve_ratio, sizeof(ratio)); + tmp.data = ratio; + rc = proc_dointvec_minmax(&tmp, write, buffer, length, ppos); + if (rc) + return rc; + + memcpy(sysctl_lowmem_reserve_ratio, ratio, sizeof(ratio)); setup_per_zone_lowmem_reserve(); + return 0; } @@ -6971,6 +6982,7 @@ static const struct ctl_table page_alloc_sysctl_table[] = { .maxlen = sizeof(sysctl_lowmem_reserve_ratio), .mode = 0644, .proc_handler = lowmem_reserve_ratio_sysctl_handler, + .extra1 = SYSCTL_ZERO, }, #ifdef CONFIG_NUMA { From 32c9625638c2fda23b9301be5184b8abf84c92a7 Mon Sep 17 00:00:00 2001 From: Daniel Palmer Date: Sat, 23 May 2026 22:04:45 +0900 Subject: [PATCH 1007/1328] tmpfs/ramfs: let memfd_create() work on nommu Currently trying to use memfd_create() on nommu returns an error with errno set to EFBIG. The manpage memfd_create() doesn't have EFBIG as a possible error value. Doing some digging this is coming from 0 getting passed as newsize to ramfs_nommu_expand_for_mapping() and that getting into get_order() and there "The result is undefined if the size is 0". Whatever comes out of get_order() is then used in the following logic and that results in the EFBIG that causes the syscall to fail and the errno in userspace. If newsize is 0 there is nothing to do so just return. Roughly tested on m68k nommu by creating a process, creating an memfd, forking another process, mmap()ing the memfd in the child, writing into the mapping, then mmap()ing in the parent and checking that the right data is there. Link: https://lore.kernel.org/20260523130445.1101818-1-daniel@thingy.jp Signed-off-by: Daniel Palmer Acked-by: Lorenzo Stoakes Cc: "Liam R. Howlett" Cc: Al Viro Cc: Christian Brauner Signed-off-by: Andrew Morton --- fs/ramfs/file-nommu.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fs/ramfs/file-nommu.c b/fs/ramfs/file-nommu.c index 2f79bcb89d2e..fb471bf88ab7 100644 --- a/fs/ramfs/file-nommu.c +++ b/fs/ramfs/file-nommu.c @@ -69,6 +69,9 @@ int ramfs_nommu_expand_for_mapping(struct inode *inode, size_t newsize) gfp_t gfp = mapping_gfp_mask(inode->i_mapping); /* make various checks */ + if (!newsize) + return 0; + order = get_order(newsize); if (unlikely(order > MAX_PAGE_ORDER)) return -EFBIG; From 4e7e499b750f0ba1806a9f60ad091364c3382ad1 Mon Sep 17 00:00:00 2001 From: Pratyush Mallick Date: Mon, 3 Aug 2026 22:17:31 +0000 Subject: [PATCH 1008/1328] selftests/mm: rename local_config.h to local_config.h_gen Patch series "selftests/mm: use pattern matching in .gitignore", v4. The current selftests/mm/.gitignore hardcodes each generated test binary by name, which requires manual updates every time a new test is added. This series switches to a pattern-matching approach (similar to KVM selftests), ignoring everything by default and allowing specific source extensions. To accommodate this without tracking generated headers, local_config.h is renamed to local_config.h_gen. This patch (of 2): Because local_config.h is a generated build artifact, un-ignoring all .h files in .gitignore causes it to incorrectly show up as an untracked file in git status. Rename it to local_config.h_gen so it no longer matches the !*.h inclusion rule, preparing for a subsequent patch that switches .gitignore to a pattern-matching approach. Update Makefile, check_config.sh, and affected test sources (cow.c, gup_longterm.c) accordingly. Link: https://lore.kernel.org/20260803221732.3651981-1-pratmal@google.com Link: https://lore.kernel.org/20260803221732.3651981-2-pratmal@google.com Signed-off-by: Pratyush Mallick Reviewed-by: Lorenzo Stoakes Acked-by: Mike Rapoport (Microsoft) Suggested-by: David Hildenbrand Cc: Jason Gunthorpe Cc: John Hubbard Cc: "Liam R. Howlett" Cc: Michal Hocko Cc: Peter Xu Cc: SeongJae Park Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Yosry Ahmed Signed-off-by: Andrew Morton --- tools/testing/selftests/mm/Makefile | 6 +++--- tools/testing/selftests/mm/check_config.sh | 2 +- tools/testing/selftests/mm/cow.c | 2 +- tools/testing/selftests/mm/gup_longterm.c | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile index 277a141d662e..0f31d850707d 100644 --- a/tools/testing/selftests/mm/Makefile +++ b/tools/testing/selftests/mm/Makefile @@ -5,7 +5,7 @@ # script so kunit knows to run it, and add it to the list below. # If you do not YOUR TESTS WILL NOT RUN IN THE CI. -LOCAL_HDRS += $(selfdir)/mm/local_config.h $(top_srcdir)/mm/gup_test.h +LOCAL_HDRS += $(selfdir)/mm/local_config.h_gen $(top_srcdir)/mm/gup_test.h LOCAL_HDRS += $(selfdir)/mm/mseal_helpers.h include local_config.mk @@ -261,11 +261,11 @@ $(OUTPUT)/migration: LDLIBS += -lnuma $(OUTPUT)/rmap: LDLIBS += -lnuma -local_config.mk local_config.h: check_config.sh +local_config.mk local_config.h_gen: check_config.sh $(call msg,CHK,config,$@) $(Q)CC="$(CC)" CFLAGS="$(CFLAGS)" ./check_config.sh -EXTRA_CLEAN += local_config.mk local_config.h +EXTRA_CLEAN += local_config.mk local_config.h_gen ifeq ($(IOURING_EXTRA_LIBS),) all: warn_missing_liburing diff --git a/tools/testing/selftests/mm/check_config.sh b/tools/testing/selftests/mm/check_config.sh index 32beaefe279e..1c603261e93d 100755 --- a/tools/testing/selftests/mm/check_config.sh +++ b/tools/testing/selftests/mm/check_config.sh @@ -4,7 +4,7 @@ # Probe for libraries and create header files to record the results. Both C # header files and Makefile include fragments are created. -OUTPUT_H_FILE=local_config.h +OUTPUT_H_FILE=local_config.h_gen OUTPUT_MKFILE=local_config.mk tmpname=$(mktemp) diff --git a/tools/testing/selftests/mm/cow.c b/tools/testing/selftests/mm/cow.c index 0c627ea89ff7..7fa2d97ca9b2 100644 --- a/tools/testing/selftests/mm/cow.c +++ b/tools/testing/selftests/mm/cow.c @@ -21,7 +21,7 @@ #include #include -#include "local_config.h" +#include "local_config.h_gen" #ifdef LOCAL_CONFIG_HAVE_LIBURING #include #endif /* LOCAL_CONFIG_HAVE_LIBURING */ diff --git a/tools/testing/selftests/mm/gup_longterm.c b/tools/testing/selftests/mm/gup_longterm.c index c03b4f8910c0..510de93be681 100644 --- a/tools/testing/selftests/mm/gup_longterm.c +++ b/tools/testing/selftests/mm/gup_longterm.c @@ -21,7 +21,7 @@ #include #include -#include "local_config.h" +#include "local_config.h_gen" #ifdef LOCAL_CONFIG_HAVE_LIBURING #include #endif /* LOCAL_CONFIG_HAVE_LIBURING */ From 8bc69b8d209f2c08b8a311e63b910b8a718eee79 Mon Sep 17 00:00:00 2001 From: Warren Xiong Date: Tue, 4 Aug 2026 20:16:58 +0800 Subject: [PATCH 1009/1328] selftests/mm: read memory information without popen read_memory_info() invokes two shell pipelines to obtain MemFree and Hugepagesize from /proc/meminfo. It does not check whether popen() returns NULL before passing the result to fgets(), and it does not call pclose() when fgets() fails. Open /proc/meminfo directly and obtain both values in a single pass. This removes the unchecked NULL path, closes the file on all paths, and avoids dependencies on external commands. The compaction test continues to pass after this change. Link: https://lore.kernel.org/1785845818-3131-1-git-send-email-warren.xiong@ugreen.com Signed-off-by: Warren Xiong Acked-by: David Hildenbrand (Arm) Acked-by: Mike Rapoport (Microsoft) Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- tools/testing/selftests/mm/compaction_test.c | 38 +++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/tools/testing/selftests/mm/compaction_test.c b/tools/testing/selftests/mm/compaction_test.c index 5b582588e015..30d4ace7155a 100644 --- a/tools/testing/selftests/mm/compaction_test.c +++ b/tools/testing/selftests/mm/compaction_test.c @@ -29,30 +29,34 @@ struct map_list { int read_memory_info(unsigned long *memfree, unsigned long *hugepagesize) { - char buffer[256] = {0}; - char *cmd = "cat /proc/meminfo | grep -i memfree | grep -o '[0-9]*'"; - FILE *cmdfile = popen(cmd, "r"); + char buffer[256]; + int found = 0; + FILE *file; + int ret = -1; - if (!(fgets(buffer, sizeof(buffer), cmdfile))) { - ksft_print_msg("Failed to read meminfo: %s\n", strerror(errno)); + file = fopen("/proc/meminfo", "r"); + if (!file) { + ksft_print_msg("Failed to open /proc/meminfo: %s\n", + strerror(errno)); return -1; } - pclose(cmdfile); - - *memfree = atoll(buffer); - cmd = "cat /proc/meminfo | grep -i hugepagesize | grep -o '[0-9]*'"; - cmdfile = popen(cmd, "r"); - - if (!(fgets(buffer, sizeof(buffer), cmdfile))) { - ksft_print_msg("Failed to read meminfo: %s\n", strerror(errno)); - return -1; + while (fgets(buffer, sizeof(buffer), file) && found != 2) { + if (sscanf(buffer, "MemFree: %lu kB", memfree) == 1 || + sscanf(buffer, "Hugepagesize: %lu kB", hugepagesize) == 1) + found++; } - pclose(cmdfile); - *hugepagesize = atoll(buffer); + if (ferror(file)) + ksft_print_msg("Failed to read /proc/meminfo: %s\n", + strerror(errno)); + else if (found != 2) + ksft_print_msg("Failed to parse /proc/meminfo\n"); + else + ret = 0; - return 0; + fclose(file); + return ret; } int prereq(void) From 2bee308f3adbd09aa7f6b01fd2271de36538973c Mon Sep 17 00:00:00 2001 From: Pratyush Mallick Date: Mon, 3 Aug 2026 22:17:32 +0000 Subject: [PATCH 1010/1328] selftests/mm: use pattern matching in .gitignore The current .gitignore hardcodes each generated test binary by name, requiring updates every time a new test is added. Switch to the pattern-matching approach similar to KVM:selftests. Ignore everything by default and then allow source extensions (.c, .h, .sh) and tracked non-source files. Note that local_config.h was renamed to local_config.h_gen in a previous patch so that un-ignoring *.h files does not cause generated build artifacts to become untracked. [akpm@linux-foundation.org: fix botched merge resolution] Link: https://lore.kernel.org/20260803221732.3651981-3-pratmal@google.com Signed-off-by: Pratyush Mallick Reviewed-by: Lorenzo Stoakes Acked-by: Mike Rapoport (Microsoft) Suggested-by: Yosry Ahmed Reviewed-by: SJ Park Acked-by: David Hildenbrand (Arm) Cc: Jason Gunthorpe Cc: John Hubbard Cc: "Liam R. Howlett" Cc: Michal Hocko Cc: Peter Xu Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- tools/testing/selftests/mm/.gitignore | 76 ++++----------------------- 1 file changed, 9 insertions(+), 67 deletions(-) diff --git a/tools/testing/selftests/mm/.gitignore b/tools/testing/selftests/mm/.gitignore index 9ccd9e1447e6..fcd892ed21e3 100644 --- a/tools/testing/selftests/mm/.gitignore +++ b/tools/testing/selftests/mm/.gitignore @@ -1,68 +1,10 @@ # SPDX-License-Identifier: GPL-2.0-only -cow -hugepage-mmap -hugepage-mremap -hugepage-shm -hugepage-vmemmap -hugetlb-mmap -hugetlb-mremap -hugetlb-shm -hugetlb-vmemmap -hugetlb-madvise -hugetlb-read-hwpoison -hugetlb-soft-offline -khugepaged -map_hugetlb -map_populate -thuge-gen -compaction_test -memory-failure -migration -mlock2-tests -mrelease_test -mremap_dontunmap -mremap_test -on-fault-limit -transhuge-stress -pagemap_ioctl -pfnmap -process_madv -*.tmp* -protection_keys -protection_keys_32 -protection_keys_64 -madv_populate -uffd-stress -uffd-unit-tests -uffd-wp-mremap -mlock-intersect-test -mlock-random-test -virtual_address_range -gup_test -va_128TBswitch -map_fixed_noreplace -write_to_hugetlbfs -hmm-tests -memfd_secret -soft-dirty -split_huge_page_test -ksm_tests -local_config.h -local_config.mk -ksm_functional_tests -mdwe_test -gup_longterm -mkdirty -va_high_addr_switch -hugetlb_fault_after_madv -hugetlb_madv_vs_map -mseal_test -droppable -hugetlb_dio -pkey_sighandler_tests_32 -pkey_sighandler_tests_64 -guard-regions -merge -prctl_thp_disable -rmap -folio_split_race_test +* +!/**/ +!*.c +!*.h +!*.sh +!.gitignore +!Makefile +!config +!settings From 3c37cac718fa407b6656d925c4437ed5410fcdda Mon Sep 17 00:00:00 2001 From: Longlong Xia Date: Wed, 5 Aug 2026 21:27:34 +0800 Subject: [PATCH 1011/1328] mm/ksm: avoid missing ksmd wakeups in ksm_enter __ksm_enter() decides whether ksmd needs a wakeup by checking if the mm slot list is empty before inserting the new slot. The empty check is currently outside ksm_mmlist_lock. Another CPU can remove the last slot and let ksmd go back to sleep after the unlocked check, while this CPU inserts a new slot and skips the wakeup based on the stale result. Take ksm_mmlist_lock before checking the list so the empty-to-nonempty transition and the insertion are observed as one critical section. Link: https://lore.kernel.org/20260805132736.1063408-1-xialonglong2025@163.com Fixes: 6e15838425ac ("ksm: keep quiet while list empty") Signed-off-by: Longlong Xia Acked-by: David Hildenbrand (Arm) Reviewed-by: Andrew Morton Cc: Chengming Zhou Cc: Izik Eidus Cc: xu xin Signed-off-by: Andrew Morton --- mm/ksm.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mm/ksm.c b/mm/ksm.c index b5854dc14a2e..14dd6a6e8e6d 100644 --- a/mm/ksm.c +++ b/mm/ksm.c @@ -3060,10 +3060,9 @@ int __ksm_enter(struct mm_struct *mm) slot = &mm_slot->slot; + spin_lock(&ksm_mmlist_lock); /* Check ksm_run too? Would need tighter locking */ needs_wakeup = list_empty(&ksm_mm_head.slot.mm_node); - - spin_lock(&ksm_mmlist_lock); mm_slot_insert(mm_slots_hash, mm, slot); /* * When KSM_RUN_MERGE (or KSM_RUN_STOP), From a44ab4bd1ec0432d686c25f9c160379ffa1e694c Mon Sep 17 00:00:00 2001 From: Hongfu Li Date: Wed, 5 Aug 2026 18:59:07 +0800 Subject: [PATCH 1012/1328] ksm: update comments and docs to reference folio->mapping The KSM code already stores and checks the stable node key via folio->mapping, but the comment in ksm_get_folio() and the reverse mapping documentation in ksm.rst still refer to page->mapping. This is a pure wording update to match the folio-based implementation. No functional change is intended. Link: https://lore.kernel.org/20260805105927.41987-1-hongfu.li@linux.dev Signed-off-by: Hongfu Li Acked-by: David Hildenbrand (Arm) Reviewed-by: Xu Xin Reviewed-by: Dongliang Mu Cc: Alex Shi Cc: Chengming Zhou Cc: David Hildenbrand Cc: Jonathan Corbet Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Yanteng Si Signed-off-by: Andrew Morton --- Documentation/mm/ksm.rst | 4 ++-- Documentation/translations/zh_CN/mm/ksm.rst | 4 ++-- mm/ksm.c | 7 +++---- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Documentation/mm/ksm.rst b/Documentation/mm/ksm.rst index 2806e3e4a10e..2b4f72f1f953 100644 --- a/Documentation/mm/ksm.rst +++ b/Documentation/mm/ksm.rst @@ -24,13 +24,13 @@ tree. If a KSM page is shared between less than ``max_page_sharing`` VMAs, the node of the stable tree that represents such KSM page points to a -list of struct ksm_rmap_item and the ``page->mapping`` of the +list of struct ksm_rmap_item and the ``folio->mapping`` of the KSM page points to the stable tree node. When the sharing passes this threshold, KSM adds a second dimension to the stable tree. The tree node becomes a "chain" that links one or more "dups". Each "dup" keeps reverse mapping information for a KSM -page with ``page->mapping`` pointing to that "dup". +page with ``folio->mapping`` pointing to that "dup". Every "chain" and all "dups" linked into a "chain" enforce the invariant that they represent the same write protected memory content, diff --git a/Documentation/translations/zh_CN/mm/ksm.rst b/Documentation/translations/zh_CN/mm/ksm.rst index f0f458753d0c..822c7a289671 100644 --- a/Documentation/translations/zh_CN/mm/ksm.rst +++ b/Documentation/translations/zh_CN/mm/ksm.rst @@ -31,10 +31,10 @@ KSM维护着稳定树中的KSM页的逆映射信息。 当KSM页面的共享数小于 ``max_page_sharing`` 的虚拟内存区域(VMAs)时,则代表了 KSM页的稳定树其中的节点指向了一个ksm_rmap_item结构体类型的列表。同时,这个KSM页 -的 ``page->mapping`` 指向了该稳定树节点。 +的 ``folio->mapping`` 指向了该稳定树节点。 如果共享数超过了阈值,KSM将给稳定树添加第二个维度。稳定树就变成链接一个或多 -个稳定树"副本"的"链"。每个副本都保留KSM页的逆映射信息,其中 ``page->mapping`` +个稳定树"副本"的"链"。每个副本都保留KSM页的逆映射信息,其中 ``folio->mapping`` 指向该"副本"。 每个链以及链接到该链中的所有"副本"强制不变的是,它们代表了相同的写保护内存 diff --git a/mm/ksm.c b/mm/ksm.c index 14dd6a6e8e6d..49d48d1e0998 100644 --- a/mm/ksm.c +++ b/mm/ksm.c @@ -959,10 +959,9 @@ enum ksm_get_folio_flags { * seconds or even minutes: much too unresponsive. So instead we use a * "keyhole reference": access to the ksm page from the stable node peeps * out through its keyhole to see if that page still holds the right key, - * pointing back to this stable node. This relies on freeing a PageAnon - * page to reset its page->mapping to NULL, and relies on no other use of - * a page to put something that might look like our key in page->mapping. - * is on its way to being freed; but it is an anomaly to bear in mind. + * pointing back to this stable node. This relies on freeing an anon + * folio to reset its mapping to NULL, and relies on no other use of a + * folio to put something that might look like our key in its mapping. */ static struct folio *ksm_get_folio(struct ksm_stable_node *stable_node, enum ksm_get_folio_flags flags) From b9183788a2def7b26785eccc8c23dba2bbf9e5b1 Mon Sep 17 00:00:00 2001 From: "Uladzislau Rezki (Sony)" Date: Sun, 2 Aug 2026 12:46:27 +0200 Subject: [PATCH 1013/1328] mm/vmalloc: do not warn on -ENOMEM from va_alloc() Since vmalloc() accepts non-blocking GFP flags, allocation requests may fail when callers pass restrictive GFP masks. va_clip() may return -ENOMEM when its GFP_NOWAIT fallback allocation fails during NE_FIT_TYPE splitting. This is an expected failure, so va_alloc() should return the error without triggering a kernel splat. Link: https://lore.kernel.org/20260802104627.63892-1-urezki@gmail.com Signed-off-by: Uladzislau Rezki (Sony) Reported-by: syzbot+61c997e6be1d9bb300ba@syzkaller.appspotmail.com Closes: https://lore.kernel.org/6a6d3cbd.6ce73036.24301b.000e.GAE@google.com Reviewed-by: Anshuman Khandual Reviewed-by: Baoquan He Signed-off-by: Andrew Morton --- mm/vmalloc.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/mm/vmalloc.c b/mm/vmalloc.c index 72d7f0d81c05..bea9f76ed7e7 100644 --- a/mm/vmalloc.c +++ b/mm/vmalloc.c @@ -1840,8 +1840,10 @@ va_alloc(struct vmap_area *va, /* Update the free vmap_area. */ ret = va_clip(root, head, va, nva_start_addr, size); - if (WARN_ON_ONCE(ret)) + if (ret) { + WARN_ON_ONCE(ret != -ENOMEM); return ret; + } return nva_start_addr; } @@ -1914,12 +1916,9 @@ preload_this_cpu_lock(spinlock_t *lock, gfp_t gfp_mask, int node) /* * Preload this CPU with one extra vmap_area object. It is used - * when fit type of free area is NE_FIT_TYPE. It guarantees that - * a CPU that does an allocation is preloaded. - * - * We do it in non-atomic context, thus it allows us to use more - * permissive allocation masks to be more stable under low memory - * condition and high memory pressure. + * when fit type of free area is NE_FIT_TYPE. It is best effort + * pre-loading. If it fails va_clip() may return -ENOMEM from its + * GFP_NOWAIT fallback. */ if (!this_cpu_read(ne_fit_preload_node)) va = kmem_cache_alloc_node(vmap_area_cachep, gfp_mask, node); From 6fd3e592c09dd19d5ba47e45dc5f38837629d1ba Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Tue, 4 Aug 2026 11:08:59 +0100 Subject: [PATCH 1014/1328] mm: add some missing includes to mm-local headers There are a number of internal headers local to mm/ which reference functions and data types without including the relevant headers. mm/vma.h is a special case that intentionally does not include additional headers, but the others are not. This breaks tooling like clangd (which is where I noticed this), though the build is OK due to the C files including the headers happening to include required dependencies. It's better to be explicit about dependencies anyway, so add the missing includes and fix clangd as a bonus. Link: https://lore.kernel.org/20260804-fix-some-local-headers-v1-1-a7beb173c116@kernel.org Signed-off-by: Lorenzo Stoakes (ARM) Acked-by: Vlastimil Babka (SUSE) Acked-by: David Hildenbrand (Arm) Acked-by: Zi Yan Reviewed-by: Barry Song Reviewed-by: Baoquan He Cc: Chris Li Cc: Johannes Weiner Cc: Kairui Song Cc: Kemeng Shi Cc: Liam R. Howlett Cc: Michal Hocko Cc: Mike Rapoport Cc: Muchun Song Cc: Nhat Pham Cc: Oscar Salvador Cc: Roman Gushchin Cc: Shakeel Butt Cc: Suren Baghdasaryan Cc: "Uladzislau Rezki (Sony)" Signed-off-by: Andrew Morton --- mm/cma.h | 1 + mm/hugetlb_cma.h | 2 ++ mm/memcontrol-v1.h | 1 + mm/pgalloc-track.h | 3 +++ mm/shuffle.h | 2 ++ mm/swap.h | 1 + mm/vmalloc.h | 2 ++ 7 files changed, 12 insertions(+) diff --git a/mm/cma.h b/mm/cma.h index c70180c36559..ab6d39898ea5 100644 --- a/mm/cma.h +++ b/mm/cma.h @@ -2,6 +2,7 @@ #ifndef __MM_CMA_H__ #define __MM_CMA_H__ +#include #include #include diff --git a/mm/hugetlb_cma.h b/mm/hugetlb_cma.h index 3aa483573d17..730b2b4965b6 100644 --- a/mm/hugetlb_cma.h +++ b/mm/hugetlb_cma.h @@ -2,6 +2,8 @@ #ifndef _LINUX_HUGETLB_CMA_H #define _LINUX_HUGETLB_CMA_H +#include + #ifdef CONFIG_CMA void hugetlb_cma_free_frozen_folio(struct folio *folio); struct folio *hugetlb_cma_alloc_frozen_folio(int order, gfp_t gfp_mask, diff --git a/mm/memcontrol-v1.h b/mm/memcontrol-v1.h index 0f703f239c80..1e394269c613 100644 --- a/mm/memcontrol-v1.h +++ b/mm/memcontrol-v1.h @@ -4,6 +4,7 @@ #define __MM_MEMCONTROL_V1_H #include +#include /* Cgroup v1 and v2 common declarations */ diff --git a/mm/pgalloc-track.h b/mm/pgalloc-track.h index e9e879de8649..1a6de1358a21 100644 --- a/mm/pgalloc-track.h +++ b/mm/pgalloc-track.h @@ -2,6 +2,9 @@ #ifndef _LINUX_PGALLOC_TRACK_H #define _LINUX_PGALLOC_TRACK_H +#include +#include + #if defined(CONFIG_MMU) static inline p4d_t *p4d_alloc_track(struct mm_struct *mm, pgd_t *pgd, unsigned long address, diff --git a/mm/shuffle.h b/mm/shuffle.h index 61bbcddeeee6..11bec7521ab8 100644 --- a/mm/shuffle.h +++ b/mm/shuffle.h @@ -2,7 +2,9 @@ // Copyright(c) 2018 Intel Corporation. All rights reserved. #ifndef _MM_SHUFFLE_H #define _MM_SHUFFLE_H + #include +#include #define SHUFFLE_ORDER MAX_PAGE_ORDER diff --git a/mm/swap.h b/mm/swap.h index 2ccf8cf7f6c1..4e4c291bbfde 100644 --- a/mm/swap.h +++ b/mm/swap.h @@ -5,6 +5,7 @@ #include /* for atomic_long_t */ #include /* for PAGE_SHIFT */ #include /* for mem_cgroup_swappiness() */ +#include /* for MAX_SWAPFILES_SHIFT, struct swap_info_struct */ struct mempolicy; struct swap_iocb; diff --git a/mm/vmalloc.h b/mm/vmalloc.h index dcfe30eaa80c..8866ddcff668 100644 --- a/mm/vmalloc.h +++ b/mm/vmalloc.h @@ -5,6 +5,8 @@ #ifndef __MM_VMALLOC_H #define __MM_VMALLOC_H +#include + #ifdef CONFIG_MMU void __init vmalloc_init(void); int __must_check vmap_pages_range_noflush(unsigned long addr, unsigned long end, From 34568000f3c9aa3a14073f20857d79983345a7df Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Wed, 5 Aug 2026 00:11:41 +0900 Subject: [PATCH 1015/1328] mm/Kconfig: make FLATMEM depend on !NUMA Patch series "mm/page_ext: remove pgdat_page_ext_init()", v2. pgdat_page_ext_init() has no effect on FLATMEM. The pgdat is always the zero-initialized contig_page_data, because no architecture supports FLATMEM + NUMA. That constraint is only implicit in the arch Kconfig files. So patch 1 makes it explicit in mm/Kconfig, and patch 2 removes pgdat_page_ext_init(). No functional change. This patch (of 2): FLATMEM + NUMA is not supported by any architecture and fails to build. The constraint is only implicit in the arch Kconfig files. So make it explicit in mm/Kconfig. No functional change. Link: https://lore.kernel.org/20260804151145.3419768-1-ekffu200098@gmail.com Link: https://lore.kernel.org/20260804151145.3419768-2-ekffu200098@gmail.com Signed-off-by: Sang-Heon Jeon Suggested-by: Zi Yan Acked-by: Zi Yan Acked-by: David Hildenbrand (Arm) Acked-by: Mike Rapoport (Microsoft) Cc: Johannes Weiner Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/Kconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mm/Kconfig b/mm/Kconfig index 060190e12bce..331daf7fcfab 100644 --- a/mm/Kconfig +++ b/mm/Kconfig @@ -390,6 +390,7 @@ choice config FLATMEM_MANUAL bool "Flat Memory" + depends on !NUMA depends on !ARCH_SPARSEMEM_ENABLE || ARCH_FLATMEM_ENABLE help This option is best suited for non-NUMA systems with @@ -424,6 +425,7 @@ config SPARSEMEM config FLATMEM def_bool y + depends on !NUMA depends on !SPARSEMEM || FLATMEM_MANUAL # From 0ddb8bb85b98ff59f4643b7e4500e45f650dddcf Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Wed, 5 Aug 2026 00:11:42 +0900 Subject: [PATCH 1016/1328] mm/page_ext: remove pgdat_page_ext_init() pgdat_page_ext_init() sets pgdat->node_page_ext to NULL only on FLATMEM. FLATMEM depends on !NUMA, so the pgdat is always the zero-initialized contig_page_data and the store has no effect. So remove the call site, the unused function and its declaration. No functional change. Link: https://lore.kernel.org/20260804151145.3419768-3-ekffu200098@gmail.com Signed-off-by: Sang-Heon Jeon Acked-by: Zi Yan Acked-by: David Hildenbrand (Arm) Cc: Johannes Weiner Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- include/linux/page_ext.h | 5 ----- mm/mm_init.c | 1 - mm/page_ext.c | 9 --------- 3 files changed, 15 deletions(-) diff --git a/include/linux/page_ext.h b/include/linux/page_ext.h index f23d4b218da0..79c53ec45dfa 100644 --- a/include/linux/page_ext.h +++ b/include/linux/page_ext.h @@ -55,7 +55,6 @@ struct page_ext { extern bool early_page_ext; extern unsigned long page_ext_size; -extern void pgdat_page_ext_init(struct pglist_data *pgdat); static inline bool early_page_ext_enabled(void) { @@ -202,10 +201,6 @@ static inline bool early_page_ext_enabled(void) return false; } -static inline void pgdat_page_ext_init(struct pglist_data *pgdat) -{ -} - static inline void page_ext_init(void) { } diff --git a/mm/mm_init.c b/mm/mm_init.c index 711f821f7b3c..e9c4204b73ad 100644 --- a/mm/mm_init.c +++ b/mm/mm_init.c @@ -1394,7 +1394,6 @@ static void __meminit pgdat_init_internals(struct pglist_data *pgdat) for (i = 0; i < NR_VMSCAN_THROTTLE; i++) init_waitqueue_head(&pgdat->reclaim_wait[i]); - pgdat_page_ext_init(pgdat); lruvec_init(&pgdat->__lruvec); } diff --git a/mm/page_ext.c b/mm/page_ext.c index e2e92bd27ebd..b679a8c1f7d7 100644 --- a/mm/page_ext.c +++ b/mm/page_ext.c @@ -164,11 +164,6 @@ void __init page_ext_init_flatmem_late(void) invoke_init_callbacks(); } -void __meminit pgdat_page_ext_init(struct pglist_data *pgdat) -{ - pgdat->node_page_ext = NULL; -} - static struct page_ext *lookup_page_ext(const struct page *page) { unsigned long pfn = page_to_pfn(page); @@ -494,10 +489,6 @@ void __init page_ext_init(void) panic("Out of memory"); } -void __meminit pgdat_page_ext_init(struct pglist_data *pgdat) -{ -} - #endif /** From 45214458d6b50124afef3187f6352adeddf74d6f Mon Sep 17 00:00:00 2001 From: Haoqin Huang Date: Tue, 4 Aug 2026 17:38:37 +0800 Subject: [PATCH 1017/1328] zram: do not release zstd global params from error paths Patch series "zram: fix zstd error paths and add parameter validation", v6, Patch 1 removes zstd_release_params() from both zstd_create() and zstd_setup_params() error paths -- the former is a layering violation in a per-CPU callback, the latter is redundant as zcomp_init() already calls release_params() on setup failure. Patch 2 rejects zero-size dictionaries and prints distinct error messages for sz < 0 (returns the original error code) and sz == 0 ("empty file"). Currently errors are silently swallowed. Patch 3 adds pr_fmt to each backend file so that pr_err() messages are auto-prefixed with the algorithm name. Patch 4 validates dict and level parameters in each backend's .setup_params(), rejecting unsupported combinations and out-of-range levels. Patch 5 resets per-priority params on algorithm change before init. This patch (of 5): zstd_setup_params() creates global cdict and ddict stored in params->drv_data, shared across all per-CPU contexts. The per-CPU zstd_create() error path called zstd_release_params(), which freed those globally-shared objects. This is a layering violation: a per-CPU callback should only clean up its own context, not release resources owned by the compression lifecycle. zstd_setup_params() called zstd_release_params() on its own error path as well, but zcomp_init() already calls release_params() when setup fails, so this is redundant. Remove zstd_release_params() from both error paths. Link: https://lore.kernel.org/20260804093841.67920-1-haoqinhuang7@gmail.com Link: https://lore.kernel.org/20260804093841.67920-2-haoqinhuang7@gmail.com Signed-off-by: Haoqin Huang Signed-off-by: Rongwei Wang Reviewed-by: Sergey Senozhatsky Tested-by: Sergey Senozhatsky Cc: David Sterba Cc: Jens Axboe Cc: Minchan Kim Cc: Nick Terrell Signed-off-by: Andrew Morton --- drivers/block/zram/backend_zstd.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/block/zram/backend_zstd.c b/drivers/block/zram/backend_zstd.c index d00b548056dc..5fabc3e7e975 100644 --- a/drivers/block/zram/backend_zstd.c +++ b/drivers/block/zram/backend_zstd.c @@ -85,7 +85,6 @@ static int zstd_setup_params(struct zcomp_params *params) return 0; error: - zstd_release_params(params); return -EINVAL; } @@ -161,7 +160,6 @@ static int zstd_create(struct zcomp_params *params, struct zcomp_ctx *ctx) return 0; error: - zstd_release_params(params); zstd_destroy(ctx); return -EINVAL; } From 6dc404d433adf09045565054aecf85714db95b46 Mon Sep 17 00:00:00 2001 From: Haoqin Huang Date: Tue, 4 Aug 2026 17:38:38 +0800 Subject: [PATCH 1018/1328] zram: reject zero-size dictionary kernel_read_file_from_path() already rejects empty files (i_size <= 0) and returns -EINVAL, but the current implementation only checks for sz < 0 without logging any information. Use sz == 0 to reject the zero-size case and print distinct error messages for each failure type. Link: https://lore.kernel.org/20260804093841.67920-3-haoqinhuang7@gmail.com Signed-off-by: Haoqin Huang Signed-off-by: Rongwei Wang Reviewed-by: Sergey Senozhatsky Tested-by: Sergey Senozhatsky Cc: David Sterba Cc: Jens Axboe Cc: Minchan Kim Cc: Nick Terrell Signed-off-by: Andrew Morton --- drivers/block/zram/zram_drv.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/block/zram/zram_drv.c b/drivers/block/zram/zram_drv.c index cfa98846ac48..f73e30b61067 100644 --- a/drivers/block/zram/zram_drv.c +++ b/drivers/block/zram/zram_drv.c @@ -1700,8 +1700,16 @@ static int comp_params_store(struct zram *zram, u32 prio, s32 level, INT_MAX, NULL, READING_POLICY); - if (sz < 0) + if (sz < 0) { + pr_err("failed to load dictionary %s (err=%zd)\n", + dict_path, sz); + return sz; + } + if (sz == 0) { + pr_err("failed to load dictionary %s (empty file)\n", + dict_path); return -EINVAL; + } } zram->params[prio].dict_sz = sz; From 70922d5ef84a5863ac80d4b13f574cb9e461a716 Mon Sep 17 00:00:00 2001 From: Haoqin Huang Date: Tue, 4 Aug 2026 17:38:39 +0800 Subject: [PATCH 1019/1328] zram: add pr_fmt to backend files Add pr_fmt to each backend so that pr_err() messages are auto-prefixed with the algorithm name. While at it, tweak the deflate winbits pr_err to avoid a duplicated "deflate" prefix. Link: https://lore.kernel.org/20260804093841.67920-4-haoqinhuang7@gmail.com Signed-off-by: Haoqin Huang Signed-off-by: Rongwei Wang Reviewed-by: Sergey Senozhatsky Tested-by: Sergey Senozhatsky Cc: David Sterba Cc: Jens Axboe Cc: Minchan Kim Cc: Nick Terrell Signed-off-by: Andrew Morton --- drivers/block/zram/backend_842.c | 2 ++ drivers/block/zram/backend_deflate.c | 4 +++- drivers/block/zram/backend_lz4.c | 4 ++++ drivers/block/zram/backend_lz4hc.c | 4 ++++ drivers/block/zram/backend_lzo.c | 2 ++ drivers/block/zram/backend_lzorle.c | 2 ++ drivers/block/zram/backend_zstd.c | 2 ++ 7 files changed, 19 insertions(+), 1 deletion(-) diff --git a/drivers/block/zram/backend_842.c b/drivers/block/zram/backend_842.c index 10d9d5c60f53..d9b8a6bba2cb 100644 --- a/drivers/block/zram/backend_842.c +++ b/drivers/block/zram/backend_842.c @@ -1,5 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later +#define pr_fmt(fmt) "842: " fmt + #include #include #include diff --git a/drivers/block/zram/backend_deflate.c b/drivers/block/zram/backend_deflate.c index b3f7d08b49d9..ee26e6c9282f 100644 --- a/drivers/block/zram/backend_deflate.c +++ b/drivers/block/zram/backend_deflate.c @@ -1,5 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later +#define pr_fmt(fmt) "deflate: " fmt + #include #include #include @@ -30,7 +32,7 @@ static int deflate_setup_params(struct zcomp_params *params) s32 wb = params->deflate.winbits; if ((wb < -15 || wb > -9) && (wb < 9 || wb > 15)) { - pr_err("invalid deflate winbits: %d\n", wb); + pr_err("invalid winbits %d\n", wb); return -EINVAL; } } diff --git a/drivers/block/zram/backend_lz4.c b/drivers/block/zram/backend_lz4.c index c449d511ba86..6d58956ed5b2 100644 --- a/drivers/block/zram/backend_lz4.c +++ b/drivers/block/zram/backend_lz4.c @@ -1,3 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#define pr_fmt(fmt) "lz4: " fmt + #include #include #include diff --git a/drivers/block/zram/backend_lz4hc.c b/drivers/block/zram/backend_lz4hc.c index f6a336acfe20..c0c3715087c8 100644 --- a/drivers/block/zram/backend_lz4hc.c +++ b/drivers/block/zram/backend_lz4hc.c @@ -1,3 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#define pr_fmt(fmt) "lz4hc: " fmt + #include #include #include diff --git a/drivers/block/zram/backend_lzo.c b/drivers/block/zram/backend_lzo.c index 4c906beaae6b..84330dea6af5 100644 --- a/drivers/block/zram/backend_lzo.c +++ b/drivers/block/zram/backend_lzo.c @@ -1,5 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later +#define pr_fmt(fmt) "lzo: " fmt + #include #include #include diff --git a/drivers/block/zram/backend_lzorle.c b/drivers/block/zram/backend_lzorle.c index 10640c96cbfc..b3b03a008b64 100644 --- a/drivers/block/zram/backend_lzorle.c +++ b/drivers/block/zram/backend_lzorle.c @@ -1,5 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later +#define pr_fmt(fmt) "lzo-rle: " fmt + #include #include #include diff --git a/drivers/block/zram/backend_zstd.c b/drivers/block/zram/backend_zstd.c index 5fabc3e7e975..fb61acdaef67 100644 --- a/drivers/block/zram/backend_zstd.c +++ b/drivers/block/zram/backend_zstd.c @@ -1,5 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later +#define pr_fmt(fmt) "zstd: " fmt + #include #include #include From 7b0f677c7bd539bd5695b14f9a195e257c3b4463 Mon Sep 17 00:00:00 2001 From: Haoqin Huang Date: Tue, 4 Aug 2026 17:38:40 +0800 Subject: [PATCH 1020/1328] zram: validate parameters in each backend's setup_params Dict and level parameters are silently accepted even for backends that do not support them. Validate these parameters in each backend's .setup_params() to reject unsupported combinations and out-of-range levels with a specific error message. Link: https://lore.kernel.org/20260804093841.67920-5-haoqinhuang7@gmail.com Signed-off-by: Haoqin Huang Signed-off-by: Rongwei Wang Reviewed-by: Sergey Senozhatsky Tested-by: Sergey Senozhatsky Cc: David Sterba Cc: Jens Axboe Cc: Minchan Kim Cc: Nick Terrell Signed-off-by: Andrew Morton --- drivers/block/zram/backend_842.c | 8 ++++++++ drivers/block/zram/backend_deflate.c | 13 ++++++++++++- drivers/block/zram/backend_lz4.c | 6 +++++- drivers/block/zram/backend_lz4hc.c | 12 +++++++++++- drivers/block/zram/backend_lzo.c | 8 ++++++++ drivers/block/zram/backend_lzorle.c | 8 ++++++++ drivers/block/zram/backend_zstd.c | 7 ++++++- 7 files changed, 58 insertions(+), 4 deletions(-) diff --git a/drivers/block/zram/backend_842.c b/drivers/block/zram/backend_842.c index d9b8a6bba2cb..3846a04c69d7 100644 --- a/drivers/block/zram/backend_842.c +++ b/drivers/block/zram/backend_842.c @@ -15,6 +15,14 @@ static void release_params_842(struct zcomp_params *params) static int setup_params_842(struct zcomp_params *params) { + if (params->dict_sz) { + pr_err("dictionary is not supported\n"); + return -EOPNOTSUPP; + } + if (params->level != ZCOMP_PARAM_NOT_SET) { + pr_err("compression level is not supported\n"); + return -EOPNOTSUPP; + } return 0; } diff --git a/drivers/block/zram/backend_deflate.c b/drivers/block/zram/backend_deflate.c index ee26e6c9282f..f71b11bcac78 100644 --- a/drivers/block/zram/backend_deflate.c +++ b/drivers/block/zram/backend_deflate.c @@ -24,8 +24,19 @@ static void deflate_release_params(struct zcomp_params *params) static int deflate_setup_params(struct zcomp_params *params) { - if (params->level == ZCOMP_PARAM_NOT_SET) + if (params->dict_sz) { + pr_err("dictionary is not supported\n"); + return -EOPNOTSUPP; + } + + if (params->level == ZCOMP_PARAM_NOT_SET) { params->level = Z_DEFAULT_COMPRESSION; + } else if (params->level < Z_DEFAULT_COMPRESSION || + params->level > Z_BEST_COMPRESSION) { + pr_err("invalid compression level %d\n", params->level); + return -EINVAL; + } + if (params->deflate.winbits == ZCOMP_PARAM_NOT_SET) { params->deflate.winbits = DEFLATE_DEF_WINBITS; } else { diff --git a/drivers/block/zram/backend_lz4.c b/drivers/block/zram/backend_lz4.c index 6d58956ed5b2..1e28104ad964 100644 --- a/drivers/block/zram/backend_lz4.c +++ b/drivers/block/zram/backend_lz4.c @@ -32,8 +32,12 @@ static int lz4_setup_params(struct zcomp_params *params) LZ4_stream_t *dict_stream; int ret; - if (params->level == ZCOMP_PARAM_NOT_SET) + if (params->level == ZCOMP_PARAM_NOT_SET) { params->level = LZ4_ACCELERATION_DEFAULT; + } else if (params->level < LZ4_ACCELERATION_DEFAULT) { + pr_err("invalid compression level %d\n", params->level); + return -EINVAL; + } if (!params->dict || !params->dict_sz) return 0; diff --git a/drivers/block/zram/backend_lz4hc.c b/drivers/block/zram/backend_lz4hc.c index c0c3715087c8..d8aa01bb258f 100644 --- a/drivers/block/zram/backend_lz4hc.c +++ b/drivers/block/zram/backend_lz4hc.c @@ -22,8 +22,18 @@ static void lz4hc_release_params(struct zcomp_params *params) static int lz4hc_setup_params(struct zcomp_params *params) { - if (params->level == ZCOMP_PARAM_NOT_SET) + if (params->level == ZCOMP_PARAM_NOT_SET) { params->level = LZ4HC_DEFAULT_CLEVEL; + } else if (params->level < 1 || params->level > LZ4HC_MAX_CLEVEL) { + /* + * Use < 1 rather than < LZ4HC_MIN_CLEVEL here because + * LZ4HC_compress_generic() only clamps levels below 1 + * (levels 1 and 2 are valid). LZ4HC_MIN_CLEVEL (3) is + * advisory and not enforced by the library. + */ + pr_err("invalid compression level %d\n", params->level); + return -EINVAL; + } return 0; } diff --git a/drivers/block/zram/backend_lzo.c b/drivers/block/zram/backend_lzo.c index 84330dea6af5..d83f92cf757c 100644 --- a/drivers/block/zram/backend_lzo.c +++ b/drivers/block/zram/backend_lzo.c @@ -14,6 +14,14 @@ static void lzo_release_params(struct zcomp_params *params) static int lzo_setup_params(struct zcomp_params *params) { + if (params->dict_sz) { + pr_err("dictionary is not supported\n"); + return -EOPNOTSUPP; + } + if (params->level != ZCOMP_PARAM_NOT_SET) { + pr_err("compression level is not supported\n"); + return -EOPNOTSUPP; + } return 0; } diff --git a/drivers/block/zram/backend_lzorle.c b/drivers/block/zram/backend_lzorle.c index b3b03a008b64..1b120d062c92 100644 --- a/drivers/block/zram/backend_lzorle.c +++ b/drivers/block/zram/backend_lzorle.c @@ -14,6 +14,14 @@ static void lzorle_release_params(struct zcomp_params *params) static int lzorle_setup_params(struct zcomp_params *params) { + if (params->dict_sz) { + pr_err("dictionary is not supported\n"); + return -EOPNOTSUPP; + } + if (params->level != ZCOMP_PARAM_NOT_SET) { + pr_err("compression level is not supported\n"); + return -EOPNOTSUPP; + } return 0; } diff --git a/drivers/block/zram/backend_zstd.c b/drivers/block/zram/backend_zstd.c index fb61acdaef67..08da3810cffd 100644 --- a/drivers/block/zram/backend_zstd.c +++ b/drivers/block/zram/backend_zstd.c @@ -60,8 +60,13 @@ static int zstd_setup_params(struct zcomp_params *params) return -ENOMEM; params->drv_data = zp; - if (params->level == ZCOMP_PARAM_NOT_SET) + if (params->level == ZCOMP_PARAM_NOT_SET) { params->level = zstd_default_clevel(); + } else if (params->level < zstd_min_clevel() || + params->level > zstd_max_clevel()) { + pr_err("invalid compression level %d\n", params->level); + goto error; + } zp->cprm = zstd_get_params(params->level, PAGE_SIZE); From 702c5a799db20e49fe67cdfa27bac65374ad00ab Mon Sep 17 00:00:00 2001 From: Haoqin Huang Date: Tue, 4 Aug 2026 17:38:41 +0800 Subject: [PATCH 1021/1328] zram: reset per-priority params when changing algorithm before init Parameters validated against one algorithm may be invalid for another (e.g. lz4 accepts level=65535 but zstd does not). Although algorithm changes are blocked after disksize is set, they are allowed before device initialization. Reset per-priority params on algorithm change so that stale parameters do not silently carry over. Link: https://lore.kernel.org/20260804093841.67920-6-haoqinhuang7@gmail.com Signed-off-by: Haoqin Huang Signed-off-by: Rongwei Wang Reviewed-by: Sergey Senozhatsky Tested-by: Sergey Senozhatsky Cc: David Sterba Cc: Jens Axboe Cc: Minchan Kim Cc: Nick Terrell Signed-off-by: Andrew Morton --- drivers/block/zram/zram_drv.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/drivers/block/zram/zram_drv.c b/drivers/block/zram/zram_drv.c index f73e30b61067..56183c827e1b 100644 --- a/drivers/block/zram/zram_drv.c +++ b/drivers/block/zram/zram_drv.c @@ -1652,6 +1652,17 @@ static void comp_algorithm_set(struct zram *zram, u32 prio, const char *alg) zram->comp_algs[prio] = alg; } +static void comp_params_reset(struct zram *zram, u32 prio) +{ + struct zcomp_params *params = &zram->params[prio]; + + vfree(params->dict); + params->level = ZCOMP_PARAM_NOT_SET; + params->deflate.winbits = ZCOMP_PARAM_NOT_SET; + params->dict_sz = 0; + params->dict = NULL; +} + static int __comp_algorithm_store(struct zram *zram, u32 prio, const char *buf) { const char *alg; @@ -1672,20 +1683,10 @@ static int __comp_algorithm_store(struct zram *zram, u32 prio, const char *buf) } comp_algorithm_set(zram, prio, alg); + comp_params_reset(zram, prio); return 0; } -static void comp_params_reset(struct zram *zram, u32 prio) -{ - struct zcomp_params *params = &zram->params[prio]; - - vfree(params->dict); - params->level = ZCOMP_PARAM_NOT_SET; - params->deflate.winbits = ZCOMP_PARAM_NOT_SET; - params->dict_sz = 0; - params->dict = NULL; -} - static int comp_params_store(struct zram *zram, u32 prio, s32 level, const char *dict_path, struct deflate_params *deflate_params) From 894913e2d35c46ff19a77530907771ae57862b96 Mon Sep 17 00:00:00 2001 From: Longlong Xia Date: Tue, 4 Aug 2026 14:59:18 +0800 Subject: [PATCH 1022/1328] zram: fix out-of-bounds access in writeback_store() Patch series "zram: fix stale scan bounds after reinitialization". Both writeback_store() and read_block_state() derive their table scan bounds from zram->disksize before acquiring dev_lock. If the device is reset and reinitialized with a smaller disksize between that read and lock acquisition, the bound can describe the old table while the scan operates on the new one. This can lead to out-of-bounds slot accesses. Move both bound calculations under dev_lock so each bound remains consistent with the table throughout its scan. Keep the fixes separate because the affected interfaces originate from different commits and can be backported independently. This patch (of 2): writeback_store() calculates the table scan bounds before taking dev_lock. A reset followed by reconfiguration with a smaller disksize can therefore replace zram->table while writeback_store() is waiting for the lock. Once it acquires the lock, it sees an initialized device but scans the new table using the old upper bound, resulting in an out-of-bounds access. Calculate the number of pages while holding dev_lock so the scan bound matches the table protected by the lock. Link: https://lore.kernel.org/20260804065919.3970386-1-xialonglong2025@163.com Link: https://lore.kernel.org/20260804065919.3970386-2-xialonglong2025@163.com Fixes: a939888ec38b ("zram: support idle/huge page writeback") Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Longlong Xia Reviewed-by: Sergey Senozhatsky Cc: Jens Axboe Cc: Minchan Kim Cc: Signed-off-by: Andrew Morton --- drivers/block/zram/zram_drv.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/block/zram/zram_drv.c b/drivers/block/zram/zram_drv.c index 56183c827e1b..2be5c20e3f14 100644 --- a/drivers/block/zram/zram_drv.c +++ b/drivers/block/zram/zram_drv.c @@ -1235,8 +1235,8 @@ static ssize_t writeback_store(struct device *dev, const char *buf, size_t len) { struct zram *zram = dev_to_zram(dev); - u64 nr_pages = zram->disksize >> PAGE_SHIFT; - unsigned long lo = 0, hi = nr_pages; + u64 nr_pages; + unsigned long lo = 0, hi; struct zram_pp_ctl *pp_ctl = NULL; struct zram_wb_ctl *wb_ctl = NULL; char *args, *param, *val; @@ -1250,6 +1250,9 @@ static ssize_t writeback_store(struct device *dev, if (!zram->backing_dev) return -ENODEV; + nr_pages = zram->disksize >> PAGE_SHIFT; + hi = nr_pages; + pp_ctl = init_pp_ctl(); if (!pp_ctl) return -ENOMEM; From 391f057f44a51cc9418da5cba78b014324174264 Mon Sep 17 00:00:00 2001 From: Longlong Xia Date: Tue, 4 Aug 2026 14:59:19 +0800 Subject: [PATCH 1023/1328] zram: fix out-of-bounds access in read_block_state() read_block_state() calculates nr_pages before taking dev_lock. If the device is reset and reinitialized with a smaller disksize before lock acquisition, nr_pages still describes the old table. The subsequent loop can then call slot_lock() past the end of the newly allocated table. Read disksize after acquiring dev_lock and checking that the device is initialized. The read lock then keeps the table and its bound stable for the duration of the scan. Link: https://lore.kernel.org/20260804065919.3970386-3-xialonglong2025@163.com Fixes: c0265342bff4 ("zram: introduce zram memory tracking") Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Longlong Xia Reviewed-by: Sergey Senozhatsky Cc: Jens Axboe Cc: Minchan Kim Cc: Signed-off-by: Andrew Morton --- drivers/block/zram/zram_drv.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/block/zram/zram_drv.c b/drivers/block/zram/zram_drv.c index 2be5c20e3f14..82b78e6e55b2 100644 --- a/drivers/block/zram/zram_drv.c +++ b/drivers/block/zram/zram_drv.c @@ -1543,7 +1543,7 @@ static ssize_t read_block_state(struct file *file, char __user *buf, char *kbuf; ssize_t index, written = 0; struct zram *zram = file->private_data; - unsigned long nr_pages = zram->disksize >> PAGE_SHIFT; + unsigned long nr_pages; kbuf = kvmalloc(count, GFP_KERNEL); if (!kbuf) @@ -1555,6 +1555,8 @@ static ssize_t read_block_state(struct file *file, char __user *buf, return -EINVAL; } + nr_pages = zram->disksize >> PAGE_SHIFT; + for (index = *ppos; index < nr_pages; index++) { int copied; From bb3e3c5c2d63fa55566a4b0647fffd68172ee17f Mon Sep 17 00:00:00 2001 From: Ye Liu Date: Wed, 5 Aug 2026 17:31:07 +0800 Subject: [PATCH 1024/1328] mm: debug_page_alloc: fix type mismatch for debug_guardpage_minorder The debug_guardpage_minorder local variable is declared as unsigned int, but debug_guardpage_minorder_setup() uses unsigned long and kstrtoul() to parse the value. Use kstrtouint() with unsigned int local variable to match the actual type of _debug_guardpage_minorder. Also fix the format specifier from %lu to %u accordingly. Link: https://lore.kernel.org/20260805093108.2352900-1-ye.liu@linux.dev Signed-off-by: Ye Liu Reviewed-by: Andrew Morton Cc: Johannes Weiner Cc: Michal Hocko Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Zi Yan Signed-off-by: Andrew Morton --- mm/debug_page_alloc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mm/debug_page_alloc.c b/mm/debug_page_alloc.c index 6a26eca546c3..41e3d1f1ad96 100644 --- a/mm/debug_page_alloc.c +++ b/mm/debug_page_alloc.c @@ -20,14 +20,14 @@ early_param("debug_pagealloc", early_debug_pagealloc); static int __init debug_guardpage_minorder_setup(char *buf) { - unsigned long res; + unsigned int res; - if (kstrtoul(buf, 10, &res) < 0 || res > MAX_PAGE_ORDER / 2) { + if (kstrtouint(buf, 10, &res) < 0 || res > MAX_PAGE_ORDER / 2) { pr_err("Bad debug_guardpage_minorder value: %s\n", buf); return 0; } _debug_guardpage_minorder = res; - pr_info("Setting debug_guardpage_minorder to %lu\n", res); + pr_info("Setting debug_guardpage_minorder to %u\n", res); return 0; } early_param("debug_guardpage_minorder", debug_guardpage_minorder_setup); From afff109c2f8b35b88ea783d345c1067a311a57d8 Mon Sep 17 00:00:00 2001 From: Abhishek Bapat Date: Wed, 5 Aug 2026 17:29:52 +0000 Subject: [PATCH 1025/1328] alloc_tag: expose boot-time compression configuration Currently, userspace has limited visibility into the exact active runtime state of memory allocation profiling and its page extension compression ('sysctl.vm.mem_profiling={0|1|never}[,compressed]'). While reading the sysctl provides basic on/off status, it is currently impossible for userspace to natively determine whether page-tag compression was successfully enabled without scraping dmesg boot logs. Add a new read-only sysctl representing how compression was configured at boot time. Link: https://lore.kernel.org/c795f8089f82841e8a6e00d7ca286da2b23aeb7b.1785950530.git.abhishekbapat@google.com Signed-off-by: Abhishek Bapat Acked-by: Suren Baghdasaryan Cc: Hao Ge Signed-off-by: Andrew Morton --- Documentation/mm/allocation-profiling.rst | 11 +++++++++++ mm/alloc_tag.c | 6 ++++++ 2 files changed, 17 insertions(+) diff --git a/Documentation/mm/allocation-profiling.rst b/Documentation/mm/allocation-profiling.rst index 5389d241176a..e928aa3e4e1e 100644 --- a/Documentation/mm/allocation-profiling.rst +++ b/Documentation/mm/allocation-profiling.rst @@ -43,6 +43,17 @@ sysctl: warnings produced by allocations made while profiling is disabled and freed when it's enabled. + /proc/sys/vm/mem_profiling_compressed + + 1: Page alloc tag compression is enabled. + + 0: Page alloc tag compression is disabled. + + This reflects a static boot-time configuration of how page allocation tags are + stored (in page flags when compression is enabled and in page_ext when disabled). + Toggling ``mem_profiling`` at runtime does not change the state of + ``mem_profiling_compressed``. + Runtime info: /proc/allocinfo diff --git a/mm/alloc_tag.c b/mm/alloc_tag.c index e93e7fec1f06..b60ee89704cc 100644 --- a/mm/alloc_tag.c +++ b/mm/alloc_tag.c @@ -961,6 +961,12 @@ static const struct ctl_table memory_allocation_profiling_sysctls[] = { .mode = 0644, .proc_handler = proc_mem_profiling_handler, }, + { + .procname = "mem_profiling_compressed", + .data = &mem_profiling_compressed, + .mode = 0444, + .proc_handler = proc_do_static_key, + }, }; static void __init sysctl_init(void) From e73aeb8a411e5327da9c0746b2f93924ab081113 Mon Sep 17 00:00:00 2001 From: Muchun Song Date: Wed, 5 Aug 2026 10:25:36 +0800 Subject: [PATCH 1026/1328] mm/sparse: keep mem_section_usage_size() internal mem_section_usage_size() is only needed by sparsemem implementation code after commit ae751d567baa ("mm/bootmem_info: stop marking mem_section_usage as MIX_SECTION_INFO"), so keeping the declaration in mmzone.h now exposes the helper to all mmzone.h users for no reason. Move the helper to sparse.h so sparse.c and sparse-vmemmap.c can share it through the internal header. While doing so, calculate the allocation size with struct_size_t(), which ties the expression to the pageblock_flags trailing array instead of open-coding the struct header plus bitmap size. Link: https://lore.kernel.org/20260805022536.1206575-1-songmuchun@bytedance.com Signed-off-by: Muchun Song Acked-by: Mike Rapoport (Microsoft) Acked-by: David Hildenbrand (Arm) Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- include/linux/mmzone.h | 1 - mm/sparse.c | 10 ---------- mm/sparse.h | 6 ++++++ 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h index 158c1fba2393..94f9c3ff5416 100644 --- a/include/linux/mmzone.h +++ b/include/linux/mmzone.h @@ -2069,7 +2069,6 @@ static inline struct mem_section *__nr_to_section(unsigned long nr) #endif return &mem_section[root][nr & SECTION_ROOT_MASK]; } -extern size_t mem_section_usage_size(void); /* * We use the lower bits of the mem_map pointer to store a little bit of diff --git a/mm/sparse.c b/mm/sparse.c index 67fa192d4289..7c15406e77f5 100644 --- a/mm/sparse.c +++ b/mm/sparse.c @@ -213,16 +213,6 @@ static void __init memblocks_present(void) memory_present(nid, start, end); } -static unsigned long usemap_size(void) -{ - return BITS_TO_LONGS(SECTION_BLOCKFLAGS_BITS) * sizeof(unsigned long); -} - -size_t mem_section_usage_size(void) -{ - return sizeof(struct mem_section_usage) + usemap_size(); -} - #ifdef CONFIG_SPARSEMEM_VMEMMAP unsigned long __init section_map_size(void) { diff --git a/mm/sparse.h b/mm/sparse.h index 95aa031213f2..3b744667a7e6 100644 --- a/mm/sparse.h +++ b/mm/sparse.h @@ -47,6 +47,12 @@ static inline void __section_mark_present(struct mem_section *ms, ms->section_mem_map |= SECTION_MARKED_PRESENT; } + +static inline size_t mem_section_usage_size(void) +{ + return struct_size_t(struct mem_section_usage, pageblock_flags, + BITS_TO_LONGS(SECTION_BLOCKFLAGS_BITS)); +} #else static inline void sparse_init(void) {} #endif /* CONFIG_SPARSEMEM */ From 8be7c167be5792ce9c9fcb784b4cd086624feed2 Mon Sep 17 00:00:00 2001 From: Ye Liu Date: Wed, 5 Aug 2026 10:15:55 +0800 Subject: [PATCH 1027/1328] mm/show_mem: fix format string inconsistencies and type mismatches Fix five format string issues in show_free_areas() and __show_mem(): 1-2. reserved_highatomic and free_highatomic: %luKB -> %lukB The uppercase "KB" is inconsistent with all other fields in the same output block and with /proc/meminfo convention. 3. local_pcp: %ukB -> %lukB with explicit (unsigned long) cast per_cpu_pages.count is int, so K(count) yields int. Using %u was a signed/unsigned mismatch. Cast to unsigned long and use %lu for consistency with all other K() usages in the file. 4. total pagecache pages: %ld -> %lu global_node_page_state() returns unsigned long. Using %ld is a signedness mismatch caught by gcc -Wformat-signedness. 5. hwpoisoned pages: %lu -> %ld atomic_long_read() returns long (signed). Using %lu is a signedness mismatch caught by gcc -Wformat-signedness. Verified with: make KCFLAGS="-Wformat -Wformat-signedness" mm/show_mem.o Link: https://lore.kernel.org/20260805021556.1908807-1-ye.liu@linux.dev Signed-off-by: Ye Liu Acked-by: Vlastimil Babka (SUSE) Acked-by: Johannes Weiner Cc: Michal Hocko Cc: Suren Baghdasaryan Cc: Zi Yan Signed-off-by: Andrew Morton --- mm/show_mem.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mm/show_mem.c b/mm/show_mem.c index d1288b4c2b64..b938cbcd774a 100644 --- a/mm/show_mem.c +++ b/mm/show_mem.c @@ -309,8 +309,8 @@ static void show_free_areas(unsigned int filter, const nodemask_t *nodemask, " min:%lukB" " low:%lukB" " high:%lukB" - " reserved_highatomic:%luKB" - " free_highatomic:%luKB" + " reserved_highatomic:%lukB" + " free_highatomic:%lukB" " active_anon:%lukB" " inactive_anon:%lukB" " active_file:%lukB" @@ -323,7 +323,7 @@ static void show_free_areas(unsigned int filter, const nodemask_t *nodemask, " mlocked:%lukB" " bounce:%lukB" " free_pcp:%lukB" - " local_pcp:%ukB" + " local_pcp:%lukB" " free_cma:%lukB" "\n", zone->name, @@ -350,7 +350,7 @@ static void show_free_areas(unsigned int filter, const nodemask_t *nodemask, K(zone_page_state(zone, NR_MLOCK)), 0UL, K(free_pcp), - K(this_cpu_read(zone->per_cpu_pageset->count)), + K((unsigned long)this_cpu_read(zone->per_cpu_pageset->count)), K(zone_page_state(zone, NR_FREE_CMA_PAGES))); printk("lowmem_reserve[]:"); for (i = 0; i < MAX_NR_ZONES; i++) @@ -400,7 +400,7 @@ static void show_free_areas(unsigned int filter, const nodemask_t *nodemask, hugetlb_show_meminfo_node(nid); } - printk("%ld total pagecache pages\n", global_node_page_state(NR_FILE_PAGES)); + printk("%lu total pagecache pages\n", global_node_page_state(NR_FILE_PAGES)); show_swap_cache_info(); } @@ -430,7 +430,7 @@ void __show_mem(unsigned int filter, const nodemask_t *nodemask, printk("%lu pages cma reserved\n", totalcma_pages); #endif #ifdef CONFIG_MEMORY_FAILURE - printk("%lu pages hwpoisoned\n", atomic_long_read(&num_poisoned_pages)); + printk("%ld pages hwpoisoned\n", atomic_long_read(&num_poisoned_pages)); #endif #ifdef CONFIG_MEM_ALLOC_PROFILING static DEFINE_SPINLOCK(mem_alloc_profiling_spinlock); From 184bf187c45ba6c1141aa7fe10bf10f85d5a7634 Mon Sep 17 00:00:00 2001 From: Sergey Senozhatsky Date: Thu, 6 Aug 2026 12:16:32 +0900 Subject: [PATCH 1028/1328] zram: switch to unsigned long indexing zram has always used "unsigned int" for (page) index calculations, which unnecessarily limited max zram disksize. Switch to "unsigned long" and permit much larger zram devices. Link: https://lore.kernel.org/20260806031640.536615-1-senozhatsky@chromium.org Signed-off-by: Sergey Senozhatsky Suggested-by: Andrew Morton Co-developed-by: Longlong Xia Cc: Minchan Kim Signed-off-by: Andrew Morton --- drivers/block/zram/zram_drv.c | 137 +++++++++++++++++++--------------- 1 file changed, 77 insertions(+), 60 deletions(-) diff --git a/drivers/block/zram/zram_drv.c b/drivers/block/zram/zram_drv.c index 82b78e6e55b2..d09fdca49cbd 100644 --- a/drivers/block/zram/zram_drv.c +++ b/drivers/block/zram/zram_drv.c @@ -56,7 +56,7 @@ static size_t huge_class_size; static const struct block_device_operations zram_devops; -static void slot_free(struct zram *zram, u32 index); +static void slot_free(struct zram *zram, unsigned long index); /* * entry locking rules: @@ -70,7 +70,7 @@ static void slot_free(struct zram *zram, u32 index); * 4) Use TRY lock variant when in atomic context * - must check return value and handle locking failers */ -static __must_check bool slot_trylock(struct zram *zram, u32 index) +static __must_check bool slot_trylock(struct zram *zram, unsigned long index) { unsigned long *lock = &zram->table[index].__lock; @@ -83,7 +83,7 @@ static __must_check bool slot_trylock(struct zram *zram, u32 index) return false; } -static void slot_lock(struct zram *zram, u32 index) +static void slot_lock(struct zram *zram, unsigned long index) { unsigned long *lock = &zram->table[index].__lock; @@ -92,7 +92,7 @@ static void slot_lock(struct zram *zram, u32 index) lock_acquired(&zram->table_lock_map, _RET_IP_); } -static void slot_unlock(struct zram *zram, u32 index) +static void slot_unlock(struct zram *zram, unsigned long index) { unsigned long *lock = &zram->table[index].__lock; @@ -110,55 +110,56 @@ static inline struct zram *dev_to_zram(struct device *dev) return (struct zram *)dev_to_disk(dev)->private_data; } -static unsigned long get_slot_handle(struct zram *zram, u32 index) +static unsigned long get_slot_handle(struct zram *zram, unsigned long index) { return zram->table[index].handle; } -static void set_slot_handle(struct zram *zram, u32 index, unsigned long handle) +static void set_slot_handle(struct zram *zram, unsigned long index, + unsigned long handle) { zram->table[index].handle = handle; } -static bool test_slot_flag(struct zram *zram, u32 index, +static bool test_slot_flag(struct zram *zram, unsigned long index, enum zram_pageflags flag) { return zram->table[index].attr.flags & BIT(flag); } -static void set_slot_flag(struct zram *zram, u32 index, +static void set_slot_flag(struct zram *zram, unsigned long index, enum zram_pageflags flag) { zram->table[index].attr.flags |= BIT(flag); } -static void clear_slot_flag(struct zram *zram, u32 index, +static void clear_slot_flag(struct zram *zram, unsigned long index, enum zram_pageflags flag) { zram->table[index].attr.flags &= ~BIT(flag); } -static size_t get_slot_size(struct zram *zram, u32 index) +static size_t get_slot_size(struct zram *zram, unsigned long index) { return zram->table[index].attr.flags & (BIT(ZRAM_FLAG_SHIFT) - 1); } -static void set_slot_size(struct zram *zram, u32 index, size_t size) +static void set_slot_size(struct zram *zram, unsigned long index, size_t size) { unsigned long flags = zram->table[index].attr.flags >> ZRAM_FLAG_SHIFT; zram->table[index].attr.flags = (flags << ZRAM_FLAG_SHIFT) | size; } -static inline bool slot_allocated(struct zram *zram, u32 index) +static inline bool slot_allocated(struct zram *zram, unsigned long index) { return get_slot_size(zram, index) || test_slot_flag(zram, index, ZRAM_SAME) || test_slot_flag(zram, index, ZRAM_WB); } -static inline void set_slot_comp_priority(struct zram *zram, u32 index, - u32 prio) +static inline void set_slot_comp_priority(struct zram *zram, + unsigned long index, u32 prio) { prio &= ZRAM_COMP_PRIORITY_MASK; /* @@ -170,14 +171,14 @@ static inline void set_slot_comp_priority(struct zram *zram, u32 index, zram->table[index].attr.flags |= (prio << ZRAM_COMP_PRIORITY_BIT1); } -static inline u32 get_slot_comp_priority(struct zram *zram, u32 index) +static inline u32 get_slot_comp_priority(struct zram *zram, unsigned long index) { u32 prio = zram->table[index].attr.flags >> ZRAM_COMP_PRIORITY_BIT1; return prio & ZRAM_COMP_PRIORITY_MASK; } -static void mark_slot_accessed(struct zram *zram, u32 index) +static void mark_slot_accessed(struct zram *zram, unsigned long index) { clear_slot_flag(zram, index, ZRAM_IDLE); clear_slot_flag(zram, index, ZRAM_PP_SLOT); @@ -284,7 +285,7 @@ static void release_pp_ctl(struct zram *zram, struct zram_pp_ctl *ctl) } static bool place_pp_slot(struct zram *zram, struct zram_pp_ctl *ctl, - u32 index) + unsigned long index) { struct zram_pp_slot *pps; u32 bid; @@ -418,7 +419,7 @@ static void mark_idle(struct zram *zram, ktime_t cutoff) { int is_idle = 1; unsigned long nr_pages = zram->disksize >> PAGE_SHIFT; - int index; + unsigned long index; for (index = 0; index < nr_pages; index++) { /* @@ -485,8 +486,9 @@ static ssize_t idle_store(struct device *dev, struct device_attribute *attr, #define INVALID_BDEV_BLOCK (~0UL) static int read_from_zspool_raw(struct zram *zram, struct page *page, - u32 index); -static int read_from_zspool(struct zram *zram, struct page *page, u32 index); + unsigned long index); +static int read_from_zspool(struct zram *zram, struct page *page, + unsigned long index); struct zram_wb_ctl { /* idle list is accessed only by the writeback task, no concurency */ @@ -522,7 +524,7 @@ struct zram_rb_req { /* error status (sync read) */ int error; }; - u32 index; + unsigned long index; }; #define FOUR_K(x) ((x) * (1 << (PAGE_SHIFT - 12))) @@ -910,7 +912,7 @@ static void zram_account_writeback_submit(struct zram *zram) static int zram_writeback_complete(struct zram *zram, struct zram_wb_req *req) { - u32 index = req->pps->index; + unsigned long index = req->pps->index; int err; err = blk_status_to_errno(req->bio.bi_status); @@ -1032,7 +1034,7 @@ static int zram_writeback_slots(struct zram *zram, struct zram_wb_req *req = NULL; struct zram_pp_slot *pps; int ret = 0, err = 0; - u32 index = 0; + unsigned long index = 0; while ((pps = select_pp_slot(ctl))) { if (zram->wb_limit_enable && !zram->bd_wb_limit) { @@ -1198,7 +1200,7 @@ static void scan_slots_for_writeback(struct zram *zram, u32 mode, unsigned long lo, unsigned long hi, struct zram_pp_ctl *ctl) { - u32 index = lo; + unsigned long index = lo; while (index < hi) { bool ok = true; @@ -1235,7 +1237,7 @@ static ssize_t writeback_store(struct device *dev, const char *buf, size_t len) { struct zram *zram = dev_to_zram(dev); - u64 nr_pages; + unsigned long nr_pages; unsigned long lo = 0, hi; struct zram_pp_ctl *pp_ctl = NULL; struct zram_wb_ctl *wb_ctl = NULL; @@ -1336,7 +1338,8 @@ static ssize_t writeback_store(struct device *dev, return ret; } -static int decompress_bdev_page(struct zram *zram, struct page *page, u32 index) +static int decompress_bdev_page(struct zram *zram, struct page *page, + unsigned long index) { struct zcomp_strm *zstrm; unsigned int size; @@ -1378,7 +1381,7 @@ static void zram_deferred_decompress(struct work_struct *w) struct zram_rb_req *req = container_of(w, struct zram_rb_req, work); struct page *page = bio_first_page_all(req->bio); struct zram *zram = req->zram; - u32 index = req->index; + unsigned long index = req->index; int ret; ret = decompress_bdev_page(zram, page, index); @@ -1429,7 +1432,7 @@ static void zram_async_read_endio(struct bio *bio) } static int read_from_bdev_async(struct zram *zram, struct page *page, - u32 index, unsigned long blk_idx, + unsigned long index, unsigned long blk_idx, struct bio *parent) { struct zram_rb_req *req; @@ -1479,8 +1482,8 @@ static void zram_sync_read(struct work_struct *w) * chained IO with parent IO in same context, it's a deadlock. To avoid that, * use a worker thread context. */ -static int read_from_bdev_sync(struct zram *zram, struct page *page, u32 index, - unsigned long blk_idx) +static int read_from_bdev_sync(struct zram *zram, struct page *page, + unsigned long index, unsigned long blk_idx) { struct zram_rb_req req; @@ -1499,8 +1502,9 @@ static int read_from_bdev_sync(struct zram *zram, struct page *page, u32 index, return decompress_bdev_page(zram, page, index); } -static int read_from_bdev(struct zram *zram, struct page *page, u32 index, - unsigned long blk_idx, struct bio *parent) +static int read_from_bdev(struct zram *zram, struct page *page, + unsigned long index, unsigned long blk_idx, + struct bio *parent) { atomic64_inc(&zram->stats.bd_reads); if (!parent) { @@ -1512,8 +1516,9 @@ static int read_from_bdev(struct zram *zram, struct page *page, u32 index, } #else static inline void reset_bdev(struct zram *zram) {}; -static int read_from_bdev(struct zram *zram, struct page *page, u32 index, - unsigned long blk_idx, struct bio *parent) +static int read_from_bdev(struct zram *zram, struct page *page, + unsigned long index, unsigned long blk_idx, + struct bio *parent) { return -EIO; } @@ -1541,7 +1546,8 @@ static ssize_t read_block_state(struct file *file, char __user *buf, size_t count, loff_t *ppos) { char *kbuf; - ssize_t index, written = 0; + unsigned long index; + ssize_t written = 0; struct zram *zram = file->private_data; unsigned long nr_pages; @@ -1565,7 +1571,7 @@ static ssize_t read_block_state(struct file *file, char __user *buf, goto next; copied = snprintf(kbuf + written, count, - "%12zd %12u.%06d %c%c%c%c%c%c\n", + "%12lu %12u.%06d %c%c%c%c%c%c\n", index, zram->table[index].attr.ac_time, 0, test_slot_flag(zram, index, ZRAM_SAME) ? 's' : '.', test_slot_flag(zram, index, ZRAM_WB) ? 'w' : '.', @@ -1972,8 +1978,8 @@ static ssize_t debug_stat_show(struct device *dev, static void zram_meta_free(struct zram *zram, u64 disksize) { - size_t num_pages = disksize >> PAGE_SHIFT; - size_t index; + unsigned long num_pages = disksize >> PAGE_SHIFT; + unsigned long index; if (!zram->table) return; @@ -1990,7 +1996,7 @@ static void zram_meta_free(struct zram *zram, u64 disksize) static bool zram_meta_alloc(struct zram *zram, u64 disksize) { - size_t num_pages; + unsigned long num_pages; num_pages = disksize >> PAGE_SHIFT; zram->table = vzalloc(array_size(num_pages, sizeof(*zram->table))); @@ -2013,7 +2019,7 @@ static bool zram_meta_alloc(struct zram *zram, u64 disksize) return true; } -static void slot_free(struct zram *zram, u32 index) +static void slot_free(struct zram *zram, unsigned long index) { unsigned long handle; @@ -2067,7 +2073,7 @@ static void slot_free(struct zram *zram, u32 index) } static int read_same_filled_page(struct zram *zram, struct page *page, - u32 index) + unsigned long index) { void *mem; @@ -2078,7 +2084,7 @@ static int read_same_filled_page(struct zram *zram, struct page *page, } static int read_incompressible_page(struct zram *zram, struct page *page, - u32 index) + unsigned long index) { unsigned long handle; void *src, *dst; @@ -2093,7 +2099,8 @@ static int read_incompressible_page(struct zram *zram, struct page *page, return 0; } -static int read_compressed_page(struct zram *zram, struct page *page, u32 index) +static int read_compressed_page(struct zram *zram, struct page *page, + unsigned long index) { struct zcomp_strm *zstrm; unsigned long handle; @@ -2118,7 +2125,8 @@ static int read_compressed_page(struct zram *zram, struct page *page, u32 index) } #if defined CONFIG_ZRAM_WRITEBACK -static int read_from_zspool_raw(struct zram *zram, struct page *page, u32 index) +static int read_from_zspool_raw(struct zram *zram, struct page *page, + unsigned long index) { struct zcomp_strm *zstrm; unsigned long handle; @@ -2150,7 +2158,8 @@ static int read_from_zspool_raw(struct zram *zram, struct page *page, u32 index) * Reads (decompresses if needed) a page from zspool (zsmalloc). * Corresponding ZRAM slot should be locked. */ -static int read_from_zspool(struct zram *zram, struct page *page, u32 index) +static int read_from_zspool(struct zram *zram, struct page *page, + unsigned long index) { if (test_slot_flag(zram, index, ZRAM_SAME) || !get_slot_handle(zram, index)) @@ -2162,8 +2171,8 @@ static int read_from_zspool(struct zram *zram, struct page *page, u32 index) return read_incompressible_page(zram, page, index); } -static int zram_read_page(struct zram *zram, struct page *page, u32 index, - struct bio *parent) +static int zram_read_page(struct zram *zram, struct page *page, + unsigned long index, struct bio *parent) { int ret; @@ -2185,7 +2194,7 @@ static int zram_read_page(struct zram *zram, struct page *page, u32 index, /* Should NEVER happen. Return bio error if it does. */ if (WARN_ON(ret < 0)) - pr_err("Decompression failed! err=%d, page=%u\n", ret, index); + pr_err("Decompression failed! err=%d, page=%lu\n", ret, index); return ret; } @@ -2195,7 +2204,7 @@ static int zram_read_page(struct zram *zram, struct page *page, u32 index, * always expects a full page for the output. */ static int zram_bvec_read_partial(struct zram *zram, struct bio_vec *bvec, - u32 index, int offset) + unsigned long index, int offset) { struct page *page = alloc_page(GFP_NOIO); int ret; @@ -2210,7 +2219,7 @@ static int zram_bvec_read_partial(struct zram *zram, struct bio_vec *bvec, } static int zram_bvec_read(struct zram *zram, struct bio_vec *bvec, - u32 index, int offset, struct bio *bio) + unsigned long index, int offset, struct bio *bio) { if (is_partial_io(bvec)) return zram_bvec_read_partial(zram, bvec, index, offset); @@ -2218,7 +2227,7 @@ static int zram_bvec_read(struct zram *zram, struct bio_vec *bvec, } static int write_same_filled_page(struct zram *zram, unsigned long fill, - u32 index) + unsigned long index) { slot_lock(zram, index); slot_free(zram, index); @@ -2233,7 +2242,7 @@ static int write_same_filled_page(struct zram *zram, unsigned long fill, } static int write_incompressible_page(struct zram *zram, struct page *page, - u32 index) + unsigned long index) { unsigned long handle; void *src; @@ -2273,7 +2282,8 @@ static int write_incompressible_page(struct zram *zram, struct page *page, return 0; } -static int zram_write_page(struct zram *zram, struct page *page, u32 index) +static int zram_write_page(struct zram *zram, struct page *page, + unsigned long index) { int ret = 0; unsigned long handle; @@ -2340,7 +2350,7 @@ static int zram_write_page(struct zram *zram, struct page *page, u32 index) * This is a partial IO. Read the full page before writing the changes. */ static int zram_bvec_write_partial(struct zram *zram, struct bio_vec *bvec, - u32 index, int offset) + unsigned long index, int offset) { struct page *page = alloc_page(GFP_NOIO); int ret; @@ -2358,7 +2368,7 @@ static int zram_bvec_write_partial(struct zram *zram, struct bio_vec *bvec, } static int zram_bvec_write(struct zram *zram, struct bio_vec *bvec, - u32 index, int offset) + unsigned long index, int offset) { if (is_partial_io(bvec)) return zram_bvec_write_partial(zram, bvec, index, offset); @@ -2426,8 +2436,9 @@ static void scan_slots_for_recompress(struct zram *zram, u32 mode, u32 prio, * * Corresponding ZRAM slot should be locked. */ -static int recompress_slot(struct zram *zram, u32 index, struct page *page, - u64 *num_recomp_pages, u32 threshold, u32 prio) +static int recompress_slot(struct zram *zram, unsigned long index, + struct page *page, u64 *num_recomp_pages, + u32 threshold, u32 prio) { struct zcomp_strm *zstrm = NULL; unsigned long handle_old; @@ -2679,7 +2690,7 @@ static ssize_t recompress_store(struct device *dev, static void zram_bio_discard(struct zram *zram, struct bio *bio) { size_t n = bio->bi_iter.bi_size; - u32 index = bio->bi_iter.bi_sector >> SECTORS_PER_PAGE_SHIFT; + unsigned long index = bio->bi_iter.bi_sector >> SECTORS_PER_PAGE_SHIFT; u32 offset = (bio->bi_iter.bi_sector & (SECTORS_PER_PAGE - 1)) << SECTOR_SHIFT; @@ -2720,7 +2731,7 @@ static void zram_bio_read(struct zram *zram, struct bio *bio) struct bvec_iter iter = bio->bi_iter; do { - u32 index = iter.bi_sector >> SECTORS_PER_PAGE_SHIFT; + unsigned long index = iter.bi_sector >> SECTORS_PER_PAGE_SHIFT; u32 offset = (iter.bi_sector & (SECTORS_PER_PAGE - 1)) << SECTOR_SHIFT; struct bio_vec bv = bio_iter_iovec(bio, iter); @@ -2751,7 +2762,7 @@ static void zram_bio_write(struct zram *zram, struct bio *bio) struct bvec_iter iter = bio->bi_iter; do { - u32 index = iter.bi_sector >> SECTORS_PER_PAGE_SHIFT; + unsigned long index = iter.bi_sector >> SECTORS_PER_PAGE_SHIFT; u32 offset = (iter.bi_sector & (SECTORS_PER_PAGE - 1)) << SECTOR_SHIFT; struct bio_vec bv = bio_iter_iovec(bio, iter); @@ -2865,6 +2876,7 @@ static void zram_reset_device(struct zram *zram) static ssize_t disksize_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t len) { + unsigned long num_pages; u64 disksize; struct zcomp *comp; struct zram *zram = dev_to_zram(dev); @@ -2882,6 +2894,11 @@ static ssize_t disksize_store(struct device *dev, struct device_attribute *attr, } disksize = PAGE_ALIGN(disksize); + num_pages = disksize >> PAGE_SHIFT; + /* Slots are addressed by an unsigned long index */ + if (!num_pages || ((u64)num_pages << PAGE_SHIFT) != disksize) + return -EINVAL; + if (!zram_meta_alloc(zram, disksize)) return -ENOMEM; From e4ce743a8f3a8ac1428e220e4d0311f39d64ff87 Mon Sep 17 00:00:00 2001 From: Baolin Wang Date: Thu, 6 Aug 2026 11:34:12 +0800 Subject: [PATCH 1029/1328] selftests: mm: extend the check_huge() to support mTHP check Patch series "add anon mTHP collapse test cases", v3. This patch (of 4): To support checking for various sized mTHPs during mTHP collapse, extend the check_huge() function prototype to accept two new parameters specifying the address range and mTHP size, in preparation for the following patches. No functional changes. Link: https://lore.kernel.org/cover.1785985999.git.baolin.wang@linux.alibaba.com Link: https://lore.kernel.org/e5039cbc70f8de853e6c21048d65803a5fe41042.1785985999.git.baolin.wang@linux.alibaba.com Signed-off-by: Baolin Wang Reviewed-by: Nico Pache (Red Hat) Tested-by: Nico Pache (Red Hat) Acked-by: Zi Yan Acked-by: Kiryl Shutsemau (Meta) Cc: Barry Song Cc: David Hildenbrand Cc: Dev Jain Cc: Lance Yang Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Ryan Roberts Signed-off-by: Andrew Morton --- .../selftests/mm/folio_split_race_test.c | 2 +- tools/testing/selftests/mm/khugepaged.c | 66 ++++++++++--------- tools/testing/selftests/mm/pagemap_ioctl.c | 2 +- .../testing/selftests/mm/prctl_thp_disable.c | 2 +- tools/testing/selftests/mm/soft-dirty.c | 2 +- .../selftests/mm/split_huge_page_test.c | 14 ++-- tools/testing/selftests/mm/uffd-common.c | 4 +- tools/testing/selftests/mm/vm_util.c | 6 +- tools/testing/selftests/mm/vm_util.h | 6 +- 9 files changed, 56 insertions(+), 48 deletions(-) diff --git a/tools/testing/selftests/mm/folio_split_race_test.c b/tools/testing/selftests/mm/folio_split_race_test.c index 6329e37fff4c..45b84f7b364e 100644 --- a/tools/testing/selftests/mm/folio_split_race_test.c +++ b/tools/testing/selftests/mm/folio_split_race_test.c @@ -182,7 +182,7 @@ static uint64_t run_iteration(void) for (i = 0; i < TOTAL_PAGES; i++) fill_page(mmap_base, i); - if (!check_huge_shmem(mmap_base, NR_PMD_PAGE, pmd_pagesize)) + if (!check_huge_shmem(mmap_base, FILE_SIZE, NR_PMD_PAGE, pmd_pagesize)) ksft_exit_fail_msg("No shmem THP is allocated\n"); if (pthread_barrier_init(&ctl.barrier, NULL, NUM_READER_THREADS + 1) != 0) diff --git a/tools/testing/selftests/mm/khugepaged.c b/tools/testing/selftests/mm/khugepaged.c index 10e8dedcb087..c02d00846a79 100644 --- a/tools/testing/selftests/mm/khugepaged.c +++ b/tools/testing/selftests/mm/khugepaged.c @@ -51,7 +51,7 @@ struct mem_ops { void *(*setup_area)(int nr_hpages); void (*cleanup_area)(void *p, unsigned long size); void (*fault)(void *p, unsigned long start, unsigned long end); - bool (*check_huge)(void *addr, int nr_hpages); + bool (*check_huge)(void *addr, size_t len, int nr_hpages, unsigned long hpage_size); const char *name; }; @@ -276,7 +276,7 @@ static void *alloc_hpage(struct mem_ops *ops) ksft_print_msg("Allocate huge page..."); if (madvise_collapse_retry(p, hpage_pmd_size)) ksft_exit_fail_perror("madvise(MADV_COLLAPSE)"); - if (!ops->check_huge(p, 1)) + if (!ops->check_huge(p, hpage_pmd_size, 1, hpage_pmd_size)) ksft_exit_fail_perror("madvise(MADV_COLLAPSE)"); if (madvise(p, hpage_pmd_size, MADV_HUGEPAGE)) ksft_exit_fail_perror("madvise(MADV_HUGEPAGE)"); @@ -310,9 +310,10 @@ static void anon_fault(void *p, unsigned long start, unsigned long end) fill_memory(p, start, end); } -static bool anon_check_huge(void *addr, int nr_hpages) +static bool anon_check_huge(void *addr, size_t len, int nr_hpages, + unsigned long hpage_size) { - return check_huge_anon(addr, nr_hpages, hpage_pmd_size); + return check_huge_anon(addr, len, nr_hpages, hpage_size); } static void *file_setup_area_common(int nr_hpages, enum file_setup_ops setup) @@ -412,13 +413,14 @@ static void file_fault_write(void *p, unsigned long start, unsigned long end) ksft_exit_fail_perror("madvise(MADV_POPULATE_WRITE)"); } -static bool file_check_huge(void *addr, int nr_hpages) +static bool file_check_huge(void *addr, size_t len, int nr_hpages, + unsigned long hpage_size) { switch (finfo.type) { case VMA_FILE: - return check_huge_file(addr, nr_hpages, hpage_pmd_size); + return check_huge_file(addr, len, nr_hpages, hpage_size); case VMA_SHMEM: - return check_huge_shmem(addr, nr_hpages, hpage_pmd_size); + return check_huge_shmem(addr, len, nr_hpages, hpage_size); default: exit(EXIT_FAILURE); return false; @@ -448,9 +450,10 @@ static void shmem_cleanup_area(void *p, unsigned long size) close(finfo.fd); } -static bool shmem_check_huge(void *addr, int nr_hpages) +static bool shmem_check_huge(void *addr, size_t len, int nr_hpages, + unsigned long hpage_size) { - return check_huge_shmem(addr, nr_hpages, hpage_pmd_size); + return check_huge_shmem(addr, len, nr_hpages, hpage_size); } static struct mem_ops __anon_ops = { @@ -533,7 +536,7 @@ static void __madvise_collapse(const char *msg, char *p, int nr_hpages, ret = madvise_collapse_retry(p, nr_hpages * hpage_pmd_size); if (((bool)ret) == expect) fail("Fail: Bad return value"); - else if (!ops->check_huge(p, expect ? nr_hpages : 0)) + else if (!ops->check_huge(p, nr_hpages * hpage_pmd_size, expect ? nr_hpages : 0, hpage_pmd_size)) fail("Fail: check_huge()"); else success("OK"); @@ -545,7 +548,7 @@ static void madvise_collapse(const char *msg, char *p, int nr_hpages, struct mem_ops *ops, bool expect) { /* Sanity check */ - if (!ops->check_huge(p, 0)) + if (!ops->check_huge(p, nr_hpages * hpage_pmd_size, 0, hpage_pmd_size)) ksft_exit_fail_msg("Unexpected huge page\n"); __madvise_collapse(msg, p, nr_hpages, ops, expect); } @@ -554,11 +557,12 @@ static void madvise_collapse(const char *msg, char *p, int nr_hpages, static bool wait_for_scan(const char *msg, char *p, int nr_hpages, struct mem_ops *ops) { + size_t len = nr_hpages * hpage_pmd_size; int full_scans; int timeout = 6; /* 3 seconds */ /* Sanity check */ - if (!ops->check_huge(p, 0)) + if (!ops->check_huge(p, len, 0, hpage_pmd_size)) ksft_exit_fail_msg("Unexpected huge page\n"); madvise(p, nr_hpages * hpage_pmd_size, MADV_HUGEPAGE); @@ -568,7 +572,7 @@ static bool wait_for_scan(const char *msg, char *p, int nr_hpages, ksft_print_msg("%s...", msg); while (timeout--) { - if (ops->check_huge(p, nr_hpages)) + if (ops->check_huge(p, len, nr_hpages, hpage_pmd_size)) break; if (thp_read_num("khugepaged/full_scans") >= full_scans) break; @@ -582,6 +586,8 @@ static bool wait_for_scan(const char *msg, char *p, int nr_hpages, static void khugepaged_collapse(const char *msg, char *p, int nr_hpages, struct mem_ops *ops, bool expect) { + size_t len = nr_hpages * hpage_pmd_size; + /* * read&write file collapse fails since khugepaged does not flush * the target dirty folios @@ -605,7 +611,7 @@ static void khugepaged_collapse(const char *msg, char *p, int nr_hpages, if (ops != &__anon_ops) ops->fault(p, 0, nr_hpages * hpage_pmd_size); - if (ops->check_huge(p, expect ? nr_hpages : 0)) + if (ops->check_huge(p, len, expect ? nr_hpages : 0, hpage_pmd_size)) success("OK"); else fail("Fail"); @@ -634,7 +640,7 @@ static void alloc_at_fault(void) p = alloc_mapping(1); *p = 1; ksft_print_msg("Allocate huge page on fault..."); - if (check_huge_anon(p, 1, hpage_pmd_size)) + if (check_huge_anon(p, hpage_pmd_size, 1, hpage_pmd_size)) success("OK"); else fail("Fail"); @@ -643,7 +649,7 @@ static void alloc_at_fault(void) madvise(p, page_size, MADV_DONTNEED); ksft_print_msg("Split huge PMD on MADV_DONTNEED..."); - if (check_huge_anon(p, 0, hpage_pmd_size)) + if (check_huge_anon(p, hpage_pmd_size, 0, hpage_pmd_size)) success("OK"); else fail("Fail"); @@ -815,7 +821,7 @@ static void collapse_single_pte_entry_compound(struct collapse_context *c, struc madvise(p, hpage_pmd_size, MADV_NOHUGEPAGE); ksft_print_msg("Split huge page leaving single PTE mapping compound page..."); madvise(p + page_size, hpage_pmd_size - page_size, MADV_DONTNEED); - if (ops->check_huge(p, 0)) + if (ops->check_huge(p, hpage_pmd_size, 0, hpage_pmd_size)) success("OK"); else fail("Fail"); @@ -836,7 +842,7 @@ static void collapse_full_of_compound(struct collapse_context *c, struct mem_ops ksft_print_msg("Split huge page leaving single PTE page table full of compound pages..."); madvise(p, page_size, MADV_NOHUGEPAGE); madvise(p, hpage_pmd_size, MADV_NOHUGEPAGE); - if (ops->check_huge(p, 0)) + if (ops->check_huge(p, hpage_pmd_size, 0, hpage_pmd_size)) success("OK"); else fail("Fail"); @@ -858,7 +864,7 @@ static void collapse_compound_extreme(struct collapse_context *c, struct mem_ops for (i = 0; i < hpage_pmd_nr; i++) { madvise(BASE_ADDR, hpage_pmd_size, MADV_HUGEPAGE); ops->fault(BASE_ADDR, 0, hpage_pmd_size); - if (!ops->check_huge(BASE_ADDR, 1)) + if (!ops->check_huge(BASE_ADDR, hpage_pmd_size, 1, hpage_pmd_size)) ksft_exit_fail_msg("Failed to allocate huge page\n"); madvise(BASE_ADDR, hpage_pmd_size, MADV_NOHUGEPAGE); @@ -881,7 +887,7 @@ static void collapse_compound_extreme(struct collapse_context *c, struct mem_ops ops->cleanup_area(BASE_ADDR, hpage_pmd_size); ops->fault(p, 0, hpage_pmd_size); - if (!ops->check_huge(p, 1)) + if (!ops->check_huge(p, hpage_pmd_size, 1, hpage_pmd_size)) success("OK"); else fail("Fail"); @@ -903,7 +909,7 @@ static void collapse_fork(struct collapse_context *c, struct mem_ops *ops) ksft_print_msg("Allocate small page..."); ops->fault(p, 0, page_size); - if (ops->check_huge(p, 0)) + if (ops->check_huge(p, hpage_pmd_size, 0, hpage_pmd_size)) success("OK"); else fail("Fail"); @@ -911,7 +917,7 @@ static void collapse_fork(struct collapse_context *c, struct mem_ops *ops) ksft_print_msg("Share small page over fork()..."); if (!fork()) { /* Do not touch settings on child exit */ - if (ops->check_huge(p, 0)) + if (ops->check_huge(p, hpage_pmd_size, 0, hpage_pmd_size)) success("OK"); else fail("Fail"); @@ -929,7 +935,7 @@ static void collapse_fork(struct collapse_context *c, struct mem_ops *ops) exit_status = WEXITSTATUS(wstatus); ksft_print_msg("Check if parent still has small page..."); - if (ops->check_huge(p, 0)) + if (ops->check_huge(p, hpage_pmd_size, 0, hpage_pmd_size)) success("OK"); else fail("Fail"); @@ -947,7 +953,7 @@ static void collapse_fork_compound(struct collapse_context *c, struct mem_ops *o ksft_print_msg("Share huge page over fork()..."); if (!fork()) { /* Do not touch settings on child exit */ - if (ops->check_huge(p, 1)) + if (ops->check_huge(p, hpage_pmd_size, 1, hpage_pmd_size)) success("OK"); else fail("Fail"); @@ -955,7 +961,7 @@ static void collapse_fork_compound(struct collapse_context *c, struct mem_ops *o ksft_print_msg("Split huge page PMD in child process..."); madvise(p, page_size, MADV_NOHUGEPAGE); madvise(p, hpage_pmd_size, MADV_NOHUGEPAGE); - if (ops->check_huge(p, 0)) + if (ops->check_huge(p, hpage_pmd_size, 0, hpage_pmd_size)) success("OK"); else fail("Fail"); @@ -976,7 +982,7 @@ static void collapse_fork_compound(struct collapse_context *c, struct mem_ops *o exit_status = WEXITSTATUS(wstatus); ksft_print_msg("Check if parent still has huge page..."); - if (ops->check_huge(p, 1)) + if (ops->check_huge(p, hpage_pmd_size, 1, hpage_pmd_size)) success("OK"); else fail("Fail"); @@ -995,7 +1001,7 @@ static void collapse_max_ptes_shared(struct collapse_context *c, struct mem_ops ksft_print_msg("Share huge page over fork()..."); if (!fork()) { /* Do not touch settings on child exit */ - if (ops->check_huge(p, 1)) + if (ops->check_huge(p, hpage_pmd_size, 1, hpage_pmd_size)) success("OK"); else fail("Fail"); @@ -1003,7 +1009,7 @@ static void collapse_max_ptes_shared(struct collapse_context *c, struct mem_ops ksft_print_msg("Trigger CoW on page %d of %d...", hpage_pmd_nr - max_ptes_shared - 1, hpage_pmd_nr); ops->fault(p, 0, (hpage_pmd_nr - max_ptes_shared - 1) * page_size); - if (ops->check_huge(p, 0)) + if (ops->check_huge(p, hpage_pmd_size, 0, hpage_pmd_size)) success("OK"); else fail("Fail"); @@ -1016,7 +1022,7 @@ static void collapse_max_ptes_shared(struct collapse_context *c, struct mem_ops hpage_pmd_nr - max_ptes_shared, hpage_pmd_nr); ops->fault(p, 0, (hpage_pmd_nr - max_ptes_shared) * page_size); - if (ops->check_huge(p, 0)) + if (ops->check_huge(p, hpage_pmd_size, 0, hpage_pmd_size)) success("OK"); else fail("Fail"); @@ -1034,7 +1040,7 @@ static void collapse_max_ptes_shared(struct collapse_context *c, struct mem_ops exit_status = WEXITSTATUS(wstatus); ksft_print_msg("Check if parent still has huge page..."); - if (ops->check_huge(p, 1)) + if (ops->check_huge(p, hpage_pmd_size, 1, hpage_pmd_size)) success("OK"); else fail("Fail"); diff --git a/tools/testing/selftests/mm/pagemap_ioctl.c b/tools/testing/selftests/mm/pagemap_ioctl.c index 1b2dffcc999b..cfd1987339c1 100644 --- a/tools/testing/selftests/mm/pagemap_ioctl.c +++ b/tools/testing/selftests/mm/pagemap_ioctl.c @@ -1085,7 +1085,7 @@ static void unpopulated_written_test(const char *name, char *mem, long size, memset(mem, 1, size); if (use_thp && (madvise(mem, size, MADV_COLLAPSE) || - !check_huge_anon(mem, size / hpage_size, hpage_size))) { + !check_huge_anon(mem, size, size / hpage_size, hpage_size))) { ksft_test_result_skip("%s could not form a THP\n", name); goto out; } diff --git a/tools/testing/selftests/mm/prctl_thp_disable.c b/tools/testing/selftests/mm/prctl_thp_disable.c index d8d9d1de57b8..82c6e96ea6eb 100644 --- a/tools/testing/selftests/mm/prctl_thp_disable.c +++ b/tools/testing/selftests/mm/prctl_thp_disable.c @@ -67,7 +67,7 @@ static int test_mmap_thp(enum thp_collapse_type madvise_buf, size_t pmdsize) /* HACK: make sure we have a separate VMA that we can check reliably. */ mprotect(mem, pmdsize, PROT_READ); - ret = check_huge_anon(mem, 1, pmdsize); + ret = check_huge_anon(mem, pmdsize, 1, pmdsize); munmap(mmap_mem, mmap_size); return ret; } diff --git a/tools/testing/selftests/mm/soft-dirty.c b/tools/testing/selftests/mm/soft-dirty.c index fb1864a68e1c..e198facf78bb 100644 --- a/tools/testing/selftests/mm/soft-dirty.c +++ b/tools/testing/selftests/mm/soft-dirty.c @@ -103,7 +103,7 @@ static void test_hugepage(int pagemap_fd, int pagesize) for (i = 0; i < hpage_len; i++) map[i] = (char)i; - if (check_huge_anon(map, 1, hpage_len)) { + if (check_huge_anon(map, hpage_len, 1, hpage_len)) { ksft_test_result_pass("Test %s huge page allocation\n", __func__); clear_softdirty(); diff --git a/tools/testing/selftests/mm/split_huge_page_test.c b/tools/testing/selftests/mm/split_huge_page_test.c index 32b991472f74..4cc70873a674 100644 --- a/tools/testing/selftests/mm/split_huge_page_test.c +++ b/tools/testing/selftests/mm/split_huge_page_test.c @@ -296,7 +296,7 @@ static void verify_rss_anon_split_huge_page_all_zeroes(char *one_page, int nr_hp unsigned long rss_anon_before, rss_anon_after; size_t i; - if (!check_huge_anon(one_page, nr_hpages, pmd_pagesize)) + if (!check_huge_anon(one_page, nr_hpages * pmd_pagesize, nr_hpages, pmd_pagesize)) ksft_exit_fail_msg("No THP is allocated\n"); rss_anon_before = rss_anon(); @@ -311,7 +311,7 @@ static void verify_rss_anon_split_huge_page_all_zeroes(char *one_page, int nr_hp if (one_page[i] != (char)0) ksft_exit_fail_msg("%ld byte corrupted\n", i); - if (!check_huge_anon(one_page, 0, pmd_pagesize)) + if (!check_huge_anon(one_page, nr_hpages * pmd_pagesize, 0, pmd_pagesize)) ksft_exit_fail_msg("Still AnonHugePages not split\n"); rss_anon_after = rss_anon(); @@ -347,7 +347,7 @@ static void split_pmd_thp_to_order(int order) for (i = 0; i < len; i++) one_page[i] = (char)i; - if (!check_huge_anon(one_page, 4, pmd_pagesize)) + if (!check_huge_anon(one_page, 4 * pmd_pagesize, 4, pmd_pagesize)) ksft_exit_fail_msg("No THP is allocated\n"); /* split all THPs */ @@ -366,7 +366,7 @@ static void split_pmd_thp_to_order(int order) (pmd_order + 1))) ksft_exit_fail_msg("Unexpected THP split\n"); - if (!check_huge_anon(one_page, 0, pmd_pagesize)) + if (!check_huge_anon(one_page, 4 * pmd_pagesize, 0, pmd_pagesize)) ksft_exit_fail_msg("Still AnonHugePages not split\n"); ksft_test_result_pass("Split huge pages to order %d successful\n", order); @@ -393,7 +393,7 @@ static void split_pte_mapped_thp(void) for (i = 0; i < thp_area_size; i++) thp_area[i] = (char)i; - if (!check_huge_anon(thp_area, nr_thps, pmd_pagesize)) { + if (!check_huge_anon(thp_area, nr_thps * pmd_pagesize, nr_thps, pmd_pagesize)) { ksft_test_result_skip("Not all THPs allocated\n"); goto out; } @@ -657,7 +657,7 @@ static int create_pagecache_thp_and_fd(const char *testfile, size_t fd_size, force_read_pages(*addr, fd_size / pmd_pagesize, pmd_pagesize); - if (!check_huge_file(*addr, fd_size / pmd_pagesize, pmd_pagesize)) { + if (!check_huge_file(*addr, fd_size, fd_size / pmd_pagesize, pmd_pagesize)) { ksft_print_msg("No large pagecache folio generated, please provide a filesystem supporting large folio\n"); munmap(*addr, fd_size); close(*fd); @@ -735,7 +735,7 @@ static void split_thp_in_pagecache_to_order_at(size_t fd_size, goto out; } - if (!check_huge_file(addr, 0, pmd_pagesize)) { + if (!check_huge_file(addr, fd_size, 0, pmd_pagesize)) { ksft_print_msg("Still FilePmdMapped not split\n"); err = EXIT_FAILURE; goto out; diff --git a/tools/testing/selftests/mm/uffd-common.c b/tools/testing/selftests/mm/uffd-common.c index f48f5d4594ab..1fb967ef4985 100644 --- a/tools/testing/selftests/mm/uffd-common.c +++ b/tools/testing/selftests/mm/uffd-common.c @@ -194,7 +194,9 @@ static void shmem_alias_mapping(uffd_global_test_opts_t *gopts, __u64 *start, static void shmem_check_pmd_mapping(uffd_global_test_opts_t *gopts, void *p, int expect_nr_hpages) { - if (!check_huge_shmem(gopts->area_dst_alias, expect_nr_hpages, + size_t len = expect_nr_hpages * read_pmd_pagesize(); + + if (!check_huge_shmem(gopts->area_dst_alias, len, expect_nr_hpages, read_pmd_pagesize())) err("Did not find expected %d number of hugepages", expect_nr_hpages); diff --git a/tools/testing/selftests/mm/vm_util.c b/tools/testing/selftests/mm/vm_util.c index ef1ea11981a7..ed7b4eae3f3c 100644 --- a/tools/testing/selftests/mm/vm_util.c +++ b/tools/testing/selftests/mm/vm_util.c @@ -247,17 +247,17 @@ bool __check_huge(void *addr, char *pattern, int nr_hpages, return thp == (nr_hpages * (hpage_size >> 10)); } -bool check_huge_anon(void *addr, int nr_hpages, uint64_t hpage_size) +bool check_huge_anon(void *addr, size_t len, int nr_hpages, uint64_t hpage_size) { return __check_huge(addr, "AnonHugePages: ", nr_hpages, hpage_size); } -bool check_huge_file(void *addr, int nr_hpages, uint64_t hpage_size) +bool check_huge_file(void *addr, size_t len, int nr_hpages, uint64_t hpage_size) { return __check_huge(addr, "FilePmdMapped:", nr_hpages, hpage_size); } -bool check_huge_shmem(void *addr, int nr_hpages, uint64_t hpage_size) +bool check_huge_shmem(void *addr, size_t len, int nr_hpages, uint64_t hpage_size) { return __check_huge(addr, "ShmemPmdMapped:", nr_hpages, hpage_size); } diff --git a/tools/testing/selftests/mm/vm_util.h b/tools/testing/selftests/mm/vm_util.h index 7799154b67ee..565570b2cf8b 100644 --- a/tools/testing/selftests/mm/vm_util.h +++ b/tools/testing/selftests/mm/vm_util.h @@ -90,9 +90,9 @@ void clear_softdirty(void); bool check_for_pattern(FILE *fp, const char *pattern, char *buf, size_t len); uint64_t read_pmd_pagesize(void); unsigned long rss_anon(void); -bool check_huge_anon(void *addr, int nr_hpages, uint64_t hpage_size); -bool check_huge_file(void *addr, int nr_hpages, uint64_t hpage_size); -bool check_huge_shmem(void *addr, int nr_hpages, uint64_t hpage_size); +bool check_huge_anon(void *addr, size_t len, int nr_hpages, uint64_t hpage_size); +bool check_huge_file(void *addr, size_t len, int nr_hpages, uint64_t hpage_size); +bool check_huge_shmem(void *addr, size_t len, int nr_hpages, uint64_t hpage_size); int64_t allocate_transhuge(void *ptr, int pagemap_fd); int pageflags_get(unsigned long pfn, int kpageflags_fd, uint64_t *flags); From 6995150ede2805914b2fbc3a2ba674d06784c04b Mon Sep 17 00:00:00 2001 From: Baolin Wang Date: Thu, 6 Aug 2026 11:34:13 +0800 Subject: [PATCH 1030/1328] selftests: mm: move gather_after_split_folio_orders() into vm_util.c file Move gather_after_split_folio_orders() to vm_util.c as a helper function in preparation for implementing checks for mTHP collapse. While we are at it, rename this function to indicate that it is not only used for large folio splits. No functional changes. Link: https://lore.kernel.org/30a0a99556adf11c2bf97aa08d6da4830bb43f6f.1785985999.git.baolin.wang@linux.alibaba.com Signed-off-by: Baolin Wang Reviewed-by: Nico Pache (Red Hat) Tested-by: Nico Pache (Red Hat) Reviewed-by: Zi Yan Acked-by: Kiryl Shutsemau (Meta) Cc: Barry Song Cc: David Hildenbrand Cc: Dev Jain Cc: Lance Yang Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Ryan Roberts Signed-off-by: Andrew Morton --- .../selftests/mm/split_huge_page_test.c | 125 +----------------- tools/testing/selftests/mm/vm_util.c | 119 +++++++++++++++++ tools/testing/selftests/mm/vm_util.h | 2 + 3 files changed, 122 insertions(+), 124 deletions(-) diff --git a/tools/testing/selftests/mm/split_huge_page_test.c b/tools/testing/selftests/mm/split_huge_page_test.c index 4cc70873a674..86a603692826 100644 --- a/tools/testing/selftests/mm/split_huge_page_test.c +++ b/tools/testing/selftests/mm/split_huge_page_test.c @@ -104,129 +104,6 @@ static bool is_backed_by_folio(char *vaddr, int order, int pagemap_fd, return false; } -static int vaddr_pageflags_get(char *vaddr, int pagemap_fd, int kpageflags_fd, - uint64_t *flags) -{ - unsigned long pfn; - - pfn = pagemap_get_pfn(pagemap_fd, vaddr); - - /* non-present PFN */ - if (pfn == -1UL) - return 1; - - if (pageflags_get(pfn, kpageflags_fd, flags)) - return -1; - - return 0; -} - -/* - * gather_after_split_folio_orders - scan through [vaddr_start, len) and record - * folio orders - * - * @vaddr_start: start vaddr - * @len: range length - * @pagemap_fd: file descriptor to /proc//pagemap - * @kpageflags_fd: file descriptor to /proc/kpageflags - * @orders: output folio order array - * @nr_orders: folio order array size - * - * gather_after_split_folio_orders() scan through [vaddr_start, len) and check - * all folios within the range and record their orders. All order-0 pages will - * be recorded. Non-present vaddr is skipped. - * - * NOTE: the function is used to check folio orders after a split is performed, - * so it assumes [vaddr_start, len) fully maps to after-split folios within that - * range. - * - * Return: 0 - no error, -1 - unhandled cases - */ -static int gather_after_split_folio_orders(char *vaddr_start, size_t len, - int pagemap_fd, int kpageflags_fd, int orders[], int nr_orders) -{ - uint64_t page_flags = 0; - int cur_order = -1; - char *vaddr; - - if (pagemap_fd == -1 || kpageflags_fd == -1) - return -1; - if (!orders) - return -1; - if (nr_orders <= 0) - return -1; - - for (vaddr = vaddr_start; vaddr < vaddr_start + len;) { - char *next_folio_vaddr; - int status; - - status = vaddr_pageflags_get(vaddr, pagemap_fd, kpageflags_fd, - &page_flags); - if (status < 0) - return -1; - - /* skip non present vaddr */ - if (status == 1) { - vaddr += psize(); - continue; - } - - /* all order-0 pages with possible false postive (non folio) */ - if (!(page_flags & (KPF_COMPOUND_HEAD | KPF_COMPOUND_TAIL))) { - orders[0]++; - vaddr += psize(); - continue; - } - - /* skip non thp compound pages */ - if (!(page_flags & KPF_THP)) { - vaddr += psize(); - continue; - } - - /* vpn points to part of a THP at this point */ - if (page_flags & KPF_COMPOUND_HEAD) - cur_order = 1; - else { - vaddr += psize(); - continue; - } - - next_folio_vaddr = vaddr + (1UL << (cur_order + pshift())); - - if (next_folio_vaddr >= vaddr_start + len) - break; - - while ((status = vaddr_pageflags_get(next_folio_vaddr, - pagemap_fd, kpageflags_fd, - &page_flags)) >= 0) { - /* - * non present vaddr, next compound head page, or - * order-0 page - */ - if (status == 1 || - (page_flags & KPF_COMPOUND_HEAD) || - !(page_flags & (KPF_COMPOUND_HEAD | KPF_COMPOUND_TAIL))) { - if (cur_order < nr_orders) { - orders[cur_order]++; - cur_order = -1; - vaddr = next_folio_vaddr; - } - break; - } - - cur_order++; - next_folio_vaddr = vaddr + (1UL << (cur_order + pshift())); - } - - if (status < 0) - return status; - } - if (cur_order > 0 && cur_order < nr_orders) - orders[cur_order]++; - return 0; -} - static int check_after_split_folio_orders(char *vaddr_start, size_t len, int pagemap_fd, int kpageflags_fd, int orders[], int nr_orders) { @@ -240,7 +117,7 @@ static int check_after_split_folio_orders(char *vaddr_start, size_t len, ksft_exit_fail_msg("Cannot allocate memory for vaddr_orders"); memset(vaddr_orders, 0, sizeof(int) * nr_orders); - status = gather_after_split_folio_orders(vaddr_start, len, pagemap_fd, + status = gather_folio_orders(vaddr_start, len, pagemap_fd, kpageflags_fd, vaddr_orders, nr_orders); if (status) ksft_exit_fail_msg("gather folio info failed\n"); diff --git a/tools/testing/selftests/mm/vm_util.c b/tools/testing/selftests/mm/vm_util.c index ed7b4eae3f3c..5a427f494cd8 100644 --- a/tools/testing/selftests/mm/vm_util.c +++ b/tools/testing/selftests/mm/vm_util.c @@ -194,6 +194,125 @@ unsigned long rss_anon(void) return rss_anon; } +static int vaddr_pageflags_get(char *vaddr, int pagemap_fd, int kpageflags_fd, + uint64_t *flags) +{ + unsigned long pfn; + + pfn = pagemap_get_pfn(pagemap_fd, vaddr); + + /* non-present PFN */ + if (pfn == -1UL) + return 1; + + if (pageflags_get(pfn, kpageflags_fd, flags)) + return -1; + + return 0; +} + +/* + * gather_folio_orders - scan through [vaddr_start, len) and record + * folio orders + * + * @vaddr_start: start vaddr + * @len: range length + * @pagemap_fd: file descriptor to /proc//pagemap + * @kpageflags_fd: file descriptor to /proc/kpageflags + * @orders: output folio order array + * @nr_orders: folio order array size + * + * gather_folio_orders() scan through [vaddr_start, len) and check + * all folios within the range and record their orders. All order-0 pages will + * be recorded. Non-present vaddr is skipped. + * + * Return: 0 - no error, -1 - unhandled cases + */ +int gather_folio_orders(char *vaddr_start, size_t len, + int pagemap_fd, int kpageflags_fd, int orders[], int nr_orders) +{ + uint64_t page_flags = 0; + int cur_order = -1; + char *vaddr; + + if (pagemap_fd == -1 || kpageflags_fd == -1) + return -1; + if (!orders) + return -1; + if (nr_orders <= 0) + return -1; + + for (vaddr = vaddr_start; vaddr < vaddr_start + len;) { + char *next_folio_vaddr; + int status; + + status = vaddr_pageflags_get(vaddr, pagemap_fd, kpageflags_fd, + &page_flags); + if (status < 0) + return -1; + + /* skip non present vaddr */ + if (status == 1) { + vaddr += psize(); + continue; + } + + /* all order-0 pages with possible false postive (non folio) */ + if (!(page_flags & (KPF_COMPOUND_HEAD | KPF_COMPOUND_TAIL))) { + orders[0]++; + vaddr += psize(); + continue; + } + + /* skip non thp compound pages */ + if (!(page_flags & KPF_THP)) { + vaddr += psize(); + continue; + } + + /* vpn points to part of a THP at this point */ + if (page_flags & KPF_COMPOUND_HEAD) + cur_order = 1; + else { + vaddr += psize(); + continue; + } + + next_folio_vaddr = vaddr + (1UL << (cur_order + pshift())); + + if (next_folio_vaddr >= vaddr_start + len) + break; + + while ((status = vaddr_pageflags_get(next_folio_vaddr, + pagemap_fd, kpageflags_fd, + &page_flags)) >= 0) { + /* + * non present vaddr, next compound head page, or + * order-0 page + */ + if (status == 1 || + (page_flags & KPF_COMPOUND_HEAD) || + !(page_flags & (KPF_COMPOUND_HEAD | KPF_COMPOUND_TAIL))) { + if (cur_order < nr_orders) { + orders[cur_order]++; + cur_order = -1; + vaddr = next_folio_vaddr; + } + break; + } + + cur_order++; + next_folio_vaddr = vaddr + (1UL << (cur_order + pshift())); + } + + if (status < 0) + return status; + } + if (cur_order > 0 && cur_order < nr_orders) + orders[cur_order]++; + return 0; +} + char *__get_smap_entry(void *addr, const char *pattern, char *buf, size_t len) { int ret; diff --git a/tools/testing/selftests/mm/vm_util.h b/tools/testing/selftests/mm/vm_util.h index 565570b2cf8b..9a49af88702e 100644 --- a/tools/testing/selftests/mm/vm_util.h +++ b/tools/testing/selftests/mm/vm_util.h @@ -95,6 +95,8 @@ bool check_huge_file(void *addr, size_t len, int nr_hpages, uint64_t hpage_size) bool check_huge_shmem(void *addr, size_t len, int nr_hpages, uint64_t hpage_size); int64_t allocate_transhuge(void *ptr, int pagemap_fd); int pageflags_get(unsigned long pfn, int kpageflags_fd, uint64_t *flags); +int gather_folio_orders(char *vaddr_start, size_t len, + int pagemap_fd, int kpageflags_fd, int orders[], int nr_orders); int uffd_register(int uffd, void *addr, uint64_t len, bool miss, bool wp, bool minor); From 6dedaf0d46a96cb659ba57a959f5493dbab0de31 Mon Sep 17 00:00:00 2001 From: Baolin Wang Date: Thu, 6 Aug 2026 11:34:14 +0800 Subject: [PATCH 1031/1328] selftests: mm: implement the mTHP-sized hugepage check helpers Implement mTHP-sized hugepage checking helpers using gather_folio_orders(). Also rename the existing PMD-sized huge page check function to __check_pmd_huge() for clarity. Link: https://lore.kernel.org/56b16691f605426b33b5cf47319233de6127a6b3.1785985999.git.baolin.wang@linux.alibaba.com Signed-off-by: Baolin Wang Reviewed-by: Nico Pache (Red Hat) Tested-by: Nico Pache (Red Hat) Acked-by: Kiryl Shutsemau (Meta) Reviewed-by: Zi Yan Cc: Barry Song Cc: David Hildenbrand Cc: Dev Jain Cc: Lance Yang Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Ryan Roberts Signed-off-by: Andrew Morton --- tools/testing/selftests/mm/vm_util.c | 76 ++++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 4 deletions(-) diff --git a/tools/testing/selftests/mm/vm_util.c b/tools/testing/selftests/mm/vm_util.c index 5a427f494cd8..13b5cff7dfe3 100644 --- a/tools/testing/selftests/mm/vm_util.c +++ b/tools/testing/selftests/mm/vm_util.c @@ -15,6 +15,9 @@ #define SMAP_FILE_PATH "/proc/self/smaps" #define STATUS_FILE_PATH "/proc/self/status" #define MAX_LINE_LENGTH 500 +#define PAGEMAP_PATH "/proc/self/pagemap" +#define KPAGEFLAGS_PATH "/proc/kpageflags" +#define MAX_NR_ORDERS 20 unsigned int __page_size; unsigned int __page_shift; @@ -348,7 +351,7 @@ char *__get_smap_entry(void *addr, const char *pattern, char *buf, size_t len) return entry; } -bool __check_huge(void *addr, char *pattern, int nr_hpages, +static bool __check_pmd_huge(void *addr, char *pattern, int nr_hpages, uint64_t hpage_size) { char buffer[MAX_LINE_LENGTH]; @@ -366,19 +369,84 @@ bool __check_huge(void *addr, char *pattern, int nr_hpages, return thp == (nr_hpages * (hpage_size >> 10)); } +static bool check_large_folios(void *addr, size_t len, int nr_hpages, + uint64_t hpage_size) +{ + int order = 0, pagesize = getpagesize(); + unsigned int nr_pages = hpage_size / pagesize; + int orders[MAX_NR_ORDERS], status; + int pagemap_fd, kpageflags_fd; + bool ret = false; + + if (!nr_pages) + ksft_exit_fail_msg("invalid hugepage size\n"); + + order = 31 - __builtin_clz(nr_pages); + if (!order || order >= MAX_NR_ORDERS) + ksft_exit_fail_msg("invalid order\n"); + + memset(orders, 0, sizeof(int) * MAX_NR_ORDERS); + pagemap_fd = open(PAGEMAP_PATH, O_RDONLY); + if (pagemap_fd == -1) + ksft_exit_fail_msg("read pagemap fail\n"); + + kpageflags_fd = open(KPAGEFLAGS_PATH, O_RDONLY); + if (kpageflags_fd == -1) { + close(pagemap_fd); + ksft_exit_fail_msg("read kpageflags fail\n"); + } + + status = gather_folio_orders(addr, len, pagemap_fd, + kpageflags_fd, orders, MAX_NR_ORDERS); + if (status) + goto out; + + if (orders[order] == nr_hpages) + ret = true; + +out: + close(pagemap_fd); + close(kpageflags_fd); + return ret; +} + bool check_huge_anon(void *addr, size_t len, int nr_hpages, uint64_t hpage_size) { - return __check_huge(addr, "AnonHugePages: ", nr_hpages, hpage_size); + uint64_t pmd_pagesize = read_pmd_pagesize(); + + if (!pmd_pagesize) + ksft_exit_fail_msg("reading PMD pagesize failed\n"); + + if (hpage_size == pmd_pagesize) + return __check_pmd_huge(addr, "AnonHugePages: ", nr_hpages, hpage_size); + + return check_large_folios(addr, len, nr_hpages, hpage_size); } bool check_huge_file(void *addr, size_t len, int nr_hpages, uint64_t hpage_size) { - return __check_huge(addr, "FilePmdMapped:", nr_hpages, hpage_size); + uint64_t pmd_pagesize = read_pmd_pagesize(); + + if (!pmd_pagesize) + ksft_exit_fail_msg("reading PMD pagesize failed\n"); + + if (hpage_size == pmd_pagesize) + return __check_pmd_huge(addr, "FilePmdMapped:", nr_hpages, hpage_size); + + return check_large_folios(addr, len, nr_hpages, hpage_size); } bool check_huge_shmem(void *addr, size_t len, int nr_hpages, uint64_t hpage_size) { - return __check_huge(addr, "ShmemPmdMapped:", nr_hpages, hpage_size); + uint64_t pmd_pagesize = read_pmd_pagesize(); + + if (!pmd_pagesize) + ksft_exit_fail_msg("reading PMD pagesize failed\n"); + + if (hpage_size == pmd_pagesize) + return __check_pmd_huge(addr, "ShmemPmdMapped:", nr_hpages, hpage_size); + + return check_large_folios(addr, len, nr_hpages, hpage_size); } int64_t allocate_transhuge(void *ptr, int pagemap_fd) From 76f134aabb623dfcec5c9cd9bece23365dd04cfd Mon Sep 17 00:00:00 2001 From: Baolin Wang Date: Thu, 6 Aug 2026 11:34:15 +0800 Subject: [PATCH 1032/1328] selftests: mm: add mTHP collapse test cases Added a new command 'mthp_khugepaged' for mTHP collapse, along with the '-c' parameter to specify the collapse order. Additionally, added mTHP collapse test cases for 'collapse_full', 'collapse_empty', and 'collapse_single_mthp' for anonymous folios. All khugepaged test cases passed. Link: https://lore.kernel.org/f260058520214a9611922a96326bc54ba282fb73.1785985999.git.baolin.wang@linux.alibaba.com Signed-off-by: Baolin Wang Reviewed-by: Nico Pache (Red Hat) Tested-by: Nico Pache (Red Hat) Acked-by: Kiryl Shutsemau (Meta) Acked-by: Zi Yan Cc: Barry Song Cc: David Hildenbrand Cc: Dev Jain Cc: Lance Yang Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Ryan Roberts Signed-off-by: Andrew Morton --- tools/testing/selftests/mm/khugepaged.c | 137 +++++++++++++++++++--- tools/testing/selftests/mm/run_vmtests.sh | 2 + 2 files changed, 120 insertions(+), 19 deletions(-) diff --git a/tools/testing/selftests/mm/khugepaged.c b/tools/testing/selftests/mm/khugepaged.c index c02d00846a79..8f221c792a28 100644 --- a/tools/testing/selftests/mm/khugepaged.c +++ b/tools/testing/selftests/mm/khugepaged.c @@ -26,9 +26,11 @@ #define BASE_ADDR ((void *)(1UL << 30)) static unsigned long hpage_pmd_size; +static int hpage_pmd_order; static unsigned long page_size; static int hpage_pmd_nr; static int anon_order; +static int collapse_order; #define PID_SMAPS "/proc/self/smaps" #define TEST_FILE "collapse_test_file" @@ -69,6 +71,7 @@ struct collapse_context { }; static struct collapse_context *khugepaged_context; +static struct collapse_context *mthp_khugepaged_context; static struct collapse_context *madvise_context; struct file_info { @@ -554,25 +557,25 @@ static void madvise_collapse(const char *msg, char *p, int nr_hpages, } #define TICK 500000 -static bool wait_for_scan(const char *msg, char *p, int nr_hpages, - struct mem_ops *ops) +static bool wait_for_scan(const char *msg, char *p, size_t len, + int nr_hpages, int collap_order, struct mem_ops *ops) { - size_t len = nr_hpages * hpage_pmd_size; + unsigned long hpage_size = page_size << collap_order; int full_scans; int timeout = 6; /* 3 seconds */ /* Sanity check */ - if (!ops->check_huge(p, len, 0, hpage_pmd_size)) + if (!ops->check_huge(p, len, 0, hpage_size)) ksft_exit_fail_msg("Unexpected huge page\n"); - madvise(p, nr_hpages * hpage_pmd_size, MADV_HUGEPAGE); + madvise(p, len, MADV_HUGEPAGE); /* Wait until the second full_scan completed */ full_scans = thp_read_num("khugepaged/full_scans") + 2; ksft_print_msg("%s...", msg); while (timeout--) { - if (ops->check_huge(p, len, nr_hpages, hpage_pmd_size)) + if (ops->check_huge(p, len, nr_hpages, hpage_size)) break; if (thp_read_num("khugepaged/full_scans") >= full_scans) break; @@ -595,7 +598,7 @@ static void khugepaged_collapse(const char *msg, char *p, int nr_hpages, if (!is_tmpfs(ops) && ops == &__read_write_file_write_ops) expect = false; - if (wait_for_scan(msg, p, nr_hpages, ops)) { + if (wait_for_scan(msg, p, len, nr_hpages, hpage_pmd_order, ops)) { if (expect) fail("Timeout"); else @@ -617,12 +620,62 @@ static void khugepaged_collapse(const char *msg, char *p, int nr_hpages, fail("Fail"); } +static void mthp_khugepaged_collapse(const char *msg, char *p, int nr_hpages, + struct mem_ops *ops, bool expect) +{ + unsigned long hpage_size = page_size << collapse_order; + struct thp_settings settings = *thp_current_settings(); + /* mTHP collpase only allocates PMD sized memory */ + size_t len = hpage_pmd_size; + + /* Set mTHP setting for mTHP collapse */ + if (ops == &__anon_ops) { + settings.thp_enabled = THP_NEVER; + settings.hugepages[collapse_order].enabled = THP_MADVISE; + } + + thp_push_settings(&settings); + + if (wait_for_scan(msg, p, len, nr_hpages, collapse_order, ops)) { + if (expect) + fail("Timeout"); + else + success("OK"); + + /* Restore THP settings for mTHP collapse. */ + thp_pop_settings(); + return; + } + + /* + * For file and shmem memory, khugepaged only retracts pte entries after + * putting the new hugepage in the page cache. The hugepage must be + * subsequently refaulted to install the pmd mapping for the mm. + */ + if (ops != &__anon_ops) + ops->fault(p, 0, nr_hpages * hpage_size); + + if (ops->check_huge(p, len, expect ? nr_hpages : 0, hpage_size)) + success("OK"); + else + fail("Fail"); + + /* Restore THP settings for mTHP collapse. */ + thp_pop_settings(); +} + static struct collapse_context __khugepaged_context = { .collapse = &khugepaged_collapse, .enforce_pte_scan_limits = true, .name = "khugepaged", }; +static struct collapse_context __mthp_khugepaged_context = { + .collapse = &mthp_khugepaged_collapse, + .enforce_pte_scan_limits = true, + .name = "mthp_khugepaged", +}; + static struct collapse_context __madvise_context = { .collapse = &madvise_collapse, .enforce_pte_scan_limits = false, @@ -661,10 +714,17 @@ static void alloc_at_fault(void) static void collapse_full(struct collapse_context *c, struct mem_ops *ops) { void *p; - int nr_hpages = 4; + int nr_pmds = 4, nr_hpages = 4; unsigned long size = nr_hpages * hpage_pmd_size; - p = ops->setup_area(nr_hpages); + /* Only try 1 PMD sized range for mTHP collapse. */ + if (c == &__mthp_khugepaged_context) { + nr_pmds = 1; + nr_hpages = 1 << (hpage_pmd_order - collapse_order); + size = hpage_pmd_size; + } + + p = ops->setup_area(nr_pmds); ops->fault(p, 0, size); c->collapse("Collapse multiple fully populated PTE table", p, nr_hpages, ops, true); @@ -676,10 +736,31 @@ static void collapse_full(struct collapse_context *c, struct mem_ops *ops) static void collapse_empty(struct collapse_context *c, struct mem_ops *ops) { + int nr_hpages = 1; + void *p; + + if (c == &__mthp_khugepaged_context) + nr_hpages = 1 << (hpage_pmd_order - collapse_order); + + p = ops->setup_area(1); + c->collapse("Do not collapse empty PTE table", p, nr_hpages, ops, false); + ops->cleanup_area(p, hpage_pmd_size); + ksft_test_result_report(exit_status, "%s\n", __func__); +} + +static void collapse_single_mthp(struct collapse_context *c, struct mem_ops *ops) +{ + unsigned long hpage_size = page_size << collapse_order; void *p; p = ops->setup_area(1); - c->collapse("Do not collapse empty PTE table", p, 1, ops, false); + /* + * Only fault collapse_order sized ranges, and only check 1 + * collapse_order sized huge page. + */ + ops->fault(p, 0, hpage_size); + c->collapse("Collapse PTE table with half PTE entries present", + p, 1, ops, true); ops->cleanup_area(p, hpage_pmd_size); ksft_test_result_report(exit_status, "%s\n", __func__); } @@ -1081,8 +1162,8 @@ static void madvise_retracted_page_tables(struct collapse_context *c, ops->fault(p, 0, size); /* Let khugepaged collapse and leave pmd cleared */ - if (wait_for_scan("Collapse and leave PMD cleared", p, nr_hpages, - ops)) { + if (wait_for_scan("Collapse and leave PMD cleared", p, size, nr_hpages, + hpage_pmd_order, ops)) { fail("Timeout"); return; } @@ -1098,17 +1179,19 @@ static void usage(void) { fprintf(stderr, "\nUsage: ./khugepaged [OPTIONS] [dir]\n\n"); fprintf(stderr, "\t\t: :\n"); - fprintf(stderr, "\t\t: [all|khugepaged|madvise]\n"); + fprintf(stderr, "\t\t: [all|khugepaged|mthp_khugepaged|madvise]\n"); fprintf(stderr, "\t\t: [all|anon|file|shmem]\n"); fprintf(stderr, "\n\t\"file,all\" mem_type requires [dir] argument\n"); fprintf(stderr, "\n\t\"file,all\" mem_type requires a file system\n"); fprintf(stderr, "\twith PMD-sized large folio support\n"); fprintf(stderr, "\n\tif [dir] is a (sub)directory of a tmpfs mount, tmpfs must be\n"); fprintf(stderr, "\tmounted with huge=advise option for khugepaged tests to work\n"); + fprintf(stderr, "\n\tmthp_khugepaged only supports anon mem_type now.\n"); fprintf(stderr, "\n\tSupported Options:\n"); fprintf(stderr, "\t\t-h: This help message.\n"); fprintf(stderr, "\t\t-s: mTHP size, expressed as page order.\n"); fprintf(stderr, "\t\t Defaults to 0. Use this size for anon or shmem allocations.\n"); + fprintf(stderr, "\t\t-c: collapse order for mTHP collapse, expressed as page order.\n"); exit(1); } @@ -1118,11 +1201,14 @@ static void parse_test_type(int argc, char **argv) char *buf; const char *token; - while ((opt = getopt(argc, argv, "s:h")) != -1) { + while ((opt = getopt(argc, argv, "s:c:h")) != -1) { switch (opt) { case 's': anon_order = atoi(optarg); break; + case 'c': + collapse_order = atoi(optarg); + break; case 'h': default: usage(); @@ -1148,6 +1234,10 @@ static void parse_test_type(int argc, char **argv) madvise_context = &__madvise_context; } else if (!strcmp(token, "khugepaged")) { khugepaged_context = &__khugepaged_context; + } else if (!strcmp(token, "mthp_khugepaged")) { + mthp_khugepaged_context = &__mthp_khugepaged_context; + if (collapse_order <= 0 || collapse_order >= hpage_pmd_order) + usage(); } else if (!strcmp(token, "madvise")) { madvise_context = &__madvise_context; } else { @@ -1163,14 +1253,20 @@ static void parse_test_type(int argc, char **argv) read_write_file_write_ops = &__read_write_file_write_ops; anon_ops = &__anon_ops; shmem_ops = &__shmem_ops; + if (mthp_khugepaged_context) + usage(); } else if (!strcmp(buf, "anon")) { anon_ops = &__anon_ops; } else if (!strcmp(buf, "file")) { read_only_file_ops = &__read_only_file_ops; read_write_file_read_ops = &__read_write_file_read_ops; read_write_file_write_ops = &__read_write_file_write_ops; + if (mthp_khugepaged_context) + usage(); } else if (!strcmp(buf, "shmem")) { shmem_ops = &__shmem_ops; + if (mthp_khugepaged_context) + usage(); } else { usage(); } @@ -1213,7 +1309,6 @@ static int nr_test_cases; int main(int argc, char **argv) { - int hpage_pmd_order; struct thp_settings default_settings = { .thp_enabled = THP_MADVISE, .thp_defrag = THP_DEFRAG_ALWAYS, @@ -1239,10 +1334,6 @@ int main(int argc, char **argv) if (!thp_is_enabled()) ksft_exit_skip("Transparent Hugepages not available\n"); - parse_test_type(argc, argv); - - setbuf(stdout, NULL); - page_size = getpagesize(); hpage_pmd_size = read_pmd_pagesize(); if (!hpage_pmd_size) @@ -1250,6 +1341,10 @@ int main(int argc, char **argv) hpage_pmd_nr = hpage_pmd_size / page_size; hpage_pmd_order = __builtin_ctz(hpage_pmd_nr); + parse_test_type(argc, argv); + + setbuf(stdout, NULL); + default_settings.khugepaged.max_ptes_none = hpage_pmd_nr - 1; default_settings.khugepaged.max_ptes_swap = hpage_pmd_nr / 8; default_settings.khugepaged.max_ptes_shared = hpage_pmd_nr / 2; @@ -1267,6 +1362,7 @@ int main(int argc, char **argv) TEST(collapse_full, khugepaged_context, read_write_file_read_ops); TEST(collapse_full, khugepaged_context, read_write_file_write_ops); TEST(collapse_full, khugepaged_context, shmem_ops); + TEST(collapse_full, mthp_khugepaged_context, anon_ops); TEST(collapse_full, madvise_context, anon_ops); TEST(collapse_full, madvise_context, read_only_file_ops); TEST(collapse_full, madvise_context, read_write_file_read_ops); @@ -1274,8 +1370,11 @@ int main(int argc, char **argv) TEST(collapse_full, madvise_context, shmem_ops); TEST(collapse_empty, khugepaged_context, anon_ops); + TEST(collapse_empty, mthp_khugepaged_context, anon_ops); TEST(collapse_empty, madvise_context, anon_ops); + TEST(collapse_single_mthp, mthp_khugepaged_context, anon_ops); + TEST(collapse_single_pte_entry, khugepaged_context, anon_ops); TEST(collapse_single_pte_entry, khugepaged_context, read_only_file_ops); TEST(collapse_single_pte_entry, khugepaged_context, read_write_file_read_ops); diff --git a/tools/testing/selftests/mm/run_vmtests.sh b/tools/testing/selftests/mm/run_vmtests.sh index 687d115e3bd8..d09f9f6a384e 100755 --- a/tools/testing/selftests/mm/run_vmtests.sh +++ b/tools/testing/selftests/mm/run_vmtests.sh @@ -410,6 +410,8 @@ CATEGORY="thp" run_test ./khugepaged all:shmem CATEGORY="thp" run_test ./khugepaged -s 4 all:shmem +CATEGORY="thp" run_test ./khugepaged -c 4 mthp_khugepaged:anon + # Try to create XFS if not provided if [ -z "${SPLIT_HUGE_PAGE_TEST_XFS_PATH}" ]; then if test_selected "thp"; then From 4b82a0b91be5f8cf7a3a46d6fa3931224150b6b4 Mon Sep 17 00:00:00 2001 From: Hongfu Li Date: Thu, 6 Aug 2026 11:08:50 +0800 Subject: [PATCH 1033/1328] selftests/mm: drop duplicate test_seal_mprotect_two_vma_with_gap() call mseal_test main() invokes test_seal_mprotect_two_vma_with_gap() twice. The second run repeats all assertions with no benefit. Drop the duplicate call. Link: https://lore.kernel.org/20260806030850.76077-1-hongfu.li@linux.dev Signed-off-by: Hongfu Li Reviewed-by: Lorenzo Stoakes (ARM) Acked-by: David Hildenbrand (Arm) Reviewed-by: SJ Park Reviewed-by: Anshuman Khandual Cc: Liam R. Howlett Cc: Michal Hocko Cc: Mike Rapoport Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- tools/testing/selftests/mm/mseal_test.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tools/testing/selftests/mm/mseal_test.c b/tools/testing/selftests/mm/mseal_test.c index 93c2e13094d4..1a05e6921fed 100644 --- a/tools/testing/selftests/mm/mseal_test.c +++ b/tools/testing/selftests/mm/mseal_test.c @@ -1876,7 +1876,7 @@ int main(void) if (!pkey_supported()) ksft_print_msg("PKEY not supported\n"); - ksft_set_plan(88); + ksft_set_plan(87); test_seal_addseal(); test_seal_unmapped_start(); @@ -1913,7 +1913,6 @@ int main(void) test_seal_mprotect_partial_mprotect(false); test_seal_mprotect_partial_mprotect(true); - test_seal_mprotect_two_vma_with_gap(); test_seal_mprotect_two_vma_with_gap(); test_seal_mprotect_merge(false); From 54cc9b38470905e7fdf4a8786e9b1b85ac045f0a Mon Sep 17 00:00:00 2001 From: Ye Liu Date: Thu, 6 Aug 2026 08:45:55 +0800 Subject: [PATCH 1034/1328] mm: debug_page_alloc: fix NULL buf in debug_guardpage_minorder_setup If the kernel command line includes "debug_guardpage_minorder" without an equals sign (i.e., no value is provided), the early parameter parser passes a NULL buf pointer to the setup function. kstrtouint() does not perform a NULL check on its input and calls directly into kstrtoull() which dereferences s[0] unconditionally, leading to a NULL pointer dereference and early boot crash. Additionally, the error path's pr_err("%s", buf) would also crash with a NULL format argument. Link: https://lore.kernel.org/20260806004556.2633049-1-ye.liu@linux.dev Fixes: c0a32fc5a2e4 ("mm: more intensive memory corruption debugging") Signed-off-by: Ye Liu Acked-by: Zi Yan Reviewed-by: John Hubbard Reviewed-by: Andrew Morton Cc: Johannes Weiner Cc: Michal Hocko Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/debug_page_alloc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mm/debug_page_alloc.c b/mm/debug_page_alloc.c index 41e3d1f1ad96..fd2664c3c86a 100644 --- a/mm/debug_page_alloc.c +++ b/mm/debug_page_alloc.c @@ -22,8 +22,8 @@ static int __init debug_guardpage_minorder_setup(char *buf) { unsigned int res; - if (kstrtouint(buf, 10, &res) < 0 || res > MAX_PAGE_ORDER / 2) { - pr_err("Bad debug_guardpage_minorder value: %s\n", buf); + if (!buf || kstrtouint(buf, 10, &res) < 0 || res > MAX_PAGE_ORDER / 2) { + pr_err("Bad debug_guardpage_minorder value: %s\n", buf ?: "(missing)"); return 0; } _debug_guardpage_minorder = res; From dc924f0f85afd5a211357e2f2b670772853d1ce3 Mon Sep 17 00:00:00 2001 From: Audra Mitchell Date: Thu, 6 Aug 2026 11:00:34 -0400 Subject: [PATCH 1035/1328] selftests/mm/vm_util.c: correct __pagemap_scan_get_categories return value Currently __pagemap_scan_get_categories returns the result from the ioctl call which should be an int, not uint64_t. The ioctl may return -1 on error, which will be interpreted as UINT64_MAX. Adjust the return type to use the correct value. Link: https://lore.kernel.org/20260806150339.1824251-2-audra@redhat.com Signed-off-by: Audra Mitchell Reviewed-by: Liam R. Howlett (Oracle) Acked-by: David Hildenbrand (Arm) Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- tools/testing/selftests/mm/vm_util.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/mm/vm_util.c b/tools/testing/selftests/mm/vm_util.c index 13b5cff7dfe3..4fe4a5a610d1 100644 --- a/tools/testing/selftests/mm/vm_util.c +++ b/tools/testing/selftests/mm/vm_util.c @@ -34,7 +34,7 @@ uint64_t pagemap_get_entry(int fd, char *start) return entry; } -static uint64_t __pagemap_scan_get_categories(int fd, char *start, struct page_region *r) +static int __pagemap_scan_get_categories(int fd, char *start, struct page_region *r) { struct pm_scan_arg arg; @@ -58,7 +58,7 @@ static uint64_t __pagemap_scan_get_categories(int fd, char *start, struct page_r static uint64_t pagemap_scan_get_categories(int fd, char *start) { struct page_region r; - long ret; + int ret; ret = __pagemap_scan_get_categories(fd, start, &r); if (ret < 0) From 36a9799b2c86fd0f86d54f3eec8ea37ac467be25 Mon Sep 17 00:00:00 2001 From: Zhan Xusheng Date: Wed, 5 Aug 2026 15:05:29 +0800 Subject: [PATCH 1036/1328] maple_tree: remove unused mas_is_root_limits() The last callers of mas_is_root_limits() were removed by commit b8852ef30c67 ("maple_tree: remove maple big node and subtree structs"), together with the maple subtree state (mast_*) code that used it. As a static inline it does not trigger -Wunused-function, so it went unnoticed. Remove it. No functional change. Link: https://lore.kernel.org/20260805070529.4118794-1-zhanxusheng@xiaomi.com Signed-off-by: Zhan Xusheng Reviewed-by: Liam R. Howlett (Oracle) Cc: Alice Ryhl Cc: Andrew Ballance Signed-off-by: Andrew Morton --- lib/maple_tree.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/maple_tree.c b/lib/maple_tree.c index 529acc056e55..b48bc4064ad2 100644 --- a/lib/maple_tree.c +++ b/lib/maple_tree.c @@ -356,11 +356,6 @@ static __always_inline bool mte_is_root(const struct maple_enode *node) return ma_is_root(mte_to_node(node)); } -static inline bool mas_is_root_limits(const struct ma_state *mas) -{ - return !mas->min && mas->max == ULONG_MAX; -} - static __always_inline bool mt_is_alloc(struct maple_tree *mt) { return (mt->ma_flags & MT_FLAGS_ALLOC_RANGE); From 20d4d490bc0ca5fbfa3f99e1e8e7d398b7971ccb Mon Sep 17 00:00:00 2001 From: Henry Elderman Date: Fri, 7 Aug 2026 11:19:58 +0200 Subject: [PATCH 1037/1328] mm/execmem: fix fallback_end description in kernel-doc The kernel-doc for struct execmem_range incorrectly describes @fallback_end as "start". Correct it to "end". Link: https://lore.kernel.org/20260807091958.4735-1-henry.elderman.edu+linux@gmail.com Signed-off-by: Henry Elderman Reviewed-by: Mike Rapoport (Microsoft) Signed-off-by: Andrew Morton --- include/linux/execmem.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/execmem.h b/include/linux/execmem.h index 7de229134e30..1bd34925d1aa 100644 --- a/include/linux/execmem.h +++ b/include/linux/execmem.h @@ -89,7 +89,7 @@ static inline int execmem_restore_rox(void *ptr, size_t size) { return 0; } * @end: address space end (inclusive) * @fallback_start: start of the secondary address space range for fallback * allocations on architectures that require it - * @fallback_end: start of the secondary address space (inclusive) + * @fallback_end: end of the secondary address space (inclusive) * @pgprot: permissions for memory in this address space * @alignment: alignment required for text allocations * @flags: options for memory allocations for this range From 4004c130c358b1561a55323b0e747f20f5133f7b Mon Sep 17 00:00:00 2001 From: Muhammad Usama Anjum Date: Mon, 27 Jul 2026 10:52:17 +0100 Subject: [PATCH 1038/1328] selftests/mm: skip COW tmpfile cases when fallocate() is unsupported Patch series "selftests/mm: Handle unsupported and transient test conditions", v3. Several MM selftests report failures when the test environment lacks an underlying prerequisite, such as fallocate() support, MADV_REMOVE, local page-cache semantics, or swap. This series converts those unsupported cases to SKIP while preserving failures for unexpected errors. It also allows migration tests to retry transient move_pages() failures. This patch (of 4): The tmpfile-backed COW cases allocate a one-page file with fallocate() before exercising private and shared mappings. When the filesystem backing tmpfile() does not implement fallocate(), setup fails with EOPNOTSUPP and no COW behavior is exercised. This occurs when the temporary directory resides on a filesystem with limited allocation support, such as NFSv3. Reporting a failure adds noise because the test prerequisite is absent rather than the COW implementation being broken. Report EOPNOTSUPP as a skip. Continue treating every other fallocate() error as a failure so unexpected setup regressions remain visible. Link: https://lore.kernel.org/20260727095225.372655-1-usama.anjum@arm.com Link: https://lore.kernel.org/20260727095225.372655-2-usama.anjum@arm.com Fixes: f8664f3c4a08 ("selftests/vm: cow: basic COW tests for non-anonymous pages") Signed-off-by: Muhammad Usama Anjum Tested-by: Sarthak Sharma Acked-by: Usama Arif Acked-by: David Hildenbrand (Arm) Cc: Baolin Wang Cc: Barry Song Cc: Dev Jain Cc: Lance Yang Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Miaohe Lin Cc: Michal Hocko Cc: Mike Rapoport Cc: Naoya Horiguchi Cc: Nico Pache Cc: Ryan Roberts Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Zi Yan Signed-off-by: Andrew Morton --- tools/testing/selftests/mm/cow.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/mm/cow.c b/tools/testing/selftests/mm/cow.c index 7fa2d97ca9b2..8aa5249d9bef 100644 --- a/tools/testing/selftests/mm/cow.c +++ b/tools/testing/selftests/mm/cow.c @@ -1718,8 +1718,13 @@ static void run_with_tmpfile(non_anon_test_fn fn, const char *desc) /* File consists of a single page filled with zeroes. */ if (fallocate(fd, 0, 0, pagesize)) { - ksft_perror("fallocate() failed"); - log_test_result(KSFT_FAIL); + if (errno == EOPNOTSUPP) { + ksft_print_msg("fallocate() not supported by filesystem\n"); + log_test_result(KSFT_SKIP); + } else { + ksft_perror("fallocate() failed"); + log_test_result(KSFT_FAIL); + } goto close; } From e5220e4d934f8c06ef7734ed56bdd9b62307ea9c Mon Sep 17 00:00:00 2001 From: Muhammad Usama Anjum Date: Mon, 27 Jul 2026 10:52:18 +0100 Subject: [PATCH 1039/1328] selftests/mm: skip guard hole-punch test if MADV_REMOVE is unsupported The hole_punch case verifies that guard regions survive MADV_REMOVE and that the backing range is punched out. MADV_REMOVE delegates the hole punch to the backing filesystem, which may reject the operation with EOPNOTSUPP. That result means the test cannot establish the state whose guard semantics it intends to validate. Treating the missing filesystem capability as a guard-region failure creates a false regression. Unmap the range and skip only when MADV_REMOVE fails with EOPNOTSUPP. Preserve the assertion for all other errors so failures on supported configurations remain visible. Link: https://lore.kernel.org/20260727095225.372655-3-usama.anjum@arm.com Signed-off-by: Muhammad Usama Anjum Tested-by: Sarthak Sharma Acked-by: Usama Arif Acked-by: David Hildenbrand (Arm) Cc: Baolin Wang Cc: Barry Song Cc: Dev Jain Cc: Lance Yang Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Miaohe Lin Cc: Michal Hocko Cc: Mike Rapoport Cc: Naoya Horiguchi Cc: Nico Pache Cc: Ryan Roberts Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Zi Yan Signed-off-by: Andrew Morton --- tools/testing/selftests/mm/guard-regions.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/mm/guard-regions.c b/tools/testing/selftests/mm/guard-regions.c index b21df3040b1c..5c8ec3ca75d7 100644 --- a/tools/testing/selftests/mm/guard-regions.c +++ b/tools/testing/selftests/mm/guard-regions.c @@ -1912,7 +1912,7 @@ TEST_F(guard_regions, hole_punch) { const unsigned long page_size = self->page_size; char *ptr; - int i; + int i, ret; if (variant->backing == ANON_BACKED) SKIP(return, "Truncation test specific to file-backed"); @@ -1944,8 +1944,12 @@ TEST_F(guard_regions, hole_punch) } /* Now hole punch the guarded region. */ - ASSERT_EQ(madvise(&ptr[3 * page_size], 4 * page_size, - MADV_REMOVE), 0); + ret = madvise(&ptr[3 * page_size], 4 * page_size, MADV_REMOVE); + if (ret == -1 && errno == EOPNOTSUPP) { + ASSERT_EQ(munmap(ptr, 10 * page_size), 0); + SKIP(return, "MADV_REMOVE not supported by filesystem"); + } + ASSERT_EQ(ret, 0); /* Ensure guard regions remain. */ for (i = 0; i < 10; i++) { From e14e52a7ce02f6b62cecbf5c61425b4137422df2 Mon Sep 17 00:00:00 2001 From: Muhammad Usama Anjum Date: Mon, 27 Jul 2026 10:52:20 +0100 Subject: [PATCH 1040/1328] selftests/mm: skip hard dirty page-cache test on NFS The hard dirty_pagecache variant uses MADV_HWPOISON to exercise recovery of a dirty file-backed page. The recovery path records -EIO in the address_space mapping, which NFS later reports when the test closes the file. This makes the test fail after the hwpoison checks have completed. Skip this variant when the test file is on NFS. Keep the hard clean-page and both soft-offline variants enabled because they use folio removal, invalidation, or migration rather than recording a delayed writeback error. The unsupported-filesystem path in clean_pagecache() also returns without closing the opened test file. Close the descriptor before skipping there and in dirty_pagecache(). Link: https://lore.kernel.org/20260727095225.372655-5-usama.anjum@arm.com Signed-off-by: Muhammad Usama Anjum Reviewed-by: Miaohe Lin Cc: Baolin Wang Cc: Barry Song Cc: David Hildenbrand Cc: Dev Jain Cc: Lance Yang Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Naoya Horiguchi Cc: Nico Pache Cc: Ryan Roberts Cc: Sarthak Sharma Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Usama Arif Cc: Vlastimil Babka Cc: Zi Yan Signed-off-by: Andrew Morton --- tools/testing/selftests/mm/memory-failure.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/mm/memory-failure.c b/tools/testing/selftests/mm/memory-failure.c index 1a5a32e22cce..f3cb578b1609 100644 --- a/tools/testing/selftests/mm/memory-failure.c +++ b/tools/testing/selftests/mm/memory-failure.c @@ -287,8 +287,10 @@ TEST_F(memory_failure, clean_pagecache) if (fd < 0) SKIP(return, "failed to open test file.\n"); fs_type = get_fs_type(fd); - if (!fs_type || fs_type == TMPFS_MAGIC) + if (!fs_type || fs_type == TMPFS_MAGIC) { + close(fd); SKIP(return, "unsupported filesystem :%x\n", fs_type); + } addr = mmap(0, self->page_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); @@ -327,8 +329,16 @@ TEST_F(memory_failure, dirty_pagecache) if (fd < 0) SKIP(return, "failed to open test file.\n"); fs_type = get_fs_type(fd); - if (!fs_type || fs_type == TMPFS_MAGIC) + /* + * MADV_HARD poisoning of dirty page-cache data records an expected + * -EIO in the file mapping. NFS reports this error on close(), so + * skip this variant. + */ + if (!fs_type || fs_type == TMPFS_MAGIC || + (fs_type == NFS_SUPER_MAGIC && variant->type == MADV_HARD)) { + close(fd); SKIP(return, "unsupported filesystem :%x\n", fs_type); + } addr = mmap(0, self->page_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); From 746c94b7cb7900327a6ca7c1fcbfdd0243729253 Mon Sep 17 00:00:00 2001 From: Muhammad Usama Anjum Date: Mon, 27 Jul 2026 10:52:21 +0100 Subject: [PATCH 1041/1328] selftests/mm: retry migration failures for the full runtime move_pages() is best effort and can temporarily fail when concurrent faults race with page unmapping. A busy shared-anon workload can exhaust the current 100 retries long before the intended 20-second runtime and produce a false failure. Use the full runtime as the retry window. Since the initial page location is unknown, require it to reach both alternating NUMA targets to confirm that cross-node migration made progress despite transient contention. Link: https://lore.kernel.org/20260727095225.372655-6-usama.anjum@arm.com Signed-off-by: Muhammad Usama Anjum Cc: Baolin Wang Cc: Barry Song Cc: David Hildenbrand Cc: Dev Jain Cc: Lance Yang Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Miaohe Lin Cc: Michal Hocko Cc: Mike Rapoport Cc: Naoya Horiguchi Cc: Nico Pache Cc: Ryan Roberts Cc: Sarthak Sharma Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Usama Arif Cc: Vlastimil Babka Cc: Zi Yan Signed-off-by: Andrew Morton --- tools/testing/selftests/mm/migration.c | 37 ++++++++++++++------------ 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/tools/testing/selftests/mm/migration.c b/tools/testing/selftests/mm/migration.c index 29f7492453d4..f19d53c69576 100644 --- a/tools/testing/selftests/mm/migration.c +++ b/tools/testing/selftests/mm/migration.c @@ -7,7 +7,7 @@ #include "kselftest_harness.h" #include "hugepage_settings.h" -#include +#include #include #include #include @@ -20,7 +20,6 @@ #define TWOMEG (2<<20) #define RUNTIME (20) -#define MAX_RETRIES 100 #define ALIGN(x, a) (((x) + (a - 1)) & (~((a) - 1))) HUGETLB_SETUP_DEFAULT_PAGES(1) @@ -110,7 +109,7 @@ int migrate(uint64_t *ptr, int n1, int n2) int ret, tmp; int status = 0; struct timespec ts1, ts2; - int failures = 0; + int success = 0; if (clock_gettime(CLOCK_MONOTONIC, &ts1)) return -1; @@ -119,29 +118,33 @@ int migrate(uint64_t *ptr, int n1, int n2) if (clock_gettime(CLOCK_MONOTONIC, &ts2)) return -1; - if (ts2.tv_sec - ts1.tv_sec >= RUNTIME) - return 0; + if (ts2.tv_sec - ts1.tv_sec >= RUNTIME) { + /* Reaching both targets verifies a cross-node move. */ + if (success >= 2) + return 0; + else + return -2; + } ret = move_pages(0, 1, (void **) &ptr, &n2, &status, MPOL_MF_MOVE_ALL); - if (ret) { - if (ret > 0) { - /* Migration is best effort; try again */ - if (++failures < MAX_RETRIES) - continue; - printf("Didn't migrate %d pages\n", ret); - } - else - perror("Couldn't migrate pages"); + if (ret < 0) { + perror("Couldn't migrate pages"); + return ret; + } + /* Migration is best effort. Try again */ + if (ret > 0 || status < 0) + continue; + if (status != n2) { + printf("Page is on node %d instead of target node %d\n", + status, n2); return -2; } - failures = 0; + success++; tmp = n2; n2 = n1; n1 = tmp; } - - return 0; } void *access_mem(void *ptr) From dc8458f43fe964d8ade74c9b0fce54fe71d156de Mon Sep 17 00:00:00 2001 From: Hao Jia Date: Thu, 6 Aug 2026 15:09:42 +0800 Subject: [PATCH 1042/1328] mm/zswap: fix global shrinker when memory cgroup is disabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Patch series "mm/zswap: Fixes and improves the zswap shrink", v4. This series fixes and improves the zswap global shrinker (shrink_worker()): Patch 1: Fix missing global shrinker when memory cgroup is disabled. Patch 2: Extend shrink_memcg() to support batch writeback and thereby improving the writeback efficiency in the shrink_worker() and zswap_store() paths. This patch (of 2): Zswap writeback when the global pool limit is hit fails when memory cgroup is disabled. The pool remains full until it is organically drained by swapins or memory freeing, leading to zswap store failures and pages bypassing getting written directly to the backing swap device, causing LRU inversion (hotter pages with higher fault latency). This happens because mem_cgroup_iter() always returns NULL when memory cgroups are disabled. As a result, the global shrinker shrink_worker() repeatedly takes empty walks. After MAX_RECLAIM_RETRIES failed attempts, the worker gives up without writing back any pages. Therefore, when memory cgroup is disabled, fall through with the !memcg branch and shrink the root memcg directly. With memcg disabled, shrink_memcg() only returns -ENOENT when the root LRU is empty, which means the total pages are already below thr. In the absence of heavy concurrent zswap stores, the loop then safely bails out via the zswap_total_pages() <= thr check; otherwise, it will resume shrinking the memcg after processing the reschedule check. For any other return value from shrink_memcg(), the loop is guaranteed to terminate, either after MAX_RECLAIM_RETRIES failures or once the threshold is met. This is a potential performance regression for people using zswap without memcg that was introduced by the commit in "Fixes". Link: https://lore.kernel.org/20260806070943.95542-1-jiahao.kernel@gmail.com Link: https://lore.kernel.org/20260806070943.95542-2-jiahao.kernel@gmail.com Fixes: a65b0e7607cc ("zswap: make shrinking memcg-aware") Signed-off-by: Hao Jia Suggested-by: Nhat Pham Acked-by: Nhat Pham Acked-by: Yosry Ahmed Reported-by: Yosry Ahmed Cc: Chengming Zhou Cc: Johannes Weiner Cc: Michal Hocko Cc: Michal Koutný Cc: Muchun Song Cc: Roman Gushchin Cc: Shakeel Butt Cc: Tejun Heo Cc: Signed-off-by: Andrew Morton --- mm/zswap.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/mm/zswap.c b/mm/zswap.c index a810524c7621..cf824e2a6b38 100644 --- a/mm/zswap.c +++ b/mm/zswap.c @@ -1356,11 +1356,12 @@ static void shrink_worker(struct work_struct *w) } while (memcg && !mem_cgroup_tryget_online(memcg)); spin_unlock(&zswap_shrink_lock); - if (!memcg) { - /* - * Continue shrinking without incrementing failures if - * we found candidate memcgs in the last tree walk. - */ + /* + * A NULL memcg ends a full hierarchy pass (except when memcg is + * disabled, where it is always NULL: fall through to the root LRU). + * Count a failure only if the last pass found no candidates. + */ + if (!memcg && !mem_cgroup_disabled()) { if (!attempts && ++failures == MAX_RECLAIM_RETRIES) break; @@ -1379,7 +1380,7 @@ static void shrink_worker(struct work_struct *w) * and failures. */ if (ret == -ENOENT) - continue; + goto resched; ++attempts; if (ret && ++failures == MAX_RECLAIM_RETRIES) From 6f34b4126b8bb7b24c2d6b6541d51d9655287fb4 Mon Sep 17 00:00:00 2001 From: Hao Jia Date: Thu, 6 Aug 2026 15:09:43 +0800 Subject: [PATCH 1043/1328] mm/zswap: support batch writeback in shrink_memcg() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, shrink_memcg() writes back at most one entry per-node during its traversal. This makes shrink_worker() inefficient, as it must repeatedly re-enter shrink_memcg() to make any substantial progress. Under high memory pressure, this can cause the writeback speed to be too slow to keep up with refaults, leading to zswap store failures and forcing pages to skip zswap and go directly to disk, which results in an LRU inversion. To address this, extend the per-node scan budget in shrink_memcg() from a single entry to up to SWAP_CLUSTER_MAX pages, enabling batch writeback for both the shrink_worker() and zswap_store() paths. Test Setup: - Total memory: 32 GB, 1 NUMA node. - zswap settings: accept_threshold_percent=50, shrinker_enabled=N. Test Case 1: Set max_pool_percent=1, allocate 512MB of anonymous pages, and fill them with random data (to avoid compression). Then, use cgroup memory.reclaim to force a large amount of anonymous pages into zswap. At an interval of 2ms, allocate a 4K anonymous page where the first 4 bytes are random numbers and the rest are zeros, and then trigger reclamation of this 4K page through cgroup memory.reclaim. When the pool threshold is reached, shrink_memcg() will be triggered. The test data after running for 120s is as follows: Baseline Patched shrink_worker wakeups 5,363 169 shrink_memcg calls 11,373,201 350,703 written_back pages 40,212 40,241 zswap_store calls 161,190 163,753 store succeeded (ret=1) 102,743 117,183 store rejected (ret=0) 58,447 46,570 store reject rate ~36% ~28% pool_limit_hit delta 55,826 33,760 pswpout 98,659 86,811 pswpin 2 0 Test Case 2: We evaluated the following two sub-configurations using stress-ng inside a cgroup capped at memory.max=1G for 120 seconds: Test Case 2a (max_pool_percent=1): Continuously triggers the global zswap pool limit, thereby waking up shrink_worker() to perform asynchronous shrinking. Test Case 2b (zswap.max=320M, max_pool_percent=50): Continuously triggers the cgroup's zswap.max limit, thereby invoking synchronous shrinking. Command executed for both setups: bash -c 'echo $$ > /sys/fs/cgroup/zswaptest/cgroup.procs ; \ exec stress-ng --vm 4 --vm-bytes 4G --vm-keep --vm-method rand-set -t \ 120s -q' Test Case 2a (max_pool_percent=1): Baseline Patched shrink_worker wakeups 5,640 1,308 shrink_memcg calls 8,481,500 3,140,972 written_back pages 260 468,216 zswap_store calls 2,742,756 2,011,269 store succeeded (ret=1) 934,640 947,988 store rejected (ret=0) 1,808,116 1,063,281 store reject rate ~66% ~52% pool_limit_hit delta 1,181,310 196,882 pswpout 1,808,376 1,531,497 pswpin 4,288,497 3,635,365 Test Case 2b (zswap.max=320M, max_pool_percent=50): Baseline Patched shrink_worker wakeups 0 0 shrink_memcg calls 687,608 54,002 written_back pages 639,176 846,663 zswap_store calls 1,224,222 1,228,548 store succeeded (ret=1) 992,816 1,208,123 store rejected (ret=0) 231,431 20,425 store reject rate ~19% ~2% pool_limit_hit delta 0 0 pswpout 870,745 867,360 pswpin 1,707,823 1,216,814 Under identical workloads and runtimes, batched zswap shrinking exhibits a significant reduction in both shrink_worker() wakeups and shrink_memcg() calls. Furthermore, the sharp drop in both pswpin and zswap_store() rejections demonstrates that batching zswap shrink operations effectively mitigates zswap_store() failures caused by hitting the pool limit. This significantly prevents pages from bypassing zswap and falling back directly to disk, thereby reducing LRU inversion. Link: https://lore.kernel.org/20260806070943.95542-3-jiahao.kernel@gmail.com Signed-off-by: Hao Jia Suggested-by: Yosry Ahmed Suggested-by: Johannes Weiner Acked-by: Yosry Ahmed Acked-by: Nhat Pham Cc: Chengming Zhou Cc: Michal Hocko Cc: Michal Koutný Cc: Muchun Song Cc: Roman Gushchin Cc: Shakeel Butt Cc: Tejun Heo Signed-off-by: Andrew Morton --- mm/zswap.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/mm/zswap.c b/mm/zswap.c index cf824e2a6b38..0b9435b4f57c 100644 --- a/mm/zswap.c +++ b/mm/zswap.c @@ -1275,6 +1275,14 @@ static struct shrinker *zswap_alloc_shrinker(void) return shrinker; } +/* + * Scan up to SWAP_CLUSTER_MAX pages on each per-node zswap LRU of @memcg + * and write back the reclaimable ones. + * + * Return: 0 if at least one entry was written back, -EAGAIN if entries + * were scanned but none could be written back, or -ENOENT if @memcg has + * writeback disabled, is a zombie cgroup, or has empty zswap LRUs. + */ static int shrink_memcg(struct mem_cgroup *memcg) { int nid, shrunk = 0, scanned = 0; @@ -1290,13 +1298,14 @@ static int shrink_memcg(struct mem_cgroup *memcg) return -ENOENT; for_each_node_state(nid, N_NORMAL_MEMORY) { - unsigned long nr_to_walk = 1; + unsigned long nr_to_walk = SWAP_CLUSTER_MAX; shrunk += list_lru_walk_one(&zswap_list_lru, nid, memcg, &shrink_memcg_cb, NULL, &nr_to_walk); - scanned += 1 - nr_to_walk; + scanned += SWAP_CLUSTER_MAX - nr_to_walk; } + /* Nothing was scanned: every LRU under @memcg was empty. */ if (!scanned) return -ENOENT; From 3774c56cc38b9ddcdf46dd717d4de954b40d8986 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Thu, 6 Aug 2026 09:58:38 +0300 Subject: [PATCH 1044/1328] drivers/base, mm: move arch_numa.c to mm/ arch_numa.c implements boot time discovery and initialization of NUMA topology on architectures that select GENERIC_ARCH_NUMA (currently arm64 and riscv). Since this is step in the initialization of the memory management subsystem, it's logical to have arch_numa.c in mm/ alongside numa.c, numa_memblks.c and numa_emulation.c. Move arch_numa.c to mm/ and add its F: entry to "MEMBLOCK AND MEMORY MANAGEMENT INITIALIZATION" in MAINTAINERS. Link: https://lore.kernel.org/20260806-arch-numa-v1-1-968ec128121e@kernel.org Signed-off-by: Mike Rapoport (Microsoft) Acked-by: Greg Kroah-Hartman Acked-by: Lorenzo Stoakes (ARM) Acked-by: Vlastimil Babka (SUSE) Acked-by: David Hildenbrand (Arm) Acked-by: Danilo Krummrich Cc: Albert Ou Cc: Alexandre Ghiti Cc: Catalin Marinas Cc: Liam R. Howlett Cc: Michal Hocko Cc: Palmer Dabbelt Cc: "Rafael J. Wysocki" Cc: Suren Baghdasaryan Cc: Will Deacon Signed-off-by: Andrew Morton --- MAINTAINERS | 1 + drivers/base/Kconfig | 7 ------- drivers/base/Makefile | 1 - mm/Kconfig | 7 +++++++ mm/Makefile | 1 + {drivers/base => mm}/arch_numa.c | 0 6 files changed, 9 insertions(+), 8 deletions(-) rename {drivers/base => mm}/arch_numa.c (100%) diff --git a/MAINTAINERS b/MAINTAINERS index e0ea1b915305..06271e742d32 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16880,6 +16880,7 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/rppt/memblock.git fixes F: Documentation/core-api/boot-time-mm.rst F: include/linux/kho/abi/memblock.h F: include/linux/memblock.h +F: mm/arch_numa.c F: mm/memblock.c F: mm/memtest.c F: mm/mm_init.c diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig index f7d385cbd3ba..1d93bfc34490 100644 --- a/drivers/base/Kconfig +++ b/drivers/base/Kconfig @@ -239,13 +239,6 @@ config GENERIC_ARCH_TOPOLOGY appropriate scaling, sysfs interface for reading capacity values at runtime. -config GENERIC_ARCH_NUMA - bool - select NUMA_MEMBLKS - help - Enable support for generic NUMA implementation. Currently, RISC-V - and ARM64 use it. - config FW_DEVLINK_SYNC_STATE_TIMEOUT bool "sync_state() behavior defaults to timeout instead of strict" help diff --git a/drivers/base/Makefile b/drivers/base/Makefile index 8074a10183dc..435710f643a5 100644 --- a/drivers/base/Makefile +++ b/drivers/base/Makefile @@ -25,7 +25,6 @@ obj-$(CONFIG_PINCTRL) += pinctrl.o obj-$(CONFIG_DEV_COREDUMP) += devcoredump.o obj-$(CONFIG_GENERIC_MSI_IRQ) += platform-msi.o obj-$(CONFIG_GENERIC_ARCH_TOPOLOGY) += arch_topology.o -obj-$(CONFIG_GENERIC_ARCH_NUMA) += arch_numa.o obj-$(CONFIG_ACPI) += physical_location.o obj-y += test/ diff --git a/mm/Kconfig b/mm/Kconfig index 331daf7fcfab..8a24c130d008 100644 --- a/mm/Kconfig +++ b/mm/Kconfig @@ -1463,6 +1463,13 @@ config NUMA_EMU into virtual nodes when booted with "numa=fake=N", where N is the number of nodes. This is only useful for debugging. +config GENERIC_ARCH_NUMA + bool + select NUMA_MEMBLKS + help + Enable support for generic NUMA implementation. Currently, RISC-V + and ARM64 use it. + config ARCH_HAS_USER_SHADOW_STACK bool help diff --git a/mm/Makefile b/mm/Makefile index ab37ef428d98..e7245cb88c66 100644 --- a/mm/Makefile +++ b/mm/Makefile @@ -122,6 +122,7 @@ obj-$(CONFIG_CMA) += cma.o obj-$(CONFIG_NUMA) += numa.o obj-$(CONFIG_NUMA_MEMBLKS) += numa_memblks.o obj-$(CONFIG_NUMA_EMU) += numa_emulation.o +obj-$(CONFIG_GENERIC_ARCH_NUMA) += arch_numa.o obj-$(CONFIG_BALLOON) += balloon.o obj-$(CONFIG_PAGE_EXTENSION) += page_ext.o obj-$(CONFIG_PAGE_TABLE_CHECK) += page_table_check.o diff --git a/drivers/base/arch_numa.c b/mm/arch_numa.c similarity index 100% rename from drivers/base/arch_numa.c rename to mm/arch_numa.c From 28b13c3c4c6592da8dd59a7b06655e39665ee624 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 7 Aug 2026 10:13:10 -0700 Subject: [PATCH 1045/1328] mm: make VM_FAULT_RESULT_TRACE compatible with sparse Fix the following sparse warnings that appear while building f2fs: ./include/trace/events/f2fs.h:1469:1: warning: incorrect type in initializer (different base types) ./include/trace/events/f2fs.h:1469:1: expected unsigned long mask ./include/trace/events/f2fs.h:1469:1: got restricted vm_fault_t Link: https://lore.kernel.org/e56c9e2aead04f79192c3110de80d846e41e3791.1786122711.git.bvanassche@acm.org Signed-off-by: Bart Van Assche Acked-by: Lorenzo Stoakes (ARM) Acked-by: David Hildenbrand (Arm) Reviewed-by: Anshuman Khandual Signed-off-by: Andrew Morton --- include/linux/mm_types.h | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h index ebf0d912be7d..6d815f6440c9 100644 --- a/include/linux/mm_types.h +++ b/include/linux/mm_types.h @@ -1718,20 +1718,20 @@ enum vm_fault_reason { VM_FAULT_SIGSEGV | VM_FAULT_HWPOISON | \ VM_FAULT_HWPOISON_LARGE | VM_FAULT_FALLBACK) -#define VM_FAULT_RESULT_TRACE \ - { VM_FAULT_OOM, "OOM" }, \ - { VM_FAULT_SIGBUS, "SIGBUS" }, \ - { VM_FAULT_MAJOR, "MAJOR" }, \ - { VM_FAULT_HWPOISON, "HWPOISON" }, \ - { VM_FAULT_HWPOISON_LARGE, "HWPOISON_LARGE" }, \ - { VM_FAULT_SIGSEGV, "SIGSEGV" }, \ - { VM_FAULT_NOPAGE, "NOPAGE" }, \ - { VM_FAULT_LOCKED, "LOCKED" }, \ - { VM_FAULT_RETRY, "RETRY" }, \ - { VM_FAULT_FALLBACK, "FALLBACK" }, \ - { VM_FAULT_DONE_COW, "DONE_COW" }, \ - { VM_FAULT_NEEDDSYNC, "NEEDDSYNC" }, \ - { VM_FAULT_COMPLETED, "COMPLETED" } +#define VM_FAULT_RESULT_TRACE \ + { (__force u32)VM_FAULT_OOM, "OOM" }, \ + { (__force u32)VM_FAULT_SIGBUS, "SIGBUS" }, \ + { (__force u32)VM_FAULT_MAJOR, "MAJOR" }, \ + { (__force u32)VM_FAULT_HWPOISON, "HWPOISON" }, \ + { (__force u32)VM_FAULT_HWPOISON_LARGE, "HWPOISON_LARGE" }, \ + { (__force u32)VM_FAULT_SIGSEGV, "SIGSEGV" }, \ + { (__force u32)VM_FAULT_NOPAGE, "NOPAGE" }, \ + { (__force u32)VM_FAULT_LOCKED, "LOCKED" }, \ + { (__force u32)VM_FAULT_RETRY, "RETRY" }, \ + { (__force u32)VM_FAULT_FALLBACK, "FALLBACK" }, \ + { (__force u32)VM_FAULT_DONE_COW, "DONE_COW" }, \ + { (__force u32)VM_FAULT_NEEDDSYNC, "NEEDDSYNC" }, \ + { (__force u32)VM_FAULT_COMPLETED, "COMPLETED" } struct vm_special_mapping { const char *name; /* The name, e.g. "[vdso]". */ From 34e0849142c317eed68f3a4818dd3808fb1c47db Mon Sep 17 00:00:00 2001 From: Sourav Panda Date: Fri, 7 Aug 2026 04:00:03 +0000 Subject: [PATCH 1046/1328] mm/hugetlb_cma: support percentage-based hugetlb_cma reservation Currently, hugetlb_cma reservation only supports absolute sizes (e.g., hugetlb_cma=2G or hugetlb_cma=0:1G,1:1G). This can be restrictive in heterogeneous environments or when deploying common kernel command lines across machines with different memory capacities. Add support for percentage-based hugetlb_cma reservation (e.g., hugetlb_cma=20% or hugetlb_cma=0:20%,1:10%). The percentage is calculated against the total memory (for global settings) or against the node-specific memory (for node-specific settings) using memblock APIs during early boot. Link: https://lore.kernel.org/20260807040003.2156630-1-souravpanda@google.com Signed-off-by: Sourav Panda Acked-by: Usama Arif Cc: David Hildenbrand Cc: David Rientjes Cc: Frank van der Linden Cc: Greg Thelen Cc: Muchun Song Cc: Oscar Salvador Cc: Suren Baghdasaryan Signed-off-by: Andrew Morton --- .../admin-guide/kernel-parameters.txt | 10 +- mm/hugetlb_cma.c | 142 +++++++++++++++++- 2 files changed, 142 insertions(+), 10 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 364c2dce8e70..1af62cd16c9d 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -2064,8 +2064,14 @@ Kernel parameters hugetlb_cma= [HW,CMA,EARLY] The size of a CMA area used for allocation of gigantic hugepages. Or using node format, the size of a CMA area per node can be specified. - Format: nn[KMGTPE] or (node format) - :nn[KMGTPE][,:nn[KMGTPE]] + The size can be an absolute value (e.g., 2G) or a + percentage of the total memory or node memory (e.g., 20%). + Percentage-derived sizes are rounded down to a multiple of + the architecture's gigantic hugepage size and may become + zero. + Format: nn[KMGTPE] or nn% or (node format) + :nn[KMGTPE][,:nn[KMGTPE]] or + :nn%[,:nn%] The size must be a multiple of the gigantic page size. When using node format, this applies to each per-node size. diff --git a/mm/hugetlb_cma.c b/mm/hugetlb_cma.c index 4dfce68b354a..db0680e82847 100644 --- a/mm/hugetlb_cma.c +++ b/mm/hugetlb_cma.c @@ -9,6 +9,9 @@ #include #include +#include +#include +#include #include "internal.h" #include "hugetlb_cma.h" @@ -18,6 +21,28 @@ static unsigned long hugetlb_cma_size_in_node[MAX_NUMNODES] __initdata; static bool hugetlb_cma_only __ro_after_init; static unsigned long hugetlb_cma_size __ro_after_init; +static unsigned int hugetlb_cma_percent __initdata; +static unsigned int hugetlb_cma_percent_in_node[MAX_NUMNODES] __initdata; + +#ifdef CONFIG_NUMA +static phys_addr_t __init memblock_node_memory_size(int nid) +{ + struct memblock_region *reg; + phys_addr_t size = 0; + + for_each_mem_region(reg) { + if (reg->nid == nid) + size += reg->size; + } + return size; +} +#else +static phys_addr_t __init memblock_node_memory_size(int nid) +{ + return memblock_phys_mem_size(); +} +#endif + void hugetlb_cma_free_frozen_folio(struct folio *folio) { WARN_ON_ONCE(!cma_release_frozen(hugetlb_cma[folio_nid(folio)], @@ -90,14 +115,31 @@ static int __init cmdline_parse_hugetlb_cma(char *p) break; if (s[count] == ':') { + char *next; + if (tmp >= MAX_NUMNODES) break; nid = array_index_nospec(tmp, MAX_NUMNODES); + hugetlb_cma_size = 0; + hugetlb_cma_percent = 0; + s += count + 1; - tmp = memparse(s, &s); - hugetlb_cma_size_in_node[nid] = tmp; - hugetlb_cma_size += tmp; + tmp = memparse(s, &next); + if (*next == '%') { + if (tmp > 100) { + pr_warn("hugetlb_cma: invalid percentage %lu for node %d\n", + tmp, nid); + break; + } + hugetlb_cma_percent_in_node[nid] = tmp; + hugetlb_cma_size_in_node[nid] = 0; + s = next + 1; + } else { + hugetlb_cma_size_in_node[nid] = tmp; + hugetlb_cma_percent_in_node[nid] = 0; + s = next; + } /* * Skip the separator if have one, otherwise @@ -108,7 +150,28 @@ static int __init cmdline_parse_hugetlb_cma(char *p) else break; } else { - hugetlb_cma_size = memparse(p, &p); + char *next; + + tmp = memparse(p, &next); + if (*next == '%') { + if (tmp > 100) { + pr_warn("hugetlb_cma: invalid percentage %lu\n", tmp); + } else { + hugetlb_cma_percent = tmp; + hugetlb_cma_size = 0; + for (nid = 0; nid < MAX_NUMNODES; nid++) { + hugetlb_cma_size_in_node[nid] = 0; + hugetlb_cma_percent_in_node[nid] = 0; + } + } + } else { + hugetlb_cma_size = tmp; + hugetlb_cma_percent = 0; + for (nid = 0; nid < MAX_NUMNODES; nid++) { + hugetlb_cma_size_in_node[nid] = 0; + hugetlb_cma_percent_in_node[nid] = 0; + } + } break; } } @@ -134,8 +197,36 @@ void __init hugetlb_cma_reserve(void) { unsigned long size, reserved, per_node, order, gigantic_page_size; bool node_specific_cma_alloc = false; + bool has_node_specific_param = false; int nid; + for (nid = 0; nid < MAX_NUMNODES; nid++) { + if (hugetlb_cma_size_in_node[nid] || hugetlb_cma_percent_in_node[nid]) { + has_node_specific_param = true; + break; + } + } + + if (has_node_specific_param) { + hugetlb_cma_size = 0; + for (nid = 0; nid < MAX_NUMNODES; nid++) { + if (hugetlb_cma_percent_in_node[nid]) { + phys_addr_t node_gfp_mem = memblock_node_memory_size(nid); + u64 s; + + s = mul_u64_u32_div((u64)node_gfp_mem, + hugetlb_cma_percent_in_node[nid], + 100); + + hugetlb_cma_size_in_node[nid] = s; + } + hugetlb_cma_size += hugetlb_cma_size_in_node[nid]; + } + } else if (hugetlb_cma_percent) { + hugetlb_cma_size = mul_u64_u32_div((u64)memblock_phys_mem_size(), + hugetlb_cma_percent, 100); + } + if (!hugetlb_cma_size) return; @@ -154,6 +245,32 @@ void __init hugetlb_cma_reserve(void) VM_WARN_ON(order <= MAX_PAGE_ORDER); gigantic_page_size = PAGE_SIZE << order; + if (hugetlb_cma_percent) { + unsigned long orig_size = hugetlb_cma_size; + + hugetlb_cma_size = ALIGN_DOWN(hugetlb_cma_size, PAGE_SIZE << order); + if (orig_size && !hugetlb_cma_size) + pr_warn("hugetlb_cma: reservation size rounded down to 0 from %lu MiB (%u%%)\n", + orig_size / SZ_1M, hugetlb_cma_percent); + } else if (has_node_specific_param) { + hugetlb_cma_size = 0; + for (nid = 0; nid < MAX_NUMNODES; nid++) { + if (hugetlb_cma_percent_in_node[nid]) { + unsigned long orig_size = hugetlb_cma_size_in_node[nid]; + + hugetlb_cma_size_in_node[nid] = + ALIGN_DOWN(hugetlb_cma_size_in_node[nid], + PAGE_SIZE << order); + if (orig_size && !hugetlb_cma_size_in_node[nid]) + pr_warn("hugetlb_cma: reservation size rounded down to 0 from %lu MiB (%u%%) on node %d\n", + orig_size / SZ_1M, + hugetlb_cma_percent_in_node[nid], + nid); + } + hugetlb_cma_size += hugetlb_cma_size_in_node[nid]; + } + } + hugetlb_bootmem_set_nodes(); for (nid = 0; nid < MAX_NUMNODES; nid++) { @@ -194,8 +311,13 @@ void __init hugetlb_cma_reserve(void) per_node = DIV_ROUND_UP(hugetlb_cma_size, nodes_weight(hugetlb_bootmem_nodes)); per_node = round_up(per_node, gigantic_page_size); - pr_info("hugetlb_cma: reserve %lu MiB, up to %lu MiB per node\n", - hugetlb_cma_size / SZ_1M, per_node / SZ_1M); + if (hugetlb_cma_percent) + pr_info("hugetlb_cma: reserve %lu MiB (%u%%), up to %lu MiB per node\n", + hugetlb_cma_size / SZ_1M, hugetlb_cma_percent, + per_node / SZ_1M); + else + pr_info("hugetlb_cma: reserve %lu MiB, up to %lu MiB per node\n", + hugetlb_cma_size / SZ_1M, per_node / SZ_1M); } reserved = 0; @@ -230,8 +352,12 @@ void __init hugetlb_cma_reserve(void) } reserved += size; - pr_info("hugetlb_cma: reserved %lu MiB on node %d\n", - size / SZ_1M, nid); + if (hugetlb_cma_percent_in_node[nid]) + pr_info("hugetlb_cma: reserved %lu MiB (%u%%) on node %d\n", + size / SZ_1M, hugetlb_cma_percent_in_node[nid], nid); + else + pr_info("hugetlb_cma: reserved %lu MiB on node %d\n", + size / SZ_1M, nid); if (reserved >= hugetlb_cma_size) break; From 4050b5b0b60c93160a816d68ea1eb9ae92739a73 Mon Sep 17 00:00:00 2001 From: Hongfu Li Date: Fri, 7 Aug 2026 09:35:55 +0800 Subject: [PATCH 1047/1328] selftests/mm: fix read_file() return value check read_file() returns 0 on open/read failures and never returns negative values. Existing < 0 error checks never trigger, so read failures are silently ignored. Check for zero return to detect read_file() failures. Also fix misleading error message in get_finfo(). The error string incorrectly references read_num when reading uevent files. Link: https://lore.kernel.org/20260807013555.36525-1-hongfu.li@linux.dev Fixes: e0c13f9761df ("khugepaged: add self test") Signed-off-by: Hongfu Li Acked-by: David Hildenbrand (Arm) Cc: Baolin Wang Cc: Barry Song Cc: Dev Jain Cc: Lance Yang Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Ryan Roberts Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Zi Yan Signed-off-by: Andrew Morton --- tools/testing/selftests/mm/khugepaged.c | 4 ++-- tools/testing/selftests/mm/vm_util.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/mm/khugepaged.c b/tools/testing/selftests/mm/khugepaged.c index 8f221c792a28..d3a53673e1f9 100644 --- a/tools/testing/selftests/mm/khugepaged.c +++ b/tools/testing/selftests/mm/khugepaged.c @@ -141,8 +141,8 @@ static void get_finfo(const char *dir) major(path_stat.st_dev), minor(path_stat.st_dev)) >= sizeof(path)) ksft_exit_fail_msg("%s: Pathname is too long\n", __func__); - if (read_file(path, buf, sizeof(buf)) < 0) - ksft_exit_fail_perror("read_file(read_num)"); + if (!read_file(path, buf, sizeof(buf))) + ksft_exit_fail_perror("read_file(uevent)"); if (strstr(buf, "DEVTYPE=disk")) { /* Found it */ if (snprintf(finfo.dev_queue_read_ahead_path, diff --git a/tools/testing/selftests/mm/vm_util.c b/tools/testing/selftests/mm/vm_util.c index 4fe4a5a610d1..4821a3563036 100644 --- a/tools/testing/selftests/mm/vm_util.c +++ b/tools/testing/selftests/mm/vm_util.c @@ -942,7 +942,7 @@ unsigned long read_num(const char *path) { char buf[21]; - if (read_file(path, buf, sizeof(buf)) < 0) + if (!read_file(path, buf, sizeof(buf))) ksft_exit_fail_perror("read_file()"); return strtoul(buf, NULL, 10); From 22709abff9d0e3b0c61434cad58a9f7e86d68384 Mon Sep 17 00:00:00 2001 From: Ethan Nelson-Moore Date: Wed, 10 Jun 2026 18:22:44 -0700 Subject: [PATCH 1048/1328] mm: fix CONFIG_STACK_GROWSUP typo in tools/testing/vma/include/dup.h Commit 2b6a3f061f11 ("mm: declare VMA flags by bit") significantly refactored the header file include/linux/mm.h. In that step, it introduced a typo in an ifdef, referring to a non-existing config option STACK_GROWS_UP, whereas the actual config option is called STACK_GROWSUP. Commit 40a4af52e047 ("mm: fix CONFIG_STACK_GROWSUP typo in mm.h") fixed this typo in the mm.h header file, but did not update the copy of the code in tools/testing/vma/include/dup.h. Update this copy as well. Commit message adapted from the above-referenced fix to mm.h. Link: https://lore.kernel.org/20260611012258.432043-1-enelsonmoore@gmail.com Signed-off-by: Ethan Nelson-Moore Reviewed-by: Lorenzo Stoakes Cc: Alice Ryhl Cc: Jann Horn Cc: Liam R. Howlett Cc: Pedro Falcato Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- tools/testing/vma/include/dup.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/vma/include/dup.h b/tools/testing/vma/include/dup.h index 4655aecffaf3..4c58487b764e 100644 --- a/tools/testing/vma/include/dup.h +++ b/tools/testing/vma/include/dup.h @@ -243,7 +243,7 @@ enum { #define VM_NOHUGEPAGE INIT_VM_FLAG(NOHUGEPAGE) #define VM_MERGEABLE INIT_VM_FLAG(MERGEABLE) #define VM_STACK INIT_VM_FLAG(STACK) -#ifdef CONFIG_STACK_GROWS_UP +#ifdef CONFIG_STACK_GROWSUP #define VM_STACK_EARLY INIT_VM_FLAG(STACK_EARLY) #define VMA_STACK_EARLY mk_vma_flags(VMA_STACK_EARLY_BIT) #else From 1d581ab2348cdbb6d4d0a467382926b68e374ec9 Mon Sep 17 00:00:00 2001 From: Suren Baghdasaryan Date: Wed, 8 Jul 2026 18:01:23 +0000 Subject: [PATCH 1049/1328] alloc_tag: add ioctl to /proc/allocinfo Patch series "alloc_tag: introduce IOCTL-based filtering for MAP", v8. Currently, memory allocation profiling data is primarily exposed through /proc/allocinfo. While useful for manual inspection, this text-based interface poses challenges for production monitoring and large-scale analysis: 1. Userspace must parse large amounts of text to extract specific fields. 2. To find specific tags, userspace must read the entire dataset, requiring many context switches and high data copying. 3. The kernel currently aggregates per-CPU counters for every allocation size, even those the user intends to filter out immediately. This series introduces a new IOCTL-based binary interface for allocinfo that supports kernel-side filtering. By allowing the user to specify a filter mask, we significantly reduce the work performed in-kernel and the amount of data transferred to userspace. The IOCTL mechanism was chosen for allocinfo to address the per-CPU counter aggregation bottleneck. A traditional read() operation must report the total allocation count and sizes for every code tag in the system. Doing so requires iterating across all CPUs to sum their per-CPU counters for thousands of tags, which introduces substantial runtime overhead. The IOCTL interface allows userspace to push selective filtering criteria directly into the kernel before the per-CPU counter aggregation. The kernel aggregates per-CPU counters only for a small subset of tags that match the filter. This results in significant performance improvement. Beyond fast filtered retrieval, the IOCTL foundation allows introducing a context capture mechanism in the future to capture the context for specific allocations. Performance measurements were conducted on an Intel Xeon Platinum 8481C (224 CPUs) with caches dropped before each run. The IOCTL mechanism shows a ~20x performance improvement for filtered queries. The kernel avoids the expensive per-CPU counter aggregation (alloc_tag_read) for any tags that fail the initial string or location filters. Scenario 1: Specific File Filtering (arch/x86/events/rapl.c) 1. Traditional (cat /proc/allocinfo | grep): 22ms (sys) 2. IOCTL Interface: 1ms (sys) Scenario 2: Compound Filtering (Filename + Size) 1. Traditional: (cat ... | grep | awk): 21ms (sys) 2. IOCTL Interface: 1ms (sys) Scenario 3: Size-Based Filtering (min_size = 1MB) 1. Traditional: (cat ... | awk): 21ms (sys) 2. IOCTL Interface: 14ms (sys) This patch (of 6): Add the following ioctl commands for /proc/allocinfo file: ALLOCINFO_IOC_CONTENT_ID - gets content identifier which can be used to check whether the file content has changed specifically due to module load/unload. Every time a module is loaded / unloaded, the returned value will be different. By comparing the identifier value at the beginning and at the end of the content retrieval operation, users can validate retrieved information for consistency. ALLOCINFO_IOC_GET_AT - gets the record at the specified position. This is the position of a record in /proc/allocinfo. ALLOCINFO_IOC_GET_NEXT - gets the record next to the last retrieved one. If no records were previously retrieved, returns the first record. Note, function file and module names often have the same prefixes, therefore when filtering for them, we compare the last 64 characters to minimize the chances of name collisions. [akpm@linux-foundation.org: include compat.h, per Suren] Closes: https://lore.kernel.org/oe-kbuild-all/202607091820.qbjlGhKK-lkp@intel.com/ Link: https://lore.kernel.org/cover.1783532853.git.abhishekbapat@google.com Link: https://lore.kernel.org/15596de2607ef13e7c77c6d74763f4ae992ec475.1783532853.git.abhishekbapat@google.com Signed-off-by: Suren Baghdasaryan Signed-off-by: Abhishek Bapat Acked-by: Hao Ge Cc: Jonathan Corbet Cc: Kent Overstreet Cc: Sourav Panda Signed-off-by: Andrew Morton --- Documentation/mm/allocation-profiling.rst | 5 + .../userspace-api/ioctl/ioctl-number.rst | 2 + MAINTAINERS | 1 + include/linux/codetag.h | 2 + include/uapi/linux/alloc_tag.h | 65 +++++ lib/codetag.c | 18 ++ mm/alloc_tag.c | 239 +++++++++++++++++- 7 files changed, 330 insertions(+), 2 deletions(-) create mode 100644 include/uapi/linux/alloc_tag.h diff --git a/Documentation/mm/allocation-profiling.rst b/Documentation/mm/allocation-profiling.rst index e928aa3e4e1e..b2ebcef8af6f 100644 --- a/Documentation/mm/allocation-profiling.rst +++ b/Documentation/mm/allocation-profiling.rst @@ -57,6 +57,11 @@ sysctl: Runtime info: /proc/allocinfo + Profiling data can be retrieved either by reading `/proc/allocinfo` directly as + text or programmatically via `ioctl()` calls defined in ``. + The ioctl interface supports structured binary data extraction as well as filtering + by module name, function, file, line number, accuracy, or allocation size limits. + Example output:: root@moria-kvm:~# sort -g /proc/allocinfo|tail|numfmt --to=iec diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst index 3f0ef1e27eb0..2fc53093752d 100644 --- a/Documentation/userspace-api/ioctl/ioctl-number.rst +++ b/Documentation/userspace-api/ioctl/ioctl-number.rst @@ -346,6 +346,8 @@ Code Seq# Include File Comments 0xA5 20-2F linux/surface_aggregator/dtx.h Microsoft Surface DTX driver +0xA6 00-0F uapi/linux/alloc_tag.h Memory allocation profiling + 0xAA 00-3F linux/uapi/linux/userfaultfd.h 0xAB 00-1F linux/nbd.h 0xAC 00-1F linux/raw.h diff --git a/MAINTAINERS b/MAINTAINERS index 06271e742d32..557e5fd32073 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16940,6 +16940,7 @@ S: Maintained F: Documentation/mm/allocation-profiling.rst F: include/linux/alloc_tag.h F: include/linux/pgalloc_tag.h +F: include/uapi/linux/alloc_tag.h F: mm/alloc_tag.c MEMORY MANAGEMENT - BALLOON diff --git a/include/linux/codetag.h b/include/linux/codetag.h index ddae7484ca45..a25a085c2df1 100644 --- a/include/linux/codetag.h +++ b/include/linux/codetag.h @@ -77,6 +77,8 @@ struct codetag_iterator { void codetag_lock_module_list(struct codetag_type *cttype); bool codetag_trylock_module_list(struct codetag_type *cttype); void codetag_unlock_module_list(struct codetag_type *cttype); +unsigned long codetag_get_content_id(struct codetag_type *cttype); +unsigned int codetag_get_count(struct codetag_type *cttype); struct codetag_iterator codetag_get_ct_iter(struct codetag_type *cttype); struct codetag *codetag_next_ct(struct codetag_iterator *iter); diff --git a/include/uapi/linux/alloc_tag.h b/include/uapi/linux/alloc_tag.h new file mode 100644 index 000000000000..ee6a023cbaf4 --- /dev/null +++ b/include/uapi/linux/alloc_tag.h @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * alloc_tag IOCTL API definition + * + * Copyright (C) 2026 Google, LLC. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _UAPI_ALLOC_TAG_H +#define _UAPI_ALLOC_TAG_H + +#include + +/* + * Function, file and module names often have the same prefixes, therefore + * when filtering by these criteria, we compare the last 64 characters to + * minimize the chances of name collisions + */ +#define ALLOCINFO_STR_SIZE 64 + +struct allocinfo_content_id { + __u64 id; +}; + +struct allocinfo_tag { + /* Longer names are trimmed */ + char modname[ALLOCINFO_STR_SIZE]; + char function[ALLOCINFO_STR_SIZE]; + char filename[ALLOCINFO_STR_SIZE]; + __u64 lineno; +}; + +/* The alignment ensures 32-bit compatible interfaces are not broken */ +struct allocinfo_counter { + __u64 bytes; + __u64 calls; + __u8 accurate; +} __attribute__((aligned(8))); + +struct allocinfo_tag_data { + struct allocinfo_tag tag; + struct allocinfo_counter counter; +}; + +struct allocinfo_get_at { + __u64 pos; /* input */ + struct allocinfo_tag_data data; +}; + +#define _ALLOCINFO_IOC_CONTENT_ID 0 +#define _ALLOCINFO_IOC_GET_AT 1 +#define _ALLOCINFO_IOC_GET_NEXT 2 + +#define ALLOCINFO_IOC_BASE 0xA6 +#define ALLOCINFO_IOC_CONTENT_ID _IOR(ALLOCINFO_IOC_BASE, _ALLOCINFO_IOC_CONTENT_ID, \ + struct allocinfo_content_id) +#define ALLOCINFO_IOC_GET_AT _IOWR(ALLOCINFO_IOC_BASE, _ALLOCINFO_IOC_GET_AT, \ + struct allocinfo_get_at) +#define ALLOCINFO_IOC_GET_NEXT _IOR(ALLOCINFO_IOC_BASE, _ALLOCINFO_IOC_GET_NEXT, \ + struct allocinfo_tag_data) + +#endif /* _UAPI_ALLOC_TAG_H */ diff --git a/lib/codetag.c b/lib/codetag.c index 4001a7ea6675..a9cda4c962a3 100644 --- a/lib/codetag.c +++ b/lib/codetag.c @@ -19,6 +19,8 @@ struct codetag_type { struct codetag_type_desc desc; /* generates unique sequence number for module load */ unsigned long next_mod_seq; + /* bumped on every module load and unload */ + unsigned long content_id; }; struct codetag_range { @@ -50,6 +52,20 @@ void codetag_unlock_module_list(struct codetag_type *cttype) up_read(&cttype->mod_lock); } +unsigned long codetag_get_content_id(struct codetag_type *cttype) +{ + lockdep_assert_held(&cttype->mod_lock); + + return cttype->content_id; +} + +unsigned int codetag_get_count(struct codetag_type *cttype) +{ + lockdep_assert_held(&cttype->mod_lock); + + return cttype->count; +} + struct codetag_iterator codetag_get_ct_iter(struct codetag_type *cttype) { struct codetag_iterator iter = { @@ -204,6 +220,7 @@ static int codetag_module_init(struct codetag_type *cttype, struct module *mod) down_write(&cttype->mod_lock); cmod->mod_seq = ++cttype->next_mod_seq; + ++cttype->content_id; mod_id = idr_alloc(&cttype->mod_idr, cmod, 0, 0, GFP_KERNEL); if (mod_id >= 0) { if (cttype->desc.module_load) { @@ -368,6 +385,7 @@ void codetag_unload_module(struct module *mod) cttype->count -= range_size(cttype, &cmod->range); idr_remove(&cttype->mod_idr, mod_id); kfree(cmod); + ++cttype->content_id; } up_write(&cttype->mod_lock); if (found && cttype->desc.free_section_mem) diff --git a/mm/alloc_tag.c b/mm/alloc_tag.c index b60ee89704cc..b2ac166880ac 100644 --- a/mm/alloc_tag.c +++ b/mm/alloc_tag.c @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include #include #include @@ -14,6 +16,7 @@ #include #include #include +#include #include "internal.h" #include "page_alloc.h" @@ -59,6 +62,10 @@ struct allocinfo_private { struct codetag_iterator iter; struct codetag_iterator reported_iter; bool print_header; + /* ioctl uses a separate iterator not to interfere with reads */ + struct codetag_iterator ioctl_iter; + bool positioned; /* seq_open_private() sets to 0 */ + struct mutex ioctl_lock; }; static void *allocinfo_start(struct seq_file *m, loff_t *pos) @@ -142,6 +149,235 @@ static const struct seq_operations allocinfo_seq_op = { .show = allocinfo_show, }; +/* + * Initializes seq_file operations and allocates private state when opening + * the /proc/allocinfo procfs entry. + */ +static int allocinfo_open(struct inode *inode, struct file *file) +{ + int ret; + + ret = seq_open_private(file, &allocinfo_seq_op, + sizeof(struct allocinfo_private)); + if (!ret) { + struct seq_file *m = file->private_data; + struct allocinfo_private *priv = m->private; + + mutex_init(&priv->ioctl_lock); + } + return ret; +} + +/* + * Cleans up the seq_file state and frees up the private state allocated in + * allocinfo_open() when closing the /proc/allocinfo file descriptor. + */ +static int allocinfo_release(struct inode *inode, struct file *file) +{ + struct seq_file *m = file->private_data; + struct allocinfo_private *priv = m->private; + + mutex_destroy(&priv->ioctl_lock); + return seq_release_private(inode, file); +} + +/* + * Returns a pointer to the suffix of a string so that its length fits within + * ALLOCINFO_STR_SIZE, preserving the trailing characters. + * Function, file and module names often have the same prefixes, therefore + * when filtering by these criteria, we compare the last 64 characters to + * minimize the chances of name collisions + */ +static const char *allocinfo_str(const char *str) +{ + size_t len = strlen(str); + + /* Keep an extra space for the trailing NULL. */ + if (len >= ALLOCINFO_STR_SIZE) + str += (len - ALLOCINFO_STR_SIZE) + 1; + return str; +} + +/* Copy a string and trim from the beginning if it's too long */ +static void allocinfo_copy_str(char *dest, const char *src) +{ + strscpy_pad(dest, allocinfo_str(src), ALLOCINFO_STR_SIZE); +} + +/* + * Populates the UAPI allocinfo_tag_data structure with active runtime + * profiling counters extracted from the given kernel codetag. + */ +static void allocinfo_to_params(struct codetag *ct, + struct allocinfo_tag_data *data) +{ + struct alloc_tag *tag = ct_to_alloc_tag(ct); + struct alloc_tag_counters counter = alloc_tag_read(tag); + + if (ct->modname) + allocinfo_copy_str(data->tag.modname, ct->modname); + else + data->tag.modname[0] = '\0'; + allocinfo_copy_str(data->tag.function, ct->function); + allocinfo_copy_str(data->tag.filename, ct->filename); + data->tag.lineno = ct->lineno; + data->counter.bytes = counter.bytes; + data->counter.calls = counter.calls; + data->counter.accurate = !alloc_tag_is_inaccurate(tag); +} + +/* + * Retrieves the unique content ID representing the current allocation tag module + * layout, allowing userspace to detect if modules were loaded / unloaded. + */ +static int allocinfo_ioctl_get_content_id(struct seq_file *m, void __user *arg) +{ + struct allocinfo_content_id params; + + codetag_lock_module_list(alloc_tag_cttype); + params.id = codetag_get_content_id(alloc_tag_cttype); + codetag_unlock_module_list(alloc_tag_cttype); + if (copy_to_user(arg, ¶ms, sizeof(params))) + return -EFAULT; + + return 0; +} + +/* + * Seeks the ioctl iterator to the specified 0-indexed tag position, reads its + * profiling data and returns it to userspace. + */ +static int allocinfo_ioctl_get_at(struct seq_file *m, void __user *arg) +{ + struct allocinfo_private *priv; + struct codetag *ct; + __u64 pos; + struct allocinfo_get_at params = {0}; + + if (copy_from_user(¶ms, arg, sizeof(params))) + return -EFAULT; + + priv = m->private; + pos = params.pos; + + mutex_lock(&priv->ioctl_lock); + codetag_lock_module_list(alloc_tag_cttype); + + if (pos >= codetag_get_count(alloc_tag_cttype)) { + codetag_unlock_module_list(alloc_tag_cttype); + mutex_unlock(&priv->ioctl_lock); + return -ENOENT; + } + + /* Find the codetag */ + priv->ioctl_iter = codetag_get_ct_iter(alloc_tag_cttype); + ct = codetag_next_ct(&priv->ioctl_iter); + while (ct && pos--) + ct = codetag_next_ct(&priv->ioctl_iter); + if (ct) { + allocinfo_to_params(ct, ¶ms.data); + priv->positioned = true; + } + + codetag_unlock_module_list(alloc_tag_cttype); + mutex_unlock(&priv->ioctl_lock); + + if (!ct) + return -ENOENT; + + if (copy_to_user(arg, ¶ms, sizeof(params))) + return -EFAULT; + + return 0; +} + +/* + * Advances the ioctl iterator to the next allocation tag in the sequence and + * returns its profiling data to userspace. + */ +static int allocinfo_ioctl_get_next(struct seq_file *m, void __user *arg) +{ + struct allocinfo_private *priv; + struct codetag *ct; + struct allocinfo_tag_data params; + int ret = 0; + + memset(¶ms, 0, sizeof(params)); + priv = m->private; + + mutex_lock(&priv->ioctl_lock); + codetag_lock_module_list(alloc_tag_cttype); + + if (!priv->positioned) { + priv->ioctl_iter = codetag_get_ct_iter(alloc_tag_cttype); + priv->positioned = true; + } + + ct = codetag_next_ct(&priv->ioctl_iter); + if (ct) + allocinfo_to_params(ct, ¶ms); + + if (!ct) { + priv->positioned = false; + ret = -ENOENT; + } + codetag_unlock_module_list(alloc_tag_cttype); + mutex_unlock(&priv->ioctl_lock); + + if (ret == 0) { + if (copy_to_user(arg, ¶ms, sizeof(params))) + return -EFAULT; + } + return ret; +} + +/* + * Entry point ioctl function for /proc/allocinfo routing requests to fetch the + * layout content ID, seek to a specific tag, or read sequential tags. + */ +static long allocinfo_ioctl(struct file *file, unsigned int cmd, + unsigned long __arg) +{ + void __user *arg = (void __user *)__arg; + int ret; + + switch (cmd) { + case ALLOCINFO_IOC_CONTENT_ID: + ret = allocinfo_ioctl_get_content_id(file->private_data, arg); + break; + case ALLOCINFO_IOC_GET_AT: + ret = allocinfo_ioctl_get_at(file->private_data, arg); + break; + case ALLOCINFO_IOC_GET_NEXT: + ret = allocinfo_ioctl_get_next(file->private_data, arg); + break; + default: + ret = -ENOIOCTLCMD; + break; + } + + return ret; +} + +#ifdef CONFIG_COMPAT +static long allocinfo_compat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + return allocinfo_ioctl(file, cmd, (unsigned long)compat_ptr(arg)); +} +#endif + +static const struct proc_ops allocinfo_proc_ops = { + .proc_open = allocinfo_open, + .proc_read_iter = seq_read_iter, + .proc_lseek = seq_lseek, + .proc_release = allocinfo_release, + .proc_ioctl = allocinfo_ioctl, +#ifdef CONFIG_COMPAT + .proc_compat_ioctl = allocinfo_compat_ioctl, +#endif +}; + size_t alloc_tag_top_users(struct codetag_bytes *tags, size_t count, bool can_sleep) { struct codetag_iterator iter; @@ -999,8 +1235,7 @@ static int __init alloc_tag_init(void) return 0; } - if (!proc_create_seq_private(ALLOCINFO_FILE_NAME, 0400, NULL, &allocinfo_seq_op, - sizeof(struct allocinfo_private), NULL)) { + if (!proc_create(ALLOCINFO_FILE_NAME, 0400, NULL, &allocinfo_proc_ops)) { pr_err("Failed to create %s file\n", ALLOCINFO_FILE_NAME); shutdown_mem_profiling(false); return -ENOMEM; From 5732a4e4c18acac152768df9ce48e057232edbf4 Mon Sep 17 00:00:00 2001 From: Abhishek Bapat Date: Wed, 8 Jul 2026 18:01:24 +0000 Subject: [PATCH 1050/1328] alloc_tag: add ioctl filters to /proc/allocinfo Extend the capability of the IOCTL mechanism to filter allocations based on tag's module name, function name, file name and line number. Link: https://lore.kernel.org/6a6100c0c58cb2911f39126b9fe177a8c17db16f.1783532853.git.abhishekbapat@google.com Signed-off-by: Abhishek Bapat Acked-by: Hao Ge Acked-by: Suren Baghdasaryan Cc: Jonathan Corbet Cc: Kent Overstreet Cc: Sourav Panda Signed-off-by: Andrew Morton --- include/uapi/linux/alloc_tag.h | 26 ++++++++++++- mm/alloc_tag.c | 68 ++++++++++++++++++++++++++++++++-- 2 files changed, 89 insertions(+), 5 deletions(-) diff --git a/include/uapi/linux/alloc_tag.h b/include/uapi/linux/alloc_tag.h index ee6a023cbaf4..13e9b5916bf5 100644 --- a/include/uapi/linux/alloc_tag.h +++ b/include/uapi/linux/alloc_tag.h @@ -45,8 +45,32 @@ struct allocinfo_tag_data { struct allocinfo_counter counter; }; +enum { + ALLOCINFO_FILTER_MODNAME, + ALLOCINFO_FILTER_FUNCTION, + ALLOCINFO_FILTER_FILENAME, + ALLOCINFO_FILTER_LINENO, + __ALLOCINFO_FILTER_LAST = ALLOCINFO_FILTER_LINENO +}; + +#define ALLOCINFO_FILTER_MASK_MODNAME (1 << ALLOCINFO_FILTER_MODNAME) +#define ALLOCINFO_FILTER_MASK_FUNCTION (1 << ALLOCINFO_FILTER_FUNCTION) +#define ALLOCINFO_FILTER_MASK_FILENAME (1 << ALLOCINFO_FILTER_FILENAME) +#define ALLOCINFO_FILTER_MASK_LINENO (1 << ALLOCINFO_FILTER_LINENO) + +#define ALLOCINFO_FILTER_MASKS \ + ((1 << (__ALLOCINFO_FILTER_LAST + 1)) - 1) + +struct allocinfo_filter { + __u64 mask; /* bitmask of the filter fields used */ + struct allocinfo_tag fields; +}; + struct allocinfo_get_at { - __u64 pos; /* input */ + /* inputs */ + __u64 pos; + struct allocinfo_filter filter; + /* output */ struct allocinfo_tag_data data; }; diff --git a/mm/alloc_tag.c b/mm/alloc_tag.c index b2ac166880ac..d7ed0034c49f 100644 --- a/mm/alloc_tag.c +++ b/mm/alloc_tag.c @@ -62,6 +62,7 @@ struct allocinfo_private { struct codetag_iterator iter; struct codetag_iterator reported_iter; bool print_header; + struct allocinfo_filter filter; /* ioctl uses a separate iterator not to interfere with reads */ struct codetag_iterator ioctl_iter; bool positioned; /* seq_open_private() sets to 0 */ @@ -204,6 +205,12 @@ static void allocinfo_copy_str(char *dest, const char *src) strscpy_pad(dest, allocinfo_str(src), ALLOCINFO_STR_SIZE); } +/* Compare two strings and only consider the trimmed suffix if s1 is too long */ +static int allocinfo_cmp_str(const char *str, const char *template) +{ + return strncmp(allocinfo_str(str), template, ALLOCINFO_STR_SIZE); +} + /* * Populates the UAPI allocinfo_tag_data structure with active runtime * profiling counters extracted from the given kernel codetag. @@ -243,6 +250,40 @@ static int allocinfo_ioctl_get_content_id(struct seq_file *m, void __user *arg) return 0; } +/* + * Verifies whether a given codetag satisfies the active filtering criteria by + * matching its characteristics against the specified filter. + */ +static bool matches_filter(struct codetag *ct, struct allocinfo_filter *filter) +{ + if (!filter || !filter->mask) + return true; + + if (filter->mask & ALLOCINFO_FILTER_MASK_MODNAME) { + /* user wants to filter by modname but ct->modname is NULL */ + if (!ct->modname) { + /* validate if user was attempting to filter for built-in allocations */ + if (filter->fields.modname[0] != '\0') + return false; + } else if (allocinfo_cmp_str(ct->modname, filter->fields.modname)) + return false; + } + + if ((filter->mask & ALLOCINFO_FILTER_MASK_FUNCTION) && + ct->function && allocinfo_cmp_str(ct->function, filter->fields.function)) + return false; + + if ((filter->mask & ALLOCINFO_FILTER_MASK_FILENAME) && + ct->filename && allocinfo_cmp_str(ct->filename, filter->fields.filename)) + return false; + + if ((filter->mask & ALLOCINFO_FILTER_MASK_LINENO) && + ct->lineno != filter->fields.lineno) + return false; + + return true; +} + /* * Seeks the ioctl iterator to the specified 0-indexed tag position, reads its * profiling data and returns it to userspace. @@ -251,29 +292,46 @@ static int allocinfo_ioctl_get_at(struct seq_file *m, void __user *arg) { struct allocinfo_private *priv; struct codetag *ct; - __u64 pos; struct allocinfo_get_at params = {0}; + __u64 skip_count; if (copy_from_user(¶ms, arg, sizeof(params))) return -EFAULT; + if (params.filter.mask & ~ALLOCINFO_FILTER_MASKS) + return -EINVAL; + priv = m->private; - pos = params.pos; mutex_lock(&priv->ioctl_lock); codetag_lock_module_list(alloc_tag_cttype); - if (pos >= codetag_get_count(alloc_tag_cttype)) { + if (params.pos >= codetag_get_count(alloc_tag_cttype)) { codetag_unlock_module_list(alloc_tag_cttype); mutex_unlock(&priv->ioctl_lock); return -ENOENT; } + skip_count = params.pos; + + if (params.filter.mask) + priv->filter = params.filter; + else + priv->filter.mask = 0; + /* Find the codetag */ priv->ioctl_iter = codetag_get_ct_iter(alloc_tag_cttype); ct = codetag_next_ct(&priv->ioctl_iter); - while (ct && pos--) + + while (ct) { + if (matches_filter(ct, &priv->filter)) { + if (skip_count == 0) + break; + skip_count--; + } ct = codetag_next_ct(&priv->ioctl_iter); + } + if (ct) { allocinfo_to_params(ct, ¶ms.data); priv->positioned = true; @@ -314,6 +372,8 @@ static int allocinfo_ioctl_get_next(struct seq_file *m, void __user *arg) } ct = codetag_next_ct(&priv->ioctl_iter); + while (ct && !matches_filter(ct, &priv->filter)) + ct = codetag_next_ct(&priv->ioctl_iter); if (ct) allocinfo_to_params(ct, ¶ms); From 6f6769ea88f89116e8d66a4ac77056e2e3b377c5 Mon Sep 17 00:00:00 2001 From: Abhishek Bapat Date: Wed, 8 Jul 2026 18:01:25 +0000 Subject: [PATCH 1051/1328] alloc_tag: add size-based filtering to ioctl Extend the allocinfo filtering mechanism to allow users to filter tags based on the total number of bytes allocated [min_size, max_size]. The size range is inclusive. Filtering by size involves retrieving allocinfo per-CPU counters, which is an expensive operation. Hence, the performance of size-based filtering will be worse than other filters. Link: https://lore.kernel.org/0a7653b70ae0d64e967fbea0e933bc35f8ac656e.1783532853.git.abhishekbapat@google.com Signed-off-by: Abhishek Bapat Acked-by: Hao Ge Acked-by: Suren Baghdasaryan Cc: Jonathan Corbet Cc: Kent Overstreet Cc: Sourav Panda Signed-off-by: Andrew Morton --- include/uapi/linux/alloc_tag.h | 8 ++++- mm/alloc_tag.c | 64 +++++++++++++++++++++++++++------- 2 files changed, 58 insertions(+), 14 deletions(-) diff --git a/include/uapi/linux/alloc_tag.h b/include/uapi/linux/alloc_tag.h index 13e9b5916bf5..0de5fc180790 100644 --- a/include/uapi/linux/alloc_tag.h +++ b/include/uapi/linux/alloc_tag.h @@ -50,13 +50,17 @@ enum { ALLOCINFO_FILTER_FUNCTION, ALLOCINFO_FILTER_FILENAME, ALLOCINFO_FILTER_LINENO, - __ALLOCINFO_FILTER_LAST = ALLOCINFO_FILTER_LINENO + ALLOCINFO_FILTER_MIN_SIZE, + ALLOCINFO_FILTER_MAX_SIZE, + __ALLOCINFO_FILTER_LAST = ALLOCINFO_FILTER_MAX_SIZE }; #define ALLOCINFO_FILTER_MASK_MODNAME (1 << ALLOCINFO_FILTER_MODNAME) #define ALLOCINFO_FILTER_MASK_FUNCTION (1 << ALLOCINFO_FILTER_FUNCTION) #define ALLOCINFO_FILTER_MASK_FILENAME (1 << ALLOCINFO_FILTER_FILENAME) #define ALLOCINFO_FILTER_MASK_LINENO (1 << ALLOCINFO_FILTER_LINENO) +#define ALLOCINFO_FILTER_MASK_MIN_SIZE (1 << ALLOCINFO_FILTER_MIN_SIZE) +#define ALLOCINFO_FILTER_MASK_MAX_SIZE (1 << ALLOCINFO_FILTER_MAX_SIZE) #define ALLOCINFO_FILTER_MASKS \ ((1 << (__ALLOCINFO_FILTER_LAST + 1)) - 1) @@ -64,6 +68,8 @@ enum { struct allocinfo_filter { __u64 mask; /* bitmask of the filter fields used */ struct allocinfo_tag fields; + __u64 min_size; + __u64 max_size; }; struct allocinfo_get_at { diff --git a/mm/alloc_tag.c b/mm/alloc_tag.c index d7ed0034c49f..04b640d74bcc 100644 --- a/mm/alloc_tag.c +++ b/mm/alloc_tag.c @@ -211,16 +211,20 @@ static int allocinfo_cmp_str(const char *str, const char *template) return strncmp(allocinfo_str(str), template, ALLOCINFO_STR_SIZE); } +/* Fetch the per-CPU counters */ +static inline struct alloc_tag_counters allocinfo_prefetch_counters(struct codetag *ct) +{ + return alloc_tag_read(ct_to_alloc_tag(ct)); +} + /* * Populates the UAPI allocinfo_tag_data structure with active runtime * profiling counters extracted from the given kernel codetag. */ static void allocinfo_to_params(struct codetag *ct, - struct allocinfo_tag_data *data) + struct allocinfo_tag_data *data, + struct alloc_tag_counters *counters) { - struct alloc_tag *tag = ct_to_alloc_tag(ct); - struct alloc_tag_counters counter = alloc_tag_read(tag); - if (ct->modname) allocinfo_copy_str(data->tag.modname, ct->modname); else @@ -228,9 +232,9 @@ static void allocinfo_to_params(struct codetag *ct, allocinfo_copy_str(data->tag.function, ct->function); allocinfo_copy_str(data->tag.filename, ct->filename); data->tag.lineno = ct->lineno; - data->counter.bytes = counter.bytes; - data->counter.calls = counter.calls; - data->counter.accurate = !alloc_tag_is_inaccurate(tag); + data->counter.bytes = counters->bytes; + data->counter.calls = counters->calls; + data->counter.accurate = !alloc_tag_is_inaccurate(ct_to_alloc_tag(ct)); } /* @@ -254,7 +258,9 @@ static int allocinfo_ioctl_get_content_id(struct seq_file *m, void __user *arg) * Verifies whether a given codetag satisfies the active filtering criteria by * matching its characteristics against the specified filter. */ -static bool matches_filter(struct codetag *ct, struct allocinfo_filter *filter) +static bool matches_filter(struct codetag *ct, struct allocinfo_filter *filter, + struct alloc_tag_counters *counters, + bool *fetched_counters) { if (!filter || !filter->mask) return true; @@ -281,6 +287,19 @@ static bool matches_filter(struct codetag *ct, struct allocinfo_filter *filter) ct->lineno != filter->fields.lineno) return false; + if (filter->mask & (ALLOCINFO_FILTER_MASK_MIN_SIZE | ALLOCINFO_FILTER_MASK_MAX_SIZE)) { + if (!*fetched_counters) { + *counters = allocinfo_prefetch_counters(ct); + *fetched_counters = true; + } + if ((filter->mask & ALLOCINFO_FILTER_MASK_MIN_SIZE) && + counters->bytes < filter->min_size) + return false; + if ((filter->mask & ALLOCINFO_FILTER_MASK_MAX_SIZE) && + counters->bytes > filter->max_size) + return false; + } + return true; } @@ -294,6 +313,8 @@ static int allocinfo_ioctl_get_at(struct seq_file *m, void __user *arg) struct codetag *ct; struct allocinfo_get_at params = {0}; __u64 skip_count; + struct alloc_tag_counters counters; + bool fetched_counters; if (copy_from_user(¶ms, arg, sizeof(params))) return -EFAULT; @@ -301,6 +322,11 @@ static int allocinfo_ioctl_get_at(struct seq_file *m, void __user *arg) if (params.filter.mask & ~ALLOCINFO_FILTER_MASKS) return -EINVAL; + if ((params.filter.mask & ALLOCINFO_FILTER_MASK_MIN_SIZE) && + (params.filter.mask & ALLOCINFO_FILTER_MASK_MAX_SIZE) && + params.filter.min_size > params.filter.max_size) + return -EINVAL; + priv = m->private; mutex_lock(&priv->ioctl_lock); @@ -324,7 +350,8 @@ static int allocinfo_ioctl_get_at(struct seq_file *m, void __user *arg) ct = codetag_next_ct(&priv->ioctl_iter); while (ct) { - if (matches_filter(ct, &priv->filter)) { + fetched_counters = false; + if (matches_filter(ct, &priv->filter, &counters, &fetched_counters)) { if (skip_count == 0) break; skip_count--; @@ -333,7 +360,9 @@ static int allocinfo_ioctl_get_at(struct seq_file *m, void __user *arg) } if (ct) { - allocinfo_to_params(ct, ¶ms.data); + if (!fetched_counters) + counters = allocinfo_prefetch_counters(ct); + allocinfo_to_params(ct, ¶ms.data, &counters); priv->positioned = true; } @@ -359,6 +388,8 @@ static int allocinfo_ioctl_get_next(struct seq_file *m, void __user *arg) struct codetag *ct; struct allocinfo_tag_data params; int ret = 0; + struct alloc_tag_counters counters; + bool fetched_counters; memset(¶ms, 0, sizeof(params)); priv = m->private; @@ -372,11 +403,18 @@ static int allocinfo_ioctl_get_next(struct seq_file *m, void __user *arg) } ct = codetag_next_ct(&priv->ioctl_iter); - while (ct && !matches_filter(ct, &priv->filter)) + while (ct) { + fetched_counters = false; + if (matches_filter(ct, &priv->filter, &counters, &fetched_counters)) + break; ct = codetag_next_ct(&priv->ioctl_iter); - if (ct) - allocinfo_to_params(ct, ¶ms); + } + if (ct) { + if (!fetched_counters) + counters = allocinfo_prefetch_counters(ct); + allocinfo_to_params(ct, ¶ms, &counters); + } if (!ct) { priv->positioned = false; ret = -ENOENT; From 33588e0b81df2972922f2591767a4e3c16813ab9 Mon Sep 17 00:00:00 2001 From: Abhishek Bapat Date: Wed, 8 Jul 2026 18:01:26 +0000 Subject: [PATCH 1052/1328] alloc_tag: add accuracy based filtering to ioctl Extend the allocinfo filtering mechanism to allow users to filter tags based on their accuracy. [abhishekbapat@google.com: move `inaccurate` filtering criteria from `struct allocinfo_tag` to `struct allocinfo_filter`] Link: https://lore.kernel.org/e4e49ec4a5960292aeeb9e196526c18dc95228a2.1785867739.git.abhishekbapat@google.com Link: https://lore.kernel.org/396a5e4bc3b2990223ab355f2cd3ceb6aa15499e.1783532853.git.abhishekbapat@google.com Signed-off-by: Abhishek Bapat Acked-by: Hao Ge Acked-by: Suren Baghdasaryan Cc: Jonathan Corbet Cc: Kent Overstreet Cc: Sourav Panda Signed-off-by: Andrew Morton --- include/uapi/linux/alloc_tag.h | 4 ++++ mm/alloc_tag.c | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/include/uapi/linux/alloc_tag.h b/include/uapi/linux/alloc_tag.h index 0de5fc180790..e3ad94444864 100644 --- a/include/uapi/linux/alloc_tag.h +++ b/include/uapi/linux/alloc_tag.h @@ -50,6 +50,7 @@ enum { ALLOCINFO_FILTER_FUNCTION, ALLOCINFO_FILTER_FILENAME, ALLOCINFO_FILTER_LINENO, + ALLOCINFO_FILTER_INACCURATE, ALLOCINFO_FILTER_MIN_SIZE, ALLOCINFO_FILTER_MAX_SIZE, __ALLOCINFO_FILTER_LAST = ALLOCINFO_FILTER_MAX_SIZE @@ -59,6 +60,7 @@ enum { #define ALLOCINFO_FILTER_MASK_FUNCTION (1 << ALLOCINFO_FILTER_FUNCTION) #define ALLOCINFO_FILTER_MASK_FILENAME (1 << ALLOCINFO_FILTER_FILENAME) #define ALLOCINFO_FILTER_MASK_LINENO (1 << ALLOCINFO_FILTER_LINENO) +#define ALLOCINFO_FILTER_MASK_INACCURATE (1 << ALLOCINFO_FILTER_INACCURATE) #define ALLOCINFO_FILTER_MASK_MIN_SIZE (1 << ALLOCINFO_FILTER_MIN_SIZE) #define ALLOCINFO_FILTER_MASK_MAX_SIZE (1 << ALLOCINFO_FILTER_MAX_SIZE) @@ -70,6 +72,8 @@ struct allocinfo_filter { struct allocinfo_tag fields; __u64 min_size; __u64 max_size; + /* filter criteria only; see allocinfo_counter.accurate for actual accuracy */ + __u64 inaccurate; }; struct allocinfo_get_at { diff --git a/mm/alloc_tag.c b/mm/alloc_tag.c index 04b640d74bcc..b33410310477 100644 --- a/mm/alloc_tag.c +++ b/mm/alloc_tag.c @@ -262,6 +262,8 @@ static bool matches_filter(struct codetag *ct, struct allocinfo_filter *filter, struct alloc_tag_counters *counters, bool *fetched_counters) { + bool inaccurate; + if (!filter || !filter->mask) return true; @@ -287,6 +289,12 @@ static bool matches_filter(struct codetag *ct, struct allocinfo_filter *filter, ct->lineno != filter->fields.lineno) return false; + if (filter->mask & ALLOCINFO_FILTER_MASK_INACCURATE) { + inaccurate = !!(ct->flags & CODETAG_FLAG_INACCURATE); + if (inaccurate != !!(filter->inaccurate)) + return false; + } + if (filter->mask & (ALLOCINFO_FILTER_MASK_MIN_SIZE | ALLOCINFO_FILTER_MASK_MAX_SIZE)) { if (!*fetched_counters) { *counters = allocinfo_prefetch_counters(ct); From 2f252a7a6c90c94be042c10ca3b85f37038d1070 Mon Sep 17 00:00:00 2001 From: Abhishek Bapat Date: Wed, 8 Jul 2026 18:01:27 +0000 Subject: [PATCH 1053/1328] kselftest: alloc_tag: add kselftest for ioctl interface Introduce a kselftest to verify the new IOCTL-based interface for /proc/allocinfo. The test covers: 1. Validation of the filename filter. 2. Validation of the function filter. The first test validates the functionality of the filename filter. Using "mm/memory.c" as the candidate filename filter, it retrieves filtered entries from both procfs and ioctl and matches the first VEC_MAX_ENTRIES entries. The second test validates the functionality of the function filter. It uses "dup_mm" as the candidate function as we do not expect this function name to change frequently and hence won't be needing to modify this test often. Note that both the tests match line no, function name and file name fields. Bytes allocated and calls are not matched as those values may change in the time when the data is being read from procfs and ioctl and hence can lead to false negatives. [abhishekbapat@google.com: fix a typo in the selftest] Link: https://lore.kernel.org/e4e49ec4a5960292aeeb9e196526c18dc95228a2.1785867739.git.abhishekbapat@google.com Closes: https://sashiko.dev/#/patchset/cover.1783532853.git.abhishekbapat@google.com Link: https://lore.kernel.org/e2a3795677a14aeab249758ba570cd5e98402032.1783532853.git.abhishekbapat@google.com Signed-off-by: Abhishek Bapat Tested-by: Hao Ge Acked-by: Hao Ge Cc: Jonathan Corbet Cc: Kent Overstreet Cc: Sourav Panda Cc: Suren Baghdasaryan Signed-off-by: Andrew Morton --- MAINTAINERS | 1 + tools/testing/selftests/Makefile | 1 + tools/testing/selftests/alloc_tag/Makefile | 8 + .../alloc_tag/allocinfo_ioctl_test.c | 334 ++++++++++++++++++ 4 files changed, 344 insertions(+) create mode 100644 tools/testing/selftests/alloc_tag/Makefile create mode 100644 tools/testing/selftests/alloc_tag/allocinfo_ioctl_test.c diff --git a/MAINTAINERS b/MAINTAINERS index 557e5fd32073..4899b81bd839 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16942,6 +16942,7 @@ F: include/linux/alloc_tag.h F: include/linux/pgalloc_tag.h F: include/uapi/linux/alloc_tag.h F: mm/alloc_tag.c +F: tools/testing/selftests/alloc_tag/ MEMORY MANAGEMENT - BALLOON M: Andrew Morton diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index 5528682a3a91..2cc63e4134fb 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 TARGETS += acct +TARGETS += alloc_tag TARGETS += alsa TARGETS += amd-pstate TARGETS += arm64 diff --git a/tools/testing/selftests/alloc_tag/Makefile b/tools/testing/selftests/alloc_tag/Makefile new file mode 100644 index 000000000000..c4637f69e9c2 --- /dev/null +++ b/tools/testing/selftests/alloc_tag/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 + +TEST_GEN_PROGS := allocinfo_ioctl_test + +CFLAGS += -Wall +CFLAGS += $(KHDR_INCLUDES) + +include ../lib.mk diff --git a/tools/testing/selftests/alloc_tag/allocinfo_ioctl_test.c b/tools/testing/selftests/alloc_tag/allocinfo_ioctl_test.c new file mode 100644 index 000000000000..3614ee9b46fb --- /dev/null +++ b/tools/testing/selftests/alloc_tag/allocinfo_ioctl_test.c @@ -0,0 +1,334 @@ +// SPDX-License-Identifier: GPL-2.0-only + +/* kselftest for allocinfo ioctl + * allocinfo ioctl retrieves allocinfo data through ioctl + * Copyright (C) 2026 Google, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../kselftest.h" + +#define MAX_LINE_LEN 512 +#define ALLOCINFO_PROC "/proc/allocinfo" + +enum ioctl_ret { + IOCTL_SUCCESS = 0, + IOCTL_FAILURE = 1, + IOCTL_INVALID_DATA = 2, +}; + +#define VEC_MAX_ENTRIES 32 + +struct allocinfo_tag_data_vec { + struct allocinfo_tag_data tag[VEC_MAX_ENTRIES]; + __u64 count; +}; + +static inline int __allocinfo_get_content_id(int dev_fd, struct allocinfo_content_id *params) +{ + return ioctl(dev_fd, ALLOCINFO_IOC_CONTENT_ID, params); +} + +static inline int __allocinfo_get_at(int dev_fd, struct allocinfo_get_at *params) +{ + return ioctl(dev_fd, ALLOCINFO_IOC_GET_AT, params); +} + +static inline int __allocinfo_get_next(int dev_fd, struct allocinfo_tag_data *params) +{ + return ioctl(dev_fd, ALLOCINFO_IOC_GET_NEXT, params); +} + +static bool match_entry(const struct allocinfo_tag_data *procfs_entry, + const struct allocinfo_tag_data *tag_data, + bool match_bytes, bool match_calls, bool match_lineno, + bool match_function, bool match_filename) +{ + if (match_bytes && tag_data->counter.bytes != procfs_entry->counter.bytes) { + ksft_print_msg("size retrieved through ioctl does not match procfs\n"); + return false; + } + + if (match_calls && tag_data->counter.calls != procfs_entry->counter.calls) { + ksft_print_msg("call count retrieved through ioctl does not match procfs\n"); + return false; + } + + if (match_lineno && tag_data->tag.lineno != procfs_entry->tag.lineno) { + ksft_print_msg("lineno retrieved through ioctl does not match procfs\n"); + return false; + } + + if (match_function && + strncmp(tag_data->tag.function, procfs_entry->tag.function, ALLOCINFO_STR_SIZE)) { + ksft_print_msg("function retrieved through ioctl does not match procfs\n"); + return false; + } + + if (match_filename && + strncmp(tag_data->tag.filename, procfs_entry->tag.filename, ALLOCINFO_STR_SIZE)) { + ksft_print_msg("filename retrieved through ioctl does not match procfs\n"); + return false; + } + return true; +} + +static bool match_entries(const struct allocinfo_tag_data_vec *procfs_entries, + const struct allocinfo_tag_data_vec *tags, + bool match_bytes, bool match_calls, bool match_lineno, + bool match_function, bool match_filename) +{ + __u64 i; + + if (procfs_entries->count != tags->count) { + ksft_print_msg("Entry count mismatch. ioctl entries: %llu, proc entries: %llu\n", + tags->count, procfs_entries->count); + return false; + } + for (i = 0; i < procfs_entries->count; i++) { + if (!match_entry(&procfs_entries->tag[i], &tags->tag[i], + match_bytes, match_calls, match_lineno, + match_function, match_filename)) { + ksft_print_msg("%lluth entry does not match.\n", i); + return false; + } + } + return true; +} + +static const char *allocinfo_str(const char *str) +{ + size_t len = strlen(str); + + if (len >= ALLOCINFO_STR_SIZE) + str += (len - ALLOCINFO_STR_SIZE) + 1; + return str; +} + +static void allocinfo_copy_str(char *dest, const char *src) +{ + strncpy(dest, allocinfo_str(src), ALLOCINFO_STR_SIZE - 1); + dest[ALLOCINFO_STR_SIZE - 1] = '\0'; +} + +static int get_filtered_procfs_entries(struct allocinfo_tag_data_vec *procfs_entries, + const struct allocinfo_filter *filter) +{ + FILE *fp = fopen(ALLOCINFO_PROC, "r"); + char line[MAX_LINE_LEN]; + int matches; + struct allocinfo_tag_data procfs_entry; + + if (!fp) { + ksft_print_msg("Failed to open " ALLOCINFO_PROC " for reading\n"); + return 1; + } + memset(procfs_entries, 0, sizeof(*procfs_entries)); + while (fgets(line, sizeof(line), fp) && procfs_entries->count < VEC_MAX_ENTRIES) { + char filename[MAX_LINE_LEN]; + char function[MAX_LINE_LEN]; + + memset(&procfs_entry, 0, sizeof(procfs_entry)); + matches = sscanf(line, "%llu %llu %[^:]:%llu func:%s", + &procfs_entry.counter.bytes, + &procfs_entry.counter.calls, + filename, + &procfs_entry.tag.lineno, + function); + + if (matches != 5) + continue; + + allocinfo_copy_str(procfs_entry.tag.filename, filename); + allocinfo_copy_str(procfs_entry.tag.function, function); + + if (filter->mask & ALLOCINFO_FILTER_MASK_FILENAME) { + if (strncmp(procfs_entry.tag.filename, + filter->fields.filename, ALLOCINFO_STR_SIZE)) + continue; + } + if (filter->mask & ALLOCINFO_FILTER_MASK_FUNCTION) { + if (strncmp(procfs_entry.tag.function, + filter->fields.function, ALLOCINFO_STR_SIZE)) + continue; + } + if (filter->mask & ALLOCINFO_FILTER_MASK_LINENO) { + if (procfs_entry.tag.lineno != filter->fields.lineno) + continue; + } + if (filter->mask & ALLOCINFO_FILTER_MASK_MIN_SIZE) { + if (procfs_entry.counter.bytes < filter->min_size) + continue; + } + if (filter->mask & ALLOCINFO_FILTER_MASK_MAX_SIZE) { + if (procfs_entry.counter.bytes > filter->max_size) + continue; + } + + memcpy(&procfs_entries->tag[procfs_entries->count++], &procfs_entry, + sizeof(procfs_entry)); + } + fclose(fp); + return 0; +} + +static enum ioctl_ret get_filtered_ioctl_entries(struct allocinfo_tag_data_vec *tags, + const struct allocinfo_filter *filter, + __u64 start_pos) +{ + int fd = open(ALLOCINFO_PROC, O_RDONLY); + + if (fd < 0) { + ksft_print_msg("Failed to open " ALLOCINFO_PROC " for IOCTL\n"); + return IOCTL_FAILURE; + } + + struct allocinfo_content_id start_cont_id, end_cont_id; + struct allocinfo_get_at get_at_params; + const int max_retries = 10; + int retry_count = 0; + int status; + + /* + * __allocinfo_get_content_id may return different values if a kernel module was loaded + * between the two calls. If that happens, the data gathered cannot be considered consistent + * and hence needs to be fetched again to avoid flakiness. + */ + do { + if (__allocinfo_get_content_id(fd, &start_cont_id)) { + ksft_print_msg("allocinfo_get_content_id failed\n"); + status = IOCTL_FAILURE; + break; + } + + memset(tags, 0, sizeof(*tags)); + memset(&get_at_params, 0, sizeof(get_at_params)); + memcpy(&get_at_params.filter, filter, sizeof(*filter)); + get_at_params.pos = start_pos; + if (__allocinfo_get_at(fd, &get_at_params)) { + ksft_print_msg("allocinfo_get_at failed\n"); + status = IOCTL_FAILURE; + break; + } + memcpy(&tags->tag[tags->count++], &get_at_params.data, sizeof(get_at_params.data)); + + while (tags->count < VEC_MAX_ENTRIES && + __allocinfo_get_next(fd, &tags->tag[tags->count]) == 0) + tags->count++; + + if (__allocinfo_get_content_id(fd, &end_cont_id)) { + ksft_print_msg("allocinfo_get_content_id failed\n"); + status = IOCTL_FAILURE; + break; + } + + if (start_cont_id.id == end_cont_id.id) { + status = IOCTL_SUCCESS; + } else { + ksft_print_msg("allocinfo_get_content_id mismatch, retrying...\n"); + status = IOCTL_INVALID_DATA; + } + } while (status == IOCTL_INVALID_DATA && retry_count++ < max_retries); + + close(fd); + return status; +} + +static int run_filter_test(const struct allocinfo_filter *filter) +{ + struct allocinfo_tag_data_vec *tags = malloc(sizeof(*tags)); + struct allocinfo_tag_data_vec *procfs_entries = malloc(sizeof(*procfs_entries)); + int ioctl_status; + int ret = KSFT_PASS; + + if (!tags || !procfs_entries) { + ksft_print_msg("Memory allocation failed.\n"); + ret = KSFT_FAIL; + goto exit; + } + + if (get_filtered_procfs_entries(procfs_entries, filter)) { + ksft_print_msg("Error retrieving entries from " ALLOCINFO_PROC "\n"); + ret = KSFT_SKIP; + goto exit; + } + + if (procfs_entries->count == 0) { + ksft_print_msg("No entries found in " ALLOCINFO_PROC ", skipping test\n"); + ret = KSFT_SKIP; + goto exit; + } + + ioctl_status = get_filtered_ioctl_entries(tags, filter, 0); + if (ioctl_status == IOCTL_INVALID_DATA) { + ksft_print_msg("Trouble retrieving valid IOCTL entries, skipping.\n"); + ret = KSFT_SKIP; + goto exit; + } + if (ioctl_status == IOCTL_FAILURE) { + ksft_print_msg("Error retrieving IOCTL entries.\n"); + ret = KSFT_FAIL; + goto exit; + } + + if (!match_entries(procfs_entries, tags, false, false, true, true, true)) + ret = KSFT_FAIL; + +exit: + free(tags); + free(procfs_entries); + return ret; +} + +static int test_filename_filter(void) +{ + struct allocinfo_filter filter; + const char *target_filename = "mm/memory.c"; + + memset(&filter, 0, sizeof(filter)); + filter.mask |= ALLOCINFO_FILTER_MASK_FILENAME; + strncpy(filter.fields.filename, target_filename, ALLOCINFO_STR_SIZE); + + return run_filter_test(&filter); +} + +static int test_function_filter(void) +{ + struct allocinfo_filter filter; + const char *target_function = "dup_mm"; + + memset(&filter, 0, sizeof(filter)); + filter.mask |= ALLOCINFO_FILTER_MASK_FUNCTION; + strncpy(filter.fields.function, target_function, ALLOCINFO_STR_SIZE); + + return run_filter_test(&filter); +} + +int main(int argc, char *argv[]) +{ + int ret; + + ksft_set_plan(2); + + ret = test_filename_filter(); + if (ret == KSFT_SKIP) + ksft_test_result_skip("Skipping test_filename_filter\n"); + else + ksft_test_result(ret == KSFT_PASS, "test_filename_filter\n"); + + ret = test_function_filter(); + if (ret == KSFT_SKIP) + ksft_test_result_skip("Skipping test_function_filter\n"); + else + ksft_test_result(ret == KSFT_PASS, "test_function_filter\n"); + + ksft_finished(); +} From 923690d8099349ec46c8930c0e73b462ea41632b Mon Sep 17 00:00:00 2001 From: Abhishek Bapat Date: Wed, 8 Jul 2026 18:01:28 +0000 Subject: [PATCH 1054/1328] kselftest: alloc_tag: extend the allocinfo ioctl kselftest Add the following 2 scenarios to the allocinfo ioctl kselftest: 1. Validate size based filtering 2. Validate lineno based filtering The first test uses "do_init_module" as the candidate function for the test. This is because the associated site will only allocate memory when a kernel module is loaded. The return value of get_content_id() changes every time modules are loaded or unloaded. Hence, as long as get_content_id() values at the start and the end of the test are the same, the memory allocated by the do_init_module call site should also remain the same. Consequently, the test can assume consistency between the value returned by the ioctl and the procfs resulting in less flakiness. Link: https://lore.kernel.org/e5171926b48802531284c1cb5f04734017141341.1783532853.git.abhishekbapat@google.com Signed-off-by: Abhishek Bapat Tested-by: Hao Ge Acked-by: Hao Ge Cc: Jonathan Corbet Cc: Kent Overstreet Cc: Sourav Panda Cc: Suren Baghdasaryan Signed-off-by: Andrew Morton --- .../alloc_tag/allocinfo_ioctl_test.c | 216 +++++++++++++++++- 1 file changed, 215 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/alloc_tag/allocinfo_ioctl_test.c b/tools/testing/selftests/alloc_tag/allocinfo_ioctl_test.c index 3614ee9b46fb..74fd64b2370c 100644 --- a/tools/testing/selftests/alloc_tag/allocinfo_ioctl_test.c +++ b/tools/testing/selftests/alloc_tag/allocinfo_ioctl_test.c @@ -5,6 +5,7 @@ * Copyright (C) 2026 Google, Inc. */ +#include #include #include #include @@ -312,11 +313,212 @@ static int test_function_filter(void) return run_filter_test(&filter); } +static int test_size_filter(void) +{ + int fd; + struct allocinfo_tag_data_vec *tags = malloc(sizeof(*tags)); + struct allocinfo_tag_data_vec *procfs_entries = malloc(sizeof(*procfs_entries)); + struct allocinfo_filter filter; + int ret = KSFT_PASS; + __u64 target_size, i, pos; + struct allocinfo_tag_data *found_tag = NULL; + const char *target_function = "do_init_module"; + struct allocinfo_content_id start_cont_id, end_cont_id; + int retry = 0; + const int max_retries = 10; + + if (!tags || !procfs_entries) { + ksft_print_msg("Memory allocation failed.\n"); + ret = KSFT_FAIL; + goto freemem; + } + + fd = open(ALLOCINFO_PROC, O_RDONLY); + if (fd < 0) { + ksft_print_msg("Failed to open " ALLOCINFO_PROC ": %s\n", strerror(errno)); + ret = KSFT_SKIP; + goto freemem; + } + + do { + found_tag = NULL; + pos = 0; + + if (__allocinfo_get_content_id(fd, &start_cont_id)) { + ksft_print_msg("allocinfo_get_content_id failed\n"); + ret = KSFT_FAIL; + goto exit; + } + + memset(&filter, 0, sizeof(filter)); + filter.mask |= ALLOCINFO_FILTER_MASK_FUNCTION; + strncpy(filter.fields.function, target_function, ALLOCINFO_STR_SIZE); + + if (get_filtered_procfs_entries(procfs_entries, &filter)) { + ksft_print_msg("Error retrieving entries from " ALLOCINFO_PROC "\n"); + ret = KSFT_SKIP; + goto exit; + } + + if (procfs_entries->count == 0) { + ksft_print_msg("Function %s not found in procfs\n", target_function); + ret = KSFT_SKIP; + goto exit; + } + + target_size = procfs_entries->tag[0].counter.bytes; + + memset(&filter, 0, sizeof(filter)); + filter.mask |= ALLOCINFO_FILTER_MASK_MIN_SIZE | ALLOCINFO_FILTER_MASK_MAX_SIZE; + filter.min_size = target_size; + filter.max_size = target_size; + + while (1) { + struct allocinfo_get_at get_at_params; + + memset(&get_at_params, 0, sizeof(get_at_params)); + memcpy(&get_at_params.filter, &filter, sizeof(filter)); + get_at_params.pos = pos; + + if (__allocinfo_get_at(fd, &get_at_params)) + break; + + tags->count = 0; + memcpy(&tags->tag[tags->count++], &get_at_params.data, + sizeof(get_at_params.data)); + + while (tags->count < VEC_MAX_ENTRIES && + __allocinfo_get_next(fd, &tags->tag[tags->count]) == 0) + tags->count++; + + for (i = 0; i < tags->count; i++) { + if (strcmp(tags->tag[i].tag.function, target_function) == 0) { + found_tag = &tags->tag[i]; + break; + } + } + + if (found_tag || tags->count < VEC_MAX_ENTRIES) + break; + + pos += tags->count; + } + + if (__allocinfo_get_content_id(fd, &end_cont_id)) { + ksft_print_msg("allocinfo_get_content_id failed\n"); + ret = KSFT_FAIL; + goto exit; + } + + if (start_cont_id.id == end_cont_id.id) + break; + + ksft_print_msg("Module load detected during size verification, retrying...\n"); + } while (retry++ < max_retries); + + if (start_cont_id.id == end_cont_id.id && !found_tag) { + ksft_print_msg("Entry with function %s not found in IOCTL results\n", + target_function); + ret = KSFT_FAIL; + } else if (start_cont_id.id != end_cont_id.id) { + ksft_print_msg("Failed to match content_ids for procfs and IOCTL, skipping...\n"); + ret = KSFT_SKIP; + } else if (found_tag && found_tag->counter.bytes != target_size) { + ksft_print_msg("IOCTL entry size %llu does not match target size %llu\n", + found_tag->counter.bytes, target_size); + ret = KSFT_FAIL; + } + +exit: + close(fd); +freemem: + free(tags); + free(procfs_entries); + return ret; +} + +static int test_lineno_filter(void) +{ + struct allocinfo_tag_data_vec *tags = malloc(sizeof(*tags)); + struct allocinfo_tag_data_vec *procfs_entries = malloc(sizeof(*procfs_entries)); + struct allocinfo_filter filter; + enum ioctl_ret ioctl_status; + int ret = KSFT_PASS; + __u64 target_lineno, i; + struct allocinfo_tag_data *target_tag; + bool found = false; + + if (!tags || !procfs_entries) { + ksft_print_msg("Memory allocation failed.\n"); + ret = KSFT_FAIL; + goto exit; + } + + memset(&filter, 0, sizeof(filter)); + + if (get_filtered_procfs_entries(procfs_entries, &filter)) { + ksft_print_msg("Error retrieving entries from " ALLOCINFO_PROC "\n"); + ret = KSFT_SKIP; + goto exit; + } + if (procfs_entries->count == 0) { + ksft_print_msg("Could not retrieve procfs entries\n"); + ret = KSFT_SKIP; + goto exit; + } + /* + * We depend on the procfs results to determine the line number for the filter before + * making the ioctl query. Hence, we cannot reuse run_filter_test here. + */ + target_tag = &procfs_entries->tag[0]; + target_lineno = target_tag->tag.lineno; + + filter.mask |= ALLOCINFO_FILTER_MASK_LINENO; + filter.fields.lineno = target_lineno; + + ioctl_status = get_filtered_ioctl_entries(tags, &filter, 0); + if (ioctl_status == IOCTL_INVALID_DATA) { + ksft_print_msg("Trouble retrieving valid IOCTL entries, skipping.\n"); + ret = KSFT_SKIP; + goto exit; + } + if (ioctl_status == IOCTL_FAILURE) { + ksft_print_msg("Error retrieving IOCTL entries.\n"); + ret = KSFT_FAIL; + goto exit; + } + + for (i = 0; i < tags->count; i++) { + if (tags->tag[i].tag.lineno != target_lineno) { + ksft_print_msg("IOCTL entry %llu has incorrect lineno %llu.\n", + i, tags->tag[i].tag.lineno); + ret = KSFT_FAIL; + goto exit; + } + + if (strncmp(tags->tag[i].tag.function, target_tag->tag.function, + ALLOCINFO_STR_SIZE) == 0 && + strncmp(tags->tag[i].tag.filename, target_tag->tag.filename, + ALLOCINFO_STR_SIZE) == 0) + found = true; + } + + if (!found) { + ksft_print_msg("Original procfs entry not found in IOCTL lineno filter results.\n"); + ret = KSFT_FAIL; + } + +exit: + free(tags); + free(procfs_entries); + return ret; +} + int main(int argc, char *argv[]) { int ret; - ksft_set_plan(2); + ksft_set_plan(4); ret = test_filename_filter(); if (ret == KSFT_SKIP) @@ -330,5 +532,17 @@ int main(int argc, char *argv[]) else ksft_test_result(ret == KSFT_PASS, "test_function_filter\n"); + ret = test_size_filter(); + if (ret == KSFT_SKIP) + ksft_test_result_skip("Skipping test_size_filter\n"); + else + ksft_test_result(ret == KSFT_PASS, "test_size_filter\n"); + + ret = test_lineno_filter(); + if (ret == KSFT_SKIP) + ksft_test_result_skip("Skipping test_lineno_filter\n"); + else + ksft_test_result(ret == KSFT_PASS, "test_lineno_filter\n"); + ksft_finished(); } From a44730dd05a3f3a663f79fde62d6731393046655 Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Fri, 31 Jul 2026 11:22:51 +0800 Subject: [PATCH 1055/1328] mm: shmem: reject page-aligned fallocate end overflow shmem_fallocate() validates offset + len with inode_newsize_ok(), but then rounds that end offset up to a page boundary before entering the preallocation loop. For a valid request ending at MAX_LFS_FILESIZE, such as offset = 0 and len = LLONG_MAX, adding PAGE_SIZE - 1 to the validated end can overflow the signed loff_t used for the rounded end calculation. If that wrapped value is then converted into a page index, shmem_fallocate() can enter the folio allocation loop with an invalid range. Use check_add_overflow() when calculating the page-aligned end, and fail before entering the allocation loop if the rounded end cannot be represented. Link: https://lore.kernel.org/1929a466735dcbb9438936ff50b7a4fc2332a8a4.1785377919.git.zhilinz@nebusec.ai Fixes: e2d12e22c59c ("tmpfs: support fallocate preallocation") Signed-off-by: Zhiling Zou Reported-by: Vega Reviewed-by: Baolin Wang Cc: Hugh Dickins Signed-off-by: Andrew Morton --- mm/shmem.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/mm/shmem.c b/mm/shmem.c index 774f4b18ff5c..8ea776e52823 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -3617,6 +3617,7 @@ static long shmem_fallocate(struct file *file, int mode, loff_t offset, struct shmem_inode_info *info = SHMEM_I(inode); struct shmem_falloc shmem_falloc; pgoff_t start, index, end, undo_fallocend; + loff_t aligned_end; int error; if (mode & ~(FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE)) @@ -3673,8 +3674,15 @@ static long shmem_fallocate(struct file *file, int mode, loff_t offset, goto out; } + /* Check for wraparound */ + if (check_add_overflow(offset + len, (loff_t)PAGE_SIZE - 1, + &aligned_end)) { + error = -EFBIG; + goto out; + } + start = offset >> PAGE_SHIFT; - end = (offset + len + PAGE_SIZE - 1) >> PAGE_SHIFT; + end = aligned_end >> PAGE_SHIFT; /* Try to avoid a swapstorm if len is impossible to satisfy */ if (sbinfo->max_blocks && end - start > sbinfo->max_blocks) { error = -ENOSPC; From a8efc69a65fbef61bed0923c0de3425d74817c1c Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 13 Jul 2026 11:33:38 +0200 Subject: [PATCH 1056/1328] shmem: provide a shmem_write_folio wrapper Patch series "better block swap batching and a different take on swap_ops v5". This series makes use of the swap_iocb for block as well so that it doesn't do inefficient single-bio I/O, and then rebases the swap_ops from Baoquan on top of the now very different method structure. When running doing kernels builds, which is a workload that doesn't really do much THP anonymous memory it still gets 2x clustering for writeout and 1.2x for reading back swap in. The overall times do not actually change, though. This patch (of 7): Provide a wrapper for the shmem abuses in drm to prepare for swap I/O refactoring by keeping swap_iocb handling entirely contained in mm/. Link: https://lore.kernel.org/20260713093350.2154226-1-hch@lst.de Link: https://lore.kernel.org/20260713093350.2154226-2-hch@lst.de Signed-off-by: Christoph Hellwig Reviewed-by: Baoquan He Reviewed-by: Nhat Pham Reviewed-by: Baolin Wang Acked-by: Chris Li Reviewed-by: Kairui Song Cc: Kemeng Shi Cc: Barry Song Cc: Youngjun Park Signed-off-by: Andrew Morton --- drivers/gpu/drm/i915/gem/i915_gem_shmem.c | 2 +- drivers/gpu/drm/ttm/ttm_backup.c | 2 +- include/linux/shmem_fs.h | 5 +---- mm/shmem.c | 7 ++++++- mm/swap.h | 4 ++++ 5 files changed, 13 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/i915/gem/i915_gem_shmem.c b/drivers/gpu/drm/i915/gem/i915_gem_shmem.c index 06543ae60706..ef9440166295 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_shmem.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_shmem.c @@ -325,7 +325,7 @@ void __shmem_writeback(size_t size, struct address_space *mapping) if (folio_mapped(folio)) folio_redirty_for_writepage(&wbc, folio); else - error = shmem_writeout(folio, NULL, NULL); + error = shmem_write_folio(folio); } } diff --git a/drivers/gpu/drm/ttm/ttm_backup.c b/drivers/gpu/drm/ttm/ttm_backup.c index 3c067aadc52d..0c2d53a13b2a 100644 --- a/drivers/gpu/drm/ttm/ttm_backup.c +++ b/drivers/gpu/drm/ttm/ttm_backup.c @@ -160,7 +160,7 @@ ttm_backup_backup_folio(struct file *backup, struct folio *folio, if (writeback && !folio_mapped(to_folio) && folio_clear_dirty_for_io(to_folio)) { folio_set_reclaim(to_folio); - ret = shmem_writeout(to_folio, NULL, NULL); + ret = shmem_write_folio(to_folio); if (!folio_test_writeback(to_folio)) folio_clear_reclaim(to_folio); if (ret == AOP_WRITEPAGE_ACTIVATE) diff --git a/include/linux/shmem_fs.h b/include/linux/shmem_fs.h index e729b9b0e38d..5663dff53186 100644 --- a/include/linux/shmem_fs.h +++ b/include/linux/shmem_fs.h @@ -12,8 +12,6 @@ #include #include -struct swap_iocb; - /* inode in-kernel data */ #ifdef CONFIG_TMPFS_QUOTA @@ -123,8 +121,7 @@ static inline bool shmem_mapping(const struct address_space *mapping) void shmem_unlock_mapping(struct address_space *mapping); struct page *shmem_read_mapping_page_gfp(struct address_space *mapping, pgoff_t index, gfp_t gfp_mask); -int shmem_writeout(struct folio *folio, struct swap_iocb **plug, - struct list_head *folio_list); +int shmem_write_folio(struct folio *folio); void shmem_truncate_range(struct inode *inode, loff_t start, uoff_t end); int shmem_unuse(unsigned int type); diff --git a/mm/shmem.c b/mm/shmem.c index 8ea776e52823..d245e01416e9 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -1751,7 +1751,12 @@ int shmem_writeout(struct folio *folio, struct swap_iocb **plug, folio_mark_dirty(folio); return AOP_WRITEPAGE_ACTIVATE; /* Return with folio locked */ } -EXPORT_SYMBOL_GPL(shmem_writeout); + +int shmem_write_folio(struct folio *folio) +{ + return shmem_writeout(folio, NULL, NULL); +} +EXPORT_SYMBOL_GPL(shmem_write_folio); #if defined(CONFIG_NUMA) && defined(CONFIG_TMPFS) static void shmem_show_mpol(struct seq_file *seq, struct mempolicy *mpol) diff --git a/mm/swap.h b/mm/swap.h index 4e4c291bbfde..276b7975a9dc 100644 --- a/mm/swap.h +++ b/mm/swap.h @@ -486,4 +486,8 @@ static inline unsigned int folio_swap_flags(struct folio *folio) } #endif /* CONFIG_SWAP */ + +int shmem_writeout(struct folio *folio, struct swap_iocb **plug, + struct list_head *folio_list); + #endif /* _MM_SWAP_H */ From 8f29aa226f82d8f9e7cf75f0ed5964960c7fe682 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 13 Jul 2026 11:33:39 +0200 Subject: [PATCH 1057/1328] mm/swap: introduce struct swap_io_ctx Generalize the context currently provided by double pointers to struct swap_iocb to an on-stack context. This cleans up the code and prepares for adding more fields and supporting batching multiple folios into a single bio for block-based swap as well. This new swap_io_ctx is required for all functions using it, the old way of allowing a NULL iocb for some callers is removed to keep the interface consistent. To reduce code duplication caused by this, a new swap_cache_read_folio_sync helper is added to consolidate the code to call swap_cache_read_folio with a local swap_io_ctx. The unpug helpers are renamed to use the submit wording as they are generalized. Link: https://lore.kernel.org/20260713093350.2154226-3-hch@lst.de Signed-off-by: Christoph Hellwig Reviewed-by: Nhat Pham Cc: Baolin Wang Cc: Baoquan He Cc: Barry Song Cc: Chris Li Cc: Kairui Song Cc: Kemeng Shi Cc: Youngjun Park Signed-off-by: Andrew Morton --- mm/madvise.c | 16 ++++++------- mm/page_io.c | 60 ++++++++++++++++++++++++++----------------------- mm/shmem.c | 13 +++++++---- mm/swap.h | 35 +++++++++++++---------------- mm/swap_state.c | 53 +++++++++++++++++++++++++------------------ mm/vmscan.c | 15 ++++++------- mm/zswap.c | 4 +++- 7 files changed, 105 insertions(+), 91 deletions(-) diff --git a/mm/madvise.c b/mm/madvise.c index bf9ce199935a..07a21ca31bad 100644 --- a/mm/madvise.c +++ b/mm/madvise.c @@ -188,7 +188,7 @@ static int swapin_walk_pmd_entry(pmd_t *pmd, unsigned long start, unsigned long end, struct mm_walk *walk) { struct vm_area_struct *vma = walk->private; - struct swap_iocb *splug = NULL; + struct swap_io_ctx ctx = {}; pte_t *ptep = NULL; spinlock_t *ptl; unsigned long addr; @@ -212,15 +212,15 @@ static int swapin_walk_pmd_entry(pmd_t *pmd, unsigned long start, pte_unmap_unlock(ptep, ptl); ptep = NULL; - folio = read_swap_cache_async(entry, GFP_HIGHUSER_MOVABLE, - vma, addr, &splug); + folio = read_swap_cache_async(&ctx, entry, GFP_HIGHUSER_MOVABLE, + vma, addr); if (folio) folio_put(folio); } if (ptep) pte_unmap_unlock(ptep, ptl); - swap_read_unplug(splug); + swap_read_submit(&ctx); cond_resched(); return 0; @@ -238,7 +238,7 @@ static void shmem_swapin_range(struct vm_area_struct *vma, XA_STATE(xas, &mapping->i_pages, linear_page_index(vma, start)); pgoff_t end_index = linear_page_index(vma, end) - 1; struct folio *folio; - struct swap_iocb *splug = NULL; + struct swap_io_ctx ctx = {}; rcu_read_lock(); xas_for_each(&xas, folio, end_index) { @@ -257,15 +257,15 @@ static void shmem_swapin_range(struct vm_area_struct *vma, xas_pause(&xas); rcu_read_unlock(); - folio = read_swap_cache_async(entry, mapping_gfp_mask(mapping), - vma, addr, &splug); + folio = read_swap_cache_async(&ctx, entry, + mapping_gfp_mask(mapping), vma, addr); if (folio) folio_put(folio); rcu_read_lock(); } rcu_read_unlock(); - swap_read_unplug(splug); + swap_read_submit(&ctx); } #endif /* CONFIG_SWAP */ diff --git a/mm/page_io.c b/mm/page_io.c index b23f494fcc83..fe24a49e034c 100644 --- a/mm/page_io.c +++ b/mm/page_io.c @@ -248,7 +248,7 @@ static void swap_zeromap_folio_clear(struct folio *folio) * We may have stale swap cache pages in memory: notice * them here and get rid of the unnecessary final write. */ -int swap_writeout(struct folio *folio, struct swap_iocb **swap_plug) +int swap_writeout(struct swap_io_ctx *ctx, struct folio *folio) { int ret = 0; @@ -295,7 +295,7 @@ int swap_writeout(struct folio *folio, struct swap_iocb **swap_plug) } rcu_read_unlock(); - __swap_writepage(folio, swap_plug); + __swap_writepage(ctx, folio); return 0; out_unlock: folio_unlock(folio); @@ -390,9 +390,9 @@ static void sio_write_complete(struct kiocb *iocb, long ret) mempool_free(sio, sio_pool); } -static void swap_writepage_fs(struct folio *folio, struct swap_iocb **swap_plug) +static void swap_writepage_fs(struct swap_io_ctx *ctx, struct folio *folio) { - struct swap_iocb *sio = swap_plug ? *swap_plug : NULL; + struct swap_iocb *sio = ctx->sio; struct swap_info_struct *sis = __swap_entry_to_info(folio->swap); struct file *swap_file = sis->swap_file; loff_t pos = swap_dev_pos(folio->swap); @@ -403,7 +403,7 @@ static void swap_writepage_fs(struct folio *folio, struct swap_iocb **swap_plug) if (sio) { if (sio->iocb.ki_filp != swap_file || sio->iocb.ki_pos + sio->len != pos) { - swap_write_unplug(sio); + swap_write_submit(ctx); sio = NULL; } } @@ -418,12 +418,11 @@ static void swap_writepage_fs(struct folio *folio, struct swap_iocb **swap_plug) bvec_set_folio(&sio->bvecs[sio->nr_bvecs], folio, folio_size(folio), 0); sio->len += folio_size(folio); sio->nr_bvecs += 1; - if (sio->nr_bvecs == ARRAY_SIZE(sio->bvecs) || !swap_plug) { - swap_write_unplug(sio); + if (sio->nr_bvecs == ARRAY_SIZE(sio->bvecs)) { + swap_write_submit(ctx); sio = NULL; } - if (swap_plug) - *swap_plug = sio; + ctx->sio = sio; } static void swap_writepage_bdev_sync(struct folio *folio, @@ -463,7 +462,7 @@ static void swap_writepage_bdev_async(struct folio *folio, submit_bio(bio); } -void __swap_writepage(struct folio *folio, struct swap_iocb **swap_plug) +void __swap_writepage(struct swap_io_ctx *ctx, struct folio *folio) { struct swap_info_struct *sis = __swap_entry_to_info(folio->swap); @@ -474,7 +473,7 @@ void __swap_writepage(struct folio *folio, struct swap_iocb **swap_plug) * is safe. */ if (data_race(sis->flags & SWP_FS_OPS)) - swap_writepage_fs(folio, swap_plug); + swap_writepage_fs(ctx, folio); /* * ->flags can be updated non-atomically, * but that will never affect SWP_SYNCHRONOUS_IO, so the data_race @@ -486,16 +485,20 @@ void __swap_writepage(struct folio *folio, struct swap_iocb **swap_plug) swap_writepage_bdev_async(folio, sis); } -void swap_write_unplug(struct swap_iocb *sio) +void swap_write_submit(struct swap_io_ctx *ctx) { + struct swap_iocb *sio = ctx->sio; struct iov_iter from; - struct address_space *mapping = sio->iocb.ki_filp->f_mapping; int ret; + if (!sio) + return; + iov_iter_bvec(&from, ITER_SOURCE, sio->bvecs, sio->nr_bvecs, sio->len); - ret = mapping->a_ops->swap_rw(&sio->iocb, &from); + ret = sio->iocb.ki_filp->f_mapping->a_ops->swap_rw(&sio->iocb, &from); if (ret != -EIOCBQUEUED) sio_write_complete(&sio->iocb, ret); + ctx->sio = NULL; } static void sio_read_complete(struct kiocb *iocb, long ret) @@ -587,18 +590,16 @@ static bool swap_read_folio_zeromap(struct folio *folio) return true; } -static void swap_read_folio_fs(struct folio *folio, struct swap_iocb **plug) +static void swap_read_folio_fs(struct swap_io_ctx *ctx, struct folio *folio) { struct swap_info_struct *sis = __swap_entry_to_info(folio->swap); - struct swap_iocb *sio = NULL; + struct swap_iocb *sio = ctx->sio; loff_t pos = swap_dev_pos(folio->swap); - if (plug) - sio = *plug; if (sio) { if (sio->iocb.ki_filp != sis->swap_file || sio->iocb.ki_pos + sio->len != pos) { - swap_read_unplug(sio); + swap_read_submit(ctx); sio = NULL; } } @@ -613,12 +614,11 @@ static void swap_read_folio_fs(struct folio *folio, struct swap_iocb **plug) bvec_set_folio(&sio->bvecs[sio->nr_bvecs], folio, folio_size(folio), 0); sio->len += folio_size(folio); sio->nr_bvecs += 1; - if (sio->nr_bvecs == ARRAY_SIZE(sio->bvecs) || !plug) { - swap_read_unplug(sio); + if (sio->nr_bvecs == ARRAY_SIZE(sio->bvecs)) { + swap_read_submit(ctx); sio = NULL; } - if (plug) - *plug = sio; + ctx->sio = sio; } static void swap_read_folio_bdev_sync(struct folio *folio, @@ -658,7 +658,7 @@ static void swap_read_folio_bdev_async(struct folio *folio, submit_bio(bio); } -void swap_read_folio(struct folio *folio, struct swap_iocb **plug) +void swap_read_folio(struct swap_io_ctx *ctx, struct folio *folio) { struct swap_info_struct *sis = __swap_entry_to_info(folio->swap); bool synchronous = sis->flags & SWP_SYNCHRONOUS_IO; @@ -693,7 +693,7 @@ void swap_read_folio(struct folio *folio, struct swap_iocb **plug) zswap_folio_swapin(folio); if (data_race(sis->flags & SWP_FS_OPS)) { - swap_read_folio_fs(folio, plug); + swap_read_folio_fs(ctx, folio); } else if (synchronous) { swap_read_folio_bdev_sync(folio, sis); } else { @@ -708,14 +708,18 @@ void swap_read_folio(struct folio *folio, struct swap_iocb **plug) delayacct_swapin_end(); } -void __swap_read_unplug(struct swap_iocb *sio) +void swap_read_submit(struct swap_io_ctx *ctx) { + struct swap_iocb *sio = ctx->sio; struct iov_iter from; - struct address_space *mapping = sio->iocb.ki_filp->f_mapping; int ret; + if (!sio) + return; + iov_iter_bvec(&from, ITER_DEST, sio->bvecs, sio->nr_bvecs, sio->len); - ret = mapping->a_ops->swap_rw(&sio->iocb, &from); + ret = sio->iocb.ki_filp->f_mapping->a_ops->swap_rw(&sio->iocb, &from); if (ret != -EIOCBQUEUED) sio_read_complete(&sio->iocb, ret); + ctx->sio = NULL; } diff --git a/mm/shmem.c b/mm/shmem.c index d245e01416e9..2e4dacdcce11 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -1597,13 +1597,13 @@ int shmem_unuse(unsigned int type) /** * shmem_writeout - Write the folio to swap + * @ctx: swap I/O context * @folio: The folio to write - * @plug: swap plug * @folio_list: list to put back folios on split * * Move the folio from the page cache to the swap cache. */ -int shmem_writeout(struct folio *folio, struct swap_iocb **plug, +int shmem_writeout(struct swap_io_ctx *ctx, struct folio *folio, struct list_head *folio_list) { struct address_space *mapping = folio->mapping; @@ -1715,7 +1715,7 @@ int shmem_writeout(struct folio *folio, struct swap_iocb **plug, shmem_delete_from_page_cache(folio, swp_to_radix_entry(folio->swap)); BUG_ON(folio_mapped(folio)); - error = swap_writeout(folio, plug); + error = swap_writeout(ctx, folio); if (error != AOP_WRITEPAGE_ACTIVATE) { /* folio has been unlocked */ return error; @@ -1754,7 +1754,12 @@ int shmem_writeout(struct folio *folio, struct swap_iocb **plug, int shmem_write_folio(struct folio *folio) { - return shmem_writeout(folio, NULL, NULL); + struct swap_io_ctx ctx = {}; + int err; + + err = shmem_writeout(&ctx, folio, NULL); + swap_write_submit(&ctx); + return err; } EXPORT_SYMBOL_GPL(shmem_write_folio); diff --git a/mm/swap.h b/mm/swap.h index 276b7975a9dc..70f8e287f140 100644 --- a/mm/swap.h +++ b/mm/swap.h @@ -91,6 +91,10 @@ static inline int mem_cgroup_swappiness(struct mem_cgroup *memcg) return READ_ONCE(vm_swappiness); } +struct swap_io_ctx { + struct swap_iocb *sio; +}; + #ifdef CONFIG_SWAP #include /* for swp_offset */ #include /* for bio_end_io_t */ @@ -253,17 +257,11 @@ extern void __swap_cluster_free_entries(struct swap_info_struct *si, /* linux/mm/page_io.c */ int sio_pool_init(void); -struct swap_iocb; -void swap_read_folio(struct folio *folio, struct swap_iocb **plug); -void __swap_read_unplug(struct swap_iocb *plug); -static inline void swap_read_unplug(struct swap_iocb *plug) -{ - if (unlikely(plug)) - __swap_read_unplug(plug); -} -void swap_write_unplug(struct swap_iocb *sio); -int swap_writeout(struct folio *folio, struct swap_iocb **swap_plug); -void __swap_writepage(struct folio *folio, struct swap_iocb **swap_plug); +void swap_read_folio(struct swap_io_ctx *ctx, struct folio *folio); +void swap_read_submit(struct swap_io_ctx *ctx); +void swap_write_submit(struct swap_io_ctx *ctx); +int swap_writeout(struct swap_io_ctx *ctx, struct folio *folio); +void __swap_writepage(struct swap_io_ctx *ctx, struct folio *folio); /* linux/mm/swap_state.c */ extern struct address_space swap_space __read_mostly; @@ -330,9 +328,8 @@ void __swap_cache_replace_folio(struct swap_cluster_info *ci, void show_swap_cache_info(void); void swapcache_clear(struct swap_info_struct *si, swp_entry_t entry, int nr); -struct folio *read_swap_cache_async(swp_entry_t entry, gfp_t gfp_mask, - struct vm_area_struct *vma, unsigned long addr, - struct swap_iocb **plug); +struct folio *read_swap_cache_async(struct swap_io_ctx *ctx, swp_entry_t entry, + gfp_t gfp_mask, struct vm_area_struct *vma, unsigned long addr); struct folio *swap_cluster_readahead(swp_entry_t entry, gfp_t flag, struct mempolicy *mpol, pgoff_t ilx); struct folio *swapin_readahead(swp_entry_t entry, gfp_t flag, @@ -348,7 +345,6 @@ static inline unsigned int folio_swap_flags(struct folio *folio) } #else /* CONFIG_SWAP */ -struct swap_iocb; static inline struct swap_cluster_info *swap_cluster_lock( struct swap_info_struct *si, pgoff_t offset, bool irq) { @@ -394,11 +390,11 @@ static inline void folio_put_swap(struct folio *folio, struct page *page) { } -static inline void swap_read_folio(struct folio *folio, struct swap_iocb **plug) +static inline void swap_read_folio(struct swap_io_ctx *ctx, struct folio *folio) { } -static inline void swap_write_unplug(struct swap_iocb *sio) +static inline void swap_write_submit(struct swap_io_ctx *ctx) { } @@ -440,8 +436,7 @@ static inline void swap_update_readahead(struct folio *folio, { } -static inline int swap_writeout(struct folio *folio, - struct swap_iocb **swap_plug) +static inline int swap_writeout(struct swap_io_ctx *ctx, struct folio *folio) { return 0; } @@ -487,7 +482,7 @@ static inline unsigned int folio_swap_flags(struct folio *folio) #endif /* CONFIG_SWAP */ -int shmem_writeout(struct folio *folio, struct swap_iocb **plug, +int shmem_writeout(struct swap_io_ctx *ctx, struct folio *folio, struct list_head *folio_list); #endif /* _MM_SWAP_H */ diff --git a/mm/swap_state.c b/mm/swap_state.c index 1444d20a40e9..5be825911e64 100644 --- a/mm/swap_state.c +++ b/mm/swap_state.c @@ -638,9 +638,9 @@ void swap_update_readahead(struct folio *folio, struct vm_area_struct *vma, } } -static struct folio *swap_cache_read_folio(swp_entry_t entry, gfp_t gfp, - struct mempolicy *mpol, pgoff_t ilx, - struct swap_iocb **plug, bool readahead) +static struct folio *swap_cache_read_folio(struct swap_io_ctx *ctx, + swp_entry_t entry, gfp_t gfp, struct mempolicy *mpol, + pgoff_t ilx, bool readahead) { struct folio *folio; @@ -654,7 +654,7 @@ static struct folio *swap_cache_read_folio(swp_entry_t entry, gfp_t gfp, if (IS_ERR_OR_NULL(folio)) return NULL; - swap_read_folio(folio, plug); + swap_read_folio(ctx, folio); if (readahead) { folio_set_readahead(folio); count_vm_event(SWAP_RA); @@ -682,6 +682,7 @@ static struct folio *swap_cache_read_folio(swp_entry_t entry, gfp_t gfp, struct folio *swapin_sync(swp_entry_t entry, gfp_t gfp, unsigned long orders, struct vm_fault *vmf, struct mempolicy *mpol, pgoff_t ilx) { + struct swap_io_ctx ctx = {}; struct folio *folio; do { @@ -694,7 +695,8 @@ struct folio *swapin_sync(swp_entry_t entry, gfp_t gfp, unsigned long orders, if (IS_ERR(folio)) return folio; - swap_read_folio(folio, NULL); + swap_read_folio(&ctx, folio); + swap_read_submit(&ctx); return folio; } @@ -704,9 +706,8 @@ struct folio *swapin_sync(swp_entry_t entry, gfp_t gfp, unsigned long orders, * A failure return means that either the page allocation failed or that * the swap entry is no longer in use. */ -struct folio *read_swap_cache_async(swp_entry_t entry, gfp_t gfp_mask, - struct vm_area_struct *vma, unsigned long addr, - struct swap_iocb **plug) +struct folio *read_swap_cache_async(struct swap_io_ctx *ctx, swp_entry_t entry, + gfp_t gfp_mask, struct vm_area_struct *vma, unsigned long addr) { struct swap_info_struct *si; struct mempolicy *mpol; @@ -718,13 +719,24 @@ struct folio *read_swap_cache_async(swp_entry_t entry, gfp_t gfp_mask, return NULL; mpol = get_vma_policy(vma, addr, 0, &ilx); - folio = swap_cache_read_folio(entry, gfp_mask, mpol, ilx, plug, false); + folio = swap_cache_read_folio(ctx, entry, gfp_mask, mpol, ilx, false); mpol_cond_put(mpol); put_swap_device(si); return folio; } +static struct folio *swap_cache_read_folio_sync(swp_entry_t entry, gfp_t gfp, + struct mempolicy *mpol, pgoff_t ilx) +{ + struct swap_io_ctx ctx = {}; + struct folio *folio; + + folio = swap_cache_read_folio(&ctx, entry, gfp, mpol, ilx, false); + swap_read_submit(&ctx); + return folio; +} + static unsigned int __swapin_nr_pages(unsigned long prev_offset, unsigned long offset, int hits, @@ -813,8 +825,8 @@ struct folio *swap_cluster_readahead(swp_entry_t entry, gfp_t gfp_mask, unsigned long start_offset, end_offset; unsigned long mask; struct swap_info_struct *si = __swap_entry_to_info(entry); + struct swap_io_ctx ctx = {}; struct blk_plug plug; - struct swap_iocb *splug = NULL; swp_entry_t ra_entry; mask = swapin_nr_pages(offset) - 1; @@ -833,17 +845,16 @@ struct folio *swap_cluster_readahead(swp_entry_t entry, gfp_t gfp_mask, for (offset = start_offset; offset <= end_offset ; offset++) { /* Ok, do the async read-ahead now */ ra_entry = swp_entry(swp_type(entry), offset); - folio = swap_cache_read_folio(ra_entry, gfp_mask, mpol, ilx, - &splug, offset != entry_offset); + folio = swap_cache_read_folio(&ctx, ra_entry, gfp_mask, mpol, + ilx, offset != entry_offset); if (!folio) continue; folio_put(folio); } blk_finish_plug(&plug); - swap_read_unplug(splug); + swap_read_submit(&ctx); skip: - /* The page was likely read above, so no need for plugging here */ - return swap_cache_read_folio(entry, gfp_mask, mpol, ilx, NULL, false); + return swap_cache_read_folio_sync(entry, gfp_mask, mpol, ilx); } static int swap_vma_ra_win(struct vm_fault *vmf, unsigned long *start, @@ -903,8 +914,8 @@ static int swap_vma_ra_win(struct vm_fault *vmf, unsigned long *start, static struct folio *swap_vma_readahead(swp_entry_t targ_entry, gfp_t gfp_mask, struct mempolicy *mpol, pgoff_t targ_ilx, struct vm_fault *vmf) { + struct swap_io_ctx ctx = {}; struct blk_plug plug; - struct swap_iocb *splug = NULL; struct folio *folio; pte_t *pte = NULL, pentry; int win; @@ -943,8 +954,8 @@ static struct folio *swap_vma_readahead(swp_entry_t targ_entry, gfp_t gfp_mask, if (!si) continue; } - folio = swap_cache_read_folio(entry, gfp_mask, mpol, ilx, - &splug, addr != vmf->address); + folio = swap_cache_read_folio(&ctx, entry, gfp_mask, mpol, ilx, + addr != vmf->address); if (si) put_swap_device(si); if (!folio) @@ -954,12 +965,10 @@ static struct folio *swap_vma_readahead(swp_entry_t targ_entry, gfp_t gfp_mask, if (pte) pte_unmap(pte); blk_finish_plug(&plug); - swap_read_unplug(splug); + swap_read_submit(&ctx); skip: /* The folio was likely read above, so no need for plugging here */ - folio = swap_cache_read_folio(targ_entry, gfp_mask, mpol, targ_ilx, - NULL, false); - return folio; + return swap_cache_read_folio_sync(targ_entry, gfp_mask, mpol, targ_ilx); } /** diff --git a/mm/vmscan.c b/mm/vmscan.c index 3a6701143620..4742297693fe 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -617,8 +617,8 @@ typedef enum { /* * pageout is called by shrink_folio_list() for each dirty folio. */ -static pageout_t pageout(struct folio *folio, struct address_space *mapping, - struct swap_iocb **plug, struct list_head *folio_list) +static pageout_t pageout(struct swap_io_ctx *ctx, struct address_space *mapping, + struct folio *folio, struct list_head *folio_list) { int res; @@ -654,9 +654,9 @@ static pageout_t pageout(struct folio *folio, struct address_space *mapping, * the split out folios get added back to folio_list. */ if (shmem_mapping(mapping)) - res = shmem_writeout(folio, plug, folio_list); + res = shmem_writeout(ctx, folio, folio_list); else - res = swap_writeout(folio, plug); + res = swap_writeout(ctx, folio); if (res < 0) handle_write_error(mapping, folio, res); @@ -1066,7 +1066,7 @@ static unsigned int shrink_folio_list(struct list_head *folio_list, unsigned int nr_reclaimed = 0, nr_demoted = 0; unsigned int pgactivate = 0; bool do_demote_pass; - struct swap_iocb *plug = NULL; + struct swap_io_ctx ctx = {}; folio_batch_init(&free_folios); memset(stat, 0, sizeof(*stat)); @@ -1394,7 +1394,7 @@ static unsigned int shrink_folio_list(struct list_head *folio_list, * starts and then write it out here. */ try_to_unmap_flush_dirty(); - switch (pageout(folio, mapping, &plug, folio_list)) { + switch (pageout(&ctx, mapping, folio, folio_list)) { case PAGE_KEEP: goto keep_locked; case PAGE_ACTIVATE: @@ -1582,8 +1582,7 @@ static unsigned int shrink_folio_list(struct list_head *folio_list, list_splice(&ret_folios, folio_list); count_vm_events(PGACTIVATE, pgactivate); - if (plug) - swap_write_unplug(plug); + swap_write_submit(&ctx); return nr_reclaimed; } diff --git a/mm/zswap.c b/mm/zswap.c index 0b9435b4f57c..c33d496bfdb2 100644 --- a/mm/zswap.c +++ b/mm/zswap.c @@ -992,6 +992,7 @@ static int zswap_writeback_entry(struct zswap_entry *entry, struct folio *folio; struct mempolicy *mpol; struct swap_info_struct *si; + struct swap_io_ctx ctx = {}; int ret = 0; /* try to allocate swap cache folio */ @@ -1049,7 +1050,8 @@ static int zswap_writeback_entry(struct zswap_entry *entry, folio_set_reclaim(folio); /* start writeback */ - __swap_writepage(folio, NULL); + __swap_writepage(&ctx, folio); + swap_write_submit(&ctx); out: if (ret) { From dda8fb68b5907d0a3c6216e5e31e45adca582b68 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 13 Jul 2026 11:33:40 +0200 Subject: [PATCH 1058/1328] mm/swap: also use struct swap_iocb for block I/O Block I/O benefits from batching just as much as remote file systems. Extend struct swap_iocb to support building a bio on the fly as well, and rewrite the block based swap code for it. This especially benefits submit_bio based drivers that do not have the block plugging available, but also saves allocating extra bios for blk-mq drivers. Add a pre-allocated bio to struct swap_iocb in a union with kiocb used for file system based swap so that struct swap_iocb can be used for all swap I/O, and initialize the pool for it unconditionally. Various low-level bdev and fs functions are now replaced with a unified can_merge/add/submit scheme. Note that the block based swap code now uses the same memcg-based check previously added for file system based swap as well. Link: https://lore.kernel.org/20260713093350.2154226-4-hch@lst.de Signed-off-by: Christoph Hellwig Cc: Baolin Wang Cc: Baoquan He Cc: Barry Song Cc: Chris Li Cc: Kairui Song Cc: Kemeng Shi Cc: Nhat Pham Cc: Youngjun Park Signed-off-by: Andrew Morton --- mm/page_io.c | 528 ++++++++++++++++++++++++-------------------------- mm/swap.h | 1 + mm/swapfile.c | 9 +- 3 files changed, 255 insertions(+), 283 deletions(-) diff --git a/mm/page_io.c b/mm/page_io.c index fe24a49e034c..0195c25a77eb 100644 --- a/mm/page_io.c +++ b/mm/page_io.c @@ -28,54 +28,6 @@ #include "swap.h" #include "swap_table.h" -static void __end_swap_bio_write(struct bio *bio) -{ - struct folio *folio = bio_first_folio_all(bio); - - if (bio->bi_status) { - /* - * We failed to write the page out to swap-space. - * Re-dirty the page in order to avoid it being reclaimed. - * Also print a dire warning that things will go BAD (tm) - * very quickly. - * - * Also clear PG_reclaim to avoid folio_rotate_reclaimable() - */ - folio_mark_dirty(folio); - pr_alert_ratelimited("Write-error on swap-device (%u:%u:%llu)\n", - MAJOR(bio_dev(bio)), MINOR(bio_dev(bio)), - (unsigned long long)bio->bi_iter.bi_sector); - folio_clear_reclaim(folio); - } - folio_end_writeback(folio); -} - -static void end_swap_bio_write(struct bio *bio) -{ - __end_swap_bio_write(bio); - bio_put(bio); -} - -static void __end_swap_bio_read(struct bio *bio) -{ - struct folio *folio = bio_first_folio_all(bio); - - if (bio->bi_status) { - pr_alert_ratelimited("Read-error on swap-device (%u:%u:%llu)\n", - MAJOR(bio_dev(bio)), MINOR(bio_dev(bio)), - (unsigned long long)bio->bi_iter.bi_sector); - } else { - folio_mark_uptodate(folio); - } - folio_unlock(folio); -} - -static void end_swap_bio_read(struct bio *bio) -{ - __end_swap_bio_read(bio); - bio_put(bio); -} - int generic_swapfile_activate(struct swap_info_struct *sis, struct file *swap_file, sector_t *span) @@ -316,18 +268,36 @@ static inline void count_swpout_vm_event(struct folio *folio) } #if defined(CONFIG_MEMCG) && defined(CONFIG_BLK_CGROUP) +static struct cgroup_subsys_state *folio_memcg_blkg_css(struct folio *folio) +{ + return cgroup_e_css(folio_memcg(folio)->css.cgroup, &io_cgrp_subsys); +} + +static bool folio_blkg_can_merge(struct folio *folio, struct folio *prev_folio) +{ + bool can_merge = true; + + if (folio_memcg_charged(folio) != folio_memcg_charged(prev_folio)) + return false; + if (folio_memcg_charged(folio)) { + rcu_read_lock(); + if (folio_memcg_blkg_css(folio) != + folio_memcg_blkg_css(prev_folio)) + can_merge = false; + rcu_read_unlock(); + } + return can_merge; +} + static void bio_associate_blkg_from_page(struct bio *bio, struct folio *folio) { struct cgroup_subsys_state *css; - struct mem_cgroup *memcg; if (!folio_memcg_charged(folio)) return; - rcu_read_lock(); - memcg = folio_memcg(folio); - css = cgroup_e_css(memcg->css.cgroup, &io_cgrp_subsys); - if (!css || !css_tryget(css)) + css = folio_memcg_blkg_css(folio); + if (css && !css_tryget(css)) css = NULL; rcu_read_unlock(); @@ -336,11 +306,18 @@ static void bio_associate_blkg_from_page(struct bio *bio, struct folio *folio) css_put(css); } #else +static bool folio_blkg_can_merge(struct folio *folio, struct folio *prev_folio) +{ + return true; +} #define bio_associate_blkg_from_page(bio, folio) do { } while (0) #endif /* CONFIG_MEMCG && CONFIG_BLK_CGROUP */ struct swap_iocb { - struct kiocb iocb; + union { + struct kiocb iocb; + struct bio bio; + }; struct bio_vec bvecs[SWAP_CLUSTER_MAX]; int nr_bvecs; int len; @@ -360,171 +337,70 @@ int sio_pool_init(void) return 0; } -static void sio_write_complete(struct kiocb *iocb, long ret) +static bool swap_can_merge(struct swap_io_ctx *ctx, struct folio *folio, + int rw) { - struct swap_iocb *sio = container_of(iocb, struct swap_iocb, iocb); - struct page *page = sio->bvecs[0].bv_page; - int p; + struct swap_info_struct *sis = __swap_entry_to_info(folio->swap); + struct bio_vec *last_bv = &ctx->sio->bvecs[ctx->sio->nr_bvecs - 1]; + struct folio *prev_folio = bvec_folio(last_bv); + size_t prev_folio_size = folio_size(prev_folio); - if (ret != sio->len) { - /* - * In the case of swap-over-nfs, this can be a - * temporary failure if the system has limited - * memory for allocating transmit buffers. - * Mark the page dirty and avoid - * folio_rotate_reclaimable but rate-limit the - * messages. - */ - pr_err_ratelimited("Write error %ld on dio swapfile (%llu)\n", - ret, swap_dev_pos(page_swap_entry(page))); - for (p = 0; p < sio->nr_bvecs; p++) { - page = sio->bvecs[p].bv_page; - set_page_dirty(page); - ClearPageReclaim(page); - } + if (ctx->sis != sis) + return false; + + if (sis->flags & SWP_FS_OPS) { + if (swap_dev_pos(folio->swap) != + swap_dev_pos(prev_folio->swap) + prev_folio_size) + return false; + } else { + if (swap_folio_sector(folio) != + swap_folio_sector(prev_folio) + + (prev_folio_size >> SECTOR_SHIFT)) + return false; + if (rw == WRITE && !folio_blkg_can_merge(folio, prev_folio)) + return false; } - for (p = 0; p < sio->nr_bvecs; p++) - end_page_writeback(sio->bvecs[p].bv_page); - - mempool_free(sio, sio_pool); + return true; } -static void swap_writepage_fs(struct swap_io_ctx *ctx, struct folio *folio) +static void swap_add_folio(struct swap_io_ctx *ctx, struct folio *folio, int rw) { - struct swap_iocb *sio = ctx->sio; struct swap_info_struct *sis = __swap_entry_to_info(folio->swap); - struct file *swap_file = sis->swap_file; - loff_t pos = swap_dev_pos(folio->swap); + struct swap_iocb *sio = ctx->sio; - count_swpout_vm_event(folio); - folio_start_writeback(folio); - folio_unlock(folio); - if (sio) { - if (sio->iocb.ki_filp != swap_file || - sio->iocb.ki_pos + sio->len != pos) { + if (sio && !swap_can_merge(ctx, folio, rw)) { + if (rw == WRITE) swap_write_submit(ctx); - sio = NULL; - } + else + swap_read_submit(ctx); + sio = ctx->sio; } + if (!sio) { - sio = mempool_alloc(sio_pool, GFP_NOIO); - init_sync_kiocb(&sio->iocb, swap_file); - sio->iocb.ki_complete = sio_write_complete; - sio->iocb.ki_pos = pos; + ctx->sis = sis; + ctx->sio = sio = mempool_alloc(sio_pool, GFP_NOIO); sio->nr_bvecs = 0; sio->len = 0; } bvec_set_folio(&sio->bvecs[sio->nr_bvecs], folio, folio_size(folio), 0); sio->len += folio_size(folio); - sio->nr_bvecs += 1; - if (sio->nr_bvecs == ARRAY_SIZE(sio->bvecs)) { - swap_write_submit(ctx); - sio = NULL; + if (++sio->nr_bvecs == ARRAY_SIZE(sio->bvecs)) { + if (rw == WRITE) + swap_write_submit(ctx); + else + swap_read_submit(ctx); } - ctx->sio = sio; -} - -static void swap_writepage_bdev_sync(struct folio *folio, - struct swap_info_struct *sis) -{ - struct bio_vec bv; - struct bio bio; - - bio_init(&bio, sis->bdev, &bv, 1, REQ_OP_WRITE | REQ_SWAP); - bio.bi_iter.bi_sector = swap_folio_sector(folio); - bio_add_folio_nofail(&bio, folio, folio_size(folio), 0); - - bio_associate_blkg_from_page(&bio, folio); - count_swpout_vm_event(folio); - - folio_start_writeback(folio); - folio_unlock(folio); - - submit_bio_wait(&bio); - __end_swap_bio_write(&bio); -} - -static void swap_writepage_bdev_async(struct folio *folio, - struct swap_info_struct *sis) -{ - struct bio *bio; - - bio = bio_alloc(sis->bdev, 1, REQ_OP_WRITE | REQ_SWAP, GFP_NOIO); - bio->bi_iter.bi_sector = swap_folio_sector(folio); - bio->bi_end_io = end_swap_bio_write; - bio_add_folio_nofail(bio, folio, folio_size(folio), 0); - - bio_associate_blkg_from_page(bio, folio); - count_swpout_vm_event(folio); - folio_start_writeback(folio); - folio_unlock(folio); - submit_bio(bio); } void __swap_writepage(struct swap_io_ctx *ctx, struct folio *folio) { - struct swap_info_struct *sis = __swap_entry_to_info(folio->swap); - VM_BUG_ON_FOLIO(!folio_test_swapcache(folio), folio); - /* - * ->flags can be updated non-atomically, - * but that will never affect SWP_FS_OPS, so the data_race - * is safe. - */ - if (data_race(sis->flags & SWP_FS_OPS)) - swap_writepage_fs(ctx, folio); - /* - * ->flags can be updated non-atomically, - * but that will never affect SWP_SYNCHRONOUS_IO, so the data_race - * is safe. - */ - else if (data_race(sis->flags & SWP_SYNCHRONOUS_IO)) - swap_writepage_bdev_sync(folio, sis); - else - swap_writepage_bdev_async(folio, sis); -} -void swap_write_submit(struct swap_io_ctx *ctx) -{ - struct swap_iocb *sio = ctx->sio; - struct iov_iter from; - int ret; - - if (!sio) - return; - - iov_iter_bvec(&from, ITER_SOURCE, sio->bvecs, sio->nr_bvecs, sio->len); - ret = sio->iocb.ki_filp->f_mapping->a_ops->swap_rw(&sio->iocb, &from); - if (ret != -EIOCBQUEUED) - sio_write_complete(&sio->iocb, ret); - ctx->sio = NULL; -} - -static void sio_read_complete(struct kiocb *iocb, long ret) -{ - struct swap_iocb *sio = container_of(iocb, struct swap_iocb, iocb); - int p; - - if (ret == sio->len) { - for (p = 0; p < sio->nr_bvecs; p++) { - struct folio *folio = bvec_folio(&sio->bvecs[p]); - - count_mthp_stat(folio_order(folio), MTHP_STAT_SWPIN); - count_memcg_folio_events(folio, PSWPIN, folio_nr_pages(folio)); - folio_mark_uptodate(folio); - folio_unlock(folio); - } - count_vm_events(PSWPIN, sio->len >> PAGE_SHIFT); - } else { - for (p = 0; p < sio->nr_bvecs; p++) { - struct folio *folio = bvec_folio(&sio->bvecs[p]); - - folio_unlock(folio); - } - pr_alert_ratelimited("Read-error on swap-device\n"); - } - mempool_free(sio, sio_pool); + count_swpout_vm_event(folio); + folio_start_writeback(folio); + folio_unlock(folio); + swap_add_folio(ctx, folio, WRITE); } /* @@ -590,74 +466,6 @@ static bool swap_read_folio_zeromap(struct folio *folio) return true; } -static void swap_read_folio_fs(struct swap_io_ctx *ctx, struct folio *folio) -{ - struct swap_info_struct *sis = __swap_entry_to_info(folio->swap); - struct swap_iocb *sio = ctx->sio; - loff_t pos = swap_dev_pos(folio->swap); - - if (sio) { - if (sio->iocb.ki_filp != sis->swap_file || - sio->iocb.ki_pos + sio->len != pos) { - swap_read_submit(ctx); - sio = NULL; - } - } - if (!sio) { - sio = mempool_alloc(sio_pool, GFP_KERNEL); - init_sync_kiocb(&sio->iocb, sis->swap_file); - sio->iocb.ki_pos = pos; - sio->iocb.ki_complete = sio_read_complete; - sio->nr_bvecs = 0; - sio->len = 0; - } - bvec_set_folio(&sio->bvecs[sio->nr_bvecs], folio, folio_size(folio), 0); - sio->len += folio_size(folio); - sio->nr_bvecs += 1; - if (sio->nr_bvecs == ARRAY_SIZE(sio->bvecs)) { - swap_read_submit(ctx); - sio = NULL; - } - ctx->sio = sio; -} - -static void swap_read_folio_bdev_sync(struct folio *folio, - struct swap_info_struct *sis) -{ - struct bio_vec bv; - struct bio bio; - - bio_init(&bio, sis->bdev, &bv, 1, REQ_OP_READ); - bio.bi_iter.bi_sector = swap_folio_sector(folio); - bio_add_folio_nofail(&bio, folio, folio_size(folio), 0); - /* - * Keep this task valid during swap readpage because the oom killer may - * attempt to access it in the page fault retry time check. - */ - get_task_struct(current); - count_mthp_stat(folio_order(folio), MTHP_STAT_SWPIN); - count_memcg_folio_events(folio, PSWPIN, folio_nr_pages(folio)); - count_vm_events(PSWPIN, folio_nr_pages(folio)); - submit_bio_wait(&bio); - __end_swap_bio_read(&bio); - put_task_struct(current); -} - -static void swap_read_folio_bdev_async(struct folio *folio, - struct swap_info_struct *sis) -{ - struct bio *bio; - - bio = bio_alloc(sis->bdev, 1, REQ_OP_READ, GFP_KERNEL); - bio->bi_iter.bi_sector = swap_folio_sector(folio); - bio->bi_end_io = end_swap_bio_read; - bio_add_folio_nofail(bio, folio, folio_size(folio), 0); - count_mthp_stat(folio_order(folio), MTHP_STAT_SWPIN); - count_memcg_folio_events(folio, PSWPIN, folio_nr_pages(folio)); - count_vm_events(PSWPIN, folio_nr_pages(folio)); - submit_bio(bio); -} - void swap_read_folio(struct swap_io_ctx *ctx, struct folio *folio) { struct swap_info_struct *sis = __swap_entry_to_info(folio->swap); @@ -691,14 +499,7 @@ void swap_read_folio(struct swap_io_ctx *ctx, struct folio *folio) /* We have to read from slower devices. Increase zswap protection. */ zswap_folio_swapin(folio); - - if (data_race(sis->flags & SWP_FS_OPS)) { - swap_read_folio_fs(ctx, folio); - } else if (synchronous) { - swap_read_folio_bdev_sync(folio, sis); - } else { - swap_read_folio_bdev_async(folio, sis); - } + swap_add_folio(ctx, folio, READ); finish: if (workingset) { @@ -708,18 +509,189 @@ void swap_read_folio(struct swap_io_ctx *ctx, struct folio *folio) delayacct_swapin_end(); } -void swap_read_submit(struct swap_io_ctx *ctx) +static void swap_write_end(struct swap_iocb *sio, bool failed) +{ + int p; + + for (p = 0; p < sio->nr_bvecs; p++) { + struct page *page = sio->bvecs[p].bv_page; + + if (failed) { + set_page_dirty(page); + ClearPageReclaim(page); + } + end_page_writeback(page); + } + mempool_free(sio, sio_pool); +} + +static void swap_fs_write_complete(struct kiocb *iocb, long ret) +{ + struct swap_iocb *sio = container_of(iocb, struct swap_iocb, iocb); + bool failed = ret != sio->len; + + if (failed) { + struct page *page = sio->bvecs[0].bv_page; + + /* + * In the case of swap-over-nfs, this can be a temporary failure + * if the system has limited memory for allocating transmit + * buffers. Mark the page dirty and avoid + * folio_rotate_reclaimable but rate-limit the messages. + */ + pr_err_ratelimited("Write error %ld on dio swapfile (%llu)\n", + ret, swap_dev_pos(page_swap_entry(page))); + } + + swap_write_end(sio, failed); +} + +static void end_swap_bio_write(struct bio *bio) +{ + struct swap_iocb *sio = container_of(bio, struct swap_iocb, bio); + bool failed = !!bio->bi_status; + + if (failed) + pr_alert_ratelimited("Write-error on swap-device (%u:%u:%llu)\n", + MAJOR(bio_dev(bio)), MINOR(bio_dev(bio)), + (unsigned long long)bio->bi_iter.bi_sector); + bio_uninit(bio); + swap_write_end(sio, failed); +} + +static void swap_read_end(struct swap_iocb *sio, bool failed) +{ + int p; + + for (p = 0; p < sio->nr_bvecs; p++) { + struct folio *folio = bvec_folio(&sio->bvecs[p]); + + if (!failed) { + count_mthp_stat(folio_order(folio), MTHP_STAT_SWPIN); + count_memcg_folio_events(folio, PSWPIN, + folio_nr_pages(folio)); + folio_mark_uptodate(folio); + } + folio_unlock(folio); + } + + if (!failed) + count_vm_events(PSWPIN, sio->len >> PAGE_SHIFT); + + mempool_free(sio, sio_pool); +} + +static void swap_fs_read_complete(struct kiocb *iocb, long ret) +{ + struct swap_iocb *sio = container_of(iocb, struct swap_iocb, iocb); + bool failed = ret != sio->len; + + if (failed) + pr_alert_ratelimited("Read-error on swap-device\n"); + swap_read_end(sio, failed); +} + +static void swap_bio_read_end_io(struct bio *bio) +{ + struct swap_iocb *sio = container_of(bio, struct swap_iocb, bio); + bool failed = !!bio->bi_status; + + if (failed) + pr_alert_ratelimited("Read-error on swap-device (%u:%u:%llu)\n", + MAJOR(bio_dev(bio)), MINOR(bio_dev(bio)), + (unsigned long long)bio->bi_iter.bi_sector); + bio_uninit(bio); + swap_read_end(sio, failed); +} + +static void swap_bdev_submit_write(struct swap_io_ctx *ctx) { struct swap_iocb *sio = ctx->sio; - struct iov_iter from; + struct bio *bio = &sio->bio; + + bio_init(bio, ctx->sis->bdev, sio->bvecs, ARRAY_SIZE(sio->bvecs), + REQ_OP_WRITE | REQ_SWAP); + bio->bi_iter.bi_size = sio->len; + bio->bi_iter.bi_sector = swap_folio_sector(bio_first_folio_all(bio)); + bio_associate_blkg_from_page(bio, bio_first_folio_all(bio)); + + if (ctx->sis->flags & SWP_SYNCHRONOUS_IO) { + submit_bio_wait(bio); + end_swap_bio_write(bio); + } else { + bio->bi_end_io = end_swap_bio_write; + submit_bio(bio); + } +} + +static void swap_bdev_submit_read(struct swap_io_ctx *ctx) +{ + struct swap_iocb *sio = ctx->sio; + struct bio *bio = &sio->bio; + + bio_init(bio, ctx->sis->bdev, sio->bvecs, ARRAY_SIZE(sio->bvecs), + REQ_OP_READ); + bio->bi_iter.bi_size = sio->len; + bio->bi_iter.bi_sector = swap_folio_sector(bio_first_folio_all(bio)); + + if (ctx->sis->flags & SWP_SYNCHRONOUS_IO) { + /* + * Keep this task valid during swap readpage because the oom + * killer may attempt to access it in the page fault retry + * time check. + */ + get_task_struct(current); + submit_bio_wait(bio); + swap_bio_read_end_io(bio); + put_task_struct(current); + } else { + bio->bi_end_io = swap_bio_read_end_io; + submit_bio(bio); + } +} + +static void swap_fs_submit(struct swap_io_ctx *ctx, int rw) +{ + struct swap_iocb *sio = ctx->sio; + struct iov_iter iter; int ret; - if (!sio) + init_sync_kiocb(&sio->iocb, ctx->sis->swap_file); + sio->iocb.ki_pos = swap_dev_pos(bvec_folio(&sio->bvecs[0])->swap); + if (rw == WRITE) + sio->iocb.ki_complete = swap_fs_write_complete; + else + sio->iocb.ki_complete = swap_fs_read_complete; + + iov_iter_bvec(&iter, rw == WRITE ? ITER_SOURCE : ITER_DEST, + sio->bvecs, sio->nr_bvecs, sio->len); + ret = sio->iocb.ki_filp->f_mapping->a_ops->swap_rw(&sio->iocb, &iter); + if (ret != -EIOCBQUEUED) + sio->iocb.ki_complete(&sio->iocb, ret); +} + +void swap_write_submit(struct swap_io_ctx *ctx) +{ + if (!ctx->sio) return; - iov_iter_bvec(&from, ITER_DEST, sio->bvecs, sio->nr_bvecs, sio->len); - ret = sio->iocb.ki_filp->f_mapping->a_ops->swap_rw(&sio->iocb, &from); - if (ret != -EIOCBQUEUED) - sio_read_complete(&sio->iocb, ret); + if (ctx->sis->flags & SWP_FS_OPS) + swap_fs_submit(ctx, WRITE); + else + swap_bdev_submit_write(ctx); ctx->sio = NULL; + ctx->sis = NULL; +} + +void swap_read_submit(struct swap_io_ctx *ctx) +{ + if (!ctx->sio) + return; + + if (ctx->sis->flags & SWP_FS_OPS) + swap_fs_submit(ctx, READ); + else + swap_bdev_submit_read(ctx); + ctx->sio = NULL; + ctx->sis = NULL; } diff --git a/mm/swap.h b/mm/swap.h index 70f8e287f140..86b2a241b734 100644 --- a/mm/swap.h +++ b/mm/swap.h @@ -93,6 +93,7 @@ static inline int mem_cgroup_swappiness(struct mem_cgroup *memcg) struct swap_io_ctx { struct swap_iocb *sio; + struct swap_info_struct *sis; }; #ifdef CONFIG_SWAP diff --git a/mm/swapfile.c b/mm/swapfile.c index d7f749ad60c2..be75c995c49b 100644 --- a/mm/swapfile.c +++ b/mm/swapfile.c @@ -2959,6 +2959,10 @@ static int setup_swap_extents(struct swap_info_struct *sis, struct inode *inode = mapping->host; int ret; + ret = sio_pool_init(); + if (ret) + return ret; + if (S_ISBLK(inode->i_mode)) { ret = add_swap_extent(sis, 0, sis->max, 0); *span = sis->pages; @@ -2970,11 +2974,6 @@ static int setup_swap_extents(struct swap_info_struct *sis, if (ret < 0) return ret; sis->flags |= SWP_ACTIVATED; - if ((sis->flags & SWP_FS_OPS) && - sio_pool_init() != 0) { - destroy_swap_extents(sis, swap_file); - return -ENOMEM; - } return ret; } From 4e915b16ded278e57519e54793ab84b4a094fa83 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 13 Jul 2026 11:33:41 +0200 Subject: [PATCH 1059/1328] mm/swap: remove count_swpout_vm_event There is only one caller left, so merge it into that. Link: https://lore.kernel.org/20260713093350.2154226-5-hch@lst.de Signed-off-by: Christoph Hellwig Reviewed-by: Baoquan He Reviewed-by: Nhat Pham Cc: Baolin Wang Cc: Barry Song Cc: Chris Li Cc: Kairui Song Cc: Kemeng Shi Cc: Youngjun Park Signed-off-by: Andrew Morton --- mm/page_io.c | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/mm/page_io.c b/mm/page_io.c index 0195c25a77eb..56f21e49572e 100644 --- a/mm/page_io.c +++ b/mm/page_io.c @@ -254,19 +254,6 @@ int swap_writeout(struct swap_io_ctx *ctx, struct folio *folio) return ret; } -static inline void count_swpout_vm_event(struct folio *folio) -{ -#ifdef CONFIG_TRANSPARENT_HUGEPAGE - if (unlikely(folio_test_pmd_mappable(folio))) { - count_memcg_folio_events(folio, THP_SWPOUT, 1); - count_vm_event(THP_SWPOUT); - } -#endif - count_mthp_stat(folio_order(folio), MTHP_STAT_SWPOUT); - count_memcg_folio_events(folio, PSWPOUT, folio_nr_pages(folio)); - count_vm_events(PSWPOUT, folio_nr_pages(folio)); -} - #if defined(CONFIG_MEMCG) && defined(CONFIG_BLK_CGROUP) static struct cgroup_subsys_state *folio_memcg_blkg_css(struct folio *folio) { @@ -397,7 +384,16 @@ void __swap_writepage(struct swap_io_ctx *ctx, struct folio *folio) { VM_BUG_ON_FOLIO(!folio_test_swapcache(folio), folio); - count_swpout_vm_event(folio); +#ifdef CONFIG_TRANSPARENT_HUGEPAGE + if (unlikely(folio_test_pmd_mappable(folio))) { + count_memcg_folio_events(folio, THP_SWPOUT, 1); + count_vm_event(THP_SWPOUT); + } +#endif + count_mthp_stat(folio_order(folio), MTHP_STAT_SWPOUT); + count_memcg_folio_events(folio, PSWPOUT, folio_nr_pages(folio)); + count_vm_events(PSWPOUT, folio_nr_pages(folio)); + folio_start_writeback(folio); folio_unlock(folio); swap_add_folio(ctx, folio, WRITE); From 563597895e65113b4dafea6cacd1df23b0cf6660 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 13 Jul 2026 11:33:42 +0200 Subject: [PATCH 1060/1328] mm/swap: use swap_ops to register swap device's methods This simplifies codes and makes logic clearer. And also makes later any new swap device type being added easier to handle. Currently there are two types of swap devices: fs and bdev. [hch@lst.de: updated for the new submit and can_merge abstraction] Link: https://lore.kernel.org/20260713093350.2154226-6-hch@lst.de Signed-off-by: Baoquan He Signed-off-by: Christoph Hellwig Suggested-by: Chris Li Reviewed-by: Nhat Pham Cc: Baolin Wang Cc: Barry Song Cc: Kairui Song Cc: Kemeng Shi Cc: Youngjun Park Signed-off-by: Andrew Morton --- include/linux/swap.h | 1 + mm/page_io.c | 68 ++++++++++++++++++++++++++++---------------- mm/swap.h | 10 +++++++ mm/swapfile.c | 4 +++ 4 files changed, 58 insertions(+), 25 deletions(-) diff --git a/include/linux/swap.h b/include/linux/swap.h index b4b1c0a84c8b..5979b1427368 100644 --- a/include/linux/swap.h +++ b/include/linux/swap.h @@ -276,6 +276,7 @@ struct swap_info_struct { struct work_struct reclaim_work; /* reclaim worker */ struct list_head discard_clusters; /* discard clusters list */ struct plist_node avail_list; /* entry in swap_avail_head */ + const struct swap_ops *ops; }; static inline swp_entry_t page_swap_entry(struct page *page) diff --git a/mm/page_io.c b/mm/page_io.c index 56f21e49572e..c36b44ffe947 100644 --- a/mm/page_io.c +++ b/mm/page_io.c @@ -334,21 +334,7 @@ static bool swap_can_merge(struct swap_io_ctx *ctx, struct folio *folio, if (ctx->sis != sis) return false; - - if (sis->flags & SWP_FS_OPS) { - if (swap_dev_pos(folio->swap) != - swap_dev_pos(prev_folio->swap) + prev_folio_size) - return false; - } else { - if (swap_folio_sector(folio) != - swap_folio_sector(prev_folio) + - (prev_folio_size >> SECTOR_SHIFT)) - return false; - if (rw == WRITE && !folio_blkg_can_merge(folio, prev_folio)) - return false; - } - - return true; + return sis->ops->can_merge(folio, prev_folio, prev_folio_size, rw); } static void swap_add_folio(struct swap_io_ctx *ctx, struct folio *folio, int rw) @@ -646,6 +632,23 @@ static void swap_bdev_submit_read(struct swap_io_ctx *ctx) } } +static bool swap_bdev_can_merge(struct folio *folio, struct folio *prev_folio, + size_t prev_folio_size, int rw) +{ + if (swap_folio_sector(folio) != + swap_folio_sector(prev_folio) + (prev_folio_size >> SECTOR_SHIFT)) + return false; + if (rw == WRITE && !folio_blkg_can_merge(folio, prev_folio)) + return false; + return true; +} + +const struct swap_ops swap_bdev_ops = { + .submit_write = swap_bdev_submit_write, + .submit_read = swap_bdev_submit_read, + .can_merge = swap_bdev_can_merge, +}; + static void swap_fs_submit(struct swap_io_ctx *ctx, int rw) { struct swap_iocb *sio = ctx->sio; @@ -666,15 +669,34 @@ static void swap_fs_submit(struct swap_io_ctx *ctx, int rw) sio->iocb.ki_complete(&sio->iocb, ret); } +static void swap_fs_submit_write(struct swap_io_ctx *ctx) +{ + swap_fs_submit(ctx, WRITE); +} + +static void swap_fs_submit_read(struct swap_io_ctx *ctx) +{ + swap_fs_submit(ctx, READ); +} + +static bool swap_fs_can_merge(struct folio *folio, struct folio *prev_folio, + size_t prev_folio_size, int rw) +{ + return swap_dev_pos(folio->swap) == + swap_dev_pos(prev_folio->swap) + prev_folio_size; +} + +const struct swap_ops swap_fs_ops = { + .submit_write = swap_fs_submit_write, + .submit_read = swap_fs_submit_read, + .can_merge = swap_fs_can_merge, +}; + void swap_write_submit(struct swap_io_ctx *ctx) { if (!ctx->sio) return; - - if (ctx->sis->flags & SWP_FS_OPS) - swap_fs_submit(ctx, WRITE); - else - swap_bdev_submit_write(ctx); + ctx->sis->ops->submit_write(ctx); ctx->sio = NULL; ctx->sis = NULL; } @@ -683,11 +705,7 @@ void swap_read_submit(struct swap_io_ctx *ctx) { if (!ctx->sio) return; - - if (ctx->sis->flags & SWP_FS_OPS) - swap_fs_submit(ctx, READ); - else - swap_bdev_submit_read(ctx); + ctx->sis->ops->submit_read(ctx); ctx->sio = NULL; ctx->sis = NULL; } diff --git a/mm/swap.h b/mm/swap.h index 86b2a241b734..ffc36695d4ac 100644 --- a/mm/swap.h +++ b/mm/swap.h @@ -96,6 +96,13 @@ struct swap_io_ctx { struct swap_info_struct *sis; }; +struct swap_ops { + bool (*can_merge)(struct folio *folio, struct folio *prev_folio, + size_t prev_folio_size, int rw); + void (*submit_write)(struct swap_io_ctx *ctx); + void (*submit_read)(struct swap_io_ctx *ctx); +}; + #ifdef CONFIG_SWAP #include /* for swp_offset */ #include /* for bio_end_io_t */ @@ -483,6 +490,9 @@ static inline unsigned int folio_swap_flags(struct folio *folio) #endif /* CONFIG_SWAP */ +extern const struct swap_ops swap_bdev_ops; +extern const struct swap_ops swap_fs_ops; + int shmem_writeout(struct swap_io_ctx *ctx, struct folio *folio, struct list_head *folio_list); diff --git a/mm/swapfile.c b/mm/swapfile.c index be75c995c49b..ad623dae483b 100644 --- a/mm/swapfile.c +++ b/mm/swapfile.c @@ -2963,6 +2963,8 @@ static int setup_swap_extents(struct swap_info_struct *sis, if (ret) return ret; + sis->ops = &swap_bdev_ops; + if (S_ISBLK(inode->i_mode)) { ret = add_swap_extent(sis, 0, sis->max, 0); *span = sis->pages; @@ -2973,6 +2975,8 @@ static int setup_swap_extents(struct swap_info_struct *sis, ret = mapping->a_ops->swap_activate(sis, swap_file, span); if (ret < 0) return ret; + if (sis->flags & SWP_FS_OPS) + sis->ops = &swap_fs_ops; sis->flags |= SWP_ACTIVATED; return ret; } From 0df74c11587941b35596d1e8990dcab06bdbfeb5 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 13 Jul 2026 11:33:43 +0200 Subject: [PATCH 1061/1328] mm/swap: remove SWP_FS_OPS Provide a swap_fs_activate helper that directly sets up swap_fs_ops, and a flag in struct swap_ops to indicate of NOFS swapping is allowed. Link: https://lore.kernel.org/20260713093350.2154226-7-hch@lst.de Signed-off-by: Christoph Hellwig Cc: Baolin Wang Cc: Baoquan He Cc: Barry Song Cc: Chris Li Cc: Kairui Song Cc: Kemeng Shi Cc: Nhat Pham Cc: Youngjun Park Signed-off-by: Andrew Morton --- Documentation/filesystems/locking.rst | 5 +++-- Documentation/filesystems/vfs.rst | 4 ++-- fs/nfs/file.c | 4 +--- fs/smb/client/file.c | 4 +--- include/linux/swap.h | 6 +++++- mm/page_io.c | 10 +++++++++- mm/swap.h | 22 ++++++++++------------ mm/swapfile.c | 2 -- mm/vmscan.c | 15 +++++++-------- 9 files changed, 38 insertions(+), 34 deletions(-) diff --git a/Documentation/filesystems/locking.rst b/Documentation/filesystems/locking.rst index 08d01bc62c31..1a50d41a39a1 100644 --- a/Documentation/filesystems/locking.rst +++ b/Documentation/filesystems/locking.rst @@ -355,13 +355,14 @@ should perform any validation and preparation necessary to ensure that writes can be performed with minimal memory allocation. It should call add_swap_extent(), or the helper iomap_swapfile_activate(), and return the number of extents added. If IO should be submitted through -->swap_rw(), it should set SWP_FS_OPS, otherwise IO will be submitted +->swap_rw(), it should call swap_fs_activate, otherwise IO will be submitted directly to the block device ``sis->bdev``. ->swap_deactivate() will be called in the sys_swapoff() path after ->swap_activate() returned success. -->swap_rw will be called for swap IO if SWP_FS_OPS was set by ->swap_activate(). +->swap_rw will be called for swap IO if swap_fs_activate was called by +->swap_activate(). file_lock_operations ==================== diff --git a/Documentation/filesystems/vfs.rst b/Documentation/filesystems/vfs.rst index 7c753148af88..e7677423a20f 100644 --- a/Documentation/filesystems/vfs.rst +++ b/Documentation/filesystems/vfs.rst @@ -977,7 +977,7 @@ cache in your filesystem. The following members are defined: can be performed with minimal memory allocation. It should call add_swap_extent(), or the helper iomap_swapfile_activate(), and return the number of extents added. If IO should be submitted - through ->swap_rw(), it should set SWP_FS_OPS, otherwise IO will + through ->swap_rw(), it should call swap_fs_activate, otherwise IO will be submitted directly to the block device ``sis->bdev``. ``swap_deactivate`` @@ -985,7 +985,7 @@ cache in your filesystem. The following members are defined: successful. ``swap_rw`` - Called to read or write swap pages when SWP_FS_OPS is set. + Called to read or write swap pages when swap_fs_activate was called. The File Object =============== diff --git a/fs/nfs/file.c b/fs/nfs/file.c index a0d8f1c1cf10..851d93a09988 100644 --- a/fs/nfs/file.c +++ b/fs/nfs/file.c @@ -597,7 +597,7 @@ static int nfs_swap_activate(struct swap_info_struct *sis, struct file *file, ret = rpc_clnt_swap_activate(clnt); if (ret) return ret; - ret = add_swap_extent(sis, 0, sis->max, 0); + ret = swap_fs_activate(sis); if (ret < 0) { rpc_clnt_swap_deactivate(clnt); return ret; @@ -607,8 +607,6 @@ static int nfs_swap_activate(struct swap_info_struct *sis, struct file *file, if (cl->rpc_ops->enable_swap) cl->rpc_ops->enable_swap(inode); - - sis->flags |= SWP_FS_OPS; return ret; } diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c index b279a44be729..7f2924ce2881 100644 --- a/fs/smb/client/file.c +++ b/fs/smb/client/file.c @@ -3451,9 +3451,7 @@ static int cifs_swap_activate(struct swap_info_struct *sis, * but we could add call to grab a byte range lock to prevent others * from reading or writing the file */ - - sis->flags |= SWP_FS_OPS; - return add_swap_extent(sis, 0, sis->max, 0); + return swap_fs_activate(sis); } static void cifs_swap_deactivate(struct file *file) diff --git a/include/linux/swap.h b/include/linux/swap.h index 5979b1427368..8dd68733c955 100644 --- a/include/linux/swap.h +++ b/include/linux/swap.h @@ -202,7 +202,6 @@ enum { SWP_SOLIDSTATE = (1 << 4), /* blkdev seeks are cheap */ SWP_BLKDEV = (1 << 6), /* its a block device */ SWP_ACTIVATED = (1 << 7), /* set after swap_activate success */ - SWP_FS_OPS = (1 << 8), /* swapfile operations go through fs */ SWP_AREA_DISCARD = (1 << 9), /* single-time swap area discards */ SWP_PAGE_DISCARD = (1 << 10), /* freed swap page-cluster discards */ SWP_STABLE_WRITES = (1 << 11), /* no overwrite PG_writeback pages */ @@ -343,6 +342,7 @@ extern void __meminit kswapd_stop(int nid); #ifdef CONFIG_SWAP +int swap_fs_activate(struct swap_info_struct *sis); int add_swap_extent(struct swap_info_struct *sis, unsigned long start_page, unsigned long nr_pages, sector_t start_block); int generic_swapfile_activate(struct swap_info_struct *, struct file *, @@ -468,6 +468,10 @@ static inline bool folio_free_swap(struct folio *folio) return false; } +static inline int swap_fs_activate(struct swap_info_struct *sis) +{ + return -EINVAL; +} static inline int add_swap_extent(struct swap_info_struct *sis, unsigned long start_page, unsigned long nr_pages, sector_t start_block) diff --git a/mm/page_io.c b/mm/page_io.c index c36b44ffe947..cea438b66bce 100644 --- a/mm/page_io.c +++ b/mm/page_io.c @@ -686,12 +686,20 @@ static bool swap_fs_can_merge(struct folio *folio, struct folio *prev_folio, swap_dev_pos(prev_folio->swap) + prev_folio_size; } -const struct swap_ops swap_fs_ops = { +static const struct swap_ops swap_fs_ops = { + .flags = SWAP_OPS_F_REQUIRE_NOFS, .submit_write = swap_fs_submit_write, .submit_read = swap_fs_submit_read, .can_merge = swap_fs_can_merge, }; +int swap_fs_activate(struct swap_info_struct *sis) +{ + sis->ops = &swap_fs_ops; + return add_swap_extent(sis, 0, sis->max, 0); +} +EXPORT_SYMBOL_GPL(swap_fs_activate); + void swap_write_submit(struct swap_io_ctx *ctx) { if (!ctx->sio) diff --git a/mm/swap.h b/mm/swap.h index ffc36695d4ac..1a78578fd067 100644 --- a/mm/swap.h +++ b/mm/swap.h @@ -96,7 +96,17 @@ struct swap_io_ctx { struct swap_info_struct *sis; }; +/* + * SWAP_OPS_F_REQUIRE_NOFS: + * When set, all reclaim operations must operated as GFS_NOFS and not + * just GFP_NOIO, as GFP_NOIO allocations could recourse into the + * file system backing this swap file. + */ +#define SWAP_OPS_F_REQUIRE_NOFS (1U << 0) + struct swap_ops { + unsigned int flags; + bool (*can_merge)(struct folio *folio, struct folio *prev_folio, size_t prev_folio_size, int rw); void (*submit_write)(struct swap_io_ctx *ctx); @@ -347,11 +357,6 @@ struct folio *swapin_sync(swp_entry_t entry, gfp_t flag, unsigned long orders, void swap_update_readahead(struct folio *folio, struct vm_area_struct *vma, unsigned long addr); -static inline unsigned int folio_swap_flags(struct folio *folio) -{ - return __swap_entry_to_info(folio->swap)->flags; -} - #else /* CONFIG_SWAP */ static inline struct swap_cluster_info *swap_cluster_lock( struct swap_info_struct *si, pgoff_t offset, bool irq) @@ -482,16 +487,9 @@ static inline void __swap_cache_replace_folio(struct swap_cluster_info *ci, struct folio *old, struct folio *new) { } - -static inline unsigned int folio_swap_flags(struct folio *folio) -{ - return 0; -} - #endif /* CONFIG_SWAP */ extern const struct swap_ops swap_bdev_ops; -extern const struct swap_ops swap_fs_ops; int shmem_writeout(struct swap_io_ctx *ctx, struct folio *folio, struct list_head *folio_list); diff --git a/mm/swapfile.c b/mm/swapfile.c index ad623dae483b..dacef34a3ed7 100644 --- a/mm/swapfile.c +++ b/mm/swapfile.c @@ -2975,8 +2975,6 @@ static int setup_swap_extents(struct swap_info_struct *sis, ret = mapping->a_ops->swap_activate(sis, swap_file, span); if (ret < 0) return ret; - if (sis->flags & SWP_FS_OPS) - sis->ops = &swap_fs_ops; sis->flags |= SWP_ACTIVATED; return ret; } diff --git a/mm/vmscan.c b/mm/vmscan.c index 4742297693fe..3194da7dcc79 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -1040,16 +1040,15 @@ static bool may_enter_fs(struct folio *folio, gfp_t gfp_mask) { if (gfp_mask & __GFP_FS) return true; - if (!folio_test_swapcache(folio) || !(gfp_mask & __GFP_IO)) - return false; /* - * We can "enter_fs" for swap-cache with only __GFP_IO - * providing this isn't SWP_FS_OPS. - * ->flags can be updated non-atomically, - * but that will never affect SWP_FS_OPS, so the data_race - * is safe. + * We can "enter_fs" for swap-cache with only __GFP_IO unless backed by + * a swapfile that requires GFP_NOFS I/O. */ - return !data_race(folio_swap_flags(folio) & SWP_FS_OPS); + if (folio_test_swapcache(folio) && (gfp_mask & __GFP_IO) && + !(__swap_entry_to_info(folio->swap)->ops->flags & + SWAP_OPS_F_REQUIRE_NOFS)) + return true; + return false; } /* From c01e6df60e7be422ee5ce5e2a76d43fb05fab4c2 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 13 Jul 2026 11:33:44 +0200 Subject: [PATCH 1062/1328] mm/vmstat: add NRSWP{IN,OUT} counters Count how many swap I/Os we cause. Due to batching this can be different than the current counter number of pages written/read, and tracking this information is useful to see how efficient the batching is. The counters are added at the end of enum vm_event_item and the vmstat_text array under the assumption that the order of fields in /proc/vmstat is an ABI. If that is not the case, they could be grouped with the other swap counters. Link: https://lore.kernel.org/20260713093350.2154226-8-hch@lst.de Signed-off-by: Christoph Hellwig Reviewed-by: Nhat Pham Cc: Baolin Wang Cc: Baoquan He Cc: Barry Song Cc: Chris Li Cc: Kairui Song Cc: Kemeng Shi Cc: Youngjun Park Signed-off-by: Andrew Morton --- include/linux/vm_event_item.h | 4 ++++ mm/page_io.c | 2 ++ mm/vmstat.c | 6 +++++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/include/linux/vm_event_item.h b/include/linux/vm_event_item.h index 03fe95f5a020..2628ccda076a 100644 --- a/include/linux/vm_event_item.h +++ b/include/linux/vm_event_item.h @@ -175,6 +175,10 @@ enum vm_event_item { PGPGIN, PGPGOUT, PSWPIN, PSWPOUT, KSTACK_REST, #endif #endif /* CONFIG_DEBUG_STACK_USAGE */ +#ifdef CONFIG_SWAP + NRSWPIN, + NRSWPOUT, +#endif /* CONFIG_SWAP */ NR_VM_EVENT_ITEMS }; diff --git a/mm/page_io.c b/mm/page_io.c index cea438b66bce..e4fa7ffffe8b 100644 --- a/mm/page_io.c +++ b/mm/page_io.c @@ -704,6 +704,7 @@ void swap_write_submit(struct swap_io_ctx *ctx) { if (!ctx->sio) return; + count_vm_events(NRSWPOUT, 1); ctx->sis->ops->submit_write(ctx); ctx->sio = NULL; ctx->sis = NULL; @@ -713,6 +714,7 @@ void swap_read_submit(struct swap_io_ctx *ctx) { if (!ctx->sio) return; + count_vm_events(NRSWPIN, 1); ctx->sis->ops->submit_read(ctx); ctx->sio = NULL; ctx->sis = NULL; diff --git a/mm/vmstat.c b/mm/vmstat.c index 7d6e61a01f51..cb57714539fb 100644 --- a/mm/vmstat.c +++ b/mm/vmstat.c @@ -1502,7 +1502,11 @@ const char * const vmstat_text[] = { #if THREAD_SIZE > 65536 [I(KSTACK_REST)] = "kstack_rest", #endif -#endif +#endif /* CONFIG_DEBUG_STACK_USAGE */ +#ifdef CONFIG_SWAP + [I(NRSWPIN)] = "nrswpin", + [I(NRSWPOUT)] = "nrswpout", +#endif /* CONFIG_SWAP */ #undef I #endif /* CONFIG_VM_EVENT_COUNTERS */ }; From b090524f775b412d12c34b71d6d39fe46aa72e30 Mon Sep 17 00:00:00 2001 From: Hongfu Li Date: Fri, 17 Jul 2026 15:11:04 +0800 Subject: [PATCH 1063/1328] mm/swap: fix swap_cluster_lock() !CONFIG_SWAP stub signature mismatch The !CONFIG_SWAP stub for swap_cluster_lock() has mismatched prototype: it has an extra unused irq argument and uses pgoff_t instead of unsigned long for offset. All callers are under CONFIG_SWAP so the extra parameter is dead. Delete the unused stub function entirely. Link: https://lore.kernel.org/20260717071104.73467-1-hongfu.li@linux.dev Signed-off-by: Hongfu Li Reviewed-by: Baoquan He Acked-by: Kairui Song Cc: Barry Song Cc: Chris Li Cc: Hongfu Li Cc: Kemeng Shi Cc: Nhat Pham Signed-off-by: Andrew Morton --- mm/swap.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/mm/swap.h b/mm/swap.h index 1a78578fd067..48379b2ab202 100644 --- a/mm/swap.h +++ b/mm/swap.h @@ -358,11 +358,6 @@ void swap_update_readahead(struct folio *folio, struct vm_area_struct *vma, unsigned long addr); #else /* CONFIG_SWAP */ -static inline struct swap_cluster_info *swap_cluster_lock( - struct swap_info_struct *si, pgoff_t offset, bool irq) -{ - return NULL; -} static inline struct swap_cluster_info *swap_cluster_get_and_lock( struct folio *folio) From 1f2b4b28aafeeeded553812c70f4a8bd054ac38a Mon Sep 17 00:00:00 2001 From: Yunzhao Li Date: Thu, 2 Jul 2026 11:07:35 -0700 Subject: [PATCH 1064/1328] mm/zswap: use ratelimited stats flush in zswap_shrinker_count() zswap_shrinker_count() calls mem_cgroup_flush_stats(), which takes the global cgroup rstat lock synchronously. On machines with many CPUs and NUMA nodes, this creates severe lock contention in the kswapd reclaim path: - Multiple kswapd threads (one per NUMA node) run concurrently. - do_shrink_slab() invokes zswap_shrinker_count() for each memcg-aware shrinker pass. - Each call flushes the full cgroup rstat hierarchy under the global lock. On AMD EPYC 9684X machines (96 cores, 192 threads, 12 NUMA nodes) running production workloads with zswap enabled, perf shows 2.88% of kernel cycles in osq_lock contention from this path: 2.88% [k] osq_lock --__mutex_lock.constprop.0 --__cgroup_rstat_lock --cgroup_rstat_flush_locked --cgroup_rstat_flush --zswap_shrinker_count do_shrink_slab shrink_slab shrink_node balance_pgdat kswapd 84% of kswapd kernel cycles are spent in shrink_slab -> zswap_shrinker_count -> cgroup_rstat_flush, not in actual page reclaim (shrink_lruvec). Controlled A/B on identical hardware and workload: shrinker=Y: 2.88% osq_lock, memory PSI 1.58% shrinker=N: 0.00% osq_lock, memory PSI 0.57% eBPF-based rstat lock wait measurement across 8 production metals confirms the contention splits cleanly along shrinker enablement: shrinker=Y: 50-250x more contended lock acquisitions (248/s vs 1.1/s) shrinker=N: baseline lock wait (0.0017 s/s vs 1.04 s/s) zswap_shrinker_count() only produces a heuristic estimate, scaled by compression ratio via mult_frac(). The actual writeback happens in zswap_shrinker_scan(). Slightly stale stats are acceptable here. Switch to mem_cgroup_flush_stats_ratelimited(), which only flushes if the periodic 2-second flusher is one full cycle late. This matches the approach already used in prepare_scan_control() (mm/vmscan.c) for the same reclaim path. After applying this patch, rstat flush latency and lock wait time on shrinker=Y machines dropped to the same level as shrinker=N controls, while the zswap shrinker continues to function (pool size remains bounded under the max_pool_percent cap). Previously discussed: - Chengming Zhou (Dec 2023): rstat contention from zswap_shrinker_count [1] - Shakeel Butt (Aug 2024): zswap_shrinker_count still uses sync flush [2] - Yosry Ahmed (Aug 2024): suggested eliminating in-kernel flushers [3] - Jesper Dangaard Brouer (Sep 2024): cgroup/rstat V11 patch [4] Link: https://lore.kernel.org/20260702180908.150136-1-yunzhao@cloudflare.com Link: https://lore.kernel.org/linux-mm/20231206103935.3440502-1-zhouchengming@bytedance.com/ [1] Link: https://lore.kernel.org/linux-mm/CALvZod7LFxLCxVpOFH8b2Ppm8T40HPGMKQwX_=NPCWB_mFW+oQ@mail.gmail.com/ [2] Link: https://lore.kernel.org/linux-mm/CAJD7tkYvFyOSX+rP_FKGBhxvZiCDxtpsNp-c5CGOA-4Bq9oXSg@mail.gmail.com/ [3] Link: https://lore.kernel.org/linux-mm/172616070094.2055617.17676042522679701515.stgit@firesoul/ [4] Suggested-by: Jesper Dangaard Brouer Signed-off-by: Jesper Dangaard Brouer Signed-off-by: Yunzhao Li Tested-by: Yunzhao Li Acked-by: Johannes Weiner Acked-by: Jesper Dangaard Brouer Acked-by: Nhat Pham Cc: Chengming Zhou Cc: Shakeel Butt Cc: Yosry Ahmed Cc: Yunzhao Li Cc: Sourav Panda Signed-off-by: Andrew Morton --- mm/zswap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/zswap.c b/mm/zswap.c index c33d496bfdb2..9f777a48b106 100644 --- a/mm/zswap.c +++ b/mm/zswap.c @@ -1219,7 +1219,7 @@ static unsigned long zswap_shrinker_count(struct shrinker *shrinker, * Without memcg, use the zswap pool-wide metrics. */ if (!mem_cgroup_disabled()) { - mem_cgroup_flush_stats(memcg); + mem_cgroup_flush_stats_ratelimited(memcg); nr_backing = memcg_page_state(memcg, MEMCG_ZSWAP_B) >> PAGE_SHIFT; nr_stored = memcg_page_state(memcg, MEMCG_ZSWAPPED); } else { From 097492865fbef3af59200e31fef9148ecea80aad Mon Sep 17 00:00:00 2001 From: Hongfu Li Date: Mon, 10 Aug 2026 17:32:15 +0800 Subject: [PATCH 1065/1328] mm/cma: remove stray newline from auto-generated CMA area name When no name is supplied, cma_new_area() generates names with format "cma%d\n", introducing an unintended newline character ('\n') in the CMA name. Most CMA regions are created with explicit names, so this path is seldom hit. The newline only creates cosmetic noise in debug logs, traces and debugfs with no functional impact. Link: https://lore.kernel.org/20260810093215.91419-1-hongfu.li@linux.dev Signed-off-by: Hongfu Li Acked-by: David Hildenbrand (Arm) Reviewed-by: Lorenzo Stoakes (ARM) Reviewed-by: SJ Park Reviewed-by: Anshuman Khandual Cc: Liam R. Howlett Cc: Michal Hocko Cc: Mike Rapoport Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/cma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/cma.c b/mm/cma.c index a7929c758df1..a10ea37a261d 100644 --- a/mm/cma.c +++ b/mm/cma.c @@ -242,7 +242,7 @@ static int __init cma_new_area(const char *name, phys_addr_t size, if (name) strscpy(cma->name, name); else - snprintf(cma->name, CMA_MAX_NAME, "cma%d\n", cma_area_count); + snprintf(cma->name, CMA_MAX_NAME, "cma%d", cma_area_count); cma->available_count = cma->count = size >> PAGE_SHIFT; cma->order_per_bit = order_per_bit; From 8790303cbaac52a11dfed4aab261f8ea60682525 Mon Sep 17 00:00:00 2001 From: Hui Su Date: Sat, 8 Aug 2026 11:14:59 +0800 Subject: [PATCH 1066/1328] kasan: fix cache shrink race with CPU hotplug kasan_quarantine_remove_cache() first invokes per_cpu_remove_cache() on all online CPUs. Each callback moves objects belonging to the cache from cpu_quarantine to the CPU's shrink_qlist, where they can later be freed from task context. kmem_cache_destroy() invokes the quarantine removal path while holding cpus_read_lock(), but kmem_cache_shrink() does not. The latter can therefore race with CPU offlining as follows: kmem_cache_shrink() CPU hotplug ------------------- ----------- on_each_cpu() CPU1 moves objects to CPU1's shrink_qlist on_each_cpu() returns CPU1 goes offline kasan_cpu_offline() drains cpu_quarantine leaves shrink_qlist untouched for_each_online_cpu() skips CPU1 The objects left on CPU1's shrink_qlist are not returned to the slab allocator. This may prevent kmem_cache_shrink() from releasing slabs that would otherwise become empty. If CPU1 remains offline, a later kmem_cache_destroy() also skips the list and can report that the cache still contains objects. An intermittent occurrence was observed with a virtio-9p filesystem. The mount and umount commands both returned 0, but the kernel logged the following during the userspace-triggered teardown: [ 2994.380134][ T111] BUG 9p-fcall-cache-1 (Tainted: G B ): Objects remaining on __kmem_cache_shutdown() [ 2994.381140][ T111] Object 0xff11000004361118 @offset=4376 [ 2994.381607][ T111] Allocated in p9_fcall_init+0x201/0x400 age=19564 cpu=1 pid=104 [ 2994.382591][ T111] p9_fcall_init+0x201/0x400 [ 2994.382810][ T111] p9_tag_alloc+0x12f/0x700 [ 2994.382982][ T111] p9_client_prepare_req+0x102/0x3e0 [ 2994.383165][ T111] p9_client_rpc+0x1ab/0xa50 [ 2994.383334][ T111] p9_client_getattr_dotl+0xb0/0x1a0 [ 2994.383515][ T111] v9fs_vfs_getattr_dotl+0x115/0x360 [ 2994.383719][ T111] vfs_getattr_nosec+0x22c/0x3a0 [ 2994.383910][ T111] vfs_statx+0xd7/0x170 [ 2994.384062][ T111] vfs_fstatat+0x45/0x80 [ 2994.384215][ T111] __do_sys_newfstatat+0x84/0xe0 [ 2994.384386][ T111] do_syscall_64+0x115/0x6a0 [ 2994.384566][ T111] entry_SYSCALL_64_after_hwframe+0x77/0x7f [ 2994.399720][ T111] WARNING: mm/slub.c:1244 at __kmem_cache_shutdown+0x363/0x500, CPU#0: busybox/111 [ 2994.405655][ T111] Call Trace: [ 2994.406325][ T111] kmem_cache_destroy+0x73/0x1b0 [ 2994.406630][ T111] p9_client_destroy+0x271/0x3c0 [ 2994.407210][ T111] v9fs_session_close+0x3c/0x260 [ 2994.407409][ T111] v9fs_kill_super+0x48/0x90 [ 2994.407584][ T111] deactivate_locked_super+0xa3/0x160 [ 2994.407778][ T111] cleanup_mnt+0x1dd/0x3e0 Thus, a successful umount left objects in the 9p fcall cache and prevented the cache from being destroyed cleanly. Per-CPU shrink_qlist storage exists for every possible CPU, and each list is protected by its own raw spinlock. Iterate over possible CPUs so that a list populated before its CPU went offline is drained as well. for_each_possible_cpu() can do more work than for_each_online_cpu(), but this change only affects CONFIG_KASAN_GENERIC kernels. The extra work is limited to cache shrink and cache destruction paths and does not affect the normal allocation/free fast path. It adds one raw-spinlock-protected scan of each possible CPU's shrink list. These lists are normally empty; a non-empty list is traversed to remove objects belonging to the cache being shrunk or destroyed. Link: https://lore.kernel.org/20260808031459.3032812-1-sh_def@163.com Fixes: 07d067e4f2ce ("kasan: fix sleeping function called from invalid context on RT kernel") Signed-off-by: Hui Su Reviewed-by: Andrey Ryabinin Cc: Alexander Potapenko Cc: Andrey Konovalov Cc: Dmitry Vyukov Cc: Vincenzo Frascino Cc: "Zhang, Qiang1" Cc: Signed-off-by: Andrew Morton --- mm/kasan/quarantine.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mm/kasan/quarantine.c b/mm/kasan/quarantine.c index 6958aa713c67..16f4e67beee8 100644 --- a/mm/kasan/quarantine.c +++ b/mm/kasan/quarantine.c @@ -355,7 +355,12 @@ void kasan_quarantine_remove_cache(struct kmem_cache *cache) */ on_each_cpu(per_cpu_remove_cache, cache, 1); - for_each_online_cpu(cpu) { + /* + * A CPU can go offline after on_each_cpu() returns, leaving cache + * objects on that CPU's shrink list. Scan all possible CPUs to + * drain those lists. + */ + for_each_possible_cpu(cpu) { sq = per_cpu_ptr(&shrink_qlist, cpu); raw_spin_lock_irqsave(&sq->lock, flags); qlist_move_cache(&sq->qlist, &to_free, cache); From 534b19bbb67717eea2272573bc0ff7ba4ba109c1 Mon Sep 17 00:00:00 2001 From: "David Hildenbrand (Arm)" Date: Mon, 10 Aug 2026 13:31:14 +0200 Subject: [PATCH 1067/1328] mm/gup_test: keep longterm pin state per file The pin longterm test currently stores its data globally, shared among multiple concurrent users of the interface (multiple open file descriptors -> multiple "struct file"'s). That makes the gup_test interface problematic to use concurrently: two users, such as concurrent selftest runs, can interfere with the same longterm pin state. While this has not been observed as a problem so far in practice, let's just handle it cleanly. There could be a way to trigger selftest failures by e.g., running the cow.c and gup_longerm.c selftests concurrently, but we usually run them sequentially. Let's add a "Fixes" tag to be safe, but not need to CC stable. Link: https://lore.kernel.org/20260810-gup_test_data-v1-1-fb1d41be5bb4@kernel.org Fixes: c77369b437f9 ("mm/gup_test: start/stop/read functionality for PIN LONGTERM test") Signed-off-by: David Hildenbrand (Arm) Reported-by: yunhui cui Closes: https://lore.kernel.org/r/20260608025043.88087-1-cuiyunhui@bytedance.com Tested-by: Yunhui Cui Tested-by: Lance Yang Cc: Jason Gunthorpe Cc: John Hubbard Cc: Peter Xu Cc: Yang Li Signed-off-by: Andrew Morton --- mm/gup_test.c | 94 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 62 insertions(+), 32 deletions(-) diff --git a/mm/gup_test.c b/mm/gup_test.c index eb4c9cda16ed..44c1cdfb9c37 100644 --- a/mm/gup_test.c +++ b/mm/gup_test.c @@ -8,6 +8,12 @@ #include #include "gup_test.h" +struct gup_test_data { + struct mutex longterm_mutex; + struct page **longterm_pages; + unsigned long longterm_nr_pages; +}; + static void put_back_pages(unsigned int cmd, struct page **pages, unsigned long nr_pages, unsigned int gup_test_flags) { @@ -208,23 +214,20 @@ static int __gup_test_ioctl(unsigned int cmd, return ret; } -static DEFINE_MUTEX(pin_longterm_test_mutex); -static struct page **pin_longterm_test_pages; -static unsigned long pin_longterm_test_nr_pages; - -static inline void pin_longterm_test_stop(void) +static inline void pin_longterm_test_stop(struct gup_test_data *data) { - if (pin_longterm_test_pages) { - if (pin_longterm_test_nr_pages) - unpin_user_pages(pin_longterm_test_pages, - pin_longterm_test_nr_pages); - kvfree(pin_longterm_test_pages); - pin_longterm_test_pages = NULL; - pin_longterm_test_nr_pages = 0; + if (data->longterm_pages) { + if (data->longterm_nr_pages) + unpin_user_pages(data->longterm_pages, + data->longterm_nr_pages); + kvfree(data->longterm_pages); + data->longterm_pages = NULL; + data->longterm_nr_pages = 0; } } -static inline int pin_longterm_test_start(unsigned long arg) +static inline int pin_longterm_test_start(struct gup_test_data *data, + unsigned long arg) { long nr_pages, cur_pages, addr, remaining_pages; int gup_flags = FOLL_LONGTERM; @@ -233,7 +236,7 @@ static inline int pin_longterm_test_start(unsigned long arg) int ret = 0; bool fast; - if (pin_longterm_test_pages) + if (data->longterm_pages) return -EINVAL; if (copy_from_user(&args, (void __user *)arg, sizeof(args))) @@ -263,12 +266,12 @@ static inline int pin_longterm_test_start(unsigned long arg) return -EINTR; } - pin_longterm_test_pages = pages; - pin_longterm_test_nr_pages = 0; + data->longterm_pages = pages; + data->longterm_nr_pages = 0; - while (nr_pages - pin_longterm_test_nr_pages) { - remaining_pages = nr_pages - pin_longterm_test_nr_pages; - addr = args.addr + pin_longterm_test_nr_pages * PAGE_SIZE; + while (nr_pages - data->longterm_nr_pages) { + remaining_pages = nr_pages - data->longterm_nr_pages; + addr = args.addr + data->longterm_nr_pages * PAGE_SIZE; if (fast) cur_pages = pin_user_pages_fast(addr, remaining_pages, @@ -277,11 +280,11 @@ static inline int pin_longterm_test_start(unsigned long arg) cur_pages = pin_user_pages(addr, remaining_pages, gup_flags, pages); if (cur_pages < 0) { - pin_longterm_test_stop(); + pin_longterm_test_stop(data); ret = cur_pages; break; } - pin_longterm_test_nr_pages += cur_pages; + data->longterm_nr_pages += cur_pages; pages += cur_pages; } @@ -290,19 +293,20 @@ static inline int pin_longterm_test_start(unsigned long arg) return ret; } -static inline int pin_longterm_test_read(unsigned long arg) +static inline int pin_longterm_test_read(struct gup_test_data *data, + unsigned long arg) { __u64 user_addr; unsigned long i; - if (!pin_longterm_test_pages) + if (!data->longterm_pages) return -EINVAL; if (copy_from_user(&user_addr, (void __user *)arg, sizeof(user_addr))) return -EFAULT; - for (i = 0; i < pin_longterm_test_nr_pages; i++) { - void *addr = kmap_local_page(pin_longterm_test_pages[i]); + for (i = 0; i < data->longterm_nr_pages; i++) { + void *addr = kmap_local_page(data->longterm_pages[i]); unsigned long ret; ret = copy_to_user((void __user *)(unsigned long)user_addr, addr, @@ -318,25 +322,26 @@ static inline int pin_longterm_test_read(unsigned long arg) static long pin_longterm_test_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) { + struct gup_test_data *data = filep->private_data; int ret = -EINVAL; - if (mutex_lock_killable(&pin_longterm_test_mutex)) + if (mutex_lock_killable(&data->longterm_mutex)) return -EINTR; switch (cmd) { case PIN_LONGTERM_TEST_START: - ret = pin_longterm_test_start(arg); + ret = pin_longterm_test_start(data, arg); break; case PIN_LONGTERM_TEST_STOP: - pin_longterm_test_stop(); + pin_longterm_test_stop(data); ret = 0; break; case PIN_LONGTERM_TEST_READ: - ret = pin_longterm_test_read(arg); + ret = pin_longterm_test_read(data, arg); break; } - mutex_unlock(&pin_longterm_test_mutex); + mutex_unlock(&data->longterm_mutex); return ret; } @@ -375,15 +380,40 @@ static long gup_test_ioctl(struct file *filep, unsigned int cmd, return 0; } +static int gup_test_open(struct inode *inode, struct file *file) +{ + struct gup_test_data *data; + int ret; + + data = kzalloc_obj(*data); + if (!data) + return -ENOMEM; + + ret = nonseekable_open(inode, file); + if (ret) { + kfree(data); + return ret; + } + + mutex_init(&data->longterm_mutex); + file->private_data = data; + return 0; +} + static int gup_test_release(struct inode *inode, struct file *file) { - pin_longterm_test_stop(); + struct gup_test_data *data = file->private_data; + + pin_longterm_test_stop(data); + mutex_destroy(&data->longterm_mutex); + kfree(data); + file->private_data = NULL; return 0; } static const struct file_operations gup_test_fops = { - .open = nonseekable_open, + .open = gup_test_open, .unlocked_ioctl = gup_test_ioctl, .compat_ioctl = compat_ptr_ioctl, .release = gup_test_release, From fb496eb062306f0a447f6059012205e10ab7b900 Mon Sep 17 00:00:00 2001 From: Catalin Marinas Date: Mon, 13 Jul 2026 04:48:04 -0700 Subject: [PATCH 1068/1328] mm: kmemleak: confirm suspected leaks with a second scan Patch series "mm: kmemleak: reduce transient false positives by confirming leaks". This series combines two kmemleak enhancements that were originally submitted separately but both required rebasing after commit 79c37ae3733e9 ("mm/kmemleak: fix checksum computation for per-cpu objects"). The first feature introduces a second scan to confirm suspected leaks: https://lore.kernel.org/all/20260709173347.689607-1-catalin.marinas@arm.com/ The second feature adds a module parameter controlling the minimum number of consecutive unreferenced scans before a leak is reported, as discussed in: https://lore.kernel.org/all/20260626-kmemleak_twice-v1-0-ab28f7cc0971@debian.org/ Changes from v1: Now that commit 79c37ae3733e9 is upstream, the selftest includes an additional priming phase scan as requested by Catalin. Additionally, I've factored out the leak-detection conditional into a helper function to be more digestible for the reader's eye. This 4-patch series resolves all outstanding kmemleak issues I've been tracking. This patch (of 4): The kmemleak marking phase is not atomic. While the object graph is traversed, the kernel can modify pointers, free objects or allocate new ones. If a reference to an object is moved from one location to another, kmemleak scanning may miss it. We have explicit annotations like kmemleak_transient_leak() but identifying and maintaining them is not trivial. Given that such transient leaks are short-lived, rather than just reporting such objects as leaks, do another scan to confirm the suspected objects. If no new leaks are found during the first scan, skip the confirmation one. Link: https://lore.kernel.org/20260713-catalin_pto-v1-0-5b93b1131089@debian.org Link: https://lore.kernel.org/20260713-catalin_pto-v1-1-5b93b1131089@debian.org Signed-off-by: Catalin Marinas Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Breno Leitao Cc: David Hildenbrand Cc: Jonathan Corbet Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Geert Uytterhoeven Signed-off-by: Andrew Morton --- mm/kmemleak.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/mm/kmemleak.c b/mm/kmemleak.c index 0a6045c857d6..95bd8ccd3c5b 100644 --- a/mm/kmemleak.c +++ b/mm/kmemleak.c @@ -175,6 +175,8 @@ struct kmemleak_object { #define OBJECT_PHYS (1 << 4) /* flag set for per-CPU pointers */ #define OBJECT_PERCPU (1 << 5) +/* flag set on an object left unreferenced by the full scan, pending confirmation */ +#define OBJECT_SUSPECT (1 << 6) /* set when __remove_object() called */ #define DELSTATE_REMOVED (1 << 0) @@ -235,6 +237,8 @@ static unsigned long jiffies_min_age; static unsigned long jiffies_last_scan; /* delay between automatic memory scannings */ static unsigned long jiffies_scan_wait; +/* number of objects flagged OBJECT_SUSPECT during the current scan */ +static int nr_suspects; /* enables or disables the task stacks scanning */ static int kmemleak_stack_scan = 1; /* protects the memory scanning, parameters and debug/kmemleak file access */ @@ -1440,6 +1444,11 @@ static void update_refs(struct kmemleak_object *object) */ object->count++; if (color_gray(object)) { + /* referenced after all, no longer a suspect */ + if (object->flags & OBJECT_SUSPECT) { + object->flags &= ~OBJECT_SUSPECT; + nr_suspects--; + } /* put_object() called when removing from gray_list */ WARN_ON(!get_object(object)); list_add_tail(&object->gray_list, &gray_list); @@ -1844,16 +1853,16 @@ static void dedup_flush(struct xarray *dedup) * kernel's standard allocators. This function must be called with the * scan_mutex held. */ -static void kmemleak_scan(void) +static int __kmemleak_scan(bool full) { struct kmemleak_object *object; struct zone *zone; int __maybe_unused i; - struct xarray dedup; - int new_leaks = 0; int stop = 0; jiffies_last_scan = jiffies; + if (full) + nr_suspects = 0; /* prepare the kmemleak_object's */ rcu_read_lock(); @@ -1883,6 +1892,8 @@ static void kmemleak_scan(void) /* reset the reference count (whiten the object) */ object->count = 0; + if (full) + object->flags &= ~OBJECT_SUSPECT; if (color_gray(object) && get_object(object)) list_add_tail(&object->gray_list, &gray_list); @@ -1950,6 +1961,10 @@ static void kmemleak_scan(void) scan_gray: scan_gray_list(); + /* a confirmation scan does not look for modified objects */ + if (!full) + return nr_suspects; + /* * Check for new or unreferenced objects modified since the previous * scan and color them gray until the next scan. @@ -1972,6 +1987,11 @@ static void kmemleak_scan(void) /* color it gray temporarily */ object->count = object->min_count; list_add_tail(&object->gray_list, &gray_list); + } else if (unreferenced_object(object) && + !(object->flags & OBJECT_REPORTED)) { + /* flag the objects left unreferenced by this scan */ + object->flags |= OBJECT_SUSPECT; + nr_suspects++; } raw_spin_unlock_irq(&object->lock); } @@ -1982,12 +2002,42 @@ static void kmemleak_scan(void) */ scan_gray_list(); + return nr_suspects; +} + +/* + * Scan the memory and report the unreferenced objects as leaks. Must be + * called with the scan_mutex held. + */ +static void kmemleak_scan(void) +{ + struct kmemleak_object *object; + struct xarray dedup; + int new_leaks = 0; + + /* + * Full scan. Objects left unreferenced are flagged OBJECT_SUSPECT and + * counted in the return value; nothing to confirm or report otherwise. + */ + if (!__kmemleak_scan(true)) + return; + /* * If scanning was stopped do not report any new unreferenced objects. */ if (scan_should_stop()) return; + /* + * A live object whose only reference is moved by, for example, a + * concurrent RCU update can be missed for one scan and reported as a + * transient false positive. Scan again and only report the objects + * left unreferenced (still flagged OBJECT_SUSPECT) by both scans. + */ + __kmemleak_scan(false); + if (scan_should_stop()) + return; + /* * Scanning result reporting. When verbose printing is enabled, dedupe * by stackdepot trace_handle so each unique backtrace is logged once @@ -2015,6 +2065,7 @@ static void kmemleak_scan(void) trace_handle = 0; dedup_print = false; if (unreferenced_object(object) && + (object->flags & OBJECT_SUSPECT) && !(object->flags & OBJECT_REPORTED)) { object->flags |= OBJECT_REPORTED; if (kmemleak_verbose) { From e776db8e710165c2bc47566b62edcf36ff46bbdd Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Mon, 13 Jul 2026 04:48:05 -0700 Subject: [PATCH 1069/1328] mm: kmemleak: report leaks only after N consecutive unreferenced scans kmemleak reports an object the first scan it is found unreferenced. Its mark phase runs without stopping the rest of the kernel and without a write barrier, so a live object whose only reference is briefly invisible during a concurrent RCU update -- e.g. a VMA moved between maple tree nodes, or a page-cache xa_node -- can be seen as unreferenced for that one scan. Because an object is flagged as reported only once, such a transient race turns into a permanent false positive. Track how many consecutive scans each object has been seen unreferenced and only report it once that reaches min_unref_scans, a new module parameter. It defaults to 1, leaving the behaviour unchanged; setting it higher (e.g. 2) still reports a genuine leak, one scan later, while an object referenced again before the threshold restarts its run and is never reported. min_unref_scans can be set at boot with kmemleak.min_unref_scans= or at run-time via /sys/module/kmemleak/parameters/min_unref_scans. Link: https://lore.kernel.org/20260713-catalin_pto-v1-2-5b93b1131089@debian.org Signed-off-by: Breno Leitao Reviewed-by: Catalin Marinas Cc: David Hildenbrand Cc: Geert Uytterhoeven Cc: Jonathan Corbet Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- Documentation/dev-tools/kmemleak.rst | 8 ++++++++ mm/kmemleak.c | 13 ++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/Documentation/dev-tools/kmemleak.rst b/Documentation/dev-tools/kmemleak.rst index 7d784e03f3f9..a8a83bc69ceb 100644 --- a/Documentation/dev-tools/kmemleak.rst +++ b/Documentation/dev-tools/kmemleak.rst @@ -198,6 +198,14 @@ systems, because of pointers temporarily stored in CPU registers or stacks. Kmemleak defines MSECS_MIN_AGE (defaulting to 1000) representing the minimum age of an object to be reported as a memory leak. +The ``min_unref_scans`` module parameter (default 1) requires an object to +be seen unreferenced in that many consecutive scans before it is reported. +Keeping it at 1 preserves the historical behaviour; higher values filter +the transient false positives described above, at the cost of delaying +genuine reports by up to that many scans. It can be set at boot with +``kmemleak.min_unref_scans=`` or at run-time via +``/sys/module/kmemleak/parameters/min_unref_scans``. + Limitations and Drawbacks ------------------------- diff --git a/mm/kmemleak.c b/mm/kmemleak.c index 95bd8ccd3c5b..7afd08ed8546 100644 --- a/mm/kmemleak.c +++ b/mm/kmemleak.c @@ -151,6 +151,8 @@ struct kmemleak_object { int min_count; /* the total number of pointers found pointing to this object */ int count; + /* consecutive scans the object has been seen unreferenced */ + unsigned int unref_scans; /* checksum for detecting modified objects */ u32 checksum; depot_stack_handle_t trace_handle; @@ -234,6 +236,9 @@ static unsigned long max_percpu_addr; static struct task_struct *scan_thread; /* used to avoid reporting of recently allocated objects */ static unsigned long jiffies_min_age; +/* consecutive scans an object must stay unreferenced before reporting */ +static unsigned int min_unref_scans = 1; +module_param(min_unref_scans, uint, 0644); static unsigned long jiffies_last_scan; /* delay between automatic memory scannings */ static unsigned long jiffies_scan_wait; @@ -692,6 +697,7 @@ static struct kmemleak_object *__alloc_object(gfp_t gfp) object->excess_ref = 0; object->count = 0; /* white color initially */ object->checksum = ~0; + object->unref_scans = 0; object->del_state = 0; /* task information */ @@ -1890,6 +1896,9 @@ static int __kmemleak_scan(bool full) __paint_it(object, KMEMLEAK_BLACK); } + /* referenced last scan: restart the unreferenced run */ + if (!color_white(object)) + object->unref_scans = 0; /* reset the reference count (whiten the object) */ object->count = 0; if (full) @@ -2064,9 +2073,11 @@ static void kmemleak_scan(void) raw_spin_lock_irq(&object->lock); trace_handle = 0; dedup_print = false; + if (unreferenced_object(object) && (object->flags & OBJECT_SUSPECT) && - !(object->flags & OBJECT_REPORTED)) { + !(object->flags & OBJECT_REPORTED) && + ++object->unref_scans >= min_unref_scans) { object->flags |= OBJECT_REPORTED; if (kmemleak_verbose) { trace_handle = object->trace_handle; From 70a964bafe5541d3d319399d814526b26690cddc Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Mon, 13 Jul 2026 04:48:06 -0700 Subject: [PATCH 1070/1328] mm: kmemleak: factor leak confirmation into a helper The reporting loop in kmemleak_scan() decided whether to tag an object as a reported leak with a four-term compound condition whose last operand also had a side effect (++object->unref_scans). Mixing the candidate tests with the counter update made the check hard to read. Move the state transition into confirm_leak(): it returns true when a still-unreferenced suspect crosses min_unref_scans consecutive scans and is newly flagged OBJECT_REPORTED, leaving only the reporting bookkeeping in the caller. No functional change. Link: https://lore.kernel.org/20260713-catalin_pto-v1-3-5b93b1131089@debian.org Signed-off-by: Breno Leitao Reviewed-by: Catalin Marinas Cc: David Hildenbrand Cc: Geert Uytterhoeven Cc: Jonathan Corbet Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/kmemleak.c | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/mm/kmemleak.c b/mm/kmemleak.c index 7afd08ed8546..f63dfacee7ca 100644 --- a/mm/kmemleak.c +++ b/mm/kmemleak.c @@ -2014,6 +2014,26 @@ static int __kmemleak_scan(bool full) return nr_suspects; } +/* + * Promote a suspected object to a reported leak once it has stayed + * unreferenced for min_unref_scans consecutive scans. Called with + * object->lock held; returns true when the object is newly reported. + */ +static bool confirm_leak(struct kmemleak_object *object) +{ + if (!unreferenced_object(object) || + !(object->flags & OBJECT_SUSPECT) || + (object->flags & OBJECT_REPORTED)) + return false; + + object->unref_scans += 1; + if (object->unref_scans < min_unref_scans) + return false; + + object->flags |= OBJECT_REPORTED; + return true; +} + /* * Scan the memory and report the unreferenced objects as leaks. Must be * called with the scan_mutex held. @@ -2074,11 +2094,7 @@ static void kmemleak_scan(void) trace_handle = 0; dedup_print = false; - if (unreferenced_object(object) && - (object->flags & OBJECT_SUSPECT) && - !(object->flags & OBJECT_REPORTED) && - ++object->unref_scans >= min_unref_scans) { - object->flags |= OBJECT_REPORTED; + if (confirm_leak(object)) { if (kmemleak_verbose) { trace_handle = object->trace_handle; dedup_print = true; From 8f07855f579ae8b06a90703cd8a4d02849728890 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Mon, 13 Jul 2026 04:48:07 -0700 Subject: [PATCH 1071/1328] selftests: mm: test kmemleak's N-consecutive-scan leak confirmation Add a functional test for the min_unref_scans kmemleak module parameter. Using samples/kmemleak's helper module it checks that min_unref_scans=1 reports an orphan on the first scan, min_unref_scans=2 reports nothing on the first scan but does on the second, and that the parameter reads back what was written. It counts only the helper module's own orphans (matched by their [kmemleak_test] backtrace, with the module kept loaded so the symbols resolve) so unrelated leaks already present on the system do not perturb the result. The test skips when run as non-root, without CONFIG_DEBUG_KMEMLEAK / CONFIG_SAMPLE_KMEMLEAK, on a kernel without the parameter, or when the helper yields no detectable orphan. Link: https://lore.kernel.org/20260713-catalin_pto-v1-4-5b93b1131089@debian.org Signed-off-by: Breno Leitao Reviewed-by: Catalin Marinas Cc: David Hildenbrand Cc: Geert Uytterhoeven Cc: Jonathan Corbet Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- tools/testing/selftests/mm/Makefile | 1 + .../selftests/mm/ksft_kmemleak_confirm.sh | 132 ++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100755 tools/testing/selftests/mm/ksft_kmemleak_confirm.sh diff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile index 0f31d850707d..2d5366196e30 100644 --- a/tools/testing/selftests/mm/Makefile +++ b/tools/testing/selftests/mm/Makefile @@ -149,6 +149,7 @@ TEST_PROGS += ksft_gup_test.sh TEST_PROGS += ksft_hmm.sh TEST_PROGS += ksft_hugetlb.sh TEST_PROGS += ksft_hugevm.sh +TEST_PROGS += ksft_kmemleak_confirm.sh TEST_PROGS += ksft_kmemleak_dedup.sh TEST_PROGS += ksft_ksm.sh TEST_PROGS += ksft_ksm_numa.sh diff --git a/tools/testing/selftests/mm/ksft_kmemleak_confirm.sh b/tools/testing/selftests/mm/ksft_kmemleak_confirm.sh new file mode 100755 index 000000000000..3a8576e835c8 --- /dev/null +++ b/tools/testing/selftests/mm/ksft_kmemleak_confirm.sh @@ -0,0 +1,132 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Functional test for kmemleak's N-consecutive-scan leak confirmation +# (the min_unref_scans module parameter). +# +# kmemleak only reports an object once it has stayed unreferenced for +# min_unref_scans consecutive scans. The default of 1 reports on the first +# scan (historical behaviour); higher values filter transient false +# positives where a live object's only reference is briefly invisible to a +# single scan (e.g. an RCU tree update in flight while the scan runs). The +# test loads samples/kmemleak's helper module to create orphan allocations +# and, counting only those orphans (matched by their [kmemleak_test] +# backtrace so unrelated leaks already present on the system are ignored), +# checks that: +# - a freshly allocated object is greyed on its first scan (its checksum +# settles then), so nothing can be reported before that priming scan; +# each case below primes once first, +# - with the default threshold (min_unref_scans=1) one scan after priming +# reports the orphans, +# - raising the threshold to 2 needs two scans after priming: one is not +# enough, the second reports, +# - the parameter reads back what was written. +# +# The "one post-prime scan is not enough at min_unref_scans=2" check is the +# core regression test: raising min_unref_scans must push the report +# strictly later. Like ksft_kmemleak_dedup.sh, if the module yields no +# detectable orphan at all in the running environment the test skips rather +# than failing. +# +# Author: Breno Leitao + +# KTAP output helpers (ktap_skip_all, ktap_exit_fail_msg, ktap_test_pass, ...). +DIR="$(dirname "$(readlink -f "$0")")" +# shellcheck source=../kselftest/ktap_helpers.sh +source "${DIR}"/../kselftest/ktap_helpers.sh + +KMEMLEAK=/sys/kernel/debug/kmemleak +PARAM=/sys/module/kmemleak/parameters/min_unref_scans +MODULE=kmemleak-test +AGE=6 # seconds; must exceed kmemleak's 5s minimum object age + +ktap_print_header + +[ "$(id -u)" -eq 0 ] || { ktap_skip_all "must run as root"; exit "$KSFT_SKIP"; } +[ -r "$KMEMLEAK" ] || + { ktap_skip_all "no kmemleak debugfs (CONFIG_DEBUG_KMEMLEAK)"; exit "$KSFT_SKIP"; } +[ -w "$PARAM" ] || + { ktap_skip_all "min_unref_scans module parameter not present"; exit "$KSFT_SKIP"; } +modinfo "$MODULE" >/dev/null 2>&1 || + { ktap_skip_all "$MODULE not built (CONFIG_SAMPLE_KMEMLEAK)"; exit "$KSFT_SKIP"; } + +# kmemleak can be present but disabled at runtime (kmemleak=off boot arg, +# or it self-disabled after an internal error); a "scan" then returns +# EPERM. Probe once and skip if so. +echo scan > "$KMEMLEAK" 2>/dev/null || + { ktap_skip_all "kmemleak is disabled (check dmesg or kmemleak= boot arg)"; exit "$KSFT_SKIP"; } + +prev=$(cat "$PARAM") +# shellcheck disable=SC2317 # invoked indirectly via trap +cleanup() { + echo "$prev" > "$PARAM" 2>/dev/null # restore the parameter + echo scan=on > "$KMEMLEAK" 2>/dev/null # re-enable auto scan + rmmod "$MODULE" 2>/dev/null + echo clear > "$KMEMLEAK" 2>/dev/null +} +trap cleanup EXIT + +# Stop the automatic scan thread: only our manual scans should advance an +# object's consecutive-unreferenced run. An auto scan landing between two +# manual scans would change the result and make the test flaky. +echo scan=off > "$KMEMLEAK" 2>/dev/null + +# Create a fresh, aged set of orphan objects from the helper module's init +# path (its kmalloc/vmalloc/percpu allocations are dropped right away). +# Pre-existing reported leaks are greyed first ("clear") so only our +# orphans are counted. The module is left loaded on purpose: once it is +# unloaded its symbols are gone, so the orphan backtraces no longer resolve +# to [kmemleak_test] and could not be matched below. +gen_orphans() { + rmmod "$MODULE" 2>/dev/null + echo clear > "$KMEMLEAK" + modprobe "$MODULE" || + { ktap_skip_all "failed to load $MODULE"; exit "$KSFT_SKIP"; } + sleep "$AGE" +} + +scan() { echo scan > "$KMEMLEAK"; } + +# Number of helper-module orphans currently reported by kmemleak. Matching +# the module's own backtrace ([kmemleak_test]) keeps the count immune to +# unrelated leaks on the running system. kmemleak only lists an object here +# once it has been reported, so this reflects the confirmation gating. +count_orphans() { + c=$(grep -c '\[kmemleak_test\]' "$KMEMLEAK" 2>/dev/null) + echo "${c:-0}" +} + +# 0) the parameter reads back what was written. +echo 3 > "$PARAM" +[ "$(cat "$PARAM")" = "3" ] || ktap_exit_fail_msg "min_unref_scans did not read back as 3" + +# Priming scan: kmemleak greys a freshly allocated object on its first scan +# (its checksum settles then), so nothing can be reported until a second +# scan. Every case below runs this priming scan before counting. +prime() { scan; } + +# 1) min_unref_scans=1 (default): one scan after priming reports the +# orphans. This also establishes that the helper produces detectable +# orphans here. +echo 1 > "$PARAM" +gen_orphans +prime +scan +first=$(count_orphans) +[ "$first" -gt 0 ] || + { ktap_skip_all "$MODULE produced no detectable orphans (cannot test min_unref_scans)"; exit "$KSFT_SKIP"; } + +# 2) min_unref_scans=2: after priming, one scan is not enough (still +# gated), the second reports. The gated-scan-zero check is the core +# regression. +echo 2 > "$PARAM" +gen_orphans +prime +scan; s1=$(count_orphans) +scan; s2=$(count_orphans) +[ "$s1" -eq 0 ] || ktap_exit_fail_msg "min_unref_scans=2: $s1 orphan(s) after 1 post-prime scan (must be 0)" +[ "$s2" -gt 0 ] || ktap_exit_fail_msg "min_unref_scans=2: no report after 2 post-prime scans (false negative)" + +ktap_set_plan 1 +ktap_test_pass "min_unref_scans=1 reported $first orphan(s) one scan after priming; =2 held them one scan longer ($s1 after one scan, $s2 after two); param read-back ok" +ktap_finished From dd0dcfe8ef21fb73dbc48af5c630ef2a6f4ab27c Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Fri, 31 Jul 2026 03:13:04 -0700 Subject: [PATCH 1072/1328] mm: kmemleak: default min_unref_scans to 2 for verbose auto-scan Patch series "mm: kmemleak: default min_unref_scans to 2 for verbose kernels", v2. When CONFIG_DEBUG_KMEMLEAK_VERBOSE is set, which means the host is in auto scan mode, set min_unref_scans to 2, avoiding false positives. CONFIG_DEBUG_KMEMLEAK_VERBOSE depends on CONFIG_DEBUG_KMEMLEAK_AUTO_SCAN, so a kernel built with it already runs the scan thread periodically and the user has asked for detailed leak reports. The confirming second scan comes for free there, so default min_unref_scans to 2 in that case and keep it at 1 everywhere else. CONFIG_DEBUG_KMEMLEAK_VERBOSE defaults to n, so nothing changes for kernels that do not opt in. The other two patches bring the documentation and the selftest comments in line with the new conditional default. PS: A similar patch (v1 of this patchset) is applied to Meta's kernel, in real production hosts. This patch (of 3): min_unref_scans defers reporting an object as leaked until it has stayed unreferenced for that many consecutive scans, filtering out objects that are only transiently unreferenced during a scan. It defaults to 1, which reports on the first unreferenced scan. CONFIG_DEBUG_KMEMLEAK_VERBOSE depends on CONFIG_DEBUG_KMEMLEAK_AUTO_SCAN, so a kernel built with it runs the scan thread periodically and the user has opted into detailed leak reporting. A second confirming scan then happens on its own. Default min_unref_scans to 2 there to suppress transient false positives, and keep it at 1 otherwise, where a manually triggered scan is expected to report immediately. The value stays writable through the module parameter. CONFIG_DEBUG_KMEMLEAK_VERBOSE defaults to n, so this does not change the default for kernels that do not opt in. Link: https://lore.kernel.org/20260731-kmemleak_hardened-v2-0-7b9689ac77cb@debian.org Link: https://lore.kernel.org/20260731-kmemleak_hardened-v2-1-7b9689ac77cb@debian.org Signed-off-by: Breno Leitao Acked-by: Catalin Marinas Cc: David Hildenbrand Cc: Jonathan Corbet Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/kmemleak.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mm/kmemleak.c b/mm/kmemleak.c index f63dfacee7ca..8fa409a4f9fb 100644 --- a/mm/kmemleak.c +++ b/mm/kmemleak.c @@ -237,7 +237,8 @@ static struct task_struct *scan_thread; /* used to avoid reporting of recently allocated objects */ static unsigned long jiffies_min_age; /* consecutive scans an object must stay unreferenced before reporting */ -static unsigned int min_unref_scans = 1; +static unsigned int min_unref_scans = + IS_ENABLED(CONFIG_DEBUG_KMEMLEAK_VERBOSE) ? 2 : 1; module_param(min_unref_scans, uint, 0644); static unsigned long jiffies_last_scan; /* delay between automatic memory scannings */ From 09dde5e9bac0429fa565d3a9fcb0e80d0f668f19 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Fri, 31 Jul 2026 03:13:05 -0700 Subject: [PATCH 1073/1328] Documentation: kmemleak: document the conditional min_unref_scans default min_unref_scans now defaults to 2 when CONFIG_DEBUG_KMEMLEAK_AUTO_SCAN and CONFIG_DEBUG_KMEMLEAK_VERBOSE are both enabled, but the documentation still states that the default is unconditionally 1. Link: https://lore.kernel.org/20260731-kmemleak_hardened-v2-2-7b9689ac77cb@debian.org Signed-off-by: Breno Leitao Acked-by: Catalin Marinas Cc: David Hildenbrand Cc: Jonathan Corbet Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- Documentation/dev-tools/kmemleak.rst | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Documentation/dev-tools/kmemleak.rst b/Documentation/dev-tools/kmemleak.rst index a8a83bc69ceb..d1b690b17169 100644 --- a/Documentation/dev-tools/kmemleak.rst +++ b/Documentation/dev-tools/kmemleak.rst @@ -198,11 +198,13 @@ systems, because of pointers temporarily stored in CPU registers or stacks. Kmemleak defines MSECS_MIN_AGE (defaulting to 1000) representing the minimum age of an object to be reported as a memory leak. -The ``min_unref_scans`` module parameter (default 1) requires an object to -be seen unreferenced in that many consecutive scans before it is reported. -Keeping it at 1 preserves the historical behaviour; higher values filter -the transient false positives described above, at the cost of delaying -genuine reports by up to that many scans. It can be set at boot with +The ``min_unref_scans`` module parameter requires an object to be seen +unreferenced in that many consecutive scans before it is reported. It +defaults to 2 when CONFIG_DEBUG_KMEMLEAK_VERBOSE is enabled, where the +periodic scan thread confirms a leak on its own, and to 1 otherwise. A +value of 1 preserves the historical behaviour; higher values filter the +transient false positives described above, at the cost of delaying genuine +reports by up to that many scans. It can be set at boot with ``kmemleak.min_unref_scans=`` or at run-time via ``/sys/module/kmemleak/parameters/min_unref_scans``. From 972195eb9b4c9a70f04d5677cff799c2524786d8 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Fri, 31 Jul 2026 03:13:06 -0700 Subject: [PATCH 1074/1328] selftests/mm: kmemleak: drop stale min_unref_scans default from comments The test writes min_unref_scans explicitly for every case, so its comments describing 1 as the default are both unnecessary and, since the default is now conditional, wrong. Refer to the threshold values directly. No functional change. Link: https://lore.kernel.org/20260731-kmemleak_hardened-v2-3-7b9689ac77cb@debian.org Signed-off-by: Breno Leitao Acked-by: Catalin Marinas Cc: David Hildenbrand Cc: Jonathan Corbet Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- tools/testing/selftests/mm/ksft_kmemleak_confirm.sh | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tools/testing/selftests/mm/ksft_kmemleak_confirm.sh b/tools/testing/selftests/mm/ksft_kmemleak_confirm.sh index 3a8576e835c8..72ded5e6794c 100755 --- a/tools/testing/selftests/mm/ksft_kmemleak_confirm.sh +++ b/tools/testing/selftests/mm/ksft_kmemleak_confirm.sh @@ -5,7 +5,7 @@ # (the min_unref_scans module parameter). # # kmemleak only reports an object once it has stayed unreferenced for -# min_unref_scans consecutive scans. The default of 1 reports on the first +# min_unref_scans consecutive scans. A threshold of 1 reports on the first # scan (historical behaviour); higher values filter transient false # positives where a live object's only reference is briefly invisible to a # single scan (e.g. an RCU tree update in flight while the scan runs). The @@ -16,8 +16,7 @@ # - a freshly allocated object is greyed on its first scan (its checksum # settles then), so nothing can be reported before that priming scan; # each case below primes once first, -# - with the default threshold (min_unref_scans=1) one scan after priming -# reports the orphans, +# - at min_unref_scans=1 one scan after priming reports the orphans, # - raising the threshold to 2 needs two scans after priming: one is not # enough, the second reports, # - the parameter reads back what was written. @@ -105,9 +104,8 @@ echo 3 > "$PARAM" # scan. Every case below runs this priming scan before counting. prime() { scan; } -# 1) min_unref_scans=1 (default): one scan after priming reports the -# orphans. This also establishes that the helper produces detectable -# orphans here. +# 1) min_unref_scans=1: one scan after priming reports the orphans. This +# also establishes that the helper produces detectable orphans here. echo 1 > "$PARAM" gen_orphans prime From cdd719b3f2b88437aa5a6ca8ee2bea6f7015c35b Mon Sep 17 00:00:00 2001 From: Mark Sercombe Date: Thu, 13 Aug 2026 20:38:47 +0200 Subject: [PATCH 1075/1328] maple_tree: fix comment typo Fix a spelling mistake n a code comment. This is a comment only change with no functional impact. Link: https://lore.kernel.org/20260813183847.474357-1-sercombe.joel.mark@gmail.com Signed-off-by: Mark Sercombe Reviewed-by: Andrew Morton Reviewed-by: Liam R. Howlett (Oracle) Cc: Alice Ryhl Cc: Andrew Ballance Signed-off-by: Andrew Morton --- lib/maple_tree.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/maple_tree.c b/lib/maple_tree.c index b48bc4064ad2..a0542b491bc2 100644 --- a/lib/maple_tree.c +++ b/lib/maple_tree.c @@ -3590,7 +3590,7 @@ static inline void mas_wr_prealloc_setup(struct ma_wr_state *wr_mas) /** * mas_prealloc_calc() - Calculate number of nodes needed for a - * given store oepration + * given store operation * @wr_mas: The maple write state * @entry: The entry to store into the tree * From a8b5875741d416703e19ad8eeac6fce8a12bd6e4 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Mon, 10 Aug 2026 21:22:37 +0100 Subject: [PATCH 1076/1328] zram: fix slot lock bit position on big-endian 64-bit The slot lock is a bit operation on the whole __lock word, which flags and ac_time alias as two u32s. On little-endian the lock bit lands in the position ZRAM_ENTRY_LOCK reserves in flags, so the aliasing works out. On 64-bit big-endian it lands in ac_time instead: with ZRAM_TRACK_ENTRY_ACTIME enabled, storing the access time from mark_slot_accessed() or slot_free() wipes out the held lock bit, letting another CPU take the same slot lock; an access time value with that bit set makes the slot look locked forever. Shift the lock bit into the flags half of the word on big-endian 64-bit. Link: https://lore.kernel.org/20260810202241.2436603-1-devnexen@gmail.com Fixes: 2e8ff2f51dde ("zram: use u32 for entry ac_time tracking") Signed-off-by: David Carlier Reviewed-by: Sergey Senozhatsky Cc: Minchan Kim Cc: Signed-off-by: Andrew Morton --- drivers/block/zram/zram_drv.c | 6 +++--- drivers/block/zram/zram_drv.h | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/drivers/block/zram/zram_drv.c b/drivers/block/zram/zram_drv.c index d09fdca49cbd..a9b3bb1d3bef 100644 --- a/drivers/block/zram/zram_drv.c +++ b/drivers/block/zram/zram_drv.c @@ -74,7 +74,7 @@ static __must_check bool slot_trylock(struct zram *zram, unsigned long index) { unsigned long *lock = &zram->table[index].__lock; - if (!test_and_set_bit_lock(ZRAM_ENTRY_LOCK, lock)) { + if (!test_and_set_bit_lock(ZRAM_ENTRY_LOCK_BIT, lock)) { mutex_acquire(&zram->table_lock_map, 0, 1, _RET_IP_); lock_acquired(&zram->table_lock_map, _RET_IP_); return true; @@ -88,7 +88,7 @@ static void slot_lock(struct zram *zram, unsigned long index) unsigned long *lock = &zram->table[index].__lock; mutex_acquire(&zram->table_lock_map, 0, 0, _RET_IP_); - wait_on_bit_lock(lock, ZRAM_ENTRY_LOCK, TASK_UNINTERRUPTIBLE); + wait_on_bit_lock(lock, ZRAM_ENTRY_LOCK_BIT, TASK_UNINTERRUPTIBLE); lock_acquired(&zram->table_lock_map, _RET_IP_); } @@ -97,7 +97,7 @@ static void slot_unlock(struct zram *zram, unsigned long index) unsigned long *lock = &zram->table[index].__lock; mutex_release(&zram->table_lock_map, _RET_IP_); - clear_and_wake_up_bit(ZRAM_ENTRY_LOCK, lock); + clear_and_wake_up_bit(ZRAM_ENTRY_LOCK_BIT, lock); } static inline bool init_done(struct zram *zram) diff --git a/drivers/block/zram/zram_drv.h b/drivers/block/zram/zram_drv.h index 4fddc582f3b8..7a55d751417e 100644 --- a/drivers/block/zram/zram_drv.h +++ b/drivers/block/zram/zram_drv.h @@ -15,6 +15,7 @@ #ifndef _ZRAM_DRV_H_ #define _ZRAM_DRV_H_ +#include #include #include @@ -57,6 +58,19 @@ enum zram_pageflags { __NR_ZRAM_PAGEFLAGS, }; +/* + * The slot lock is a bit-wait lock on the whole __lock word, while + * flags and ac_time alias that word as two u32s. The lock bit must + * land in the slot that ZRAM_ENTRY_LOCK reserves in attr.flags; on + * 64-bit big-endian the flags word maps to the upper half of __lock, + * so the bit position has to be shifted up. + */ +#if defined(CONFIG_64BIT) && defined(__BIG_ENDIAN) +#define ZRAM_ENTRY_LOCK_BIT (ZRAM_ENTRY_LOCK + 32) +#else +#define ZRAM_ENTRY_LOCK_BIT ZRAM_ENTRY_LOCK +#endif + /* * Allocated for each disk page. We use bit-lock (ZRAM_ENTRY_LOCK bit * of flags) to save memory. There can be plenty of entries and standard From 1dea8e081ec3833e5150250b9fa3afd0dc8768db Mon Sep 17 00:00:00 2001 From: Jann Horn Date: Mon, 10 Aug 2026 20:10:01 +0200 Subject: [PATCH 1077/1328] mm/page-writeback: document folio_mark_dirty() locking more explicitly We have had bugs where set_page_dirty() was used on a page from GUP without appropriate locking, leading to UAF, in: - KVM, see https://lore.kernel.org/r/20260810-x86-kvm-setpagedirty-v1-1-85f180892d4f@google.com - i915, see commit 0d4bbe3d407f ("drm/i915/userptr: Try to acquire the page lock around set_page_dirty()"). - VMCI, see commit 5a16c535409f ("VMCI: Use set_page_dirty_lock() when unregistering guest memory") - kpc2000 staging driver, see commit b6d13bd9f2c1 ("staging: kpc2000: kpc_dma: Convert set_page_dirty() --> set_page_dirty_lock()") I think set_page_dirty() and folio_mark_dirty() need more explicit documentation on how they should be used with pages from GUP; so add a comment on top of set_page_dirty() and make the comment above folio_mark_dirty() more explicit. Link: https://lore.kernel.org/20260810-set-page-dirty-warnings-v2-1-1bd40fadfacd@google.com Signed-off-by: Jann Horn Reviewed-by: Jan Kara Reviewed-by: Christoph Hellwig Cc: Matthew Wilcox (Oracle) Signed-off-by: Andrew Morton --- mm/folio-compat.c | 1 + mm/page-writeback.c | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/mm/folio-compat.c b/mm/folio-compat.c index a02179a0bded..6212fdd6761a 100644 --- a/mm/folio-compat.c +++ b/mm/folio-compat.c @@ -41,6 +41,7 @@ void set_page_writeback(struct page *page) } EXPORT_SYMBOL(set_page_writeback); +/* Read the comment above folio_mark_dirty() regarding required locks! */ bool set_page_dirty(struct page *page) { return folio_mark_dirty(page_folio(page)); diff --git a/mm/page-writeback.c b/mm/page-writeback.c index 47495be68598..3a03e04b640c 100644 --- a/mm/page-writeback.c +++ b/mm/page-writeback.c @@ -2763,6 +2763,11 @@ EXPORT_SYMBOL(folio_redirty_for_writepage); * in this folio. Truncation will block on the page table lock as it * unmaps pages before removing the folio from its mapping. * + * .. DANGER:: + * Do not use this on a folio obtained from a function like + * get_user_pages_fast() without holding appropriate locks; you might want to + * use set_page_dirty_lock() or folio_mark_dirty_lock() instead. + * * Return: True if the folio was newly dirtied, false if it was already dirty. */ bool folio_mark_dirty(struct folio *folio) From f7bf5cd5b5f2b13fe2361860880c4e214c08b440 Mon Sep 17 00:00:00 2001 From: Longlong Xia Date: Sun, 9 Aug 2026 19:55:18 +0800 Subject: [PATCH 1078/1328] zsmalloc: account for handle size in class lookup zs_lookup_class_index() lets zram recompression decide whether a newly compressed object would use a smaller size class. It currently classifies the payload size directly, while zs_malloc() adds ZS_HANDLE_SIZE before selecting the class. This makes lookup disagree with allocation near size-class boundaries. With 4 KiB pages, CONFIG_ZSMALLOC_CHAIN_SIZE=8, and 64-bit handles, a 1025-to-1024-byte recompression appears to move from class 64 to class 62 although both allocations use class 64. Conversely, a 1049-to-1025-byte recompression appears to stay in class 64 although the allocations move from class 65 to class 64. As a result, zram can accept replacements with no allocation benefit or reject ones that would save memory, potentially marking the object incompressible. Factor size-class selection into lookup_size_class(), account for the handle there, and use the helper for both lookup and allocation. Link: https://lore.kernel.org/20260809115518.3791787-1-xialonglong2025@163.com Fixes: 7c2af309abd2 ("zram: add size class equals check into recompression") Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Longlong Xia Reviewed-by: Sergey Senozhatsky Cc: Minchan Kim Cc: Signed-off-by: Andrew Morton --- mm/zsmalloc.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/mm/zsmalloc.c b/mm/zsmalloc.c index 8204b76f7830..825022a7a328 100644 --- a/mm/zsmalloc.c +++ b/mm/zsmalloc.c @@ -552,6 +552,11 @@ static int get_size_class_index(int size) return min_t(int, ZS_SIZE_CLASSES - 1, idx); } +static struct size_class *lookup_size_class(struct zs_pool *pool, size_t size) +{ + return pool->size_class[get_size_class_index(size + ZS_HANDLE_SIZE)]; +} + static inline void class_stat_add(struct size_class *class, int type, unsigned long cnt) { @@ -1117,7 +1122,7 @@ unsigned int zs_lookup_class_index(struct zs_pool *pool, unsigned int size) { struct size_class *class; - class = pool->size_class[get_size_class_index(size)]; + class = lookup_size_class(pool, size); return class->index; } @@ -1407,9 +1412,7 @@ unsigned long zs_malloc(struct zs_pool *pool, size_t size, gfp_t gfp, if (!handle) return (unsigned long)ERR_PTR(-ENOMEM); - /* extra space in chunk to keep the handle */ - size += ZS_HANDLE_SIZE; - class = pool->size_class[get_size_class_index(size)]; + class = lookup_size_class(pool, size); /* class->lock effectively protects the zpage migration */ spin_lock(&class->lock); From 3c5194eb2e644b8360a368a1637bb2851be0a9c3 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Thu, 23 Jul 2026 07:46:04 +0200 Subject: [PATCH 1079/1328] mm/swap: revert to single-folio writes for synchronous swap devices Patch series "swap_ops updates", v2. This series is a follow on to the swap ops series now in mm-unstable. The first patch reintroduces direct folio writes for synchronous swap files, the other two remove the double indirect for file system based swap. This patch (of 3): Kairui Song reported that zram benefits from submitting each folio directly instead of batching up I/O because the classic LRU scanning benefits from clearing the folio writeback bit in the scan loop. Accommodate that by kicking off writes for synchronous devices for each iteration. Link: https://lore.kernel.org/20260723054622.3460249-1-hch@lst.de Link: https://lore.kernel.org/20260723054622.3460249-2-hch@lst.de Signed-off-by: Christoph Hellwig Acked-by: Usama Arif Acked-by: Chris Li Cc: Baoquan He Cc: Kairui Song Cc: Kairui Song Cc: Kemeng Shi Cc: Nhat Pham Cc: Steve French Signed-off-by: Andrew Morton --- mm/page_io.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/mm/page_io.c b/mm/page_io.c index e4fa7ffffe8b..c984a4023a65 100644 --- a/mm/page_io.c +++ b/mm/page_io.c @@ -358,7 +358,16 @@ static void swap_add_folio(struct swap_io_ctx *ctx, struct folio *folio, int rw) } bvec_set_folio(&sio->bvecs[sio->nr_bvecs], folio, folio_size(folio), 0); sio->len += folio_size(folio); - if (++sio->nr_bvecs == ARRAY_SIZE(sio->bvecs)) { + + /* + * Write out the iocb if we filled it, or if the device is synchronous. + * + * The latter is to work around expectations in the classic LRU code + * which make synchronous clearing of the folio writeback flag in the + * reclaim path beneficial. + */ + if (++sio->nr_bvecs == ARRAY_SIZE(sio->bvecs) || + (rw == WRITE && (sis->flags & SWP_SYNCHRONOUS_IO))) { if (rw == WRITE) swap_write_submit(ctx); else From 52d85ca90c7db41f8cfc72e5fe7dc62c2f983032 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Thu, 23 Jul 2026 07:46:05 +0200 Subject: [PATCH 1080/1328] mm/swap: add a new swap_ops.h header to allow for pluggable swap ops Add a new header to declare the swap_iocb, swap_ops and swap_ctx to allow for swap_ops implementations outside of mm/page_io.c. This will be used to remove the double indirection for file system-based swap. There is no functional change, just a move of the declarations. Note that there already is a swapops.h header, which is totally unrelated to struct swap_ops. The close naming is a bit unfortunate, but I could not think of a better name for this header. Link: https://lore.kernel.org/20260723054622.3460249-3-hch@lst.de Signed-off-by: Christoph Hellwig Acked-by: Chris Li Cc: Baoquan He Cc: Kairui Song Cc: Kairui Song Cc: Kemeng Shi Cc: Nhat Pham Cc: Steve French Cc: Usama Arif Signed-off-by: Andrew Morton --- MAINTAINERS | 1 + include/linux/swap_ops.h | 39 +++++++++++++++++++++++++++++++++++++++ mm/madvise.c | 1 + mm/page_io.c | 10 +--------- mm/shmem.c | 1 + mm/swap.h | 23 +---------------------- mm/swap_state.c | 1 + mm/vmscan.c | 1 + mm/zswap.c | 2 +- 9 files changed, 47 insertions(+), 32 deletions(-) create mode 100644 include/linux/swap_ops.h diff --git a/MAINTAINERS b/MAINTAINERS index 4899b81bd839..604285d848e6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17242,6 +17242,7 @@ S: Maintained F: Documentation/ABI/testing/sysfs-kernel-mm-swap F: Documentation/mm/swap-table.rst F: include/linux/swap.h +F: include/linux/swap_ops.h F: include/linux/swapfile.h F: include/linux/swapops.h F: mm/page_io.c diff --git a/include/linux/swap_ops.h b/include/linux/swap_ops.h new file mode 100644 index 000000000000..e92b4f532604 --- /dev/null +++ b/include/linux/swap_ops.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _MM_SWAP_OPS_H +#define _MM_SWAP_OPS_H + +#include /* for SWAP_CLUSTER_MAX */ + +struct swap_iocb { + union { + struct kiocb iocb; + struct bio bio; + }; + struct bio_vec bvecs[SWAP_CLUSTER_MAX]; + int nr_bvecs; + int len; +}; + +struct swap_io_ctx { + struct swap_iocb *sio; + struct swap_info_struct *sis; +}; + +/* + * SWAP_OPS_F_REQUIRE_NOFS: + * When set, all reclaim operations must operated as GFS_NOFS and not + * just GFP_NOIO, as GFP_NOIO allocations could recourse into the + * file system backing this swap file. + */ +#define SWAP_OPS_F_REQUIRE_NOFS (1U << 0) + +struct swap_ops { + unsigned int flags; + + bool (*can_merge)(struct folio *folio, struct folio *prev_folio, + size_t prev_folio_size, int rw); + void (*submit_write)(struct swap_io_ctx *ctx); + void (*submit_read)(struct swap_io_ctx *ctx); +}; + +#endif /* _MM_SWAP_OPS_H */ diff --git a/mm/madvise.c b/mm/madvise.c index 07a21ca31bad..c179938097bf 100644 --- a/mm/madvise.c +++ b/mm/madvise.c @@ -32,6 +32,7 @@ #include #include #include +#include #include diff --git a/mm/page_io.c b/mm/page_io.c index c984a4023a65..e741e67d6592 100644 --- a/mm/page_io.c +++ b/mm/page_io.c @@ -25,6 +25,7 @@ #include #include #include +#include #include "swap.h" #include "swap_table.h" @@ -300,15 +301,6 @@ static bool folio_blkg_can_merge(struct folio *folio, struct folio *prev_folio) #define bio_associate_blkg_from_page(bio, folio) do { } while (0) #endif /* CONFIG_MEMCG && CONFIG_BLK_CGROUP */ -struct swap_iocb { - union { - struct kiocb iocb; - struct bio bio; - }; - struct bio_vec bvecs[SWAP_CLUSTER_MAX]; - int nr_bvecs; - int len; -}; static mempool_t *sio_pool; int sio_pool_init(void) diff --git a/mm/shmem.c b/mm/shmem.c index 2e4dacdcce11..599665a3d6e7 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -41,6 +41,7 @@ #include #include #include +#include #include "swap.h" static struct vfsmount *shm_mnt __ro_after_init; diff --git a/mm/swap.h b/mm/swap.h index 48379b2ab202..90a551a88df6 100644 --- a/mm/swap.h +++ b/mm/swap.h @@ -10,6 +10,7 @@ struct mempolicy; struct swap_iocb; struct swap_memcg_table; +struct swap_io_ctx; #if defined(MAX_POSSIBLE_PHYSMEM_BITS) #define SWAP_CACHE_PFN_BITS (MAX_POSSIBLE_PHYSMEM_BITS - PAGE_SHIFT) @@ -91,28 +92,6 @@ static inline int mem_cgroup_swappiness(struct mem_cgroup *memcg) return READ_ONCE(vm_swappiness); } -struct swap_io_ctx { - struct swap_iocb *sio; - struct swap_info_struct *sis; -}; - -/* - * SWAP_OPS_F_REQUIRE_NOFS: - * When set, all reclaim operations must operated as GFS_NOFS and not - * just GFP_NOIO, as GFP_NOIO allocations could recourse into the - * file system backing this swap file. - */ -#define SWAP_OPS_F_REQUIRE_NOFS (1U << 0) - -struct swap_ops { - unsigned int flags; - - bool (*can_merge)(struct folio *folio, struct folio *prev_folio, - size_t prev_folio_size, int rw); - void (*submit_write)(struct swap_io_ctx *ctx); - void (*submit_read)(struct swap_io_ctx *ctx); -}; - #ifdef CONFIG_SWAP #include /* for swp_offset */ #include /* for bio_end_io_t */ diff --git a/mm/swap_state.c b/mm/swap_state.c index 5be825911e64..b76eb3d876fd 100644 --- a/mm/swap_state.c +++ b/mm/swap_state.c @@ -23,6 +23,7 @@ #include #include #include +#include #include "internal.h" #include "swap_table.h" #include "swap.h" diff --git a/mm/vmscan.c b/mm/vmscan.c index 3194da7dcc79..be6bd26e8c57 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -58,6 +58,7 @@ #include #include #include +#include #include #include diff --git a/mm/zswap.c b/mm/zswap.c index 9f777a48b106..37f34e406c8e 100644 --- a/mm/zswap.c +++ b/mm/zswap.c @@ -20,7 +20,7 @@ #include #include #include -#include +#include #include #include #include From 22779ae8175aad7c04827db44e934e53bf2bd2d4 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Thu, 23 Jul 2026 07:46:06 +0200 Subject: [PATCH 1081/1328] mm/swap: move swap_ops into file systems for file system-based swap Currently swap to and from file systems goes through two indirect calls between the swap ops and the swap_rw method. Reduce this by directly providing the swap_ops from the file system. For this refactor swap_fs_submit into a swap_fs_prepare_rw helper that initializes the iov_iter on the callers stack so that file systems can call it directly, and use that to initialize file system specific ops in the NFS and SMB clients, which then get passed to swap_fs_activate. Link: https://lore.kernel.org/20260723054622.3460249-4-hch@lst.de Signed-off-by: Christoph Hellwig Acked-by: Chris Li Cc: Baoquan He Cc: Kairui Song Cc: Kairui Song Cc: Kemeng Shi Cc: Nhat Pham Cc: Steve French Cc: Usama Arif Signed-off-by: Andrew Morton --- Documentation/filesystems/locking.rst | 9 +--- Documentation/filesystems/vfs.rst | 8 +--- fs/nfs/direct.c | 20 --------- fs/nfs/file.c | 42 ++++++++++++++++-- fs/smb/client/file.c | 63 +++++++++++++++++---------- include/linux/fs.h | 1 - include/linux/nfs_fs.h | 1 - include/linux/swap.h | 6 --- include/linux/swap_ops.h | 5 +++ mm/page_io.c | 34 +++------------ 10 files changed, 93 insertions(+), 96 deletions(-) diff --git a/Documentation/filesystems/locking.rst b/Documentation/filesystems/locking.rst index 1a50d41a39a1..f58a8d7d5897 100644 --- a/Documentation/filesystems/locking.rst +++ b/Documentation/filesystems/locking.rst @@ -266,7 +266,6 @@ prototypes:: int (*error_remove_folio)(struct address_space *, struct folio *); int (*swap_activate)(struct swap_info_struct *sis, struct file *f, sector_t *span) int (*swap_deactivate)(struct file *); - int (*swap_rw)(struct kiocb *iocb, struct iov_iter *iter); locking rules: All except dirty_folio and free_folio may block @@ -291,7 +290,6 @@ is_partially_uptodate: yes error_remove_folio: yes swap_activate: no swap_deactivate: no -swap_rw: yes, unlocks ====================== ======================== ========= =============== ->write_begin(), ->write_end() and ->read_folio() may be called from @@ -355,15 +353,12 @@ should perform any validation and preparation necessary to ensure that writes can be performed with minimal memory allocation. It should call add_swap_extent(), or the helper iomap_swapfile_activate(), and return the number of extents added. If IO should be submitted through -->swap_rw(), it should call swap_fs_activate, otherwise IO will be submitted -directly to the block device ``sis->bdev``. +the file system it should call swap_fs_activate, otherwise IO will be +submitted directly to the block device ``sis->bdev``. ->swap_deactivate() will be called in the sys_swapoff() path after ->swap_activate() returned success. -->swap_rw will be called for swap IO if swap_fs_activate was called by -->swap_activate(). - file_lock_operations ==================== diff --git a/Documentation/filesystems/vfs.rst b/Documentation/filesystems/vfs.rst index e7677423a20f..c437a342d4f3 100644 --- a/Documentation/filesystems/vfs.rst +++ b/Documentation/filesystems/vfs.rst @@ -776,7 +776,6 @@ cache in your filesystem. The following members are defined: int (*error_remove_folio)(struct mapping *mapping, struct folio *); int (*swap_activate)(struct swap_info_struct *sis, struct file *f, sector_t *span) int (*swap_deactivate)(struct file *); - int (*swap_rw)(struct kiocb *iocb, struct iov_iter *iter); }; ``read_folio`` @@ -977,16 +976,13 @@ cache in your filesystem. The following members are defined: can be performed with minimal memory allocation. It should call add_swap_extent(), or the helper iomap_swapfile_activate(), and return the number of extents added. If IO should be submitted - through ->swap_rw(), it should call swap_fs_activate, otherwise IO will - be submitted directly to the block device ``sis->bdev``. + through the file system it should call swap_fs_activate, otherwise IO + will be submitted directly to the block device ``sis->bdev``. ``swap_deactivate`` Called during swapoff on files where swap_activate was successful. -``swap_rw`` - Called to read or write swap pages when swap_fs_activate was called. - The File Object =============== diff --git a/fs/nfs/direct.c b/fs/nfs/direct.c index e626c72495e6..ccafdc1ce64d 100644 --- a/fs/nfs/direct.c +++ b/fs/nfs/direct.c @@ -145,26 +145,6 @@ static void nfs_direct_file_adjust_size_locked(struct inode *inode, } } -/** - * nfs_swap_rw - NFS address space operation for swap I/O - * @iocb: target I/O control block - * @iter: I/O buffer - * - * Perform IO to the swap-file. This is much like direct IO. - */ -int nfs_swap_rw(struct kiocb *iocb, struct iov_iter *iter) -{ - ssize_t ret; - - if (iov_iter_rw(iter) == READ) - ret = nfs_file_direct_read(iocb, iter, true); - else - ret = nfs_file_direct_write(iocb, iter, true); - if (ret < 0) - return ret; - return 0; -} - static void nfs_direct_release_pages(struct page **pages, unsigned int npages) { unsigned int i; diff --git a/fs/nfs/file.c b/fs/nfs/file.c index 851d93a09988..e1bdd10b35f1 100644 --- a/fs/nfs/file.c +++ b/fs/nfs/file.c @@ -29,9 +29,8 @@ #include #include #include -#include #include - +#include #include #include @@ -575,6 +574,38 @@ static int nfs_launder_folio(struct folio *folio) return ret; } +#ifdef CONFIG_SWAP +static void nfs_swap_submit_write(struct swap_io_ctx *ctx) +{ + struct swap_iocb *sio = ctx->sio; + struct iov_iter iter; + int ret; + + swap_fs_prepare_rw(ctx, WRITE, &iter); + ret = nfs_file_direct_write(&sio->iocb, &iter, true); + if (ret != -EIOCBQUEUED) + sio->iocb.ki_complete(&sio->iocb, ret); +} + +static void nfs_swap_submit_read(struct swap_io_ctx *ctx) +{ + struct swap_iocb *sio = ctx->sio; + struct iov_iter iter; + int ret; + + swap_fs_prepare_rw(ctx, READ, &iter); + ret = nfs_file_direct_read(&sio->iocb, &iter, true); + if (ret != -EIOCBQUEUED) + sio->iocb.ki_complete(&sio->iocb, ret); +} + +static const struct swap_ops nfs_swap_ops = { + .flags = SWAP_OPS_F_REQUIRE_NOFS, + .submit_write = nfs_swap_submit_write, + .submit_read = nfs_swap_submit_read, + .can_merge = swap_fs_can_merge, +}; + static int nfs_swap_activate(struct swap_info_struct *sis, struct file *file, sector_t *span) { @@ -597,7 +628,7 @@ static int nfs_swap_activate(struct swap_info_struct *sis, struct file *file, ret = rpc_clnt_swap_activate(clnt); if (ret) return ret; - ret = swap_fs_activate(sis); + ret = swap_fs_activate(sis, &nfs_swap_ops); if (ret < 0) { rpc_clnt_swap_deactivate(clnt); return ret; @@ -620,6 +651,10 @@ static void nfs_swap_deactivate(struct file *file) if (cl->rpc_ops->disable_swap) cl->rpc_ops->disable_swap(file_inode(file)); } +#else +#define nfs_swap_activate NULL +#define nfs_swap_deactivate NULL +#endif /* CONFIG_SWAP */ const struct address_space_operations nfs_file_aops = { .read_folio = nfs_read_folio, @@ -636,7 +671,6 @@ const struct address_space_operations nfs_file_aops = { .error_remove_folio = generic_error_remove_folio, .swap_activate = nfs_swap_activate, .swap_deactivate = nfs_swap_deactivate, - .swap_rw = nfs_swap_rw, }; /* diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c index 7f2924ce2881..ead69232ac1c 100644 --- a/fs/smb/client/file.c +++ b/fs/smb/client/file.c @@ -20,7 +20,7 @@ #include #include #include -#include +#include #include #include #include "cifsfs.h" @@ -3410,6 +3410,38 @@ void cifs_oplock_break(struct work_struct *work) cifs_done_oplock_break(cinode); } +#ifdef CONFIG_SWAP +static void cifs_swap_submit_write(struct swap_io_ctx *ctx) +{ + struct swap_iocb *sio = ctx->sio; + struct iov_iter iter; + int ret; + + swap_fs_prepare_rw(ctx, WRITE, &iter); + ret = netfs_unbuffered_write_iter_locked(&sio->iocb, &iter, NULL); + if (ret != -EIOCBQUEUED) + sio->iocb.ki_complete(&sio->iocb, ret); +} + +static void cifs_swap_submit_read(struct swap_io_ctx *ctx) +{ + struct swap_iocb *sio = ctx->sio; + struct iov_iter iter; + int ret; + + swap_fs_prepare_rw(ctx, READ, &iter); + ret = netfs_unbuffered_read_iter_locked(&sio->iocb, &iter); + if (ret != -EIOCBQUEUED) + sio->iocb.ki_complete(&sio->iocb, ret); +} + +static const struct swap_ops cifs_swap_ops = { + .flags = SWAP_OPS_F_REQUIRE_NOFS, + .submit_write = cifs_swap_submit_write, + .submit_read = cifs_swap_submit_read, + .can_merge = swap_fs_can_merge, +}; + static int cifs_swap_activate(struct swap_info_struct *sis, struct file *swap_file, sector_t *span) { @@ -3420,7 +3452,7 @@ static int cifs_swap_activate(struct swap_info_struct *sis, cifs_dbg(FYI, "swap activate\n"); - if (!swap_file->f_mapping->a_ops->swap_rw) + if (swap_file->f_mapping->a_ops != &cifs_addr_ops) /* Cannot support swap */ return -EINVAL; @@ -3451,7 +3483,7 @@ static int cifs_swap_activate(struct swap_info_struct *sis, * but we could add call to grab a byte range lock to prevent others * from reading or writing the file */ - return swap_fs_activate(sis); + return swap_fs_activate(sis, &cifs_swap_ops); } static void cifs_swap_deactivate(struct file *file) @@ -3467,26 +3499,10 @@ static void cifs_swap_deactivate(struct file *file) /* do we need to unpin (or unlock) the file */ } - -/** - * cifs_swap_rw - SMB3 address space operation for swap I/O - * @iocb: target I/O control block - * @iter: I/O buffer - * - * Perform IO to the swap-file. This is much like direct IO. - */ -static int cifs_swap_rw(struct kiocb *iocb, struct iov_iter *iter) -{ - ssize_t ret; - - if (iov_iter_rw(iter) == READ) - ret = netfs_unbuffered_read_iter_locked(iocb, iter); - else - ret = netfs_unbuffered_write_iter_locked(iocb, iter, NULL); - if (ret < 0) - return ret; - return 0; -} +#else +#define cifs_swap_activate NULL +#define cifs_swap_deactivate NULL +#endif /* CONFIG_SWAP */ const struct address_space_operations cifs_addr_ops = { .read_folio = netfs_read_folio, @@ -3503,7 +3519,6 @@ const struct address_space_operations cifs_addr_ops = { */ .swap_activate = cifs_swap_activate, .swap_deactivate = cifs_swap_deactivate, - .swap_rw = cifs_swap_rw, }; /* diff --git a/include/linux/fs.h b/include/linux/fs.h index 50ce731a2b78..87b5e9957c00 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -438,7 +438,6 @@ struct address_space_operations { int (*swap_activate)(struct swap_info_struct *sis, struct file *file, sector_t *span); void (*swap_deactivate)(struct file *file); - int (*swap_rw)(struct kiocb *iocb, struct iov_iter *iter); }; extern const struct address_space_operations empty_aops; diff --git a/include/linux/nfs_fs.h b/include/linux/nfs_fs.h index ec17e602c979..764056498eba 100644 --- a/include/linux/nfs_fs.h +++ b/include/linux/nfs_fs.h @@ -548,7 +548,6 @@ static inline const struct cred *nfs_file_cred(struct file *file) /* * linux/fs/nfs/direct.c */ -int nfs_swap_rw(struct kiocb *iocb, struct iov_iter *iter); ssize_t nfs_file_direct_read(struct kiocb *iocb, struct iov_iter *iter, bool swap); ssize_t nfs_file_direct_write(struct kiocb *iocb, diff --git a/include/linux/swap.h b/include/linux/swap.h index 8dd68733c955..5658a1634b85 100644 --- a/include/linux/swap.h +++ b/include/linux/swap.h @@ -341,8 +341,6 @@ extern void __meminit kswapd_run(int nid); extern void __meminit kswapd_stop(int nid); #ifdef CONFIG_SWAP - -int swap_fs_activate(struct swap_info_struct *sis); int add_swap_extent(struct swap_info_struct *sis, unsigned long start_page, unsigned long nr_pages, sector_t start_block); int generic_swapfile_activate(struct swap_info_struct *, struct file *, @@ -468,10 +466,6 @@ static inline bool folio_free_swap(struct folio *folio) return false; } -static inline int swap_fs_activate(struct swap_info_struct *sis) -{ - return -EINVAL; -} static inline int add_swap_extent(struct swap_info_struct *sis, unsigned long start_page, unsigned long nr_pages, sector_t start_block) diff --git a/include/linux/swap_ops.h b/include/linux/swap_ops.h index e92b4f532604..57ac6c703f68 100644 --- a/include/linux/swap_ops.h +++ b/include/linux/swap_ops.h @@ -36,4 +36,9 @@ struct swap_ops { void (*submit_read)(struct swap_io_ctx *ctx); }; +void swap_fs_prepare_rw(struct swap_io_ctx *ctx, int rw, struct iov_iter *iter); +bool swap_fs_can_merge(struct folio *folio, struct folio *prev_folio, + size_t prev_folio_size, int rw); +int swap_fs_activate(struct swap_info_struct *sis, const struct swap_ops *ops); + #endif /* _MM_SWAP_OPS_H */ diff --git a/mm/page_io.c b/mm/page_io.c index e741e67d6592..88962571cb93 100644 --- a/mm/page_io.c +++ b/mm/page_io.c @@ -650,11 +650,9 @@ const struct swap_ops swap_bdev_ops = { .can_merge = swap_bdev_can_merge, }; -static void swap_fs_submit(struct swap_io_ctx *ctx, int rw) +void swap_fs_prepare_rw(struct swap_io_ctx *ctx, int rw, struct iov_iter *iter) { struct swap_iocb *sio = ctx->sio; - struct iov_iter iter; - int ret; init_sync_kiocb(&sio->iocb, ctx->sis->swap_file); sio->iocb.ki_pos = swap_dev_pos(bvec_folio(&sio->bvecs[0])->swap); @@ -663,40 +661,22 @@ static void swap_fs_submit(struct swap_io_ctx *ctx, int rw) else sio->iocb.ki_complete = swap_fs_read_complete; - iov_iter_bvec(&iter, rw == WRITE ? ITER_SOURCE : ITER_DEST, + iov_iter_bvec(iter, rw == WRITE ? ITER_SOURCE : ITER_DEST, sio->bvecs, sio->nr_bvecs, sio->len); - ret = sio->iocb.ki_filp->f_mapping->a_ops->swap_rw(&sio->iocb, &iter); - if (ret != -EIOCBQUEUED) - sio->iocb.ki_complete(&sio->iocb, ret); } +EXPORT_SYMBOL_GPL(swap_fs_prepare_rw); -static void swap_fs_submit_write(struct swap_io_ctx *ctx) -{ - swap_fs_submit(ctx, WRITE); -} - -static void swap_fs_submit_read(struct swap_io_ctx *ctx) -{ - swap_fs_submit(ctx, READ); -} - -static bool swap_fs_can_merge(struct folio *folio, struct folio *prev_folio, +bool swap_fs_can_merge(struct folio *folio, struct folio *prev_folio, size_t prev_folio_size, int rw) { return swap_dev_pos(folio->swap) == swap_dev_pos(prev_folio->swap) + prev_folio_size; } +EXPORT_SYMBOL_GPL(swap_fs_can_merge); -static const struct swap_ops swap_fs_ops = { - .flags = SWAP_OPS_F_REQUIRE_NOFS, - .submit_write = swap_fs_submit_write, - .submit_read = swap_fs_submit_read, - .can_merge = swap_fs_can_merge, -}; - -int swap_fs_activate(struct swap_info_struct *sis) +int swap_fs_activate(struct swap_info_struct *sis, const struct swap_ops *ops) { - sis->ops = &swap_fs_ops; + sis->ops = ops; return add_swap_extent(sis, 0, sis->max, 0); } EXPORT_SYMBOL_GPL(swap_fs_activate); From bd1ad3cf07d11fbf203ac362222807113321dfbb Mon Sep 17 00:00:00 2001 From: Hui Su Date: Tue, 11 Aug 2026 15:33:32 +0800 Subject: [PATCH 1082/1328] kasan: fix quarantine_size accounting during cache removal quarantine_size tracks the total number of bytes stored in global_quarantine[]. It is incremented when per-CPU quarantine objects are moved into the global quarantine and decremented when a global batch is evicted by kasan_quarantine_reduce(). kasan_quarantine_remove_cache() also removes objects from the global quarantine. qlist_move_cache() rebuilds the source batch and updates its .bytes field, but quarantine_size is not adjusted accordingly. As a result, quarantine_size remains over-counted by the size of the removed objects. The stale accounting accumulates across cache removals. Once the inflated value exceeds quarantine_max_size, kasan_quarantine_reduce() can evict a batch even though the actual number of bytes in global_quarantine[] is still below quarantine_max_size, shortening the quarantine window. Fix the accounting by recording each batch's size before qlist_move_cache() and subtracting the number of bytes actually removed from quarantine_size while holding quarantine_lock. A KUnit reproducer used during testing observed the over-count grow by 4698864 bytes after one kasan_quarantine_remove_cache() call with the fix reverted. With this change applied, the over-count did not grow. Link: https://lore.kernel.org/20260811073332.1351893-1-sh_def@163.com Fixes: 64abdcb24351 ("kasan: eliminate long stalls during quarantine reduction") Signed-off-by: Hui Su Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260808031459.3032812-1-sh_def%40163.com Reviewed-by: Andrey Ryabinin Cc: Alexander Potapenko Cc: Andrey Konovalov Cc: Dmitry Vyukov Cc: Vincenzo Frascino Signed-off-by: Andrew Morton --- mm/kasan/quarantine.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mm/kasan/quarantine.c b/mm/kasan/quarantine.c index 16f4e67beee8..c9944fdf48ca 100644 --- a/mm/kasan/quarantine.c +++ b/mm/kasan/quarantine.c @@ -370,9 +370,14 @@ void kasan_quarantine_remove_cache(struct kmem_cache *cache) raw_spin_lock_irqsave(&quarantine_lock, flags); for (i = 0; i < QUARANTINE_BATCHES; i++) { + size_t old_bytes; + if (qlist_empty(&global_quarantine[i])) continue; + old_bytes = global_quarantine[i].bytes; qlist_move_cache(&global_quarantine[i], &to_free, cache); + WRITE_ONCE(quarantine_size, quarantine_size - + (old_bytes - global_quarantine[i].bytes)); /* Scanning whole quarantine can take a while. */ raw_spin_unlock_irqrestore(&quarantine_lock, flags); cond_resched(); From 6f615890b84820c2e223bd14238319f0415eae88 Mon Sep 17 00:00:00 2001 From: Wilson Felipe Pereira Date: Tue, 11 Aug 2026 05:14:11 +0000 Subject: [PATCH 1083/1328] selftests/cgroup: test_zswap: skip test_no_kmem_bypass if debugfs is unavailable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit test_no_kmem_bypass() needs to read /sys/kernel/debug/zswap/stored_pages via get_zswap_stored_pages() to verify that compressed pages are charged to the memcg. When running in an environment where debugfs is not mounted or CONFIG_DEBUG_FS is disabled, get_zswap_stored_pages() fails, causing the loop to terminate early and report a false negative (KSFT_FAIL). Selftests should not fail if debugfs is unavailable, and it should print a message when it is skipped. While I'm here, also add a warning message if the test is being skipped due to totalram size and make the check for totalram more readable. [akpm@linux-foundation.org: clarify debugfs-unavailable error message] Link: https://lore.kernel.org/20260812050848.848882-1-wfelipe@google.com Link: https://lore.kernel.org/20260811051434.3805648-1-wfelipe@google.com Signed-off-by: Wilson Felipe Pereira Reviewed-by: Anshuman Khandual Reviewed-by: SJ Park Cc: Chengming Zhou Cc: Johannes Weiner Cc: Michal Koutný Cc: Nhat Pham Cc: Shuah Khan Cc: Tejun Heo Signed-off-by: Andrew Morton --- .../selftests/cgroup/lib/include/cgroup_util.h | 1 + tools/testing/selftests/cgroup/test_zswap.c | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/cgroup/lib/include/cgroup_util.h b/tools/testing/selftests/cgroup/lib/include/cgroup_util.h index febc1723d090..c0f07226b222 100644 --- a/tools/testing/selftests/cgroup/lib/include/cgroup_util.h +++ b/tools/testing/selftests/cgroup/lib/include/cgroup_util.h @@ -7,6 +7,7 @@ #endif #define MB(x) (x << 20) +#define GB(x) ((unsigned long long)(x) << 30) #define USEC_PER_SEC 1000000L #define NSEC_PER_SEC 1000000000L diff --git a/tools/testing/selftests/cgroup/test_zswap.c b/tools/testing/selftests/cgroup/test_zswap.c index 49b36ee79160..f7b4c4370db6 100644 --- a/tools/testing/selftests/cgroup/test_zswap.c +++ b/tools/testing/selftests/cgroup/test_zswap.c @@ -20,6 +20,7 @@ static int page_size; #define PATH_ZSWAP "/sys/module/zswap" #define PATH_ZSWAP_ENABLED "/sys/module/zswap/parameters/enabled" +#define PATH_ZSWAP_STORED_PAGES "/sys/kernel/debug/zswap/stored_pages" static int read_int(const char *path, size_t *value) { @@ -55,7 +56,7 @@ static int read_min_free_kb(size_t *value) static int get_zswap_stored_pages(size_t *value) { - return read_int("/sys/kernel/debug/zswap/stored_pages", value); + return read_int(PATH_ZSWAP_STORED_PAGES, value); } static long get_cg_wb_count(const char *cg) @@ -570,8 +571,16 @@ static int test_no_kmem_bypass(const char *root) /* Read sys info and compute test values accordingly */ if (sysinfo(&sys_info) != 0) return KSFT_FAIL; - if (sys_info.totalram > 5000000000) + if (sys_info.totalram > GB(4)) { + ksft_print_msg( + "requires less than 4GB total ram, sys_info.totalram: %.1fGB\n", + (double)sys_info.totalram / GB(1)); return KSFT_SKIP; + } + if (access(PATH_ZSWAP_STORED_PAGES, R_OK)) { + ksft_print_msg("debugfs not mounted at /sys/kernel/debug\n"); + return KSFT_SKIP; + } values = mmap(0, sizeof(struct no_kmem_bypass_child_args), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); if (values == MAP_FAILED) From 508537753b5ca06c1de9658239648510c9a999cc Mon Sep 17 00:00:00 2001 From: Hui Zhu Date: Wed, 12 Aug 2026 14:59:33 +0800 Subject: [PATCH 1084/1328] mm/mglru: fix young counter undercount for large folios lru_gen_look_around() feeds its local 'young' counter into suitable_to_scan(), which decides whether the current PMD is added to the bloom filter and checked again on the next aging round. The folio triggering the look-around is processed at function entry: test_and_clear_young_ptes_notify() clears the accessed bits of the nr PTEs it maps, and the function bails out if none of them is young. The loop that follows therefore never recounts this folio, since its accessed bits are already cleared. Every other young folio the loop finds is accounted as a batch (young += nr), where nr is the number of consecutive PTEs it maps. The triggering folio, however, still contributes a fixed young = 1 regardless of its size -- a leftover from before PTE batching. A large triggering folio is thus accounted inconsistently with the rest of the window. Initialize young to nr so the triggering folio is accounted the same way as any other young folio batch in the loop. Note this is a deliberate overestimate, not a measured value. The test-and-clear helper only reports whether any of the nr PTEs is young, not how many were accessed, so the true number of accessed PTEs in a large folio is unknown and can be smaller than nr. Counting the full batch is intentional: the mm core tracks accessed/dirty state per folio, not per page, so a per-page count is neither obtainable nor meaningful. The only consumer is suitable_to_scan(), and the bloom filter it feeds tolerates error. Overestimating is also the safe direction: at worst a PMD that saw little access is rescanned, whereas underestimating could skip rescanning a PMD whose folios are still hot and reclaim them incorrectly. (nr here is the PTE batch size, not necessarily folio_nr_pages().) Link: https://lore.kernel.org/20260813061019.49806-1-hui.zhu@linux.dev Link: https://lore.kernel.org/20260812065933.103627-1-hui.zhu@linux.dev Fixes: 56e5b60b2114 ("mm: support batched checking of the young flag for MGLRU") Signed-off-by: Hui Zhu Reviewed-by: Baolin Wang Reviewed-by: Barry Song Cc: Axel Rasmussen Cc: David Hildenbrand Cc: Johannes Weiner Cc: Kairui Song Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Shakeel Butt Cc: Wei Xu Cc: Yuanchu Xie Signed-off-by: Andrew Morton --- mm/vmscan.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/vmscan.c b/mm/vmscan.c index be6bd26e8c57..6c35f7e21465 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -4261,7 +4261,7 @@ bool lru_gen_look_around(struct page_vma_mapped_walk *pvmw, unsigned int nr) unsigned long end; struct lru_gen_mm_walk *walk; struct folio *last = NULL; - int young = 1; + int young = nr; pte_t *pte = pvmw->pte; unsigned long addr = pvmw->address; struct vm_area_struct *vma = pvmw->vma; From 3372b6631b52b3442a1e3fe379bacc433bd7eec8 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Wed, 12 Aug 2026 08:43:31 +0100 Subject: [PATCH 1085/1328] MAINTAINERS: add drivers/char/mem.c to mm misc, memory mapping sections This file is a 'special' driver that implements /dev/zero and /dev/mem among other things. As such it makes sense for mm to be cc'd on mails and to have some say in how things are changed there, so add it to the mm misc section. Uniquely, it provides the 'old way' of obtaining an anonymous mapping - MAP_PRIVATE of /dev/zero - so is directly tied to memory mapping, therefore also add it to the memory mapping section. scripts/get_maintainer.pl copes perfectly fine with files in multiple sections so everything should work correctly. Link: https://lore.kernel.org/20260812-add-drivers-mem-to-mm-maintainers-v1-1-6218b861f4c8@kernel.org Signed-off-by: Lorenzo Stoakes (ARM) Acked-by: David Hildenbrand (Arm) Acked-by: Mike Rapoport (Microsoft) Acked-by: SJ Park Reviewed-by: Anshuman Khandual Cc: Arnd Bergmann Cc: Jann Horn Cc: Liam R. Howlett Cc: Michal Hocko Cc: Mike Rapoport Cc: Pedro Falcato Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- MAINTAINERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 604285d848e6..0f513b42bc18 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17115,6 +17115,7 @@ F: Documentation/ABI/testing/sysfs-kernel-mm-memory-tiers F: Documentation/ABI/testing/sysfs-kernel-mm-numa F: Documentation/admin-guide/mm/ F: Documentation/mm/ +F: drivers/char/mem.c F: include/linux/cma.h F: include/linux/dmapool.h F: include/linux/ioremap.h @@ -17319,6 +17320,7 @@ L: linux-mm@kvack.org S: Maintained W: http://www.linux-mm.org T: git git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm +F: drivers/char/mem.c F: include/trace/events/mmap.h F: fs/proc/task_mmu.c F: fs/proc/task_nommu.c From b86a7d03ea5368a0943afe7e0648ce3edf83eef8 Mon Sep 17 00:00:00 2001 From: "Nico Pache (Red Hat)" Date: Tue, 11 Aug 2026 06:48:33 -0600 Subject: [PATCH 1086/1328] mm/khugepaged: refactor per-scan state clearing into collapse_control_init_scan() Patch series "mm/khugepaged: several cleanups", v4. The following changes stem from a number of reviews during my khugepaged mTHP support series [1]. Some of these are minor code cleanups, issues or reviews that we decided to deferred to a followup series, or in the case of the more major patch of the series, changes [2] Lance Yang attempted while my series was in-flight and we decided to wait till later to try. The first 3 patches introduce helper functions to increase code reuse and readability. This includes a per-scan state clearing function, extracting the young page check into a helper, and a count_collapse_event() function to reduce a repetative pattern used across mTHP collapse. The 4th patch was the byproduct of me throwing Claude at all the comments in khugepaged verifying and looking for any outdated info. The 5th patch is based on Lance Yang's commit series [2] trying to extract the PTE state checking into a helper function. This required a bit of rewriting due to differences after mTHP collapse was introduced. I also took into account the changes requested during his patches review cycle. The remaining 2 patches were review points during my mTHP series that we agreed can be deferred to a later series. Thank you to those whos reviews and work I leveraged to achieve these cleanups. This patch (of 6): Extract the repeated clearing of node_load, alloc_nmask, and mthp_present_ptes into a helper to reduce duplication in collapse_scan_pmd() and collapse_scan_file(). Althought file scans do not current use the bitmap, they will in the future, and clearing it now is harmless. Link: https://lore.kernel.org/20260811-khugepaged_pte_refactor-v4-0-ddac39d61c4a@linux.dev Link: https://lore.kernel.org/20260811-khugepaged_pte_refactor-v4-1-ddac39d61c4a@linux.dev Link: https://lore.kernel.org/all/20260605161422.213817-1-npache@redhat.com/ [1] Link: https://lore.kernel.org/all/20251008043748.45554-1-lance.yang@linux.dev/ [2] Signed-off-by: Nico Pache (Red Hat) Reviewed-by: Baolin Wang Acked-by: Usama Arif Acked-by: David Hildenbrand (Arm) Reviewed-by: Lorenzo Stoakes (ARM) Reviewed-by: Zi Yan Reviewed-by: Pedro Falcato Reviewed-by: Lance Yang Cc: Barry Song Cc: Dev Jain Cc: Jonathan Corbet Cc: Liam R. Howlett Cc: Michal Hocko Cc: Mike Rapoport Cc: Ryan Roberts Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/khugepaged.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/mm/khugepaged.c b/mm/khugepaged.c index b237f6e7662a..1e26ea97381a 100644 --- a/mm/khugepaged.c +++ b/mm/khugepaged.c @@ -629,6 +629,13 @@ void __khugepaged_exit(struct mm_struct *mm) } } +static void collapse_control_init_scan(struct collapse_control *cc) +{ + memset(cc->node_load, 0, sizeof(cc->node_load)); + nodes_clear(cc->alloc_nmask); + bitmap_zero(cc->mthp_present_ptes, MAX_PTRS_PER_PTE); +} + static void release_pte_folio(struct folio *folio) { node_stat_mod_folio(folio, @@ -1617,9 +1624,7 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm, goto out; } - bitmap_zero(cc->mthp_present_ptes, MAX_PTRS_PER_PTE); - memset(cc->node_load, 0, sizeof(cc->node_load)); - nodes_clear(cc->alloc_nmask); + collapse_control_init_scan(cc); enabled_orders = collapse_possible_orders(vma, vma->vm_flags, tva_flags); @@ -2691,8 +2696,7 @@ static enum scan_result collapse_scan_file(struct mm_struct *mm, present = 0; swap = 0; - memset(cc->node_load, 0, sizeof(cc->node_load)); - nodes_clear(cc->alloc_nmask); + collapse_control_init_scan(cc); rcu_read_lock(); xas_for_each(&xas, folio, start + HPAGE_PMD_NR - 1) { if (xas_retry(&xas, folio)) From e8122742cfb4e28f3a499c09c08f968a692647d4 Mon Sep 17 00:00:00 2001 From: "Nico Pache (Red Hat)" Date: Tue, 11 Aug 2026 06:48:34 -0600 Subject: [PATCH 1087/1328] mm/khugepaged: extract reference check into folio_pte_referenced() helper This change deduplicates the "is this PTE/folio referenced enough to be considered for a collapse" condition that was repeated in both __collapse_huge_page_isolate() and collapse_scan_pmd(), extracting it into a single inline helper function. Also move the comment and use it as the function header. While we are at it, updated the comment to clarify that a young pte is a recently accessed one. [nico.pache@linux.dev: drop the trivial helper kerneldoc and inline marker per review] Link: https://lore.kernel.org/9038f552-926b-4c4c-b023-69271f45e3d5@linux.dev Link: https://lore.kernel.org/20260811-khugepaged_pte_refactor-v4-2-ddac39d61c4a@linux.dev Signed-off-by: Nico Pache (Red Hat) Acked-by: Usama Arif Acked-by: David Hildenbrand (Arm) Reviewed-by: Zi Yan Reviewed-by: Baolin Wang Reviewed-by: Lance Yang Cc: Barry Song Cc: Dev Jain Cc: Jonathan Corbet Cc: Liam R. Howlett Cc: Lorenzo Stoakes (ARM) Cc: Michal Hocko Cc: Mike Rapoport Cc: Ryan Roberts Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Pedro Falcato Signed-off-by: Andrew Morton --- mm/khugepaged.c | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/mm/khugepaged.c b/mm/khugepaged.c index 1e26ea97381a..34654d1c1259 100644 --- a/mm/khugepaged.c +++ b/mm/khugepaged.c @@ -672,6 +672,16 @@ static void release_pte_pages(pte_t *pte, pte_t *_pte, } } +static bool folio_pte_referenced(struct folio *folio, + struct vm_area_struct *vma, unsigned long addr, pte_t pteval) +{ + /* The folio was referenced previously ... */ + if (folio_test_young(folio) || folio_test_referenced(folio)) + return true; + /* ... or the PTE mapping was recently used */ + return pte_young(pteval) || mmu_notifier_test_young(vma->vm_mm, addr); +} + static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma, unsigned long start_addr, pte_t *pte, struct collapse_control *cc, unsigned int order, struct list_head *compound_pagelist) @@ -810,14 +820,8 @@ static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma, if (folio_test_large(folio)) list_add_tail(&folio->lru, compound_pagelist); next: - /* - * If collapse was initiated by khugepaged, check that there is - * enough young pte to justify collapsing the page - */ if (cc->is_khugepaged && - (pte_young(pteval) || folio_test_young(folio) || - folio_test_referenced(folio) || - mmu_notifier_test_young(vma->vm_mm, addr))) + folio_pte_referenced(folio, vma, addr, pteval)) referenced++; } @@ -1766,14 +1770,8 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm, goto out_unmap; } - /* - * If collapse was initiated by khugepaged, check that there is - * enough young pte to justify collapsing the page - */ if (cc->is_khugepaged && - (pte_young(pteval) || folio_test_young(folio) || - folio_test_referenced(folio) || - mmu_notifier_test_young(vma->vm_mm, addr))) + folio_pte_referenced(folio, vma, addr, pteval)) referenced++; } if (cc->is_khugepaged && From 948ec48e568649bc639088c605515afbc1bf9e18 Mon Sep 17 00:00:00 2001 From: "Nico Pache (Red Hat)" Date: Tue, 11 Aug 2026 06:48:35 -0600 Subject: [PATCH 1088/1328] mm/khugepaged: introduce a count_collapse_event() helper Provide a simple helper function to help reduce a often used, and duplicate pattern across the khugepaged code. When collapsing to a PMD we need to record a vm_event and the mTHP_stat event. When doing mTHP collapse we only update the mTHP stat. Link: https://lore.kernel.org/20260811-khugepaged_pte_refactor-v4-3-ddac39d61c4a@linux.dev Signed-off-by: Nico Pache (Red Hat) Reviewed-by: Baolin Wang Acked-by: David Hildenbrand (Arm) Acked-by: Usama Arif Reviewed-by: Zi Yan Reviewed-by: Pedro Falcato Reviewed-by: Lorenzo Stoakes (ARM) Reviewed-by: Lance Yang Cc: Barry Song Cc: Dev Jain Cc: Jonathan Corbet Cc: Liam R. Howlett Cc: Michal Hocko Cc: Mike Rapoport Cc: Ryan Roberts Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/khugepaged.c | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/mm/khugepaged.c b/mm/khugepaged.c index 34654d1c1259..6d7206e5f4d2 100644 --- a/mm/khugepaged.c +++ b/mm/khugepaged.c @@ -682,6 +682,14 @@ static bool folio_pte_referenced(struct folio *folio, return pte_young(pteval) || mmu_notifier_test_young(vma->vm_mm, addr); } +static void count_collapse_event(unsigned int order, enum vm_event_item vm_event, + enum mthp_stat_item mthp_event) +{ + if (is_pmd_order(order)) + count_vm_event(vm_event); + count_mthp_stat(order, mthp_event); +} + static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma, unsigned long start_addr, pte_t *pte, struct collapse_control *cc, unsigned int order, struct list_head *compound_pagelist) @@ -702,9 +710,8 @@ static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma, if (pte_none_or_zero(pteval)) { if (++none_or_zero > max_ptes_none) { result = SCAN_EXCEED_NONE_PTE; - if (is_pmd_order(order)) - count_vm_event(THP_SCAN_EXCEED_NONE_PTE); - count_mthp_stat(order, MTHP_STAT_COLLAPSE_EXCEED_NONE); + count_collapse_event(order, THP_SCAN_EXCEED_NONE_PTE, + MTHP_STAT_COLLAPSE_EXCEED_NONE); goto out; } continue; @@ -746,9 +753,8 @@ static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma, */ if (++shared > max_ptes_shared) { result = SCAN_EXCEED_SHARED_PTE; - if (is_pmd_order(order)) - count_vm_event(THP_SCAN_EXCEED_SHARED_PTE); - count_mthp_stat(order, MTHP_STAT_COLLAPSE_EXCEED_SHARED); + count_collapse_event(order, THP_SCAN_EXCEED_SHARED_PTE, + MTHP_STAT_COLLAPSE_EXCEED_SHARED); goto out; } } @@ -1258,15 +1264,12 @@ static enum scan_result alloc_charge_folio(struct folio **foliop, struct mm_stru folio = __folio_alloc(gfp, order, node, &cc->alloc_nmask); if (!folio) { *foliop = NULL; - if (is_pmd_order(order)) - count_vm_event(THP_COLLAPSE_ALLOC_FAILED); - count_mthp_stat(order, MTHP_STAT_COLLAPSE_ALLOC_FAILED); + count_collapse_event(order, THP_COLLAPSE_ALLOC_FAILED, + MTHP_STAT_COLLAPSE_ALLOC_FAILED); return SCAN_ALLOC_HUGE_PAGE_FAIL; } - if (is_pmd_order(order)) - count_vm_event(THP_COLLAPSE_ALLOC); - count_mthp_stat(order, MTHP_STAT_COLLAPSE_ALLOC); + count_collapse_event(order, THP_COLLAPSE_ALLOC, MTHP_STAT_COLLAPSE_ALLOC); if (unlikely(mem_cgroup_charge(folio, mm, gfp))) { folio_put(folio); @@ -1656,9 +1659,8 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm, if (pte_none_or_zero(pteval)) { if (++none_or_zero > max_ptes_none) { result = SCAN_EXCEED_NONE_PTE; - count_vm_event(THP_SCAN_EXCEED_NONE_PTE); - count_mthp_stat(HPAGE_PMD_ORDER, - MTHP_STAT_COLLAPSE_EXCEED_NONE); + count_collapse_event(HPAGE_PMD_ORDER, THP_SCAN_EXCEED_NONE_PTE, + MTHP_STAT_COLLAPSE_EXCEED_NONE); goto out_unmap; } continue; @@ -1666,9 +1668,8 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm, if (!pte_present(pteval)) { if (++unmapped > max_ptes_swap) { result = SCAN_EXCEED_SWAP_PTE; - count_vm_event(THP_SCAN_EXCEED_SWAP_PTE); - count_mthp_stat(HPAGE_PMD_ORDER, - MTHP_STAT_COLLAPSE_EXCEED_SWAP); + count_collapse_event(HPAGE_PMD_ORDER, THP_SCAN_EXCEED_SWAP_PTE, + MTHP_STAT_COLLAPSE_EXCEED_SWAP); goto out_unmap; } /* @@ -1725,9 +1726,8 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm, if (folio_maybe_mapped_shared(folio)) { if (++shared > max_ptes_shared) { result = SCAN_EXCEED_SHARED_PTE; - count_vm_event(THP_SCAN_EXCEED_SHARED_PTE); - count_mthp_stat(HPAGE_PMD_ORDER, - MTHP_STAT_COLLAPSE_EXCEED_SHARED); + count_collapse_event(HPAGE_PMD_ORDER, THP_SCAN_EXCEED_SHARED_PTE, + MTHP_STAT_COLLAPSE_EXCEED_SHARED); goto out_unmap; } } From cc044178edc9d7b5cd291ef0e2daf060379e6447 Mon Sep 17 00:00:00 2001 From: "Nico Pache (Red Hat)" Date: Tue, 11 Aug 2026 06:48:36 -0600 Subject: [PATCH 1089/1328] mm/khugepaged: fix outdated comments Fix comment in collapse_scan_pmd() that still described the old folio_mapcount() > folio_ref_count() check and a "512" false-positive scenario. The code now uses folio_expected_ref_count() != folio_ref_count() which doesn't suffer from the same limitation. Fix comment in collapse_huge_page() that referenced ptep_clear_flush, when the code actually uses pmdp_collapse_flush. Fix comment in __collapse_huge_page_swapin() that referenced the old function name khugepaged_scan_pmd, now collapse_scan_pmd. Also clean up some simple typos and stale terminology (mmap_sem -> mmap_lock, PG_lock -> folio lock, page -> folio, grammar). We also clarify a comment regarding where the max_ptes_none check is deferred to in mthp_collapse() from the original collapse_scan_pmd check. Update all comments that references a function to include parentheses. [nico.pache@linux.dev: fix outdated comments] Link: https://lore.kernel.org/1c96e2f3-802f-472b-81e6-4af17a721a3c@linux.dev Link: https://lore.kernel.org/20260811-khugepaged_pte_refactor-v4-4-ddac39d61c4a@linux.dev Signed-off-by: Nico Pache (Red Hat) Acked-by: Usama Arif Assisted-by: Cursor(claude-sonnet-4):4.6 Acked-by: David Hildenbrand (Arm) Reviewed-by: Zi Yan Acked-by: Pedro Falcato Reviewed-by: Lorenzo Stoakes (ARM) Reviewed-by: Lance Yang Cc: Baolin Wang Cc: Barry Song Cc: Dev Jain Cc: Jonathan Corbet Cc: Liam R. Howlett Cc: Michal Hocko Cc: Mike Rapoport Cc: Ryan Roberts Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/khugepaged.c | 44 +++++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/mm/khugepaged.c b/mm/khugepaged.c index 6d7206e5f4d2..30f17c7494fa 100644 --- a/mm/khugepaged.c +++ b/mm/khugepaged.c @@ -620,7 +620,7 @@ void __khugepaged_exit(struct mm_struct *mm) /* * This is required to serialize against * collapse_test_exit() (which is guaranteed to run - * under mmap sem read mode). Stop here (after we return all + * under mmap_lock read mode). Stop here (after we return all * pagetables will be destroyed) until khugepaged has finished * working on the pagetables under the mmap_lock. */ @@ -782,8 +782,8 @@ static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma, /* * We can do it before folio_isolate_lru because the - * folio can't be freed from under us. NOTE: PG_lock - * is needed to serialize against split_huge_page + * folio can't be freed from under us. NOTE: folio lock + * is needed to serialize against split_huge_page() * when invoked from the VM. */ if (!folio_trylock(folio)) { @@ -809,7 +809,7 @@ static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma, } /* - * Isolate the page to avoid collapsing an hugepage + * Isolate the folio to avoid collapsing a hugepage * currently in use by the VM. */ if (!folio_isolate_lru(folio)) { @@ -921,7 +921,7 @@ static void __collapse_huge_page_copy_failed(pte_t *pte, * Re-establish the PMD to point to the original page table * entry. Restoring PMD needs to be done prior to releasing * pages. Since pages are still isolated and locked here, - * acquiring anon_vma_lock_write is unnecessary. + * acquiring anon_vma_lock_write() is unnecessary. */ pmd_ptl = pmd_lock(vma->vm_mm, pmd); pmd_populate(vma->vm_mm, pmd, pmd_pgtable(orig_pmd)); @@ -1095,9 +1095,9 @@ static enum scan_result hugepage_vma_revalidate(struct mm_struct *mm, unsigned l return SCAN_VMA_CHECK; /* * Anon VMA expected, the address may be unmapped then - * remapped to file after khugepaged reaquired the mmap_lock. + * remapped to file after khugepaged reacquired the mmap_lock. * - * thp_vma_allowable_orders may return true for qualified file + * thp_vma_allowable_orders() may return true for qualified file * vmas. */ if (expect_anon && (!(*vmap)->anon_vma || !vma_is_anonymous(*vmap))) @@ -1153,7 +1153,7 @@ static enum scan_result check_pmd_still_valid(struct mm_struct *mm, /* * Bring missing pages in from swap, to complete THP collapse. - * Only done if khugepaged_scan_pmd believes it is worthwhile. + * Only done if collapse_scan_pmd() believes it is worthwhile. * * For mTHP orders the function bails on the first swap entry, because * faulting pages back in during collapse could re-populate PTEs that @@ -1221,7 +1221,7 @@ static enum scan_result __collapse_huge_page_swapin(struct mm_struct *mm, pte = NULL; /* - * do_swap_page returns VM_FAULT_RETRY with released mmap_lock. + * do_swap_page() returns VM_FAULT_RETRY with released mmap_lock. * Note we treat VM_FAULT_RETRY as VM_FAULT_ERROR here because * we do not retry here and swap entry will remain in pagetable * resulting in later failure. @@ -1285,7 +1285,7 @@ static enum scan_result alloc_charge_folio(struct folio **foliop, struct mm_stru } /* - * collapse_huge_page expects the mmap_lock to be unlocked before entering and + * collapse_huge_page() expects the mmap_lock to be unlocked before entering and * will always return with the lock unlocked, to avoid holding the mmap_lock * while allocating a THP, as that could trigger direct reclaim/compaction. * Note that the VMA must be rechecked after grabbing the mmap_lock again. @@ -1332,7 +1332,7 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long s if (unmapped) { /* - * __collapse_huge_page_swapin will return with mmap_lock + * __collapse_huge_page_swapin() will return with mmap_lock * released when it fails. So we jump out_nolock directly in * that case. Continuing to collapse causes inconsistency. */ @@ -1345,8 +1345,8 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long s mmap_read_unlock(mm); /* * Prevent all access to pagetables with the exception of - * gup_fast later handled by the ptep_clear_flush and the VM - * handled by the anon_vma lock + PG_lock. + * gup_fast later handled by the pmdp_collapse_flush() and the VM + * handled by the anon_vma lock + folio lock. * * UFFDIO_MOVE is prevented to race as well thanks to the * mmap_lock. @@ -1403,9 +1403,9 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long s spin_lock(pmd_ptl); VM_WARN_ON_ONCE(!pmd_none(*pmd)); /* - * We can only use set_pmd_at when establishing + * We can only use set_pmd_at() when establishing * hugepmds and never for establishing regular pmds that - * points to regular pagetables. Use pmd_populate for that + * points to regular pagetables. Use pmd_populate() for that */ pmd_populate(mm, pmd, pmd_pgtable(_pmd)); spin_unlock(pmd_ptl); @@ -1637,7 +1637,8 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm, /* * If PMD is the only enabled order, enforce max_ptes_none, otherwise - * scan all pages to populate the bitmap for mTHP collapse. + * scan all pages to populate the bitmap for mTHP collapse. The bitmap + * is then checked again in mthp_collapse() for each attempted order. */ if (enabled_orders != BIT(HPAGE_PMD_ORDER)) max_ptes_none = KHUGEPAGED_MAX_PTES_LIMIT; @@ -1758,12 +1759,9 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm, /* * Check if the page has any GUP (or other external) pins. * - * Here the check may be racy: - * it may see folio_mapcount() > folio_ref_count(). - * But such case is ephemeral we could always retry collapse - * later. However it may report false positive if the page - * has excessive GUP pins (i.e. 512). Anyway the same check - * will be done again later the risk seems low. + * Here the check is racy, but such cases are ephemeral and + * we can always retry collapse later. Anyway the same + * check will be done again later, so the risk seems to be low. */ if (folio_expected_ref_count(folio) != folio_ref_count(folio)) { result = SCAN_PAGE_COUNT; @@ -1784,7 +1782,7 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm, out_unmap: pte_unmap_unlock(pte, ptl); if (result == SCAN_SUCCEED) { - /* collapse_huge_page expects the lock to be dropped before calling */ + /* collapse_huge_page() expects the lock to be dropped before calling */ mmap_read_unlock(mm); result = mthp_collapse(mm, start_addr, referenced, unmapped, cc, enabled_orders); From 27490db7ec048175522368ac0aa2e99249e5c48d Mon Sep 17 00:00:00 2001 From: "Nico Pache (Red Hat)" Date: Tue, 11 Aug 2026 06:48:38 -0600 Subject: [PATCH 1090/1328] mm/khugepaged: unmap pte before releasing vma write lock We are currently dropping the anon_vma write lock before unmapping the PTE. Although this is safe, due to us still holding the mmap_write_lock, its safer and less confusing to switch the order of these two operations. Link: https://lore.kernel.org/20260811-khugepaged_pte_refactor-v4-6-ddac39d61c4a@linux.dev Signed-off-by: Nico Pache (Red Hat) Suggested-by: David Hildenbrand Acked-by: David Hildenbrand (Arm) Reviewed-by: Zi Yan Reviewed-by: Baolin Wang Acked-by: Pedro Falcato Reviewed-by: Lorenzo Stoakes (ARM) Reviewed-by: Lance Yang Cc: Barry Song Cc: Dev Jain Cc: Jonathan Corbet Cc: Liam R. Howlett Cc: Michal Hocko Cc: Mike Rapoport Cc: Ryan Roberts Cc: Suren Baghdasaryan Cc: Usama Arif Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/khugepaged.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mm/khugepaged.c b/mm/khugepaged.c index 30f17c7494fa..11ff98d55c76 100644 --- a/mm/khugepaged.c +++ b/mm/khugepaged.c @@ -1463,10 +1463,10 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long s result = SCAN_SUCCEED; out_up_write: - if (anon_vma_locked) - anon_vma_unlock_write(vma->anon_vma); if (pte) pte_unmap(pte); + if (anon_vma_locked) + anon_vma_unlock_write(vma->anon_vma); mmap_write_unlock(mm); out_nolock: if (folio) From 2a0be246e342e239ef91d28e2658b6bf508cc065 Mon Sep 17 00:00:00 2001 From: "Nico Pache (Red Hat)" Date: Tue, 11 Aug 2026 06:48:39 -0600 Subject: [PATCH 1091/1328] mm: Documentation: clarify where the mTHP stats live The note about khugepaged counters references /proc/vmstat for the PMD case, but never mentions where the mTHPs stats can be found (i.e.: /sys/kernel/mm/transparent_hugepage/hugepages-kB/stats/) Add a small addition to this section for clarity. Also fix a missing period while we are at it. Link: https://lore.kernel.org/20260811-khugepaged_pte_refactor-v4-7-ddac39d61c4a@linux.dev Signed-off-by: Nico Pache (Red Hat) Reviewed-by: Baolin Wang Suggested-by: Lorenzo Stoakes Acked-by: David Hildenbrand (Arm) Reviewed-by: Zi Yan Acked-by: Pedro Falcato Reviewed-by: Lorenzo Stoakes (ARM) Reviewed-by: Lance Yang Cc: Barry Song Cc: Dev Jain Cc: Jonathan Corbet Cc: Liam R. Howlett Cc: Michal Hocko Cc: Mike Rapoport Cc: Ryan Roberts Cc: Suren Baghdasaryan Cc: Usama Arif Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- Documentation/admin-guide/mm/transhuge.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Documentation/admin-guide/mm/transhuge.rst b/Documentation/admin-guide/mm/transhuge.rst index 16f37135ed80..b187d618452f 100644 --- a/Documentation/admin-guide/mm/transhuge.rst +++ b/Documentation/admin-guide/mm/transhuge.rst @@ -224,7 +224,7 @@ khugepaged will be automatically started when any THP size is enabled (either of the per-size anon control or the top-level control are set to "always" or "madvise"), and it'll be automatically shutdown when all THP sizes are disabled (when both the per-size anon control and the -top-level control are "never") +top-level control are "never"). process THP controls -------------------- @@ -301,7 +301,9 @@ being replaced by a PMD mapping, or (2) physical pages replaced by one hugepage of various sizes (PMD-sized or mTHP). Each may happen independently, or together, depending on the type of memory and the failures that occur. As such, this value should be interpreted roughly as a sign of progress, -and counters in /proc/vmstat consulted for more accurate accounting):: +and counters in /proc/vmstat consulted for more accurate accounting. +Per-order mTHP collapse statistics are also available under +/sys/kernel/mm/transparent_hugepage/hugepages-kB/stats/):: /sys/kernel/mm/transparent_hugepage/khugepaged/pages_collapsed From 73b5d07990a0e6ea9cdf2c07fa8fbc865d398c1d Mon Sep 17 00:00:00 2001 From: Song Hu Date: Wed, 12 Aug 2026 15:57:39 +0800 Subject: [PATCH 1092/1328] Docs/mm: fix outdated "radix tree" in page_migration Steps 7 and 9 of the migration description still say "radix tree", unlike steps 5 and 11 which already use "i_pages lock". The page cache moved to the XArray at mapping->i_pages long ago. Use "page cache tree" for the two remaining references. Link: https://lore.kernel.org/20260812075739.325441-1-husong@kylinos.cn Signed-off-by: Song Hu Cc: David Hildenbrand Cc: Jonathan Corbet Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Randy Dunlap Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Matthew Wilcox Cc: Jan Kara Signed-off-by: Andrew Morton --- Documentation/mm/page_migration.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Documentation/mm/page_migration.rst b/Documentation/mm/page_migration.rst index 34602b254aa6..5b8d50308db1 100644 --- a/Documentation/mm/page_migration.rst +++ b/Documentation/mm/page_migration.rst @@ -110,13 +110,13 @@ Steps: 6. The refcount of the page is examined and we back out if references remain. Otherwise, we know that we are the only one referencing this page. -7. The radix tree is checked and if it does not contain the pointer to this - page then we back out because someone else modified the radix tree. +7. The page cache tree is checked and if it does not contain the pointer to this + page then we back out because someone else modified the page cache tree. 8. The new page is prepped with some settings from the old page so that accesses to the new page will discover a page with the correct settings. -9. The radix tree is changed to point to the new page. +9. The page cache tree is changed to point to the new page. 10. The reference count of the old page is dropped because the address space reference is gone. A reference to the new page is established because From f7e698e326b239a91ea15844817551921209e826 Mon Sep 17 00:00:00 2001 From: Kairui Song Date: Wed, 12 Aug 2026 20:22:39 +0800 Subject: [PATCH 1093/1328] mm/mglru: fix and remove redundant unevictable folio handling sort_folio() has a shortcut for moving folios that are no longer evictable but are still sitting on a generation list. However, this shortcut is buggy. It does not follow the PG_lru usage convention, and it has a more serious issue. Unevictable folios are not threaded on lists[LRU_UNEVICTABLE], so that folio->lru can be reused to hold folio->mlock_count (see the comment in lruvec_init()). Hence lruvec_add_folio() skips the list_add() for them, and every other place that turns a folio unevictable initialises mlock_count explicitly: lru_add() sets it to 0, __mlock_folio() and __mlock_new_folio() set it to !!folio_test_mlocked(folio). sort_folio() sets nothing, and the lru_gen_del_folio() right above it may have already poisoned folio->lru via list_del(), so mlock_count ends up aliasing LIST_POISON2, which reads as 0x122, i.e. 290. The result is user visible. On munlock, __munlock_folio() decrements that bogus count, finds it still non-zero and bails out before clearing PG_mlocked, so the folio remains unevictable and the Mlocked accounting stays inflated until the folio is freed. The shortcut also touches the LRU flags in the wrong order. It calls lru_gen_del_folio() while PG_lru is still set, so a concurrent folio_test_clear_lru() (e.g. compaction, folio_isolate_lru()) can succeed on a folio that has already been taken off the generation list, which may lead to unexpected behavior. So fix it by isolating them as common folios and letting the generic shrink path cull them. This matches the classical LRU behavior, and there should be no visible effect on the generic eviction or isolation behavior. There is no performance concern either, such a folio goes through this once, and then it is off the generation lists for good. Link: https://lore.kernel.org/20260812-mglru-mlock-fix-v2-1-a3fec5853c08@tencent.com Fixes: ac35a4902374 ("mm: multi-gen LRU: minimal implementation") Signed-off-by: Kairui Song Reviewed-by: Barry Song Reviewed-by: Baolin Wang Cc: Axel Rasmussen Cc: Brian Geffon Cc: David Hildenbrand Cc: Jan Alexander Steffens (heftig) Cc: Johannes Weiner Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Oleksandr Natalenko Cc: Shakeel Butt Cc: Steven Barrett Cc: Suleiman Souhlal Cc: Wei Xu Cc: Yuanchu Xie Cc: Yu Zhao Cc: Signed-off-by: Andrew Morton --- mm/vmscan.c | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/mm/vmscan.c b/mm/vmscan.c index 6c35f7e21465..c1404a59523d 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -4649,7 +4649,6 @@ void lru_gen_reparent_memcg(struct mem_cgroup *memcg, struct mem_cgroup *parent, static bool sort_folio(struct lruvec *lruvec, struct folio *folio, struct scan_control *sc, int tier_idx) { - bool success; int gen = folio_lru_gen(folio); int type = folio_is_file_lru(folio); int zone = folio_zonenum(folio); @@ -4661,15 +4660,9 @@ static bool sort_folio(struct lruvec *lruvec, struct folio *folio, struct scan_c VM_WARN_ON_ONCE_FOLIO(gen >= MAX_NR_GENS, folio); - /* unevictable */ - if (!folio_evictable(folio)) { - success = lru_gen_del_folio(lruvec, folio, true); - VM_WARN_ON_ONCE_FOLIO(!success, folio); - folio_set_unevictable(folio); - lruvec_add_folio(lruvec, folio); - __count_vm_events(UNEVICTABLE_PGCULLED, delta); - return true; - } + /* unevictable: let it through and the generic path will cull it */ + if (!folio_evictable(folio)) + return false; /* promoted */ if (gen != lru_gen_from_seq(lrugen->min_seq[type])) { @@ -4922,11 +4915,9 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec, list_for_each_entry_safe_reverse(folio, next, &list, lru) { DEFINE_MIN_SEQ(lruvec); - if (!folio_evictable(folio)) { - list_del(&folio->lru); - folio_putback_lru(folio); + /* move_folios_to_lru() culls unevictable folios via folio_putback_lru() */ + if (!folio_evictable(folio)) continue; - } /* retry folios that may have missed folio_rotate_reclaimable() */ if (!skip_retry && !folio_test_active(folio) && !folio_mapped(folio) && From f525001b3309a0decdcdcdaeceafd7ab9dc927fe Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Wed, 12 Aug 2026 09:47:43 -1000 Subject: [PATCH 1094/1328] percpu: drop CONFIG_DEBUG_FORCE_WEAK_PER_CPU alpha requires percpu variables in modules to be defined as weak so that the compiler generates GOT based external references for them. This puts two extra restrictions on percpu variable definitions. The symbol must be globally unique even when static and a static percpu variable can't be defined inside a function. DEBUG_FORCE_WEAK_PER_CPU exists to give generic code build coverage for these restrictions without building for alpha. MEM_ALLOC_PROFILING defines a static percpu counter at each allocation call site and thus can't be built with weak percpu definitions, so it depends on !DEBUG_FORCE_WEAK_PER_CPU. As allmodconfig enables DEBUG_FORCE_WEAK_PER_CPU, this knocks MEM_ALLOC_PROFILING out of allmodconfig build coverage. allmodconfig coverage for MEM_ALLOC_PROFILING is worth more than build coverage for restrictions which only matter to alpha module builds. Drop DEBUG_FORCE_WEAK_PER_CPU. Restriction violations will now show up only on alpha builds. Link: https://lore.kernel.org/178656406317.2437052.7257990869957704195@slm.duckdns.org Signed-off-by: Tejun Heo Reported-by: Andrew Morton Reviewed-by: Suren Baghdasaryan Acked-by: Gabriele Monaco [include/rv/da_monitor.h] Cc: Dennis Zhou Cc: Kent Overstreet Cc: Steven Rostedt Signed-off-by: Andrew Morton --- include/linux/percpu-defs.h | 7 +------ include/rv/da_monitor.h | 2 +- lib/Kconfig.debug | 15 --------------- mm/Kconfig.debug | 1 - 4 files changed, 2 insertions(+), 23 deletions(-) diff --git a/include/linux/percpu-defs.h b/include/linux/percpu-defs.h index 2cba7cc2b01f..dbe3267a0a13 100644 --- a/include/linux/percpu-defs.h +++ b/include/linux/percpu-defs.h @@ -65,13 +65,8 @@ * * Archs which need weak percpu definitions should set * CONFIG_ARCH_MODULE_NEEDS_WEAK_PER_CPU when necessary. - * - * To ensure that the generic code observes the above two - * restrictions, if CONFIG_DEBUG_FORCE_WEAK_PER_CPU is set weak - * definition is used for all cases. */ -#if (defined(CONFIG_ARCH_MODULE_NEEDS_WEAK_PER_CPU) && defined(MODULE)) || \ - defined(CONFIG_DEBUG_FORCE_WEAK_PER_CPU) +#if defined(CONFIG_ARCH_MODULE_NEEDS_WEAK_PER_CPU) && defined(MODULE) /* * __pcpu_scope_* dummy variable is used to enforce scope. It * receives the static modifier when it's used in front of diff --git a/include/rv/da_monitor.h b/include/rv/da_monitor.h index 34b8fba9ecd4..6b641697106b 100644 --- a/include/rv/da_monitor.h +++ b/include/rv/da_monitor.h @@ -24,7 +24,7 @@ /* * Per-cpu variables require a unique name although static in some - * configurations (e.g. CONFIG_DEBUG_FORCE_WEAK_PER_CPU or alpha modules). + * configurations (e.g. alpha modules). */ #define DA_MON_NAME CONCATENATE(da_mon_, MONITOR_NAME) diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index b82515cde538..00921b1676e8 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -613,21 +613,6 @@ config BUILTIN_MODULE_RANGES It also records an anchor symbol to determine the load address of the section. -config DEBUG_FORCE_WEAK_PER_CPU - bool "Force weak per-cpu definitions" - depends on DEBUG_KERNEL - help - s390 and alpha require percpu variables in modules to be - defined weak to work around addressing range issue which - puts the following two restrictions on percpu variable - definitions. - - 1. percpu symbols must be unique whether static or not - 2. percpu variables can't be defined inside a function - - To ensure that generic code follows the above rules, this - option forces all percpu variables to be defined as weak. - config WARN_CONTEXT_ANALYSIS bool "Compiler context-analysis warnings" depends on CC_IS_CLANG && CLANG_VERSION >= 230000 diff --git a/mm/Kconfig.debug b/mm/Kconfig.debug index 5737a504efbb..15dca19dd07d 100644 --- a/mm/Kconfig.debug +++ b/mm/Kconfig.debug @@ -326,7 +326,6 @@ config MEM_ALLOC_PROFILING default n depends on MMU depends on PROC_FS - depends on !DEBUG_FORCE_WEAK_PER_CPU select CODE_TAGGING select PAGE_EXTENSION select SLAB_OBJ_EXT From 0d0878fd7c9b49b8eff95c4426a2121c5f8f31cc Mon Sep 17 00:00:00 2001 From: Qiang Liu Date: Wed, 12 Aug 2026 17:28:56 +0800 Subject: [PATCH 1095/1328] lib/test_hmm: fix garbage pfn and wrong direction in devmem fault debug Move pr_debug() inside the `if (dpage)` block to avoid printing garbage pfn for NULL dpage, and correct the direction label from "sys to dev" to "dev to sys". Link: https://lore.kernel.org/20260812092856.55296-1-liuqiangneo@163.com Signed-off-by: Qiang Liu Assisted-by: Qoder:Qwen-3.8-MAX-Preview Cc: Jason Gunthorpe Cc: Leon Romanovsky Signed-off-by: Andrew Morton --- lib/test_hmm.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/test_hmm.c b/lib/test_hmm.c index 6205fb313bd0..7c4d10eae6fe 100644 --- a/lib/test_hmm.c +++ b/lib/test_hmm.c @@ -1151,10 +1151,9 @@ static vm_fault_t dmirror_devmem_fault_alloc_and_copy(struct migrate_vma *args, if (!dpage && !order) return VM_FAULT_OOM; - pr_debug("migrating from sys to dev pfn src: 0x%lx pfn dst: 0x%lx\n", - page_to_pfn(spage), page_to_pfn(dpage)); - if (dpage) { + pr_debug("migrating from dev to sys pfn src: 0x%lx pfn dst: 0x%lx\n", + page_to_pfn(spage), page_to_pfn(dpage)); lock_page(dpage); *dst |= migrate_pfn(page_to_pfn(dpage)); } From a1b114b4cec1263e693cd6e7aa5c8321295143c6 Mon Sep 17 00:00:00 2001 From: Xie Yuanbin Date: Thu, 13 Aug 2026 21:49:16 +0800 Subject: [PATCH 1096/1328] mm/Kconfig: make MEMORY_FAILURE select MIGRATION For embedded devices, lacking support for NUMA, memory hotplug/hotremove, CMA and huge pages is a quite common scenario. In this scenario, the demand for contiguous physical memory allocation is very low. To reduce the kernel image size, some devices disable the compaction. However, their SoCs do support DDR ECC, meaning that memory-failure may be needed. Migration is very useful for soft_offline_page() in memory-failure, which may be triggered by correctable memory errors. Most anonymous and file-mapped faulty pages can be migrated to other healthy pages. Currently, MEMORY_FAILURE does not explicitly select MIGRATION. When COMPACTION, MEMORY_HOTREMOVE, NUMA_MIGRATION and CMA are all disabled, MEMORY_FAILURE can be enabled, but MIGRATION cannot be selected. Make MEMORY_FAILURE select MIGRATION to handle this situation. Link: https://lore.kernel.org/20260813134916.292733-1-xieyuanbin1@huawei.com Signed-off-by: Xie Yuanbin Suggested-by: Mike Rapoport Reviewed-by: Lorenzo Stoakes (ARM) Reviewed-by: Mike Rapoport (Microsoft) Acked-by: Zi Yan Acked-by: David Hildenbrand (Arm) Acked-by: Miaohe Lin Cc: Alistair Popple Cc: "Borislav Petkov (AMD)" Cc: Byungchul Park Cc: David Hildenbrand Cc: Gregory Price Cc: "Huang, Ying" Cc: Joshua Hahn Cc: Liam R. Howlett Cc: liaohua Cc: "Luck, Tony" Cc: Matthew Brost Cc: Michal Hocko Cc: Naoya Horiguchi Cc: Rakie Kim Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Yuanbin Xie Signed-off-by: Andrew Morton --- mm/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/mm/Kconfig b/mm/Kconfig index 8a24c130d008..604c58199acb 100644 --- a/mm/Kconfig +++ b/mm/Kconfig @@ -767,6 +767,7 @@ config MEMORY_FAILURE depends on ARCH_SUPPORTS_MEMORY_FAILURE bool "Enable recovery from hardware memory errors" select INTERVAL_TREE + select MIGRATION help Enables code to recover from some memory failures on systems with MCA recovery. This allows a system to continue running From 556147fc27b5d9c3c731ae4c5599457831102735 Mon Sep 17 00:00:00 2001 From: Andrew Morton Date: Mon, 10 Aug 2026 13:16:37 -0700 Subject: [PATCH 1097/1328] mm/hmm.c:hmm_do_fault(): suppress sparse warning mm/hmm.c:673 hmm_do_fault() error: we previously assumed 'hmm_vma_walk->locked' could be null (see line 654) Stanislav says this can't happen. Waste a few cycles to make the warning go away. [akpm@linux-foundation.org: WARN_ON_ONCE() if the handler didn't set ->locked, per Stanislav] Link: https://lore.kernel.org/anu1N-DOnQwxO1kF@skinsburskii Fixes: 121170831228 ("mm/hmm: add hmm_range_fault_unlocked_timeout() for mmap lock-drop support") Reported-by: kernel test robot Closes: https://lore.kernel.org/202608101053.PhnVUM4u-lkp@intel.com Cc: Stanislav Kinsburskii Cc: David Hildenbrand Signed-off-by: Andrew Morton --- mm/hmm.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mm/hmm.c b/mm/hmm.c index 2b05c53b82dc..2f1e98c6b644 100644 --- a/mm/hmm.c +++ b/mm/hmm.c @@ -670,7 +670,10 @@ static int hmm_do_fault(struct mm_struct *mm, ret = handle_mm_fault(vma, addr, fault_flags, NULL); if (ret & (VM_FAULT_COMPLETED | VM_FAULT_RETRY)) { - *hmm_vma_walk->locked = false; + if (hmm_vma_walk->locked) /* needed by sparse */ + *hmm_vma_walk->locked = false; + else + WARN_ON_ONCE(1); /* broken fault handler */ return HMM_FAULT_UNLOCKED; } From f52b3b89faba20cb347f4b908f649fa6351ff10d Mon Sep 17 00:00:00 2001 From: Eric Kim Date: Fri, 14 Aug 2026 15:30:51 +0900 Subject: [PATCH 1098/1328] mm/rmap: synchronize lock and unlock target in anon_vma_clone Currently, in anon_vma_clone(), src vma's anon_vma is assigned to active_anon_vma and is used when unlocking anon_vma after linking new AVCs. However, the anon_vma is locked using src->anon_vma, instead of active_anon_vma, making the lock and unlock target inconsistent. Use active_anon_vma for both locking and unlocking. Link: https://lore.kernel.org/OS7PR01MB139142FE16EC63B892559D40496DA2@OS7PR01MB13914.jpnprd01.prod.outlook.com Signed-off-by: Eric Kim Reviewed-by: Lorenzo Stoakes (ARM) Reviewed-by: Lance Yang Cc: David Hildenbrand Cc: Harry Yoo Cc: Jann Horn Cc: Liam R. Howlett Cc: Rik van Riel Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/rmap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/rmap.c b/mm/rmap.c index 14f2f9b07572..d1819fd69938 100644 --- a/mm/rmap.c +++ b/mm/rmap.c @@ -350,7 +350,7 @@ int anon_vma_clone(struct vm_area_struct *dst, struct vm_area_struct *src, * Now link the anon_vma's back to the newly inserted AVCs. * Note that all anon_vma's share the same root. */ - anon_vma_lock_write(src->anon_vma); + anon_vma_lock_write(active_anon_vma); list_for_each_entry_reverse(avc, &dst->anon_vma_chain, same_vma) { struct anon_vma *anon_vma = avc->anon_vma; From f2b1cb39d5ccab090d8353788f186f7e7a1fffd4 Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Thu, 13 Aug 2026 20:12:55 -0700 Subject: [PATCH 1099/1328] arch_numa: avoid false positive fortify warning in setup_node_to_cpumask_map() When building ARCH=riscv using clang with CONFIG_FORTIFY_SOURCE and CONFIG_UBSAN_BOUNDS enabled, CONFIG_NR_CPUS > 64, and the default value of 2 for CONFIG_NODES_SHIFT, there is a compiletime warning from the fortify routines. In file included from mm/arch_numa.c:11: In file included from include/linux/acpi.h:14: In file included from include/linux/resource_ext.h:11: In file included from include/linux/slab.h:17: In file included from include/linux/gfp.h:7: In file included from include/linux/mmzone.h:8: In file included from include/linux/spinlock.h:60: In file included from include/linux/interrupt_rc.h:17: In file included from include/linux/smp.h:13: In file included from include/linux/cpumask.h:11: In file included from include/linux/bitmap.h:13: In file included from include/linux/string.h:383: include/linux/fortify-string.h:430:4: warning: call to '__write_overflow_field' declared with 'warning' attribute: detected write beyond size of field (1st parameter); maybe use struct_group()? [-Wattribue-warning] 430 | __write_overflow_field(p_size_field, size); | ^ include/linux/fortify-string.h:430:4: note: called by function 'fortify_memset_chk(unsigned long, unsigned long, unsigned long)' include/linux/bitmap.h:248:3: note: inlined by function 'setup_node_to_cpumask_map' 248 | memset(dst, 0, len); | ^ include/linux/fortify-string.h:462:25: note: expanded from macro 'memset' 462 | #define memset(p, c, s) __fortify_memset_chk(p, c, s, \ | ^ include/linux/fortify-string.h:453:2: note: expanded from macro '__fortify_memset_chk' 453 | fortify_memset_chk(__fortify_size, p_size, p_size_field), \ | ^ include/linux/fortify-string.h:430:4: note: use '-gline-directives-only' (implied by '-g1') or higher for more accurate inlining chain locations 430 | __write_overflow_field(p_size_field, size); | ^ 1 warning generated. In this configuration, MAX_NUMNODES is 4. clang unrolls the for loop in setup_node_to_cpumask_map() past this, which triggers the fortify check when accessing node_to_cpumask_map on the theoretical fifth loop iteration because it would be an out of bounds write. Make it clear to clang that nr_node_ids is bounded by MAX_NUMNODES due to the logic in setup_nr_node_ids() by early returning in setup_node_to_cpumask_map() should that condition be violated. Link: https://lore.kernel.org/20260813-arch_numa-avoid-fortify-warning-v2-1-093ad97a78df@kernel.org Signed-off-by: Nathan Chancellor Closes: https://github.com/ClangBuiltLinux/linux/issues/2174 Reviewed-by: Mike Rapoport (Microsoft) Cc: Kees Cook Cc: Bill Wendling Cc: Justin Stitt Cc: Nathan Chancellor Cc: Nick Desaulniers Cc: Signed-off-by: Andrew Morton --- mm/arch_numa.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/mm/arch_numa.c b/mm/arch_numa.c index 442ea239bba7..459fa60a5621 100644 --- a/mm/arch_numa.c +++ b/mm/arch_numa.c @@ -105,6 +105,18 @@ static void __init setup_node_to_cpumask_map(void) if (nr_node_ids == MAX_NUMNODES) setup_nr_node_ids(); + /* + * This check should never be true but it makes it clear to compilers + * that node_to_cpumask_map is bound by nr_node_ids, avoiding false + * positive fortify warnings when accessing node_to_cpumask_map in the + * for loop below. + */ + if (unlikely(nr_node_ids > MAX_NUMNODES)) { + pr_err("nr_node_ids (%u) is larger than MAX_NUMNODES (%u)\n", + nr_node_ids, MAX_NUMNODES); + return; + } + /* allocate and clear the mapping */ for (node = 0; node < nr_node_ids; node++) { alloc_bootmem_cpumask_var(&node_to_cpumask_map[node]); From c1afbd5de131f5e3c4fc7559acf055f8d9d86868 Mon Sep 17 00:00:00 2001 From: Usama Arif Date: Mon, 17 Aug 2026 03:38:35 -0700 Subject: [PATCH 1100/1328] mm/memcontrol: avoid false sharing between vmstats and events Moving v1 userspace eventfd handling into memcontrol-v1.c shrank struct vmpressure from 112 to 24 bytes when CONFIG_MEMCG_V1 is disabled. This moved memory_events_local[MEMCG_SWAP_FAIL] and the hot vmstats_percpu pointer onto the same cacheline. The stress-ng mremap stressor exercises MADV_PAGEOUT with swap disabled, generating about 20 million MEMCG_SWAP_FAIL updates per 60-second run on a 176-CPU test system. Those writes bounce the line while memcg statistics paths load vmstats_percpu. Move cgwb_list into the existing alignment gap and cacheline-align vmstats_percpu. This separates the pointer from the event counters without increasing the size of struct mem_cgroup in the tested configuration. The blamed commit reduced median mremap throughput by 4.38% on the test system with one socket. The patched kernel brings the performance to within 0.5% of the parent which is within the observed boot-to-boot spread (up to 1.2%). Link: https://lore.kernel.org/20260817103835.2937733-1-usama.arif@linux.dev Fixes: ea928e9e18da ("mm/vmpressure: move v1 userspace eventfd code into memcontrol-v1.c") Signed-off-by: Usama Arif Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-lkp/202608131743.c6a7dda4-lkp@intel.com Tested-by: kernel test robot Link: http://lore.kernel.org/aoAABX59IzUXz/Rv@ly-workstation Acked-by: Shakeel Butt Acked-by: Michal Hocko Cc: David Hildenbrand Cc: Johannes Weiner Cc: Muchun Song Cc: Roman Gushchin Cc: Yi Lai Signed-off-by: Andrew Morton --- include/linux/memcontrol.h | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h index e78bc98ab229..215e2e87f42b 100644 --- a/include/linux/memcontrol.h +++ b/include/linux/memcontrol.h @@ -268,10 +268,15 @@ struct mem_cgroup { #endif int kmemcg_id; - struct memcg_vmstats_percpu __percpu *vmstats_percpu; - #ifdef CONFIG_CGROUP_WRITEBACK struct list_head cgwb_list; +#endif + + /* Keep the hot per-CPU stats pointer away from memory event counters. */ + struct memcg_vmstats_percpu __percpu *vmstats_percpu + ____cacheline_aligned_in_smp; + +#ifdef CONFIG_CGROUP_WRITEBACK struct wb_domain cgwb_domain; struct memcg_cgwb_frn cgwb_frn[MEMCG_CGWB_FRN_CNT]; #endif From dd14e6cd33927fff38c78ce55c436bc0959ace27 Mon Sep 17 00:00:00 2001 From: Hongfu Li Date: Mon, 17 Aug 2026 16:06:16 +0800 Subject: [PATCH 1101/1328] selftests/mm: drop redundant open() in mprotect_tests() Remove duplicate open() for local pagemap_fd in mprotect_tests() that shadows the global pagemap_fd already opened in main(). The local fd is never used in the function. Link: https://lore.kernel.org/20260817080616.52946-1-hongfu.li@linux.dev Signed-off-by: Hongfu Li Reviewed-by: Lorenzo Stoakes (ARM) Reviewed-by: Muhammad Usama Anjum Reviewed-by: SJ Park Acked-by: David Hildenbrand (Arm) Reviewed-by: Anshuman Khandual Cc: Liam R. Howlett Cc: Michal Hocko Cc: Mike Rapoport Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- tools/testing/selftests/mm/pagemap_ioctl.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tools/testing/selftests/mm/pagemap_ioctl.c b/tools/testing/selftests/mm/pagemap_ioctl.c index cfd1987339c1..eadc7159ca5b 100644 --- a/tools/testing/selftests/mm/pagemap_ioctl.c +++ b/tools/testing/selftests/mm/pagemap_ioctl.c @@ -1332,12 +1332,6 @@ int mprotect_tests(void) int ret; char *mem, *mem2; struct page_region vec; - int pagemap_fd = open("/proc/self/pagemap", O_RDONLY); - - if (pagemap_fd < 0) { - fprintf(stderr, "open() failed\n"); - exit(1); - } /* 1. Map two pages */ mem = mmap(0, 2 * page_size, PROT_READ|PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); From 9add2cc22de6c56881eabd9f1bd6c85bf98157d0 Mon Sep 17 00:00:00 2001 From: Hui Su Date: Mon, 17 Aug 2026 14:08:46 +0800 Subject: [PATCH 1102/1328] mm/migrate_device: fix cache flush when replacing huge zero PMD migrate_vma_insert_huge_pmd_page() calls flush_cache_page() before replacing an existing huge zero PMD. However, the third argument to flush_cache_page() is a PFN, while addr + HPAGE_PMD_SIZE is an end virtual address. More importantly, the mapping being invalidated is PMD-sized rather than PAGE_SIZE-sized. Flush the whole PMD range with flush_cache_range(), matching other huge PMD invalidation paths. There is no userspace-visible effect today. The architectures that currently enable ARCH_ENABLE_THP_MIGRATION use no-op implementations of flush_cache_page()/flush_cache_range(). 32-bit ARM has non-trivial implementations, but does not enable ARCH_ENABLE_THP_MIGRATION. So this appears to be a latent API misuse rather than a currently observable bug, and I don't think a stable backport is necessary. Link: https://lore.kernel.org/20260817060845.377800-2-sh_def@163.com Fixes: a30b48bf1b24 ("mm/migrate_device: implement THP migration of zone device pages") Signed-off-by: Hui Su Reviewed-by: Balbir Singh Reviewed-by: Zi Yan Acked-by: David Hildenbrand (Arm) Cc: Alistair Popple Cc: Byungchul Park Cc: Gregory Price Cc: "Huang, Ying" Cc: Joshua Hahn Cc: Matthew Brost Cc: Rakie Kim Signed-off-by: Andrew Morton --- mm/migrate_device.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/migrate_device.c b/mm/migrate_device.c index 9a346162c688..762c5cee8fec 100644 --- a/mm/migrate_device.c +++ b/mm/migrate_device.c @@ -882,7 +882,7 @@ static int migrate_vma_insert_huge_pmd_page(struct migrate_vma *migrate, if (flush) { pte_free(vma->vm_mm, pgtable); - flush_cache_page(vma, addr, addr + HPAGE_PMD_SIZE); + flush_cache_range(vma, addr, addr + HPAGE_PMD_SIZE); pmdp_invalidate(vma, addr, pmdp); } else { pgtable_trans_huge_deposit(vma->vm_mm, pmdp, pgtable); From dd1638dfbb1c48f13cfb4f4f4e55e6570e25384b Mon Sep 17 00:00:00 2001 From: "Kiryl Shutsemau (Meta)" Date: Tue, 18 Aug 2026 12:50:26 +0100 Subject: [PATCH 1103/1328] mm: include swap.h in swapops.h swapops.h uses MAX_SWAPFILES_SHIFT, SWP_MIGRATION_READ and SWP_PTE_MARKER, all of which swap.h defines, but does not include swap.h. It compiles only where the translation unit pulled swap.h in first. leafops.h includes swapops.h on the line above swap.h, so a file whose include list reaches leafops.h before swap.h gets: In file included from include/linux/leafops.h:11: include/linux/swapops.h:88:21: error: use of undeclared identifier 'MAX_SWAPFILES_SHIFT' A header that uses a definition has to include the header that provides it. Link: https://lore.kernel.org/20260818115026.656406-1-kirill@shutemov.name Signed-off-by: Kiryl Shutsemau (Meta) Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202608181757.mza9RRj7-lkp@intel.com/ Reviewed-by: Lorenzo Stoakes (ARM) Reviewed-by: Barry Song Cc: Baoquan He Cc: Chris Li Cc: Kairui Song Cc: Kemeng Shi Cc: Nhat Pham Signed-off-by: Andrew Morton --- include/linux/swapops.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/linux/swapops.h b/include/linux/swapops.h index 1f3ff3b93e16..e7d0d529f3e0 100644 --- a/include/linux/swapops.h +++ b/include/linux/swapops.h @@ -5,6 +5,7 @@ #include #include #include +#include #ifdef CONFIG_MMU From 48863da10ba1ffe3889fc5945d3292b4e4bf1c60 Mon Sep 17 00:00:00 2001 From: Song Hu Date: Tue, 18 Aug 2026 21:01:35 +0800 Subject: [PATCH 1104/1328] mm: memcg: release the css reference when a stock slot empties consume_stock() can drive a stock slot's nr_pages to zero while its cached[] pointer stays set, so the slot keeps pinning the css reference that refill_stock() took. The offlining drain only flushes slots with cached pages, so the reference is never released unless the slot happens to be displaced by an unrelated charge or by CPU hotplug, and the memcg lingers in the dying state - up to NR_MEMCG_STOCK (7) of them per CPU under container churn. Keeping the slot populated past the last page only saves a css_get()/css_put() pair on the next charge of the same memcg, and costs more than that: the offlining drain has to know about empty slots, and refill_stock() cannot reuse them either, so a charge under a different memcg evicts a live batch through the drain_idx rotation instead. Drop the reference in consume_stock() when the slot empties. Empty slots stop existing, so is_memcg_drain_needed() and the drain path stay as they are, and refill_stock() reuses emptied slots directly. The cost is one refcount pair per emptied slot, at most once per MEMCG_CHARGE_BATCH pages. Link: https://lore.kernel.org/20260818130135.154315-1-husong@kylinos.cn Fixes: d1a05b6973c7 ("memcg: do not try to drain per-cpu caches without pages") Signed-off-by: Song Hu Acked-by: Michal Hocko Acked-by: Shakeel Butt Reviewed-by: Joshua Hahn Cc: Audra Mitchell Cc: Johannes Weiner Cc: Matthew Wilcox (Oracle) Cc: Muchun Song Cc: Roman Gushchin Cc: Nico Pache Signed-off-by: Andrew Morton --- mm/memcontrol.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mm/memcontrol.c b/mm/memcontrol.c index 1d3339520809..11b85f4b6828 100644 --- a/mm/memcontrol.c +++ b/mm/memcontrol.c @@ -2140,7 +2140,12 @@ static bool consume_stock(struct mem_cgroup *memcg, unsigned int nr_pages) stock_pages = READ_ONCE(stock->nr_pages[i]); if (stock_pages >= nr_pages) { - WRITE_ONCE(stock->nr_pages[i], stock_pages - nr_pages); + stock_pages -= nr_pages; + WRITE_ONCE(stock->nr_pages[i], stock_pages); + if (!stock_pages) { + css_put(&memcg->css); + WRITE_ONCE(stock->cached[i], NULL); + } ret = true; } break; From c7a4e939f87cc75a9a664485b10c0bf7db632156 Mon Sep 17 00:00:00 2001 From: Anshuman Date: Tue, 18 Aug 2026 19:02:06 +0530 Subject: [PATCH 1105/1328] selftests/mm: fix unchecked ftruncate return value in soft-dirty test test_mprotect() calls ftruncate() to resize the backing file before mmap()'ing it, but never checks the return value. If ftruncate() fails, the file may remain shorter than the requested mapping size. The subsequent mmap() with MAP_SHARED can still succeed in this case, but the very next line writes directly into the mapped memory (*map = 1), which can trigger SIGBUS if the mapping extends beyond the actual file size. Check the return value and fail cleanly with ksft_exit_fail_msg() if ftruncate() fails, matching the error-handling style already used for the mmap() call immediately below it. Link: https://lore.kernel.org/20260818133206.39503-1-anshumantewari123@gmail.com Signed-off-by: Anshuman Reviewed-by: Andrew Morton Reviewed-by: Sarthak Sharma Cc: David Hildenbrand Cc: Shuah Khan Signed-off-by: Andrew Morton --- tools/testing/selftests/mm/soft-dirty.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/mm/soft-dirty.c b/tools/testing/selftests/mm/soft-dirty.c index e198facf78bb..5f278913c4d7 100644 --- a/tools/testing/selftests/mm/soft-dirty.c +++ b/tools/testing/selftests/mm/soft-dirty.c @@ -152,7 +152,8 @@ static void test_mprotect(int pagemap_fd, int pagesize, bool anon) return; } unlink(fname); - ftruncate(test_fd, pagesize); + if (ftruncate(test_fd, pagesize) != 0) + ksft_exit_fail_msg("ftruncate failed\n"); map = mmap(NULL, pagesize, PROT_READ|PROT_WRITE, MAP_SHARED, test_fd, 0); if (map == MAP_FAILED) From f9dc428249ed962a70acf301f01eae8578449161 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Tue, 18 Aug 2026 02:03:40 -0700 Subject: [PATCH 1106/1328] mm, swap: ratelimit bad swap entry reports A corrupt page table hands the same bogus entry to get_swap_device() on every access to the mapping, and every rejection is logged. One machine logged 6185620 copies of the same line in a few hours. swap_dup_entry_direct() prints the same message from the fork path, once per call: the WARN_ON_ONCE() guarding it warns once, the pr_err() inside does not. Rate limit all three prints. Link: https://lore.kernel.org/20260818-swap_part_one-v1-1-a4fc58119fc0@debian.org Fixes: 23b230ba8ac3 ("mm/swap: print bad swap offset entry in get_swap_device") Signed-off-by: Breno Leitao Reviewed-by: Barry Song Reviewed-by: Nhat Pham Acked-by: Kairui Song Acked-by: David Hildenbrand (Arm) Cc: Baoquan He Cc: Chris Li Cc: Kemeng Shi Cc: Miaohe Lin Cc: Oscar Salvador Cc: Signed-off-by: Andrew Morton --- mm/swapfile.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mm/swapfile.c b/mm/swapfile.c index dacef34a3ed7..53bf01d5f7f1 100644 --- a/mm/swapfile.c +++ b/mm/swapfile.c @@ -1899,11 +1899,11 @@ struct swap_info_struct *get_swap_device(swp_entry_t entry) return si; bad_nofile: - pr_err("%s: %s%08lx\n", __func__, Bad_file, entry.val); + pr_err_ratelimited("%s: %s%08lx\n", __func__, Bad_file, entry.val); out: return NULL; put_out: - pr_err("%s: %s%08lx\n", __func__, Bad_offset, entry.val); + pr_err_ratelimited("%s: %s%08lx\n", __func__, Bad_offset, entry.val); percpu_ref_put(&si->users); return NULL; } @@ -3883,7 +3883,7 @@ int swap_dup_entry_direct(swp_entry_t entry) si = swap_entry_to_info(entry); if (WARN_ON_ONCE(!si)) { - pr_err("%s%08lx\n", Bad_file, entry.val); + pr_err_ratelimited("%s%08lx\n", Bad_file, entry.val); return -EINVAL; } From d16e52a9ba9ed5060f97ed3191017a21b5fc25a2 Mon Sep 17 00:00:00 2001 From: Anshuman Date: Wed, 19 Aug 2026 17:44:26 +0530 Subject: [PATCH 1107/1328] selftests/mm: check stat() return value in khugepaged get_finfo() get_finfo() calls stat() to get metadata about the target directory, but never checks the return value. On failure, stat() returns -1 and leaves path_stat unmodified, so path_stat.st_mode may contain uninitialized stack data. The code then checks S_ISDIR(path_stat.st_mode) against this potentially garbage value. This can produce a misleading "Not a directory" error when the real problem is a nonexistent or inaccessible path, or, in the worst case, the check could pass by chance on garbage data and let the function continue using an invalid path_stat for the rest of its logic. Check the return value and fail with a clear error message if stat() fails, matching the error-handling style already used for statfs() and read_file() later in the same function. Link: https://lore.kernel.org/20260819121426.49500-1-anshumantewari123@gmail.com Signed-off-by: Anshuman Reviewed-by: Andrew Morton Reviewed-by: SJ Park Reviewed-by: Sarthak Sharma Acked-by: David Hildenbrand (Arm) Cc: Lorenzo Stoakes Cc: Shuah Khan Signed-off-by: Andrew Morton --- tools/testing/selftests/mm/khugepaged.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/mm/khugepaged.c b/tools/testing/selftests/mm/khugepaged.c index d3a53673e1f9..1d2d6bd72fd2 100644 --- a/tools/testing/selftests/mm/khugepaged.c +++ b/tools/testing/selftests/mm/khugepaged.c @@ -124,7 +124,8 @@ static void get_finfo(const char *dir) char *str, *end; finfo.dir = dir; - stat(finfo.dir, &path_stat); + if (stat(finfo.dir, &path_stat)) + ksft_exit_fail_perror("stat()"); if (!S_ISDIR(path_stat.st_mode)) ksft_exit_fail_msg("%s: Not a directory (%s)\n", __func__, finfo.dir); if (snprintf(finfo.path, sizeof(finfo.path), "%s/" TEST_FILE, From 9e32ec53b1ec2ab28b29c82a95a65bf3d3a5d32c Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Oracle)" Date: Fri, 21 Aug 2026 15:26:09 -0400 Subject: [PATCH 1108/1328] maple_tree: add rcu locking check when LOCKDEP is enabled Patch series "maple_tree: lock checking and clean ups", v3. In this series: 1. Try to detect lock issues A number of syzbot reports are incorrectly pointing to the mm exit as a source of the locking error. The first three patches attempt to help users detect errors in their locking - but they still have to use LOCKDEP. I guess it's still down to hope and prayers. 2. Documentation fixes The documentation was lacking clarity, there are updates to try and help the users, especially around the erase() cases. 3. Two benign issues The cyclic allocator may have a race, although no in-kernel user can hit it. The erase functions may cause allocation issues if used with the incorrect locking type, but none are present in-tree. 4. The erase gfp uses mas_erase() and mtree_erase() do not take a gfp argument. To improve reliability of the erase, the first attempt to allocate will be GFP_NOWAIT, followed by a retry (if necessary of GFP_KERNEL | GFP_NOFAIL. This will ensure the data is gone. I've updated the documentation to make it more clear as well. mas_store() is not addressed in the same way, but may need to be updated at a later date, but that may require changing callers so it is out of scope here. Beyond these goals there are some test fixes, some general speed-up patches targeting extra work and cycles, and dropping dead code. This patch (of 19): When CONFIG_LOCKDEP and CONFIG_RCU_STRICT_GRACE_PERIOD is enabled, check for rcu locking issues by recording the grace period in the maple state and checking the rcu window is still valid whenever the maple state is reused with a state that is not MA_START or MA_PAUSED. Link: https://lore.kernel.org/20260821192627.4085470-1-liam@infradead.org Link: https://lore.kernel.org/20260821192627.4085470-2-liam@infradead.org Signed-off-by: Liam R. Howlett (Oracle) Cc: Boqun Feng Cc: Breno Leitao Cc: Chris Mason Cc: Chuck Lever Cc: Ingo Molnar Cc: Jason Gunthorpe Cc: Joe Perches Cc: Peter Zijlstra Cc: Rik van Riel Cc: Waiman Long Cc: Will Deacon Signed-off-by: Andrew Morton --- include/linux/maple_tree.h | 3 +++ lib/maple_tree.c | 50 +++++++++++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/include/linux/maple_tree.h b/include/linux/maple_tree.h index 1b3014377105..1acf932fcd33 100644 --- a/include/linux/maple_tree.h +++ b/include/linux/maple_tree.h @@ -484,6 +484,9 @@ struct ma_state { unsigned char mas_flags; unsigned char end; /* The end of the node */ enum store_type store_type; /* The type of store needed for this operation */ +#if IS_ENABLED(CONFIG_LOCKDEP) && IS_ENABLED(CONFIG_RCU_STRICT_GRACE_PERIOD) + unsigned long rcu_gp; +#endif }; struct ma_wr_state { diff --git a/lib/maple_tree.c b/lib/maple_tree.c index a0542b491bc2..6d805521bedd 100644 --- a/lib/maple_tree.c +++ b/lib/maple_tree.c @@ -1148,6 +1148,42 @@ static inline void mas_free(struct ma_state *mas, struct maple_enode *used) ma_free_rcu(mte_to_node(used)); } +void mas_lock_check(struct ma_state *mas) +{ + +#if IS_ENABLED(CONFIG_LOCKDEP) && IS_ENABLED(CONFIG_RCU_STRICT_GRACE_PERIOD) + if (!mas_is_active(mas)) + return; + + if (!mt_locked(mas->tree)) { + if (mt_in_rcu(mas->tree)) + WARN_ON_ONCE(poll_state_synchronize_rcu(mas->rcu_gp)); + } +#endif + +} + +void mas_init_lock_check(struct ma_state *mas) +{ +#if IS_ENABLED(CONFIG_LOCKDEP) && IS_ENABLED(CONFIG_RCU_STRICT_GRACE_PERIOD) + if (!mt_locked(mas->tree)) { + if (mt_in_rcu(mas->tree)) + mas->rcu_gp = get_state_synchronize_rcu(); + } +#endif + +} + +static void mas_may_init_lock_check(struct ma_state *mas) +{ +#if IS_ENABLED(CONFIG_LOCKDEP) && IS_ENABLED(CONFIG_RCU_STRICT_GRACE_PERIOD) + if (mas_is_start(mas) || mas_is_paused(mas)) + mas_init_lock_check(mas); + else + mas_lock_check(mas); +#endif +} + /* * mas_start() - Sets up maple state for operations. * @mas: The maple state. @@ -1166,6 +1202,7 @@ static inline struct maple_enode *mas_start(struct ma_state *mas) if (likely(mas_is_start(mas))) { struct maple_enode *root; + mas_init_lock_check(mas); mas->min = 0; mas->max = ULONG_MAX; @@ -4355,6 +4392,7 @@ void *mas_walk(struct ma_state *mas) { void *entry; + mas_may_init_lock_check(mas); if (!mas_is_active(mas) && !mas_is_start(mas)) mas->status = ma_start; retry: @@ -4992,6 +5030,7 @@ static void mas_may_activate(struct ma_state *mas) mas->status = ma_start; } else { mas->status = ma_active; + mas_lock_check(mas); } } @@ -5069,6 +5108,7 @@ void *mas_next(struct ma_state *mas, unsigned long max) { void *entry = NULL; + mas_may_init_lock_check(mas); if (mas_next_setup(mas, max, &entry)) return entry; @@ -5092,6 +5132,7 @@ void *mas_next_range(struct ma_state *mas, unsigned long max) { void *entry = NULL; + mas_may_init_lock_check(mas); if (mas_next_setup(mas, max, &entry)) return entry; @@ -5200,6 +5241,7 @@ void *mas_prev(struct ma_state *mas, unsigned long min) { void *entry = NULL; + mas_may_init_lock_check(mas); if (mas_prev_setup(mas, min, &entry)) return entry; @@ -5223,6 +5265,7 @@ void *mas_prev_range(struct ma_state *mas, unsigned long min) { void *entry = NULL; + mas_may_init_lock_check(mas); if (mas_prev_setup(mas, min, &entry)) return entry; @@ -5269,6 +5312,7 @@ EXPORT_SYMBOL_GPL(mt_prev); */ void mas_pause(struct ma_state *mas) { + mas_lock_check(mas); mas->status = ma_pause; mas->node = NULL; } @@ -5377,6 +5421,7 @@ void *mas_find(struct ma_state *mas, unsigned long max) { void *entry = NULL; + mas_may_init_lock_check(mas); if (mas_find_setup(mas, max, &entry)) return entry; @@ -5404,6 +5449,7 @@ void *mas_find_range(struct ma_state *mas, unsigned long max) { void *entry = NULL; + mas_may_init_lock_check(mas); if (mas_find_setup(mas, max, &entry)) return entry; @@ -5516,6 +5562,7 @@ void *mas_find_rev(struct ma_state *mas, unsigned long min) { void *entry = NULL; + mas_may_init_lock_check(mas); if (mas_find_rev_setup(mas, min, &entry)) return entry; @@ -5542,6 +5589,7 @@ void *mas_find_range_rev(struct ma_state *mas, unsigned long min) { void *entry = NULL; + mas_may_init_lock_check(mas); if (mas_find_rev_setup(mas, min, &entry)) return entry; @@ -5618,7 +5666,7 @@ bool mas_nomem(struct ma_state *mas, gfp_t gfp) if (!mas->sheaf && !mas->alloc) return false; - mas->status = ma_start; + mas_reset(mas); return true; } From 8f2109843137da8068f19a120d79e58ef3838429 Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Oracle)" Date: Fri, 21 Aug 2026 15:26:10 -0400 Subject: [PATCH 1109/1328] locking/lockdep: add sequence counter to held_lock Add an 8 bit small sequence counter to the held_lock struct to detect if the lock as been dropped and reacquired. This is useful when a data structure depends on a constant locking context, but is not able to detect locking and unlocking of the lock through its own API. Since the __lock_unpin_lock() will no longer detect underflow by casting the unsigned int to a signed int, update the casting code to use a temp variable for calculations using a signed int. Link: https://lore.kernel.org/20260821192627.4085470-3-liam@infradead.org Signed-off-by: Liam R. Howlett (Oracle) Suggested-by: Peter Zijlstra Cc: Ingo Molnar Cc: Will Deacon Cc: Boqun Feng Cc: Waiman Long Link: https://lore.kernel.org/all/h3tpnj5kzcrxms5picmimtkpg4aypcpip5wbd6bt2rpdj5k7eb@nhtzs3lefrkq/ Cc: Breno Leitao Cc: Chris Mason Cc: Chuck Lever Cc: Jason Gunthorpe Cc: Joe Perches Cc: Rik van Riel Signed-off-by: Andrew Morton --- include/linux/lockdep.h | 3 ++ include/linux/lockdep_types.h | 3 +- include/linux/sched.h | 1 + kernel/locking/lockdep.c | 58 ++++++++++++++++++++++++++++++----- 4 files changed, 57 insertions(+), 8 deletions(-) diff --git a/include/linux/lockdep.h b/include/linux/lockdep.h index 621566345406..a6451ecbbe9a 100644 --- a/include/linux/lockdep.h +++ b/include/linux/lockdep.h @@ -273,6 +273,9 @@ extern struct pin_cookie lock_pin_lock(struct lockdep_map *lock); extern void lock_repin_lock(struct lockdep_map *lock, struct pin_cookie); extern void lock_unpin_lock(struct lockdep_map *lock, struct pin_cookie); +extern u32 lock_sequence(struct lockdep_map *lock); +#define lockdep_sequence(lock) lock_sequence(&(lock)->dep_map) + #define lockdep_depth(tsk) (debug_locks ? (tsk)->lockdep_depth : 0) #define lockdep_assert(cond) \ diff --git a/include/linux/lockdep_types.h b/include/linux/lockdep_types.h index eae115a26488..55c4b152fedf 100644 --- a/include/linux/lockdep_types.h +++ b/include/linux/lockdep_types.h @@ -253,7 +253,8 @@ struct held_lock { unsigned int hardirqs_off:1; unsigned int sync:1; unsigned int references:11; /* 32 bits */ - unsigned int pin_count; + unsigned int pin_count:24; + unsigned int seq_count:8; }; #else /* !CONFIG_LOCKDEP */ diff --git a/include/linux/sched.h b/include/linux/sched.h index 373bcc0598d1..14d5ce8dd613 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -1288,6 +1288,7 @@ struct task_struct { u64 curr_chain_key; int lockdep_depth; unsigned int lockdep_recursion; + unsigned int lockdep_seq; struct held_lock held_locks[MAX_LOCK_DEPTH]; #endif diff --git a/kernel/locking/lockdep.c b/kernel/locking/lockdep.c index 2d4c5bab5af8..a69567bdd791 100644 --- a/kernel/locking/lockdep.c +++ b/kernel/locking/lockdep.c @@ -5077,7 +5077,7 @@ static int __lock_is_held(const struct lockdep_map *lock, int read); static int __lock_acquire(struct lockdep_map *lock, unsigned int subclass, int trylock, int read, int check, int hardirqs_off, struct lockdep_map *nest_lock, unsigned long ip, - int references, int pin_count, int sync) + int references, int pin_count, int sync, int seq) { struct task_struct *curr = current; struct lock_class *class = NULL; @@ -5183,6 +5183,7 @@ static int __lock_acquire(struct lockdep_map *lock, unsigned int subclass, hlock->holdtime_stamp = lockstat_clock(); #endif hlock->pin_count = pin_count; + hlock->seq_count = seq; if (check_wait_context(curr, hlock)) return 0; @@ -5388,7 +5389,7 @@ static int reacquire_held_locks(struct task_struct *curr, unsigned int depth, hlock->read, hlock->check, hlock->hardirqs_off, hlock->nest_lock, hlock->acquire_ip, - hlock->references, hlock->pin_count, 0)) { + hlock->references, hlock->pin_count, 0, hlock->seq_count)) { case 0: return 1; case 1: @@ -5669,14 +5670,17 @@ static void __lock_unpin_lock(struct lockdep_map *lock, struct pin_cookie cookie struct held_lock *hlock = curr->held_locks + i; if (match_held_lock(hlock, lock)) { + int pin_count; + if (WARN(!hlock->pin_count, "unpinning an unpinned lock\n")) return; - hlock->pin_count -= cookie.val; + pin_count = hlock->pin_count - cookie.val; - if (WARN((int)hlock->pin_count < 0, "pin count corrupted\n")) - hlock->pin_count = 0; + if (WARN(pin_count < 0, "pin count corrupted\n")) + pin_count = 0; + hlock->pin_count = pin_count; return; } } @@ -5684,6 +5688,24 @@ static void __lock_unpin_lock(struct lockdep_map *lock, struct pin_cookie cookie WARN(1, "unpinning an unheld lock\n"); } +static u32 __lock_sequence(struct lockdep_map *lock) +{ + struct task_struct *curr = current; + int i; + + if (unlikely(!debug_locks)) + return ~0; + + for (i = 0; i < curr->lockdep_depth; i++) { + struct held_lock *hlock = curr->held_locks + i; + + if (match_held_lock(hlock, lock)) + return hlock->seq_count; + } + + return ~0; +} + /* * Check whether we follow the irq-flags state precisely: */ @@ -5866,7 +5888,8 @@ void lock_acquire(struct lockdep_map *lock, unsigned int subclass, lockdep_recursion_inc(); __lock_acquire(lock, subclass, trylock, read, check, - irqs_disabled_flags(flags), nest_lock, ip, 0, 0, 0); + irqs_disabled_flags(flags), nest_lock, ip, 0, 0, 0, + ++current->lockdep_seq); lockdep_recursion_finish(); raw_local_irq_restore(flags); } @@ -5914,7 +5937,8 @@ void lock_sync(struct lockdep_map *lock, unsigned subclass, int read, lockdep_recursion_inc(); __lock_acquire(lock, subclass, 0, read, check, - irqs_disabled_flags(flags), nest_lock, ip, 0, 0, 1); + irqs_disabled_flags(flags), nest_lock, ip, 0, 0, 1, + ++current->lockdep_seq); check_chain_key(current); lockdep_recursion_finish(); raw_local_irq_restore(flags); @@ -6000,6 +6024,26 @@ void lock_unpin_lock(struct lockdep_map *lock, struct pin_cookie cookie) } EXPORT_SYMBOL_GPL(lock_unpin_lock); +u32 lock_sequence(struct lockdep_map *lock) +{ + unsigned long flags; + u32 seq = ~0; + + if (unlikely(!lockdep_enabled())) + return seq; + + raw_local_irq_save(flags); + check_flags(flags); + + lockdep_recursion_inc(); + seq = __lock_sequence(lock); + lockdep_recursion_finish(); + raw_local_irq_restore(flags); + + return seq; +} +EXPORT_SYMBOL_GPL(lock_sequence); + #ifdef CONFIG_LOCK_STAT static void print_lock_contention_bug(struct task_struct *curr, struct lockdep_map *lock, From 19e269917dc416f932474686bf1fcf3e91a740bd Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Oracle)" Date: Fri, 21 Aug 2026 15:26:11 -0400 Subject: [PATCH 1110/1328] maple_tree: add write lock checking with lockdep sequence numbers Use the lockdep sequence numbers to ensure the write lock is not dropped between write operations. The lockdep sequence is recorded on any walk that starts from the top of the tree and re-checked prior to any operation using an active node. When lockdep detects an issue, it sets debug_locks to 0 disabling further reports. __lock_sequnece() will return u32 ~0 when debug_locks is zero, and the real sequnece count cannot return such a high value as it is less than 32bits. By always updating the sequence number, regardless of lock state and by ignoring ~0 value in the sequence number will avoid ever printing a WARN_ON when lockdep sets debug_locks to 0. Link: https://lore.kernel.org/20260821192627.4085470-4-liam@infradead.org Signed-off-by: Liam R. Howlett (Oracle) Cc: Breno Leitao Tested-by: Breno Leitao Cc: Boqun Feng Cc: Chris Mason Cc: Chuck Lever Cc: Ingo Molnar Cc: Jason Gunthorpe Cc: Joe Perches Cc: Peter Zijlstra Cc: Rik van Riel Cc: Waiman Long Cc: Will Deacon Signed-off-by: Andrew Morton --- include/linux/maple_tree.h | 7 ++-- lib/maple_tree.c | 66 ++++++++++++++++++++++++++++++-------- 2 files changed, 58 insertions(+), 15 deletions(-) diff --git a/include/linux/maple_tree.h b/include/linux/maple_tree.h index 1acf932fcd33..d63ac92208d0 100644 --- a/include/linux/maple_tree.h +++ b/include/linux/maple_tree.h @@ -484,9 +484,12 @@ struct ma_state { unsigned char mas_flags; unsigned char end; /* The end of the node */ enum store_type store_type; /* The type of store needed for this operation */ -#if IS_ENABLED(CONFIG_LOCKDEP) && IS_ENABLED(CONFIG_RCU_STRICT_GRACE_PERIOD) +#ifdef CONFIG_LOCKDEP + u32 ld_seq; +#ifdef CONFIG_RCU_STRICT_GRACE_PERIOD unsigned long rcu_gp; -#endif +#endif /* CONFIG_RCU_STRICT_GRACE_PERIOD */ +#endif /* CONFIG_LOCKDEP */ }; struct ma_wr_state { diff --git a/lib/maple_tree.c b/lib/maple_tree.c index 6d805521bedd..d8c826e1ca0c 100644 --- a/lib/maple_tree.c +++ b/lib/maple_tree.c @@ -1148,40 +1148,77 @@ static inline void mas_free(struct ma_state *mas, struct maple_enode *used) ma_free_rcu(mte_to_node(used)); } -void mas_lock_check(struct ma_state *mas) -{ -#if IS_ENABLED(CONFIG_LOCKDEP) && IS_ENABLED(CONFIG_RCU_STRICT_GRACE_PERIOD) +#ifdef CONFIG_LOCKDEP +static struct lockdep_map *mas_lockdep_map(struct ma_state *mas) +{ + struct maple_tree *mt = mas->tree; + + if (mt_external_lock(mt)) + return mt->ma_external_lock; + + return &(mt->ma_lock).dep_map; +} + +#endif + +static void mas_lock_check(struct ma_state *mas) +{ +#ifdef CONFIG_LOCKDEP + struct lockdep_map *map; + u32 seq; + if (!mas_is_active(mas)) return; +#ifdef CONFIG_RCU_STRICT_GRACE_PERIOD if (!mt_locked(mas->tree)) { if (mt_in_rcu(mas->tree)) WARN_ON_ONCE(poll_state_synchronize_rcu(mas->rcu_gp)); } -#endif +#endif /* CONFIG_RCU_STRICT_GRACE_PERIOD */ + + map = mas_lockdep_map(mas); + if (!map) + return; + + seq = lock_sequence(map); + if (seq != UINT_MAX && mas->ld_seq != UINT_MAX) + WARN_ON_ONCE(mas->ld_seq != seq); +#endif /* CONFIG_LOCKDEP */ } -void mas_init_lock_check(struct ma_state *mas) +static void mas_init_lock_check(struct ma_state *mas) { -#if IS_ENABLED(CONFIG_LOCKDEP) && IS_ENABLED(CONFIG_RCU_STRICT_GRACE_PERIOD) +#ifdef CONFIG_LOCKDEP + struct lockdep_map *map; +#ifdef CONFIG_RCU_STRICT_GRACE_PERIOD if (!mt_locked(mas->tree)) { if (mt_in_rcu(mas->tree)) mas->rcu_gp = get_state_synchronize_rcu(); + return; } -#endif +#endif /* CONFIG_RCU_STRICT_GRACE_PERIOD */ + + map = mas_lockdep_map(mas); + if (map) /* Update regardless of lock state */ + mas->ld_seq = lock_sequence(map); +#endif /* CONFIG_LOCKDEP */ } static void mas_may_init_lock_check(struct ma_state *mas) { -#if IS_ENABLED(CONFIG_LOCKDEP) && IS_ENABLED(CONFIG_RCU_STRICT_GRACE_PERIOD) - if (mas_is_start(mas) || mas_is_paused(mas)) +#ifdef CONFIG_LOCKDEP +#ifdef CONFIG_RCU_STRICT_GRACE_PERIOD + if (mas_is_start(mas) || mas_is_paused(mas)) { mas_init_lock_check(mas); - else - mas_lock_check(mas); -#endif + return; + } +#endif /* CONFIG_RCU_STRICT_GRACE_PERIOD */ + mas_lock_check(mas); +#endif /* CONFIG_LOCKDEP */ } /* @@ -4864,6 +4901,7 @@ void *mas_store(struct ma_state *mas, void *entry) { MA_WR_STATE(wr_mas, mas, entry); + mas_may_init_lock_check(mas); trace_ma_write(TP_FCT, mas, 0, entry); #ifdef CONFIG_DEBUG_MAPLE_TREE if (MAS_WARN_ON(mas, mas->index > mas->last)) @@ -4922,6 +4960,7 @@ int mas_store_gfp(struct ma_state *mas, void *entry, gfp_t gfp) MA_WR_STATE(wr_mas, mas, entry); int ret = 0; + mas_may_init_lock_check(mas); retry: mas_wr_preallocate(&wr_mas, entry); if (unlikely(mas_nomem(mas, gfp))) { @@ -4952,6 +4991,7 @@ void mas_store_prealloc(struct ma_state *mas, void *entry) { MA_WR_STATE(wr_mas, mas, entry); + mas_lock_check(mas); if (mas->store_type == wr_store_root) { mas_wr_prealloc_setup(&wr_mas); goto store; @@ -4984,6 +5024,7 @@ int mas_preallocate(struct ma_state *mas, void *entry, gfp_t gfp) { MA_WR_STATE(wr_mas, mas, entry); + mas_may_init_lock_check(mas); mas_wr_prealloc_setup(&wr_mas); mas->store_type = mas_wr_store_type(&wr_mas); mas_prealloc_calc(&wr_mas, entry); @@ -5469,7 +5510,6 @@ EXPORT_SYMBOL_GPL(mas_find_range); static bool mas_find_rev_setup(struct ma_state *mas, unsigned long min, void **entry) { - switch (mas->status) { case ma_active: goto active; From 5e0b9b71bcf405a0390ea9efc853bd07186c65a0 Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Oracle)" Date: Fri, 21 Aug 2026 15:26:12 -0400 Subject: [PATCH 1111/1328] maple_tree: documentation fix Don't include the word flag in the quotes with the actual flag. Link: https://lore.kernel.org/20260821192627.4085470-5-liam@infradead.org Signed-off-by: Liam R. Howlett (Oracle) Cc: Boqun Feng Cc: Breno Leitao Cc: Chris Mason Cc: Chuck Lever Cc: Ingo Molnar Cc: Jason Gunthorpe Cc: Joe Perches Cc: Peter Zijlstra Cc: Rik van Riel Cc: Waiman Long Cc: Will Deacon Signed-off-by: Andrew Morton --- Documentation/core-api/maple_tree.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/core-api/maple_tree.rst b/Documentation/core-api/maple_tree.rst index ccdd1615cf97..34964ec88d17 100644 --- a/Documentation/core-api/maple_tree.rst +++ b/Documentation/core-api/maple_tree.rst @@ -211,7 +211,7 @@ Advanced Locking The maple tree uses a spinlock by default, but external locks can be used for tree updates as well. To use an external lock, the tree must be initialized -with the ``MT_FLAGS_LOCK_EXTERN flag``, this is usually done with the +with the ``MT_FLAGS_LOCK_EXTERN`` flag, this is usually done with the MTREE_INIT_EXT() #define, which takes an external lock as an argument. Functions and structures From acac9108a6a1a897f66060cafd98c452af1442c1 Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Oracle)" Date: Fri, 21 Aug 2026 15:26:13 -0400 Subject: [PATCH 1112/1328] maple_tree: drop dead code from mas_extend_spanning_null() mas_extend_spanning_null() had a clause if the end of the range being written (mas->last) is the same as the end of the existing range it is overwriting (wr_mas->r_max), action will be taken. This code path is not possible because the only calling function increments mas->last (unless it's ULONG_MAX) to walk to one beyond the write and then resets the value back to the initial value. In the case of mas->last == ULONG_MAX, then the second part of the statement will always be false - mas->last cannot be less than the node max. This code never executed and is flawed anyways (the arguments are incorrectly ordered), so removing it is the safest action. Since the code never executes, it is not fixing any issue so Fixes tag is not given. Link: https://lore.kernel.org/20260821192627.4085470-6-liam@infradead.org Signed-off-by: Liam R. Howlett (Oracle) Cc: Boqun Feng Cc: Breno Leitao Cc: Chris Mason Cc: Chuck Lever Cc: Ingo Molnar Cc: Jason Gunthorpe Cc: Joe Perches Cc: Peter Zijlstra Cc: Rik van Riel Cc: Waiman Long Cc: Will Deacon Signed-off-by: Andrew Morton --- lib/maple_tree.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/maple_tree.c b/lib/maple_tree.c index d8c826e1ca0c..3509e293c84f 100644 --- a/lib/maple_tree.c +++ b/lib/maple_tree.c @@ -2997,13 +2997,6 @@ static inline void mas_extend_spanning_null(struct ma_wr_state *l_wr_mas, if (r_mas->last < r_wr_mas->r_max) r_mas->last = r_wr_mas->r_max; r_mas->offset++; - } else if ((r_mas->last == r_wr_mas->r_max) && - (r_mas->last < r_mas->max) && - !mas_slot_locked(r_mas, r_wr_mas->slots, r_mas->offset + 1)) { - r_mas->last = mas_safe_pivot(r_mas, r_wr_mas->pivots, - r_wr_mas->type, r_mas->offset + 1); - r_mas->offset++; - r_wr_mas->r_max = r_mas->last; } } From 3526e09d8cab0aea3f3737dbeb613ccb52660359 Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Oracle)" Date: Fri, 21 Aug 2026 15:26:14 -0400 Subject: [PATCH 1113/1328] maple_tree: drop MAPLE_ALLOC_SLOTS MAPLE_ALLOC_SLOTS is no longer used, so remove it. Link: https://lore.kernel.org/20260821192627.4085470-7-liam@infradead.org Signed-off-by: Liam R. Howlett (Oracle) Cc: Boqun Feng Cc: Breno Leitao Cc: Chris Mason Cc: Chuck Lever Cc: Ingo Molnar Cc: Jason Gunthorpe Cc: Joe Perches Cc: Peter Zijlstra Cc: Rik van Riel Cc: Waiman Long Cc: Will Deacon Signed-off-by: Andrew Morton --- include/linux/maple_tree.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/linux/maple_tree.h b/include/linux/maple_tree.h index d63ac92208d0..14ca9ac775d9 100644 --- a/include/linux/maple_tree.h +++ b/include/linux/maple_tree.h @@ -28,13 +28,11 @@ #define MAPLE_NODE_SLOTS 31 /* 256 bytes including ->parent */ #define MAPLE_RANGE64_SLOTS 16 /* 256 bytes */ #define MAPLE_ARANGE64_SLOTS 10 /* 240 bytes */ -#define MAPLE_ALLOC_SLOTS (MAPLE_NODE_SLOTS - 1) #else /* 32bit sizes */ #define MAPLE_NODE_SLOTS 63 /* 256 bytes including ->parent */ #define MAPLE_RANGE64_SLOTS 32 /* 256 bytes */ #define MAPLE_ARANGE64_SLOTS 21 /* 240 bytes */ -#define MAPLE_ALLOC_SLOTS (MAPLE_NODE_SLOTS - 2) #endif /* defined(CONFIG_64BIT) || defined(BUILD_VDSO32_64) */ #define MAPLE_NODE_MASK 255UL From 7d1e34352727cf1073eccbacd13fac075defc7a4 Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Oracle)" Date: Fri, 21 Aug 2026 15:26:15 -0400 Subject: [PATCH 1114/1328] maple_tree: clarify comments on mas_nomem() When an allocation completely fails, the return is false. If the allocation succeeds or partially succeeds, return true to indicate a retry of the operation. Note that since the lock may have been dropped, the operation is retried from the start - including potentially allocating more memory. Link: https://lore.kernel.org/20260821192627.4085470-8-liam@infradead.org Signed-off-by: Liam R. Howlett (Oracle) Cc: Boqun Feng Cc: Breno Leitao Cc: Chris Mason Cc: Chuck Lever Cc: Ingo Molnar Cc: Jason Gunthorpe Cc: Joe Perches Cc: Peter Zijlstra Cc: Rik van Riel Cc: Waiman Long Cc: Will Deacon Signed-off-by: Andrew Morton --- lib/maple_tree.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/maple_tree.c b/lib/maple_tree.c index 3509e293c84f..baaaa128594c 100644 --- a/lib/maple_tree.c +++ b/lib/maple_tree.c @@ -5677,10 +5677,11 @@ EXPORT_SYMBOL_GPL(mas_erase); /** * mas_nomem() - Check if there was an error allocating and do the allocation - * if necessary If there are allocations, then free them. + * if necessary. + * * @mas: The maple state * @gfp: The GFP_FLAGS to use for allocations - * Return: true on allocation, false otherwise. + * Return: False on no memory. True otherwise (partial success as well) */ bool mas_nomem(struct ma_state *mas, gfp_t gfp) __must_hold(mas->tree->ma_lock) @@ -5696,6 +5697,10 @@ bool mas_nomem(struct ma_state *mas, gfp_t gfp) mas_alloc_nodes(mas, gfp); } + /* + * Return false on zero forward progress. Partial allocations are kept + * so the retry path will attempt to get the rest. + */ if (!mas->sheaf && !mas->alloc) return false; From 4bd59d5bc2c8f3e7e1bd62f3a5029471a6b531bb Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Oracle)" Date: Fri, 21 Aug 2026 15:26:16 -0400 Subject: [PATCH 1115/1328] maple_tree: use prefetched value in mas_wr_store_type() The slot contents exist in wr_mas->content, which has less overhead than reading the slot again. Link: https://lore.kernel.org/20260821192627.4085470-9-liam@infradead.org Signed-off-by: Liam R. Howlett (Oracle) Cc: Boqun Feng Cc: Breno Leitao Cc: Chris Mason Cc: Chuck Lever Cc: Ingo Molnar Cc: Jason Gunthorpe Cc: Joe Perches Cc: Peter Zijlstra Cc: Rik van Riel Cc: Waiman Long Cc: Will Deacon Signed-off-by: Andrew Morton --- lib/maple_tree.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/maple_tree.c b/lib/maple_tree.c index baaaa128594c..d5fa85bc7aab 100644 --- a/lib/maple_tree.c +++ b/lib/maple_tree.c @@ -3280,7 +3280,7 @@ static inline void mas_wr_slot_store(struct ma_wr_state *wr_mas) void __rcu **slots = wr_mas->slots; bool gap = false; - gap |= !mt_slot_locked(mas->tree, slots, offset); + gap |= !wr_mas->content; gap |= !mt_slot_locked(mas->tree, slots, offset + 1); if (wr_mas->offset_end - offset == 1) { From 88f87f881240da3f09541d8232255376778b8f1c Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Oracle)" Date: Fri, 21 Aug 2026 15:26:17 -0400 Subject: [PATCH 1116/1328] maple_tree: optimise mas_wr_node_store() when not in rcu mode Clearing the entire node on the stack is unnecessary since most of the node will be overwritten anyways. Just clear what isn't used after the data is in place. Benchmarking shows a speedup of 0.67% on a height 4 tree with 2048 entries. Link: https://lore.kernel.org/20260821192627.4085470-10-liam@infradead.org Signed-off-by: Liam R. Howlett (Oracle) Cc: Boqun Feng Cc: Breno Leitao Cc: Chris Mason Cc: Chuck Lever Cc: Ingo Molnar Cc: Jason Gunthorpe Cc: Joe Perches Cc: Peter Zijlstra Cc: Rik van Riel Cc: Waiman Long Cc: Will Deacon Signed-off-by: Andrew Morton --- lib/maple_tree.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/lib/maple_tree.c b/lib/maple_tree.c index d5fa85bc7aab..56812db8b0ef 100644 --- a/lib/maple_tree.c +++ b/lib/maple_tree.c @@ -3187,7 +3187,7 @@ static void mas_wr_spanning_store(struct ma_wr_state *wr_mas) static inline void mas_wr_node_store(struct ma_wr_state *wr_mas) { unsigned char dst_offset, offset_end; - unsigned char copy_size, node_pivots; + unsigned char copy_size, node_pivots, node_slots; struct maple_node reuse, *newnode; unsigned long *dst_pivots; void __rcu **dst_slots; @@ -3200,6 +3200,7 @@ static inline void mas_wr_node_store(struct ma_wr_state *wr_mas) in_rcu = mt_in_rcu(mas->tree); offset_end = wr_mas->offset_end; node_pivots = mt_pivots[wr_mas->type]; + node_slots = mt_slots[wr_mas->type]; /* Assume last adds an entry */ new_end = mas->end + 1 - offset_end + mas->offset; if (mas->last == wr_mas->end_piv) { @@ -3211,7 +3212,6 @@ static inline void mas_wr_node_store(struct ma_wr_state *wr_mas) if (in_rcu) { newnode = mas_pop_node(mas); } else { - memset(&reuse, 0, sizeof(struct maple_node)); newnode = &reuse; } @@ -3255,7 +3255,21 @@ static inline void mas_wr_node_store(struct ma_wr_state *wr_mas) dst_pivots[new_end] = mas->max; done: - mas_leaf_set_meta(newnode, maple_leaf_64, new_end); + if (!in_rcu && new_end + 2 < node_slots) { + unsigned char clear_from = new_end + 1; + + /* + * Note that the last slot is never cleared, since the metadata + * will be stored there or it has a value. + */ + memset(dst_slots + clear_from, 0, + sizeof(void __rcu *) * (node_slots - clear_from)); + if (clear_from < node_pivots) + memset(dst_pivots + clear_from, 0, + sizeof(unsigned long) * (node_pivots - clear_from)); + } + + mas_leaf_set_meta(newnode, wr_mas->type, new_end); if (in_rcu) { struct maple_enode *old_enode = mas->node; From f0a3892cd726909b0e9c30ec9b5b05f1f63a5fbf Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Oracle)" Date: Fri, 21 Aug 2026 15:26:18 -0400 Subject: [PATCH 1117/1328] maple_tree: micro optimisation of mas_wr_store_type() Use three new local booleans instead of reading other structures. This has shown an increase of 0.62% on a 2048 entry tree of height 4. Link: https://lore.kernel.org/20260821192627.4085470-11-liam@infradead.org Signed-off-by: Liam R. Howlett (Oracle) Cc: Boqun Feng Cc: Breno Leitao Cc: Chris Mason Cc: Chuck Lever Cc: Ingo Molnar Cc: Jason Gunthorpe Cc: Joe Perches Cc: Peter Zijlstra Cc: Rik van Riel Cc: Waiman Long Cc: Will Deacon Signed-off-by: Andrew Morton --- lib/maple_tree.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/maple_tree.c b/lib/maple_tree.c index 56812db8b0ef..e2c780a64c9c 100644 --- a/lib/maple_tree.c +++ b/lib/maple_tree.c @@ -3737,6 +3737,9 @@ static inline enum store_type mas_wr_store_type(struct ma_wr_state *wr_mas) { struct ma_state *mas = wr_mas->mas; unsigned char new_end; + bool appending; + bool one_slot; + bool in_rcu; if (unlikely(mas_is_none(mas) || mas_is_ptr(mas))) return wr_store_root; @@ -3756,21 +3759,30 @@ static inline enum store_type mas_wr_store_type(struct ma_wr_state *wr_mas) return wr_new_root; new_end = mas_wr_new_end(wr_mas); + in_rcu = mt_in_rcu(mas->tree); + appending = mas->offset == mas->end; + one_slot = wr_mas->offset_end - mas->offset == 1; + /* Potential spanning rebalance collapsing a node */ if (new_end < mt_min_slots[wr_mas->type]) { if (!mte_is_root(mas->node)) return wr_rebalance; + if (!in_rcu) { + if (appending) + return wr_append; + else if (mas->end == new_end && one_slot) + return wr_slot_store; + } return wr_node_store; } if (new_end >= mt_slots[wr_mas->type]) return wr_split_store; - if (!mt_in_rcu(mas->tree) && (mas->offset == mas->end)) + if (!in_rcu && appending) return wr_append; - if ((new_end == mas->end) && (!mt_in_rcu(mas->tree) || - (wr_mas->offset_end - mas->offset == 1))) + if (new_end == mas->end && (!in_rcu || one_slot)) return wr_slot_store; return wr_node_store; From cf1f9bae5dac2082db374a0acfe41e55aaf909e2 Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Oracle)" Date: Fri, 21 Aug 2026 15:26:19 -0400 Subject: [PATCH 1118/1328] maple_tree: add bulk parent set helper Instead of calculating the parent pointer each time for a child, cache the majority of the parent pointer and only change the slot per child. Drop the mas_set_parent() function since the last user has been removed. Testing on a tree containing 2048 entries of height 4 had an increased gain of 3.51% on nodes tracking gaps. Link: https://lore.kernel.org/20260821192627.4085470-12-liam@infradead.org Signed-off-by: Liam R. Howlett (Oracle) Cc: Boqun Feng Cc: Breno Leitao Cc: Chris Mason Cc: Chuck Lever Cc: Ingo Molnar Cc: Jason Gunthorpe Cc: Joe Perches Cc: Peter Zijlstra Cc: Rik van Riel Cc: Waiman Long Cc: Will Deacon Signed-off-by: Andrew Morton --- lib/maple_tree.c | 97 +++++++++++++++++++++--------------------------- 1 file changed, 42 insertions(+), 55 deletions(-) diff --git a/lib/maple_tree.c b/lib/maple_tree.c index e2c780a64c9c..c968e25bea0a 100644 --- a/lib/maple_tree.c +++ b/lib/maple_tree.c @@ -450,46 +450,6 @@ enum maple_type mas_parent_type(struct ma_state *mas, struct maple_enode *enode) return 0; } -/* - * mas_set_parent() - Set the parent node and encode the slot - * @mas: The maple state - * @enode: The encoded maple node. - * @parent: The encoded maple node that is the parent of @enode. - * @slot: The slot that @enode resides in @parent. - * - * Slot number is encoded in the enode->parent bit 3-6 or 2-6, depending on the - * parent type. - */ -static inline -void mas_set_parent(struct ma_state *mas, struct maple_enode *enode, - const struct maple_enode *parent, unsigned char slot) -{ - unsigned long val = (unsigned long)parent; - unsigned long shift; - unsigned long type; - enum maple_type p_type = mte_node_type(parent); - - MAS_BUG_ON(mas, p_type == maple_dense); - MAS_BUG_ON(mas, p_type == maple_leaf_64); - - switch (p_type) { - case maple_range_64: - case maple_arange_64: - shift = MAPLE_PARENT_SLOT_SHIFT; - type = MAPLE_PARENT_RANGE64; - break; - default: - case maple_dense: - case maple_leaf_64: - shift = type = 0; - break; - } - - val &= ~MAPLE_NODE_MASK; /* Clear all node metadata in parent */ - val |= (slot << shift) | type; - mte_to_node(enode)->parent = ma_parent_ptr(val); -} - /* * mte_parent_slot() - get the parent slot of @enode. * @enode: The encoded maple node. @@ -871,6 +831,42 @@ static inline void ma_set_meta_gap(struct maple_node *mn, enum maple_type mt, meta->gap = offset; } +/* + * mas_set_parent_slots() - Bulk operation to set many slot parent pointers + * @mas: The maple state + * @parent: The encoded maple node that is the parent of @enode. + * @slot: The slot that of the @enode. + * @start_slot: The offset into @slot + * @count: The number of slots to set (eg: exclusive) + */ +static inline +void mas_set_parent_slots(struct ma_state *mas, struct maple_enode *parent, + void __rcu **slots, unsigned char start_slot, unsigned char count) +{ + unsigned long val; + unsigned long shift; + unsigned long type; + enum maple_type p_type = mte_node_type(parent); + unsigned char i; + + MAS_BUG_ON(mas, p_type != maple_range_64 && + p_type != maple_arange_64); + + shift = MAPLE_PARENT_SLOT_SHIFT; + type = MAPLE_PARENT_RANGE64; + + val = (unsigned long)parent; + val &= ~MAPLE_NODE_MASK; + + for (i = 0; i < count; i++) { + unsigned long pval = val | ((start_slot + i) << shift) | type; + struct maple_enode *child; + + child = mt_slot_locked(mas->tree, slots, i); + mte_to_node(child)->parent = ma_parent_ptr(pval); + } +} + /* * mat_add() - Add a @dead_enode to the ma_topiary of a list of dead nodes. * @mat: the ma_topiary, a linked list of dead nodes. @@ -1609,14 +1605,10 @@ static inline void mas_adopt_children(struct ma_state *mas, struct maple_node *node = mte_to_node(parent); void __rcu **slots = ma_slots(node, type); unsigned long *pivots = ma_pivots(node, type); - struct maple_enode *child; - unsigned char offset; + unsigned char end; - offset = ma_data_end(node, type, pivots, mas->max); - do { - child = mas_slot_locked(mas, slots, offset); - mas_set_parent(mas, child, parent, offset); - } while (offset--); + end = ma_data_end(node, type, pivots, mas->max); + mas_set_parent_slots(mas, parent, slots, 0, end + 1); } /* @@ -1998,15 +1990,10 @@ unsigned long node_copy(struct ma_state *mas, struct maple_node *src, s_slots = ma_slots(src, s_mt) + start; s_pivots = ma_pivots(src, s_mt) + start; memcpy(d_slots, s_slots, size * sizeof(void __rcu *)); - if (!ma_is_leaf(d_mt) && s_mt == maple_copy) { - struct maple_enode *edst = mt_mk_node(dst, d_mt); - - for (int i = 0; i < size; i++) - mas_set_parent(mas, - mt_slot_locked(mas->tree, d_slots, i), - edst, d_start + i); - } + if (!ma_is_leaf(d_mt) && s_mt == maple_copy) + mas_set_parent_slots(mas, mt_mk_node(dst, d_mt), + d_slots, d_start, size); d_gaps = ma_gaps(dst, d_mt); if (d_gaps) { From 35f1342e5b893a740eff2ef9ab337bfaa63ab76d Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Oracle)" Date: Fri, 21 Aug 2026 15:26:20 -0400 Subject: [PATCH 1119/1328] maple_tree: catch race in mas_alloc_cyclic() If mas_alloc_cyclic() is called during a low memory situation, it is possible the lock may be dropped so reclaim can occur. There is a window where some other task may allocate the same id and cause the mas_insert() to fail with -EEXIST. In this scenario the function will return -EEXIST, which is not expected. Modifying the retry on mas_nomem() to re-search for a slot means that any race with other writes will not matter as the lock will be held between finding the index and writing the index. Moving the flag logic avoids cases where the flag is modified on drop lock/reacquire or when the write fails after clearing the flag. No existing users are exposed to this issue. Link: https://lore.kernel.org/20260821192627.4085470-13-liam@infradead.org Fixes: 9b6713cc7522 ("maple_tree: Add mtree_alloc_cyclic()") Signed-off-by: Liam R. Howlett (Oracle) Reported-by: Chris Mason Reviewed-by: Chuck Lever Cc: Boqun Feng Cc: Breno Leitao Cc: Ingo Molnar Cc: Jason Gunthorpe Cc: Joe Perches Cc: Peter Zijlstra Cc: Rik van Riel Cc: Waiman Long Cc: Will Deacon Signed-off-by: Andrew Morton --- lib/maple_tree.c | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/lib/maple_tree.c b/lib/maple_tree.c index c968e25bea0a..190f480d6850 100644 --- a/lib/maple_tree.c +++ b/lib/maple_tree.c @@ -3868,35 +3868,40 @@ int mas_alloc_cyclic(struct ma_state *mas, unsigned long *startp, void *entry, unsigned long range_lo, unsigned long range_hi, unsigned long *next, gfp_t gfp) { - unsigned long min = range_lo; - int ret = 0; + int ret; + unsigned long min; + + min = range_lo; + do { + range_lo = max(min, *next); + ret = mas_empty_area(mas, range_lo, range_hi, 1); + if (ret < 0 && range_lo > min) { + mas_reset(mas); + ret = mas_empty_area(mas, min, range_hi, 1); + if (ret == 0) + ret = 1; + } + if (ret < 0) + goto out; + + mas_insert(mas, entry); + } while (mas_nomem(mas, gfp)); + + if (mas_is_err(mas)) { + ret = xa_err(mas->node); + goto out; + } - range_lo = max(min, *next); - ret = mas_empty_area(mas, range_lo, range_hi, 1); if ((mas->tree->ma_flags & MT_FLAGS_ALLOC_WRAPPED) && ret == 0) { mas->tree->ma_flags &= ~MT_FLAGS_ALLOC_WRAPPED; ret = 1; } - if (ret < 0 && range_lo > min) { - mas_reset(mas); - ret = mas_empty_area(mas, min, range_hi, 1); - if (ret == 0) - ret = 1; - } - if (ret < 0) - return ret; - - do { - mas_insert(mas, entry); - } while (mas_nomem(mas, gfp)); - if (mas_is_err(mas)) - return xa_err(mas->node); - *startp = mas->index; *next = *startp + 1; if (*next == 0) mas->tree->ma_flags |= MT_FLAGS_ALLOC_WRAPPED; +out: mas_destroy(mas); return ret; } From 3f06ef1f34a7ee3b77ee03c13a0c50db8fd3c536 Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Oracle)" Date: Fri, 21 Aug 2026 15:26:21 -0400 Subject: [PATCH 1120/1328] maple_tree: document that erase may use GFP_KERNEL for allocations State that the mas_erase() and mtree_erase() functions may use GFP_KERNEL on allocation retry. Don't just depend on people reading the documentation by adding a check that will warn of the use. Link: https://lore.kernel.org/20260821192627.4085470-14-liam@infradead.org Signed-off-by: Liam R. Howlett (Oracle) Reviewed-by: Rik van Riel Cc: Jason Gunthorpe Cc: Boqun Feng Cc: Breno Leitao Cc: Chris Mason Cc: Chuck Lever Cc: Ingo Molnar Cc: Joe Perches Cc: Peter Zijlstra Cc: Waiman Long Cc: Will Deacon Signed-off-by: Andrew Morton --- lib/maple_tree.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/lib/maple_tree.c b/lib/maple_tree.c index 190f480d6850..440863bdea26 100644 --- a/lib/maple_tree.c +++ b/lib/maple_tree.c @@ -5658,6 +5658,10 @@ EXPORT_SYMBOL_GPL(mas_find_range_rev); * Searches for @mas->index, sets @mas->index and @mas->last to the range and * erases that range. * + * Note that erase requires allocations and will use GFP_KERNEL to do so if + * necessary. If the allocation fails, the internal lock will be dropped to + * retry. + * * Return: the entry that was erased or %NULL, @mas->index and @mas->last are updated. */ void *mas_erase(struct ma_state *mas) @@ -5666,13 +5670,21 @@ void *mas_erase(struct ma_state *mas) unsigned long index = mas->index; MA_WR_STATE(wr_mas, mas, NULL); + /* + * In low memory situations, the allocation is retried with the gfp flag + * GFP_KERNEL. The internal spinlock is dropped in mas_nomem(), however + * the external lock is not dropped. + */ + if (mt_external_lock(mas->tree)) + might_alloc(GFP_KERNEL); + if (!mas_is_active(mas) || !mas_is_start(mas)) mas->status = ma_start; write_retry: entry = mas_state_walk(mas); if (!entry) - return NULL; + goto out; /* Must reset to ensure spanning writes of last slot are detected */ mas_reset(mas); @@ -5683,8 +5695,10 @@ void *mas_erase(struct ma_state *mas) goto write_retry; } - if (mas_is_err(mas)) + if (mas_is_err(mas)) { + entry = NULL; goto out; + } mas_wr_store_entry(&wr_mas); out: @@ -6012,6 +6026,10 @@ EXPORT_SYMBOL(mtree_alloc_rrange); * Erasing is the same as a walk to an entry then a store of a NULL to that * ENTIRE range. In fact, it is implemented as such using the advanced API. * + * Note that erase requires allocations and will use GFP_KERNEL to do so if + * necessary. If the allocation fails, the internal lock will be dropped to + * retry. + * * Return: The entry stored at the @index or %NULL */ void *mtree_erase(struct maple_tree *mt, unsigned long index) @@ -6021,6 +6039,7 @@ void *mtree_erase(struct maple_tree *mt, unsigned long index) MA_STATE(mas, mt, index, index); trace_ma_op(TP_FCT, &mas); + might_alloc(GFP_KERNEL); mtree_lock(mt); entry = mas_erase(&mas); mtree_unlock(mt); From f1681380b5f928e147954d87d875f57a25df189c Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Oracle)" Date: Fri, 21 Aug 2026 15:26:22 -0400 Subject: [PATCH 1121/1328] maple_tree: avoid mas_erase() and mtree_erase() failures Failures to remove entries using the two APIs to erase the entries may result in allocation failures. The failures may go unnoticed and an unexpected entry may remain. Instead, fall back to retrying with GFP_KERNEL | __GFP_NOFAIL so that the entry will be removed. Link: https://lore.kernel.org/20260821192627.4085470-15-liam@infradead.org Signed-off-by: Liam R. Howlett (Oracle) Cc: Rik van Riel Cc: Jason Gunthorpe Cc: Boqun Feng Cc: Breno Leitao Cc: Chris Mason Cc: Chuck Lever Cc: Ingo Molnar Cc: Joe Perches Cc: Peter Zijlstra Cc: Waiman Long Cc: Will Deacon Signed-off-by: Andrew Morton --- include/linux/maple_tree.h | 2 ++ lib/maple_tree.c | 68 ++++++++++++++++++++++++++++---------- 2 files changed, 52 insertions(+), 18 deletions(-) diff --git a/include/linux/maple_tree.h b/include/linux/maple_tree.h index 14ca9ac775d9..173602e87c14 100644 --- a/include/linux/maple_tree.h +++ b/include/linux/maple_tree.h @@ -570,6 +570,8 @@ int mas_alloc_cyclic(struct ma_state *mas, unsigned long *startp, unsigned long *next, gfp_t gfp); bool mas_nomem(struct ma_state *mas, gfp_t gfp); +bool mas_nomem_nofail(struct ma_state *mas, unsigned long index, + unsigned long last); void mas_pause(struct ma_state *mas); void maple_tree_init(void); void mas_destroy(struct ma_state *mas); diff --git a/lib/maple_tree.c b/lib/maple_tree.c index 440863bdea26..d47d4304f781 100644 --- a/lib/maple_tree.c +++ b/lib/maple_tree.c @@ -5658,9 +5658,10 @@ EXPORT_SYMBOL_GPL(mas_find_range_rev); * Searches for @mas->index, sets @mas->index and @mas->last to the range and * erases that range. * - * Note that erase requires allocations and will use GFP_KERNEL to do so if - * necessary. If the allocation fails, the internal lock will be dropped to - * retry. + * Note that erase requires allocations and will use GFP_KERNEL | __GFP_NOFAIL + * to do so if necessary. If the allocation fails, the internal lock will be + * dropped to retry. An externally locked tree must be protected by a lock that + * allows blocking for this API. * * Return: the entry that was erased or %NULL, @mas->index and @mas->last are updated. */ @@ -5672,8 +5673,8 @@ void *mas_erase(struct ma_state *mas) /* * In low memory situations, the allocation is retried with the gfp flag - * GFP_KERNEL. The internal spinlock is dropped in mas_nomem(), however - * the external lock is not dropped. + * GFP_KERNEL | __GFP_NOFAIL. The internal spinlock is dropped in + * mas_nomem_nofail(), however the external lock is not dropped. */ if (mt_external_lock(mas->tree)) might_alloc(GFP_KERNEL); @@ -5689,16 +5690,8 @@ void *mas_erase(struct ma_state *mas) /* Must reset to ensure spanning writes of last slot are detected */ mas_reset(mas); mas_wr_preallocate(&wr_mas, NULL); - if (mas_nomem(mas, GFP_KERNEL)) { - /* in case the range of entry changed when unlocked */ - mas->index = mas->last = index; + if (mas_nomem_nofail(mas, index, index)) goto write_retry; - } - - if (mas_is_err(mas)) { - entry = NULL; - goto out; - } mas_wr_store_entry(&wr_mas); out: @@ -5721,6 +5714,10 @@ bool mas_nomem(struct ma_state *mas, gfp_t gfp) if (likely(mas->node != MA_ERROR(-ENOMEM))) return false; + /* Allocations can fail, don't do this. */ + WARN_ON_ONCE(!gfpflags_allow_blocking(gfp) && + mt_external_lock(mas->tree)); + if (gfpflags_allow_blocking(gfp) && !mt_external_lock(mas->tree)) { mtree_unlock(mas->tree); mas_alloc_nodes(mas, gfp); @@ -5731,7 +5728,9 @@ bool mas_nomem(struct ma_state *mas, gfp_t gfp) /* * Return false on zero forward progress. Partial allocations are kept - * so the retry path will attempt to get the rest. + * so the retry path will attempt to get the rest. The failure should + * not happen as we try our best to reclaim. The user would need an + * external lock with a non-blocking gfp in a low memory situation. */ if (!mas->sheaf && !mas->alloc) return false; @@ -5740,6 +5739,39 @@ bool mas_nomem(struct ma_state *mas, gfp_t gfp) return true; } +/** + * mas_nomem_nofail() - Retry allocations with __GFP_NOFAIL, if the maple state + * has stored the -ENOMEM error. + * @mas: The maple state + * @index: The start of the range for the @mas reset + * @last: The end of the range for the @mas reset + * + * Return: false if @mas isn't in an -ENOMEM state. True if the allocation + * happens, the state is reset. The internal lock will be dropped and external + * locks must allow blocking. + */ +bool mas_nomem_nofail(struct ma_state *mas, unsigned long index, + unsigned long last) + __must_hold(mas->tree->ma_lock) +{ + gfp_t gfp; + + if (likely(mas->node != MA_ERROR(-ENOMEM))) + return false; + + gfp = GFP_KERNEL | __GFP_NOFAIL; + if (!mt_external_lock(mas->tree)) { + mtree_unlock(mas->tree); + mas_alloc_nodes(mas, gfp); + mtree_lock(mas->tree); + } else { + mas_alloc_nodes(mas, gfp); + } + + mas_set_range(mas, index, last); + return true; +} + void __init maple_tree_init(void) { struct kmem_cache_args args = { @@ -6026,9 +6058,9 @@ EXPORT_SYMBOL(mtree_alloc_rrange); * Erasing is the same as a walk to an entry then a store of a NULL to that * ENTIRE range. In fact, it is implemented as such using the advanced API. * - * Note that erase requires allocations and will use GFP_KERNEL to do so if - * necessary. If the allocation fails, the internal lock will be dropped to - * retry. + * Note that erase requires allocations and will use GFP_KERNEL | __GFP_NOFAIL + * to do so if necessary. If the allocation fails, the internal lock will be + * dropped to retry. * * Return: The entry stored at the @index or %NULL */ From ee2487d9ba6ccf1b10c55fbae2cb0526c7b775e3 Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Oracle)" Date: Fri, 21 Aug 2026 15:26:23 -0400 Subject: [PATCH 1122/1328] maple_tree: document erase and allocations better During a discussion on the maple tree erase process and GFP flags, Jason suggested there be an amendment to the documentation to clarify the situation on allocations within the tree. The added text is an attempt to better explain that the tree may allocate, even when erasing, and provide some guidance on how to work around such issues. [akpm@linux-foundation.org: tweak mtree_erase() description, per Jason] Link: https://lore.kernel.org/all/20260617180419.GA231643@ziepe.ca/ Link: https://lore.kernel.org/20260821192627.4085470-16-liam@infradead.org Signed-off-by: Liam R. Howlett (Oracle) Suggested-by: Jason Gunthorpe Cc: Rik van Riel Cc: Boqun Feng Cc: Breno Leitao Cc: Chris Mason Cc: Chuck Lever Cc: Ingo Molnar Cc: Joe Perches Cc: Peter Zijlstra Cc: Waiman Long Cc: Will Deacon Signed-off-by: Andrew Morton --- Documentation/core-api/maple_tree.rst | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/Documentation/core-api/maple_tree.rst b/Documentation/core-api/maple_tree.rst index 34964ec88d17..12bccfb6aac1 100644 --- a/Documentation/core-api/maple_tree.rst +++ b/Documentation/core-api/maple_tree.rst @@ -17,7 +17,8 @@ supports iterating over a range of entries and going to the previous or next entry in a cache-efficient manner. The tree can also be put into an RCU-safe mode of operation which allows reading and writing concurrently. Writers must synchronize on a lock, which can be the default spinlock, or the user can set -the lock to an external lock of a different type. +the lock to an external lock of a different type. Note that external locks may +interfere with allocations in a low memory situation. The Maple Tree maintains a small memory footprint and was designed to use modern processor cache efficiently. The majority of the users will be able to @@ -42,6 +43,15 @@ successful store operation within a given code segment when allocating cannot be done. Allocations of nodes are relatively small at around 256 bytes. +Since the maple tree uses internal nodes that are allocated and has rules on +data density, erasing an entry may cause allocations to occur. That is, +erasing an entry may consume memory. Users must take care to ensure that they +do not violate the larger system constraints on when and how memory is +allocated. Most situations are fine to allocate, but the pre-allocation +support is provided as a mechanism to avoid trickier situations. There is also +the possibility of using special entries and clean up the tree later, in +extreme circumstances. + .. _maple-tree-normal-api: Normal API @@ -63,7 +73,10 @@ success or an error code otherwise. mtree_store_range() works in the same way but takes a range. mtree_load() is used to retrieve the entry stored at a given index. You can use mtree_erase() to erase an entire range by only knowing one value within that range, or mtree_store() call with an entry of -NULL may be used to partially erase a range or many ranges at once. +NULL may be used to partially erase a range or many ranges at once. Note that +mtree_erase() may use GFP_KERNEL | __GFP_NOFAIL for allocations and cannot +fail. mtree_erase() can sleep, so it must not be called from an atomic +context. If you want to only store a new entry to a range (or index) if that range is currently ``NULL``, you can use mtree_insert_range() or mtree_insert() which @@ -163,7 +176,10 @@ You can use mas_erase() to erase an entire range by setting index and last of the maple state to the desired range to erase. This will erase the first range that is found in that range, set the maple state index and last as the range that was erased and return the entry that existed -at that location. +at that location. Note that mas_erase() may allocate with the GFP_KERNEL +__GFP_NOFAIL and cannot fail, but may sleep. If this is not okay, consider +using mas_store_gfp() and pass it a ``NULL``, +after setting up the correct range by walking to the entry. You can walk each entry within a range by using mas_for_each(). If you want to walk each element of the tree then ``0`` and ``ULONG_MAX`` may be used as From 18d4f8e6e6ce9b2ebd1c733777babec67356acd3 Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Oracle)" Date: Fri, 21 Aug 2026 15:26:24 -0400 Subject: [PATCH 1123/1328] maple_tree: change two GFP flags in tests The GFP flags in two tests are obviously incorrect. Make the tests correctly run by updating the GFP flags. Link: https://lore.kernel.org/all/d9cbb89faa5bdb71d451781d214a51ce8923a83e.camel@perches.com/ Link: https://lore.kernel.org/20260821192627.4085470-17-liam@infradead.org Signed-off-by: Liam R. Howlett (Oracle) Reported-by: Joe Perches Cc: Boqun Feng Cc: Breno Leitao Cc: Chris Mason Cc: Chuck Lever Cc: Ingo Molnar Cc: Jason Gunthorpe Cc: Peter Zijlstra Cc: Rik van Riel Cc: Waiman Long Cc: Will Deacon Signed-off-by: Andrew Morton --- tools/testing/radix-tree/maple.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/testing/radix-tree/maple.c b/tools/testing/radix-tree/maple.c index 0607913a3022..d967e76a3c06 100644 --- a/tools/testing/radix-tree/maple.c +++ b/tools/testing/radix-tree/maple.c @@ -35234,7 +35234,7 @@ static noinline void __init check_prealloc(struct maple_tree *mt) mt_set_non_kernel(1); /* Spanning store */ mas_set_range(&mas, 1, 100); - MT_BUG_ON(mt, mas_preallocate(&mas, ptr, GFP_KERNEL & GFP_NOWAIT) == 0); + MT_BUG_ON(mt, mas_preallocate(&mas, ptr, GFP_NOWAIT) == 0); allocated = mas_allocated(&mas); height = mas_mt_height(&mas); MT_BUG_ON(mt, allocated != 0); @@ -35257,7 +35257,7 @@ static noinline void __init check_prealloc(struct maple_tree *mt) MT_BUG_ON(mt, mas_allocated(&mas) != 0); mas_set_range(&mas, 0, 200); mt_set_non_kernel(1); - MT_BUG_ON(mt, mas_preallocate(&mas, ptr, GFP_KERNEL & GFP_NOWAIT) == 0); + MT_BUG_ON(mt, mas_preallocate(&mas, ptr, GFP_NOWAIT) == 0); allocated = mas_allocated(&mas); height = mas_mt_height(&mas); MT_BUG_ON(mt, allocated != 0); From 00f67814a14e614b749ebe54076ef1e3e6454f2b Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Oracle)" Date: Fri, 21 Aug 2026 15:26:25 -0400 Subject: [PATCH 1124/1328] maple_tree: fix argument name in header The mas_prev_range() function takes a min and not a max. Link: https://lore.kernel.org/20260821192627.4085470-18-liam@infradead.org Fixes: 6b9e93e01020 ("maple_tree: add mas_prev_range() and mas_find_range_rev interface") Signed-off-by: Liam R. Howlett (Oracle) Cc: Boqun Feng Cc: Breno Leitao Cc: Chris Mason Cc: Chuck Lever Cc: Ingo Molnar Cc: Jason Gunthorpe Cc: Joe Perches Cc: Peter Zijlstra Cc: Rik van Riel Cc: Waiman Long Cc: Will Deacon Signed-off-by: Andrew Morton --- include/linux/maple_tree.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/maple_tree.h b/include/linux/maple_tree.h index 173602e87c14..e595ae5cd0ee 100644 --- a/include/linux/maple_tree.h +++ b/include/linux/maple_tree.h @@ -577,7 +577,7 @@ void maple_tree_init(void); void mas_destroy(struct ma_state *mas); void *mas_prev(struct ma_state *mas, unsigned long min); -void *mas_prev_range(struct ma_state *mas, unsigned long max); +void *mas_prev_range(struct ma_state *mas, unsigned long min); void *mas_next(struct ma_state *mas, unsigned long max); void *mas_next_range(struct ma_state *mas, unsigned long max); From 4e4fe9d7271edf83ee4475e92b96aa89bd2ccd7e Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Oracle)" Date: Fri, 21 Aug 2026 15:26:26 -0400 Subject: [PATCH 1125/1328] maple_tree: avoid extra gap calculation Prior to ending the ascension loop of larger operations like split, rebalance, and spanning store the gap in the node had been calculated. Once the node is inserted into the tree, the gap is recalculated in mas_update_gap(). This can be avoided by creating a helper for mas_update_gap() that accepts the known gap value, which reduces the operations required for gap updating path. Link: https://lore.kernel.org/20260821192627.4085470-19-liam@infradead.org Signed-off-by: Liam R. Howlett (Oracle) Cc: Boqun Feng Cc: Breno Leitao Cc: Chris Mason Cc: Chuck Lever Cc: Ingo Molnar Cc: Jason Gunthorpe Cc: Joe Perches Cc: Peter Zijlstra Cc: Rik van Riel Cc: Waiman Long Cc: Will Deacon Signed-off-by: Andrew Morton --- lib/maple_tree.c | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/lib/maple_tree.c b/lib/maple_tree.c index d47d4304f781..b43e2ce129b3 100644 --- a/lib/maple_tree.c +++ b/lib/maple_tree.c @@ -1566,14 +1566,26 @@ static inline void mas_parent_gap(struct ma_state *mas, unsigned char offset, goto ascend; } +static __always_inline void mas_update_gap_known(struct ma_state *mas, + unsigned long gap) +{ + unsigned char pslot; + unsigned long p_gap; + + pslot = mte_parent_slot(mas->node); + p_gap = ma_gaps(mte_parent(mas->node), + mas_parent_type(mas, mas->node))[pslot]; + + if (p_gap != gap) + mas_parent_gap(mas, pslot, gap); +} + /* * mas_update_gap() - Update a nodes gaps and propagate up if necessary. * @mas: the maple state. */ static inline void mas_update_gap(struct ma_state *mas) { - unsigned char pslot; - unsigned long p_gap; unsigned long max_gap; if (!mt_is_alloc(mas->tree)) @@ -1583,13 +1595,7 @@ static inline void mas_update_gap(struct ma_state *mas) return; max_gap = mas_max_gap(mas); - - pslot = mte_parent_slot(mas->node); - p_gap = ma_gaps(mte_parent(mas->node), - mas_parent_type(mas, mas->node))[pslot]; - - if (p_gap != max_gap) - mas_parent_gap(mas, pslot, max_gap); + mas_update_gap_known(mas, max_gap); } /* @@ -2137,8 +2143,8 @@ static inline void mas_wmb_replace(struct ma_state *mas, struct maple_copy *cp) mas->node = mt_slot_locked(mas->tree, cp->slot, 0); /* Insert the new data in the tree */ mas_topiary_replace(mas, old_enode, cp->height); - if (!mte_is_leaf(mas->node)) - mas_update_gap(mas); + if (mt_is_alloc(mas->tree) && !mte_is_root(mas->node)) + mas_update_gap_known(mas, cp->gap[0]); mtree_range_walk(mas); } From d17c749d32b2d6d16981ac003ff1cae85860e819 Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Oracle)" Date: Fri, 21 Aug 2026 15:26:27 -0400 Subject: [PATCH 1126/1328] maple_tree: add helper mas_make_walkable() A check in mas_walk() was incorrect and caused inefficient use of the maple state. The same issue existed in mas_erase(), but was left unfixed. Making a helper function is the obvious answer. Link: https://lore.kernel.org/20260821192627.4085470-20-liam@infradead.org Signed-off-by: Liam R. Howlett (Oracle) Cc: Boqun Feng Cc: Breno Leitao Cc: Chris Mason Cc: Chuck Lever Cc: Ingo Molnar Cc: Jason Gunthorpe Cc: Joe Perches Cc: Peter Zijlstra Cc: Rik van Riel Cc: Waiman Long Cc: Will Deacon Signed-off-by: Andrew Morton --- lib/maple_tree.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/maple_tree.c b/lib/maple_tree.c index b43e2ce129b3..1aba6cced713 100644 --- a/lib/maple_tree.c +++ b/lib/maple_tree.c @@ -261,6 +261,12 @@ static inline bool mas_is_underflow(struct ma_state *mas) return mas->status == ma_underflow; } +static inline void mas_make_walkable(struct ma_state *mas) +{ + if (!mas_is_active(mas) && !mas_is_start(mas)) + mas->status = ma_start; +} + static __always_inline struct maple_node *mte_to_node( const struct maple_enode *entry) { @@ -4447,8 +4453,7 @@ void *mas_walk(struct ma_state *mas) void *entry; mas_may_init_lock_check(mas); - if (!mas_is_active(mas) && !mas_is_start(mas)) - mas->status = ma_start; + mas_make_walkable(mas); retry: entry = mas_state_walk(mas); if (mas_is_start(mas)) { @@ -5685,9 +5690,7 @@ void *mas_erase(struct ma_state *mas) if (mt_external_lock(mas->tree)) might_alloc(GFP_KERNEL); - if (!mas_is_active(mas) || !mas_is_start(mas)) - mas->status = ma_start; - + mas_make_walkable(mas); write_retry: entry = mas_state_walk(mas); if (!entry) From 5d3fe91b70e7b71174d2a29eb9731a5601e7df0c Mon Sep 17 00:00:00 2001 From: Enlin Mu Date: Fri, 21 Aug 2026 14:40:57 +0800 Subject: [PATCH 1127/1328] mm/vmscan: fix comment logic in balance_pgdat In balance_pgdat(), when the low watermark is met, processes sleeping on pfmemalloc_wait are woken up because they are able to safely make forward progress. However, the comment incorrectly states "they should not be able", which contradicts the actual code behavior. Fix this typo to accurately reflect the logic. Link: https://lore.kernel.org/20260821064057.4081-1-enlin.mu@linux.dev Signed-off-by: Enlin Mu Signed-off-by: Enlin Mu Reviewed-by: Barry Song Acked-by: Johannes Weiner Acked-by: Shakeel Butt Cc: Axel Rasmussen Cc: David Hildenbrand Cc: Kairui Song Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Wei Xu Cc: Yuanchu Xie Signed-off-by: Andrew Morton --- mm/vmscan.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/vmscan.c b/mm/vmscan.c index c1404a59523d..b569eeca590d 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -7276,7 +7276,7 @@ static int balance_pgdat(pg_data_t *pgdat, int order, int highest_zoneidx) /* * If the low watermark is met there is no need for processes - * to be throttled on pfmemalloc_wait as they should not be + * to be throttled on pfmemalloc_wait as they should now be * able to safely make forward progress. Wake them */ if (waitqueue_active(&pgdat->pfmemalloc_wait) && From 0e0ac326c511d514817cc7b6d7741afd59098ce2 Mon Sep 17 00:00:00 2001 From: Shakeel Butt Date: Fri, 21 Aug 2026 19:47:07 -0700 Subject: [PATCH 1128/1328] memcg: move LRU size accounting on reparenting instead of copying it When a memory cgroup is offlined its LRU folios are reparented to the parent. lruvec_reparent_lru() splices the child's lists into the parent's and credits the parent with the child's per-zone lru_zone_size[], but never clears the child's copy, so the size is copied rather than moved. lru_gen_reparent_memcg() does the same for MGLRU. The parent is left correct, credited with exactly the folios it took over. The stale value sits on the child and nothing will correct it: folio->memcg_data now resolves to the parent, so every later update_lru_size() for those folios goes there. Dying cgroups are not freed immediately and mem_cgroup_iter() still walks them, so shrink_lruvec() keeps being called on them. get_scan_count() reads the phantom counter through lruvec_lru_size() and the scan loop then grinds through nr[] in SWAP_CLUSTER_MAX steps against an empty list, for as long as the dead cgroup lives. Under MGLRU the MGLRU scanner runs instead, but count_shadow_nodes() sums all of NR_LRU_LISTS through lruvec_lru_size() and over-budgets the shadow node limit just the same. On one 251 GiB host a sweep of every mz->lru_zone_size[] found 380 counters describing folios on no list at all: 124777314 pages, 476 GiB, 1.89x the machine's RAM, across 57 cgroups. All were on memcgs with CSS_DYING set and CSS_ONLINE clear, and parent/child pairs reported byte-identical sizes. LRU_UNEVICTABLE needs its size moved too. Its list is deliberately not spliced because lruvec_init() poisons the head - the unevictable LRU is imaginary and folios are never threaded on it - but the size is kept by lruvec_add_folio()/lruvec_del_folio() and those folios account to the parent from here on. This depends on commit bf4ade7dbd76 ("memcg: keep folio's objcg same as its node") and must not be backported ahead of it. Without that invariant a folio's objcg can belong to another node, so a folio already spliced onto the parent's list can still resolve to the child's lruvec until the objcg's node is reparented in a later iteration of memcg_reparent_objcgs(); clearing the child's counter early then lets lruvec_del_folio() underflow it and trip the WARN_ONCE()/VM_BUG_ON() in mem_cgroup_update_lru_size(). Link: https://lore.kernel.org/20260822024707.77192-1-shakeel.butt@linux.dev Fixes: 07a6e9a2c199 ("mm: vmscan: prepare for reparenting traditional LRU folios") Fixes: f304652609ea ("mm: vmscan: prepare for reparenting MGLRU folios") Signed-off-by: Shakeel Butt Acked-by: Michal Hocko Cc: Johannes Weiner Cc: Roman Gushchin Cc: Muchun Song Cc: # After: bf4ade7dbd76: memcg: keep folio's objcg same as its node Signed-off-by: Andrew Morton --- mm/folio.c | 9 +++++++++ mm/vmscan.c | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/mm/folio.c b/mm/folio.c index 59c477120b9a..c02dcea9c03c 100644 --- a/mm/folio.c +++ b/mm/folio.c @@ -1130,7 +1130,16 @@ static void lruvec_reparent_lru(struct lruvec *child_lruvec, for_each_managed_zone_pgdat(zone, NODE_DATA(nid), zid, MAX_NR_ZONES - 1) { unsigned long size = mem_cgroup_get_zone_lru_size(child_lruvec, lru, zid); + if (!size) + continue; + + /* + * The folios are accounted to the parent from now on, so the + * size has to be moved, not just copied. Leaving it behind + * makes the dying child describe folios it no longer owns. + */ mem_cgroup_update_lru_size(parent_lruvec, lru, zid, size); + mem_cgroup_update_lru_size(child_lruvec, lru, zid, -(long)size); } } diff --git a/mm/vmscan.c b/mm/vmscan.c index b569eeca590d..73a81b4a3e16 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -4635,7 +4635,12 @@ void lru_gen_reparent_memcg(struct mem_cgroup *memcg, struct mem_cgroup *parent, for_each_managed_zone_pgdat(zone, NODE_DATA(nid), zid, MAX_NR_ZONES - 1) { unsigned long size = mem_cgroup_get_zone_lru_size(child_lruvec, lru, zid); + if (!size) + continue; + + /* Move the accounting, do not duplicate it. */ mem_cgroup_update_lru_size(parent_lruvec, lru, zid, size); + mem_cgroup_update_lru_size(child_lruvec, lru, zid, -(long)size); } } } From 0685630fdccb62dcb0e3f44525a40578da5f6dc8 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 8 Aug 2026 22:03:12 +0200 Subject: [PATCH 1129/1328] selftests/proc: make proc-maps-race work with READ_IMPLIES_EXEC test_maps_tearing_from_split times out when READ_IMPLIES_EXEC is set. This happens by default on pre-ARMv6 CPUs, which lack no-execute support. split_vma() re-maps the first page with mod_info->prot | PROT_EXEC to make it differ from its neighbours. With READ_IMPLIES_EXEC the original mapping is already executable, so no split occurs and the test hangs waiting for the modifier child. Use PROT_NONE for the split mapping, which always differs from its readable neighbours. Link: https://lore.kernel.org/20260808200312.6326-1-kmehltretter@gmail.com Fixes: beb69e817246 ("selftests/proc: add /proc/pid/maps tearing from vma split test") Assisted-by: Codex:gpt-5.6-terra Signed-off-by: Karl Mehltretter Acked-by: Suren Baghdasaryan Cc: Alexey Dobriyan Cc: Jann Horn Cc: Liam R. Howlett Cc: Shuah Khan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- tools/testing/selftests/proc/proc-maps-race.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/proc/proc-maps-race.c b/tools/testing/selftests/proc/proc-maps-race.c index 1026d8c400e1..415eccb70468 100644 --- a/tools/testing/selftests/proc/proc-maps-race.c +++ b/tools/testing/selftests/proc/proc-maps-race.c @@ -490,7 +490,8 @@ static bool query_addr_at(int maps_fd, void *addr, static inline bool split_vma(FIXTURE_DATA(proc_maps_race) *self) { - return mmap(self->mod_info->addr, self->page_size, self->mod_info->prot | PROT_EXEC, + /* PROT_NONE differs from both readable neighbors. */ + return mmap(self->mod_info->addr, self->page_size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) != MAP_FAILED; } From ce366bfa821ec81dd45bde547ee31e659306cc61 Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Mon, 24 Aug 2026 21:17:29 +0800 Subject: [PATCH 1130/1328] f2fs: fix to avoid potential deadloop in f2fs_fsync_node_pages() There is potential deadloop in race condition: Thread A Thread B - fsync - f2fs_do_sync_file - f2fs_fsync_node_pages - last_fsync_dnode - folio_get(last_folio) - f2fs_setattr - f2fs_truncate - f2fs_truncate_blocks - f2fs_do_truncate_blocks - f2fs_truncate_inode_blocks - truncate_dnode - truncate_node - invalidate_mapping_pages - folio->mapping = NULL - is_node_folio alwasy return false - atomic && !marked is always true, then goto retry Cc: stable@kernel.org Fixes: 608514deba38 ("f2fs: set fsync mark only for the last dnode") Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/node.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fs/f2fs/node.c b/fs/f2fs/node.c index 968e5ed38816..86c2e67e43b6 100644 --- a/fs/f2fs/node.c +++ b/fs/f2fs/node.c @@ -2016,6 +2016,11 @@ int f2fs_fsync_node_pages(struct f2fs_sb_info *sbi, struct inode *inode, f2fs_debug(sbi, "Retry to write fsync mark: ino=%u, idx=%lx", ino, last_folio->index); folio_lock(last_folio); + if (unlikely(!is_node_folio(last_folio))) { + f2fs_folio_put(last_folio, true); + ret = -EAGAIN; + goto out; + } f2fs_folio_wait_writeback(last_folio, NODE, true, true); folio_mark_dirty(last_folio); folio_unlock(last_folio); From 0dbc2398fca3bb33eda963849f865ddb1b3aa05e Mon Sep 17 00:00:00 2001 From: Norbert Szetei Date: Sat, 22 Aug 2026 11:12:11 +0200 Subject: [PATCH 1131/1328] openvswitch: only skb_tx_error() a packet we are about to drop queue_userspace_packet() borrows the packet skb -- it only copies it into a private netlink message (user_skb) and does not own it; on return do_execute_actions() keeps forwarding it through the flow's remaining actions. Its error path nevertheless calls skb_tx_error(skb), which via skb_zcopy_clear() does skb_shinfo(skb)->flags &= ~SKBFL_ALL_ZEROCOPY, stripping SKBFL_SHARED_FRAG from that live skb (skb_tx_error()'s kerneldoc says "skb must be freed afterwards"). For a MSG_ZEROCOPY skb carrying page-cache frags, SKBFL_SHARED_FRAG is what makes esp_input() skb_cow_data() before in-place AEAD; once it is stripped a later local ESP-in-UDP delivery decrypts in place over pages the sender does not own -- an unprivileged page-cache write (the "Fragnesia" primitive). do_execute_actions() ignores output_userspace()'s return value, so any action after a failed USERSPACE upcall inherits the stripped skb. Move the skb_tx_error() to the flow-miss drop path - the "default" branch of ovs_dp_process_packet()'s switch(error), before kfree_skb(). The call has been here since commit 36d5fe6a0007 ("core, nfqueue, openvswitch: Orphan frags in skb_zerocopy and handle errors") but was harmless until esp_input() began relying on SKBFL_SHARED_FRAG to gate in-place decrypt; only then did stripping it on a still-forwarded skb become a page-cache write primitive. Fixes: 36d5fe6a0007 ("core, nfqueue, openvswitch: Orphan frags in skb_zerocopy and handle errors") Fixes: f4c50a4034e6 ("xfrm: esp: avoid in-place decrypt on shared skb frags") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Signed-off-by: Norbert Szetei Reviewed-by: Ilya Maximets Tested-by: Jongmin Jang Link: https://patch.msgid.link/55A52703-7548-4A55-A9CE-2A37145BDCAD@doyensec.com Signed-off-by: Paolo Abeni --- net/openvswitch/datapath.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/net/openvswitch/datapath.c b/net/openvswitch/datapath.c index 100a2ce4d74d..631a03136fa1 100644 --- a/net/openvswitch/datapath.c +++ b/net/openvswitch/datapath.c @@ -285,6 +285,7 @@ void ovs_dp_process_packet(struct sk_buff *skb, struct sw_flow_key *key) consume_skb(skb); break; default: + skb_tx_error(skb); kfree_skb(skb); break; } @@ -604,8 +605,6 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb, err = genlmsg_unicast(ovs_dp_get_net(dp), user_skb, upcall_info->portid); user_skb = NULL; out: - if (err) - skb_tx_error(skb); consume_skb(user_skb); consume_skb(nskb); From 8ece906150128d5ec2462aabcc978c568433eca4 Mon Sep 17 00:00:00 2001 From: Norbert Szetei Date: Sat, 22 Aug 2026 11:13:47 +0200 Subject: [PATCH 1132/1328] net: skbuff: don't skb_tx_error() the source skb in skb_zerocopy() skb_zerocopy() copies frags from @from into @to. On an skb_orphan_frags() failure it calls skb_tx_error(@from), a destructive operation on the source skb the copy helper does not own. That completes @from's zerocopy uarg and clears SKBFL_ALL_ZEROCOPY, including the SKBFL_SHARED_FRAG page-ownership marker. Both callers already report the failure on their own drop path. nfnetlink_queue does it at nla_put_failure, and Open vSwitch does it in the flow-miss drop arm of ovs_dp_process_packet(), so nothing is lost by dropping it here. On Open vSwitch's OVS_ACTION_ATTR_USERSPACE path the skb is not freed on this error: do_execute_actions() ignores output_userspace()'s return value and, unless the upcall was the last action, keeps forwarding the same skb through the flow's remaining actions. The uarg is completed while that skb is still in flight, telling the producer its buffers are free, and SKBFL_SHARED_FRAG is cleared on an skb the rest of the stack still handles. That flag is what makes esp_input() call skb_cow_data() instead of decrypting in place, so a later local ESP delivery can decrypt over frags the skb does not own privately. Leave error reporting to the callers. Fixes: 36d5fe6a0007 ("core, nfqueue, openvswitch: Orphan frags in skb_zerocopy and handle errors") Cc: stable@vger.kernel.org Suggested-by: Ilya Maximets Signed-off-by: Norbert Szetei Reviewed-by: Ilya Maximets Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/6E3A780D-FB87-421F-9964-B1D457D7D106@doyensec.com Signed-off-by: Paolo Abeni --- net/core/skbuff.c | 1 - 1 file changed, 1 deletion(-) diff --git a/net/core/skbuff.c b/net/core/skbuff.c index d4382b68d56e..ab3d161247b9 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -3914,7 +3914,6 @@ skb_zerocopy(struct sk_buff *to, struct sk_buff *from, int len, int hlen) skb_len_add(to, len + plen); if (unlikely(skb_orphan_frags(from, GFP_ATOMIC))) { - skb_tx_error(from); if (j > 0) put_page(virt_to_head_page(from->head)); return -ENOMEM; From f66bdb1cc0fcd227a062378f8be0b5873aa5600a Mon Sep 17 00:00:00 2001 From: Norbert Szetei Date: Sat, 22 Aug 2026 11:15:08 +0200 Subject: [PATCH 1133/1328] net: skbuff: don't touch shared zerocopy state in skb_tx_error() skb_tx_error() completes the zerocopy uarg and clears SKBFL_ALL_ZEROCOPY, and skb_zcopy_downgrade_managed() clears SKBFL_MANAGED_FRAG_REFS. Both live in skb_shinfo(), which every clone shares, while the caller only owns the reference it is about to drop. Through a clone it tells the producer its pages are free and drops SKBFL_SHARED_FRAG for an skb that is still in flight. Open vSwitch reaches this with a non-last OVS_ACTION_ATTR_RECIRC: clone_execute() sends a skb_clone() into ovs_dp_process_packet() while do_execute_actions() keeps forwarding the original, and skb_clone() does not privatise the frags here -- skb_orphan_frags() returns early on SKBFL_DONT_ORPHAN. A flow miss on the clone then strips the marker from the packet still being forwarded, and a later local ESP delivery decrypts in place over frags it does not own privately. Skip it for a cloned skb. Nothing is lost: skb_release_data() clears the zerocopy state once the last reference to the shared data goes. Fixes: 25121173f7b1 ("skb: api to report errors for zero copy skbs") Cc: stable@vger.kernel.org Suggested-by: Ilya Maximets Signed-off-by: Norbert Szetei Reviewed-by: Ilya Maximets Tested-by: Jongmin Jang Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/CFAB292A-674B-4C14-BB2C-BB8830AD5659@doyensec.com Signed-off-by: Paolo Abeni --- net/core/skbuff.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/net/core/skbuff.c b/net/core/skbuff.c index ab3d161247b9..b9541329f1a7 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -1417,10 +1417,13 @@ EXPORT_SYMBOL(skb_dump); * * Report xmit error if a device callback is tracking this skb. * skb must be freed afterwards. + * + * Does nothing for a cloned skb: the zerocopy state lives in + * skb_shinfo(), which the clones share. */ void skb_tx_error(struct sk_buff *skb) { - if (skb) { + if (skb && !skb_cloned(skb)) { skb_zcopy_downgrade_managed(skb); skb_zcopy_clear(skb, true); } From b3be0e5bed8ab9adbf127a3eee3806e61190ec14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Federico=20Valent=C3=ADn=20Andrade?= Date: Mon, 24 Aug 2026 11:02:10 -0300 Subject: [PATCH 1134/1328] ALSA: usb-audio: Skip reading sample rate on M-Audio Venom MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The M-Audio Venom (0763:2084) is an USB Audio Class 1 compliant synth with an integrated audio interface, it does not implement GET_CUR on SAMPLING_FREQ_CONTROL, hanging up if requested on endpoint 0. The first class request issued by the driver after enumeration is a GET_CUR on endpoint 0x02, freezing the device's USB microcontroller. Timing out on every transfer afterwards with -ETIMEDOUT, such as SET_INTERFACE, so neither the mixer nor any streaming interface is set up. Analyzing a USBPcap capture of the Windows driver I found it never requests the sampling frequency, as the synth exposes a single discrete of 44100Hz on both streaming interfaces, thus asking for it is unnecessary. So I applied get_sample_rate to avoid this check, and disable_autosuspend because the synth doesn't come back from being suspended. Signed-off-by: Federico Valentín Andrade Link: https://patch.msgid.link/20260824140211.17003-2-fandrade@frba.utn.edu.ar Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 5c544bd88728..91938172912a 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2333,6 +2333,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_GENERIC_IMPLICIT_FB), DEVICE_FLG(0x0763, 0x2081, /* M-Audio Fast Track Ultra */ QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x0763, 0x2084, /* M-Audio Venom */ + QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_DISABLE_AUTOSUSPEND), DEVICE_FLG(0x07fd, 0x000b, /* MOTU M Series 2nd hardware revision */ QUIRK_FLAG_CTL_MSG_DELAY_1M), DEVICE_FLG(0x08bb, 0x2702, /* LineX FM Transmitter */ From 4c0ec35e011309bd7091053ad5bbc01420967a66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Federico=20Valent=C3=ADn=20Andrade?= Date: Mon, 24 Aug 2026 11:02:11 -0300 Subject: [PATCH 1135/1328] ALSA: usb-audio: Skip mixer creation on M-Audio Venom MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The M-Audio Venom (0763:2084) does not answer any GET_CUR request of its feature units, hanging up the USB microcontroller and "responding" with timeouts. So the mixer building lasts around 47 seconds, and the device does not stream at all. The same GET_CUR requests issued through libusb (with no kernel driver bound) complete correctly and blazingly fast. So it seems to only happen during the initial probe. I defined an explicit composite quirk to bypass the mixer creation, as it is not needed (the synth already comes with volume controls). With both this and the device flag applied, the synth works flawlessly. Signed-off-by: Federico Valentín Andrade Link: https://patch.msgid.link/20260824140211.17003-3-fandrade@frba.utn.edu.ar Signed-off-by: Takashi Iwai --- sound/usb/quirks-table.h | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index 0a3d39b8385b..dd02c6294edf 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -1814,6 +1814,28 @@ YAMAHA_DEVICE(0x7010, "UB99"), } } }, +{ + /* + * M-Audio Venom + * + * The AudioControl interface times out on every GET_CUR request, + * which adds around 47 seconds to the card registration and + * freezes the device, blocking streaming. + * Using an explicit composite quirk to skip the mixer entirely. + */ + USB_DEVICE_VENDOR_SPEC(0x0763, 0x2084), + QUIRK_DRIVER_INFO { + .vendor_name = "M-Audio", + .product_name = "Venom", + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_IGNORE(0) }, + { QUIRK_DATA_STANDARD_AUDIO(1) }, + { QUIRK_DATA_STANDARD_AUDIO(2) }, + { QUIRK_DATA_STANDARD_MIDI(3) }, + QUIRK_COMPOSITE_END + } + } +}, /* Casio devices */ { From 15fd93cbbff7d9d92cd19f38a94355462fa91930 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Mon, 24 Aug 2026 17:07:30 +0700 Subject: [PATCH 1136/1328] ALSA: aoa: i2sbus: Check IRQ before requesting it irq_of_parse_and_map() returns 0 when parsing or mapping an IRQ fails. The return value was passed to request_irq() without checking for failure. Check the return value before passing it to request_irq(). Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260824100730.31601-1-phucduc.bui@gmail.com Signed-off-by: Takashi Iwai --- sound/aoa/soundbus/i2sbus/core.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/aoa/soundbus/i2sbus/core.c b/sound/aoa/soundbus/i2sbus/core.c index 833c44c0a950..adf123dd79a2 100644 --- a/sound/aoa/soundbus/i2sbus/core.c +++ b/sound/aoa/soundbus/i2sbus/core.c @@ -240,6 +240,8 @@ static int i2sbus_add_dev(struct macio_dev *macio, } for (i = aoa_resource_i2smmio; i <= aoa_resource_rxdbdma; i++) { int irq = irq_of_parse_and_map(np, i); + if (!irq) + goto err; if (request_irq(irq, ints[i], 0, dev->rnames[i], dev)) goto err; dev->interrupts[i] = irq; From be79a45e2ec289eb9fad519e8f5a3d4fc3789dd0 Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Mon, 24 Aug 2026 20:37:52 +0800 Subject: [PATCH 1137/1328] ALSA: hda/realtek: Add quirk for Lenovo IdeaPad Slim 3 15ABR8 The internal microphone on Lenovo IdeaPad Slim 3 15ABR8 (subsystem 17aa:3862) exhibits severe distortion at default gain levels. The Capture Volume is fixed at 63 (max) and cannot be lowered without explicit userspace intervention. The existing fixup ALC269_FIXUP_LIMIT_INT_MIC_BOOST limits the gain range and resolves the issue. Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221891 Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20260824123752.525173-1-zhangheng@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index b08d25fb0e17..f230a2be3914 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -8086,6 +8086,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x3852, "Lenovo Yoga 7 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x3853, "Lenovo Yoga 7 15ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x3855, "Legion 7 16ITHG6", ALC287_FIXUP_LEGION_16ITHG6), + SND_PCI_QUIRK(0x17aa, 0x3862, "Lenovo IdeaPad Slim 3 15ABR8", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), SND_PCI_QUIRK(0x17aa, 0x3865, "Lenovo 13X", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x3866, "Lenovo 13X", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x3869, "Lenovo Yoga7 14IAL7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), From 1739a976312e110c93a8dee66a1cdf893a1b187e Mon Sep 17 00:00:00 2001 From: Will Porter Date: Mon, 24 Aug 2026 17:57:57 -0500 Subject: [PATCH 1138/1328] ALSA: usb-audio: Complete cleanup after system-resume errors A failed system resume can leave the card unusable until reboot. usb_audio_resume() jumps to err_out when snd_usb_pcm_resume() or snd_usb_mixer_resume() fails. The error path skips the out: block, which restores D0 and decrements chip->num_suspended_intf. The card stays in SNDRV_CTL_POWER_D3hot, so later control access blocks in snd_power_ref_and_wait(). USB core logs an interface resume callback error. It does not retry that callback, so a later callback cannot complete the skipped cleanup. usb_audio_suspend() increments num_suspended_intf before returning success. A system-resume callback must consume the system-suspend count even if a component resume fails. Otherwise, the stranded count skews later suspend and resume cycles. Do not apply this cleanup to runtime-resume errors. Runtime PM can retry -EAGAIN or -EBUSY without another suspend callback. The count must continue to describe that suspended interface. Other runtime-resume errors latch runtime_error in the PM core and do not cause an immediate callback retry. Both parts of the system-resume error path are longstanding. Commit 88a8516a2128a ("ALSA: usbaudio: implement USB autosuspend") introduced err_out past the D0 restore. Commit 862b2509d157c ("ALSA: usb-audio: Fix inconsistent card PM state after resume") later moved num_suspended_intf-- into the out: block. The error path now skips both operations. No third-party code is needed to reach the error path. snd_usb_mixer_resume() ends in snd_usb_mixer_activate(), which returns the result of usb_submit_urb() for devices that have a mixer status URB. Its mixer->private_resume hook can also fail through scarlett2_init_notify(). snd_usb_pcm_resume() issues a SET_CUR request to a UAC3 power domain. It can return -EPIPE or -EIO when the device stalls the request. Route a component error through out: only when system_suspend is nonzero. Continue to return runtime-resume errors through err_out. Later component resume stages remain skipped. The original error still reaches USB core. A later transfer can fail if the device did not recover. I reproduced the system-resume failure on an Audient iD14 MkI with an out-of-tree diagnostic mixer resume hook. An injected -EIO on the unpatched core left control readers in uninterruptible sleep in snd_power_ref_and_wait() until a reboot. With this patch, the same failure restored control access. A second system suspend and resume also succeeded after I disabled fault injection. Assisted-by: Claude:claude-opus-5 Assisted-by: Antigravity:gemini-3.1-pro-high Assisted-by: Codex:gpt-5.6-sol Fixes: 88a8516a2128a ("ALSA: usbaudio: implement USB autosuspend") Fixes: 862b2509d157c ("ALSA: usb-audio: Fix inconsistent card PM state after resume") Cc: Signed-off-by: Will Porter Link: https://patch.msgid.link/20260824225757.26749-1-mrwillporter@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/card.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/sound/usb/card.c b/sound/usb/card.c index 24112e491779..9307da95efbe 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -1282,8 +1282,11 @@ static int usb_audio_resume(struct usb_interface *intf) list_for_each_entry(as, &chip->pcm_list, list) { err = snd_usb_pcm_resume(as); - if (err < 0) - goto err_out; + if (err < 0) { + if (!chip->system_suspend) + goto err_out; + goto out; + } } /* @@ -1292,8 +1295,11 @@ static int usb_audio_resume(struct usb_interface *intf) */ list_for_each_entry(mixer, &chip->mixer_list, list) { err = snd_usb_mixer_resume(mixer); - if (err < 0) - goto err_out; + if (err < 0) { + if (!chip->system_suspend) + goto err_out; + goto out; + } } list_for_each(p, &chip->midi_list) { From b17cf742eaad70ae29ac558cefb3aa9bbeea03d4 Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Sun, 23 Aug 2026 16:47:56 +0800 Subject: [PATCH 1139/1328] tls: device: fix out-of-bounds write in tls_append_frag() Found with syzkaller and a local syzbot instance running on top of a netdevsim TLS offload emulation; tls_device.c is otherwise only reachable on a machine with a NIC that implements the offload. tls_push_data() only checks whether the open record still has room for another frag at the bottom of its loop, and the MSG_MORE early break skips that check. The record survives to the next syscall with the frag count it already had, and tls_append_frag() does not check either, so with TLS_TX_ZEROCOPY_RO every splice(SPLICE_F_MORE) of a byte or two adds a non-coalescing pipe page and num_frags walks off the end of tls_record_info.frags[MAX_SKB_FRAGS]. Once the record is pushed, tls_push_record() runs the same index over sg_tx_data[MAX_SKB_FRAGS] and the sg_set_page() writes land on the destruct_work that follows it, which the workqueue then calls. The byte limit is fine because copy drops to 0 and the loop falls through to the same check; the frag count has no such feedback. Push the record rather than keep a full one open, which is what a plain TCP socket does - tcp_sendmsg_locked() uses tcp_mark_push() and new_segment in both the copy and the MSG_SPLICE_PAGES paths, and tls_sw already sets full_record when the sk_msg ring fills up, MSG_MORE or not. BUG: KASAN: slab-out-of-bounds in tls_append_frag ( net/tls/tls_device.c:269) Write of size 8 at addr ffff8881104d1530 by task tls_oob/450 CPU: 2 UID: 0 PID: 450 Comm: tls_oob Not tainted 7.2.0-rc7+ #329 PREEMPT Call Trace: dump_stack_lvl (lib/dump_stack.c:94 lib/dump_stack.c:120) print_report (mm/kasan/report.c:378 mm/kasan/report.c:482) kasan_report (mm/kasan/report.c:595) tls_append_frag (net/tls/tls_device.c:269) tls_push_data (net/tls/tls_device.c:518) tls_device_sendmsg (net/tls/tls_device.c:583) inet_sendmsg (net/ipv4/af_inet.c:865) sock_sendmsg (net/socket.c:775 net/socket.c:790 net/socket.c:813) splice_to_socket (fs/splice.c:884) do_splice (fs/splice.c:936 fs/splice.c:1349) __do_splice (fs/splice.c:1431) __x64_sys_splice (fs/splice.c:1634 fs/splice.c:1616) do_syscall_64 (arch/x86/entry/syscall_64.c:63 arch/x86/entry/syscall_64.c:94) entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121) and, once the record is pushed: UBSAN: array-index-out-of-bounds in net/tls/tls_device.c:300:24 index 18 is out of range for type 'skb_frag_t [17]' UBSAN: array-index-out-of-bounds in net/tls/tls_device.c:301:41 index 18 is out of range for type 'scatterlist [17]' UBSAN: array-index-out-of-bounds in net/tls/tls_device.c:302:39 index 18 is out of range for type 'scatterlist [17]' UBSAN: array-index-out-of-bounds in net/tls/tls_device.c:307:38 index 26 is out of range for type 'scatterlist [17]' kernel tried to execute NX-protected page - exploit attempt? (uid: 0) BUG: unable to handle page fault for address: ffffea000411a680 #PF: supervisor instruction fetch in kernel mode #PF: error_code(0x0011) - permissions violation Oops: Oops: 0011 [#1] SMP KASAN PTI Workqueue: ktls_device_destruct 0xffffea000411a680 RIP: 0010:0xffffea000411a680 Call Trace: worker_thread (kernel/workqueue.c:3405 kernel/workqueue.c:3486) kthread (kernel/kthread.c:436) ret_from_fork (arch/x86/kernel/process.c:158) ret_from_fork_asm (arch/x86/entry/entry_64.S:245) Fixes: e8f69799810c ("net/tls: Add generic NIC offload infrastructure") Cc: stable@vger.kernel.org Signed-off-by: Jiayuan Chen Link: https://patch.msgid.link/20260823084758.20936-1-jiayuan.chen@linux.dev Signed-off-by: Paolo Abeni --- net/tls/tls_device.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/tls/tls_device.c b/net/tls/tls_device.c index 37bb06a8e8f5..f11d0528fc43 100644 --- a/net/tls/tls_device.c +++ b/net/tls/tls_device.c @@ -531,7 +531,8 @@ static int tls_push_data(struct sock *sk, if (!size) { last_record: tls_push_record_flags = flags; - if (flags & MSG_MORE) { + if ((flags & MSG_MORE) && + record->num_frags < MAX_SKB_FRAGS - 1) { more = true; break; } From 81d0d1e64f30d9989c829c0953cd6e6c68d9c5fb Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Sun, 23 Aug 2026 18:22:41 +0000 Subject: [PATCH 1140/1328] net/sched: act_skbmod: fix length calculations and avoid invalid header warnings syzbot reported a warning in skb_network_header_len() triggered by tcf_skbmod_act(): !skb_transport_header_was_set(skb) WARNING: CPU: 0 PID: 14949 at include/linux/skbuff.h:3243 skb_network_header_len include/linux/skbuff.h:3243 [inline] WARNING: CPU: 0 PID: 14949 at net/sched/act_skbmod.c:55 tcf_skbmod_act+0xfe8/0x1810 net/sched/act_skbmod.c:55 There are a few issues in tcf_skbmod_act(): 1. Calling skb_network_header_len() assumes skb->transport_header is set, which is not guaranteed when tcf_skbmod_act() runs at TC ingress. 2. Unconditionally calling skb_mac_header_len() at the beginning of tcf_skbmod_act() triggers a warning on L3 devices (e.g. TUN) where the MAC header is unset, evaluating to an underflowed garbage length. 3. On TC ingress, skb->data points to the network header. Adding the MAC header length to the IP header length causes skb_ensure_writable() to request more bytes than the actual IP packet length, dropping valid short packets (e.g. 28-byte UDP/IPv4 packets). Fix these by: - Using skb_network_offset(skb) + sizeof(struct iphdr/ipv6hdr) for SKBMOD_F_ECN so that the required length is correctly calculated on both ingress (offset == 0) and egress (offset == mac_len). - Setting max_edit_len to ETH_HLEN for Ethernet header modifications after validating ARPHRD_ETHER. Fixes: 56af5e749f20 ("net/sched: act_skbmod: Add SKBMOD_F_ECN option support") Reported-by: syzbot+1d56f14f95c0480cfdc9@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/6a8b39c0.dbb3a75c.13dd47.0051.GAE@google.com/T/#u Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260823182241.1958695-1-edumazet@google.com Signed-off-by: Paolo Abeni --- net/sched/act_skbmod.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/net/sched/act_skbmod.c b/net/sched/act_skbmod.c index a464b0a3c1b8..7579cf1e0ff3 100644 --- a/net/sched/act_skbmod.c +++ b/net/sched/act_skbmod.c @@ -38,7 +38,6 @@ TC_INDIRECT_SCOPE int tcf_skbmod_act(struct sk_buff *skb, if (unlikely(p->action == TC_ACT_SHOT)) goto drop; - max_edit_len = skb_mac_header_len(skb); flags = p->flags; /* tcf_skbmod_init() guarantees "flags" to be one of the following: @@ -51,14 +50,19 @@ TC_INDIRECT_SCOPE int tcf_skbmod_act(struct sk_buff *skb, if (flags == SKBMOD_F_ECN) { switch (skb_protocol(skb, true)) { case cpu_to_be16(ETH_P_IP): + max_edit_len = sizeof(struct iphdr); + break; case cpu_to_be16(ETH_P_IPV6): - max_edit_len += skb_network_header_len(skb); + max_edit_len = sizeof(struct ipv6hdr); break; default: goto out; } - } else if (!skb->dev || skb->dev->type != ARPHRD_ETHER) { - goto out; + max_edit_len += skb_network_offset(skb); + } else { + if (!skb->dev || skb->dev->type != ARPHRD_ETHER) + goto out; + max_edit_len = ETH_HLEN; } err = skb_ensure_writable(skb, max_edit_len); From 2db9bfa3e27bdea15e05ea70b56bad3d21e570ec Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Mon, 24 Aug 2026 01:28:58 +0800 Subject: [PATCH 1141/1328] sctp: fix NULL deref on untransmitted RECONF completion sctp_process_strreset_outreq(), sctp_process_strreset_addstrm_out() and sctp_process_strreset_resp() complete a pending stream reconfiguration request by stopping the reconf timer on the transport it was sent on: t = asoc->strreset_chunk->transport; if (timer_delete(&t->reconf_timer)) sctp_transport_put(t); chunk->transport is assigned by __sctp_packet_append_chunk() when the chunk is appended to an outbound packet, and sctp_outq_flush_ctrl() arms the reconf timer at that same point. A request already published in asoc->strreset_chunk but not yet transmitted has neither, so completing it dereferences NULL. Two ways to get there. sctp_send_asconf_del_ip() sets asoc->src_out_of_asoc_ok without sending anything when the address being removed is the association's last one, and sctp_outq_flush_ctrl() then leaves every non-ASCONF control chunk queued; as only sctp_process_asconf_ack() clears that flag, it persists. An unprivileged process that removes such an address and then asks for a stream reset panics the kernel from softirq. A peer needs neither ASCONF nor local help: sctp_cmd_interpreter() uncorks the outqueue only once the whole packet has been processed, so a reply built while walking a RECONF chunk stays untransmitted for the rest of that walk, and one RECONF chunk carrying [Incoming SSN Reset Request, Outgoing SSN Reset Request, Response] -- or two RECONF chunks in one packet -- reaches the same dereference. KASAN: null-ptr-deref in range [0x00000000000001e8-0x00000000000001ef] RIP: 0010:timer_delete+0x67/0x110 Call Trace: sctp_process_strreset_addstrm_out (net/sctp/stream.c:832) sctp_sf_do_reconf (net/sctp/sm_statefuns.c:4212) sctp_do_sm (net/sctp/sm_sideeffect.c:1172) sctp_assoc_bh_rcv (net/sctp/associola.c:1044) sctp_rcv (net/sctp/input.c:243) ip_local_deliver (net/ipv4/ip_input.c:262) process_backlog (net/core/dev.c:6680) A response can only acknowledge a request that was actually sent, so do not match asoc->strreset_chunk while chunk->transport is NULL. Guarding the lookup covers all three completion sites. Fixes: 810544764536 ("sctp: implement receiver-side procedures for the Outgoing SSN Reset Request Parameter") Cc: stable@vger.kernel.org Reported-by: Xiang Mei Suggested-by: Xin Long Assisted-by: Claude:claude-opus-5 Signed-off-by: Weiming Shi Acked-by: Xin Long Link: https://patch.msgid.link/20260823172857.896146-2-bestswngs@gmail.com Signed-off-by: Paolo Abeni --- net/sctp/stream.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/sctp/stream.c b/net/sctp/stream.c index 34ffe6c945a4..2012f61e250e 100644 --- a/net/sctp/stream.c +++ b/net/sctp/stream.c @@ -488,7 +488,7 @@ static struct sctp_paramhdr *sctp_chunk_lookup_strreset_param( struct sctp_reconf_chunk *hdr; union sctp_params param; - if (!chunk) + if (!chunk || !chunk->transport) return NULL; hdr = (struct sctp_reconf_chunk *)chunk->chunk_hdr; From 00e11ee9831b3439264e0ae6762a0470126515af Mon Sep 17 00:00:00 2001 From: Mina Almasry Date: Sun, 23 Aug 2026 18:36:01 +0000 Subject: [PATCH 1142/1328] net: core: check skb_frags_readable before uncloning in skb_copy_ubufs skb_copy_ubufs drops clones and modifies the SKB via pskb_expand_head() before checking for !skb_frags_readable(skb). This alters the SKB geometry prior to throwing an -EFAULT on an invalid SKB. Check readability first. Fixes: 65249feb6b3d ("net: add support for skbs with unreadable frags") Signed-off-by: Mina Almasry Link: https://patch.msgid.link/20260823183602.1051453-1-almasrymina@google.com Signed-off-by: Paolo Abeni --- net/core/skbuff.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/net/core/skbuff.c b/net/core/skbuff.c index b9541329f1a7..d2583fe94001 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -2004,12 +2004,12 @@ int skb_copy_ubufs(struct sk_buff *skb, gfp_t gfp_mask) int i, order, psize, new_frags; u32 d_off; - if (skb_shared(skb) || skb_unclone(skb, gfp_mask)) - return -EINVAL; - if (!skb_frags_readable(skb)) return -EFAULT; + if (skb_shared(skb) || skb_unclone(skb, gfp_mask)) + return -EINVAL; + if (!num_frags) goto release; From 97148bcb751105cd7cf86a21344887028f329890 Mon Sep 17 00:00:00 2001 From: Mina Almasry Date: Sun, 23 Aug 2026 18:36:02 +0000 Subject: [PATCH 1143/1328] net: core: fix head-page leak in skb_zerocopy When skb_orphan_frags() throws -ENOMEM, skb_copy_ubufs() may have already reallocated and replaced 'from->head'. Accessing from->head to drop the old refcount leaks the original head page, and erroneously puts an unrelated new buffer. Use the local 'page' tracker variable instead to drop the reference properly. Fixes: 36d5fe6a0007 ("core, nfqueue, openvswitch: Orphan frags in skb_zerocopy and handle errors") Signed-off-by: Mina Almasry Link: https://patch.msgid.link/20260823183602.1051453-2-almasrymina@google.com Signed-off-by: Paolo Abeni --- net/core/skbuff.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/core/skbuff.c b/net/core/skbuff.c index d2583fe94001..cbbd60455abb 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -3910,7 +3910,7 @@ skb_zerocopy(struct sk_buff *to, struct sk_buff *from, int len, int hlen) } if (!skb_frags_readable(from) && j > 0 && len) { - put_page(virt_to_head_page(from->head)); + put_page(page); return -EFAULT; } @@ -3918,7 +3918,7 @@ skb_zerocopy(struct sk_buff *to, struct sk_buff *from, int len, int hlen) if (unlikely(skb_orphan_frags(from, GFP_ATOMIC))) { if (j > 0) - put_page(virt_to_head_page(from->head)); + put_page(page); return -ENOMEM; } skb_zerocopy_clone(to, from, GFP_ATOMIC); From 3220b62fbb8a55feebd2a826d5ead0f49f09ed5a Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Sat, 22 Aug 2026 17:18:31 +0900 Subject: [PATCH 1144/1328] net: fix a resource leak in copy_net_ns() error handling path Currently, preinit_net() does two things: (1) call ns_common_init() which might fail (2) initialize resources which does not fail However, preinit_net() is returning early when (1) fails, and copy_net_ns() is jumping to the dec_ucounts: label. As a result, resources allocated by net_alloc() are leaking. We need to call key_remove_domain() and net_passive_dec() in order to release resources allocated by net_alloc(). We cannot simply jump to the put_userns: label when preinit_net() failed, for (2) is not yet done. But we can reorder (1) and (2), for there is no dependency between (1) and (2). Therefore, this patch decouples (1) from preinit_net() and changes preinit_net() back to a void function, and calls ns_common_init() after preinit_net() succeeded. Then, we can jump to immediately after ns_common_free() of the put_userns: label. Reported-by: sashiko (no mail address) Closes: https://sashiko.dev/#/patchset/af7dabf3-d0d7-46dc-a878-e1715b3c9ac6%40I-love.SAKURA.ne.jp Fixes: 08027f6b790b ("net: use ns_common_init()") Signed-off-by: Tetsuo Handa Link: https://patch.msgid.link/c182cf90-1ed7-435b-88f7-9f00e88a0487@I-love.SAKURA.ne.jp Signed-off-by: Paolo Abeni --- net/core/net_namespace.c | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c index 9166f467293e..da5f881fbd3b 100644 --- a/net/core/net_namespace.c +++ b/net/core/net_namespace.c @@ -400,14 +400,8 @@ static __net_init void preinit_net_sysctl(struct net *net) } /* init code that must occur even if setup_net() is not called. */ -static __net_init int preinit_net(struct net *net, struct user_namespace *user_ns) +static __net_init void preinit_net(struct net *net, struct user_namespace *user_ns) { - int ret; - - ret = ns_common_init(net); - if (ret) - return ret; - refcount_set(&net->passive, 1); ref_tracker_dir_init(&net->refcnt_tracker, 128, "net_refcnt"); ref_tracker_dir_init(&net->notrefcnt_tracker, 128, "net_notrefcnt"); @@ -431,7 +425,6 @@ static __net_init int preinit_net(struct net *net, struct user_namespace *user_n INIT_LIST_HEAD(&net->ptype_all); INIT_LIST_HEAD(&net->ptype_specific); preinit_net_sysctl(net); - return 0; } /* @@ -574,12 +567,14 @@ struct net *copy_net_ns(u64 flags, goto dec_ucounts; } - rv = preinit_net(net, user_ns); - if (rv < 0) - goto dec_ucounts; + preinit_net(net, user_ns); net->ucounts = ucounts; get_user_ns(user_ns); + rv = ns_common_init(net); + if (rv) + goto put_userns_no_common; + rv = down_read_killable(&pernet_ops_rwsem); if (rv < 0) goto put_userns; @@ -591,6 +586,7 @@ struct net *copy_net_ns(u64 flags, if (rv < 0) { put_userns: ns_common_free(net); +put_userns_no_common: #ifdef CONFIG_KEYS key_remove_domain(net->key_domain); #endif @@ -1293,7 +1289,8 @@ void __init net_ns_init(void) * This currently cannot fail as the initial network namespace * has a static inode number. */ - if (preinit_net(&init_net, &init_user_ns)) + preinit_net(&init_net, &init_user_ns); + if (ns_common_init(&init_net)) panic("Could not preinitialize the initial network namespace"); down_write(&pernet_ops_rwsem); From 728836ebca239810f164262b10211ef59182f811 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Sun, 23 Aug 2026 00:45:56 +0800 Subject: [PATCH 1145/1328] vsock/virtio: flush works in dependency order virtio_vsock_remove() stops the virtqueues and then flushes each work item before freeing the enclosing virtio_vsock. The current order does not account for dependencies between those items: tx_work may queue send_pkt_work, and send_pkt_work may queue rx_work. In particular, send_pkt_work can set restart_rx and release tx_lock. The remove path can then stop the queues and flush rx_work before send_pkt_work queues it. Although the later send_pkt_work flush waits for that producer to finish, nothing waits for the newly queued rx_work, so kfree(vsock) can race with it. KASAN reported: BUG: KASAN: slab-use-after-free in virtio_transport_rx_work+0x487/0x4b0 Read of size 8 at addr ffff888114c2b008 by task kworker/1:1/47 Workqueue: virtio_vsock virtio_transport_rx_work Call Trace: virtio_transport_rx_work+0x487/0x4b0 process_one_work+0x688/0x1120 worker_thread+0x45b/0xd10 Allocated by task 1: virtio_vsock_probe+0xef/0x6b0 Freed by task 84: kfree+0x131/0x3c0 virtio_vsock_remove+0xd1/0x100 Flush the works in producer-to-consumer order. virtio_vsock_vqs_del() has already disabled the queue callbacks and cleared the run flags, so after tx_work and send_pkt_work are drained, no source remains that can queue rx_work after its flush. Fixes: 0ea9e1d3a9e3 ("VSOCK: Introduce virtio_transport.ko") Cc: stable@vger.kernel.org Signed-off-by: Chengfeng Ye Link: https://patch.msgid.link/20260822164556.3750959-1-nicoyip.dev@gmail.com Signed-off-by: Paolo Abeni --- net/vmw_vsock/virtio_transport.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/vmw_vsock/virtio_transport.c b/net/vmw_vsock/virtio_transport.c index 96c9fe8d357c..4f9aa9c4c3aa 100644 --- a/net/vmw_vsock/virtio_transport.c +++ b/net/vmw_vsock/virtio_transport.c @@ -872,10 +872,10 @@ static void virtio_vsock_remove(struct virtio_device *vdev) /* Other works can be queued before 'config->del_vqs()', so we flush * all works before to free the vsock object to avoid use after free. */ - flush_work(&vsock->rx_work); flush_work(&vsock->tx_work); flush_work(&vsock->event_work); flush_work(&vsock->send_pkt_work); + flush_work(&vsock->rx_work); mutex_unlock(&the_virtio_vsock_mutex); From 709f34f7c28dc4dd6c40343d101850f11e172312 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Sat, 22 Aug 2026 15:55:04 -0400 Subject: [PATCH 1146/1328] net/sched: fq: add overflow bounds to quantum and initial quantum fq_init() computes quantum = 2 * psched_mtu() and initial_quantum = 10 * psched_mtu() with no overflow check. A device with a huge MTU (e.g. dummy with max_mtu == 0 accepting MTU 2147483634) makes psched_mtu() return 0x80000000; the 2 * and 10 * multiplications wrap to 0 in 32-bit arithmetic, so q->quantum == 0. Then in fq_dequeue() the credit-refill loop adds 0 to f->credit (which stays <= 0) and goto begin loops forever under the qdisc lock, creating a soft lockup. Clamp psched_mtu() to [1, 1 << 20] before multiplying so the product cannot wrap, then cap the result at 1 << 20, matching the bound already enforced on TCA_FQ_QUANTUM in fq_change(). Conditions to recreate the bug: a device whose MTU (plus hard_header_len) is large enough that 2 * psched_mtu() wraps (e.g. a dummy device with max_mtu == 0 accepting MTU 2147483634). Requires CAP_NET_ADMIN in a user namespace. Fixes: afe4fd062416 ("pkt_sched: fq: Fair Queue packet scheduler") Reported-by: vega@nebusec.ai Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/20260822195509.112717-2-jhs@mojatatu.com Signed-off-by: Paolo Abeni --- net/sched/sch_fq.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/net/sched/sch_fq.c b/net/sched/sch_fq.c index 4b5f6d896c6d..6144b5686f13 100644 --- a/net/sched/sch_fq.c +++ b/net/sched/sch_fq.c @@ -1226,12 +1226,14 @@ static int fq_init(struct Qdisc *sch, struct nlattr *opt, struct netlink_ext_ack *extack) { struct fq_sched_data *q = qdisc_priv(sch); + u32 mtu; int i, err; sch->limit = 10000; q->flow_plimit = 100; - q->quantum = 2 * psched_mtu(qdisc_dev(sch)); - q->initial_quantum = 10 * psched_mtu(qdisc_dev(sch)); + mtu = clamp_t(u32, psched_mtu(qdisc_dev(sch)), 1, 1 << 20); + q->quantum = min_t(u32, 2 * mtu, 1 << 20); + q->initial_quantum = min_t(u32, 10 * mtu, 1 << 20); q->flow_refill_delay = msecs_to_jiffies(40); q->flow_max_rate = ~0UL; q->time_next_delayed_flow = ~0ULL; From d9ebd8f9aa8b2773235889cb903fafd61f2d8585 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Sat, 22 Aug 2026 15:55:05 -0400 Subject: [PATCH 1147/1328] net/sched: fq_codel: clamp default quantum and mtu fq_codel_init() sets q->quantum = psched_mtu(qdisc_dev(sch)) without clamping. A device with a huge MTU (e.g. dummy with max_mtu == 0 accepting MTU 2147483634) makes psched_mtu() return 0x80000000, which overflows the signed flow->deficit to INT_MIN in fq_codel_dequeue(), causing an infinite loop and soft lockup. Emulate fq_codel_change() and constrain to [256, FQ_CODEL_QUANTUM_MAX]. The same unclamped psched_mtu() is assigned to q->cparams.mtu a bit below, and fq_codel_change() never updates it. codel_should_drop() tests "*backlog <= params->mtu"; with mtu == 0x80000000 (~2 GiB) and the default 32 MiB memory_limit, the test is always true, so CoDel is silently and completely disabled (no drops, no ECN). Declare a single clamped mtu and assign both q->quantum and q->cparams.mtu from it, which also removes the double psched_mtu() call. Conditions to recreate the bug: a device whose MTU (plus hard_header_len) wraps psched_mtu() into the sign bit (e.g. a dummy device with max_mtu == 0 accepting MTU 2147483634). Requires CAP_NET_ADMIN in a user namespace. Fixes: 4b549a2ef4be ("fq_codel: Fair Queue Codel AQM") Reported-by: vega@nebusec.ai Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/20260822195509.112717-3-jhs@mojatatu.com Signed-off-by: Paolo Abeni --- net/sched/sch_fq_codel.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/net/sched/sch_fq_codel.c b/net/sched/sch_fq_codel.c index 6cce86ba383c..969b2510b0b8 100644 --- a/net/sched/sch_fq_codel.c +++ b/net/sched/sch_fq_codel.c @@ -509,6 +509,7 @@ static int fq_codel_init(struct Qdisc *sch, struct nlattr *opt, struct netlink_ext_ack *extack) { struct fq_codel_sched_data *q = qdisc_priv(sch); + u32 mtu; int i; int err; @@ -516,13 +517,14 @@ static int fq_codel_init(struct Qdisc *sch, struct nlattr *opt, q->flows_cnt = 1024; q->memory_limit = 32 << 20; /* 32 MBytes */ q->drop_batch_size = 64; - q->quantum = psched_mtu(qdisc_dev(sch)); + mtu = clamp_t(u32, psched_mtu(qdisc_dev(sch)), 256, FQ_CODEL_QUANTUM_MAX); + q->quantum = mtu; INIT_LIST_HEAD(&q->new_flows); INIT_LIST_HEAD(&q->old_flows); codel_params_init(&q->cparams); codel_stats_init(&q->cstats); q->cparams.ecn = true; - q->cparams.mtu = psched_mtu(qdisc_dev(sch)); + q->cparams.mtu = mtu; if (opt) { err = fq_codel_change(sch, opt, extack); From 6439461f1618ae176c048673ad28bdb6c68efbfc Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Sat, 22 Aug 2026 15:55:06 -0400 Subject: [PATCH 1148/1328] net/sched: sch_codel: clamp default mtu to avoid disabling CoDel codel_init() sets q->params.mtu = psched_mtu(qdisc_dev(sch)) without clamping. A device with a huge MTU (e.g. dummy with max_mtu == 0 accepting MTU 2147483634) makes psched_mtu() return 0x80000000. In codel_should_drop() the test "*backlog <= params->mtu" then compares the backlog against ~2 GiB; with the default sch->limit of DEFAULT_CODEL_LIMIT (1000) packets the backlog can never reach it, so the test is always true and CoDel is silently and completely disabled i.e no drops, no ECN marking, codel degrades to a tail-drop FIFO. codel_change() never updates params.mtu, so the init path is the only place to clamp it. Constrain to [256, 1 << 20], matching the fq_codel bound; 256 is a sane floor that only makes CoDel slightly more willing to act on very small queues, which is the safe direction. Conditions to recreate the bug: a device whose MTU (plus hard_header_len) wraps psched_mtu() into the sign bit (e.g. a dummy device with max_mtu == 0 accepting MTU 2147483634). Requires CAP_NET_ADMIN in a user namespace. Fixes: 76e3cc126bb2 ("codel: Controlled Delay AQM") Reported-by: vega@nebusec.ai Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/20260822195509.112717-4-jhs@mojatatu.com Signed-off-by: Paolo Abeni --- net/sched/sch_codel.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/sched/sch_codel.c b/net/sched/sch_codel.c index cacf5244958e..6aa5829d6961 100644 --- a/net/sched/sch_codel.c +++ b/net/sched/sch_codel.c @@ -205,7 +205,7 @@ static int codel_init(struct Qdisc *sch, struct nlattr *opt, codel_params_init(&q->params); codel_vars_init(&q->vars); codel_stats_init(&q->stats); - q->params.mtu = psched_mtu(qdisc_dev(sch)); + q->params.mtu = clamp_t(u32, psched_mtu(qdisc_dev(sch)), 256, 1 << 20); if (opt) { int err = codel_change(sch, opt, extack); From c86cd7ed0b0e44779a3d1683f03e4353baf4bdc9 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Sat, 22 Aug 2026 15:55:07 -0400 Subject: [PATCH 1149/1328] net/sched: fq_pie: clamp default quantum to avoid signed overflow fq_pie_init() sets q->quantum = psched_mtu(qdisc_dev(sch)) without clamping. A device with a huge MTU (e.g. dummy with max_mtu == 0 accepting MTU 2147483634) makes psched_mtu() return 0x80000000, which overflows the signed flow->deficit to INT_MIN in fq_pie_qdisc_dequeue(), causing an infinite loop and soft lockup. Emulate fq_pie_policy which is already bounded to [1, 1 << 20]; clamp the default to [256, 1 << 20]. 256 matches fq_codel's floor and is a sane minimum for a DRR quantum. Conditions to recreate the bug: a device whose MTU (plus hard_header_len) wraps psched_mtu() into the sign bit (e.g. a dummy device with max_mtu == 0 accepting MTU 2147483634). Requires CAP_NET_ADMIN in a user namespace. Fixes: ec97ecf1ebe4 ("net: sched: add Flow Queue PIE packet scheduler") Reported-by: vega@nebusec.ai Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/20260822195509.112717-5-jhs@mojatatu.com Signed-off-by: Paolo Abeni --- net/sched/sch_fq_pie.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/sched/sch_fq_pie.c b/net/sched/sch_fq_pie.c index 069e1facd413..b27d95418707 100644 --- a/net/sched/sch_fq_pie.c +++ b/net/sched/sch_fq_pie.c @@ -427,7 +427,8 @@ static int fq_pie_init(struct Qdisc *sch, struct nlattr *opt, pie_params_init(&q->p_params); sch->limit = 10 * 1024; q->p_params.limit = sch->limit; - q->quantum = psched_mtu(qdisc_dev(sch)); + q->quantum = clamp_t(u32, psched_mtu(qdisc_dev(sch)), + 256, 1 << 20); q->sch = sch; q->ecn_prob = 10; q->flows_cnt = 1024; From 2164b512b97bb053e8ce4d6e95576f11bed6a005 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Sat, 22 Aug 2026 15:55:08 -0400 Subject: [PATCH 1150/1328] net/sched: hhf: clamp quantum before hhf_change() to avoid overflow hhf_init() sets q->quantum = psched_mtu(qdisc_dev(sch)) with no overflow check. A device with a huge MTU (e.g. dummy with max_mtu == 0 accepting MTU 2147483634) makes weight * quantum overflow the signed deficit in hhf_dequeue(), spinning forever. Clamp q->quantum before hhf_change() so both the opt and !opt paths see a sane quantum. Without this, bare "tc qdisc add ... hhf" succeeds with a clamped quantum but "tc qdisc add ... hhf limit 1000" (any option present) fails with -EINVAL because hhf_change() re-validates the unclamped default (sch_hhf.c:559). 256 matches fq_codel's floor and is a sane minimum for a DRR quantum. Conditions to recreate the bug: a device whose MTU (plus hard_header_len) wraps psched_mtu() into the sign bit (e.g. a dummy device with max_mtu == 0 accepting MTU 2147483634). Requires CAP_NET_ADMIN in a user namespace. Fixes: 10239edf86f1 ("net-qdisc-hhf: Heavy-Hitter Filter (HHF) qdisc") Reported-by: vega@nebusec.ai Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/20260822195509.112717-6-jhs@mojatatu.com Signed-off-by: Paolo Abeni --- net/sched/sch_hhf.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/sched/sch_hhf.c b/net/sched/sch_hhf.c index d85cb0263b67..96acab6a8da0 100644 --- a/net/sched/sch_hhf.c +++ b/net/sched/sch_hhf.c @@ -624,6 +624,10 @@ static int hhf_init(struct Qdisc *sch, struct nlattr *opt, q->hhf_evict_timeout = HZ; /* 1 sec */ q->hhf_non_hh_weight = 2; + if ((int)q->quantum <= 0 || + (u64)q->quantum * q->hhf_non_hh_weight > INT_MAX) + q->quantum = 256; + if (opt) { int err = hhf_change(sch, opt, extack); From 816e90057ab1879562a5b7cc688e35bb9027ae97 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Sat, 22 Aug 2026 15:55:09 -0400 Subject: [PATCH 1151/1328] net/sched: sfq: clamp quantum to avoid signed overflow soft lockup sfq_init() sets q->quantum = psched_mtu(qdisc_dev(sch)) (unsigned). A device with a huge MTU (e.g. dummy with max_mtu == 0 accepting MTU 2147483634) makes psched_mtu() return 0x80000000, so slot->allot = INT_MIN and INT_MIN + INT_MIN toggles between INT_MIN and 0 forever, spinning sfq_dequeue() under the qdisc lock. Clamp the quantum to [256, 1 << 20] so the refill loop terminates. The lower bound also covers q->quantum == 0 (psched_mtu() returning 0), which spins sfq_dequeue() identically. sfq_change() already rejects a negative quantum, so only the init path was exposed. Conditions to recreate the bug: a device whose MTU (plus hard_header_len) wraps psched_mtu() into the sign bit (e.g. a dummy device with max_mtu == 0 accepting MTU 2147483634). Requires CAP_NET_ADMIN in a user namespace. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: vega@nebusec.ai Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/20260822195509.112717-7-jhs@mojatatu.com Signed-off-by: Paolo Abeni --- net/sched/sch_sfq.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/sched/sch_sfq.c b/net/sched/sch_sfq.c index 77675f9a4c46..187d3ed578f2 100644 --- a/net/sched/sch_sfq.c +++ b/net/sched/sch_sfq.c @@ -799,7 +799,8 @@ static int sfq_init(struct Qdisc *sch, struct nlattr *opt, q->tail = NULL; q->divisor = SFQ_DEFAULT_HASH_DIVISOR; q->maxflows = SFQ_DEFAULT_FLOWS; - q->quantum = psched_mtu(qdisc_dev(sch)); + q->quantum = clamp_t(u32, psched_mtu(qdisc_dev(sch)), + 256, 1 << 20); q->perturb_period = 0; get_random_bytes(&q->perturbation, sizeof(q->perturbation)); From 63f44178f0a0f86060c9b576d6efab8a3ffa403e Mon Sep 17 00:00:00 2001 From: Jun Yang Date: Mon, 24 Aug 2026 16:18:19 +0800 Subject: [PATCH 1152/1328] sctp: distinguish sequence zero from wildcard in reconf lookup Zero is a valid response sequence after strreset_outseq wraps, but sctp_chunk_lookup_strreset_param() currently treats it as a wildcard. Add match_seq so response lookups match zero exactly while the one type-only lookup can still ignore the sequence. Fixes: 50a41591f110 ("sctp: implement receiver-side procedures for the Add Outgoing Streams Request Parameter") Cc: stable@kernel.org Suggested-by: Simon Horman Acked-by: Xin Long Signed-off-by: Jun Yang Link: https://patch.msgid.link/20260824081832.98717-2-juny24602@gmail.com Signed-off-by: Paolo Abeni --- net/sctp/stream.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/net/sctp/stream.c b/net/sctp/stream.c index 2012f61e250e..285cbebd899e 100644 --- a/net/sctp/stream.c +++ b/net/sctp/stream.c @@ -482,7 +482,7 @@ int sctp_send_add_streams(struct sctp_association *asoc, static struct sctp_paramhdr *sctp_chunk_lookup_strreset_param( struct sctp_association *asoc, __be32 resp_seq, - __be16 type) + __be16 type, bool match_seq) { struct sctp_chunk *chunk = asoc->strreset_chunk; struct sctp_reconf_chunk *hdr; @@ -499,7 +499,7 @@ static struct sctp_paramhdr *sctp_chunk_lookup_strreset_param( */ struct sctp_strreset_tsnreq *req = param.v; - if ((!resp_seq || req->request_seq == resp_seq) && + if ((!match_seq || req->request_seq == resp_seq) && (!type || type == req->param_hdr.type)) return param.v; } @@ -564,7 +564,7 @@ struct sctp_chunk *sctp_process_strreset_outreq( if (asoc->strreset_chunk) { if (!sctp_chunk_lookup_strreset_param( asoc, outreq->response_seq, - SCTP_PARAM_RESET_IN_REQUEST)) { + SCTP_PARAM_RESET_IN_REQUEST, true)) { /* same process with outstanding isn't 0 */ result = SCTP_STRRESET_ERR_IN_PROGRESS; goto out; @@ -816,7 +816,7 @@ struct sctp_chunk *sctp_process_strreset_addstrm_out( if (asoc->strreset_chunk) { if (!sctp_chunk_lookup_strreset_param( - asoc, 0, SCTP_PARAM_RESET_ADD_IN_STREAMS)) { + asoc, 0, SCTP_PARAM_RESET_ADD_IN_STREAMS, false)) { /* same process with outstanding isn't 0 */ result = SCTP_STRRESET_ERR_IN_PROGRESS; goto out; @@ -927,7 +927,8 @@ struct sctp_chunk *sctp_process_strreset_resp( struct sctp_paramhdr *req; __u32 result; - req = sctp_chunk_lookup_strreset_param(asoc, resp->response_seq, 0); + req = sctp_chunk_lookup_strreset_param(asoc, resp->response_seq, 0, + true); if (!req) return NULL; From 3faf13aff243ca9f78d08b1a2956ef5a6fc77b6e Mon Sep 17 00:00:00 2001 From: Jun Yang Date: Mon, 24 Aug 2026 16:18:20 +0800 Subject: [PATCH 1153/1328] sctp: fix stream->outcnt underflow on duplicate RECONF responses A cached RECONF chunk may contain more than one request parameter. A duplicate response can therefore find and process the same ADD_OUT request again while another parameter is still outstanding, rolling back outcnt twice and possibly underflowing it. Track outstanding request types as bits and clear each bit after its first response. Later responses for the same request are then ignored. Fixes: 11ae76e67a17 ("sctp: implement receiver-side procedures for the Reconf Response Parameter") Cc: stable@kernel.org Reported-by: TencentOS Corvus AI Link: https://lore.kernel.org/netdev/20260730110225.37371-1-juny24602@gmail.com/ Suggested-by: Xin Long Assisted-by: tencentos-corvus-ai:kimi-k3 Signed-off-by: Jun Yang Link: https://patch.msgid.link/20260824081832.98717-3-juny24602@gmail.com Signed-off-by: Paolo Abeni --- include/net/sctp/structs.h | 2 +- net/sctp/stream.c | 39 +++++++++++++++++++++++++++----------- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/include/net/sctp/structs.h b/include/net/sctp/structs.h index cccc662561aa..b21f23b736fd 100644 --- a/include/net/sctp/structs.h +++ b/include/net/sctp/structs.h @@ -2057,7 +2057,7 @@ struct sctp_association { force_delay:1; __u8 strreset_enable; - __u8 strreset_outstanding; /* request param count on the fly */ + __u8 strreset_outstanding; /* request param bitmask on the fly */ __u32 strreset_outseq; /* Update after receiving response */ __u32 strreset_inseq; /* Update after receiving request */ diff --git a/net/sctp/stream.c b/net/sctp/stream.c index 285cbebd899e..e4354d58f20b 100644 --- a/net/sctp/stream.c +++ b/net/sctp/stream.c @@ -22,6 +22,15 @@ #include #include +#define SCTP_STRRESET_MASK(type) \ + BIT(ntohs(type) - ntohs(SCTP_PARAM_RESET_OUT_REQUEST)) +#define SCTP_STRRESET_TEST(asoc, type) \ + ((asoc)->strreset_outstanding & SCTP_STRRESET_MASK(type)) +#define SCTP_STRRESET_SET(asoc, type) \ + ((asoc)->strreset_outstanding |= SCTP_STRRESET_MASK(type)) +#define SCTP_STRRESET_CLEAR(asoc, type) \ + ((asoc)->strreset_outstanding &= ~SCTP_STRRESET_MASK(type)) + static void sctp_stream_shrink_out(struct sctp_stream *stream, __u16 outcnt) { struct sctp_association *asoc; @@ -372,7 +381,10 @@ int sctp_send_reset_streams(struct sctp_association *asoc, goto out; } - asoc->strreset_outstanding = out + in; + if (out) + SCTP_STRRESET_SET(asoc, SCTP_PARAM_RESET_OUT_REQUEST); + if (in) + SCTP_STRRESET_SET(asoc, SCTP_PARAM_RESET_IN_REQUEST); out: return retval; @@ -417,7 +429,7 @@ int sctp_send_reset_assoc(struct sctp_association *asoc) return retval; } - asoc->strreset_outstanding = 1; + SCTP_STRRESET_SET(asoc, SCTP_PARAM_RESET_TSN_REQUEST); return 0; } @@ -474,7 +486,10 @@ int sctp_send_add_streams(struct sctp_association *asoc, goto out; } - asoc->strreset_outstanding = !!out + !!in; + if (out) + SCTP_STRRESET_SET(asoc, SCTP_PARAM_RESET_ADD_OUT_STREAMS); + if (in) + SCTP_STRRESET_SET(asoc, SCTP_PARAM_RESET_ADD_IN_STREAMS); out: return retval; @@ -564,13 +579,14 @@ struct sctp_chunk *sctp_process_strreset_outreq( if (asoc->strreset_chunk) { if (!sctp_chunk_lookup_strreset_param( asoc, outreq->response_seq, - SCTP_PARAM_RESET_IN_REQUEST, true)) { + SCTP_PARAM_RESET_IN_REQUEST, true) || + !SCTP_STRRESET_TEST(asoc, SCTP_PARAM_RESET_IN_REQUEST)) { /* same process with outstanding isn't 0 */ result = SCTP_STRRESET_ERR_IN_PROGRESS; goto out; } - asoc->strreset_outstanding--; + SCTP_STRRESET_CLEAR(asoc, SCTP_PARAM_RESET_IN_REQUEST); asoc->strreset_outseq++; if (!asoc->strreset_outstanding) { @@ -669,7 +685,7 @@ struct sctp_chunk *sctp_process_strreset_inreq( SCTP_SO(stream, i)->state = SCTP_STREAM_CLOSED; asoc->strreset_chunk = chunk; - asoc->strreset_outstanding = 1; + SCTP_STRRESET_SET(asoc, SCTP_PARAM_RESET_OUT_REQUEST); sctp_chunk_hold(asoc->strreset_chunk); result = SCTP_STRRESET_PERFORMED; @@ -816,13 +832,14 @@ struct sctp_chunk *sctp_process_strreset_addstrm_out( if (asoc->strreset_chunk) { if (!sctp_chunk_lookup_strreset_param( - asoc, 0, SCTP_PARAM_RESET_ADD_IN_STREAMS, false)) { + asoc, 0, SCTP_PARAM_RESET_ADD_IN_STREAMS, false) || + !SCTP_STRRESET_TEST(asoc, SCTP_PARAM_RESET_ADD_IN_STREAMS)) { /* same process with outstanding isn't 0 */ result = SCTP_STRRESET_ERR_IN_PROGRESS; goto out; } - asoc->strreset_outstanding--; + SCTP_STRRESET_CLEAR(asoc, SCTP_PARAM_RESET_ADD_IN_STREAMS); asoc->strreset_outseq++; if (!asoc->strreset_outstanding) { @@ -899,7 +916,7 @@ struct sctp_chunk *sctp_process_strreset_addstrm_in( goto out; asoc->strreset_chunk = chunk; - asoc->strreset_outstanding = 1; + SCTP_STRRESET_SET(asoc, SCTP_PARAM_RESET_ADD_OUT_STREAMS); sctp_chunk_hold(asoc->strreset_chunk); stream->outcnt = outcnt; @@ -929,7 +946,7 @@ struct sctp_chunk *sctp_process_strreset_resp( req = sctp_chunk_lookup_strreset_param(asoc, resp->response_seq, 0, true); - if (!req) + if (!req || !SCTP_STRRESET_TEST(asoc, req->type)) return NULL; result = ntohl(resp->result); @@ -1079,7 +1096,7 @@ struct sctp_chunk *sctp_process_strreset_resp( nums, 0, GFP_ATOMIC); } - asoc->strreset_outstanding--; + SCTP_STRRESET_CLEAR(asoc, req->type); asoc->strreset_outseq++; /* remove everything for this reconf request */ From ab0304fd69b07605553e9f161d65ecf9ccc2e2fe Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Mon, 24 Aug 2026 10:15:19 +0200 Subject: [PATCH 1154/1328] verification/rvgen: Use .old instead of .bak for kunit backup files The rvgen kunit command generates .bak backup files and these can be checked in for selftests (make check). Clean targets like make disclean remove such files, leaving the tree dirty. Switch to .old to preserve a clean tree after make disclean. Reported-by: Kuan-Wei Chiu Closes: https://lore.kernel.org/lkml/aosuwKH5GOEo0xTN@google.com Fixes: 7b6246294eb0 ("verification/rvgen: Add selftests for rvgen kunit") Reviewed-by: Nam Cao Tested-by: Kuan-Wei Chiu Link: https://lore.kernel.org/r/20260824081519.81103-2-gmonaco@redhat.com Signed-off-by: Gabriele Monaco --- tools/verification/rvgen/rvgen/kunit.py | 2 +- .../{test_bak_kunit_kunit.c.bak => test_bak_kunit_kunit.c.old} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename tools/verification/rvgen/tests/golden/test_bak_kunit/{test_bak_kunit_kunit.c.bak => test_bak_kunit_kunit.c.old} (100%) diff --git a/tools/verification/rvgen/rvgen/kunit.py b/tools/verification/rvgen/rvgen/kunit.py index ed2082d7d3bc..85973f918c9b 100644 --- a/tools/verification/rvgen/rvgen/kunit.py +++ b/tools/verification/rvgen/rvgen/kunit.py @@ -173,7 +173,7 @@ EXPORT_SYMBOL_IF_KUNIT({struct_name}); for path in (header_file_path, kunit_c_file_path): if path.exists(): try: - path.rename(path.with_suffix(path.suffix + ".bak")) + path.rename(path.with_suffix(path.suffix + ".old")) except OSError as e: raise KUnitError(f"Error backing up file {path}: {e}") from e diff --git a/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.c.bak b/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.c.old similarity index 100% rename from tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.c.bak rename to tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.c.old From adb176c1407a7f802b21627a7ec912ff649ab6a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Habil=20Eren=20T=C3=BCrker?= Date: Tue, 25 Aug 2026 11:40:14 +0300 Subject: [PATCH 1155/1328] ALSA: hda/realtek: Fix speaker mute LED for HP Laptop 15-fd0039nt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HP Laptop 15-fd0039nt (SSID 103c:8bb6) needs a quirk to control the speaker mute LED via VREF100 on NID 0x1a (active-high). This patch replaces the previous ALC236_FIXUP_HP_MICMUTE_LED_ONLY with ALC236_FIXUP_HP_15_FD0XXX, which covers both mic mute (GPIO0) and speaker mute (NID 0x1a) LEDs. Use spec->no_shutup_pins instead of a custom shutup hook, as suggested by Takashi Iwai. Fixes: e711ebfadc9a ("ALSA: hda/realtek: Add quirk for HP Laptop 15-fd0039nt") Tested-by: Habil Eren Türker Signed-off-by: Habil Eren Türker Link: https://patch.msgid.link/20260825084125.4103-1-habilerenturker@hotmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 61 +++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 8 deletions(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index f230a2be3914..e4349743a251 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -1370,14 +1370,59 @@ static void alc269_fixup_hp_gpio_led(struct hda_codec *codec, alc_fixup_hp_gpio_led(codec, action, 0x08, 0x10); } -static void alc236_fixup_hp_micmute_led_only(struct hda_codec *codec, - const struct hda_fixup *fix, int action) +/* + * HP Laptop 15-fd0xxx (SSID 103c:8bb6) Speaker Mute LED fix + * + * The speaker mute LED is controlled via VREF100 on NID 0x1a. + * This pin must remain powered (D0) even during suspend, otherwise + * the LED state is lost and the pin defaults to Hi-Z on resume, + * causing the LED to stop responding to mute toggles. + * Windows keeps this pin powered unconditionally, so matching that + * behavior ensures consistent LED operation across suspend/resume. + * The mic-mute LED is controlled via GPIO 0 with active-low polarity. + */ +static unsigned int hp_8bb6_power_filter(struct hda_codec *codec, + hda_nid_t nid, + unsigned int power_state) +{ + if (nid == 0x1a) + return AC_PWRST_D0; + return snd_hda_gen_path_power_filter(codec, nid, power_state); +} + +static int hp_8bb6_speaker_mute_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct hda_codec *codec = dev_to_hda_codec(led_cdev->dev->parent); + unsigned int val = (brightness == LED_OFF) ? PIN_IN : PIN_VREF100; + + snd_hda_set_pin_ctl_cache(codec, 0x1a, val); + return 0; +} + +static void alc236_fixup_hp_15_fd0xxx(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) { struct alc_spec *spec = codec->spec; - if (action == HDA_FIXUP_ACT_PRE_PROBE) + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: spec->micmute_led_polarity = 1; - alc_fixup_hp_gpio_led(codec, action, 0x00, 0x01); + alc_fixup_hp_gpio_led(codec, action, 0x00, 0x01); + + spec->mute_led_polarity = 0; + snd_hda_gen_add_mute_led_cdev(codec, hp_8bb6_speaker_mute_led_set); + + codec->power_filter = hp_8bb6_power_filter; + spec->no_shutup_pins = 1; + break; + + case HDA_FIXUP_ACT_INIT: + if (spec->gen.vmaster_mute.hook) + snd_hda_sync_vmaster_hook(&spec->gen.vmaster_mute); + break; + } } static void alc285_fixup_hp_gpio_led(struct hda_codec *codec, @@ -4163,7 +4208,7 @@ enum { ALC236_FIXUP_HP_GPIO_LED, ALC236_FIXUP_HP_MUTE_LED, ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF, - ALC236_FIXUP_HP_MICMUTE_LED_ONLY, + ALC236_FIXUP_HP_15_FD0XXX, ALC236_FIXUP_LENOVO_INV_DMIC, ALC298_FIXUP_SAMSUNG_AMP, ALC298_FIXUP_SAMSUNG_AMP_V2_2_AMPS, @@ -5943,9 +5988,9 @@ static const struct hda_fixup alc269_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc236_fixup_hp_mute_led_micmute_gpio, }, - [ALC236_FIXUP_HP_MICMUTE_LED_ONLY] = { + [ALC236_FIXUP_HP_15_FD0XXX] = { .type = HDA_FIXUP_FUNC, - .v.func = alc236_fixup_hp_micmute_led_only, + .v.func = alc236_fixup_hp_15_fd0xxx, }, [ALC236_FIXUP_LENOVO_INV_DMIC] = { .type = HDA_FIXUP_FUNC, @@ -7488,7 +7533,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8b97, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), SND_PCI_QUIRK(0x103c, 0x8bb3, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8bb4, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), - SND_PCI_QUIRK(0x103c, 0x8bb6, "HP Laptop 15-fd0039nt", ALC236_FIXUP_HP_MICMUTE_LED_ONLY), + SND_PCI_QUIRK(0x103c, 0x8bb6, "HP Laptop 15-fd0039nt", ALC236_FIXUP_HP_15_FD0XXX), SND_PCI_QUIRK(0x103c, 0x8bbe, "HP Victus 16-r0xxx (MB 8BBE)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8bc8, "HP Victus 15-fa1xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8bcd, "HP Omen 16-xd0xxx", ALC245_FIXUP_HP_MUTE_LED_V1_COEFBIT), From e2aa5ad3be41accfcdcccc62348f21af7baa3a38 Mon Sep 17 00:00:00 2001 From: Christopher Tolang Date: Sun, 23 Aug 2026 19:32:21 +0800 Subject: [PATCH 1156/1328] ASoC: amd: yc: Add DMI entry for MSI Thin A15 B7UC This model requires an additional detection quirk to enable the internal microphone. Fixes: fa991481b8b2 ("ASoC: amd: add YC machine driver using dmic") Cc: stable@vger.kernel.org Assisted-by: OpenAI Codex Signed-off-by: Christopher Tolang Link: https://patch.msgid.link/20260823113221.19744-1-christophertolang@gmail.com Signed-off-by: Mark Brown --- sound/soc/amd/yc/acp6x-mach.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c index 2dde300a9220..4f2ccf9e1fa6 100644 --- a/sound/soc/amd/yc/acp6x-mach.c +++ b/sound/soc/amd/yc/acp6x-mach.c @@ -836,6 +836,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Thin A15 B7VE"), } }, + { + .driver_data = &acp6x_card, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Micro-Star International Co., Ltd."), + DMI_MATCH(DMI_PRODUCT_NAME, "Thin A15 B7UC"), + } + }, { .driver_data = &acp6x_card, .matches = { From dc4b95b8fee95113587e93ca116356032d271371 Mon Sep 17 00:00:00 2001 From: Victor Nogueira Date: Mon, 24 Aug 2026 08:59:28 -0300 Subject: [PATCH 1157/1328] net/sched: sch_teql: restore skb->dev on the slave failure path teql_master_xmit() sets skb->dev = slave before calling the slave's ndo_start_xmit(), but never restores it when that transmit fails. The skb then walks on to the next slave still pointing at the previous one. If a later slave has no resolved neighbour, teql_resolve() hands the skb to neigh_event_send(), which queues it on that neighbour's arp_queue with the stale skb->dev. skb->dev holds no reference, so deleting the previous slave frees the net_device while the skb is still queued. Whatever runs next on that skb - arp_error_report() on timeout, or neigh_direct_output() -> dev_queue_xmit() once the neighbour resolves - causes a UAF like the one below: BUG: KASAN: slab-use-after-free in __icmp_send (net/ipv4/icmp.c:914 (discriminator 2)) Read of size 4 at addr ffff888106e100b0 by task flood_packet/527 CPU: 0 UID: 0 PID: 527 Comm: flood_packet Not tainted 7.2.0-rc6-g594d90519502 #1 PREEMPT(lazy) Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 Call Trace: dump_stack_lvl (lib/dump_stack.c:94 lib/dump_stack.c:120) print_report (mm/kasan/report.c:378 mm/kasan/report.c:482) ? __pfx__raw_spin_lock_irqsave (./include/asm-generic/qrwlock.h:122 (discriminator 4)) ? __icmp_send (net/ipv4/icmp.c:914 (discriminator 2)) kasan_report (mm/kasan/report.c:595) ? __icmp_send (net/ipv4/icmp.c:914 (discriminator 2)) __icmp_send (net/ipv4/icmp.c:914 (discriminator 2)) [...] ipv4_link_failure (net/ipv4/route.c:1251 net/ipv4/route.c:1258) ? __pfx_ipv4_link_failure (./include/linux/skbuff.h:4327) ? _raw_write_lock (./include/linux/instrumented.h:55 ./include/linux/atomic/atomic-instrumented.h:1301 ./include/asm-generic/qrwlock.h:98 ./include/linux/rwlock_api_smp.h:230 kernel/locking/spinlock.c:304) ? __pfx__raw_write_lock (kernel/locking/spinlock.c:175) arp_error_report (./include/net/dst.h:438 net/ipv4/arp.c:296) neigh_invalidate (net/core/neighbour.c:1077) neigh_timer_handler (net/core/neighbour.c:1169) [...] Allocated by task 505: kasan_save_stack (mm/kasan/common.c:57) kasan_save_track (mm/kasan/common.c:78) __kasan_kmalloc (mm/kasan/common.c:398 mm/kasan/common.c:415) __kvmalloc_node_noprof (./include/linux/kasan.h:263 mm/slub.c:5334 mm/slub.c:6905) alloc_netdev_mqs (net/core/dev.c:12055 (discriminator 2)) rtnl_create_link (net/core/rtnetlink.c:3721) rtnl_newlink (net/core/rtnetlink.c:3903 net/core/rtnetlink.c:4044 net/core/rtnetlink.c:4159) rtnetlink_rcv_msg (net/core/rtnetlink.c:7076) [...] Freed by task 536: kasan_save_stack (mm/kasan/common.c:57) kasan_save_track (mm/kasan/common.c:78) kasan_save_free_info (mm/kasan/generic.c:584) __kasan_slab_free (mm/kasan/common.c:253 mm/kasan/common.c:285) kfree (./include/linux/kasan.h:235 mm/slub.c:2677 mm/slub.c:6377 mm/slub.c:6692) device_release (drivers/base/core.c:2636) kobject_put (lib/kobject.c:689 lib/kobject.c:720 ./include/linux/kref.h:65 lib/kobject.c:737) netdev_run_todo (net/core/dev.c:11756) rtnl_dellink (net/core/rtnetlink.c:157 ./include/linux/rtnetlink.h:135 net/core/rtnetlink.c:3651) rtnetlink_rcv_msg (net/core/rtnetlink.c:7076) [...] Fix this by restoring skb->dev to the master at the end of each slave's iteration. Fixes: 0cc0c2e661af ("net/sched: teql: fix NULL pointer dereference in iptunnel_xmit on TEQL slave xmit") Reported-by: Vega Acked-by: Jamal Hadi Salim Signed-off-by: Victor Nogueira Link: https://patch.msgid.link/20260824115928.4099988-1-victor@mojatatu.com Signed-off-by: Paolo Abeni --- net/sched/sch_teql.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/sched/sch_teql.c b/net/sched/sch_teql.c index 5c42a29a981c..9e52afc2d980 100644 --- a/net/sched/sch_teql.c +++ b/net/sched/sch_teql.c @@ -377,6 +377,7 @@ static netdev_tx_t teql_master_xmit(struct sk_buff *skb, struct net_device *dev) nores = 1; break; } + skb->dev = dev; __skb_pull(skb, skb_network_offset(skb)); } while ((q = rcu_dereference(NEXT_SLAVE(q))) != start); From fd8ed52ecea6b93e23d52cb2758fb4fb4029067f Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Mon, 24 Aug 2026 21:03:02 +0800 Subject: [PATCH 1158/1328] ASoC: amd: yc: Add DMI entry for Alienware m18 R1 AMD Add DMI entry so the YC machine driver probes on this model and the internal DMIC works. Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221485 Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20260824130302.553419-1-zhangheng@kylinos.cn Signed-off-by: Mark Brown --- sound/soc/amd/yc/acp6x-mach.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c index 4f2ccf9e1fa6..d1d2ab1bfefa 100644 --- a/sound/soc/amd/yc/acp6x-mach.c +++ b/sound/soc/amd/yc/acp6x-mach.c @@ -570,6 +570,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m17 R5 AMD"), } }, + { + .driver_data = &acp6x_card, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m18 R1 AMD"), + } + }, { .driver_data = &acp6x_card, .matches = { From 58c1c30074a8d1179e17a0188cd695b2f416bf75 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 25 Aug 2026 15:49:31 +0200 Subject: [PATCH 1159/1328] ALSA: rawmidi: Another workaround for false-positive mutex lockdep warning While we attempted to work around the false-positive lockdep warning due to the nested mutex lock in rawmidi at the open path for a UMP legacy rawmidi, it didn't cover the similar locking at its close path, and this still caused another false-positive reports by syzkaller. Add a similar workaround to snd_rawmidi_kernel_release() as done in the former commit 9c04742e73b3 ("ALSA: rawmidi: Work around false-positive mutex lockdep warning") to cover completely. Reported-by: syzbot+7d1edf0ff6a05961020c@syzkaller.appspotmail.com Closes: https://lore.kernel.org/6a8c7e4d.4d75e56a.c9a88.0052.GAE@google.com Link: https://patch.msgid.link/20260825134942.1289272-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/rawmidi.h | 8 +++++++- sound/core/rawmidi.c | 11 +++++++---- sound/core/ump.c | 3 ++- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/include/sound/rawmidi.h b/include/sound/rawmidi.h index 88a6159364d0..4154035af414 100644 --- a/include/sound/rawmidi.h +++ b/include/sound/rawmidi.h @@ -179,7 +179,8 @@ int snd_rawmidi_info_select(struct snd_card *card, struct snd_rawmidi_info *info int snd_rawmidi_kernel_open_nested(struct snd_rawmidi *rmidi, int subdevice, int mode, struct snd_rawmidi_file *rfile, int depth); -int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile); +int snd_rawmidi_kernel_release_nested(struct snd_rawmidi_file *rfile, + int depth); int snd_rawmidi_output_params(struct snd_rawmidi_substream *substream, struct snd_rawmidi_params *params); int snd_rawmidi_input_params(struct snd_rawmidi_substream *substream, @@ -201,6 +202,11 @@ static inline int snd_rawmidi_kernel_open(struct snd_rawmidi *rmidi, return snd_rawmidi_kernel_open_nested(rmidi, subdevice, mode, rfile, 0); } +static inline int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile) +{ + return snd_rawmidi_kernel_release_nested(rfile, 0); +} + /* set up the tied devices */ static inline void snd_rawmidi_tie_devices(struct snd_rawmidi *r1, struct snd_rawmidi *r2) diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c index bf504e27f73e..34b4c7d6dbe6 100644 --- a/sound/core/rawmidi.c +++ b/sound/core/rawmidi.c @@ -571,7 +571,6 @@ static void rawmidi_release_priv(struct snd_rawmidi_file *rfile) struct snd_rawmidi *rmidi; rmidi = rfile->rmidi; - guard(mutex)(&rmidi->open_mutex); if (rfile->input) { close_substream(rmidi, rfile->input, 1); rfile->input = NULL; @@ -585,7 +584,8 @@ static void rawmidi_release_priv(struct snd_rawmidi_file *rfile) } /* called from sound/core/seq/seq_midi.c */ -int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile) +int snd_rawmidi_kernel_release_nested(struct snd_rawmidi_file *rfile, + int depth) { struct snd_rawmidi *rmidi; @@ -593,11 +593,13 @@ int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile) return -ENXIO; rmidi = rfile->rmidi; + mutex_lock_nested(&rmidi->open_mutex, depth); rawmidi_release_priv(rfile); + mutex_unlock(&rmidi->open_mutex); module_put(rmidi->card->module); return 0; } -EXPORT_SYMBOL(snd_rawmidi_kernel_release); +EXPORT_SYMBOL(snd_rawmidi_kernel_release_nested); static int snd_rawmidi_release(struct inode *inode, struct file *file) { @@ -607,7 +609,8 @@ static int snd_rawmidi_release(struct inode *inode, struct file *file) rfile = file->private_data; rmidi = rfile->rmidi; - rawmidi_release_priv(rfile); + scoped_guard(mutex, &rmidi->open_mutex) + rawmidi_release_priv(rfile); kfree(rfile); module = rmidi->card->module; snd_card_file_remove(rmidi->card, file); diff --git a/sound/core/ump.c b/sound/core/ump.c index 82ad155c56e6..d183c8a000bd 100644 --- a/sound/core/ump.c +++ b/sound/core/ump.c @@ -1184,7 +1184,8 @@ static int snd_ump_legacy_close(struct snd_rawmidi_substream *substream) ump->legacy_substreams[dir][group] = NULL; if (dir == SNDRV_RAWMIDI_STREAM_OUTPUT) { if (!--ump->legacy_out_opens) - snd_rawmidi_kernel_release(&ump->legacy_out_rfile); + snd_rawmidi_kernel_release_nested(&ump->legacy_out_rfile, + SINGLE_DEPTH_NESTING); } return 0; } From 705c4ed0643366963547b2616d53165f2519c81f Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Fri, 26 Jun 2026 19:35:52 +0300 Subject: [PATCH 1160/1328] tpm: tpm_i2c_nuvoton: disable IRQ on wait timeout i2c_nuvoton_wait_for_stat() enables the IRQ before waiting for the interrupt handler to report a status change. If the wait times out, or is interrupted before the handler runs, the function returns without balancing the enable_irq() call. Disable the IRQ before leaving the failed wait path. Also preserve an interrupted wait's original error code instead of converting it to -ETIMEDOUT inside the helper. Cc: stable@vger.kernel.org # v5.10+ Fixes: 4c336e4b1556 ("tpm: Add support for the Nuvoton NPCT501 I2C TPM") Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Reviewed-by: Jarkko Sakkinen Link: https://lore.kernel.org/r/20260626091653.54929-1-mhun512@gmail.com Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/tpm_i2c_nuvoton.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/char/tpm/tpm_i2c_nuvoton.c b/drivers/char/tpm/tpm_i2c_nuvoton.c index d44903b29929..aa3673002f3a 100644 --- a/drivers/char/tpm/tpm_i2c_nuvoton.c +++ b/drivers/char/tpm/tpm_i2c_nuvoton.c @@ -182,8 +182,10 @@ static int i2c_nuvoton_wait_for_stat(struct tpm_chip *chip, u8 mask, u8 value, timeout); if (rc > 0) return 0; - /* At this point we know that the SINT pin is asserted, so we - * do not need to do i2c_nuvoton_check_status */ + + disable_irq(priv->irq); + if (rc < 0) + return rc; } else { unsigned long ten_msec, stop; bool status_valid; From cf92237e81130e50337456dc2bedbc7818f75269 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Sat, 11 Jul 2026 20:36:37 +0300 Subject: [PATCH 1161/1328] tpm: Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() calls. Signed-off-by: Pan Chuang Reviewed-by: Jarkko Sakkinen Link: https://lore.kernel.org/r/20260710105318.376496-3-panchuang@vivo.com Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/st33zp24/st33zp24.c | 5 +---- drivers/char/tpm/tpm_i2c_nuvoton.c | 2 -- drivers/char/tpm/tpm_tis_i2c_cr50.c | 4 +--- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/drivers/char/tpm/st33zp24/st33zp24.c b/drivers/char/tpm/st33zp24/st33zp24.c index e2b7451ea7cc..8d5179367eac 100644 --- a/drivers/char/tpm/st33zp24/st33zp24.c +++ b/drivers/char/tpm/st33zp24/st33zp24.c @@ -506,11 +506,8 @@ int st33zp24_probe(void *phy_id, const struct st33zp24_phy_ops *ops, ret = devm_request_irq(dev, irq, tpm_ioserirq_handler, IRQF_TRIGGER_HIGH, "TPM SERIRQ management", chip); - if (ret < 0) { - dev_err(&chip->dev, "TPM SERIRQ signals %d not available\n", - irq); + if (ret < 0) goto _tpm_clean_answer; - } intmask |= TPM_INTF_CMD_READY_INT | TPM_INTF_STS_VALID_INT diff --git a/drivers/char/tpm/tpm_i2c_nuvoton.c b/drivers/char/tpm/tpm_i2c_nuvoton.c index aa3673002f3a..c84afb29d548 100644 --- a/drivers/char/tpm/tpm_i2c_nuvoton.c +++ b/drivers/char/tpm/tpm_i2c_nuvoton.c @@ -576,8 +576,6 @@ static int i2c_nuvoton_probe(struct i2c_client *client) dev_name(&chip->dev), chip); if (rc) { - dev_err(dev, "%s() Unable to request irq: %d for use\n", - __func__, priv->irq); priv->irq = 0; } else { chip->flags |= TPM_CHIP_FLAG_IRQ; diff --git a/drivers/char/tpm/tpm_tis_i2c_cr50.c b/drivers/char/tpm/tpm_tis_i2c_cr50.c index b48cacacc066..7f828fae70d3 100644 --- a/drivers/char/tpm/tpm_tis_i2c_cr50.c +++ b/drivers/char/tpm/tpm_tis_i2c_cr50.c @@ -751,10 +751,8 @@ static int tpm_cr50_i2c_probe(struct i2c_client *client) rc = devm_request_irq(dev, client->irq, tpm_cr50_i2c_int_handler, IRQF_TRIGGER_FALLING | IRQF_NO_AUTOEN, dev->driver->name, chip); - if (rc < 0) { - dev_err(dev, "Failed to probe IRQ %d\n", client->irq); + if (rc < 0) return rc; - } priv->irq = client->irq; } else { From 4bfacee94f309346bef0b598ea091088b9150af9 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sun, 12 Jul 2026 20:32:34 +0200 Subject: [PATCH 1162/1328] tpm: atmel: depend on X86 tpm_atmel probes for the chip at fixed x86 Super-I/O ports (0x4e) with inb()/outb(), so it only works on x86. TCG_ATMEL nevertheless depends only on HAS_IOPORT_MAP/HAS_IOPORT, which arm and arm64 also satisfy. There the probe is useless, and on platforms whose unbacked I/O access faults it oopses in init_atmel() at boot (e.g. arm/versatile): Unable to handle kernel paging request at virtual address fee0004e PC is at init_atmel+0x34/0x244 TCG_NSC and TCG_TIS already "depends on X86" (commit 2f592f2a7d74 ("TPM: NSC and TIS drivers X86 dependency fix")); TCG_ATMEL was missed. Add the same dependency. Signed-off-by: Karl Mehltretter Reviewed-by: Jarkko Sakkinen Link: https://lore.kernel.org/r/20260712183234.23125-1-kmehltretter@gmail.com Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/Kconfig | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/char/tpm/Kconfig b/drivers/char/tpm/Kconfig index 8a8f692b6088..5f672f2c01b0 100644 --- a/drivers/char/tpm/Kconfig +++ b/drivers/char/tpm/Kconfig @@ -163,8 +163,7 @@ config TCG_NSC config TCG_ATMEL tristate "Atmel TPM Interface" - depends on HAS_IOPORT_MAP - depends on HAS_IOPORT + depends on X86 help If you have a TPM security chip from Atmel say Yes and it will be accessible from within Linux. To compile this driver From b4d1adc70471d0752bba674552d19971ed9fa869 Mon Sep 17 00:00:00 2001 From: Li Jun Date: Wed, 12 Aug 2026 18:09:14 +0800 Subject: [PATCH 1163/1328] tpm: tpm_tis_spi: fix nodef CR50 tpm_tis_spi_resume is null if no define CONFIG_TCG_TIS_SPI_CR50 in config, the add of tpm_tis_spi_resume is null, this cause the tpm chip to fail to resume. Signed-off-by: Li Jun Link: https://lore.kernel.org/r/20260812100914.3540149-1-lijun01@kylinos.cn Reviewed-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/tpm_tis_spi.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/char/tpm/tpm_tis_spi.h b/drivers/char/tpm/tpm_tis_spi.h index d0f66f6f1931..ced416ecd92c 100644 --- a/drivers/char/tpm/tpm_tis_spi.h +++ b/drivers/char/tpm/tpm_tis_spi.h @@ -40,8 +40,12 @@ static inline int cr50_spi_probe(struct spi_device *spi) } #endif -#if defined(CONFIG_PM_SLEEP) && defined(CONFIG_TCG_TIS_SPI_CR50) -extern int tpm_tis_spi_resume(struct device *dev); +#if defined(CONFIG_PM_SLEEP) + #if defined(CONFIG_TCG_TIS_SPI_CR50) + extern int tpm_tis_spi_resume(struct device *dev); + #else + #define tpm_tis_spi_resume tpm_tis_resume + #endif #else #define tpm_tis_spi_resume NULL #endif From 8b92687708f5ef980de01c2042dbd76d11f78547 Mon Sep 17 00:00:00 2001 From: Ruoyu Wang Date: Fri, 21 Aug 2026 04:38:05 +0300 Subject: [PATCH 1164/1328] tpm: st33zp24: Return zero on status read failure st33zp24_status() ignores the result of the transport read and returns data even when no byte was received. The I2C transport, for example, skips i2c_master_recv() when the register-select write is short or fails, leaving data uninitialized. The resulting stack value can be interpreted as TPM_STS flags and let status checks complete spuriously. The status callback cannot propagate a transport error. Return zero unless recv() reports exactly one byte. With no status bits set, callers retry or take their existing timeout or error path instead of acting on an invalid status value. This issue was found by a static analysis checker and confirmed by manual source review. Fixes: 251a7b08213a ("TPM: STMicroelectronics ST33 I2C KERNEL 3.x") Signed-off-by: Ruoyu Wang Link: https://lore.kernel.org/r/20260813153032.3951878-1-ruoyuw560@gmail.com Reviewed-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/st33zp24/st33zp24.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/char/tpm/st33zp24/st33zp24.c b/drivers/char/tpm/st33zp24/st33zp24.c index 8d5179367eac..168df7c8d13b 100644 --- a/drivers/char/tpm/st33zp24/st33zp24.c +++ b/drivers/char/tpm/st33zp24/st33zp24.c @@ -93,7 +93,9 @@ static u8 st33zp24_status(struct tpm_chip *chip) struct st33zp24_dev *tpm_dev = dev_get_drvdata(&chip->dev); u8 data; - tpm_dev->ops->recv(tpm_dev->phy_id, TPM_STS, &data, 1); + if (tpm_dev->ops->recv(tpm_dev->phy_id, TPM_STS, &data, 1) != 1) + return 0; + return data; } From 264216889d39df509b9c8045df53529480b0b718 Mon Sep 17 00:00:00 2001 From: Ruoyu Wang Date: Thu, 13 Aug 2026 23:30:32 +0800 Subject: [PATCH 1165/1328] tpm: st33zp24: Validate locality read result check_locality() treats every nonzero transport return as success. SPI errors remain negative, while the I2C path can convert a negative write error through its byte-sized status variable. Either result is nonzero even though the TPM_ACCESS byte can remain unwritten, so indeterminate ACTIVE_LOCALITY and VALID bits can falsely report an active locality. Require recv() to return exactly the requested byte before examining TPM_ACCESS. Transport errors and short reads now report an inactive locality, while successful reads retain the existing behavior. This issue was found by a static analysis checker and confirmed by manual source review. Fixes: 251a7b08213a ("TPM: STMicroelectronics ST33 I2C KERNEL 3.x") Signed-off-by: Ruoyu Wang Link: https://lore.kernel.org/r/20260813153032.3951878-2-ruoyuw560@gmail.com Reviewed-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/st33zp24/st33zp24.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/char/tpm/st33zp24/st33zp24.c b/drivers/char/tpm/st33zp24/st33zp24.c index 168df7c8d13b..c15a9b7aaff8 100644 --- a/drivers/char/tpm/st33zp24/st33zp24.c +++ b/drivers/char/tpm/st33zp24/st33zp24.c @@ -106,10 +106,10 @@ static bool check_locality(struct tpm_chip *chip) { struct st33zp24_dev *tpm_dev = dev_get_drvdata(&chip->dev); u8 data; - u8 status; + int status; status = tpm_dev->ops->recv(tpm_dev->phy_id, TPM_ACCESS, &data, 1); - if (status && (data & + if (status == 1 && (data & (TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID)) == (TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID)) return true; From e06f28d32f31206c0be32a08b0273e26cfc1fc92 Mon Sep 17 00:00:00 2001 From: Ross Philipson Date: Sat, 11 Jul 2026 09:01:01 -0700 Subject: [PATCH 1166/1328] tpm: Initial step to reorganize TPM public headers Consolidate TPM1 constants in tpm_command.h and remove duplicate constants from tpm1-cmd.c. Co-developed-by: Daniel P. Smith Signed-off-by: Daniel P. Smith Co-developed-by: Alec Brown Signed-off-by: Alec Brown Signed-off-by: Ross Philipson Reviewed-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/tpm-buf.c | 1 - drivers/char/tpm/tpm1-cmd.c | 14 +------- include/keys/trusted_tpm.h | 1 - include/linux/tpm.h | 2 ++ include/linux/tpm_command.h | 41 ++++++++++++++++------- security/keys/trusted-keys/trusted_tpm1.c | 1 - security/keys/trusted-keys/trusted_tpm2.c | 1 - 7 files changed, 31 insertions(+), 30 deletions(-) diff --git a/drivers/char/tpm/tpm-buf.c b/drivers/char/tpm/tpm-buf.c index dc882fc9fa9e..4c4f450630df 100644 --- a/drivers/char/tpm/tpm-buf.c +++ b/drivers/char/tpm/tpm-buf.c @@ -3,7 +3,6 @@ * Handling of TPM command and other buffers. */ -#include #include #include diff --git a/drivers/char/tpm/tpm1-cmd.c b/drivers/char/tpm/tpm1-cmd.c index b49a790f1bd5..664ca1fff2e8 100644 --- a/drivers/char/tpm/tpm1-cmd.c +++ b/drivers/char/tpm/tpm1-cmd.c @@ -22,8 +22,6 @@ #include "tpm.h" -#define TPM_MAX_ORDINAL 243 - /* * Array with one entry per ordinal defining the maximum amount * of time the chip could take to return the result. The ordinal @@ -308,9 +306,6 @@ unsigned long tpm1_calc_ordinal_duration(struct tpm_chip *chip, u32 ordinal) return duration; } -#define TPM_ORD_STARTUP 153 -#define TPM_ST_CLEAR 1 - /** * tpm1_startup() - turn on the TPM * @chip: TPM chip to use @@ -459,7 +454,6 @@ int tpm1_get_timeouts(struct tpm_chip *chip) return 0; } -#define TPM_ORD_PCR_EXTEND 20 int tpm1_pcr_extend(struct tpm_chip *chip, u32 pcr_idx, const u8 *hash, const char *log_msg) { @@ -478,7 +472,6 @@ int tpm1_pcr_extend(struct tpm_chip *chip, u32 pcr_idx, const u8 *hash, return rc; } -#define TPM_ORD_GET_CAP 101 ssize_t tpm1_getcap(struct tpm_chip *chip, u32 subcap_id, cap_t *cap, const char *desc, size_t min_cap_length) { @@ -511,7 +504,6 @@ ssize_t tpm1_getcap(struct tpm_chip *chip, u32 subcap_id, cap_t *cap, } EXPORT_SYMBOL_GPL(tpm1_getcap); -#define TPM_ORD_GET_RANDOM 70 struct tpm1_get_random_out { __be32 rng_data_len; u8 rng_data[TPM_MAX_RNG_DATA]; @@ -580,13 +572,12 @@ int tpm1_get_random(struct tpm_chip *chip, u8 *dest, size_t max) return rc; } -#define TPM_ORD_PCRREAD 21 int tpm1_pcr_read(struct tpm_chip *chip, u32 pcr_idx, u8 *res_buf) { struct tpm_buf buf; int rc; - rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_PCRREAD); + rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_PCR_READ); if (rc) return rc; @@ -609,7 +600,6 @@ int tpm1_pcr_read(struct tpm_chip *chip, u32 pcr_idx, u8 *res_buf) return rc; } -#define TPM_ORD_CONTINUE_SELFTEST 83 /** * tpm1_continue_selftest() - run TPM's selftest * @chip: TPM chip to use @@ -726,8 +716,6 @@ int tpm1_auto_startup(struct tpm_chip *chip) return rc; } -#define TPM_ORD_SAVESTATE 152 - /** * tpm1_pm_suspend() - pm suspend handler * @chip: TPM chip to use. diff --git a/include/keys/trusted_tpm.h b/include/keys/trusted_tpm.h index 0fadc6a4f166..3a0fa3bc8454 100644 --- a/include/keys/trusted_tpm.h +++ b/include/keys/trusted_tpm.h @@ -3,7 +3,6 @@ #define __TRUSTED_TPM_H #include -#include extern struct trusted_key_ops trusted_key_tpm_ops; diff --git a/include/linux/tpm.h b/include/linux/tpm.h index 202da079d500..1846d5485a2c 100644 --- a/include/linux/tpm.h +++ b/include/linux/tpm.h @@ -25,6 +25,8 @@ #include #include +#include + #define TPM_DIGEST_SIZE 20 /* Max TPM v1.2 PCR size */ #define TPM2_MAX_DIGEST_SIZE SHA512_DIGEST_SIZE diff --git a/include/linux/tpm_command.h b/include/linux/tpm_command.h index f5c03e9c3913..174b043d8bbc 100644 --- a/include/linux/tpm_command.h +++ b/include/linux/tpm_command.h @@ -3,27 +3,42 @@ #define __LINUX_TPM_COMMAND_H__ /* - * TPM Command constants from specifications at - * http://www.trustedcomputinggroup.org + * == TPM 1 Family Chips == + * + * TPM 1.2 Main Specification: + * https://trustedcomputinggroup.org/resource/tpm-main-specification/ */ +#define TPM_MAX_ORDINAL 243 + /* Command TAGS */ -#define TPM_TAG_RQU_COMMAND 193 -#define TPM_TAG_RQU_AUTH1_COMMAND 194 -#define TPM_TAG_RQU_AUTH2_COMMAND 195 -#define TPM_TAG_RSP_COMMAND 196 -#define TPM_TAG_RSP_AUTH1_COMMAND 197 -#define TPM_TAG_RSP_AUTH2_COMMAND 198 +enum tpm_command_tags { + TPM_TAG_RQU_COMMAND = 193, + TPM_TAG_RQU_AUTH1_COMMAND = 194, + TPM_TAG_RQU_AUTH2_COMMAND = 195, + TPM_TAG_RSP_COMMAND = 196, + TPM_TAG_RSP_AUTH1_COMMAND = 197, + TPM_TAG_RSP_AUTH2_COMMAND = 198, +}; /* Command Ordinals */ -#define TPM_ORD_GETRANDOM 70 -#define TPM_ORD_OSAP 11 -#define TPM_ORD_OIAP 10 -#define TPM_ORD_SEAL 23 -#define TPM_ORD_UNSEAL 24 +enum tpm_command_ordinals { + TPM_ORD_CONTINUE_SELFTEST = 83, + TPM_ORD_GET_CAP = 101, + TPM_ORD_GET_RANDOM = 70, + TPM_ORD_PCR_EXTEND = 20, + TPM_ORD_PCR_READ = 21, + TPM_ORD_OSAP = 11, + TPM_ORD_OIAP = 10, + TPM_ORD_SAVESTATE = 152, + TPM_ORD_SEAL = 23, + TPM_ORD_STARTUP = 153, + TPM_ORD_UNSEAL = 24, +}; /* Other constants */ #define SRKHANDLE 0x40000000 #define TPM_NONCE_SIZE 20 +#define TPM_ST_CLEAR 1 #endif diff --git a/security/keys/trusted-keys/trusted_tpm1.c b/security/keys/trusted-keys/trusted_tpm1.c index 8f57c6111e7e..ae256c540ca0 100644 --- a/security/keys/trusted-keys/trusted_tpm1.c +++ b/security/keys/trusted-keys/trusted_tpm1.c @@ -18,7 +18,6 @@ #include #include #include -#include #include diff --git a/security/keys/trusted-keys/trusted_tpm2.c b/security/keys/trusted-keys/trusted_tpm2.c index 6340823f8b53..29d79c05ed6b 100644 --- a/security/keys/trusted-keys/trusted_tpm2.c +++ b/security/keys/trusted-keys/trusted_tpm2.c @@ -9,7 +9,6 @@ #include #include #include -#include #include #include From 94d48062eb9626244f6ddb5a682cdaae40540030 Mon Sep 17 00:00:00 2001 From: Ross Philipson Date: Sat, 11 Jul 2026 09:01:02 -0700 Subject: [PATCH 1167/1328] tpm: Move TPM1 specific definitions to the command header Gather all the TPM1 definitions and structures from the internal header file drivers/char/tpm/tpm.h into the command header. In addition, bring in the single RNG structure from tpm1-cmd.c. The definitions moved to these files correspond to the TCG specification for TPM 1 family: TPM 1.2 Main Specification - https://trustedcomputinggroup.org/resource/tpm-main-specification/ Co-developed-by: Daniel P. Smith Signed-off-by: Daniel P. Smith Co-developed-by: Alec Brown Signed-off-by: Alec Brown Signed-off-by: Ross Philipson Reviewed-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/tpm.h | 102 -------------------------------- drivers/char/tpm/tpm1-cmd.c | 5 -- include/linux/tpm_command.h | 115 ++++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 107 deletions(-) diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h index 87d68ddf270a..043d78a9617a 100644 --- a/drivers/char/tpm/tpm.h +++ b/drivers/char/tpm/tpm.h @@ -52,105 +52,9 @@ enum tpm_addr { TPM_ADDR = 0x4E, }; -#define TPM_WARN_RETRY 0x800 -#define TPM_WARN_DOING_SELFTEST 0x802 -#define TPM_ERR_DEACTIVATED 0x6 -#define TPM_ERR_DISABLED 0x7 -#define TPM_ERR_FAILEDSELFTEST 0x1C -#define TPM_ERR_INVALID_POSTINIT 38 - -#define TPM_TAG_RQU_COMMAND 193 - /* TPM2 specific constants. */ #define TPM2_SPACE_BUFFER_SIZE 16384 /* 16 kB */ -struct stclear_flags_t { - __be16 tag; - u8 deactivated; - u8 disableForceClear; - u8 physicalPresence; - u8 physicalPresenceLock; - u8 bGlobalLock; -} __packed; - -struct tpm1_version { - u8 major; - u8 minor; - u8 rev_major; - u8 rev_minor; -} __packed; - -struct tpm1_version2 { - __be16 tag; - struct tpm1_version version; -} __packed; - -struct timeout_t { - __be32 a; - __be32 b; - __be32 c; - __be32 d; -} __packed; - -struct duration_t { - __be32 tpm_short; - __be32 tpm_medium; - __be32 tpm_long; -} __packed; - -struct permanent_flags_t { - __be16 tag; - u8 disable; - u8 ownership; - u8 deactivated; - u8 readPubek; - u8 disableOwnerClear; - u8 allowMaintenance; - u8 physicalPresenceLifetimeLock; - u8 physicalPresenceHWEnable; - u8 physicalPresenceCMDEnable; - u8 CEKPUsed; - u8 TPMpost; - u8 TPMpostLock; - u8 FIPS; - u8 operator; - u8 enableRevokeEK; - u8 nvLocked; - u8 readSRKPub; - u8 tpmEstablished; - u8 maintenanceDone; - u8 disableFullDALogicInfo; -} __packed; - -typedef union { - struct permanent_flags_t perm_flags; - struct stclear_flags_t stclear_flags; - __u8 owned; - __be32 num_pcrs; - struct tpm1_version version1; - struct tpm1_version2 version2; - __be32 manufacturer_id; - struct timeout_t timeout; - struct duration_t duration; -} cap_t; - -enum tpm_capabilities { - TPM_CAP_FLAG = 4, - TPM_CAP_PROP = 5, - TPM_CAP_VERSION_1_1 = 0x06, - TPM_CAP_VERSION_1_2 = 0x1A, -}; - -enum tpm_sub_capabilities { - TPM_CAP_PROP_PCR = 0x101, - TPM_CAP_PROP_MANUFACTURER = 0x103, - TPM_CAP_FLAG_PERM = 0x108, - TPM_CAP_FLAG_VOL = 0x109, - TPM_CAP_PROP_OWNER = 0x111, - TPM_CAP_PROP_TIS_TIMEOUT = 0x115, - TPM_CAP_PROP_TIS_DURATION = 0x120, -}; - enum tpm2_pt_props { TPM2_PT_NONE = 0x00000000, TPM2_PT_GROUP = 0x00000100, @@ -225,12 +129,6 @@ enum tpm2_pt_props { TPM2_PT_AUDIT_COUNTER_1 = TPM2_PT_VAR + 20, }; -/* 128 bytes is an arbitrary cap. This could be as large as TPM_BUFSIZE - 18 - * bytes, but 128 is still a relatively large number of random bytes and - * anything much bigger causes users of struct tpm_cmd_t to start getting - * compiler warnings about stack frame size. */ -#define TPM_MAX_RNG_DATA 128 - extern const struct class tpm_class; extern const struct class tpmrm_class; extern dev_t tpm_devt; diff --git a/drivers/char/tpm/tpm1-cmd.c b/drivers/char/tpm/tpm1-cmd.c index 664ca1fff2e8..96f189b5fd6f 100644 --- a/drivers/char/tpm/tpm1-cmd.c +++ b/drivers/char/tpm/tpm1-cmd.c @@ -504,11 +504,6 @@ ssize_t tpm1_getcap(struct tpm_chip *chip, u32 subcap_id, cap_t *cap, } EXPORT_SYMBOL_GPL(tpm1_getcap); -struct tpm1_get_random_out { - __be32 rng_data_len; - u8 rng_data[TPM_MAX_RNG_DATA]; -} __packed; - /** * tpm1_get_random() - get random bytes from the TPM's RNG * @chip: a &struct tpm_chip instance diff --git a/include/linux/tpm_command.h b/include/linux/tpm_command.h index 174b043d8bbc..30d01953a6f8 100644 --- a/include/linux/tpm_command.h +++ b/include/linux/tpm_command.h @@ -36,6 +36,121 @@ enum tpm_command_ordinals { TPM_ORD_UNSEAL = 24, }; +enum tpm_capabilities { + TPM_CAP_FLAG = 4, + TPM_CAP_PROP = 5, + TPM_CAP_VERSION_1_1 = 0x06, + TPM_CAP_VERSION_1_2 = 0x1A, +}; + +enum tpm_sub_capabilities { + TPM_CAP_PROP_PCR = 0x101, + TPM_CAP_PROP_MANUFACTURER = 0x103, + TPM_CAP_FLAG_PERM = 0x108, + TPM_CAP_FLAG_VOL = 0x109, + TPM_CAP_PROP_OWNER = 0x111, + TPM_CAP_PROP_TIS_TIMEOUT = 0x115, + TPM_CAP_PROP_TIS_DURATION = 0x120, +}; + +/* Return Codes */ +enum tpm_return_codes { + TPM_BASE_MASK = 0, + TPM_NON_FATAL_MASK = 0x00000800, + TPM_SUCCESS = TPM_BASE_MASK + 0, + TPM_ERR_DEACTIVATED = TPM_BASE_MASK + 6, + TPM_ERR_DISABLED = TPM_BASE_MASK + 7, + TPM_ERR_FAIL = TPM_BASE_MASK + 9, + TPM_ERR_FAILEDSELFTEST = TPM_BASE_MASK + 28, + TPM_ERR_INVALID_POSTINIT = TPM_BASE_MASK + 38, + TPM_ERR_INVALID_FAMILY = TPM_BASE_MASK + 55, + TPM_WARN_RETRY = TPM_BASE_MASK + TPM_NON_FATAL_MASK + 0, + TPM_WARN_DOING_SELFTEST = TPM_BASE_MASK + TPM_NON_FATAL_MASK + 2, +}; + +struct stclear_flags_t { + __be16 tag; + u8 deactivated; + u8 disableForceClear; + u8 physicalPresence; + u8 physicalPresenceLock; + u8 bGlobalLock; +} __packed; + +struct tpm1_version { + u8 major; + u8 minor; + u8 rev_major; + u8 rev_minor; +} __packed; + +struct tpm1_version2 { + __be16 tag; + struct tpm1_version version; +} __packed; + +struct timeout_t { + __be32 a; + __be32 b; + __be32 c; + __be32 d; +} __packed; + +struct duration_t { + __be32 tpm_short; + __be32 tpm_medium; + __be32 tpm_long; +} __packed; + +struct permanent_flags_t { + __be16 tag; + u8 disable; + u8 ownership; + u8 deactivated; + u8 readPubek; + u8 disableOwnerClear; + u8 allowMaintenance; + u8 physicalPresenceLifetimeLock; + u8 physicalPresenceHWEnable; + u8 physicalPresenceCMDEnable; + u8 CEKPUsed; + u8 TPMpost; + u8 TPMpostLock; + u8 FIPS; + u8 operator; + u8 enableRevokeEK; + u8 nvLocked; + u8 readSRKPub; + u8 tpmEstablished; + u8 maintenanceDone; + u8 disableFullDALogicInfo; +} __packed; + +typedef union { + struct permanent_flags_t perm_flags; + struct stclear_flags_t stclear_flags; + __u8 owned; + __be32 num_pcrs; + struct tpm1_version version1; + struct tpm1_version2 version2; + __be32 manufacturer_id; + struct timeout_t timeout; + struct duration_t duration; +} cap_t; + +/* + * 128 bytes is an arbitrary cap. This could be as large as TPM_BUFSIZE - 18 + * bytes, but 128 is still a relatively large number of random bytes and + * anything much bigger causes users of struct tpm_cmd_t to start getting + * compiler warnings about stack frame size. + */ +#define TPM_MAX_RNG_DATA 128 + +struct tpm1_get_random_out { + __be32 rng_data_len; + u8 rng_data[TPM_MAX_RNG_DATA]; +} __packed; + /* Other constants */ #define SRKHANDLE 0x40000000 #define TPM_NONCE_SIZE 20 From 7a5ec92813bc794ab3674aab993fe8f8b5cee9f0 Mon Sep 17 00:00:00 2001 From: Ross Philipson Date: Sat, 11 Jul 2026 09:01:03 -0700 Subject: [PATCH 1168/1328] tpm: Move TPM2 specific definitions to the command header Gather all the TPM2 definitions and structures in the internal header file drivers/char/tpm/tpm.h into the command header, including: - Command codes, return codes and definitions from the public and internal tpm.h files. - Structures defined in numerous TPM driver C modules. The definitions moved to these files correspond to the TCG specification for TPM 2 family: TPM 2.0 Library - https://trustedcomputinggroup.org/resource/tpm-library-specification/ Co-developed-by: Daniel P. Smith Signed-off-by: Daniel P. Smith Co-developed-by: Alec Brown Signed-off-by: Alec Brown Signed-off-by: Ross Philipson Reviewed-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/tpm.h | 77 ---------- drivers/char/tpm/tpm2-cmd.c | 30 ---- drivers/char/tpm/tpm2-space.c | 13 -- include/linux/tpm.h | 145 ------------------ include/linux/tpm_command.h | 271 ++++++++++++++++++++++++++++++++++ 5 files changed, 271 insertions(+), 265 deletions(-) diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h index 043d78a9617a..680f89d9c9f9 100644 --- a/drivers/char/tpm/tpm.h +++ b/drivers/char/tpm/tpm.h @@ -52,83 +52,6 @@ enum tpm_addr { TPM_ADDR = 0x4E, }; -/* TPM2 specific constants. */ -#define TPM2_SPACE_BUFFER_SIZE 16384 /* 16 kB */ - -enum tpm2_pt_props { - TPM2_PT_NONE = 0x00000000, - TPM2_PT_GROUP = 0x00000100, - TPM2_PT_FIXED = TPM2_PT_GROUP * 1, - TPM2_PT_FAMILY_INDICATOR = TPM2_PT_FIXED + 0, - TPM2_PT_LEVEL = TPM2_PT_FIXED + 1, - TPM2_PT_REVISION = TPM2_PT_FIXED + 2, - TPM2_PT_DAY_OF_YEAR = TPM2_PT_FIXED + 3, - TPM2_PT_YEAR = TPM2_PT_FIXED + 4, - TPM2_PT_MANUFACTURER = TPM2_PT_FIXED + 5, - TPM2_PT_VENDOR_STRING_1 = TPM2_PT_FIXED + 6, - TPM2_PT_VENDOR_STRING_2 = TPM2_PT_FIXED + 7, - TPM2_PT_VENDOR_STRING_3 = TPM2_PT_FIXED + 8, - TPM2_PT_VENDOR_STRING_4 = TPM2_PT_FIXED + 9, - TPM2_PT_VENDOR_TPM_TYPE = TPM2_PT_FIXED + 10, - TPM2_PT_FIRMWARE_VERSION_1 = TPM2_PT_FIXED + 11, - TPM2_PT_FIRMWARE_VERSION_2 = TPM2_PT_FIXED + 12, - TPM2_PT_INPUT_BUFFER = TPM2_PT_FIXED + 13, - TPM2_PT_HR_TRANSIENT_MIN = TPM2_PT_FIXED + 14, - TPM2_PT_HR_PERSISTENT_MIN = TPM2_PT_FIXED + 15, - TPM2_PT_HR_LOADED_MIN = TPM2_PT_FIXED + 16, - TPM2_PT_ACTIVE_SESSIONS_MAX = TPM2_PT_FIXED + 17, - TPM2_PT_PCR_COUNT = TPM2_PT_FIXED + 18, - TPM2_PT_PCR_SELECT_MIN = TPM2_PT_FIXED + 19, - TPM2_PT_CONTEXT_GAP_MAX = TPM2_PT_FIXED + 20, - TPM2_PT_NV_COUNTERS_MAX = TPM2_PT_FIXED + 22, - TPM2_PT_NV_INDEX_MAX = TPM2_PT_FIXED + 23, - TPM2_PT_MEMORY = TPM2_PT_FIXED + 24, - TPM2_PT_CLOCK_UPDATE = TPM2_PT_FIXED + 25, - TPM2_PT_CONTEXT_HASH = TPM2_PT_FIXED + 26, - TPM2_PT_CONTEXT_SYM = TPM2_PT_FIXED + 27, - TPM2_PT_CONTEXT_SYM_SIZE = TPM2_PT_FIXED + 28, - TPM2_PT_ORDERLY_COUNT = TPM2_PT_FIXED + 29, - TPM2_PT_MAX_COMMAND_SIZE = TPM2_PT_FIXED + 30, - TPM2_PT_MAX_RESPONSE_SIZE = TPM2_PT_FIXED + 31, - TPM2_PT_MAX_DIGEST = TPM2_PT_FIXED + 32, - TPM2_PT_MAX_OBJECT_CONTEXT = TPM2_PT_FIXED + 33, - TPM2_PT_MAX_SESSION_CONTEXT = TPM2_PT_FIXED + 34, - TPM2_PT_PS_FAMILY_INDICATOR = TPM2_PT_FIXED + 35, - TPM2_PT_PS_LEVEL = TPM2_PT_FIXED + 36, - TPM2_PT_PS_REVISION = TPM2_PT_FIXED + 37, - TPM2_PT_PS_DAY_OF_YEAR = TPM2_PT_FIXED + 38, - TPM2_PT_PS_YEAR = TPM2_PT_FIXED + 39, - TPM2_PT_SPLIT_MAX = TPM2_PT_FIXED + 40, - TPM2_PT_TOTAL_COMMANDS = TPM2_PT_FIXED + 41, - TPM2_PT_LIBRARY_COMMANDS = TPM2_PT_FIXED + 42, - TPM2_PT_VENDOR_COMMANDS = TPM2_PT_FIXED + 43, - TPM2_PT_NV_BUFFER_MAX = TPM2_PT_FIXED + 44, - TPM2_PT_MODES = TPM2_PT_FIXED + 45, - TPM2_PT_MAX_CAP_BUFFER = TPM2_PT_FIXED + 46, - TPM2_PT_VAR = TPM2_PT_GROUP * 2, - TPM2_PT_PERMANENT = TPM2_PT_VAR + 0, - TPM2_PT_STARTUP_CLEAR = TPM2_PT_VAR + 1, - TPM2_PT_HR_NV_INDEX = TPM2_PT_VAR + 2, - TPM2_PT_HR_LOADED = TPM2_PT_VAR + 3, - TPM2_PT_HR_LOADED_AVAIL = TPM2_PT_VAR + 4, - TPM2_PT_HR_ACTIVE = TPM2_PT_VAR + 5, - TPM2_PT_HR_ACTIVE_AVAIL = TPM2_PT_VAR + 6, - TPM2_PT_HR_TRANSIENT_AVAIL = TPM2_PT_VAR + 7, - TPM2_PT_HR_PERSISTENT = TPM2_PT_VAR + 8, - TPM2_PT_HR_PERSISTENT_AVAIL = TPM2_PT_VAR + 9, - TPM2_PT_NV_COUNTERS = TPM2_PT_VAR + 10, - TPM2_PT_NV_COUNTERS_AVAIL = TPM2_PT_VAR + 11, - TPM2_PT_ALGORITHM_SET = TPM2_PT_VAR + 12, - TPM2_PT_LOADED_CURVES = TPM2_PT_VAR + 13, - TPM2_PT_LOCKOUT_COUNTER = TPM2_PT_VAR + 14, - TPM2_PT_MAX_AUTH_FAIL = TPM2_PT_VAR + 15, - TPM2_PT_LOCKOUT_INTERVAL = TPM2_PT_VAR + 16, - TPM2_PT_LOCKOUT_RECOVERY = TPM2_PT_VAR + 17, - TPM2_PT_NV_WRITE_RECOVERY = TPM2_PT_VAR + 18, - TPM2_PT_AUDIT_COUNTER_0 = TPM2_PT_VAR + 19, - TPM2_PT_AUDIT_COUNTER_1 = TPM2_PT_VAR + 20, -}; - extern const struct class tpm_class; extern const struct class tpmrm_class; extern dev_t tpm_devt; diff --git a/drivers/char/tpm/tpm2-cmd.c b/drivers/char/tpm/tpm2-cmd.c index 52ee350da867..280f870e6517 100644 --- a/drivers/char/tpm/tpm2-cmd.c +++ b/drivers/char/tpm/tpm2-cmd.c @@ -94,17 +94,6 @@ unsigned long tpm2_calc_ordinal_duration(u32 ordinal) return msecs_to_jiffies(TPM2_DURATION_DEFAULT); } -struct tpm2_pcr_read_out { - __be32 update_cnt; - __be32 pcr_selects_cnt; - __be16 hash_alg; - u8 pcr_select_size; - u8 pcr_select[TPM2_PCR_SELECT_MIN]; - __be32 digests_cnt; - __be16 digest_size; - u8 digest[]; -} __packed; - /** * tpm2_pcr_read() - read a PCR value * @chip: TPM chip to use. @@ -238,11 +227,6 @@ int tpm2_pcr_extend(struct tpm_chip *chip, u32 pcr_idx, return rc; } -struct tpm2_get_random_out { - __be16 size; - u8 buffer[TPM_MAX_RNG_DATA]; -} __packed; - /** * tpm2_get_random() - get random bytes from the TPM RNG * @@ -364,14 +348,6 @@ void tpm2_flush_context(struct tpm_chip *chip, u32 handle) } EXPORT_SYMBOL_GPL(tpm2_flush_context); -struct tpm2_get_cap_out { - u8 more_data; - __be32 subcap_id; - __be32 property_cnt; - __be32 property_id; - __be32 value; -} __packed; - /** * tpm2_get_tpm_pt() - get value of a TPM_CAP_TPM_PROPERTIES type property * @chip: a &tpm_chip instance @@ -539,12 +515,6 @@ static int tpm2_init_bank_info(struct tpm_chip *chip, u32 bank_index) return tpm2_pcr_read(chip, 0, &digest, &bank->digest_size); } -struct tpm2_pcr_selection { - __be16 hash_alg; - u8 size_of_select; - u8 pcr_select[3]; -} __packed; - ssize_t tpm2_get_pcr_allocation(struct tpm_chip *chip) { struct tpm2_pcr_selection pcr_selection; diff --git a/drivers/char/tpm/tpm2-space.c b/drivers/char/tpm/tpm2-space.c index 60354cd53b5c..7c1c0a174a2b 100644 --- a/drivers/char/tpm/tpm2-space.c +++ b/drivers/char/tpm/tpm2-space.c @@ -15,19 +15,6 @@ #include #include "tpm.h" -enum tpm2_handle_types { - TPM2_HT_HMAC_SESSION = 0x02000000, - TPM2_HT_POLICY_SESSION = 0x03000000, - TPM2_HT_TRANSIENT = 0x80000000, -}; - -struct tpm2_context { - __be64 sequence; - __be32 saved_handle; - __be32 hierarchy; - __be16 blob_size; -} __packed; - static void tpm2_flush_sessions(struct tpm_chip *chip, struct tpm_space *space) { int i; diff --git a/include/linux/tpm.h b/include/linux/tpm.h index 1846d5485a2c..8551b24c2bff 100644 --- a/include/linux/tpm.h +++ b/include/linux/tpm.h @@ -38,12 +38,6 @@ struct trusted_key_options; /* opaque structure, holds auth session parameters like the session key */ struct tpm2_auth; -enum tpm2_session_types { - TPM2_SE_HMAC = 0x00, - TPM2_SE_POLICY = 0x01, - TPM2_SE_TRIAL = 0x02, -}; - /* if you add a new hash to this, increment TPM_MAX_HASHES below */ enum tpm_algorithms { TPM_ALG_ERROR = 0x0000, @@ -65,11 +59,6 @@ enum tpm_algorithms { */ #define TPM_MAX_HASHES 5 -enum tpm2_curves { - TPM2_ECC_NONE = 0x0000, - TPM2_ECC_NIST_P256 = 0x0003, -}; - struct tpm_digest { u16 alg_id; u8 digest[TPM2_MAX_DIGEST_SIZE]; @@ -222,122 +211,11 @@ struct tpm_chip { #define TPM_HEADER_SIZE 10 -enum tpm2_const { - TPM2_PLATFORM_PCR = 24, - TPM2_PCR_SELECT_MIN = ((TPM2_PLATFORM_PCR + 7) / 8), -}; - -enum tpm2_timeouts { - TPM2_TIMEOUT_A = 750, - TPM2_TIMEOUT_B = 4000, - TPM2_TIMEOUT_C = 200, - TPM2_TIMEOUT_D = 30, -}; - -enum tpm2_durations { - TPM2_DURATION_SHORT = 20, - TPM2_DURATION_LONG = 2000, - TPM2_DURATION_DEFAULT = 120000, -}; - -enum tpm2_structures { - TPM2_ST_NO_SESSIONS = 0x8001, - TPM2_ST_SESSIONS = 0x8002, - TPM2_ST_CREATION = 0x8021, -}; - -/* Indicates from what layer of the software stack the error comes from */ -#define TSS2_RC_LAYER_SHIFT 16 -#define TSS2_RESMGR_TPM_RC_LAYER (11 << TSS2_RC_LAYER_SHIFT) - -enum tpm2_return_codes { - TPM2_RC_SUCCESS = 0x0000, - TPM2_RC_HASH = 0x0083, /* RC_FMT1 */ - TPM2_RC_HANDLE = 0x008B, - TPM2_RC_INTEGRITY = 0x009F, - TPM2_RC_INITIALIZE = 0x0100, /* RC_VER1 */ - TPM2_RC_FAILURE = 0x0101, - TPM2_RC_DISABLED = 0x0120, - TPM2_RC_UPGRADE = 0x012D, - TPM2_RC_COMMAND_CODE = 0x0143, - TPM2_RC_TESTING = 0x090A, /* RC_WARN */ - TPM2_RC_REFERENCE_H0 = 0x0910, - TPM2_RC_RETRY = 0x0922, - TPM2_RC_SESSION_MEMORY = 0x0903, -}; - -enum tpm2_command_codes { - TPM2_CC_FIRST = 0x011F, - TPM2_CC_HIERARCHY_CONTROL = 0x0121, - TPM2_CC_HIERARCHY_CHANGE_AUTH = 0x0129, - TPM2_CC_CREATE_PRIMARY = 0x0131, - TPM2_CC_SEQUENCE_COMPLETE = 0x013E, - TPM2_CC_SELF_TEST = 0x0143, - TPM2_CC_STARTUP = 0x0144, - TPM2_CC_SHUTDOWN = 0x0145, - TPM2_CC_NV_READ = 0x014E, - TPM2_CC_CREATE = 0x0153, - TPM2_CC_LOAD = 0x0157, - TPM2_CC_SEQUENCE_UPDATE = 0x015C, - TPM2_CC_UNSEAL = 0x015E, - TPM2_CC_CONTEXT_LOAD = 0x0161, - TPM2_CC_CONTEXT_SAVE = 0x0162, - TPM2_CC_FLUSH_CONTEXT = 0x0165, - TPM2_CC_READ_PUBLIC = 0x0173, - TPM2_CC_START_AUTH_SESS = 0x0176, - TPM2_CC_VERIFY_SIGNATURE = 0x0177, - TPM2_CC_GET_CAPABILITY = 0x017A, - TPM2_CC_GET_RANDOM = 0x017B, - TPM2_CC_PCR_READ = 0x017E, - TPM2_CC_PCR_EXTEND = 0x0182, - TPM2_CC_EVENT_SEQUENCE_COMPLETE = 0x0185, - TPM2_CC_HASH_SEQUENCE_START = 0x0186, - TPM2_CC_CREATE_LOADED = 0x0191, - TPM2_CC_LAST = 0x0193, /* Spec 1.36 */ -}; - -enum tpm2_permanent_handles { - TPM2_RH_NULL = 0x40000007, - TPM2_RS_PW = 0x40000009, -}; - -/* Most Significant Octet for key types */ -enum tpm2_mso_type { - TPM2_MSO_NVRAM = 0x01, - TPM2_MSO_SESSION = 0x02, - TPM2_MSO_POLICY = 0x03, - TPM2_MSO_PERMANENT = 0x40, - TPM2_MSO_VOLATILE = 0x80, - TPM2_MSO_PERSISTENT = 0x81, -}; - static inline enum tpm2_mso_type tpm2_handle_mso(u32 handle) { return handle >> 24; } -enum tpm2_capabilities { - TPM2_CAP_HANDLES = 1, - TPM2_CAP_COMMANDS = 2, - TPM2_CAP_PCRS = 5, - TPM2_CAP_TPM_PROPERTIES = 6, -}; - -enum tpm2_properties { - TPM_PT_TOTAL_COMMANDS = 0x0129, -}; - -enum tpm2_startup_types { - TPM2_SU_CLEAR = 0x0000, - TPM2_SU_STATE = 0x0001, -}; - -enum tpm2_cc_attrs { - TPM2_CC_ATTR_CHANDLES = 25, - TPM2_CC_ATTR_RHANDLE = 28, - TPM2_CC_ATTR_VENDOR = 29, -}; - #define TPM_VID_INTEL 0x8086 #define TPM_VID_WINBOND 0x1050 #define TPM_VID_STM 0x104A @@ -389,29 +267,6 @@ struct tpm_buf { u8 handles; }; -enum tpm2_object_attributes { - TPM2_OA_FIXED_TPM = BIT(1), - TPM2_OA_ST_CLEAR = BIT(2), - TPM2_OA_FIXED_PARENT = BIT(4), - TPM2_OA_SENSITIVE_DATA_ORIGIN = BIT(5), - TPM2_OA_USER_WITH_AUTH = BIT(6), - TPM2_OA_ADMIN_WITH_POLICY = BIT(7), - TPM2_OA_NO_DA = BIT(10), - TPM2_OA_ENCRYPTED_DUPLICATION = BIT(11), - TPM2_OA_RESTRICTED = BIT(16), - TPM2_OA_DECRYPT = BIT(17), - TPM2_OA_SIGN = BIT(18), -}; - -enum tpm2_session_attributes { - TPM2_SA_CONTINUE_SESSION = BIT(0), - TPM2_SA_AUDIT_EXCLUSIVE = BIT(1), - TPM2_SA_AUDIT_RESET = BIT(3), - TPM2_SA_DECRYPT = BIT(5), - TPM2_SA_ENCRYPT = BIT(6), - TPM2_SA_AUDIT = BIT(7), -}; - struct tpm2_hash { unsigned int crypto_id; unsigned int tpm_id; diff --git a/include/linux/tpm_command.h b/include/linux/tpm_command.h index 30d01953a6f8..9dd903dd6b5c 100644 --- a/include/linux/tpm_command.h +++ b/include/linux/tpm_command.h @@ -156,4 +156,275 @@ struct tpm1_get_random_out { #define TPM_NONCE_SIZE 20 #define TPM_ST_CLEAR 1 +/* + * == TPM 2 Family Chips == + * + * TPM 2.0 Library + * https://trustedcomputinggroup.org/resource/tpm-library-specification/ + */ + +/* TPM2 specific constants. */ +#define TPM2_SPACE_BUFFER_SIZE 16384 /* 16 kB */ + +enum tpm2_session_types { + TPM2_SE_HMAC = 0x00, + TPM2_SE_POLICY = 0x01, + TPM2_SE_TRIAL = 0x02, +}; + +enum tpm2_timeouts { + TPM2_TIMEOUT_A = 750, + TPM2_TIMEOUT_B = 4000, + TPM2_TIMEOUT_C = 200, + TPM2_TIMEOUT_D = 30, + TPM2_DURATION_SHORT = 20, + TPM2_DURATION_MEDIUM = 750, + TPM2_DURATION_LONG = 2000, + TPM2_DURATION_LONG_LONG = 300000, + TPM2_DURATION_DEFAULT = 120000, +}; + +enum tpm2_structures { + TPM2_ST_NO_SESSIONS = 0x8001, + TPM2_ST_SESSIONS = 0x8002, + TPM2_ST_CREATION = 0x8021, +}; + +/* Indicates from what layer of the software stack the error comes from */ +#define TSS2_RC_LAYER_SHIFT 16 +#define TSS2_RESMGR_TPM_RC_LAYER (11 << TSS2_RC_LAYER_SHIFT) + +enum tpm2_return_codes { + TPM2_RC_SUCCESS = 0x0000, + TPM2_RC_HASH = 0x0083, /* RC_FMT1 */ + TPM2_RC_HANDLE = 0x008B, + TPM2_RC_INTEGRITY = 0x009F, + TPM2_RC_INITIALIZE = 0x0100, /* RC_VER1 */ + TPM2_RC_FAILURE = 0x0101, + TPM2_RC_DISABLED = 0x0120, + TPM2_RC_UPGRADE = 0x012D, + TPM2_RC_COMMAND_CODE = 0x0143, + TPM2_RC_TESTING = 0x090A, /* RC_WARN */ + TPM2_RC_REFERENCE_H0 = 0x0910, + TPM2_RC_RETRY = 0x0922, + TPM2_RC_SESSION_MEMORY = 0x0903, +}; + +enum tpm2_command_codes { + TPM2_CC_FIRST = 0x011F, + TPM2_CC_HIERARCHY_CONTROL = 0x0121, + TPM2_CC_HIERARCHY_CHANGE_AUTH = 0x0129, + TPM2_CC_CREATE_PRIMARY = 0x0131, + TPM2_CC_SEQUENCE_COMPLETE = 0x013E, + TPM2_CC_SELF_TEST = 0x0143, + TPM2_CC_STARTUP = 0x0144, + TPM2_CC_SHUTDOWN = 0x0145, + TPM2_CC_NV_READ = 0x014E, + TPM2_CC_CREATE = 0x0153, + TPM2_CC_LOAD = 0x0157, + TPM2_CC_SEQUENCE_UPDATE = 0x015C, + TPM2_CC_UNSEAL = 0x015E, + TPM2_CC_CONTEXT_LOAD = 0x0161, + TPM2_CC_CONTEXT_SAVE = 0x0162, + TPM2_CC_FLUSH_CONTEXT = 0x0165, + TPM2_CC_READ_PUBLIC = 0x0173, + TPM2_CC_START_AUTH_SESS = 0x0176, + TPM2_CC_VERIFY_SIGNATURE = 0x0177, + TPM2_CC_GET_CAPABILITY = 0x017A, + TPM2_CC_GET_RANDOM = 0x017B, + TPM2_CC_PCR_READ = 0x017E, + TPM2_CC_PCR_EXTEND = 0x0182, + TPM2_CC_EVENT_SEQUENCE_COMPLETE = 0x0185, + TPM2_CC_HASH_SEQUENCE_START = 0x0186, + TPM2_CC_CREATE_LOADED = 0x0191, + TPM2_CC_LAST = 0x0193, /* Spec 1.36 */ +}; + +enum tpm2_capabilities { + TPM2_CAP_HANDLES = 1, + TPM2_CAP_COMMANDS = 2, + TPM2_CAP_PCRS = 5, + TPM2_CAP_TPM_PROPERTIES = 6, +}; + +enum tpm2_properties { + TPM_PT_TOTAL_COMMANDS = 0x0129, +}; + +enum tpm2_startup_types { + TPM2_SU_CLEAR = 0x0000, + TPM2_SU_STATE = 0x0001, +}; + +enum tpm2_cc_attrs { + TPM2_CC_ATTR_CHANDLES = 25, + TPM2_CC_ATTR_RHANDLE = 28, + TPM2_CC_ATTR_VENDOR = 29, +}; + +enum tpm2_permanent_handles { + TPM2_RH_NULL = 0x40000007, + TPM2_RS_PW = 0x40000009, +}; + +/* Most Significant Octet for key types */ +enum tpm2_mso_type { + TPM2_MSO_NVRAM = 0x01, + TPM2_MSO_SESSION = 0x02, + TPM2_MSO_POLICY = 0x03, + TPM2_MSO_PERMANENT = 0x40, + TPM2_MSO_VOLATILE = 0x80, + TPM2_MSO_PERSISTENT = 0x81, +}; + +enum tpm2_curves { + TPM2_ECC_NONE = 0x0000, + TPM2_ECC_NIST_P256 = 0x0003, +}; + +enum tpm2_object_attributes { + TPM2_OA_FIXED_TPM = BIT(1), + TPM2_OA_ST_CLEAR = BIT(2), + TPM2_OA_FIXED_PARENT = BIT(4), + TPM2_OA_SENSITIVE_DATA_ORIGIN = BIT(5), + TPM2_OA_USER_WITH_AUTH = BIT(6), + TPM2_OA_ADMIN_WITH_POLICY = BIT(7), + TPM2_OA_NO_DA = BIT(10), + TPM2_OA_ENCRYPTED_DUPLICATION = BIT(11), + TPM2_OA_RESTRICTED = BIT(16), + TPM2_OA_DECRYPT = BIT(17), + TPM2_OA_SIGN = BIT(18), +}; + +enum tpm2_session_attributes { + TPM2_SA_CONTINUE_SESSION = BIT(0), + TPM2_SA_AUDIT_EXCLUSIVE = BIT(1), + TPM2_SA_AUDIT_RESET = BIT(3), + TPM2_SA_DECRYPT = BIT(5), + TPM2_SA_ENCRYPT = BIT(6), + TPM2_SA_AUDIT = BIT(7), +}; + +enum tpm2_pcr_select { + TPM2_PLATFORM_PCR = 24, + TPM2_PCR_SELECT_MIN = ((TPM2_PLATFORM_PCR + 7) / 8), +}; + +enum tpm2_handle_types { + TPM2_HT_HMAC_SESSION = 0x02000000, + TPM2_HT_POLICY_SESSION = 0x03000000, + TPM2_HT_TRANSIENT = 0x80000000, +}; + +enum tpm2_pt_props { + TPM2_PT_NONE = 0x00000000, + TPM2_PT_GROUP = 0x00000100, + TPM2_PT_FIXED = TPM2_PT_GROUP * 1, + TPM2_PT_FAMILY_INDICATOR = TPM2_PT_FIXED + 0, + TPM2_PT_LEVEL = TPM2_PT_FIXED + 1, + TPM2_PT_REVISION = TPM2_PT_FIXED + 2, + TPM2_PT_DAY_OF_YEAR = TPM2_PT_FIXED + 3, + TPM2_PT_YEAR = TPM2_PT_FIXED + 4, + TPM2_PT_MANUFACTURER = TPM2_PT_FIXED + 5, + TPM2_PT_VENDOR_STRING_1 = TPM2_PT_FIXED + 6, + TPM2_PT_VENDOR_STRING_2 = TPM2_PT_FIXED + 7, + TPM2_PT_VENDOR_STRING_3 = TPM2_PT_FIXED + 8, + TPM2_PT_VENDOR_STRING_4 = TPM2_PT_FIXED + 9, + TPM2_PT_VENDOR_TPM_TYPE = TPM2_PT_FIXED + 10, + TPM2_PT_FIRMWARE_VERSION_1 = TPM2_PT_FIXED + 11, + TPM2_PT_FIRMWARE_VERSION_2 = TPM2_PT_FIXED + 12, + TPM2_PT_INPUT_BUFFER = TPM2_PT_FIXED + 13, + TPM2_PT_HR_TRANSIENT_MIN = TPM2_PT_FIXED + 14, + TPM2_PT_HR_PERSISTENT_MIN = TPM2_PT_FIXED + 15, + TPM2_PT_HR_LOADED_MIN = TPM2_PT_FIXED + 16, + TPM2_PT_ACTIVE_SESSIONS_MAX = TPM2_PT_FIXED + 17, + TPM2_PT_PCR_COUNT = TPM2_PT_FIXED + 18, + TPM2_PT_PCR_SELECT_MIN = TPM2_PT_FIXED + 19, + TPM2_PT_CONTEXT_GAP_MAX = TPM2_PT_FIXED + 20, + TPM2_PT_NV_COUNTERS_MAX = TPM2_PT_FIXED + 22, + TPM2_PT_NV_INDEX_MAX = TPM2_PT_FIXED + 23, + TPM2_PT_MEMORY = TPM2_PT_FIXED + 24, + TPM2_PT_CLOCK_UPDATE = TPM2_PT_FIXED + 25, + TPM2_PT_CONTEXT_HASH = TPM2_PT_FIXED + 26, + TPM2_PT_CONTEXT_SYM = TPM2_PT_FIXED + 27, + TPM2_PT_CONTEXT_SYM_SIZE = TPM2_PT_FIXED + 28, + TPM2_PT_ORDERLY_COUNT = TPM2_PT_FIXED + 29, + TPM2_PT_MAX_COMMAND_SIZE = TPM2_PT_FIXED + 30, + TPM2_PT_MAX_RESPONSE_SIZE = TPM2_PT_FIXED + 31, + TPM2_PT_MAX_DIGEST = TPM2_PT_FIXED + 32, + TPM2_PT_MAX_OBJECT_CONTEXT = TPM2_PT_FIXED + 33, + TPM2_PT_MAX_SESSION_CONTEXT = TPM2_PT_FIXED + 34, + TPM2_PT_PS_FAMILY_INDICATOR = TPM2_PT_FIXED + 35, + TPM2_PT_PS_LEVEL = TPM2_PT_FIXED + 36, + TPM2_PT_PS_REVISION = TPM2_PT_FIXED + 37, + TPM2_PT_PS_DAY_OF_YEAR = TPM2_PT_FIXED + 38, + TPM2_PT_PS_YEAR = TPM2_PT_FIXED + 39, + TPM2_PT_SPLIT_MAX = TPM2_PT_FIXED + 40, + TPM2_PT_TOTAL_COMMANDS = TPM2_PT_FIXED + 41, + TPM2_PT_LIBRARY_COMMANDS = TPM2_PT_FIXED + 42, + TPM2_PT_VENDOR_COMMANDS = TPM2_PT_FIXED + 43, + TPM2_PT_NV_BUFFER_MAX = TPM2_PT_FIXED + 44, + TPM2_PT_MODES = TPM2_PT_FIXED + 45, + TPM2_PT_MAX_CAP_BUFFER = TPM2_PT_FIXED + 46, + TPM2_PT_VAR = TPM2_PT_GROUP * 2, + TPM2_PT_PERMANENT = TPM2_PT_VAR + 0, + TPM2_PT_STARTUP_CLEAR = TPM2_PT_VAR + 1, + TPM2_PT_HR_NV_INDEX = TPM2_PT_VAR + 2, + TPM2_PT_HR_LOADED = TPM2_PT_VAR + 3, + TPM2_PT_HR_LOADED_AVAIL = TPM2_PT_VAR + 4, + TPM2_PT_HR_ACTIVE = TPM2_PT_VAR + 5, + TPM2_PT_HR_ACTIVE_AVAIL = TPM2_PT_VAR + 6, + TPM2_PT_HR_TRANSIENT_AVAIL = TPM2_PT_VAR + 7, + TPM2_PT_HR_PERSISTENT = TPM2_PT_VAR + 8, + TPM2_PT_HR_PERSISTENT_AVAIL = TPM2_PT_VAR + 9, + TPM2_PT_NV_COUNTERS = TPM2_PT_VAR + 10, + TPM2_PT_NV_COUNTERS_AVAIL = TPM2_PT_VAR + 11, + TPM2_PT_ALGORITHM_SET = TPM2_PT_VAR + 12, + TPM2_PT_LOADED_CURVES = TPM2_PT_VAR + 13, + TPM2_PT_LOCKOUT_COUNTER = TPM2_PT_VAR + 14, + TPM2_PT_MAX_AUTH_FAIL = TPM2_PT_VAR + 15, + TPM2_PT_LOCKOUT_INTERVAL = TPM2_PT_VAR + 16, + TPM2_PT_LOCKOUT_RECOVERY = TPM2_PT_VAR + 17, + TPM2_PT_NV_WRITE_RECOVERY = TPM2_PT_VAR + 18, + TPM2_PT_AUDIT_COUNTER_0 = TPM2_PT_VAR + 19, + TPM2_PT_AUDIT_COUNTER_1 = TPM2_PT_VAR + 20, +}; + +struct tpm2_pcr_read_out { + __be32 update_cnt; + __be32 pcr_selects_cnt; + __be16 hash_alg; + u8 pcr_select_size; + u8 pcr_select[TPM2_PCR_SELECT_MIN]; + __be32 digests_cnt; + __be16 digest_size; + u8 digest[]; +} __packed; + +struct tpm2_get_random_out { + __be16 size; + u8 buffer[TPM_MAX_RNG_DATA]; +} __packed; + +struct tpm2_get_cap_out { + u8 more_data; + __be32 subcap_id; + __be32 property_cnt; + __be32 property_id; + __be32 value; +} __packed; + +struct tpm2_pcr_selection { + __be16 hash_alg; + u8 size_of_select; + u8 pcr_select[3]; +} __packed; + +struct tpm2_context { + __be64 sequence; + __be32 saved_handle; + __be32 hierarchy; + __be16 blob_size; +} __packed; + #endif From 88c5a62c3af7ca6f36dc6ffc52bcfb29cd2990cb Mon Sep 17 00:00:00 2001 From: Ross Philipson Date: Sat, 11 Jul 2026 09:01:04 -0700 Subject: [PATCH 1169/1328] tpm: Move TPM common base definitions to the command header These are top level definitions shared by both TPM 1 and 2 family chips. This includes core definitions like TPM localities, common crypto algorithm IDs, and the base TPM command header. Co-developed-by: Daniel P. Smith Signed-off-by: Daniel P. Smith Co-developed-by: Alec Brown Signed-off-by: Alec Brown Signed-off-by: Ross Philipson Reviewed-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/tpm.h | 1 - include/linux/tpm.h | 50 +------------------- include/linux/tpm_command.h | 91 +++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 50 deletions(-) diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h index 680f89d9c9f9..fa554c5ad80b 100644 --- a/drivers/char/tpm/tpm.h +++ b/drivers/char/tpm/tpm.h @@ -33,7 +33,6 @@ #endif #define TPM_MINOR 224 /* officially assigned */ -#define TPM_BUFSIZE 4096 #define TPM_NUM_DEVICES 65536 #define TPM_RETRY 50 diff --git a/include/linux/tpm.h b/include/linux/tpm.h index 8551b24c2bff..3630b2ea6aef 100644 --- a/include/linux/tpm.h +++ b/include/linux/tpm.h @@ -27,49 +27,12 @@ #include -#define TPM_DIGEST_SIZE 20 /* Max TPM v1.2 PCR size */ - -#define TPM2_MAX_DIGEST_SIZE SHA512_DIGEST_SIZE -#define TPM2_MAX_PCR_BANKS 8 - struct tpm_chip; struct trusted_key_payload; struct trusted_key_options; /* opaque structure, holds auth session parameters like the session key */ struct tpm2_auth; -/* if you add a new hash to this, increment TPM_MAX_HASHES below */ -enum tpm_algorithms { - TPM_ALG_ERROR = 0x0000, - TPM_ALG_SHA1 = 0x0004, - TPM_ALG_AES = 0x0006, - TPM_ALG_KEYEDHASH = 0x0008, - TPM_ALG_SHA256 = 0x000B, - TPM_ALG_SHA384 = 0x000C, - TPM_ALG_SHA512 = 0x000D, - TPM_ALG_NULL = 0x0010, - TPM_ALG_SM3_256 = 0x0012, - TPM_ALG_ECC = 0x0023, - TPM_ALG_CFB = 0x0043, -}; - -/* - * maximum number of hashing algorithms a TPM can have. This is - * basically a count of every hash in tpm_algorithms above - */ -#define TPM_MAX_HASHES 5 - -struct tpm_digest { - u16 alg_id; - u8 digest[TPM2_MAX_DIGEST_SIZE]; -} __packed; - -struct tpm_bank_info { - u16 alg_id; - u16 digest_size; - u16 crypto_id; -}; - enum TPM_OPS_FLAGS { TPM_OPS_AUTO_STARTUP = BIT(0), }; @@ -127,7 +90,7 @@ struct tpm_chip_seqops { const struct seq_operations *seqops; }; -/* fixed define for the curve we use which is NIST_P256 */ +/* Fixed define for the curve we use which is NIST_P256 */ #define EC_PT_SZ 32 /* @@ -209,8 +172,6 @@ struct tpm_chip { #endif }; -#define TPM_HEADER_SIZE 10 - static inline enum tpm2_mso_type tpm2_handle_mso(u32 handle) { return handle >> 24; @@ -239,15 +200,6 @@ enum tpm_chip_flags { #define to_tpm_chip(d) container_of(d, struct tpm_chip, dev) -struct tpm_header { - __be16 tag; - __be32 length; - union { - __be32 ordinal; - __be32 return_code; - }; -} __packed; - enum tpm_buf_flags { /* the capacity exceeded: */ TPM_BUF_OVERFLOW = BIT(0), diff --git a/include/linux/tpm_command.h b/include/linux/tpm_command.h index 9dd903dd6b5c..fc446a1282e2 100644 --- a/include/linux/tpm_command.h +++ b/include/linux/tpm_command.h @@ -2,6 +2,8 @@ #ifndef __LINUX_TPM_COMMAND_H__ #define __LINUX_TPM_COMMAND_H__ +#include + /* * == TPM 1 Family Chips == * @@ -427,4 +429,93 @@ struct tpm2_context { __be16 blob_size; } __packed; +/* + * == TPM Common Defs == + */ + +#define TPM_DIGEST_SIZE 20 /* Max TPM v1.2 PCR size */ +#define TPM_BUFSIZE 4096 + +/* + * SHA-512 is, as of today, the largest digest in the TCG algorithm repository. + */ +#define TPM2_MAX_DIGEST_SIZE SHA512_DIGEST_SIZE + +/* + * A TPM name digest i.e., TPMT_HA, is a concatenation of TPM_ALG_ID of the + * name algorithm and hash of TPMT_PUBLIC. + */ +#define TPM2_MAX_NAME_SIZE (TPM2_MAX_DIGEST_SIZE + 2) + +/* + * Fixed define for the size of a name. This is actually HASHALG size + * plus 2, so 32 for SHA256 + */ +#define TPM2_NULL_NAME_SIZE 34 + +/* + * The maximum number of PCR banks. + */ +#define TPM2_MAX_PCR_BANKS 8 + +/* If you add a new hash to this, increment TPM_MAX_HASHES below */ +enum tpm_algorithms { + TPM_ALG_ERROR = 0x0000, + TPM_ALG_SHA1 = 0x0004, + TPM_ALG_AES = 0x0006, + TPM_ALG_KEYEDHASH = 0x0008, + TPM_ALG_SHA256 = 0x000B, + TPM_ALG_SHA384 = 0x000C, + TPM_ALG_SHA512 = 0x000D, + TPM_ALG_NULL = 0x0010, + TPM_ALG_SM3_256 = 0x0012, + TPM_ALG_ECC = 0x0023, + TPM_ALG_CFB = 0x0043, +}; + +/* + * The locality (0 - 4) for a TPM, as defined in section 3.2 of the + * Client Platform Profile Specification. + */ +enum tpm_localities { + TPM_LOCALITY_0 = 0, /* Static RTM */ + TPM_LOCALITY_1 = 1, /* Dynamic OS */ + TPM_LOCALITY_2 = 2, /* DRTM Environment */ + TPM_LOCALITY_3 = 3, /* Aux Components */ + TPM_LOCALITY_4 = 4, /* CPU DRTM Establishment */ + TPM_MAX_LOCALITY = TPM_LOCALITY_4 +}; + +/* + * Structure to represent active PCR algorithm banks usable by the + * TPM chip. + */ +struct tpm_bank_info { + u16 alg_id; + u16 digest_size; + u16 crypto_id; +}; + +/* + * Maximum number of hashing algorithms a TPM can have. This is + * basically a count of every hash in tpm_algorithms above + */ +#define TPM_MAX_HASHES 5 + +struct tpm_digest { + u16 alg_id; + u8 digest[TPM2_MAX_DIGEST_SIZE]; +} __packed; + +#define TPM_HEADER_SIZE 10 + +struct tpm_header { + __be16 tag; + __be32 length; + union { + __be32 ordinal; + __be32 return_code; + }; +} __packed; + #endif From 5467ade151973477742af922cc33bb30e816af42 Mon Sep 17 00:00:00 2001 From: Ross Philipson Date: Sat, 11 Jul 2026 09:01:05 -0700 Subject: [PATCH 1170/1328] tpm: Move platform specific definitions to the new PTP header These are definitions for TPM 2.0 interface and interactions with the platform as defined in the TCG specification: These definitions are located here in a separate file to avoid conflicts with vendor specific TIS/FIFO definition (e.g. STMicroelectronics, Infineon Technologies, etc). This allows the TCG defined TIS/FIFO interface to be in a public header while the former chip specific implementations contain their own definitions. TPM 1.x family chips that adhere to the TCG specifications use the TIS/FIFO interface as defined here. TCG PC Client Platform TPM Profile (PTP) Specification - https://trustedcomputinggroup.org/resource/pc-client-platform-tpm-profile-ptp-specification/ Co-developed-by: Daniel P. Smith Signed-off-by: Daniel P. Smith Co-developed-by: Alec Brown Signed-off-by: Alec Brown Signed-off-by: Ross Philipson Reviewed-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/tpm_tis_core.h | 64 +-------------- include/linux/tpm_ptp.h | 133 ++++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+), 63 deletions(-) create mode 100644 include/linux/tpm_ptp.h diff --git a/drivers/char/tpm/tpm_tis_core.h b/drivers/char/tpm/tpm_tis_core.h index aa6d78898ef3..d737b3d67411 100644 --- a/drivers/char/tpm/tpm_tis_core.h +++ b/drivers/char/tpm/tpm_tis_core.h @@ -19,71 +19,9 @@ #ifndef __TPM_TIS_CORE_H__ #define __TPM_TIS_CORE_H__ +#include #include "tpm.h" -enum tis_access { - TPM_ACCESS_VALID = 0x80, - TPM_ACCESS_ACTIVE_LOCALITY = 0x20, - TPM_ACCESS_REQUEST_PENDING = 0x04, - TPM_ACCESS_REQUEST_USE = 0x02, -}; - -enum tis_status { - TPM_STS_VALID = 0x80, - TPM_STS_COMMAND_READY = 0x40, - TPM_STS_GO = 0x20, - TPM_STS_DATA_AVAIL = 0x10, - TPM_STS_DATA_EXPECT = 0x08, - TPM_STS_RESPONSE_RETRY = 0x02, - TPM_STS_READ_ZERO = 0x23, /* bits that must be zero on read */ -}; - -enum tis_int_flags { - TPM_GLOBAL_INT_ENABLE = 0x80000000, - TPM_INTF_BURST_COUNT_STATIC = 0x100, - TPM_INTF_CMD_READY_INT = 0x080, - TPM_INTF_INT_EDGE_FALLING = 0x040, - TPM_INTF_INT_EDGE_RISING = 0x020, - TPM_INTF_INT_LEVEL_LOW = 0x010, - TPM_INTF_INT_LEVEL_HIGH = 0x008, - TPM_INTF_LOCALITY_CHANGE_INT = 0x004, - TPM_INTF_STS_VALID_INT = 0x002, - TPM_INTF_DATA_AVAIL_INT = 0x001, -}; - -enum tis_defaults { - TIS_MEM_LEN = 0x5000, - TIS_SHORT_TIMEOUT = 750, /* ms */ - TIS_LONG_TIMEOUT = 4000, /* 4 secs */ - TIS_TIMEOUT_MIN_ATML = 14700, /* usecs */ - TIS_TIMEOUT_MAX_ATML = 15000, /* usecs */ -}; - -/* Some timeout values are needed before it is known whether the chip is - * TPM 1.0 or TPM 2.0. - */ -#define TIS_TIMEOUT_A_MAX max_t(int, TIS_SHORT_TIMEOUT, TPM2_TIMEOUT_A) -#define TIS_TIMEOUT_B_MAX max_t(int, TIS_LONG_TIMEOUT, TPM2_TIMEOUT_B) -#define TIS_TIMEOUT_C_MAX max_t(int, TIS_SHORT_TIMEOUT, TPM2_TIMEOUT_C) -#define TIS_TIMEOUT_D_MAX max_t(int, TIS_SHORT_TIMEOUT, TPM2_TIMEOUT_D) - -#define TPM_ACCESS(l) (0x0000 | ((l) << 12)) -#define TPM_INT_ENABLE(l) (0x0008 | ((l) << 12)) -#define TPM_INT_VECTOR(l) (0x000C | ((l) << 12)) -#define TPM_INT_STATUS(l) (0x0010 | ((l) << 12)) -#define TPM_INTF_CAPS(l) (0x0014 | ((l) << 12)) -#define TPM_STS(l) (0x0018 | ((l) << 12)) -#define TPM_STS3(l) (0x001b | ((l) << 12)) -#define TPM_DATA_FIFO(l) (0x0024 | ((l) << 12)) - -#define TPM_DID_VID(l) (0x0F00 | ((l) << 12)) -#define TPM_RID(l) (0x0F04 | ((l) << 12)) - -#define LPC_CNTRL_OFFSET 0x84 -#define LPC_CLKRUN_EN (1 << 2) -#define INTEL_LEGACY_BLK_BASE_ADDR 0xFED08000 -#define ILB_REMAP_SIZE 0x100 - enum tpm_tis_flags { TPM_TIS_ITPM_WORKAROUND = 0, TPM_TIS_INVALID_STATUS = 1, diff --git a/include/linux/tpm_ptp.h b/include/linux/tpm_ptp.h new file mode 100644 index 000000000000..e80ed810a269 --- /dev/null +++ b/include/linux/tpm_ptp.h @@ -0,0 +1,133 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Following copyright information was take from the original file + * where the definitions were moved + * from: + * + * Copyright (C) 2005, 2006 IBM Corporation + * Copyright (C) 2014, 2015 Intel Corporation + * + * Authors: + * Leendert van Doorn + * Kylene Hall + * + * Maintained by: + * + * Device driver for TCG/TCPA TPM (trusted platform module). + * Specifications at www.trustedcomputinggroup.org + * + * This device driver implements the TPM interface as defined in + * the TCG TPM Interface Spec version 1.2, revision 1.0. + */ + +#ifndef __LINUX_TPM_PTP_H__ +#define __LINUX_TPM_PTP_H__ + +/* + * TCG PC Client Platform TPM Profile (PTP) Specification + * https://trustedcomputinggroup.org/resource/pc-client-platform-tpm-profile-ptp-specification/ + */ + +/* TIS/FIFO macros and definitions */ + +enum tis_access { + TPM_ACCESS_VALID = 0x80, + TPM_ACCESS_ACTIVE_LOCALITY = 0x20, /* (R) */ + TPM_ACCESS_RELINQUISH_LOCALITY = 0x20, /* (W) */ + TPM_ACCESS_REQUEST_PENDING = 0x04, /* (W) */ + TPM_ACCESS_REQUEST_USE = 0x02, /* (W) */ +}; + +enum tis_status { + TPM_STS_VALID = 0x80, /* (R) */ + TPM_STS_COMMAND_READY = 0x40, /* (R) */ + TPM_STS_DATA_AVAIL = 0x10, /* (R) */ + TPM_STS_DATA_EXPECT = 0x08, /* (R) */ + TPM_STS_GO = 0x20, /* (W) */ + TPM_STS_RESPONSE_RETRY = 0x02, /* (R) */ + TPM_STS_READ_ZERO = 0x23, /* bits that must be zero on read */ +}; + +enum tis_int_flags { + TPM_GLOBAL_INT_ENABLE = 0x80000000, + TPM_INTF_BURST_COUNT_STATIC = 0x100, + TPM_INTF_CMD_READY_INT = 0x080, + TPM_INTF_INT_EDGE_FALLING = 0x040, + TPM_INTF_INT_EDGE_RISING = 0x020, + TPM_INTF_INT_LEVEL_LOW = 0x010, + TPM_INTF_INT_LEVEL_HIGH = 0x008, + TPM_INTF_LOCALITY_CHANGE_INT = 0x004, + TPM_INTF_STS_VALID_INT = 0x002, + TPM_INTF_DATA_AVAIL_INT = 0x001, +}; + +enum tis_defaults { + TIS_MEM_LEN = 0x5000, + TIS_SHORT_TIMEOUT = 750, /* ms */ + TIS_LONG_TIMEOUT = 4000, /* 4 secs */ + TIS_TIMEOUT_MIN_ATML = 14700, /* usecs */ + TIS_TIMEOUT_MAX_ATML = 15000, /* usecs */ +}; + +#define TIS_MEM_X86_LPC_BASE 0xFED40000 +#define INTEL_LEGACY_BLK_BASE_ADDR 0xFED08000 + +enum tis_x86_defaults { + TIS_MEM_X86_LEN = 0x5000, + ILB_REMAP_SIZE = 0x100, + LPC_CNTRL_OFFSET = 0x84, + LPC_CLKRUN_EN = (1 << 2), +}; + +/* + * Some timeout values are needed before it is known whether the chip is + * TPM 1.0 or TPM 2.0. + */ +#define TIS_TIMEOUT_A_MAX max_t(int, TIS_SHORT_TIMEOUT, TPM2_TIMEOUT_A) +#define TIS_TIMEOUT_B_MAX max_t(int, TIS_LONG_TIMEOUT, TPM2_TIMEOUT_B) +#define TIS_TIMEOUT_C_MAX max_t(int, TIS_SHORT_TIMEOUT, TPM2_TIMEOUT_C) +#define TIS_TIMEOUT_D_MAX max_t(int, TIS_SHORT_TIMEOUT, TPM2_TIMEOUT_D) + +#define TPM_ACCESS(l) (0x0000 | ((l) << 12)) +#define TPM_INT_ENABLE(l) (0x0008 | ((l) << 12)) +#define TPM_INT_VECTOR(l) (0x000C | ((l) << 12)) +#define TPM_INT_STATUS(l) (0x0010 | ((l) << 12)) +#define TPM_INTF_CAPS(l) (0x0014 | ((l) << 12)) +#define TPM_STS(l) (0x0018 | ((l) << 12)) +#define TPM_STS3(l) (0x001b | ((l) << 12)) +#define TPM_DATA_FIFO(l) (0x0024 | ((l) << 12)) +#define TPM_INTF_ID(l) (0x0030 | ((l) << 12)) + +#define TPM_DID_VID(l) (0x0F00 | ((l) << 12)) +#define TPM_RID(l) (0x0F04 | ((l) << 12)) + +/* TPM HW Interface and Capabilities */ +#define TPM_TIS_INTF_ACTIVE 0x00 +#define TPM_CRB_INTF_ACTIVE 0x01 + +#define TPM_INTID_INTERFACE_TYPE GENMASK(3, 0) +#define TPM_INTID_INTERFACE_VERSION GENMASK(7, 4) +#define TPM_INTID_CAP_LOCALITY BIT(8) +#define TPM_INTID_CAP_TIS BIT(13) +#define TPM_INTID_CAP_CRB BIT(14) +#define TPM_INTID_CAP_IF_RES GENMASK(16, 15) +#define TPM_INTID_INTERFACE_SELECTOR GENMASK(18, 17) +#define TPM_INTID_INTF_SEL_LOCK BIT(19) + +#define TPM_TIS_INTF_12 0x00 +#define TPM_TIS_INTF_13 0x02 +#define TPM2_TIS_INTF_13 0x03 + +#define TPM_INTF_DATA_AVAIL_INT_SUPPORT BIT(0) +#define TPM_INTF_STS_VALID_INT_SUPPORT BIT(1) +#define TPM_INTF_LOCALITY_CHANGE_INT_SUPPORT BIT(2) +#define TPM_INTF_INTERRUPT_LEVEL_HIGH BIT(3) +#define TPM_INTF_INTERRUPT_LEVEL_LOW BIT(4) +#define TPM_INTF_INTERRUPT_EDGE_RISING BIT(5) +#define TPM_INTF_INTERRUPT_EDGE_FALLING BIT(6) +#define TPM_INTF_COMMAND_READY_INT_SUPPORT BIT(7) +#define TPM_INTF_BURST_COUNT_STATIC BIT(8) +#define TPM_INTF_DATA_TRANSFER_SIZE_SUPPORT GENMASK(10, 9) +#define TPM_INTF_INTERFACE_VERSION GENMASK(30, 28) + +#endif From 71ab22dbcfb5cf4c99d167a7e0452a40a74a192e Mon Sep 17 00:00:00 2001 From: Alec Brown Date: Sat, 11 Jul 2026 09:01:06 -0700 Subject: [PATCH 1171/1328] tpm: Remove main TPM header from TPM event log header Allow the TPM event log functionality to be used without including the main TPM driver definitions. Signed-off-by: Alec Brown Signed-off-by: Ross Philipson Reviewed-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- include/linux/tpm_eventlog.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/tpm_eventlog.h b/include/linux/tpm_eventlog.h index aff8ea2fa98e..40fe92eb7deb 100644 --- a/include/linux/tpm_eventlog.h +++ b/include/linux/tpm_eventlog.h @@ -3,7 +3,7 @@ #ifndef __LINUX_TPM_EVENTLOG_H__ #define __LINUX_TPM_EVENTLOG_H__ -#include +#include #define TCG_EVENT_NAME_LEN_MAX 255 #define MAX_TEXT_EVENT 1000 /* Max event string length */ From 0b9551c189518544b65000a234277298e931c8f3 Mon Sep 17 00:00:00 2001 From: Jarkko Sakkinen Date: Sat, 11 Jul 2026 09:01:07 -0700 Subject: [PATCH 1172/1328] tpm-buf: Merge TPM_BUF_BOUNDARY_ERROR and TPM_BUF_OVERFLOW Merge TPM_BUF_BOUNDARY_ERROR and TPM_BUF_OVERFLOW flags into the TPM_BUF_INVALID flag, as their behavior is identical (the only difference being the associated log messages). Message-ID: <20260125192526.782202-11-jarkko@kernel.org> Signed-off-by: Jarkko Sakkinen Reviewed-by: Jonathan McDowell Signed-off-by: Ross Philipson --- drivers/char/tpm/tpm-buf.c | 10 ++++------ include/linux/tpm.h | 8 +++----- security/keys/trusted-keys/trusted_tpm2.c | 6 +++--- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/drivers/char/tpm/tpm-buf.c b/drivers/char/tpm/tpm-buf.c index 4c4f450630df..61833b4d81f0 100644 --- a/drivers/char/tpm/tpm-buf.c +++ b/drivers/char/tpm/tpm-buf.c @@ -103,13 +103,12 @@ EXPORT_SYMBOL_GPL(tpm_buf_length); */ void tpm_buf_append(struct tpm_buf *buf, const u8 *new_data, u16 new_length) { - /* Return silently if overflow has already happened. */ - if (buf->flags & TPM_BUF_OVERFLOW) + if (buf->flags & TPM_BUF_INVALID) return; if ((buf->length + new_length) > PAGE_SIZE) { WARN(1, "tpm_buf: write overflow\n"); - buf->flags |= TPM_BUF_OVERFLOW; + buf->flags |= TPM_BUF_INVALID; return; } @@ -176,14 +175,13 @@ static void tpm_buf_read(struct tpm_buf *buf, off_t *offset, size_t count, void { off_t next_offset; - /* Return silently if overflow has already happened. */ - if (buf->flags & TPM_BUF_BOUNDARY_ERROR) + if (buf->flags & TPM_BUF_INVALID) return; next_offset = *offset + count; if (next_offset > buf->length) { WARN(1, "tpm_buf: read out of boundary\n"); - buf->flags |= TPM_BUF_BOUNDARY_ERROR; + buf->flags |= TPM_BUF_INVALID; return; } diff --git a/include/linux/tpm.h b/include/linux/tpm.h index 3630b2ea6aef..3c6a5bcc138a 100644 --- a/include/linux/tpm.h +++ b/include/linux/tpm.h @@ -201,12 +201,10 @@ enum tpm_chip_flags { #define to_tpm_chip(d) container_of(d, struct tpm_chip, dev) enum tpm_buf_flags { - /* the capacity exceeded: */ - TPM_BUF_OVERFLOW = BIT(0), /* TPM2B format: */ - TPM_BUF_TPM2B = BIT(1), - /* read out of boundary: */ - TPM_BUF_BOUNDARY_ERROR = BIT(2), + TPM_BUF_TPM2B = BIT(0), + /* The buffer is in invalid and unusable state: */ + TPM_BUF_INVALID = BIT(1), }; /* diff --git a/security/keys/trusted-keys/trusted_tpm2.c b/security/keys/trusted-keys/trusted_tpm2.c index 29d79c05ed6b..779a2e66ac20 100644 --- a/security/keys/trusted-keys/trusted_tpm2.c +++ b/security/keys/trusted-keys/trusted_tpm2.c @@ -312,7 +312,7 @@ int tpm2_seal_trusted(struct tpm_chip *chip, /* creation PCR */ tpm_buf_append_u32(&buf, 0); - if (buf.flags & TPM_BUF_OVERFLOW) { + if (buf.flags & TPM_BUF_INVALID) { rc = -E2BIG; tpm2_end_auth_session(chip); goto out; @@ -328,7 +328,7 @@ int tpm2_seal_trusted(struct tpm_chip *chip, goto out; blob_len = tpm_buf_read_u32(&buf, &offset); - if (blob_len > MAX_BLOB_SIZE || buf.flags & TPM_BUF_BOUNDARY_ERROR) { + if (blob_len > MAX_BLOB_SIZE || buf.flags & TPM_BUF_INVALID) { rc = -E2BIG; goto out; } @@ -441,7 +441,7 @@ static int tpm2_load_cmd(struct tpm_chip *chip, tpm_buf_append(&buf, blob, blob_len); - if (buf.flags & TPM_BUF_OVERFLOW) { + if (buf.flags & TPM_BUF_INVALID) { rc = -E2BIG; tpm2_end_auth_session(chip); goto out; From 065cfba1f4545e5948a2ddc5888a27c7641d5bfc Mon Sep 17 00:00:00 2001 From: Jarkko Sakkinen Date: Sat, 11 Jul 2026 09:01:08 -0700 Subject: [PATCH 1173/1328] tpm-buf: Remove chip parameter from tpm_buf_append_handle() Remove the TPM driver chip parameter from the function tpm_buf_append_handle(). The chip parameter is only for error logging which can be done with other facilities like WARN(). Message-ID: <20260125192526.782202-11-jarkko@kernel.org> Signed-off-by: Jarkko Sakkinen Signed-off-by: Ross Philipson --- drivers/char/tpm/tpm-buf.c | 9 ++++++--- drivers/char/tpm/tpm2-cmd.c | 2 +- drivers/char/tpm/tpm2-sessions.c | 2 +- include/linux/tpm.h | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/char/tpm/tpm-buf.c b/drivers/char/tpm/tpm-buf.c index 61833b4d81f0..99811809a72a 100644 --- a/drivers/char/tpm/tpm-buf.c +++ b/drivers/char/tpm/tpm-buf.c @@ -146,17 +146,20 @@ EXPORT_SYMBOL_GPL(tpm_buf_append_u32); /** * tpm_buf_append_handle() - Add a handle - * @chip: &tpm_chip instance * @buf: &tpm_buf instance * @handle: a TPM object handle * * Add a handle to the buffer, and increase the count tracking the number of * handles in the command buffer. Works only for command buffers. */ -void tpm_buf_append_handle(struct tpm_chip *chip, struct tpm_buf *buf, u32 handle) +void tpm_buf_append_handle(struct tpm_buf *buf, u32 handle) { + if (buf->flags & TPM_BUF_INVALID) + return; + if (buf->flags & TPM_BUF_TPM2B) { - dev_err(&chip->dev, "Invalid buffer type (TPM2B)\n"); + WARN(1, "tpm-buf: invalid type: TPM2B\n"); + buf->flags |= TPM_BUF_INVALID; return; } diff --git a/drivers/char/tpm/tpm2-cmd.c b/drivers/char/tpm/tpm2-cmd.c index 280f870e6517..c7952319384e 100644 --- a/drivers/char/tpm/tpm2-cmd.c +++ b/drivers/char/tpm/tpm2-cmd.c @@ -198,7 +198,7 @@ int tpm2_pcr_extend(struct tpm_chip *chip, u32 pcr_idx, } tpm_buf_append_hmac_session(chip, &buf, 0, NULL, 0); } else { - tpm_buf_append_handle(chip, &buf, pcr_idx); + tpm_buf_append_handle(&buf, pcr_idx); tpm_buf_append_auth(chip, &buf, NULL, 0); } diff --git a/drivers/char/tpm/tpm2-sessions.c b/drivers/char/tpm/tpm2-sessions.c index a1ae5e1829cb..0d89643e6880 100644 --- a/drivers/char/tpm/tpm2-sessions.c +++ b/drivers/char/tpm/tpm2-sessions.c @@ -260,7 +260,7 @@ int tpm_buf_append_name(struct tpm_chip *chip, struct tpm_buf *buf, #endif if (!tpm2_chip_auth(chip)) { - tpm_buf_append_handle(chip, buf, handle); + tpm_buf_append_handle(buf, handle); return 0; } diff --git a/include/linux/tpm.h b/include/linux/tpm.h index 3c6a5bcc138a..b357f8971d03 100644 --- a/include/linux/tpm.h +++ b/include/linux/tpm.h @@ -235,7 +235,7 @@ void tpm_buf_append_u32(struct tpm_buf *buf, const u32 value); u8 tpm_buf_read_u8(struct tpm_buf *buf, off_t *offset); u16 tpm_buf_read_u16(struct tpm_buf *buf, off_t *offset); u32 tpm_buf_read_u32(struct tpm_buf *buf, off_t *offset); -void tpm_buf_append_handle(struct tpm_chip *chip, struct tpm_buf *buf, u32 handle); +void tpm_buf_append_handle(struct tpm_buf *buf, u32 handle); /* * Check if TPM device is in the firmware upgrade mode. From 3d9e043dab0a038a53a43570f60bbf4b1e27d63f Mon Sep 17 00:00:00 2001 From: Jarkko Sakkinen Date: Sat, 11 Jul 2026 09:01:09 -0700 Subject: [PATCH 1174/1328] tpm-buf: Memory-safe allocations Decouple kzalloc from buffer creation, so that a managed allocation can be used: struct tpm_buf *buf __free(kfree) buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); if (!buf) return -ENOMEM; tpm_buf_init(buf, TPM_BUFSIZE); Alternatively, other allocations are also possible (static data, stack, etc) for example: u8 buf_data[512]; struct tpm_buf *buf = (struct tpm_buf *)buf_data; tpm_buf_init(buf, sizeof(buf_data)); This is achieved by embedding buffer's header inside the allocated blob, instead of having an outer wrapper. Reviewed-by: Stefan Berger Signed-off-by: Jarkko Sakkinen Tested-by: Srish Srinivasan Message-ID: <20260522013555.1063716-1-jarkko@kernel.org> Signed-off-by: Ross Philipson --- drivers/char/tpm/tpm-buf.c | 124 +++++---- drivers/char/tpm/tpm-sysfs.c | 26 +- drivers/char/tpm/tpm1-cmd.c | 167 ++++++------ drivers/char/tpm/tpm2-cmd.c | 297 +++++++++++----------- drivers/char/tpm/tpm2-sessions.c | 164 ++++++------ drivers/char/tpm/tpm2-space.c | 44 ++-- drivers/char/tpm/tpm_vtpm_proxy.c | 30 +-- include/linux/tpm.h | 17 +- security/keys/trusted-keys/trusted_tpm1.c | 44 ++-- security/keys/trusted-keys/trusted_tpm2.c | 167 ++++++------ 10 files changed, 538 insertions(+), 542 deletions(-) diff --git a/drivers/char/tpm/tpm-buf.c b/drivers/char/tpm/tpm-buf.c index 99811809a72a..1e5c11c312a8 100644 --- a/drivers/char/tpm/tpm-buf.c +++ b/drivers/char/tpm/tpm-buf.c @@ -6,90 +6,118 @@ #include #include -/** - * tpm_buf_init() - Allocate and initialize a TPM command - * @buf: A &tpm_buf - * @tag: TPM_TAG_RQU_COMMAND, TPM2_ST_NO_SESSIONS or TPM2_ST_SESSIONS - * @ordinal: A command ordinal - * - * Return: 0 or -ENOMEM - */ -int tpm_buf_init(struct tpm_buf *buf, u16 tag, u32 ordinal) +static void __tpm_buf_size_invariant(struct tpm_buf *buf, u16 buf_size) { - buf->data = (u8 *)__get_free_page(GFP_KERNEL); - if (!buf->data) - return -ENOMEM; + u32 buf_size_2 = (u32)buf->capacity + (u32)sizeof(*buf); - tpm_buf_reset(buf, tag, ordinal); - return 0; + if (!buf->capacity) { + if (buf_size > TPM_BUFSIZE) { + WARN(1, "%s: size overflow: %u\n", __func__, buf_size); + buf->flags |= TPM_BUF_INVALID; + } + } else { + if (buf_size != buf_size_2) { + WARN(1, "%s: size mismatch: %u != %u\n", __func__, + buf_size, buf_size_2); + buf->flags |= TPM_BUF_INVALID; + } + } } -EXPORT_SYMBOL_GPL(tpm_buf_init); -/** - * tpm_buf_reset() - Initialize a TPM command - * @buf: A &tpm_buf - * @tag: TPM_TAG_RQU_COMMAND, TPM2_ST_NO_SESSIONS or TPM2_ST_SESSIONS - * @ordinal: A command ordinal - */ -void tpm_buf_reset(struct tpm_buf *buf, u16 tag, u32 ordinal) +static void __tpm_buf_reset(struct tpm_buf *buf, u16 buf_size, u16 tag, + u32 ordinal) { struct tpm_header *head = (struct tpm_header *)buf->data; + __tpm_buf_size_invariant(buf, buf_size); + + if (buf->flags & TPM_BUF_INVALID) + return; + WARN_ON(tag != TPM_TAG_RQU_COMMAND && tag != TPM2_ST_NO_SESSIONS && tag != TPM2_ST_SESSIONS && tag != 0); buf->flags = 0; buf->length = sizeof(*head); + buf->capacity = buf_size - sizeof(*buf); + buf->handles = 0; head->tag = cpu_to_be16(tag); head->length = cpu_to_be32(sizeof(*head)); head->ordinal = cpu_to_be32(ordinal); - buf->handles = 0; } -EXPORT_SYMBOL_GPL(tpm_buf_reset); + +static void __tpm_buf_reset_sized(struct tpm_buf *buf, u16 buf_size) +{ + __tpm_buf_size_invariant(buf, buf_size); + + if (buf->flags & TPM_BUF_INVALID) + return; + + buf->flags = TPM_BUF_TPM2B; + buf->length = 2; + buf->capacity = buf_size - sizeof(*buf); + buf->handles = 0; + buf->data[0] = 0; + buf->data[1] = 0; +} /** - * tpm_buf_init_sized() - Allocate and initialize a sized (TPM2B) buffer - * @buf: A @tpm_buf - * - * Return: 0 or -ENOMEM + * tpm_buf_init() - Initialize a TPM command + * @buf: A &tpm_buf + * @buf_size: Size of the buffer. */ -int tpm_buf_init_sized(struct tpm_buf *buf) +void tpm_buf_init(struct tpm_buf *buf, u16 buf_size) { - buf->data = (u8 *)__get_free_page(GFP_KERNEL); - if (!buf->data) - return -ENOMEM; + memset(buf, 0, buf_size); + __tpm_buf_reset(buf, buf_size, TPM_TAG_RQU_COMMAND, 0); +} +EXPORT_SYMBOL_GPL(tpm_buf_init); - tpm_buf_reset_sized(buf); - return 0; +/** + * tpm_buf_init_sized() - Initialize a sized buffer + * @buf: A &tpm_buf + * @buf_size: Size of the buffer. + */ +void tpm_buf_init_sized(struct tpm_buf *buf, u16 buf_size) +{ + memset(buf, 0, buf_size); + __tpm_buf_reset_sized(buf, buf_size); } EXPORT_SYMBOL_GPL(tpm_buf_init_sized); /** - * tpm_buf_reset_sized() - Initialize a sized buffer + * tpm_buf_reset() - Re-initialize a TPM command + * @buf: A &tpm_buf + * @tag: TPM_TAG_RQU_COMMAND, TPM2_ST_NO_SESSIONS or TPM2_ST_SESSIONS + * @ordinal: A command ordinal + */ +void tpm_buf_reset(struct tpm_buf *buf, u16 tag, u32 ordinal) +{ + u16 buf_size = buf->capacity + sizeof(*buf); + + __tpm_buf_reset(buf, buf_size, tag, ordinal); +} +EXPORT_SYMBOL_GPL(tpm_buf_reset); + +/** + * tpm_buf_reset_sized() - Re-initialize a sized buffer * @buf: A &tpm_buf */ void tpm_buf_reset_sized(struct tpm_buf *buf) { - buf->flags = TPM_BUF_TPM2B; - buf->length = 2; - buf->data[0] = 0; - buf->data[1] = 0; + u16 buf_size = buf->capacity + sizeof(*buf); + + __tpm_buf_reset_sized(buf, buf_size); } EXPORT_SYMBOL_GPL(tpm_buf_reset_sized); -void tpm_buf_destroy(struct tpm_buf *buf) -{ - free_page((unsigned long)buf->data); -} -EXPORT_SYMBOL_GPL(tpm_buf_destroy); - /** * tpm_buf_length() - Return the number of bytes consumed by the data * @buf: A &tpm_buf * * Return: The number of bytes consumed by the buffer */ -u32 tpm_buf_length(struct tpm_buf *buf) +u16 tpm_buf_length(struct tpm_buf *buf) { return buf->length; } @@ -103,10 +131,12 @@ EXPORT_SYMBOL_GPL(tpm_buf_length); */ void tpm_buf_append(struct tpm_buf *buf, const u8 *new_data, u16 new_length) { + u32 total_length = (u32)buf->length + (u32)new_length; + if (buf->flags & TPM_BUF_INVALID) return; - if ((buf->length + new_length) > PAGE_SIZE) { + if (total_length > (u32)buf->capacity) { WARN(1, "tpm_buf: write overflow\n"); buf->flags |= TPM_BUF_INVALID; return; diff --git a/drivers/char/tpm/tpm-sysfs.c b/drivers/char/tpm/tpm-sysfs.c index 94231f052ea7..d915307ae833 100644 --- a/drivers/char/tpm/tpm-sysfs.c +++ b/drivers/char/tpm/tpm-sysfs.c @@ -32,28 +32,35 @@ struct tpm_readpubek_out { static ssize_t pubek_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct tpm_buf tpm_buf; struct tpm_readpubek_out *out; int i; char *str = buf; struct tpm_chip *chip = to_tpm_chip(dev); char anti_replay[20]; + struct tpm_buf *tpm_buf __free(kfree) = NULL; memset(&anti_replay, 0, sizeof(anti_replay)); if (tpm_try_get_ops(chip)) return 0; - if (tpm_buf_init(&tpm_buf, TPM_TAG_RQU_COMMAND, TPM_ORD_READPUBEK)) - goto out_ops; + tpm_buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!tpm_buf) { + tpm_put_ops(chip); + return 0; + } - tpm_buf_append(&tpm_buf, anti_replay, sizeof(anti_replay)); + tpm_buf_init(tpm_buf, TPM_BUFSIZE); + tpm_buf_reset(tpm_buf, TPM_TAG_RQU_COMMAND, TPM_ORD_READPUBEK); + tpm_buf_append(tpm_buf, anti_replay, sizeof(anti_replay)); - if (tpm_transmit_cmd(chip, &tpm_buf, READ_PUBEK_RESULT_MIN_BODY_SIZE, - "attempting to read the PUBEK")) - goto out_buf; + if (tpm_transmit_cmd(chip, tpm_buf, READ_PUBEK_RESULT_MIN_BODY_SIZE, + "attempting to read the PUBEK")) { + tpm_put_ops(chip); + return 0; + } - out = (struct tpm_readpubek_out *)&tpm_buf.data[10]; + out = (struct tpm_readpubek_out *)&tpm_buf->data[10]; str += sprintf(str, "Algorithm: %4ph\n" @@ -71,9 +78,6 @@ static ssize_t pubek_show(struct device *dev, struct device_attribute *attr, for (i = 0; i < 256; i += 16) str += sprintf(str, "%16ph\n", &out->modulus[i]); -out_buf: - tpm_buf_destroy(&tpm_buf); -out_ops: tpm_put_ops(chip); return str - buf; } diff --git a/drivers/char/tpm/tpm1-cmd.c b/drivers/char/tpm/tpm1-cmd.c index 96f189b5fd6f..7a8606e8c711 100644 --- a/drivers/char/tpm/tpm1-cmd.c +++ b/drivers/char/tpm/tpm1-cmd.c @@ -318,20 +318,18 @@ unsigned long tpm1_calc_ordinal_duration(struct tpm_chip *chip, u32 ordinal) */ static int tpm1_startup(struct tpm_chip *chip) { - struct tpm_buf buf; - int rc; + struct tpm_buf *buf __free(kfree) = NULL; dev_info(&chip->dev, "starting up the TPM manually\n"); - rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_STARTUP); - if (rc < 0) - return rc; + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; - tpm_buf_append_u16(&buf, TPM_ST_CLEAR); - - rc = tpm_transmit_cmd(chip, &buf, 0, "attempting to start the TPM"); - tpm_buf_destroy(&buf); - return rc; + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_STARTUP); + tpm_buf_append_u16(buf, TPM_ST_CLEAR); + return tpm_transmit_cmd(chip, buf, 0, "attempting to start the TPM"); } int tpm1_get_timeouts(struct tpm_chip *chip) @@ -457,49 +455,49 @@ int tpm1_get_timeouts(struct tpm_chip *chip) int tpm1_pcr_extend(struct tpm_chip *chip, u32 pcr_idx, const u8 *hash, const char *log_msg) { - struct tpm_buf buf; - int rc; + struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; - rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_PCR_EXTEND); - if (rc) - return rc; + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_PCR_EXTEND); + tpm_buf_append_u32(buf, pcr_idx); + tpm_buf_append(buf, hash, TPM_DIGEST_SIZE); - tpm_buf_append_u32(&buf, pcr_idx); - tpm_buf_append(&buf, hash, TPM_DIGEST_SIZE); - - rc = tpm_transmit_cmd(chip, &buf, TPM_DIGEST_SIZE, log_msg); - tpm_buf_destroy(&buf); - return rc; + return tpm_transmit_cmd(chip, buf, TPM_DIGEST_SIZE, log_msg); } ssize_t tpm1_getcap(struct tpm_chip *chip, u32 subcap_id, cap_t *cap, const char *desc, size_t min_cap_length) { - struct tpm_buf buf; int rc; - rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_GET_CAP); - if (rc) - return rc; + struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_GET_CAP); if (subcap_id == TPM_CAP_VERSION_1_1 || subcap_id == TPM_CAP_VERSION_1_2) { - tpm_buf_append_u32(&buf, subcap_id); - tpm_buf_append_u32(&buf, 0); + tpm_buf_append_u32(buf, subcap_id); + tpm_buf_append_u32(buf, 0); } else { if (subcap_id == TPM_CAP_FLAG_PERM || subcap_id == TPM_CAP_FLAG_VOL) - tpm_buf_append_u32(&buf, TPM_CAP_FLAG); + tpm_buf_append_u32(buf, TPM_CAP_FLAG); else - tpm_buf_append_u32(&buf, TPM_CAP_PROP); + tpm_buf_append_u32(buf, TPM_CAP_PROP); - tpm_buf_append_u32(&buf, 4); - tpm_buf_append_u32(&buf, subcap_id); + tpm_buf_append_u32(buf, 4); + tpm_buf_append_u32(buf, subcap_id); } - rc = tpm_transmit_cmd(chip, &buf, min_cap_length, desc); + + rc = tpm_transmit_cmd(chip, buf, min_cap_length, desc); if (!rc) - *cap = *(cap_t *)&buf.data[TPM_HEADER_SIZE + 4]; - tpm_buf_destroy(&buf); + *cap = *(cap_t *)&buf->data[TPM_HEADER_SIZE + 4]; + return rc; } EXPORT_SYMBOL_GPL(tpm1_getcap); @@ -517,81 +515,77 @@ EXPORT_SYMBOL_GPL(tpm1_getcap); int tpm1_get_random(struct tpm_chip *chip, u8 *dest, size_t max) { struct tpm1_get_random_out *out; + struct tpm_buf *buf __free(kfree) = NULL; u32 num_bytes = min_t(u32, max, TPM_MAX_RNG_DATA); - struct tpm_buf buf; u32 total = 0; int retries = 5; u32 recd; int rc; - rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_GET_RANDOM); - if (rc) - return rc; + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_GET_RANDOM); do { - tpm_buf_append_u32(&buf, num_bytes); + tpm_buf_append_u32(buf, num_bytes); - rc = tpm_transmit_cmd(chip, &buf, sizeof(out->rng_data_len), + rc = tpm_transmit_cmd(chip, buf, sizeof(out->rng_data_len), "attempting get random"); if (rc) { if (rc > 0) rc = -EIO; - goto out; + return rc; } - out = (struct tpm1_get_random_out *)&buf.data[TPM_HEADER_SIZE]; + out = (struct tpm1_get_random_out *)&buf->data[TPM_HEADER_SIZE]; recd = be32_to_cpu(out->rng_data_len); - if (recd > num_bytes) { - rc = -EFAULT; - goto out; - } + if (recd > num_bytes) + return -EFAULT; + + if (tpm_buf_length(buf) < TPM_HEADER_SIZE + + sizeof(out->rng_data_len) + recd) + return -EFAULT; - if (tpm_buf_length(&buf) < TPM_HEADER_SIZE + - sizeof(out->rng_data_len) + recd) { - rc = -EFAULT; - goto out; - } memcpy(dest, out->rng_data, recd); dest += recd; total += recd; num_bytes -= recd; - tpm_buf_reset(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_GET_RANDOM); + tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_GET_RANDOM); } while (retries-- && total < max); rc = total ? (int)total : -EIO; -out: - tpm_buf_destroy(&buf); + return rc; } int tpm1_pcr_read(struct tpm_chip *chip, u32 pcr_idx, u8 *res_buf) { - struct tpm_buf buf; int rc; - rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_PCR_READ); + struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_PCR_READ); + tpm_buf_append_u32(buf, pcr_idx); + + rc = tpm_transmit_cmd(chip, buf, TPM_DIGEST_SIZE, + "attempting to read a pcr value"); if (rc) return rc; - tpm_buf_append_u32(&buf, pcr_idx); + if (tpm_buf_length(buf) < TPM_DIGEST_SIZE) + return -EFAULT; - rc = tpm_transmit_cmd(chip, &buf, TPM_DIGEST_SIZE, - "attempting to read a pcr value"); - if (rc) - goto out; + memcpy(res_buf, &buf->data[TPM_HEADER_SIZE], TPM_DIGEST_SIZE); - if (tpm_buf_length(&buf) < TPM_DIGEST_SIZE) { - rc = -EFAULT; - goto out; - } - - memcpy(res_buf, &buf.data[TPM_HEADER_SIZE], TPM_DIGEST_SIZE); - -out: - tpm_buf_destroy(&buf); return rc; } @@ -604,16 +598,13 @@ int tpm1_pcr_read(struct tpm_chip *chip, u32 pcr_idx, u8 *res_buf) */ static int tpm1_continue_selftest(struct tpm_chip *chip) { - struct tpm_buf buf; - int rc; + struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; - rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_CONTINUE_SELFTEST); - if (rc) - return rc; - - rc = tpm_transmit_cmd(chip, &buf, 0, "continue selftest"); - tpm_buf_destroy(&buf); - return rc; + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_CONTINUE_SELFTEST); + return tpm_transmit_cmd(chip, buf, 0, "continue selftest"); } /** @@ -725,22 +716,24 @@ int tpm1_auto_startup(struct tpm_chip *chip) int tpm1_pm_suspend(struct tpm_chip *chip, u32 tpm_suspend_pcr) { u8 dummy_hash[TPM_DIGEST_SIZE] = { 0 }; - struct tpm_buf buf; unsigned int try; + struct tpm_buf *buf __free(kfree) = NULL; int rc; - /* for buggy tpm, flush pcrs with extend to selected dummy */ if (tpm_suspend_pcr) rc = tpm1_pcr_extend(chip, tpm_suspend_pcr, dummy_hash, "extending dummy pcr before suspend"); + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_SAVESTATE); - rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_SAVESTATE); - if (rc) - return rc; /* now do the actual savestate */ for (try = 0; try < TPM_RETRY; try++) { - rc = tpm_transmit_cmd(chip, &buf, 0, NULL); + rc = tpm_transmit_cmd(chip, buf, 0, NULL); /* * If the TPM indicates that it is too busy to respond to * this command then retry before giving up. It can take @@ -755,7 +748,7 @@ int tpm1_pm_suspend(struct tpm_chip *chip, u32 tpm_suspend_pcr) break; tpm_msleep(TPM_TIMEOUT_RETRY); - tpm_buf_reset(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_SAVESTATE); + tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_SAVESTATE); } if (rc) @@ -765,8 +758,6 @@ int tpm1_pm_suspend(struct tpm_chip *chip, u32 tpm_suspend_pcr) dev_warn(&chip->dev, "TPM savestate took %dms\n", try * TPM_TIMEOUT_RETRY); - tpm_buf_destroy(&buf); - return rc; } diff --git a/drivers/char/tpm/tpm2-cmd.c b/drivers/char/tpm/tpm2-cmd.c index c7952319384e..48cec39995fe 100644 --- a/drivers/char/tpm/tpm2-cmd.c +++ b/drivers/char/tpm/tpm2-cmd.c @@ -108,12 +108,13 @@ int tpm2_pcr_read(struct tpm_chip *chip, u32 pcr_idx, { int i; int rc; - struct tpm_buf buf; struct tpm2_pcr_read_out *out; u8 pcr_select[TPM2_PCR_SELECT_MIN] = {0}; u16 digest_size; u16 expected_digest_size = 0; + struct tpm_buf *buf __free(kfree) = NULL; + if (pcr_idx >= TPM2_PLATFORM_PCR) return -EINVAL; @@ -128,36 +129,35 @@ int tpm2_pcr_read(struct tpm_chip *chip, u32 pcr_idx, expected_digest_size = chip->allocated_banks[i].digest_size; } - rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_PCR_READ); - if (rc) - return rc; + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_PCR_READ); pcr_select[pcr_idx >> 3] = 1 << (pcr_idx & 0x7); - tpm_buf_append_u32(&buf, 1); - tpm_buf_append_u16(&buf, digest->alg_id); - tpm_buf_append_u8(&buf, TPM2_PCR_SELECT_MIN); - tpm_buf_append(&buf, (const unsigned char *)pcr_select, + tpm_buf_append_u32(buf, 1); + tpm_buf_append_u16(buf, digest->alg_id); + tpm_buf_append_u8(buf, TPM2_PCR_SELECT_MIN); + tpm_buf_append(buf, (const unsigned char *)pcr_select, sizeof(pcr_select)); - rc = tpm_transmit_cmd(chip, &buf, 0, "attempting to read a pcr value"); + rc = tpm_transmit_cmd(chip, buf, 0, "attempting to read a pcr value"); if (rc) - goto out; + return rc; - out = (struct tpm2_pcr_read_out *)&buf.data[TPM_HEADER_SIZE]; + out = (struct tpm2_pcr_read_out *)&buf->data[TPM_HEADER_SIZE]; digest_size = be16_to_cpu(out->digest_size); if (digest_size > sizeof(digest->digest) || - (!digest_size_ptr && digest_size != expected_digest_size)) { - rc = -EINVAL; - goto out; - } + (!digest_size_ptr && digest_size != expected_digest_size)) + return -EINVAL; if (digest_size_ptr) *digest_size_ptr = digest_size; memcpy(digest->digest, out->digest, digest_size); -out: - tpm_buf_destroy(&buf); return rc; } @@ -173,7 +173,7 @@ int tpm2_pcr_read(struct tpm_chip *chip, u32 pcr_idx, int tpm2_pcr_extend(struct tpm_chip *chip, u32 pcr_idx, struct tpm_digest *digests) { - struct tpm_buf buf; + struct tpm_buf *buf __free(kfree) = NULL; int rc; int i; @@ -183,46 +183,45 @@ int tpm2_pcr_extend(struct tpm_chip *chip, u32 pcr_idx, return rc; } - rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_PCR_EXTEND); - if (rc) { + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) { if (!disable_pcr_integrity) tpm2_end_auth_session(chip); - return rc; + return -ENOMEM; } + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_PCR_EXTEND); + if (!disable_pcr_integrity) { - rc = tpm_buf_append_name(chip, &buf, pcr_idx, NULL); + rc = tpm_buf_append_name(chip, buf, pcr_idx, NULL); if (rc) { - tpm_buf_destroy(&buf); + tpm2_end_auth_session(chip); return rc; } - tpm_buf_append_hmac_session(chip, &buf, 0, NULL, 0); + tpm_buf_append_hmac_session(chip, buf, 0, NULL, 0); } else { - tpm_buf_append_handle(&buf, pcr_idx); - tpm_buf_append_auth(chip, &buf, NULL, 0); + tpm_buf_append_handle(buf, pcr_idx); + tpm_buf_append_auth(chip, buf, NULL, 0); } - tpm_buf_append_u32(&buf, chip->nr_allocated_banks); + tpm_buf_append_u32(buf, chip->nr_allocated_banks); for (i = 0; i < chip->nr_allocated_banks; i++) { - tpm_buf_append_u16(&buf, digests[i].alg_id); - tpm_buf_append(&buf, (const unsigned char *)&digests[i].digest, + tpm_buf_append_u16(buf, digests[i].alg_id); + tpm_buf_append(buf, (const unsigned char *)&digests[i].digest, chip->allocated_banks[i].digest_size); } if (!disable_pcr_integrity) { - rc = tpm_buf_fill_hmac_session(chip, &buf); - if (rc) { - tpm_buf_destroy(&buf); + rc = tpm_buf_fill_hmac_session(chip, buf); + if (rc) return rc; - } } - rc = tpm_transmit_cmd(chip, &buf, 0, "attempting extend a PCR value"); + rc = tpm_transmit_cmd(chip, buf, 0, "attempting extend a PCR value"); if (!disable_pcr_integrity) - rc = tpm_buf_check_hmac_response(chip, &buf, rc); - - tpm_buf_destroy(&buf); + rc = tpm_buf_check_hmac_response(chip, buf, rc); return rc; } @@ -242,7 +241,6 @@ int tpm2_get_random(struct tpm_chip *chip, u8 *dest, size_t max) { struct tpm2_get_random_out *out; struct tpm_header *head; - struct tpm_buf buf; u32 recd; u32 num_bytes = max; int err; @@ -251,6 +249,8 @@ int tpm2_get_random(struct tpm_chip *chip, u8 *dest, size_t max) u8 *dest_ptr = dest; off_t offset; + struct tpm_buf *buf __free(kfree) = NULL; + if (!num_bytes || max > TPM_MAX_RNG_DATA) return -EINVAL; @@ -258,55 +258,58 @@ int tpm2_get_random(struct tpm_chip *chip, u8 *dest, size_t max) if (err) return err; - err = tpm_buf_init(&buf, 0, 0); - if (err) { + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) { tpm2_end_auth_session(chip); - return err; + return -ENOMEM; } + tpm_buf_init(buf, TPM_BUFSIZE); + do { - tpm_buf_reset(&buf, TPM2_ST_SESSIONS, TPM2_CC_GET_RANDOM); + tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_GET_RANDOM); if (tpm2_chip_auth(chip)) { - tpm_buf_append_hmac_session(chip, &buf, + tpm_buf_append_hmac_session(chip, buf, TPM2_SA_ENCRYPT | TPM2_SA_CONTINUE_SESSION, NULL, 0); } else { - offset = buf.handles * 4 + TPM_HEADER_SIZE; - head = (struct tpm_header *)buf.data; - if (tpm_buf_length(&buf) == offset) + offset = buf->handles * 4 + TPM_HEADER_SIZE; + head = (struct tpm_header *)buf->data; + if (tpm_buf_length(buf) == offset) head->tag = cpu_to_be16(TPM2_ST_NO_SESSIONS); } - tpm_buf_append_u16(&buf, num_bytes); - err = tpm_buf_fill_hmac_session(chip, &buf); + tpm_buf_append_u16(buf, num_bytes); + err = tpm_buf_fill_hmac_session(chip, buf); if (err) - goto out; + return err; - err = tpm_transmit_cmd(chip, &buf, + err = tpm_transmit_cmd(chip, buf, offsetof(struct tpm2_get_random_out, buffer), "attempting get random"); - err = tpm_buf_check_hmac_response(chip, &buf, err); + err = tpm_buf_check_hmac_response(chip, buf, err); if (err) { if (err > 0) err = -EIO; - goto out; + tpm2_end_auth_session(chip); + return err; } - head = (struct tpm_header *)buf.data; + head = (struct tpm_header *)buf->data; offset = TPM_HEADER_SIZE; /* Skip the parameter size field: */ if (be16_to_cpu(head->tag) == TPM2_ST_SESSIONS) offset += 4; - out = (struct tpm2_get_random_out *)&buf.data[offset]; + out = (struct tpm2_get_random_out *)&buf->data[offset]; recd = min_t(u32, be16_to_cpu(out->size), num_bytes); - if (tpm_buf_length(&buf) < + if (tpm_buf_length(buf) < TPM_HEADER_SIZE + offsetof(struct tpm2_get_random_out, buffer) + recd) { - err = -EFAULT; - goto out; + tpm2_end_auth_session(chip); + return -EFAULT; } memcpy(dest_ptr, out->buffer, recd); @@ -315,13 +318,7 @@ int tpm2_get_random(struct tpm_chip *chip, u8 *dest, size_t max) num_bytes -= recd; } while (retries-- && total < max); - tpm_buf_destroy(&buf); - return total ? total : -EIO; -out: - tpm_buf_destroy(&buf); - tpm2_end_auth_session(chip); - return err; } /** @@ -331,20 +328,18 @@ int tpm2_get_random(struct tpm_chip *chip, u8 *dest, size_t max) */ void tpm2_flush_context(struct tpm_chip *chip, u32 handle) { - struct tpm_buf buf; - int rc; - - rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_FLUSH_CONTEXT); - if (rc) { + struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) { dev_warn(&chip->dev, "0x%08x was not flushed, out of memory\n", handle); return; } - tpm_buf_append_u32(&buf, handle); + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_FLUSH_CONTEXT); + tpm_buf_append_u32(buf, handle); - tpm_transmit_cmd(chip, &buf, 0, "flushing context"); - tpm_buf_destroy(&buf); + tpm_transmit_cmd(chip, buf, 0, "flushing context"); } EXPORT_SYMBOL_GPL(tpm2_flush_context); @@ -363,19 +358,21 @@ ssize_t tpm2_get_tpm_pt(struct tpm_chip *chip, u32 property_id, u32 *value, const char *desc) { struct tpm2_get_cap_out *out; - struct tpm_buf buf; int rc; - rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_GET_CAPABILITY); - if (rc) - return rc; - tpm_buf_append_u32(&buf, TPM2_CAP_TPM_PROPERTIES); - tpm_buf_append_u32(&buf, property_id); - tpm_buf_append_u32(&buf, 1); - rc = tpm_transmit_cmd(chip, &buf, 0, NULL); + struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_GET_CAPABILITY); + tpm_buf_append_u32(buf, TPM2_CAP_TPM_PROPERTIES); + tpm_buf_append_u32(buf, property_id); + tpm_buf_append_u32(buf, 1); + rc = tpm_transmit_cmd(chip, buf, 0, NULL); if (!rc) { out = (struct tpm2_get_cap_out *) - &buf.data[TPM_HEADER_SIZE]; + &buf->data[TPM_HEADER_SIZE]; /* * To prevent failing boot up of some systems, Infineon TPM2.0 * returns SUCCESS on TPM2_Startup in field upgrade mode. Also @@ -387,7 +384,6 @@ ssize_t tpm2_get_tpm_pt(struct tpm_chip *chip, u32 property_id, u32 *value, else rc = -ENODATA; } - tpm_buf_destroy(&buf); return rc; } EXPORT_SYMBOL_GPL(tpm2_get_tpm_pt); @@ -404,15 +400,14 @@ EXPORT_SYMBOL_GPL(tpm2_get_tpm_pt); */ void tpm2_shutdown(struct tpm_chip *chip, u16 shutdown_type) { - struct tpm_buf buf; - int rc; - - rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_SHUTDOWN); - if (rc) + struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) return; - tpm_buf_append_u16(&buf, shutdown_type); - tpm_transmit_cmd(chip, &buf, 0, "stopping the TPM"); - tpm_buf_destroy(&buf); + + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_SHUTDOWN); + tpm_buf_append_u16(buf, shutdown_type); + tpm_transmit_cmd(chip, buf, 0, "stopping the TPM"); } /** @@ -430,20 +425,21 @@ void tpm2_shutdown(struct tpm_chip *chip, u16 shutdown_type) */ static int tpm2_do_selftest(struct tpm_chip *chip) { - struct tpm_buf buf; int full; int rc; for (full = 0; full < 2; full++) { - rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_SELF_TEST); - if (rc) - return rc; + struct tpm_buf *buf __free(kfree) = NULL; - tpm_buf_append_u8(&buf, full); - rc = tpm_transmit_cmd(chip, &buf, 0, + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_SELF_TEST); + tpm_buf_append_u8(buf, full); + rc = tpm_transmit_cmd(chip, buf, 0, "attempting the self test"); - tpm_buf_destroy(&buf); - if (rc == TPM2_RC_TESTING) rc = TPM2_RC_SUCCESS; if (rc == TPM2_RC_INITIALIZE || rc == TPM2_RC_SUCCESS) @@ -468,23 +464,24 @@ static int tpm2_do_selftest(struct tpm_chip *chip) int tpm2_probe(struct tpm_chip *chip) { struct tpm_header *out; - struct tpm_buf buf; int rc; - rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_GET_CAPABILITY); - if (rc) - return rc; - tpm_buf_append_u32(&buf, TPM2_CAP_TPM_PROPERTIES); - tpm_buf_append_u32(&buf, TPM_PT_TOTAL_COMMANDS); - tpm_buf_append_u32(&buf, 1); - rc = tpm_transmit_cmd(chip, &buf, 0, NULL); + struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_GET_CAPABILITY); + tpm_buf_append_u32(buf, TPM2_CAP_TPM_PROPERTIES); + tpm_buf_append_u32(buf, TPM_PT_TOTAL_COMMANDS); + tpm_buf_append_u32(buf, 1); + rc = tpm_transmit_cmd(chip, buf, 0, NULL); /* We ignore TPM return codes on purpose. */ if (rc >= 0) { - out = (struct tpm_header *)buf.data; + out = (struct tpm_header *)buf->data; if (be16_to_cpu(out->tag) == TPM2_ST_NO_SESSIONS) chip->flags |= TPM_CHIP_FLAG_TPM2; } - tpm_buf_destroy(&buf); return 0; } EXPORT_SYMBOL_GPL(tpm2_probe); @@ -518,7 +515,6 @@ static int tpm2_init_bank_info(struct tpm_chip *chip, u32 bank_index) ssize_t tpm2_get_pcr_allocation(struct tpm_chip *chip) { struct tpm2_pcr_selection pcr_selection; - struct tpm_buf buf; void *marker; void *end; void *pcr_select_offset; @@ -530,31 +526,32 @@ ssize_t tpm2_get_pcr_allocation(struct tpm_chip *chip) int rc; int i = 0; - rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_GET_CAPABILITY); + struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_GET_CAPABILITY); + tpm_buf_append_u32(buf, TPM2_CAP_PCRS); + tpm_buf_append_u32(buf, 0); + tpm_buf_append_u32(buf, 1); + + rc = tpm_transmit_cmd(chip, buf, 9, "get tpm pcr allocation"); if (rc) return rc; - tpm_buf_append_u32(&buf, TPM2_CAP_PCRS); - tpm_buf_append_u32(&buf, 0); - tpm_buf_append_u32(&buf, 1); - - rc = tpm_transmit_cmd(chip, &buf, 9, "get tpm pcr allocation"); - if (rc) - goto out; - nr_possible_banks = be32_to_cpup( - (__be32 *)&buf.data[TPM_HEADER_SIZE + 5]); + (__be32 *)&buf->data[TPM_HEADER_SIZE + 5]); if (nr_possible_banks > TPM2_MAX_PCR_BANKS) { pr_err("tpm: out of bank capacity: %u > %u\n", nr_possible_banks, TPM2_MAX_PCR_BANKS); - rc = -ENOMEM; - goto out; + return -ENOMEM; } - marker = &buf.data[TPM_HEADER_SIZE + 9]; + marker = &buf->data[TPM_HEADER_SIZE + 9]; - rsp_len = be32_to_cpup((__be32 *)&buf.data[2]); - end = &buf.data[rsp_len]; + rsp_len = be32_to_cpup((__be32 *)&buf->data[2]); + end = &buf->data[rsp_len]; for (i = 0; i < nr_possible_banks; i++) { pcr_select_offset = marker + @@ -586,21 +583,20 @@ ssize_t tpm2_get_pcr_allocation(struct tpm_chip *chip) } chip->nr_allocated_banks = nr_alloc_banks; -out: - tpm_buf_destroy(&buf); return rc; } int tpm2_get_cc_attrs_tbl(struct tpm_chip *chip) { - struct tpm_buf buf; u32 nr_commands; __be32 *attrs; u32 cc; int i; int rc; + struct tpm_buf *buf __free(kfree) = NULL; + rc = tpm2_get_tpm_pt(chip, TPM_PT_TOTAL_COMMANDS, &nr_commands, NULL); if (rc) goto out; @@ -617,30 +613,31 @@ int tpm2_get_cc_attrs_tbl(struct tpm_chip *chip) goto out; } - rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_GET_CAPABILITY); - if (rc) - goto out; - - tpm_buf_append_u32(&buf, TPM2_CAP_COMMANDS); - tpm_buf_append_u32(&buf, TPM2_CC_FIRST); - tpm_buf_append_u32(&buf, nr_commands); - - rc = tpm_transmit_cmd(chip, &buf, 9 + 4 * nr_commands, NULL); - if (rc) { - tpm_buf_destroy(&buf); + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) { + rc = -ENOMEM; goto out; } + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_GET_CAPABILITY); + tpm_buf_append_u32(buf, TPM2_CAP_COMMANDS); + tpm_buf_append_u32(buf, TPM2_CC_FIRST); + tpm_buf_append_u32(buf, nr_commands); + + rc = tpm_transmit_cmd(chip, buf, 9 + 4 * nr_commands, NULL); + if (rc) + goto out; + if (nr_commands != - be32_to_cpup((__be32 *)&buf.data[TPM_HEADER_SIZE + 5])) { + be32_to_cpup((__be32 *)&buf->data[TPM_HEADER_SIZE + 5])) { rc = -EFAULT; - tpm_buf_destroy(&buf); goto out; } chip->nr_commands = nr_commands; - attrs = (__be32 *)&buf.data[TPM_HEADER_SIZE + 9]; + attrs = (__be32 *)&buf->data[TPM_HEADER_SIZE + 9]; for (i = 0; i < nr_commands; i++, attrs++) { chip->cc_attrs_tbl[i] = be32_to_cpup(attrs); cc = chip->cc_attrs_tbl[i] & 0xFFFF; @@ -652,8 +649,6 @@ int tpm2_get_cc_attrs_tbl(struct tpm_chip *chip) } } - tpm_buf_destroy(&buf); - out: if (rc > 0) rc = -ENODEV; @@ -674,20 +669,18 @@ EXPORT_SYMBOL_GPL(tpm2_get_cc_attrs_tbl); static int tpm2_startup(struct tpm_chip *chip) { - struct tpm_buf buf; - int rc; + struct tpm_buf *buf __free(kfree) = NULL; dev_info(&chip->dev, "starting up the TPM manually\n"); - rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_STARTUP); - if (rc < 0) - return rc; + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; - tpm_buf_append_u16(&buf, TPM2_SU_CLEAR); - rc = tpm_transmit_cmd(chip, &buf, 0, "attempting to start the TPM"); - tpm_buf_destroy(&buf); - - return rc; + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_STARTUP); + tpm_buf_append_u16(buf, TPM2_SU_CLEAR); + return tpm_transmit_cmd(chip, buf, 0, "attempting to start the TPM"); } /** diff --git a/drivers/char/tpm/tpm2-sessions.c b/drivers/char/tpm/tpm2-sessions.c index 0d89643e6880..cf8f1fd6790b 100644 --- a/drivers/char/tpm/tpm2-sessions.c +++ b/drivers/char/tpm/tpm2-sessions.c @@ -167,8 +167,8 @@ static int tpm2_read_public(struct tpm_chip *chip, u32 handle, void *name) { u32 mso = tpm2_handle_mso(handle); off_t offset = TPM_HEADER_SIZE; + struct tpm_buf *buf __free(kfree) = NULL; int rc, name_size_alg; - struct tpm_buf buf; if (mso != TPM2_MSO_PERSISTENT && mso != TPM2_MSO_VOLATILE && mso != TPM2_MSO_NVRAM) { @@ -176,50 +176,40 @@ static int tpm2_read_public(struct tpm_chip *chip, u32 handle, void *name) return sizeof(u32); } - rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_READ_PUBLIC); + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_READ_PUBLIC); + tpm_buf_append_u32(buf, handle); + + rc = tpm_transmit_cmd(chip, buf, 0, "TPM2_ReadPublic"); if (rc) - return rc; - - tpm_buf_append_u32(&buf, handle); - - rc = tpm_transmit_cmd(chip, &buf, 0, "TPM2_ReadPublic"); - if (rc) { - tpm_buf_destroy(&buf); return tpm_ret_to_err(rc); - } /* Skip TPMT_PUBLIC: */ - offset += tpm_buf_read_u16(&buf, &offset); + offset += tpm_buf_read_u16(buf, &offset); /* * Ensure space for the length field of TPM2B_NAME and hashAlg field of * TPMT_HA (the extra four bytes). */ - if (offset + 4 > tpm_buf_length(&buf)) { - tpm_buf_destroy(&buf); + if (offset + 4 > tpm_buf_length(buf)) return -EIO; - } - rc = tpm_buf_read_u16(&buf, &offset); - name_size_alg = name_size(&buf.data[offset]); - - if (name_size_alg < 0) { - tpm_buf_destroy(&buf); + rc = tpm_buf_read_u16(buf, &offset); + name_size_alg = name_size(&buf->data[offset]); + if (name_size_alg < 0) return name_size_alg; - } - if (rc != name_size_alg) { - tpm_buf_destroy(&buf); + if (rc != name_size_alg) return -EIO; - } - if (offset + rc > tpm_buf_length(&buf)) { - tpm_buf_destroy(&buf); + if (offset + rc > tpm_buf_length(buf)) return -EIO; - } - memcpy(name, &buf.data[offset], rc); - tpm_buf_destroy(&buf); + memcpy(name, &buf->data[offset], rc); return name_size_alg; } #endif /* CONFIG_TCG_TPM2_HMAC */ @@ -1005,8 +995,8 @@ static int tpm2_load_null(struct tpm_chip *chip, u32 *null_key) */ int tpm2_start_auth_session(struct tpm_chip *chip) { + struct tpm_buf *buf __free(kfree) = NULL; struct tpm2_auth *auth; - struct tpm_buf buf; u32 null_key; int rc; @@ -1020,58 +1010,61 @@ int tpm2_start_auth_session(struct tpm_chip *chip) return -ENOMEM; rc = tpm2_load_null(chip, &null_key); - if (rc) - goto out; + if (rc) { + kfree_sensitive(auth); + return rc; + } auth->session = TPM_HEADER_SIZE; - rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_START_AUTH_SESS); - if (rc) - goto out; + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) { + kfree_sensitive(auth); + return -ENOMEM; + } + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_START_AUTH_SESS); /* salt key handle */ - tpm_buf_append_u32(&buf, null_key); + tpm_buf_append_u32(buf, null_key); /* bind key handle */ - tpm_buf_append_u32(&buf, TPM2_RH_NULL); + tpm_buf_append_u32(buf, TPM2_RH_NULL); /* nonce caller */ get_random_bytes(auth->our_nonce, sizeof(auth->our_nonce)); - tpm_buf_append_u16(&buf, sizeof(auth->our_nonce)); - tpm_buf_append(&buf, auth->our_nonce, sizeof(auth->our_nonce)); + tpm_buf_append_u16(buf, sizeof(auth->our_nonce)); + tpm_buf_append(buf, auth->our_nonce, sizeof(auth->our_nonce)); /* append encrypted salt and squirrel away unencrypted in auth */ - rc = tpm_buf_append_salt(&buf, chip, auth); + rc = tpm_buf_append_salt(buf, chip, auth); if (rc) { tpm2_flush_context(chip, null_key); - tpm_buf_destroy(&buf); - goto out; + kfree_sensitive(auth); + return rc; } /* session type (HMAC, audit or policy) */ - tpm_buf_append_u8(&buf, TPM2_SE_HMAC); + tpm_buf_append_u8(buf, TPM2_SE_HMAC); /* symmetric encryption parameters */ /* symmetric algorithm */ - tpm_buf_append_u16(&buf, TPM_ALG_AES); + tpm_buf_append_u16(buf, TPM_ALG_AES); /* bits for symmetric algorithm */ - tpm_buf_append_u16(&buf, AES_KEY_BITS); + tpm_buf_append_u16(buf, AES_KEY_BITS); /* symmetric algorithm mode (must be CFB) */ - tpm_buf_append_u16(&buf, TPM_ALG_CFB); + tpm_buf_append_u16(buf, TPM_ALG_CFB); /* hash algorithm for session */ - tpm_buf_append_u16(&buf, TPM_ALG_SHA256); + tpm_buf_append_u16(buf, TPM_ALG_SHA256); - rc = tpm_ret_to_err(tpm_transmit_cmd(chip, &buf, 0, "StartAuthSession")); + rc = tpm_ret_to_err(tpm_transmit_cmd(chip, buf, 0, "StartAuthSession")); tpm2_flush_context(chip, null_key); if (rc == TPM2_RC_SUCCESS) - rc = tpm2_parse_start_auth_session(auth, &buf); - - tpm_buf_destroy(&buf); + rc = tpm2_parse_start_auth_session(auth, buf); if (rc == TPM2_RC_SUCCESS) { chip->auth = auth; return 0; } -out: kfree_sensitive(auth); return rc; } @@ -1285,19 +1278,21 @@ static int tpm2_parse_create_primary(struct tpm_chip *chip, struct tpm_buf *buf, static int tpm2_create_primary(struct tpm_chip *chip, u32 hierarchy, u32 *handle, u8 *name) { + struct tpm_buf *template __free(kfree) = NULL; + struct tpm_buf *buf __free(kfree) = NULL; int rc; - struct tpm_buf buf; - struct tpm_buf template; - rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_CREATE_PRIMARY); - if (rc) - return rc; + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; - rc = tpm_buf_init_sized(&template); - if (rc) { - tpm_buf_destroy(&buf); - return rc; - } + template = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!template) + return -ENOMEM; + + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_CREATE_PRIMARY); + tpm_buf_init_sized(template, TPM_BUFSIZE); /* * create the template. Note: in order for userspace to @@ -1309,75 +1304,72 @@ static int tpm2_create_primary(struct tpm_chip *chip, u32 hierarchy, */ /* key type */ - tpm_buf_append_u16(&template, TPM_ALG_ECC); + tpm_buf_append_u16(template, TPM_ALG_ECC); /* name algorithm */ - tpm_buf_append_u16(&template, TPM_ALG_SHA256); + tpm_buf_append_u16(template, TPM_ALG_SHA256); /* object properties */ - tpm_buf_append_u32(&template, TPM2_OA_NULL_KEY); + tpm_buf_append_u32(template, TPM2_OA_NULL_KEY); /* sauth policy (empty) */ - tpm_buf_append_u16(&template, 0); + tpm_buf_append_u16(template, 0); /* BEGIN parameters: key specific; for ECC*/ /* symmetric algorithm */ - tpm_buf_append_u16(&template, TPM_ALG_AES); + tpm_buf_append_u16(template, TPM_ALG_AES); /* bits for symmetric algorithm */ - tpm_buf_append_u16(&template, AES_KEY_BITS); + tpm_buf_append_u16(template, AES_KEY_BITS); /* algorithm mode (must be CFB) */ - tpm_buf_append_u16(&template, TPM_ALG_CFB); + tpm_buf_append_u16(template, TPM_ALG_CFB); /* scheme (NULL means any scheme) */ - tpm_buf_append_u16(&template, TPM_ALG_NULL); + tpm_buf_append_u16(template, TPM_ALG_NULL); /* ECC Curve ID */ - tpm_buf_append_u16(&template, TPM2_ECC_NIST_P256); + tpm_buf_append_u16(template, TPM2_ECC_NIST_P256); /* KDF Scheme */ - tpm_buf_append_u16(&template, TPM_ALG_NULL); + tpm_buf_append_u16(template, TPM_ALG_NULL); /* unique: key specific; for ECC it is two zero size points */ - tpm_buf_append_u16(&template, 0); - tpm_buf_append_u16(&template, 0); + tpm_buf_append_u16(template, 0); + tpm_buf_append_u16(template, 0); /* END parameters */ /* primary handle */ - tpm_buf_append_u32(&buf, hierarchy); - tpm_buf_append_empty_auth(&buf, TPM2_RS_PW); + tpm_buf_append_u32(buf, hierarchy); + tpm_buf_append_empty_auth(buf, TPM2_RS_PW); /* sensitive create size is 4 for two empty buffers */ - tpm_buf_append_u16(&buf, 4); + tpm_buf_append_u16(buf, 4); /* sensitive create auth data (empty) */ - tpm_buf_append_u16(&buf, 0); + tpm_buf_append_u16(buf, 0); /* sensitive create sensitive data (empty) */ - tpm_buf_append_u16(&buf, 0); + tpm_buf_append_u16(buf, 0); /* the public template */ - tpm_buf_append(&buf, template.data, template.length); - tpm_buf_destroy(&template); + tpm_buf_append(buf, template->data, template->length); /* outside info (empty) */ - tpm_buf_append_u16(&buf, 0); + tpm_buf_append_u16(buf, 0); /* creation PCR (none) */ - tpm_buf_append_u32(&buf, 0); + tpm_buf_append_u32(buf, 0); - rc = tpm_transmit_cmd(chip, &buf, 0, + rc = tpm_transmit_cmd(chip, buf, 0, "attempting to create NULL primary"); if (rc == TPM2_RC_SUCCESS) - rc = tpm2_parse_create_primary(chip, &buf, handle, hierarchy, + rc = tpm2_parse_create_primary(chip, buf, handle, hierarchy, name); - tpm_buf_destroy(&buf); - return rc; } diff --git a/drivers/char/tpm/tpm2-space.c b/drivers/char/tpm/tpm2-space.c index 7c1c0a174a2b..5b0f233db898 100644 --- a/drivers/char/tpm/tpm2-space.c +++ b/drivers/char/tpm/tpm2-space.c @@ -58,24 +58,25 @@ void tpm2_del_space(struct tpm_chip *chip, struct tpm_space *space) int tpm2_load_context(struct tpm_chip *chip, u8 *buf, unsigned int *offset, u32 *handle) { - struct tpm_buf tbuf; struct tpm2_context *ctx; unsigned int body_size; int rc; - rc = tpm_buf_init(&tbuf, TPM2_ST_NO_SESSIONS, TPM2_CC_CONTEXT_LOAD); - if (rc) - return rc; + struct tpm_buf *tbuf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!tbuf) + return -ENOMEM; + + tpm_buf_init(tbuf, TPM_BUFSIZE); + tpm_buf_reset(tbuf, TPM2_ST_NO_SESSIONS, TPM2_CC_CONTEXT_LOAD); ctx = (struct tpm2_context *)&buf[*offset]; body_size = sizeof(*ctx) + be16_to_cpu(ctx->blob_size); - tpm_buf_append(&tbuf, &buf[*offset], body_size); + tpm_buf_append(tbuf, &buf[*offset], body_size); - rc = tpm_transmit_cmd(chip, &tbuf, 4, NULL); + rc = tpm_transmit_cmd(chip, tbuf, 4, NULL); if (rc < 0) { dev_warn(&chip->dev, "%s: failed with a system error %d\n", __func__, rc); - tpm_buf_destroy(&tbuf); return -EFAULT; } else if (tpm2_rc_value(rc) == TPM2_RC_HANDLE || rc == TPM2_RC_REFERENCE_H0) { @@ -90,64 +91,55 @@ int tpm2_load_context(struct tpm_chip *chip, u8 *buf, * flushed outside the space */ *handle = 0; - tpm_buf_destroy(&tbuf); return -ENOENT; } else if (tpm2_rc_value(rc) == TPM2_RC_INTEGRITY) { - tpm_buf_destroy(&tbuf); return -EINVAL; } else if (rc > 0) { dev_warn(&chip->dev, "%s: failed with a TPM error 0x%04X\n", __func__, rc); - tpm_buf_destroy(&tbuf); return -EFAULT; } - *handle = be32_to_cpup((__be32 *)&tbuf.data[TPM_HEADER_SIZE]); + *handle = be32_to_cpup((__be32 *)&tbuf->data[TPM_HEADER_SIZE]); *offset += body_size; - - tpm_buf_destroy(&tbuf); return 0; } int tpm2_save_context(struct tpm_chip *chip, u32 handle, u8 *buf, unsigned int buf_size, unsigned int *offset) { - struct tpm_buf tbuf; unsigned int body_size; int rc; - rc = tpm_buf_init(&tbuf, TPM2_ST_NO_SESSIONS, TPM2_CC_CONTEXT_SAVE); - if (rc) - return rc; + struct tpm_buf *tbuf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!tbuf) + return -ENOMEM; - tpm_buf_append_u32(&tbuf, handle); + tpm_buf_init(tbuf, TPM_BUFSIZE); + tpm_buf_reset(tbuf, TPM2_ST_NO_SESSIONS, TPM2_CC_CONTEXT_SAVE); + tpm_buf_append_u32(tbuf, handle); - rc = tpm_transmit_cmd(chip, &tbuf, 0, NULL); + rc = tpm_transmit_cmd(chip, tbuf, 0, NULL); if (rc < 0) { dev_warn(&chip->dev, "%s: failed with a system error %d\n", __func__, rc); - tpm_buf_destroy(&tbuf); return -EFAULT; } else if (tpm2_rc_value(rc) == TPM2_RC_REFERENCE_H0) { - tpm_buf_destroy(&tbuf); return -ENOENT; } else if (rc) { dev_warn(&chip->dev, "%s: failed with a TPM error 0x%04X\n", __func__, rc); - tpm_buf_destroy(&tbuf); return -EFAULT; } - body_size = tpm_buf_length(&tbuf) - TPM_HEADER_SIZE; + body_size = tpm_buf_length(tbuf) - TPM_HEADER_SIZE; if ((*offset + body_size) > buf_size) { dev_warn(&chip->dev, "%s: out of backing storage\n", __func__); - tpm_buf_destroy(&tbuf); return -ENOMEM; } - memcpy(&buf[*offset], &tbuf.data[TPM_HEADER_SIZE], body_size); + memcpy(&buf[*offset], &tbuf->data[TPM_HEADER_SIZE], body_size); *offset += body_size; - tpm_buf_destroy(&tbuf); return 0; } diff --git a/drivers/char/tpm/tpm_vtpm_proxy.c b/drivers/char/tpm/tpm_vtpm_proxy.c index 7bb0f4d4a2ed..b81fd2a537df 100644 --- a/drivers/char/tpm/tpm_vtpm_proxy.c +++ b/drivers/char/tpm/tpm_vtpm_proxy.c @@ -395,40 +395,36 @@ static bool vtpm_proxy_tpm_req_canceled(struct tpm_chip *chip, u8 status) static int vtpm_proxy_request_locality(struct tpm_chip *chip, int locality) { - struct tpm_buf buf; int rc; const struct tpm_header *header; struct proxy_dev *proxy_dev = dev_get_drvdata(&chip->dev); + struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + tpm_buf_init(buf, TPM_BUFSIZE); if (chip->flags & TPM_CHIP_FLAG_TPM2) - rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, - TPM2_CC_SET_LOCALITY); + tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_SET_LOCALITY); else - rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, - TPM_ORD_SET_LOCALITY); - if (rc) - return rc; - tpm_buf_append_u8(&buf, locality); + tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_SET_LOCALITY); + + tpm_buf_append_u8(buf, locality); proxy_dev->state |= STATE_DRIVER_COMMAND; - rc = tpm_transmit_cmd(chip, &buf, 0, "attempting to set locality"); + rc = tpm_transmit_cmd(chip, buf, 0, "attempting to set locality"); proxy_dev->state &= ~STATE_DRIVER_COMMAND; - if (rc < 0) { - locality = rc; - goto out; - } + if (rc < 0) + return rc; - header = (const struct tpm_header *)buf.data; + header = (const struct tpm_header *)buf->data; rc = be32_to_cpu(header->return_code); if (rc) locality = -1; -out: - tpm_buf_destroy(&buf); - return locality; } diff --git a/include/linux/tpm.h b/include/linux/tpm.h index b357f8971d03..598dd53a10d8 100644 --- a/include/linux/tpm.h +++ b/include/linux/tpm.h @@ -208,13 +208,15 @@ enum tpm_buf_flags { }; /* - * A string buffer type for constructing TPM commands. + * A buffer for constructing and parsing TPM commands, responses and sized + * (TPM2B) buffers. */ struct tpm_buf { - u32 flags; - u32 length; - u8 *data; + u8 flags; + u16 length; + u16 capacity; u8 handles; + u8 data[]; }; struct tpm2_hash { @@ -222,12 +224,11 @@ struct tpm2_hash { unsigned int tpm_id; }; -int tpm_buf_init(struct tpm_buf *buf, u16 tag, u32 ordinal); +void tpm_buf_init(struct tpm_buf *buf, u16 buf_size); +void tpm_buf_init_sized(struct tpm_buf *buf, u16 buf_size); void tpm_buf_reset(struct tpm_buf *buf, u16 tag, u32 ordinal); -int tpm_buf_init_sized(struct tpm_buf *buf); void tpm_buf_reset_sized(struct tpm_buf *buf); -void tpm_buf_destroy(struct tpm_buf *buf); -u32 tpm_buf_length(struct tpm_buf *buf); +u16 tpm_buf_length(struct tpm_buf *buf); void tpm_buf_append(struct tpm_buf *buf, const u8 *new_data, u16 new_length); void tpm_buf_append_u8(struct tpm_buf *buf, const u8 value); void tpm_buf_append_u16(struct tpm_buf *buf, const u16 value); diff --git a/security/keys/trusted-keys/trusted_tpm1.c b/security/keys/trusted-keys/trusted_tpm1.c index ae256c540ca0..bf0bf7f36970 100644 --- a/security/keys/trusted-keys/trusted_tpm1.c +++ b/security/keys/trusted-keys/trusted_tpm1.c @@ -316,9 +316,8 @@ static int TSS_checkhmac2(unsigned char *buffer, * For key specific tpm requests, we will generate and send our * own TPM command packets using the drivers send function. */ -static int trusted_tpm_send(unsigned char *cmd, size_t buflen) +static int trusted_tpm_send(struct tpm_buf *buf) { - struct tpm_buf buf; int rc; if (!chip) @@ -328,12 +327,9 @@ static int trusted_tpm_send(unsigned char *cmd, size_t buflen) if (rc) return rc; - buf.flags = 0; - buf.length = buflen; - buf.data = cmd; - dump_tpm_buf(cmd); - rc = tpm_transmit_cmd(chip, &buf, 4, "sending data"); - dump_tpm_buf(cmd); + dump_tpm_buf(buf->data); + rc = tpm_transmit_cmd(chip, buf, 4, "sending data"); + dump_tpm_buf(buf->data); if (rc > 0) /* TPM error */ @@ -379,7 +375,7 @@ static int osap(struct tpm_buf *tb, struct osapsess *s, tpm_buf_append_u32(tb, handle); tpm_buf_append(tb, ononce, TPM_NONCE_SIZE); - ret = trusted_tpm_send(tb->data, tb->length); + ret = trusted_tpm_send(tb); if (ret < 0) return ret; @@ -403,7 +399,7 @@ static int oiap(struct tpm_buf *tb, uint32_t *handle, unsigned char *nonce) return -ENODEV; tpm_buf_reset(tb, TPM_TAG_RQU_COMMAND, TPM_ORD_OIAP); - ret = trusted_tpm_send(tb->data, tb->length); + ret = trusted_tpm_send(tb); if (ret < 0) return ret; @@ -512,7 +508,7 @@ static int tpm_seal(struct tpm_buf *tb, uint16_t keytype, tpm_buf_append_u8(tb, cont); tpm_buf_append(tb, td->pubauth, SHA1_DIGEST_SIZE); - ret = trusted_tpm_send(tb->data, tb->length); + ret = trusted_tpm_send(tb); if (ret < 0) goto out; @@ -603,7 +599,7 @@ static int tpm_unseal(struct tpm_buf *tb, tpm_buf_append_u8(tb, cont); tpm_buf_append(tb, authdata2, SHA1_DIGEST_SIZE); - ret = trusted_tpm_send(tb->data, tb->length); + ret = trusted_tpm_send(tb); if (ret < 0) { pr_info("authhmac failed (%d)\n", ret); return ret; @@ -630,23 +626,23 @@ static int tpm_unseal(struct tpm_buf *tb, static int key_seal(struct trusted_key_payload *p, struct trusted_key_options *o) { - struct tpm_buf tb; int ret; - ret = tpm_buf_init(&tb, 0, 0); - if (ret) - return ret; + struct tpm_buf *tb __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!tb) + return -ENOMEM; + + tpm_buf_init(tb, TPM_BUFSIZE); /* include migratable flag at end of sealed key */ p->key[p->key_len] = p->migratable; - ret = tpm_seal(&tb, o->keytype, o->keyhandle, o->keyauth, + ret = tpm_seal(tb, o->keytype, o->keyhandle, o->keyauth, p->key, p->key_len + 1, p->blob, &p->blob_len, o->blobauth, o->pcrinfo, o->pcrinfo_len); if (ret < 0) pr_info("srkseal failed (%d)\n", ret); - tpm_buf_destroy(&tb); return ret; } @@ -656,14 +652,15 @@ static int key_seal(struct trusted_key_payload *p, static int key_unseal(struct trusted_key_payload *p, struct trusted_key_options *o) { - struct tpm_buf tb; int ret; - ret = tpm_buf_init(&tb, 0, 0); - if (ret) - return ret; + struct tpm_buf *tb __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!tb) + return -ENOMEM; - ret = tpm_unseal(&tb, o->keyhandle, o->keyauth, p->blob, p->blob_len, + tpm_buf_init(tb, TPM_BUFSIZE); + + ret = tpm_unseal(tb, o->keyhandle, o->keyauth, p->blob, p->blob_len, o->blobauth, p->key, &p->key_len); if (ret < 0) pr_info("srkunseal failed (%d)\n", ret); @@ -671,7 +668,6 @@ static int key_unseal(struct trusted_key_payload *p, /* pull migratable flag out of sealed key */ p->migratable = p->key[--p->key_len]; - tpm_buf_destroy(&tb); return ret; } diff --git a/security/keys/trusted-keys/trusted_tpm2.c b/security/keys/trusted-keys/trusted_tpm2.c index 779a2e66ac20..67225dd562a9 100644 --- a/security/keys/trusted-keys/trusted_tpm2.c +++ b/security/keys/trusted-keys/trusted_tpm2.c @@ -233,7 +233,8 @@ int tpm2_seal_trusted(struct tpm_chip *chip, struct trusted_key_options *options) { off_t offset = TPM_HEADER_SIZE; - struct tpm_buf buf, sized; + struct tpm_buf *buf __free(kfree) = NULL; + struct tpm_buf *sized __free(kfree) = NULL; int blob_len = 0; int hash; u32 flags; @@ -254,97 +255,100 @@ int tpm2_seal_trusted(struct tpm_chip *chip, if (rc) goto out_put; - rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_CREATE); - if (rc) { + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) { + rc = -ENOMEM; tpm2_end_auth_session(chip); goto out_put; } - rc = tpm_buf_init_sized(&sized); - if (rc) { - tpm_buf_destroy(&buf); + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_CREATE); + + sized = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!sized) { + rc = -ENOMEM; tpm2_end_auth_session(chip); goto out_put; } - rc = tpm_buf_append_name(chip, &buf, options->keyhandle, NULL); + tpm_buf_init_sized(sized, TPM_BUFSIZE); + + rc = tpm_buf_append_name(chip, buf, options->keyhandle, NULL); if (rc) goto out; - tpm_buf_append_hmac_session(chip, &buf, TPM2_SA_DECRYPT, + tpm_buf_append_hmac_session(chip, buf, TPM2_SA_DECRYPT, options->keyauth, TPM_DIGEST_SIZE); /* sensitive */ - tpm_buf_append_u16(&sized, options->blobauth_len); + tpm_buf_append_u16(sized, options->blobauth_len); if (options->blobauth_len) - tpm_buf_append(&sized, options->blobauth, options->blobauth_len); + tpm_buf_append(sized, options->blobauth, options->blobauth_len); - tpm_buf_append_u16(&sized, payload->key_len); - tpm_buf_append(&sized, payload->key, payload->key_len); - tpm_buf_append(&buf, sized.data, sized.length); + tpm_buf_append_u16(sized, payload->key_len); + tpm_buf_append(sized, payload->key, payload->key_len); + tpm_buf_append(buf, sized->data, sized->length); /* public */ - tpm_buf_reset_sized(&sized); - tpm_buf_append_u16(&sized, TPM_ALG_KEYEDHASH); - tpm_buf_append_u16(&sized, hash); + tpm_buf_reset_sized(sized); + tpm_buf_append_u16(sized, TPM_ALG_KEYEDHASH); + tpm_buf_append_u16(sized, hash); /* key properties */ flags = 0; flags |= options->policydigest_len ? 0 : TPM2_OA_USER_WITH_AUTH; flags |= payload->migratable ? 0 : (TPM2_OA_FIXED_TPM | TPM2_OA_FIXED_PARENT); - tpm_buf_append_u32(&sized, flags); + tpm_buf_append_u32(sized, flags); /* policy */ - tpm_buf_append_u16(&sized, options->policydigest_len); + tpm_buf_append_u16(sized, options->policydigest_len); if (options->policydigest_len) - tpm_buf_append(&sized, options->policydigest, options->policydigest_len); + tpm_buf_append(sized, options->policydigest, options->policydigest_len); /* public parameters */ - tpm_buf_append_u16(&sized, TPM_ALG_NULL); - tpm_buf_append_u16(&sized, 0); + tpm_buf_append_u16(sized, TPM_ALG_NULL); + tpm_buf_append_u16(sized, 0); - tpm_buf_append(&buf, sized.data, sized.length); + tpm_buf_append(buf, sized->data, sized->length); /* outside info */ - tpm_buf_append_u16(&buf, 0); + tpm_buf_append_u16(buf, 0); /* creation PCR */ - tpm_buf_append_u32(&buf, 0); + tpm_buf_append_u32(buf, 0); - if (buf.flags & TPM_BUF_INVALID) { + if (buf->flags & TPM_BUF_INVALID) { rc = -E2BIG; tpm2_end_auth_session(chip); goto out; } - rc = tpm_buf_fill_hmac_session(chip, &buf); + rc = tpm_buf_fill_hmac_session(chip, buf); if (rc) goto out; - rc = tpm_transmit_cmd(chip, &buf, 4, "sealing data"); - rc = tpm_buf_check_hmac_response(chip, &buf, rc); + rc = tpm_transmit_cmd(chip, buf, 4, "sealing data"); + rc = tpm_buf_check_hmac_response(chip, buf, rc); if (rc) goto out; - blob_len = tpm_buf_read_u32(&buf, &offset); - if (blob_len > MAX_BLOB_SIZE || buf.flags & TPM_BUF_INVALID) { + blob_len = tpm_buf_read_u32(buf, &offset); + if (blob_len > MAX_BLOB_SIZE || buf->flags & TPM_BUF_INVALID) { rc = -E2BIG; goto out; } - if (buf.length - offset < blob_len) { + if (buf->length - offset < blob_len) { rc = -EFAULT; goto out; } - blob_len = tpm2_key_encode(payload, options, &buf.data[offset], blob_len); + blob_len = tpm2_key_encode(payload, options, &buf->data[offset], blob_len); if (blob_len < 0) rc = blob_len; out: - tpm_buf_destroy(&sized); - tpm_buf_destroy(&buf); - if (!rc) payload->blob_len = blob_len; @@ -372,7 +376,7 @@ static int tpm2_load_cmd(struct tpm_chip *chip, u32 *blob_handle) { u8 *blob_ref __free(kfree) = NULL; - struct tpm_buf buf; + struct tpm_buf *buf __free(kfree) = NULL; unsigned int private_len; unsigned int public_len; unsigned int blob_len; @@ -426,39 +430,38 @@ static int tpm2_load_cmd(struct tpm_chip *chip, if (rc) return rc; - rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_LOAD); - if (rc) { + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) { tpm2_end_auth_session(chip); - return rc; + return -ENOMEM; } - rc = tpm_buf_append_name(chip, &buf, options->keyhandle, NULL); - if (rc) - goto out; + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_LOAD); - tpm_buf_append_hmac_session(chip, &buf, 0, options->keyauth, + rc = tpm_buf_append_name(chip, buf, options->keyhandle, NULL); + if (rc) + return rc; + + tpm_buf_append_hmac_session(chip, buf, 0, options->keyauth, TPM_DIGEST_SIZE); - tpm_buf_append(&buf, blob, blob_len); + tpm_buf_append(buf, blob, blob_len); - if (buf.flags & TPM_BUF_INVALID) { - rc = -E2BIG; + if (buf->flags & TPM_BUF_INVALID) { tpm2_end_auth_session(chip); - goto out; + return -E2BIG; } - rc = tpm_buf_fill_hmac_session(chip, &buf); + rc = tpm_buf_fill_hmac_session(chip, buf); if (rc) - goto out; + return rc; - rc = tpm_transmit_cmd(chip, &buf, 4, "loading blob"); - rc = tpm_buf_check_hmac_response(chip, &buf, rc); + rc = tpm_transmit_cmd(chip, buf, 4, "loading blob"); + rc = tpm_buf_check_hmac_response(chip, buf, rc); if (!rc) *blob_handle = be32_to_cpup( - (__be32 *) &buf.data[TPM_HEADER_SIZE]); - -out: - tpm_buf_destroy(&buf); + (__be32 *)&buf->data[TPM_HEADER_SIZE]); return tpm_ret_to_err(rc); } @@ -481,7 +484,7 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip, u32 blob_handle) { struct tpm_header *head; - struct tpm_buf buf; + struct tpm_buf *buf __free(kfree) = NULL; u16 data_len; int offset; u8 *data; @@ -491,18 +494,21 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip, if (rc) return rc; - rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_UNSEAL); - if (rc) { + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) { tpm2_end_auth_session(chip); - return rc; + return -ENOMEM; } - rc = tpm_buf_append_name(chip, &buf, blob_handle, NULL); + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_UNSEAL); + + rc = tpm_buf_append_name(chip, buf, blob_handle, NULL); if (rc) - goto out; + return rc; if (!options->policyhandle) { - tpm_buf_append_hmac_session(chip, &buf, TPM2_SA_ENCRYPT, + tpm_buf_append_hmac_session(chip, buf, TPM2_SA_ENCRYPT, options->blobauth, options->blobauth_len); } else { @@ -517,39 +523,36 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip, * could repeat our actions with the exfiltrated * password. */ - tpm2_buf_append_auth(&buf, options->policyhandle, + tpm2_buf_append_auth(buf, options->policyhandle, NULL /* nonce */, 0, 0, options->blobauth, options->blobauth_len); if (tpm2_chip_auth(chip)) { - tpm_buf_append_hmac_session(chip, &buf, TPM2_SA_ENCRYPT, NULL, 0); + tpm_buf_append_hmac_session(chip, buf, TPM2_SA_ENCRYPT, + NULL, 0); } else { - offset = buf.handles * 4 + TPM_HEADER_SIZE; - head = (struct tpm_header *)buf.data; - if (tpm_buf_length(&buf) == offset) + offset = buf->handles * 4 + TPM_HEADER_SIZE; + head = (struct tpm_header *)buf->data; + if (tpm_buf_length(buf) == offset) head->tag = cpu_to_be16(TPM2_ST_NO_SESSIONS); } } - rc = tpm_buf_fill_hmac_session(chip, &buf); + rc = tpm_buf_fill_hmac_session(chip, buf); if (rc) - goto out; + return rc; - rc = tpm_transmit_cmd(chip, &buf, 6, "unsealing"); - rc = tpm_buf_check_hmac_response(chip, &buf, rc); + rc = tpm_transmit_cmd(chip, buf, 6, "unsealing"); + rc = tpm_buf_check_hmac_response(chip, buf, rc); if (!rc) { data_len = be16_to_cpup( - (__be16 *) &buf.data[TPM_HEADER_SIZE + 4]); - if (data_len < MIN_KEY_SIZE || data_len > MAX_KEY_SIZE) { - rc = -EFAULT; - goto out; - } + (__be16 *)&buf->data[TPM_HEADER_SIZE + 4]); + if (data_len < MIN_KEY_SIZE || data_len > MAX_KEY_SIZE) + return -EFAULT; - if (tpm_buf_length(&buf) < TPM_HEADER_SIZE + 6 + data_len) { - rc = -EFAULT; - goto out; - } - data = &buf.data[TPM_HEADER_SIZE + 6]; + if (tpm_buf_length(buf) < TPM_HEADER_SIZE + 6 + data_len) + return -EFAULT; + data = &buf->data[TPM_HEADER_SIZE + 6]; if (payload->old_format) { /* migratable flag is at the end of the key */ @@ -566,8 +569,6 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip, } } -out: - tpm_buf_destroy(&buf); return tpm_ret_to_err(rc); } From 55ba91d4c54ab31ee8387bae40e799de467f8269 Mon Sep 17 00:00:00 2001 From: Ross Philipson Date: Sat, 11 Jul 2026 09:01:10 -0700 Subject: [PATCH 1175/1328] tpm-buf: Add TPM buffer support header for standalone reuse Extract all the functions and definitions for TPM buffer handling and separate them into their own header. Signed-off-by: Ross Philipson Reviewed-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/tpm-buf.c | 3 +- include/linux/tpm.h | 34 +-------------------- include/linux/tpm_buf.h | 60 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 34 deletions(-) create mode 100644 include/linux/tpm_buf.h diff --git a/drivers/char/tpm/tpm-buf.c b/drivers/char/tpm/tpm-buf.c index 1e5c11c312a8..233e81d3f149 100644 --- a/drivers/char/tpm/tpm-buf.c +++ b/drivers/char/tpm/tpm-buf.c @@ -4,7 +4,8 @@ */ #include -#include +#include +#include static void __tpm_buf_size_invariant(struct tpm_buf *buf, u16 buf_size) { diff --git a/include/linux/tpm.h b/include/linux/tpm.h index 598dd53a10d8..0db277af45c3 100644 --- a/include/linux/tpm.h +++ b/include/linux/tpm.h @@ -26,6 +26,7 @@ #include #include +#include struct tpm_chip; struct trusted_key_payload; @@ -200,44 +201,11 @@ enum tpm_chip_flags { #define to_tpm_chip(d) container_of(d, struct tpm_chip, dev) -enum tpm_buf_flags { - /* TPM2B format: */ - TPM_BUF_TPM2B = BIT(0), - /* The buffer is in invalid and unusable state: */ - TPM_BUF_INVALID = BIT(1), -}; - -/* - * A buffer for constructing and parsing TPM commands, responses and sized - * (TPM2B) buffers. - */ -struct tpm_buf { - u8 flags; - u16 length; - u16 capacity; - u8 handles; - u8 data[]; -}; - struct tpm2_hash { unsigned int crypto_id; unsigned int tpm_id; }; -void tpm_buf_init(struct tpm_buf *buf, u16 buf_size); -void tpm_buf_init_sized(struct tpm_buf *buf, u16 buf_size); -void tpm_buf_reset(struct tpm_buf *buf, u16 tag, u32 ordinal); -void tpm_buf_reset_sized(struct tpm_buf *buf); -u16 tpm_buf_length(struct tpm_buf *buf); -void tpm_buf_append(struct tpm_buf *buf, const u8 *new_data, u16 new_length); -void tpm_buf_append_u8(struct tpm_buf *buf, const u8 value); -void tpm_buf_append_u16(struct tpm_buf *buf, const u16 value); -void tpm_buf_append_u32(struct tpm_buf *buf, const u32 value); -u8 tpm_buf_read_u8(struct tpm_buf *buf, off_t *offset); -u16 tpm_buf_read_u16(struct tpm_buf *buf, off_t *offset); -u32 tpm_buf_read_u32(struct tpm_buf *buf, off_t *offset); -void tpm_buf_append_handle(struct tpm_buf *buf, u32 handle); - /* * Check if TPM device is in the firmware upgrade mode. */ diff --git a/include/linux/tpm_buf.h b/include/linux/tpm_buf.h new file mode 100644 index 000000000000..f8c105d8b8bf --- /dev/null +++ b/include/linux/tpm_buf.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Following copyright information was take from the original file + * where the definitions were moved from: + * + * Copyright (C) 2004,2007,2008 IBM Corporation + * + * Authors: + * Leendert van Doorn + * Dave Safford + * Reiner Sailer + * Kylene Hall + * Debora Velarde + * + * Maintained by: + * + * Device driver for TCG/TCPA TPM (trusted platform module). + * Specifications at www.trustedcomputinggroup.org + */ + +#ifndef __LINUX_TPM_BUF_H__ +#define __LINUX_TPM_BUF_H__ + +#include +#include + +enum tpm_buf_flags { + /* TPM2B format: */ + TPM_BUF_TPM2B = BIT(0), + /* The buffer is in invalid and unusable state: */ + TPM_BUF_INVALID = BIT(1), +}; + +/* + * A buffer for constructing and parsing TPM commands, responses and sized + * (TPM2B) buffers. + */ +struct tpm_buf { + u8 flags; + u8 handles; + u16 length; + u16 capacity; + u8 data[]; +}; + +void tpm_buf_init(struct tpm_buf *buf, u16 buf_size); +void tpm_buf_init_sized(struct tpm_buf *buf, u16 buf_size); +void tpm_buf_reset(struct tpm_buf *buf, u16 tag, u32 ordinal); +void tpm_buf_reset_sized(struct tpm_buf *buf); +u16 tpm_buf_length(struct tpm_buf *buf); +void tpm_buf_append(struct tpm_buf *buf, const u8 *new_data, u16 new_length); +void tpm_buf_append_u8(struct tpm_buf *buf, const u8 value); +void tpm_buf_append_u16(struct tpm_buf *buf, const u16 value); +void tpm_buf_append_u32(struct tpm_buf *buf, const u32 value); +u8 tpm_buf_read_u8(struct tpm_buf *buf, off_t *offset); +u16 tpm_buf_read_u16(struct tpm_buf *buf, off_t *offset); +u32 tpm_buf_read_u32(struct tpm_buf *buf, off_t *offset); +void tpm_buf_append_handle(struct tpm_buf *buf, u32 handle); + +#endif From 8c963d1738fdca400082ff5f9d99e083de4f4e70 Mon Sep 17 00:00:00 2001 From: Daeho Jeong Date: Mon, 24 Aug 2026 18:53:03 -0700 Subject: [PATCH 1176/1328] f2fs: accurately adjust free_sections during free_segment_range In free_segment_range(), MAIN_SECS(sbi) is temporarily reduced by `secs` to restrict block allocation to the safe remaining main area while valid blocks in the truncated range are evacuated by GC. However, FREE_I(sbi)->free_sections tracks the total number of free sections across the whole filesystem. If any sections within the truncated range were already free upon entering free_segment_range(), failing to deduct them from free_sections causes the filesystem to overestimate available free sections in the active, reduced main area. This leads to inconsistent free section accounting during GC data migration and can trigger unexpected allocation failures or assertion errors when space is tight. Fix this by calculating the number of already-free sections in the truncated range, deducting them from free_sections upon entering free_segment_range(), and restoring them on exit. Fixes: b4b10061ef98 ("f2fs: refactor resize_fs to avoid meta updates in progress") Cc: stable@vger.kernel.org Signed-off-by: Daeho Jeong Signed-off-by: Sunmin Jeong Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/gc.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c index 0c17038fcfd7..0a00180c21dc 100644 --- a/fs/f2fs/gc.c +++ b/fs/f2fs/gc.c @@ -2231,17 +2231,27 @@ void f2fs_reset_gc_victim_resource(struct f2fs_sb_info *sbi, static int free_segment_range(struct f2fs_sb_info *sbi, unsigned int secs, bool dry_run) { - unsigned int next_inuse, start, end; + unsigned int secno, next_inuse, start, end, end_secno; struct cp_control cpc = { CP_RESIZE, 0, 0, 0 }; + unsigned int freed_secs = 0; int err = 0; int type; MAIN_SECS(sbi) -= secs; start = MAIN_SECS(sbi) * SEGS_PER_SEC(sbi); end = MAIN_SEGS(sbi) - 1; + end_secno = GET_SEC_FROM_SEG(sbi, end); f2fs_reset_gc_victim_resource(sbi, start, end); + spin_lock(&FREE_I(sbi)->segmap_lock); + for (secno = MAIN_SECS(sbi); secno <= end_secno; secno++) { + if (!test_bit(secno, FREE_I(sbi)->free_secmap)) + freed_secs++; + } + FREE_I(sbi)->free_sections -= freed_secs; + spin_unlock(&FREE_I(sbi)->segmap_lock); + /* Move out cursegs from the target range */ for (type = CURSEG_HOT_DATA; type < NR_CURSEG_TYPE; type++) { err = f2fs_allocate_segment_for_resize(sbi, type, start, end); @@ -2266,6 +2276,9 @@ static int free_segment_range(struct f2fs_sb_info *sbi, f2fs_bug_on(sbi, 1); } out: + spin_lock(&FREE_I(sbi)->segmap_lock); + FREE_I(sbi)->free_sections += freed_secs; + spin_unlock(&FREE_I(sbi)->segmap_lock); MAIN_SECS(sbi) += secs; return err; } From ba77efee1b95b4ad7559b1cdbe7cd7fa36dca95b Mon Sep 17 00:00:00 2001 From: Laxman Acharya Padhya Date: Fri, 21 Aug 2026 16:18:17 +0545 Subject: [PATCH 1177/1328] io_uring/query: cap user size passed to copy_struct_to_user io_handle_query_entry() clamps hdr.size for the inbound copy_from_user() but keeps the original user value as usize. copy_struct_to_user() uses that usize and, when it is larger than the kernel result, clear_user()s the trailing bytes. As hdr.size is a __u32, a query can request nearly 4 GiB of zeroing, including on the error path where res_size stays 0. The interface is reachable without a ring via IORING_REGISTER_QUERY. Reject sizes larger than PAGE_SIZE, as recommended for copy_struct_* interfaces. Fixes: c265ae75f900 ("io_uring: introduce io_uring querying") Cc: stable@vger.kernel.org # 6.18+ Signed-off-by: Laxman Acharya Padhya Reviewed-by: Gabriel Krisman Bertazi Link: https://patch.msgid.link/20260821103317.91437-1-acharyalaxman8848@gmail.com Signed-off-by: Jens Axboe --- io_uring/query.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/io_uring/query.c b/io_uring/query.c index 4321f0b94b4b..7bce2ec162c1 100644 --- a/io_uring/query.c +++ b/io_uring/query.c @@ -76,6 +76,9 @@ static int io_handle_query_entry(union io_query_data *data, void __user *uhdr, if (copy_from_user(&hdr, uhdr, sizeof(hdr))) return -EFAULT; + /* copy_struct_to_user() zeros up to usize bytes */ + if (hdr.size > PAGE_SIZE) + return -E2BIG; usize = hdr.size; hdr.size = min(hdr.size, IO_MAX_QUERY_SIZE); udata = u64_to_user_ptr(hdr.query_data); From 14572de82e5022899e5856008bc9cac97004a88c Mon Sep 17 00:00:00 2001 From: Hui Su Date: Tue, 18 Aug 2026 18:33:36 +0800 Subject: [PATCH 1178/1328] io_uring/waitid: honor task_work cancellation io_waitid_cb() may run through the fallback task_work path when task_work_add() can no longer queue work to the originating task. The fallback runs from a kworker and io_uring marks such task work as canceled through tw.cancel. io_waitid_cb() currently ignores tw.cancel and calls __do_wait(). waitid is task-context dependent: __do_wait() performs child lookup relative to current, and the retry path also uses current->signal->wait_chldexit. If the callback runs from the fallback kworker, current is therefore not the task that submitted the request. Honor tw.cancel before entering __do_wait(). Complete the request with -ECANCELED and skip the siginfo copy, since canceled task work may run without the submitting task's userspace execution context. Keep the existing siginfo handling for normal waitid completion and explicit cancellation. Fixes: f31ecf671ddc ("io_uring: add IORING_OP_WAITID support") Cc: stable@vger.kernel.org Signed-off-by: Hui Su Link: https://patch.msgid.link/20260818103336.1922818-2-sh_def@163.com Signed-off-by: Jens Axboe --- io_uring/waitid.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/io_uring/waitid.c b/io_uring/waitid.c index 76af129ba8ca..596f2da40260 100644 --- a/io_uring/waitid.c +++ b/io_uring/waitid.c @@ -125,7 +125,7 @@ static void io_waitid_remove_wq(struct io_kiocb *req) } } -static void io_waitid_complete(struct io_kiocb *req, int ret) +static void io_waitid_complete(struct io_kiocb *req, int ret, bool copy_si) { struct io_waitid *iw = io_kiocb_to_cmd(req, struct io_waitid); @@ -137,7 +137,10 @@ static void io_waitid_complete(struct io_kiocb *req, int ret) hlist_del_init(&req->hash_node); io_waitid_remove_wq(req); - ret = io_waitid_finish(req, ret); + if (copy_si) + ret = io_waitid_finish(req, ret); + else + io_waitid_free(req); if (ret < 0) req_set_fail(req); io_req_set_res(req, ret, 0); @@ -159,7 +162,7 @@ static bool __io_waitid_cancel(struct io_kiocb *req) if (atomic_fetch_inc(&iw->refs) & IO_WAITID_REF_MASK) return false; - io_waitid_complete(req, -ECANCELED); + io_waitid_complete(req, -ECANCELED, true); io_req_queue_tw_complete(req, -ECANCELED); return true; } @@ -202,6 +205,11 @@ static void io_waitid_cb(struct io_tw_req tw_req, io_tw_token_t tw) int ret; io_tw_lock(ctx, tw); + if (unlikely(tw.cancel)) { + io_waitid_complete(req, -ECANCELED, false); + io_req_task_complete(tw_req, tw); + return; + } ret = __do_wait(&iwa->wo); @@ -229,7 +237,7 @@ static void io_waitid_cb(struct io_tw_req tw_req, io_tw_token_t tw) } } - io_waitid_complete(req, ret); + io_waitid_complete(req, ret, true); io_req_task_complete(tw_req, tw); } From 2cf20c4e0f72d523b8673053e7120d092ff1f074 Mon Sep 17 00:00:00 2001 From: Hui Su Date: Tue, 18 Aug 2026 18:33:37 +0800 Subject: [PATCH 1179/1328] io_uring/waitid: avoid siginfo copy during ring teardown During ring teardown, io_ring_exit_work() cancels outstanding requests from a kworker with a NULL tctx. The waitid cancellation path eventually reaches io_waitid_finish(), which copies the stored siginfo to the userspace pointer supplied with the request. Ring-wide teardown does not run in the task context that submitted the request, so it must not access that task's userspace pointer. Depending on the address and mm state, the copy may fail with -EFAULT, but the uaccess itself is inappropriate from the teardown kworker. Use a no-copy cancellation callback when io_waitid_remove_all() is called without an owning task context. Complete the request with -ECANCELED while releasing the waitid state without touching siginfo. Keep the existing siginfo handling for explicit async cancellation and task-scoped cancellation. Fixes: f31ecf671ddc ("io_uring: add IORING_OP_WAITID support") Cc: stable@vger.kernel.org Signed-off-by: Hui Su Link: https://patch.msgid.link/20260818103336.1922818-3-sh_def@163.com Signed-off-by: Jens Axboe --- io_uring/waitid.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/io_uring/waitid.c b/io_uring/waitid.c index 596f2da40260..2b756017423f 100644 --- a/io_uring/waitid.c +++ b/io_uring/waitid.c @@ -146,7 +146,7 @@ static void io_waitid_complete(struct io_kiocb *req, int ret, bool copy_si) io_req_set_res(req, ret, 0); } -static bool __io_waitid_cancel(struct io_kiocb *req) +static bool __io_waitid_cancel(struct io_kiocb *req, bool copy_si) { struct io_waitid *iw = io_kiocb_to_cmd(req, struct io_waitid); @@ -162,21 +162,32 @@ static bool __io_waitid_cancel(struct io_kiocb *req) if (atomic_fetch_inc(&iw->refs) & IO_WAITID_REF_MASK) return false; - io_waitid_complete(req, -ECANCELED, true); + io_waitid_complete(req, -ECANCELED, copy_si); io_req_queue_tw_complete(req, -ECANCELED); return true; } +static bool io_waitid_cancel_cb(struct io_kiocb *req) +{ + return __io_waitid_cancel(req, true); +} + +static bool io_waitid_cancel_nocopy_cb(struct io_kiocb *req) +{ + return __io_waitid_cancel(req, false); +} + int io_waitid_cancel(struct io_ring_ctx *ctx, struct io_cancel_data *cd, unsigned int issue_flags) { - return io_cancel_remove(ctx, cd, issue_flags, &ctx->waitid_list, __io_waitid_cancel); + return io_cancel_remove(ctx, cd, issue_flags, &ctx->waitid_list, io_waitid_cancel_cb); } bool io_waitid_remove_all(struct io_ring_ctx *ctx, struct io_uring_task *tctx, bool cancel_all) { - return io_cancel_remove_all(ctx, tctx, &ctx->waitid_list, cancel_all, __io_waitid_cancel); + return io_cancel_remove_all(ctx, tctx, &ctx->waitid_list, cancel_all, + tctx ? io_waitid_cancel_cb : io_waitid_cancel_nocopy_cb); } static inline bool io_waitid_drop_issue_ref(struct io_kiocb *req) From 1087c29d9c5f0ca6793ad5b117943b45e000364b Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 24 Aug 2026 19:52:08 +0200 Subject: [PATCH 1180/1328] Revert "thermal/core: Use the thermal class pointer as init guard" This reverts commit 499274d078d0 ("thermal/core: Use the thermal class pointer as init guard") because it depends on another commit that needs to be reverted. Signed-off-by: Rafael J. Wysocki Reviewed-by: Greg Kroah-Hartman Link: https://patch.msgid.link/6301222.lOV4Wx5bFT@rafael.j.wysocki --- drivers/thermal/thermal_core.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index 28a20d4b475c..2d9d8740491f 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -890,6 +890,7 @@ static void thermal_unbind_cdev_from_trip(struct thermal_zone_device *tz, } static struct class *thermal_class __ro_after_init; +static bool thermal_class_unavailable __ro_after_init = true; static inline void print_bind_err_msg(struct thermal_zone_device *tz, @@ -973,7 +974,7 @@ thermal_cooling_device_alloc(const char *type, const struct thermal_cooling_devi !ops->set_cur_state) return ERR_PTR(-EINVAL); - if (!thermal_class) + if (thermal_class_unavailable) return ERR_PTR(-ENODEV); cdev = kzalloc_obj(*cdev); @@ -1447,7 +1448,7 @@ thermal_zone_device_register_with_trips(const char *type, if (polling_delay && passive_delay > polling_delay) return ERR_PTR(-EINVAL); - if (!thermal_class) + if (thermal_class_unavailable) return ERR_PTR(-ENODEV); tz = kzalloc_flex(*tz, trips, num_trips); @@ -1745,7 +1746,7 @@ static void __thermal_pm_prepare(void) void thermal_pm_prepare(void) { - if (!thermal_class) + if (thermal_class_unavailable) return; __thermal_pm_prepare(); @@ -1776,7 +1777,7 @@ void thermal_pm_complete(void) { struct thermal_zone_device *tz; - if (!thermal_class) + if (thermal_class_unavailable) return; guard(mutex)(&thermal_list_lock); @@ -1815,6 +1816,8 @@ static int __init thermal_init(void) } thermal_class = tc; + thermal_class_unavailable = false; + return 0; unregister_governors: From aa4174127fe63f3b6521529d0f1d66470ae4d8ad Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 24 Aug 2026 19:53:00 +0200 Subject: [PATCH 1181/1328] Revert "thermal/core: Allocate the thermal class dynamically" This reverts commit 34f54003643e ("thermal/core: Allocate the thermal class dynamically") that went against driver core changes aiming at the elimination of class_create() [1]. No intentional functional impact. Link: https://lore.kernel.org/linux-pm/2026082411-flask-rewire-434f@gregkh/ Signed-off-by: Rafael J. Wysocki Reviewed-by: Greg Kroah-Hartman Link: https://patch.msgid.link/4761117.LvFx2qVVIh@rafael.j.wysocki --- drivers/thermal/thermal_core.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index 2d9d8740491f..82e2f0d8a26d 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -889,7 +889,9 @@ static void thermal_unbind_cdev_from_trip(struct thermal_zone_device *tz, kfree(pos); } -static struct class *thermal_class __ro_after_init; +static const struct class thermal_class = { + .name = "thermal", +}; static bool thermal_class_unavailable __ro_after_init = true; static inline @@ -1011,7 +1013,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.class = thermal_class; + cdev->device.class = &thermal_class; cdev->device.release = thermal_cdev_release; device_initialize(&cdev->device); cdev->devdata = devdata; @@ -1484,7 +1486,7 @@ thermal_zone_device_register_with_trips(const char *type, if (!tz->ops.critical) tz->ops.critical = thermal_zone_device_critical; - tz->device.class = thermal_class; + tz->device.class = &thermal_class; tz->device.release = thermal_zone_device_release; tz->devdata = devdata; tz->num_trips = num_trips; @@ -1790,7 +1792,6 @@ void thermal_pm_complete(void) static int __init thermal_init(void) { - struct class *tc; int result; thermal_debug_init(); @@ -1809,13 +1810,10 @@ static int __init thermal_init(void) if (result) goto unregister_governors; - tc = class_create("thermal"); - if (IS_ERR(tc)) { - result = PTR_ERR(tc); + result = class_register(&thermal_class); + if (result) goto unregister_governors; - } - thermal_class = tc; thermal_class_unavailable = false; return 0; From 79a57e48822a88f082a4dadb366abaf3c0988b5d Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Wed, 12 Aug 2026 18:17:22 -0700 Subject: [PATCH 1182/1328] thermal/drivers/qcom-spmi-mbg-tm: Add module namespace import for IIO_CONSUMER Commit ebf1d03dab96 ("iio: inkern: Use namespaced exports") in the iio tree restricts certain exported core functions that a driver added in commit c3dce117333c ("thermal/drivers/qcom: Add support for Qualcomm MBG thermal monitoring") from the thermal tree uses, causing modpost to warn (or error without CONFIG_MODULE_ALLOW_MISSING_NAMESPACE_IMPORTS): ERROR: modpost: drivers/thermal/qcom/qcom-spmi-mbg-tm.ko: module uses symbol 'devm_iio_channel_get' from namespace 'IIO_CONSUMER', but does not import it. ERROR: modpost: drivers/thermal/qcom/qcom-spmi-mbg-tm.ko: module uses symbol 'iio_read_channel_processed' from namespace 'IIO_CONSUMER', but does not import it. Add the IIO_CONSUMER namespace import to clear up the error. Signed-off-by: Nathan Chancellor Acked-by: Randy Dunlap Tested-by: Randy Dunlap Link: https://patch.msgid.link/20260812-qcom-spmi-mbg-tm-ns-modpost-error-v1-1-d849390d2714@kernel.org Signed-off-by: Rafael J. Wysocki --- drivers/thermal/qcom/qcom-spmi-mbg-tm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/thermal/qcom/qcom-spmi-mbg-tm.c b/drivers/thermal/qcom/qcom-spmi-mbg-tm.c index 0492d5eeca01..a05bb444627a 100644 --- a/drivers/thermal/qcom/qcom-spmi-mbg-tm.c +++ b/drivers/thermal/qcom/qcom-spmi-mbg-tm.c @@ -254,3 +254,4 @@ module_platform_driver(mbg_tm_driver); MODULE_DESCRIPTION("PMIC MBG Temperature monitor driver"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_CONSUMER"); From 1b0bab4a873f1034c27573cfc613394cff7e0a5b Mon Sep 17 00:00:00 2001 From: Miguel Ojeda Date: Sun, 23 Aug 2026 21:35:29 +0200 Subject: [PATCH 1183/1328] rust: kbuild: disambiguate `zerocopy_derive` for `rusttest` The `rustc-dev` components for Rust 1.82.0 through 1.87.0 include a precompiled `zerocopy_derive` procedural macro in the sysroot. This range includes Rust 1.85.0, our minimum supported version. This makes `rusttest` fail because the compiler finds both the sysroot copy and the copy built in `rust/test`: error[E0464]: multiple candidates for `dylib` dependency `zerocopy_derive` found --> rust/kernel/prelude.rs:70:9 | 70 | pub use zerocopy_derive::{ | ^^^^^^^^^^^^^^^ | = note: candidate #1: .../lib/rustlib/x86_64-unknown-linux-gnu/lib/libzerocopy_derive-54d2b38896fa6bc5.so = note: candidate #2: .../rust/test/libzerocopy_derive.so Commit fe39a233ea52 ("rust: kbuild: disambiguate `zerocopy` for `rusttest`") fixed the equivalent ambiguity for `zerocopy`. Thus point to the dependency explicitly in this case too. Cc: Antoni Boucher Cc: stable@vger.kernel.org Fixes: 506054980429 ("rust: zerocopy-derive: enable support in kbuild") Link: https://patch.msgid.link/20260823193529.156066-1-ojeda@kernel.org Signed-off-by: Miguel Ojeda --- rust/Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rust/Makefile b/rust/Makefile index 3afaad4a4a3a..9211d82ac96d 100644 --- a/rust/Makefile +++ b/rust/Makefile @@ -350,7 +350,8 @@ rusttestlib-pin_init: $(src)/pin-init/src/lib.rs rusttestlib-macros \ rusttestlib-kernel: private rustc_target_flags = --extern ffi \ --extern build_error --extern macros --extern pin_init \ --extern bindings --extern uapi \ - --extern zerocopy=$(objtree)/$(obj)/test/libzerocopy.rlib --extern zerocopy_derive + --extern zerocopy=$(objtree)/$(obj)/test/libzerocopy.rlib \ + --extern zerocopy_derive=$(objtree)/$(obj)/test/$(libzerocopy_derive_name) rusttestlib-kernel: $(src)/kernel/lib.rs rusttestlib-bindings rusttestlib-uapi \ rusttestlib-build_error rusttestlib-pin_init $(obj)/$(libmacros_name) \ $(obj)/bindings.o rusttestlib-zerocopy rusttestlib-zerocopy_derive FORCE From aef2ca9353c2f26dbacfb3b8e6f33fecfbf2e67d Mon Sep 17 00:00:00 2001 From: Prike Liang Date: Wed, 19 Aug 2026 15:23:56 +0800 Subject: [PATCH 1184/1328] drm/amdgpu/mes: fix the inconsistent indenting for mes_userq_map() Fix the inconsistent indenting warning for mes_userq_map(). Fixes: d0827dda8fa7 ("drm/amdgpu/mes: refactor the amdgpu_mes_alloc/free_proc|gang()") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202608190252.8XCa0HqR-lkp@intel.com/ Signed-off-by: Prike Liang Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/mes_userqueue.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c index fae709f134bb..14a5abe42d1f 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c @@ -146,7 +146,7 @@ static int mes_userq_map(struct amdgpu_usermode_queue *queue) queue_input.wptr_mc_addr = queue->wptr_obj.gpu_addr; if (mes->use_rs64mem) { - if (!uq_mgr->proc_ctx_allocated) { + if (!uq_mgr->proc_ctx_allocated) { r = amdgpu_mes_alloc_proc_ctx_index(mes, &uq_mgr->proc_ctx_array_index); if (r) { DRM_ERROR("Failed to allocate userq process index err:%d\n", r); From d36fbf82189319e9af564c93900d30e55e87e7e0 Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Fri, 14 Aug 2026 15:01:44 +0800 Subject: [PATCH 1185/1328] drm/amdgpu/userq: lock and validate wptr BOs before reading their GPU offset on restore On resume, amdgpu_userq_vm_validate_and_restore_queue() updates each queue's wptr GPU address via amdgpu_bo_gpu_offset(). WPTR BOs are VM-mapped, but each BO has its own reservation object and is not implicitly covered by the VM validation path here. This can leave offset reads without proper BO locking/placement state and trigger WARN_ONs. ------------[ cut here ]------------ WARNING: amdgpu_object.c:1486 at amdgpu_bo_gpu_offset+0x75/0xa0 [amdgpu], CPU#3: kworker/3:1/116 Workqueue: events amdgpu_userq_restore_worker [amdgpu] RIP: 0010:amdgpu_bo_gpu_offset+0x75/0xa0 [amdgpu] Call Trace: amdgpu_userq_vm_validate_and_restore_queue+0x629/0x960 [amdgpu] amdgpu_userq_restore_worker+0xa6/0x180 [amdgpu] process_scheduled_works+0xa6/0x460 worker_thread+0x13c/0x290 kthread+0xfb/0x140 ret_from_fork+0x1b6/0x2b0 ret_from_fork_asm+0x1a/0x30 ---[ end trace 0000000000000000 ]--- ------------[ cut here ]------------ WARNING: amdgpu_object.c:1485 at amdgpu_bo_gpu_offset+0x9a/0xa0 [amdgpu], CPU#2: kworker/2:1/127 Workqueue: events amdgpu_userq_restore_worker [amdgpu] RIP: 0010:amdgpu_bo_gpu_offset+0x9a/0xa0 [amdgpu] Add each queue's WPTR BO to the drm_exec ww context and validate it to its allowed placement before the later offset update. v2: - Clarify that WPTR BOs are VM-mapped (fix incorrect "not part of VM" wording). (Christian) - Describe both parts of the fix: lock BO reservations in drm_exec and validate BO placement before offset reads. Acked-by: Alex Deucher Signed-off-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 24 +++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index 24adad7be251..0a816b3c5ff9 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -1047,6 +1047,30 @@ amdgpu_userq_vm_validate_and_restore_queue(struct amdgpu_userq_mgr *uq_mgr) drm_exec_retry_on_contention(&exec); if (unlikely(ret)) goto unlock_all; + + /* + * WPTR BOs are VM-mapped, but each BO has its own reservation + * object. Lock them into this drm_exec ww context so the later + * amdgpu_bo_gpu_offset() reads are done with the BO resv locked. + */ + xa_for_each(&uq_mgr->userq_xa, tmp_key, queue) { + struct ttm_operation_ctx wptr_ctx = { false, false }; + + bo = queue->wptr_obj.obj; + if (!bo) + continue; + + ret = drm_exec_prepare_obj(&exec, &bo->tbo.base, + TTM_NUM_MOVE_FENCES + 1); + drm_exec_retry_on_contention(&exec); + if (unlikely(ret)) + goto unlock_all; + + amdgpu_bo_placement_from_domain(bo, bo->allowed_domains); + ret = ttm_bo_validate(&bo->tbo, &bo->placement, &wptr_ctx); + if (unlikely(ret)) + goto unlock_all; + } } if (invalidated) { From 6760f5cb12d2366ddd58a2d8637f7583d73f596b Mon Sep 17 00:00:00 2001 From: Bob Zhou Date: Wed, 19 Aug 2026 14:23:49 +0800 Subject: [PATCH 1186/1328] drm/amdgpu: avoid force-completing uninitialized UVD rings uvd_v7_0_sw_init() does not initialize the UVD decode ring for an SR-IOV VF. However, amdgpu_uvd_resume() unconditionally force-completes the decode ring when restoring its fence sequence. Skip fence completion when the fence driver is not initialized. Fixes: 0a33b11d26c6 ("drm/amdgpu: mark force completed fences with -ECANCELED") Cc: stable@vger.kernel.org Signed-off-by: Bob Zhou Acked-by: Leo Liu Acked-by: Frank Min Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c index 228a405a94c4..ecd7caa95d4b 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c @@ -517,7 +517,8 @@ int amdgpu_uvd_resume(struct amdgpu_device *adev) } memset_io(ptr, 0, size); /* to restore uvd fence seq */ - amdgpu_fence_driver_force_completion(&adev->uvd.inst[i].ring, NULL); + if (adev->uvd.inst[i].ring.fence_drv.initialized) + amdgpu_fence_driver_force_completion(&adev->uvd.inst[i].ring, NULL); } } return 0; From 290e0be2abb7d9f1473d12ef2ed3220b071c42c3 Mon Sep 17 00:00:00 2001 From: David Belanger Date: Fri, 14 Aug 2026 12:57:18 -0400 Subject: [PATCH 1187/1328] drm/kfd: Add CU occupancy support to GFX11 Port changes from GFX9 to GFX11 mostly as-is. Minor changes to register access code. Assisted-by: Claude:Sonnet-4-6 Signed-off-by: David Belanger Acked-by: Alex Deucher Reviewed-by: Sreekant Somasekharan Signed-off-by: Alex Deucher --- .../drm/amd/amdgpu/amdgpu_amdkfd_gfx_v11.c | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gfx_v11.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gfx_v11.c index 724beb96ed1a..04fab30ab5dd 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gfx_v11.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gfx_v11.c @@ -807,6 +807,153 @@ static uint32_t kgd_gfx_v11_hqd_sdma_get_doorbell(struct amdgpu_device *adev, return 0; } +static void lock_spi_csq_mutexes(struct amdgpu_device *adev) +{ + mutex_lock(&adev->srbm_mutex); + mutex_lock(&adev->grbm_idx_mutex); + +} + +static void unlock_spi_csq_mutexes(struct amdgpu_device *adev) +{ + mutex_unlock(&adev->grbm_idx_mutex); + mutex_unlock(&adev->srbm_mutex); +} + +/** + * get_wave_count: Read device registers to get number of waves in flight for + * a particular queue. The method also returns the doorbell offset associated + * with the queue. + * + * @adev: Handle of device whose registers are to be read + * @queue_idx: Index of queue in the queue-map bit-field + * @queue_cnt: Stores the wave count and doorbell offset for an active queue + * @inst: xcc's instance number on a multi-XCC setup + */ +static void get_wave_count(struct amdgpu_device *adev, int queue_idx, + struct kfd_cu_occupancy *queue_cnt, uint32_t inst) +{ + int pipe_idx; + int queue_slot; + unsigned int reg_val; + unsigned int wave_cnt; + /* + * Program GRBM with appropriate MEID, PIPEID, QUEUEID and VMID + * parameters to read out waves in flight. Get doorbell offset if there are + * non-zero waves in flight. + */ + pipe_idx = queue_idx / adev->gfx.mec.num_queue_per_pipe; + queue_slot = queue_idx % adev->gfx.mec.num_queue_per_pipe; + soc21_grbm_select(adev, 1, pipe_idx, queue_slot, 0); + reg_val = RREG32_SOC15_IP(GC, SOC15_REG_OFFSET(GC, 0, + regSPI_CSQ_WF_ACTIVE_COUNT_0) + queue_slot); + wave_cnt = reg_val & SPI_CSQ_WF_ACTIVE_COUNT_0__COUNT_MASK; + if (wave_cnt != 0) { + queue_cnt->wave_cnt += wave_cnt; + queue_cnt->doorbell_off = + (RREG32_SOC15(GC, 0, regCP_HQD_PQ_DOORBELL_CONTROL) & + CP_HQD_PQ_DOORBELL_CONTROL__DOORBELL_OFFSET_MASK) >> + CP_HQD_PQ_DOORBELL_CONTROL__DOORBELL_OFFSET__SHIFT; + } +} + +/** + * kgd_gfx_v11_get_cu_occupancy: Reads relevant registers associated with each + * shader engine and aggregates the number of waves that are in flight for the + * process whose pasid is provided as a parameter. The process could have ZERO + * or more queues running and submitting waves to compute units. + * + * @adev: Handle of device from which to get number of waves in flight + * @cu_occupancy: Array that gets filled with wave_cnt and doorbell offset + * for comparison later. + * @max_waves_per_cu: Output parameter updated with maximum number of waves + * possible per Compute Unit + * @inst: xcc's instance number on a multi-XCC setup + * + * Note: It's possible that the device has too many queues (oversubscription) + * in which case a VMID could be remapped to a different PASID. This could lead + * to an inaccurate wave count. Following is a high-level sequence: + * Time T1: vmid = getVmid(); vmid is associated with Pasid P1 + * Time T2: passId = getPasId(vmid); vmid is associated with Pasid P2 + * In the sequence above wave count obtained from time T1 will be incorrectly + * lost or added to total wave count. + * + * The registers that provide the waves in flight are: + * + * SPI_CSQ_WF_ACTIVE_STATUS - bit-map of queues per pipe. The bit is ON if a + * queue is slotted, OFF if there is no queue. A process could have ZERO or + * more queues slotted and submitting waves to be run on compute units. Even + * when there is a queue it is possible there could be zero wave fronts, this + * can happen when queue is waiting on top-of-pipe events - e.g. waitRegMem + * command + * + * For each bit that is ON from above: + * + * Read (SPI_CSQ_WF_ACTIVE_COUNT_0 + queue_idx) register. It provides the + * number of waves that are in flight for the queue at specified index. The + * index ranges from 0 to 7. + * + * If non-zero waves are in flight, store the corresponding doorbell offset + * of the queue, along with the wave count. + * + * Determine if the queue belongs to the process by comparing the doorbell + * offset against the process's queues. If it matches, aggregate the wave + * count for the process. + * + * Reading registers referenced above involves programming GRBM appropriately + */ +static void kgd_gfx_v11_get_cu_occupancy(struct amdgpu_device *adev, + struct kfd_cu_occupancy *cu_occupancy, + int *max_waves_per_cu, uint32_t inst) +{ + int qidx; + int se_idx; + int se_cnt; + int queue_map; + int max_queue_cnt; + DECLARE_BITMAP(cp_queue_bitmap, AMDGPU_MAX_QUEUES); + + lock_spi_csq_mutexes(adev); + soc21_grbm_select(adev, 1, 0, 0, 0); + + /* + * Iterate through the shader engines and arrays of the device + * to get number of waves in flight + */ + bitmap_complement(cp_queue_bitmap, adev->gfx.mec_bitmap[0].queue_bitmap, + AMDGPU_MAX_QUEUES); + max_queue_cnt = adev->gfx.mec.num_pipe_per_mec * + adev->gfx.mec.num_queue_per_pipe; + se_cnt = adev->gfx.config.max_shader_engines; + for (se_idx = 0; se_idx < se_cnt; se_idx++) { + amdgpu_gfx_select_se_sh(adev, se_idx, 0, 0xffffffff, inst); + queue_map = RREG32_SOC15(GC, 0, + regSPI_CSQ_WF_ACTIVE_STATUS); + + for (qidx = 0; qidx < max_queue_cnt; qidx++) { + /* Skip queues that are not associated with + * compute functions + */ + if (!test_bit(qidx, cp_queue_bitmap)) + continue; + + if (!(queue_map & (1 << qidx))) + continue; + + /* Get number of waves in flight and aggregate them */ + get_wave_count(adev, qidx, &cu_occupancy[qidx], inst); + } + } + + amdgpu_gfx_select_se_sh(adev, 0xffffffff, 0xffffffff, 0xffffffff, inst); + soc21_grbm_select(adev, 0, 0, 0, 0); + unlock_spi_csq_mutexes(adev); + + /* Update the output parameters and return */ + *max_waves_per_cu = adev->gfx.cu_info.simd_per_cu * + adev->gfx.cu_info.max_waves_per_simd; +} + const struct kfd2kgd_calls gfx_v11_kfd2kgd = { .program_sh_mem_settings = program_sh_mem_settings_v11, .set_pasid_vmid_mapping = set_pasid_vmid_mapping_v11, @@ -832,5 +979,6 @@ const struct kfd2kgd_calls gfx_v11_kfd2kgd = { .clear_address_watch = kgd_gfx_v11_clear_address_watch, .hqd_get_pq_addr = kgd_gfx_v11_hqd_get_pq_addr, .hqd_reset = kgd_gfx_v11_hqd_reset, + .get_cu_occupancy = kgd_gfx_v11_get_cu_occupancy, .hqd_sdma_get_doorbell = kgd_gfx_v11_hqd_sdma_get_doorbell }; From fb1e65a80dd9167e8dfdfdfef178d1d012d8bb58 Mon Sep 17 00:00:00 2001 From: David Belanger Date: Fri, 14 Aug 2026 13:25:56 -0400 Subject: [PATCH 1188/1328] drm/kfd: Add CU occupancy support to GFX12 Port changes from GFX9 to GFX12 mostly as-is. Minor changes to register access code. Assisted-by: Claude:Sonnet-4-6 Signed-off-by: David Belanger Acked-by: Alex Deucher Reviewed-by: Sreekant Somasekharan Signed-off-by: Alex Deucher --- .../drm/amd/amdgpu/amdgpu_amdkfd_gfx_v12.c | 150 +++++++++++++++++- 1 file changed, 149 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gfx_v12.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gfx_v12.c index e11ba3e91841..62b9db64368e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gfx_v12.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gfx_v12.c @@ -368,6 +368,153 @@ static uint32_t kgd_gfx_v12_hqd_sdma_get_doorbell(struct amdgpu_device *adev, return 0; } +static void lock_spi_csq_mutexes(struct amdgpu_device *adev) +{ + mutex_lock(&adev->srbm_mutex); + mutex_lock(&adev->grbm_idx_mutex); + +} + +static void unlock_spi_csq_mutexes(struct amdgpu_device *adev) +{ + mutex_unlock(&adev->grbm_idx_mutex); + mutex_unlock(&adev->srbm_mutex); +} + +/** + * get_wave_count: Read device registers to get number of waves in flight for + * a particular queue. The method also returns the doorbell offset associated + * with the queue. + * + * @adev: Handle of device whose registers are to be read + * @queue_idx: Index of queue in the queue-map bit-field + * @queue_cnt: Stores the wave count and doorbell offset for an active queue + * @inst: xcc's instance number on a multi-XCC setup + */ +static void get_wave_count(struct amdgpu_device *adev, int queue_idx, + struct kfd_cu_occupancy *queue_cnt, uint32_t inst) +{ + int pipe_idx; + int queue_slot; + unsigned int reg_val; + unsigned int wave_cnt; + /* + * Program GRBM with appropriate MEID, PIPEID, QUEUEID and VMID + * parameters to read out waves in flight. Get doorbell offset if there are + * non-zero waves in flight. + */ + pipe_idx = queue_idx / adev->gfx.mec.num_queue_per_pipe; + queue_slot = queue_idx % adev->gfx.mec.num_queue_per_pipe; + soc24_grbm_select(adev, 1, pipe_idx, queue_slot, 0); + reg_val = RREG32_SOC15_IP(GC, SOC15_REG_OFFSET(GC, 0, + regSPI_CSQ_WF_ACTIVE_COUNT_0) + queue_slot); + wave_cnt = reg_val & SPI_CSQ_WF_ACTIVE_COUNT_0__COUNT_MASK; + if (wave_cnt != 0) { + queue_cnt->wave_cnt += wave_cnt; + queue_cnt->doorbell_off = + (RREG32_SOC15(GC, 0, regCP_HQD_PQ_DOORBELL_CONTROL) & + CP_HQD_PQ_DOORBELL_CONTROL__DOORBELL_OFFSET_MASK) >> + CP_HQD_PQ_DOORBELL_CONTROL__DOORBELL_OFFSET__SHIFT; + } +} + +/** + * kgd_gfx_v12_get_cu_occupancy: Reads relevant registers associated with each + * shader engine and aggregates the number of waves that are in flight for the + * process whose pasid is provided as a parameter. The process could have ZERO + * or more queues running and submitting waves to compute units. + * + * @adev: Handle of device from which to get number of waves in flight + * @cu_occupancy: Array that gets filled with wave_cnt and doorbell offset + * for comparison later. + * @max_waves_per_cu: Output parameter updated with maximum number of waves + * possible per Compute Unit + * @inst: xcc's instance number on a multi-XCC setup + * + * Note: It's possible that the device has too many queues (oversubscription) + * in which case a VMID could be remapped to a different PASID. This could lead + * to an inaccurate wave count. Following is a high-level sequence: + * Time T1: vmid = getVmid(); vmid is associated with Pasid P1 + * Time T2: passId = getPasId(vmid); vmid is associated with Pasid P2 + * In the sequence above wave count obtained from time T1 will be incorrectly + * lost or added to total wave count. + * + * The registers that provide the waves in flight are: + * + * SPI_CSQ_WF_ACTIVE_STATUS - bit-map of queues per pipe. The bit is ON if a + * queue is slotted, OFF if there is no queue. A process could have ZERO or + * more queues slotted and submitting waves to be run on compute units. Even + * when there is a queue it is possible there could be zero wave fronts, this + * can happen when queue is waiting on top-of-pipe events - e.g. waitRegMem + * command + * + * For each bit that is ON from above: + * + * Read (SPI_CSQ_WF_ACTIVE_COUNT_0 + queue_idx) register. It provides the + * number of waves that are in flight for the queue at specified index. The + * index ranges from 0 to 7. + * + * If non-zero waves are in flight, store the corresponding doorbell offset + * of the queue, along with the wave count. + * + * Determine if the queue belongs to the process by comparing the doorbell + * offset against the process's queues. If it matches, aggregate the wave + * count for the process. + * + * Reading registers referenced above involves programming GRBM appropriately + */ +static void kgd_gfx_v12_get_cu_occupancy(struct amdgpu_device *adev, + struct kfd_cu_occupancy *cu_occupancy, + int *max_waves_per_cu, uint32_t inst) +{ + int qidx; + int se_idx; + int se_cnt; + int queue_map; + int max_queue_cnt; + DECLARE_BITMAP(cp_queue_bitmap, AMDGPU_MAX_QUEUES); + + lock_spi_csq_mutexes(adev); + soc24_grbm_select(adev, 1, 0, 0, 0); + + /* + * Iterate through the shader engines and arrays of the device + * to get number of waves in flight + */ + bitmap_complement(cp_queue_bitmap, adev->gfx.mec_bitmap[0].queue_bitmap, + AMDGPU_MAX_QUEUES); + max_queue_cnt = adev->gfx.mec.num_pipe_per_mec * + adev->gfx.mec.num_queue_per_pipe; + se_cnt = adev->gfx.config.max_shader_engines; + for (se_idx = 0; se_idx < se_cnt; se_idx++) { + amdgpu_gfx_select_se_sh(adev, se_idx, 0, 0xffffffff, inst); + queue_map = RREG32_SOC15(GC, 0, + regSPI_CSQ_WF_ACTIVE_STATUS); + + for (qidx = 0; qidx < max_queue_cnt; qidx++) { + /* Skip queues that are not associated with + * compute functions + */ + if (!test_bit(qidx, cp_queue_bitmap)) + continue; + + if (!(queue_map & (1 << qidx))) + continue; + + /* Get number of waves in flight and aggregate them */ + get_wave_count(adev, qidx, &cu_occupancy[qidx], inst); + } + } + + amdgpu_gfx_select_se_sh(adev, 0xffffffff, 0xffffffff, 0xffffffff, inst); + soc24_grbm_select(adev, 0, 0, 0, 0); + unlock_spi_csq_mutexes(adev); + + /* Update the output parameters and return */ + *max_waves_per_cu = adev->gfx.cu_info.simd_per_cu * + adev->gfx.cu_info.max_waves_per_simd; +} + const struct kfd2kgd_calls gfx_v12_kfd2kgd = { .init_interrupts = init_interrupts_v12, .hqd_dump = hqd_dump_v12, @@ -381,5 +528,6 @@ const struct kfd2kgd_calls gfx_v12_kfd2kgd = { .set_wave_launch_mode = kgd_gfx_v12_set_wave_launch_mode, .set_address_watch = kgd_gfx_v12_set_address_watch, .clear_address_watch = kgd_gfx_v12_clear_address_watch, - .hqd_sdma_get_doorbell = kgd_gfx_v12_hqd_sdma_get_doorbell + .hqd_sdma_get_doorbell = kgd_gfx_v12_hqd_sdma_get_doorbell, + .get_cu_occupancy = kgd_gfx_v12_get_cu_occupancy, }; From fb62f7f031155fe6c1095d2fc921654ad51f87c2 Mon Sep 17 00:00:00 2001 From: David Belanger Date: Fri, 14 Aug 2026 13:26:17 -0400 Subject: [PATCH 1189/1328] drm/kfd: Add CU occupancy support to GFX12.1 Port changes from GFX9 to GFX12.1 mostly as-is. Minor changes to register access code. Assisted-by: Claude:Sonnet 4.6 Signed-off-by: David Belanger Reviewed-by: Sreekant Somasekharan Signed-off-by: Alex Deucher --- .../drm/amd/amdgpu/amdgpu_amdkfd_gfx_v12_1.c | 150 +++++++++++++++++- 1 file changed, 149 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gfx_v12_1.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gfx_v12_1.c index 38ca1aea33b2..070001fd34b0 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gfx_v12_1.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gfx_v12_1.c @@ -371,6 +371,153 @@ static uint32_t kgd_gfx_v12_1_hqd_sdma_get_doorbell(struct amdgpu_device *adev, return 0; } +static void lock_spi_csq_mutexes(struct amdgpu_device *adev) +{ + mutex_lock(&adev->srbm_mutex); + mutex_lock(&adev->grbm_idx_mutex); + +} + +static void unlock_spi_csq_mutexes(struct amdgpu_device *adev) +{ + mutex_unlock(&adev->grbm_idx_mutex); + mutex_unlock(&adev->srbm_mutex); +} + +/** + * get_wave_count: Read device registers to get number of waves in flight for + * a particular queue. The method also returns the doorbell offset associated + * with the queue. + * + * @adev: Handle of device whose registers are to be read + * @queue_idx: Index of queue in the queue-map bit-field + * @queue_cnt: Stores the wave count and doorbell offset for an active queue + * @inst: xcc's instance number on a multi-XCC setup + */ +static void get_wave_count(struct amdgpu_device *adev, int queue_idx, + struct kfd_cu_occupancy *queue_cnt, uint32_t inst) +{ + int pipe_idx; + int queue_slot; + unsigned int reg_val; + unsigned int wave_cnt; + /* + * Program GRBM with appropriate MEID, PIPEID, QUEUEID and VMID + * parameters to read out waves in flight. Get doorbell offset if there are + * non-zero waves in flight. + */ + pipe_idx = queue_idx / adev->gfx.mec.num_queue_per_pipe; + queue_slot = queue_idx % adev->gfx.mec.num_queue_per_pipe; + amdgpu_gfx_select_me_pipe_q(adev, 1, pipe_idx, queue_slot, 0, inst); + reg_val = RREG32_SOC15_IP(GC, SOC15_REG_OFFSET(GC, GET_INST(GC, inst), + regSPI_CSQ_WF_ACTIVE_COUNT_0) + queue_slot); + wave_cnt = reg_val & SPI_CSQ_WF_ACTIVE_COUNT_0__COUNT_MASK; + if (wave_cnt != 0) { + queue_cnt->wave_cnt += wave_cnt; + queue_cnt->doorbell_off = + (RREG32_SOC15(GC, GET_INST(GC, inst), regCP_HQD_PQ_DOORBELL_CONTROL) & + CP_HQD_PQ_DOORBELL_CONTROL__DOORBELL_OFFSET_MASK) >> + CP_HQD_PQ_DOORBELL_CONTROL__DOORBELL_OFFSET__SHIFT; + } +} + +/** + * kgd_gfx_v12_1_get_cu_occupancy: Reads relevant registers associated with + * each shader engine and aggregates the number of waves that are in flight + * for the process whose pasid is provided as a parameter. The process could + * have ZERO or more queues running and submitting waves to compute units. + * + * @adev: Handle of device from which to get number of waves in flight + * @cu_occupancy: Array that gets filled with wave_cnt and doorbell offset + * for comparison later. + * @max_waves_per_cu: Output parameter updated with maximum number of waves + * possible per Compute Unit + * @inst: xcc's instance number on a multi-XCC setup + * + * Note: It's possible that the device has too many queues (oversubscription) + * in which case a VMID could be remapped to a different PASID. This could lead + * to an inaccurate wave count. Following is a high-level sequence: + * Time T1: vmid = getVmid(); vmid is associated with Pasid P1 + * Time T2: passId = getPasId(vmid); vmid is associated with Pasid P2 + * In the sequence above wave count obtained from time T1 will be incorrectly + * lost or added to total wave count. + * + * The registers that provide the waves in flight are: + * + * SPI_CSQ_WF_ACTIVE_STATUS - bit-map of queues per pipe. The bit is ON if a + * queue is slotted, OFF if there is no queue. A process could have ZERO or + * more queues slotted and submitting waves to be run on compute units. Even + * when there is a queue it is possible there could be zero wave fronts, this + * can happen when queue is waiting on top-of-pipe events - e.g. waitRegMem + * command + * + * For each bit that is ON from above: + * + * Read (SPI_CSQ_WF_ACTIVE_COUNT_0 + queue_idx) register. It provides the + * number of waves that are in flight for the queue at specified index. The + * index ranges from 0 to 7. + * + * If non-zero waves are in flight, store the corresponding doorbell offset + * of the queue, along with the wave count. + * + * Determine if the queue belongs to the process by comparing the doorbell + * offset against the process's queues. If it matches, aggregate the wave + * count for the process. + * + * Reading registers referenced above involves programming GRBM appropriately + */ +static void kgd_gfx_v12_1_get_cu_occupancy(struct amdgpu_device *adev, + struct kfd_cu_occupancy *cu_occupancy, + int *max_waves_per_cu, uint32_t inst) +{ + int qidx; + int se_idx; + int se_cnt; + int queue_map; + int max_queue_cnt; + DECLARE_BITMAP(cp_queue_bitmap, AMDGPU_MAX_QUEUES); + + lock_spi_csq_mutexes(adev); + amdgpu_gfx_select_me_pipe_q(adev, 1, 0, 0, 0, inst); + + /* + * Iterate through the shader engines and arrays of the device + * to get number of waves in flight + */ + bitmap_complement(cp_queue_bitmap, adev->gfx.mec_bitmap[0].queue_bitmap, + AMDGPU_MAX_QUEUES); + max_queue_cnt = adev->gfx.mec.num_pipe_per_mec * + adev->gfx.mec.num_queue_per_pipe; + se_cnt = adev->gfx.config.max_shader_engines; + for (se_idx = 0; se_idx < se_cnt; se_idx++) { + amdgpu_gfx_select_se_sh(adev, se_idx, 0, 0xffffffff, inst); + queue_map = RREG32_SOC15(GC, GET_INST(GC, inst), + regSPI_CSQ_WF_ACTIVE_STATUS); + + for (qidx = 0; qidx < max_queue_cnt; qidx++) { + /* Skip queues that are not associated with + * compute functions + */ + if (!test_bit(qidx, cp_queue_bitmap)) + continue; + + if (!(queue_map & (1 << qidx))) + continue; + + /* Get number of waves in flight and aggregate them */ + get_wave_count(adev, qidx, &cu_occupancy[qidx], inst); + } + } + + amdgpu_gfx_select_se_sh(adev, 0xffffffff, 0xffffffff, 0xffffffff, inst); + amdgpu_gfx_select_me_pipe_q(adev, 0, 0, 0, 0, inst); + unlock_spi_csq_mutexes(adev); + + /* Update the output parameters and return */ + *max_waves_per_cu = adev->gfx.cu_info.simd_per_cu * + adev->gfx.cu_info.max_waves_per_simd; +} + const struct kfd2kgd_calls gfx_v12_1_kfd2kgd = { .init_interrupts = init_interrupts_v12_1, .hqd_dump = hqd_dump_v12_1, @@ -384,5 +531,6 @@ const struct kfd2kgd_calls gfx_v12_1_kfd2kgd = { .set_wave_launch_mode = kgd_gfx_v12_1_set_wave_launch_mode, .set_address_watch = kgd_gfx_v12_1_set_address_watch, .clear_address_watch = kgd_gfx_v12_1_clear_address_watch, - .hqd_sdma_get_doorbell = kgd_gfx_v12_1_hqd_sdma_get_doorbell + .hqd_sdma_get_doorbell = kgd_gfx_v12_1_hqd_sdma_get_doorbell, + .get_cu_occupancy = kgd_gfx_v12_1_get_cu_occupancy }; From 2ee9836545e690c9e66ec203445d7705959fd7a8 Mon Sep 17 00:00:00 2001 From: David Rosca Date: Thu, 20 Aug 2026 16:32:51 +0200 Subject: [PATCH 1190/1328] drm/amdgpu: Fix VCE 3 ring align_mask The largest frame is 20 dwords, so 0xf mask is too small. This was always wrong, but we were lucky with the VCE_CMD_END commands inserted after fence and vm_flush. Fixes: 8897ea8c761b ("drm/amdgpu: Implement insert_end for VCE 3") Cc: stable@vger.kernel.org Acked-by: Alex Deucher Signed-off-by: David Rosca Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/vce_v3_0.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/vce_v3_0.c b/drivers/gpu/drm/amd/amdgpu/vce_v3_0.c index a9497e2e07f7..4dbbeaf97ad1 100644 --- a/drivers/gpu/drm/amd/amdgpu/vce_v3_0.c +++ b/drivers/gpu/drm/amd/amdgpu/vce_v3_0.c @@ -891,7 +891,7 @@ static const struct amdgpu_ring_funcs vce_v3_0_ring_phys_funcs = { static const struct amdgpu_ring_funcs vce_v3_0_ring_vm_funcs = { .type = AMDGPU_RING_TYPE_VCE, - .align_mask = 0xf, + .align_mask = 0x1f, .nop = VCE_CMD_NO_OP, .support_64bit_ptrs = false, .no_user_fence = true, From 40ba09e11188d1b7f79d51fc28aca5ea45e0c138 Mon Sep 17 00:00:00 2001 From: Sunday Clement Date: Thu, 6 Aug 2026 10:59:34 -0400 Subject: [PATCH 1191/1328] drm/amdkfd: Reject zero-sized AQL queue allocations after size halving KFD_IOC_ALLOC_MEMORY_OF_GPU with flag KFD_IOC_ALLOC_MEM_FLAGS_AQL_QUEUE_MEM and size=1 triggers the AQL wraparound workaround (size >>= 1), reducing size to 0. The resulting zero passes through PAGE_ALIGN(0) = 0 without validation, bypassing the per-process VRAM quota check in reserve_mem_limit() (vram_used + 0 > vram_available is always false). The fix adds post-halving zero-size validation in the primary allocation path (amdgpu_amdkfd_gpuvm.c). The check happens after size halving but before reserve_mem_limit(), and uses err_alignment_size error path to properly clean up the allocated kgd_mem structure and mutex. Cc: stable@vger.kernel.org Signed-off-by: Sunday Clement Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c | 7 +++++++ drivers/gpu/drm/amd/amdkfd/kfd_chardev.c | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c index 34481ee7065a..d66881684ee5 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c @@ -1795,6 +1795,12 @@ int amdgpu_amdkfd_gpuvm_alloc_memory_of_gpu( size >>= 1; aligned_size = PAGE_ALIGN(size); + /* reject AQL queue with size < 2 */ + if (!aligned_size) { + ret = -EINVAL; + goto err_alignment_size; + } + (*mem)->alloc_flags = flags; amdgpu_sync_create(&(*mem)->sync); @@ -1886,6 +1892,7 @@ int amdgpu_amdkfd_gpuvm_alloc_memory_of_gpu( amdgpu_amdkfd_unreserve_mem_limit(adev, aligned_size, flags, xcp_id); err_reserve_limit: amdgpu_sync_free(&(*mem)->sync); +err_alignment_size: mutex_destroy(&(*mem)->lock); if (gobj) drm_gem_object_put(gobj); diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c index 6fd18488d5cf..7fcfc150a7fc 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c @@ -1200,7 +1200,8 @@ static int kfd_ioctl_alloc_memory_of_gpu(struct file *filep, if (flags & KFD_IOC_ALLOC_MEM_FLAGS_AQL_QUEUE_MEM) size >>= 1; - atomic64_add(PAGE_ALIGN(size), &pdd->vram_usage); + size = PAGE_ALIGN(size); + atomic64_add(size, &pdd->vram_usage); } mutex_unlock(&p->mutex); From b30900566642ceb2c9e12b56c2afec28d0fd91a0 Mon Sep 17 00:00:00 2001 From: Xiang Liu Date: Fri, 21 Aug 2026 17:41:57 +0800 Subject: [PATCH 1192/1328] drm/amdgpu: clamp the isolation index for rings outside a partition adev->isolation[] has one slot per partition, but a ring that is not assigned to one keeps AMDGPU_XCP_NO_PARTITION, which is ~0, so indexing the array with it is out of bounds. SDMA submissions hit this on both the isolation enforcement and the VM flush path and trip UBSAN. Fall back to the first slot the way the cleaner shader path already does, and stop taking the address before the ring type check that makes it relevant. Cc: stable@vger.kernel.org Signed-off-by: Xiang Liu Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 5 ++++- drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index 019581577603..44bed0ba64a3 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -6687,8 +6687,8 @@ struct dma_fence *amdgpu_device_enforce_isolation(struct amdgpu_device *adev, struct amdgpu_ring *ring, struct amdgpu_job *job) { - struct amdgpu_isolation *isolation = &adev->isolation[ring->xcp_id]; struct drm_sched_fence *f = job->base.s_fence; + struct amdgpu_isolation *isolation; struct dma_fence *dep; void *owner; int r; @@ -6701,6 +6701,9 @@ struct dma_fence *amdgpu_device_enforce_isolation(struct amdgpu_device *adev, ring->funcs->type != AMDGPU_RING_TYPE_COMPUTE) return NULL; + isolation = &adev->isolation[ring->xcp_id == AMDGPU_XCP_NO_PARTITION ? + 0 : ring->xcp_id]; + /* * All submissions where enforce isolation is false are handled as if * they come from a single client. Use ~0l as the owner to distinct it diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c index a3758c654dd4..aedf72c2333e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c @@ -776,7 +776,9 @@ void amdgpu_vm_flush(struct amdgpu_ring *ring, struct amdgpu_job *job, bool *emit_gds_needed) { struct amdgpu_device *adev = ring->adev; - struct amdgpu_isolation *isolation = &adev->isolation[ring->xcp_id]; + struct amdgpu_isolation *isolation = + &adev->isolation[ring->xcp_id == AMDGPU_XCP_NO_PARTITION ? + 0 : ring->xcp_id]; unsigned vmhub = ring->vm_hub; struct amdgpu_vmid_mgr *id_mgr = &adev->vm_manager.id_mgr[vmhub]; struct amdgpu_vmid *id = &id_mgr->ids[job->vmid]; From 52536ce677a3470c0e5323b940791efb33975450 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Fri, 21 Aug 2026 23:50:58 +0200 Subject: [PATCH 1193/1328] drm/amd/display: Fix HPD consideration for VGA/LVDS connectors on DCE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After a refactor that landed in Linux 7.0, DC now crashes when it is initialized on GPUs that have a VGA or LVDS connector. This is because these connectors have no HPD so the hpd_gpio is NULL and therefore DC takes the code path meant for DCN 4.2+ which sets irq_source_hpd = 255 that causes the subsequent code to try to register the HPD interrupt, which fails, and causes a crash. This commit should be backported to Linux 7.0 and newer. Cc: stable@vger.kernel.org Cc: Dmytro Laktyushkin Cc: Roman Li Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5490 Fixes: def3488eb0fd ("drm/amd/display: refactor HPD to increase flexibility") Signed-off-by: Timur Kristóf Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260821215059.312868-1-timur.kristof@gmail.com Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/link/link_factory.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/link/link_factory.c b/drivers/gpu/drm/amd/display/dc/link/link_factory.c index 89265b083935..7146f8356233 100644 --- a/drivers/gpu/drm/amd/display/dc/link/link_factory.c +++ b/drivers/gpu/drm/amd/display/dc/link/link_factory.c @@ -639,7 +639,7 @@ static bool construct_phy(struct dc_link *link, DC_LOG_DC("BIOS object table - hpd_gpio id: %d", enc_init_data.hpd_gpio->id); DC_LOG_DC("BIOS object table - hpd_gpio en: %d", enc_init_data.hpd_gpio->en); - } else { + } else if (link->ctx->dce_version > DCN_VERSION_4_01) { struct graphics_object_hpd_info hpd_info; if (link->ctx->dc_bios->funcs->get_hpd_info(link->ctx->dc_bios, link->link_id, &hpd_info) == BP_RESULT_OK) { From 9e8bcfde0039238e904b4e720e66b7f4fbf82c0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Fri, 21 Aug 2026 23:50:59 +0200 Subject: [PATCH 1194/1328] drm/amd/display: Log details when failing to register HPD IRQ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This should help diagnose HPD IRQ related issues in the future. Signed-off-by: Timur Kristóf Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260821215059.312868-2-timur.kristof@gmail.com Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c index 9be63996b062..d0239a3de2e1 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c @@ -1735,7 +1735,9 @@ int amdgpu_dm_register_hpd_handlers(struct amdgpu_device *adev) if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || int_params.irq_source < DC_IRQ_SOURCE_HPD1 || int_params.irq_source > DC_IRQ_SOURCE_HPD6) { - drm_err(adev_to_drm(adev), "Failed to register hpd irq!\n"); + drm_err(adev_to_drm(adev), + "Failed to register hpd irq %u for %s!\n", + int_params.irq_source, connector->name); return -EINVAL; } @@ -1753,7 +1755,9 @@ int amdgpu_dm_register_hpd_handlers(struct amdgpu_device *adev) if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || int_params.irq_source < DC_IRQ_SOURCE_HPD1RX || int_params.irq_source > DC_IRQ_SOURCE_HPD6RX) { - drm_err(adev_to_drm(adev), "Failed to register hpd rx irq!\n"); + drm_err(adev_to_drm(adev), + "Failed to register hpd rx irq %u for %s!\n", + int_params.irq_source, connector->name); return -EINVAL; } From 960c4a8069bfd352c48cc88592618f1ebe24c69e Mon Sep 17 00:00:00 2001 From: Xiaogang Chen Date: Sun, 23 Aug 2026 15:22:54 -0500 Subject: [PATCH 1195/1328] drm/amdkfd: Fix error path at svm_migrate_copy_to_ram If page migration from device to sys ram fails for some reasons driver needs release and unlock allocated system pages. To do that driver should use page physical address, or pfn, then get struct page*. Current driver uses dma address(for adev) that is not correct with IOMMU enabled, or even in general. The patch releases and unlocks allocated system pages based on where migration failed by struct page* of sys ram pages. Also dma_unmap correspodent system ram pages at error path. Cc: stable@vger.kernel.org Signed-off-by: Xiaogang Chen Reviewed-by: Felix Kuehling Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_migrate.c | 45 ++++++++++++++++-------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c b/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c index f5af1dd3b70e..a6bb41fdc8c1 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c @@ -258,15 +258,6 @@ svm_migrate_get_sys_page(struct vm_area_struct *vma, unsigned long addr) return page; } -static void svm_migrate_put_sys_page(unsigned long addr) -{ - struct page *page; - - page = pfn_to_page(addr >> PAGE_SHIFT); - unlock_page(page); - put_page(page); -} - static unsigned long svm_migrate_successful_pages(struct migrate_vma *migrate) { unsigned long mpages = 0; @@ -591,9 +582,10 @@ svm_migrate_copy_to_ram(struct amdgpu_device *adev, struct svm_range *prange, dma_addr_t *scratch, u64 npages) { struct device *dev = adev->dev; - u64 *src; + struct page *dpage = NULL; dma_addr_t *dst; - struct page *dpage; + u64 *src; + u64 i = 0, j; u64 addr; int r = 0; @@ -647,6 +639,7 @@ svm_migrate_copy_to_ram(struct amdgpu_device *adev, struct svm_range *prange, r = dma_mapping_error(dev, dst[i]); if (r) { dev_err(adev->dev, "%s: fail %d dma_map_page\n", __func__, r); + dst[i] = 0; goto out_oom; } @@ -654,17 +647,39 @@ svm_migrate_copy_to_ram(struct amdgpu_device *adev, struct svm_range *prange, dst[i] >> PAGE_SHIFT, page_to_pfn(dpage)); migrate->dst[i] = migrate_pfn(page_to_pfn(dpage)); + + dpage = NULL; j++; } - r = svm_migrate_copy_memory_gart(adev, dst + i - j, src + i - j, j, - FROM_VRAM_TO_RAM, mfence); - + if (j > 0) + r = svm_migrate_copy_memory_gart(adev, dst + i - j, src + i - j, j, + FROM_VRAM_TO_RAM, mfence); out_oom: if (r) { pr_debug("failed %d copy to ram\n", r); + + /* first release current dpage when dma_map_page fail */ + if (dpage) { + unlock_page(dpage); + put_page(dpage); + } + + /* release previous allocated sys pages and unmap dma address */ while (i--) { - svm_migrate_put_sys_page(dst[i]); + + if (dst[i]) { + dma_unmap_page(dev, dst[i], PAGE_SIZE, + DMA_BIDIRECTIONAL); + dst[i] = 0; + } + + dpage = migrate_pfn_to_page(migrate->dst[i]); + if (!dpage) + continue; + + unlock_page(dpage); + put_page(dpage); migrate->dst[i] = 0; } } From 520e345ffe05aabef1db82beda4288afb1757ff2 Mon Sep 17 00:00:00 2001 From: Xiaogang Chen Date: Sun, 23 Aug 2026 15:47:15 -0500 Subject: [PATCH 1196/1328] drm/amdkfd: Fix the case that vm range is hole at svm_migrate_copy_to_vram When migration vm range is hole at cpu side(MIGRATE_PFN_MIGRATE set + MIGRATE_PFN_VALID unset) driver still allocates device pages. There is no dma map of src pages and migration. j is 0 and svm_migrate_copy_memory_gart() will return an uninitialized r. That can trigger out_free_vram_pages to drop all VRAM just set up. Initialize r and only call the last svm_migrate_copy_memory_gart if j > 0. Current code postponed the last page to the final copy. This patch flushes on the last page when reach to the end of current drm_buddy_block; avoids another svm_migrate_copy_memory_gart. Cc: stable@vger.kernel.org Signed-off-by: Xiaogang Chen Reviewed-by: Felix Kuehling Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_migrate.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c b/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c index a6bb41fdc8c1..253365a8257e 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c @@ -134,7 +134,7 @@ svm_migrate_copy_memory_gart(struct amdgpu_device *adev, dma_addr_t *sys, u64 gart_s, gart_d; struct dma_fence *next; u64 size; - int r; + int r = 0; ring = to_amdgpu_ring(adev->mman.buffer_funcs_scheds[0]); entity = &adev->mman.move_entities[0]; @@ -284,7 +284,7 @@ svm_migrate_copy_to_vram(struct kfd_node *node, struct svm_range *prange, dma_addr_t *src; u64 *dst; u64 i, j; - int r; + int r = 0; pr_debug("svms 0x%p [0x%lx 0x%lx 0x%llx]\n", prange->svms, prange->start, prange->last, ttm_res_offset); @@ -310,6 +310,7 @@ svm_migrate_copy_to_vram(struct kfd_node *node, struct svm_range *prange, DMA_BIDIRECTIONAL); r = dma_mapping_error(dev, src[i]); if (r) { + src[i] = 0; dev_err(dev, "%s: fail %d dma_map_page\n", __func__, r); goto out_free_vram_pages; @@ -334,7 +335,8 @@ svm_migrate_copy_to_vram(struct kfd_node *node, struct svm_range *prange, pr_debug_ratelimited("dma mapping src to 0x%llx, pfn 0x%lx\n", src[i] >> PAGE_SHIFT, page_to_pfn(spage)); - if (j >= (cursor.size >> PAGE_SHIFT) - 1 && i < npages - 1) { + /* accumulated j + 1 pages reach end of current drm_buddy_block */ + if (j + 1 >= (cursor.size >> PAGE_SHIFT)) { r = svm_migrate_copy_memory_gart(adev, src + i - j, dst + i - j, j + 1, FROM_RAM_TO_VRAM, @@ -348,7 +350,8 @@ svm_migrate_copy_to_vram(struct kfd_node *node, struct svm_range *prange, } } - r = svm_migrate_copy_memory_gart(adev, src + i - j, dst + i - j, j, + if (j > 0) + r = svm_migrate_copy_memory_gart(adev, src + i - j, dst + i - j, j, FROM_RAM_TO_VRAM, mfence); out_free_vram_pages: From a04ea08ddb516f9f21f17574b6a2e7b540dfadf8 Mon Sep 17 00:00:00 2001 From: Prike Liang Date: Fri, 7 Aug 2026 09:46:22 +0800 Subject: [PATCH 1197/1328] drm/amdgpu/userq: fix lock missing for userq fence error set amdgpu_userq_fence_driver() and amdgpu_userq_fence_driver_destroy() don't acquire the dma_fence spinlock, so locking the dma_fence lock before test the signaled state and set error state to avoid missing lock assert error. Signed-off-by: Prike Liang Acked-by: Alex Deucher Signed-off-by: Alex Deucher --- .../gpu/drm/amd/amdgpu/amdgpu_userq_fence.c | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c index a33dbe978798..4b023e024d9f 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c @@ -191,14 +191,15 @@ void amdgpu_userq_fence_driver_destroy(struct kref *ref) struct dma_fence *f; spin_lock_irqsave(&fence_drv->fence_list_lock, flags); + lockdep_assert_held(&fence_drv->fence_list_lock); list_for_each_entry_safe(fence, tmp, &fence_drv->fences, link) { f = &fence->base; - - if (!dma_fence_is_signaled(f)) { + spin_lock(dma_fence_spinlock(f)); + if (!dma_fence_is_signaled_locked(f)) { dma_fence_set_error(f, -ECANCELED); - dma_fence_signal(f); + dma_fence_signal_locked(f); } - + spin_unlock(dma_fence_spinlock(f)); list_del(&fence->link); dma_fence_put(f); } @@ -423,11 +424,16 @@ amdgpu_userq_fence_driver_set_error(struct amdgpu_userq_fence *fence, struct dma_fence *f; spin_lock_irqsave(&fence_drv->fence_list_lock, flags); - + lockdep_assert_held(&fence_drv->fence_list_lock); f = rcu_dereference_protected(&fence->base, lockdep_is_held(&fence_drv->fence_list_lock)); - if (f && !dma_fence_is_signaled_locked(f)) - dma_fence_set_error(f, error); + if (f) { + /* nest f->lock inside fence_list_lock */ + spin_lock(dma_fence_spinlock(f)); + if (!dma_fence_is_signaled_locked(f)) + dma_fence_set_error(f, error); + spin_unlock(dma_fence_spinlock(f)); + } spin_unlock_irqrestore(&fence_drv->fence_list_lock, flags); } From 6aa530642f95d5c48aa336416f94a35e7949b647 Mon Sep 17 00:00:00 2001 From: Vladimir Marioukhine Date: Wed, 12 Aug 2026 12:58:12 -0400 Subject: [PATCH 1198/1328] drm/amdkfd: guard against NULL restore_mqd in CRIU queue restore MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both create_queue_cpsch() and create_queue_nocpsch() unconditionally call mqd_mgr->restore_mqd() when a CRIU restore is in progress (qd != NULL), with no NULL guard. On any system where restore_mqd is not implemented for the given queue type, a user holding CAP_CHECKPOINT_RESTORE can trigger a kernel NULL pointer dereference and panic the machine by issuing KFD_IOC_CRIU_OP_RESTORE with a crafted queue restore object. Note that checkpoint_mqd is likewise unimplemented on GFX12, so no legitimate CRIU image can reach this path — only a hand-crafted restore payload. Add a NULL guard for restore_mqd immediately after mqd_mgr is resolved, unwinding via the existing error labels and returning -EOPNOTSUPP if the callback is not implemented. This mirrors the existing checkpoint_mqd guard in checkpoint_mqd(). Fixes: 48f0bdf4e38e ("drm/amdkfd: Added MQD manager files for GFX12.") Cc: stable@vger.kernel.org Signed-off-by: Vladimir Marioukhine Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c index a23384571193..4bc947c3bd0d 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c @@ -769,6 +769,11 @@ static int create_queue_nocpsch(struct device_queue_manager *dqm, mqd_mgr = dqm->mqd_mgrs[get_mqd_type_from_queue_type( q->properties.type)]; + if (qd && !mqd_mgr->restore_mqd) { + pr_debug("restore_mqd not implemented for this GPU\n"); + retval = -EOPNOTSUPP; + goto deallocate_vmid; + } if (q->properties.type == KFD_QUEUE_TYPE_COMPUTE) { retval = allocate_hqd(dqm, q); if (retval) @@ -2236,6 +2241,11 @@ static int create_queue_cpsch(struct device_queue_manager *dqm, struct queue *q, mqd_mgr = dqm->mqd_mgrs[get_mqd_type_from_queue_type( q->properties.type)]; + if (qd && !mqd_mgr->restore_mqd) { + pr_debug("restore_mqd not implemented for this GPU\n"); + retval = -EOPNOTSUPP; + goto out_deallocate_doorbell; + } if (q->properties.type == KFD_QUEUE_TYPE_SDMA || q->properties.type == KFD_QUEUE_TYPE_SDMA_XGMI) From fdc290ff4ab19c7e0dde36c4cd1e2771b61f6bf5 Mon Sep 17 00:00:00 2001 From: Srinivasan Shanmugam Date: Thu, 20 Aug 2026 15:29:43 +0530 Subject: [PATCH 1199/1328] drm/amd/display: Fix wrong bytes-per-pixel value for dml2_422_packed_10 The pixel format dml2_422_packed_10 needs BytePerPixelDETY set to 8.0/3. But it was accidentally placed in the wrong group that sets it to 4, so the correct value was never used. This caused wrong DET buffer size and bandwidth calculations whenever this format was used. Fix it by moving dml2_422_packed_10 out of the wrong group so it gets the correct value of 8.0/3. Fixes: 7f7d7ea1fa51 ("drm/amd/display: Add new sources for DCN6") Reported-by: Dan Carpenter Cc: Roman Li Cc: Alex Hung Cc: Tom Chung Cc: Aurabindo Pillai Signed-off-by: Srinivasan Shanmugam Reviewed-by: George Zhang Signed-off-by: Alex Deucher --- .../dml2_0/dml21/src/dml2_core/dml2_core_dcn5_calcs_dchub.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn5_calcs_dchub.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn5_calcs_dchub.c index 05a99c4f761b..38ccf9dab31f 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn5_calcs_dchub.c +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn5_calcs_dchub.c @@ -74,7 +74,9 @@ void dcn5_calculate_byte_per_pixel_and_block_sizes( *BytePerPixelDETC = 0; *BytePerPixelY = 8; *BytePerPixelC = 0; - } else if (SourcePixelFormat == dml2_444_32 || SourcePixelFormat == dml2_rgbe || SourcePixelFormat == dml2_422_packed_10 || SourcePixelFormat == dml2_422_packed_12) { + } else if (SourcePixelFormat == dml2_444_32 || + SourcePixelFormat == dml2_rgbe || + SourcePixelFormat == dml2_422_packed_12) { *BytePerPixelDETY = 4; *BytePerPixelDETC = 0; *BytePerPixelY = 4; From 84298acf1c8be2b1b03c0339bf1eb63102c51728 Mon Sep 17 00:00:00 2001 From: Srinivasan Shanmugam Date: Thu, 20 Aug 2026 15:46:01 +0530 Subject: [PATCH 1200/1328] drm/amd/display: Fix redundant GPUVMEnable checks in dcn6 flip schedule Inside dcn6_calculate_flip_schedule(), GPUVMEnable is already checked in the outer if block. But the same GPUVMEnable is checked again in two inner if blocks inside it. Since GPUVMEnable is always true at that point, the inner else branches that assign meta_row_height are never reached. Remove the redundant inner GPUVMEnable checks and directly assign dpte_row_height, which is always the correct value here. Fixes: 7f7d7ea1fa51 ("drm/amd/display: Add new sources for DCN6") Reported-by: Dan Carpenter Cc: Roman Li Cc: Alex Hung Cc: Tom Chung Cc: Aurabindo Pillai Signed-off-by: Srinivasan Shanmugam Reviewed-by: George Zhang Signed-off-by: Alex Deucher --- .../src/dml2_core/dml2_core_dcn6_calcs_dchub.c | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn6_calcs_dchub.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn6_calcs_dchub.c index cae6bee93fe3..a4e2f8604650 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn6_calcs_dchub.c +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn6_calcs_dchub.c @@ -569,20 +569,11 @@ void dcn6_calculate_flip_schedule( if (GPUVMEnable) { if (l->dual_plane) { - if (GPUVMEnable) { - l->min_row_height = dpte_row_height; - l->min_row_height_chroma = dpte_row_height_chroma; - } else { - l->min_row_height = meta_row_height; - l->min_row_height_chroma = meta_row_height_chroma; - } + l->min_row_height = dpte_row_height; + l->min_row_height_chroma = dpte_row_height_chroma; l->min_row_time = math_min2(l->min_row_height * LineTime / VRatio, l->min_row_height_chroma * LineTime / VRatioChroma); } else { - if (GPUVMEnable) - l->min_row_height = dpte_row_height; - else - l->min_row_height = meta_row_height; - + l->min_row_height = dpte_row_height; l->min_row_time = l->min_row_height * LineTime / VRatio; } DML_LOG_VERBOSE("DML::%s: min_row_time = %f\n", __func__, l->min_row_time); From 92a9eebd2a1f892fe482154d83f9f1626bc73d3b Mon Sep 17 00:00:00 2001 From: Linkai Gong Date: Wed, 19 Aug 2026 13:47:42 +0800 Subject: [PATCH 1201/1328] drm/amd/display: fix dc_lock leak on GPU reset error paths On GPU reset, dm_suspend() takes dc_lock and leaves it for dm_resume() to drop. If amdgpu_dm_commit_zero_streams() or dm_dmub_hw_init() fails, the function returns with the lock still held. The matching resume path is then skipped, so every later dc_lock take hangs. Release the cached DC state and unlock before returning the error. Fixes: 3cf7a0bc87f0 ("drm/amd/display: Catch failures for amdgpu_dm_commit_zero_streams()") Fixes: 2b6943df5413 ("drm/amd/display: Pass up errors for reset GPU that fails to init HW") Cc: stable@vger.kernel.org Signed-off-by: Linkai Gong Reviewed-by: Mario Limonciello Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 284aac4d96bc..ec483276d753 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -1589,6 +1589,9 @@ static int dm_suspend(struct amdgpu_ip_block *ip_block) res = amdgpu_dm_commit_zero_streams(dm->dc); if (res != DC_OK) { drm_err(adev_to_drm(adev), "Failed to commit zero streams: %d\n", res); + dc_state_release(dm->cached_dc_state); + dm->cached_dc_state = NULL; + mutex_unlock(&dm->dc_lock); return -EINVAL; } @@ -1884,6 +1887,9 @@ static int dm_resume(struct amdgpu_ip_block *ip_block) r = dm_dmub_hw_init(adev); if (r) { drm_err(adev_to_drm(adev), "DMUB interface failed to initialize: status=%d\n", r); + dc_state_release(dm->cached_dc_state); + dm->cached_dc_state = NULL; + mutex_unlock(&dm->dc_lock); return r; } From 05ec76cfbce653e07cec19b9b8b20e33449d5d87 Mon Sep 17 00:00:00 2001 From: Yifei Gao Date: Tue, 25 Aug 2026 23:46:29 +0000 Subject: [PATCH 1202/1328] ipmi: Fix use-after-free of cmd_rcvr in _ipmi_destroy_user() Commit 9e91f8a6c868 ("ipmi:msghandler: Remove srcu for the ipmi_interfaces list") dropped the synchronize_rcu() between unlinking the command receivers from intf->cmd_rcvrs and freeing them, updating only the comment that explains why the barrier is needed. The cmd_rcvrs list is still traversed under plain RCU: find_cmd_rcvr() walks it inside rcu_read_lock(), and handle_ipmb_get_msg_cmd() borrows rcvr->user from that lookup within the same read-side section. Without the grace period, _ipmi_destroy_user() can kfree() a cmd_rcvr while a reader still holds a pointer to it, causing a use-after-free. The rework only made srcu unnecessary for the interfaces list; the cmd_rcvrs list still relies on plain RCU. Restore the synchronize_rcu() before freeing the receivers. Fixes: 9e91f8a6c868 ("ipmi:msghandler: Remove srcu for the ipmi_interfaces list") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Yifei Gao Message-ID: <20260825234630.1196170-1-gyf161023@gmail.com> Signed-off-by: Corey Minyard --- drivers/char/ipmi/ipmi_msghandler.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/char/ipmi/ipmi_msghandler.c b/drivers/char/ipmi/ipmi_msghandler.c index c73e9def59a6..152dd6b5bb27 100644 --- a/drivers/char/ipmi/ipmi_msghandler.c +++ b/drivers/char/ipmi/ipmi_msghandler.c @@ -1391,6 +1391,7 @@ static void _ipmi_destroy_user(struct ipmi_user *user) } } mutex_unlock(&intf->cmd_rcvrs_mutex); + synchronize_rcu(); while (rcvrs) { rcvr = rcvrs; rcvrs = rcvr->next; From ee440d4fc0d2f15894ab1f64c474a3adbc858880 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Jean?= Date: Sat, 15 Aug 2026 10:09:18 +0000 Subject: [PATCH 1203/1328] crypto: acomp - allocate async request context when cloning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ACOMP_REQUEST_ON_STACK() reserves only enough storage for the synchronous fallback. When an async implementation is selected, callers clone that stack request before retrying, but acomp_request_clone() currently copies only the stack-sized object. The clone therefore has no storage for the async provider request context, and providers such as QAT write past the allocation through acomp_request_ctx(). KASAN does report a slab OOB write. Allocate a zeroed clone large enough for the runtime acomp request size, copy only the bytes present in the source object, and preserve the existing fallback-on-allocation-failure behavior. Use the runtime reqsize because an implementation may adjust it during tfm initialization. Fixes: 097c432caaa6 ("crypto: acomp - Add ACOMP_REQUEST_CLONE") Assisted-by: Codex:gpt-5 Signed-off-by: Jérémy Jean Signed-off-by: Herbert Xu --- crypto/acompress.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/crypto/acompress.c b/crypto/acompress.c index 032de704eb2c..4de1a2ad577f 100644 --- a/crypto/acompress.c +++ b/crypto/acompress.c @@ -559,12 +559,22 @@ EXPORT_SYMBOL_GPL(acomp_walk_virt); struct acomp_req *acomp_request_clone(struct acomp_req *req, size_t total, gfp_t gfp) { + struct crypto_tfm *tfm = req->base.tfm; struct acomp_req *nreq; + size_t len; - nreq = container_of(crypto_request_clone(&req->base, total, gfp), - struct acomp_req, base); - if (nreq == req) + len = sizeof(*req) + + crypto_acomp_reqsize(crypto_acomp_reqtfm(req)); + len = ALIGN(len, CRYPTO_MINALIGN); + + nreq = kzalloc(len, gfp); + if (!nreq) { + req->base.tfm = tfm->fb; return req; + } + + memcpy(nreq, req, sizeof(*req)); + nreq->base.flags &= ~CRYPTO_TFM_REQ_ON_STACK; if (req->src == &req->chain.ssg) nreq->src = &nreq->chain.ssg; From 24c1a47f1ed28f8b31db9a36eb26d1ffbd089d7a Mon Sep 17 00:00:00 2001 From: Jaegeuk Kim Date: Tue, 18 Aug 2026 19:37:23 +0000 Subject: [PATCH 1204/1328] f2fs: don't leave the hashed inode while it's unlinked f2fs_symlink() 1. f2fs_new_inode 2. f2fs_add_link 3. write_being|end to fill the symlink path 4. flush dirty pages and or checkpoint Step 4 is nice to succeed, which doesn't become a reason to roll back the created symlink. OTOH, if we get an error till step 3, don't leave its dentry and its inode. Reviewed-by: Chao Yu Reviewed-by: Wenjie Qi Signed-off-by: Jaegeuk Kim --- fs/f2fs/f2fs.h | 3 +- fs/f2fs/inode.c | 6 ++- fs/f2fs/namei.c | 118 +++++++++++++++++++++++++----------------------- 3 files changed, 69 insertions(+), 58 deletions(-) diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index a1f5f375045a..b0a9c14de595 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -3878,7 +3878,8 @@ void f2fs_update_inode_page(struct inode *inode); int f2fs_write_inode(struct inode *inode, struct writeback_control *wbc); void f2fs_remove_donate_inode(struct inode *inode); void f2fs_evict_inode(struct inode *inode); -void f2fs_handle_failed_inode(struct inode *inode, struct f2fs_lock_context *lc); +void f2fs_handle_failed_inode(struct inode *inode, + struct f2fs_lock_context *lc, bool add_orphan); int f2fs_init_evict_inode_work(void); void f2fs_destroy_evict_inode_work(void); diff --git a/fs/f2fs/inode.c b/fs/f2fs/inode.c index bac1e360d966..96cc0e777567 100644 --- a/fs/f2fs/inode.c +++ b/fs/f2fs/inode.c @@ -1107,7 +1107,8 @@ void f2fs_evict_inode(struct inode *inode) } /* caller should call f2fs_lock_op() */ -void f2fs_handle_failed_inode(struct inode *inode, struct f2fs_lock_context *lc) +void f2fs_handle_failed_inode(struct inode *inode, + struct f2fs_lock_context *lc, bool orphan_free) { struct f2fs_sb_info *sbi = F2FS_I_SB(inode); struct node_info ni; @@ -1129,6 +1130,9 @@ void f2fs_handle_failed_inode(struct inode *inode, struct f2fs_lock_context *lc) /* don't make bad inode, since it becomes a regular file. */ unlock_new_inode(inode); + if (!orphan_free) + goto out; + /* * Note: we should add inode to orphan list before f2fs_unlock_op() * so we can prevent losing this orphan when encoutering checkpoint diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c index 7d9f8e92de06..afaab7739283 100644 --- a/fs/f2fs/namei.c +++ b/fs/f2fs/namei.c @@ -411,7 +411,7 @@ static int f2fs_create(struct mnt_idmap *idmap, struct inode *dir, f2fs_balance_fs(sbi, true); return 0; out: - f2fs_handle_failed_inode(inode, &lc); + f2fs_handle_failed_inode(inode, &lc, true); return err; } @@ -566,40 +566,31 @@ static struct dentry *f2fs_lookup(struct inode *dir, struct dentry *dentry, return ERR_PTR(err); } -static int f2fs_unlink(struct inode *dir, struct dentry *dentry) +static int __do_unlink(struct inode *dir, struct inode *inode, + const struct qstr *name) { struct f2fs_sb_info *sbi = F2FS_I_SB(dir); - struct inode *inode = d_inode(dentry); struct f2fs_dir_entry *de; struct f2fs_lock_context lc; struct folio *folio; int err; - trace_f2fs_unlink_enter(dir, dentry); + if (IS_DEVICE_ALIASING(inode)) + return -EPERM; - if (IS_DEVICE_ALIASING(inode)) { - err = -EPERM; - goto out; - } - - if (unlikely(f2fs_cp_error(sbi))) { - err = -EIO; - goto out; - } + if (unlikely(f2fs_cp_error(sbi))) + return -EIO; err = f2fs_dquot_initialize(dir); if (err) - goto out; + return err; err = f2fs_dquot_initialize(inode); if (err) - goto out; + return err; - de = f2fs_find_entry(dir, &dentry->d_name, &folio); - if (!de) { - if (IS_ERR(folio)) - err = PTR_ERR(folio); - goto out; - } + de = f2fs_find_entry(dir, name, &folio); + if (!de) + return IS_ERR(folio) ? PTR_ERR(folio) : 0; if (unlikely(inode->i_nlink == 0)) { f2fs_warn(sbi, "%s: inode (ino=%llx) has zero i_nlink", @@ -617,11 +608,28 @@ static int f2fs_unlink(struct inode *dir, struct dentry *dentry) err = f2fs_acquire_orphan_inode(sbi); if (err) { f2fs_unlock_op(sbi, &lc); - f2fs_folio_put(folio, false); - goto out; + goto err_out; } f2fs_delete_entry(de, folio, dir, inode); f2fs_unlock_op(sbi, &lc); + return 0; + +corrupted: + err = -EFSCORRUPTED; + set_sbi_flag(sbi, SBI_NEED_FSCK); +err_out: + f2fs_folio_put(folio, false); + return err; +} + +static int f2fs_unlink(struct inode *dir, struct dentry *dentry) +{ + int err; + + trace_f2fs_unlink_enter(dir, dentry); + err = __do_unlink(dir, d_inode(dentry), &dentry->d_name); + if (err) + goto out; /* VFS negative dentries are incompatible with Encoding and * Case-insensitiveness. Eventually we'll want avoid @@ -632,19 +640,10 @@ static int f2fs_unlink(struct inode *dir, struct dentry *dentry) if (IS_ENABLED(CONFIG_UNICODE) && IS_CASEFOLDED(dir)) d_invalidate(dentry); - if (IS_DIRSYNC(dir)) { - err = f2fs_sync_fs(sbi->sb, 1); - if (err) - goto out; - } - - goto out; -corrupted: - err = -EFSCORRUPTED; - set_sbi_flag(sbi, SBI_NEED_FSCK); - f2fs_folio_put(folio, false); + if (IS_DIRSYNC(dir)) + err = f2fs_sync_fs(F2FS_I_SB(dir)->sb, 1); out: - trace_f2fs_unlink_exit(inode, err); + trace_f2fs_unlink_exit(d_inode(dentry), err); return err; } @@ -671,6 +670,8 @@ static int f2fs_symlink(struct mnt_idmap *idmap, struct inode *dir, struct inode *inode; size_t len = strlen(symname); struct fscrypt_str disk_link; + bool orphan_free = true; + int ret = -EAGAIN; int err; if (unlikely(f2fs_cp_error(sbi))) @@ -701,17 +702,19 @@ static int f2fs_symlink(struct mnt_idmap *idmap, struct inode *dir, f2fs_lock_op(sbi, &lc); err = f2fs_add_link(dentry, inode); if (err) - goto out_f2fs_handle_failed_inode; + goto free_inode; f2fs_unlock_op(sbi, &lc); f2fs_alloc_nid_done(sbi, inode->i_ino); + /* Write the symlink path to the new inode. */ err = fscrypt_encrypt_symlink(inode, symname, len, &disk_link); if (err) - goto err_out; + goto unlink_free_inode; err = page_symlink(inode, disk_link.name, disk_link.len); + if (err) + goto unlink_free_inode; -err_out: d_instantiate_new(dentry, inode); /* @@ -723,26 +726,29 @@ static int f2fs_symlink(struct mnt_idmap *idmap, struct inode *dir, * If the symlink path is stored into inline_data, there is no * performance regression. */ - if (!err) { - err = filemap_write_and_wait_range(inode->i_mapping, 0, - disk_link.len - 1); - - if (!err && IS_DIRSYNC(dir)) - err = f2fs_sync_fs(sbi->sb, 1); - } - - if (err) - f2fs_unlink(dir, dentry); + ret = filemap_write_and_wait_range(inode->i_mapping, 0, + disk_link.len - 1); + if (!ret && IS_DIRSYNC(dir)) + err = f2fs_sync_fs(sbi->sb, 1); f2fs_balance_fs(sbi, true); - goto out_free_encrypted_link; - -out_f2fs_handle_failed_inode: - f2fs_handle_failed_inode(inode, &lc); -out_free_encrypted_link: +out: if (disk_link.name != (unsigned char *)symname) kfree(disk_link.name); return err; + +unlink_free_inode: + ret = __do_unlink(dir, inode, &dentry->d_name); + if (ret) { + /* Give up and leave a broken symlink. */ + d_instantiate_new(dentry, inode); + goto out; + } + orphan_free = false; + f2fs_lock_op(sbi, &lc); +free_inode: + f2fs_handle_failed_inode(inode, &lc, orphan_free); + goto out; } static struct dentry *f2fs_mkdir(struct mnt_idmap *idmap, struct inode *dir, @@ -791,7 +797,7 @@ static struct dentry *f2fs_mkdir(struct mnt_idmap *idmap, struct inode *dir, out_fail: clear_inode_flag(inode, FI_INC_LINK); - f2fs_handle_failed_inode(inode, &lc); + f2fs_handle_failed_inode(inode, &lc, true); return ERR_PTR(err); } @@ -847,7 +853,7 @@ static int f2fs_mknod(struct mnt_idmap *idmap, struct inode *dir, f2fs_balance_fs(sbi, true); return 0; out: - f2fs_handle_failed_inode(inode, &lc); + f2fs_handle_failed_inode(inode, &lc, true); return err; } @@ -918,7 +924,7 @@ static int __f2fs_tmpfile(struct mnt_idmap *idmap, struct inode *dir, release_out: f2fs_release_orphan_inode(sbi); out: - f2fs_handle_failed_inode(inode, &lc); + f2fs_handle_failed_inode(inode, &lc, true); return err; } From 3daad923a8685adb66087e0d819559b7eb6ba975 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 24 Aug 2026 11:01:18 -0700 Subject: [PATCH 1205/1328] apparmor: policy_int make sure list heads are initialized before fail path If profile create fails before policy_init is complete the list heads are not properly initialized causing profile_free() sanity checks to trigger the following splat. AppArmor WARN aa_policy_destroy: (((!list_empty(&policy->profiles) && (&policy->profiles)->prev != ((void *) 0x122 + (0xdead000000000000UL))))): WARNING: security/apparmor/lib.c:509 at aa_policy_destroy+0x164/0x1b0 security/apparmor/lib.c:509, CPU#0: syz.0.17/5541 Modules linked in: CPU: 0 UID: 0 PID: 5541 Comm: syz.0.17 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 RIP: 0010:aa_policy_destroy+0x16b/0x1b0 security/apparmor/lib.c:509 Code: 85 ed 7e 4d e8 96 bc 37 fd 5b 41 5c 41 5e 41 5f 5d e9 19 27 4e 07 cc e8 83 bc 37 fd 48 8d 3d 0c f0 d3 0b 48 c7 c6 a4 eb 38 8e <67> 48 0f b9 3a e9 04 ff ff ff e8 66 bc 37 fd 48 8d 3d ff ef d3 0b RSP: 0018:ffffc9000345eaa0 EFLAGS: 00010293 RAX: ffffffff848f530d RBX: ffff88803f734800 RCX: ffff88801af2a580 RDX: 0000000000000000 RSI: ffffffff8e38eba4 RDI: ffffffff90634320 RBP: 0000000000000000 R08: 0000000000000cc0 R09: 00000000ffffffff R10: dffffc0000000000 R11: fffffbfff1d95913 R12: dead000000000122 R13: ffff88803f734800 R14: ffff88803f734828 R15: dffffc0000000000 FS: 00007f5f6a1836c0(0000) GS:ffff88808c519000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 000055d02407b048 CR3: 0000000012aa9000 CR4: 0000000000352ef0 Call Trace: aa_free_profile+0x9d/0x9f0 security/apparmor/policy.c:334 aa_alloc_profile+0x1e4/0x3e0 security/apparmor/policy.c:416 unpack_profile security/apparmor/policy_unpack.c:1153 [inline] aa_unpack+0x17db/0x7430 security/apparmor/policy_unpack.c:1748 aa_replace_profiles+0x226/0x2a20 security/apparmor/policy.c:1183 policy_update+0x234/0x4a0 security/apparmor/apparmorfs.c:505 profile_load+0x1cb/0x320 security/apparmor/apparmorfs.c:522 vfs_write+0x296/0xba0 fs/read_write.c:685 ksys_write+0x150/0x270 fs/read_write.c:739 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:0x7f5f6939e0d9 Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48 RSP: 002b:00007f5f6a183028 EFLAGS: 00000246 ORIG_RAX: 0000000000000001 RAX: ffffffffffffffda RBX: 00007f5f69625fa0 RCX: 00007f5f6939e0d9 RDX: 0000000000000041 RSI: 0000200000000400 RDI: 0000000000000003 RBP: 00007f5f6a183090 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000001 R13: 00007f5f69626038 R14: 00007f5f69625fa0 R15: 00007ffe23725c18 Reported-by: syzbot+faed97c4ed43bfe7fee5@syzkaller.appspotmail.com Link: https://syzkaller.appspot.com/bug?extid=faed97c4ed43bfe7fee5 Fixes: fe6bb31f590c9 ("apparmor: split out shared policy_XXX fns to lib") Signed-off-by: John Johansen --- security/apparmor/lib.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index e97bb7240e0e..73b88bde40bb 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -481,6 +481,8 @@ bool aa_policy_init(struct aa_policy *policy, const char *prefix, char *hname; size_t hname_sz; + INIT_LIST_HEAD(&policy->list); + INIT_LIST_HEAD(&policy->profiles); hname_sz = (prefix ? strlen(prefix) + 2 : 0) + strlen(name) + 1; /* freed by policy_free */ hname = aa_str_alloc(hname_sz, gfp); @@ -493,8 +495,6 @@ bool aa_policy_init(struct aa_policy *policy, const char *prefix, policy->hname = hname; /* base.name is a substring of fqname */ policy->name = basename(policy->hname); - INIT_LIST_HEAD(&policy->list); - INIT_LIST_HEAD(&policy->profiles); return true; } From d51fc9d4cd6eb18ac82913d83ecf7bd8c85f71ee Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Fri, 31 Jul 2026 15:42:18 +0200 Subject: [PATCH 1206/1328] power: sequencing: rename pwrseq_power_on/off() to pwrseq_enable/disable() The way power sequencing works means that a call to pwrseq_power_on() does not necessarily result in the pwrseq target being powered-on at that time: it may have already been powered on before. Similarly: a call to pwrseq_power_off() does not have to result in an actual powering off of resources: there may still be other users that requested a power-on before. We will also introduce the concept of "non-controllable" pwrseq targets soon which further increases the disconnect between the naming convention and the actual semantics. What consumers of pwrseq descriptors actually do is: they *vote* for a powering on of a given target or retract that vote. These operations could be called get/put in line with runtime PM but this could become confusing since we already provide pwrseq_get/put() for a different purpose. pwrseq_vote_on/off() also have been rejected as unusual in the tree. Change the name of the two functions to pwrseq_enable/disable() which better reflects their purpose and semantics and also mirrors other enable-counted resources like regulators and clocks. No functional change intended. If at any point users need to know *when* the exact power event happens, we can provide that information in the form of a notifier. Acked-by: Jeff Johnson Acked-by: Bjorn Helgaas Acked-by: Manivannan Sadhasivam Acked-by: Alessio Belle # imagination Link: https://patch.msgid.link/20260731-pwrseq-vote-rename-v3-1-44e60b8be053@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- Documentation/driver-api/pwrseq.rst | 4 ++-- drivers/bluetooth/hci_qca.c | 4 ++-- drivers/gpu/drm/imagination/pvr_power.c | 4 ++-- drivers/net/wireless/ath/ath10k/snoc.c | 6 +++--- drivers/pci/pwrctrl/generic.c | 4 ++-- drivers/pci/pwrctrl/pci-pwrctrl-pwrseq.c | 4 ++-- drivers/power/sequencing/core.c | 16 ++++++++-------- include/linux/pwrseq/consumer.h | 8 ++++---- 8 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Documentation/driver-api/pwrseq.rst b/Documentation/driver-api/pwrseq.rst index ad18b2326b68..e1056d141845 100644 --- a/Documentation/driver-api/pwrseq.rst +++ b/Documentation/driver-api/pwrseq.rst @@ -50,9 +50,9 @@ Consumer interface The consumer API is aimed to be as simple as possible. The driver interested in getting a descriptor from the power sequencer should call pwrseq_get() and specify the name of the target it wants to reach in the sequence after calling -pwrseq_power_up(). The descriptor can be released by calling pwrseq_put() and +pwrseq_enable(). The descriptor can be released by calling pwrseq_put() and the consumer can request the powering down of its target with -pwrseq_power_off(). Note that there is no guarantee that pwrseq_power_off() +pwrseq_disable(). Note that there is no guarantee that pwrseq_disable() will have any effect as there may be multiple users of the underlying resources who may keep them active. diff --git a/drivers/bluetooth/hci_qca.c b/drivers/bluetooth/hci_qca.c index 244447195619..1153ba9507e3 100644 --- a/drivers/bluetooth/hci_qca.c +++ b/drivers/bluetooth/hci_qca.c @@ -2256,7 +2256,7 @@ static void qca_power_off(struct hci_uart *hu) } if (power && power->pwrseq) { - pwrseq_power_off(power->pwrseq); + pwrseq_disable(power->pwrseq); set_bit(QCA_BT_OFF, &qca->flags); return; } @@ -2316,7 +2316,7 @@ static int qca_regulator_enable(struct qca_serdev *qcadev) int ret; if (power->pwrseq) - return pwrseq_power_on(power->pwrseq); + return pwrseq_enable(power->pwrseq); /* Already enabled */ if (power->vregs_on) diff --git a/drivers/gpu/drm/imagination/pvr_power.c b/drivers/gpu/drm/imagination/pvr_power.c index a71d5b35601e..eb4b6ecdf4f4 100644 --- a/drivers/gpu/drm/imagination/pvr_power.c +++ b/drivers/gpu/drm/imagination/pvr_power.c @@ -352,12 +352,12 @@ static int pvr_power_init_pwrseq(struct pvr_device *pvr_dev) static int pvr_power_on_sequence_pwrseq(struct pvr_device *pvr_dev) { - return pwrseq_power_on(pvr_dev->pwrseq); + return pwrseq_enable(pvr_dev->pwrseq); } static int pvr_power_off_sequence_pwrseq(struct pvr_device *pvr_dev) { - return pwrseq_power_off(pvr_dev->pwrseq); + return pwrseq_disable(pvr_dev->pwrseq); } const struct pvr_power_sequence_ops pvr_power_sequence_ops_pwrseq = { diff --git a/drivers/net/wireless/ath/ath10k/snoc.c b/drivers/net/wireless/ath/ath10k/snoc.c index 310650227578..f28fed89cc28 100644 --- a/drivers/net/wireless/ath/ath10k/snoc.c +++ b/drivers/net/wireless/ath/ath10k/snoc.c @@ -1025,7 +1025,7 @@ static int ath10k_hw_power_on(struct ath10k *ar) ath10k_dbg(ar, ATH10K_DBG_SNOC, "soc power on\n"); - ret = pwrseq_power_on(ar_snoc->pwrseq); + ret = pwrseq_enable(ar_snoc->pwrseq); if (ret) return ret; @@ -1042,7 +1042,7 @@ static int ath10k_hw_power_on(struct ath10k *ar) vreg_off: regulator_bulk_disable(ar_snoc->num_vregs, ar_snoc->vregs); pwrseq_off: - pwrseq_power_off(ar_snoc->pwrseq); + pwrseq_disable(ar_snoc->pwrseq); return ret; } @@ -1060,7 +1060,7 @@ static int ath10k_hw_power_off(struct ath10k *ar) ret_vreg = regulator_bulk_disable(ar_snoc->num_vregs, ar_snoc->vregs); if (ar_snoc->pwrseq) - ret_seq = pwrseq_power_off(ar_snoc->pwrseq); + ret_seq = pwrseq_disable(ar_snoc->pwrseq); return ret_vreg ? : ret_seq; } diff --git a/drivers/pci/pwrctrl/generic.c b/drivers/pci/pwrctrl/generic.c index 1ae19450a455..dc6d9796160e 100644 --- a/drivers/pci/pwrctrl/generic.c +++ b/drivers/pci/pwrctrl/generic.c @@ -30,7 +30,7 @@ static int slot_pwrctrl_power_on(struct pci_pwrctrl *pwrctrl) int ret; if (slot->pwrseq) { - pwrseq_power_on(slot->pwrseq); + pwrseq_enable(slot->pwrseq); return 0; } @@ -49,7 +49,7 @@ static int slot_pwrctrl_power_off(struct pci_pwrctrl *pwrctrl) struct slot_pwrctrl, pwrctrl); if (slot->pwrseq) { - pwrseq_power_off(slot->pwrseq); + pwrseq_disable(slot->pwrseq); return 0; } diff --git a/drivers/pci/pwrctrl/pci-pwrctrl-pwrseq.c b/drivers/pci/pwrctrl/pci-pwrctrl-pwrseq.c index c7e4beec160a..d233800e5906 100644 --- a/drivers/pci/pwrctrl/pci-pwrctrl-pwrseq.c +++ b/drivers/pci/pwrctrl/pci-pwrctrl-pwrseq.c @@ -57,7 +57,7 @@ static int pwrseq_pwrctrl_power_on(struct pci_pwrctrl *pwrctrl) struct pwrseq_pwrctrl *pwrseq = container_of(pwrctrl, struct pwrseq_pwrctrl, pwrctrl); - return pwrseq_power_on(pwrseq->pwrseq); + return pwrseq_enable(pwrseq->pwrseq); } static int pwrseq_pwrctrl_power_off(struct pci_pwrctrl *pwrctrl) @@ -65,7 +65,7 @@ static int pwrseq_pwrctrl_power_off(struct pci_pwrctrl *pwrctrl) struct pwrseq_pwrctrl *pwrseq = container_of(pwrctrl, struct pwrseq_pwrctrl, pwrctrl); - return pwrseq_power_off(pwrseq->pwrseq); + return pwrseq_disable(pwrseq->pwrseq); } static int pwrseq_pwrctrl_probe(struct platform_device *pdev) diff --git a/drivers/power/sequencing/core.c b/drivers/power/sequencing/core.c index 02f42da91598..721e888b658d 100644 --- a/drivers/power/sequencing/core.c +++ b/drivers/power/sequencing/core.c @@ -708,7 +708,7 @@ void pwrseq_put(struct pwrseq_desc *desc) pwrseq = desc->pwrseq; if (desc->powered_on) - pwrseq_power_off(desc); + pwrseq_disable(desc); kfree(desc); module_put(pwrseq->owner); @@ -874,7 +874,7 @@ static int pwrseq_unit_disable(struct pwrseq_device *pwrseq, } /** - * pwrseq_power_on() - Issue a power-on request on behalf of the consumer + * pwrseq_enable() - Issue a power-on request on behalf of the consumer * device. * @desc: Descriptor referencing the power sequencer. * @@ -887,7 +887,7 @@ static int pwrseq_unit_disable(struct pwrseq_device *pwrseq, * Returns: * 0 on success, negative error number on failure. */ -int pwrseq_power_on(struct pwrseq_desc *desc) +int pwrseq_enable(struct pwrseq_desc *desc) { struct pwrseq_device *pwrseq; struct pwrseq_target *target; @@ -925,14 +925,14 @@ int pwrseq_power_on(struct pwrseq_desc *desc) return ret; } -EXPORT_SYMBOL_GPL(pwrseq_power_on); +EXPORT_SYMBOL_GPL(pwrseq_enable); /** - * pwrseq_power_off() - Issue a power-off request on behalf of the consumer + * pwrseq_disable() - Issue a power-off request on behalf of the consumer * device. * @desc: Descriptor referencing the power sequencer. * - * This undoes the effects of pwrseq_power_on(). It issues a power-off request + * This undoes the effects of pwrseq_enable(). It issues a power-off request * on behalf of the consumer and when the last remaining user does so, the * power-down sequence will be started. If one is in progress, the function * will block until it's complete and then return. @@ -940,7 +940,7 @@ EXPORT_SYMBOL_GPL(pwrseq_power_on); * Returns: * 0 on success, negative error number on failure. */ -int pwrseq_power_off(struct pwrseq_desc *desc) +int pwrseq_disable(struct pwrseq_desc *desc) { struct pwrseq_device *pwrseq; struct pwrseq_unit *unit; @@ -966,7 +966,7 @@ int pwrseq_power_off(struct pwrseq_desc *desc) return ret; } -EXPORT_SYMBOL_GPL(pwrseq_power_off); +EXPORT_SYMBOL_GPL(pwrseq_disable); /** * pwrseq_to_device() - Get the pwrseq device pointer from a descriptor. diff --git a/include/linux/pwrseq/consumer.h b/include/linux/pwrseq/consumer.h index 3c907c9e1885..16fad5f3e3ab 100644 --- a/include/linux/pwrseq/consumer.h +++ b/include/linux/pwrseq/consumer.h @@ -20,8 +20,8 @@ void pwrseq_put(struct pwrseq_desc *desc); struct pwrseq_desc * __must_check devm_pwrseq_get(struct device *dev, const char *target); -int pwrseq_power_on(struct pwrseq_desc *desc); -int pwrseq_power_off(struct pwrseq_desc *desc); +int pwrseq_enable(struct pwrseq_desc *desc); +int pwrseq_disable(struct pwrseq_desc *desc); struct device *pwrseq_to_device(struct pwrseq_desc *desc); @@ -43,12 +43,12 @@ devm_pwrseq_get(struct device *dev, const char *target) return ERR_PTR(-ENOSYS); } -static inline int pwrseq_power_on(struct pwrseq_desc *desc) +static inline int pwrseq_enable(struct pwrseq_desc *desc) { return -ENOSYS; } -static inline int pwrseq_power_off(struct pwrseq_desc *desc) +static inline int pwrseq_disable(struct pwrseq_desc *desc) { return -ENOSYS; } From f967455fb2a5a2079b9eb5823e9ccf359174bf9f Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Sat, 22 Aug 2026 16:49:27 +0800 Subject: [PATCH 1207/1328] seg6: reset IP6CB after IPv6 decapsulation decap_and_validate() pulls the outer SRv6 headers and makes the inner packet the skb network header. The IPv6 control block still contains values collected while parsing the outer packet, including nhoff and extension-header flags. End.DX6 and End.DT6 route the inner IPv6 packet directly to the IPv6 input path. An unprivileged user can reach End.DT6 from a user and net namespace by installing a local SID and injecting an outer packet with Hop-by-Hop and Destination Options headers followed by an SRH and a minimal inner IPv6 packet. The outer extension headers leave a large nhoff in IP6CB. After decapsulation, ip6_protocol_deliver_rcu() uses that stale offset on the inner packet and reads beyond the skb head. KASAN reports: BUG: KASAN: slab-out-of-bounds in ip6_protocol_deliver_rcu ip6_protocol_deliver_rcu+0x1118/0x1450 ip6_input_finish+0x11b/0x240 seg6_local_input_core+0xed/0x2e0 lwtunnel_input+0x1e9/0x4e0 ipv6_rthdr_rcv+0x525f/0x6c50 ip6_protocol_deliver_rcu+0xcb7/0x1450 Before clearing IP6CB for an inner IPv6 packet, save its incoming interface index and L3 slave state. Restore both after the clear and set nhoff to the inner IPv6 base-header nexthdr field. Use IP6CB(skb)->iif rather than skb->skb_iif because VRF processing can replace skb_iif with the L3 master while IP6CB keeps the receiving interface. Preserve IP6SKB_L3SLAVE for the same reason. Fixes: d7a669dd2f8b ("ipv6: sr: add helper functions for seg6local") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Zhiling Zou Reviewed-by: Andrea Mayer Signed-off-by: David S. Miller --- net/ipv6/seg6_local.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/net/ipv6/seg6_local.c b/net/ipv6/seg6_local.c index 95ea0b62729a..7b5212220185 100644 --- a/net/ipv6/seg6_local.c +++ b/net/ipv6/seg6_local.c @@ -261,6 +261,15 @@ static bool decap_and_validate(struct sk_buff *skb, int proto) memset(IPCB(skb), 0, sizeof(*IPCB(skb))); IPCB(skb)->iif = iif; + } else if (proto == IPPROTO_IPV6) { + bool l3slave = ipv6_l3mdev_skb(IP6CB(skb)->flags); + int iif = IP6CB(skb)->iif; + + memset(IP6CB(skb), 0, sizeof(*IP6CB(skb))); + IP6CB(skb)->iif = iif; + IP6CB(skb)->nhoff = offsetof(struct ipv6hdr, nexthdr); + if (l3slave) + IP6CB(skb)->flags |= IP6SKB_L3SLAVE; } return true; From 84d2d96ac3f1bb467aca65adf2c19431e6eeb151 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Tue, 25 Aug 2026 15:19:49 +0700 Subject: [PATCH 1208/1328] ASoC: soc-generic-dmaengine: Fix DMA channel request warning The DMA channel request code currently warns about legacy DMA failures when the channel name is not present in dma-names. This can report a firmware lookup failure as a legacy DMA failure. Furthermore, failures from the legacy DMA path are already reported by find_candidate(), making these warnings redundant. Only warn when the channel name is present in dma-names but the request fails, avoiding misleading and duplicate error messages. Fixes: 9167f260477b ("ASoC: soc-generic-dmaengine: Handle DMA channel request failures correctly") Reported-by: Sebastian Reichel Link: https://lore.kernel.org/all/aoyBuho270dTWYBL@jupiter.universe/ Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260825081949.55537-1-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/soc-generic-dmaengine-pcm.c | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/sound/soc/soc-generic-dmaengine-pcm.c b/sound/soc/soc-generic-dmaengine-pcm.c index 3b18d90e81c3..65b7ec52a054 100644 --- a/sound/soc/soc-generic-dmaengine-pcm.c +++ b/sound/soc/soc-generic-dmaengine-pcm.c @@ -397,24 +397,8 @@ static int dmaengine_pcm_request_chan_of(struct dmaengine_pcm *pcm, if (PTR_ERR(chan) == -EPROBE_DEFER) return -EPROBE_DEFER; - bool has_fw_node = dev->of_node || is_acpi_device_node(dev->fwnode); - bool name_exists_in_fw = false; - - if (has_fw_node) - name_exists_in_fw = device_property_match_string(dev, - "dma-names", - name) >= 0; - - if (has_fw_node && name_exists_in_fw) - dev_warn(dev, "DTS/ACPI DMA channel '%s' request failed (%ld)\n", - name, PTR_ERR(chan)); - - if (has_fw_node && !name_exists_in_fw) - dev_warn(dev, "DTS/ACPI name '%s' not found, legacy failed (%ld)\n", - name, PTR_ERR(chan)); - - if (!has_fw_node) - dev_warn(dev, "Legacy DMA channel '%s' request failed (%ld)\n", + if (device_property_match_string(dev, "dma-names", name) >= 0) + dev_warn(dev, "dma-names has '%s' but request failed (%ld)\n", name, PTR_ERR(chan)); pcm->chan[i] = NULL; From 385c7af4e3b95d0769fd211831674e83b16a2ebf Mon Sep 17 00:00:00 2001 From: Ahmad Byagowi Date: Sun, 23 Aug 2026 09:34:36 -0700 Subject: [PATCH 1209/1328] i2c: mux: Fix channel node leak on adapter add failure i2c_mux_add_adapter() takes a reference to the Device Tree channel node before registering the new adapter. If adapter registration fails, the error path frees the private data without dropping that reference. Release the channel node before freeing the private data. Fixes: bc45449b1444 ("i2c/of: Automatically populate i2c mux busses from device tree data.") Signed-off-by: Ahmad Byagowi Cc: # v3.5+ Acked-by: Peter Rosin Signed-off-by: Andi Shyti Link: https://patch.msgid.link/b3e46bbee781b3cb4029aca9a71316cc5e36dc17.1787502619.git.ahmadexp@gmail.com --- drivers/i2c/i2c-mux.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/i2c/i2c-mux.c b/drivers/i2c/i2c-mux.c index 681a201c239b..68a4c34b5987 100644 --- a/drivers/i2c/i2c-mux.c +++ b/drivers/i2c/i2c-mux.c @@ -408,6 +408,7 @@ int i2c_mux_add_adapter(struct i2c_mux_core *muxc, return 0; err_free_priv: + of_node_put(priv->adap.dev.of_node); kfree(priv); return ret; } From 032bb633e0c34a88b70a4bfc65ebd4e2b510f706 Mon Sep 17 00:00:00 2001 From: Mehmet Aysel Date: Tue, 25 Aug 2026 12:24:31 +0300 Subject: [PATCH 1210/1328] ASoC: amd: acp-config: Add HVY-WXX9/M1060 DMI quirk Add DMI match table entry for HUAWEI HVY-WXX9 board, product version M1060, a MateBook D16 2021 (Ryzen 5 4600H) revision not covered by the existing M1010/M1020/M1040 entries. This board uses the same FLAG_AMD_LEGACY / ACP_PCI_DEV_ID configuration as the other HVY-WXX9 variants. Signed-off-by: Mehmet Aysel Link: https://patch.msgid.link/20260825092432.56292-1-mehmet4ysel@gmail.com Signed-off-by: Mark Brown --- sound/soc/amd/acp-config.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/sound/soc/amd/acp-config.c b/sound/soc/amd/acp-config.c index 3021dc498c85..ef6fa3cca848 100644 --- a/sound/soc/amd/acp-config.c +++ b/sound/soc/amd/acp-config.c @@ -209,6 +209,20 @@ static const struct config_entry config_table[] = { {} }, }, + { + .flags = FLAG_AMD_LEGACY, + .device = ACP_PCI_DEV_ID, + .dmi_table = (const struct dmi_system_id []) { + { + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "HUAWEI"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "HVY-WXX9"), + DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "M1060"), + }, + }, + {} + }, + }, }; static int snd_amd_acp_acpi_find_config(struct pci_dev *pci) From c1a39c228aceebc0ede83781ea906d257e324009 Mon Sep 17 00:00:00 2001 From: Mehmet Aysel Date: Tue, 25 Aug 2026 12:24:32 +0300 Subject: [PATCH 1211/1328] ASoC: amd: acp3x-es83xx: Add HVY-WXX9/M1060 DMI quirk Add matching DMI table entry for the ES83xx machine driver, so the HUAWEI HVY-WXX9 / M1060 board (MateBook D16 2021, Ryzen 5 4600H) can successfully probe its ES8316 codec via the acp3x-es83xx machine driver, consistent with the existing M1010/M1020/M1040 entries for the same board name. Signed-off-by: Mehmet Aysel Link: https://patch.msgid.link/20260825092432.56292-2-mehmet4ysel@gmail.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c b/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c index 46fff565d732..3a640e652314 100644 --- a/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c +++ b/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c @@ -382,6 +382,14 @@ static const struct dmi_system_id acp3x_es83xx_dmi_table[] = { }, .driver_data = (void *)(ES83XX_ENABLE_DMIC), }, + { + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "HUAWEI"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "HVY-WXX9"), + DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "M1060"), + }, + .driver_data = (void *)(ES83XX_ENABLE_DMIC), + }, {} }; From 4d6642d64cd00f276549ec2c0b0278aea2f8c1aa Mon Sep 17 00:00:00 2001 From: Sehat Mahde Date: Tue, 25 Aug 2026 17:46:40 -0500 Subject: [PATCH 1212/1328] ASoC: amd: acp-config: force SoundWire probe on HP OmniBook X Flip 16 The BIOS on the HP OmniBook X Flip 16-cc0xxx (board 8EA2) reports acp-audio-config-flag = FLAG_AMD_LEGACY_ONLY_DMIC. This binds the legacy ACP driver and registers a PDM-only card, so the SoundWire links are never scanned and the two TAS2783 speaker amplifiers and RT712-VB codec do not enumerate. Add a DMI entry for board 8EA2 to the ACP70 ACPI flag override table so the firmware-provided flag is overridden and snd_pci_ps probes instead. On the affected system, an otherwise identical upstream kernel without this entry binds snd_acp_pci, enumerates no SoundWire slave devices and exposes no internal speaker PCM. With the entry added, snd_pci_ps binds, both TAS2783 amplifiers and the RT712-VB enumerate over SoundWire, and the amd-soundwire card exposes the internal speaker playback PCM. Developed with AI assistance. ChatGPT helped analyze the ACP and SoundWire behavior, structure the controlled A/B testing, and draft the patch changelog. All hardware measurements, kernel builds, reboots and playback tests were performed by the submitter. The submitter has reviewed the change, understands it and takes responsibility for it. Assisted-by: ChatGPT:GPT-5.6 Sol Signed-off-by: Sehat Mahde Link: https://patch.msgid.link/20260825224640.13662-1-hskmahde@gmail.com Signed-off-by: Mark Brown --- sound/soc/amd/acp-config.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/soc/amd/acp-config.c b/sound/soc/amd/acp-config.c index ef6fa3cca848..a27336b132f2 100644 --- a/sound/soc/amd/acp-config.c +++ b/sound/soc/amd/acp-config.c @@ -70,6 +70,13 @@ static const struct dmi_system_id acp70_acpi_flag_override_table[] = { DMI_MATCH(DMI_BOARD_NAME, "8EA1"), }, }, + { + /* HP OmniBook X Flip 16-cc0xxx */ + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "HP"), + DMI_MATCH(DMI_BOARD_NAME, "8EA2"), + }, + }, {} }; From fe66c3ff85e85d492673c6dc95c3d624a63e0282 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Wed, 26 Aug 2026 12:22:34 +0100 Subject: [PATCH 1213/1328] of/irq: Fix device node refcount leak in of_irq_get_affinity() of_irq_parse_one() raises the refcount of the interrupt controller node on success, and of_irq_get_affinity() returns without putting it, so every call past the parse leaks one reference. It is reached from platform_get_irq_affinity(), used by arm_pmu, arm_spe_pmu and coresight-trbe. Put it once irq_populate_fwspec_info() has run: no in-tree ->get_fwspec_info() returns a mask that lives in the node. Fixes: 5404f5c06dd4 ("of/irq: Add interrupt affinity reporting interface") Signed-off-by: Fuad Tabba Link: https://patch.msgid.link/20260826112234.1033974-1-fuad.tabba@linux.dev Signed-off-by: Rob Herring (Arm) --- drivers/of/irq.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/of/irq.c b/drivers/of/irq.c index 967c19100879..ec035367c950 100644 --- a/drivers/of/irq.c +++ b/drivers/of/irq.c @@ -563,7 +563,9 @@ const struct cpumask *of_irq_get_affinity(struct device_node *dev, int index) of_phandle_args_to_fwspec(oirq.np, oirq.args, oirq.args_count, &fwspec); - if (irq_populate_fwspec_info(&fwspec, &info)) + rc = irq_populate_fwspec_info(&fwspec, &info); + of_node_put(oirq.np); + if (rc) return NULL; return info.affinity; From 14511c9b54ceeeef487409d73947c89ee8563590 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Tue, 25 Aug 2026 21:57:45 +0900 Subject: [PATCH 1214/1328] ASoC: dapm: Fix off-by-one check on the second enum channel The snd_soc_dapm_put_enum_double() rejects item[0] once it reaches e->items, but it lets item[1] be equal to it. Both go on to snd_soc_enum_item_to_val(), which indexes e->values with no bound of its own, so an enum with a value table reads one element past the end. The indexing arrived with the MUX consolidation, which relaxed the item[1] check in the same hunk. The value MUX handler it deleted used >= there, and the snd_soc_put_enum_double() in soc-ops.c still does. Only adav80x pairs a value table with two shifts, and its second channel looks accidental, but the control does report two values. Writing three into it reads off the end of adav80x_mux_values. The core catches that only under CONFIG_SND_CTL_INPUT_VALIDATION, which defaults off. Fixes: 3727b4968453 ("ASoC: dapm: Consolidate MUXs and value MUXs") Assisted-by: Claude:claude-opus-5 Signed-off-by: HyeongJun An Link: https://patch.msgid.link/20260825125745.932832-1-sammiee5311@gmail.com Signed-off-by: Mark Brown --- sound/soc/soc-dapm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index c98e917cc911..06b85313af12 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -3614,7 +3614,7 @@ int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol, val = snd_soc_enum_item_to_val(e, item[0]) << e->shift_l; mask = e->mask << e->shift_l; if (e->shift_l != e->shift_r) { - if (item[1] > e->items) + if (item[1] >= e->items) return -EINVAL; val |= snd_soc_enum_item_to_val(e, item[1]) << e->shift_r; mask |= e->mask << e->shift_r; From 9642a5e843758686e02a7f0d1df9eb0f7f96603c Mon Sep 17 00:00:00 2001 From: Bob Song Date: Wed, 26 Aug 2026 19:53:44 +0800 Subject: [PATCH 1215/1328] ALSA: hda/conexant: Always enable the headset-mic pin on plugin Rename the function to cx_process_headset_detect_plug_type() to reflect that it only reports the detected plug type, and move the pin control write into cx_update_headset_mic_vref() so that node 0x19 is always set to enable the headset mic with the 80% VREF whenever the mic is present, regardless of the type detection result. Signed-off-by: Bob Song Link: https://patch.msgid.link/20260826115344.2128835-1-songxiebing@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/codecs/conexant.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/sound/hda/codecs/conexant.c b/sound/hda/codecs/conexant.c index 8e950c796209..7357dc91ac49 100644 --- a/sound/hda/codecs/conexant.c +++ b/sound/hda/codecs/conexant.c @@ -207,7 +207,7 @@ static void cx_remove(struct hda_codec *codec) snd_hda_gen_remove(codec); } -static void cx_process_headset_plugin(struct hda_codec *codec) +static void cx_process_headset_detect_plug_type(struct hda_codec *codec) { unsigned int val; unsigned int count = 0; @@ -241,10 +241,12 @@ static void cx_update_headset_mic_vref(struct hda_codec *codec, struct hda_jack_ * Check hp&mic tag to process headset plugin & plugout. */ mic_present = snd_hda_codec_read(codec, 0x19, 0, AC_VERB_GET_PIN_SENSE, 0x0); - if (!(mic_present & AC_PINSENSE_PRESENCE)) /* mic plugout */ + if (!(mic_present & AC_PINSENSE_PRESENCE)) { /* mic plugout */ snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20); - else - cx_process_headset_plugin(codec); + } else { + cx_process_headset_detect_plug_type(codec); + snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24); + } } static int cx_suspend(struct hda_codec *codec) From 4f49c3f8a5a86d237bb202ecb17c2802ddc8fd2f Mon Sep 17 00:00:00 2001 From: Hongling Zeng Date: Thu, 4 Jun 2026 15:43:25 +0800 Subject: [PATCH 1216/1328] ceph: Fix ERR_PTR(0) in ceph_mkdir() When mkdir succeeds, ceph_mkdir() sets ret to ERR_PTR(0) which is incorrect. It should return NULL instead for success. Fixes: 88d5baf69082 ("Change inode_operations.mkdir to return struct dentry *") Signed-off-by: Hongling Zeng Reviewed-by: Viacheslav Dubeyko Signed-off-by: Ilya Dryomov --- fs/ceph/dir.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c index ef9e92e362d3..b4b541a1180c 100644 --- a/fs/ceph/dir.c +++ b/fs/ceph/dir.c @@ -1174,7 +1174,7 @@ static struct dentry *ceph_mkdir(struct mnt_idmap *idmap, struct inode *dir, !req->r_reply_info.head->is_target && !req->r_reply_info.head->is_dentry) err = ceph_handle_notrace_create(dir, dentry); - ret = ERR_PTR(err); + ret = err ? ERR_PTR(err) : NULL; out_req: if (!IS_ERR(ret) && req->r_dentry != dentry) /* Some other dentry was spliced in */ From 888d33b208bd6929808abdc0728e3e5f744b60dc Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Fri, 29 May 2026 20:06:45 -0700 Subject: [PATCH 1217/1328] ceph: pass fscrypt `tname` buffers directly ceph_fname_to_usr() needs a temporary buffer for some operations (currently only base64-decoding ciphertext) and it is convenient to allow the caller to specify this buffer to avoid a heap allocation, so it has a (nullable) `tname` argument. Until now, this argument was a `struct fscrypt_str`; however, this is unnecessary for two reasons: 1. `tname->len` isn't used anywhere: ceph_fname_to_usr() assumes a buffer large enough to hold the ciphertext, and parse_reply_info_readdir() -- the only caller to use tname -- doesn't set it. 2. While the `tname` parameter is documented "may be NULL," parse_reply_info_readdir() always passes it but with `tname->name` sometimes NULL in violation of the contract, indicating that the unnecessary container creates actual confusion. Therefore, change the type to `unsigned char *` and pass the buffer directly. Signed-off-by: Sam Edwards Reviewed-by: Alex Markuze Signed-off-by: Ilya Dryomov --- fs/ceph/crypto.c | 9 ++++----- fs/ceph/crypto.h | 4 ++-- fs/ceph/mds_client.c | 6 +++--- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/fs/ceph/crypto.c b/fs/ceph/crypto.c index 64d240759277..7493a3acd7d0 100644 --- a/fs/ceph/crypto.c +++ b/fs/ceph/crypto.c @@ -300,7 +300,7 @@ int ceph_encode_encrypted_dname(struct inode *parent, char *buf, int elen) * * Returns 0 on success or negative error code on error. */ -int ceph_fname_to_usr(const struct ceph_fname *fname, struct fscrypt_str *tname, +int ceph_fname_to_usr(const struct ceph_fname *fname, unsigned char *tname, struct fscrypt_str *oname, bool *is_nokey) { struct inode *dir = fname->dir; @@ -357,16 +357,15 @@ int ceph_fname_to_usr(const struct ceph_fname *fname, struct fscrypt_str *tname, ret = fscrypt_fname_alloc_buffer(NAME_MAX, &_tname); if (ret) goto out_inode; - tname = &_tname; + tname = _tname.name; } - declen = base64_decode(name, name_len, - tname->name, false, BASE64_IMAP); + declen = base64_decode(name, name_len, tname, false, BASE64_IMAP); if (declen <= 0) { ret = -EIO; goto out; } - iname.name = tname->name; + iname.name = tname; iname.len = declen; } else { iname.name = fname->ctext; diff --git a/fs/ceph/crypto.h b/fs/ceph/crypto.h index b748e2060bc9..79cb563fd887 100644 --- a/fs/ceph/crypto.h +++ b/fs/ceph/crypto.h @@ -115,7 +115,7 @@ static inline void ceph_fname_free_buffer(struct inode *parent, fscrypt_fname_free_buffer(fname); } -int ceph_fname_to_usr(const struct ceph_fname *fname, struct fscrypt_str *tname, +int ceph_fname_to_usr(const struct ceph_fname *fname, unsigned char *tname, struct fscrypt_str *oname, bool *is_nokey); int ceph_fscrypt_prepare_readdir(struct inode *dir); @@ -204,7 +204,7 @@ static inline void ceph_fname_free_buffer(struct inode *parent, } static inline int ceph_fname_to_usr(const struct ceph_fname *fname, - struct fscrypt_str *tname, + unsigned char *tname, struct fscrypt_str *oname, bool *is_nokey) { oname->name = fname->name; diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c index 3c692ad02c85..80c72f295bcb 100644 --- a/fs/ceph/mds_client.c +++ b/fs/ceph/mds_client.c @@ -491,11 +491,11 @@ static int parse_reply_info_readdir(void **p, void *end, struct inode *inode = d_inode(req->r_dentry); struct ceph_inode_info *ci = ceph_inode(inode); struct ceph_mds_reply_dir_entry *rde = info->dir_entries + i; - struct fscrypt_str tname = FSTR_INIT(NULL, 0); struct fscrypt_str oname = FSTR_INIT(NULL, 0); struct ceph_fname fname; u32 altname_len, _name_len; u8 *altname, *_name; + u8 *tname = NULL; /* dentry */ ceph_decode_32_safe(p, end, _name_len, bad); @@ -543,7 +543,7 @@ static int parse_reply_info_readdir(void **p, void *end, * always be shorter, which is 3/4 of origin * string. */ - tname.name = _name; + tname = _name; /* * Set oname to _name too, and this will be @@ -560,7 +560,7 @@ static int parse_reply_info_readdir(void **p, void *end, oname.len = altname_len; } rde->is_nokey = false; - err = ceph_fname_to_usr(&fname, &tname, &oname, &rde->is_nokey); + err = ceph_fname_to_usr(&fname, tname, &oname, &rde->is_nokey); if (err) { pr_err_client(cl, "unable to decode %.*s, got %d\n", _name_len, _name, err); From e939fc6a7bd969a58a150b7f188c1047138403e3 Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Fri, 29 May 2026 20:06:46 -0700 Subject: [PATCH 1218/1328] ceph: properly decrypt filenames in vmalloc() buffers The fscrypt subsystem uses the scatterlist crypto API, inheriting its requirement that any buffers are in the linear mapping region. However, the messenger client uses kvmalloc() to create buffers for messages, which will occasionally place those buffers in the vmalloc() region when physical memory fragmentation doesn't permit a large enough kmalloc(). The various callers of ceph_fname_to_usr() directly pass (slices of) raw messages from the MDS without considering that the messages may be in vmalloc() buffers, resulting in oopses especially on non-x86 platforms (see 'Closes:' for more details and a reproducer). Make ceph_fname_to_usr() explicitly tolerant of vmalloc()-allocated fname->ctext, fname->name, and/or oname->name buffers, using `tname` (which, when non-null, must be a linear address; when null, is briefly allocated as necessary) as a bounce buffer to avoid passing any inappropriate addresses to fscrypt_fname_disk_to_usr(). Additionally change parse_reply_info_readdir() -- the only function to supply its own `tname` -- to follow the new "tname must never come from vmalloc()" rule by passing NULL when the message is not in the linear region. Though this causes a per-dentry kmalloc()+kfree(), this overhead exists only when processing the minority of messages that spill into vmalloc(). My (crude) testing puts this at only about 1 in 8,000 readdir messages. Still, if the overhead proves unreasonable in the future, it is easy enough to mitigate: a future change could allocate a bounce buffer in parse_reply_info_readdir() and use that as `tname` instead. Cc: stable@vger.kernel.org # 888d33b208bd: ceph: pass fscrypt `tname` buffers directly Cc: stable@vger.kernel.org Fixes: 457117f077c6 ("ceph: add helpers for converting names for userland presentation") Closes: https://lore.kernel.org/ceph-devel/20260415034020.11530-1-CFSworks@gmail.com/ Signed-off-by: Sam Edwards Reviewed-by: Alex Markuze Signed-off-by: Ilya Dryomov --- fs/ceph/crypto.c | 43 ++++++++++++++++++++++++++++++++++--------- fs/ceph/mds_client.c | 8 ++++++-- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/fs/ceph/crypto.c b/fs/ceph/crypto.c index 7493a3acd7d0..bc0a097a4cea 100644 --- a/fs/ceph/crypto.c +++ b/fs/ceph/crypto.c @@ -298,6 +298,10 @@ int ceph_encode_encrypted_dname(struct inode *parent, char *buf, int elen) * Otherwise, base64 decode the string, and then ask fscrypt to format it * for userland presentation. * + * Though the fscrypt/crypto subsystems broadly expect all buffers to be in the + * linear-mapped region, this function slightly relaxes those requirements: + * fname->ctext, fname->name, and oname->name may be vmalloc(), but not tname. + * * Returns 0 on success or negative error code on error. */ int ceph_fname_to_usr(const struct ceph_fname *fname, unsigned char *tname, @@ -305,11 +309,15 @@ int ceph_fname_to_usr(const struct ceph_fname *fname, unsigned char *tname, { struct inode *dir = fname->dir; struct fscrypt_str _tname = FSTR_INIT(NULL, 0); + struct fscrypt_str _oname; struct fscrypt_str iname; char *name = fname->name; int name_len = fname->name_len; int ret; + if (WARN_ON_ONCE(tname && is_vmalloc_addr(tname))) + return -EIO; + /* Sanity check that the resulting name will fit in the buffer */ if (fname->name_len > NAME_MAX || fname->ctext_len > NAME_MAX) return -EIO; @@ -350,16 +358,18 @@ int ceph_fname_to_usr(const struct ceph_fname *fname, unsigned char *tname, goto out_inode; } + if (!tname && (fname->ctext_len == 0 || + unlikely(is_vmalloc_addr(fname->ctext)) || + unlikely(is_vmalloc_addr(oname->name)))) { + ret = fscrypt_fname_alloc_buffer(NAME_MAX, &_tname); + if (ret) + goto out_inode; + tname = _tname.name; + } + if (fname->ctext_len == 0) { int declen; - if (!tname) { - ret = fscrypt_fname_alloc_buffer(NAME_MAX, &_tname); - if (ret) - goto out_inode; - tname = _tname.name; - } - declen = base64_decode(name, name_len, tname, false, BASE64_IMAP); if (declen <= 0) { ret = -EIO; @@ -367,13 +377,28 @@ int ceph_fname_to_usr(const struct ceph_fname *fname, unsigned char *tname, } iname.name = tname; iname.len = declen; + } else if (unlikely(is_vmalloc_addr(fname->ctext))) { + memcpy(tname, fname->ctext, fname->ctext_len); + + iname.name = tname; + iname.len = fname->ctext_len; } else { iname.name = fname->ctext; iname.len = fname->ctext_len; } - ret = fscrypt_fname_disk_to_usr(dir, 0, 0, &iname, oname); - if (!ret && (dir != fname->dir)) { + _oname.name = unlikely(is_vmalloc_addr(oname->name)) ? tname : oname->name; + _oname.len = oname->len; + + ret = fscrypt_fname_disk_to_usr(dir, 0, 0, &iname, &_oname); + if (ret) + goto out; + + if (unlikely(is_vmalloc_addr(oname->name))) + memcpy(oname->name, _oname.name, _oname.len); + oname->len = _oname.len; + + if (dir != fname->dir) { char tmp_buf[BASE64_CHARS(NAME_MAX)]; name_len = snprintf(tmp_buf, sizeof(tmp_buf), "_%.*s_%llu", diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c index 80c72f295bcb..1cb95688c633 100644 --- a/fs/ceph/mds_client.c +++ b/fs/ceph/mds_client.c @@ -541,9 +541,13 @@ static int parse_reply_info_readdir(void **p, void *end, * to do the base64_decode in-place. It's * safe because the decoded string should * always be shorter, which is 3/4 of origin - * string. + * string. If this message was allocated with + * vmalloc() (happens, but rarely), leave it + * NULL and let ceph_fname_to_usr() allocate + * suitable temporary working space instead. */ - tname = _name; + if (likely(!is_vmalloc_addr(_name))) + tname = _name; /* * Set oname to _name too, and this will be From ad2d093781a0d9a9e49f4e102055d9e9b8f3b463 Mon Sep 17 00:00:00 2001 From: Marco Crivellari Date: Mon, 6 Jul 2026 18:04:50 +0200 Subject: [PATCH 1219/1328] ceph: Change system_unbound_wq with system_dfl_wq system_wq (per-CPU) and system_unbound_wq (unbound) are the older workqueue name, replaced by system_{percpu|dfl}_wq. The new workqueues have been introduced by: 128ea9f6ccfb ("workqueue: Add system_percpu_wq and system_dfl_wq") Usage of older workqueues will now trigger a pr_warn_once() because they are marked as deprecated as per commit: 64d8eae3f895 ("workqueue: Add warnings and fallback if system_{unbound}_wq is used") So change the used workqueue with the newer, keeping the same behavior. Suggested-by: Tejun Heo Signed-off-by: Marco Crivellari Reviewed-by: Alex Markuze Signed-off-by: Ilya Dryomov --- fs/ceph/mds_client.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c index 1cb95688c633..32d5b59ccc4f 100644 --- a/fs/ceph/mds_client.c +++ b/fs/ceph/mds_client.c @@ -5757,7 +5757,7 @@ int ceph_mdsc_schedule_reset(struct ceph_mds_client *mdsc, strscpy(st->last_reason, msg, sizeof(st->last_reason)); spin_unlock(&st->lock); - if (WARN_ON_ONCE(!queue_work(system_unbound_wq, &mdsc->reset_work))) { + if (WARN_ON_ONCE(!queue_work(system_dfl_wq, &mdsc->reset_work))) { spin_lock(&st->lock); st->phase = CEPH_CLIENT_RESET_IDLE; st->last_errno = -EALREADY; From e33752c8510076f3ef63198ee2bce15ed1ae14d9 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 6 Jul 2026 16:59:25 +0200 Subject: [PATCH 1220/1328] ceph: skip __touch_cap() most of the time __touch_cap() moves one capability to the end of the LRU list; this list is sorted by access time for just one thing: ceph_trim_caps(). That function is supposed to discard the least-recently used capabilities. __touch_cap() is called extremely often - several times for every system call, but ceph_trim_caps() is only called rarely. __touch_cap() causes considerable lock contention on `ceph_mds_session.s_cap_lock`; this is a /proc/lock_stat I captured on one of our web servers for 5 minutes: class name con-bounces contentions waittime-min waittime-max waittime-total waittime-avg acq-bounces acquisitions holdtime-min holdtime-max holdtime-total holdtime-avg &s->s_cap_lock: 336304046 341686597 0.04 4905.76 418498578.76 1.22 892783632 1957814739 0.04 959.40 355752146.24 0.18 -------------- &s->s_cap_lock 339379730 [<00000000a2197200>] __ceph_caps_issued_mask+0x1bc/0x240 &s->s_cap_lock 1268054 [<00000000c96a24b7>] ceph_add_cap+0x234/0x3e0 &s->s_cap_lock 1021360 [<00000000aa76f996>] ceph_add_cap+0x108/0x3e0 &s->s_cap_lock 16042 [<0000000099463548>] __ceph_remove_cap+0x1f4/0x270 -------------- &s->s_cap_lock 338509619 [<00000000a2197200>] __ceph_caps_issued_mask+0x1bc/0x240 &s->s_cap_lock 1937864 [<00000000c96a24b7>] ceph_add_cap+0x234/0x3e0 &s->s_cap_lock 1203451 [<00000000aa76f996>] ceph_add_cap+0x108/0x3e0 &s->s_cap_lock 202 [<00000000888f212a>] __ceph_remove_cap+0x7c/0x270 In this /proc/lock_stat output, __touch_cap() is inlined in __ceph_caps_issued_mask(). It is responsible for 99% of all contentions. Since __touch_cap() is called so often, it is acceptable to just skip most calls. The most busy capabilities will still gravitate towards the end of the linked list, and if not, it doesn't hurt as much as the lock contention. This is still good enough for ceph_trim_caps(). This patch adds a static variable that gets incremented with each call, and 255 out of 256 calls will just be skipped. I didn't bother to make the increment atomic or use READ_ONCE because I don't think that makes a practical difference for this use case. Another /proc/lock_stat for 5 minutes with this patch (__touch_cap() is no longer inlined probably because it contains a static variable): class name con-bounces contentions waittime-min waittime-max waittime-total waittime-avg acq-bounces acquisitions holdtime-min holdtime-max holdtime-total holdtime-avg &s->s_cap_lock: 1043711 1065182 0.04 502.72 737472.88 0.69 10522578 25069948 0.04 796.44 11053669.64 0.44 -------------- &s->s_cap_lock 1043074 [<00000000f4367d73>] __touch_cap.isra.0+0x50/0xa8 &s->s_cap_lock 12147 [<0000000096f45706>] ceph_add_cap+0x234/0x3e0 &s->s_cap_lock 9472 [<0000000038a23e0f>] ceph_add_cap+0x108/0x3e0 &s->s_cap_lock 471 [<00000000e2eba934>] __ceph_remove_cap+0x1f4/0x270 -------------- &s->s_cap_lock 978499 [<00000000f4367d73>] __touch_cap.isra.0+0x50/0xa8 &s->s_cap_lock 57794 [<0000000038a23e0f>] ceph_add_cap+0x108/0x3e0 &s->s_cap_lock 27226 [<0000000096f45706>] ceph_add_cap+0x234/0x3e0 &s->s_cap_lock 1581 [<00000000e2eba934>] __ceph_remove_cap+0x1f4/0x270 __touch_cap() is still responsible for 91% of all contentions, but the number of contentions has been reduced by a factor of 320 and the total wait time by a factor of 567. Signed-off-by: Max Kellermann Reviewed-by: Alex Markuze Signed-off-by: Ilya Dryomov --- fs/ceph/caps.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/fs/ceph/caps.c b/fs/ceph/caps.c index d7283fb54cec..a0660541bf8d 100644 --- a/fs/ceph/caps.c +++ b/fs/ceph/caps.c @@ -871,6 +871,14 @@ static void __touch_cap(struct ceph_cap *cap) struct inode *inode = &cap->ci->netfs.inode; struct ceph_mds_session *s = cap->session; struct ceph_client *cl = s->s_mdsc->fsc->client; + static u8 skip_counter; + + if (data_race(++skip_counter)) + /* skip this call most of the time to reduce lock + * contention; the LRU list is still accurate enough + * for ceph_trim_caps() + */ + return; spin_lock(&s->s_cap_lock); if (!s->s_cap_iterator) { From ac9d69ae4835807fc1ecdab9c85fe7eca682de08 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 9 Jul 2026 12:48:56 +0200 Subject: [PATCH 1221/1328] ceph: use detach_cap_releases() in ceph_send_cap_releases() Eliminate some redundant code. Signed-off-by: Max Kellermann Reviewed-by: Xiubo Li Signed-off-by: Ilya Dryomov --- fs/ceph/mds_client.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c index 32d5b59ccc4f..c7b47c98b777 100644 --- a/fs/ceph/mds_client.c +++ b/fs/ceph/mds_client.c @@ -1804,16 +1804,19 @@ static void __open_export_target_sessions(struct ceph_mds_client *mdsc, * session caps */ -static void detach_cap_releases(struct ceph_mds_session *session, - struct list_head *target) +static int detach_cap_releases(struct ceph_mds_session *session, + struct list_head *target) { struct ceph_client *cl = session->s_mdsc->fsc->client; + const int num_cap_releases = session->s_num_cap_releases; lockdep_assert_held(&session->s_cap_lock); list_splice_init(&session->s_cap_releases, target); session->s_num_cap_releases = 0; doutc(cl, "mds%d\n", session->s_mds); + + return num_cap_releases; } static void dispose_cap_releases(struct ceph_mds_client *mdsc, @@ -2469,9 +2472,7 @@ static void ceph_send_cap_releases(struct ceph_mds_client *mdsc, spin_lock(&session->s_cap_lock); again: - list_splice_init(&session->s_cap_releases, &tmp_list); - num_cap_releases = session->s_num_cap_releases; - session->s_num_cap_releases = 0; + num_cap_releases = detach_cap_releases(session, &tmp_list); spin_unlock(&session->s_cap_lock); while (!list_empty(&tmp_list)) { From 9be23efacbac35ebd6ff1512cb22e69c25e4861d Mon Sep 17 00:00:00 2001 From: Xiubo Li Date: Thu, 23 Jul 2026 13:47:42 +0800 Subject: [PATCH 1222/1328] ceph: use GFP_NOFS for cap flush allocation in writeback path ceph_alloc_cap_flush() is called from ceph_writepages_start() inside the writeback layer, where other allocations in the same path (ceph_osdc_alloc_request, ceph_osdc_alloc_messages) already use GFP_NOFS. A GFP_KERNEL allocation here can trigger direct reclaim that recursively enters the filesystem writeback path: ceph_writepages_start() // inode A writeback ceph_alloc_cap_flush() kmem_cache_alloc(..., GFP_KERNEL) [direct reclaim] try_to_free_pages() shrink_slab() super_cache_scan() prune_icache_sb() inode_lru_isolate() iput() -> evict(inode_B) [inode_B has dirty pages] filemap_flush() ceph_writepages_start() // re-enters writeback ceph_alloc_cap_flush() -> RECURSION / STACK OVERFLOW All 11 callers of ceph_alloc_cap_flush() are in write or writeback contexts: writepages (x2), write_iter, fallocate, copy_file_range, setxattr, setattr, and page_mkwrite. Signed-off-by: Xiubo Li Reviewed-by: Viacheslav Dubeyko Reviewed-by: Alex Markuze Signed-off-by: Ilya Dryomov --- fs/ceph/caps.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/ceph/caps.c b/fs/ceph/caps.c index a0660541bf8d..1e6ffe23fd08 100644 --- a/fs/ceph/caps.c +++ b/fs/ceph/caps.c @@ -1850,7 +1850,7 @@ struct ceph_cap_flush *ceph_alloc_cap_flush(void) { struct ceph_cap_flush *cf; - cf = kmem_cache_alloc(ceph_cap_flush_cachep, GFP_KERNEL); + cf = kmem_cache_alloc(ceph_cap_flush_cachep, GFP_NOFS); if (!cf) return NULL; From 699411a3534139e0edde1e7fc190c3eea0786ffc Mon Sep 17 00:00:00 2001 From: Xiubo Li Date: Wed, 12 Aug 2026 00:36:26 -0700 Subject: [PATCH 1223/1328] ceph: use GFP_KERNEL consistently in __ceph_pool_perm_get() __ceph_pool_perm_get() has six allocations for building OSD STAT requests, five of which used GFP_NOFS and one (the page vector allocation) used GFP_KERNEL, making them inconsistent. The function is only called from ceph_try_get_caps() and __ceph_get_caps(), both of which are in the user I/O path (read, write, fallocate, mmap fault), not in the writeback path. There is no risk of recursive writeback, so GFP_NOFS is unnecessarily restrictive. Use GFP_KERNEL consistently for all six allocations. Signed-off-by: Xiubo Li Reviewed-by: Alex Markuze Signed-off-by: Ilya Dryomov --- fs/ceph/addr.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/fs/ceph/addr.c b/fs/ceph/addr.c index ecf33b66610c..702e5f8ab565 100644 --- a/fs/ceph/addr.c +++ b/fs/ceph/addr.c @@ -2464,7 +2464,7 @@ static int __ceph_pool_perm_get(struct ceph_inode_info *ci, } rd_req = ceph_osdc_alloc_request(&fsc->client->osdc, NULL, - 1, false, GFP_NOFS); + 1, false, GFP_KERNEL); if (!rd_req) { err = -ENOMEM; goto out_unlock; @@ -2477,12 +2477,12 @@ static int __ceph_pool_perm_get(struct ceph_inode_info *ci, rd_req->r_base_oloc.pool_ns = ceph_get_string(pool_ns); ceph_oid_printf(&rd_req->r_base_oid, "%llx.00000000", ci->i_vino.ino); - err = ceph_osdc_alloc_messages(rd_req, GFP_NOFS); + err = ceph_osdc_alloc_messages(rd_req, GFP_KERNEL); if (err) goto out_unlock; wr_req = ceph_osdc_alloc_request(&fsc->client->osdc, NULL, - 1, false, GFP_NOFS); + 1, false, GFP_KERNEL); if (!wr_req) { err = -ENOMEM; goto out_unlock; @@ -2493,7 +2493,7 @@ static int __ceph_pool_perm_get(struct ceph_inode_info *ci, ceph_oloc_copy(&wr_req->r_base_oloc, &rd_req->r_base_oloc); ceph_oid_copy(&wr_req->r_base_oid, &rd_req->r_base_oid); - err = ceph_osdc_alloc_messages(wr_req, GFP_NOFS); + err = ceph_osdc_alloc_messages(wr_req, GFP_KERNEL); if (err) goto out_unlock; @@ -2532,7 +2532,7 @@ static int __ceph_pool_perm_get(struct ceph_inode_info *ci, } pool_ns_len = pool_ns ? pool_ns->len : 0; - perm = kmalloc_flex(*perm, pool_ns, pool_ns_len + 1, GFP_NOFS); + perm = kmalloc_flex(*perm, pool_ns, pool_ns_len + 1, GFP_KERNEL); if (!perm) { err = -ENOMEM; goto out_unlock; From af59562a5b3d34fb3aa7753f2543393578d06dbf Mon Sep 17 00:00:00 2001 From: Xiubo Li Date: Thu, 23 Jul 2026 14:28:19 +0800 Subject: [PATCH 1224/1328] ceph: do not cache negative dentries for snapped directories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a LOOKUP/LOOKUPSNAP in a snapped directory returns ENOENT without a trace, ceph_finish_lookup() creates a negative dentry via d_add(dentry, NULL). For live directories this is fine — the dentry naturally expires. But for snapped directories, ceph_d_revalidate() unconditionally trusts all cached dentries (valid = 1), so a negative dentry created by a transient error persists forever, hiding entries that genuinely exist in the snapshot. Only cache negative dentries for live (non-snapshotted) parent directories. For snapped parents, skip the negative dentry so that VFS retries the lookup on the next access. Since the conditions that trigger a negative dentry (MDS transient error, local ENOENT shortcut, or MDS null dentry lease) are all rare in snapped directories, the performance impact of this change is negligible. Link: https://tracker.ceph.com/issues/78529 Reported-by: Andras Pataki Signed-off-by: Xiubo Li Reviewed-by: Viacheslav Dubeyko Signed-off-by: Ilya Dryomov --- fs/ceph/dir.c | 10 ++++++++-- fs/ceph/inode.c | 3 ++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c index b4b541a1180c..d5d8f935fb62 100644 --- a/fs/ceph/dir.c +++ b/fs/ceph/dir.c @@ -774,8 +774,13 @@ struct dentry *ceph_finish_lookup(struct ceph_mds_request *req, d_drop(dentry); err = -ENOENT; } else { - if (d_unhashed(dentry)) - d_add(dentry, NULL); + if (d_unhashed(dentry)) { + struct inode *parent = + d_inode(dentry->d_parent); + if (!parent || + ceph_snap(parent) == CEPH_NOSNAP) + d_add(dentry, NULL); + } } } } @@ -840,6 +845,7 @@ static struct dentry *ceph_lookup(struct inode *dir, struct dentry *dentry, dentry->d_name.len) && !is_root_ceph_dentry(dir, dentry) && ceph_test_mount_opt(fsc, DCACHE) && + ceph_snap(dir) == CEPH_NOSNAP && __ceph_dir_is_complete(ci) && __ceph_caps_issued_mask_metric(ci, CEPH_CAP_FILE_SHARED, 1)) { __ceph_touch_fmode(ci, mdsc, CEPH_FILE_MODE_RD); diff --git a/fs/ceph/inode.c b/fs/ceph/inode.c index 61d7c0b8161f..d52e2b389e0b 100644 --- a/fs/ceph/inode.c +++ b/fs/ceph/inode.c @@ -1814,7 +1814,8 @@ int ceph_fill_trace(struct super_block *sb, struct ceph_mds_request *req) ceph_dir_clear_ordered(dir); d_delete(dn); } else if (have_lease) { - if (d_unhashed(dn)) + if (d_unhashed(dn) && + ceph_snap(dir) == CEPH_NOSNAP) d_add(dn, NULL); } From a354d7eaa1a57f1532c8072a424cc2d339a73cc0 Mon Sep 17 00:00:00 2001 From: Xiubo Li Date: Tue, 14 Jul 2026 14:20:37 +0800 Subject: [PATCH 1225/1328] ceph: fix use-after-dereference of NULL ci in __ceph_remove_cap() The NULL check for "ci" in __ceph_remove_cap() was dead code because ci was dereferenced via &ci->netfs.inode before the check, and cap->session was dereferenced via session->s_mdsc->fsc->client even earlier. On a double-remove, both cap->ci and cap->session are set to NULL by the first call, so the second call would crash before ever reaching the guard. Move ci, session, cl, and inode initializations after the NULL check so that the early-return actually works. Signed-off-by: Xiubo Li Reviewed-by: Viacheslav Dubeyko Signed-off-by: Ilya Dryomov --- fs/ceph/caps.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/fs/ceph/caps.c b/fs/ceph/caps.c index 1e6ffe23fd08..f3110e8d19a8 100644 --- a/fs/ceph/caps.c +++ b/fs/ceph/caps.c @@ -1129,18 +1129,21 @@ int ceph_is_any_caps(struct inode *inode) */ void __ceph_remove_cap(struct ceph_cap *cap, bool queue_release) { - struct ceph_mds_session *session = cap->session; - struct ceph_client *cl = session->s_mdsc->fsc->client; - struct ceph_inode_info *ci = cap->ci; - struct inode *inode = &ci->netfs.inode; + struct ceph_mds_session *session; + struct ceph_client *cl; + struct ceph_inode_info *ci; + struct inode *inode; struct ceph_mds_client *mdsc; int removed = 0; /* 'ci' being NULL means the remove have already occurred */ - if (!ci) { - doutc(cl, "inode is NULL\n"); + ci = cap->ci; + if (!ci) return; - } + + session = cap->session; + cl = session->s_mdsc->fsc->client; + inode = &ci->netfs.inode; lockdep_assert_held(&ci->i_ceph_lock); From d2a8d446a09c74c8ddfe108b50dd791c889983fc Mon Sep 17 00:00:00 2001 From: Xiubo Li Date: Tue, 21 Jul 2026 13:06:53 +0800 Subject: [PATCH 1226/1328] ceph: revalidate ki_pos for O_APPEND writes after cap acquisition For O_APPEND writes, ki_pos is set to the current EOF via generic_write_checks() after fetching i_size from the MDS. However, ceph_get_caps() may need to wait for Fwx exclusive caps if the write extends the file (endoff > i_max_size). While waiting for Fwx, the previous Fwx holder (another client) may have already extended the file. When the MDS grants us Fwx, the cap grant message updates the local i_size, but ki_pos remains at the old EOF, causing the append write to land at a stale offset and overwrite data from the other client. Fix by re-reading i_size_read(inode) after ceph_get_caps() returns. At this point we hold Fwx exclusive caps, no other client can modify the file, and i_size reflects the true EOF from the MDS cap grant. No extra MDS round-trip is needed. Only adjust ki_pos when the EOF has actually changed. After adjusting ki_pos forward, the write range [pos, pos+count) may now exceed the i_max_size that was validated by ceph_get_caps() for the old range. Re-check against i_max_size and truncate the write if necessary to stay within the MDS-granted limit. Link: https://tracker.ceph.com/issues/7333 Fixes: 8e4473bb50a1 ("ceph: do not execute direct write in parallel if O_APPEND is specified") Signed-off-by: Xiubo Li Reviewed-by: Viacheslav Dubeyko Signed-off-by: Ilya Dryomov --- fs/ceph/file.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/fs/ceph/file.c b/fs/ceph/file.c index a4a2a4b6a027..a0b9c2b5a583 100644 --- a/fs/ceph/file.c +++ b/fs/ceph/file.c @@ -2477,6 +2477,54 @@ static ssize_t ceph_write_iter(struct kiocb *iocb, struct iov_iter *from) if (err < 0) goto out; + /* + * For O_APPEND writes we may have waited for Fwx exclusive caps + * while the previous Fwx holder (another client) extended the + * file. i_size has been updated via the cap grant message from + * the MDS, but ki_pos is still the old EOF. Re-read i_size here + * (no extra MDS round-trip needed) and adjust ki_pos to the true + * EOF. Since we hold Fwx, no other client can change the file. + */ + if (iocb->ki_flags & IOCB_APPEND) { + loff_t cur_eof = i_size_read(inode); + + if (cur_eof != pos) { + doutc(cl, + "%p %llx.%llx O_APPEND: pos adjusted %lld -> %lld\n", + inode, ceph_vinop(inode), pos, cur_eof); + iocb->ki_pos = cur_eof; + pos = cur_eof; + if (pos >= limit) { + err = -EFBIG; + goto out_caps; + } + iov_iter_truncate(from, limit - pos); + count = iov_iter_count(from); + + /* + * ceph_get_caps() validated the old endoff + * against i_max_size; adjusting ki_pos forward + * may have shifted the write range beyond the + * granted max_size. Re-check and truncate if + * necessary. + */ + spin_lock(&ci->i_ceph_lock); + if (pos + count > (loff_t)ci->i_max_size) { + loff_t max_size = ci->i_max_size; + + spin_unlock(&ci->i_ceph_lock); + if (pos >= max_size) { + err = -EFBIG; + goto out_caps; + } + iov_iter_truncate(from, max_size - pos); + count = iov_iter_count(from); + } else { + spin_unlock(&ci->i_ceph_lock); + } + } + } + err = file_update_time(file); if (err) goto out_caps; From 9ec08b7499a62c6d4afa93d36ab47a43fcad57d1 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Tue, 14 Jul 2026 07:51:39 -0400 Subject: [PATCH 1227/1328] libceph: validate OSD extent maps before cursor advance net/ceph/osd_client.c:osd_sparse_read() validates that the sparse-read data length matches the summed extent lengths, but it does not validate that each OSD-supplied extent is monotonic and lies inside the original request range. A malformed authenticated OSD reply can advertise a far-forward nonzero extent offset with a matching data length and make the client advance the message-data cursor beyond the request buffer. This reaches the BUG_ON(!*length) assertion in ceph_msg_data_next() from the client receive path. Impact: A malicious or compromised authenticated Ceph OSD peer can crash a kernel Ceph client via a malformed sparse-read reply. Reject sparse extent maps that overflow, move backwards, overlap, or extend outside the original sparse-read request before advancing the cursor. [ idryomov: perform sparse_extent_map_valid() check a bit earlier, in CEPH_SPARSE_READ_DATA_LEN instead of CEPH_SPARSE_READ_DATA_PRE state ] Cc: stable@vger.kernel.org Fixes: f628d7999727 ("libceph: add sparse read support to OSD client") Assisted-by: Codex:gpt-5-5-xhigh Signed-off-by: Michael Bommarito Reviewed-by: Viacheslav Dubeyko Signed-off-by: Ilya Dryomov --- net/ceph/osd_client.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/net/ceph/osd_client.c b/net/ceph/osd_client.c index 28d76c2f6b3e..f36ce5ae7568 100644 --- a/net/ceph/osd_client.c +++ b/net/ceph/osd_client.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -5802,6 +5803,31 @@ static inline void convert_extent_map(struct ceph_sparse_read *sr) } #endif +static bool sparse_extent_map_valid(struct ceph_sparse_read *sr) +{ + u64 req_end, pos; + int i; + + if (check_add_overflow(sr->sr_req_off, sr->sr_req_len, &req_end)) + return false; + + pos = sr->sr_req_off; + for (i = 0; i < sr->sr_count; i++) { + struct ceph_sparse_extent *ext = &sr->sr_extent[i]; + u64 end; + + if (ext->off < pos) + return false; + if (check_add_overflow(ext->off, ext->len, &end)) + return false; + if (end > req_end) + return false; + pos = end; + } + + return true; +} + static int osd_sparse_read(struct ceph_connection *con, struct ceph_msg_data_cursor *cursor, char **pbuf) @@ -5852,6 +5878,10 @@ static int osd_sparse_read(struct ceph_connection *con, fallthrough; case CEPH_SPARSE_READ_DATA_LEN: convert_extent_map(sr); + if (!sparse_extent_map_valid(sr)) { + pr_warn_ratelimited("invalid sparse extent map\n"); + return -EREMOTEIO; + } ret = sizeof(sr->sr_datalen); *pbuf = (char *)&sr->sr_datalen; sr->sr_state = CEPH_SPARSE_READ_DATA_PRE; From eff8013c5a8916613c742ae5a2cc341cb605c0ae Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Sat, 11 Jul 2026 11:07:05 -0400 Subject: [PATCH 1228/1328] ceph: bound copied dentry name length in NFS export get_name ceph_get_name() copies the MDS-supplied name into the caller's NAME_MAX-sized buffer with memcpy(name, rinfo->dname, rinfo->dname_len) and then writes name[rinfo->dname_len] = 0, without checking dname_len against NAME_MAX. A malicious or buggy MDS that returns a LOOKUPNAME reply with dname_len > NAME_MAX overflows the buffer. __get_snap_name() copies rde->name / rde->name_len the same unchecked way. Impact: a malicious or compromised Ceph MDS overflows the NAME_MAX name buffer in a client's NFS-export get_name path, a slab out-of-bounds write reported by KASAN. Reachable when a CephFS mount is re-exported over NFS. Add ceph_export_copy_name(), which rejects lengths above NAME_MAX with -ENAMETOOLONG before the copy, and use it in both ceph_get_name() and __get_snap_name(). Cc: stable@vger.kernel.org Fixes: 19913b4eac4a ("ceph: add get_name() NFS export callback") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Michael Bommarito Reviewed-by: Viacheslav Dubeyko Signed-off-by: Ilya Dryomov --- fs/ceph/export.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/fs/ceph/export.c b/fs/ceph/export.c index b2f2af104679..debb9634b9e3 100644 --- a/fs/ceph/export.c +++ b/fs/ceph/export.c @@ -442,6 +442,16 @@ static struct dentry *ceph_fh_to_parent(struct super_block *sb, return dentry; } +static int ceph_export_copy_name(char *name, const char *src, u32 len) +{ + if (len > NAME_MAX) + return -ENAMETOOLONG; + + memcpy(name, src, len); + name[len] = '\0'; + return 0; +} + static int __get_snap_name(struct dentry *parent, char *name, struct dentry *child) { @@ -513,9 +523,8 @@ static int __get_snap_name(struct dentry *parent, char *name, BUG_ON(!rde->inode.in); if (ceph_snap(inode) == le64_to_cpu(rde->inode.in->snapid)) { - memcpy(name, rde->name, rde->name_len); - name[rde->name_len] = '\0'; - err = 0; + err = ceph_export_copy_name(name, rde->name, + rde->name_len); goto out; } } @@ -580,8 +589,8 @@ static int ceph_get_name(struct dentry *parent, char *name, rinfo = &req->r_reply_info; if (!IS_ENCRYPTED(dir)) { - memcpy(name, rinfo->dname, rinfo->dname_len); - name[rinfo->dname_len] = 0; + err = ceph_export_copy_name(name, rinfo->dname, + rinfo->dname_len); } else { struct fscrypt_str oname = FSTR_INIT(NULL, 0); struct ceph_fname fname = { .dir = dir, @@ -595,10 +604,9 @@ static int ceph_get_name(struct dentry *parent, char *name, goto out; err = ceph_fname_to_usr(&fname, NULL, &oname, NULL); - if (!err) { - memcpy(name, oname.name, oname.len); - name[oname.len] = 0; - } + if (!err) + err = ceph_export_copy_name(name, oname.name, + oname.len); ceph_fname_free_buffer(dir, &oname); } out: From 68d541754d6cd3bb98d1fd8314f57e5eb533557d Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Tue, 7 Jul 2026 14:05:57 -0400 Subject: [PATCH 1229/1328] ceph: bound xattr value length in __build_xattrs() __build_xattrs() decodes the MDS-supplied xattr blob one attribute at a time. For each attribute it reads a 32-bit name length, advances past the name bytes, reads a 32-bit value length, records the value pointer, and advances past the value bytes. The two length fields are read with ceph_decode_32_safe(), but the value bytes themselves are advanced over with a bare "p += len" and no ceph_decode_need() check that "len" bytes remain in the blob. For every attribute except the last, the next iteration's ceph_decode_32_safe() on the following name length implicitly verifies that the previous value did not run past the blob end. The final attribute has no successor, so its decoded value length is never checked against the blob bounds. A malicious or compromised metadata server can set the last attribute's value length larger than the bytes actually present in the blob. The blob is a dedicated kvmalloc() allocation sized to the wire length (ceph_buffer_new() in ceph_fill_inode()). __set_xattr() records the oversized length in xattr->val_len verbatim, and a later getxattr(2) runs memcpy(value, xattr->val, xattr->val_len) into a user-supplied buffer, copying bytes past the end of the allocation back to user space. Impact: a malicious metadata server discloses adjacent kernel heap bytes to a local user via getxattr(2) on a CephFS file. Add the missing ceph_decode_need() so an out-of-bounds value length on the final attribute fails the decode and returns -EIO instead of being stored. Cc: stable@vger.kernel.org Fixes: 355da1eb7a1f ("ceph: inode operations") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Michael Bommarito Reviewed-by: Viacheslav Dubeyko Signed-off-by: Ilya Dryomov --- fs/ceph/xattr.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/ceph/xattr.c b/fs/ceph/xattr.c index 860fc8e1867d..cc4ffbbcb719 100644 --- a/fs/ceph/xattr.c +++ b/fs/ceph/xattr.c @@ -848,6 +848,7 @@ static int __build_xattrs(struct inode *inode) name = p; p += len; ceph_decode_32_safe(&p, end, len, bad); + ceph_decode_need(&p, end, len, bad); val = p; p += len; From 77933e22adfe813be2bd10be08d6e950103c3967 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Tue, 7 Jul 2026 14:05:58 -0400 Subject: [PATCH 1230/1328] ceph: bound MDSCapAuth path and fs_name decode in handle_session() handle_session() decodes the MDSCapAuth records carried by a CEPH_SESSION_OPEN message (msg_version >= 6). For each record the match.path and match.fs_name byte strings are read by first decoding a 32-bit length and then copying that many bytes with the bare ceph_decode_copy(). Unlike the surrounding fields, which all use the _safe decode variants, these two copies are not preceded by a ceph_decode_need() bounds check, and the enclosing MDSCapAuth and MDSCapMatch struct_len fields are skipped rather than enforced as an upper bound. A length larger than the bytes remaining in the message front makes ceph_decode_copy() read past the end of the front buffer. The message front is a dedicated allocation (ceph_msg_new2() -> kvmalloc), so the over-read runs off that object. A malicious or compromised MDS can trigger this with the first post-connect message on mount, with no client-side user interaction; under KASAN it is reported as a slab-out-of-bounds read in handle_session(). Impact: a malicious MDS can force the kernel client to read up to 4 GiB past the message front allocation during session setup, crashing the client (out-of-bounds read). Switch both copies to ceph_decode_copy_safe(), which performs the ceph_decode_need() bounds check before the copy and branches to the existing bad label, matching the rest of the decoder and the error path that frees the partially decoded cap_auths array. Cc: stable@vger.kernel.org Fixes: 1d17de9534cb ("ceph: save cap_auths in MDS client when session is opened") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Michael Bommarito Reviewed-by: Viacheslav Dubeyko Signed-off-by: Ilya Dryomov --- fs/ceph/mds_client.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c index c7b47c98b777..313281197ff4 100644 --- a/fs/ceph/mds_client.c +++ b/fs/ceph/mds_client.c @@ -4446,7 +4446,9 @@ static void handle_session(struct ceph_mds_session *session, pr_err_client(cl, "No memory for path\n"); goto fail; } - ceph_decode_copy(&p, cap_auths[i].match.path, _len); + ceph_decode_copy_safe(&p, end, + cap_auths[i].match.path, + _len, bad); /* Remove the tailing '/' */ while (_len && cap_auths[i].match.path[_len - 1] == '/') { @@ -4463,7 +4465,9 @@ static void handle_session(struct ceph_mds_session *session, pr_err_client(cl, "No memory for fs_name\n"); goto fail; } - ceph_decode_copy(&p, cap_auths[i].match.fs_name, _len); + ceph_decode_copy_safe(&p, end, + cap_auths[i].match.fs_name, + _len, bad); } ceph_decode_8_safe(&p, end, cap_auths[i].match.root_squash, bad); From a3eb169ee297aa99670ba927c659990bd1e453f3 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Tue, 7 Jul 2026 14:05:59 -0400 Subject: [PATCH 1231/1328] ceph: bound num_export_targets array for mds info v2/v3 ceph_mdsmap_decode() in fs/ceph/mdsmap.c reads num_export_targets from each per-mds info record and advances the decode cursor by num_export_targets * sizeof(u32) without first checking that many bytes remain. The only upper-bound check that catches a runaway cursor (*p > info_end) is gated on info_v >= 4, because info_end is left NULL for info_v 2 and 3. When the monitor sends an MDS map whose per-mds info version is 2 or 3 with an oversized num_export_targets, the cursor moves past the message front buffer and the later export-targets loop calls the unchecked ceph_decode_32() on out-of-bounds memory. A kernel client processes CEPH_MSG_MDS_MAP from its monitor session (net/ceph/mon_client.c dispatches it; fs/ceph/super.c routes it to ceph_mdsc_handle_mdsmap(), which sets end to the front buffer bound and calls ceph_mdsmap_decode()). A malicious or compromised monitor, or an on-path attacker on an unsigned/unencrypted messenger session, can therefore drive an out-of-bounds read in the client kernel; on x86_64 with KASAN it is reported as a slab-out-of-bounds read in ceph_mdsmap_decode(). The decoded values land in the internal info->export_targets[] array, so the consequence is a kernel out-of-bounds read, not an information leak to the attacker. Impact: a malicious or compromised Ceph monitor sending an MDS map with a per-mds info version of 2 or 3 and an oversized num_export_targets field triggers an out-of-bounds read in the CephFS client kernel. Add a ceph_decode_need() for the export-targets array before advancing the cursor, so the bound is enforced for every info_v >= 2, not only info_v >= 4. This mirrors the count-then-need idiom already used for m_data_pg_pools later in the same function. Compute the export-targets byte count with size_mul() and reuse that checked length when advancing the cursor, so the attacker-controlled num_export_targets multiplication fails closed on overflow rather than relying on the later kcalloc() guard. Cc: stable@vger.kernel.org Fixes: d463a43d69f4 ("ceph: CEPH_FEATURE_MDSENC support") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Michael Bommarito Reviewed-by: Viacheslav Dubeyko Signed-off-by: Ilya Dryomov --- fs/ceph/mdsmap.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/fs/ceph/mdsmap.c b/fs/ceph/mdsmap.c index 450a4dc9662e..4f0626753429 100644 --- a/fs/ceph/mdsmap.c +++ b/fs/ceph/mdsmap.c @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -126,6 +127,7 @@ struct ceph_mdsmap *ceph_mdsmap_decode(struct ceph_mds_client *mdsc, void **p, u8 mdsmap_v; u16 mdsmap_ev; u32 target; + size_t export_targets_len; m = kzalloc_obj(*m, GFP_NOFS); if (!m) @@ -224,8 +226,11 @@ struct ceph_mdsmap *ceph_mdsmap_decode(struct ceph_mds_client *mdsc, void **p, *p += namelen; if (info_v >= 2) { ceph_decode_32_safe(p, end, num_export_targets, bad); + export_targets_len = size_mul(num_export_targets, + sizeof(u32)); + ceph_decode_need(p, end, export_targets_len, bad); pexport_targets = *p; - *p += num_export_targets * sizeof(u32); + *p += export_targets_len; } else { num_export_targets = 0; } From 4bd3158bd62466d57ed72a3f7bc5f205fedd6919 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Tue, 7 Jul 2026 14:06:00 -0400 Subject: [PATCH 1232/1328] ceph: cap delegated inode count in ceph_parse_deleg_inos() ceph_parse_deleg_inos() decodes interval sets of delegated inode numbers from an MDS create-with-delegation reply. For each set it reads a 64-bit start and a 64-bit len with ceph_decode_64_safe(), which only validates that the eight bytes are present in the message, not the value, and then loops over len while inserting entries into s_delegated_inos. len is fully attacker controlled. A malicious or compromised MDS can send one huge interval, many intervals in one reply, duplicate intervals, or repeated replies that accumulate delegated inodes on the same session. The original code bounded none of these and could spin the insert loop or grow the xarray without limit. Bound both dimensions with a single enforcement point. Track the number of delegated inodes held by each MDS session in an atomic counter and grow it only in ceph_insert_deleg_ino(), which uses atomic_add_unless() to refuse to push the count past CEPH_MAX_DELEG_INOS. Because that helper is the only place the counter grows, the per-session population can never exceed the cap, so no separate per-session pre-check is needed. The counter is decremented when async create consumes a delegated inode or when an insert fails, incremented when a delegated inode is restored, initialized with the session xarray, and reset when reconnect destroys the xarray. A per-session cap alone still lets one reply spin the insert loop on duplicate ranges without growing the counter, so also cap the aggregate interval length accepted from a single reply. Together these bound both the loop trip count per reply and the xarray population across replies. The cap is a fixed, client-chosen constant rather than a value derived from the MDS. mds_client_prealloc_inos is a userspace MDS configuration option; it is never sent to the kernel client on the wire, and a server-supplied bound could not be trusted for a defensive limit in any case. The constant is set well above that option's documented default of 1000 (a generous multiple), so legitimate refill behavior is unaffected while the CPU and xarray memory a malformed delegation stream can consume stays bounded. Impact: a malicious or compromised Ceph MDS can no longer make a client spin through an unbounded delegated-inode interval or grow one session's delegated-inode xarray without limit. Cc: stable@vger.kernel.org Fixes: d48464878708 ("ceph: decode interval_sets for delegated inos") Suggested-by: Viacheslav Dubeyko Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Michael Bommarito Reviewed-by: Viacheslav Dubeyko Signed-off-by: Ilya Dryomov --- fs/ceph/mds_client.c | 59 +++++++++++++++++++++++++++++++++++++++----- fs/ceph/mds_client.h | 1 + fs/ceph/super.h | 9 +++++++ 3 files changed, 63 insertions(+), 6 deletions(-) diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c index 313281197ff4..9925e7e355e8 100644 --- a/fs/ceph/mds_client.c +++ b/fs/ceph/mds_client.c @@ -619,10 +619,36 @@ static int parse_reply_info_filelock(void **p, void *end, #define DELEGATED_INO_AVAILABLE xa_mk_value(1) +static int ceph_insert_deleg_ino(struct ceph_mds_session *s, u64 ino) +{ + struct ceph_client *cl = s->s_mdsc->fsc->client; + int err; + + /* + * Cap how many delegated inodes a single session may hold. This is + * the only place that grows the count, so atomic_add_unless() bounds + * it at exactly CEPH_MAX_DELEG_INOS; s_num_deleg_inos can never exceed + * that. + */ + if (!atomic_add_unless(&s->s_num_deleg_inos, 1, CEPH_MAX_DELEG_INOS)) { + pr_warn_ratelimited_client(cl, + "MDS session already holds %d delegated inodes\n", + CEPH_MAX_DELEG_INOS); + return -EOVERFLOW; + } + + err = xa_insert(&s->s_delegated_inos, ino, DELEGATED_INO_AVAILABLE, + GFP_KERNEL); + if (err) + atomic_dec(&s->s_num_deleg_inos); + return err; +} + static int ceph_parse_deleg_inos(void **p, void *end, struct ceph_mds_session *s) { struct ceph_client *cl = s->s_mdsc->fsc->client; + u64 msg_deleg_inos = 0; u32 sets; ceph_decode_32_safe(p, end, sets, bad); @@ -640,16 +666,34 @@ static int ceph_parse_deleg_inos(void **p, void *end, start, len); continue; } + + /* + * Bound the number of inodes one reply may delegate. + * ceph_insert_deleg_ino() separately caps the per-session + * population, so this only has to stop one reply from spinning + * the insert loop under an attacker-controlled len. + */ + if (len > (u64)CEPH_MAX_DELEG_INOS || + msg_deleg_inos > (u64)CEPH_MAX_DELEG_INOS - len) { + pr_warn_ratelimited_client(cl, + "MDS reply delegates too many inodes (have %llu, +%llu, max %d)\n", + msg_deleg_inos, len, CEPH_MAX_DELEG_INOS); + return -EIO; + } + msg_deleg_inos += len; + while (len--) { - int err = xa_insert(&s->s_delegated_inos, start++, - DELEGATED_INO_AVAILABLE, - GFP_KERNEL); + int err = ceph_insert_deleg_ino(s, start++); + if (!err) { doutc(cl, "added delegated inode 0x%llx\n", start - 1); } else if (err == -EBUSY) { pr_warn_client(cl, "MDS delegated inode 0x%llx more than once.\n", start - 1); + } else if (err == -EOVERFLOW) { + /* ceph_insert_deleg_ino() already warned. */ + return -EIO; } else { return err; } @@ -667,16 +711,17 @@ u64 ceph_get_deleg_ino(struct ceph_mds_session *s) xa_for_each(&s->s_delegated_inos, ino, val) { val = xa_erase(&s->s_delegated_inos, ino); - if (val == DELEGATED_INO_AVAILABLE) + if (val == DELEGATED_INO_AVAILABLE) { + atomic_dec(&s->s_num_deleg_inos); return ino; + } } return 0; } int ceph_restore_deleg_ino(struct ceph_mds_session *s, u64 ino) { - return xa_insert(&s->s_delegated_inos, ino, DELEGATED_INO_AVAILABLE, - GFP_KERNEL); + return ceph_insert_deleg_ino(s, ino); } #else /* BITS_PER_LONG == 64 */ /* @@ -1063,6 +1108,7 @@ static struct ceph_mds_session *register_session(struct ceph_mds_client *mdsc, INIT_LIST_HEAD(&s->s_waiting); INIT_LIST_HEAD(&s->s_unsafe); xa_init(&s->s_delegated_inos); + atomic_set(&s->s_num_deleg_inos, 0); INIT_LIST_HEAD(&s->s_cap_releases); INIT_WORK(&s->s_cap_release_work, ceph_cap_release_work); @@ -5115,6 +5161,7 @@ static int send_mds_reconnect(struct ceph_mds_client *mdsc, /* Serialized by s_mutex against concurrent ceph_get_deleg_ino(). */ xa_destroy(&session->s_delegated_inos); + atomic_set(&session->s_num_deleg_inos, 0); if (session->s_state == CEPH_MDS_SESSION_CLOSED || session->s_state == CEPH_MDS_SESSION_REJECTED) { pr_info_client(cl, "mds%d skipping reconnect, session %s\n", diff --git a/fs/ceph/mds_client.h b/fs/ceph/mds_client.h index 0ece4c9e3529..3c62e3c3530b 100644 --- a/fs/ceph/mds_client.h +++ b/fs/ceph/mds_client.h @@ -300,6 +300,7 @@ struct ceph_mds_session { struct list_head s_waiting; /* waiting requests */ struct list_head s_unsafe; /* unsafe requests */ struct xarray s_delegated_inos; + atomic_t s_num_deleg_inos; }; /* diff --git a/fs/ceph/super.h b/fs/ceph/super.h index 1d6aab060780..6ce0b771c66b 100644 --- a/fs/ceph/super.h +++ b/fs/ceph/super.h @@ -641,6 +641,15 @@ static inline int ceph_ino_compare(struct inode *inode, void *data) #define CEPH_MDS_INO_LOG_OFFSET (2 * CEPH_MAX_MDS) #define CEPH_INO_SYSTEM_BASE ((6*CEPH_MAX_MDS) + (CEPH_MAX_MDS * CEPH_NUM_STRAY)) +/* + * Upper bound on the number of delegated inodes a single MDS session may + * hold. The MDS normally hands out a small preallocation window (the + * userspace mds_client_prealloc_inos option defaults to 1000) and refills + * it as the client consumes entries. This leaves generous headroom while + * bounding the CPU and memory a malformed delegation interval can consume. + */ +#define CEPH_MAX_DELEG_INOS 8192 + static inline bool ceph_vino_is_reserved(const struct ceph_vino vino) { if (vino.ino >= CEPH_INO_SYSTEM_BASE || From 6cd69ea0f04c481b7b104c32824546e7df1806a5 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 6 Jul 2026 09:38:09 +0200 Subject: [PATCH 1233/1328] ceph: make __ceph_remove_cap() static It's only used from within caps.c. Signed-off-by: Max Kellermann Reviewed-by: Alex Markuze Signed-off-by: Ilya Dryomov --- fs/ceph/caps.c | 2 +- fs/ceph/super.h | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/fs/ceph/caps.c b/fs/ceph/caps.c index f3110e8d19a8..78ed3fcf4e46 100644 --- a/fs/ceph/caps.c +++ b/fs/ceph/caps.c @@ -1127,7 +1127,7 @@ int ceph_is_any_caps(struct inode *inode) * caller should hold i_ceph_lock. * caller will not hold session s_mutex if called from destroy_inode. */ -void __ceph_remove_cap(struct ceph_cap *cap, bool queue_release) +static void __ceph_remove_cap(struct ceph_cap *cap, bool queue_release) { struct ceph_mds_session *session; struct ceph_client *cl; diff --git a/fs/ceph/super.h b/fs/ceph/super.h index 6ce0b771c66b..3d7f91bc29b2 100644 --- a/fs/ceph/super.h +++ b/fs/ceph/super.h @@ -1278,7 +1278,6 @@ extern void ceph_add_cap(struct inode *inode, unsigned issued, unsigned wanted, unsigned cap, unsigned seq, u64 realmino, int flags, struct ceph_cap **new_cap); -extern void __ceph_remove_cap(struct ceph_cap *cap, bool queue_release); extern void ceph_remove_cap(struct ceph_mds_client *mdsc, struct ceph_cap *cap, bool queue_release); extern void __ceph_remove_caps(struct ceph_inode_info *ci); From 8619a36ff55ac8723bc449332460368f9a090a77 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 6 Jul 2026 09:38:10 +0200 Subject: [PATCH 1234/1328] ceph: add helper function ceph_cap_is_removed() Having it as a wrapper allows replacing the implementation, which the next patch will do. Signed-off-by: Max Kellermann Reviewed-by: Alex Markuze Signed-off-by: Ilya Dryomov --- fs/ceph/caps.c | 8 +++----- fs/ceph/mds_client.c | 2 +- fs/ceph/super.h | 14 ++++++++++++++ 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/fs/ceph/caps.c b/fs/ceph/caps.c index 78ed3fcf4e46..f859cf07f93b 100644 --- a/fs/ceph/caps.c +++ b/fs/ceph/caps.c @@ -1136,11 +1136,10 @@ static void __ceph_remove_cap(struct ceph_cap *cap, bool queue_release) struct ceph_mds_client *mdsc; int removed = 0; - /* 'ci' being NULL means the remove have already occurred */ - ci = cap->ci; - if (!ci) + if (ceph_cap_is_removed(cap)) return; + ci = cap->ci; session = cap->session; cl = session->s_mdsc->fsc->client; inode = &ci->netfs.inode; @@ -1212,8 +1211,7 @@ void ceph_remove_cap(struct ceph_mds_client *mdsc, struct ceph_cap *cap, struct ceph_inode_info *ci = cap->ci; struct ceph_fs_client *fsc; - /* 'ci' being NULL means the remove have already occurred */ - if (!ci) { + if (ceph_cap_is_removed(cap)) { doutc(mdsc->fsc->client, "inode is NULL\n"); return; } diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c index 9925e7e355e8..38657616e2a3 100644 --- a/fs/ceph/mds_client.c +++ b/fs/ceph/mds_client.c @@ -1956,7 +1956,7 @@ int ceph_iterate_session_caps(struct ceph_mds_session *session, spin_lock(&session->s_cap_lock); p = p->next; - if (!cap->ci) { + if (ceph_cap_is_removed(cap)) { doutc(cl, "finishing cap %p removal\n", cap); BUG_ON(cap->session != session); cap->session = NULL; diff --git a/fs/ceph/super.h b/fs/ceph/super.h index 3d7f91bc29b2..c3378493c42c 100644 --- a/fs/ceph/super.h +++ b/fs/ceph/super.h @@ -1278,6 +1278,20 @@ extern void ceph_add_cap(struct inode *inode, unsigned issued, unsigned wanted, unsigned cap, unsigned seq, u64 realmino, int flags, struct ceph_cap **new_cap); + +/** + * Determine whether __ceph_remove_cap() has been called on this #cap + * (but the object has not yet been freed because it is protected by + * `ceph_mds_session.s_cap_iterator`). + * + * Caller must lock either `ceph_inode_info.i_ceph_lock` or + * `ceph_mds_session.s_cap_lock`. + */ +static inline bool ceph_cap_is_removed(const struct ceph_cap *cap) +{ + return !cap->ci; +} + extern void ceph_remove_cap(struct ceph_mds_client *mdsc, struct ceph_cap *cap, bool queue_release); extern void __ceph_remove_caps(struct ceph_inode_info *ci); From af05588c9700de133aad9f8ba623ad0469e174fe Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 6 Jul 2026 09:38:11 +0200 Subject: [PATCH 1235/1328] ceph: mark cap remove with RB_CLEAR_NODE() instead of setting ci=NULL __ceph_remove_cap() erases the ceph_cap object from the RB tree, thus it seems natural to use RB_CLEAR_NODE() / RB_EMPTY_NODE() for the removal check. Signed-off-by: Max Kellermann Reviewed-by: Alex Markuze Signed-off-by: Ilya Dryomov --- fs/ceph/caps.c | 7 +++++-- fs/ceph/super.h | 16 ++++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/fs/ceph/caps.c b/fs/ceph/caps.c index f859cf07f93b..730180eebf77 100644 --- a/fs/ceph/caps.c +++ b/fs/ceph/caps.c @@ -1168,8 +1168,11 @@ static void __ceph_remove_cap(struct ceph_cap *cap, bool queue_release) cap->session = NULL; removed = 1; } - /* protect backpointer with s_cap_lock: see iterate_session_caps */ - cap->ci = NULL; + + /* protect removal marker with both i_ceph_lock and + s_cap_lock, so either one can be used to check for + removal */ + RB_CLEAR_NODE(&cap->ci_node); /* * s_cap_reconnect is protected by s_cap_lock. no one changes diff --git a/fs/ceph/super.h b/fs/ceph/super.h index c3378493c42c..55ec5cb0a033 100644 --- a/fs/ceph/super.h +++ b/fs/ceph/super.h @@ -203,7 +203,19 @@ struct ceph_fs_client { */ struct ceph_cap { struct ceph_inode_info *ci; - struct rb_node ci_node; /* per-ci cap tree */ + + /** + * Per-ci cap tree. Protected with + * `ceph_inode_info.i_ceph_lock`. + * + * Clearing this field with RB_CLEAR_NODE() requires holding + * both `ceph_inode_info.i_ceph_lock` and + * `ceph_mds_session->s_cap_lock`. Calling RB_EMPTY_NODE() + * (via ceph_cap_is_removed()) requires holding at least one + * of these. + */ + struct rb_node ci_node; + struct ceph_mds_session *session; struct list_head session_caps; /* per-session caplist */ u64 cap_id; /* unique cap id (mds provided) */ @@ -1289,7 +1301,7 @@ extern void ceph_add_cap(struct inode *inode, */ static inline bool ceph_cap_is_removed(const struct ceph_cap *cap) { - return !cap->ci; + return RB_EMPTY_NODE(&cap->ci_node); } extern void ceph_remove_cap(struct ceph_mds_client *mdsc, struct ceph_cap *cap, From 0cb176595797466da1792aaff1124b74d9df6e81 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 6 Jul 2026 09:38:12 +0200 Subject: [PATCH 1236/1328] ceph: pass inode pointer around instead of reloading it All these functions already have a ceph_inode_info pointer, so let's use that instead of letting every function reload it from RAM (i.e. `ceph_cap.ci`). This eliminates several memory accesses. Signed-off-by: Max Kellermann Reviewed-by: Alex Markuze Signed-off-by: Ilya Dryomov --- fs/ceph/caps.c | 58 +++++++++++++++++++++----------------------- fs/ceph/mds_client.c | 2 +- fs/ceph/super.h | 1 + 3 files changed, 30 insertions(+), 31 deletions(-) diff --git a/fs/ceph/caps.c b/fs/ceph/caps.c index 730180eebf77..eff7eecabb25 100644 --- a/fs/ceph/caps.c +++ b/fs/ceph/caps.c @@ -785,9 +785,9 @@ void ceph_add_cap(struct inode *inode, * generation of the MDS session (i.e. has not gone 'stale' due to * us losing touch with the mds). */ -static int __cap_is_valid(struct ceph_cap *cap) +static int __cap_is_valid(struct ceph_inode_info *ci, struct ceph_cap *cap) { - struct inode *inode = &cap->ci->netfs.inode; + struct inode *inode = &ci->netfs.inode; struct ceph_client *cl = cap->session->s_mdsc->fsc->client; unsigned long ttl; u32 gen; @@ -822,7 +822,7 @@ int __ceph_caps_issued(struct ceph_inode_info *ci, int *implemented) *implemented = 0; for (p = rb_first(&ci->i_caps); p; p = rb_next(p)) { cap = rb_entry(p, struct ceph_cap, ci_node); - if (!__cap_is_valid(cap)) + if (!__cap_is_valid(ci, cap)) continue; doutc(cl, "%p %llx.%llx cap %p issued %s\n", inode, ceph_vinop(inode), cap, ceph_cap_string(cap->issued)); @@ -855,7 +855,7 @@ int __ceph_caps_issued_other(struct ceph_inode_info *ci, struct ceph_cap *ocap) cap = rb_entry(p, struct ceph_cap, ci_node); if (cap == ocap) continue; - if (!__cap_is_valid(cap)) + if (!__cap_is_valid(ci, cap)) continue; have |= cap->issued; } @@ -866,9 +866,9 @@ int __ceph_caps_issued_other(struct ceph_inode_info *ci, struct ceph_cap *ocap) * Move a cap to the end of the LRU (oldest caps at list head, newest * at list tail). */ -static void __touch_cap(struct ceph_cap *cap) +static void __touch_cap(struct ceph_inode_info *ci, struct ceph_cap *cap) { - struct inode *inode = &cap->ci->netfs.inode; + struct inode *inode = &ci->netfs.inode; struct ceph_mds_session *s = cap->session; struct ceph_client *cl = s->s_mdsc->fsc->client; static u8 skip_counter; @@ -914,7 +914,7 @@ int __ceph_caps_issued_mask(struct ceph_inode_info *ci, int mask, int touch) for (p = rb_first(&ci->i_caps); p; p = rb_next(p)) { cap = rb_entry(p, struct ceph_cap, ci_node); - if (!__cap_is_valid(cap)) + if (!__cap_is_valid(ci, cap)) continue; if ((cap->issued & mask) == mask) { doutc(cl, "mask %p %llx.%llx cap %p issued %s (mask %s)\n", @@ -922,7 +922,7 @@ int __ceph_caps_issued_mask(struct ceph_inode_info *ci, int mask, int touch) ceph_cap_string(cap->issued), ceph_cap_string(mask)); if (touch) - __touch_cap(cap); + __touch_cap(ci, cap); return 1; } @@ -937,15 +937,15 @@ int __ceph_caps_issued_mask(struct ceph_inode_info *ci, int mask, int touch) struct rb_node *q; /* touch this + preceding caps */ - __touch_cap(cap); + __touch_cap(ci, cap); for (q = rb_first(&ci->i_caps); q != p; q = rb_next(q)) { cap = rb_entry(q, struct ceph_cap, ci_node); - if (!__cap_is_valid(cap)) + if (!__cap_is_valid(ci, cap)) continue; if (cap->issued & mask) - __touch_cap(cap); + __touch_cap(ci, cap); } } return 1; @@ -1099,7 +1099,7 @@ int __ceph_caps_mds_wanted(struct ceph_inode_info *ci, bool check) for (p = rb_first(&ci->i_caps); p; p = rb_next(p)) { cap = rb_entry(p, struct ceph_cap, ci_node); - if (check && !__cap_is_valid(cap)) + if (check && !__cap_is_valid(ci, cap)) continue; if (cap == ci->i_auth_cap) mds_wanted |= cap->mds_wanted; @@ -1127,11 +1127,10 @@ int ceph_is_any_caps(struct inode *inode) * caller should hold i_ceph_lock. * caller will not hold session s_mutex if called from destroy_inode. */ -static void __ceph_remove_cap(struct ceph_cap *cap, bool queue_release) +static void __ceph_remove_cap(struct ceph_inode_info *ci, struct ceph_cap *cap, bool queue_release) { struct ceph_mds_session *session; struct ceph_client *cl; - struct ceph_inode_info *ci; struct inode *inode; struct ceph_mds_client *mdsc; int removed = 0; @@ -1139,7 +1138,6 @@ static void __ceph_remove_cap(struct ceph_cap *cap, bool queue_release) if (ceph_cap_is_removed(cap)) return; - ci = cap->ci; session = cap->session; cl = session->s_mdsc->fsc->client; inode = &ci->netfs.inode; @@ -1209,9 +1207,9 @@ static void __ceph_remove_cap(struct ceph_cap *cap, bool queue_release) } void ceph_remove_cap(struct ceph_mds_client *mdsc, struct ceph_cap *cap, + struct ceph_inode_info *ci, bool queue_release) { - struct ceph_inode_info *ci = cap->ci; struct ceph_fs_client *fsc; if (ceph_cap_is_removed(cap)) { @@ -1227,7 +1225,7 @@ void ceph_remove_cap(struct ceph_mds_client *mdsc, struct ceph_cap *cap, !fsc->blocklisted && !ceph_inode_is_shutdown(&ci->netfs.inode)); - __ceph_remove_cap(cap, queue_release); + __ceph_remove_cap(ci, cap, queue_release); } struct cap_msg_args { @@ -1387,7 +1385,7 @@ void __ceph_remove_caps(struct ceph_inode_info *ci) while (p) { struct ceph_cap *cap = rb_entry(p, struct ceph_cap, ci_node); p = rb_next(p); - ceph_remove_cap(mdsc, cap, true); + ceph_remove_cap(mdsc, cap, ci, true); } spin_unlock(&ci->i_ceph_lock); } @@ -1400,11 +1398,11 @@ void __ceph_remove_caps(struct ceph_inode_info *ci) * Make note of max_size reported/requested from mds, revoked caps * that have now been implemented. */ -static void __prep_cap(struct cap_msg_args *arg, struct ceph_cap *cap, +static void __prep_cap(struct cap_msg_args *arg, struct ceph_inode_info *ci, + struct ceph_cap *cap, int op, int flags, int used, int want, int retain, int flushing, u64 flush_tid, u64 oldest_flush_tid) { - struct ceph_inode_info *ci = cap->ci; struct inode *inode = &ci->netfs.inode; struct ceph_client *cl = ceph_inode_to_client(inode); int held, revoking; @@ -2222,7 +2220,7 @@ void ceph_check_caps(struct ceph_inode_info *ci, int flags) if (want & ~cap->mds_wanted) { if (want & ~(cap->mds_wanted | cap->issued)) goto ack; - if (!__cap_is_valid(cap)) + if (!__cap_is_valid(ci, cap)) goto ack; } @@ -2264,7 +2262,7 @@ void ceph_check_caps(struct ceph_inode_info *ci, int flags) mds = cap->mds; /* remember mds, so we don't repeat */ - __prep_cap(&arg, cap, CEPH_CAP_OP_UPDATE, mflags, cap_used, + __prep_cap(&arg, ci, cap, CEPH_CAP_OP_UPDATE, mflags, cap_used, want, retain, flushing, flush_tid, oldest_flush_tid); spin_unlock(&ci->i_ceph_lock); @@ -2326,7 +2324,7 @@ static int try_flush_caps(struct inode *inode, u64 *ptid) flush_tid = __mark_caps_flushing(inode, session, true, &oldest_flush_tid); - __prep_cap(&arg, cap, CEPH_CAP_OP_FLUSH, CEPH_CLIENT_CAPS_SYNC, + __prep_cap(&arg, ci, cap, CEPH_CAP_OP_FLUSH, CEPH_CLIENT_CAPS_SYNC, __ceph_caps_used(ci), __ceph_caps_wanted(ci), (cap->issued | cap->implemented), flushing, flush_tid, oldest_flush_tid); @@ -2620,7 +2618,7 @@ static void __kick_flushing_caps(struct ceph_mds_client *mdsc, doutc(cl, "%p %llx.%llx cap %p tid %llu %s\n", inode, ceph_vinop(inode), cap, cf->tid, ceph_cap_string(cf->caps)); - __prep_cap(&arg, cap, CEPH_CAP_OP_FLUSH, + __prep_cap(&arg, ci, cap, CEPH_CAP_OP_FLUSH, (cf->tid < last_snap_flush ? CEPH_CLIENT_CAPS_PENDING_CAPSNAP : 0), __ceph_caps_used(ci), @@ -4131,7 +4129,7 @@ static void handle_cap_export(struct inode *inode, struct ceph_mds_caps *ex, goto out_unlock; if (target < 0) { - ceph_remove_cap(mdsc, cap, false); + ceph_remove_cap(mdsc, cap, ci, false); goto out_unlock; } @@ -4168,7 +4166,7 @@ static void handle_cap_export(struct inode *inode, struct ceph_mds_caps *ex, change_auth_cap_ses(ci, tcap->session); } } - ceph_remove_cap(mdsc, cap, false); + ceph_remove_cap(mdsc, cap, ci, false); goto out_unlock; } else if (tsession) { /* add placeholder for the export target */ @@ -4185,7 +4183,7 @@ static void handle_cap_export(struct inode *inode, struct ceph_mds_caps *ex, spin_unlock(&mdsc->cap_dirty_lock); } - ceph_remove_cap(mdsc, cap, false); + ceph_remove_cap(mdsc, cap, ci, false); goto out_unlock; } @@ -4301,7 +4299,7 @@ static void handle_cap_import(struct ceph_mds_client *mdsc, inode, ceph_vinop(inode), peer, ocap->seq, ocap->mseq, mds, piseq, pmseq); } - ceph_remove_cap(mdsc, ocap, (ph->flags & CEPH_CAP_FLAG_RELEASE)); + ceph_remove_cap(mdsc, ocap, ci, (ph->flags & CEPH_CAP_FLAG_RELEASE)); } *old_issued = issued; @@ -4899,7 +4897,7 @@ int ceph_encode_inode_release(void **p, struct inode *inode, drop &= ~(used | dirty); cap = __get_cap_for_mds(ci, mds); - if (cap && __cap_is_valid(cap)) { + if (cap && __cap_is_valid(ci, cap)) { unless &= cap->issued; if (unless) { if (unless & CEPH_CAP_AUTH_EXCL) @@ -5058,7 +5056,7 @@ int ceph_purge_inode_cap(struct inode *inode, struct ceph_cap *cap, bool *invali cap, ci, inode, ceph_vinop(inode)); is_auth = (cap == ci->i_auth_cap); - __ceph_remove_cap(cap, false); + __ceph_remove_cap(ci, cap, false); if (is_auth) { struct ceph_cap_flush *cf; diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c index 38657616e2a3..4c38fe5a9a4b 100644 --- a/fs/ceph/mds_client.c +++ b/fs/ceph/mds_client.c @@ -2312,7 +2312,7 @@ static int trim_caps_cb(struct inode *inode, int mds, void *arg) if (oissued) { /* we aren't the only cap.. just remove us */ - ceph_remove_cap(mdsc, cap, true); + ceph_remove_cap(mdsc, cap, ci, true); (*remaining)--; } else { struct dentry *dentry; diff --git a/fs/ceph/super.h b/fs/ceph/super.h index 55ec5cb0a033..1902b7cc55d3 100644 --- a/fs/ceph/super.h +++ b/fs/ceph/super.h @@ -1305,6 +1305,7 @@ static inline bool ceph_cap_is_removed(const struct ceph_cap *cap) } extern void ceph_remove_cap(struct ceph_mds_client *mdsc, struct ceph_cap *cap, + struct ceph_inode_info *ci, bool queue_release); extern void __ceph_remove_caps(struct ceph_inode_info *ci); extern void ceph_put_cap(struct ceph_mds_client *mdsc, From 7af4c4f01305b0935adf6d4301b1ec407025485d Mon Sep 17 00:00:00 2001 From: Xiubo Li Date: Tue, 14 Jul 2026 16:13:43 +0800 Subject: [PATCH 1237/1328] ceph: fix UAF in __kick_flushing_caps() on cf entry freed during unlock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit list_for_each_entry() iterates ci->i_cap_flush_list but drops i_ceph_lock to send cap messages. During the unlock window, handle_cap_flush_ack() can acquire i_ceph_lock, detach cf entries with tid <= flush_tid from the list, release i_ceph_lock, and free them via ceph_free_cap_flush() outside any lock. When the original thread reacquires i_ceph_lock and the for-loop macro advances via cf = list_next_entry(cf, i_list), it dereferences cf->i_list.next on freed memory. The race timeline: __kick_flushing_caps() handle_cap_flush_ack() ----------------------- ----------------------- holds i_ceph_lock <--- iterates to cf (tid=10) prepares FLUSH message drops i_ceph_lock <--- __send_cap() ── FLUSH(tid=10) MDS sends FLUSH_ACK(tid=10) ---> acquires i_ceph_lock cf->tid(10) <= flush_tid(10), detaches cf from i_cap_flush_list drops i_ceph_lock ceph_free_cap_flush(cf) <- frees it! acquires i_ceph_lock <--- for-loop advances: cf = list_next_entry(cf, i_list) -- UAF on freed cf->i_list.next The cf was just sent by __kick_flushing_caps itself via __send_cap(). The MDS may respond with FLUSH_ACK quickly enough that handle_cap_flush_ack() frees cf before __kick_flushing_caps can finish the iteration. Fix by converting to a manual while loop: save the next pointer under i_ceph_lock before dropping it, then use the saved pointer after reacquiring, so the potentially-freed cf is never accessed again. Cc: stable@vger.kernel.org Signed-off-by: Xiubo Li Reviewed-by: Viacheslav Dubeyko Signed-off-by: Ilya Dryomov --- fs/ceph/caps.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/fs/ceph/caps.c b/fs/ceph/caps.c index eff7eecabb25..539a24965afe 100644 --- a/fs/ceph/caps.c +++ b/fs/ceph/caps.c @@ -2599,9 +2599,14 @@ static void __kick_flushing_caps(struct ceph_mds_client *mdsc, } } - list_for_each_entry(cf, &ci->i_cap_flush_list, i_list) { - if (cf->tid < first_tid) + cf = list_first_entry(&ci->i_cap_flush_list, struct ceph_cap_flush, i_list); + while (&cf->i_list != &ci->i_cap_flush_list) { + struct ceph_cap_flush *next; + + if (cf->tid < first_tid) { + cf = list_next_entry(cf, i_list); continue; + } cap = ci->i_auth_cap; if (!(cap && cap->session == session)) { @@ -2611,6 +2616,7 @@ static void __kick_flushing_caps(struct ceph_mds_client *mdsc, } first_tid = cf->tid + 1; + next = list_next_entry(cf, i_list); if (!cf->is_capsnap) { struct cap_msg_args arg; @@ -2651,6 +2657,7 @@ static void __kick_flushing_caps(struct ceph_mds_client *mdsc, } spin_lock(&ci->i_ceph_lock); + cf = next; } } From ee611a7509554c4ca1f54f6aefe592fb1df7ea70 Mon Sep 17 00:00:00 2001 From: Xiubo Li Date: Tue, 14 Jul 2026 16:13:44 +0800 Subject: [PATCH 1238/1328] ceph: fix UAF in check_new_map() on session freed during unlock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit check_new_map() iterates mdsc->sessions[] and for each active session drops mdsc->mutex to perform per-session operations. The forced-close path (rank removed from map) correctly takes a reference on s via ceph_get_mds_session() before releasing mdsc->mutex, but three other paths do not: Path A (address changed): mutex_unlock → mutex_lock(&s->s_mutex) Path B (reconnect): mutex_unlock → send_mds_reconnect(mdsc, s) Path C (active transition): mutex_unlock → mutex_lock(&s->s_mutex) Without the extra reference, another thread can acquire mdsc->mutex during the unlock window, call __unregister_session() which drops the last reference on s, and free it. The original thread then accesses freed memory via s->s_mutex. Fix by adding ceph_get_mds_session(s) before each mutex_unlock and ceph_put_mds_session(s) after the corresponding mutex_lock, matching the pattern already used in the forced-close path. Race timeline (Path A): Thread A (check_new_map) Thread B (another map update holds mdsc->mutex or session teardown) -------------------------- -------------------------- s = mdsc->sessions[i] (refcount == 1, held only by sessions[] array) mutex_unlock(&mdsc->mutex) ---> acquires mdsc->mutex __unregister_session(mdsc, s) sessions[i] = NULL ceph_put_mds_session(s) refcount: 1 -> 0 kfree(s) <--- freed! mutex_lock(&s->s_mutex) UAF on freed s->s_mutex Cc: stable@vger.kernel.org Signed-off-by: Xiubo Li Reviewed-by: Viacheslav Dubeyko Signed-off-by: Ilya Dryomov --- fs/ceph/mds_client.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c index 4c38fe5a9a4b..d11c7eeaf0bf 100644 --- a/fs/ceph/mds_client.c +++ b/fs/ceph/mds_client.c @@ -5890,9 +5890,11 @@ static void check_new_map(struct ceph_mds_client *mdsc, ceph_mdsmap_get_addr(newmap, i), sizeof(struct ceph_entity_addr))) { /* just close it */ + ceph_get_mds_session(s); mutex_unlock(&mdsc->mutex); mutex_lock(&s->s_mutex); mutex_lock(&mdsc->mutex); + ceph_put_mds_session(s); ceph_con_close(&s->s_con); mutex_unlock(&s->s_mutex); s->s_state = CEPH_MDS_SESSION_RESTARTING; @@ -5907,6 +5909,7 @@ static void check_new_map(struct ceph_mds_client *mdsc, newstate >= CEPH_MDS_STATE_RECONNECT) { int rc; + ceph_get_mds_session(s); mutex_unlock(&mdsc->mutex); clear_bit(i, targets); rc = send_mds_reconnect(mdsc, s); @@ -5915,6 +5918,7 @@ static void check_new_map(struct ceph_mds_client *mdsc, "mds%d reconnect failed: %d\n", i, rc); mutex_lock(&mdsc->mutex); + ceph_put_mds_session(s); } /* @@ -5927,9 +5931,11 @@ static void check_new_map(struct ceph_mds_client *mdsc, pr_info_client(cl, "mds%d recovery completed\n", s->s_mds); kick_requests(mdsc, i); + ceph_get_mds_session(s); mutex_unlock(&mdsc->mutex); mutex_lock(&s->s_mutex); mutex_lock(&mdsc->mutex); + ceph_put_mds_session(s); ceph_kick_flushing_caps(mdsc, s); mutex_unlock(&s->s_mutex); wake_up_session_caps(s, RECONNECT); From 1319b97dfe9eaa0e15132af95efe6513718f9123 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 8 Jul 2026 22:40:22 +0200 Subject: [PATCH 1239/1328] ceph: drop mdsc->mutex before decoding the MDS reply handle_reply() held `mdsc->mutex` across parse_reply_info(), i.e. across the full decode of the reply message. For large replies (a big readdir allocates and parses many dir_entries), this can take a while and blocks ceph_mdsc_submit_request() calls meanwhile. The decode does not need `mdsc->mutex`: parse_reply_info() mostly fills the request's `r_reply_info`. Create replies may also add delegated inode numbers to the session xarray, but that xarray is protected by its own lock and is not serialized by `mdsc->mutex` today. By the time we reach parse_reply_info(), all `mdsc->mutex`-protected state has already been updated under the lock (the request has either been unregistered (safe reply) or added to the session's unsafe list (unsafe reply)) and the request is pinned by the reference taken in lookup_get_request(). Drop `mdsc->mutex` before calling parse_reply_info() so reply decoding no longer blocks request submission. This only widens the existing unlocked window that already covers the heavier ceph_fill_trace() / ceph_readdir_prepopulate() processing, so no new races are introduced. Signed-off-by: Max Kellermann Reviewed-by: Xiubo Li Signed-off-by: Ilya Dryomov --- fs/ceph/mds_client.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c index d11c7eeaf0bf..a091f77cedaf 100644 --- a/fs/ceph/mds_client.c +++ b/fs/ceph/mds_client.c @@ -4142,13 +4142,19 @@ static void handle_reply(struct ceph_mds_session *session, struct ceph_msg *msg) list_add_tail(&req->r_unsafe_item, &req->r_session->s_unsafe); } + /* + * Now that all mutex-protected state has been updated above + * (the request has been unregistered or added to the + * session's unsafe list), we can unlock it. + */ + mutex_unlock(&mdsc->mutex); + doutc(cl, "tid %lld result %d\n", tid, result); if (test_bit(CEPHFS_FEATURE_REPLY_ENCODING, &session->s_features)) err = parse_reply_info(session, msg, req, (u64)-1); else err = parse_reply_info(session, msg, req, session->s_con.peer_features); - mutex_unlock(&mdsc->mutex); /* Must find target inode outside of mutexes to avoid deadlocks */ rinfo = &req->r_reply_info; From e7d7aa7b730178278109c41fa1b17b06873065d5 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Tue, 7 Jul 2026 23:42:28 +0200 Subject: [PATCH 1240/1328] ceph: do not repeat ceph_trim_dentries() if no progress possible ceph_cap_reclaim_work() re-queues itself for as long as ceph_trim_dentries() returns -EAGAIN, which happens whenever a lease walk exhausts its `nr_to_scan` budget. This creates a busy loop that consumes CPU without making any progress when there is nothing to reclaim: with no cap pressure (`count==0`) and every scanned lease still valid, each pass runs the full scan budget down to zero and returns `-EAGAIN`, only to be queued again immediately. The dir-lease walk made this worse. When `expire_dir_lease` is `false` (i.e. we have no intention of reclaiming dir leases), __dir_lease_check() returned `TOUCH` for every valid lease. `TOUCH` moves the dentry to the tail of the list and resets `di->time` via __dentry_dir_lease_touch(), so a walk over N valid leases pointlessly rewrote the list, refreshed the timestamps (preventing them from ever aging out) and always drained `nr_to_scan`, guaranteeing the `-EAGAIN` requeue. Fix this in three steps: - Return `KEEP` instead of `TOUCH` when `expire_dir_lease` is `false`. If we are not going to reclaim the lease, leave it in place instead of churning the list and resetting its timestamp; the walk then terminates naturally (or via `STOP` at the first fresh lease). - Only return `-EAGAIN` from the first (dentry-lease) walk when something was actually freed. A full batch that frees nothing means retrying the same list immediately is futile; fall through to the dir-lease walk instead. - After both walks, bail out with success (0) when nothing was freed and there is no cap pressure (`count==0`). There is no reason to keep retrying when we are not over the cap limit and made no progress. Under real cap pressure (`count>0`) the reclaim path is unchanged and still retries via `-EAGAIN`. Without this patch, I saw 500 ceph_trim_dentries() calls per second on our web servers. This is very visible in `/proc/lock_stat` (5 minute capture): class name con-bounces contentions waittime-min waittime-max waittime-total waittime-avg acq-bounces acquisitions holdtime-min holdtime-max holdtime-total holdtime-avg &mdsc->dentry_list_lock: 126180 128218 0.04 8063.44 15986965.20 124.69 1573354 5296812 0.04 8291.28 74164526.48 14.00 ----------------------- &mdsc->dentry_list_lock 111736 [<000000007b11e319>] __ceph_dentry_dir_lease_touch+0x7c/0xa8 &mdsc->dentry_list_lock 2631 [<0000000050597999>] __dentry_leases_walk+0x64/0x2c8 &mdsc->dentry_list_lock 3878 [<00000000c0022f62>] __ceph_dentry_lease_touch+0x5c/0xa8 &mdsc->dentry_list_lock 9973 [<000000002f27cb6f>] __dentry_lease_unlist+0x50/0xa0 ----------------------- &mdsc->dentry_list_lock 123621 [<0000000050597999>] __dentry_leases_walk+0x64/0x2c8 &mdsc->dentry_list_lock 1822 [<000000007b11e319>] __ceph_dentry_dir_lease_touch+0x7c/0xa8 &mdsc->dentry_list_lock 2720 [<000000002f27cb6f>] __dentry_lease_unlist+0x50/0xa0 &mdsc->dentry_list_lock 55 [<00000000c0022f62>] __ceph_dentry_lease_touch+0x5c/0xa8 With this patch: class name con-bounces contentions waittime-min waittime-max waittime-total waittime-avg acq-bounces acquisitions holdtime-min holdtime-max holdtime-total holdtime-avg &mdsc->dentry_list_lock: 1203 1215 0.16 408.88 33082.88 27.23 4320501 7357389 0.04 500.64 1961578.00 0.27 ----------------------- &mdsc->dentry_list_lock 1029 [<000000003c9aea8a>] __ceph_dentry_dir_lease_touch+0x7c/0xa8 &mdsc->dentry_list_lock 169 [<000000002038c577>] __dentry_lease_unlist+0x50/0xa0 &mdsc->dentry_list_lock 16 [<00000000c991106d>] __ceph_dentry_lease_touch+0x5c/0xa8 &mdsc->dentry_list_lock 1 [<00000000612fe15f>] __dentry_leases_walk+0x64/0x2c8 ----------------------- &mdsc->dentry_list_lock 158 [<000000002038c577>] __dentry_lease_unlist+0x50/0xa0 &mdsc->dentry_list_lock 858 [<000000003c9aea8a>] __ceph_dentry_dir_lease_touch+0x7c/0xa8 &mdsc->dentry_list_lock 182 [<00000000612fe15f>] __dentry_leases_walk+0x64/0x2c8 &mdsc->dentry_list_lock 17 [<00000000c991106d>] __ceph_dentry_lease_touch+0x5c/0xa8 __dentry_leases_walk() is almost gone. The total wait time is reduced by a factor of 483. That will give some latency gains to ceph_readdir(). Cc: stable@vger.kernel.org Fixes: 37c4efc1ddf9 ("ceph: periodically trim stale dentries") Signed-off-by: Max Kellermann Reviewed-by: Alex Markuze Signed-off-by: Ilya Dryomov --- fs/ceph/dir.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c index d5d8f935fb62..f4e0bf244fd2 100644 --- a/fs/ceph/dir.c +++ b/fs/ceph/dir.c @@ -1770,11 +1770,11 @@ static int __dir_lease_check(const struct dentry *dentry, if (ret > 0) { if (time_before(jiffies, di->time + lwc->dir_lease_ttl)) return STOP; + if (!lwc->expire_dir_lease) + return KEEP; /* Move dentry to tail of dir lease list if we don't want * to delete it. So dentries in the list are checked in a * round robin manner */ - if (!lwc->expire_dir_lease) - return TOUCH; if (dentry->d_lockref.count > 0 || (di->flags & CEPH_DENTRY_REFERENCED)) return TOUCH; @@ -1801,7 +1801,7 @@ int ceph_trim_dentries(struct ceph_mds_client *mdsc) lwc.dir_lease = false; lwc.nr_to_scan = CEPH_CAPS_PER_RELEASE * 2; freed = __dentry_leases_walk(mdsc, &lwc); - if (!lwc.nr_to_scan) /* more invalid leases */ + if (freed > 0 && !lwc.nr_to_scan) /* more invalid leases */ return -EAGAIN; if (lwc.nr_to_scan < CEPH_CAPS_PER_RELEASE) @@ -1811,6 +1811,10 @@ int ceph_trim_dentries(struct ceph_mds_client *mdsc) lwc.expire_dir_lease = freed < count; lwc.dir_lease_ttl = mdsc->fsc->mount_options->caps_wanted_delay_max * HZ; freed +=__dentry_leases_walk(mdsc, &lwc); + if (freed == 0 && count == 0) + /* no progress possible currently, retry futile */ + return 0; + if (!lwc.nr_to_scan) /* more to check */ return -EAGAIN; From 5f074d7f2938d7461facbbd073b9394b1496e73e Mon Sep 17 00:00:00 2001 From: Alex Markuze Date: Mon, 6 Jul 2026 13:11:27 +0000 Subject: [PATCH 1241/1328] ceph: make nearfull sync writes opt-in The kernel CephFS client has historically treated a cluster or pool NEARFULL condition as a request to force successful writes through generic_write_sync(). That effectively turns otherwise buffered writes into synchronous writes and can cause a severe throughput drop as soon as a single OSD or the file data pool crosses the nearfull threshold. On modern large clusters, NEARFULL is primarily an operator health signal rather than an immediate client-side capacity failure. Operators can still have substantial usable capacity while a cluster is rebalancing, splitting PGs, or expanding onto new devices. RBD, RGW and the userspace CephFS client do not impose this extra client-side sync-write throttle, so the kernel client behavior is surprising and operationally painful. Change the default behavior so NEARFULL no longer changes normal write-sync semantics. FULL and pool FULL still fail with -ENOSPC, and explicitly synchronous writes continue to be synced by generic_write_sync(). Add a nearfull_sync mount option for deployments that want the legacy backpressure behavior. When this option is set, successful writes are promoted to IOCB_DSYNC if the cluster or file data pool is marked NEARFULL, preserving the old behavior for conservative deployments. Link: https://tracker.ceph.com/issues/74849 Signed-off-by: Alex Markuze Reviewed-by: Xiubo Li Signed-off-by: Ilya Dryomov --- Documentation/filesystems/ceph.rst | 6 ++++++ fs/ceph/file.c | 8 +++++--- fs/ceph/super.c | 10 ++++++++++ fs/ceph/super.h | 1 + 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/Documentation/filesystems/ceph.rst b/Documentation/filesystems/ceph.rst index 6d2276a87a5a..ee2ca0c0c654 100644 --- a/Documentation/filesystems/ceph.rst +++ b/Documentation/filesystems/ceph.rst @@ -194,6 +194,12 @@ Mount Options copies. Currently, it's only used in copy_file_range, which will revert to the default VFS implementation if this option is used. + nearfull_sync + Force written data to stable storage when the cluster or file data pool is + marked NEARFULL. This restores the legacy client-side backpressure + behavior. By default, CephFS writes are not forced synchronous solely + because of NEARFULL. + recover_session= Set auto reconnect mode in the case where the client is blocklisted. The available modes are "no" and "clean". The default is "no". diff --git a/fs/ceph/file.c b/fs/ceph/file.c index a0b9c2b5a583..bd3e3f5c269e 100644 --- a/fs/ceph/file.c +++ b/fs/ceph/file.c @@ -2388,7 +2388,8 @@ static ssize_t ceph_splice_read(struct file *in, loff_t *ppos, * dropping our cap refs and allowing the pending snap to logically * complete _before_ this write occurs. * - * If we are near ENOSPC, write synchronously. + * If requested, nearfull writes are synced to preserve the legacy + * client-side backpressure behavior. */ static ssize_t ceph_write_iter(struct kiocb *iocb, struct iov_iter *from) { @@ -2604,8 +2605,9 @@ static ssize_t ceph_write_iter(struct kiocb *iocb, struct iov_iter *from) } if (written >= 0) { - if ((map_flags & CEPH_OSDMAP_NEARFULL) || - (pool_flags & CEPH_POOL_FLAG_NEARFULL)) + if (ceph_test_mount_opt(fsc, NEARFULL_SYNC) && + ((map_flags & CEPH_OSDMAP_NEARFULL) || + (pool_flags & CEPH_POOL_FLAG_NEARFULL))) iocb->ki_flags |= IOCB_DSYNC; written = generic_write_sync(iocb, written); } diff --git a/fs/ceph/super.c b/fs/ceph/super.c index c05fbd4237f8..15edea30dc8b 100644 --- a/fs/ceph/super.c +++ b/fs/ceph/super.c @@ -177,6 +177,7 @@ enum { Opt_wsync, Opt_pagecache, Opt_sparseread, + Opt_nearfull_sync, }; enum ceph_recover_session_mode { @@ -205,6 +206,7 @@ static const struct fs_parameter_spec ceph_mount_parameters[] = { fsparam_flag_no ("ino32", Opt_ino32), fsparam_string ("mds_namespace", Opt_mds_namespace), fsparam_string ("mon_addr", Opt_mon_addr), + fsparam_flag_no ("nearfull_sync", Opt_nearfull_sync), fsparam_flag_no ("poolperm", Opt_poolperm), fsparam_flag_no ("quotadf", Opt_quotadf), fsparam_u32 ("rasize", Opt_rasize), @@ -593,6 +595,12 @@ static int ceph_parse_mount_param(struct fs_context *fc, else fsopt->flags |= CEPH_MOUNT_OPT_SPARSEREAD; break; + case Opt_nearfull_sync: + if (result.negated) + fsopt->flags &= ~CEPH_MOUNT_OPT_NEARFULL_SYNC; + else + fsopt->flags |= CEPH_MOUNT_OPT_NEARFULL_SYNC; + break; case Opt_test_dummy_encryption: #ifdef CONFIG_FS_ENCRYPTION fscrypt_free_dummy_policy(&fsopt->dummy_enc_policy); @@ -749,6 +757,8 @@ static int ceph_show_options(struct seq_file *m, struct dentry *root) seq_puts(m, ",nopagecache"); if (fsopt->flags & CEPH_MOUNT_OPT_SPARSEREAD) seq_puts(m, ",sparseread"); + if (fsopt->flags & CEPH_MOUNT_OPT_NEARFULL_SYNC) + seq_puts(m, ",nearfull_sync"); fscrypt_show_test_dummy_encryption(m, ',', root->d_sb); diff --git a/fs/ceph/super.h b/fs/ceph/super.h index 1902b7cc55d3..0020ccd0f974 100644 --- a/fs/ceph/super.h +++ b/fs/ceph/super.h @@ -45,6 +45,7 @@ #define CEPH_MOUNT_OPT_ASYNC_DIROPS (1<<15) /* allow async directory ops */ #define CEPH_MOUNT_OPT_NOPAGECACHE (1<<16) /* bypass pagecache altogether */ #define CEPH_MOUNT_OPT_SPARSEREAD (1<<17) /* always do sparse reads */ +#define CEPH_MOUNT_OPT_NEARFULL_SYNC (1<<18) /* sync writes when nearfull */ #define CEPH_MOUNT_OPT_DEFAULT \ (CEPH_MOUNT_OPT_DCACHE | \ From f374967fcdf04001c9b66df1c19106fa83cd91f7 Mon Sep 17 00:00:00 2001 From: Aleksandr Nogikh Date: Fri, 31 Jul 2026 10:14:50 +0000 Subject: [PATCH 1242/1328] libceph: validate banner payload length When parsing the Ceph messenger v2 protocol banner, the `payload_len` field is decoded from the banner prefix. If a client sends a banner with a `payload_len` of 0, the kernel sets up a 0-length socket read. This violates an invariant in the state machine, triggering a warning in `populate_in_iter()`: ------------[ cut here ]------------ !iov_iter_count(&con->v2.in_iter) WARNING: net/ceph/messenger_v2.c:3129 at populate_in_iter net/ceph/messenger_v2.c:3129 [inline], CPU#1: kworker/1:3/5070 WARNING: net/ceph/messenger_v2.c:3129 at ceph_con_v2_try_read+0x6634/0x6810 net/ceph/messenger_v2.c:3159, CPU#1: kworker/1:3/5070 ... Call Trace: ceph_con_workfn+0x1f5/0x14a0 net/ceph/messenger.c:1575 process_one_work kernel/workqueue.c:3322 [inline] process_scheduled_works+0xa8e/0x14e0 kernel/workqueue.c:3405 worker_thread+0xa47/0xfb0 kernel/workqueue.c:3486 kthread+0x388/0x470 kernel/kthread.c:436 ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245 According to the msgr2 protocol specification, the banner payload is expected to contain at least two 64-bit integers (`server_feat` and `server_req_feat`). Therefore, `payload_len` must be at least 16 bytes. Fix this by adding a check in `process_banner_prefix()` to reject a `payload_len` smaller than 16 bytes. This prevents the 0-length read and correctly aborts the connection with a protocol error. Fixes: cd1a677cad99 ("libceph, ceph: implement msgr2.1 protocol (crc and secure modes)") Assisted-by: Gemini:gemini-3.5-flash Gemini:gemini-3.1-pro-preview syzbot Reported-by: syzbot+87c7c2d63c44e41c77a3@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=87c7c2d63c44e41c77a3 Link: https://syzkaller.appspot.com/ai_job?id=c8ca3d63-717a-4933-89ec-f3d761b8690d Signed-off-by: Aleksandr Nogikh Reviewed-by: Alex Markuze Signed-off-by: Ilya Dryomov --- net/ceph/messenger_v2.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/net/ceph/messenger_v2.c b/net/ceph/messenger_v2.c index 05f6eea299fc..b323b61e7023 100644 --- a/net/ceph/messenger_v2.c +++ b/net/ceph/messenger_v2.c @@ -2142,6 +2142,11 @@ static int process_banner_prefix(struct ceph_connection *con) payload_len = ceph_decode_16(&p); dout("%s con %p payload_len %d\n", __func__, con, payload_len); + if (payload_len < sizeof(u64) + sizeof(u64)) { + con->error_msg = "protocol error, bad banner payload len"; + return -EINVAL; + } + return prepare_read_banner_payload(con, payload_len); } From 2a2f98e17e1d322a94027daf0c6df88af2f9af50 Mon Sep 17 00:00:00 2001 From: Tal Zussman Date: Mon, 17 Aug 2026 15:29:17 -0400 Subject: [PATCH 1243/1328] libceph: remove ceph_put_page_vector() ceph_put_page_vector() was paired with ceph_get_direct_page_vector(), which was removed in commit 97a385e55829 ("libceph: remove ceph_get_direct_page_vector()"). Its only remaining caller, finish_netfs_read(), uses it to put a page vector allocated with iov_iter_get_pages_alloc2(), which is confusing. Open-code the put_page() loop and kvfree() there instead. The caller passed dirty = false, so this also removes the dead dirty branch and with it a call to the deprecated set_page_dirty_lock(). Signed-off-by: Tal Zussman Reviewed-by: Ilya Dryomov Signed-off-by: Ilya Dryomov --- fs/ceph/addr.c | 9 ++++++--- include/linux/ceph/libceph.h | 2 -- net/ceph/pagevec.c | 13 ------------- 3 files changed, 6 insertions(+), 18 deletions(-) diff --git a/fs/ceph/addr.c b/fs/ceph/addr.c index 702e5f8ab565..7e454b854a0b 100644 --- a/fs/ceph/addr.c +++ b/fs/ceph/addr.c @@ -255,9 +255,12 @@ static void finish_netfs_read(struct ceph_osd_request *req) } if (osd_data->type == CEPH_OSD_DATA_TYPE_PAGES) { - ceph_put_page_vector(osd_data->pages, - calc_pages_for(osd_data->alignment, - osd_data->length), false); + int num_pages = calc_pages_for(osd_data->alignment, + osd_data->length); + + for (int i = 0; i < num_pages; i++) + put_page(osd_data->pages[i]); + kvfree(osd_data->pages); } if (err > 0) { ceph_subvolume_metrics_record_io(fsc->mdsc, ceph_inode(inode), diff --git a/include/linux/ceph/libceph.h b/include/linux/ceph/libceph.h index 63e0e2aa1ce9..691e1bdece49 100644 --- a/include/linux/ceph/libceph.h +++ b/include/linux/ceph/libceph.h @@ -313,8 +313,6 @@ int ceph_wait_for_latest_osdmap(struct ceph_client *client, /* pagevec.c */ extern void ceph_release_page_vector(struct page **pages, int num_pages); -extern void ceph_put_page_vector(struct page **pages, int num_pages, - bool dirty); extern struct page **ceph_alloc_page_vector(int num_pages, gfp_t flags); extern void ceph_copy_from_page_vector(struct page **pages, void *data, diff --git a/net/ceph/pagevec.c b/net/ceph/pagevec.c index 858359873c4d..a6aa5b3b7a1e 100644 --- a/net/ceph/pagevec.c +++ b/net/ceph/pagevec.c @@ -10,19 +10,6 @@ #include -void ceph_put_page_vector(struct page **pages, int num_pages, bool dirty) -{ - int i; - - for (i = 0; i < num_pages; i++) { - if (dirty) - set_page_dirty_lock(pages[i]); - put_page(pages[i]); - } - kvfree(pages); -} -EXPORT_SYMBOL(ceph_put_page_vector); - void ceph_release_page_vector(struct page **pages, int num_pages) { int i; From c25aee9c630fb86f98d79eccb75765067079b972 Mon Sep 17 00:00:00 2001 From: Matthew Brown Date: Wed, 12 Aug 2026 18:13:21 +0100 Subject: [PATCH 1244/1328] ceph: fix leaked inode reference on writeback abort at umount ceph_dirty_folio() takes a wrbuffer claim on each newly dirtied folio: it bumps i_wrbuffer_ref (taking an ihold() on the 0->1 transition) and attaches the snap_context to folio->private. That claim is released only by ceph_put_wrbuffer_cap_refs(), which for a submitted write runs from writepages_finish(). In ceph_submit_write(), if ceph_inc_osd_stopping_blocker() fails -- which happens during umount -- the request is aborted before submission: the already-collected folios are only redirtied and unlocked, so writepages_finish() never runs and the claim is leaked. redirty_page_for_writepage() -> folio_redirty_for_writepage() -> filemap_dirty_folio() sets PG_dirty directly and does not go through ->dirty_folio, so ceph_dirty_folio() is not re-entered to rebalance it. Because every subsequent writeback also fails the osd_stopping_blocker, i_wrbuffer_ref never returns to 0, the ihold() is never dropped, and the inode cannot be evicted: VFS: Busy inodes after unmount of ceph kernel BUG at fs/super.c:650! Release the orphaned claim in the abort path before redirtying, via ceph_undo_wrbuffer_claim(): detach the snap_context, drop the wrbuffer reference (letting i_wrbuffer_ref reach 0 and iput() the inode), and drop the snap_context reference -- i.e. do what writepages_finish() would have done for these never-submitted folios. Only the locked_pages entries are undone; folios still in the fbatch were never dirty-cleared by this call (folio_clear_dirty_for_io() is the ownership-transfer point, and a successful move NULLs the fbatch slot), so they hold no claim this call owns. Cc: stable@vger.kernel.org Fixes: fd7449d937e7 ("ceph: fix generic/421 test failure") Signed-off-by: Matthew Brown Reviewed-by: Xiubo Li Signed-off-by: Ilya Dryomov --- fs/ceph/addr.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/fs/ceph/addr.c b/fs/ceph/addr.c index 7e454b854a0b..657c2cb0f881 100644 --- a/fs/ceph/addr.c +++ b/fs/ceph/addr.c @@ -1429,6 +1429,16 @@ void ceph_shift_unused_folios_left(struct folio_batch *fbatch) fbatch->nr = n; } +static void ceph_undo_wrbuffer_claim(struct inode *inode, struct folio *folio) +{ + struct ceph_snap_context *snapc = folio_detach_private(folio); + + if (!snapc) + return; + ceph_put_wrbuffer_cap_refs(ceph_inode(inode), 1, snapc); + ceph_put_snap_context(snapc); +} + static int ceph_submit_write(struct address_space *mapping, struct writeback_control *wbc, @@ -1492,6 +1502,7 @@ int ceph_submit_write(struct address_space *mapping, if (!page) continue; + ceph_undo_wrbuffer_claim(inode, page_folio(page)); redirty_page_for_writepage(wbc, page); unlock_page(page); } From aedc9053d909508a5f56c3f49f885fc030df4730 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Jean?= Date: Thu, 13 Aug 2026 14:00:00 +0200 Subject: [PATCH 1245/1328] ceph: reject export_targets ranks >= CEPH_MAX_MDS in mdsmap decode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MDSMap export_targets entries are monitor controlled. check_new_map() uses each entry as a bit number in a fixed stack bitmap, so a rank outside the protocol namespace can make set_bit() write past the end of the array. Reject ranks outside CEPH_MAX_MDS while decoding the map. Do not validate against possible_max_rank here because maps may legitimately reference ranks beyond a temporarily reduced max_mds. Cc: stable@vger.kernel.org Fixes: d517b3983dd3 ("ceph: reconnect to the export targets on new mdsmaps") Signed-off-by: Jérémy Jean Reviewed-by: Alex Markuze Signed-off-by: Alex Markuze Signed-off-by: Ilya Dryomov --- fs/ceph/mdsmap.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/ceph/mdsmap.c b/fs/ceph/mdsmap.c index 4f0626753429..53079ef34c3a 100644 --- a/fs/ceph/mdsmap.c +++ b/fs/ceph/mdsmap.c @@ -269,6 +269,10 @@ struct ceph_mdsmap *ceph_mdsmap_decode(struct ceph_mds_client *mdsc, void **p, goto nomem; for (j = 0; j < num_export_targets; j++) { target = ceph_decode_32(&pexport_targets); + if (target >= CEPH_MAX_MDS) { + err = -EIO; + goto corrupt; + } info->export_targets[j] = target; } } else { From 3cde4a8302301679937474a5f7a851394cc1bd11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Jean?= Date: Sat, 15 Aug 2026 21:46:37 +0000 Subject: [PATCH 1246/1328] libceph: reject buckets with mismatched CRUSH ids MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit crush_decode() stores bucket data by array slot, and the mapper later derives the per-bucket workspace index from the decoded bucket id. A malformed map can therefore make one bucket reuse another bucket's workspace by encoding an id different from -1 - slot. For uniform buckets, the second replica selection expands the source bucket's permutation into that aliased workspace buffer. If the source bucket is larger than the aliased bucket, the write runs past the smaller permutation array and can escape the kvmalloc'd CRUSH workspace. KASAN reports a slab OOB write of 4 bytes in bucket_perm_choose(). Reject buckets whose encoded id does not match their array slot. Valid CRUSH maps already use the canonical negative id corresponding to the bucket slot, so this restores the invariant expected by work->work[-1 - in->id] without changing valid map behavior. Cc: stable@vger.kernel.org Fixes: 66a0e2d579db ("crush: remove mutable part of CRUSH map") Assisted-by: Codex:gpt-5 Signed-off-by: Jérémy Jean Reviewed-by: Alex Markuze Signed-off-by: Ilya Dryomov --- net/ceph/osdmap.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/ceph/osdmap.c b/net/ceph/osdmap.c index d6282f0bcff8..cf34b35c9a90 100644 --- a/net/ceph/osdmap.c +++ b/net/ceph/osdmap.c @@ -517,6 +517,8 @@ static struct crush_map *crush_decode(void *pbyval, void *end) ceph_decode_need(p, end, 4*sizeof(u32), bad); b->id = ceph_decode_32(p); + if (b->id != -1 - i) + goto bad; b->type = ceph_decode_16(p); if (b->type == 0) goto bad; From 8fdf946445732c2bcd685abc8bd0e509d2ebc158 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Tue, 18 Aug 2026 20:40:05 +0200 Subject: [PATCH 1247/1328] ceph: force a cap message when a deferred revoke can't be acked immediately When the MDS revokes capabilities, handle_cap_grant() normally guarantees a response by setting `CHECK_CAPS_FLUSH_FORCE` (see commit 31634d7597d8 ("ceph: force sending a cap update msg back to MDS for revoke op")), so ceph_check_caps() sends a cap message even if the client would otherwise decide it has nothing to do. That guarantee is skipped whenever the revoke has to be deferred (via revoke_wait): revoking Fb while dirty data is still buffered (writeback is queued first) or revoking Fc while pages are cached (async invalidation is queued first). In those cases, the ack is left to the deferred completion (ceph_put_wrbuffer_cap_refs() after writeback, or the invalidate worker after invalidation); both of which call ceph_check_caps(ci,0) i.e. without `CHECK_CAPS_FLUSH_FORCE`. Nothing gets sent under one of the following conditions: - the inode is retaining caps because the file was used recently (file_wanted != 0; retain |= CEPH_CAP_ANY) - the revoked cap is still used because the page was re-cached (e.g. a file being re-read) - the MDS has meanwhile re-granted, so `issued==implemented` and the client sees nothing being revoked The client then never emits the cap message which the MDS is waiting for. The MDS blocks on the revoke indefinitely and logs, for minutes or hours: client.NNN isn't responding to mclientcaps(revoke), ino 0x... pending pAsxLsXsxFsxcrwb issued pAsxLsXsxFsxcrwb, sent 964.899182 seconds ago The client-side state at that point shows the full cap set still issued, nothing in the revoking/flushing sets. Thus nothing gets sent. This patch fixes it by remembering that a forced response is expected. When a revoke is deferred, set `CEPH_I_FLUSH_FORCE` on the inode. ceph_check_caps() replays it as `CHECK_CAPS_FLUSH_FORCE`, so whichever path re-checks the inode next (the writeback/invalidate completion, the delayed worker, or any other caller) is guaranteed to send a cap message to the MDS. __prep_cap() clears the flag once a message is actually built. This is the deferred-path counterpart of the existing `CHECK_CAPS_FLUSH_FORCE` handling; a normal (non-deferred) revoke still forces the response inline as before. Cc: stable@vger.kernel.org Fixes: 31634d7597d8 ("ceph: force sending a cap update msg back to MDS for revoke op") Fixes: 257e6172ab36 ("ceph: don't let check_caps skip sending responses for revoke msgs") Signed-off-by: Max Kellermann Reviewed-by: Alex Markuze Signed-off-by: Ilya Dryomov --- fs/ceph/caps.c | 63 +++++++++++++++++++++++++++++++++++++++++++------ fs/ceph/super.h | 5 ++++ 2 files changed, 61 insertions(+), 7 deletions(-) diff --git a/fs/ceph/caps.c b/fs/ceph/caps.c index 539a24965afe..bcb04c6cb92c 100644 --- a/fs/ceph/caps.c +++ b/fs/ceph/caps.c @@ -987,6 +987,27 @@ int __ceph_caps_revoking_other(struct ceph_inode_info *ci, return 0; } +/* + * Return true if any cap of this inode holds caps which the MDS has + * revoked, but which we have not released yet. + */ +static bool __ceph_is_any_revoking(const struct ceph_inode_info *ci) +{ + const struct rb_node *p; + + lockdep_assert_held(&ci->i_ceph_lock); + + for (p = rb_first(&ci->i_caps); p; p = rb_next(p)) { + const struct ceph_cap *cap = + rb_entry(p, struct ceph_cap, ci_node); + + if (cap->implemented & ~cap->issued) + return true; + } + + return false; +} + int __ceph_caps_used(struct ceph_inode_info *ci) { int used = 0; @@ -1431,6 +1452,9 @@ static void __prep_cap(struct cap_msg_args *arg, struct ceph_inode_info *ci, cap->implemented &= cap->issued | used; cap->mds_wanted = want; + if ((ci->i_ceph_flags & CEPH_I_FLUSH_FORCE) != 0 && !__ceph_is_any_revoking(ci)) + clear_bit(CEPH_I_FLUSH_FORCE_BIT, &ci->i_ceph_flags); + arg->session = cap->session; arg->ino = ceph_vino(inode).ino; arg->cid = cap->cap_id; @@ -2048,6 +2072,14 @@ void ceph_check_caps(struct ceph_inode_info *ci, int flags) if (ci->i_ceph_flags & CEPH_I_FLUSH) flags |= CHECK_CAPS_FLUSH; + /* + * A revoke whose response was deferred (see handle_cap_grant()) must + * still be acknowledged. Replay the forced flush here so that even a + * check triggered by writeback/invalidation completion sends a cap + * message to the MDS. + */ + if (ci->i_ceph_flags & CEPH_I_FLUSH_FORCE) + flags |= CHECK_CAPS_FLUSH_FORCE; retry: /* Caps wanted by virtue of active open files. */ file_wanted = __ceph_caps_file_wanted(ci); @@ -3774,13 +3806,30 @@ static void handle_cap_grant(struct inode *inode, BUG_ON(cap->issued & ~cap->implemented); /* don't let check_caps skip sending a response to MDS for revoke msgs */ - if (!revoke_wait && le32_to_cpu(grant->op) == CEPH_CAP_OP_REVOKE) { - cap->mds_wanted = 0; - flags |= CHECK_CAPS_FLUSH_FORCE; - if (cap == ci->i_auth_cap) - check_caps = 1; /* check auth cap only */ - else - check_caps = 2; /* check all caps */ + if (le32_to_cpu(grant->op) == CEPH_CAP_OP_REVOKE) { + if (revoke_wait) { + /* + * We can't ack the revoke yet: the response is deferred + * until the writeback or cache invalidation queued above + * completes. Set the CEPH_I_FLUSH_FORCE flag to remember + * that a forced cap message is owed so that deferred + * completion (ceph_put_wrbuffer_cap_refs() or the + * invalidate worker, both of which call ceph_check_caps()) + * actually sends one, even if by then the revoked caps look + * unused, the inode is retaining caps, or the MDS has + * re-granted them. Without this, the cap message is never + * sent and the MDS hangs ("isn't responding to + * mclientcaps(revoke)"). + */ + set_bit(CEPH_I_FLUSH_FORCE_BIT, &ci->i_ceph_flags); + } else { + cap->mds_wanted = 0; + flags |= CHECK_CAPS_FLUSH_FORCE; + if (cap == ci->i_auth_cap) + check_caps = 1; /* check auth cap only */ + else + check_caps = 2; /* check all caps */ + } } if (extra_info->inline_version > 0 && diff --git a/fs/ceph/super.h b/fs/ceph/super.h index 0020ccd0f974..72d4e30304dc 100644 --- a/fs/ceph/super.h +++ b/fs/ceph/super.h @@ -709,6 +709,10 @@ static inline struct inode *ceph_find_inode(struct super_block *sb, #define CEPH_I_ASYNC_CREATE_BIT (12) /* async create in flight for this */ #define CEPH_I_SHUTDOWN_BIT (13) /* inode is no longer usable */ #define CEPH_I_ASYNC_CHECK_CAPS_BIT (14) /* check caps after async creating finishes */ +#define CEPH_I_FLUSH_FORCE_BIT (15) /* a revoke's response was deferred; + * force a cap message to the MDS once + * the deferred work completes + */ #define CEPH_I_DIR_ORDERED (1 << CEPH_I_DIR_ORDERED_BIT) #define CEPH_I_FLUSH (1 << CEPH_I_FLUSH_BIT) @@ -721,6 +725,7 @@ static inline struct inode *ceph_find_inode(struct super_block *sb, #define CEPH_I_ODIRECT (1 << CEPH_I_ODIRECT_BIT) #define CEPH_I_ASYNC_CREATE (1 << CEPH_I_ASYNC_CREATE_BIT) #define CEPH_I_SHUTDOWN (1 << CEPH_I_SHUTDOWN_BIT) +#define CEPH_I_FLUSH_FORCE (1 << CEPH_I_FLUSH_FORCE_BIT) /* * Masks of ceph inode work. From d79fb758e7e2e7a181eab630c0545a56041c473d Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Wed, 26 Aug 2026 09:50:34 +0800 Subject: [PATCH 1248/1328] samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-modify ftrace_direct_init() assigns kthread_run()'s return value to simple_tsk without an IS_ERR() check. When kthread_run() fails it returns ERR_PTR(-ENOMEM), but init still returns 0, so the module loads with simple_tsk holding an error pointer. On unload, ftrace_direct_exit() then passes that ERR_PTR to kthread_stop(), leading to a null-pointer-dereference. Check the return value of kthread_run() with IS_ERR(); on failure, unregister the ftrace direct call and propagate the error code. Link: https://patch.msgid.link/20260826015034.10755-1-vulab@iscas.ac.cn Fixes: ae0cc3b7e7f5 ("ftrace/samples: Add a sample module that implements modify_ftrace_direct()") Suggested-by: Steven Rostedt Signed-off-by: Haotian Zhang Signed-off-by: Steven Rostedt --- samples/ftrace/ftrace-direct-modify.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/samples/ftrace/ftrace-direct-modify.c b/samples/ftrace/ftrace-direct-modify.c index 1ba1927b548e..164d9dd6fd92 100644 --- a/samples/ftrace/ftrace-direct-modify.c +++ b/samples/ftrace/ftrace-direct-modify.c @@ -320,9 +320,15 @@ static int __init ftrace_direct_init(void) ftrace_set_filter_ip(&direct, (unsigned long) my_ip, 0, 0); ret = register_ftrace_direct(&direct, my_tramp); - if (!ret) - simple_tsk = kthread_run(simple_thread, NULL, "event-sample-fn"); - return ret; + if (ret) + return ret; + simple_tsk = kthread_run(simple_thread, NULL, "event-sample-fn"); + if (IS_ERR(simple_tsk)) { + unregister_ftrace_direct(&direct, my_tramp, true); + return PTR_ERR(simple_tsk); + } + + return 0; } static void __exit ftrace_direct_exit(void) From 6727b7618f49401acf373fa3ec5712e2ec52e5cf Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Wed, 26 Aug 2026 09:50:50 +0800 Subject: [PATCH 1249/1328] samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-multi-modify ftrace_direct_multi_init() assigns kthread_run()'s return value to simple_tsk without an IS_ERR() check. When kthread_run() fails it returns ERR_PTR(-ENOMEM), but init still returns 0, so the module loads with simple_tsk holding an error pointer. On unload, ftrace_direct_multi_exit() then passes that ERR_PTR to kthread_stop(), leading to a null-pointer-dereference. Check the return value of kthread_run() with IS_ERR(); on failure, unregister the ftrace direct call and propagate the error code. Link: https://patch.msgid.link/20260826015050.10772-1-vulab@iscas.ac.cn Fixes: e1067a07cfbc ("ftrace/samples: Add module to test multi direct modify interface") Suggested-by: Steven Rostedt Signed-off-by: Haotian Zhang Signed-off-by: Steven Rostedt --- samples/ftrace/ftrace-direct-multi-modify.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/samples/ftrace/ftrace-direct-multi-modify.c b/samples/ftrace/ftrace-direct-multi-modify.c index 7a7822dfeb50..b03766c6217b 100644 --- a/samples/ftrace/ftrace-direct-multi-modify.c +++ b/samples/ftrace/ftrace-direct-multi-modify.c @@ -364,9 +364,15 @@ static int __init ftrace_direct_multi_init(void) ret = register_ftrace_direct(&direct, my_tramp); - if (!ret) - simple_tsk = kthread_run(simple_thread, NULL, "event-sample-fn"); - return ret; + if (ret) + return ret; + simple_tsk = kthread_run(simple_thread, NULL, "event-sample-fn"); + if (IS_ERR(simple_tsk)) { + unregister_ftrace_direct(&direct, my_tramp, true); + return PTR_ERR(simple_tsk); + } + + return 0; } static void __exit ftrace_direct_multi_exit(void) From 15596a87fcc6fa07a162858e5fd00ba31752096b Mon Sep 17 00:00:00 2001 From: Shengyu Qu Date: Thu, 27 Aug 2026 01:20:50 +0800 Subject: [PATCH 1250/1328] ASoC: amd: acp-config: change quirks to cover all ASUS FA401EA variants Existing quirk doesn't cover all known existing FA401EA devices, so use "FA401EA" to cover all of them. Link: https://bugzilla.kernel.org/show_bug.cgi?id=221310#c49 Fixes: 27d090f3ccd4 ("ASoC: amd: acp: add ACP70 DMI override for new ASUS TUF platforms") Signed-off-by: Shengyu Qu Link: https://patch.msgid.link/20260826172050.15686-1-wiagn@4d2.org Signed-off-by: Mark Brown --- sound/soc/amd/acp-config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/amd/acp-config.c b/sound/soc/amd/acp-config.c index a27336b132f2..2d50be8190c1 100644 --- a/sound/soc/amd/acp-config.c +++ b/sound/soc/amd/acp-config.c @@ -54,7 +54,7 @@ static const struct dmi_system_id acp70_acpi_flag_override_table[] = { { .matches = { DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC"), - DMI_MATCH(DMI_PRODUCT_NAME, "TUF Gaming A14 FA401EA"), + DMI_MATCH(DMI_PRODUCT_NAME, "FA401EA"), }, }, { From c966d29e01bbf829f8bb4a39a49811c56cdb49c3 Mon Sep 17 00:00:00 2001 From: Daeho Jeong Date: Wed, 26 Aug 2026 08:36:44 -0700 Subject: [PATCH 1251/1328] f2fs: support resizable tail section and unify pinned allocation Currently, zoned block devices restrict pinned file allocations to conventional zones at the beginning of the storage (before first_seq_zone_segno), triggering range GC when conventional space is exhausted. On regular block devices, when preparing for future online filesystem resizing (e.g. partition shrinking), pinned files must not be allocated in the tail area that will be truncated, as pinned files cannot be relocated by GC. Specifying the resizable tail area size (in sections) allows uniform mount configuration across devices of different storage capacities. To support this, introduce a unified `pinned_area_max_secno` boundary abstraction in `f2fs_sb_info`: 1. Add `-o resizable_tail_secno=%u` mount option to specify the number of sections at the tail of the filesystem reserved for resizing. 2. In `f2fs_fill_super()`, initialize `sbi->pinned_area_max_secno` as: min(MAIN_SECS(sbi) - resizable_tail_sec, zoned_max_sec). 3. In `get_new_segment()`, restrict segment allocation for pinned files (`pinning == true`) to `0 .. sbi->pinned_area_max_secno - 1`. If no free section is available in the pinned area, return -EAGAIN. 4. In `f2fs_allocate_pinning_section()`, unify the range GC trigger to run `f2fs_gc_range()` up to `sbi->pinned_area_max_secno` whenever `sbi->pinned_area_max_secno < MAIN_SECS(sbi)` and allocation returns -EAGAIN. 5. Expose `/sys/fs/f2fs//pinned_area_max_secno` as a read-only sysfs node. Signed-off-by: Daeho Jeong Signed-off-by: Sunmin Jeong Reviewed-by: Wenjie Qi Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- Documentation/ABI/testing/sysfs-fs-f2fs | 7 +++++ Documentation/filesystems/f2fs.rst | 8 +++++- fs/f2fs/f2fs.h | 2 ++ fs/f2fs/segment.c | 35 ++++++++++++------------ fs/f2fs/segment.h | 1 + fs/f2fs/super.c | 36 +++++++++++++++++++++++++ fs/f2fs/sysfs.c | 2 ++ 7 files changed, 73 insertions(+), 18 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-fs-f2fs b/Documentation/ABI/testing/sysfs-fs-f2fs index 85194e4c7f01..0cebc89799dd 100644 --- a/Documentation/ABI/testing/sysfs-fs-f2fs +++ b/Documentation/ABI/testing/sysfs-fs-f2fs @@ -1013,3 +1013,10 @@ Description: Every time a write operation completes f2fs_write_end_io() is the maximum size of a write bio that is completed in atomic (atc) context. The default value for this attribute is UINT_MAX which means that this functionality is disabled by default. + +What: /sys/fs/f2fs//pinned_area_max_secno +Date: August 2026 +Contact: "Daeho Jeong" +Description: This is a read-only entry to show the upper bound section number + for pinned files. Pinned files will only be allocated within + sections 0 to pinned_area_max_secno - 1. diff --git a/Documentation/filesystems/f2fs.rst b/Documentation/filesystems/f2fs.rst index 1a5fd4afe609..a3c3b6948734 100644 --- a/Documentation/filesystems/f2fs.rst +++ b/Documentation/filesystems/f2fs.rst @@ -417,7 +417,13 @@ lookup_mode=%s Control the directory lookup behavior for casefolded auto F2FS determines the mode based on the on-disk `SB_ENC_NO_COMPAT_FALLBACK_FL` flag. - ================== ======================================== +resizable_tail_secno=%u Control the number of sections at the tail of the + filesystem reserved for online resizing. Pinned files + will only be allocated within sections 0 to + (MAIN_SECS - resizable_tail_secno) - 1. If set to 0 + (default), there is no tail restriction unless running + on a zoned block device where conventional zones are + used. ======================== ============================================================ Debugfs Entries diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index b0a9c14de595..16720f1f0a9c 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -255,6 +255,7 @@ struct f2fs_mount_info { block_t unusable_cap; /* Amount of space allowed to be * unusable when disabling checkpoint */ + unsigned int resizable_tail_secno; /* number of resizable tail sections */ /* For compression */ unsigned char compress_algorithm; /* algorithm type */ @@ -2005,6 +2006,7 @@ struct f2fs_sb_info { spinlock_t dev_lock; /* protect dirty_device */ bool aligned_blksize; /* all devices has the same logical blksize */ unsigned int first_seq_zone_segno; /* first segno in sequential zone */ + unsigned int pinned_area_max_secno; /* upper bound section for pinned files */ unsigned int bggc_io_aware; /* For adjust the BG_GC priority when pending IO */ unsigned int allocate_section_hint; /* the boundary position between devices */ unsigned int allocate_section_policy; /* determine the section writing priority */ diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c index 56decf9c691c..1e7e745be71d 100644 --- a/fs/f2fs/segment.c +++ b/fs/f2fs/segment.c @@ -2877,6 +2877,7 @@ static int get_new_segment(struct f2fs_sb_info *sbi, unsigned int old_zoneno = GET_ZONE_FROM_SEG(sbi, *newseg); unsigned int alloc_policy = sbi->allocate_section_policy; unsigned int alloc_hint = sbi->allocate_section_hint; + unsigned int max_secno = MAIN_SECS(sbi); bool init = true; bool looped = false; int i, devi; @@ -2908,7 +2909,7 @@ static int get_new_segment(struct f2fs_sb_info *sbi, */ if (f2fs_sb_has_blkzoned(sbi)) { /* Prioritize writing to conventional zones */ - if (sbi->blkzone_alloc_policy == BLKZONE_ALLOC_PRIOR_CONV || pinning) + if (sbi->blkzone_alloc_policy == BLKZONE_ALLOC_PRIOR_CONV) segno = 0; else segno = max(sbi->first_seq_zone_segno, *newseg); @@ -2924,19 +2925,24 @@ static int get_new_segment(struct f2fs_sb_info *sbi, alloc_hint > MAIN_SECS(sbi)) alloc_hint = MAIN_SECS(sbi); - if (alloc_policy == ALLOCATE_FORWARD_FROM_HINT && - hint < alloc_hint) - hint = alloc_hint; - else if (alloc_policy == ALLOCATE_FORWARD_WITHIN_HINT && - hint >= alloc_hint) + if (pinning) { + max_secno = sbi->pinned_area_max_secno; hint = 0; + } else if (alloc_policy == ALLOCATE_FORWARD_FROM_HINT && + hint < alloc_hint) { + hint = alloc_hint; + } else if (alloc_policy == ALLOCATE_FORWARD_WITHIN_HINT && + hint >= alloc_hint) { + hint = 0; + } find_other_zone: - secno = find_next_zero_bit(free_i->free_secmap, MAIN_SECS(sbi), hint); + secno = find_next_zero_bit(free_i->free_secmap, max_secno, hint); - if (secno >= MAIN_SECS(sbi)) { + if (secno >= max_secno) { if (looped) { - ret = -ENOSPC; + ret = (pinning && has_unpinned_area(sbi)) ? + -EAGAIN : -ENOSPC; f2fs_bug_on(sbi, !pinning); goto out_unlock; } @@ -3001,12 +3007,6 @@ static int get_new_segment(struct f2fs_sb_info *sbi, goto out_unlock; } - /* no free section in conventional device or conventional zone */ - if (new_sec && pinning && - f2fs_is_sequential_zone_area(sbi, START_BLOCK(sbi, segno))) { - ret = -EAGAIN; - goto out_unlock; - } __set_inuse(sbi, segno); *newseg = segno; out_unlock: @@ -3472,8 +3472,9 @@ int f2fs_allocate_pinning_section(struct f2fs_sb_info *sbi) err = f2fs_allocate_new_section(sbi, CURSEG_COLD_DATA_PINNED, false); f2fs_unlock_op(sbi, &lc); - if (f2fs_sb_has_blkzoned(sbi) && err == -EAGAIN && gc_required) { - err = f2fs_gc_range(sbi, 0, sbi->first_seq_zone_segno - 1, + if (has_unpinned_area(sbi) && err == -EAGAIN && gc_required) { + err = f2fs_gc_range(sbi, 0, + sbi->pinned_area_max_secno * SEGS_PER_SEC(sbi) - 1, true, ZONED_PIN_SEC_REQUIRED_COUNT, true); if (err) return err; diff --git a/fs/f2fs/segment.h b/fs/f2fs/segment.h index db1079169a23..5949aa5200ac 100644 --- a/fs/f2fs/segment.h +++ b/fs/f2fs/segment.h @@ -43,6 +43,7 @@ static inline void sanity_check_seg_type(struct f2fs_sb_info *sbi, #define MAIN_SEGS(sbi) (SM_I(sbi)->main_segments) #define MAIN_SECS(sbi) ((sbi)->total_sections) +#define has_unpinned_area(sbi) ((sbi)->pinned_area_max_secno < MAIN_SECS(sbi)) #define TOTAL_SEGS(sbi) \ (SM_I(sbi) ? SM_I(sbi)->segment_count : \ diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c index 3bdb0f891c35..253a579e9d5b 100644 --- a/fs/f2fs/super.c +++ b/fs/f2fs/super.c @@ -235,6 +235,7 @@ enum { Opt_jqfmt, Opt_checkpoint, Opt_lookup_mode, + Opt_resizable_tail_secno, Opt_err, }; @@ -366,6 +367,7 @@ static const struct fs_parameter_spec f2fs_param_specs[] = { fsparam_flag("age_extent_cache", Opt_age_extent_cache), fsparam_enum("errors", Opt_errors, f2fs_param_errors), fsparam_enum("lookup_mode", Opt_lookup_mode, f2fs_param_lookup_mode), + fsparam_u32("resizable_tail_secno", Opt_resizable_tail_secno), {} }; @@ -404,6 +406,7 @@ static match_table_t f2fs_checkpoint_tokens = { #define F2FS_SPEC_errors (1 << 23) #define F2FS_SPEC_lookup_mode (1 << 24) #define F2FS_SPEC_reserve_node (1 << 25) +#define F2FS_SPEC_resizable_tail_secno (1 << 26) struct f2fs_fs_context { struct f2fs_mount_info info; @@ -551,6 +554,17 @@ static inline void adjust_unusable_cap_perc(struct f2fs_sb_info *sbi) F2FS_OPTION(sbi).unusable_cap_perc); } +static inline void adjust_pinned_area_boundary(struct f2fs_sb_info *sbi) +{ + sbi->pinned_area_max_secno = MAIN_SECS(sbi); + if (f2fs_sb_has_blkzoned(sbi) && sbi->first_seq_zone_segno != NULL_SEGNO) + sbi->pinned_area_max_secno = min(sbi->pinned_area_max_secno, + GET_SEC_FROM_SEG(sbi, sbi->first_seq_zone_segno)); + if (F2FS_OPTION(sbi).resizable_tail_secno) + sbi->pinned_area_max_secno = min(sbi->pinned_area_max_secno, + MAIN_SECS(sbi) - F2FS_OPTION(sbi).resizable_tail_secno); +} + static void init_once(void *foo) { struct f2fs_inode_info *fi = (struct f2fs_inode_info *) foo; @@ -1235,6 +1249,10 @@ static int f2fs_parse_param(struct fs_context *fc, struct fs_parameter *param) F2FS_CTX_INFO(ctx).lookup_mode = result.uint_32; ctx->spec_mask |= F2FS_SPEC_lookup_mode; break; + case Opt_resizable_tail_secno: + F2FS_CTX_INFO(ctx).resizable_tail_secno = result.uint_32; + ctx->spec_mask |= F2FS_SPEC_resizable_tail_secno; + break; } return 0; } @@ -1763,6 +1781,9 @@ static void f2fs_apply_options(struct fs_context *fc, struct super_block *sb) F2FS_OPTION(sbi).errors = F2FS_CTX_INFO(ctx).errors; if (ctx->spec_mask & F2FS_SPEC_lookup_mode) F2FS_OPTION(sbi).lookup_mode = F2FS_CTX_INFO(ctx).lookup_mode; + if (ctx->spec_mask & F2FS_SPEC_resizable_tail_secno) + F2FS_OPTION(sbi).resizable_tail_secno = + F2FS_CTX_INFO(ctx).resizable_tail_secno; f2fs_apply_compression(fc, sb); f2fs_apply_test_dummy_encryption(fc, sb); @@ -1771,6 +1792,13 @@ static void f2fs_apply_options(struct fs_context *fc, struct super_block *sb) static int f2fs_sanity_check_options(struct f2fs_sb_info *sbi, bool remount) { + unsigned int total_sections = le32_to_cpu(sbi->raw_super->section_count); + + if (F2FS_OPTION(sbi).resizable_tail_secno >= total_sections) { + f2fs_err(sbi, "Option resizable_tail_secno is larger than or equal to total sections (%u >= %u)", + F2FS_OPTION(sbi).resizable_tail_secno, total_sections); + return -EINVAL; + } if (f2fs_sb_has_device_alias(sbi) && !test_opt(sbi, READ_EXTENT_CACHE)) { f2fs_err(sbi, "device aliasing requires extent cache"); @@ -2544,6 +2572,10 @@ static int f2fs_show_options(struct seq_file *seq, struct dentry *root) else if (F2FS_OPTION(sbi).lookup_mode == LOOKUP_AUTO) seq_show_option(seq, "lookup_mode", "auto"); + if (F2FS_OPTION(sbi).resizable_tail_secno) + seq_printf(seq, ",resizable_tail_secno=%u", + F2FS_OPTION(sbi).resizable_tail_secno); + return 0; } @@ -2586,6 +2618,7 @@ static void default_options(struct f2fs_sb_info *sbi, bool remount) F2FS_OPTION(sbi).bggc_mode = BGGC_MODE_ON; F2FS_OPTION(sbi).memory_mode = MEMORY_MODE_NORMAL; F2FS_OPTION(sbi).errors = MOUNT_ERRORS_CONTINUE; + F2FS_OPTION(sbi).resizable_tail_secno = 0; set_opt(sbi, INLINE_XATTR); set_opt(sbi, INLINE_DATA); @@ -3045,6 +3078,7 @@ static int __f2fs_remount(struct fs_context *fc, struct super_block *sb) sb->s_flags = (sb->s_flags & ~SB_POSIXACL) | (test_opt(sbi, POSIX_ACL) ? SB_POSIXACL : 0); + adjust_pinned_area_boundary(sbi); limit_reserve_root(sbi); fc->sb_flags = (flags & ~SB_LAZYTIME) | (sb->s_flags & SB_LAZYTIME); @@ -5287,6 +5321,8 @@ static int f2fs_fill_super(struct super_block *sb, struct fs_context *fc) /* get segno of first zoned block device */ sbi->first_seq_zone_segno = get_first_seq_zone_segno(sbi); + adjust_pinned_area_boundary(sbi); + sbi->reserved_pin_section = f2fs_sb_has_blkzoned(sbi) ? ZONED_PIN_SEC_REQUIRED_COUNT : GET_SEC_FROM_SEG(sbi, overprovision_segments(sbi)); diff --git a/fs/f2fs/sysfs.c b/fs/f2fs/sysfs.c index 3201e2185fea..811e350a1430 100644 --- a/fs/f2fs/sysfs.c +++ b/fs/f2fs/sysfs.c @@ -1313,6 +1313,7 @@ F2FS_SBI_GENERAL_RW_ATTR(blkzone_alloc_policy); #endif F2FS_SBI_GENERAL_RW_ATTR(carve_out); F2FS_SBI_GENERAL_RW_ATTR(reserved_pin_section); +F2FS_SBI_GENERAL_RO_ATTR(pinned_area_max_secno); F2FS_SBI_GENERAL_RW_ATTR(bggc_io_aware); F2FS_SBI_GENERAL_RW_ATTR(max_lock_elapsed_time); F2FS_SBI_GENERAL_RW_ATTR(lock_duration_priority); @@ -1525,6 +1526,7 @@ static struct attribute *f2fs_attrs[] = { ATTR_LIST(max_read_extent_count), ATTR_LIST(carve_out), ATTR_LIST(reserved_pin_section), + ATTR_LIST(pinned_area_max_secno), ATTR_LIST(allocate_section_hint), ATTR_LIST(allocate_section_policy), ATTR_LIST(max_lock_elapsed_time), From 991c2be78257cba5bf53cf935fe70f8836964288 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Mon, 24 Aug 2026 17:09:01 +0700 Subject: [PATCH 1252/1328] net: ethernet: sun4i-emac: Fix IRQ error handling irq_of_parse_and_map() returns 0 when parsing or mapping an IRQ fails. The current code checks for -ENXIO and therefore does not detect the failure. Check for a zero return value and convert it to -ENXIO. Fixes: 492205050d77 ("net: Add EMAC ethernet driver found on Allwinner A10 SoC's") Signed-off-by: bui duc phuc Reviewed-by: Andre Przywara Link: https://patch.msgid.link/20260824100901.31675-1-phucduc.bui@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/allwinner/sun4i-emac.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/allwinner/sun4i-emac.c b/drivers/net/ethernet/allwinner/sun4i-emac.c index 42174249ef61..942454e29488 100644 --- a/drivers/net/ethernet/allwinner/sun4i-emac.c +++ b/drivers/net/ethernet/allwinner/sun4i-emac.c @@ -996,9 +996,9 @@ static int emac_probe(struct platform_device *pdev) /* fill in parameters for net-dev structure */ ndev->base_addr = (unsigned long)db->membase; ndev->irq = irq_of_parse_and_map(np, 0); - if (ndev->irq == -ENXIO) { + if (!ndev->irq) { netdev_err(ndev, "No irq resource\n"); - ret = ndev->irq; + ret = -ENXIO; goto out_iounmap; } From 82aeed2400786bd3f79d88cb8b8f42e6127e5923 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Tue, 25 Aug 2026 03:50:10 -0700 Subject: [PATCH 1253/1328] net: fix spurious TX timeout after dev_activate() While debugging another issue today, I found out that my TX queue is reported as stopped for 4294907392 ms (49.7 days), on a machine that had been up for four minutes. bnxt_en 0002:01:00.0 eth0: NETDEV WATCHDOG: CPU: 28: transmit queue 23 timed out 4294907392 ms 4294907392 is not an elapsed time. It is the value of jiffies at that moment: INITIAL_JIFFIES is 4294667296, which leaves jiffies 59 seconds short of wrapping. dev_activate() runs transition_one_qdisc() over every TX queue, which resets trans_start to 0, and then stamps only queue 0 through netif_trans_update(). Stamp jiffies instead. A queue stopped across dev_activate() now gets a full watchdog_timeo of grace, and is still reported if it is stopped that long. Fixes: 9b36627acecd ("net: remove dev->trans_start") Cc: stable@vger.kernel.org Signed-off-by: Breno Leitao Reviewed-by: Nicolai Buchwitz Reviewed-by: Jason Xing Link: https://patch.msgid.link/20260825-trans_start-v2-1-286b4d6d70cb@debian.org Signed-off-by: Paolo Abeni --- net/sched/sch_generic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/sched/sch_generic.c b/net/sched/sch_generic.c index ef2b4bf51564..4539dc2c6d38 100644 --- a/net/sched/sch_generic.c +++ b/net/sched/sch_generic.c @@ -1278,7 +1278,7 @@ static void transition_one_qdisc(struct net_device *dev, rcu_assign_pointer(dev_queue->qdisc, new_qdisc); if (need_watchdog_p) { - WRITE_ONCE(dev_queue->trans_start, 0); + WRITE_ONCE(dev_queue->trans_start, jiffies); *need_watchdog_p = 1; } } From cec261b0b4c5c0b044165303198d10ffcdf3414c Mon Sep 17 00:00:00 2001 From: Yifei Chu Date: Mon, 24 Aug 2026 10:27:19 +0800 Subject: [PATCH 1254/1328] net/smc: release the internal TCP sock on IPPROTO_SMC socket creation failure IPPROTO_SMC sockets create an internal TCP sock ("clcsock") from the proto->init hook. When socket creation fails after proto->init has run - e.g. a cgroup BPF program attached to BPF_CGROUP_INET_SOCK_CREATE denies the socket - sk_common_release() only invokes sk_prot->destroy if it is set, but neither smc_inet_prot nor smc_inet6_prot defines it, and smc_destruct() returns early unless sk_state is SMC_CLOSED. As a result, every failing socket(AF_INET, SOCK_STREAM, IPPROTO_SMC) call leaks one tcp_sock, so an unprivileged task able to attach a deny-all BPF_CGROUP_INET_SOCK_CREATE program to its own cgroup can grow kernel memory unboundedly. Add a .destroy hook to both protos that releases the clcsock via smc_clcsock_release(). smc_sk_init() hashes the sock into the smc hashinfo before the clcsock is created, and smc_diag dumps walk that hash dereferencing smc->clcsock without taking clcsock_release_lock, while sk_common_release() calls .destroy before .unhash. Unhash the sock before releasing the clcsock, as __smc_release() does, so a concurrent dump cannot observe the release; the second unhash in sk_common_release() is a no-op. Fixes: d25a92ccae6b ("net/smc: Introduce IPPROTO_SMC") Reported-by: Abaci Assisted-by: abaci:qwen3.8-max Signed-off-by: Yifei Chu Reviewed-by: Dust Li Link: https://patch.msgid.link/178753843966.342810.566471390946765094@linux.alibaba.com Signed-off-by: Paolo Abeni --- net/smc/smc_inet.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/net/smc/smc_inet.c b/net/smc/smc_inet.c index a94084b4a498..520b666fdd8f 100644 --- a/net/smc/smc_inet.c +++ b/net/smc/smc_inet.c @@ -15,13 +15,16 @@ #include "smc_inet.h" #include "smc.h" +#include "smc_close.h" static int smc_inet_init_sock(struct sock *sk); +static void smc_inet_destroy_sock(struct sock *sk); static struct proto smc_inet_prot = { .name = "INET_SMC", .owner = THIS_MODULE, .init = smc_inet_init_sock, + .destroy = smc_inet_destroy_sock, .hash = smc_hash_sk, .unhash = smc_unhash_sk, .release_cb = smc_release_cb, @@ -68,6 +71,7 @@ static struct proto smc_inet6_prot = { .name = "INET6_SMC", .owner = THIS_MODULE, .init = smc_inet_init_sock, + .destroy = smc_inet_destroy_sock, .hash = smc_hash_sk, .unhash = smc_unhash_sk, .release_cb = smc_release_cb, @@ -116,6 +120,18 @@ static int smc_inet_init_sock(struct sock *sk) return smc_create_clcsk(net, sk, sk->sk_family); } +static void smc_inet_destroy_sock(struct sock *sk) +{ + /* The sock is hashed and smc_diag dumps dereference smc->clcsock + * without clcsock_release_lock, while sk_common_release() calls + * .destroy before .unhash. Unhash first, as __smc_release() does, + * so no dump can observe the clcsock being released; the second + * unhash is a no-op. + */ + sk->sk_prot->unhash(sk); + smc_clcsock_release(smc_sk(sk)); +} + int __init smc_inet_init(void) { int rc; From 00eeab0c644a881a1dc86fbffb7e6047a6ce8ecd Mon Sep 17 00:00:00 2001 From: Joe Damato Date: Tue, 25 Aug 2026 17:02:33 -0700 Subject: [PATCH 1255/1328] bnxt_en: Write doorbell when linearizing skb fails When the driver is handed a burst of packets, the doorbell is deferred until the end. If the last packet has a huge number of frags, but fails to linearize, the doorbell will not be written adding latency on TX for any packets in the ring and holding their DMA mappings until the next TX. Note that the queue is not stopped, so this issue would delay pending BDs until the next TX. This issue was discovered by Sashiko and reading the code verifies that, while unlikely, it is possible. Fix this by jumping to tx_free, which replicates the same pre-existing logic but also writes the doorbell. Fixes: b91e82129400 ("bnxt_en: Linearize TX SKB if the fragments exceed the max") Cc: stable@vger.kernel.org Signed-off-by: Joe Damato Reviewed-by: Michael Chan Reviewed-by: Andy Gospodarek Link: https://patch.msgid.link/20260826000234.2031564-1-joe@dama.to Signed-off-by: Paolo Abeni --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index 9c2cc50276a5..d59bcca73a2b 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -502,11 +502,8 @@ static netdev_tx_t bnxt_start_xmit(struct sk_buff *skb, struct net_device *dev) if (skb_shinfo(skb)->nr_frags > TX_MAX_FRAGS) { netdev_warn_once(dev, "SKB has too many (%d) fragments, max supported is %d. SKB will be linearized.\n", skb_shinfo(skb)->nr_frags, TX_MAX_FRAGS); - if (skb_linearize(skb)) { - dev_kfree_skb_any(skb); - dev_core_stats_tx_dropped_inc(dev); - return NETDEV_TX_OK; - } + if (skb_linearize(skb)) + goto tx_free; } #endif if (skb_is_gso(skb) && From 03b4702fc5e311cbac9ba8654021a88f0dac914c Mon Sep 17 00:00:00 2001 From: Vitaliy Sochnev Date: Sun, 23 Aug 2026 14:06:36 +0100 Subject: [PATCH 1256/1328] net: phy: air_en8811h: move LED GPIO configuration to config_init The LED GPIO pins (GPIO3/4/5, mapped to LED2/LED1/LED0) are only ever configured as outputs once, in .probe(). But .config_init() restarts the MD32 MCU via en8811h_restart_mcu() on every call after the first (priv->mcu_needs_restart), and that restart resets buckpbus-mapped MCU state, including EN8811H_GPIO_OUTPUT. As a result the LED GPIOs fall back to inputs after the first event that re-triggers .config_init() (link renegotiation, ifdown/ifup, resume), and the PHY's LEDs stop reflecting link/activity state even though they worked right after probe. Move the GPIO-as-output configuration from .probe() to the end of .config_init(), so it is reapplied every time the MCU may have been restarted. Fixes: 71e79430117d ("net: phy: air_en8811h: Add the Airoha EN8811H PHY driver") Suggested-by: Mikhail Zhilkin Signed-off-by: Vitaliy Sochnev Link: https://patch.msgid.link/20260823130638.1166453-2-sochnev.v.74@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/phy/air_en8811h.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/drivers/net/phy/air_en8811h.c b/drivers/net/phy/air_en8811h.c index edd49c193e47..0eeb7b9a4e26 100644 --- a/drivers/net/phy/air_en8811h.c +++ b/drivers/net/phy/air_en8811h.c @@ -1173,13 +1173,6 @@ static int en8811h_probe(struct phy_device *phydev) if (ret) return ret; - /* Configure led gpio pins as output */ - ret = air_phy_buckpbus_reg_modify(phydev, EN8811H_GPIO_OUTPUT, - EN8811H_GPIO_OUTPUT_345, - EN8811H_GPIO_OUTPUT_345); - if (ret < 0) - return ret; - return 0; } @@ -1324,6 +1317,17 @@ static int en8811h_config_init(struct phy_device *phydev) return ret; } + /* Configure led gpio pins as output. Must be redone on every + * .config_init(), not just once in .probe(): en8811h_restart_mcu() + * resets buckpbus-mapped MCU state (incl. this register) on every + * call after the first, e.g. on link renegotiation or ifup/ifdown. + */ + ret = air_phy_buckpbus_reg_modify(phydev, EN8811H_GPIO_OUTPUT, + EN8811H_GPIO_OUTPUT_345, + EN8811H_GPIO_OUTPUT_345); + if (ret < 0) + return ret; + return 0; } From 94ad9e114a1c7b16ea418c1456ac3835e038ab3f Mon Sep 17 00:00:00 2001 From: Rastislav Szabo Date: Sat, 22 Aug 2026 22:11:18 +0200 Subject: [PATCH 1257/1328] net/tcp: fix TCP-AO key deletion in VRFs TCP-AO keys with TCP_AO_KEYF_IFINDEX store the VRF L3 interface index in l3index. tcp_ao_del_cmd() validates the supplied ifindex, but does not assign it to its local l3index before matching keys. As a result, deleting a key scoped to a non-default VRF always fails with ENOENT because it is matched against l3index 0. Fixes: 248411b8cb89 ("net/tcp: Wire up l3index to TCP-AO") Cc: stable@vger.kernel.org Signed-off-by: Rastislav Szabo Reviewed-by: David Ahern Acked-by: Dmitry Safonov <0x7f454c46@gmail.com> Link: https://patch.msgid.link/20260822201119.272269-1-rastislav.szabo@isovalent.com Signed-off-by: Paolo Abeni --- net/ipv4/tcp_ao.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/ipv4/tcp_ao.c b/net/ipv4/tcp_ao.c index 5094267447ae..26864621e6de 100644 --- a/net/ipv4/tcp_ao.c +++ b/net/ipv4/tcp_ao.c @@ -1834,6 +1834,9 @@ static int tcp_ao_del_cmd(struct sock *sk, unsigned short int family, if (cmd.ifindex && !(cmd.keyflags & TCP_AO_KEYF_IFINDEX)) return -EINVAL; + if (cmd.keyflags & TCP_AO_KEYF_IFINDEX) + l3index = cmd.ifindex; + ao_info = setsockopt_ao_info(sk); if (IS_ERR(ao_info)) return PTR_ERR(ao_info); From 687e5b48c4f59c4b323c93fbe77918c2c440fe4a Mon Sep 17 00:00:00 2001 From: Rastislav Szabo Date: Sat, 22 Aug 2026 22:11:19 +0200 Subject: [PATCH 1258/1328] selftests: net: tcp_ao: test VRF-scoped key deletion Verify that TCP_AO_DEL_KEY can remove a TCP-AO key scoped to a VRF. Assisted-by: Codex:GPT-5 Signed-off-by: Rastislav Szabo Reviewed-by: David Ahern Acked-by: Dmitry Safonov Link: https://patch.msgid.link/20260822201119.272269-2-rastislav.szabo@isovalent.com Signed-off-by: Paolo Abeni --- .../selftests/net/tcp_ao/key-management.c | 67 ++++++++++++------- .../testing/selftests/net/tcp_ao/lib/aolib.h | 6 +- tools/testing/selftests/net/tcp_ao/lib/sock.c | 4 +- 3 files changed, 51 insertions(+), 26 deletions(-) diff --git a/tools/testing/selftests/net/tcp_ao/key-management.c b/tools/testing/selftests/net/tcp_ao/key-management.c index d86bb380b79f..0451f92f4645 100644 --- a/tools/testing/selftests/net/tcp_ao/key-management.c +++ b/tools/testing/selftests/net/tcp_ao/key-management.c @@ -63,8 +63,8 @@ static int prepare_lsk(union tcp_addr *addr, uint8_t sndid, uint8_t rcvid) return sk; } -static int test_del_key(int sk, uint8_t sndid, uint8_t rcvid, bool async, - int current_key, int rnext_key) +static int test_del_key(int sk, uint8_t sndid, uint8_t rcvid, int ifindex, + bool async, int current_key, int rnext_key) { struct tcp_ao_info_opt ao_info = {}; struct tcp_ao_getsockopt key = {}; @@ -76,6 +76,10 @@ static int test_del_key(int sk, uint8_t sndid, uint8_t rcvid, bool async, del.prefix = DEFAULT_TEST_PREFIX; del.sndid = sndid; del.rcvid = rcvid; + if (ifindex) { + del.keyflags = TCP_AO_KEYF_IFINDEX; + del.ifindex = ifindex; + } if (current_key >= 0) { del.set_current = 1; @@ -95,7 +99,8 @@ static int test_del_key(int sk, uint8_t sndid, uint8_t rcvid, bool async, tcp_addr_to_sockaddr_in(&sockaddr, &this_ip_dest, 0); err = test_get_one_ao(sk, &key, &sockaddr, sizeof(sockaddr), - DEFAULT_TEST_PREFIX, sndid, rcvid); + DEFAULT_TEST_PREFIX, sndid, rcvid, + del.keyflags, del.ifindex); if (!err) return -EEXIST; if (err != -E2BIG) @@ -112,12 +117,12 @@ static int test_del_key(int sk, uint8_t sndid, uint8_t rcvid, bool async, } static void try_delete_key(char *tst_name, int sk, uint8_t sndid, uint8_t rcvid, - bool async, int current_key, int rnext_key, + int ifindex, bool async, int current_key, int rnext_key, fault_t inj) { int err; - err = test_del_key(sk, sndid, rcvid, async, current_key, rnext_key); + err = test_del_key(sk, sndid, rcvid, ifindex, async, current_key, rnext_key); if ((err == -EBUSY && fault(BUSY)) || (err == -EINVAL && fault(CURRNEXT))) { test_ok("%s: key deletion was prevented", tst_name); return; @@ -236,15 +241,15 @@ static void check_closed_socket(void) int sk; sk = prepare_sk(&this_ip_dest, 200, 200); - try_delete_key("closed socket, delete a key", sk, 200, 200, 0, -1, -1, 0); - try_delete_key("closed socket, delete all keys", sk, 100, 100, 0, -1, -1, 0); + try_delete_key("closed socket, delete a key", sk, 200, 200, 0, 0, -1, -1, 0); + try_delete_key("closed socket, delete all keys", sk, 100, 100, 0, 0, -1, -1, 0); close(sk); sk = prepare_sk(&this_ip_dest, 200, 200); if (test_set_key(sk, 100, 200)) test_error("failed to set current/rnext keys"); - try_delete_key("closed socket, delete current key", sk, 100, 100, 0, -1, -1, FAULT_BUSY); - try_delete_key("closed socket, delete rnext key", sk, 200, 200, 0, -1, -1, FAULT_BUSY); + try_delete_key("closed socket, delete current key", sk, 100, 100, 0, 0, -1, -1, FAULT_BUSY); + try_delete_key("closed socket, delete rnext key", sk, 200, 200, 0, 0, -1, -1, FAULT_BUSY); close(sk); sk = prepare_sk(&this_ip_dest, 200, 200); @@ -254,10 +259,12 @@ static void check_closed_socket(void) if (test_add_key(sk, "Glory to Ukraine!", this_ip_dest, DEFAULT_TEST_PREFIX, 12, 13)) test_error("test_add_key()"); - try_delete_key("closed socket, delete a key + set current/rnext", sk, 100, 100, 0, 10, 13, 0); - try_delete_key("closed socket, force-delete current key", sk, 10, 11, 0, 200, -1, 0); - try_delete_key("closed socket, force-delete rnext key", sk, 12, 13, 0, -1, 200, 0); - try_delete_key("closed socket, delete current+rnext key", sk, 200, 200, 0, -1, -1, FAULT_BUSY); + try_delete_key("closed socket, delete a key + set current/rnext", sk, + 100, 100, 0, 0, 10, 13, 0); + try_delete_key("closed socket, force-delete current key", sk, 10, 11, 0, 0, 200, -1, 0); + try_delete_key("closed socket, force-delete rnext key", sk, 12, 13, 0, 0, -1, 200, 0); + try_delete_key("closed socket, delete current+rnext key", sk, + 200, 200, 0, 0, -1, -1, FAULT_BUSY); close(sk); sk = prepare_sk(&this_ip_dest, 200, 200); @@ -272,6 +279,18 @@ static void check_closed_socket(void) this_ip_dest, DEFAULT_TEST_PREFIX, false, true, 20, 10, 0); close(sk); + + if (!should_skip_test("closed socket, add + delete VRF-scoped key", + KCONFIG_NET_VRF)) { + sk = prepare_sk(&this_ip_dest, 200, 200); + if (test_add_key_vrf(sk, SECOND_PASSWORD, TCP_AO_KEYF_IFINDEX, + this_ip_dest, DEFAULT_TEST_PREFIX, + test_vrf_ifindex, 201, 201)) + test_error("test_add_key_vrf()"); + try_delete_key("closed socket, add + delete VRF-scoped key", sk, 201, 201, + test_vrf_ifindex, 0, -1, -1, 0); + close(sk); + } } static void assert_no_current_rnext(const char *tst_msg, int sk) @@ -322,8 +341,8 @@ static void check_listen_socket(void) int sk, err; sk = prepare_lsk(&this_ip_dest, 200, 200); - try_delete_key("listen socket, delete a key", sk, 200, 200, 0, -1, -1, 0); - try_delete_key("listen socket, delete all keys", sk, 100, 100, 0, -1, -1, 0); + try_delete_key("listen socket, delete a key", sk, 200, 200, 0, 0, -1, -1, 0); + try_delete_key("listen socket, delete all keys", sk, 100, 100, 0, 0, -1, -1, 0); close(sk); sk = prepare_lsk(&this_ip_dest, 200, 200); @@ -345,8 +364,10 @@ static void check_listen_socket(void) if (listen(sk, 10)) test_error("listen()"); assert_no_current_rnext("listen() after current/rnext keys set", sk); - try_delete_key("listen socket, delete current key from before listen()", sk, 100, 100, 0, -1, -1, FAULT_FIXME); - try_delete_key("listen socket, delete rnext key from before listen()", sk, 200, 200, 0, -1, -1, FAULT_FIXME); + try_delete_key("listen socket, delete current key from before listen()", sk, + 100, 100, 0, 0, -1, -1, FAULT_FIXME); + try_delete_key("listen socket, delete rnext key from before listen()", sk, + 200, 200, 0, 0, -1, -1, FAULT_FIXME); close(sk); assert_no_tcp_repair(); @@ -359,13 +380,13 @@ static void check_listen_socket(void) DEFAULT_TEST_PREFIX, 12, 13)) test_error("test_add_key()"); try_delete_key("listen socket, delete a key + set current/rnext", sk, - 100, 100, 0, 10, 13, FAULT_CURRNEXT); + 100, 100, 0, 0, 10, 13, FAULT_CURRNEXT); try_delete_key("listen socket, force-delete current key", sk, - 10, 11, 0, 200, -1, FAULT_CURRNEXT); + 10, 11, 0, 0, 200, -1, FAULT_CURRNEXT); try_delete_key("listen socket, force-delete rnext key", sk, - 12, 13, 0, -1, 200, FAULT_CURRNEXT); + 12, 13, 0, 0, -1, 200, FAULT_CURRNEXT); try_delete_key("listen socket, delete a key", sk, - 200, 200, 0, -1, -1, 0); + 200, 200, 0, 0, -1, -1, 0); close(sk); sk = prepare_lsk(&this_ip_dest, 200, 200); @@ -1131,7 +1152,6 @@ static void check_established_socket(void) { unsigned int port = test_server_port; - setup_vrfs(); try_client_run("client: Check current/rnext keys unset before connect()", port++, 20, -1, -1); try_client_run("client: Check current/rnext keys set before connect()", @@ -1150,6 +1170,7 @@ static void *client_fn(void *arg) { if (inet_pton(TEST_FAMILY, TEST_WRONG_IP, &wrong_addr) != 1) test_error("Can't convert ip address %s", TEST_WRONG_IP); + setup_vrfs(); check_closed_socket(); check_listen_socket(); check_established_socket(); @@ -1158,6 +1179,6 @@ static void *client_fn(void *arg) int main(int argc, char *argv[]) { - test_init(121, server_fn, client_fn); + test_init(122, server_fn, client_fn); return 0; } diff --git a/tools/testing/selftests/net/tcp_ao/lib/aolib.h b/tools/testing/selftests/net/tcp_ao/lib/aolib.h index ebb2899c12fe..53be1744237e 100644 --- a/tools/testing/selftests/net/tcp_ao/lib/aolib.h +++ b/tools/testing/selftests/net/tcp_ao/lib/aolib.h @@ -404,7 +404,8 @@ static inline int test_prepare_def_key(struct tcp_ao_add *ao, extern int test_get_one_ao(int sk, struct tcp_ao_getsockopt *out, void *addr, size_t addr_sz, - uint8_t prefix, uint8_t sndid, uint8_t rcvid); + uint8_t prefix, uint8_t sndid, uint8_t rcvid, + uint8_t keyflags, int ifindex); extern int test_get_ao_info(int sk, struct tcp_ao_info_opt *out); extern int test_set_ao_info(int sk, struct tcp_ao_info_opt *in); extern int test_cmp_getsockopt_setsockopt(const struct tcp_ao_add *a, @@ -418,7 +419,8 @@ static inline int test_verify_socket_key(int sk, struct tcp_ao_add *key) int err; err = test_get_one_ao(sk, &key2, &key->addr, sizeof(key->addr), - key->prefix, key->sndid, key->rcvid); + key->prefix, key->sndid, key->rcvid, + key->keyflags, key->ifindex); if (err) return err; diff --git a/tools/testing/selftests/net/tcp_ao/lib/sock.c b/tools/testing/selftests/net/tcp_ao/lib/sock.c index ef8e9031d47a..2e7b06a1a156 100644 --- a/tools/testing/selftests/net/tcp_ao/lib/sock.c +++ b/tools/testing/selftests/net/tcp_ao/lib/sock.c @@ -252,7 +252,7 @@ static int test_get_ao_keys_nr(int sk) int test_get_one_ao(int sk, struct tcp_ao_getsockopt *out, void *addr, size_t addr_sz, uint8_t prefix, - uint8_t sndid, uint8_t rcvid) + uint8_t sndid, uint8_t rcvid, uint8_t keyflags, int ifindex) { struct tcp_ao_getsockopt tmp = {}; socklen_t tmp_sz = sizeof(tmp); @@ -262,6 +262,8 @@ int test_get_one_ao(int sk, struct tcp_ao_getsockopt *out, tmp.prefix = prefix; tmp.sndid = sndid; tmp.rcvid = rcvid; + tmp.keyflags = keyflags; + tmp.ifindex = ifindex; tmp.nkeys = 1; ret = getsockopt(sk, IPPROTO_TCP, TCP_AO_GET_KEYS, &tmp, &tmp_sz); From ea30dc5267e367b8a5e1e06cc074f813bcbf18b2 Mon Sep 17 00:00:00 2001 From: Qing Ming Date: Tue, 25 Aug 2026 15:20:33 +0800 Subject: [PATCH 1259/1328] tcp: fix AO info use-after-free in tcp_ao_connect_init() tcp_v4_connect() adds a SYN-SENT socket to the ehash before calling tcp_connect(). If TCP-AO is configured, tcp_connect() first verifies that a key matches the peer and the bound device's current L3 master. tcp_ao_connect_init() later resolves the L3 master again and removes keys which do not match it. The socket lock does not stabilize the bound device's VRF membership. Detaching the device from its VRF between the initial validation and the L3-master calculation in tcp_ao_connect_init() can therefore make the validation succeed while initialization observes the default L3 domain and removes the only key. The subsequent AO lookup then fails, so the no-key path clears tp->ao_info and frees it directly. The receive path can find the socket in the ehash and load tp->ao_info under RCU before acquiring the socket lock. A reader which loaded the old pointer can thus continue into tcp_inbound_ao_hash() after the direct free. The issue was found during a static audit of TCP-AO object lifetime. An unprivileged reproducer in self-created user and network namespaces raced connect() with detaching a veth from its VRF while sending TCP-AO segments. It triggered the same KASAN report on two fresh boots: BUG: KASAN: slab-use-after-free in tcp_inbound_ao_hash+0x585/0x19f0 Write of size 8 at addr ffff88800bf88128 by task tcp_ao_vrf_race/232 Call Trace: tcp_inbound_ao_hash+0x585/0x19f0 tcp_inbound_hash+0x677/0xa80 tcp_v4_rcv+0x1c3e/0x3ab0 Allocated by task 235: tcp_ao_alloc_info+0x43/0xf0 tcp_ao_add_cmd+0xdf7/0x13b0 do_tcp_setsockopt+0x168c/0x2640 Freed by task 235: kfree+0x1b8/0x550 tcp_connect+0x252/0x4f00 tcp_v4_connect+0x1114/0x1720 The bad address is 40 bytes inside the freed 128-byte object, matching the tcp_ao_info counters.key_not_found field. The two runs used 1000 attempts each, reached the no-key path 366 and 411 times, and produced one and two KASAN reports respectively. With this change, the same reproducer reached the no-key path 366 times in 1000 attempts without a KASAN report or oops. Use tcp_ao_destroy_sock() for the no-key path. It unpublishes the AO info, updates the socket memory and static-key accounting, and defers the free until after an RCU grace period. Also drop the WARN_ON_ONCE() and its stale comment. The VRF detach race makes the no-key state reachable during normal operation, so it is a handled condition rather than an impossible assertion. On panic_on_warn kernels the WARN would turn this handled race into a kernel panic. Fixes: 248411b8cb89 ("net/tcp: Wire up l3index to TCP-AO") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5 Signed-off-by: Qing Ming Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260825072033.6921-1-a0yami@mailbox.org Signed-off-by: Paolo Abeni --- net/ipv4/tcp_ao.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/net/ipv4/tcp_ao.c b/net/ipv4/tcp_ao.c index 26864621e6de..bb7bbc20ba3f 100644 --- a/net/ipv4/tcp_ao.c +++ b/net/ipv4/tcp_ao.c @@ -1196,12 +1196,7 @@ void tcp_ao_connect_init(struct sock *sk) ao_info->lisn = htonl(tp->write_seq); ao_info->snd_sne = 0; } else { - /* Can't happen: tcp_connect() verifies that there's - * at least one tcp-ao key that matches the remote peer. - */ - WARN_ON_ONCE(1); - rcu_assign_pointer(tp->ao_info, NULL); - kfree(ao_info); + tcp_ao_destroy_sock(sk, false); } } From 63c885688f38a757947d7050b1ee4171215269ce Mon Sep 17 00:00:00 2001 From: Jiawen Wu Date: Mon, 24 Aug 2026 15:21:19 +0800 Subject: [PATCH 1260/1328] net: wangxun: use BIT_ULL() to prevent shift overflow on 32-bit archs The macros TXGBE_INTR_MISC() and WX_INTR_Q() rely on the standard BIT() macro to generate interrupt masks based on the queue vector index. On 32-bit architectures, BIT() evaluates to a 32-bit `unsigned long`. Since the number of queue vectors can be up to 63 on txgbe devices, performing a left shift of 32 or more results in an integer overflow and undefined behavior. This causes incorrect interrupt masking and unmasking logic for both the queue and miscellaneous interrupts on 32-bit systems. Fix this by replacing BIT() with BIT_ULL() in these macros. This ensures that the bitwise shift is always performed safely on a 64-bit `unsigned long long` type, regardless of the underlying architecture. Fixes: e37546ad1f9b ("net: wangxun: revert the adjustment of the IRQ vector sequence") Signed-off-by: Jiawen Wu Reviewed-by: Aleksandr Loktionov Link: https://patch.msgid.link/45F5565CE6AC4329+20260824072119.48399-1-jiawenwu@trustnetic.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/wangxun/libwx/wx_type.h | 2 +- drivers/net/ethernet/wangxun/txgbe/txgbe_type.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/wangxun/libwx/wx_type.h b/drivers/net/ethernet/wangxun/libwx/wx_type.h index 2eba5ab59925..9454e90258d8 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_type.h +++ b/drivers/net/ethernet/wangxun/libwx/wx_type.h @@ -1446,7 +1446,7 @@ struct wx { }; #define WX_INTR_ALL (~0ULL) -#define WX_INTR_Q(i) BIT((i)) +#define WX_INTR_Q(i) BIT_ULL((i)) /* register operations */ #define wr32(a, reg, value) writel((value), ((a)->hw_addr + (reg))) diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_type.h b/drivers/net/ethernet/wangxun/txgbe/txgbe_type.h index 3e93a3f309c1..757d1ce8d3ad 100644 --- a/drivers/net/ethernet/wangxun/txgbe/txgbe_type.h +++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_type.h @@ -303,7 +303,7 @@ struct txgbe_fdir_filter { #define TXGBE_DEFAULT_RX_WORK 128 #endif -#define TXGBE_INTR_MISC(A) BIT((A)->num_q_vectors) +#define TXGBE_INTR_MISC(A) BIT_ULL((A)->num_q_vectors) #define TXGBE_INTR_QALL(A) (TXGBE_INTR_MISC(A) - 1) #define TXGBE_MAX_EITR GENMASK(11, 3) From 9a56a27e6002e29a6707dc4238d469ec84c3a68e Mon Sep 17 00:00:00 2001 From: Maxime Chevallier Date: Tue, 25 Aug 2026 23:17:46 +0200 Subject: [PATCH 1261/1328] net: stmmac: selftests: Pass the IP proto mask in the TC selftest The stmmac TC filtering rules have recently gained sanity checks to make sure the passed keys and their respective masks are aligned with the HW filtering abilities. The stmmac selftests failed to pass the mask in the match data for L4 filtering tests, and are now failing consistently with -EINVAL : $ ethtool -t eth1 [...] 23. L4 DA TCP Filtering -22 24. L4 SA TCP Filtering -22 25. L4 DA UDP Filtering -22 26. L4 SA UDP Filtering -22 Let's pass the ip_proto mask in the l4 filtering tests match data. Found on imx8mp, which now have passing L4 tests : $ ethtool -t eth1 [...] 23. L4 DA TCP Filtering 0 24. L4 SA TCP Filtering 0 25. L4 DA UDP Filtering 0 26. L4 SA UDP Filtering 0 While at it, initialize the masks and keys to avoid re-using whatever was on the stack. Fixes: 5536d7c84363 ("net: stmmac: fix l3l4 filter rejecting unsupported offload requests") Reviewed-by: Andrew Lunn Signed-off-by: Maxime Chevallier Link: https://patch.msgid.link/20260825211748.360935-1-maxime.chevallier@bootlin.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c index 1df26c217f9a..a2b917dd60e5 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c @@ -1432,11 +1432,11 @@ static int __stmmac_test_l4filt(struct stmmac_priv *priv, u32 dst, u32 src, struct { struct flow_dissector_key_basic bkey; struct flow_dissector_key_ports key; - } __aligned(BITS_PER_LONG / 8) keys; + } __aligned(BITS_PER_LONG / 8) keys = { }; struct { struct flow_dissector_key_basic bmask; struct flow_dissector_key_ports mask; - } __aligned(BITS_PER_LONG / 8) masks; + } __aligned(BITS_PER_LONG / 8) masks = { }; unsigned long dummy_cookie = 0xdeadbeef; struct stmmac_packet_attrs attr = { }; struct flow_dissector *dissector; @@ -1489,6 +1489,8 @@ static int __stmmac_test_l4filt(struct stmmac_priv *priv, u32 dst, u32 src, keys.bkey.ip_proto = udp ? IPPROTO_UDP : IPPROTO_TCP; keys.key.src = htons(src); keys.key.dst = htons(dst); + /* Match the full IP proto field */ + masks.bmask.ip_proto = 0xff; masks.mask.src = src_mask; masks.mask.dst = dst_mask; From 2c4e7c42d77e78ad595dbb9e4b5886b58b45d89d Mon Sep 17 00:00:00 2001 From: Aleksandr Khromov Date: Mon, 24 Aug 2026 13:05:47 +0300 Subject: [PATCH 1262/1328] slip: fix use-after-free in sl_sync() slip_devs[] stores bare net_device pointers and takes no reference on them. sl_sync() and sl_alloc() walk that table from slip_open() under rtnl_lock(), while an entry is dropped by sl_free_netdev(), which sl_setup() installs as dev->priv_destructor. priv_destructor is called from netdev_run_todo(), which deliberately runs with the RTNL semaphore released so that it can sleep while waiting for the device refcount to drop: /* Snapshot list, allow later requests */ list_replace_init(&net_todo_list, &list); __rtnl_unlock(); ... if (dev->priv_destructor) dev->priv_destructor(dev); /* slip_devs[i] = NULL */ if (dev->needs_free_netdev) free_netdev(dev); ... /* Free network device */ kobject_put(&dev->dev.kobj); So rtnl_lock() does not serialise slip_open() against the teardown at all. sl_sync() can load slip_devs[i] while the entry is still published and dereference it after netdev_run_todo() has run the destructor and released the device: CPU0 (slip_open) CPU1 (slip_close) unregister_netdev() rtnl_unlock() netdev_run_todo() __rtnl_unlock() rtnl_lock() sl_sync() dev = slip_devs[i] priv_destructor(dev) slip_devs[i] = NULL kobject_put(&dev->dev.kobj) /* dev is freed */ sl = netdev_priv(dev) if (sl->tty || sl->leased) /* use-after-free */ BUG: KASAN: use-after-free in sl_sync drivers/net/slip/slip.c:730 [inline] BUG: KASAN: use-after-free in slip_open+0xef4/0x1210 drivers/net/slip/slip.c:806 Read of size 1 at addr ffff8880712dac71 by task syz-executor.2/6506 CPU: 2 PID: 6506 Comm: syz-executor.2 Not tainted 6.1.134-syzkaller-00260-g0c8fc3469765 #0 Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.12.0-1 04/01/2014 Call Trace: sl_sync drivers/net/slip/slip.c:730 [inline] slip_open+0xef4/0x1210 drivers/net/slip/slip.c:806 tty_ldisc_open+0xa2/0x120 drivers/tty/tty_ldisc.c:433 tty_set_ldisc+0x324/0x720 drivers/tty/tty_ldisc.c:564 tiocsetd drivers/tty/tty_io.c:2428 [inline] tty_ioctl+0x5f0/0x1530 drivers/tty/tty_io.c:2712 Allocated by task 6502: alloc_netdev_mqs+0x98/0xfe0 net/core/dev.c:10719 sl_alloc drivers/net/slip/slip.c:756 [inline] slip_open+0x36d/0x1210 drivers/net/slip/slip.c:817 tty_ldisc_open+0xa2/0x120 drivers/tty/tty_ldisc.c:433 tty_set_ldisc+0x324/0x720 drivers/tty/tty_ldisc.c:564 Freed by task 6497: device_release+0xa2/0x240 drivers/base/core.c:2507 kobject_put+0x179/0x280 lib/kobject.c:729 netdev_run_todo+0x6c8/0xef0 net/core/dev.c:10509 slip_close+0x166/0x1c0 drivers/net/slip/slip.c:906 tty_ldisc_close+0x113/0x1a0 drivers/tty/tty_ldisc.c:456 tty_ldisc_kill+0x94/0x160 drivers/tty/tty_ldisc.c:614 tty_ldisc_release+0xe3/0x2b0 drivers/tty/tty_ldisc.c:782 tty_release+0xbcc/0xe70 drivers/tty/tty_io.c:1860 Commit e58c19124189 ("slip: Fix use-after-free Read in slip_open") fixed a different source of stale entries - a device left in slip_devs[] after slip_open() freed it on the registration error path - and does not address this race, which is why the report survives it. Drop the entry from ndo_uninit instead. unregister_netdevice() calls ndo_uninit under RTNL, before the device is queued to netdev_run_todo(), so an entry that sl_sync() can still see while holding RTNL belongs to a device that cannot be freed until RTNL is dropped. sl_free_netdev() stays only for the slip_open() error path, where register_netdevice() may have failed before ndo_init and ndo_uninit is then not called either. Both running for the same device is harmless: they run under the same RTNL section, so the slot cannot have been reused in between. This also removes the second symptom of the missing exclusion: a destructor running after sl_alloc() had already handed the slot out to another channel used to clear a live entry, so sl_sync() stopped at that NULL, sl_alloc() returned the same index again, and register_netdevice() failed with -EEXIST because slN was still there. Reproduced on x86_64 with several threads looping over open("/dev/ptmx") + ioctl(TIOCSETD, N_SLIP) + close(). Found by Linux Verification Center (linuxtesting.org) with Syzkaller. Fixes: 5342b77c4123 ("slip: Clean up create and destroy") Cc: stable@vger.kernel.org Suggested-by: Jakub Kicinski Signed-off-by: Aleksandr Khromov Link: https://patch.msgid.link/20260824100547.164773-1-haa@amicon.ru Signed-off-by: Paolo Abeni --- drivers/net/slip/slip.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/net/slip/slip.c b/drivers/net/slip/slip.c index faae711cf793..85b2438e8923 100644 --- a/drivers/net/slip/slip.c +++ b/drivers/net/slip/slip.c @@ -628,9 +628,15 @@ static void sl_uninit(struct net_device *dev) struct slip *sl = netdev_priv(dev); sl_free_bufs(sl); + /* Drop the slip_devs[] entry here rather than from the destructor: + * ndo_uninit runs under RTNL, so it cannot race sl_sync(). + */ + slip_devs[dev->base_addr] = NULL; } -/* Hook the destructor so we can free slip devices at the right point in time */ +/* Only for the slip_open() error path: register_netdevice() can fail before + * ndo_init, and then ndo_uninit is not called either. + */ static void sl_free_netdev(struct net_device *dev) { int i = dev->base_addr; @@ -657,7 +663,6 @@ static void sl_setup(struct net_device *dev) { dev->netdev_ops = &sl_netdev_ops; dev->needs_free_netdev = true; - dev->priv_destructor = sl_free_netdev; dev->hard_header_len = 0; dev->addr_len = 0; @@ -908,7 +913,7 @@ static void slip_close(struct tty_struct *tty) #endif /* Flush network side */ unregister_netdev(sl->dev); - /* This will complete via sl_free_netdev */ + /* sl_uninit() has dropped the slip_devs[] entry by now */ } static void slip_hangup(struct tty_struct *tty) From 1f0391aea883e99d402d0f77ba3530e4e879350d Mon Sep 17 00:00:00 2001 From: Fernando Fernandez Mancera Date: Mon, 1 Jun 2026 21:30:46 +0200 Subject: [PATCH 1263/1328] netfilter: tproxy: use DEBUG_NET_WARN_ON_ONCE for protocol fallbacks Replace WARN_ON calls with DEBUG_NET_WARN_ON_ONCE in the default switch blocks of nf_tproxy_get_sock_v4 and v6. Unsupported transport protocols are already safely handled by returning a NULL socket pointer. This prevents unnecessary system panics when panic_on_warn=1 is enabled in production systems. Link: https://patch.msgid.link/cover.1786968834.git.zhilinz@nebusec.ai/ Signed-off-by: Fernando Fernandez Mancera Signed-off-by: Pablo Neira Ayuso --- net/ipv4/netfilter/nf_tproxy_ipv4.c | 2 +- net/ipv6/netfilter/nf_tproxy_ipv6.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/net/ipv4/netfilter/nf_tproxy_ipv4.c b/net/ipv4/netfilter/nf_tproxy_ipv4.c index 041c3f37f237..5eab7a2dc8ef 100644 --- a/net/ipv4/netfilter/nf_tproxy_ipv4.c +++ b/net/ipv4/netfilter/nf_tproxy_ipv4.c @@ -137,7 +137,7 @@ nf_tproxy_get_sock_v4(struct net *net, struct sk_buff *skb, } break; default: - WARN_ON(1); + DEBUG_NET_WARN_ON_ONCE(1); sk = NULL; } diff --git a/net/ipv6/netfilter/nf_tproxy_ipv6.c b/net/ipv6/netfilter/nf_tproxy_ipv6.c index b2f59ed9d7cc..12ec36a6be2e 100644 --- a/net/ipv6/netfilter/nf_tproxy_ipv6.c +++ b/net/ipv6/netfilter/nf_tproxy_ipv6.c @@ -136,7 +136,7 @@ nf_tproxy_get_sock_v6(struct net *net, struct sk_buff *skb, int thoff, } break; default: - WARN_ON(1); + DEBUG_NET_WARN_ON_ONCE(1); sk = NULL; } From 9b4ab1f3fed89d8c1e953dc069a0ddd705a73f09 Mon Sep 17 00:00:00 2001 From: Fernando Fernandez Mancera Date: Mon, 1 Jun 2026 21:30:49 +0200 Subject: [PATCH 1264/1328] netfilter: conncount: use DEBUG_NET_WARN_ON_ONCE on reaching count limit Replace WARN_ON_ONCE with DEBUG_NET_WARN_ON_ONCE in __nf_conncount_add. The function handles count limit breaches safely by returning -EOVERFLOW, so a production backtrace is not needed. This prevents unnecessary system panics when panic_on_warn=1 is enabled in production systems. Signed-off-by: Fernando Fernandez Mancera Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_conncount.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/netfilter/nf_conncount.c b/net/netfilter/nf_conncount.c index 85487f92af50..4292c3d9addd 100644 --- a/net/netfilter/nf_conncount.c +++ b/net/netfilter/nf_conncount.c @@ -251,7 +251,8 @@ static int __nf_conncount_add(struct net *net, list->last_gc_count = list->count; add_new_node: - if (WARN_ON_ONCE(list->count > INT_MAX)) { + if (unlikely(list->count > INT_MAX)) { + DEBUG_NET_WARN_ON_ONCE(1); err = -EOVERFLOW; goto out_put; } From f0feab6e9e008faa406d189fb40cb7ab1bed420a Mon Sep 17 00:00:00 2001 From: Daniele Ceraolo Spurio Date: Tue, 18 Aug 2026 21:35:20 +0000 Subject: [PATCH 1265/1328] drm/xe: Do not apply WA 14025883347 to media 3503 The database was updated and the WA is no longer listed as applicable to media 3503, so don't enable it there. Fixes: c57db41b8d2c ("drm/xe/guc: Add Wa_14025883347 for GuC DMA failure on reset") Signed-off-by: Daniele Ceraolo Spurio Cc: Sk Anirban Cc: Badal Nilawar Cc: Matt Roper Reviewed-by: Matt Roper Link: https://patch.msgid.link/20260818213520.283063-1-daniele.ceraolospurio@intel.com (cherry picked from commit fae59d5de5de39bc51ac2839f74970312e0c8905) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_wa_oob.rules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_wa_oob.rules b/drivers/gpu/drm/xe/xe_wa_oob.rules index f02ac9bf7424..dd69ad07f7a9 100644 --- a/drivers/gpu/drm/xe/xe_wa_oob.rules +++ b/drivers/gpu/drm/xe/xe_wa_oob.rules @@ -63,7 +63,7 @@ 16026007364 MEDIA_VERSION(3000) 14020316580 MEDIA_VERSION(1301) -14025883347 MEDIA_VERSION_RANGE(1301, 3503) +14025883347 MEDIA_VERSION_RANGE(1301, 3500) GRAPHICS_VERSION_RANGE(2004, 3005) 16029380221 MEDIA_VERSION(3500) 22022079272 MEDIA_VERSION(3503) From 369ba0d1efe91cccabe98ae53c53b7425f327edf Mon Sep 17 00:00:00 2001 From: Balasubramani Vivekanandan Date: Wed, 19 Aug 2026 13:04:58 +0530 Subject: [PATCH 1266/1328] drm/xe/xe_gt_idle: Add CCS to the powergating info print While reading the main GT powergating info from debugfs, include both RCS and CCS engine masks. Fixes: 0914c1e45d3a1 ("drm/xe/xe_gt_idle: add debugfs entry for powergating info") Signed-off-by: Balasubramani Vivekanandan Link: https://patch.msgid.link/20260819073457.1812722-2-balasubramani.vivekanandan@intel.com Reviewed-by: Matt Roper Signed-off-by: Matt Roper (cherry picked from commit 8899e413c5ab85443ec9bbc50cffe924c6b596de) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_gt_idle.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_gt_idle.c b/drivers/gpu/drm/xe/xe_gt_idle.c index 04b24e1c8b78..7dc9873aef54 100644 --- a/drivers/gpu/drm/xe/xe_gt_idle.c +++ b/drivers/gpu/drm/xe/xe_gt_idle.c @@ -248,7 +248,8 @@ int xe_gt_idle_pg_print(struct xe_gt *gt, struct drm_printer *p) pg_status = xe_mmio_read32(>->mmio, POWERGATE_DOMAIN_STATUS); } - if (gt->info.engine_mask & XE_HW_ENGINE_RCS_MASK) { + if (gt->info.engine_mask & + (XE_HW_ENGINE_RCS_MASK | XE_HW_ENGINE_CCS_MASK)) { drm_printf(p, "Render Power Gating Enabled: %s\n", str_yes_no(pg_enabled & RENDER_POWERGATE_ENABLE)); From 5e977521d21717edb8e91d004434697d6e3f248c Mon Sep 17 00:00:00 2001 From: Arvind Yadav Date: Thu, 20 Aug 2026 12:24:45 +0530 Subject: [PATCH 1267/1328] drm/xe: Reject page faults from non-fault-mode scratch VMs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Having scratch enabled does not make a VM capable of handling recoverable page faults. Allowing scratch VMs through the ASID lookup also admits dma-fence mode VMs. If such a VM faults on an already valid VMA, the handler reports success without fixing the fault, causing the GPU to retry indefinitely. Only allow fault-mode VMs through the ASID lookup. Fault-mode VMs using scratch remain supported, while faults from 3D VMs are rejected. Fixes: ad9843aac91a ("drm/xe/madvise: Implement purgeable buffer object support") Cc: Matthew Brost Cc: Thomas Hellström Cc: Himal Prasad Ghimiray Suggested-by: Matthew Brost Signed-off-by: Arvind Yadav Reviewed-by: Matthew Brost Signed-off-by: Matthew Brost Link: https://patch.msgid.link/20260820065445.567228-1-arvind.yadav@intel.com (cherry picked from commit bfb24a06405b652d37831f3fb66b71d33a6605de) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_pagefault.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_pagefault.c b/drivers/gpu/drm/xe/xe_pagefault.c index dd3c068e1a39..dbf8f71d3328 100644 --- a/drivers/gpu/drm/xe/xe_pagefault.c +++ b/drivers/gpu/drm/xe/xe_pagefault.c @@ -158,7 +158,7 @@ static struct xe_vm *xe_pagefault_asid_to_vm(struct xe_device *xe, u32 asid) down_read(&xe->usm.lock); vm = xa_load(&xe->usm.asid_to_vm, asid); - if (vm && (xe_vm_in_fault_mode(vm) || xe_vm_has_scratch(vm))) + if (vm && xe_vm_in_fault_mode(vm)) xe_vm_get(vm); else vm = ERR_PTR(-EINVAL); From c27c449d455aafd9018a3cbab150f1c42c87923f Mon Sep 17 00:00:00 2001 From: Alice Mikityanska Date: Sat, 22 Aug 2026 15:01:16 +0300 Subject: [PATCH 1268/1328] virtio-net: Ensure that TCP packets don't overflow gso_segs The user can specify any gso_size in a packet crafted with an AF_PACKET PACKET_VNET_HDR socket, even smaller than TCP_MIN_GSO_SIZE = 8. At the same time, GSO_MAX_SIZE = 8 * GSO_MAX_SEGS = 8 * 65535. When the user crafts a packet with gso_size < 8, there is a risk for partial GSO to overflow the 16-bit gso_segs field when dividing the SKB length by gso_size. Adjust gso_size of TCP packets to be at least TCP_MIN_GSO_SIZE = 8. Keep gso_size of UDP GSO packets, as gso_size=1 is valid and explicitly tested at tools/testing/selftests/net/tun.c:649. Fixes: 7c6d2ecbda83 ("net: be more gentle about silly gso requests coming from user") Signed-off-by: Alice Mikityanska Suggested-by: Eric Dumazet Link: https://patch.msgid.link/20260822120117.1163423-2-alice.kernel@fastmail.im Signed-off-by: Paolo Abeni --- include/linux/virtio_net.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/linux/virtio_net.h b/include/linux/virtio_net.h index f36d21b5bc19..c381b916c1b5 100644 --- a/include/linux/virtio_net.h +++ b/include/linux/virtio_net.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -179,6 +180,9 @@ static inline int __virtio_net_hdr_to_skb(struct sk_buff *skb, if (skb->ip_summed == CHECKSUM_PARTIAL && skb->csum_offset != offsetof(struct tcphdr, check)) return -EINVAL; + + BUILD_BUG_ON(TCP_MIN_GSO_SIZE * GSO_MAX_SEGS < GSO_MAX_SIZE); + gso_size = max(gso_size, TCP_MIN_GSO_SIZE); break; } From 0b13256ce37b66dbd0e4ce78d5bee32fd38db1a5 Mon Sep 17 00:00:00 2001 From: Alice Mikityanska Date: Sat, 22 Aug 2026 15:01:17 +0300 Subject: [PATCH 1269/1328] net: Guard for gso_segs overflow in skb_segment skb_segment calculates 32-bit partial_segs as len / gso_size, and then assigns it to the 16-bit gso_segs field. The division might overflow in some edge cases where the SKB is BIG TCP (65536 <= len <= 8*65535), and gso_size < TCP_MIN_GSO_SIZE = 8. While normally this can't happen due to TCP_MIN_GSO_SIZE, an AF_PACKET PACKET_VNET_HDR socket could generate such a malformed packet until the previous patch. Blocking malformed virtio_net packets was implemented in the previous patch, but this patch clamps partial_segs in skb_segment itself for more generic robustness. Should len / gso_size happen to be bigger than 65535 in partial GSO, skb_segment will now just produce more than two output SKBs, all of which will be valid with gso_segs <= 65535. In order to catch possible other cases of too many partial_segs, add a DEBUG_NET_WARN_ON_ONCE when len / gso_size happens to be too big. Signed-off-by: Alice Mikityanska Link: https://patch.msgid.link/20260822120117.1163423-3-alice.kernel@fastmail.im Signed-off-by: Paolo Abeni --- net/core/skbuff.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/core/skbuff.c b/net/core/skbuff.c index cbbd60455abb..966af3beed94 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -4873,7 +4873,8 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb, * doesn't fit into an MSS sized block, so take care of that * now. */ - partial_segs = len / mss; + DEBUG_NET_WARN_ON_ONCE(len / mss > GSO_MAX_SEGS); + partial_segs = min(len / mss, GSO_MAX_SEGS); if (partial_segs > 1) mss *= partial_segs; else From 3b446d169a93e6abae9a93535369fa18bbcbefd9 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 27 Aug 2026 15:59:32 +0200 Subject: [PATCH 1270/1328] Revert "ACPI: scan: Defer device power initialization" Revert commit dc948f8b384a ("ACPI: scan: Defer device power initialization") that is incomplete and may cause ACPI power management of devices to fail. The problem is that PCI devices are associated with the corresponding ACPI device objects before acpi_bus_attach() runs for them, so after commit dc948f8b384a, ACPI power management will not be initialized for them before making that association. Consequently, the reference counting of ACPI power resources may not work as expected going forward and power management issues may appear. If they appear, they may be elusive and hard to diagnose. While this is fixable, I am not sure if fixing it on top of commit dc948f8b384a is the best way to go, so it is better to revert that commit for now and revisit the whole thing in the next cycle. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/6029658.DvuYhMxLoT@rafael.j.wysocki --- drivers/acpi/power.c | 1 - drivers/acpi/scan.c | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/acpi/power.c b/drivers/acpi/power.c index 922ba9803a93..23a4e207a01e 100644 --- a/drivers/acpi/power.c +++ b/drivers/acpi/power.c @@ -954,7 +954,6 @@ struct acpi_device *acpi_add_power_resource(acpi_handle handle) INIT_LIST_HEAD(&resource->list_node); INIT_LIST_HEAD(&resource->dependents); device->power.state = ACPI_STATE_UNKNOWN; - device->flags.initialized = true; /* Evaluate the object to get the system level and resource order. */ status = acpi_evaluate_object(handle, NULL, NULL, &buffer); diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 7dcf784199fa..f48715ed827c 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -1144,6 +1144,9 @@ static void acpi_bus_get_power_flags(struct acpi_device *device) if (!list_empty(&device->power.states[ACPI_STATE_D3_HOT].resources)) device->power.states[ACPI_STATE_D3_COLD].flags.valid = 1; } + + if (acpi_bus_init_power(device)) + device->flags.power_manageable = 0; } static void acpi_bus_get_flags(struct acpi_device *device) @@ -1825,6 +1828,7 @@ void acpi_init_device_object(struct acpi_device *device, acpi_handle handle, acpi_set_pnp_ids(handle, &device->pnp, type); acpi_init_properties(device); acpi_bus_get_flags(device); + device->flags.initialized = true; device->flags.enumeration_by_parent = acpi_device_enumeration_by_parent(device); acpi_device_clear_enumerated(device); From b1881d362e1924b66f6016c3efd28807032b41bf Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Thu, 13 Aug 2026 02:16:02 +0200 Subject: [PATCH 1271/1328] netfilter: nf_tables: move hardware offload step after building the chain blob Allocate the chain blob before the ruleset offload to reduce chances of entering an inconsistent state where the offloaded ruleset in the nic and the software ruleset differ. Fixes: c9626a2cbdb2 ("netfilter: nf_tables: add hardware offload support") Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_tables_api.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index c112ecc4fca3..71f4227d7ac7 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -10982,10 +10982,6 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb) return -EAGAIN; } - err = nft_flow_rule_offload_commit(net); - if (err < 0) - return err; - /* 1. Allocate space for next generation rules_gen_X[] */ list_for_each_entry_safe(trans, next, &nft_net->commit_list, list) { struct nft_table *table = trans->table; @@ -11010,6 +11006,16 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb) } } + /* must be last, so audit and chain blob set up does not leave hardware + * in consistent state. + */ + err = nft_flow_rule_offload_commit(net); + if (err < 0) { + nf_tables_commit_chain_prepare_cancel(net); + nf_tables_commit_audit_free(&adl); + return err; + } + /* step 2. Make rules_gen_X visible to packet path */ nft_set_commit_update(&ctx, nft_net); From 55dd20f0f4b1be5c9c8a0275d8d763c86563eac2 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sat, 15 Aug 2026 13:57:50 -0700 Subject: [PATCH 1272/1328] netfilter: nft_set_pipapo_avx2: add missing vzeroupper Since pipapo_get_avx2() uses YMM registers, execute vzeroupper before returning from it. This is needed to avoid degrading the performance of any later SSE code that may happen to be executed. Fixes: 7400b063969b ("nft_set_pipapo: Introduce AVX2-based lookup implementation") Cc: stable@vger.kernel.org Signed-off-by: Eric Biggers Reviewed-by: Stefano Brivio Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nft_set_pipapo_avx2.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/net/netfilter/nft_set_pipapo_avx2.c b/net/netfilter/nft_set_pipapo_avx2.c index b3f105520a85..21f5be68c703 100644 --- a/net/netfilter/nft_set_pipapo_avx2.c +++ b/net/netfilter/nft_set_pipapo_avx2.c @@ -1134,6 +1134,7 @@ struct nft_pipapo_elem *pipapo_get_avx2(const struct nft_pipapo_match *m, struct nft_pipapo_scratch *scratch; const struct nft_pipapo_field *f; unsigned long *res, *fill, *map; + struct nft_pipapo_elem *e; bool map_index; int ret = 0; int i; @@ -1207,14 +1208,11 @@ struct nft_pipapo_elem *pipapo_get_avx2(const struct nft_pipapo_match *m, next_match: if (ret < 0) { scratch->map_index = map_index; - kernel_fpu_end(); - __local_unlock_nested_bh(&scratch->bh_lock); - return NULL; + e = NULL; + goto out; } if (last) { - struct nft_pipapo_elem *e; - e = f->mt[ret].e; if (unlikely(__nft_set_elem_expired(&e->ext, tstamp) || !nft_set_elem_active(&e->ext, genmask))) { @@ -1224,9 +1222,7 @@ struct nft_pipapo_elem *pipapo_get_avx2(const struct nft_pipapo_match *m, } scratch->map_index = map_index; - kernel_fpu_end(); - __local_unlock_nested_bh(&scratch->bh_lock); - return e; + goto out; } map_index = !map_index; @@ -1234,9 +1230,12 @@ struct nft_pipapo_elem *pipapo_get_avx2(const struct nft_pipapo_match *m, data += NFT_PIPAPO_GROUPS_PADDED_SIZE(f); } + e = NULL; +out: + asm volatile("vzeroupper"); kernel_fpu_end(); __local_unlock_nested_bh(&scratch->bh_lock); - return NULL; + return e; } /** From f43358489db46c8ad63207ee229ebdf1e7932be9 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Tue, 18 Aug 2026 10:15:05 +0200 Subject: [PATCH 1273/1328] netfilter: x_tables: remove pr_debug Remove pr_debug() for these xtables extensions, these have no use these days. Still, turn pr_debug() into pr_info_ratelimited() in the .checkentry path since this helps provide a hint via dmesg in legacy iptables. Exception is xt_IDLETIMER in the module init path, where pr_err() is used. Add missing pr_fmt() definition in xt_REDIRECT, xt_NETMAP and xt_MASQUERADE. Add missing \n to several pr_debug() that were translated to use pr_info_ratelimited(). Link: https://patch.msgid.link/cover.1786933680.git.rakukuip@gmail.com/ Signed-off-by: Pablo Neira Ayuso --- net/ipv4/netfilter/ipt_ah.c | 10 +---- net/ipv6/netfilter/ip6t_ah.c | 27 +------------- net/ipv6/netfilter/ip6t_frag.c | 41 +------------------- net/ipv6/netfilter/ip6t_hbh.c | 40 +++++--------------- net/ipv6/netfilter/ip6t_mh.c | 3 -- net/ipv6/netfilter/ip6t_rt.c | 6 +-- net/netfilter/xt_IDLETIMER.c | 68 ++++++---------------------------- net/netfilter/xt_LOG.c | 4 +- net/netfilter/xt_MASQUERADE.c | 4 +- net/netfilter/xt_NETMAP.c | 6 ++- net/netfilter/xt_REDIRECT.c | 6 ++- net/netfilter/xt_esp.c | 10 +---- net/netfilter/xt_ipcomp.c | 8 +--- net/netfilter/xt_iprange.c | 32 ++-------------- net/netfilter/xt_ipvs.c | 1 - net/netfilter/xt_multiport.c | 4 -- net/netfilter/xt_sctp.c | 19 +--------- net/netfilter/xt_tcpudp.c | 8 +--- 18 files changed, 51 insertions(+), 246 deletions(-) diff --git a/net/ipv4/netfilter/ipt_ah.c b/net/ipv4/netfilter/ipt_ah.c index 161ba412cb08..7131f297ada2 100644 --- a/net/ipv4/netfilter/ipt_ah.c +++ b/net/ipv4/netfilter/ipt_ah.c @@ -19,12 +19,7 @@ MODULE_DESCRIPTION("Xtables: IPv4 IPsec-AH SPI match"); static inline bool spi_match(u_int32_t min, u_int32_t max, u_int32_t spi, bool invert) { - bool r; - pr_debug("spi_match:%c 0x%x <= 0x%x <= 0x%x\n", - invert ? '!' : ' ', min, spi, max); - r = (spi >= min && spi <= max) ^ invert; - pr_debug(" result %s\n", r ? "PASS" : "FAILED"); - return r; + return (spi >= min && spi <= max) ^ invert; } static bool ah_mt(const struct sk_buff *skb, struct xt_action_param *par) @@ -42,7 +37,6 @@ static bool ah_mt(const struct sk_buff *skb, struct xt_action_param *par) /* We've been asked to examine this packet, and we * can't. Hence, no choice but to drop. */ - pr_debug("Dropping evil AH tinygram.\n"); par->hotdrop = true; return false; } @@ -58,7 +52,7 @@ static int ah_mt_check(const struct xt_mtchk_param *par) /* Must specify no unknown invflags */ if (ahinfo->invflags & ~IPT_AH_INV_MASK) { - pr_debug("unknown flags %X\n", ahinfo->invflags); + pr_info_ratelimited("unknown flags %X\n", ahinfo->invflags); return -EINVAL; } return 0; diff --git a/net/ipv6/netfilter/ip6t_ah.c b/net/ipv6/netfilter/ip6t_ah.c index 1258783ed876..dab7dbc6a675 100644 --- a/net/ipv6/netfilter/ip6t_ah.c +++ b/net/ipv6/netfilter/ip6t_ah.c @@ -24,13 +24,7 @@ MODULE_AUTHOR("Andras Kis-Szabo "); static inline bool spi_match(u_int32_t min, u_int32_t max, u_int32_t spi, bool invert) { - bool r; - - pr_debug("spi_match:%c 0x%x <= 0x%x <= 0x%x\n", - invert ? '!' : ' ', min, spi, max); - r = (spi >= min && spi <= max) ^ invert; - pr_debug(" result %s\n", r ? "PASS" : "FAILED"); - return r; + return (spi >= min && spi <= max) ^ invert; } static bool ah_mt6(const struct sk_buff *skb, struct xt_action_param *par) @@ -62,23 +56,6 @@ static bool ah_mt6(const struct sk_buff *skb, struct xt_action_param *par) return false; } - pr_debug("IPv6 AH LEN %u %u ", hdrlen, ah->hdrlen); - pr_debug("RES %04X ", ah->reserved); - pr_debug("SPI %u %08X\n", ntohl(ah->spi), ntohl(ah->spi)); - - pr_debug("IPv6 AH spi %02X ", - spi_match(ahinfo->spis[0], ahinfo->spis[1], - ntohl(ah->spi), - !!(ahinfo->invflags & IP6T_AH_INV_SPI))); - pr_debug("len %02X %04X %02X ", - ahinfo->hdrlen, hdrlen, - (!ahinfo->hdrlen || - (ahinfo->hdrlen == hdrlen) ^ - !!(ahinfo->invflags & IP6T_AH_INV_LEN))); - pr_debug("res %02X %04X %02X\n", - ahinfo->hdrres, ah->reserved, - !(ahinfo->hdrres && ah->reserved)); - return spi_match(ahinfo->spis[0], ahinfo->spis[1], ntohl(ah->spi), !!(ahinfo->invflags & IP6T_AH_INV_SPI)) && @@ -93,7 +70,7 @@ static int ah_mt6_check(const struct xt_mtchk_param *par) const struct ip6t_ah *ahinfo = par->matchinfo; if (ahinfo->invflags & ~IP6T_AH_INV_MASK) { - pr_debug("unknown flags %X\n", ahinfo->invflags); + pr_info_ratelimited("unknown flags %X\n", ahinfo->invflags); return -EINVAL; } return 0; diff --git a/net/ipv6/netfilter/ip6t_frag.c b/net/ipv6/netfilter/ip6t_frag.c index 3aad6439386b..f5f3cfb8704c 100644 --- a/net/ipv6/netfilter/ip6t_frag.c +++ b/net/ipv6/netfilter/ip6t_frag.c @@ -23,12 +23,7 @@ MODULE_AUTHOR("Andras Kis-Szabo "); static inline bool id_match(u_int32_t min, u_int32_t max, u_int32_t id, bool invert) { - bool r; - pr_debug("id_match:%c 0x%x <= 0x%x <= 0x%x\n", invert ? '!' : ' ', - min, id, max); - r = (id >= min && id <= max) ^ invert; - pr_debug(" result %s\n", r ? "PASS" : "FAILED"); - return r; + return (id >= min && id <= max) ^ invert; } static bool @@ -53,38 +48,6 @@ frag_mt6(const struct sk_buff *skb, struct xt_action_param *par) return false; } - pr_debug("INFO %04X ", fh->frag_off); - pr_debug("OFFSET %04X ", ntohs(fh->frag_off) & ~0x7); - pr_debug("RES %02X %04X", fh->reserved, ntohs(fh->frag_off) & 0x6); - pr_debug("MF %04X ", fh->frag_off & htons(IP6_MF)); - pr_debug("ID %u %08X\n", ntohl(fh->identification), - ntohl(fh->identification)); - - pr_debug("IPv6 FRAG id %02X ", - id_match(fraginfo->ids[0], fraginfo->ids[1], - ntohl(fh->identification), - !!(fraginfo->invflags & IP6T_FRAG_INV_IDS))); - pr_debug("res %02X %02X%04X %02X ", - fraginfo->flags & IP6T_FRAG_RES, fh->reserved, - ntohs(fh->frag_off) & 0x6, - !((fraginfo->flags & IP6T_FRAG_RES) && - (fh->reserved || (ntohs(fh->frag_off) & 0x06)))); - pr_debug("first %02X %02X %02X ", - fraginfo->flags & IP6T_FRAG_FST, - ntohs(fh->frag_off) & ~0x7, - !((fraginfo->flags & IP6T_FRAG_FST) && - (ntohs(fh->frag_off) & ~0x7))); - pr_debug("mf %02X %02X %02X ", - fraginfo->flags & IP6T_FRAG_MF, - ntohs(fh->frag_off) & IP6_MF, - !((fraginfo->flags & IP6T_FRAG_MF) && - !((ntohs(fh->frag_off) & IP6_MF)))); - pr_debug("last %02X %02X %02X\n", - fraginfo->flags & IP6T_FRAG_NMF, - ntohs(fh->frag_off) & IP6_MF, - !((fraginfo->flags & IP6T_FRAG_NMF) && - (ntohs(fh->frag_off) & IP6_MF))); - return id_match(fraginfo->ids[0], fraginfo->ids[1], ntohl(fh->identification), !!(fraginfo->invflags & IP6T_FRAG_INV_IDS)) && @@ -103,7 +66,7 @@ static int frag_mt6_check(const struct xt_mtchk_param *par) const struct ip6t_frag *fraginfo = par->matchinfo; if (fraginfo->invflags & ~IP6T_FRAG_INV_MASK) { - pr_debug("unknown flags %X\n", fraginfo->invflags); + pr_info_ratelimited("unknown flags %X\n", fraginfo->invflags); return -EINVAL; } return 0; diff --git a/net/ipv6/netfilter/ip6t_hbh.c b/net/ipv6/netfilter/ip6t_hbh.c index 6d1a5d2026a6..6008dcff8488 100644 --- a/net/ipv6/netfilter/ip6t_hbh.c +++ b/net/ipv6/netfilter/ip6t_hbh.c @@ -79,14 +79,6 @@ hbh_mt6(const struct sk_buff *skb, struct xt_action_param *par) return false; } - pr_debug("IPv6 OPTS LEN %u %u ", hdrlen, oh->hdrlen); - - pr_debug("len %02X %04X %02X ", - optinfo->hdrlen, hdrlen, - (!(optinfo->flags & IP6T_OPTS_LEN) || - ((optinfo->hdrlen == hdrlen) ^ - !!(optinfo->invflags & IP6T_OPTS_INV_LEN)))); - ret = (!(optinfo->flags & IP6T_OPTS_LEN) || ((optinfo->hdrlen == hdrlen) ^ !!(optinfo->invflags & IP6T_OPTS_INV_LEN))); @@ -96,8 +88,6 @@ hbh_mt6(const struct sk_buff *skb, struct xt_action_param *par) if (!(optinfo->flags & IP6T_OPTS_OPTS)) { return ret; } else { - pr_debug("Strict "); - pr_debug("#%d ", optinfo->optsnr); for (temp = 0; temp < optinfo->optsnr; temp++) { /* type field exists ? */ if (hdrlen < 1) @@ -108,13 +98,9 @@ hbh_mt6(const struct sk_buff *skb, struct xt_action_param *par) break; /* Type check */ - if (*tp != (optinfo->opts[temp] & 0xFF00) >> 8) { - pr_debug("Tbad %02X %02X\n", *tp, - (optinfo->opts[temp] & 0xFF00) >> 8); + if (*tp != (optinfo->opts[temp] & 0xFF00) >> 8) return false; - } else { - pr_debug("Tok "); - } + /* Length check */ if (*tp) { u16 spec_len; @@ -129,26 +115,18 @@ hbh_mt6(const struct sk_buff *skb, struct xt_action_param *par) break; spec_len = optinfo->opts[temp] & 0x00FF; - if (spec_len != 0x00FF && spec_len != *lp) { - pr_debug("Lbad %02X %04X\n", *lp, - spec_len); + if (spec_len != 0x00FF && spec_len != *lp) return false; - } - pr_debug("Lok "); + optlen = *lp + 2; } else { - pr_debug("Pad1\n"); optlen = 1; } - /* Step to the next */ - pr_debug("len%04X\n", optlen); - if ((ptr > skb->len - optlen || hdrlen < optlen) && - temp < optinfo->optsnr - 1) { - pr_debug("new pointer is too large!\n"); + temp < optinfo->optsnr - 1) break; - } + ptr += optlen; hdrlen -= optlen; } @@ -166,16 +144,16 @@ static int hbh_mt6_check(const struct xt_mtchk_param *par) const struct ip6t_opts *optsinfo = par->matchinfo; if (optsinfo->invflags & ~IP6T_OPTS_INV_MASK) { - pr_debug("unknown flags %X\n", optsinfo->invflags); + pr_info_ratelimited("unknown flags %X\n", optsinfo->invflags); return -EINVAL; } if (optsinfo->optsnr > IP6T_OPTS_OPTSNR) { - pr_debug("too many supported opts specified\n"); + pr_info_ratelimited("too many supported opts specified\n"); return -EINVAL; } if (optsinfo->flags & IP6T_OPTS_NSTRICT) { - pr_debug("Not strict - not implemented"); + pr_info_ratelimited("Not strict - not implemented\n"); return -EINVAL; } diff --git a/net/ipv6/netfilter/ip6t_mh.c b/net/ipv6/netfilter/ip6t_mh.c index fd492b69acbc..ba6dcc7791a0 100644 --- a/net/ipv6/netfilter/ip6t_mh.c +++ b/net/ipv6/netfilter/ip6t_mh.c @@ -42,14 +42,11 @@ static bool mh_mt6(const struct sk_buff *skb, struct xt_action_param *par) if (mh == NULL) { /* We've been asked to examine this packet, and we can't. Hence, no choice but to drop. */ - pr_debug("Dropping evil MH tinygram.\n"); par->hotdrop = true; return false; } if (mh->ip6mh_proto != IPPROTO_NONE) { - pr_debug("Dropping invalid MH Payload Proto: %u\n", - mh->ip6mh_proto); par->hotdrop = true; return false; } diff --git a/net/ipv6/netfilter/ip6t_rt.c b/net/ipv6/netfilter/ip6t_rt.c index 278b52752f36..8051425213dd 100644 --- a/net/ipv6/netfilter/ip6t_rt.c +++ b/net/ipv6/netfilter/ip6t_rt.c @@ -155,18 +155,18 @@ static int rt_mt6_check(const struct xt_mtchk_param *par) const struct ip6t_rt *rtinfo = par->matchinfo; if (rtinfo->invflags & ~IP6T_RT_INV_MASK) { - pr_debug("unknown flags %X\n", rtinfo->invflags); + pr_info_ratelimited("unknown flags %X\n", rtinfo->invflags); return -EINVAL; } if (rtinfo->addrnr > IP6T_RT_HOPS) { - pr_debug("too many addresses specified\n"); + pr_info_ratelimited("too many addresses specified\n"); return -EINVAL; } if ((rtinfo->flags & (IP6T_RT_RES | IP6T_RT_FST_MASK)) && (!(rtinfo->flags & IP6T_RT_TYP) || (rtinfo->rt_type != 0) || (rtinfo->invflags & IP6T_RT_INV_TYP))) { - pr_debug("`--rt-type 0' required before `--rt-0-*'"); + pr_info_ratelimited("`--rt-type 0' required before `--rt-0-*'\n"); return -EINVAL; } diff --git a/net/netfilter/xt_IDLETIMER.c b/net/netfilter/xt_IDLETIMER.c index bfcf2d44e93d..fe7d8d19629b 100644 --- a/net/netfilter/xt_IDLETIMER.c +++ b/net/netfilter/xt_IDLETIMER.c @@ -102,8 +102,6 @@ static void idletimer_tg_expired(struct timer_list *t) { struct idletimer_tg *timer = timer_container_of(timer, t, timer); - pr_debug("timer %s expired\n", timer->attr.attr.name); - schedule_work(&timer->work); } @@ -111,7 +109,6 @@ static void idletimer_tg_alarmproc(struct alarm *alarm, ktime_t now) { struct idletimer_tg *timer = alarm->data; - pr_debug("alarm %s expired\n", timer->attr.attr.name); schedule_work(&timer->work); } @@ -171,7 +168,7 @@ static int idletimer_tg_create(struct idletimer_tg_info *info) ret = sysfs_create_file(idletimer_tg_kobj, &info->timer->attr.attr); if (ret < 0) { - pr_debug("couldn't add file to sysfs"); + pr_info_ratelimited("couldn't add file to sysfs\n"); goto out_free_attr; } @@ -220,7 +217,7 @@ static int idletimer_tg_create_v1(struct idletimer_tg_info_v1 *info) ret = sysfs_create_file(idletimer_tg_kobj, &info->timer->attr.attr); if (ret < 0) { - pr_debug("couldn't add file to sysfs"); + pr_info_ratelimited("couldn't add file to sysfs\n"); goto out_free_attr; } @@ -228,7 +225,6 @@ static int idletimer_tg_create_v1(struct idletimer_tg_info_v1 *info) kobject_uevent(idletimer_tg_kobj,KOBJ_ADD); list_add(&info->timer->entry, &idletimer_tg_list); - pr_debug("timer type value is %u", info->timer_type); info->timer->timer_type = info->timer_type; info->timer->refcnt = 1; @@ -263,9 +259,6 @@ static unsigned int idletimer_tg_target(struct sk_buff *skb, { const struct idletimer_tg_info *info = par->targinfo; - pr_debug("resetting timer %s, timeout period %u\n", - info->label, info->timeout); - mod_timer(&info->timer->timer, secs_to_jiffies(info->timeout) + jiffies); @@ -280,9 +273,6 @@ static unsigned int idletimer_tg_target_v1(struct sk_buff *skb, { const struct idletimer_tg_info_v1 *info = par->targinfo; - pr_debug("resetting timer %s, timeout period %u\n", - info->label, info->timeout); - if (info->timer->timer_type & XT_IDLETIMER_ALARM) { idletimer_start_alarm_sec(info->timer, info->timeout); } else { @@ -296,17 +286,17 @@ static unsigned int idletimer_tg_target_v1(struct sk_buff *skb, static int idletimer_tg_helper(struct idletimer_tg_info *info) { if (info->timeout == 0) { - pr_debug("timeout value is zero\n"); + pr_info_ratelimited("timeout value is zero\n"); return -EINVAL; } if (info->timeout >= INT_MAX / 1000) { - pr_debug("timeout value is too big\n"); + pr_info_ratelimited("timeout value is too big\n"); return -EINVAL; } if (info->label[0] == '\0' || strnlen(info->label, MAX_IDLETIMER_LABEL_SIZE) == MAX_IDLETIMER_LABEL_SIZE) { - pr_debug("label is empty or not nul-terminated\n"); + pr_info_ratelimited("label is empty or not nul-terminated\n"); return -EINVAL; } return 0; @@ -318,34 +308,25 @@ static int idletimer_tg_checkentry(const struct xt_tgchk_param *par) struct idletimer_tg_info *info = par->targinfo; int ret; - pr_debug("checkentry targinfo%s\n", info->label); - ret = idletimer_tg_helper(info); if(ret < 0) - { - pr_debug("checkentry helper return invalid\n"); return -EINVAL; - } mutex_lock(&list_mutex); info->timer = __idletimer_tg_find_by_label(info->label); if (info->timer) { if (info->timer->timer_type & XT_IDLETIMER_ALARM) { - pr_debug("Adding/Replacing rule with same label and different timer type is not allowed\n"); mutex_unlock(&list_mutex); + pr_info_ratelimited("Adding/Replacing rule with same label and different timer type is not allowed\n"); return -EINVAL; } info->timer->refcnt++; mod_timer(&info->timer->timer, secs_to_jiffies(info->timeout) + jiffies); - - pr_debug("increased refcnt of timer %s to %u\n", - info->label, info->timer->refcnt); } else { ret = idletimer_tg_create(info); if (ret < 0) { - pr_debug("failed to create timer\n"); mutex_unlock(&list_mutex); return ret; } @@ -360,30 +341,23 @@ static int idletimer_tg_checkentry_v1(const struct xt_tgchk_param *par) struct idletimer_tg_info_v1 *info = par->targinfo; int ret; - pr_debug("checkentry targinfo%s\n", info->label); - if (info->send_nl_msg) return -EOPNOTSUPP; ret = idletimer_tg_helper((struct idletimer_tg_info *)info); if(ret < 0) - { - pr_debug("checkentry helper return invalid\n"); return -EINVAL; - } - if (info->timer_type > XT_IDLETIMER_ALARM) { - pr_debug("invalid value for timer type\n"); + if (info->timer_type > XT_IDLETIMER_ALARM) return -EINVAL; - } mutex_lock(&list_mutex); info->timer = __idletimer_tg_find_by_label(info->label); if (info->timer) { if (info->timer->timer_type != info->timer_type) { - pr_debug("Adding/Replacing rule with same label and different timer type is not allowed\n"); mutex_unlock(&list_mutex); + pr_info_ratelimited("Adding/Replacing rule with same label and different timer type is not allowed\n"); return -EINVAL; } @@ -393,21 +367,15 @@ static int idletimer_tg_checkentry_v1(const struct xt_tgchk_param *par) ktime_t tout = alarm_expires_remaining(&info->timer->alarm); struct timespec64 ktimespec = ktime_to_timespec64(tout); - if (ktimespec.tv_sec > 0) { - pr_debug("time_expiry_remaining %lld\n", - ktimespec.tv_sec); + if (ktimespec.tv_sec > 0) idletimer_start_alarm_ktime(info->timer, tout); - } } else { mod_timer(&info->timer->timer, secs_to_jiffies(info->timeout) + jiffies); } - pr_debug("increased refcnt of timer %s to %u\n", - info->label, info->timer->refcnt); } else { ret = idletimer_tg_create_v1(info); if (ret < 0) { - pr_debug("failed to create timer\n"); mutex_unlock(&list_mutex); return ret; } @@ -421,19 +389,13 @@ static void idletimer_tg_destroy(const struct xt_tgdtor_param *par) { const struct idletimer_tg_info *info = par->targinfo; - pr_debug("destroy targinfo %s\n", info->label); - mutex_lock(&list_mutex); if (--info->timer->refcnt > 0) { - pr_debug("decreased refcnt of timer %s to %u\n", - info->label, info->timer->refcnt); mutex_unlock(&list_mutex); return; } - pr_debug("deleting timer %s\n", info->label); - list_del(&info->timer->entry); mutex_unlock(&list_mutex); @@ -448,19 +410,13 @@ static void idletimer_tg_destroy_v1(const struct xt_tgdtor_param *par) { const struct idletimer_tg_info_v1 *info = par->targinfo; - pr_debug("destroy targinfo %s\n", info->label); - mutex_lock(&list_mutex); if (--info->timer->refcnt > 0) { - pr_debug("decreased refcnt of timer %s to %u\n", - info->label, info->timer->refcnt); mutex_unlock(&list_mutex); return; } - pr_debug("deleting timer %s\n", info->label); - list_del(&info->timer->entry); mutex_unlock(&list_mutex); @@ -534,7 +490,7 @@ static int __init idletimer_tg_init(void) idletimer_tg_class = class_create("xt_idletimer"); err = PTR_ERR(idletimer_tg_class); if (IS_ERR(idletimer_tg_class)) { - pr_debug("couldn't register device class\n"); + pr_err("couldn't register device class\n"); goto out; } @@ -542,7 +498,7 @@ static int __init idletimer_tg_init(void) MKDEV(0, 0), NULL, "timers"); err = PTR_ERR(idletimer_tg_device); if (IS_ERR(idletimer_tg_device)) { - pr_debug("couldn't register system device\n"); + pr_err("couldn't register system device\n"); goto out_class; } @@ -551,7 +507,7 @@ static int __init idletimer_tg_init(void) err = xt_register_targets(idletimer_tg, ARRAY_SIZE(idletimer_tg)); if (err < 0) { - pr_debug("couldn't register xt target\n"); + pr_err("couldn't register xt target\n"); goto out_dev; } diff --git a/net/netfilter/xt_LOG.c b/net/netfilter/xt_LOG.c index f39244f9c0ed..de3f176792a0 100644 --- a/net/netfilter/xt_LOG.c +++ b/net/netfilter/xt_LOG.c @@ -50,12 +50,12 @@ static int log_tg_check(const struct xt_tgchk_param *par) return -EINVAL; if (loginfo->level >= 8) { - pr_debug("level %u >= 8\n", loginfo->level); + pr_info_ratelimited("level %u >= 8\n", loginfo->level); return -EINVAL; } if (loginfo->prefix[sizeof(loginfo->prefix)-1] != '\0') { - pr_debug("prefix is not null-terminated\n"); + pr_info_ratelimited("prefix is not null-terminated\n"); return -EINVAL; } diff --git a/net/netfilter/xt_MASQUERADE.c b/net/netfilter/xt_MASQUERADE.c index eae05c178336..cea488cec544 100644 --- a/net/netfilter/xt_MASQUERADE.c +++ b/net/netfilter/xt_MASQUERADE.c @@ -21,11 +21,11 @@ static int masquerade_tg_check(const struct xt_tgchk_param *par) const struct nf_nat_ipv4_multi_range_compat *mr = par->targinfo; if (mr->range[0].flags & NF_NAT_RANGE_MAP_IPS) { - pr_debug("bad MAP_IPS.\n"); + pr_info_ratelimited("bad MAP_IPS.\n"); return -EINVAL; } if (mr->rangesize != 1) { - pr_debug("bad rangesize %u\n", mr->rangesize); + pr_info_ratelimited("bad rangesize %u\n", mr->rangesize); return -EINVAL; } return nf_ct_netns_get(par->net, par->family); diff --git a/net/netfilter/xt_NETMAP.c b/net/netfilter/xt_NETMAP.c index cb2ee80d84fa..7da065a472e0 100644 --- a/net/netfilter/xt_NETMAP.c +++ b/net/netfilter/xt_NETMAP.c @@ -4,6 +4,8 @@ * Copyright (c) 2011 Patrick McHardy */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include #include #include @@ -106,11 +108,11 @@ static int netmap_tg4_check(const struct xt_tgchk_param *par) const struct nf_nat_ipv4_multi_range_compat *mr = par->targinfo; if (!(mr->range[0].flags & NF_NAT_RANGE_MAP_IPS)) { - pr_debug("bad MAP_IPS.\n"); + pr_info_ratelimited("bad MAP_IPS.\n"); return -EINVAL; } if (mr->rangesize != 1) { - pr_debug("bad rangesize %u.\n", mr->rangesize); + pr_info_ratelimited("bad rangesize %u.\n", mr->rangesize); return -EINVAL; } return nf_ct_netns_get(par->net, par->family); diff --git a/net/netfilter/xt_REDIRECT.c b/net/netfilter/xt_REDIRECT.c index ff66b56a3f97..dd050947257b 100644 --- a/net/netfilter/xt_REDIRECT.c +++ b/net/netfilter/xt_REDIRECT.c @@ -8,6 +8,8 @@ * NAT funded by Astaro. */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include #include #include @@ -51,11 +53,11 @@ static int redirect_tg4_check(const struct xt_tgchk_param *par) const struct nf_nat_ipv4_multi_range_compat *mr = par->targinfo; if (mr->range[0].flags & NF_NAT_RANGE_MAP_IPS) { - pr_debug("bad MAP_IPS.\n"); + pr_info_ratelimited("bad MAP_IPS.\n"); return -EINVAL; } if (mr->rangesize != 1) { - pr_debug("bad rangesize %u.\n", mr->rangesize); + pr_info_ratelimited("bad rangesize %u.\n", mr->rangesize); return -EINVAL; } return nf_ct_netns_get(par->net, par->family); diff --git a/net/netfilter/xt_esp.c b/net/netfilter/xt_esp.c index 2a1c0ad0ff07..68fd75884268 100644 --- a/net/netfilter/xt_esp.c +++ b/net/netfilter/xt_esp.c @@ -25,12 +25,7 @@ MODULE_ALIAS("ip6t_esp"); static inline bool spi_match(u_int32_t min, u_int32_t max, u_int32_t spi, bool invert) { - bool r; - pr_debug("spi_match:%c 0x%x <= 0x%x <= 0x%x\n", - invert ? '!' : ' ', min, spi, max); - r = (spi >= min && spi <= max) ^ invert; - pr_debug(" result %s\n", r ? "PASS" : "FAILED"); - return r; + return (spi >= min && spi <= max) ^ invert; } static bool esp_mt(const struct sk_buff *skb, struct xt_action_param *par) @@ -48,7 +43,6 @@ static bool esp_mt(const struct sk_buff *skb, struct xt_action_param *par) /* We've been asked to examine this packet, and we * can't. Hence, no choice but to drop. */ - pr_debug("Dropping evil ESP tinygram.\n"); par->hotdrop = true; return false; } @@ -62,7 +56,7 @@ static int esp_mt_check(const struct xt_mtchk_param *par) const struct xt_esp *espinfo = par->matchinfo; if (espinfo->invflags & ~XT_ESP_INV_MASK) { - pr_debug("unknown flags %X\n", espinfo->invflags); + pr_info_ratelimited("unknown flags %X\n", espinfo->invflags); return -EINVAL; } diff --git a/net/netfilter/xt_ipcomp.c b/net/netfilter/xt_ipcomp.c index 472da639a32e..3299c1ea60f9 100644 --- a/net/netfilter/xt_ipcomp.c +++ b/net/netfilter/xt_ipcomp.c @@ -29,12 +29,7 @@ MODULE_ALIAS("ip6t_ipcomp"); static inline bool spi_match(u_int32_t min, u_int32_t max, u_int32_t spi, bool invert) { - bool r; - pr_debug("spi_match:%c 0x%x <= 0x%x <= 0x%x\n", - invert ? '!' : ' ', min, spi, max); - r = (spi >= min && spi <= max) ^ invert; - pr_debug(" result %s\n", r ? "PASS" : "FAILED"); - return r; + return (spi >= min && spi <= max) ^ invert; } static bool comp_mt(const struct sk_buff *skb, struct xt_action_param *par) @@ -52,7 +47,6 @@ static bool comp_mt(const struct sk_buff *skb, struct xt_action_param *par) /* We've been asked to examine this packet, and we * can't. Hence, no choice but to drop. */ - pr_debug("Dropping evil IPComp tinygram.\n"); par->hotdrop = true; return false; } diff --git a/net/netfilter/xt_iprange.c b/net/netfilter/xt_iprange.c index 0c9e014e30b4..bf61141fb785 100644 --- a/net/netfilter/xt_iprange.c +++ b/net/netfilter/xt_iprange.c @@ -24,27 +24,15 @@ iprange_mt4(const struct sk_buff *skb, struct xt_action_param *par) m = ntohl(iph->saddr) < ntohl(info->src_min.ip); m |= ntohl(iph->saddr) > ntohl(info->src_max.ip); m ^= !!(info->flags & IPRANGE_SRC_INV); - if (m) { - pr_debug("src IP %pI4 NOT in range %s%pI4-%pI4\n", - &iph->saddr, - (info->flags & IPRANGE_SRC_INV) ? "(INV) " : "", - &info->src_min.ip, - &info->src_max.ip); + if (m) return false; - } } if (info->flags & IPRANGE_DST) { m = ntohl(iph->daddr) < ntohl(info->dst_min.ip); m |= ntohl(iph->daddr) > ntohl(info->dst_max.ip); m ^= !!(info->flags & IPRANGE_DST_INV); - if (m) { - pr_debug("dst IP %pI4 NOT in range %s%pI4-%pI4\n", - &iph->daddr, - (info->flags & IPRANGE_DST_INV) ? "(INV) " : "", - &info->dst_min.ip, - &info->dst_max.ip); + if (m) return false; - } } return true; } @@ -73,27 +61,15 @@ iprange_mt6(const struct sk_buff *skb, struct xt_action_param *par) m = iprange_ipv6_lt(&iph->saddr, &info->src_min.in6); m |= iprange_ipv6_lt(&info->src_max.in6, &iph->saddr); m ^= !!(info->flags & IPRANGE_SRC_INV); - if (m) { - pr_debug("src IP %pI6 NOT in range %s%pI6-%pI6\n", - &iph->saddr, - (info->flags & IPRANGE_SRC_INV) ? "(INV) " : "", - &info->src_min.in6, - &info->src_max.in6); + if (m) return false; - } } if (info->flags & IPRANGE_DST) { m = iprange_ipv6_lt(&iph->daddr, &info->dst_min.in6); m |= iprange_ipv6_lt(&info->dst_max.in6, &iph->daddr); m ^= !!(info->flags & IPRANGE_DST_INV); - if (m) { - pr_debug("dst IP %pI6 NOT in range %s%pI6-%pI6\n", - &iph->daddr, - (info->flags & IPRANGE_DST_INV) ? "(INV) " : "", - &info->dst_min.in6, - &info->dst_max.in6); + if (m) return false; - } } return true; } diff --git a/net/netfilter/xt_ipvs.c b/net/netfilter/xt_ipvs.c index 253c71cc9a63..e13c0ffb73a9 100644 --- a/net/netfilter/xt_ipvs.c +++ b/net/netfilter/xt_ipvs.c @@ -148,7 +148,6 @@ ipvs_mt(const struct sk_buff *skb, struct xt_action_param *par) out_put_cp: __ip_vs_conn_put(cp); out: - pr_debug("match=%d\n", match); return match; } diff --git a/net/netfilter/xt_multiport.c b/net/netfilter/xt_multiport.c index a1691ff405d3..bff5f53a9bef 100644 --- a/net/netfilter/xt_multiport.c +++ b/net/netfilter/xt_multiport.c @@ -37,7 +37,6 @@ ports_match_v1(const struct xt_multiport_v1 *minfo, if (minfo->pflags[i]) { /* range port matching */ e = minfo->ports[++i]; - pr_debug("src or dst matches with %d-%d?\n", s, e); switch (minfo->flags) { case XT_MULTIPORT_SOURCE: @@ -58,8 +57,6 @@ ports_match_v1(const struct xt_multiport_v1 *minfo, } } else { /* exact port matching */ - pr_debug("src or dst matches with %d?\n", s); - switch (minfo->flags) { case XT_MULTIPORT_SOURCE: if (src == s) @@ -97,7 +94,6 @@ multiport_mt(const struct sk_buff *skb, struct xt_action_param *par) /* We've been asked to examine this packet, and we * can't. Hence, no choice but to drop. */ - pr_debug("Dropping evil offset=0 tinygram.\n"); par->hotdrop = true; return false; } diff --git a/net/netfilter/xt_sctp.c b/net/netfilter/xt_sctp.c index b46a6a512058..d35c21d9651b 100644 --- a/net/netfilter/xt_sctp.c +++ b/net/netfilter/xt_sctp.c @@ -48,30 +48,17 @@ match_packet(const struct sk_buff *skb, const struct xt_sctp_flag_info *flag_info = info->flag_info; int flag_count = info->flag_count; -#ifdef DEBUG - int i = 0; -#endif - if (chunk_match_type == SCTP_CHUNK_MATCH_ALL) SCTP_CHUNKMAP_COPY(chunkmapcopy, info->chunkmap); do { sch = skb_header_pointer(skb, offset, sizeof(_sch), &_sch); if (sch == NULL || sch->length == 0) { - pr_debug("Dropping invalid SCTP packet.\n"); *hotdrop = true; return false; } -#ifdef DEBUG - pr_debug("Chunk num: %d\toffset: %d\ttype: %d\tlength: %d" - "\tflags: %x\n", - ++i, offset, sch->type, htons(sch->length), - sch->flags); -#endif offset += SCTP_PAD4(ntohs(sch->length)); - pr_debug("skb->len: %d\toffset: %d\n", skb->len, offset); - if (SCTP_CHUNKMAP_IS_SET(info->chunkmap, sch->type)) { switch (chunk_match_type) { case SCTP_CHUNK_MATCH_ANY: @@ -121,18 +108,14 @@ sctp_mt(const struct sk_buff *skb, struct xt_action_param *par) const struct sctphdr *sh; struct sctphdr _sh; - if (par->fragoff != 0) { - pr_debug("Dropping non-first fragment.. FIXME\n"); + if (par->fragoff != 0) return false; - } sh = skb_header_pointer(skb, par->thoff, sizeof(_sh), &_sh); if (sh == NULL) { - pr_debug("Dropping evil TCP offset=0 tinygram.\n"); par->hotdrop = true; return false; } - pr_debug("spt: %d\tdpt: %d\n", ntohs(sh->source), ntohs(sh->dest)); return SCCHECK(ntohs(sh->source) >= info->spts[0] && ntohs(sh->source) <= info->spts[1], diff --git a/net/netfilter/xt_tcpudp.c b/net/netfilter/xt_tcpudp.c index f76cf18f1a24..70608b8d06ab 100644 --- a/net/netfilter/xt_tcpudp.c +++ b/net/netfilter/xt_tcpudp.c @@ -44,8 +44,6 @@ tcp_find_option(u_int8_t option, u_int8_t _opt[60 - sizeof(struct tcphdr)]; unsigned int i; - pr_debug("finding option\n"); - if (!optlen) return invert; @@ -81,10 +79,8 @@ static bool tcp_mt(const struct sk_buff *skb, struct xt_action_param *par) causes this. Its a cracker trying to break in by doing a flag overwrite to pass the direction checks. */ - if (par->fragoff == 1) { - pr_debug("Dropping evil TCP offset=1 frag.\n"); + if (par->fragoff == 1) par->hotdrop = true; - } /* Must not be a fragment. */ return false; } @@ -93,7 +89,6 @@ static bool tcp_mt(const struct sk_buff *skb, struct xt_action_param *par) if (th == NULL) { /* We've been asked to examine this packet, and we can't. Hence, no choice but to drop. */ - pr_debug("Dropping evil TCP offset=0 tinygram.\n"); par->hotdrop = true; return false; } @@ -145,7 +140,6 @@ static bool udp_mt(const struct sk_buff *skb, struct xt_action_param *par) if (uh == NULL) { /* We've been asked to examine this packet, and we can't. Hence, no choice but to drop. */ - pr_debug("Dropping evil UDP tinygram.\n"); par->hotdrop = true; return false; } From 793d9eda4821f75b5f7cc9e6a870b72a58b44c2b Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Tue, 18 Aug 2026 10:31:24 +0200 Subject: [PATCH 1274/1328] netfilter: x_tables: replace pr_{info,err}() by pr_info_ratelimited() Several xtables extension still use pr_err() or pr_info() without ratelimit. For xt_cgroup, while at this, remove redundant "xt_cgroup:" prefix since pr_fmt is already set on. Fixes: c38c4597e4bf ("netfilter: implement xt_cgroup cgroup2 path match") Signed-off-by: Pablo Neira Ayuso --- net/netfilter/xt_cgroup.c | 12 ++++++------ net/netfilter/xt_hl.c | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/net/netfilter/xt_cgroup.c b/net/netfilter/xt_cgroup.c index 43d2ae2be628..28e6cd51b2fb 100644 --- a/net/netfilter/xt_cgroup.c +++ b/net/netfilter/xt_cgroup.c @@ -23,7 +23,7 @@ MODULE_DESCRIPTION("Xtables: process control group matching"); MODULE_ALIAS("ipt_cgroup"); MODULE_ALIAS("ip6t_cgroup"); -#define NET_CLS_CLASSID_INVALID_MSG "xt_cgroup: classid invalid without net_cls cgroups\n" +#define NET_CLS_CLASSID_INVALID_MSG "classid invalid without net_cls cgroups\n" static int cgroup_mt_check_v0(const struct xt_mtchk_param *par) { @@ -33,7 +33,7 @@ static int cgroup_mt_check_v0(const struct xt_mtchk_param *par) return -EINVAL; if (!IS_ENABLED(CONFIG_CGROUP_NET_CLASSID)) { - pr_info(NET_CLS_CLASSID_INVALID_MSG); + pr_info_ratelimited(NET_CLS_CLASSID_INVALID_MSG); return -EINVAL; } @@ -49,7 +49,7 @@ static int cgroup_mt_check_v1(const struct xt_mtchk_param *par) return -EINVAL; if (!info->has_path && !info->has_classid) { - pr_info("xt_cgroup: no path or classid specified\n"); + pr_info_ratelimited("no path or classid specified\n"); return -EINVAL; } @@ -59,7 +59,7 @@ static int cgroup_mt_check_v1(const struct xt_mtchk_param *par) } if (info->has_classid && !IS_ENABLED(CONFIG_CGROUP_NET_CLASSID)) { - pr_info(NET_CLS_CLASSID_INVALID_MSG); + pr_info_ratelimited(NET_CLS_CLASSID_INVALID_MSG); return -EINVAL; } @@ -89,7 +89,7 @@ static int cgroup_mt_check_v2(const struct xt_mtchk_param *par) return -EINVAL; if (!info->has_path && !info->has_classid) { - pr_info("xt_cgroup: no path or classid specified\n"); + pr_info_ratelimited("no path or classid specified\n"); return -EINVAL; } @@ -99,7 +99,7 @@ static int cgroup_mt_check_v2(const struct xt_mtchk_param *par) } if (info->has_classid && !IS_ENABLED(CONFIG_CGROUP_NET_CLASSID)) { - pr_info(NET_CLS_CLASSID_INVALID_MSG); + pr_info_ratelimited(NET_CLS_CLASSID_INVALID_MSG); return -EINVAL; } diff --git a/net/netfilter/xt_hl.c b/net/netfilter/xt_hl.c index 4a12a757ecbf..59e93d97b507 100644 --- a/net/netfilter/xt_hl.c +++ b/net/netfilter/xt_hl.c @@ -28,7 +28,7 @@ static int ttl_mt_check(const struct xt_mtchk_param *par) const struct ipt_ttl_info *info = par->matchinfo; if (info->mode > IPT_TTL_GT) { - pr_err("Unknown TTL match mode: %d\n", info->mode); + pr_info_ratelimited("Unknown TTL match mode: %d\n", info->mode); return -EINVAL; } @@ -59,7 +59,7 @@ static int hl_mt6_check(const struct xt_mtchk_param *par) const struct ip6t_hl_info *info = par->matchinfo; if (info->mode > IP6T_HL_GT) { - pr_err("Unknown Hop Limit match mode: %d\n", info->mode); + pr_info_ratelimited("Unknown Hop Limit match mode: %d\n", info->mode); return -EINVAL; } From 43559058d21e0493aa220ac167e0279334dea5f9 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Wed, 19 Aug 2026 13:42:36 +0200 Subject: [PATCH 1275/1328] netfilter: nf_tables: skip double clone set expressions on element insert Both the dynset and newsetelem path clone the existing set expressions when setting set element expressions if no override expressions are provided. This results in a double clone, once to clone the template set expressions then another clone on the new element. Add a flag to annotate if userspace provides a override expression (ie. expression of the same type of the set but different configuration), otherwise borrow the existing expression from the set. Add conditionals to release expression iif they represent an override. Use this new override_exprs flag to dump the dynset expression override to userspace. This simplifies the existing logic and it also fixes a bug with the connlimit expression which results in a module refcount imbalance WARNING splat when resorting on the default set expressions. Fixes: 65038428b2c6 ("netfilter: nf_tables: allow to specify stateful expression in set definition") Fixes: fca05d4d61e6 ("netfilter: nft_dynset: honor stateful expressions in set definition") Reported-by: Xingyuan Mo Signed-off-by: Pablo Neira Ayuso --- include/net/netfilter/nf_tables.h | 2 -- net/netfilter/nf_tables_api.c | 56 ++++++++++--------------------- net/netfilter/nft_dynset.c | 25 ++++++++------ 3 files changed, 33 insertions(+), 50 deletions(-) diff --git a/include/net/netfilter/nf_tables.h b/include/net/netfilter/nf_tables.h index 238f6ecb90e9..9d597482363d 100644 --- a/include/net/netfilter/nf_tables.h +++ b/include/net/netfilter/nf_tables.h @@ -870,8 +870,6 @@ struct nft_elem_priv *nft_set_elem_init(const struct nft_set *set, const u32 *key, const u32 *key_end, const u32 *data, u64 timeout, u64 expiration, gfp_t gfp); -int nft_set_elem_expr_clone(const struct nft_ctx *ctx, struct nft_set *set, - struct nft_expr *expr_array[]); void nft_set_elem_expr_destroy(const struct nft_ctx *ctx, struct nft_set_elem_expr *elem_expr); void nft_set_elem_destroy(const struct nft_set *set, diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index 71f4227d7ac7..20c562174b27 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -6921,39 +6921,11 @@ static void nft_trans_elems_destroy(const struct nft_ctx *ctx, nf_tables_set_elem_destroy(ctx, te->set, te->elems[i].priv); } -int nft_set_elem_expr_clone(const struct nft_ctx *ctx, struct nft_set *set, - struct nft_expr *expr_array[]) -{ - struct nft_expr *expr; - int err, i, k; - - for (i = 0; i < set->num_exprs; i++) { - expr = kzalloc(set->exprs[i]->ops->size, GFP_KERNEL_ACCOUNT); - if (!expr) - goto err_expr; - - err = nft_expr_clone(expr, set->exprs[i], GFP_KERNEL_ACCOUNT); - if (err < 0) { - kfree(expr); - goto err_expr; - } - expr_array[i] = expr; - } - - return 0; - -err_expr: - for (k = i - 1; k >= 0; k--) - nft_expr_destroy(ctx, expr_array[k]); - - return -ENOMEM; -} - static int nft_set_elem_expr_setup(struct nft_ctx *ctx, const struct nft_set_ext_tmpl *tmpl, const struct nft_set_ext *ext, struct nft_expr *expr_array[], - u32 num_exprs) + u32 num_exprs, bool override_exprs) { struct nft_set_elem_expr *elem_expr = nft_set_ext_expr(ext); u32 len = sizeof(struct nft_set_elem_expr); @@ -6976,7 +6948,8 @@ static int nft_set_elem_expr_setup(struct nft_ctx *ctx, goto err_elem_expr_setup; elem_expr->size += expr_array[i]->ops->size; - nft_expr_destroy(ctx, expr_array[i]); + if (override_exprs) + nft_expr_destroy(ctx, expr_array[i]); expr_array[i] = NULL; } @@ -6984,7 +6957,9 @@ static int nft_set_elem_expr_setup(struct nft_ctx *ctx, err_elem_expr_setup: for (; i < num_exprs; i++) { - nft_expr_destroy(ctx, expr_array[i]); + if (override_exprs) + nft_expr_destroy(ctx, expr_array[i]); + expr_array[i] = NULL; } @@ -7280,6 +7255,7 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set, struct nft_set_binding *binding; struct nft_elem_priv *elem_priv; struct nft_object *obj = NULL; + bool override_exprs = false; struct nft_userdata *udata; struct nft_data_desc desc; enum nft_registers dreg; @@ -7385,6 +7361,7 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set, expr_array[0] = expr; num_exprs = 1; + override_exprs = true; if (set->num_exprs && set->exprs[0]->ops != expr->ops) { err = -EOPNOTSUPP; @@ -7413,6 +7390,7 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set, } expr_array[i] = expr; num_exprs++; + override_exprs = true; if (set->num_exprs && expr->ops != set->exprs[i]->ops) { err = -EOPNOTSUPP; @@ -7426,9 +7404,8 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set, } } else if (set->num_exprs > 0 && !(flags & NFT_SET_ELEM_INTERVAL_END)) { - err = nft_set_elem_expr_clone(ctx, set, expr_array); - if (err < 0) - goto err_set_elem_expr_clone; + for (i = 0; i < set->num_exprs; i++) + expr_array[i] = set->exprs[i]; num_exprs = set->num_exprs; } @@ -7567,7 +7544,8 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set, udata->len = ulen - 1; nla_memcpy(&udata->data, nla[NFTA_SET_ELEM_USERDATA], ulen); } - err = nft_set_elem_expr_setup(ctx, &tmpl, ext, expr_array, num_exprs); + err = nft_set_elem_expr_setup(ctx, &tmpl, ext, expr_array, num_exprs, + override_exprs); if (err < 0) goto err_elem_free; @@ -7675,9 +7653,11 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set, err_parse_key: nft_data_release(&elem.key.val, NFT_DATA_VALUE); err_set_elem_expr: - for (i = 0; i < num_exprs && expr_array[i]; i++) - nft_expr_destroy(ctx, expr_array[i]); -err_set_elem_expr_clone: + if (override_exprs) { + for (i = 0; i < num_exprs && expr_array[i]; i++) + nft_expr_destroy(ctx, expr_array[i]); + } + return err; } diff --git a/net/netfilter/nft_dynset.c b/net/netfilter/nft_dynset.c index ee9d3e7b1ecf..fa4da694e92d 100644 --- a/net/netfilter/nft_dynset.c +++ b/net/netfilter/nft_dynset.c @@ -19,7 +19,8 @@ struct nft_dynset { u8 sreg_key; u8 sreg_data; bool invert; - bool expr; + bool expr:1, + override_exprs:1; u8 num_exprs; u64 timeout; struct nft_expr *expr_array[NFT_SET_EXPR_MAX]; @@ -257,6 +258,7 @@ static int nft_dynset_init(const struct nft_ctx *ctx, priv->num_exprs++; priv->expr_array[0] = dynset_expr; + priv->override_exprs = true; if (set->num_exprs > 1 || (set->num_exprs == 1 && @@ -289,6 +291,7 @@ static int nft_dynset_init(const struct nft_ctx *ctx, } priv->expr_array[i] = dynset_expr; priv->num_exprs++; + priv->override_exprs = true; if (set->num_exprs) { if (i >= set->num_exprs) { @@ -307,9 +310,8 @@ static int nft_dynset_init(const struct nft_ctx *ctx, goto err_expr_free; } } else if (set->num_exprs > 0) { - err = nft_set_elem_expr_clone(ctx, set, priv->expr_array); - if (err < 0) - return err; + for (i = 0; i < set->num_exprs; i++) + priv->expr_array[i] = set->exprs[i]; priv->num_exprs = set->num_exprs; } @@ -339,8 +341,10 @@ static int nft_dynset_init(const struct nft_ctx *ctx, return 0; err_expr_free: - for (i = 0; i < priv->num_exprs; i++) - nft_expr_destroy(ctx, priv->expr_array[i]); + if (priv->override_exprs) { + for (i = 0; i < priv->num_exprs; i++) + nft_expr_destroy(ctx, priv->expr_array[i]); + } return err; } @@ -367,9 +371,10 @@ static void nft_dynset_destroy(const struct nft_ctx *ctx, struct nft_dynset *priv = nft_expr_priv(expr); int i; - for (i = 0; i < priv->num_exprs; i++) - nft_expr_destroy(ctx, priv->expr_array[i]); - + if (priv->override_exprs) { + for (i = 0; i < priv->num_exprs; i++) + nft_expr_destroy(ctx, priv->expr_array[i]); + } nf_tables_destroy_set(ctx, priv->set); } @@ -393,7 +398,7 @@ static int nft_dynset_dump(struct sk_buff *skb, nf_jiffies64_to_msecs(priv->timeout), NFTA_DYNSET_PAD)) goto nla_put_failure; - if (priv->set->num_exprs == 0) { + if (priv->set->num_exprs == 0 || priv->override_exprs) { if (priv->num_exprs == 1) { if (nft_expr_dump(skb, NFTA_DYNSET_EXPR, priv->expr_array[0], reset)) From 132a02beb46fc4d497c41c81ed0dda7956fa4171 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Thu, 20 Aug 2026 10:26:32 +0200 Subject: [PATCH 1276/1328] netfilter: nf_tables: set on dead bit when performing early element removal .commit call for sets is skipped if set->dead flag is set on, but this flag is set on later in the commit path. This also reintroduces the bug fixed in commit 7315dc1e122c8 ("netfilter: nf_tables: skip set commit for deleted/destroyed sets"). Fixes: 1e3b9e1c77fe ("netfilter: nf_tables: call set ops .commit when building new ruleset blob") Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_tables_api.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index 20c562174b27..9b776f402d14 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -10874,6 +10874,10 @@ static void nft_set_commit_update(struct nft_ctx *ctx, nft_ctx_update(ctx, trans); switch (trans->msg_type) { + case NFT_MSG_DELSET: + case NFT_MSG_DESTROYSET: + nft_trans_set(trans)->dead = 1; + break; case NFT_MSG_DELSETELEM: te = nft_trans_container_elem(trans); if (!te->set->ops->commit) From fc04229727d8fffbf02e0635de38413fe0102d02 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Fri, 21 Aug 2026 12:25:55 +0200 Subject: [PATCH 1277/1328] netfilter: nf_tables: remove leftover set_update_list This list has been moved to per-netns, remove onstack list which is not used anymore. Fixes: b343ededb3f9 ("netfilter: nf_tables: move set_update_list to nftables per-netns") Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_tables_api.c | 1 - 1 file changed, 1 deletion(-) diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index 9b776f402d14..765a92fa90d6 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -11295,7 +11295,6 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) { struct nftables_pernet *nft_net = nft_pernet(net); struct nft_trans *trans, *next; - LIST_HEAD(set_update_list); struct nft_trans_elem *te; struct nft_ctx ctx = { .net = net, From 74e3b979ce8b78a690f8b94ccf2e2c965f7f5c11 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 27 Aug 2026 13:39:03 +0200 Subject: [PATCH 1278/1328] ALSA: control: Don't add invalid kcontrols to LED layer The kcontrol LED state layer tries to track the all associated kcontrol elements with naive assumptions that they are readable. But one can create a write-only element that has no get callback (even a user element can do it), and this may lead to a NULL dereference at the call chain of snd_ctl_led_notify(), as found by syzkaller. For avoiding the Oops, add a sanity check of the kcontrol's info and get callbacks, and just skip the invalid kcontrols before assigning the kctl to the LED layer. Reported-by: syzbot+b7fe2760ea6f1ee44b4d@syzkaller.appspotmail.com Closes: https://lore.kernel.org/6a9007b3.1d9ded08.62e62.00cd.GAE@google.com Fixes: 22d8de62f11b ("ALSA: control - add generic LED trigger module as the new control layer") Reviewed-by: Jaroslav Kysela Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260827113951.893291-1-tiwai@suse.de --- sound/core/control_led.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/core/control_led.c b/sound/core/control_led.c index 8cbacee57ce7..3d13bbec1c54 100644 --- a/sound/core/control_led.c +++ b/sound/core/control_led.c @@ -255,6 +255,8 @@ static int snd_ctl_led_set_id(int card_number, struct snd_ctl_elem_id *id, kctl = snd_ctl_find_id(card, id); if (!kctl) return -ENOENT; + if (!kctl->info || !kctl->get) + return -EINVAL; ioff = snd_ctl_get_ioff(kctl, id); vd = &kctl->vd[ioff]; access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK; From f5b8b9037df387394a73aab47c5437bbac975077 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Thu, 20 Aug 2026 00:27:12 +0200 Subject: [PATCH 1279/1328] arm64: compat: Fix decrementing LDM/STM alignment emulation The compat alignment emulator inherited unsigned long data addresses from the 32-bit ARM implementation. In do_alignment_ldmstm(), nr_regs is an unsigned int holding the transfer size. The function uses the same address addition for both transfer directions, negating nr_regs first for a decrementing LDM or STM. The 32-bit negation wraps before the addition, so the handler adds nearly 4 GiB instead of subtracting the transfer size. The resulting address lies outside the compat task's address space, so decrementing LDM/STM emulation fails, while incrementing forms work. For example, a backwards-moving copy routine using decrementing LDM/STM can take an alignment fault when called with unaligned pointers. The compat handler should emulate the transfer, but this bug instead causes SIGBUS. The offset negated in do_alignment_finish_ldst() is offset_union.un, which is already unsigned long and does not have this width mismatch. Make nr_regs unsigned long so its negation and the address arithmetic use the same width. Fixes: 3fc24ef32d3b ("arm64: compat: Implement misalignment fixups for multiword loads") Cc: stable@vger.kernel.org Suggested-by: Arnd Bergmann Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Karl Mehltretter Signed-off-by: Will Deacon --- arch/arm64/kernel/compat_alignment.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/kernel/compat_alignment.c b/arch/arm64/kernel/compat_alignment.c index b68e1d328d4c..9b58d0aa38d2 100644 --- a/arch/arm64/kernel/compat_alignment.c +++ b/arch/arm64/kernel/compat_alignment.c @@ -114,8 +114,8 @@ do_alignment_ldrdstrd(unsigned long addr, u32 instr, struct pt_regs *regs) static int do_alignment_ldmstm(unsigned long addr, u32 instr, struct pt_regs *regs) { - unsigned int rd, rn, nr_regs, regbits; - unsigned long eaddr, newaddr; + unsigned int rd, rn, regbits; + unsigned long eaddr, newaddr, nr_regs; unsigned int val; /* count the number of registers in the mask to be transferred */ From 8d2237e9d6902e234bb89aabb9cd6a9e91357223 Mon Sep 17 00:00:00 2001 From: Muhammad Usama Anjum Date: Tue, 25 Aug 2026 12:18:34 +0100 Subject: [PATCH 1280/1328] selftests/arm64: Print missing MTE TAP headers Most MTE tests set a TAP plan and emit results without first printing the TAP version header. Direct execution therefore starts with a plan such as "1..20" instead of "TAP version 13". The problem is particularly visible in the GCR_EL1 context-switch test. It prints its plan before forking 1,024 child processes. When stdout is fully buffered, the plan remains in the stdio buffer. Each child inherits the pending "1..1" line and flushes its copy from exit(), producing repeated plan lines. ksft_print_header() prints the TAP header and enables line buffering. Call it in every MTE test that is missing it. In the GCR_EL1 test, call it before the plan so the plan is flushed before the children are forked. In the remaining tests, call it before setup and prerequisite checks so early failures and whole-test skips also retain the header. Fixes: 29f080881601 ("kselftest/arm64: check GCR_EL1 after context switch") Signed-off-by: Muhammad Usama Anjum Reviewed-by: Vincenzo Frascino Reviewed-by: Mark Brown Signed-off-by: Will Deacon --- tools/testing/selftests/arm64/mte/check_buffer_fill.c | 2 ++ tools/testing/selftests/arm64/mte/check_child_memory.c | 2 ++ tools/testing/selftests/arm64/mte/check_gcr_el1_cswitch.c | 1 + tools/testing/selftests/arm64/mte/check_hugetlb_options.c | 2 ++ tools/testing/selftests/arm64/mte/check_ksm_options.c | 2 ++ tools/testing/selftests/arm64/mte/check_mmap_options.c | 2 ++ tools/testing/selftests/arm64/mte/check_tags_inclusion.c | 2 ++ tools/testing/selftests/arm64/mte/check_user_mem.c | 2 ++ 8 files changed, 15 insertions(+) diff --git a/tools/testing/selftests/arm64/mte/check_buffer_fill.c b/tools/testing/selftests/arm64/mte/check_buffer_fill.c index ff4e07503349..039b1d7d8566 100644 --- a/tools/testing/selftests/arm64/mte/check_buffer_fill.c +++ b/tools/testing/selftests/arm64/mte/check_buffer_fill.c @@ -406,6 +406,8 @@ int main(int argc, char *argv[]) size_t page_size = getpagesize(); int item = ARRAY_SIZE(sizes); + ksft_print_header(); + sizes[item - 3] = page_size - 1; sizes[item - 2] = page_size; sizes[item - 1] = page_size + 1; diff --git a/tools/testing/selftests/arm64/mte/check_child_memory.c b/tools/testing/selftests/arm64/mte/check_child_memory.c index 5e97ee792e4d..e6a8acca2a94 100644 --- a/tools/testing/selftests/arm64/mte/check_child_memory.c +++ b/tools/testing/selftests/arm64/mte/check_child_memory.c @@ -146,6 +146,8 @@ int main(int argc, char *argv[]) int err; int item = ARRAY_SIZE(sizes); + ksft_print_header(); + page_size = getpagesize(); if (!page_size) { ksft_print_msg("ERR: Unable to get page size\n"); diff --git a/tools/testing/selftests/arm64/mte/check_gcr_el1_cswitch.c b/tools/testing/selftests/arm64/mte/check_gcr_el1_cswitch.c index 325bca0de0f6..d23f154d3288 100644 --- a/tools/testing/selftests/arm64/mte/check_gcr_el1_cswitch.c +++ b/tools/testing/selftests/arm64/mte/check_gcr_el1_cswitch.c @@ -131,6 +131,7 @@ int main(int argc, char *argv[]) if (err) return err; + ksft_print_header(); ksft_set_plan(1); evaluate_test(mte_gcr_fork_test(), diff --git a/tools/testing/selftests/arm64/mte/check_hugetlb_options.c b/tools/testing/selftests/arm64/mte/check_hugetlb_options.c index aad1234c7e0f..23e4a7a9950c 100644 --- a/tools/testing/selftests/arm64/mte/check_hugetlb_options.c +++ b/tools/testing/selftests/arm64/mte/check_hugetlb_options.c @@ -230,6 +230,8 @@ int main(int argc, char *argv[]) void *map_ptr; unsigned long map_size; + ksft_print_header(); + err = mte_default_setup(); if (err) return err; diff --git a/tools/testing/selftests/arm64/mte/check_ksm_options.c b/tools/testing/selftests/arm64/mte/check_ksm_options.c index 0cf5faef1724..866f0929b664 100644 --- a/tools/testing/selftests/arm64/mte/check_ksm_options.c +++ b/tools/testing/selftests/arm64/mte/check_ksm_options.c @@ -132,6 +132,8 @@ int main(int argc, char *argv[]) { int err; + ksft_print_header(); + err = mte_default_setup(); if (err) return err; diff --git a/tools/testing/selftests/arm64/mte/check_mmap_options.c b/tools/testing/selftests/arm64/mte/check_mmap_options.c index c100af3012cb..492f2cd41f43 100644 --- a/tools/testing/selftests/arm64/mte/check_mmap_options.c +++ b/tools/testing/selftests/arm64/mte/check_mmap_options.c @@ -945,6 +945,8 @@ int main(int argc, char *argv[]) }, }; + ksft_print_header(); + err = mte_default_setup(); if (err) return err; diff --git a/tools/testing/selftests/arm64/mte/check_tags_inclusion.c b/tools/testing/selftests/arm64/mte/check_tags_inclusion.c index 4b764f2a8185..6b4fa6705d7c 100644 --- a/tools/testing/selftests/arm64/mte/check_tags_inclusion.c +++ b/tools/testing/selftests/arm64/mte/check_tags_inclusion.c @@ -175,6 +175,8 @@ int main(int argc, char *argv[]) { int err; + ksft_print_header(); + err = mte_default_setup(); if (err) return err; diff --git a/tools/testing/selftests/arm64/mte/check_user_mem.c b/tools/testing/selftests/arm64/mte/check_user_mem.c index fb7936c4e097..af343aa61732 100644 --- a/tools/testing/selftests/arm64/mte/check_user_mem.c +++ b/tools/testing/selftests/arm64/mte/check_user_mem.c @@ -201,6 +201,8 @@ int main(int argc, char *argv[]) int tag_offsets[] = {page_sz, MT_GRANULE_SIZE}; char test_name[TEST_NAME_MAX]; + ksft_print_header(); + page_sz = getpagesize(); if (!page_sz) { ksft_print_msg("ERR: Unable to get page size\n"); From 1a0dba077f34a2f8faa98308d30d4b546d073145 Mon Sep 17 00:00:00 2001 From: Muhammad Usama Anjum Date: Tue, 25 Aug 2026 12:18:35 +0100 Subject: [PATCH 1281/1328] selftests/arm64: Treat KSM merge_across_nodes as optional The MTE KSM test requires write access to KSM sysfs but does not check that it is running as root. It also unconditionally saves, enables and restores the merge_across_nodes attribute. The kernel only creates this attribute when CONFIG_NUMA=y, so a non-NUMA kernel prints the following message three times even though every KSM subtest passes: # ERR: missing /sys/kernel/mm/ksm/merge_across_nodes Skip the test when it is not running as root. Check that the optional attribute is readable and writable, treating ENOENT as its expected absence on non-NUMA kernels and skipping the test for other access failures. Only save, enable and restore the attribute when it is available. Check MTE availability before the privilege and sysfs checks so systems without MTE retain the existing feature-unavailable skip result. This preserves the existing behavior on NUMA kernels without requiring NUMA or reducing KSM coverage on single-node systems. Fixes: f981d8fa2646 ("kselftest/arm64: Verify KSM page merge for MTE pages") Signed-off-by: Muhammad Usama Anjum Reviewed-by: Vincenzo Frascino Reviewed-by: Mark Brown Signed-off-by: Will Deacon --- .../selftests/arm64/mte/check_ksm_options.c | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/arm64/mte/check_ksm_options.c b/tools/testing/selftests/arm64/mte/check_ksm_options.c index 866f0929b664..4855b737d550 100644 --- a/tools/testing/selftests/arm64/mte/check_ksm_options.c +++ b/tools/testing/selftests/arm64/mte/check_ksm_options.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -22,6 +23,20 @@ static size_t page_sz; static unsigned long ksm_sysfs[5]; +static bool has_merge_across_nodes; + +static bool merge_across_nodes_available(void) +{ + const char *path = PATH_KSM "merge_across_nodes"; + + if (!access(path, R_OK | W_OK)) + return true; + if (errno == ENOENT) + return false; + + ksft_exit_skip("Unable to read and write %s: %s\n", path, + strerror(errno)); +} static unsigned long read_sysfs(char *str) { @@ -56,8 +71,10 @@ static void write_sysfs(char *str, unsigned long val) static void mte_ksm_setup(void) { - ksm_sysfs[0] = read_sysfs(PATH_KSM "merge_across_nodes"); - write_sysfs(PATH_KSM "merge_across_nodes", 1); + if (has_merge_across_nodes) { + ksm_sysfs[0] = read_sysfs(PATH_KSM "merge_across_nodes"); + write_sysfs(PATH_KSM "merge_across_nodes", 1); + } ksm_sysfs[1] = read_sysfs(PATH_KSM "sleep_millisecs"); write_sysfs(PATH_KSM "sleep_millisecs", 0); ksm_sysfs[2] = read_sysfs(PATH_KSM "run"); @@ -70,7 +87,8 @@ static void mte_ksm_setup(void) static void mte_ksm_restore(void) { - write_sysfs(PATH_KSM "merge_across_nodes", ksm_sysfs[0]); + if (has_merge_across_nodes) + write_sysfs(PATH_KSM "merge_across_nodes", ksm_sysfs[0]); write_sysfs(PATH_KSM "sleep_millisecs", ksm_sysfs[1]); write_sysfs(PATH_KSM "run", ksm_sysfs[2]); write_sysfs(PATH_KSM "max_page_sharing", ksm_sysfs[3]); @@ -137,6 +155,11 @@ int main(int argc, char *argv[]) err = mte_default_setup(); if (err) return err; + + if (geteuid() != 0) + ksft_exit_skip("Please run the test as root\n"); + + has_merge_across_nodes = merge_across_nodes_available(); page_sz = getpagesize(); if (!page_sz) { ksft_print_msg("ERR: Unable to get page size\n"); From bb52892f9234e4ecd982fa51222aab33ce282f79 Mon Sep 17 00:00:00 2001 From: Muhammad Usama Anjum Date: Tue, 25 Aug 2026 12:18:36 +0100 Subject: [PATCH 1282/1328] selftests/arm64: Fix MTE prctl TAP plan The MTE prctl test emits one result from check_basic_read() followed by one result for each of the seven entries in mte_modes[]. However, the TAP plan only accounts for the array entries, producing: # Planned tests != run tests (7 != 8) Include the basic read check in the plan so that all eight emitted results are declared. Reviewed-by: Mark Brown Fixes: 1f488fb91378 ("kselftest/arm64/mte: Add MTE_STORE_ONLY testcases") Signed-off-by: Muhammad Usama Anjum Reviewed-by: Vincenzo Frascino Signed-off-by: Will Deacon --- tools/testing/selftests/arm64/mte/check_prctl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/arm64/mte/check_prctl.c b/tools/testing/selftests/arm64/mte/check_prctl.c index f7f320defa7b..d16a91117eef 100644 --- a/tools/testing/selftests/arm64/mte/check_prctl.c +++ b/tools/testing/selftests/arm64/mte/check_prctl.c @@ -119,7 +119,7 @@ int main(void) int i; ksft_print_header(); - ksft_set_plan(ARRAY_SIZE(mte_modes)); + ksft_set_plan(ARRAY_SIZE(mte_modes) + 1); check_basic_read(); for (i = 0; i < ARRAY_SIZE(mte_modes); i++) From 2bd533739234d79b74afabfece1abfe9c6d52c83 Mon Sep 17 00:00:00 2001 From: Muhammad Usama Anjum Date: Tue, 25 Aug 2026 12:18:37 +0100 Subject: [PATCH 1283/1328] selftests/arm64: Add MTE test config fragment The arm64 selftest collection has no Kconfig fragment, so kernels built with the selftest requirements are not guaranteed to provide the support used by these tests. Add a fragment covering all tests in arm64. Reviewed-by: Mark Brown Signed-off-by: Muhammad Usama Anjum Reviewed-by: Vincenzo Frascino Signed-off-by: Will Deacon --- tools/testing/selftests/arm64/config | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 tools/testing/selftests/arm64/config diff --git a/tools/testing/selftests/arm64/config b/tools/testing/selftests/arm64/config new file mode 100644 index 000000000000..0fa975585392 --- /dev/null +++ b/tools/testing/selftests/arm64/config @@ -0,0 +1,17 @@ +CONFIG_ARM64_BTI=y +CONFIG_ARM64_GCS=y +CONFIG_ARM64_MTE=y +CONFIG_ARM64_POE=y +CONFIG_ARM64_PTR_AUTH=y +CONFIG_ARM64_SME=y +CONFIG_ARM64_SVE=y +CONFIG_ARM64_TAGGED_ADDR_ABI=y +CONFIG_HUGETLBFS=y +CONFIG_KSM=y +CONFIG_PROC_FS=y +CONFIG_SECCOMP=y +CONFIG_SECCOMP_FILTER=y +CONFIG_SHMEM=y +CONFIG_SYSCTL=y +CONFIG_SYSFS=y +CONFIG_TMPFS=y From 5bb01c657ff9fc807c2c592ca18af34c4fc3bc6f Mon Sep 17 00:00:00 2001 From: Abdurrahman Hussain Date: Wed, 5 Aug 2026 13:31:00 -0700 Subject: [PATCH 1284/1328] of: fix out-of-bounds read in of_alias_scan() stem parser The stem parser tests isdigit(*(end - 1)) before checking end > start and so reads one byte before the property name when the name is empty or all digits. Check the bound first. Fixes: 611cad720148 ("dt: add of_alias_scan and of_alias_get_id") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-fable-5 [Claude Code] Signed-off-by: Abdurrahman Hussain Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260805-nh-of-alias-overlay-v6-1-74f21d440819@nexthop.ai Signed-off-by: Rob Herring (Arm) --- drivers/of/base.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/of/base.c b/drivers/of/base.c index dd7dda0e0a57..378703dbc11f 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -1971,7 +1971,7 @@ void of_alias_scan(void * (*dt_alloc)(u64 size, u64 align)) /* walk the alias backwards to extract the id and work out * the 'stem' string */ - while (isdigit(*(end-1)) && end > start) + while (end > start && isdigit(*(end - 1))) end--; len = end - start; From 6fe7e31a45e3418a39e6343a85126124feec1c2f Mon Sep 17 00:00:00 2001 From: Daniel Pawlik Date: Thu, 20 Aug 2026 10:59:40 +0200 Subject: [PATCH 1285/1328] net: airoha: npu: fix missing streaming DMA mask The driver calls dma_set_coherent_mask() but never dma_set_mask(), leaving the streaming DMA mask at the bus default. On the non-coherent EN7581 platform (Cortex-A53), this causes the NPU mailbox to hang after approximately 41 calls when using streaming DMA mappings. Replace dma_set_coherent_mask() with dma_set_mask_and_coherent() to set both the streaming and coherent DMA masks, matching standard driver practice. Fixes: 6f884eb87a79 ("net: airoha: Fix DMA direction for NPU mailbox buffer") Link: https://patchwork.kernel.org/project/linux-mediatek/patch/20260814110017.2795022-1-pawlik.dan@gmail.com/ Link: https://patchwork.kernel.org/project/linux-mediatek/patch/20260809152813.585797-1-pawlik.dan@gmail.com/ Link: https://patchwork.kernel.org/project/linux-mediatek/patch/20260805070851.2885888-1-pawlik.dan@gmail.com/ Signed-off-by: Daniel Pawlik Acked-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260820085941.380401-1-pawlik.dan@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/airoha/airoha_npu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/airoha/airoha_npu.c b/drivers/net/ethernet/airoha/airoha_npu.c index de75376db194..5bb4817a898d 100644 --- a/drivers/net/ethernet/airoha/airoha_npu.c +++ b/drivers/net/ethernet/airoha/airoha_npu.c @@ -768,7 +768,7 @@ static int airoha_npu_probe(struct platform_device *pdev) npu->irqs[i] = irq; } - err = dma_set_coherent_mask(dev, DMA_BIT_MASK(32)); + err = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); if (err) return err; From dc170da3347e0f7b6d120d13882c4e1f04ca00d6 Mon Sep 17 00:00:00 2001 From: Alice Mikityanska Date: Sat, 22 Aug 2026 15:03:05 +0300 Subject: [PATCH 1286/1328] selftests: net: Wait for netserver to launch Use wait_local_port_listen after starting netserver in big_tcp_tunnels.sh to ensure it's listening when the test starts. Fixes: 5cb53743e1ff ("selftests: net: Add a test for BIG TCP in UDP tunnels") Signed-off-by: Alice Mikityanska Acked-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260822120308.1165200-2-alice.kernel@fastmail.im Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/big_tcp_tunnels.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/testing/selftests/net/big_tcp_tunnels.sh b/tools/testing/selftests/net/big_tcp_tunnels.sh index d6513ed8d4e8..9b7d0456bcc1 100755 --- a/tools/testing/selftests/net/big_tcp_tunnels.sh +++ b/tools/testing/selftests/net/big_tcp_tunnels.sh @@ -3,6 +3,8 @@ # # Testing for IPv4 and IPv6 BIG TCP over VXLAN and GENEVE tunnels. +source "$(dirname "$0")/lib.sh" + SERVER_NS=$(mktemp -u server-XXXXXXXX) SERVER_IP4="192.168.1.1" SERVER_IP6="2001:db8::1:1" @@ -39,6 +41,7 @@ setup() { gro_max_size 196608 gro_ipv4_max_size 196608 ip netns exec "$SERVER_NS" netserver >/dev/null + wait_local_port_listen "$SERVER_NS" 12865 tcp } setup_tunnel() { From bb42c16f489f10144f7d2fbb1f57753f14e12ac8 Mon Sep 17 00:00:00 2001 From: Alice Mikityanska Date: Sat, 22 Aug 2026 15:03:06 +0300 Subject: [PATCH 1287/1328] selftests: net: Lower threshold on debug kernels for big_tcp_tunnels.sh Debug kernels on upstream CI runners run slower and generate fewer BIG TCP packets, making the test flaky on upstream CI runners. Lower the default threshold for those kernels. Fixes: 5cb53743e1ff ("selftests: net: Add a test for BIG TCP in UDP tunnels") Signed-off-by: Alice Mikityanska Acked-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260822120308.1165200-3-alice.kernel@fastmail.im Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/big_tcp_tunnels.sh | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/net/big_tcp_tunnels.sh b/tools/testing/selftests/net/big_tcp_tunnels.sh index 9b7d0456bcc1..e0b41f394ebb 100755 --- a/tools/testing/selftests/net/big_tcp_tunnels.sh +++ b/tools/testing/selftests/net/big_tcp_tunnels.sh @@ -17,11 +17,18 @@ CLIENT_IP6="2001:db8::1:2" CLIENT_IP4_TUN="192.168.2.2" CLIENT_IP6_TUN="2001:db8::2:2" -: "${PACKETS_THRESHOLD:=1000}" - # Kselftest framework requirement - SKIP code is 4. ksft_skip=4 +if [ -z "$PACKETS_THRESHOLD" ]; then + if [ "$KSFT_MACHINE_SLOW" = yes ]; then + echo 'Debug kernel detected, lowering the default threshold' + PACKETS_THRESHOLD=100 + else + PACKETS_THRESHOLD=1000 + fi +fi + setup() { ip netns add "$SERVER_NS" ip netns add "$CLIENT_NS" From f7d0400bd3452ed6915592b1929ba1d39d8e6552 Mon Sep 17 00:00:00 2001 From: Alice Mikityanska Date: Sat, 22 Aug 2026 15:03:07 +0300 Subject: [PATCH 1288/1328] selftests: net: Lower threshold with csum offload off in big_tcp_tunnels.sh With checksum offload disabled, much fewer BIG TCP packets are generated due to overall loss of throughput. Use a separate threshold in these tests, which is 1/10 of the threshold set for the rest of tests. Fixes: 5cb53743e1ff ("selftests: net: Add a test for BIG TCP in UDP tunnels") Signed-off-by: Alice Mikityanska Acked-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260822120308.1165200-4-alice.kernel@fastmail.im Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/big_tcp_tunnels.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/net/big_tcp_tunnels.sh b/tools/testing/selftests/net/big_tcp_tunnels.sh index e0b41f394ebb..003878df7415 100755 --- a/tools/testing/selftests/net/big_tcp_tunnels.sh +++ b/tools/testing/selftests/net/big_tcp_tunnels.sh @@ -107,6 +107,8 @@ cleanup() { } do_test() { + local packets_threshold="$PACKETS_THRESHOLD" + # When tx csum offload is off, software GSO is performed before passing the # packet to veth. Check BIG TCP packets inside the VXLAN tunnel to verify # the software checksum path: if the checksum code is broken, these packets @@ -125,6 +127,7 @@ do_test() { else IPTABLES=ip6tables fi + packets_threshold=$(( PACKETS_THRESHOLD / 10 )) fi if [ "$2" = 4 ]; then IPTABLES_SACK=iptables @@ -157,8 +160,8 @@ do_test() { echo "Captured BIG TCP RX packets: $PACKETS_SERVER" echo "Captured BIG TCP TX packets: $PACKETS_CLIENT" echo "Captured TCP SACK packets: $PACKETS_SACK" - [ "$PACKETS_SERVER" -gt "$PACKETS_THRESHOLD" ] || return 1 - [ "$PACKETS_CLIENT" -gt "$PACKETS_THRESHOLD" ] || return 1 + [ "$PACKETS_SERVER" -gt "$packets_threshold" ] || return 1 + [ "$PACKETS_CLIENT" -gt "$packets_threshold" ] || return 1 [ "$PACKETS_SACK" -lt "$(( PACKETS_CLIENT / 2 ))" ] || return 1 } From 1d62b83fb75125344693db8ebf970653db529f40 Mon Sep 17 00:00:00 2001 From: Alice Mikityanska Date: Sat, 22 Aug 2026 15:03:08 +0300 Subject: [PATCH 1289/1328] selftests: net: Fix slow configurations in big_tcp_tunnels.sh The combination of checksum offload disabled (that causes software GSO) and a debug kernel is inherently slow. Depending on the CPU power and load, RTT may increase, limiting sk_pacing_rate, so tcp_tso_autosize caps SKBs at around 40 segments, and zero BIG TCP packets are produced. Increase sysctl net.ipv4.tcp_min_tso_segs and set a bigger initial value of CWND in these configurations to force BIG TCP. Fixes: 5cb53743e1ff ("selftests: net: Add a test for BIG TCP in UDP tunnels") Signed-off-by: Alice Mikityanska Acked-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260822120308.1165200-5-alice.kernel@fastmail.im Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/big_tcp_tunnels.sh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tools/testing/selftests/net/big_tcp_tunnels.sh b/tools/testing/selftests/net/big_tcp_tunnels.sh index 003878df7415..cc0875e52fb9 100755 --- a/tools/testing/selftests/net/big_tcp_tunnels.sh +++ b/tools/testing/selftests/net/big_tcp_tunnels.sh @@ -49,6 +49,8 @@ setup() { ip netns exec "$SERVER_NS" netserver >/dev/null wait_local_port_listen "$SERVER_NS" 12865 tcp + + DEFAULT_TCP_MIN_TSO_SEGS=$(ip netns exec "$CLIENT_NS" sysctl -n net.ipv4.tcp_min_tso_segs) } setup_tunnel() { @@ -135,6 +137,21 @@ do_test() { IPTABLES_SACK=ip6tables fi + if [ "$3" != 'on' ] && [ "$KSFT_MACHINE_SLOW" = yes ]; then + echo 'Slow configuration; increasing net.ipv4.tcp_min_tso_segs and initcwnd' + ip netns exec "$CLIENT_NS" sysctl -w net.ipv4.tcp_min_tso_segs=52 + if [ "$2" = 4 ]; then + ip -netns "$CLIENT_NS" \ + route change 192.168.2.0/24 dev tun0 initcwnd 100 + else + ip -netns "$CLIENT_NS" -6 \ + route change 2001:db8::2:0/112 dev tun0 initcwnd 100 + fi + else + ip netns exec "$CLIENT_NS" \ + sysctl -w net.ipv4.tcp_min_tso_segs="$DEFAULT_TCP_MIN_TSO_SEGS" + fi + ip netns exec "$SERVER_NS" "$IPTABLES" -w -t raw -I PREROUTING -i "${CAPTURE_IFACE}1" -m length ! --length 0:65535 -m comment --comment "bigtcp" ip netns exec "$CLIENT_NS" "$IPTABLES" -w -t raw -I OUTPUT -o "${CAPTURE_IFACE}0" -m length ! --length 0:65535 -m comment --comment "bigtcp" ip netns exec "$SERVER_NS" "$IPTABLES_SACK" -w -t raw -I OUTPUT -o "tun1" -p tcp -m tcp --tcp-flags ACK ACK --tcp-option 5 -m comment --comment "sack" From 874ef9a6f2fc45d3f6021842d92fa5420fa4c825 Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Tue, 11 Aug 2026 14:10:06 +0200 Subject: [PATCH 1290/1328] i2c: designware: Global register definitions Moving the register definitions to a global header file include/linux/designware_i2c.h. That removes the need to duplicate them in the adaptation layers for this driver outside of drivers/i2c/busses/. There is at least one of those in drivers/gpu/drm/xe/xe_i2c.c. Suggested-by: Andy Shevchenko Suggested-by: Raag Jadav Reviewed-by: Raag Jadav Reviewed-by: Mika Westerberg Reviewed-by: Andy Shevchenko Signed-off-by: Heikki Krogerus Acked-by: Mika Westerberg Link: https://patch.msgid.link/20260811121008.1493015-2-heikki.krogerus@linux.intel.com Signed-off-by: Rodrigo Vivi (cherry picked from commit 2ab2fb31411a494e4579dfacda986a2672f80e65) Signed-off-by: Rodrigo Vivi --- MAINTAINERS | 1 + drivers/i2c/busses/i2c-designware-common.c | 2 + drivers/i2c/busses/i2c-designware-core.h | 85 +--------------- drivers/i2c/busses/i2c-designware-master.c | 2 + drivers/i2c/busses/i2c-designware-slave.c | 2 + include/linux/designware_i2c.h | 107 +++++++++++++++++++++ 6 files changed, 116 insertions(+), 83 deletions(-) create mode 100644 include/linux/designware_i2c.h diff --git a/MAINTAINERS b/MAINTAINERS index 928b3ba23a76..1d0685b2cfdb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -26253,6 +26253,7 @@ R: Andy Shevchenko L: linux-i2c@vger.kernel.org S: Supported F: drivers/i2c/busses/i2c-designware-* +F: include/linux/designware_i2c.h SYNOPSYS DESIGNWARE I2C DRIVER - AMDISP M: Nirujogi Pratap diff --git a/drivers/i2c/busses/i2c-designware-common.c b/drivers/i2c/busses/i2c-designware-common.c index e4dfa2ec58bb..a1eca6cd4b75 100644 --- a/drivers/i2c/busses/i2c-designware-common.c +++ b/drivers/i2c/busses/i2c-designware-common.c @@ -33,6 +33,8 @@ #include #include +#include + #include "i2c-designware-core.h" #define DW_IC_DEFAULT_BUS_CAPACITANCE_pF 100 diff --git a/drivers/i2c/busses/i2c-designware-core.h b/drivers/i2c/busses/i2c-designware-core.h index c71aa2dd368d..2c929a6e8da2 100644 --- a/drivers/i2c/busses/i2c-designware-core.h +++ b/drivers/i2c/busses/i2c-designware-core.h @@ -18,6 +18,8 @@ #include #include +#include + #define DW_IC_DEFAULT_FUNCTIONALITY (I2C_FUNC_I2C | \ I2C_FUNC_SMBUS_BYTE | \ I2C_FUNC_SMBUS_BYTE_DATA | \ @@ -25,23 +27,6 @@ I2C_FUNC_SMBUS_BLOCK_DATA | \ I2C_FUNC_SMBUS_I2C_BLOCK) -#define DW_IC_CON_MASTER BIT(0) -#define DW_IC_CON_SPEED_STD (1 << 1) -#define DW_IC_CON_SPEED_FAST (2 << 1) -#define DW_IC_CON_SPEED_HIGH (3 << 1) -#define DW_IC_CON_SPEED_MASK GENMASK(2, 1) -#define DW_IC_CON_10BITADDR_SLAVE BIT(3) -#define DW_IC_CON_10BITADDR_MASTER BIT(4) -#define DW_IC_CON_RESTART_EN BIT(5) -#define DW_IC_CON_SLAVE_DISABLE BIT(6) -#define DW_IC_CON_STOP_DET_IFADDRESSED BIT(7) -#define DW_IC_CON_TX_EMPTY_CTRL BIT(8) -#define DW_IC_CON_RX_FIFO_FULL_HLD_CTRL BIT(9) -#define DW_IC_CON_BUS_CLEAR_CTRL BIT(11) - -#define DW_IC_DATA_CMD_DAT GENMASK(7, 0) -#define DW_IC_DATA_CMD_FIRST_DATA_BYTE BIT(11) - /* * Register access parameters */ @@ -55,65 +40,9 @@ #define DW_IC_FIFO_RX_FIELD GENMASK(15, 8) #define DW_IC_FIFO_MIN_DEPTH 2 -/* - * Registers offset - */ -#define DW_IC_CON 0x00 -#define DW_IC_TAR 0x04 -#define DW_IC_SAR 0x08 -#define DW_IC_DATA_CMD 0x10 -#define DW_IC_SS_SCL_HCNT 0x14 -#define DW_IC_SS_SCL_LCNT 0x18 -#define DW_IC_FS_SCL_HCNT 0x1c -#define DW_IC_FS_SCL_LCNT 0x20 -#define DW_IC_HS_SCL_HCNT 0x24 -#define DW_IC_HS_SCL_LCNT 0x28 -#define DW_IC_INTR_STAT 0x2c -#define DW_IC_INTR_MASK 0x30 -#define DW_IC_RAW_INTR_STAT 0x34 -#define DW_IC_RX_TL 0x38 -#define DW_IC_TX_TL 0x3c -#define DW_IC_CLR_INTR 0x40 -#define DW_IC_CLR_RX_UNDER 0x44 -#define DW_IC_CLR_RX_OVER 0x48 -#define DW_IC_CLR_TX_OVER 0x4c -#define DW_IC_CLR_RD_REQ 0x50 -#define DW_IC_CLR_TX_ABRT 0x54 -#define DW_IC_CLR_RX_DONE 0x58 -#define DW_IC_CLR_ACTIVITY 0x5c -#define DW_IC_CLR_STOP_DET 0x60 -#define DW_IC_CLR_START_DET 0x64 -#define DW_IC_CLR_GEN_CALL 0x68 -#define DW_IC_ENABLE 0x6c -#define DW_IC_STATUS 0x70 -#define DW_IC_TXFLR 0x74 -#define DW_IC_RXFLR 0x78 -#define DW_IC_SDA_HOLD 0x7c -#define DW_IC_TX_ABRT_SOURCE 0x80 -#define DW_IC_ENABLE_STATUS 0x9c -#define DW_IC_CLR_RESTART_DET 0xa8 -#define DW_IC_SMBUS_INTR_MASK 0xcc -#define DW_IC_COMP_PARAM_1 0xf4 -#define DW_IC_COMP_VERSION 0xf8 #define DW_IC_SDA_HOLD_MIN_VERS 0x3131312A /* "111*" == v1.11* */ -#define DW_IC_COMP_TYPE 0xfc #define DW_IC_COMP_TYPE_VALUE 0x44570140 /* "DW" + 0x0140 */ -#define DW_IC_INTR_RX_UNDER BIT(0) -#define DW_IC_INTR_RX_OVER BIT(1) -#define DW_IC_INTR_RX_FULL BIT(2) -#define DW_IC_INTR_TX_OVER BIT(3) -#define DW_IC_INTR_TX_EMPTY BIT(4) -#define DW_IC_INTR_RD_REQ BIT(5) -#define DW_IC_INTR_TX_ABRT BIT(6) -#define DW_IC_INTR_RX_DONE BIT(7) -#define DW_IC_INTR_ACTIVITY BIT(8) -#define DW_IC_INTR_STOP_DET BIT(9) -#define DW_IC_INTR_START_DET BIT(10) -#define DW_IC_INTR_GEN_CALL BIT(11) -#define DW_IC_INTR_RESTART_DET BIT(12) -#define DW_IC_INTR_MST_ON_HOLD BIT(13) - #define DW_IC_INTR_DEFAULT_MASK (DW_IC_INTR_RX_FULL | \ DW_IC_INTR_TX_ABRT | \ DW_IC_INTR_STOP_DET) @@ -123,16 +52,6 @@ DW_IC_INTR_RX_UNDER | \ DW_IC_INTR_RD_REQ) -#define DW_IC_ENABLE_ENABLE BIT(0) -#define DW_IC_ENABLE_ABORT BIT(1) - -#define DW_IC_STATUS_ACTIVITY BIT(0) -#define DW_IC_STATUS_TFE BIT(2) -#define DW_IC_STATUS_RFNE BIT(3) -#define DW_IC_STATUS_MASTER_ACTIVITY BIT(5) -#define DW_IC_STATUS_SLAVE_ACTIVITY BIT(6) -#define DW_IC_STATUS_MASTER_HOLD_TX_FIFO_EMPTY BIT(7) - #define DW_IC_SDA_HOLD_RX_SHIFT 16 #define DW_IC_SDA_HOLD_RX_MASK GENMASK(23, 16) diff --git a/drivers/i2c/busses/i2c-designware-master.c b/drivers/i2c/busses/i2c-designware-master.c index 7a301c8b604e..a1bcc3797e4f 100644 --- a/drivers/i2c/busses/i2c-designware-master.c +++ b/drivers/i2c/busses/i2c-designware-master.c @@ -25,6 +25,8 @@ #include #include +#include + #include "i2c-designware-core.h" #define AMD_TIMEOUT_MIN_US 25 diff --git a/drivers/i2c/busses/i2c-designware-slave.c b/drivers/i2c/busses/i2c-designware-slave.c index ad0d5fbfa6d5..0abcc7757b23 100644 --- a/drivers/i2c/busses/i2c-designware-slave.c +++ b/drivers/i2c/busses/i2c-designware-slave.c @@ -19,6 +19,8 @@ #include #include +#include + #include "i2c-designware-core.h" int i2c_dw_reg_slave(struct i2c_client *slave) diff --git a/include/linux/designware_i2c.h b/include/linux/designware_i2c.h new file mode 100644 index 000000000000..53f37f18a722 --- /dev/null +++ b/include/linux/designware_i2c.h @@ -0,0 +1,107 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Synopsys DesignWare I2C register definitions + * + * Copyright (C) 2026, Intel Corporation + */ + +#ifndef __LINUX_DESIGNWARE_I2C_H +#define __LINUX_DESIGNWARE_I2C_H + +#include + +/* + * Registers offset + */ +#define DW_IC_CON 0x00 +#define DW_IC_TAR 0x04 +#define DW_IC_SAR 0x08 +#define DW_IC_DATA_CMD 0x10 +#define DW_IC_SS_SCL_HCNT 0x14 +#define DW_IC_SS_SCL_LCNT 0x18 +#define DW_IC_FS_SCL_HCNT 0x1c +#define DW_IC_FS_SCL_LCNT 0x20 +#define DW_IC_HS_SCL_HCNT 0x24 +#define DW_IC_HS_SCL_LCNT 0x28 +#define DW_IC_INTR_STAT 0x2c +#define DW_IC_INTR_MASK 0x30 +#define DW_IC_RAW_INTR_STAT 0x34 +#define DW_IC_RX_TL 0x38 +#define DW_IC_TX_TL 0x3c +#define DW_IC_CLR_INTR 0x40 +#define DW_IC_CLR_RX_UNDER 0x44 +#define DW_IC_CLR_RX_OVER 0x48 +#define DW_IC_CLR_TX_OVER 0x4c +#define DW_IC_CLR_RD_REQ 0x50 +#define DW_IC_CLR_TX_ABRT 0x54 +#define DW_IC_CLR_RX_DONE 0x58 +#define DW_IC_CLR_ACTIVITY 0x5c +#define DW_IC_CLR_STOP_DET 0x60 +#define DW_IC_CLR_START_DET 0x64 +#define DW_IC_CLR_GEN_CALL 0x68 +#define DW_IC_ENABLE 0x6c +#define DW_IC_STATUS 0x70 +#define DW_IC_TXFLR 0x74 +#define DW_IC_RXFLR 0x78 +#define DW_IC_SDA_HOLD 0x7c +#define DW_IC_TX_ABRT_SOURCE 0x80 +#define DW_IC_ENABLE_STATUS 0x9c +#define DW_IC_CLR_RESTART_DET 0xa8 +#define DW_IC_SMBUS_INTR_STAT 0xc8 +#define DW_IC_SMBUS_INTR_MASK 0xcc +#define DW_IC_CLR_SMBUS_INTR 0xd4 +#define DW_IC_COMP_PARAM_1 0xf4 +#define DW_IC_COMP_VERSION 0xf8 +#define DW_IC_COMP_TYPE 0xfc + +/* DW_IC_CON bits */ +#define DW_IC_CON_MASTER BIT(0) +#define DW_IC_CON_SPEED_STD (1 << 1) +#define DW_IC_CON_SPEED_FAST (2 << 1) +#define DW_IC_CON_SPEED_HIGH (3 << 1) +#define DW_IC_CON_SPEED_MASK GENMASK(2, 1) +#define DW_IC_CON_10BITADDR_SLAVE BIT(3) +#define DW_IC_CON_10BITADDR_MASTER BIT(4) +#define DW_IC_CON_RESTART_EN BIT(5) +#define DW_IC_CON_SLAVE_DISABLE BIT(6) +#define DW_IC_CON_STOP_DET_IFADDRESSED BIT(7) +#define DW_IC_CON_TX_EMPTY_CTRL BIT(8) +#define DW_IC_CON_RX_FIFO_FULL_HLD_CTRL BIT(9) +#define DW_IC_CON_BUS_CLEAR_CTRL BIT(11) + +/* DW_IC_DATA_CMD bits */ +#define DW_IC_DATA_CMD_DAT GENMASK(7, 0) +#define DW_IC_DATA_CMD_FIRST_DATA_BYTE BIT(11) + +/* DW_IC_INTR_* bits */ +#define DW_IC_INTR_RX_UNDER BIT(0) +#define DW_IC_INTR_RX_OVER BIT(1) +#define DW_IC_INTR_RX_FULL BIT(2) +#define DW_IC_INTR_TX_OVER BIT(3) +#define DW_IC_INTR_TX_EMPTY BIT(4) +#define DW_IC_INTR_RD_REQ BIT(5) +#define DW_IC_INTR_TX_ABRT BIT(6) +#define DW_IC_INTR_RX_DONE BIT(7) +#define DW_IC_INTR_ACTIVITY BIT(8) +#define DW_IC_INTR_STOP_DET BIT(9) +#define DW_IC_INTR_START_DET BIT(10) +#define DW_IC_INTR_GEN_CALL BIT(11) +#define DW_IC_INTR_RESTART_DET BIT(12) +#define DW_IC_INTR_MST_ON_HOLD BIT(13) + +/* DW_IC_ENABLE bits */ +#define DW_IC_ENABLE_ENABLE BIT(0) +#define DW_IC_ENABLE_ABORT BIT(1) + +/* DW_IC_STATUS bits */ +#define DW_IC_STATUS_ACTIVITY BIT(0) +#define DW_IC_STATUS_TFE BIT(2) +#define DW_IC_STATUS_RFNE BIT(3) +#define DW_IC_STATUS_MASTER_ACTIVITY BIT(5) +#define DW_IC_STATUS_SLAVE_ACTIVITY BIT(6) +#define DW_IC_STATUS_MASTER_HOLD_TX_FIFO_EMPTY BIT(7) + +/* DW_IC_SMBUS_INTR_* bits */ +#define DW_IC_SMBUS_INTR_ALERT BIT(10) + +#endif /* __LINUX_DESIGNWARE_I2C_H */ From f43fa4b8522ba6038b77e86e5f0d94be35effcde Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Tue, 11 Aug 2026 14:10:07 +0200 Subject: [PATCH 1291/1328] drm/xe/i2c: Fix the interrupt handling The platforms that support the interrupt from the I2C adapter can not handle the amount of interrupts the adapter generates because of the way the IRQ is routed in the hardware. The I2C controller driver has to be kept in polling mode because of that. The AMC MCU can still generate critical alerts that have to be handled. The interrupt from SMBus Alert is left enabled and handled separately in the Xe. The alerts from the AMC will cause the device to be declared wedged for now. Fixes: f0e53aadd702 ("drm/xe: Support for I2C attached MCUs") Cc: stable@vger.kernel.org Reviewed-by: Raag Jadav Co-developed-by: Ramesh Babu B Signed-off-by: Ramesh Babu B Signed-off-by: Heikki Krogerus Link: https://patch.msgid.link/20260811121008.1493015-3-heikki.krogerus@linux.intel.com Signed-off-by: Rodrigo Vivi (cherry picked from commit a55b76b8bc2c49b11d753c1c6d06ec3a2c61c85e) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/Makefile | 4 +- drivers/gpu/drm/xe/regs/xe_i2c_regs.h | 2 + drivers/gpu/drm/xe/xe_amc.c | 197 ++++++++++++++++++++++++++ drivers/gpu/drm/xe/xe_amc.h | 25 ++++ drivers/gpu/drm/xe/xe_i2c.c | 128 +++++++---------- drivers/gpu/drm/xe/xe_i2c.h | 13 +- 6 files changed, 282 insertions(+), 87 deletions(-) create mode 100644 drivers/gpu/drm/xe/xe_amc.c create mode 100644 drivers/gpu/drm/xe/xe_amc.h diff --git a/drivers/gpu/drm/xe/Makefile b/drivers/gpu/drm/xe/Makefile index 67ada1d6c2fb..c92468cb9b89 100644 --- a/drivers/gpu/drm/xe/Makefile +++ b/drivers/gpu/drm/xe/Makefile @@ -153,7 +153,9 @@ xe-y += xe_bb.o \ xe_wait_user_fence.o \ xe_wopcm.o -xe-$(CONFIG_I2C) += xe_i2c.o +xe-$(CONFIG_I2C) += xe_i2c.o \ + xe_amc.o + xe-$(CONFIG_DRM_XE_GPUSVM) += xe_svm.o xe-$(CONFIG_DRM_GPUSVM) += xe_userptr.o diff --git a/drivers/gpu/drm/xe/regs/xe_i2c_regs.h b/drivers/gpu/drm/xe/regs/xe_i2c_regs.h index f2e455e2bfe4..37550e4a20f8 100644 --- a/drivers/gpu/drm/xe/regs/xe_i2c_regs.h +++ b/drivers/gpu/drm/xe/regs/xe_i2c_regs.h @@ -20,4 +20,6 @@ #define I2C_CONFIG_CMD XE_REG(I2C_CONFIG_SPACE_OFFSET + PCI_COMMAND) #define I2C_CONFIG_PMCSR XE_REG(I2C_CONFIG_SPACE_OFFSET + 0x84) +#define I2C_REG(reg) XE_REG((reg) + I2C_MEM_SPACE_OFFSET) + #endif /* _XE_I2C_REGS_H_ */ diff --git a/drivers/gpu/drm/xe/xe_amc.c b/drivers/gpu/drm/xe/xe_amc.c new file mode 100644 index 000000000000..8ecadee6eea3 --- /dev/null +++ b/drivers/gpu/drm/xe/xe_amc.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2026 Intel Corporation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "regs/xe_i2c_regs.h" + +#include "xe_amc.h" +#include "xe_device.h" +#include "xe_i2c.h" +#include "xe_mmio.h" + +/** + * DOC: Add-In Management Controller (AMC) + * + * Handler for the SMBus Alerts from the AMC. All the alerts from AMC will cause + * the device to be declared wedged. + */ + +#define AMC_COMMAND 0x0f +#define AMC_GPU_I2C_ADDR 0x8f +#define AMC_VERSION_V1 0x01 +#define AMC_DESTINATION_ID 12 +#define AMC_SOURCE_ID 8 +#define AMC_FLAGS 0xc8 + +#define AMC_MSG_TYPE 0x7e +#define AMC_GET_ALERT_REASON 0x01 + +enum xe_amc_alert { + AMC_ALERT_UNKNOWN, + AMC_ALERT_FW_DOWNLOAD, + AMC_ALERT_THERMAL_TRIP, + AMC_ALERT_OOB_REQUEST, + AMC_ALERT_OOB_RESET, + AMC_ALERT_CATERR, +}; + +static const char * const amc_alert[] = { + [AMC_ALERT_FW_DOWNLOAD] = "Firmware Download", + [AMC_ALERT_THERMAL_TRIP] = "Thermal Trip", + [AMC_ALERT_OOB_REQUEST] = "OOB Request", + [AMC_ALERT_OOB_RESET] = "OOB Reset", + [AMC_ALERT_CATERR] = "Catastrophic", +}; + +struct xe_amc { + struct xe_i2c *i2c; + struct work_struct work; +}; + +struct amc_header { + u8 command; + u8 len; + u8 address; + u8 version; + u8 destination; + u8 source; + u8 flags; +} __packed; + +struct amc_message { + u8 type; + u16 vendor; + u8 command; +} __packed; + +struct amc_request { + struct amc_header header; + struct amc_message message; + u32 reserved; +} __packed; + +struct amc_response { + struct amc_header header; + struct amc_message message; + u8 error; + u8 value; +} __packed; + +static const struct amc_request amc_get_alert_reason = { + .header = { + .command = AMC_COMMAND, + .len = sizeof(struct amc_request) - 2, + .address = AMC_GPU_I2C_ADDR, + .version = AMC_VERSION_V1, + .destination = AMC_DESTINATION_ID, + .source = AMC_SOURCE_ID, + .flags = AMC_FLAGS, + }, + .message = { + .type = AMC_MSG_TYPE, + .vendor = htons(PCI_VENDOR_ID_INTEL), + .command = AMC_GET_ALERT_REASON, + }, +}; + +static void xe_amc_work(struct work_struct *work) +{ + const struct amc_request *request = &amc_get_alert_reason; + struct xe_amc *amc = from_work(amc, work, work); + u8 alert_reason = AMC_ALERT_UNKNOWN; + struct amc_response response; + struct i2c_client *client; + int ret; + + client = amc->i2c->client[XE_I2C_CLIENT_AMC]; + if (IS_ERR_OR_NULL(client)) + goto out_reassert_interrupt; + + ret = i2c_master_send(client, (u8 *)request, sizeof(*request)); + if (ret < 0) { + dev_err(&client->dev, "failed to send request (%d)\n", ret); + goto out_reassert_interrupt; + } + + /* AMC needs 20ms to generate the response. */ + fsleep(20 * USEC_PER_MSEC); + + ret = i2c_master_recv(client, (u8 *)&response, sizeof(response)); + if (ret < 0) { + dev_err(&client->dev, "failed to read response (%d)\n", ret); + goto out_reassert_interrupt; + } + + if (!response.header.len) { + dev_err(&client->dev, "empty response from AMC\n"); + goto out_reassert_interrupt; + } + + if (memcmp(&response.message, &request->message, sizeof(struct amc_message))) { + dev_err(&client->dev, "response does not match the request\n"); + goto out_reassert_interrupt; + } + + if (response.error) { + dev_err(&client->dev, "AMC error 0x%02x\n", response.error); + goto out_reassert_interrupt; + } + + alert_reason = response.value; + dev_dbg(&client->dev, "Alert reason: %d\n", alert_reason); + +out_reassert_interrupt: + xe_mmio_rmw32(amc->i2c->mmio, I2C_CONFIG_CMD, PCI_COMMAND_INTX_DISABLE, 0); + + switch (alert_reason) { + case AMC_ALERT_FW_DOWNLOAD: + case AMC_ALERT_THERMAL_TRIP: + case AMC_ALERT_OOB_REQUEST: + case AMC_ALERT_OOB_RESET: + case AMC_ALERT_CATERR: + dev_warn(amc->i2c->drm_dev, "AMC Alert: %s\n", amc_alert[alert_reason]); + xe_device_declare_wedged(i2c_client_to_xe_device(client)); + break; + default: + dev_warn(amc->i2c->drm_dev, "unknown AMC alert: %d\n", alert_reason); + break; + } +} + +void xe_amc_handle_alert(struct xe_i2c *i2c) +{ + queue_work(system_long_wq, &i2c->amc->work); +} + +int xe_amc_init(struct xe_i2c *i2c) +{ + struct xe_amc *amc; + + amc = kzalloc(sizeof(*amc), GFP_KERNEL); + if (!amc) + return -ENOMEM; + + INIT_WORK(&amc->work, xe_amc_work); + i2c->amc = amc; + amc->i2c = i2c; + + return 0; +} + +void xe_amc_exit(struct xe_i2c *i2c) +{ + if (i2c->amc) { + cancel_work_sync(&i2c->amc->work); + kfree(i2c->amc); + } +} diff --git a/drivers/gpu/drm/xe/xe_amc.h b/drivers/gpu/drm/xe/xe_amc.h new file mode 100644 index 000000000000..b1d5311fee53 --- /dev/null +++ b/drivers/gpu/drm/xe/xe_amc.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _XE_AMC_H_ +#define _XE_AMC_H_ + +#include + +#include "xe_device.h" + +struct xe_i2c; + +static inline struct xe_device *i2c_adapter_to_xe_device(struct i2c_adapter *adapter) +{ + return kdev_to_xe_device(adapter->dev.parent->parent); +} + +static inline struct xe_device *i2c_client_to_xe_device(struct i2c_client *client) +{ + return i2c_adapter_to_xe_device(client->adapter); +} + +int xe_amc_init(struct xe_i2c *i2c); +void xe_amc_exit(struct xe_i2c *i2c); +void xe_amc_handle_alert(struct xe_i2c *i2c); + +#endif /* _XE_AMC_H_ */ diff --git a/drivers/gpu/drm/xe/xe_i2c.c b/drivers/gpu/drm/xe/xe_i2c.c index a26c38bb17a1..32767570e43d 100644 --- a/drivers/gpu/drm/xe/xe_i2c.c +++ b/drivers/gpu/drm/xe/xe_i2c.c @@ -12,8 +12,6 @@ #include #include #include -#include -#include #include #include #include @@ -24,9 +22,12 @@ #include #include +#include + #include "regs/xe_i2c_regs.h" #include "regs/xe_irq_regs.h" +#include "xe_amc.h" #include "xe_device.h" #include "xe_i2c.h" #include "xe_mmio.h" @@ -61,16 +62,32 @@ static inline void xe_i2c_read_endpoint(struct xe_mmio *mmio, void *ep) val[1] = xe_mmio_read32(mmio, REG_SG_REMAP_ADDR_POSTFIX); } +static void xe_i2c_handle_smbus_alert(struct xe_i2c *i2c) +{ + u32 stat; + + stat = xe_mmio_read32(i2c->mmio, I2C_REG(DW_IC_SMBUS_INTR_STAT)); + if (!stat) + return; + + xe_mmio_write32(i2c->mmio, I2C_REG(DW_IC_CLR_SMBUS_INTR), stat); + + if (stat & DW_IC_SMBUS_INTR_ALERT && i2c->amc) + xe_amc_handle_alert(i2c); + else + xe_mmio_rmw32(i2c->mmio, I2C_CONFIG_CMD, PCI_COMMAND_INTX_DISABLE, 0); +} + static void xe_i2c_client_work(struct work_struct *work) { struct xe_i2c *i2c = container_of(work, struct xe_i2c, work); struct i2c_board_info info = { .type = "amc", .flags = I2C_CLIENT_HOST_NOTIFY, - .addr = i2c->ep.addr[1], + .addr = i2c->ep.addr[XE_I2C_CLIENT_AMC], }; - i2c->client[0] = i2c_new_client_device(i2c->adapter, &info); + i2c->client[XE_I2C_CLIENT_AMC] = i2c_new_client_device(i2c->adapter, &info); } static int xe_i2c_notifier(struct notifier_block *nb, unsigned long action, void *data) @@ -115,16 +132,6 @@ static int xe_i2c_register_adapter(struct xe_i2c *i2c) goto err_fwnode_remove; } - if (i2c->adapter_irq) { - struct resource res; - - res = DEFINE_RES_IRQ_NAMED(i2c->adapter_irq, "xe_i2c"); - - ret = platform_device_add_resources(pdev, &res, 1); - if (ret) - goto err_pdev_put; - } - pdev->dev.parent = i2c->drm_dev; pdev->dev.fwnode = fwnode; i2c->adapter_node = fwnode; @@ -166,7 +173,8 @@ bool xe_i2c_present(struct xe_device *xe) static bool xe_i2c_irq_present(struct xe_device *xe) { - return xe->i2c && xe->i2c->adapter_irq; + return xe->i2c && xe->i2c->ep.capabilities & XE_I2C_EP_CAP_IRQ && + !xe_survivability_mode_is_boot_enabled(xe); } /** @@ -179,18 +187,10 @@ static bool xe_i2c_irq_present(struct xe_device *xe) */ void xe_i2c_irq_handler(struct xe_device *xe, u32 master_ctl) { - struct xe_mmio *mmio = xe_root_tile_mmio(xe); - if (!(master_ctl & I2C_IRQ) || !xe_i2c_irq_present(xe)) return; - /* Forward interrupt to I2C adapter */ - generic_handle_irq_safe(xe->i2c->adapter_irq); - - /* Deassert after I2C adapter clears the interrupt */ - xe_mmio_rmw32(mmio, I2C_CONFIG_CMD, 0, PCI_COMMAND_INTX_DISABLE); - /* Reassert to allow subsequent interrupt generation */ - xe_mmio_rmw32(mmio, I2C_CONFIG_CMD, PCI_COMMAND_INTX_DISABLE, 0); + xe_i2c_handle_smbus_alert(xe->i2c); } void xe_i2c_irq_reset(struct xe_device *xe) @@ -215,45 +215,6 @@ void xe_i2c_irq_postinstall(struct xe_device *xe) xe_mmio_rmw32(mmio, I2C_CONFIG_CMD, PCI_COMMAND_INTX_DISABLE, 0); } -static int xe_i2c_irq_map(struct irq_domain *h, unsigned int virq, - irq_hw_number_t hw_irq_num) -{ - irq_set_chip_and_handler(virq, &dummy_irq_chip, handle_simple_irq); - return 0; -} - -static const struct irq_domain_ops xe_i2c_irq_ops = { - .map = xe_i2c_irq_map, -}; - -static int xe_i2c_create_irq(struct xe_device *xe) -{ - struct xe_i2c *i2c = xe->i2c; - struct irq_domain *domain; - - if (!(i2c->ep.capabilities & XE_I2C_EP_CAP_IRQ) || - xe_survivability_mode_is_boot_enabled(xe)) - return 0; - - domain = irq_domain_create_linear(dev_fwnode(i2c->drm_dev), 1, &xe_i2c_irq_ops, NULL); - if (!domain) - return -ENOMEM; - - i2c->adapter_irq = irq_create_mapping(domain, 0); - i2c->irqdomain = domain; - - return 0; -} - -static void xe_i2c_remove_irq(struct xe_i2c *i2c) -{ - if (!i2c->irqdomain) - return; - - irq_dispose_mapping(i2c->adapter_irq); - irq_domain_remove(i2c->irqdomain); -} - static int xe_i2c_read(void *context, unsigned int reg, unsigned int *val) { struct xe_i2c *i2c = context; @@ -267,8 +228,16 @@ static int xe_i2c_write(void *context, unsigned int reg, unsigned int val) { struct xe_i2c *i2c = context; - xe_mmio_write32(i2c->mmio, XE_REG(reg + I2C_MEM_SPACE_OFFSET), val); + switch (reg) { + case DW_IC_SMBUS_INTR_MASK: + /* Make sure the Alert is never masked. */ + val |= DW_IC_SMBUS_INTR_ALERT; + break; + default: + break; + } + xe_mmio_write32(i2c->mmio, I2C_REG(reg), val); return 0; } @@ -310,12 +279,15 @@ static void xe_i2c_remove(void *data) struct xe_i2c *i2c = data; unsigned int i; - for (i = 0; i < XE_I2C_MAX_CLIENTS; i++) + xe_amc_exit(i2c); + + for (i = 0; i < XE_I2C_MAX_CLIENTS; i++) { i2c_unregister_device(i2c->client[i]); + i2c->client[i] = NULL; + } bus_unregister_notifier(&i2c_bus_type, &i2c->bus_notifier); xe_i2c_unregister_adapter(i2c); - xe_i2c_remove_irq(i2c); } /** @@ -363,22 +335,18 @@ int xe_i2c_probe(struct xe_device *xe) if (ret) return ret; - ret = xe_i2c_create_irq(xe); - if (ret) - goto err_unregister_notifier; - ret = xe_i2c_register_adapter(i2c); - if (ret) - goto err_remove_irq; + if (ret) { + bus_unregister_notifier(&i2c_bus_type, &i2c->bus_notifier); + return ret; + } + + ret = xe_amc_init(i2c); + if (ret) { + xe_i2c_remove(i2c); + return ret; + } xe_i2c_irq_postinstall(xe); return devm_add_action_or_reset(drm_dev, xe_i2c_remove, i2c); - -err_remove_irq: - xe_i2c_remove_irq(i2c); - -err_unregister_notifier: - bus_unregister_notifier(&i2c_bus_type, &i2c->bus_notifier); - - return ret; } diff --git a/drivers/gpu/drm/xe/xe_i2c.h b/drivers/gpu/drm/xe/xe_i2c.h index 425d8160835f..b200966b0048 100644 --- a/drivers/gpu/drm/xe/xe_i2c.h +++ b/drivers/gpu/drm/xe/xe_i2c.h @@ -11,18 +11,21 @@ struct device; struct fwnode_handle; struct i2c_adapter; struct i2c_client; -struct irq_domain; struct platform_device; +struct xe_amc; struct xe_device; struct xe_mmio; -#define XE_I2C_MAX_CLIENTS 3 - #define XE_I2C_EP_COOKIE_DEVICE 0xde /* Endpoint Capabilities */ #define XE_I2C_EP_CAP_IRQ BIT(0) +enum XE_I2C_CLIENT { + XE_I2C_CLIENT_AMC = 1, + XE_I2C_MAX_CLIENTS = 3, +}; + struct xe_i2c_endpoint { u8 cookie; u8 capabilities; @@ -38,13 +41,11 @@ struct xe_i2c { struct notifier_block bus_notifier; struct work_struct work; - struct irq_domain *irqdomain; - int adapter_irq; - struct xe_i2c_endpoint ep; struct device *drm_dev; struct xe_mmio *mmio; + struct xe_amc *amc; }; #if IS_ENABLED(CONFIG_I2C) From 244abef7f280a6a84297bfab5fd2e77147bc419a Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Tue, 11 Aug 2026 14:10:08 +0200 Subject: [PATCH 1292/1328] drm/xe/i2c: Keep the i2c controller always enabled Some platforms make an assumption that the i2c controller's enabled state indicates also the power state of the controller. This can create a problem when the controller is in disabled state, because the hardware may assume incorrectly that it is then also in low-power state. To fix this, the controller is kept enabled by taking over the IC_ENABLE register. The controller has to be disabled when the configuration is updated and when the target address or the slave address are assigned, so disabling it when IC_CON, IC_TAR or IC_SAR registers are programmed, and then re-enabling it again. Fixes: f0e53aadd702 ("drm/xe: Support for I2C attached MCUs") Cc: stable@vger.kernel.org Signed-off-by: Heikki Krogerus Reviewed-by: Rodrigo Vivi Link: https://patch.msgid.link/20260811121008.1493015-4-heikki.krogerus@linux.intel.com Signed-off-by: Rodrigo Vivi (cherry picked from commit 76cc14e2faed1adae20f4ee144ead0e3a7566c49) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_i2c.c | 49 ++++++++++++++++++++++++++++++++++++- drivers/gpu/drm/xe/xe_i2c.h | 1 + 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_i2c.c b/drivers/gpu/drm/xe/xe_i2c.c index 32767570e43d..d8fa68206f41 100644 --- a/drivers/gpu/drm/xe/xe_i2c.c +++ b/drivers/gpu/drm/xe/xe_i2c.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -215,11 +216,40 @@ void xe_i2c_irq_postinstall(struct xe_device *xe) xe_mmio_rmw32(mmio, I2C_CONFIG_CMD, PCI_COMMAND_INTX_DISABLE, 0); } +/* See "Disabling DW_apb_i2c" in the DesignWare DW_abp_i2c databook. */ +static void xe_i2c_disable(struct xe_i2c *i2c) +{ + int timeout = 100; + u32 status; + + xe_mmio_rmw32(i2c->mmio, I2C_REG(DW_IC_ENABLE), DW_IC_ENABLE_ENABLE, 0); + + do { + status = xe_mmio_read32(i2c->mmio, I2C_REG(DW_IC_ENABLE_STATUS)); + if (!(status & DW_IC_ENABLE_ENABLE)) + return; + /* Can't sleep here. */ + udelay(25); + } while (timeout--); + + dev_warn(i2c->drm_dev, "timeout in disabling i2c adapter\n"); +} + static int xe_i2c_read(void *context, unsigned int reg, unsigned int *val) { struct xe_i2c *i2c = context; - *val = xe_mmio_read32(i2c->mmio, XE_REG(reg + I2C_MEM_SPACE_OFFSET)); + *val = xe_mmio_read32(i2c->mmio, I2C_REG(reg)); + + switch (reg) { + case DW_IC_ENABLE: + case DW_IC_ENABLE_STATUS: + FIELD_MODIFY(DW_IC_ENABLE_ENABLE, val, + i2c->ic_enable & DW_IC_ENABLE_ENABLE); + break; + default: + break; + } return 0; } @@ -229,6 +259,23 @@ static int xe_i2c_write(void *context, unsigned int reg, unsigned int val) struct xe_i2c *i2c = context; switch (reg) { + case DW_IC_CON: + case DW_IC_TAR: + case DW_IC_SAR: + /* Disable the controller. */ + xe_i2c_disable(i2c); + + /* Write the register. */ + xe_mmio_write32(i2c->mmio, I2C_REG(reg), val); + + /* Enable the controller. */ + xe_mmio_rmw32(i2c->mmio, I2C_REG(DW_IC_ENABLE), 0, DW_IC_ENABLE_ENABLE); + return 0; + case DW_IC_ENABLE: + i2c->ic_enable = val; + /* Other fields can be updated except the enable bit. */ + val |= DW_IC_ENABLE_ENABLE; + break; case DW_IC_SMBUS_INTR_MASK: /* Make sure the Alert is never masked. */ val |= DW_IC_SMBUS_INTR_ALERT; diff --git a/drivers/gpu/drm/xe/xe_i2c.h b/drivers/gpu/drm/xe/xe_i2c.h index b200966b0048..d63adacfefe7 100644 --- a/drivers/gpu/drm/xe/xe_i2c.h +++ b/drivers/gpu/drm/xe/xe_i2c.h @@ -37,6 +37,7 @@ struct xe_i2c { struct platform_device *pdev; struct i2c_adapter *adapter; struct i2c_client *client[XE_I2C_MAX_CLIENTS]; + unsigned int ic_enable; struct notifier_block bus_notifier; struct work_struct work; From a62212b35a214c2ff3bd1c785a440d7ad8205ec9 Mon Sep 17 00:00:00 2001 From: Anoop Vijay Date: Tue, 25 Aug 2026 10:28:28 -0700 Subject: [PATCH 1293/1328] drm/xe/sysctrl: Read mailbox phase bit from hardware The mailbox PHASE bit in SYSCTRL_MB_CTRL is toggled per-message and was tracked in software as sc->phase_bit, reset to 0 on error paths. If the cached value ever drifts from what the hardware last saw, all following messages carry the wrong phase and get silently misread by firmware. Drop the cache and read PHASE directly from SYSCTRL_MB_CTRL before each frame instead, removing xe_sysctrl_mailbox_init() and its call sites along with it. Fixes: 1f95f618182b ("drm/xe/xe_sysctrl: Add System Controller mailbox communication support") Signed-off-by: Anoop Vijay Reviewed-by: Umesh Nerlige Ramappa Reviewed-by: Rodrigo Vivi Link: https://patch.msgid.link/20260825172827.3801591-2-anoop.c.vijay@intel.com Signed-off-by: Rodrigo Vivi (cherry picked from commit 04984fcdbf6876c940c01026a7404c1e9cc91ba7) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_sysctrl.c | 7 +------ drivers/gpu/drm/xe/xe_sysctrl_mailbox.c | 22 ++++------------------ drivers/gpu/drm/xe/xe_sysctrl_mailbox.h | 1 - drivers/gpu/drm/xe/xe_sysctrl_types.h | 3 --- 4 files changed, 5 insertions(+), 28 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_sysctrl.c b/drivers/gpu/drm/xe/xe_sysctrl.c index 1db20be8158b..62ccc9be71b4 100644 --- a/drivers/gpu/drm/xe/xe_sysctrl.c +++ b/drivers/gpu/drm/xe/xe_sysctrl.c @@ -85,7 +85,6 @@ int xe_sysctrl_init(struct xe_device *xe) return ret; xe->soc_remapper.set_sysctrl_region(xe, SYSCTRL_MAILBOX_INDEX); - xe_sysctrl_mailbox_init(sc); INIT_WORK(&sc->work, xe_sysctrl_work); return devm_add_action_or_reset(xe->drm.dev, sysctrl_fini, xe); @@ -114,12 +113,10 @@ void xe_sysctrl_irq_handler(struct xe_device *xe, u32 master_ctl) * @xe: xe device instance * * Invoked during system resume (S3/S4 to S0) and runtime resume from D3cold. - * Restores SoC remapper configuration and reinitializes mailbox interface. + * Restores SoC remapper configuration. */ void xe_sysctrl_pm_resume(struct xe_device *xe) { - struct xe_sysctrl *sc = &xe->sc; - if (!xe->info.has_soc_remapper_sysctrl) return; @@ -127,6 +124,4 @@ void xe_sysctrl_pm_resume(struct xe_device *xe) return; xe->soc_remapper.set_sysctrl_region(xe, SYSCTRL_MAILBOX_INDEX); - - xe_sysctrl_mailbox_init(sc); } diff --git a/drivers/gpu/drm/xe/xe_sysctrl_mailbox.c b/drivers/gpu/drm/xe/xe_sysctrl_mailbox.c index e13eebaac1d0..72baf1aa4b3a 100644 --- a/drivers/gpu/drm/xe/xe_sysctrl_mailbox.c +++ b/drivers/gpu/drm/xe/xe_sysctrl_mailbox.c @@ -145,6 +145,7 @@ static int sysctrl_send_frames(struct xe_sysctrl *sc, struct xe_device *xe = sc_to_xe(sc); u32 ctrl_reg, total_frames, frame; size_t bytes_sent, frame_size; + bool phase; total_frames = DIV_ROUND_UP(cmd_size, XE_SYSCTRL_MB_FRAME_SIZE); @@ -153,7 +154,8 @@ static int sysctrl_send_frames(struct xe_sysctrl *sc, return -EBUSY; } - sc->phase_bit ^= 1; + ctrl_reg = xe_mmio_read32(sc->mmio, SYSCTRL_MB_CTRL); + phase = !(ctrl_reg & SYSCTRL_FRAME_PHASE); bytes_sent = 0; for (frame = 0; frame < total_frames; frame++) { @@ -161,7 +163,6 @@ static int sysctrl_send_frames(struct xe_sysctrl *sc, if (sysctrl_write_frame(sc, mbox_cmd + bytes_sent, frame_size)) { xe_err(xe, "sysctrl: Failed to write frame %u\n", frame); - sc->phase_bit = 0; return -EIO; } @@ -169,13 +170,12 @@ static int sysctrl_send_frames(struct xe_sysctrl *sc, REG_FIELD_PREP(SYSCTRL_FRAME_CURRENT_MASK, frame) | REG_FIELD_PREP(SYSCTRL_FRAME_TOTAL_MASK, total_frames - 1) | SYSCTRL_MB_CTRL_CMD | - (sc->phase_bit ? SYSCTRL_FRAME_PHASE : 0); + (phase ? SYSCTRL_FRAME_PHASE : 0); xe_mmio_write32(sc->mmio, SYSCTRL_MB_CTRL, ctrl_reg); if (!sysctrl_wait_bit_clear(sc, SYSCTRL_MB_CTRL_RUN_BUSY, timeout_ms)) { xe_err(xe, "sysctrl: Frame %u acknowledgment timeout\n", frame); - sc->phase_bit = 0; return -ETIMEDOUT; } @@ -321,20 +321,6 @@ void xe_sysctrl_create_command(struct xe_sysctrl_mailbox_command *command, u8 gr command->data_out_len = response_len; } -/** - * xe_sysctrl_mailbox_init - Initialize System Controller mailbox interface - * @sc: System controller structure - * - * Initialize system controller mailbox interface for communication. - */ -void xe_sysctrl_mailbox_init(struct xe_sysctrl *sc) -{ - u32 ctrl_reg; - - ctrl_reg = xe_mmio_read32(sc->mmio, SYSCTRL_MB_CTRL); - sc->phase_bit = (ctrl_reg & SYSCTRL_FRAME_PHASE) ? 1 : 0; -} - /** * xe_sysctrl_send_command() - Send mailbox command to System Controller * @sc: System Controller instance diff --git a/drivers/gpu/drm/xe/xe_sysctrl_mailbox.h b/drivers/gpu/drm/xe/xe_sysctrl_mailbox.h index fb434cc165b2..f0d5e3d7f5e3 100644 --- a/drivers/gpu/drm/xe/xe_sysctrl_mailbox.h +++ b/drivers/gpu/drm/xe/xe_sysctrl_mailbox.h @@ -26,7 +26,6 @@ struct xe_sysctrl_mailbox_command; void xe_sysctrl_create_command(struct xe_sysctrl_mailbox_command *command, u8 group_id, u8 cmd_id, void *request, size_t request_len, void *response, size_t response_len); -void xe_sysctrl_mailbox_init(struct xe_sysctrl *sc); int xe_sysctrl_send_command(struct xe_sysctrl *sc, struct xe_sysctrl_mailbox_command *cmd, size_t *rdata_len); diff --git a/drivers/gpu/drm/xe/xe_sysctrl_types.h b/drivers/gpu/drm/xe/xe_sysctrl_types.h index 66ba24f43017..98c2f473f7c6 100644 --- a/drivers/gpu/drm/xe/xe_sysctrl_types.h +++ b/drivers/gpu/drm/xe/xe_sysctrl_types.h @@ -26,9 +26,6 @@ struct xe_sysctrl { /** @cmd_lock: Mutex protecting mailbox command operations */ struct mutex cmd_lock; - /** @phase_bit: Message boundary phase toggle bit (0 or 1) */ - bool phase_bit; - /** @work: Pending events worker */ struct work_struct work; From d29b399150b07796dfa81d8778d4804c08c2a41d Mon Sep 17 00:00:00 2001 From: Maxime Chevallier Date: Wed, 26 Aug 2026 16:04:53 +0200 Subject: [PATCH 1294/1328] net: stmmac: selftests: Check multiple MMC counters The MMC counters report MAC statistics. Multiple counters can be enabled when the IP is integrated, however there's no way to know exactly which ones. Un-implemented counters seem to report 0. It was found that on StarFive JH7110 and Amlogic SM1, the counter that's used by the selftest (mmc_tx_framecount_g) isn't implemented, triggering an MMC selftest failure. Both the above SoCs seem to implement mmc_rx_framecount_gb, let's use this counter as well for MMC counter validation. Note that this doesn't guarantee that we won't encounter the same issue again if another IP implements yet another set of counters that don't include that new one. If the game of whack-a-mole with implemented counters becomes too hard to maintain, we may simply consider removing the MMC selftest entirely. Fixes: 091810dbded9 ("net: stmmac: Introduce selftests support") Signed-off-by: Maxime Chevallier Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260826140500.616466-2-maxime.chevallier@bootlin.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/stmicro/stmmac/stmmac_selftests.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c index a2b917dd60e5..14db0c0e0ba9 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c @@ -395,11 +395,17 @@ static int stmmac_test_mmc(struct stmmac_priv *priv) stmmac_mmc_read(priv, priv->mmcaddr, &final); /* - * The number of MMC counters available depends on HW configuration - * so we just use this one to validate the feature. I hope there is - * not a version without this counter. + * The number of MMC counters available depends on HW configuration, + * and there doesn't seem to be a way to enumerate the implemented + * counters. + * + * Let's check a hand-picked set of counters, knowing that : + * - Starfive JH7110 doesn't implement mmc_tx_framecount_g + * - Amlogic SM1 doesn't implement any mmc_tx_* + * */ - if (final.mmc_tx_framecount_g <= initial.mmc_tx_framecount_g) + if (final.mmc_tx_framecount_g <= initial.mmc_tx_framecount_g && + final.mmc_rx_framecount_gb <= initial.mmc_rx_framecount_gb) return -EINVAL; return 0; From 9698b6da3714fd2ef47846cb63098d2b2d252e25 Mon Sep 17 00:00:00 2001 From: Maxime Chevallier Date: Wed, 26 Aug 2026 16:04:54 +0200 Subject: [PATCH 1295/1328] net: stmmac: dwmac1000: Account for the primary MAC address for UC filtering The same filter slots are used to store the main MAC address as well as the address for the unicast filter. Let's account for that when deciding whether or not to use promisc when programming the UC list in hardware. Fixes: 47dd7a540b8a ("net: add support for STMicroelectronics Ethernet controllers.") Signed-off-by: Maxime Chevallier Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260826140500.616466-3-maxime.chevallier@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c index caac85fc08f1..d4ace3924891 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c @@ -194,7 +194,7 @@ static void dwmac1000_set_filter(struct mac_device_info *hw, dwmac1000_set_mchash(ioaddr, mc_filter, mcbitslog2); /* Handle multiple unicast addresses (perfect filtering) */ - if (netdev_uc_count(dev) > perfect_addr_number) + if (netdev_uc_count(dev) + 1 > perfect_addr_number) /* Switch to promiscuous mode if more than unicast * addresses are requested than supported by hardware. */ From 82187f42c014d22520b9c3c4e2cfb519223fb29b Mon Sep 17 00:00:00 2001 From: Maxime Chevallier Date: Wed, 26 Aug 2026 16:04:55 +0200 Subject: [PATCH 1296/1328] net: stmmac: dwmac4: Account for the primary MAC address for UC filtering The same filter slots are used to store the main MAC address as well as the address for the unicast filter. Let's account for that when deciding whether or not to use promisc when programming the UC list in hardware. Fixes: 477286b53f55 ("stmmac: add GMAC4 core support") Signed-off-by: Maxime Chevallier Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260826140500.616466-4-maxime.chevallier@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c index c6fcfae27c3d..18b357b257cc 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c @@ -521,7 +521,7 @@ static void dwmac4_set_filter(struct mac_device_info *hw, value |= GMAC_PACKET_FILTER_HPF; /* Handle multiple unicast addresses */ - if (netdev_uc_count(dev) > hw->unicast_filter_entries) { + if (netdev_uc_count(dev) + 1 > hw->unicast_filter_entries) { /* Switch to promiscuous mode if more than 128 addrs * are required */ From 2739d6f9a2b8729b0d85cbe0dc93e1d68670b6f2 Mon Sep 17 00:00:00 2001 From: Maxime Chevallier Date: Wed, 26 Aug 2026 16:04:56 +0200 Subject: [PATCH 1297/1328] net: stmmac: dwxgmac: Account for the primary MAC address for UC filtering The same filter slots are used to store the main MAC address as well as the address for the unicast filter. Let's account for that when deciding whether or not to use promisc when programming the UC list in hardware. Fixes: 0efedbf11f07 ("net: stmmac: xgmac: Fix XGMAC selftests") Signed-off-by: Maxime Chevallier Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260826140500.616466-5-maxime.chevallier@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c index 52054f31376d..fc6ddb51c682 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c @@ -532,7 +532,7 @@ static void dwxgmac2_set_filter(struct mac_device_info *hw, dwxgmac2_set_mchash(ioaddr, mc_filter, mcbitslog2); /* Handle multiple unicast addresses */ - if (netdev_uc_count(dev) > hw->unicast_filter_entries) { + if (netdev_uc_count(dev) + 1 > hw->unicast_filter_entries) { value |= XGMAC_FILTER_PR; } else { struct netdev_hw_addr *ha; From cd8c3b2752c684141eab2282e294cae2971a9759 Mon Sep 17 00:00:00 2001 From: Maxime Chevallier Date: Wed, 26 Aug 2026 16:04:57 +0200 Subject: [PATCH 1298/1328] net: stmmac: selftests: Account for the UC filter list for filtering tests On dwmac, one of the Unicast filter entries is used to store the local HW addr. This means that we have to use promisc mode for any kind of unicast filtering if we only have one slot in our unicast filter. The number of slots available depends on how the IP is integrated, and we can't autodiscover how many of these slots we have available, so the DT property snps,perfect-filter-entries can be used to specify how many are available. Most IP variants default to 1 if this isn't specified, which is the case for the amlogic variants (in this case, S905X3). The stmmac selftests for UC filtering look if we have enough slots in the filter to store the dev->uc list, but doesn't account for the device's own MAC address. The dev->uc list's size we get with netdev_uc_count() also doesn't account for the HW addr. As the selftest only requires one available slot, in the case of single-slot platforms, that means we erroneously consider we have enough room for the test, when we actually don't, and the filtering test fails. Fixes: 091810dbded9 ("net: stmmac: Introduce selftests support") Signed-off-by: Maxime Chevallier Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260826140500.616466-6-maxime.chevallier@bootlin.com Signed-off-by: Jakub Kicinski --- .../stmicro/stmmac/stmmac_selftests.c | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c index 14db0c0e0ba9..ae236a264e74 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c @@ -479,6 +479,21 @@ static int stmmac_filter_check(struct stmmac_priv *priv) return -EOPNOTSUPP; } +static int stmmac_uc_filter_check(struct stmmac_priv *priv) +{ + /* For tests involving the UC filter, we need at least one empty + * slot in the UC filter. The UC filters contains netdev_uc_count() + 1 + * entries: The dev->uc list + one entry for the HW address. + * + * Having an empty slot therefore means netdev_uc_count() + 2 entries + * can fit in the filter + */ + if (netdev_uc_count(priv->dev) + 2 > priv->hw->unicast_filter_entries) + return -EOPNOTSUPP; + + return 0; +} + static bool stmmac_hash_check(struct stmmac_priv *priv, unsigned char *addr) { int mc_offset = 32 - priv->hw->mcast_bits_log2; @@ -570,7 +585,7 @@ static int stmmac_test_pfilt(struct stmmac_priv *priv) if (stmmac_filter_check(priv)) return -EOPNOTSUPP; - if (netdev_uc_count(priv->dev) >= priv->hw->unicast_filter_entries) + if (stmmac_uc_filter_check(priv)) return -EOPNOTSUPP; while (--tries) { @@ -614,7 +629,7 @@ static int stmmac_test_mcfilt(struct stmmac_priv *priv) if (stmmac_filter_check(priv)) return -EOPNOTSUPP; - if (netdev_uc_count(priv->dev) >= priv->hw->unicast_filter_entries) + if (stmmac_uc_filter_check(priv)) return -EOPNOTSUPP; if (netdev_mc_count(priv->dev) >= priv->hw->multicast_filter_bins) return -EOPNOTSUPP; @@ -660,7 +675,7 @@ static int stmmac_test_ucfilt(struct stmmac_priv *priv) if (stmmac_filter_check(priv)) return -EOPNOTSUPP; - if (netdev_uc_count(priv->dev) >= priv->hw->unicast_filter_entries) + if (stmmac_uc_filter_check(priv)) return -EOPNOTSUPP; if (netdev_mc_count(priv->dev) >= priv->hw->multicast_filter_bins) return -EOPNOTSUPP; From 96e8cb5527ce50c024a8e2d22d2bfedeccaf97d0 Mon Sep 17 00:00:00 2001 From: Maxime Chevallier Date: Wed, 26 Aug 2026 16:04:58 +0200 Subject: [PATCH 1299/1328] net: stmmac: selftests: Don't test flow control for small rx fifos On dwmac1000, dwmac4 and dwxgmac, we only emit pause frames if there's at least 4096 bytes in each queue's fifo. The phylink mac capabilities are still MAC_ASYM_PAUSE | MAC_SYM_PAUSE as otherwise we won't be able to negotiate 'rx on' pause. ASYM only will prevent negotiating 'rx off tx on', while SYM only doesn't really matche the reality (not symmetric if we can only do RX pause). Fixes: 091810dbded9 ("net: stmmac: Introduce selftests support") Signed-off-by: Maxime Chevallier Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260826140500.616466-7-maxime.chevallier@bootlin.com Signed-off-by: Jakub Kicinski --- .../ethernet/stmicro/stmmac/stmmac_selftests.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c index ae236a264e74..6372ec7c3f31 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c @@ -739,12 +739,24 @@ static int stmmac_test_flowctrl(struct stmmac_priv *priv) u32 rx_cnt = priv->plat->rx_queues_to_use; struct mac_device_info *mac = priv->hw; struct stmmac_test_priv *tpriv; + unsigned int rx_fifo_size; unsigned int pkt_count; int i, ret = 0; if (!(mac->link.caps & MAC_SYM_PAUSE)) return -EOPNOTSUPP; + rx_fifo_size = priv->plat->rx_fifo_size; + if (!rx_fifo_size) + rx_fifo_size = priv->dma_cap.rx_fifo_size; + + /* No pause frame is emitted if we don't have at least 4096 bytes per + * queue, except on dwmac100. + */ + if (priv->plat->core_type != DWMAC_CORE_MAC100 && + rx_fifo_size / priv->plat->rx_queues_to_use < 4096) + return -EOPNOTSUPP; + tpriv = kzalloc_obj(*tpriv); if (!tpriv) return -ENOMEM; @@ -758,9 +770,7 @@ static int stmmac_test_flowctrl(struct stmmac_priv *priv) dev_add_pack(&tpriv->pt); /* Compute minimum number of packets to make FIFO full */ - pkt_count = priv->plat->rx_fifo_size; - if (!pkt_count) - pkt_count = priv->dma_cap.rx_fifo_size; + pkt_count = rx_fifo_size; pkt_count /= 1400; pkt_count *= 2; From 9c24a504a3af1acb96da8d6a45a373fda8a9c687 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Mon, 24 Aug 2026 11:59:08 +0200 Subject: [PATCH 1300/1328] net: stmmac: drop gso_enabled_types and rely on netdev features The gso_enabled_types field is used by stmmac_xmit() to decide whether a GSO skb should be passed to stmmac_tso_xmit(). It is updated in stmmac_set_features() based solely on NETIF_F_TSO, so disabling IPv4 TSO while keeping IPv6 TSO (NETIF_F_TSO6) enabled zeroes the mask. As a result IPv6 GSO frames, which the networking stack still generates since NETIF_F_TSO6 is enabled, fall through to the non-TSO xmit path where they are not handled. The networking stack already manages the GSO logic: a GSO skb is only delivered to the driver when the matching offload feature (NETIF_F_TSO, NETIF_F_TSO6 or NETIF_F_GSO_UDP_L4) is enabled, otherwise the frame is segmented in software before reaching ndo_start_xmit(). stmmac_features_check() also validates each GSO frame against the TSO hardware constraints and falls back to software GSO when they are not met. Drop the gso_enabled_types field and rely on skb_is_gso() in stmmac_xmit() instead, which correctly routes IPv6 GSO frames to the TSO path when NETIF_F_TSO is disabled. This also removes the data race between stmmac_set_gso_types(), called from the feature-set path, and the lockless read of gso_enabled_types in stmmac_xmit(). Fixes: 2e4082e4b739 ("net: stmmac: simplify GSO/TSO test in stmmac_xmit()") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260824-stmmac-fix-tso6-features-v3-1-c73a7a4a0ec7@oss.qualcomm.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/stmmac.h | 2 -- .../net/ethernet/stmicro/stmmac/stmmac_main.c | 19 +------------------ 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h index 8ba8f03e1ce0..7582fca63741 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h @@ -265,8 +265,6 @@ struct stmmac_priv { u32 rx_coal_frames[MTL_MAX_RX_QUEUES]; int hwts_tx_en; - /* skb_shinfo(skb)->gso_type types that we handle */ - unsigned int gso_enabled_types; bool tx_path_in_lpi_mode; bool sph_active; bool sph_capable; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index efa35cfecc4f..bc6ba4be18b1 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -4376,18 +4376,6 @@ static void stmmac_flush_tx_descriptors(struct stmmac_priv *priv, int queue) stmmac_set_queue_tx_tail_ptr(priv, tx_q, queue, tx_q->cur_tx); } -static void stmmac_set_gso_types(struct stmmac_priv *priv, bool tso) -{ - if (!tso) { - priv->gso_enabled_types = 0; - } else { - /* Manage oversized TCP frames for GMAC4 device */ - priv->gso_enabled_types = SKB_GSO_TCPV4 | SKB_GSO_TCPV6; - if (priv->plat->core_type == DWMAC_CORE_GMAC4) - priv->gso_enabled_types |= SKB_GSO_UDP_L4; - } -} - static void stmmac_set_gso_features(struct net_device *ndev) { struct stmmac_priv *priv = netdev_priv(ndev); @@ -4421,8 +4409,6 @@ static void stmmac_set_gso_features(struct net_device *ndev) if (priv->plat->core_type == DWMAC_CORE_GMAC4) ndev->hw_features |= NETIF_F_GSO_UDP_L4; - stmmac_set_gso_types(priv, true); - dev_info(priv->device, "TSO feature enabled\n"); } @@ -4772,8 +4758,7 @@ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev) if (priv->tx_path_in_lpi_mode && priv->eee_sw_timer_en) stmmac_stop_sw_lpi(priv); - if (skb_is_gso(skb) && - skb_shinfo(skb)->gso_type & priv->gso_enabled_types) + if (skb_is_gso(skb)) return stmmac_tso_xmit(skb, dev); if (priv->est && priv->est->enable && @@ -6207,8 +6192,6 @@ static int stmmac_set_features(struct net_device *netdev, stmmac_enable_sph(priv, priv->ioaddr, sph_en, chan); } - stmmac_set_gso_types(priv, features & NETIF_F_TSO); - if (features & NETIF_F_HW_VLAN_CTAG_RX) priv->hw->hw_vlan_en = true; else From 23680bf5f8c69c923546b84a8e6c401bef8b88fe Mon Sep 17 00:00:00 2001 From: Pascal Kneuper Date: Mon, 24 Aug 2026 14:50:14 +0200 Subject: [PATCH 1301/1328] net: stmmac: restore NET_IP_ALIGN in the RX DMA offset Since the RX path was converted to zero-copy, the page pool page is handed to the stack directly as the skb head, and the offset the DMA engine writes at is what determines the alignment of the packet headers. Before the conversion the payload was copied into an skb obtained from napi_alloc_skb(), which reserves NET_SKB_PAD + NET_IP_ALIGN. The conversion moved the headroom into stmmac_rx_offset() but did not carry over NET_IP_ALIGN, so on architectures where NET_IP_ALIGN is 2 the IP header now lands misaligned: 64 (NET_SKB_PAD) + 14 (ethernet) + 20 (IP) = 98 Same for the XDP branch: 256 (XDP_PACKET_HEADROOM) + 14 (ethernet) + 20 (IP) = 290 On ARM32 this is fatal, because ldm and ldrd trap on unaligned addresses even when CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS is set. Any received echo request panics the machine, e.g: Unhandled fault: alignment exception (0x001) at 0x81873062 Internal error: : 1 [#1] SMP ARM Hardware name: Altera SOCFPGA Arria10 PC is at icmp_echo+0x38/0xa8 LR is at icmp_rcv+0x22c/0x370 Call trace: icmp_echo from icmp_rcv+0x22c/0x370 icmp_rcv from ip_protocol_deliver_rcu+0x2c/0x224 ip_protocol_deliver_rcu from ip_local_deliver+0xc8/0x1a0 ip_local_deliver from ip_sublist_rcv_finish+0x3c/0x50 ip_sublist_rcv_finish from ip_list_rcv_finish+0x110/0x118 ip_list_rcv_finish from ip_list_rcv+0xc8/0xdc ip_list_rcv from __netif_receive_skb_list_core+0x170/0x1c0 ... napi_complete_done from stmmac_napi_poll_rx+0xcb0/0x1030 Code: e24dd068 e59020a0 e28dc010 e0822001 (e8920003) Kernel panic - not syncing: Fatal exception in interrupt The faulting instruction is the ldm of *icmp_hdr(skb) in icmp_echo(). Fix by adding NET_IP_ALIGN back to the RX offset, which restores the alignment the stack used to get. Note that commit a955318fe67e ("stmmac: align RX buffers") made a similar change in 2021 and was reverted by commit 12d125b4574b ("stmmac: Revert "stmmac: align RX buffers"") because it caused packet corruption. That patch raised the offset from 0 without adjusting the buffer size accounting, so the DMA engine could arguably write past the end of the RX buffers, though this was never root caused. Commit df542f669307 ("net: stmmac: Switch to zero-copy in non-XDP RX path") since derives the page pool allocation from stmmac_rx_offset(), so the extra bytes are accounted for. Fixes: df542f669307 ("net: stmmac: Switch to zero-copy in non-XDP RX path") Cc: Daniel Baldin Signed-off-by: Pascal Kneuper Link: https://patch.msgid.link/20260824125014.47862-1-PKneuper@dspace.de Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index bc6ba4be18b1..f2fc89176654 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -1531,9 +1531,9 @@ static void stmmac_display_rings(struct stmmac_priv *priv, static unsigned int stmmac_rx_offset(struct stmmac_priv *priv) { if (stmmac_xdp_is_enabled(priv)) - return XDP_PACKET_HEADROOM; + return XDP_PACKET_HEADROOM + NET_IP_ALIGN; - return NET_SKB_PAD; + return NET_SKB_PAD + NET_IP_ALIGN; } static int stmmac_set_bfsize(int mtu) From 88c71cc0ad9800d8814bd3627b21a0da9028e057 Mon Sep 17 00:00:00 2001 From: Edoardo Pinci Date: Mon, 24 Aug 2026 15:11:43 +0200 Subject: [PATCH 1302/1328] net: dsa: mxl862xx: enable assisted learning on CPU port The MxL862xx driver enables FDB isolation but does not enable assisted learning on the CPU port. Consequently, MAC addresses learned through a physical switch port are not updated in hardware when the corresponding station moves to a foreign bridge port, such as a Wi-Fi interface. The stale hardware FDB entry continues directing return traffic toward the original physical port. Traffic entering that same port is then filtered instead of being forwarded to the CPU and software bridge. This causes downstream unicast traffic, including DHCP OFFER and ACK packets, to disappear after a client roams to a local wireless interface. The client eventually considers the connection unusable and disconnects. Enable assisted CPU-port learning so DSA installs foreign bridge FDB entries on the CPU port. This keeps the hardware FDB synchronized with the software bridge and allows return traffic to reach locally attached Wi-Fi clients after roaming. Tested on a BPI R4 PRO with a MxL862xx switch and a BE14000 WiFi interface. - Without patch, wired uplink on lan6 port (mxl path) Wifi clients connect but roam away not getting DHCP offers - Without patch, wired uplink on wan port (no mxl path) Wifi clients connect and roam successfully - With this patch, uplink on lan6 (mxl path) Wifi clients connect and roam successfully Fixes: 340bdf984613 ("net: dsa: mxl862xx: implement bridge offloading") Signed-off-by: Edoardo Pinci Link: https://patch.msgid.link/DU0P251MB069949C6DEB4D1D51F31FE87C4A02@DU0P251MB0699.EURP251.PROD.OUTLOOK.COM Signed-off-by: Jakub Kicinski --- drivers/net/dsa/mxl862xx/mxl862xx.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.c b/drivers/net/dsa/mxl862xx/mxl862xx.c index 45d237b3a40f..cfa7e3e269a2 100644 --- a/drivers/net/dsa/mxl862xx/mxl862xx.c +++ b/drivers/net/dsa/mxl862xx/mxl862xx.c @@ -2111,6 +2111,7 @@ static int mxl862xx_probe(struct mdio_device *mdiodev) ds->ops = &mxl862xx_switch_ops; ds->phylink_mac_ops = &mxl862xx_phylink_mac_ops; ds->num_ports = MXL862XX_MAX_PORTS; + ds->assisted_learning_on_cpu_port = true; ds->fdb_isolation = true; ds->max_num_bridges = MXL862XX_MAX_BRIDGES; From 8f735d64382dcf162f4276d6699d03ad2f859c0b Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Tue, 25 Aug 2026 04:14:03 -0400 Subject: [PATCH 1303/1328] net/sched: bound qdisc_pkt_len to prevent qdisc soft lockup qdisc_get_stab() accepts a user-supplied size table, and __qdisc_calculate_pkt_len() amplifies qdisc_pkt_len() through the overhead, the size-table data (u16), and size_log (up to STAB_SIZE_LOG_MAX). A crafted stab can therefore set qdisc_pkt_len() to ~1 GiB for an ordinary skb. Per-flow deficit schedulers such as DRR and ETS replenish one quantum per loop iteration; with a tiny quantum (1) they spin billions of times under the qdisc lock, producing a soft lockup / RCU stall as illustrated by vega@nebusec.ai. Cap the final qdisc_pkt_len() to QDISC_PKT_LEN_MAX so the size-table amplification cannot drive deficit schedulers into an unbounded loop. A legitimate size table (e.g. qfq's overhead 999999999, which is handled by dropping) is still accepted. Introduce cap QDISC_PKT_LEN_MAX (1 << 20) = 1 MiB which is well above any legitimate single-skb wire length: the largest current skb->len is GSO_MAX_SIZE (524280), and an ATM-style size table (53/48 cell tax) amplifies that to ~578 KB, both comfortably below 1 MiB. At the same time, 1 MiB bounds the deficit refill loop to ~1M iterations per packet with quantum=1, which completes in a few milliseconds well under the demonstrated softlockup threshold (~10^9 iterations). Conditions to recreate the bug: - CONFIG_NET_SCHED=y, CONFIG_NET_SCH_DRR=y (or CONFIG_NET_SCH_ETS=y). - Attach a DRR (or ETS) root qdisc with a crafted TCA_STAB that amplifies qdisc_pkt_len to ~1 GiB (e.g. size_log=15, data=[32768]). - Add a class with a tiny quantum of 1 and send one small packet; the deficit loop spins billions of times under the qdisc lock and trips the softlockup detector (panic with kernel.softlockup_panic=1). - Reachable as root or from an unprivileged user in a fresh user+net namespace (unshare -Urn) with namespace-local CAP_NET_ADMIN. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: vega@nebusec.ai Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/20260825081403.133992-1-jhs@mojatatu.com Signed-off-by: Jakub Kicinski --- include/net/pkt_sched.h | 1 + net/sched/sch_api.c | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/include/net/pkt_sched.h b/include/net/pkt_sched.h index 18a419cd9d94..90d3e7943b19 100644 --- a/include/net/pkt_sched.h +++ b/include/net/pkt_sched.h @@ -12,6 +12,7 @@ #define DEFAULT_TX_QUEUE_LEN 1000 #define STAB_SIZE_LOG_MAX 30 +#define QDISC_PKT_LEN_MAX (1 << 20) /* 1 MiB */ struct qdisc_walker { int stop; diff --git a/net/sched/sch_api.c b/net/sched/sch_api.c index 65b35528d125..90503e59e6e3 100644 --- a/net/sched/sch_api.c +++ b/net/sched/sch_api.c @@ -610,8 +610,11 @@ void __qdisc_calculate_pkt_len(struct sk_buff *skb, pkt_len <<= stab->szopts.size_log; out: - if (unlikely(pkt_len < 1)) - pkt_len = 1; + /* A size table can inflate qdisc_pkt_len() beyond any real packet + * (via overhead, the data table, or size_log); cap it so deficit + * schedulers such as DRR/ETS terminate their refill loops. + */ + pkt_len = clamp_t(int, pkt_len, 1, QDISC_PKT_LEN_MAX); qdisc_skb_cb(skb)->pkt_len = pkt_len; } From 50e5c6605cc9c2dd57bd2d1b3459674d19738983 Mon Sep 17 00:00:00 2001 From: Norbert Szetei Date: Wed, 26 Aug 2026 11:12:27 +0200 Subject: [PATCH 1304/1328] net: bridge: mcast: fix use-after-free of a master VLAN's multicast context br_multicast_toggle_one_vlan() clears BR_VLFLAG_MCAST_ENABLED under br->multicast_lock before stopping a VLAN's multicast context. That is the teardown handshake: lockless readers gate on the flag through br_multicast_ctx_should_use() -> br_multicast_ctx_vlan_disabled(), so once it is cleared under the lock no reader can arm the context again. For a master VLAN the handshake never runs. __vlan_del() clears BRIDGE_VLAN_INFO_BRENTRY before calling br_vlan_put_master(), so br_multicast_toggle_one_vlan(masterv, false) returns early on !br_vlan_is_brentry(vlan): the flag stays set and br->multicast_lock is never taken. br_vlan_put_master() then drains the context in br_multicast_ctx_deinit() and frees the VLAN through call_rcu(), while a reader still inside rcu_read_lock() sees the context as enabled and re-arms it. The port and port-VLAN branch of the function has no br_vlan_is_brentry() test and flips the flag under br->multicast_lock, so it is not affected. The reader is the bridge transmit path. For a master VLAN br_multicast_rcv() selects brmctx = &vlan->br_mcast_ctx with pmctx = NULL, so IGMP sent to the bridge device re-arms the context's timers after br_multicast_ctx_deinit() has already stopped them. BUG: KASAN: slab-use-after-free in detach_if_pending+0x412/0x4a0 Write of size 8 at addr ffff88810ac39918 by task brmc/601 __mod_timer+0x51a/0xc50 br_multicast_host_join+0x25b/0x390 __br_multicast_add_group+0x468/0x530 br_ip4_multicast_add_group+0x1a0/0x260 br_multicast_rcv+0x2cda/0x61e0 br_dev_xmit+0x6c4/0x1540 Allocated by task 610: br_vlan_add+0x111/0xb40 br_vlan_info+0x370/0x3e0 Freed by task 0: kfree+0x1a7/0x4f0 rcu_core+0x7dc/0x10a0 Only test br_vlan_is_brentry() when enabling, like the br_multicast_ctx_vlan_global_disabled() test next to it. Disabling then always clears BR_VLFLAG_MCAST_ENABLED under br->multicast_lock before br_multicast_ctx_deinit() drains the context. Fixes: 7b54aaaf53cb ("net: bridge: multicast: add vlan state initialization and control") Cc: stable@vger.kernel.org Signed-off-by: Norbert Szetei Acked-by: Nikolay Aleksandrov Link: https://patch.msgid.link/D400F6C7-543A-4B79-9E5B-D1D8974DE5C9@doyensec.com Signed-off-by: Jakub Kicinski --- net/bridge/br_multicast.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c index 75e1e2a8fc83..3ef5d8bbf552 100644 --- a/net/bridge/br_multicast.c +++ b/net/bridge/br_multicast.c @@ -4377,8 +4377,8 @@ void br_multicast_toggle_one_vlan(struct net_bridge_vlan *vlan, bool on) if (br_vlan_is_master(vlan)) { br = vlan->br; - if (!br_vlan_is_brentry(vlan) || - (on && + if (on && + (!br_vlan_is_brentry(vlan) || br_multicast_ctx_vlan_global_disabled(&vlan->br_mcast_ctx))) return; From 23c53269f2baaedf2d92784290cb9ef6db2a3bce Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 26 Aug 2026 10:52:38 +0000 Subject: [PATCH 1305/1328] slip: remove slip_hangup() to fix use-after-free in slip_receive_buf() Jaeyoung Chung and Eulgyu Kim reported a slab-use-after-free read in slip_receive_buf() when racing against tty hangup. tty_ldisc_hangup() calls ld->ops->hangup() while holding only a read lock on tty->ldisc_sem (via tty_ldisc_ref()). Because slip_hangup() simply called slip_close(), it ran concurrently with reader functions such as slip_receive_buf(). slip_close() unregisters and frees the net device and its private struct slip, causing concurrent reader threads in slip_receive_buf() to dereference freed memory. Line discipline close() is already guaranteed to be called under the write lock of tty->ldisc_sem during hangup processing (in tty_ldisc_reinit() or tty_ldisc_kill()). Remove slip_hangup() so teardown is serialized cleanly by slip_close(). Fixes: 5342b77c4123 ("slip: Clean up create and destroy") Reported-by: Jaeyoung Chung Reported-by: Eulgyu Kim Closes: https://lore.kernel.org/netdev/20260825150655.1450271-1-jjy600901@snu.ac.kr/ Cc: Qingfang Deng Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260826105238.3323436-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- drivers/net/slip/slip.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/drivers/net/slip/slip.c b/drivers/net/slip/slip.c index 85b2438e8923..fdd6f63fb849 100644 --- a/drivers/net/slip/slip.c +++ b/drivers/net/slip/slip.c @@ -886,8 +886,6 @@ static int slip_open(struct tty_struct *tty) * Close down a SLIP channel. * This means flushing out any pending queues, and then returning. This * call is serialized against other ldisc functions. - * - * We also use this method fo a hangup event */ static void slip_close(struct tty_struct *tty) @@ -915,11 +913,6 @@ static void slip_close(struct tty_struct *tty) unregister_netdev(sl->dev); /* sl_uninit() has dropped the slip_devs[] entry by now */ } - -static void slip_hangup(struct tty_struct *tty) -{ - slip_close(tty); -} /************************************************************************ * STANDARD SLIP ENCAPSULATION * ************************************************************************/ @@ -1280,7 +1273,6 @@ static struct tty_ldisc_ops sl_ldisc = { .name = "slip", .open = slip_open, .close = slip_close, - .hangup = slip_hangup, .ioctl = slip_ioctl, .receive_buf = slip_receive_buf, .write_wakeup = slip_write_wakeup, From dd890ae29299636fb037276fc1b5238698d08b03 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 26 Aug 2026 17:34:28 +0700 Subject: [PATCH 1306/1328] net: fec: only stop PTP if it was initialized fec_ptp_init() is only called when fep->bufdesc_ex is available. However, fec_probe() unconditionally calls fec_ptp_stop() on the failed_init path, and fec_drv_remove() unconditionally calls fec_ptp_stop() during device removal. Check fep->bufdesc_ex before calling fec_ptp_stop() in both paths to avoid stopping PTP when it was not initialized. Fixes: 32cba57ba74b ("net: fec: introduce fec_ptp_stop and use in probe fail path") Reviewed-by: Wei Fang Reviewed-by: Frank Li Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260826103428.32807-1-phucduc.bui@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/freescale/fec_main.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/freescale/fec_main.c b/drivers/net/ethernet/freescale/fec_main.c index ced4dbf8cd90..794ec427b0ee 100644 --- a/drivers/net/ethernet/freescale/fec_main.c +++ b/drivers/net/ethernet/freescale/fec_main.c @@ -5457,7 +5457,8 @@ fec_probe(struct platform_device *pdev) failed_irq: fec_enet_deinit(ndev); failed_init: - fec_ptp_stop(pdev); + if (fep->bufdesc_ex) + fec_ptp_stop(pdev); failed_reset: pm_runtime_put_noidle(&pdev->dev); pm_runtime_disable(&pdev->dev); @@ -5499,7 +5500,8 @@ fec_drv_remove(struct platform_device *pdev) ERR_PTR(ret)); cancel_work_sync(&fep->tx_timeout_work); - fec_ptp_stop(pdev); + if (fep->bufdesc_ex) + fec_ptp_stop(pdev); unregister_netdev(ndev); fec_enet_mii_remove(fep); if (fep->reg_phy) From a60fd8c6dbaa76da4163cf225ed2b9e982540f39 Mon Sep 17 00:00:00 2001 From: Deepanshu Kartikey Date: Wed, 26 Aug 2026 19:02:58 +0530 Subject: [PATCH 1307/1328] usb: atm: usbatm: fix invalid ci_range initialization syzbot reported a shift-out-of-bounds in __vcc_connect(): UBSAN: shift-out-of-bounds in net/atm/common.c:382:32 shift exponent -1 is negative CPU: 0 UID: 0 PID: 5987 Comm: syz.0.18 Not tainted syzkaller #0 PREEMPT(full) Hardware name: Google Compute Engine/Google Compute Engine, BIOS Google 08/05/2026 Call Trace: dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120 ubsan_epilogue+0xa/0x30 lib/ubsan.c:233 __ubsan_handle_shift_out_of_bounds+0x36d/0x400 lib/ubsan.c:494 __vcc_connect+0x14b4/0x19c0 net/atm/common.c:382 vcc_connect+0x328/0x8f0 net/atm/common.c:498 pvc_bind+0x272/0x380 net/atm/pvc.c:52 __sys_bind+0x2e3/0x410 net/socket.c:1976 __x64_sys_bind+0x7a/0x90 net/socket.c:1979 ... ATM device ci_range fields (vpi_bits and vci_bits) represent the number of bits supported for VPI and VCI addressing on the device. net/atm/common.c directly uses these fields as bit shift counts: vpi >> dev->ci_range.vpi_bits vci >> dev->ci_range.vci_bits 1 << vcc->dev->ci_range.vpi_bits 1 << vcc->dev->ci_range.vci_bits usbatm_atm_init() sets ci_range.vpi_bits and ci_range.vci_bits to ATM_CI_MAX (-1), which is defined in as a sentinel value for userspace ATM_SETCIRANGE requests, not a valid bit count. Shifting by -1 is undefined behavior and triggers UBSAN warnings. ATM UNI cell headers allow up to 8 bits for VPI (0..255) and 16 bits for VCI (0..65535). Initialize vpi_bits to 8 and vci_bits to 16, as done by solos-pci. Fixes: c59bba75fa50 ("[PATCH] USB ATM: new usbatm core") Reported-by: syzbot+6665d3db5fef15914802@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=6665d3db5fef15914802 Suggested-by: Eric Dumazet Link: https://lore.kernel.org/all/20260824024620.23485-1-kartikey406@gmail.com/T/ [v1] Signed-off-by: Deepanshu Kartikey Link: https://patch.msgid.link/20260826133258.8306-1-kartikey406@gmail.com Signed-off-by: Jakub Kicinski --- drivers/usb/atm/usbatm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/atm/usbatm.c b/drivers/usb/atm/usbatm.c index 9600e1ec0993..7b0c791399ea 100644 --- a/drivers/usb/atm/usbatm.c +++ b/drivers/usb/atm/usbatm.c @@ -917,8 +917,8 @@ static int usbatm_atm_init(struct usbatm_data *instance) instance->atm_dev = atm_dev; - atm_dev->ci_range.vpi_bits = ATM_CI_MAX; - atm_dev->ci_range.vci_bits = ATM_CI_MAX; + atm_dev->ci_range.vpi_bits = 8; + atm_dev->ci_range.vci_bits = 16; atm_dev->signal = ATM_PHY_SIG_UNKNOWN; /* temp init ATM device, set to 128kbit */ From ce2b807f42ed5e55567b8864ab72963f90779270 Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Wed, 26 Aug 2026 22:11:26 +0800 Subject: [PATCH 1308/1328] tcp: fix corruption of urgent data on multi-segment retransmit On the normal xmit path, while in urgent mode we refuse to build a multi-segment TSO packet, so every segment gets its own urg_ptr: /* tcp_write_xmit() */ limit = mss_now; if (tso_segs > 1 && !tcp_urg_mode(tp)) limit = tcp_mss_split_point(...); The retransmit path has no such guard. __tcp_retransmit_skb() builds a segs > 1 skb and hands it to the GSO layer, which only advances th->seq per segment and copies urg_ptr verbatim: /* __tcp_retransmit_skb() */ len = cur_mss * segs; /* segs > 1, no urg_mode check */ ... /* tcp_gso_segment(): bumps seq only, urg_ptr is copied */ urg_ptr is an offset from the segment's own seq, so a copied value points at a different place on each segment. The receiver rebuilds the absolute urgent seq as seg.seq + urg_ptr, so it walks a moving urgent point instead of the one OOB byte: seg1 seq 1 urg_ptr 5001 -> urgent @ 5001 (ok) seg2 seq 1001 urg_ptr 5001 -> urgent @ 6001 (wrong, +MSS) seg3 seq 2001 urg_ptr 5001 -> urgent @ 7001 (wrong, +2*MSS) The real OOB byte is never pointed at, so the receiver stops splicing it out and delivers it as normal in-band data, corrupting the stream. Guard the retransmit length like the xmit path: keep segs = 1 while in urgent mode. Fixes: 10d3be569243 ("tcp-tso: do not split TSO packets at retransmit time") Signed-off-by: Jiayuan Chen Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260826141145.67823-1-jiayuan.chen@linux.dev Signed-off-by: Jakub Kicinski --- net/ipv4/tcp_output.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c index f2709d585edb..6f4dca4a4de9 100644 --- a/net/ipv4/tcp_output.c +++ b/net/ipv4/tcp_output.c @@ -3603,7 +3603,7 @@ int __tcp_retransmit_skb(struct sock *sk, struct sk_buff *skb, int segs) avail_wnd = cur_mss; } - len = cur_mss * segs; + len = cur_mss * (tcp_urg_mode(tp) ? 1 : segs); if (len > avail_wnd) { len = rounddown(avail_wnd, cur_mss); if (!len) From 6a7e91f890eceb4fd9d3662e7ffba1fcb55cc00e Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Wed, 26 Aug 2026 22:11:27 +0800 Subject: [PATCH 1309/1328] selftests/net: packetdrill: add tcp_urg_ptr_retransmit Drive a connection into urgent mode and force a multi-segment retransmit, checking that each retransmitted segment keeps its own urg_ptr. The test asserts the fixed behaviour: the hole is retransmitted as two independent skbs, each with its own urg_ptr (5001 and 4001) and no PSH. An unpatched kernel instead sends one super-skb whose GSO split copies urg_ptr onto the second segment and also sets PSH there, so on an unpatched kernel the mismatch shows up on the PSH bit (actual P.U ... urg 5001) before the urg_ptr: tcp_urg_ptr_retransmit.pkt:63: live packet field tcp_psh: expected: 0 (0x0) vs actual: 1 (0x1) script packet: .U 1001:2001(1000) ack 1 actual packet: P.U 1001:2001(1000) ack 1 win 1050 After the fix the retransmit carries a per-segment urg_ptr and the test passes. Signed-off-by: Jiayuan Chen Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260826141145.67823-2-jiayuan.chen@linux.dev Signed-off-by: Jakub Kicinski --- .../packetdrill/tcp_urg_ptr_retransmit.pkt | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 tools/testing/selftests/net/packetdrill/tcp_urg_ptr_retransmit.pkt diff --git a/tools/testing/selftests/net/packetdrill/tcp_urg_ptr_retransmit.pkt b/tools/testing/selftests/net/packetdrill/tcp_urg_ptr_retransmit.pkt new file mode 100644 index 000000000000..22f750ce09c1 --- /dev/null +++ b/tools/testing/selftests/net/packetdrill/tcp_urg_ptr_retransmit.pkt @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-2.0 +--ip_version=ipv4 +// +// Reproduce urg_ptr being copied across segments on a multi-segment retransmit +// in urgent mode (regression since 10d3be569243). +// +// server (kernel, under test) client (packetdrill) +// | write(5000): 1:1001 .. 4001:5001 | mss 1000 from +// | -------------------------------------------> | the client SYN +// | send(MSG_OOB): 5001:5002 urg 1 | snd_up = 5002 +// | -------------------------------------------> | +// | SACK 2001:5002, leaving hole 1:2001| +// | <------------------------------------------- | +// | retransmit hole 1:2001 as ONE skb: | +// | seq=1, 2 segments, urg_ptr = 5002-1 = 5001| +// | tun tso off -> software GSO splits it: | +// | seg A 1:1001 urg_ptr 5001 (correct) | +// | seg B 1001:2001 urg_ptr ? | +// | want 5002-1001 = 4001 | +// | bug inherits 5001 <- caught here | +// | -------------------------------------------> | +// + +`./defaults.sh` + + 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 + +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 + +0 bind(3, ..., ...) = 0 + +0 listen(3, 1) = 0 + +// 1. client force mss=1000 + +.1 < S 0:0(0) win 32792 + +0 > S. 0:0(0) ack 1 + +.1 < . 1:1(0) ack 1 win 320 + +0 accept(3, ..., ...) = 4 + +// 2. server sends 5000 bytes; TSO on, so packetdrill sees whole super-skbs + +0 write(4, ..., 5000) = 5000 + +0 > P. 1:5001(5000) ack 1 + +// 3. server send OOB + +0 send(4, ..., 1, MSG_OOB) = 1 + +0 > PU. 5001:5002(1) ack 1 urg 1 + +// We could disable GSO at the start of the script, but then the PSH flag on +// the 5 initial server segments is not deterministic and hard to match. Keep +// TSO on for the initial send (one super-skb, stable PSH) and disable it only +// here, so software GSO splits the retransmit and each segment's urg_ptr is +// checked on the wire. + +0 `ethtool -K tun0 tso off gso off gro off lro off 2>/dev/null` + +// 4. SACKed blocks reach dupthresh -> fast retransmit of the 1:2001 hole. + +.05 < . 1:1(0) ack 1 win 320 + +0 < . 1:1(0) ack 1 win 320 + +0 < . 1:1(0) ack 1 win 320 + +// Retransmit must keep a per-segment urg_ptr (5002 - seg.seq): seg A 5001, +// seg B 4001. The fix sends the hole as two independent skbs, so seg B has +// no PSH. Unpatched it goes out as one super-skb whose GSO split copies +// urg_ptr onto seg B and also adds PSH there, so on an unpatched kernel the +// mismatch shows up on the PSH bit before the urg_ptr. + +0 > U. 1:1001(1000) ack 1 urg 5001 + +0 > U. 1001:2001(1000) ack 1 urg 4001 + + +.1 < . 1:1(0) ack 5002 win 320 From 729c4896ab829169f95915d65edd530325910b37 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Wed, 26 Aug 2026 11:33:39 -0300 Subject: [PATCH 1310/1328] net/sched: sch_htb: limit htb_classify inner-class filter hops MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit htb_classify() follows each filter-selected inner class by switching to cl->filter_list, but never bounds the number of hops. A filter on an inner class can point back to itself or to another inner class that points back, creating an infinite loop in the packet classification path with the qdisc lock held and BH disabled — a soft lockup / panic from a single packet. Bound the traversal with a hop counter and drop the packet with a rate-limited warning once the bound is exceeded. The counter is incremented at the point the inner filter chain is picked up, after the TC_ACT_* switch has consumed the classifier verdict, so a terminal TC_ACT_QUEUED/STOLEN/TRAP on the last permitted chain still sets *qerr to __NET_XMIT_STOLEN and the packet is not charged as a drop by this qdisc or its parent. The bound is TC_HTB_MAXDEPTH, taken from HTB's own parameters rather than from the qdisc hierarchy depth limit. Class levels run from 0 to TC_HTB_MAXDEPTH - 1, so a traversal that strictly descends in level can take at most TC_HTB_MAXDEPTH hops. That descent is what a sane configuration does, but it is assumed here rather than enforced: htb_find() resolves a classid against every class in the qdisc, so a filter may equally select a sibling or an ancestor. The normal root -> inner -> leaf path takes a single hop, so the bound does not affect legitimate classification. htb_classify() can now return NULL irrespective of CONFIG_NET_CLS_ACT, whereas previously every NULL return sat inside that ifdef. The NULL handler in htb_enqueue() therefore cannot stay conditional either, so drop the ifdef around it. This matches hfsc_enqueue(), which has always handled a NULL class unconditionally. Without it, a kernel built without actions would dereference a NULL class instead of dropping. Conditions to recreate the bug: - CONFIG_NET_SCHED, CONFIG_NET_SCH_HTB, CONFIG_NET_CLS_U32, CONFIG_LOCKUP_DETECTOR. - Create an HTB qdisc on a device (e.g. lo), add an inner class 1:1 with a leaf child 1:10, install a root u32 filter selecting 1:1, and an inner-class u32 filter on 1:1 also selecting 1:1. - Send one packet (ping). On the unfixed kernel the classify loop spins with the qdisc lock held; with softlockup_panic=1 it panics. - Reachable from unprivileged user via unshare -Urn (CAP_NET_ADMIN). Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: Vega Co-developed-by: Victor Nogueira Signed-off-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260826143339.271935-1-victor@mojatatu.com Signed-off-by: Jakub Kicinski --- net/sched/sch_htb.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/net/sched/sch_htb.c b/net/sched/sch_htb.c index fdac0dc8f35a..1ba67b121de4 100644 --- a/net/sched/sch_htb.c +++ b/net/sched/sch_htb.c @@ -224,6 +224,7 @@ static struct htb_class *htb_classify(struct sk_buff *skb, struct Qdisc *sch, struct htb_class *cl; struct tcf_result res; struct tcf_proto *tcf; + unsigned int hops = 0; int result; /* allow to select class by setting skb->priority to valid classid; @@ -266,6 +267,10 @@ static struct htb_class *htb_classify(struct sk_buff *skb, struct Qdisc *sch, if (!cl->level) return cl; /* we hit leaf; return it */ + if (++hops > TC_HTB_MAXDEPTH) { + pr_warn_ratelimited("htb: classify loop detected, dropping packet\n"); + return NULL; + } /* we have got inner class; apply inner filter chain */ tcf = rcu_dereference_bh(cl->filter_list); } @@ -633,13 +638,11 @@ static int htb_enqueue(struct sk_buff *skb, struct Qdisc *sch, } else { return qdisc_drop(skb, sch, to_free); } -#ifdef CONFIG_NET_CLS_ACT } else if (!cl) { if (ret & __NET_XMIT_BYPASS) qdisc_qstats_drop(sch); __qdisc_drop(skb, to_free); return ret; -#endif } else if ((ret = qdisc_enqueue(skb, cl->leaf.q, to_free)) != NET_XMIT_SUCCESS) { if (net_xmit_drop_count(ret)) { From d5dc1e69fd7258ea605c9952e5d5947539159ae3 Mon Sep 17 00:00:00 2001 From: Xinyang Ge Date: Thu, 27 Aug 2026 16:07:07 +0200 Subject: [PATCH 1311/1328] inet: frags: strip GSO state from fragments before reassembly A virtio_net_hdr (tun/tap, or AF_PACKET with PACKET_VNET_HDR) can mark an IPv4 or IPv6 fragment as GSO; nothing relates gso_type to frag_off. inet_frag_reasm_prepare()/inet_frag_reasm_finish() keep the first fragment's skb as the head of the reassembled datagram, including its shinfo->gso_size/gso_type/gso_segs, and chain the remaining fragments on frag_list with whatever linear/paged layout they arrived with. After ip_defrag() (ip_local_deliver(), nf_defrag_ipv4, ...) the reassembled skb therefore still claims to be GSO (SKB_GSO_DODGY), and the next software segmentation point - udp_rcv_segment() on local delivery, validate_xmit_skb(), or the ip_finish_output_gso() slow path - hands it to skb_segment(). skb_segment()'s frag_list walk assumes GRO-shaped input and hits one of its BUG_ON()s. Two writes to a tap by an unprivileged user in its own userns are enough: kernel BUG at net/core/skbuff.c:4899! Oops: invalid opcode: 0000 [#1] SMP KASAN NOPTI CPU: 0 UID: 1000 PID: 82 Comm: poc Not tainted 7.2.0-pentest+ #2 RIP: 0010:skb_segment+0x20ca/0x48b0 Call Trace: __udp_gso_segment+0x29a/0x27d0 udp4_ufo_fragment+0x458/0x6c0 inet_gso_segment+0x429/0x1340 skb_mac_gso_segment+0x233/0x4f0 __skb_gso_segment+0x308/0x660 udp_queue_rcv_skb+0x440/0xad0 udp_unicast_rcv_skb+0xc7/0x2c0 udp_rcv+0x16ce/0x2260 ip_protocol_deliver_rcu+0x197/0x2d0 ip_local_deliver+0x430/0x690 ip_rcv+0x16f/0x1f0 __netif_receive_skb_one_core+0x15e/0x1c0 __netif_receive_skb+0x1e/0x110 netif_receive_skb+0xf6/0x5c0 tun_rx_batched.isra.0+0x3ab/0x790 tun_get_user+0x17c3/0x3550 tun_chr_write_iter+0xba/0x1b0 vfs_write+0x646/0x1130 Kernel panic - not syncing: Fatal exception in interrupt This runs with BH disabled, so it is a panic rather than an oops. The same is reachable with CAP_NET_RAW in a netns where a defrag point precedes a GSO point, and from a guest whose VMM forwards virtio_net_hdr to a tap. The SKB_GSO_DODGY frag_list checks added by commit 3dcbdb134f32 ("net: gso: Fix skb_segment splat when splitting gso_size mangled skb having linear-headed frag_list") and by commit 9e4b7a99a03a ("net: gso: fix panic on frag_list with mixed head alloc types") do not cover it: page-backed heads skip them, and kmalloc heads skip them when gso_size == skb_headlen(head), which the sender controls. An skb entering a frag queue is an IP fragment by definition and cannot legitimately carry GSO state: GRO does not merge fragments and the stack segments before it fragments, so only untrusted sources are affected. This has been reachable since commit f43798c27684 ("tun: Allow GSO using virtio_net_hdr"), the first path that let userspace attach GSO metadata to an IP fragment. Reset the GSO fields of every fragment as it is queued, in inet_frag_queue_insert(), which IPv4, IPv6, nf_conntrack_reasm and 6lowpan reassembly share; then neither the head nor the frag_list members of the reassembled skb carry them (the members matter too: the ip_do_fragment()/ip6_fragment() fast paths send them out as they are). The head may remain CHECKSUM_PARTIAL; that is already accepted on receive and resolved by skb_checksum_help() in ip_do_fragment()/ip6_fragment() on forward. Tested on top of net.git (dc4b95b8fee9), x86_64: the tap reproducer above, two further IPv4 frag_list geometries that reach BUG_ON(i >= nfrags) and BUG_ON(!list_skb->head_frag), and an IPv6 fragment-header variant (udp6_ufo_fragment()) each panic the unpatched kernel; with this patch all four datagrams are delivered intact and nothing is logged. Fixes: f43798c27684 ("tun: Allow GSO using virtio_net_hdr") Cc: stable@kernel.org Suggested-by: Eric Dumazet Signed-off-by: Xinyang Ge Signed-off-by: Paolo Abeni Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/937926e509f2acd8e0e66520dc2b30fd6b4d1687.1787839506.git.pabeni@redhat.com Signed-off-by: Jakub Kicinski --- net/ipv4/inet_fragment.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/net/ipv4/inet_fragment.c b/net/ipv4/inet_fragment.c index fc0cb993959f..c17e57ec7d5c 100644 --- a/net/ipv4/inet_fragment.c +++ b/net/ipv4/inet_fragment.c @@ -435,6 +435,13 @@ int inet_frag_queue_insert(struct inet_frag_queue *q, struct sk_buff *skb, { struct sk_buff *last = q->fragments_tail; + /* An IP fragment is never a GSO packet, but an untrusted source + * (virtio_net_hdr) may have attached GSO metadata to it. Do not let + * that reach the reassembled skb, whose head keeps the first + * fragment's shinfo and whose frag_list is not GRO-shaped. + */ + skb_gso_reset(skb); + /* RFC5722, Section 4, amended by Errata ID : 3089 * When reassembling an IPv6 datagram, if * one or more its constituent fragments is determined to be an From afce9701d6423a63194a349d2f1e34c50ce76482 Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Fri, 21 Aug 2026 22:07:58 +0200 Subject: [PATCH 1312/1328] MAINTAINERS: update rtc subsystem patchwork location The RTC subsystem is migrating it patchwork to kernel.org. Link: https://patch.msgid.link/202608212007582a463833@mail.local Signed-off-by: Alexandre Belloni --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 15011f5752a9..e187e54477cd 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22685,7 +22685,7 @@ REAL TIME CLOCK (RTC) SUBSYSTEM M: Alexandre Belloni L: linux-rtc@vger.kernel.org S: Maintained -Q: http://patchwork.ozlabs.org/project/rtc-linux/list/ +Q: https://patchwork.kernel.org/project/linux-rtc/list/ T: git git://git.kernel.org/pub/scm/linux/kernel/git/abelloni/linux.git F: Documentation/admin-guide/rtc.rst F: Documentation/devicetree/bindings/rtc/ From a4f3fbccb65de757569686baaf2b72e329096aba Mon Sep 17 00:00:00 2001 From: Kathiravan Thirumoorthy Date: Wed, 12 Aug 2026 14:00:55 +0530 Subject: [PATCH 1313/1328] i2c: qcom-geni: update frequency table to fix timing parameters In IPQ5424, to meet the setup and hold timing requirements in the standard mode, update the frequency table with the values recommended by HW design team. Also remove the stray space in the I2C_MAX_FAST_MODE_FREQ entry. Fixes: 85c34532849d ("i2c: qcom-geni: fix I2C frequency table to achieve accurate bus rates") Fixes: 506bb2ab0075 ("i2c: qcom-geni: Support systems with 32MHz serial engine clock") Signed-off-by: Kathiravan Thirumoorthy Cc: # v6.13+ Reviewed-by: Mukesh Savaliya Reviewed-by: Konrad Dybcio Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260812-ipq5424_i2c_scl_updates-v2-1-e09cd39d01d7@oss.qualcomm.com --- drivers/i2c/busses/i2c-qcom-geni.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c index 658636c1ee0e..00013b41a6f5 100644 --- a/drivers/i2c/busses/i2c-qcom-geni.c +++ b/drivers/i2c/busses/i2c-qcom-geni.c @@ -188,8 +188,8 @@ static const struct geni_i2c_clk_fld geni_i2c_clk_map_19p2mhz[] = { /* source_clock = 32 MHz */ static const struct geni_i2c_clk_fld geni_i2c_clk_map_32mhz[] = { - { I2C_MAX_STANDARD_MODE_FREQ, 8, 14, 18, 38 }, - { I2C_MAX_FAST_MODE_FREQ, 4, 3, 9, 19 }, + { I2C_MAX_STANDARD_MODE_FREQ, 12, 9, 10, 26 }, + { I2C_MAX_FAST_MODE_FREQ, 4, 3, 9, 19 }, { I2C_MAX_FAST_MODE_PLUS_FREQ, 2, 3, 5, 15 }, {} }; From 77549d01edecc20da73c8599e14648877198ce9b Mon Sep 17 00:00:00 2001 From: Hongbo Yao Date: Wed, 26 Aug 2026 15:05:47 +0800 Subject: [PATCH 1314/1328] i2c: designware: Enable interrupt mask workaround for HJMC3001 On HJMicro ARM64 servers, the DesignWare I2C controller does not retrigger a pending interrupt if the interrupt status changes after the current status bits have been cleared. The issue is exposed under heavy system load when the corresponding SPI is routed across sockets to a core in the remote socket. The interrupt is then lost and the I2C transfer times out. Enable ACCESS_INTR_MASK for HJMC3001. This toggles DW_IC_INTR_MASK before returning from the interrupt handler and retriggers any pending interrupt. Fixes: 6816ce57c479 ("i2c: designware: Add a new ACPI HID for HJMC01 I2C controller") Signed-off-by: Hongbo Yao Cc: # v6.13+ Acked-by: Mika Westerberg Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260826070547.268672-1-andy.xu@hj-micro.com --- drivers/i2c/busses/i2c-designware-platdrv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/i2c/busses/i2c-designware-platdrv.c b/drivers/i2c/busses/i2c-designware-platdrv.c index 6d6e81242f74..447af5523c2e 100644 --- a/drivers/i2c/busses/i2c-designware-platdrv.c +++ b/drivers/i2c/busses/i2c-designware-platdrv.c @@ -272,7 +272,7 @@ static const struct acpi_device_id dw_i2c_acpi_match[] = { { "HISI02A1", 0 }, { "HISI02A2", 0 }, { "HISI02A3", 0 }, - { "HJMC3001", 0 }, + { "HJMC3001", ACCESS_INTR_MASK }, { "HYGO0010", ACCESS_INTR_MASK }, { "INT33C2", 0 }, { "INT33C3", 0 }, From f98d4986482151a835b521a734722fe8dc5ca37d Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Wed, 12 Aug 2026 17:44:25 +0800 Subject: [PATCH 1315/1328] i2c: qcom-cci: fix autosuspend cleanup cci_probe() calls pm_runtime_use_autosuspend(), but the remove path does not call the matching pm_runtime_dont_use_autosuspend() before disabling runtime PM. If the autosuspend delay is set to a negative value while autosuspend is enabled, the runtime PM core increments usage_count to prevent runtime suspend. Without undoing the autosuspend setting during teardown, this reference is not dropped and usage_count remains unbalanced. Use devm_pm_runtime_set_active_enabled() to manage the runtime PM state. Its managed cleanup disables autosuspend and runtime PM and restores the suspended state on probe failure and driver removal. Remove the now redundant manual runtime PM cleanup. This issue was found by manual code inspection. Fixes: e517526195de ("i2c: Add Qualcomm CCI I2C driver") Signed-off-by: Guangshuo Li Cc: # v5.8+ Reviewed-by: Vladimir Zapolskiy Reviewed-by: Loic Poulain Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260812094425.3515179-1-lgs201920130244@gmail.com --- drivers/i2c/busses/i2c-qcom-cci.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/drivers/i2c/busses/i2c-qcom-cci.c b/drivers/i2c/busses/i2c-qcom-cci.c index bdeda3979c48..25b6e4e9e3fa 100644 --- a/drivers/i2c/busses/i2c-qcom-cci.c +++ b/drivers/i2c/busses/i2c-qcom-cci.c @@ -595,9 +595,11 @@ static int cci_probe(struct platform_device *pdev) goto disable_clocks; pm_runtime_set_autosuspend_delay(dev, MSEC_PER_SEC); + ret = devm_pm_runtime_set_active_enabled(dev); + if (ret) + goto disable_clocks; + pm_runtime_use_autosuspend(dev); - pm_runtime_set_active(dev); - pm_runtime_enable(dev); for (i = 0; i < cci->data->num_masters; i++) { if (!cci->master[i].cci) @@ -613,8 +615,6 @@ static int cci_probe(struct platform_device *pdev) return 0; error_i2c: - pm_runtime_disable(dev); - pm_runtime_dont_use_autosuspend(dev); for (--i ; i >= 0; i--) { if (cci->master[i].cci) { @@ -640,9 +640,6 @@ static void cci_remove(struct platform_device *pdev) cci_halt(cci, i); } } - - pm_runtime_disable(&pdev->dev); - pm_runtime_set_suspended(&pdev->dev); } static const struct cci_data cci_v1_data = { From 27c9445be86b1313746c46d659d3f823a5f7a218 Mon Sep 17 00:00:00 2001 From: Carlos Song Date: Mon, 3 Aug 2026 11:27:05 +0800 Subject: [PATCH 1316/1328] i2c: imx-lpi2c: avoid accessing target registers on master-only controllers Not all LPI2C controller instances implement the Target block. Since commit 90311787f483 ("i2c: imx-lpi2c: reset controller in probe stage"), the driver unconditionally resets both the Master and Target blocks during probe. On controllers that do not support target mode, accessing the Target registers triggers an asynchronous SError and prevents the driver from probing successfully. For example on i.MX8QM: SError Interrupt on CPU2, code 0x00000000bf000002 -- SError Hardware name: Freescale i.MX8QM MEK (DT) pc : lpi2c_imx_probe+0x280/0x594 lr : lpi2c_imx_probe+0x224/0x594 Kernel panic - not syncing: Asynchronous SError Interrupt The VERID register is implemented in the Master block and can be safely accessed on all controller variants. Its FEATURE field indicates whether target mode is supported. Read VERID during probe and use it to determine whether the Target block is present. Only access Target registers when target mode is supported and reject target registration requests with -EOPNOTSUPP otherwise. Fixes: 90311787f483 ("i2c: imx-lpi2c: reset controller in probe stage") Signed-off-by: Carlos Song Reviewed-by: Frank Li Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260803032705.2250373-1-carlos.song@oss.nxp.com --- drivers/i2c/busses/i2c-imx-lpi2c.c | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/drivers/i2c/busses/i2c-imx-lpi2c.c b/drivers/i2c/busses/i2c-imx-lpi2c.c index e1a4338bc51e..1cfd7a4c8237 100644 --- a/drivers/i2c/busses/i2c-imx-lpi2c.c +++ b/drivers/i2c/busses/i2c-imx-lpi2c.c @@ -29,6 +29,7 @@ #define DRIVER_NAME "imx-lpi2c" +#define LPI2C_VERID 0x00 /* i2c version ID */ #define LPI2C_PARAM 0x04 /* i2c RX/TX FIFO size */ #define LPI2C_MCR 0x10 /* i2c contrl register */ #define LPI2C_MSR 0x14 /* i2c status register */ @@ -136,6 +137,9 @@ #define I2C_PM_LONG_TIMEOUT_MS 1000 /* Avoid dead lock caused by big clock prepare lock */ #define I2C_DMA_THRESHOLD 8 /* bytes */ +/* Bit 0 indicates the presence of the target feature */ +#define VERID_FEATURE_TARGET_PRESENT BIT(0) + enum lpi2c_imx_mode { STANDARD, /* 100+Kbps */ FAST, /* 400+Kbps */ @@ -194,6 +198,7 @@ struct lpi2c_imx_struct { bool can_use_dma; struct lpi2c_imx_dma *dma; struct i2c_client *target; + bool target_supported; int irq; const struct imx_lpi2c_hwdata *hwdata; }; @@ -1330,6 +1335,10 @@ static int lpi2c_imx_register_target(struct i2c_client *client) struct lpi2c_imx_struct *lpi2c_imx = i2c_get_adapdata(client->adapter); int ret; + /* Reject target-mode registration on controllers that don't support it. */ + if (!lpi2c_imx->target_supported) + return -EOPNOTSUPP; + if (lpi2c_imx->target) return -EBUSY; @@ -1546,13 +1555,23 @@ static int lpi2c_imx_probe(struct platform_device *pdev) pm_runtime_enable(&pdev->dev); /* - * Reset all internal controller registers of both Master and Target - * to avoid effects of previous status. + * Reset all internal controller registers to avoid effects of any + * state left over from a previous stage (e.g. the bootloader). + * + * The Master block (MCR) is present on every controller, so reset it + * unconditionally. VERID shows whether the target feature is supported. + * Do not touch the Target block (SCR) on a master-only controller to + * avoid an asynchronous SError. */ writel(MCR_RST, lpi2c_imx->base + LPI2C_MCR); - writel(SCR_RST, lpi2c_imx->base + LPI2C_SCR); writel(0, lpi2c_imx->base + LPI2C_MCR); - writel(0, lpi2c_imx->base + LPI2C_SCR); + + lpi2c_imx->target_supported = !!(readl(lpi2c_imx->base + LPI2C_VERID) & + VERID_FEATURE_TARGET_PRESENT); + if (lpi2c_imx->target_supported) { + writel(SCR_RST, lpi2c_imx->base + LPI2C_SCR); + writel(0, lpi2c_imx->base + LPI2C_SCR); + } ret = devm_request_irq(&pdev->dev, lpi2c_imx->irq, lpi2c_imx_isr, IRQF_NO_SUSPEND, pdev->name, lpi2c_imx); From b15b548d52b43ba8ac4652bc2c7244a8dd1e9622 Mon Sep 17 00:00:00 2001 From: Vasileios Almpanis Date: Wed, 12 Aug 2026 11:14:48 +0200 Subject: [PATCH 1317/1328] i2c: core: fix debugfs UAF on adapter removal i2c_del_adapter() frees the adapter's debugfs directory before it unregisters the adapter device, but the new_device sysfs attribute stays writable until device_del(). A write racing with removal still reaches i2c_device_probe(), which passes the freed adap->debugfs to debugfs_create_dir() as the new client's parent: BUG: KASAN: slab-use-after-free in lookup_noperm_common+0x407/0x430 Read of size 4 at addr ffff88803ef87810 by task syz.0.61/6090 lookup_noperm_common+0x407/0x430 simple_start_creating+0x9c/0x110 debugfs_start_creating+0xdb/0x1a0 debugfs_create_dir+0x24/0x350 i2c_device_probe+0x814/0xbf0 It's technically possible to create a client after i2c_deregister_clients has run. That client will never be unregistered and make wait_for_completion hang. Close the window by removing the new_device attribute at the start of i2c_del_adapter(). device_remove_file() will drain any clients left. Fixes: 73febd775bdb ("i2c: create debugfs entry per adapter") Reported-by: syzbot+23ad911c819b923238b7@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=23ad911c819b923238b7 Signed-off-by: Vasileios Almpanis Cc: # v6.8+ Tested-by: syzbot+23ad911c819b923238b7@syzkaller.appspotmail.com Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260812-i2c-v2-1-5efaab4c3334@gmail.com --- drivers/i2c/i2c-core-base.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c index fb25704219c7..ddaacf876dad 100644 --- a/drivers/i2c/i2c-core-base.c +++ b/drivers/i2c/i2c-core-base.c @@ -1816,6 +1816,12 @@ void i2c_del_adapter(struct i2c_adapter *adap) return; } + /* + * This drains any in-flight writers, so all + * clients will be caught by i2c_deregister_clients(). + */ + device_remove_file(&adap->dev, &dev_attr_new_device); + i2c_acpi_remove_space_handler(adap); i2c_deregister_clients(adap); From 390f6bd8583d177029d9df4bea6667509e55a765 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Jean?= Date: Thu, 27 Aug 2026 18:43:22 +0000 Subject: [PATCH 1318/1328] tracing/user_events: Clear copied tracing state before fork duplication MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dup_task_struct() copies user_event_mm from the parent into the child, without grabbing a reference to it. user_event_mm_dup() should replace it, but it leaves that copied pointer unmodified if user_event_mm_alloc() fails. When the child exits, user_event_mm_remove() decrements a reference the child never owned, which ultimately frees user_event_mm, while the parent still as a stale pointer to it. This creates a UAF, which KASAN reports as: BUG: KASAN: slab-use-after-free in current_user_event_mm+0x51/0x1d0 Write of size 4 at addr ffff888005010d30 by task init/44 Call Trace: kasan_report+0xce/0x100 kasan_check_range+0x10f/0x1e0 current_user_event_mm+0x51/0x1d0 user_events_ioctl+0x82e/0x15c0 __x64_sys_ioctl+0x139/0x1c0 do_syscall_64+0xce/0x450 entry_SYSCALL_64_after_hwframe+0x77/0x7f Allocated by task 44: __kasan_kmalloc+0x8f/0xa0 __kmalloc_cache_noprof+0x180/0x3a0 user_event_mm_alloc+0x3c/0x1f0 current_user_event_mm+0x88/0x1d0 Freed by task 42: __kasan_slab_free+0x43/0x70 kfree+0x13a/0x390 process_one_work+0x696/0xf90 worker_thread+0x420/0xba0 The fix simply clears the copied pointer before any possible failure. In case of failure, the child then has nothing to free. Cc: stable@vger.kernel.org Fixes: 7235759084a4 ("tracing/user_events: Use remote writes for event enablement") Link: https://patch.msgid.link/20260827184321.2964601-2-Jeremy.Jean@oss.cyber.gouv.fr Assisted-by: Codex:gpt-5 Signed-off-by: Jérémy Jean Reviewed-by: Bradley Morgan Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_user.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kernel/trace/trace_events_user.c b/kernel/trace/trace_events_user.c index 2bbc89d4a266..93cda2f6f269 100644 --- a/kernel/trace/trace_events_user.c +++ b/kernel/trace/trace_events_user.c @@ -868,6 +868,9 @@ void user_event_mm_dup(struct task_struct *t, struct user_event_mm *old_mm) struct user_event_mm *mm = user_event_mm_alloc(t); struct user_event_enabler *enabler; + /* On failure, do not free parent's copy */ + t->user_event_mm = NULL; + if (!mm) return; From e0d3aed7b12cf37b74c7cc5265073d0263b49cde Mon Sep 17 00:00:00 2001 From: Ivan Immanuel Shaji Date: Tue, 25 Aug 2026 12:52:49 -0400 Subject: [PATCH 1319/1328] tracing: Fix retry exhaustion in simple ring buffer reader swap simple_ring_buffer_swap_reader_page() starts with retry set to 8 and post-decrements it only after a failed link replacement. On the final attempt, a successful replacement leaves retry at zero, while a failed replacement leaves it at -1. The current !retry test reverses both outcomes. It returns an error after a successful final replacement, leaving the link update complete but the reader bookkeeping unfinished. After a failed final replacement, it falls through and updates the head and reader pointers as though the replacement succeeded, which can corrupt the ring. Treat only a negative counter as exhaustion and return the documented -EBUSY error. Cc: stable@vger.kernel.org Fixes: 34e5b958bdad ("tracing: Introduce simple_ring_buffer") Link: https://patch.msgid.link/20260825-kernel-patch-1-v2-1-bb3461807a32@gmail.com Assisted-by: LLM sparse Reviewed-by: Vincent Donnefort Signed-off-by: Ivan Immanuel Shaji Signed-off-by: Steven Rostedt --- kernel/trace/simple_ring_buffer.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/trace/simple_ring_buffer.c b/kernel/trace/simple_ring_buffer.c index f4642f5adda3..49913bb0057a 100644 --- a/kernel/trace/simple_ring_buffer.c +++ b/kernel/trace/simple_ring_buffer.c @@ -160,8 +160,8 @@ int simple_ring_buffer_swap_reader_page(struct simple_rb_per_cpu *cpu_buffer) overrun = cpu_buffer->meta->overrun; } while (!simple_bpage_unset_head_link(last, reader, SIMPLE_RB_LINK_NORMAL) && retry--); - if (!retry) - return -EINVAL; + if (retry < 0) + return -EBUSY; cpu_buffer->head_page = simple_bpage_from_link(reader->link.next); cpu_buffer->head_page->link.prev = &reader->link; From 5eab74874d11160725c42ab676ba97a797a362eb Mon Sep 17 00:00:00 2001 From: Ivan Immanuel Shaji Date: Tue, 25 Aug 2026 12:52:50 -0400 Subject: [PATCH 1320/1328] ring-buffer: Stop remote reader update when page swap fails The remote swap_reader_page callback can return -EBUSY when the writer moves the head before the remote catches it, particularly during an event storm on a small buffer. __rb_get_reader_page_from_remote() currently warns about that failure but continues with the unchanged reader ID and rearranges the local page list as though the swap succeeded. Handle the callback failure as a recoverable error. Report it with pr_warn_ratelimited() and return NULL. Callers already handle a NULL reader page as a failed attempt. This avoids splicing the same page as both the previous and new reader without flooding the log under contention. Cc: stable@vger.kernel.org Fixes: 2e67fabd8b77 ("ring-buffer: Introduce ring-buffer remotes") Link: https://patch.msgid.link/20260825-kernel-patch-1-v2-2-bb3461807a32@gmail.com Assisted-by: LLM sparse Signed-off-by: Ivan Immanuel Shaji Signed-off-by: Steven Rostedt --- kernel/trace/ring_buffer.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c index 5fc009edc1ec..b7d076b6edcf 100644 --- a/kernel/trace/ring_buffer.c +++ b/kernel/trace/ring_buffer.c @@ -5805,8 +5805,11 @@ __rb_get_reader_page_from_remote(struct ring_buffer_per_cpu *cpu_buffer) prev_reader = cpu_buffer->subbuf_ids[cpu_buffer->meta_page->reader.id]; - WARN_ON_ONCE(cpu_buffer->remote->swap_reader_page(cpu_buffer->cpu, - cpu_buffer->remote->priv)); + if (cpu_buffer->remote->swap_reader_page(cpu_buffer->cpu, + cpu_buffer->remote->priv)) { + pr_warn_ratelimited("Remote reader page swap failed\n"); + return NULL; + } /* nr_pages doesn't include the reader page */ if (WARN_ON_ONCE(cpu_buffer->meta_page->reader.id > cpu_buffer->nr_pages)) return NULL; From 78004e9a87f240df03e2f73120d291763c32e0a7 Mon Sep 17 00:00:00 2001 From: Ali Ahmet Memis Date: Fri, 21 Aug 2026 01:45:27 +0000 Subject: [PATCH 1321/1328] openrisc: fix arbitrary kernel memory access via or1k_atomic syscall sys_or1k_atomic() (syscall 244 in the "or1k" ABI) takes two user pointers, v1 and v2, and swaps the words they point to in hand-written assembly. l.lwz r29,0(r4) l.lwz r27,0(r5) l.sw 0(r4),r27 l.sw 0(r5),r29 The pointers are not checked with access_ok(). The four memory accesses also have no exception table entries. A caller passes a kernel address as either pointer, and the syscall reads from and writes to it directly. This gives an unprivileged process a kernel read/write primitive. It overwrites kernel data such as the sys_call_table, gaining code execution in kernel context. Check both pointers before entering the critical section. Add fixups for the four memory accesses so faults on valid but unmapped user addresses return -EFAULT. [shorne@gmail.com: fix comment style] Fixes: 9d02a4283e9c ("OpenRISC: Boot code") Cc: stable@vger.kernel.org Signed-off-by: Ali Ahmet Memis Signed-off-by: Stafford Horne --- arch/openrisc/kernel/entry.S | 43 ++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/arch/openrisc/kernel/entry.S b/arch/openrisc/kernel/entry.S index c7e90b09645e..18e68680471e 100644 --- a/arch/openrisc/kernel/entry.S +++ b/arch/openrisc/kernel/entry.S @@ -1223,15 +1223,50 @@ _no_syscall_trace: * */ +/* Keep this literal; hi()/lo() can't use the UL-suffixed TASK_SIZE. */ +#define OR1K_ATOMIC_ADDR_LIMIT 0x7ffffffc + ENTRY(sys_or1k_atomic) /* FIXME: This ignores r3 and always does an XCHG */ + + /* Check both user pointers before accessing them. */ + l.movhi r13,hi(OR1K_ATOMIC_ADDR_LIMIT) + l.ori r13,r13,lo(OR1K_ATOMIC_ADDR_LIMIT) + l.sfgtu r4,r13 + l.bf 9f + l.nop + l.sfgtu r5,r13 + l.bf 9f + l.nop + DISABLE_INTERRUPTS(r17,r19) - l.lwz r29,0(r4) - l.lwz r27,0(r5) - l.sw 0(r4),r27 - l.sw 0(r5),r29 +10: l.lwz r29,0(r4) +11: l.lwz r27,0(r5) +12: l.sw 0(r4),r27 +13: l.sw 0(r5),r29 ENABLE_INTERRUPTS(r17) l.jr r9 l.or r11,r0,r0 + /* + * Either pointer was outside user space, or turned out to be + * unmapped/inaccessible when we actually touched it. + */ +9: l.jr r9 + l.addi r11,r0,-EFAULT + + .section .fixup, "ax" +14: + ENABLE_INTERRUPTS(r17) + l.j 9b + l.nop + .previous + + .section __ex_table, "a" + .long 10b, 14b + .long 11b, 14b + .long 12b, 14b + .long 13b, 14b + .previous + /* ============================================================[ EOF ]=== */ From 729eb52aa17f480a14ec0e7df363deabd41e57a3 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Mon, 24 Aug 2026 01:18:16 +0900 Subject: [PATCH 1322/1328] coccinelle: pool_zalloc-simple: drop the pci_pool_alloc rules pci_pool_alloc() and pci_pool_zalloc() were removed by commit 88dee3b0efe4 ("PCI: Remove unused pci_pool wrappers"). So drop the pci_pool_alloc rules. No functional change. Signed-off-by: Sang-Heon Jeon Signed-off-by: Julia Lawall --- .../api/alloc/pool_zalloc-simple.cocci | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/scripts/coccinelle/api/alloc/pool_zalloc-simple.cocci b/scripts/coccinelle/api/alloc/pool_zalloc-simple.cocci index 9c61a23b34db..07af8c7433d1 100644 --- a/scripts/coccinelle/api/alloc/pool_zalloc-simple.cocci +++ b/scripts/coccinelle/api/alloc/pool_zalloc-simple.cocci @@ -5,7 +5,7 @@ // Copyright: (C) 2015 Intel Corp. // Options: --no-includes --include-headers // -// Keywords: dma_pool_zalloc, pci_pool_zalloc +// Keywords: dma_pool_zalloc // virtual context @@ -22,7 +22,7 @@ expression x; statement S; @@ -* x = \(dma_pool_alloc\|pci_pool_alloc\)(...); +* x = dma_pool_alloc(...); if ((x==NULL) || ...) S * memset(x,0, ...); @@ -41,17 +41,6 @@ statement S; if ((x==NULL) || ...) S - memset(x,0,...); -@depends on patch@ -expression x; -expression a,b,c; -statement S; -@@ - -- x = pci_pool_alloc(a,b,c); -+ x = pci_pool_zalloc(a,b,c); - if ((x==NULL) || ...) S -- memset(x,0,...); - //---------------------------------------------------------- // For org and report mode //---------------------------------------------------------- @@ -63,7 +52,7 @@ statement S; position p; @@ - x = @p\(dma_pool_alloc\|pci_pool_alloc\)(a,b,c); + x =@p dma_pool_alloc(a,b,c); if ((x==NULL) || ...) S memset(x,0, ...); From 3beb6e620fae118f1dc1092b08b2c82b4e762b8a Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Mon, 24 Aug 2026 01:18:17 +0900 Subject: [PATCH 1323/1328] coccinelle: kfree_mismatch: drop vmalloc_exec vmalloc_exec() was removed by commit 7a0e27b2a0ce ("mm: remove vmalloc_exec"). So drop it from the rules. No functional change. Signed-off-by: Sang-Heon Jeon Signed-off-by: Julia Lawall --- scripts/coccinelle/api/kfree_mismatch.cocci | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/coccinelle/api/kfree_mismatch.cocci b/scripts/coccinelle/api/kfree_mismatch.cocci index d46a9b3eb7b3..bc90d0c071ac 100644 --- a/scripts/coccinelle/api/kfree_mismatch.cocci +++ b/scripts/coccinelle/api/kfree_mismatch.cocci @@ -29,7 +29,7 @@ position kok, vok; } else { ... E = \(vmalloc\|vzalloc\|vmalloc_user\|vmalloc_node\| - vzalloc_node\|vmalloc_exec\|vmalloc_32\| + vzalloc_node\|vmalloc_32\| vmalloc_32_user\|__vmalloc\|__vmalloc_node_range\| __vmalloc_node\)(...)@vok ... @@ -42,7 +42,7 @@ position kok, vok; if (E == NULL) { ... E = \(vmalloc\|vzalloc\|vmalloc_user\|vmalloc_node\| - vzalloc_node\|vmalloc_exec\|vmalloc_32\| + vzalloc_node\|vmalloc_32\| vmalloc_32_user\|__vmalloc\|__vmalloc_node_range\| __vmalloc_node\)(...)@vok ... @@ -68,7 +68,7 @@ position f != free.fok; * E = \(kmalloc\|kzalloc\|krealloc\|kcalloc\|kmalloc_node\| * kzalloc_node\|kmalloc_array\|kmalloc_array_node\| * kcalloc_node\)(...)@a - ... when != if (...) { ... E = \(vmalloc\|vzalloc\|vmalloc_user\|vmalloc_node\|vzalloc_node\|vmalloc_exec\|vmalloc_32\|vmalloc_32_user\|__vmalloc\|__vmalloc_node_range\|__vmalloc_node\)(...); ... } + ... when != if (...) { ... E = \(vmalloc\|vzalloc\|vmalloc_user\|vmalloc_node\|vzalloc_node\|vmalloc_32\|vmalloc_32_user\|__vmalloc\|__vmalloc_node_range\|__vmalloc_node\)(...); ... } when != is_vmalloc_addr(E) when any * \(vfree\|vfree_atomic\|kvfree\)(E)@f @@ -82,7 +82,7 @@ position f != free.fok; E = \(kmalloc\|kzalloc\|krealloc\|kcalloc\|kmalloc_node\| kzalloc_node\|kmalloc_array\|kmalloc_array_node\| kcalloc_node\)(...)@a - ... when != if (...) { ... E = \(vmalloc\|vzalloc\|vmalloc_user\|vmalloc_node\|vzalloc_node\|vmalloc_exec\|vmalloc_32\|vmalloc_32_user\|__vmalloc\|__vmalloc_node_range\|__vmalloc_node\)(...); ... } + ... when != if (...) { ... E = \(vmalloc\|vzalloc\|vmalloc_user\|vmalloc_node\|vzalloc_node\|vmalloc_32\|vmalloc_32_user\|__vmalloc\|__vmalloc_node_range\|__vmalloc_node\)(...); ... } when != is_vmalloc_addr(E) when any - \(vfree\|vfree_atomic\|kvfree\)(E)@f @@ -95,7 +95,7 @@ position f != free.fok; @@ * E = \(vmalloc\|vzalloc\|vmalloc_user\|vmalloc_node\|vzalloc_node\| -* vmalloc_exec\|vmalloc_32\|vmalloc_32_user\|__vmalloc\| +* vmalloc_32\|vmalloc_32_user\|__vmalloc\| * __vmalloc_node_range\|__vmalloc_node\)(...)@a ... when != is_vmalloc_addr(E) when any @@ -108,7 +108,7 @@ position f != free.fok; @@ E = \(vmalloc\|vzalloc\|vmalloc_user\|vmalloc_node\|vzalloc_node\| - vmalloc_exec\|vmalloc_32\|vmalloc_32_user\|__vmalloc\| + vmalloc_32\|vmalloc_32_user\|__vmalloc\| __vmalloc_node_range\|__vmalloc_node\)(...)@a ... when != is_vmalloc_addr(E) when any From 5264281879ef19b68f61846a7455a627f10b92e8 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Mon, 24 Aug 2026 01:18:18 +0900 Subject: [PATCH 1324/1328] coccinelle: atomic_as_refcounter: drop atomic_long_dec_and_lock atomic_long_dec_and_lock() has never existed. So drop it from the rules. No functional change. Signed-off-by: Sang-Heon Jeon Signed-off-by: Julia Lawall --- scripts/coccinelle/api/atomic_as_refcounter.cocci | 4 ---- 1 file changed, 4 deletions(-) diff --git a/scripts/coccinelle/api/atomic_as_refcounter.cocci b/scripts/coccinelle/api/atomic_as_refcounter.cocci index bbe5b2932933..af82b9e094aa 100644 --- a/scripts/coccinelle/api/atomic_as_refcounter.cocci +++ b/scripts/coccinelle/api/atomic_as_refcounter.cocci @@ -26,8 +26,6 @@ identifier fname6 =~ ".*call_rcu.*"; atomic_dec_and_test@p1(&(a)->x) | atomic_dec_and_lock@p1(&(a)->x, ...) -| - atomic_long_dec_and_lock@p1(&(a)->x, ...) | atomic_long_dec_and_test@p1(&(a)->x) | @@ -69,8 +67,6 @@ identifier fname =~ ".*free.*"; atomic_dec_and_test@p1(&(a)->x) | atomic_dec_and_lock@p1(&(a)->x, ...) -| - atomic_long_dec_and_lock@p1(&(a)->x, ...) | atomic_long_dec_and_test@p1(&(a)->x) | From f83b8a58695cbf419beaa3f63b9d3e264792d8ea Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Mon, 24 Aug 2026 01:18:19 +0900 Subject: [PATCH 1325/1328] coccinelle: ifnulldev_put: update outdated helper names dev_put_track() and dev_hold_track() were renamed to netdev_put() and netdev_hold() by commit d62607c3fe45 ("net: rename reference+tracking helpers"). So update the names. Signed-off-by: Sang-Heon Jeon Signed-off-by: Julia Lawall --- scripts/coccinelle/free/ifnulldev_put.cocci | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/coccinelle/free/ifnulldev_put.cocci b/scripts/coccinelle/free/ifnulldev_put.cocci index 2bd2e8fae485..adb08252f98d 100644 --- a/scripts/coccinelle/free/ifnulldev_put.cocci +++ b/scripts/coccinelle/free/ifnulldev_put.cocci @@ -23,13 +23,13 @@ expression E; | dev_put(E); | - dev_put_track(E, ...); + netdev_put(E, ...); | __dev_hold(E); | dev_hold(E); | - dev_hold_track(E, ...); + netdev_hold(E, ...); ) @r depends on context || report || org @ @@ -38,8 +38,8 @@ position p; @@ * if (E != NULL) -* \(__dev_put@p\|dev_put@p\|dev_put_track@p\|__dev_hold@p\|dev_hold@p\| -* dev_hold_track@p\)(E, ...); +* \(__dev_put@p\|dev_put@p\|netdev_put@p\|__dev_hold@p\|dev_hold@p\| +* netdev_hold@p\)(E, ...); @script:python depends on org@ p << r.p; From ef6a1dca8de41a408019310649e6d1b7b21ac4bc Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sun, 30 Aug 2026 14:36:11 +0200 Subject: [PATCH 1326/1328] coccinelle: ifnulldev_put: update error message Update the report and org mode messages to reflect the new function names. Signed-off-by: Julia Lawall --- scripts/coccinelle/free/ifnulldev_put.cocci | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/coccinelle/free/ifnulldev_put.cocci b/scripts/coccinelle/free/ifnulldev_put.cocci index adb08252f98d..1430d68ff268 100644 --- a/scripts/coccinelle/free/ifnulldev_put.cocci +++ b/scripts/coccinelle/free/ifnulldev_put.cocci @@ -45,11 +45,11 @@ position p; p << r.p; @@ -cocci.print_main("NULL check before dev_{put, hold} functions is not needed", p) +cocci.print_main("NULL check before (net)dev_{put, hold} functions is not needed", p) @script:python depends on report@ p << r.p; @@ -msg = "WARNING: NULL check before dev_{put, hold} functions is not needed." +msg = "WARNING: NULL check before (net)dev_{put, hold} functions is not needed." coccilib.report.print_report(p[0], msg) From cbb4c6d9af7dde462f8c9261bdcd35c47a0b4054 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Fri, 28 Aug 2026 09:15:01 -0700 Subject: [PATCH 1327/1328] CREDITS/mailmap: add some info about Darrick J. Wong Add myself to CREDITS because apparently I've never done that; and update mailmap so that all my old email addresses get remapped to the kernel.org redirector. Signed-off-by: "Darrick J. Wong" Signed-off-by: Linus Torvalds --- .mailmap | 4 ++++ CREDITS | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/.mailmap b/.mailmap index eeb885837e88..6803f3bd2865 100644 --- a/.mailmap +++ b/.mailmap @@ -242,6 +242,10 @@ Daniele Alessandrelli Danila Tikhonov Danilo Krummrich +Darrick J. Wong +Darrick J. Wong +Darrick J. Wong +Darrick J. Wong David Brownell David Collins David Gow diff --git a/CREDITS b/CREDITS index f8a33916e2a5..a1455b471051 100644 --- a/CREDITS +++ b/CREDITS @@ -4469,6 +4469,13 @@ S: The Netherlands N: Thomas Woller D: CS461x Cirrus Logic sound driver +N: Darrick J. Wong +E: djwong@kernel.org +D: ext4 filesystem +D: XFS filesystem and online fsck +D: Various hwmon drivers +S: USA + N: David Woodhouse E: dwmw2@infradead.org D: JFFS2 file system, Memory Technology Device subsystem, From cee9395acd8043be0644b25c34bfa86623f2b935 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 30 Aug 2026 13:34:40 -0700 Subject: [PATCH 1328/1328] Linux 7.3-rc1 --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 7860ce1fe501..4ad67b737af7 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ # SPDX-License-Identifier: GPL-2.0 VERSION = 7 -PATCHLEVEL = 2 +PATCHLEVEL = 3 SUBLEVEL = 0 -EXTRAVERSION = +EXTRAVERSION = -rc1 NAME = Baby Opossum Posse # *DOCUMENTATION*