From 667d0fb32149f023b8b34a1f6f3d384556eafb5a Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Tue, 23 Jun 2026 12:00:22 -0700 Subject: [PATCH 001/612] driver core: add missing kernel-doc for union members MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The use of __SYSFS_FUNCTION_ALTERNATIVE() adds an anonymous union (or struct if CONFIG_CFI=y).* Describe the additional struct/union members to avoid docs build warnings. Warning: include/linux/device.h:117 struct member 'show_const' not described in 'device_attribute' Warning: include/linux/device.h:117 struct member 'store_const' not described in 'device_attribute' *: kernel-doc ignores CONFIG_ symbols in source files; it is using the first definition of __SYSFS_FUNCTION_ALTERNATIVE(), which is struct instead of union. Fixes: 434506b86a6c ("driver core: Allow the constification of device attributes") Signed-off-by: Randy Dunlap Reviewed-by: Thomas Weißschuh Link: https://patch.msgid.link/20260623190023.407781-1-rdunlap@infradead.org Signed-off-by: Danilo Krummrich --- include/linux/device.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/linux/device.h b/include/linux/device.h index 7b2baffdd2f55f..aee79fd6b32b47 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -99,7 +99,9 @@ struct device_type { * struct device_attribute - Interface for exporting device attributes. * @attr: sysfs attribute definition. * @show: Show handler. + * @show_const: Show handler (read-only). * @store: Store handler. + * @store_const: Store handler (read-only). */ struct device_attribute { struct attribute attr; From 52c7084c8fe57c259e50ff0a7d4f99ccecfc4c7a Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Thu, 11 Jun 2026 19:00:44 +0000 Subject: [PATCH 002/612] arm64: dts: qcom: glymur: fix PCIe SMMU interrupts The PCIe SMMUv3 wired interrupts are routed to GIC extended SPI INTIDs 4100, 4098 and 4096. Describe them as ESPIs with the ESPI-relative interrupt numbers instead of regular SPIs 964, 962 and 960. Assisted-by: Codex:GPT-5.5 Fixes: 41b6e8db400c ("arm64: dts: qcom: Introduce Glymur base dtsi") Signed-off-by: Bjorn Andersson Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260611-glymur-pcie-smmu-espi-v1-1-5effbaa3e1af@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- arch/arm64/boot/dts/qcom/glymur.dtsi | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/glymur.dtsi b/arch/arm64/boot/dts/qcom/glymur.dtsi index 20b49af7298e95..c7edfa8156f3af 100644 --- a/arch/arm64/boot/dts/qcom/glymur.dtsi +++ b/arch/arm64/boot/dts/qcom/glymur.dtsi @@ -6874,9 +6874,9 @@ pcie_smmu: iommu@15480000 { compatible = "arm,smmu-v3"; reg = <0x0 0x15480000 0x0 0x20000>; - interrupts = , - , - ; + interrupts = , + , + ; interrupt-names = "eventq", "cmdq-sync", "gerror"; dma-coherent; #iommu-cells = <1>; From a74a98f956ef8e075f28d58507bc5dad7f937fb4 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Thu, 11 Jun 2026 17:22:37 +0000 Subject: [PATCH 003/612] arm64: dts: qcom: glymur: fix QUP serial engine IRQs The Geni serial-engine interrupts from QUP wrapper 0 all fall in ESPI INTIDs space. While some of the i2c instances has gotten their interrupt specifiers corrected, even the other functions on the same serial-engines are wrong. Ensure that all the serial engine interrupts for QUP wrapper 0 matches the datasheet. Assisted-by: Codex:GPT-5.5 Fixes: 41b6e8db400c ("arm64: dts: qcom: Introduce Glymur base dtsi") Signed-off-by: Bjorn Andersson Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20260611-glymur-geni-irqs-v1-1-269428faeb6d@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- arch/arm64/boot/dts/qcom/glymur.dtsi | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/glymur.dtsi b/arch/arm64/boot/dts/qcom/glymur.dtsi index c7edfa8156f3af..129be417ac4245 100644 --- a/arch/arm64/boot/dts/qcom/glymur.dtsi +++ b/arch/arm64/boot/dts/qcom/glymur.dtsi @@ -1876,7 +1876,7 @@ spi0: spi@b80000 { compatible = "qcom,geni-spi"; reg = <0x0 0x00b80000 0x0 0x4000>; - interrupts = ; + interrupts = ; clocks = <&gcc GCC_QUPV3_WRAP0_S0_CLK>; clock-names = "se"; interconnects = <&clk_virt MASTER_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS @@ -1903,7 +1903,7 @@ i2c1: i2c@b84000 { compatible = "qcom,geni-i2c"; reg = <0x0 0x00b84000 0x0 0x4000>; - interrupts = ; + interrupts = ; clocks = <&gcc GCC_QUPV3_WRAP0_S1_CLK>; clock-names = "se"; interconnects = <&clk_virt MASTER_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS @@ -1930,7 +1930,7 @@ spi1: spi@b84000 { compatible = "qcom,geni-spi"; reg = <0x0 0x00b84000 0x0 0x4000>; - interrupts = ; + interrupts = ; clocks = <&gcc GCC_QUPV3_WRAP0_S1_CLK>; clock-names = "se"; interconnects = <&clk_virt MASTER_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS @@ -1957,7 +1957,7 @@ i2c2: i2c@b88000 { compatible = "qcom,geni-i2c"; reg = <0x0 0x00b88000 0x0 0x4000>; - interrupts = ; + interrupts = ; clocks = <&gcc GCC_QUPV3_WRAP0_S2_CLK>; clock-names = "se"; interconnects = <&clk_virt MASTER_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS @@ -1984,7 +1984,7 @@ spi2: spi@b88000 { compatible = "qcom,geni-spi"; reg = <0x0 0x00b88000 0x0 0x4000>; - interrupts = ; + interrupts = ; clocks = <&gcc GCC_QUPV3_WRAP0_S2_CLK>; clock-names = "se"; interconnects = <&clk_virt MASTER_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS @@ -2011,7 +2011,7 @@ uart2: serial@b88000 { compatible = "qcom,geni-uart"; reg = <0x0 0x00b88000 0x0 0x4000>; - interrupts = ; + interrupts = ; clocks = <&gcc GCC_QUPV3_WRAP0_S2_CLK>; clock-names = "se"; interconnects = <&clk_virt MASTER_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS @@ -2056,7 +2056,7 @@ spi3: spi@b8c000 { compatible = "qcom,geni-spi"; reg = <0x0 0x00b8c000 0x0 0x4000>; - interrupts = ; + interrupts = ; clocks = <&gcc GCC_QUPV3_WRAP0_S3_CLK>; clock-names = "se"; interconnects = <&clk_virt MASTER_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS @@ -2110,7 +2110,7 @@ spi4: spi@b90000 { compatible = "qcom,geni-spi"; reg = <0x0 0x00b90000 0x0 0x4000>; - interrupts = ; + interrupts = ; clocks = <&gcc GCC_QUPV3_WRAP0_S4_CLK>; clock-names = "se"; interconnects = <&clk_virt MASTER_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS @@ -2164,7 +2164,7 @@ spi5: spi@b94000 { compatible = "qcom,geni-spi"; reg = <0x0 0x00b94000 0x0 0x4000>; - interrupts = ; + interrupts = ; clocks = <&gcc GCC_QUPV3_WRAP0_S5_CLK>; clock-names = "se"; interconnects = <&clk_virt MASTER_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS @@ -2191,7 +2191,7 @@ i2c6: i2c@b98000 { compatible = "qcom,geni-i2c"; reg = <0x0 0x00b98000 0x0 0x4000>; - interrupts = ; + interrupts = ; clocks = <&gcc GCC_QUPV3_WRAP0_S6_CLK>; clock-names = "se"; interconnects = <&clk_virt MASTER_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS @@ -2218,7 +2218,7 @@ spi6: spi@b98000 { compatible = "qcom,geni-spi"; reg = <0x0 0x00b98000 0x0 0x4000>; - interrupts = ; + interrupts = ; clocks = <&gcc GCC_QUPV3_WRAP0_S6_CLK>; clock-names = "se"; interconnects = <&clk_virt MASTER_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS @@ -2245,7 +2245,7 @@ i2c7: i2c@b9c000 { compatible = "qcom,geni-i2c"; reg = <0x0 0x00b9c000 0x0 0x4000>; - interrupts = ; + interrupts = ; clocks = <&gcc GCC_QUPV3_WRAP0_S7_CLK>; clock-names = "se"; interconnects = <&clk_virt MASTER_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS @@ -2272,7 +2272,7 @@ spi7: spi@b9c000 { compatible = "qcom,geni-spi"; reg = <0x0 0x00b9c000 0x0 0x4000>; - interrupts = ; + interrupts = ; clocks = <&gcc GCC_QUPV3_WRAP0_S7_CLK>; clock-names = "se"; interconnects = <&clk_virt MASTER_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS From 4cd774c1feb3f720265c512174c5c3312eca1be2 Mon Sep 17 00:00:00 2001 From: Akhil P Oommen Date: Fri, 10 Apr 2026 02:38:51 +0530 Subject: [PATCH 004/612] arm64: dts: qcom: purwa: Fix GPU IOMMU property Purwa's GPU does not support SID 1, which is typically used for LPAC-related traffic. Remove SID 1 from the GPU node's iommus property to accurately describe the hardware. This fixes the splat below, seen with some versions of Gunyah hypervisor: Internal error: synchronous external abort: 0000000096000010 [#1] SMP CPU: 0 UID: 0 PID: 80 Comm: kworker/u33:2 Tainted: G M Tainted: [M]=MACHINE_CHECK Hardware name: Qualcomm Technologies, Inc. Purwa IoT EVK (DT) Workqueue: events_unbound deferred_probe_work_func pstate: 21400005 (nzCv daif +PAN -UAO -TCO +DIT -SSBS BTYPE=--) pc : arm_smmu_write_s2cr+0x9c/0xbc lr : arm_smmu_master_install_s2crs+0x78/0xa4 sp : ffff80008039b570 x29: ffff80008039b570 x28: 0000000000000000 x27: ffffaddd62f1ab78 x26: ffff00080a4ff280 x25: 0000000000000018 x24: ffff00080b896480 x23: ffff00080ba9b7a0 x22: ffff00080bb05160 x21: 0000000000000000 x20: 0000000000000000 x19: 0000000000000001 x18: 00000000ffffffff x17: 0000000000000000 x16: 0000000000000000 x15: ffff80008039b1d0 x14: ffff80010039b37d x13: 00746c7561662d74 x12: 0000000000000000 x11: ffff00080b7fbd98 x10: ffffffffffffffc0 x9 : ffffffffffffffff x8 : 0000000000000228 x7 : 0000000000000e87 x6 : 0000000000000000 x5 : 0000000000000000 x4 : ffff00080a4ff280 x3 : 0000000000000000 x2 : ffff800082a40c04 x1 : 0000000000000000 x0 : ffff800082a40000 Call trace: arm_smmu_write_s2cr+0x9c/0xbc (P) arm_smmu_master_install_s2crs+0x78/0xa4 arm_smmu_attach_dev+0xb0/0x1d8 __iommu_device_set_domain+0x84/0x11c __iommu_group_set_domain_internal+0x60/0x120 __iommu_attach_group+0x88/0x9c iommu_attach_device+0x6c/0xa0 msm_iommu_new.part.0+0x84/0xe4 [msm] msm_iommu_gpu_new+0x3c/0x104 [msm] adreno_iommu_create_vm+0x24/0xc8 [msm] a6xx_create_vm+0x48/0x78 [msm] msm_gpu_init+0x2d8/0x508 [msm] adreno_gpu_init+0x208/0x324 [msm] a6xx_gpu_init+0x604/0x8cc [msm] adreno_bind+0xb4/0x124 [msm] component_bind_all+0x114/0x23c msm_drm_init+0x1b0/0x1ec [msm] msm_drm_bind+0x30/0x3c [msm] try_to_bring_up_aggregate_device+0x164/0x1d0 __component_add+0xa4/0x16c component_add+0x14/0x20 msm_dp_display_probe_tail+0x4c/0xac [msm] msm_dp_auxbus_done_probe+0x14/0x20 [msm] dp_aux_ep_probe+0x4c/0xf4 [drm_dp_aux_bus] really_probe+0xbc/0x29c __driver_probe_device+0x78/0x12c driver_probe_device+0x3c/0x15c __device_attach_driver+0xb8/0x134 bus_for_each_drv+0x88/0xe8 __device_attach+0xa0/0x190 device_initial_probe+0x50/0x54 bus_probe_device+0x38/0xa4 deferred_probe_work_func+0x88/0xc0 process_one_work+0x148/0x28c worker_thread+0x2cc/0x3d4 kthread+0x12c/0x204 ret_from_fork+0x10/0x20 ---[ end trace 0000000000000000 ]--- Fixes: 1aa0b4e36436 ("arm64: dts: qcom: x1p42100: Add GPU support") Signed-off-by: Akhil P Oommen Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260410-purwa-gpu-dt-fix-v1-1-4637892156cf@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- arch/arm64/boot/dts/qcom/purwa.dtsi | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/purwa.dtsi b/arch/arm64/boot/dts/qcom/purwa.dtsi index 9ab4f26b35f298..5b17840fb62fb2 100644 --- a/arch/arm64/boot/dts/qcom/purwa.dtsi +++ b/arch/arm64/boot/dts/qcom/purwa.dtsi @@ -47,6 +47,8 @@ &gpu { compatible = "qcom,adreno-43030c00", "qcom,adreno"; + iommus = <&adreno_smmu 0 0x0>; + nvmem-cells = <&gpu_speed_bin>; nvmem-cell-names = "speed_bin"; From d291245e2fb1eba55d751f88613a41e292958a96 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 7 Apr 2026 22:15:19 +0200 Subject: [PATCH 005/612] arm64: dts: qcom: monaco: Add default GIC address cells Add missing address-cells 0 to GIC interrupt node to silence W=1 warning: monaco.dtsi:2326.4-2329.30: Warning (interrupt_map): /soc@0/pci@1c00000:interrupt-map: Missing property '#address-cells' in node /soc@0/interrupt-controller@17a00000, using 0 as fallback Value '0' is correct because: 1. GIC interrupt controller does not have children, 2. interrupt-map property (in PCI node) consists of five components and the fourth component 'parent unit address', which size is defined by '#address-cells' of the node pointed to by the interrupt-parent component, is not used (=0). Fixes: 46a7c01e7e9d ("arm64: dts: qcom: qcs8300: enable pcie0") Signed-off-by: Krzysztof Kozlowski Reviewed-by: Manivannan Sadhasivam Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260407201518.24949-2-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- arch/arm64/boot/dts/qcom/monaco.dtsi | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/boot/dts/qcom/monaco.dtsi b/arch/arm64/boot/dts/qcom/monaco.dtsi index e4c8466f941bdb..a350a8ba48fa39 100644 --- a/arch/arm64/boot/dts/qcom/monaco.dtsi +++ b/arch/arm64/boot/dts/qcom/monaco.dtsi @@ -7384,6 +7384,7 @@ interrupt-controller; #redistributor-regions = <1>; redistributor-stride = <0x0 0x20000>; + #address-cells = <0>; }; watchdog@17c10000 { From 41d237b1546af5d5cf877175930cd23163422c83 Mon Sep 17 00:00:00 2001 From: Alexander Koskovich Date: Wed, 15 Apr 2026 23:45:18 +0000 Subject: [PATCH 006/612] arm64: dts: qcom: sm8650: Fix IPA IMEM slice Downstream the IPA IMEM slice for SM8650 is described as: qcom,additional-mapping = <0x14683000 0x14683000 0x2000>; Update upstream ipa_modem_tables to reflect downstream. Signed-off-by: Alexander Koskovich Fixes: 581fc5d5ade6 ("arm64: dts: qcom: sm8650: Explicitly describe the IPA IMEM slice") Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260415-fix-8650-ipa-modem-tables-v1-1-95f8f425e416@pm.me Signed-off-by: Bjorn Andersson --- arch/arm64/boot/dts/qcom/sm8650.dtsi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/sm8650.dtsi b/arch/arm64/boot/dts/qcom/sm8650.dtsi index 160ead25ecf745..65c4a2b46d2159 100644 --- a/arch/arm64/boot/dts/qcom/sm8650.dtsi +++ b/arch/arm64/boot/dts/qcom/sm8650.dtsi @@ -7091,8 +7091,8 @@ ranges = <0 0 0x14680000 0x2c000>; - ipa_modem_tables: modem-tables@8000 { - reg = <0x8000 0x2000>; + ipa_modem_tables: modem-tables@3000 { + reg = <0x3000 0x2000>; }; }; From 96f65d01f3132a163a8c0e84aca00f2acdc75e9b Mon Sep 17 00:00:00 2001 From: Pengyu Luo Date: Mon, 20 Apr 2026 01:32:49 +0800 Subject: [PATCH 007/612] arm64: dts: qcom: sc8280xp: add several missing pdc map entries pdc 215, 256, 257 are missing, but we can find tlmm pin 103, 84, 90 are mapped to them respectively, so add the map entries from pdc to gic. These entries are reversed from .data section of qcgpio.sys Signed-off-by: Pengyu Luo Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260419173251.1180026-1-mitltlatltl@gmail.com Signed-off-by: Bjorn Andersson --- arch/arm64/boot/dts/qcom/sc8280xp.dtsi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/sc8280xp.dtsi b/arch/arm64/boot/dts/qcom/sc8280xp.dtsi index a2bd6b10e4752a..b0de9e262f299c 100644 --- a/arch/arm64/boot/dts/qcom/sc8280xp.dtsi +++ b/arch/arm64/boot/dts/qcom/sc8280xp.dtsi @@ -5356,7 +5356,7 @@ <211 699 1>, <212 705 1>, <213 450 1>, - <214 643 1>, + <214 643 2>, <216 646 5>, <221 390 5>, <226 700 3>, @@ -5379,7 +5379,7 @@ <252 798 1>, <253 765 1>, <254 763 1>, - <255 454 1>, + <255 454 3>, <258 139 1>, <259 786 2>, <261 370 2>, From 6267f93eac9aa6963797c5bc1a18de9571ccac26 Mon Sep 17 00:00:00 2001 From: Pengyu Luo Date: Mon, 20 Apr 2026 01:32:50 +0800 Subject: [PATCH 008/612] arm64: dts: qcom: sc8280xp: gaokun3: correct EC interrupt pin Unlike other sc8280xp platforms, on this platform, in dsdt, gpio 0x2c0 is not mapped to gpio107, it is gpio103, so fix it. I found this until I did a trigger track, irq is regularly triggerd every several millisecs. In the past, since here gpio107 was low forever, ec irq would keep to be triggered and polling the event every several millisecs. About how to get the map, please check openbsd driver for it https://github.com/openbsd/src/blob/master/sys/dev/acpi/qcgpio.c Signed-off-by: Pengyu Luo Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260419173251.1180026-2-mitltlatltl@gmail.com Signed-off-by: Bjorn Andersson --- arch/arm64/boot/dts/qcom/sc8280xp-huawei-gaokun3.dts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/qcom/sc8280xp-huawei-gaokun3.dts b/arch/arm64/boot/dts/qcom/sc8280xp-huawei-gaokun3.dts index f3c00be67081ac..cd575e20d90aa2 100644 --- a/arch/arm64/boot/dts/qcom/sc8280xp-huawei-gaokun3.dts +++ b/arch/arm64/boot/dts/qcom/sc8280xp-huawei-gaokun3.dts @@ -637,7 +637,7 @@ compatible = "huawei,gaokun3-ec"; reg = <0x38>; - interrupts-extended = <&tlmm 107 IRQ_TYPE_LEVEL_LOW>; + interrupts-extended = <&tlmm 103 IRQ_TYPE_LEVEL_LOW>; #address-cells = <1>; #size-cells = <0>; From 07db10de262f4150e24fd631a7a6c428f7bf80c9 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Wed, 29 Apr 2026 01:27:12 +0300 Subject: [PATCH 009/612] arm64: dts: qcom: sdm850-lenovo-yoga-c630: lower PSCI cluster idle With the default PSCI suspend value for cluster idle state Lenovo Yoga C630 isn't stable enough. For example it might reset if display device isn't probed early enough. Drop the bit 0x4000 from the PSCI suspend value to make C630 work in stable way. The bit was found by expertimenting with the cluster idle PSCI value. Most likely it results in the less deep sleep and more energy beign spent in the suspend state, but it's better than the non-stable system behaviour. Fixes: a1ade6cac5a2 ("arm64: dts: qcom: sdm845: Switch PSCI cpu idle states from PC to OSI") Signed-off-by: Dmitry Baryshkov Reviewed-by: Abel Vesa Link: https://lore.kernel.org/r/20260429-c630-fix-idle-v2-1-ac867dad6f21@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- arch/arm64/boot/dts/qcom/sdm850-lenovo-yoga-c630.dts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/sdm850-lenovo-yoga-c630.dts b/arch/arm64/boot/dts/qcom/sdm850-lenovo-yoga-c630.dts index e41200839dbeca..8cdaef5360d112 100644 --- a/arch/arm64/boot/dts/qcom/sdm850-lenovo-yoga-c630.dts +++ b/arch/arm64/boot/dts/qcom/sdm850-lenovo-yoga-c630.dts @@ -347,6 +347,10 @@ status = "okay"; }; +&cluster_sleep_0 { + arm,psci-suspend-param = <0x41008244>; +}; + &gcc { protected-clocks = , , From fadeedd7cfc5d73d33fa3d7ac54b9b27aabd09d2 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Sun, 12 Jul 2026 07:23:55 -1000 Subject: [PATCH 010/612] sched/psi: Create the psimon kthread outside of cgroup_mutex a5b98009f16d ("sched/psi: fix race between file release and pressure write") made pressure_write() hold cgroup_mutex across psi_trigger_create(), which forks the psimon kthread for the first rtpoll trigger. As kthread creation depends on the whole fork path, the commit inadvertently created a lot of unwanted locking dependencies from cgroup_mutex. sched_ext got hit by one: its enable path blocks forks and then grabs cgroup_mutex, so a pressure write racing a scheduler enable deadlocks, with every other fork piling up behind. Fix it by splitting trigger creation so that the worker is forked with cgroup_mutex dropped and the kernfs active reference left broken. The latter matters because rmdir and cgroup.pressure writes drain active references under cgroup_mutex. Publishing the trigger last keeps error reporting synchronous and preserves the of->priv lifetime rules. The trigger registered in the first stage pins the group's rtpoll machinery across the unlocked window, leaving only creation races to resolve. The catch-up poll on installation covers scheduling attempts dropped while there was no worker. v2: Retagged sched/psi (was cgroup). Fixes: a5b98009f16d ("sched/psi: fix race between file release and pressure write") Cc: stable@vger.kernel.org Cc: Edward Adam Davis Cc: Chen Ridong Reported-by: Matt Fleming Closes: https://lore.kernel.org/all/20260710100441.2653477-1-matt@readmodwrite.com/ Signed-off-by: Tejun Heo Acked-by: Johannes Weiner Tested-by: Matt Fleming Acked-by: Suren Baghdasaryan --- include/linux/psi.h | 4 ++- kernel/cgroup/cgroup.c | 23 +++++++++++++- kernel/sched/psi.c | 69 ++++++++++++++++++++++++++++++++---------- 3 files changed, 78 insertions(+), 18 deletions(-) diff --git a/include/linux/psi.h b/include/linux/psi.h index e0745873e3f26c..7966e3ac03b99b 100644 --- a/include/linux/psi.h +++ b/include/linux/psi.h @@ -25,7 +25,9 @@ void psi_memstall_leave(unsigned long *flags); int psi_show(struct seq_file *s, struct psi_group *group, enum psi_res res); struct psi_trigger *psi_trigger_create(struct psi_group *group, char *buf, enum psi_res res, struct file *file, - struct kernfs_open_file *of); + struct kernfs_open_file *of, + bool *need_rtpoll_worker); +int psi_trigger_create_rtpoll_worker(struct psi_group *group); void psi_trigger_destroy(struct psi_trigger *t); __poll_t psi_trigger_poll(void **trigger_ptr, struct file *file, diff --git a/kernel/cgroup/cgroup.c b/kernel/cgroup/cgroup.c index 38f8d9df8fbc4a..b5b461d4418b19 100644 --- a/kernel/cgroup/cgroup.c +++ b/kernel/cgroup/cgroup.c @@ -3996,6 +3996,7 @@ static ssize_t pressure_write(struct kernfs_open_file *of, char *buf, struct psi_trigger *new; struct cgroup *cgrp; struct psi_group *psi; + bool need_rtpoll_worker; ssize_t ret = 0; cgrp = cgroup_kn_lock_live(of->kn, false); @@ -4015,12 +4016,32 @@ static ssize_t pressure_write(struct kernfs_open_file *of, char *buf, } psi = cgroup_psi(cgrp); - new = psi_trigger_create(psi, buf, res, of->file, of); + new = psi_trigger_create(psi, buf, res, of->file, of, + &need_rtpoll_worker); if (IS_ERR(new)) { ret = PTR_ERR(new); goto out_unlock; } + /* + * The worker fork must run with neither cgroup_mutex nor the file's + * kernfs active reference held. The latter is broken since + * cgroup_kn_lock_live(). @of->priv may be released while unlocked, so + * recheck before publishing @new. + */ + if (need_rtpoll_worker) { + cgroup_unlock(); + ret = psi_trigger_create_rtpoll_worker(psi); + cgroup_lock(); + + if (!ret && !of->priv) + ret = -ENODEV; + if (ret) { + psi_trigger_destroy(new); + goto out_unlock; + } + } + smp_store_release(&ctx->psi.trigger, new); out_unlock: diff --git a/kernel/sched/psi.c b/kernel/sched/psi.c index d9c9d9480a45b7..565ec7b807432b 100644 --- a/kernel/sched/psi.c +++ b/kernel/sched/psi.c @@ -1292,9 +1292,44 @@ int psi_show(struct seq_file *m, struct psi_group *group, enum psi_res res) return 0; } +/* + * Create @group's rtpoll worker after psi_trigger_create() reported the need + * for one. kthread creation depends on the whole fork path and we don't want + * all of that nested inside cgroup_mutex, so the caller must drop it and any + * other lock that forks can wait behind. If two callers race, the loser stops + * its never-woken kthread. + */ +int psi_trigger_create_rtpoll_worker(struct psi_group *group) +{ + struct task_struct *task; + + task = kthread_create(psi_rtpoll_worker, group, "psimon"); + if (IS_ERR(task)) + return PTR_ERR(task); + + scoped_guard(mutex, &group->rtpoll_trigger_lock) { + if (!rcu_access_pointer(group->rtpoll_task)) { + atomic_set(&group->rtpoll_wakeup, 0); + wake_up_process(task); + rcu_assign_pointer(group->rtpoll_task, task); + + /* + * Poll once to catch up on scheduling attempts dropped + * while there was no rtpoll worker. + */ + psi_schedule_rtpoll_work(group, 1, true); + return 0; + } + } + + kthread_stop(task); + return 0; +} + struct psi_trigger *psi_trigger_create(struct psi_group *group, char *buf, enum psi_res res, struct file *file, - struct kernfs_open_file *of) + struct kernfs_open_file *of, + bool *need_rtpoll_worker) { struct psi_trigger *t; enum psi_states state; @@ -1302,6 +1337,8 @@ struct psi_trigger *psi_trigger_create(struct psi_group *group, char *buf, bool privileged; u32 window_us; + *need_rtpoll_worker = false; + if (static_branch_likely(&psi_disabled)) return ERR_PTR(-EOPNOTSUPP); @@ -1362,26 +1399,14 @@ struct psi_trigger *psi_trigger_create(struct psi_group *group, char *buf, if (privileged) { mutex_lock(&group->rtpoll_trigger_lock); - if (!rcu_access_pointer(group->rtpoll_task)) { - struct task_struct *task; - - task = kthread_create(psi_rtpoll_worker, group, "psimon"); - if (IS_ERR(task)) { - kfree(t); - mutex_unlock(&group->rtpoll_trigger_lock); - return ERR_CAST(task); - } - atomic_set(&group->rtpoll_wakeup, 0); - wake_up_process(task); - rcu_assign_pointer(group->rtpoll_task, task); - } - list_add(&t->node, &group->rtpoll_triggers); group->rtpoll_min_period = min(group->rtpoll_min_period, div_u64(t->win.size, UPDATES_PER_WINDOW)); group->rtpoll_nr_triggers[t->state]++; group->rtpoll_states |= (1 << t->state); + *need_rtpoll_worker = !rcu_access_pointer(group->rtpoll_task); + mutex_unlock(&group->rtpoll_trigger_lock); } else { mutex_lock(&group->avgs_lock); @@ -1541,6 +1566,8 @@ static ssize_t psi_write(struct file *file, const char __user *user_buf, size_t buf_size; struct seq_file *seq; struct psi_trigger *new; + bool need_rtpoll_worker; + int ret; if (static_branch_likely(&psi_disabled)) return -EOPNOTSUPP; @@ -1565,12 +1592,22 @@ static ssize_t psi_write(struct file *file, const char __user *user_buf, return -EBUSY; } - new = psi_trigger_create(&psi_system, buf, res, file, NULL); + new = psi_trigger_create(&psi_system, buf, res, file, NULL, + &need_rtpoll_worker); if (IS_ERR(new)) { mutex_unlock(&seq->lock); return PTR_ERR(new); } + if (need_rtpoll_worker) { + ret = psi_trigger_create_rtpoll_worker(&psi_system); + if (ret) { + psi_trigger_destroy(new); + mutex_unlock(&seq->lock); + return ret; + } + } + smp_store_release(&seq->private, new); mutex_unlock(&seq->lock); From 5457025fa8ca3c0d2732109513de839e3e797190 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Sun, 12 Jul 2026 07:23:55 -1000 Subject: [PATCH 011/612] sched/psi: Shut down rtpoll_timer in psi_cgroup_free() psi_schedule_rtpoll_work() is called locklessly from the scheduler hotpath and can race psi_trigger_destroy() taking down the last rtpoll trigger under rtpoll_trigger_lock: psi_schedule_rtpoll_work() psi_trigger_destroy() rcu_read_lock(); task = rcu_dereference(rtpoll_task); rcu_assign_pointer(rtpoll_task, NULL); timer_delete(&rtpoll_timer); mod_timer(&rtpoll_timer, ...); rcu_read_unlock(); synchronize_rcu(); kthread_stop(task_to_destroy); The group can then be freed with the re-armed timer still pending, and poll_timer_fn() runs on freed memory. 461daba06bdc ("psi: eliminate kthread_worker from psi trigger scheduling mechanism") deleted the timer synchronously after the synchronize_rcu(), which prevented this but raced trigger creation instead: the deletion could cancel the timer that a new trigger set armed during the grace period and, as creation also reinitialized the timer at the time, corrupt it. 8f91efd870ea ("psi: Fix race between psi_trigger_create/destroy") moved the initialization into group_init() and the deletion into the locked section, trading the creation races for the window above. Neither placement in the destruction path works. A pending timer firing while the group is alive is harmless though. poll_timer_fn() just wakes the rtpoll waitqueue and doesn't re-arm itself. Bind the timer to the group's lifetime instead and shut it down in psi_cgroup_free(). Nothing can arm it by then. timer_shutdown_sync() because the timer is never armed again. Fixes: 8f91efd870ea ("psi: Fix race between psi_trigger_create/destroy") Cc: stable@vger.kernel.org # v5.10+ Reported-by: Sashiko AI Closes: https://lore.kernel.org/all/20260711000434.36C4A1F000E9@smtp.kernel.org/ Signed-off-by: Tejun Heo Acked-by: Johannes Weiner Tested-by: Matt Fleming Acked-by: Suren Baghdasaryan --- kernel/sched/psi.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/kernel/sched/psi.c b/kernel/sched/psi.c index 565ec7b807432b..e2e825dcd088fc 100644 --- a/kernel/sched/psi.c +++ b/kernel/sched/psi.c @@ -1134,6 +1134,12 @@ void psi_cgroup_free(struct cgroup *cgroup) return; cancel_delayed_work_sync(&cgroup->psi->avgs_work); + /* + * A psi_schedule_rtpoll_work() call racing the last trigger's + * destruction may have re-armed the timer after psi_trigger_destroy() + * deleted it. Spurious firing while the group is alive is harmless. + */ + timer_shutdown_sync(&cgroup->psi->rtpoll_timer); free_percpu(cgroup->psi->pcpu); /* All triggers must be removed by now */ WARN_ONCE(cgroup->psi->rtpoll_states, "psi: trigger leak\n"); From 477869bfafea65492d23de62c1b5208147c09dd2 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 16 Jul 2026 10:43:25 -1000 Subject: [PATCH 012/612] sched_ext: Reject setting disallow from init_task outside the enable path The p->scx.disallow revert assumes the root enable path, where the switching loop reads the reverted policy right afterwards and leaves the task off SCX. The sub-scheduler disable path also reaches it when re-initializing the returned tasks on a root parent. Nothing reads the policy there: the task is enabled on root anyway and keeps running on the ext class with a silently rewritten policy. Kill the sched instead, matching the fork and non-root branches, and update the disallow documentation, which equated !fork with the load path and pointed at a stale debugfs path for nr_rejected. Fixes: 337ec00b1d9c ("sched_ext: Implement cgroup sub-sched enabling and disabling") Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi --- include/linux/sched/ext.h | 10 +++++----- kernel/sched/ext/ext.c | 3 +++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/include/linux/sched/ext.h b/include/linux/sched/ext.h index 20b2343aa344be..87e353f7e0112e 100644 --- a/include/linux/sched/ext.h +++ b/include/linux/sched/ext.h @@ -244,11 +244,11 @@ struct sched_ext_entity { * to %SCHED_EXT with -%EACCES. * * Can be set from ops.init_task() while the BPF scheduler is being - * loaded (!scx_init_task_args->fork). If set and the task's policy is - * already %SCHED_EXT, the task's policy is rejected and forcefully - * reverted to %SCHED_NORMAL. The number of such events are reported - * through /sys/kernel/debug/sched_ext::nr_rejected. Setting this flag - * during fork is not allowed. + * loaded. If set and the task's policy is already %SCHED_EXT, the + * task's policy is rejected and forcefully reverted to %SCHED_NORMAL. + * The number of such events are reported through + * /sys/kernel/sched_ext/nr_rejected. Setting this flag from any other + * ops.init_task() invocation, such as during fork, fails the scheduler. */ bool disallow; /* reject switching into SCX */ diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index e3fa7b2fac9dfc..46f135bddd460b 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -3606,6 +3606,9 @@ static int __scx_init_task(struct scx_sched *sch, struct task_struct *p, bool fo } else if (unlikely(fork)) { scx_error(sch, "ops.init_task() set task->scx.disallow for %s[%d] during fork", p->comm, p->pid); + } else if (unlikely(scx_enable_state() != SCX_ENABLING)) { + scx_error(sch, "ops.init_task() set task->scx.disallow for %s[%d] outside the enable path", + p->comm, p->pid); } else { struct rq *rq; struct rq_flags rf; From 5f8b69642d18e1f3e11996707842ac530444e959 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 16 Jul 2026 10:44:02 -1000 Subject: [PATCH 013/612] sched_ext: Take cgroup_lock() first in scx_cgroup_lock() scx_cgroup_lock() write-locks scx_cgroup_ops_rwsem and then takes cgroup_lock(), which can deadlock through kernfs: scx enable/disable cgroup rmdir cpu.weight write ------------------ ------------ ---------------- cgroup_lock() percpu_down_write(rwsem) cgroup_lock() kernfs_get_active() percpu_down_read(rwsem) kernfs_drain() The enable path waits for the rmdir to release cgroup_mutex. The rmdir, deactivating the cpu controller's files, waits in kernfs_drain() for the write's active reference. The write, in scx_group_set_weight(), waits for the rwsem behind the pending writer. Take cgroup_lock() first. The set_* paths take no cgroup locks inside the read side, so a pending write-lock then only waits for read sections that always run to completion, and no dependency from the rwsem back to cgroup_mutex remains. Fixes: a5bd6ba30b33 ("sched_ext: Use cgroup_lock/unlock() to synchronize against cgroup operations") Cc: stable@vger.kernel.org # v6.18+ Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi --- kernel/sched/ext/ext.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 46f135bddd460b..58898cd0727bf4 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -4580,20 +4580,25 @@ static struct cgroup *root_cgroup(void) return &cgrp_dfl_root.cgrp; } +/* + * cgroup_lock() must nest outside the rwsem write side: a writer waiting + * for cgroup_mutex deadlocks with cgroup teardown, which holds it while + * draining a set_* file write blocked on the rwsem behind the writer. + */ static void scx_cgroup_lock(void) { + cgroup_lock(); #ifdef CONFIG_EXT_GROUP_SCHED percpu_down_write(&scx_cgroup_ops_rwsem); #endif - cgroup_lock(); } static void scx_cgroup_unlock(void) { - cgroup_unlock(); #ifdef CONFIG_EXT_GROUP_SCHED percpu_up_write(&scx_cgroup_ops_rwsem); #endif + cgroup_unlock(); } #else /* CONFIG_EXT_GROUP_SCHED || CONFIG_EXT_SUB_SCHED */ static inline struct cgroup *root_cgroup(void) { return NULL; } From 8c13364db9c9a43ed286f3a8d0fb9477b1adc43c Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 16 Jul 2026 10:45:23 -1000 Subject: [PATCH 014/612] sched_ext: Skip sub-disable teardown for never-linked sub-schedulers A sub-scheduler enable can fail before scx_link_sched() links the sched into the hierarchy, e.g. when the parent is already being disabled, and cleanup still runs the full scx_sub_disable(). That is racy against root disable: drain_descendants() is the only ordering between a sub's disable-time task walk and root disable's all-task teardown, and an unlinked sub is invisible to it. Root's teardown can thus run between the never-linked sub's drain and its walk, exiting every task to no scheduler. The walk then trips the membership WARN and re-homes the exited tasks onto the dying hierarchy, a use-after-free. Skip the cgroup ownership reset and the task walk if @sch was never linked, indicated by the empty ->sibling as unlinking only happens later in the same function. The membership WARN remains valid: a linked sub is always waited on by an ancestor's drain. Fixes: 337ec00b1d9c ("sched_ext: Implement cgroup sub-sched enabling and disabling") Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi --- kernel/sched/ext/ext.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 58898cd0727bf4..98dd7df88db41c 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -5937,6 +5937,15 @@ static void scx_sub_disable(struct scx_sched *sch) percpu_down_write(&scx_fork_rwsem); scx_cgroup_lock(); + /* + * An enable that failed before scx_link_sched() never owned a cgroup or + * task and won't be waited on by an ancestor's drain_descendants(). + * Nothing to reparent and walking the tasks can misbehave as the task + * ownership invariant (either owned by self or parent) does not hold. + */ + if (list_empty(&sch->sibling)) + goto dump; + set_cgroup_sched(sch_cgroup(sch), parent); scx_task_iter_start(&sti, sch->cgrp); @@ -5949,8 +5958,8 @@ static void scx_sub_disable(struct scx_sched *sch) continue; /* - * By the time control reaches here, all descendant schedulers - * should already have been disabled. + * By the time control reaches here, all linked descendant + * schedulers should have been disabled. */ WARN_ON_ONCE(!scx_task_on_sched(sch, p)); @@ -6017,6 +6026,7 @@ static void scx_sub_disable(struct scx_sched *sch) } scx_task_iter_stop(&sti); +dump: scx_disable_dump(sch); scx_cgroup_unlock(); From 5cdc928598095b2c7d5f265e5f21eadd1634bfbe Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 16 Jul 2026 10:46:04 -1000 Subject: [PATCH 015/612] sched_ext: Don't enable non-ext tasks in the sub-sched task loops Root enable and scx_post_fork() enable a task only if it's on the ext class. Tasks on other classes, possible under an SCX_OPS_SWITCH_PARTIAL root, are left READY and enabled by switching_to_scx() when they switch over. The sub enable-commit pass and the sub-disable re-home loop enable unconditionally, so a fair-class READY task in the subtree becomes ENABLED while not on sched_ext. A later switch to SCHED_EXT then trips the task state validation WARN (ENABLED with the previous state not READY) and calls ops.enable() a second time. Gate scx_enable_task() on the task's class in both loops. Fixes: 337ec00b1d9c ("sched_ext: Implement cgroup sub-sched enabling and disabling") Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi --- kernel/sched/ext/ext.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 98dd7df88db41c..18183062f751c9 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -6010,15 +6010,22 @@ static void scx_sub_disable(struct scx_sched *sch) /* * $p is initialized for $parent and still attached to * @sch. Disable and exit for @sch, switch over to - * $parent, override the state to READY to account for - * $p having already been initialized, and then enable. + * $parent and override the state to READY to account + * for $p having already been initialized. */ scx_disable_and_exit_task(sch, p); scx_set_task_state(p, SCX_TASK_INIT_BEGIN); scx_set_task_state(p, SCX_TASK_INIT); scx_set_task_sched(p, parent); scx_set_task_state(p, SCX_TASK_READY); - scx_enable_task(parent, p); + + /* + * A task on a non-ext class, possible under an + * %SCX_OPS_SWITCH_PARTIAL root, stays READY and is + * enabled by switching_to_scx() if it switches over. + */ + if (p->sched_class == &ext_sched_class) + scx_enable_task(parent, p); } task_rq_unlock(rq, p, &rf); @@ -7726,10 +7733,14 @@ static void scx_sub_enable_workfn(struct kthread_work *work) /* * $p is now only initialized for @sch and READY, which - * is what we want. Assign it to @sch and enable. + * is what we want. Assign it to @sch and, if it's on + * the ext class, enable. A non-ext task, possible under + * an %SCX_OPS_SWITCH_PARTIAL root, stays READY and is + * enabled by switching_to_scx() if it switches over. */ scx_set_task_sched(p, sch); - scx_enable_task(sch, p); + if (p->sched_class == &ext_sched_class) + scx_enable_task(sch, p); p->scx.flags &= ~SCX_TASK_SUB_INIT; } From b7e53968cb8882c2d276429ea8550848a4940874 Mon Sep 17 00:00:00 2001 From: Guanghui Yang <3497809730@qq.com> Date: Sun, 12 Jul 2026 03:42:56 +0000 Subject: [PATCH 016/612] xfs: propagate errors from xfs_rtginode_load xfs_rtginode_ensure() treats every xfs_rtginode_load() error other than -ENOENT as success. This can leave the realtime group inode unset after an I/O, allocation, or corruption error. Growfs then continues as though the inode had been loaded. Only -ENOENT means that the inode needs to be created. Return all other errors to the growfs caller. Fixes: ae897e0bed0f ("xfs: support creating per-RTG files in growfs") Cc: stable@vger.kernel.org Signed-off-by: Guanghui Yang <3497809730@qq.com> Reviewed-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_rtalloc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/xfs_rtalloc.c b/fs/xfs/xfs_rtalloc.c index 7a3f97686989df..84efe5a8fb11d8 100644 --- a/fs/xfs/xfs_rtalloc.c +++ b/fs/xfs/xfs_rtalloc.c @@ -737,7 +737,7 @@ xfs_rtginode_ensure( xfs_trans_cancel(tp); if (error != -ENOENT) - return 0; + return error; return xfs_rtginode_create(rtg, type, true); } From cf616096a0f3a2b60f7d68b6b39674a6867ded9c Mon Sep 17 00:00:00 2001 From: Anna Schumaker Date: Tue, 30 Jun 2026 14:31:00 -0400 Subject: [PATCH 017/612] NFS: Pin the 'struct nfs_server' during a FREE_STATEID call Dan Aloni reports that he was able to hit a use-after-free bug if a FREE_STATEID operation gets delayed for whatever reason. Fix this by bumping the refcount of the 'struct nfs_server' object for the duration of the FREE_STATEID so it doesn't get cleaned up from underneath us while operations are still in flight. Reported-by: Dan Aloni Fixes: 7c1d5fae4a87 ("NFSv4: Convert nfs41_free_stateid to use an asynchronous RPC call") Tested-by: Dan Aloni Signed-off-by: Anna Schumaker --- fs/nfs/nfs4proc.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c index 1360409d8de96d..71b6ab863b3954 100644 --- a/fs/nfs/nfs4proc.c +++ b/fs/nfs/nfs4proc.c @@ -10364,6 +10364,7 @@ static void nfs41_free_stateid_release(void *calldata) struct nfs_free_stateid_data *data = calldata; struct nfs_client *clp = data->server->nfs_client; + nfs_sb_deactive(data->server->super); nfs_put_client(clp); kfree(calldata); } @@ -10405,6 +10406,10 @@ static int nfs41_free_stateid(struct nfs_server *server, if (!refcount_inc_not_zero(&clp->cl_count)) return -EIO; + if (!nfs_sb_active(server->super)) { + nfs_put_client(clp); + return -EIO; + } nfs4_state_protect(clp, NFS_SP4_MACH_CRED_STATEID, &task_setup.rpc_client, &msg); From 4aeb63d5ac2dba2a474e7b64d60776d9dd1c6cd2 Mon Sep 17 00:00:00 2001 From: Anna Schumaker Date: Tue, 30 Jun 2026 15:39:45 -0400 Subject: [PATCH 018/612] NFS: Decrement refcounts if allocating nfs_free_stateid_data fails I noticed that we were immediately exiting this function if the allocation fails, leaving the client and server object refcounts bumped. Fix this by creating a common exit point to clean up dangling references. Fixes: 576acc259146 ("nfs4: take a reference on the nfs_client when running FREE_STATEID") Signed-off-by: Anna Schumaker --- fs/nfs/nfs4proc.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c index 71b6ab863b3954..7d98e9a98580eb 100644 --- a/fs/nfs/nfs4proc.c +++ b/fs/nfs/nfs4proc.c @@ -10403,21 +10403,22 @@ static int nfs41_free_stateid(struct nfs_server *server, struct nfs_free_stateid_data *data; struct rpc_task *task; struct nfs_client *clp = server->nfs_client; + int ret = -EIO; if (!refcount_inc_not_zero(&clp->cl_count)) - return -EIO; - if (!nfs_sb_active(server->super)) { - nfs_put_client(clp); - return -EIO; - } + return ret; + if (!nfs_sb_active(server->super)) + goto out_put_clp; nfs4_state_protect(clp, NFS_SP4_MACH_CRED_STATEID, &task_setup.rpc_client, &msg); dprintk("NFS call free_stateid %p\n", stateid); data = kmalloc_obj(*data); - if (!data) - return -ENOMEM; + if (!data) { + ret = -ENOMEM; + goto out_put_server; + } data->server = server; nfs4_stateid_copy(&data->args.stateid, stateid); @@ -10433,6 +10434,11 @@ static int nfs41_free_stateid(struct nfs_server *server, rpc_put_task(task); stateid->type = NFS4_FREED_STATEID_TYPE; return 0; +out_put_server: + nfs_sb_deactive(server->super); +out_put_clp: + nfs_put_client(clp); + return ret; } static void From 75952cfc7752c52a2b692b59d34ce160d3edabb2 Mon Sep 17 00:00:00 2001 From: Daniel Drake Date: Thu, 16 Jul 2026 21:45:27 +0100 Subject: [PATCH 019/612] arm64: dts: broadcom: bcm2712: Remove non-functional EL2 virtual timer Commit d87773de9efe1 ("clocksource/drivers/arm_arch_timer: Default to EL2 virtual timer when running VHE") causes boot to hang on Raspberry Pi 5. The newly-selected EL2 virtual timer does not generate any interrupts, even though the GIC_DIST_ENABLE_SET flag has been confirmed set via readback. It is highly unusual that this timer interrupt is non-operational because this is a standard GIC interrupt corresponding to a standard Cortex-A76 CPU timer. However, Broadcom have confirmed for this SoC: > the interrupt line was never connected in the first place as this was > not identified as being a requirement Remove the corresponding DeviceTree entry. Reported-by: Marek Szyprowski Closes: https://lore.kernel.org/all/ea15cce1-b393-43f6-8d58-3d6f90f0c0cd@samsung.com/ Fixes: faa3381267d0 ("arm64: dts: broadcom: Add minimal support for Raspberry Pi 5") Signed-off-by: Daniel Drake Link: https://lore.kernel.org/r/20260716-bcm2712-el2-v2-1-e708f7fb42fa@reactivated.net Signed-off-by: Florian Fainelli --- arch/arm64/boot/dts/broadcom/bcm2712.dtsi | 2 -- 1 file changed, 2 deletions(-) diff --git a/arch/arm64/boot/dts/broadcom/bcm2712.dtsi b/arch/arm64/boot/dts/broadcom/bcm2712.dtsi index 761c59d90ffc95..09ff5e9959d3a6 100644 --- a/arch/arm64/boot/dts/broadcom/bcm2712.dtsi +++ b/arch/arm64/boot/dts/broadcom/bcm2712.dtsi @@ -678,8 +678,6 @@ , , - ; }; From 1acef6d85bfd98bd9dfe1f08bffa397a4dda8a6f Mon Sep 17 00:00:00 2001 From: Karthikeyan KS Date: Fri, 12 Jun 2026 19:07:43 +0000 Subject: [PATCH 020/612] soc: aspeed: lpc-snoop: Fix usercopy overflow in snoop_file_read put_fifo_with_discard() acts as both producer and consumer on the kfifo: it calls kfifo_skip() (advances out) and kfifo_put() (advances in) from the IRQ handler without synchronizing with snoop_file_read(), which also consumes via kfifo_to_user(). On SMP systems this concurrent access can leave (in - out) larger than the ring buffer, so __kfifo_to_user()'s clamp to (in - out) is ineffective and kfifo_copy_to_user() can attempt a copy_to_user() past the kmalloc-2k backing store: usercopy: Kernel memory exposure attempt detected from SLUB object 'kmalloc-2k' (offset 0, size 2049)! kernel BUG at mm/usercopy.c! Call trace: usercopy_abort __check_heap_object __check_object_size kfifo_copy_to_user __kfifo_to_user snoop_file_read vfs_read Serialize kfifo access with a per-channel spinlock shared between the IRQ handler (producer) and the file reader (consumer). Annotate @fifo with __guarded_by(&lock) and opt the driver into context analysis so the compiler enforces that all fifo access holds the lock. Fixes: 3772e5da4454 ("drivers/misc: Aspeed LPC snoop output using misc chardev") Signed-off-by: Karthikeyan KS Signed-off-by: Andrew Jeffery --- drivers/soc/aspeed/Makefile | 1 + drivers/soc/aspeed/aspeed-lpc-snoop.c | 38 ++++++++++++++++++--------- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/drivers/soc/aspeed/Makefile b/drivers/soc/aspeed/Makefile index b35d745929646f..b5188dcde37a71 100644 --- a/drivers/soc/aspeed/Makefile +++ b/drivers/soc/aspeed/Makefile @@ -4,3 +4,4 @@ obj-$(CONFIG_ASPEED_LPC_SNOOP) += aspeed-lpc-snoop.o obj-$(CONFIG_ASPEED_UART_ROUTING) += aspeed-uart-routing.o obj-$(CONFIG_ASPEED_P2A_CTRL) += aspeed-p2a-ctrl.o obj-$(CONFIG_ASPEED_SOCINFO) += aspeed-socinfo.o +CONTEXT_ANALYSIS_aspeed-lpc-snoop.o := y diff --git a/drivers/soc/aspeed/aspeed-lpc-snoop.c b/drivers/soc/aspeed/aspeed-lpc-snoop.c index 28e491fffc7d71..b09dca34099fe5 100644 --- a/drivers/soc/aspeed/aspeed-lpc-snoop.c +++ b/drivers/soc/aspeed/aspeed-lpc-snoop.c @@ -11,6 +11,7 @@ */ #include +#include #include #include #include @@ -74,7 +75,8 @@ struct aspeed_lpc_snoop_channel_cfg { struct aspeed_lpc_snoop_channel { const struct aspeed_lpc_snoop_channel_cfg *cfg; bool enabled; - struct kfifo fifo; + spinlock_t lock; + struct kfifo fifo __guarded_by(&lock); wait_queue_head_t wq; struct miscdevice miscdev; }; @@ -114,6 +116,7 @@ static ssize_t snoop_file_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) { struct aspeed_lpc_snoop_channel *chan = snoop_file_to_chan(file); + u8 *buf __free(kfree) = NULL; unsigned int copied; int ret = 0; @@ -125,9 +128,16 @@ static ssize_t snoop_file_read(struct file *file, char __user *buffer, if (ret == -ERESTARTSYS) return -EINTR; } - ret = kfifo_to_user(&chan->fifo, buffer, count, &copied); - if (ret) - return ret; + + count = min_t(size_t, count, SNOOP_FIFO_SIZE); + + buf = kmalloc(count, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + copied = kfifo_out_spinlocked(&chan->fifo, buf, count, &chan->lock); + if (copied && copy_to_user(buffer, buf, copied)) + return -EFAULT; return copied; } @@ -151,11 +161,13 @@ static const struct file_operations snoop_fops = { /* Save a byte to a FIFO and discard the oldest byte if FIFO is full */ static void put_fifo_with_discard(struct aspeed_lpc_snoop_channel *chan, u8 val) { - if (!kfifo_initialized(&chan->fifo)) - return; - if (kfifo_is_full(&chan->fifo)) - kfifo_skip(&chan->fifo); - kfifo_put(&chan->fifo, val); + scoped_guard(spinlock, &chan->lock) { + if (!kfifo_initialized(&chan->fifo)) + return; + if (kfifo_is_full(&chan->fifo)) + kfifo_skip(&chan->fifo); + kfifo_put(&chan->fifo, val); + } wake_up_interruptible(&chan->wq); } @@ -239,9 +251,11 @@ static int aspeed_lpc_enable_snoop(struct device *dev, if (!channel->miscdev.name) return -ENOMEM; - rc = kfifo_alloc(&channel->fifo, SNOOP_FIFO_SIZE, GFP_KERNEL); - if (rc) - return rc; + scoped_guard(spinlock_init, &channel->lock) { + rc = kfifo_alloc(&channel->fifo, SNOOP_FIFO_SIZE, GFP_KERNEL); + if (rc) + return rc; + } rc = misc_register(&channel->miscdev); if (rc) From 87b2a82e135ba81e49d82b59e3d72468cb66ea98 Mon Sep 17 00:00:00 2001 From: Qiang Ma Date: Thu, 16 Jul 2026 17:22:45 +0800 Subject: [PATCH 021/612] xfs: clear zapped attr fork state when bmap repair finds no attr fork xfstests xfs/377 can make xfs_scrub repeatedly check and repair the attr block map after inode repair zaps an attr fork. When inode repair zaps an attr fork, it records XFS_SICK_INO_BMBTA_ZAPPED so that scrub/repair can revisit the attr fork block map. If the fork has been reset to an empty state and removed, BMBTA repair has no attr fork mappings to rebuild and can return success. The post-repair scrub then runs with XREP_ALREADY_FIXED set, which means xchk_file_looks_zapped() deliberately ignores the stale zapped health bit and asks xchk_bmap() to check the current attr fork. For an absent attr fork, xchk_bmap() returns -ENOENT. Returning that error prevents xchk_bmap_attr() from marking XFS_SICK_INO_BMBTA_ZAPPED healthy, leaving the zapped health state behind even though there are no attr fork mappings left to check. Treat -ENOENT during post-repair BMBTA revalidation as a clean result for the zapped attr fork: clear XFS_SICK_INO_BMBTA_ZAPPED and return success. Keep the existing -ENOENT behavior for ordinary scrubs of absent attr forks. Fixes: d9041681dd2f ("xfs: set inode sick state flags when we zap either ondisk fork") Cc: stable@vger.kernel.org # v6.8 Reviewed-by: "Darrick J. Wong" Signed-off-by: Qiang Ma Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/bmap.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fs/xfs/scrub/bmap.c b/fs/xfs/scrub/bmap.c index 70028da1aacc77..401c278725d216 100644 --- a/fs/xfs/scrub/bmap.c +++ b/fs/xfs/scrub/bmap.c @@ -1170,6 +1170,11 @@ xchk_bmap_attr( } error = xchk_bmap(sc, XFS_ATTR_FORK); + /* A repaired, empty attr fork no longer has mappings to check. */ + if (error == -ENOENT && (sc->flags & XREP_ALREADY_FIXED)) { + xchk_mark_healthy_if_clean(sc, XFS_SICK_INO_BMBTA_ZAPPED); + return 0; + } if (error) return error; From cc3144da377de5fb422d44a2311f978623f7c900 Mon Sep 17 00:00:00 2001 From: Xiang Mei Date: Tue, 14 Jul 2026 14:56:12 -0700 Subject: [PATCH 022/612] xfs: fix off-by-one in rtrefcount btree root level validation xfs_rtrefcountbt_compute_maxlevels() sets mp->m_rtrefc_maxlevels = min(d_maxlevels, r_maxlevels) + 1; where the trailing "+ 1" already accounts for the inode-root level, so the deepest valid on-disk root level is m_rtrefc_maxlevels - 1 and a cursor must satisfy bc_nlevels <= bc_maxlevels (= m_rtrefc_maxlevels). The two on-disk validation paths, xfs_rtrefcountbt_verify() and xfs_iformat_rtrefcount(), check the root level with ">" instead of ">=", so a crafted rtreflink (metadir + realtime + reflink) image whose /rtgroups/N.refcount inode has bb_level == m_rtrefc_maxlevels is accepted on mount. xfs_rtrefcountbt_init_cursor() then sets bc_nlevels = bb_level + 1, exceeding bc_maxlevels by one. Since the xfs_rtrefcountbt_cur slab object is sized for exactly bc_maxlevels entries, the first btree op on such a cursor indexes bc_levels[m_rtrefc_maxlevels] past the end of the object. This is reached by the first rtrefcount cursor built after mount, via log/CoW recovery (xfs_reflink_recover_cow() during xfs_mountfs()) or an FS_IOC_GETFSMAP over the realtime device. Reject a root level equal to m_rtrefc_maxlevels, matching the ">=" form already used by the sibling data-device refcount/rmap verifiers and the in-memory rtrmap verifier. BUG: KASAN: slab-out-of-bounds in xfs_btree_lookup (fs/xfs/libxfs/xfs_btree.c:2101) Write of size 2 at addr ffff888018391658 by task exploit/144 xfs_btree_lookup (fs/xfs/libxfs/xfs_btree.c:2101) xfs_btree_query_range (fs/xfs/libxfs/xfs_btree.c:5308) xfs_refcount_recover_cow_leftovers (fs/xfs/libxfs/xfs_refcount.c:2113) xfs_reflink_recover_cow (fs/xfs/xfs_reflink.c:1085) xlog_recover_finish (fs/xfs/xfs_log_recover.c:3551) xfs_mountfs (fs/xfs/xfs_mount.c:1158) xfs_fs_fill_super (fs/xfs/xfs_super.c:1940) get_tree_bdev_flags (fs/super.c:1634) vfs_get_tree (fs/super.c:1694) path_mount (fs/namespace.c:4161) __x64_sys_mount (fs/namespace.c:4367) entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121) The buggy address belongs to the cache xfs_rtrefcountbt_cur of size 216 The buggy address is located 8 bytes to the right of allocated 216-byte region [ffff888018391578, ffff888018391650) Kernel panic - not syncing: Fatal exception Cc: stable@vger.kernel.org # v6.14 Fixes: 9abe03a0e4f978 ("xfs: introduce realtime refcount btree ondisk definitions") Reported-by: Weiming Shi Signed-off-by: Xiang Mei Reviewed-by: "Darrick J. Wong" Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_rtrefcount_btree.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/xfs/libxfs/xfs_rtrefcount_btree.c b/fs/xfs/libxfs/xfs_rtrefcount_btree.c index f27b80a199ba2e..22acc1411aacd8 100644 --- a/fs/xfs/libxfs/xfs_rtrefcount_btree.c +++ b/fs/xfs/libxfs/xfs_rtrefcount_btree.c @@ -201,7 +201,7 @@ xfs_rtrefcountbt_verify( if (fa) return fa; level = be16_to_cpu(block->bb_level); - if (level > mp->m_rtrefc_maxlevels) + if (level >= mp->m_rtrefc_maxlevels) return __this_address; return xfs_btree_fsblock_verify(bp, mp->m_rtrefc_mxr[level != 0]); @@ -651,7 +651,7 @@ xfs_iformat_rtrefcount( numrecs = be16_to_cpu(dfp->bb_numrecs); level = be16_to_cpu(dfp->bb_level); - if (level > mp->m_rtrefc_maxlevels || + if (level >= mp->m_rtrefc_maxlevels || xfs_rtrefcount_droot_space_calc(level, numrecs) > dsize) { xfs_inode_mark_sick(ip, XFS_SICK_INO_CORE); return -EFSCORRUPTED; From 813f8136a2ce1fee266d02a7df73db6e8a541604 Mon Sep 17 00:00:00 2001 From: Ibrahim Hashimov Date: Wed, 15 Jul 2026 09:17:23 +0200 Subject: [PATCH 023/612] xfs: bounds-check buffer log item's dirty bitmap xlog_recover_do_reg_buffer() replays each dirty region described by a buffer log item's bitmap into the buffer read for that item: memcpy(xfs_buf_offset(bp, (uint)bit << XFS_BLF_SHIFT), item->ri_buf[i].iov_base, nbits << XFS_BLF_SHIFT); The destination offset (bit/nbits, from the logged dirty bitmap) and the buffer size (from the logged blf_len) are both attacker-controlled and otherwise unrelated, yet the only thing bounding the copy is an ASSERT(), which compiles away on production kernels. A crafted image logging a small blf_len together with a bitmap bit past the end of that buffer drives the memcpy() past the buffer's allocation, corrupting adjacent kernel heap during mount-time log recovery. This is reachable by anyone who can get a crafted image mounted -- the malicious-filesystem threat model XFS already guards against elsewhere. Turn the ASSERT() into a real XFS_IS_CORRUPT() check that aborts recovery of the buffer with -EFSCORRUPTED, consistent with the validate-and-fail idiom already used in xlog_recover_do_inode_buffer() and xfs_dquot_item_recover.c. xlog_recover_do_reg_buffer() therefore becomes STATIC int and its three callers propagate the error. Found and confirmed with KASAN on a CONFIG_XFS_DEBUG=n build: the crafted image trips a slab-out-of-bounds write before this change and fails recovery cleanly with -EFSCORRUPTED after it. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Ibrahim Hashimov Reviewed-by: "Darrick J. Wong" Reviewed-by: Brian Foster Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_buf_item_recover.c | 57 +++++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/fs/xfs/xfs_buf_item_recover.c b/fs/xfs/xfs_buf_item_recover.c index 02b95b89d1b597..240deb3f7827fa 100644 --- a/fs/xfs/xfs_buf_item_recover.c +++ b/fs/xfs/xfs_buf_item_recover.c @@ -461,7 +461,7 @@ xlog_recover_validate_buf_type( * given buffer. The bitmap in the buf log format structure indicates * where to place the logged data. */ -STATIC void +STATIC int xlog_recover_do_reg_buffer( struct xfs_mount *mp, struct xlog_recover_item *item, @@ -489,8 +489,24 @@ xlog_recover_do_reg_buffer( ASSERT(nbits > 0); ASSERT(item->ri_buf[i].iov_base != NULL); ASSERT(item->ri_buf[i].iov_len % XFS_BLF_CHUNK == 0); - ASSERT(BBTOB(bp->b_length) >= - ((uint)bit << XFS_BLF_SHIFT) + (nbits << XFS_BLF_SHIFT)); + /* + * The bitmap is only trustworthy to the extent that it + * describes a region that actually fits inside the buffer we + * read in based on the (attacker-controlled) blf_len. Do not + * rely on an ASSERT() for this -- it compiles away entirely on + * non-DEBUG kernels, which is exactly where this matters, so + * validate it for real and abort recovery of this buffer rather + * than copying past the end of it. + */ + if (XFS_IS_CORRUPT(mp, BBTOB(bp->b_length) < + ((uint)bit << XFS_BLF_SHIFT) + + (nbits << XFS_BLF_SHIFT))) { + xfs_alert(mp, + "Bad buffer log item dirty bitmap (bit %d, nbits %d) for %d-byte buffer at daddr 0x%llx.", + bit, nbits, BBTOB(bp->b_length), + xfs_buf_daddr(bp)); + return -EFSCORRUPTED; + } /* * The dirty regions logged in the buffer, even though @@ -544,6 +560,7 @@ xlog_recover_do_reg_buffer( ASSERT(i == item->ri_total); xlog_recover_validate_buf_type(mp, bp, buf_f, current_lsn); + return 0; } /* @@ -552,10 +569,10 @@ xlog_recover_do_reg_buffer( * (ie. USR or GRP), then just toss this buffer away; don't recover it. * Else, treat it as a regular buffer and do recovery. * - * Return false if the buffer was tossed and true if we recovered the buffer to - * indicate to the caller if the buffer needs writing. + * Return 0 if the buffer was not recovered (tossed), 1 if it was recovered and + * needs writing, or a negative errno if recovery of the buffer failed. */ -STATIC bool +STATIC int xlog_recover_do_dquot_buffer( struct xfs_mount *mp, struct xlog *log, @@ -564,6 +581,7 @@ xlog_recover_do_dquot_buffer( struct xfs_buf_log_format *buf_f) { uint type; + int error; trace_xfs_log_recover_buf_dquot_buf(log, buf_f); @@ -571,7 +589,7 @@ xlog_recover_do_dquot_buffer( * Filesystems are required to send in quota flags at mount time. */ if (!mp->m_qflags) - return false; + return 0; type = 0; if (buf_f->blf_flags & XFS_BLF_UDQUOT_BUF) @@ -584,10 +602,12 @@ xlog_recover_do_dquot_buffer( * This type of quotas was turned off, so ignore this buffer */ if (log->l_quotaoffs_flag & type) - return false; + return 0; - xlog_recover_do_reg_buffer(mp, item, bp, buf_f, NULLCOMMITLSN); - return true; + error = xlog_recover_do_reg_buffer(mp, item, bp, buf_f, NULLCOMMITLSN); + if (error) + return error; + return 1; } /* @@ -724,7 +744,9 @@ xlog_recover_do_primary_sb_buffer( xfs_rgnumber_t orig_rgcount = mp->m_sb.sb_rgcount; int error; - xlog_recover_do_reg_buffer(mp, item, bp, buf_f, current_lsn); + error = xlog_recover_do_reg_buffer(mp, item, bp, buf_f, current_lsn); + if (error) + return error; if (orig_agcount == 0) { xfs_alert(mp, "Trying to grow file system without AGs"); @@ -1081,11 +1103,11 @@ xlog_recover_buf_commit_pass2( goto out_release; } else if (buf_f->blf_flags & (XFS_BLF_UDQUOT_BUF|XFS_BLF_PDQUOT_BUF|XFS_BLF_GDQUOT_BUF)) { - bool dirty; - - dirty = xlog_recover_do_dquot_buffer(mp, log, item, bp, buf_f); - if (!dirty) + error = xlog_recover_do_dquot_buffer(mp, log, item, bp, buf_f); + if (error <= 0) goto out_release; + /* write dirty buffer */ + error = 0; } else if ((xfs_blft_from_flags(buf_f) & XFS_BLFT_SB_BUF) && xfs_buf_daddr(bp) == 0) { error = xlog_recover_do_primary_sb_buffer(mp, item, bp, buf_f, @@ -1105,7 +1127,10 @@ xlog_recover_buf_commit_pass2( xfs_buf_relse(rtsb_bp); } } else { - xlog_recover_do_reg_buffer(mp, item, bp, buf_f, current_lsn); + error = xlog_recover_do_reg_buffer(mp, item, bp, buf_f, + current_lsn); + if (error) + goto out_release; } /* From d852729c5f4f830fbe7413df032e29459b3daf83 Mon Sep 17 00:00:00 2001 From: Yun Zhou Date: Sun, 19 Jul 2026 23:11:24 +0800 Subject: [PATCH 024/612] xfs: handle NULL b_addr in xfs_buf_free When xfs_buf_alloc_backing_mem() fails, xfs_buf_free() is called with bp->b_addr still NULL. The code falls through to the folio_put path which calls virt_to_folio(NULL), dereferencing an invalid address and causing a kernel crash. Call Trace: xfs_buf_free+0x25f/0x510 xfs_buf_alloc+0xc98/0x19b0 xfs_buf_find_insert+0x55/0x14d0 xfs_buf_get_map+0x122b/0x17c0 xfbtree_init_leaf_block+0x11c/0x4a0 xfbtree_init+0x1bb/0x460 xrep_rmap_setup_scan+0x100/0x1f0 xrep_rmapbt+0x41/0xc0 Fix this by skipping folio_put() when bp->b_addr is NULL. Fixes: 5076a6040ca1 ("xfs: support in-memory buffer cache targets") Reported-by: syzbot+94c22d92f72f5a235b7d@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=94c22d92f72f5a235b7d Signed-off-by: Yun Zhou Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_buf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/xfs_buf.c b/fs/xfs/xfs_buf.c index e1465e950acc79..48d7dfd3e15f04 100644 --- a/fs/xfs/xfs_buf.c +++ b/fs/xfs/xfs_buf.c @@ -114,7 +114,7 @@ xfs_buf_free( vfree(bp->b_addr); else if (bp->b_flags & _XBF_KMEM) kfree(bp->b_addr); - else + else if (bp->b_addr) folio_put(virt_to_folio(bp->b_addr)); call_rcu(&bp->b_rcu, xfs_buf_free_callback); From 7aa67044e7d8b6eebc44b25745f16c83ccc40a94 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 20 Jul 2026 16:08:47 +0200 Subject: [PATCH 025/612] xfs: update BDI {io,ra}_pages values based on the RT device limits When using XFS with a main device on an SSD that stores metadata and a RT device to store data on a HDD, we fail to take the I/O sizes for the RT device into accounting, leading to up to 5% slower read performance when using an SSD for metadata vs storing data and metadata on the HDD. Fix this up by taking the RT settings into account at mount an restoring the old settings at unmount time, unless the BDI settings have changed from those set by XFS. Reported-by: Filip Blagojevic Signed-off-by: Christoph Hellwig Reviewed-by: Carlos Maiolino Reviewed-by: Damien Le Moal Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_mount.h | 7 ++++++ fs/xfs/xfs_super.c | 53 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/fs/xfs/xfs_mount.h b/fs/xfs/xfs_mount.h index 66a02d1b9ad7a1..216a38a354e7c2 100644 --- a/fs/xfs/xfs_mount.h +++ b/fs/xfs/xfs_mount.h @@ -349,6 +349,13 @@ typedef struct xfs_mount { /* Index of uuid record in the uuid xarray. */ unsigned int m_uuid_table_index; + + /* + * Old io_pages/ra_pages valued in the main bdev BDI, and our initial + * calculated values. + */ + unsigned long m_old_io_pages, m_initial_io_pages; + unsigned long m_old_ra_pages, m_initial_ra_pages; } xfs_mount_t; #define M_IGEO(mp) (&(mp)->m_ino_geo) diff --git a/fs/xfs/xfs_super.c b/fs/xfs/xfs_super.c index eac7f9503805fb..323aa1228b6cf2 100644 --- a/fs/xfs/xfs_super.c +++ b/fs/xfs/xfs_super.c @@ -545,6 +545,52 @@ xfs_open_devices( return error; } +/* + * When using a RT device some or all data I/O is using the RT device, but + * the BDI is inherited from the main data device. When the underlying block + * device for the RT device has larger I/O sizes, the BDI settings might be + * incorrect, which is especially bad if the main device is a SSD and the + * RT device is a HDD, as the io_opt fixup in blk_apply_bdi_limits is missing + * for this case. + * + * Update the BDI values to the max of the data and RT device to cover our + * bases. + */ +static void +xfs_update_bdi_rahead( + struct xfs_mount *mp) +{ + struct backing_dev_info *rt_bdi = + mp->m_rtdev_targp->bt_bdev->bd_disk->bdi; + struct backing_dev_info *sb_bdi = mp->m_super->s_bdi; + + mp->m_old_io_pages = sb_bdi->io_pages; + mp->m_old_ra_pages = sb_bdi->ra_pages; + + sb_bdi->io_pages = mp->m_initial_io_pages = + max(sb_bdi->io_pages, rt_bdi->io_pages); + sb_bdi->ra_pages = mp->m_initial_ra_pages = + max(sb_bdi->ra_pages, rt_bdi->ra_pages); +} + +static void +xfs_restore_bdi_rahead( + struct xfs_mount *mp) +{ + struct backing_dev_info *sb_bdi = mp->m_super->s_bdi; + + if (sb_bdi->io_pages == mp->m_initial_io_pages) + sb_bdi->io_pages = mp->m_old_io_pages; + else + xfs_info(mp, "io_pages changed from %lu to %lu, not restoring.", + mp->m_initial_io_pages, sb_bdi->io_pages); + if (sb_bdi->ra_pages == mp->m_initial_ra_pages) + sb_bdi->ra_pages = mp->m_old_ra_pages; + else + xfs_info(mp, "ra_pages changed from %lu to %lu, not restoring.", + mp->m_initial_ra_pages, sb_bdi->ra_pages); +} + /* * Setup xfs_mount buffer target pointers based on superblock */ @@ -582,6 +628,7 @@ xfs_setup_devices( mp->m_sb.sb_sectsize, mp->m_sb.sb_rblocks); if (error) return error; + xfs_update_bdi_rahead(mp); } return 0; @@ -2280,8 +2327,12 @@ static void xfs_kill_sb( struct super_block *sb) { + struct xfs_mount *mp = XFS_M(sb); + + if (mp->m_rtdev_targp && mp->m_rtdev_targp != mp->m_ddev_targp) + xfs_restore_bdi_rahead(mp); kill_block_super(sb); - xfs_mount_free(XFS_M(sb)); + xfs_mount_free(mp); } static struct file_system_type xfs_fs_type = { From 270ffcd9b0a46254fbf3079d0e3341db7d7ec0e6 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 20 Jul 2026 20:23:46 -0700 Subject: [PATCH 026/612] xfs: check cowextsize in xrep_inode_cowextsize LOLLM points out that the function that corrects cowextsize should check i_cowextsize, not i_extsize. Cc: stable@vger.kernel.org # v6.14 Fixes: a9600db96f74af ("xfs: detect and repair misaligned rtinherit directory cowextsize hints") Signed-off-by: "Darrick J. Wong" Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/inode_repair.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/inode_repair.c b/fs/xfs/scrub/inode_repair.c index 3ec41c19835116..b88427a4460cba 100644 --- a/fs/xfs/scrub/inode_repair.c +++ b/fs/xfs/scrub/inode_repair.c @@ -1960,7 +1960,7 @@ xrep_inode_cowextsize( /* Fix misaligned CoW extent size hints on a directory. */ if ((sc->ip->i_diflags & XFS_DIFLAG_RTINHERIT) && (sc->ip->i_diflags2 & XFS_DIFLAG2_COWEXTSIZE) && - sc->ip->i_extsize % sc->mp->m_sb.sb_rextsize > 0) { + xfs_extlen_to_rtxmod(sc->mp, sc->ip->i_cowextsize) > 0) { sc->ip->i_cowextsize = 0; sc->ip->i_diflags2 &= ~XFS_DIFLAG2_COWEXTSIZE; } From 61606f8846a9da8ab7d2d36ff4617134c9d6df1b Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 20 Jul 2026 20:24:02 -0700 Subject: [PATCH 027/612] xfs: fix transaction block reservation in xrep_rtbitmap LOLLM pointed out an inconsistency in the block reservation code in xrep_rtbitmap. The first is that the reservation computation is not consistent between the code that sets up the repair and the code that tries to avoid exceeding the transaction reservation once we know how big the rtbitmap really must be. As a result, the logic doesn't work. In fixing that, a second problem emerges: if we do readjust, we ask for the entire reservation all over again. We really only need the delta, so ask only for that. Fix all these problems by hoisting the computation to a trivial helper so that it gets used in both places. Cc: stable@vger.kernel.org # v6.14 Fixes: 8defee8dff2b20 ("xfs: online repair of realtime bitmaps for a realtime group") Signed-off-by: "Darrick J. Wong" Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/rtbitmap_repair.c | 46 +++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/fs/xfs/scrub/rtbitmap_repair.c b/fs/xfs/scrub/rtbitmap_repair.c index dc64902d6c25a4..442a17bf972029 100644 --- a/fs/xfs/scrub/rtbitmap_repair.c +++ b/fs/xfs/scrub/rtbitmap_repair.c @@ -36,6 +36,24 @@ /* rt bitmap content repairs */ +/* + * Reserve enough blocks to write out a completely new bitmap file, plus twice + * as many blocks as we would need if we can only allocate one block per data + * fork mapping. This should cover the preallocation of the temporary file and + * exchanging the extent mappings. + * + * We cannot use xfs_exchmaps_estimate because we have not yet constructed the + * replacement bitmap and therefore do not know how many extents it will use. + * By the time we do, we will have a dirty transaction (which we cannot drop + * because we cannot drop the rtbitmap ILOCK) and cannot ask for more + * reservation. + */ +static inline unsigned long long +xrep_rtbitmap_calc_blocks(struct xfs_mount *mp, unsigned long long blocks) +{ + return blocks + (xfs_bmbt_calc_size(mp, blocks) * 2); +} + /* Set up to repair the realtime bitmap for this group. */ int xrep_setup_rtbitmap( @@ -56,20 +74,7 @@ xrep_setup_rtbitmap( if (error) return error; - /* - * Reserve enough blocks to write out a completely new bitmap file, - * plus twice as many blocks as we would need if we can only allocate - * one block per data fork mapping. This should cover the - * preallocation of the temporary file and exchanging the extent - * mappings. - * - * We cannot use xfs_exchmaps_estimate because we have not yet - * constructed the replacement bitmap and therefore do not know how - * many extents it will use. By the time we do, we will have a dirty - * transaction (which we cannot drop because we cannot drop the - * rtbitmap ILOCK) and cannot ask for more reservation. - */ - blocks += xfs_bmbt_calc_size(mp, blocks) * 2; + blocks = xrep_rtbitmap_calc_blocks(mp, mp->m_sb.sb_rbmblocks); if (blocks > UINT_MAX) return -EOPNOTSUPP; @@ -512,7 +517,7 @@ xrep_rtbitmap( struct xchk_rtbitmap *rtb = sc->buf; struct xfs_mount *mp = sc->mp; struct xfs_group *xg = rtg_group(sc->sr.rtg); - unsigned long long blocks = 0; + unsigned long long blocks; unsigned int busy_gen; int error; @@ -532,15 +537,20 @@ xrep_rtbitmap( * figure out if we need to adjust the block reservation in the * transaction. */ - blocks = xfs_bmbt_calc_size(mp, rtb->rbmblocks); + blocks = xrep_rtbitmap_calc_blocks(mp, rtb->rbmblocks); if (blocks > UINT_MAX) return -EOPNOTSUPP; if (blocks > rtb->resblks) { - error = xfs_trans_reserve_more(sc->tp, blocks, 0); + uint64_t delta = blocks - rtb->resblks; + + if (delta > UINT_MAX) + return -EOPNOTSUPP; + + error = xfs_trans_reserve_more(sc->tp, delta, 0); if (error) return error; - rtb->resblks += blocks; + rtb->resblks += delta; } /* Fix inode core and forks. */ From 8ed78104722b57df69478b0b1608a408c1036085 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 20 Jul 2026 20:24:17 -0700 Subject: [PATCH 028/612] xfs: rtsummary scrub should treat rtbitmap corruption errors as an xref error LOLLM notes the discrepancy between the comment saying that this is an xref error and the code, which marks a (non-xref) corruption error. This appears to be a regression. Cc: stable@vger.kernel.org # v6.13 Fixes: e3088ae2dcae3c ("xfs: move RT bitmap and summary information to the rtgroup") Signed-off-by: "Darrick J. Wong" Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/rtsummary.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/rtsummary.c b/fs/xfs/scrub/rtsummary.c index 78f72a0468870e..546b335ade1354 100644 --- a/fs/xfs/scrub/rtsummary.c +++ b/fs/xfs/scrub/rtsummary.c @@ -358,7 +358,7 @@ xchk_rtsummary( * EFSCORRUPTED means the rtbitmap is corrupt, which is an xref * error since we're checking the summary file. */ - xchk_ip_set_corrupt(sc, rbmip); + xchk_ip_xref_set_corrupt(sc, rbmip); return 0; } if (error) From 49933254ba8d421ed706cbe6ed0fbc264e572cab Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 20 Jul 2026 20:24:33 -0700 Subject: [PATCH 029/612] xfs: zero i_nlink before repair puts inode on unlinked list LOLLM observes that we don't reset i_nlink if we encounter a file with no parent that isn't on the unlinked list. This causes unnecessary assertion trips on debugging kernels and an inconsistent file, so let's fix that. Cc: stable@vger.kernel.org # v6.10 Fixes: 669dfe883c8e20 ("xfs: update the unlinked list when repairing link counts") Signed-off-by: "Darrick J. Wong" Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/nlinks_repair.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/fs/xfs/scrub/nlinks_repair.c b/fs/xfs/scrub/nlinks_repair.c index fbc2ff809fc06f..09e097e1668935 100644 --- a/fs/xfs/scrub/nlinks_repair.c +++ b/fs/xfs/scrub/nlinks_repair.c @@ -232,9 +232,14 @@ xrep_nlinks_repair_inode( * unlinked list, put it on the unlinked list. */ if (total_links == 0 && !xfs_inode_on_unlinked_list(ip)) { + if (actual_nlink) + clear_nlink(VFS_I(ip)); error = xfs_iunlink(sc->tp, ip); - if (error) + if (error) { + if (actual_nlink) + set_nlink(VFS_I(ip), actual_nlink); goto out_trans; + } dirty = true; } From b28d23c51635b646784a2a62c71ba99458c07d5e Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 20 Jul 2026 20:24:48 -0700 Subject: [PATCH 030/612] xfs: mark nonzero sb_gquotino as corrupt on metadir filesystems On a metadir filesystem, the superblock gquotino field is always zero because we find the quota inodes through the metadata directory tree. A nonzero value is therefore a corruption (as pointed out by LOLLM) so mark the failure as such. Cc: stable@vger.kernel.org # v6.13 Fixes: 06b20ef09ba163 ("xfs: check pre-metadir fields correctly") Signed-off-by: "Darrick J. Wong" Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/agheader.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/agheader.c b/fs/xfs/scrub/agheader.c index 9ed053b5f06125..62ed5eaf08fbc1 100644 --- a/fs/xfs/scrub/agheader.c +++ b/fs/xfs/scrub/agheader.c @@ -266,7 +266,7 @@ xchk_superblock( xchk_block_set_corrupt(sc, bp); if (sb->sb_gquotino != cpu_to_be64(0)) - xchk_block_set_preen(sc, bp); + xchk_block_set_corrupt(sc, bp); } else { if (sb->sb_uquotino != cpu_to_be64(mp->m_sb.sb_uquotino)) xchk_block_set_preen(sc, bp); From 8eb052f48331474c2789d07b7f11165c323bd2f9 Mon Sep 17 00:00:00 2001 From: Yuho Choi Date: Sun, 24 May 2026 23:38:46 -0400 Subject: [PATCH 031/612] ARM: npcm: Fix OF node refcount leaks in SMP setup npcm7xx_smp_boot_secondary() and npcm7xx_smp_prepare_cpus() look up the GCR and SCU nodes with of_find_compatible_node(). The returned nodes are used for of_iomap(), but the node references are never released. of_iomap() does not consume the device node reference, and iounmap() only releases the MMIO mapping. Drop each node reference after the corresponding mapping attempt. Fixes: 7bffa14c9aed ("arm: npcm: add basic support for Nuvoton BMCs") Signed-off-by: Yuho Choi Reviewed-by: Avi Fishman Signed-off-by: Andrew Jeffery --- arch/arm/mach-npcm/platsmp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm/mach-npcm/platsmp.c b/arch/arm/mach-npcm/platsmp.c index 41891d3aa1247d..4c1fc9983746ce 100644 --- a/arch/arm/mach-npcm/platsmp.c +++ b/arch/arm/mach-npcm/platsmp.c @@ -32,6 +32,7 @@ static int npcm7xx_smp_boot_secondary(unsigned int cpu, goto out; } gcr_base = of_iomap(gcr_np, 0); + of_node_put(gcr_np); if (!gcr_base) { pr_err("could not iomap gcr"); ret = -ENOMEM; @@ -60,6 +61,7 @@ static void __init npcm7xx_smp_prepare_cpus(unsigned int max_cpus) return; } scu_base = of_iomap(scu_np, 0); + of_node_put(scu_np); if (!scu_base) { pr_err("could not iomap scu"); return; From 9591fcc95dddfd24298724f8eb5239e907980779 Mon Sep 17 00:00:00 2001 From: Kuba Piecuch Date: Wed, 22 Jul 2026 14:33:07 +0000 Subject: [PATCH 032/612] sched_ext: Mark waker CPU busy when selected in WAKE_SYNC case SCX's built-in idle CPU tracking is imperfect and can be out-of-sync with the actual idle state of CPUs, especially immediately after enabling SCX due to scx_idle_enable() marking all online CPUs idle. scx_select_cpu_dfl() skips marking the selected CPU as busy if the selected CPU is the waker CPU in the SCX_WAKE_SYNC case. If the waker CPU was marked idle by SCX, it will still be marked idle after CPU selection and potentially even after switching to the wakee. In the allowed_cpus selftest, this can manifest as the test failing with the following message in dmesg: allowed_cpus.bpf.c:21: CPU 0 should be marked as busy This patch explicitly marks the waker CPU as busy. With this patch, the test failure no longer reproduces. There are still some pretty unlikely races that could make the test fail (e.g. pick_task_idle() marking the selected CPU idle between selection and validation), but these can't be fixed easily. Signed-off-by: Kuba Piecuch Signed-off-by: Tejun Heo --- kernel/sched/ext/idle.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kernel/sched/ext/idle.c b/kernel/sched/ext/idle.c index 8e8c6201b7dfdc..6f93cc32b650e1 100644 --- a/kernel/sched/ext/idle.c +++ b/kernel/sched/ext/idle.c @@ -554,8 +554,10 @@ s32 scx_select_cpu_dfl(struct task_struct *p, s32 prev_cpu, u64 wake_flags, cpu_rq(cpu)->scx.local_dsq.nr == 0 && (!(flags & SCX_PICK_IDLE_IN_NODE) || (waker_node == node)) && !cpumask_empty(idle_cpumask(waker_node)->cpu)) { - if (cpumask_test_cpu(cpu, allowed)) + if (cpumask_test_cpu(cpu, allowed)) { + scx_idle_test_and_clear_cpu(cpu); goto out_unlock; + } } } From c83e79c0842ed29860648bcce5022ef0ba5001c6 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Mon, 20 Jul 2026 14:12:59 +0800 Subject: [PATCH 033/612] Input: byd - synchronize timer deletion before freeing private data byd_disconnect() uses timer_delete() before freeing the driver's private data. This does not wait for a running byd_clear_touch() callback, which dereferences the private data and its psmouse pointer. A callback racing with disconnect can therefore access the private data after it has been freed. The timer can also still be re-armed by byd_process_byte() while the disconnect is in progress. Use timer_shutdown_sync() before freeing the private data: it waits for a running callback and turns any later re-arm attempt into a no-op. Fixes: 2d5f5611dd0d ("Input: byd - enable absolute mode") Cc: stable@vger.kernel.org Signed-off-by: Linmao Li Link: https://patch.msgid.link/20260720061259.1601281-1-lilinmao@kylinos.cn Signed-off-by: Dmitry Torokhov --- drivers/input/mouse/byd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/input/mouse/byd.c b/drivers/input/mouse/byd.c index f5770a3af2f125..5fc3c629590ae3 100644 --- a/drivers/input/mouse/byd.c +++ b/drivers/input/mouse/byd.c @@ -423,7 +423,7 @@ static void byd_disconnect(struct psmouse *psmouse) struct byd_data *priv = psmouse->private; if (priv) { - timer_delete(&priv->timer); + timer_shutdown_sync(&priv->timer); kfree(psmouse->private); psmouse->private = NULL; } From ad1d940efbf13fba516821f3d00fbd34a26e289e Mon Sep 17 00:00:00 2001 From: Alexandre Hamamdjian Date: Thu, 23 Jul 2026 20:24:09 +0700 Subject: [PATCH 034/612] Input: edt-ft5x06 - ignore contacts with an out-of-range slot id The per-contact slot id is taken from the top nibble of the third report byte, so it can be any value from 0 to 15. The driver only allocates max_support_points MT slots (2 to 10 depending on the variant), so a report that carries an id at or above that count - be it a genuinely higher-numbered contact or a corrupted byte - is outside the range the input core was told about. input_mt_slot() silently ignores an ABS_MT_SLOT beyond num_slots and leaves the current slot unchanged, so the following input_mt_report_slot_state()/touchscreen_report_pos() pair is applied to whichever slot happened to be selected last, reporting the contact at the wrong position. Skip such entries instead. Signed-off-by: Alexandre Hamamdjian Link: https://patch.msgid.link/20260723-b4-ft5426-v2-1-cd2bed168051@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/edt-ft5x06.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c index d3b1177185a3b3..d6c3d033b83df0 100644 --- a/drivers/input/touchscreen/edt-ft5x06.c +++ b/drivers/input/touchscreen/edt-ft5x06.c @@ -331,6 +331,8 @@ static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id) swap(x, y); id = (buf[2] >> 4) & 0x0f; + if (id >= tsdata->max_support_points) + continue; input_mt_slot(tsdata->input, id); if (input_mt_report_slot_state(tsdata->input, MT_TOOL_FINGER, From 7d971337ebfad0b173cb46097c709db174ac3557 Mon Sep 17 00:00:00 2001 From: Kyohei Kadota Date: Thu, 23 Jul 2026 19:55:57 +0900 Subject: [PATCH 035/612] Input: xpad - add support for ZENAIM LEVERLESS Add the VID/PID for the ZENAIM LEVERLESS controller to xpad_device and the VID to xpad_table. Signed-off-by: KADOTA, Kyohei Link: https://patch.msgid.link/CAFMepckDUuOHiDDVVhUYc-UqJMeCqrWSfCuxbJ2x2sGgdDD4nw@mail.gmail.com Cc: stable@vger.kernel.org Signed-off-by: Dmitry Torokhov --- drivers/input/joystick/xpad.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c index feb8f368f834e3..2da0b7f1722aae 100644 --- a/drivers/input/joystick/xpad.c +++ b/drivers/input/joystick/xpad.c @@ -394,6 +394,7 @@ static const struct xpad_device { { 0x3285, 0x0646, "Nacon Pro Compact", 0, XTYPE_XBOXONE }, { 0x3285, 0x0662, "Nacon Revolution5 Pro", 0, XTYPE_XBOX360 }, { 0x3285, 0x0663, "Nacon Evol-X", 0, XTYPE_XBOXONE }, + { 0x3507, 0x000b, "ZENAIM LEVERLESS", 0, XTYPE_XBOX360 }, { 0x3537, 0x1004, "GameSir T4 Kaleid", 0, XTYPE_XBOX360 }, { 0x3537, 0x100f, "GameSir Nova 2 Lite", 0, XTYPE_XBOX360 }, { 0x3537, 0x1010, "GameSir G7 SE", 0, XTYPE_XBOXONE }, @@ -557,6 +558,7 @@ static const struct usb_device_id xpad_table[] = { XPAD_XBOX360_VENDOR(0x31e3), /* Wooting Keyboards */ XPAD_XBOX360_VENDOR(0x3285), /* Nacon GC-100 */ XPAD_XBOXONE_VENDOR(0x3285), /* Nacon Evol-X */ + XPAD_XBOX360_VENDOR(0x3507), /* ZENAIM Controllers */ XPAD_XBOX360_VENDOR(0x3537), /* GameSir Controllers */ XPAD_XBOXONE_VENDOR(0x3537), /* GameSir Controllers */ XPAD_XBOX360_VENDOR(0x3651), /* CRKD Controllers */ From d4a00d61a5c2c24973175ace5368d1f6acf9bb0a Mon Sep 17 00:00:00 2001 From: Kuba Piecuch Date: Thu, 23 Jul 2026 09:59:55 +0000 Subject: [PATCH 036/612] selftests/sched_ext: Handle sleeping task affinity changes in numa test When a sleeping task's affinity is changed, task_cpu(p) can be outside of p->cpus_ptr until after select_task_rq() selects a new runqueue for the task during wakeup. Thus, the task's NUMA node determined by numa_select_cpu() can be completely outside of the task's cpumask, leading to scx_pick_{idle,any}_cpu_node() failing to find an eligible CPU and returning -EBUSY. This leads to the numa.bpf.c scheduler abnormally exiting with the following message in dmesg: sched_ext: numa: invalid CPU -16 scx_bpf_cpu_node+0x120/0x190 bpf_prog_0a34b8e0f515771f_numa_select_cpu+0x108/0x14e bpf__sched_ext_ops_select_cpu+0x4f/0xb4 select_task_rq_scx+0xb0/0x210 select_task_rq+0xa0/0xd0 __try_to_wake_up+0x196/0x650 complete_all+0x76/0x100 migration_cpu_stop+0x22b/0x300 cpu_stopper_thread+0xc1/0x180 smpboot_thread_fn+0x16b/0x230 kthread+0x2d7/0x350 ret_from_fork+0x1c2/0x350 ret_from_fork_asm+0x1a/0x30 Make numa_select_cpu() robust against this case by returning @prev_cpu if no CPU could be found in the selected NUMA node _and_ we have reason to believe that the task's affinity was changed while it was sleeping. Fixes: 5ae5161820e5 ("selftests/sched_ext: Add NUMA-aware scheduler test") Signed-off-by: Kuba Piecuch Signed-off-by: Tejun Heo --- tools/testing/selftests/sched_ext/numa.bpf.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/sched_ext/numa.bpf.c b/tools/testing/selftests/sched_ext/numa.bpf.c index 78cc49a7f9a676..6b4515c28aa0b9 100644 --- a/tools/testing/selftests/sched_ext/numa.bpf.c +++ b/tools/testing/selftests/sched_ext/numa.bpf.c @@ -34,7 +34,8 @@ static bool is_cpu_idle(s32 cpu, int node) s32 BPF_STRUCT_OPS(numa_select_cpu, struct task_struct *p, s32 prev_cpu, u64 wake_flags) { - int node = __COMPAT_scx_bpf_cpu_node(scx_bpf_task_cpu(p)); + s32 task_cpu = scx_bpf_task_cpu(p); + int node = __COMPAT_scx_bpf_cpu_node(task_cpu); s32 cpu; /* @@ -48,6 +49,16 @@ s32 BPF_STRUCT_OPS(numa_select_cpu, cpu = __COMPAT_scx_bpf_pick_any_cpu_node(p->cpus_ptr, node, __COMPAT_SCX_PICK_IDLE_IN_NODE); + /* + * @task_cpu may be outside of p->cpus_ptr if @p's affinity + * changed while it was sleeping. This means it's possible for + * p->cpus_ptr to not include any CPUs from @node. + * If we failed to find a cpu in @node, check if @task_cpu + * is outside of p->cpus_ptr and just return @prev_cpu if it is. + */ + if (cpu < 0 && !bpf_cpumask_test_cpu(task_cpu, p->cpus_ptr)) + return prev_cpu; + if (is_cpu_idle(cpu, node)) scx_bpf_error("CPU %d should be marked as busy", cpu); From 645effc0984ba8cd83cdf31e5a29945cd40973e1 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Tue, 30 Jun 2026 16:16:18 -0400 Subject: [PATCH 037/612] MAINTAINERS: ARM/FREESCALE: merge Layerscape entry into i.MX entry Both platforms are maintained by the same people, and the two entries contain largely identical information. Maintaining separate entries adds unnecessary duplication. Additionally, the standalone Layerscape entry does not include the imx@lists.linux.dev mailing list, causing Layerscape patches to miss NXP's Patchwork instance. Consolidating the entries ensures patches for both platforms are routed consistently and reduces future maintenance overhead. Signed-off-by: Frank Li --- MAINTAINERS | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 15011f5752a994..31188163a85921 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2881,7 +2881,7 @@ W: http://www.armlinux.org.uk/ F: arch/arm/include/asm/hardware/dec21285.h F: arch/arm/mach-footbridge/ -ARM/FREESCALE IMX / MXC ARM ARCHITECTURE +ARM/FREESCALE IMX / MXC / LAYERSCAPE ARM ARCHITECTURE M: Frank Li M: Sascha Hauer R: Pengutronix Kernel Team @@ -2895,22 +2895,11 @@ F: Documentation/devicetree/bindings/firmware/nxp* F: arch/arm/boot/dts/nxp/ F: arch/arm64/boot/dts/freescale/ X: Documentation/devicetree/bindings/media/i2c/ -X: arch/arm64/boot/dts/freescale/fsl-* -X: arch/arm64/boot/dts/freescale/qoriq-* X: drivers/media/i2c/ N: imx N: mxs N: \bmxc[^\d] -ARM/FREESCALE LAYERSCAPE ARM ARCHITECTURE -M: Frank Li -L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) -S: Maintained -T: git git://git.kernel.org/pub/scm/linux/kernel/git/frank.li/linux.git -F: arch/arm/boot/dts/nxp/ls/ -F: arch/arm64/boot/dts/freescale/fsl-* -F: arch/arm64/boot/dts/freescale/qoriq-* - ARM/FREESCALE VYBRID ARM ARCHITECTURE M: Frank Li M: Sascha Hauer From 7d5c576cb1c86047b1fcb1aa9532e17fc5e46c1d Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Sat, 18 Jul 2026 16:40:32 +0900 Subject: [PATCH 038/612] Input: cs40l50-vibra - validate custom data from user space cs40l50_add() copies the custom data of an FF_PERIODIC/FF_CUSTOM effect straight from the ff_effect the user passed to EVIOCSFF, without requiring it to hold anything: work_data.custom_data = memdup_array_user(periodic->custom_data, periodic->custom_len, sizeof(s16)); work_data.custom_len = periodic->custom_len; The driver then reads two words out of that buffer: custom_data[0] as the waveform bank in cs40l50_effect_bank_set(), and custom_data[1] as the index within the bank in cs40l50_effect_index_set(). Neither read is covered by a length check, and custom_len is fully user controlled: - custom_len == 0 makes memdup_array_user() call memdup_user() with a length of zero, which returns ZERO_SIZE_PTR rather than an error, so custom_data[0] dereferences it. - custom_len == 1 allocates two bytes. A bank of ROM or RAM keeps effect->type out of the OWT case, and custom_data[1] is then read one word past the allocation. The bank value itself is also mishandled. It is masked with CS40L50_CUSTOM_DATA_MASK (0xffff) but stored in an s16, so a custom_data[0] of 0x8000 or above wraps to a negative value that passes the "bank_type >= CS40L50_WVFRM_BANK_NUM" test. cs40l50_effect_index_set() indexes vib->dsp.banks[] with it before the switch statement's default case gets a chance to reject it: base_index = vib->dsp.banks[effect->type].base_index; max_index = vib->dsp.banks[effect->type].max_index; Require the two words the driver reads to be present, and hold the masked bank in a u32 so the existing upper-bound test covers the whole range. The da7280 haptic driver already range checks custom_len this way. Fixes: c38fe1bb5d21 ("Input: cs40l50 - Add support for the CS40L50 haptic driver") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-8 Signed-off-by: HyeongJun An Link: https://patch.msgid.link/20260718074032.1864861-1-sammiee5311@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/misc/cs40l50-vibra.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/input/misc/cs40l50-vibra.c b/drivers/input/misc/cs40l50-vibra.c index 996d6c38cca439..7ef4534fea2fb0 100644 --- a/drivers/input/misc/cs40l50-vibra.c +++ b/drivers/input/misc/cs40l50-vibra.c @@ -139,10 +139,10 @@ static struct cs40l50_effect *cs40l50_find_effect(int id, struct list_head *effe static int cs40l50_effect_bank_set(struct cs40l50_work *work_data, struct cs40l50_effect *effect) { - s16 bank_type = work_data->custom_data[0] & CS40L50_CUSTOM_DATA_MASK; + u32 bank_type = work_data->custom_data[0] & CS40L50_CUSTOM_DATA_MASK; if (bank_type >= CS40L50_WVFRM_BANK_NUM) { - dev_err(work_data->vib->dev, "Invalid bank (%d)\n", bank_type); + dev_err(work_data->vib->dev, "Invalid bank (%u)\n", bank_type); return -EINVAL; } @@ -326,6 +326,12 @@ static int cs40l50_add(struct input_dev *dev, struct ff_effect *effect, return -EINVAL; } + if (periodic->custom_len < CS40L50_OWT_CUSTOM_DATA_SIZE) { + dev_err(vib->dev, "Invalid custom data length (%u)\n", + periodic->custom_len); + return -EINVAL; + } + work_data.custom_data = memdup_array_user(effect->u.periodic.custom_data, effect->u.periodic.custom_len, sizeof(s16)); From 732f38c36059e68ba3b4b89c56911d777fd3185c Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Fri, 24 Jul 2026 18:42:06 -0700 Subject: [PATCH 039/612] Input: psxpad-spi - set driver data before use psxpad_spi_suspend() retrieves the controller state with spi_get_drvdata(), but probe never stores it, so suspend dereferences a NULL pointer. Store it during probe. Fixes: 8be193c7b1f4 ("Input: add support for PlayStation 1/2 joypads connected via SPI") Signed-off-by: Linmao Li Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260721055551.1714965-1-lilinmao@kylinos.cn Signed-off-by: Dmitry Torokhov --- drivers/input/joystick/psxpad-spi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/input/joystick/psxpad-spi.c b/drivers/input/joystick/psxpad-spi.c index f902a56d011f4e..aac77a2cfe468f 100644 --- a/drivers/input/joystick/psxpad-spi.c +++ b/drivers/input/joystick/psxpad-spi.c @@ -373,6 +373,7 @@ static int psxpad_spi_probe(struct spi_device *spi) return err; } + spi_set_drvdata(spi, pad); pm_runtime_enable(&spi->dev); return 0; From 5751c781d3c97ab6ce0e2a966156ed882152c415 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Fri, 24 Jul 2026 20:46:27 -0700 Subject: [PATCH 040/612] Input: iforce - validate input packet lengths iforce_process_packet() reads fixed fields from joystick, wheel and status packets without first checking their lengths. In particular, the shared hats-and-buttons helper unconditionally reads data[6]. The status tail is a sequence of 16-bit effect addresses, but an incomplete final address is also consumed. A successful zero-length USB URB additionally reads the packet ID before the common parser is called. Reject the zero-length USB transfer, require the seven-byte joystick and wheel prefixes and the two-byte status prefix, and consume only complete status-tail addresses. Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260720115018.75045-1-pengpeng@iscas.ac.cn Cc: stable@vger.kernel.org Signed-off-by: Dmitry Torokhov --- drivers/input/joystick/iforce/iforce-packets.c | 11 ++++++++++- drivers/input/joystick/iforce/iforce-usb.c | 3 +++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/drivers/input/joystick/iforce/iforce-packets.c b/drivers/input/joystick/iforce/iforce-packets.c index effa76bfd8f9a1..01fee14054fb23 100644 --- a/drivers/input/joystick/iforce/iforce-packets.c +++ b/drivers/input/joystick/iforce/iforce-packets.c @@ -155,6 +155,9 @@ void iforce_process_packet(struct iforce *iforce, switch (packet_id) { case 0x01: /* joystick position data */ + if (len < 7) + break; + input_report_abs(dev, ABS_X, (__s16) get_unaligned_le16(data)); input_report_abs(dev, ABS_Y, @@ -170,6 +173,9 @@ void iforce_process_packet(struct iforce *iforce, break; case 0x03: /* wheel position data */ + if (len < 7) + break; + input_report_abs(dev, ABS_WHEEL, (__s16) get_unaligned_le16(data)); input_report_abs(dev, ABS_GAS, 255 - data[2]); @@ -181,6 +187,9 @@ void iforce_process_packet(struct iforce *iforce, break; case 0x02: /* status report */ + if (len < 2) + break; + input_report_key(dev, BTN_DEAD, data[0] & 0x02); input_sync(dev); @@ -200,7 +209,7 @@ void iforce_process_packet(struct iforce *iforce, } } - for (j = 3; j < len; j += 2) + for (j = 3; j + sizeof(u16) <= len; j += sizeof(u16)) mark_core_as_ready(iforce, get_unaligned_le16(data + j)); break; diff --git a/drivers/input/joystick/iforce/iforce-usb.c b/drivers/input/joystick/iforce/iforce-usb.c index 0482eaaecf39a9..f04370e4191ea2 100644 --- a/drivers/input/joystick/iforce/iforce-usb.c +++ b/drivers/input/joystick/iforce/iforce-usb.c @@ -158,6 +158,9 @@ static void iforce_usb_irq(struct urb *urb) goto exit; } + if (!urb->actual_length) + goto exit; + iforce_process_packet(iforce, iforce_usb->data_in[0], iforce_usb->data_in + 1, urb->actual_length - 1); From 797fe91e50d6927f90f0c3b4444277c2c7c8b42b Mon Sep 17 00:00:00 2001 From: Michal Clapinski Date: Fri, 17 Jul 2026 15:40:28 +0200 Subject: [PATCH 041/612] kho: align kho_scratch to MAX_ORDER_NR_PAGES pages While booting with KHO, the following crash was observed: BUG: unable to handle page fault for address: ff19164fffff8328 RIP: 0010:__free_one_page+0x1a1/0x6b0 Call Trace: [] free_one_page+0xaf/0x240 [] deferred_free_pages+0xa8/0xd0 [] deferred_init_memmap_chunk+0x10f/0x1b0 [] padata_mt_helper+0x65/0xa0 [] process_scheduled_works+0x202/0x410 [] worker_thread+0x1f9/0x2d0 [] kthread+0x27d/0x2f0 [] ? __pfx_worker_thread+0x10/0x10 [] ? __pfx_kthread+0x10/0x10 [] ret_from_fork+0x145/0x280 [] ? __pfx_kthread+0x10/0x10 [] ret_from_fork_asm+0x1a/0x30 deferred_init_memmap_chunk() interleaves initialization of struct pages with freeing them. This works fine without KHO because free regions will never be buddy neighbors. However, with KHO, free memory will be split into (free && scratch) and (free && !scratch), that can be buddy neighbors. KHO scratch is aligned to CMA_MIN_ALIGNMENT_PAGES pages but buddy looks at the neighborhood of MAX_ORDER_NR_PAGES pages. These values are configurable but CMA_MIN_ALIGNMENT_PAGES is always less or equal to MAX_ORDER_NR_PAGES. In the crashing configuration they were set as follows: CMA_MIN_ALIGNMENT_PAGES = 1 << 9 MAX_ORDER_NR_PAGES = 1 << 10 So while freeing one chunk, buddy accessed uninitialized struct pages from another chunk, tried to merge the blocks and crashed. To fix this, let's just align KHO scratch to MAX_ORDER_NR_PAGES pages. Fixes: c6073743d0c7 ("kho: make preserved pages compatible with deferred struct page init") Signed-off-by: Michal Clapinski Link: https://patch.msgid.link/20260717134028.2880508-1-mclapinski@google.com [rppt: massaged the changelog] Signed-off-by: Mike Rapoport (Microsoft) --- kernel/liveupdate/kexec_handover.c | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c index 4834a809985ab7..175c08a6e41e3f 100644 --- a/kernel/liveupdate/kexec_handover.c +++ b/kernel/liveupdate/kexec_handover.c @@ -38,6 +38,16 @@ #include "../kexec_internal.h" #include "kexec_handover_internal.h" +/* + * This is the minimal alignment required by deferred struct page init. + * deferred_init_memmap_chunk frees memory to the buddy allocator, which looks + * at the neighboring pages (up to MAX_PAGE_ORDER) to merge them. + * If KHO scratch is not aligned to that value, buddy can access uninitialized + * struct pages, which can cause a crash. + */ +#define SCRATCH_ALIGNMENT_BYTES (PAGE_SIZE * MAX_ORDER_NR_PAGES) +static_assert(SCRATCH_ALIGNMENT_BYTES >= CMA_MIN_ALIGNMENT_BYTES); + /* The magic token for preserved pages */ #define KHO_PAGE_MAGIC 0x4b484f50U /* ASCII for 'KHOP' */ @@ -640,8 +650,8 @@ static void __init scratch_size_update(void) * Scratch areas are released as MIGRATE_CMA. Round them up to the right * size. */ - scratch_size_lowmem = round_up(scratch_size_lowmem, CMA_MIN_ALIGNMENT_BYTES); - scratch_size_global = round_up(scratch_size_global, CMA_MIN_ALIGNMENT_BYTES); + scratch_size_lowmem = round_up(scratch_size_lowmem, SCRATCH_ALIGNMENT_BYTES); + scratch_size_global = round_up(scratch_size_global, SCRATCH_ALIGNMENT_BYTES); } static phys_addr_t __init scratch_size_node(int nid) @@ -656,7 +666,7 @@ static phys_addr_t __init scratch_size_node(int nid) size = scratch_size_pernode; } - return round_up(size, CMA_MIN_ALIGNMENT_BYTES); + return round_up(size, SCRATCH_ALIGNMENT_BYTES); } /** @@ -692,7 +702,7 @@ static void __init kho_reserve_scratch(void) * next kernel */ size = scratch_size_lowmem; - addr = memblock_phys_alloc_range(size, CMA_MIN_ALIGNMENT_BYTES, 0, + addr = memblock_phys_alloc_range(size, SCRATCH_ALIGNMENT_BYTES, 0, ARCH_LOW_ADDRESS_LIMIT); if (!addr) { pr_err("Failed to reserve lowmem scratch buffer\n"); @@ -705,7 +715,7 @@ static void __init kho_reserve_scratch(void) /* reserve large contiguous area for allocations without nid */ size = scratch_size_global; - addr = memblock_phys_alloc(size, CMA_MIN_ALIGNMENT_BYTES); + addr = memblock_phys_alloc(size, SCRATCH_ALIGNMENT_BYTES); if (!addr) { pr_err("Failed to reserve global scratch buffer\n"); goto err_free_scratch_areas; @@ -721,7 +731,7 @@ static void __init kho_reserve_scratch(void) */ for_each_node_state(nid, N_MEMORY) { size = scratch_size_node(nid); - addr = memblock_alloc_range_nid(size, CMA_MIN_ALIGNMENT_BYTES, + addr = memblock_alloc_range_nid(size, SCRATCH_ALIGNMENT_BYTES, 0, MEMBLOCK_ALLOC_ACCESSIBLE, nid, true); if (!addr) { From 7e7f81cf6f5ca3311e526308f55d7c54d3ba71f9 Mon Sep 17 00:00:00 2001 From: "Borislav Petkov (AMD)" Date: Tue, 2 Jun 2026 21:26:44 -0700 Subject: [PATCH 042/612] x86/bugs: Make Safe-RET robust against interrupt injection An attacker injecting interrupts while the Safe-RET mitigation executes on machines affected by SRSO can neutralize the safe return sequence, potentially leading to data leakage through speculative execution. Fixup register state as if the Safe-RET sequence executed successfully by "emulating" it, in a manner of speaking, and avoid executing a RET instruction after returning from the interrupt. Co-developed-by: David Kaplan Signed-off-by: David Kaplan Signed-off-by: Borislav Petkov (AMD) --- arch/x86/entry/entry_64.S | 8 +++- arch/x86/include/asm/nospec-branch.h | 57 ++++++++++++++++++++++++++++ arch/x86/kernel/cpu/bugs.c | 39 +++++++++++++++++++ arch/x86/lib/retpoline.S | 20 ++++++++++ 4 files changed, 123 insertions(+), 1 deletion(-) diff --git a/arch/x86/entry/entry_64.S b/arch/x86/entry/entry_64.S index c6d996593f3297..253f0a585927fe 100644 --- a/arch/x86/entry/entry_64.S +++ b/arch/x86/entry/entry_64.S @@ -937,6 +937,8 @@ SYM_CODE_START(paranoid_entry) IBRS_ENTER save_reg=%r15 UNTRAIN_RET_FROM_CALL + HANDLE_INTR_SAFERET 8(%rsp) + RET SYM_CODE_END(paranoid_entry) @@ -1039,6 +1041,11 @@ SYM_CODE_START(error_entry) movl %ecx, %eax /* zero extend */ cmpq %rax, RIP+8(%rsp) je .Lbstep_iret + + VALIDATE_UNRET_END + + HANDLE_INTR_SAFERET 8(%rsp) + cmpq $.Lgs_change, RIP+8(%rsp) jne .Lerror_entry_done_lfence @@ -1057,7 +1064,6 @@ SYM_CODE_START(error_entry) FENCE_SWAPGS_KERNEL_ENTRY CALL_DEPTH_ACCOUNT leaq 8(%rsp), %rax /* return pt_regs pointer */ - VALIDATE_UNRET_END RET .Lbstep_iret: diff --git a/arch/x86/include/asm/nospec-branch.h b/arch/x86/include/asm/nospec-branch.h index b68892e6d58c47..2ea6591bc7b903 100644 --- a/arch/x86/include/asm/nospec-branch.h +++ b/arch/x86/include/asm/nospec-branch.h @@ -12,6 +12,7 @@ #include #include #include +#include /* * Call depth tracking for Intel SKL CPUs to address the RSB underflow @@ -176,6 +177,50 @@ add $(BITS_PER_LONG/8), %_ASM_SP; \ lfence; +/* + * Helper for detecting if an interrupt occurred at an unsafe location within + * Safe-RET. If Safe-RET is interrupted after the CALL or LEA the RSB may get + * poisoned by the interrupt handler. + * + * The Safe-RET sequence is: + * + * CALL + * LEA 8(%RSP), %RSP + * RET + * + * The two CMPs below check whether RIP points to after the CALL or after the + * LEA. + * + * The LFENCE below is to address this particular speculation case: + * + * 1. Userspace runs and poisons the BTB around the safe-RET routine + * + * 2. Userspace triggers some kind of exception + * + * 3. Kernel executes error_entry() and mis-speculates the branch into thinking + * it actually came from kernel space + * + * 4. The kernel then further mis-speculates that the exception occurred due + * to an interrupted safe-RET + * + * 5. The handle_interrupted_saferet() routine speculatively executes and + * speculatively does a safe-RET. But this is unsafe since it was never + * untrained. + * + * The LFENCE fixes this by ensuring step 5 is never reached speculatively. + * Note that this LFENCE only occurs if safe-RET was actually interrupted (so + * it's outside of the normal path). + */ +#define __HANDLE_INTR_SAFERET(name, pt_regs) \ + cmpq $(name), RIP+pt_regs; \ + jb 1f; \ + cmpq $(name)+5, RIP+pt_regs; \ + ja 1f; \ + lfence; \ + leaq pt_regs, %rdi; \ + call handle_interrupted_saferet; \ + 1: + #ifdef __ASSEMBLER__ /* @@ -293,6 +338,14 @@ #define UNTRAIN_RET_FROM_CALL \ __UNTRAIN_RET X86_FEATURE_ENTRY_IBPB, __stringify(RESET_CALL_DEPTH_FROM_CALL) +.macro HANDLE_INTR_SAFERET pt_regs +#ifdef CONFIG_MITIGATION_SRSO + ALTERNATIVE_2 "", \ + __stringify(__HANDLE_INTR_SAFERET(srso_safe_ret, \pt_regs)), X86_FEATURE_SRSO, \ + __stringify(__HANDLE_INTR_SAFERET(srso_alias_safe_ret, \pt_regs)), X86_FEATURE_SRSO_ALIAS + +#endif +.endm .macro CALL_DEPTH_ACCOUNT #ifdef CONFIG_MITIGATION_CALL_DEPTH_TRACKING @@ -625,6 +678,10 @@ static __always_inline void x86_idle_clear_cpu_buffers(void) x86_clear_cpu_buffers(); } +void srso_safe_ret(void); +void srso_alias_safe_ret(void); +void handle_interrupted_saferet(struct pt_regs *regs); + #endif /* __ASSEMBLER__ */ #endif /* _ASM_X86_NOSPEC_BRANCH_H_ */ diff --git a/arch/x86/kernel/cpu/bugs.c b/arch/x86/kernel/cpu/bugs.c index d9af230c051259..82436b3534fa64 100644 --- a/arch/x86/kernel/cpu/bugs.c +++ b/arch/x86/kernel/cpu/bugs.c @@ -3775,3 +3775,42 @@ void __warn_thunk(void) { WARN_ONCE(1, "Unpatched return thunk in use. This should not happen!\n"); } + +#ifdef CONFIG_MITIGATION_SRSO +/* + * Called during exception/interrupt entry if interrupted during the + * safe-RET sequence. The safe-RET sequence consists of 3 instructions: + * + * CALL + * LEA 8(%RSP), %RSP + * RET + * + * An interrupt after the CALL or after the LEA could potentially lead + * to branch predictor poisoning and results in the sequence not being + * able to be safely resumed. + * + * Therefore, modify the regs state as if the remaining part of the + * safe-RET sequence executed so the interrupt returns back to the + * desired return target, instead of the to the safe-RET sequence. + */ +void noinstr handle_interrupted_saferet(struct pt_regs *regs) +{ + unsigned long rip = regs->ip; + + if (rip == (unsigned long) srso_safe_ret || + rip == (unsigned long) srso_alias_safe_ret) { + /* Modify stack pointer as if LEA executed: */ + regs->sp += 8; + } + + /* + * Adjust registers as if RET executed: + * + * 1. Read the return address off the stack and into rIP: + */ + regs->ip = *(unsigned long *)(regs->sp); + + /* 2. Pop rIP off the stack: */ + regs->sp += 8; +} +#endif /* CONFIG_MITIGATION_SRSO */ diff --git a/arch/x86/lib/retpoline.S b/arch/x86/lib/retpoline.S index 8f1fed0c3b83f4..f9ca1d8eabca0d 100644 --- a/arch/x86/lib/retpoline.S +++ b/arch/x86/lib/retpoline.S @@ -207,10 +207,24 @@ __EXPORT_THUNK(srso_alias_untrain_ret) .pushsection .text..__x86.rethunk_safe SYM_CODE_START_NOALIGN(srso_alias_safe_ret) + + /* + * Tell objtool that those are not function pointers referenced by + * __HANDLE_INTR_SAFERET(). Below too. + */ + ANNOTATE_NOENDBR + + /* + * Safe-RET sequence. If you need to change it, adjust + * handle_interrupted_saferet() too. + */ lea 8(%_ASM_SP), %_ASM_SP UNWIND_HINT_FUNC + + ANNOTATE_NOENDBR ANNOTATE_UNRET_SAFE ret + /* End of Safe-RET sequence */ int3 SYM_FUNC_END(srso_alias_safe_ret) @@ -245,8 +259,14 @@ SYM_CODE_START_LOCAL_NOALIGN(srso_untrain_ret) * the stack. */ SYM_INNER_LABEL(srso_safe_ret, SYM_L_GLOBAL) + /* + * Safe-RET sequence. If you need to change it, adjust + * handle_interrupted_saferet() too. + */ lea 8(%_ASM_SP), %_ASM_SP ret + /* End of Safe-RET sequence */ + int3 int3 /* end of movabs */ From 97b228e596740fca5eeeb1d8e619c1f457d7e2f0 Mon Sep 17 00:00:00 2001 From: Xu Rao Date: Thu, 11 Jun 2026 14:45:30 +0800 Subject: [PATCH 043/612] thunderbolt: stream: Unmap buffers with mapped size The size passed to dma_unmap_page() must match the size used for the corresponding dma_map_page() call. Stream RX and TX buffers are mapped with TB_MAX_FRAME_SIZE when the buffer pools are allocated. However, tbstream_ring_free() currently uses tb_ring_frame_size() as the unmap size. That helper returns the current frame payload size, not the DMA mapping size. On the TX path, tbstream_dev_alloc_tx() stores a shorter payload length in frame.size when the payload is smaller than TB_MAX_FRAME_SIZE. This happens for a short final DATA frame, and also for the CLOSE frame, which is allocated with SZ_256. In those cases the buffer was mapped with TB_MAX_FRAME_SIZE, but tb_ring_frame_size() returns the shorter frame payload length. This makes the dma_unmap_page() size differ from the original dma_map_page() size. Use TB_MAX_FRAME_SIZE when unmapping stream buffers so the unmap size matches the DMA mapping size used by the buffer allocation paths. Signed-off-by: Xu Rao Fixes: 6db21d817b43 ("thunderbolt: Add support for USB4STREAM") Signed-off-by: Mika Westerberg --- drivers/thunderbolt/stream.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thunderbolt/stream.c b/drivers/thunderbolt/stream.c index c1f5c55583d069..4cc86d8d649183 100644 --- a/drivers/thunderbolt/stream.c +++ b/drivers/thunderbolt/stream.c @@ -257,7 +257,7 @@ static void tbstream_ring_free(struct tbstream_ring *ring) if (sf->frame.buffer_phy) dma_unmap_page(dma_dev, sf->frame.buffer_phy, - tb_ring_frame_size(&sf->frame), dir); + TB_MAX_FRAME_SIZE, dir); sf->frame.buffer_phy = 0; if (sf->page) __free_page(sf->page); From d2ee4d47aacbd2ba456092eeec670dba35fde291 Mon Sep 17 00:00:00 2001 From: Xu Rao Date: Wed, 24 Jun 2026 14:27:03 +0800 Subject: [PATCH 044/612] thunderbolt: Fix bandwidth group reservation indexing Valid bandwidth group IDs range from 1 through MAX_GROUPS, while Group ID 0 is reserved. tb_consumed_dp_bandwidth() uses the Group ID directly to index its local group_reserved[] array. The array currently has MAX_GROUPS entries, so its valid indices are 0 through MAX_GROUPS - 1. Group ID MAX_GROUPS therefore accesses one element past the end, and the final group's reserved bandwidth is not included when the array is summed. Give group_reserved[] MAX_GROUPS + 1 entries so direct Group ID indexing covers the reserved ID 0 and valid IDs 1 through MAX_GROUPS. Fixes: 52a4490e89d7 ("thunderbolt: Reserve released DisplayPort bandwidth for a group for 10 seconds") Cc: stable@vger.kernel.org Signed-off-by: Xu Rao Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 76323255439a88..f43f2d952372c6 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -609,7 +609,7 @@ static int tb_consumed_dp_bandwidth(struct tb *tb, int *consumed_up, int *consumed_down) { - int group_reserved[MAX_GROUPS] = {}; + int group_reserved[MAX_GROUPS + 1] = {}; struct tb_cm *tcm = tb_priv(tb); struct tb_tunnel *tunnel; bool downstream; From d6764992f17b23d91ff93ce905ab53c2aa7191f0 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Thu, 25 Jun 2026 06:54:09 -0500 Subject: [PATCH 045/612] thunderbolt: Bound the DROM dual link port number before indexing sw->ports tb_drom_parse_entry_port() validates the device-supplied header->index against sw->config.max_port_number before indexing sw->ports[], but the sibling field entry->dual_link_port_nr -- a 6-bit value also read from the DROM -- indexes the same array with no such check. A malicious or malformed Thunderbolt device can set dual_link_port_nr beyond the allocated sw->ports[] (max_port_number + 1 entries), producing an out-of-bounds tb_port pointer that is stored and later dereferenced. Reject a port entry whose dual_link_port_nr exceeds max_port_number, the same bound already applied to header->index. Fixes: cd22e73bdf5e ("thunderbolt: Read port configuration from eeprom.") Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas Signed-off-by: Mika Westerberg --- drivers/thunderbolt/eeprom.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c index 5681c17f82ecc1..2a13fa6888ba13 100644 --- a/drivers/thunderbolt/eeprom.c +++ b/drivers/thunderbolt/eeprom.c @@ -394,9 +394,16 @@ static int tb_drom_parse_entry_port(struct tb_switch *sw, return -EIO; } port->link_nr = entry->link_nr; - if (entry->has_dual_link_port) + if (entry->has_dual_link_port) { + if (entry->dual_link_port_nr > sw->config.max_port_number) { + tb_sw_warn(sw, + "port entry has invalid dual link port number %u\n", + entry->dual_link_port_nr); + return -EIO; + } port->dual_link_port = &port->sw->ports[entry->dual_link_port_nr]; + } } return 0; } From e48844ece5e3ed1d1eb865f6da2b16f62cd9f86d Mon Sep 17 00:00:00 2001 From: Xu Rao Date: Mon, 13 Jul 2026 17:32:37 +0800 Subject: [PATCH 046/612] thunderbolt: icm: Preserve USB4 proxy data-valid bit The ICM USB4 switch operation request encodes two values in request.data_len_valid: bit 4 marks the data payload valid, while bits 3:0 hold the payload length in dwords. A zero length with the valid bit set represents the full 16-dword data array. icm_usb4_switch_op() sets the valid bit when a transmit payload is present. For payloads shorter than the full 16 dwords, it then assigns the length to the whole field and clears the valid bit that was just set. The payload is still copied into the request, but the descriptor sent to firmware marks that data as invalid. This affects USB4 router operations that send short payloads through the firmware connection manager. In particular, USB4 NVM writes can send a short final block when the image size is not aligned to the 64-byte proxy payload size. Firmware may then ignore or reject that final block, while full 16-dword blocks are unaffected because they are encoded as length 0 with the valid bit set. OR the short payload length into data_len_valid so the valid bit is preserved. Fixes: 9039387e166e ("thunderbolt: Add USB4 router operation proxy for firmware connection manager") Cc: stable@vger.kernel.org Signed-off-by: Xu Rao Signed-off-by: Mika Westerberg --- drivers/thunderbolt/icm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c index 10fefac3b1d931..669807f0eaf86b 100644 --- a/drivers/thunderbolt/icm.c +++ b/drivers/thunderbolt/icm.c @@ -2341,7 +2341,7 @@ static int icm_usb4_switch_op(struct tb_switch *sw, u16 opcode, u32 *metadata, if (tx_data_len) { request.data_len_valid |= ICM_USB4_SWITCH_DATA_VALID; if (tx_data_len < ARRAY_SIZE(request.data)) - request.data_len_valid = + request.data_len_valid |= tx_data_len & ICM_USB4_SWITCH_DATA_LEN_MASK; memcpy(request.data, tx_data, tx_data_len * sizeof(u32)); } From 4a2e2c563b0ee2253a7902f775bf71232820186d Mon Sep 17 00:00:00 2001 From: Taceddin Sancak Date: Sat, 18 Jul 2026 03:21:46 +0300 Subject: [PATCH 047/612] pinctrl: qcom: ipq9650: fix audio_sec_mclk_in1/out1 group pins The audio_sec_mclk_in1 and audio_sec_mclk_out1 groups both list "gpio37", but in the pingroup table those functions are muxed on gpio39, while gpio37 provides the audio_sec_mclk_in0/out0 variants. This makes both functions unusable: selecting them on gpio39 is rejected by the pinmux core because the group is not listed for the function, and selecting them on gpio37 trips the WARN_ON() in msm_pinmux_set_mux() and fails with -EINVAL because that group cannot mux them. Point both groups at gpio39, matching the pingroup table. This also mirrors the primary audio MCLK pair, where the mclk0 and mclk1 variants live on separate pins (gpio53 and gpio51 respectively). Fixes: 3c8e7ba0e399 ("pinctrl: qcom: Introduce IPQ9650 TLMM driver") Assisted-by: Claude:claude-fable-5 Signed-off-by: Taceddin Sancak Acked-by: Linus Walleij Reviewed-by: Kathiravan Thirumoorthy Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260718002146.698973-1-ts.solidarity@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/pinctrl/qcom/pinctrl-ipq9650.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/pinctrl/qcom/pinctrl-ipq9650.c b/drivers/pinctrl/qcom/pinctrl-ipq9650.c index 64e443aa31b2f0..5399d7ad934c92 100644 --- a/drivers/pinctrl/qcom/pinctrl-ipq9650.c +++ b/drivers/pinctrl/qcom/pinctrl-ipq9650.c @@ -310,11 +310,11 @@ static const char *const audio_sec_mclk_out0_groups[] = { }; static const char *const audio_sec_mclk_in1_groups[] = { - "gpio37", + "gpio39", }; static const char *const audio_sec_mclk_out1_groups[] = { - "gpio37", + "gpio39", }; static const char *const audio_sec_groups[] = { From 8d2b10eef6f3b1336be2d1197db29b0c697f1d41 Mon Sep 17 00:00:00 2001 From: Surendra Singh Chouhan Date: Thu, 23 Jul 2026 07:56:34 +0530 Subject: [PATCH 048/612] watchdog: atcwdt200: fix return value when watchdog is enabled atcwdt_get_int_timer_type() returned TMR_UNKNOWN (enum value 3) when CTRL_WDT_EN was set in the control register. Because atcwdt_probe() checks "ret = atcwdt_get_int_timer_type(drv_data); if (ret) return ret;", returning TMR_UNKNOWN caused probe() to return 3 instead of a valid negative error code. Fix this by returning -EBUSY via dev_err_probe() when the watchdog is already enabled and update the kernel-doc description accordingly. Fixes: e4e0848ad046 ("watchdog: atcwdt200: Add driver for Andes ATCWDT200") Signed-off-by: Surendra Singh Chouhan Link: https://lore.kernel.org/r/20260723022634.8642-1-kr494167@gmail.com Signed-off-by: Guenter Roeck --- drivers/watchdog/atcwdt200_wdt.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/watchdog/atcwdt200_wdt.c b/drivers/watchdog/atcwdt200_wdt.c index 8e3b18aea368ff..7e757c217b0b53 100644 --- a/drivers/watchdog/atcwdt200_wdt.c +++ b/drivers/watchdog/atcwdt200_wdt.c @@ -260,9 +260,9 @@ static void atcwdt_get_timeout_params(struct atcwdt_drv *drv_data, * register to determine the interrupt timer type supported by the hardware. * * Note: This function must only be called when the ATCWDT200 watchdog is - * disabled. If the watchdog is enabled, this function returns TMR_UNKNOWN. + * disabled. If the watchdog is enabled, this function returns -EBUSY. * - * Returns: The interrupt timer type supported by the hardware. + * Returns: 0 on success or negative error code on failure. */ static int atcwdt_get_int_timer_type(struct atcwdt_drv *drv_data) { @@ -274,7 +274,8 @@ static int atcwdt_get_int_timer_type(struct atcwdt_drv *drv_data) regmap_read(drv_data->regmap, REG_CTRL, &val); if (val & CTRL_WDT_EN) { spin_unlock(&drv_data->lock); - return TMR_UNKNOWN; + return dev_err_probe(dev, -EBUSY, + "Watchdog is enabled, cannot detect timer type\n"); } /* From 1c3e23e78862493e8cf1adad02b10ffcb8b9921c Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Sun, 19 Jul 2026 08:06:31 +0500 Subject: [PATCH 049/612] staging: rtl8723bs: fix OOB read in rtw_get_wpa_ie() rtw_get_wpa_ie() reads bytes at fixed offsets into a vendor-specific information element without checking that the element is long enough, causing an out-of-bounds read for a short trailing IE. The function locates a vendor-specific IE (EID 221) with rtw_get_ie() and then compares a 4-byte OUI+type at pbuf + 2 and reads a 2-byte version word at pbuf + 6. Those accesses require the IE body to be at least 6 bytes, but rtw_get_ie() only guarantees that the element fits within the buffer; it does not enforce a minimum body length. A vendor-specific IE whose length byte is 0 to 5, placed at the end of the buffer, therefore makes these reads run past the end of the IE and past the end of the buffer itself. The buffer holds information elements taken from received management frames and from the IE blob passed to rtw_cfg80211_set_wpa_ie(), which is kmemdup'd to its exact length, so the read can run off the end of the allocation. The sibling helpers rtw_get_sec_ie(), rtw_get_wapi_ie() and rtw_get_wps_ie() in this file already reject too-short vendor-specific IEs before their OUI memcmp(); rtw_get_wpa_ie() was never brought in line with them, and needs a minimum of 6 rather than 4 bytes because of the version word. Add the missing length check. Fixes: 554c0a3abf216 ("staging: Add rtl8723bs sdio wifi driver") Cc: stable Signed-off-by: Muhammad Bilal Link: https://patch.msgid.link/20260719030631.88254-1-meatuni001@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/staging/rtl8723bs/core/rtw_ieee80211.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/staging/rtl8723bs/core/rtw_ieee80211.c b/drivers/staging/rtl8723bs/core/rtw_ieee80211.c index 863ddf84621860..e60ed33598662c 100644 --- a/drivers/staging/rtl8723bs/core/rtw_ieee80211.c +++ b/drivers/staging/rtl8723bs/core/rtw_ieee80211.c @@ -371,6 +371,9 @@ unsigned char *rtw_get_wpa_ie(unsigned char *pie, int *wpa_ie_len, int limit) pbuf = rtw_get_ie(pbuf, WLAN_EID_VENDOR_SPECIFIC, &len, limit_new); if (pbuf) { + if (len < 6) + goto check_next_ie; + /* check if oui matches... */ if (memcmp((pbuf + 2), wpa_oui_type, sizeof(wpa_oui_type))) goto check_next_ie; From ae21407350151bddfd4fea7aa39bd0643c0ca9d3 Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Sun, 19 Jul 2026 09:15:09 +0500 Subject: [PATCH 050/612] staging: rtl8723bs: fix OOB read in WMM_param_handler() WMM_param_handler() copies a fixed-size WMM parameter element out of a received information element without checking that the element is long enough, causing an out-of-bounds read for a short WMM IE. The handler reads sizeof(struct WMM_para_element) (18) bytes at pIE->data + 6, so it requires pIE->length to be at least 24 (WLAN_WMM_LEN), but it never validates the length. Two of its three callers reach it after matching only the WMM OUI: OnAssocRsp() in rtw_mlme_ext.c matches a 6-byte OUI, and join_cmd_hdl() matches a 4-byte OUI, before calling the handler. A vendor-specific IE carrying the WMM OUI but a length between 6 and 23, placed in an association response or in the IE blob handed to join_cmd_hdl(), passes the OUI check and then makes the memcmp() and memcpy() at pIE->data + 6 read past the end of the element. OnAssocRsp() parses a frame received from the AP, so this is reachable from a remote peer. The remaining caller in rtw_wlan_util.c already guards the handler with "pIE->length == WLAN_WMM_LEN". Move the equivalent check into the handler itself so every caller is covered; the sibling IE handlers in the same parsing loop (HT_caps_handler(), HT_info_handler(), ERP_IE_handler()) likewise bound their accesses by pIE->length. Fixes: 554c0a3abf21 ("staging: Add rtl8723bs sdio wifi driver") Cc: stable@vger.kernel.org Signed-off-by: Muhammad Bilal Link: https://patch.msgid.link/20260719041509.97894-1-meatuni001@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/staging/rtl8723bs/core/rtw_wlan_util.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/staging/rtl8723bs/core/rtw_wlan_util.c b/drivers/staging/rtl8723bs/core/rtw_wlan_util.c index a4de538722b56e..c614c0b4c792dc 100644 --- a/drivers/staging/rtl8723bs/core/rtw_wlan_util.c +++ b/drivers/staging/rtl8723bs/core/rtw_wlan_util.c @@ -666,6 +666,9 @@ int WMM_param_handler(struct adapter *padapter, struct ndis_80211_var_ie *pIE) return false; } + if (pIE->length != WLAN_WMM_LEN) + return false; + if (!memcmp(&(pmlmeinfo->WMM_param), (pIE->data + 6), sizeof(struct WMM_para_element))) return false; From 2c56ef658ac8c6bca36bc5574715e8f717207c6c Mon Sep 17 00:00:00 2001 From: Panagiotis Petrakopoulos Date: Mon, 20 Jul 2026 11:24:09 +0300 Subject: [PATCH 051/612] staging: rtl8723bs: fix missing shared-key auth challenge length check The WEP shared-key authentication handler uses the challenge-text element's attacker-controlled length without checking it against the fixed 128-byte chg_txt buffer. In OnAuthClient() the length from rtw_get_ie() - up to 255 - is used to perform memcpy() into the 128-byte pmlmeinfo->chg_txt, so a malicious AP sending a malformed WLAN_EID_CHALLENGE element can overflow/underfill chg_txt by up to 127 bytes. It is reachable over the air, before association, during shared-key authentication. In the case of an overflow, the driver can write out of bounds. In the case of an underfill, the driver can echo stale buffer memory. The challenge text is defined to be exactly 128 octets, which is already provided as the WLAN_AUTH_CHALLENGE_LEN define; require the element to be exactly that length before use. Fixes: 554c0a3abf21 ("staging: Add rtl8723bs sdio wifi driver") Cc: stable Signed-off-by: Panagiotis Petrakopoulos Link: https://patch.msgid.link/20260720082409.168379-1-npetrakopoulos2003@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/staging/rtl8723bs/core/rtw_mlme_ext.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/staging/rtl8723bs/core/rtw_mlme_ext.c b/drivers/staging/rtl8723bs/core/rtw_mlme_ext.c index a443b3530fb986..a9382dc1294b35 100644 --- a/drivers/staging/rtl8723bs/core/rtw_mlme_ext.c +++ b/drivers/staging/rtl8723bs/core/rtw_mlme_ext.c @@ -879,7 +879,7 @@ unsigned int OnAuthClient(struct adapter *padapter, union recv_frame *precv_fram p = rtw_get_ie(pframe + WLAN_HDR_A3_LEN + _AUTH_IE_OFFSET_, WLAN_EID_CHALLENGE, (int *)&len, pkt_len - WLAN_HDR_A3_LEN - _AUTH_IE_OFFSET_); - if (!p) + if (!p || len != WLAN_AUTH_CHALLENGE_LEN) goto authclnt_fail; memcpy(pmlmeinfo->chg_txt, p + 2, len); From 6829665d050983907b560173e49dcc6c11cb2730 Mon Sep 17 00:00:00 2001 From: Mariano Baragiola Date: Mon, 27 Jul 2026 13:08:59 -0300 Subject: [PATCH 052/612] staging: rtl8723bs: validate monitor transmit frame lengths rtw_cfg80211_monitor_if_xmit_entry() removes the radiotap header and then reads the 802.11 frame control field without checking that a base 802.11 header remains. The data path also pulls the calculated 802.11, QoS and SNAP header span before confirming that the skb contains it. A truncated frame can therefore cause out-of-bounds reads or leave insufficient data for the Ethernet address writes. Reject frames that do not contain the base 802.11 header and data frames that do not contain their complete calculated header span. Fixes: 554c0a3abf21 ("staging: Add rtl8723bs sdio wifi driver") Cc: stable Signed-off-by: Mariano Baragiola Link: https://patch.msgid.link/20260727160859.1917096-1-mbaragiola@linux.com Signed-off-by: Greg Kroah-Hartman --- drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c b/drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c index 967cd1b34aed86..9fa3131feb6628 100644 --- a/drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c +++ b/drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c @@ -2033,6 +2033,8 @@ static netdev_tx_t rtw_cfg80211_monitor_if_xmit_entry(struct sk_buff *skb, struc /* Skip the ratio tap header */ skb_pull(skb, rtap_len); + if (skb->len < dot11_hdr_len) + goto fail; dot11_hdr = (struct ieee80211_hdr *)skb->data; frame_control = le16_to_cpu(dot11_hdr->frame_control); @@ -2045,6 +2047,8 @@ static netdev_tx_t rtw_cfg80211_monitor_if_xmit_entry(struct sk_buff *skb, struc qos_len = 2; if ((frame_control & 0x0300) == 0x0300) dot11_hdr_len += 6; + if (skb->len < dot11_hdr_len + qos_len + snap_len) + goto fail; memcpy(dst_mac_addr, dot11_hdr->addr1, sizeof(dst_mac_addr)); memcpy(src_mac_addr, dot11_hdr->addr2, sizeof(src_mac_addr)); From 687f39faccba29ab26de965411db37e849af8ec2 Mon Sep 17 00:00:00 2001 From: Hans Ulli Kroll Date: Sun, 19 Jul 2026 15:35:59 +0200 Subject: [PATCH 053/612] pinctrl: qcom: ipq806x: mark gpio as a GPIO pin function The qcom pinctrl core supports marking functions that represent GPIO mode via PINCTRL_GPIO_PINFUNCTION(), so that strict pinmuxing does not reject GPIO requests for pins that are muxed to the GPIO function. Add a IPQ_GPIO_PIN_FUNCTION() helper and use it for the ipq806x gpio function, matching how the msm-based qcom drivers handle this. This allows ipq806x to keep the GPIO-related configuration in DTS without tripping over strict pinmux ownership checks. Fixes: cc85cb96e2e4 ("pinctrl: qcom: make the pinmuxing strict") Signed-off-by: Hans Ulli Kroll Acked-by: Linus Walleij Reviewed-by: Dmitry Baryshkov Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260719134548.8830-2-linux@ulli-kroll.de Signed-off-by: Bartosz Golaszewski --- drivers/pinctrl/qcom/pinctrl-ipq8064.c | 2 +- drivers/pinctrl/qcom/pinctrl-msm.h | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/pinctrl/qcom/pinctrl-ipq8064.c b/drivers/pinctrl/qcom/pinctrl-ipq8064.c index 78d320d56be6d8..e1c3c213559f9f 100644 --- a/drivers/pinctrl/qcom/pinctrl-ipq8064.c +++ b/drivers/pinctrl/qcom/pinctrl-ipq8064.c @@ -480,7 +480,7 @@ static const char * const ps_hold_groups[] = { }; static const struct pinfunction ipq8064_functions[] = { - IPQ_PIN_FUNCTION(gpio), + IPQ_GPIO_PIN_FUNCTION(gpio), IPQ_PIN_FUNCTION(mdio), IPQ_PIN_FUNCTION(ssbi), IPQ_PIN_FUNCTION(spmi), diff --git a/drivers/pinctrl/qcom/pinctrl-msm.h b/drivers/pinctrl/qcom/pinctrl-msm.h index 4fbff61de6bb3e..b94ba1a4177ede 100644 --- a/drivers/pinctrl/qcom/pinctrl-msm.h +++ b/drivers/pinctrl/qcom/pinctrl-msm.h @@ -24,6 +24,11 @@ struct pinctrl_pin_desc; fname##_groups, \ ARRAY_SIZE(fname##_groups)) +#define IPQ_GPIO_PIN_FUNCTION(fname) \ + [IPQ_MUX_##fname] = PINCTRL_GPIO_PINFUNCTION(#fname, \ + fname##_groups, \ + ARRAY_SIZE(fname##_groups)) + #define MSM_PIN_FUNCTION(fname) \ [msm_mux_##fname] = PINCTRL_PINFUNCTION(#fname, \ fname##_groups, \ From fd46760956509f580f7d3d25db4de10e7c6f949b Mon Sep 17 00:00:00 2001 From: Hans Ulli Kroll Date: Sun, 19 Jul 2026 15:36:00 +0200 Subject: [PATCH 054/612] pinctrl: qcom: ipq806x: mark pci reset as a GPIO pin function The qcom pinctrl core supports marking functions that represent GPIO mode via PINCTRL_GPIO_PINFUNCTION(), so that strict pinmuxing does not reject GPIO requests for pins that are muxed to the GPIO function. Mark PCIe reset as GPIO pin function This allows ipq806x to keep the PCIe-reset related configuration in DTS without tripping over strict pinmux ownership checks. Fixes: cc85cb96e2e4 ("pinctrl: qcom: make the pinmuxing strict") Signed-off-by: Hans Ulli Kroll Acked-by: Linus Walleij Reviewed-by: Dmitry Baryshkov Link: https://patch.msgid.link/20260719134548.8830-3-linux@ulli-kroll.de Signed-off-by: Bartosz Golaszewski --- drivers/pinctrl/qcom/pinctrl-ipq8064.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/pinctrl/qcom/pinctrl-ipq8064.c b/drivers/pinctrl/qcom/pinctrl-ipq8064.c index e1c3c213559f9f..0a50486337d319 100644 --- a/drivers/pinctrl/qcom/pinctrl-ipq8064.c +++ b/drivers/pinctrl/qcom/pinctrl-ipq8064.c @@ -507,19 +507,19 @@ static const struct pinfunction ipq8064_functions[] = { IPQ_PIN_FUNCTION(usb2_hsic), IPQ_PIN_FUNCTION(rgmii2), IPQ_PIN_FUNCTION(sata), - IPQ_PIN_FUNCTION(pcie1_rst), + IPQ_GPIO_PIN_FUNCTION(pcie1_rst), IPQ_PIN_FUNCTION(pcie1_prsnt), IPQ_PIN_FUNCTION(pcie1_pwren_n), IPQ_PIN_FUNCTION(pcie1_pwren), IPQ_PIN_FUNCTION(pcie1_pwrflt), IPQ_PIN_FUNCTION(pcie1_clk_req), - IPQ_PIN_FUNCTION(pcie2_rst), + IPQ_GPIO_PIN_FUNCTION(pcie2_rst), IPQ_PIN_FUNCTION(pcie2_prsnt), IPQ_PIN_FUNCTION(pcie2_pwren_n), IPQ_PIN_FUNCTION(pcie2_pwren), IPQ_PIN_FUNCTION(pcie2_pwrflt), IPQ_PIN_FUNCTION(pcie2_clk_req), - IPQ_PIN_FUNCTION(pcie3_rst), + IPQ_GPIO_PIN_FUNCTION(pcie3_rst), IPQ_PIN_FUNCTION(pcie3_prsnt), IPQ_PIN_FUNCTION(pcie3_pwren_n), IPQ_PIN_FUNCTION(pcie3_pwren), From cf6c993c0feca7984797e634deba3c80342e199a Mon Sep 17 00:00:00 2001 From: Zhan Xusheng Date: Sat, 25 Jul 2026 16:00:04 +0800 Subject: [PATCH 055/612] fscrypt: use the mount idmap for the owner check in fscrypt_ioctl_set_policy() fscrypt_ioctl_set_policy() calls inode_owner_or_capable() with &nop_mnt_idmap before allowing an encryption policy to be set, instead of the idmap of the mount the ioctl was issued on. fscrypt is used by filesystems that support idmapped mounts (e.g. ext4, f2fs), so on such a mount this compares the caller's fsuid against the unmapped on-disk owner rather than the mapped owner: the actual owner can be wrongly denied with -EACCES and an unrelated caller wrongly allowed. Use file_mnt_idmap(filp) instead. Fixes: 14f3db5542e6 ("ext4: support idmapped mounts") Cc: stable@vger.kernel.org Signed-off-by: Zhan Xusheng Link: https://patch.msgid.link/20260725080004.929328-1-zhanxusheng1024@gmail.com Signed-off-by: Eric Biggers --- fs/crypto/policy.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/crypto/policy.c b/fs/crypto/policy.c index 9915e39362dbab..c80b24a941ad05 100644 --- a/fs/crypto/policy.c +++ b/fs/crypto/policy.c @@ -534,7 +534,7 @@ int fscrypt_ioctl_set_policy(struct file *filp, const void __user *arg) return -EFAULT; policy.version = version; - if (!inode_owner_or_capable(&nop_mnt_idmap, inode)) + if (!inode_owner_or_capable(file_mnt_idmap(filp), inode)) return -EACCES; ret = mnt_want_write_file(filp); From d2f96bcb89d36d488a10e3bcf819b98536968286 Mon Sep 17 00:00:00 2001 From: Andrey Albershteyn Date: Mon, 27 Jul 2026 11:43:52 +0200 Subject: [PATCH 056/612] fs,fsverity: remove check for fsverity being enabled in setattr_prepare() The check that fs-verity is available in the kernel is not necessary here. Filesystems could have fsverity files even without fs-verity enabled. In that case, truncate on fsverity file will succeed, what this check is trying to prevent. Fixes: e9734653c523 ("fs,fsverity: reject size changes on fsverity files in setattr_prepare") Cc: stable@vger.kernel.org Signed-off-by: Andrey Albershteyn Reviewed-by: Christoph Hellwig Link: https://patch.msgid.link/20260727094352.1734826-1-aalbersh@kernel.org Signed-off-by: Eric Biggers --- fs/attr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/attr.c b/fs/attr.c index 4f437fabb7f0f5..71888ac903c27d 100644 --- a/fs/attr.c +++ b/fs/attr.c @@ -176,7 +176,7 @@ int setattr_prepare(struct mnt_idmap *idmap, struct dentry *dentry, * covered by the open-time check because sys_truncate() takes a * path, not an open file. */ - if (IS_ENABLED(CONFIG_FS_VERITY) && IS_VERITY(inode)) + if (IS_VERITY(inode)) return -EPERM; error = inode_newsize_ok(inode, attr->ia_size); From 6e9f539e4f01153651dd77609b5ccadd44b74df8 Mon Sep 17 00:00:00 2001 From: Ralf Lici Date: Wed, 29 Jul 2026 15:41:30 +0200 Subject: [PATCH 057/612] ovpn: add missing rtnl_link_ops->get_size callback ovpn_fill_info emits IFLA_OVPN_MODE inside IFLA_INFO_DATA, but ovpn_link_ops does not provide a get_size callback. Consequently, rtnetlink's size estimate for ovpn link messages does not include the nested mode attribute. Available skb tailroom may hide this mismatch. When the remaining space is insufficient, however, ovpn_fill_info returns -EMSGSIZE and message construction fails. Add the callback and account for IFLA_OVPN_MODE. Fixes: c2d950c4672a ("ovpn: add basic interface creation/destruction/management routines") Signed-off-by: Ralf Lici Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/main.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c index 9993c1dfe471d9..9d9a0ff690d662 100644 --- a/drivers/net/ovpn/main.c +++ b/drivers/net/ovpn/main.c @@ -210,6 +210,12 @@ static int ovpn_newlink(struct net_device *dev, return register_netdevice(dev); } +static size_t ovpn_get_size(const struct net_device *dev) +{ + /* IFLA_OVPN_MODE */ + return nla_total_size(sizeof(u8)); +} + static int ovpn_fill_info(struct sk_buff *skb, const struct net_device *dev) { struct ovpn_priv *ovpn = netdev_priv(dev); @@ -228,6 +234,7 @@ static struct rtnl_link_ops ovpn_link_ops = { .policy = ovpn_policy, .maxtype = IFLA_OVPN_MAX, .newlink = ovpn_newlink, + .get_size = ovpn_get_size, .fill_info = ovpn_fill_info, }; From a3ba349af8e1bb7e0efdcd2dd53d69282f18478a Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 13 Jul 2026 14:58:38 +0200 Subject: [PATCH 058/612] arm64: dts: qcom: eliza: Fix DSI1 phy reference clock rate The DSI PHY CXO clock input is the SoC CXO divided by two. DSI0 already uses correct one, but DSI1 got copy-paste from SM8750, which had same problem and copy-pasted code from SM8650. Wrong clock parent will cause incorrect DSI1 PHY PLL frequencies to be used making the DSI panel non-working, although there is no upstream user of DSI1. Fixes: 159d252ed800 ("arm64: dts: qcom: eliza: Add display (MDSS) with Display CC") Signed-off-by: Krzysztof Kozlowski Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260713125837.727632-2-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- arch/arm64/boot/dts/qcom/eliza.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/qcom/eliza.dtsi b/arch/arm64/boot/dts/qcom/eliza.dtsi index 977de44b816e42..72b786fec195f2 100644 --- a/arch/arm64/boot/dts/qcom/eliza.dtsi +++ b/arch/arm64/boot/dts/qcom/eliza.dtsi @@ -2559,7 +2559,7 @@ "dsi_pll"; clocks = <&dispcc DISP_CC_MDSS_AHB_CLK>, - <&rpmhcc RPMH_CXO_CLK>; + <&bi_tcxo_div2>; clock-names = "iface", "ref"; From bab4d538f8485e0d48538fcb82b285df3779278e Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sat, 25 Jul 2026 14:57:22 -0700 Subject: [PATCH 059/612] ARM: dts: BCM5301X: fix PCIe controller 2 second interrupt PCIe controller 2 has interrupts 0-4 mapping to GIC SPI 138-142. The mapping for interrupt 1 was incorrectly set to 138 due to a copy-paste error. Fix it to 139. Assisted-by: opencode:big-pickle Signed-off-by: Rosen Penev Link: https://lore.kernel.org/r/20260725215722.9323-1-rosenp@gmail.com Fixes: 3b3e35b279be ("ARM: dts: BCM5301X: Relicense AXI interrupts code to the GPL 2.0+ / MIT") Signed-off-by: Florian Fainelli --- arch/arm/boot/dts/broadcom/bcm-ns.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/boot/dts/broadcom/bcm-ns.dtsi b/arch/arm/boot/dts/broadcom/bcm-ns.dtsi index 392a2571366964..bd52de0faa3ea2 100644 --- a/arch/arm/boot/dts/broadcom/bcm-ns.dtsi +++ b/arch/arm/boot/dts/broadcom/bcm-ns.dtsi @@ -141,7 +141,7 @@ /* PCIe Controller 2 */ <0x00014000 0 &gic GIC_SPI 138 IRQ_TYPE_LEVEL_HIGH>, - <0x00014000 1 &gic GIC_SPI 138 IRQ_TYPE_LEVEL_HIGH>, + <0x00014000 1 &gic GIC_SPI 139 IRQ_TYPE_LEVEL_HIGH>, <0x00014000 2 &gic GIC_SPI 140 IRQ_TYPE_LEVEL_HIGH>, <0x00014000 3 &gic GIC_SPI 141 IRQ_TYPE_LEVEL_HIGH>, <0x00014000 4 &gic GIC_SPI 142 IRQ_TYPE_LEVEL_HIGH>, From 6de6732c4c784ce64e2457630ca800dfe5efb774 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sun, 28 Jun 2026 16:10:49 -0700 Subject: [PATCH 060/612] ARM: dts: BCM5301X: EA9200: fix NVRAM size Fixes: [ 0.182121] WARNING: CPU: 0 PID: 1 at drivers/nvmem/brcm_nvram.c:85 brcm_nvram_probe+0x400/0x480 [ 0.182159] Unexpected (big) NVRAM size: 1056112 B Fixes: 90a77291ac09 ("ARM: dts: BCM5301X: R8000 add NVRAM with MAC address for WAN port") Signed-off-by: Rosen Penev Link: https://lore.kernel.org/r/20260628231049.1248899-1-rosenp@gmail.com Signed-off-by: Florian Fainelli --- arch/arm/boot/dts/broadcom/bcm4709-linksys-ea9200.dts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/boot/dts/broadcom/bcm4709-linksys-ea9200.dts b/arch/arm/boot/dts/broadcom/bcm4709-linksys-ea9200.dts index 5bbc2ba0f95989..87569408bb6959 100644 --- a/arch/arm/boot/dts/broadcom/bcm4709-linksys-ea9200.dts +++ b/arch/arm/boot/dts/broadcom/bcm4709-linksys-ea9200.dts @@ -26,7 +26,7 @@ nvram@1c080000 { compatible = "brcm,nvram"; - reg = <0x1c080000 0x180000>; + reg = <0x1c080000 0x100000>; et2macaddr: et2macaddr { #nvmem-cell-cells = <1>; From 9cbc63400f7dc000adf898bbbc8208dfd93091d7 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Tue, 28 Jul 2026 09:15:39 +0300 Subject: [PATCH 061/612] thunderbolt: Initialize ->domain_released completion before it is being used MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both Woody and Marek reported following crash: BUG: unable to handle page fault for address: fffffffffffffff8 Call Trace: device_release+0x43/0x90 kobject_cleanup+0x3c/0x180 icm_probe+0x19c/0x550 [thunderbolt] nhi_probe+0x1a4/0x370 [thunderbolt] local_pci_probe+0x41/0x90 pci_call_probe+0x5b/0x1a0 ... This only triggers on the error path when icm_probe() fails and the domain structure is released, it tries to complete() uninitialized completion. Fix this by initializing the completion earlier. Reported-by: Marek Marczykowski-Górecki Closes: https://lore.kernel.org/linux-usb/amdezCBiW4fd_DuB@mail-itl/ Reported-by: Woody Suwalski Tested_by: Woody Suwalski Closes: https://lore.kernel.org/linux-usb/62caf7f8-b403-d0dd-15bc-b31b56f71c28@gmail.com/ Fixes: f5cc545f5969 ("thunderbolt: Wait for tb_domain_release() to complete when driver is removed") Signed-off-by: Mika Westerberg --- drivers/thunderbolt/nhi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index 0f795ea58756d6..35e3c119d5ee84 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -1226,6 +1226,8 @@ int nhi_probe(struct tb_nhi *nhi) return dev_err_probe(dev, res, "NHI specific init failed\n"); } + init_completion(&nhi->domain_released); + tb = nhi_select_cm(nhi); if (!tb) return dev_err_probe(dev, -ENODEV, @@ -1233,8 +1235,6 @@ int nhi_probe(struct tb_nhi *nhi) dev_dbg(dev, "NHI initialized, starting thunderbolt\n"); - init_completion(&nhi->domain_released); - res = tb_domain_add(tb, host_reset); if (res) { /* From 935dc3bb20e0fee89b598e10d1f5f1f500ddc79a Mon Sep 17 00:00:00 2001 From: Marco Baffo Date: Wed, 22 Jul 2026 06:47:56 +0200 Subject: [PATCH 062/612] ovpn: limit keepalive values to one day Large keepalive values can overflow the delayed-work delay on 32-bit systems, causing the keepalive worker to be repeatedly scheduled. A correct configuration should not require such large keepalive values, and an upper limit of one day is already generous and unnecessary in practice. Limit both the keepalive interval and timeout to 86400 seconds. Signed-off-by: Marco Baffo Signed-off-by: Antonio Quartulli --- Documentation/netlink/specs/ovpn.yaml | 4 ++++ drivers/net/ovpn/netlink-gen.c | 20 ++++++++++++++------ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/Documentation/netlink/specs/ovpn.yaml b/Documentation/netlink/specs/ovpn.yaml index b0c782e59a32ba..ac50d1d7c00a63 100644 --- a/Documentation/netlink/specs/ovpn.yaml +++ b/Documentation/netlink/specs/ovpn.yaml @@ -118,12 +118,16 @@ attribute-sets: doc: >- The number of seconds after which a keep alive message is sent to the peer + checks: + max: 86400 - name: keepalive-timeout type: u32 doc: >- The number of seconds from the last activity after which the peer is assumed dead + checks: + max: 86400 - name: del-reason type: u32 diff --git a/drivers/net/ovpn/netlink-gen.c b/drivers/net/ovpn/netlink-gen.c index 2147cec7c2c524..92d2fdc17c2e8b 100644 --- a/drivers/net/ovpn/netlink-gen.c +++ b/drivers/net/ovpn/netlink-gen.c @@ -16,6 +16,14 @@ static const struct netlink_range_validation ovpn_a_peer_id_range = { .max = 16777215ULL, }; +static const struct netlink_range_validation ovpn_a_peer_keepalive_interval_range = { + .max = 86400ULL, +}; + +static const struct netlink_range_validation ovpn_a_peer_keepalive_timeout_range = { + .max = 86400ULL, +}; + static const struct netlink_range_validation ovpn_a_peer_tx_id_range = { .max = 16777215ULL, }; @@ -68,8 +76,8 @@ const struct nla_policy ovpn_peer_nl_policy[OVPN_A_PEER_TX_ID + 1] = { [OVPN_A_PEER_LOCAL_IPV4] = { .type = NLA_BE32, }, [OVPN_A_PEER_LOCAL_IPV6] = NLA_POLICY_EXACT_LEN(16), [OVPN_A_PEER_LOCAL_PORT] = NLA_POLICY_MIN(NLA_BE16, 1), - [OVPN_A_PEER_KEEPALIVE_INTERVAL] = { .type = NLA_U32, }, - [OVPN_A_PEER_KEEPALIVE_TIMEOUT] = { .type = NLA_U32, }, + [OVPN_A_PEER_KEEPALIVE_INTERVAL] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_keepalive_interval_range), + [OVPN_A_PEER_KEEPALIVE_TIMEOUT] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_keepalive_timeout_range), [OVPN_A_PEER_DEL_REASON] = NLA_POLICY_MAX(NLA_U32, 4), [OVPN_A_PEER_VPN_RX_BYTES] = { .type = NLA_UINT, }, [OVPN_A_PEER_VPN_TX_BYTES] = { .type = NLA_UINT, }, @@ -97,8 +105,8 @@ const struct nla_policy ovpn_peer_new_input_nl_policy[OVPN_A_PEER_TX_ID + 1] = { [OVPN_A_PEER_VPN_IPV6] = NLA_POLICY_EXACT_LEN(16), [OVPN_A_PEER_LOCAL_IPV4] = { .type = NLA_BE32, }, [OVPN_A_PEER_LOCAL_IPV6] = NLA_POLICY_EXACT_LEN(16), - [OVPN_A_PEER_KEEPALIVE_INTERVAL] = { .type = NLA_U32, }, - [OVPN_A_PEER_KEEPALIVE_TIMEOUT] = { .type = NLA_U32, }, + [OVPN_A_PEER_KEEPALIVE_INTERVAL] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_keepalive_interval_range), + [OVPN_A_PEER_KEEPALIVE_TIMEOUT] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_keepalive_timeout_range), [OVPN_A_PEER_TX_ID] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_tx_id_range), }; @@ -112,8 +120,8 @@ const struct nla_policy ovpn_peer_set_input_nl_policy[OVPN_A_PEER_TX_ID + 1] = { [OVPN_A_PEER_VPN_IPV6] = NLA_POLICY_EXACT_LEN(16), [OVPN_A_PEER_LOCAL_IPV4] = { .type = NLA_BE32, }, [OVPN_A_PEER_LOCAL_IPV6] = NLA_POLICY_EXACT_LEN(16), - [OVPN_A_PEER_KEEPALIVE_INTERVAL] = { .type = NLA_U32, }, - [OVPN_A_PEER_KEEPALIVE_TIMEOUT] = { .type = NLA_U32, }, + [OVPN_A_PEER_KEEPALIVE_INTERVAL] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_keepalive_interval_range), + [OVPN_A_PEER_KEEPALIVE_TIMEOUT] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_keepalive_timeout_range), [OVPN_A_PEER_TX_ID] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_tx_id_range), }; From 33ec10567fe14456063daf549fdf1a4f53448e4c Mon Sep 17 00:00:00 2001 From: Antonio Quartulli Date: Tue, 28 Jul 2026 13:48:47 +0200 Subject: [PATCH 063/612] ovpn: skip rehash for peers already removed from by_id ovpn_nl_peer_set_doit() resolves the target peer via ovpn_peer_get_by_id() before taking ovpn->lock. In the window between the lookup (which only takes a refcount) and the subsequent spin_lock_bh(&ovpn->lock), a concurrent OVPN_CMD_PEER_DEL, keepalive expiry, or socket teardown can take ovpn->lock first, run ovpn_peer_remove() to unhash the peer from all four tables (by_id, by_vpn_addr4/6, by_transp_addr) and release the lock. set_doit then acquires ovpn->lock and calls ovpn_peer_hash_vpn_ip(), which re-inserts the now-removed peer back into the rehashing tables. The same race affects the float path: ovpn_peer_endpoints_update() holds only a refcount and acquires ovpn->lock very late (after async AEAD decrypt and a netlink notification), then rehashes the peer in the by_transp_addr table. The resurrected peer becomes reachable again from the RX lookup (ovpn_peer_get_by_transp_addr) and the TX VPN-IP lookup, even though userspace believes it is gone. Once the data-path refcount drops the peer is freed via call_rcu while the hash entries embedded in it remain linked, opening a UAF window. Bail out of the rehash when hash_entry_id is unhashed, mirroring the sentinel already used by ovpn_peer_remove() to detect the already-removed state. The check is safe under ovpn->lock, which serializes every mutation of hash_entry_id, and is a no-op for the add path because ovpn_peer_add_mp() inserts hash_entry_id before calling ovpn_peer_hash_vpn_ip(). Fixes: 1d36a36f6d53 ("ovpn: implement peer add/get/dump/delete via netlink") Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/peer.c | 73 ++++++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 30 deletions(-) diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index a21d02ac715e0e..68021c0c178309 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -297,40 +297,46 @@ void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *skb) /* rehashing is required only in MP mode as P2P has one peer * only and thus there is no hashtable */ - if (peer->ovpn->mode == OVPN_MODE_MP) { - spin_lock_bh(&peer->ovpn->lock); - spin_lock_bh(&peer->lock); - bind = rcu_dereference_protected(peer->bind, - lockdep_is_held(&peer->lock)); - if (unlikely(!bind)) { - spin_unlock_bh(&peer->lock); - spin_unlock_bh(&peer->ovpn->lock); - return; - } + if (peer->ovpn->mode != OVPN_MODE_MP) + return; - /* This function may be invoked concurrently, therefore another - * float may have happened in parallel: perform rehashing - * using the peer->bind->remote directly as key - */ + spin_lock_bh(&peer->ovpn->lock); + spin_lock_bh(&peer->lock); + bind = rcu_dereference_protected(peer->bind, + lockdep_is_held(&peer->lock)); + if (unlikely(!bind)) + goto unlock2; - switch (bind->remote.in4.sin_family) { - case AF_INET: - salen = sizeof(*sa); - break; - case AF_INET6: - salen = sizeof(*sa6); - break; - } + /* peer may have been concurrently removed between the caller's + * initial lookup and our acquisition of ovpn->lock; skip the + * rehash so we don't re-insert a removed peer + */ + if (unlikely(hlist_unhashed(&peer->hash_entry_id))) + goto unlock2; - /* remove old hashing */ - hlist_nulls_del_init_rcu(&peer->hash_entry_transp_addr); - /* re-add with new transport address */ - nhead = ovpn_get_hash_head(peer->ovpn->peers->by_transp_addr, - &bind->remote, salen); - hlist_nulls_add_head_rcu(&peer->hash_entry_transp_addr, nhead); - spin_unlock_bh(&peer->lock); - spin_unlock_bh(&peer->ovpn->lock); + /* This function may be invoked concurrently, therefore another + * float may have happened in parallel: perform rehashing + * using the peer->bind->remote directly as key + */ + + switch (bind->remote.in4.sin_family) { + case AF_INET: + salen = sizeof(*sa); + break; + case AF_INET6: + salen = sizeof(*sa6); + break; } + + /* remove old hashing */ + hlist_nulls_del_init_rcu(&peer->hash_entry_transp_addr); + /* re-add with new transport address */ + nhead = ovpn_get_hash_head(peer->ovpn->peers->by_transp_addr, + &bind->remote, salen); + hlist_nulls_add_head_rcu(&peer->hash_entry_transp_addr, nhead); +unlock2: + spin_unlock_bh(&peer->lock); + spin_unlock_bh(&peer->ovpn->lock); return; unlock: spin_unlock_bh(&peer->lock); @@ -906,6 +912,13 @@ void ovpn_peer_hash_vpn_ip(struct ovpn_peer *peer) if (peer->ovpn->mode != OVPN_MODE_MP) return; + /* peer may have been concurrently removed between the caller's + * initial lookup and our acquisition of ovpn->lock; skip the + * rehash so we don't re-insert a removed peer + */ + if (hlist_unhashed(&peer->hash_entry_id)) + return; + if (peer->vpn_addrs.ipv4.s_addr != htonl(INADDR_ANY)) { /* remove potential old hashing */ hlist_nulls_del_init_rcu(&peer->hash_entry_addr4); From cc12f7240c8c4dee557749d33237542613992f14 Mon Sep 17 00:00:00 2001 From: Antonio Quartulli Date: Tue, 28 Jul 2026 13:48:48 +0200 Subject: [PATCH 064/612] ovpn: rehash peer in by_transp_addr table on CMD_PEER_SET When userspace updates a peer's remote endpoint via OVPN_CMD_PEER_SET, ovpn_nl_peer_modify() installs a new ovpn_bind through ovpn_peer_reset_sockaddr(), but ovpn_nl_peer_set_doit() only calls ovpn_peer_hash_vpn_ip() to refresh the VPN-IP hashtables. The peer is left in the bucket of peers->by_transp_addr corresponding to its old remote address. As a consequence, datagrams arriving at the UDP RX path from the newly configured remote hash to a different slot and the lockless lookup in ovpn_peer_get_by_transp_addr() (called from ovpn_udp_encap_recv()) does not find the peer, until either a float event or a peer re-add fixes the bucket. Introduce ovpn_peer_hash_transp_addr() (modeled after ovpn_peer_hash_vpn_ip()) and invoke it from ovpn_nl_peer_set_doit() whenever the request carried a new remote address. The helper bails out in P2P mode and on peers without a bind (TCP), and relies on hlist_nulls_del_init_rcu()'s pprev==NULL short-circuit to handle the case of an entry not currently linked in the table. Fixes: 1d36a36f6d53 ("ovpn: implement peer add/get/dump/delete via netlink") Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/netlink.c | 6 +++ drivers/net/ovpn/peer.c | 105 +++++++++++++++++++++++++------------ drivers/net/ovpn/peer.h | 1 + 3 files changed, 79 insertions(+), 33 deletions(-) diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c index 4c66c1ec497ec2..4dad852941982e 100644 --- a/drivers/net/ovpn/netlink.c +++ b/drivers/net/ovpn/netlink.c @@ -534,6 +534,12 @@ int ovpn_nl_peer_set_doit(struct sk_buff *skb, struct genl_info *info) */ if (ret > 0) ovpn_peer_hash_vpn_ip(peer); + /* if the remote endpoint was updated, the by_transp_addr hash bucket + * also needs to be refreshed, otherwise incoming packets from the new + * remote address would fail the lockless lookup + */ + if (attrs[OVPN_A_PEER_REMOTE_IPV4] || attrs[OVPN_A_PEER_REMOTE_IPV6]) + ovpn_peer_hash_transp_addr(peer); spin_unlock_bh(&ovpn->lock); ovpn_peer_put(peer); diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index 68021c0c178309..a330892e82bf7f 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -189,6 +189,9 @@ int ovpn_peer_reset_sockaddr(struct ovpn_peer *peer, &(*__tbl1)[ovpn_get_hash_slot(*__tbl1, _key, _key_len)];\ }) +static void __ovpn_peer_hash_transp_addr(struct ovpn_peer *peer, + const struct ovpn_bind *bind); + /** * ovpn_peer_endpoints_update - update remote or local endpoint for peer * @peer: peer to update the remote endpoint for @@ -196,7 +199,6 @@ int ovpn_peer_reset_sockaddr(struct ovpn_peer *peer, */ void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *skb) { - struct hlist_nulls_head *nhead; struct sockaddr_storage ss; struct sockaddr_in6 *sa6; bool reset_cache = false; @@ -295,46 +297,23 @@ void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *skb) ovpn_nl_peer_float_notify(peer, &ss); /* rehashing is required only in MP mode as P2P has one peer - * only and thus there is no hashtable + * only and thus there is no hashtable. + * + * This function may be invoked concurrently, so re-read peer->bind + * under the proper locks and rehash against its current value. */ if (peer->ovpn->mode != OVPN_MODE_MP) return; + /* This function may be invoked concurrently, therefore another + * float may have happened in parallel: re-acquire the locks and + * rehash using the peer->bind->remote directly as key + */ spin_lock_bh(&peer->ovpn->lock); spin_lock_bh(&peer->lock); bind = rcu_dereference_protected(peer->bind, lockdep_is_held(&peer->lock)); - if (unlikely(!bind)) - goto unlock2; - - /* peer may have been concurrently removed between the caller's - * initial lookup and our acquisition of ovpn->lock; skip the - * rehash so we don't re-insert a removed peer - */ - if (unlikely(hlist_unhashed(&peer->hash_entry_id))) - goto unlock2; - - /* This function may be invoked concurrently, therefore another - * float may have happened in parallel: perform rehashing - * using the peer->bind->remote directly as key - */ - - switch (bind->remote.in4.sin_family) { - case AF_INET: - salen = sizeof(*sa); - break; - case AF_INET6: - salen = sizeof(*sa6); - break; - } - - /* remove old hashing */ - hlist_nulls_del_init_rcu(&peer->hash_entry_transp_addr); - /* re-add with new transport address */ - nhead = ovpn_get_hash_head(peer->ovpn->peers->by_transp_addr, - &bind->remote, salen); - hlist_nulls_add_head_rcu(&peer->hash_entry_transp_addr, nhead); -unlock2: + __ovpn_peer_hash_transp_addr(peer, bind); spin_unlock_bh(&peer->lock); spin_unlock_bh(&peer->ovpn->lock); return; @@ -902,6 +881,66 @@ bool ovpn_peer_check_by_src(struct ovpn_priv *ovpn, struct sk_buff *skb, return match; } +/* Move @peer to the by_transp_addr bucket matching its current bind. + * + * Caller must hold both peer->ovpn->lock and peer->lock, and must have + * already dereferenced a valid (non-NULL) peer->bind, passed in as @bind. + */ +static void __ovpn_peer_hash_transp_addr(struct ovpn_peer *peer, + const struct ovpn_bind *bind) +{ + struct hlist_nulls_head *nhead; + size_t salen; + + lockdep_assert_held(&peer->ovpn->lock); + lockdep_assert_held(&peer->lock); + + if (WARN_ON_ONCE(!bind)) + return; + + /* peer may have been concurrently removed between the caller's + * initial lookup and our acquisition of ovpn->lock; skip the + * rehash so we don't re-insert a removed peer + */ + if (unlikely(hlist_unhashed(&peer->hash_entry_id))) + return; + + switch (bind->remote.in4.sin_family) { + case AF_INET: + salen = sizeof(struct sockaddr_in); + break; + case AF_INET6: + salen = sizeof(struct sockaddr_in6); + break; + default: + return; + } + + /* remove old hashing (no-op if entry is not currently linked) */ + hlist_nulls_del_init_rcu(&peer->hash_entry_transp_addr); + /* re-add with current transport address */ + nhead = ovpn_get_hash_head(peer->ovpn->peers->by_transp_addr, + &bind->remote, salen); + hlist_nulls_add_head_rcu(&peer->hash_entry_transp_addr, nhead); +} + +void ovpn_peer_hash_transp_addr(struct ovpn_peer *peer) +{ + struct ovpn_bind *bind; + + lockdep_assert_held(&peer->ovpn->lock); + + /* rehashing makes sense only in multipeer mode */ + if (peer->ovpn->mode != OVPN_MODE_MP) + return; + + spin_lock_bh(&peer->lock); + bind = rcu_dereference_protected(peer->bind, + lockdep_is_held(&peer->lock)); + __ovpn_peer_hash_transp_addr(peer, bind); + spin_unlock_bh(&peer->lock); +} + void ovpn_peer_hash_vpn_ip(struct ovpn_peer *peer) { struct hlist_nulls_head *nhead; diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h index 86c8cffada6d42..dfa5c0037e02b0 100644 --- a/drivers/net/ovpn/peer.h +++ b/drivers/net/ovpn/peer.h @@ -150,6 +150,7 @@ struct ovpn_peer *ovpn_peer_get_by_id(struct ovpn_priv *ovpn, u32 peer_id); struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_priv *ovpn, struct sk_buff *skb); void ovpn_peer_hash_vpn_ip(struct ovpn_peer *peer); +void ovpn_peer_hash_transp_addr(struct ovpn_peer *peer); bool ovpn_peer_check_by_src(struct ovpn_priv *ovpn, struct sk_buff *skb, struct ovpn_peer *peer); From 59aed1eb60d70678a53acccb0cb337a26ce6680e Mon Sep 17 00:00:00 2001 From: Antonio Quartulli Date: Tue, 28 Jul 2026 13:48:50 +0200 Subject: [PATCH 065/612] ovpn: ensure socket is owned by ovpn before deref sk_user_data Some subsystems, like BPF SOCKMAP, set sk_user_data without actually setting the encap_type. For this reason, we must make sure that the type is the one ovpn expects before dereferencing sk_user_data. Failing to do so may lead to out-of-bounds reads. Fixes: f6226ae7a0cd ("ovpn: introduce the ovpn_socket object") Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/socket.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/net/ovpn/socket.c b/drivers/net/ovpn/socket.c index 517caa64a4fef7..6cbeb2caaeecaa 100644 --- a/drivers/net/ovpn/socket.c +++ b/drivers/net/ovpn/socket.c @@ -162,6 +162,15 @@ struct ovpn_socket *ovpn_socket_new(struct socket *sock, struct ovpn_peer *peer) rcu_read_lock(); ovpn_sock = rcu_dereference_sk_user_data(sk); if (ovpn_sock) { + /* something else filled the sk_user_data without + * setting the encap_type. Reject the socket. + */ + if (!type) { + ovpn_sock = ERR_PTR(-EBUSY); + rcu_read_unlock(); + goto sock_release; + } + /* socket owned by another ovpn instance, we can't use it */ if (ovpn_sock->ovpn != peer->ovpn) { ovpn_sock = ERR_PTR(-EBUSY); From 3f012bdbabe211ccbc0c50ea5a1dbc60f8af1532 Mon Sep 17 00:00:00 2001 From: Antonio Quartulli Date: Tue, 28 Jul 2026 13:48:51 +0200 Subject: [PATCH 066/612] ovpn: zero-initialize sockaddr before learning a floated endpoint ovpn_peer_endpoints_update() builds the new remote endpoint in an on-stack struct sockaddr_storage that is left uninitialized. For IPv4 only sin_family/sin_addr/sin_port are written, leaving the 8-byte sin_zero padding as stack garbage (for IPv6, sin6_flowinfo is left uninitialized likewise). ovpn_peer_reset_sockaddr() -> ovpn_bind_from_sockaddr() then memcpy()s sizeof(struct sockaddr_in)/sizeof(struct sockaddr_in6) bytes - padding included - into bind->remote. That buffer is later hashed with jhash() over the same length to place the peer in the by_transp_addr table, so the garbage padding lands the floated peer in an essentially random bucket. Lockless lookups in ovpn_peer_get_by_transp_addr() build their key from a zero-initialized sockaddr_storage, compute a different bucket and fail to find the peer. This is also a plain use of uninitialized stack memory in jhash(). Build the floated endpoint with a designated initializer so the padding (sin_zero for IPv4, sin6_flowinfo for IPv6) is zeroed as part of the assignment. This keeps the padding out of the by_transp_addr hash key without memset-ing the whole sockaddr_storage on every received packet. Fixes: f0281c1d3732 ("ovpn: add support for updating local or remote UDP endpoint") Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/peer.c | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index a330892e82bf7f..33fb0a75e60066 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -222,9 +222,16 @@ void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *skb) */ local_ip = &ip_hdr(skb)->daddr; sa = (struct sockaddr_in *)&ss; - sa->sin_family = AF_INET; - sa->sin_addr.s_addr = ip_hdr(skb)->saddr; - sa->sin_port = udp_hdr(skb)->source; + /* use a designated initializer so the sin_zero padding + * is zeroed (it ends up in the by_transp_addr hash key) + * without memset-ing the whole sockaddr_storage on the + * RX fast path + */ + *sa = (struct sockaddr_in) { + .sin_family = AF_INET, + .sin_addr.s_addr = ip_hdr(skb)->saddr, + .sin_port = udp_hdr(skb)->source, + }; salen = sizeof(*sa); reset_cache = true; break; @@ -250,11 +257,19 @@ void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *skb) */ local_ip = &ipv6_hdr(skb)->daddr; sa6 = (struct sockaddr_in6 *)&ss; - sa6->sin6_family = AF_INET6; - sa6->sin6_addr = ipv6_hdr(skb)->saddr; - sa6->sin6_port = udp_hdr(skb)->source; - sa6->sin6_scope_id = ipv6_iface_scope_id(&ipv6_hdr(skb)->saddr, - skb->skb_iif); + /* use a designated initializer so the sin6_flowinfo + * padding is zeroed (it ends up in the by_transp_addr + * hash key) without memset-ing the whole + * sockaddr_storage on the RX fast path + */ + *sa6 = (struct sockaddr_in6) { + .sin6_family = AF_INET6, + .sin6_addr = ipv6_hdr(skb)->saddr, + .sin6_port = udp_hdr(skb)->source, + .sin6_scope_id = + ipv6_iface_scope_id(&ipv6_hdr(skb)->saddr, + skb->skb_iif), + }; salen = sizeof(*sa6); reset_cache = true; break; From b47a52dcd598a50207a33df304acdf45348a690f Mon Sep 17 00:00:00 2001 From: Antonio Quartulli Date: Tue, 28 Jul 2026 13:48:52 +0200 Subject: [PATCH 067/612] ovpn: hash floated peer by transport identity only The by_transp_addr table is keyed on the peer's remote transport address, but the float rehash hashed bind->remote directly, while the two other sites that touch the table build a clean key first: ovpn_peer_add_mp() and the lookup in ovpn_peer_get_by_transp_addr() both hash a sockaddr holding only family/address/port. For a link-local IPv6 peer, bind->remote carries sin6_scope_id (set from ipv6_iface_scope_id() when the endpoint is learned), and that field is folded into the jhash() over sizeof(struct sockaddr_in6). The lookup never sets sin6_scope_id, so after such a peer floats it is rehashed into a scope_id-dependent bucket that lookups (scope_id 0) never visit, making the peer unreachable through the by_transp_addr fallback. ovpn_peer_transp_match() only compares address and port, so the hash was keying on a field the match ignores. sin6_scope_id must stay in bind->remote because the TX path uses it as flowi6_oif, so it cannot just be cleared there. Instead build the hash key from family/address/port only, exactly like ovpn_peer_add_mp() and the lookup, so all three sites agree on the bucket. Fixes: f0281c1d3732 ("ovpn: add support for updating local or remote UDP endpoint") Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/peer.c | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index 33fb0a75e60066..eada414a9d9226 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -904,7 +904,10 @@ bool ovpn_peer_check_by_src(struct ovpn_priv *ovpn, struct sk_buff *skb, static void __ovpn_peer_hash_transp_addr(struct ovpn_peer *peer, const struct ovpn_bind *bind) { + struct sockaddr_storage sa = {}; struct hlist_nulls_head *nhead; + struct sockaddr_in6 *sa6; + struct sockaddr_in *sa4; size_t salen; lockdep_assert_held(&peer->ovpn->lock); @@ -920,12 +923,26 @@ static void __ovpn_peer_hash_transp_addr(struct ovpn_peer *peer, if (unlikely(hlist_unhashed(&peer->hash_entry_id))) return; + /* Build the hash key from the transport identity only + * (family/address/port), matching ovpn_peer_add_mp() and the lookup + * in ovpn_peer_get_by_transp_addr(). Hashing bind->remote directly + * would fold in sin6_scope_id (set on the float path but never by the + * lookup), scattering the peer into a bucket lookups cannot reach. + */ switch (bind->remote.in4.sin_family) { case AF_INET: - salen = sizeof(struct sockaddr_in); + sa4 = (struct sockaddr_in *)&sa; + sa4->sin_family = AF_INET; + sa4->sin_addr.s_addr = bind->remote.in4.sin_addr.s_addr; + sa4->sin_port = bind->remote.in4.sin_port; + salen = sizeof(*sa4); break; case AF_INET6: - salen = sizeof(struct sockaddr_in6); + sa6 = (struct sockaddr_in6 *)&sa; + sa6->sin6_family = AF_INET6; + sa6->sin6_addr = bind->remote.in6.sin6_addr; + sa6->sin6_port = bind->remote.in6.sin6_port; + salen = sizeof(*sa6); break; default: return; @@ -934,8 +951,8 @@ static void __ovpn_peer_hash_transp_addr(struct ovpn_peer *peer, /* remove old hashing (no-op if entry is not currently linked) */ hlist_nulls_del_init_rcu(&peer->hash_entry_transp_addr); /* re-add with current transport address */ - nhead = ovpn_get_hash_head(peer->ovpn->peers->by_transp_addr, - &bind->remote, salen); + nhead = ovpn_get_hash_head(peer->ovpn->peers->by_transp_addr, &sa, + salen); hlist_nulls_add_head_rcu(&peer->hash_entry_transp_addr, nhead); } From 0301aa324941698bec3dd455df1c5abc7afb10db Mon Sep 17 00:00:00 2001 From: Antonio Quartulli Date: Tue, 28 Jul 2026 13:48:53 +0200 Subject: [PATCH 068/612] ovpn: disable IPv4 redirects on MP interfaces ovpn_mp_alloc() tried to disable SEND_REDIRECTS on a multipeer interface, but it runs from ovpn_net_init() (->ndo_init), which register_netdevice() invokes before the NETDEV_REGISTER notifier chain. The IPv4 in_device is only created when that notifier reaches inetdev_event() -> inetdev_init(), so __in_dev_get_rtnl() always returned NULL at ndo_init time and the whole redirect-disabling block (both the per-device and the per-netns IPV4_DEVCONF_ALL write) was dead. MP interfaces therefore kept emitting ICMP redirects. Disabling redirects only once is not enough either: the IPv4 in_device is destroyed and recreated when the interface is moved to a different network namespace (NETDEV_UNREGISTER/NETDEV_REGISTER), and the newly created in_device inherits the destination namespace defaults, silently re-enabling SEND_REDIRECTS. Disable redirects from ovpn_net_open() (->ndo_open) instead: it runs every time the interface is brought up, including after the in_device has been recreated, so the setting is always re-applied. This mirrors what wireguard does in wg_open(). RTNL is held on the ndo_open() path, so __in_dev_get_rtnl() is safe. Fixes: 05003b408c20 ("ovpn: implement multi-peer support") Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/main.c | 50 ++++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c index 9d9a0ff690d662..3a04757d5c31e1 100644 --- a/drivers/net/ovpn/main.c +++ b/drivers/net/ovpn/main.c @@ -35,25 +35,11 @@ static void ovpn_priv_free(struct net_device *net) static int ovpn_mp_alloc(struct ovpn_priv *ovpn) { - struct in_device *dev_v4; int i; if (ovpn->mode != OVPN_MODE_MP) return 0; - dev_v4 = __in_dev_get_rtnl(ovpn->dev); - if (dev_v4) { - /* disable redirects as Linux gets confused by ovpn - * handling same-LAN routing. - * This happens because a multipeer interface is used as - * relay point between hosts in the same subnet, while - * in a classic LAN this would not be needed because the - * two hosts would be able to talk directly. - */ - IN_DEV_CONF_SET(dev_v4, SEND_REDIRECTS, false); - IPV4_DEVCONF_ALL(dev_net(ovpn->dev), SEND_REDIRECTS) = false; - } - /* the peer container is fairly large, therefore we allocate it only in * MP mode */ @@ -97,9 +83,38 @@ static void ovpn_net_uninit(struct net_device *dev) gro_cells_destroy(&ovpn->gro_cells); } +static int ovpn_net_open(struct net_device *dev) +{ + struct ovpn_priv *ovpn = netdev_priv(dev); + struct in_device *dev_v4; + + /* the IPv4 in_device (and thus its config) is recreated whenever the + * interface is moved to a new netns, so redirects must be disabled on + * every bring-up rather than once at creation time, otherwise the + * setting is silently lost after such a move + */ + if (ovpn->mode == OVPN_MODE_MP) { + dev_v4 = __in_dev_get_rtnl(dev); + if (dev_v4) { + /* disable redirects as Linux gets confused by ovpn + * handling same-LAN routing. + * This happens because a multipeer interface is used as + * relay point between hosts in the same subnet, while + * in a classic LAN this would not be needed because the + * two hosts would be able to talk directly. + */ + IN_DEV_CONF_SET(dev_v4, SEND_REDIRECTS, false); + IPV4_DEVCONF_ALL(dev_net(dev), SEND_REDIRECTS) = false; + } + } + + return 0; +} + static const struct net_device_ops ovpn_netdev_ops = { .ndo_init = ovpn_net_init, .ndo_uninit = ovpn_net_uninit, + .ndo_open = ovpn_net_open, .ndo_start_xmit = ovpn_net_xmit, }; @@ -183,6 +198,7 @@ static int ovpn_newlink(struct net_device *dev, struct ovpn_priv *ovpn = netdev_priv(dev); struct nlattr **data = params->data; enum ovpn_mode mode = OVPN_MODE_P2P; + int ret; if (data && data[IFLA_OVPN_MODE]) { mode = nla_get_u8(data[IFLA_OVPN_MODE]); @@ -207,7 +223,11 @@ static int ovpn_newlink(struct net_device *dev, else netif_carrier_off(dev); - return register_netdevice(dev); + ret = register_netdevice(dev); + if (ret < 0) + return ret; + + return 0; } static size_t ovpn_get_size(const struct net_device *dev) From 4680c0ebd958fc18e53c8b91d80436b236a8fc09 Mon Sep 17 00:00:00 2001 From: Antonio Quartulli Date: Tue, 28 Jul 2026 13:48:54 +0200 Subject: [PATCH 069/612] ovpn: ensure TCP vars are initialized first Netlink calls may access TCP global vars (i.e. when attaching a TCP socket), therefore we need to make sure the latters are initialized beforehand. For this reason move the global TCP initialization at the top of the module init function. Fixes: 11851cbd60ea ("ovpn: implement TCP transport") Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/main.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c index 3a04757d5c31e1..168cfe9b59a98a 100644 --- a/drivers/net/ovpn/main.c +++ b/drivers/net/ovpn/main.c @@ -260,8 +260,11 @@ static struct rtnl_link_ops ovpn_link_ops = { static int __init ovpn_init(void) { - int err = rtnl_link_register(&ovpn_link_ops); + int err; + ovpn_tcp_init(); + + err = rtnl_link_register(&ovpn_link_ops); if (err) { pr_err("ovpn: can't register rtnl link ops: %d\n", err); return err; @@ -273,8 +276,6 @@ static int __init ovpn_init(void) goto unreg_rtnl; } - ovpn_tcp_init(); - return 0; unreg_rtnl: From 26ba17d845193dac4921ae1ab280d28d1938052e Mon Sep 17 00:00:00 2001 From: Qingfang Deng Date: Fri, 24 Jul 2026 09:49:26 +0800 Subject: [PATCH 070/612] ovpn: fix incorrect use of rcu_access_pointer() rcu_access_pointer() should only be used to test the value of a pointer, not to dereference it. As it's in a spin_lock_bh() critical section, use rcu_dereference_bh() instead, avoiding an extra rcu_read_lock(). Fixes: f6226ae7a0cd ("ovpn: introduce the ovpn_socket object") Signed-off-by: Qingfang Deng Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/peer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index eada414a9d9226..b0519f9840d830 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -1249,7 +1249,7 @@ static void ovpn_peer_release_p2p(struct ovpn_priv *ovpn, struct sock *sk, } if (sk) { - ovpn_sock = rcu_access_pointer(peer->sock); + ovpn_sock = rcu_dereference_bh(peer->sock); if (!ovpn_sock || ovpn_sock->sk != sk) { spin_unlock_bh(&ovpn->lock); return; From 2b8f13d3c7e26c46c20d9e367904cf01729c88e6 Mon Sep 17 00:00:00 2001 From: Osama Abdelkader Date: Fri, 24 Jul 2026 19:26:20 +0200 Subject: [PATCH 071/612] drm/panthor: skip zero-sized firmware sections panthor_fw_load_section_entry() skips BO creation when the firmware section VA range is empty. If such a section is added to the firmware section list, section->mem is left as NULL. Later reload and unplug paths iterate over all firmware sections and dereference section->mem, which can lead to a NULL pointer dereference. Zero-sized firmware sections are valid, so accept them as no-op entries but skip adding them to the section list. Fixes: 2718d91816ee ("drm/panthor: Add the FW logical block") Cc: stable@vger.kernel.org Signed-off-by: Osama Abdelkader Reviewed-by: Liviu Dudau Reviewed-by: Steven Price Link: https://patch.msgid.link/20260724172621.63046-1-osama.abdelkader@gmail.com Signed-off-by: Steven Price --- drivers/gpu/drm/panthor/panthor_fw.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/panthor/panthor_fw.c b/drivers/gpu/drm/panthor/panthor_fw.c index e45933e333b5b6..fa88b2f9da76dc 100644 --- a/drivers/gpu/drm/panthor/panthor_fw.c +++ b/drivers/gpu/drm/panthor/panthor_fw.c @@ -603,6 +603,9 @@ static int panthor_fw_load_section_entry(struct panthor_device *ptdev, return -EINVAL; } + if (!section_size) + return 0; + name_len = iter->size - iter->offset; section = drmm_kzalloc(&ptdev->base, sizeof(*section), GFP_KERNEL); From 246ac114f485c2affb454240f3ea4fabfce22456 Mon Sep 17 00:00:00 2001 From: Luca Fresi Date: Wed, 22 Jul 2026 00:24:04 +0200 Subject: [PATCH 072/612] serial: sc16is7xx: enable THRI before filling TX FIFO sc16is7xx_handle_tx() currently requests the THRI enable only after it has filled the TX FIFO. The request is asynchronous because the IER update is performed later by reg_work. The SC16IS7xx generates a THRI interrupt when the TX FIFO crosses its trigger level. If the FIFO drains past that level before reg_work enables THRI, the chip does not generate a new interrupt. Characters remain queued indefinitely even though the hardware FIFO is empty. This was observed on an SC16IS752 while both UART channels were active. During the stall the software TX buffer remained non-empty while TXLVL reported 64 bytes free, LSR reported THR and transmitter empty, IER had THRI enabled, and IIR reported no interrupt pending. Enable THRI synchronously before filling the FIFO so the threshold crossing cannot be missed. Fixes: cc4c1d05eb10 ("sc16is7xx: Properly resume TX after stop") Cc: stable Signed-off-by: Luca Fresi Link: https://patch.msgid.link/20260721222404.204746-1-luca.fresi@bithiatec.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sc16is7xx.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c index 6c97953d593b57..9b152ead050fa7 100644 --- a/drivers/tty/serial/sc16is7xx.c +++ b/drivers/tty/serial/sc16is7xx.c @@ -827,6 +827,9 @@ static void sc16is7xx_tx_proc(struct kthread_work *ws) msleep(port->rs485.delay_rts_before_send); guard(mutex)(&one->lock); + sc16is7xx_port_update(port, SC16IS7XX_IER_REG, + SC16IS7XX_IER_THRI_BIT, + SC16IS7XX_IER_THRI_BIT); sc16is7xx_handle_tx(port); } From e2fe6a0efecbef00e3ecc2db64dd5afa8c212b41 Mon Sep 17 00:00:00 2001 From: Cunhao Lu <1579567540@qq.com> Date: Mon, 27 Jul 2026 14:25:22 +0800 Subject: [PATCH 073/612] serial: 8250_dma: Clear stale RX state on shutdown serial8250_release_dma() terminates RX DMA and releases the channel, but leaves rx_running set. If the port is closed while an RX transfer is active, the stale state remains while rxchan is NULL until the channel is requested again on the next open. The DesignWare BUSY workaround added by commit a7b9ce39fbe4 ("serial: 8250_dw: Ensure BUSY is deasserted") calls serial8250_rx_dma_flush() from the LCR write path during startup. This happens before serial8250_request_dma() obtains a new RX channel. On reopen, the stale rx_running state therefore makes the flush path pass a NULL channel to dmaengine_pause(), causing a kernel Oops. Clear rx_running after terminating RX DMA, matching the TX cleanup. Also make the flush helper return if the DMA object or RX channel is not available so startup and teardown paths cannot pass a NULL channel to the DMAengine API. Fixes: 0fcb7901f9d6 ("tty: serial: 8250_dma: keep own book keeping about RX transfers") Cc: stable Signed-off-by: Cunhao Lu <1579567540@qq.com> Link: https://patch.msgid.link/tencent_9EE2945F4C933B4D810C73C2D7485E000F06@qq.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_dma.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/tty/serial/8250/8250_dma.c b/drivers/tty/serial/8250/8250_dma.c index 3b6452e759d5b5..5a83e5269b4151 100644 --- a/drivers/tty/serial/8250/8250_dma.c +++ b/drivers/tty/serial/8250/8250_dma.c @@ -211,11 +211,12 @@ void serial8250_rx_dma_flush(struct uart_8250_port *p) { struct uart_8250_dma *dma = p->dma; - if (dma->rx_running) { - dmaengine_pause(dma->rxchan); - __dma_rx_complete(p); - dmaengine_terminate_async(dma->rxchan); - } + if (!dma || !dma->rxchan || !dma->rx_running) + return; + + dmaengine_pause(dma->rxchan); + __dma_rx_complete(p); + dmaengine_terminate_async(dma->rxchan); } EXPORT_SYMBOL_GPL(serial8250_rx_dma_flush); @@ -324,6 +325,7 @@ void serial8250_release_dma(struct uart_8250_port *p) /* Release RX resources */ dmaengine_terminate_sync(dma->rxchan); + dma->rx_running = 0; dma_free_coherent(dma->rxchan->device->dev, dma->rx_size, dma->rx_buf, dma->rx_addr); dma_release_channel(dma->rxchan); From e3c04834ae1ab5e9cfbe8ac54ec734aa4774249d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Sebastian=20G=C3=B6tte?= Date: Wed, 29 Jul 2026 19:41:05 +0200 Subject: [PATCH 074/612] serial: qcom-geni: fix TX DMA buffer flush MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When transmit flushing a qcom-geni UART during an ongoing TX DMA, the UART gets stuck infinitely repeating corrupted TX DMA frames. The DMA-mode uart_ops does not provide a flush_buffer callback, so an in-flight transfer can complete after serial core has reset the transmit kfifo, underflowing its length and resubmitting page-sized transfers indefinitely. Add one that stops the transfer and clears tx_remaining and tx_queued. The stop path was also broken: it unmapped the buffer while the serial engine could still read it, and never reset the TX DMA state machine. Cancel the main sequencer command first, then reset the state machine and wait for it before unmapping. Drop the early return so a pending mapping is also cleaned up when the main command is inactive. The bug can be triggered from userspace with a large write immediately followed by TCOFLUSH. A following tcdrain will hang forever. The bug was reproduced and this fix was validated on Arduino Uno Q (QRB2210) using /dev/ttyHS1. Assisted-by: Claude:claude-5-opus Codex:gpt-5 Signed-off-by: Jan Sebastian Götte Fixes: 2aaa43c70778 ("tty: serial: qcom-geni-serial: add support for serial engine DMA") Cc: stable Reviewed-by: Praveen Talari Link: https://patch.msgid.link/20260729174105.21838-2-git@jaseg.de Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/qcom_geni_serial.c | 43 ++++++++++++++------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c index 7ead87b4eb65b2..1e39122ec09f4d 100644 --- a/drivers/tty/serial/qcom_geni_serial.c +++ b/drivers/tty/serial/qcom_geni_serial.c @@ -158,6 +158,7 @@ static const struct uart_ops qcom_geni_uart_pops; static struct uart_driver qcom_geni_console_driver; static struct uart_driver qcom_geni_uart_driver; +static void qcom_geni_serial_stop_tx_dma(struct uart_port *uport); static void __qcom_geni_serial_cancel_tx_cmd(struct uart_port *uport); static void qcom_geni_serial_cancel_tx_cmd(struct uart_port *uport); static int qcom_geni_serial_port_setup(struct uart_port *uport); @@ -636,35 +637,34 @@ static unsigned int qcom_geni_serial_tx_empty(struct uart_port *uport) return !readl(uport->membase + SE_GENI_TX_FIFO_STATUS); } +static void qcom_geni_serial_flush_buffer_dma(struct uart_port *uport) +{ + struct qcom_geni_serial_port *port = to_dev_port(uport); + + qcom_geni_serial_stop_tx_dma(uport); + port->tx_remaining = 0; + port->tx_queued = 0; +} + static void qcom_geni_serial_stop_tx_dma(struct uart_port *uport) { struct qcom_geni_serial_port *port = to_dev_port(uport); - bool done; - if (!qcom_geni_serial_main_active(uport)) - return; + if (qcom_geni_serial_main_active(uport)) + __qcom_geni_serial_cancel_tx_cmd(uport); if (port->tx_dma_addr) { + writel(1, uport->membase + SE_DMA_TX_FSM_RST); + if (!qcom_geni_serial_poll_bit(uport, SE_DMA_TX_IRQ_STAT, + TX_RESET_DONE, true)) + dev_err_ratelimited(uport->dev, "TX DMA reset failed"); + writel(TX_RESET_DONE | TX_DMA_DONE, + uport->membase + SE_DMA_TX_IRQ_CLR); + geni_se_tx_dma_unprep(&port->se, port->tx_dma_addr, port->tx_remaining); port->tx_dma_addr = 0; - port->tx_remaining = 0; } - - geni_se_cancel_m_cmd(&port->se); - - done = qcom_geni_serial_poll_bit(uport, SE_GENI_M_IRQ_STATUS, - M_CMD_CANCEL_EN, true); - if (!done) { - geni_se_abort_m_cmd(&port->se); - done = qcom_geni_serial_poll_bit(uport, SE_GENI_M_IRQ_STATUS, - M_CMD_ABORT_EN, true); - if (!done) - dev_err_ratelimited(uport->dev, "M_CMD_ABORT_EN not set"); - writel(M_CMD_ABORT_EN, uport->membase + SE_GENI_M_IRQ_CLEAR); - } - - writel(M_CMD_CANCEL_EN, uport->membase + SE_GENI_M_IRQ_CLEAR); } static void qcom_geni_serial_start_tx_dma(struct uart_port *uport) @@ -1180,7 +1180,7 @@ static void qcom_geni_serial_shutdown(struct uart_port *uport) uart_port_unlock_irq(uport); } -static void qcom_geni_serial_flush_buffer(struct uart_port *uport) +static void qcom_geni_serial_flush_buffer_fifo(struct uart_port *uport) { qcom_geni_serial_cancel_tx_cmd(uport); } @@ -1769,7 +1769,7 @@ static const struct uart_ops qcom_geni_console_pops = { .request_port = qcom_geni_serial_request_port, .config_port = qcom_geni_serial_config_port, .shutdown = qcom_geni_serial_shutdown, - .flush_buffer = qcom_geni_serial_flush_buffer, + .flush_buffer = qcom_geni_serial_flush_buffer_fifo, .type = qcom_geni_serial_get_type, .set_mctrl = qcom_geni_serial_set_mctrl, .get_mctrl = qcom_geni_serial_get_mctrl, @@ -1792,6 +1792,7 @@ static const struct uart_ops qcom_geni_uart_pops = { .request_port = qcom_geni_serial_request_port, .config_port = qcom_geni_serial_config_port, .shutdown = qcom_geni_serial_shutdown, + .flush_buffer = qcom_geni_serial_flush_buffer_dma, .type = qcom_geni_serial_get_type, .set_mctrl = qcom_geni_serial_set_mctrl, .get_mctrl = qcom_geni_serial_get_mctrl, From 0680cbbf39ca61c70be16141b5259f822e7cdb3b Mon Sep 17 00:00:00 2001 From: Boris Burkov Date: Mon, 27 Jul 2026 15:23:30 -0700 Subject: [PATCH 075/612] btrfs: trigger cow fixup via dirty_folio() The problem scenario: If we have a folio mmapped shared and then somebody does a dio read with that folio as the read destination, then it is possible that the dio will see a dirty destination page when it starts (and thus skip dirtying and just GUP pin it) but then while it is doing the read, btrfs finishes writing it back and by the endio, the folio is clean. In that case, the dio read must re-dirty the folio with aops->dirty_folio(): btrfs_check_read_bio() |- __iomap_dio_bio_end_io() from btrfs_bio_end_io() |- bio_check_pages_dirty() |- bio_dirty_fn() |- bio_release_pages(bio, true) |- __bio_release_pages(bio, mark_dirty == true) |- folio_lock() |- folio_mark_dirty() |- aops->dirty_folio() |- folio_unlock() A data block normally moves through writeback as follows: TASK folio_lock write clean -> dirty bit + delalloc folio_unlock WRITEBACK for-each-dirty-folio: folio_lock run_delalloc delalloc consumed -> dirty bit + OE submission dirty bit consumed -> writeback bit + OE folio_unlock ENDIO endio OE bytes accounted OE finish writeback -> clean; destroy OE Three critical invariants that this path maintains are: I1. Any dirty block is covered by delalloc xor an ordered extent I2. Any dirty block covered by an OE will be submitted into that OE I3. Any dirty block already submitted into an OE will not be submitted again into the same OE. These ensure that the block will be written exactly once. It is clear that not reserving delalloc for the re-dirty case violates I1. This situation, even without bs < folio_size, has long required btrfs to fixup such dirty pages during writeback with an asynchronous worker that is allowed to do this expensive work and writeback does not proceed for a folio while it is doing this work. Commit 247e743cbe6e ("Btrfs: Use async helpers to deal with pages that have been improperly dirtied") introduced the COW fixup to catch exactly this class at writeback, way back in 2008. Since then, there have been many advances to prevent most of the causes of such re-dirtying and we thought we could get away with removing the annoying cow-fixup in the hope of simplifying writeback for large folio support. Commit b2a9f217ad3f ("btrfs: remove the COW fixup mechanism") Commit 4927b141877c ("btrfs: remove folio ordered flag and subpage bitmap") Since it turns out this assumption was incorrect, as evidenced by the report and attendant reproducers, we must reintroduce the fixup concept. This is of course critically further complicated by bs < folio_size. In that case, rather than just a folio dirty bit, we have a bitmap for the dirty blocks in the folio. And the (also broken) invariant is: I4. folio dirty IFF at least one block bitmap dirty. The original report of a stall on a misinterpreted empty bitmap is exactly evidence of a violation of I4. It is exactly because of bs < folio_size we don't want to simply revert the removal patches. The original fixup was not properly bs < folio_size aware, which motivated removal in the first place. So we wish to build a bs < folio_size aware fixup. One other important detail from the old design, any normal write that happens after a re-dirty but before a fixup is racing with the cow fixup to do the delalloc reservation, therefore it must cancel the fixup state. If it arrives after the reservation exists, it will be a normal dirty overwrite. This critically informs the design in a pretty clear way. fixup requiring re-dirty has folio granularity, while cancellation has delalloc (block) granularity so while we only ever produce fixup in chunks of folios, we must be able to clear it in blocks. Therefore we must track the blocks needing fixup at block granularity. The obvious way to do this is with a new bitmap in btrfs_folio_state, but it is desirable to avoid that if possible. Unfortunately, I don't think it is possible and the reason is subtle and leans on a sort of extreme reproducer, but I think can be explained relatively succinctly. Consider a folio whose two halves will land in different ordered extents (can be accomplished with tricks using nodatasum) and a dio read is running with it as the shared mmap destination. 1. The front half: a. folio comes clean on a normal write b. dio read completes into the folio marking it fixup. c. a write comes for the previous folio for a range extending into this folio, this is a cancellation of the fixup which reserves space. d. writeback runs on the range *not* overlapping the folio. This half remains dirty but is now covered by an OE and is awaiting writeback running on its range to be submitted and finish the OE. 2. The back half: a. the folio is part of an OE that gets far enough along to clear writeback. b. dio read completes into the folio marking it fixup. After this, the folio's front half is dirty in the "normal" sense, it needs to be submitted to the OE waiting for it. It's a cancelled fixup. Meanwhile, the second half is a true fresh fixup. So at this point if we run writeback on this folio, we genuinely can't know what to do without block level information. If we submit it, we submit unreserved dirty from the back half. If we don't, we will never finish the OE waiting for it. So it's either a corruption or a deadlock. Thus, the full high level design picture: - btrfs_data_dirty_folio(): For out of band non-reserving dirties, mark still-clean blocks inside EOF dirty and set their fixup bits (the event carries no range, so every clean block is suspect). Already-dirty blocks are covered or pending and are left alone. - Writeback: skip fixup blocks and enqueue work for them - writepage_fixup(): for each fixup block do the fixup reservation in a worker, after which the blocks can be written back normally. - Typical reserving write paths cancel fixup state for the ranges they cover with btrfs_folio_cancel_fixup() Link: https://lore.kernel.org/linux-btrfs/20260721191152.101118-1-borntraeger@linux.ibm.com/ Assisted-by: LLM Reviewed-by: Qu Wenruo Signed-off-by: Boris Burkov Signed-off-by: David Sterba --- fs/btrfs/btrfs_inode.h | 1 + fs/btrfs/disk-io.c | 7 +- fs/btrfs/extent_io.c | 113 ++++++++++++++++++ fs/btrfs/fs.h | 12 ++ fs/btrfs/inode.c | 200 +++++++++++++++++++++++++++++++- fs/btrfs/subpage.c | 216 ++++++++++++++++++++++++++++++++++- fs/btrfs/subpage.h | 41 ++++++- include/trace/events/btrfs.h | 35 ++++++ 8 files changed, 613 insertions(+), 12 deletions(-) diff --git a/fs/btrfs/btrfs_inode.h b/fs/btrfs/btrfs_inode.h index 7fdc6c3fd0666c..1082fa92c1457a 100644 --- a/fs/btrfs/btrfs_inode.h +++ b/fs/btrfs/btrfs_inode.h @@ -600,6 +600,7 @@ int btrfs_prealloc_file_range_trans(struct inode *inode, loff_t actual_len, u64 *alloc_hint); int btrfs_run_delalloc_range(struct btrfs_inode *inode, struct folio *locked_folio, u64 start, u64 end, struct writeback_control *wbc); +void btrfs_queue_writepage_fixup(struct btrfs_inode *inode, struct folio *folio); int btrfs_encoded_io_compression_from_extent(struct btrfs_fs_info *fs_info, int compress_type); int btrfs_encoded_read_regular_fill_pages(struct btrfs_inode *inode, diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c index 36332df9a0f1c0..6bb70c43a63f64 100644 --- a/fs/btrfs/disk-io.c +++ b/fs/btrfs/disk-io.c @@ -1760,6 +1760,8 @@ static int read_backup_root(struct btrfs_fs_info *fs_info, u8 priority) /* helper to cleanup workers */ static void btrfs_stop_all_workers(struct btrfs_fs_info *fs_info) { + if (fs_info->fixup_workers) + destroy_workqueue(fs_info->fixup_workers); btrfs_destroy_workqueue(fs_info->delalloc_workers); btrfs_destroy_workqueue(fs_info->workers); if (fs_info->endio_workers) @@ -1967,6 +1969,9 @@ static int btrfs_init_workqueues(struct btrfs_fs_info *fs_info) fs_info->caching_workers = btrfs_alloc_workqueue(fs_info, "cache", flags, max_active, 0); + fs_info->fixup_workers = + alloc_ordered_workqueue("btrfs-fixup", ordered_flags); + fs_info->endio_workers = alloc_workqueue("btrfs-endio", flags, max_active); fs_info->endio_meta_workers = @@ -1992,7 +1997,7 @@ static int btrfs_init_workqueues(struct btrfs_fs_info *fs_info) fs_info->endio_workers && fs_info->endio_meta_workers && fs_info->endio_write_workers && fs_info->endio_freespace_worker && fs_info->rmw_workers && - fs_info->caching_workers && + fs_info->caching_workers && fs_info->fixup_workers && fs_info->delayed_workers && fs_info->qgroup_rescan_workers && fs_info->discard_ctl.discard_workers)) { return -ENOMEM; diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c index de5785117a47f0..f032f0858f40f3 100644 --- a/fs/btrfs/extent_io.c +++ b/fs/btrfs/extent_io.c @@ -1440,6 +1440,115 @@ static bool find_next_delalloc_bitmap(struct folio *folio, return true; } +/* + * Debug checks for fixup selection logic to help ensure the invariants + * we expect for fixup marking hold in practice. + * + * - A dirty block without a fixup bit is covered by delalloc or a running + * ordered extent (it was dirtied by a reserving write path). + * - A block with a fixup bit is never covered by delalloc: every delalloc + * setter holds the folio lock and cancels the fixup state of the blocks + * it covers (btrfs_folio_set_dirty()) before releasing it. + */ +static void debug_check_writepage_fixup(struct btrfs_inode *inode, u64 start, + u32 len, bool needs_fixup) +{ + struct btrfs_ordered_extent *ordered; + bool delalloc; + + if (!IS_ENABLED(CONFIG_BTRFS_DEBUG)) + return; + + delalloc = btrfs_test_range_bit_exists(&inode->io_tree, start, + start + len - 1, EXTENT_DELALLOC); + if (needs_fixup) { + if (unlikely(delalloc)) + DEBUG_WARN("writeback: delalloc and fixup conflict. ino %llu start %llu", + btrfs_ino(inode), start); + } else { + if (delalloc) + return; + + ordered = btrfs_lookup_ordered_range(inode, start, len); + if (unlikely(!ordered)) + DEBUG_WARN("dirty block, no delalloc, fixup, ordered. ino %llu start %llu", + btrfs_ino(inode), start); + else + btrfs_put_ordered_extent(ordered); + } +} + +/* + * Handle folios dirtied without a delalloc reservation, e.g. + * O_DIRECT read into a MAP_SHARED mapping dirtying via set_page_dirty_lock(). + * + * btrfs_data_dirty_folio() records the affected blocks in the fixup bitmap + * and the folio fixup flag and we check them here in writeback. + * + * Don't submit such blocks and queue work for the fixup worker to reserve + * space for them so that they can be submitted properly by writeback. + * + * Return 1 if the folio needed fixup, 0 if not, and a negative error code + * on error. + */ +static noinline_for_stack int writepage_fixup(struct btrfs_inode *inode, + struct folio *folio, + struct btrfs_bio_ctrl *bio_ctrl) +{ + struct btrfs_fs_info *fs_info = inode_to_fs_info(&inode->vfs_inode); + const unsigned int blocks_per_folio = btrfs_blocks_per_folio(fs_info, folio); + const u32 sectorsize = fs_info->sectorsize; + const u64 page_start = folio_pos(folio); + bool found_fixup = false; + unsigned int bit; + + /* + * A folio was dirtied without calling aops->dirty_folio() which we + * explicitly assert is not allowed. + */ + if (unlikely(bitmap_empty(bio_ctrl->submit_bitmap, blocks_per_folio))) { + DEBUG_WARN(); + btrfs_err_rl(fs_info, + "root %lld ino %llu folio %llu is dirty with an empty dirty bitmap", + btrfs_root_id(inode->root), btrfs_ino(inode), + folio_pos(folio)); + return -EUCLEAN; + } + + /* Cheap check on the folio flag. Set iff the fixup bitmap is non-empty. */ + if (likely(!folio_test_fixup_pending(folio))) + return 0; + + for_each_set_bit(bit, bio_ctrl->submit_bitmap, blocks_per_folio) { + const u64 start = page_start + (bit << fs_info->sectorsize_bits); + const bool needs_fixup = btrfs_folio_test_fixup(fs_info, folio, + start, sectorsize); + + debug_check_writepage_fixup(inode, start, sectorsize, needs_fixup); + if (needs_fixup) { + bitmap_clear(bio_ctrl->submit_bitmap, bit, 1); + found_fixup = true; + } + } + if (likely(found_fixup)) { + btrfs_queue_writepage_fixup(inode, folio); + folio_redirty_for_writepage(bio_ctrl->wbc, folio); + if (bitmap_empty(bio_ctrl->submit_bitmap, blocks_per_folio)) { + folio_unlock(folio); + return 1; + } + return 0; + } + /* We should always find fixup if the folio fixup flag was set. */ + DEBUG_WARN(); + btrfs_err_rl(fs_info, + "root %lld ino %llu folio %llu is fixup with an empty fixup bitmap", + btrfs_root_id(inode->root), btrfs_ino(inode), + folio_pos(folio)); + + return -EUCLEAN; +} + /* * Do all of the delayed allocation setup. * @@ -1492,6 +1601,10 @@ static noinline_for_stack int writepage_delalloc(struct btrfs_inode *inode, /* Save the dirty bitmap as our submission bitmap will be a subset of it. */ btrfs_copy_subpage_dirty_bitmap(fs_info, folio, bio_ctrl->submit_bitmap); + ret = writepage_fixup(inode, folio, bio_ctrl); + if (ret) + return ret; + for_each_set_bitrange(start_bit, end_bit, bio_ctrl->submit_bitmap, blocks_per_folio) { u64 start = page_start + (start_bit << fs_info->sectorsize_bits); diff --git a/fs/btrfs/fs.h b/fs/btrfs/fs.h index 7ee9ec2b0efba1..f7f343fbe732fa 100644 --- a/fs/btrfs/fs.h +++ b/fs/btrfs/fs.h @@ -713,6 +713,8 @@ struct btrfs_fs_info { struct btrfs_workqueue *endio_write_workers; struct btrfs_workqueue *endio_freespace_worker; struct btrfs_workqueue *caching_workers; + + struct workqueue_struct *fixup_workers; struct btrfs_workqueue *delayed_workers; struct task_struct *transaction_kthread; @@ -1200,6 +1202,16 @@ static inline void btrfs_wake_unfinished_drop(struct btrfs_fs_info *fs_info) clear_and_wake_up_bit(BTRFS_FS_UNFINISHED_DROPS, &fs_info->flags); } +/* + * We use the folio owner_2 flag to indicate the folio has blocks that were + * dirtied without a space reservation and need the writepage fixup before + * writeback. For bs < folio_size the fixup bitmap tracks the affected + * blocks. + */ +#define folio_test_fixup_pending(folio) folio_test_owner_2(folio) +#define folio_set_fixup_pending(folio) folio_set_owner_2(folio) +#define folio_clear_fixup_pending(folio) folio_clear_owner_2(folio) + #define BTRFS_FS_ERROR(fs_info) (READ_ONCE((fs_info)->fs_error)) #define BTRFS_FS_LOG_CLEANUP_ERROR(fs_info) \ diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 04ea10b61bbb79..98b31a090626ee 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -2812,6 +2812,163 @@ int btrfs_set_extent_delalloc(struct btrfs_inode *inode, u64 start, u64 end, EXTENT_DELALLOC | extra_bits, cached_state); } +struct btrfs_writepage_fixup { + struct folio *folio; + struct btrfs_inode *inode; + struct work_struct work; +}; + +/* + * Do the real fixup work of reserving space for the blocks a folio's fixup + * state records. Queued by writepage_fixup() when writeback found the bits set. + * + * Since the fixup can be cancelled by a task dirtying with a reservation, we must + * re-check the state of fixup under the folio lock. + */ +static void btrfs_writepage_fixup_worker(struct work_struct *work) +{ + struct btrfs_writepage_fixup *fixup = + container_of(work, struct btrfs_writepage_fixup, work); + struct extent_state *cached_state = NULL; + struct extent_changeset *data_reserved = NULL; + unsigned long delalloc_bitmap[BITS_TO_LONGS(BTRFS_MAX_BLOCKS_PER_FOLIO)] = { 0 }; + struct folio *folio = fixup->folio; + struct btrfs_inode *inode = fixup->inode; + struct btrfs_fs_info *fs_info = inode->root->fs_info; + const unsigned int blocks_per_folio = btrfs_blocks_per_folio(fs_info, folio); + const u32 sectorsize = fs_info->sectorsize; + const u64 page_start = folio_pos(folio); + const u64 page_end = folio_next_pos(folio) - 1; + unsigned int start_bit; + unsigned int end_bit; + unsigned int bit; + bool reserved; + int ret; + + /* + * We would prefer to reserve under the folio lock when we know exactly + * which blocks need a reservation. Unfortunately, since the reservation + * can go into flushers which can go into writeback, which takes folio + * locks, that is not possible. Therefore, we have to reserve for the + * whole folio here, then release what we didn't end up needing once we + * figure it out. + * + * Also note the slightly strange error checking. If fixup is actually + * not set, we don't need to mark an error on the mapping. So hang on to + * ret until after we lock and find out if we actually care. + */ + ret = btrfs_delalloc_reserve_space(inode, &data_reserved, page_start, + folio_size(folio)); + reserved = (ret == 0); +again: + folio_lock(folio); + + if (!folio->mapping || !folio_test_fixup_pending(folio)) { + ret = 0; + goto out; + } + if (ret) + goto out; + + btrfs_lock_extent(&inode->io_tree, page_start, page_end, &cached_state); + + for (bit = 0; bit < blocks_per_folio; bit++) { + struct btrfs_ordered_extent *ordered; + const u64 start = page_start + (bit << fs_info->sectorsize_bits); + + if (test_bit(bit, delalloc_bitmap)) + continue; + if (!btrfs_folio_test_fixup(fs_info, folio, start, sectorsize)) + continue; + /* + * Any task that sets EXTENT_DELALLOC clears the fixup bits + * under the folio lock, so it should be impossible to observe + * both under the lock. Setting delalloc twice would wrongly + * double account the space. + */ + if (IS_ENABLED(CONFIG_BTRFS_DEBUG) && + unlikely(btrfs_test_range_bit_exists(&inode->io_tree, start, + start + sectorsize - 1, + EXTENT_DELALLOC))) { + DEBUG_WARN("fixup worker: delalloc and fixup conflict. ino %llu start %llu", + btrfs_ino(inode), start); + btrfs_folio_clear_fixup(fs_info, folio, start, sectorsize); + continue; + } + ordered = btrfs_lookup_ordered_range(inode, start, sectorsize); + if (ordered) { + trace_btrfs_writepage_fixup_defer(inode, ordered); + btrfs_unlock_extent(&inode->io_tree, page_start, + page_end, &cached_state); + folio_unlock(folio); + btrfs_start_ordered_extent(ordered); + btrfs_put_ordered_extent(ordered); + goto again; + } + ret = btrfs_set_extent_delalloc(inode, start, + start + sectorsize - 1, 0, + &cached_state); + if (ret) + break; + trace_btrfs_writepage_fixup_reserve(inode, start, sectorsize); + btrfs_folio_clear_fixup(fs_info, folio, start, sectorsize); + set_bit(bit, delalloc_bitmap); + } + + btrfs_unlock_extent(&inode->io_tree, page_start, page_end, &cached_state); +out: + if (ret < 0) { + /* Failure here is analogous to failure in writeback. */ + mapping_set_error(folio->mapping, ret); + btrfs_folio_clear_fixup_dirty(fs_info, folio, page_start, + folio_size(folio)); + } + if (reserved) { + btrfs_delalloc_release_extents(inode, folio_size(folio)); + for_each_clear_bitrange(start_bit, end_bit, delalloc_bitmap, + blocks_per_folio) + btrfs_delalloc_release_space(inode, data_reserved, + page_start + (start_bit << fs_info->sectorsize_bits), + (end_bit - start_bit) << fs_info->sectorsize_bits, + true); + } + folio_unlock(folio); + folio_put(folio); + kfree(fixup); + extent_changeset_free(data_reserved); + btrfs_add_delayed_iput(inode); +} + +/* + * Queue space reservation fixup work for blocks dirtied without a space reservation. + * + * Should be used by writeback while holding the folio locked. + * + * If we fail to queue fixup, then the folio state is unchanged and a future + * writeback pass will still see it. + */ +void btrfs_queue_writepage_fixup(struct btrfs_inode *inode, struct folio *folio) +{ + struct btrfs_fs_info *fs_info = inode->root->fs_info; + struct btrfs_writepage_fixup *fixup; + + fixup = kzalloc_obj(*fixup, GFP_NOFS); + if (!fixup) + return; + + /* + * This is called from within extent_write_cache_pages() which + * has successfully done an igrab(). But that will be released at the + * end of the writeback pass. We need to extend it for the worker as well. + */ + ihold(&inode->vfs_inode); + folio_get(folio); + INIT_WORK(&fixup->work, btrfs_writepage_fixup_worker); + fixup->folio = folio; + fixup->inode = inode; + queue_work(fs_info->fixup_workers, &fixup->work); +} + /* * Clear the old accounting flags and set EXTENT_DELALLOC for the range. * @@ -7507,6 +7664,12 @@ static void btrfs_invalidate_folio(struct folio *folio, size_t offset, folio_wait_writeback(folio); wait_subpage_spinlock(folio); + /* + * The invalidated blocks are going away; drop any fixup blocks among + * them, data included, as they have no space reservation. + */ + btrfs_folio_clear_fixup_dirty(fs_info, folio, page_start + offset, length); + /* * For subpage case, we have call sites like * btrfs_punch_hole_lock_range() which passes range not aligned to @@ -10548,6 +10711,41 @@ static const struct file_operations btrfs_dir_file_operations = { .setlease = generic_setlease, }; +/* + * The folio is going dirty without a btrfs delalloc space reservation. + * This requires a fixup before writeback which we might sleep so cannot + * run in this context, so we merely set state on the folio indicating it + * needs fixup before writeback. + * + * Note that there is no range in the input, so the whole folio is marked + * dirty and fixup. + * + * We believe that all callers of dirty_folio either: + * - take the folio lock (e.g. pinned folio release notification). + * - take the pte lock but must be running on a dirty pte which means + * page_mkwrite() ran on it and reserved the space. zap_pte_range() cannot + * race with writeback cleaning the folio because writeback runs + * folio_mkclean() which also uses the pte lock and revokes outstanding + * writable mappings. + * Therefore, an additional folio private lock (a la bfs->lock for all cases, + * not just subpage) is not necessary. + */ +static bool btrfs_data_dirty_folio(struct address_space *mapping, + struct folio *folio) +{ + struct btrfs_inode *inode = BTRFS_I(mapping->host); + struct btrfs_fs_info *fs_info = inode->root->fs_info; + const u64 page_start = folio_pos(folio); + const u64 range_end = min_t(u64, folio_next_pos(folio), + round_up(i_size_read(&inode->vfs_inode), + fs_info->sectorsize)); + + if (range_end > page_start) + btrfs_folio_set_fixup_dirty(fs_info, folio, page_start, + range_end - page_start); + return filemap_dirty_folio(mapping, folio); +} + /* * btrfs doesn't support the bmap operation because swapfiles * use bmap to make a mapping of extents in the file. They assume @@ -10568,7 +10766,7 @@ static const struct address_space_operations btrfs_aops = { .launder_folio = btrfs_launder_folio, .release_folio = btrfs_release_folio, .migrate_folio = btrfs_migrate_folio, - .dirty_folio = filemap_dirty_folio, + .dirty_folio = btrfs_data_dirty_folio, .error_remove_folio = generic_error_remove_folio, .swap_activate = btrfs_swap_activate, .swap_deactivate = btrfs_swap_deactivate, diff --git a/fs/btrfs/subpage.c b/fs/btrfs/subpage.c index 2a9397be8116c9..27dd677ca687dc 100644 --- a/fs/btrfs/subpage.c +++ b/fs/btrfs/subpage.c @@ -345,18 +345,57 @@ void btrfs_subpage_clear_uptodate(const struct btrfs_fs_info *fs_info, spin_unlock_irqrestore(&bfs->lock, flags); } +/* + * folio_mark_dirty() for a folio we are dirtying with a space reservation. + * + * Dirtiers without a reservation use btrfs_data_dirty_folio(). + */ +static void btrfs_folio_mark_dirty(struct folio *folio) +{ + struct address_space *mapping = folio_mapping(folio); + + if (!mapping || !mapping->host || !is_data_inode(BTRFS_I(mapping->host))) { + folio_mark_dirty(folio); + return; + } + if (folio_test_reclaim(folio)) + folio_clear_reclaim(folio); + filemap_dirty_folio(mapping, folio); +} + +/* + * The set helper of the dirty ops, so it only runs for folios without a + * fixup bitmap: for those the folio flag is the whole fixup state, and this + * reserving write covers the block, so retire it. Metadata never has the + * flag set and only pays the test. + */ +static void btrfs_folio_mark_dirty_reserved(struct folio *folio) +{ + if (folio_test_fixup_pending(folio)) + folio_clear_fixup_pending(folio); + btrfs_folio_mark_dirty(folio); +} + void btrfs_subpage_set_dirty(const struct btrfs_fs_info *fs_info, struct folio *folio, u64 start, u32 len) { struct btrfs_folio_state *bfs = folio_get_private(folio); - unsigned int start_bit = subpage_calc_start_bit(fs_info, folio, + unsigned int dirty_bit = subpage_calc_start_bit(fs_info, folio, dirty, start, len); + unsigned int fixup_bit = subpage_calc_start_bit(fs_info, folio, + fixup, start, len); + const unsigned int nbits = len >> fs_info->sectorsize_bits; unsigned long flags; spin_lock_irqsave(&bfs->lock, flags); - bitmap_set(bfs->bitmaps, start_bit, len >> fs_info->sectorsize_bits); + bitmap_set(bfs->bitmaps, dirty_bit, nbits); + /* Proper dirtying obviates the need for fixup. */ + bitmap_clear(bfs->bitmaps, fixup_bit, nbits); + if (folio_test_fixup_pending(folio) && + subpage_test_bitmap_all_zero(fs_info, folio, fixup)) + folio_clear_fixup_pending(folio); spin_unlock_irqrestore(&bfs->lock, flags); - folio_mark_dirty(folio); + btrfs_folio_mark_dirty(folio); } static void folio_clear_tags(struct folio *folio) @@ -457,6 +496,172 @@ void btrfs_subpage_clear_writeback(const struct btrfs_fs_info *fs_info, spin_unlock_irqrestore(&bfs->lock, flags); } +void btrfs_subpage_clear_fixup(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len) +{ + struct btrfs_folio_state *bfs = folio_get_private(folio); + unsigned int start_bit = subpage_calc_start_bit(fs_info, folio, + fixup, start, len); + unsigned long flags; + + spin_lock_irqsave(&bfs->lock, flags); + bitmap_clear(bfs->bitmaps, start_bit, len >> fs_info->sectorsize_bits); + if (subpage_test_bitmap_all_zero(fs_info, folio, fixup)) + folio_clear_fixup_pending(folio); + spin_unlock_irqrestore(&bfs->lock, flags); +} + +/* + * In one pass under bfs->lock, mark every block with a clear dirty bit in the + * range both dirty and needing fixup. + * + * Only called from the dirty_folio callback, which owns the folio-level + * dirty flag; calling folio_mark_dirty() here would recurse. + * + * The folio fixup flag and bits are both set under bfs->lock so that a + * writeback pass observing the new bits also observes the flag. + */ +static void btrfs_subpage_set_fixup_dirty(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len) +{ + struct btrfs_folio_state *bfs = folio_get_private(folio); + unsigned int dirty_bit = subpage_calc_start_bit(fs_info, folio, + dirty, start, len); + unsigned int fixup_bit = subpage_calc_start_bit(fs_info, folio, + fixup, start, len); + const unsigned int nbits = len >> fs_info->sectorsize_bits; + unsigned long flags; + bool marked = false; + + spin_lock_irqsave(&bfs->lock, flags); + for (unsigned int i = 0; i < nbits; i++) { + if (test_bit(dirty_bit + i, bfs->bitmaps)) + continue; + set_bit(dirty_bit + i, bfs->bitmaps); + set_bit(fixup_bit + i, bfs->bitmaps); + marked = true; + } + if (marked) + folio_set_fixup_pending(folio); + spin_unlock_irqrestore(&bfs->lock, flags); +} + +/* + * Mark the still-clean blocks of a folio dirty and needing fixup, for + * btrfs_data_dirty_folio(). + * + * A subpage block size folio that is not uptodate is left alone: its clean + * blocks may hold content that was never read in, which must not be marked + * dirty. + */ +void btrfs_folio_set_fixup_dirty(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len) +{ + if (!btrfs_is_subpage(fs_info, folio)) { + if (!folio_test_dirty(folio)) + folio_set_fixup_pending(folio); + return; + } + if (!folio_test_uptodate(folio)) + return; + btrfs_subpage_set_fixup_dirty(fs_info, folio, start, len); +} + +/* + * Drop the fixup blocks inside the range: clear both their fixup and dirty + * bits. + * + * Fixup blocks carry no space reservation, so their fixup and dirty bits + * must be dropped together. Clearing only the fixup bit would leave a + * dirty block without a reservation which is not a valid state. + * + * Returns true if the folio has no dirty blocks left. + */ +static bool btrfs_subpage_clear_fixup_dirty(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len) +{ + struct btrfs_folio_state *bfs = folio_get_private(folio); + unsigned int dirty_bit = subpage_calc_start_bit(fs_info, folio, + dirty, start, len); + unsigned int fixup_bit = subpage_calc_start_bit(fs_info, folio, + fixup, start, len); + const unsigned int nbits = len >> fs_info->sectorsize_bits; + unsigned long flags; + bool last; + + spin_lock_irqsave(&bfs->lock, flags); + for (unsigned int i = 0; i < nbits; i++) { + if (!test_bit(fixup_bit + i, bfs->bitmaps)) + continue; + clear_bit(fixup_bit + i, bfs->bitmaps); + clear_bit(dirty_bit + i, bfs->bitmaps); + } + if (subpage_test_bitmap_all_zero(fs_info, folio, fixup)) + folio_clear_fixup_pending(folio); + last = subpage_test_bitmap_all_zero(fs_info, folio, dirty); + spin_unlock_irqrestore(&bfs->lock, flags); + return last; +} + +/* + * Drop the fixup blocks inside the range, for callers discarding their data: + * btrfs_invalidate_folio() and the writepage fixup worker's error path. + * + * Callers that have just reserved space for a block want + * btrfs_folio_clear_fixup() instead - there the block stays dirty and gets + * written. + * + * The range can be byte-granular (an unaligned truncate through + * btrfs_invalidate_folio()); only blocks fully inside it are dropped, as a + * partially covered block still holds live data outside the range. For + * single-block folios the folio flag is the fixup state, so it is dropped + * only when the range covers the whole folio. + */ +void btrfs_folio_clear_fixup_dirty(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len) +{ + u64 aligned_start; + u64 aligned_end; + + /* The folio flag is set whenever any fixup bitmap bit is. */ + if (!folio_test_fixup_pending(folio)) + return; + if (!btrfs_is_subpage(fs_info, folio)) { + if (start <= folio_pos(folio) && + start + len >= folio_next_pos(folio)) { + folio_clear_fixup_pending(folio); + folio_clear_dirty_for_io(folio); + } + return; + } + btrfs_subpage_clamp_range(folio, &start, &len); + aligned_start = round_up(start, fs_info->sectorsize); + aligned_end = round_down(start + len, fs_info->sectorsize); + if (aligned_end <= aligned_start) + return; + if (btrfs_subpage_clear_fixup_dirty(fs_info, folio, aligned_start, + aligned_end - aligned_start)) + folio_clear_dirty_for_io(folio); +} + +bool btrfs_folio_test_fixup(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len) +{ + if (!btrfs_is_subpage(fs_info, folio)) + return folio_test_fixup_pending(folio); + return btrfs_subpage_test_fixup(fs_info, folio, start, len); +} + +void btrfs_folio_clear_fixup(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len) +{ + if (!btrfs_is_subpage(fs_info, folio)) { + folio_clear_fixup_pending(folio); + return; + } + btrfs_subpage_clear_fixup(fs_info, folio, start, len); +} + /* * Unlike set/clear which is dependent on each page status, for test all bits * are tested in the same way. @@ -480,6 +685,7 @@ bool btrfs_subpage_test_##name(const struct btrfs_fs_info *fs_info, \ IMPLEMENT_BTRFS_SUBPAGE_TEST_OP(uptodate); IMPLEMENT_BTRFS_SUBPAGE_TEST_OP(dirty); IMPLEMENT_BTRFS_SUBPAGE_TEST_OP(writeback); +IMPLEMENT_BTRFS_SUBPAGE_TEST_OP(fixup); /* * Note that, in selftests (extent-io-tests), we can have empty fs_info passed @@ -571,8 +777,8 @@ bool btrfs_meta_folio_test_##name(struct folio *folio, const struct extent_buffe } IMPLEMENT_BTRFS_PAGE_OPS(uptodate, folio_mark_uptodate, folio_clear_uptodate, folio_test_uptodate); -IMPLEMENT_BTRFS_PAGE_OPS(dirty, folio_mark_dirty, folio_clear_dirty_for_io, - folio_test_dirty); +IMPLEMENT_BTRFS_PAGE_OPS(dirty, btrfs_folio_mark_dirty_reserved, + folio_clear_dirty_for_io, folio_test_dirty); IMPLEMENT_BTRFS_PAGE_OPS(writeback, folio_start_writeback, folio_end_writeback, folio_test_writeback); diff --git a/fs/btrfs/subpage.h b/fs/btrfs/subpage.h index c6d7394e6418af..9aceba93c818f6 100644 --- a/fs/btrfs/subpage.h +++ b/fs/btrfs/subpage.h @@ -14,15 +14,15 @@ struct folio; /* * Extra info for subpage bitmap. * - * For subpage we pack all uptodate/dirty/writeback bitmaps into + * For subpage we pack all uptodate/dirty/writeback/fixup bitmaps into * one larger bitmap. * * This structure records how they are organized in the bitmap: * - * /- uptodate /- dirty /- writeback - * | | | - * v v v - * |u|u|u|u|........|u|u|d|d|.......|d|d|w|w|.......|w|w| + * /- uptodate /- dirty /- writeback /- fixup + * | | | | + * v v v v + * |u|u|u|u|........|u|u|d|d|.......|d|d|w|w|.....|w|w|f|f|.....|f|f| * |< sectors_per_page >| * * Unlike regular macro-like enums, here we do not go upper-case names, as @@ -40,6 +40,14 @@ enum { */ btrfs_bitmap_nr_writeback, + /* + * Blocks dirtied by the dirty_folio callback instead of a reserving + * write path (e.g. set_page_dirty_lock() on a GUP pin). They have + * no space reservation and need the writepage fixup before they can + * be submitted. + */ + btrfs_bitmap_nr_fixup, + btrfs_bitmap_nr_max }; @@ -165,6 +173,29 @@ DECLARE_BTRFS_SUBPAGE_OPS(uptodate); DECLARE_BTRFS_SUBPAGE_OPS(dirty); DECLARE_BTRFS_SUBPAGE_OPS(writeback); +/* + * Fixup bit helpers. + * + * The fixup bit is data-only and has no plain set helper (setting happens + * together with dirtying in btrfs_subpage_set_fixup_dirty()), so it does not + * go through DECLARE_BTRFS_SUBPAGE_OPS(). For single-block folios the + * folio_*_fixup_pending() flag takes the place of the bitmap. + */ +void btrfs_subpage_clear_fixup(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len); +bool btrfs_subpage_test_fixup(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len); +bool btrfs_folio_test_fixup(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len); +void btrfs_folio_set_fixup_dirty(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len); +/* For a block that just got its space reserved; it stays dirty. */ +void btrfs_folio_clear_fixup(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len); +/* For callers discarding the data; clears the dirty bits too. */ +void btrfs_folio_clear_fixup_dirty(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len); + /* * Helper for error cleanup, where a folio will have its dirty flag cleared, * with writeback started and finished. diff --git a/include/trace/events/btrfs.h b/include/trace/events/btrfs.h index 4c5c47c5edb745..6c1438f6a4d38c 100644 --- a/include/trace/events/btrfs.h +++ b/include/trace/events/btrfs.h @@ -689,6 +689,41 @@ DEFINE_EVENT(btrfs__ordered_extent, btrfs_ordered_extent_lookup_first, TP_ARGS(inode, ordered) ); +/* + * The writepage fixup worker deferred a block because this still-running + * ordered extent covers it. + */ +DEFINE_EVENT(btrfs__ordered_extent, btrfs_writepage_fixup_defer, + + TP_PROTO(const struct btrfs_inode *inode, + const struct btrfs_ordered_extent *ordered), + + TP_ARGS(inode, ordered) +); + +/* The writepage fixup worker reserved space for a block and set delalloc. */ +TRACE_EVENT(btrfs_writepage_fixup_reserve, + + TP_PROTO(const struct btrfs_inode *inode, u64 start, u32 len), + + TP_ARGS(inode, start, len), + + TP_STRUCT__entry_btrfs( + __field( u64, ino ) + __field( u64, start ) + __field( u32, len ) + ), + + TP_fast_assign_btrfs(inode->root->fs_info, + __entry->ino = btrfs_ino(inode); + __entry->start = start; + __entry->len = len; + ), + + TP_printk_btrfs("ino=%llu start=%llu len=%u", + __entry->ino, __entry->start, __entry->len) +); + DEFINE_EVENT(btrfs__ordered_extent, btrfs_ordered_extent_split, TP_PROTO(const struct btrfs_inode *inode, From 74186c2968f8f756ac3226b545b598457c910c75 Mon Sep 17 00:00:00 2001 From: Eric Farman Date: Tue, 28 Jul 2026 05:30:13 +0200 Subject: [PATCH 076/612] s390/vfio_ccw: Free all memory if cp_init() fails The routine cp_free() is called to unpin/free any memory once an I/O is completed successfully, or if cp_prefetch() fails. But if cp_init() fails, and cp->initialized is not enabled, the same routine cannot be used to free all the memory. An attempt to address this exists in ccwchain_handle_ccw(), where a single call to ccwchain_free() is made for the currently-processed CCW segment. But this will leak other segments (created as a result of a Transfer in Channel) that had been allocated as part of the same channel program. Address this by performing the cleanup outside of the recursive ccwchain_handle_ccw()/ccwchain_loop_tic() logic. Fixes: 8b515be512a2 ("vfio-ccw: Fix memory leak and don't call cp_free in cp_init") Cc: stable@vger.kernel.org Reviewed-by: Farhan Ali Reviewed-by: Matthew Rosato Signed-off-by: Eric Farman Signed-off-by: Christian Borntraeger --- drivers/s390/cio/vfio_ccw_cp.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/drivers/s390/cio/vfio_ccw_cp.c b/drivers/s390/cio/vfio_ccw_cp.c index 7561aa7d3e017c..086d1b54bdb056 100644 --- a/drivers/s390/cio/vfio_ccw_cp.c +++ b/drivers/s390/cio/vfio_ccw_cp.c @@ -455,9 +455,6 @@ static int ccwchain_handle_ccw(dma32_t cda, struct channel_program *cp) /* Loop for tics on this new chain. */ ret = ccwchain_loop_tic(chain, cp); - if (ret) - ccwchain_free(chain); - return ret; } @@ -486,6 +483,23 @@ static int ccwchain_loop_tic(struct ccwchain *chain, struct channel_program *cp) return 0; } +static int ccwchain_build_ccws(dma32_t cda, struct channel_program *cp) +{ + struct ccwchain *chain, *temp; + int ret; + + ret = ccwchain_handle_ccw(cda, cp); + + if (ret) { + /* Cleanup if an error occurred */ + list_for_each_entry_safe(chain, temp, &cp->ccwchain_list, next) { + ccwchain_free(chain); + } + } + + return ret; +} + static int ccwchain_fetch_tic(struct ccw1 *ccw, struct channel_program *cp) { @@ -735,7 +749,7 @@ int cp_init(struct channel_program *cp, union orb *orb) memcpy(&cp->orb, orb, sizeof(*orb)); /* Build a ccwchain for the first CCW segment */ - ret = ccwchain_handle_ccw(orb->cmd.cpa, cp); + ret = ccwchain_build_ccws(orb->cmd.cpa, cp); if (!ret) cp->initialized = true; From 5405c90d6a47b3014e74ee0618a162449abbbc93 Mon Sep 17 00:00:00 2001 From: Eric Farman Date: Tue, 28 Jul 2026 05:30:14 +0200 Subject: [PATCH 077/612] s390/vfio_ccw: Limit the number of channel program segments The processing of channel programs, and the CCWs within them, is done recursively. As such, there is an arbitrary (but not architectural) limit to the number of CCWs that can exist in a single channel program. The vfio-ccw logic breaks these channel programs into segments whenever it encounters a Transfer-In-Channel (TIC) CCW, and the combined number of segments count towards the global limit. Impose an equivalent limit to the number of segments until such logic can be made non-recursive. Fixes: 0a19e61e6d4c ("vfio: ccw: introduce channel program interfaces") Cc: stable@vger.kernel.org Reviewed-by: Matthew Rosato Signed-off-by: Eric Farman Signed-off-by: Christian Borntraeger --- drivers/s390/cio/vfio_ccw_cp.c | 6 ++++++ drivers/s390/cio/vfio_ccw_cp.h | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/drivers/s390/cio/vfio_ccw_cp.c b/drivers/s390/cio/vfio_ccw_cp.c index 086d1b54bdb056..1c2890d139c6b8 100644 --- a/drivers/s390/cio/vfio_ccw_cp.c +++ b/drivers/s390/cio/vfio_ccw_cp.c @@ -332,6 +332,7 @@ static struct ccwchain *ccwchain_alloc(struct channel_program *cp, int len) goto out_err; list_add_tail(&chain->next, &cp->ccwchain_list); + cp->ccwchain_count++; return chain; @@ -441,6 +442,10 @@ static int ccwchain_handle_ccw(dma32_t cda, struct channel_program *cp) if (len < 0) return len; + /* Limit number of chains in a single channel program */ + if (cp->ccwchain_count >= CCWCHAIN_COUNT_MAX) + return -EINVAL; + /* Need alloc a new chain for this one. */ chain = ccwchain_alloc(cp, len); if (!chain) @@ -745,6 +750,7 @@ int cp_init(struct channel_program *cp, union orb *orb) vdev->dev, "Prefetching channel program even though prefetch not specified in ORB"); + cp->ccwchain_count = 0; INIT_LIST_HEAD(&cp->ccwchain_list); memcpy(&cp->orb, orb, sizeof(*orb)); diff --git a/drivers/s390/cio/vfio_ccw_cp.h b/drivers/s390/cio/vfio_ccw_cp.h index fc31eb69980724..a9b1d8dbc6f655 100644 --- a/drivers/s390/cio/vfio_ccw_cp.h +++ b/drivers/s390/cio/vfio_ccw_cp.h @@ -23,11 +23,18 @@ */ #define CCWCHAIN_LEN_MAX 256 +/* + * Maximum number of chains + */ +#define CCWCHAIN_COUNT_MAX 16 + /** * struct channel_program - manage information for channel program * @ccwchain_list: list head of ccwchains * @orb: orb for the currently processed ssch request * @initialized: whether this instance is actually initialized + * @guest_cp: copy of guest channel program + * @ccwchain_count: number of channel program segments (linked by TIC) * * @ccwchain_list is the head of a ccwchain list, that contents the * translated result of the guest channel program that pointed out by @@ -38,6 +45,7 @@ struct channel_program { union orb orb; bool initialized; struct ccw1 *guest_cp; + unsigned int ccwchain_count; }; int cp_init(struct channel_program *cp, union orb *orb); From a005b7f1a491ffda61bff0fd0f6548f8986fb977 Mon Sep 17 00:00:00 2001 From: Eric Farman Date: Tue, 28 Jul 2026 05:30:15 +0200 Subject: [PATCH 078/612] s390/vfio_ccw: Fix out of bounds check on CCW array The routine ccwchain_calc_length() counts the number of channel command words (CCWs) that are chained together in a single channel program, and rejects anything larger than CCWCHAIN_LEN_MAX (256) CCWs. The loop itself is "do..while (count < 257)", and while the logic in is_cpa_within_range() correctly adjusts between the 0-index array of CCWs and the count of CCWs starting at 1, this means it would look at a possible 257th CCW before ending the loop and (correctly) returning an error. Fix this by restructuring the loop to break as soon as 256 CCWs (thus indexes 0-255) are examined, without looking at memory outside the range. Fixes: 0a19e61e6d4c ("vfio: ccw: introduce channel program interfaces") Cc: stable@vger.kernel.org Reviewed-by: Matthew Rosato Signed-off-by: Eric Farman Signed-off-by: Christian Borntraeger --- drivers/s390/cio/vfio_ccw_cp.c | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/drivers/s390/cio/vfio_ccw_cp.c b/drivers/s390/cio/vfio_ccw_cp.c index 1c2890d139c6b8..af632f9d545356 100644 --- a/drivers/s390/cio/vfio_ccw_cp.c +++ b/drivers/s390/cio/vfio_ccw_cp.c @@ -377,11 +377,9 @@ static void ccwchain_cda_free(struct ccwchain *chain, int idx) static int ccwchain_calc_length(u64 iova, struct channel_program *cp) { struct ccw1 *ccw = cp->guest_cp; - int cnt = 0; - - do { - cnt++; + int cnt; + for (cnt = 1; cnt <= CCWCHAIN_LEN_MAX; cnt++, ccw++) { /* * We want to keep counting if the current CCW has the * command-chaining flag enabled, or if it is a TIC CCW @@ -391,15 +389,10 @@ static int ccwchain_calc_length(u64 iova, struct channel_program *cp) * after the TIC, depending on the results of its operation. */ if (!ccw_is_chain(ccw) && !is_tic_within_range(ccw, iova, cnt)) - break; - - ccw++; - } while (cnt < CCWCHAIN_LEN_MAX + 1); - - if (cnt == CCWCHAIN_LEN_MAX + 1) - cnt = -EINVAL; + return cnt; + } - return cnt; + return -EINVAL; } static int tic_target_chain_exists(struct ccw1 *tic, struct channel_program *cp) From 565bef268d75bf7df665bce6923a88cd0eb74592 Mon Sep 17 00:00:00 2001 From: Eric Farman Date: Tue, 28 Jul 2026 05:30:16 +0200 Subject: [PATCH 079/612] s390/vfio_ccw: Ensure first IDAW remains constant The first IDAW in a list does not need to be on a 2K/4K boundary like all others, and so is read separately to accurately calculate the size of the buffer needed to read the full IDAL. Verify that the address found in the first IDAW is unchanged between reads, to ensure a consistent set of IDAWs being worked with. Fixes: 01aa26c672c0 ("s390/cio: Combine direct and indirect CCW paths") Cc: stable@vger.kernel.org Reviewed-by: Matthew Rosato Signed-off-by: Eric Farman Signed-off-by: Christian Borntraeger --- drivers/s390/cio/vfio_ccw_cp.c | 16 ++++++++++++++++ drivers/s390/cio/vfio_ccw_cp.h | 2 ++ 2 files changed, 18 insertions(+) diff --git a/drivers/s390/cio/vfio_ccw_cp.c b/drivers/s390/cio/vfio_ccw_cp.c index af632f9d545356..6275794751cb45 100644 --- a/drivers/s390/cio/vfio_ccw_cp.c +++ b/drivers/s390/cio/vfio_ccw_cp.c @@ -523,6 +523,7 @@ static dma64_t *get_guest_idal(struct ccw1 *ccw, struct channel_program *cp, int &container_of(cp, struct vfio_ccw_private, cp)->vdev; dma64_t *idaws; dma32_t *idaws_f1; + u64 first_idaw; int idal_len = idaw_nr * sizeof(*idaws); int idaw_size = idal_is_2k(cp) ? PAGE_SIZE / 2 : PAGE_SIZE; int idaw_mask = ~(idaw_size - 1); @@ -539,6 +540,18 @@ static dma64_t *get_guest_idal(struct ccw1 *ccw, struct channel_program *cp, int kfree(idaws); return ERR_PTR(ret); } + + idaws_f1 = (dma32_t *)idaws; + if (cp->orb.cmd.c64) + first_idaw = dma64_to_u64(idaws[0]); + else + first_idaw = dma32_to_u32(idaws_f1[0]); + + /* Unexpected mismatch from earlier read */ + if (first_idaw != cp->guest_iova) { + kfree(idaws); + return ERR_PTR(-EINVAL); + } } else { /* Fabricate an IDAL based off CCW data address */ if (cp->orb.cmd.c64) { @@ -604,6 +617,9 @@ static int ccw_count_idaws(struct ccw1 *ccw, iova = dma32_to_u32(ccw->cda); } + /* Save the read address for later */ + cp->guest_iova = iova; + /* Format-1 IDAWs operate on 2K each */ if (!cp->orb.cmd.c64) return idal_2k_nr_words((void *)iova, bytes); diff --git a/drivers/s390/cio/vfio_ccw_cp.h b/drivers/s390/cio/vfio_ccw_cp.h index a9b1d8dbc6f655..9af98ff12d6716 100644 --- a/drivers/s390/cio/vfio_ccw_cp.h +++ b/drivers/s390/cio/vfio_ccw_cp.h @@ -35,6 +35,7 @@ * @initialized: whether this instance is actually initialized * @guest_cp: copy of guest channel program * @ccwchain_count: number of channel program segments (linked by TIC) + * @guest_iova: first data address of a guest channel program * * @ccwchain_list is the head of a ccwchain list, that contents the * translated result of the guest channel program that pointed out by @@ -46,6 +47,7 @@ struct channel_program { bool initialized; struct ccw1 *guest_cp; unsigned int ccwchain_count; + u64 guest_iova; }; int cp_init(struct channel_program *cp, union orb *orb); From 4f6fdc6e1a7fbfa36b945af33c65a417948feac0 Mon Sep 17 00:00:00 2001 From: Eric Farman Date: Tue, 28 Jul 2026 05:30:17 +0200 Subject: [PATCH 080/612] s390/vfio_ccw: Calculate idal length based on idaw type Sashiko pointed out that get_guest_idal() unconditionally calculates the length of the IDAL presuming everything is a Format-2 IDAW. The output of vfio-ccw is always Format-2, but the input can be either Format-1 (31-bit addresses) or Format-2 (64-bit addresses). As a result, the size of the guest IDAL may be incorrect and should be trimmed down. Reported-by: sashiko-bot Link: https://lore.kernel.org/r/20260720203400.7328E1F000E9@smtp.kernel.org/ Fixes: 1b676fe3d9d3 ("vfio/ccw: handle a guest Format-1 IDAL") Cc: stable@vger.kernel.org Reviewed-by: Matthew Rosato Signed-off-by: Eric Farman Signed-off-by: Christian Borntraeger --- drivers/s390/cio/vfio_ccw_cp.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/s390/cio/vfio_ccw_cp.c b/drivers/s390/cio/vfio_ccw_cp.c index 6275794751cb45..5ef082b8289af0 100644 --- a/drivers/s390/cio/vfio_ccw_cp.c +++ b/drivers/s390/cio/vfio_ccw_cp.c @@ -233,6 +233,7 @@ static void convert_ccw0_to_ccw1(struct ccw1 *source, unsigned long len) } #define idal_is_2k(_cp) (!(_cp)->orb.cmd.c64 || (_cp)->orb.cmd.i2k) +#define get_idaw_size(_cp) ((_cp)->orb.cmd.c64 ? sizeof(u64) : sizeof(u32)) /* * Helpers to operate ccwchain. @@ -524,7 +525,7 @@ static dma64_t *get_guest_idal(struct ccw1 *ccw, struct channel_program *cp, int dma64_t *idaws; dma32_t *idaws_f1; u64 first_idaw; - int idal_len = idaw_nr * sizeof(*idaws); + int idal_len = idaw_nr * get_idaw_size(cp); int idaw_size = idal_is_2k(cp) ? PAGE_SIZE / 2 : PAGE_SIZE; int idaw_mask = ~(idaw_size - 1); int i, ret; @@ -593,7 +594,7 @@ static int ccw_count_idaws(struct ccw1 *ccw, struct vfio_device *vdev = &container_of(cp, struct vfio_ccw_private, cp)->vdev; u64 iova; - int size = cp->orb.cmd.c64 ? sizeof(u64) : sizeof(u32); + int size = get_idaw_size(cp); int ret; int bytes = 1; From 9f5f9a78fedc45bc29d6a0a64e3a3472361afae5 Mon Sep 17 00:00:00 2001 From: Eric Farman Date: Tue, 28 Jul 2026 05:30:18 +0200 Subject: [PATCH 081/612] s390/vfio_ccw: Ensure index for read/write regions are within range The introduction of the capability chain rightly clamped the region indexes to the range of the capabilities itself, but neglected to do so for the existing read/write regions which should also be enforced. Fixes: db8e5d17ac03 ("vfio-ccw: add capabilities chain") Cc: stable@vger.kernel.org Cc: Cornelia Huck Reviewed-by: Matthew Rosato Signed-off-by: Eric Farman Signed-off-by: Christian Borntraeger --- drivers/s390/cio/vfio_ccw_async.c | 16 ++++++++++++++++ drivers/s390/cio/vfio_ccw_chp.c | 15 +++++++++++++++ drivers/s390/cio/vfio_ccw_ops.c | 7 +++---- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/drivers/s390/cio/vfio_ccw_async.c b/drivers/s390/cio/vfio_ccw_async.c index 420d89ba7f8387..4aff0b58fa5d56 100644 --- a/drivers/s390/cio/vfio_ccw_async.c +++ b/drivers/s390/cio/vfio_ccw_async.c @@ -8,6 +8,7 @@ */ #include +#include #include "vfio_ccw_private.h" @@ -24,11 +25,20 @@ static ssize_t vfio_ccw_async_region_read(struct vfio_ccw_private *private, return -EINVAL; mutex_lock(&private->io_mutex); + + if (i >= private->num_regions) { + ret = -EINVAL; + goto out_unlock; + } + + i = array_index_nospec(i, private->num_regions); region = private->region[i].data; if (copy_to_user(buf, (void *)region + pos, count)) ret = -EFAULT; else ret = count; + +out_unlock: mutex_unlock(&private->io_mutex); return ret; } @@ -48,6 +58,12 @@ static ssize_t vfio_ccw_async_region_write(struct vfio_ccw_private *private, if (!mutex_trylock(&private->io_mutex)) return -EAGAIN; + if (i >= private->num_regions) { + ret = -EINVAL; + goto out_unlock; + } + + i = array_index_nospec(i, private->num_regions); region = private->region[i].data; if (copy_from_user((void *)region + pos, buf, count)) { ret = -EFAULT; diff --git a/drivers/s390/cio/vfio_ccw_chp.c b/drivers/s390/cio/vfio_ccw_chp.c index 38c176cf629572..f3015132d4b5f7 100644 --- a/drivers/s390/cio/vfio_ccw_chp.c +++ b/drivers/s390/cio/vfio_ccw_chp.c @@ -9,6 +9,7 @@ */ #include +#include #include #include "vfio_ccw_private.h" @@ -26,6 +27,13 @@ static ssize_t vfio_ccw_schib_region_read(struct vfio_ccw_private *private, return -EINVAL; mutex_lock(&private->io_mutex); + + if (i >= private->num_regions) { + ret = -EINVAL; + goto out; + } + + i = array_index_nospec(i, private->num_regions); region = private->region[i].data; if (cio_update_schib(sch)) { @@ -97,6 +105,12 @@ static ssize_t vfio_ccw_crw_region_read(struct vfio_ccw_private *private, list_del(&crw->next); mutex_lock(&private->io_mutex); + if (i >= private->num_regions) { + ret = -EINVAL; + goto out; + } + + i = array_index_nospec(i, private->num_regions); region = private->region[i].data; if (crw) @@ -109,6 +123,7 @@ static ssize_t vfio_ccw_crw_region_read(struct vfio_ccw_private *private, region->crw = 0; +out: mutex_unlock(&private->io_mutex); kfree(crw); diff --git a/drivers/s390/cio/vfio_ccw_ops.c b/drivers/s390/cio/vfio_ccw_ops.c index 45ec722d25ea69..032a1cdf4df707 100644 --- a/drivers/s390/cio/vfio_ccw_ops.c +++ b/drivers/s390/cio/vfio_ccw_ops.c @@ -243,6 +243,7 @@ static ssize_t vfio_ccw_mdev_read(struct vfio_device *vdev, return vfio_ccw_mdev_read_io_region(private, buf, count, ppos); default: index -= VFIO_CCW_NUM_REGIONS; + index = array_index_nospec(index, private->num_regions); return private->region[index].ops->read(private, buf, count, ppos); } @@ -295,6 +296,7 @@ static ssize_t vfio_ccw_mdev_write(struct vfio_device *vdev, return vfio_ccw_mdev_write_io_region(private, buf, count, ppos); default: index -= VFIO_CCW_NUM_REGIONS; + index = array_index_nospec(index, private->num_regions); return private->region[index].ops->write(private, buf, count, ppos); } @@ -338,11 +340,8 @@ static int vfio_ccw_mdev_ioctl_get_region_info(struct vfio_device *vdev, VFIO_CCW_NUM_REGIONS + private->num_regions) return -EINVAL; - info->index = array_index_nospec(info->index, - VFIO_CCW_NUM_REGIONS + - private->num_regions); - i = info->index - VFIO_CCW_NUM_REGIONS; + i = array_index_nospec(i, private->num_regions); info->offset = VFIO_CCW_INDEX_TO_OFFSET(info->index); info->size = private->region[i].size; From 79c60b2c61105368dcc8444eb45847e21734f7c4 Mon Sep 17 00:00:00 2001 From: Eric Farman Date: Tue, 28 Jul 2026 05:30:19 +0200 Subject: [PATCH 082/612] s390/vfio_ccw: Cancel existing workqueues The initialization of the io_work and crw_work workqueues begs the question of whether they should be un-initialized. Add the corresponding cleanup tags in _release_dev to ensure work isn't dispatched after the private struct is free'd. Suggested-by: Matthew Rosato Fixes: e5f84dbaea59 ("vfio: ccw: return I/O results asynchronously") Fixes: 3f02cb2fd9d2 ("vfio-ccw: Wire up the CRW irq and CRW region") Cc: stable@vger.kernel.org Reviewed-by: Matthew Rosato Signed-off-by: Eric Farman Signed-off-by: Christian Borntraeger --- drivers/s390/cio/vfio_ccw_ops.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/s390/cio/vfio_ccw_ops.c b/drivers/s390/cio/vfio_ccw_ops.c index 032a1cdf4df707..d361d1fde3a0d3 100644 --- a/drivers/s390/cio/vfio_ccw_ops.c +++ b/drivers/s390/cio/vfio_ccw_ops.c @@ -131,6 +131,13 @@ static void vfio_ccw_mdev_release_dev(struct vfio_device *vdev) container_of(vdev, struct vfio_ccw_private, vdev); struct vfio_ccw_crw *crw, *temp; + /* + * Ensure these work items are fully drained, so none can + * fire after being released. + */ + cancel_work_sync(&private->io_work); + cancel_work_sync(&private->crw_work); + list_for_each_entry_safe(crw, temp, &private->crw, next) { list_del(&crw->next); kfree(crw); @@ -202,6 +209,14 @@ static void vfio_ccw_mdev_close_device(struct vfio_device *vdev) container_of(vdev, struct vfio_ccw_private, vdev); vfio_ccw_fsm_event(private, VFIO_CCW_EVENT_CLOSE); + + /* + * Ensure these work items are drained, in the event the + * device is re-opened instead of released. + */ + cancel_work_sync(&private->io_work); + cancel_work_sync(&private->crw_work); + vfio_ccw_unregister_dev_regions(private); } From 0c11f61a876ed6fcca53d442ed3f33ea8362a0f9 Mon Sep 17 00:00:00 2001 From: Eric Farman Date: Tue, 28 Jul 2026 05:30:20 +0200 Subject: [PATCH 083/612] s390/vfio_ccw: Move cp cleanup out of not operational The fsm_notoper() routine is called when the device has been lost, and is (by definition) no longer operational. Since this can happen asynchronously from the normal behavior of the driver, the cleanup may happen when holding other locks in the calling sequence (notably, the cio subchannel lock). Push the cleanup of the private->cp resources to a workqueue, where it can be done out from under that lock sequence and a future patch can safely manage the locking requirements. Fixes: 204b394a23ad ("vfio/ccw: Move FSM open/close to MDEV open/close") Cc: stable@vger.kernel.org Signed-off-by: Eric Farman Reviewed-by: Matthew Rosato Signed-off-by: Christian Borntraeger --- drivers/s390/cio/vfio_ccw_drv.c | 9 +++++++++ drivers/s390/cio/vfio_ccw_fsm.c | 3 +-- drivers/s390/cio/vfio_ccw_ops.c | 13 +++++++++++++ drivers/s390/cio/vfio_ccw_private.h | 3 +++ 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/drivers/s390/cio/vfio_ccw_drv.c b/drivers/s390/cio/vfio_ccw_drv.c index 1a095085bc7281..c197ad5ab58056 100644 --- a/drivers/s390/cio/vfio_ccw_drv.c +++ b/drivers/s390/cio/vfio_ccw_drv.c @@ -125,6 +125,15 @@ void vfio_ccw_crw_todo(struct work_struct *work) eventfd_signal(private->crw_trigger); } +void vfio_ccw_notoper_todo(struct work_struct *work) +{ + struct vfio_ccw_private *private; + + private = container_of(work, struct vfio_ccw_private, notoper_work); + + cp_free(&private->cp); +} + /* * Css driver callbacks */ diff --git a/drivers/s390/cio/vfio_ccw_fsm.c b/drivers/s390/cio/vfio_ccw_fsm.c index 4d7988ea47ef0f..4d47a3c7b9a069 100644 --- a/drivers/s390/cio/vfio_ccw_fsm.c +++ b/drivers/s390/cio/vfio_ccw_fsm.c @@ -170,8 +170,7 @@ static void fsm_notoper(struct vfio_ccw_private *private, css_sched_sch_todo(sch, SCH_TODO_UNREG); private->state = VFIO_CCW_STATE_NOT_OPER; - /* This is usually handled during CLOSE event */ - cp_free(&private->cp); + queue_work(vfio_ccw_work_q, &private->notoper_work); } /* diff --git a/drivers/s390/cio/vfio_ccw_ops.c b/drivers/s390/cio/vfio_ccw_ops.c index d361d1fde3a0d3..1df6d649565b80 100644 --- a/drivers/s390/cio/vfio_ccw_ops.c +++ b/drivers/s390/cio/vfio_ccw_ops.c @@ -54,6 +54,7 @@ static int vfio_ccw_mdev_init_dev(struct vfio_device *vdev) INIT_LIST_HEAD(&private->crw); INIT_WORK(&private->io_work, vfio_ccw_sch_io_todo); INIT_WORK(&private->crw_work, vfio_ccw_crw_todo); + INIT_WORK(&private->notoper_work, vfio_ccw_notoper_todo); private->cp.guest_cp = kzalloc_objs(struct ccw1, CCWCHAIN_LEN_MAX); if (!private->cp.guest_cp) @@ -134,9 +135,16 @@ static void vfio_ccw_mdev_release_dev(struct vfio_device *vdev) /* * Ensure these work items are fully drained, so none can * fire after being released. + * + * notoper_work should have nothing to do here, because only + * open devices could have channel_program resources in use + * and those would be released during close. Nevertheless, + * call flush here as well to be certain anything that was + * allocated is freed. */ cancel_work_sync(&private->io_work); cancel_work_sync(&private->crw_work); + flush_work(&private->notoper_work); list_for_each_entry_safe(crw, temp, &private->crw, next) { list_del(&crw->next); @@ -213,9 +221,14 @@ static void vfio_ccw_mdev_close_device(struct vfio_device *vdev) /* * Ensure these work items are drained, in the event the * device is re-opened instead of released. + * + * notoper_work needs to be given a chance to run if it + * is queued, so any memory associated with the channel + * program can be returned. */ cancel_work_sync(&private->io_work); cancel_work_sync(&private->crw_work); + flush_work(&private->notoper_work); vfio_ccw_unregister_dev_regions(private); } diff --git a/drivers/s390/cio/vfio_ccw_private.h b/drivers/s390/cio/vfio_ccw_private.h index 0501d4bbcdbd63..e2256402b08940 100644 --- a/drivers/s390/cio/vfio_ccw_private.h +++ b/drivers/s390/cio/vfio_ccw_private.h @@ -102,6 +102,7 @@ struct vfio_ccw_parent { * @req_trigger: eventfd ctx for signaling userspace to return device * @io_work: work for deferral process of I/O handling * @crw_work: work for deferral process of CRW handling + * @notoper_work: work for deferred processing in not-operational state */ struct vfio_ccw_private { struct vfio_device vdev; @@ -125,11 +126,13 @@ struct vfio_ccw_private { struct eventfd_ctx *req_trigger; struct work_struct io_work; struct work_struct crw_work; + struct work_struct notoper_work; } __aligned(8); int vfio_ccw_sch_quiesce(struct subchannel *sch); void vfio_ccw_sch_io_todo(struct work_struct *work); void vfio_ccw_crw_todo(struct work_struct *work); +void vfio_ccw_notoper_todo(struct work_struct *work); extern struct mdev_driver vfio_ccw_mdev_driver; From 34f4feff3e90bd09308fad0974e97113b23b812a Mon Sep 17 00:00:00 2001 From: Eric Farman Date: Tue, 28 Jul 2026 05:30:21 +0200 Subject: [PATCH 084/612] s390/vfio_ccw: Selectively expand io_mutex The io_mutex was defined to serialize the io_regions, but then has also sort of been associated with the I/O themselves because of the close relationship they share. With the handful of races that are possible, the choices are either to: A) expand the scope of io_mutex to close these remaining windows, or B) reduce the scope of io_mutex to just io_region, and introduce a new lock mechanism for the remaining I/O resources This patch implements A, since B brings with it a lot more interactions that would need to be tracked and kept in a correct hierarchy. It also takes advantage of the workqueue element for cp_free() that now gets called out of fsm_notoper(), which could be invoked out of an interrupt context and thus cannot acquire a mutex itself. Fixes: 4f76617378ee ("vfio-ccw: protect the I/O region") Cc: stable@vger.kernel.org Signed-off-by: Eric Farman Reviewed-by: Matthew Rosato Signed-off-by: Christian Borntraeger --- drivers/s390/cio/vfio_ccw_chp.c | 2 +- drivers/s390/cio/vfio_ccw_cp.c | 8 +++++++- drivers/s390/cio/vfio_ccw_drv.c | 6 ++++-- drivers/s390/cio/vfio_ccw_fsm.c | 5 +++++ drivers/s390/cio/vfio_ccw_private.h | 3 ++- 5 files changed, 19 insertions(+), 5 deletions(-) diff --git a/drivers/s390/cio/vfio_ccw_chp.c b/drivers/s390/cio/vfio_ccw_chp.c index f3015132d4b5f7..9269b54f5cfd4f 100644 --- a/drivers/s390/cio/vfio_ccw_chp.c +++ b/drivers/s390/cio/vfio_ccw_chp.c @@ -98,13 +98,13 @@ static ssize_t vfio_ccw_crw_region_read(struct vfio_ccw_private *private, if (pos + count > sizeof(*region)) return -EINVAL; + mutex_lock(&private->io_mutex); crw = list_first_entry_or_null(&private->crw, struct vfio_ccw_crw, next); if (crw) list_del(&crw->next); - mutex_lock(&private->io_mutex); if (i >= private->num_regions) { ret = -EINVAL; goto out; diff --git a/drivers/s390/cio/vfio_ccw_cp.c b/drivers/s390/cio/vfio_ccw_cp.c index 5ef082b8289af0..58722c4baa251f 100644 --- a/drivers/s390/cio/vfio_ccw_cp.c +++ b/drivers/s390/cio/vfio_ccw_cp.c @@ -977,17 +977,23 @@ void cp_update_scsw(struct channel_program *cp, union scsw *scsw) */ bool cp_iova_pinned(struct channel_program *cp, u64 iova, u64 length) { + struct vfio_ccw_private *private = + container_of(cp, struct vfio_ccw_private, cp); struct ccwchain *chain; int i; if (!cp->initialized) return false; + mutex_lock(&private->io_mutex); list_for_each_entry(chain, &cp->ccwchain_list, next) { for (i = 0; i < chain->ch_len; i++) - if (page_array_iova_pinned(&chain->ch_pa[i], iova, length)) + if (page_array_iova_pinned(&chain->ch_pa[i], iova, length)) { + mutex_unlock(&private->io_mutex); return true; + } } + mutex_unlock(&private->io_mutex); return false; } diff --git a/drivers/s390/cio/vfio_ccw_drv.c b/drivers/s390/cio/vfio_ccw_drv.c index c197ad5ab58056..757ff5b2556e33 100644 --- a/drivers/s390/cio/vfio_ccw_drv.c +++ b/drivers/s390/cio/vfio_ccw_drv.c @@ -91,6 +91,7 @@ void vfio_ccw_sch_io_todo(struct work_struct *work) is_final = !(scsw_actl(&irb->scsw) & (SCSW_ACTL_DEVACT | SCSW_ACTL_SCHACT)); + mutex_lock(&private->io_mutex); if (scsw_is_solicited(&irb->scsw)) { cp_update_scsw(&private->cp, &irb->scsw); if (is_final && private->state == VFIO_CCW_STATE_CP_PENDING) { @@ -98,9 +99,7 @@ void vfio_ccw_sch_io_todo(struct work_struct *work) cp_is_finished = true; } } - mutex_lock(&private->io_mutex); memcpy(private->io_region->irb_area, irb, sizeof(*irb)); - mutex_unlock(&private->io_mutex); /* * Reset to IDLE only if processing of a channel program @@ -110,6 +109,7 @@ void vfio_ccw_sch_io_todo(struct work_struct *work) */ if (cp_is_finished) private->state = VFIO_CCW_STATE_IDLE; + mutex_unlock(&private->io_mutex); if (private->io_trigger) eventfd_signal(private->io_trigger); @@ -131,7 +131,9 @@ void vfio_ccw_notoper_todo(struct work_struct *work) private = container_of(work, struct vfio_ccw_private, notoper_work); + mutex_lock(&private->io_mutex); cp_free(&private->cp); + mutex_unlock(&private->io_mutex); } /* diff --git a/drivers/s390/cio/vfio_ccw_fsm.c b/drivers/s390/cio/vfio_ccw_fsm.c index 4d47a3c7b9a069..5fd94e9d5c6181 100644 --- a/drivers/s390/cio/vfio_ccw_fsm.c +++ b/drivers/s390/cio/vfio_ccw_fsm.c @@ -170,6 +170,7 @@ static void fsm_notoper(struct vfio_ccw_private *private, css_sched_sch_todo(sch, SCH_TODO_UNREG); private->state = VFIO_CCW_STATE_NOT_OPER; + /* This routine could be called from IRQ context, so defer */ queue_work(vfio_ccw_work_q, &private->notoper_work); } @@ -409,7 +410,11 @@ static void fsm_close(struct vfio_ccw_private *private, private->state = VFIO_CCW_STATE_STANDBY; spin_unlock_irq(&sch->lock); + + mutex_lock(&private->io_mutex); cp_free(&private->cp); + mutex_unlock(&private->io_mutex); + return; err_unlock: diff --git a/drivers/s390/cio/vfio_ccw_private.h b/drivers/s390/cio/vfio_ccw_private.h index e2256402b08940..739121116ab64c 100644 --- a/drivers/s390/cio/vfio_ccw_private.h +++ b/drivers/s390/cio/vfio_ccw_private.h @@ -88,7 +88,8 @@ struct vfio_ccw_parent { * @state: internal state of the device * @completion: synchronization helper of the I/O completion * @io_region: MMIO region to input/output I/O arguments/results - * @io_mutex: protect against concurrent update of I/O regions + * @io_mutex: protect against concurrent update of I/O resources + * and @cp lifecycle * @region: additional regions for other subchannel operations * @cmd_region: MMIO region for asynchronous I/O commands other than START * @schib_region: MMIO region for SCHIB information From 16b0798024c0e9117e395829ddbbe70981c79d9c Mon Sep 17 00:00:00 2001 From: Eric Farman Date: Tue, 28 Jul 2026 05:30:22 +0200 Subject: [PATCH 085/612] s390/vfio_ccw: Implement a crw lock Unlike the channel_program struct, which covers synchronous I/O submissions and asynchronous interrupts, the CRW region relies exclusively on asynchronous events coming from hardware. Implement a lock to manage the list of those payloads, to ensure they are read cohesively. Fixes: 3f02cb2fd9d2 ("vfio-ccw: Wire up the CRW irq and CRW region") Cc: stable@vger.kernel.org Reviewed-by: Matthew Rosato Reviewed-by: Farhan Ali Signed-off-by: Eric Farman Signed-off-by: Christian Borntraeger --- drivers/s390/cio/vfio_ccw_chp.c | 26 ++++++++++++++++---------- drivers/s390/cio/vfio_ccw_drv.c | 6 ++++++ drivers/s390/cio/vfio_ccw_ops.c | 4 ++++ drivers/s390/cio/vfio_ccw_private.h | 4 ++++ 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/drivers/s390/cio/vfio_ccw_chp.c b/drivers/s390/cio/vfio_ccw_chp.c index 9269b54f5cfd4f..7708eb4d6de0be 100644 --- a/drivers/s390/cio/vfio_ccw_chp.c +++ b/drivers/s390/cio/vfio_ccw_chp.c @@ -93,18 +93,13 @@ static ssize_t vfio_ccw_crw_region_read(struct vfio_ccw_private *private, loff_t pos = *ppos & VFIO_CCW_OFFSET_MASK; struct ccw_crw_region *region; struct vfio_ccw_crw *crw; + unsigned long flags; int ret; if (pos + count > sizeof(*region)) return -EINVAL; mutex_lock(&private->io_mutex); - crw = list_first_entry_or_null(&private->crw, - struct vfio_ccw_crw, next); - - if (crw) - list_del(&crw->next); - if (i >= private->num_regions) { ret = -EINVAL; goto out; @@ -113,6 +108,16 @@ static ssize_t vfio_ccw_crw_region_read(struct vfio_ccw_private *private, i = array_index_nospec(i, private->num_regions); region = private->region[i].data; + spin_lock_irqsave(&private->crw_lock, flags); + crw = list_first_entry_or_null(&private->crw, + struct vfio_ccw_crw, next); + + if (crw) + list_del(&crw->next); + + /* Drop CRW lock while copying to userspace */ + spin_unlock_irqrestore(&private->crw_lock, flags); + if (crw) memcpy(®ion->crw, &crw->crw, sizeof(region->crw)); @@ -122,15 +127,16 @@ static ssize_t vfio_ccw_crw_region_read(struct vfio_ccw_private *private, ret = count; region->crw = 0; - -out: - mutex_unlock(&private->io_mutex); - kfree(crw); /* Notify the guest if more CRWs are on our queue */ + spin_lock_irqsave(&private->crw_lock, flags); if (!list_empty(&private->crw) && private->crw_trigger) eventfd_signal(private->crw_trigger); + spin_unlock_irqrestore(&private->crw_lock, flags); + +out: + mutex_unlock(&private->io_mutex); return ret; } diff --git a/drivers/s390/cio/vfio_ccw_drv.c b/drivers/s390/cio/vfio_ccw_drv.c index 757ff5b2556e33..ab6b518cc353f9 100644 --- a/drivers/s390/cio/vfio_ccw_drv.c +++ b/drivers/s390/cio/vfio_ccw_drv.c @@ -118,11 +118,14 @@ void vfio_ccw_sch_io_todo(struct work_struct *work) void vfio_ccw_crw_todo(struct work_struct *work) { struct vfio_ccw_private *private; + unsigned long flags; private = container_of(work, struct vfio_ccw_private, crw_work); + spin_lock_irqsave(&private->crw_lock, flags); if (!list_empty(&private->crw) && private->crw_trigger) eventfd_signal(private->crw_trigger); + spin_unlock_irqrestore(&private->crw_lock, flags); } void vfio_ccw_notoper_todo(struct work_struct *work) @@ -286,6 +289,7 @@ static void vfio_ccw_queue_crw(struct vfio_ccw_private *private, unsigned int rsid) { struct vfio_ccw_crw *crw; + unsigned long flags; /* * If unable to allocate a CRW, just drop the event and @@ -303,7 +307,9 @@ static void vfio_ccw_queue_crw(struct vfio_ccw_private *private, crw->crw.erc = erc; crw->crw.rsid = rsid; + spin_lock_irqsave(&private->crw_lock, flags); list_add_tail(&crw->next, &private->crw); + spin_unlock_irqrestore(&private->crw_lock, flags); queue_work(vfio_ccw_work_q, &private->crw_work); } diff --git a/drivers/s390/cio/vfio_ccw_ops.c b/drivers/s390/cio/vfio_ccw_ops.c index 1df6d649565b80..5ce91285c7d52a 100644 --- a/drivers/s390/cio/vfio_ccw_ops.c +++ b/drivers/s390/cio/vfio_ccw_ops.c @@ -55,6 +55,7 @@ static int vfio_ccw_mdev_init_dev(struct vfio_device *vdev) INIT_WORK(&private->io_work, vfio_ccw_sch_io_todo); INIT_WORK(&private->crw_work, vfio_ccw_crw_todo); INIT_WORK(&private->notoper_work, vfio_ccw_notoper_todo); + spin_lock_init(&private->crw_lock); private->cp.guest_cp = kzalloc_objs(struct ccw1, CCWCHAIN_LEN_MAX); if (!private->cp.guest_cp) @@ -131,6 +132,7 @@ static void vfio_ccw_mdev_release_dev(struct vfio_device *vdev) struct vfio_ccw_private *private = container_of(vdev, struct vfio_ccw_private, vdev); struct vfio_ccw_crw *crw, *temp; + unsigned long flags; /* * Ensure these work items are fully drained, so none can @@ -146,10 +148,12 @@ static void vfio_ccw_mdev_release_dev(struct vfio_device *vdev) cancel_work_sync(&private->crw_work); flush_work(&private->notoper_work); + spin_lock_irqsave(&private->crw_lock, flags); list_for_each_entry_safe(crw, temp, &private->crw, next) { list_del(&crw->next); kfree(crw); } + spin_unlock_irqrestore(&private->crw_lock, flags); kmem_cache_free(vfio_ccw_crw_region, private->crw_region); kmem_cache_free(vfio_ccw_schib_region, private->schib_region); diff --git a/drivers/s390/cio/vfio_ccw_private.h b/drivers/s390/cio/vfio_ccw_private.h index 739121116ab64c..3bd0171d38d049 100644 --- a/drivers/s390/cio/vfio_ccw_private.h +++ b/drivers/s390/cio/vfio_ccw_private.h @@ -98,6 +98,8 @@ struct vfio_ccw_parent { * @cp: channel program for the current I/O operation * @irb: irb info received from interrupt * @scsw: scsw info + * @crw_lock: serialization of CRW list information + * @crw: list of Channel Report Word elements * @io_trigger: eventfd ctx for signaling userspace I/O results * @crw_trigger: eventfd ctx for signaling userspace CRW information * @req_trigger: eventfd ctx for signaling userspace to return device @@ -120,6 +122,8 @@ struct vfio_ccw_private { struct channel_program cp; struct irb irb; union scsw scsw; + + spinlock_t crw_lock; struct list_head crw; struct eventfd_ctx *io_trigger; From d14b5d0e97fccd27974fedc03b903408872907fd Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Mon, 27 Jul 2026 20:30:58 -0500 Subject: [PATCH 086/612] selinux: reject a permission value exceeding the class permission count perm_read() bounds a permission value by SEL_VEC_MAX but never by the nprim of the owning class or common, which is taken verbatim from the policy image. security_get_permissions() then writes perms[value - 1] into an nprim-sized kcalloc() array, so a class declaring fewer permissions than its largest permission value drives an out-of-bounds heap write. The top-level symbol tables are validated this way; the nested per-class permission table is not. Reject a permission whose value exceeds nprim, which is already set when perm_read() runs. Well-formed policies are unaffected. Cc: stable@vger.kernel.org Fixes: 55fcf09b3fe4 ("selinux: add support for querying object classes and permissions from the running policy") Signed-off-by: Bryam Vargas Acked-by: Stephen Smalley [PM: tweak comment for line length] Signed-off-by: Paul Moore --- security/selinux/ss/policydb.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c index ead504a639e363..5ff4d095ec2ef8 100644 --- a/security/selinux/ss/policydb.c +++ b/security/selinux/ss/policydb.c @@ -1175,6 +1175,9 @@ static int perm_read(struct policydb *p, struct symtab *s, struct policy_file *f rc = -EINVAL; if (perdatum->value < 1 || perdatum->value > SEL_VEC_MAX) goto bad; + /* indexes an nprim-sized array in security_get_permissions() */ + if (perdatum->value > s->nprim) + goto bad; rc = str_read(&key, GFP_KERNEL, fp, len); if (rc) From 9a82dcd98b6e6e11cfd162410967951f12152528 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Mon, 27 Jul 2026 20:30:59 -0500 Subject: [PATCH 087/612] selinux: reject a class permission count below its inherited common security_get_permissions() maps an inherited common's permissions into an array sized by the class's own permissions.nprim, but class_read() takes that nprim verbatim from the policy image and never checks that it covers the common. A class that inherits a common of N permissions while declaring a smaller nprim is accepted, and on load the common's permissions are written past the class-sized array -- an out-of-bounds heap write. Reject a class whose permission count is below its inherited common's. Well-formed policies, where the class count already includes the inherited permissions, are unaffected. Cc: stable@vger.kernel.org Fixes: 55fcf09b3fe4 ("selinux: add support for querying object classes and permissions from the running policy") Signed-off-by: Bryam Vargas Acked-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/ss/policydb.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c index 5ff4d095ec2ef8..69777e885ae750 100644 --- a/security/selinux/ss/policydb.c +++ b/security/selinux/ss/policydb.c @@ -1422,6 +1422,18 @@ static int class_read(struct policydb *p, struct symtab *s, struct policy_file * cladatum->comkey); goto bad; } + + /* + * security_get_permissions() maps the common's permissions + * into an array sized by this class's nprim, so a class must + * declare at least as many as the common it inherits. + */ + if (cladatum->permissions.nprim < + cladatum->comdatum->permissions.nprim) { + pr_err("SELinux: class %s has fewer permissions than common %s\n", + key, cladatum->comkey); + goto bad; + } } for (i = 0; i < nel; i++) { rc = perm_read(p, &cladatum->permissions, fp); From 20697ecb299cd77b4cf8b28f655e56606b0472d8 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Thu, 23 Jul 2026 10:38:06 +0800 Subject: [PATCH 088/612] drm/bridge: ps8640: propagate AUX transfer register errors ps8640_aux_transfer_msg() programs the AUX address registers, starts the AUX transfer, waits for SWAUX_SEND to clear, and reads the AUX status register. Several of those regmap operations have return values, but the function only checks a stale ret after the status read. Propagate failures from the address write, transfer start, completion poll, and status read. This avoids returning a transfer length when the bridge register transaction or AUX completion wait failed. Fixes: 13afcdd7277e ("drm/bridge: parade-ps8640: Add support for AUX channel") Signed-off-by: Pengpeng Hou Reviewed-by: Douglas Anderson Signed-off-by: Douglas Anderson Link: https://patch.msgid.link/20260723103509.2-ps8640-v2-pengpeng@iscas.ac.cn --- drivers/gpu/drm/bridge/parade-ps8640.c | 27 ++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/bridge/parade-ps8640.c b/drivers/gpu/drm/bridge/parade-ps8640.c index b93514023baa8b..39ab94a73c88cb 100644 --- a/drivers/gpu/drm/bridge/parade-ps8640.c +++ b/drivers/gpu/drm/bridge/parade-ps8640.c @@ -257,8 +257,14 @@ static ssize_t ps8640_aux_transfer_msg(struct drm_dp_aux *aux, addr_len[PAGE0_SWAUX_LENGTH - base] = (len == 0) ? SWAUX_NO_PAYLOAD : ((len - 1) & SWAUX_LENGTH_MASK); - regmap_bulk_write(map, PAGE0_SWAUX_ADDR_7_0, addr_len, - ARRAY_SIZE(addr_len)); + ret = regmap_bulk_write(map, PAGE0_SWAUX_ADDR_7_0, addr_len, + ARRAY_SIZE(addr_len)); + if (ret) { + DRM_DEV_ERROR(dev, + "failed to write AUX address %#x, len %zu: %d\n", + msg->address, len, ret); + return ret; + } if (len && (request == DP_AUX_NATIVE_WRITE || request == DP_AUX_I2C_WRITE)) { @@ -274,13 +280,22 @@ static ssize_t ps8640_aux_transfer_msg(struct drm_dp_aux *aux, } } - regmap_write(map, PAGE0_SWAUX_CTRL, SWAUX_SEND); + ret = regmap_write(map, PAGE0_SWAUX_CTRL, SWAUX_SEND); + if (ret) { + DRM_DEV_ERROR(dev, "failed to start AUX transfer: %d\n", ret); + return ret; + } /* Zero delay loop because i2c transactions are slow already */ - regmap_read_poll_timeout(map, PAGE0_SWAUX_CTRL, data, - !(data & SWAUX_SEND), 0, 50 * 1000); + ret = regmap_read_poll_timeout(map, PAGE0_SWAUX_CTRL, data, + !(data & SWAUX_SEND), 0, 50 * 1000); + if (ret) { + DRM_DEV_ERROR(dev, "failed to complete AUX transfer: %d\n", + ret); + return ret; + } - regmap_read(map, PAGE0_SWAUX_STATUS, &data); + ret = regmap_read(map, PAGE0_SWAUX_STATUS, &data); if (ret) { DRM_DEV_ERROR(dev, "failed to read PAGE0_SWAUX_STATUS: %d\n", ret); From 3860d8748af315bfee6fe669fddc1fc17d3214db Mon Sep 17 00:00:00 2001 From: Jijie Shao Date: Fri, 24 Jul 2026 17:30:36 +0800 Subject: [PATCH 089/612] net: hns3: fix speed configuration residue after driver reload After setting a 100G optical port to 40G via ethtool and reloading the driver, the port remains at 40G instead of reverting to the firmware default speed of 100G. The commit referenced in Fixes: added two overwrites in hclge_init_ae_dev() for non-copper media, so that optical ports connected to forced-mode remotes inherit the firmware-preset autoneg and speed instead of the hardcoded defaults: req_autoneg = mac.autoneg req_speed = mac.speed (when autoneg disabled) The autoneg overwrite keeps existing behavior: hclge_set_autoneg_speed_dup() already uses mac.autoneg (not req_autoneg) since it was introduced, so autoneg inheritance from firmware was already in place. This part is kept. The speed overwrite, however, introduces the residue: mac.speed reflects whatever was last programmed into the MAC, and after unload firmware does not restore the MAC speed to the flash default. So if the user changed speed via ethtool in a prior load, mac.speed still carries that value on reload and req_speed inherits it. Fix by dropping the req_speed overwrite only. req_speed keeps the firmware default value set in hclge_configure() (cfg.default_speed), so a reload reverts the speed to default, matching the expectation that a driver reload resets link configuration. Trade-off: on optical ports whose firmware default speed does not match a forced-mode remote, reload now drops the link and the user must re-apply ethtool configuration. This is acceptable: a driver reload is expected to reset link configuration, not to inherit runtime state from before unload. The autoneg inheritance is left in place as established behavior; changing it is out of scope for this patch and would itself be a user-perceivable behavior change. Fixes: d9d349c4e8a0 ("net: hns3: differentiate autoneg default values between copper and fiber") Signed-off-by: Jijie Shao Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260724093036.426631-1-shaojijie@huawei.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c index fc8587c8081371..164c3ecf195c6d 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c +++ b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c @@ -9498,12 +9498,8 @@ static int hclge_init_ae_dev(struct hnae3_ae_dev *ae_dev) if (ret) goto err_ptp_uninit; - if (hdev->hw.mac.media_type != HNAE3_MEDIA_TYPE_COPPER) { + if (hdev->hw.mac.media_type != HNAE3_MEDIA_TYPE_COPPER) hdev->hw.mac.req_autoneg = hdev->hw.mac.autoneg; - if (hdev->hw.mac.autoneg == AUTONEG_DISABLE && - hdev->hw.mac.speed != SPEED_UNKNOWN) - hdev->hw.mac.req_speed = hdev->hw.mac.speed; - } ret = hclge_set_autoneg_speed_dup(hdev); if (ret) { From 1881f2efbf7f78dc0a79a387b29fde6ff56d3731 Mon Sep 17 00:00:00 2001 From: Fan Ye Date: Mon, 27 Jul 2026 12:29:48 +0000 Subject: [PATCH 090/612] Revert "net: thunderbolt: Enable end-to-end flow control also in transmit" This reverts commit a8065af3346ebd7c76ebc113451fb3ba94cf7769. Per the USB4 spec, a Transmit Descriptor Ring with E2E flow control disabled does not require any credits to be available before the Host Interface Adapter Layer transmits a tunneled packet from it. Once E2E is enabled on that ring the controller must first obtain end-to-end credits. The ASMedia ASM4242 USB4 host router (PCI 1b21:2425) never delivers those credits. The controller does accept the configuration: reading the ring OPTIONS register back right after tb_ring_start() returns exactly what was written, including RING_FLAG_E2E_FLOW_CONTROL (bit 28) and the E2E HopID field. No credit ever arrives though, so the Tx ring's hardware consumer index never advances and the link carries no traffic at all. Measured on two hosts connected point to point, onboard ASM4242 on MSI X870E and X870, v6.17, stock drivers/net/thunderbolt/main.c with only this revert applied on top: before: 100% packet loss to the peer; thunderbolt0 is up and the XDomain handshake completes ("new host found"), but iperf3 fails with "No route to host" once the neighbour entry expires after: 0% packet loss, 0.28 ms RTT; iperf3 4.21 Gb/s one way and 5.17 Gb/s the other (5 runs each, stddev <= 0.02), 1 retransmit in 10 s An instrumented build additionally showed a frozen-Tx-consumer watchdog firing ~30k times in a 10 s window before this change. Rx-side E2E is not touched by this revert, so peers that do return credits keep receive-side flow control. ASMedia does not look like an isolated case. The out-of-tree thunderbolt-ibverbs project disables native E2E on AMD NHI by default, noting that "Strix Halo has reproduced TX completion wedges with multiple native E2E rings active" -- the same failure mode, on a different vendor. Since the driver has no way to tell in advance which host router returns the credits, going back to the previous behaviour looks safer than adding a quirk per affected part; Tx-side E2E can be reintroduced as an opt-in for controllers that are known to implement the credit return. Note that the reverted commit was not fixing a reported problem, it was derived from the spec wording alone, so this revert is not expected to regress a known workload. Cc'ing the original author in case there was one. Fixes: a8065af3346e ("net: thunderbolt: Enable end-to-end flow control also in transmit") Cc: zhangjianrong Signed-off-by: Fan Ye Acked-by: Mika Westerberg Link: https://patch.msgid.link/20260727123002.25225-1-fy15309206903@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/thunderbolt/main.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/net/thunderbolt/main.c b/drivers/net/thunderbolt/main.c index 02a91650561ab1..c5670d61820c61 100644 --- a/drivers/net/thunderbolt/main.c +++ b/drivers/net/thunderbolt/main.c @@ -925,12 +925,8 @@ static int tbnet_open(struct net_device *dev) netif_carrier_off(dev); - flags = RING_FLAG_FRAME; - /* Only enable full E2E if the other end supports it too */ - if (tbnet_e2e && net->svc->prtcstns & TBNET_E2E) - flags |= RING_FLAG_E2E; - - ring = tb_ring_alloc_tx(xd->tb->nhi, -1, TBNET_RING_SIZE, flags); + ring = tb_ring_alloc_tx(xd->tb->nhi, -1, TBNET_RING_SIZE, + RING_FLAG_FRAME); if (!ring) { netdev_err(dev, "failed to allocate Tx ring\n"); return -ENOMEM; @@ -949,6 +945,11 @@ static int tbnet_open(struct net_device *dev) sof_mask = BIT(TBIP_PDF_FRAME_START); eof_mask = BIT(TBIP_PDF_FRAME_END); + flags = RING_FLAG_FRAME; + /* Only enable full E2E if the other end supports it too */ + if (tbnet_e2e && net->svc->prtcstns & TBNET_E2E) + flags |= RING_FLAG_E2E; + ring = tb_ring_alloc_rx(xd->tb->nhi, -1, TBNET_RING_SIZE, flags, net->tx_ring.ring->hop, sof_mask, eof_mask, tbnet_start_poll, net); From 683c6ba6e58e6ed1037831ea97dd58d9c0e76b8d Mon Sep 17 00:00:00 2001 From: "Xiang Mei (Microsoft)" Date: Sat, 25 Jul 2026 23:39:30 +0000 Subject: [PATCH 091/612] bonding: alb: re-check primary_is_promisc under RTNL in bond_alb_monitor bond_alb_monitor() reads primary_is_promisc under RCU, then drops RCU and takes RTNL via rtnl_trylock() before undoing the promiscuity it set on the active slave. In that window the active slave can change under RTNL (RTM_DELLINK -> __bond_release_one() -> bond_alb_handle_active_change()), which already drops the promiscuity and clears primary_is_promisc. The monitor still acts on the stale decision: if the slave was removed with no failover, curr_active_slave is now NULL and the deref faults; if it failed over, the stale dev_set_promiscuity(-1) underflows the new slave's promiscuity counter and pins it in IFF_PROMISC. Oops: general protection fault, probably for non-canonical address ... KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007] Workqueue: b42 bond_alb_monitor RIP: 0010:bond_alb_monitor (drivers/net/bonding/bond_alb.c:1600) process_one_work (kernel/workqueue.c:3322) worker_thread (kernel/workqueue.c:3486) kthread (kernel/kthread.c:436) ret_from_fork (arch/x86/kernel/process.c:158) Kernel panic - not syncing: Fatal exception Re-check primary_is_promisc (and curr_active_slave) after taking RTNL so the monitor only undoes an increment it still owns. The other bonding monitors already re-read state under RTNL in their commit phase (bond_miimon_commit/bond_ab_arp_commit); bond_alb_monitor() was the only one acting on the pre-trylock decision. Fixes: d0e81b7e2246 ("bonding: Acquire correct locks in alb for promisc change") Reported-by: AutonomousCodeSecurity@microsoft.com Signed-off-by: Xiang Mei (Microsoft) Reviewed-by: Nikolay Aleksandrov Acked-by: Jay Vosburgh Link: https://patch.msgid.link/20260725233930.2957317-1-xmei5@asu.edu Signed-off-by: Jakub Kicinski --- drivers/net/bonding/bond_alb.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/net/bonding/bond_alb.c b/drivers/net/bonding/bond_alb.c index 2d37b07c8215cb..839f7482dc1823 100644 --- a/drivers/net/bonding/bond_alb.c +++ b/drivers/net/bonding/bond_alb.c @@ -1534,8 +1534,8 @@ void bond_alb_monitor(struct work_struct *work) struct bonding *bond = container_of(work, struct bonding, alb_work.work); struct alb_bond_info *bond_info = &(BOND_ALB_INFO(bond)); + struct slave *slave, *curr; struct list_head *iter; - struct slave *slave; if (!bond_has_slaves(bond)) { atomic_set(&bond_info->tx_rebalance_counter, 0); @@ -1597,9 +1597,11 @@ void bond_alb_monitor(struct work_struct *work) * because a slave was disabled then * it can now leave promiscuous mode. */ - dev_set_promiscuity(rtnl_dereference(bond->curr_active_slave)->dev, - -1); - bond_info->primary_is_promisc = 0; + curr = rtnl_dereference(bond->curr_active_slave); + if (bond_info->primary_is_promisc && curr) { + dev_set_promiscuity(curr->dev, -1); + bond_info->primary_is_promisc = 0; + } rtnl_unlock(); rcu_read_lock(); From 53a43508ee332d8bffe40590c3d189c92a551f9f Mon Sep 17 00:00:00 2001 From: Pavel Begunkov Date: Mon, 27 Jul 2026 12:19:37 +0100 Subject: [PATCH 092/612] net: devmem: prevent net-iov / page mixing We should either have net_iov or page backed frags in a single skb, otherwise it blows up down the stack. Don't allow mixing in zerocopy_fill_skb_from_devmem(). Fixes: bd61848900bff ("net: devmem: Implement TX path") Cc: stable@vger.kernel.org Signed-off-by: Pavel Begunkov Acked-by: Stanislav Fomichev Reviewed-by: Mina Almasry Reviewed-by: Bobby Eshleman Link: https://patch.msgid.link/e3199788c4732545627a4721097ebb71ad737bab.1785150502.git.asml.silence@gmail.com Signed-off-by: Jakub Kicinski --- net/core/datagram.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/core/datagram.c b/net/core/datagram.c index c285c6465923e4..173b5d97bd409a 100644 --- a/net/core/datagram.c +++ b/net/core/datagram.c @@ -712,6 +712,9 @@ zerocopy_fill_skb_from_devmem(struct sk_buff *skb, struct iov_iter *from, size_t virt_addr, size, off; struct net_iov *niov; + if (i && skb_frags_readable(skb)) + return -EFAULT; + /* Devmem filling works by taking an IOVEC from the user where the * iov_addrs are interpreted as an offset in bytes into the dma-buf to * send from. We do not support other iter types. From c2707480cfbf19c7619acc9c089d17f20869821f Mon Sep 17 00:00:00 2001 From: Doruk Tan Ozturk Date: Fri, 24 Jul 2026 16:40:15 +0200 Subject: [PATCH 093/612] net/packet: reset the MAC header on the packet-socket transmit path packet_parse_headers() resets the MAC header only for a SOCK_RAW frame whose socket did not bind a protocol. A protocol-bound SOCK_RAW socket, any SOCK_DGRAM frame, and the legacy SOCK_PACKET path therefore leave skb->mac_header unset here. For frames sent via __dev_queue_xmit() this is harmless: it resets the MAC header unconditionally. But the packet-socket PACKET_QDISC_BYPASS path uses dev_direct_xmit(), which does not, so the frame reaches ndo_start_xmit() with the MAC header unset. A driver that reads eth_hdr(skb) on transmit then dereferences skb->head + (u16)~0, an out-of-bounds access ~64 KiB past the head -- the same class fixed for one consumer in commit f5089008f90c ("macsec: do not read an unset MAC header in macsec_encrypt()"). packet_parse_headers() runs only on the transmit path, where skb->data points at the start of the L2 header for every packet-socket type regardless of its length: SOCK_RAW and SOCK_PACKET carry a user-supplied header and SOCK_DGRAM has one built by dev_hard_header(). Reset the MAC header unconditionally, mirroring __dev_queue_xmit(), so the frame is anchored on the bypass path too. Found by 0sec (https://0sec.ai) using automated source analysis; verified against source and matched to the macsec KASAN report in f5089008f90c. Compile-tested. Fixes: 75c65772c3d1 ("net/packet: Ask driver for protocol if not provided by user") Cc: stable@vger.kernel.org Signed-off-by: Doruk Tan Ozturk Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260724144015.63219-1-doruk@0sec.ai Signed-off-by: Jakub Kicinski --- net/packet/af_packet.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c index e75d2932475ac0..5ae0511e89e32c 100644 --- a/net/packet/af_packet.c +++ b/net/packet/af_packet.c @@ -1924,11 +1924,12 @@ static void packet_parse_headers(struct sk_buff *skb, struct socket *sock) { int depth; + /* On TX skb->data is the L2 header; anchor it for all socket types. */ + skb_reset_mac_header(skb); + if ((!skb->protocol || skb->protocol == htons(ETH_P_ALL)) && - sock->type == SOCK_RAW) { - skb_reset_mac_header(skb); + sock->type == SOCK_RAW) skb->protocol = dev_parse_header_protocol(skb); - } /* Move network header to the right position for VLAN tagged packets */ if (likely(skb->dev->type == ARPHRD_ETHER) && From ec680ea4ba1bca92a767fb7e7869758bfdd886e3 Mon Sep 17 00:00:00 2001 From: Satish Kharat Date: Mon, 27 Jul 2026 23:26:30 -0700 Subject: [PATCH 094/612] enic: fix tx_hang_reset use-after-free on device removal enic_remove() cancels the reset and change_mtu_work items but does not cancel tx_hang_reset. A TX timeout that fires while the device is being removed can schedule enic_tx_hang_reset() so that it runs after free_netdev(), resulting in a use-after-free. cancel_work_sync() alone is not sufficient here: the still-live watchdog and notify paths can re-schedule these work items in the window between the cancel and unregister_netdev(). Use disable_work_sync(), which cancels the work and blocks any subsequent schedule_work() from requeuing it, and apply it to the reset and change_mtu_work items as well so the same requeue race is closed for all teardown work. Fixes: 937317c7c109 ("enic: do hang reset only in case of tx timeout") Signed-off-by: Satish Kharat Link: https://patch.msgid.link/20260728062730.2394873-1-satishkh@cisco.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cisco/enic/enic_main.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/cisco/enic/enic_main.c b/drivers/net/ethernet/cisco/enic/enic_main.c index e7125b81808799..d98f7e7ccab9b7 100644 --- a/drivers/net/ethernet/cisco/enic/enic_main.c +++ b/drivers/net/ethernet/cisco/enic/enic_main.c @@ -3011,8 +3011,9 @@ static void enic_remove(struct pci_dev *pdev) if (netdev) { struct enic *enic = netdev_priv(netdev); - cancel_work_sync(&enic->reset); - cancel_work_sync(&enic->change_mtu_work); + disable_work_sync(&enic->reset); + disable_work_sync(&enic->tx_hang_reset); + disable_work_sync(&enic->change_mtu_work); unregister_netdev(netdev); enic_dev_deinit(enic); vnic_dev_close(enic->vdev); From 6ddfba2ea98db21b001e0e5c472499156224650c Mon Sep 17 00:00:00 2001 From: Shay Drory Date: Tue, 28 Jul 2026 07:43:38 +0300 Subject: [PATCH 095/612] net/mlx5e: TC, Check if flow is PEER before acquiring devcom lock In case __mlx5e_add_fdb_flow() fails in lower levels, the flow is deleted via mlx5e_tc_del_flow(), and mlx5e_tc_del_flow() is acquiring ESW devcom lock without condition. In addition, in case of peer_flow, __mlx5e_add_fdb_flow() is called while holding ESW devcom comp lock. This results in an AA deadlock. To fix this, introduce a new PEER flag that is set on flows created as peer flows (the duplicate flows on peer devices), and check it in mlx5e_tc_del_flow() before acquiring ESW devcom lock. Lockdep splat: ============================================ WARNING: possible recursive locking detected ============================================ Possible unsafe locking scenario: CPU0 ---- lock(&comp->lock_key#2); lock(&comp->lock_key#2); *** DEADLOCK *** Call Trace: dump_stack_lvl+0x69/0xa0 print_deadlock_bug.cold+0xbd/0xca __lock_acquire+0x1671/0x2ec0 lock_acquire+0x10e/0x2e0 down_read+0x95/0x430 mlx5_devcom_for_each_peer_begin+0x4e/0xe0 [mlx5_core] mlx5e_tc_del_flow+0x11d/0xa70 [mlx5_core] mlx5e_flow_put+0x99/0x100 [mlx5_core] __mlx5e_add_fdb_flow+0x409/0xf00 [mlx5_core] mlx5e_configure_flower+0x2a86/0x4100 [mlx5_core] mlx5e_rep_setup_tc_cls_flower+0x12f/0x1b0 [mlx5_core] mlx5e_rep_setup_tc_cb+0x153/0x750 [mlx5_core] tc_setup_cb_add+0x1dc/0x470 fl_change+0x2f4d/0x626d [cls_flower] tc_new_tfilter+0x79b/0x2310 rtnetlink_rcv_msg+0x778/0xad0 do_syscall_64+0x70/0x960 entry_SYSCALL_64_after_hwframe+0x4b/0x53 Fixes: 04de7dda7394 ("net/mlx5e: Infrastructure for duplicated offloading of TC flows") Signed-off-by: Shay Drory Reviewed-by: Cosmin Ratiu Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260728044338.2271143-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en/tc_priv.h | 1 + drivers/net/ethernet/mellanox/mlx5/core/en_tc.c | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/tc_priv.h b/drivers/net/ethernet/mellanox/mlx5/core/en/tc_priv.h index 28cab4bf525ccf..7bfe7cdc577081 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/tc_priv.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/tc_priv.h @@ -30,6 +30,7 @@ enum { MLX5E_TC_FLOW_FLAG_FAILED = MLX5E_TC_FLOW_BASE + 9, MLX5E_TC_FLOW_FLAG_SAMPLE = MLX5E_TC_FLOW_BASE + 10, MLX5E_TC_FLOW_FLAG_USE_ACT_STATS = MLX5E_TC_FLOW_BASE + 11, + MLX5E_TC_FLOW_FLAG_PEER = MLX5E_TC_FLOW_BASE + 12, }; struct mlx5e_tc_flow_parse_attr { diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_tc.c b/drivers/net/ethernet/mellanox/mlx5/core/en_tc.c index 1bc7b9019124a4..b290beb4369ad5 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_tc.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_tc.c @@ -2161,7 +2161,8 @@ static void mlx5e_tc_del_flow(struct mlx5e_priv *priv, if (mlx5e_is_eswitch_flow(flow)) { struct mlx5_devcom_comp_dev *devcom = flow->priv->mdev->priv.eswitch->devcom; - if (!mlx5_devcom_for_each_peer_begin(devcom)) { + if (flow_flag_test(flow, PEER) || + !mlx5_devcom_for_each_peer_begin(devcom)) { mlx5e_tc_del_fdb_flow(priv, flow); return; } @@ -4628,6 +4629,7 @@ static int mlx5e_tc_add_fdb_peer_flow(struct flow_cls_offload *f, else in_mdev = priv->mdev; + flow_flags |= BIT(MLX5E_TC_FLOW_FLAG_PEER); parse_attr = flow->attr->parse_attr; peer_flow = __mlx5e_add_fdb_flow(peer_priv, f, flow_flags, parse_attr->filter_dev, From cd09971dcc1c499ae0879010a00e9dba87abdc4f Mon Sep 17 00:00:00 2001 From: "Nikhil P. Rao" Date: Mon, 27 Jul 2026 16:45:48 +0000 Subject: [PATCH 096/612] pds_core: keep the health thread stopped during reset Commit d9407ff11809 ("pds_core: Prevent health thread from running during reset/remove") stops the health thread with cancel_work_sync() before a reset, but a devcmd timeout during pdsc_fw_down() re-queues health_work, so pdsc_health_thread() runs again mid-reset and double allocates the core DMA queues via pdsc_fw_up(). Only the reset path is affected: on remove PDSC_S_STOPPING_DRIVER gates the health thread and the workqueue is destroyed. Use disable_work_sync() to cancel health_work and block further queue_work() on it, and enable_work() in pdsc_restart_health_thread() to re-allow it after the reset. disable_work_sync() keeps a disable depth, so every disable must be matched by one enable. pdsc_reset_prepare() stops the health thread and pdsc_reset_done() restarts it. On the AER path pdsc_pci_error_detected() calls pdsc_reset_prepare(), then pdsc_pci_error_resume() re-inits via pci_reset_function_locked() (pds_core has no .slot_reset handler), which runs the pair again - stopping the thread twice but restarting it once. Gate the disable and enable on a health_stopped flag so each fires at most once per stopped/running transition. Fixes: d9407ff11809 ("pds_core: Prevent health thread from running during reset/remove") Reported-by: sashiko-bot Closes: https://sashiko.dev/#/patchset/20260629200358.2626129-1-nikhil.rao%40amd.com?part=2 Signed-off-by: Nikhil P. Rao Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260727164548.359562-1-nikhil.rao@amd.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/amd/pds_core/core.h | 1 + drivers/net/ethernet/amd/pds_core/main.c | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/amd/pds_core/core.h b/drivers/net/ethernet/amd/pds_core/core.h index b7fe9ad733498f..a1d41329209fad 100644 --- a/drivers/net/ethernet/amd/pds_core/core.h +++ b/drivers/net/ethernet/amd/pds_core/core.h @@ -171,6 +171,7 @@ struct pdsc { struct timer_list wdtimer; unsigned int wdtimer_period; struct work_struct health_work; + bool health_stopped; struct devlink_health_reporter *fw_reporter; u32 fw_recoveries; diff --git a/drivers/net/ethernet/amd/pds_core/main.c b/drivers/net/ethernet/amd/pds_core/main.c index 8d94a4d70395ed..71a1d4b001c227 100644 --- a/drivers/net/ethernet/amd/pds_core/main.c +++ b/drivers/net/ethernet/amd/pds_core/main.c @@ -470,8 +470,10 @@ static void pdsc_stop_health_thread(struct pdsc *pdsc) return; timer_shutdown_sync(&pdsc->wdtimer); - if (pdsc->health_work.func) - cancel_work_sync(&pdsc->health_work); + if (pdsc->health_work.func && !pdsc->health_stopped) { + disable_work_sync(&pdsc->health_work); + pdsc->health_stopped = true; + } } static void pdsc_restart_health_thread(struct pdsc *pdsc) @@ -479,6 +481,10 @@ static void pdsc_restart_health_thread(struct pdsc *pdsc) if (pdsc->pdev->is_virtfn) return; + if (pdsc->health_stopped) { + enable_work(&pdsc->health_work); + pdsc->health_stopped = false; + } timer_setup(&pdsc->wdtimer, pdsc_wdtimer_cb, 0); mod_timer(&pdsc->wdtimer, jiffies + 1); } From 57d635329d799b79096155cdf47ee0013d6780d1 Mon Sep 17 00:00:00 2001 From: "Nikhil P. Rao" Date: Mon, 27 Jul 2026 17:00:30 +0000 Subject: [PATCH 097/612] pds_core: cancel pending PCI reset work on AER recovery pdsc_check_pci_health() queues pci_reset_work when it sees a broken PCI connection, and nothing cancels it. When the PCI core starts AER recovery, pdsc_pci_error_detected() runs pdsc_reset_prepare() and recovers the device, but a pci_reset_work queued just before is left pending. If it runs after recovery released the device lock, it resets a device the driver now considers healthy, bouncing the link for no reason. Cancel pci_reset_work in pdsc_pci_error_detected() after pdsc_reset_prepare(), which has already stopped the health thread so it cannot requeue the work. cancel_work_sync() is safe under the device lock here because pdsc_pci_reset_thread() uses pci_try_reset_function(), which returns instead of blocking on the lock. Only PFs initialize pci_reset_work, so guard the cancel with !is_virtfn. Fixes: 81665adf25d2 ("pds_core: Fix pdsc_check_pci_health function to use work thread") Reported-by: sashiko-bot Closes: https://sashiko.dev/#/patchset/20260714180223.1642792-2-nikhil.rao%40amd.com?part=1 Signed-off-by: Nikhil P. Rao Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260727170030.361116-1-nikhil.rao@amd.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/amd/pds_core/main.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/ethernet/amd/pds_core/main.c b/drivers/net/ethernet/amd/pds_core/main.c index 71a1d4b001c227..9a2c64198d03bb 100644 --- a/drivers/net/ethernet/amd/pds_core/main.c +++ b/drivers/net/ethernet/amd/pds_core/main.c @@ -561,7 +561,11 @@ static pci_ers_result_t pdsc_pci_error_detected(struct pci_dev *pdev, pci_channel_state_t error) { if (error == pci_channel_io_frozen) { + struct pdsc *pdsc = pci_get_drvdata(pdev); + pdsc_reset_prepare(pdev); + if (!pdev->is_virtfn) + cancel_work_sync(&pdsc->pci_reset_work); return PCI_ERS_RESULT_NEED_RESET; } From 2195424c3da2ef1829a63b807e3a900a90e57d85 Mon Sep 17 00:00:00 2001 From: Baul Lee Date: Mon, 27 Jul 2026 07:03:42 +0900 Subject: [PATCH 098/612] net/x25: fix use-after-free of the socket by its timers The x25 timers are armed with mod_timer() and cancelled with timer_delete(), so a pending timer holds no reference on the socket and a cancel does not wait for a callback already running on another CPU. x25_heartbeat_expiry() also rearms unconditionally, so it can reinstall sk->sk_timer after __x25_destroy_socket() has passed its cancel point. The following __sock_put() frees the socket while the timer is still queued, and the next expiry uses freed memory. KASAN reports a slab-use-after-free on the kmalloc-2k object freed by close(). timer_delete_sync() cannot be used here: x25_heartbeat_expiry() and x25_timer_expiry() both reach the cancels from inside the timer they would wait on, through __x25_destroy_socket() and x25_disconnect(). Arm the timers with sk_reset_timer() and cancel them with sk_stop_timer() so that an armed timer owns a reference, and release it in both expiry handlers. Rearm the heartbeat only while sk_hashed(sk) is still true, since __x25_destroy_socket() unlinks the socket before dropping it. Arm the deferred destroy timer the same way and drop its reference in x25_destroy_timer(). Reproduced on net with KASAN, with the heartbeat period shortened so the window recurs. With this patch the reproducer no longer triggers a report and /proc/net/x25 drains. Discovered by XBOW, triaged by Baul Lee Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Baul Lee Link: https://patch.msgid.link/20260726220342.47245-1-baul.lee@xbow.com Signed-off-by: Jakub Kicinski --- net/x25/af_x25.c | 4 ++-- net/x25/x25_timer.c | 25 ++++++++++++++++--------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/net/x25/af_x25.c b/net/x25/af_x25.c index 8aae9273b7c144..033e7d059f58c6 100644 --- a/net/x25/af_x25.c +++ b/net/x25/af_x25.c @@ -363,6 +363,7 @@ static void x25_destroy_timer(struct timer_list *t) struct sock *sk = timer_container_of(sk, t, sk_timer); x25_destroy_socket_from_timer(sk); + sock_put(sk); } /* @@ -398,9 +399,8 @@ static void __x25_destroy_socket(struct sock *sk) if (sk_has_allocations(sk)) { /* Defer: outstanding buffers */ - sk->sk_timer.expires = jiffies + 10 * HZ; sk->sk_timer.function = x25_destroy_timer; - add_timer(&sk->sk_timer); + sk_reset_timer(sk, &sk->sk_timer, jiffies + 10 * HZ); } else { /* drop last reference so sock_put will free */ __sock_put(sk); diff --git a/net/x25/x25_timer.c b/net/x25/x25_timer.c index 2ec63a1f4c6d42..7896cd43f1cc1e 100644 --- a/net/x25/x25_timer.c +++ b/net/x25/x25_timer.c @@ -36,45 +36,45 @@ void x25_init_timers(struct sock *sk) void x25_start_heartbeat(struct sock *sk) { - mod_timer(&sk->sk_timer, jiffies + 5 * HZ); + sk_reset_timer(sk, &sk->sk_timer, jiffies + 5 * HZ); } void x25_stop_heartbeat(struct sock *sk) { - timer_delete(&sk->sk_timer); + sk_stop_timer(sk, &sk->sk_timer); } void x25_start_t2timer(struct sock *sk) { struct x25_sock *x25 = x25_sk(sk); - mod_timer(&x25->timer, jiffies + x25->t2); + sk_reset_timer(sk, &x25->timer, jiffies + x25->t2); } void x25_start_t21timer(struct sock *sk) { struct x25_sock *x25 = x25_sk(sk); - mod_timer(&x25->timer, jiffies + x25->t21); + sk_reset_timer(sk, &x25->timer, jiffies + x25->t21); } void x25_start_t22timer(struct sock *sk) { struct x25_sock *x25 = x25_sk(sk); - mod_timer(&x25->timer, jiffies + x25->t22); + sk_reset_timer(sk, &x25->timer, jiffies + x25->t22); } void x25_start_t23timer(struct sock *sk) { struct x25_sock *x25 = x25_sk(sk); - mod_timer(&x25->timer, jiffies + x25->t23); + sk_reset_timer(sk, &x25->timer, jiffies + x25->t23); } void x25_stop_timer(struct sock *sk) { - timer_delete(&x25_sk(sk)->timer); + sk_stop_timer(sk, &x25_sk(sk)->timer); } unsigned long x25_display_timer(struct sock *sk) @@ -108,7 +108,7 @@ static void x25_heartbeat_expiry(struct timer_list *t) sock_flag(sk, SOCK_DEAD))) { bh_unlock_sock(sk); x25_destroy_socket_from_timer(sk); - return; + goto out; } break; @@ -120,8 +120,14 @@ static void x25_heartbeat_expiry(struct timer_list *t) break; } restart_heartbeat: - x25_start_heartbeat(sk); + /* Do not rearm once __x25_destroy_socket() has unlinked the socket: + * it is past its cancel point and owns the teardown from there on. + */ + if (sk_hashed(sk)) + x25_start_heartbeat(sk); bh_unlock_sock(sk); +out: + sock_put(sk); } /* @@ -166,4 +172,5 @@ static void x25_timer_expiry(struct timer_list *t) } else x25_do_timer_expiry(sk); bh_unlock_sock(sk); + sock_put(sk); } From ab99eaafb0c4b412cfeb895a8cf091626e2bbd86 Mon Sep 17 00:00:00 2001 From: Ekansh Gupta Date: Fri, 24 Jul 2026 23:33:37 +0100 Subject: [PATCH 099/612] misc: fastrpc: Fix initial memory allocation for Audio PD memory pool The initial buffer allocated for the Audio PD memory pool is never added to the pool because pageslen is set to 0. As a result, the buffer is not registered with Audio PD and is never used, causing a memory leak. Audio PD immediately falls back to allocating memory from the remote heap since the pool starts out empty. Fix this by setting pageslen to 1 so that the initially allocated buffer is correctly registered and becomes part of the Audio PD memory pool. Fixes: 0871561055e66 ("misc: fastrpc: Add support for audiopd") Cc: stable@kernel.org Reviewed-by: Dmitry Baryshkov Signed-off-by: Ekansh Gupta Signed-off-by: Jianping Li Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260724223342.629168-2-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/misc/fastrpc.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/misc/fastrpc.c b/drivers/misc/fastrpc.c index f3a49384586d1f..4f01ebfa6f95e4 100644 --- a/drivers/misc/fastrpc.c +++ b/drivers/misc/fastrpc.c @@ -1370,7 +1370,9 @@ static int fastrpc_init_create_static_process(struct fastrpc_user *fl, err = PTR_ERR(name); goto err; } - + inbuf.client_id = fl->client_id; + inbuf.namelen = init.namelen; + inbuf.pageslen = 0; if (!fl->cctx->remote_heap) { err = fastrpc_remote_heap_alloc(fl, fl->sctx->dev, init.memlen, &fl->cctx->remote_heap); @@ -1393,12 +1395,10 @@ static int fastrpc_init_create_static_process(struct fastrpc_user *fl, goto err_map; } scm_done = true; + inbuf.pageslen = 1; } } - inbuf.client_id = fl->client_id; - inbuf.namelen = init.namelen; - inbuf.pageslen = 0; fl->pd = USER_PD; args[0].ptr = (u64)(uintptr_t)&inbuf; From 6102ceb4eab845743ee57acd3863fbd06e93c927 Mon Sep 17 00:00:00 2001 From: Ekansh Gupta Date: Fri, 24 Jul 2026 23:33:38 +0100 Subject: [PATCH 100/612] misc: fastrpc: Remove buffer from list prior to unmap operation fastrpc_req_munmap_impl() is called to unmap any buffer. The buffer is getting removed from the list after it is unmapped from DSP. This can create potential race conditions if multiple threads invoke unmap concurrently, where one thread may remove the entry from the list while another thread's unmap operation is still ongoing. Fix this by removing the buffer entry from the list before calling the unmap operation. If the unmap fails, the entry is re-added to the list so that userspace can retry the unmap, or alternatively, the buffer will be cleaned up during device release when the DSP process is torn down and all DSP-side mappings are freed along with remaining buffers in the list. Fixes: 2419e55e532de ("misc: fastrpc: add mmap/unmap support") Cc: stable@kernel.org Reviewed-by: Dmitry Baryshkov Signed-off-by: Ekansh Gupta Signed-off-by: Jianping Li Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260724223342.629168-3-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/misc/fastrpc.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/misc/fastrpc.c b/drivers/misc/fastrpc.c index 4f01ebfa6f95e4..12dcd2e737c31d 100644 --- a/drivers/misc/fastrpc.c +++ b/drivers/misc/fastrpc.c @@ -1889,9 +1889,6 @@ static int fastrpc_req_munmap_impl(struct fastrpc_user *fl, struct fastrpc_buf * &args[0]); if (!err) { dev_dbg(dev, "unmmap\tpt 0x%09lx OK\n", buf->raddr); - spin_lock(&fl->lock); - list_del(&buf->node); - spin_unlock(&fl->lock); fastrpc_buf_free(buf); } else { dev_err(dev, "unmmap\tpt 0x%09lx ERROR\n", buf->raddr); @@ -1905,6 +1902,7 @@ static int fastrpc_req_munmap(struct fastrpc_user *fl, char __user *argp) struct fastrpc_buf *buf = NULL, *iter, *b; struct fastrpc_req_munmap req; struct device *dev = fl->sctx->dev; + int err; if (copy_from_user(&req, argp, sizeof(req))) return -EFAULT; @@ -1912,6 +1910,7 @@ static int fastrpc_req_munmap(struct fastrpc_user *fl, char __user *argp) spin_lock(&fl->lock); list_for_each_entry_safe(iter, b, &fl->mmaps, node) { if ((iter->raddr == req.vaddrout) && (iter->size == req.size)) { + list_del(&iter->node); buf = iter; break; } @@ -1924,7 +1923,14 @@ static int fastrpc_req_munmap(struct fastrpc_user *fl, char __user *argp) return -EINVAL; } - return fastrpc_req_munmap_impl(fl, buf); + err = fastrpc_req_munmap_impl(fl, buf); + if (err) { + spin_lock(&fl->lock); + list_add_tail(&buf->node, &fl->mmaps); + spin_unlock(&fl->lock); + } + + return err; } static int fastrpc_req_mmap(struct fastrpc_user *fl, char __user *argp) From b85a0e91d7d6cd06a53c881a46f749cfcef416a2 Mon Sep 17 00:00:00 2001 From: Junrui Luo Date: Fri, 24 Jul 2026 23:33:39 +0100 Subject: [PATCH 101/612] misc: fastrpc: take fl->lock when moving mmaps on interrupted invoke When an invoke is interrupted by a signal, wait_for_completion_interruptible() returns -ERESTARTSYS and fastrpc_internal_invoke() moves every buffer from fl->mmaps onto cctx->invoke_interrupted_mmaps. This list_del()/list_add_tail() walk runs without holding fl->lock, the lock that serialises fl->mmaps in fastrpc_req_mmap() and fastrpc_req_munmap() everywhere else. Take fl->lock around the move, matching every other fl->mmaps accessor. Fixes: 76e8e4ace1ed ("misc: fastrpc: Safekeep mmaps on interrupted invoke") Reported-by: Yuhao Jiang Cc: stable@vger.kernel.org Signed-off-by: Junrui Luo Reviewed-by: Dmitry Baryshkov Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260724223342.629168-4-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/misc/fastrpc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/misc/fastrpc.c b/drivers/misc/fastrpc.c index 12dcd2e737c31d..c75eafe872cc4c 100644 --- a/drivers/misc/fastrpc.c +++ b/drivers/misc/fastrpc.c @@ -1305,10 +1305,12 @@ static int fastrpc_internal_invoke(struct fastrpc_user *fl, u32 kernel, } if (err == -ERESTARTSYS) { + spin_lock(&fl->lock); list_for_each_entry_safe(buf, b, &fl->mmaps, node) { list_del(&buf->node); list_add_tail(&buf->node, &fl->cctx->invoke_interrupted_mmaps); } + spin_unlock(&fl->lock); } if (err) From 310f7868399668c6d99d88acc9c4cf3462e69d5b Mon Sep 17 00:00:00 2001 From: Anandu Krishnan E Date: Fri, 24 Jul 2026 23:33:40 +0100 Subject: [PATCH 102/612] misc: fastrpc: fix channel ctx ref leak when session alloc fails fastrpc_channel_ctx_get() is called in fastrpc_device_open() before fastrpc_session_alloc(). If session alloc fails, the error path returns -EBUSY without calling fastrpc_channel_ctx_put(), leaking the reference. Fix by adding the missing put. Fixes: 278d56f970ae ("misc: fastrpc: Reference count channel context") Cc: stable@kernel.org Signed-off-by: Anandu Krishnan E Reviewed-by: Dmitry Baryshkov Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260724223342.629168-5-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/misc/fastrpc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/misc/fastrpc.c b/drivers/misc/fastrpc.c index c75eafe872cc4c..f8cbe30c5d5c2d 100644 --- a/drivers/misc/fastrpc.c +++ b/drivers/misc/fastrpc.c @@ -1675,7 +1675,7 @@ static int fastrpc_device_open(struct inode *inode, struct file *filp) dev_err(&cctx->rpdev->dev, "No session available\n"); mutex_destroy(&fl->mutex); kfree(fl); - + fastrpc_channel_ctx_put(cctx); return -EBUSY; } From 2fae94ee14f7fea11d3f95e10383a87c01d21518 Mon Sep 17 00:00:00 2001 From: Eddie Lin Date: Fri, 24 Jul 2026 23:33:41 +0100 Subject: [PATCH 103/612] misc: fastrpc: fix memory leak in fastrpc_channel_ctx_free The 'ctx_idr' is initialized but never destroyed when the channel context is freed, leading to a memory leak. Add idr_destroy() to properly clean up the IDR resources. Fixes: f6f9279f2bf0 ("misc: fastrpc: Add Qualcomm fastrpc basic driver model") Cc: stable@vger.kernel.org Signed-off-by: Eddie Lin Reviewed-by: Ekansh Gupta Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260724223342.629168-6-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/misc/fastrpc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/misc/fastrpc.c b/drivers/misc/fastrpc.c index f8cbe30c5d5c2d..eb6c2a78d3c7b7 100644 --- a/drivers/misc/fastrpc.c +++ b/drivers/misc/fastrpc.c @@ -492,6 +492,7 @@ static void fastrpc_channel_ctx_free(struct kref *ref) cctx = container_of(ref, struct fastrpc_channel_ctx, refcount); + idr_destroy(&cctx->ctx_idr); kfree(cctx); } From ff10b6db0ab75b132aed69ab144ac04f63ef9bdb Mon Sep 17 00:00:00 2001 From: Aelin Reidel Date: Fri, 24 Jul 2026 23:34:03 +0100 Subject: [PATCH 104/612] nvmem: apple-spmi-nvmem: wrap regmap calls to satisfy CFI The Apple SPMI NVMEM driver previously cast regmap_bulk_read/write to void * when assigning them to nvmem_config's reg_read/reg_write function pointers. This cast breaks the expected function signature of nvmem_reg_read_t and nvmem_reg_write_t. With CFI enabled, indirect calls through these pointers fail: CFI failure at nvmem_reg_write+0x194/0x1e4 (target: regmap_bulk_write+0x0/0x2c8; expected type: 0x83a189c3) ... Call trace: nvmem_reg_write+0x194/0x1e4 (P) __nvmem_cell_entry_write+0x298/0x2e8 nvmem_cell_write+0x24/0x34 macsmc_reboot_probe+0x1dc/0x454 [macsmc_reboot] ... Introduce thin wrapper functions with the correct nvmem function pointer types to satisfy the CFI checks. Fixes: fe91c24a551c ("nvmem: Add apple-spmi-nvmem driver") Signed-off-by: Aelin Reidel Reported-by: Clayton Craft Tested-by: Clayton Craft Reviewed-by: Sven Peter Cc: stable@vger.kernel.org Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260724223404.629248-2-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/apple-spmi-nvmem.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/drivers/nvmem/apple-spmi-nvmem.c b/drivers/nvmem/apple-spmi-nvmem.c index 88614005d5ce1d..7acb0c07d6abe9 100644 --- a/drivers/nvmem/apple-spmi-nvmem.c +++ b/drivers/nvmem/apple-spmi-nvmem.c @@ -18,6 +18,22 @@ static const struct regmap_config apple_spmi_regmap_config = { .max_register = 0xffff, }; +static int apple_spmi_nvmem_read(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + struct regmap *map = priv; + + return regmap_bulk_read(map, offset, val, bytes); +} + +static int apple_spmi_nvmem_write(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + struct regmap *map = priv; + + return regmap_bulk_write(map, offset, val, bytes); +} + static int apple_spmi_nvmem_probe(struct spmi_device *sdev) { struct regmap *regmap; @@ -28,8 +44,8 @@ static int apple_spmi_nvmem_probe(struct spmi_device *sdev) .word_size = 1, .stride = 1, .size = 0xffff, - .reg_read = (void *)regmap_bulk_read, - .reg_write = (void *)regmap_bulk_write, + .reg_read = apple_spmi_nvmem_read, + .reg_write = apple_spmi_nvmem_write, }; regmap = devm_regmap_init_spmi_ext(sdev, &apple_spmi_regmap_config); From b5be879519291f139fa7b365fd0dbc84710e4919 Mon Sep 17 00:00:00 2001 From: Mathieu Dubois-Briand Date: Fri, 24 Jul 2026 23:34:04 +0100 Subject: [PATCH 105/612] nvmem: layouts: Add fixed-layout driver Current implementation isn't working well when device tree nodes have a phandle on a fixed-layout nvmem node. As the fixed layout is handled in nvmem core, no driver is ever associated with the layout, and the device consumer driver probe is deferred indefinitely. Remove the specific handling of fixed-layout and add a layout driver. This makes the fixed-layout similar to all other layouts, fixing the whole issue. Fixes: fc29fd821d9a ("nvmem: core: Rework layouts to become regular devices") Cc: stable@vger.kernel.org Reviewed-by: Miquel Raynal Signed-off-by: Mathieu Dubois-Briand Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260724223404.629248-3-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 5 +++ drivers/nvmem/core.c | 24 +----------- drivers/nvmem/internals.h | 2 + drivers/nvmem/layouts.c | 11 ------ drivers/nvmem/layouts/Makefile | 1 + drivers/nvmem/layouts/fixed-layout.c | 58 ++++++++++++++++++++++++++++ include/linux/nvmem-provider.h | 6 +++ 7 files changed, 74 insertions(+), 33 deletions(-) create mode 100644 drivers/nvmem/layouts/fixed-layout.c diff --git a/MAINTAINERS b/MAINTAINERS index 716acfc3d7c1b8..1b044ddc68f983 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10134,6 +10134,11 @@ F: drivers/base/firmware_loader/ F: rust/kernel/firmware.rs F: include/linux/firmware.h +FIXED-LAYOUT NVMEM LAYOUT DRIVER +M: Mathieu Dubois-Briand +S: Maintained +F: drivers/nvmem/layouts/fixed-layout.c + FLEXTIMER FTM-QUADDEC DRIVER M: Patrick Havelange L: linux-iio@vger.kernel.org diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c index e871181751f3c2..2f971b059704fe 100644 --- a/drivers/nvmem/core.c +++ b/drivers/nvmem/core.c @@ -786,7 +786,7 @@ static int nvmem_validate_keepouts(struct nvmem_device *nvmem) return 0; } -static int nvmem_add_cells_from_dt(struct nvmem_device *nvmem, struct device_node *np) +int nvmem_add_cells_from_dt(struct nvmem_device *nvmem, struct device_node *np) { struct device *dev = &nvmem->dev; const __be32 *addr; @@ -834,29 +834,13 @@ static int nvmem_add_cells_from_dt(struct nvmem_device *nvmem, struct device_nod return 0; } +EXPORT_SYMBOL_GPL(nvmem_add_cells_from_dt); static int nvmem_add_cells_from_legacy_of(struct nvmem_device *nvmem) { return nvmem_add_cells_from_dt(nvmem, nvmem->dev.of_node); } -static int nvmem_add_cells_from_fixed_layout(struct nvmem_device *nvmem) -{ - struct device_node *layout_np; - int err = 0; - - layout_np = of_nvmem_layout_get_container(nvmem); - if (!layout_np) - return 0; - - if (of_device_is_compatible(layout_np, "fixed-layout")) - err = nvmem_add_cells_from_dt(nvmem, layout_np); - - of_node_put(layout_np); - - return err; -} - int nvmem_layout_register(struct nvmem_layout *layout) { int ret; @@ -1005,10 +989,6 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config) goto err_remove_cells; } - rval = nvmem_add_cells_from_fixed_layout(nvmem); - if (rval) - goto err_remove_cells; - dev_dbg(&nvmem->dev, "Registering nvmem device %s\n", config->name); rval = device_add(&nvmem->dev); diff --git a/drivers/nvmem/internals.h b/drivers/nvmem/internals.h index 18fed57270e5e3..f6d452b6a28f15 100644 --- a/drivers/nvmem/internals.h +++ b/drivers/nvmem/internals.h @@ -35,6 +35,8 @@ struct nvmem_device { bool sysfs_cells_populated; }; +int nvmem_add_cells_from_dt(struct nvmem_device *nvmem, struct device_node *np); + #if IS_ENABLED(CONFIG_OF) int nvmem_layout_bus_register(void); void nvmem_layout_bus_unregister(void); diff --git a/drivers/nvmem/layouts.c b/drivers/nvmem/layouts.c index b90584e1b99eab..07a34be9669ca1 100644 --- a/drivers/nvmem/layouts.c +++ b/drivers/nvmem/layouts.c @@ -125,11 +125,6 @@ static int nvmem_layout_create_device(struct nvmem_device *nvmem, return 0; } -static const struct of_device_id of_nvmem_layout_skip_table[] = { - { .compatible = "fixed-layout", }, - {} -}; - static int nvmem_layout_bus_populate(struct nvmem_device *nvmem, struct device_node *layout_dn) { @@ -142,12 +137,6 @@ static int nvmem_layout_bus_populate(struct nvmem_device *nvmem, return 0; } - /* Fixed layouts are parsed manually somewhere else for now */ - if (of_match_node(of_nvmem_layout_skip_table, layout_dn)) { - pr_debug("%s() - skipping %pOF node\n", __func__, layout_dn); - return 0; - } - if (of_node_check_flag(layout_dn, OF_POPULATED_BUS)) { pr_debug("%s() - skipping %pOF, already populated\n", __func__, layout_dn); diff --git a/drivers/nvmem/layouts/Makefile b/drivers/nvmem/layouts/Makefile index 4940c9db066564..dd6c6c70b1a92c 100644 --- a/drivers/nvmem/layouts/Makefile +++ b/drivers/nvmem/layouts/Makefile @@ -3,6 +3,7 @@ # Makefile for nvmem layouts. # +obj-$(CONFIG_NVMEM_LAYOUTS) += fixed-layout.o obj-$(CONFIG_NVMEM_LAYOUT_SL28_VPD) += sl28vpd.o obj-$(CONFIG_NVMEM_LAYOUT_ONIE_TLV) += onie-tlv.o obj-$(CONFIG_NVMEM_LAYOUT_U_BOOT_ENV) += u-boot-env.o diff --git a/drivers/nvmem/layouts/fixed-layout.c b/drivers/nvmem/layouts/fixed-layout.c new file mode 100644 index 00000000000000..635d448b3dd256 --- /dev/null +++ b/drivers/nvmem/layouts/fixed-layout.c @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2026 Bootlin + * + * Authors: Mathieu Dubois-Briand + */ + +#include +#include + +#include "../internals.h" + +static int fixed_layout_add_cells(struct nvmem_layout *layout) +{ + struct device_node *np; + int ret; + + np = of_nvmem_layout_get_container(layout->nvmem); + if (!np) + return -ENOENT; + + ret = nvmem_add_cells_from_dt(layout->nvmem, np); + of_node_put(np); + + return ret; +} + +static int fixed_layout_probe(struct nvmem_layout *layout) +{ + layout->add_cells = fixed_layout_add_cells; + + return nvmem_layout_register(layout); +} + +static void fixed_layout_remove(struct nvmem_layout *layout) +{ + nvmem_layout_unregister(layout); +} + +static const struct of_device_id fixed_layout_of_match_table[] = { + { .compatible = "fixed-layout", }, + {}, +}; + +static struct nvmem_layout_driver fixed_layout_layout = { + .driver = { + .name = "fixed-layout", + .of_match_table = fixed_layout_of_match_table, + }, + .probe = fixed_layout_probe, + .remove = fixed_layout_remove, +}; +module_nvmem_layout_driver(fixed_layout_layout); + +MODULE_AUTHOR("Mathieu Dubois-Briand"); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE(of, fixed_layout_of_match_table); +MODULE_DESCRIPTION("NVMEM fixed-layout driver"); diff --git a/include/linux/nvmem-provider.h b/include/linux/nvmem-provider.h index f3b13da78aac94..6063fe5b7784ac 100644 --- a/include/linux/nvmem-provider.h +++ b/include/linux/nvmem-provider.h @@ -214,6 +214,12 @@ static inline int nvmem_layout_register(struct nvmem_layout *layout) static inline void nvmem_layout_unregister(struct nvmem_layout *layout) {} +static inline int nvmem_add_cells_from_dt(struct nvmem_device *nvmem, + struct device_node *np) +{ + return -EOPNOTSUPP; +} + #endif /* CONFIG_NVMEM */ #if IS_ENABLED(CONFIG_NVMEM) && IS_ENABLED(CONFIG_OF) From dd7aea9ee2091cfae3a5e376af87aa106d7735cd Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Mon, 27 Jul 2026 12:28:59 +0000 Subject: [PATCH 106/612] rust_binder: do not query current thread for all ioctls The get_current_thread() method is currently called for every ioctl to ensure that a Thread struct exists for the thread calling into the driver. However, not all ioctls require a Thread object, so this means we are unnecessarily creating these objects in cases where we don't need to. If said thread does not invoke BINDER_THREAD_EXIT on exit, Binder's Thread struct stays around until the fd is closed. For long-lived processes the Thread object is effectively leaked. Furthermore, when the BINDER_GET_NODE_DEBUG_INFO ioctl is invoked by libmemunreachable to ensure that objects reachable only through the Binder driver are not considered leaked, this is done from a fork of the process owning the fd, which means that it fails the group_leader check inside get_current_thread(). This results in EINVAL errors for this ioctl, causing libmemunreachable to report a false positive memory leak. Thus, do not invoke get_current_thread() for ioctls that do not require it. Signed-off-by: Alice Ryhl Cc: stable Fixes: eafedbc7c050 ("rust_binder: add Rust Binder driver") Acked-by: Carlos Llamas Link: https://patch.msgid.link/20260727-binder-cur-thread-v1-1-8edf2b64e235@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/process.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/android/binder/process.rs b/drivers/android/binder/process.rs index cdd1a907972667..5b8f73ec193124 100644 --- a/drivers/android/binder/process.rs +++ b/drivers/android/binder/process.rs @@ -1586,6 +1586,10 @@ impl Process { cmd: u32, reader: &mut UserSliceReader, ) -> Result { + if cmd == uapi::BINDER_FREEZE { + return ioctl_freeze(reader); + } + let thread = this.get_current_thread()?; match cmd { uapi::BINDER_SET_MAX_THREADS => this.set_max_threads(reader.read()?), @@ -1597,7 +1601,6 @@ impl Process { uapi::BINDER_ENABLE_ONEWAY_SPAM_DETECTION => { this.set_oneway_spam_detection_enabled(reader.read()?) } - uapi::BINDER_FREEZE => ioctl_freeze(reader)?, _ => return Err(EINVAL), } Ok(()) @@ -1612,15 +1615,16 @@ impl Process { cmd: u32, data: UserSlice, ) -> Result { - let thread = this.get_current_thread()?; let blocking = (file.flags() & file::flags::O_NONBLOCK) == 0; match cmd { - uapi::BINDER_WRITE_READ => thread.write_read(data, blocking)?, + uapi::BINDER_WRITE_READ => this.get_current_thread()?.write_read(data, blocking)?, uapi::BINDER_GET_NODE_DEBUG_INFO => this.get_node_debug_info(data)?, uapi::BINDER_GET_NODE_INFO_FOR_REF => this.get_node_info_from_ref(data)?, uapi::BINDER_VERSION => this.version(data)?, uapi::BINDER_GET_FROZEN_INFO => get_frozen_status(data)?, - uapi::BINDER_GET_EXTENDED_ERROR => thread.get_extended_error(data)?, + uapi::BINDER_GET_EXTENDED_ERROR => { + this.get_current_thread()?.get_extended_error(data)? + } _ => return Err(EINVAL), } Ok(()) From b0495bb58af06a7de4628c72d500e3d5e180d808 Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Sun, 19 Jul 2026 12:57:55 +0300 Subject: [PATCH 107/612] mei: pull kvfree out of spinlock The read buffer allocation was changed from kmalloc() to kvmalloc(). This buffer is part of mei_cl_cb structure that can be queued in rd_complete queue protected by spinlock. Releasing the structure leads to errors like below when freeing buffer that allocated non-contiguous: BUG: sleeping function called from invalid context at mm/vmalloc.c:3448 Separate mei_cl_cb structure dequeue and release to perform only dequeue under spinlock and push release out of spinlock. Cc: stable Fixes: 4adf613e01bf ("mei: use kvmalloc for read buffer") Closes: https://gitlab.freedesktop.org/drm/i915/kernel/-/work_items/16359 Reviewed-by: Menachem Adin Signed-off-by: Alexander Usyskin Link: https://patch.msgid.link/20260719-kvfree_out_of_spinlock-v1-1-e07d6333bea7@intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/client.c | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index 643b0039cc72c4..26d2b2742d5094 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -425,18 +425,24 @@ static void mei_io_tx_list_free_cl(struct list_head *head, } /** - * mei_io_list_free_fp - free cb from a list that matches file pointer + * mei_io_rd_list_free_fp - free cb from a rd_completed list that matches file pointer * - * @head: io list + * @cl: host client * @fp: file pointer (matching cb file object), may be NULL */ -static void mei_io_list_free_fp(struct list_head *head, const struct file *fp) +static void mei_io_rd_list_free_fp(struct mei_cl *cl, const struct file *fp) { struct mei_cl_cb *cb, *next; + LIST_HEAD(cmpl_list); - list_for_each_entry_safe(cb, next, head, list) + spin_lock(&cl->rd_completed_lock); + list_for_each_entry_safe(cb, next, &cl->rd_completed, list) if (!fp || fp == cb->fp) - mei_io_cb_free(cb); + list_move(&cb->list, &cmpl_list); + spin_unlock(&cl->rd_completed_lock); + + list_for_each_entry_safe(cb, next, &cmpl_list, list) + mei_io_cb_free(cb); } /** @@ -565,9 +571,7 @@ int mei_cl_flush_queues(struct mei_cl *cl, const struct file *fp) mei_io_list_flush_cl(&cl->dev->ctrl_rd_list, cl); mei_cl_free_pending(cl); } - spin_lock(&cl->rd_completed_lock); - mei_io_list_free_fp(&cl->rd_completed, fp); - spin_unlock(&cl->rd_completed_lock); + mei_io_rd_list_free_fp(cl, fp); return 0; } @@ -1401,7 +1405,7 @@ void mei_cl_add_rd_completed(struct mei_cl *cl, struct mei_cl_cb *cb) } /** - * mei_cl_del_rd_completed - free read completed callback with lock + * mei_cl_del_rd_completed - unlink read completed callback with lock and free it * * @cl: host client * @cb: callback block @@ -1410,8 +1414,9 @@ void mei_cl_add_rd_completed(struct mei_cl *cl, struct mei_cl_cb *cb) void mei_cl_del_rd_completed(struct mei_cl *cl, struct mei_cl_cb *cb) { spin_lock(&cl->rd_completed_lock); - mei_io_cb_free(cb); + list_del_init(&cb->list); spin_unlock(&cl->rd_completed_lock); + mei_io_cb_free(cb); } /** From 558f67f1340f803a346ecd14a69c49653111c5f4 Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Wed, 29 Jul 2026 21:56:59 +0800 Subject: [PATCH 108/612] ipvs: stop estimator after disabled calc phase IPVS estimator kthread 0 starts with zeroed chain and tick limits until its initial calculation phase completes. If network namespace teardown clears ipvs->enable during that phase, ip_vs_est_calc_phase() can return without installing positive limits. The kthread can then continue into its main loop and drain est_temp_list with zero chain_max, tick_max and est_max_count values. Each enqueue consumes one available tick row, but est_count never reaches the zero est_max_count value. After all rows are consumed, the row lookup returns IPVS_EST_NTICKS and ip_vs_enqueue_estimator() writes past the ticks and tick_len arrays. Exit kthread 0 after the calculation phase if the kthread is stopping or IPVS has been disabled. That keeps temporary estimators from being drained after the limits failed to initialize. Estimator kthreads can now self-exit before teardown or reload stops kd->task. Keep an extra task reference after creation and release it with kthread_stop_put(), so kd->task remains valid until the stop paths consume that reference. Fixes: 705dd3444081 ("ipvs: use kthreads for stats estimation") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Zhiling Zou Acked-by: Julian Anastasov Signed-off-by: Pablo Neira Ayuso --- net/netfilter/ipvs/ip_vs_est.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/net/netfilter/ipvs/ip_vs_est.c b/net/netfilter/ipvs/ip_vs_est.c index ab09f518295122..05a216a47b45e3 100644 --- a/net/netfilter/ipvs/ip_vs_est.c +++ b/net/netfilter/ipvs/ip_vs_est.c @@ -191,8 +191,11 @@ static int ip_vs_estimation_kthread(void *data) } /* kthread 0 will handle the calc phase */ - if (ipvs->est_calc_phase) + if (ipvs->est_calc_phase) { ip_vs_est_calc_phase(ipvs); + if (kthread_should_stop() || !READ_ONCE(ipvs->enable)) + return 0; + } } while (1) { @@ -270,6 +273,7 @@ int ip_vs_est_kthread_start(struct netns_ipvs *ipvs, kd->task = NULL; goto out; } + get_task_struct(kd->task); set_user_nice(kd->task, sysctl_est_nice(ipvs)); if (sysctl_est_preferred_cpulist(ipvs)) @@ -286,7 +290,7 @@ void ip_vs_est_kthread_stop(struct ip_vs_est_kt_data *kd) { if (kd->task) { pr_info("stopping estimator thread %d...\n", kd->id); - kthread_stop(kd->task); + kthread_stop_put(kd->task); kd->task = NULL; } } @@ -526,7 +530,7 @@ static void ip_vs_est_kthread_destroy(struct ip_vs_est_kt_data *kd) if (kd) { if (kd->task) { pr_info("stop unused estimator thread %d...\n", kd->id); - kthread_stop(kd->task); + kthread_stop_put(kd->task); } ip_vs_stats_free(kd->calc_stats); kfree(kd); From 30825970339c107bacaf7f61af90fcdb1f597ca1 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Thu, 30 Jul 2026 01:31:00 +0800 Subject: [PATCH 109/612] netfilter: ebt_nflog: pin the NFLOG backend nf_log_unregister() runs after the per-net teardown so its final RCU grace period also drains readers that obtained the logger from a per-net binding. However, ebt_nflog passes an explicit ULOG log type to nf_log_packet() without holding a reference on the selected logger module, unlike the xt_NFLOG and nft_log frontends. An ebtables nflog rule can therefore remain callable while nfnetlink_log is unloaded. The resulting interleaving is: CPU 0 CPU 1 nfnetlink_log_fini() unregister_pernet_subsys() kfree(nfnl_log_pernet(net)) ebt_nflog_tg() nf_log_packet() nfulnl_log_packet() instance_lookup_get_rcu() The global ULOG logger is still registered at this point, so CPU 1 dereferences the per-net state after CPU 0 has freed it. KASAN reported: BUG: KASAN: slab-use-after-free in instance_lookup_get_rcu Read of size 8 at addr ff110001052e6210 by task poc/92 Call Trace: instance_lookup_get_rcu+0x1ce/0x1f0 [nfnetlink_log] nfulnl_log_packet+0x248/0x2fb0 [nfnetlink_log] nf_log_packet+0x204/0x300 ebt_nflog_tg+0x351/0x550 ebt_do_table+0xedf/0x22b0 Allocated by task 90: __kmalloc_noprof+0x186/0x470 ops_init+0x6d/0x420 register_pernet_operations+0x2f6/0x670 register_pernet_subsys+0x23/0x40 Freed by task 93: kfree+0x131/0x3c0 ops_undo_list+0x3e3/0x700 unregister_pernet_operations+0x232/0x490 unregister_pernet_subsys+0x1c/0x30 nfnetlink_log_fini+0x34/0x450 [nfnetlink_log] Acquire the ULOG logger module reference when an ebt_nflog rule is validated and release it when the rule is destroyed. Request the NFLOG backend for legacy callers when needed, matching xt_NFLOG. This prevents module teardown until all ebt_nflog rules have stopped using the logger. Fixes: c83fa19603bd ("netfilter: nf_log: don't call synchronize_rcu in nf_log_unset") Cc: stable@vger.kernel.org Signed-off-by: Chengfeng Ye Signed-off-by: Pablo Neira Ayuso --- net/bridge/netfilter/ebt_nflog.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/net/bridge/netfilter/ebt_nflog.c b/net/bridge/netfilter/ebt_nflog.c index 61bf8f4465ab7d..426f8adc912c75 100644 --- a/net/bridge/netfilter/ebt_nflog.c +++ b/net/bridge/netfilter/ebt_nflog.c @@ -41,11 +41,25 @@ ebt_nflog_tg(struct sk_buff *skb, const struct xt_action_param *par) static int ebt_nflog_tg_check(const struct xt_tgchk_param *par) { struct ebt_nflog_info *info = par->targinfo; + int ret; if (info->flags & ~EBT_NFLOG_MASK) return -EINVAL; info->prefix[EBT_NFLOG_PREFIX_SIZE - 1] = '\0'; - return 0; + + ret = nf_logger_find_get(par->family, NF_LOG_TYPE_ULOG); + if (ret != 0 && !par->nft_compat) { + request_module("%s", "nfnetlink_log"); + + ret = nf_logger_find_get(par->family, NF_LOG_TYPE_ULOG); + } + + return ret; +} + +static void ebt_nflog_tg_destroy(const struct xt_tgdtor_param *par) +{ + nf_logger_put(par->family, NF_LOG_TYPE_ULOG); } static struct xt_target ebt_nflog_tg_reg __read_mostly = { @@ -54,6 +68,7 @@ static struct xt_target ebt_nflog_tg_reg __read_mostly = { .family = NFPROTO_BRIDGE, .target = ebt_nflog_tg, .checkentry = ebt_nflog_tg_check, + .destroy = ebt_nflog_tg_destroy, .targetsize = sizeof(struct ebt_nflog_info), .me = THIS_MODULE, }; From 8e5fd2a55e2468f6225ef5af01ae58cdbcbae8cf Mon Sep 17 00:00:00 2001 From: Jozsef Kadlecsik Date: Thu, 30 Jul 2026 20:38:49 +0200 Subject: [PATCH 110/612] netfilter: ipset: rework cidr bookkeeping According to sashiko, the current bookkeeping of cidr values are unsafe on weakly-ordered architectures. Replace the in-place updating with an RCU based method: create the new bookeeping structure, update and replace the old one with the new. Downside that we need to allocate memory when deleting a cidr entry - in case of memory pressure fall back to leave holes which possibility is taken into account at evaluation time. Thanks to Pablo (Pablo Neira Ayuso ) and Cyntia (Cynthia ) for helping me in debugging which resulted the patch "netfilter: ipset: allocate the proper memory for the generic hash structure" on which this very patch depends. Signed-off-by: Jozsef Kadlecsik Signed-off-by: Florian Westphal Signed-off-by: Pablo Neira Ayuso --- net/netfilter/ipset/ip_set_hash_gen.h | 235 +++++++++++++------ net/netfilter/ipset/ip_set_hash_ipportnet.c | 4 +- net/netfilter/ipset/ip_set_hash_net.c | 4 +- net/netfilter/ipset/ip_set_hash_netiface.c | 4 +- net/netfilter/ipset/ip_set_hash_netnet.c | 12 +- net/netfilter/ipset/ip_set_hash_netport.c | 4 +- net/netfilter/ipset/ip_set_hash_netportnet.c | 12 +- 7 files changed, 182 insertions(+), 93 deletions(-) diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h index b2d77973272d25..dd31992c915ca8 100644 --- a/net/netfilter/ipset/ip_set_hash_gen.h +++ b/net/netfilter/ipset/ip_set_hash_gen.h @@ -99,9 +99,15 @@ struct htable { #endif /* Book-keeping of the prefixes added to the set */ +struct net_prefix { + u8 cidr; /* the cidr value */ + u32 count; /* number of elements of this cidr */ +}; + struct net_prefixes { - u32 nets[IPSET_NET_COUNT]; /* number of elements for this cidr */ - u8 cidr[IPSET_NET_COUNT]; /* the cidr value */ + struct rcu_head rcu; + u8 len; + struct net_prefix nets[] __counted_by(len); }; /* Compute the hash table size */ @@ -127,11 +133,6 @@ htable_size(u8 hbits) #else #define __CIDR(cidr, i) (cidr) #endif - -/* cidr + 1 is stored in net_prefixes to support /0 */ -#define NCIDR_PUT(cidr) ((cidr) + 1) -#define NCIDR_GET(cidr) ((cidr) - 1) - #ifdef IP_SET_HASH_WITH_NETS_PACKED /* When cidr is packed with nomatch, cidr - 1 is stored in the data entry */ #define DCIDR_PUT(cidr) ((cidr) - 1) @@ -141,21 +142,11 @@ htable_size(u8 hbits) #define DCIDR_GET(cidr, i) __CIDR(cidr, i) #endif -#define INIT_CIDR(cidr, host_mask) \ - DCIDR_PUT(((cidr) ? NCIDR_GET(cidr) : host_mask)) - -#ifdef IP_SET_HASH_WITH_NET0 -/* cidr from 0 to HOST_MASK value and c = cidr + 1 */ -#define NLEN (HOST_MASK + 1) -#define CIDR_POS(c) ((c) - 1) -#else -/* cidr from 1 to HOST_MASK value and c = cidr + 1 */ -#define NLEN HOST_MASK -#define CIDR_POS(c) ((c) - 2) -#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);\ +}) -#else -#define NLEN 0 #endif /* IP_SET_HASH_WITH_NETS */ #define SET_ELEM_EXPIRED(set, d) \ @@ -292,6 +283,7 @@ static const union nf_inet_addr zeromask = {}; /* The generic hash structure */ struct htype { struct htable __rcu *table; /* the hash table */ + struct net_prefixes __rcu *rnets[IPSET_NET_COUNT]; /* cidr prefixes */ struct htable_gc gc; /* gc workqueue */ u32 maxelem; /* max elements in the hash */ u32 initval; /* random jhash init value */ @@ -302,9 +294,6 @@ struct htype { #if defined(IP_SET_HASH_WITH_NETMASK) || defined(IP_SET_HASH_WITH_BITMASK) u8 netmask; /* netmask value for subnets to store */ union nf_inet_addr bitmask; /* stores bitmask */ -#endif -#ifdef IP_SET_HASH_WITH_NETS - struct net_prefixes nets[NLEN]; /* book-keeping of prefixes */ #endif /* Because 'next' is IPv4/IPv6 dependent, no elements of this * structure and referred in create() may come after 'next'. @@ -326,50 +315,92 @@ struct mtype_resize_ad { /* Network cidr size book keeping when the hash stores different * sized networks. cidr == real cidr + 1 to support /0. */ -static void +static int mtype_add_cidr(struct ip_set *set, struct htype *h, u8 cidr, u8 n) { - int i, j; + struct net_prefixes *nets, *tmp; + int i, j, found, len = 0, ret = 0; spin_lock_bh(&set->lock); + nets = __ipset_dereference(h->rnets[n]); /* Add in increasing prefix order, so larger cidr first */ - for (i = 0, j = -1; i < NLEN && h->nets[i].cidr[n]; i++) { - if (j != -1) { + for (i = 0, found = -1; i < nets->len; i++) { + if (nets->nets[i].count) + len++; + if (found != -1) { continue; - } else if (h->nets[i].cidr[n] < cidr) { - j = i; - } else if (h->nets[i].cidr[n] == cidr) { - h->nets[CIDR_POS(cidr)].nets[n]++; + } else if (nets->nets[i].cidr < cidr) { + found = i; + } else if (nets->nets[i].cidr == cidr) { + nets->nets[i].count++; goto unlock; } } - if (j != -1) { - for (; i > j; i--) - h->nets[i].cidr[n] = h->nets[i - 1].cidr[n]; + len++; + tmp = kzalloc_flex(*tmp, nets, len, GFP_ATOMIC); + if (!tmp) { + ret = -ENOMEM; + goto unlock; } - h->nets[i].cidr[n] = cidr; - h->nets[CIDR_POS(cidr)].nets[n] = 1; + + tmp->len = len; + for (i = 0, j = 0; i < nets->len; i++) { + if (i == found) { + tmp->nets[j].cidr = cidr; + tmp->nets[j++].count = 1; + } + if (!nets->nets[i].count) + continue; + tmp->nets[j].cidr = nets->nets[i].cidr; + tmp->nets[j++].count = nets->nets[i].count; + } + if (found == -1) { + tmp->nets[j].cidr = cidr; + tmp->nets[j].count = 1; + } + rcu_assign_pointer(h->rnets[n], tmp); + kfree_rcu(nets, rcu); unlock: spin_unlock_bh(&set->lock); + return ret; } static void mtype_del_cidr(struct ip_set *set, struct htype *h, u8 cidr, u8 n) { - u8 i, j, net_end = NLEN - 1; + struct net_prefixes *nets, *tmp; + u8 i, j, len = 0; + int found; spin_lock_bh(&set->lock); - for (i = 0; i < NLEN; i++) { - if (h->nets[i].cidr[n] != cidr) - continue; - h->nets[CIDR_POS(cidr)].nets[n]--; - if (h->nets[CIDR_POS(cidr)].nets[n] > 0) - goto unlock; - for (j = i; j < net_end && h->nets[j].cidr[n]; j++) - h->nets[j].cidr[n] = h->nets[j + 1].cidr[n]; - h->nets[j].cidr[n] = 0; + 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) + found = i; + } + if (unlikely(found == -1)) + goto unlock; + + nets->nets[found].count--; + if (nets->nets[found].count) 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); unlock: spin_unlock_bh(&set->lock); } @@ -402,6 +433,9 @@ static void mtype_flush(struct ip_set *set) { struct htype *h = set->data; +#ifdef IP_SET_HASH_WITH_NETS + struct net_prefixes *nets, *tmp; +#endif struct htable *t; struct hbucket *n; u32 r, i; @@ -425,7 +459,19 @@ mtype_flush(struct ip_set *set) spin_unlock_bh(&t->hregion[r].lock); } #ifdef IP_SET_HASH_WITH_NETS - memset(h->nets, 0, sizeof(h->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; + + for (j = 0; j < nets->len; j++) + nets->nets[j].count = 0; + } else { + rcu_assign_pointer(h->rnets[i], tmp); + kfree_rcu(nets, rcu); + } + } #endif } @@ -433,6 +479,9 @@ mtype_flush(struct ip_set *set) static void mtype_ahash_destroy(struct ip_set *set, struct htable *t, bool ext_destroy) { +#ifdef IP_SET_HASH_WITH_NETS + struct htype *h = set->data; +#endif struct hbucket *n; u32 i; @@ -446,6 +495,11 @@ mtype_ahash_destroy(struct ip_set *set, struct htable *t, bool ext_destroy) kfree(n); } +#ifdef IP_SET_HASH_WITH_NETS + if (ext_destroy) + for (i = 0; i < IPSET_NET_COUNT; i++) + kfree(rcu_dereference_raw(h->rnets[i])); +#endif ip_set_free(t->hregion); ip_set_free(t); } @@ -519,8 +573,7 @@ mtype_gc_do(struct ip_set *set, struct htype *h, struct htable *t, u32 r) #ifdef IP_SET_HASH_WITH_NETS for (k = 0; k < IPSET_NET_COUNT; k++) mtype_del_cidr(set, h, - NCIDR_PUT(DCIDR_GET(data->cidr, k)), - k); + DCIDR_GET(data->cidr, k), k); #endif t->hregion[r].elements--; ip_set_ext_destroy(set, data); @@ -950,8 +1003,7 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext, #ifdef IP_SET_HASH_WITH_NETS for (i = 0; i < IPSET_NET_COUNT; i++) mtype_del_cidr(set, h, - NCIDR_PUT(DCIDR_GET(data->cidr, i)), - i); + DCIDR_GET(data->cidr, i), i); #endif ip_set_ext_destroy(set, data); t->hregion[r].elements--; @@ -996,7 +1048,7 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext, t->hregion[r].elements++; #ifdef IP_SET_HASH_WITH_NETS for (i = 0; i < IPSET_NET_COUNT; i++) - mtype_add_cidr(set, h, NCIDR_PUT(DCIDR_GET(d->cidr, i)), i); + mtype_add_cidr(set, h, DCIDR_GET(d->cidr, i), i); #endif memcpy(data, d, sizeof(struct mtype_elem)); overwrite_extensions: @@ -1110,7 +1162,7 @@ mtype_del(struct ip_set *set, void *value, const struct ip_set_ext *ext, #ifdef IP_SET_HASH_WITH_NETS for (j = 0; j < IPSET_NET_COUNT; j++) mtype_del_cidr(set, h, - NCIDR_PUT(DCIDR_GET(d->cidr, j)), j); + DCIDR_GET(d->cidr, j), j); #endif ip_set_ext_destroy(set, data); @@ -1193,28 +1245,37 @@ mtype_test_cidrs(struct ip_set *set, struct mtype_elem *d, { struct htype *h = set->data; struct htable *t = rcu_dereference_bh(h->table); + struct net_prefixes *nets0; struct hbucket *n; struct mtype_elem *data; #if IPSET_NET_COUNT == 2 + struct net_prefixes *nets1; struct mtype_elem orig = *d; - int ret, i, j = 0, k; + int ret, i, j, k; #else - int ret, i, j = 0; + int ret, i, j; #endif u32 key, multi = 0; u8 pos; pr_debug("test by nets\n"); - for (; j < NLEN && h->nets[j].cidr[0] && !multi; j++) { + rcu_read_lock_bh(); + nets0 = rcu_dereference_bh(h->rnets[0]); +#if IPSET_NET_COUNT == 2 + nets1 = rcu_dereference_bh(h->rnets[1]); +#endif + for (j = 0; j < nets0->len && !multi; j++) { + if (!nets0->nets[j].count) + continue; #if IPSET_NET_COUNT == 2 mtype_data_reset_elem(d, &orig); - mtype_data_netmask(d, NCIDR_GET(h->nets[j].cidr[0]), false); - for (k = 0; k < NLEN && h->nets[k].cidr[1] && !multi; - k++) { - mtype_data_netmask(d, NCIDR_GET(h->nets[k].cidr[1]), - true); + mtype_data_netmask(d, nets0->nets[j].cidr, false); + for (k = 0; k < nets1->len && !multi; k++) { + if (!nets1->nets[k].count) + continue; + mtype_data_netmask(d, nets1->nets[k].cidr, true); #else - mtype_data_netmask(d, NCIDR_GET(h->nets[j].cidr[0])); + mtype_data_netmask(d, nets0->nets[j].cidr); #endif key = HKEY(d, h->initval, t->htable_bits); n = rcu_dereference_bh(hbucket(t, key)); @@ -1229,7 +1290,7 @@ mtype_test_cidrs(struct ip_set *set, struct mtype_elem *d, continue; ret = mtype_data_match(data, ext, mext, set, flags); if (ret != 0) - return ret; + goto unlock; #ifdef IP_SET_HASH_WITH_MULTI /* No match, reset multiple match flag */ multi = 0; @@ -1239,7 +1300,10 @@ mtype_test_cidrs(struct ip_set *set, struct mtype_elem *d, } #endif } - return 0; + ret = 0; +unlock: + rcu_read_unlock_bh(); + return ret; } #endif @@ -1504,6 +1568,9 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set, int ret __attribute__((unused)) = 0; u8 netmask = set->family == NFPROTO_IPV4 ? 32 : 128; union nf_inet_addr bitmask = onesmask; +#endif +#ifdef IP_SET_HASH_WITH_NETS + struct net_prefixes *nets; #endif size_t hsize; struct htype *h; @@ -1604,21 +1671,25 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set, */ hbits = fls(hashsize - 1); hsize = htable_size(hbits); - if (hsize == 0) { - kfree(h); - return -ENOMEM; - } + if (hsize == 0) + goto free_h; t = ip_set_alloc(hsize); - if (!t) { - kfree(h); - return -ENOMEM; - } + if (!t) + goto free_h; t->hregion = ip_set_alloc(ahash_sizeof_regions(hbits)); - if (!t->hregion) { - ip_set_free(t); - kfree(h); - return -ENOMEM; + if (!t->hregion) + goto free_t; +#ifdef IP_SET_HASH_WITH_NETS + for (i = 0; i < IPSET_NET_COUNT; i++) { + nets = kzalloc_obj(*nets); + if (!nets) { + while (i > 0) + kfree(rcu_dereference_raw(h->rnets[--i])); + goto free_hregion; + } + RCU_INIT_POINTER(h->rnets[i], nets); } +#endif h->gc.set = set; spin_lock_init(&h->gc.lock); for (i = 0; i < ahash_numof_locks(hbits); i++) @@ -1682,6 +1753,16 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set, t->htable_bits, h->maxelem, set->data, t); return 0; + +#ifdef IP_SET_HASH_WITH_NETS +free_hregion: + ip_set_free(t->hregion); +#endif +free_t: + ip_set_free(t); +free_h: + kfree(h); + return -ENOMEM; } #endif /* IP_SET_EMIT_CREATE */ diff --git a/net/netfilter/ipset/ip_set_hash_ipportnet.c b/net/netfilter/ipset/ip_set_hash_ipportnet.c index 2d6652d43199a4..195853a25b0606 100644 --- a/net/netfilter/ipset/ip_set_hash_ipportnet.c +++ b/net/netfilter/ipset/ip_set_hash_ipportnet.c @@ -138,7 +138,7 @@ hash_ipportnet4_kadt(struct ip_set *set, const struct sk_buff *skb, const struct hash_ipportnet4 *h = set->data; ipset_adtfn adtfn = set->variant->adt[adt]; struct hash_ipportnet4_elem e = { - .cidr = INIT_CIDR(h->nets[0].cidr[0], HOST_MASK), + .cidr = INIT_CIDR(h->rnets[0], HOST_MASK), }; struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set); @@ -398,7 +398,7 @@ hash_ipportnet6_kadt(struct ip_set *set, const struct sk_buff *skb, const struct hash_ipportnet6 *h = set->data; ipset_adtfn adtfn = set->variant->adt[adt]; struct hash_ipportnet6_elem e = { - .cidr = INIT_CIDR(h->nets[0].cidr[0], HOST_MASK), + .cidr = INIT_CIDR(h->rnets[0], HOST_MASK), }; struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set); diff --git a/net/netfilter/ipset/ip_set_hash_net.c b/net/netfilter/ipset/ip_set_hash_net.c index ce0a9ce5a91f1a..092f3c9281b80f 100644 --- a/net/netfilter/ipset/ip_set_hash_net.c +++ b/net/netfilter/ipset/ip_set_hash_net.c @@ -117,7 +117,7 @@ hash_net4_kadt(struct ip_set *set, const struct sk_buff *skb, const struct hash_net4 *h = set->data; ipset_adtfn adtfn = set->variant->adt[adt]; struct hash_net4_elem e = { - .cidr = INIT_CIDR(h->nets[0].cidr[0], HOST_MASK), + .cidr = INIT_CIDR(h->rnets[0], HOST_MASK), }; struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set); @@ -291,7 +291,7 @@ hash_net6_kadt(struct ip_set *set, const struct sk_buff *skb, const struct hash_net6 *h = set->data; ipset_adtfn adtfn = set->variant->adt[adt]; struct hash_net6_elem e = { - .cidr = INIT_CIDR(h->nets[0].cidr[0], HOST_MASK), + .cidr = INIT_CIDR(h->rnets[0], HOST_MASK), }; struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set); diff --git a/net/netfilter/ipset/ip_set_hash_netiface.c b/net/netfilter/ipset/ip_set_hash_netiface.c index 30a655e5c4fdcd..b44b95f766b7ad 100644 --- a/net/netfilter/ipset/ip_set_hash_netiface.c +++ b/net/netfilter/ipset/ip_set_hash_netiface.c @@ -161,7 +161,7 @@ hash_netiface4_kadt(struct ip_set *set, const struct sk_buff *skb, struct hash_netiface4 *h = set->data; ipset_adtfn adtfn = set->variant->adt[adt]; struct hash_netiface4_elem e = { - .cidr = INIT_CIDR(h->nets[0].cidr[0], HOST_MASK), + .cidr = INIT_CIDR(h->rnets[0], HOST_MASK), .elem = 1, }; struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set); @@ -382,7 +382,7 @@ hash_netiface6_kadt(struct ip_set *set, const struct sk_buff *skb, struct hash_netiface6 *h = set->data; ipset_adtfn adtfn = set->variant->adt[adt]; struct hash_netiface6_elem e = { - .cidr = INIT_CIDR(h->nets[0].cidr[0], HOST_MASK), + .cidr = INIT_CIDR(h->rnets[0], HOST_MASK), .elem = 1, }; struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set); diff --git a/net/netfilter/ipset/ip_set_hash_netnet.c b/net/netfilter/ipset/ip_set_hash_netnet.c index 8fbe649c9dd3d4..f7c8a1cc30fc7b 100644 --- a/net/netfilter/ipset/ip_set_hash_netnet.c +++ b/net/netfilter/ipset/ip_set_hash_netnet.c @@ -149,8 +149,10 @@ hash_netnet4_kadt(struct ip_set *set, const struct sk_buff *skb, struct hash_netnet4_elem e = { }; struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set); - e.cidr[0] = INIT_CIDR(h->nets[0].cidr[0], HOST_MASK); - e.cidr[1] = INIT_CIDR(h->nets[0].cidr[1], HOST_MASK); + rcu_read_lock_bh(); + e.cidr[0] = INIT_CIDR(h->rnets[0], HOST_MASK); + e.cidr[1] = INIT_CIDR(h->rnets[1], HOST_MASK); + rcu_read_unlock_bh(); if (adt == IPSET_TEST) e.ccmp = (HOST_MASK << (sizeof(e.cidr[0]) * 8)) | HOST_MASK; @@ -388,8 +390,10 @@ hash_netnet6_kadt(struct ip_set *set, const struct sk_buff *skb, struct hash_netnet6_elem e = { }; struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set); - e.cidr[0] = INIT_CIDR(h->nets[0].cidr[0], HOST_MASK); - e.cidr[1] = INIT_CIDR(h->nets[0].cidr[1], HOST_MASK); + rcu_read_lock_bh(); + e.cidr[0] = INIT_CIDR(h->rnets[0], HOST_MASK); + e.cidr[1] = INIT_CIDR(h->rnets[1], HOST_MASK); + rcu_read_unlock_bh(); if (adt == IPSET_TEST) e.ccmp = (HOST_MASK << (sizeof(u8) * 8)) | HOST_MASK; diff --git a/net/netfilter/ipset/ip_set_hash_netport.c b/net/netfilter/ipset/ip_set_hash_netport.c index d1a0628df4ef3a..5de4b511de7636 100644 --- a/net/netfilter/ipset/ip_set_hash_netport.c +++ b/net/netfilter/ipset/ip_set_hash_netport.c @@ -133,7 +133,7 @@ hash_netport4_kadt(struct ip_set *set, const struct sk_buff *skb, const struct hash_netport4 *h = set->data; ipset_adtfn adtfn = set->variant->adt[adt]; struct hash_netport4_elem e = { - .cidr = INIT_CIDR(h->nets[0].cidr[0], HOST_MASK), + .cidr = INIT_CIDR(h->rnets[0], HOST_MASK), }; struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set); @@ -353,7 +353,7 @@ hash_netport6_kadt(struct ip_set *set, const struct sk_buff *skb, const struct hash_netport6 *h = set->data; ipset_adtfn adtfn = set->variant->adt[adt]; struct hash_netport6_elem e = { - .cidr = INIT_CIDR(h->nets[0].cidr[0], HOST_MASK), + .cidr = INIT_CIDR(h->rnets[0], HOST_MASK), }; struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set); diff --git a/net/netfilter/ipset/ip_set_hash_netportnet.c b/net/netfilter/ipset/ip_set_hash_netportnet.c index bf4f91b78e1dcb..6291532be7a5a2 100644 --- a/net/netfilter/ipset/ip_set_hash_netportnet.c +++ b/net/netfilter/ipset/ip_set_hash_netportnet.c @@ -157,8 +157,10 @@ hash_netportnet4_kadt(struct ip_set *set, const struct sk_buff *skb, struct hash_netportnet4_elem e = { }; struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set); - e.cidr[0] = INIT_CIDR(h->nets[0].cidr[0], HOST_MASK); - e.cidr[1] = INIT_CIDR(h->nets[0].cidr[1], HOST_MASK); + rcu_read_lock_bh(); + e.cidr[0] = INIT_CIDR(h->rnets[0], HOST_MASK); + e.cidr[1] = INIT_CIDR(h->rnets[1], HOST_MASK); + rcu_read_unlock_bh(); if (adt == IPSET_TEST) e.ccmp = (HOST_MASK << (sizeof(e.cidr[0]) * 8)) | HOST_MASK; @@ -452,8 +454,10 @@ hash_netportnet6_kadt(struct ip_set *set, const struct sk_buff *skb, struct hash_netportnet6_elem e = { }; struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set); - e.cidr[0] = INIT_CIDR(h->nets[0].cidr[0], HOST_MASK); - e.cidr[1] = INIT_CIDR(h->nets[0].cidr[1], HOST_MASK); + rcu_read_lock_bh(); + e.cidr[0] = INIT_CIDR(h->rnets[0], HOST_MASK); + e.cidr[1] = INIT_CIDR(h->rnets[1], HOST_MASK); + rcu_read_unlock_bh(); if (adt == IPSET_TEST) e.ccmp = (HOST_MASK << (sizeof(u8) * 8)) | HOST_MASK; From 712a6f545c359b427daa9a5a782e30d2f8331e25 Mon Sep 17 00:00:00 2001 From: Jozsef Kadlecsik Date: Thu, 30 Jul 2026 20:38:50 +0200 Subject: [PATCH 111/612] netfilter: ipset: switch ext_size to atomic64_t The hash types do not acquire set->lock, they use 'region locking' where only part of the hash table is locked. Parallel inserts and deletes are possible and CPUs can race on ->ext_size update. Switch to atomic64_t. This leaves another bug unresolved: there still can be a race on comment extension re-init. This will be handled in a later commit when converting to rhashtable backend. Fixes: f66ee0410b1c ("netfilter: ipset: Fix "INFO: rcu detected stall in hash_xxx" reports") Signed-off-by: Jozsef Kadlecsik Signed-off-by: Florian Westphal Signed-off-by: Pablo Neira Ayuso --- include/linux/netfilter/ipset/ip_set.h | 2 +- net/netfilter/ipset/ip_set_bitmap_gen.h | 4 ++-- net/netfilter/ipset/ip_set_core.c | 6 +++--- net/netfilter/ipset/ip_set_hash_gen.h | 2 +- net/netfilter/ipset/ip_set_list_set.c | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/include/linux/netfilter/ipset/ip_set.h b/include/linux/netfilter/ipset/ip_set.h index b98331572ad298..cadae9b2578f1f 100644 --- a/include/linux/netfilter/ipset/ip_set.h +++ b/include/linux/netfilter/ipset/ip_set.h @@ -273,7 +273,7 @@ struct ip_set { /* Number of elements (vs timeout) */ u32 elements; /* Size of the dynamic extensions (vs timeout) */ - size_t ext_size; + atomic64_t ext_size; /* Element data size */ size_t dsize; /* Offsets to extensions in elements */ diff --git a/net/netfilter/ipset/ip_set_bitmap_gen.h b/net/netfilter/ipset/ip_set_bitmap_gen.h index bb9b5bed10e19c..226fdf17b68329 100644 --- a/net/netfilter/ipset/ip_set_bitmap_gen.h +++ b/net/netfilter/ipset/ip_set_bitmap_gen.h @@ -77,7 +77,7 @@ mtype_flush(struct ip_set *set) mtype_ext_cleanup(set); bitmap_zero(map->members, map->elements); set->elements = 0; - set->ext_size = 0; + atomic64_set(&set->ext_size, 0); } /* Calculate the actual memory size of the set data */ @@ -93,7 +93,7 @@ mtype_head(struct ip_set *set, struct sk_buff *skb) { const struct mtype *map = set->data; struct nlattr *nested; - size_t memsize = mtype_memsize(map, set->dsize) + set->ext_size; + size_t memsize = mtype_memsize(map, set->dsize) + atomic64_read(&set->ext_size); nested = nla_nest_start(skb, IPSET_ATTR_DATA); if (!nested) diff --git a/net/netfilter/ipset/ip_set_core.c b/net/netfilter/ipset/ip_set_core.c index 6cfad152d7d1ba..822a53a7f502a9 100644 --- a/net/netfilter/ipset/ip_set_core.c +++ b/net/netfilter/ipset/ip_set_core.c @@ -350,7 +350,7 @@ ip_set_init_comment(struct ip_set *set, struct ip_set_comment *comment, size_t len = ext->comment ? strlen(ext->comment) : 0; if (unlikely(c)) { - set->ext_size -= sizeof(*c) + strlen(c->str) + 1; + atomic64_sub(sizeof(*c) + strlen(c->str) + 1, &set->ext_size); rcu_assign_pointer(comment->c, NULL); kfree_rcu(c, rcu); } @@ -362,7 +362,7 @@ ip_set_init_comment(struct ip_set *set, struct ip_set_comment *comment, if (unlikely(!c)) return; strscpy(c->str, ext->comment, len + 1); - set->ext_size += sizeof(*c) + strlen(c->str) + 1; + atomic64_add(sizeof(*c) + strlen(c->str) + 1, &set->ext_size); rcu_assign_pointer(comment->c, c); } EXPORT_SYMBOL_GPL(ip_set_init_comment); @@ -392,7 +392,7 @@ ip_set_comment_free(struct ip_set *set, void *ptr) c = rcu_dereference_protected(comment->c, 1); if (unlikely(!c)) return; - set->ext_size -= sizeof(*c) + strlen(c->str) + 1; + atomic64_sub(sizeof(*c) + strlen(c->str) + 1, &set->ext_size); rcu_assign_pointer(comment->c, NULL); kfree_rcu(c, rcu); } diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h index dd31992c915ca8..8841daf28f017b 100644 --- a/net/netfilter/ipset/ip_set_hash_gen.h +++ b/net/netfilter/ipset/ip_set_hash_gen.h @@ -1373,7 +1373,7 @@ mtype_head(struct ip_set *set, struct sk_buff *skb) rcu_read_lock_bh(); t = rcu_dereference_bh(h->table); mtype_ext_size(set, &elements, &ext_size); - memsize = mtype_ahash_memsize(h, t) + ext_size + set->ext_size; + memsize = mtype_ahash_memsize(h, t) + ext_size + atomic64_read(&set->ext_size); htable_bits = t->htable_bits; rcu_read_unlock_bh(); diff --git a/net/netfilter/ipset/ip_set_list_set.c b/net/netfilter/ipset/ip_set_list_set.c index 1cef84f15e8c75..ca3ef9479e838d 100644 --- a/net/netfilter/ipset/ip_set_list_set.c +++ b/net/netfilter/ipset/ip_set_list_set.c @@ -421,7 +421,7 @@ list_set_flush(struct ip_set *set) list_for_each_entry_safe(e, n, &map->members, list) list_set_del(set, e); set->elements = 0; - set->ext_size = 0; + atomic64_set(&set->ext_size, 0); } static void @@ -455,7 +455,7 @@ list_set_head(struct ip_set *set, struct sk_buff *skb) { const struct list_set *map = set->data; struct nlattr *nested; - size_t memsize = list_set_memsize(map, set->dsize) + set->ext_size; + size_t memsize = list_set_memsize(map, set->dsize) + atomic64_read(&set->ext_size); nested = nla_nest_start(skb, IPSET_ATTR_DATA); if (!nested) From c266769e9ede96b95f9cffc90274da1e25f7a74e Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Thu, 30 Jul 2026 20:38:51 +0200 Subject: [PATCH 112/612] netfilter: ipset: add small wrappers for hash and bucket sizes Preparation patch. Once the ipset hash table is replaced with rhashtable these functions are needed. Add them in extra commit to have reviewable chunks. Assisted-by: Claude:claude-sonnet-4-6 Signed-off-by: Florian Westphal Signed-off-by: Pablo Neira Ayuso --- net/netfilter/ipset/ip_set_hash_gen.h | 39 +++++++++++++++++++++------ 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h index 8841daf28f017b..ef586b486f519f 100644 --- a/net/netfilter/ipset/ip_set_hash_gen.h +++ b/net/netfilter/ipset/ip_set_hash_gen.h @@ -201,6 +201,8 @@ static const union nf_inet_addr zeromask = {}; #undef mtype_same_set #undef mtype_kadt #undef mtype_uadt +#undef mtype_bucket_size +#undef mtype_hash_size #undef mtype_add #undef mtype_del @@ -246,6 +248,8 @@ static const union nf_inet_addr zeromask = {}; #define mtype_same_set IPSET_TOKEN(MTYPE, _same_set) #define mtype_kadt IPSET_TOKEN(MTYPE, _kadt) #define mtype_uadt IPSET_TOKEN(MTYPE, _uadt) +#define mtype_bucket_size IPSET_TOKEN(MTYPE, _bucket_size) +#define mtype_hash_size IPSET_TOKEN(MTYPE, _hash_size) #define mtype_add IPSET_TOKEN(MTYPE, _add) #define mtype_del IPSET_TOKEN(MTYPE, _del) @@ -1358,6 +1362,24 @@ mtype_test(struct ip_set *set, void *value, const struct ip_set_ext *ext, return ret; } +static u32 mtype_hash_size(const struct htype *h) +{ + const struct htable *t; + u8 htable_bits; + + rcu_read_lock(); + t = rcu_dereference(h->table); + htable_bits = t->htable_bits; + rcu_read_unlock(); + + return jhash_size(htable_bits); +} + +static u32 mtype_bucket_size(const struct htype *h) +{ + return h->bucketsize; +} + /* Reply a HEADER request: fill out the header part of the set */ static int mtype_head(struct ip_set *set, struct sk_buff *skb) @@ -1368,21 +1390,20 @@ mtype_head(struct ip_set *set, struct sk_buff *skb) size_t memsize; u32 elements = 0; size_t ext_size = 0; - u8 htable_bits; rcu_read_lock_bh(); t = rcu_dereference_bh(h->table); mtype_ext_size(set, &elements, &ext_size); memsize = mtype_ahash_memsize(h, t) + ext_size + atomic64_read(&set->ext_size); - htable_bits = t->htable_bits; rcu_read_unlock_bh(); nested = nla_nest_start(skb, IPSET_ATTR_DATA); if (!nested) goto nla_put_failure; - if (nla_put_net32(skb, IPSET_ATTR_HASHSIZE, - htonl(jhash_size(htable_bits))) || - nla_put_net32(skb, IPSET_ATTR_MAXELEM, htonl(h->maxelem))) + + if (nla_put_net32(skb, IPSET_ATTR_HASHSIZE, htonl(mtype_hash_size(h)))) + goto nla_put_failure; + if (nla_put_net32(skb, IPSET_ATTR_MAXELEM, htonl(h->maxelem))) goto nla_put_failure; #ifdef IP_SET_HASH_WITH_BITMASK /* if netmask is set to anything other than HOST_MASK we know that the user supplied netmask @@ -1406,8 +1427,9 @@ mtype_head(struct ip_set *set, struct sk_buff *skb) goto nla_put_failure; #endif if (set->flags & IPSET_CREATE_FLAG_BUCKETSIZE) { - if (nla_put_u8(skb, IPSET_ATTR_BUCKETSIZE, h->bucketsize) || - nla_put_net32(skb, IPSET_ATTR_INITVAL, htonl(h->initval))) + if (nla_put_u8(skb, IPSET_ATTR_BUCKETSIZE, mtype_bucket_size(h))) + goto nla_put_failure; + if (nla_put_net32(skb, IPSET_ATTR_INITVAL, htonl(h->initval))) goto nla_put_failure; } if (nla_put_net32(skb, IPSET_ATTR_REFERENCES, htonl(set->ref)) || @@ -1721,6 +1743,7 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set, INIT_LIST_HEAD(&t->ad); RCU_INIT_POINTER(h->table, t); set->data = h; + #ifndef IP_SET_PROTO_UNDEF if (set->family == NFPROTO_IPV4) { #endif @@ -1749,7 +1772,7 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set, #endif } pr_debug("create %s hashsize %u (%u) maxelem %u: %p(%p)\n", - set->name, jhash_size(t->htable_bits), + set->name, mtype_hash_size(h), t->htable_bits, h->maxelem, set->data, t); return 0; From cdd97fae0e96fc0f78418b93ca864f98cd63e99a Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Thu, 30 Jul 2026 20:38:52 +0200 Subject: [PATCH 113/612] netfilter: ipset: add and use mtype_del_cidr_all helper Reduces size of upcoming rhashtable conversion. Assisted-by: Claude:claude-sonnet-4-6 Signed-off-by: Florian Westphal Signed-off-by: Pablo Neira Ayuso --- net/netfilter/ipset/ip_set_hash_gen.h | 34 +++++++++++++-------------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h index ef586b486f519f..f00c82acd7f053 100644 --- a/net/netfilter/ipset/ip_set_hash_gen.h +++ b/net/netfilter/ipset/ip_set_hash_gen.h @@ -195,6 +195,7 @@ static const union nf_inet_addr zeromask = {}; #undef mtype_ext_cleanup #undef mtype_add_cidr #undef mtype_del_cidr +#undef mtype_del_cidr_all #undef mtype_ahash_memsize #undef mtype_flush #undef mtype_destroy @@ -242,6 +243,7 @@ static const union nf_inet_addr zeromask = {}; #define mtype_ext_cleanup IPSET_TOKEN(MTYPE, _ext_cleanup) #define mtype_add_cidr IPSET_TOKEN(MTYPE, _add_cidr) #define mtype_del_cidr IPSET_TOKEN(MTYPE, _del_cidr) +#define mtype_del_cidr_all IPSET_TOKEN(MTYPE, _del_cidr_all) #define mtype_ahash_memsize IPSET_TOKEN(MTYPE, _ahash_memsize) #define mtype_flush IPSET_TOKEN(MTYPE, _flush) #define mtype_destroy IPSET_TOKEN(MTYPE, _destroy) @@ -410,6 +412,17 @@ mtype_del_cidr(struct ip_set *set, struct htype *h, u8 cidr, u8 n) } #endif +static void +mtype_del_cidr_all(struct ip_set *set, struct htype *h, const struct mtype_elem *data) +{ +#ifdef IP_SET_HASH_WITH_NETS + int k; + + for (k = 0; k < IPSET_NET_COUNT; k++) + mtype_del_cidr(set, h, DCIDR_GET(data->cidr, k), k); +#endif +} + /* Calculate the actual memory size of the set data */ static size_t mtype_ahash_memsize(const struct htype *h, const struct htable *t) @@ -551,9 +564,6 @@ mtype_gc_do(struct ip_set *set, struct htype *h, struct htable *t, u32 r) struct mtype_elem *data; u32 i, j, d; size_t dsize = set->dsize; -#ifdef IP_SET_HASH_WITH_NETS - u8 k; -#endif u8 pos, htable_bits = t->htable_bits; spin_lock_bh(&t->hregion[r].lock); @@ -574,11 +584,7 @@ mtype_gc_do(struct ip_set *set, struct htype *h, struct htable *t, u32 r) pr_debug("expired %u/%u\n", i, j); clear_bit(j, n->used); smp_mb__after_atomic(); -#ifdef IP_SET_HASH_WITH_NETS - for (k = 0; k < IPSET_NET_COUNT; k++) - mtype_del_cidr(set, h, - DCIDR_GET(data->cidr, k), k); -#endif + mtype_del_cidr_all(set, h, data); t->hregion[r].elements--; ip_set_ext_destroy(set, data); d++; @@ -1004,11 +1010,7 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext, j = 0; data = ahash_data(n, j, set->dsize); if (!deleted) { -#ifdef IP_SET_HASH_WITH_NETS - for (i = 0; i < IPSET_NET_COUNT; i++) - mtype_del_cidr(set, h, - DCIDR_GET(data->cidr, i), i); -#endif + mtype_del_cidr_all(set, h, data); ip_set_ext_destroy(set, data); t->hregion[r].elements--; } @@ -1163,11 +1165,7 @@ mtype_del(struct ip_set *set, void *value, const struct ip_set_ext *ext, if (i + 1 == pos) smp_store_release(&n->pos, --pos); t->hregion[r].elements--; -#ifdef IP_SET_HASH_WITH_NETS - for (j = 0; j < IPSET_NET_COUNT; j++) - mtype_del_cidr(set, h, - DCIDR_GET(d->cidr, j), j); -#endif + mtype_del_cidr_all(set, h, d); ip_set_ext_destroy(set, data); if (t->resizing && ext && ext->target) { From 7defddefa95b3699b05b23bf3ceea94d872509bb Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Thu, 30 Jul 2026 20:38:53 +0200 Subject: [PATCH 114/612] netfilter: ipset: switch to rcu work In the initial ipset rhashtable conversion RFC series syzbot reported following splat: BUG: sleeping function [..] at kernel/irq_work.c:289 in_atomic(): 1, [..] irq_work_sync.. kernel/irq_work.c:289 rhashtable_free_and_destroy.. lib/rhashtable.c:1295 hash_netport4_destroy.. net/netfilter/ipset/ip_set_hash_gen.h:420 ip_set_destroy_set_rcu.. net/netfilter/ipset/ip_set_core.c:1169 rcu_core.. kernel/rcu/tree.c:2897 This is because post-rhashtable-conversion hash implementation needs to schedule in the destroy callback. At this time this isn't allowed. Replace existing call_rcu() based destruction with rcu_work api. Also allows to undo split of set destruction and gc work cancelling in a future patch. Signed-off-by: Florian Westphal Signed-off-by: Pablo Neira Ayuso --- include/linux/netfilter/ipset/ip_set.h | 4 +-- net/netfilter/ipset/ip_set_core.c | 46 ++++++++++++++++---------- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/include/linux/netfilter/ipset/ip_set.h b/include/linux/netfilter/ipset/ip_set.h index cadae9b2578f1f..c46864cc662390 100644 --- a/include/linux/netfilter/ipset/ip_set.h +++ b/include/linux/netfilter/ipset/ip_set.h @@ -244,8 +244,8 @@ extern void ip_set_type_unregister(struct ip_set_type *set_type); /* A generic IP set */ struct ip_set { - /* For call_cru in destroy */ - struct rcu_head rcu; + /* for set destruction */ + struct rcu_work rwork; /* The name of the set */ char name[IPSET_MAXNAMELEN]; /* Lock protecting the set data */ diff --git a/net/netfilter/ipset/ip_set_core.c b/net/netfilter/ipset/ip_set_core.c index 822a53a7f502a9..543851a923d0fd 100644 --- a/net/netfilter/ipset/ip_set_core.c +++ b/net/netfilter/ipset/ip_set_core.c @@ -25,6 +25,7 @@ static LIST_HEAD(ip_set_type_list); /* all registered set types */ static DEFINE_MUTEX(ip_set_type_mutex); /* protects ip_set_type_list */ static DEFINE_RWLOCK(ip_set_ref_lock); /* protects the set refs */ +static struct workqueue_struct *ipset_destroy_wq; struct ip_set_net { struct ip_set * __rcu *ip_set_list; /* all individual sets */ @@ -1178,22 +1179,26 @@ ip_set_setname_policy[IPSET_ATTR_CMD_MAX + 1] = { .len = IPSET_MAXNAMELEN - 1 }, }; -/* In order to return quickly when destroying a single set, it is split - * into two stages: - * - Cancel garbage collector - * - Destroy the set itself via call_rcu() - */ - static void -ip_set_destroy_set_rcu(struct rcu_head *head) +destroy_and_free_set(struct ip_set *set) { - struct ip_set *set = container_of(head, struct ip_set, rcu); - set->variant->destroy(set); module_put(set->type->me); kfree(set); } +/* In order to return quickly when destroying a single set, + * destruction is done asynchronously via work queues. + */ +static void +ip_set_destroy_set_work(struct work_struct *work) +{ + struct ip_set *set = container_of(to_rcu_work(work), + struct ip_set, rwork); + + destroy_and_free_set(set); +} + static void _destroy_all_sets(struct ip_set_net *inst) { @@ -1283,7 +1288,8 @@ static int ip_set_destroy(struct sk_buff *skb, const struct nfnl_info *info, /* Must wait for flush to be really finished */ rcu_barrier(); } - call_rcu(&s->rcu, ip_set_destroy_set_rcu); + INIT_RCU_WORK(&s->rwork, ip_set_destroy_set_work); + queue_rcu_work(ipset_destroy_wq, &s->rwork); } return 0; out: @@ -2421,18 +2427,23 @@ static struct pernet_operations ip_set_net_ops = { static int __init ip_set_init(void) { - int ret = register_pernet_subsys(&ip_set_net_ops); + int ret; + + ipset_destroy_wq = alloc_ordered_workqueue("ipset_destroy_wq", 0); + if (!ipset_destroy_wq) + return -ENOMEM; + ret = register_pernet_subsys(&ip_set_net_ops); if (ret) { pr_err("ip_set: cannot register pernet_subsys.\n"); - return ret; + goto out_wq; } ret = nfnetlink_subsys_register(&ip_set_netlink_subsys); if (ret != 0) { pr_err("ip_set: cannot register with nfnetlink.\n"); unregister_pernet_subsys(&ip_set_net_ops); - return ret; + goto out_wq; } ret = nf_register_sockopt(&so_set); @@ -2440,10 +2451,13 @@ ip_set_init(void) pr_err("SO_SET registry failed: %d\n", ret); nfnetlink_subsys_unregister(&ip_set_netlink_subsys); unregister_pernet_subsys(&ip_set_net_ops); - return ret; + goto out_wq; } return 0; +out_wq: + destroy_workqueue(ipset_destroy_wq); + return ret; } static void __exit @@ -2453,9 +2467,7 @@ ip_set_fini(void) nfnetlink_subsys_unregister(&ip_set_netlink_subsys); unregister_pernet_subsys(&ip_set_net_ops); - /* Wait for call_rcu() in destroy */ - rcu_barrier(); - + destroy_workqueue(ipset_destroy_wq); pr_debug("these are the famous last words\n"); } From 646922a0379496154e8c8faca4f8e2fd9100cacc Mon Sep 17 00:00:00 2001 From: Julian Anastasov Date: Thu, 30 Jul 2026 21:35:05 +0300 Subject: [PATCH 115/612] ipvs: avoid out-of-bounds write in ip_vs_nat_icmp Sashiko warns that local attacker can modify the packet while it is processed by IPVS. Some places read the IP ihl field multiple times which can cause out-of-bounds access. One such place is ip_vs_nat_icmp where we can write after the validated area. Fix it by providing ciph argument just like it is done for IPv6 and use ciph->len as offset to the embedded transport header. Modify some IPv4 header checks by reading the ihl field only once. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Link: https://sashiko.dev/#/patchset/20260722101517.36313-1-ja%40ssi.bg Signed-off-by: Julian Anastasov Signed-off-by: Pablo Neira Ayuso --- include/net/ip_vs.h | 2 +- net/netfilter/ipvs/ip_vs_core.c | 67 +++++++++++++++++---------------- net/netfilter/ipvs/ip_vs_xmit.c | 2 +- 3 files changed, 36 insertions(+), 35 deletions(-) diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h index e6ca930a3507b5..1235f1934e9466 100644 --- a/include/net/ip_vs.h +++ b/include/net/ip_vs.h @@ -2062,7 +2062,7 @@ static inline bool ip_vs_conn_use_hash2(struct ip_vs_conn *cp) void ip_vs_nat_icmp(struct sk_buff *skb, struct ip_vs_protocol *pp, struct ip_vs_conn *cp, int dir, unsigned int toff, - bool has_ports); + bool has_ports, struct ip_vs_iphdr *ciph); #ifdef CONFIG_IP_VS_IPV6 void ip_vs_nat_icmp_v6(struct sk_buff *skb, struct ip_vs_protocol *pp, diff --git a/net/netfilter/ipvs/ip_vs_core.c b/net/netfilter/ipvs/ip_vs_core.c index 6b79e0c4d9e284..0bdaeb4ed61e4b 100644 --- a/net/netfilter/ipvs/ip_vs_core.c +++ b/net/netfilter/ipvs/ip_vs_core.c @@ -925,28 +925,27 @@ static int ip_vs_route_me_harder(struct netns_ipvs *ipvs, int af, */ void ip_vs_nat_icmp(struct sk_buff *skb, struct ip_vs_protocol *pp, struct ip_vs_conn *cp, int inout, unsigned int toff, - bool has_ports) + bool has_ports, struct ip_vs_iphdr *ciph) { struct iphdr *iph = ip_hdr(skb); struct icmphdr *icmph = (struct icmphdr *)(skb->data + toff); - struct iphdr *ciph = (struct iphdr *)(icmph + 1); - unsigned int coff __maybe_unused = toff + sizeof(struct icmphdr); + struct iphdr *cih = (struct iphdr *)(icmph + 1); if (inout) { iph->saddr = cp->vaddr.ip; ip_send_check(iph); - ciph->daddr = cp->vaddr.ip; - ip_send_check(ciph); + cih->daddr = cp->vaddr.ip; + ip_send_check(cih); } else { iph->daddr = cp->daddr.ip; ip_send_check(iph); - ciph->saddr = cp->daddr.ip; - ip_send_check(ciph); + cih->saddr = cp->daddr.ip; + ip_send_check(cih); } /* the TCP/UDP/SCTP port */ if (has_ports) { - __be16 *ports = (void *)ciph + ciph->ihl*4; + __be16 *ports = (void *)(skb->data + ciph->len); if (inout) ports[1] = cp->vport; @@ -960,10 +959,10 @@ void ip_vs_nat_icmp(struct sk_buff *skb, struct ip_vs_protocol *pp, skb->ip_summed = CHECKSUM_UNNECESSARY; if (inout) - IP_VS_DBG_PKT(11, AF_INET, pp, skb, coff, + IP_VS_DBG_PKT(11, AF_INET, pp, skb, ciph->off, "Forwarding altered outgoing ICMP"); else - IP_VS_DBG_PKT(11, AF_INET, pp, skb, coff, + IP_VS_DBG_PKT(11, AF_INET, pp, skb, ciph->off, "Forwarding altered incoming ICMP"); } @@ -1056,7 +1055,7 @@ static int handle_response_icmp(int af, struct sk_buff *skb, ip_vs_nat_icmp_v6(skb, pp, cp, 1, toff, has_ports, ciph); else #endif - ip_vs_nat_icmp(skb, pp, cp, 1, toff, has_ports); + ip_vs_nat_icmp(skb, pp, cp, 1, toff, has_ports, ciph); if (ip_vs_route_me_harder(cp->ipvs, af, skb, hooknum)) goto out; @@ -1092,7 +1091,7 @@ static int ip_vs_out_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, struct ip_vs_iphdr ciph; struct ip_vs_conn *cp; struct ip_vs_protocol *pp; - unsigned int offset, ihl; + unsigned int offset; union nf_inet_addr snet; *related = 1; @@ -1105,7 +1104,6 @@ static int ip_vs_out_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, return NF_ACCEPT; } - ihl = ipvsh->len; offset = ipvsh->len; ic = skb_header_pointer(skb, offset, sizeof(_icmph), &_icmph); if (ic == NULL) @@ -1131,11 +1129,15 @@ static int ip_vs_out_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, /* Now find the contained IP header */ offset += sizeof(_icmph); + if (!ip_vs_fill_iph_skb_icmp(AF_INET, skb, offset, true, &ciph)) + return NF_ACCEPT; /* The packet looks wrong, ignore */ + cih = skb_header_pointer(skb, offset, sizeof(_ciph), &_ciph); - if (!(cih && cih->version == 4 && cih->ihl >= 5)) + if (!(cih && cih->version == 4 && + ciph.len - ciph.off >= sizeof(struct iphdr))) return NF_ACCEPT; /* The packet looks wrong, ignore */ - pp = ip_vs_proto_get(cih->protocol); + pp = ip_vs_proto_get(ciph.protocol); if (!pp) return NF_ACCEPT; @@ -1146,8 +1148,6 @@ static int ip_vs_out_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, IP_VS_DBG_PKT(11, AF_INET, pp, skb, offset, "Checking outgoing ICMP for"); - ip_vs_fill_iph_skb_icmp(AF_INET, skb, offset, true, &ciph); - /* The embedded headers contain source and dest in reverse order */ cp = INDIRECT_CALL_1(pp->conn_out_get, ip_vs_conn_out_get_proto, ipvs, AF_INET, skb, &ciph); @@ -1155,8 +1155,8 @@ static int ip_vs_out_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, return NF_ACCEPT; snet.ip = ipvsh->saddr.ip; - return handle_response_icmp(AF_INET, skb, &snet, cp, pp, &ciph, ihl, - hooknum); + return handle_response_icmp(AF_INET, skb, &snet, cp, pp, &ciph, + ipvsh->len, hooknum); } #ifdef CONFIG_IP_VS_IPV6 @@ -1803,10 +1803,12 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, /* Now find the contained IP header */ offset += sizeof(_icmph); cih = skb_header_pointer(skb, offset, sizeof(_ciph), &_ciph); - if (!(cih && cih->version == 4 && cih->ihl >= 5)) + if (!cih) return NF_ACCEPT; /* The packet looks wrong, ignore */ - raddr = (union nf_inet_addr *)&cih->daddr; hlen_ipip = cih->ihl * 4; + if (!(cih->version == 4 && hlen_ipip >= sizeof(struct iphdr))) + return NF_ACCEPT; /* The packet looks wrong, ignore */ + raddr = (union nf_inet_addr *)&cih->daddr; /* Special case for errors for IPIP/UDP/GRE tunnel packets */ tunnel = false; @@ -1823,9 +1825,6 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, if (!dest || dest->tun_type != IP_VS_CONN_F_TUNNEL_TYPE_IPIP) return NF_ACCEPT; offset += hlen_ipip; - cih = skb_header_pointer(skb, offset, sizeof(_ciph), &_ciph); - if (!(cih && cih->version == 4 && cih->ihl >= 5)) - return NF_ACCEPT; /* The packet looks wrong, ignore */ tunnel = true; } else if ((cih->protocol == IPPROTO_UDP || /* Can be UDP encap */ cih->protocol == IPPROTO_GRE) && /* Can be GRE encap */ @@ -1850,21 +1849,25 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, /* Skip IP and UDP/GRE tunnel headers */ offset = offset2 + ulen; /* Now we should be at the original IP header */ - cih = skb_header_pointer(skb, offset, sizeof(_ciph), - &_ciph); - if (cih && cih->version == 4 && cih->ihl >= 5 && - iproto == IPPROTO_IPIP) + if (iproto == IPPROTO_IPIP) tunnel = true; else return NF_ACCEPT; } } - pd = ip_vs_proto_data_get(ipvs, cih->protocol); + if (!ip_vs_fill_iph_skb_icmp(AF_INET, skb, offset, !tunnel, &ciph)) + return NF_ACCEPT; + pd = ip_vs_proto_data_get(ipvs, ciph.protocol); if (!pd) return NF_ACCEPT; pp = pd->pp; + cih = skb_header_pointer(skb, offset, sizeof(_ciph), &_ciph); + if (!(cih && cih->version == 4 && + ciph.len - ciph.off >= sizeof(struct iphdr))) + return NF_ACCEPT; /* The packet looks wrong, ignore */ + /* Is the embedded protocol header present? */ if (unlikely(cih->frag_off & htons(IP_OFFSET) && !pp->dont_defrag)) return NF_ACCEPT; @@ -1872,9 +1875,6 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, IP_VS_DBG_PKT(11, AF_INET, pp, skb, offset, "Checking incoming ICMP for"); - offset2 = offset; - ip_vs_fill_iph_skb_icmp(AF_INET, skb, offset, !tunnel, &ciph); - /* The embedded headers contain source and dest in reverse order. * For IPIP/UDP/GRE tunnel this is error for request, not for reply. */ @@ -1904,11 +1904,12 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, } if (tunnel) { - unsigned int hlen_orig = cih->ihl * 4; + unsigned int hlen_orig = ciph.len - ciph.off; __be32 info = ic->un.gateway; __u8 type = ic->type; __u8 code = ic->code; + offset2 = offset; /* Update the MTU */ if (ic->type == ICMP_DEST_UNREACH && ic->code == ICMP_FRAG_NEEDED) { diff --git a/net/netfilter/ipvs/ip_vs_xmit.c b/net/netfilter/ipvs/ip_vs_xmit.c index 0b0c5304993a91..c4508f3f43dd37 100644 --- a/net/netfilter/ipvs/ip_vs_xmit.c +++ b/net/netfilter/ipvs/ip_vs_xmit.c @@ -1580,7 +1580,7 @@ ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, if (skb_cow(skb, rt->dst.dev->hard_header_len)) goto tx_error; - ip_vs_nat_icmp(skb, pp, cp, 0, toff, has_ports); + ip_vs_nat_icmp(skb, pp, cp, 0, toff, has_ports, ciph); /* Another hack: avoid icmp_send in ip_fragment */ skb->ignore_df = 1; From 99609cb0aa789c8d071050ce8579989551882cc6 Mon Sep 17 00:00:00 2001 From: Julian Anastasov Date: Thu, 30 Jul 2026 21:35:06 +0300 Subject: [PATCH 116/612] ipvs: return the csum validation for forward hook Sashiko notes that playing games with the skb dst and rt flags instead of providing hooknum is not a good idea when validating the checksums. Also, skipping checksum validation for FORWARD packets risk silent data corruption, even if the only user is the FTP-CMD packets coming from the real server. Sashiko also noticed that by using common checksum helper in the previous commit we actually fixed old bug where the TCP/UDP checksum for IPv6 on CHECKSUM_COMPLETE was not validated correctly. Fixes: e876b75b9020 ("ipvs: fix the checksum validations") Link: https://sashiko.dev/#/patchset/20260722211420.153933-1-pablo%40netfilter.org Link: https://sashiko.dev/#/patchset/20260727185024.67534-1-ja%40ssi.bg Link: https://sashiko.dev/#/patchset/20260728202520.59179-1-ja%40ssi.bg Signed-off-by: Julian Anastasov Signed-off-by: Pablo Neira Ayuso --- include/net/ip_vs.h | 19 +++++-------------- net/netfilter/ipvs/ip_vs_proto_sctp.c | 2 +- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h index 1235f1934e9466..d2813eb795be21 100644 --- a/include/net/ip_vs.h +++ b/include/net/ip_vs.h @@ -25,9 +25,7 @@ #include /* for union nf_inet_addr */ #include #include /* for struct ipv6hdr */ -#include #include -#include #if IS_ENABLED(CONFIG_NF_CONNTRACK) #include #endif @@ -2095,30 +2093,23 @@ static inline __wsum ip_vs_check_diff2(__be16 old, __be16 new, __wsum oldsum) return csum_partial(diff, sizeof(diff), oldsum); } -static inline bool ip_vs_checksum_needed(struct sk_buff *skb, int af) +static inline bool ip_vs_checksum_needed(struct sk_buff *skb) { /* Checksum unnecessary or already validated? */ if (skb_csum_unnecessary(skb)) return false; - /* LOCAL_OUT ? */ - if (!skb->dev || skb->dev->flags & IFF_LOOPBACK) + /* Locally generated ? */ + if (!skb->dev) return false; - /* !LOCAL_IN (FORWARD) ? */ - if (af == AF_INET6) { - if (!(dst_rt6_info(skb_dst(skb))->rt6i_flags & RTF_LOCAL)) - return false; - } else { - if (!(skb_rtable(skb)->rt_flags & RTCF_LOCAL)) - return false; - } return true; } static inline bool ip_vs_checksum_common_check(struct sk_buff *skb, int offset, int proto, int af) { - if (!ip_vs_checksum_needed(skb, af)) + if (!ip_vs_checksum_needed(skb)) return true; + /* Validate csum even for FORWARD */ return !nf_checksum(skb, NF_INET_LOCAL_IN, offset, proto, af); } diff --git a/net/netfilter/ipvs/ip_vs_proto_sctp.c b/net/netfilter/ipvs/ip_vs_proto_sctp.c index 3dbd3096e1637b..c80567c73469bb 100644 --- a/net/netfilter/ipvs/ip_vs_proto_sctp.c +++ b/net/netfilter/ipvs/ip_vs_proto_sctp.c @@ -193,7 +193,7 @@ sctp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp, struct sctphdr *sh; __le32 cmp, val; - if (!ip_vs_checksum_needed(skb, af)) + if (!ip_vs_checksum_needed(skb)) return 1; sh = (struct sctphdr *)(skb->data + sctphoff); cmp = sh->checksum; From 1246aa2b6ccc8944676bd24ff3e37cc56b93b51b Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Fri, 31 Jul 2026 12:36:28 +0300 Subject: [PATCH 117/612] watchdog: bd96801_wdt: Fix timeout for enabled WDG When watchdog is enabled at the probe time, the bd96801 driver retrieves the timeout configuration from the registers to set-up the heart-beat values. As Sashiko pointed out at https://lore.kernel.org/all/20260722085819.495211F000E9@smtp.kernel.org/ the timeout values are incorrectly computed in driver, resulting wrong heartbeat. This leads to devere problems if watchdog was enabled at probe time. According to the data-sheet, the "too fast" ping limit is configured as multiple of FASTNG_MIN. Furthermore, the "too slow" ping limit is configured as multiples of "too fast" timeout. The FASTNG_MIN is set to 11, meaning 1.1 mS and "too fast" and "too slow" limits are computed from this. Hence, converting the limits to mS should be done by dividing by 10, not by dividing by USEC_PER_MSEC. Fix this by dividing the timeout values with correct scaling factor. While at it, fix whitespace problem (double empty line). Signed-off-by: Matti Vaittinen Fixes: 09dad69757b6 ("watchdog: ROHM BD96801 PMIC WDG driver") Link: https://lore.kernel.org/r/amxskHmQbi9v-8_l@mva-rohm [groeck: Added reference to whitespace change to description] Signed-off-by: Guenter Roeck --- drivers/watchdog/bd96801_wdt.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/watchdog/bd96801_wdt.c b/drivers/watchdog/bd96801_wdt.c index 12b74fd2bc052f..a25b7cf1488b29 100644 --- a/drivers/watchdog/bd96801_wdt.c +++ b/drivers/watchdog/bd96801_wdt.c @@ -169,7 +169,6 @@ static int bd96801_set_wdt_mode(struct wdtbd96801 *w, unsigned int hw_margin, int fastng, slowng, type, ret, reg, mask; struct device *dev = w->dev; - if (hw_margin_min * 1000 > FASTNG_MAX_US) { dev_err(dev, "Unsupported fast timeout %u uS [max %u]\n", hw_margin_min * 1000, FASTNG_MAX_US); @@ -258,10 +257,10 @@ static int bd96801_set_heartbeat_from_hw(struct wdtbd96801 *w, fast = FASTNG_MIN << sel; sel = (val & BD96801_WD_RATIO_MASK) + 1; - w->wdt.max_hw_heartbeat_ms = (fast << sel) / USEC_PER_MSEC; + w->wdt.max_hw_heartbeat_ms = (fast << sel) / 10; if ((conf_reg & BD96801_WD_TYPE_MASK) == BD96801_WD_TYPE_WIN) - w->wdt.min_hw_heartbeat_ms = fast / USEC_PER_MSEC; + w->wdt.min_hw_heartbeat_ms = fast / 10; return 0; } From c89039b724cc4d5e63f295d45b754e0de0faa0ad Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 31 Jul 2026 15:00:36 +0200 Subject: [PATCH 118/612] Revert "thermal: hwmon: Use extra_groups for adding temperature attributes" Revert commit cfb5dc0f60fb ("thermal: hwmon: Use extra_groups for adding temperature attributes") because it is depended on by another one that turned out to be problematic. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/1992232.tdWV9SEqCh@rafael.j.wysocki --- drivers/hwmon/hwmon.c | 6 +- drivers/thermal/thermal_hwmon.c | 122 ++++++++++++++++++++------------ include/linux/hwmon.h | 3 +- 3 files changed, 80 insertions(+), 51 deletions(-) diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index 55a9a3ddd4aac7..29dc90a2c3fe70 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -1083,7 +1083,6 @@ EXPORT_SYMBOL_GPL(hwmon_device_register_with_info); * @dev: the parent device * @name: hwmon name attribute * @drvdata: driver data to attach to created device - * @extra_groups: pointer to list of additional non-standard attribute groups * * The use of this function is restricted. It is provided for legacy reasons * and must only be called from the thermal subsystem. @@ -1095,13 +1094,12 @@ EXPORT_SYMBOL_GPL(hwmon_device_register_with_info); */ struct device * hwmon_device_register_for_thermal(struct device *dev, const char *name, - void *drvdata, - const struct attribute_group **extra_groups) + void *drvdata) { if (!name || !dev) return ERR_PTR(-EINVAL); - return __hwmon_device_register(dev, name, drvdata, NULL, extra_groups); + return __hwmon_device_register(dev, name, drvdata, NULL, NULL); } EXPORT_SYMBOL_NS_GPL(hwmon_device_register_for_thermal, "HWMON_THERMAL"); diff --git a/drivers/thermal/thermal_hwmon.c b/drivers/thermal/thermal_hwmon.c index 386dfb9f559e33..223ae1571655bd 100644 --- a/drivers/thermal/thermal_hwmon.c +++ b/drivers/thermal/thermal_hwmon.c @@ -25,13 +25,25 @@ */ #define THERMAL_HWMON_NAME_LENGTH (THERMAL_NAME_LENGTH + 11) +struct thermal_hwmon_attr { + struct device_attribute attr; +}; + +/* one temperature input for each thermal zone */ +struct thermal_hwmon_temp { + struct thermal_zone_device *tz; + struct thermal_hwmon_attr temp_input; /* hwmon sys attr */ + struct thermal_hwmon_attr temp_crit; /* hwmon sys attr */ + bool temp_crit_present; +}; + /* hwmon sys I/F */ /* thermal zone devices with the same type share one hwmon device */ struct thermal_hwmon_device { char name[THERMAL_HWMON_NAME_LENGTH]; struct device *device; struct list_head node; - struct thermal_zone_device *tz; + struct thermal_hwmon_temp tz_temp; }; static LIST_HEAD(thermal_hwmon_list); @@ -39,14 +51,19 @@ static LIST_HEAD(thermal_hwmon_list); static DEFINE_MUTEX(thermal_hwmon_list_lock); static ssize_t -temp1_input_show(struct device *dev, struct device_attribute *attr, char *buf) +temp_input_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct thermal_hwmon_device *hwmon = dev_get_drvdata(dev); - struct thermal_zone_device *tz = hwmon->tz; int temperature; int ret; + struct thermal_hwmon_attr *hwmon_attr + = container_of(attr, struct thermal_hwmon_attr, attr); + struct thermal_hwmon_temp *temp + = container_of(hwmon_attr, struct thermal_hwmon_temp, + temp_input); + struct thermal_zone_device *tz = temp->tz; ret = thermal_zone_get_temp(tz, &temperature); + if (ret) return ret; @@ -54,10 +71,14 @@ temp1_input_show(struct device *dev, struct device_attribute *attr, char *buf) } static ssize_t -temp1_crit_show(struct device *dev, struct device_attribute *attr, char *buf) +temp_crit_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct thermal_hwmon_device *hwmon = dev_get_drvdata(dev); - struct thermal_zone_device *tz = hwmon->tz; + struct thermal_hwmon_attr *hwmon_attr + = container_of(attr, struct thermal_hwmon_attr, attr); + struct thermal_hwmon_temp *temp + = container_of(hwmon_attr, struct thermal_hwmon_temp, + temp_crit); + struct thermal_zone_device *tz = temp->tz; int temperature; int ret; @@ -70,49 +91,22 @@ temp1_crit_show(struct device *dev, struct device_attribute *attr, char *buf) return sysfs_emit(buf, "%d\n", temperature); } -static DEVICE_ATTR_RO(temp1_input); -static DEVICE_ATTR_RO(temp1_crit); - -static struct attribute *thermal_hwmon_attrs[] = { - &dev_attr_temp1_input.attr, - &dev_attr_temp1_crit.attr, - NULL, -}; - -static umode_t thermal_hwmon_attr_is_visible(struct kobject *kobj, - struct attribute *a, int n) +static bool thermal_zone_crit_temp_valid(struct thermal_zone_device *tz) { - if (a == &dev_attr_temp1_input.attr) - return a->mode; - - if (a == &dev_attr_temp1_crit.attr) { - struct thermal_hwmon_device *hwmon = dev_get_drvdata(kobj_to_dev(kobj)); - struct thermal_zone_device *tz = hwmon->tz; - int dummy; - - if (tz->ops.get_crit_temp && !tz->ops.get_crit_temp(tz, &dummy)) - return a->mode; - } - - return 0; + int temp; + return tz->ops.get_crit_temp && !tz->ops.get_crit_temp(tz, &temp); } -static const struct attribute_group thermal_hwmon_group = { - .attrs = thermal_hwmon_attrs, - .is_visible = thermal_hwmon_attr_is_visible, -}; - -__ATTRIBUTE_GROUPS(thermal_hwmon); - int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) { struct thermal_hwmon_device *hwmon; + struct thermal_hwmon_temp *temp; + int result; hwmon = kzalloc_obj(*hwmon); if (!hwmon) return -ENOMEM; - hwmon->tz = tz; /* * Append the thermal zone ID preceded by an underline character to the * type to disambiguate the sensors command output. @@ -120,13 +114,35 @@ int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) scnprintf(hwmon->name, THERMAL_HWMON_NAME_LENGTH, "%s_%d", tz->type, tz->id); strreplace(hwmon->name, '-', '_'); hwmon->device = hwmon_device_register_for_thermal(&tz->device, - hwmon->name, hwmon, - thermal_hwmon_groups); + hwmon->name, hwmon); if (IS_ERR(hwmon->device)) { - int result = PTR_ERR(hwmon->device); + result = PTR_ERR(hwmon->device); + goto free_mem; + } - kfree(hwmon); - return result; + temp = &hwmon->tz_temp; + + temp->tz = tz; + + temp->temp_input.attr.attr.name = "temp1_input"; + temp->temp_input.attr.attr.mode = 0444; + temp->temp_input.attr.show = temp_input_show; + sysfs_attr_init(&temp->temp_input.attr.attr); + result = device_create_file(hwmon->device, &temp->temp_input.attr); + if (result) + goto unregister_name; + + if (thermal_zone_crit_temp_valid(tz)) { + temp->temp_crit.attr.attr.name = "temp1_crit"; + temp->temp_crit.attr.attr.mode = 0444; + temp->temp_crit.attr.show = temp_crit_show; + sysfs_attr_init(&temp->temp_crit.attr.attr); + result = device_create_file(hwmon->device, + &temp->temp_crit.attr); + if (result) + goto unregister_input; + + temp->temp_crit_present = true; } /* The list is needed for hwmon lookup during removal. */ @@ -135,6 +151,15 @@ int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) mutex_unlock(&thermal_hwmon_list_lock); return 0; + + unregister_input: + device_remove_file(hwmon->device, &temp->temp_input.attr); + unregister_name: + hwmon_device_unregister(hwmon->device); + free_mem: + kfree(hwmon); + + return result; } EXPORT_SYMBOL_GPL(thermal_add_hwmon_sysfs); @@ -144,7 +169,7 @@ thermal_hwmon_lookup(const struct thermal_zone_device *tz) struct thermal_hwmon_device *hwmon; list_for_each_entry(hwmon, &thermal_hwmon_list, node) { - if (hwmon->tz == tz) + if (hwmon->tz_temp.tz == tz) return hwmon; } return NULL; @@ -153,6 +178,7 @@ thermal_hwmon_lookup(const struct thermal_zone_device *tz) void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz) { struct thermal_hwmon_device *hwmon; + struct thermal_hwmon_temp *temp; scoped_guard(mutex, &thermal_hwmon_list_lock) { hwmon = thermal_hwmon_lookup(tz); @@ -162,6 +188,12 @@ void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz) list_del(&hwmon->node); } + temp = &hwmon->tz_temp; + + device_remove_file(hwmon->device, &temp->temp_input.attr); + if (temp->temp_crit_present) + device_remove_file(hwmon->device, &temp->temp_crit.attr); + hwmon_device_unregister(hwmon->device); kfree(hwmon); } diff --git a/include/linux/hwmon.h b/include/linux/hwmon.h index 77a6f2bffcba79..dd713e193d0c3a 100644 --- a/include/linux/hwmon.h +++ b/include/linux/hwmon.h @@ -480,8 +480,7 @@ hwmon_device_register_with_info(struct device *dev, const struct attribute_group **extra_groups); struct device * hwmon_device_register_for_thermal(struct device *dev, const char *name, - void *drvdata, - const struct attribute_group **extra_groups); + void *drvdata); struct device * devm_hwmon_device_register_with_info(struct device *dev, const char *name, void *drvdata, From f93d951ce0d02b5dca01c0c72add411fb17849bb Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 31 Jul 2026 15:01:15 +0200 Subject: [PATCH 119/612] Revert "thermal: hwmon: Register a hwmon device for each thermal zone" Revert commit d6323469bcfb ("thermal: hwmon: Register a hwmon device for each thermal zone") that changed the names of hwmon class devices associated with thermal zones and their sysfs layout which made user space unhappy. Closes: https://lore.kernel.org/linux-pm/cafd8af9-c6e9-4bf2-b496-23e796fbc9a6@linux.dev/ Closes: https://lore.kernel.org/linux-hwmon/ab8b093b-46e6-4738-afcf-4b97c9ad5af9@googlemail.com/ Cc: stable@vger.kernel.org Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/2301040.irdbgypaU6@rafael.j.wysocki --- drivers/thermal/thermal_hwmon.c | 151 ++++++++++++++++++++++---------- 1 file changed, 104 insertions(+), 47 deletions(-) diff --git a/drivers/thermal/thermal_hwmon.c b/drivers/thermal/thermal_hwmon.c index 223ae1571655bd..597c33c8a55508 100644 --- a/drivers/thermal/thermal_hwmon.c +++ b/drivers/thermal/thermal_hwmon.c @@ -19,33 +19,30 @@ #include "thermal_hwmon.h" #include "thermal_core.h" -/* - * Needs to be large enough to hold a thermal zone type string followed by an - * underline character and a 32-bit integer in decimal representation. - */ -#define THERMAL_HWMON_NAME_LENGTH (THERMAL_NAME_LENGTH + 11) +/* hwmon sys I/F */ +/* thermal zone devices with the same type share one hwmon device */ +struct thermal_hwmon_device { + char type[THERMAL_NAME_LENGTH]; + struct device *device; + int count; + struct list_head tz_list; + struct list_head node; +}; struct thermal_hwmon_attr { struct device_attribute attr; + char name[16]; }; /* one temperature input for each thermal zone */ struct thermal_hwmon_temp { + struct list_head hwmon_node; struct thermal_zone_device *tz; struct thermal_hwmon_attr temp_input; /* hwmon sys attr */ struct thermal_hwmon_attr temp_crit; /* hwmon sys attr */ bool temp_crit_present; }; -/* hwmon sys I/F */ -/* thermal zone devices with the same type share one hwmon device */ -struct thermal_hwmon_device { - char name[THERMAL_HWMON_NAME_LENGTH]; - struct device *device; - struct list_head node; - struct thermal_hwmon_temp tz_temp; -}; - static LIST_HEAD(thermal_hwmon_list); static DEFINE_MUTEX(thermal_hwmon_list_lock); @@ -91,6 +88,45 @@ temp_crit_show(struct device *dev, struct device_attribute *attr, char *buf) return sysfs_emit(buf, "%d\n", temperature); } + +static struct thermal_hwmon_device * +thermal_hwmon_lookup_by_type(const struct thermal_zone_device *tz) +{ + struct thermal_hwmon_device *hwmon; + char type[THERMAL_NAME_LENGTH]; + + mutex_lock(&thermal_hwmon_list_lock); + list_for_each_entry(hwmon, &thermal_hwmon_list, node) { + strscpy(type, tz->type); + strreplace(type, '-', '_'); + if (!strcmp(hwmon->type, type)) { + mutex_unlock(&thermal_hwmon_list_lock); + return hwmon; + } + } + mutex_unlock(&thermal_hwmon_list_lock); + + return NULL; +} + +/* Find the temperature input matching a given thermal zone */ +static struct thermal_hwmon_temp * +thermal_hwmon_lookup_temp(const struct thermal_hwmon_device *hwmon, + const struct thermal_zone_device *tz) +{ + struct thermal_hwmon_temp *temp; + + mutex_lock(&thermal_hwmon_list_lock); + list_for_each_entry(temp, &hwmon->tz_list, hwmon_node) + if (temp->tz == tz) { + mutex_unlock(&thermal_hwmon_list_lock); + return temp; + } + mutex_unlock(&thermal_hwmon_list_lock); + + return NULL; +} + static bool thermal_zone_crit_temp_valid(struct thermal_zone_device *tz) { int temp; @@ -101,39 +137,54 @@ int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) { struct thermal_hwmon_device *hwmon; struct thermal_hwmon_temp *temp; + int new_hwmon_device = 1; int result; + hwmon = thermal_hwmon_lookup_by_type(tz); + if (hwmon) { + new_hwmon_device = 0; + goto register_sys_interface; + } + hwmon = kzalloc_obj(*hwmon); if (!hwmon) return -ENOMEM; - /* - * Append the thermal zone ID preceded by an underline character to the - * type to disambiguate the sensors command output. - */ - scnprintf(hwmon->name, THERMAL_HWMON_NAME_LENGTH, "%s_%d", tz->type, tz->id); - strreplace(hwmon->name, '-', '_'); + INIT_LIST_HEAD(&hwmon->tz_list); + strscpy(hwmon->type, tz->type, THERMAL_NAME_LENGTH); + strreplace(hwmon->type, '-', '_'); hwmon->device = hwmon_device_register_for_thermal(&tz->device, - hwmon->name, hwmon); + hwmon->type, hwmon); if (IS_ERR(hwmon->device)) { result = PTR_ERR(hwmon->device); goto free_mem; } - temp = &hwmon->tz_temp; + register_sys_interface: + temp = kzalloc_obj(*temp); + if (!temp) { + result = -ENOMEM; + goto unregister_name; + } temp->tz = tz; + hwmon->count++; - temp->temp_input.attr.attr.name = "temp1_input"; + snprintf(temp->temp_input.name, sizeof(temp->temp_input.name), + "temp%d_input", hwmon->count); + temp->temp_input.attr.attr.name = temp->temp_input.name; temp->temp_input.attr.attr.mode = 0444; temp->temp_input.attr.show = temp_input_show; sysfs_attr_init(&temp->temp_input.attr.attr); result = device_create_file(hwmon->device, &temp->temp_input.attr); if (result) - goto unregister_name; + goto free_temp_mem; if (thermal_zone_crit_temp_valid(tz)) { - temp->temp_crit.attr.attr.name = "temp1_crit"; + snprintf(temp->temp_crit.name, + sizeof(temp->temp_crit.name), + "temp%d_crit", hwmon->count); + temp->temp_crit.attr.attr.name = temp->temp_crit.name; temp->temp_crit.attr.attr.mode = 0444; temp->temp_crit.attr.show = temp_crit_show; sysfs_attr_init(&temp->temp_crit.attr.attr); @@ -145,17 +196,21 @@ int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) temp->temp_crit_present = true; } - /* The list is needed for hwmon lookup during removal. */ mutex_lock(&thermal_hwmon_list_lock); - list_add_tail(&hwmon->node, &thermal_hwmon_list); + if (new_hwmon_device) + list_add_tail(&hwmon->node, &thermal_hwmon_list); + list_add_tail(&temp->hwmon_node, &hwmon->tz_list); mutex_unlock(&thermal_hwmon_list_lock); return 0; unregister_input: device_remove_file(hwmon->device, &temp->temp_input.attr); + free_temp_mem: + kfree(temp); unregister_name: - hwmon_device_unregister(hwmon->device); + if (new_hwmon_device) + hwmon_device_unregister(hwmon->device); free_mem: kfree(hwmon); @@ -163,37 +218,39 @@ int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) } EXPORT_SYMBOL_GPL(thermal_add_hwmon_sysfs); -static struct thermal_hwmon_device * -thermal_hwmon_lookup(const struct thermal_zone_device *tz) -{ - struct thermal_hwmon_device *hwmon; - - list_for_each_entry(hwmon, &thermal_hwmon_list, node) { - if (hwmon->tz_temp.tz == tz) - return hwmon; - } - return NULL; -} - void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz) { struct thermal_hwmon_device *hwmon; struct thermal_hwmon_temp *temp; - scoped_guard(mutex, &thermal_hwmon_list_lock) { - hwmon = thermal_hwmon_lookup(tz); - if (!hwmon) - return; - - list_del(&hwmon->node); + hwmon = thermal_hwmon_lookup_by_type(tz); + if (unlikely(!hwmon)) { + /* Should never happen... */ + dev_dbg(&tz->device, "hwmon device lookup failed!\n"); + return; } - temp = &hwmon->tz_temp; + temp = thermal_hwmon_lookup_temp(hwmon, tz); + if (unlikely(!temp)) { + /* Should never happen... */ + dev_dbg(&tz->device, "temperature input lookup failed!\n"); + return; + } device_remove_file(hwmon->device, &temp->temp_input.attr); if (temp->temp_crit_present) device_remove_file(hwmon->device, &temp->temp_crit.attr); + mutex_lock(&thermal_hwmon_list_lock); + list_del(&temp->hwmon_node); + kfree(temp); + if (!list_empty(&hwmon->tz_list)) { + mutex_unlock(&thermal_hwmon_list_lock); + return; + } + list_del(&hwmon->node); + mutex_unlock(&thermal_hwmon_list_lock); + hwmon_device_unregister(hwmon->device); kfree(hwmon); } From 7f03a417fc75fbe31a06199d8b609b8f313b5301 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Mon, 20 Jul 2026 19:19:41 +0930 Subject: [PATCH 120/612] btrfs: disable large folios for systems with highmem [BUG] There is a bug report that on 32bit systems (i686), btrfs crashes when trying to do zstd compression: BUG: unable to handle page fault for address: fffbc000 #PF: supervisor read access in kernel mode #PF: error_code(0x0000) - not-present page CPU: 0 UID: 0 PID: 61 Comm: kworker/u8:5 Tainted: G N 7.2.0-rc3-P3 #2 PREEMPTLAZY Hardware name: LENOVO 2007F2G/2007F2G, BIOS 79ETE7WW (2.27 ) 03/21/2011 Workqueue: btrfs-delalloc btrfs_work_helper EIP: ZSTD_compressStream2+0x221/0x5fc Call Trace: ZSTD_compressStream+0xd/0x48 zstd_compress_stream+0x8/0x10 zstd_compress_bio+0x20a/0x564 btrfs_compress_bio+0x94/0xc0 compress_file_range+0x20a/0x380 btrfs_work_helper+0xc1/0x1b4 process_scheduled_works+0x15f/0x204 worker_thread+0x10c/0x178 kthread+0xe1/0xe8 ret_from_fork+0x1d/0x14c ret_from_fork_asm+0x12/0x18 entry_INT80_32+0xf0/0xf0 CR2: 00000000fffbc000 ---[ end trace 0000000000000000 ]--- [CAUSE] Inside zstd_compress_bio(), we assume the whole page cache folio can be mapped in one go. However that assumption is not true on systems with CONFIG_HIGHMEM, the pages of the large folio can be in HIGHMEM, which needs to be mapped before access. Meanwhile zstd_compress_bio() only map the page of a large folio where the start filepos is, the remaining pages are not mapped, and accessing the remaining pages will trigger the above crash. [FIX] Do not enable large folios when the kernel has CONFIG_HIGHMEM enabled. This is the same handling for bs > ps support. Link: https://github.com/kdave/btrfs-progs/issues/1146 Reported-by: Erhard Furtner Fixes: 9bce95edb1b4 ("btrfs: move large data folios out of experimental features") Reviewed-by: Jeff Layton Signed-off-by: Qu Wenruo Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/disk-io.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c index 36332df9a0f1c0..87385b97c81bd3 100644 --- a/fs/btrfs/disk-io.c +++ b/fs/btrfs/disk-io.c @@ -3468,7 +3468,15 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device fs_info->sectorsize = sectorsize; fs_info->sectorsize_bits = ilog2(sectorsize); fs_info->block_min_order = ilog2(round_up(sectorsize, PAGE_SIZE) >> PAGE_SHIFT); - fs_info->block_max_order = calc_block_max_order(fs_info->sectorsize_bits); + /* + * For HIGHMEM, a large folio cannot be mapped in one go, breaking a lot + * of basic assumptions for btrfs IOs. + * Disable large folios for such 32-bit systems. + */ + if (IS_ENABLED(CONFIG_HIGHMEM)) + fs_info->block_max_order = fs_info->block_min_order; + else + fs_info->block_max_order = calc_block_max_order(fs_info->sectorsize_bits); fs_info->csums_per_leaf = BTRFS_MAX_ITEM_SIZE(fs_info) / fs_info->csum_size; fs_info->stripesize = stripesize; fs_info->fs_devices->fs_info = fs_info; From 0fa78ef637deb5dbe341582f88553a4bce496de0 Mon Sep 17 00:00:00 2001 From: David Lee Date: Wed, 15 Jul 2026 09:05:14 +0000 Subject: [PATCH 121/612] btrfs: lzo: reject inline extents without valid headers [BUG] For a crafted btrfs image, the following KASAN can be triggered when reading an inline lzo compressed file extent: BUG: KASAN: slab-out-of-bounds in lzo_decompress+0x57d/0x700 Read of size 4 at addr ffff888006f2e644 by task btrfs_lzo_inlin/77 Call Trace: dump_stack_lvl+0x5b/0x70 print_report+0xd1/0x610 kasan_report+0xe0/0x110 __asan_report_load_n_noabort+0x13/0x20 lzo_decompress+0x57d/0x700 btrfs_decompress+0x140/0x1c0 uncompress_inline+0x147/0x1b0 btrfs_get_extent+0xb23/0x10a0 btrfs_do_readpage.constprop.0+0x538/0x1ac0 btrfs_readahead+0x32f/0x5f0 read_pages+0x16f/0x850 page_cache_ra_unbounded+0x296/0x490 do_page_cache_ra+0xd9/0x130 page_cache_sync_ra+0x3ee/0x6f0 filemap_get_pages+0x306/0x15c0 filemap_read+0x329/0xd00 btrfs_file_read_iter+0x1f8/0x2b0 vfs_read+0x4ef/0x720 ksys_read+0xf8/0x1d0 __x64_sys_read+0x71/0xb0 x64_sys_call+0x1ab0/0x1b70 do_syscall_64+0x61/0x470 entry_SYSCALL_64_after_hwframe+0x4b/0x53 [CAUSE] For an inline lzo compressed file extent, there should always be one lzo header, recording the total length of the compressed data, followed by one segment header, recording the compressed lzo payload. But if a crafted inline lzo compressed file extent contains only an lzo header, without the segment header or payload, lzo_decompress() will still try to read the segment header, causing a read beyond the item boundary. Furthermore if the inline lzo compressed file extent is the first item of the leaf, it will be at the extent buffer boundary. The above out-of-boundary read will go beyond the extent buffer boundary, triggering the above KASAN report. [FIX] Validate the total length of the inlined lzo compressed file extent, to make sure there is at least one LZO header and one segment header, and a non-zero payload. Fixes: a6fa6fae40ec ("btrfs: Add lzo compression support") Assisted-by: Codex:gpt-5.5 Signed-off-by: David Lee [ Rework the commit message to remove slop ] Signed-off-by: Qu Wenruo Signed-off-by: David Sterba --- fs/btrfs/lzo.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fs/btrfs/lzo.c b/fs/btrfs/lzo.c index 1531adb117d15a..2f0996692da05a 100644 --- a/fs/btrfs/lzo.c +++ b/fs/btrfs/lzo.c @@ -552,9 +552,10 @@ int lzo_decompress(struct list_head *ws, const u8 *data_in, size_t max_segment_len = workspace_buf_length(fs_info); int ret; - if (unlikely(srclen < LZO_LEN || srclen > max_segment_len + LZO_LEN * 2)) { + if (unlikely(srclen <= LZO_LEN * 2 || + srclen > max_segment_len + LZO_LEN * 2)) { btrfs_err(fs_info, "invalid lzo header length, has %zu expect (%u, %zu)", - srclen, LZO_LEN, max_segment_len + LZO_LEN * 2); + srclen, LZO_LEN * 2, max_segment_len + LZO_LEN * 2); return -EUCLEAN; } From d2a4e4e626b2f4670b69b430c357f03f53eb6632 Mon Sep 17 00:00:00 2001 From: Dmitry Antipov Date: Mon, 27 Jul 2026 14:53:52 +0300 Subject: [PATCH 122/612] btrfs: fix memory leak in btrfs_do_encoded_write() Local fuzzing of 6.12.94 has found the following memory leak: Unreferenced object 0xffff888018050a80 (size 64): comm "syz.0.17", pid 10297, jiffies 4294953601 hex dump (first 32 bytes): 00 10 00 00 00 00 00 00 01 00 00 00 00 00 00 00 ................ 10 0a 05 18 80 88 ff ff 10 0a 05 18 80 88 ff ff ................ backtrace (crc a8a6fc29): kmemleak_alloc_recursive include/linux/kmemleak.h:42 [inline] slab_post_alloc_hook mm/slub.c:4152 [inline] slab_alloc_node mm/slub.c:4197 [inline] __kmalloc_cache_noprof+0x168/0x2c0 mm/slub.c:4358 kmalloc_noprof include/linux/slab.h:878 [inline] extent_changeset_alloc fs/btrfs/extent_io.h:207 [inline] qgroup_reserve_data+0x1c5/0x7d0 fs/btrfs/qgroup.c:4305 btrfs_qgroup_reserve_data+0x2e/0xb0 fs/btrfs/qgroup.c:4355 btrfs_do_encoded_write+0x92e/0x1040 fs/btrfs/inode.c:9746 btrfs_encoded_write fs/btrfs/file.c:1482 [inline] btrfs_do_write_iter+0x280/0x610 fs/btrfs/file.c:1507 btrfs_ioctl_encoded_write+0x3d6/0x490 fs/btrfs/ioctl.c:4738 btrfs_ioctl+0x6f9/0xc90 fs/btrfs/ioctl.c:-1 vfs_ioctl fs/ioctl.c:51 [inline] __do_sys_ioctl fs/ioctl.c:906 [inline] __se_sys_ioctl+0xf9/0x170 fs/ioctl.c:892 do_syscall_x64 arch/x86/entry/common.c:47 [inline] do_syscall_64+0xbe/0x1a0 arch/x86/entry/common.c:78 entry_SYSCALL_64_after_hwframe+0x77/0x7f Unreferenced object 0xffff888018050a00 (size 64): comm "syz.0.17", pid 10297, jiffies 4294953601 hex dump (first 32 bytes): 00 00 00 00 00 00 00 00 ff 0f 00 00 00 00 00 00 ................ 90 0a 05 18 80 88 ff ff 90 0a 05 18 80 88 ff ff ................ backtrace (crc cb5c9580): kmemleak_alloc_recursive include/linux/kmemleak.h:42 [inline] slab_post_alloc_hook mm/slub.c:4152 [inline] slab_alloc_node mm/slub.c:4197 [inline] __kmalloc_cache_noprof+0x168/0x2c0 mm/slub.c:4358 kmalloc_noprof include/linux/slab.h:878 [inline] kzalloc_noprof include/linux/slab.h:1014 [inline] ulist_prealloc+0x9c/0x110 fs/btrfs/ulist.c:114 extent_changeset_prealloc fs/btrfs/extent_io.h:217 [inline] __set_extent_bit+0x16b/0x1a70 fs/btrfs/extent-io-tree.c:1086 set_record_extent_bits+0x50/0x90 fs/btrfs/extent-io-tree.c:1821 qgroup_reserve_data+0x274/0x7d0 fs/btrfs/qgroup.c:4312 btrfs_qgroup_reserve_data+0x2e/0xb0 fs/btrfs/qgroup.c:4355 btrfs_do_encoded_write+0x92e/0x1040 fs/btrfs/inode.c:9746 btrfs_encoded_write fs/btrfs/file.c:1482 [inline] btrfs_do_write_iter+0x280/0x610 fs/btrfs/file.c:1507 btrfs_ioctl_encoded_write+0x3d6/0x490 fs/btrfs/ioctl.c:4738 btrfs_ioctl+0x6f9/0xc90 fs/btrfs/ioctl.c:-1 vfs_ioctl fs/ioctl.c:51 [inline] __do_sys_ioctl fs/ioctl.c:906 [inline] __se_sys_ioctl+0xf9/0x170 fs/ioctl.c:892 do_syscall_x64 arch/x86/entry/common.c:47 [inline] do_syscall_64+0xbe/0x1a0 arch/x86/entry/common.c:78 entry_SYSCALL_64_after_hwframe+0x77/0x7f Fix this by freeing an extent changeset before returning from btrfs_do_encoded_write(). Fixes: 7c0c7269f7b5 ("btrfs: add BTRFS_IOC_ENCODED_WRITE") Reviewed-by: Filipe Manana Signed-off-by: Dmitry Antipov Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/inode.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 04ea10b61bbb79..a443b8e6824d1e 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -9954,6 +9954,7 @@ ssize_t btrfs_do_encoded_write(struct kiocb *iocb, struct iov_iter *from, if (cb) cleanup_compressed_bio(cb); out: + extent_changeset_free(data_reserved); if (ret >= 0) iocb->ki_pos += encoded->len; return ret; From 4c375ac546ea667e619ab77d34cca6edcab7c448 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Thu, 30 Jul 2026 16:37:39 +0930 Subject: [PATCH 123/612] btrfs: disable bs > ps support if no transparent hugepage support Btrfs relies on mapping_set_folio_order_range() to set the minimal folio order for all its data inodes, but that function will be no-op if transparent hugepage is not enabled. Guard the bs > ps support behind CONFIG_TRANSPARENT_HUGEPAGE, just like all other filesystems. Fixes: 98077f7f2180 ("btrfs: enable experimental bs > ps support") Reviewed-by: Filipe Manana Signed-off-by: Qu Wenruo Signed-off-by: David Sterba --- fs/btrfs/Kconfig | 3 ++- fs/btrfs/fs.c | 12 ++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/fs/btrfs/Kconfig b/fs/btrfs/Kconfig index 9de04c37e11af4..4b10d78ed99b16 100644 --- a/fs/btrfs/Kconfig +++ b/fs/btrfs/Kconfig @@ -106,7 +106,8 @@ config BTRFS_EXPERIMENTAL - extent tree v2 - complex rework of extent tracking - - block size > page size support + - block size > page size support - needs transparent huge page and + non-HIGHMEM system - huge folios for data - folios can be as large as 2MiB now diff --git a/fs/btrfs/fs.c b/fs/btrfs/fs.c index 14d83565cdee17..dcf12979af33ce 100644 --- a/fs/btrfs/fs.c +++ b/fs/btrfs/fs.c @@ -166,9 +166,17 @@ bool __attribute_const__ btrfs_supported_blocksize(u32 blocksize) * * Considering HIGHMEM is such a pain to deal with and it's going * to be deprecated eventually, just reject HIGHMEM && bs > ps cases. + * + * Finally, for bs > ps cases, we need to set the minimal folio order, + * which requires transparent hugepage. */ - if (IS_ENABLED(CONFIG_HIGHMEM) && blocksize > PAGE_SIZE) - return false; + if (blocksize > PAGE_SIZE) { + if (IS_ENABLED(CONFIG_HIGHMEM)) + return false; + + if (!IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE)) + return false; + } return true; #endif return false; From 0ef349734a93227b45f65fc50a3311d1cc5f03e9 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Fri, 31 Jul 2026 10:14:49 +0930 Subject: [PATCH 124/612] btrfs: initialize inode mapping flags for cached inodes [BUG] When running generic/795 with 8K block size, 4K page size, the test always fails, triggering some ASSERT()s related to folio size: 795 (241074): drop_caches: 3 assertion failed: IS_ALIGNED(start, blocksize) && IS_ALIGNED(end + 1, blocksize), in extent_io.c:1404 (blocksize=8192 root=262 ino=258 start=16826368 end=16830463 mapping min order=0) ------------[ cut here ]------------ kernel BUG at extent_io.c:1404! Oops: invalid opcode: 0000 [#1] SMP CPU: 8 UID: 0 PID: 241105 Comm: fsstress Tainted: G OE 7.2.0-rc5-custom+ #442 PREEMPT(full) f4bfb352566f3949f29c233ce6f735050a03b245 Tainted: [O]=OOT_MODULE, [E]=UNSIGNED_MODULE Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS unknown 02/02/2022 RIP: 0010:assert_folio_range.cold+0x3d/0x3f [btrfs] Call Trace: btrfs_read_folio+0x9e/0x170 [btrfs 4cd1dd93b341b8ef766643f9512f4a86259567a3] prepare_one_folio.constprop.0+0x104/0x2a0 [btrfs 4cd1dd93b341b8ef766643f9512f4a86259567a3] btrfs_buffered_write+0x285/0xa50 [btrfs 4cd1dd93b341b8ef766643f9512f4a86259567a3] btrfs_do_write_iter+0x1aa/0x210 [btrfs 4cd1dd93b341b8ef766643f9512f4a86259567a3] iter_file_splice_write+0x31a/0x540 direct_splice_actor+0x53/0x170 splice_direct_to_actor+0xe9/0x240 do_splice_direct+0x76/0xb0 vfs_copy_file_range+0x1fd/0x630 __x64_sys_copy_file_range+0xf9/0x220 do_syscall_64+0xe1/0x790 entry_SYSCALL_64_after_hwframe+0x4b/0x53 ---[ end trace 0000000000000000 ]--- The ASSERT() itself is added by a later patch. The crash is triggered with that new debug patch, and without this fix. [CAUSE] In the above case, the start 16826368 is properly 8K aligned, but the end (16830463 + 1) is not 8K aligned. Furthermore the mapping's minimal folio order is 0, not the expected 1 for 8K block size with 4K page size. So this means some inodes do not have btrfs_set_inode_mapping_order() called on it. The missing btrfs_set_inode_mapping_order() call happens for cached inodes, through the following events: - btrfs_create_new_inode() called for inode X Which properly sets minimal folio order for the VFS inode. - btrfs_update_inode() called for inode X Which calls btrfs_delayed_update_inode() to create a delayed_node into root->delayed_nodes xarray. - Drop cache/memory pressure, evicting in-memory inode X Which evicted the inode X, but delayed_node is still in root->delayed_nodes for future reuse. - btrfs_iget() for inode X called again btrfs_iget() |- btrfs_iget_locked() | |- iget5_locked_rcu() | Which creates a new vfs_inode for btrfs, whose mapping still | has the minimal order as 0. | |- btrfs_read_locked_inode() |- btrfs_fill_inode() | |- btrfs_get_delayed_node() | Which found out the previous node, and use that delayed | node to initialize the new inode. | |- filled = true; |- if (filled) goto cache_index; Which skips the btrfs_update_inode_mapping_flags() and btrfs_set_inode_mapping_order() calls. So the inode still has minimal folio order set as 0, not the required 1. Thus later page cache read will get a folio whose size is smaller than block size, as the mapping has its minimal folio order set as 0 not 1, then trigger the ASSERT(). [FIX] Move the btrfs_update_inode_mapping_flags() and btrfs_set_inode_mapping_order() calls under cache_index label, so that the mapping flags and minimal folio order is always set no matter if we have a cached inode. Assisted-by: LLM (analysis) Fixes: ecde48a1a6b3 ("btrfs: expose per-inode stable writes flag") Fixes: cc38d178ff33 ("btrfs: enable large data folio support under CONFIG_BTRFS_EXPERIMENTAL") Reviewed-by: Filipe Manana Signed-off-by: Qu Wenruo Signed-off-by: David Sterba --- fs/btrfs/inode.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index a443b8e6824d1e..afea512c2219ac 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -3938,10 +3938,11 @@ static int btrfs_read_locked_inode(struct btrfs_inode *inode, struct btrfs_path btrfs_inode_split_flags(btrfs_inode_flags(leaf, inode_item), &inode->flags, &inode->ro_flags); + +cache_index: btrfs_update_inode_mapping_flags(inode); btrfs_set_inode_mapping_order(inode); -cache_index: /* * If we were modified in the current generation and evicted from memory * and then re-read we need to do a full sync since we don't have any From 28254722a459938d97150d3b0712b81e06d0645e Mon Sep 17 00:00:00 2001 From: Carlos Llamas Date: Thu, 30 Jul 2026 22:15:07 +0000 Subject: [PATCH 125/612] selinux: bpf: check SBLABEL_MNT before isec init selinux_inode_init_security() marks the isec as initialized before checking if mount labeling is supported (SBLABEL_MNT). This was fine until commit 9722955b5430 ("bpf: Add simple xattr support to bpffs"), where genfscon bpffs mounts fail the SBLABEL_MNT check as expected (no xattrs) and yet leave the isec->initialized. This breaks subsequent calls to inode_doinit_with_dentry(). Do the SBLABEL_MNT check before the inode security is initialized. Cc: stable@vger.kernel.org Closes: https://lore.kernel.org/all/akWdcp6P0FkNDzBk@google.com/ Fixes: 9722955b5430 ("bpf: Add simple xattr support to bpffs") Acked-by: Stephen Smalley Signed-off-by: Carlos Llamas Signed-off-by: Paul Moore --- security/selinux/hooks.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 8d6945edae7aac..18dd28b2bb13d2 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -2974,6 +2974,10 @@ static int selinux_inode_init_security(struct inode *inode, struct inode *dir, sbsec = selinux_superblock(dir->i_sb); + if (!selinux_initialized() || + !(sbsec->flags & SBLABEL_MNT)) + return -EOPNOTSUPP; + newsid = crsec->create_sid; newsclass = inode_mode_to_security_class(inode->i_mode); rc = selinux_determine_inode_label(crsec, dir, qstr, newsclass, &newsid); @@ -2988,10 +2992,6 @@ static int selinux_inode_init_security(struct inode *inode, struct inode *dir, isec->initialized = LABEL_INITIALIZED; } - if (!selinux_initialized() || - !(sbsec->flags & SBLABEL_MNT)) - return -EOPNOTSUPP; - xattr = lsm_get_xattr_slot(xattrs, xattr_count); if (xattr) { rc = security_sid_to_context_force(newsid, From e053b624f5d36669756990743346157be9f68c34 Mon Sep 17 00:00:00 2001 From: Achilles Gaikwad Date: Tue, 7 Jul 2026 11:23:05 -0400 Subject: [PATCH 126/612] NFSv4.2: fix nfs4_listxattr size accounting A call to listxattr() with a buffer size of 0 returns the actual size of the buffer needed for a subsequent call. On an NFSv4.2 mount this triggers the following oops: [ 399.768687] BUG: kernel NULL pointer dereference, address: 0000000000000000 [ 399.768705] RIP: 0010:_copy_from_pages+0x44/0xe0 [ 399.768722] Call Trace: [ 399.768723] nfs4_xattr_alloc_entry+0x1bf/0x1e0 [ 399.768730] nfs4_xattr_cache_set_list+0x43/0x1f0 [ 399.768731] nfs4_listxattr+0x21f/0x250 [ 399.768733] vfs_listxattr+0x55/0xa0 [ 399.768736] listxattr+0x23/0x160 [ 399.768737] path_listxattrat+0xba/0x1e0 [ 399.768739] do_syscall_64+0xe2/0x680 security_inode_listsecurity() (via the xattr_list_one() helper) now decrements the remaining size even when the buffer pointer is NULL, so in the size-query case, 'left' underflows to a huge size_t value. As a result, nfs4_listxattr_nfs4_user() treats the NULL buffer as a real one, leading to a NULL pointer dereference in _copy_from_pages(). security_inode_listsecurity() does not return the number of bytes it added to the list, so the code derived it as 'size - error - left'. That is also wrong in the size-query case: the generic_listxattr() contribution is only subtracted from 'left' when a buffer is present. Thus, the query result comes up short by exactly that contribution (e.g., "system.nfs4_acl" on a mount with ACL support), and a caller that allocates the returned size gets -ERANGE on the subsequent call. Declare 'left' as ssize_t, use a scratch copy to measure security hook consumption, and only decrement 'left' if a buffer is present. Fixes: f71ece9712b7 ("security,fs,nfs,net: update security_inode_listsecurity() interface") Suggested-by: Paul Moore Signed-off-by: Achilles Gaikwad Reviewed-by: Paul Moore Signed-off-by: Anna Schumaker --- fs/nfs/nfs4proc.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c index 7d98e9a98580eb..5709c6fea85bbd 100644 --- a/fs/nfs/nfs4proc.c +++ b/fs/nfs/nfs4proc.c @@ -10596,7 +10596,8 @@ const struct nfs4_minor_version_ops *nfs_v4_minor_ops[] = { static ssize_t nfs4_listxattr(struct dentry *dentry, char *list, size_t size) { ssize_t error, error2, error3; - size_t left = size; + ssize_t left = size; + ssize_t left2; error = generic_listxattr(dentry, list, left); if (error < 0) @@ -10606,10 +10607,13 @@ static ssize_t nfs4_listxattr(struct dentry *dentry, char *list, size_t size) left -= error; } - error2 = security_inode_listsecurity(d_inode(dentry), &list, &left); + left2 = left; + error2 = security_inode_listsecurity(d_inode(dentry), &list, &left2); if (error2 < 0) return error2; - error2 = size - error - left; + error2 = left - left2; + if (list) + left -= error2; error3 = nfs4_listxattr_nfs4_user(d_inode(dentry), list, left); if (error3 < 0) From 8d51e0fd3e698919d2adeff71936377f0c0d4aa0 Mon Sep 17 00:00:00 2001 From: Lizhi Hou Date: Thu, 23 Jul 2026 00:42:56 -0700 Subject: [PATCH 127/612] accel/amxdna: Fix page-insertion errors in amdxdna_insert_pages() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two error paths in amdxdna_insert_pages() called vma->vm_ops->close(vma) before returning an error code to the caller. This is incorrect: amdxdna_gem_obj_mmap() registers an HMM interval notifier before calling amdxdna_insert_pages(), and on a hard error it jumps to hmm_unreg to undo that registration. Calling vm_ops->close() manually — which drops the shmem pages_pin_count and the GEM object reference that backs the VMA — before the mmap syscall has even returned causes those resources to be released while the VMA is still alive. The kernel VMA teardown will call vm_ops->close() a second time when the process later unmaps the range, producing a reference count underflow. Replace both hard-error returns with a deferred-fault approach that keeps the VMA alive and retries page insertion through the HMM range-fault path. Fixes: e486147c912f ("accel/amdxdna: Add BO import and export") Reviewed-by: Max Zhen Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20260723074256.2435143-1-lizhi.hou@amd.com --- drivers/accel/amdxdna/amdxdna_gem.c | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/drivers/accel/amdxdna/amdxdna_gem.c b/drivers/accel/amdxdna/amdxdna_gem.c index 4628a27872656f..aec69d5f463e62 100644 --- a/drivers/accel/amdxdna/amdxdna_gem.c +++ b/drivers/accel/amdxdna/amdxdna_gem.c @@ -436,6 +436,23 @@ static void amdxdna_gem_dev_obj_free(struct drm_gem_object *gobj) amdxdna_gem_destroy_obj(abo); } +static void amdxdna_mark_mapp_invalid(struct amdxdna_gem_obj *abo, + struct vm_area_struct *vma) +{ + struct amdxdna_dev *xdna = to_xdna_dev(to_gobj(abo)->dev); + struct amdxdna_umap *mapp; + + down_write(&xdna->notifier_lock); + abo->mem.map_invalid = true; + list_for_each_entry(mapp, &abo->mem.umap_list, node) { + if (compare_range(mapp, vma->vm_mm, vma->vm_start, vma->vm_end)) { + mapp->invalid = true; + break; + } + } + up_write(&xdna->notifier_lock); +} + static int amdxdna_insert_pages(struct amdxdna_gem_obj *abo, struct vm_area_struct *vma) { @@ -457,8 +474,7 @@ static int amdxdna_insert_pages(struct amdxdna_gem_obj *abo, &num_pages); if (ret) { XDNA_ERR(xdna, "Failed insert pages %d", ret); - vma->vm_ops->close(vma); - return ret; + amdxdna_mark_mapp_invalid(abo, vma); } return 0; @@ -478,9 +494,9 @@ static int amdxdna_insert_pages(struct amdxdna_gem_obj *abo, fault_ret = handle_mm_fault(vma, vma->vm_start + offset, FAULT_FLAG_WRITE, NULL); if (fault_ret & VM_FAULT_ERROR) { - vma->vm_ops->close(vma); XDNA_ERR(xdna, "Fault in page failed"); - return -EFAULT; + amdxdna_mark_mapp_invalid(abo, vma); + break; } offset += PAGE_SIZE; From a15970d916b39acc7c60a0a99c27a6e378690aa9 Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Wed, 29 Jul 2026 15:18:27 +0000 Subject: [PATCH 128/612] bpf: Simplify sanitize_err() signature The sanitize_err() function is called when: - ptr += scalar - scalar += ptr - scalar += scalar ALU operations are processed. This commit drops offset and pointer registers parameters from its signature to simplify the follow-up changes for 'scalar += ptr' case. regs[src].type is safe to access, as it is not mutated by the callers. Signed-off-by: Yiyang Chen Acked-by: Shung-Hsi Yu Link: https://patch.msgid.link/20260729-c3-035-public-bpf-v4-v4-1-8ee297e2346b@mails.tsinghua.edu.cn Signed-off-by: Eduard Zingerman --- kernel/bpf/verifier.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 7aa47342dc659c..9792d6622ffd66 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -13557,23 +13557,21 @@ static void sanitize_mark_insn_seen(struct bpf_verifier_env *env) env->insn_aux_data[env->insn_idx].seen = env->pass_cnt; } -static int sanitize_err(struct bpf_verifier_env *env, - const struct bpf_insn *insn, int reason, - const struct bpf_reg_state *off_reg, - const struct bpf_reg_state *dst_reg) +static int sanitize_err(struct bpf_verifier_env *env, const struct bpf_insn *insn, int reason) { static const char *err = "pointer arithmetic with it prohibited for !root"; const char *op = BPF_OP(insn->code) == BPF_ADD ? "add" : "sub"; u32 dst = insn->dst_reg, src = insn->src_reg; + struct bpf_reg_state *regs = cur_regs(env); switch (reason) { case REASON_BOUNDS: verbose(env, "R%d has unknown scalar with mixed signed bounds, %s\n", - off_reg == dst_reg ? dst : src, err); + regs[src].type == SCALAR_VALUE ? src : dst, err); break; case REASON_TYPE: verbose(env, "R%d has pointer with unsupported alu operation, %s\n", - off_reg == dst_reg ? src : dst, err); + regs[src].type == SCALAR_VALUE ? dst : src, err); break; case REASON_PATHS: verbose(env, "R%d tried to %s from different maps, paths or scalars, %s\n", @@ -13762,7 +13760,7 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env, ret = sanitize_ptr_alu(env, insn, ptr_reg, off_reg, dst_reg, &info, false); if (ret < 0) - return sanitize_err(env, insn, ret, off_reg, dst_reg); + return sanitize_err(env, insn, ret); } switch (opcode) { @@ -13855,7 +13853,7 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env, return -EFAULT; } if (ret < 0) - return sanitize_err(env, insn, ret, off_reg, dst_reg); + return sanitize_err(env, insn, ret); } return 0; @@ -14607,7 +14605,7 @@ static int adjust_scalar_min_max_vals(struct bpf_verifier_env *env, if (sanitize_needed(opcode)) { ret = sanitize_val_alu(env, insn); if (ret < 0) - return sanitize_err(env, insn, ret, NULL, NULL); + return sanitize_err(env, insn, ret); } /* Calculate sign/unsigned bounds and tnum for alu32 and alu64 bit ops. From a4c6f804b44c5c790269b25e0e61cf4e9f117c86 Mon Sep 17 00:00:00 2001 From: Yiyang Chen Date: Wed, 29 Jul 2026 15:18:28 +0000 Subject: [PATCH 129/612] bpf: Preserve pointer state for commuted arithmetic When scalar += pointer is handled in adjust_ptr_min_max_vals(), the destination register inherits the pointer state from the source pointer. Copying only selected fields is fragile because pointer provenance is tracked by several bpf_reg_state fields. Use the caller's temporary offset register to preserve the scalar operand while replacing the destination with the full pointer state. This preserves the frame number for PTR_TO_STACK registers and keeps parent identity fields consistent. Fixes: f4d7e40a5b71 ("bpf: introduce function calls (verification)") Signed-off-by: Yiyang Chen Tested-by: Daniel Wade Acked-by: Shung-Hsi Yu Link: https://patch.msgid.link/20260729-c3-035-public-bpf-v4-v4-2-8ee297e2346b@mails.tsinghua.edu.cn Signed-off-by: Eduard Zingerman --- kernel/bpf/verifier.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 9792d6622ffd66..cdb61fab843518 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -13743,11 +13743,12 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env, return -EACCES; } - /* In case of 'scalar += pointer', dst_reg inherits pointer type and id. - * The id may be overwritten later if we create a new variable offset. + /* For 'scalar += pointer', dst_reg inherits the complete pointer + * register state. Individual fields may be adjusted later by pointer + * arithmetic. Callers guarantee that below does not overwrite off_reg. */ - dst_reg->type = ptr_reg->type; - dst_reg->id = ptr_reg->id; + if (dst_reg != ptr_reg) + *dst_reg = *ptr_reg; if (!check_reg_sane_offset_scalar(env, off_reg, ptr_reg->type) || !check_reg_sane_offset_ptr(env, ptr_reg, ptr_reg->type)) @@ -13790,7 +13791,7 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env, } break; case BPF_SUB: - if (dst_reg == off_reg) { + if (dst_reg != ptr_reg) { /* scalar -= pointer. Creates an unknown scalar */ verbose(env, "R%d tried to subtract pointer from scalar\n", dst); @@ -14808,8 +14809,8 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env, err = mark_chain_precision(env, insn->dst_reg); if (err) return err; - return adjust_ptr_min_max_vals(env, insn, - src_reg, dst_reg); + off_reg = *dst_reg; + return adjust_ptr_min_max_vals(env, insn, src_reg, &off_reg); } } else if (ptr_reg) { /* pointer += scalar */ From cdf19b1b3c01791de074ce282089131026f52261 Mon Sep 17 00:00:00 2001 From: Yiyang Chen Date: Wed, 29 Jul 2026 15:18:29 +0000 Subject: [PATCH 130/612] bpf: Propagate untrusted pointer state in commuted arithmetic The untrusted PTR_TO_MEM early return skips pointer offset tracking because accesses go through probe-read handling. Moving it after full pointer-state propagation ensures scalar += untrusted_pointer leaves the destination as PTR_TO_MEM instead of an unrelated scalar. Fixes: f2362a57aeff ("bpf: allow void* cast using bpf_rdonly_cast()") Signed-off-by: Yiyang Chen Tested-by: Daniel Wade Link: https://patch.msgid.link/20260729-c3-035-public-bpf-v4-v4-3-8ee297e2346b@mails.tsinghua.edu.cn Signed-off-by: Eduard Zingerman --- kernel/bpf/verifier.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index cdb61fab843518..fdc5fbb1f78ca7 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -13707,13 +13707,6 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env, return -EACCES; } - /* - * Accesses to untrusted PTR_TO_MEM are done through probe - * instructions, hence no need to track offsets. - */ - if (base_type(ptr_reg->type) == PTR_TO_MEM && (ptr_reg->type & PTR_UNTRUSTED)) - return 0; - switch (base_type(ptr_reg->type)) { case PTR_TO_CTX: case PTR_TO_MAP_VALUE: @@ -13750,6 +13743,13 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env, if (dst_reg != ptr_reg) *dst_reg = *ptr_reg; + /* + * Accesses to untrusted PTR_TO_MEM are done through probe + * instructions, hence no need to track offsets. + */ + if (base_type(ptr_reg->type) == PTR_TO_MEM && (ptr_reg->type & PTR_UNTRUSTED)) + return 0; + if (!check_reg_sane_offset_scalar(env, off_reg, ptr_reg->type) || !check_reg_sane_offset_ptr(env, ptr_reg, ptr_reg->type)) return -EINVAL; From 21596761ff370f05460ad0f9078786082bbfa87d Mon Sep 17 00:00:00 2001 From: Yiyang Chen Date: Wed, 29 Jul 2026 15:18:30 +0000 Subject: [PATCH 131/612] selftests/bpf: Cover commuted pointer state propagation Add verifier coverage for the three cases affected by preserving the full pointer state across scalar += pointer: stack frame number inheritance, readonly-untrusted memory access, and dynptr data-slice invalidation. Signed-off-by: Yiyang Chen Tested-by: Daniel Wade Acked-by: Eduard Zingerman Link: https://patch.msgid.link/20260729-c3-035-public-bpf-v4-v4-4-8ee297e2346b@mails.tsinghua.edu.cn Signed-off-by: Eduard Zingerman --- .../testing/selftests/bpf/progs/dynptr_fail.c | 30 ++++++++++++++ .../bpf/progs/mem_rdonly_untrusted.c | 17 ++++++++ .../bpf/progs/verifier_basic_stack.c | 41 +++++++++++++++++++ 3 files changed, 88 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/dynptr_fail.c b/tools/testing/selftests/bpf/progs/dynptr_fail.c index 344fb2aa0813d9..29c6361d8820a7 100644 --- a/tools/testing/selftests/bpf/progs/dynptr_fail.c +++ b/tools/testing/selftests/bpf/progs/dynptr_fail.c @@ -1635,6 +1635,36 @@ static int callback(__u32 index, void *data) return 0; } +/* A commuted add should preserve the parent id of a dynptr data slice. */ +SEC("?raw_tp") +__failure __msg("invalid mem access 'scalar'") +int dynptr_slice_commuted_invalidate(void *ctx) +{ + struct bpf_dynptr ptr; + __u32 *slice, *derived; + + bpf_ringbuf_reserve_dynptr(&ringbuf, sizeof(__u32), 0, &ptr); + + slice = bpf_dynptr_data(&ptr, 0, sizeof(__u32)); + if (!slice) + goto done; + + asm volatile ("%[dst] = 0;" + "%[dst] += %[src];" + "%[src] = 0;" + : [dst]"=&r"(derived), [src]"+r"(slice) + : + : "memory"); + + bpf_ringbuf_discard_dynptr(&ptr, 0); + val = *derived; + return 0; + +done: + bpf_ringbuf_discard_dynptr(&ptr, 0); + return 0; +} + /* If the dynptr is written into in a callback function, its data * slices should be invalidated as well. */ diff --git a/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c b/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c index 5b4453747c2308..f166fff8f21761 100644 --- a/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c +++ b/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c @@ -226,4 +226,21 @@ int null_check(void *ctx) return 0; } +SEC("socket") +__success +__retval(1) +int ldx_is_ok_commuted_addr(void *ctx) +{ + int v, *p, *derived; + + v = 1; + p = bpf_rdonly_cast(&v, 0); + asm volatile ("%[dst] = 0;" + "%[dst] += %[src];" + : [dst]"=&r"(derived) + : [src]"r"(p) + : "memory"); + return *derived; +} + char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/verifier_basic_stack.c b/tools/testing/selftests/bpf/progs/verifier_basic_stack.c index fb62e09f21146d..d3df7a9f1d8c85 100644 --- a/tools/testing/selftests/bpf/progs/verifier_basic_stack.c +++ b/tools/testing/selftests/bpf/progs/verifier_basic_stack.c @@ -97,4 +97,45 @@ __naked void misaligned_read_from_stack(void) " ::: __clobber_all); } +SEC("socket") +__description("stack pointer arithmetic preserves frame number") +__failure __msg("R7 invalid mem access 'scalar'") +__naked void stack_ptr_arith_preserves_frameno(void) +{ + asm volatile ("\ + r3 = 0; \ + *(u64 *)(r10 - 8) = r3; \ + r1 = %[map_hash_8b] ll; \ + r2 = r10; \ + r2 += -8; \ + call %[bpf_map_lookup_elem]; \ + if r0 != 0 goto +2; \ + r0 = 0; \ + exit; \ + r1 = r0; \ + r2 = 0; \ + r3 = 0; \ + call stack_ptr_arith_preserves_frameno_subprog;\ + r0 = 0; \ + exit; \ + ": + : __imm(bpf_map_lookup_elem), + __imm_addr(map_hash_8b) + : __clobber_all); +} + +static __used __naked void stack_ptr_arith_preserves_frameno_subprog(void) +{ + asm volatile ("\ + *(u64 *)(r10 - 8) = r1; \ + r6 = -8; \ + r6 += r10; \ + *(u64 *)(r6 + 0) = r2; \ + r7 = *(u64 *)(r10 - 8); \ + *(u64 *)(r7 + 0) = r3; \ + r0 = 0; \ + exit; \ + "::: __clobber_all); +} + char _license[] SEC("license") = "GPL"; From 976245094925bab9bc39366b2e9ab44ffcde61d0 Mon Sep 17 00:00:00 2001 From: Mahanta Jambigi Date: Wed, 29 Jul 2026 15:01:53 +0200 Subject: [PATCH 132/612] net/smc: fix qentry overwrite for CONFIRM_LINK and ADD_LINK_CONT in smc_llc_event_handler() The SMC_LLC_CONFIRM_LINK / SMC_LLC_ADD_LINK_CONT branch in smc_llc_event_handler() stores an incoming qentry into the local LLC flow without first checking whether a qentry is already pending. If a malicious or buggy peer sends a second CONFIRM_LINK or ADD_LINK_CONT request while a flow is active and flow->qentry is already set, smc_llc_flow_qentry_set() overwrites the pointer without freeing the previous allocation, leaking one kmalloc-96 object per spurious message. The sibling SMC_LLC_DELETE_LINK branch already has the correct !flow->qentry guard. Apply the same guard to the CONFIRM_LINK/ADD_LINK_CONT branch so that a duplicate message when qentry is already occupied falls through to break and is freed by the kfree(qentry) at the out: label, rather than silently leaking the existing allocation. The response direction (smc_llc_rx_response()) is unaffected: it already guards with flow->qentry at the equivalent site and drops duplicate responses correctly. Fixes: 0fb0b02bd6fd ("net/smc: adapt SMC client code to use the LLC flow") Signed-off-by: Mahanta Jambigi Reviewed-by: Hidayath Khan Reviewed-by: Sidraya Jayagond Reviewed-by: Dust Li Link: https://patch.msgid.link/20260729130153.970800-1-mjambigi@linux.ibm.com Signed-off-by: Jakub Kicinski --- net/smc/smc_llc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/smc/smc_llc.c b/net/smc/smc_llc.c index 954b2ff1815c2c..aa6d83af55eda0 100644 --- a/net/smc/smc_llc.c +++ b/net/smc/smc_llc.c @@ -1927,7 +1927,8 @@ static void smc_llc_event_handler(struct smc_llc_qentry *qentry) return; case SMC_LLC_CONFIRM_LINK: case SMC_LLC_ADD_LINK_CONT: - if (lgr->llc_flow_lcl.type != SMC_LLC_FLOW_NONE) { + if (lgr->llc_flow_lcl.type != SMC_LLC_FLOW_NONE && + !lgr->llc_flow_lcl.qentry) { /* a flow is waiting for this message */ smc_llc_flow_qentry_set(&lgr->llc_flow_lcl, qentry); wake_up(&lgr->llc_msg_waiter); From 63488dba65ef91373ef616575b32eb0eb21459f4 Mon Sep 17 00:00:00 2001 From: Baul Lee Date: Wed, 29 Jul 2026 22:19:41 +0900 Subject: [PATCH 133/612] net: bridge: mrp: fix uninitialised bytes on the wire br_mrp_alloc_test_skb() builds MRP test frames on an skb from dev_alloc_skb(), which does not clear the linear data area. On the MRA ring-role branch the sub-option TLV header is appended with sub_tlv = skb_put(skb, sizeof(*sub_tlv)); sub_tlv->type = BR_MRP_SUB_TLV_HEADER_TEST_AUTO_MGR; so sub_tlv->length is never written, and the two trailing alignment bytes are appended with a bare skb_put() that does not clear them either. The neighbouring oui and sub_opt regions are explicitly zeroed, so three uninitialised bytes are left in every MRA MRP_Test frame that goes out. Put the sub-option TLV header and the alignment padding in a single skb_put_zero(), which clears both. The AUTO_MGR sub-TLV carries no payload, so the zeroed length field is already the value it should have. Fixes: f7458934b079 ("net: bridge: mrp: Update the Test frames for MRA") Suggested-by: Nikolay Aleksandrov Cc: stable@vger.kernel.org Signed-off-by: Baul Lee Acked-by: Nikolay Aleksandrov Link: https://patch.msgid.link/20260729131941.10254-1-baul.lee@xbow.com Signed-off-by: Jakub Kicinski --- net/bridge/br_mrp.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/net/bridge/br_mrp.c b/net/bridge/br_mrp.c index 179d2470b72488..ef16d07039241a 100644 --- a/net/bridge/br_mrp.c +++ b/net/bridge/br_mrp.c @@ -224,11 +224,9 @@ static struct sk_buff *br_mrp_alloc_test_skb(struct br_mrp *mrp, sub_opt = skb_put(skb, sizeof(*sub_opt)); memset(sub_opt, 0x0, sizeof(*sub_opt)); - sub_tlv = skb_put(skb, sizeof(*sub_tlv)); - sub_tlv->type = BR_MRP_SUB_TLV_HEADER_TEST_AUTO_MGR; - /* 32 bit alligment shall be ensured therefore add 2 bytes */ - skb_put(skb, MRP_OPT_PADDING); + sub_tlv = skb_put_zero(skb, sizeof(*sub_tlv) + MRP_OPT_PADDING); + sub_tlv->type = BR_MRP_SUB_TLV_HEADER_TEST_AUTO_MGR; } br_mrp_skb_tlv(skb, BR_MRP_TLV_HEADER_END, 0x0); From 653d7ddf6cba867777a3d14c4f83ace008c5ad13 Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Tue, 28 Jul 2026 01:23:29 +0800 Subject: [PATCH 134/612] inet: frags: publish queues before arming timer inet_frag_create() arms the fragment queue timer before inserting the queue into the fqdir rhashtable. If the namespace fragment timeout is zero or negative, the timer can run before the queue is published. The timer callback then marks the queue complete, tries to remove a node that is not in the hash table yet, and drops the anticipated hash reference. Creation can subsequently publish the completed queue without restoring that reference, leaving a stale hash node after the caller drops the remaining reference. Publish the queue first and arm the timer while holding the queue lock. This makes timer expiry wait until the queue is visible in the hash table, so inet_frag_kill() can remove the node and balance the hash reference. Fixes: 648700f76b03 ("inet: frags: use rhashtables for reassembly units") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Zhiling Zou Signed-off-by: Ren Wei Link: https://patch.msgid.link/bf66785e7c0c139d7a1900e2f01faeeab344b960.1784948849.git.zhilinz@nebusec.ai Signed-off-by: Jakub Kicinski --- net/ipv4/inet_fragment.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/net/ipv4/inet_fragment.c b/net/ipv4/inet_fragment.c index 848c0f0c2ed918..fc0cb993959f9b 100644 --- a/net/ipv4/inet_fragment.c +++ b/net/ipv4/inet_fragment.c @@ -393,8 +393,8 @@ static struct inet_frag_queue *inet_frag_create(struct fqdir *fqdir, *prev = ERR_PTR(-ENOMEM); return NULL; } - mod_timer(&q->timer, jiffies + fqdir->timeout); + spin_lock_bh(&q->lock); *prev = rhashtable_lookup_get_insert_key(&fqdir->rhashtable, &q->key, &q->node, f->rhash_params); if (*prev) { @@ -402,13 +402,13 @@ static struct inet_frag_queue *inet_frag_create(struct fqdir *fqdir, * we need to cancel what inet_frag_alloc() * anticipated. */ - int refs = 1; - q->flags |= INET_FRAG_COMPLETE; - inet_frag_kill(q, &refs); - inet_frag_putn(q, refs); + spin_unlock_bh(&q->lock); + inet_frag_putn(q, 2); return NULL; } + mod_timer(&q->timer, jiffies + fqdir->timeout); + spin_unlock_bh(&q->lock); return q; } From 47d7f7051253bdc02b1d245d87e38f16d31a74df Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Wed, 29 Jul 2026 05:44:11 -0400 Subject: [PATCH 135/612] net/sched: cls_route: fix fastmap use-after-free on filter The route4 classifier maintains a 16-slot fastmap cache that stores raw struct route4_filter pointers indexed by (id, iif). The reader (route4_classify) populates this cache via route4_set_fastmap() for every classified packet that hits a filter. The writer (route4_delete, route4_change) clears the cache via route4_reset_fastmap() before RCU-deferred kfree of the filter. This creates a UAF race: 1. Reader walks the RCU-protected bucket chain, finds filter f 2. Writer unlinks f, calls route4_reset_fastmap(), then tcf_queue_work() 3. Reader calls route4_set_fastmap() and writes f into the cache *after* the writer's reset, caching a pointer about to be freed 4. After the RCU grace period, kfree(f) executes 5. Next classified packet on the same (id, iif) tuple hits the stale fastmap entry and reads f->res from freed memory Reproduced with an mdelay(100) accelerator in route4_set_fastmap() and a concurrent add/delete stress test (provided by both zdi and Santosh). Both triggered KASAN slab-use-after-free reports in the route4 fastmap paths. Fix: Introduce a per-filter boolean dying flag to suppress stale fastmap republishing by in-flight readers. Fixes: 1109c00547fc ("net: sched: RCU cls_route") Reported-by: zdi-disclosures@trendmicro.com Reported-by: Santosh Kalluri Suggested-by: Paolo Abeni Tested-by: Victor Nogueira Tested-by: Santosh Kalluri Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/20260729094411.46257-1-jhs@mojatatu.com Signed-off-by: Jakub Kicinski --- net/sched/cls_route.c | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/net/sched/cls_route.c b/net/sched/cls_route.c index bd6f945bd38831..eded7aacd3f70e 100644 --- a/net/sched/cls_route.c +++ b/net/sched/cls_route.c @@ -52,6 +52,7 @@ struct route4_filter { struct tcf_result res; struct tcf_exts exts; u32 handle; + bool dying; struct route4_bucket *bkt; struct tcf_proto *tp; struct rcu_work rwork; @@ -66,9 +67,11 @@ static inline int route4_fastmap_hash(u32 id, int iif) static DEFINE_SPINLOCK(fastmap_lock); static void -route4_reset_fastmap(struct route4_head *head) +route4_reset_fastmap(struct route4_head *head, struct route4_filter *f) { spin_lock_bh(&fastmap_lock); + if (f) + f->dying = true; memset(head->fastmap, 0, sizeof(head->fastmap)); spin_unlock_bh(&fastmap_lock); } @@ -81,9 +84,11 @@ route4_set_fastmap(struct route4_head *head, u32 id, int iif, /* fastmap updates must look atomic to aling id, iff, filter */ spin_lock_bh(&fastmap_lock); - head->fastmap[h].id = id; - head->fastmap[h].iif = iif; - head->fastmap[h].filter = f; + if (f == ROUTE4_FAILURE || !f->dying) { + head->fastmap[h].id = id; + head->fastmap[h].iif = iif; + head->fastmap[h].filter = f; + } spin_unlock_bh(&fastmap_lock); } @@ -297,6 +302,13 @@ static void route4_destroy(struct tcf_proto *tp, bool rtnl_held, next = rtnl_dereference(f->next); RCU_INIT_POINTER(b->ht[h2], next); tcf_unbind_filter(tp, &f->res); + /* Mark the filter dying under fastmap_lock so + * any in-flight reader that still holds it + * will skip the republish in route4_set_fastmap(). + */ + spin_lock_bh(&fastmap_lock); + f->dying = true; + spin_unlock_bh(&fastmap_lock); if (tcf_exts_get_net(&f->exts)) route4_queue_work(f); else @@ -307,6 +319,11 @@ static void route4_destroy(struct tcf_proto *tp, bool rtnl_held, kfree_rcu(b, rcu); } } + + /* All filters are unlinked and marked dying, so no in-flight + * reader can republish a stale entry after this reset. + */ + route4_reset_fastmap(head, NULL); kfree_rcu(head, rcu); } @@ -334,11 +351,11 @@ static int route4_delete(struct tcf_proto *tp, void *arg, bool *last, /* unlink it */ RCU_INIT_POINTER(*fp, rtnl_dereference(f->next)); - /* Remove any fastmap lookups that might ref filter - * notice we unlink'd the filter so we can't get it - * back in the fastmap. + /* Clear any fastmap entries that may ref this filter and + * mark it dying so in-flight readers can't republish it + * after the reset. */ - route4_reset_fastmap(head); + route4_reset_fastmap(head, f); /* Delete it */ tcf_unbind_filter(tp, &f->res); @@ -558,7 +575,7 @@ static int route4_change(struct net *net, struct sk_buff *in_skb, } } - route4_reset_fastmap(head); + route4_reset_fastmap(head, fold); *arg = f; if (fold) { tcf_unbind_filter(tp, &fold->res); From f307a7dc32097c11413178fca437a10d20890bc2 Mon Sep 17 00:00:00 2001 From: Jiawen Liu <1298662399@qq.com> Date: Tue, 28 Jul 2026 12:17:10 +0400 Subject: [PATCH 136/612] net: hisilicon: hix5hd2_gmac: remove redundant NAPI delete hix5hd2_dev_remove() calls netif_napi_del() before unregister_netdev(). This is not needed because free_netdev() deletes all NAPI instances attached to the net_device. Remove the redundant call and let the networking core tear down the NAPI instance during unregister_netdev(). The probe error path still keeps its explicit netif_napi_del(), because the device has not been registered there. Fixes: 57c5bc9ad7d7 ("net: hisilicon: add hix5hd2 mac driver") Signed-off-by: Jiawen Liu <1298662399@qq.com> Link: https://patch.msgid.link/tencent_5FFD37A252B4FEA6A80AD25B17C8E904F005@qq.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/hisilicon/hix5hd2_gmac.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/net/ethernet/hisilicon/hix5hd2_gmac.c b/drivers/net/ethernet/hisilicon/hix5hd2_gmac.c index e3e7f2270560cb..02282dc86faf4a 100644 --- a/drivers/net/ethernet/hisilicon/hix5hd2_gmac.c +++ b/drivers/net/ethernet/hisilicon/hix5hd2_gmac.c @@ -1282,7 +1282,6 @@ static void hix5hd2_dev_remove(struct platform_device *pdev) struct net_device *ndev = platform_get_drvdata(pdev); struct hix5hd2_priv *priv = netdev_priv(ndev); - netif_napi_del(&priv->napi); unregister_netdev(ndev); mdiobus_unregister(priv->bus); mdiobus_free(priv->bus); From 1c4dac9bf1d2ac31da63b794bdec697777cbd0fd Mon Sep 17 00:00:00 2001 From: Or Har-Toov Date: Wed, 29 Jul 2026 11:06:00 +0300 Subject: [PATCH 137/612] devlink: fix net namespace reference leak in reload devlink_nl_reload_doit() calls devlink_netns_get(), which returns a net with a held reference. When the requested namespace differs from the current one and the reload action is not DRIVER_REINIT, the function returns -EOPNOTSUPP without releasing the reference. Add the missing put_net() on this error path. Fixes: 2edd92570441 ("devlink: don't allow to change net namespace for FW_ACTIVATE reload action") Signed-off-by: Or Har-Toov Reviewed-by: Jiri Pirko Signed-off-by: Tariq Toukan Reviewed-by: Antoine Tenart Link: https://patch.msgid.link/20260729080600.2427721-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- net/devlink/dev.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/devlink/dev.c b/net/devlink/dev.c index 57b2b8f035436b..fd5633fa88ecd4 100644 --- a/net/devlink/dev.c +++ b/net/devlink/dev.c @@ -578,6 +578,7 @@ int devlink_nl_reload_doit(struct sk_buff *skb, struct genl_info *info) action != DEVLINK_RELOAD_ACTION_DRIVER_REINIT) { NL_SET_ERR_MSG_MOD(info->extack, "Changing namespace is only supported for reinit action"); + put_net(dest_net); return -EOPNOTSUPP; } } From 43e970d961ad3cc0c23e89db255c7fa4a353eff9 Mon Sep 17 00:00:00 2001 From: Chris Mi Date: Wed, 29 Jul 2026 10:16:22 +0300 Subject: [PATCH 138/612] net/mlx5: SF, Handle function changed event When host is powered off, firmware does not send vhca_state event for every probed host SF on the DPU because it may have deployed thousands of SFs to the host. Instead it sends a function changed event. Currently, only VFs handle this event. This commit extends support to SFs. When DPU user deactivates[1] SFs, mlx5 expects vhca_state event and leaves the SF in dangling state[2]. When DPU user deletes[3] SFs, mlx5 also expects vhca_state event and destroys the SF resources[4]. Fix it by changing SF to the right state and freeing SF resources when the function changed event is received. When this event is received, driver checks all SF states. - If state is in_use, change it to active. - If state is teardown_request, change it to allocated. And SF hardware table entry is freed if it is pending for delete. [1] # devlink port function set en3f0c1pf0sf0 state inactive [2] # devlink port function set en3f0c1pf0sf0 state active Error: mlx5_core: SF is inactivated but it is still attached. kernel answers: Device or resource busy [3] # devlink port show pci/0000:03:00.0/229376: type eth netdev en3f0c1pf0sf0 \ flavour pcisf controller 1 pfnum 0 sfnum 0 splittable false function: hw_addr 00:00:00:00:00:00 state active opstate attached \ roce enable trust off max_uc_macs 4096 max_io_eqs 8 # devlink port del en3f0c1pf0sf0 [4] # devlink port add pci/0000:03:00.0 flavour pcisf pfnum 0 sfnum 0 \ controller 1 Error: mlx5_core: SF already exist. Choose different sfnum. kernel answers: File exists Fixes: 6a3273217469 ("net/mlx5: SF, Port function state change support") Signed-off-by: Chris Mi Reviewed-by: Shay Drori Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260729071622.2423270-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../mellanox/mlx5/core/eswitch_offloads.c | 10 +++++-- .../ethernet/mellanox/mlx5/core/sf/devlink.c | 29 +++++++++++++++++++ .../mlx5/core/sf/diag/sf_tracepoint.h | 8 +++++ .../ethernet/mellanox/mlx5/core/sf/hw_table.c | 22 ++++++++++++++ .../net/ethernet/mellanox/mlx5/core/sf/sf.h | 12 ++++++++ 5 files changed, 78 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c b/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c index 907ee83a722d2f..247e5d85ec3671 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c @@ -3986,7 +3986,7 @@ static void esw_offloads_steering_cleanup(struct mlx5_eswitch *esw) mutex_destroy(&esw->fdb_table.offloads.vports.lock); } -static void esw_vfs_changed_event_handler(struct mlx5_eswitch *esw) +static void esw_changed_event_handler(struct mlx5_eswitch *esw) { struct mlx5_esw_pf_info host_pf_info; u16 new_num_vfs; @@ -3999,6 +3999,11 @@ static void esw_vfs_changed_event_handler(struct mlx5_eswitch *esw) host_pf_info = mlx5_esw_get_host_pf_info(esw->dev, out); new_num_vfs = host_pf_info.num_of_vfs; + if (host_pf_info.pf_disabled) { + mlx5_sf_table_esw_changed_event_handler(esw->dev); + mlx5_sf_hw_table_esw_changed_event_handler(esw->dev); + } + if (new_num_vfs == esw->esw_funcs.num_vfs || host_pf_info.pf_disabled) goto free; @@ -4091,8 +4096,7 @@ int mlx5_esw_funcs_changed_handler(struct notifier_block *nb, esw_funcs = mlx5_nb_cof(nb, struct mlx5_esw_functions, nb); esw = container_of(esw_funcs, struct mlx5_eswitch, esw_funcs); - ret = mlx5_esw_add_work(esw, esw_vfs_changed_event_handler, - GFP_ATOMIC); + ret = mlx5_esw_add_work(esw, esw_changed_event_handler, GFP_ATOMIC); if (ret) return NOTIFY_DONE; diff --git a/drivers/net/ethernet/mellanox/mlx5/core/sf/devlink.c b/drivers/net/ethernet/mellanox/mlx5/core/sf/devlink.c index b6cecbcc392de3..4564d460416bdb 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/sf/devlink.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/sf/devlink.c @@ -561,3 +561,32 @@ bool mlx5_sf_table_empty(const struct mlx5_core_dev *dev) return xa_empty(&table->function_ids); } + +void mlx5_sf_table_esw_changed_event_handler(struct mlx5_core_dev *dev) +{ + struct mlx5_sf_table *table = dev->priv.sf_table; + unsigned long index; + struct mlx5_sf *sf; + + trace_mlx5_sf_host_pf_disabled(dev); + + if (!table) + return; + + mutex_lock(&table->sf_state_lock); + xa_for_each(&table->function_ids, index, sf) { + if (!sf->controller) + continue; + + if (sf->hw_state == MLX5_VHCA_STATE_IN_USE) + sf->hw_state = MLX5_VHCA_STATE_ACTIVE; + else if (sf->hw_state == MLX5_VHCA_STATE_TEARDOWN_REQUEST) + sf->hw_state = MLX5_VHCA_STATE_ALLOCATED; + else + continue; + trace_mlx5_sf_update_state(table->dev, sf->port_index, + sf->controller, sf->hw_fn_id, + sf->hw_state); + } + mutex_unlock(&table->sf_state_lock); +} diff --git a/drivers/net/ethernet/mellanox/mlx5/core/sf/diag/sf_tracepoint.h b/drivers/net/ethernet/mellanox/mlx5/core/sf/diag/sf_tracepoint.h index 302ce00da5a99e..9dc88a7e30ca58 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/sf/diag/sf_tracepoint.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/sf/diag/sf_tracepoint.h @@ -11,6 +11,14 @@ #include #include "sf/vhca_event.h" +TRACE_EVENT(mlx5_sf_host_pf_disabled, + TP_PROTO(const struct mlx5_core_dev *dev), + TP_ARGS(dev), + TP_STRUCT__entry(__string(devname, dev_name(dev->device))), + TP_fast_assign(__assign_str(devname);), + TP_printk("(%s)\n", __get_str(devname)) +); + TRACE_EVENT(mlx5_sf_add, TP_PROTO(const struct mlx5_core_dev *dev, unsigned int port_index, diff --git a/drivers/net/ethernet/mellanox/mlx5/core/sf/hw_table.c b/drivers/net/ethernet/mellanox/mlx5/core/sf/hw_table.c index 0bc9146a359817..95a8b1e64ba46b 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/sf/hw_table.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/sf/hw_table.c @@ -459,3 +459,25 @@ bool mlx5_sf_hw_table_supported(const struct mlx5_core_dev *dev) { return !!dev->priv.sf_hw_table; } + +void mlx5_sf_hw_table_esw_changed_event_handler(struct mlx5_core_dev *dev) +{ + struct mlx5_sf_hw_table *table; + struct mlx5_sf_hwc_table *hwc; + int i; + + table = dev->priv.sf_hw_table; + if (!table) + return; + + mutex_lock(&table->table_lock); + hwc = &table->hwc[MLX5_SF_HWC_EXT_HOST]; + for (i = 0; i < hwc->max_fn; i++) { + struct mlx5_sf_hw *sf_hw; + + sf_hw = &hwc->sfs[i]; + if (sf_hw->allocated && sf_hw->pending_delete) + mlx5_sf_hw_table_hwc_sf_free(dev, hwc, i); + } + mutex_unlock(&table->table_lock); +} diff --git a/drivers/net/ethernet/mellanox/mlx5/core/sf/sf.h b/drivers/net/ethernet/mellanox/mlx5/core/sf/sf.h index d8a934a0e968f5..ed784682b9b141 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/sf/sf.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/sf/sf.h @@ -15,12 +15,14 @@ void mlx5_sf_hw_table_cleanup(struct mlx5_core_dev *dev); int mlx5_sf_hw_notifier_init(struct mlx5_core_dev *dev); void mlx5_sf_hw_notifier_cleanup(struct mlx5_core_dev *dev); void mlx5_sf_hw_table_destroy(struct mlx5_core_dev *dev); +void mlx5_sf_hw_table_esw_changed_event_handler(struct mlx5_core_dev *dev); int mlx5_sf_notifiers_init(struct mlx5_core_dev *dev); int mlx5_sf_table_init(struct mlx5_core_dev *dev); void mlx5_sf_notifiers_cleanup(struct mlx5_core_dev *dev); void mlx5_sf_table_cleanup(struct mlx5_core_dev *dev); bool mlx5_sf_table_empty(const struct mlx5_core_dev *dev); +void mlx5_sf_table_esw_changed_event_handler(struct mlx5_core_dev *dev); int mlx5_devlink_sf_port_new(struct devlink *devlink, const struct devlink_port_new_attrs *add_attr, @@ -60,6 +62,11 @@ static inline void mlx5_sf_hw_table_destroy(struct mlx5_core_dev *dev) { } +static inline void +mlx5_sf_hw_table_esw_changed_event_handler(struct mlx5_core_dev *dev) +{ +} + static inline int mlx5_sf_notifiers_init(struct mlx5_core_dev *dev) { return 0; @@ -83,6 +90,11 @@ static inline bool mlx5_sf_table_empty(const struct mlx5_core_dev *dev) return true; } +static inline void +mlx5_sf_table_esw_changed_event_handler(struct mlx5_core_dev *dev) +{ +} + #endif #endif From af39eb111ce6b5eba9c08513b62c4868eb7e7fd5 Mon Sep 17 00:00:00 2001 From: Michael Guralnik Date: Wed, 29 Jul 2026 11:04:02 +0300 Subject: [PATCH 139/612] net/mlx5: fw_tracer, return NULL on create error Tracer creation can fail by returning either NULL or ERR_PTR. The return value is stored without a check on the device, and users treat ERR_PTR and NULL the same way. This also causes a crash in the core dump logic, which is missing the ERR_PTR check and ends up dereferencing it, as shown in the trace below. Switch tracer creation to return NULL on failure only, so callers only need a single NULL check. Internal error: Oops: 0000000096000006 [#1] SMP Modules linked in: mlx5_ib ib_uverbs ib_core ipv6 mlx5_core CPU: 1 UID: 0 PID: 12 Comm: kworker/u16:0 Not tainted 6.19.7 #1 PREEMPT(none) Workqueue: mlx5_health0001:01:00.0 mlx5_fw_reporter_err_work [mlx5_core] pstate: a3400009 (NzCv daif +PAN -UAO +TCO +DIT -SSBS BTYPE=--) pc : mlx5_fw_tracer_trigger_core_dump_general+0x58/0xe0 [mlx5_core] lr : mlx5_fw_tracer_trigger_core_dump_general+0x40/0xe0 [mlx5_core] sp : ffff800081cf3c40 x29: ffff800081cf3c90 x28: 0000000000000000 x27: 0000000000000000 x26: ffff000080018828 x25: 0000000000000000 x24: ffff000080304a05 x23: ffff800081cf3d80 x22: ffff0000847e01a0 x21: 0000000000000000 x20: ffff0000847e01a0 x19: ffffffffffffffa1 x18: ffff80008310bbf0 x17: ffff800080119650 x16: ffff80008010df54 x15: ffff80008010d4ac x14: ffff800079c202e4 x13: ffff80008002fe60 x12: ffff800080119650 x11: ffff80008010df54 x10: ffff80008010d4ac x9 : ffff800079c203d8 x8 : ffff800081cf3c88 x7 : 0000000000000000 x6 : 0000000000000000 x5 : 0000000000000000 x4 : 0000000000000008 x3 : 0000000000000030 x2 : 0000000000000008 x1 : 0000000000000000 x0 : 00000000c5c4000e Call trace: mlx5_fw_tracer_trigger_core_dump_general+0x58/0xe0 [mlx5_core] (P) mlx5_fw_reporter_dump+0x30/0x2e0 [mlx5_core] devlink_health_do_dump+0x9c/0x160 devlink_health_report+0x1c0/0x288 mlx5_fw_reporter_err_work+0xac/0xc0 [mlx5_core] process_one_work+0x15c/0x3d8 worker_thread+0x18c/0x320 kthread+0x148/0x228 ret_from_fork+0x10/0x20 Code: b9400000 5ac00800 7a401800 540003ca (3940a260) ---[ end trace 0000000000000000 ]--- Kernel panic - not syncing: Oops: Fatal exception SMP: stopping secondary CPUs Kernel Offset: disabled CPU features: 0x000000,00078031,75fce5a1,35fffe67 Memory Limit: none ---[ end Kernel panic - not syncing: Oops: Fatal exception ]--- Fixes: fd1483fe1f9f ("net/mlx5: Add support for FW reporter dump") Signed-off-by: Michael Guralnik Reviewed-by: Shay Drori Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260729080402.2427184-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../ethernet/mellanox/mlx5/core/diag/fw_tracer.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/diag/fw_tracer.c b/drivers/net/ethernet/mellanox/mlx5/core/diag/fw_tracer.c index adcc73e2a5b38b..1493d8106c1a40 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/diag/fw_tracer.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/diag/fw_tracer.c @@ -1025,13 +1025,11 @@ struct mlx5_fw_tracer *mlx5_fw_tracer_create(struct mlx5_core_dev *dev) tracer = kvzalloc_obj(*tracer); if (!tracer) - return ERR_PTR(-ENOMEM); + return NULL; tracer->work_queue = create_singlethread_workqueue("mlx5_fw_tracer"); - if (!tracer->work_queue) { - err = -ENOMEM; + if (!tracer->work_queue) goto free_tracer; - } tracer->dev = dev; @@ -1073,7 +1071,7 @@ struct mlx5_fw_tracer *mlx5_fw_tracer_create(struct mlx5_core_dev *dev) destroy_workqueue(tracer->work_queue); free_tracer: kvfree(tracer); - return ERR_PTR(err); + return NULL; } static int fw_tracer_event(struct notifier_block *nb, unsigned long action, void *data); @@ -1084,7 +1082,7 @@ int mlx5_fw_tracer_init(struct mlx5_fw_tracer *tracer) struct mlx5_core_dev *dev; int err; - if (IS_ERR_OR_NULL(tracer)) + if (!tracer) return 0; if (!tracer->str_db.loaded) @@ -1134,7 +1132,7 @@ int mlx5_fw_tracer_init(struct mlx5_fw_tracer *tracer) /* Stop tracer + Cleanup HW resources */ void mlx5_fw_tracer_cleanup(struct mlx5_fw_tracer *tracer) { - if (IS_ERR_OR_NULL(tracer)) + if (!tracer) return; mutex_lock(&tracer->state_lock); @@ -1163,7 +1161,7 @@ void mlx5_fw_tracer_cleanup(struct mlx5_fw_tracer *tracer) /* Free software resources (Buffers, etc ..) */ void mlx5_fw_tracer_destroy(struct mlx5_fw_tracer *tracer) { - if (IS_ERR_OR_NULL(tracer)) + if (!tracer) return; mlx5_core_dbg(tracer->dev, "FWTracer: Destroy\n"); @@ -1215,7 +1213,7 @@ int mlx5_fw_tracer_reload(struct mlx5_fw_tracer *tracer) struct mlx5_core_dev *dev; int err; - if (IS_ERR_OR_NULL(tracer)) + if (!tracer) return 0; dev = tracer->dev; From 4a19f7ab5972ef608b31ae921419bc3e04b3f8ad Mon Sep 17 00:00:00 2001 From: Lizhi Hou Date: Fri, 31 Jul 2026 11:59:55 -0700 Subject: [PATCH 140/612] accel/amdxdna: Fix locally exploitable BUG_ON in amdxdna_insert_pages() In amdxdna_insert_pages(), vm_flags_mod() sets VM_MIXEDMAP and clears VM_PFNMAP. If an unprivileged userspace process mmaps a non-imported GEM object and then calls madvise(MADV_DONTNEED), the PTEs will be successfully cleared because VM_MIXEDMAP allows this (unlike VM_PFNMAP). When userspace subsequently accesses the memory, drm_gem_shmem_fault() handles the page fault and attempts to map the backing shmem page via vmf_insert_pfn() which calls vmf_insert_pfn_prot(). Because the backing shmem page is normal system memory (pfn_valid(pfn) is true) and the VMA now has VM_MIXEDMAP set, won't this predictably trigger the explicit assertion BUG_ON((vma->vm_flags & VM_MIXEDMAP) && pfn_valid(pfn)) Fix by removing the vm_flags_mod() call and replacing the vm_insert_pages() pre-population with the handle_mm_fault() loop that was already used for the import (dma-buf) path. Fixes: e486147c912f ("accel/amdxdna: Add BO import and export") Reviewed-by: Max Zhen Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20260731185955.3449311-1-lizhi.hou@amd.com --- drivers/accel/amdxdna/amdxdna_gem.c | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/drivers/accel/amdxdna/amdxdna_gem.c b/drivers/accel/amdxdna/amdxdna_gem.c index aec69d5f463e62..1c63eff0a4a855 100644 --- a/drivers/accel/amdxdna/amdxdna_gem.c +++ b/drivers/accel/amdxdna/amdxdna_gem.c @@ -467,25 +467,17 @@ static int amdxdna_insert_pages(struct amdxdna_gem_obj *abo, XDNA_ERR(xdna, "Failed shmem mmap %d", ret); return ret; } - - /* The buffer is based on memory pages. Fix the flag. */ - vm_flags_mod(vma, VM_MIXEDMAP, VM_PFNMAP); - ret = vm_insert_pages(vma, vma->vm_start, abo->base.pages, - &num_pages); + } else { + vma->vm_private_data = NULL; + vma->vm_ops = NULL; + ret = dma_buf_mmap(abo->dma_buf, vma, 0); if (ret) { - XDNA_ERR(xdna, "Failed insert pages %d", ret); - amdxdna_mark_mapp_invalid(abo, vma); + XDNA_ERR(xdna, "Failed to mmap dma buf %d", ret); + return ret; } - return 0; - } - - vma->vm_private_data = NULL; - vma->vm_ops = NULL; - ret = dma_buf_mmap(abo->dma_buf, vma, 0); - if (ret) { - XDNA_ERR(xdna, "Failed to mmap dma buf %d", ret); - return ret; + /* Drop the reference drm_gem_mmap_obj() acquired.*/ + drm_gem_object_put(to_gobj(abo)); } do { @@ -502,9 +494,6 @@ static int amdxdna_insert_pages(struct amdxdna_gem_obj *abo, offset += PAGE_SIZE; } while (--num_pages); - /* Drop the reference drm_gem_mmap_obj() acquired.*/ - drm_gem_object_put(to_gobj(abo)); - return 0; } From a76624733730e541e4955fdecf506af2f6b20558 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Sun, 19 Jul 2026 23:22:07 +0800 Subject: [PATCH 141/612] bpf, sockmap: Fix sk_redir use-after-free in send verdict sk_psock_msg_verdict() takes a socket reference for psock->sk_redir. tcp_bpf_send_verdict() copies that pointer while holding the source socket lock, but does not take a reference for the local copy before dropping the lock around tcp_bpf_sendmsg_redir(). When apply_bytes keeps the cached verdict active, another sendmsg() on the same source socket can consume the remaining bytes and release the cached reference while the first thread still holds only the raw local pointer: CPU 0 CPU 1 sk_redir = psock->sk_redir apply_bytes remains nonzero release_sock(sk) lock_sock(sk) apply_bytes reaches zero psock->sk_redir = NULL release_sock(sk) tcp_bpf_sendmsg_redir(sk_redir) sock_put(sk_redir) tcp_bpf_sendmsg_redir(sk_redir) The final sock_put() can free sk_redir before CPU 0 dereferences it. KASAN reported: BUG: KASAN: slab-use-after-free in tcp_bpf_sendmsg_redir+0xf39/0x1020 Read of size 8 at addr ffff888108537090 by task poc/87 Call Trace: tcp_bpf_sendmsg_redir+0xf39/0x1020 tcp_bpf_sendmsg+0x977/0x1a50 __sys_sendto+0x32c/0x3a0 __x64_sys_sendto+0xdb/0x1b0 Allocated by task 85: sk_prot_alloc+0x56/0x210 sk_clone+0x6f/0x14b0 inet_csk_clone_lock+0x24/0x740 tcp_create_openreq_child+0x25/0x2710 tcp_v4_syn_recv_sock+0x10a/0xe00 Freed by task 0: __kasan_slab_free+0x43/0x70 slab_free_after_rcu_debug+0xa6/0x1e0 rcu_core+0x50a/0x1850 Last potentially related work creation: __sk_destruct+0x3da/0x540 sk_psock_destroy+0x81e/0xab0 process_one_work+0x63a/0x1070 Take a temporary socket reference while the source socket lock still protects psock->sk_redir, and drop it after tcp_bpf_sendmsg_redir() returns. This keeps each unlocked use independent of cached-verdict ownership. Fixes: 604326b41a6f ("bpf, sockmap: convert to generic sk_msg interface") Signed-off-by: Chengfeng Ye Reviewed-by: John Fastabend Reviewed-by: Emil Tsalapatis Cc: stable@vger.kernel.org Link: https://lore.kernel.org/bpf/20260719152207.2892156-1-nicoyip.dev@gmail.com Signed-off-by: Kumar Kartikeya Dwivedi --- net/ipv4/tcp_bpf.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/ipv4/tcp_bpf.c b/net/ipv4/tcp_bpf.c index a30475afb6f899..2e234d155b5e61 100644 --- a/net/ipv4/tcp_bpf.c +++ b/net/ipv4/tcp_bpf.c @@ -469,6 +469,7 @@ static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock, case __SK_REDIRECT: redir_ingress = psock->redir_ingress; sk_redir = psock->sk_redir; + sock_hold(sk_redir); sk_msg_apply_bytes(psock, tosend); if (!psock->apply_bytes) { /* Clean up before releasing the sock lock. */ @@ -489,6 +490,7 @@ static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock, if (eval == __SK_REDIRECT) sock_put(sk_redir); + sock_put(sk_redir); lock_sock(sk); sk_mem_uncharge(sk, sent); From fdeba03fea78407a8c52faa99177c9f7f29f90eb Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Sat, 1 Aug 2026 00:09:21 +0800 Subject: [PATCH 142/612] bpf: Fix netns reference imbalance in conntrack kfuncs The opts argument of the BPF conntrack kfuncs can point to a shared map value. __bpf_nf_ct_lookup() and __bpf_nf_ct_alloc_entry() read opts->netns_id separately when acquiring and releasing the network namespace reference. The reference imbalance can occur as follows: CPU 0 CPU 1 read opts->netns_id (-1) skip get_net_ns_by_id() write opts->netns_id (id) read opts->netns_id (id) put_net(net) /* no matching get */ The reverse transition leaks the reference. Repeating the unmatched put can destroy a live namespace and crash later users. The kernel reported: Oops: general protection fault, probably for non-canonical address KASAN: null-ptr-deref in range [0x00000000000000e8-0x00000000000000ef] RIP: 0010:bpf_prog_test_run_xdp+0x52c/0x1700 Call Trace: __sys_bpf+0x1662/0x50c0 __x64_sys_bpf+0x73/0xb0 do_syscall_64+0xf9/0x540 entry_SYSCALL_64_after_hwframe+0x77/0x7f Kernel panic - not syncing: Fatal exception Snapshot every input field of opts with READ_ONCE() before validating or using it. The netns_id snapshot keeps the namespace get/put pair balanced, while the other snapshots keep the remaining options from changing partway through an invocation. The individual reads can still observe an inconsistent combination during a concurrent update, but each selected field value remains stable for that invocation. Fixes: aed8ee7feb44 ("net: netfilter: Deduplicate code in bpf_{xdp,skb}_ct_lookup") Fixes: d7e79c97c00c ("net: netfilter: Add kfuncs to allocate and insert CT") Signed-off-by: Chengfeng Ye Reviewed-by: Emil Tsalapatis Link: https://lore.kernel.org/bpf/20260731160921.3245840-1-nicoyip.dev@gmail.com Signed-off-by: Kumar Kartikeya Dwivedi --- net/netfilter/nf_conntrack_bpf.c | 72 +++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 24 deletions(-) diff --git a/net/netfilter/nf_conntrack_bpf.c b/net/netfilter/nf_conntrack_bpf.c index f98d1d4b42c3d6..c2df7c9482812d 100644 --- a/net/netfilter/nf_conntrack_bpf.c +++ b/net/netfilter/nf_conntrack_bpf.c @@ -122,42 +122,54 @@ __bpf_nf_ct_alloc_entry(struct net *net, struct bpf_sock_tuple *bpf_tuple, struct nf_conntrack_tuple otuple, rtuple; struct nf_conntrack_zone ct_zone; struct nf_conn *ct; + u8 ct_zone_dir = 0; + u16 ct_zone_id; + s32 netns_id; + u8 l4proto; int err; if (!(opts_len == NF_BPF_CT_OPTS_SZ || opts_len == 12)) return ERR_PTR(-EINVAL); + + netns_id = READ_ONCE(opts->netns_id); + l4proto = READ_ONCE(opts->l4proto); + ct_zone_id = READ_ONCE(opts->ct_zone_id); if (opts_len == NF_BPF_CT_OPTS_SZ) { - if (opts->reserved[0] || opts->reserved[1] || opts->reserved[2]) + ct_zone_dir = READ_ONCE(opts->ct_zone_dir); + if (READ_ONCE(opts->reserved[0]) || + READ_ONCE(opts->reserved[1]) || + READ_ONCE(opts->reserved[2])) return ERR_PTR(-EINVAL); } else { - if (opts->ct_zone_id) + if (ct_zone_id) return ERR_PTR(-EINVAL); } - if (unlikely(opts->netns_id < BPF_F_CURRENT_NETNS)) + if (unlikely(netns_id < BPF_F_CURRENT_NETNS)) return ERR_PTR(-EINVAL); - err = bpf_nf_ct_tuple_parse(bpf_tuple, tuple_len, opts->l4proto, + err = bpf_nf_ct_tuple_parse(bpf_tuple, tuple_len, l4proto, IP_CT_DIR_ORIGINAL, &otuple); if (err < 0) return ERR_PTR(err); - err = bpf_nf_ct_tuple_parse(bpf_tuple, tuple_len, opts->l4proto, + err = bpf_nf_ct_tuple_parse(bpf_tuple, tuple_len, l4proto, IP_CT_DIR_REPLY, &rtuple); if (err < 0) return ERR_PTR(err); - if (opts->netns_id >= 0) { - net = get_net_ns_by_id(net, opts->netns_id); + if (netns_id >= 0) { + net = get_net_ns_by_id(net, netns_id); if (unlikely(!net)) return ERR_PTR(-ENONET); } if (opts_len == NF_BPF_CT_OPTS_SZ) { - if (opts->ct_zone_dir == 0) - opts->ct_zone_dir = NF_CT_DEFAULT_ZONE_DIR; - nf_ct_zone_init(&ct_zone, - opts->ct_zone_id, opts->ct_zone_dir, 0); + if (ct_zone_dir == 0) { + ct_zone_dir = NF_CT_DEFAULT_ZONE_DIR; + opts->ct_zone_dir = ct_zone_dir; + } + nf_ct_zone_init(&ct_zone, ct_zone_id, ct_zone_dir, 0); } else { ct_zone = nf_ct_zone_dflt; } @@ -171,7 +183,7 @@ __bpf_nf_ct_alloc_entry(struct net *net, struct bpf_sock_tuple *bpf_tuple, __nf_ct_set_timeout(ct, timeout * HZ); out: - if (opts->netns_id >= 0) + if (netns_id >= 0) put_net(net); return ct; @@ -186,46 +198,58 @@ static struct nf_conn *__bpf_nf_ct_lookup(struct net *net, struct nf_conntrack_tuple tuple; struct nf_conntrack_zone ct_zone; struct nf_conn *ct; + u8 ct_zone_dir = 0; + u16 ct_zone_id; + s32 netns_id; + u8 l4proto; int err; if (!opts || !bpf_tuple) return ERR_PTR(-EINVAL); if (!(opts_len == NF_BPF_CT_OPTS_SZ || opts_len == 12)) return ERR_PTR(-EINVAL); + + netns_id = READ_ONCE(opts->netns_id); + l4proto = READ_ONCE(opts->l4proto); + ct_zone_id = READ_ONCE(opts->ct_zone_id); if (opts_len == NF_BPF_CT_OPTS_SZ) { - if (opts->reserved[0] || opts->reserved[1] || opts->reserved[2]) + ct_zone_dir = READ_ONCE(opts->ct_zone_dir); + if (READ_ONCE(opts->reserved[0]) || + READ_ONCE(opts->reserved[1]) || + READ_ONCE(opts->reserved[2])) return ERR_PTR(-EINVAL); } else { - if (opts->ct_zone_id) + if (ct_zone_id) return ERR_PTR(-EINVAL); } - if (unlikely(opts->l4proto != IPPROTO_TCP && opts->l4proto != IPPROTO_UDP)) + if (unlikely(l4proto != IPPROTO_TCP && l4proto != IPPROTO_UDP)) return ERR_PTR(-EPROTO); - if (unlikely(opts->netns_id < BPF_F_CURRENT_NETNS)) + if (unlikely(netns_id < BPF_F_CURRENT_NETNS)) return ERR_PTR(-EINVAL); - err = bpf_nf_ct_tuple_parse(bpf_tuple, tuple_len, opts->l4proto, + err = bpf_nf_ct_tuple_parse(bpf_tuple, tuple_len, l4proto, IP_CT_DIR_ORIGINAL, &tuple); if (err < 0) return ERR_PTR(err); - if (opts->netns_id >= 0) { - net = get_net_ns_by_id(net, opts->netns_id); + if (netns_id >= 0) { + net = get_net_ns_by_id(net, netns_id); if (unlikely(!net)) return ERR_PTR(-ENONET); } if (opts_len == NF_BPF_CT_OPTS_SZ) { - if (opts->ct_zone_dir == 0) - opts->ct_zone_dir = NF_CT_DEFAULT_ZONE_DIR; - nf_ct_zone_init(&ct_zone, - opts->ct_zone_id, opts->ct_zone_dir, 0); + if (ct_zone_dir == 0) { + ct_zone_dir = NF_CT_DEFAULT_ZONE_DIR; + opts->ct_zone_dir = ct_zone_dir; + } + nf_ct_zone_init(&ct_zone, ct_zone_id, ct_zone_dir, 0); } else { ct_zone = nf_ct_zone_dflt; } hash = nf_conntrack_find_get(net, &ct_zone, &tuple); - if (opts->netns_id >= 0) + if (netns_id >= 0) put_net(net); if (!hash) return ERR_PTR(-ENOENT); From 6d4514ca9cdf61fec4ec634cf50386f6f7e69748 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Tue, 21 Jul 2026 00:31:48 +0000 Subject: [PATCH 143/612] futex: Prevent robust futex exit race some more A robust futex unlock stores 0 over the whole futex value - wiping FUTEX_WAITERS - and wakes a single waiter. That wakeup is a one-shot notification: the protocol relies on its recipient to either acquire the futex (and eventually unlock while aware of the remaining contention) or re-arm FUTEX_WAITERS before sleeping again. If the woken waiter is killed before it can do either, the kernel must jump in and wake the next task down the line. This is a known complication of the futex protocol with a previous partial fix in commit ca16d5bee598 ("futex: Prevent robust futex exit race"). Unfortunately, that fix is insufficient. If a third task re-acquired the futex through the uncontended fast path in the meantime, the notification is lost: robust exit processing sees that it is owned by another task and does nothing, while the new owner sees no FUTEX_WAITERS when it unlocks and wakes nobody. The remaining waiters sleep forever behind a free futex: A owns the futex, B and C sleep in FUTEX_WAIT uval == A | FUTEX_WAITERS A robust unlock: store 0, FUTEX_WAKE(1) wakes B uval == 0 D fast path acquire: cmpxchg(0 -> D) uval == D, no FUTEX_WAITERS B killed before acting on the wakeup B exit walk, pending op: owner D != B -> no action D unlock: no FUTEX_WAITERS -> no wake C sleeps forever This is clearly a shortcoming in the implementation, which fails to keep the FUTEX_WAITERS bit consistent. Work around this by augmenting the robust list exit processing to also perform the extra wakeup if the futex word is owned by another thread but FUTEX_WAITERS is not set. This does not fix the problem of a non-contended take over/release and free sequence, which has been discussed for years and has been addressed by commit 3ca9595d9fb6 ("futex: Add support for unlocking robust futexes") and subsequent changes, but failed to take the problem described above into account. A more complete solution which is based on the in kernel unlock of contended robust futexes has been discussed in the context of this change and should show up in mainline sooner than later. [ tglx: Amend change log slightly and fixup coding style ] Fixes: ca16d5bee598 ("futex: Prevent robust futex exit race") Signed-off-by: Keno Fischer Signed-off-by: Thomas Gleixner Signed-off-by: Ingo Molnar Assisted-by: ClaudeCode:claude-fable-5 tla+ Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260730194705.38981-1-keno@juliacomputing.com --- kernel/futex/core.c | 85 +++++++++++++++++++++++++++++++-------------- 1 file changed, 58 insertions(+), 27 deletions(-) diff --git a/kernel/futex/core.c b/kernel/futex/core.c index 179b26e9c93416..2650d1e5280317 100644 --- a/kernel/futex/core.c +++ b/kernel/futex/core.c @@ -982,8 +982,11 @@ static int handle_futex_death(u32 __user *uaddr, struct task_struct *curr, return -1; /* - * Special case for regular (non PI) futexes. The unlock path in - * user space has two race scenarios: + * Special case for regular (non PI) futexes. Ordinarily, we do + * not perform any processing here unless the current thread was + * the owner of the futex (by the TID check below). + * + * However, the unlock path has three race scenarios: * * 1. The unlock path releases the user space futex value and * before it can execute the futex() syscall to wake up @@ -992,42 +995,70 @@ static int handle_futex_death(u32 __user *uaddr, struct task_struct *curr, * 2. A woken up waiter is killed before it can acquire the * futex in user space. * - * In the second case, the wake up notification could be generated - * by the unlock path in user space after setting the futex value - * to zero or by the kernel after setting the OWNER_DIED bit below. + * 3. A woken up waiter is killed in user space after another + * thread has acquired the futex, but before it can set + * FUTEX_WAITERS. + * + * Note that, if userspace uses the FUTEX_ROBUST_UNLOCK flag, we + * will not see case 1 here. + * + * In the second and third case, the wake up notification could + * be generated from any of: + * + * i. An ordinary futex wakeup after unlock (with or + * without FUTEX_ROBUST_UNLOCK) + * ii. A robust wakeup from another thread's death + * iii. A previous round through this special case + * + * As a result, the futex world will be in one of four states: + * + * A. The futex word is 0 (unlocked) + * B. The futex word is owned by another thread + * (FUTEX_WAITERS is not set) + * C. The futex word is owned by another thread + * (FUTEX_WAITERS set) + * D. The futex's owner died and OWNER_DIED is set + * (the owner part of the word is 0) * - * In both cases the TID validation below prevents a wakeup of - * potential waiters which can cause these waiters to block - * forever. + * The key issue is that the kernel usually (at least from + * sources ii. and iii. or when so requested by userspace from + * source i.) only ever wakes *one* waiter at a time. If this + * waiter dies before acquiring the futex (or setting the + * FUTEX_WAITERS bit), the kernel *must* still wake the next + * waiter down the line to uphold the futex invariants and + * avoid lost wakeups. Note we do not need to handle state C, + * as it does not matter to us whether *we* successfully set + * the bit or a third thread did so in the meantime. * - * In both cases the following conditions are met: + * Therefore, in these cases we must issue an additional + * futex_wake(). Note however that we *must not* set OWNER_DIED + * here. Our thread is *not* the owner of the futex. * - * 1) task->futex.robust_list->list_op_pending != NULL - * @pending_op == true - * 2) The owner part of user space futex value == 0 + * Thus to summarize, the conditions for needing the additional + * futex_wake() are: + * + * 1) @pending_op == true (the thread has not finished the + * mutex operation) + * 2) The futex word is in one of the states A, B or D * 3) Regular futex: @pi == false * - * If these conditions are met, it is safe to attempt waking up a - * potential waiter without touching the user space futex value and - * trying to set the OWNER_DIED bit. If the futex value is zero, - * the rest of the user space mutex state is consistent, so a woken - * waiter will just take over the uncontended futex. Setting the - * OWNER_DIED bit would create inconsistent state and malfunction - * of the user space owner died handling. Otherwise, the OWNER_DIED - * bit is already set, and the woken waiter is expected to deal with - * this. + * Note in particular that in all of the states A-D the owner + * portion of the futex word differs from our thread's TID + * (unless the actual owner has the same TID in another PID + * namespace, but we cannot currently distinguish that + * scenario), so this can be a special-case wakeup in the bail + * path of the ordinary TID check. */ owner = uval & FUTEX_TID_MASK; - if (pending_op && !pi && !owner) { - futex_wake(uaddr, FLAGS_SIZE_32 | FLAGS_SHARED, NULL, 1, - FUTEX_BITSET_MATCH_ANY); + if (owner != task_pid_vnr(curr)) { + if (pending_op && !pi && (!owner || !(uval & FUTEX_WAITERS))) { + futex_wake(uaddr, FLAGS_SIZE_32 | FLAGS_SHARED, NULL, 1, + FUTEX_BITSET_MATCH_ANY); + } return 0; } - if (owner != task_pid_vnr(curr)) - return 0; - /* * Ok, this dying thread is truly holding a futex * of interest. Set the OWNER_DIED bit atomically From f1a3a9946aab611dd2200c01ff122f64b033dad2 Mon Sep 17 00:00:00 2001 From: Babanpreet Singh Date: Tue, 14 Jul 2026 04:29:10 +0000 Subject: [PATCH 144/612] counter: microchip-tcb-capture: Fix DT channel validation mchp_tc_probe() reads the devicetree "reg" cell - a u32, per the API contract of of_property_read_u32_index() - into a signed int, so the bounds check "channel > 2" fails to reject cell values at or above 0x80000000: reinterpreted as a negative int, they compare below 2 and pass validation. A malformed devicetree can therefore drive a negative channel into the ATMEL_TC_REG() offset arithmetic, making the driver access syscon regmap offsets outside the TC block's register window, and into the "t%d_clk" clock-name formatting, where it truncates clk_name (sized for "t0_clk".."t2_clk"). Declare channel as u32, matching the API contract; the unsigned comparison then rejects everything except channels 0..2. Adjust the format specifier to %u accordingly, which also resolves the W=1 warning that exposed the gap: microchip-tcb-capture.c:520:56: warning: '%d' directive output may be truncated writing between 1 and 11 bytes into a region of size 6 [-Wformat-truncation=] note: directive argument in the range [-2147483648, 2] No behavior change for well-formed devicetrees: channels 0..2 take identical paths before and after. Fixes: 106b104137fd ("counter: Add microchip TCB capture counter") Assisted-by: Claude:claude-fable-5 [gcc W=1] Signed-off-by: Babanpreet Singh Reviewed-by: Joshua Crofts Link: https://lore.kernel.org/r/20260714042910.7-1-bbnpreetsingh@gmail.com Signed-off-by: William Breathitt Gray --- drivers/counter/microchip-tcb-capture.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/counter/microchip-tcb-capture.c b/drivers/counter/microchip-tcb-capture.c index 19d457ae4c3bb3..e53a8390756b7e 100644 --- a/drivers/counter/microchip-tcb-capture.c +++ b/drivers/counter/microchip-tcb-capture.c @@ -483,7 +483,7 @@ static int mchp_tc_probe(struct platform_device *pdev) char clk_name[7]; struct regmap *regmap; struct clk *clk[3]; - int channel; + u32 channel; int ret, i; counter = devm_counter_alloc(&pdev->dev, sizeof(*priv)); @@ -517,7 +517,7 @@ static int mchp_tc_probe(struct platform_device *pdev) priv->channel[i] = channel; - snprintf(clk_name, sizeof(clk_name), "t%d_clk", channel); + snprintf(clk_name, sizeof(clk_name), "t%u_clk", channel); clk[i] = of_clk_get_by_name(np->parent, clk_name); if (IS_ERR(clk[i])) { From 2fd9b4cfcefe30cb506072f78f2cd3b6dc8a29b1 Mon Sep 17 00:00:00 2001 From: Tao Cui Date: Tue, 28 Jul 2026 17:57:07 +0800 Subject: [PATCH 145/612] Docs/admin-guide/cgroup-v2: document io.latency rotational vs non-rotational behavior MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit io.latency is documented only in terms of average latency and the avg_lat stat, which matches rotational devices. On non-rotational devices a group misses its target once enough of the IOs in the window individually exceed it, and io.stat reports missed/total rather than avg_lat/win. Describe both cases: how a miss is detected, note that the avg_lat tuning guidance is rotational-only, and update the io.stat field list (mark avg_lat/win as rotational-only, document missed/total). Acked-by: Michal Koutný Signed-off-by: Tao Cui Signed-off-by: Tejun Heo --- Documentation/admin-guide/cgroup-v2.rst | 50 ++++++++++++++++++------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/Documentation/admin-guide/cgroup-v2.rst b/Documentation/admin-guide/cgroup-v2.rst index 14b8c571c0d142..aed195a71cbf1e 100644 --- a/Documentation/admin-guide/cgroup-v2.rst +++ b/Documentation/admin-guide/cgroup-v2.rst @@ -2239,9 +2239,12 @@ IO Latency ~~~~~~~~~~ This is a cgroup v2 controller for IO workload protection. You provide a group -with a latency target, and if the average latency exceeds that target the -controller will throttle any peers that have a lower latency target than the -protected workload. +with a latency target, and if the group misses its target the controller will +throttle any peers that have a lower latency target than the protected +workload. How a miss is detected depends on the device: on rotational devices +the average latency over the window must exceed the target, while on +non-rotational devices a miss is counted once enough of the IOs in the window +individually exceed the target. The limits are only applied at the peer level in the hierarchy. This means that in the diagram below, only groups A, B, and C will influence each other, and @@ -2258,10 +2261,12 @@ So the ideal way to configure this is to set io.latency in groups A, B, and C. Generally you do not want to set a value lower than the latency your device supports. Experiment to find the value that works best for your workload. Start at higher than the expected latency for your device and, with -blkcg_debug_stats enabled, watch the avg_lat value in io.stat for your -workload group to get an idea of the latency you see during normal operation. -Use the avg_lat value as a basis for your real setting, setting at 10-15% -higher than the value in io.stat. +blkcg_debug_stats enabled, observe io.stat for your workload group to get an +idea of the latency you see during normal operation. On rotational devices, +use the avg_lat value as a basis for your real setting, setting it 10-15% +higher. On non-rotational devices io.stat reports no average latency; set +the target based on your device and use the missed/total fields to verify it +is being met. How IO Latency Throttling Works ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -2303,19 +2308,36 @@ IO Latency Interface Files the blkcg_debug_stats module parameter is enabled (it is disabled by default). + The reported latency fields depend on the device. Rotational devices + report avg_lat and win; non-rotational devices report missed and total + instead. missed and total are live counters for the current window and + may change between reads. + depth This is the current queue depth for the group. avg_lat - This is an exponential moving average with a decay rate of 1/exp - bound by the sampling interval. The decay rate interval can be - calculated by multiplying the win value in io.stat by the - corresponding number of samples based on the win value. + (Rotational devices only.) This is an exponential moving + average with a decay rate of 1/exp bound by the sampling + interval. The decay rate interval can be calculated by + multiplying the win value in io.stat by the corresponding number + of samples based on the win value. win - The sampling window size in milliseconds. This is the minimum - duration of time between evaluation events. Windows only elapse - with IO activity. Idle periods extend the most recent window. + (Rotational devices only.) The sampling window size in + milliseconds. This is the minimum duration of time between + evaluation events. Windows only elapse with IO activity. Idle + periods extend the most recent window. + + missed + (Non-rotational devices only.) The number of IOs in the + current window whose latency exceeded the target. A group is + considered to be missing its target once missed reaches a + certain ratio of total. + + total + (Non-rotational devices only.) The total number of IOs + accounted in the current window. IO Priority ~~~~~~~~~~~ From 4da94744707b27a3ae1197bdd7127da4505dc5b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ADra=20Canal?= Date: Tue, 28 Jul 2026 23:09:22 -0300 Subject: [PATCH 146/612] drm/v3d: Serialize the scheduler timeout handlers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit V3D exposes several independent hardware queues (BIN, RENDER, TFU and CSD) but has only a single, global reset. A timeout on any one queue therefore has to stop, reset and restart the schedulers of every other queue as well. That makes concurrent timeout handlers unsafe. `reset_lock` was never able to make them safe, as a driver-side lock can only cover the driver's &drm_sched_backend_ops.timedout_job callback. The scheduler handles the timed out job and its pending list around that callback, outside of the driver's control, so a global reset triggered by one queue can still interfere with another queue that is in the middle of handling a timeout of its own. Consequently, if a reset happens in the CSD queue while a CL-intensive application is running, the global reset stops and restarts the CL queue's scheduler while that queue is handling a timeout of its own. As drm_sched_stop() and drm_sched_start() subtract and add the credits of every job sitting on the pending list of the scheduler they are called on, and as the CL queue's handler concurrently takes its job off that same list and puts it back, the stop and the start no longer see the same set of jobs. The CL queue is left with more credits in flight than its limit: [ 327.302739] ------------[ cut here ]------------ [ 327.302744] WARNING: CPU: 2 PID: 43 at drivers/gpu/drm/scheduler/sched_main.c:102 drm_sched_run_job_work+0x238/0x4d0 [gpu_sched] [ 327.302884] CPU: 2 UID: 0 PID: 43 Comm: kworker/u16:1 Not tainted 6.18.39-v8-16k+ #3 PREEMPT [ 327.302889] Hardware name: Raspberry Pi 5 Model B Rev 1.0 (DT) [ 327.302893] Workqueue: v3d_bin drm_sched_run_job_work [gpu_sched] [ 327.302984] Call trace: [ 327.302987] drm_sched_run_job_work+0x238/0x4d0 [gpu_sched] (P) [ 327.302997] process_scheduled_works+0x180/0x3d0 [ 327.303010] worker_thread+0x268/0x3e8 [ 327.303016] kthread+0x140/0x250 [ 327.303022] ret_from_fork+0x10/0x20 [ 327.303031] ---[ end trace 0000000000000000 ]--- From that point on, the credit count of the CL queue is broken, causing a complete GPU hang and UI freeze. The DRM scheduler already provides a mechanism to serialize the timeout handlers of different schedulers: an ordered workqueue passed as drm_sched_init()'s @timeout_wq parameter. By default, each scheduler queues its timeout work on the system workqueue, which runs the handlers concurrently. Give all of the queues a shared ordered workqueue instead, as recommended by the DRM scheduler documentation for hardware that has distinct queues but resets globally. Cc: stable@vger.kernel.org # 6.15 Reviewed-by: Iago Toral Quiroga Link: https://patch.msgid.link/20260728-v3d-order-global-reset-v1-1-e47be838158d@igalia.com Signed-off-by: Maíra Canal --- drivers/gpu/drm/v3d/v3d_drv.h | 6 ++++++ drivers/gpu/drm/v3d/v3d_sched.c | 9 ++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/v3d/v3d_drv.h b/drivers/gpu/drm/v3d/v3d_drv.h index 423bcfe331a014..8779a42c65081a 100644 --- a/drivers/gpu/drm/v3d/v3d_drv.h +++ b/drivers/gpu/drm/v3d/v3d_drv.h @@ -182,6 +182,12 @@ struct v3d_dev { */ struct mutex reset_lock; + /* Ordered workqueue shared by every queue's scheduler timeout work. + * V3D reset is global to all queues, so the timeout handlers must not + * run concurrently. + */ + struct workqueue_struct *reset_wq; + /* Lock taken when creating and pushing the GPU scheduler * jobs, to keep the sched-fence seqnos in order. */ diff --git a/drivers/gpu/drm/v3d/v3d_sched.c b/drivers/gpu/drm/v3d/v3d_sched.c index c35f9f70cf8d63..63dd95b828c850 100644 --- a/drivers/gpu/drm/v3d/v3d_sched.c +++ b/drivers/gpu/drm/v3d/v3d_sched.c @@ -837,6 +837,7 @@ v3d_queue_sched_init(struct v3d_dev *v3d, const struct drm_sched_backend_ops *op struct drm_sched_init_args args = { .credit_limit = 1, .timeout = msecs_to_jiffies(500), + .timeout_wq = v3d->reset_wq, .dev = v3d->drm.dev, }; @@ -851,9 +852,13 @@ v3d_sched_init(struct v3d_dev *v3d) { int ret; + v3d->reset_wq = alloc_ordered_workqueue("v3d_reset", 0); + if (!v3d->reset_wq) + return -ENOMEM; + ret = v3d_queue_sched_init(v3d, &v3d_bin_sched_ops, V3D_BIN, "v3d_bin"); if (ret) - return ret; + goto fail; ret = v3d_queue_sched_init(v3d, &v3d_render_sched_ops, V3D_RENDER, "v3d_render"); @@ -896,4 +901,6 @@ v3d_sched_fini(struct v3d_dev *v3d) if (v3d->queue[q].sched.ready) drm_sched_fini(&v3d->queue[q].sched); } + + destroy_workqueue(v3d->reset_wq); } From 3a046db33bb9f28b43a951a7a090db771dc0f8b3 Mon Sep 17 00:00:00 2001 From: Zhefu Zhang Date: Sun, 2 Aug 2026 15:36:54 -0700 Subject: [PATCH 147/612] Input: atkbd - skip deactivate for Xiaomi Book Pro 14's internal keyboard The internal keyboard of the Xiaomi Book Pro 14 does not work unless atkbd skips deactivating it at the end of atkbd_probe(). Using 'i8042.dumbkbd=1' also makes the keyboard work, but then the driver never writes to the keyboard at all, so the Caps Lock LED is lost. The atkbd_deactivate_fixup quirk fixes both without a boot parameter. DMI: XIAOMI Xiaomi Book Pro 14/TM2424, BIOS XMAPT4B0P0909 05/06/2026 Signed-off-by: Zhefu Zhang Reviewed-by: Andrew Zhou Link: https://patch.msgid.link/20260802031559.19701-1-a723356@gmail.com Cc: stable@vger.kernel.org Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/atkbd.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/input/keyboard/atkbd.c b/drivers/input/keyboard/atkbd.c index 8cb4dc6fb16589..6c897282d25ffe 100644 --- a/drivers/input/keyboard/atkbd.c +++ b/drivers/input/keyboard/atkbd.c @@ -1938,6 +1938,14 @@ static const struct dmi_system_id atkbd_dmi_quirk_table[] __initconst = { }, .callback = atkbd_deactivate_fixup, }, + { + /* Xiaomi Book Pro 14 (TM2424) */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "XIAOMI"), + DMI_MATCH(DMI_PRODUCT_NAME, "Xiaomi Book Pro 14"), + }, + .callback = atkbd_deactivate_fixup, + }, { } }; From 410c44b1096789d0c40fbee706520e981dba7bc1 Mon Sep 17 00:00:00 2001 From: Donglin Lyu Date: Sun, 2 Aug 2026 15:37:18 -0700 Subject: [PATCH 148/612] Input: atkbd - skip deactivate for HONOR ZQC-P The internal keyboard on the HONOR ZQC-P (HONOR MagicBook Pro 14 2026) does not work after boot. Using the kernel command line 'i8042.dumbkbd=1' makes the keyboard functional, but the CapsLock LED does not work. Adding the 'atkbd_deactivate_fixup' quirk fixes the keyboard and CapsLock LED natively without requiring boot parameters. DMI: HONOR ZQC-P/ZQC-P-PCB, BIOS 1.09 03/19/2026 Fixes: 9cf6e24c9fbf ("Input: atkbd - do not skip atkbd_deactivate() when skipping ATKBD_CMD_GETID") Signed-off-by: Donglin Lyu Tested-by: Ruslan Shevchenko Link: https://patch.msgid.link/20260801151115.52709-1-donglin_lyu@outlook.com Cc: stable@vger.kernel.org [dtor: keep all HONOR entries together] Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/atkbd.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/drivers/input/keyboard/atkbd.c b/drivers/input/keyboard/atkbd.c index 6c897282d25ffe..4c82e988260e57 100644 --- a/drivers/input/keyboard/atkbd.c +++ b/drivers/input/keyboard/atkbd.c @@ -1919,22 +1919,30 @@ static const struct dmi_system_id atkbd_dmi_quirk_table[] __initconst = { { .matches = { DMI_MATCH(DMI_SYS_VENDOR, "HONOR"), - DMI_MATCH(DMI_PRODUCT_NAME, "FMB-P"), + DMI_MATCH(DMI_PRODUCT_NAME, "BCC-N"), }, .callback = atkbd_deactivate_fixup, }, { - /* Lenovo Yoga Air 14 (83QK) */ .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_NAME, "83QK"), + DMI_MATCH(DMI_SYS_VENDOR, "HONOR"), + DMI_MATCH(DMI_PRODUCT_NAME, "FMB-P"), }, .callback = atkbd_deactivate_fixup, }, { + /* HONOR MagicBook Pro 14 2026 */ .matches = { DMI_MATCH(DMI_SYS_VENDOR, "HONOR"), - DMI_MATCH(DMI_PRODUCT_NAME, "BCC-N"), + DMI_MATCH(DMI_PRODUCT_NAME, "ZQC-P"), + }, + .callback = atkbd_deactivate_fixup, + }, + { + /* Lenovo Yoga Air 14 (83QK) */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "83QK"), }, .callback = atkbd_deactivate_fixup, }, From e5fd3f514e27db1f05fbd72ba615d74941e23c51 Mon Sep 17 00:00:00 2001 From: "Jose Fernandez (Anthropic)" Date: Thu, 30 Jul 2026 22:32:47 +0000 Subject: [PATCH 149/612] bpf: tcp: Fix use-after-free in bpf_iter_tcp_established_batch() reqsk_queue_hash_req() publishes a TCP_NEW_SYN_RECV request_sock onto the ehash chain, drops the bucket lock, and only afterwards sets rsk_refcnt to 3. Lockless readers such as __inet_lookup_established() handle this with refcount_inc_not_zero(), but bpf_iter_tcp_established_batch() uses plain sock_hold() while holding the bucket lock, on the assumption that the lock guarantees sk_refcnt > 0. That assumption does not hold for request_sock: CPU 0 CPU 1 ----- ----- tcp_conn_request() reqsk_queue_hash_req() inet_ehash_insert(req) spin_lock(bucket) __sk_nulls_add_node_rcu(req) // rsk_refcnt == 0 spin_unlock(bucket) bpf_iter_tcp_established_batch() spin_lock(bucket) sock_hold(req) <-- addition on 0 spin_unlock(bucket) refcount_set(&req->rsk_refcnt, 3) // clobbers saturated value which surfaces as: refcount_t: addition on 0; use-after-free. WARNING: lib/refcount.c:25 at refcount_warn_saturate+0x48/0x90, CPU#1 Call Trace: bpf_iter_tcp_established_batch+0x14e/0x170 bpf_iter_tcp_batch+0x53/0x200 bpf_iter_tcp_seq_next+0x27/0x70 bpf_seq_read+0x107/0x410 vfs_read+0xb9/0x380 The iterator's stolen reference is lost when the publishing CPU's refcount_set() overwrites the count, leaving the socket one reference short. When the last legitimate owner drops its reference the reqsk is freed while still reachable, leading to use-after-free. This reproduces in seconds with tcp_syncookies=0, a handful of threads doing connect()/close() to a local listener while others read an iter/tcp link in a tight loop. Use refcount_inc_not_zero() and skip the socket on failure. A skipped socket is still part of the bucket, so keep counting it in expected. The reallocations are sized from expected, and a request sock whose refcount gets published while the lock is held across the last realloc must already have room. A skipped socket is counted in expected but never batched, so end_sk can be short of expected on a batch that is actually complete. Decide completeness by whether the walk left any socket behind instead. The WARN after the locked realloc checks the same, replacing an end_sk == expected check that could not hold on that path since commit cdec67a489d4 ("bpf: tcp: Make sure iter->batch always contains a full bucket snapshot"). If every matching socket in a bucket is mid-init (refcount 0), end_sk stays 0. Advance to the next bucket rather than returning a batch entry that was never filled this round. Fixes: 04c7820b776f ("bpf: tcp: Bpf iter batching and lock_sock") Assisted-by: Claude:unspecified Signed-off-by: Jose Fernandez (Anthropic) Reviewed-by: Kuniyuki Iwashima Link: https://lore.kernel.org/bpf/20260730-bpf-iter-tcp-refcnt-v3-1-754b9c8a6717@linux.dev Signed-off-by: Kumar Kartikeya Dwivedi --- net/ipv4/tcp_ipv4.c | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c index b8887cdd66c571..7f413f509d7dce 100644 --- a/net/ipv4/tcp_ipv4.c +++ b/net/ipv4/tcp_ipv4.c @@ -3078,24 +3078,24 @@ static unsigned int bpf_iter_tcp_established_batch(struct seq_file *seq, { struct bpf_tcp_iter_state *iter = seq->private; struct hlist_nulls_node *node; - unsigned int expected = 1; - struct sock *sk; - - sock_hold(*start_sk); - iter->batch[iter->end_sk++].sk = *start_sk; + struct sock *sk = *start_sk; + unsigned int expected = 0; - sk = sk_nulls_next(*start_sk); *start_sk = NULL; sk_nulls_for_each_from(sk, node) { - if (seq_sk_match(seq, sk)) { - if (iter->end_sk < iter->max_sk) { - sock_hold(sk); - iter->batch[iter->end_sk++].sk = sk; - } else if (!*start_sk) { - /* Remember where we left off. */ - *start_sk = sk; - } - expected++; + if (!seq_sk_match(seq, sk)) + continue; + expected++; + if (iter->end_sk < iter->max_sk) { + /* reqsk_queue_hash_req() inserts with sk_refcnt == 0 + * and refcount_set()s it after the bucket lock drops. + */ + if (unlikely(!refcount_inc_not_zero(&sk->sk_refcnt))) + continue; + iter->batch[iter->end_sk++].sk = sk; + } else if (!*start_sk) { + /* Remember where we left off. */ + *start_sk = sk; } } @@ -3133,12 +3133,13 @@ static struct sock *bpf_iter_tcp_batch(struct seq_file *seq) struct sock *sk; int err; +again: sk = bpf_iter_tcp_resume(seq); if (!sk) return NULL; /* Done */ expected = bpf_iter_fill_batch(seq, &sk); - if (likely(iter->end_sk == expected)) + if (likely(!sk)) goto done; /* Batch size was too small. */ @@ -3157,7 +3158,7 @@ static struct sock *bpf_iter_tcp_batch(struct seq_file *seq) return NULL; /* Done */ expected = bpf_iter_fill_batch(seq, &sk); - if (likely(iter->end_sk == expected)) + if (likely(!sk)) goto done; /* Batch size was still too small. Hold onto the lock while we try @@ -3170,10 +3171,14 @@ static struct sock *bpf_iter_tcp_batch(struct seq_file *seq) return ERR_PTR(err); } - expected = bpf_iter_fill_batch(seq, &sk); - WARN_ON_ONCE(iter->end_sk != expected); + bpf_iter_fill_batch(seq, &sk); + WARN_ON_ONCE(sk); done: bpf_iter_tcp_unlock_bucket(seq); + if (unlikely(!iter->end_sk)) { + ++iter->state.bucket; + goto again; + } return iter->batch[0].sk; } From 296736076b3fd078742651c719555a488624023a Mon Sep 17 00:00:00 2001 From: Richard Davies Date: Sun, 2 Aug 2026 17:53:10 -0700 Subject: [PATCH 150/612] Input: focaltech - fix array out-of-bounds in focaltech_process_rel_packet Make finger2 (and also finger1) unsigned, so that if the finger index in the packet is 0 then subtracting 1 creates an array index which overflows above the existing check for FOC_MAX_FINGERS, as the existing comment says it should, instead of writing to state->fingers[-1]. Fixes: 05be1d079ec0 ("Input: psmouse - support for the FocalTech PS/2 protocol extensions") Signed-off-by: Richard Davies Link: https://patch.msgid.link/20260701190932.14960-1-richard@arachsys.com Cc: stable@vger.kernel.org Signed-off-by: Dmitry Torokhov --- drivers/input/mouse/focaltech.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/input/mouse/focaltech.c b/drivers/input/mouse/focaltech.c index 43f9939b7c63ce..d3ad4af5aa09d7 100644 --- a/drivers/input/mouse/focaltech.c +++ b/drivers/input/mouse/focaltech.c @@ -197,7 +197,7 @@ static void focaltech_process_rel_packet(struct psmouse *psmouse, { struct focaltech_data *priv = psmouse->private; struct focaltech_hw_state *state = &priv->state; - int finger1, finger2; + unsigned int finger1, finger2; state->pressed = packet[0] >> 7; finger1 = ((packet[0] >> 4) & 0x7) - 1; From c422d34a4ad953473b8cc2b232915a4beaf8c3fa Mon Sep 17 00:00:00 2001 From: ChenXiaoSong Date: Sat, 1 Aug 2026 07:55:15 +0000 Subject: [PATCH 151/612] smb/client: show compress mount option Example: 1. mount -t cifs -o compress //localhost/test /mnt 2. mount | grep cifs //localhost/test on /mnt type cifs (..., compress, ...) Signed-off-by: ChenXiaoSong Signed-off-by: Steve French --- fs/smb/client/cifsfs.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c index 1788d93a252246..a1dacc7d8f74b6 100644 --- a/fs/smb/client/cifsfs.c +++ b/fs/smb/client/cifsfs.c @@ -692,6 +692,8 @@ cifs_show_options(struct seq_file *s, struct dentry *root) seq_puts(s, ",seal"); else if (tcon->ses->server->ignore_signature) seq_puts(s, ",signloosely"); + if (cifs_sb->ctx->compress) + seq_puts(s, ",compress"); if (tcon->nocase) seq_puts(s, ",nocase"); if (tcon->nodelete) From 4986410316b1ae0e63c6ce418e4eb196723626e7 Mon Sep 17 00:00:00 2001 From: Shuangpeng Bai Date: Sat, 1 Aug 2026 20:48:09 -0400 Subject: [PATCH 152/612] smb: client: Fix use-after-free in cifs_try_adding_channels() cifs_try_adding_channels() takes a temporary reference to an interface before dropping iface_lock. If cifs_ses_add_channel() fails, it drops that reference and then increments iface->weight_fulfilled. A concurrent interface list refresh can remove the list reference while channel creation is in progress. In that case, the failure-path kref_put() releases the last reference and frees iface. Updating weight_fulfilled afterward then accesses freed memory. Increment weight_fulfilled before dropping the temporary reference, keeping iface alive for the final access. Fixes: 6aac002bcfd5 ("cifs: failure to add channel on iface should bump up weight") Cc: stable@vger.kernel.org Signed-off-by: Shuangpeng Bai Signed-off-by: Steve French --- fs/smb/client/sess.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/smb/client/sess.c b/fs/smb/client/sess.c index de2012cc9cf3ed..7cf7dd104f7c31 100644 --- a/fs/smb/client/sess.c +++ b/fs/smb/client/sess.c @@ -233,9 +233,9 @@ int cifs_try_adding_channels(struct cifs_ses *ses) cifs_dbg(VFS, "failed to open extra channel on iface:%pIS rc=%d\n", &iface->sockaddr, rc); - kref_put(&iface->refcount, release_iface); /* failure to add chan should increase weight */ iface->weight_fulfilled++; + kref_put(&iface->refcount, release_iface); continue; } From 4c77b45fa23015d41d7401f684bc22ca585b41fe Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Thu, 30 Jul 2026 22:06:25 -0700 Subject: [PATCH 153/612] fixp-arith: convert comments to kernel-doc format Insert a hyphen ('-') in 2 places to prevent kernel-doc warnings: Warning: include/linux/fixp-arith.h:42 This comment starts with '/**', but isn't a kernel-doc comment. * __fixp_sin32() returns the sin of an angle in degrees Warning: include/linux/fixp-arith.h:66 This comment starts with '/**', but isn't a kernel-doc comment. * fixp_sin32() returns the sin of an angle in degrees Signed-off-by: Randy Dunlap Link: https://patch.msgid.link/20260731050625.455556-1-rdunlap@infradead.org Signed-off-by: Dmitry Torokhov --- include/linux/fixp-arith.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/fixp-arith.h b/include/linux/fixp-arith.h index e485fb0c1201e0..4d4f4a7d47498a 100644 --- a/include/linux/fixp-arith.h +++ b/include/linux/fixp-arith.h @@ -40,7 +40,7 @@ static const s32 sin_table[] = { }; /** - * __fixp_sin32() returns the sin of an angle in degrees + * __fixp_sin32() - returns the sin of an angle in degrees * * @degrees: angle, in degrees, from 0 to 360. * @@ -64,7 +64,7 @@ static inline s32 __fixp_sin32(int degrees) } /** - * fixp_sin32() returns the sin of an angle in degrees + * fixp_sin32() - returns the sin of an angle in degrees * * @degrees: angle, in degrees. The angle can be positive or negative * From b2d5a81dae385333f9734910277fbf94c78bd17f Mon Sep 17 00:00:00 2001 From: Lin Jiapeng Date: Tue, 28 Jul 2026 15:19:10 +0800 Subject: [PATCH 154/612] xfs: fix exchange-range reflink flag clearing issue with INO1_WRITTEN When exchanging two full-file ranges, xmi_can_exchange_reflink_flags() can move the reflink inode flag from the file that currently has it to the other file, as long as exactly one side is marked. This assumes that the file contents, and therefore all shared extents, are exchanged. That assumption is not true when XFS_EXCHMAPS_INO1_WRITTEN is set. xfs_exchmaps_can_skip_mapping() can skip hole and unwritten mappings from file1, so an exchange can complete without moving every mapping that the earlier flag-swap decision accounted for. In that case the post-operation cleanup can clear the reflink flag from an inode that still owns shared written extents. Later writes then take the non-reflink write path and may update blocks that should still have been protected by CoW, which shows up as data corruption between reflink-related files. Fix this by disabling the reflink flag exchange whenever XFS_EXCHMAPS_INO1_WRITTEN is requested. The contents exchange can still proceed; the conservative outcome is that both inodes keep the reflink flag. The regular reflink flag cleanup path can drop the extra flag later once the inode no longer has shared extents. Reported-by: Lin Jiapeng (TencentOS Red Team) Fixes: 966ceafc7a43 ("xfs: create deferred log items for file mapping exchanges") Cc: stable@vger.kernel.org # v6.10 Reviewed-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Lin Jiapeng Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_exchmaps.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/fs/xfs/libxfs/xfs_exchmaps.c b/fs/xfs/libxfs/xfs_exchmaps.c index dcd0bd0b13b4a5..3efed37cb98a8f 100644 --- a/fs/xfs/libxfs/xfs_exchmaps.c +++ b/fs/xfs/libxfs/xfs_exchmaps.c @@ -959,6 +959,16 @@ xmi_can_exchange_reflink_flags( { struct xfs_mount *mp = req->ip1->i_mount; + /* + * The INO1_WRITTEN optimization can skip exchanging hole and + * unwritten mappings, which means we cannot guarantee that all + * shared extents actually moved to the other file. Clearing the + * reflink flag of an inode that still holds shared extents breaks + * the CoW write path, so refuse to exchange the flags in that case. + */ + if (req->flags & XFS_EXCHMAPS_INO1_WRITTEN) + return false; + if (hweight32(reflink_state) != 1) return false; if (req->startoff1 != 0 || req->startoff2 != 0) From fb082235c67551d526704e07a4efb82ac3a9ba48 Mon Sep 17 00:00:00 2001 From: Andrey Albershteyn Date: Wed, 22 Jul 2026 11:17:59 +0200 Subject: [PATCH 155/612] xfs: fix inverted clearance of inode junk flags sashiko.dev noticed that these checks clear all the valid flags instead of invalid. This probably was never hit as it only executed on invalid flag presence. Fixes: 2d295fe65776 ("xfs: repair inode records") Signed-off-by: Andrey Albershteyn Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/inode_repair.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/xfs/scrub/inode_repair.c b/fs/xfs/scrub/inode_repair.c index b88427a4460cba..8bc508336aa5f4 100644 --- a/fs/xfs/scrub/inode_repair.c +++ b/fs/xfs/scrub/inode_repair.c @@ -1797,7 +1797,7 @@ xrep_inode_flags( /* Clear junk flags */ if (sc->ip->i_diflags & ~XFS_DIFLAG_ANY) - sc->ip->i_diflags &= ~XFS_DIFLAG_ANY; + sc->ip->i_diflags &= XFS_DIFLAG_ANY; /* NEWRTBM only applies to realtime bitmaps */ if (I_INO(sc->ip) == sc->mp->m_sb.sb_rbmino) @@ -1828,7 +1828,7 @@ xrep_inode_flags( /* Clear junk flags. */ if (sc->ip->i_diflags2 & ~XFS_DIFLAG2_ANY) - sc->ip->i_diflags2 &= ~XFS_DIFLAG2_ANY; + sc->ip->i_diflags2 &= XFS_DIFLAG2_ANY; /* No reflink flag unless we support it and it's a file. */ if (!xfs_has_reflink(sc->mp) || !S_ISREG(mode)) From 97efed1091a50e4dbf31307015138b43a972d2e4 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 26 Jul 2026 22:22:59 -0700 Subject: [PATCH 156/612] xfs: only check mergeability of bnobt records In the cntbt (free space by block count) btree, records are not supposed to be in startblock order. Hence the mergeability check is pointless. Remove it, since it does nothing, as LOLLM points out. Cc: stable@vger.kernel.org # v6.4 Fixes: d5784ae82778d9 ("xfs: flag free space btree records that could be merged") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/alloc.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fs/xfs/scrub/alloc.c b/fs/xfs/scrub/alloc.c index 48edaa2cb1e079..c666be69f16481 100644 --- a/fs/xfs/scrub/alloc.c +++ b/fs/xfs/scrub/alloc.c @@ -136,7 +136,7 @@ xchk_allocbt_rec( const union xfs_btree_rec *rec) { struct xfs_alloc_rec_incore irec; - struct xchk_alloc *ca = bs->private; + struct xchk_alloc *ca = bs->private; xfs_alloc_btrec_to_irec(rec, &irec); if (xfs_alloc_check_irec(to_perag(bs->cur->bc_group), &irec) != NULL) { @@ -144,7 +144,8 @@ xchk_allocbt_rec( return 0; } - xchk_allocbt_mergeable(bs, ca, &irec); + if (bs->sc->sm->sm_type == XFS_SCRUB_TYPE_BNOBT) + xchk_allocbt_mergeable(bs, ca, &irec); xchk_allocbt_xref(bs->sc, &irec); return 0; From 5fc643fb86599e29b38e7b2c2680b4b15bf8f772 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 26 Jul 2026 22:23:15 -0700 Subject: [PATCH 157/612] xfs: don't double-lock when deleting a self-referential directory LOLLM notices that the dirtree scrubber can detect a directory that refers to itself. In this case, it's not correct for the directory tree repair code to try to iolock/ilock both sc->ip and dp, because they're the same inode. Fix this by detecting that corner case and handling it appropriately. Cc: stable@vger.kernel.org # v6.10 Fixes: 3f31406aef493b ("xfs: fix corruptions in the directory tree") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/dirtree_repair.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/fs/xfs/scrub/dirtree_repair.c b/fs/xfs/scrub/dirtree_repair.c index 1c0d7ea4a5be6a..bbf6acf6fd400c 100644 --- a/fs/xfs/scrub/dirtree_repair.c +++ b/fs/xfs/scrub/dirtree_repair.c @@ -349,6 +349,8 @@ xrep_dirtree_unlink_iolock( ASSERT(sc->ilock_flags & XFS_IOLOCK_EXCL); + if (sc->ip == dp) + return 0; if (xfs_ilock_nowait(dp, XFS_IOLOCK_EXCL)) return 0; @@ -400,8 +402,18 @@ xrep_dirtree_unlink( * directory code can handle a reservationless update. */ resblks = xfs_remove_space_res(mp, step->name_len); - error = xfs_trans_alloc_dir(dp, &M_RES(mp)->tr_remove, sc->ip, - &resblks, &sc->tp, &dontcare); + if (sc->ip == dp) { +again: + error = xfs_trans_alloc_inode(dp, &M_RES(mp)->tr_remove, + resblks, 0, false, &sc->tp); + if ((error == -ENOSPC || error == -EDQUOT) && resblks > 0) { + resblks = 0; + goto again; + } + } else { + error = xfs_trans_alloc_dir(dp, &M_RES(mp)->tr_remove, sc->ip, + &resblks, &sc->tp, &dontcare); + } if (error) goto out_iolock; @@ -489,9 +501,11 @@ xrep_dirtree_unlink( xchk_trans_cancel(sc); out_ilock: xfs_iunlock(sc->ip, XFS_ILOCK_EXCL); - xfs_iunlock(dp, XFS_ILOCK_EXCL); + if (dp != sc->ip) + xfs_iunlock(dp, XFS_ILOCK_EXCL); out_iolock: - xfs_iunlock(dp, XFS_IOLOCK_EXCL); + if (dp != sc->ip) + xfs_iunlock(dp, XFS_IOLOCK_EXCL); return error; } From 8af9cd79cdf6ee96ec610d707db22244fa21eb40 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 26 Jul 2026 22:23:31 -0700 Subject: [PATCH 158/612] xfs: don't return EFSCORRUPTED when scrubbing corrupt parent pointers LOLLM noticed that scrub sets the CORRUPT flag when xfs_parent_from_attr thinks it's been given a corrupt parent pointer. This eliminates the potential to repair the filesystem because that error code is bubbled up the call stack. Fix this by collapsing them all to ECANCELED in xchk_parent_pptr, which doesn't have that trait. Cc: stable@vger.kernel.org # v6.10 Fixes: 0d29a20fbdba89 ("xfs: scrub parent pointers") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/parent.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/parent.c b/fs/xfs/scrub/parent.c index a8c4807e1d9464..99b60773b7158d 100644 --- a/fs/xfs/scrub/parent.c +++ b/fs/xfs/scrub/parent.c @@ -485,7 +485,7 @@ xchk_parent_scan_attr( valuelen, &parent_ino, NULL); if (error) { xchk_fblock_set_corrupt(sc, XFS_ATTR_FORK, 0); - return error; + return -ECANCELED; } /* No self-referential parent pointers. */ From 5ee37132ea81abd36213b31a72140660c2aac54b Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 26 Jul 2026 22:23:46 -0700 Subject: [PATCH 159/612] xfs: don't zap the attr fork on repair when there are queued pptr updates LOLLM noticed that xrep_xattr_rebuild_tree doesn't check for queued parent pointer updates when it decides that it's going to zap the attr fork. This is obviously incorrect, so fix that. We hold the IOLOCK and the ILOCK of sc->ip at that point in time, so we can't race with any /new/ operations. Cc: stable@vger.kernel.org # v6.10 Fixes: e5d7ce0364d8ee ("xfs: replay unlocked parent pointer updates that accrue during xattr repair") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/attr_repair.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/xfs/scrub/attr_repair.c b/fs/xfs/scrub/attr_repair.c index be627ab655ad16..6e6af142f1fb8e 100644 --- a/fs/xfs/scrub/attr_repair.c +++ b/fs/xfs/scrub/attr_repair.c @@ -1427,7 +1427,8 @@ xrep_xattr_rebuild_tree( * If we didn't find any attributes to salvage, repair the file by * zapping its attr fork. */ - if (rx->attrs_found == 0) { + if (rx->attrs_found == 0 && + (!xfs_has_parent(sc->mp) || xfarray_length(rx->pptr_recs) == 0)) { xfs_trans_ijoin(sc->tp, sc->ip, 0); error = xrep_xattr_reset_fork(sc); if (error) From b1a296fc2241f724ef8f14da6a4efa800d444dac Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 26 Jul 2026 22:24:02 -0700 Subject: [PATCH 160/612] xfs: nlink scrub must take IOLOCK before determining ILOCK state In xchk_nlinks_ilock_dir, take the IOLOCK before accessing internal inode state to figure out if we need to take ILOCK shared or exclusive. That way we can't race with directory updates. LOLLM pointed out that the code was initially correct w.r.t. the IOLOCK, but then I broke it. Cc: stable@vger.kernel.org # v6.18 Fixes: f477af0cfa0487 ("xfs: fix locking in xchk_nlinks_collect_dir") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/nlinks.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/fs/xfs/scrub/nlinks.c b/fs/xfs/scrub/nlinks.c index 355ab6de23eaba..bcedb8c3e4e608 100644 --- a/fs/xfs/scrub/nlinks.c +++ b/fs/xfs/scrub/nlinks.c @@ -382,6 +382,12 @@ xchk_nlinks_ilock_dir( { uint lock_mode = XFS_ILOCK_SHARED; + /* + * Take the IOLOCK so that other threads cannot start a directory + * update while we're scanning. + */ + xfs_ilock(ip, XFS_IOLOCK_SHARED); + /* * We're going to scan the directory entries, so we must be ready to * pull the data fork mappings into memory if they aren't already. @@ -397,13 +403,8 @@ xchk_nlinks_ilock_dir( xfs_need_iread_extents(&ip->i_af)) lock_mode = XFS_ILOCK_EXCL; - /* - * Take the IOLOCK so that other threads cannot start a directory - * update while we're scanning. - */ - lock_mode |= XFS_IOLOCK_SHARED; xfs_ilock(ip, lock_mode); - return lock_mode; + return lock_mode | XFS_IOLOCK_SHARED; } /* Walk a directory to bump the observed link counts of the children. */ From 0c88e10d12de9ca7cbed1467bb1b52310101bff8 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 26 Jul 2026 22:24:17 -0700 Subject: [PATCH 161/612] xfs: avoid UAF on sc->tempip in xrep_tempfile_create LOLLM noticed a potential UAF if the tempfile creation code fails after it set sc->tempip. Fix that. Cc: stable@vger.kernel.org # v6.10 Fixes: 84c14ee39dd388 ("xfs: create temporary files and directories for online repair") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/tempfile.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/xfs/scrub/tempfile.c b/fs/xfs/scrub/tempfile.c index e0c630f888cf6e..98820003b9298f 100644 --- a/fs/xfs/scrub/tempfile.c +++ b/fs/xfs/scrub/tempfile.c @@ -174,6 +174,7 @@ xrep_tempfile_create( xfs_iunlock(sc->tempip, XFS_ILOCK_EXCL); xfs_finish_inode_setup(sc->tempip); xchk_irele(sc, sc->tempip); + sc->tempip = NULL; } out_release_dquots: xfs_qm_dqrele(udqp); From 7cdafd8f10ebdf745ba6046b9fa67490c343a17f Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 26 Jul 2026 22:24:33 -0700 Subject: [PATCH 162/612] xfs: hoist per-bucket unlinked list check to helper In the next patch we're going to make this loop more exciting, so hoist the code to a helper function to reduce clutter in the resulting code. Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/agheader.c | 64 +++++++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/fs/xfs/scrub/agheader.c b/fs/xfs/scrub/agheader.c index 62ed5eaf08fbc1..cecf034ef989c7 100644 --- a/fs/xfs/scrub/agheader.c +++ b/fs/xfs/scrub/agheader.c @@ -932,6 +932,42 @@ xchk_agi_xref( /* scrub teardown will take care of sc->sa for us */ } +/* + * Walk the incore unlinked list for a particular AGI bucket to construct + * the unlinked inode bitmap for later reconstruction of the unlinked list. + * Returns 1 if we should keep checking, or 0 to stop checking. + */ +static int +xchk_iunlink_bucket( + struct xfs_scrub *sc, + unsigned int bucket, + xfs_agino_t agino) +{ + while (agino != NULLAGINO) { + struct xfs_inode *ip; + + if (agino % XFS_AGI_UNLINKED_BUCKETS != bucket) { + xchk_block_set_corrupt(sc, sc->sa.agi_bp); + return 0; + } + + ip = xfs_iunlink_lookup(sc->sa.pag, agino); + if (!ip) { + xchk_block_set_corrupt(sc, sc->sa.agi_bp); + return 0; + } + + if (!xfs_inode_on_unlinked_list(ip)) { + xchk_block_set_corrupt(sc, sc->sa.agi_bp); + return 0; + } + + agino = ip->i_next_unlinked; + } + + return 1; +} + /* * Check the unlinked buckets for links to bad inodes. We hold the AGI, so * there cannot be any threads updating unlinked list pointers in this AG. @@ -942,30 +978,14 @@ xchk_iunlink( struct xfs_agi *agi) { unsigned int i; - struct xfs_inode *ip; for (i = 0; i < XFS_AGI_UNLINKED_BUCKETS; i++) { - xfs_agino_t agino = be32_to_cpu(agi->agi_unlinked[i]); - - while (agino != NULLAGINO) { - if (agino % XFS_AGI_UNLINKED_BUCKETS != i) { - xchk_block_set_corrupt(sc, sc->sa.agi_bp); - return; - } - - ip = xfs_iunlink_lookup(sc->sa.pag, agino); - if (!ip) { - xchk_block_set_corrupt(sc, sc->sa.agi_bp); - return; - } - - if (!xfs_inode_on_unlinked_list(ip)) { - xchk_block_set_corrupt(sc, sc->sa.agi_bp); - return; - } - - agino = ip->i_next_unlinked; - } + int ret; + + ret = xchk_iunlink_bucket(sc, i, + be32_to_cpu(agi->agi_unlinked[i])); + if (ret < 1) + return; } } From 527eaaefddb6ec5c83a06c9a1559960dd6361753 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 26 Jul 2026 22:24:48 -0700 Subject: [PATCH 163/612] xfs: don't livelock in scrub on a circular unlinked list LOLLM points out that online fsck can livelock if an unlinked inode list contains a loop. Use a bitmap to detect cycles. Cc: stable@vger.kernel.org # v4.15 Fixes: a12890aebb8959 ("xfs: scrub the AGI") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/agheader.c | 46 +++++++++++++++++++++++++++------- fs/xfs/scrub/agheader_repair.c | 17 +++++++++++-- 2 files changed, 52 insertions(+), 11 deletions(-) diff --git a/fs/xfs/scrub/agheader.c b/fs/xfs/scrub/agheader.c index cecf034ef989c7..1fa66aa68e169f 100644 --- a/fs/xfs/scrub/agheader.c +++ b/fs/xfs/scrub/agheader.c @@ -18,6 +18,8 @@ #include "xfs_inode.h" #include "scrub/scrub.h" #include "scrub/common.h" +#include "scrub/bitmap.h" +#include "scrub/agino_bitmap.h" int xchk_setup_agheader( @@ -935,7 +937,8 @@ xchk_agi_xref( /* * Walk the incore unlinked list for a particular AGI bucket to construct * the unlinked inode bitmap for later reconstruction of the unlinked list. - * Returns 1 if we should keep checking, or 0 to stop checking. + * Returns 1 if we should keep checking, 0 to stop checking, or a negative + * errno. */ static int xchk_iunlink_bucket( @@ -943,36 +946,57 @@ xchk_iunlink_bucket( unsigned int bucket, xfs_agino_t agino) { + struct xagino_bitmap seen; + int ret; + + xagino_bitmap_init(&seen); + while (agino != NULLAGINO) { struct xfs_inode *ip; + unsigned int len = 1; if (agino % XFS_AGI_UNLINKED_BUCKETS != bucket) { xchk_block_set_corrupt(sc, sc->sa.agi_bp); - return 0; + goto bad; + } + + if (xagino_bitmap_test(&seen, agino, &len)) { + xchk_block_set_corrupt(sc, sc->sa.agi_bp); + goto bad; } ip = xfs_iunlink_lookup(sc->sa.pag, agino); if (!ip) { xchk_block_set_corrupt(sc, sc->sa.agi_bp); - return 0; + goto bad; } if (!xfs_inode_on_unlinked_list(ip)) { xchk_block_set_corrupt(sc, sc->sa.agi_bp); - return 0; + goto bad; } + ret = xagino_bitmap_set(&seen, agino, 1); + if (ret) + goto out_bitmap; + agino = ip->i_next_unlinked; } - - return 1; + ret = 1; + +out_bitmap: + xagino_bitmap_destroy(&seen); + return ret; +bad: + ret = 0; + goto out_bitmap; } /* * Check the unlinked buckets for links to bad inodes. We hold the AGI, so * there cannot be any threads updating unlinked list pointers in this AG. */ -STATIC void +STATIC int xchk_iunlink( struct xfs_scrub *sc, struct xfs_agi *agi) @@ -985,8 +1009,10 @@ xchk_iunlink( ret = xchk_iunlink_bucket(sc, i, be32_to_cpu(agi->agi_unlinked[i])); if (ret < 1) - return; + return ret; } + + return 0; } /* Scrub the AGI. */ @@ -1073,7 +1099,9 @@ xchk_agi( if (pag->pagi_freecount != be32_to_cpu(agi->agi_freecount)) xchk_block_set_corrupt(sc, sc->sa.agi_bp); - xchk_iunlink(sc, agi); + error = xchk_iunlink(sc, agi); + if (error) + goto out; xchk_agi_xref(sc); out: diff --git a/fs/xfs/scrub/agheader_repair.c b/fs/xfs/scrub/agheader_repair.c index 2554494847ff1b..13074d5e319cc0 100644 --- a/fs/xfs/scrub/agheader_repair.c +++ b/fs/xfs/scrub/agheader_repair.c @@ -1080,18 +1080,22 @@ xrep_iunlink_walk_ondisk_bucket( struct xrep_agi *ragi, unsigned int bucket) { + struct xagino_bitmap seen; struct xfs_scrub *sc = ragi->sc; struct xfs_agi *agi = sc->sa.agi_bp->b_addr; xfs_agino_t prev_agino = NULLAGINO; xfs_agino_t next_agino; int error = 0; + xagino_bitmap_init(&seen); + next_agino = be32_to_cpu(agi->agi_unlinked[bucket]); while (next_agino != NULLAGINO) { xfs_agino_t agino = next_agino; + unsigned int len = 1; if (xchk_should_terminate(ragi->sc, &error)) - return error; + goto out_bitmap; trace_xrep_iunlink_walk_ondisk_bucket(sc->sa.pag, bucket, prev_agino, agino); @@ -1099,15 +1103,24 @@ xrep_iunlink_walk_ondisk_bucket( if (bucket != agino % XFS_AGI_UNLINKED_BUCKETS) break; + if (xagino_bitmap_test(&seen, agino, &len)) + break; + next_agino = xrep_iunlink_next(sc, agino); if (!next_agino) next_agino = xrep_iunlink_reload_next(ragi, prev_agino, agino); + error = xagino_bitmap_set(&seen, agino, 1); + if (error) + goto out_bitmap; + prev_agino = agino; } - return 0; +out_bitmap: + xagino_bitmap_destroy(&seen); + return error; } /* Decide if this is an unlinked inode in this AG. */ From 1e96a00e0d3a00be6e4f368b2f18e2d345f813ce Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 26 Jul 2026 22:25:04 -0700 Subject: [PATCH 164/612] xfs: don't walk off the end of a null sc->sa.agi_bp in AGI repair LOLLM noticed a longstanding bug where xrep_iunlink_walk_ondisk_bucket tries to walk ragi->sc->sa.agi_bp to rebuild the unlinked inode lists. Unfortunately, it's possible for agi_bp to be null if the buffer verifier fails, so we have to use ragi->agi_bp (which skips verifier checks) instead. Cc: stable@vger.kernel.org # v6.10 Fixes: ab97f4b1c03075 ("xfs: repair AGI unlinked inode bucket lists") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/agheader_repair.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/agheader_repair.c b/fs/xfs/scrub/agheader_repair.c index 13074d5e319cc0..39468b8fa9863d 100644 --- a/fs/xfs/scrub/agheader_repair.c +++ b/fs/xfs/scrub/agheader_repair.c @@ -1082,7 +1082,7 @@ xrep_iunlink_walk_ondisk_bucket( { struct xagino_bitmap seen; struct xfs_scrub *sc = ragi->sc; - struct xfs_agi *agi = sc->sa.agi_bp->b_addr; + struct xfs_agi *agi = ragi->agi_bp->b_addr; xfs_agino_t prev_agino = NULLAGINO; xfs_agino_t next_agino; int error = 0; From 6b9cd540138a06660a843a519facc147060acbef Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 26 Jul 2026 22:25:19 -0700 Subject: [PATCH 165/612] xfs: load next_agino from the correct xfarray in xrep_iunlink_relink_prev LOLLM notices that xrep_iunlink_relink_prev has the comment "set the forward pointer..." but then loads the value from the xfarray that stores pointers to the previous inode in the unlinked list. That's wrong, so fix the variable access. Cc: stable@vger.kernel.org # v6.10 Fixes: ab97f4b1c03075 ("xfs: repair AGI unlinked inode bucket lists") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/agheader_repair.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/agheader_repair.c b/fs/xfs/scrub/agheader_repair.c index 39468b8fa9863d..5204eab09c6450 100644 --- a/fs/xfs/scrub/agheader_repair.c +++ b/fs/xfs/scrub/agheader_repair.c @@ -1619,7 +1619,7 @@ xrep_iunlink_relink_prev( want_rele = true; /* Set the forward pointer since this just came off disk. */ - error = xfarray_load(ragi->iunlink_prev, agino, &next_agino); + error = xfarray_load(ragi->iunlink_next, agino, &next_agino); if (error) goto out_rele; From 5644fab990fc72406dddc91cbb8304659d77f3f1 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 26 Jul 2026 22:25:35 -0700 Subject: [PATCH 166/612] xfs: pass runtime errors from xrep_iunlink_mark_ondisk_rec up to callers LOLLM points out that the only error that xrep_iunlink_mark_ondisk_rec returns is ENOMEM, but we ignore that, and can end up writing a garbage AGI based on incomplete information. We shouldn't do that, though here we must be screen out EFSCORRUPTED/EFSBASDCRC because we haven't checked the inobt yet. Cc: stable@vger.kernel.org # v6.10 Fixes: ab97f4b1c03075 ("xfs: repair AGI unlinked inode bucket lists") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/agheader_repair.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/fs/xfs/scrub/agheader_repair.c b/fs/xfs/scrub/agheader_repair.c index 5204eab09c6450..7f19554843038d 100644 --- a/fs/xfs/scrub/agheader_repair.c +++ b/fs/xfs/scrub/agheader_repair.c @@ -1309,7 +1309,7 @@ xrep_iunlink_mark_ondisk_rec( * iunlink_bmp. We haven't checked the inobt yet, so we don't error out if * the btree is corrupt. */ -STATIC void +STATIC int xrep_iunlink_mark_ondisk( struct xrep_agi *ragi) { @@ -1321,6 +1321,14 @@ xrep_iunlink_mark_ondisk( cur = xfs_inobt_init_cursor(sc->sa.pag, sc->tp, agi_bp); error = xfs_btree_query_all(cur, xrep_iunlink_mark_ondisk_rec, ragi); xfs_btree_del_cursor(cur, error); + + /* + * Don't proceed if we couldn't set a bit in the bitmap. All other + * errors we ignore because we haven't actually checked the inobt yet. + */ + if (error == -ENOMEM) + return -ENOMEM; + return 0; } /* @@ -1508,7 +1516,9 @@ xrep_iunlink_rebuild_buckets( * If there are ondisk inodes that are unlinked and are not been loaded * into cache, record them in iunlink_bmp. */ - xrep_iunlink_mark_ondisk(ragi); + error = xrep_iunlink_mark_ondisk(ragi); + if (error) + return error; /* * Walk each iunlink bucket to (re)construct as much of the incore list From 2daf3ed5d059dec79c123aec42eb8d28e0c016d4 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 26 Jul 2026 22:25:50 -0700 Subject: [PATCH 167/612] xfs: check xfarray iteration errors when committing unlinked inode lists LOLLM noticed that we neglect to check for xfarray_iter itself returning errors when writing a new AGI. Fix that. Cc: stable@vger.kernel.org # v6.10 Fixes: ab97f4b1c03075 ("xfs: repair AGI unlinked inode bucket lists") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/agheader_repair.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/xfs/scrub/agheader_repair.c b/fs/xfs/scrub/agheader_repair.c index 7f19554843038d..8514d2d7e3cea3 100644 --- a/fs/xfs/scrub/agheader_repair.c +++ b/fs/xfs/scrub/agheader_repair.c @@ -1675,6 +1675,8 @@ xrep_iunlink_commit( if (error) return error; } + if (error < 0) + return error; /* Fix all the back links */ idx = XFARRAY_CURSOR_INIT; @@ -1683,6 +1685,8 @@ xrep_iunlink_commit( if (error) return error; } + if (error < 0) + return error; /* Copy the staged iunlink buckets to the new AGI. */ for (i = 0; i < XFS_AGI_UNLINKED_BUCKETS; i++) { From 68ab37650ce5195b4f4f8466444a36a78207840c Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 26 Jul 2026 22:26:06 -0700 Subject: [PATCH 168/612] xfs: fix allocated inodes that show up in the unlinked list If an allocated inode shows up in the unlinked list, we need to get it completely off the list. Set the corrected next/prev pointers such that the inode will not look like it should be on an unlinked list at all. Cc: stable@vger.kernel.org # v6.10 Fixes: ab97f4b1c03075 ("xfs: repair AGI unlinked inode bucket lists") Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/agheader_repair.c | 63 ++++++++++++++++++++++++++++++---- fs/xfs/scrub/trace.h | 1 + 2 files changed, 57 insertions(+), 7 deletions(-) diff --git a/fs/xfs/scrub/agheader_repair.c b/fs/xfs/scrub/agheader_repair.c index 8514d2d7e3cea3..41de5cf87352b4 100644 --- a/fs/xfs/scrub/agheader_repair.c +++ b/fs/xfs/scrub/agheader_repair.c @@ -979,6 +979,13 @@ xrep_agi_calc_from_btrees( return error; } +/* + * Magic value that means "not unlinked" because xfarrays don't support storing + * totally zeroed elements. There can't be a cluster that starts in daddr 0 so + * there can't be an inode #1 either. + */ +#define LINKED_AGINO (0x1) + /* * Record a forwards unlinked chain pointer from agino -> next_agino in our * staging information. @@ -1362,6 +1369,35 @@ xrep_iunlink_resolve_bucket( break; } + if (VFS_I(ip)->i_nlink != 0) { + /* + * Inode is linked somewhere! Blow out both unlinked + * list pointers, advance the list, and pretend we + * didn't see this inode. Clear it from iunlink_bmp + * because it's linked. + */ + trace_xrep_iunlink_resolve_allocated(sc->sa.pag, + bucket, prev_agino, next_agino); + + error = xrep_iunlink_store_next(ragi, next_agino, + NULLAGINO); + if (error) + return error; + + error = xrep_iunlink_store_prev(ragi, next_agino, + LINKED_AGINO); + if (error) + return error; + + error = xagino_bitmap_clear(&ragi->iunlink_bmp, + next_agino, 1); + if (error) + return error; + + next_agino = ip->i_next_unlinked; + continue; + } + if (next_agino % XFS_AGI_UNLINKED_BUCKETS != bucket) { /* * Inode is in the wrong bucket. Advance the list, @@ -1540,6 +1576,24 @@ xrep_iunlink_rebuild_buckets( xrep_iunlink_add_lost_inodes, ragi); } +static inline void +set_inode_prev_unlinked( + struct xfs_inode *ip, + xfs_agino_t prev_agino) +{ + /* + * Magic value that means "not unlinked" because xfarrays don't support + * storing totally zeroed elements. + */ + if (prev_agino == LINKED_AGINO) + prev_agino = 0; + + if (ip->i_prev_unlinked != prev_agino) { + trace_xrep_iunlink_relink_prev(ip, prev_agino); + ip->i_prev_unlinked = prev_agino; + } +} + /* Update i_next_iunlinked for the inode @agino. */ STATIC int xrep_iunlink_relink_next( @@ -1573,8 +1627,7 @@ xrep_iunlink_relink_next( if (error) goto out_rele; - trace_xrep_iunlink_relink_prev(ip, prev_agino); - ip->i_prev_unlinked = prev_agino; + set_inode_prev_unlinked(ip, prev_agino); } /* Update the forward pointer. */ @@ -1641,11 +1694,7 @@ xrep_iunlink_relink_prev( ip->i_next_unlinked = next_agino; } - /* Update the backward pointer. */ - if (ip->i_prev_unlinked != prev_agino) { - trace_xrep_iunlink_relink_prev(ip, prev_agino); - ip->i_prev_unlinked = prev_agino; - } + set_inode_prev_unlinked(ip, prev_agino); out_rele: /* diff --git a/fs/xfs/scrub/trace.h b/fs/xfs/scrub/trace.h index d5d39d82749e5c..00fbe1b9c2354f 100644 --- a/fs/xfs/scrub/trace.h +++ b/fs/xfs/scrub/trace.h @@ -3542,6 +3542,7 @@ DEFINE_REPAIR_IUNLINK_RESOLVE_EVENT(xrep_iunlink_resolve_uncached); DEFINE_REPAIR_IUNLINK_RESOLVE_EVENT(xrep_iunlink_resolve_wronglist); DEFINE_REPAIR_IUNLINK_RESOLVE_EVENT(xrep_iunlink_resolve_nolist); DEFINE_REPAIR_IUNLINK_RESOLVE_EVENT(xrep_iunlink_resolve_ok); +DEFINE_REPAIR_IUNLINK_RESOLVE_EVENT(xrep_iunlink_resolve_allocated); TRACE_EVENT(xrep_iunlink_relink_next, TP_PROTO(struct xfs_inode *ip, xfs_agino_t next_agino), From 6d67c6b99f1fc07c64b97fcbc974c6f1ada7f622 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 26 Jul 2026 22:26:21 -0700 Subject: [PATCH 169/612] xfs: fix another iunlink infinite loop bug in online fsck xrep_iunlink_resolve_bucket is supposed to reconstruct as much of the incore prev and next unlinked list pointers based on what it finds on disk and in memory before we move on to relinking the truly lost inodes back into the unlinked list. However, it's still vulnerable to infinite loops that come in via the next_unlinked pointers. Fix this problem by remembering which inodes we've already seen and checking new agino pointers against that. If a bit is already set, either this is a loop or the inode has nonzero link count. We'll deal with the second case in a subsequent patch. Cc: stable@vger.kernel.org # v6.10 Fixes: ab97f4b1c03075 ("xfs: repair AGI unlinked inode bucket lists") Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/agheader_repair.c | 37 +++++++++++++++++++++++++--------- fs/xfs/scrub/trace.h | 1 + 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/fs/xfs/scrub/agheader_repair.c b/fs/xfs/scrub/agheader_repair.c index 41de5cf87352b4..65b9a8befce99b 100644 --- a/fs/xfs/scrub/agheader_repair.c +++ b/fs/xfs/scrub/agheader_repair.c @@ -1348,15 +1348,32 @@ xrep_iunlink_resolve_bucket( struct xrep_agi *ragi, unsigned int bucket) { + struct xagino_bitmap seen; struct xfs_scrub *sc = ragi->sc; struct xfs_inode *ip; xfs_agino_t prev_agino = NULLAGINO; xfs_agino_t next_agino = ragi->iunlink_heads[bucket]; int error = 0; + xagino_bitmap_init(&seen); + while (next_agino != NULLAGINO) { + unsigned int len = 1; + if (xchk_should_terminate(ragi->sc, &error)) - return error; + goto out_bitmap; + + /* Inode already seen? We're stuck in a loop */ + if (xagino_bitmap_test(&seen, next_agino, &len)) { + trace_xrep_iunlink_resolve_infinite_loop(sc->sa.pag, + bucket, prev_agino, next_agino); + next_agino = NULLAGINO; + break; + } + + error = xagino_bitmap_set(&seen, next_agino, 1); + if (error) + goto out_bitmap; /* Find the next inode in the chain. */ ip = xfs_iunlink_lookup(sc->sa.pag, next_agino); @@ -1382,17 +1399,17 @@ xrep_iunlink_resolve_bucket( error = xrep_iunlink_store_next(ragi, next_agino, NULLAGINO); if (error) - return error; + goto out_bitmap; error = xrep_iunlink_store_prev(ragi, next_agino, LINKED_AGINO); if (error) - return error; + goto out_bitmap; error = xagino_bitmap_clear(&ragi->iunlink_bmp, next_agino, 1); if (error) - return error; + goto out_bitmap; next_agino = ip->i_next_unlinked; continue; @@ -1433,20 +1450,20 @@ xrep_iunlink_resolve_bucket( */ error = xagino_bitmap_clear(&ragi->iunlink_bmp, next_agino, 1); if (error) - return error; + goto out_bitmap; /* Remember the previous inode's next pointer. */ if (prev_agino != NULLAGINO) { error = xrep_iunlink_store_next(ragi, prev_agino, next_agino); if (error) - return error; + goto out_bitmap; } /* Remember this inode's previous pointer. */ error = xrep_iunlink_store_prev(ragi, next_agino, prev_agino); if (error) - return error; + goto out_bitmap; /* Advance the list and remember this inode. */ prev_agino = next_agino; @@ -1457,10 +1474,12 @@ xrep_iunlink_resolve_bucket( if (prev_agino != NULLAGINO) { error = xrep_iunlink_store_next(ragi, prev_agino, next_agino); if (error) - return error; + goto out_bitmap; } - return 0; +out_bitmap: + xagino_bitmap_destroy(&seen); + return error; } /* Reinsert this unlinked inode into the head of the staged bucket list. */ diff --git a/fs/xfs/scrub/trace.h b/fs/xfs/scrub/trace.h index 00fbe1b9c2354f..14aa0ec1f09e4a 100644 --- a/fs/xfs/scrub/trace.h +++ b/fs/xfs/scrub/trace.h @@ -3538,6 +3538,7 @@ DEFINE_EVENT(xrep_iunlink_resolve_class, name, \ TP_PROTO(const struct xfs_perag *pag, unsigned int bucket, \ xfs_agino_t prev_agino, xfs_agino_t next_agino), \ TP_ARGS(pag, bucket, prev_agino, next_agino)) +DEFINE_REPAIR_IUNLINK_RESOLVE_EVENT(xrep_iunlink_resolve_infinite_loop); DEFINE_REPAIR_IUNLINK_RESOLVE_EVENT(xrep_iunlink_resolve_uncached); DEFINE_REPAIR_IUNLINK_RESOLVE_EVENT(xrep_iunlink_resolve_wronglist); DEFINE_REPAIR_IUNLINK_RESOLVE_EVENT(xrep_iunlink_resolve_nolist); From af146cb7ff8ff5c54162f35c238f3ff1d5ad110f Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 26 Jul 2026 22:26:37 -0700 Subject: [PATCH 170/612] xfs: set the prev pointer when reinserting an inode on the unlinked list If we find a rogue free inode and decide to reinsert it into the unlinked list, we need to set the prev pointer to NULLAGINO so that the incore list gets updated. Cc: stable@vger.kernel.org # v6.10 Fixes: ab97f4b1c03075 ("xfs: repair AGI unlinked inode bucket lists") Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/agheader_repair.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/xfs/scrub/agheader_repair.c b/fs/xfs/scrub/agheader_repair.c index 65b9a8befce99b..4f1f235349c5a3 100644 --- a/fs/xfs/scrub/agheader_repair.c +++ b/fs/xfs/scrub/agheader_repair.c @@ -1504,6 +1504,10 @@ xrep_iunlink_add_to_bucket( if (error) return error; + error = xrep_iunlink_store_prev(ragi, agino, NULLAGINO); + if (error) + return error; + /* Remember the head inode's previous pointer. */ if (current_head != NULLAGINO) { error = xrep_iunlink_store_prev(ragi, current_head, agino); From 0052633527158b49762ab427e73924e4f8d25e6c Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 26 Jul 2026 22:26:52 -0700 Subject: [PATCH 171/612] xfs: don't ignore runtime errors in xrep_iunlink_reload_next LOLLM complained that this function ignores runtime errors being returned by xrep_iunlink_store_*. Rework the function signature so that we can return runtime errors to abort the repair. Cc: stable@vger.kernel.org # v6.10 Fixes: ab97f4b1c03075 ("xfs: repair AGI unlinked inode bucket lists") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/agheader_repair.c | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/fs/xfs/scrub/agheader_repair.c b/fs/xfs/scrub/agheader_repair.c index 4f1f235349c5a3..2104512f1ee19a 100644 --- a/fs/xfs/scrub/agheader_repair.c +++ b/fs/xfs/scrub/agheader_repair.c @@ -1041,31 +1041,40 @@ xrep_iunlink_next( * the chain or if we should stop walking the chain due to corruption; or a * per-AG inode number. */ -STATIC xfs_agino_t +STATIC int xrep_iunlink_reload_next( struct xrep_agi *ragi, xfs_agino_t prev_agino, - xfs_agino_t agino) + xfs_agino_t agino, + xfs_agino_t *next_agino) { struct xfs_scrub *sc = ragi->sc; struct xfs_inode *ip; - xfs_agino_t ret = NULLAGINO; int error; + *next_agino = NULLAGINO; + error = xchk_iget(ragi->sc, xfs_agino_to_ino(sc->sa.pag, agino), &ip); if (error) - return ret; + return 0; trace_xrep_iunlink_reload_next(ip, prev_agino); /* If this is a linked inode, stop processing the chain. */ if (VFS_I(ip)->i_nlink != 0) { - xrep_iunlink_store_next(ragi, agino, NULLAGINO); + error = xrep_iunlink_store_next(ragi, agino, NULLAGINO); + if (error) + return error; + + error = xrep_iunlink_store_prev(ragi, agino, LINKED_AGINO); + if (error) + return error; + goto rele; } ip->i_prev_unlinked = prev_agino; - ret = ip->i_next_unlinked; + *next_agino = ip->i_next_unlinked; /* * Drop the inode reference that we just took. We hold the AGI, so @@ -1074,7 +1083,7 @@ xrep_iunlink_reload_next( */ rele: xchk_irele(sc, ip); - return ret; + return 0; } /* @@ -1114,9 +1123,12 @@ xrep_iunlink_walk_ondisk_bucket( break; next_agino = xrep_iunlink_next(sc, agino); - if (!next_agino) - next_agino = xrep_iunlink_reload_next(ragi, prev_agino, - agino); + if (!next_agino) { + error = xrep_iunlink_reload_next(ragi, prev_agino, + agino, &next_agino); + if (error) + break; + } error = xagino_bitmap_set(&seen, agino, 1); if (error) From 63320a0f70f66f311f4bccff3af0719c2119f46c Mon Sep 17 00:00:00 2001 From: Long Li Date: Mon, 27 Jul 2026 10:38:48 +0800 Subject: [PATCH 172/612] xfs: fix ilock leak on error in xfs_dq_get_next_id xfs_dq_get_next_id() takes the quota inode ILOCK before calling xfs_iread_extents(). If xfs_iread_extents() fails, the function returns immediately without releasing the lock, leaking the quota inode ILOCK. This can leave the quota inode locked and cause subsequent quota operations to hang. Fix this by jumping to a common unlock path on error instead of returning directly. Fixes: bda250dbaf39f ("xfs: rewrite xfs_dq_get_next_id using xfs_iext_lookup_extent") Cc: stable@vger.kernel.org # v4.12 Signed-off-by: Long Li Reviewed-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_dquot.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/xfs/xfs_dquot.c b/fs/xfs/xfs_dquot.c index c311f61d955417..b4f6c594808ce9 100644 --- a/fs/xfs/xfs_dquot.c +++ b/fs/xfs/xfs_dquot.c @@ -778,7 +778,7 @@ xfs_dq_get_next_id( lock_flags = xfs_ilock_data_map_shared(quotip); error = xfs_iread_extents(NULL, quotip, XFS_DATA_FORK); if (error) - return error; + goto out_unlock; if (xfs_iext_lookup_extent(quotip, "ip->i_df, start, &cur, &got)) { /* contiguous chunk, bump startoff for the id calculation */ @@ -789,6 +789,7 @@ xfs_dq_get_next_id( error = -ENOENT; } +out_unlock: xfs_iunlock(quotip, lock_flags); return error; From e2b4a856085e9bd939bde2dee0d08b1d41babde9 Mon Sep 17 00:00:00 2001 From: Long Li Date: Mon, 27 Jul 2026 10:38:49 +0800 Subject: [PATCH 173/612] xfs: don't swallow dquot recovery verification errors xlog_recover_dquot_commit_pass2() validates the recovered dquot with xfs_dqblk_verify() and, on failure, sets error = -EFSCORRUPTED and jumps to out_release. But out_release unconditionally returns 0, so the corruption error is discarded: the caller xlog_recover_items_pass2() sees success, log recovery proceeds as if the dquot were valid, and the corrupt quota buffer can be written back to disk. Fixes: 9c235dfc3d3f ("xfs: dquot recovery does not validate the recovered dquot") Cc: stable@vger.kernel.org # v6.8 Signed-off-by: Long Li Reviewed-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_dquot_item_recover.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/xfs_dquot_item_recover.c b/fs/xfs/xfs_dquot_item_recover.c index fe419b28de22b3..63bc9ab7d947de 100644 --- a/fs/xfs/xfs_dquot_item_recover.c +++ b/fs/xfs/xfs_dquot_item_recover.c @@ -173,7 +173,7 @@ xlog_recover_dquot_commit_pass2( out_release: xfs_buf_relse(bp); - return 0; + return error; } const struct xlog_recover_item_ops xlog_dquot_item_ops = { From 63de19199342e2598373cbb99186fa93e9116603 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 29 Jul 2026 15:02:48 +0200 Subject: [PATCH 174/612] xfs: add a separate bio_set for spliting GC writes Allocating the new bio for a split from the same pool as the original one can deadlock under memory pressure as the origin bio could be the last one from the mempool. Add a separate pool for splitting GC write bios to avoid this. Fixes: 080d01c41d44 ("xfs: implement zoned garbage collection") Signed-off-by: Christoph Hellwig Reviewed-by: Damien Le Moal Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_zone_gc.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/fs/xfs/xfs_zone_gc.c b/fs/xfs/xfs_zone_gc.c index f76a091308528b..e4f70e02463273 100644 --- a/fs/xfs/xfs_zone_gc.c +++ b/fs/xfs/xfs_zone_gc.c @@ -130,6 +130,9 @@ struct xfs_zone_gc_data { /* bioset used to allocate the gc_bios */ struct bio_set bio_set; + /* bioset used when writes need to be split to hardware limits */ + struct bio_set split_bio_set; + /* * Scratchpad to buffer GC data, organized as a ring buffer over * discontiguous folios. scratch_head is where the buffer is filled, @@ -221,6 +224,9 @@ xfs_zone_gc_data_alloc( if (bioset_init(&data->bio_set, 16, offsetof(struct xfs_gc_bio, bio), BIOSET_NEED_BVECS)) goto out_free_recs; + if (bioset_init(&data->split_bio_set, 16, + offsetof(struct xfs_gc_bio, bio), 0)) + goto out_exit_bio_set; for (i = 0; i < XFS_GC_NR_BUFS; i++) { data->scratch_folios[i] = folio_alloc(GFP_KERNEL, get_order(XFS_GC_BUF_SIZE)); @@ -238,6 +244,8 @@ xfs_zone_gc_data_alloc( out_free_scratch: while (--i >= 0) folio_put(data->scratch_folios[i]); + bioset_exit(&data->split_bio_set); +out_exit_bio_set: bioset_exit(&data->bio_set); out_free_recs: kfree(data->iter.recs); @@ -254,6 +262,7 @@ xfs_zone_gc_data_free( for (i = 0; i < XFS_GC_NR_BUFS; i++) folio_put(data->scratch_folios[i]); + bioset_exit(&data->split_bio_set); bioset_exit(&data->bio_set); kfree(data->iter.recs); kfree(data); @@ -810,7 +819,8 @@ xfs_zone_gc_split_write( data->mp->m_sb.sb_blocksize) >> SECTOR_SHIFT; split_len = split_sectors << SECTOR_SHIFT; - split = bio_split(&chunk->bio, split_sectors, GFP_NOFS, &data->bio_set); + split = bio_split(&chunk->bio, split_sectors, GFP_NOFS, + &data->split_bio_set); split_chunk = container_of(split, struct xfs_gc_bio, bio); split_chunk->data = data; ihold(VFS_I(chunk->ip)); From ec6978e6bf68fb6f51edd4f700f9a554dc0da894 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 29 Jul 2026 15:02:49 +0200 Subject: [PATCH 175/612] xfs: add a comment to describe xfs_gc_bio.victim_rtg All other fields have comments describing them, add one for this field as well. Signed-off-by: Christoph Hellwig Reviewed-by: Damien Le Moal Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_zone_gc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/xfs/xfs_zone_gc.c b/fs/xfs/xfs_zone_gc.c index e4f70e02463273..7ab8f2218c6a83 100644 --- a/fs/xfs/xfs_zone_gc.c +++ b/fs/xfs/xfs_zone_gc.c @@ -103,6 +103,7 @@ struct xfs_gc_bio { /* Open Zone being written to */ struct xfs_open_zone *oz; + /* Realtime group currently being reclaimed */ struct xfs_rtgroup *victim_rtg; /* Bio used for reads and writes, including the bvec used by it */ From eb6b2cc1fc8ad566d746d128a559989ff0bba5cc Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 29 Jul 2026 15:00:58 +0200 Subject: [PATCH 176/612] xfs: check v5 superblock features early When working on a new features that reuses the existing pad in the superblock, I noticed that mounting such a file system on an old kernel logs a rather confusing warning: XFS (vdc): Metadir superblock padding fields must be zero. This is because we only validate the various feature fields in v5 superblocks after the common superblock validation helper is called. Fix this by calling the feature validation first. Fixes: eca383fcd63b ("xfs: refactor superblock verifiers") Cc: # v4.19 Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_sb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/xfs/libxfs/xfs_sb.c b/fs/xfs/libxfs/xfs_sb.c index 47322adb769059..75f2a021ee6d4a 100644 --- a/fs/xfs/libxfs/xfs_sb.c +++ b/fs/xfs/libxfs/xfs_sb.c @@ -1118,10 +1118,10 @@ xfs_sb_read_verify( * because _verify_common checks the on-disk values. */ __xfs_sb_from_disk(&sb, dsb, false); - error = xfs_validate_sb_common(mp, bp, &sb); + error = xfs_validate_sb_read(mp, &sb); if (error) goto out_error; - error = xfs_validate_sb_read(mp, &sb); + error = xfs_validate_sb_common(mp, bp, &sb); out_error: if (error == -EFSCORRUPTED || error == -EFSBADCRC) From 496e0f706b8ad7831bf7189ff9deb8701d2ebc2b Mon Sep 17 00:00:00 2001 From: Claudio Imbrenda Date: Mon, 3 Aug 2026 14:40:28 +0200 Subject: [PATCH 177/612] KVM: s390: Fix unlikely NULL gmap dereference When creating a new vCPU, kvm_vm_ioctl_create_vcpu() will call kvm_arch_vcpu_postcreate() after the file descriptor for the new vCPU has been created. The new file descriptor has not been returned yet, but a malicious userspace program could try to guess it. If a malicious userspace program manages to start the newly created vCPU before kvm_arch_vcpu_postcreate() is called, __vcpu_run() will try to dereference vcpu->arch.gmap and trigger a NULL pointer dereference. Fix this by adding a new field to struct kvm_vcpu_arch to keep track of the initialization status of the vCPU. Refuse to run a vCPU that is not fully initialized. Fixes: dafd032a15f8 ("KVM: s390: move vcpu specific initalization to a later point") Fixes: e38c884df921 ("KVM: s390: Switch to new gmap") Reviewed-by: Steffen Eiden Reviewed-by: Janosch Frank Reviewed-by: Christian Borntraeger Signed-off-by: Claudio Imbrenda Message-ID: <20260803124040.126471-2-imbrenda@linux.ibm.com> --- arch/s390/include/asm/kvm_host.h | 1 + arch/s390/kvm/kvm-s390.c | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/arch/s390/include/asm/kvm_host.h b/arch/s390/include/asm/kvm_host.h index c172f9b212d18e..b4182ca4435fb8 100644 --- a/arch/s390/include/asm/kvm_host.h +++ b/arch/s390/include/asm/kvm_host.h @@ -440,6 +440,7 @@ struct kvm_vcpu_arch { bool skey_enabled; /* Indicator if the access registers have been loaded from guest */ bool acrs_loaded; + bool initialized; struct kvm_s390_pv_vcpu pv; union diag318_info diag318_info; struct kvm_s390_mmu_cache *mc; diff --git a/arch/s390/kvm/kvm-s390.c b/arch/s390/kvm/kvm-s390.c index 150b5dd2170e2f..f86b4b0b356f42 100644 --- a/arch/s390/kvm/kvm-s390.c +++ b/arch/s390/kvm/kvm-s390.c @@ -3613,6 +3613,9 @@ void kvm_arch_vcpu_postcreate(struct kvm_vcpu *vcpu) if (test_kvm_facility(vcpu->kvm, 74) || vcpu->kvm->arch.user_instr0 || vcpu->kvm->arch.user_operexec) vcpu->arch.sie_block->ictl |= ICTL_OPEREXC; + + /* Pairs with smp_load_acquire() in kvm_arch_vcpu_ioctl_run() and kvm_arch_vcpu_ioctl() */ + smp_store_release(&vcpu->arch.initialized, true); } static bool kvm_has_pckmo_subfunc(struct kvm *kvm, unsigned long nr) @@ -5039,6 +5042,10 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu) kvm_run->kvm_dirty_regs & ~KVM_SYNC_S390_VALID_FIELDS) return -EINVAL; + /* Pairs with smp_store_release() in kvm_arch_vcpu_postcreate() */ + if (!smp_load_acquire(&vcpu->arch.initialized)) + return -EINVAL; + vcpu_load(vcpu); if (guestdbg_exit_pending(vcpu)) { @@ -5523,6 +5530,10 @@ long kvm_arch_vcpu_ioctl(struct file *filp, long r; u16 rc, rrc; + /* Pairs with smp_store_release() in kvm_arch_vcpu_postcreate() */ + if (!smp_load_acquire(&vcpu->arch.initialized)) + return -EINVAL; + vcpu_load(vcpu); switch (ioctl) { From 1423415471274abda87024967d7fe2206ceee0ea Mon Sep 17 00:00:00 2001 From: Ryan Wilbur Date: Thu, 30 Jul 2026 16:39:20 -0300 Subject: [PATCH 178/612] serial: 8250_of: clear stuck empty-FIFO RX-timeout on LPC32xx The NXP LPC32xx UART (PORT_LPC3220) can latch an RX character-timeout interrupt while the RX FIFO is empty: IIR reports UART_IIR_RX_TIMEOUT (0x0c) but LSR.DR is clear. A character timeout is only cleared by reading RHR, but serial8250_rx_chars() reads RHR only when LSR.DR is set, so nothing ever clears the condition. The interrupt is level-triggered and re-fires immediately, so on a single-core ARM926 the resulting interrupt storm livelocks the CPU. It is reproducible when userspace repeatedly opens the front-panel port (ttyS1): serial8250_do_set_termios() re-enables interrupts on unlock and the handler then spins forever with iir=0xcc lsr=0x60 ier=0x05, tripping the soft-lockup detector in serial8250_handle_irq_locked(). LPC32xx has no dedicated 8250 glue driver, it's driven by the generic 8250_of. Add a hardware specific handle_irq for PORT_LPC3220, wired up in of_platform_serial_setup() the same way fsl8250_handle_irq is installed. The handler follows dw8250_handle_irq(): on an RX timeout with an empty FIFO (LSR.DR and LSR.BI clear) it does one throwaway RHR read to clear the condition, then calls serial8250_handle_irq_locked(). No real received data is ever discarded, and it is a no-op on healthy UARTs which never report a timeout with DR clear. This is the same class of bug already worked around in other 8250 drivers; see commit 424d79183af0 ("serial: 8250_dw: Avoid "too much work" from bogus rx timeout interrupt") which reports the identical iir=0xcc/lsr=0x60. See also UART_RX_TIMEOUT_QUIRK in 8250_omap, and the note in 8250_bcm7271. Cc: stable Assisted-by: Claude:Opus4.8 Signed-off-by: Ryan Wilbur Link: https://patch.msgid.link/20260730193920.28954-1-rwilbur633@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_of.c | 38 +++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/drivers/tty/serial/8250/8250_of.c b/drivers/tty/serial/8250/8250_of.c index 81644d40b09a18..160f4c71de06d0 100644 --- a/drivers/tty/serial/8250/8250_of.c +++ b/drivers/tty/serial/8250/8250_of.c @@ -81,6 +81,40 @@ static int of_platform_serial_clk_notifier_cb(struct notifier_block *nb, unsigne return NOTIFY_DONE; } +static int lpc32xx_handle_irq(struct uart_port *port) +{ + struct uart_8250_port *up = up_to_u8250p(port); + unsigned int iir; + u16 status; + + guard(serial8250_rpm)(up); + + iir = serial_port_in(port, UART_IIR); + if (iir & UART_IIR_NO_INT) + return 0; + + guard(uart_port_lock_check_sysrq_irqsave)(port); + + /* + * The LPC32xx UART can assert an RX character-timeout interrupt while + * the RX FIFO is empty: IIR reports UART_IIR_RX_TIMEOUT but LSR.DR is + * clear. The timeout is only cleared by reading RHR, but the core RX + * path skips that read when the FIFO is empty, so the level-triggered + * IRQ re-fires forever and livelocks this single-core SoC. Do one + * throwaway RHR read to clear it; a healthy UART never reports a + * timeout with DR/BI clear, so no received data is ever discarded. + */ + if ((iir & 0x3f) == UART_IIR_RX_TIMEOUT) { + status = serial_lsr_in(up); + if (!(status & (UART_LSR_DR | UART_LSR_BI))) + serial_port_in(port, UART_RX); + } + + serial8250_handle_irq_locked(port, iir); + + return 1; +} + /* * Fill a struct uart_port for a given device node */ @@ -173,6 +207,9 @@ static int of_platform_serial_setup(struct platform_device *ofdev, case PORT_NPCM: ret = npcm_setup(port); break; + case PORT_LPC3220: + port->handle_irq = lpc32xx_handle_irq; + break; default: /* Nothing to do */ ret = 0; @@ -369,6 +406,7 @@ static struct platform_driver of_platform_serial_driver = { module_platform_driver(of_platform_serial_driver); +MODULE_IMPORT_NS("SERIAL_8250"); MODULE_AUTHOR("Arnd Bergmann "); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Serial Port driver for Open Firmware platform devices"); From e25d47a526939ad44b75f778b8a7500562b84fc1 Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Fri, 31 Jul 2026 09:56:16 +0200 Subject: [PATCH 179/612] vt: stabilize tty reference in kbd_keycode with tty_port_tty_get kbd_keycode() reads vc->port.tty without acquiring a tty reference, racing against con_shutdown() which clears port.tty under a different lock. Use tty_port_tty_get()/tty_kref_put() to hold a proper reference for the duration the tty pointer is needed. Assisted-by: AISLE:Snapshot Signed-off-by: Joshua Rogers Cc: stable Link: https://patch.msgid.link/20260731-tty-vt-stuff-v1-1-be99b9da8e30@linuxfoundation.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/vt/keyboard.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/tty/vt/keyboard.c b/drivers/tty/vt/keyboard.c index 763a3f1b7be08a..c41d850b29c6e6 100644 --- a/drivers/tty/vt/keyboard.c +++ b/drivers/tty/vt/keyboard.c @@ -1437,7 +1437,7 @@ static void kbd_keycode(unsigned int keycode, int down, bool hw_raw) struct keyboard_notifier_param param = { .vc = vc, .value = keycode, .down = down }; int rc; - tty = vc->port.tty; + tty = tty_port_tty_get(&vc->port); if (tty && (!tty->driver_data)) { /* No driver data? Strange. Okay we fix it then. */ @@ -1497,9 +1497,12 @@ static void kbd_keycode(unsigned int keycode, int down, bool hw_raw) * characters get aren't echoed locally. This makes key repeat * usable with slow applications and under heavy loads. */ + tty_kref_put(tty); return; } + tty_kref_put(tty); + param.shift = shift_final = (shift_state | kbd->slockstate) ^ kbd->lockstate; param.ledstate = kbd->ledflagstate; key_map = key_maps[shift_final]; From a7ad0034453ba4c353f9b8f810ee2569de33d283 Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Fri, 31 Jul 2026 09:56:17 +0200 Subject: [PATCH 180/612] vt: add permission check for KDSKBMETA ioctl KDSKBMETA modifies keyboard meta mode but lacks the !perm check that all other keyboard setter ioctls in vt_k_ioctl() enforce, allowing a process to change meta mode on a non-controlling console without authorization. Assisted-by: AISLE:Snapshot Cc: stable Signed-off-by: Joshua Rogers Link: https://patch.msgid.link/20260731-tty-vt-stuff-v1-2-be99b9da8e30@linuxfoundation.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/vt/vt_ioctl.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/tty/vt/vt_ioctl.c b/drivers/tty/vt/vt_ioctl.c index 28993a3d0acb63..deb3b3f934616f 100644 --- a/drivers/tty/vt/vt_ioctl.c +++ b/drivers/tty/vt/vt_ioctl.c @@ -406,6 +406,8 @@ static int vt_k_ioctl(struct tty_struct *tty, unsigned int cmd, /* this could be folded into KDSKBMODE, but for compatibility reasons it is not so easy to fold KDGKBMETA into KDGKBMODE */ case KDSKBMETA: + if (!perm) + return -EPERM; return vt_do_kdskbmeta(console, arg); case KDGKBMETA: From dcb2f7576ce460eb4f6b9048b7c266c8da5848a8 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Fri, 31 Jul 2026 08:59:13 +0000 Subject: [PATCH 181/612] serial: amba-pl011: fix indefinite RS485 post-send delay The RS485 stop hrtimer is used both to drain the transmitter and to wait out delay_rts_after_send. The callback cannot tell the two apart, so it restarts the post-send delay on every expiry and the timer never stops. Add a WAIT_AFTER_SEND_DELAY state so its expiry ends the stop sequence instead of restarting the delay. Fixes: 2c1fd53af21b ("serial: amba-pl011: Fix RTS handling in RS485 mode") Cc: stable Assisted-by: Codex:gpt-5.6 Signed-off-by: Fan Wu Link: https://patch.msgid.link/20260731085915.326775-2-fanwu01@zju.edu.cn Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/amba-pl011.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index 8ed91e1da22be8..d88c1242e6d880 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -309,6 +309,7 @@ enum pl011_rs485_tx_state { WAIT_AFTER_RTS, SEND, WAIT_AFTER_SEND, + WAIT_AFTER_SEND_DELAY, }; /* @@ -1350,6 +1351,7 @@ static void pl011_rs485_tx_stop(struct uart_amba_port *uap) return; } if (port->rs485.delay_rts_after_send > 0) { + uap->rs485_tx_state = WAIT_AFTER_SEND_DELAY; hrtimer_start(&uap->trigger_stop_tx, ms_to_ktime(port->rs485.delay_rts_after_send), HRTIMER_MODE_REL); @@ -1415,7 +1417,8 @@ static void pl011_rs485_tx_start(struct uart_amba_port *uap) uap->rs485_tx_state = SEND; return; } - if (uap->rs485_tx_state == WAIT_AFTER_SEND) { + if (uap->rs485_tx_state == WAIT_AFTER_SEND || + uap->rs485_tx_state == WAIT_AFTER_SEND_DELAY) { hrtimer_try_to_cancel(&uap->trigger_stop_tx); uap->rs485_tx_state = SEND; return; @@ -1482,7 +1485,8 @@ static enum hrtimer_restart pl011_trigger_stop_tx(struct hrtimer *t) unsigned long flags; uart_port_lock_irqsave(&uap->port, &flags); - if (uap->rs485_tx_state == WAIT_AFTER_SEND) + if (uap->rs485_tx_state == WAIT_AFTER_SEND || + uap->rs485_tx_state == WAIT_AFTER_SEND_DELAY) pl011_rs485_tx_stop(uap); uart_port_unlock_irqrestore(&uap->port, flags); From 36672c8d7d14e9c43287528455d2c97b526ea6ad Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Fri, 31 Jul 2026 08:59:14 +0000 Subject: [PATCH 182/612] serial: amba-pl011: cancel RS485 hrtimers after freeing IRQ The RS485 trigger hrtimers are embedded in the devm-managed port and can fire after it is freed. The IRQ handler can arm a timer, so free the IRQ first and then cancel both timers. Complete the RS485 stop without arming a timer, and cancel the timers in remove() for the suspend-then-unbind path, where shutdown is not called. This issue was found by an in-house static analysis tool. Fixes: 2c1fd53af21b ("serial: amba-pl011: Fix RTS handling in RS485 mode") Cc: stable Assisted-by: Codex:gpt-5.6 Signed-off-by: Fan Wu Link: https://patch.msgid.link/20260731085915.326775-3-fanwu01@zju.edu.cn Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/amba-pl011.c | 52 ++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index d88c1242e6d880..5232042fdd929c 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -1334,11 +1334,30 @@ static inline bool pl011_dma_rx_running(struct uart_amba_port *uap) #define pl011_dma_flush_buffer NULL #endif -static void pl011_rs485_tx_stop(struct uart_amba_port *uap) +static void pl011_rs485_tx_stop_now(struct uart_amba_port *uap) { struct uart_port *port = &uap->port; u32 cr; + cr = pl011_read(uap, REG_CR); + + if (port->rs485.flags & SER_RS485_RTS_AFTER_SEND) + cr &= ~UART011_CR_RTS; + else + cr |= UART011_CR_RTS; + + /* Disable the transmitter and reenable the transceiver */ + cr &= ~UART011_CR_TXE; + cr |= UART011_CR_RXE; + pl011_write(cr, uap, REG_CR); + + uap->rs485_tx_state = OFF; +} + +static void pl011_rs485_tx_stop(struct uart_amba_port *uap) +{ + struct uart_port *port = &uap->port; + if (uap->rs485_tx_state == SEND) uap->rs485_tx_state = WAIT_AFTER_SEND; @@ -1362,19 +1381,7 @@ static void pl011_rs485_tx_stop(struct uart_amba_port *uap) hrtimer_try_to_cancel(&uap->trigger_start_tx); } - cr = pl011_read(uap, REG_CR); - - if (port->rs485.flags & SER_RS485_RTS_AFTER_SEND) - cr &= ~UART011_CR_RTS; - else - cr |= UART011_CR_RTS; - - /* Disable the transmitter and reenable the transceiver */ - cr &= ~UART011_CR_TXE; - cr |= UART011_CR_RXE; - pl011_write(cr, uap, REG_CR); - - uap->rs485_tx_state = OFF; + pl011_rs485_tx_stop_now(uap); } static void pl011_stop_tx(struct uart_port *port) @@ -2084,11 +2091,20 @@ static void pl011_shutdown(struct uart_port *port) pl011_dma_shutdown(uap); - if ((port->rs485.flags & SER_RS485_ENABLED && uap->rs485_tx_state != OFF)) - pl011_rs485_tx_stop(uap); - free_irq(uap->port.irq, uap); + /* + * free_irq() drains the UART interrupt handler, which can arm either + * timer. Cancel the timers afterwards to drain their callbacks too. + */ + hrtimer_cancel(&uap->trigger_start_tx); + hrtimer_cancel(&uap->trigger_stop_tx); + + uart_port_lock_irq(port); + if (uap->rs485_tx_state != OFF) + pl011_rs485_tx_stop_now(uap); + uart_port_unlock_irq(port); + pl011_disable_uart(uap); /* @@ -3067,6 +3083,8 @@ static void pl011_remove(struct amba_device *dev) struct uart_amba_port *uap = amba_get_drvdata(dev); uart_remove_one_port(&amba_reg, &uap->port); + hrtimer_cancel(&uap->trigger_start_tx); + hrtimer_cancel(&uap->trigger_stop_tx); pl011_unregister_port(uap); } From 440915499231e9db1c361aa45bb702e8fd3b4a32 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Fri, 31 Jul 2026 08:59:15 +0000 Subject: [PATCH 183/612] serial: amba-pl011: synchronize DMA teardown dmaengine_terminate_all() does not wait for a running callback, so the TX callback can still touch the TX buffer after it is freed. The RX poll timer reads the RX buffers without the port lock. Switch to dmaengine_terminate_sync() and delete the RX timer before freeing the buffers. Fixes: ead76f329f77 ("ARM: 6763/1: pl011: add optional RX DMA to PL011 v2") Cc: stable Assisted-by: Codex:gpt-5.6 Signed-off-by: Fan Wu Link: https://patch.msgid.link/20260731085915.326775-4-fanwu01@zju.edu.cn Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/amba-pl011.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index 5232042fdd929c..9abaeecd05fc80 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -1247,7 +1247,7 @@ static void pl011_dma_shutdown(struct uart_amba_port *uap) if (uap->using_tx_dma) { /* In theory, this should already be done by pl011_dma_flush_buffer */ - dmaengine_terminate_all(uap->dmatx.chan); + dmaengine_terminate_sync(uap->dmatx.chan); if (uap->dmatx.queued) { dma_unmap_single(uap->dmatx.chan->device->dev, uap->dmatx.dma, uap->dmatx.len, @@ -1260,12 +1260,12 @@ static void pl011_dma_shutdown(struct uart_amba_port *uap) } if (uap->using_rx_dma) { - dmaengine_terminate_all(uap->dmarx.chan); + if (uap->dmarx.poll_rate) + timer_delete_sync(&uap->dmarx.timer); + dmaengine_terminate_sync(uap->dmarx.chan); /* Clean up the RX DMA */ pl011_dmabuf_free(uap->dmarx.chan, &uap->dmarx.dbuf_a, DMA_FROM_DEVICE); pl011_dmabuf_free(uap->dmarx.chan, &uap->dmarx.dbuf_b, DMA_FROM_DEVICE); - if (uap->dmarx.poll_rate) - timer_delete_sync(&uap->dmarx.timer); uap->using_rx_dma = false; } } From f8e370058e9e1396fea1a8d11906fa92ec9bad88 Mon Sep 17 00:00:00 2001 From: Claudio Imbrenda Date: Mon, 3 Aug 2026 14:40:29 +0200 Subject: [PATCH 184/612] KVM: s390: Do not free SCA if it was not allocated If VM creation fails early in kvm_arch_init_vm(), the cleanup code tries to free up the SCA, even though the address is 0. Due to using free_pages_exact(), only the first page is skipped, accidentally freeing pages 1, 2, and 3. Fix by checking whether the pointer is NULL before attempting to free the SCA in sca_dispose(). Fixes: e72753ed1267 ("KVM: s390: Use ESCA instead of BSCA at VM init") Signed-off-by: Claudio Imbrenda Message-ID: <20260803124040.126471-3-imbrenda@linux.ibm.com> Reviewed-by: Christian Borntraeger Reviewed-by: Janosch Frank --- arch/s390/kvm/kvm-s390.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/s390/kvm/kvm-s390.c b/arch/s390/kvm/kvm-s390.c index f86b4b0b356f42..1b3290a5ad1aee 100644 --- a/arch/s390/kvm/kvm-s390.c +++ b/arch/s390/kvm/kvm-s390.c @@ -3247,7 +3247,8 @@ static void kvm_s390_crypto_init(struct kvm *kvm) static void sca_dispose(struct kvm *kvm) { - free_pages_exact(kvm->arch.sca, sizeof(*kvm->arch.sca)); + if (kvm->arch.sca) + free_pages_exact(kvm->arch.sca, sizeof(*kvm->arch.sca)); kvm->arch.sca = NULL; } From b050f741fd0d636f2daab72b74aeccea97abdb5f Mon Sep 17 00:00:00 2001 From: Claudio Imbrenda Date: Mon, 3 Aug 2026 14:40:30 +0200 Subject: [PATCH 185/612] KVM: s390: Fix kvm_s390_vcpu_unsetup_cmma() In some cases kvm_s390_vcpu_unsetup_cmma() can be called with a 0 cbrlo; in such cases, if running with V != R, free_page() will attempt to free physical page 0. Fix by freeing cbrlo only if it's non-zero. Fixes: b31605c12f4e ("KVM: s390: make cmma usage conditionally") Fixes: 29b40f105ec8 ("KVM: s390: protvirt: Add initial vm and cpu lifecycle handling") Signed-off-by: Claudio Imbrenda Message-ID: <20260803124040.126471-4-imbrenda@linux.ibm.com> --- arch/s390/kvm/kvm-s390.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/s390/kvm/kvm-s390.c b/arch/s390/kvm/kvm-s390.c index 1b3290a5ad1aee..9be27db0a21e62 100644 --- a/arch/s390/kvm/kvm-s390.c +++ b/arch/s390/kvm/kvm-s390.c @@ -3678,7 +3678,8 @@ static void kvm_s390_vcpu_crypto_setup(struct kvm_vcpu *vcpu) void kvm_s390_vcpu_unsetup_cmma(struct kvm_vcpu *vcpu) { - free_page((unsigned long)phys_to_virt(vcpu->arch.sie_block->cbrlo)); + if (vcpu->arch.sie_block->cbrlo) + free_page((unsigned long)phys_to_virt(vcpu->arch.sie_block->cbrlo)); vcpu->arch.sie_block->cbrlo = 0; } From a0496b40a4ab346052aaa59a5163c10224dda01f Mon Sep 17 00:00:00 2001 From: Claudio Imbrenda Date: Mon, 3 Aug 2026 14:40:31 +0200 Subject: [PATCH 186/612] KVM: s390: Fix overclearing ESCA in case of error If an attempt is made to create a vCPU with an already existing ID, the duplicated vCPU will be destroyed. When destroying a vCPU, its ESCA entry will be cleared. In the above scenario, the spurious duplicate vCPU is destroyed, but the ESCA entry corresponding to the original vCPU is cleared. Fix by skipping clearing the ESCA entry if the vCPU creation was not successful, i.e. if the vcpu->arch.initialized is still zero. Reviewed-by: Janosch Frank Signed-off-by: Claudio Imbrenda Fixes: abf4a71ed95f ("KVM: s390: Unlink vcpu on destroy - v2") [ Added Fixes tag while picking -- Claudio ] Message-ID: <20260803124040.126471-5-imbrenda@linux.ibm.com> --- arch/s390/kvm/interrupt.c | 2 +- arch/s390/kvm/kvm-s390.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/s390/kvm/interrupt.c b/arch/s390/kvm/interrupt.c index 7514d9e2403c1b..bb4e7246604387 100644 --- a/arch/s390/kvm/interrupt.c +++ b/arch/s390/kvm/interrupt.c @@ -86,7 +86,7 @@ static void sca_clear_ext_call(struct kvm_vcpu *vcpu) struct esca_block *sca = vcpu->kvm->arch.sca; union esca_sigp_ctrl *sigp_ctrl = &sca->cpu[vcpu->vcpu_id].sigp_ctrl; - if (!kvm_s390_use_sca_entries()) + if (!kvm_s390_use_sca_entries() || !vcpu->arch.initialized) return; kvm_s390_clear_cpuflags(vcpu, CPUSTAT_ECALL_PEND); diff --git a/arch/s390/kvm/kvm-s390.c b/arch/s390/kvm/kvm-s390.c index 9be27db0a21e62..5b2727d7dfd12c 100644 --- a/arch/s390/kvm/kvm-s390.c +++ b/arch/s390/kvm/kvm-s390.c @@ -3462,7 +3462,7 @@ static void sca_del_vcpu(struct kvm_vcpu *vcpu) { struct esca_block *sca = vcpu->kvm->arch.sca; - if (!kvm_s390_use_sca_entries()) + if (!kvm_s390_use_sca_entries() || !vcpu->arch.initialized) return; clear_bit_inv(vcpu->vcpu_id, (unsigned long *)sca->mcn); From d301ade41831e746783bdb697fae3afba47841ba Mon Sep 17 00:00:00 2001 From: Claudio Imbrenda Date: Mon, 3 Aug 2026 14:40:32 +0200 Subject: [PATCH 187/612] KVM: s390: ucontrol: Fix sca_clear_ext_call() When cleaning up a UCONTROL VM, sca_clear_ext_call() will touch memory outside of the allocated ESCA block, and UCONTROL VMs don't even use ESCA. Fix by not touching ESCA for UCONTROL VMs, and fence the KVM_S390_INTERRUPT ioctl altogether. Add extra checks in sca_ext_call_pending() and sca_inject_ext_call() to make sure UCONTROL VMs won't touch ESCA. Fencing does not cause regressions with userspace, since UCONTROL VMs never used KVM_S390_INTERRUPT ioctls. Fixes: 7d43bafcff17 ("KVM: s390: Make provisions for ESCA utilization") Signed-off-by: Claudio Imbrenda Message-ID: <20260803124040.126471-6-imbrenda@linux.ibm.com> --- arch/s390/kvm/interrupt.c | 19 ++++++++++++++----- arch/s390/kvm/kvm-s390.c | 5 +++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/arch/s390/kvm/interrupt.c b/arch/s390/kvm/interrupt.c index bb4e7246604387..8f24bcd1a6d360 100644 --- a/arch/s390/kvm/interrupt.c +++ b/arch/s390/kvm/interrupt.c @@ -45,13 +45,16 @@ static struct kvm_s390_gib *gib; static int sca_ext_call_pending(struct kvm_vcpu *vcpu, int *src_id) { struct esca_block *sca = vcpu->kvm->arch.sca; - union esca_sigp_ctrl sigp_ctrl = sca->cpu[vcpu->vcpu_id].sigp_ctrl; + union esca_sigp_ctrl sigp_ctrl; if (!kvm_s390_test_cpuflags(vcpu, CPUSTAT_ECALL_PEND)) return 0; + if (kvm_is_ucontrol(vcpu->kvm)) + return 0; BUG_ON(!kvm_s390_use_sca_entries()); + sigp_ctrl = sca->cpu[vcpu->vcpu_id].sigp_ctrl; if (src_id) *src_id = sigp_ctrl.scn; @@ -60,13 +63,16 @@ static int sca_ext_call_pending(struct kvm_vcpu *vcpu, int *src_id) static int sca_inject_ext_call(struct kvm_vcpu *vcpu, int src_id) { - struct esca_block *sca = vcpu->kvm->arch.sca; - union esca_sigp_ctrl *sigp_ctrl = &sca->cpu[vcpu->vcpu_id].sigp_ctrl; union esca_sigp_ctrl old_val, new_val = {.scn = src_id, .c = 1}; + struct esca_block *sca = vcpu->kvm->arch.sca; + union esca_sigp_ctrl *sigp_ctrl; int expect, rc; BUG_ON(!kvm_s390_use_sca_entries()); + if (kvm_is_ucontrol(vcpu->kvm)) + return -EINVAL; + sigp_ctrl = &sca->cpu[vcpu->vcpu_id].sigp_ctrl; old_val = READ_ONCE(*sigp_ctrl); old_val.c = 0; @@ -84,10 +90,13 @@ static int sca_inject_ext_call(struct kvm_vcpu *vcpu, int src_id) static void sca_clear_ext_call(struct kvm_vcpu *vcpu) { struct esca_block *sca = vcpu->kvm->arch.sca; - union esca_sigp_ctrl *sigp_ctrl = &sca->cpu[vcpu->vcpu_id].sigp_ctrl; + union esca_sigp_ctrl *sigp_ctrl; - if (!kvm_s390_use_sca_entries() || !vcpu->arch.initialized) + if (!kvm_s390_use_sca_entries() || !vcpu->arch.initialized || kvm_is_ucontrol(vcpu->kvm)) return; + + /* Initialize after the above check, to prevent going out of bounds */ + sigp_ctrl = &sca->cpu[vcpu->vcpu_id].sigp_ctrl; kvm_s390_clear_cpuflags(vcpu, CPUSTAT_ECALL_PEND); WRITE_ONCE(sigp_ctrl->value, 0); diff --git a/arch/s390/kvm/kvm-s390.c b/arch/s390/kvm/kvm-s390.c index 5b2727d7dfd12c..21574f57be72e9 100644 --- a/arch/s390/kvm/kvm-s390.c +++ b/arch/s390/kvm/kvm-s390.c @@ -2934,6 +2934,9 @@ int kvm_arch_vm_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg) case KVM_S390_INTERRUPT: { struct kvm_s390_interrupt s390int; + r = -EINVAL; + if (kvm_is_ucontrol(kvm)) + break; r = -EFAULT; if (copy_from_user(&s390int, argp, sizeof(s390int))) break; @@ -5456,6 +5459,8 @@ long kvm_arch_vcpu_unlocked_ioctl(struct file *filp, unsigned int ioctl, struct kvm_s390_interrupt s390int; struct kvm_s390_irq s390irq = {}; + if (kvm_is_ucontrol(vcpu->kvm)) + return -EINVAL; if (copy_from_user(&s390int, argp, sizeof(s390int))) return -EFAULT; if (s390int_to_s390irq(&s390int, &s390irq)) From d699986f11bf854bddecd6b1bfdf53e05ef945d6 Mon Sep 17 00:00:00 2001 From: Claudio Imbrenda Date: Mon, 3 Aug 2026 14:40:33 +0200 Subject: [PATCH 188/612] KVM: s390: Fix leaking of PGM_ADDRESSING to userspace If kvm_s390_set_cmma_bits() is asked to set CMMA values outside of a memslot, PGM_ADDRESSING (5) is returned, instead of a negative error value. Same issue with kvm_s390_{g,s}et_skeys(), kvm_s390_keyop(), and dat_reset_reference_bit(). Fix by returning -EFAULT whenever the return value would be > 0, which is consistent with the behaviour before the gmap rewrite. Fixes: e38c884df921 ("KVM: s390: Switch to new gmap") Signed-off-by: Claudio Imbrenda Message-ID: <20260803124040.126471-7-imbrenda@linux.ibm.com> --- arch/s390/kvm/dat.c | 16 ++++++++++------ arch/s390/kvm/dat.h | 2 +- arch/s390/kvm/kvm-s390.c | 16 ++++++++-------- arch/s390/kvm/priv.c | 5 +++-- 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/arch/s390/kvm/dat.c b/arch/s390/kvm/dat.c index ed4259d1762954..171b6195990891 100644 --- a/arch/s390/kvm/dat.c +++ b/arch/s390/kvm/dat.c @@ -755,13 +755,15 @@ int dat_cond_set_storage_key(struct kvm_s390_mmu_cache *mmc, union asce asce, gf return rc; } -int dat_reset_reference_bit(union asce asce, gfn_t gfn) +int dat_reset_reference_bit(union asce asce, gfn_t gfn, union skey *skey) { union pgste pgste, old; union crste *crstep; union pte *ptep; int rc; + skey->skey = 0; + rc = dat_entry_walk(NULL, gfn, asce, DAT_WALK_ANY, TABLE_TYPE_PAGE_TABLE, &crstep, &ptep); if (rc) return rc; @@ -771,21 +773,23 @@ int dat_reset_reference_bit(union asce asce, gfn_t gfn) if (!crste.h.fc || !crste.s.fc1.pr) return 0; - return page_reset_referenced(large_crste_to_phys(*crstep, gfn)); + skey->skey = page_reset_referenced(large_crste_to_phys(*crstep, gfn)) << 1; + return 0; } old = pgste_get_lock(ptep); pgste = old; if (!ptep->h.i) { - rc = page_reset_referenced(pte_origin(*ptep)); - pgste.hr = rc >> 1; + skey->skey = page_reset_referenced(pte_origin(*ptep)) << 1; + pgste.hr = skey->r; } - rc |= (pgste.gr << 1) | pgste.gc; + skey->r |= pgste.gr; + skey->c |= pgste.gc; pgste.gr = 0; dat_update_ptep_sd(old, pgste, ptep); pgste_set_unlock(ptep, pgste); - return rc; + return 0; } static long dat_reset_skeys_pte(union pte *ptep, gfn_t gfn, gfn_t next, struct dat_walk *walk) diff --git a/arch/s390/kvm/dat.h b/arch/s390/kvm/dat.h index fad605305e05c3..141ee7b9f01919 100644 --- a/arch/s390/kvm/dat.h +++ b/arch/s390/kvm/dat.h @@ -537,7 +537,7 @@ int dat_set_storage_key(struct kvm_s390_mmu_cache *mc, union asce asce, gfn_t gf union skey skey, bool nq); int dat_cond_set_storage_key(struct kvm_s390_mmu_cache *mmc, union asce asce, gfn_t gfn, union skey skey, union skey *oldkey, bool nq, bool mr, bool mc); -int dat_reset_reference_bit(union asce asce, gfn_t gfn); +int dat_reset_reference_bit(union asce asce, gfn_t gfn, union skey *skey); long dat_reset_skeys(union asce asce, gfn_t start); unsigned long dat_get_ptval(struct page_table *table, struct ptval_param param); diff --git a/arch/s390/kvm/kvm-s390.c b/arch/s390/kvm/kvm-s390.c index 21574f57be72e9..e162efaa35b8fe 100644 --- a/arch/s390/kvm/kvm-s390.c +++ b/arch/s390/kvm/kvm-s390.c @@ -571,7 +571,7 @@ static int kvm_s390_keyop(struct kvm_s390_mmu_cache *mc, struct kvm *kvm, int op switch (op) { case KVM_S390_KEYOP_SSKE: r = dat_cond_set_storage_key(mc, asce, gfn, skey, &skey, 0, 0, 0); - if (r >= 0) + if (r == 0 || r == 1) return skey.skey; break; case KVM_S390_KEYOP_ISKE: @@ -580,14 +580,14 @@ static int kvm_s390_keyop(struct kvm_s390_mmu_cache *mc, struct kvm *kvm, int op return skey.skey; break; case KVM_S390_KEYOP_RRBE: - r = dat_reset_reference_bit(asce, gfn); - if (r > 0) - return r << 1; + r = dat_reset_reference_bit(asce, gfn, &skey); + if (!r) + return skey.skey; break; default: return -EINVAL; } - return r; + return r > 0 ? -EFAULT : r; } /* Section: device related */ @@ -2214,7 +2214,7 @@ static int kvm_s390_get_skeys(struct kvm *kvm, struct kvm_s390_skeys *args) } kvfree(keys); - return r; + return r <= 0 ? r : -EFAULT; } static int kvm_s390_set_skeys(struct kvm *kvm, struct kvm_s390_skeys *args) @@ -2276,7 +2276,7 @@ static int kvm_s390_set_skeys(struct kvm *kvm, struct kvm_s390_skeys *args) kvm_s390_free_mmu_cache(mc); out: kvfree(keys); - return r; + return r <= 0 ? r : -EFAULT; } /* @@ -2386,7 +2386,7 @@ static int kvm_s390_set_cmma_bits(struct kvm *kvm, set_bit(GMAP_FLAG_USES_CMM, &kvm->arch.gmap->flags); - return r; + return r <= 0 ? r : -EFAULT; } /** diff --git a/arch/s390/kvm/priv.c b/arch/s390/kvm/priv.c index ad0ddc433a73c3..ea5a99537346eb 100644 --- a/arch/s390/kvm/priv.c +++ b/arch/s390/kvm/priv.c @@ -289,6 +289,7 @@ static int handle_iske(struct kvm_vcpu *vcpu) static int handle_rrbe(struct kvm_vcpu *vcpu) { unsigned long gaddr; + union skey skey; int reg1, reg2; int rc; @@ -307,12 +308,12 @@ static int handle_rrbe(struct kvm_vcpu *vcpu) gaddr = kvm_s390_logical_to_effective(vcpu, gaddr); gaddr = kvm_s390_real_to_abs(vcpu, gaddr); scoped_guard(read_lock, &vcpu->kvm->mmu_lock) - rc = dat_reset_reference_bit(vcpu->arch.gmap->asce, gpa_to_gfn(gaddr)); + rc = dat_reset_reference_bit(vcpu->arch.gmap->asce, gpa_to_gfn(gaddr), &skey); if (rc > 0) return kvm_s390_inject_program_int(vcpu, rc); if (rc < 0) return rc; - kvm_s390_set_psw_cc(vcpu, rc); + kvm_s390_set_psw_cc(vcpu, (skey.skey >> 1) & 3); return 0; } From 4db7207052874f13dec1e9be384cca279c6693e8 Mon Sep 17 00:00:00 2001 From: Claudio Imbrenda Date: Mon, 3 Aug 2026 14:40:34 +0200 Subject: [PATCH 189/612] KVM: s390: Fix race in __do_essa() An unlikely race between __do_essa() and kvm_s390_vm_start_migration(), kvm_s390_vm_stop_migration(), or dat_get_cmma() was possible. Fix by locking kvm->slots_arch_lock. Since this is not a hot path, the overhead of an additional mutex is negligible. Fixes: e38c884df921 ("KVM: s390: Switch to new gmap") Signed-off-by: Claudio Imbrenda Message-ID: <20260803124040.126471-8-imbrenda@linux.ibm.com> --- arch/s390/kvm/kvm-s390.c | 20 ++++++++++---------- arch/s390/kvm/priv.c | 5 +++-- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/arch/s390/kvm/kvm-s390.c b/arch/s390/kvm/kvm-s390.c index e162efaa35b8fe..e5c5e9f61cb24b 100644 --- a/arch/s390/kvm/kvm-s390.c +++ b/arch/s390/kvm/kvm-s390.c @@ -1219,8 +1219,8 @@ static void kvm_s390_sync_request_broadcast(struct kvm *kvm, int req) /* * Must be called with kvm->srcu held to avoid races on memslots, and with - * kvm->slots_lock to avoid races with ourselves, kvm_s390_vm_stop_migration(), - * and kvm_s390_get_cmma_bits(). + * kvm->slots_arch_lock to avoid races with ourselves, + * kvm_s390_vm_stop_migration(), and kvm_s390_get_cmma_bits(). */ static int kvm_s390_vm_start_migration(struct kvm *kvm) { @@ -1265,7 +1265,7 @@ static int kvm_s390_vm_start_migration(struct kvm *kvm) } /* - * Must be called with kvm->slots_lock to avoid races with ourselves, + * Must be called with kvm->slots_arch_lock to avoid races with ourselves, * kvm_s390_vm_start_migration() and kvm_s390_get_cmma_bits(). */ static int kvm_s390_vm_stop_migration(struct kvm *kvm) @@ -1300,7 +1300,9 @@ static int kvm_s390_vm_set_migration(struct kvm *kvm, { int res = -ENXIO; - mutex_lock(&kvm->slots_lock); + guard(srcu)(&kvm->srcu); + guard(mutex)(&kvm->slots_arch_lock); + switch (attr->attr) { case KVM_S390_VM_MIGRATION_START: res = kvm_s390_vm_start_migration(kvm); @@ -1311,7 +1313,6 @@ static int kvm_s390_vm_set_migration(struct kvm *kvm, default: break; } - mutex_unlock(&kvm->slots_lock); return res; } @@ -3001,9 +3002,8 @@ int kvm_arch_vm_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg) r = -EFAULT; if (copy_from_user(&args, argp, sizeof(args))) break; - mutex_lock(&kvm->slots_lock); - r = kvm_s390_get_cmma_bits(kvm, &args); - mutex_unlock(&kvm->slots_lock); + scoped_guard(mutex, &kvm->slots_arch_lock) + r = kvm_s390_get_cmma_bits(kvm, &args); if (!r) { r = copy_to_user(argp, &args, sizeof(args)); if (r) @@ -3017,9 +3017,9 @@ int kvm_arch_vm_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg) r = -EFAULT; if (copy_from_user(&args, argp, sizeof(args))) break; - mutex_lock(&kvm->slots_lock); + mutex_lock(&kvm->slots_arch_lock); r = kvm_s390_set_cmma_bits(kvm, &args); - mutex_unlock(&kvm->slots_lock); + mutex_unlock(&kvm->slots_arch_lock); break; } case KVM_S390_PV_COMMAND: { diff --git a/arch/s390/kvm/priv.c b/arch/s390/kvm/priv.c index ea5a99537346eb..b1ba24c346efa1 100644 --- a/arch/s390/kvm/priv.c +++ b/arch/s390/kvm/priv.c @@ -1261,8 +1261,9 @@ static int handle_essa(struct kvm_vcpu *vcpu) /* Retry the ESSA instruction */ kvm_s390_retry_instr(vcpu); } else { - scoped_guard(read_lock, &vcpu->kvm->mmu_lock) - i = __do_essa(vcpu, orc); + scoped_guard(mutex, &vcpu->kvm->slots_arch_lock) + scoped_guard(read_lock, &vcpu->kvm->mmu_lock) + i = __do_essa(vcpu, orc); if (i < 0) return i; /* Account for the possible extra cbrl entry */ From ec215346270512db60478e45e8dff2de950225aa Mon Sep 17 00:00:00 2001 From: Claudio Imbrenda Date: Mon, 3 Aug 2026 14:40:35 +0200 Subject: [PATCH 190/612] KVM: s390: cmma: Fix dirty tracking when removing memslot When a memslot is removed, all ptes that mapped the slot are cleared or even deallocated. If this happens while the system is in migration mode, and if cmma-dirty pages are removed, the cmma-dirty counter will not reflect reality. Fix by appropriately decrementing the cmma-dirty counter when removing a memslot. Opportunistically improve kvm_arch_commit_memory_region() to use __free() for the struct kvm_s390_mmu_cache. Fixes: e38c884df921 ("KVM: s390: Switch to new gmap") Signed-off-by: Claudio Imbrenda Message-ID: <20260803124040.126471-9-imbrenda@linux.ibm.com> --- arch/s390/kvm/dat.c | 7 ++++++- arch/s390/kvm/kvm-s390.c | 25 +++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/arch/s390/kvm/dat.c b/arch/s390/kvm/dat.c index 171b6195990891..3f2d6e8902d76a 100644 --- a/arch/s390/kvm/dat.c +++ b/arch/s390/kvm/dat.c @@ -850,6 +850,7 @@ static long _dat_slot_pte(union pte *ptep, gfn_t gfn, gfn_t next, struct dat_wal struct slot_priv *p = walk->priv; union crste dummy = { .val = p->token }; union pte new_pte, pte = READ_ONCE(*ptep); + union pgste pgste; new_pte = _PTE_TOK(dummy.tok.type, dummy.tok.par); @@ -857,7 +858,11 @@ static long _dat_slot_pte(union pte *ptep, gfn_t gfn, gfn_t next, struct dat_wal if (pte.val == new_pte.val) return 0; - dat_ptep_xchg(ptep, new_pte, gfn, walk->asce, false); + pgste = pgste_get_lock(ptep); + pgste = __dat_ptep_xchg(ptep, pgste, new_pte, gfn, walk->asce, false); + pgste.cmma_d = 0; + pgste_set_unlock(ptep, pgste); + return 0; } diff --git a/arch/s390/kvm/kvm-s390.c b/arch/s390/kvm/kvm-s390.c index e5c5e9f61cb24b..ba811f0673d107 100644 --- a/arch/s390/kvm/kvm-s390.c +++ b/arch/s390/kvm/kvm-s390.c @@ -5812,14 +5812,30 @@ int kvm_arch_prepare_memory_region(struct kvm *kvm, return 0; } +static long cmma_d_count_pte(union pte *ptep, gfn_t gfn, gfn_t next, struct dat_walk *walk) +{ + union pgste pgste; + + pgste = pgste_get_lock(ptep); + if (pgste.cmma_d) { + pgste.cmma_d = 0; + atomic64_dec(walk->priv); + } + pgste_set_unlock(ptep, pgste); + return 0; +} + void kvm_arch_commit_memory_region(struct kvm *kvm, struct kvm_memory_slot *old, const struct kvm_memory_slot *new, enum kvm_mr_change change) { - struct kvm_s390_mmu_cache *mc = NULL; + const struct dat_walk_ops ops = { .pte_entry = cmma_d_count_pte, }; + struct kvm_s390_mmu_cache *mc __free(kvm_s390_mmu_cache) = NULL; int rc = 0; + guard(mutex)(&kvm->slots_arch_lock); + if (change == KVM_MR_FLAGS_ONLY) return; @@ -5830,6 +5846,12 @@ void kvm_arch_commit_memory_region(struct kvm *kvm, } scoped_guard(write_lock, &kvm->mmu_lock) { + if (kvm->arch.migration_mode && kvm->arch.use_cmma && old) { + _dat_walk_gfn_range(old->base_gfn, old->base_gfn + old->npages, + kvm->arch.gmap->asce, &ops, DAT_WALK_IGN_HOLES, + &kvm->arch.cmma_dirty_pages); + } + switch (change) { case KVM_MR_DELETE: rc = dat_delete_slot(mc, kvm->arch.gmap->asce, old->base_gfn, old->npages); @@ -5851,7 +5873,6 @@ void kvm_arch_commit_memory_region(struct kvm *kvm, out: if (rc) pr_warn("failed to commit memory region\n"); - kvm_s390_free_mmu_cache(mc); return; } From dab62d218754e00a22aff45d2b0116c5ee30cbd2 Mon Sep 17 00:00:00 2001 From: Claudio Imbrenda Date: Mon, 3 Aug 2026 14:40:36 +0200 Subject: [PATCH 191/612] KVM: s390: ucontrol: Add missing locking around gmap_remove_child() gmap_remove_child() needs to be called while holding the children_lock of the parent gmap. This was not the case in the error handling path of kvm_arch_vcpu_create() for UCONTROL guests. Fix by adding the missing lock. Fixes: e38c884df921 ("KVM: s390: Switch to new gmap") Reviewed-by: Steffen Eiden Signed-off-by: Claudio Imbrenda Message-ID: <20260803124040.126471-10-imbrenda@linux.ibm.com> --- arch/s390/kvm/kvm-s390.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/s390/kvm/kvm-s390.c b/arch/s390/kvm/kvm-s390.c index ba811f0673d107..2741ca323edea5 100644 --- a/arch/s390/kvm/kvm-s390.c +++ b/arch/s390/kvm/kvm-s390.c @@ -3875,7 +3875,9 @@ int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu) out_ucontrol_uninit: if (kvm_is_ucontrol(vcpu->kvm)) { + spin_lock(&vcpu->kvm->arch.gmap->children_lock); gmap_remove_child(vcpu->arch.gmap); + spin_unlock(&vcpu->kvm->arch.gmap->children_lock); vcpu->arch.gmap = gmap_put(vcpu->arch.gmap); } out_free_sie_block: From e4d678900a1ae66112812fe4d2aff16044ca37f2 Mon Sep 17 00:00:00 2001 From: Claudio Imbrenda Date: Mon, 3 Aug 2026 14:40:37 +0200 Subject: [PATCH 192/612] KVM: s390: Free the mmu cache when kvm_arch_vcpu_create() fails The mmu cache is the first thing that is allocated in kvm_arch_vcpu_create(), but in case of failure it was not freed. Fix by freeing the mmu cache in case of failure. Refactor kvm_arch_vcpu_create() to use scope-based cleanup instead of gotos. Fixes: e38c884df921 ("KVM: s390: Switch to new gmap") Signed-off-by: Claudio Imbrenda Message-ID: <20260803124040.126471-11-imbrenda@linux.ibm.com> --- arch/s390/kvm/kvm-s390.c | 40 ++++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/arch/s390/kvm/kvm-s390.c b/arch/s390/kvm/kvm-s390.c index 2741ca323edea5..9b8a35e7dc822e 100644 --- a/arch/s390/kvm/kvm-s390.c +++ b/arch/s390/kvm/kvm-s390.c @@ -3800,21 +3800,21 @@ int kvm_arch_vcpu_precreate(struct kvm *kvm, unsigned int id) return 0; } +DEFINE_FREE(sie_page, struct sie_page *, if (_T) free_page((unsigned long)(_T))) + int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu) { - struct sie_page *sie_page; + struct kvm_s390_mmu_cache *mc __free(kvm_s390_mmu_cache) = NULL; + struct sie_page *sie_page __free(sie_page) = NULL; int rc; BUILD_BUG_ON(sizeof(struct sie_page) != 4096); - vcpu->arch.mc = kvm_s390_new_mmu_cache(); - if (!vcpu->arch.mc) + mc = kvm_s390_new_mmu_cache(); + if (!mc) return -ENOMEM; sie_page = (struct sie_page *) get_zeroed_page(GFP_KERNEL_ACCOUNT); - if (!sie_page) { - kvm_s390_free_mmu_cache(vcpu->arch.mc); - vcpu->arch.mc = NULL; + if (!sie_page) return -ENOMEM; - } vcpu->arch.sie_block = &sie_page->sie_block; vcpu->arch.sie_block->itdba = virt_to_phys(&sie_page->itdb); @@ -3856,10 +3856,9 @@ int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu) vcpu->run->kvm_valid_regs |= KVM_SYNC_FPRS; if (kvm_is_ucontrol(vcpu->kvm)) { - rc = -ENOMEM; vcpu->arch.gmap = gmap_new_child(vcpu->kvm->arch.gmap, -1UL); if (!vcpu->arch.gmap) - goto out_free_sie_block; + return -ENOMEM; } VM_EVENT(vcpu->kvm, 3, "create cpu %d at 0x%p, sie block at 0x%p", @@ -3867,22 +3866,19 @@ int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu) trace_kvm_s390_create_vcpu(vcpu->vcpu_id, vcpu, vcpu->arch.sie_block); rc = kvm_s390_vcpu_setup(vcpu); - if (rc) - goto out_ucontrol_uninit; + if (rc) { + if (kvm_is_ucontrol(vcpu->kvm)) { + scoped_guard(spinlock, &vcpu->kvm->arch.gmap->children_lock) + gmap_remove_child(vcpu->arch.gmap); + vcpu->arch.gmap = gmap_put(vcpu->arch.gmap); + } + return rc; + } + vcpu->arch.mc = no_free_ptr(mc); + sie_page = NULL; kvm_s390_update_topology_change_report(vcpu->kvm, 1); return 0; - -out_ucontrol_uninit: - if (kvm_is_ucontrol(vcpu->kvm)) { - spin_lock(&vcpu->kvm->arch.gmap->children_lock); - gmap_remove_child(vcpu->arch.gmap); - spin_unlock(&vcpu->kvm->arch.gmap->children_lock); - vcpu->arch.gmap = gmap_put(vcpu->arch.gmap); - } -out_free_sie_block: - free_page((unsigned long)(vcpu->arch.sie_block)); - return rc; } int kvm_arch_vcpu_runnable(struct kvm_vcpu *vcpu) From 216c5289dd66d9cde6b42cbe68ee8dec5669678a Mon Sep 17 00:00:00 2001 From: Claudio Imbrenda Date: Mon, 3 Aug 2026 14:40:38 +0200 Subject: [PATCH 193/612] KVM: s390: Return -EINTR if a signal is pending while faulting-in If a fatal signal is pending while trying to fault-in a page, return -EINTR instead of -EAGAIN. Also fix unpack_one() to handle -EINTR properly. Fixes: e907ae530133 ("KVM: s390: Add helper functions for fault handling") Signed-off-by: Claudio Imbrenda Message-ID: <20260803124040.126471-12-imbrenda@linux.ibm.com> --- arch/s390/kvm/faultin.c | 6 +++--- arch/s390/kvm/pv.c | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/arch/s390/kvm/faultin.c b/arch/s390/kvm/faultin.c index fee80047bd94ff..3cc45f7f5b2d7b 100644 --- a/arch/s390/kvm/faultin.c +++ b/arch/s390/kvm/faultin.c @@ -91,9 +91,9 @@ int kvm_s390_faultin_gfn(struct kvm_vcpu *vcpu, struct kvm *kvm, struct guest_fa /* Access outside memory, addressing exception. */ if (is_noslot_pfn(f->pfn)) return PGM_ADDRESSING; - /* Signal pending: try again. */ - if (f->pfn == KVM_PFN_ERR_SIGPENDING) - return -EAGAIN; + /* Fatal signal pending: bail out. */ + if (is_sigpending_pfn(f->pfn)) + return -EINTR; /* Check if it's read-only memory; don't try to actually handle that case. */ if (f->pfn == KVM_PFN_ERR_RO_FAULT) return -EOPNOTSUPP; diff --git a/arch/s390/kvm/pv.c b/arch/s390/kvm/pv.c index 1beacc841ca850..dc204b521052c4 100644 --- a/arch/s390/kvm/pv.c +++ b/arch/s390/kvm/pv.c @@ -809,7 +809,7 @@ static int unpack_one(struct kvm *kvm, unsigned long addr, u64 tweak, return -EAGAIN; } - if (ret && ret != -EAGAIN) + if (ret && ret != -EAGAIN && ret != -EINTR) KVM_UV_EVENT(kvm, 3, "PROTVIRT VM UNPACK: failed addr %llx with rc %x rrc %x", uvcb.gaddr, *rc, *rrc); return ret; From 9187a9186d0ac7b260b07917aabc672a80d37c3c Mon Sep 17 00:00:00 2001 From: Claudio Imbrenda Date: Mon, 3 Aug 2026 14:40:39 +0200 Subject: [PATCH 194/612] KVM: s390: Fix ordering when adding to SCA When adding a new vCPU to the SCA area, the validity bit in the MCN was set before the pointer to the state description, potentially allowing for a race. Fix by setting the pointer before setting the bit. Fixes: 14542a0a54c5 ("KVM: S390: Remove sca_lock") Reviewed-by: Steffen Eiden Reviewed-by: Janosch Frank Signed-off-by: Claudio Imbrenda Message-ID: <20260803124040.126471-13-imbrenda@linux.ibm.com> --- arch/s390/kvm/kvm-s390.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/s390/kvm/kvm-s390.c b/arch/s390/kvm/kvm-s390.c index 9b8a35e7dc822e..518a69c55e85c9 100644 --- a/arch/s390/kvm/kvm-s390.c +++ b/arch/s390/kvm/kvm-s390.c @@ -3485,8 +3485,8 @@ static void sca_add_vcpu(struct kvm_vcpu *vcpu) if (!kvm_s390_use_sca_entries()) return; + WRITE_ONCE(sca->cpu[vcpu->vcpu_id].sda, virt_to_phys(vcpu->arch.sie_block)); set_bit_inv(vcpu->vcpu_id, (unsigned long *)sca->mcn); - sca->cpu[vcpu->vcpu_id].sda = virt_to_phys(vcpu->arch.sie_block); } static int sca_can_add_vcpu(struct kvm *kvm, unsigned int id) From feadc5e84dcb53422a437556c35af9efd9826fd5 Mon Sep 17 00:00:00 2001 From: Claudio Imbrenda Date: Mon, 3 Aug 2026 14:40:40 +0200 Subject: [PATCH 195/612] KVM: s390: Fix cleanup in kvm_s390_pv_create_cpu() If creating a protected vCPU in kvm_s390_pv_create_cpu() fails, kvm_s390_pv_destroy_cpu() was called, which checks whether the vCPU has a PV handle and exits doing nothing otherwise. At that point, due to not having created the protected vCPU, the PV handle will not be set, and kvm_s390_pv_destroy_cpu() will do nothing, thus leaking the allocated memory. Fix by factoring out the code to free and reset a PV vCPU; call it from kvm_s390_pv_destroy_cpu() and kvm_s390_pv_create_cpu(). Opportunistically fix the return value of kvm_s390_pv_destroy_cpu() in case of errors: return -EIO instead if EIO. Fixes: d4074324b07a ("KVM: s390: pv: avoid double free of sida page") Reviewed-by: Steffen Eiden Reviewed-by: Janosch Frank Signed-off-by: Claudio Imbrenda Message-ID: <20260803124040.126471-14-imbrenda@linux.ibm.com> --- arch/s390/kvm/pv.c | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/arch/s390/kvm/pv.c b/arch/s390/kvm/pv.c index dc204b521052c4..b02e0159d3cd21 100644 --- a/arch/s390/kvm/pv.c +++ b/arch/s390/kvm/pv.c @@ -244,6 +244,24 @@ static void kvm_s390_clear_pv_state(struct kvm *kvm) kvm->arch.pv.stor_var = NULL; } +static void kvm_s390_pv_dispose_cpu(struct kvm_vcpu *vcpu, bool free_stor_base) +{ + if (free_stor_base) + free_pages(vcpu->arch.pv.stor_base, get_order(uv_info.guest_cpu_stor_len)); + free_page((unsigned long)sida_addr(vcpu->arch.sie_block)); + vcpu->arch.sie_block->pv_handle_cpu = 0; + vcpu->arch.sie_block->pv_handle_config = 0; + memset(&vcpu->arch.pv, 0, sizeof(vcpu->arch.pv)); + vcpu->arch.sie_block->sdf = 0; + /* + * The sidad field (for sdf == 2) is now the gbea field (for sdf == 0). + * Use the reset value of gbea to avoid leaking the kernel pointer of + * the just freed sida. + */ + vcpu->arch.sie_block->gbea = 1; + kvm_make_request(KVM_REQ_TLB_FLUSH, vcpu); +} + int kvm_s390_pv_destroy_cpu(struct kvm_vcpu *vcpu, u16 *rc, u16 *rrc) { int cc; @@ -258,24 +276,9 @@ int kvm_s390_pv_destroy_cpu(struct kvm_vcpu *vcpu, u16 *rc, u16 *rrc) WARN_ONCE(cc, "protvirt destroy cpu failed rc %x rrc %x", *rc, *rrc); /* Intended memory leak for something that should never happen. */ - if (!cc) - free_pages(vcpu->arch.pv.stor_base, - get_order(uv_info.guest_cpu_stor_len)); - - free_page((unsigned long)sida_addr(vcpu->arch.sie_block)); - vcpu->arch.sie_block->pv_handle_cpu = 0; - vcpu->arch.sie_block->pv_handle_config = 0; - memset(&vcpu->arch.pv, 0, sizeof(vcpu->arch.pv)); - vcpu->arch.sie_block->sdf = 0; - /* - * The sidad field (for sdf == 2) is now the gbea field (for sdf == 0). - * Use the reset value of gbea to avoid leaking the kernel pointer of - * the just freed sida. - */ - vcpu->arch.sie_block->gbea = 1; - kvm_make_request(KVM_REQ_TLB_FLUSH, vcpu); + kvm_s390_pv_dispose_cpu(vcpu, !cc); - return cc ? EIO : 0; + return cc ? -EIO : 0; } int kvm_s390_pv_create_cpu(struct kvm_vcpu *vcpu, u16 *rc, u16 *rrc) @@ -319,9 +322,7 @@ int kvm_s390_pv_create_cpu(struct kvm_vcpu *vcpu, u16 *rc, u16 *rrc) uvcb.header.rrc); if (cc) { - u16 dummy; - - kvm_s390_pv_destroy_cpu(vcpu, &dummy, &dummy); + kvm_s390_pv_dispose_cpu(vcpu, true); return -EIO; } From 50b303f3d0f7de543ee90d50879970783d06da33 Mon Sep 17 00:00:00 2001 From: Pawel Laszczak Date: Mon, 20 Jul 2026 13:11:58 +0200 Subject: [PATCH 196/612] usb: cdnsp: fix incorrect endian conversions for APB timeout register readl() already returns a CPU-endian value. Passing its return value to le32_to_cpu() is therefore redundant and causes an incorrect double byte swap on big-endian systems. Similarly, writel() expects a CPU-endian value, so passing the result of cpu_to_le32() is incorrect. Remove the unnecessary conversions and operate on the MMIO register value as a CPU-endian u32. Fixes: 241e2ce88e5a ("usb: cdnsp: Fix issue with resuming from L1") Suggested-by: Arnd Bergmann Cc: stable Signed-off-by: Pawel Laszczak Acked-by: Arnd Bergmann Link: https://patch.msgid.link/20260720-endian-fix-v1-v1-1-b5681fa1ea9f@cadence.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/cdns3/cdnsp-gadget.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/cdns3/cdnsp-gadget.c b/drivers/usb/cdns3/cdnsp-gadget.c index a5275c2fb43b8e..7a516e509198b8 100644 --- a/drivers/usb/cdns3/cdnsp-gadget.c +++ b/drivers/usb/cdns3/cdnsp-gadget.c @@ -163,9 +163,9 @@ static void cdnsp_set_apb_timeout_value(struct cdnsp_device *pdev) offset = cdnsp_find_next_ext_cap(base, offset, D_XEC_PRE_REGS_CAP); reg = base + offset + REG_CHICKEN_BITS_3_OFFSET; - val = le32_to_cpu(readl(reg)); + val = readl(reg); val = CHICKEN_APB_TIMEOUT_SET(val, cdns->override_apb_timeout); - writel(cpu_to_le32(val), reg); + writel(val, reg); } static void cdnsp_set_chicken_bits_2(struct cdnsp_device *pdev, u32 bit) From 6b1c8a9403a26cb0fed7a648916c74dc236da591 Mon Sep 17 00:00:00 2001 From: Sonali Pradhan Date: Mon, 20 Jul 2026 16:56:54 +0000 Subject: [PATCH 197/612] usb: gadget: f_ncm: Use unsigned int for ndp_index The variable ndp_index is declared as a signed integer, but it stores the return value of get_ncm(), which is unsigned. A malicious host can supply a large offset that overflows the signed ndp_index, making it negative. Because ndp_index is compared against unsigned bounds, this negative value bypasses sanity checks and leads to an out-of-bounds read when calculating the address of the NDP block (ntb_ptr + ndp_index). Fix this by changing ndp_index to unsigned int to ensure consistent unsigned comparisons throughout the function. Fixes: 370af734dfaf ("usb: gadget: NCM: RX function support multiple NDPs") Cc: stable Signed-off-by: Sonali Pradhan Link: https://patch.msgid.link/20260720165654.2224591-1-sonalipradhan@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_ncm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/gadget/function/f_ncm.c b/drivers/usb/gadget/function/f_ncm.c index 64eabda2f54696..bf02545b37a22b 100644 --- a/drivers/usb/gadget/function/f_ncm.c +++ b/drivers/usb/gadget/function/f_ncm.c @@ -1171,7 +1171,7 @@ static int ncm_unwrap_ntb(struct gether *port, unsigned char *ntb_ptr = skb->data; __le16 *tmp; unsigned index, index2; - int ndp_index; + unsigned int ndp_index; unsigned dg_len, dg_len2; unsigned ndp_len; unsigned block_len; From 7e22c9f79b200672f3e477421b6c9050d8cf70a5 Mon Sep 17 00:00:00 2001 From: Jiangshan Yi Date: Wed, 22 Jul 2026 18:18:10 +0800 Subject: [PATCH 198/612] usb: misc: usbio: check ibuf_len against rxbuf_len in bulk msg MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ibuf_len is the bulk IN (receive) buffer size, but the EMSGSIZE check in usbio_bulk_msg() compares it against txbuf_len — the bulk OUT endpoint size. Both are taken independently from different endpoints in usbio_probe(), so the check is wrong when they differ. Use rxbuf_len for the IN direction. This matches the buffer that actually holds the response data. Fixes: 121a0f839dbb ("usb: misc: Add Intel USBIO bridge driver") Cc: stable Signed-off-by: Jiangshan Yi Tested-by: Antti Laakso Link: https://patch.msgid.link/20260722101810.458634-1-yijiangshan@kylinos.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/usbio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/misc/usbio.c b/drivers/usb/misc/usbio.c index 3c2474dca8101c..fe093e7760d5ef 100644 --- a/drivers/usb/misc/usbio.c +++ b/drivers/usb/misc/usbio.c @@ -265,7 +265,7 @@ int usbio_bulk_msg(struct auxiliary_device *adev, u8 type, u8 cmd, bool last, lockdep_assert_held(&usbio->bulk_mutex); if ((obuf_len > (usbio->txbuf_len - sizeof(*bpkt))) || - (ibuf_len > (usbio->txbuf_len - sizeof(*bpkt)))) + (ibuf_len > (usbio->rxbuf_len - sizeof(*bpkt)))) return -EMSGSIZE; if (ibuf_len) From c2f811314be351d86b6ab41e9297ae80d8da6f86 Mon Sep 17 00:00:00 2001 From: Aleksandr Nogikh Date: Fri, 31 Jul 2026 10:15:20 +0000 Subject: [PATCH 199/612] usb: atm: cxacru: properly kill rcv_urb on error in cxacru_cm() If cxacru_cm() encounters an error while submitting or waiting for snd_urb, it aborts and returns the error without killing the already submitted rcv_urb. This leaves the rcv_urb active. When this happens during initialization (e.g., in cxacru_atm_start()), the driver may ignore the error and proceed to call cxacru_poll_status(), which invokes cxacru_cm() again. Attempting to submit the still-active rcv_urb triggers a warning in usb_submit_urb(): cxacru 1-1:1.0: send of cm 0x84 failed (-104) ATM dev 0: cxacru_atm_start: CHIP_ADSL_LINE_START returned -104 ------------[ cut here ]------------ URB ffff88812658d200 submitted while active WARNING: drivers/usb/core/urb.c:379 at usb_submit_urb+0x79/0x18b0 drivers/usb/core/urb.c:379 ... Call Trace: cxacru_cm+0x21a/0xf10 drivers/usb/atm/cxacru.c:631 cxacru_cm_get_array drivers/usb/atm/cxacru.c:722 [inline] cxacru_poll_status+0x178/0x1110 drivers/usb/atm/cxacru.c:828 cxacru_atm_start+0x185/0x360 drivers/usb/atm/cxacru.c:814 usbatm_atm_init+0x144/0x3a0 drivers/usb/atm/usbatm.c:927 usbatm_usb_probe+0x15cb/0x1db0 drivers/usb/atm/usbatm.c:1178 cxacru_usb_probe+0x17f/0x220 drivers/usb/atm/cxacru.c:1370 ... To fix this, ensure that rcv_urb is properly killed if cxacru_cm() aborts early. We can safely call usb_kill_urb() on rcv_urb in the error path, as it is safe to call even if the URB is not active (e.g., if it failed to submit in the first place, or if it already completed). Fixes: 1b0e61465234 ("[PATCH] USB ATM: driver for the Conexant AccessRunner chipset cxacru") Cc: stable Assisted-by: Gemini:gemini-3.5-flash Gemini:gemini-3.1-pro-preview syzbot Reported-by: syzbot+c9dff578c3a41775176a@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=c9dff578c3a41775176a Link: https://syzkaller.appspot.com/ai_job?id=75fec6f2-c8a6-43b1-b184-4d26baba86cc Signed-off-by: Aleksandr Nogikh Link: https://patch.msgid.link/91edfa4c-a63d-400c-9f00-31f3e1f98c00@mail.kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/atm/cxacru.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/atm/cxacru.c b/drivers/usb/atm/cxacru.c index f1900c567ba414..429ac20a8999f5 100644 --- a/drivers/usb/atm/cxacru.c +++ b/drivers/usb/atm/cxacru.c @@ -700,6 +700,8 @@ static int cxacru_cm(struct cxacru_data *instance, enum cxacru_cm_request cm, ret = offd; usb_dbg(instance->usbatm, "cm %#x\n", cm); fail: + if (ret < 0) + usb_kill_urb(instance->rcv_urb); mutex_unlock(&instance->cm_serialize); err: return ret; From 152f174a13618bec1f842d2deb69245cb2ace51f Mon Sep 17 00:00:00 2001 From: Nikhil Solanke Date: Wed, 29 Jul 2026 01:21:57 +0530 Subject: [PATCH 200/612] usb: core: Add quirk for 255-bytes initial config read Certain third-party USB game controllers exposing (or spoofing) an Xbox 360-compatible interface (VID:PID 045e:028e) fail to enumerate under Linux. The device disconnects from the bus without responding to the initial GET_DESCRIPTOR(CONFIGURATION) request, and the kernel logs 'unable to read config index 0 descriptor/start: -71'. The device then falls back to a secondary Android HID mode (with a different VID:PID), losing XInput functionality including rumble support. The failure reproduces across multiple machines, host controller types, and kernel versions including current mainline and LTS. The device enumerates correctly and remains in XInput mode under Windows. Notably, the device enumerates correctly in Android mode when the same 9-byte request is issued for that mode's configuration descriptor, confirming the firmware bug is specific to the XInput mode. usbmon traces from Linux and Wireshark/USBPcap traces from Windows are identical up to the point of failure, with no visible protocol-level difference explaining the divergence. The root cause was identified when Michal Pecio discovered via a QEMU bus-level capture that Windows does not use wLength=9 for the initial config descriptor request; it uses wLength=255. Alan Stern subsequently confirmed this with a bus analyzer on a different USB 2.0 device, and Michal verified the behavior goes back to Windows 95 OSR2.1. So, add a new quirk flag USB_QUIRK_WINDOWS_CONFIG_REQ_SIZE which causes usb_get_configuration() to issue a 255 byte sized configuration request instead of USB_DT_CONFIG_SIZE (9) for the initial GET_DESCRIPTOR(CONFIGURATION) request, mimicking long-standing Windows behavior. This patch intentionally does not add any new VID:PID entries using this quirk. Some affected Xbox 360-compatible controllers spoof Microsoft's VID:PID, while genuine Microsoft controllers already enumerate correctly and do not require this quirk. Other affected clone devices use their own VID:PID pairs and can be added individually as they are identified. Suggested-by: Alan Stern Suggested-by: Michal Pecio Closes: https://lore.kernel.org/linux-usb/CAFgddh+JWdT4LLwMc5qjM8q_pBu-fRo2qADR5ovAKoGHWMQrRw@mail.gmail.com/ Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable Acked-by: Alan Stern Signed-off-by: Nikhil Solanke Link: https://patch.msgid.link/20260728195158.65162-2-nikhilsolanke5@gmail.com Signed-off-by: Greg Kroah-Hartman --- .../admin-guide/kernel-parameters.txt | 5 +++ drivers/usb/core/config.c | 32 ++++++++++++++----- drivers/usb/core/quirks.c | 4 +++ include/linux/usb/quirks.h | 3 ++ 4 files changed, 36 insertions(+), 8 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index b5493a7f8f2281..3d35270dddefc5 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -8169,6 +8169,11 @@ Kernel parameters q = USB_QUIRK_FORCE_ONE_CONFIG (Device claims zero configurations, forcing to 1); + r = USB_QUIRK_WINDOWS_CONFIG_REQ_SIZE (Device + fails during initialization when asked for + 9-bytes configuration descriptor request. + Ask for 255-bytes request instead to mirror + Windows' behavior); Example: quirks=0781:5580:bk,0a5c:5834:gij usbhid.mousepoll= diff --git a/drivers/usb/core/config.c b/drivers/usb/core/config.c index 45e20c6d76c04c..346a2faa9bb862 100644 --- a/drivers/usb/core/config.c +++ b/drivers/usb/core/config.c @@ -912,6 +912,18 @@ int usb_get_configuration(struct usb_device *dev) unsigned char *bigbuffer; struct usb_config_descriptor *desc; int result; + size_t usb_config_req_size; + + /* + * We usually start by grabbing the first 9-bytes descriptor so we know + * how long the whole configuration is. Some devices with quirky + * firmware will fail enumeration, so if the quirk is set, use 255 instead, + * mirroring the behavior of Windows. + */ + if (dev->quirks & USB_QUIRK_WINDOWS_CONFIG_REQ_SIZE) + usb_config_req_size = 255; + else + usb_config_req_size = USB_DT_CONFIG_SIZE; if (ncfg > USB_MAXCONFIG) { dev_notice(ddev, "too many configurations: %d, " @@ -938,15 +950,13 @@ int usb_get_configuration(struct usb_device *dev) if (!dev->rawdescriptors) return -ENOMEM; - desc = kmalloc(USB_DT_CONFIG_SIZE, GFP_KERNEL); + desc = kmalloc(usb_config_req_size, GFP_KERNEL); if (!desc) return -ENOMEM; for (cfgno = 0; cfgno < ncfg; cfgno++) { - /* We grab just the first descriptor so we know how long - * the whole configuration is */ result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno, - desc, USB_DT_CONFIG_SIZE); + desc, usb_config_req_size); if (result < 0) { dev_err(ddev, "unable to read config index %d " "descriptor/%s: %d\n", cfgno, "start", result); @@ -956,16 +966,14 @@ int usb_get_configuration(struct usb_device *dev) dev->descriptor.bNumConfigurations = cfgno; break; } else if (result < 4) { - dev_err(ddev, "config index %d descriptor too short " - "(expected %i, got %i)\n", cfgno, - USB_DT_CONFIG_SIZE, result); + dev_err(ddev, "config index %d descriptor too short (asked for %zu, got %i)\n", + cfgno, usb_config_req_size, result); result = -EINVAL; goto err; } length = max_t(int, le16_to_cpu(desc->wTotalLength), USB_DT_CONFIG_SIZE); - /* Now that we know the length, get the whole thing */ bigbuffer = kmalloc(length, GFP_KERNEL); if (!bigbuffer) { result = -ENOMEM; @@ -975,6 +983,13 @@ int usb_get_configuration(struct usb_device *dev) if (dev->quirks & USB_QUIRK_DELAY_INIT) msleep(200); + /* Skip the second read if we already got everything */ + if (result >= length) { + memcpy(bigbuffer, desc, length); + goto store_and_parse; + } + + /* Get the whole thing */ result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno, bigbuffer, length); if (result < 0) { @@ -989,6 +1004,7 @@ int usb_get_configuration(struct usb_device *dev) length = result; } +store_and_parse: dev->rawdescriptors[cfgno] = bigbuffer; result = usb_parse_configuration(dev, cfgno, diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c index 87ee2d938bc038..f5a60ccf21d3d4 100644 --- a/drivers/usb/core/quirks.c +++ b/drivers/usb/core/quirks.c @@ -142,6 +142,10 @@ static int quirks_param_set(const char *value, const struct kernel_param *kp) break; case 'q': flags |= USB_QUIRK_FORCE_ONE_CONFIG; + break; + case 'r': + flags |= USB_QUIRK_WINDOWS_CONFIG_REQ_SIZE; + break; /* Ignore unrecognized flag characters */ } } diff --git a/include/linux/usb/quirks.h b/include/linux/usb/quirks.h index b3cc7beab4a3ce..a4043b33c2c2a5 100644 --- a/include/linux/usb/quirks.h +++ b/include/linux/usb/quirks.h @@ -81,4 +81,7 @@ /* Device claims zero configurations, forcing to 1 */ #define USB_QUIRK_FORCE_ONE_CONFIG BIT(18) +/* Use a 255 bytes config descriptor request mirroring windows behavior */ +#define USB_QUIRK_WINDOWS_CONFIG_REQ_SIZE BIT(19) + #endif /* __LINUX_USB_QUIRKS_H */ From 37a6e2f9c30245d06de4d5755a9d8cc1de3923f6 Mon Sep 17 00:00:00 2001 From: Nikhil Solanke Date: Wed, 29 Jul 2026 01:21:58 +0530 Subject: [PATCH 201/612] usb: hub: Split announce_device() to log device identity before enumeration announce_device() currently logs the device VID:PID and string descriptors only after successful enumeration. This means that if enumeration fails, no identifying information about the device appears in the kernel log, making it difficult to diagnose failures. Split announce_device() into announce_device_ids(), which logs the VID:PID and bcdDevice immediately after the device descriptor is read, and announce_device_strings(), which logs the product, manufacturer, and serial number strings after successful enumeration. This ensures that a device's identity is always visible in the log regardless of whether enumeration succeeds or fails. Suggested-by: Michal Pecio Acked-by: Alan Stern Signed-off-by: Nikhil Solanke Link: https://patch.msgid.link/20260728195158.65162-3-nikhilsolanke5@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 5262e11c12cd3a..d92bf887739d08 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -2401,7 +2401,7 @@ static void show_string(struct usb_device *udev, char *id, char *string) dev_info(&udev->dev, "%s: %s\n", id, string); } -static void announce_device(struct usb_device *udev) +static void announce_device_ids(struct usb_device *udev) { u16 bcdDevice = le16_to_cpu(udev->descriptor.bcdDevice); @@ -2410,6 +2410,10 @@ static void announce_device(struct usb_device *udev) le16_to_cpu(udev->descriptor.idVendor), le16_to_cpu(udev->descriptor.idProduct), bcdDevice >> 8, bcdDevice & 0xff); +} + +static void announce_device_strings(struct usb_device *udev) +{ dev_info(&udev->dev, "New USB device strings: Mfr=%d, Product=%d, SerialNumber=%d\n", udev->descriptor.iManufacturer, @@ -2420,7 +2424,8 @@ static void announce_device(struct usb_device *udev) show_string(udev, "SerialNumber", udev->serial); } #else -static inline void announce_device(struct usb_device *udev) { } +static inline void announce_device_ids(struct usb_device *udev) { } +static inline void announce_device_strings(struct usb_device *udev) { } #endif @@ -2651,6 +2656,9 @@ int usb_new_device(struct usb_device *udev) device_init_wakeup(&udev->dev, 0); } + /* Announce the device identity */ + announce_device_ids(udev); + /* Tell the runtime-PM framework the device is active */ pm_runtime_set_active(&udev->dev); pm_runtime_get_noresume(&udev->dev); @@ -2672,8 +2680,8 @@ int usb_new_device(struct usb_device *udev) udev->dev.devt = MKDEV(USB_DEVICE_MAJOR, (((udev->bus->busnum-1) * 128) + (udev->devnum-1))); - /* Tell the world! */ - announce_device(udev); + /* Announce the device's product, manufacturer and serial number */ + announce_device_strings(udev); if (udev->serial) add_device_randomness(udev->serial, strlen(udev->serial)); From f3988e68fc089f6a5883f4f807955a3825bb7d45 Mon Sep 17 00:00:00 2001 From: Ishaan Dandekar Date: Sun, 2 Aug 2026 17:31:29 +0530 Subject: [PATCH 202/612] usb: quirks: Add ShanWan gamepad to quirk list The ShanWan Wireless Gamepad (dongle ID 2563:0575) crashes with a -71 EPROTO error during standard enumeration because it expects a 255-byte initial configuration request. Add this device to the quirk list to use the USB_QUIRK_WINDOWS_CONFIG_REQ_SIZE flag. Signed-off-by: Ishaan Dandekar Cc: stable Link: https://patch.msgid.link/20260802120128.38302-1-ishaan.dandekar@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/quirks.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c index f5a60ccf21d3d4..b5b577f0b931ca 100644 --- a/drivers/usb/core/quirks.c +++ b/drivers/usb/core/quirks.c @@ -593,6 +593,9 @@ static const struct usb_device_id usb_quirk_list[] = { { USB_DEVICE(0x2386, 0x350e), .driver_info = USB_QUIRK_NO_LPM }, + /* ShanWan Wireless Gamepad */ + { USB_DEVICE(0x2563, 0x0575), .driver_info = USB_QUIRK_WINDOWS_CONFIG_REQ_SIZE }, + /* UGREEN 35871 - BOS descriptor fetch hangs at SuperSpeed Plus */ { USB_DEVICE(0x2b89, 0x5871), .driver_info = USB_QUIRK_NO_BOS }, From 0710dd08824a6f3b9892fc5be24acd2e4a36f178 Mon Sep 17 00:00:00 2001 From: Anatolii Shumak Date: Sat, 1 Aug 2026 08:19:52 +0300 Subject: [PATCH 203/612] smb: compress: reject Pattern_V1 when not negotiated Pattern_V1 is an optional chained payload type selected during SMB 3.1.1 compression negotiate. conn->compress_pattern was only consulted when building responses, so a peer that negotiated LZ77 with chained support could still submit Pattern payloads on the receive path. Pass allow_pattern through smb_compression_decompress() and reject SMB3_COMPRESS_PATTERN in the chained decoder when it is false. Link: https://github.com/namjaejeon/ksmbd/issues/529 Fixes: a08de24c2b85 ("ksmbd: negotiate and decode SMB2 compression") Signed-off-by: Anatolii Shumak Reviewed-by: ChenXiaoSong Acked-by: Namjae Jeon Signed-off-by: Steve French --- fs/smb/common/compress/compress.c | 11 ++++++++--- fs/smb/common/compress/compress.h | 3 ++- fs/smb/server/compress.c | 1 + 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/fs/smb/common/compress/compress.c b/fs/smb/common/compress/compress.c index b07a317597a4cf..a4123c8f1c0ab3 100644 --- a/fs/smb/common/compress/compress.c +++ b/fs/smb/common/compress/compress.c @@ -95,6 +95,7 @@ static int smb_decompress_lz77_payload(const u8 **src, u32 *slen, u8 **dst, } static int smb_decompress_chained(__le16 alg, bool allow_chained, + bool allow_pattern, const struct smb2_compression_hdr *hdr, u32 slen, void *dst, u32 dlen) { @@ -143,6 +144,8 @@ static int smb_decompress_chained(__le16 alg, bool allow_chained, rc = smb_decompress_none(&src, &remaining, &out, &out_remaining, len); } else if (payload_alg == SMB3_COMPRESS_PATTERN) { + if (!allow_pattern) + return -EINVAL; rc = smb_decompress_pattern(&src, &remaining, &out, &out_remaining, len); } else if (payload_alg == alg && alg == SMB3_COMPRESS_LZ77) { @@ -185,6 +188,7 @@ static int smb_decompress_unchained(__le16 alg, * smb_compression_decompress() - decode an SMB2 compression transform * @alg: negotiated general-purpose compression algorithm * @allow_chained: whether chained transforms were negotiated + * @allow_pattern: whether Pattern_V1 payloads were negotiated * @src: transform header followed by compressed payload data * @slen: total number of bytes available at @src * @dst: output buffer for the reconstructed SMB2 message @@ -197,7 +201,8 @@ static int smb_decompress_unchained(__le16 alg, * Return: 0 on success, otherwise a negative errno. */ int smb_compression_decompress(__le16 alg, bool allow_chained, - const void *src, u32 slen, void *dst, u32 dlen) + bool allow_pattern, const void *src, u32 slen, + void *dst, u32 dlen) { const struct smb2_compression_hdr *hdr = src; @@ -207,8 +212,8 @@ int smb_compression_decompress(__le16 alg, bool allow_chained, return -EINVAL; if (hdr->Flags == cpu_to_le16(SMB2_COMPRESSION_FLAG_CHAINED)) - return smb_decompress_chained(alg, allow_chained, hdr, slen, - dst, dlen); + return smb_decompress_chained(alg, allow_chained, allow_pattern, + hdr, slen, dst, dlen); if (hdr->Flags != cpu_to_le16(SMB2_COMPRESSION_FLAG_NONE)) return -EINVAL; diff --git a/fs/smb/common/compress/compress.h b/fs/smb/common/compress/compress.h index 7ace3bf4b664ca..d6916669f88715 100644 --- a/fs/smb/common/compress/compress.h +++ b/fs/smb/common/compress/compress.h @@ -20,7 +20,8 @@ static __always_inline bool smb_compress_alg_valid(__le16 alg, bool valid_none) } int smb_compression_decompress(__le16 alg, bool allow_chained, - const void *src, u32 slen, void *dst, u32 dlen); + bool allow_pattern, const void *src, u32 slen, + void *dst, u32 dlen); int smb_compression_compress_chained(__le16 alg, bool allow_pattern, const void *src, u32 slen, void *dst, u32 *dlen); diff --git a/fs/smb/server/compress.c b/fs/smb/server/compress.c index 95e48fa6b4486b..821299888ad3ad 100644 --- a/fs/smb/server/compress.c +++ b/fs/smb/server/compress.c @@ -69,6 +69,7 @@ int ksmbd_decompress_request(struct ksmbd_conn *conn) *(__be32 *)out = cpu_to_be32(out_size); rc = smb_compression_decompress(conn->compress_algorithm, conn->compress_chained, + conn->compress_pattern, buf, pdu_size, out + 4, out_size); if (rc) { kvfree(out); From ba3afa8ccd154962c4a6b975b6e8b11027c5ab95 Mon Sep 17 00:00:00 2001 From: Anatolii Shumak Date: Sat, 1 Aug 2026 08:19:53 +0300 Subject: [PATCH 204/612] ksmbd: validate compression Flags before kvmalloc ksmbd_decompress_request() allocated the decompressed request buffer before smb_compression_decompress() rejected unknown transform Flags or chained mode when it was not negotiated. A remote peer could force a transient multi-megabyte allocation that was immediately freed on -EINVAL. Validate CHAINED/NONE Flags and compress_chained before kvmalloc. Link: https://github.com/namjaejeon/ksmbd/issues/529 Fixes: a08de24c2b85 ("ksmbd: negotiate and decode SMB2 compression") Signed-off-by: Anatolii Shumak Reviewed-by: ChenXiaoSong Acked-by: Namjae Jeon Signed-off-by: Steve French --- fs/smb/server/compress.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/fs/smb/server/compress.c b/fs/smb/server/compress.c index 821299888ad3ad..7e13cae705e2f5 100644 --- a/fs/smb/server/compress.c +++ b/fs/smb/server/compress.c @@ -46,13 +46,22 @@ int ksmbd_decompress_request(struct ksmbd_conn *conn) return -EINVAL; orig_size = le32_to_cpu(hdr->OriginalCompressedSegmentSize); + /* + * For chained transforms the top-level header is only eight bytes; the + * Flags field overlays the first payload header. Reject unknown Flags + * and unnegotiated chained mode before allocating the output buffer. + */ if (hdr->Flags == cpu_to_le16(SMB2_COMPRESSION_FLAG_CHAINED)) { + if (!conn->compress_chained) + return -EINVAL; out_size = orig_size; - } else { + } else if (hdr->Flags == cpu_to_le16(SMB2_COMPRESSION_FLAG_NONE)) { offset = le32_to_cpu(hdr->Offset); if (offset > pdu_size - sizeof(*hdr) || check_add_overflow(orig_size, offset, &out_size)) return -EINVAL; + } else { + return -EINVAL; } max_allowed_pdu_size = SMB3_MAX_MSGSIZE + conn->vals->max_write_size; From ab88cb66cb0028cb8038b64c2fa71b0f3e91d5f2 Mon Sep 17 00:00:00 2001 From: Sujal Tuladhar Date: Sun, 2 Aug 2026 00:53:03 +0900 Subject: [PATCH 205/612] ksmbd: apply the pre-authentication PDU limit when decompressing ksmbd_conn_handler_loop() caps a request from an unauthenticated connection at SMB3_MAX_MSGSIZE, and only allows the larger SMB3_MAX_MSGSIZE + conn->vals->max_write_size once the connection has authenticated. ksmbd_decompress_request() runs inside that same loop but applies the authenticated limit unconditionally, and then allocates from it. The unauthenticated cap is not re-applied afterwards, as the caller only refreshes pdu_size from the new RFC1002 header. An unauthenticated client that negotiates SMB 3.1.1 with a compression context can therefore send a 104 byte chained transform whose OriginalCompressedSegmentSize is SMB3_MAX_MSGSIZE + max_write_size and have ksmbd kvmalloc() that much memory, 4210693 bytes by default. The payload costs the client nothing, because a SMB3_COMPRESS_PATTERN payload expands an eight byte structure into arbitrarily many output bytes. The decompressed PDU is rejected later by ksmbd_smb2_check_message(), but that happens in the worker, after the allocation has been made and conn->req_running has been incremented, and it results in an error response rather than dropping the connection. A client that stops reading its socket keeps each work queued for up to KSMBD_TCP_SEND_TIMEOUT while ksmbd_conn_write() holds conn->srv_mutex, so the allocations accumulate up to server_conf.max_inflight_req per connection. Move the limit into ksmbd_max_allowed_pdu_size() and call it from both sites, so the authentication state is consulted in one place and the two ceilings cannot drift apart again. Fixes: a08de24c2b85 ("ksmbd: negotiate and decode SMB2 compression") Signed-off-by: Sujal Tuladhar Acked-by: Namjae Jeon Signed-off-by: Steve French --- fs/smb/server/compress.c | 2 +- fs/smb/server/connection.c | 6 +----- fs/smb/server/connection.h | 9 +++++++++ 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/fs/smb/server/compress.c b/fs/smb/server/compress.c index 7e13cae705e2f5..01d1771ff6636b 100644 --- a/fs/smb/server/compress.c +++ b/fs/smb/server/compress.c @@ -64,7 +64,7 @@ int ksmbd_decompress_request(struct ksmbd_conn *conn) return -EINVAL; } - max_allowed_pdu_size = SMB3_MAX_MSGSIZE + conn->vals->max_write_size; + max_allowed_pdu_size = ksmbd_max_allowed_pdu_size(conn); if (out_size < sizeof(struct smb2_pdu) || out_size > max_allowed_pdu_size || out_size > MAX_STREAM_PROT_LEN) diff --git a/fs/smb/server/connection.c b/fs/smb/server/connection.c index dee8e4aced9946..ef6f202f4024c0 100644 --- a/fs/smb/server/connection.c +++ b/fs/smb/server/connection.c @@ -488,11 +488,7 @@ int ksmbd_conn_handler_loop(void *p) pdu_size = get_rfc1002_len(hdr_buf); ksmbd_debug(CONN, "RFC1002 header %u bytes\n", pdu_size); - if (ksmbd_conn_good(conn)) - max_allowed_pdu_size = - SMB3_MAX_MSGSIZE + conn->vals->max_write_size; - else - max_allowed_pdu_size = SMB3_MAX_MSGSIZE; + max_allowed_pdu_size = ksmbd_max_allowed_pdu_size(conn); if (pdu_size > max_allowed_pdu_size) { pr_err_ratelimited("PDU length(%u) exceeded maximum allowed pdu size(%u) on connection(%d)\n", diff --git a/fs/smb/server/connection.h b/fs/smb/server/connection.h index 2a194ee36fb40c..0e4ebfac555889 100644 --- a/fs/smb/server/connection.h +++ b/fs/smb/server/connection.h @@ -210,6 +210,15 @@ static inline bool ksmbd_conn_good(struct ksmbd_conn *conn) return READ_ONCE(conn->status) == KSMBD_SESS_GOOD; } +static inline unsigned int +ksmbd_max_allowed_pdu_size(struct ksmbd_conn *conn) +{ + if (ksmbd_conn_good(conn)) + return SMB3_MAX_MSGSIZE + conn->vals->max_write_size; + + return SMB3_MAX_MSGSIZE; +} + static inline bool ksmbd_conn_need_negotiate(struct ksmbd_conn *conn) { return READ_ONCE(conn->status) == KSMBD_SESS_NEED_NEGOTIATE; From 1ed35ac7f3fe2b4396bdd29ac3a7f0ebc0829e94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linfeng=20Sun=C2=A0?= Date: Sat, 20 Jun 2026 21:00:05 +0800 Subject: [PATCH 206/612] vhost_iotlb: bound map allocation in add_range MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vhost_iotlb_add_range_ctx() only retires an old entry when the table has a non-zero limit, has exactly reached that limit and has VHOST_IOTLB_FLAG_RETIRE set. Non-retiring tables can keep allocating entries after reaching their configured limit. Existing vhost devices allocate their IOTLB with max_iotlb_entries from vhost.c, which defaults to 2048 and is tunable by module parameter. Use the caller-provided limit at the allocation point instead of adding a separate default in the common IOTLB helper, and reject non-positive values in vhost paths that can report an error. Other vhost IOTLB users should not create zero-limit tables when entries can be populated from userspace or guest-controlled requests. Add caller-side max_iotlb_entries parameters for mlx5 vDPA, VDUSE and vhost-vDPA. Reject non-positive VDUSE and vhost-vDPA values, and require at least two entries for vdpa_sim and mlx5 vDPA paths that install full-range mappings, since those mappings are split into two IOTLB entries. Handle full-range mappings in the common helper by checking that the IOTLB can hold both split entries before inserting the first half. This avoids returning an error after leaving a half mapping behind. When the table is full, keep the existing retire behavior for retiring tables and return -ENOSPC for non-retiring tables. Reuse the retired map node instead of freeing it and allocating a replacement, so a stream of IOTLB updates cannot keep forcing GFP_ATOMIC allocations after the table has reached its limit. If a zero-limit IOTLB still reaches the common helper, treat it as a configuration error and return -EINVAL. I found this bug myself, though the patch was written with AI assistance. Fixes: 0bbe30668d89 ("vhost: factor out IOTLB") Assisted-by: OpenAI-Codex:GPT-5 Signed-off-by: Linfeng Sun  Message-ID: Signed-off-by: Michael S. Tsirkin --- drivers/vdpa/mlx5/core/mlx5_vdpa.h | 2 ++ drivers/vdpa/mlx5/core/mr.c | 5 ++- drivers/vdpa/mlx5/core/resources.c | 11 ++++++- drivers/vdpa/vdpa_sim/vdpa_sim.c | 10 ++++-- drivers/vdpa/vdpa_user/iova_domain.c | 11 ++++++- drivers/vhost/iotlb.c | 47 +++++++++++++++++++--------- drivers/vhost/vdpa.c | 9 +++++- drivers/vhost/vhost.c | 8 +++++ 8 files changed, 82 insertions(+), 21 deletions(-) diff --git a/drivers/vdpa/mlx5/core/mlx5_vdpa.h b/drivers/vdpa/mlx5/core/mlx5_vdpa.h index 2cedf7e2dbc495..42f2f44b383c70 100644 --- a/drivers/vdpa/mlx5/core/mlx5_vdpa.h +++ b/drivers/vdpa/mlx5/core/mlx5_vdpa.h @@ -11,6 +11,8 @@ #define MLX5V_ETH_HARD_MTU (ETH_HLEN + VLAN_HLEN + ETH_FCS_LEN) +extern int mlx5_vdpa_max_iotlb_entries; + struct mlx5_vdpa_direct_mr { u64 start; u64 end; diff --git a/drivers/vdpa/mlx5/core/mr.c b/drivers/vdpa/mlx5/core/mr.c index 6d02ccf9eb9104..7d681961a5ccea 100644 --- a/drivers/vdpa/mlx5/core/mr.c +++ b/drivers/vdpa/mlx5/core/mr.c @@ -776,6 +776,9 @@ static int _mlx5_vdpa_create_mr(struct mlx5_vdpa_dev *mvdev, { int err; + if (mlx5_vdpa_max_iotlb_entries < 2) + return -EINVAL; + if (iotlb) err = create_user_mr(mvdev, mr, iotlb); else @@ -784,7 +787,7 @@ static int _mlx5_vdpa_create_mr(struct mlx5_vdpa_dev *mvdev, if (err) return err; - mr->iotlb = vhost_iotlb_alloc(0, 0); + mr->iotlb = vhost_iotlb_alloc(mlx5_vdpa_max_iotlb_entries, 0); if (!mr->iotlb) { err = -ENOMEM; goto err_mr; diff --git a/drivers/vdpa/mlx5/core/resources.c b/drivers/vdpa/mlx5/core/resources.c index aeae31d0cefaee..28a4d7a35bf4e4 100644 --- a/drivers/vdpa/mlx5/core/resources.c +++ b/drivers/vdpa/mlx5/core/resources.c @@ -3,8 +3,14 @@ #include #include +#include #include "mlx5_vdpa.h" +int mlx5_vdpa_max_iotlb_entries = 2048; +module_param_named(max_iotlb_entries, mlx5_vdpa_max_iotlb_entries, int, 0444); +MODULE_PARM_DESC(max_iotlb_entries, + "Maximum number of iotlb entries. (default: 2048)"); + static int alloc_pd(struct mlx5_vdpa_dev *dev, u32 *pdn, u16 uid) { struct mlx5_core_dev *mdev = dev->mdev; @@ -229,7 +235,10 @@ int mlx5_vdpa_destroy_mkey(struct mlx5_vdpa_dev *mvdev, u32 mkey) static int init_ctrl_vq(struct mlx5_vdpa_dev *mvdev) { - mvdev->cvq.iotlb = vhost_iotlb_alloc(0, 0); + if (mlx5_vdpa_max_iotlb_entries < 2) + return -EINVAL; + + mvdev->cvq.iotlb = vhost_iotlb_alloc(mlx5_vdpa_max_iotlb_entries, 0); if (!mvdev->cvq.iotlb) return -ENOMEM; diff --git a/drivers/vdpa/vdpa_sim/vdpa_sim.c b/drivers/vdpa/vdpa_sim/vdpa_sim.c index 8cb1cc2ea1391a..4d116644851d92 100644 --- a/drivers/vdpa/vdpa_sim/vdpa_sim.c +++ b/drivers/vdpa/vdpa_sim/vdpa_sim.c @@ -34,7 +34,7 @@ MODULE_PARM_DESC(batch_mapping, "Batched mapping 1 -Enable; 0 - Disable"); static int max_iotlb_entries = 2048; module_param(max_iotlb_entries, int, 0444); MODULE_PARM_DESC(max_iotlb_entries, - "Maximum number of iotlb entries for each address space. 0 means unlimited. (default: 2048)"); + "Maximum number of iotlb entries for each address space. (default: 2048)"); static bool use_va = true; module_param(use_va, bool, 0444); @@ -201,6 +201,8 @@ struct vdpasim *vdpasim_create(struct vdpasim_dev_attr *dev_attr, if (!dev_attr->alloc_size) return ERR_PTR(-EINVAL); + if (max_iotlb_entries < 2) + return ERR_PTR(-EINVAL); if (config->mask & BIT_ULL(VDPA_ATTR_DEV_FEATURES)) { if (config->device_features & @@ -261,8 +263,10 @@ struct vdpasim *vdpasim_create(struct vdpasim_dev_attr *dev_attr, for (i = 0; i < vdpasim->dev_attr.nas; i++) { vhost_iotlb_init(&vdpasim->iommu[i], max_iotlb_entries, 0); - vhost_iotlb_add_range(&vdpasim->iommu[i], 0, ULONG_MAX, 0, - VHOST_MAP_RW); + ret = vhost_iotlb_add_range(&vdpasim->iommu[i], 0, ULONG_MAX, + 0, VHOST_MAP_RW); + if (ret) + goto err_iommu; vdpasim->iommu_pt[i] = true; } diff --git a/drivers/vdpa/vdpa_user/iova_domain.c b/drivers/vdpa/vdpa_user/iova_domain.c index 4dc76c0d0d13d9..b6c958224b7ceb 100644 --- a/drivers/vdpa/vdpa_user/iova_domain.c +++ b/drivers/vdpa/vdpa_user/iova_domain.c @@ -12,11 +12,17 @@ #include #include #include +#include #include #include #include "iova_domain.h" +static int max_iotlb_entries = 2048; +module_param(max_iotlb_entries, int, 0444); +MODULE_PARM_DESC(max_iotlb_entries, + "Maximum number of iotlb entries. (default: 2048)"); + static int vduse_iotlb_add_range(struct vduse_iova_domain *domain, u64 start, u64 last, u64 addr, unsigned int perm, @@ -622,11 +628,14 @@ vduse_domain_create(unsigned long iova_limit, size_t bounce_size) if (iova_limit <= bounce_size) return NULL; + if (max_iotlb_entries <= 0) + return NULL; + domain = kzalloc_obj(*domain); if (!domain) return NULL; - domain->iotlb = vhost_iotlb_alloc(0, 0); + domain->iotlb = vhost_iotlb_alloc(max_iotlb_entries, 0); if (!domain->iotlb) goto err_iotlb; diff --git a/drivers/vhost/iotlb.c b/drivers/vhost/iotlb.c index e1414c774c344c..a1d4376a5b8722 100644 --- a/drivers/vhost/iotlb.c +++ b/drivers/vhost/iotlb.c @@ -20,6 +20,14 @@ INTERVAL_TREE_DEFINE(struct vhost_iotlb_map, rb, __u64, __subtree_last, START, LAST, static inline, vhost_iotlb_itree); +static void vhost_iotlb_map_unlink(struct vhost_iotlb *iotlb, + struct vhost_iotlb_map *map) +{ + vhost_iotlb_itree_remove(map, &iotlb->root); + list_del(&map->link); + iotlb->nmaps--; +} + /** * vhost_iotlb_map_free - remove a map node and free it * @iotlb: the IOTLB @@ -28,10 +36,8 @@ INTERVAL_TREE_DEFINE(struct vhost_iotlb_map, void vhost_iotlb_map_free(struct vhost_iotlb *iotlb, struct vhost_iotlb_map *map) { - vhost_iotlb_itree_remove(map, &iotlb->root); - list_del(&map->link); + vhost_iotlb_map_unlink(iotlb, map); kfree(map); - iotlb->nmaps--; } EXPORT_SYMBOL_GPL(vhost_iotlb_map_free); @@ -57,14 +63,25 @@ int vhost_iotlb_add_range_ctx(struct vhost_iotlb *iotlb, if (last < start) return -EFAULT; + if (!iotlb->limit) + return -EINVAL; + /* If the range being mapped is [0, ULONG_MAX], split it into two entries * otherwise its size would overflow u64. */ if (start == 0 && last == ULONG_MAX) { u64 mid = last / 2; - int err = vhost_iotlb_add_range_ctx(iotlb, start, mid, addr, - perm, opaque); + int err; + + if (iotlb->limit < 2) + return -ENOSPC; + if (!(iotlb->flags & VHOST_IOTLB_FLAG_RETIRE) && + iotlb->nmaps > iotlb->limit - 2) + return -ENOSPC; + + err = vhost_iotlb_add_range_ctx(iotlb, start, mid, addr, + perm, opaque); if (err) return err; @@ -72,17 +89,19 @@ int vhost_iotlb_add_range_ctx(struct vhost_iotlb *iotlb, start = mid + 1; } - if (iotlb->limit && - iotlb->nmaps == iotlb->limit && - iotlb->flags & VHOST_IOTLB_FLAG_RETIRE) { - map = list_first_entry(&iotlb->list, typeof(*map), link); - vhost_iotlb_map_free(iotlb, map); + if (iotlb->nmaps >= iotlb->limit) { + if (iotlb->flags & VHOST_IOTLB_FLAG_RETIRE) { + map = list_first_entry(&iotlb->list, typeof(*map), link); + vhost_iotlb_map_unlink(iotlb, map); + } else { + return -ENOSPC; + } + } else { + map = kmalloc_obj(*map, GFP_ATOMIC); + if (!map) + return -ENOMEM; } - map = kmalloc_obj(*map, GFP_ATOMIC); - if (!map) - return -ENOMEM; - map->start = start; map->size = last - start + 1; map->last = last; diff --git a/drivers/vhost/vdpa.c b/drivers/vhost/vdpa.c index ac55275fa0d0ae..ef642bc9f97e11 100644 --- a/drivers/vhost/vdpa.c +++ b/drivers/vhost/vdpa.c @@ -34,6 +34,11 @@ enum { #define VHOST_VDPA_DEV_MAX (1U << MINORBITS) +static int max_iotlb_entries = 2048; +module_param(max_iotlb_entries, int, 0444); +MODULE_PARM_DESC(max_iotlb_entries, + "Maximum number of iotlb entries. (default: 2048)"); + #define VHOST_VDPA_IOTLB_BUCKETS 16 struct vhost_vdpa_as { @@ -109,12 +114,14 @@ static struct vhost_vdpa_as *vhost_vdpa_alloc_as(struct vhost_vdpa *v, u32 asid) if (asid >= v->vdpa->nas) return NULL; + if (max_iotlb_entries <= 0) + return NULL; as = kmalloc_obj(*as); if (!as) return NULL; - vhost_iotlb_init(&as->iotlb, 0, 0); + vhost_iotlb_init(&as->iotlb, max_iotlb_entries, 0); as->id = asid; hlist_add_head(&as->hash_link, head); diff --git a/drivers/vhost/vhost.c b/drivers/vhost/vhost.c index 4c525b3e16ead8..ae29a7ef7baad8 100644 --- a/drivers/vhost/vhost.c +++ b/drivers/vhost/vhost.c @@ -1137,6 +1137,9 @@ EXPORT_SYMBOL_GPL(vhost_dev_set_owner); static struct vhost_iotlb *iotlb_alloc(void) { + if (max_iotlb_entries <= 0) + return NULL; + return vhost_iotlb_alloc(max_iotlb_entries, VHOST_IOTLB_FLAG_RETIRE); } @@ -1981,6 +1984,8 @@ static long vhost_set_memory(struct vhost_dev *d, struct vhost_memory __user *m) return -EOPNOTSUPP; if (mem.nregions > max_mem_regions) return -E2BIG; + if (max_iotlb_entries <= 0) + return -EINVAL; newmem = kvzalloc_flex(*newmem, regions, mem.nregions); if (!newmem) return -ENOMEM; @@ -2275,6 +2280,9 @@ int vhost_init_device_iotlb(struct vhost_dev *d) struct vhost_iotlb *niotlb, *oiotlb; int i; + if (max_iotlb_entries <= 0) + return -EINVAL; + niotlb = iotlb_alloc(); if (!niotlb) return -ENOMEM; From 0619aaa34c0c2a2dcb07f0e9c8a34e7efb8c4cdf Mon Sep 17 00:00:00 2001 From: Yousef Alhouseen Date: Wed, 24 Jun 2026 15:02:02 -0700 Subject: [PATCH 207/612] vhost/vdpa: reject overflowing PA map page counts on 32-bit vhost_vdpa_pa_map() adds the IOVA page offset to the user-controlled map size before computing the number of pages to pin. On 32-bit systems, where unsigned long is narrower than u64, that addition can overflow and the code can pin and map fewer pages than the requested IOTLB range. Reject sizes that overflow the unsigned long page-count calculation. Fixes: 22af48cf91aa ("vdpa: factor out vhost_vdpa_pa_map() and vhost_vdpa_pa_unmap()") Acked-by: Michael S. Tsirkin Signed-off-by: Yousef Alhouseen Signed-off-by: Michael S. Tsirkin Message-ID: --- drivers/vhost/vdpa.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/vhost/vdpa.c b/drivers/vhost/vdpa.c index ef642bc9f97e11..c3d913bd7cac7c 100644 --- a/drivers/vhost/vdpa.c +++ b/drivers/vhost/vdpa.c @@ -1109,6 +1109,7 @@ static int vhost_vdpa_pa_map(struct vhost_vdpa *v, unsigned int gup_flags = FOLL_LONGTERM; unsigned long npages, cur_base, map_pfn, last_pfn = 0; unsigned long lock_limit, sz2pin, nchunks, i; + unsigned long page_offset; u64 start = iova; long pinned; int ret = 0; @@ -1121,7 +1122,13 @@ static int vhost_vdpa_pa_map(struct vhost_vdpa *v, if (perm & VHOST_ACCESS_WO) gup_flags |= FOLL_WRITE; - npages = PFN_UP(size + (iova & ~PAGE_MASK)); + page_offset = iova & ~PAGE_MASK; + if (size > ULONG_MAX - page_offset) { + ret = -EINVAL; + goto free; + } + + npages = PFN_UP(size + page_offset); if (!npages) { ret = -EINVAL; goto free; From 727e1f569855df83579edbd73dcb4a0723543a12 Mon Sep 17 00:00:00 2001 From: Christian Borntraeger Date: Mon, 6 Jul 2026 16:15:37 +0200 Subject: [PATCH 208/612] vdpa/mlx5: Fix buffer length in create_direct_keys() We have seen in our CI the following KASAN message: BUG: KASAN: slab-out-of-bounds in cmd_exec+0x550/0xca0 [mlx5_core] Read of size 272 at addr 0000000176795020 by task qemu-system-s39/82764 [...] [<000011388ab3a7a0>] cmd_exec+0x550/0xca0 [mlx5_core] [<000011388ab3b61c>] mlx5_cmd_exec_cb+0x25c/0x4f0 [mlx5_core] [<000011388b21e82e>] mlx5_vdpa_exec_async_cmds+0x22e/0x5e0 [mlx5_vdpa] [<000011388b21fd44>] create_direct_keys+0x954/0xef0 [mlx5_vdpa] [...] The buggy address is located 4128 bytes inside of allocated 4384-byte region [0000000176794000, 0000000176795120) So in essence we read 16 bytes beyond 4384-byte allocation. create_direct_keys calculates the pointer and length for in and out buffers. The size calculation for in includes the entire structure size (out + in + mtt[]) but the pointer passed to cmd_exec points only to the 'in' field, skipping the 'out' field. This causes mlx5_copy_to_msg() to read beyond the allocated buffer by sizeof(out) bytes when copying command data. Properly calculate the input size to match the pointer and allocation size. Fixes: 0071b138d44a ("vdpa/mlx5: Create direct MKEYs in parallel") Signed-off-by: Christian Borntraeger Tested-by: Dragos Tatulea Reviewed-by: Dragos Tatulea Signed-off-by: Michael S. Tsirkin Message-ID: <20260706141537.3510294-1-borntraeger@linux.ibm.com> --- drivers/vdpa/mlx5/core/mr.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/vdpa/mlx5/core/mr.c b/drivers/vdpa/mlx5/core/mr.c index 7d681961a5ccea..77a479aeaa85ae 100644 --- a/drivers/vdpa/mlx5/core/mr.c +++ b/drivers/vdpa/mlx5/core/mr.c @@ -233,7 +233,8 @@ static int create_direct_keys(struct mlx5_vdpa_dev *mvdev, struct mlx5_vdpa_mr * cmds[i].out = cmd_mem->out; cmds[i].outlen = sizeof(cmd_mem->out); cmds[i].in = cmd_mem->in; - cmds[i].inlen = struct_size(cmd_mem, mtt, mttcount); + cmds[i].inlen = struct_size(cmd_mem, mtt, mttcount) - + offsetof(struct mlx5_create_mkey_mem, in); fill_create_direct_mr(mvdev, dmr, cmd_mem); From 71638a456852a18a4578f52d51926ba1d223053b Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Mon, 3 Aug 2026 11:40:50 -0700 Subject: [PATCH 209/612] hwmon: (pmbus/core) Avoid race condition during probe pmbus_write_smbalert_mask() is not guarded, which can cause race conditions with concurrent sysfs attribute accesses. Similar, PMBus accesses in pmbus_init_debugfs() are not guarded, also resulting in potential race conditions. Add guards to both functions to prevent the races. Reported-by: Sashiko Fixes: 221819ca4c36e ("hwmon: (pmbus/core) Add interrupt support") Fixes: 1e069dfd96dfe ("hwmon: (pmbus) Add debugfs for status registers") Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/pmbus_core.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index 5567d37f13fe26..095e128709af44 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -3427,6 +3427,8 @@ static int pmbus_write_smbalert_mask(struct i2c_client *client, u8 page, u8 reg, { int ret; + guard(pmbus_lock)(client); + ret = _pmbus_write_word_data(client, page, PMBUS_SMBALERT_MASK, reg | (val << 8)); /* @@ -3662,6 +3664,8 @@ static void pmbus_init_debugfs(struct i2c_client *client, if (!entries) return; + guard(pmbus_lock)(client); + /* * Add device-specific entries. * Please note that the PMBUS standard allows all registers to be From 59bd68ab05a8f9c9a60b6ec44682084184803ff4 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Thu, 23 Jul 2026 10:57:35 -0700 Subject: [PATCH 210/612] hwmon: (pmbus) Fix type confusion in notification logic Sashiko reports: At the start of the loop in pmbus_notify(), the code unconditionally casts every attribute to a struct sensor_device_attribute: drivers/hwmon/pmbus/pmbus_core.c:pmbus_notify() { for (i = 0; i < data->num_attributes; i++) { struct device_attribute *da = to_dev_attr(data->group.attrs[i]); struct sensor_device_attribute *attr = to_sensor_dev_attr(da); int index = attr->index; ... } However, data->group.attrs can contain other types like struct pmbus_samples_reg or struct pmbus_sensor, which only embed a base struct device_attribute. If da is a struct pmbus_samples_reg, dev_attr is the last member. Casting it to struct sensor_device_attribute and reading the index field appears to access memory past the end of the allocation, which might trigger a slab-out-of-bounds read. Additionally, if da is a struct pmbus_sensor, casting it causes the index field to overlap with the page, phase, and reg fields. Could this produce a garbage mask on little-endian systems that spuriously matches the target reg, page, and flags during an alert? Fix the problem by using struct sensor_device_attr in struct pmbus_sensor and struct pmbus_label. Since those attributes never trigger a notification, set the value of attr->index to -1 for them. Use this value to distinguish from boolean attributes which _can_ trigger a notification and use the index field to encode mask, page, and register values. Fixes: f469bde9afd1 ("hwmon: (pmbus/core) Notify hwmon events") Cc: Vincent Jardin Tested-by: Vincent Jardin Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/pmbus_core.c | 61 +++++++++++++++++++------------- 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index 095e128709af44..3b219ed5386fba 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -45,7 +45,7 @@ module_param(wp, int, 0444); struct pmbus_sensor { struct pmbus_sensor *next; char name[PMBUS_NAME_SIZE]; /* sysfs sensor name */ - struct device_attribute attribute; + struct sensor_device_attribute attribute; u8 page; /* page number */ u8 phase; /* phase number, 0xff for all phases */ u16 reg; /* register */ @@ -68,7 +68,7 @@ struct pmbus_boolean { struct pmbus_label { char name[PMBUS_NAME_SIZE]; /* sysfs label name */ - struct device_attribute attribute; + struct sensor_device_attribute attribute; char label[PMBUS_NAME_SIZE]; /* label */ }; #define to_pmbus_label(_attr) \ @@ -1241,7 +1241,8 @@ static ssize_t pmbus_show_sensor(struct device *dev, struct device_attribute *devattr, char *buf) { struct i2c_client *client = to_i2c_client(dev->parent); - struct pmbus_sensor *sensor = to_pmbus_sensor(devattr); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct pmbus_sensor *sensor = to_pmbus_sensor(attr); struct pmbus_data *data = i2c_get_clientdata(client); s64 val; @@ -1261,7 +1262,8 @@ static ssize_t pmbus_set_sensor(struct device *dev, { struct i2c_client *client = to_i2c_client(dev->parent); struct pmbus_data *data = i2c_get_clientdata(client); - struct pmbus_sensor *sensor = to_pmbus_sensor(devattr); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct pmbus_sensor *sensor = to_pmbus_sensor(attr); s64 val; int ret; u16 regval; @@ -1283,7 +1285,8 @@ static ssize_t pmbus_set_sensor(struct device *dev, static ssize_t pmbus_show_label(struct device *dev, struct device_attribute *da, char *buf) { - struct pmbus_label *label = to_pmbus_label(da); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct pmbus_label *label = to_pmbus_label(attr); return sysfs_emit(buf, "%s\n", label->label); } @@ -1436,8 +1439,8 @@ static struct pmbus_sensor *pmbus_add_sensor(struct pmbus_data *data, bool update, bool readonly, bool writeonly, bool convert) { + struct sensor_device_attribute *a; struct pmbus_sensor *sensor; - struct device_attribute *a; sensor = devm_kzalloc(data->dev, sizeof(*sensor), GFP_KERNEL); if (!sensor) @@ -1461,12 +1464,11 @@ static struct pmbus_sensor *pmbus_add_sensor(struct pmbus_data *data, sensor->update = update; sensor->convert = convert; sensor->data = -ENODATA; - pmbus_dev_attr_init(a, sensor->name, - readonly ? 0444 : 0644, - writeonly ? pmbus_show_zero : pmbus_show_sensor, - pmbus_set_sensor); + pmbus_attr_init(a, sensor->name, readonly ? 0444 : 0644, + writeonly ? pmbus_show_zero : pmbus_show_sensor, + pmbus_set_sensor, -1); - if (pmbus_add_attribute(data, &a->attr)) + if (pmbus_add_attribute(data, &a->dev_attr.attr)) return NULL; sensor->next = data->sensors; @@ -1483,8 +1485,8 @@ static int pmbus_add_label(struct pmbus_data *data, const char *name, int seq, const char *lstring, int index, int phase) { + struct sensor_device_attribute *a; struct pmbus_label *label; - struct device_attribute *a; label = devm_kzalloc(data->dev, sizeof(*label), GFP_KERNEL); if (!label) @@ -1508,8 +1510,8 @@ static int pmbus_add_label(struct pmbus_data *data, lstring, index, phase); } - pmbus_dev_attr_init(a, label->name, 0444, pmbus_show_label, NULL); - return pmbus_add_attribute(data, &a->attr); + pmbus_attr_init(a, label->name, 0444, pmbus_show_label, NULL, -1); + return pmbus_add_attribute(data, &a->dev_attr.attr); } /* @@ -2397,7 +2399,7 @@ struct pmbus_samples_attr { struct pmbus_samples_reg { int page; struct pmbus_samples_attr *attr; - struct device_attribute dev_attr; + struct sensor_device_attribute attribute; }; static struct pmbus_samples_attr pmbus_samples_registers[] = { @@ -2419,14 +2421,15 @@ static struct pmbus_samples_attr pmbus_samples_registers[] = { } }; -#define to_samples_reg(x) container_of(x, struct pmbus_samples_reg, dev_attr) +#define to_samples_reg(x) container_of(x, struct pmbus_samples_reg, attribute) static ssize_t pmbus_show_samples(struct device *dev, struct device_attribute *devattr, char *buf) { int val; struct i2c_client *client = to_i2c_client(dev->parent); - struct pmbus_samples_reg *reg = to_samples_reg(devattr); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct pmbus_samples_reg *reg = to_samples_reg(attr); scoped_guard(pmbus_lock, client) { val = _pmbus_read_word_data(client, reg->page, 0xff, reg->attr->reg); @@ -2444,7 +2447,8 @@ static ssize_t pmbus_set_samples(struct device *dev, int ret; long val; struct i2c_client *client = to_i2c_client(dev->parent); - struct pmbus_samples_reg *reg = to_samples_reg(devattr); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct pmbus_samples_reg *reg = to_samples_reg(attr); if (kstrtol(buf, 0, &val) < 0) return -EINVAL; @@ -2459,6 +2463,7 @@ static ssize_t pmbus_set_samples(struct device *dev, static int pmbus_add_samples_attr(struct pmbus_data *data, int page, struct pmbus_samples_attr *attr) { + struct sensor_device_attribute *a; struct pmbus_samples_reg *reg; reg = devm_kzalloc(data->dev, sizeof(*reg), GFP_KERNEL); @@ -2468,10 +2473,12 @@ static int pmbus_add_samples_attr(struct pmbus_data *data, int page, reg->attr = attr; reg->page = page; - pmbus_dev_attr_init(®->dev_attr, attr->name, 0644, - pmbus_show_samples, pmbus_set_samples); + a = ®->attribute; + + pmbus_attr_init(a, attr->name, 0644, + pmbus_show_samples, pmbus_set_samples, -1); - return pmbus_add_attribute(data, ®->dev_attr.attr); + return pmbus_add_attribute(data, &a->dev_attr.attr); } static int pmbus_add_samples_attributes(struct i2c_client *client, @@ -2979,9 +2986,15 @@ static void pmbus_notify(struct pmbus_data *data, int page, int reg, int flags) struct device_attribute *da = to_dev_attr(data->group.attrs[i]); struct sensor_device_attribute *attr = to_sensor_dev_attr(da); int index = attr->index; - u16 smask = pb_index_to_mask(index); - u8 spage = pb_index_to_page(index); - u16 sreg = pb_index_to_reg(index); + u16 smask, sreg; + u8 spage; + + if (index == -1) + continue; + + smask = pb_index_to_mask(index); + spage = pb_index_to_page(index); + sreg = pb_index_to_reg(index); if (reg == sreg && page == spage && (smask & flags)) { dev_dbg(data->dev, "sysfs notify: %s", da->attr.name); From e5c0235a3c4e9eb047a16cd02323fe4ecf2f570e Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Fri, 31 Jul 2026 12:44:09 -0500 Subject: [PATCH 211/612] selinux: do not cancel a policy conversion that never started sel_write_load() calls selinux_policy_cancel() when sel_make_policy_nodes() fails, and that helper dereferences the outgoing policy to cancel its sidtab conversion. On the first policy load there is no outgoing policy: security_load_policy() returns early for that case, before it converts anything, and state->policy is still NULL. A first load that fails while building the selinuxfs tree therefore takes a NULL dereference in selinux_policy_cancel(), reached from a write(2) to /sys/fs/selinux/load. Skip the cancel when there is no old policy, mirroring the check security_load_policy() already makes before it converts. Cc: stable@vger.kernel.org Fixes: 02a52c5c8c3b ("selinux: move policy commit after updating selinuxfs") Signed-off-by: Bryam Vargas Acked-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/ss/services.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index 2d828548f3db6a..90e81186cb2ee3 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c @@ -2221,7 +2221,9 @@ void selinux_policy_cancel(struct selinux_load_state *load_state) oldpolicy = rcu_dereference_protected(state->policy, lockdep_is_held(&state->policy_mutex)); - sidtab_cancel_convert(oldpolicy->sidtab); + /* a first load has no outgoing policy and converted nothing */ + if (oldpolicy) + sidtab_cancel_convert(oldpolicy->sidtab); selinux_policy_free(load_state->policy); kfree(load_state->convert_data); } From b98a8ac50775540f3804397ed08f61ef9910bcab Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Fri, 31 Jul 2026 12:44:10 -0500 Subject: [PATCH 212/612] selinux: require a class's permission values to cover its permission count security_get_permissions() sizes an array by the class's permissions.nprim and fills it at value - 1, from the inherited common's permission table and then the class's own. A value no permission defines leaves a NULL that sel_make_perm_files() passes to d_alloc_name(), an oops inside sel_write_load() that strands selinux_state.policy_mutex and leaves every later load in uninterruptible sleep; two permissions sharing a value overwrite the first kstrdup(). Bounding each value by nprim catches neither, and neither would a count: the symbol table is keyed on the permission name, so duplicates pass. Track the values each permission table claims and require them to cover exactly what its count declares, rejecting a count no value can reach. Conforming policies are unaffected. Cc: stable@vger.kernel.org Fixes: 55fcf09b3fe4 ("selinux: add support for querying object classes and permissions from the running policy") Signed-off-by: Bryam Vargas Acked-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/ss/policydb.c | 51 ++++++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c index 69777e885ae750..d358200817bd9a 100644 --- a/security/selinux/ss/policydb.c +++ b/security/selinux/ss/policydb.c @@ -1154,7 +1154,18 @@ int str_read(char **strp, gfp_t flags, struct policy_file *fp, u32 len) return 0; } -static int perm_read(struct policydb *p, struct symtab *s, struct policy_file *fp) +/* + * Bitmap of the permission values a symtab has claimed. Values are 1-based + * and bounded by SEL_VEC_MAX, the width of an access vector, so the whole set + * fits in a u32 and the callers reject an nprim past that width. + */ +static u32 perm_claimed_mask(u32 nprim) +{ + return nprim ? U32_MAX >> (SEL_VEC_MAX - nprim) : 0; +} + +static int perm_read(struct policydb *p, struct symtab *s, + struct policy_file *fp, u32 *claimed) { char *key = NULL; struct perm_datum *perdatum; @@ -1178,6 +1189,10 @@ static int perm_read(struct policydb *p, struct symtab *s, struct policy_file *f /* indexes an nprim-sized array in security_get_permissions() */ if (perdatum->value > s->nprim) goto bad; + /* two permissions cannot share one slot of that array */ + if (*claimed & (1U << (perdatum->value - 1))) + goto bad; + *claimed |= 1U << (perdatum->value - 1); rc = str_read(&key, GFP_KERNEL, fp, len); if (rc) @@ -1198,7 +1213,7 @@ static int common_read(struct policydb *p, struct symtab *s, struct policy_file char *key = NULL; struct common_datum *comdatum; __le32 buf[4]; - u32 i, len, nel; + u32 i, len, nel, claimed = 0; int rc; comdatum = kzalloc_obj(*comdatum); @@ -1225,17 +1240,28 @@ static int common_read(struct policydb *p, struct symtab *s, struct policy_file if (rc) goto bad; comdatum->permissions.nprim = le32_to_cpu(buf[2]); + /* no permission value can reach a slot past SEL_VEC_MAX */ + rc = -EINVAL; + if (comdatum->permissions.nprim > SEL_VEC_MAX) + goto bad; rc = str_read(&key, GFP_KERNEL, fp, len); if (rc) goto bad; for (i = 0; i < nel; i++) { - rc = perm_read(p, &comdatum->permissions, fp); + rc = perm_read(p, &comdatum->permissions, fp, &claimed); if (rc) goto bad; } + rc = -EINVAL; + if (claimed != perm_claimed_mask(comdatum->permissions.nprim)) { + pr_err("SELinux: common %s does not define every permission it declares\n", + key); + goto bad; + } + hash_eval(&comdatum->permissions.table, "common_permissions", key); rc = symtab_insert(s, key, comdatum); @@ -1369,7 +1395,7 @@ static int class_read(struct policydb *p, struct symtab *s, struct policy_file * char *key = NULL; struct class_datum *cladatum; __le32 buf[6]; - u32 i, len, len2, ncons, nel, val; + u32 i, len, len2, ncons, nel, val, claimed = 0, inherited = 0; int rc; cladatum = kzalloc_obj(*cladatum); @@ -1402,6 +1428,10 @@ static int class_read(struct policydb *p, struct symtab *s, struct policy_file * if (rc) goto bad; cladatum->permissions.nprim = le32_to_cpu(buf[3]); + /* no permission value can reach a slot past SEL_VEC_MAX */ + rc = -EINVAL; + if (cladatum->permissions.nprim > SEL_VEC_MAX) + goto bad; ncons = le32_to_cpu(buf[5]); @@ -1436,11 +1466,22 @@ static int class_read(struct policydb *p, struct symtab *s, struct policy_file * } } for (i = 0; i < nel; i++) { - rc = perm_read(p, &cladatum->permissions, fp); + rc = perm_read(p, &cladatum->permissions, fp, &claimed); if (rc) goto bad; } + /* the class's own permissions must claim the slots the common leaves */ + if (cladatum->comdatum) + inherited = cladatum->comdatum->permissions.nprim; + rc = -EINVAL; + if (claimed != (perm_claimed_mask(cladatum->permissions.nprim) & + ~perm_claimed_mask(inherited))) { + pr_err("SELinux: class %s does not define every permission it declares\n", + key); + goto bad; + } + hash_eval(&cladatum->permissions.table, "class_permissions", key); rc = read_cons_helper(p, &cladatum->constraints, ncons, 0, fp); From 22b05fec62c0fe9864cfceb52f7d0f3a34d9b1dd Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Fri, 31 Jul 2026 12:44:11 -0500 Subject: [PATCH 213/612] selinux: reject an unclaimed class value in security_get_classes() security_get_classes() sizes an array by p_classes.nprim and fills it at value - 1, so a class value the policy never defines leaves a NULL. sel_make_classes() passes every entry to sel_make_dir(), reaching the same d_alloc_name() dereference as the permission array. The class symbol table is allowed to be sparse (policydb_class_isvalid() exists to absorb that), but this getter builds its own array straight from the hash table and has no such predicate. Fail the lookup when a value went unclaimed instead of handing out the NULL. Conforming policies define every class they declare and are unaffected. Cc: stable@vger.kernel.org Fixes: 55fcf09b3fe4 ("selinux: add support for querying object classes and permissions from the running policy") Signed-off-by: Bryam Vargas Acked-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/ss/services.c | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index 90e81186cb2ee3..7afce975436e93 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c @@ -3304,6 +3304,7 @@ int security_get_classes(struct selinux_policy *policy, char ***classes, u32 *nclasses) { struct policydb *policydb; + u32 i; int rc; policydb = &policy->policydb; @@ -3316,16 +3317,29 @@ int security_get_classes(struct selinux_policy *policy, rc = hashtab_map(&policydb->p_classes.table, get_classes_callback, *classes); - if (rc) { - u32 i; + if (rc) + goto err; - for (i = 0; i < *nclasses; i++) - kfree((*classes)[i]); - kfree(*classes); + /* + * The class symtab may be sparse, which policydb_class_isvalid() exists + * to absorb; the callback fills this array by value, so an unclaimed + * one leaves a NULL that sel_make_classes() hands to sel_make_dir(). + */ + for (i = 0; i < *nclasses; i++) { + if (!(*classes)[i]) { + rc = -EINVAL; + goto err; + } } out: return rc; + +err: + for (i = 0; i < *nclasses; i++) + kfree((*classes)[i]); + kfree(*classes); + return rc; } static int get_permissions_callback(void *k, void *d, void *args) From a93d37a09b863810653f93d371fb197457d59deb Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Fri, 31 Jul 2026 12:44:12 -0500 Subject: [PATCH 214/612] selinux: require every boolean value to be defined p_bools.nprim comes from the policy image independently of how many booleans follow it, and cond_index_bool() fills bool_val_to_struct[] at value - 1, so a count larger than the values present leaves NULL entries. Every user of that array then walks it by index and dereferences each entry: cond_evaluate_expr() on the access-vector path, security_get_bools() and security_get_bool_value() behind selinuxfs, and security_set_bools(). A sparse class value is absorbed by policydb_class_isvalid() and its siblings; booleans have no such predicate, and no consumer that could use one. Reject a boolean value that no boolean defines, once, where the array is built. Conforming policies define every boolean they declare and are unaffected. Cc: stable@vger.kernel.org Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Bryam Vargas Acked-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/ss/policydb.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c index d358200817bd9a..d88713201be92a 100644 --- a/security/selinux/ss/policydb.c +++ b/security/selinux/ss/policydb.c @@ -719,6 +719,7 @@ static inline void symtab_hash_eval(struct symtab *s) static int policydb_index(struct policydb *p) { int i, rc; + u32 v; if (p->mls_enabled) pr_debug( @@ -769,6 +770,24 @@ static int policydb_index(struct policydb *p) if (rc) goto out; } + + /* + * A sparse class value is absorbed by policydb_class_isvalid() and + * its siblings, but no such predicate exists for booleans: every + * user of bool_val_to_struct[] walks it by index and dereferences + * each entry -- cond_evaluate_expr(), the two getters and + * security_set_bools() -- so an unclaimed one has no consumer that + * can tolerate it. + */ + for (v = 0; v < p->p_bools.nprim; v++) { + if (!p->bool_val_to_struct[v]) { + pr_err("SELinux: boolean %u is declared but not defined\n", + v + 1); + rc = -EINVAL; + goto out; + } + } + rc = 0; out: return rc; From 0e125ecfe20c077625cf0be8d750d5c3abc0dce9 Mon Sep 17 00:00:00 2001 From: Nathan Gao Date: Fri, 24 Jul 2026 20:08:06 -0700 Subject: [PATCH 215/612] tcp: do not change rcv_ssthresh in tcp_measure_rcv_mss() Commit f5da7c45188e ("tcp: adjust rcvq_space after updating scaling ratio") replaced the direct window_clamp update in tcp_measure_rcv_mss() with a call to tcp_set_window_clamp(), a helper that implements the TCP_WINDOW_CLAMP setsockopt. As a side effect, the helper also shrinks rcv_ssthresh via __tcp_adjust_rcv_ssthresh(). As a result, each scaling_ratio decrease detected by tcp_measure_rcv_mss() also cuts rcv_ssthresh. Elsewhere in TCP, rcv_ssthresh is usually cut under memory pressure and grows via tcp_grow_window(). Flows whose segment sizes vary keep scaling_ratio oscillating, which leads to an unstable rcv_ssthresh: a dip of rcv_ssthresh only recovers via tcp_grow_window(), keeping the advertised window at a relatively low level even after the ratio itself has recovered, and can even stall the sender. Observed on a customer's proxy gateway after upgrading from kernel 6.1 to 6.12: in the worst case, rcv_ssthresh was cut in half by a scaling_ratio dip. P99 latency jumped from <10ms on 6.1 to ~100ms on 6.12, and almost returned to the 6.1 level with this patch applied. Restore the plain WRITE_ONCE() update of window_clamp, as introduced in commit a2cbb1603943 ("tcp: Update window clamping condition"), and keep the rcvq_space.space adjustment. Now rcv_ssthresh is decoupled from scaling_ratio changes in tcp_measure_rcv_mss(). Fixes: f5da7c45188e ("tcp: adjust rcvq_space after updating scaling ratio") Signed-off-by: Nathan Gao Link: https://patch.msgid.link/20260725030806.28135-1-zcgao@amazon.com Signed-off-by: Jakub Kicinski --- net/ipv4/tcp_input.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c index daff93d5134287..5b6378b94701ef 100644 --- a/net/ipv4/tcp_input.c +++ b/net/ipv4/tcp_input.c @@ -252,7 +252,7 @@ static void tcp_measure_rcv_mss(struct sock *sk, const struct sk_buff *skb) struct tcp_sock *tp = tcp_sk(sk); val = tcp_win_from_space(sk, sk->sk_rcvbuf); - tcp_set_window_clamp(sk, val); + WRITE_ONCE(tp->window_clamp, val); if (tp->window_clamp < tp->rcvq_space.space) tp->rcvq_space.space = tp->window_clamp; From 9f2cf069a9a72a2d6b97ca8b4c70e714aac99749 Mon Sep 17 00:00:00 2001 From: Baul Lee Date: Thu, 30 Jul 2026 01:00:28 +0900 Subject: [PATCH 216/612] sctp: keep chunk->transport in step with the list it is queued on __sctp_outq_flush_rtx() moves a gap-acked chunk onto another transport's transmitted list without updating chunk->transport: if (chunk->tsn_gap_acked) { list_move_tail(&chunk->transmitted_list, &transport->transmitted); continue; } The chunk then sits on a live transport's list while chunk->transport still names a different one. If that transport is removed - sctp_assoc_rm_peer() from an ASCONF Delete-IP - sctp_transport_free() RCU-frees it and the chunk is left with a dangling pointer. sctp_assoc_rm_peer() scrubs peer->transmitted and asoc->outqueue.out_chunk_list, but the chunk is on neither. The pointer is not followed while tsn_gap_acked is set. A SACK that reneges on the TSN clears the flag, and the next SACK reaches tchunk->transport->flight_size -= sctp_data_size(tchunk); inside the freed transport. KASAN reports a slab-use-after-free read in sctp_check_transmitted(), freed from sctp_assoc_rm_peer(). Both the removal and the SACKs come from the association peer. Set chunk->transport at the move. The ordinary resend path needs nothing: it reaches its list_move_tail() only after sctp_packet_append_chunk() returned SCTP_XMIT_OK, and __sctp_packet_append_chunk() has rebound the chunk by then. Discovered by XBOW, triaged by Baul Lee Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Baul Lee Acked-by: Xin Long Link: https://patch.msgid.link/20260729160028.54546-1-baul.lee@xbow.com Signed-off-by: Jakub Kicinski --- net/sctp/outqueue.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/sctp/outqueue.c b/net/sctp/outqueue.c index f6b8c13dafa4a6..e92fb9da4647a7 100644 --- a/net/sctp/outqueue.c +++ b/net/sctp/outqueue.c @@ -650,6 +650,7 @@ static int __sctp_outq_flush_rtx(struct sctp_outq *q, struct sctp_packet *pkt, if (chunk->tsn_gap_acked) { list_move_tail(&chunk->transmitted_list, &transport->transmitted); + chunk->transport = transport; continue; } From d1ad8fb2ac6a1afb71dc22d9ae8efb4dda96c824 Mon Sep 17 00:00:00 2001 From: Yuejie Shi Date: Thu, 30 Jul 2026 11:52:32 +0800 Subject: [PATCH 217/612] ipv6: fix Route Information option length validation rt6_route_rcv() validates the Route Information option (RFC 4191) length against the prefix length, but both checks are off by one. rinfo->length is the ND option length in units of 8 octets and it *includes* the 8-byte option header, so an option carrying N bytes of prefix has length == 1 + N/8. RFC 4191 section 2.3 requires length 3 when Prefix Length is greater than 64, and 2 or 3 when it is greater than 0. The code accepts length >= 2 and length >= 1 respectively. ipv6_addr_prefix() then copies prefix_len/8 bytes out of rinfo->prefix, so a Router Advertisement with (prefix_len=128, length=2) or (prefix_len=64, length=1) makes the kernel read up to 8 bytes past the end of the option. Those bytes end up in the prefix of the route that gets installed, so they are visible to userspace: # RA with a Route Information option (prefix_len=128, length=2) # followed by a source link-layer address option, 01 01 de ad be ef ca fe $ ip -6 route show 2001:db8:dead:beef:101:dead:beef:cafe via fe80::1234 dev veth0 proto ra ^^^^^^^^^^^^^^^^^^ the next option, read out of bounds When the Route Information option is the last one in the packet, those eight bytes come from the skb tail room instead. Reject the option lengths RFC 4191 does not allow. Fixes: 70ceb4f53929 ("[IPV6]: ROUTE: Add experimental support for Route Information Option in RA (RFC4191).") Cc: stable@vger.kernel.org Signed-off-by: Yuejie Shi Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260730035310.74584-1-syjcnss@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv6/route.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/net/ipv6/route.c b/net/ipv6/route.c index fc42d67e582261..5968ce5ad15082 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -988,13 +988,13 @@ int rt6_route_rcv(struct net_device *dev, u8 *opt, int len, } else if (rinfo->prefix_len > 128) { return -EINVAL; } else if (rinfo->prefix_len > 64) { - if (rinfo->length < 2) { + /* RFC 4191: Length MUST be 3 when Prefix Length > 64 */ + if (rinfo->length < 3) return -EINVAL; - } } else if (rinfo->prefix_len > 0) { - if (rinfo->length < 1) { + /* RFC 4191: Length MUST be 2 or 3 when Prefix Length > 0 */ + if (rinfo->length < 2) return -EINVAL; - } } pref = rinfo->route_pref; From d1000fd7995e51deec872d154e0a40d82f7a539f Mon Sep 17 00:00:00 2001 From: Will Chen Date: Wed, 29 Jul 2026 15:01:31 -0700 Subject: [PATCH 218/612] bnxt: fix memory leak in bnxt_queue_mem_alloc error cases There is a small memory leak in bnxt_queue_mem_alloc: when bnxt_alloc_rx_agg_bmap() succeeds but bnxt_alloc_one_tpa_info() later fails, the rx_agg_bmap allocated by bnxt_alloc_rx_agg_bmap() is not freed in the fallthrough cleanup cases. Free the rx_agg_bmap in the err_free_rx_agg_ring case and initialize clone->rx_agg_bmap = NULL earlier in the function to allow for safe fallthrough. Fixes: bd649c5cc958 ("bnxt_en: handle tpa_info in queue API implementation") Signed-off-by: Will Chen Reviewed-by: Joe Damato Reviewed-by: Michael Chan Link: https://patch.msgid.link/20260729220132.1256924-1-will.chen.tty@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index 7513618793daf3..004ef99f453068 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -16217,6 +16217,7 @@ static int bnxt_queue_mem_alloc(struct net_device *dev, clone->rx_next_cons = 0; clone->need_head_pool = false; clone->rx_page_size = qcfg->rx_page_size; + clone->rx_agg_bmap = NULL; rc = bnxt_alloc_rx_page_pool(bp, clone, rxr->page_pool->p.nid); if (rc) @@ -16269,6 +16270,8 @@ static int bnxt_queue_mem_alloc(struct net_device *dev, bnxt_free_one_tpa_info(bp, clone); err_free_rx_agg_ring: bnxt_free_ring(bp, &clone->rx_agg_ring_struct.ring_mem); + kfree(clone->rx_agg_bmap); + clone->rx_agg_bmap = NULL; err_free_rx_ring: bnxt_free_ring(bp, &clone->rx_ring_struct.ring_mem); err_rxq_info_unreg: From ae2567b11c3df43861d05f856bdb3434b3961aa1 Mon Sep 17 00:00:00 2001 From: Boris Burkov Date: Thu, 30 Jul 2026 09:38:02 -0700 Subject: [PATCH 219/612] btrfs: flush the fixup workers during close_ctree Reintroducing the COW fixup worker brought back the unmount race fixed by commit 41fd1e94066a ("btrfs: wait for fixup workers before stopping cleaner kthread during umount") without bringing back the fix. A fixup work item queued by the final writeback pass can still be in flight when close_ctree() stops the cleaner kthread and frees the fs roots. While destroy_workqueue() drains the queue, that happens after the cleaner thread was freed, so btrfs_add_delayed_iput() called from the fixup worker is no longer safe (not to mention that we are already in BTRFS_FS_STATE_NO_DELAYED_IPUT when it runs). Therefore we need to bring back explicitly flushing the fixup workqueue as in Filipe's original fix. The first flush will catch all the fixup writeback queued during the final sync before umount, but some of that might hit memory allocation errors and stay fixup in the blocks/folio, leading any subsequent writeback triggered *inside* umount (e.g. reclaim workers shutting down) to hit it and queue again. To fix that, and the possibility of any really long-lived pinned folios getting marked, deny queueing new fixup during umount. That allows us to flush twice (once before doing a real writeback pass to get the actual data, second time to clean up any rather unlikely stragglers right before declaring BTRFS_FS_STATE_NO_DELAYED_IPUT) and be certain nothing got re-queued. Reproduced by injecting a one-shot 30s sleep at the head of btrfs_writepage_fixup_worker() on a KASAN kernel, running the normal reproducing read dio workload before unmount and then observing: BUG: KASAN: slab-use-after-free in _raw_spin_lock_irqsave+0x35/0x50 Read of size 1 at addr ffff88810b4b08f8 by task kworker/u32:5/219 Workqueue: btrfs-fixup btrfs_writepage_fixup_worker [btrfs] Call Trace: _raw_spin_lock_irqsave+0x35/0x50 try_to_wake_up+0xc0/0x18c0 btrfs_writepage_fixup_worker+0x7f3/0xf20 [btrfs] ... Fixes: 4be9c7da6860 ("btrfs: trigger cow fixup via dirty_folio()") Assisted-by: LLM (reproduction, analysis) Reviewed-by: Qu Wenruo Signed-off-by: Boris Burkov Signed-off-by: David Sterba --- fs/btrfs/disk-io.c | 21 +++++++++++++++++++++ fs/btrfs/inode.c | 17 +++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c index 6bb70c43a63f64..8bdc94d3ddee3d 100644 --- a/fs/btrfs/disk-io.c +++ b/fs/btrfs/disk-io.c @@ -4361,6 +4361,18 @@ void __cold close_ctree(struct btrfs_fs_info *fs_info) /* clear out the rbtree of defraggable inodes */ btrfs_cleanup_defrag_inodes(fs_info); + /* + * Before the unmount, we sync down all the writeback which can + * generate fixup work. We are about to run delalloc for autodefrag so + * piggy back on that by also flushing the fixup work which can also + * generate delalloc we would like to get run. + * + * After this, it is still possible that some thread doing writeback is + * in btrfs_queue_writepage_fixup() and might finish queueing some final + * work, racing the btrfs_fs_closing() check there. + */ + flush_workqueue(fs_info->fixup_workers); + /* * Handle the error fs first, as it will flush and wait for all ordered * extents. This will generate delayed iputs, thus we want to handle @@ -4438,6 +4450,15 @@ void __cold close_ctree(struct btrfs_fs_info *fs_info) cancel_work_sync(&fs_info->preempt_reclaim_work); cancel_work_sync(&fs_info->em_shrinker_work); + /* + * Reclaim workers can run writeback which can queue fixup. + * After the above cancel_work_sync() calls, any such queueing attempts are + * guaranteed to see btrfs_fs_closing(), so at this point we can genuinely fully + * flush the fixup workqueue. This relies on the belief that *now* no thread can + * still be sitting in btrfs_queue_writepage_fixup(). + */ + flush_workqueue(fs_info->fixup_workers); + /* * Run delayed iputs again because an async reclaim worker may have * added new ones if it was flushing delalloc: diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 98b31a090626ee..9b1bf2e03497b2 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -2952,6 +2952,23 @@ void btrfs_queue_writepage_fixup(struct btrfs_inode *inode, struct folio *folio) struct btrfs_fs_info *fs_info = inode->root->fs_info; struct btrfs_writepage_fixup *fixup; + /* + * Disallow queueing more fixup during unmount to break the cycle + * of writeback queuing fixup queuing writeback etc. + * + * If it actually hit, then something which was fixup wasn't written + * which we should warn about. + */ + if (btrfs_fs_closing(fs_info)) { + btrfs_warn_rl(fs_info, + "dropping unqueued fixup blocks at unmount. root %lld ino %llu folio %llu", + btrfs_root_id(inode->root), btrfs_ino(inode), + folio_pos(folio)); + btrfs_folio_clear_fixup_dirty(fs_info, folio, + folio_pos(folio), folio_size(folio)); + return; + } + fixup = kzalloc_obj(*fixup, GFP_NOFS); if (!fixup) return; From 3971921a055330669b281962ff723d1abb76a58c Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Mon, 3 Aug 2026 16:30:05 +0200 Subject: [PATCH 220/612] ata: libata-core: Disable LPM on WDC WD141KFGX-68FH9N0 According to a user report, WDC WD141KFGX-68FH9N0 has problems with LPM. Link: https://bugzilla.kernel.org/show_bug.cgi?id=220693 Signed-off-by: Niklas Cassel Signed-off-by: Damien Le Moal --- drivers/ata/libata-core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c index 961d03fbc4d92c..59e87b987d73e8 100644 --- a/drivers/ata/libata-core.c +++ b/drivers/ata/libata-core.c @@ -4420,6 +4420,7 @@ static const struct ata_dev_quirks_entry __ata_dev_quirks[] = { */ { "WDC WD100EFGX-68CPLN0", NULL, ATA_QUIRK_NOLPM }, { "WDC WD102KFBX-68M95N0", NULL, ATA_QUIRK_NOLPM }, + { "WDC WD141KFGX-68FH9N0", NULL, ATA_QUIRK_NOLPM }, { "WD Green 2.5 480GB", NULL, ATA_QUIRK_NOLPM }, /* From ebac8f6b1ef0e9278afe204b8692a7479988dace Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Wed, 29 Jul 2026 12:16:54 -0700 Subject: [PATCH 221/612] vsock/virtio: read virtqueues under worker locks Commit bd50c5dc182b ("vsock/virtio: add support for device suspend/resume") made the *_run flags transition from false to true when restore installs replacement virtqueues. The RX, TX and event workers read their virtqueue before locking and checking the corresponding flag, so a worker delayed across freeze and restore can observe the replacement queue's running state while retaining a pointer to the deleted queue. Read each virtqueue under its mutex after checking the run flag, keeping the pointer and state in the same queue generation. Fixes: bd50c5dc182b ("vsock/virtio: add support for device suspend/resume") Cc: stable@vger.kernel.org Reported-by: Xiang Mei Link: https://lore.kernel.org/r/20260727035804.1860862-1-bestswngs@gmail.com Signed-off-by: Weiming Shi Reviewed-by: Bobby Eshleman Link: https://patch.msgid.link/e79f68ad9284c983364fc3ac46904b6d9ef50231.1785352330.git.bestswngs@gmail.com Signed-off-by: Jakub Kicinski --- net/vmw_vsock/virtio_transport.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/net/vmw_vsock/virtio_transport.c b/net/vmw_vsock/virtio_transport.c index 57f2d6ec3ffc69..a8e1dd95ba8c8d 100644 --- a/net/vmw_vsock/virtio_transport.c +++ b/net/vmw_vsock/virtio_transport.c @@ -346,12 +346,13 @@ static void virtio_transport_tx_work(struct work_struct *work) struct virtqueue *vq; bool added = false; - vq = vsock->vqs[VSOCK_VQ_TX]; mutex_lock(&vsock->tx_lock); if (!vsock->tx_run) goto out; + vq = vsock->vqs[VSOCK_VQ_TX]; + do { struct sk_buff *skb; unsigned int len; @@ -451,13 +452,13 @@ static void virtio_transport_event_work(struct work_struct *work) container_of(work, struct virtio_vsock, event_work); struct virtqueue *vq; - vq = vsock->vqs[VSOCK_VQ_EVENT]; - mutex_lock(&vsock->event_lock); if (!vsock->event_run) goto out; + vq = vsock->vqs[VSOCK_VQ_EVENT]; + do { struct virtio_vsock_event *event; unsigned int len; @@ -634,13 +635,13 @@ static void virtio_transport_rx_work(struct work_struct *work) container_of(work, struct virtio_vsock, rx_work); struct virtqueue *vq; - vq = vsock->vqs[VSOCK_VQ_RX]; - mutex_lock(&vsock->rx_lock); if (!vsock->rx_run) goto out; + vq = vsock->vqs[VSOCK_VQ_RX]; + do { virtqueue_disable_cb(vq); for (;;) { From a31e0ad444698d8aa7534a0f89fda543730f97a5 Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Wed, 29 Jul 2026 12:16:55 -0700 Subject: [PATCH 222/612] vsock/virtio: avoid refilling the RX queue after teardown Commit b917507e5ad9 ("vsock/virtio: stop workers during the .remove()") made the RX worker jump to its common exit when rx_run is clear. That exit still refills the RX queue when the buffer count is low, so work queued across virtio_vsock_vqs_del() can add buffers after the virtqueues have been deleted. BUG: KASAN: slab-use-after-free in virtqueue_add_sgs Read of size 4 by task kworker/0:1 Workqueue: virtio_vsock virtio_transport_rx_work Call Trace: virtqueue_add_sgs (drivers/virtio/virtio_ring.c:2796) virtio_vsock_rx_fill (net/vmw_vsock/virtio_transport.c:332) virtio_transport_rx_work (net/vmw_vsock/virtio_transport.c:701) process_one_work (kernel/workqueue.c:3314) worker_thread (kernel/workqueue.c:3478) 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) ... Freed by task 141: kfree (mm/slub.c:6566) vp_del_vq (drivers/virtio/virtio_pci_common.c:259) vp_del_vqs (drivers/virtio/virtio_pci_common.c:285) virtio_vsock_freeze (net/vmw_vsock/virtio_transport.c:912) virtio_device_freeze (drivers/virtio/virtio.c:658) virtio_pci_freeze (drivers/virtio/virtio_pci_common.c:601) pci_pm_freeze (drivers/pci/pci-driver.c:1098) device_suspend (drivers/base/power/main.c:1968) Kernel panic - not syncing: KASAN: panic_on_warn set ... Jump to a no-refill exit when rx_run is clear, leaving the normal exit to replenish a running queue. Fixes: b917507e5ad9 ("vsock/virtio: stop workers during the .remove()") Cc: stable@vger.kernel.org Reported-by: Xiang Mei Link: https://lore.kernel.org/r/20260727035804.1860862-1-bestswngs@gmail.com Suggested-by: Stefano Garzarella Signed-off-by: Weiming Shi Reviewed-by: Bobby Eshleman Link: https://patch.msgid.link/f9c8c1d64cad9d262f305d02ffe164c2f900fadf.1785352330.git.bestswngs@gmail.com Signed-off-by: Jakub Kicinski --- net/vmw_vsock/virtio_transport.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/vmw_vsock/virtio_transport.c b/net/vmw_vsock/virtio_transport.c index a8e1dd95ba8c8d..96c9fe8d357c6c 100644 --- a/net/vmw_vsock/virtio_transport.c +++ b/net/vmw_vsock/virtio_transport.c @@ -638,7 +638,7 @@ static void virtio_transport_rx_work(struct work_struct *work) mutex_lock(&vsock->rx_lock); if (!vsock->rx_run) - goto out; + goto out_nofill; vq = vsock->vqs[VSOCK_VQ_RX]; @@ -692,6 +692,7 @@ static void virtio_transport_rx_work(struct work_struct *work) out: if (vsock->rx_buf_nr < vsock->rx_buf_max_nr / 2) virtio_vsock_rx_fill(vsock); +out_nofill: mutex_unlock(&vsock->rx_lock); } From 1bb30b181d9f0484e141f8411e15ed906d5c6780 Mon Sep 17 00:00:00 2001 From: Stanislav Fomichev Date: Mon, 27 Jul 2026 09:19:54 -0700 Subject: [PATCH 223/612] xsk: require at least 16 bytes of TX metadata AF_XDP accepts a TX metadata length as small as eight bytes, but every supported request needs the flags plus at least one eight-byte request field. Such short metadata also lets the kernel read beyond the registered area. Require 16 bytes rather than sizeof(struct xsk_tx_metadata) to preserve compatibility with applications that do not use launch-time metadata. Fixes: 341ac980eab9 ("xsk: Support tx_metadata_len") Reported-by: AutonomousCodeSecurity@microsoft.com Reported-by: Cen Zhang (Microsoft) Link: https://lore.kernel.org/netdev/20260720155210.34229-1-blbllhy@gmail.com/ Signed-off-by: Stanislav Fomichev Link: https://patch.msgid.link/20260727161959.885642-2-sdf@fomichev.me Signed-off-by: Jakub Kicinski --- net/xdp/xdp_umem.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/xdp/xdp_umem.c b/net/xdp/xdp_umem.c index 58da2f4f4397aa..cd8643360eb3a2 100644 --- a/net/xdp/xdp_umem.c +++ b/net/xdp/xdp_umem.c @@ -210,6 +210,8 @@ static int xdp_umem_reg(struct xdp_umem *umem, struct xdp_umem_reg *mr) if (mr->flags & XDP_UMEM_TX_METADATA_LEN) { if (mr->tx_metadata_len >= 256 || mr->tx_metadata_len % 8) return -EINVAL; + if (mr->tx_metadata_len < 16) + return -EINVAL; umem->tx_metadata_len = mr->tx_metadata_len; } From 19366db6dfccac9b0867a151678cd7b89fb8fd99 Mon Sep 17 00:00:00 2001 From: Stanislav Fomichev Date: Mon, 27 Jul 2026 09:19:55 -0700 Subject: [PATCH 224/612] xsk: pass TX metadata pointer by reference Completion handling needs to know whether a timestamp was requested when the metadata was processed. Let xsk_tx_metadata_request() update the caller's metadata pointer so that decision can be carried forward without rereading user-controlled flags. This only changes the interface; behavior remains unchanged. Fixes: ca4419f15abd ("xsk: Add launch time hardware offload support to XDP Tx metadata") Cc: Cen Zhang (Microsoft) Signed-off-by: Stanislav Fomichev Link: https://patch.msgid.link/20260727161959.885642-3-sdf@fomichev.me Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/igc/igc_main.c | 2 +- drivers/net/ethernet/mellanox/mlx5/core/en.h | 2 +- drivers/net/ethernet/mellanox/mlx5/core/en/xdp.c | 14 +++++++++----- drivers/net/ethernet/mellanox/mlx5/core/en/xdp.h | 4 ++-- .../net/ethernet/mellanox/mlx5/core/en/xsk/tx.c | 2 +- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 2 +- include/net/libeth/xsk.h | 2 +- include/net/xdp_sock.h | 8 +++++--- 8 files changed, 21 insertions(+), 15 deletions(-) diff --git a/drivers/net/ethernet/intel/igc/igc_main.c b/drivers/net/ethernet/intel/igc/igc_main.c index b3883a5a7d7ae1..beaea147948572 100644 --- a/drivers/net/ethernet/intel/igc/igc_main.c +++ b/drivers/net/ethernet/intel/igc/igc_main.c @@ -3082,7 +3082,7 @@ static void igc_xdp_xmit_zc(struct igc_ring *ring) meta_req.tx_buffer = bi; meta_req.meta = meta; meta_req.used_desc = 0; - xsk_tx_metadata_request(meta, &igc_xsk_tx_metadata_ops, + xsk_tx_metadata_request(&meta, &igc_xsk_tx_metadata_ops, &meta_req); /* xsk_tx_metadata_request() may have updated next_to_use */ diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en.h b/drivers/net/ethernet/mellanox/mlx5/core/en.h index 6867a5aed42c08..822859c360e754 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/en.h @@ -483,7 +483,7 @@ typedef int (*mlx5e_fp_xmit_xdp_frame_check)(struct mlx5e_xdpsq *); typedef bool (*mlx5e_fp_xmit_xdp_frame)(struct mlx5e_xdpsq *, struct mlx5e_xmit_data *, int, - struct xsk_tx_metadata *); + struct xsk_tx_metadata **); struct mlx5e_xdpsq { /* data path */ diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/xdp.c b/drivers/net/ethernet/mellanox/mlx5/core/en/xdp.c index d8c7cb8837d742..dac5e9d4c8bdb9 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/xdp.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/xdp.c @@ -452,11 +452,11 @@ INDIRECT_CALLABLE_SCOPE int mlx5e_xmit_xdp_frame_check_mpwqe(struct mlx5e_xdpsq INDIRECT_CALLABLE_SCOPE bool mlx5e_xmit_xdp_frame(struct mlx5e_xdpsq *sq, struct mlx5e_xmit_data *xdptxd, - int check_result, struct xsk_tx_metadata *meta); + int check_result, struct xsk_tx_metadata **meta); INDIRECT_CALLABLE_SCOPE bool mlx5e_xmit_xdp_frame_mpwqe(struct mlx5e_xdpsq *sq, struct mlx5e_xmit_data *xdptxd, - int check_result, struct xsk_tx_metadata *meta) + int check_result, struct xsk_tx_metadata **meta) { struct mlx5e_tx_mpwqe *session = &sq->mpwqe; struct mlx5e_xdpsq_stats *stats = sq->stats; @@ -504,7 +504,10 @@ mlx5e_xmit_xdp_frame_mpwqe(struct mlx5e_xdpsq *sq, struct mlx5e_xmit_data *xdptx * and it's safe to complete it at any time. */ mlx5e_xdp_mpwqe_session_start(sq); - xsk_tx_metadata_request(meta, &mlx5e_xsk_tx_metadata_ops, &session->wqe->eth); + if (meta) + xsk_tx_metadata_request(meta, + &mlx5e_xsk_tx_metadata_ops, + &session->wqe->eth); } mlx5e_xdp_mpwqe_add_dseg(sq, p, stats); @@ -535,7 +538,7 @@ INDIRECT_CALLABLE_SCOPE int mlx5e_xmit_xdp_frame_check(struct mlx5e_xdpsq *sq) INDIRECT_CALLABLE_SCOPE bool mlx5e_xmit_xdp_frame(struct mlx5e_xdpsq *sq, struct mlx5e_xmit_data *xdptxd, - int check_result, struct xsk_tx_metadata *meta) + int check_result, struct xsk_tx_metadata **meta) { struct mlx5e_xmit_data_frags *xdptxdf = container_of(xdptxd, struct mlx5e_xmit_data_frags, xd); @@ -649,7 +652,8 @@ mlx5e_xmit_xdp_frame(struct mlx5e_xdpsq *sq, struct mlx5e_xmit_data *xdptxd, sq->pc += num_wqebbs; - xsk_tx_metadata_request(meta, &mlx5e_xsk_tx_metadata_ops, eseg); + if (meta) + xsk_tx_metadata_request(meta, &mlx5e_xsk_tx_metadata_ops, eseg); sq->doorbell_cseg = cseg; diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/xdp.h b/drivers/net/ethernet/mellanox/mlx5/core/en/xdp.h index 3c54f8962664fc..5b8f4094d55306 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/xdp.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/xdp.h @@ -114,11 +114,11 @@ extern const struct xsk_tx_metadata_ops mlx5e_xsk_tx_metadata_ops; INDIRECT_CALLABLE_DECLARE(bool mlx5e_xmit_xdp_frame_mpwqe(struct mlx5e_xdpsq *sq, struct mlx5e_xmit_data *xdptxd, int check_result, - struct xsk_tx_metadata *meta)); + struct xsk_tx_metadata **meta)); INDIRECT_CALLABLE_DECLARE(bool mlx5e_xmit_xdp_frame(struct mlx5e_xdpsq *sq, struct mlx5e_xmit_data *xdptxd, int check_result, - struct xsk_tx_metadata *meta)); + struct xsk_tx_metadata **meta)); INDIRECT_CALLABLE_DECLARE(int mlx5e_xmit_xdp_frame_check_mpwqe(struct mlx5e_xdpsq *sq)); INDIRECT_CALLABLE_DECLARE(int mlx5e_xmit_xdp_frame_check(struct mlx5e_xdpsq *sq)); 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 8aeab4b21035cd..3d19dad8f86889 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/xsk/tx.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/xsk/tx.c @@ -105,7 +105,7 @@ bool mlx5e_xsk_tx(struct mlx5e_xdpsq *sq, unsigned int budget) ret = INDIRECT_CALL_2(sq->xmit_xdp_frame, mlx5e_xmit_xdp_frame_mpwqe, mlx5e_xmit_xdp_frame, sq, &xdptxd, - check_result, meta); + check_result, &meta); if (unlikely(!ret)) { if (sq->mpwqe.wqe) mlx5e_xdp_mpwqe_complete(sq); diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 3801f9d45278ef..7c3a7c56a81adf 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -2748,7 +2748,7 @@ static bool stmmac_xdp_xmit_zc(struct stmmac_priv *priv, u32 queue, u32 budget) meta_req.set_ic = &set_ic; meta_req.tbs = tx_q->tbs; meta_req.edesc = &tx_q->dma_entx[entry]; - xsk_tx_metadata_request(meta, &stmmac_xsk_tx_metadata_ops, + xsk_tx_metadata_request(&meta, &stmmac_xsk_tx_metadata_ops, &meta_req); if (set_ic) { tx_q->tx_count_frames = 0; diff --git a/include/net/libeth/xsk.h b/include/net/libeth/xsk.h index 82b5d21aae8784..e2fa6bf6b1b31e 100644 --- a/include/net/libeth/xsk.h +++ b/include/net/libeth/xsk.h @@ -205,7 +205,7 @@ __libeth_xsk_xmit_fill_buf_md(const struct xdp_desc *xdesc, BUILD_BUG_ON(!__builtin_constant_p(tmo == libeth_xsktmo)); tmo = tmo == libeth_xsktmo ? &__libeth_xsktmo : tmo; - xsk_tx_metadata_request(ctx.meta, tmo, &desc); + xsk_tx_metadata_request(&ctx.meta, tmo, &desc); return desc; } diff --git a/include/net/xdp_sock.h b/include/net/xdp_sock.h index 8b51876efbed1a..06c081feff42d4 100644 --- a/include/net/xdp_sock.h +++ b/include/net/xdp_sock.h @@ -153,17 +153,19 @@ static inline void xsk_tx_metadata_to_compl(struct xsk_tx_metadata *meta, /** * xsk_tx_metadata_request - Evaluate AF_XDP TX metadata at submission * and call appropriate xsk_tx_metadata_ops operation. - * @meta: pointer to AF_XDP metadata area + * @pmeta: pointer to pointer to AF_XDP metadata area * @ops: pointer to struct xsk_tx_metadata_ops * @priv: pointer to driver-private aread * * This function should be called by the networking device when * it prepares AF_XDP egress packet. */ -static inline void xsk_tx_metadata_request(const struct xsk_tx_metadata *meta, +static inline void xsk_tx_metadata_request(struct xsk_tx_metadata **pmeta, const struct xsk_tx_metadata_ops *ops, void *priv) { + const struct xsk_tx_metadata *meta = *pmeta; + if (!meta) return; @@ -231,7 +233,7 @@ static inline void xsk_tx_metadata_to_compl(struct xsk_tx_metadata *meta, { } -static inline void xsk_tx_metadata_request(struct xsk_tx_metadata *meta, +static inline void xsk_tx_metadata_request(struct xsk_tx_metadata **pmeta, const struct xsk_tx_metadata_ops *ops, void *priv) { From 9f60a67df8d3c862503bee62bada8e7089cba438 Mon Sep 17 00:00:00 2001 From: Stanislav Fomichev Date: Mon, 27 Jul 2026 09:19:56 -0700 Subject: [PATCH 225/612] xsk: clear metadata pointer when no timestamp is requested User space can change metadata flags after request processing. Rereading them during completion can therefore make the kernel write a timestamp that was not requested when the packet was submitted. Clear the metadata pointer during request processing unless timestamp completion is requested. Completion handling can then use the pointer itself instead of rereading the flags. On the mlx5 multi-packet WQE path metadata is evaluated per batch: xsk_tx_metadata_request() runs only for the descriptor that starts a session, just like the checksum offload that is applied once through the shared WQE. Only that descriptor's pointer is reset, so completion handling can record a timestamp for the other descriptors of the session regardless of their own XDP_TXMD_FLAGS_TIMESTAMP bit. The write stays inside the metadata area; the single-WQE, other zero-copy, and generic paths reset the pointer per descriptor and are unaffected. Fixes: ca4419f15abd ("xsk: Add launch time hardware offload support to XDP Tx metadata") Cc: Cen Zhang (Microsoft) Signed-off-by: Stanislav Fomichev Link: https://patch.msgid.link/20260727161959.885642-4-sdf@fomichev.me Signed-off-by: Jakub Kicinski --- include/net/xdp_sock.h | 14 ++++++++++---- net/xdp/xsk.c | 2 ++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/include/net/xdp_sock.h b/include/net/xdp_sock.h index 06c081feff42d4..2b2eb9b9d580e4 100644 --- a/include/net/xdp_sock.h +++ b/include/net/xdp_sock.h @@ -141,13 +141,16 @@ INDIRECT_CALLABLE_DECLARE(void xsk_destruct_skb(struct sk_buff *)); static inline void xsk_tx_metadata_to_compl(struct xsk_tx_metadata *meta, struct xsk_tx_metadata_compl *compl) { + compl->tx_timestamp = NULL; + if (!meta) return; - if (meta->flags & XDP_TXMD_FLAGS_TIMESTAMP) - compl->tx_timestamp = &meta->completion.tx_timestamp; - else - compl->tx_timestamp = NULL; + /* we can only arrive here if the completion timestamp has been + * requested via XDP_TXMD_FLAGS_TIMESTAMP, see xsk_tx_metadata_request + */ + + compl->tx_timestamp = &meta->completion.tx_timestamp; } /** @@ -182,6 +185,9 @@ static inline void xsk_tx_metadata_request(struct xsk_tx_metadata **pmeta, if (meta->flags & XDP_TXMD_FLAGS_CHECKSUM) ops->tmo_request_checksum(meta->request.csum_start, meta->request.csum_offset, priv); + + if (!(meta->flags & XDP_TXMD_FLAGS_TIMESTAMP)) + *pmeta = NULL; } /** diff --git a/net/xdp/xsk.c b/net/xdp/xsk.c index f906d51b669904..fcc6f17f35760a 100644 --- a/net/xdp/xsk.c +++ b/net/xdp/xsk.c @@ -998,6 +998,8 @@ static int xsk_skb_metadata(struct sk_buff *skb, void *buffer, if (meta->flags & XDP_TXMD_FLAGS_LAUNCH_TIME) skb->skb_mstamp_ns = meta->request.launch_time; + if (!(meta->flags & XDP_TXMD_FLAGS_TIMESTAMP)) + meta = NULL; xsk_tx_metadata_to_compl(meta, &skb_shinfo(skb)->xsk_meta); return 0; From 439ce2dddf3d22129b9113a7881637256a35e936 Mon Sep 17 00:00:00 2001 From: Stanislav Fomichev Date: Mon, 27 Jul 2026 09:19:57 -0700 Subject: [PATCH 226/612] xsk: validate launch-time metadata size Launch-time metadata extends beyond the first 16 bytes of struct xsk_tx_metadata. Reject the request when the registered metadata area does not contain the complete field. Snapshot the validated flags for the generic transmit path and use that snapshot for request and completion processing, avoiding inconsistent decisions if user space changes the flags concurrently. Note that only xsk_skb_metadata is properly using the flags, __xsk_buff_get_metadata ignores them. Next commits address that. Fixes: ca4419f15abd ("xsk: Add launch time hardware offload support to XDP Tx metadata") Cc: Cen Zhang (Microsoft) Signed-off-by: Stanislav Fomichev Link: https://patch.msgid.link/20260727161959.885642-5-sdf@fomichev.me Signed-off-by: Jakub Kicinski --- include/net/xdp_sock_drv.h | 17 +++++++++++++---- net/xdp/xsk.c | 11 ++++++----- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/include/net/xdp_sock_drv.h b/include/net/xdp_sock_drv.h index 46797645a0c241..68a78760135721 100644 --- a/include/net/xdp_sock_drv.h +++ b/include/net/xdp_sock_drv.h @@ -260,21 +260,28 @@ xsk_buff_raw_get_ctx(const struct xsk_buff_pool *pool, u64 addr) 0) static inline bool -xsk_buff_valid_tx_metadata(const struct xsk_tx_metadata *meta) +xsk_buff_valid_tx_metadata(const struct xsk_buff_pool *pool, + const struct xsk_tx_metadata *meta, u64 *flags) { - return !(meta->flags & ~XDP_TXMD_FLAGS_VALID); + *flags = READ_ONCE(meta->flags); + if (*flags & XDP_TXMD_FLAGS_LAUNCH_TIME) + if (pool->tx_metadata_len < + offsetofend(struct xsk_tx_metadata, request.launch_time)) + return false; + return !(*flags & ~XDP_TXMD_FLAGS_VALID); } static inline struct xsk_tx_metadata * __xsk_buff_get_metadata(const struct xsk_buff_pool *pool, void *data) { struct xsk_tx_metadata *meta; + u64 flags; if (!pool->tx_metadata_len) return NULL; meta = data - pool->tx_metadata_len; - if (unlikely(!xsk_buff_valid_tx_metadata(meta))) + if (unlikely(!xsk_buff_valid_tx_metadata(pool, meta, &flags))) return NULL; /* no way to signal the error to the user */ return meta; @@ -469,7 +476,9 @@ xsk_buff_raw_get_ctx(const struct xsk_buff_pool *pool, u64 addr) return (struct xdp_desc_ctx){ }; } -static inline bool xsk_buff_valid_tx_metadata(struct xsk_tx_metadata *meta) +static inline bool +xsk_buff_valid_tx_metadata(const struct xsk_buff_pool *pool, + const struct xsk_tx_metadata *meta, u64 *flags) { return false; } diff --git a/net/xdp/xsk.c b/net/xdp/xsk.c index fcc6f17f35760a..7855ee09c4b640 100644 --- a/net/xdp/xsk.c +++ b/net/xdp/xsk.c @@ -967,15 +967,16 @@ static int xsk_skb_metadata(struct sk_buff *skb, void *buffer, { struct xsk_tx_metadata *meta = NULL; u16 csum_start, csum_offset; + u64 flags; if (unlikely(pool->tx_metadata_len == 0)) return -EINVAL; meta = buffer - pool->tx_metadata_len; - if (unlikely(!xsk_buff_valid_tx_metadata(meta))) + if (unlikely(!xsk_buff_valid_tx_metadata(pool, meta, &flags))) return -EINVAL; - if (meta->flags & XDP_TXMD_FLAGS_CHECKSUM) { + if (flags & XDP_TXMD_FLAGS_CHECKSUM) { csum_start = READ_ONCE(meta->request.csum_start); csum_offset = READ_ONCE(meta->request.csum_offset); @@ -996,9 +997,9 @@ static int xsk_skb_metadata(struct sk_buff *skb, void *buffer, } } - if (meta->flags & XDP_TXMD_FLAGS_LAUNCH_TIME) - skb->skb_mstamp_ns = meta->request.launch_time; - if (!(meta->flags & XDP_TXMD_FLAGS_TIMESTAMP)) + if (flags & XDP_TXMD_FLAGS_LAUNCH_TIME) + skb->skb_mstamp_ns = READ_ONCE(meta->request.launch_time); + if (!(flags & XDP_TXMD_FLAGS_TIMESTAMP)) meta = NULL; xsk_tx_metadata_to_compl(meta, &skb_shinfo(skb)->xsk_meta); From ddd0d6c5bfe2fef7c7cf31f62265f29b7b9eb9ef Mon Sep 17 00:00:00 2001 From: Stanislav Fomichev Date: Mon, 27 Jul 2026 09:19:58 -0700 Subject: [PATCH 227/612] xsk: move xsk_tx_metadata_request() to xdp_sock_drv.h xsk_tx_metadata_request() must validate metadata with xsk_buff_valid_tx_metadata(), which is defined in xdp_sock_drv.h. Move the helper there before adding that dependency. All callers already include the destination header, so this has no functional effect. Fixes: ca4419f15abd ("xsk: Add launch time hardware offload support to XDP Tx metadata") Cc: Cen Zhang (Microsoft) Signed-off-by: Stanislav Fomichev Link: https://patch.msgid.link/20260727161959.885642-6-sdf@fomichev.me Signed-off-by: Jakub Kicinski --- include/net/xdp_sock.h | 43 -------------------------------------- include/net/xdp_sock_drv.h | 43 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/include/net/xdp_sock.h b/include/net/xdp_sock.h index 2b2eb9b9d580e4..6e70b320b399f8 100644 --- a/include/net/xdp_sock.h +++ b/include/net/xdp_sock.h @@ -153,43 +153,6 @@ static inline void xsk_tx_metadata_to_compl(struct xsk_tx_metadata *meta, compl->tx_timestamp = &meta->completion.tx_timestamp; } -/** - * xsk_tx_metadata_request - Evaluate AF_XDP TX metadata at submission - * and call appropriate xsk_tx_metadata_ops operation. - * @pmeta: pointer to pointer to AF_XDP metadata area - * @ops: pointer to struct xsk_tx_metadata_ops - * @priv: pointer to driver-private aread - * - * This function should be called by the networking device when - * it prepares AF_XDP egress packet. - */ -static inline void xsk_tx_metadata_request(struct xsk_tx_metadata **pmeta, - const struct xsk_tx_metadata_ops *ops, - void *priv) -{ - const struct xsk_tx_metadata *meta = *pmeta; - - if (!meta) - return; - - if (ops->tmo_request_launch_time) - if (meta->flags & XDP_TXMD_FLAGS_LAUNCH_TIME) - ops->tmo_request_launch_time(meta->request.launch_time, - priv); - - if (ops->tmo_request_timestamp) - if (meta->flags & XDP_TXMD_FLAGS_TIMESTAMP) - ops->tmo_request_timestamp(priv); - - if (ops->tmo_request_checksum) - if (meta->flags & XDP_TXMD_FLAGS_CHECKSUM) - ops->tmo_request_checksum(meta->request.csum_start, - meta->request.csum_offset, priv); - - if (!(meta->flags & XDP_TXMD_FLAGS_TIMESTAMP)) - *pmeta = NULL; -} - /** * xsk_tx_metadata_complete - Evaluate AF_XDP TX metadata at completion * and call appropriate xsk_tx_metadata_ops operation. @@ -239,12 +202,6 @@ static inline void xsk_tx_metadata_to_compl(struct xsk_tx_metadata *meta, { } -static inline void xsk_tx_metadata_request(struct xsk_tx_metadata **pmeta, - const struct xsk_tx_metadata_ops *ops, - void *priv) -{ -} - static inline void xsk_tx_metadata_complete(struct xsk_tx_metadata_compl *compl, const struct xsk_tx_metadata_ops *ops, void *priv) diff --git a/include/net/xdp_sock_drv.h b/include/net/xdp_sock_drv.h index 68a78760135721..f87c4215673edb 100644 --- a/include/net/xdp_sock_drv.h +++ b/include/net/xdp_sock_drv.h @@ -271,6 +271,43 @@ xsk_buff_valid_tx_metadata(const struct xsk_buff_pool *pool, return !(*flags & ~XDP_TXMD_FLAGS_VALID); } +/** + * xsk_tx_metadata_request - Evaluate AF_XDP TX metadata at submission + * and call appropriate xsk_tx_metadata_ops operation. + * @pmeta: pointer to pointer to AF_XDP metadata area + * @ops: pointer to struct xsk_tx_metadata_ops + * @priv: pointer to driver-private aread + * + * This function should be called by the networking device when + * it prepares AF_XDP egress packet. + */ +static inline void xsk_tx_metadata_request(struct xsk_tx_metadata **pmeta, + const struct xsk_tx_metadata_ops *ops, + void *priv) +{ + const struct xsk_tx_metadata *meta = *pmeta; + + if (!meta) + return; + + if (ops->tmo_request_launch_time) + if (meta->flags & XDP_TXMD_FLAGS_LAUNCH_TIME) + ops->tmo_request_launch_time(meta->request.launch_time, + priv); + + if (ops->tmo_request_timestamp) + if (meta->flags & XDP_TXMD_FLAGS_TIMESTAMP) + ops->tmo_request_timestamp(priv); + + if (ops->tmo_request_checksum) + if (meta->flags & XDP_TXMD_FLAGS_CHECKSUM) + ops->tmo_request_checksum(meta->request.csum_start, + meta->request.csum_offset, priv); + + if (!(meta->flags & XDP_TXMD_FLAGS_TIMESTAMP)) + *pmeta = NULL; +} + static inline struct xsk_tx_metadata * __xsk_buff_get_metadata(const struct xsk_buff_pool *pool, void *data) { @@ -483,6 +520,12 @@ xsk_buff_valid_tx_metadata(const struct xsk_buff_pool *pool, return false; } +static inline void xsk_tx_metadata_request(struct xsk_tx_metadata **pmeta, + const struct xsk_tx_metadata_ops *ops, + void *priv) +{ +} + static inline struct xsk_tx_metadata * __xsk_buff_get_metadata(const struct xsk_buff_pool *pool, void *data) { From 849b1664dbda1cf6c63e0fd4f9dec23782b8c851 Mon Sep 17 00:00:00 2001 From: Stanislav Fomichev Date: Mon, 27 Jul 2026 09:19:59 -0700 Subject: [PATCH 228/612] xsk: validate metadata when processing requests The zero-copy path validates TX metadata while obtaining the descriptor context, then reads it again later when preparing the hardware request. User space can change the metadata between those operations and bypass the original validation. Validate the metadata in xsk_tx_metadata_request() and use the resulting flags snapshot for every feature check. Read request fields once so all zero-copy drivers process only values observed after successful validation. Fixes: ca4419f15abd ("xsk: Add launch time hardware offload support to XDP Tx metadata") Cc: Cen Zhang (Microsoft) Signed-off-by: Stanislav Fomichev Link: https://patch.msgid.link/20260727161959.885642-7-sdf@fomichev.me Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/igc/igc_main.c | 2 +- .../net/ethernet/mellanox/mlx5/core/en/xdp.c | 5 +- .../net/ethernet/stmicro/stmmac/stmmac_main.c | 4 +- include/net/libeth/xsk.h | 2 +- include/net/xdp_sock_drv.h | 51 ++++++++++--------- net/xdp/xsk_buff_pool.c | 6 +-- 6 files changed, 37 insertions(+), 33 deletions(-) diff --git a/drivers/net/ethernet/intel/igc/igc_main.c b/drivers/net/ethernet/intel/igc/igc_main.c index beaea147948572..16fcc8cf619195 100644 --- a/drivers/net/ethernet/intel/igc/igc_main.c +++ b/drivers/net/ethernet/intel/igc/igc_main.c @@ -3082,7 +3082,7 @@ static void igc_xdp_xmit_zc(struct igc_ring *ring) meta_req.tx_buffer = bi; meta_req.meta = meta; meta_req.used_desc = 0; - xsk_tx_metadata_request(&meta, &igc_xsk_tx_metadata_ops, + xsk_tx_metadata_request(pool, &meta, &igc_xsk_tx_metadata_ops, &meta_req); /* xsk_tx_metadata_request() may have updated next_to_use */ diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/xdp.c b/drivers/net/ethernet/mellanox/mlx5/core/en/xdp.c index dac5e9d4c8bdb9..77ea51bfbaae58 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/xdp.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/xdp.c @@ -505,7 +505,7 @@ mlx5e_xmit_xdp_frame_mpwqe(struct mlx5e_xdpsq *sq, struct mlx5e_xmit_data *xdptx */ mlx5e_xdp_mpwqe_session_start(sq); if (meta) - xsk_tx_metadata_request(meta, + xsk_tx_metadata_request(sq->xsk_pool, meta, &mlx5e_xsk_tx_metadata_ops, &session->wqe->eth); } @@ -653,7 +653,8 @@ mlx5e_xmit_xdp_frame(struct mlx5e_xdpsq *sq, struct mlx5e_xmit_data *xdptxd, sq->pc += num_wqebbs; if (meta) - xsk_tx_metadata_request(meta, &mlx5e_xsk_tx_metadata_ops, eseg); + xsk_tx_metadata_request(sq->xsk_pool, meta, + &mlx5e_xsk_tx_metadata_ops, eseg); sq->doorbell_cseg = cseg; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 7c3a7c56a81adf..97b28cfa14c268 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -2748,8 +2748,8 @@ static bool stmmac_xdp_xmit_zc(struct stmmac_priv *priv, u32 queue, u32 budget) meta_req.set_ic = &set_ic; meta_req.tbs = tx_q->tbs; meta_req.edesc = &tx_q->dma_entx[entry]; - xsk_tx_metadata_request(&meta, &stmmac_xsk_tx_metadata_ops, - &meta_req); + xsk_tx_metadata_request(pool, &meta, + &stmmac_xsk_tx_metadata_ops, &meta_req); if (set_ic) { tx_q->tx_count_frames = 0; stmmac_set_tx_ic(priv, tx_desc); diff --git a/include/net/libeth/xsk.h b/include/net/libeth/xsk.h index e2fa6bf6b1b31e..5dcc0d7f65b763 100644 --- a/include/net/libeth/xsk.h +++ b/include/net/libeth/xsk.h @@ -205,7 +205,7 @@ __libeth_xsk_xmit_fill_buf_md(const struct xdp_desc *xdesc, BUILD_BUG_ON(!__builtin_constant_p(tmo == libeth_xsktmo)); tmo = tmo == libeth_xsktmo ? &__libeth_xsktmo : tmo; - xsk_tx_metadata_request(&ctx.meta, tmo, &desc); + xsk_tx_metadata_request(sq->pool, &ctx.meta, tmo, &desc); return desc; } diff --git a/include/net/xdp_sock_drv.h b/include/net/xdp_sock_drv.h index f87c4215673edb..b344789f5df836 100644 --- a/include/net/xdp_sock_drv.h +++ b/include/net/xdp_sock_drv.h @@ -245,7 +245,7 @@ static inline void *xsk_buff_raw_get_data(struct xsk_buff_pool *pool, u64 addr) * details. * * Return: new &xdp_desc_ctx struct containing desc's DMA address and metadata - * pointer, if it is present and valid (initialized to %NULL otherwise). + * 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) @@ -274,54 +274,56 @@ xsk_buff_valid_tx_metadata(const struct xsk_buff_pool *pool, /** * xsk_tx_metadata_request - Evaluate AF_XDP TX metadata at submission * and call appropriate xsk_tx_metadata_ops operation. + * @pool: pointer to AF_XDP buffer pool, used to validate the metadata * @pmeta: pointer to pointer to AF_XDP metadata area * @ops: pointer to struct xsk_tx_metadata_ops - * @priv: pointer to driver-private aread + * @priv: pointer to driver-private area * * This function should be called by the networking device when * it prepares AF_XDP egress packet. */ -static inline void xsk_tx_metadata_request(struct xsk_tx_metadata **pmeta, - const struct xsk_tx_metadata_ops *ops, - void *priv) +static inline void +xsk_tx_metadata_request(const struct xsk_buff_pool *pool, + struct xsk_tx_metadata **pmeta, + const struct xsk_tx_metadata_ops *ops, void *priv) { const struct xsk_tx_metadata *meta = *pmeta; + u64 flags; if (!meta) return; + if (unlikely(!xsk_buff_valid_tx_metadata(pool, meta, &flags))) { + *pmeta = NULL; + return; /* no way to signal the error to the user */ + } + if (ops->tmo_request_launch_time) - if (meta->flags & XDP_TXMD_FLAGS_LAUNCH_TIME) - ops->tmo_request_launch_time(meta->request.launch_time, - priv); + if (flags & XDP_TXMD_FLAGS_LAUNCH_TIME) + ops->tmo_request_launch_time( + READ_ONCE(meta->request.launch_time), priv); if (ops->tmo_request_timestamp) - if (meta->flags & XDP_TXMD_FLAGS_TIMESTAMP) + if (flags & XDP_TXMD_FLAGS_TIMESTAMP) ops->tmo_request_timestamp(priv); if (ops->tmo_request_checksum) - if (meta->flags & XDP_TXMD_FLAGS_CHECKSUM) - ops->tmo_request_checksum(meta->request.csum_start, - meta->request.csum_offset, priv); + if (flags & XDP_TXMD_FLAGS_CHECKSUM) + ops->tmo_request_checksum( + READ_ONCE(meta->request.csum_start), + READ_ONCE(meta->request.csum_offset), priv); - if (!(meta->flags & XDP_TXMD_FLAGS_TIMESTAMP)) + if (!(flags & XDP_TXMD_FLAGS_TIMESTAMP)) *pmeta = NULL; } static inline struct xsk_tx_metadata * __xsk_buff_get_metadata(const struct xsk_buff_pool *pool, void *data) { - struct xsk_tx_metadata *meta; - u64 flags; - if (!pool->tx_metadata_len) return NULL; - meta = data - pool->tx_metadata_len; - if (unlikely(!xsk_buff_valid_tx_metadata(pool, meta, &flags))) - return NULL; /* no way to signal the error to the user */ - - return meta; + return data - pool->tx_metadata_len; } static inline struct xsk_tx_metadata * @@ -520,9 +522,10 @@ xsk_buff_valid_tx_metadata(const struct xsk_buff_pool *pool, return false; } -static inline void xsk_tx_metadata_request(struct xsk_tx_metadata **pmeta, - const struct xsk_tx_metadata_ops *ops, - void *priv) +static inline void +xsk_tx_metadata_request(const struct xsk_buff_pool *pool, + struct xsk_tx_metadata **pmeta, + const struct xsk_tx_metadata_ops *ops, void *priv) { } diff --git a/net/xdp/xsk_buff_pool.c b/net/xdp/xsk_buff_pool.c index a4089480b22b25..78c14f10639573 100644 --- a/net/xdp/xsk_buff_pool.c +++ b/net/xdp/xsk_buff_pool.c @@ -765,11 +765,11 @@ EXPORT_SYMBOL(xp_raw_get_dma); * @addr: desc address (from userspace) * * Helper for getting desc's DMA address and metadata pointer, if present. - * Saves one call on hotpath, double calculation of the actual address, - * and inline checks for metadata presence and sanity. + * Saves one call on hotpath and double calculation of the actual address. + * Metadata is validated later by xsk_tx_metadata_request(). * * Return: new &xdp_desc_ctx struct containing desc's DMA address and metadata - * pointer, if it is present and valid (initialized to %NULL otherwise). + * 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) { From 1f428e30947395d9b9aacee03e25a4e6cfcad7a4 Mon Sep 17 00:00:00 2001 From: Yi Cong Date: Wed, 29 Jul 2026 11:04:36 +0800 Subject: [PATCH 229/612] net: usb: ax88179_178a: fix skb leak in ax88179_tx_fixup() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the interface has NETIF_F_SG enabled and skb_linearize() fails in ax88179_tx_fixup(), the function returns NULL without freeing the skb. usbnet_start_xmit() treats a NULL return from tx_fixup() as a drop (info->flags does not set FLAG_MULTI_PACKET for this driver), jumping to the "drop" label where it does `if (skb) dev_kfree_skb_any(skb)`. Because tx_fixup() returned NULL, the local skb variable in usbnet_start_xmit() is NULL, so the original skb is never freed — a memory leak on every TX frame whose linearization fails (i.e. under memory pressure). Free the skb before returning, matching the error handling already used for the pskb_expand_head() failure path in the same function. Fixes: 16b1c4e01c89 ("net: usb: ax88179_178a: add TSO feature") Cc: stable@vger.kernel.org Signed-off-by: Yi Cong Link: https://patch.msgid.link/20260729030436.3420477-1-cong.yi@linux.dev Signed-off-by: Jakub Kicinski --- drivers/net/usb/ax88179_178a.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c index 98f899ea2e9462..81d8412ce8e2fa 100644 --- a/drivers/net/usb/ax88179_178a.c +++ b/drivers/net/usb/ax88179_178a.c @@ -1487,8 +1487,10 @@ ax88179_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags) headroom = skb_headroom(skb) - 8; - if ((dev->net->features & NETIF_F_SG) && skb_linearize(skb)) + if ((dev->net->features & NETIF_F_SG) && skb_linearize(skb)) { + dev_kfree_skb_any(skb); return NULL; + } if ((skb_header_cloned(skb) || headroom < 0) && pskb_expand_head(skb, headroom < 0 ? 8 : 0, 0, GFP_ATOMIC)) { From 90f305f2c7a30257c683e13f4bf7c798eea992a0 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Wed, 29 Jul 2026 11:30:45 -0700 Subject: [PATCH 230/612] Input: evdev - fix information leak in evdev_pass_values() In evdev_pass_values(), the input_event structure is allocated on the kernel stack and populated field-by-field. However, it is never fully initialized. On architectures where struct input_event contains explicit or implicit padding (such as the 32-bit __pad field on SPARC64), these padding bytes are left uninitialized. When this event structure is subsequently passed to the client buffer and later copied to userspace, the uninitialized padding bytes leak kernel stack memory, potentially exposing sensitive information. Similar issues exist in __evdev_queue_syn_dropped and __pass_event. Fix this by explicitly zeroing the entire event structure with memset() before populating its fields. This ensures all padding bytes are cleared before the data crosses the security boundary. Reported-by: sashiko-bot@kernel.org Cc: stable@vger.kernel.org Link: https://patch.msgid.link/ampGGKo4UMKru6f5@google.com Signed-off-by: Dmitry Torokhov --- drivers/input/evdev.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c index c7325226cb8663..531db70aa9f923 100644 --- a/drivers/input/evdev.c +++ b/drivers/input/evdev.c @@ -146,11 +146,11 @@ static void __evdev_queue_syn_dropped(struct evdev_client *client) struct timespec64 ts = ktime_to_timespec64(ev_time[client->clk_type]); struct input_event ev; + memset(&ev, 0, sizeof(ev)); ev.input_event_sec = ts.tv_sec; ev.input_event_usec = ts.tv_nsec / NSEC_PER_USEC; ev.type = EV_SYN; ev.code = SYN_DROPPED; - ev.value = 0; client->buffer[client->head++] = ev; client->head &= client->bufsize - 1; @@ -218,20 +218,20 @@ static void __pass_event(struct evdev_client *client, client->head &= client->bufsize - 1; if (unlikely(client->head == client->tail)) { + struct input_event ev; + + memset(&ev, 0, sizeof(ev)); + ev.input_event_sec = event->input_event_sec; + ev.input_event_usec = event->input_event_usec; + ev.type = EV_SYN; + ev.code = SYN_DROPPED; + /* * This effectively "drops" all unconsumed events, leaving * EV_SYN/SYN_DROPPED plus the newest event in the queue. */ client->tail = (client->head - 2) & (client->bufsize - 1); - - client->buffer[client->tail] = (struct input_event) { - .input_event_sec = event->input_event_sec, - .input_event_usec = event->input_event_usec, - .type = EV_SYN, - .code = SYN_DROPPED, - .value = 0, - }; - + client->buffer[client->tail] = ev; client->packet_head = client->tail; } @@ -253,6 +253,8 @@ static void evdev_pass_values(struct evdev_client *client, if (client->revoked) return; + memset(&event, 0, sizeof(event)); + ts = ktime_to_timespec64(ev_time[client->clk_type]); event.input_event_sec = ts.tv_sec; event.input_event_usec = ts.tv_nsec / NSEC_PER_USEC; From 1cb4298810e27e037d3ca07286ecbb97e89ba58d Mon Sep 17 00:00:00 2001 From: Alok Tiwari Date: Fri, 31 Jul 2026 12:22:59 -0700 Subject: [PATCH 231/612] bnge: Fix NULL pointer dereference in aux device release If allocation of auxr_dev fails during auxiliary device setup, the error path calls auxiliary_device_uninit(), which eventually invokes bnge_aux_dev_release(). The release callback unconditionally dereferences aux_priv->auxr_dev->pdev to retrieve the parent bnge_dev. Since auxr_dev has not yet been allocated on this failure path, the dereference results in a NULL pointer exception Retrieve the parent bnge_dev from the auxiliary device's parent instead of auxr_dev, and free auxr_dev only when it was successfully allocated. This allows the release callback to correctly clean up partially initialized auxiliary devices. Fixes: 8ac050ec3b1c ("bng_en: Add RoCE aux device support") Signed-off-by: Alok Tiwari Reviewed-by: Bhargava Marreddy Link: https://patch.msgid.link/20260731192301.1427645-1-alok.a.tiwari@oracle.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnge/bnge_auxr.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnge/bnge_auxr.c b/drivers/net/ethernet/broadcom/bnge/bnge_auxr.c index 67e93e17d4d9f1..0955b488b6fea0 100644 --- a/drivers/net/ethernet/broadcom/bnge/bnge_auxr.c +++ b/drivers/net/ethernet/broadcom/bnge/bnge_auxr.c @@ -141,12 +141,15 @@ static void bnge_aux_dev_release(struct device *dev) { struct bnge_auxr_priv *aux_priv = container_of(dev, struct bnge_auxr_priv, aux_dev.dev); - struct bnge_dev *bd = pci_get_drvdata(aux_priv->auxr_dev->pdev); + struct bnge_auxr_dev *auxr_dev = aux_priv->auxr_dev; + struct bnge_dev *bd = pci_get_drvdata(to_pci_dev(dev->parent)); ida_free(&bnge_aux_dev_ids, aux_priv->id); - kfree(aux_priv->auxr_dev->auxr_info); + if (auxr_dev) { + kfree(auxr_dev->auxr_info); + kfree(auxr_dev); + } bd->auxr_dev = NULL; - kfree(aux_priv->auxr_dev); kfree(aux_priv); bd->aux_priv = NULL; } From 5d9686af2976741bbd79b150d1c9e60b81e7f12e Mon Sep 17 00:00:00 2001 From: Daming Li Date: Thu, 30 Jul 2026 22:55:52 +0800 Subject: [PATCH 232/612] net: smc: fix splice entry lifetime imbalance in smc_rx_splice smc_rx_splice() passes pages to splice_to_pipe() before taking the references that cover the lifetime of each splice entry. In the VM-backed RMB path, splice_to_pipe() may drop unqueued entries through smc_rx_spd_release(), while queued entries are released later via the pipe buffer callback. The old post-splice accounting also derives the number of queued VM pages from an offset mutated while building the descriptor, and a multi-page splice pairs one sock_hold() with multiple sock_put() calls. Take the page and socket references for every candidate entry before splice_to_pipe(), and drop the matching private state, page reference, and socket reference from smc_rx_spd_release() for entries that never get queued. This fixes a refcount imbalance that can underflow page refcounts and trigger a use-after-free. Fixes: 9014db202cb7 ("smc: add support for splice()") Cc: stable@vger.kernel.org Reported-by: Vega Co-developed-by: Xiao Liu Signed-off-by: Xiao Liu Signed-off-by: Daming Li Signed-off-by: Ren Wei Reviewed-by: Dust Li Reviewed-by: Sidraya Jayagond Link: https://patch.msgid.link/20260730145552.360287-2-enjou1224z@gmail.com Signed-off-by: Jakub Kicinski --- net/smc/smc_rx.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/net/smc/smc_rx.c b/net/smc/smc_rx.c index c1d9b923938dad..5c9e4d8b57de4d 100644 --- a/net/smc/smc_rx.c +++ b/net/smc/smc_rx.c @@ -150,7 +150,12 @@ static const struct pipe_buf_operations smc_pipe_ops = { static void smc_rx_spd_release(struct splice_pipe_desc *spd, unsigned int i) { + struct smc_spd_priv *priv = (struct smc_spd_priv *)spd->partial[i].private; + struct sock *sk = &priv->smc->sk; + + kfree(priv); put_page(spd->pages[i]); + sock_put(sk); } static int smc_rx_splice(struct pipe_inode_info *pipe, char *src, size_t len, @@ -209,6 +214,10 @@ static int smc_rx_splice(struct pipe_inode_info *pipe, char *src, size_t len, offset = 0; } } + for (i = 0; i < nr_pages; i++) { + get_page(pages[i]); + sock_hold(&smc->sk); + } spd.nr_pages_max = nr_pages; spd.nr_pages = nr_pages; spd.pages = pages; @@ -217,16 +226,8 @@ static int smc_rx_splice(struct pipe_inode_info *pipe, char *src, size_t len, spd.spd_release = smc_rx_spd_release; bytes = splice_to_pipe(pipe, &spd); - if (bytes > 0) { - sock_hold(&smc->sk); - if (!lgr->is_smcd && smc->conn.rmb_desc->is_vm) { - for (i = 0; i < PAGE_ALIGN(bytes + offset) / PAGE_SIZE; i++) - get_page(pages[i]); - } else { - get_page(smc->conn.rmb_desc->pages); - } + if (bytes > 0) atomic_add(bytes, &smc->conn.splice_pending); - } kfree(priv); kfree(partial); kfree(pages); From 4ff9548d84945d2cbf9e4c207288063a200ea397 Mon Sep 17 00:00:00 2001 From: Zihan Xi Date: Thu, 30 Jul 2026 12:59:26 +0000 Subject: [PATCH 233/612] ipv4: Fix fib_nlmsg_size() for RTA_VIA nexthops fib_nlmsg_size() still estimates nexthop space as if every gateway is encoded as an IPv4 RTA_GATEWAY attribute. IPv4 routes can also carry an IPv6 gateway, which fib_nexthop_info() dumps as RTA_VIA. As a result, route notifications can allocate an skb that is too small. fib_dump_info() then fails with -EMSGSIZE and rtmsg_fib() hits the WARN_ON() that marks such failures as a fib_nlmsg_size() bug. With panic_on_warn set, this becomes a kernel panic. Mirror the actual nexthop dump layout in fib_nlmsg_size(): account for IPv6 nexthop gateways dumped as RTA_VIA, for the no-header rtnexthop layout used inside RTA_MULTIPATH, and for RTA_FLOW only when it is actually present. Fixes: d15662682db2 ("ipv4: Allow ipv6 gateway with ipv4 routes") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Zihan Xi Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/6f53fa797fcaeb26966432ed7ae9bb87c4961f37.1785411220.git.zihanx@nebusec.ai Signed-off-by: Jakub Kicinski --- net/ipv4/fib_semantics.c | 67 +++++++++++++++++++++++++++++----------- 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/net/ipv4/fib_semantics.c b/net/ipv4/fib_semantics.c index 4f3c0740dde913..78f84ae3ee1209 100644 --- a/net/ipv4/fib_semantics.c +++ b/net/ipv4/fib_semantics.c @@ -490,6 +490,34 @@ int ip_fib_check_default(__be32 gw, struct net_device *dev) return -1; } +static size_t fib_nexthop_nlmsg_size(const struct fib_nh_common *nhc, + bool skip_oif) +{ + size_t nhsize = 0; + + switch (nhc->nhc_gw_family) { + case AF_INET: + nhsize += nla_total_size(4); /* RTA_GATEWAY */ + break; + case AF_INET6: + nhsize += nla_total_size(sizeof(struct rtvia) + + sizeof(struct in6_addr)); + break; + } + + if (!skip_oif && nhc->nhc_dev) + nhsize += nla_total_size(4); /* RTA_OIF */ + + if (nhc->nhc_lwtstate) { + /* RTA_ENCAP */ + nhsize += lwtunnel_get_encap_size(nhc->nhc_lwtstate); + /* RTA_ENCAP_TYPE */ + nhsize += nla_total_size(2); + } + + return nhsize; +} + size_t fib_nlmsg_size(struct fib_info *fi) { size_t payload = NLMSG_ALIGN(sizeof(struct rtmsg)) @@ -507,32 +535,35 @@ size_t fib_nlmsg_size(struct fib_info *fi) payload += nla_total_size(4); /* RTA_NH_ID */ if (nhs) { - size_t nh_encapsize = 0; - /* Also handles the special case nhs == 1 */ - - /* each nexthop is packed in an attribute */ - size_t nhsize = nla_total_size(sizeof(struct rtnexthop)); + size_t mpsize = 0; unsigned int i; - /* may contain flow and gateway attribute */ - nhsize += 2 * nla_total_size(4); - - /* grab encap info */ for (i = 0; i < fib_info_num_path(fi); i++) { struct fib_nh_common *nhc = fib_info_nhc(fi, i); + size_t nhsize; + + nhsize = fib_nexthop_nlmsg_size(nhc, nhs != 1); - if (nhc->nhc_lwtstate) { - /* RTA_ENCAP_TYPE */ - nh_encapsize += lwtunnel_get_encap_size( - nhc->nhc_lwtstate); - /* RTA_ENCAP */ - nh_encapsize += nla_total_size(2); + if (nhs != 1) + nhsize += NLA_ALIGN(sizeof(struct rtnexthop)); + +#ifdef CONFIG_IP_ROUTE_CLASSID + if (nhc->nhc_family == AF_INET) { + struct fib_nh *nh; + + nh = container_of(nhc, struct fib_nh, nh_common); + if (nh->nh_tclassid) + nhsize += nla_total_size(4); } +#endif + if (nhs == 1) + payload += nhsize; + else + mpsize += nhsize; } - /* all nexthops are packed in a nested attribute */ - payload += nla_total_size((nhs * nhsize) + nh_encapsize); - + if (nhs != 1) + payload += nla_total_size(mpsize); } return payload; From d141f087b1af656f055d7c5793a3e87817ba0bbe Mon Sep 17 00:00:00 2001 From: Hidayath Khan Date: Thu, 30 Jul 2026 16:22:16 +0200 Subject: [PATCH 234/612] s390/qeth: validate user buffer length in SNMP and ARP query ioctls qeth_snmp_command() and qeth_l3_arp_query() allocate a buffer sized by a user-supplied length (udata_len) without checking a lower bound, then set udata_offset to a fixed non-zero value and pass both to a reply callback. The callback bounds-checks the copy with if ((udata_len - udata_offset) < len) Both fields are u32, so a udata_len smaller than udata_offset makes the subtraction wrap and the check pass, and the following memcpy() writes past the allocation. A udata_len of 0 also yields ZERO_SIZE_PTR from kzalloc(), which the existing NULL check does not catch. Reject buffers smaller than udata_offset before allocating, so the callback subtraction can no longer underflow. Fixes: 4a71df50047f ("qeth: new qeth device driver") Cc: stable@vger.kernel.org Reviewed-by: Alexandra Winter Signed-off-by: Hidayath Khan Reviewed-by: Joe Damato Link: https://patch.msgid.link/20260730142216.218309-1-hidayath@linux.ibm.com Signed-off-by: Jakub Kicinski --- drivers/s390/net/qeth_core_main.c | 3 +++ drivers/s390/net/qeth_l3_main.c | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/drivers/s390/net/qeth_core_main.c b/drivers/s390/net/qeth_core_main.c index f18eed9df3c7a8..c3257b213360cc 100644 --- a/drivers/s390/net/qeth_core_main.c +++ b/drivers/s390/net/qeth_core_main.c @@ -4710,6 +4710,9 @@ static int qeth_snmp_command(struct qeth_card *card, char __user *udata) if (req_len > QETH_BUFSIZE) return -EINVAL; + if (qinfo.udata_len < sizeof(struct qeth_snmp_ureq_hdr)) + return -EINVAL; + iob = qeth_get_adapter_cmd(card, IPA_SETADP_SET_SNMP_CONTROL, req_len); if (!iob) return -ENOMEM; diff --git a/drivers/s390/net/qeth_l3_main.c b/drivers/s390/net/qeth_l3_main.c index 1542bfc9f561bf..f1ac9950dcb4eb 100644 --- a/drivers/s390/net/qeth_l3_main.c +++ b/drivers/s390/net/qeth_l3_main.c @@ -1415,6 +1415,11 @@ static int qeth_l3_arp_query(struct qeth_card *card, char __user *udata) rc = -EFAULT; goto out; } + + if (qinfo.udata_len < QETH_QARP_ENTRIES_OFFSET) { + rc = -EINVAL; + goto out; + } qinfo.udata = kzalloc(qinfo.udata_len, GFP_KERNEL); if (!qinfo.udata) { rc = -ENOMEM; From d0f86fb36eb260abd10007b62c9dcc1028e03e61 Mon Sep 17 00:00:00 2001 From: Xuanqiang Luo Date: Thu, 30 Jul 2026 17:35:54 +0800 Subject: [PATCH 235/612] udp: fix potential use-after-free in tunnel segmentation __skb_udp_tunnel_segment() gets the UDP header before ensuring the tunnel header is in the skb head. If the pull reallocates skb->head, the saved UDP header pointer is no longer valid. Get the UDP header after the pull to avoid a potential use-after-free. Fixes: dbef491ebe7f ("udp: Use uh->len instead of skb->len to compute checksum in segmentation") Signed-off-by: Xuanqiang Luo Reviewed-by: Antoine Tenart Link: https://patch.msgid.link/20260730093554.68127-1-xuanqiang.luo@linux.dev Signed-off-by: Jakub Kicinski --- net/ipv4/udp_offload.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/ipv4/udp_offload.c b/net/ipv4/udp_offload.c index 29651b1a0bc704..abcd3cc2d9a385 100644 --- a/net/ipv4/udp_offload.c +++ b/net/ipv4/udp_offload.c @@ -178,17 +178,19 @@ static struct sk_buff *__skb_udp_tunnel_segment(struct sk_buff *skb, int tnl_hlen = skb_inner_mac_header(skb) - skb_transport_header(skb); bool remcsum, need_csum, offload_csum, gso_partial; struct sk_buff *segs = ERR_PTR(-EINVAL); - struct udphdr *uh = udp_hdr(skb); u16 mac_offset = skb->mac_header; __be16 protocol = skb->protocol; u16 mac_len = skb->mac_len; int udp_offset, outer_hlen; + struct udphdr *uh; __wsum partial; bool need_ipsec; if (unlikely(!pskb_may_pull(skb, tnl_hlen))) goto out; + uh = udp_hdr(skb); + /* Adjust partial header checksum to negate old length. * We cannot rely on the value contained in uh->len as it is * possible that the actual value exceeds the boundaries of the From 2a33516f9ef59ad11844d4fc152f889449b5daf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toke=20H=C3=B8iland-J=C3=B8rgensen?= Date: Wed, 29 Jul 2026 21:14:16 +0200 Subject: [PATCH 236/612] net/sched: sch_cake: drop WARN_ON(1) for malformed packets in ACK filter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sch_cake ACK filter parses packets to find the TCP header and filter duplicated ACKs if the flow is backlogged. The parsing code contains a WARN_ON(1) which can be triggered by a malformed IP header in certain cases. Depending on the system configuration, this leads either to either spamming dmesg with warnings, or a panic if panic_on_warn is set. The code already correctly skips the offending packet in the branch that triggers the warning, so the WARN_ON itself doesn't really serve any purpose. So just drop it altogether to avoid the inconvenient side effects. Fixes: 8b7138814f29 ("sch_cake: Add optional ACK filter") Reported-by: Zhiling Zou Reported-by: Ren Wei Signed-off-by: Toke Høiland-Jørgensen Link: https://patch.msgid.link/20260729191417.45665-1-toke@toke.dk Signed-off-by: Jakub Kicinski --- net/sched/sch_cake.c | 1 - 1 file changed, 1 deletion(-) diff --git a/net/sched/sch_cake.c b/net/sched/sch_cake.c index f64be54ead49b5..f25f60978631a4 100644 --- a/net/sched/sch_cake.c +++ b/net/sched/sch_cake.c @@ -1287,7 +1287,6 @@ static struct sk_buff *cake_ack_filter(struct cake_sched_data *q, seglen = ipv6_payload_len(skb, ipv6h_check); } else { - WARN_ON(1); /* shouldn't happen */ continue; } From d876c493fc4b811941bfeb4c80beb2dfc4bf025e Mon Sep 17 00:00:00 2001 From: Linfeng Sun Date: Mon, 27 Jul 2026 16:18:41 +0800 Subject: [PATCH 237/612] vhost-scsi: Validate T10 PI scatterlist counts When T10 PI is negotiated, vhost-scsi splits protection bytes from the data iterator before mapping the request scatterlists. A malformed request can claim protection bytes that cover or exceed the full payload length. The former leaves no data bytes to map, while the latter underflows exp_data_len before advancing the iterator. Both cases can let a zero data SGL count reach sg_alloc_table_chained(), which triggers BUG_ON(!nents). Reject protection lengths that cover or exceed the payload before subtracting prot_bytes and advancing the iterator. Also propagate negative errors from the protection SGL calculation before calling the allocator, matching the data SGL path. Fixes: bca939d5bcd0 ("vhost-scsi: Dynamically allocate scatterlists") Suggested-by: Jia Jia Signed-off-by: Jia Jia Assisted-by: OpenAI-Codex:GPT-5 Signed-off-by: Linfeng Sun Message-ID: <20260727081841.923151-1-slf@hdu.edu.cn> Signed-off-by: Michael S. Tsirkin --- drivers/vhost/scsi.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/vhost/scsi.c b/drivers/vhost/scsi.c index 9a1253b9d8c50e..c79197edb16371 100644 --- a/drivers/vhost/scsi.c +++ b/drivers/vhost/scsi.c @@ -972,6 +972,9 @@ vhost_scsi_mapal(struct vhost_scsi *vs, struct vhost_scsi_cmd *cmd, if (prot_bytes) { sgl_count = vhost_scsi_calc_sgls(prot_iter, prot_bytes, VHOST_SCSI_PREALLOC_PROT_SGLS); + if (sgl_count < 0) + return sgl_count; + cmd->prot_table.sgl = cmd->prot_sgl; ret = sg_alloc_table_chained(&cmd->prot_table, sgl_count, cmd->prot_table.sgl, @@ -1416,6 +1419,11 @@ vhost_scsi_handle_vq(struct vhost_scsi *vs, struct vhost_virtqueue *vq) * actual data payload length. */ if (prot_bytes) { + if (prot_bytes >= exp_data_len) { + vq_err(vq, "Protection data exceeds payload length\n"); + goto err; + } + exp_data_len -= prot_bytes; prot_iter = data_iter; iov_iter_truncate(&prot_iter, prot_bytes); From de845981da67a6b049080c87e605130b0c30adc5 Mon Sep 17 00:00:00 2001 From: Jun Yang Date: Mon, 3 Aug 2026 09:45:14 +0800 Subject: [PATCH 238/612] vhost: reset the vring metadata cache on vring reconfiguration vq->meta_iotlb[] caches the vhost_iotlb_map that backs each vring metadata region, and iotlb_access_ok() returns early on a cache hit, taking the hit as proof that the region has already been validated: if (vhost_vq_meta_fetch(vq, addr, len, type)) return true; The cache is reset on VHOST_IOTLB_UPDATE and VHOST_IOTLB_INVALIDATE, on device IOTLB (re)initialisation and on vq reset, but not when VHOST_SET_VRING_ADDR replaces vq->desc, vq->avail and vq->used, nor when VHOST_SET_VRING_NUM changes the region sizes. With a device IOTLB attached both ioctls are accepted while the vq is live, and neither validates the addresses at ioctl time: vq_access_ok() and vq_log_used_access_ok() return true early because the addresses are GIOVAs, deferring validation to prefetch time. Once the cache has been populated that deferred validation no longer runs -- vq_meta_prefetch() hits the stale entry and returns true -- and vhost_vq_meta_fetch() keeps translating through the old mapping as map->addr + addr - map->start for an address the mapping no longer covers. vhost_copy_to_user() and vhost_copy_from_user() consume the result with __copy_to_user() and __copy_from_user(), which do not check it either, so a subsequent used ring update or descriptor fetch accesses memory outside the region the IOTLB actually maps. Reset the metadata cache whenever the vring is reconfigured, so the new addresses are pushed back through iotlb_access_ok()'s slow path. Fixes: f88949138058 ("vhost: introduce O(1) vq metadata cache") Cc: stable@vger.kernel.org Assisted-by: tencentos-corvus-ai:kimi-k3 Signed-off-by: Jun Yang Message-ID: <20260803014823.68623-1-juny24602@gmail.com> Signed-off-by: Michael S. Tsirkin --- drivers/vhost/vhost.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/vhost/vhost.c b/drivers/vhost/vhost.c index ae29a7ef7baad8..269efad90369e5 100644 --- a/drivers/vhost/vhost.c +++ b/drivers/vhost/vhost.c @@ -2131,6 +2131,14 @@ static long vhost_vring_set_num_addr(struct vhost_dev *d, BUG(); } + /* + * The metadata cache holds the IOTLB mapping that backed the previous + * desc/avail/used addresses and vring size, both of which are being + * replaced here. iotlb_access_ok() takes a cache hit as proof that the + * region was validated, so the stale entries have to go. + */ + __vhost_vq_meta_reset(vq); + mutex_unlock(&vq->mutex); return r; From 22598f55a4c2b510b3df5e69e563387a963222ae Mon Sep 17 00:00:00 2001 From: Jia Jia Date: Fri, 24 Jul 2026 14:09:19 +0800 Subject: [PATCH 239/612] vhost-scsi: flush backend after device ioctls vhost-scsi translates guest response descriptors into userspace iovecs when commands are submitted. Target-core completes those commands asynchronously, so VHOST_SET_MEM_TABLE can replace the memory table while an in-flight command still retains response iovecs translated through the old table. If the old mapping is reused after VHOST_SET_MEM_TABLE returns, command completion can write the response to an unrelated userspace object. Flush the vhost-scsi backend after vhost_dev_ioctl() handles a device ioctl. This waits for in-flight commands that can still use the old response iovecs before the ioctl returns. Signed-off-by: Jia Jia Signed-off-by: Michael S. Tsirkin Message-ID: <20260724060919.1569170-1-physicalmtea@gmail.com> --- drivers/vhost/scsi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/vhost/scsi.c b/drivers/vhost/scsi.c index c79197edb16371..aae1164e1ca9e3 100644 --- a/drivers/vhost/scsi.c +++ b/drivers/vhost/scsi.c @@ -2434,9 +2434,10 @@ vhost_scsi_ioctl(struct file *f, default: mutex_lock(&vs->dev.mutex); r = vhost_dev_ioctl(&vs->dev, ioctl, argp); - /* TODO: flush backend after dev ioctl. */ if (r == -ENOIOCTLCMD) r = vhost_vring_ioctl(&vs->dev, ioctl, argp); + else + vhost_scsi_flush(vs); mutex_unlock(&vs->dev.mutex); return r; } From 42bc45df5905e2b7dccb72adaf7730f66cfbe03f Mon Sep 17 00:00:00 2001 From: Jia Jia Date: Sun, 26 Jul 2026 22:43:14 +0800 Subject: [PATCH 240/612] vhost-scsi: reject feature changes after endpoint vhost_scsi_setup_vq_cmds() runs from VHOST_SCSI_SET_ENDPOINT and allocates each command's protection scatterlist array (prot_sgl) according to the acknowledged VIRTIO_SCSI_F_T10_PI bit. The command pools are not rebuilt when VHOST_SET_FEATURES changes that bit later. Although virtio feature bits must not change after feature negotiation, vhost_scsi_set_features() currently accepts such a request after the endpoint is active and updates acked_features. Enabling T10-PI after endpoint setup therefore leaves prot_sgl NULL while the I/O path follows the new feature bit. For a 129-page protection payload, vhost_scsi_mapal() passes the missing first chunk to sg_alloc_table_chained(): sg_alloc_table_chained(table, 129, first_chunk=NULL, nents_first_chunk=inline_sg_cnt) sg_pool_index() then hits: BUG_ON(nents > SG_CHUNK_SIZE); /* 129 > 128 */ The kernel reported the following call trace and register state: Call Trace: ? __sg_alloc_table+0x1d8/0x250 ? __pfx_vhost_run_work_list+0x10/0x10 [vhost] sg_alloc_table_chained+0x59/0xf0 ? __pfx_sg_pool_alloc+0x10/0x10 ? vhost_scsi_calc_sgls.constprop.0+0x43/0x60 [vhost_scsi] vhost_scsi_handle_vq+0xf02/0x1700 [vhost_scsi] ? __pfx_vhost_scsi_handle_vq+0x10/0x10 [vhost_scsi] vhost_scsi_handle_kick+0x37/0x50 [vhost_scsi] vhost_run_work_list+0x8e/0xd0 [vhost] vhost_task_fn+0xe1/0x210 ret_from_fork+0x348/0x540 RIP: 0010:0x4 CR2 = 0x4 RSP: 0018:ffffc90000dbf940 EFLAGS: 00010202 RAX: ffffffff82396810 RBX: ffff88811dc28b80 RCX: 0000000000000000 RDX: 0000000000000000 RSI: 0000000000000820 RDI: 0000000000000081 VHOST_F_LOG_ALL is a vhost-specific runtime feature and remains the only exception. Reject changes to any feature other than VHOST_F_LOG_ALL while the endpoint is active. This preserves the existing runtime log toggle while preventing feature-dependent command resources and data-path state from becoming inconsistent. Userspace must clear the endpoint before changing any other negotiated feature and set the endpoint up again afterward. Fixes: bf2d650391be ("vhost-scsi: Allocate T10 PI structs only when enabled") Signed-off-by: Jia Jia Reviewed-by: Stefan Hajnoczi Signed-off-by: Michael S. Tsirkin Message-ID: <20260726144314.1652934-1-physicalmtea@gmail.com> --- drivers/vhost/scsi.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/vhost/scsi.c b/drivers/vhost/scsi.c index aae1164e1ca9e3..7a1f39a327da63 100644 --- a/drivers/vhost/scsi.c +++ b/drivers/vhost/scsi.c @@ -2227,6 +2227,7 @@ static int vhost_scsi_set_features(struct vhost_scsi *vs, u64 features) { struct vhost_virtqueue *vq; bool is_log, was_log; + u64 old_features; int i; if (features & ~VHOST_SCSI_FEATURES) @@ -2242,6 +2243,14 @@ static int vhost_scsi_set_features(struct vhost_scsi *vs, u64 features) if (!vs->dev.nvqs) goto out; + old_features = vs->vqs[0].vq.acked_features; + if (vs->vs_tpg && + ((features ^ old_features) & + ~(1ULL << VHOST_F_LOG_ALL))) { + mutex_unlock(&vs->dev.mutex); + return -EBUSY; + } + is_log = features & (1 << VHOST_F_LOG_ALL); /* * All VQs should have same feature. From 6cdd8cbbf89611da6c948d58daed1c53f4bf8321 Mon Sep 17 00:00:00 2001 From: Billy Tsai Date: Thu, 23 Jul 2026 10:33:09 +0800 Subject: [PATCH 241/612] MAINTAINERS: add Ryan Chen and Billy Tsai as reviewer for ARM/ASPEED Add Ryan Chen and myself as a reviewer for the ARM/ASPEED MACHINE SUPPORT entry to reflect ongoing review and contribution work on AST2xxx/AST27xx platform support. Signed-off-by: Billy Tsai Signed-off-by: Andrew Jeffery --- MAINTAINERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 15011f5752a994..791a5d5865d1cf 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2676,6 +2676,8 @@ F: drivers/irqchip/irq-aspeed-i2c-ic.c ARM/ASPEED MACHINE SUPPORT M: Joel Stanley M: Andrew Jeffery +R: Ryan Chen +R: Billy Tsai L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) L: linux-aspeed@lists.ozlabs.org (moderated for non-subscribers) S: Supported From 27f380ef0e1d3de3cde114e02d33f9320ce3a5a6 Mon Sep 17 00:00:00 2001 From: Jianing Li Date: Mon, 3 Aug 2026 21:18:52 -0700 Subject: [PATCH 242/612] Input: hynitron_cstxxx - validate touch count and finger IDs The driver allocates max_touch_num input slots, which are indexed from zero through max_touch_num - 1. The current check allows a finger ID equal to max_touch_num to reach cst3xx_report_contact(). While the input core ignores out-of-range slot indices, reporting touch data without a valid slot change corrupts the touch state of the previously active slot. The touch count is read from the controller's report and is used to index the fixed-size report buffer without first checking its range. Reject counts larger than the supported number of touch slots before checking the trailing byte or parsing touch data. Reject finger IDs equal to or greater than max_touch_num, and return immediately when an invalid finger ID is encountered so that corrupt touch frames are discarded instead of reporting partial contact state. The V821 Avaota F1 board configures the vendor driver with one touch slot, so finger ID 1 is already invalid on that device. Fixes: 66603243f528 ("Input: add driver for Hynitron cstxxx touchscreens") Signed-off-by: Jianing Li Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260804031339.2379-1-m13940358460@163.com Assisted-by: Antigravity:gemini-3.6-flash Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/hynitron_cstxxx.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/input/touchscreen/hynitron_cstxxx.c b/drivers/input/touchscreen/hynitron_cstxxx.c index 1d8ca90dcda695..af66d91848b300 100644 --- a/drivers/input/touchscreen/hynitron_cstxxx.c +++ b/drivers/input/touchscreen/hynitron_cstxxx.c @@ -313,6 +313,12 @@ static void cst3xx_touch_report(struct i2c_client *client) return; touch_cnt = buf[5] & CST3XX_TOUCH_COUNT_MASK; + if (touch_cnt > ts_data->chip->max_touch_num) { + dev_err(&client->dev, "cst3xx invalid touch count (%d vs %d max)\n", + touch_cnt, ts_data->chip->max_touch_num); + return; + } + /* * Check the check bit of the last touch slot. The check bit is * always present after touch point 1 for valid data, and then @@ -335,9 +341,10 @@ static void cst3xx_touch_report(struct i2c_client *client) finger_id = (buf[idx] >> 4) & 0x0f; /* Sanity check we don't have more fingers than we expect */ - if (ts_data->chip->max_touch_num < finger_id) { - dev_err(&client->dev, "cst3xx touch read failure\n"); - break; + if (finger_id >= ts_data->chip->max_touch_num) { + dev_err(&client->dev, + "cst3xx invalid finger id %d\n", finger_id); + return; } /* sw value of 0 means no touch, 0x03 means touch */ From 3e8ec7c0387273329374f5c7bd61f5f38af71fe1 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Mon, 3 Aug 2026 11:12:31 -0700 Subject: [PATCH 243/612] fsverity: Fix bpf_get_fsverity_digest() dynptr assumptions The BPF verifier and the dynptr abstraction ensure that the memory space referenced by a dynptr remains valid. They do not, however, provide any guarantee that the contents of the memory are stable. kfuncs are expected to remain memory-safe even if concurrent modifications occur. bpf_get_fsverity_digest() didn't follow that: it could crash if arg->digest_size was concurrently modified. Fix that by using the known-good value hash_alg->digest_size instead. Also widen 'dynptr_sz' and 'out_digest_sz' to u64 to match the return type of __bpf_dynptr_size(). It doesn't appear that it can actually be more than INT_MAX currently (since __bpf_dynptr_data_rw() excludes file-based pointers), but the correct type might as well be used. Fixes: 67814c00de31 ("bpf, fsverity: Add kfunc bpf_get_fsverity_digest") Signed-off-by: Eric Biggers Acked-by: Kumar Kartikeya Dwivedi Acked-by: Song Liu Cc: stable@vger.kernel.org Link: https://lore.kernel.org/bpf/20260803181232.14743-2-ebiggers@kernel.org Signed-off-by: Kumar Kartikeya Dwivedi --- fs/verity/measure.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/fs/verity/measure.c b/fs/verity/measure.c index cfe2d5e535f96a..f8b3526af00469 100644 --- a/fs/verity/measure.c +++ b/fs/verity/measure.c @@ -122,11 +122,11 @@ __bpf_kfunc int bpf_get_fsverity_digest(struct file *file, const struct bpf_dynp { const struct bpf_dynptr_kern *digest_ptr = (struct bpf_dynptr_kern *)digest_p; const struct inode *inode = file_inode(file); - u32 dynptr_sz = __bpf_dynptr_size(digest_ptr); + u64 dynptr_sz = __bpf_dynptr_size(digest_ptr); struct fsverity_digest *arg; const struct fsverity_info *vi; const struct fsverity_hash_alg *hash_alg; - int out_digest_sz; + u64 out_digest_sz; if (dynptr_sz < sizeof(struct fsverity_digest)) return -EINVAL; @@ -150,11 +150,13 @@ __bpf_kfunc int bpf_get_fsverity_digest(struct file *file, const struct bpf_dynp out_digest_sz = dynptr_sz - sizeof(struct fsverity_digest); /* copy digest */ - memcpy(arg->digest, vi->file_digest, min_t(int, hash_alg->digest_size, out_digest_sz)); + memcpy(arg->digest, vi->file_digest, + min(hash_alg->digest_size, out_digest_sz)); /* fill the extra buffer with zeros */ if (out_digest_sz > hash_alg->digest_size) - memset(arg->digest + arg->digest_size, 0, out_digest_sz - hash_alg->digest_size); + memset(arg->digest + hash_alg->digest_size, 0, + out_digest_sz - hash_alg->digest_size); return 0; } From 7c68ed5c5ad4c185ea9654f5d8ee36560277b7dd Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Mon, 3 Aug 2026 11:12:32 -0700 Subject: [PATCH 244/612] fsverity: Fix silent truncation in bpf_get_fsverity_digest() bpf_get_fsverity_digest() silently truncates the digest if the provided buffer is too small. This is a footgun, and it doesn't match the semantics of the equivalent UAPI (FS_IOC_MEASURE_VERITY). Change it to return -EOVERFLOW instead, matching FS_IOC_MEASURE_VERITY. Fixes: 67814c00de31 ("bpf, fsverity: Add kfunc bpf_get_fsverity_digest") Signed-off-by: Eric Biggers Acked-by: Song Liu Cc: stable@vger.kernel.org Link: https://lore.kernel.org/bpf/20260803181232.14743-3-ebiggers@kernel.org Signed-off-by: Kumar Kartikeya Dwivedi --- fs/verity/measure.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/fs/verity/measure.c b/fs/verity/measure.c index f8b3526af00469..68dfccb6977201 100644 --- a/fs/verity/measure.c +++ b/fs/verity/measure.c @@ -144,14 +144,15 @@ __bpf_kfunc int bpf_get_fsverity_digest(struct file *file, const struct bpf_dynp hash_alg = vi->tree_params.hash_alg; + out_digest_sz = dynptr_sz - sizeof(struct fsverity_digest); + if (out_digest_sz < hash_alg->digest_size) + return -EOVERFLOW; + arg->digest_algorithm = hash_alg - fsverity_hash_algs; arg->digest_size = hash_alg->digest_size; - out_digest_sz = dynptr_sz - sizeof(struct fsverity_digest); - /* copy digest */ - memcpy(arg->digest, vi->file_digest, - min(hash_alg->digest_size, out_digest_sz)); + memcpy(arg->digest, vi->file_digest, hash_alg->digest_size); /* fill the extra buffer with zeros */ if (out_digest_sz > hash_alg->digest_size) From 3d26cd1f3ff25cebd10d4b0e8188cf40dade28e9 Mon Sep 17 00:00:00 2001 From: Lachlan Hodges Date: Tue, 4 Aug 2026 11:36:39 +0300 Subject: [PATCH 245/612] usb: xhci: use BIT_ULL for CRCR bits to fix incorrect 64bit mask xhci is unusable on some systems after driver switched to BIT() macro. Upper 32bits of 64bit CRCR command register are unintentionally cleared. Seen on a raspberry pi 4B compiled for arm32. The main symptoms were the following log message: [ 0.549897] raspberrypi-firmware soc:firmware: Attached to firmware from 2021-02-25T12:11:39 [ 0.626859] xhci_hcd 0000:01:00.0: xHCI Host Controller [ 0.626889] xhci_hcd 0000:01:00.0: new USB bus registered, assigned bus number 1 [ 0.812619] xhci_hcd 0000:01:00.0: hcc params 0x002841eb hci version 0x100 quirks 0x0000200000000890 [ 0.813188] xhci_hcd 0000:01:00.0: xHCI Host Controller [ 0.813203] xhci_hcd 0000:01:00.0: new USB bus registered, assigned bus number 2 [ 0.813219] xhci_hcd 0000:01:00.0: Host supports USB 3.0 SuperSpeed [ 0.813602] hub 1-0:1.0: USB hub found [ 0.814052] hub 2-0:1.0: USB hub found [ 0.952714] xhci_hcd 0000:01:00.0: ERROR mismatched command completion event Additionally running lsusb just hangs. Running the same kernel compiled for aarch64 worked fine. Bisected to the commit in the Fixes line. Additionally a USB device plugged in to the USB3.0 (or 2.0) did not enumerate. Once this patch is applied the USB device enumerates properly. The CRCR register is 64 bits wide - commit abe93f27cdd7 ("xhci: use BIT macro") changed the flag definitions from (1 << n), a signed int, to BIT(n), an unsigned long. Within xhci_set_cmd_ring_deq(), the following operation is performed on the CRCR register: ... crcr &= ~CMD_RING_PTR_MASK; crcr |= deq_dma; crcr &= ~CMD_RING_CYCLE; crcr |= xhci->cmd_ring->cycle_state; ... Previously, ~CMD_RING_CYCLE was ~(int)1, a negative signed value (0xFFFFFFFE with the sign bit set). Widening a negative signed int to u64 sign-extends it to 0xFFFFFFFFFFFFFFFE, correctly clearing only bit 0 and preserving the 64-bit pointer written two lines above. After the change when running on 32 bit kernels, ~CMD_RING_CYCLE is ~(unsigned long)1UL. On a 32-bit host this is an unsigned 32-bit value (0xFFFFFFFE, no sign bit). Widening an unsigned value to u64 zero-extends it instead (0x00000000FFFFFFFE), so the subsequent AND silently clears bits 63:32 of crcr, truncating the command ring pointer that was just written before the value reaches hardware. To fix, similar to how CMD_RING_PTR_MASK is defined, make sure we use the BIT_ULL variant when defining the CRCR bits. [Mathias: use BIT_ULL() for ERST_EHB and EP_CTX_CYCLE_MASK as suggested by Michal Pecio, also include raspberry case in commit message] Fixes: abe93f27cdd7 ("xhci: use BIT macro") Cc: stable Assisted-by: Claude:claude-sonnet-5 cc: Michal Pecio Signed-off-by: Lachlan Hodges Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260804083639.2148950-2-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 2d3941b5e1e3d7..708e3ccc5d87c6 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -190,13 +190,13 @@ struct xhci_op_regs { /* CRCR - Command Ring Control Register - cmd_ring bitmasks */ /* bit 0 - Cycle bit indicates the ownership of the command ring */ -#define CMD_RING_CYCLE BIT(0) +#define CMD_RING_CYCLE BIT_ULL(0) /* stop ring operation after completion of the currently executing command */ -#define CMD_RING_PAUSE BIT(1) +#define CMD_RING_PAUSE BIT_ULL(1) /* stop ring immediately - abort the currently executing command */ -#define CMD_RING_ABORT BIT(2) +#define CMD_RING_ABORT BIT_ULL(2) /* true: command ring is running */ -#define CMD_RING_RUNNING BIT(3) +#define CMD_RING_RUNNING BIT_ULL(3) /* bits 63:6 - Command Ring pointer */ #define CMD_RING_PTR_MASK GENMASK_ULL(63, 6) @@ -271,7 +271,7 @@ struct xhci_intr_reg { * bit 3 - Event Handler Busy (EHB), whether the event ring is scheduled to be serviced by * a work queue (or delayed service routine)? */ -#define ERST_EHB BIT(3) +#define ERST_EHB BIT_ULL(3) /* bits 63:4 - Event Ring Dequeue Pointer */ #define ERST_PTR_MASK GENMASK_ULL(63, 4) @@ -502,7 +502,7 @@ struct xhci_ep_ctx { #define CTX_TO_MAX_ESIT_PAYLOAD(p) (((p) >> 16) & 0xffff) /* deq bitmasks */ -#define EP_CTX_CYCLE_MASK BIT(0) +#define EP_CTX_CYCLE_MASK BIT_ULL(0) /* bits 63:4 - TR Dequeue Pointer */ #define TR_DEQ_PTR_MASK GENMASK_ULL(63, 4) From 1a35da325cac4d5bcad76a2aa943408a6f1d9000 Mon Sep 17 00:00:00 2001 From: Zihan Xi Date: Wed, 29 Jul 2026 09:16:53 +0000 Subject: [PATCH 246/612] packet: synchronize pressure clearing with ring reconfiguration packet_set_ring() updates the RX ring state under sk_receive_queue.lock, but used to publish the tpacket receive mode through po->prot_hook.func after releasing that lock. packet_poll() and packet_recvmsg() can then run the pressure clearing path after the ring has been cleared while still seeing tpacket_rcv, causing __packet_rcv_has_room() to dereference stale or NULL ring storage. Move the existing receive hook assignment into the same sk_receive_queue.lock section as the ring state update. Keep the assignment otherwise unchanged, including on TX ring reconfiguration, to avoid adding behavior changes that are not required for the fix. Serialize packet_recvmsg() pressure clearing with the same queue lock only after PACKET_SOCK_PRESSURE has been observed. If the flag is clear and the socket has moved away from tpacket_rcv, packet_set_ring() has already detached the socket and waited for synchronize_net(), so no new packet input can set the flag again. packet_poll() already holds sk_receive_queue.lock, so it uses the new unlocked helper directly. Fixes: 2ccdbaa6d55b ("packet: rollover lock contention avoidance") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: Codex:gpt-5.4 Signed-off-by: Zihan Xi Link: https://patch.msgid.link/f90b5688311fa278d1361ea8c6be0bf25967d591.1785247446.git.zihanx@nebusec.ai Signed-off-by: Paolo Abeni --- net/packet/af_packet.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c index 5ae0511e89e32c..0e1355be89f64b 100644 --- a/net/packet/af_packet.c +++ b/net/packet/af_packet.c @@ -1315,13 +1315,25 @@ static int packet_rcv_has_room(struct packet_sock *po, struct sk_buff *skb) return ret; } -static void packet_rcv_try_clear_pressure(struct packet_sock *po) +static void __packet_rcv_try_clear_pressure(struct packet_sock *po) { if (packet_sock_flag(po, PACKET_SOCK_PRESSURE) && __packet_rcv_has_room(po, NULL) == ROOM_NORMAL) packet_sock_flag_set(po, PACKET_SOCK_PRESSURE, false); } +static void packet_rcv_try_clear_pressure(struct packet_sock *po) +{ + struct sock *sk = &po->sk; + + if (!packet_sock_flag(po, PACKET_SOCK_PRESSURE)) + return; + + spin_lock_bh(&sk->sk_receive_queue.lock); + __packet_rcv_try_clear_pressure(po); + spin_unlock_bh(&sk->sk_receive_queue.lock); +} + static void packet_sock_destruct(struct sock *sk) { skb_queue_purge(&sk->sk_error_queue); @@ -4305,7 +4317,7 @@ static __poll_t packet_poll(struct file *file, struct socket *sock, TP_STATUS_KERNEL)) mask |= EPOLLIN | EPOLLRDNORM; } - packet_rcv_try_clear_pressure(po); + __packet_rcv_try_clear_pressure(po); spin_unlock_bh(&sk->sk_receive_queue.lock); spin_lock_bh(&sk->sk_write_queue.lock); if (po->tx_ring.pg_vec) { @@ -4545,14 +4557,14 @@ static int packet_set_ring(struct sock *sk, union tpacket_req_u *req_u, rb->frame_max = (req->tp_frame_nr - 1); rb->head = 0; rb->frame_size = req->tp_frame_size; + po->prot_hook.func = (po->rx_ring.pg_vec) ? + tpacket_rcv : packet_rcv; spin_unlock_bh(&rb_queue->lock); swap(rb->pg_vec_order, order); swap(rb->pg_vec_len, req->tp_block_nr); rb->pg_vec_pages = req->tp_block_size/PAGE_SIZE; - po->prot_hook.func = (po->rx_ring.pg_vec) ? - tpacket_rcv : packet_rcv; skb_queue_purge(rb_queue); if (atomic_long_read(&po->mapped)) pr_err("packet_mmap: vma is busy: %ld\n", From cf6f8b29befb92173659bcef6a441d274947bfae Mon Sep 17 00:00:00 2001 From: "Cen Zhang (Microsoft)" Date: Thu, 30 Jul 2026 18:20:06 -0400 Subject: [PATCH 247/612] net/openvswitch: check Ethernet header length in key_extract() When a packet arrives on an ARPHRD_NONE device (e.g. TUN), ovs_flow_key_extract() trusts the user-provided skb->protocol field: if it is ETH_P_TEB, the packet is classified as MAC_PROTO_ETHERNET and key_extract() is called without ensuring the skb has ETH_HLEN (14) bytes of linear data. key_extract() unconditionally pulls 2 * ETH_ALEN bytes for MAC addresses and parse_ethertype() pulls 2 more, either of which triggers a kernel BUG in __skb_pull() when the linear area is too small. kernel BUG at include/linux/skbuff.h:2848! RIP: 0010:key_extract+0xa7e/0xd90 net/openvswitch/flow.c:933 ovs_flow_key_extract+0x419/0xa70 ovs_vport_receive+0x222/0x390 netdev_frame_hook+0x3e0/0x630 tun_get_user+0x2d0c/0x38e0 Fixed by calling check_header() in key_extract() before accessing the Ethernet header. Fixes: 217ac77a3c25 ("openvswitch: allow L3 netdev ports") Reported-by: AutonomousCodeSecurity@microsoft.com Reviewed-by: Eelco Chaudron Signed-off-by: Cen Zhang (Microsoft) Reviewed-by: Ilya Maximets Link: https://patch.msgid.link/20260730222006.118652-1-blbllhy@gmail.com Signed-off-by: Paolo Abeni --- net/openvswitch/flow.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/net/openvswitch/flow.c b/net/openvswitch/flow.c index 66366982f60446..46c1d66aad8c3d 100644 --- a/net/openvswitch/flow.c +++ b/net/openvswitch/flow.c @@ -889,8 +889,6 @@ static int key_extract_l3l4(struct sk_buff *skb, struct sw_flow_key *key) * Ethernet header * @key: output flow key * - * The caller must ensure that skb->len >= ETH_HLEN. - * * Initializes @skb header fields as follows: * * - skb->mac_header: the L2 header. @@ -910,8 +908,6 @@ static int key_extract_l3l4(struct sk_buff *skb, struct sw_flow_key *key) */ static int key_extract(struct sk_buff *skb, struct sw_flow_key *key) { - struct ethhdr *eth; - /* Flags are always used as part of stats */ key->tp.flags = 0; @@ -926,6 +922,13 @@ static int key_extract(struct sk_buff *skb, struct sw_flow_key *key) skb_reset_network_header(skb); key->eth.type = skb->protocol; } else { + struct ethhdr *eth; + int err; + + err = check_header(skb, ETH_HLEN); + if (unlikely(err)) + return err; + eth = eth_hdr(skb); ether_addr_copy(key->eth.src, eth->h_source); ether_addr_copy(key->eth.dst, eth->h_dest); From 617bbd08714857c1613d7c550d43a9092ec0fb97 Mon Sep 17 00:00:00 2001 From: "Christian A. Ehrhardt" Date: Mon, 22 Jun 2026 23:57:17 +0200 Subject: [PATCH 248/612] drm/shmem_helper: Check VMA boundaries for PMD mappings In the ->huge_fault handler do not install a PMD huge page mapping if the huge page exceeds the boundaries of the VMA. All other ->huge_fault handlers have similar checks and the resulting mapping will trigger a VM_BUG_ON_VMA() if it ever reaches copy_pmd_range(). Cc: Pedro Demarchi Gomes Cc: Boris Brezillon Cc: stable@vger.kernel.org Fixes: fc3bbf34e643 ("drm/shmem-helper: Fix huge page mapping in fault handler") Signed-off-by: Christian A. Ehrhardt Reviewed-by: Boris Brezillon Link: https://patch.msgid.link/20260622215718.1532689-1-lk@c--e.de Signed-off-by: Boris Brezillon --- drivers/gpu/drm/drm_gem_shmem_helper.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/drm_gem_shmem_helper.c b/drivers/gpu/drm/drm_gem_shmem_helper.c index c989459eb21591..00807039f8d7db 100644 --- a/drivers/gpu/drm/drm_gem_shmem_helper.c +++ b/drivers/gpu/drm/drm_gem_shmem_helper.c @@ -597,9 +597,13 @@ static vm_fault_t try_insert_pfn(struct vm_fault *vmf, unsigned int order, #ifdef CONFIG_ARCH_SUPPORTS_PMD_PFNMAP } else if (order == PMD_ORDER) { unsigned long paddr = pfn << PAGE_SHIFT; + struct vm_area_struct *vma = vmf->vma; + unsigned long start = ALIGN_DOWN(vmf->address, PMD_SIZE); + unsigned long end = start + PMD_SIZE; + bool in_range = vma->vm_start <= start && end <= vma->vm_end; bool aligned = (vmf->address & ~PMD_MASK) == (paddr & ~PMD_MASK); - if (aligned && + if (aligned && in_range && folio_test_pmd_mappable(page_folio(pfn_to_page(pfn)))) { vm_fault_t ret; From bea79063a35121e49a86801671cca56fc4d83f7d Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Sun, 2 Aug 2026 22:35:36 +0200 Subject: [PATCH 249/612] docs: threat-model: clarify "security bug" vs "vulnerability" Throwing an LLM (Opus 5) at a file looking for random bugs after having read the threat model made it ignore certain bugs it found because "the threat model said they must be ignored". When asked why, the LLM rightfully reported the ambiguous wording used at a few places: "is not a security bug", which can also be read as "is not a bug", despite the rest of the document. That is particularly true when the LLM decides to look for info using grep. This change replaces "security bug" with "vulnerability" at a few places in order to remove this identified ambiguity, and uses "bugs" instead of issues in two such sentences to insist that what is described remains a bug. Cc: Greg KH Signed-off-by: Willy Tarreau Link: https://patch.msgid.link/20260802203540.3453-2-w@1wt.eu Signed-off-by: Greg Kroah-Hartman --- Documentation/process/threat-model.rst | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Documentation/process/threat-model.rst b/Documentation/process/threat-model.rst index 9dd8011dde828b..7990673072c4d0 100644 --- a/Documentation/process/threat-model.rst +++ b/Documentation/process/threat-model.rst @@ -98,11 +98,11 @@ measures whose purpose is to avoid crossing a security boundary when certain classes of bugs are found, but a failure of these extra protections do not constitute a vulnerability alone. -What does not constitute a security bug ---------------------------------------- +What classes of problems are not considered vulnerabilities +----------------------------------------------------------- In the Linux kernel's threat model, the following classes of problems are -**NOT** considered as Linux Kernel security bugs. However, when it is believed +**NOT** considered Linux Kernel vulnerabilities. However, when it is believed that the kernel could do better, they should be reported, so that they can be reviewed and fixed where reasonably possible, but they will be handled as any regular bug: @@ -111,8 +111,8 @@ regular bug: * outdated kernels and particularly end-of-life branches are out of the scope of the kernel's threat model: administrators are responsible for keeping - their system up to date. For a bug to qualify as a security bug, it must be - demonstrated that it affects actively maintained versions. + their system up to date. For a bug to qualify as a vulnerability, it must + be demonstrated that it affects actively maintained versions. * build-level: changes to the kernel configuration that are explicitly documented as lowering the security level (e.g. ``CONFIG_NOMMU``), or @@ -178,7 +178,7 @@ regular bug: involving tens of millions of threads, tens of thousands of CPUs, unrealistic CPU frequencies, RAM sizes or disk capacities, network speeds). - * issues whose reproduction requires hardware modification or emulation, + * bugs whose reproduction requires hardware modification or emulation, including fake USB devices that pretend to be another one. * as well as issues that can be triggered at a cost that is orders of @@ -208,17 +208,17 @@ regular bug: messages. * Leaks of kernel memory addresses/pointers do not constitute an immediately - exploitable vector and are not security bugs, though they must be reported - and fixed. + exploitable vector and are not vulnerabilities, though they must be + reported and fixed. * **Crafted file system images**: * bugs triggered by mounting a corrupted or maliciously crafted file system - image are generally not security bugs, as the kernel assumes the underlying + image are generally not vulnerabilities, as the kernel assumes the underlying storage media is under the administrator's control, unless the filesystem driver is specifically documented as being hardened against untrusted media. - * issues that are resolved, mitigated, or detected by running a filesystem + * bugs that are resolved, mitigated, or detected by running a filesystem consistency check (fsck) on the image prior to mounting. * **Physical access**: @@ -232,4 +232,4 @@ regular bug: * **Functional and performance regressions**: Any issue that can be mitigated by setting proper permissions and limits - doesn't qualify as a security bug. + doesn't qualify as a vulnerability. From 11f46e30e6885734be609e337e65a4784bdd282d Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Sun, 2 Aug 2026 22:35:37 +0200 Subject: [PATCH 250/612] docs: threat-model: move fake devices out of "non production use" When originally writing the "fake USB device" entry, it was difficult to find a suitable section for it and it ended up in "non production use" but that doesn't fit particularly well. Actually it's very similar to crafted filesystems, it's a matter of spec violation. Both drivers and FS are written against a spec, and what the threat model doesn't cover is out-of-spec use. So let's move the entry there and rename the crafted FS entry to "Non-conforming devices and media" instead. Overall it looks more consistent. The spec was tested agains Qwen3.6-27B-Architect-Polaris2-Fable-B-F451, Opus-5 and Gemini by reading the threat-model file, then reading a tens of FS and driver fixes, and they were now all properly classified as regular bugs, except two that Gemini and Opus rightfully classified as vulns (Qwen didn't spot the security potential but that's out of our scope). Cc: Greg KH Signed-off-by: Willy Tarreau Link: https://patch.msgid.link/20260802203540.3453-3-w@1wt.eu Signed-off-by: Greg Kroah-Hartman --- Documentation/process/threat-model.rst | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/Documentation/process/threat-model.rst b/Documentation/process/threat-model.rst index 7990673072c4d0..a68be888ce8e56 100644 --- a/Documentation/process/threat-model.rst +++ b/Documentation/process/threat-model.rst @@ -178,9 +178,6 @@ regular bug: involving tens of millions of threads, tens of thousands of CPUs, unrealistic CPU frequencies, RAM sizes or disk capacities, network speeds). - * bugs whose reproduction requires hardware modification or emulation, - including fake USB devices that pretend to be another one. - * as well as issues that can be triggered at a cost that is orders of magnitude higher than the expected benefits (e.g. fully functional keyboard emulator only to retrieve 7 uninitialized bytes in a structure, or @@ -211,16 +208,24 @@ regular bug: exploitable vector and are not vulnerabilities, though they must be reported and fixed. -* **Crafted file system images**: +* **Non-conforming devices and media**: - * bugs triggered by mounting a corrupted or maliciously crafted file system - image are generally not vulnerabilities, as the kernel assumes the underlying - storage media is under the administrator's control, unless the filesystem - driver is specifically documented as being hardened against untrusted media. + Drivers are implemented against a specification. When a device or a storage + medium violates the specification its driver was written against, the + resulting misbehaviour is a regular bug to be fixed, not a vulnerability, + unless the driver is specifically documented as being hardened against + hostile inputs. The following are therefore not considered vulnerabilities: - * bugs that are resolved, mitigated, or detected by running a filesystem + * bugs triggered by mounting a corrupted or maliciously crafted file system + image: mounting a block device is a privileged operation (see above), and + the administrator is responsible for the media they mount. This includes + issues that are resolved, mitigated, or detected by running a filesystem consistency check (fsck) on the image prior to mounting. + * bugs whose reproduction requires hardware modification or emulation, + including fake USB devices that pretend to be another one, or devices + reporting values outside their documented ranges. + * **Physical access**: Issues that require physical access to the machine, hardware modification, or From e447f7f33fa0a50d42d0539c161a662e67bd1a2c Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Sun, 2 Aug 2026 22:35:38 +0200 Subject: [PATCH 251/612] docs: security-bugs: clarify what counts as a valid version Too often we're getting reports saying "still valid in latest mainline" with no indication of when this was verified, making this indication pointless. Let's clarify it and insist on having a version or commit ID, and that the version must necessarily be for a kernel.org kernel and not a distro one. Cc: Greg KH Signed-off-by: Willy Tarreau Link: https://patch.msgid.link/20260802203540.3453-4-w@1wt.eu Signed-off-by: Greg Kroah-Hartman --- Documentation/process/security-bugs.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/process/security-bugs.rst b/Documentation/process/security-bugs.rst index 3c51ddde31dd91..6f7c21515e9ea4 100644 --- a/Documentation/process/security-bugs.rst +++ b/Documentation/process/security-bugs.rst @@ -18,6 +18,10 @@ information is helpful. The following information are absolutely necessary in **any** security bug report: * **affected kernel version range**: with no version indication, your report + will not be processed. Note that a time-dependent version such as "latest + mainline" is not acceptable. A stable identifier such as a commit ID or an + exact version is required. Versions designating kernels not coming from + kernel.org (such as distro kernels) are meaningless to maintainers and will not be processed. A significant part of reports are for bugs that have already been fixed, so it is extremely important that vulnerabilities are verified on recent versions (development tree or latest stable From 3d7c44f73765d98665fb97a4fb89c002c88ba1b9 Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Sun, 2 Aug 2026 22:35:39 +0200 Subject: [PATCH 252/612] docs: coding-assistant: explain important steps when looking for bugs Due to the increasing capabilities of available AI models, it's becoming common to see them used to find bugs anywhere. Unfortunately the quality of reports (especially when they're believed to be security relevant) is still lacking a lot. Let's add a section dedicated to bug finding, explaining the few mandatory steps (noting commit ID, writing the fix from the session that found the bug, building and testing, etc). This was tested both against Qwen3.6-27B-Architect-Polaris2-Fable-B-F451 running under Hermes, and Opus-5, and both followed the instructions to the letter, verifying their results and checking threat-model.rst to decline the vulnerability aspect. At least in the current form it's expected to improve the situation a little bit. Cc: Greg KH Signed-off-by: Willy Tarreau Link: https://patch.msgid.link/20260802203540.3453-5-w@1wt.eu Signed-off-by: Greg Kroah-Hartman --- Documentation/process/coding-assistants.rst | 37 +++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/Documentation/process/coding-assistants.rst b/Documentation/process/coding-assistants.rst index 899f4459c52d2d..e71df7d28467e2 100644 --- a/Documentation/process/coding-assistants.rst +++ b/Documentation/process/coding-assistants.rst @@ -57,3 +57,40 @@ Basic development tools (git, gcc, make, editors) should not be listed. Example:: Assisted-by: Claude:claude-3-opus coccinelle sparse + +Procedure for finding and fixing bugs +===================================== + +When an AI assistant is used to find and fix bugs, it **MUST** follow at least +these steps: + +1. Before starting, read the whole process documentation listed above, as well + as any other document mentioned in the request. Do not rely on isolated + parts found by keyword search. +2. Note the commit ID and Locate a bug as instructed. +3. For any bug found that is not trivial, verify that it looks real by + attempting to create a reproducer to demonstrate it. Lacking it may cause + the report to be ignored, as many unverified bug reports sent to maintainers + happen to be invalid. Stop here if it finally looks wrong. +4. Write a fix for the bug. This part is not optional: except in a few very + rare cases, an AI assistant able to find a bug is able to fix it. Note that + fixes written in the same session as used to find the bug will generally + lead to better and more accurate fixes as the LLM's reasoning context + remains present. +5. Build and verify that the fix works either using the reproducer or by + re-running a complete analysis; drop any fix that doesn't work and try + another one. The fix must not add build warnings and must pass the + checkpatch.pl checks (see submitting-patches.rst). +6. Commit the working fix with a detailed message describing the problem, the + solution and a Fixes tag. Do not add a Signed-off-by tag, and add an + Assisted-by tag, as described above. +7. Identify the maintainers and lists using scripts/get_maintainer.pl. + Documentation/process/security-bugs.rst shows how to do that. +8. Indicate what could not be done. If the fix could not be built or tested, or + if no reproducer could be produced, say so explicitly: maintainers currently + waste too much time analyzing unverified reports and untested fixes. +9. Read Documentation/process/threat-model.rst to determine whether the bug is + a vulnerability or a regular bug, and leave the result to the reporter for + review (the assistant must never send anything itself). Regular bugs are + submitted as described in Documentation/process/submitting-patches.rst, + vulnerabilities as described in Documentation/process/security-bugs.rst. From 6ffecfe0805e8811e7637e8f381b6740a3e9767b Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Sun, 2 Aug 2026 22:35:40 +0200 Subject: [PATCH 253/612] docs: security-bugs: clarify some mandatory steps for AI reports The security team is still seeing a lot of reports lacking a full patch and showing missing contents and formatting issues. Since AI assistants tend to be better than humans at following instructions, let's defer to coding-assistants.rst to follow mandatory steps, and insist on the plain text format, as well as asking for recipient addresses and an e-mail client setup hint to be mentioned early in the report for the reporter. Also add a link to https://github.com/masoncl/kres.git which contains way more advanced and detailed steps for those willing to go further. Tested with Opus-5 and Qwen3.6-27B-Architect-Polaris2-Fable-B-F451, both of which proceeded according to instructions. Cc: Greg KH Cc: Chris Mason Signed-off-by: Willy Tarreau Link: https://patch.msgid.link/20260802203540.3453-6-w@1wt.eu Signed-off-by: Greg Kroah-Hartman --- Documentation/process/security-bugs.rst | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Documentation/process/security-bugs.rst b/Documentation/process/security-bugs.rst index 6f7c21515e9ea4..c7dfecc87764c0 100644 --- a/Documentation/process/security-bugs.rst +++ b/Documentation/process/security-bugs.rst @@ -229,6 +229,28 @@ there is no need to consume a maintainer's time with an unimportant report. If the issue is clearly trivial and publicly discoverable, you should report it directly to the public mailing lists. +At the very least, when using an AI assistant to find and report bugs, make the +assistant read this file and threat-model.rst before proceeding, and have it +follow the procedure described in coding-assistants.rst: work on an up-to-date +mainline tree and note the commit ID, verify the bug is real, write a fix, +build it warning-free and checkpatch-clean, commit it with a Fixes tag, and +identify the maintainers with get_maintainer.pl. + +On top of that procedure, the AI assistant **MUST**: + +1. Prepare a plain-text report explaining the problem. It must contain the + four items listed at the top of this file as absolutely necessary: the + affected version or commit ID noted while following the procedure above, + the description of the problem, the reproducer or its status, and the + triggering conditions. +2. Start the report with a temporary section listing the recipients' addresses + (maintainers+list for the patch, maintainers only for the report and + reproducer), and with instructions reminding the reporter to check that + their email client is properly setup (see email-clients.rst), and leave it + to the reporter to remove that temporary section. + +A more detailed process is covered at https://github.com/masoncl/kres.git. + Sending the report ------------------ From a347304b2ca1a5377d5bd2d8a72e4b4f12afe648 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Sat, 1 Aug 2026 08:56:32 -0400 Subject: [PATCH 254/612] net/sched: cls_api: Always acquire rtnl_lock when destroying locked classifiers Another challenge with unlocked filters. There is a short window in tc_new_tfilter where a tcf_proto can be found and briefly referenced by a totally unrelated, unlocked classifier's request and cause a race. Feng created a poc which created this race with two threads, one creating a u32 filter and other a flower filter in the same chain/prio: 1. Both threads enter tc_new_tfilter, both find the chain empty, both drop filter_chain_lock 2. u32 finishes tcf_proto_create("u32") first, calls tcf_chain_tp_insert_unique() -> inserts u32_tp into the chain 3. flower finishes tcf_proto_create("flower") later, calls tcf_chain_tp_insert_unique() -> tcf_chain_tp_find() now sees u32_tp already there, takes a reference on it, destroys flower's own tp_new and returns u32_tp to the caller. Flower then hits the kind mismatch check (because it requested for kind "flower" but tp->ops->kind is "u32") and goes through the errout path which calls tcf_proto_put() on u32_tp. If the u32 thread has already gone through its own errout (its change() call failed on the PoC's empty options) and dropped its create and insert refs, flower's put is the last one and drops u32_tp's refcnt to zero. At this point tp->ops->destroy() runs in a context that never took rtnl_lock. When that happens, it might cause a UAF like the following (illustrated by the PoC): [ +0.000710] BUG: KASAN: slab-use-after-free in u32_init (net/sched/cls_u32.c:393) [ +0.000281] Read of size 8 at addr ffff888120022f00 by task poc_feng_xue/524 Call Trace: u32_init (net/sched/cls_u32.c:393) tc_new_tfilter (net/sched/cls_api.c:2378) Allocated by task 526: u32_init (net/sched/cls_u32.c:378) tc_new_tfilter (net/sched/cls_api.c:2378) Freed by task 522: kfree u32_destroy (net/sched/cls_u32.c:662) tcf_proto_destroy (net/sched/cls_api.c:446) tcf_proto_put (net/sched/cls_api.c:459) tc_new_tfilter (net/sched/cls_api.c:2459) Fix this by having tcf_proto_destroy() take rtnl_lock around tp->ops->destroy() for locked classifiers whenever rtnl is not held. To explain why I used a temp variable "not_lockless" I'd like to point to a semi-related note on rtnl_held vs TCF_PROTO_OPS_DOIT_UNLOCKED (adding here for future cleanup if deemed necessary): The rtnl_held parameter and the TCF_PROTO_OPS_DOIT_UNLOCKED flag are redundant sources of truth for whether rtnl_lock is held. Among the nine classifier destroy(..rtnl_held..) callbacks, only flower consults the rtnl_held parameter which it propagates to tc_setup_cb_destroy() and tc_setup_cb_call(). The other eight (u32, flow, bpf, cgroup, route, basic, fw, mall) ignore it entirely;-> those that call tc_setup_cb_destroy() (u32, bpf, mall) hardcode true always instead of forwarding the parameter. A future cleanup should remove the rtnl_held parameter from the destroy callback signature entirely and have callers rely solely on their knowledge whether they are running in an unlocked context. Fixes: 12db03b65c2b ("net: sched: extend proto ops to support unlocked classifiers") Reported-by: Feng Xue Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/20260801125632.360365-1-jhs@mojatatu.com Signed-off-by: Paolo Abeni --- net/sched/cls_api.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/net/sched/cls_api.c b/net/sched/cls_api.c index fee4524adc98ef..4e6a2812a4f325 100644 --- a/net/sched/cls_api.c +++ b/net/sched/cls_api.c @@ -443,7 +443,22 @@ static void tcf_chain_put(struct tcf_chain *chain); static void tcf_proto_destroy(struct tcf_proto *tp, bool rtnl_held, bool sig_destroy, struct netlink_ext_ack *extack) { - tp->ops->destroy(tp, rtnl_held, extack); + /* A locked classifier's destroy callback (e.g. u32_destroy) uses + * rtnl_dereference() and mutates shared structures (e.g. the + * tc_u_common hash list) that are only safe under rtnl_lock. When an + * unlocked classifier's request (e.g. flower on ingress) loses the + * tcf_chain_tp_insert_unique() race and ends up dropping the last + * reference on a locked classifier's proto, destroy() would run + * without rtnl held. Take it here in that case. + */ + bool not_lockless = !rtnl_held && + !(tp->ops->flags & TCF_PROTO_OPS_DOIT_UNLOCKED); + + if (not_lockless) + rtnl_lock(); + tp->ops->destroy(tp, rtnl_held || not_lockless, extack); + if (not_lockless) + rtnl_unlock(); tcf_proto_count_usesw(tp, false); if (sig_destroy) tcf_proto_signal_destroyed(tp->chain, tp); From 9c1cc4a7f79275ef93746f6247685763b475bfb0 Mon Sep 17 00:00:00 2001 From: Stephen Smalley Date: Tue, 4 Aug 2026 09:57:00 -0400 Subject: [PATCH 255/612] selinux: check level category sets once at load time As reported by Jiri Vozar, commit 7edea6e8c8e8 ("selinux: beef up isvalid checks") introduces a new loop in mls_level_isvalid() that causes ~89-94% throughput regression in System V IPC message queue operations (msgsnd/msgrcv). Move the expensive part of the ebitmap checking to policy load time instead as the reporter suggested. Link: https://lore.kernel.org/selinux/CAMgFczCi2Z011dNf84Amc0Q-qnTt0+VUjWY+Y7zPyXdaH35Jvw@mail.gmail.com/ Fixes: 7edea6e8c8e8 ("selinux: beef up isvalid checks") Reported-by: Jiri Vozar Suggested-by: Jiri Vozar Signed-off-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/ss/mls.c | 24 +++++++----------------- security/selinux/ss/policydb.c | 23 +++++++++++++++++++++++ 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/security/selinux/ss/mls.c b/security/selinux/ss/mls.c index 3cd36e2015fa22..40c62600650e8e 100644 --- a/security/selinux/ss/mls.c +++ b/security/selinux/ss/mls.c @@ -160,9 +160,6 @@ bool mls_level_isvalid(const struct policydb *p, const struct mls_level *l) { const char *name; const struct level_datum *levdatum; - struct ebitmap_node *node; - u32 bit; - int rc; if (!l->sens || l->sens > p->p_levels.nprim) return false; @@ -176,21 +173,14 @@ bool mls_level_isvalid(const struct policydb *p, const struct mls_level *l) return false; /* - * Validate that all bits set in l->cat are also be set in - * levdatum->level->cat and no bit in l->cat is larger than - * p->p_cats.nprim. + * l is valid iff every bit in l->cat is set in levdatum->level.cat + * and no bit in l->cat is larger than p->p_cats.nprim. + * policydb_index() has already verified that every bit set in + * levdatum->level.cat names a defined category, so containment is + * sufficient here. */ - rc = ebitmap_contains(&levdatum->level.cat, &l->cat, - p->p_cats.nprim); - if (!rc) - return false; - - ebitmap_for_each_positive_bit(&levdatum->level.cat, node, bit) { - if (!sym_name(p, SYM_CATS, bit)) - return false; - } - - return true; + return ebitmap_contains(&levdatum->level.cat, &l->cat, + p->p_cats.nprim); } bool mls_range_isvalid(const struct policydb *p, const struct mls_range *r) diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c index d88713201be92a..8a32666c0ba22f 100644 --- a/security/selinux/ss/policydb.c +++ b/security/selinux/ss/policydb.c @@ -665,6 +665,23 @@ static int cat_index(void *key, void *datum, void *datap) return 0; } +static int sens_cat_index_check(void *key, void *datum, void *datap) +{ + struct policydb *p = datap; + struct level_datum *levdatum = datum; + struct ebitmap_node *node; + u32 bit; + + ebitmap_for_each_positive_bit(&levdatum->level.cat, node, bit) { + if (bit >= p->p_cats.nprim || !sym_name(p, SYM_CATS, bit)) { + pr_err("SELinux: sensitivity %s allows undefined category %u\n", + (const char *)key, bit + 1); + return -EINVAL; + } + } + return 0; +} + /* clang-format off */ static int (*const index_f[SYM_NUM])(void *key, void *datum, void *datap) = { common_index, @@ -788,6 +805,12 @@ static int policydb_index(struct policydb *p) } } + if (p->mls_enabled) { + rc = hashtab_map(&p->p_levels.table, sens_cat_index_check, p); + if (rc) + goto out; + } + rc = 0; out: return rc; From 620f1e52a46f604635efd0fb78138afd6a513b5d Mon Sep 17 00:00:00 2001 From: Baul Lee Date: Tue, 4 Aug 2026 21:36:11 +0900 Subject: [PATCH 256/612] ALSA: FCP: fix OOB write in fcp_meter_ctl_get() fcp_ioctl_set_meter_map() bounds the user-supplied Level Meter map size by the driver's own limit of 255 if (map.map_size < 1 || map.map_size > 255 || map.meter_slots < 1 || map.meter_slots > 255) return -EINVAL; and passes it to fcp_add_new_ctl() as the control's channel count, where it is stored as elem->channels. Every control read writes into struct snd_ctl_elem_value, whose integer array is declared long value[128], so the limit is 128, not 255. fcp_meter_ctl_get() stores one 64-bit word per channel into that array with no bound of its own: for (i = 0; i < elem->channels; i++) { int idx = private->meter_level_map[i]; int value = idx < 0 ? 0 : le32_to_cpu(resp[idx]); ucontrol->value.integer.value[i] = value; } snd_ctl_elem_read_user() serves that object from memdup_user(_control, sizeof(*control)), 1224 bytes on LP64 out of kmalloc-2048. offsetof(struct snd_ctl_elem_value, value) is 72, so element i is written at byte 72 + 8 * i and element 144 already lands past the allocation. At map_size 255 the last store ends at byte 2112, 888 bytes past the object and 64 bytes into the adjacent slab object. The stored words come from the device and meter_level_map[] selects which word lands in which slot, so extent and contents are both controlled. The core does not catch this. snd_ctl_check_elem_info() is reached only from __snd_ctl_elem_info(), which snd_ctl_elem_read() calls under CONFIG_SND_CTL_DEBUG; without that option snd_ctl_skip_validation() is a compile-time true. __snd_ctl_add_replace() validates kcontrol->count and never inspects elem->channels. Installing an oversized map needs CAP_SYS_RAWIO, but the control outlives the hwdep descriptor that created it, so the out-of-bounds stores are issued by any process able to read controls on /dev/snd/controlC0. KASAN on 7.2.0-rc5 (arm64), triggered by an unprivileged control read: BUG: KASAN: slab-out-of-bounds in fcp_meter_ctl_get Write of size 8 at addr ffff000017af04c8 by task fcp_trigger/185 __asan_store8 fcp_meter_ctl_get snd_ctl_elem_read snd_ctl_ioctl Allocated by task 185: memdup_user snd_ctl_ioctl The buggy address is located 0 bytes to the right of allocated 1224-byte region [ffff000017af0000, ffff000017af04c8) Bound the map size by the ABI limit rather than by 255, and bound the store loop at the sink so it cannot run past the value array whatever elem->channels holds. Discovered by XBOW, triaged by Baul Lee Fixes: 46757a3e7d50 ("ALSA: FCP: Add Focusrite Control Protocol driver") Reported-by: Federico Kirschbaum Reported-by: Baul Lee Cc: stable@vger.kernel.org Signed-off-by: Baul Lee Link: https://patch.msgid.link/20260804123611.91715-1-baul.lee@xbow.com Signed-off-by: Takashi Iwai --- sound/usb/fcp.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/sound/usb/fcp.c b/sound/usb/fcp.c index 6f5dcd35e1d4a1..2bf572c6fdc40b 100644 --- a/sound/usb/fcp.c +++ b/sound/usb/fcp.c @@ -129,6 +129,10 @@ struct fcp_data { #define FCP_SEGMENT_APP_GOLD 0 +#define FCP_MAX_METER_MAP_SIZE \ + (sizeof_field(struct snd_ctl_elem_value, value.integer.value) / \ + sizeof(long)) + /* Forward declarations */ static int fcp_init(struct usb_mixer_interface *mixer, void *step0_resp, void *step2_resp); @@ -410,6 +414,9 @@ static int fcp_meter_ctl_get(struct snd_kcontrol *kctl, if (err < 0) return err; + if (WARN_ON_ONCE(elem->channels > FCP_MAX_METER_MAP_SIZE)) + return -EINVAL; + /* copy & translate from resp[] using meter_level_map[] */ for (i = 0; i < elem->channels; i++) { int idx = private->meter_level_map[i]; @@ -636,7 +643,8 @@ static int fcp_ioctl_set_meter_map(struct usb_mixer_interface *mixer, } /* Validate the map size */ - if (map.map_size < 1 || map.map_size > 255 || + if (map.map_size < 1 || + map.map_size > FCP_MAX_METER_MAP_SIZE || map.meter_slots < 1 || map.meter_slots > 255) return -EINVAL; From 2615f0fb90df8cf5a96133ca4be74294ed288604 Mon Sep 17 00:00:00 2001 From: Baul Lee Date: Tue, 4 Aug 2026 21:36:25 +0900 Subject: [PATCH 257/612] ALSA: us144mkii: re-anchor capture URBs on resubmission capture_urb_complete() resubmits each capture URB without anchoring it: usb_get_urb(urb); ret = usb_submit_urb(urb, GFP_ATOMIC); Anchoring is a property of a submission, not of the URB. The giveback path calls usb_unanchor_urb() before urb->complete(), so an URB resubmitted from its own completion handler is off the anchor. The capture URBs are anchored once, at stream start, so from the first completion onward tascam->capture_anchor is empty. tascam_free_urbs(), tascam_disconnect(), tascam_suspend() and the stop-work path all call usb_kill_anchored_urbs(&tascam->capture_anchor) to reap the capture URBs before anything is freed. With the anchor empty those calls return immediately and the URBs stay queued on the host controller. tascam_free_urbs() then returns the capture transfer buffers with usb_free_coherent(), and snd_card_free() releases the snd_card allocation that embeds tascam (card->private_data). The controller completes the queued URBs afterwards, writing device-supplied data into the freed transfer buffer, and capture_urb_complete() dereferences the freed driver object. KASAN on 7.2.0-rc5 (arm64): BUG: KASAN: slab-use-after-free in dummy_timer Write of size 512 at addr ffff000015b62000 __asan_memcpy dummy_timer hrtimer_run_softirq Allocated by task 64: usb_alloc_coherent tascam_alloc_urbs tascam_probe Freed by task 170: usb_free_coherent tascam_free_urbs tascam_disconnect usb_unbind_interface BUG: KASAN: slab-use-after-free in capture_urb_complete Read of size 4 at addr ffff0000170ee878 Freed by task 170: release_card_device snd_card_free tascam_disconnect Restore the usb_anchor_urb() between the reference count bump and the resubmission. That also makes the handler's usb_unanchor_urb() failure arm meaningful again and restores usb_kill_anchored_urbs() as a barrier on the disconnect, suspend and stop-work paths. The anchoring was removed on the premise that the URB is already anchored from the initial submission, which does not hold once the first giveback has run. Discovered by XBOW, triaged by Baul Lee Fixes: 5cff1529a2f9 ("ALSA: us144mkii: capture_urb_complete: redundant usb_anchor_urb corrupts anchor list on each resubmission") Reported-by: Federico Kirschbaum Reported-by: Baul Lee Cc: stable@vger.kernel.org Signed-off-by: Baul Lee Link: https://patch.msgid.link/20260804123625.91769-1-baul.lee@xbow.com Signed-off-by: Takashi Iwai --- sound/usb/usx2y/us144mkii_capture.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/usb/usx2y/us144mkii_capture.c b/sound/usb/usx2y/us144mkii_capture.c index fa01da98151aad..d7cdf9e0f68513 100644 --- a/sound/usb/usx2y/us144mkii_capture.c +++ b/sound/usb/usx2y/us144mkii_capture.c @@ -302,6 +302,7 @@ void capture_urb_complete(struct urb *urb) } usb_get_urb(urb); + usb_anchor_urb(urb, &tascam->capture_anchor); ret = usb_submit_urb(urb, GFP_ATOMIC); if (ret < 0) { dev_err_ratelimited(tascam->card->dev, From 5ff232d31106f45ac87c3b64e1d35a0667777797 Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Mon, 3 Aug 2026 10:50:21 -0300 Subject: [PATCH 258/612] ima: fix out-of-bounds read in xattr_verify() The digest-length check in xattr_verify() mixes int and size_t: if (xattr_len - sizeof(xattr_value->type) - hash_start >= iint->ima_hash->length) sizeof() yields size_t, so the usual arithmetic conversions promote the whole left-hand side to unsigned 64-bit before the subtraction runs. For a truncated xattr this underflows instead of going negative: a 1-byte IMA_XATTR_DIGEST_NG xattr (xattr_len == 1, hash_start == 1) turns "1 - 1 - 1" into SIZE_MAX, which is trivially >= ima_hash->length. The check then passes and the following memcmp() reads iint->ima_hash->length bytes starting past the end of the buffer vfs_getxattr_alloc() allocated for it. Nothing upstream clamps xattr_len back into a safe range first: ima_get_hash_algo() only special-cases xattr_len < 2 to pick a default algorithm, and evm_verifyxattr() returns INTEGRITY_UNKNOWN rather than failing when no HMAC key is loaded, so a truncated security.ima value reaches the length check as-is. Rewrite the comparison so every operand stays a signed int and no implicit conversion to size_t can occur. Fixes: 3ea7a56067e6 ("ima: provide hash algo info in the xattr") Cc: stable@vger.kernel.org Signed-off-by: Lincoln Wallace Signed-off-by: Mimi Zohar --- security/integrity/ima/ima_appraise.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index 18d0d9154317b8..ced2e131b061bf 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -274,8 +274,13 @@ static int xattr_verify(enum ima_hooks func, struct ima_iint_cache *iint, } else { set_bit(IMA_DIGSIG, &iint->atomic_flags); } - if (xattr_len - sizeof(xattr_value->type) - hash_start >= - iint->ima_hash->length) + /* + * Use addition, not subtraction: sizeof() forces unsigned + * math and a short xattr_len would wrap around, bypassing + * this bounds check. + */ + if (xattr_len >= (int)sizeof(xattr_value->type) + hash_start + + (int)iint->ima_hash->length) /* * xattr length may be longer. md5 hash in previous * version occupied 20 bytes in xattr, instead of 16 From b80bed5c871a80151351342c065579405ce77145 Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Mon, 27 Jul 2026 20:39:41 -0400 Subject: [PATCH 259/612] ima: Instantiate file_truncate and path_truncate hooks Instantiate the file_truncate and path_truncate LSM hooks to reset the action cache flags (IMA_DONE_MASK) as soon as truncation is requested, so the file, based on policy, is re-collected, re-measured, re-audited, and re-appraised on next access. Tested-by: Frederick Lawler Cc: stable@vger.kernel.org Signed-off-by: Mimi Zohar --- security/integrity/ima/ima_main.c | 39 +++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 5cea53fc36dffc..ff52becc303187 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -687,6 +687,43 @@ static int ima_file_check(struct file *file, int mask) MAY_APPEND), FILE_CHECK, 0, false); } +/* + * ima_reset_action_flags - invalidate action flags after a content change + * @inode: inode of the file whose content is about to be truncated + * + * Clear IMA_DONE_MASK so the file is re-collected, re-measured, + * re-audited, and re-appraised on next access. + */ +static void ima_reset_action_flags(struct inode *inode) +{ + struct ima_iint_cache *iint; + + if (!ima_policy_flag || !S_ISREG(inode->i_mode)) + return; + + iint = ima_iint_find(inode); + if (!iint) + return; + + mutex_lock(&iint->mutex); + iint->flags &= ~IMA_DONE_MASK; + iint->measured_pcrs = 0; + mutex_unlock(&iint->mutex); + return; +} + +static int ima_path_truncate(const struct path *path) +{ + ima_reset_action_flags(path->dentry->d_inode); + return 0; +} + +static int ima_file_truncate(struct file *file) +{ + ima_reset_action_flags(file_inode(file)); + return 0; +} + static int __ima_inode_hash(struct inode *inode, struct file *file, char *buf, size_t buf_size) { @@ -1300,11 +1337,13 @@ static struct security_hook_list ima_hooks[] __ro_after_init = { LSM_HOOK_INIT(file_release, ima_file_free), LSM_HOOK_INIT(mmap_file, ima_file_mmap), LSM_HOOK_INIT(file_mprotect, ima_file_mprotect), + LSM_HOOK_INIT(file_truncate, ima_file_truncate), LSM_HOOK_INIT(kernel_load_data, ima_load_data), LSM_HOOK_INIT(kernel_post_load_data, ima_post_load_data), LSM_HOOK_INIT(kernel_read_file, ima_read_file), LSM_HOOK_INIT(kernel_post_read_file, ima_post_read_file), LSM_HOOK_INIT(path_post_mknod, ima_post_path_mknod), + LSM_HOOK_INIT(path_truncate, ima_path_truncate), #ifdef CONFIG_IMA_MEASURE_ASYMMETRIC_KEYS LSM_HOOK_INIT(key_post_create_or_update, ima_post_key_create_or_update), #endif From c5f500161709f27719701334190dff2325868ef0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Winiarski?= Date: Wed, 24 Jun 2026 13:14:20 +0200 Subject: [PATCH 260/612] drm/xe: Fix memory leak in exec_queue_set_hang_replay_state() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The q->replay_state is blindly overwritten, which can potentially leak memory that was previously allocated by vmemdup_user(). Return an error if q->replay_state is not empty. Discovered using AI-assisted static analysis confirmed by Intel Product Security. Reported-by: Martin Hodo Fixes: 1026c1a73a96 ("drm/xe: Implement DRM_XE_EXEC_QUEUE_SET_HANG_REPLAY_STATE") Reviewed-by: Matthew Brost Link: https://patch.msgid.link/20260624111421.1258364-1-michal.winiarski@intel.com Signed-off-by: Michał Winiarski (cherry picked from commit f6b6cc1118bdbc4265fa8b3bdf8565b26f13e56e) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_exec_queue.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_exec_queue.c b/drivers/gpu/drm/xe/xe_exec_queue.c index 1b5ca3ce578a4a..663decb19304da 100644 --- a/drivers/gpu/drm/xe/xe_exec_queue.c +++ b/drivers/gpu/drm/xe/xe_exec_queue.c @@ -805,6 +805,9 @@ static int exec_queue_set_hang_replay_state(struct xe_device *xe, u64 __user *address = u64_to_user_ptr(value); void *ptr; + if (q->replay_state) + return -EINVAL; + ptr = vmemdup_user(address, size); if (XE_IOCTL_DBG(xe, IS_ERR(ptr))) return PTR_ERR(ptr); From d1643db3b037b57f2af7f85c3821d6fe69c492f6 Mon Sep 17 00:00:00 2001 From: Marcin Bernatowicz Date: Thu, 9 Jul 2026 09:59:45 +0200 Subject: [PATCH 261/612] drm/xe/uc: Apply RCS/CCS yield policy to SR-IOV VFs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VFs were missing the call to apply the global scheduling policy. Call xe_guc_submit_enable() during vf_uc_load_hw() to ensure VFs get the same policy enforcement as PF. Fixes: 26caeae9fb48 ("drm/xe/guc: Set RCS/CCS yield policy") Suggested-by: Michal Wajdeczko Signed-off-by: Marcin Bernatowicz Cc: Daniele Ceraolo Spurio Cc: Michal Wajdeczko Reviewed-by: Daniele Ceraolo Spurio Link: https://patch.msgid.link/20260709075945.1337660-1-marcin.bernatowicz@linux.intel.com Signed-off-by: Michał Winiarski (cherry picked from commit f09360e857130f7ab7f069e2421e6b4a6e502531) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_uc.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_uc.c b/drivers/gpu/drm/xe/xe_uc.c index 75091bde0d50c4..65f59f06177fd4 100644 --- a/drivers/gpu/drm/xe/xe_uc.c +++ b/drivers/gpu/drm/xe/xe_uc.c @@ -15,6 +15,7 @@ #include "xe_guc_pc.h" #include "xe_guc_rc.h" #include "xe_guc_engine_activity.h" +#include "xe_guc_submit.h" #include "xe_huc.h" #include "xe_sriov.h" #include "xe_wopcm.h" @@ -159,12 +160,14 @@ static int vf_uc_load_hw(struct xe_uc *uc) if (err) return err; - uc->guc.submission_state.enabled = true; - err = xe_guc_opt_in_features_enable(&uc->guc); if (err) return err; + err = xe_guc_submit_enable(&uc->guc); + if (err) + return err; + err = xe_gt_record_default_lrcs(uc_to_gt(uc)); if (err) return err; From d533882ce1060866a590257f2c77ee23eabef5b8 Mon Sep 17 00:00:00 2001 From: Qingshuang Fu Date: Tue, 4 Aug 2026 15:48:42 +0800 Subject: [PATCH 262/612] hwmon: (nzxt-smart2) Check return value of init_device() in probe The init_device() call in nzxt_smart2_hid_probe() can fail because it sends HID output reports to the hardware to detect fans and set the update interval. If the hardware is not responding or the HID reports fail, init_device() returns a negative error code. However, the return value was ignored, causing the probe to continue and register an hwmon device even though the device was never properly initialized. This leads to an inconsistent state where the driver reports stale data or blocks on wait queues that will never be woken. The same function's return value is already checked in the reset_resume() handler, confirming the author's intent that errors should be propagated. Note that this fix was not possible before commit 59d104b54b0b ("hwmon: (nzxt-smart2) Stop device IO before calling hid_hw_stop") because the out_hw_close error path was missing hid_device_io_stop(), which would have opened a use-after-free risk window. Fixes: 53e68c20aeb1 ("hwmon: add driver for NZXT RGB&Fan Controller/Smart Device v2.") Signed-off-by: Qingshuang Fu Link: https://lore.kernel.org/r/20260804074842.505923-1-fffsqian@163.com Signed-off-by: Guenter Roeck --- drivers/hwmon/nzxt-smart2.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/nzxt-smart2.c b/drivers/hwmon/nzxt-smart2.c index ff0c0bee0e8397..efc78a8f58c88c 100644 --- a/drivers/hwmon/nzxt-smart2.c +++ b/drivers/hwmon/nzxt-smart2.c @@ -754,7 +754,11 @@ static int nzxt_smart2_hid_probe(struct hid_device *hdev, hid_device_io_start(hdev); - init_device(drvdata, UPDATE_INTERVAL_DEFAULT_MS); + ret = init_device(drvdata, UPDATE_INTERVAL_DEFAULT_MS); + if (ret) { + dev_err(&hdev->dev, "init_device failed: %d\n", ret); + goto out_hw_close; + } drvdata->hwmon = hwmon_device_register_with_info(&hdev->dev, "nzxtsmart2", drvdata, From 0e243671bc7b8eaf00f83dd2f4367436dc0cff98 Mon Sep 17 00:00:00 2001 From: Kyle Zeng Date: Mon, 3 Aug 2026 12:27:57 +0000 Subject: [PATCH 263/612] ipv6: prevent in6_dev_get() from resurrecting inet6_dev in6_dev_get() reads dev->ip6_ptr under RCU and then unconditionally increments its refcount. Device teardown can clear the pointer and drop the last reference between these operations. The increment then resurrects an object whose RCU free has already been queued, so callers can use it after it is freed. Use refcount_inc_not_zero() and return NULL when the object has already reached zero. RCU keeps the memory accessible through the attempted reference acquisition, and a successful increment pins the object for the caller. An independent run on the exact unpatched 6f5156d7a31a (v7.2-rc3) kernel reproduced the invalid reference acquisition as UID 1000: refcount_t: addition on 0; use-after-free. ip6_mc_source+0xef4/0x17e0 It was followed by the corresponding reference underflow in ip6_mc_source(). The supplied trace from the same unpatched revision additionally shows the access after the RCU read-side section ends: BUG: KASAN: slab-use-after-free in mutex_lock+0x76/0xe0 Write of size 8 at addr ffff888015b50240 by task poc/1219 Bug found and triaged by OpenAI Security Research and validated by Trail of Bits. Fixes: 8814c4b53381 ("[IPV6] ADDRCONF: Convert addrconf_lock to RCU.") Cc: stable@vger.kernel.org Signed-off-by: Kyle Zeng Co-developed-by: David Lee Signed-off-by: David Lee Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260803122758.666112-1-david.lee@trailofbits.com Signed-off-by: Jakub Kicinski --- include/net/addrconf.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/net/addrconf.h b/include/net/addrconf.h index 8ced27a8229b6e..e6764245995f25 100644 --- a/include/net/addrconf.h +++ b/include/net/addrconf.h @@ -405,8 +405,8 @@ static inline struct inet6_dev *in6_dev_get(const struct net_device *dev) rcu_read_lock(); idev = rcu_dereference(dev->ip6_ptr); - if (idev) - refcount_inc(&idev->refcnt); + if (idev && !refcount_inc_not_zero(&idev->refcnt)) + idev = NULL; rcu_read_unlock(); return idev; } From a213dfaa2596c1c0dc4dae91c14fbfa499c03223 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Mon, 3 Aug 2026 02:47:40 -0700 Subject: [PATCH 264/612] x86/mce: Set up the polling timer before CMCI discovery I hit the following on one of my machines: mce: CPU0 BANK15 CMCI inherited storm ------------[ cut here ]------------ ODEBUG: assert_init not available (active state 0) object: (____ptrval____) object type: timer_list hint: 0x0 WARNING: lib/debugobjects.c:632 at debug_object_assert_init+0x178/0x230, CPU#0: swapper/0/0 CPU: 0 UID: 0 PID: 0 Comm: swapper/0 Not tainted 7.2.0-rc5 #3 PREEMPTLAZY RIP: 0010:debug_object_assert_init+0x18f/0x230 Call Trace: __mod_timer mce_timer_kick cmci_discover intel_init_cmci mce_intel_feature_init mcheck_cpu_init identify_cpu identify_boot_cpu arch_cpu_finalize_init start_kernel A second splat follows right after, from timer_setup() finding that same timer already queued: ODEBUG: init active (active state 0) object: (____ptrval____) object type: timer_list hint: stub_timer+0x0/0x10 This is happening because CMCI storm detection is trying to modify the timer before latter was properly set up. Set up the timer first. __mcheck_cpu_setup_timer() only calls timer_setup(), and depends on neither the generic nor the vendor init. [ bp: Massage commit message. ] Fixes: 1f68ce2a0272 ("x86/mce: Handle Intel threshold interrupt storms") Signed-off-by: Breno Leitao Signed-off-by: Borislav Petkov (AMD) Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260803-mce_timer_init-v1-1-9539db424330@debian.org --- arch/x86/kernel/cpu/mce/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/x86/kernel/cpu/mce/core.c b/arch/x86/kernel/cpu/mce/core.c index 9bba1e2f03af78..cfb74be19994d4 100644 --- a/arch/x86/kernel/cpu/mce/core.c +++ b/arch/x86/kernel/cpu/mce/core.c @@ -2266,10 +2266,10 @@ void mcheck_cpu_init(struct cpuinfo_x86 *c) mca_cfg.initialized = 1; + __mcheck_cpu_setup_timer(); __mcheck_cpu_init_generic(); __mcheck_cpu_init_vendor(c); __mcheck_cpu_init_prepare_banks(); - __mcheck_cpu_setup_timer(); cr4_set_bits(X86_CR4_MCE); } From 0dabe8a56f772f0ece46d2597799f412c277d874 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 4 Aug 2026 14:12:31 -0700 Subject: [PATCH 265/612] hwmon: (pmbus/lm25066) Fix PMBus coefficient calculations In lm25066_probe(), the PMBus coefficients for current and power are scaled based on the shunt resistor value. The calculation evaluates the multiplication using 32-bit arithmetic because info->m is an int and shunt is a u32: static int lm25066_probe(struct i2c_client *client) { ... info->m[PSC_CURRENT_IN] = info->m[PSC_CURRENT_IN] * shunt / 1000; info->m[PSC_POWER] = info->m[PSC_POWER] * shunt / 1000; ... } For large coefficients like 26882 (LM25056) or 15076 (LM5066i), a device tree shunt-resistor-micro-ohms value exceeding approximately 159,000 (159 mOhm, which is physically valid for low-current applications) causes the intermediate product to exceed UINT_MAX (4,294,967,295). This results in a silent wraparound before the division by 1000. Furthermore, if the wrapped value has the most significant bit set, converting it back to the signed int info->m results in negative coefficients. This logic error leads to drastically corrupted current and power readings, which can cause erratic thermal or power management behavior in the system. Fix the problem by using 64-bit operations for the multiply/divide operations. This can still overflow, but only for unreasonably large shunt resistor values. Reported-by: Sashiko Fixes: 94ee5fcc240fe ("hwmon: (pmbus/lm25066) Support configurable sense resistor values") Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/lm25066.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/pmbus/lm25066.c b/drivers/hwmon/pmbus/lm25066.c index 2be50cf1bbdea5..c797e2e8258fde 100644 --- a/drivers/hwmon/pmbus/lm25066.c +++ b/drivers/hwmon/pmbus/lm25066.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include "pmbus.h" @@ -542,8 +543,8 @@ static int lm25066_probe(struct i2c_client *client) if (of_property_read_u32(client->dev.of_node, "shunt-resistor-micro-ohms", &shunt)) shunt = 1000; - info->m[PSC_CURRENT_IN] = info->m[PSC_CURRENT_IN] * shunt / 1000; - info->m[PSC_POWER] = info->m[PSC_POWER] * shunt / 1000; + info->m[PSC_CURRENT_IN] = DIV_ROUND_CLOSEST_ULL((u64)info->m[PSC_CURRENT_IN] * shunt, 1000); + info->m[PSC_POWER] = DIV_ROUND_CLOSEST_ULL((u64)info->m[PSC_POWER] * shunt, 1000); #if IS_ENABLED(CONFIG_SENSORS_LM25066_REGULATOR) /* LM25056 doesn't support OPERATION */ From 26fa4d17c023dbe5427a92d3a7bd9ae1d1e58bc5 Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Tue, 4 Aug 2026 14:21:12 -0500 Subject: [PATCH 266/612] smb: client: fix SMB1 TRANS2 multi-response truncation in SendReceive() When a TRANS2 response is split across multiple secondary packets, coalesce_t2() assembles the payload into the large response buffer. Two bugs cause SendReceive() to copy only a small fraction of the assembled buffer into the caller's output buffer. This manifests when listing a large directory on an SMB1 share (observed against Windows XP); the first getdents returns only partial results, and subsequent getdents returns EINVAL. Bug 1: coalesce_t2() computes the coalesced size as: *pdu_len += total_in_src; cifs_demultiplex_thread() resets *pdu_len to each secondary's own pdu_length before calling coalesce_t2(), so this accumulates from the wrong baseline on every secondary after the first. Bug 2: after reassembly, cifs_demultiplex_thread() sets mid->resp_buf_size to the final secondary's raw packet size. This value is later used as the memcpy length, so only a portion of the coalesced response is copied. Fix both by replacing the stale *pdu_len arithmetic with smbCalcSize(), which reads the BCC field that coalesce_t2() maintains correctly throughout reassembly. Fixes: 83bfbd0bb902 ("cifs: Remove the RFC1002 header from smb_hdr") Cc: stable@vger.kernel.org Signed-off-by: Frank Sorenson Reviewed-by: Paulo Alcantara Signed-off-by: Steve French --- fs/smb/client/smb1transport.c | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/fs/smb/client/smb1transport.c b/fs/smb/client/smb1transport.c index 53abb29fe71bdd..966f2cf83a5174 100644 --- a/fs/smb/client/smb1transport.c +++ b/fs/smb/client/smb1transport.c @@ -260,9 +260,23 @@ SendReceive(const unsigned int xid, struct cifs_ses *ses, goto out; if (out_buf) { - *pbytes_returned = resp_iov.iov_len; - if (resp_iov.iov_len) - memcpy(out_buf, resp_iov.iov_base, resp_iov.iov_len); + /* Use smbCalcSize() for both single- and multi-part T2 responses, + * both here and in coalesce_t2(). + */ + unsigned int copy_len; + if (WARN_ON_ONCE(!resp_iov.iov_base)) { + rc = -EIO; + goto out; + } + copy_len = smbCalcSize(resp_iov.iov_base); + if (copy_len > CIFSMaxBufSize + MAX_CIFS_HDR_SIZE) { + cifs_dbg(VFS, "response size %u exceeds buffer\n", + copy_len); + rc = -ENOBUFS; + goto out; + } + *pbytes_returned = copy_len; + memcpy(out_buf, resp_iov.iov_base, copy_len); } out: @@ -386,11 +400,13 @@ coalesce_t2(char *second_buf, struct smb_hdr *target_hdr, unsigned int *pdu_len) } put_bcc(byte_count, target_hdr); - byte_count = *pdu_len; - byte_count += total_in_src; + /* use smbCalcSize() rather than *pdu_len: the demux loop resets + * *pdu_len to each secondary's pdu_length, making it unreliable. + */ + byte_count = smbCalcSize(target_hdr); /* don't allow buffer to overflow */ if (byte_count > CIFSMaxBufSize + MAX_CIFS_HDR_SIZE) { - cifs_dbg(FYI, "coalesced BCC exceeds buffer size (%u)\n", + cifs_dbg(FYI, "coalesced size exceeds buffer size (%u)\n", byte_count); return -ENOBUFS; } From 6e3abef2a27e7402a94111c9eff85d887e64a309 Mon Sep 17 00:00:00 2001 From: Martin Kaiser Date: Tue, 4 Aug 2026 21:46:35 +0200 Subject: [PATCH 267/612] selftests/ftrace: refactor eprobes test to fix argument checks The add/remove eprobe test installs an eprobe for the openat syscall and runs ls. It checks the filenames that were opened by ls against a whitelist and a blacklist. Commit 206b25c09080 ("tracing: eprobe: read the complete FILTER_PTR_STRING pointer") fixed access to some string fields in eprobes. This triggers test failures as the blacklist does not allow relative paths for the openat parameters. What makes this test unstable is the fact that the openat calls vary a lot between different systems. Refactor the test to make it more robust. "cd " will issue a chdir syscall with the target directory as parameter. Set an eprobe on the sys_enter_chdir event and filter for the exact directory name. Allow (fault) as fallback. Link: https://lore.kernel.org/all/20260804194705.760893-1-martin@kaiser.cx/ Fixes: 206b25c09080 ("tracing: eprobe: read the complete FILTER_PTR_STRING pointer") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-lkp/202607151010.b68428e1-lkp@intel.com Signed-off-by: Martin Kaiser Signed-off-by: Masami Hiramatsu (Google) --- .../ftrace/test.d/dynevent/add_remove_eprobe.tc | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_eprobe.tc b/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_eprobe.tc index c300eb0202620c..e2322693d0c320 100644 --- a/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_eprobe.tc +++ b/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_eprobe.tc @@ -1,16 +1,16 @@ #!/bin/sh # SPDX-License-Identifier: GPL-2.0 # description: Generic dynamic event - add/remove eprobe events -# requires: dynamic_events events/syscalls/sys_enter_openat ". []":README +# requires: dynamic_events events/syscalls/sys_enter_chdir ". []":README echo 0 > events/enable clear_dynamic_events SYSTEM="syscalls" -EVENT="sys_enter_openat" +EVENT="sys_enter_chdir" FIELD="filename" -EPROBE="eprobe_open" +EPROBE="eprobe_chdir" OPTIONS="file=+0(\$filename):ustring" echo "e:$EPROBE $SYSTEM/$EVENT $OPTIONS" >> dynamic_events @@ -18,20 +18,14 @@ grep -q "$EPROBE" dynamic_events test -d events/eprobes/$EPROBE echo 1 > events/eprobes/$EPROBE/enable -ls +cd /sys/kernel/tracing echo 0 > events/eprobes/$EPROBE/enable -content=`grep '^ *ls-' trace | grep 'file='` -nocontent=`grep '^ *ls-' trace | grep 'file=' | grep -v -e '"/' -e '"."' -e '(fault)' ` || true - +content=`grep -e 'file="/sys/kernel/tracing"\|(fault)' trace` if [ -z "$content" ]; then exit_fail fi -if [ ! -z "$nocontent" ]; then - exit_fail -fi - echo "-:$EPROBE" >> dynamic_events ! grep -q "$EPROBE" dynamic_events From 06232cb44bc8e81adc2f1d40a01bed830b607ea2 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Mon, 3 Aug 2026 11:51:56 +0200 Subject: [PATCH 268/612] net: stmmac: resume PHY before hardware setup when opening the interface Since the referenced commit, changing the MTU on a running interface no longer disconnects and reconnects the PHY; __stmmac_release() merely stops phylink, which also suspends the PHY (BMCR power-down) when WoL is not enabled. __stmmac_open() then performs the DMA software reset in stmmac_hw_setup() before phylink_start() resumes the PHY again. IEEE 802.3 22.2.4.1.5 allows a PHY to stop its receive clock while powered down, and stmmac requires a running receive clock for the DMA software reset to complete (the phylink config sets mac_requires_rxc). On such setups, e.g. the RK3566-based Home Assistant Green with an RTL8211F-VD PHY in RGMII mode, any runtime MTU change now times out and leaves the interface dead: rk_gmac-dwmac fe010000.ethernet end0: Failed to reset the dma rk_gmac-dwmac fe010000.ethernet end0: stmmac_hw_setup: DMA engine initialization failed rk_gmac-dwmac fe010000.ethernet end0: __stmmac_open: Hw setup failed rk_gmac-dwmac fe010000.ethernet end0: failed reopening the interface after MTU change In the field this is triggered by NetworkManager applying an MTU while activating the connection, breaking networking entirely. The same regression has also been reported on i.MX8MP and reproduced on SoCFPGA based systems. Resume the PHY in __stmmac_open() before the hardware setup, making it the counterpart of the phylink_stop() in __stmmac_release(), like stmmac_resume() already does for the same reason. phylink_start() also resumes the PHY, but only after stmmac_hw_setup(), and it cannot be moved before the hardware setup since it may bring the link up immediately from a workqueue, racing with the initialization (see the comment in stmmac_resume()). For the regular ndo_open path the PHY has just been attached and is not suspended, in which case phylink_prepare_resume() does nothing. Fixes: db299a0c09e9 ("net: stmmac: move PHY handling out of __stmmac_open()/release()") Link: https://github.com/home-assistant/operating-system/issues/4858 Tested-by: Alexander Stein Signed-off-by: Stefan Agner Tested-by: Maxime Chevallier Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260803095156.132827-1-stefan@agner.ch Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 97b28cfa14c268..a71f0df263785d 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -4134,6 +4134,15 @@ static int __stmmac_open(struct net_device *dev, dma_conf->tx_queue[i].tbs = priv->dma_conf.tx_queue[i].tbs; memcpy(&priv->dma_conf, dma_conf, sizeof(*dma_conf)); + /* The PHY is suspended when the interface is reopened without + * disconnecting the PHY, e.g. on MTU change. IEEE 802.3 allows PHYs + * to stop their receive clock while powered down, but the DMA + * software reset in stmmac_hw_setup() requires a running receive + * clock, and phylink_start() below resumes the PHY only after the + * hardware setup. Resume a suspended PHY here first. + */ + phylink_prepare_resume(priv->phylink); + stmmac_reset_queues_param(priv); ret = stmmac_hw_setup(dev); From 452636ea5410a96e02ebaaf80b21e3620b98e0dd Mon Sep 17 00:00:00 2001 From: Yangyu Chen Date: Sun, 2 Aug 2026 23:46:00 +0800 Subject: [PATCH 269/612] net: atlantic: free stranded TX buffers on ring deinit aq_vec_deinit() drains the TX rings with a single aq_ring_tx_clean() call, which frees at most AQ_CFG_TX_CLEAN_BUDGET (256) descriptors and stops at hw_head, which no longer moves once aq_vec_stop() has stopped the hardware and NAPI. Completed descriptors beyond the budget and everything still posted in [hw_head, sw_tail) keep their skb or xdp_frame when the interface goes down: aq_vec_ring_free() then frees the buffer ring and the references are lost for good. Today this is a silent memory leak on every interface down under TX/XDP_TX load. With the conversion of the RX path to page_pool posted for net-next it becomes much more visible: XDP_TX frames carry fragment references on the RX ring's page_pool, so a single stranded frame keeps the pool's inflight count above zero forever. page_pool_destroy() then never completes, the pool is leaked together with its pages, and "page_pool_release_retry() stalled pool shutdown" is warned every 60 seconds from that point on, on every ifdown, XDP detach or ring resize under XDP_TX load. Bring back aq_ring_tx_deinit() as it was before the removal and use it for teardown again, with one extension: TX rings can hold xdp_frames nowadays, so release those too. They are returned with xdp_return_frame() since this runs in process context. Fixes: eb36bedf28be ("net: aquantia: remove function aq_ring_tx_deinit") Cc: stable@vger.kernel.org # v4.11+ Reviewed-by: Sukhdeep Singh Signed-off-by: Yangyu Chen Acked-by: Mina Almasry Link: https://patch.msgid.link/tencent_EEDC35FAF2750A3A6A0B39BAE0E2C484860A@qq.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/aquantia/atlantic/aq_ring.c | 29 +++++++++++++++++++ .../net/ethernet/aquantia/atlantic/aq_ring.h | 1 + .../net/ethernet/aquantia/atlantic/aq_vec.c | 2 +- 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/aquantia/atlantic/aq_ring.c b/drivers/net/ethernet/aquantia/atlantic/aq_ring.c index 8ff07de2bd5240..81685a4dc5a6d3 100644 --- a/drivers/net/ethernet/aquantia/atlantic/aq_ring.c +++ b/drivers/net/ethernet/aquantia/atlantic/aq_ring.c @@ -360,6 +360,35 @@ bool aq_ring_tx_clean(struct aq_ring_s *self) return !!budget; } +void aq_ring_tx_deinit(struct aq_ring_s *self) +{ + if (!self) + return; + + for (; self->sw_head != self->sw_tail; + self->sw_head = aq_ring_next_dx(self, self->sw_head)) { + struct aq_ring_buff_s *buff = &self->buff_ring[self->sw_head]; + struct device *ndev = aq_nic_get_dev(self->aq_nic); + + if (buff->is_mapped) { + if (buff->is_sop) { + dma_unmap_single(ndev, buff->pa, buff->len, + DMA_TO_DEVICE); + } else { + dma_unmap_page(ndev, buff->pa, buff->len, + DMA_TO_DEVICE); + } + } + + if (buff->is_eop) { + if (buff->skb) + dev_kfree_skb_any(buff->skb); + else if (buff->xdpf) + xdp_return_frame(buff->xdpf); + } + } +} + static void aq_rx_checksum(struct aq_ring_s *self, struct aq_ring_buff_s *buff, struct sk_buff *skb) diff --git a/drivers/net/ethernet/aquantia/atlantic/aq_ring.h b/drivers/net/ethernet/aquantia/atlantic/aq_ring.h index a70b880ada67e9..6431cc62962f83 100644 --- a/drivers/net/ethernet/aquantia/atlantic/aq_ring.h +++ b/drivers/net/ethernet/aquantia/atlantic/aq_ring.h @@ -202,6 +202,7 @@ void aq_ring_update_queue_state(struct aq_ring_s *ring); void aq_ring_queue_wake(struct aq_ring_s *ring); void aq_ring_queue_stop(struct aq_ring_s *ring); bool aq_ring_tx_clean(struct aq_ring_s *self); +void aq_ring_tx_deinit(struct aq_ring_s *self); int aq_xdp_xmit(struct net_device *dev, int num_frames, struct xdp_frame **frames, u32 flags); int aq_ring_rx_clean(struct aq_ring_s *self, diff --git a/drivers/net/ethernet/aquantia/atlantic/aq_vec.c b/drivers/net/ethernet/aquantia/atlantic/aq_vec.c index 2f9033ceed8ce3..05814fea0f5f75 100644 --- a/drivers/net/ethernet/aquantia/atlantic/aq_vec.c +++ b/drivers/net/ethernet/aquantia/atlantic/aq_vec.c @@ -275,7 +275,7 @@ void aq_vec_deinit(struct aq_vec_s *self) for (i = 0U; self->tx_rings > i; ++i) { ring = self->ring[i]; - aq_ring_tx_clean(&ring[AQ_VEC_TX_ID]); + aq_ring_tx_deinit(&ring[AQ_VEC_TX_ID]); aq_ring_rx_deinit(&ring[AQ_VEC_RX_ID]); } From e8e7471ef686b6c002218fee9671cc61992ae01a Mon Sep 17 00:00:00 2001 From: Yangyu Chen Date: Sun, 2 Aug 2026 23:46:38 +0800 Subject: [PATCH 270/612] net: atlantic: free RX pages of consumed but not refilled buffers aq_ring_rx_deinit() only walks [sw_head, sw_tail), the region posted to hardware. Since the page reuse strategy was added, a cleaned RX buffer keeps its page (and its DMA mapping) in the ring for reuse, and refill is batched: aq_ring_rx_fill() returns early until AQ_CFG_RX_REFILL_THRES slots are free. Slots that were consumed but not yet reposted therefore sit in the complementary [sw_tail, sw_head) gap with a live page, and the deinit walk never visits them: up to a refill batch worth of pages and DMA mappings leak on every interface down. Walk the whole ring instead and release whatever is still there. Also bail out if the buffer ring is already gone: a partial aq_ptp_ring_alloc() failure frees the ring but leaves aq_nic set, so aq_ptp_ring_deinit() still gets here on the unwind path. Cc: stable@vger.kernel.org # v5.2+ Fixes: 46f4c29d9de6 ("net: aquantia: optimize rx performance by page reuse strategy") Reviewed-by: Sukhdeep Singh Signed-off-by: Yangyu Chen Acked-by: Mina Almasry Link: https://patch.msgid.link/tencent_607CBA8237DA438E36B844318B21538DE008@qq.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/aquantia/atlantic/aq_ring.c | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/aquantia/atlantic/aq_ring.c b/drivers/net/ethernet/aquantia/atlantic/aq_ring.c index 81685a4dc5a6d3..e1193c6719d9d6 100644 --- a/drivers/net/ethernet/aquantia/atlantic/aq_ring.c +++ b/drivers/net/ethernet/aquantia/atlantic/aq_ring.c @@ -950,15 +950,29 @@ int aq_ring_rx_fill(struct aq_ring_s *self) void aq_ring_rx_deinit(struct aq_ring_s *self) { - if (!self) + unsigned int i; + + if (!self || !self->buff_ring) return; - for (; self->sw_head != self->sw_tail; - self->sw_head = aq_ring_next_dx(self, self->sw_head)) { - struct aq_ring_buff_s *buff = &self->buff_ring[self->sw_head]; + /* Release every page still owned by the ring. + * + * Walking [sw_head, sw_tail) is not enough: refill is batched + * (aq_ring_rx_fill() waits for AQ_CFG_RX_REFILL_THRES free slots), + * so slots that were cleaned but not yet reposted accumulate in the + * [sw_tail, sw_head) gap, and they keep their page for reuse. Walk + * the whole ring and release whatever is left. + */ + for (i = 0; i < self->size; i++) { + struct aq_ring_buff_s *buff = &self->buff_ring[i]; + + if (!buff->rxdata.page) + continue; aq_free_rxpage(&buff->rxdata, aq_nic_get_dev(self->aq_nic)); } + + self->sw_head = self->sw_tail; } void aq_ring_free(struct aq_ring_s *self) From 2cbd8a4e5e09aa232a1f8d56ce3d070b18ab2b10 Mon Sep 17 00:00:00 2001 From: Alok Tiwari Date: Sat, 1 Aug 2026 03:09:20 -0700 Subject: [PATCH 271/612] bnge: use int for bnge_fix_rings_count() return value bnge_fix_rings_count() returns 0 on success or a negative errno on failure However, bnge_adjust_rings() stores its return value in a u16 variable, causing negative error codes such as -ENOMEM to be converted to a large positive value. Use an int for the return code variable so that error values are preserved and propagated correctly. Fixes: 627c67f038d2 ("bng_en: Add resource management support") Signed-off-by: Alok Tiwari Reviewed-by: Bhargava Marreddy Link: https://patch.msgid.link/20260801100923.1498570-1-alok.a.tiwari@oracle.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnge/bnge_resc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/broadcom/bnge/bnge_resc.c b/drivers/net/ethernet/broadcom/bnge/bnge_resc.c index 0e94f092813e06..4711dd4945ffeb 100644 --- a/drivers/net/ethernet/broadcom/bnge/bnge_resc.c +++ b/drivers/net/ethernet/broadcom/bnge/bnge_resc.c @@ -163,7 +163,8 @@ static int bnge_adjust_rings(struct bnge_dev *bd, u16 *rx, u16 tx_chunks = bnge_num_tx_to_cp(bd, *tx); if (tx_chunks != *tx) { - u16 tx_saved = tx_chunks, rc; + u16 tx_saved = tx_chunks; + int rc; rc = bnge_fix_rings_count(rx, &tx_chunks, max_nq, sh); if (rc) From 1505b2cb6ae1c7e8ac0c6e4590a204ffc3ab2b24 Mon Sep 17 00:00:00 2001 From: Laurent Vivier Date: Fri, 31 Jul 2026 11:27:11 +0200 Subject: [PATCH 272/612] usbnet: cap max_mtu for drivers without bind callback usbnet_probe() initializes max_mtu to ETH_MAX_MTU and only caps it inside the if (info->bind) block. Drivers without a bind callback never enter this block, so max_mtu stays at ETH_MAX_MTU. QEMU's usb-net device (0x0525/0xa4a2) is claimed by the cdc_subset driver which has no bind callback. The guest accepts any MTU from DHCP (e.g. 65520 from passt), leading to TCP segments that exceed the device's 2048-byte receive buffer and are silently dropped. Initialize max_mtu to net->mtu at probe time and update it inside the bind block. Fixes: f77f0aee4da4 ("net: use core MTU range checking in USB NIC drivers") Cc: jarod@redhat.com Cc: stable@vger.kernel.org Link: https://gitlab.com/qemu-project/qemu/-/issues/3268 Link: https://bugs.passt.top/show_bug.cgi?id=189 Signed-off-by: Laurent Vivier Link: https://patch.msgid.link/20260731092711.857684-1-lvivier@redhat.com Signed-off-by: Jakub Kicinski --- drivers/net/usb/usbnet.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/usb/usbnet.c b/drivers/net/usb/usbnet.c index 25518635b7b72d..a19ecf718f36a6 100644 --- a/drivers/net/usb/usbnet.c +++ b/drivers/net/usb/usbnet.c @@ -1794,7 +1794,7 @@ usbnet_probe(struct usb_interface *udev, const struct usb_device_id *prod) */ dev->hard_mtu = net->mtu + net->hard_header_len; net->min_mtu = 0; - net->max_mtu = ETH_MAX_MTU; + net->max_mtu = net->mtu; net->netdev_ops = &usbnet_netdev_ops; net->watchdog_timeo = TX_TIMEOUT_JIFFIES; @@ -1804,6 +1804,7 @@ usbnet_probe(struct usb_interface *udev, const struct usb_device_id *prod) // allow device-specific bind/init procedures // NOTE net->name still not usable ... if (info->bind) { + net->max_mtu = ETH_MAX_MTU; status = info->bind(dev, udev); if (status < 0) goto out1; From 5d1c224dd914579524a183a514c12b95095d12ce Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Mon, 3 Aug 2026 08:29:36 +0800 Subject: [PATCH 273/612] net: openvswitch: reallocate update replies for mismatched IDs ovs_flow_cmd_new() preallocates the optional reply skb before it takes ovs_mutex and before it knows which existing flow will be updated. That is normally fine because the skb is sized from the request flow identifier. That identifier also becomes the inserted flow's identifier. For updates, however, a request with a UFID may miss the UFID lookup and then fall back to the flow key lookup. That lookup can legitimately find an existing key-identified flow. UFIDs are optional and the flow key is the primary identifier. For echoed replies, ovs_flow_cmd_fill_info() writes the matched flow's identifier, not the request identifier used for the preallocation. A short request UFID can therefore leave too little room for the key identifier. The fill can then fail with -EMSGSIZE and hit the BUG_ON(error < 0) in the update path. Once the update target has been resolved, reallocate the reply skb if the matched flow needs a larger reply than the request identifier allowed. Do this before replacing the actions so the request can still fail cleanly if the rare extra allocation fails. Fixes: 74ed7ab9264c ("openvswitch: Add support for unique flow IDs.") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Zhiling Zou Reviewed-by: Ilya Maximets Link: https://patch.msgid.link/f7bbd3c30ce81a39156e226b3872d73abed21d2f.1785644623.git.zhilinz@nebusec.ai Signed-off-by: Jakub Kicinski --- net/openvswitch/datapath.c | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/net/openvswitch/datapath.c b/net/openvswitch/datapath.c index eaf332b156d731..ae69b2cabab9e5 100644 --- a/net/openvswitch/datapath.c +++ b/net/openvswitch/datapath.c @@ -1113,9 +1113,8 @@ static int ovs_flow_cmd_new(struct sk_buff *skb, struct genl_info *info) error = -EEXIST; goto err_unlock_ovs; } - /* The flow identifier has to be the same for flow updates. - * Look for any overlapping flow. - */ + + /* Look for any overlapping flow. */ if (unlikely(!ovs_flow_cmp(flow, &match))) { if (ovs_identifier_is_key(&flow->id)) flow = ovs_flow_tbl_lookup_exact(&dp->table, @@ -1127,6 +1126,30 @@ static int ovs_flow_cmd_new(struct sk_buff *skb, struct genl_info *info) goto err_unlock_ovs; } } + + if (unlikely(reply)) { + size_t cur, req; + + cur = ovs_flow_cmd_msg_size(acts, &new_flow->id, + ufid_flags); + req = ovs_flow_cmd_msg_size(acts, &flow->id, + ufid_flags); + if (cur < req) { + struct sk_buff *resized; + + resized = ovs_flow_cmd_alloc_info(acts, + &flow->id, + info, false, + ufid_flags); + if (IS_ERR(resized)) { + error = PTR_ERR(resized); + goto err_unlock_ovs; + } + kfree_skb(reply); + reply = resized; + } + } + /* Update actions. */ old_acts = ovsl_dereference(flow->sf_acts); rcu_assign_pointer(flow->sf_acts, acts); From e7386770be1bf810bcd6af39d1e4bfeab3408430 Mon Sep 17 00:00:00 2001 From: Bobby Eshleman Date: Mon, 3 Aug 2026 16:47:29 -0700 Subject: [PATCH 274/612] net/mlx5e: fix BQL reset on SQ re-activation mlx5e_queue_start() deactivates and re-activates all channels but closes only the queue being restarted. mlx5e_activate_txqsq() then unconditionally calls netdev_tx_reset_queue(), zeroing the BQL counters of channels that kept their in-flight TX WQEs. The next completion then over-charges and trips the BUG_ON() in dql_completed(): kernel BUG at lib/dynamic_queue_limits.c:99! RIP: 0010:dql_completed+0x23d/0x280 Call Trace: mlx5e_poll_tx_cq+0x668/0xa60 mlx5e_napi_poll+0x5b/0x7b0 net_rx_action+0x15a/0x580 Reset BQL only when the SQ has no bytes in flight (sq->cc == sq->pc). In the case that reset is skipped, the outstanding WQEs will eventually complete and rebalance the dql. The dql->limit is carried across the reset. Fixes: b2588ea40ec9 ("net/mlx5e: Implement queue mgmt ops and single channel swap") Signed-off-by: Bobby Eshleman Reviewed-by: Tariq Toukan Link: https://patch.msgid.link/20260803-mlx5-bql-v3-1-a30d4c66fe1d@meta.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en_main.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c index 7d47a1da8b6b64..f0407a850ea824 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c @@ -1939,8 +1939,10 @@ int mlx5e_open_txqsq(struct mlx5e_channel *c, u32 tisn, int txq_ix, void mlx5e_activate_txqsq(struct mlx5e_txqsq *sq) { sq->txq = netdev_get_tx_queue(sq->netdev, sq->txq_ix); + /* Reset BQL only when the SQ has no bytes in flight. */ + if (sq->cc == sq->pc) + netdev_tx_reset_queue(sq->txq); set_bit(MLX5E_SQ_STATE_ENABLED, &sq->state); - netdev_tx_reset_queue(sq->txq); netif_tx_start_queue(sq->txq); netif_queue_set_napi(sq->netdev, sq->txq_ix, NETDEV_QUEUE_TYPE_TX, sq->cq.napi); } From 1d0fc6c7ea49994b8ff50d02979d1e4207ec6c4f Mon Sep 17 00:00:00 2001 From: Shravya KN Date: Fri, 31 Jul 2026 12:09:33 -0700 Subject: [PATCH 275/612] bnxt_en: Move RSS table fill outside __bnxt_hwrm_vnic_set_rss() This is a refactor patch with no change in behavior. The caller will now fill the RSS table before calling __bnxt_hwrm_vnic_set_rss(). In the next patch, we'll add code to determine the default ring for the VNIC when we fill the RSS table. Reviewed-by: Kalesh AP Reviewed-by: Pavan Chebbi Signed-off-by: Shravya KN Signed-off-by: Michael Chan Link: https://patch.msgid.link/20260731190937.807270-2-michael.chan@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index 004ef99f453068..4599b6e0d21946 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -6706,6 +6706,9 @@ static void bnxt_fill_hw_rss_tbl(struct bnxt *bp, struct bnxt_vnic_info *vnic) bool no_rss = !(vnic->flags & BNXT_VNIC_RSS_FLAG); u16 i, j; + if (!vnic->rss_table) + return; + /* Fill the RSS indirection table with ring group ids */ for (i = 0, j = 0; i < HW_HASH_INDEX_SIZE; i++) { if (!no_rss) @@ -6745,13 +6748,8 @@ static void __bnxt_hwrm_vnic_set_rss(struct bnxt *bp, struct hwrm_vnic_rss_cfg_input *req, struct bnxt_vnic_info *vnic) { - if (bp->flags & BNXT_FLAG_CHIP_P5_PLUS) { - bnxt_fill_hw_rss_tbl_p5(bp, vnic); - if (bp->flags & BNXT_FLAG_CHIP_P7) - req->flags |= VNIC_RSS_CFG_REQ_FLAGS_IPSEC_HASH_TYPE_CFG_SUPPORT; - } else { - bnxt_fill_hw_rss_tbl(bp, vnic); - } + if (bp->flags & BNXT_FLAG_CHIP_P7) + req->flags |= VNIC_RSS_CFG_REQ_FLAGS_IPSEC_HASH_TYPE_CFG_SUPPORT; if (bp->rss_hash_delta) { req->hash_type = cpu_to_le32(bp->rss_hash_delta); @@ -6803,6 +6801,7 @@ static int bnxt_hwrm_vnic_set_rss_p5(struct bnxt *bp, if (!set_rss) return hwrm_req_send(bp, req); + bnxt_fill_hw_rss_tbl_p5(bp, vnic); __bnxt_hwrm_vnic_set_rss(bp, req, vnic); ring_tbl_map = vnic->rss_table_dma_addr; nr_ctxs = bnxt_get_nr_rss_ctxs(bp, bp->rx_nr_rings); @@ -10866,6 +10865,7 @@ static int __bnxt_setup_vnic(struct bnxt *bp, struct bnxt_vnic_info *vnic) } skip_rss_ctx: + bnxt_fill_hw_rss_tbl(bp, vnic); /* configure default vnic, ring grp */ rc = bnxt_hwrm_vnic_cfg(bp, vnic); if (rc) { From 53f01cd594e223aabb538d5288e60111523c96f2 Mon Sep 17 00:00:00 2001 From: Shravya KN Date: Fri, 31 Jul 2026 12:09:34 -0700 Subject: [PATCH 276/612] bnxt_en: Determine and store default RX ring in vnic structure Each VNIC has a default RX ring. The purpose of the default RX ring is to provide a destination for any packets that cannot be parsed by the RSS logic. Up until now, the default RX ring is always Ring 0. We neglected to take care of this default RX ring when adding the queue restart feature. If ring 0 (default ring) is re-started, it may now have a new FW ring ID after freeing the old one and allocating a new one. The VNIC now may have a stale default ring and it may generate an internal exception. This exception may appear in dmesg: FW reported unknown error type 10 The best way to resolve this issue is to use a more appropriate ring for the default ring instead of always ring 0. Ring 0 may not even be in the RSS table, especially on a new RSS context. This patch adds the logic to determine and store the proper default RX ring for a VNIC. For an RSS VNIC, the default ring is the lowest ring number in the RSS table. The next patch will add proper logic to update the VNIC if the default ring changes after queue restart. Reviewed-by: Kalesh AP Reviewed-by: Pavan Chebbi Signed-off-by: Shravya KN Signed-off-by: Michael Chan Link: https://patch.msgid.link/20260731190937.807270-3-michael.chan@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 33 +++++++++++++++-------- drivers/net/ethernet/broadcom/bnxt/bnxt.h | 1 + 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index 4599b6e0d21946..8b1f4e9fc7803c 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -6704,25 +6704,36 @@ int bnxt_get_nr_rss_ctxs(struct bnxt *bp, int rx_rings) static void bnxt_fill_hw_rss_tbl(struct bnxt *bp, struct bnxt_vnic_info *vnic) { bool no_rss = !(vnic->flags & BNXT_VNIC_RSS_FLAG); - u16 i, j; + u16 i, j, min_j = bp->rx_nr_rings - 1; if (!vnic->rss_table) - return; + goto skip_rss_tbl; /* Fill the RSS indirection table with ring group ids */ for (i = 0, j = 0; i < HW_HASH_INDEX_SIZE; i++) { if (!no_rss) j = bp->rss_indir_tbl[i]; + min_j = min(j, min_j); vnic->rss_table[i] = cpu_to_le16(vnic->fw_grp_ids[j]); } + +skip_rss_tbl: + if (vnic->rss_table && !no_rss) + vnic->default_rx_ring = min_j; + else if (vnic->flags & BNXT_VNIC_RFS_FLAG) + vnic->default_rx_ring = vnic->vnic_id - 1; + else if ((vnic->vnic_id == 1) && BNXT_CHIP_TYPE_NITRO_A0(bp)) + vnic->default_rx_ring = bp->rx_nr_rings - 1; + else + vnic->default_rx_ring = 0; } static void bnxt_fill_hw_rss_tbl_p5(struct bnxt *bp, struct bnxt_vnic_info *vnic) { + u16 tbl_size, i, min_j = bp->rx_nr_rings - 1; __le16 *ring_tbl = vnic->rss_table; struct bnxt_rx_ring_info *rxr; - u16 tbl_size, i; tbl_size = bnxt_get_rxfh_indir_size(bp->dev); @@ -6735,6 +6746,7 @@ static void bnxt_fill_hw_rss_tbl_p5(struct bnxt *bp, j = ethtool_rxfh_context_indir(vnic->rss_ctx)[i]; else j = bp->rss_indir_tbl[i]; + min_j = min(j, min_j); rxr = &bp->rx_ring[j]; ring_id = rxr->rx_ring_struct.fw_ring_id; @@ -6742,6 +6754,7 @@ static void bnxt_fill_hw_rss_tbl_p5(struct bnxt *bp, ring_id = bnxt_cp_ring_for_rx(bp, rxr); *ring_tbl++ = cpu_to_le16(ring_id); } + vnic->default_rx_ring = min_j; } static void @@ -6938,8 +6951,9 @@ int bnxt_hwrm_vnic_cfg(struct bnxt *bp, struct bnxt_vnic_info *vnic) return rc; if (bp->flags & BNXT_FLAG_CHIP_P5_PLUS) { - struct bnxt_rx_ring_info *rxr = &bp->rx_ring[0]; + struct bnxt_rx_ring_info *rxr; + rxr = &bp->rx_ring[vnic->default_rx_ring]; req->default_rx_ring_id = cpu_to_le16(rxr->rx_ring_struct.fw_ring_id); req->default_cmpl_ring_id = @@ -6972,13 +6986,7 @@ int bnxt_hwrm_vnic_cfg(struct bnxt *bp, struct bnxt_vnic_info *vnic) req->cos_rule = cpu_to_le16(0xffff); } - if (vnic->flags & BNXT_VNIC_RSS_FLAG) - ring = 0; - else if (vnic->flags & BNXT_VNIC_RFS_FLAG) - ring = vnic->vnic_id - 1; - else if ((vnic->vnic_id == 1) && BNXT_CHIP_TYPE_NITRO_A0(bp)) - ring = bp->rx_nr_rings - 1; - + ring = vnic->default_rx_ring; grp_idx = bp->rx_ring[ring].bnapi->index; req->dflt_ring_grp = cpu_to_le16(bp->grp_info[grp_idx].fw_grp_id); req->lb_rule = cpu_to_le16(0xffff); @@ -11171,6 +11179,9 @@ static int bnxt_setup_nitroa0_vnic(struct bnxt *bp) return rc; } + /* Setup the proper default RX ring */ + bnxt_fill_hw_rss_tbl(bp, vnic); + rc = bnxt_hwrm_vnic_cfg(bp, vnic); if (rc) { netdev_err(bp->dev, "Cannot allocate special vnic for NS2 A0: %x\n", diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.h b/drivers/net/ethernet/broadcom/bnxt/bnxt.h index 6335dfc14c985b..44ea8fc7b766d7 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.h +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.h @@ -1334,6 +1334,7 @@ struct bnxt_vnic_info { #define BNXT_VNIC_RSSCTX_FLAG 0x40 struct ethtool_rxfh_context *rss_ctx; u32 vnic_id; + u16 default_rx_ring; }; struct bnxt_rss_ctx { From 0b137529a8997caf67190ca1d71ba8bbdb44fbfb Mon Sep 17 00:00:00 2001 From: Shravya KN Date: Fri, 31 Jul 2026 12:09:35 -0700 Subject: [PATCH 277/612] bnxt_en: Refresh VNIC default ring on queue restart if needed When a queue is restarted, refresh VNIC_CFG for all VNICs whose default RX ring is the restarted ring. This will eliminate this possible FW warning caused by a stale default ring in the VNIC: FW reported unknown error type 10 Fixes: 5ac066b7b062 ("bnxt_en: Fix queue start to update vnic RSS table") Reviewed-by: Kalesh AP Reviewed-by: Pavan Chebbi Signed-off-by: Shravya KN Signed-off-by: Michael Chan Link: https://patch.msgid.link/20260731190937.807270-4-michael.chan@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index 8b1f4e9fc7803c..6deb8f348d07b4 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -11098,6 +11098,11 @@ static int bnxt_set_vnic_mru_p5(struct bnxt *bp, struct bnxt_vnic_info *vnic, vnic->vnic_id, rc); return rc; } + if (rxr_id == vnic->default_rx_ring) { + rc = bnxt_hwrm_vnic_cfg(bp, vnic); + if (rc) + return rc; + } } vnic->mru = mru; bnxt_hwrm_vnic_update(bp, vnic, From c3faf548a00f4c17100cc9204746975fa46a73b9 Mon Sep 17 00:00:00 2001 From: Michael Chan Date: Fri, 31 Jul 2026 12:09:36 -0700 Subject: [PATCH 278/612] bnxt_en: Disable EOP for TPA on all chips to prevent data corruption EOP (End of frame padding) on the AGG ring may cause overlapping of zero padding at the end of one segment with the next segment's data. If Relaxed Ordering (RO) is enabled, the zero padding may overwrite valid data in the next segment and corrupt the data. Older chips (P5 and older) do not automatically disable RO when EOP is enabled. On some ARM systems, data corruption was reported on 57508 (P5) chips with RO enabled. Always disable EOP on all chips on the AGG rings when TPA is enabled to fix the data corruption. Fixes: bfcd8d791ec1 ("bnxt_en: Add fast path logic for TPA on 57500 chips.") Reviewed-by: Pavan Chebbi Reviewed-by: Kalesh AP Signed-off-by: Michael Chan Link: https://patch.msgid.link/20260731190937.807270-5-michael.chan@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index 6deb8f348d07b4..25099077fe4f68 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -4611,11 +4611,14 @@ static void bnxt_init_one_rx_agg_ring_rxbd(struct bnxt *bp, type = ((u32)rxr->rx_page_size << RX_BD_LEN_SHIFT) | RX_BD_TYPE_RX_AGG_BD; - /* On P7, setting EOP will cause the chip to disable - * Relaxed Ordering (RO) for TPA data. Disable EOP for - * potentially higher performance with RO. + /* Disable EOP if TPA is enabled to prevent overlapping zero + * padding with the next segment's data. On P7_PLUS, EOP will + * automatically disable Relaxed Ordering (RO) to prevent + * potential data corruption (and may degrade performance). On + * older chips, RO will not be automatically disabled and may + * cause corruption. */ - if (BNXT_CHIP_P5_AND_MINUS(bp) || !(bp->flags & BNXT_FLAG_TPA)) + if (!(bp->flags & BNXT_FLAG_TPA)) type |= RX_BD_FLAGS_AGG_EOP; bnxt_init_rxbd_pages(ring, type); From 80eaf88efec33ac77ed7726d066c4f2f932cc329 Mon Sep 17 00:00:00 2001 From: Keegan Freyhof Date: Fri, 31 Jul 2026 12:09:37 -0700 Subject: [PATCH 279/612] bnxt_en: Fix PTP PPS setting bug The existing driver logic is always turning on PTP_CLK_REQ_PPS regardless of the "on" parameter passed to bnxt_ptp_enable(). During shutdown, PTP_CLK_REQ_PPS may be turned off and this bug will do the opposite and may trigger a PCIe PTM request TLP. On some systems this can trigger a PCIe AER. Fix it by properly configuring PTP_CLK_REQ_PPS based on the "on" parameter. Fixes: 9e518f25802c ("bnxt_en: 1PPS functions to configure TSIO pins") Reviewed-by: Pavan Chebbi Signed-off-by: Keegan Freyhof Signed-off-by: Michael Chan Link: https://patch.msgid.link/20260731190937.807270-6-michael.chan@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnxt/bnxt_ptp.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt_ptp.c b/drivers/net/ethernet/broadcom/bnxt/bnxt_ptp.c index 5d41dc1bc78209..b89293dfd2d1e3 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt_ptp.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt_ptp.c @@ -495,12 +495,15 @@ static int bnxt_ptp_enable(struct ptp_clock_info *ptp_info, return rc; case PTP_CLK_REQ_PPS: /* Configure PHC PPS IN */ - rc = bnxt_ptp_cfg_pin(bp, 0, BNXT_PPS_PIN_PPS_IN); + pin_id = 0; + if (!on) + break; + rc = bnxt_ptp_cfg_pin(bp, pin_id, BNXT_PPS_PIN_PPS_IN); if (rc) return rc; rc = bnxt_ptp_cfg_event(bp, BNXT_PPS_EVENT_INTERNAL); if (!rc) - ptp->pps_info.pins[0].event = BNXT_PPS_EVENT_INTERNAL; + ptp->pps_info.pins[pin_id].event = BNXT_PPS_EVENT_INTERNAL; return rc; default: netdev_err(ptp->bp->dev, "Unrecognized PIN function\n"); From 26444eb71465c9934d9d418ef69c43f61185329b Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Thu, 23 Jul 2026 16:16:31 +0100 Subject: [PATCH 280/612] mm/vmalloc: acquire init_mm lock on huge vmap to avoid ptdump UAF Patch series "mm: fix UAF caused by race between ptdump and vmap pgtable freeing", v6. Kernel page table walkers fall into two broad categories - those ranges where no exclusion is required via walk_kernel_page_table_range_lockless() and those where exclusion is required via walk_kernel_page_table_range() or walk_page_range_debug(). The former category is used only by arm64 arch code operating on ranges it both wholly owns and does not concurrently write. The latter category consists of kernel page table walkers operating on ranges that are wholly owned (but which need exclusion against concurrent writers). The lock used for exclusion is the mmap lock, and for kernel ranges this is the mmap lock on init_mm. ptdump is a special case being both the only user of walk_page_range_debug(), and the only case in which it walks ranges it does not own. This presents a problem, as page tables may be freed under ptdump. And indeed there is a use-after-free bug in the kernel as a result, which this series addresses. vmap promotes page tables to huge leaf entries where possible, freeing the lower page table when it does. It does this with no meaningful locks held against concurrent ptdump walks. As a result, use-after-free can currently occur. This series addresses the issue by having the vmap huge promotion logic acquire the mmap read lock while both setting the huge page table entry and freeing the prior leaf page table. The ptdump code already acquires the mmap write lock, so by doing so we ensure that the ptdump walker only ever observes either the huge page table entry or the existing page table entry, and nothing is freed underneath it. A mitigation for this issue was already applied for arm64 in commit fa93b45fd397 ("arm64: Enable vmalloc-huge with ptdump"), which this series has to deal with carefully. This mitigation resolves the issue by acquiring the mmap read lock on init_mm on vmap page table free if a ptdump is in progress. However the fix in this series would cause a deadlock if we were to simply apply it for arm64 without also reverting the change. This is because vmap may acquire the read lock before ptdump attempts to acquire the write lock, which then gets queued, and rwsem starvation rules mean that the (unacknowledged) nested mmap read lock in the arm64 code would also block, meaning the original read lock is never released and thus deadlock. This series works around this by #ifndef CONFIG_ARM64'ing the mmap read lock in vmap logic, then partially reverting commit fa93b45fd397 ("arm64: Enable vmalloc-huge with ptdump"), keeping the enablement of huge vmap support, and removing the ifdeffery with the partial revert patch. There are related issues that are also addressed in this series: * x86 page attribute logic, specifically Change Page Attributes (CPA), implements a feature whereby huge ranges can be collapsed into huge leaf entries. This can similarly cause a UAF when done in parallel with a ptdump walk, so similarly acquire the init_mm mmap lock to avoid this. * The CPA logic allows concurrent page table manipulation and CPA collapse, meaning the former risks accessing a page table the latter frees. Fix this by acquiring mmap write lock on init_mm across the whole CPA collapse operation and read lock on the page table manipulation. * x86 and arm64 permit walks of non-kernel mm's (both allowing efi mm walks, and in x86's case arbitrary mm's), so we ensure kernel mappings remain stable by locking the init_mm as well as the mm being walked. The ordering of patches is established for both strict dependencies (the arm64 partial revert in particular has to be done after the vmap changes) and logical ones (the non-kernel mm fix only makes sense once the vmap/CPA fixes are in place). This patch (of 3): Currently there is a nasty race between ptdump and vmap when attempting to map a huge P4D, PUD or PMD entry: * ptdump walks kernel page table ranges it doesn't own. * When vmap maps ranges it tries to promotes existing ones to huge page tables in vmap_try_huge_[p4d,pud,pmd]() at P4D, PUD and PMD level, freeing the lower page table in [p4d,pud,pmd]_free_[pud,pmd,pte]_page() when it succeeds. Both of these things can happen at the same time and as a result ptdump can access a freed page table, resulting in a use-after-free and memory corruption. This is possible because while ptdump_walk_pgd() holds both the mem hotplug lock and the mmap write lock before invoking walk_page_range_debug(), vmap takes no relevant locks at all. Fix this by holding the mmap read lock in vmap_try_huge_*() when freeing page tables. The read lock is sufficient: ptdump is the only walker that must be excluded and it holds the mmap write lock. Other holders of the read lock may run concurrently, but each exclusively owns the range it operates on and cannot reach the page tables freed here. We also hold the lock while assigning the huge page table entry, which means page table walkers observe only the huge or non-huge page table entry. We use a trylock to prevent ptdump from blocking vmap making forward progress. This is fine because it's an optimisation in any case, and thus the vmap can safely proceed regardless. All other kernel page table walkers that touch vmalloc ranges either exclusively own the memory walked or acquire the mmap lock, so this correctly excludes those walkers. One wrinkle here is commit fa93b45fd397 ("arm64: Enable vmalloc-huge with ptdump"), which addresses the issue for arm64 only by explicitly acquiring the mmap read lock on kernel page table freeing should a concurrent ptdump be in progress. This is problematic as vmap may acquire the mmap read lock prior to ptdump attempting to acquire an mmap write lock, leading to a deadlock when the mmap read lock is slept upon on page table freeing due to rwsem anti-starvation. We work around this by predicating the mmap lock being taken on !CONFIG_ARM64 for the time being. With this patch applied, a follow up will partially revert commit fa93b45fd397 ("arm64: Enable vmalloc-huge with ptdump") and at that stage remove the arm64 ifdeffery. We also update walk_page_range_debug() to assert the mmap write lock unconditionally and update the comment here to reflect this change. The issue has existed as long as ptdump was available and vmap freed page tables when promoting to a huge leaf entry, that is, since commit b6bdb7517c3d ("mm/vmalloc: add interfaces to free unmapped page table") for huge ioremap, and commit 121e6f3258fe ("mm/vmalloc: hugepage vmalloc mappings") for huge vmalloc. Since the former is the earlier of the two we choose that for our Fixes tag. We also define a guard class for mmap_read_trylock() so we can use cleanup.h to make the scope handling cleaner in the implementation. This patch is based on work by David Carlier (linked), with gratitude! Link: https://lore.kernel.org/20260723-series-vmap-race-fix-v6-0-8cc77dcc0018@kernel.org Link: https://lore.kernel.org/20260723-series-vmap-race-fix-v6-1-8cc77dcc0018@kernel.org Fixes: b6bdb7517c3d ("mm/vmalloc: add interfaces to free unmapped page table") Signed-off-by: Lorenzo Stoakes (ARM) Reported-by: syzbot+fd95a72470f5a44e464c@syzkaller.appspotmail.com Closes: https://lore.kernel.org/all/6a287988.39669fcc.33b062.00a0.GAE@google.com/T/ Link: https://lore.kernel.org/linux-mm/20260706203128.162335-1-devnexen@gmail.com/ Reviewed-by: Mike Rapoport (Microsoft) Reviewed-by: Dev Jain Acked-by: David Hildenbrand (Arm) Reviewed-by: Kiryl Shutsemau Cc: Cc: Andy Lutomirski Cc: "Borah, Chaitanya Kumar" Cc: "Borislav Petkov (AMD)" Cc: Catalin Marinas Cc: Dave Hansen Cc: "H. Peter Anvin" Cc: Ingo Molnar Cc: Liam R. Howlett Cc: Michal Hocko Cc: Peter Zijlstra Cc: Ryan Roberts Cc: Shakeel Butt Cc: Suren Baghdasaryan Cc: Toshi Kani Cc: "Uladzislau Rezki (Sony)" Cc: Vlastimil Babka Cc: Will Deacon Signed-off-by: Andrew Morton --- include/linux/mmap_lock.h | 1 + mm/pagewalk.c | 22 ++++++++++-------- mm/vmalloc.c | 49 ++++++++++++++++++++++++++++++++------- 3 files changed, 53 insertions(+), 19 deletions(-) diff --git a/include/linux/mmap_lock.h b/include/linux/mmap_lock.h index 04b8f61ece5d8e..6b5c2390cc301f 100644 --- a/include/linux/mmap_lock.h +++ b/include/linux/mmap_lock.h @@ -621,6 +621,7 @@ static inline void mmap_read_unlock(struct mm_struct *mm) DEFINE_GUARD(mmap_read_lock, struct mm_struct *, mmap_read_lock(_T), mmap_read_unlock(_T)) +DEFINE_GUARD_COND(mmap_read_lock, _try, mmap_read_trylock(_T)) static inline void mmap_read_unlock_non_owner(struct mm_struct *mm) { diff --git a/mm/pagewalk.c b/mm/pagewalk.c index 3ae2586ff45b44..bbcfd68d0907df 100644 --- a/mm/pagewalk.c +++ b/mm/pagewalk.c @@ -678,6 +678,8 @@ int walk_kernel_page_table_range_lockless(unsigned long start, unsigned long end * will also not lock the PTEs for the pte_entry() callback. * * This is for debugging purposes ONLY. + * + * The mmap write lock must be held. */ int walk_page_range_debug(struct mm_struct *mm, unsigned long start, unsigned long end, const struct mm_walk_ops *ops, @@ -691,6 +693,16 @@ int walk_page_range_debug(struct mm_struct *mm, unsigned long start, .no_vma = true }; + /* + * When walking userland page tables, an mmap write lock must be held to + * account for munmap() downgrading to an mmap read lock when tearing + * down page tables. + * + * When walking kernel page tables, an mmap write lock must also be held + * to account for page table freeing on vmap huge page mapping. + */ + mmap_assert_write_locked(mm); + /* For convenience, we allow traversal of kernel mappings. */ if (mm == &init_mm) return walk_kernel_page_table_range(start, end, ops, @@ -700,16 +712,6 @@ int walk_page_range_debug(struct mm_struct *mm, unsigned long start, if (!check_ops_safe(ops)) return -EINVAL; - /* - * The mmap lock protects the page walker from changes to the page - * tables during the walk. However a read lock is insufficient to - * protect those areas which don't have a VMA as munmap() detaches - * the VMAs before downgrading to a read lock and actually tearing - * down PTEs/page tables. In which case, the mmap write lock should - * be held. - */ - mmap_assert_write_locked(mm); - return walk_pgd_range(start, end, &walk); } diff --git a/mm/vmalloc.c b/mm/vmalloc.c index 1afca3568b9b65..d5c4d2bb770bfb 100644 --- a/mm/vmalloc.c +++ b/mm/vmalloc.c @@ -43,6 +43,7 @@ #include #include #include +#include #define CREATE_TRACE_POINTS #include @@ -158,10 +159,24 @@ static int vmap_try_huge_pmd(pmd_t *pmd, unsigned long addr, unsigned long end, if (!IS_ALIGNED(phys_addr, PMD_SIZE)) return 0; - if (pmd_present(*pmd) && !pmd_free_pte_page(pmd, addr)) - return 0; + if (!pmd_present(*pmd)) + return pmd_set_huge(pmd, phys_addr, prot); - return pmd_set_huge(pmd, phys_addr, prot); + /* + * Acquire the mmap read lock to exclude ptdump, which walks + * kernel page tables it does not own under the mmap write lock. + * + * Concurrent read lock holders are safe: each exclusively owns + * the range it operates on and cannot reach this page table. + */ +#ifndef CONFIG_ARM64 + scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm) +#endif + { + if (!pmd_free_pte_page(pmd, addr)) + return 0; + return pmd_set_huge(pmd, phys_addr, prot); + } } static int vmap_pmd_range(pud_t *pud, unsigned long addr, unsigned long end, @@ -210,10 +225,18 @@ static int vmap_try_huge_pud(pud_t *pud, unsigned long addr, unsigned long end, if (!IS_ALIGNED(phys_addr, PUD_SIZE)) return 0; - if (pud_present(*pud) && !pud_free_pmd_page(pud, addr)) - return 0; + if (!pud_present(*pud)) + return pud_set_huge(pud, phys_addr, prot); - return pud_set_huge(pud, phys_addr, prot); + /* See comment in vmap_try_huge_pmd(). */ +#ifndef CONFIG_ARM64 + scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm) +#endif + { + if (!pud_free_pmd_page(pud, addr)) + return 0; + return pud_set_huge(pud, phys_addr, prot); + } } static int vmap_pud_range(p4d_t *p4d, unsigned long addr, unsigned long end, @@ -262,10 +285,18 @@ static int vmap_try_huge_p4d(p4d_t *p4d, unsigned long addr, unsigned long end, if (!IS_ALIGNED(phys_addr, P4D_SIZE)) return 0; - if (p4d_present(*p4d) && !p4d_free_pud_page(p4d, addr)) - return 0; + if (!p4d_present(*p4d)) + return p4d_set_huge(p4d, phys_addr, prot); - return p4d_set_huge(p4d, phys_addr, prot); + /* See comment in vmap_try_huge_pmd(). */ +#ifndef CONFIG_ARM64 + scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm) +#endif + { + if (!p4d_free_pud_page(p4d, addr)) + return 0; + return p4d_set_huge(p4d, phys_addr, prot); + } } static int vmap_p4d_range(pgd_t *pgd, unsigned long addr, unsigned long end, From 27c32e5538344b13c1505a08861e04620c125d47 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Thu, 23 Jul 2026 16:16:34 +0100 Subject: [PATCH 281/612] mm/ptdump: always stabilise against page table freeing using init_mm Previous commits have established the invariant that kernel page table freeing is performed while an mmap read lock on init_mm is held, which fixes races between ptdump and kernel page table freeing over init_mm. However, x86 and arm64 can perform a ptdump over an mm other than init_mm via ptdump_walk_pgd() and since kernel memory ranges are shared across non-kernel mm's, this means that the race still exists for these cases. Fix this by acquiring a nested mmap write lock for init_mm in ptdump_walk_pgd(). This is safe as we take this after mmap write locking the mm, and nothing acquires the init_mm lock first before locking an arbitrary mm, so no deadlock is possible. Also update walk_page_range_debug() to assert that init_mm is write locked, add a comment explaining why and remove some redundant code, and eliminate the unnecessary and confusing invocation of walk_kernel_page_table_range(). We can safely remove the non-NULL check for walk.mm, as the mmap lock asserts would NULL pointer deref if it was (and of course no callers do this). The first point at which ptdump can race kernel page table freeing is commit b6bdb7517c3d ("mm/vmalloc: add interfaces to free unmapped page table"), so we target this in the Fixes tag. Link: https://lore.kernel.org/20260723-series-vmap-race-fix-v6-4-8cc77dcc0018@kernel.org Fixes: b6bdb7517c3d ("mm/vmalloc: add interfaces to free unmapped page table") Signed-off-by: Lorenzo Stoakes (ARM) Reviewed-by: Mike Rapoport (Microsoft) Acked-by: David Hildenbrand (Arm) Reviewed-by: Kiryl Shutsemau Cc: Andy Lutomirski Cc: "Borah, Chaitanya Kumar" Cc: "Borislav Petkov (AMD)" Cc: Catalin Marinas Cc: Dave Hansen Cc: David Carlier Cc: Dev Jain Cc: "H. Peter Anvin" Cc: Ingo Molnar Cc: Liam R. Howlett Cc: Michal Hocko Cc: Peter Zijlstra Cc: Ryan Roberts Cc: Shakeel Butt Cc: Suren Baghdasaryan Cc: Toshi Kani Cc: "Uladzislau Rezki (Sony)" Cc: Vlastimil Babka Cc: Will Deacon Cc: Signed-off-by: Andrew Morton --- mm/pagewalk.c | 14 +++++++++----- mm/ptdump.c | 7 +++++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/mm/pagewalk.c b/mm/pagewalk.c index bbcfd68d0907df..5d87c632a25507 100644 --- a/mm/pagewalk.c +++ b/mm/pagewalk.c @@ -702,12 +702,16 @@ int walk_page_range_debug(struct mm_struct *mm, unsigned long start, * to account for page table freeing on vmap huge page mapping. */ mmap_assert_write_locked(mm); + /* + * x86, arm64 ptdump allow walks of efi mm's and x86 ptdump allows walks + * of arbitrary mm's. + * + * However, they both must also hold the init_mm lock to account for + * concurrent kernel page table freeing. + */ + mmap_assert_write_locked(&init_mm); - /* For convenience, we allow traversal of kernel mappings. */ - if (mm == &init_mm) - return walk_kernel_page_table_range(start, end, ops, - pgd, private); - if (start >= end || !walk.mm) + if (start >= end) return -EINVAL; if (!check_ops_safe(ops)) return -EINVAL; diff --git a/mm/ptdump.c b/mm/ptdump.c index 973020000096cf..5851096e6f656c 100644 --- a/mm/ptdump.c +++ b/mm/ptdump.c @@ -178,11 +178,18 @@ void ptdump_walk_pgd(struct ptdump_state *st, struct mm_struct *mm, pgd_t *pgd) get_online_mems(); mmap_write_lock(mm); + /* To stabilise kernel page tables we must hold the init_mm lock too. */ + if (mm != &init_mm) + mmap_write_lock_nested(&init_mm, SINGLE_DEPTH_NESTING); + while (range->start != range->end) { walk_page_range_debug(mm, range->start, range->end, &ptdump_ops, pgd, st); range++; } + + if (mm != &init_mm) + mmap_write_unlock(&init_mm); mmap_write_unlock(mm); put_online_mems(); From 9d3277b2c07ccc9508d648098b3bbb46c61b7f3c Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Thu, 23 Jul 2026 16:16:35 +0100 Subject: [PATCH 282/612] arm64: remove redundant concurrent ptdump UAF mitigation This partially reverts commit fa93b45fd397 ("arm64: Enable vmalloc-huge with ptdump"), retaining vmalloc-huge support but eliminating the now redundant mitigation against a race between huge vmap page table freeing and ptdump, as this issue has now been fixed at core. We also simultaneously remove the arm64 if-deffery when acquiring the mmap read lock upon vmap huge page table promotion as it is no longer required. Note that this patch relies on the preceding vmalloc patch, and should not be backported alone. Link: https://lore.kernel.org/20260723-series-vmap-race-fix-v6-5-8cc77dcc0018@kernel.org Fixes: fa93b45fd397 ("arm64: Enable vmalloc-huge with ptdump") Signed-off-by: Lorenzo Stoakes (ARM) Reviewed-by: Dev Jain Acked-by: Mike Rapoport (Microsoft) Acked-by: Kiryl Shutsemau (Meta) Acked-by: Will Deacon Reviewed-by: David Hildenbrand (Arm) Cc: Andy Lutomirski Cc: "Borah, Chaitanya Kumar" Cc: "Borislav Petkov (AMD)" Cc: Catalin Marinas Cc: Dave Hansen Cc: David Carlier Cc: "H. Peter Anvin" Cc: Ingo Molnar Cc: Liam R. Howlett Cc: Michal Hocko Cc: Peter Zijlstra Cc: Ryan Roberts Cc: Shakeel Butt Cc: Suren Baghdasaryan Cc: Toshi Kani Cc: "Uladzislau Rezki (Sony)" Cc: Vlastimil Babka Cc: Signed-off-by: Andrew Morton --- arch/arm64/include/asm/ptdump.h | 2 -- arch/arm64/mm/mmu.c | 43 +++------------------------------ arch/arm64/mm/ptdump.c | 11 ++------- mm/vmalloc.c | 15 +++--------- 4 files changed, 9 insertions(+), 62 deletions(-) diff --git a/arch/arm64/include/asm/ptdump.h b/arch/arm64/include/asm/ptdump.h index 5b374a6ab34a4e..50a195eda8ed22 100644 --- a/arch/arm64/include/asm/ptdump.h +++ b/arch/arm64/include/asm/ptdump.h @@ -7,8 +7,6 @@ #include -DECLARE_STATIC_KEY_FALSE(arm64_ptdump_lock_key); - #ifdef CONFIG_PTDUMP #include diff --git a/arch/arm64/mm/mmu.c b/arch/arm64/mm/mmu.c index 18a8b0d3714ee5..d4de88770ecfea 100644 --- a/arch/arm64/mm/mmu.c +++ b/arch/arm64/mm/mmu.c @@ -49,8 +49,6 @@ #define NO_CONT_MAPPINGS BIT(1) #define NO_EXEC_MAPPINGS BIT(2) /* assumes FEAT_HPDS is not used */ -DEFINE_STATIC_KEY_FALSE(arm64_ptdump_lock_key); - u64 kimage_voffset __ro_after_init; EXPORT_SYMBOL(kimage_voffset); @@ -1864,8 +1862,7 @@ int pmd_clear_huge(pmd_t *pmdp) return 1; } -static int __pmd_free_pte_page(pmd_t *pmdp, unsigned long addr, - bool acquire_mmap_lock) +int pmd_free_pte_page(pmd_t *pmdp, unsigned long addr) { pte_t *table; pmd_t pmd; @@ -1877,25 +1874,13 @@ static int __pmd_free_pte_page(pmd_t *pmdp, unsigned long addr, return 1; } - /* See comment in pud_free_pmd_page for static key logic */ table = pte_offset_kernel(pmdp, addr); pmd_clear(pmdp); __flush_tlb_kernel_pgtable(addr); - if (static_branch_unlikely(&arm64_ptdump_lock_key) && acquire_mmap_lock) { - mmap_read_lock(&init_mm); - mmap_read_unlock(&init_mm); - } - pte_free_kernel(NULL, table); return 1; } -int pmd_free_pte_page(pmd_t *pmdp, unsigned long addr) -{ - /* If ptdump is walking the pagetables, acquire init_mm.mmap_lock */ - return __pmd_free_pte_page(pmdp, addr, /* acquire_mmap_lock = */ true); -} - int pud_free_pmd_page(pud_t *pudp, unsigned long addr) { pmd_t *table; @@ -1911,36 +1896,16 @@ int pud_free_pmd_page(pud_t *pudp, unsigned long addr) } table = pmd_offset(pudp, addr); - - /* - * Our objective is to prevent ptdump from reading a PMD table which has - * been freed. In this race, if pud_free_pmd_page observes the key on - * (which got flipped by ptdump) then the mmap lock sequence here will, - * as a result of the mmap write lock/unlock sequence in ptdump, give - * us the correct synchronization. If not, this means that ptdump has - * yet not started walking the pagetables - the sequence of barriers - * issued by __flush_tlb_kernel_pgtable() guarantees that ptdump will - * observe an empty PUD. - */ - pud_clear(pudp); - __flush_tlb_kernel_pgtable(addr); - if (static_branch_unlikely(&arm64_ptdump_lock_key)) { - mmap_read_lock(&init_mm); - mmap_read_unlock(&init_mm); - } - pmdp = table; next = addr; end = addr + PUD_SIZE; do { if (pmd_present(pmdp_get(pmdp))) - /* - * PMD has been isolated, so ptdump won't see it. No - * need to acquire init_mm.mmap_lock. - */ - __pmd_free_pte_page(pmdp, next, /* acquire_mmap_lock = */ false); + pmd_free_pte_page(pmdp, next); } while (pmdp++, next += PMD_SIZE, next != end); + pud_clear(pudp); + __flush_tlb_kernel_pgtable(addr); pmd_free(NULL, table); return 1; } diff --git a/arch/arm64/mm/ptdump.c b/arch/arm64/mm/ptdump.c index 1c20144700d7d7..5a76c59b5ada55 100644 --- a/arch/arm64/mm/ptdump.c +++ b/arch/arm64/mm/ptdump.c @@ -283,13 +283,6 @@ void note_page_flush(struct ptdump_state *pt_st) note_page(pt_st, 0, -1, pte_val(pte_zero)); } -static void arm64_ptdump_walk_pgd(struct ptdump_state *st, struct mm_struct *mm) -{ - static_branch_inc(&arm64_ptdump_lock_key); - ptdump_walk_pgd(st, mm, NULL); - static_branch_dec(&arm64_ptdump_lock_key); -} - void ptdump_walk(struct seq_file *s, struct ptdump_info *info) { unsigned long end = ~0UL; @@ -318,7 +311,7 @@ void ptdump_walk(struct seq_file *s, struct ptdump_info *info) } }; - arm64_ptdump_walk_pgd(&st.ptdump, info->mm); + ptdump_walk_pgd(&st.ptdump, info->mm, NULL); } static void __init ptdump_initialize(void) @@ -360,7 +353,7 @@ bool ptdump_check_wx(void) } }; - arm64_ptdump_walk_pgd(&st.ptdump, &init_mm); + ptdump_walk_pgd(&st.ptdump, &init_mm, NULL); if (st.wx_pages || st.uxn_pages) { pr_warn("Checked W+X mappings: FAILED, %lu W+X pages found, %lu non-UXN pages found\n", diff --git a/mm/vmalloc.c b/mm/vmalloc.c index d5c4d2bb770bfb..f4fa227a8d7f04 100644 --- a/mm/vmalloc.c +++ b/mm/vmalloc.c @@ -169,10 +169,7 @@ static int vmap_try_huge_pmd(pmd_t *pmd, unsigned long addr, unsigned long end, * Concurrent read lock holders are safe: each exclusively owns * the range it operates on and cannot reach this page table. */ -#ifndef CONFIG_ARM64 - scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm) -#endif - { + scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm) { if (!pmd_free_pte_page(pmd, addr)) return 0; return pmd_set_huge(pmd, phys_addr, prot); @@ -229,10 +226,7 @@ static int vmap_try_huge_pud(pud_t *pud, unsigned long addr, unsigned long end, return pud_set_huge(pud, phys_addr, prot); /* See comment in vmap_try_huge_pmd(). */ -#ifndef CONFIG_ARM64 - scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm) -#endif - { + scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm) { if (!pud_free_pmd_page(pud, addr)) return 0; return pud_set_huge(pud, phys_addr, prot); @@ -289,10 +283,7 @@ static int vmap_try_huge_p4d(p4d_t *p4d, unsigned long addr, unsigned long end, return p4d_set_huge(p4d, phys_addr, prot); /* See comment in vmap_try_huge_pmd(). */ -#ifndef CONFIG_ARM64 - scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm) -#endif - { + scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm) { if (!p4d_free_pud_page(p4d, addr)) return 0; return p4d_set_huge(p4d, phys_addr, prot); From c2689266e5f70ecc960e64d40516518c038ae34e Mon Sep 17 00:00:00 2001 From: Ramin Moussavi Date: Mon, 27 Jul 2026 23:58:23 +0200 Subject: [PATCH 283/612] microblaze: restore the page alignment of swapper_pg_dir microblaze handles TLB misses in software, and the handler builds the address of the L1 entry by ORing the index into the page directory base instead of adding it (hw_exception_handler.S): bsrli r5, r3, PGDIR_SHIFT - 2 andi r5, r5, PAGE_SIZE - 4 /* Assume pgdir aligned on 4K boundary, no need for "andi r4,r4,0xfffff003" */ or r4, r4, r5 lwi r4, r4, 0 /* Get L1 entry */ The index is masked to the low 12 bits, so the OR only works if those bits of the base are zero -- which is exactly the assumption the comment states and the reason the masking of the base can be skipped. swapper_pg_dir had no alignment directive of its own. It was aligned because it followed empty_zero_page in head.S, and that one carried the .align 12: .section .data .global empty_zero_page .align 12 empty_zero_page: .space PAGE_SIZE .global swapper_pg_dir swapper_pg_dir: .space PAGE_SIZE Commit 6215d9f4470f ("arch, mm: consolidate empty_zero_page") removed empty_zero_page from head.S, and with it the .align 12 that -- despite sitting next to empty_zero_page -- was what page aligned swapper_pg_dir. Since then swapper_pg_dir lands wherever .data happens to put it, its low bits are no longer zero, and every kernel TLB miss ORs the index into a base with a nonzero offset. The resulting L1 lookups read the wrong words, no valid translation is ever installed, and the kernel spins in exceptions long before it can print anything. On qemu-system-microblazeel (petalogix-s3adsp1800) the console stays completely silent at 100% CPU; there is no oops and no guest error reported by qemu, which makes this awkward to diagnose. Give swapper_pg_dir the alignment it requires, rather than relying on a neighbour to provide it. microblaze has no noMMU variant left in mainline -- CONFIG_MMU is def_bool y and mmu_defconfig is the only defconfig -- so this is not a corner case: every mainline microblaze kernel since v7.1-rc1 fails to boot, including the v7.1 release. v7.0: swapper_pg_dir = 0xc05fd000 (aligned) v7.1-rc1: swapper_pg_dir = 0xc0603140 (offset 320) v7.1-rc1 + this fix: swapper_pg_dir = 0xc0604000 (aligned) next-20260726: swapper_pg_dir = 0xc0615140 (offset 320) next-20260726 + this fix: swapper_pg_dir = 0xc0616000 (aligned) Verified on qemu-system-microblazeel (petalogix-s3adsp1800) with mmu_defconfig and microblazeel gcc 12.5.0: v7.1-rc1 and next-20260726 both print nothing at all without the fix, and both boot to userspace with it. Link: https://lore.kernel.org/20260727215823.1422701-1-ramin.moussavi@yacoub.de Fixes: 6215d9f4470f ("arch, mm: consolidate empty_zero_page") Signed-off-by: Ramin Moussavi Cc: Michal Simek Cc: Mike Rapoport Cc: Signed-off-by: Andrew Morton --- arch/microblaze/kernel/head.S | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/microblaze/kernel/head.S b/arch/microblaze/kernel/head.S index 808019c3b7ac2c..9bd3e513c89be8 100644 --- a/arch/microblaze/kernel/head.S +++ b/arch/microblaze/kernel/head.S @@ -39,6 +39,8 @@ #include .section .data +/* The MMU requires a page aligned page directory. */ +.align 12 .global swapper_pg_dir swapper_pg_dir: .space PAGE_SIZE From 86da3f7e1e609e1e8bfbab198af68467c5a015a5 Mon Sep 17 00:00:00 2001 From: Hugh Dickins Date: Mon, 27 Jul 2026 22:24:14 -0700 Subject: [PATCH 284/612] mm/filemap: __filemap_add_folio() restore index before retrying In __filemap_add_folio()'s split-a-conflict loop, xas_set_order() is applied repeatedly: each application modifies xas.xa_index, rounding it down according to the split_order attempted at that stage: and if all goes as intended, it eventually (or immediately) converges on an xas_try_split() to the required folio_order, with xas.xa_index now the same as index: then xas_store() puts the new folio into the xarray there. But if a new node was needed, and GFP_NOWAIT allocation did not get one, the lock is dropped, xas_nomem() used to allocate, and sequence retried. If (that part of) the xarray is unchanged when the lock is reacquired, no problem. But what if the conflict was meanwhile resolved by another thread (perhaps even doing the same thing, inserting a folio at that same index)? Isn't there a danger of now putting our folio into the xarray at an intermediate rounded-down index? With !folio_contains() bug to follow, when CONFIG_DEBUG_VM=y is checking for that. Fix this with an xas_set_order() to restore the original xas.xa_index at the bottom of the loop, so the retry does a full re-evaluation after reacquiring the lock, and cannot reach xas_store() with the wrong index. Production was suffering from rare SIGILLs and SIGSEGVs, executable text found a page away from where it belonged, !folio_contains() bug hit when debug enabled: symptoms not seen since this patch went in. Link: https://lore.kernel.org/562fbfa6-dd6d-0b6a-2461-ed2ff1173bc8@google.com Fixes: 200a89c159a7 ("mm/filemap: use xas_try_split() in __filemap_add_folio()") Signed-off-by: Hugh Dickins Acked-by: Kiryl Shutsemau (Meta) Reviewed-by: Matthew Wilcox (Oracle) Reviewed-by: Zi Yan Cc: Chris J Arges Cc: David Hildenbrand Cc: Jan Kara Cc: Kairui Song Cc: Signed-off-by: Andrew Morton --- mm/filemap.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mm/filemap.c b/mm/filemap.c index 58eb9d24064348..d721986d5f4655 100644 --- a/mm/filemap.c +++ b/mm/filemap.c @@ -931,6 +931,12 @@ noinline int __filemap_add_folio(struct address_space *mapping, if (!xas_nomem(&xas, gfp)) break; + + /* + * Lock has been dropped: start again with the original index + * and order (but now with the memory reserved by xas_nomem()). + */ + xas_set_order(&xas, index, forder); } if (xas_error(&xas)) From 50124648db87fb63d9548b6d09deb6d2c6359dfa Mon Sep 17 00:00:00 2001 From: Brendan Jackman Date: Wed, 29 Jul 2026 12:01:48 +0000 Subject: [PATCH 285/612] MAINTAINERS: update address for Brendan Jackman Switch my entry in MAINTAINERS and .mailmap to my @linux.dev email address Link: https://lore.kernel.org/20260729-email-change-v1-1-666ae7c2b7fc@google.com Signed-off-by: Brendan Jackman Signed-off-by: Brendan Jackman Acked-by: Mike Rapoport (Microsoft) Acked-by: Lorenzo Stoakes (ARM) Acked-by: Zi Yan Cc: David Hildenbrand Cc: Johannes Weiner Cc: Liam R. Howlett Cc: Michal Hocko Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- .mailmap | 1 + MAINTAINERS | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index ca6dc257580261..7324e1af59c2f5 100644 --- a/.mailmap +++ b/.mailmap @@ -170,6 +170,7 @@ Boris Brezillon Boris Brezillon Boris Brezillon Brendan Higgins +Brendan Jackman Brian Avery Brian Cain Brian Cain diff --git a/MAINTAINERS b/MAINTAINERS index 48b2baa1541f03..d28e9f59e9211b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17160,7 +17160,7 @@ M: Andrew Morton M: Vlastimil Babka R: Suren Baghdasaryan R: Michal Hocko -R: Brendan Jackman +R: Brendan Jackman R: Johannes Weiner R: Zi Yan L: linux-mm@kvack.org From 33192a26cddea7a7e4ca66e5c3eebd36fa8be2bb Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Thu, 30 Jul 2026 11:55:47 +0100 Subject: [PATCH 286/612] mm/huge_memory: fix huge_zero_pfn race Patch series "mm/huge_memory: fix huge_zero_pfn race", v2. There is a subtle race in the reference-counted huge_zero_folio implementation. The fast path atomic logic fails to account for the fact that the shrinker (which drops the final huge_zero_refcount pin) can overwrite huge_zero_pfn with the ~0UL sentinel value in shrink_huge_zero_folio_scan() after a racing get_huge_zero_folio() installed a valid value there. This results in huge_zero_folio being correctly set but huge_zero_pfn being set incorrectly and thus is_huge_zero_pfn() and consequently is_huge_zero_pmd() will misidentify the huge zero folio as being an ordinary THP folio. This can result in the huge zero folio being split and otherwise treated incorrectly. The solution to this is very subtle as there is an atomic fast path, and thus ordering in weakly ordered architectures has to be treated very carefully. The first commit fixes the issue by introducing a spinlock around huge_zero_[pfn, folio, refcount] write, with careful consideration paid to load/store ordering in the fast path. It is placed first and kept as small as possible so that it can be backported on its own. The second commit is a pure cleanup which reworks the CONFIG_PERSISTENT_HUGE_ZERO_FOLIO logic to better separate the persistent logic from the dynamically allocated one. This patch (of 2): If !CONFIG_PERSISTENT_HUGE_ZERO_FOLIO, the huge_zero_folio is refcounted by huge_zero_refcount and returned by mm_get_huge_zero_folio(). When the caller is done with the huge zero page, its reference count is decremented. Only a shrinker can set the reference count to zero. A race can unfortunately occur between a shrinker decrementing the reference count to zero and a concurrent page fault. This is because shrink_huge_zero_folio_scan() might, if very unlucky, be preempted between setting huge_zero_refcount to zero and writing an invalid value. During this time get_huge_zero_folio() could write to huge_zero_pfn before shrink_huge_zero_folio_scan() resumes. In this event the huge zero folio will be persistently misidentified causing the THP code path to be entered inappropriately for the huge zero folio: CPU 0 CPU 1 =======================================|================================= shrink_huge_zero_folio_scan() | atomic_cmpxchg() sets refcount to 0 | xchg() sets huge_zero_folio to NULL | get_huge_zero_folio() | | atomic_inc_not_zero() -> zero preempted for a long time | Allocate new huge zero folio | | Write valid huge_zero_folio v | Write valid huge_zero_pfn Overwrite huge_zero_pfn with ~0UL <--- Invalid overwrite! This results in is_huge_zero_pfn() and is_huge_zero_pmd() incorrectly returning false for a huge zero page which could result in issues like the huge zero folio being incorrectly split. Note that the issue is with huge_zero_pfn not huge_zero_folio, as get_huge_zero_folio() uses cmpxchg() gated on huge_zero_folio being NULL with a retry loop and shrink_huge_zero_folio_scan() uses xchg() to set huge_zero_folio. Fix the issue by introducing a spinlock, huge_zero_lock, to prevent concurrent write of huge_zero_folio, huge_zero_pfn and huge_zero_refcount. There needs to be significant care taken here to ensure correctness: The fast path in get_huge_zero_folio() uses atomic_inc_not_zero(), which is outside of the critical section, and means huge zero allocation is gated on zero huge_zero_refcount. The fast path doesn't use huge_zero_lock, so the critical section is irrelevant to it. So invariants are required - huge_zero_refcount MUST: * Only be set in the huge_zero_lock critical section to ensure serialisation of huge_zero_pfn, huge_zero_folio and huge_zero_refcount writes. * Be set non-zero only AFTER huge_zero_[pfn, folio] are set to valid values so installation of the huge zero folio on read page fault ensures concurrent is_huge_zero_*() calls correctly identify the huge zero folio. * Be set zero only BEFORE huge_zero_[pfn, folio] are set to NULL and ~0UL respectively, and atomically. Establish these by: * Only setting huge_zero_refcount to zero or an absolute value in the huge_zero_lock critical section in get_huge_zero_folio() and shrink_huge_zero_folio_scan(), and always updating atomically there and elsewhere. * Using atomic_set_release(&huge_zero_refcount) in get_huge_zero_folio() after huge_zero_[pfn, folio] are set. This is paired with atomic_inc_not_zero() to ensure atomic_inc_not_zero() only observes a non-zero value if huge_zero_[pfn, folio] are set. * Using atomic_cmpxchg() in shrink_huge_zero_folio_scan() (as before) to ensure that it is set zero only when equal to 1 and set atomically. * atomic_cmpxchg() being fully ordered ensures this is done prior to huge_zero_[folio, pfn] being set to NULL and ~0UL respectively. Eliminate the retry loop in get_huge_zero_folio() as the atomic_cmpxchg() in shrink_huge_zero_folio_scan() is now performed under the lock, and replace with an equally locked atomic_inc() to set the reference count should the caller be raced on huge zero folio installation. folio_put() naturally implies a full memory barrier so its ordering is maintained correctly. The huge zero folio also cannot be released except when the shrinker does so as it is non-LRU and non-rmappable. Note that only the huge zero shrinker (via shrink_huge_zero_folio_scan()) can actually set huge_zero_refcount to zero, which is the count of mm's which have at least one huge zero folio installed plus one shrinker pin. Additionally convert a BUG_ON() to a VM_WARN_ON_ONCE(). Link: https://lore.kernel.org/20260730-fix-refcounted-huge-zero-v2-0-c5d8a41b317f@kernel.org Link: https://lore.kernel.org/20260730-fix-refcounted-huge-zero-v2-1-c5d8a41b317f@kernel.org Fixes: 3b77e8c8cde5 ("mm/thp: make is_huge_zero_pmd() safe and quicker") Signed-off-by: Lorenzo Stoakes (ARM) Reported-by: Hengbin Zhang Closes: https://lore.kernel.org/linux-mm/20260727154001.4102341-1-uqbarz@gmail.com/ Suggested-by: David Hildenbrand (Arm) Acked-by: David Hildenbrand (Arm) Cc: Baolin Wang Cc: Barry Song Cc: Dev Jain Cc: Hannes Reinecke Cc: Hugh Dickins Cc: Kiryl Shutsemau Cc: Lance Yang Cc: Liam R. Howlett Cc: Nico Pache Cc: Pankaj Raghav Cc: Ryan Roberts Cc: Yang Shi Cc: Zi Yan Cc: Signed-off-by: Andrew Morton --- mm/huge_memory.c | 43 +++++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/mm/huge_memory.c b/mm/huge_memory.c index 58cabe6af33d03..f43852ef99443e 100644 --- a/mm/huge_memory.c +++ b/mm/huge_memory.c @@ -41,6 +41,7 @@ #include #include #include +#include #include #include "internal.h" @@ -78,6 +79,7 @@ static unsigned long deferred_split_scan(struct shrinker *shrink, static bool split_underused_thp = true; static atomic_t huge_zero_refcount; +static DEFINE_SPINLOCK(huge_zero_lock); struct folio *huge_zero_folio __read_mostly; unsigned long huge_zero_pfn __read_mostly = ~0UL; unsigned long huge_anon_orders_always __read_mostly; @@ -224,7 +226,8 @@ unsigned long __thp_vma_allowable_orders(struct vm_area_struct *vma, static bool get_huge_zero_folio(void) { struct folio *zero_folio; -retry: + + /* Paired with atomic_set_release(). */ if (likely(atomic_inc_not_zero(&huge_zero_refcount))) return true; @@ -237,17 +240,22 @@ static bool get_huge_zero_folio(void) } /* Ensure zero folio won't have large_rmappable flag set. */ folio_clear_large_rmappable(zero_folio); - preempt_disable(); - if (cmpxchg(&huge_zero_folio, NULL, zero_folio)) { - preempt_enable(); + + /* Paired with critical section in shrink_huge_zero_folio_scan(). */ + spin_lock(&huge_zero_lock); + if (huge_zero_folio) { + /* Somebody else already installed it. */ + atomic_inc(&huge_zero_refcount); + spin_unlock(&huge_zero_lock); folio_put(zero_folio); - goto retry; + return true; } + WRITE_ONCE(huge_zero_folio, zero_folio); WRITE_ONCE(huge_zero_pfn, folio_pfn(zero_folio)); + /* Paired with atomic_inc_not_zero(). +1 for shrinker pin. */ + atomic_set_release(&huge_zero_refcount, 2); + spin_unlock(&huge_zero_lock); - /* We take additional reference here. It will be put back by shrinker */ - atomic_set(&huge_zero_refcount, 2); - preempt_enable(); count_vm_event(THP_ZERO_PAGE_ALLOC); return true; } @@ -297,15 +305,22 @@ static unsigned long shrink_huge_zero_folio_count(struct shrinker *shrink, static unsigned long shrink_huge_zero_folio_scan(struct shrinker *shrink, struct shrink_control *sc) { - if (atomic_cmpxchg(&huge_zero_refcount, 1, 0) == 1) { - struct folio *zero_folio = xchg(&huge_zero_folio, NULL); - BUG_ON(zero_folio == NULL); + struct folio *zero_folio; + + /* Paired with critical section in get_huge_zero_folio(). */ + scoped_guard(spinlock, &huge_zero_lock) { + /* Paired with atomic_inc_not_zero() in get_huge_zero_folio(). */ + if (atomic_cmpxchg(&huge_zero_refcount, 1, 0) != 1) + return 0; + + zero_folio = huge_zero_folio; + VM_WARN_ON_ONCE(!zero_folio); + WRITE_ONCE(huge_zero_folio, NULL); WRITE_ONCE(huge_zero_pfn, ~0UL); - folio_put(zero_folio); - return HPAGE_PMD_NR; } - return 0; + folio_put(zero_folio); + return HPAGE_PMD_NR; } static struct shrinker *huge_zero_folio_shrinker; From 98bd3af0bb6bfdb0fb39cbfa05456acb374b691e Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Thu, 30 Jul 2026 11:55:48 +0100 Subject: [PATCH 287/612] mm/huge_memory: separate out CONFIG_PERSISTENT_HUGE_ZERO_FOLIO logic Rather than mixing the refcounted and non-refcounted CONFIG_PERSISTENT_HUGE_ZERO_FOLIO logic, separate the two out cleanly so it is clear what happens when this configuration option is set and what happens when it is not. Introduce HUGE_ZERO_UNSET_PFN to abstract the ~0UL assignment, only introduce the refcount, lock and shrinker if !CONFIG_PERSISTENT_HUGE_ZERO_FOLIO, abstract initialisation and teardown, abstract the huge zero folio allocation from refcounting. Also change a BUG_ON() to WARN_ON_ONCE() while we're at it. No functional change intended. Link: https://lore.kernel.org/20260730-fix-refcounted-huge-zero-v2-2-c5d8a41b317f@kernel.org Signed-off-by: Lorenzo Stoakes (ARM) Fixes: 3b77e8c8cde5 ("mm/thp: make is_huge_zero_pmd() safe and quicker") Cc: Baolin Wang Cc: Barry Song Cc: David Hildenbrand (Arm) Cc: Dev Jain Cc: Hannes Reinecke Cc: Hengbin Zhang Cc: Hugh Dickins Cc: Kiryl Shutsemau Cc: Lance Yang Cc: Liam R. Howlett Cc: Nico Pache Cc: Pankaj Raghav Cc: Ryan Roberts Cc: Yang Shi Cc: Zi Yan Cc: Signed-off-by: Andrew Morton --- mm/huge_memory.c | 160 ++++++++++++++++++++++++++++------------------- 1 file changed, 94 insertions(+), 66 deletions(-) diff --git a/mm/huge_memory.c b/mm/huge_memory.c index f43852ef99443e..f84fe7913d88fb 100644 --- a/mm/huge_memory.c +++ b/mm/huge_memory.c @@ -78,10 +78,15 @@ static unsigned long deferred_split_scan(struct shrinker *shrink, struct shrink_control *sc); static bool split_underused_thp = true; +#define HUGE_ZERO_UNSET_PFN (~0UL) +struct folio *huge_zero_folio __read_mostly; +unsigned long huge_zero_pfn __read_mostly = HUGE_ZERO_UNSET_PFN; +#ifndef CONFIG_PERSISTENT_HUGE_ZERO_FOLIO static atomic_t huge_zero_refcount; static DEFINE_SPINLOCK(huge_zero_lock); -struct folio *huge_zero_folio __read_mostly; -unsigned long huge_zero_pfn __read_mostly = ~0UL; +static struct shrinker *huge_zero_folio_shrinker; +#endif + unsigned long huge_anon_orders_always __read_mostly; unsigned long huge_anon_orders_madvise __read_mostly; unsigned long huge_anon_orders_inherit __read_mostly; @@ -223,23 +228,58 @@ unsigned long __thp_vma_allowable_orders(struct vm_area_struct *vma, return orders; } -static bool get_huge_zero_folio(void) +static struct folio *alloc_huge_zero_folio(void) { struct folio *zero_folio; - /* Paired with atomic_set_release(). */ - if (likely(atomic_inc_not_zero(&huge_zero_refcount))) - return true; - zero_folio = folio_alloc((GFP_TRANSHUGE | __GFP_ZERO | __GFP_ZEROTAGS) & ~__GFP_MOVABLE, HPAGE_PMD_ORDER); if (!zero_folio) { count_vm_event(THP_ZERO_PAGE_ALLOC_FAILED); - return false; + return NULL; + } + folio_clear_large_rmappable(zero_folio); /* Explicitly not rmappable. */ + return zero_folio; +} + +#ifdef CONFIG_PERSISTENT_HUGE_ZERO_FOLIO +static int __init huge_zero_init(void) +{ + huge_zero_folio = alloc_huge_zero_folio(); + if (!huge_zero_folio) { + pr_warn("Allocating persistent huge zero folio failed\n"); + } else { + huge_zero_pfn = folio_pfn(huge_zero_folio); + count_vm_event(THP_ZERO_PAGE_ALLOC); } - /* Ensure zero folio won't have large_rmappable flag set. */ - folio_clear_large_rmappable(zero_folio); + return 0; +} + +static void __init huge_zero_shrinker_exit(void) +{ +} + +struct folio *mm_get_huge_zero_folio(struct mm_struct *mm) +{ + return huge_zero_folio; +} + +void mm_put_huge_zero_folio(struct mm_struct *mm) +{ +} +#else +static bool get_huge_zero_folio(void) +{ + struct folio *zero_folio; + + /* Paired with atomic_set_release(). */ + if (likely(atomic_inc_not_zero(&huge_zero_refcount))) + return true; + + zero_folio = alloc_huge_zero_folio(); + if (unlikely(!zero_folio)) + return false; /* Paired with critical section in shrink_huge_zero_folio_scan(). */ spin_lock(&huge_zero_lock); @@ -266,33 +306,7 @@ static void put_huge_zero_folio(void) * Counter should never go to zero here. Only shrinker can put * last reference. */ - BUG_ON(atomic_dec_and_test(&huge_zero_refcount)); -} - -struct folio *mm_get_huge_zero_folio(struct mm_struct *mm) -{ - if (IS_ENABLED(CONFIG_PERSISTENT_HUGE_ZERO_FOLIO)) - return huge_zero_folio; - - if (mm_flags_test(MMF_HUGE_ZERO_FOLIO, mm)) - return READ_ONCE(huge_zero_folio); - - if (!get_huge_zero_folio()) - return NULL; - - if (mm_flags_test_and_set(MMF_HUGE_ZERO_FOLIO, mm)) - put_huge_zero_folio(); - - return READ_ONCE(huge_zero_folio); -} - -void mm_put_huge_zero_folio(struct mm_struct *mm) -{ - if (IS_ENABLED(CONFIG_PERSISTENT_HUGE_ZERO_FOLIO)) - return; - - if (mm_flags_test(MMF_HUGE_ZERO_FOLIO, mm)) - put_huge_zero_folio(); + WARN_ON_ONCE(atomic_dec_and_test(&huge_zero_refcount)); } static unsigned long shrink_huge_zero_folio_count(struct shrinker *shrink, @@ -316,14 +330,53 @@ static unsigned long shrink_huge_zero_folio_scan(struct shrinker *shrink, zero_folio = huge_zero_folio; VM_WARN_ON_ONCE(!zero_folio); WRITE_ONCE(huge_zero_folio, NULL); - WRITE_ONCE(huge_zero_pfn, ~0UL); + WRITE_ONCE(huge_zero_pfn, HUGE_ZERO_UNSET_PFN); } folio_put(zero_folio); return HPAGE_PMD_NR; } -static struct shrinker *huge_zero_folio_shrinker; +static int __init huge_zero_init(void) +{ + huge_zero_folio_shrinker = shrinker_alloc(0, "thp-zero"); + if (!huge_zero_folio_shrinker) { + shrinker_free(deferred_split_shrinker); + list_lru_destroy(&deferred_split_lru); + return -ENOMEM; + } + + huge_zero_folio_shrinker->count_objects = shrink_huge_zero_folio_count; + huge_zero_folio_shrinker->scan_objects = shrink_huge_zero_folio_scan; + shrinker_register(huge_zero_folio_shrinker); + return 0; +} + +static void __init huge_zero_shrinker_exit(void) +{ + shrinker_free(huge_zero_folio_shrinker); +} + +struct folio *mm_get_huge_zero_folio(struct mm_struct *mm) +{ + if (mm_flags_test(MMF_HUGE_ZERO_FOLIO, mm)) + return READ_ONCE(huge_zero_folio); + + if (!get_huge_zero_folio()) + return NULL; + + if (mm_flags_test_and_set(MMF_HUGE_ZERO_FOLIO, mm)) + put_huge_zero_folio(); + + return READ_ONCE(huge_zero_folio); +} + +void mm_put_huge_zero_folio(struct mm_struct *mm) +{ + if (mm_flags_test(MMF_HUGE_ZERO_FOLIO, mm)) + put_huge_zero_folio(); +} +#endif /* CONFIG_PERSISTENT_HUGE_ZERO_FOLIO */ #ifdef CONFIG_SYSFS static ssize_t enabled_show(struct kobject *kobj, @@ -987,39 +1040,14 @@ static int __init thp_shrinker_init(void) deferred_split_shrinker->scan_objects = deferred_split_scan; shrinker_register(deferred_split_shrinker); - if (IS_ENABLED(CONFIG_PERSISTENT_HUGE_ZERO_FOLIO)) { - /* - * Bump the reference of the huge_zero_folio and do not - * initialize the shrinker. - * - * huge_zero_folio will always be NULL on failure. We assume - * that get_huge_zero_folio() will most likely not fail as - * thp_shrinker_init() is invoked early on during boot. - */ - if (!get_huge_zero_folio()) - pr_warn("Allocating persistent huge zero folio failed\n"); - return 0; - } - - huge_zero_folio_shrinker = shrinker_alloc(0, "thp-zero"); - if (!huge_zero_folio_shrinker) { - shrinker_free(deferred_split_shrinker); - list_lru_destroy(&deferred_split_lru); - return -ENOMEM; - } - - huge_zero_folio_shrinker->count_objects = shrink_huge_zero_folio_count; - huge_zero_folio_shrinker->scan_objects = shrink_huge_zero_folio_scan; - shrinker_register(huge_zero_folio_shrinker); - - return 0; + return huge_zero_init(); } static void __init thp_shrinker_exit(void) { - shrinker_free(huge_zero_folio_shrinker); shrinker_free(deferred_split_shrinker); list_lru_destroy(&deferred_split_lru); + huge_zero_shrinker_exit(); } static int __init hugepage_init(void) From 4194140a51201e76c02c666ba83c07a81cefb6cc Mon Sep 17 00:00:00 2001 From: Danila Tikhonov Date: Sat, 1 Aug 2026 18:33:33 +0300 Subject: [PATCH 288/612] mailmap: map old addresses to Danila Tikhonov Map my old jiaxyga.com and ProtonMail addresses to the current mainlining.org address. Link: https://lore.kernel.org/20260801153333.296218-1-danila@mainlining.org Signed-off-by: Danila Tikhonov Cc: Jakub Kacinski Signed-off-by: Andrew Morton --- .mailmap | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.mailmap b/.mailmap index 7324e1af59c2f5..086f81a5bc1cfc 100644 --- a/.mailmap +++ b/.mailmap @@ -233,6 +233,8 @@ Daniel Lezcano Daniel Lezcano Daniel Thompson Daniele Alessandrelli +Danila Tikhonov +Danila Tikhonov Danilo Krummrich David Brownell David Collins From a16fd3ad9d89b05475864da97327870464611736 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Mon, 3 Aug 2026 06:40:32 -0700 Subject: [PATCH 289/612] samples/damon/mtier: error out for zero quota goal target values Patch series "mm/damon: avoid division by zero from damos_quota_score()". DAMON_SAMPLE_MTIER and DAMON_LRU_SORT allow the user to trigger division by zero in damos_quota_score(). Avoid it by adding parameters validation checks. This patch (of 2): damos_quota_score() can trigger division by zero if the target_value is zero. DAMON_SAMPLE_MTIER lets users set the target_value via node0_mem_{used,free}_bp parameters. It doesn't guard zero value case, though. As a result, users can trigger division by zero. Fix the issue by returning an error when the user tries to start DAMON with zero node0_mem_{used,free}_bp parameter values. DAMON_SAMPLE_MTIER is just a sample module, but the consequence is quite bad. Also the zero node0_mem_free_bp parameter might look like a reasonable setup to some users. Hence, the issue might really happen in the real world. One reliable way to reproduce the issue is like below: # cd /sys/module/damon_sample_mtier/parameters # echo 4096 > node0_start_addr # echo 8192 > node0_end_addr # echo 8192 > node1_start_addr # echo 81920 > node1_end_addr # echo 0 > node0_mem_free_bp # echo Y > enabled # dmesg -w [...] [18792.235916] Oops: divide error: 0000 [#1] SMP NOPTI [...] [18792.242787] RIP: 0010:damos_quota_score+0x6f/0x480 [...] This issue was discovered [1] by Sashiko. Link: https://lore.kernel.org/20260803134034.15217-1-sj@kernel.org Link: https://lore.kernel.org/20260803134034.15217-2-sj@kernel.org Link: https://lore.kernel.org/20260801202657.117135-1-sj@kernel.org [1] Fixes: c5e67d40a102 ("samples/damon/mtier: add parameters for node0 memory usage") Signed-off-by: SJ Park Cc: # 6.17.x Signed-off-by: Andrew Morton --- samples/damon/mtier.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/samples/damon/mtier.c b/samples/damon/mtier.c index 3785b0c7ffb175..dae6929f827c28 100644 --- a/samples/damon/mtier.c +++ b/samples/damon/mtier.c @@ -156,6 +156,9 @@ static struct damon_ctx *damon_sample_mtier_build_ctx(bool promote) if (!scheme) goto free_out; damon_set_schemes(ctx, &scheme, 1); + /* zero target value causes division by zero in damos_quota_store() */ + if (!node0_mem_used_bp || !node0_mem_free_bp) + goto free_out; quota_goal = damos_new_quota_goal( promote ? DAMOS_QUOTA_NODE_MEM_USED_BP : DAMOS_QUOTA_NODE_MEM_FREE_BP, From 06befa61c427e74319781e6f35a364cfc32dbae8 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Mon, 3 Aug 2026 06:40:33 -0700 Subject: [PATCH 290/612] mm/damon/lru_sort: error out for >10000 active_mem_bp damos_quota_score() can trigger division by zero if the target value is zero. DAMON_LRU_SORT lets users set the target value for the hot memory scheme via active_mem_bp parameter. It avoids setting it as the target value if the parameter value is zero. However, it also sets the cold memory scheme with a target value that is calculated as '10000 - active_mem_bp + 2'. Hence, if a user sets active_mem_bp 10002, the cold memory scheme's quota goal target value can be zero. As a result, division by zero can be triggered. Fix by returning an error when the user tries to start DAMON with >10000 active_mem_bp parameter value. It makes no sense to set active_mem_bp with 10002. It also requires module parameters write permission to reproduce the issue. That said, the consequence is quite bad. One reliable way to reproduce the issue is like below: # cd /sys/module/damon_lru_sort/parameters # echo 1000 > wmarks_high # echo 995 > wmarks_mid # echo 0 > wmarks_low # echo 10002 > active_mem_bp # echo Y > enabled # dmesg -w [...] [ 597.421247] Oops: divide error: 0000 [#1] SMP NOPTI [ 597.428848] RIP: 0010:damos_quota_score+0x6f/0x480 This issue was discovered [1] by Sashiko. Link: https://lore.kernel.org/20260803134034.15217-3-sj@kernel.org Link: https://lore.kernel.org/20260801213028.5127-1-sj@kernel.org [1] Fixes: 40d98d31cd70 ("mm/damon/lru_sort: support active:inactive memory ratio based auto-tuning") Signed-off-by: SJ Park Cc: # 7.0.x Signed-off-by: Andrew Morton --- mm/damon/lru_sort.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mm/damon/lru_sort.c b/mm/damon/lru_sort.c index 32f41491b726b2..120b3fe3fc9d5e 100644 --- a/mm/damon/lru_sort.c +++ b/mm/damon/lru_sort.c @@ -233,6 +233,8 @@ static int damon_lru_sort_add_quota_goals(struct damos *hot_scheme, if (!active_mem_bp) return 0; + if (10000 < active_mem_bp) + return -EINVAL; goal = damos_new_quota_goal(DAMOS_QUOTA_ACTIVE_MEM_BP, active_mem_bp); if (!goal) return -ENOMEM; From b9b6bad94c62cbccb9e0ad34635c49fc5f9c52cb Mon Sep 17 00:00:00 2001 From: SJ Park Date: Mon, 3 Aug 2026 06:46:44 -0700 Subject: [PATCH 291/612] mm/damon/reclaim: skip damon_call() if ctx has not started Patch series "mm/damon/{reclaim,lru_sort}: fix commit_inputs infinite hang". Writing 'Y' to commit_inputs parameters of DAMON_RECLAIM and DAMON_LRU_SORT before the modules were ever turned on causes infinite hang. Fix those. The issue was discovered [1] by Sashiko. This patch (of 2): DAMON_RECLAIM calls damon_call() for commit_inputs parameter user input if the DAMON context is initialized. The context could be initialized, but not yet successfully started. In the case, damon_call() could indefinitely hang. Read the comment on damon_call() for more detail. Fix the problem by memorizing if the DAMON context has ever successfully started, and skip damon_call() if it has not. This issue can easily be reproduced by writing Y to commit_inputs on a system that DAMON_RECLAIM was not turned on before. Link: https://lore.kernel.org/20260803134646.16640-1-sj@kernel.org Link: https://lore.kernel.org/20260803134646.16640-2-sj@kernel.org Link: https://lore.kernel.org/20260802173021.762-1-sj@kernel.org [1] Fixes: de3c60e1c831 ("mm/damon: add synchronous commit for commit_inputs") Signed-off-by: SJ Park Cc: Liew Rui Yan Cc: # 7.2.x Signed-off-by: Andrew Morton --- mm/damon/reclaim.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/mm/damon/reclaim.c b/mm/damon/reclaim.c index 11b70d0a9a6f0a..a1a65b1270c8db 100644 --- a/mm/damon/reclaim.c +++ b/mm/damon/reclaim.c @@ -276,6 +276,8 @@ static int damon_reclaim_commit_inputs_fn(void *arg) return damon_reclaim_apply_parameters(); } +static bool damon_reclaim_damon_has_started; + static int damon_reclaim_commit_inputs_store(const char *val, const struct kernel_param *kp) { @@ -296,11 +298,8 @@ static int damon_reclaim_commit_inputs_store(const char *val, if (!commit_inputs_request) return 0; - /* - * Skip damon_call() if ctx is not initialized to avoid - * NULL pointer dereference. - */ - if (!ctx) + /* Skip damon_call() if ctx has not successfully started. */ + if (!damon_reclaim_damon_has_started) return -EINVAL; err = damon_call(ctx, &control); @@ -347,6 +346,8 @@ static int damon_reclaim_turn(bool on) err = damon_start(&ctx, 1, true); if (err) return err; + if (!damon_reclaim_damon_has_started) + damon_reclaim_damon_has_started = true; return damon_call(ctx, &call_control); } From 0f1868310347f99f1b80d5c6a613ddc747288355 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Mon, 3 Aug 2026 06:46:45 -0700 Subject: [PATCH 292/612] mm/damon/lru_sort: skip damon_call() if ctx has not started DAMON_LRU_SORT calls damon_call() for commit_inputs parameter user input if the DAMON context is initialized. The context could be initialized, but not yet successfully started. In the case, damon_call() could indefinitely hang. Read the comment on damon_call() for more detail. Fix the problem by memorizing if the DAMON context has ever successfully started, and skip damon_call() if it has not. This issue can easily be reproduced by writing Y to the commit_inputs parameter file on a system that DAMON_LRU_SORT has not turned on before. Link: https://lore.kernel.org/20260803134646.16640-3-sj@kernel.org Fixes: de3c60e1c831 ("mm/damon: add synchronous commit for commit_inputs") Signed-off-by: SJ Park Cc: Liew Rui Yan Cc: # 7.2.x Signed-off-by: Andrew Morton --- mm/damon/lru_sort.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/mm/damon/lru_sort.c b/mm/damon/lru_sort.c index 120b3fe3fc9d5e..729d94b8d77ef3 100644 --- a/mm/damon/lru_sort.c +++ b/mm/damon/lru_sort.c @@ -351,6 +351,8 @@ static int damon_lru_sort_commit_inputs_fn(void *arg) return damon_lru_sort_apply_parameters(); } +static bool damon_lru_sort_damon_has_started; + static int damon_lru_sort_commit_inputs_store(const char *val, const struct kernel_param *kp) { @@ -371,11 +373,8 @@ static int damon_lru_sort_commit_inputs_store(const char *val, if (!commit_inputs_request) return 0; - /* - * Skip damon_call() if ctx is not initialized to avoid - * NULL pointer dereference. - */ - if (!ctx) + /* Skip damon_call() if ctx has not successfully started. */ + if (!damon_lru_sort_damon_has_started) return -EINVAL; err = damon_call(ctx, &control); @@ -426,6 +425,8 @@ static int damon_lru_sort_turn(bool on) err = damon_start(&ctx, 1, true); if (err) return err; + if (!damon_lru_sort_damon_has_started) + damon_lru_sort_damon_has_started = true; return damon_call(ctx, &call_control); } From 8db4bab826ccc9ec10fa41736a48031cd338d392 Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Thu, 23 Jul 2026 00:48:13 +0800 Subject: [PATCH 293/612] mm/page_table_check: skip special zero mappings page_table_check_set() and page_table_check_clear() account mappings based on PageAnon(). Shared zero-page PTEs and huge zero PMDs are special mappings, but page_table_check can still account them as file-backed pages. An unprivileged process can populate enough zero mappings to overflow file_map_count and hit the existing BUG_ON(). The PTE path can do this with the shared zero page, and the PMD path can do the same with huge zero mappings. Skip special zero mappings in the user page-table accounting paths. Keep the PTE-side pte_special() check, and identify huge zero PMDs from the mapped folio instead of pmd_special(). That covers architectures where pmd_special() is a no-op without adding huge_zero_pfn checks to the generic counter helpers. Link: https://lore.kernel.org/cover.1784717203.git.zhilinz@nebusec.ai Link: https://lore.kernel.org/e94478e4fb7912fb7e8ebebed5ce85d00dc9a69d.1784717203.git.zhilinz@nebusec.ai Fixes: df4e817b7108 ("mm: page table check") Signed-off-by: Zhiling Zou Signed-off-by: Ren Wei Reported-by: Vega Cc: Pasha Tatashin Assisted-by: Codex:gpt-5.4 Cc: Signed-off-by: Andrew Morton --- mm/page_table_check.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/mm/page_table_check.c b/mm/page_table_check.c index 53a8997ec043ea..2403f5a1141058 100644 --- a/mm/page_table_check.c +++ b/mm/page_table_check.c @@ -151,18 +151,29 @@ void __page_table_check_pte_clear(struct mm_struct *mm, unsigned long addr, if (&init_mm == mm) return; - if (pte_user_accessible_page(mm, addr, pte)) + if (pte_user_accessible_page(mm, addr, pte) && !pte_special(pte)) page_table_check_clear(pte_pfn(pte), PAGE_SIZE >> PAGE_SHIFT); } EXPORT_SYMBOL(__page_table_check_pte_clear); +static inline bool page_table_check_huge_zero_pmd(pmd_t pmd) +{ + unsigned long pfn = pmd_pfn(pmd); + + if (!pfn_valid(pfn)) + return false; + + return is_huge_zero_folio(page_folio(pfn_to_page(pfn))); +} + void __page_table_check_pmd_clear(struct mm_struct *mm, unsigned long addr, pmd_t pmd) { if (&init_mm == mm) return; - if (pmd_user_accessible_page(mm, addr, pmd)) + if (pmd_user_accessible_page(mm, addr, pmd) && + !page_table_check_huge_zero_pmd(pmd)) page_table_check_clear(pmd_pfn(pmd), PMD_SIZE >> PAGE_SHIFT); } EXPORT_SYMBOL(__page_table_check_pmd_clear); @@ -208,7 +219,7 @@ void __page_table_check_ptes_set(struct mm_struct *mm, unsigned long addr, for (i = 0; i < nr; i++) __page_table_check_pte_clear(mm, addr + PAGE_SIZE * i, ptep_get(ptep + i)); - if (pte_user_accessible_page(mm, addr, pte)) + if (pte_user_accessible_page(mm, addr, pte) && !pte_special(pte)) page_table_check_set(pte_pfn(pte), nr, pte_write(pte)); } EXPORT_SYMBOL(__page_table_check_ptes_set); @@ -238,7 +249,8 @@ void __page_table_check_pmds_set(struct mm_struct *mm, unsigned long addr, for (i = 0; i < nr; i++) __page_table_check_pmd_clear(mm, addr + PMD_SIZE * i, *(pmdp + i)); - if (pmd_user_accessible_page(mm, addr, pmd)) + if (pmd_user_accessible_page(mm, addr, pmd) && + !page_table_check_huge_zero_pmd(pmd)) page_table_check_set(pmd_pfn(pmd), stride * nr, pmd_write(pmd)); } EXPORT_SYMBOL(__page_table_check_pmds_set); From aca1f2d5de17e138bc6c4859126b77e516b82541 Mon Sep 17 00:00:00 2001 From: Matt Fleming Date: Sat, 25 Jul 2026 11:14:19 +0100 Subject: [PATCH 294/612] mm/huge_memory: initialise workingset state before folio split xas_try_split() adds __GFP_ACCOUNT for page-cache xa_nodes, but __folio_split() leaves the xa_state's xa_lru unset. That lets a live, memcg-charged xa_node exist without being linked into the mapping's shadow_nodes list_lru; when reclaim later walks the list_lru it trips VM_WARN_ON(!css_is_dying()). Use mapping_set_update() to install both the workingset update callback and the shadow_nodes list_lru on the xa_state. Link: https://lore.kernel.org/20260725101419.3938406-1-matt@readmodwrite.com Fixes: 58729c04cf10 ("mm/huge_memory: add buddy allocator like (non-uniform) folio_split()") Signed-off-by: Matt Fleming Reported-by: syzbot+c5b060ce82921a2fd500@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=c5b060ce82921a2fd500 Reviewed-by: Zi Yan Acked-by: David Hildenbrand (Arm) Cc: Baolin Wang Cc: Barry Song Cc: Dave Chinner Cc: Dev Jain Cc: Kairui Song Cc: Lance Yang Cc: Liam Howlett Cc: Lorenzo Stoakes Cc: Matthew Wilcox (Oracle) Cc: Muchun Song Cc: Nico Pache Cc: Roman Gushchin Cc: Ryan Roberts Cc: Shakeel Butt Cc: Signed-off-by: Andrew Morton --- mm/huge_memory.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mm/huge_memory.c b/mm/huge_memory.c index f84fe7913d88fb..5d94fa4c74fdb6 100644 --- a/mm/huge_memory.c +++ b/mm/huge_memory.c @@ -4076,7 +4076,7 @@ static int __folio_split(struct folio *folio, unsigned int new_order, gfp_t gfp; mapping = folio->mapping; - min_order = mapping_min_folio_order(folio->mapping); + min_order = mapping_min_folio_order(mapping); if (new_order < min_order) { ret = -EINVAL; goto out; @@ -4090,6 +4090,8 @@ static int __folio_split(struct folio *folio, unsigned int new_order, goto out; } + mapping_set_update(&xas, mapping); + if (split_type == SPLIT_TYPE_UNIFORM) { xas_set_order(&xas, folio->index, new_order); xas_split_alloc(&xas, folio, old_order, gfp); From 5deb65c34e682e7c5f5df417a70e223e8fcc5f5a Mon Sep 17 00:00:00 2001 From: liyouhong Date: Sun, 26 Jul 2026 09:48:15 +0800 Subject: [PATCH 295/612] mm/damon/ops-common: putback folios on invalid migrate nid damon_pa_migrate() and damos_va_migrate() isolate folios into a local list and then call damon_migrate_pages(). When target_nid is invalid (including the scheme default NUMA_NO_NODE / -1), damon_migrate_pages() returns early without putting the folios back to the LRU. Callers then discard the list head while those folios remain isolated with an extra reference taken by folio_isolate_lru(). The pages stay off the LRU for as long as the mapping exists (anon active+inactive counts drop while RSS does not), and the leftover references can pin the pages after the mapping is gone. Put the folios back on the invalid-nid path so ignored migration requests still return them to the LRU. Link: https://lore.kernel.org/20260726014815.1280757-1-dayou5941@163.com Fixes: 7e6c3130690a ("mm/damon/ops-common: ignore migration request to invalid nodes") Assisted-by: Cursor:grok-4.5 Reviewed-by: SJ Park Signed-off-by: liyouhong Cc: Signed-off-by: Andrew Morton --- mm/damon/ops-common.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/mm/damon/ops-common.c b/mm/damon/ops-common.c index 6bdd1cfd3863a3..9c178c175832fd 100644 --- a/mm/damon/ops-common.c +++ b/mm/damon/ops-common.c @@ -392,8 +392,15 @@ unsigned long damon_migrate_pages(struct list_head *folio_list, int target_nid) return nr_migrated; if (target_nid < 0 || target_nid >= MAX_NUMNODES || - !node_state(target_nid, N_MEMORY)) + !node_state(target_nid, N_MEMORY)) { + while (!list_empty(folio_list)) { + struct folio *folio = lru_to_folio(folio_list); + + list_del(&folio->lru); + folio_putback_lru(folio); + } return nr_migrated; + } noreclaim_flag = memalloc_noreclaim_save(); From 1ec0e6b6f7321feb769f50d2f094a0aa6c2eda63 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Tue, 28 Jul 2026 07:04:03 -0700 Subject: [PATCH 296/612] mm/damon: adjust isolated pages stat for DAMOS_MIGRATE_{HOT,COLD} Callers of migrate_pages() should adjust NR_MIGRATED_{ANON,FILE} for isolations and putback of the folios. That for migration succeeded folios is done by migrate_pages(), in migrate_folio_done(). That for MR_DEMOTION reason is an exception though. DAMOS_MIGRATE_{HOT,COLD} call migrate_pages() but mistakenly not doing the stat adjustment. As a result, use of DAMOS_MIGRATE_{HOT,COLD} could corrupt the stat. It could confuse too_many_isolated(), make compaction and reclaim to behave in unexpected ways. The stat corruption can be reproduced and confirmed using DAMON user-space tool [1] on NUMA systems, like below. $ numactl --hardware available: 2 nodes (0-1) [...] $ sudo ./damo start --damos_action migrate_hot 1 $ sudo cat /proc/sys/vm/stat_refresh $ sudo dmesg [...] [ 80.215554] vmstat_refresh: nr_isolated_anon -5578 [ 80.216842] vmstat_refresh: nr_isolated_file -34400 This issue was discovered [2] by Sashiko. Link: https://lore.kernel.org/20260728140404.94476-1-sj@kernel.org Link: https://github.com/damonitor/damo [1] Link: https://lore.kernel.org/20260726164356.87940-1-sj@kernel.org [2] Fixes: b51820ebea65 ("mm/damon/paddr: introduce DAMOS_MIGRATE_COLD action for demotion") Signed-off-by: SJ Park Cc: Honggyu Kim Cc: Hyeongtak Ji Cc: # 6.11.x Signed-off-by: Andrew Morton --- mm/damon/ops-common.c | 4 ++++ mm/damon/paddr.c | 2 ++ mm/damon/vaddr.c | 3 ++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/mm/damon/ops-common.c b/mm/damon/ops-common.c index 9c178c175832fd..0bcad6b1e5b9e9 100644 --- a/mm/damon/ops-common.c +++ b/mm/damon/ops-common.c @@ -375,6 +375,8 @@ static unsigned int damon_migrate_folio_list(struct list_head *folio_list, while (!list_empty(folio_list)) { folio = lru_to_folio(folio_list); list_del(&folio->lru); + node_stat_sub_folio(folio, NR_ISOLATED_ANON + + folio_is_file_lru(folio)); folio_putback_lru(folio); } @@ -397,6 +399,8 @@ unsigned long damon_migrate_pages(struct list_head *folio_list, int target_nid) struct folio *folio = lru_to_folio(folio_list); list_del(&folio->lru); + node_stat_sub_folio(folio, NR_ISOLATED_ANON + + folio_is_file_lru(folio)); folio_putback_lru(folio); } return nr_migrated; diff --git a/mm/damon/paddr.c b/mm/damon/paddr.c index 5c2da45f988cf9..f7613ce279a24c 100644 --- a/mm/damon/paddr.c +++ b/mm/damon/paddr.c @@ -350,6 +350,8 @@ static unsigned long damon_pa_migrate(struct damon_region *r, if (!folio_isolate_lru(folio)) goto put_folio; + node_stat_add_folio(folio, NR_ISOLATED_ANON + + folio_is_file_lru(folio)); list_add(&folio->lru, &folio_list); put_folio: addr += folio_size(folio); diff --git a/mm/damon/vaddr.c b/mm/damon/vaddr.c index e73ec1ce016e5b..2c1c1952c008d0 100644 --- a/mm/damon/vaddr.c +++ b/mm/damon/vaddr.c @@ -649,7 +649,8 @@ static void damos_va_migrate_dests_add(struct folio *folio, isolate: if (!folio_isolate_lru(folio)) return; - + node_stat_add_folio(folio, NR_ISOLATED_ANON + + folio_is_file_lru(folio)); list_add(&folio->lru, &migration_lists[i]); } From aa2e13ae8d3cbe2c15ef4f7e971b2de0832794aa Mon Sep 17 00:00:00 2001 From: Qing Luo Date: Tue, 4 Aug 2026 10:55:14 +0800 Subject: [PATCH 297/612] sctp: fix addip_serial increment on ASCONF_ACK allocation failure In sctp_process_asconf(), when sctp_make_asconf_ack() fails to allocate the ASCONF_ACK chunk due to memory pressure, the code jumps to the done label where asoc->peer.addip_serial is unconditionally incremented. This leaves the peer's ASCONF (serial N) unacknowledged while the local endpoint now expects serial N+1. When the peer retransmits serial N, it falls into the serial < addip_serial + 1 branch , which attempts to look up a cached ACK for serial N. No cached ACK exists since the allocation failed, so the retransmission is silently discarded. The peer eventually times out and ABORTs the association. Move the addip_serial increment inside the if (asconf_ack) block so that the serial number is only advanced when the ASCONF_ACK is successfully created and cached. This way, on allocation failure, the serial number is unchanged and the peer's retransmitted ASCONF will be correctly re-processed. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Qing Luo Acked-by: Xin Long Link: https://patch.msgid.link/20260804025514.241767-1-l1138897701@163.com Signed-off-by: Jakub Kicinski --- net/sctp/sm_make_chunk.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/net/sctp/sm_make_chunk.c b/net/sctp/sm_make_chunk.c index 0ae30c3c8913a8..e25612e9d0821f 100644 --- a/net/sctp/sm_make_chunk.c +++ b/net/sctp/sm_make_chunk.c @@ -3336,12 +3336,11 @@ struct sctp_chunk *sctp_process_asconf(struct sctp_association *asoc, goto done; } done: - asoc->peer.addip_serial++; - /* If we are sending a new ASCONF_ACK hold a reference to it in assoc * after freeing the reference to old asconf ack if any. */ if (asconf_ack) { + asoc->peer.addip_serial++; sctp_chunk_hold(asconf_ack); list_add_tail(&asconf_ack->transmitted_list, &asoc->asconf_ack_list); From 6058f0fea10f3caf63a435677358d1b8e9325114 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Thu, 25 Jun 2026 22:17:50 -0700 Subject: [PATCH 298/612] Input: synaptics-rmi4 - fix F55 transmitter electrode count typo During F55 sensor detection, the transmitter (TX) electrode count was incorrectly assigned the value of the receiver (RX) electrode count due to copy-paste typos. This incorrect value was then propagated to the driver data and used by F54 to determine the diagnostics report size. On devices with more RX than TX electrodes, this inflated the perceived TX count, leading to incorrect report size calculations and potential out-of-bounds buffer accesses. Fix the typos by correctly assigning the TX electrode counts. Fixes: 6adba43fd222 ("Input: synaptics-rmi4 - add support for F55 sensor tuning") Fixes: c762cc68b6a1 ("Input: synaptics-rmi4 - propagate correct number of rx and tx electrodes to F54") Reported-by: sashiko-bot@kernel.org Cc: stable@vger.kernel.org Assisted-by: Antigravity:gemini-3.5-flash Link: https://patch.msgid.link/20260626051802.4033172-1-dmitry.torokhov@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/rmi4/rmi_f55.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/input/rmi4/rmi_f55.c b/drivers/input/rmi4/rmi_f55.c index 488adaca4dd004..a0877d32a9141f 100644 --- a/drivers/input/rmi4/rmi_f55.c +++ b/drivers/input/rmi4/rmi_f55.c @@ -54,10 +54,10 @@ static int rmi_f55_detect(struct rmi_function *fn) f55->num_tx_electrodes = f55->qry[F55_NUM_TX_OFFSET]; f55->cfg_num_rx_electrodes = f55->num_rx_electrodes; - f55->cfg_num_tx_electrodes = f55->num_rx_electrodes; + f55->cfg_num_tx_electrodes = f55->num_tx_electrodes; drv_data->num_rx_electrodes = f55->cfg_num_rx_electrodes; - drv_data->num_tx_electrodes = f55->cfg_num_rx_electrodes; + drv_data->num_tx_electrodes = f55->cfg_num_tx_electrodes; if (f55->qry[F55_PHYS_CHAR_OFFSET] & F55_CAP_SENSOR_ASSIGN) { int i, total; From dc76c3c8e8ad09362b8c1561f3928288c15cba2e Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Thu, 25 Jun 2026 22:17:51 -0700 Subject: [PATCH 299/612] Input: synaptics-rmi4 - zero report size on F54 work error In rmi_f54_work(), if an error occurs during report request or command verification, the code jumped directly to the 'error' label, bypassing the 'abort' label where f54->report_size was normally zeroed out. This left f54->report_size containing its previous successful payload size. If a user then altered the V4L2 format to a smaller size, and a subsequent run failed, rmi_f54_buffer_queue() would copy the stale, larger payload size into the shrunken V4L2 buffer, causing a heap buffer overflow. Fix this by merging the 'abort' and 'error' labels into a single 'out' exit path, and ensuring that f54->report_size is always set to 0 on failure by checking for error and zeroing the local report_size first. Fixes: 3a762dbd5347 ("[media] Input: synaptics-rmi4 - add support for F54 diagnostics") Cc: stable@vger.kernel.org Reported-by: sashiko-bot@kernel.org Assisted-by: Antigravity:gemini-3.5-flash Link: https://patch.msgid.link/20260626051802.4033172-2-dmitry.torokhov@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/rmi4/rmi_f54.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/input/rmi4/rmi_f54.c b/drivers/input/rmi4/rmi_f54.c index 61909e1a39e248..8eac320c43e35c 100644 --- a/drivers/input/rmi4/rmi_f54.c +++ b/drivers/input/rmi4/rmi_f54.c @@ -545,7 +545,7 @@ static void rmi_f54_work(struct work_struct *work) dev_err(&fn->dev, "Bad report size, report type=%d\n", f54->report_type); error = -EINVAL; - goto error; /* retry won't help */ + goto out; /* retry won't help */ } /* @@ -556,7 +556,7 @@ static void rmi_f54_work(struct work_struct *work) &command); if (error) { dev_err(&fn->dev, "Failed to read back command\n"); - goto error; + goto out; } if (command & F54_GET_REPORT) { if (time_after(jiffies, f54->timeout)) { @@ -564,7 +564,7 @@ static void rmi_f54_work(struct work_struct *work) error = -ETIMEDOUT; } report_size = 0; - goto error; + goto out; } rmi_dbg(RMI_DEBUG_FN, &fn->dev, "Get report command completed, reading data\n"); @@ -579,7 +579,7 @@ static void rmi_f54_work(struct work_struct *work) fifo, sizeof(fifo)); if (error) { dev_err(&fn->dev, "Failed to set fifo start offset\n"); - goto abort; + goto out; } error = rmi_read_block(fn->rmi_dev, fn->fd.data_base_addr + @@ -588,16 +588,16 @@ static void rmi_f54_work(struct work_struct *work) if (error) { dev_err(&fn->dev, "%s: read [%d bytes] returned %d\n", __func__, size, error); - goto abort; + goto out; } } -abort: - f54->report_size = error ? 0 : report_size; -error: +out: if (error) report_size = 0; + f54->report_size = report_size; + if (report_size == 0 && !error) { queue_delayed_work(f54->workqueue, &f54->work, msecs_to_jiffies(1)); From 49c5adc2b7d6e43c5cf033e1c86fdb9c16ababb1 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Thu, 25 Jun 2026 22:17:52 -0700 Subject: [PATCH 300/612] Input: synaptics-rmi4 - bound the F54 report size to the allocated buffer rmi_f54_work() reads a diagnostics report from the device into f54->report_data, sizing the transfer with rmi_f54_get_report_size(): report_size = rmi_f54_get_report_size(f54); ... for (i = 0; i < report_size; i += F54_REPORT_DATA_SIZE) { int size = min(F54_REPORT_DATA_SIZE, report_size - i); ... rmi_read_block(.., f54->report_data + i, size); } report_data is allocated once at probe from F54's own electrode counts (array3_size(f54->num_tx_electrodes, f54->num_rx_electrodes, sizeof(u16))), but rmi_f54_get_report_size() computes the size from drv_data->num_*_electrodes when those are set, i.e. from the F55 function's electrode counts. Both counts come straight from device queries (F54 and F55 each report up to 255 electrodes) and nothing constrains the F55 counts to the F54 ones. A malicious or malfunctioning RMI4 device that reports larger F55 electrode counts than its F54 counts makes report_size exceed the allocation, so the read loop writes past report_data (and the V4L2 dequeue memcpy() then reads past it). On conforming hardware the F55 configured electrodes are a subset of the F54 physical electrodes, so report_size never exceeds the buffer and well-behaved devices are unaffected. Record the allocation size and reject a report that does not fit, mirroring the existing zero-size check. Fixes: c762cc68b6a1 ("Input: synaptics-rmi4 - propagate correct number of rx and tx electrodes to F54") Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas Assisted-by: Antigravity:gemini-3.5-flash Link: https://patch.msgid.link/20260626051802.4033172-3-dmitry.torokhov@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/rmi4/rmi_f54.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/input/rmi4/rmi_f54.c b/drivers/input/rmi4/rmi_f54.c index 8eac320c43e35c..75839a54656b9b 100644 --- a/drivers/input/rmi4/rmi_f54.c +++ b/drivers/input/rmi4/rmi_f54.c @@ -104,6 +104,7 @@ struct f54_data { enum rmi_f54_report_type report_type; u8 *report_data; + size_t max_report_size; int report_size; bool is_busy; @@ -548,6 +549,13 @@ static void rmi_f54_work(struct work_struct *work) goto out; /* retry won't help */ } + if (report_size > f54->max_report_size) { + dev_err(&fn->dev, "Report size %d exceeds buffer size %zu\n", + report_size, f54->max_report_size); + error = -EINVAL; + goto out; + } + /* * Need to check if command has completed. * If not try again later. @@ -678,8 +686,8 @@ static int rmi_f54_probe(struct rmi_function *fn) rx = f54->num_rx_electrodes; tx = f54->num_tx_electrodes; - f54->report_data = devm_kzalloc(&fn->dev, - array3_size(tx, rx, sizeof(u16)), + f54->max_report_size = array3_size(tx, rx, sizeof(u16)); + f54->report_data = devm_kzalloc(&fn->dev, f54->max_report_size, GFP_KERNEL); if (f54->report_data == NULL) return -ENOMEM; From fbfd76746adc16d64be29ff113f673b70bc3f5c2 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Thu, 25 Jun 2026 22:17:54 -0700 Subject: [PATCH 301/612] Input: synaptics-rmi4 - block s_input when F54 queue is busy Changing the input (diagnostic report type) mid-stream changes the report size. Since V4L2 buffers are allocated based on the size at stream start, changing the input while streaming could lead to a heap buffer overflow if the new size is larger than the allocated buffers. Prevent this by blocking VIDIOC_S_INPUT with -EBUSY if the V4L2 queue is busy (streaming). Fixes: 3a762dbd5347 ("[media] Input: synaptics-rmi4 - add support for F54 diagnostics") Cc: stable@vger.kernel.org Assisted-by: Antigravity:gemini-3.5-flash Reviewed-by: Hans Verkuil Link: https://patch.msgid.link/20260626051802.4033172-5-dmitry.torokhov@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/rmi4/rmi_f54.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/input/rmi4/rmi_f54.c b/drivers/input/rmi4/rmi_f54.c index 75839a54656b9b..345646612032db 100644 --- a/drivers/input/rmi4/rmi_f54.c +++ b/drivers/input/rmi4/rmi_f54.c @@ -445,7 +445,12 @@ static int rmi_f54_set_input(struct f54_data *f54, unsigned int i) static int rmi_f54_vidioc_s_input(struct file *file, void *priv, unsigned int i) { - return rmi_f54_set_input(video_drvdata(file), i); + struct f54_data *f54 = video_drvdata(file); + + if (vb2_is_busy(&f54->queue)) + return -EBUSY; + + return rmi_f54_set_input(f54, i); } static int rmi_f54_vidioc_g_input(struct file *file, void *priv, From 69ee44e1a23be62318189dc4b37fa4ad94053269 Mon Sep 17 00:00:00 2001 From: Baul Lee Date: Wed, 5 Aug 2026 10:34:41 +0900 Subject: [PATCH 302/612] ALSA: usb-audio: fix OOB write on Type II inbound URBs data_ep_set_params() sizes each URB transfer buffer before it adds the Format Type II transfer delimiter: u->packets = urb_packs; u->buffer_size = maxsize * u->packets; if (fmt->fmt_type == UAC_FORMAT_TYPE_II) u->packets++; /* for transfer delimiter */ u->urb = usb_alloc_urb(u->packets, GFP_KERNEL); buffer_size is computed from the pre-increment packet count and never recomputed, so for a Type II endpoint the buffer is one packet short of the packet count the URB is built with. prepare_inbound_urb() then lays out one iso frame per packet and never consults buffer_size: offs = 0; for (i = 0; i < urb_ctx->packets; i++) { urb->iso_frame_desc[i].offset = offs; urb->iso_frame_desc[i].length = ep->curpacksize; offs += ep->curpacksize; } urb->transfer_buffer_length = offs; urb->number_of_packets = urb_ctx->packets; The last descriptor therefore points one packet past the end of the transfer buffer, where the host controller writes device data on every inbound transfer. prepare_silent_urb() and prepare_playback_urb() bound their fill loops by ctx->buffer_size, so only capture is affected. fmt_type comes from the device's audio streaming descriptors, so any device advertising a Type II capture format hits this once userspace sets hw_params on the stream. KASAN on 7.2.0-rc5 (arm64) with a dummy_hcd/raw-gadget device, one report per inbound transfer: BUG: KASAN: slab-out-of-bounds in dummy_timer Write of size 64 at addr ffff0000186171c0 by task cons02/166 __asan_memcpy dummy_timer hrtimer_run_softirq Allocated by task 166: usb_alloc_coherent snd_usb_endpoint_set_params The buggy address is located 0 bytes to the right of allocated 64-byte region [ffff000018617180, ffff0000186171c0) Compute buffer_size after the delimiter packet has been accounted for, and bound the fill loop by buffer_size, as prepare_silent_urb() already does on the outbound side. This grows every Type II URB allocation by one maxsize packet. Discovered by XBOW, triaged by Baul Lee Fixes: 8fdff6a319e7 ("ALSA: snd-usb: implement new endpoint streaming model") Reported-by: Federico Kirschbaum Reported-by: Baul Lee Cc: stable@vger.kernel.org Signed-off-by: Baul Lee Link: https://patch.msgid.link/20260805013441.38245-1-baul.lee@xbow.com Signed-off-by: Takashi Iwai --- sound/usb/endpoint.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c index dca06ba1c67e0b..a1d449f2a34235 100644 --- a/sound/usb/endpoint.c +++ b/sound/usb/endpoint.c @@ -385,13 +385,15 @@ static int prepare_inbound_urb(struct snd_usb_endpoint *ep, case SND_USB_ENDPOINT_TYPE_DATA: offs = 0; for (i = 0; i < urb_ctx->packets; i++) { + if (offs + ep->curpacksize > urb_ctx->buffer_size) + break; urb->iso_frame_desc[i].offset = offs; urb->iso_frame_desc[i].length = ep->curpacksize; offs += ep->curpacksize; } urb->transfer_buffer_length = offs; - urb->number_of_packets = urb_ctx->packets; + urb->number_of_packets = i; break; case SND_USB_ENDPOINT_TYPE_SYNC: @@ -1243,10 +1245,10 @@ static int data_ep_set_params(struct snd_usb_endpoint *ep) u->index = i; u->ep = ep; u->packets = urb_packs; - u->buffer_size = maxsize * u->packets; if (fmt->fmt_type == UAC_FORMAT_TYPE_II) u->packets++; /* for transfer delimiter */ + u->buffer_size = maxsize * u->packets; u->urb = usb_alloc_urb(u->packets, GFP_KERNEL); if (!u->urb) goto out_of_memory; From 2ca1eea3cd17930daffe9e429a7c89232036ec24 Mon Sep 17 00:00:00 2001 From: Baul Lee Date: Wed, 5 Aug 2026 10:34:45 +0900 Subject: [PATCH 303/612] ALSA: usx2y: bound the hwdep mmap fault offset snd_us428ctls_vm_fault() turns the faulting page offset into a kernel address with no bound of any kind: offset = vmf->pgoff << PAGE_SHIFT; vaddr = (char *)(...)->us428ctls_sharedmem + offset; page = virt_to_page(vaddr); get_page(page); vmf->page = page; return 0; snd_us428ctls_mmap() checks only the length of the mapping, never the offset, and us428ctls_sharedmem is a single page from alloc_pages_exact(). For a character device file_mmap_size_max() returns ULONG_MAX, so the mm layer imposes no ceiling either. Every page offset above zero resolves to a struct page outside the object, and the handler installs it into the caller's address space read-write; the vma is not marked read-only. The caller picks the page frame with a single mmap() argument and gets read-write access to a page of kernel memory it does not own; an offset that lands in an unpopulated vmemmap region oopses instead. A process that can open the hwdep node of an attached US-X2Y reaches this after loading the FPGA image through the same node; no capability check is involved. On 7.2.0-rc5 (arm64), mmap() with a large offset: Unable to handle kernel paging request at virtual address fffffdffc45d5ac8 pc : snd_us428ctls_vm_fault+0x68/0x140 [snd_usb_usx2y] Call trace: snd_us428ctls_vm_fault+0x68/0x140 [snd_usb_usx2y] __do_fault __handle_mm_fault handle_mm_fault el0_da Reject any offset outside the shared region. The pcm hwdep handler in usx2yhwdeppcm.c computes its address the same way and needs the same bound. Discovered by XBOW, triaged by Baul Lee Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: Federico Kirschbaum Reported-by: Baul Lee Cc: stable@vger.kernel.org Signed-off-by: Baul Lee Link: https://patch.msgid.link/20260805013445.38283-1-baul.lee@xbow.com Signed-off-by: Takashi Iwai --- sound/usb/usx2y/usX2Yhwdep.c | 2 ++ sound/usb/usx2y/usx2yhwdeppcm.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/sound/usb/usx2y/usX2Yhwdep.c b/sound/usb/usx2y/usX2Yhwdep.c index 4d792518482618..7f153a2fcdd813 100644 --- a/sound/usb/usx2y/usX2Yhwdep.c +++ b/sound/usb/usx2y/usX2Yhwdep.c @@ -25,6 +25,8 @@ static vm_fault_t snd_us428ctls_vm_fault(struct vm_fault *vmf) void *vaddr; offset = vmf->pgoff << PAGE_SHIFT; + if (offset >= US428_SHAREDMEM_PAGES) + return VM_FAULT_SIGBUS; vaddr = (char *)((struct usx2ydev *)vmf->vma->vm_private_data)->us428ctls_sharedmem + offset; page = virt_to_page(vaddr); get_page(page); diff --git a/sound/usb/usx2y/usx2yhwdeppcm.c b/sound/usb/usx2y/usx2yhwdeppcm.c index 7c90214485d970..f1d5b4b797ad01 100644 --- a/sound/usb/usx2y/usx2yhwdeppcm.c +++ b/sound/usb/usx2y/usx2yhwdeppcm.c @@ -672,6 +672,8 @@ static vm_fault_t snd_usx2y_hwdep_pcm_vm_fault(struct vm_fault *vmf) void *vaddr; offset = vmf->pgoff << PAGE_SHIFT; + if (offset >= USX2Y_HWDEP_PCM_PAGES) + return VM_FAULT_SIGBUS; vaddr = (char *)((struct usx2ydev *)vmf->vma->vm_private_data)->hwdep_pcm_shm + offset; vmf->page = virt_to_page(vaddr); get_page(vmf->page); From ff8da20b6f47c48d46e47f93f7a59e2d56ee9107 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 4 Aug 2026 22:09:10 +0200 Subject: [PATCH 304/612] Revert "thermal/drivers/hwmon: Cleanup coding style a bit" Revert commit 030a48b0f6ce ("thermal/drivers/hwmon: Cleanup coding style a bit") that introduced a use-after-free into the error path of thermal_add_hwmon_sysfs() by removing a valid check from it. Link: https://lore.kernel.org/linux-hwmon/20260803183915.4ED7D1F000E9@smtp.kernel.org/ Cc: All applicable Signed-off-by: Rafael J. Wysocki Reviewed-by: Lukasz Luba Link: https://patch.msgid.link/5123895.31r3eYUQgx@rafael.j.wysocki --- drivers/thermal/thermal_hwmon.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/thermal/thermal_hwmon.c b/drivers/thermal/thermal_hwmon.c index 597c33c8a55508..ec73d03a1e60a2 100644 --- a/drivers/thermal/thermal_hwmon.c +++ b/drivers/thermal/thermal_hwmon.c @@ -212,7 +212,8 @@ int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) if (new_hwmon_device) hwmon_device_unregister(hwmon->device); free_mem: - kfree(hwmon); + if (new_hwmon_device) + kfree(hwmon); return result; } From 5004889551dfaf7f4b2cda8ed213d234bb8ebf82 Mon Sep 17 00:00:00 2001 From: Harald Freudenberger Date: Mon, 3 Aug 2026 10:33:34 +0200 Subject: [PATCH 305/612] s390/zcrypt: Fix CPRB memory allocation in zcrypt misc code Both CPRB alloc functions in zcrypt_ccamisc.c and zcrypt_ep11misc.c did not round up the memory allocation to a multiple of 4 bytes as it is needed by the zcrypt layer to process the CPRBs. Now the alloc_and_prep_cprbmem() and alloc_cprbmem() functions guarantee that the base CPRB struct and a possible parameter block are aligned to a 4-byte boundary and the backing memory allocation is rounded up to the next multiple of 4 byte. Also the free_cprbmem() is updated and scrubs the rounded up amount of memory. Fixes: 9bdb5f7e8369 ("s390/zcrypt: Introduce cprb mempool for cca misc functions") Signed-off-by: Harald Freudenberger Reviewed-by: Holger Dengler Cc: stable@vger.kernel.org # 6.16+ Signed-off-by: Vasily Gorbik --- drivers/s390/crypto/zcrypt_ccamisc.c | 20 ++++++++++++++------ drivers/s390/crypto/zcrypt_ep11misc.c | 16 +++++++++++----- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/drivers/s390/crypto/zcrypt_ccamisc.c b/drivers/s390/crypto/zcrypt_ccamisc.c index 86d2ee78c9f48c..d4ce6352b5b2e2 100644 --- a/drivers/s390/crypto/zcrypt_ccamisc.c +++ b/drivers/s390/crypto/zcrypt_ccamisc.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -267,6 +268,10 @@ EXPORT_SYMBOL(cca_check_sececckeytoken); * block, reply CPRB and reply param block and fill in values * for the common fields. Returns 0 on success or errno value * on failure. + * It is guaranteed that request and a possible param block + * are aligned to a 4 byte boundary. Furthermore if a param + * block is used, the memory allocated for this is rounded up to + * the next multiple of 4 bytes. */ static int alloc_and_prep_cprbmem(size_t paramblen, u8 **p_cprb_mem, @@ -275,7 +280,8 @@ static int alloc_and_prep_cprbmem(size_t paramblen, u32 xflags) { u8 *cprbmem = NULL; - size_t cprbplusparamblen = sizeof(struct CPRBX) + paramblen; + size_t cprbplusparamblen = + ALIGN(sizeof(struct CPRBX), 4) + ALIGN(paramblen, 4); size_t len = 2 * cprbplusparamblen; struct CPRBX *preqcblk, *prepcblk; @@ -302,10 +308,10 @@ static int alloc_and_prep_cprbmem(size_t paramblen, memcpy(preqcblk->func_id, "T2", 2); preqcblk->rpl_msgbl = cprbplusparamblen; if (paramblen) { - preqcblk->req_parmb = - ((u8 __user *)preqcblk) + sizeof(struct CPRBX); - preqcblk->rpl_parmb = - ((u8 __user *)prepcblk) + sizeof(struct CPRBX); + preqcblk->req_parmb = ((u8 __user *)preqcblk) + + ALIGN(sizeof(struct CPRBX), 4); + preqcblk->rpl_parmb = ((u8 __user *)prepcblk) + + ALIGN(sizeof(struct CPRBX), 4); } *p_cprb_mem = cprbmem; @@ -323,8 +329,10 @@ static int alloc_and_prep_cprbmem(size_t paramblen, */ static void free_cprbmem(void *mem, size_t paramblen, bool scrub, u32 xflags) { + size_t cprblen = ALIGN(sizeof(struct CPRBX), 4) + ALIGN(paramblen, 4); + if (mem && scrub) - memzero_explicit(mem, 2 * (sizeof(struct CPRBX) + paramblen)); + memzero_explicit(mem, 2 * cprblen); if (xflags & ZCRYPT_XFLAG_NOMEMALLOC) mempool_free(mem, cprb_mempool); diff --git a/drivers/s390/crypto/zcrypt_ep11misc.c b/drivers/s390/crypto/zcrypt_ep11misc.c index 3dda9589f2b955..2d900ffc5068ac 100644 --- a/drivers/s390/crypto/zcrypt_ep11misc.c +++ b/drivers/s390/crypto/zcrypt_ep11misc.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -355,21 +356,24 @@ EXPORT_SYMBOL(ep11_check_aes_key); /* * Allocate and prepare ep11 cprb plus additional payload. + * It is guaranteed that the memory is aligned to a 4 byte boundary. + * Furthermore the memory allocation is rounded up to the next + * multiple of 4 bytes (with taking the payload_len into account). */ static void *alloc_cprbmem(size_t payload_len, u32 xflags) { - size_t len = sizeof(struct ep11_cprb) + payload_len; + size_t memlen = ALIGN(sizeof(struct ep11_cprb) + payload_len, 4); struct ep11_cprb *cprb = NULL; if (xflags & ZCRYPT_XFLAG_NOMEMALLOC) { - if (len <= CPRB_MEMPOOL_ITEM_SIZE) + if (memlen <= CPRB_MEMPOOL_ITEM_SIZE) cprb = mempool_alloc_preallocated(cprb_mempool); } else { - cprb = kmalloc(len, GFP_KERNEL); + cprb = kmalloc(memlen, GFP_KERNEL); } if (!cprb) return NULL; - memset(cprb, 0, len); + memset(cprb, 0, memlen); cprb->cprb_len = sizeof(struct ep11_cprb); cprb->cprb_ver_id = 0x04; @@ -385,8 +389,10 @@ static void *alloc_cprbmem(size_t payload_len, u32 xflags) */ static void free_cprbmem(void *mem, size_t payload_len, bool scrub, u32 xflags) { + size_t memlen = ALIGN(sizeof(struct ep11_cprb) + payload_len, 4); + if (mem && scrub) - memzero_explicit(mem, sizeof(struct ep11_cprb) + payload_len); + memzero_explicit(mem, memlen); if (xflags & ZCRYPT_XFLAG_NOMEMALLOC) mempool_free(mem, cprb_mempool); From 35ac6754ba6da45fd193f5ce3f665f7490b830d5 Mon Sep 17 00:00:00 2001 From: Harald Freudenberger Date: Mon, 3 Aug 2026 10:33:35 +0200 Subject: [PATCH 306/612] s390/zcrypt: Improve CCA CPRB length and overflow checks The xcrb_msg_to_type6cprb_msgx() function lacks proper input validation, creating security vulnerabilities: 1. Integer overflow after CEIL4 alignment: Signed int variables could overflow during 4-byte boundary alignment, causing undersized buffer allocations or incorrect bounds checking. 2. Missing minimum size validation: The CPRBX structure is copied from userspace without verifying sufficient buffer length. Undersized buffers cause uninitialized memory access when reading structure fields like cprbx.cprb_len and cprbx.domain. 3. Arithmetic overflow in sum calculations: Adding control block and data block sizes could overflow, bypassing size checks and enabling buffer overflows. Fix by using size_t for length calculations, adding U32_MAX boundary checks after alignment, validating minimum control block size before copying from userspace, and detecting sum calculation overflows. Fixes: e2c6d91eb8b1 ("s390/zcrypt: Rework domain processing within zcrypt device driver") Signed-off-by: Harald Freudenberger Reviewed-by: Holger Dengler Cc: stable@vger.kernel.org # 7.1+ Signed-off-by: Vasily Gorbik --- drivers/s390/crypto/zcrypt_msgtype6.c | 79 ++++++++++++--------------- 1 file changed, 36 insertions(+), 43 deletions(-) diff --git a/drivers/s390/crypto/zcrypt_msgtype6.c b/drivers/s390/crypto/zcrypt_msgtype6.c index 40f72cdf284d3e..3e19e79d747cf0 100644 --- a/drivers/s390/crypto/zcrypt_msgtype6.c +++ b/drivers/s390/crypto/zcrypt_msgtype6.c @@ -342,49 +342,39 @@ static int xcrb_msg_to_type6cprb_msgx(bool userspace, struct ap_message *ap_msg, }; } __packed * msg = ap_msg->msg; - int rcblen = CEIL4(xcrb->request_control_blk_length); - int req_sumlen, resp_sumlen; - char *req_data = ap_msg->msg + sizeof(struct type6_hdr) + rcblen; - char *function_code; + size_t req_cblen, rep_cblen, req_sumlen, rep_sumlen; + char *function_code, *req_data; - if (CEIL4(xcrb->request_control_blk_length) < - xcrb->request_control_blk_length) - return -EINVAL; /* overflow after alignment*/ - - /* length checks */ - ap_msg->len = sizeof(struct type6_hdr) + - CEIL4(xcrb->request_control_blk_length) + - xcrb->request_data_length; + /* request length and overflow checks */ + if (xcrb->request_control_blk_length < sizeof(struct CPRBX)) + return -EINVAL; + req_cblen = CEIL4((size_t)xcrb->request_control_blk_length); + if (req_cblen > U32_MAX) + return -EINVAL; + req_sumlen = req_cblen + xcrb->request_data_length; + if (req_sumlen > U32_MAX) + return -EINVAL; + ap_msg->len = sizeof(struct type6_hdr) + req_sumlen; if (ap_msg->len > ap_msg->bufsize) return -EINVAL; - - /* - * Overflow check - * sum must be greater (or equal) than the largest operand - */ - req_sumlen = CEIL4(xcrb->request_control_blk_length) + - xcrb->request_data_length; - if ((CEIL4(xcrb->request_control_blk_length) <= - xcrb->request_data_length) ? + if (req_cblen <= xcrb->request_data_length ? req_sumlen < xcrb->request_data_length : - req_sumlen < CEIL4(xcrb->request_control_blk_length)) { + req_sumlen < req_cblen) { return -EINVAL; } - if (CEIL4(xcrb->reply_control_blk_length) < - xcrb->reply_control_blk_length) - return -EINVAL; /* overflow after alignment*/ - - /* - * Overflow check - * sum must be greater (or equal) than the largest operand - */ - resp_sumlen = CEIL4(xcrb->reply_control_blk_length) + - xcrb->reply_data_length; - if ((CEIL4(xcrb->reply_control_blk_length) <= - xcrb->reply_data_length) ? - resp_sumlen < xcrb->reply_data_length : - resp_sumlen < CEIL4(xcrb->reply_control_blk_length)) { + /* reply length and overflow checks */ + if (xcrb->reply_control_blk_length < sizeof(struct CPRBX)) + return -EINVAL; + rep_cblen = CEIL4((size_t)xcrb->reply_control_blk_length); + if (rep_cblen > U32_MAX) + return -EINVAL; + rep_sumlen = rep_cblen + xcrb->reply_data_length; + if (rep_sumlen > U32_MAX) + return -EINVAL; + if (rep_cblen <= xcrb->reply_data_length ? + rep_sumlen < xcrb->reply_data_length : + rep_sumlen < rep_cblen) { return -EINVAL; } @@ -393,7 +383,7 @@ static int xcrb_msg_to_type6cprb_msgx(bool userspace, struct ap_message *ap_msg, memcpy(msg->hdr.agent_id, &xcrb->agent_ID, sizeof(xcrb->agent_ID)); msg->hdr.tocardlen1 = xcrb->request_control_blk_length; if (xcrb->request_data_length) { - msg->hdr.offset2 = msg->hdr.offset1 + rcblen; + msg->hdr.offset2 = msg->hdr.offset1 + req_cblen; msg->hdr.tocardlen2 = xcrb->request_data_length; } msg->hdr.fromcardlen1 = xcrb->reply_control_blk_length; @@ -404,8 +394,8 @@ static int xcrb_msg_to_type6cprb_msgx(bool userspace, struct ap_message *ap_msg, xcrb->request_control_blk_addr, xcrb->request_control_blk_length)) return -EFAULT; - if (msg->cprbx.cprb_len + sizeof(msg->hdr.function_code) > - xcrb->request_control_blk_length) + /* copy subfunction code into AP msg type 6 function code field */ + if (msg->cprbx.cprb_len > req_cblen - sizeof(msg->hdr.function_code)) return -EINVAL; function_code = ((unsigned char *)&msg->cprbx) + msg->cprbx.cprb_len; memcpy(msg->hdr.function_code, function_code, @@ -437,10 +427,13 @@ static int xcrb_msg_to_type6cprb_msgx(bool userspace, struct ap_message *ap_msg, } /* copy data block */ - if (xcrb->request_data_length && - z_copy_from_user(userspace, req_data, xcrb->request_data_address, - xcrb->request_data_length)) - return -EFAULT; + if (xcrb->request_data_length) { + req_data = ap_msg->msg + sizeof(struct type6_hdr) + req_cblen; + if (z_copy_from_user(userspace, req_data, + xcrb->request_data_address, + xcrb->request_data_length)) + return -EFAULT; + } return 0; } From 17ac0bc866fc624cd05f022dcd8b730c0af11bb1 Mon Sep 17 00:00:00 2001 From: Harald Freudenberger Date: Mon, 3 Aug 2026 10:33:36 +0200 Subject: [PATCH 307/612] s390/zcrypt: Improve EP11 CPRB length and overflow checks The xcrb_msg_to_type6_ep11cprb_msgx() function lacks proper input validation, creating security vulnerabilities: 1. Missing minimum size validation: The ep11_cprb structure and subsequent payload fields (pld_tag, pld_lenfmt) are copied from userspace without verifying sufficient buffer length. 2. Arithmetic overflow in length calculations: CEIL4 alignment could overflow, bypassing size checks and enabling buffer overflows. 3. The payload is asn1 encoded but the function just uses a simple c struct overlay to access some fields of the payload. Fix by using size_t for length calculations, adding U32_MAX boundary checks after alignment, and validating minimum request size and minimum reply size before copying from userspace. Do a very simple asn1 parsing of the payload up to the function value field. Fixes: e2c6d91eb8b1 ("s390/zcrypt: Rework domain processing within zcrypt device driver") Signed-off-by: Harald Freudenberger Cc: stable@vger.kernel.org # 7.1+ Reviewed-by: Holger Dengler Signed-off-by: Vasily Gorbik --- drivers/s390/crypto/zcrypt_msgtype6.c | 152 +++++++++++++++++++------- 1 file changed, 113 insertions(+), 39 deletions(-) diff --git a/drivers/s390/crypto/zcrypt_msgtype6.c b/drivers/s390/crypto/zcrypt_msgtype6.c index 3e19e79d747cf0..7e1f76c935ee18 100644 --- a/drivers/s390/crypto/zcrypt_msgtype6.c +++ b/drivers/s390/crypto/zcrypt_msgtype6.c @@ -19,6 +19,7 @@ #include #include #include +#include #include "ap_bus.h" #include "zcrypt_api.h" @@ -34,6 +35,9 @@ #define CEXXC_RESPONSE_TYPE_XCRB 1 #define CEXXC_RESPONSE_TYPE_EP11 2 +/* smallest possible EP11 payload size */ +#define MIN_EP11_PAYLOAD_SIZE 5 + MODULE_AUTHOR("IBM Corporation"); MODULE_DESCRIPTION("Cryptographic Coprocessor (message type 6), " \ "Copyright IBM Corp. 2001, 2023"); @@ -438,12 +442,59 @@ static int xcrb_msg_to_type6cprb_msgx(bool userspace, struct ap_message *ap_msg, return 0; } +/* + * Simple asn1 int reader/decoder helper function + * Returns number of bytes processed or < 0 on failure + * Only accepts int length values of 1, 2 or 4. + */ +static inline int asn1_int_decode(const u8 *buf, size_t intlen, u32 *u) +{ + switch (intlen) { + case 1: + *u = (u32)(*buf); + return 1; + case 2: + *u = (u32)get_unaligned_be16(buf); + return 2; + case 4: + *u = (u32)get_unaligned_be32(buf); + return 4; + default: + return -EINVAL; + } +} + +/* + * Simple asn1 length parse helper function + * Returns number of bytes processed or < 0 on failure + * Only accepts length encoded within the length octet + * or for long form 1, 2 or 4 octet length bytes. + */ +static inline int asn1_length_decode(const u8 *buf, size_t buflen, u32 *u) +{ + int i; + + if (buflen < 1) + return -EINVAL; + + if (*buf < 128) { + *u = (u32)(*buf & 0x7F); + return 1; + } + + i = *buf & 0x7F; + if (--buflen < i) + return -EINVAL; + i = asn1_int_decode(++buf, i, u); + + return i < 0 ? i : i + 1; +} + static int xcrb_msg_to_type6_ep11cprb_msgx(bool userspace, struct ap_message *ap_msg, struct ep11_urb *xcrb, unsigned int *fcode, unsigned int *domain) { - unsigned int lfmt; static struct type6_hdr static_type6_ep11_hdr = { .type = 0x06, .rqid = {0x00, 0x01}, @@ -455,34 +506,32 @@ static int xcrb_msg_to_type6_ep11cprb_msgx(bool userspace, struct ap_message *ap struct { struct type6_hdr hdr; union { - struct { - struct ep11_cprb cprbx; - unsigned char pld_tag; /* fixed value 0x30 */ - unsigned char pld_lenfmt; /* length format */ - } __packed; + struct ep11_cprb cprbx; DECLARE_FLEX_ARRAY(u8, userdata); }; } __packed * msg = ap_msg->msg; - struct pld_hdr { - unsigned char func_tag; /* fixed value 0x4 */ - unsigned char func_len; /* fixed value 0x4 */ - unsigned int func_val; /* function ID */ - unsigned char dom_tag; /* fixed value 0x4 */ - unsigned char dom_len; /* fixed value 0x4 */ - unsigned int dom_val; /* domain id */ - } __packed * payload_hdr = NULL; - - if (CEIL4(xcrb->req_len) < xcrb->req_len) - return -EINVAL; /* overflow after alignment*/ + size_t req_len, rep_len, pld_len; + unsigned char *pld; + int offs = 0, i; + unsigned int u; - /* length checks */ - ap_msg->len = sizeof(struct type6_hdr) + CEIL4(xcrb->req_len); + /* request length and overflow checks */ + if (xcrb->req_len < sizeof(struct ep11_cprb) + MIN_EP11_PAYLOAD_SIZE) + return -EINVAL; + req_len = CEIL4(xcrb->req_len); + if (req_len < xcrb->req_len || req_len > U32_MAX) + return -EINVAL; + ap_msg->len = sizeof(struct type6_hdr) + req_len; if (ap_msg->len > ap_msg->bufsize) return -EINVAL; - if (CEIL4(xcrb->resp_len) < xcrb->resp_len) - return -EINVAL; /* overflow after alignment*/ + /* reply length and overflow checks */ + if (xcrb->resp_len < sizeof(struct ep11_cprb)) + return -EINVAL; + rep_len = CEIL4(xcrb->resp_len); + if (rep_len < xcrb->resp_len || rep_len > U32_MAX) + return -EINVAL; /* prepare type6 header */ msg->hdr = static_type6_ep11_hdr; @@ -491,26 +540,51 @@ static int xcrb_msg_to_type6_ep11cprb_msgx(bool userspace, struct ap_message *ap /* Import CPRB data from the ioctl input parameter */ if (z_copy_from_user(userspace, msg->userdata, - (char __force __user *)xcrb->req, xcrb->req_len)) { + (char __force __user *)xcrb->req, xcrb->req_len)) return -EFAULT; - } - if ((msg->pld_lenfmt & 0x80) == 0x80) { /*ext.len.fmt 2 or 3*/ - switch (msg->pld_lenfmt & 0x03) { - case 1: - lfmt = 2; - break; - case 2: - lfmt = 3; - break; - default: - return -EINVAL; - } - } else { - lfmt = 1; /* length format #1 */ - } - payload_hdr = (struct pld_hdr *)((&msg->pld_lenfmt) + lfmt); - *fcode = payload_hdr->func_val & 0xFFFF; + pld = msg->userdata + sizeof(struct ep11_cprb); + pld_len = msg->cprbx.payload_len; + if (pld_len != xcrb->req_len - sizeof(struct ep11_cprb)) + return -EINVAL; + /* + * At this point pld_len is always >= MIN_EP11_PAYLOAD_SIZE + * and the smallest supported asn1 payload is: + * payload tag (1 octet) + * payload length (1-5 octets) + * function tag (1 octet) + * function length (1-5 octets) + * function value (1-4 octets) + */ + + /* payload tag */ + if (pld[offs++] != 0x30) + return -EINVAL; + /* payload length field */ + i = asn1_length_decode(pld + offs, pld_len - offs, &u); + if (i < 0) + return -EINVAL; + offs += i; + if (offs >= pld_len || u > pld_len - offs) + return -EINVAL; + /* function tag */ + if (pld[offs++] != 0x04) + return -EINVAL; + /* function length */ + if (offs >= pld_len) + return -EINVAL; + i = asn1_length_decode(pld + offs, pld_len - offs, &u); + if (i < 0) + return -EINVAL; + offs += i; + if (offs >= pld_len || u > pld_len - offs) + return -EINVAL; + /* function value */ + i = asn1_int_decode(pld + offs, u, &u); + if (i < 0) + return -EINVAL; + offs += i; + *fcode = 0xFFFF & u; /* enable special processing based on the cprbs flags special bit */ if (msg->cprbx.flags & 0x20) From 0864a163783bff109b548266921829ea794edc93 Mon Sep 17 00:00:00 2001 From: Harald Freudenberger Date: Mon, 3 Aug 2026 10:33:37 +0200 Subject: [PATCH 308/612] s390/zcrypt: Improve EP11 CPRB domain handling with ASN.1 parsing The zcrypt_msgtype6_send_ep11_cprb() function uses fragile struct overlays to access and modify the domain field in the EP11 CPRB payload, creating maintainability and security concerns: 1. Struct overlay approach (pld_hdr) assumes fixed payload structure and doesn't validate the actual ASN.1 encoding. 2. Complex length format detection logic is error-prone and doesn't properly validate bounds at each parsing step. 3. Direct struct member access bypasses proper ASN.1 validation. Fix by replacing struct overlays with explicit ASN.1 parsing that validates each field (payload tag/length, function tag/length/value, optional domain tag/length/value) with proper bounds checking at every step. Add asn1_int_encode() helper function to safely write integer values with correct endianness conversion. This makes the code consistent with the validation pattern introduced with the rework of the xcrb_msg_to_type6_ep11cprb_msgx() function. Fixes: e2c6d91eb8b1 ("s390/zcrypt: Rework domain processing within zcrypt device driver") Signed-off-by: Harald Freudenberger Cc: stable@vger.kernel.org # 7.1+ Reviewed-by: Holger Dengler Signed-off-by: Vasily Gorbik --- drivers/s390/crypto/zcrypt_msgtype6.c | 127 +++++++++++++++++++------- 1 file changed, 96 insertions(+), 31 deletions(-) diff --git a/drivers/s390/crypto/zcrypt_msgtype6.c b/drivers/s390/crypto/zcrypt_msgtype6.c index 7e1f76c935ee18..e79e68b4dd41d0 100644 --- a/drivers/s390/crypto/zcrypt_msgtype6.c +++ b/drivers/s390/crypto/zcrypt_msgtype6.c @@ -1226,6 +1226,28 @@ int prep_ep11_ap_msg(bool userspace, struct ep11_urb *xcrb, func_code, domain); } +/* + * Simple asn1 int writer/encoder helper function + * Returns number of bytes processed or < 0 on failure + * Only accepts int length values of 1, 2 or 4. + */ +static inline int asn1_int_encode(u8 *buf, size_t intlen, u32 u) +{ + switch (intlen) { + case 1: + *buf = (u8)u; + return 1; + case 2: + put_unaligned_be16((u16)u, buf); + return 2; + case 4: + put_unaligned_be32((u32)u, buf); + return 4; + default: + return -EINVAL; + } +} + /* * The request distributor calls this function if it picked the CEX4P * device to handle a send_ep11_cprb request. @@ -1238,51 +1260,94 @@ static long zcrypt_msgtype6_send_ep11_cprb(bool userspace, struct zcrypt_queue * struct ap_message *ap_msg) { int rc; - unsigned int lfmt; struct ap_response_type *resp_type = &ap_msg->response; struct { struct type6_hdr hdr; struct ep11_cprb cprbx; - unsigned char pld_tag; /* fixed value 0x30 */ - unsigned char pld_lenfmt; /* payload length format */ } __packed * msg = ap_msg->msg; - struct pld_hdr { - unsigned char func_tag; /* fixed value 0x4 */ - unsigned char func_len; /* fixed value 0x4 */ - unsigned int func_val; /* function ID */ - unsigned char dom_tag; /* fixed value 0x4 */ - unsigned char dom_len; /* fixed value 0x4 */ - unsigned int dom_val; /* domain id */ - } __packed * payload_hdr = NULL; /* * The target domain field within the cprb body/payload block will be * replaced by the usage domain for non-management commands only. * Therefore we check the first bit of the 'flags' parameter for * management command indication. - * 0 - non management command - * 1 - management command */ - if (!((msg->cprbx.flags & 0x80) == 0x80)) { - msg->cprbx.target_id = (unsigned int) - AP_QID_QUEUE(zq->queue->qid); - - if ((msg->pld_lenfmt & 0x80) == 0x80) { /*ext.len.fmt 2 or 3*/ - switch (msg->pld_lenfmt & 0x03) { - case 1: - lfmt = 2; - break; - case 2: - lfmt = 3; - break; - default: + if (!(msg->cprbx.flags & 0x80)) { + int i, offs = 0; + size_t pld_len; + u8 *pld; + u32 u; + + /* update target field in ep11_cprb */ + msg->cprbx.target_id = (u32)AP_QID_QUEUE(zq->queue->qid); + + /* ptr and length to payload */ + pld = ap_msg->msg + + sizeof(struct type6_hdr) + sizeof(struct ep11_cprb); + pld_len = msg->cprbx.payload_len; + if (pld_len < MIN_EP11_PAYLOAD_SIZE) + return -EINVAL; + + /* + * Parse the asn1 payload, at least we have + * pld tag (1 octet) + * payload length (1-5 octets) + * function tag (1 octet) + * function length (1-5 octets) + * function value (1-4 octets) + * ----- optional fields ----- + * domain tag (1 octet) + * domain length (1-5 octets) + * domain value (1-4 octets) + * ... maybe much more data ... + */ + + /* payload tag */ + if (pld[offs++] != 0x30) + return -EINVAL; + /* payload length field */ + i = asn1_length_decode(pld + offs, pld_len - offs, &u); + if (i < 0) + return -EINVAL; + offs += i; + if (offs >= pld_len || u > pld_len - offs) + return -EINVAL; + /* function tag */ + if (pld[offs++] != 0x04) + return -EINVAL; + /* function length */ + if (offs >= pld_len) + return -EINVAL; + i = asn1_length_decode(pld + offs, pld_len - offs, &u); + if (i < 0) + return -EINVAL; + offs += i; + if (u > pld_len - offs) + return -EINVAL; + /* skip over the function value */ + offs += u; + /* is there some payload left which could hold a domain value ? */ + if (offs < pld_len && pld_len - offs >= 3) { + /* domain tag */ + if (pld[offs++] != 0x04) return -EINVAL; - } - } else { - lfmt = 1; /* length format #1 */ + /* domain length */ + i = asn1_length_decode(pld + offs, pld_len - offs, &u); + if (i < 0) + return -EINVAL; + offs += i; + if (offs >= pld_len || u > pld_len - offs) + return -EINVAL; + /* + * pld[offs] is now at the start of the domain value + * with the value sprawled in u octets. + */ + i = asn1_int_encode(pld + offs, u, + AP_QID_QUEUE(zq->queue->qid)); + if (i < 0) + return -EINVAL; + offs += i; } - payload_hdr = (struct pld_hdr *)((&msg->pld_lenfmt) + lfmt); - payload_hdr->dom_val = AP_QID_QUEUE(zq->queue->qid); } /* From eb363254472493e3458156fc11fd56dca92f4333 Mon Sep 17 00:00:00 2001 From: Harald Freudenberger Date: Mon, 3 Aug 2026 10:33:38 +0200 Subject: [PATCH 309/612] s390/zcrypt: Pad trailing CCA or EP11 message with zeros The both functions xcrb_msg_to_type6cprb_msgx() and xcrb_msg_to_type6_ep11cprb_msgx() copy the user space message into a kernel buffer based on the message length. But on further processing the message is supposed to be 4 byte length adjusted. Thus up to 3 bytes of uninitialized kernel memory are forwarded to further processing steps and may unwanted expose kernel memory to the crypto card firmware. This patch contains code to pad the gap between user space copied message and message buffer length sent down to further processing of the CCA or EP11 message to zeros. Fixes: e2c6d91eb8b1 ("s390/zcrypt: Rework domain processing within zcrypt device driver") Signed-off-by: Harald Freudenberger Reviewed-by: Holger Dengler Cc: stable@vger.kernel.org # 7.1+ Signed-off-by: Vasily Gorbik --- drivers/s390/crypto/zcrypt_msgtype6.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/s390/crypto/zcrypt_msgtype6.c b/drivers/s390/crypto/zcrypt_msgtype6.c index e79e68b4dd41d0..3df1d676de5d8e 100644 --- a/drivers/s390/crypto/zcrypt_msgtype6.c +++ b/drivers/s390/crypto/zcrypt_msgtype6.c @@ -398,6 +398,10 @@ static int xcrb_msg_to_type6cprb_msgx(bool userspace, struct ap_message *ap_msg, xcrb->request_control_blk_addr, xcrb->request_control_blk_length)) return -EFAULT; + /* pad tail with 0 up to req_cblen */ + if (xcrb->request_control_blk_length < req_cblen) + memset(msg->userdata + xcrb->request_control_blk_length, + 0, req_cblen - xcrb->request_control_blk_length); /* copy subfunction code into AP msg type 6 function code field */ if (msg->cprbx.cprb_len > req_cblen - sizeof(msg->hdr.function_code)) return -EINVAL; @@ -542,6 +546,10 @@ static int xcrb_msg_to_type6_ep11cprb_msgx(bool userspace, struct ap_message *ap if (z_copy_from_user(userspace, msg->userdata, (char __force __user *)xcrb->req, xcrb->req_len)) return -EFAULT; + /* pad tail with 0 up to req_len */ + if (xcrb->req_len < req_len) + memset(msg->userdata + xcrb->req_len, 0, + req_len - xcrb->req_len); pld = msg->userdata + sizeof(struct ep11_cprb); pld_len = msg->cprbx.payload_len; From 478a1c3abebfc717db0d1281a9cdd7befafee542 Mon Sep 17 00:00:00 2001 From: Andy Lutomirski Date: Mon, 3 Aug 2026 17:37:08 -0700 Subject: [PATCH 310/612] mm: fix incorrect flush address in direct page table reclaim When zap_pte_range reclaims a page table, it does: pte_free_tlb(tlb, pmd_pgtable(pmdval), addr); and this is unconditionally wrong: if this code executes, addr *always* points one past the end of the range covered by the table. The addr parameter is used to flush the TLB (really the paging-structure-cache) to drop references to the to-be-freed table, and any architecture that cares about the parameter will flush the wrong address. (But they'll still free the correct page). I think it's worth contemplating why the kernel works at all. If we hit the offending line of code, we will first clear the PMD entry (line 1954, zap_empty_pte_table), then we will issue pending flushes if force_flush is set (tlb_flush_mmu_tlbonly(tlb)), then we will skip the retry on line 1979 (phew!), and then we will do the offending pte_free_tlb call. *Or* we will clear the PMD entry immediately before pte_free_tlb (line 1983, zap_pte_table_if_empty). If we have any pending flushes (i.e. we actually zapped any last-level entries) at the time we clear the PMD entry, then the flush really ought to flush all references to the table (Linus certainly seems to think it will on all architectures [0]). The condition under which we have no accumulated flushes at the time of the clear is very complex (the whole zap_pte_range function has absurdly complex control flow). If we do hit the bad case, then we will end up clearing the PMD entry after the last time the range is flushed, and any CPU is free to cache a reference to the (empty) page table. If this happens due to an ordinary read or write, it would segfault, so it would be rare. But the cache could be speculatively filled as well. Then we'll flush the wrong address and then free and possibly reuse the table. On x86, even flushing the wrong address works on non-KPTI Intel systems because INVLPG flushes *all* paging-structure-caches, not just the ones for the target address. But INVPCID does not, and flush_tlb_one_user will use INVPCID if it's available. And then we're toast. AMD systems are more susceptible: we set the EFER.TCE bit, which makes even INVLPG only flush the target address. I think this might fix an issue in ripgrep reported here: https://github.com/BurntSushi/ripgrep/issues/3494 [0] https://lore.kernel.org/all/CA+55aFzBggoXtNXQeng5d_mRoDnaMBE5Y+URs+PHR67nUpMtaw@mail.gmail.com/T/#u Signed-off-by: Andy Lutomirski Fixes: 4c640eb4181c ("mm: move pte table reclaim code to memory.c") Cc: Liam Howlett Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Mike Rapoport Cc: Suren Baghdasaryan Cc: stable@vger.kernel.org Acked-by: Vlastimil Babka (SUSE) Acked-by: David Hildenbrand (Arm) Acked-by: Michal Hocko Acked-by: Qi Zheng Signed-off-by: Linus Torvalds --- mm/memory.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/memory.c b/mm/memory.c index ff338c2abe9231..6b8280cfc1dbea 100644 --- a/mm/memory.c +++ b/mm/memory.c @@ -1981,7 +1981,7 @@ static unsigned long zap_pte_range(struct mmu_gather *tlb, if (can_reclaim_pt) { if (direct_reclaim || zap_pte_table_if_empty(mm, pmd, start, &pmdval)) { - pte_free_tlb(tlb, pmd_pgtable(pmdval), addr); + pte_free_tlb(tlb, pmd_pgtable(pmdval), start); mm_dec_nr_ptes(mm); } } From 8786d74bf50e6797b6f655eb381ef6b25451161f Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Thu, 25 Jun 2026 22:17:55 -0700 Subject: [PATCH 311/612] Input: synaptics-rmi4 - propagate F54 worker errors to V4L2 queue Previously, rmi_f54_buffer_queue() waited for the worker thread to finish but ignored whether it succeeded. If the worker failed (e.g., due to a timeout or register read failure), the queue thread would silently return success, delivering stale or uninitialized memory to userspace. Add a 'report_error' field to struct f54_data to store the worker's exit status. Check this field in rmi_f54_buffer_queue() after the worker finishes, and mark the buffer as VB2_BUF_STATE_ERROR if an error occurred. Fixes: 3a762dbd5347 ("[media] Input: synaptics-rmi4 - add support for F54 diagnostics") Reported-by: sashiko-bot@kernel.org Cc: stable@vger.kernel.org Assisted-by: Antigravity:gemini-3.5-flash Link: https://patch.msgid.link/20260626051802.4033172-6-dmitry.torokhov@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/rmi4/rmi_f54.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/input/rmi4/rmi_f54.c b/drivers/input/rmi4/rmi_f54.c index 345646612032db..6c6cdec7da9e33 100644 --- a/drivers/input/rmi4/rmi_f54.c +++ b/drivers/input/rmi4/rmi_f54.c @@ -106,6 +106,7 @@ struct f54_data { u8 *report_data; size_t max_report_size; int report_size; + int report_error; bool is_busy; struct mutex status_mutex; @@ -340,6 +341,12 @@ static void rmi_f54_buffer_queue(struct vb2_buffer *vb) mutex_lock(&f54->data_mutex); } + if (f54->report_error) { + dev_err(&f54->fn->dev, "Error acquiring report: %d\n", f54->report_error); + state = VB2_BUF_STATE_ERROR; + goto data_done; + } + ptr = vb2_plane_vaddr(vb, 0); if (!ptr) { dev_err(&f54->fn->dev, "Error acquiring frame ptr\n"); @@ -610,6 +617,7 @@ static void rmi_f54_work(struct work_struct *work) report_size = 0; f54->report_size = report_size; + f54->report_error = error; if (report_size == 0 && !error) { queue_delayed_work(f54->workqueue, &f54->work, From 31a420a822ff92e2090bd5d65efe8e34e2d6d9b8 Mon Sep 17 00:00:00 2001 From: Luxiao Xu Date: Tue, 4 Aug 2026 22:29:01 +0800 Subject: [PATCH 312/612] bpf: Check sk_state before sk_protocol in bpf_tcp_*_syncookie bpf_tcp_gen_syncookie and bpf_tcp_check_syncookie accept a socket pointer 'sk' with argument type ARG_PTR_TO_BTF_ID_SOCK_COMMON. However, they access sk->sk_protocol without validating whether 'sk' represents a full socket. Fix this issue by checking sk->sk_state != TCP_LISTEN before inspecting sk->sk_protocol in both bpf_tcp_gen_syncookie and bpf_tcp_check_syncookie. Since mini-sockets are never in the TCP_LISTEN state, the condition short-circuits and prevents dereferencing fullsock-specific fields. Fixes: 399040847084 ("bpf: add helper to check for a valid SYN cookie") Fixes: 70d66244317e ("bpf: add bpf_tcp_gen_syncookie helper") Reported-by: Vega Signed-off-by: Luxiao Xu Signed-off-by: Ren Wei Signed-off-by: Daniel Borkmann Reviewed-by: Eric Dumazet Reviewed-by: Kuniyuki Iwashima Link: https://lore.kernel.org/bpf/6218aa3534d0d2d3f448fde70a8dc2769d7a8201.1785823138.git.rakukuip@gmail.com --- net/core/filter.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/core/filter.c b/net/core/filter.c index 11bb0d236822af..16845987b24491 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -7684,7 +7684,7 @@ BPF_CALL_5(bpf_tcp_check_syncookie, struct sock *, sk, void *, iph, u32, iph_len return -EINVAL; /* sk_listener() allows TCP_NEW_SYN_RECV, which makes no sense here. */ - if (sk->sk_protocol != IPPROTO_TCP || sk->sk_state != TCP_LISTEN) + if (sk->sk_state != TCP_LISTEN || sk->sk_protocol != IPPROTO_TCP) return -EINVAL; if (!READ_ONCE(sock_net(sk)->ipv4.sysctl_tcp_syncookies)) @@ -7757,7 +7757,7 @@ BPF_CALL_5(bpf_tcp_gen_syncookie, struct sock *, sk, void *, iph, u32, iph_len, if (unlikely(!sk || th_len < sizeof(*th) || th_len != th->doff * 4)) return -EINVAL; - if (sk->sk_protocol != IPPROTO_TCP || sk->sk_state != TCP_LISTEN) + if (sk->sk_state != TCP_LISTEN || sk->sk_protocol != IPPROTO_TCP) return -EINVAL; if (!READ_ONCE(sock_net(sk)->ipv4.sysctl_tcp_syncookies)) From a0ab2ba83e35159d81cec830a92e885ecf8139be Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Mon, 3 Aug 2026 14:17:38 +0800 Subject: [PATCH 313/612] tcp: fix TFO max_qlen accounting across reuseport migration A listener's TCP_FASTOPEN max_qlen stops being accurate and lets through far more pending Fast Open requests than it was configured for. This only shows up with SO_REUSEPORT listener migration, where closing a listener hands its still-pending TFO children over to a surviving one. fastopenq.qlen is charged in tcp_fastopen_create_child() when the child is created and uncharged in reqsk_fastopen_remove() when the handshake completes. The uncharge follows rsk_listener of the request the child points at, and inet_reqsk_clone() has repointed the child at a new request owned by the new listener, so the ++ and the -- land on two different sockets. The new listener's qlen drifts negative and its limit no longer binds. Charge the new listener during migration, like reqsk_queue_migrated() already does for queue->young and queue->qlen. Fixes: 54b92e841937 ("tcp: Migrate TCP_ESTABLISHED/TCP_SYN_RECV sockets in accept queues.") Signed-off-by: Jiayuan Chen Reviewed-by: Kuniyuki Iwashima Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260803061739.134737-1-jiayuan.chen@linux.dev Signed-off-by: Jakub Kicinski --- net/ipv4/inet_connection_sock.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/net/ipv4/inet_connection_sock.c b/net/ipv4/inet_connection_sock.c index 56902bba54838b..6257459bcee247 100644 --- a/net/ipv4/inet_connection_sock.c +++ b/net/ipv4/inet_connection_sock.c @@ -943,11 +943,23 @@ static struct request_sock *inet_reqsk_clone(struct request_sock *req, nreq->rsk_listener = sk; - /* We need not acquire fastopenq->lock - * because the child socket is locked in inet_csk_listen_stop(). - */ - if (sk->sk_protocol == IPPROTO_TCP && tcp_rsk(nreq)->tfo_listener) + if (sk->sk_protocol == IPPROTO_TCP && tcp_rsk(nreq)->tfo_listener) { + struct fastopen_queue *fastopenq; + + /* reqsk_fastopen_remove() will uncharge nreq->rsk_listener, + * that is @sk, so charge it here. Unlike the listener + * being closed, @sk is live and needs its lock. + */ + fastopenq = &inet_csk(sk)->icsk_accept_queue.fastopenq; + spin_lock_bh(&fastopenq->lock); + fastopenq->qlen++; + spin_unlock_bh(&fastopenq->lock); + + /* We need not acquire fastopenq->lock + * because the child socket is locked in inet_csk_listen_stop(). + */ rcu_assign_pointer(tcp_sk(nreq->sk)->fastopen_rsk, nreq); + } return nreq; } From 8aecf0bbcc72605592134c917c222207d8f63ab0 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Tue, 4 Aug 2026 09:33:28 +0000 Subject: [PATCH 314/612] netfilter: nf_flow_table: drop existing skb dst before skb_dst_set_noref() Incoming skbs passing through netfilter flowtable offload hooks (or XFRM offload path) might already carry a ref-counted dst_entry assigned during earlier RX or routing steps. Calling skb_dst_set_noref() when skb already holds a ref-counted dst overwrites skb->_skb_refdst, leaking the previous dst_entry reference count and triggering a DEBUG_NET_WARN_ON_ONCE assertion in skb_dst_check_unset(): WARNING: at skb_dst_check_unset include/linux/skbuff.h:1170 WARNING: at skb_dst_set_noref include/linux/skbuff.h:1234 WARNING: at nf_flow_offload_ip_hook+0xf6c/0x2b60 net/netfilter/nf_flow_table_ip.c:864 Drop any existing dst_entry reference with skb_dst_drop(skb) before setting the non-referenced flowtable destination. Fixes: 2a79fd3908ac ("netfilter: nf_flow_table: attach dst to skbs") Reported-by: syzbot+76d4e3a055aec3b007ec@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/6a71b141.9511d2ce.1fc5b9.033b.GAE@google.com/T/#u Signed-off-by: Eric Dumazet Reviewed-by: Pablo Neira Ayuso Link: https://patch.msgid.link/20260804093328.1831847-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/netfilter/nf_flow_table_ip.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/netfilter/nf_flow_table_ip.c b/net/netfilter/nf_flow_table_ip.c index 0b78decce8a9bd..c9e332fafcb5c2 100644 --- a/net/netfilter/nf_flow_table_ip.c +++ b/net/netfilter/nf_flow_table_ip.c @@ -310,6 +310,7 @@ static unsigned int nf_flow_xmit_xfrm(struct sk_buff *skb, struct dst_entry *dst) { skb_orphan(skb); + skb_dst_drop(skb); skb_dst_set_noref(skb, dst); dst_output(state->net, state->sk, skb); return NF_STOLEN; @@ -861,6 +862,7 @@ nf_flow_offload_ip_hook(void *priv, struct sk_buff *skb, return NF_DROP; } xmit.dest = neigh->ha; + skb_dst_drop(skb); skb_dst_set_noref(skb, &rt->dst); break; case FLOW_OFFLOAD_XMIT_DIRECT: @@ -1178,6 +1180,7 @@ nf_flow_offload_ipv6_hook(void *priv, struct sk_buff *skb, return NF_DROP; } xmit.dest = neigh->ha; + skb_dst_drop(skb); skb_dst_set_noref(skb, &rt->dst); break; case FLOW_OFFLOAD_XMIT_DIRECT: From 5f26a690e8efa54315e4922368daf54e0b8f5515 Mon Sep 17 00:00:00 2001 From: Zihan Xi Date: Sun, 2 Aug 2026 09:23:34 +0000 Subject: [PATCH 315/612] mac802154: fix netdev use-after-free in beacon worker mac802154_beacon_worker() reads local->beacon_req under RCU and derives the sub-interface from the request, but then drops the RCU read lock and continues to use both sdata and the embedded wpan_dev. mac802154_stop_beacons_locked() cancels only pending beacon work, clears local->beacon_req and frees the request. A beacon worker that is already running can therefore continue after interface teardown and dereference the freed netdev private area. The scan worker already pins the netdev before leaving RCU. Apply the same lifetime rule to the beacon worker: take a netdev reference while the request is still protected by RCU, and release it on all paths that continue after the reference is acquired. Fixes: 3accf4762734 ("mac802154: Handle basic beaconing") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Zihan Xi Reviewed-by: Miquel Raynal Link: https://patch.msgid.link/e9a3909c7a6281967961773ca841e860b8ecf40e.1785596603.git.zihanx@nebusec.ai Signed-off-by: Jakub Kicinski --- net/mac802154/scan.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/mac802154/scan.c b/net/mac802154/scan.c index 65089826ff5972..005338f89b75e1 100644 --- a/net/mac802154/scan.c +++ b/net/mac802154/scan.c @@ -415,6 +415,7 @@ void mac802154_beacon_worker(struct work_struct *work) container_of(work, struct ieee802154_local, beacon_work.work); struct cfg802154_beacon_request *beacon_req; struct ieee802154_sub_if_data *sdata; + netdevice_tracker dev_tracker; struct wpan_dev *wpan_dev; u8 interval; int ret; @@ -427,12 +428,14 @@ void mac802154_beacon_worker(struct work_struct *work) } sdata = IEEE802154_WPAN_DEV_TO_SUB_IF(beacon_req->wpan_dev); + netdev_hold(sdata->dev, &dev_tracker, GFP_ATOMIC); /* Wait an arbitrary amount of time in case we cannot use the device */ if (local->suspended || !ieee802154_sdata_running(sdata)) { rcu_read_unlock(); queue_delayed_work(local->mac_wq, &local->beacon_work, msecs_to_jiffies(1000)); + netdev_put(sdata->dev, &dev_tracker); return; } @@ -450,6 +453,7 @@ void mac802154_beacon_worker(struct work_struct *work) if (interval < IEEE802154_ACTIVE_SCAN_DURATION) queue_delayed_work(local->mac_wq, &local->beacon_work, local->beacon_interval); + netdev_put(sdata->dev, &dev_tracker); } int mac802154_stop_beacons_locked(struct ieee802154_local *local, From afa58b7384913c8773d837acdb07b035690ec5d2 Mon Sep 17 00:00:00 2001 From: Henry Martin Date: Mon, 3 Aug 2026 12:36:18 +0800 Subject: [PATCH 316/612] net/ncsi: fix heap OOB read in NCSI_CMD_SEND_CMD payload length ncsi_send_cmd_nl() takes the number of bytes to copy from the attacker-controlled ncsi_pkt_hdr.length field of the in-band packet header, while the source buffer is the NCSI_ATTR_DATA netlink attribute whose readable size is nla_len() - sizeof(ncsi_pkt_hdr). The two length sources are never cross-checked: only nla_len() >= sizeof(struct ncsi_pkt_hdr) is enforced. With hdr->length set larger than the attribute payload (up to 65535 against at most 2032 readable bytes), ncsi_cmd_handler_oem() copies past the end of the netlink attribute buffer with unsafe_memcpy(), leaking up to ~64KB of kernel heap memory into the transmitted NCSI command packet. The destination skb is sized by the declared payload, so the write side does not overflow - this is a pure OOB read / information leak, reachable with CAP_NET_ADMIN on systems with a registered NCSI device (e.g. OpenBMC on Aspeed BMC SoCs, where NET_NCSI=y is standard). Reject commands whose declared payload extends past the end of the data attribute. The issue was found by the autokbug dynamic kernel fuzzer at Tencent Yunding Lab. Fixes: 9771b8ccdfa6 ("net/ncsi: Extend NC-SI Netlink interface to allow user space to send NC-SI command") Reported-by: Henry Martin Signed-off-by: Henry Martin Link: https://patch.msgid.link/20260803043618.3210301-1-bsdhenrymartin@gmail.com Signed-off-by: Jakub Kicinski --- net/ncsi/ncsi-netlink.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/ncsi/ncsi-netlink.c b/net/ncsi/ncsi-netlink.c index 2f872d064396df..8cc538358f6a39 100644 --- a/net/ncsi/ncsi-netlink.c +++ b/net/ncsi/ncsi-netlink.c @@ -461,6 +461,10 @@ static int ncsi_send_cmd_nl(struct sk_buff *msg, struct genl_info *info) nca.req_flags = NCSI_REQ_FLAG_NETLINK_DRIVEN; nca.info = info; nca.payload = ntohs(hdr->length); + if (nca.payload > len - sizeof(*hdr)) { + ret = -EINVAL; + goto out_netlink; + } nca.data = data + sizeof(*hdr); ret = ncsi_xmit_cmd(&nca); From f803c086399da277b5d0ff36a107d0f162751800 Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Mon, 3 Aug 2026 14:12:33 +0800 Subject: [PATCH 317/612] ip6_tunnel: clear skb2->cb[] in ip6ip6_err() ip6ip6_err() clones an outer IPv6 ICMP error skb, pulls it to the quoted inner IPv6 packet, and then passes the clone to icmpv6_send(). The clone still carries the outer packet's inet6_skb_parm in skb->cb. If the outer packet had a Home Address Option, IP6CB(skb2)->dsthao remains non-zero after skb_pull(). icmpv6_send() later calls mip6_addr_swap(), which uses that stale dsthao offset against the quoted inner packet. A malformed inner destination-options header can then make the HAO lookup and address swap run past the end of the quoted packet and corrupt skb_shared_info. Clear skb2->cb[] before pulling the quoted inner IPv6 packet so the reply path does not reuse metadata left by the outer IPv6 stack. Fixes: e490d1d85cf5 ("[IPV6] IP6TUNNEL: Split out generic routine in ip6ip6_err().") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Zhiling Zou Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/fe1a5e765fbca88d69391887f0ed26a19e3e4d39.1785736562.git.zhilinz@nebusec.ai Signed-off-by: Jakub Kicinski --- net/ipv6/ip6_tunnel.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c index bf8e40af60b083..ebf83f090376ce 100644 --- a/net/ipv6/ip6_tunnel.c +++ b/net/ipv6/ip6_tunnel.c @@ -684,6 +684,9 @@ ip6ip6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, if (!skb2) return 0; + /* Remove debris left by outer IPv6 stack. */ + memset(IP6CB(skb2), 0, sizeof(*IP6CB(skb2))); + skb_dst_drop(skb2); skb_pull(skb2, offset); skb_reset_network_header(skb2); From 3874892dd27d5387aa9a06f58d9060f18f351d24 Mon Sep 17 00:00:00 2001 From: Dongli Zhang Date: Sun, 2 Aug 2026 15:46:12 -0700 Subject: [PATCH 318/612] net: tap: set skb->dev before parsing virtio net header in tap_get_user_xdp() The commit 4f61f133f354 ("net: tap: NULL pointer derefence in dev_parse_header_protocol when skb->dev is null") fixed a crash in tap_get_user() by assigning skb->dev before calling tun_vnet_hdr_to_skb(). This is required because virtio_net_hdr_to_skb() may invoke dev_parse_header_protocol(), which dereferences skb->dev. Without the assignment, a NULL pointer dereference can occur. However, tap_get_user_xdp() still parses the virtio-net header before assigning skb->dev. When the vhost TX path passes an XDP buffer containing a GSO virtio-net header but the protocol is set to zero on purpose, tun_vnet_hdr_to_skb() can reach dev_parse_header_protocol() while skb->dev is still NULL, resulting in a crash. Fix this by looking up the tap device and assigning skb->dev before calling tun_vnet_hdr_to_skb(), matching the ordering already used in tap_get_user(). Preserve the existing RCU read-side critical section across dev_queue_xmit(). Fixes: 924a9bc362a5 ("net: check if protocol extracted by virtio_net_hdr_set_proto is correct") Cc: stable@vger.kernel.org Signed-off-by: Dongli Zhang Reviewed-by: Willem de Bruijn Acked-by: Michael S. Tsirkin Link: https://patch.msgid.link/20260802224612.264563-1-dongli.zhang@oracle.com Signed-off-by: Jakub Kicinski --- drivers/net/tap.c | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/drivers/net/tap.c b/drivers/net/tap.c index fae115915c8eff..5d2d34d24ce818 100644 --- a/drivers/net/tap.c +++ b/drivers/net/tap.c @@ -1074,10 +1074,21 @@ static int tap_get_user_xdp(struct tap_queue *q, struct xdp_buff *xdp) skb_reset_mac_header(skb); skb->protocol = eth_hdr(skb)->h_proto; + rcu_read_lock(); + tap = rcu_dereference(q->tap); + if (!tap) { + kfree_skb(skb); + rcu_read_unlock(); + return 0; + } + skb->dev = tap->dev; + if (vnet_hdr_len) { err = tun_vnet_hdr_to_skb(q->flags, skb, gso); - if (err) + if (err) { + rcu_read_unlock(); goto err_kfree; + } } /* Move network header to the right position for VLAN tagged packets */ @@ -1085,15 +1096,8 @@ static int tap_get_user_xdp(struct tap_queue *q, struct xdp_buff *xdp) vlan_get_protocol_and_depth(skb, skb->protocol, &depth) != 0) skb_set_network_header(skb, depth); - rcu_read_lock(); - tap = rcu_dereference(q->tap); - if (tap) { - skb->dev = tap->dev; - skb_probe_transport_header(skb); - dev_queue_xmit(skb); - } else { - kfree_skb(skb); - } + skb_probe_transport_header(skb); + dev_queue_xmit(skb); rcu_read_unlock(); return 0; From 8ae344eb540af3f457179b52bc6061416752485c Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Fri, 31 Jul 2026 22:19:06 +0800 Subject: [PATCH 319/612] net: prestera: validate firmware header length prestera_fw_hdr_parse() reads the firmware header before checking that the firmware image contains that header. Reject images shorter than struct prestera_fw_header before decoding the magic and version fields. Fixes: 4c2703dfd7fabb ("net: marvell: prestera: Add PCI interface support") Signed-off-by: Pengpeng Hou Acked-by: Elad Nachman Link: https://patch.msgid.link/20260731141500.1-prestera-v2-pengpeng@iscas.ac.cn Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/marvell/prestera/prestera_pci.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/ethernet/marvell/prestera/prestera_pci.c b/drivers/net/ethernet/marvell/prestera/prestera_pci.c index 2989a77e3b422a..1ad0e62a8433ba 100644 --- a/drivers/net/ethernet/marvell/prestera/prestera_pci.c +++ b/drivers/net/ethernet/marvell/prestera/prestera_pci.c @@ -684,6 +684,9 @@ static int prestera_fw_hdr_parse(struct prestera_fw *fw) struct prestera_fw_header *hdr; u32 magic; + if (fw->bin->size < sizeof(*hdr)) + return -EINVAL; + hdr = (struct prestera_fw_header *)fw->bin->data; magic = be32_to_cpu(hdr->magic_number); From c27e360545373b7aee9862a5beef3b9fb3df0c25 Mon Sep 17 00:00:00 2001 From: Hidayath Khan Date: Thu, 30 Jul 2026 14:42:27 +0200 Subject: [PATCH 320/612] dibs: initialise dibs->lock in dibs_dev_alloc() dibs->lock is initialised by dibs_dev_add(), but a dibs device can already take interrupts before that call: ism_probe() runs ism_dev_init(), and hence request_irq(), before it calls dibs_dev_add(). No client can have registered a dmb at that point, so no dmb interrupt can occur, but a GID event interrupt can, and ism_handle_irq() takes dibs->lock unconditionally on entry, before it inspects anything else. Initialise the lock in dibs_dev_alloc() instead, so that it is valid as soon as a driver can publish the device to its interrupt handler. Fixes: cc21191b584c ("dibs: Move data path to dibs layer") Cc: stable@vger.kernel.org Reviewed-by: Alexandra Winter Signed-off-by: Hidayath Khan Link: https://patch.msgid.link/20260730124227.167829-1-hidayath@linux.ibm.com Signed-off-by: Jakub Kicinski --- drivers/dibs/dibs_main.c | 2 +- include/linux/dibs.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/dibs/dibs_main.c b/drivers/dibs/dibs_main.c index 14c3e2d84902e0..4c26fd06973f7c 100644 --- a/drivers/dibs/dibs_main.c +++ b/drivers/dibs/dibs_main.c @@ -138,6 +138,7 @@ struct dibs_dev *dibs_dev_alloc(void) dibs = kzalloc_obj(*dibs); if (!dibs) return dibs; + spin_lock_init(&dibs->lock); dibs->dev.release = dibs_dev_release; dibs->dev.class = &dibs_class; device_initialize(&dibs->dev); @@ -186,7 +187,6 @@ int dibs_dev_add(struct dibs_dev *dibs) int i, ret; max_dmbs = dibs->ops->max_dmbs(); - spin_lock_init(&dibs->lock); dibs->dmb_clientid_arr = kzalloc(max_dmbs, GFP_KERNEL); if (!dibs->dmb_clientid_arr) return -ENOMEM; diff --git a/include/linux/dibs.h b/include/linux/dibs.h index c75607f8a5cf98..d3e0777f25ae09 100644 --- a/include/linux/dibs.h +++ b/include/linux/dibs.h @@ -439,7 +439,7 @@ static inline void *dibs_get_priv(struct dibs_dev *dev, /** * dibs_dev_alloc() - allocate and reference device structure * - * The following fields will be valid upon successful return: dev + * The following fields will be valid upon successful return: dev, lock * NOTE: Use put_device(dibs_get_dev(@dibs)) to give up your reference instead * of freeing @dibs @dev directly once you have successfully called this * function. From 44871eadd07a7f004aa00cb87399461eea08c630 Mon Sep 17 00:00:00 2001 From: Jijie Shao Date: Tue, 4 Aug 2026 21:05:54 +0800 Subject: [PATCH 321/612] MAINTAINERS: add myself as a maintainer for Hisilicon Network Subsystem I am already listed as a maintainer for the HNS3 and HIBMCGE drivers, but not for the broader Hisilicon Network Subsystem entry, whose file pattern covers drivers/net/ethernet/hisilicon/ (e.g. the legacy hns driver). As a result, patches to those files are not CC'd to me. Add myself alongside Jian Shen to help maintain these legacy Hisilicon ethernet drivers and ensure patches in this tree are routed to me. Signed-off-by: Jijie Shao Acked-by: Jian Shen Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260804130554.871716-1-shaojijie@huawei.com Signed-off-by: Jakub Kicinski --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index 0d7987278c07e4..a77cf599bac283 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11743,6 +11743,7 @@ F: drivers/net/ethernet/hisilicon/hibmcge/ HISILICON NETWORK SUBSYSTEM DRIVER M: Jian Shen +M: Jijie Shao L: netdev@vger.kernel.org S: Maintained W: http://www.hisilicon.com From 3abd29c61d2ef37c4102cf755b18be53bb9dbea6 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Mon, 3 Aug 2026 18:41:49 -0700 Subject: [PATCH 322/612] Input: evdev - sanitize event type index when fetching event masks The user-supplied event type index passed to EVIOCGMASK / EVIOCSMASK ioctls is used to index the static counts array in evdev_get_mask_cnt() and client evmasks array in evdev_get_mask(). While the event type is architecturally bounded by EV_CNT, speculative execution may mispredict bounds checks and perform out-of-bounds loads. Sanitize the event type index in evdev_get_mask_cnt() branchlessly using array_index_mask_nospec(). This clamps the index to 0 for safe array access and forces the returned count to 0 speculatively when the index is out of bounds. We do not need additional array_index_nospec() calls in evdev_get_mask() because evdev_get_mask_cnt() speculatively forces the count (and resulting xfer_size) to 0 for out-of-bounds types, preventing any speculative memory access to client evmasks array. Reported-by: "Wagenaar, C.C.J. (Chris)" Cc: stable@vger.kernel.org Assisted-by: Antigravity:gemini-3.6-flash Acked-by: Greg Kroah-Hartman Link: https://patch.msgid.link/anFCAfvxwXB5eJF1@google.com Signed-off-by: Dmitry Torokhov --- drivers/input/evdev.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c index 531db70aa9f923..3a718d6000063e 100644 --- a/drivers/input/evdev.c +++ b/drivers/input/evdev.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include "input-compat.h" @@ -67,8 +68,10 @@ static size_t evdev_get_mask_cnt(unsigned int type) [EV_SND] = SND_CNT, [EV_FF] = FF_CNT, }; + unsigned long mask = array_index_mask_nospec(type, EV_CNT); - return (type < EV_CNT) ? counts[type] : 0; + /* Returns 0 for out-of-bounds types, including speculatively */ + return counts[type & mask] & mask; } /* requires the buffer lock to be held */ From e0d2b5902c5c0d3943af95728753aae165ed1346 Mon Sep 17 00:00:00 2001 From: "Christian A. Ehrhardt" Date: Tue, 23 Jun 2026 20:19:42 +0200 Subject: [PATCH 323/612] drm/panthor: Check VMA boundaries for PMD mappings When checking a different patch[1] sashiko AI pointed out that panthor needs the same fix[2]: In the ->huge_fault handler do not install a PMD huge page mapping if the huge page exceeds the boundaries of the VMA. [1] https://lore.kernel.org/lkml/20260622215718.1532689-1-lk@c--e.de/ [2] https://sashiko.dev/#/patchset/20260622215718.1532689-1-lk%40c--e.de Cc: Boris Brezillon Cc: Steven Price Cc: Liviu Dudau Fixes: 68cbf96b1e9b ("drm/panthor: Part ways with drm_gem_shmem_object") Signed-off-by: Christian A. Ehrhardt Reviewed-by: Steven Price Reviewed-by: Boris Brezillon Link: https://lore.kernel.org/lkml/20260622215718.1532689-1-lk@c--e.de/ Link: https://patch.msgid.link/20260623181942.1536598-1-lk@c--e.de Signed-off-by: Boris Brezillon --- drivers/gpu/drm/panthor/panthor_gem.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c index a1e2eb1ca7bbe6..54535bae2b0cfe 100644 --- a/drivers/gpu/drm/panthor/panthor_gem.c +++ b/drivers/gpu/drm/panthor/panthor_gem.c @@ -802,9 +802,13 @@ static vm_fault_t insert_page(struct vm_fault *vmf, unsigned int order, struct p } else if (order == PMD_ORDER) { unsigned long pfn = page_to_pfn(page); unsigned long paddr = pfn << PAGE_SHIFT; + struct vm_area_struct *vma = vmf->vma; + unsigned long start = ALIGN_DOWN(vmf->address, PMD_SIZE); + unsigned long end = start + PMD_SIZE; + bool in_range = vma->vm_start <= start && end <= vma->vm_end; bool aligned = (vmf->address & ~PMD_MASK) == (paddr & ~PMD_MASK); - if (aligned && + if (aligned && in_range && folio_test_pmd_mappable(page_folio(page))) { pfn &= PMD_MASK >> PAGE_SHIFT; return vmf_insert_pfn_pmd(vmf, pfn, vmf->flags & FAULT_FLAG_WRITE); From b8a39a09ae4eaae04309e1e38ed6a1101d967496 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Tue, 4 Aug 2026 15:20:48 +0000 Subject: [PATCH 324/612] net: remove WARN_ON_ONCE() from sk_mc_loop() sk_mc_loop() can be called for sockets that are neither AF_INET nor AF_INET6 (e.g. AF_PACKET sockets when sending packets via raw/packet socket over virtual devices such as VRF or ipvlan). In such cases, sk_family is not AF_INET/AF_INET6 and sk_mc_loop() falls through the switch statement and triggers WARN_ON_ONCE(1). Non-INET sockets do not support IP_MULTICAST_LOOP or IPV6_MULTICAST_LOOP options, so loopback should default to true without generating a warning. Fixes: f60e5990d9c1 ("ipv6: protect skb->sk accesses from recursive dereference inside the stack") Reported-by: syzbot+22c3218a6fa219e47321@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/6a72024c.13623e66.bdc14.0019.GAE@google.com/T/#u Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260804152048.2134341-1-edumazet@google.com Signed-off-by: Paolo Abeni --- net/core/sock.c | 1 - 1 file changed, 1 deletion(-) diff --git a/net/core/sock.c b/net/core/sock.c index ffa73594c13c75..1ad41904db25b4 100644 --- a/net/core/sock.c +++ b/net/core/sock.c @@ -779,7 +779,6 @@ bool sk_mc_loop(const struct sock *sk) return inet6_test_bit(MC6_LOOP, sk); #endif } - WARN_ON_ONCE(1); return true; } EXPORT_SYMBOL(sk_mc_loop); From 185a4caeecabc150106deda1da170b09f2ad803f Mon Sep 17 00:00:00 2001 From: Sidraya Jayagond Date: Mon, 3 Aug 2026 09:07:01 +0200 Subject: [PATCH 325/612] net/smc: fix TOCTOU race between smc_listen_out() and listener close smc_listen_out() reads lsmc->sk.sk_state without the listener lock, then acquires lock_sock_nested() only after the check passes. This opens a window where smc_close_active() can transition the listener to SMC_CLOSED, call smc_close_cleanup_listen() to drain the accept queue, and release the lock, all between the lockless read and the delayed lock acquisition: smc_listen_work (smc_hs_wq) smc_close_active() ------------------------------- ------------------------- release_sock(child) if (sk_state == SMC_LISTEN) TRUE lock_sock(listener) sk_state = SMC_CLOSED smc_close_cleanup_listen() release_sock(listener) flush_work(tcp_listen_work) lock_sock_nested(listener) smc_accept_enqueue(listener, child) /* child enqueued on dead listener */ smc_close_active() flushes only tcp_listen_work. Work items already dispatched onto smc_hs_wq for the CLC handshake continue running unguarded. smc_accept_enqueue() takes a sock_hold() on the child that is never released, so the child smc_sock, its clcsock, and the reference all leak. A remote peer that opens TCP connections while the server calls close() can exhaust kernel memory. Move lock_sock_nested() to before the sk_state check so that the test and the enqueue are atomic under the listener lock. Fixes: fd57770dd198 ("net/smc: wait for pending work before clcsock release_sock") Reviewed-by: Mahanta Jambigi Signed-off-by: Sidraya Jayagond Reviewed-by: Breno Leitao Reviewed-by: Dust Li Link: https://patch.msgid.link/20260803070701.126339-1-sidraya@linux.ibm.com Signed-off-by: Paolo Abeni --- net/smc/af_smc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/smc/af_smc.c b/net/smc/af_smc.c index b5db69073e20fc..00403175b7406a 100644 --- a/net/smc/af_smc.c +++ b/net/smc/af_smc.c @@ -1931,11 +1931,12 @@ static void smc_listen_out(struct smc_sock *new_smc) atomic_dec(&lsmc->queued_smc_hs); release_sock(newsmcsk); /* lock in smc_listen_work() */ + lock_sock_nested(&lsmc->sk, SINGLE_DEPTH_NESTING); if (lsmc->sk.sk_state == SMC_LISTEN) { - lock_sock_nested(&lsmc->sk, SINGLE_DEPTH_NESTING); smc_accept_enqueue(&lsmc->sk, newsmcsk); release_sock(&lsmc->sk); } else { /* no longer listening */ + release_sock(&lsmc->sk); smc_close_non_accepted(newsmcsk); } From f684c514f7965385dae21f2535f99938e73ec1af Mon Sep 17 00:00:00 2001 From: Ahmed Naseef Date: Tue, 4 Aug 2026 15:35:11 +0400 Subject: [PATCH 326/612] net: phy: mediatek: fix TX blink masks using the RX bits MTK_GPHY_LED_TX_BLINK_SET and MTK_2P5GPHY_LED_TX_BLINK_SET are built from the RX blink bits instead of the TX ones, so both TX masks are identical to their RX counterparts. The TX bits they should be using, MTK_PHY_LED_BLINK_{10,100,1000,2500}TX, are otherwise only referenced by the per-speed branch of mtk_phy_led_hw_ctrl_set(). A TX trigger selected without a link trigger therefore programs the RX blink bits, and the LED blinks on received traffic. The masks are also used to decode the blink register in mtk_phy_led_hw_ctrl_get(), which as a result cannot tell the two triggers apart: an RX-only configuration reads back as RX and TX, and a TX-only configuration reads back as neither. Fixes: 7f9c320c98db ("net: phy: mediatek: Move LED helper functions into mtk phy lib") Cc: stable@vger.kernel.org Signed-off-by: Ahmed Naseef Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260804113511.3371248-1-naseefkm@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/phy/mediatek/mtk.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/net/phy/mediatek/mtk.h b/drivers/net/phy/mediatek/mtk.h index 320f76ffa81fa2..79e09727b81bcc 100644 --- a/drivers/net/phy/mediatek/mtk.h +++ b/drivers/net/phy/mediatek/mtk.h @@ -53,15 +53,15 @@ #define MTK_GPHY_LED_RX_BLINK_SET (MTK_PHY_LED_BLINK_1000RX | \ MTK_PHY_LED_BLINK_100RX | \ MTK_PHY_LED_BLINK_10RX) -#define MTK_GPHY_LED_TX_BLINK_SET (MTK_PHY_LED_BLINK_1000RX | \ - MTK_PHY_LED_BLINK_100RX | \ - MTK_PHY_LED_BLINK_10RX) +#define MTK_GPHY_LED_TX_BLINK_SET (MTK_PHY_LED_BLINK_1000TX | \ + MTK_PHY_LED_BLINK_100TX | \ + MTK_PHY_LED_BLINK_10TX) #define MTK_2P5GPHY_LED_ON_SET (MTK_PHY_LED_ON_LINK2500 | \ MTK_GPHY_LED_ON_SET) #define MTK_2P5GPHY_LED_RX_BLINK_SET (MTK_PHY_LED_BLINK_2500RX | \ MTK_GPHY_LED_RX_BLINK_SET) -#define MTK_2P5GPHY_LED_TX_BLINK_SET (MTK_PHY_LED_BLINK_2500RX | \ +#define MTK_2P5GPHY_LED_TX_BLINK_SET (MTK_PHY_LED_BLINK_2500TX | \ MTK_GPHY_LED_TX_BLINK_SET) #define MTK_PHY_LED_STATE_FORCE_ON 0 From 7e2d693af0d4c05bddccb3541a0aabd69f4cb244 Mon Sep 17 00:00:00 2001 From: "Sergey V. Frolov" Date: Tue, 4 Aug 2026 15:04:48 +0300 Subject: [PATCH 327/612] net: octeontx2-pf: Fix UB in shift operation In function otx2_get_egress_burst_cfg, when the parameter `burst` is 255 and the max mantissa is 255 (0xFFULL), `burst_exp` is set to `ilog2(255) - 1`, which equals 6. This results in an unsigned wrap-around when calculating `(1ULL << (*burst_exp - 7))`, since `*burst_exp - 7` becomes -1, which makes the shift operand 0xFFFFFFFF. This value is greater than the width of the left operand. According to standard 6.5.7 p.3: "The type of the result is that of the promoted left operand. If the value of the right operand is negative or is greater than or equal to the width of the promoted left operand, the behavior is undefined." Fix the off-by-one boundary condition. Add a WARN_ON(*burst_exp < 7) before the else branch as an explicit safeguard. This ensures that if max_mantissa ever changes in a way that reintroduces this condition, it will be immediately caught at runtime rather than silently triggering UB. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: e638a83f167e ("octeontx2-pf: TC_MATCHALL egress ratelimiting offload") Signed-off-by: Sergey V. Frolov Cc: stable@vger.kernel.org Reviewed-by: Ratheesh Kannoth Reviewed-by: Sunil Goutham Link: https://patch.msgid.link/20260804120446.1955448-1-Sergey.V.Frolov@kaspersky.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/marvell/octeontx2/nic/otx2_tc.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_tc.c b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_tc.c index 0b46ec29e64eaa..039fd47ebf5220 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_tc.c +++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_tc.c @@ -54,10 +54,12 @@ static void otx2_get_egress_burst_cfg(struct otx2_nic *nic, u32 burst, if (burst) { *burst_exp = ilog2(burst) ? ilog2(burst) - 1 : 0; tmp = burst - rounddown_pow_of_two(burst); - if (burst < max_mantissa) + if (burst <= max_mantissa) { *burst_mantissa = tmp * 2; - else + } else { + WARN_ON(*burst_exp < 7); *burst_mantissa = tmp / (1ULL << (*burst_exp - 7)); + } } else { *burst_exp = MAX_BURST_EXPONENT; *burst_mantissa = max_mantissa; From dedd34b0f2310e28c5f6d4875cfbf4b7ed821c01 Mon Sep 17 00:00:00 2001 From: Zijie Huang Date: Sat, 1 Aug 2026 21:42:33 +0800 Subject: [PATCH 328/612] net/sched: reject overly deep qdisc hierarchies Deep qdisc hierarchies can lead to excessive recursion in qdisc tree walkers and exhaust the kernel stack. The existing loop check does not cover the create-and-graft path, so a hierarchy can still be extended by creating a new child qdisc below an already deep parent. Store the hierarchy depth in struct Qdisc and update it when qdiscs are grafted. Reject new child qdiscs once the parent is already at the maximum allowed depth. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Suggested-by: Jamal Hadi Salim Reported-by: Vega Assisted-by: Codex:gpt-5.4 Signed-off-by: Zijie Huang Signed-off-by: Ren Wei Reviewed-by: Victor Nogueira Link: https://patch.msgid.link/1e9ab39597423fd5d13cfaaf52279b8ee3d9fc3c.1785434373.git.milkory@outlook.com Acked-by: Jamal Hadi Salim Signed-off-by: Paolo Abeni --- include/net/sch_generic.h | 1 + net/sched/sch_api.c | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/include/net/sch_generic.h b/include/net/sch_generic.h index 45a1e8c78222bd..cbc24877651187 100644 --- a/include/net/sch_generic.h +++ b/include/net/sch_generic.h @@ -99,6 +99,7 @@ struct Qdisc { struct hlist_node hash; u32 handle; u32 parent; + int depth; struct netdev_queue *dev_queue; diff --git a/net/sched/sch_api.c b/net/sched/sch_api.c index 668bcd60d183e0..65b35528d125f4 100644 --- a/net/sched/sch_api.c +++ b/net/sched/sch_api.c @@ -1114,6 +1114,9 @@ static int qdisc_graft(struct net_device *dev, struct Qdisc *parent, unsigned int i, num_q, ingress; struct netdev_queue *dev_queue; + if (new) + new->depth = 0; + ingress = 0; num_q = dev->num_tx_queues; if ((q && q->flags & TCQ_F_INGRESS) || @@ -1211,9 +1214,15 @@ static int qdisc_graft(struct net_device *dev, struct Qdisc *parent, NL_SET_ERR_MSG(extack, "STAB not supported on a non root"); return -EINVAL; } + if (new && parent->depth >= 7) { + NL_SET_ERR_MSG(extack, "Qdisc hierarchy is too deep"); + return -E2BIG; + } err = cops->graft(parent, cl, new, &old, extack); if (err) return err; + if (new) + new->depth = parent->depth + 1; notify_and_destroy(net, skb, n, classid, old, new, extack); } return 0; From d8eb9a06b922418598eac38fb9772ea4e9ef4273 Mon Sep 17 00:00:00 2001 From: Zijie Huang Date: Sat, 1 Aug 2026 21:42:34 +0800 Subject: [PATCH 329/612] selftests/tc-testing: add qdisc hierarchy depth tests Add tdc coverage for the qdisc hierarchy depth limit. The tests verify that the deepest allowed hierarchy can still be created and that adding another child qdisc below it is rejected. Signed-off-by: Zijie Huang Assisted-by: Codex:gpt-5.4 Signed-off-by: Ren Wei Reviewed-by: Victor Nogueira Link: https://patch.msgid.link/5cc2d26a7c8e553759cdd29a3116f843fabc25ba.1785434373.git.milkory@outlook.com Acked-by: Jamal Hadi Salim Signed-off-by: Paolo Abeni --- .../tc-testing/tc-tests/qdiscs/depth.json | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 tools/testing/selftests/tc-testing/tc-tests/qdiscs/depth.json diff --git a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/depth.json b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/depth.json new file mode 100644 index 00000000000000..48f8683f0d1ab0 --- /dev/null +++ b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/depth.json @@ -0,0 +1,74 @@ +[ + { + "id": "ad43", + "name": "Create qdisc hierarchy at maximum depth", + "category": [ + "qdisc", + "depth" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DUMMY root handle 1: htb default 1", + "$TC class add dev $DUMMY parent 1: classid 1:1 htb rate 10mbit", + "$TC qdisc add dev $DUMMY parent 1:1 handle 2: htb default 1", + "$TC class add dev $DUMMY parent 2: classid 2:1 htb rate 10mbit", + "$TC qdisc add dev $DUMMY parent 2:1 handle 3: htb default 1", + "$TC class add dev $DUMMY parent 3: classid 3:1 htb rate 10mbit", + "$TC qdisc add dev $DUMMY parent 3:1 handle 4: htb default 1", + "$TC class add dev $DUMMY parent 4: classid 4:1 htb rate 10mbit", + "$TC qdisc add dev $DUMMY parent 4:1 handle 5: htb default 1", + "$TC class add dev $DUMMY parent 5: classid 5:1 htb rate 10mbit", + "$TC qdisc add dev $DUMMY parent 5:1 handle 6: htb default 1", + "$TC class add dev $DUMMY parent 6: classid 6:1 htb rate 10mbit", + "$TC qdisc add dev $DUMMY parent 6:1 handle 7: htb default 1", + "$TC class add dev $DUMMY parent 7: classid 7:1 htb rate 10mbit" + ], + "cmdUnderTest": "$TC qdisc add dev $DUMMY parent 7:1 handle 8: htb default 1", + "expExitCode": "0", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc htb 8: parent 7:1", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DUMMY root" + ] + }, + { + "id": "c519", + "name": "Reject qdisc hierarchy above maximum depth", + "category": [ + "qdisc", + "depth" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DUMMY root handle 1: htb default 1", + "$TC class add dev $DUMMY parent 1: classid 1:1 htb rate 10mbit", + "$TC qdisc add dev $DUMMY parent 1:1 handle 2: htb default 1", + "$TC class add dev $DUMMY parent 2: classid 2:1 htb rate 10mbit", + "$TC qdisc add dev $DUMMY parent 2:1 handle 3: htb default 1", + "$TC class add dev $DUMMY parent 3: classid 3:1 htb rate 10mbit", + "$TC qdisc add dev $DUMMY parent 3:1 handle 4: htb default 1", + "$TC class add dev $DUMMY parent 4: classid 4:1 htb rate 10mbit", + "$TC qdisc add dev $DUMMY parent 4:1 handle 5: htb default 1", + "$TC class add dev $DUMMY parent 5: classid 5:1 htb rate 10mbit", + "$TC qdisc add dev $DUMMY parent 5:1 handle 6: htb default 1", + "$TC class add dev $DUMMY parent 6: classid 6:1 htb rate 10mbit", + "$TC qdisc add dev $DUMMY parent 6:1 handle 7: htb default 1", + "$TC class add dev $DUMMY parent 7: classid 7:1 htb rate 10mbit", + "$TC qdisc add dev $DUMMY parent 7:1 handle 8: htb default 1", + "$TC class add dev $DUMMY parent 8: classid 8:1 htb rate 10mbit" + ], + "cmdUnderTest": "$TC qdisc add dev $DUMMY parent 8:1 handle 9: htb default 1", + "expExitCode": "2", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc htb 9:", + "matchCount": "0", + "teardown": [ + "$TC qdisc del dev $DUMMY root" + ] + } +] From 26505e1b5b546e2fa9a0296b951ca158460c72d8 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 23 Jul 2026 10:15:22 +0200 Subject: [PATCH 330/612] KVM: SVM: make svm_flush_tlb_gva do a full asid flush if NPT enabled Red Hat is seeing multiple reports of Windows memory corruptions (and consequent BSODs) with hv-tlbflush=on, on AMD processors only. The crashes, while extremely rare, happen even with a stock configuration, but with Driver Verifier enabled they can be detected after approximately 200 VM hours. In particular, Alexander Lougovski measured the following: - on AMD Turin, 15 crashes in 3300 VM hours - on AMD Milan, 2 crashes in 500 VM hours (there are fewer hours here due to the host being smaller) - on Intel Sapphire Rapids, 0 crashes in 8000 VM hours - on AMD Turin with full TLB flush (not exactly this patch but similar), no crashes in ~2 weeks of run time which should also be ~7000 VM hours For Turin, the microcode version was 0x0b002162, which (assuming this is the same issue) should not be affected by the problem listed in https://knowledge.broadcom.com/external/article/419026/bsod-on-virtual-machines-running-on-amd.html; on the other hand that problem should not apply to earlier processors. AMD has not provided any information or analysis yet, and when we asked we didn't know yet that it reproduced on Milan as well. As to the workload, Alexander threw more or less everything at the same time at the VM: - a full Windows Defender scan every 30 minutes - a disk I/O job - a loop doing repeated mmap of system files (mostly to hope that it triggers some consistency check in the Windows memory manager) - SQL Express 2022 + StressDB (1.6M rows), with the host doing queries (75% write/25% read) via sqlcmd Driver Verifier is able to detect BSODs more or less at the same time as the pages are freed. They mostly happen in the Windows Defender filter driver, but occasionally also in the networking stack (e.g., afd.sys) or elsewhere in the filesystem stack (e.g., fltmgr.sys). The flush is issued from kvm_hv_vcpu_flush_tlb(), which receives the cross-CPU requests from the Hyper-V TLB flush hypercalls via a kfifo and is invoked by the KVM_REQ_HV_TLB_FLUSH request. The mechanism is the same for both Intel and AMD, and the handler for both vendors is a simple INVVPID(ADDR)/INVLPGA instruction. Because the request is handled on the destination CPU, there is a question of what happens if the VM is migrated across physical CPUs. In that case, the INVLPGA instruction would use a stale svm->vmcb->control.asid; but if anything that might do an *unnecessary* flush (on an asid that's being used for another VM) and then pre_svm_run() would force a full TLB rebuild. So, for lack of better ideas, this patch forces a full ASID bump in svm_flush_tlb_gva(). To avoid paying the price on Intel and also to avoid unnecessary loops on AMD, the flush_tlb_gva op now returns whether it did a full flush or not; kvm_hv_vcpu_flush_tlb() takes note and exits its loops immediately. While there is an obvious performance impact, about half of the benefit from Hyper-V tlbflush is preserved (10% vs. 20% on the SQL Server workload). kvm_mmu_invalidate_addr() is the only other caller of the flush_tlb_gva op. The change would have a performance impact on every intercepted INVLPG and, for nested SVM, on every L1 INVLPGA. For INVLPGA specifically, this covers the same suspected issue but for nested hypervisors, so it is correct to apply the workaround; for INVLPG on shadow paging, instead, the impact would be stronger and, due to lack of data, for now the use of INVLPGA is left in place in svm_flush_tlb_gva(). Analyzed-by: Vitaly Kuznetsov Analyzed-by: Alexander Lougovski Signed-off-by: Paolo Bonzini --- arch/x86/include/asm/kvm_host.h | 2 +- arch/x86/kvm/hyperv.c | 7 ++++--- arch/x86/kvm/mmu/mmu.c | 2 +- arch/x86/kvm/svm/svm.c | 25 +++++++++++++++++++------ arch/x86/kvm/vmx/main.c | 4 ++-- arch/x86/kvm/vmx/vmx.c | 2 +- arch/x86/kvm/vmx/x86_ops.h | 2 +- 7 files changed, 29 insertions(+), 15 deletions(-) diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h index 5f6c1ce9673b7f..6db5b5f79df9b8 100644 --- a/arch/x86/include/asm/kvm_host.h +++ b/arch/x86/include/asm/kvm_host.h @@ -1858,7 +1858,7 @@ struct kvm_x86_ops { * Can potentially get non-canonical addresses through INVLPGs, which * the implementation may choose to ignore if appropriate. */ - void (*flush_tlb_gva)(struct kvm_vcpu *vcpu, gva_t addr); + void (*flush_tlb_gva)(struct kvm_vcpu *vcpu, gva_t addr, bool *full); /* * Flush any TLB entries created by the guest. Like tlb_flush_gva(), diff --git a/arch/x86/kvm/hyperv.c b/arch/x86/kvm/hyperv.c index fd4eb1e561f7fb..d8736b5dfcd3a8 100644 --- a/arch/x86/kvm/hyperv.c +++ b/arch/x86/kvm/hyperv.c @@ -1974,6 +1974,7 @@ int kvm_hv_vcpu_flush_tlb(struct kvm_vcpu *vcpu) u64 entries[KVM_HV_TLB_FLUSH_FIFO_SIZE]; int i, j, count; gva_t gva; + bool full = false; if (!tdp_enabled || !hv_vcpu) return -EINVAL; @@ -1982,7 +1983,7 @@ int kvm_hv_vcpu_flush_tlb(struct kvm_vcpu *vcpu) count = kfifo_out(&tlb_flush_fifo->entries, entries, KVM_HV_TLB_FLUSH_FIFO_SIZE); - for (i = 0; i < count; i++) { + for (i = 0; i < count && !full; i++) { if (entries[i] == KVM_HV_TLB_FLUSHALL_ENTRY) goto out_flush_all; @@ -1991,11 +1992,11 @@ int kvm_hv_vcpu_flush_tlb(struct kvm_vcpu *vcpu) * pages to flush. */ gva = entries[i] & PAGE_MASK; - for (j = 0; j < (entries[i] & ~PAGE_MASK) + 1; j++) { + for (j = 0; j < (entries[i] & ~PAGE_MASK) + 1 && !full; j++) { if (is_noncanonical_invlpg_address(gva + j * PAGE_SIZE, vcpu)) continue; - kvm_x86_call(flush_tlb_gva)(vcpu, gva + j * PAGE_SIZE); + kvm_x86_call(flush_tlb_gva)(vcpu, gva + j * PAGE_SIZE, &full); } ++vcpu->stat.tlb_flush; diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c index 66e69d2a41b3c5..c9e4739b26d711 100644 --- a/arch/x86/kvm/mmu/mmu.c +++ b/arch/x86/kvm/mmu/mmu.c @@ -6652,7 +6652,7 @@ void kvm_mmu_invalidate_addr(struct kvm_vcpu *vcpu, struct kvm_mmu *mmu, if (is_noncanonical_invlpg_address(addr, vcpu)) return; - kvm_x86_call(flush_tlb_gva)(vcpu, addr); + kvm_x86_call(flush_tlb_gva)(vcpu, addr, NULL); } if (!mmu->sync_spte) diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c index d0971685034b92..60495447f0ca02 100644 --- a/arch/x86/kvm/svm/svm.c +++ b/arch/x86/kvm/svm/svm.c @@ -4227,18 +4227,31 @@ static void svm_flush_tlb_all(struct kvm_vcpu *vcpu) svm_flush_tlb_asid(vcpu); } -static void svm_flush_tlb_gva(struct kvm_vcpu *vcpu, gva_t gva) +static void svm_flush_tlb_guest(struct kvm_vcpu *vcpu) { - struct vcpu_svm *svm = to_svm(vcpu); + kvm_register_mark_dirty(vcpu, VCPU_REG_ERAPS); - invlpga(gva, svm->vmcb->control.asid); + svm_flush_tlb_asid(vcpu); } -static void svm_flush_tlb_guest(struct kvm_vcpu *vcpu) +static void svm_flush_tlb_gva(struct kvm_vcpu *vcpu, gva_t gva, bool *full) { - kvm_register_mark_dirty(vcpu, VCPU_REG_ERAPS); + struct vcpu_svm *svm = to_svm(vcpu); - svm_flush_tlb_asid(vcpu); + /* + * INVLPGA has had errata on Genoa and Turin, and even on older + * generations there were reports of Windows BSODs if INVLPGA + * was used for Hyper-V tlbflush. Use it only for shadow paging + * where it seems to be okay. + */ + if (!npt_enabled) { + invlpga(gva, svm->vmcb->control.asid); + return; + } + + svm_flush_tlb_guest(vcpu); + if (full) + *full = true; } static inline void sync_cr8_to_lapic(struct kvm_vcpu *vcpu) diff --git a/arch/x86/kvm/vmx/main.c b/arch/x86/kvm/vmx/main.c index 83d9921277eac2..f204a0fc0a5711 100644 --- a/arch/x86/kvm/vmx/main.c +++ b/arch/x86/kvm/vmx/main.c @@ -535,12 +535,12 @@ static void vt_flush_tlb_current(struct kvm_vcpu *vcpu) vmx_flush_tlb_current(vcpu); } -static void vt_flush_tlb_gva(struct kvm_vcpu *vcpu, gva_t addr) +static void vt_flush_tlb_gva(struct kvm_vcpu *vcpu, gva_t addr, bool *full) { if (is_td_vcpu(vcpu)) return; - vmx_flush_tlb_gva(vcpu, addr); + vmx_flush_tlb_gva(vcpu, addr, full); } static void vt_flush_tlb_guest(struct kvm_vcpu *vcpu) diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c index cc75feec05daa4..b8d745f6fd2219 100644 --- a/arch/x86/kvm/vmx/vmx.c +++ b/arch/x86/kvm/vmx/vmx.c @@ -3361,7 +3361,7 @@ void vmx_flush_tlb_current(struct kvm_vcpu *vcpu) vpid_sync_context(vmx_get_current_vpid(vcpu)); } -void vmx_flush_tlb_gva(struct kvm_vcpu *vcpu, gva_t addr) +void vmx_flush_tlb_gva(struct kvm_vcpu *vcpu, gva_t addr, bool *full) { /* * vpid_sync_vcpu_addr() is a nop if vpid==0, see the comment in diff --git a/arch/x86/kvm/vmx/x86_ops.h b/arch/x86/kvm/vmx/x86_ops.h index 4098580742462c..17595d52985cf1 100644 --- a/arch/x86/kvm/vmx/x86_ops.h +++ b/arch/x86/kvm/vmx/x86_ops.h @@ -82,7 +82,7 @@ void vmx_set_rflags(struct kvm_vcpu *vcpu, unsigned long rflags); bool vmx_get_if_flag(struct kvm_vcpu *vcpu); void vmx_flush_tlb_all(struct kvm_vcpu *vcpu); void vmx_flush_tlb_current(struct kvm_vcpu *vcpu); -void vmx_flush_tlb_gva(struct kvm_vcpu *vcpu, gva_t addr); +void vmx_flush_tlb_gva(struct kvm_vcpu *vcpu, gva_t addr, bool *full); void vmx_flush_tlb_guest(struct kvm_vcpu *vcpu); void vmx_set_interrupt_shadow(struct kvm_vcpu *vcpu, int mask); u32 vmx_get_interrupt_shadow(struct kvm_vcpu *vcpu); From 4f5f23846d6713c05481c8cfc710bb23817bf5e9 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Wed, 5 Aug 2026 23:28:35 +0200 Subject: [PATCH 331/612] rust: io: gate ioremap/iounmap on CONFIG_HAS_IOMEM s390 does not provide ioremap()/iounmap() when CONFIG_HAS_IOMEM is not set (which requires CONFIG_PCI on that architecture). This causes a build failure with Rust enabled on e.g. s390 allnoconfig: In file included from rust/helpers/helpers.c:68: rust/helpers/io.c:8:9: error: call to undeclared function 'ioremap'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration] 8 | return ioremap(offset, size); | ^ rust/helpers/io.c:19:2: error: call to undeclared function 'iounmap'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration] 19 | iounmap(addr); Guard the C helpers behind #ifdef CONFIG_HAS_IOMEM and cfg-gate the Rust io::mem module, such that IoMem, ExclusiveIoMem and IoRequest are not available without CONFIG_HAS_IOMEM. Note that the C API is inconsistent about this. For instance, devm_ioremap() has no stub and produces a link failure without CONFIG_HAS_IOMEM, whereas devm_platform_ioremap_resource() provides an inline stub returning -EINVAL. The approach taken here (compile-time gating) matches the former, which is slightly more appropriate since any driver performing MMIO currently requires CONFIG_HAS_IOMEM. Ideally, s390 should provide ioremap()/iounmap() stubs unconditionally (as UML already does), removing the need for any config gating as discussed in [1]; a follow-up patch for s390 is expected. Cc: Arnd Bergmann Reported-by: Miguel Ojeda Closes: https://lore.kernel.org/all/20260803180931.97202-1-ojeda@kernel.org [1] Fixes: 3f70ebe63858 ("s390: Enable Rust support") Reviewed-by: Arnd Bergmann Link: https://patch.msgid.link/20260805212920.1996937-1-dakr@kernel.org Signed-off-by: Danilo Krummrich --- rust/helpers/io.c | 2 ++ rust/kernel/io.rs | 1 + rust/kernel/platform.rs | 9 +++++---- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/rust/helpers/io.c b/rust/helpers/io.c index 397810864a2439..1edbc274951cbf 100644 --- a/rust/helpers/io.c +++ b/rust/helpers/io.c @@ -3,6 +3,7 @@ #include #include +#ifdef CONFIG_HAS_IOMEM __rust_helper void __iomem *rust_helper_ioremap(phys_addr_t offset, size_t size) { return ioremap(offset, size); @@ -18,6 +19,7 @@ __rust_helper void rust_helper_iounmap(void __iomem *addr) { iounmap(addr); } +#endif /* CONFIG_HAS_IOMEM */ __rust_helper u8 rust_helper_readb(const void __iomem *addr) { diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs index fcc7678fd9e3c1..d30bb5c6d4fcf9 100644 --- a/rust/kernel/io.rs +++ b/rust/kernel/io.rs @@ -9,6 +9,7 @@ use crate::{ prelude::*, // }; +#[cfg(CONFIG_HAS_IOMEM)] pub mod mem; pub mod poll; pub mod register; diff --git a/rust/kernel/platform.rs b/rust/kernel/platform.rs index 9b362e0495d323..d41555a4b31d23 100644 --- a/rust/kernel/platform.rs +++ b/rust/kernel/platform.rs @@ -17,10 +17,7 @@ use crate::{ from_result, to_result, // }, - io::{ - mem::IoRequest, - Resource, // - }, + io::Resource, irq::{ self, IrqRequest, // @@ -31,6 +28,9 @@ use crate::{ ThisModule, // }; +#[cfg(CONFIG_HAS_IOMEM)] +use crate::io::mem::IoRequest; + use core::{ marker::PhantomData, mem::offset_of, @@ -307,6 +307,7 @@ impl Device { } } +#[cfg(CONFIG_HAS_IOMEM)] impl Device { /// Returns an `IoRequest` for the resource at `index`, if any. pub fn io_request_by_index(&self, index: u32) -> Option> { From f88db65aece9f9d26287b6377b7c7730ecf9f11a Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Wed, 5 Aug 2026 23:28:36 +0200 Subject: [PATCH 332/612] rust: io: gate ioremap doctests on CONFIG_HAS_IOMEM The doc examples in io.rs and devres.rs directly call bindings::ioremap() and bindings::iounmap(), which do not exist when CONFIG_HAS_IOMEM is not set. This causes build failures with CONFIG_RUST_KERNEL_DOCTESTS=y on such configurations (e.g. s390 allnoconfig). Gate the affected doctests with `#![cfg(CONFIG_HAS_IOMEM)]` so they are skipped when IOMEM is unavailable. Fixes: 3f70ebe63858 ("s390: Enable Rust support") Reviewed-by: Arnd Bergmann Link: https://patch.msgid.link/20260805212920.1996937-2-dakr@kernel.org Signed-off-by: Danilo Krummrich --- rust/kernel/devres.rs | 1 + rust/kernel/io.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/rust/kernel/devres.rs b/rust/kernel/devres.rs index 11ce500e9b76f5..8ff8aedf251a86 100644 --- a/rust/kernel/devres.rs +++ b/rust/kernel/devres.rs @@ -59,6 +59,7 @@ struct Inner { /// # Examples /// /// ```no_run +/// # #![cfg(CONFIG_HAS_IOMEM)] /// use kernel::{ /// bindings, /// device::{ diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs index d30bb5c6d4fcf9..d4063ee4120002 100644 --- a/rust/kernel/io.rs +++ b/rust/kernel/io.rs @@ -81,6 +81,7 @@ impl MmioRaw { /// # Examples /// /// ```no_run +/// # #![cfg(CONFIG_HAS_IOMEM)] /// use kernel::{ /// bindings, /// ffi::c_void, From 1d78d33275ef2a16c6d080910b291d0a97a0e613 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 6 Aug 2026 16:44:17 +0200 Subject: [PATCH 333/612] KVM: SVM: Serialize accesses to the owner and mirror list with separate lock Interaction between KVM_CAP_VM_MOVE_ENC_CONTEXT_FROM and KVM_CAP_VM_COPY_ENC_CONTEXT_FROM can cause two separate issues: - in sev_migrate_from(), when the destination KVM is a mirror, the mirror entry is moved from the source's list to the owner's mirror_vms list, without holding the owner's lock unlike other writers of the owner's mirror list (sev_vm_copy_enc_context_from(), sev_vm_destroy()). A concurrent COPY or destroy can race with sev_migrate_from() and corrupt the list. - In sev_vm_destroy(), the *owner* is still active and could receive concurrently a KVM_CAP_VM_MOVE_ENC_CONTEXT_FROM that causes sev->enc_context_owner to change. In this case the incorrect VM receives kvm_put_kvm(). The second issue needs particular care because the owner could disappear altogether (even though the race window is impossibly small) between reading it and locking it. There is thus no way to perform the checks under the owner lock without putting struct kvm under SLAB_TYPESAFE_BY_RCU (which would allow kvm_get_kvm_safe() under RCU critical section). It is much simpler to just use a global lock, since the critical sections are so small and the new lock is always a leaf lock. Fixes: b2125513dfc0 ("KVM: SEV: Allow SEV intra-host migration of VM with mirrors") Cc: stable@vger.kernel.org Reported-by: Shen Yongchao Link: https://lore.kernel.org/kvm/tencent_625C0F42824E542C72B34733392AF2C49709@qq.com/ Link: https://lore.kernel.org/kvm/tencent_DDC4E4352EC91CAC05A9A8F4E55E8C96730A@qq.com/ Signed-off-by: Paolo Bonzini --- arch/x86/kvm/svm/sev.c | 34 +++++++++++++++++++++++++--------- arch/x86/kvm/svm/svm.h | 1 + 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c index 944aaea6501f2f..0f0ea7896af5bf 100644 --- a/arch/x86/kvm/svm/sev.c +++ b/arch/x86/kvm/svm/sev.c @@ -97,6 +97,8 @@ static u64 sev_supported_vmsa_features __ro_after_init; static u8 sev_enc_bit; static DECLARE_RWSEM(sev_deactivate_lock); static DEFINE_MUTEX(sev_bitmap_lock); +/* Protects kvm_sev_info's enc_context_owner, mirror_vms and mirror_entry. */ +static DEFINE_MUTEX(sev_mirror_lock); unsigned int max_sev_asid; static unsigned int min_sev_asid; static unsigned int max_sev_es_asid; @@ -2018,7 +2020,6 @@ static void sev_migrate_from(struct kvm *dst_kvm, struct kvm *src_kvm) dst->asid = src->asid; dst->handle = src->handle; dst->pages_locked = src->pages_locked; - dst->enc_context_owner = src->enc_context_owner; dst->es_active = src->es_active; dst->vmsa_features = src->vmsa_features; @@ -2026,11 +2027,12 @@ static void sev_migrate_from(struct kvm *dst_kvm, struct kvm *src_kvm) src->active = false; src->handle = 0; src->pages_locked = 0; - src->enc_context_owner = NULL; src->es_active = false; list_cut_before(&dst->regions_list, &src->regions_list, &src->regions_list); + mutex_lock(&sev_mirror_lock); + /* * If this VM has mirrors, "transfer" each mirror's refcount of the * source to the destination (this KVM). The caller holds a reference @@ -2047,12 +2049,15 @@ static void sev_migrate_from(struct kvm *dst_kvm, struct kvm *src_kvm) * If this VM is a mirror, remove the old mirror from the owners list * and add the new mirror to the list. */ - if (is_mirroring_enc_context(dst_kvm)) { - struct kvm_sev_info *owner_sev_info = to_kvm_sev_info(dst->enc_context_owner); + if (is_mirroring_enc_context(src_kvm)) { + struct kvm_sev_info *owner_sev_info = to_kvm_sev_info(src->enc_context_owner); + dst->enc_context_owner = src->enc_context_owner; + src->enc_context_owner = NULL; list_del(&src->mirror_entry); list_add_tail(&dst->mirror_entry, &owner_sev_info->mirror_vms); } + mutex_unlock(&sev_mirror_lock); kvm_for_each_vcpu(i, dst_vcpu, dst_kvm) { dst_svm = to_svm(dst_vcpu); @@ -2871,11 +2876,14 @@ int sev_vm_copy_enc_context_from(struct kvm *kvm, unsigned int source_fd) * disappear until we're done with it */ source_sev = to_kvm_sev_info(source_kvm); - kvm_get_kvm(source_kvm); - list_add_tail(&mirror_sev->mirror_entry, &source_sev->mirror_vms); /* Set enc_context_owner and copy its encryption context over */ + mutex_lock(&sev_mirror_lock); + kvm_get_kvm(source_kvm); + list_add_tail(&mirror_sev->mirror_entry, &source_sev->mirror_vms); mirror_sev->enc_context_owner = source_kvm; + mutex_unlock(&sev_mirror_lock); + mirror_sev->active = true; mirror_sev->asid = source_sev->asid; mirror_sev->fd = source_sev->fd; @@ -2963,11 +2971,19 @@ void sev_vm_destroy(struct kvm *kvm) * Note, mirror VMs don't support registering encrypted regions. */ if (is_mirroring_enc_context(kvm)) { - struct kvm *owner_kvm = sev->enc_context_owner; + struct kvm *owner_kvm; - mutex_lock(&owner_kvm->lock); + mutex_lock(&sev_mirror_lock); + owner_kvm = sev->enc_context_owner; list_del(&sev->mirror_entry); - mutex_unlock(&owner_kvm->lock); + sev->enc_context_owner = NULL; + + /* + * The reference to owner_kvm cannot move after sev_mirror_lock is + * released. Release it before kvm_put_kvm() so that owner_kvm is + * never destroyed inside sev_mirror_lock. + */ + mutex_unlock(&sev_mirror_lock); kvm_put_kvm(owner_kvm); return; } diff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h index 716be21fba3354..d63e5878988aad 100644 --- a/arch/x86/kvm/svm/svm.h +++ b/arch/x86/kvm/svm/svm.h @@ -109,6 +109,7 @@ struct kvm_sev_info { u64 ap_jump_table; /* SEV-ES AP Jump Table address */ u64 vmsa_features; u16 ghcb_version; /* Highest guest GHCB protocol version allowed */ + /* The three fields below are protected by sev_mirror_lock */ struct kvm *enc_context_owner; /* Owner of copied encryption context */ struct list_head mirror_vms; /* List of VMs mirroring */ struct list_head mirror_entry; /* Use as a list entry of mirrors */ From 7fc1c937b6b37c77df4ba374c37435ab06a2e945 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Patron?= Date: Tue, 4 Aug 2026 22:18:30 +0200 Subject: [PATCH 334/612] net: qrtr: ns: Raise lookup limit to 128 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Current limit of 64 is not enough for Sony Xperia 10 VII (SM6475). After merging v6.6.142 into a downstream AOSP device, it's stuck on boot animation and following log spam can be observed in dmesg: E qrtr : ctrl_cmd_new_lookup(): QRTR client node exceeds max lookup limit! E qrtr : qrtr_ns_worker(): failed while handling packet from 1:16600 No idea why it needs more than 64 client lookups, but it appears to work fine with 128 as it did when there were no limits. I don't really have a good way to investigate what it needs all these lookups for as most of the userspace is closed source. Fixes: 5640227d9a21 ("net: qrtr: ns: Limit the maximum number of lookups") Signed-off-by: Łukasz Patron Link: https://patch.msgid.link/20260804201919.1148015-1-priv.luk@gmail.com Signed-off-by: Jakub Kicinski --- net/qrtr/ns.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/qrtr/ns.c b/net/qrtr/ns.c index e5b2adb161d92d..c5e7e01db24984 100644 --- a/net/qrtr/ns.c +++ b/net/qrtr/ns.c @@ -78,7 +78,7 @@ struct qrtr_node { */ #define QRTR_NS_MAX_NODES 512 #define QRTR_NS_MAX_SERVERS 256 -#define QRTR_NS_MAX_LOOKUPS 64 +#define QRTR_NS_MAX_LOOKUPS 128 static u16 node_count; From 68bf02b6b4ad3f748c6db71fd77b6c0402d252f4 Mon Sep 17 00:00:00 2001 From: Fan XinRan Date: Mon, 3 Aug 2026 14:38:50 +0000 Subject: [PATCH 335/612] net: thunderbolt: Tear down DMA paths before stopping the rings tbnet_tear_down() stops both rings and frees their frame buffers before calling tb_xdomain_disable_paths(). tb_ring_stop() zeroes the ring's descriptor base and tbnet_free_buffers() unmaps and frees the pages the frames sit in, so by the time __tb_path_deactivate_hop() polls the hop's 'pending' bit, anything still in flight has nowhere to drain to. The teardown sequence has been in this order since the driver was added. The setup path has not: commit ff7cd07f3064 ("net: thunderbolt: Enable DMA paths only after rings are enabled") moved the path enable to the end of tbnet_connected_work() and documented why: /* Both logins successful so enable the rings, high-speed DMA * paths and start the network device queue. * * Note we enable the DMA paths last to make sure we have primed * the Rx ring before any incoming packets are allowed to * arrive. */ Teardown was never updated to match, so the rings and the paths now come down in the same order they go up instead of in reverse. On an ASMedia ASM4242 host router the 'pending' bit then never clears: every teardown burns the full 500 ms timeout and __tb_path_deactivate_hop() returns -ETIMEDOUT. Raising the timeout to 5 s does not help, so the hop is not slow to drain, it never drains at all. The failure is invisible above the thunderbolt core. __tb_path_deactivate_hops() is void and only calls tb_port_warn(); tb_path_deactivate(), tb_tunnel_deactivate() and __tb_disconnect_xdomain_paths() are void as well, and tb_disconnect_xdomain_paths() ends in an unconditional "return 0". So tb_xdomain_disable_paths() reports success and the netdev_warn() below it never fires. Repeated teardowns eventually take the XDomain control channel down, after which the peer node is gone and only a power cycle brings the controller back. Deactivating the paths first fixes it. Measured with kretprobes on a stock v6.17 tree with no other patches applied, on a link that was up and had just carried traffic: before: __tb_path_deactivate_hop() returns 0 for the first hop, then -ETIMEDOUT for the second 500335 us later after: 0 for both, 525 us apart Alternating the two orderings ABBA over three load levels, four teardowns per arm: every teardown failed before the change (21 of 21 that ran), none failed after (0 of 24). The before arms ran short because the link died partway through. The same split shows up when the interface is enslaved to a bond instead of just brought down, which is how I ran into this in the first place. Throughput and latency after the change are unchanged. Hosts whose routers drain the hop despite the stale descriptor base see no functional difference, since the paths end up deactivated either way. Fixes: e69b6c02b4c3 ("net: Add support for networking over Thunderbolt cable") Signed-off-by: Fan XinRan Acked-by: Mika Westerberg Link: https://patch.msgid.link/20260803-b4-tbnet-teardown-v2-1-27de6a13ca2d@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/thunderbolt/main.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/drivers/net/thunderbolt/main.c b/drivers/net/thunderbolt/main.c index c5670d61820c61..98893732bc6e46 100644 --- a/drivers/net/thunderbolt/main.c +++ b/drivers/net/thunderbolt/main.c @@ -386,11 +386,16 @@ static void tbnet_tear_down(struct tbnet *net, bool send_logout) break; } - tb_ring_stop(net->rx_ring.ring); - tb_ring_stop(net->tx_ring.ring); - tbnet_free_buffers(&net->rx_ring); - tbnet_free_buffers(&net->tx_ring); - + /* Tear the paths down before stopping the rings. This mirrors + * tbnet_connected_work(), which enables the paths last so the + * Rx ring is primed before packets can arrive. Stopping a + * ring zeroes its descriptor base and tbnet_free_buffers() + * unmaps and frees the frame buffers, leaving anything still + * in flight with nowhere to drain to; + * __tb_path_deactivate_hop() then waits for the hop's + * 'pending' bit, which on some host routers never clears in + * that state. + */ ret = tb_xdomain_disable_paths(net->xd, net->local_transmit_path, net->tx_ring.ring->hop, @@ -399,6 +404,11 @@ static void tbnet_tear_down(struct tbnet *net, bool send_logout) if (ret) netdev_warn(net->dev, "failed to disable DMA paths\n"); + tb_ring_stop(net->rx_ring.ring); + tb_ring_stop(net->tx_ring.ring); + tbnet_free_buffers(&net->rx_ring); + tbnet_free_buffers(&net->tx_ring); + tb_xdomain_release_in_hopid(net->xd, net->remote_transmit_path); net->remote_transmit_path = 0; } From fde39b8a521780391fb4e5bda2c0aa4928947f12 Mon Sep 17 00:00:00 2001 From: Doruk Tan Ozturk Date: Sun, 2 Aug 2026 14:06:02 +0200 Subject: [PATCH 336/612] net: usb: ipheth: fix carrier_work UAF on disconnect ipheth_sndbulk_callback() re-arms the carrier-check work on any non-zero URB status: else schedule_delayed_work(&dev->carrier_work, 0); Nothing ties that to the interface being up, so the work can be armed again after ipheth_close() has already drained it, and stay armed until the netdev whose private area embeds it is freed. On unplug with a TX URB in flight, ipheth_disconnect() drains the work through unregister_netdev() -> ipheth_close() -> cancel_delayed_work_sync() and only then calls ipheth_kill_urbs(). usb_kill_urb() completes the in-flight TX URB with -ENOENT, so ipheth_sndbulk_callback() runs after the drain and re-arms carrier_work. The same completion also re-arms the work if the interface is only brought down while a TX URB is in flight, and ipheth_carrier_check_work() then keeps re-queueing itself once a second. unregister_netdev() does not call ipheth_close() for an already-down interface, so nothing drains it on the later unplug either. In both cases free_netdev() frees the netdev while carrier_work is still pending, and ipheth_carrier_check_work() dereferences freed memory. Tie the work to the interface state instead of chasing the completion: disable it in ipheth_close() and enable it in ipheth_open(), so a schedule_delayed_work() from the URB completion is a no-op whenever the interface is not up. disable_delayed_work_sync() also waits for a running instance, so it fully replaces the cancel_delayed_work_sync() it takes the place of. The work starts out disabled in ipheth_probe() so the enable/disable counts balance from the first open. Reproduced under KASAN on linux-next (next-20260731) with dummy_hcd and raw-gadget standing in for the device, driving the second path above (the interface is already down, so unregister_netdev() does not call ipheth_close()): 15 of 15 unpatched boots report a slab-use-after-free in __run_timers(), freed by ipheth_disconnect() and re-armed from ipheth_sndbulk_callback() via queue_delayed_work_on(). The same trigger on a kernel differing only by this patch reports 0 of 15, and the carrier check still functions across open/close cycles. The reproducer needs an attached USB device that stops draining bulk OUT, plus a link down and unplug, driven as root. It is not a privilege boundary crossing and no exploit primitive was developed. Found by 0sec (https://0sec.ai). Fixes: bb1b40c7cb86 ("usbnet: ipheth: prevent TX queue timeouts when device not ready") Cc: stable@vger.kernel.org Signed-off-by: Doruk Tan Ozturk Link: https://patch.msgid.link/20260802120602.42595-1-doruk@0sec.ai Signed-off-by: Jakub Kicinski --- drivers/net/usb/ipheth.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/net/usb/ipheth.c b/drivers/net/usb/ipheth.c index bb1364f85bd1fc..2b490114d23271 100644 --- a/drivers/net/usb/ipheth.c +++ b/drivers/net/usb/ipheth.c @@ -490,6 +490,7 @@ static int ipheth_open(struct net_device *net) if (retval) return retval; + enable_delayed_work(&dev->carrier_work); schedule_delayed_work(&dev->carrier_work, IPHETH_CARRIER_CHECK_TIMEOUT); return retval; } @@ -499,7 +500,11 @@ static int ipheth_close(struct net_device *net) struct ipheth_device *dev = netdev_priv(net); netif_stop_queue(net); - cancel_delayed_work_sync(&dev->carrier_work); + /* A TX URB can still complete with an error after this point and + * try to re-arm the carrier work. Disable it instead of cancelling + * it, so that such a schedule_delayed_work() is a no-op. + */ + disable_delayed_work_sync(&dev->carrier_work); return 0; } @@ -629,6 +634,10 @@ static int ipheth_probe(struct usb_interface *intf, } INIT_DELAYED_WORK(&dev->carrier_work, ipheth_carrier_check_work); + /* Armed only between ipheth_open() and ipheth_close(). Start out + * disabled so the enable/disable counts balance from the first open. + */ + disable_delayed_work(&dev->carrier_work); retval = ipheth_alloc_urbs(dev); if (retval) { From 35772b4981f38ba8059372cde8753e8e477e98ec Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Mon, 3 Aug 2026 18:16:33 +0200 Subject: [PATCH 337/612] mptcp: options: reset DSS fields in case of unexpected size A remote peer could send a malformed DSS with a wrong size, followed by another DSS or MPC + Data. In this case, the first suboption will be ignored, but leaving some fields written, which could lead to inconsistency or access uninitialized data. Explicitly reset the fields that could have been modified in case of unexpected size. Link: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260728-net-mptcp-misc-fixes-7-2-rc6-v1-0-f7e2d229159d%40kernel.org?part=1 Fixes: 648ef4b88673 ("mptcp: Implement MPTCP receive path") Cc: stable@vger.kernel.org Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260803-net-mptcp-misc-fixes-7-2-rc6-v2-1-b8f496d71664@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/options.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/net/mptcp/options.c b/net/mptcp/options.c index c664023d37ba8e..038eca33c6b1ec 100644 --- a/net/mptcp/options.c +++ b/net/mptcp/options.c @@ -188,8 +188,14 @@ static void mptcp_parse_option(const struct sk_buff *skb, * RFC 8684 Section 3.3.0 checks later in subflow_data_ready */ if (opsize != expected_opsize && - opsize != expected_opsize + TCPOLEN_MPTCP_DSS_CHECKSUM) + opsize != expected_opsize + TCPOLEN_MPTCP_DSS_CHECKSUM) { + mp_opt->dsn64 = 0; + mp_opt->use_map = 0; + mp_opt->ack64 = 0; + mp_opt->use_ack = 0; + mp_opt->data_fin = 0; break; + } mp_opt->suboptions |= OPTION_MPTCP_DSS; if (mp_opt->use_ack) { From b6ee361524641f57b2e2363f7737f20e17f67827 Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Mon, 3 Aug 2026 18:16:34 +0200 Subject: [PATCH 338/612] mptcp: avoid combining some incoming suboptions Some MPTCP suboptions are mutually exclusive according to the RFC8684, but also because in different places, the code doesn't expect some combinations to be present. That's specially true for suboptions that would be present twice, but with different attributes. The new restrictions are the same as the ones applied on the output side, with mptcp_write_options. The same rules can be reused with a small fix: an MP_FASTCLOSE can be used with a DSS when the sender picks this option [1], which is not the case on Linux. Here are the rules: Which options can be used together? X: mutually exclusive O: often used together C: can be used together in some cases P: could be used together but we prefer not to (optimisations) | Opt: | MPC | MPJ | DSS | ADD | RM | PRIO | FAIL | FC | |------|------|------|------|------|------|------|------|------| | MPC |------|------|------|------|------|------|------|------| | MPJ | X |------|------|------|------|------|------|------| | DSS | X | X |------|------|------|------|------|------| | ADD | X | X | P |------|------|------|------|------| | RM | C | C | C | P |------|------|------|------| | PRIO | X | C | C | C | C |------|------|------| | FAIL | X | X | C | X | X | X |------|------| | FC | X | X | P | X | X | X | X |------| | RST | X | X | X | X | X | X | O | O | |------|------|------|------|------|------|------|------|------| The only difference is with the 'P': another stack could send and ADD_ADDR with other suboptions (DSS, RM_ADDR), and this should be allowed. A few points of attention: - In theory, an MP_CAPABLE could be used with a RM_ADDR, but there is no reason to add it with a SYN. Note that even with a 4th ACK, it doesn't seem to be useful, except when IDs are known in advance via another channel. Better not to break that. - Now, combining both an MP_CAPABLE and an MP_JOIN will no longer result to a reject of the two options, but only the second suboption is ignored. That seems OK to do that for this unexpected error. At least now all inconsistent combinations are handled the same way. This could change later in next. This also means the explicit checks for having both MPC + MPJ in subflow.c will now be unreachable. That's fine, they will be removed in a follow-up patch. - In case of conflicting combinations, the extra suboption(s) is/are ignored: having such combinations either means the remote peer is buggy, or is evil. The simplest action is then taken in this case: stop processing the current suboption. - In mp_opt->suboptions, there is also a bit reserved to the checksum, which can be used in an MP_CAPABLE and a DSS. Each time a DSS option can be used in parallel with another option, the checksum can be set, so the verification is combined into a new OPTIONS_MPTCP_DSS macro. - An MP_CAPABLE ACK can carry a Data-Level Length, and an optional Checksum: they are the same as the ones found in a DSS, because a DSS cannot be used in parallel to an MP_CAPABLE. Similarly, even if there is room, a DSS cannot be used with an MP_JOIN. Fixes: eda7acddf808 ("mptcp: Handle MPTCP TCP options") Cc: stable@vger.kernel.org Link: https://www.rfc-editor.org/rfc/rfc8684.html#section-3.5-5.1 [1] Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260803-net-mptcp-misc-fixes-7-2-rc6-v2-2-b8f496d71664@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/options.c | 59 +++++++++++++++++++++++++++++++++++++++++++- net/mptcp/protocol.h | 1 + 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/net/mptcp/options.c b/net/mptcp/options.c index 038eca33c6b1ec..1057d500577b04 100644 --- a/net/mptcp/options.c +++ b/net/mptcp/options.c @@ -50,6 +50,14 @@ static void mptcp_parse_option(const struct sk_buff *skb, } } + /* Only the MPC + ACK can be used with a RM_ADDR */ + if (subopt == OPTION_MPTCP_MPC_ACK) { + if ((mp_opt->suboptions & ~OPTION_MPTCP_RM_ADDR) != 0) + break; + } else if (mp_opt->suboptions != 0) { + break; + } + /* Cfr RFC 8684 Section 3.3.0: * If a checksum is present but its use had * not been negotiated in the MP_CAPABLE handshake, the receiver MUST @@ -122,6 +130,11 @@ static void mptcp_parse_option(const struct sk_buff *skb, break; case MPTCPOPT_MP_JOIN: + /* Can be used with a restricted number of other options */ + if ((mp_opt->suboptions & ~(OPTION_MPTCP_RM_ADDR | + OPTION_MPTCP_PRIO)) != 0) + break; + if (opsize == TCPOLEN_MPTCP_MPJ_SYN) { mp_opt->suboptions |= OPTION_MPTCP_MPJ_SYN; mp_opt->backup = *ptr++ & MPTCPOPT_BACKUP; @@ -153,6 +166,14 @@ static void mptcp_parse_option(const struct sk_buff *skb, break; case MPTCPOPT_DSS: + /* Can be used with a restricted number of other options */ + if ((mp_opt->suboptions & ~(OPTION_MPTCP_ADD_ADDR | + OPTION_MPTCP_RM_ADDR | + OPTION_MPTCP_PRIO | + OPTION_MPTCP_FASTCLOSE | + OPTION_MPTCP_FAIL)) != 0) + break; + pr_debug("DSS\n"); ptr++; @@ -240,6 +261,12 @@ static void mptcp_parse_option(const struct sk_buff *skb, break; case MPTCPOPT_ADD_ADDR: + /* Can be used with a restricted number of other options */ + if ((mp_opt->suboptions & ~(OPTIONS_MPTCP_DSS | + OPTION_MPTCP_RM_ADDR | + OPTION_MPTCP_PRIO)) != 0) + break; + mp_opt->echo = (*ptr++) & MPTCP_ADDR_ECHO; if (!mp_opt->echo) { if (opsize == TCPOLEN_MPTCP_ADD_ADDR || @@ -299,6 +326,14 @@ static void mptcp_parse_option(const struct sk_buff *skb, break; case MPTCPOPT_RM_ADDR: + /* Can be used with a restricted number of other options */ + if ((mp_opt->suboptions & ~(OPTION_MPTCP_MPC_ACK | + OPTIONS_MPTCP_MPJ | + OPTIONS_MPTCP_DSS | + OPTION_MPTCP_ADD_ADDR | + OPTION_MPTCP_PRIO)) != 0) + break; + if (opsize < TCPOLEN_MPTCP_RM_ADDR_BASE + 1 || opsize > TCPOLEN_MPTCP_RM_ADDR_BASE + MPTCP_RM_IDS_MAX) break; @@ -313,6 +348,13 @@ static void mptcp_parse_option(const struct sk_buff *skb, break; case MPTCPOPT_MP_PRIO: + /* Can be used with a restricted number of other options */ + if ((mp_opt->suboptions & ~(OPTIONS_MPTCP_MPJ | + OPTIONS_MPTCP_DSS | + OPTION_MPTCP_ADD_ADDR | + OPTION_MPTCP_RM_ADDR)) != 0) + break; + if (opsize != TCPOLEN_MPTCP_PRIO) break; @@ -322,6 +364,11 @@ static void mptcp_parse_option(const struct sk_buff *skb, break; case MPTCPOPT_MP_FASTCLOSE: + /* Can be used with a restricted number of other options */ + if ((mp_opt->suboptions & ~(OPTIONS_MPTCP_DSS | + OPTION_MPTCP_RST)) != 0) + break; + if (opsize != TCPOLEN_MPTCP_FASTCLOSE) break; @@ -333,6 +380,11 @@ static void mptcp_parse_option(const struct sk_buff *skb, break; case MPTCPOPT_RST: + /* Can be used with a restricted number of other options */ + if ((mp_opt->suboptions & ~(OPTION_MPTCP_FAIL | + OPTION_MPTCP_FASTCLOSE)) != 0) + break; + if (opsize != TCPOLEN_MPTCP_RST) break; @@ -348,6 +400,11 @@ static void mptcp_parse_option(const struct sk_buff *skb, break; case MPTCPOPT_MP_FAIL: + /* Can be used with a restricted number of other options */ + if ((mp_opt->suboptions & ~(OPTIONS_MPTCP_DSS | + OPTION_MPTCP_RST)) != 0) + break; + if (opsize != TCPOLEN_MPTCP_FAIL) break; @@ -1406,7 +1463,7 @@ void mptcp_write_options(struct tcphdr *th, __be32 *ptr, struct tcp_sock *tp, * RM | C | C | C | P |------|------|------|------| * PRIO | X | C | C | C | C |------|------|------| * FAIL | X | X | C | X | X | X |------|------| - * FC | X | X | X | X | X | X | X |------| + * FC | X | X | P | X | X | X | X |------| * RST | X | X | X | X | X | X | O | O | * ------|------|------|------|------|------|------|------|------| * diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h index 4a2d40cd7b1325..c13680d189942d 100644 --- a/net/mptcp/protocol.h +++ b/net/mptcp/protocol.h @@ -37,6 +37,7 @@ OPTION_MPTCP_MPC_ACK) #define OPTIONS_MPTCP_MPJ (OPTION_MPTCP_MPJ_SYN | OPTION_MPTCP_MPJ_SYNACK | \ OPTION_MPTCP_MPJ_ACK) +#define OPTIONS_MPTCP_DSS (OPTION_MPTCP_DSS | OPTION_MPTCP_CSUMREQD) /* MPTCP option subtypes */ #define MPTCPOPT_MP_CAPABLE 0 From 900e6d80548e69be7ffd358122790fbaa458a9b4 Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Mon, 3 Aug 2026 18:16:35 +0200 Subject: [PATCH 339/612] mptcp: remove MPC && MPJ check After the parent commit ("mptcp: avoid combining some incoming suboptions"), the parsing step no longer allow to have both the MP_CAPABLE and MP_JOIN suboptions set together. These chunks are now unreachable, these checks can then be removed. Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260803-net-mptcp-misc-fixes-7-2-rc6-v2-3-b8f496d71664@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/subflow.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/net/mptcp/subflow.c b/net/mptcp/subflow.c index 8e386899ceb980..e1f20ff8fdb424 100644 --- a/net/mptcp/subflow.c +++ b/net/mptcp/subflow.c @@ -174,8 +174,6 @@ static int subflow_check_req(struct request_sock *req, if (unlikely(listener->pm_listener)) return subflow_reset_req_endp(req, skb); - if (opt_mp_join) - return 0; } else if (opt_mp_join) { SUBFLOW_REQ_INC_STATS(req, MPTCP_MIB_JOINSYNRX); @@ -277,9 +275,6 @@ int mptcp_subflow_init_cookie_req(struct request_sock *req, opt_mp_capable = !!(mp_opt.suboptions & OPTION_MPTCP_MPC_ACK); opt_mp_join = !!(mp_opt.suboptions & OPTION_MPTCP_MPJ_ACK); - if (opt_mp_capable && opt_mp_join) - return -EINVAL; - if (opt_mp_capable && listener->request_mptcp) { if (mp_opt.sndr_key == 0) return -EINVAL; From a7aad5b69d3bdaec20a3ed9284e184502450c0cd Mon Sep 17 00:00:00 2001 From: Qing Luo Date: Mon, 3 Aug 2026 18:16:36 +0200 Subject: [PATCH 340/612] mptcp: pm: fix data race in add_addr timer callback The timer callback reads entry->retrans_times outside pm.lock to decide whether to call mptcp_pm_subflow_established(). Since mptcp_pm_announced_del_timer() can concurrently set retrans_times = ADD_ADDR_RETRANS_MAX under pm.lock, a race condition exists. I discovered this issue while studying the code. AI tools helped me to verify the issue can potentially happen under race conditions. Use a local 'retransmit' flag set inside pm.lock to capture whether retransmission is still possible when the lock is taken. This allows to call mptcp_pm_subflow_established() accordingly, and not depending on the situation that can be different when checked outside the pm.lock. Fixes: 348d5c1dec60 ("mptcp: move to next addr when timeout") Cc: stable@vger.kernel.org Signed-off-by: Qing Luo Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260803-net-mptcp-misc-fixes-7-2-rc6-v2-4-b8f496d71664@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/pm.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/net/mptcp/pm.c b/net/mptcp/pm.c index 6afd39aea110a2..c71dcf887683c9 100644 --- a/net/mptcp/pm.c +++ b/net/mptcp/pm.c @@ -380,6 +380,7 @@ static void mptcp_pm_add_addr_timer(struct timer_list *timer) struct mptcp_sock *msk = entry->sock; struct sock *sk = (struct sock *)msk; unsigned int timeout = 0; + bool retransmit; pr_debug("msk=%p\n", msk); @@ -412,14 +413,15 @@ static void mptcp_pm_add_addr_timer(struct timer_list *timer) entry->retrans_times++; } - if (entry->retrans_times < ADD_ADDR_RETRANS_MAX) + retransmit = entry->retrans_times < ADD_ADDR_RETRANS_MAX; + if (retransmit) timeout <<= entry->retrans_times; else timeout = 0; spin_unlock_bh(&msk->pm.lock); - if (entry->retrans_times == ADD_ADDR_RETRANS_MAX) + if (!retransmit) mptcp_pm_subflow_established(msk); out: From ca318e7bbb7723f57bcd9e69a2873b5884435552 Mon Sep 17 00:00:00 2001 From: Gang Yan Date: Mon, 3 Aug 2026 18:16:37 +0200 Subject: [PATCH 341/612] selftests: mptcp: join: mark tests with data corruption as failed check_transfer() compares the input and output files byte-by-byte using `cmp -l "$in" "$out" | while read ...`. Because the while-loop body runs in a subshell (the script sets neither lastpipe nor pipefail), the fail_test call inside it -- which sets the global ret/last_test_failed -- and the `return 1` both act on the subshell, not on check_transfer(). check_transfer() thus always falls through to `return 0`, and any data corruption affecting only the payload (leaving the subflow/PM counters untouched) is silently reported as PASS. Fixes: 8117dac3e7c3 ("selftests: mptcp: add invert check in check_transfer") Cc: stable@vger.kernel.org Signed-off-by: Gang Yan Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260803-net-mptcp-misc-fixes-7-2-rc6-v2-5-b8f496d71664@kernel.org Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/mptcp/mptcp_join.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/net/mptcp/mptcp_join.sh b/tools/testing/selftests/net/mptcp/mptcp_join.sh index c0aeffd5cb714a..7dc91fac4917ee 100755 --- a/tools/testing/selftests/net/mptcp/mptcp_join.sh +++ b/tools/testing/selftests/net/mptcp/mptcp_join.sh @@ -584,7 +584,7 @@ check_transfer() mv "$tmpfile" "$out" tmpfile="" fi - cmp -l "$in" "$out" | while read -r i a b; do + while read -r i a b; do local sum=$((0${a} + 0${b})) if [ $check_invert -eq 0 ] || [ $sum -ne $((0xff)) ]; then fail_test "$what does not match (in, out):" @@ -595,7 +595,7 @@ check_transfer() else print_info "$what has inverted byte at ${i}" fi - done + done < <(cmp -l "$in" "$out") return 0 } From efc33b5102ff859bacd390a5f30112d8e0c084c0 Mon Sep 17 00:00:00 2001 From: Shardul Bankar Date: Mon, 3 Aug 2026 18:16:38 +0200 Subject: [PATCH 342/612] mptcp: pm: fix memory leak from alloc-during-teardown race mptcp_pm_destroy() empties msk->pm.anno_list and msk->pm.userspace_pm_local_addr_list under msk->pm.lock during socket teardown, dropping the lock between the two. A concurrent userspace PM genl ANNOUNCE on the same msk holds a sock reference via mptcp_token_get_sock() and, in mptcp_pm_nl_announce_doit(), calls mptcp_userspace_pm_append_new_local_addr() and mptcp_pm_announced_alloc(). Both take msk->pm.lock briefly to add to their respective lists. Because the genl handler holds a sock reference, mptcp_pm_destroy() may run on the same msk via mptcp_disconnect(), which invokes mptcp_destroy_common() without dropping the sock refcount, before the handler completes. If the lock acquisitions interleave such that mptcp_pm_destroy() empties a list first, the later alloc adds its entry to a list head that nothing else iterates for this msk, and the entry leaks. kmemleak reports both mptcp_pm_add_addr objects (from mptcp_pm_announced_alloc()) and mptcp_pm_addr_entry objects (from mptcp_userspace_pm_append_new_local_addr()) under sustained concurrent ANNOUNCE + close load against the userspace PM. Add an MPTCP_PM_DESTROYING bit in msk->pm.status, set by mptcp_pm_destroy() under pm.lock before the lists are emptied and checked under pm.lock by the alloc paths. Either the alloc takes pm.lock first, in which case its entry is on the list when mptcp_pm_destroy() frees it; or mptcp_pm_destroy() takes pm.lock first, in which case the later alloc observes the bit and refuses. Found by an MPTCP protocol-flow harness extending BRF (arXiv:2305.08782). Fixes: 9ab4807c84a4 ("mptcp: netlink: Add MPTCP_PM_CMD_ANNOUNCE") Cc: stable@vger.kernel.org Signed-off-by: Shardul Bankar Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260803-net-mptcp-misc-fixes-7-2-rc6-v2-6-b8f496d71664@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/pm.c | 13 +++++++++++-- net/mptcp/pm_userspace.c | 4 ++++ net/mptcp/protocol.h | 7 ++++--- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/net/mptcp/pm.c b/net/mptcp/pm.c index c71dcf887683c9..64a1236aabee96 100644 --- a/net/mptcp/pm.c +++ b/net/mptcp/pm.c @@ -443,6 +443,9 @@ bool mptcp_pm_announced_alloc(struct mptcp_sock *msk, lockdep_assert_held(&msk->pm.lock); + if (msk->pm.status & BIT(MPTCP_PM_DESTROYING)) + return false; + add_entry = mptcp_pm_announced_lookup(msk, addr); if (add_entry) { if (WARN_ON_ONCE(mptcp_pm_is_kernel(msk))) @@ -1145,10 +1148,16 @@ void mptcp_pm_worker(struct mptcp_sock *msk) void mptcp_pm_destroy(struct mptcp_sock *msk) { + spin_lock_bh(&msk->pm.lock); + msk->pm.status |= BIT(MPTCP_PM_DESTROYING); + spin_unlock_bh(&msk->pm.lock); + mptcp_pm_free_announced_list(msk); - if (mptcp_pm_is_userspace(msk)) - mptcp_userspace_pm_free_local_addr_list(msk); + /* Free the userspace local address list unconditionally: the socket + * can be reused (mptcp_disconnect()) and re-selected to a different PM + */ + mptcp_userspace_pm_free_local_addr_list(msk); } void mptcp_pm_data_reset(struct mptcp_sock *msk) diff --git a/net/mptcp/pm_userspace.c b/net/mptcp/pm_userspace.c index 945aa5afc2ddfd..2203cc2d27485f 100644 --- a/net/mptcp/pm_userspace.c +++ b/net/mptcp/pm_userspace.c @@ -54,6 +54,10 @@ static int mptcp_userspace_pm_append_new_local_addr(struct mptcp_sock *msk, bitmap_zero(id_bitmap, MPTCP_PM_MAX_ADDR_ID + 1); spin_lock_bh(&msk->pm.lock); + if (msk->pm.status & BIT(MPTCP_PM_DESTROYING)) { + ret = -EINVAL; + goto append_err; + } mptcp_for_each_userspace_pm_addr(msk, e) { addr_match = mptcp_addresses_equal(&e->addr, &entry->addr, true); if (addr_match && entry->addr.id == 0 && needs_id) diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h index c13680d189942d..1b80f2d6ec5a23 100644 --- a/net/mptcp/protocol.h +++ b/net/mptcp/protocol.h @@ -190,9 +190,10 @@ enum mptcp_pm_status { MPTCP_PM_ESTABLISHED, MPTCP_PM_SUBFLOW_ESTABLISHED, MPTCP_PM_ALREADY_ESTABLISHED, /* persistent status, set after ESTABLISHED event */ - MPTCP_PM_MPC_ENDPOINT_ACCOUNTED /* persistent status, set after MPC local address is - * accounted int id_avail_bitmap - */ + MPTCP_PM_MPC_ENDPOINT_ACCOUNTED, /* persistent status, set after MPC local address is + * accounted int id_avail_bitmap + */ + MPTCP_PM_DESTROYING, /* To fence out PM list allocs */ }; enum mptcp_pm_type { From e00b63056fb4f261455b3e5df5268a1f8ce47a87 Mon Sep 17 00:00:00 2001 From: Wyatt Feng Date: Mon, 3 Aug 2026 18:16:39 +0200 Subject: [PATCH 343/612] mptcp: fastopen: only mark MPTFO subflows with SYN data Passive TCP Fast Open accepts a valid-cookie SYN even when it carries no data. In that case the child socket's receive queue is intentionally left empty. mptcp_fastopen_subflow_synack_set_params() set is_mptfo before checking for queued SYN data. That made data-less TFO SYNs hit a WARN and, if the warning was non-fatal, left stale MPTFO state behind. The stale flag could later trigger a state-confusion bug in check_fully_established(). Only mark the subflow as MPTFO after confirming that an SKB was queued. Return quietly when the receive queue is empty. Note that mptcp_subflow_context's is_mptfo field is now not just about subflows where the TFO was present, but about MPTFO subflow that consumed SYN data. Only having a valid cookie but not carrying data is not really "doing TFO". Fixes: 36b122baf6a8 ("mptcp: add subflow_v(4,6)_send_synack()") Cc: stable@vger.kernel.org Reported-by: Yuan Tan Reported-by: Yifan Wu Reported-by: Juefei Pu Reported-by: Zhengchuan Liang Reported-by: Xin Liu Signed-off-by: Wyatt Feng Signed-off-by: Ren Wei Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260803-net-mptcp-misc-fixes-7-2-rc6-v2-7-b8f496d71664@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/fastopen.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/net/mptcp/fastopen.c b/net/mptcp/fastopen.c index 082c46c0f50ee7..f717750906ffaa 100644 --- a/net/mptcp/fastopen.c +++ b/net/mptcp/fastopen.c @@ -24,12 +24,13 @@ void mptcp_fastopen_subflow_synack_set_params(struct mptcp_subflow_context *subf sk = subflow->conn; tp = tcp_sk(ssk); - subflow->is_mptfo = 1; - + /* A valid TFO cookie does not guarantee SYN data. */ skb = skb_peek(&ssk->sk_receive_queue); - if (WARN_ON_ONCE(!skb)) + if (!skb) return; + subflow->is_mptfo = 1; + /* dequeue the skb from sk receive queue */ __skb_unlink(skb, &ssk->sk_receive_queue); skb_ext_reset(skb); From 41b49a8b914ec7dcb03eae93fb27f3c464078644 Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Mon, 3 Aug 2026 18:16:40 +0200 Subject: [PATCH 344/612] mptcp: reclaim forward-allocated memory on RX path errors After commit 9db5b3cec4ec ("mptcp: borrow forward memory from subflow"), errors in the receive path prior to queueing skbs into the receive queue do not trigger forward-allocated memory reclaiming. Prevent forward memory from growing unboundedly in pathological drop scenarios by explicitly reclaiming memory when skbs are dropped. Fixes: 9db5b3cec4ec ("mptcp: borrow forward memory from subflow") Cc: stable@vger.kernel.org Signed-off-by: Paolo Abeni Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260803-net-mptcp-misc-fixes-7-2-rc6-v2-8-b8f496d71664@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/protocol.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index ca644ec53eedb1..7c8180d8d5eff6 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -149,6 +149,12 @@ struct sock *__mptcp_nmpc_sk(struct mptcp_sock *msk) static void mptcp_drop(struct sock *sk, struct sk_buff *skb) { + /* The skb forward memory was already transferred to sk by + * mptcp_borrow_fwdmem(), even before setting the destructor. + */ + if (!skb->destructor) + sk_mem_reclaim(sk); + sk_drops_skbadd(sk, skb); __kfree_skb(skb); } From e48e8edbef2eb824201495daa5234560f632b23c Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Mon, 3 Aug 2026 20:15:32 +0800 Subject: [PATCH 345/612] xdp: reject clones that overrun skb_shared_info tailroom xdpf_clone() clones broadcast copies into a single page and sets frame_sz to PAGE_SIZE. __xdp_build_skb_from_frame() later treats that page like a normal XDP frame and expects the usual skb_shared_info tailroom at the end of the buffer. The current check only rejects frames whose linear xdp_frame header, headroom, and packet data exceed PAGE_SIZE. A source frame backed by a larger allocation can still satisfy that check while extending into the clone's required shared-info area. When such a clone is converted back into an skb, build_skb_around() places skb_shared_info over live packet bytes and later writes can corrupt XDP return metadata. Reject clones unless their linear area fits inside SKB_WITH_OVERHEAD(PAGE_SIZE), matching the tailroom requirement already enforced by the XDP-to-skb conversion path. Fixes: e624d4ed4aa8 ("xdp: Extend xdp_redirect_map with broadcast support") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Zhiling Zou Link: https://patch.msgid.link/6b2afef5d1738763c6965e8e466eb16e43e4f956.1785757386.git.zhilinz@nebusec.ai Signed-off-by: Jakub Kicinski --- net/core/xdp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/core/xdp.c b/net/core/xdp.c index 9890a30584ba7b..0194e69da339a5 100644 --- a/net/core/xdp.c +++ b/net/core/xdp.c @@ -871,7 +871,7 @@ struct xdp_frame *xdpf_clone(struct xdp_frame *xdpf) headroom = xdpf->headroom + sizeof(*xdpf); totalsize = headroom + xdpf->len; - if (unlikely(totalsize > PAGE_SIZE)) + if (unlikely(totalsize > SKB_WITH_OVERHEAD(PAGE_SIZE))) return NULL; page = dev_alloc_page(); if (!page) From 7700a31039cdc6715cb6cce7e7a664ee4e945f67 Mon Sep 17 00:00:00 2001 From: Hongyan Xu Date: Thu, 6 Aug 2026 14:06:28 +0800 Subject: [PATCH 346/612] ata: pata_sl82c105: fix bridge revision use-after-free pci_get_slot() returns a referenced PCI device. Commit 44c10138fd4b ("PCI: Change all drivers to use pci_device->revision") replaced a configuration-space read with direct access to the cached revision field, but left that access after pci_dev_put(). The bridge may therefore be freed before its revision is read. Read the revision before dropping the reference. Fixes: 44c10138fd4b ("PCI: Change all drivers to use pci_device->revision") Signed-off-by: Hongyan Xu Reviewed-by: Niklas Cassel Signed-off-by: Damien Le Moal --- drivers/ata/pata_sl82c105.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/ata/pata_sl82c105.c b/drivers/ata/pata_sl82c105.c index 2d24c6b3e9d9fb..25bce3fe2a101d 100644 --- a/drivers/ata/pata_sl82c105.c +++ b/drivers/ata/pata_sl82c105.c @@ -264,6 +264,7 @@ static struct ata_port_operations sl82c105_port_ops = { static int sl82c105_bridge_revision(struct pci_dev *pdev) { struct pci_dev *bridge; + u8 revision; /* * The bridge should be part of the same device, but function 0. @@ -285,8 +286,9 @@ static int sl82c105_bridge_revision(struct pci_dev *pdev) /* * We need to find function 0's revision, not function 1 */ + revision = bridge->revision; pci_dev_put(bridge); - return bridge->revision; + return revision; } static void sl82c105_fixup(struct pci_dev *pdev) From 7bca91d63341274e857f4aeaad54d229405e93dc Mon Sep 17 00:00:00 2001 From: chanyoung Date: Tue, 4 Aug 2026 14:28:35 +0900 Subject: [PATCH 347/612] tls: don't leave a full plaintext sk_msg ring unpushed When the copy path in tls_sw_sendmsg_locked() adds the fragment that fills the plaintext sk_msg ring, it does not set full_record, so the record is left full and unpushed. A later splice() then adds to an already full ring: sk_msg_page_add() has no fullness check of its own, so sg.end wraps onto sg.start and the ring appears empty. Fragments added after that overwrite live entries, and sg.size no longer matches what is reachable between sg.start and sg.end, so pushing the record runs the scatterwalk off the end of the scatterlist. An unprivileged user can trigger this on a loopback TCP socket with the "tls" ULP attached: BUG: kernel NULL pointer dereference, address: 0000000000000008 RIP: 0010:memcpy_from_scatterwalk+0x32/0xc0 Call Trace: skcipher_walk_next+0x1d1/0x2c0 gcm_encrypt_aesni_avx+0x1e9/0x220 bpf_exec_tx_verdict+0x3bb/0x860 tls_sw_sendmsg+0xa1a/0xca0 __sys_sendto+0x1da/0x1f0 Set full_record in the copy path when the ring becomes full, and push a record that is already full on entry to the sendmsg loop. Suggested-by: Sabrina Dubroca Fixes: fe1e81d4f73b ("tls/sw: Support MSG_SPLICE_PAGES") Cc: stable@vger.kernel.org Signed-off-by: chanyoung Link: https://patch.msgid.link/20260804052837.49015-2-ppoo1220@gmail.com Signed-off-by: Jakub Kicinski --- net/tls/tls_sw.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/net/tls/tls_sw.c b/net/tls/tls_sw.c index d4afc90fd7966e..d2e399be8ef6e0 100644 --- a/net/tls/tls_sw.c +++ b/net/tls/tls_sw.c @@ -832,6 +832,14 @@ static int tls_sw_sendmsg_locked(struct sock *sk, struct msghdr *msg, if (!sk_stream_memory_free(sk)) goto wait_for_sndbuf; + /* open record may be full if we couldn't push it in the last sendmsg call */ + if (sk_msg_full(msg_pl)) { + full_record = true; + sk_msg_trim(sk, msg_en, + msg_pl->sg.size + prot->overhead_size); + goto copied; + } + alloc_encrypted: ret = tls_alloc_encrypted_msg(sk, required_size); if (ret) { @@ -921,6 +929,12 @@ static int tls_sw_sendmsg_locked(struct sock *sk, struct msghdr *msg, msg_pl, try_to_copy); if (ret < 0) goto trim_sgl; + + if (sk_msg_full(msg_pl)) { + full_record = true; + sk_msg_trim(sk, msg_en, + msg_pl->sg.size + prot->overhead_size); + } } /* Open records defined only if successfully copied, otherwise From 3834e079d67feda5fd57d1ea8d4185d316997335 Mon Sep 17 00:00:00 2001 From: chanyoung Date: Tue, 4 Aug 2026 14:28:36 +0900 Subject: [PATCH 348/612] selftests: tls: add a test for splicing onto a full plaintext record Splicing onto a plaintext sk_msg ring that is already full used to wrap the ring and make the kernel oops in the scatterwalk once the record was pushed. Only the copy path leaves the ring full without pushing it, so splice until the ring is one fragment short, add the last fragment with a one-byte MSG_MORE send, and splice once more before pushing the record. CONFIG_MAX_SKB_FRAGS is 17..45, so that last fragment follows between 16 and 44 splices; sweep that range to trigger the bug on any build. Signed-off-by: chanyoung Link: https://patch.msgid.link/20260804052837.49015-3-ppoo1220@gmail.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/tls.c | 37 +++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tools/testing/selftests/net/tls.c b/tools/testing/selftests/net/tls.c index cbdd3ea28b998f..3d6f553eaf973d 100644 --- a/tools/testing/selftests/net/tls.c +++ b/tools/testing/selftests/net/tls.c @@ -835,6 +835,43 @@ TEST_F(tls, send_and_splice) EXPECT_EQ(memcmp(mem_send, mem_recv, send_len), 0); } +TEST_F(tls, splice_onto_full_record) +{ + char mem_send[4608]; + char mem_recv[4608]; + int frag_len = 100; + int nfrags, i, off; + int p[2]; + + memrnd(mem_send, sizeof(mem_send)); + ASSERT_GE(pipe(p), 0); + + for (nfrags = 16; nfrags <= 44; nfrags++) { + for (i = 0, off = 0; i < nfrags; i++, off += frag_len) { + EXPECT_EQ(write(p[1], mem_send + off, frag_len), frag_len); + EXPECT_EQ(splice(p[0], NULL, self->fd, NULL, frag_len, + SPLICE_F_MORE), frag_len); + } + + EXPECT_EQ(send(self->fd, mem_send + off, 1, MSG_MORE), 1); + off++; + + EXPECT_EQ(write(p[1], mem_send + off, frag_len), frag_len); + EXPECT_EQ(splice(p[0], NULL, self->fd, NULL, frag_len, + SPLICE_F_MORE), frag_len); + off += frag_len; + + EXPECT_EQ(send(self->fd, mem_send + off, 1, 0), 1); + off++; + + EXPECT_EQ(recv(self->cfd, mem_recv, off, MSG_WAITALL), off); + EXPECT_EQ(memcmp(mem_send, mem_recv, off), 0); + } + + close(p[0]); + close(p[1]); +} + TEST_F(tls, splice_to_pipe) { int send_len = TLS_PAYLOAD_MAX_LEN; From 1c8629651cb54f7b51db8fc0b1a9944e4a4b0f5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Jean?= Date: Tue, 4 Aug 2026 12:55:28 +0000 Subject: [PATCH 349/612] tls: rx: restore msg_iter before TLS 1.3 optimistic retry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tls_decrypt_sg() advances msg->msg_iter when it maps user pages for the optimistic TLS 1.3 zero-copy path. If the decrypted record turns out not to be unpadded application data, tls_decrypt_sw() retries into a kernel skb, but leaves the iterator advanced. The subsequent copy from the skb then writes decrypted bytes again at a later point in the caller iovecs while recvmsg() reports only the post-retry length. A TLS peer can trigger this after the receiver enables TLS_RX_EXPECT_NO_PAD. Revert the iterator by the number of bytes consumed by the optimistic mapping before retrying without zero-copy. Add a selftest which sends a TLS 1.3 control record with TLS_RX_EXPECT_NO_PAD enabled and verifies that recvmsg() does not overwrite later iovecs beyond the returned length. Fixes: ce61327ce989 ("tls: rx: support optimistic decrypt to user buffer with TLS 1.3") Cc: stable@vger.kernel.org Signed-off-by: Jérémy Jean Link: https://patch.msgid.link/20260804125528.2139928-1-Jeremy.Jean@oss.cyber.gouv.fr Signed-off-by: Jakub Kicinski --- net/tls/tls_sw.c | 2 ++ tools/testing/selftests/net/tls.c | 57 +++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/net/tls/tls_sw.c b/net/tls/tls_sw.c index d2e399be8ef6e0..133ed7c8940284 100644 --- a/net/tls/tls_sw.c +++ b/net/tls/tls_sw.c @@ -1456,6 +1456,8 @@ tls_decrypt_sw(struct sock *sk, struct tls_context *tls_ctx, /* If opportunistic TLS 1.3 ZC failed retry without ZC */ if (unlikely(darg->zc && prot->version == TLS_1_3_VERSION && darg->tail != TLS_RECORD_TYPE_DATA)) { + iov_iter_revert(&msg->msg_iter, strp_msg(darg->skb)->full_len - + prot->overhead_size); darg->zc = false; if (!darg->tail) TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSRXNOPADVIOL); diff --git a/tools/testing/selftests/net/tls.c b/tools/testing/selftests/net/tls.c index 3d6f553eaf973d..a0e72e402748fe 100644 --- a/tools/testing/selftests/net/tls.c +++ b/tools/testing/selftests/net/tls.c @@ -1839,6 +1839,63 @@ TEST_F(tls, recv_efault) } #define TLS_RECORD_TYPE_HANDSHAKE 0x16 + +TEST_F(tls_basic, recvmsg_nopad_retry_iov) +{ + char payload[32]; + char first_iov[sizeof(payload)]; + char later_iov[sizeof(payload) * 2]; + char expected_later_iov[sizeof(later_iov)]; + char cbuf[CMSG_SPACE(sizeof(char))]; + struct tls_crypto_info_keys tls13; + struct iovec iov[] = { + { .iov_base = first_iov, .iov_len = sizeof(first_iov) }, + { .iov_base = later_iov, .iov_len = sizeof(later_iov) }, + }; + struct msghdr msg = { + .msg_iov = iov, + .msg_iovlen = ARRAY_SIZE(iov), + .msg_control = cbuf, + .msg_controllen = sizeof(cbuf), + }; + int one = 1; + int ret; + int i; + + if (self->notls) + SKIP(return, "no TLS support"); + + tls_crypto_info_init(TLS_1_3_VERSION, TLS_CIPHER_AES_GCM_128, + &tls13, 0); + + ret = setsockopt(self->fd, SOL_TLS, TLS_TX, &tls13, tls13.len); + ASSERT_EQ(ret, 0); + + ret = setsockopt(self->cfd, SOL_TLS, TLS_RX, &tls13, tls13.len); + ASSERT_EQ(ret, 0); + + ret = setsockopt(self->cfd, SOL_TLS, TLS_RX_EXPECT_NO_PAD, + &one, sizeof(one)); + ASSERT_EQ(ret, 0); + + for (i = 0; i < sizeof(payload); i++) + payload[i] = 0x40 + i; + memset(first_iov, 0xa5, sizeof(first_iov)); + memset(later_iov, 0x5a, sizeof(later_iov)); + memset(expected_later_iov, 0x5a, sizeof(expected_later_iov)); + + /* A control record forces optimistic TLS 1.3 RX to retry. */ + ret = tls_send_cmsg(self->fd, TLS_RECORD_TYPE_HANDSHAKE, + payload, sizeof(payload), 0); + ASSERT_EQ(ret, sizeof(payload)); + + ret = recvmsg(self->cfd, &msg, 0); + ASSERT_EQ(ret, sizeof(payload)); + EXPECT_EQ(memcmp(first_iov, payload, sizeof(payload)), 0); + EXPECT_EQ(memcmp(later_iov, expected_later_iov, + sizeof(later_iov)), 0); +} + /* key_update, length 1, update_not_requested */ static const char key_update_msg[] = "\x18\x00\x00\x01\x00"; static void tls_send_keyupdate(struct __test_metadata *_metadata, int fd) From 6b69f2ef10cdb018c0b127a7cab88e590bbddba4 Mon Sep 17 00:00:00 2001 From: Ahmad Byagowi Date: Tue, 4 Aug 2026 14:07:51 -0700 Subject: [PATCH 350/612] ptp: ocp: Fix board ID over-read The EEPROM board ID is a fixed 13-byte field and is not guaranteed to contain a NUL terminator. Passing it directly to devlink_info_version_fixed_put() treats it as a C string and may read beyond the field. Format at most OCP_BOARD_ID_LEN bytes into the existing local buffer before reporting the ID. Use a precision limit because the snprintf() output size alone does not bound the source string scan. Fixes: 0cfcdd1ebcfe ("ptp: ocp: add nvmem interface for accessing eeprom") Cc: stable@vger.kernel.org Signed-off-by: Ahmad Byagowi Reviewed-by: Vadim Fedorenko Link: https://patch.msgid.link/20260804210751.48248-1-ahmadexp@gmail.com Signed-off-by: Jakub Kicinski --- drivers/ptp/ptp_ocp.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c index 35e911f1ad78c0..3d26ec1f7b9ec2 100644 --- a/drivers/ptp/ptp_ocp.c +++ b/drivers/ptp/ptp_ocp.c @@ -2177,9 +2177,11 @@ ptp_ocp_devlink_info_get(struct devlink *devlink, struct devlink_info_req *req, if (err) return err; + snprintf(buf, sizeof(buf), "%.*s", OCP_BOARD_ID_LEN, + (const char *)bp->board_id); err = devlink_info_version_fixed_put(req, DEVLINK_INFO_VERSION_GENERIC_BOARD_ID, - bp->board_id); + buf); if (err) return err; From bfec39ff1484b4e9f7d93bc4580fdb634bbc7d19 Mon Sep 17 00:00:00 2001 From: Bhargava Marreddy Date: Wed, 5 Aug 2026 15:10:22 +0530 Subject: [PATCH 351/612] bnge: Fix resource leak in bnge_init_nic() error path If bnge_init_chip() fails, bnge_init_nic() jumps to err_free_ring_grps and returns immediately, skipping cleanup for RX ring pair buffers. Remove the early return so execution falls through to err_free_rx_ring_pair_bufs to properly free resources on error. Fixes: 23df6aebf803 ("bng_en: Allocate stat contexts") Signed-off-by: Bhargava Marreddy Reviewed-by: Dharmender Garg Reviewed-by: Rajashekar Hudumula Link: https://patch.msgid.link/20260805094022.15487-1-bhargava.marreddy@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnge/bnge_netdev.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnge/bnge_netdev.c b/drivers/net/ethernet/broadcom/bnge/bnge_netdev.c index 6f7ef506d4e18c..ac4c93e5b634ac 100644 --- a/drivers/net/ethernet/broadcom/bnge/bnge_netdev.c +++ b/drivers/net/ethernet/broadcom/bnge/bnge_netdev.c @@ -2768,8 +2768,6 @@ static int bnge_init_nic(struct bnge_net *bn) err_free_ring_grps: bnge_free_ring_grps(bn); - return rc; - err_free_rx_ring_pair_bufs: bnge_free_rx_ring_pair_bufs(bn); return rc; From 3b9a324e646d3657a8d9806dfbfe4f3e4066e882 Mon Sep 17 00:00:00 2001 From: Qihang Tang Date: Wed, 5 Aug 2026 20:57:27 +0800 Subject: [PATCH 352/612] net: remove CAP_SYS_RAWIO zero-padding in dev_validate_header dev_validate_header() reads dev->hard_header_len directly when zero-padding short link layer headers for CAP_SYS_RAWIO holders: if (capable(CAP_SYS_RAWIO)) { memset(ll_header + len, 0, dev->hard_header_len - len); return true; } Packet send paths call dev_validate_header() on skbs whose headroom was allocated from an earlier hard_header_len read. If the device is reconfigured so that dev->hard_header_len increases before validation, the memset writes past the reserved buffer, an out-of-bounds write. This out-of-bounds write is masked in some SOCK_RAW paths today because the same concurrent increase can first make skb_push() exceed the reserved headroom and trigger skb_under_panic(). Remove the zero-padding branch before making those hard_header_len reads consistent, so the snapshot fixes do not turn a loud panic into a silent overwrite. This path is only reached for variable length L2 protocols, where len < hard_header_len but len >= min_header_len. No remaining in-tree variable length L2 protocol implements header_ops->validate, and the CAP_SYS_RAWIO bypass that zero-pads and accepts short headers has no real value beyond allowing testing of intentionally malformed input. Drop the CAP_SYS_RAWIO branch. The remaining reads of dev->hard_header_len in dev_validate_header() are comparisons only and have no memory safety impact. Suggested-by: Willem de Bruijn Fixes: 2793a23aacbd ("net: validate variable length ll headers") Cc: stable@vger.kernel.org Signed-off-by: Qihang Tang Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260805125729.19220-2-q.h.hack.winter@gmail.com Signed-off-by: Jakub Kicinski --- include/linux/netdevice.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 9981d637f8b546..9a770eb823ce18 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -3531,11 +3531,6 @@ static inline bool dev_validate_header(const struct net_device *dev, if (len < dev->min_header_len) return false; - if (capable(CAP_SYS_RAWIO)) { - memset(ll_header + len, 0, dev->hard_header_len - len); - return true; - } - if (dev->header_ops && dev->header_ops->validate) return dev->header_ops->validate(ll_header, len); From 03390aa32e669cc4ecd7d34108e2e1afc13d689d Mon Sep 17 00:00:00 2001 From: Qihang Tang Date: Wed, 5 Aug 2026 20:57:28 +0800 Subject: [PATCH 353/612] packet: use consistent hard_header_len in non-ring send paths packet_snd() reads dev->hard_header_len multiple times while allocating and constructing an skb. Device reconfiguration can change this value concurrently, for example through bonding device type changes. For SOCK_RAW, packet_snd() can save a larger value in reserve and later allocate headroom using a smaller value. Moving skb->data back by reserve then places it before skb->head, and the following copy from userspace can attempt an out-of-bounds write. packet_sendmsg_spkt() has the same issue because it calculates its reservation and header offset from separate reads before dropping the RCU read lock to allocate the skb. Add LL_RESERVED_SPACE_EX() for callers that already saved a header length. Read hard_header_len once in packet_snd() and use it for allocation and construction. In packet_sendmsg_spkt(), preserve the allocation-time value through the device lookup retry. The separate SOCK_DGRAM consistency problem between hard_header_len and header_ops->create is not addressed here. Fixes: b84bbaf7a6c8 ("packet: in packet_snd start writing at link layer allocation") Cc: stable@vger.kernel.org Signed-off-by: Qihang Tang Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260805125729.19220-3-q.h.hack.winter@gmail.com Signed-off-by: Jakub Kicinski --- include/linux/netdevice.h | 6 ++++-- net/packet/af_packet.c | 26 ++++++++++++++++---------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 9a770eb823ce18..8840b126979ff8 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -300,9 +300,11 @@ struct hh_cache { * We could use other alignment values, but we must maintain the * relationship HH alignment <= LL alignment. */ -#define LL_RESERVED_SPACE(dev) \ - ((((dev)->hard_header_len + READ_ONCE((dev)->needed_headroom)) \ +#define LL_RESERVED_SPACE_EX(dev, hlen) \ + ((((hlen) + READ_ONCE((dev)->needed_headroom)) \ & ~(HH_DATA_MOD - 1)) + HH_DATA_MOD) +#define LL_RESERVED_SPACE(dev) \ + LL_RESERVED_SPACE_EX(dev, (dev)->hard_header_len) #define LL_RESERVED_SPACE_EXTRA(dev,extra) \ ((((dev)->hard_header_len + READ_ONCE((dev)->needed_headroom) + (extra)) \ & ~(HH_DATA_MOD - 1)) + HH_DATA_MOD) diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c index 0e1355be89f64b..b7af45c809e4e2 100644 --- a/net/packet/af_packet.c +++ b/net/packet/af_packet.c @@ -1966,8 +1966,9 @@ static int packet_sendmsg_spkt(struct socket *sock, struct msghdr *msg, struct net_device *dev; struct sockcm_cookie sockc; __be16 proto = 0; - int err; + int hard_header_len; int extra_len = 0; + int err; /* * Get and verify the address. @@ -2010,14 +2011,18 @@ static int packet_sendmsg_spkt(struct socket *sock, struct msghdr *msg, extra_len = 4; /* We're doing our own CRC */ } + /* Keep the allocation-time header length across retry. */ + if (!skb) + hard_header_len = READ_ONCE(dev->hard_header_len); + err = -EMSGSIZE; - if (len > dev->mtu + dev->hard_header_len + VLAN_HLEN + extra_len) + if (len > dev->mtu + hard_header_len + VLAN_HLEN + extra_len) goto out_unlock; if (!skb) { - size_t reserved = LL_RESERVED_SPACE(dev); + size_t reserved = LL_RESERVED_SPACE_EX(dev, hard_header_len); int tlen = dev->needed_tailroom; - unsigned int hhlen = dev->header_ops ? dev->hard_header_len : 0; + unsigned int hhlen = dev->header_ops ? hard_header_len : 0; rcu_read_unlock(); skb = sock_wmalloc(sk, len + reserved + tlen, 0, GFP_KERNEL); @@ -2047,7 +2052,7 @@ static int packet_sendmsg_spkt(struct socket *sock, struct msghdr *msg, err = -EINVAL; goto out_unlock; } - if (len > (dev->mtu + dev->hard_header_len + extra_len) && + if (len > (dev->mtu + hard_header_len + extra_len) && !packet_extra_vlan_len_allowed(dev, skb)) { err = -EMSGSIZE; goto out_unlock; @@ -2969,7 +2974,7 @@ static int packet_snd(struct socket *sock, struct msghdr *msg, size_t len) int offset = 0; struct packet_sock *po = pkt_sk(sk); int vnet_hdr_sz = READ_ONCE(po->vnet_hdr_sz); - int hlen, tlen, linear; + int hard_header_len, hlen, tlen, linear; int extra_len = 0; /* @@ -3009,8 +3014,9 @@ static int packet_snd(struct socket *sock, struct msghdr *msg, size_t len) goto out_unlock; } + hard_header_len = READ_ONCE(dev->hard_header_len); if (sock->type == SOCK_RAW) - reserve = dev->hard_header_len; + reserve = hard_header_len; if (vnet_hdr_sz) { err = packet_snd_vnet_parse(msg, &len, &vnet_hdr, vnet_hdr_sz); if (err) @@ -3031,10 +3037,10 @@ static int packet_snd(struct socket *sock, struct msghdr *msg, size_t len) goto out_unlock; err = -ENOBUFS; - hlen = LL_RESERVED_SPACE(dev); + hlen = LL_RESERVED_SPACE_EX(dev, hard_header_len); tlen = dev->needed_tailroom; linear = __virtio16_to_cpu(vio_le(), vnet_hdr.hdr_len); - linear = max(linear, min_t(int, len, dev->hard_header_len)); + linear = max(linear, min_t(int, len, hard_header_len)); skb = packet_alloc_skb(sk, hlen + tlen, hlen, len, linear, msg->msg_flags & MSG_DONTWAIT, &err); if (skb == NULL) @@ -3050,7 +3056,7 @@ static int packet_snd(struct socket *sock, struct msghdr *msg, size_t len) } else if (reserve) { skb_reserve(skb, -reserve); if (len < reserve + sizeof(struct ipv6hdr) && - dev->min_header_len != dev->hard_header_len) + dev->min_header_len != hard_header_len) skb_reset_network_header(skb); } From 21b5953e7494c16a42e6cd8cf110e18d13ae4a6b Mon Sep 17 00:00:00 2001 From: Qihang Tang Date: Wed, 5 Aug 2026 20:57:29 +0800 Subject: [PATCH 354/612] packet: use consistent hard_header_len in TX_RING send path tpacket_snd() reads dev->hard_header_len independently for skb allocation and header construction in tpacket_fill_skb(). Concurrent netdevice reconfiguration can therefore make the reserved headroom smaller than the amount later pushed, or make copylen - hard_header_len negative. Snapshot hard_header_len once before processing ring frames and use it for the frame limit, headroom allocation, copy length, and skb construction. Pass the snapshot to tpacket_fill_skb(). The separate SOCK_DGRAM consistency problem between hard_header_len and header_ops->create is not addressed here. Fixes: 69e3c75f4d54 ("net: TX_RING and packet mmap") Cc: stable@vger.kernel.org Signed-off-by: Qihang Tang Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260805125729.19220-4-q.h.hack.winter@gmail.com Signed-off-by: Jakub Kicinski --- net/packet/af_packet.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c index b7af45c809e4e2..435756877abaa1 100644 --- a/net/packet/af_packet.c +++ b/net/packet/af_packet.c @@ -2587,6 +2587,7 @@ static int packet_snd_vnet_parse(struct msghdr *msg, size_t *len, static int tpacket_fill_skb(struct packet_sock *po, struct sk_buff *skb, void *frame, struct net_device *dev, void *data, int tp_len, __be16 proto, unsigned char *addr, int hlen, int copylen, + int hard_header_len, const struct sockcm_cookie *sockc) { union tpacket_uhdr ph; @@ -2618,8 +2619,8 @@ static int tpacket_fill_skb(struct packet_sock *po, struct sk_buff *skb, } else if (copylen) { int hdrlen = min_t(int, copylen, tp_len); - skb_push(skb, dev->hard_header_len); - skb_put(skb, copylen - dev->hard_header_len); + skb_push(skb, hard_header_len); + skb_put(skb, copylen - hard_header_len); err = skb_store_bits(skb, 0, data, hdrlen); if (unlikely(err)) return err; @@ -2750,7 +2751,7 @@ static int tpacket_snd(struct packet_sock *po, struct msghdr *msg) void *data; int len_sum = 0; int status = TP_STATUS_AVAILABLE; - int hlen, tlen, copylen = 0; + int hard_header_len, hlen, tlen, copylen = 0; long timeo; mutex_lock(&po->pg_vec_lock); @@ -2797,8 +2798,9 @@ static int tpacket_snd(struct packet_sock *po, struct msghdr *msg) goto out_put; } + hard_header_len = READ_ONCE(dev->hard_header_len); if (po->sk.sk_socket->type == SOCK_RAW) - reserve = dev->hard_header_len; + reserve = hard_header_len; size_max = po->tx_ring.frame_size - (po->tp_hdrlen - sizeof(struct sockaddr_ll)); @@ -2835,7 +2837,7 @@ static int tpacket_snd(struct packet_sock *po, struct msghdr *msg) goto tpacket_error; status = TP_STATUS_SEND_REQUEST; - hlen = LL_RESERVED_SPACE(dev); + hlen = LL_RESERVED_SPACE_EX(dev, hard_header_len); tlen = dev->needed_tailroom; if (vnet_hdr_sz) { data += vnet_hdr_sz; @@ -2853,10 +2855,10 @@ static int tpacket_snd(struct packet_sock *po, struct msghdr *msg) vnet_hdr.hdr_len); has_vnet_hdr = true; } - copylen = max_t(int, copylen, dev->hard_header_len); + copylen = max_t(int, copylen, hard_header_len); skb = sock_alloc_send_skb(&po->sk, hlen + tlen + sizeof(struct sockaddr_ll) + - (copylen - dev->hard_header_len), + (copylen - hard_header_len), !need_wait, &err); if (unlikely(skb == NULL)) { @@ -2866,7 +2868,8 @@ static int tpacket_snd(struct packet_sock *po, struct msghdr *msg) goto out_status; } tp_len = tpacket_fill_skb(po, skb, ph, dev, data, tp_len, proto, - addr, hlen, copylen, &sockc); + addr, hlen, copylen, hard_header_len, + &sockc); if (likely(tp_len >= 0) && tp_len > dev->mtu + reserve && !vnet_hdr_sz && From b1896543ce59c4258625a35cf41e23a9a1f80ea2 Mon Sep 17 00:00:00 2001 From: Alexandra Winter Date: Wed, 5 Aug 2026 15:10:43 +0200 Subject: [PATCH 355/612] s390/ism: Fix UAF of sba and ieq during ism_dev_exit() A ism interrupt handler can be active in parallel with ism_dev_exit(), accessing freed data structures. No new interrupts will be generated after unregister_ieq(). Drain ongoing interrupt handlers by free_irq(), before freeing ism data structures. Fixes: 684b89bc39ce ("s390/ism: add device driver for internal shared memory") Signed-off-by: Alexandra Winter Link: https://patch.msgid.link/20260805131043.954639-1-wintera@linux.ibm.com Signed-off-by: Jakub Kicinski --- drivers/s390/net/ism_drv.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/drivers/s390/net/ism_drv.c b/drivers/s390/net/ism_drv.c index d99c588d3e00ce..242da20f27e0ed 100644 --- a/drivers/s390/net/ism_drv.c +++ b/drivers/s390/net/ism_drv.c @@ -148,13 +148,16 @@ static int unregister_sba(struct ism_dev *ism) if (ret && ret != ISM_ERROR) return -EIO; + return 0; +} + +static void ism_free_sba(struct ism_dev *ism) +{ dma_free_coherent(&ism->pdev->dev, PAGE_SIZE, ism->sba, ism->sba_dma_addr); ism->sba = NULL; ism->sba_dma_addr = 0; - - return 0; } static int unregister_ieq(struct ism_dev *ism) @@ -168,13 +171,16 @@ static int unregister_ieq(struct ism_dev *ism) if (ret && ret != ISM_ERROR) return -EIO; + return 0; +} + +static void ism_free_ieq(struct ism_dev *ism) +{ dma_free_coherent(&ism->pdev->dev, PAGE_SIZE, ism->ieq, ism->ieq_dma_addr); ism->ieq = NULL; ism->ieq_dma_addr = 0; - - return 0; } static int ism_read_local_gid(struct dibs_dev *dibs) @@ -573,6 +579,7 @@ static int ism_dev_init(struct ism_dev *ism) unreg_sba: unregister_sba(ism); + ism_free_sba(ism); free_irq: free_irq(pci_irq_vector(pdev, 0), ism); free_vectors: @@ -585,9 +592,13 @@ static void ism_dev_exit(struct ism_dev *ism) { struct pci_dev *pdev = ism->pdev; + /* ism will only generate new IRQs while ieq & sba are registered */ unregister_ieq(ism); unregister_sba(ism); + /* drain ongoing irpt handlers */ free_irq(pci_irq_vector(pdev, 0), ism); + ism_free_ieq(ism); + ism_free_sba(ism); pci_free_irq_vectors(pdev); } From d0c80dbb970439bd2eeb0e5effff8c16a5f4e1e3 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 5 Aug 2026 13:15:08 +0000 Subject: [PATCH 356/612] net/atm: fix slab-out-of-bounds read in vcc_setsockopt() vcc_setsockopt() contained an ineffective optlen check: if (__SO_LEVEL_MATCH(optname, level) && optlen != __SO_SIZE(optname)) return -EINVAL; If __SO_LEVEL_MATCH(optname, level) evaluated to false (e.g. if the caller passed a mismatched level), the length check optlen != __SO_SIZE(optname) was short-circuited and bypassed. Execution then fell through to switch(optname), calling copy_from_sockptr() assuming optval contained sufficient space. Furthermore, even if level matched, a cgroup BPF setsockopt filter could shrink optlen after entry. Because copy_from_sockptr() on kernel pointers uses memcpy(), this leads to a KASAN slab-out-of-bounds read when optlen is smaller than the expected structure size. Fix this by using copy_safe_from_sockptr(), which unconditionally validates that optlen is at least the expected size before copying. Also change the local 'value' variable type from 'unsigned long' to 'int' so that SO_SETCLP matches its sizeof(int) ABI encoding on 64-bit systems. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: syzbot+53ecc09fb81df10ef4de@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=53ecc09fb81df10ef4de Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260805131508.3227331-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/atm/common.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/net/atm/common.c b/net/atm/common.c index c7f92405daf050..81195727fa18ba 100644 --- a/net/atm/common.c +++ b/net/atm/common.c @@ -710,7 +710,7 @@ int vcc_setsockopt(struct socket *sock, int level, int optname, sockptr_t optval, unsigned int optlen) { struct atm_vcc *vcc; - unsigned long value; + int value; int error; if (__SO_LEVEL_MATCH(optname, level) && optlen != __SO_SIZE(optname)) @@ -722,8 +722,10 @@ int vcc_setsockopt(struct socket *sock, int level, int optname, { struct atm_qos qos; - if (copy_from_sockptr(&qos, optval, sizeof(qos))) - return -EFAULT; + error = copy_safe_from_sockptr(&qos, sizeof(qos), optval, + optlen); + if (error) + return error; error = check_qos(&qos); if (error) return error; @@ -737,8 +739,10 @@ int vcc_setsockopt(struct socket *sock, int level, int optname, return 0; } case SO_SETCLP: - if (copy_from_sockptr(&value, optval, sizeof(value))) - return -EFAULT; + error = copy_safe_from_sockptr(&value, sizeof(value), optval, + optlen); + if (error) + return error; if (value) vcc->atm_options |= ATM_ATMOPT_CLP; else From c9158ceaf27780ef64534ad72f44ffde3f8ccc49 Mon Sep 17 00:00:00 2001 From: Xin Long Date: Wed, 5 Aug 2026 11:18:40 -0400 Subject: [PATCH 357/612] sctp: clear control chunk transport if it is being removed sctp_make_heartbeat_ack() caches the destination transport in chunk->transport without taking a reference. When src_out_of_asoc_ok is enabled, the HEARTBEAT ACK may remain queued on control_chunk_list instead of being transmitted immediately. If the peer transport is removed while the chunk is still queued, sctp_assoc_rm_peer() drops the transport and schedules it for RCU freeing, but only clears cached transport pointers in out_chunk_list. The queued control chunk therefore retains a dangling transport pointer. Once an ASCONF_ACK clears the suppression and the queued control chunk is transmitted, SCTP dereferences the stale transport pointer, leading to a use-after-free. Fix this by also clearing chunk->transport for queued control chunks in control_chunk_list when removing the transport. Fixes: 8a07eb0a50ae ("sctp: Add ASCONF operation on the single-homed host") Reported-by: Daniele Linguaglossa Signed-off-by: Xin Long Link: https://patch.msgid.link/7e1168cb722132152a29d47e5eafaeac4a3bf6f3.1785943120.git.lucien.xin@gmail.com Signed-off-by: Jakub Kicinski --- net/sctp/associola.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/sctp/associola.c b/net/sctp/associola.c index b6ac0966420a1f..5b0ae616e1ff94 100644 --- a/net/sctp/associola.c +++ b/net/sctp/associola.c @@ -573,6 +573,10 @@ void sctp_assoc_rm_peer(struct sctp_association *asoc, if (ch->transport == peer) ch->transport = NULL; + list_for_each_entry(ch, &asoc->outqueue.control_chunk_list, list) + if (ch->transport == peer) + ch->transport = NULL; + asoc->peer.transport_count--; sctp_ulpevent_notify_peer_addr_change(peer, SCTP_ADDR_REMOVED, 0); From 03a736fde464cefa0a6e10d29914c27635965ce2 Mon Sep 17 00:00:00 2001 From: Ivan Vecera Date: Wed, 5 Aug 2026 17:54:25 +0200 Subject: [PATCH 358/612] MAINTAINERS: dpll: zl3073x: replace Prathosh Satish with Min Li Replace Prathosh Satish by Min Li as the Microchip co-maintainer of the ZL3073X DPLL driver. Signed-off-by: Ivan Vecera Link: https://patch.msgid.link/20260805155425.38808-1-ivecera@redhat.com Signed-off-by: Jakub Kicinski --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index a77cf599bac283..9de29cdcd8c69d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17847,7 +17847,7 @@ F: drivers/net/wireless/microchip/ MICROCHIP ZL3073X DRIVER M: Ivan Vecera -M: Prathosh Satish +M: Min Li L: netdev@vger.kernel.org S: Supported F: Documentation/devicetree/bindings/dpll/microchip,zl30731.yaml From 8e63c9e6179ace3033dcd19991f95386e9d3130f Mon Sep 17 00:00:00 2001 From: Dragos Tatulea Date: Thu, 6 Aug 2026 11:07:58 +0300 Subject: [PATCH 359/612] net: Defer netdev KOBJ_ADD uevent until the device is published netdev_register_kobject() calls device_add(), which emits KOBJ_ADD and wakes udev, but register_netdevice() only makes the device findable by name later, in list_netdevice(). A udev worker that reacts to the uevent can therefore run against a device that no lookup can find yet. This used to be harmless because the ethtool ioctl took the rtnl_lock when looking the device up, and register_netdevice() runs under rtnl, so the worker simply blocked until registration finished. The commit in the fixes tag moved the lookup out from under rtnl for ops-locked drivers. Now there is a short window in register_netdevice() between netdev_register_kobject() until list_netdevice() when the device is not findable by name. This was reproduced with the mlx5 driver on a kernel with KASAN enabled during devlink reload: systemd-udevd's net_driver builtin gets -ENODEV from ETHTOOL_GDRVINFO, which was preventing interface renaming. Suppress the uevent in netdev_register_kobject() and emit it from register_netdevice() next to rtmsg_ifinfo(). This is the last point in register_netdevice() where no error can happen, so only fully registered devices are announced: the registration error paths never reach it, and the device_del() that unwinds them stays silent as well, leaving userspace with neither an add nor a remove. Fixes: f994752b1127 ("net: ethtool: optionally skip rtnl_lock on IOCTL path") Signed-off-by: Dragos Tatulea Reviewed-by: Shahar Shitrit Link: https://patch.msgid.link/20260806080758.2039586-2-dtatulea@nvidia.com Signed-off-by: Jakub Kicinski --- net/core/dev.c | 1 + net/core/net-sysfs.c | 14 ++++++++++++++ net/core/net-sysfs.h | 1 + 3 files changed, 16 insertions(+) diff --git a/net/core/dev.c b/net/core/dev.c index 5933c5dab09ee1..c49d2ce5128510 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -11494,6 +11494,7 @@ int register_netdevice(struct net_device *dev) * Prevent userspace races by waiting until the network * device is fully setup before sending notifications. */ + netdev_uevent_add(dev); if (!(dev->rtnl_link_ops && dev->rtnl_link_initializing)) rtmsg_ifinfo(RTM_NEWLINK, dev, ~0U, GFP_KERNEL, 0, NULL); diff --git a/net/core/net-sysfs.c b/net/core/net-sysfs.c index 0e71c9ed41e81d..25546deacec802 100644 --- a/net/core/net-sysfs.c +++ b/net/core/net-sysfs.c @@ -2334,6 +2334,9 @@ int netdev_register_kobject(struct net_device *ndev) *groups++ = &wireless_group; #endif /* CONFIG_SYSFS */ + /* Hold back the KOBJ_ADD uevent until the device is listed. */ + dev_set_uevent_suppress(dev, 1); + error = device_add(dev); if (error) return error; @@ -2349,6 +2352,17 @@ int netdev_register_kobject(struct net_device *ndev) return error; } +/* Announce a fully registered device to userspace. This pairs with the uevent + * suppression from netdev_register_kobject(). + */ +void netdev_uevent_add(struct net_device *ndev) +{ + struct device *dev = &ndev->dev; + + dev_set_uevent_suppress(dev, 0); + kobject_uevent(&dev->kobj, KOBJ_ADD); +} + /* Change owner for sysfs entries when moving network devices across network * namespaces owned by different user namespaces. */ diff --git a/net/core/net-sysfs.h b/net/core/net-sysfs.h index 38e2e3ffd0bdc4..2f41a4dee86622 100644 --- a/net/core/net-sysfs.h +++ b/net/core/net-sysfs.h @@ -4,6 +4,7 @@ int __init netdev_kobject_init(void); int netdev_register_kobject(struct net_device *); +void netdev_uevent_add(struct net_device *dev); void netdev_unregister_kobject(struct net_device *); int net_rx_queue_update_kobjects(struct net_device *, int old_num, int new_num); int netdev_queue_update_kobjects(struct net_device *net, From d31c14e56a4f01f0689c29c0bcbcd45f65b4a473 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Wed, 5 Aug 2026 19:28:21 -0700 Subject: [PATCH 360/612] net: avoid theoretical races with ref drain Technically, it's illegal to take a ref on a netdev just because we have a pointer on which we already hold a ref, with no other protection. This is because our simple per-cpu refcount implementation cannot atomically read the count. Let's make sure we cancel outstanding work and never queue more work for a device we know is dead. This way taking a ref on a dev we know is on the netdev_work_list is always going to be safe. Jiangshan Yi reports that the issues is caught by ref tracker infra leading to a warning: WARNING: lib/ref_tracker.c:322 at ref_tracker_free WARNING: lib/ref_tracker.c:246 at ref_tracker_dir_exit Reported-by: Jiangshan Yi Link: https://lore.kernel.org/20260731035135.3917308-2-yijiangshan@kylinos.cn Fixes: 12c765be84d2 ("net: turn the rx_mode work into a generic netdev_work facility") Link: https://patch.msgid.link/20260806022821.2079945-1-kuba@kernel.org Signed-off-by: Jakub Kicinski --- net/core/dev.c | 1 + net/core/dev.h | 1 + net/core/netdev_work.c | 16 ++++++++++++++++ 3 files changed, 18 insertions(+) diff --git a/net/core/dev.c b/net/core/dev.c index c49d2ce5128510..af260ff5462a6e 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -12436,6 +12436,7 @@ void unregister_netdevice_many_notify(struct list_head *head, dev_tcx_uninstall(dev); dev_xdp_uninstall(dev); dev_memory_provider_uninstall(dev); + netdev_work_cancel_all(dev); netdev_unlock_ops(dev); bpf_dev_bound_netdev_unregister(dev); diff --git a/net/core/dev.h b/net/core/dev.h index 5d0b0305d3baaf..b757faead4d1a3 100644 --- a/net/core/dev.h +++ b/net/core/dev.h @@ -179,6 +179,7 @@ enum netdev_work_core { void __netdev_work_core_sched(struct net_device *dev, unsigned long event); unsigned long __netdev_work_core_cancel(struct net_device *dev, unsigned long mask); +void netdev_work_cancel_all(struct net_device *dev); void __dev_notify_flags(struct net_device *dev, unsigned int old_flags, unsigned int gchanges, u32 portid, diff --git a/net/core/netdev_work.c b/net/core/netdev_work.c index 3109fae132ad18..e721a06d58df1f 100644 --- a/net/core/netdev_work.c +++ b/net/core/netdev_work.c @@ -31,6 +31,10 @@ static void netdev_work_enqueue(struct net_device *dev, unsigned long events, return; spin_lock_bh(&netdev_work_lock); + if (!dev_isalive(dev)) { + spin_unlock_bh(&netdev_work_lock); + return; + } if (list_empty(&dev->work_node)) { list_add_tail(&dev->work_node, &netdev_work_list); netdev_hold(dev, &dev->work_tracker, GFP_ATOMIC); @@ -61,6 +65,18 @@ netdev_work_dequeue(struct net_device *dev, unsigned long *pending, return events; } +void netdev_work_cancel_all(struct net_device *dev) +{ + spin_lock_bh(&netdev_work_lock); + dev->work_pending = 0; + dev->work_core_pending = 0; + if (!list_empty(&dev->work_node)) { + list_del_init(&dev->work_node); + netdev_put(dev, &dev->work_tracker); + } + spin_unlock_bh(&netdev_work_lock); +} + void netdev_work_sched(struct net_device *dev, unsigned long events) { netdev_work_enqueue(dev, events, 0); From af0e5cdd031f4f4a8f6d4160bfbda4f36872b0ed Mon Sep 17 00:00:00 2001 From: Maximilian Immanuel Brandtner Date: Wed, 5 Aug 2026 08:22:48 +0200 Subject: [PATCH 361/612] tls: don't abort the connection on signal-interrupted sends When a signal interrupts a blocking send, tls_tx_records() treats the resulting -ERESTARTSYS as a transmission failure and marks the socket errored via tls_err_abort() with the raw error code. Later syscalls return the kernel-internal errno 512 (ERESTARTSYS) to userspace, as the signal it stems from is no longer pending during syscall exit and thus never translated. An interrupted send is not a connection error: the partially sent record stays queued and is resent later. Interrupt error codes are therefore excluded from the abort in the same way as -EAGAIN. Fixes: b341ca51d267 ("tls: Fix tls_sw_sendmsg error handling") Signed-off-by: Maximilian Immanuel Brandtner Link: https://patch.msgid.link/20260805063109.1772314-1-maxbr@linux.ibm.com Signed-off-by: Jakub Kicinski --- net/tls/tls_sw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/tls/tls_sw.c b/net/tls/tls_sw.c index 133ed7c8940284..62d46736e24b7d 100644 --- a/net/tls/tls_sw.c +++ b/net/tls/tls_sw.c @@ -458,7 +458,7 @@ int tls_tx_records(struct sock *sk, int flags) } tx_err: - if (rc < 0 && rc != -EAGAIN) + if (rc < 0 && rc != -EAGAIN && rc != -EINTR && rc != -ERESTARTSYS) tls_err_abort(sk, rc); return rc; From b0ce5fd9fabe7c79463cf4602217d4dfeff5b1fd Mon Sep 17 00:00:00 2001 From: Philipp David Date: Tue, 4 Aug 2026 15:22:03 -0700 Subject: [PATCH 362/612] igc: fix netdev not re-attached after resume if interface is down __igc_resume() calls netif_device_attach() only inside the netif_running() branch, so an interface that was down during suspend is never re-attached on resume. It then stays in the not-present state that __igc_shutdown() set via netif_device_detach(): ethtool reports ENODEV and every attempt to bring the interface up fails the netif_device_present() check in __dev_open() with -ENODEV, silently, since __igc_resume() returns 0. Only reloading the driver recovers the device. This is easy to hit in practice because NetworkManager brings managed interfaces down before sleep unless Wake-on-LAN is configured, making the adapter unusable after every suspend/resume cycle with WoL disabled. Re-attach the netdev on every successful resume, as igb and e1000e do. Fixes: 6f31d6b643a3 ("igc: Refactor runtime power management flow") Cc: stable@vger.kernel.org Signed-off-by: Philipp David Reviewed-by: Aleksandr Loktionov Reviewed-by: Dima Ruinskiy Signed-off-by: Tony Nguyen Link: https://patch.msgid.link/20260804222205.1580328-11-anthony.l.nguyen@intel.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/igc/igc_main.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/intel/igc/igc_main.c b/drivers/net/ethernet/intel/igc/igc_main.c index 16fcc8cf619195..39043d8ca1bf48 100644 --- a/drivers/net/ethernet/intel/igc/igc_main.c +++ b/drivers/net/ethernet/intel/igc/igc_main.c @@ -7585,11 +7585,13 @@ static int __igc_resume(struct device *dev, bool rpm) err = __igc_open(netdev, true); if (!rpm) rtnl_unlock(); - if (!err) - netif_device_attach(netdev); + if (err) + return err; } - return err; + netif_device_attach(netdev); + + return 0; } static int igc_resume(struct device *dev) From c87801f545dac9fcbb84c96f280706572be00a85 Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Wed, 29 Jul 2026 16:57:08 +0800 Subject: [PATCH 363/612] drm/amdgpu: fix JPEG v5.0.0 queue reset failure in DPG mode In DPG mode jpeg_v5_0_0_ring_reset() takes the DPG stop path, which only clears the JPEG_PG_MODE bit and never resets the JRBC. A hung ring is not recovered: the post-reset ring test times out and the driver falls back to a full MODE1 reset. Temporarily force the static power-gating path during the reset so the stop/start sequence power-cycles the JPEG block (JMI soft reset + power off/on), matching the jpeg_v4_0 reset which has no DPG path. Acked-by: Alex Deucher Signed-off-by: Jesse Zhang Signed-off-by: Alex Deucher (cherry picked from commit 79b3612827d1adcd2008cd585961fa35a6ff20f2) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_0.c | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_0.c b/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_0.c index 72a4b2d0676fa4..c696a0dea32ce1 100644 --- a/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_0.c +++ b/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_0.c @@ -648,15 +648,28 @@ static int jpeg_v5_0_0_ring_reset(struct amdgpu_ring *ring, unsigned int vmid, struct amdgpu_fence *timedout_fence) { + struct amdgpu_device *adev = ring->adev; + u32 pg_flags = adev->pg_flags; int r; amdgpu_ring_reset_helper_begin(ring, timedout_fence); - r = jpeg_v5_0_0_stop(ring->adev); - if (r) - return r; - r = jpeg_v5_0_0_start(ring->adev); + + /* + * The DPG stop path only clears the JPEG_PG_MODE bit and never resets a + * hung JRBC, so the post-reset ring test times out and the driver falls + * back to a full MODE1 reset. Temporarily force the static power-gating + * path so the stop/start sequence actually power-cycles the JPEG block + * (JMI soft reset + ONO1 power off/on), matching the working jpeg_v4_0 + * reset. + */ + adev->pg_flags &= ~AMD_PG_SUPPORT_JPEG_DPG; + r = jpeg_v5_0_0_stop(adev); + if (!r) + r = jpeg_v5_0_0_start(adev); + adev->pg_flags = pg_flags; if (r) return r; + return amdgpu_ring_reset_helper_end(ring, timedout_fence); } From 4301e60e406c613aea06fdc0c36bf0675b0b8a2e Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Wed, 29 Jul 2026 16:57:08 +0800 Subject: [PATCH 364/612] drm/amdgpu: fix JPEG v4.0.5 queue reset failure in DPG mode Like jpeg_v5_0_0, in DPG mode the ring reset path only clears the JPEG_PG_MODE bit and never resets a hung JRBC, so the post-reset ring test times out and the driver falls back to a full MODE1 reset. Temporarily force the static power-gating path during the reset so the stop/start sequence power-cycles the JPEG block (JMI soft reset + power off/on), matching the jpeg_v4_0 reset. Acked-by: Alex Deucher Signed-off-by: Jesse Zhang Signed-off-by: Alex Deucher (cherry picked from commit 75a308eef4503a9d2bf297bef5a9317d2209e696) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/jpeg_v4_0_5.c | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/jpeg_v4_0_5.c b/drivers/gpu/drm/amd/amdgpu/jpeg_v4_0_5.c index a43582b9c876c7..3f61d5367d9469 100644 --- a/drivers/gpu/drm/amd/amdgpu/jpeg_v4_0_5.c +++ b/drivers/gpu/drm/amd/amdgpu/jpeg_v4_0_5.c @@ -772,15 +772,28 @@ static int jpeg_v4_0_5_ring_reset(struct amdgpu_ring *ring, unsigned int vmid, struct amdgpu_fence *timedout_fence) { + struct amdgpu_device *adev = ring->adev; + u32 pg_flags = adev->pg_flags; int r; amdgpu_ring_reset_helper_begin(ring, timedout_fence); - r = jpeg_v4_0_5_stop(ring->adev); - if (r) - return r; - r = jpeg_v4_0_5_start(ring->adev); + + /* + * The DPG stop path only clears the JPEG_PG_MODE bit and never resets a + * hung JRBC, so the post-reset ring test times out and the driver falls + * back to a full MODE1 reset. Temporarily force the static power-gating + * path so the stop/start sequence actually power-cycles the JPEG block + * (JMI soft reset + static power off/on), matching the working jpeg_v4_0 + * reset. + */ + adev->pg_flags &= ~AMD_PG_SUPPORT_JPEG_DPG; + r = jpeg_v4_0_5_stop(adev); + if (!r) + r = jpeg_v4_0_5_start(adev); + adev->pg_flags = pg_flags; if (r) return r; + return amdgpu_ring_reset_helper_end(ring, timedout_fence); } From d2dc81ed5191e7e06a8aa3e153f665d36bae8eb6 Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Wed, 29 Jul 2026 16:57:08 +0800 Subject: [PATCH 365/612] drm/amdgpu: fix JPEG v5.3.0 queue reset failure in DPG mode Like jpeg_v5_0_0, in DPG mode the ring reset path only clears the JPEG_PG_MODE bit and never resets a hung JRBC, so the post-reset ring test times out and the driver falls back to a full MODE1 reset. Temporarily force the static power-gating path during the reset so the stop/start sequence power-cycles the JPEG block (JMI soft reset + power off/on), matching the jpeg_v4_0 reset. Acked-by: Alex Deucher Signed-off-by: Jesse Zhang Signed-off-by: Alex Deucher (cherry picked from commit e93659cab11c48255dcac58af60203c99815586b) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/jpeg_v5_3_0.c | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/jpeg_v5_3_0.c b/drivers/gpu/drm/amd/amdgpu/jpeg_v5_3_0.c index 2c3e6d9f52a655..33c2626abc80f1 100644 --- a/drivers/gpu/drm/amd/amdgpu/jpeg_v5_3_0.c +++ b/drivers/gpu/drm/amd/amdgpu/jpeg_v5_3_0.c @@ -655,15 +655,28 @@ static int jpeg_v5_3_0_ring_reset(struct amdgpu_ring *ring, unsigned int vmid, struct amdgpu_fence *timedout_fence) { + struct amdgpu_device *adev = ring->adev; + u32 pg_flags = adev->pg_flags; int r; amdgpu_ring_reset_helper_begin(ring, timedout_fence); - r = jpeg_v5_3_0_stop(ring->adev); - if (r) - return r; - r = jpeg_v5_3_0_start(ring->adev); + + /* + * The DPG stop path only clears the JPEG_PG_MODE bit and never resets a + * hung JRBC, so the post-reset ring test times out and the driver falls + * back to a full MODE1 reset. Temporarily force the static power-gating + * path so the stop/start sequence actually power-cycles the JPEG block + * (JMI soft reset + static power off/on), matching the working jpeg_v4_0 + * reset. + */ + adev->pg_flags &= ~AMD_PG_SUPPORT_JPEG_DPG; + r = jpeg_v5_3_0_stop(adev); + if (!r) + r = jpeg_v5_3_0_start(adev); + adev->pg_flags = pg_flags; if (r) return r; + return amdgpu_ring_reset_helper_end(ring, timedout_fence); } From 2d69604b4d0b9c0c0ac71624b5fafb36cf249729 Mon Sep 17 00:00:00 2001 From: Qiang Yu Date: Thu, 23 Jul 2026 17:20:47 +0800 Subject: [PATCH 366/612] drm/amdgpu: read TRUNCATE_COORD_MODE on gfx12 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TA_CNTL2.TRUNCATE_COORD_MODE selects whether texture coordinate truncation is D3D9/GL/Vulkan conformant. gfx11 reads it and reports it to userspace via AMDGPU_IDS_FLAGS_CONFORMANT_TRUNC_COORD, but gfx12 never read it, so the flag was always reported as 0 and userspace fell back to the non-conformant path. Read it in gfx_v12_0_constants_init() like gfx11 does. Fixes: 52cb80c12e8a ("drm/amdgpu: Add gfx v12_0 ip block support (v6)") Signed-off-by: Qiang Yu Reviewed-by: Marek Olšák Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher (cherry picked from commit 4261cbc7b03f1f56e95aeaf1492b8690fa5a253e) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c index da668a8d6abdf2..9d7a75670beee1 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c @@ -1823,6 +1823,11 @@ static void gfx_v12_0_constants_init(struct amdgpu_device *adev) gfx_v12_0_get_tcc_info(adev); adev->gfx.config.pa_sc_tile_steering_override = 0; + /* Set whether texture coordinate truncation is conformant. */ + tmp = RREG32_SOC15(GC, 0, regTA_CNTL2); + adev->gfx.config.ta_cntl2_truncate_coord_mode = + REG_GET_FIELD(tmp, TA_CNTL2, TRUNCATE_COORD_MODE); + /* XXX SH_MEM regs */ /* where to put LDS, scratch, GPUVM in FSA64 space */ mutex_lock(&adev->srbm_mutex); From cda6ab11c1a25a572857b9ea2ded4b7cb13c2895 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Thu, 30 Jul 2026 12:00:06 -0400 Subject: [PATCH 367/612] drm/amdgpu/gmc12.1: implement tlb inv semaphore Needed to properly lock the interface before using it. Cc: Sonny Jiang Reviewed-by: Sonny Jiang Signed-off-by: Alex Deucher (cherry picked from commit 8e37aa0bd56ba75801a6a21bed45f96372cd9fdc) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/gmc_v12_1.c | 46 ++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/gmc_v12_1.c b/drivers/gpu/drm/amd/amdgpu/gmc_v12_1.c index 855cd29cbffaac..bdf6788b85b58f 100644 --- a/drivers/gpu/drm/amd/amdgpu/gmc_v12_1.c +++ b/drivers/gpu/drm/amd/amdgpu/gmc_v12_1.c @@ -267,9 +267,24 @@ static bool gmc_v12_1_get_vmid_pasid_mapping_info(struct amdgpu_device *adev, * by the amdgpu vm/hsa code. */ +/** + * gmc_v12_1_use_invalidate_semaphore - judge whether to use semaphore + * + * @adev: amdgpu_device pointer + * @vmhub: vmhub type + * + */ +static bool gmc_v12_1_use_invalidate_semaphore(struct amdgpu_device *adev, + uint32_t vmhub) +{ + return ((!AMDGPU_IS_GFXHUB(vmhub)) && + (!amdgpu_sriov_vf(adev))); +} + static void gmc_v12_1_flush_vm_hub(struct amdgpu_device *adev, uint32_t vmid, unsigned int vmhub, uint32_t flush_type) { + bool use_semaphore = gmc_v12_1_use_invalidate_semaphore(adev, vmhub); struct amdgpu_vmhub *hub = &adev->vmhub[vmhub]; u32 inv_req = hub->vmhub_funcs->get_invalidate_req(vmid, flush_type); u32 tmp; @@ -283,6 +298,19 @@ static void gmc_v12_1_flush_vm_hub(struct amdgpu_device *adev, uint32_t vmid, spin_lock(&adev->gmc.invalidate_lock); + if (use_semaphore) { + for (i = 0; i < adev->usec_timeout; i++) { + /* a read return value of 1 means semaphore acuqire */ + tmp = RREG32_RLC_NO_KIQ(hub->vm_inv_eng0_sem + hub->eng_distance * eng, hub_ip); + if (tmp & 0x1) + break; + udelay(1); + } + + if (i >= adev->usec_timeout) + DRM_ERROR("Timeout waiting for sem acquire in VM flush!\n"); + } + WREG32_RLC_NO_KIQ(hub->vm_inv_eng0_req + hub->eng_distance * eng, inv_req, hub_ip); /* Wait for ACK with a delay.*/ @@ -296,6 +324,9 @@ static void gmc_v12_1_flush_vm_hub(struct amdgpu_device *adev, uint32_t vmid, udelay(1); } + if (use_semaphore) + WREG32_RLC_NO_KIQ(hub->vm_inv_eng0_sem + hub->eng_distance * eng, 0, hub_ip); + /* Issue additional private vm invalidation to MMHUB */ if (!AMDGPU_IS_GFXHUB(vmhub) && (hub->vm_l2_bank_select_reserved_cid2) && @@ -431,10 +462,17 @@ static void gmc_v12_1_flush_gpu_tlb_pasid(struct amdgpu_device *adev, static uint64_t gmc_v12_1_emit_flush_gpu_tlb(struct amdgpu_ring *ring, unsigned vmid, uint64_t pd_addr) { + bool use_semaphore = gmc_v12_1_use_invalidate_semaphore(ring->adev, ring->vm_hub); struct amdgpu_vmhub *hub = &ring->adev->vmhub[ring->vm_hub]; uint32_t req = hub->vmhub_funcs->get_invalidate_req(vmid, 0); unsigned eng = ring->vm_inv_eng; + if (use_semaphore) + /* a read return value of 1 means semaphore acuqire */ + amdgpu_ring_emit_reg_wait(ring, + hub->vm_inv_eng0_sem + + hub->eng_distance * eng, 0x1, 0x1); + amdgpu_ring_emit_wreg(ring, hub->ctx0_ptb_addr_lo32 + (hub->ctx_addr_distance * vmid), lower_32_bits(pd_addr)); @@ -449,6 +487,14 @@ static uint64_t gmc_v12_1_emit_flush_gpu_tlb(struct amdgpu_ring *ring, hub->eng_distance * eng, req, 1 << vmid); + if (use_semaphore) + /* + * add semaphore release after invalidation, + * write with 0 means semaphore release + */ + amdgpu_ring_emit_wreg(ring, hub->vm_inv_eng0_sem + + hub->eng_distance * eng, 0); + return pd_addr; } From 384c1d907eeb3be65591991fdda4e2ee2ec1f350 Mon Sep 17 00:00:00 2001 From: Vitaly Prosyak Date: Fri, 31 Jul 2026 20:18:20 -0400 Subject: [PATCH 368/612] drm/amdgpu: Fix lockdep false positive in amdgpu_lockdep_init MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move fs_reclaim_acquire() to before all lock acquisitions to eliminate false positive circular locking dependency warning. This is a 7.2-cycle regression fix suitable for stable backport. v3: Address Mikhail Gavrilov technical review: - Clarify that fs_reclaim_acquire/release pair only REGISTERS the fs_reclaim lock class, does NOT create a static edge when called with no locks held - Explain that the actual fs_reclaim -> notifier_lock edge is established at runtime during memory reclaim -> MMU notifier path - Add Cc: Arunpravin PaneerSelvam v2: Address Mikhail Gavrilov review feedback: - Fix author name: Michael -> Mikhail Gavrilov in all trailers - Add Fixes: tag to link regression to original commit - Add Tested-by: Mikhail Gavrilov (tested on RX 7900 XTX) Fixes: 1d0f5838b126 ("drm/amdgpu: Add lockdep annotations for lock ordering validation") Reported-by: Mikhail Gavrilov Analyzed-by: Mikhail Gavrilov Test-case-by: Mikhail Gavrilov Tested-by: Mikhail Gavrilov Suggested-by: Christian König Tested-by: Vitaly Prosyak Cc: Christian König Cc: Alex Deucher Cc: Arunpravin PaneerSelvam Signed-off-by: Vitaly Prosyak Acked-by: Arunpravin Paneer Selvam Signed-off-by: Alex Deucher (cherry picked from commit 70a1e9849e6ed12bb9f1c0faa24b0f1f9de601eb) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_lockdep.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_lockdep.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_lockdep.c index 61450af539a67d..d5787d848d0440 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_lockdep.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_lockdep.c @@ -135,6 +135,21 @@ int amdgpu_lockdep_init(void) lockdep_set_class(&locks->srbm_mutex, &amdgpu_srbm_lock_key); lockdep_set_class(&locks->grbm_idx_mutex, &amdgpu_grbm_lock_key); lockdep_set_class(&locks->mmio_idx_lock, &amdgpu_mmio_lock_key); + + /* + * Register fs_reclaim lock class FIRST, before taking any locks. + * + * This acquire/release pair does NOT create a static lockdep edge + * (no locks are held between acquire and release). It only registers + * the fs_reclaim lock class with lockdep. + * + * The actual fs_reclaim -> notifier_lock dependency is established at + * RUNTIME when memory reclaim invokes MMU notifiers: + * fs_reclaim (held by reclaim) -> notifier_lock (acquired in callback) + */ + fs_reclaim_acquire(GFP_KERNEL); + fs_reclaim_release(GFP_KERNEL); + /* * Take locks in the correct order to train lockdep. * This establishes the dependency chain. @@ -154,11 +169,6 @@ int amdgpu_lockdep_init(void) /* Level 6: Reset control lock */ mutex_lock(&locks->reset_lock); - /* - * Mark potential memory reclaim boundary. - * GPU operations might trigger memory allocation/reclaim. - */ - fs_reclaim_acquire(GFP_KERNEL); /* Level 7: SRBM register access */ mutex_lock(&locks->srbm_mutex); @@ -176,7 +186,6 @@ int amdgpu_lockdep_init(void) spin_unlock_irqrestore(&locks->mmio_idx_lock, flags); mutex_unlock(&locks->grbm_idx_mutex); mutex_unlock(&locks->srbm_mutex); - fs_reclaim_release(GFP_KERNEL); mutex_unlock(&locks->reset_lock); up_read(&reset_domain->sem); From b77a725e50c87c091f51affc8e46710badea0d49 Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Mon, 3 Aug 2026 17:19:46 +0800 Subject: [PATCH 369/612] drm/amdgpu/userq: serialize queue map against GPU reset Creating a user queue can race with a GPU reset. While recovery holds reset_domain->sem for write, MES is unresponsive, so the ADD_QUEUE from amdgpu_userq_map_helper() times out (-110) and an otherwise valid queue create fails: amdgpu: MES(0) failed to respond to msg=ADD_QUEUE [drm:mes_userq_map [amdgpu]] *ERROR* Failed to map queue in HW, err (-110) amdgpu: [drm] *ERROR* ... Failed to map Queue amdgpu: [drm] *ERROR* ... Failed to create usermode queue Take reset_domain->sem for read around the map so it runs only once MES is back up. This mirrors amdgpu_userq_cleanup() and honors the userq_mutex -> reset_domain->sem order; the reset path never takes userq_mutex, so there is no deadlock. Reviewed-by: Alex Deucher Signed-off-by: Jesse Zhang Signed-off-by: Alex Deucher (cherry picked from commit a8e151fe629c63b0eb08aa57de0d434614db3e1b) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index 3efe1ae273861e..b18d7872065625 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -700,7 +700,12 @@ amdgpu_userq_create(struct drm_file *filp, union drm_amdgpu_userq *args) if (!adev->userq_halt_for_enforce_isolation || ((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(). + */ + down_read(&adev->reset_domain->sem); r = amdgpu_userq_map_helper(queue); + up_read(&adev->reset_domain->sem); if (r) { drm_file_err(uq_mgr->file, "Failed to map Queue\n"); mutex_unlock(&uq_mgr->userq_mutex); From fd37f9dd5b5ab70a46fa7bc76623c0528d602b27 Mon Sep 17 00:00:00 2001 From: Candice Li Date: Thu, 30 Jul 2026 11:28:10 +0800 Subject: [PATCH 370/612] drm/amdgpu: reject oversized IBs with per-ring packet limits On GFX rings, amdgpu_cs_p2_ib() passed user-supplied ib_bytes through to ib->length_dw without a limit, while ring_emit_ib() encodes length into packet fields. Oversized values can corrupt adjacent control bits and destabilize command submission. Add a per-ring IB packet size limit helper and reject command submissions exceeding the corresponding dword limit before IB allocation. Use the documented 20-bit limit for GFX/compute/SDMA/VPE, and apply the MM fallback limit for other ring types. Signed-off-by: Candice Li Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher (cherry picked from commit 7f48fa2cf62e3fa6c9c3870aa74988f773247e52) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c | 27 +++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c index 5445f75741b53d..f8bf0f6b5097b0 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c @@ -42,6 +42,26 @@ #include "amdgpu_ras.h" #include "amdgpu_hmm.h" +/* + * Maximum IB length (dwords) for rings whose emit_ib packet format + * documents a 20-bit size field. + */ +#define AMDGPU_GFX_SDMA_IB_PACKET_SIZE_MAX_DW 0xFFFFF +#define AMDGPU_MM_IB_PACKET_SIZE_MAX_DW 0x7FFFF0 + +static u32 amdgpu_cs_ib_packet_size_max_dw(enum amdgpu_ring_type type) +{ + switch (type) { + case AMDGPU_RING_TYPE_GFX: + case AMDGPU_RING_TYPE_COMPUTE: + case AMDGPU_RING_TYPE_SDMA: + case AMDGPU_RING_TYPE_VPE: + return AMDGPU_GFX_SDMA_IB_PACKET_SIZE_MAX_DW; + default: + return AMDGPU_MM_IB_PACKET_SIZE_MAX_DW; + } +} + static int amdgpu_cs_parser_init(struct amdgpu_cs_parser *p, struct amdgpu_device *adev, struct drm_file *filp, @@ -345,7 +365,6 @@ static int amdgpu_cs_p2_ib(struct amdgpu_cs_parser *p, job = p->jobs[r]; ring = amdgpu_job_ring(job); - ib = &job->ibs[job->num_ibs++]; /* submissions to kernel queues are disabled */ if (ring->no_user_submission) @@ -374,6 +393,12 @@ static int amdgpu_cs_p2_ib(struct amdgpu_cs_parser *p, return -EINVAL; } + if (chunk_ib->ib_bytes / 4 > + amdgpu_cs_ib_packet_size_max_dw(ring->funcs->type)) + return -EINVAL; + + ib = &job->ibs[job->num_ibs++]; + if (chunk_ib->flags & AMDGPU_IB_FLAG_PREAMBLE) job->preamble_status |= AMDGPU_PREAMBLE_IB_PRESENT; From b88a5a43c070df46939de419663d4679b90caf2f Mon Sep 17 00:00:00 2001 From: Lijo Lazar Date: Wed, 29 Jul 2026 13:00:21 +0530 Subject: [PATCH 371/612] drm/amdgpu: Use virtual alloc during coredump The number of rings with outstanding fences can be large, requiring a bigger allocation. Such allocations don't need to be physically contiguous, so use kvzalloc/kvcalloc which fall back to vmalloc when contiguous memory isn't available. This also matches the existing kvfree used to free these allocations. Also guard the allocation with ring_count to avoid passing 0 size to allocation routines. Fixes: eea85914d15b ("drm/amdgpu: save ring content before resetting the device") Signed-off-by: Lijo Lazar Reviewed-by: Hawking Zhang Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher (cherry picked from commit 74d48bd6b7e12eba65de0507475b059966685ad1) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c index 6480a344006d31..8dafc84f423217 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c @@ -553,7 +553,7 @@ void amdgpu_coredump(struct amdgpu_device *adev, bool skip_vram_check, if (job && job->pasid) size += sizeof(struct amdgpu_coredump_ib_info) * job->num_ibs; - coredump = kzalloc(size, GFP_NOWAIT); + coredump = kvzalloc(size, GFP_NOWAIT); if (!coredump) return; @@ -596,8 +596,12 @@ void amdgpu_coredump(struct amdgpu_device *adev, bool skip_vram_check, total_ring_size += ring->ring_size; ring_count++; } - coredump->rings_dw = kzalloc(total_ring_size, GFP_NOWAIT); - coredump->rings = kcalloc(ring_count, sizeof(struct amdgpu_coredump_ring), GFP_NOWAIT); + if (ring_count) { + coredump->rings_dw = kvzalloc(total_ring_size, GFP_NOWAIT); + coredump->rings = kvcalloc(ring_count, + sizeof(struct amdgpu_coredump_ring), + GFP_NOWAIT); + } if (coredump->rings && coredump->rings_dw) { for (i = 0, off = 0, idx = 0; i < adev->num_rings && idx < ring_count; i++) { ring = adev->rings[i]; From e40ff9840fa8a633d149f0242df10cae5e518062 Mon Sep 17 00:00:00 2001 From: Lijo Lazar Date: Wed, 29 Jul 2026 16:23:58 +0530 Subject: [PATCH 372/612] drm/amdgpu: Allocate coredump ring buffers per ring Allocate each ring buffer separately. A single allocation summing all ring sizes can exceed the page allocator's MAX_ORDER limit and fail; per-ring buffers stay small enough to satisfy. The existing allocation style doesn't capture any ring data if the huge allocation fails. Splitting into multiple allocations helps to capture as much data as possible for the core dump. A failed ring is left with a NULL buffer and skipped when formatting. Fixes: eea85914d15b ("drm/amdgpu: save ring content before resetting the device") Signed-off-by: Lijo Lazar Assisted-by: Claude Code Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher (cherry picked from commit 3e8e92b7892a6377bef86106bfff1b98cf586aee) Cc: stable@vger.kernel.org --- .../gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c | 50 ++++++++++--------- .../gpu/drm/amd/amdgpu/amdgpu_dev_coredump.h | 3 +- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c index 8dafc84f423217..45dfe1c8a11b59 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c @@ -342,7 +342,7 @@ amdgpu_devcoredump_format(char *buffer, size_t count, struct amdgpu_coredump_inf struct amdgpu_ip_block *ip_block; struct amdgpu_ring *ring; int ver, i, j; - u32 ring_idx, off; + u32 ring_idx; bool sizing_pass; sizing_pass = buffer == NULL; @@ -442,7 +442,6 @@ amdgpu_devcoredump_format(char *buffer, size_t count, struct amdgpu_coredump_inf for (i = 0; i < coredump->num_rings; i++) { ring_idx = coredump->rings[i].ring_index; ring = coredump->adev->rings[ring_idx]; - off = coredump->rings[i].offset; drm_printf(&p, "ring name: %s\n", ring->name); drm_printf(&p, "Rptr: 0x%llx Wptr: 0x%llx RB mask: %x\n", @@ -451,12 +450,18 @@ amdgpu_devcoredump_format(char *buffer, size_t count, struct amdgpu_coredump_inf ring->buf_mask); drm_printf(&p, "Ring size in dwords: %d\n", ring->ring_size / 4); + + if (!coredump->rings[i].ring_dw) { + drm_printf(&p, "Ring contents unavailable\n"); + continue; + } + drm_printf(&p, "Ring contents\n"); drm_printf(&p, "Offset \t Value\n"); for (j = 0; j < ring->ring_size; j += 4) drm_printf(&p, "0x%x \t 0x%x\n", j, - coredump->rings_dw[off + j / 4]); + coredump->rings[i].ring_dw[j / 4]); } } @@ -497,10 +502,12 @@ amdgpu_devcoredump_read(char *buffer, loff_t offset, size_t count, static void amdgpu_devcoredump_free(void *data) { struct amdgpu_coredump_info *coredump = data; + u32 i; kvfree(coredump->formatted); + for (i = 0; i < coredump->num_rings; i++) + kvfree(coredump->rings[i].ring_dw); kvfree(coredump->rings); - kvfree(coredump->rings_dw); kvfree(data); } @@ -542,9 +549,9 @@ void amdgpu_coredump(struct amdgpu_device *adev, bool skip_vram_check, struct amdgpu_coredump_info *coredump; size_t size = sizeof(*coredump); struct drm_sched_job *s_job; - u64 total_ring_size, ring_count; + u64 ring_count; struct amdgpu_ring *ring; - int i, off, idx; + int i, idx; /* No need to generate a new coredump if there's one in progress already. */ if (work_busy(&adev->coredump_work)) @@ -584,7 +591,6 @@ void amdgpu_coredump(struct amdgpu_device *adev, bool skip_vram_check, /* Dump ring content if memory allocation succeeds. */ ring_count = 0; - total_ring_size = 0; for (i = 0; i < adev->num_rings; i++) { ring = adev->rings[i]; @@ -593,38 +599,34 @@ void amdgpu_coredump(struct amdgpu_device *adev, bool skip_vram_check, coredump->ring != ring) continue; - total_ring_size += ring->ring_size; ring_count++; } - if (ring_count) { - coredump->rings_dw = kvzalloc(total_ring_size, GFP_NOWAIT); + if (ring_count) coredump->rings = kvcalloc(ring_count, sizeof(struct amdgpu_coredump_ring), GFP_NOWAIT); - } - if (coredump->rings && coredump->rings_dw) { - for (i = 0, off = 0, idx = 0; i < adev->num_rings && idx < ring_count; i++) { + if (coredump->rings) { + for (i = 0, idx = 0; i < adev->num_rings && idx < ring_count; i++) { + struct amdgpu_coredump_ring *cdump_ring; + ring = adev->rings[i]; if (atomic_read(&ring->fence_drv.last_seq) == ring->fence_drv.sync_seq && coredump->ring != ring) continue; - coredump->rings[idx].ring_index = ring->idx; - coredump->rings[idx].rptr = amdgpu_ring_get_rptr(ring); - coredump->rings[idx].wptr = amdgpu_ring_get_wptr(ring); - coredump->rings[idx].offset = off; + cdump_ring = &coredump->rings[idx]; + + cdump_ring->ring_dw = kvzalloc(ring->ring_size, GFP_NOWAIT); + if (cdump_ring->ring_dw) + memcpy(cdump_ring->ring_dw, ring->ring, ring->ring_size); - memcpy(&coredump->rings_dw[off], ring->ring, ring->ring_size); - off += ring->ring_size / 4; + cdump_ring->ring_index = ring->idx; + cdump_ring->rptr = amdgpu_ring_get_rptr(ring); + cdump_ring->wptr = amdgpu_ring_get_wptr(ring); idx++; } coredump->num_rings = idx; - } else { - kvfree(coredump->rings_dw); - kvfree(coredump->rings); - coredump->rings_dw = NULL; - coredump->rings = NULL; } coredump->adev = adev; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.h index 63f27337c09ad8..cb874d8c33947e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.h @@ -34,8 +34,8 @@ struct amdgpu_coredump_ring { u64 rptr; u64 wptr; + u32 *ring_dw; u32 ring_index; - u32 offset; }; struct amdgpu_coredump_ib_info { @@ -53,7 +53,6 @@ struct amdgpu_coredump_info { struct amdgpu_ring *ring; struct amdgpu_coredump_ring *rings; - u32 *rings_dw; u32 num_rings; /* Readable form of coredevdump, generate once to speed up From 5227c2c77c3869cbbc680d5a61cb5d4574fd8e38 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Tue, 28 Jul 2026 11:20:38 -0400 Subject: [PATCH 373/612] drm/amdgpu/gmc12.1: fix MMHUB0 check in pasid tlb flush Check for mmhub0 rather than mmhub1. Looks like a copy paste typo. Fixes: d0c989a0aad3 ("drm/amd/amdgpu : Use the MES INV_TLBS API for tlb invalidation on gfx12_1") Cc: Shaoyun Liu Reviewed-by: Shaoyun Liu Signed-off-by: Alex Deucher (cherry picked from commit 0e8faef0aaa4d08f3f4f67ee7bb74e1babc8efc4) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/gmc_v12_1.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gmc_v12_1.c b/drivers/gpu/drm/amd/amdgpu/gmc_v12_1.c index bdf6788b85b58f..5fe43f7eab29d3 100644 --- a/drivers/gpu/drm/amd/amdgpu/gmc_v12_1.c +++ b/drivers/gpu/drm/amd/amdgpu/gmc_v12_1.c @@ -427,7 +427,7 @@ static void gmc_v12_1_flush_gpu_tlb_pasid(struct amdgpu_device *adev, if (all_hub) { /* invalidate mm_hub */ - if (test_bit(AMDGPU_MMHUB1(0), adev->vmhubs_mask)) { + if (test_bit(AMDGPU_MMHUB0(0), adev->vmhubs_mask)) { input.hub_id = AMDGPU_MMHUB0(0); adev->mes.funcs->invalidate_tlbs_pasid(&adev->mes, &input); } From ff209cd04845d819acc2fcc19b25904b4b7c3ea9 Mon Sep 17 00:00:00 2001 From: "Travis K. Bangs" Date: Mon, 3 Aug 2026 15:13:52 -0400 Subject: [PATCH 374/612] drm/amd: Disable DP audio spread spectrum for Cyan Skillfish The VBIOS for Cyan Skillfish devices (DCN201) indicates there is DisplayPort ref clock spread spectrum downspread, so the audio clock is corrected for it. However, the clock source in this hardware does not seem to actually be running with a clock downspread, so DisplayPort audio desyncs with video after several minutes. Ignore dprefclk SS downspread on CYAN_SKILLFISH2 asic. Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5429 Signed-off-by: Travis K. Bangs Signed-off-by: Alex Deucher (cherry picked from commit f3a2d86587432fdd9a6d401507b60a01153453c5) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 5 +++++ 1 file changed, 5 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 1820547b1dde80..941c1a31282498 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -2189,6 +2189,11 @@ static int amdgpu_dm_init(struct amdgpu_device *adev) init_data.flags.unify_link_enc_assignment = true; init_data.flags.usb4_bw_alloc_support = true; } + + /* DCN201 audio desyncs using DP SS */ + if (adev->apu_flags & AMD_APU_IS_CYAN_SKILLFISH2) + init_data.flags.ignore_dpref_ss = true; + retrieve_dmi_info(&adev->dm); if (adev->dm.edp0_on_dp1_quirk) init_data.flags.support_edp0_on_dp1 = true; From 5f08eee9c3fb1a5a70966612e0ff249900f8e77b Mon Sep 17 00:00:00 2001 From: Wang Jiang Date: Wed, 29 Jul 2026 18:26:26 +0800 Subject: [PATCH 375/612] drm/radeon: restore hardware polling in fence_is_signaled to fix performance regression MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 527ba26e50ec ("drm/radeon: delete radeon_fence_process in is_signaled, no deadlock") removed the hardware polling from radeon_fence_is_signaled() to fix a self-deadlock caused by wake_up_all(&rdev->fence_queue) being called with the fence queue lock held. However, removing the polling entirely causes significant performance regression (e.g. glxgears FPS drop) because the fence signaled check becomes purely passive — it only reads the cached last_seq without probing the GPU, so completed GPU work is not detected in time, causing unnecessary CPU stalls in sync-heavy workloads. Fix this by calling radeon_fence_activity() directly instead of radeon_fence_process(). radeon_fence_activity() reads the hardware fence counter and updates last_seq via atomic ops without calling wake_up_all(), thus avoiding the deadlock while restoring timely fence detection. Fixes: 527ba26e50ec ("drm/radeon: delete radeon_fence_process in is_signaled, no deadlock") Acked-by: Christian König Signed-off-by: Wang Jiang Signed-off-by: Alex Deucher (cherry picked from commit f59ad4cca219c7fdf934f712c5860ec5f5900fd7) Cc: stable@vger.kernel.org --- drivers/gpu/drm/radeon/radeon_fence.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/gpu/drm/radeon/radeon_fence.c b/drivers/gpu/drm/radeon/radeon_fence.c index 02a40e4750c7dd..299865569252d7 100644 --- a/drivers/gpu/drm/radeon/radeon_fence.c +++ b/drivers/gpu/drm/radeon/radeon_fence.c @@ -360,6 +360,13 @@ static bool radeon_fence_is_signaled(struct dma_fence *f) if (atomic64_read(&rdev->fence_drv[ring].last_seq) >= seq) return true; + if (down_read_trylock(&rdev->exclusive_lock)) { + radeon_fence_activity(rdev, ring); + up_read(&rdev->exclusive_lock); + + if (atomic64_read(&rdev->fence_drv[ring].last_seq) >= seq) + return true; + } return false; } From b96c529cd2551b78316a4afa3237b2ed96ba03c8 Mon Sep 17 00:00:00 2001 From: Asad Kamal Date: Thu, 30 Jul 2026 15:00:00 +0800 Subject: [PATCH 376/612] Revert "drm/amdgpu: fix aperture mapping leak" devres teardown is LIFO. The aperture devres node was registered after the DRM device node, so devres_release_all() unmaps the aperture before the DRM device release callback fires amdgpu_device_fini_sw(). IP sw_fini callbacks (e.g. vcn_v4_0_sw_fini) write to fw_shared through a pointer derived from aper_base_kaddr, causing a kernel page fault on probe failure / rollback: BUG: unable to handle page fault ... PMD 0 RIP: vcn_v4_0_sw_fini+0x7b/0x170 [amdgpu] Call Trace: amdgpu_device_fini_sw amdgpu_driver_release_kms devm_drm_dev_init_release devres_release_all This reverts commit d871e99879cb5fd1fa798b006b4888887e63a17a. Fixes: d871e99879cb ("drm/amdgpu: fix aperture mapping leak") Reported-by: Yuansheng Mao Signed-off-by: Asad Kamal Reviewed-by: Lijo Lazar Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher (cherry picked from commit 336e0cd576817ac64a4b394ca2b3680029f3e37f) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 2 ++ drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c | 35 ++++++++++++---------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index e5f26e5892bac7..bff3e06a756c52 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -4194,6 +4194,8 @@ static void amdgpu_device_unmap_mmio(struct amdgpu_device *adev) iounmap(adev->rmmio); adev->rmmio = NULL; + if (adev->mman.aper_base_kaddr) + iounmap(adev->mman.aper_base_kaddr); adev->mman.aper_base_kaddr = NULL; /* Memory manager related */ diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c index 5fc91f45db5b96..12a3b8bc58a4c9 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c @@ -2120,23 +2120,17 @@ int amdgpu_ttm_init(struct amdgpu_device *adev) /* Change the size here instead of the init above so only lpfn is affected */ amdgpu_ttm_disable_buffer_funcs(adev); #ifdef CONFIG_64BIT - if (adev->gmc.xgmi.connected_to_cpu) { - void *kaddr = devm_memremap(adev->dev, adev->gmc.aper_base, - adev->gmc.visible_vram_size, - MEMREMAP_WB); - if (IS_ERR(kaddr)) - return PTR_ERR(kaddr); - adev->mman.aper_base_kaddr = (__force void __iomem *)kaddr; - } else if (adev->gmc.is_app_apu) { +#ifdef CONFIG_X86 + if (adev->gmc.xgmi.connected_to_cpu) + adev->mman.aper_base_kaddr = ioremap_cache(adev->gmc.aper_base, + adev->gmc.visible_vram_size); + else if (adev->gmc.is_app_apu) DRM_DEBUG_DRIVER( "No need to ioremap when real vram size is 0\n"); - } else { - adev->mman.aper_base_kaddr = devm_ioremap_wc(adev->dev, - adev->gmc.aper_base, - adev->gmc.visible_vram_size); - if (!adev->mman.aper_base_kaddr) - return -ENOMEM; - } + else +#endif + adev->mman.aper_base_kaddr = ioremap_wc(adev->gmc.aper_base, + adev->gmc.visible_vram_size); #endif amdgpu_ttm_init_vram_resv_regions(adev); @@ -2265,6 +2259,8 @@ int amdgpu_ttm_init(struct amdgpu_device *adev) */ void amdgpu_ttm_fini(struct amdgpu_device *adev) { + int idx; + if (!adev->mman.initialized) return; @@ -2287,7 +2283,14 @@ void amdgpu_ttm_fini(struct amdgpu_device *adev) amdgpu_ttm_unmark_vram_reserved(adev, AMDGPU_RESV_FW_VRAM_USAGE); amdgpu_ttm_unmark_vram_reserved(adev, AMDGPU_RESV_DRV_VRAM_USAGE); - adev->mman.aper_base_kaddr = NULL; + if (drm_dev_enter(adev_to_drm(adev), &idx)) { + + if (adev->mman.aper_base_kaddr) + iounmap(adev->mman.aper_base_kaddr); + adev->mman.aper_base_kaddr = NULL; + + drm_dev_exit(idx); + } if (!adev->gmc.is_app_apu) amdgpu_vram_mgr_fini(adev); From 3141e3d61469bba2624a91c5e2407f110b33b29e Mon Sep 17 00:00:00 2001 From: Ray Wu Date: Wed, 5 Aug 2026 09:47:17 +0800 Subject: [PATCH 377/612] drm/amd/display: Check for tg ops in dce110_set_avmute MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some older DCE timing generators do not implement is_tg_enabled in their ops table. Calling it unconditionally when waiting for AV mute frames causes a NULL pointer dereference on Southern Islands dGPUs when turning the display off over HDMI. Check that tg and the required ops exist before waiting for frames. Fixes: 414da24137ac ("drm/amd/display: Add AV mute wait frames to dce110_set_avmute") Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5557 Tested-by: Viktor Jägersküpper Signed-off-by: Ray Wu Signed-off-by: Alex Deucher (cherry picked from commit 2686a0c0aaa07bec2e24131835cf27b5fd4935a5) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c index 53fffe58602f13..f5b3a2eff94a0b 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c @@ -1348,7 +1348,10 @@ void dce110_set_avmute(struct pipe_ctx *pipe_ctx, bool enable) * process the mute state, especially after link re-establishment * with HDMI 2.0 scrambling enabled. */ - if (enable && pipe_ctx->stream_res.tg->funcs->is_tg_enabled(pipe_ctx->stream_res.tg)) { + if (enable && pipe_ctx->stream_res.tg && + pipe_ctx->stream_res.tg->funcs->is_tg_enabled && + pipe_ctx->stream_res.tg->funcs->wait_for_state && + pipe_ctx->stream_res.tg->funcs->is_tg_enabled(pipe_ctx->stream_res.tg)) { int i; pipe_ctx->stream_res.tg->funcs->wait_for_state(pipe_ctx->stream_res.tg, CRTC_STATE_VACTIVE); From f9e5f51549000e2665e3b5e02ff876b9e09cfe95 Mon Sep 17 00:00:00 2001 From: Asad Kamal Date: Thu, 30 Jul 2026 15:00:00 +0800 Subject: [PATCH 378/612] drm/amdgpu: fix aperture iounmap skipped on device removal amdgpu_pci_remove() calls drm_dev_unplug() before invoking the fini routines. After drm_dev_unplug() the drm_dev_enter() guard in amdgpu_ttm_fini() always returns false, so iounmap() for aper_base_kaddr is silently skipped. On connected_to_cpu hardware ioremap_cache() maps the aperture as WB; when iounmap() is skipped the stale WB PAT entry persists. On reload IP discovery's memremap(MEMREMAP_WC) on the same aperture range hits a WB/WC conflict, producing an ioremap error and failing re-probe. Remove the drm_dev_enter() guard and call iounmap() unconditionally. The aperture mapping is plain MMIO and does not require device-presence protection. Surprise-removal cleanup of aper_base_kaddr is already handled unconditionally by amdgpu_device_unmap_mmio(). Fixes: 62d5f9f7110a ("drm/amdgpu: Unmap MMIO mappings when device is not unplugged") Signed-off-by: Asad Kamal Reviewed-by: Lijo Lazar Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher (cherry picked from commit fb3f68af9f6fce9343a2bd13b4d68a1c02d283df) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c index 12a3b8bc58a4c9..eaa86e32912e59 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c @@ -2259,8 +2259,6 @@ int amdgpu_ttm_init(struct amdgpu_device *adev) */ void amdgpu_ttm_fini(struct amdgpu_device *adev) { - int idx; - if (!adev->mman.initialized) return; @@ -2283,13 +2281,9 @@ void amdgpu_ttm_fini(struct amdgpu_device *adev) amdgpu_ttm_unmark_vram_reserved(adev, AMDGPU_RESV_FW_VRAM_USAGE); amdgpu_ttm_unmark_vram_reserved(adev, AMDGPU_RESV_DRV_VRAM_USAGE); - if (drm_dev_enter(adev_to_drm(adev), &idx)) { - - if (adev->mman.aper_base_kaddr) - iounmap(adev->mman.aper_base_kaddr); + if (adev->mman.aper_base_kaddr) { + iounmap(adev->mman.aper_base_kaddr); adev->mman.aper_base_kaddr = NULL; - - drm_dev_exit(idx); } if (!adev->gmc.is_app_apu) From 8099bd08646544730ba8c9fc7c0ebf6773206e19 Mon Sep 17 00:00:00 2001 From: David Weber Date: Thu, 30 Jul 2026 05:32:00 +0200 Subject: [PATCH 379/612] drm/amd/display: allow self-refresh exit while entry is blocked amdgpu_dm_crtc_set_static_screen_optimze() maps sso_enable to the Replay and PSR1 vsync events. allow_sr_entry is an entry gate, but the helper currently applies it to both directions. A non-fast update clears allow_sr_entry. During a modeset, a separate hardware-programming event keeps self-refresh blocked while the stream is reprogrammed. If vblank is enabled before the entry delay expires, the ISM calls the helper with sso_enable false. The early return drops the disable request, so the vsync events are not set. After enough fast commits, allow_sr_entry becomes true and the hardware-programming event can be cleared. Since the vblank reference remains held, there is no further zero-to-one vblank transition to restore the missing vsync events. Replay or PSR1 can then become active while vblank is still enabled. Gate only requests that enable static-screen optimization. Always process disable requests so a vblank requestor keeps Replay and PSR1 blocked. On a Phoenix system, repeated SDDM-to-VT handoffs produced stuck flips followed by flip_done and commit-wait timeouts. The timeout was not observed with this change applied. Fixes: 3c108046e1d6 ("drm/amd/display: Add power module on Linux") Assisted-by: Codex:gpt-5.6-sol Assisted-by: Claude:opus-5 Signed-off-by: David Weber Reviewed-by: Leo Li Signed-off-by: Alex Deucher (cherry picked from commit bd0c00982166d34ed47b11ba29cd8bf2950cc2e2) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c index 95d3da3c4199b4..06598273d48199 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c @@ -119,7 +119,11 @@ void amdgpu_dm_crtc_set_static_screen_optimze( struct dc_link *link = stream->link; bool set_vsync_event = !sso_enable; - if (!allow_sr_entry) + /* + * allow_sr_entry gates only entry. A disable request must still set + * the vsync events to force Replay and PSR1 out and keep them blocked. + */ + if (sso_enable && !allow_sr_entry) return; amdgpu_dm_replay_set_event(dm, stream, From 8444d66aa6b6e7fe0a26fa1a00a11cb4d0523783 Mon Sep 17 00:00:00 2001 From: Hongyan Xu Date: Thu, 6 Aug 2026 14:06:13 +0800 Subject: [PATCH 380/612] watchdog: at91sam9_wdt: prevent timer rearm during teardown at91_ping() rearms the watchdog timer from its callback. timer_delete() neither waits for a running callback nor prevents it from rearming the timer, so probe failure or driver removal can leave the timer accessing the devm-allocated at91wdt after it has been freed. Use timer_shutdown_sync() on both teardown paths. It waits for a running callback and rejects any attempt by the callback to rearm the timer. Fixes: 5161b31dc39a ("watchdog: at91sam9_wdt: better watchdog support") Signed-off-by: Hongyan Xu Link: https://lore.kernel.org/r/20260806060613.1830-1-getshell@seu.edu.cn Signed-off-by: Guenter Roeck --- drivers/watchdog/at91sam9_wdt.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/watchdog/at91sam9_wdt.c b/drivers/watchdog/at91sam9_wdt.c index aba66b8e9d0339..80ba04df54adfd 100644 --- a/drivers/watchdog/at91sam9_wdt.c +++ b/drivers/watchdog/at91sam9_wdt.c @@ -242,7 +242,7 @@ static int at91_wdt_init(struct platform_device *pdev, struct at91wdt *wdt) return 0; out_stop_timer: - timer_delete(&wdt->timer); + timer_shutdown_sync(&wdt->timer); return err; } @@ -378,7 +378,7 @@ static void at91wdt_remove(struct platform_device *pdev) watchdog_unregister_device(&wdt->wdd); pr_warn("I quit now, hardware will probably reboot!\n"); - timer_delete(&wdt->timer); + timer_shutdown_sync(&wdt->timer); } #if defined(CONFIG_OF) From 7a3c0289c3c8eb4607dff448ae9ff9f902c813af Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Sun, 2 Aug 2026 04:17:59 +0200 Subject: [PATCH 381/612] rqspinlock: Reset tail when preserving queue on deadlock Currently, the destruction of the waiter queue is suppressed for rqspinlock in cases where a deadlock is detected. Deadlock checks happen relatively frequently (on entry for AA, within 1ms for ABBA), and waiter threads may not be involved in locking scenarios involving deadlocks. Thus, it is useful to not flush the queue and let other waiters take a stab at acquiring the lock after we detect a deadlock and exit. However, we need to follow the same logic as what we did previously for the waitq_timeout label: reset the tail, and if we cannot, signal the next waiter appropriately. In case of deadlocks, this signal would just mark the MCS node as unlocked, and in case of timeouts, it would signal RES_TIMEOUT_VAL. The difference thus is in the value propagated, which decides whether the queue remains active or gets flushed. Not doing the tail reset, and waiting for the next waiter can lead to cases where we are the final waiter, and thus no next waiter arrives, leading to intermittent stalls in this path. Once the next waiter does join, we will be unblocked. In the theoretical case when the next waiter never joins, we risk stalling indefinitely. This can only happen for ABBA deadlocks, since entry into the wait queue is guarded with AA checks. A precise sequence of executions leading up to this scenario can be: CPU 0 holds lock A. CPU 1 holds lock B. CPU 2 attempts lock B, becomes the pending waiter for B. CPU 0 attempts lock B. B has locked+pending bits set, thus CPU 0 queues. CPU 1 attempts lock A. CPU 0 detects an ABBA deadlock. Once deadlock detection happens for CPU 0, it will sit waiting for the next waiter in the queue to populate node->next, which will experience delays until such a waiter arrives. Fix this by adjusting the logic for the check for deadlocks preceding the waitq_timeout label. It would make sense to consolidate code for both cases and use 'ret' to distinguish the value being propagated, but that is left as an exercise for a future refactoring task to avoid diff noise in this patch. Fixes: 7bd6e5ce5be6 ("rqspinlock: Disable queue destruction for deadlocks") Signed-off-by: Kumar Kartikeya Dwivedi Link: https://patch.msgid.link/20260802021759.1139457-1-memxor@gmail.com Signed-off-by: Eduard Zingerman --- kernel/bpf/rqspinlock.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/kernel/bpf/rqspinlock.c b/kernel/bpf/rqspinlock.c index e4e338cdb437c2..2129defc4a9a7d 100644 --- a/kernel/bpf/rqspinlock.c +++ b/kernel/bpf/rqspinlock.c @@ -572,9 +572,10 @@ int __lockfunc resilient_queued_spin_lock_slowpath(rqspinlock_t *lock, u32 val) /* Disable queue destruction when we detect deadlocks. */ if (ret == -EDEADLK) { - if (!next) + if (!try_cmpxchg_tail(lock, tail, 0)) { next = smp_cond_load_relaxed(&node->next, (VAL)); - arch_mcs_spin_unlock_contended(&next->locked); + arch_mcs_spin_unlock_contended(&next->locked); + } goto err_release_node; } From 36c4d73ce05d1d8896c2669eb0730d35a02a2ec1 Mon Sep 17 00:00:00 2001 From: Wilken Gottwalt Date: Wed, 5 Aug 2026 07:19:20 +0000 Subject: [PATCH 382/612] hwmon: (corsair-psu) fix possible out-of-bounds access on missing string termination In theory it could be possible that the REPLY_SIZE sized buffers for holding the vendor and product strings could be end up missing the null termination (for example by malicious hardware built on purpose) required by the seq_printf() call. That limits the debugfs printf calls to a maximum string length of REPLY_SIZE. Fixes: d115b51e0e567 ("hwmon: add Corsair PSU HID controller driver") Signed-off-by: Wilken Gottwalt Link: https://lore.kernel.org/r/anLj9gPWRoRDbQBV@monster.localdomain Signed-off-by: Guenter Roeck --- drivers/hwmon/corsair-psu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/corsair-psu.c b/drivers/hwmon/corsair-psu.c index ce958cdaef58a3..3c01ae3fc4af22 100644 --- a/drivers/hwmon/corsair-psu.c +++ b/drivers/hwmon/corsair-psu.c @@ -701,7 +701,7 @@ static int vendor_show(struct seq_file *seqf, void *unused) { struct corsairpsu_data *priv = seqf->private; - seq_printf(seqf, "%s\n", priv->vendor); + seq_printf(seqf, "%.*s\n", REPLY_SIZE, priv->vendor); return 0; } @@ -711,7 +711,7 @@ static int product_show(struct seq_file *seqf, void *unused) { struct corsairpsu_data *priv = seqf->private; - seq_printf(seqf, "%s\n", priv->product); + seq_printf(seqf, "%.*s\n", REPLY_SIZE, priv->product); return 0; } From fddb5ceaf901b050ed2a1a7deeecbf97e003435a Mon Sep 17 00:00:00 2001 From: Qingshuang Fu Date: Wed, 5 Aug 2026 14:16:45 +0800 Subject: [PATCH 383/612] hwmon: (ads7828) Fix external VREF regulator handling The driver currently has two issues with the external VREF regulator handling in ads7828_probe(): 1. All errors from devm_regulator_get_optional() are ignored, causing the driver to incorrectly fall back to internal VREF even for transient errors like -EPROBE_DEFER or genuine failures like -ENOMEM. 2. The external regulator is never enabled. The driver calls regulator_get_voltage() without first calling regulator_enable(), so the VREF pin may remain unpowered if the regulator is not configured as always-on. Fix both issues by switching to devm_regulator_get_enable_read_voltage(), which handles regulator get, enable, and voltage read in one call. Only -ENODEV (no regulator specified in device tree) should trigger the fallback to internal VREF. All other errors are propagated to the caller. Fixes: a8ddfea09566 ("hwmon: (ads7828) Accept optional parameters from device tree") Signed-off-by: Qingshuang Fu Link: https://lore.kernel.org/r/20260805061645.1331652-1-fffsqian@163.com Signed-off-by: Guenter Roeck --- drivers/hwmon/ads7828.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/hwmon/ads7828.c b/drivers/hwmon/ads7828.c index 149cfcec78dcc2..f8f2f621dfeafe 100644 --- a/drivers/hwmon/ads7828.c +++ b/drivers/hwmon/ads7828.c @@ -106,12 +106,11 @@ static int ads7828_probe(struct i2c_client *client) struct ads7828_data *data; struct device *hwmon_dev; unsigned int vref_mv = ADS7828_INT_VREF_MV; - unsigned int vref_uv; + int vref_uv; bool diff_input = false; bool ext_vref = false; unsigned int regval; enum ads7828_chips chip; - struct regulator *reg; data = devm_kzalloc(dev, sizeof(struct ads7828_data), GFP_KERNEL); if (!data) @@ -125,9 +124,11 @@ static int ads7828_probe(struct i2c_client *client) } else if (dev->of_node) { diff_input = of_property_read_bool(dev->of_node, "ti,differential-input"); - reg = devm_regulator_get_optional(dev, "vref"); - if (!IS_ERR(reg)) { - vref_uv = regulator_get_voltage(reg); + vref_uv = devm_regulator_get_enable_read_voltage(dev, "vref"); + if (vref_uv < 0) { + if (vref_uv != -ENODEV) + return vref_uv; + } else { vref_mv = DIV_ROUND_CLOSEST(vref_uv, 1000); if (vref_mv < ADS7828_EXT_VREF_MV_MIN || vref_mv > ADS7828_EXT_VREF_MV_MAX) From edd11a94335747423569500a194c6eaa915f2963 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 4 Aug 2026 15:42:42 -0700 Subject: [PATCH 384/612] hwmon: (ltc4282) Avoid overflow in maximum power calculation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit During device initialization in ltc4282_set_max_limits(), the calculation of the maximum power limit can suffer from a 32-bit integer overflow. static int ltc4282_set_max_limits(struct ltc4282_state *st) { ... st->power_max = DIV_ROUND_CLOSEST(st->vsense_max * DECA * MILLI, st->rsense) * st->vfs_out; ... } The result of DIV_ROUND_CLOSEST() evaluates to a 32-bit unsigned integer on 32-bit architectures. This result is then multiplied by st->vfs_out, which is a 16-bit unsigned integer. According to C promotion rules, since both operands are 32-bit or smaller, the multiplication is performed in 32-bit precision. If the device is configured with a low sense resistor value via the device tree (for example, 100 nano-ohms, resulting in st->rsense = 1) and the voltage is high, the division result can reach 343,750,000 and st->vfs_out can be 33,280. The product of these values is approximately 11.44 trillion, which exceeds the maximum capacity of a 32-bit integer and overflows before being stored in st->power_max. This overflow causes a truncated value to be assigned to st->power_max and written to the hardware limit register. An incorrect maximum power limit can trigger spurious power-bad faults or alarms, which may lead to the shutdown of the monitored power rail. Avoid the problem by calculating and storing the maximum power using 64-bit variables. Reported-by: Sashiko Fixes: cbc29538dbf7d ("hwmon: Add driver for LTC4282") Cc: Nuno Sa Reviewed-by: Nuno Sá Signed-off-by: Guenter Roeck --- drivers/hwmon/ltc4282.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/hwmon/ltc4282.c b/drivers/hwmon/ltc4282.c index cc698803f8bf8b..bb7f6727c44d2a 100644 --- a/drivers/hwmon/ltc4282.c +++ b/drivers/hwmon/ltc4282.c @@ -137,7 +137,7 @@ struct ltc4282_state { */ struct ltc4282_cache in0_1_cache[LTC4282_CHAN_VGPIO]; u32 vsense_max; - long power_max; + s64 power_max; u32 rsense; u16 vdd; u16 vfs_out; @@ -613,13 +613,12 @@ static int ltc4282_read(struct device *dev, enum hwmon_sensor_types type, } static int ltc4282_write_power_byte(const struct ltc4282_state *st, u32 reg, - long val) + s64 val) { u32 power; u64 temp; - if (val > st->power_max) - val = st->power_max; + val = clamp(val, 0, st->power_max); temp = val * int_pow(U8_MAX, 2) * st->rsense; power = DIV64_U64_ROUND_CLOSEST(temp, @@ -629,7 +628,7 @@ static int ltc4282_write_power_byte(const struct ltc4282_state *st, u32 reg, } static int ltc4282_write_power_word(const struct ltc4282_state *st, u32 reg, - long val) + u64 val) { u64 temp = int_pow(U16_MAX, 2) * st->rsense, temp_2; __be16 __raw; @@ -1222,7 +1221,8 @@ static int ltc4282_set_max_limits(struct ltc4282_state *st) return ret; /* Power is given by ISENSE * Vout. */ - st->power_max = DIV_ROUND_CLOSEST(st->vsense_max * DECA * MILLI, st->rsense) * st->vfs_out; + st->power_max = DIV_ROUND_CLOSEST_ULL((u64)st->vsense_max * DECA * MILLI, + st->rsense) * st->vfs_out; ret = ltc4282_write_power_byte(st, LTC4282_POWER_MAX, st->power_max); if (ret) return ret; From e253dd5f9f6d875a317895bf43ec9534ed7523cb Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 4 Aug 2026 16:26:05 -0700 Subject: [PATCH 385/612] hwmon: (ltc4282) Clamp negative current limits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a negative value is passed to ltc4282_write_curr(), the signed long val is cast directly to u64: drivers/hwmon/ltc4282.c:ltc4282_write_curr() { /* need to pass it in millivolt */ u32 in = DIV_ROUND_CLOSEST_ULL((u64)val * st->rsense, DECA * MICRO); ... } This cast converts negative inputs into large positive values. The subsequent division result overflows the u32 in variable, truncating to a pseudo-random positive value. When this is passed to ltc4282_write_voltage_byte(), it is clamped to the maximum limit instead of zero. Clamp val to 0 and to the maximum supported upper limit before the cast and assign the result to a 64-bit temporary variable before the division to avoid the underflow and an also possible overflow. Reported-by: Sashiko Fixes: cbc29538dbf7d ("hwmon: Add driver for LTC4282") Cc: Nuno Sa Reviewed-by: Nuno Sá Signed-off-by: Guenter Roeck --- drivers/hwmon/ltc4282.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/ltc4282.c b/drivers/hwmon/ltc4282.c index bb7f6727c44d2a..bb1bcb3690163d 100644 --- a/drivers/hwmon/ltc4282.c +++ b/drivers/hwmon/ltc4282.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -929,8 +930,11 @@ static int ltc4282_curr_reset_hist(struct ltc4282_state *st) static int ltc4282_write_curr(struct ltc4282_state *st, u32 attr, long val) { + s32 ulimit = min_t(u64, INT_MAX, + div_u64((u64)INT_MAX * DECA * MICRO, st->rsense)); + u64 val64 = clamp(val, 0, ulimit); /* need to pass it in millivolt */ - u32 in = DIV_ROUND_CLOSEST_ULL((u64)val * st->rsense, DECA * MICRO); + u32 in = DIV_ROUND_CLOSEST_ULL(val64 * st->rsense, DECA * MICRO); switch (attr) { case hwmon_curr_max: From 335698fd7f60b6707b21fda725f97f35fa956b07 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 4 Aug 2026 17:30:42 -0700 Subject: [PATCH 386/612] hwmon: (ltc4282) Fix parsing adi,current-limit-sense-microvolt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ltc4282_parse_dt() evaluates the wrong variable when parsing the current limit. When the adi,current-limit-sense-microvolt property is parsed into st->vsense_max, the subsequent switch statement evaluates the unrelated val variable instead of st->vsense_max: drivers/hwmon/ltc4282.c:ltc4282_parse_dt() { ... ret = device_property_read_u32(dev, "adi,current-limit-sense-microvolt", &st->vsense_max); if (!ret) { int reg_val; switch (val) { case 12500: reg_val = 0; break; ... } Because val holds a small integer representing vin_mode (from 0 to 3), it never matches any of the valid current limit cases. This causes it to always fall through to the default error case, return -EINVAL, and aborts probe initialization for any device tree using this property. Validate st->vsense_max instead to fix the problem. Reported-by: Sashiko Fixes: cbc29538dbf7d ("hwmon: Add driver for LTC4282") Cc: Nuno Sa Reviewed-by: Nuno Sá Signed-off-by: Guenter Roeck --- drivers/hwmon/ltc4282.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/ltc4282.c b/drivers/hwmon/ltc4282.c index bb1bcb3690163d..b1675dc5b3c7fd 100644 --- a/drivers/hwmon/ltc4282.c +++ b/drivers/hwmon/ltc4282.c @@ -1394,7 +1394,7 @@ static int ltc4282_setup(struct ltc4282_state *st, struct device *dev) if (!ret) { int reg_val; - switch (val) { + switch (st->vsense_max) { case 12500: reg_val = 0; break; From 2da6050809d437a805e7a3aa22398a15073b0234 Mon Sep 17 00:00:00 2001 From: Ali Ahmet Memis Date: Thu, 6 Aug 2026 14:21:39 +0000 Subject: [PATCH 387/612] hwmon: (corsair-psu) serialize debugfs access against hwmon corsairpsu_request() sends a rail select command and then the actual read as two separate transfers, both going through the single shared cmd_buffer and wait_completion in corsairpsu_usb_cmd(). The hwmon core serializes its own callers, but the debugfs files call corsairpsu_get_value() directly and never take that lock, so a debugfs read can land between another reader's rail select and its value read. The result is a value from the wrong rail reported as the right one, because corsairpsu_usb_cmd() only checks the command echo and both transfers echo the command it expects. It can also make a caller consume the reply meant for the other one, since raw_event() writes into the shared buffer and completes whoever happens to be waiting. Locking was dropped in commit 4207069edbf0 ("hwmon: (corsair-psu) Rely on subsystem locking") on the grounds that the subsystem serializes for us, which holds for sysfs but not for these files. Take the same lock in the debugfs paths that issue commands, using the guard added in commit d1e720c7328e ("hwmon: Support guard() and scoped_guard for subsystem locks"). The lock cannot go into corsairpsu_request() itself: the hwmon core already holds it across ->read, so every sysfs read would deadlock. vendor_show() and product_show() only print strings cached during probe and issue no command, and corsairpsu_get_criticals() and corsairpsu_check_cmd_support() run before either interface is registered, so none of them need it. Fixes: 4207069edbf0 ("hwmon: (corsair-psu) Rely on subsystem locking") Signed-off-by: Ali Ahmet Memis Tested-by: Wilken Gottwalt Link: https://lore.kernel.org/r/20260806142139.168611-1-ali@iusegentoo.com Signed-off-by: Guenter Roeck --- drivers/hwmon/corsair-psu.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/hwmon/corsair-psu.c b/drivers/hwmon/corsair-psu.c index 3c01ae3fc4af22..ebcaddf337d807 100644 --- a/drivers/hwmon/corsair-psu.c +++ b/drivers/hwmon/corsair-psu.c @@ -664,6 +664,8 @@ static void print_uptime(struct seq_file *seqf, u8 cmd) long val; int ret; + guard(hwmon_lock)(priv->hwmon_dev); + ret = corsairpsu_get_value(priv, cmd, 0, &val); if (ret < 0) { seq_puts(seqf, "N/A\n"); @@ -723,6 +725,8 @@ static int ocpmode_show(struct seq_file *seqf, void *unused) long val; int ret; + guard(hwmon_lock)(priv->hwmon_dev); + /* * The rail mode is switchable on the fly. The RAW interface can be used for this. But it * will not be included here, because I consider it somewhat dangerous for the health of the From c6c4234928d2eb4f61fecb61067e612d9bdbd2ff Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Mon, 3 Aug 2026 20:48:11 -0700 Subject: [PATCH 388/612] hwmon: (corsair-psu) Fix linear11 calculation In corsairpsu_linear11_to_int(), the mantissa is extracted using bitwise operations and cast to s16 before being shifted left: static int corsairpsu_linear11_to_int(const u16 val, const int scale) { ... const int mant = (((s16)(val & 0x7ff)) << 5) >> 5; ... } Due to C integer promotion rules, the masked value (which is always positive) is promoted to a 32-bit integer before the left shift. As a result, the sign bit is never extended to bit 31 of the promoted integer. When the device hardware reports a negative temperature in Linear11 format (such as an ambient temperature probe reporting sub-zero), the negative mantissa is parsed incorrectly as a massive positive value. For example, -1 becomes 2047, which scales to 2047 degrees Celsius. Fix the problem by type casting the result of the left shift operation to s16. Another problem is left-shifting of negative values. In C, the result of left-shifting negative values is undefined. Use a multiplication instead to avoid the problem. Also use a local s64 variable to store temporary results, change the return value type from int to long, and clamp the final value to LONG_MIN and LONG_MAX to avoid under- and overflow issues while retaining as much information as possible. Reported-by: Sashiko Cc: Wilken Gottwalt Signed-off-by: Guenter Roeck Tested-by: Wilken Gottwalt Link: https://lore.kernel.org/r/20260804034811.2385506-1-linux@roeck-us.net Signed-off-by: Guenter Roeck --- drivers/hwmon/corsair-psu.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/drivers/hwmon/corsair-psu.c b/drivers/hwmon/corsair-psu.c index ebcaddf337d807..0cf072f86acbfa 100644 --- a/drivers/hwmon/corsair-psu.c +++ b/drivers/hwmon/corsair-psu.c @@ -137,13 +137,18 @@ struct corsairpsu_data { }; /* some values are SMBus LINEAR11 data which need a conversion */ -static int corsairpsu_linear11_to_int(const u16 val, const int scale) +static long corsairpsu_linear11_to_long(const u16 val, const int scale) { const int exp = ((s16)val) >> 11; - const int mant = (((s16)(val & 0x7ff)) << 5) >> 5; - const int result = mant * scale; + const int mant = ((s16)((val & 0x7ff) << 5)) >> 5; + s64 result = mant * scale; - return (exp >= 0) ? (result << exp) : (result >> -exp); + if (exp >= 0) + result *= (int)(1UL << exp); + else + result >>= -exp; + + return clamp(result, LONG_MIN, LONG_MAX); } /* the micro-controller uses percentage values to control pwm */ @@ -263,13 +268,13 @@ static int corsairpsu_get_value(struct corsairpsu_data *priv, u8 cmd, u8 rail, l case PSU_CMD_RAIL_AMPS: case PSU_CMD_TEMP0: case PSU_CMD_TEMP1: - *val = corsairpsu_linear11_to_int(tmp & 0xFFFF, 1000); + *val = corsairpsu_linear11_to_long(tmp & 0xFFFF, 1000); break; case PSU_CMD_FAN: - *val = corsairpsu_linear11_to_int(tmp & 0xFFFF, 1); + *val = corsairpsu_linear11_to_long(tmp & 0xFFFF, 1); break; case PSU_CMD_FAN_PWM_ENABLE: - *val = corsairpsu_linear11_to_int(tmp & 0xFFFF, 1); + *val = corsairpsu_linear11_to_long(tmp & 0xFFFF, 1); /* * 0 = automatic mode, means the micro-controller controls the fan using a plan * which can be modified, but changing this plan is not supported by this @@ -283,12 +288,12 @@ static int corsairpsu_get_value(struct corsairpsu_data *priv, u8 cmd, u8 rail, l *val = 2; break; case PSU_CMD_FAN_PWM: - *val = corsairpsu_linear11_to_int(tmp & 0xFFFF, 1); + *val = corsairpsu_linear11_to_long(tmp & 0xFFFF, 1); *val = corsairpsu_dutycycle_to_pwm(*val); break; case PSU_CMD_RAIL_WATTS: case PSU_CMD_TOTAL_WATTS: - *val = corsairpsu_linear11_to_int(tmp & 0xFFFF, 1000000); + *val = corsairpsu_linear11_to_long(tmp & 0xFFFF, 1000000); break; case PSU_CMD_TOTAL_UPTIME: case PSU_CMD_UPTIME: From 5ec42d57655c690234c14aece6dd3f209778c1d8 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Mon, 13 Jul 2026 08:25:49 -0700 Subject: [PATCH 389/612] KVM: x86/mmu: WARN and clear role.invalid when creating a child shadow page Explicitly clear role.invalid when deriving a child shadow page's role from its parent to harden against bugs elsewhere in KVM, as violating KVM's invariant that invalid pages are NOT on the list of active MMU pages leads to use-after-free due to __kvm_mmu_prepare_zap_page() using list_add() instead of list_move() when processing an invalid shadow page, i.e. makes a bad situation far worse. Yell loudly if the parent is invalid, as it means KVM has missed a validity check, i.e. KVM is attempting to map memory using an invalid/obsolete root, but continue on as the child is otherwise still a valid shadow page. ================================================================== BUG: KASAN: slab-use-after-free in __kvm_mmu_get_shadow_page+0x1817/0x1860 [kvm] Write of size 8 at addr ff11000153dd1368 by task repro/853 CPU: 1 UID: 1000 PID: 853 Comm: repro Not tainted 7.2.0-rc2-3aec122bdcaf-next-vm #5 PREEMPT Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 0.0.0 02/06/2015 Call Trace: dump_stack_lvl+0x4b/0x70 print_report+0x153/0x49c kasan_report+0xbc/0xf0 __kvm_mmu_get_shadow_page+0x1817/0x1860 [kvm] mmu_alloc_root+0x141/0x320 [kvm] kvm_mmu_load+0x612/0x20f0 [kvm] kvm_arch_vcpu_ioctl_run+0x3dd5/0x6150 [kvm] kvm_vcpu_ioctl+0x5e4/0x10d0 [kvm] __x64_sys_ioctl+0x131/0x1b0 do_syscall_64+0x67/0x5f0 entry_SYSCALL_64_after_hwframe+0x4b/0x53 Allocated by task 853: kasan_save_stack+0x20/0x40 kasan_save_track+0x14/0x30 __kasan_slab_alloc+0x5f/0x70 kmem_cache_alloc_noprof+0xfe/0x2e0 __kvm_mmu_topup_memory_cache+0x135/0x530 [kvm] paging64_page_fault+0x318/0x1e30 [kvm] kvm_mmu_do_page_fault+0x21d/0x630 [kvm] kvm_mmu_page_fault+0x18c/0x17b0 [kvm] kvm_arch_vcpu_ioctl_run+0x1f35/0x6150 [kvm] kvm_vcpu_ioctl+0x5e4/0x10d0 [kvm] __x64_sys_ioctl+0x131/0x1b0 do_syscall_64+0x67/0x5f0 entry_SYSCALL_64_after_hwframe+0x4b/0x53 Freed by task 853: kasan_save_stack+0x20/0x40 kasan_save_track+0x14/0x30 kasan_save_free_info+0x3b/0x60 __kasan_slab_free+0x43/0x70 kmem_cache_free+0xe2/0x400 kvm_mmu_commit_zap_page.part.0+0x1e2/0x310 [kvm] kvm_mmu_free_roots+0x283/0x560 [kvm] kvm_arch_vcpu_ioctl_run+0x33c8/0x6150 [kvm] kvm_vcpu_ioctl+0x5e4/0x10d0 [kvm] __x64_sys_ioctl+0x131/0x1b0 do_syscall_64+0x67/0x5f0 entry_SYSCALL_64_after_hwframe+0x4b/0x53 Reported-by: Hyunwoo Kim Fixes: a770f6f28b1a ("KVM: MMU: Inherit a shadow page's guest level count from vcpu setup") Cc: stable@vger.kernel.org Signed-off-by: Sean Christopherson Signed-off-by: Paolo Bonzini --- arch/x86/kvm/mmu/mmu.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c index c9e4739b26d711..a61750f8e1e373 100644 --- a/arch/x86/kvm/mmu/mmu.c +++ b/arch/x86/kvm/mmu/mmu.c @@ -2442,6 +2442,9 @@ static union kvm_mmu_page_role kvm_mmu_child_role(u64 *sptep, bool direct, role.direct = direct; role.passthrough = 0; + WARN_ON_ONCE(role.invalid); + role.invalid = 0; + /* * If the guest has 4-byte PTEs then that means it's using 32-bit, * 2-level, non-PAE paging. KVM shadows such guests with PAE paging From a8934c2c6dfd72901cf3cc0de28e85eb902a61a4 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 7 Aug 2026 10:34:16 +0200 Subject: [PATCH 390/612] ALSA: usb-audio: Fix sticky mixer regressions on M-Audio Fast Track Ultra The recent fix for sticky mixer volumes caused regressions of M-audio Fast Track Ultra device, where the mixer state is kept to the default value. Add the quirk entries to tolerate the broken mixer behavior. As the device is known to work in the implicit feedback mode, explicitly enable the implicit feedback mode, too. Since there are two FTU models that are almost identical, both entries are added in this patch (0763:2080 and 0763:2081). Fixes: 86aa1ea1f15c ("ALSA: usb-audio: Do not expose sticky mixers") Link: https://bugzilla.suse.com/show_bug.cgi?id=1273166 Link: https://patch.msgid.link/20260807083418.1712585-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index c90c89200baf61..90ca39dbed18be 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2327,6 +2327,10 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_GENERIC_IMPLICIT_FB), 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), + DEVICE_FLG(0x0763, 0x2081, /* M-Audio Fast Track Ultra */ + QUIRK_FLAG_MIXER_GET_CUR_BROKEN | 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 */ From 8e7ff730dd96519a333d1570edf1c3fabb6d3629 Mon Sep 17 00:00:00 2001 From: Yao Kai Date: Tue, 4 Aug 2026 20:55:30 +0800 Subject: [PATCH 391/612] futex: Fix race in futex_pivot_pending() during private hash resize A task performing a custom private hash resize can remain blocked in uninterruptible sleep indefinitely. The hung-task detector reports: INFO: task futex-resizer:314 blocked for more than 10 seconds. task:futex-resizer state:D stack:14824 pid:314 tgid:312 ppid:311 Call Trace: __schedule+0x521/0xf30 schedule+0x22/0xa0 futex_hash_allocate+0x3db/0x490 __do_sys_prctl+0x6f5/0xbd0 do_syscall_64+0xf9/0x530 entry_SYSCALL_64_after_hwframe+0x77/0x7f Kernel panic - not syncing: hung_task: blocked tasks futex_pivot_pending() allows the resize request to continue when either no replacement hash is pending (hash_new == NULL) or the current hash reference count has reached zero. After the final-reference wake, another futex task can complete the pivot between the two observations: T1 T2 futex_hash_allocate() wait_var_event(mm, ...) futex_pivot_pending(mm) hash_new != NULL futex_hash() futex_ref_get(old) -> false futex_pivot_hash(mm) hash_new = NULL __futex_pivot_hash(mm, new) rcu_assign_pointer(hash, new) fph = rcu_dereference(hash) /* new */ futex_ref_is_dead(fph) -> false schedule() The pivot changes the state from hash_new != NULL with a dead current hash to hash_new == NULL with a live current hash. Because futex_pivot_pending() reads hash_new and hash without serialization, the resize task can observe hash_new in the pre-pivot state and hash in the post-pivot state, causing futex_pivot_pending() to return false even though the pivot has completed. The task then goes to sleep after the wakeup has already been consumed. Serialize state reads in futex_pivot_pending() using futex_mm_phash::lock. This guarantees that futex_pivot_pending() observes hash_new and hash atomically, eliminating the race condition. Fixes: bd54df5ea7ca ("futex: Allow to resize the private local hash") Suggested-by: Peter Zijlstra Signed-off-by: Yao Kai Signed-off-by: Thomas Gleixner Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260804125530.3933754-1-yaokai34@huawei.com --- kernel/futex/core.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/kernel/futex/core.c b/kernel/futex/core.c index 2650d1e5280317..128c5752f225c2 100644 --- a/kernel/futex/core.c +++ b/kernel/futex/core.c @@ -1783,14 +1783,15 @@ void futex_hash_free(struct mm_struct *mm) static bool futex_pivot_pending(struct mm_struct *mm) { + struct futex_mm_phash *mmph = &mm->futex.phash; struct futex_private_hash *fph; - guard(rcu)(); + guard(mutex)(&mmph->lock); - if (!mm->futex.phash.hash_new) + if (!mmph->hash_new) return true; - fph = rcu_dereference(mm->futex.phash.hash); + fph = rcu_dereference_raw(mmph->hash); return futex_ref_is_dead(fph); } From 5b17f3f34391372faf03e79d947e0c50ab6dd258 Mon Sep 17 00:00:00 2001 From: George Wilson Date: Fri, 7 Aug 2026 11:56:21 -0500 Subject: [PATCH 392/612] powerpc/pseries: papr-phy-attest - validate cmd.length, plug mem leak In papr_phy_attest_create_handle(), the params->cmd.length is not validated before use, which can result in a buffer overlow. Check it and return -EINVAL if it is either 0 or exceeds sizeof(params->cmd). Also, params is freed on the success path but not error. Free it on errors after memory allocation. And free it on negative fd. Fixes: 86900ab620a4 ("powerpc/pseries: Add a char driver for physical-attestation RTAS") Acked-by: Haren Myneni Acked-by: Nayna Jain Tested-by: R Nageswara Sastry Cc: stable@vger.kernel.org # 6.16 Signed-off-by: George Wilson Signed-off-by: Madhavan Srinivasan --- arch/powerpc/platforms/pseries/papr-phy-attest.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/arch/powerpc/platforms/pseries/papr-phy-attest.c b/arch/powerpc/platforms/pseries/papr-phy-attest.c index 20a0e1581302e8..350ba26e596278 100644 --- a/arch/powerpc/platforms/pseries/papr-phy-attest.c +++ b/arch/powerpc/platforms/pseries/papr-phy-attest.c @@ -230,10 +230,17 @@ static long papr_phy_attest_create_handle(struct papr_phy_attest_io_block __user return -ENOMEM; if (copy_from_user(¶ms->cmd, ulc, - sizeof(struct papr_phy_attest_io_block))) + sizeof(struct papr_phy_attest_io_block))) { + kfree(params); return -EFAULT; + } params->cmd_len = be32_to_cpu(params->cmd.length); + if (params->cmd_len == 0 || params->cmd_len > sizeof(params->cmd)) { + kfree(params); + return -EINVAL; + } + seq = (struct papr_rtas_sequence) { .begin = phy_attest_sequence_begin, .end = phy_attest_sequence_end, @@ -246,6 +253,9 @@ static long papr_phy_attest_create_handle(struct papr_phy_attest_io_block __user &papr_phy_attest_handle_ops, "[papr-physical-attestation]"); + if (fd < 0) + kfree(params); + return fd; } From 649c10bff5cb7a514bf299094833ec8c9190aac3 Mon Sep 17 00:00:00 2001 From: George Wilson Date: Fri, 7 Aug 2026 11:58:36 -0500 Subject: [PATCH 393/612] powerpc/pseries: pci - logic bug The checks on num_vfs in pseries_pci_sriov_enable() are ANDed where OR was apparently intended. Change it to OR. Fixes: 9a7f6b438664 ("powerpc/pseries/pci: Associate PEs to VFs in configure SR-IOV") Acked-by: Nayna Jain Tested-by: R Nageswara Sastry Cc: stable@vger.kernel.org # 4.16 Signed-off-by: George Wilson Signed-off-by: Madhavan Srinivasan --- arch/powerpc/platforms/pseries/pci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/powerpc/platforms/pseries/pci.c b/arch/powerpc/platforms/pseries/pci.c index 84e4ffe957a86a..d11a64a086c143 100644 --- a/arch/powerpc/platforms/pseries/pci.c +++ b/arch/powerpc/platforms/pseries/pci.c @@ -132,7 +132,7 @@ static int pseries_pci_sriov_enable(struct pci_dev *pdev, u16 num_vfs) /* First integer stores max config */ max_config_vfs = of_read_number(&max_vfs[0], 1); - if (max_config_vfs < num_vfs && num_vfs > MAX_VFS_FOR_MAP_PE) { + if (max_config_vfs < num_vfs || num_vfs > MAX_VFS_FOR_MAP_PE) { dev_err(&pdev->dev, "Num VFs %x > %x Configurable VFs\n", num_vfs, (num_vfs > MAX_VFS_FOR_MAP_PE) ? From fb442a6673ff1046bf67754957d95880fdb394b5 Mon Sep 17 00:00:00 2001 From: George Wilson Date: Fri, 7 Aug 2026 11:59:00 -0500 Subject: [PATCH 394/612] powerpc/pseries: lparcfg - fix kbuf[] underflow In lparcfg_write(), a count of 0 results in kbuf[] being indexed at -1. Check for count == 0 in the existing check for count > sizeof(kbuf) and return -EINVAL if true. Fixes: 74422e2b1939 ("powerpc/pseries: Remove VLA from lparcfg_write()") Acked-by: Nayna Jain Tested-by: R Nageswara Sastry Cc: stable@vger.kernel.org # 4.20 Signed-off-by: George Wilson Signed-off-by: Madhavan Srinivasan --- arch/powerpc/platforms/pseries/lparcfg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/powerpc/platforms/pseries/lparcfg.c b/arch/powerpc/platforms/pseries/lparcfg.c index 54b7ecf375b5af..3280b7968cab50 100644 --- a/arch/powerpc/platforms/pseries/lparcfg.c +++ b/arch/powerpc/platforms/pseries/lparcfg.c @@ -699,7 +699,7 @@ static ssize_t lparcfg_write(struct file *file, const char __user * buf, if (!firmware_has_feature(FW_FEATURE_SPLPAR)) return -EINVAL; - if (count > sizeof(kbuf)) + if (count == 0 || count > sizeof(kbuf)) return -EINVAL; if (copy_from_user(kbuf, buf, count)) From d15d51fb26e830af58f3f21964f1c09c239077ea Mon Sep 17 00:00:00 2001 From: Melbin K Mathew Date: Thu, 2 Jul 2026 01:42:46 +0200 Subject: [PATCH 395/612] fbdev: bound mode sysfs output to the sysfs buffer mode_string() uses snprintf() which can return a value larger than the remaining buffer space. show_modes() accumulates the return value into i without checking whether i has reached PAGE_SIZE, causing the offset to advance past the sysfs buffer if the modelist is long enough. Add a size parameter to mode_string() and use scnprintf() to return only the bytes actually written. Add an early return when offset already exceeds the buffer. In show_modes(), stop accumulating once the buffer is full. Cc: stable@vger.kernel.org # v7.1+ Signed-off-by: Melbin K Mathew Signed-off-by: Helge Deller --- drivers/video/fbdev/core/fbsysfs.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/video/fbdev/core/fbsysfs.c b/drivers/video/fbdev/core/fbsysfs.c index ea196603c7a87b..af21dc5052df37 100644 --- a/drivers/video/fbdev/core/fbsysfs.c +++ b/drivers/video/fbdev/core/fbsysfs.c @@ -27,12 +27,15 @@ static int activate(struct fb_info *fb_info, struct fb_var_screeninfo *var) return 0; } -static int mode_string(char *buf, unsigned int offset, +static int mode_string(char *buf, size_t size, unsigned int offset, const struct fb_videomode *mode) { char m = 'U'; char v = 'p'; + if (offset >= size) + return 0; + if (mode->flag & FB_MODE_IS_DETAILED) m = 'D'; if (mode->flag & FB_MODE_IS_VESA) @@ -45,7 +48,7 @@ static int mode_string(char *buf, unsigned int offset, if (mode->vmode & FB_VMODE_DOUBLE) v = 'd'; - return snprintf(&buf[offset], PAGE_SIZE - offset, "%c:%dx%d%c-%d\n", + return scnprintf(&buf[offset], size - offset, "%c:%dx%d%c-%d\n", m, mode->xres, mode->yres, v, mode->refresh); } @@ -64,7 +67,7 @@ static ssize_t store_mode(struct device *device, struct device_attribute *attr, list_for_each_entry(modelist, &fb_info->modelist, list) { mode = &modelist->mode; - i = mode_string(mstr, 0, mode); + i = mode_string(mstr, sizeof(mstr), 0, mode); if (strncmp(mstr, buf, max(count, i)) == 0) { var = fb_info->var; @@ -86,7 +89,7 @@ static ssize_t show_mode(struct device *device, struct device_attribute *attr, if (!fb_info->mode) return 0; - return mode_string(buf, 0, fb_info->mode); + return mode_string(buf, PAGE_SIZE, 0, fb_info->mode); } static ssize_t store_modes(struct device *device, @@ -136,7 +139,9 @@ static ssize_t show_modes(struct device *device, struct device_attribute *attr, i = 0; list_for_each_entry(modelist, &fb_info->modelist, list) { mode = &modelist->mode; - i += mode_string(buf, i, mode); + i += mode_string(buf, PAGE_SIZE, i, mode); + if (i >= PAGE_SIZE - 1) + break; } return i; } From 95e647d2a5304a8fd11f1ba3c8502de700650131 Mon Sep 17 00:00:00 2001 From: Melbin K Mathew Date: Thu, 2 Jul 2026 01:42:47 +0200 Subject: [PATCH 396/612] fbdev: clear fb_info->mode before deleting a videomode fb_set_var() can delete a mode from info->modelist when userspace passes FB_ACTIVATE_INV_MODE through FBIOPUT_VSCREENINFO. The code checks that the mode being deleted is not the current info->var and that fbcon is not using it, but it does not check fb_info->mode. fb_info->mode may still point into the modelist entry being deleted. If the entry is freed, later mode sysfs reads through show_mode() can dereference a stale pointer. Clear fb_info->mode before calling fb_delete_videomode() when it matches the mode being removed. Cc: stable@vger.kernel.org # v7.1+ Signed-off-by: Melbin K Mathew Signed-off-by: Helge Deller --- drivers/video/fbdev/core/fbmem.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/video/fbdev/core/fbmem.c b/drivers/video/fbdev/core/fbmem.c index 2f1c56e5a7a232..c8aa163b0ecfa3 100644 --- a/drivers/video/fbdev/core/fbmem.c +++ b/drivers/video/fbdev/core/fbmem.c @@ -246,8 +246,11 @@ fb_set_var(struct fb_info *info, struct fb_var_screeninfo *var) ret = fb_mode_is_equal(&mode1, &mode2); if (!ret) { ret = fbcon_mode_deleted(info, &mode1); - if (!ret) + if (!ret) { + if (info->mode && fb_mode_is_equal(info->mode, &mode1)) + info->mode = NULL; fb_delete_videomode(&mode1, &info->modelist); + } } return ret ? -EINVAL : 0; From 061db6b7a910b8378f3b2df64f8c0a3ddc6e85f2 Mon Sep 17 00:00:00 2001 From: Melbin K Mathew Date: Thu, 2 Jul 2026 01:42:48 +0200 Subject: [PATCH 397/612] fbdev: serialize mode sysfs access with lock_fb_info() show_mode(), show_modes(), and store_mode() access fb_info->modelist and fb_info->mode without holding lock_fb_info(). store_modes() takes lock_fb_info() while replacing the modelist and freeing the old one. A concurrent reader or writer can load a pointer to an old modelist entry before store_modes() frees it, then dereference freed memory or store a stale freed pointer in fb_info->mode. Take lock_fb_info() in show_mode(), show_modes(), and store_mode() to serialize with store_modes(). In show_mode(), copy the mode to the stack and format after dropping the lock. In store_mode(), split activate() into a _locked variant to avoid double-locking, and hold the locks for the modelist walk, mode conversion, activation, and fb_info->mode assignment together. Cc: stable@vger.kernel.org # v7.1+ Signed-off-by: Melbin K Mathew Signed-off-by: Helge Deller --- drivers/video/fbdev/core/fbsysfs.c | 46 ++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/drivers/video/fbdev/core/fbsysfs.c b/drivers/video/fbdev/core/fbsysfs.c index af21dc5052df37..d3d60c555bb86f 100644 --- a/drivers/video/fbdev/core/fbsysfs.c +++ b/drivers/video/fbdev/core/fbsysfs.c @@ -12,19 +12,24 @@ #include "fb_internal.h" #include "fbcon.h" +static int activate_locked(struct fb_info *fb_info, + struct fb_var_screeninfo *var) +{ + var->activate |= FB_ACTIVATE_FORCE; + return fb_set_var_from_user(fb_info, var); +} + static int activate(struct fb_info *fb_info, struct fb_var_screeninfo *var) { int err; - var->activate |= FB_ACTIVATE_FORCE; console_lock(); lock_fb_info(fb_info); - err = fb_set_var_from_user(fb_info, var); + err = activate_locked(fb_info, var); unlock_fb_info(fb_info); console_unlock(); - if (err) - return err; - return 0; + + return err; } static int mode_string(char *buf, size_t size, unsigned int offset, @@ -65,6 +70,9 @@ static ssize_t store_mode(struct device *device, struct device_attribute *attr, memset(&var, 0, sizeof(var)); + console_lock(); + lock_fb_info(fb_info); + list_for_each_entry(modelist, &fb_info->modelist, list) { mode = &modelist->mode; i = mode_string(mstr, sizeof(mstr), 0, mode); @@ -72,12 +80,22 @@ static ssize_t store_mode(struct device *device, struct device_attribute *attr, var = fb_info->var; fb_videomode_to_var(&var, mode); - if ((err = activate(fb_info, &var))) + err = activate_locked(fb_info, &var); + if (err) { + unlock_fb_info(fb_info); + console_unlock(); return err; + } fb_info->mode = mode; + unlock_fb_info(fb_info); + console_unlock(); return count; } } + + unlock_fb_info(fb_info); + console_unlock(); + return -EINVAL; } @@ -85,11 +103,20 @@ static ssize_t show_mode(struct device *device, struct device_attribute *attr, char *buf) { struct fb_info *fb_info = dev_get_drvdata(device); + struct fb_videomode mode; + bool have_mode = false; - if (!fb_info->mode) + lock_fb_info(fb_info); + if (fb_info->mode) { + mode = *fb_info->mode; + have_mode = true; + } + unlock_fb_info(fb_info); + + if (!have_mode) return 0; - return mode_string(buf, PAGE_SIZE, 0, fb_info->mode); + return mode_string(buf, PAGE_SIZE, 0, &mode); } static ssize_t store_modes(struct device *device, @@ -137,12 +164,15 @@ static ssize_t show_modes(struct device *device, struct device_attribute *attr, const struct fb_videomode *mode; i = 0; + lock_fb_info(fb_info); list_for_each_entry(modelist, &fb_info->modelist, list) { mode = &modelist->mode; i += mode_string(buf, PAGE_SIZE, i, mode); if (i >= PAGE_SIZE - 1) break; } + unlock_fb_info(fb_info); + return i; } From 81cc73be40c6f028f1ee3f438ace46afe666dbae Mon Sep 17 00:00:00 2001 From: Mingyu Wang <25181214217@stu.xidian.edu.cn> Date: Tue, 21 Jul 2026 16:19:42 +0800 Subject: [PATCH 398/612] fbdev: core: Fix pointer desynchronization in fb_io_read() In fb_io_read(), if copy_to_user() performs a partial copy (e.g., due to a faulty user buffer), the loop adjusts the chunk size 'c' and updates the remaining 'count'. However, the hardware 'src' pointer has already been eagerly advanced by the original chunk size. If the loop is allowed to continue, the read will resume from an incorrect, over-advanced offset. Since the remaining 'count' was only decremented by the successful bytes, this desynchronization causes the next iterations to execute more hardware reads than originally bounded, eventually leading to out-of-bounds I/O reads. Fix this by breaking out of the loop immediately upon a partial copy_to_user(). A partial copy indicates a faulty user buffer, making subsequent read attempts futile. Breaking out ensures we return the number of successfully read bytes without risking out-of-bounds hardware accesses in subsequent mismatched iterations. Fixes: 6121cd9ef911 ("fbdev: Move I/O read and write code into helper functions") Cc: stable@vger.kernel.org Signed-off-by: Mingyu Wang <25181214217@stu.xidian.edu.cn> Signed-off-by: Helge Deller --- drivers/video/fbdev/core/fb_io_fops.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/video/fbdev/core/fb_io_fops.c b/drivers/video/fbdev/core/fb_io_fops.c index 6ab60fcd005014..0798e88799ebbe 100644 --- a/drivers/video/fbdev/core/fb_io_fops.c +++ b/drivers/video/fbdev/core/fb_io_fops.c @@ -61,6 +61,14 @@ ssize_t fb_io_read(struct fb_info *info, char __user *buf, size_t count, loff_t buf += c; cnt += c; count -= c; + + /* + * If there was a partial copy, the user buffer is faulty. + * Break out to avoid over-advancing the src pointer and + * reading out of bounds in the next iteration. + */ + if (trailing) + break; } kfree(buffer); From ef7656e85f1a4400999625cd398b655517368e7e Mon Sep 17 00:00:00 2001 From: Zizhi Wo Date: Wed, 29 Jul 2026 10:12:04 +0800 Subject: [PATCH 399/612] fbdev: Fix out-of-bounds access when rotating console after font resize [BUG] Recently, we encountered a KASAN warning as follows: BUG: KASAN: slab-out-of-bounds in ccw_putcs+0x8bd/0xa80 Read of size 1 at addr ff11000110067100 by task bash/1209 CPU: 10 UID: 0 PID: 1209 Comm: bash Not tainted 7.2.0-rc3 #69 PREEMPT(full) Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.17.0-4.fc41 04/01/2014 Call Trace: ... kasan_report+0xf0/0x120 ? ccw_putcs+0x8bd/0xa80 ccw_putcs+0x8bd/0xa80 ? __pfx_ccw_putcs+0x10/0x10 fbcon_putcs+0x338/0x410 ? __pfx_ccw_putcs+0x10/0x10 do_update_region+0x21d/0x450 invert_screen+0x29d/0x5e0 ? __kmalloc_noprof+0x493/0x640 ? vc_do_resize+0x17c/0xe50 clear_selection+0x4c/0x60 vc_do_resize+0xaee/0xe50 fbcon_modechanged+0x2bd/0x640 rotate_all_store+0x298/0x380 ... reproduce: 1) issue two ioctls: first a KDFONTOP ioctl with op.op = KD_FONT_OP_SET, op.width = 1 and op.height = 1, then a TIOCL_SETSEL ioctl 2) echo 2 > /sys/devices/virtual/graphics/fbcon/rotate_all 3) issue two ioctls: first a KDFONTOP ioctl with op.op = KD_FONT_OP_SET, op.width = 8 and op.height = 1, then a TIOCL_SETSEL ioctl 4) echo 3 > /sys/devices/virtual/graphics/fbcon/rotate_all [CAUSE] The root cause is that fbcon_modechanged() first sets the current rotate's corresponding ops. Subsequently, during vc_resize(), it may trigger clear_selection(), and in fbcon_putcs->ccw_putcs[rotate=3], this can result in an out-of-bounds access to "src". This happens because par->rotated.buf is reallocated in fbcon_rotate_font(): 1) When rotate=2, its size is (width + 7) / 8 * height 2) When rotate=3, its size is (height + 7) / 8 * width And the call to fbcon_rotate_font() occurs after clear_selection(). In other words, the fontbuffer is allocated using the size calculated from the previous rotation 2, but before reallocating it with the new size, con_putcs is already using the new rotation 3: rotate_all_store fbcon_rotate_all fbcon_set_all_vcs fbcon_modechanged set_blitting_type ... par->bitops = &ccw_fbcon_bitops vc_resize ... clear_selection highlight ... do_update_region fbcon_putcs ... image.dy = vyres - ((xx + count) * vc->vc_font.width) [1] // overflow! ccw_putcs_aligned // old buf size is still being used during the read! src = par->rotated.buf + (scr_readw(s--) & charmask) * cellsize fb_pad_aligned_buffer----[src KASAN!!!] [2] info->fbops->fb_imageblit(info, image) sys_imageblit fb_imageblit fb_address_forward // offset: image->dy * bits_per_line + image->dx * bpp unsigned int bits = (unsigned int)adr->bits + offset adr->address += (bits & ~(BITS_PER_LONG - 1u)) / BITS_PER_BYTE [3] fb_bitmap_imageblit ... fb_read_offset // page fault! [4] update_screen redraw_screen ... ccw_cursor soft_cursor memcpy(src, image->data, dsize)----[src KASAN again!!!] [5] fbcon_switch fbcon_rotate_font font_data_rotate dst = kmalloc_array(charcount, d_cellsize, GFP_KERNEL) // the new size is allocated only here! par->rotated.buf = buf [6] [FIX] A fairly obvious approach is to follow fbcon_switch(): in fbcon_modechanged(), call rotate_font() before vc_resize() so that a correctly sized buffer is allocated in time, as done in [6]. This fix is necessary, but it is not sufficient on its own. In [1] it causes an image.dy overflow (ccw_putcs: vyres = 768, image.dy = 4294967040), because vc_cols has not been updated in time at this point (it is likewise only updated after clear_selection()). This allows (xx + count) * width to exceed vyres, causing image.dy to overflow. Subsequently, address in [3] is incremented by an even larger amount, which triggers a page fault at [4]. Therefore, a second fix is required in combination with the first: move clear_selection() earlier, before set_blitting_type() in fbcon_set_all_vcs(), to prevent the out-of-bounds access. fbcon_rotate() has a similar problem, so add the same clear there. Since vc_is_sel() is not exported, the fbdev side is currently forced to call clear_selection() unconditionally, causing the global selection to be cleared prematurely. And this will not cause any other significant impact. Signed-off-by: Zizhi Wo Signed-off-by: Helge Deller --- drivers/video/fbdev/core/fbcon.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c index 9f5c4c101581d0..23b3c536d53d1c 100644 --- a/drivers/video/fbdev/core/fbcon.c +++ b/drivers/video/fbdev/core/fbcon.c @@ -2641,9 +2641,31 @@ static void fbcon_modechanged(struct fb_info *info) fbcon_info_from_console(par->currcon) != info) return; + /* + * Clear the selection before switching bitops. Without this, the + * clear_selection() inside vc_resize() below repaints the highlighted + * cells through the new bitops while the console geometry(vc_rows/vc_cols) + * has not been updated to match, so the repaint is computed from a + * half-switched geometry and overflows the framebuffer address. + * Pre-clearing makes that repaint a no-op. + */ + clear_selection(); + p = &fb_display[vc->vc_num]; set_blitting_type(vc, info); + /* + * Rebuild par->rotated.buf for the new rotation now that bitops have + * switched. The new putcs/cursor ops read this buffer; if it is still + * sized for the old rotation, fbcon_putcs() and the cursor path reached + * via update_screen() below overflow it. Mirrors fbcon_switch(); fall + * back to unrotated rendering on allocation failure. + */ + if (par->bitops->rotate_font && par->bitops->rotate_font(info, vc)) { + par->rotate = FB_ROTATE_UR; + set_blitting_type(vc, info); + } + if (con_is_visible(vc)) { var_to_display(p, &info->var, info); cols = FBCON_SWAP(par->rotate, info->var.xres, info->var.yres); @@ -2675,6 +2697,9 @@ static void fbcon_set_all_vcs(struct fb_info *info) if (!par || par->currcon < 0) return; + /* See the comment in fbcon_modechanged(). */ + clear_selection(); + for (i = first_fb_vc; i <= last_fb_vc; i++) { vc = vc_cons[i].d; if (!vc || vc->vc_mode != KD_TEXT || From e033cbf3975a8465f879ebd5989dc35b04423a4d Mon Sep 17 00:00:00 2001 From: Rik van Riel Date: Fri, 7 Aug 2026 22:19:56 -0400 Subject: [PATCH 400/612] fbdev: bitblit: bound-check glyph index in bit_cursor() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bit_cursor() fetches the glyph under the cursor with c = scr_readw(vc_pos); src = vc_font.data + ((c & charmask) * w * height); where charmask is 0x1ff when vc_hi_font_mask is set. The screen buffer value comes directly from scr_readw() and may be larger than the current font's glyph count. Syzkaller triggers this via vcs_write(). The Call Trace shows vcs_write() in vc_screen.c writing an arbitrary 16-bit value with writev() to /dev/vcsa, which vcs_write_buf() in vc_screen.c stores via vcs_scr_writew() without checking charcount. The stored value is later read in bit_cursor() in bitblit.c. When the font is changed from a font with 512 glyphs to a font with 256 glyphs, the screen buffer can retain characters with the high bit set from the previous mode, which could also produce the same out-of-bounds access. BUG: KASAN: global-out-of-bounds in soft_cursor+0x378/0x6bc drivers/video/fbdev/core/softcursor.c:70 Read of size 16 at addr ffff800086c57970 Call Trace: soft_cursor+0x378/0x6bc drivers/video/fbdev/core/softcursor.c:70 bit_cursor+0xa90/0x1108 drivers/video/fbdev/core/bitblit.c:365 fbcon_cursor+0x344/0x498 drivers/video/fbdev/core/fbcon.c:1427 hide_cursor+0xdc/0x2d0 drivers/tty/vt/vt.c:883 update_region+0x100/0x18c drivers/tty/vt/vt.c:669 vcs_write+0x8ec/0xaf0 drivers/tty/vt/vc_screen.c:685 bit_putcs_aligned() and bit_putcs_unaligned() already clamp the glyph index to vc_font.charcount. Apply the same clamp in bit_cursor() after extracting the attribute and masking, before indexing fontdata. The fix completes the bounds checking started in commit 18c4ef4e765a ("fbdev: bitblit: bound-check glyph index in bit_putcs*"), which missed the cursor path. This change should be safe because the clamp reuses the existing contract from fbcon: charcount is maintained under console_lock in con_font_set() and fbcon_font_set(), and hi_font_mask is cleared when switching from 512 to 256 glyphs. When stale screen data with high bits remains after a font switch, or when vcs_write() stores an arbitrary value, clamping the index to 0 prevents the out-of-bounds read without changing cursor semantics — the same fallback bit_putcs uses. Reported-by: syzbot+61b1db46218109869c14@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=61b1db46218109869c14 Link: https://lore.kernel.org/all/6a75205c.01d0871a.3a0d52.0032.GAE@google.com/ Fixes: 18c4ef4e765a ("fbdev: bitblit: bound-check glyph index in bit_putcs*") Cc: stable@vger.kernel.org Assisted-by: Hermes:muse-spark-1.2 syzkaller Signed-off-by: Rik van Riel Signed-off-by: Helge Deller --- drivers/video/fbdev/core/bitblit.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/video/fbdev/core/bitblit.c b/drivers/video/fbdev/core/bitblit.c index 65681dcc5930dc..39f44258d793f1 100644 --- a/drivers/video/fbdev/core/bitblit.c +++ b/drivers/video/fbdev/core/bitblit.c @@ -273,9 +273,14 @@ static void bit_cursor(struct vc_data *vc, struct fb_info *info, bool enable, if (!vc->vc_font.data) return; - c = scr_readw((u16 *) vc->vc_pos); + c = scr_readw((u16 *) vc->vc_pos); attribute = get_attribute(info, c); - src = vc->vc_font.data + ((c & charmask) * (w * vc->vc_font.height)); + c &= charmask; + + /* Clamp to font size, same as bit_putcs_aligned() */ + if (c >= vc->vc_font.charcount) + c = 0; + src = vc->vc_font.data + (c * (w * vc->vc_font.height)); if (par->cursor_state.image.data != (const char *)src || par->cursor_reset) { From fd73b691702170d37d66f4b0278530cea8ed419a Mon Sep 17 00:00:00 2001 From: Shuangpeng Bai Date: Wed, 5 Aug 2026 22:27:19 -0400 Subject: [PATCH 401/612] eventfs: Fix use-after-free in eventfs_remove_rec() eventfs_remove_rec() recursively removes the child at the current loop position. After the recursive call returns, list_for_each_entry() advances by reading list.next from the removed child. If free_ei() drops the final reference, release_ei() reuses the list/rcu union to queue an SRCU callback. The child may be freed before that read. The eventfs_mutex serializes list updates, but it does not keep the removed child alive or prevent the SRCU callback from running. Use list_for_each_entry_safe() to save the next sibling before recursively removing the current child. Cc: stable@vger.kernel.org Fixes: 43aa6f97c2d0 ("eventfs: Get rid of dentry pointers without refcounts") Link: https://patch.msgid.link/20260806022719.375354-1-shuangpeng.kernel@gmail.com Signed-off-by: Shuangpeng Bai Acked-by: Masami Hiramatsu (Google) Signed-off-by: Steven Rostedt --- fs/tracefs/event_inode.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/tracefs/event_inode.c b/fs/tracefs/event_inode.c index 39c7a34531e8a1..93bc4f83b73e3f 100644 --- a/fs/tracefs/event_inode.c +++ b/fs/tracefs/event_inode.c @@ -822,7 +822,7 @@ struct eventfs_inode *eventfs_create_events_dir(const char *name, struct dentry */ static void eventfs_remove_rec(struct eventfs_inode *ei, int level) { - struct eventfs_inode *ei_child; + struct eventfs_inode *ei_child, *tmp; /* * Check recursion depth. It should never be greater than 3: @@ -835,7 +835,7 @@ static void eventfs_remove_rec(struct eventfs_inode *ei, int level) return; /* search for nested folders or files */ - list_for_each_entry(ei_child, &ei->children, list) + list_for_each_entry_safe(ei_child, tmp, &ei->children, list) eventfs_remove_rec(ei_child, level + 1); list_del_rcu(&ei->list); From f0ece16ffca7384787b692431961ce202907acf5 Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Sat, 8 Aug 2026 09:42:15 -0400 Subject: [PATCH 402/612] eventfs: Use children field for rcu head and add memory barriers When an eventfs inode is freed, it sets ei->is_freed and then uses its ei->list to add it to the srcu link list as the list field is a union with the rcu list head. As the ei->list is used to iterate over an SRCU protected list without taking the eventfs_mutex, there's nothing stopping the iteration over that list to see the ei->rcu instead of the ei->list and it will read a corrupt target. To fix this, change the union of the rcu list head with the children list. On freeing the eventfs inode, set the is_free and execute a smp_wmb() before adding the eventfs inode to the SRCU list. On iteration of the ei->children list, at the start, execute a smp_rmb() and then read the is_freed of the ei to see if the children list is still valid. If is_freed is set, then the ei_child read is not valid and the loop should exit immediately. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260808094215.4252430d@robin Fixes: 704f960dbee2f ("eventfs: Read ei->entries before ei->children in eventfs_iterate()") Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260806022719.375354-1-shuangpeng.kernel%40gmail.com Reviewed-by: Masami Hiramatsu (Google) Signed-off-by: Steven Rostedt --- fs/tracefs/event_inode.c | 24 ++++++++++++++++++++++++ fs/tracefs/internal.h | 4 ++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/fs/tracefs/event_inode.c b/fs/tracefs/event_inode.c index 93bc4f83b73e3f..a524584353279f 100644 --- a/fs/tracefs/event_inode.c +++ b/fs/tracefs/event_inode.c @@ -124,7 +124,17 @@ static inline void put_ei(struct eventfs_inode *ei) static inline void free_ei(struct eventfs_inode *ei) { if (ei) { + /* The ei should have no children if it is being freed. */ + WARN_ON_ONCE(!list_empty(&ei->children)); ei->is_freed = 1; + /* + * The SRCU iteration has a smp_rmb() to make sure it + * sees a child (that may have already been freed) + * before it reads is_free. If is_free is set, it must + * not use the child it acquired from ei->children, as + * the list may be used for SRCU. + */ + smp_wmb(); put_ei(ei); } } @@ -627,6 +637,20 @@ static int eventfs_iterate(struct file *file, struct dir_context *ctx) list_for_each_entry_srcu(ei_child, &ei->children, list, srcu_read_lock_held(&eventfs_srcu)) { + /* + * If the ei is being freed, then the ei->children may be + * being used as the rcu list, which means the next element + * may be garbage. The ei->is_free is set before switching + * the ei->children over to ei->rcu. The read memory barrier + * here makes sure the ei_child is read before is_free is + * updated. + * + * Matches the smp_wmb() in free_ei() + */ + smp_rmb(); + if (ei->is_freed) + return -EINVAL; + if (c > 0) { c--; continue; diff --git a/fs/tracefs/internal.h b/fs/tracefs/internal.h index a4a7f8431affb0..c61481d04c8eeb 100644 --- a/fs/tracefs/internal.h +++ b/fs/tracefs/internal.h @@ -46,11 +46,11 @@ struct eventfs_attr { * @ino: The saved inode number */ struct eventfs_inode { + struct list_head list; union { - struct list_head list; + struct list_head children; struct rcu_head rcu; }; - struct list_head children; const struct eventfs_entry *entries; const char *name; struct eventfs_attr *entry_attrs; From 63444b7617c09aeed36282e061c3f80818f2b600 Mon Sep 17 00:00:00 2001 From: Leon Hwang Date: Thu, 30 Jul 2026 23:04:08 +0800 Subject: [PATCH 403/612] ftrace: Protect direct_functions in ftrace_find_rec_direct Fix accessing the __rcu pointer direct_functions with RCU protection. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260730150411.88667-2-leon.hwang@linux.dev Fixes: d05cb470663a ("ftrace: Fix modification of direct_function hash while in use") Acked-by: Jiri Olsa Suggested-by: Steven Rostedt Signed-off-by: Leon Hwang Signed-off-by: Steven Rostedt --- kernel/trace/ftrace.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c index 6c47a94f592478..c5d1d0d42ccc1d 100644 --- a/kernel/trace/ftrace.c +++ b/kernel/trace/ftrace.c @@ -2645,7 +2645,8 @@ unsigned long ftrace_find_rec_direct(unsigned long ip) { struct ftrace_func_entry *entry; - entry = __ftrace_lookup_ip(direct_functions, ip); + guard(preempt_notrace)(); + entry = __ftrace_lookup_ip(rcu_dereference_sched(direct_functions), ip); if (!entry) return 0; From f26e5fa75fccd54bb95793c6519d405cf83233b2 Mon Sep 17 00:00:00 2001 From: Leon Hwang Date: Thu, 30 Jul 2026 23:04:09 +0800 Subject: [PATCH 404/612] ftrace: Protect direct_functions in update_ftrace_direct_del Fix accessing the __rcu pointer direct_functions with RCU protection. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260730150411.88667-3-leon.hwang@linux.dev Fixes: 8d2c1233f371 ("ftrace: Add update_ftrace_direct_del function") Acked-by: Jiri Olsa Signed-off-by: Leon Hwang Signed-off-by: Steven Rostedt --- kernel/trace/ftrace.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c index c5d1d0d42ccc1d..9ea39110927fa9 100644 --- a/kernel/trace/ftrace.c +++ b/kernel/trace/ftrace.c @@ -6512,6 +6512,7 @@ int update_ftrace_direct_del(struct ftrace_ops *ops, struct ftrace_hash *hash) struct ftrace_hash *new_direct_functions; struct ftrace_hash *new_filter_hash = NULL; struct ftrace_hash *old_filter_hash; + struct ftrace_hash *direct_hash; struct ftrace_func_entry *entry; struct ftrace_func_entry *del; unsigned long size; @@ -6523,11 +6524,13 @@ int update_ftrace_direct_del(struct ftrace_ops *ops, struct ftrace_hash *hash) return -EINVAL; if (!(ops->flags & FTRACE_OPS_FL_ENABLED)) return -EINVAL; - if (direct_functions == EMPTY_HASH) - return -EINVAL; mutex_lock(&direct_mutex); + direct_hash = rcu_dereference_protected(direct_functions, lockdep_is_held(&direct_mutex)); + if (direct_hash == EMPTY_HASH) + goto out_unlock; + old_filter_hash = ops->func_hash ? ops->func_hash->filter_hash : NULL; if (!hash_count(old_filter_hash)) @@ -6537,7 +6540,7 @@ int update_ftrace_direct_del(struct ftrace_ops *ops, struct ftrace_hash *hash) size = 1 << hash->size_bits; for (int i = 0; i < size; i++) { hlist_for_each_entry(entry, &hash->buckets[i], hlist) { - del = __ftrace_lookup_ip(direct_functions, entry->ip); + del = __ftrace_lookup_ip(direct_hash, entry->ip); if (!del || del->direct != entry->direct) goto out_unlock; } @@ -6548,7 +6551,7 @@ int update_ftrace_direct_del(struct ftrace_ops *ops, struct ftrace_hash *hash) if (!new_filter_hash) goto out_unlock; - new_direct_functions = hash_sub(direct_functions, hash); + new_direct_functions = hash_sub(direct_hash, hash); if (!new_direct_functions) goto out_unlock; @@ -6575,7 +6578,7 @@ int update_ftrace_direct_del(struct ftrace_ops *ops, struct ftrace_hash *hash) /* free the new_direct_functions */ old_direct_functions = new_direct_functions; } else { - old_direct_functions = direct_functions; + old_direct_functions = direct_hash; rcu_assign_pointer(direct_functions, new_direct_functions); } From 092f8ec7dbdc71f5bde9bb0f8dead384d2115a44 Mon Sep 17 00:00:00 2001 From: Leon Hwang Date: Thu, 30 Jul 2026 23:04:10 +0800 Subject: [PATCH 405/612] ftrace: Protect direct_functions in update_ftrace_direct_mod Fix accessing the __rcu pointer direct_functions with RCU protection. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260730150411.88667-4-leon.hwang@linux.dev Fixes: e93672f770d7 ("ftrace: Add update_ftrace_direct_mod function") Acked-by: Jiri Olsa Signed-off-by: Leon Hwang Signed-off-by: Steven Rostedt --- kernel/trace/ftrace.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c index 9ea39110927fa9..414e425c2d8042 100644 --- a/kernel/trace/ftrace.c +++ b/kernel/trace/ftrace.c @@ -6617,6 +6617,7 @@ int update_ftrace_direct_mod(struct ftrace_ops *ops, struct ftrace_hash *hash, b .func = ftrace_stub, .flags = FTRACE_OPS_FL_STUB, }; + struct ftrace_hash *direct_hash; struct ftrace_hash *orig_hash; unsigned long size, i; int err = -EINVAL; @@ -6627,8 +6628,6 @@ int update_ftrace_direct_mod(struct ftrace_ops *ops, struct ftrace_hash *hash, b return -EINVAL; if (!(ops->flags & FTRACE_OPS_FL_ENABLED)) return -EINVAL; - if (direct_functions == EMPTY_HASH) - return -EINVAL; /* * We can be called from within ops_func callback with direct_mutex @@ -6636,6 +6635,12 @@ int update_ftrace_direct_mod(struct ftrace_ops *ops, struct ftrace_hash *hash, b */ if (do_direct_lock) mutex_lock(&direct_mutex); + else + lockdep_assert_held_once(&direct_mutex); + + direct_hash = rcu_dereference_protected(direct_functions, lockdep_is_held(&direct_mutex)); + if (direct_hash == EMPTY_HASH) + goto unlock; orig_hash = ops->func_hash ? ops->func_hash->filter_hash : NULL; if (!orig_hash) @@ -6667,7 +6672,7 @@ int update_ftrace_direct_mod(struct ftrace_ops *ops, struct ftrace_hash *hash, b size = 1 << hash->size_bits; for (i = 0; i < size; i++) { hlist_for_each_entry(entry, &hash->buckets[i], hlist) { - tmp = __ftrace_lookup_ip(direct_functions, entry->ip); + tmp = __ftrace_lookup_ip(direct_hash, entry->ip); if (!tmp) continue; tmp->direct = entry->direct; From 48f2fd0d938651f600dce4a4f76f6e84730c5378 Mon Sep 17 00:00:00 2001 From: Leon Hwang Date: Thu, 30 Jul 2026 23:04:11 +0800 Subject: [PATCH 406/612] ftrace: Drop extra comma in trace_buffered_event_enable Drop the extra comma in "scoped_guard()" to cleanup the code. Link: https://patch.msgid.link/20260730150411.88667-5-leon.hwang@linux.dev Acked-by: Jiri Olsa Signed-off-by: Leon Hwang Signed-off-by: Steven Rostedt --- kernel/trace/trace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index 01a5e87af2998e..395238b2b715c1 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -1788,7 +1788,7 @@ void trace_buffered_event_enable(void) per_cpu(trace_buffered_event, cpu) = event; - scoped_guard(preempt,) { + scoped_guard(preempt) { if (cpu == smp_processor_id() && __this_cpu_read(trace_buffered_event) != per_cpu(trace_buffered_event, cpu)) From f8a80cfb68613fb7e6452b66447dbc63f435d140 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Sat, 8 Aug 2026 17:20:06 +0200 Subject: [PATCH 407/612] ALSA: usb: Fix UAF at delayed release of MIDI2 EPs The recent fix for UAF in ump_to_endpoint() caused another UAF because it tries to dereference the UMP endpoint object, but this might be executed at a delayed context where the endpoint has been already released. Add private_free to clear the associated data for avoiding the further dereference for delayed releases. Fixes: 4a05b2d1b464 ("ALSA: usb-audio: fix use-after-free in ump_to_endpoint()") Reported-by: syzbot+565b1138cfbe549d4422@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=565b1138cfbe549d4422 Cc: Link: https://patch.msgid.link/20260808152009.1947835-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/midi2.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sound/usb/midi2.c b/sound/usb/midi2.c index 83980fb83ac84b..1dc78f536ef937 100644 --- a/sound/usb/midi2.c +++ b/sound/usb/midi2.c @@ -677,6 +677,14 @@ static int parse_midi_2_0_endpoints(struct snd_usb_midi2_interface *umidi) return 0; } +static void free_ump_private_data(struct snd_ump_endpoint *ump) +{ + struct snd_usb_midi2_ump *rmidi = ump->private_data; + + if (rmidi) + rmidi->ump = NULL; +} + static void free_all_midi2_umps(struct snd_usb_midi2_interface *umidi) { struct snd_usb_midi2_ump *rmidi; @@ -727,6 +735,7 @@ static int create_midi2_ump(struct snd_usb_midi2_interface *umidi, ump->private_data = rmidi; ump->ops = &snd_usb_midi_v2_ump_ops; + ump->private_free = free_ump_private_data; rmidi->eps[STR_IN] = ep_in; rmidi->eps[STR_OUT] = ep_out; From 885c22d259c8b245c479f3e19e9eeee54dee8b24 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Sat, 8 Aug 2026 17:22:54 +0200 Subject: [PATCH 408/612] ALSA: usb-audio: Fix mixer regression on SteelSeries Arctis Nova 5 The recent "sticky mixer" sanity check in USB-audio driver caused a regression on SteelSeries Arctis Nova 5 (1038:2232); because the firmware doesn't handle GET_CUR requests, some mixers are effectively disabled, leading to the too low / soft volumes: usb 5-1.1: 9:0: sticky mixer values (-19712/0/256 => 0), disabling usb 5-1.1: 10:0: sticky mixer values (-21248/0/256 => 0), disabling Restore the functionality by ignoring GET_CUR errors intentionally with MIXER_GET_CUR_BROKEN quirk. Fixes: 86aa1ea1f15c ("ALSA: usb-audio: Do not expose sticky mixers") Reported-by: Gert Burger Closes: https://lore.kernel.org/CAEQ1D3kdA3mkQx7ei9Kq0gwky0qroJqCLKrkvgfkqgTbeu086A@mail.gmail.com Link: https://bbs.archlinux.org/viewtopic.php?id=314220 Link: https://patch.msgid.link/20260808152258.1948767-1-tiwai@suse.de 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 90ca39dbed18be..4d2957e62fbd3d 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2367,6 +2367,8 @@ 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 */ From f27bdc43077e4fcb5557dfc315ee8d91e741f483 Mon Sep 17 00:00:00 2001 From: Tengda Wu Date: Mon, 3 Aug 2026 00:56:39 +0000 Subject: [PATCH 409/612] ring-buffer: Use current_context for safe per-CPU buffer swap The ring_buffer_swap_cpu() function currently checks the per-CPU committing counter to determine if a buffer is actively being written to before performing the swap. However, there exists a race window where this check can be bypassed: ring_buffer_lock_reserve cpu_buffer = buffer->buffers[cpu]; // cpu_buffer_a rb_reserve_next_event rb_start_commit // inc committing if (unlikely(READ_ONCE(cpu_buffer->buffer) != buffer)) {...} __rb_reserve_next rb_move_tail rb_end_commit(cpu_buffer); // dec committing => 0 /* interrupt hits here, successfully swaps! */ local_inc(&cpu_buffer->committing); ring_buffer_unlock_commit cpu_buffer = buffer->buffers[cpu]; // cpu_buffer_b rb_commit rb_end_commit RB_WARN_ON(cpu_buffer, !local_read(&cpu_buffer->committing)) // triggers warning The committing counter can temporarily drop to 0 during a single write operation (within rb_move_tail), creating a window where swap can succeed even though the write is still in progress. This leads to inconsistent buffer state and triggers the RB_WARN_ON in rb_commit(). Replace the committing counter check with current_context checks, which are set at the entry of ring_buffer_lock_reserve() and remain valid throughout the entire write operation, providing a reliable indicator of buffer busy state during swap. Cc: stable@vger.kernel.org Fixes: 4239c38fe0b3 ("ring-buffer: Process commits whenever moving to a new page.") Link: https://patch.msgid.link/20260803005640.2445666-2-wutengda@huaweicloud.com Signed-off-by: Tengda Wu Signed-off-by: Steven Rostedt --- kernel/trace/ring_buffer.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c index 8e2485bb3aa87e..58dc8995a88de5 100644 --- a/kernel/trace/ring_buffer.c +++ b/kernel/trace/ring_buffer.c @@ -6852,7 +6852,7 @@ int ring_buffer_swap_cpu(struct trace_buffer *buffer_a, { struct ring_buffer_per_cpu *cpu_buffer_a; struct ring_buffer_per_cpu *cpu_buffer_b; - int ret = -EINVAL; + int ret = -EBUSY; if (!cpumask_test_cpu(cpu, buffer_a->cpumask) || !cpumask_test_cpu(cpu, buffer_b->cpumask)) @@ -6893,10 +6893,10 @@ int ring_buffer_swap_cpu(struct trace_buffer *buffer_a, atomic_inc(&cpu_buffer_a->record_disabled); atomic_inc(&cpu_buffer_b->record_disabled); - ret = -EBUSY; - if (local_read(&cpu_buffer_a->committing)) + /* Do not swap if either buffer is in the process of writing */ + if (cpu_buffer_a->current_context) goto out_dec; - if (local_read(&cpu_buffer_b->committing)) + if (cpu_buffer_b->current_context) goto out_dec; /* From 8b8292d6487c81bd57c2605a9b404b1cf8f1edfb Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Wed, 5 Aug 2026 21:56:46 -0700 Subject: [PATCH 410/612] ftrace: Fix off-by-one fentry site disable in ftrace_free_mem() When a module's init text is freed, do_init_module() calls ftrace_free_mem() with a half-open [start, end) range. However the ftrace_cmp_recs() comparator treats the upper bound as inclusive, as all its other users do, passing 'ip + size - 1'. So ftrace_free_mem() can delete a record sitting exactly at 'end', which is outside the freed range. For a kernel without CFI or IBT, the first record of a function is at the function start, which for the first function in a module is also the base of its text allocation. As the module allocator packs its regions, that address is often the 'end' passed by a neighboring module's do_init_module(), causing the first function's ftrace location to get disabled, preventing an attempt to livepatch it: livepatch: failed to find location for function 'pcspkr_probe' Convert the exclusive end to the inclusive 'end - 1' the comparator expects, and return early for an empty range to avoid the subtraction from underflowing when the init text size is zero. Cc: stable@vger.kernel.org Fixes: 42c269c88dc1 ("ftrace: Allow for function tracing to record init functions on boot up") Link: https://patch.msgid.link/1b5ccfa8095bdb1277f84af1c2c2e2205aca03ae.1785992188.git.jpoimboe@kernel.org Signed-off-by: Josh Poimboeuf Acked-by: Masami Hiramatsu (Google) Signed-off-by: Steven Rostedt --- kernel/trace/ftrace.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c index 414e425c2d8042..7c50f8ae5a0c42 100644 --- a/kernel/trace/ftrace.c +++ b/kernel/trace/ftrace.c @@ -8305,7 +8305,8 @@ static void add_to_clear_hash_list(struct list_head *clear_list, void ftrace_free_mem(struct module *mod, void *start_ptr, void *end_ptr) { unsigned long start = (unsigned long)(start_ptr); - unsigned long end = (unsigned long)(end_ptr); + /* end is inclusive and end_ptr is exclusive */ + unsigned long end = (unsigned long)(end_ptr) - 1; struct ftrace_page **last_pg = &ftrace_pages_start; struct ftrace_page *tmp_page = NULL; struct ftrace_page *pg; @@ -8315,6 +8316,9 @@ void ftrace_free_mem(struct module *mod, void *start_ptr, void *end_ptr) struct ftrace_init_func *func, *func_next; LIST_HEAD(clear_hash); + if (start_ptr >= end_ptr) + return; + key.ip = start; key.flags = end; /* overload flags, as it is unsigned long */ From 7c727dfce6be04dd009b29091a4a17d952dbfe03 Mon Sep 17 00:00:00 2001 From: Vincent Donnefort Date: Thu, 6 Aug 2026 22:13:01 +0100 Subject: [PATCH 411/612] ring-buffer: Prevent resizing of persistent ring buffer Dynamically resizing a persistent ring buffer is not possible. Disable the feature. Cc: stable@vger.kernel.org Fixes: be68d63a139b ("ring-buffer: Add ring_buffer_alloc_range()") Link: https://patch.msgid.link/20260806211306.3704194-2-vdonnefort@google.com Signed-off-by: Vincent Donnefort Signed-off-by: Steven Rostedt --- kernel/trace/ring_buffer.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c index 58dc8995a88de5..09d502ef4c5586 100644 --- a/kernel/trace/ring_buffer.c +++ b/kernel/trace/ring_buffer.c @@ -2528,6 +2528,8 @@ rb_allocate_cpu_buffer(struct trace_buffer *buffer, long nr_pages, int cpu) if (cpu_buffer->ring_meta->head_buffer) rb_meta_buffer_update(cpu_buffer, bpage); bpage->range = 1; + + atomic_inc(&cpu_buffer->resize_disabled); } else if (buffer->remote) { struct ring_buffer_desc *desc = ring_buffer_desc(buffer->remote->desc, cpu); From bf98d7b0d5a99991e47e66cee4eb1d3fa514be97 Mon Sep 17 00:00:00 2001 From: Vincent Donnefort Date: Thu, 6 Aug 2026 22:13:02 +0100 Subject: [PATCH 412/612] ring-buffer: Prevent subbuf order change when resizing is disabled Because ring_buffer_subbuf_order_set() frees buffer pages, we can't allow it when resizing is disabled. A non-consuming reader is at risk of use-after-free (rb_advance_iter()). Return -EBUSY on resize_disabled, matching ring_buffer_resize() behaviour. Cc: stable@vger.kernel.org Fixes: f9b94daa542a ("ring-buffer: Set new size of the ring buffer sub page") Link: https://patch.msgid.link/20260806211306.3704194-3-vdonnefort@google.com Reported-by: syzbot+e0cc44465d6bae735679@syzkaller.appspotmail.com Signed-off-by: Vincent Donnefort Signed-off-by: Steven Rostedt --- kernel/trace/ring_buffer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c index 09d502ef4c5586..6cbd80ccef3772 100644 --- a/kernel/trace/ring_buffer.c +++ b/kernel/trace/ring_buffer.c @@ -7360,7 +7360,7 @@ int ring_buffer_subbuf_order_set(struct trace_buffer *buffer, int order) cpu_buffer = buffer->buffers[cpu]; - if (cpu_buffer->mapped) { + if (atomic_read(&cpu_buffer->resize_disabled)) { err = -EBUSY; goto error; } From 6d014e44b68ddd43f71288d2a4dbb1a259869149 Mon Sep 17 00:00:00 2001 From: Vincent Donnefort Date: Thu, 6 Aug 2026 22:13:03 +0100 Subject: [PATCH 413/612] ring-buffer: Initialise reader page order in rb_allocate_cpu_buffer() In rb_allocate_cpu_buffer(), bpage->order was omitted, leaving it as 0. This is an issue for a ring-buffer with subbufs bigger than PAGE_SIZE if when freed: free_buffer_page() relies on this value. Align the value with the actual allocation size (buffer::subbuf_order). Cc: stable@vger.kernel.org Fixes: f9b94daa542a ("ring-buffer: Set new size of the ring buffer sub page") Link: https://patch.msgid.link/20260806211306.3704194-4-vdonnefort@google.com Signed-off-by: Vincent Donnefort Signed-off-by: Steven Rostedt --- kernel/trace/ring_buffer.c | 1 + 1 file changed, 1 insertion(+) diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c index 6cbd80ccef3772..760a00e8505cf4 100644 --- a/kernel/trace/ring_buffer.c +++ b/kernel/trace/ring_buffer.c @@ -2510,6 +2510,7 @@ rb_allocate_cpu_buffer(struct trace_buffer *buffer, long nr_pages, int cpu) bpage = alloc_cpu_page(cpu); if (!bpage) return NULL; + bpage->order = cpu_buffer->buffer->subbuf_order; rb_check_bpage(cpu_buffer, bpage); From 91542863abade2fd4f2b361991f5386ad9d19c8c Mon Sep 17 00:00:00 2001 From: Hui Su Date: Fri, 7 Aug 2026 23:41:46 +0800 Subject: [PATCH 414/612] ring-buffer: Fix crash passing ERR_PTR to kthread_stop() In test_ringbuffer()'s out_free cleanup loop, the check `!rb_threads[cpu]` only catches NULL entries and misses entries that hold an ERR_PTR. rb_threads[] is static, so unassigned slots are NULL. But when kthread_run_on_cpu() fails for a cpu, it stores ERR_PTR(-ENOMEM) (or -EINTR) in rb_threads[cpu] before the creation loop jumps to out_free. That entry is non-NULL, so the old `!ptr` check does not break, and the cleanup proceeds to call kthread_stop() on the ERR_PTR. kthread_stop() then dereferences the bogus pointer, crashing the kernel during the late_initcall self-test. crash logs: BUG: kernel NULL pointer dereference, address: 000000000000001c Oops: 0002 [#1] SMP NOPTI CPU: 1 PID: 1 Comm: swapper/0 Not tainted 7.2.0-rc6-dirty #7 PREEMPT(lazy) RIP: 0010:kthread_stop+0x2e/0x220 RBX: fffffffffffffff4 CR2: 000000000000001c Call Trace: test_ringbuffer+0x1ec/0x650 do_one_initcall+0x6c/0x2c0 kernel_init_freeable+0x21d/0x420 kernel_init+0x15/0x1c0 ret_from_fork+0x21b/0x320 Kernel panic - not syncing: Fatal exception Cc: stable@vger.kernel.org Fixes: 64ed3a049e3e ("ring-buffer: make use of the helper function kthread_run_on_cpu()") Link: https://patch.msgid.link/20260807154145.2846521-2-sh_def@163.com Signed-off-by: Hui Su Reviewed-by: Vincent Donnefort Acked-by: Masami Hiramatsu (Google) Signed-off-by: Steven Rostedt --- kernel/trace/ring_buffer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c index 760a00e8505cf4..2667992f0aa294 100644 --- a/kernel/trace/ring_buffer.c +++ b/kernel/trace/ring_buffer.c @@ -8217,7 +8217,7 @@ static __init int test_ringbuffer(void) out_free: for_each_online_cpu(cpu) { - if (!rb_threads[cpu]) + if (IS_ERR_OR_NULL(rb_threads[cpu])) break; kthread_stop(rb_threads[cpu]); } From db2ddb87143519e20a95aa36c60b36107b736a58 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 9 Aug 2026 14:54:50 -0700 Subject: [PATCH 415/612] Linux 7.2-rc7 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 902f3f3d54b713..0e6fc0803775a7 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ VERSION = 7 PATCHLEVEL = 2 SUBLEVEL = 0 -EXTRAVERSION = -rc6 +EXTRAVERSION = -rc7 NAME = Baby Opossum Posse # *DOCUMENTATION* From 918b8d231c571c50a00efe92ffc8404a537a0490 Mon Sep 17 00:00:00 2001 From: "Geoffrey D. Bennett" Date: Mon, 10 Aug 2026 03:36:01 +0930 Subject: [PATCH 416/612] ALSA: FCP: Use a private URB for the notification endpoint fcp_init_notify() used mixer->urb, which snd_usb_mixer_status_create() allocates for the optional UAC2 status interrupt endpoint and mixer.c kills, resubmits and frees. On a device with that endpoint, fcp_init_notify()'s "already set up" early return fires on the status URB and returns success without doing anything. No FCP notification URB is submitted, and cmd_done is left zeroed because it is initialised past that early return and nowhere else. fcp_init() then issues init1_opcode and wait_for_completion_timeout() would crash adding to the zeroed wait.head. fcp_cleanup_urb() would also kill and free mixer.c's status URB. Use a separate URB in fcp_data, and initialise cmd_done in fcp_init_private() where fcp_data is allocated. fcp_init_notify() is reached again after suspend via fcp_reinit(), and the URB kill path in fcp_notify() completes cmd_done, leaving a stale count that would satisfy the next command's wait before the device ACKs. Use reinit_completion() to clear it. Fixes: 46757a3e7d50 ("ALSA: FCP: Add Focusrite Control Protocol driver") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Signed-off-by: Geoffrey D. Bennett Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/2cad281e6434024ca48a9ecc94fa19d6777e9be7.1786290885.git.g@b4.vu --- sound/usb/fcp.c | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/sound/usb/fcp.c b/sound/usb/fcp.c index 8f52a3dc9ec38a..6bd659d47e8e19 100644 --- a/sound/usb/fcp.c +++ b/sound/usb/fcp.c @@ -82,6 +82,7 @@ struct fcp_data { struct mutex mutex; /* serialise access to the device */ struct completion cmd_done; /* wait for command completion */ struct file *file; /* hwdep file */ + struct urb *urb; /* FCP notification endpoint */ struct fcp_notify notify; @@ -186,7 +187,7 @@ static int fcp_usb(struct usb_mixer_interface *mixer, u32 opcode, const int max_retries = 5; int err; - if (!mixer->urb) + if (!private->urb) return -ENODEV; struct fcp_usb_packet *req __free(kfree) = NULL; @@ -301,7 +302,7 @@ static int fcp_reinit(struct usb_mixer_interface *mixer) { struct fcp_data *private = mixer->private_data; - if (mixer->urb) + if (private->urb) return 0; void *step0_resp __free(kfree) = @@ -893,13 +894,15 @@ static int fcp_hwdep_init(struct usb_mixer_interface *mixer) static void fcp_cleanup_urb(struct usb_mixer_interface *mixer) { - if (!mixer->urb) + struct fcp_data *private = mixer->private_data; + + if (!private->urb) return; - usb_kill_urb(mixer->urb); - kfree(mixer->urb->transfer_buffer); - usb_free_urb(mixer->urb); - mixer->urb = NULL; + usb_kill_urb(private->urb); + kfree(private->urb->transfer_buffer); + usb_free_urb(private->urb); + private->urb = NULL; } static void fcp_private_free(struct usb_mixer_interface *mixer) @@ -970,37 +973,37 @@ static int fcp_init_notify(struct usb_mixer_interface *mixer) int err; /* Already set up */ - if (mixer->urb) + if (private->urb) return 0; if (usb_pipe_type_check(dev, pipe)) return -EINVAL; - mixer->urb = usb_alloc_urb(0, GFP_KERNEL); - if (!mixer->urb) + private->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!private->urb) return -ENOMEM; transfer_buffer = kmalloc(private->wMaxPacketSize, GFP_KERNEL); if (!transfer_buffer) { - usb_free_urb(mixer->urb); - mixer->urb = NULL; + usb_free_urb(private->urb); + private->urb = NULL; return -ENOMEM; } - usb_fill_int_urb(mixer->urb, dev, pipe, + usb_fill_int_urb(private->urb, dev, pipe, transfer_buffer, private->wMaxPacketSize, fcp_notify, mixer, private->bInterval); - init_completion(&private->cmd_done); + reinit_completion(&private->cmd_done); - err = usb_submit_urb(mixer->urb, GFP_KERNEL); + err = usb_submit_urb(private->urb, GFP_KERNEL); if (err) { usb_audio_err(mixer->chip, "%s: usb_submit_urb failed: %d\n", __func__, err); kfree(transfer_buffer); - usb_free_urb(mixer->urb); - mixer->urb = NULL; + usb_free_urb(private->urb); + private->urb = NULL; } return err; @@ -1053,6 +1056,7 @@ static int fcp_init_private(struct usb_mixer_interface *mixer) return -ENOMEM; mutex_init(&private->mutex); + init_completion(&private->cmd_done); init_waitqueue_head(&private->notify.queue); spin_lock_init(&private->notify.lock); From cd17d6ff7b7d2b1dd9bcc80ae7b4a83773f918c6 Mon Sep 17 00:00:00 2001 From: "Geoffrey D. Bennett" Date: Mon, 10 Aug 2026 03:36:11 +0930 Subject: [PATCH 417/612] ALSA: scarlett2: Use a private URB for the notification endpoint scarlett2_init_notify() used mixer->urb, which snd_usb_mixer_status_create() allocates for the UAC2 status interrupt endpoint and mixer.c manages. On a device with that endpoint, the "already in use" check fires on the status URB and returns 0 for success without doing anything. No notification URB is submitted, and cmd_done is left zeroed because it is initialised past that check and nowhere else. scarlett2_usb_init() then issues SCARLETT2_USB_INIT_1 and wait_for_completion_timeout() would crash adding to the zeroed wait.head. Use a separate URB in scarlett2_data, as done for FCP, and initialise cmd_done in scarlett2_init_private(). mixer.c was also freeing the URB in snd_usb_mixer_free() and resubmitting it in snd_usb_mixer_activate(), so scarlett2 must now do both: add scarlett2_cleanup_urb(), called from private_free and private_suspend, and a private_resume callback to re-establish the URB after resume. scarlett2_init_notify() is reached from there, and the URB kill path in scarlett2_notify() completes cmd_done, leaving a stale count that would satisfy the next command's wait before the device ACKs. Use reinit_completion() to clear it. Also free the URB if the transfer buffer allocation fails, and both if usb_submit_urb() fails. Move scarlett2_init_notify() up next to scarlett2_cleanup_urb() so scarlett2_init_private() can reference it without a forward declaration. Fixes: 1b65088958ca ("ALSA: scarlett2: Implement handling of the ACK notification") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Signed-off-by: Geoffrey D. Bennett Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/ffb8ba37d5d605dfdfd8576949d67098651f9349.1786290885.git.g@b4.vu --- sound/usb/mixer.c | 6 +++ sound/usb/mixer.h | 2 + sound/usb/mixer_scarlett2.c | 98 ++++++++++++++++++++++++------------- 3 files changed, 71 insertions(+), 35 deletions(-) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 703c118f9d4eda..5de182181edee2 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -3935,6 +3935,12 @@ int snd_usb_mixer_resume(struct usb_mixer_interface *mixer) struct usb_mixer_elem_list *list; int id, err; + if (mixer->private_resume) { + err = mixer->private_resume(mixer); + if (err < 0) + return err; + } + /* restore cached mixer values */ for (id = 0; id < MAX_ID_ELEMS; id++) { for_each_mixer_elem(list, mixer, id) { diff --git a/sound/usb/mixer.h b/sound/usb/mixer.h index 3fa1bd96f85884..037b446d8b6f5b 100644 --- a/sound/usb/mixer.h +++ b/sound/usb/mixer.h @@ -18,6 +18,7 @@ struct usb_mixer_interface { struct usb_host_interface *hostif; struct list_head list; unsigned int ignore_ctl_error; + /* UAC2 status interrupt endpoint; owned by mixer.c */ struct urb *urb; /* array[MAX_ID_ELEMS], indexed by unit id */ struct usb_mixer_elem_list **id_elems; @@ -42,6 +43,7 @@ struct usb_mixer_interface { void *private_data; void (*private_free)(struct usb_mixer_interface *mixer); void (*private_suspend)(struct usb_mixer_interface *mixer); + int (*private_resume)(struct usb_mixer_interface *mixer); }; #define MAX_CHANNELS 64 /* max logical channels */ diff --git a/sound/usb/mixer_scarlett2.c b/sound/usb/mixer_scarlett2.c index 78fb72e626cac6..502854cc9f9fa4 100644 --- a/sound/usb/mixer_scarlett2.c +++ b/sound/usb/mixer_scarlett2.c @@ -1403,6 +1403,7 @@ struct scarlett2_data { struct usb_mixer_interface *mixer; struct mutex usb_mutex; /* prevent sending concurrent USB requests */ struct completion cmd_done; + struct urb *urb; /* notification endpoint */ struct mutex data_mutex; /* lock access to this data */ u8 running; u8 hwdep_in_use; @@ -8565,13 +8566,70 @@ static void scarlett2_notify(struct urb *urb) } } -/*** Cleanup/Suspend Callbacks ***/ +/*** Notification URB and Cleanup/Suspend Callbacks ***/ + +/* Submit a URB to receive notifications from the device */ +static int scarlett2_init_notify(struct usb_mixer_interface *mixer) +{ + struct usb_device *dev = mixer->chip->dev; + struct scarlett2_data *private = mixer->private_data; + unsigned int pipe = usb_rcvintpipe(dev, private->bEndpointAddress); + void *transfer_buffer; + int err; + + /* Already set up */ + if (private->urb) + return 0; + + if (usb_pipe_type_check(dev, pipe)) + return -EINVAL; + + private->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!private->urb) + return -ENOMEM; + + transfer_buffer = kmalloc(private->wMaxPacketSize, GFP_KERNEL); + if (!transfer_buffer) { + usb_free_urb(private->urb); + private->urb = NULL; + return -ENOMEM; + } + + usb_fill_int_urb(private->urb, dev, pipe, + transfer_buffer, private->wMaxPacketSize, + scarlett2_notify, mixer, private->bInterval); + + reinit_completion(&private->cmd_done); + + err = usb_submit_urb(private->urb, GFP_KERNEL); + if (err) { + kfree(transfer_buffer); + usb_free_urb(private->urb); + private->urb = NULL; + } + + return err; +} + +static void scarlett2_cleanup_urb(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + + if (!private->urb) + return; + + usb_kill_urb(private->urb); + kfree(private->urb->transfer_buffer); + usb_free_urb(private->urb); + private->urb = NULL; +} static void scarlett2_private_free(struct usb_mixer_interface *mixer) { struct scarlett2_data *private = mixer->private_data; cancel_delayed_work_sync(&private->work); + scarlett2_cleanup_urb(mixer); kfree(private); mixer->private_data = NULL; } @@ -8582,6 +8640,8 @@ static void scarlett2_private_suspend(struct usb_mixer_interface *mixer) if (cancel_delayed_work_sync(&private->work)) scarlett2_config_save(private->mixer); + + scarlett2_cleanup_urb(mixer); } /*** Initialisation ***/ @@ -8701,11 +8761,13 @@ static int scarlett2_init_private(struct usb_mixer_interface *mixer, mutex_init(&private->usb_mutex); mutex_init(&private->data_mutex); + init_completion(&private->cmd_done); INIT_DELAYED_WORK(&private->work, scarlett2_config_save_work); mixer->private_data = private; mixer->private_free = scarlett2_private_free; mixer->private_suspend = scarlett2_private_suspend; + mixer->private_resume = scarlett2_init_notify; private->info = entry->info; @@ -8722,40 +8784,6 @@ static int scarlett2_init_private(struct usb_mixer_interface *mixer, return scarlett2_find_fc_interface(mixer->chip->dev, private); } -/* Submit a URB to receive notifications from the device */ -static int scarlett2_init_notify(struct usb_mixer_interface *mixer) -{ - struct usb_device *dev = mixer->chip->dev; - struct scarlett2_data *private = mixer->private_data; - unsigned int pipe = usb_rcvintpipe(dev, private->bEndpointAddress); - void *transfer_buffer; - - if (mixer->urb) { - usb_audio_err(mixer->chip, - "%s: mixer urb already in use!\n", __func__); - return 0; - } - - if (usb_pipe_type_check(dev, pipe)) - return -EINVAL; - - mixer->urb = usb_alloc_urb(0, GFP_KERNEL); - if (!mixer->urb) - return -ENOMEM; - - transfer_buffer = kmalloc(private->wMaxPacketSize, GFP_KERNEL); - if (!transfer_buffer) - return -ENOMEM; - - usb_fill_int_urb(mixer->urb, dev, pipe, - transfer_buffer, private->wMaxPacketSize, - scarlett2_notify, mixer, private->bInterval); - - init_completion(&private->cmd_done); - - return usb_submit_urb(mixer->urb, GFP_KERNEL); -} - /* Cargo cult proprietary initialisation sequence */ static int scarlett2_usb_init(struct usb_mixer_interface *mixer) { From fabae5548149ef5c8a056875d9ebdc38366db539 Mon Sep 17 00:00:00 2001 From: Michal Pecio Date: Mon, 10 Aug 2026 07:57:28 +0200 Subject: [PATCH 418/612] ALSA: usx2y: Stop clearing urb->hcpriv before submission This is managed by USB core and drivers aren't expected to touch it. It should only be not NULL on a submitted URB, in which case clearing defeats the "submitted while active" sanity check in usb_submit_urb() and may crash the HCD handling the URB and panic the kernel. Signed-off-by: Michal Pecio Link: https://patch.msgid.link/20260810075728.483c827e.michal.pecio@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/usx2y/usbusx2yaudio.c | 1 - 1 file changed, 1 deletion(-) diff --git a/sound/usb/usx2y/usbusx2yaudio.c b/sound/usb/usx2y/usbusx2yaudio.c index 3808df54727d65..a3a0bc13632b8c 100644 --- a/sound/usb/usx2y/usbusx2yaudio.c +++ b/sound/usb/usx2y/usbusx2yaudio.c @@ -167,7 +167,6 @@ static int usx2y_urb_submit(struct snd_usx2y_substream *subs, struct urb *urb, i if (!urb) return -ENODEV; urb->start_frame = frame + NRURBS * nr_of_packs(); // let hcd do rollover sanity checks - urb->hcpriv = NULL; urb->dev = subs->usx2y->dev; /* we need to set this at each time */ err = usb_submit_urb(urb, GFP_ATOMIC); if (err < 0) { From 566fec6a33075a0ea5c441c26571221f17f4ed98 Mon Sep 17 00:00:00 2001 From: Shenghao Ding Date: Fri, 7 Aug 2026 08:03:04 +0800 Subject: [PATCH 419/612] ASoC: tas2781: fix clang build error for goto bypassing cleanup variable Remove invalid goto exit paths that jump across guard(mutex) cleanup variable initialization, replace them with direct kfree(src) and return, to fix the s390 clang build error in acoustic_ctl_write(). Fixes: d75d38dc4604 ("ASoC: tas2781: Add a debugfs node for acoustic tuning") Signed-off-by: Shenghao Ding Link: https://patch.msgid.link/20260807000304.826-1-shenghao-ding@ti.com Signed-off-by: Mark Brown --- sound/soc/codecs/tas2781-i2c.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/sound/soc/codecs/tas2781-i2c.c b/sound/soc/codecs/tas2781-i2c.c index 209067e98e1fdf..95d875c44b9129 100644 --- a/sound/soc/codecs/tas2781-i2c.c +++ b/sound/soc/codecs/tas2781-i2c.c @@ -1544,8 +1544,8 @@ static ssize_t acoustic_ctl_write(struct file *file, if (src[0] > max_pkg_len && src[0] != count) { dev_err(priv->dev, "pkg(%u), max(%u), count(%u) mismatch.\n", src[0], max_pkg_len, (unsigned int)count); - ret = 0; - goto exit; + kfree(src); + return 0; } switch (src[1]) { @@ -1559,14 +1559,14 @@ static ssize_t acoustic_ctl_write(struct file *file, break; default: dev_err(priv->dev, "%s Wrong code %02x.\n", __func__, src[1]); - ret = 0; - goto exit; + kfree(src); + return 0; } if (len < 1) { dev_err(priv->dev, "pkg fmt invalid %02x.\n", len); - ret = 0; - goto exit; + kfree(src); + return 0; } for (j = 0; j < priv->ndev; j++) @@ -1576,8 +1576,8 @@ static ssize_t acoustic_ctl_write(struct file *file, } if (j >= priv->ndev) { dev_err(priv->dev, "no such device 0x%02x.\n", src[2]); - ret = 0; - goto exit; + kfree(src); + return 0; } reg = TASDEVICE_REG(src[3], src[4], src[5]); @@ -1608,7 +1608,7 @@ static ssize_t acoustic_ctl_write(struct file *file, dev_err(priv->dev, "i2c communication error.\n"); else ret = count; -exit: + kfree(src); return ret; } From fe21db8c5e7c2a9815a9be54a1f5d556f905506e Mon Sep 17 00:00:00 2001 From: Lin Xianglin <1021538027@qq.com> Date: Fri, 7 Aug 2026 22:19:43 +0800 Subject: [PATCH 420/612] ASoC: amd: yc: Add DMI quirk for HyperX OMEN Gaming Laptop 16-ap1xxx The HyperX OMEN Gaming Laptop 16-ap1xxx (HP board 8F06) has an internal digital microphone array attached to the AMD ACP PDM controller, but the acp6x machine driver does not register the DMIC sound card because this board is missing from the DMI quirk table, leaving the internal microphone unusable. Add a DMI quirk entry for the HP board "8F06" so the acp6x DMIC capture card gets registered. Signed-off-by: Lin Xianglin <1021538027@qq.com> Link: https://patch.msgid.link/tencent_428392223C2AD3BF23E7ABAA7521FE5C0C07@qq.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 14b970f160201a..ae90e9f513d15a 100644 --- a/sound/soc/amd/yc/acp6x-mach.c +++ b/sound/soc/amd/yc/acp6x-mach.c @@ -731,6 +731,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { DMI_MATCH(DMI_BOARD_NAME, "8E35"), } }, + { + .driver_data = &acp6x_card, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "HP"), + DMI_MATCH(DMI_BOARD_NAME, "8F06"), + } + }, { .driver_data = &acp6x_card, .matches = { From 8560d458509f798d8518d11cfee5c5cc58170558 Mon Sep 17 00:00:00 2001 From: Balamurugan C Date: Thu, 6 Aug 2026 18:57:41 +0800 Subject: [PATCH 421/612] ASoC: Intel: soc-acpi: Add entry for HDMI_In capture support in NVL match table Adding HDMI-In capture via I2S feature support in NVL platform. Signed-off-by: Balamurugan C Reviewed-by: Liam Girdwood Signed-off-by: Bard Liao Link: https://patch.msgid.link/20260806105742.2676322-2-yung-chuan.liao@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/sof_es8336.c | 10 ++++++++++ sound/soc/intel/common/soc-acpi-intel-nvl-match.c | 12 ++++++++++++ 2 files changed, 22 insertions(+) diff --git a/sound/soc/intel/boards/sof_es8336.c b/sound/soc/intel/boards/sof_es8336.c index e9ee752d5ec320..9b016136c6395b 100644 --- a/sound/soc/intel/boards/sof_es8336.c +++ b/sound/soc/intel/boards/sof_es8336.c @@ -847,6 +847,16 @@ static const struct platform_device_id board_ids[] = { SOF_ES8336_SPEAKERS_EN_GPIO1_QUIRK | SOF_ES8336_JD_INVERTED), }, + { + .name = "nvl_es83x6_c1_h02", + .driver_data = (kernel_ulong_t)(SOF_ES8336_SSP_CODEC(1) | + SOF_NO_OF_HDMI_CAPTURE_SSP(2) | + SOF_HDMI_CAPTURE_1_SSP(0) | + SOF_HDMI_CAPTURE_2_SSP(2) | + SOF_SSP_HDMI_CAPTURE_PRESENT | + SOF_ES8336_SPEAKERS_EN_GPIO1_QUIRK | + SOF_ES8336_JD_INVERTED), + }, { } }; MODULE_DEVICE_TABLE(platform, board_ids); diff --git a/sound/soc/intel/common/soc-acpi-intel-nvl-match.c b/sound/soc/intel/common/soc-acpi-intel-nvl-match.c index a7f4097d426c19..51bbae5ed8fc8b 100644 --- a/sound/soc/intel/common/soc-acpi-intel-nvl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-nvl-match.c @@ -16,12 +16,24 @@ static const struct snd_soc_acpi_codecs nvl_essx_83x6 = { .codecs = { "ESSX8316", "ESSX8326", "ESSX8336"}, }; +static const struct snd_soc_acpi_codecs nvl_lt6911_hdmi = { + .num_codecs = 1, + .codecs = {"INTC10B0"} +}; + static const struct snd_soc_acpi_codecs nvl_rt5682_rt5682s_hp = { .num_codecs = 2, .codecs = {RT5682_ACPI_HID, RT5682S_ACPI_HID}, }; struct snd_soc_acpi_mach snd_soc_acpi_intel_nvl_machines[] = { + { + .comp_ids = &nvl_essx_83x6, + .drv_name = "nvl_es83x6_c1_h02", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &nvl_lt6911_hdmi, + .sof_tplg_filename = "sof-nvl-es83x6-ssp1-hdmi-ssp02.tplg", + }, { .comp_ids = &nvl_essx_83x6, .drv_name = "sof-essx8336", From d10549c4bcaad7ef69641c58db15470ec6ec5761 Mon Sep 17 00:00:00 2001 From: Balamurugan C Date: Thu, 6 Aug 2026 18:57:42 +0800 Subject: [PATCH 422/612] ASoC: Intel: sof_rt5682: Add HDMI-In capture with rt5682 support for NVL. Added match table entry on nvl machines to support HDMI-In capture with rt5682 I2S audio codec. also added the respective quirk configuration in rt5682 machine driver. Signed-off-by: Balamurugan C Reviewed-by: Liam Girdwood Signed-off-by: Bard Liao Link: https://patch.msgid.link/20260806105742.2676322-3-yung-chuan.liao@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/sof_rt5682.c | 7 +++++++ sound/soc/intel/common/soc-acpi-intel-nvl-match.c | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/sound/soc/intel/boards/sof_rt5682.c b/sound/soc/intel/boards/sof_rt5682.c index 649378957b2038..7899f7ffd99b73 100644 --- a/sound/soc/intel/boards/sof_rt5682.c +++ b/sound/soc/intel/boards/sof_rt5682.c @@ -915,6 +915,13 @@ static const struct platform_device_id board_ids[] = { /* SSP 0 and SSP 2 are used for HDMI IN */ SOF_SSP_MASK_HDMI_CAPTURE(0x5)), }, + { + .name = "nvl_rt5682_c1_h02", + .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | + SOF_SSP_PORT_CODEC(1) | + /* SSP 0 and SSP 2 are used for HDMI IN */ + SOF_SSP_MASK_HDMI_CAPTURE(0x5)), + }, { } }; MODULE_DEVICE_TABLE(platform, board_ids); diff --git a/sound/soc/intel/common/soc-acpi-intel-nvl-match.c b/sound/soc/intel/common/soc-acpi-intel-nvl-match.c index 51bbae5ed8fc8b..4a6208f821eb09 100644 --- a/sound/soc/intel/common/soc-acpi-intel-nvl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-nvl-match.c @@ -42,6 +42,13 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_nvl_machines[] = { SND_SOC_ACPI_TPLG_INTEL_SSP_MSB | SND_SOC_ACPI_TPLG_INTEL_DMIC_NUMBER, }, + { + .comp_ids = &nvl_rt5682_rt5682s_hp, + .drv_name = "nvl_rt5682_c1_h02", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &nvl_lt6911_hdmi, + .sof_tplg_filename = "sof-nvl-rt5682-ssp1-hdmi-ssp02.tplg", + }, { .comp_ids = &nvl_rt5682_rt5682s_hp, .drv_name = "sof_rt5682", From 54b279699279411c77c8afbc73b83c70740a7303 Mon Sep 17 00:00:00 2001 From: Rudi Heitbaum Date: Wed, 5 Aug 2026 15:21:02 +0000 Subject: [PATCH 423/612] ASoC: rt5645: Perform the initial jack detect at probe The only initial jack detect is the rt5645_irq(0, rt5645) at the end of rt5645_set_jack_detect(). A card described with simple-audio-card has no machine driver to call that, so jack state is only ever sampled from an edge on hp-detect-gpios. A headphone already in the socket at boot is therefore never noticed, and the card is silent with every mixer control set correctly. rt5645_jack_detect() is what force enables the "LDO2" and "Mic Det Power" supplies that the "HP amp" widget depends on, and what programs RT5645_CHARGE_PUMP away from its reset value, so without it "HP amp" cannot power up. Unplugging and replugging the jack is the only way to recover. Do the detect at the end of the component probe when the driver owns a hp-detect GPIO and the codec's own jack detect is unused, which is the case that has no other trigger. A machine driver calling rt5645_set_jack_detect() later just repeats it. Signed-off-by: Rudi Heitbaum Link: https://patch.msgid.link/anNU3tOUR7rOReSB@5e001e58230e Signed-off-by: Mark Brown --- sound/soc/codecs/rt5645.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c index e9819653b30d6a..e7e39671a5763d 100644 --- a/sound/soc/codecs/rt5645.c +++ b/sound/soc/codecs/rt5645.c @@ -3496,6 +3496,10 @@ static int rt5645_probe(struct snd_soc_component *component) if (!rt5645->eq_param) return -ENOMEM; + /* no machine driver to call rt5645_set_jack_detect(), so detect here */ + if (!rt5645->pdata.jd_mode && rt5645->gpiod_hp_det) + rt5645_irq(0, rt5645); + return 0; } From 530e344e4b7ebaef88b845c3a2413a114f517d79 Mon Sep 17 00:00:00 2001 From: Le Qi Date: Mon, 10 Aug 2026 11:10:32 +0800 Subject: [PATCH 424/612] ASoC: qcom: sc8280xp: configure codec sysclk for QCS615 Continuous high-amplitude noise could occur in the DA7213 microphone capture path after a Bluetooth out-of-range/reset event followed by reconnection. The noise was present in both the raw ALSA capture and PipeWire input, confirming that it originated before Bluetooth encoding. The codec already obtains and enables MCLK through its DT clock and bias-level handling. However, the machine driver did not explicitly configure the codec sysclk during hw_params(). Enable codec_sysclk_set for QCS615 so that the DA7213 clock source and rate are configured before the codec power-up sequence. Verified on QCS615 Talos with repeated Bluetooth disconnect and reconnect cycles. The noise was no longer reproducible. Signed-off-by: Le Qi Link: https://patch.msgid.link/20260810031032.2001053-1-le.qi@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/qcom/sc8280xp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/qcom/sc8280xp.c b/sound/soc/qcom/sc8280xp.c index 597c0d887d2f4a..913ae81be42408 100644 --- a/sound/soc/qcom/sc8280xp.c +++ b/sound/soc/qcom/sc8280xp.c @@ -462,6 +462,7 @@ static const struct qcom_snd_soc_common qcs615_priv_data = { .driver_name = "qcs615", .dapm_widgets = sc8280xp_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), + .codec_sysclk_set = true, }; static const struct qcom_snd_soc_common qcm6490_priv_data = { From 3eea69748a49b179642e4743dd0ea922ec0da354 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 6 Aug 2026 15:59:33 +0200 Subject: [PATCH 425/612] ASoC: aw87390: Use auto-cleanup for firmware loading Simplify the code to manage the firmware loading with __free(firmware) auto-cleanup. Only the code refactoring, no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260806140006.1412298-2-tiwai@suse.de Signed-off-by: Mark Brown --- sound/soc/codecs/aw87390.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/sound/soc/codecs/aw87390.c b/sound/soc/codecs/aw87390.c index 8150670fde2d80..5555c45dffcceb 100644 --- a/sound/soc/codecs/aw87390.c +++ b/sound/soc/codecs/aw87390.c @@ -248,7 +248,7 @@ static const struct snd_kcontrol_new aw87390_controls[] = { static int aw87390_request_firmware_file(struct aw87390 *aw87390) { - const struct firmware *cont = NULL; + const struct firmware *cont __free(firmware) = NULL; int ret; aw87390->aw_pa->fw_status = AW87390_DEV_FW_FAILED; @@ -263,14 +263,11 @@ static int aw87390_request_firmware_file(struct aw87390 *aw87390) aw87390->aw_cfg = devm_kzalloc(aw87390->aw_pa->dev, struct_size(aw87390->aw_cfg, data, cont->size), GFP_KERNEL); - if (!aw87390->aw_cfg) { - release_firmware(cont); + if (!aw87390->aw_cfg) return -ENOMEM; - } aw87390->aw_cfg->len = cont->size; memcpy(aw87390->aw_cfg->data, cont->data, cont->size); - release_firmware(cont); ret = aw88395_dev_load_acf_check(aw87390->aw_pa, aw87390->aw_cfg); if (ret) { From a26aa707bdf60d36d76cce03580e1bd744b03c69 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 6 Aug 2026 15:59:34 +0200 Subject: [PATCH 426/612] ASoC: aw88081: Use auto-cleanup for firmware loading Simplify the code to manage the firmware loading with __free(firmware) auto-cleanup. Only the code refactoring, no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260806140006.1412298-3-tiwai@suse.de Signed-off-by: Mark Brown --- sound/soc/codecs/aw88081.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/sound/soc/codecs/aw88081.c b/sound/soc/codecs/aw88081.c index a3cc027de60659..3247ba5c71b833 100644 --- a/sound/soc/codecs/aw88081.c +++ b/sound/soc/codecs/aw88081.c @@ -1133,7 +1133,7 @@ static int aw88081_dev_init(struct aw88081 *aw88081, struct aw_container *aw_cfg static int aw88081_request_firmware_file(struct aw88081 *aw88081) { - const struct firmware *cont = NULL; + const struct firmware *cont __free(firmware) = NULL; struct aw_container *aw_cfg; int ret; @@ -1147,17 +1147,14 @@ static int aw88081_request_firmware_file(struct aw88081 *aw88081) AW88081_ACF_FILE, cont ? cont->size : 0); aw_cfg = devm_kzalloc(aw88081->aw_pa->dev, struct_size(aw_cfg, data, cont->size), GFP_KERNEL); - if (!aw_cfg) { - release_firmware(cont); + if (!aw_cfg) return -ENOMEM; - } + aw_cfg->len = (int)cont->size; memcpy(aw_cfg->data, cont->data, cont->size); aw88081->aw_cfg = aw_cfg; - release_firmware(cont); - ret = aw88395_dev_load_acf_check(aw88081->aw_pa, aw88081->aw_cfg); if (ret) return ret; From dba82cc766613856a2b214936720c2f62bc07758 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 6 Aug 2026 15:59:35 +0200 Subject: [PATCH 427/612] ASoC: aw88166: Use auto-cleanup for firmware loading Simplify the code to manage the firmware loading with __free(firmware) auto-cleanup. Only the code refactoring, no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260806140006.1412298-4-tiwai@suse.de Signed-off-by: Mark Brown --- sound/soc/codecs/aw88166.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/sound/soc/codecs/aw88166.c b/sound/soc/codecs/aw88166.c index b72f87f677ddf4..d2a138211c596f 100644 --- a/sound/soc/codecs/aw88166.c +++ b/sound/soc/codecs/aw88166.c @@ -1570,7 +1570,7 @@ static int aw88166_dev_init(struct aw88166 *aw88166, struct aw_container *aw_cfg static int aw88166_request_firmware_file(struct aw88166 *aw88166) { - const struct firmware *cont = NULL; + const struct firmware *cont __free(firmware) = NULL; const char *fw_name; int ret; @@ -1590,13 +1590,11 @@ static int aw88166_request_firmware_file(struct aw88166 *aw88166) aw88166->aw_cfg = devm_kzalloc(aw88166->aw_pa->dev, struct_size(aw88166->aw_cfg, data, cont->size), GFP_KERNEL); - if (!aw88166->aw_cfg) { - release_firmware(cont); + if (!aw88166->aw_cfg) return -ENOMEM; - } + aw88166->aw_cfg->len = (int)cont->size; memcpy(aw88166->aw_cfg->data, cont->data, cont->size); - release_firmware(cont); ret = aw88395_dev_load_acf_check(aw88166->aw_pa, aw88166->aw_cfg); if (ret) { From 037729f509fe0b0ad0b3f02d8eb4e95e961e0ef6 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 6 Aug 2026 15:59:36 +0200 Subject: [PATCH 428/612] ASoC: aw88261: Use auto-cleanup for firmware loading Simplify the code to manage the firmware loading with __free(firmware) auto-cleanup. Only the code refactoring, no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260806140006.1412298-5-tiwai@suse.de Signed-off-by: Mark Brown --- sound/soc/codecs/aw88261.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/sound/soc/codecs/aw88261.c b/sound/soc/codecs/aw88261.c index acbd7de2e40e97..b8d06534bc8ff1 100644 --- a/sound/soc/codecs/aw88261.c +++ b/sound/soc/codecs/aw88261.c @@ -1149,7 +1149,7 @@ static int aw88261_dev_init(struct aw88261 *aw88261, struct aw_container *aw_cfg static int aw88261_request_firmware_file(struct aw88261 *aw88261) { - const struct firmware *cont = NULL; + const struct firmware *cont __free(firmware) = NULL; struct aw_container *aw_cfg; const char *fw_name; int ret; @@ -1169,13 +1169,11 @@ static int aw88261_request_firmware_file(struct aw88261 *aw88261) fw_name, cont ? cont->size : 0); aw_cfg = devm_kzalloc(aw88261->aw_pa->dev, struct_size(aw_cfg, data, cont->size), GFP_KERNEL); - if (!aw_cfg) { - release_firmware(cont); + if (!aw_cfg) return -ENOMEM; - } + aw_cfg->len = (int)cont->size; memcpy(aw_cfg->data, cont->data, cont->size); - release_firmware(cont); aw88261->aw_cfg = aw_cfg; From d8e40355d9a3fe58b3e0c87e5d19a6ffd3710f65 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 6 Aug 2026 15:59:37 +0200 Subject: [PATCH 429/612] ASoC: aw88395: Use auto-cleanup for firmware loading Simplify the code to manage the firmware loading with __free(firmware) auto-cleanup. Only the code refactoring, no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260806140006.1412298-6-tiwai@suse.de Signed-off-by: Mark Brown --- sound/soc/codecs/aw88395/aw88395.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/sound/soc/codecs/aw88395/aw88395.c b/sound/soc/codecs/aw88395/aw88395.c index e9ff2c79ac1523..2f4108624d1393 100644 --- a/sound/soc/codecs/aw88395/aw88395.c +++ b/sound/soc/codecs/aw88395/aw88395.c @@ -457,7 +457,7 @@ static void aw88395_hw_reset(struct aw88395 *aw88395) static int aw88395_request_firmware_file(struct aw88395 *aw88395) { - const struct firmware *cont = NULL; + const struct firmware *cont __free(firmware) = NULL; struct aw_container *aw_cfg; int ret; @@ -473,13 +473,11 @@ static int aw88395_request_firmware_file(struct aw88395 *aw88395) AW88395_ACF_FILE, cont ? cont->size : 0); aw_cfg = devm_kzalloc(aw88395->aw_pa->dev, struct_size(aw_cfg, data, cont->size), GFP_KERNEL); - if (!aw_cfg) { - release_firmware(cont); + if (!aw_cfg) return -ENOMEM; - } + aw_cfg->len = (int)cont->size; memcpy(aw_cfg->data, cont->data, cont->size); - release_firmware(cont); aw88395->aw_cfg = aw_cfg; From 1767b85125e2e3f8dc9623bc89015402fffcd341 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 6 Aug 2026 15:59:38 +0200 Subject: [PATCH 430/612] ASoC: aw88399: Use auto-cleanup for firmware loading Simplify the code to manage the firmware loading with __free(firmware) auto-cleanup. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260806140006.1412298-7-tiwai@suse.de Signed-off-by: Mark Brown --- sound/soc/codecs/aw88399-lib.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/sound/soc/codecs/aw88399-lib.c b/sound/soc/codecs/aw88399-lib.c index 5c7982891def28..809c2faa0c0c7d 100644 --- a/sound/soc/codecs/aw88399-lib.c +++ b/sound/soc/codecs/aw88399-lib.c @@ -1282,7 +1282,7 @@ static int aw88399_dev_init(struct aw88399 *aw88399, struct aw_container *aw_cfg int aw88399_request_firmware_file(struct aw88399 *aw88399) { - const struct firmware *cont = NULL; + const struct firmware *cont __free(firmware) = NULL; int ret; aw88399->aw_pa->fw_status = AW88399_DEV_FW_FAILED; @@ -1298,13 +1298,11 @@ int aw88399_request_firmware_file(struct aw88399 *aw88399) aw88399->aw_cfg = devm_kzalloc(aw88399->aw_pa->dev, struct_size(aw88399->aw_cfg, data, cont->size), GFP_KERNEL); - if (!aw88399->aw_cfg) { - release_firmware(cont); + if (!aw88399->aw_cfg) return -ENOMEM; - } + aw88399->aw_cfg->len = (int)cont->size; memcpy(aw88399->aw_cfg->data, cont->data, cont->size); - release_firmware(cont); ret = aw88395_dev_load_acf_check(aw88399->aw_pa, aw88399->aw_cfg); if (ret) { From aec8a1e14a80abbcf895b4780d68831d86cfcb88 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 6 Aug 2026 15:59:39 +0200 Subject: [PATCH 431/612] ASoC: fs-amp-lib: Use auto-cleanup for firmware loading Simplify the code to manage the firmware loading with __free(firmware) auto-cleanup. Only the code refactoring, no functional changes. Cc: Nick Li Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260806140006.1412298-8-tiwai@suse.de Signed-off-by: Mark Brown --- sound/soc/codecs/fs-amp-lib.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sound/soc/codecs/fs-amp-lib.c b/sound/soc/codecs/fs-amp-lib.c index c8f56617e370b2..a562c72fc52476 100644 --- a/sound/soc/codecs/fs-amp-lib.c +++ b/sound/soc/codecs/fs-amp-lib.c @@ -221,7 +221,7 @@ static void fs_print_firmware_info(struct fs_amp_lib *amp_lib) int fs_amp_load_firmware(struct fs_amp_lib *amp_lib, const char *name) { - const struct firmware *cont; + const struct firmware *cont __free(firmware) = NULL; struct fs_fwm_header *hdr; int ret; @@ -237,7 +237,6 @@ int fs_amp_load_firmware(struct fs_amp_lib *amp_lib, const char *name) dev_info(amp_lib->dev, "Loading %s - size: %zu\n", name, cont->size); hdr = devm_kmemdup(amp_lib->dev, cont->data, cont->size, GFP_KERNEL); - release_firmware(cont); if (!hdr) return -ENOMEM; From a57beee8f816cc43fe0e78b794fa6cb54b3a2034 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 6 Aug 2026 15:59:40 +0200 Subject: [PATCH 432/612] ASoC: hdac_hda: Use auto-cleanup for firmware loading Simplify the code to manage the firmware loading with __free(firmware) auto-cleanup. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260806140006.1412298-9-tiwai@suse.de Signed-off-by: Mark Brown --- sound/soc/codecs/hdac_hda.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sound/soc/codecs/hdac_hda.c b/sound/soc/codecs/hdac_hda.c index 1ab5f8a26e03b5..1c06fdbf0e712e 100644 --- a/sound/soc/codecs/hdac_hda.c +++ b/sound/soc/codecs/hdac_hda.c @@ -437,7 +437,7 @@ static int hdac_hda_codec_probe(struct snd_soc_component *component) #ifdef CONFIG_SND_HDA_PATCH_LOADER if (loadable_patch[hda_pvt->dev_index] && *loadable_patch[hda_pvt->dev_index]) { - const struct firmware *fw; + const struct firmware *fw __free(firmware) = NULL; dev_info(&hdev->dev, "Applying patch firmware '%s'\n", loadable_patch[hda_pvt->dev_index]); @@ -451,7 +451,6 @@ static int hdac_hda_codec_probe(struct snd_soc_component *component) dev_err(&hdev->dev, "%s: failed to load hda patch %d\n", __func__, ret); goto error_no_pm; } - release_firmware(fw); } } #endif From b37d70e5bfc32be630b2b86943f0c9f49382eee3 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 6 Aug 2026 15:59:41 +0200 Subject: [PATCH 433/612] ASoC: max98390: Use auto-cleanup for firmware loading Simplify the code to manage the firmware loading with __free(firmware) auto-cleanup. Only the code refactoring, no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260806140006.1412298-10-tiwai@suse.de Signed-off-by: Mark Brown --- sound/soc/codecs/max98390.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/sound/soc/codecs/max98390.c b/sound/soc/codecs/max98390.c index 66309e87fdbd8c..2295fc057c71d5 100644 --- a/sound/soc/codecs/max98390.c +++ b/sound/soc/codecs/max98390.c @@ -788,7 +788,6 @@ static int max98390_dsm_init(struct snd_soc_component *component) const char *vendor, *product; struct max98390_priv *max98390 = snd_soc_component_get_drvdata(component); - const struct firmware *fw; char *dsm_param; vendor = dmi_get_system_info(DMI_SYS_VENDOR); @@ -805,6 +804,8 @@ static int max98390_dsm_init(struct snd_soc_component *component) snprintf(filename, sizeof(filename), "%s", max98390->dsm_param_name); } + + const struct firmware *fw __free(firmware) = NULL; ret = request_firmware(&fw, filename, component->dev); if (ret) { ret = request_firmware(&fw, "dsm_param.bin", component->dev); @@ -812,7 +813,7 @@ static int max98390_dsm_init(struct snd_soc_component *component) ret = request_firmware(&fw, "dsmparam.bin", component->dev); if (ret) - goto err; + return ret; } } @@ -822,8 +823,7 @@ static int max98390_dsm_init(struct snd_soc_component *component) if (fw->size < MAX98390_DSM_PARAM_MIN_SIZE) { dev_err(component->dev, "param fw is invalid.\n"); - ret = -EINVAL; - goto err_alloc; + return -EINVAL; } dsm_param = (char *)fw->data; param_start_addr = (dsm_param[0] & 0xff) | (dsm_param[1] & 0xff) << 8; @@ -833,8 +833,7 @@ static int max98390_dsm_init(struct snd_soc_component *component) fw->size < param_size + MAX98390_DSM_PAYLOAD_OFFSET) { dev_err(component->dev, "param fw is invalid.\n"); - ret = -EINVAL; - goto err_alloc; + return -EINVAL; } regmap_write(max98390->regmap, MAX98390_R203A_AMP_EN, 0x80); dsm_param += MAX98390_DSM_PAYLOAD_OFFSET; @@ -842,10 +841,7 @@ static int max98390_dsm_init(struct snd_soc_component *component) dsm_param, param_size); regmap_write(max98390->regmap, MAX98390_R23E1_DSP_GLOBAL_EN, 0x01); -err_alloc: - release_firmware(fw); -err: - return ret; + return 0; } static void max98390_init_regs(struct snd_soc_component *component) From 5dd15c805eefd56baabdca3cbe8f64e6c5f02eba Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 6 Aug 2026 15:59:42 +0200 Subject: [PATCH 434/612] ASoC: ntpfw: Use auto-cleanup for firmware loading Simplify the code to manage the firmware loading with __free(firmware) auto-cleanup. Only the code refactoring, no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260806140006.1412298-11-tiwai@suse.de Signed-off-by: Mark Brown --- sound/soc/codecs/ntpfw.c | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/sound/soc/codecs/ntpfw.c b/sound/soc/codecs/ntpfw.c index 5ced2e966ab7d2..b6443e24ae8eea 100644 --- a/sound/soc/codecs/ntpfw.c +++ b/sound/soc/codecs/ntpfw.c @@ -89,7 +89,7 @@ int ntpfw_load(struct i2c_client *i2c, const char *name, u32 magic) { struct device *dev = &i2c->dev; const struct ntpfw_chunk *chunk; - const struct firmware *fw; + const struct firmware *fw __free(firmware) = NULL; const u8 *data; size_t leftover; int ret; @@ -101,10 +101,8 @@ int ntpfw_load(struct i2c_client *i2c, const char *name, u32 magic) return ret; } - if (!ntpfw_verify(dev, fw->data, fw->size, magic)) { - ret = -EINVAL; - goto done; - } + if (!ntpfw_verify(dev, fw->data, fw->size, magic)) + return -EINVAL; data = fw->data + sizeof(struct ntpfw_header); leftover = fw->size - sizeof(struct ntpfw_header); @@ -112,23 +110,18 @@ int ntpfw_load(struct i2c_client *i2c, const char *name, u32 magic) while (leftover) { chunk = (struct ntpfw_chunk *)data; - if (!ntpfw_verify_chunk(dev, chunk, leftover)) { - ret = -EINVAL; - goto done; - } + if (!ntpfw_verify_chunk(dev, chunk, leftover)) + return -EINVAL; ret = ntpfw_send_chunk(i2c, chunk); if (ret) - goto done; + return ret; data += be16_to_cpu(chunk->length) + sizeof(*chunk); leftover -= be16_to_cpu(chunk->length) + sizeof(*chunk); } -done: - release_firmware(fw); - - return ret; + return 0; } EXPORT_SYMBOL_GPL(ntpfw_load); From c0124e91a9f2f4b8a466f4685631ffb415d21501 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 6 Aug 2026 15:59:43 +0200 Subject: [PATCH 435/612] ASoC: pcm6240: Use auto-cleanup for firmware loading Simplify the code to manage the firmware loading with __free(firmware) auto-cleanup. Since the firmware release is cleaned up automatically, we can convert the mutex call with guard() gracefully, too. Only the code refactoring, no functional changes. Reviewed-by: Herve Codina Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260806140006.1412298-12-tiwai@suse.de Signed-off-by: Mark Brown --- sound/soc/codecs/pcm6240.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/sound/soc/codecs/pcm6240.c b/sound/soc/codecs/pcm6240.c index a2b66eae6ac48c..db85ae2f8aed05 100644 --- a/sound/soc/codecs/pcm6240.c +++ b/sound/soc/codecs/pcm6240.c @@ -1577,10 +1577,10 @@ static int pcmdevice_comp_probe(struct snd_soc_component *comp) { struct pcmdevice_priv *pcm_dev = snd_soc_component_get_drvdata(comp); struct i2c_adapter *adap = pcm_dev->client->adapter; - const struct firmware *fw_entry = NULL; + const struct firmware *fw_entry __free(firmware) = NULL; int ret, i, j; - mutex_lock(&pcm_dev->codec_lock); + guard(mutex)(&pcm_dev->codec_lock); pcm_dev->component = comp; @@ -1588,7 +1588,7 @@ static int pcmdevice_comp_probe(struct snd_soc_component *comp) for (j = 0; j < 2; j++) { ret = pcmdev_gain_ctrl_add(pcm_dev, i, j); if (ret < 0) - goto out; + return ret; } } @@ -1621,21 +1621,17 @@ static int pcmdevice_comp_probe(struct snd_soc_component *comp) if (ret) { dev_err(pcm_dev->dev, "%s: request %s err = %d\n", __func__, pcm_dev->bin_name, ret); - goto out; + return ret; } ret = pcmdev_regbin_ready(fw_entry, pcm_dev); if (ret) { dev_err(pcm_dev->dev, "%s: %s parse err = %d\n", __func__, pcm_dev->bin_name, ret); - goto out; + return ret; } - ret = pcmdev_profile_ctrl_add(pcm_dev); -out: - release_firmware(fw_entry); - mutex_unlock(&pcm_dev->codec_lock); - return ret; + return pcmdev_profile_ctrl_add(pcm_dev); } From 38a3855d0c378d3e414ea2eb4fd4e0e099ff95fa Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 6 Aug 2026 15:59:44 +0200 Subject: [PATCH 436/612] ASoC: peb2466: Use auto-cleanup for firmware loading Simplify the code to manage the firmware loading with __free(firmware) auto-cleanup. Only the code refactoring, no functional changes. Reviewed-by: Herve Codina Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260806140006.1412298-13-tiwai@suse.de Signed-off-by: Mark Brown --- sound/soc/codecs/peb2466.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/sound/soc/codecs/peb2466.c b/sound/soc/codecs/peb2466.c index 5a1ed02abb843c..f1ded68c2d7563 100644 --- a/sound/soc/codecs/peb2466.c +++ b/sound/soc/codecs/peb2466.c @@ -1538,17 +1538,14 @@ static int peb2466_fw_parse(struct snd_soc_component *component, static int peb2466_load_coeffs(struct snd_soc_component *component, const char *fw_name) { - const struct firmware *fw; + const struct firmware *fw __free(firmware) = NULL; int ret; ret = request_firmware(&fw, fw_name, component->dev); if (ret) return ret; - ret = peb2466_fw_parse(component, fw->data, fw->size); - release_firmware(fw); - - return ret; + return peb2466_fw_parse(component, fw->data, fw->size); } static int peb2466_component_probe(struct snd_soc_component *component) From a372669b8dec902533603fcdca23e5aef69e98ee Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 6 Aug 2026 15:59:45 +0200 Subject: [PATCH 437/612] ASoC: rt1320-sdw: Use auto-cleanup for firmware loading Simplify the code to manage the firmware loading with __free(firmware) auto-cleanup. Only the code refactoring, no functional changes. Cc: Oder Chiou Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260806140006.1412298-14-tiwai@suse.de Signed-off-by: Mark Brown --- sound/soc/codecs/rt1320-sdw.c | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/sound/soc/codecs/rt1320-sdw.c b/sound/soc/codecs/rt1320-sdw.c index 3a5eebcfefdd5e..8d2d6697a50279 100644 --- a/sound/soc/codecs/rt1320-sdw.c +++ b/sound/soc/codecs/rt1320-sdw.c @@ -1786,7 +1786,7 @@ static int rt1320_r0_cali_put(struct snd_kcontrol *kcontrol, static void rt1320_load_mcu_patch(struct rt1320_sdw_priv *rt1320) { struct sdw_slave *slave = rt1320->sdw_slave; - const struct firmware *patch; + const struct firmware *patch __free(firmware) = NULL; const char *filename; unsigned int addr, val, min_addr, max_addr; const unsigned char *ptr; @@ -1840,17 +1840,15 @@ static void rt1320_load_mcu_patch(struct rt1320_sdw_priv *rt1320) if (addr > max_addr || addr < min_addr) { dev_err(&slave->dev, "%s: the address 0x%x is wrong", __func__, addr); - goto _exit_; + return; } if (val > 0xff) { dev_err(&slave->dev, "%s: the value 0x%x is wrong", __func__, val); - goto _exit_; + return; } regmap_write(rt1320->regmap, addr, val); } } -_exit_: - release_firmware(patch); } } @@ -1924,7 +1922,7 @@ static int rt1320_rae_load(struct rt1320_sdw_priv *rt1320) struct device *dev = &rt1320->sdw_slave->dev; static const char func_tag[] = "FUNC"; static const char xu_tag[] = "XU"; - const struct firmware *rae_fw = NULL; + const struct firmware *rae_fw __free(firmware) = NULL; unsigned int fw_offset; unsigned char *fw_data; unsigned char *param_data; @@ -1977,7 +1975,6 @@ static int rt1320_rae_load(struct rt1320_sdw_priv *rt1320) } if (!retry && !(value & 0x40)) { dev_err(dev, "%s: RAE is not ready to load\n", __func__); - release_firmware(rae_fw); return -ETIMEDOUT; } break; @@ -1998,7 +1995,6 @@ static int rt1320_rae_load(struct rt1320_sdw_priv *rt1320) } if (!retry && !(value & 0x40)) { dev_err(dev, "%s: RAE is not ready to load\n", __func__); - release_firmware(rae_fw); return -ETIMEDOUT; } break; @@ -2057,7 +2053,6 @@ static int rt1320_rae_load(struct rt1320_sdw_priv *rt1320) } regcache_cache_bypass(rt1320->regmap, false); - release_firmware(rae_fw); } else { dev_err(dev, "%s: Failed to load %s firmware\n", __func__, rae_filename); @@ -2124,7 +2119,7 @@ struct rt1320_dspfwheader { struct rt1320_dspfwheader *fwheader; struct rt1320_imageinfo *ptr_img; struct sdw_bpt_section sec[10]; - const struct firmware *fw = NULL; + const struct firmware *fw __free(firmware) = NULL; unsigned char *fw_data; bool dev_fw_match = false; static const char hdr_sig[] = "AFX"; @@ -2178,7 +2173,6 @@ struct rt1320_dspfwheader { if (fwheader->sync != 0x0a1c5679) { dev_err(dev, "%s: FW sync error\n", __func__); - release_firmware(fw); goto _exit_; } @@ -2256,7 +2250,6 @@ struct rt1320_dspfwheader { } regcache_cache_bypass(rt1320->regmap, false); - release_firmware(fw); if (!dev_fw_match) { dev_err(dev, "%s: FW file doesn't match to device\n", __func__); From c36d435b745582cb740cfa6f748f4535c322b0d2 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 6 Aug 2026 15:59:46 +0200 Subject: [PATCH 438/612] ASoC: rt5575: Use auto-cleanup for firmware loading Simplify the code to manage the firmware loading with __free(firmware) auto-cleanup. Only the code refactoring, no functional changes. Cc: Oder Chiou Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260806140006.1412298-15-tiwai@suse.de Signed-off-by: Mark Brown --- sound/soc/codecs/rt5575-spi.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sound/soc/codecs/rt5575-spi.c b/sound/soc/codecs/rt5575-spi.c index d5b3a57c886664..750f1f7169e690 100644 --- a/sound/soc/codecs/rt5575-spi.c +++ b/sound/soc/codecs/rt5575-spi.c @@ -93,7 +93,6 @@ static void rt5575_spi_burst_write(struct spi_device *spi, u32 addr, const u8 *t int rt5575_spi_fw_load(struct spi_device *spi) { struct device *dev = &spi->dev; - const struct firmware *firmware; int i, ret; static const char * const fw_path[] = { "realtek/rt5575/rt5575_fw1.bin", @@ -104,6 +103,7 @@ int rt5575_spi_fw_load(struct spi_device *spi) static const u32 fw_addr[] = { 0x5f400000, 0x5f600000, 0x5f7fe000, 0x5f7ff000 }; for (i = 0; i < ARRAY_SIZE(fw_addr); i++) { + const struct firmware *firmware __free(firmware) = NULL; ret = request_firmware(&firmware, fw_path[i], dev); if (ret) { dev_err(dev, "Request firmware failure: %d\n", ret); @@ -111,7 +111,6 @@ int rt5575_spi_fw_load(struct spi_device *spi) } rt5575_spi_burst_write(spi, fw_addr[i], firmware->data, firmware->size); - release_firmware(firmware); } return 0; From 0582725c6b4252bf56afafbb19e26fd6c00f634e Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 6 Aug 2026 15:59:47 +0200 Subject: [PATCH 439/612] ASoC: rt5677: Use auto-cleanup for firmware loading Simplify the code to manage the firmware loading with __free(firmware) auto-cleanup. Only the code refactoring, no functional changes. Cc: Oder Chiou Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260806140006.1412298-16-tiwai@suse.de Signed-off-by: Mark Brown --- sound/soc/codecs/rt5677.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/sound/soc/codecs/rt5677.c b/sound/soc/codecs/rt5677.c index 3e4d1dbce740e7..4757017cc83d85 100644 --- a/sound/soc/codecs/rt5677.c +++ b/sound/soc/codecs/rt5677.c @@ -849,11 +849,11 @@ static int rt5677_parse_and_load_dsp(struct rt5677_priv *rt5677, const u8 *buf, static int rt5677_load_dsp_from_file(struct rt5677_priv *rt5677) { - const struct firmware *fwp; struct device *dev = rt5677->component->dev; - int ret = 0; + int ret; /* Load dsp firmware from rt5677_elf_vad file */ + const struct firmware *fwp __free(firmware) = NULL; ret = request_firmware(&fwp, "rt5677_elf_vad", dev); if (ret) { dev_err(dev, "Request rt5677_elf_vad failed %d\n", ret); @@ -861,9 +861,7 @@ static int rt5677_load_dsp_from_file(struct rt5677_priv *rt5677) } dev_info(dev, "Requested rt5677_elf_vad (%zu)\n", fwp->size); - ret = rt5677_parse_and_load_dsp(rt5677, fwp->data, fwp->size); - release_firmware(fwp); - return ret; + return rt5677_parse_and_load_dsp(rt5677, fwp->data, fwp->size); } static int rt5677_set_dsp_vad(struct snd_soc_component *component, bool on) From d8c13497200faef7584d968b48abff073e1031fe Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 6 Aug 2026 15:59:48 +0200 Subject: [PATCH 440/612] ASoC: rt722-sdca: Use auto-cleanup for firmware loading Simplify the code to manage the firmware loading with __free(firmware) auto-cleanup, as well as the firmware file name being released with __free(kfree). Only the code refactoring, no functional changes. Cc: Oder Chiou Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260806140006.1412298-17-tiwai@suse.de Signed-off-by: Mark Brown --- sound/soc/codecs/rt722-sdca.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/sound/soc/codecs/rt722-sdca.c b/sound/soc/codecs/rt722-sdca.c index 27bb0eb1ece789..4cbe9e9095856f 100644 --- a/sound/soc/codecs/rt722-sdca.c +++ b/sound/soc/codecs/rt722-sdca.c @@ -352,8 +352,6 @@ static int rt722_cae_load(struct rt722_sdca_priv *rt722) static const char func_tag[] = "FUNC"; static const char xu_tag[] = "XU"; const char *dmi_vendor, *dmi_product, *dmi_sku; - char *cae_filename; - const struct firmware *cae_fw = NULL; unsigned int cae_st_spk, cae_st_hp, cae_st_mic; unsigned int func, value; unsigned int combined_val; @@ -385,7 +383,8 @@ static int rt722_cae_load(struct rt722_sdca_priv *rt722) space = strchr(dmi_sku, ' '); s_len = space ? space - dmi_sku : strlen(dmi_sku); - cae_filename = kasprintf(GFP_KERNEL, + char *cae_filename __free(kfree) = + kasprintf(GFP_KERNEL, "realtek/rt722/rt722_RAE_%.*s_%.*s_%.*s.dat", v_len, dmi_vendor, p_len, dmi_product, @@ -399,8 +398,8 @@ static int rt722_cae_load(struct rt722_sdca_priv *rt722) regmap_write(rt722->regmap, RT722_MIC_CAE_PARAM39, 0x5f); usleep_range(50000, 60000); + const struct firmware *cae_fw __free(firmware) = NULL; request_firmware(&cae_fw, cae_filename, dev); - kfree(cae_filename); if (!cae_fw) { dev_err(dev, "%s: Failed to load CAE firmware\n", __func__); return -ENOENT; @@ -555,7 +554,6 @@ static int rt722_cae_load(struct rt722_sdca_priv *rt722) regcache_cache_bypass(rt722->regmap, false); rt722->cae_update_done = 1; dev_dbg(dev, "%s: CAE FW update done.\n", __func__); - release_firmware(cae_fw); return 0; verify_abort: @@ -565,7 +563,6 @@ static int rt722_cae_load(struct rt722_sdca_priv *rt722) out_release: rt722_sdca_index_update_bits(rt722, RT722_VENDOR_REG, RT722_MISC_CTRL1, 0x8000, 0x0000); - release_firmware(cae_fw); dev_err(dev, "%s: CAE FW update aborted (ret=%d).\n", __func__, ret); return ret; } From 4eea8eb99d6cd78a0246d4f7da27d945e36f29ed Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 6 Aug 2026 15:59:49 +0200 Subject: [PATCH 441/612] ASoC: sigmadsp: se auto-cleanup for firmware loading MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Simplify the code to manage the firmware loading with __free(firmware) auto-cleanup. Cc: Lars-Peter Clausen Cc: Nuno Sá Acked-by: Nuno Sá Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260806140006.1412298-18-tiwai@suse.de Signed-off-by: Mark Brown --- sound/soc/codecs/sigmadsp.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/sound/soc/codecs/sigmadsp.c b/sound/soc/codecs/sigmadsp.c index b7dbeb2374473b..4ecbbac9325882 100644 --- a/sound/soc/codecs/sigmadsp.c +++ b/sound/soc/codecs/sigmadsp.c @@ -484,7 +484,7 @@ static void devm_sigmadsp_release(struct device *dev, void *res) static int sigmadsp_firmware_load(struct sigmadsp *sigmadsp, const char *name) { const struct sigma_firmware_header *ssfw_head; - const struct firmware *fw; + const struct firmware *fw __free(firmware) = NULL; int ret; u32 crc; @@ -492,7 +492,7 @@ static int sigmadsp_firmware_load(struct sigmadsp *sigmadsp, const char *name) ret = request_firmware(&fw, name, sigmadsp->dev); if (ret) { pr_debug("%s: request_firmware() failed with %i\n", __func__, ret); - goto done; + return ret; } /* then verify the header */ @@ -506,13 +506,13 @@ static int sigmadsp_firmware_load(struct sigmadsp *sigmadsp, const char *name) */ if (fw->size < sizeof(*ssfw_head) || fw->size >= 0x4000000) { dev_err(sigmadsp->dev, "Failed to load firmware: Invalid size\n"); - goto done; + return -EINVAL; } ssfw_head = (void *)fw->data; if (memcmp(ssfw_head->magic, SIGMA_MAGIC, ARRAY_SIZE(ssfw_head->magic))) { dev_err(sigmadsp->dev, "Failed to load firmware: Invalid magic\n"); - goto done; + return -EINVAL; } crc = crc32(0, fw->data + sizeof(*ssfw_head), @@ -521,7 +521,7 @@ static int sigmadsp_firmware_load(struct sigmadsp *sigmadsp, const char *name) if (crc != le32_to_cpu(ssfw_head->crc)) { dev_err(sigmadsp->dev, "Failed to load firmware: Wrong crc checksum: expected %x got %x\n", le32_to_cpu(ssfw_head->crc), crc); - goto done; + return -EINVAL; } switch (ssfw_head->version) { @@ -542,9 +542,6 @@ static int sigmadsp_firmware_load(struct sigmadsp *sigmadsp, const char *name) if (ret) sigmadsp_firmware_release(sigmadsp); -done: - release_firmware(fw); - return ret; } From 2afbdeb48302ab076e302f11be79e4323607ec15 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 6 Aug 2026 15:59:50 +0200 Subject: [PATCH 442/612] ASoC: sma1307: Use auto-cleanup for firmware loading Simplify the code to manage the firmware loading with __free(firmware) auto-cleanup. Only the code refactoring, no functional changes. Cc: Kiseok Jo Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260806140006.1412298-19-tiwai@suse.de Signed-off-by: Mark Brown --- sound/soc/codecs/sma1307.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/sound/soc/codecs/sma1307.c b/sound/soc/codecs/sma1307.c index c52fe95b30c6ad..adb369a29b9d31 100644 --- a/sound/soc/codecs/sma1307.c +++ b/sound/soc/codecs/sma1307.c @@ -1690,7 +1690,7 @@ static void sma1307_check_fault_worker(struct work_struct *work) static void sma1307_setting_loaded(struct sma1307_priv *sma1307, const char *file) { - const struct firmware *fw; + const struct firmware *fw __free(firmware) = NULL; int size, offset, num_mode; int ret; @@ -1703,22 +1703,18 @@ static void sma1307_setting_loaded(struct sma1307_priv *sma1307, const char *fil return; } else if ((fw->size) < SMA1307_SETTING_HEADER_SIZE) { dev_err(sma1307->dev, "%s: Invalid file\n", __func__); - release_firmware(fw); sma1307->set.status = false; return; } int *data __free(kfree) = kzalloc(fw->size, GFP_KERNEL); if (!data) { - release_firmware(fw); sma1307->set.status = false; return; } size = fw->size >> 2; memcpy(data, fw->data, fw->size); - release_firmware(fw); - /* HEADER */ sma1307->set.header_size = SMA1307_SETTING_HEADER_SIZE; sma1307->set.checksum = data[sma1307->set.header_size - 2]; From 37cb22498fa8d450064661c9f691f28ba1bae887 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 6 Aug 2026 15:59:51 +0200 Subject: [PATCH 443/612] ASoC: tas2781: Use auto-cleanup for firmware loading Simplify the code to manage the firmware loading with __free(firmware) auto-cleanup. Only the code refactoring, no functional changes. Cc: Shenghao Ding Cc: Kevin Lu Cc: Baojun Xu Cc: Sen Wang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260806140006.1412298-20-tiwai@suse.de Signed-off-by: Mark Brown --- sound/soc/codecs/tas2781-fmwlib.c | 45 +++++++++++-------------------- 1 file changed, 15 insertions(+), 30 deletions(-) diff --git a/sound/soc/codecs/tas2781-fmwlib.c b/sound/soc/codecs/tas2781-fmwlib.c index 11d1c2ac865b8c..df30abfb3b6b1e 100644 --- a/sound/soc/codecs/tas2781-fmwlib.c +++ b/sound/soc/codecs/tas2781-fmwlib.c @@ -2243,7 +2243,7 @@ int tas2781_load_calibration(void *context, char *file_name, { struct tasdevice_priv *tas_priv = (struct tasdevice_priv *)context; struct tasdevice *tasdev = &(tas_priv->tasdevice[i]); - const struct firmware *fw_entry = NULL; + const struct firmware *fw_entry __free(firmware) = NULL; struct tasdevice_fw *tas_fmw; struct firmware fmw; int offset = 0; @@ -2253,60 +2253,50 @@ int tas2781_load_calibration(void *context, char *file_name, if (ret) { dev_err(tas_priv->dev, "%s: Request firmware %s failed\n", __func__, file_name); - goto out; + return ret; } if (!fw_entry->size) { dev_err(tas_priv->dev, "%s: file read error: size = %lu\n", __func__, (unsigned long)fw_entry->size); - ret = -EINVAL; - goto out; + return -EINVAL; } fmw.size = fw_entry->size; fmw.data = fw_entry->data; tas_fmw = tasdev->cali_data_fmw = kzalloc_obj(struct tasdevice_fw); - if (!tasdev->cali_data_fmw) { - ret = -ENOMEM; - goto out; - } + if (!tasdev->cali_data_fmw) + return -ENOMEM; + tas_fmw->dev = tas_priv->dev; offset = fw_parse_header(tas_priv, tas_fmw, &fmw, offset); if (offset == -EINVAL) { dev_err(tas_priv->dev, "fw_parse_header EXIT!\n"); - ret = offset; - goto out; + return -EINVAL; } offset = fw_parse_variable_hdr_cal(tas_priv, tas_fmw, &fmw, offset); if (offset == -EINVAL) { dev_err(tas_priv->dev, "%s: fw_parse_variable_header_cal EXIT!\n", __func__); - ret = offset; - goto out; + return -EINVAL; } offset = fw_parse_program_data(tas_priv, tas_fmw, &fmw, offset); if (offset < 0) { dev_err(tas_priv->dev, "fw_parse_program_data EXIT!\n"); - ret = offset; - goto out; + return offset; } offset = fw_parse_configuration_data(tas_priv, tas_fmw, &fmw, offset); if (offset < 0) { dev_err(tas_priv->dev, "fw_parse_configuration_data EXIT!\n"); - ret = offset; - goto out; + return offset; } offset = fw_parse_calibration_data(tas_priv, tas_fmw, &fmw, offset); if (offset < 0) { dev_err(tas_priv->dev, "fw_parse_calibration_data EXIT!\n"); - ret = offset; - goto out; + return offset; } -out: - release_firmware(fw_entry); - - return ret; + return 0; } EXPORT_SYMBOL_NS_GPL(tas2781_load_calibration, "SND_SOC_TAS2781_FMWLIB"); @@ -2399,7 +2389,7 @@ static int tasdevice_dspfw_ready(const struct firmware *fmw, int tasdevice_dsp_parser(void *context) { struct tasdevice_priv *tas_priv = (struct tasdevice_priv *)context; - const struct firmware *fw_entry; + const struct firmware *fw_entry __free(firmware) = NULL; int ret; ret = request_firmware(&fw_entry, tas_priv->coef_binaryname, @@ -2407,15 +2397,10 @@ int tasdevice_dsp_parser(void *context) if (ret) { dev_err(tas_priv->dev, "%s: load %s error\n", __func__, tas_priv->coef_binaryname); - goto out; + return ret; } - ret = tasdevice_dspfw_ready(fw_entry, tas_priv); - release_firmware(fw_entry); - fw_entry = NULL; - -out: - return ret; + return tasdevice_dspfw_ready(fw_entry, tas_priv); } EXPORT_SYMBOL_NS_GPL(tasdevice_dsp_parser, "SND_SOC_TAS2781_FMWLIB"); From 834c7c851179d86f04c18f0e69466e395128875f Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 6 Aug 2026 15:59:52 +0200 Subject: [PATCH 444/612] ASoC: tas5805m: Use auto-cleanup for firmware loading Simplify the code to manage the firmware loading with __free(firmware) auto-cleanup. Only the code refactoring, no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260806140006.1412298-21-tiwai@suse.de Signed-off-by: Mark Brown --- sound/soc/codecs/tas5805m.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/sound/soc/codecs/tas5805m.c b/sound/soc/codecs/tas5805m.c index f76e04b403b5e7..d32796a6fa7528 100644 --- a/sound/soc/codecs/tas5805m.c +++ b/sound/soc/codecs/tas5805m.c @@ -457,7 +457,6 @@ static int tas5805m_i2c_probe(struct i2c_client *i2c) struct tas5805m_priv *tas5805m; char filename[128]; const char *config_name; - const struct firmware *fw; int ret; regmap = devm_regmap_init_i2c(i2c, &tas5805m_regmap); @@ -502,24 +501,20 @@ static int tas5805m_i2c_probe(struct i2c_client *i2c) snprintf(filename, sizeof(filename), "tas5805m_dsp_%s.bin", config_name); + const struct firmware *fw __free(firmware) = NULL; ret = request_firmware(&fw, filename, dev); if (ret) return ret; if ((fw->size < 2) || (fw->size & 1)) { dev_err(dev, "firmware is invalid\n"); - release_firmware(fw); return -EINVAL; } tas5805m->dsp_cfg_len = fw->size; tas5805m->dsp_cfg_data = devm_kmemdup(dev, fw->data, fw->size, GFP_KERNEL); - if (!tas5805m->dsp_cfg_data) { - release_firmware(fw); + if (!tas5805m->dsp_cfg_data) return -ENOMEM; - } - - release_firmware(fw); /* Do the first part of the power-on here, while we can expect * the I2S interface to be quiet. We must raise PDN# and then From e420e87ddc9abfc263c50acd77315486f3defbfa Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 6 Aug 2026 15:59:53 +0200 Subject: [PATCH 445/612] ASoC: tlv320aic31xx: Use auto-cleanup for firmware loading Simplify the code to manage the firmware loading with __free(firmware) auto-cleanup. Only the code refactoring, no functional changes. Cc: Shenghao Ding Cc: Kevin Lu Cc: Baojun Xu Cc: Sen Wang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260806140006.1412298-22-tiwai@suse.de Signed-off-by: Mark Brown --- sound/soc/codecs/tlv320aic31xx.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/sound/soc/codecs/tlv320aic31xx.c b/sound/soc/codecs/tlv320aic31xx.c index 1d2e0ea6d4fedd..43bcbc5449e1a3 100644 --- a/sound/soc/codecs/tlv320aic31xx.c +++ b/sound/soc/codecs/tlv320aic31xx.c @@ -1720,18 +1720,14 @@ static int tlv320dac3100_fw_load(struct aic31xx_priv *aic31xx, static int tlv320dac3100_load_coeffs(struct aic31xx_priv *aic31xx, const char *fw_name) { - const struct firmware *fw; + const struct firmware *fw __free(firmware) = NULL; int ret; ret = request_firmware(&fw, fw_name, aic31xx->dev); if (ret) return ret; - ret = tlv320dac3100_fw_load(aic31xx, fw->data, fw->size); - - release_firmware(fw); - - return ret; + return tlv320dac3100_fw_load(aic31xx, fw->data, fw->size); } static int aic31xx_i2c_probe(struct i2c_client *i2c) From 148f54aecccc769dd17414ea2e2a2b68f7b4ba4e Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 6 Aug 2026 15:59:54 +0200 Subject: [PATCH 446/612] ASoC: wm0010: Use auto-cleanup for firmware loading Simplify the code to manage the firmware loading with __free(firmware) auto-cleanup. Only the code refactoring, no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260806140006.1412298-23-tiwai@suse.de Signed-off-by: Mark Brown --- sound/soc/codecs/wm0010.c | 58 ++++++++++++++------------------------- 1 file changed, 21 insertions(+), 37 deletions(-) diff --git a/sound/soc/codecs/wm0010.c b/sound/soc/codecs/wm0010.c index 58c0c601ee6c7d..c44abffe9b5651 100644 --- a/sound/soc/codecs/wm0010.c +++ b/sound/soc/codecs/wm0010.c @@ -333,7 +333,6 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp struct wm0010_boot_xfer *xfer; int ret; DECLARE_COMPLETION_ONSTACK(done); - const struct firmware *fw; const struct dfw_binrec *rec; const struct dfw_inforec *inforec; u64 *img; @@ -342,6 +341,7 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp INIT_LIST_HEAD(&xfer_list); + const struct firmware *fw __free(firmware) = NULL; ret = request_firmware(&fw, name, component->dev); if (ret != 0) { dev_err(component->dev, "Failed to request application(%s): %d\n", @@ -360,16 +360,14 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp /* First record should be INFO */ if (rec->command != DFW_CMD_INFO) { dev_err(component->dev, "First record not INFO\r\n"); - ret = -EINVAL; - goto abort; + return -EINVAL; } if (inforec->info_version != INFO_VERSION) { dev_err(component->dev, "Unsupported version (%02d) of INFO record\r\n", inforec->info_version); - ret = -EINVAL; - goto abort; + return -EINVAL; } dev_dbg(component->dev, "Version v%02d INFO record found\r\n", @@ -378,8 +376,7 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp /* Check it's a DSP file */ if (dsp != DEVICE_ID_WM0010) { dev_err(component->dev, "Not a WM0010 firmware file.\r\n"); - ret = -EINVAL; - goto abort; + return -EINVAL; } /* Skip the info record as we don't need to send it */ @@ -404,14 +401,14 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp out = kzalloc(len, GFP_KERNEL | GFP_DMA); if (!out) { ret = -ENOMEM; - goto abort1; + goto abort; } xfer->t.rx_buf = out; img = kzalloc(len, GFP_KERNEL | GFP_DMA); if (!img) { ret = -ENOMEM; - goto abort1; + goto abort; } xfer->t.tx_buf = img; @@ -449,13 +446,13 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp ret = spi_async(spi, &xfer->m); if (ret != 0) { dev_err(component->dev, "Write failed: %d\n", ret); - goto abort1; + goto abort; } if (wm0010->boot_failed) { dev_dbg(component->dev, "Boot fail!\n"); ret = -EINVAL; - goto abort1; + goto abort; } } @@ -463,7 +460,7 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp ret = 0; -abort1: +abort: while (!list_empty(&xfer_list)) { xfer = list_first_entry(&xfer_list, struct wm0010_boot_xfer, list); @@ -473,8 +470,6 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp kfree(xfer); } -abort: - release_firmware(fw); return ret; } @@ -482,14 +477,12 @@ static int wm0010_stage2_load(struct snd_soc_component *component) { struct spi_device *spi = to_spi_device(component->dev); struct wm0010_priv *wm0010 = snd_soc_component_get_drvdata(component); - const struct firmware *fw; struct spi_message m; struct spi_transfer t; - u32 *img; - u8 *out; int i; int ret = 0; + const struct firmware *fw __free(firmware) = NULL; ret = request_firmware(&fw, "wm0010_stage2.bin", component->dev); if (ret != 0) { dev_err(component->dev, "Failed to request stage2 loader: %d\n", @@ -500,17 +493,15 @@ static int wm0010_stage2_load(struct snd_soc_component *component) dev_dbg(component->dev, "Downloading %zu byte stage 2 loader\n", fw->size); /* Copy to local buffer first as vmalloc causes problems for dma */ - img = kmemdup(&fw->data[0], fw->size, GFP_KERNEL | GFP_DMA); - if (!img) { - ret = -ENOMEM; - goto abort2; - } + u32 *img __free(kfree) = + kmemdup(&fw->data[0], fw->size, GFP_KERNEL | GFP_DMA); + if (!img) + return -ENOMEM; - out = kzalloc(fw->size, GFP_KERNEL | GFP_DMA); - if (!out) { - ret = -ENOMEM; - goto abort1; - } + u8 *out __free(kfree) = + kzalloc(fw->size, GFP_KERNEL | GFP_DMA); + if (!out) + return -ENOMEM; spi_message_init(&m); memset(&t, 0, sizeof(t)); @@ -527,7 +518,7 @@ static int wm0010_stage2_load(struct snd_soc_component *component) ret = spi_sync(spi, &m); if (ret != 0) { dev_err(component->dev, "Initial download failed: %d\n", ret); - goto abort; + return ret; } /* Look for errors from the boot ROM */ @@ -536,18 +527,11 @@ static int wm0010_stage2_load(struct snd_soc_component *component) dev_err(component->dev, "Boot ROM error: %x in %d\n", out[i], i); wm0010_mark_boot_failure(wm0010); - ret = -EBUSY; - goto abort; + return -EBUSY; } } -abort: - kfree(out); -abort1: - kfree(img); -abort2: - release_firmware(fw); - return ret; + return 0; } static int wm0010_boot(struct snd_soc_component *component) From 2db054a5efa5f1a99aac4d714726b2b743b4720a Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 6 Aug 2026 15:59:55 +0200 Subject: [PATCH 447/612] ASoC: wm2000: Use auto-cleanup for firmware loading Simplify the code to manage the firmware loading with __free(firmware) auto-cleanup. Only the code refactoring, no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260806140006.1412298-24-tiwai@suse.de Signed-off-by: Mark Brown --- sound/soc/codecs/wm2000.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/sound/soc/codecs/wm2000.c b/sound/soc/codecs/wm2000.c index 897b0acac5f324..41c8cfb346c669 100644 --- a/sound/soc/codecs/wm2000.c +++ b/sound/soc/codecs/wm2000.c @@ -796,7 +796,7 @@ static int wm2000_i2c_probe(struct i2c_client *i2c) struct wm2000_priv *wm2000; struct wm2000_platform_data *pdata; const char *filename; - const struct firmware *fw = NULL; + const struct firmware *fw __free(firmware) = NULL; int ret, i; unsigned int reg; u16 id; @@ -814,7 +814,7 @@ static int wm2000_i2c_probe(struct i2c_client *i2c) ret = PTR_ERR(wm2000->regmap); dev_err(&i2c->dev, "Failed to allocate register map: %d\n", ret); - goto out; + return ret; } for (i = 0; i < WM2000_NUM_SUPPLIES; i++) @@ -908,9 +908,6 @@ static int wm2000_i2c_probe(struct i2c_client *i2c) err_supplies: regulator_bulk_disable(WM2000_NUM_SUPPLIES, wm2000->supplies); - -out: - release_firmware(fw); return ret; } From b6ba77dfeb2abb52945ea205a1865c1a60bdfebe Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 6 Aug 2026 15:59:56 +0200 Subject: [PATCH 448/612] ASoC: zl38060: Use auto-cleanup for firmware loading Simplify the code to manage the firmware loading with __free(firmware) auto-cleanup. Only the code refactoring, no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260806140006.1412298-25-tiwai@suse.de Signed-off-by: Mark Brown --- sound/soc/codecs/zl38060.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/sound/soc/codecs/zl38060.c b/sound/soc/codecs/zl38060.c index 7de4014e626d21..894b8eb42e399d 100644 --- a/sound/soc/codecs/zl38060.c +++ b/sound/soc/codecs/zl38060.c @@ -162,7 +162,7 @@ static int zl38_fw_send_xaddr(struct regmap *regmap, const void *data) static int zl38_load_firmware(struct device *dev, struct regmap *regmap) { const struct ihex_binrec *rec; - const struct firmware *fw; + const struct firmware *fw __free(firmware) = NULL; u32 addr; u16 len; int err; @@ -180,7 +180,7 @@ static int zl38_load_firmware(struct device *dev, struct regmap *regmap) return err; err = zl38_fw_enter_boot_mode(regmap); if (err) - goto out; + return err; rec = (const struct ihex_binrec *)fw->data; while (rec) { addr = be32_to_cpu(rec->addr); @@ -195,15 +195,12 @@ static int zl38_load_firmware(struct device *dev, struct regmap *regmap) err = -EINVAL; } if (err) - goto out; + return err; /* next ! */ rec = ihex_next_binrec(rec); } - err = zl38_fw_go(regmap); -out: - release_firmware(fw); - return err; + return zl38_fw_go(regmap); } From 997261a59b9458bce2a38a93322309404470ee2e Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 6 Aug 2026 15:59:57 +0200 Subject: [PATCH 449/612] ASoC: fsl: Use auto-cleanup for firmware loading Simplify the code to manage the firmware loading with __free(firmware) auto-cleanup. Only the code refactoring, no functional changes. Cc: Shengjiu Wang Cc: Xiubo Li Cc: Fabio Estevam Cc: Nicolin Chen Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260806140006.1412298-26-tiwai@suse.de Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_xcvr.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/sound/soc/fsl/fsl_xcvr.c b/sound/soc/fsl/fsl_xcvr.c index 41d100500534f6..98282720435112 100644 --- a/sound/soc/fsl/fsl_xcvr.c +++ b/sound/soc/fsl/fsl_xcvr.c @@ -921,10 +921,10 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, static int fsl_xcvr_load_firmware(struct fsl_xcvr *xcvr) { struct device *dev = &xcvr->pdev->dev; - const struct firmware *fw; int ret = 0, rem, off, out, page = 0, size = FSL_XCVR_REG_OFFSET; u32 mask, val; + const struct firmware *fw __free(firmware) = NULL; ret = request_firmware(&fw, xcvr->soc_data->fw_name, dev); if (ret) { dev_err(dev, "failed to request firmware.\n"); @@ -936,7 +936,6 @@ static int fsl_xcvr_load_firmware(struct fsl_xcvr *xcvr) /* RAM is 20KiB = 16KiB code + 4KiB data => max 10 pages 2KiB each */ if (rem > 16384) { dev_err(dev, "FW size %d is bigger than 16KiB.\n", rem); - release_firmware(fw); return -ENOMEM; } @@ -947,7 +946,7 @@ static int fsl_xcvr_load_firmware(struct fsl_xcvr *xcvr) if (ret < 0) { dev_err(dev, "FW: failed to set page %d, err=%d\n", page, ret); - goto err_firmware; + return ret; } off = page * size; @@ -968,11 +967,6 @@ static int fsl_xcvr_load_firmware(struct fsl_xcvr *xcvr) } } -err_firmware: - release_firmware(fw); - if (ret < 0) - return ret; - /* configure watermarks */ mask = FSL_XCVR_EXT_CTRL_RX_FWM_MASK | FSL_XCVR_EXT_CTRL_TX_FWM_MASK; val = FSL_XCVR_EXT_CTRL_RX_FWM(FSL_XCVR_FIFO_WMK_RX); From 6a052cf54fa19940e3cddd1fd63e2fac6cdb374a Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 6 Aug 2026 15:59:58 +0200 Subject: [PATCH 450/612] ASoC: Intel: avs: Use auto-cleanup for firmware loading Simplify the code to manage the firmware loading with __free(firmware) auto-cleanup. Only the code refactoring, no functional changes. Cc: Cezary Rojewski Acked-by: Cezary Rojewski Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260806140006.1412298-27-tiwai@suse.de Signed-off-by: Mark Brown --- sound/soc/intel/avs/topology.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sound/soc/intel/avs/topology.c b/sound/soc/intel/avs/topology.c index 9033f683393c43..673ac31f2fea1a 100644 --- a/sound/soc/intel/avs/topology.c +++ b/sound/soc/intel/avs/topology.c @@ -2222,7 +2222,7 @@ struct avs_tplg *avs_tplg_new(struct snd_soc_component *comp) int avs_load_topology(struct snd_soc_component *comp, const char *filename) { - const struct firmware *fw; + const struct firmware *fw __free(firmware) = NULL; int ret; ret = request_firmware(&fw, filename, comp->dev); @@ -2235,7 +2235,6 @@ int avs_load_topology(struct snd_soc_component *comp, const char *filename) if (ret < 0) dev_err(comp->dev, "load topology \"%s\" failed: %d\n", filename, ret); - release_firmware(fw); return ret; } From a0dae90ea9668a050c37cf8e0938a113872971f9 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 6 Aug 2026 15:59:59 +0200 Subject: [PATCH 451/612] ASoC: Intel: catpt: Use auto-cleanup for firmware loading Simplify the code to manage the firmware loading with __free(firmware) auto-cleanup. Only the code refactoring, no functional changes. Cc: Cezary Rojewski Acked-by: Cezary Rojewski Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260806140006.1412298-28-tiwai@suse.de Signed-off-by: Mark Brown --- sound/soc/intel/catpt/loader.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/sound/soc/intel/catpt/loader.c b/sound/soc/intel/catpt/loader.c index e7ba9e1e60ae13..724cbe337db419 100644 --- a/sound/soc/intel/catpt/loader.c +++ b/sound/soc/intel/catpt/loader.c @@ -568,27 +568,24 @@ static int catpt_request_load_firmware(struct catpt_dev *cdev, struct dma_chan * const char *name, bool restore) { struct catpt_fw_hdr *fw; - struct firmware *img; dma_addr_t paddr; void *vaddr; int ret; - ret = request_firmware((const struct firmware **)&img, name, cdev->dev); + const struct firmware *img __free(firmware) = NULL; + ret = request_firmware(&img, name, cdev->dev); if (ret) return ret; fw = (struct catpt_fw_hdr *)img->data; if (strncmp(fw->signature, FW_SIGNATURE, FW_SIGNATURE_SIZE)) { dev_err(cdev->dev, "firmware signature mismatch\n"); - ret = -EINVAL; - goto release_fw; + return -EINVAL; } vaddr = dma_alloc_coherent(cdev->dev, img->size, &paddr, GFP_KERNEL); - if (!vaddr) { - ret = -ENOMEM; - goto release_fw; - } + if (!vaddr) + return -ENOMEM; memcpy(vaddr, img->data, img->size); fw = (struct catpt_fw_hdr *)vaddr; @@ -598,8 +595,6 @@ static int catpt_request_load_firmware(struct catpt_dev *cdev, struct dma_chan * ret = catpt_load_firmware(cdev, chan, paddr, fw); dma_free_coherent(cdev->dev, img->size, vaddr, paddr); -release_fw: - release_firmware(img); return ret; } From c5663770970c75b168e8dff9f2b2a334d4449865 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 6 Aug 2026 16:00:00 +0200 Subject: [PATCH 452/612] ASoC: qcom: Use auto-cleanup for firmware loading Simplify the code to manage the firmware loading with __free(firmware) auto-cleanup. Only the code refactoring, no functional changes. Cc: Srinivas Kandagatla Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260806140006.1412298-29-tiwai@suse.de Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/topology.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sound/soc/qcom/qdsp6/topology.c b/sound/soc/qcom/qdsp6/topology.c index 54661bcb006c90..faafbc5c0ad004 100644 --- a/sound/soc/qcom/qdsp6/topology.c +++ b/sound/soc/qcom/qdsp6/topology.c @@ -1416,7 +1416,6 @@ int audioreach_tplg_init(struct snd_soc_component *component) { struct snd_soc_card *card = component->card; struct device *dev = component->dev; - const struct firmware *fw; int ret; /* Inline with Qualcomm UCM configs and linux-firmware path */ @@ -1426,6 +1425,7 @@ int audioreach_tplg_init(struct snd_soc_component *component) if (!tplg_fw_name) return -ENOMEM; + const struct firmware *fw __free(firmware) = NULL; ret = request_firmware(&fw, tplg_fw_name, dev); if (ret < 0) { dev_err(dev, "tplg firmware loading %s failed %d\n", tplg_fw_name, ret); @@ -1438,8 +1438,6 @@ int audioreach_tplg_init(struct snd_soc_component *component) dev_err(dev, "tplg component load failed: %d\n", ret); } - release_firmware(fw); - return ret; } EXPORT_SYMBOL_GPL(audioreach_tplg_init); From 81ab98716983e1dd43de6f3c0f5a3a78a8c66dc1 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 6 Aug 2026 16:00:01 +0200 Subject: [PATCH 453/612] ASoC: renesas: Use auto-cleanup for firmware loading Simplify the code to manage the firmware loading with __free(firmware) auto-cleanup. Only the code refactoring, no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260806140006.1412298-30-tiwai@suse.de Signed-off-by: Mark Brown --- sound/soc/renesas/siu_dai.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sound/soc/renesas/siu_dai.c b/sound/soc/renesas/siu_dai.c index 039b1264d90dbe..85dee902571050 100644 --- a/sound/soc/renesas/siu_dai.c +++ b/sound/soc/renesas/siu_dai.c @@ -715,7 +715,6 @@ static struct snd_soc_dai_driver siu_i2s_dai = { static int siu_probe(struct platform_device *pdev) { - const struct firmware *fw_entry; struct resource *res, *region; struct siu_info *info; int ret; @@ -726,6 +725,7 @@ static int siu_probe(struct platform_device *pdev) siu_i2s_data = info; info->dev = &pdev->dev; + const struct firmware *fw_entry __free(firmware) = NULL; ret = request_firmware(&fw_entry, "siu_spb.bin", &pdev->dev); if (ret) return ret; @@ -736,8 +736,6 @@ static int siu_probe(struct platform_device *pdev) */ memcpy(&info->fw, fw_entry->data, fw_entry->size); - release_firmware(fw_entry); - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) return -ENODEV; From 73a5271e10da282705793b6e7f3f1197b1ea998a Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 6 Aug 2026 16:00:02 +0200 Subject: [PATCH 454/612] ASoC: SDCA: Use auto-cleanup for firmware loading Simplify the code to manage the firmware loading with __free(firmware) auto-cleanup. Only the code refactoring, no functional changes. Cc: Charles Keepax Cc: Maciej Strozek Cc: Bard Liao Cc: Pierre-Louis Bossart Reviewed-by: Charles Keepax Tested-by: Charles Keepax Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260806140006.1412298-31-tiwai@suse.de Signed-off-by: Mark Brown --- sound/soc/sdca/sdca_fdl.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/sound/soc/sdca/sdca_fdl.c b/sound/soc/sdca/sdca_fdl.c index dbe572336f8ca7..150e36ed24bcc7 100644 --- a/sound/soc/sdca/sdca_fdl.c +++ b/sound/soc/sdca/sdca_fdl.c @@ -195,7 +195,6 @@ static int fdl_load_file(struct sdca_interrupt *interrupt, { struct device *dev = interrupt->dev; struct sdca_fdl_data *fdl_data = &interrupt->function->fdl_data; - const struct firmware *firmware = NULL; struct acpi_sw_file *swf = NULL, *tmp; struct sdca_fdl_file *fdl_file; char *disk_filename; @@ -230,6 +229,7 @@ static int fdl_load_file(struct sdca_interrupt *interrupt, dev_dbg(dev, "FDL disk filename: %s\n", disk_filename); + const struct firmware *firmware __free(firmware) = NULL; ret = firmware_request_nowarn(&firmware, disk_filename, dev); kfree(disk_filename); if (ret) { @@ -258,8 +258,7 @@ static int fdl_load_file(struct sdca_interrupt *interrupt, if (!swf) { dev_err(dev, "failed to locate SWF\n"); - ret = -ENOENT; - goto error; + return -ENOENT; } dev_info(dev, "loading SWF: %x-%x-%x\n", @@ -271,9 +270,6 @@ static int fdl_load_file(struct sdca_interrupt *interrupt, SDCA_CTL_XU_FDL_MESSAGEOFFSET, fdl_file->fdl_offset, SDCA_CTL_XU_FDL_MESSAGELENGTH, swf->data, swf->file_length - offsetof(struct acpi_sw_file, data)); - -error: - release_firmware(firmware); return ret; } From 6d0a9e4df17979ef8acbf8d7d6145fb1375e45cd Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 6 Aug 2026 16:00:03 +0200 Subject: [PATCH 455/612] ASoC: SOF: Use auto-cleanup for firmware loading Simplify the code to manage the firmware loading with __free(firmware) and __free(kfree) auto-cleanups for the firmware data and the temporary string or array. Only the code refactoring, no functional changes. Cc: Liam Girdwood Cc: Bard Liao Cc: Daniel Baluta Cc: Pierre-Louis Bossart Cc: Vijendar Mukunda Acked-by: Peter Ujfalusi Tested-by: Peter Ujfalusi Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260806140006.1412298-32-tiwai@suse.de Signed-off-by: Mark Brown --- sound/soc/sof/fw-file-profile.c | 19 +++++++------------ sound/soc/sof/topology.c | 20 ++++++-------------- 2 files changed, 13 insertions(+), 26 deletions(-) diff --git a/sound/soc/sof/fw-file-profile.c b/sound/soc/sof/fw-file-profile.c index 76bde2e0be1ddd..fcd57f04ca36d6 100644 --- a/sound/soc/sof/fw-file-profile.c +++ b/sound/soc/sof/fw-file-profile.c @@ -16,20 +16,19 @@ static int sof_test_firmware_file(struct device *dev, enum sof_ipc_type *ipc_type_to_adjust) { enum sof_ipc_type fw_ipc_type; - const struct firmware *fw; - const char *fw_filename; const u32 *magic; int ret; - fw_filename = kasprintf(GFP_KERNEL, "%s/%s", profile->fw_path, - profile->fw_name); + const char *fw_filename __free(kfree) = + kasprintf(GFP_KERNEL, "%s/%s", profile->fw_path, + profile->fw_name); if (!fw_filename) return -ENOMEM; + const struct firmware *fw __free(firmware) = NULL; ret = firmware_request_nowarn(&fw, fw_filename, dev); if (ret < 0) { dev_dbg(dev, "Failed to open firmware file: %s\n", fw_filename); - kfree(fw_filename); return ret; } @@ -44,8 +43,7 @@ static int sof_test_firmware_file(struct device *dev, break; default: dev_err(dev, "Invalid firmware magic: %#x\n", *magic); - ret = -EINVAL; - goto out; + return -EINVAL; } if (ipc_type_to_adjust) { @@ -54,13 +52,10 @@ static int sof_test_firmware_file(struct device *dev, dev_err(dev, "ipc type mismatch between %s and expected: %d vs %d\n", fw_filename, fw_ipc_type, profile->ipc_type); - ret = -EINVAL; + return -EINVAL; } -out: - release_firmware(fw); - kfree(fw_filename); - return ret; + return 0; } static int sof_test_topology_file(struct device *dev, diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c index 6fd69ba11c4106..820513bb257714 100644 --- a/sound/soc/sof/topology.c +++ b/sound/soc/sof/topology.c @@ -2506,13 +2506,12 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct snd_sof_pdata *sof_pdata = sdev->pdata; const char *tplg_filename_prefix = sof_pdata->tplg_filename_prefix; - const struct firmware *fw; - const char **tplg_files; int tplg_cnt = 0; int ret; int i; - tplg_files = kcalloc(scomp->card->num_links, sizeof(char *), GFP_KERNEL); + const char **tplg_files __free(kfree) = + kcalloc(scomp->card->num_links, sizeof(char *), GFP_KERNEL); if (!tplg_files) return -ENOMEM; @@ -2538,10 +2537,8 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) tplg_filename_prefix, &tplg_files, no_fallback); - if (tplg_cnt < 0) { - kfree(tplg_files); + if (tplg_cnt < 0) return tplg_cnt; - } } /* @@ -2552,8 +2549,6 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) if (strstr(file, "dummy")) { dev_err(scomp->dev, "Function topology is required, please upgrade sof-firmware\n"); - - kfree(tplg_files); return -EINVAL; } tplg_files[0] = file; @@ -2568,6 +2563,7 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) if (tplg_files[0] != file) dev_info(scomp->dev, "loading topology %d: %s\n", i, tplg_files[i]); + const struct firmware *fw __free(firmware) = NULL; ret = request_firmware(&fw, tplg_files[i], scomp->dev); if (ret < 0) { /* @@ -2586,8 +2582,6 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) else ret = snd_soc_tplg_component_load(scomp, &sof_tplg_ops, fw); - release_firmware(fw); - if (ret < 0) { dev_err(scomp->dev, "tplg %s component load failed %d\n", tplg_files[i], ret); @@ -2606,6 +2600,8 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) goto out; } dev_info(scomp->dev, "loading feature topology %d: %s\n", i, feature_topology); + + const struct firmware *fw __free(firmware) = NULL; ret = request_firmware(&fw, feature_topology, scomp->dev); if (ret < 0) { /* @@ -2630,8 +2626,6 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) else ret = snd_soc_tplg_component_load(scomp, &sof_tplg_ops, fw); - release_firmware(fw); - if (ret < 0) { dev_err(scomp->dev, "feature tplg %s component load failed %d\n", feature_topologies[i], ret); @@ -2650,8 +2644,6 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) if (ret >= 0 && sdev->led_present) ret = snd_ctl_led_request(); - kfree(tplg_files); - return ret; } EXPORT_SYMBOL(snd_sof_load_topology); From 564db8b8da8d5293424406114244c24ef947dd1c Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 6 Aug 2026 13:10:45 +0700 Subject: [PATCH 456/612] ASoC: dwc: Propagate -EPROBE_DEFER from IRQ lookup platform_get_irq_optional() never returns 0. It returns a positive IRQ number on success or a negative error code on failure. Return -EPROBE_DEFER from platform_get_irq_optional() so the driver is re-probed when the interrupt resource becomes available instead of continuing probe without an IRQ. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260806061046.25323-1-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/dwc/dwc-i2s.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/soc/dwc/dwc-i2s.c b/sound/soc/dwc/dwc-i2s.c index 74dfd39fd6047d..2e9fb79b1cd8b8 100644 --- a/sound/soc/dwc/dwc-i2s.c +++ b/sound/soc/dwc/dwc-i2s.c @@ -955,7 +955,9 @@ static int dw_i2s_probe(struct platform_device *pdev) } irq = platform_get_irq_optional(pdev, 0); - if (irq >= 0) { + if (irq == -EPROBE_DEFER) + return irq; + if (irq > 0) { ret = devm_request_irq(&pdev->dev, irq, i2s_irq_handler, 0, pdev->name, dev); if (ret < 0) { From 9b30521074f01aff856f539c1241a48342b69f7c Mon Sep 17 00:00:00 2001 From: Sergey Lebedev Date: Tue, 4 Aug 2026 22:59:24 +0000 Subject: [PATCH 457/612] ASoC: rt1320: run the initialisation preset on the first hardware init rt1320_io_init() applies the vendor initialisation preset only when the amplifier's SDCA function status has FUNCTION_NEEDS_INITIALIZATION set: if ((amp_func_status & FUNCTION_NEEDS_INITIALIZATION)) { Its two sibling drivers guard the same write differently, also running the preset on the first hardware init: rt712-sdca.c: if ((amp_func_status & FUNCTION_NEEDS_INITIALIZATION) || (!rt712->first_hw_init)) { rt722-sdca.c: if ((amp_func_status & FUNCTION_NEEDS_INITIALIZATION) || (!rt722->first_hw_init)) { On the Microsoft Surface Pro 11 (Intel) the RT1320 never sets that bit. Its function status reads back 0x41 on every boot, cold or warm: rt1320-sdca sdw:0:0:025d:1320:01: rt1320_io_init amp func_status=0x41 which is NEWLY_ATTACHED | FUNCTION_HAS_BEEN_RESET: the function reports that it has been reset and does not consider itself in need of initialisation. Bit 5 is never set, so the preset never runs, rt1320_vc_preset() and the MCU patch load are skipped, and the amplifier is left unprogrammed. rt712 and rt722 would have run it via their first_hw_init fallback. Add the same fallback. With it rt1320_vc_preset() executes and the amplifier reports RT1320_KR0_INT_READY=0x1f where previously it did not. Signed-off-by: Sergey Lebedev Link: https://patch.msgid.link/20260804225853.31585-2-lsa.uz@pm.me Signed-off-by: Mark Brown --- sound/soc/codecs/rt1320-sdw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/rt1320-sdw.c b/sound/soc/codecs/rt1320-sdw.c index 1e930b27c67a8f..42acb6cef77ac2 100644 --- a/sound/soc/codecs/rt1320-sdw.c +++ b/sound/soc/codecs/rt1320-sdw.c @@ -1899,7 +1899,7 @@ static int rt1320_io_init(struct device *dev, struct sdw_slave *slave) dev_dbg(dev, "%s amp func_status=0x%x\n", __func__, amp_func_status); /* initialization write */ - if ((amp_func_status & FUNCTION_NEEDS_INITIALIZATION)) { + if ((amp_func_status & FUNCTION_NEEDS_INITIALIZATION) || !rt1320->first_hw_init) { switch (rt1320->dev_id) { case RT1320_DEV_ID: if (rt1320->version_id < RT1320_VC) From 2ce1d17dcbb38db4d44394c822b9d03c65a22931 Mon Sep 17 00:00:00 2001 From: Brian Koebbe Date: Mon, 10 Aug 2026 09:57:00 -0500 Subject: [PATCH 458/612] ALSA: hda/realtek: Fix quiet 3.5mm jacks on Beelink SER6 Both the front headphone jack and the rear line-out jack play back at a barely audible volume on the Beelink SER6 Max (ALC897, PCI subsystem ID 1f66:0202), even with all mixer controls at 0dB. GPIO2 on the codec gates an external headphone/line amplifier that the generic parser never enables. Add a fixup that asserts it. Verified fixed on both jacks. Signed-off-by: Brian Koebbe Link: https://patch.msgid.link/20260810145700.1206010-1-brian@koeb.be Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc662.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sound/hda/codecs/realtek/alc662.c b/sound/hda/codecs/realtek/alc662.c index a640292c4f100f..eff7c83b9c592f 100644 --- a/sound/hda/codecs/realtek/alc662.c +++ b/sound/hda/codecs/realtek/alc662.c @@ -325,6 +325,7 @@ enum { ALC897_FIXUP_UNIS_H3C_X500S, ALC897_FIXUP_HEADSET_MIC_PIN3, ALC662_FIXUP_CSL_GPIO, + ALC897_FIXUP_BEELINK_SER6_AMP, }; static const struct hda_fixup alc662_fixups[] = { @@ -782,6 +783,10 @@ static const struct hda_fixup alc662_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc662_fixup_csl_amp, }, + [ALC897_FIXUP_BEELINK_SER6_AMP] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_gpio4, + }, }; static const struct hda_quirk alc662_fixup_tbl[] = { @@ -853,6 +858,7 @@ static const struct hda_quirk alc662_fixup_tbl[] = { SND_PCI_QUIRK(0x1b35, 0x2206, "CZC P10T", ALC662_FIXUP_CZC_P10T), SND_PCI_QUIRK(0x1c6c, 0x1239, "Compaq N14JP6-V2", ALC897_FIXUP_HP_HSMIC_VERB), SND_PCI_QUIRK(0x1e63, 0x6d9a, "F+ FLAPTOP r", ALC897_FIXUP_HP_HSMIC_VERB), + SND_PCI_QUIRK(0x1f66, 0x0202, "Beelink SER6 Max 6900", ALC897_FIXUP_BEELINK_SER6_AMP), #if 0 /* Below is a quirk table taken from the old code. From e7da28b820d12927de30abf554c727a319f80359 Mon Sep 17 00:00:00 2001 From: Denis Batishchev Date: Mon, 10 Aug 2026 17:14:41 +0200 Subject: [PATCH 459/612] ALSA: hda/realtek: Enable micmute LED on HP EliteBook 6 G1a p/n: AD3Q9ET#UUG The HP EliteBook 6 G1a (SSID 103c:8e0d) uses a Realtek ALC236 codec. Without a quirk no fixup is selected and the mic-mute LED stays off. It needs the same ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF quirk as the already-supported 14" variant (SSID 103c:8dfb), so add it. Signed-off-by: Denis Batishchev Cc: Link: https://patch.msgid.link/20260810151440.2306217-2-ii343hbka@gmail.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 6b36c730ce788d..f92c044ea553cb 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7545,6 +7545,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8dfc, "HP EliteBook 645 G12", ALC236_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8dfd, "HP EliteBook 6 G1a 16", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), SND_PCI_QUIRK(0x103c, 0x8dfe, "HP EliteBook 665 G12", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8e0d, "HP EliteBook 6 G1a 14 (AD3Q9ET#UUG)", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), SND_PCI_QUIRK(0x103c, 0x8e11, "HP Trekker", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8e12, "HP Trekker", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8e13, "HP Trekker", ALC287_FIXUP_CS35L41_I2C_2), From a9760db775efb483e6e4cb571f5bda37532a75a8 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 10 Aug 2026 15:37:02 +0200 Subject: [PATCH 460/612] ALSA: seq: Use RCU for the port subscriber list Each sequencer port keeps two subscriber groups (c_src and c_dest), each protected by both an rwlock (list_lock) and a rw_semaphore (list_mutex). The rwlock is taken read-side in the event delivery hot path (__deliver_to_subscribers()) for delivering every event to subscribers, while the mutex serializes subscribe/unsubscribe and covers the sleepable delivery and query walks. Subscriptions change rarely but delivery happens constantly, so this is a textbook read-mostly case. Convert the subscriber list traversal to RCU and drop the rwlock entirely while keeping the existing list_mutex for serializing the writers. The atomic delivery path now runs lock-free under rcu_read_lock() instead of contending on the shared rwlock. Along with the conversion to RCU, the subscriber lists are switched from list_head to hlist so that removal can use hlist_del_init_rcu(): it keeps the ->next pointer intact for concurrent readers while clearing ->pprev, which lets the double-deletion guard (added in commit 13d5e5d4725c) keep detecting an already-removed entry via hlist_unhashed(). Dropping write_lock_irq() from the writers is safe: no writer runs in atomic/IRQ context, and the sole atomic reader now uses RCU, which is IRQ-safe. Port lifetime handling (use_lock/closing drain in port_delete()) is orthogonal and unchanged. Note that the conversion to RCU has another merit: it automatically "fixes" the (rather false) lockdep warnings for the doubly read-locks of the same subscriber list, too. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260810133711.42483-2-tiwai@suse.de --- sound/core/seq/seq_clientmgr.c | 25 ++++++++-------- sound/core/seq/seq_ports.c | 52 ++++++++++++++++------------------ sound/core/seq/seq_ports.h | 8 +++--- 3 files changed, 41 insertions(+), 44 deletions(-) diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 23ec239640c337..77f5020f187396 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -717,10 +717,11 @@ static int __deliver_to_subscribers(struct snd_seq_client *client, /* lock list */ if (atomic) - read_lock(&grp->list_lock); + rcu_read_lock(); else down_read_nested(&grp->list_mutex, hop); - list_for_each_entry(subs, &grp->list_head, src_list) { + hlist_for_each_entry_rcu(subs, &grp->list_head, src_list, + lockdep_is_held(&grp->list_mutex)) { /* both ports ready? */ if (atomic_read(&subs->ref_count) != 2) continue; @@ -741,7 +742,7 @@ static int __deliver_to_subscribers(struct snd_seq_client *client, memcpy(event, &event_saved, saved_size); } if (atomic) - read_unlock(&grp->list_lock); + rcu_read_unlock(); else up_read(&grp->list_mutex); memcpy(event, &event_saved, saved_size); @@ -1938,7 +1939,7 @@ static int snd_seq_ioctl_query_subs(struct snd_seq_client *client, void *arg) { struct snd_seq_query_subs *subs = arg; struct snd_seq_port_subs_info *group; - struct list_head *p; + struct hlist_node *p; int i; struct snd_seq_client *cptr __free(snd_seq_client) = @@ -1965,15 +1966,15 @@ static int snd_seq_ioctl_query_subs(struct snd_seq_client *client, void *arg) /* search for the subscriber */ subs->num_subs = group->count; i = 0; - list_for_each(p, &group->list_head) { + hlist_for_each(p, &group->list_head) { if (i++ == subs->index) { /* found! */ struct snd_seq_subscribers *s; if (subs->type == SNDRV_SEQ_QUERY_SUBS_READ) { - s = list_entry(p, struct snd_seq_subscribers, src_list); + s = hlist_entry(p, struct snd_seq_subscribers, src_list); subs->addr = s->info.dest; } else { - s = list_entry(p, struct snd_seq_subscribers, dest_list); + s = hlist_entry(p, struct snd_seq_subscribers, dest_list); subs->addr = s->info.sender; } subs->flags = s->info.flags; @@ -2529,19 +2530,19 @@ static void snd_seq_info_dump_subscribers(struct snd_info_buffer *buffer, struct snd_seq_port_subs_info *group, int is_src, char *msg) { - struct list_head *p; + struct hlist_node *p; struct snd_seq_subscribers *s; int count = 0; guard(rwsem_read)(&group->list_mutex); - if (list_empty(&group->list_head)) + if (hlist_empty(&group->list_head)) return; snd_iprintf(buffer, msg); - list_for_each(p, &group->list_head) { + hlist_for_each(p, &group->list_head) { if (is_src) - s = list_entry(p, struct snd_seq_subscribers, src_list); + s = hlist_entry(p, struct snd_seq_subscribers, src_list); else - s = list_entry(p, struct snd_seq_subscribers, dest_list); + s = hlist_entry(p, struct snd_seq_subscribers, dest_list); if (count++) snd_iprintf(buffer, ", "); snd_iprintf(buffer, "%d:%d", diff --git a/sound/core/seq/seq_ports.c b/sound/core/seq/seq_ports.c index 6612e92d801f5f..357c72ed0d3b4c 100644 --- a/sound/core/seq/seq_ports.c +++ b/sound/core/seq/seq_ports.c @@ -98,10 +98,9 @@ struct snd_seq_client_port *snd_seq_port_query_nearest(struct snd_seq_client *cl /* initialize snd_seq_port_subs_info */ static void port_subs_info_init(struct snd_seq_port_subs_info *grp) { - INIT_LIST_HEAD(&grp->list_head); + INIT_HLIST_HEAD(&grp->list_head); grp->count = 0; grp->exclusive = 0; - rwlock_init(&grp->list_lock); init_rwsem(&grp->list_mutex); grp->open = NULL; grp->close = NULL; @@ -202,12 +201,12 @@ static void delete_and_unsubscribe_port(struct snd_seq_client *client, bool is_src, bool ack); static inline struct snd_seq_subscribers * -get_subscriber(struct list_head *p, bool is_src) +get_subscriber(struct hlist_node *p, bool is_src) { if (is_src) - return list_entry(p, struct snd_seq_subscribers, src_list); + return hlist_entry(p, struct snd_seq_subscribers, src_list); else - return list_entry(p, struct snd_seq_subscribers, dest_list); + return hlist_entry(p, struct snd_seq_subscribers, dest_list); } /* @@ -219,9 +218,9 @@ static void clear_subscriber_list(struct snd_seq_client *client, struct snd_seq_port_subs_info *grp, int is_src) { - struct list_head *p, *n; + struct hlist_node *p, *n; - list_for_each_safe(p, n, &grp->list_head) { + hlist_for_each_safe(p, n, &grp->list_head) { struct snd_seq_subscribers *subs; subs = get_subscriber(p, is_src); @@ -238,13 +237,13 @@ static void clear_subscriber_list(struct snd_seq_client *client, * remove the subscriber info */ if (atomic_dec_and_test(&subs->ref_count)) - kfree(subs); + kfree_rcu(subs, rcu); continue; } /* ok we got the connected port */ delete_and_unsubscribe_port(c, aport, subs, !is_src, true); - kfree(subs); + kfree_rcu(subs, rcu); } } @@ -499,20 +498,20 @@ static int check_and_subscribe_port(struct snd_seq_client *client, bool is_src, bool exclusive, bool ack) { struct snd_seq_port_subs_info *grp; - struct list_head *p; + struct hlist_node *p; struct snd_seq_subscribers *s; int err; grp = is_src ? &port->c_src : &port->c_dest; guard(rwsem_write)(&grp->list_mutex); if (exclusive) { - if (!list_empty(&grp->list_head)) + if (!hlist_empty(&grp->list_head)) return -EBUSY; } else { if (grp->exclusive) return -EBUSY; /* check whether already exists */ - list_for_each(p, &grp->list_head) { + hlist_for_each(p, &grp->list_head) { s = get_subscriber(p, is_src); if (match_subs_info(&subs->info, &s->info)) return -EBUSY; @@ -526,11 +525,10 @@ static int check_and_subscribe_port(struct snd_seq_client *client, } /* add to list */ - guard(write_lock_irq)(&grp->list_lock); if (is_src) - list_add_tail(&subs->src_list, &grp->list_head); + hlist_add_tail_rcu(&subs->src_list, &grp->list_head); else - list_add_tail(&subs->dest_list, &grp->list_head); + hlist_add_tail_rcu(&subs->dest_list, &grp->list_head); grp->exclusive = exclusive; atomic_inc(&subs->ref_count); @@ -544,17 +542,15 @@ static void __delete_and_unsubscribe_port(struct snd_seq_client *client, bool is_src, bool ack) { struct snd_seq_port_subs_info *grp; - struct list_head *list; + struct hlist_node *list; bool empty; grp = is_src ? &port->c_src : &port->c_dest; list = is_src ? &subs->src_list : &subs->dest_list; - scoped_guard(write_lock_irq, &grp->list_lock) { - empty = list_empty(list); - if (!empty) - list_del_init(list); - grp->exclusive = 0; - } + empty = hlist_unhashed(list); + if (!empty) + hlist_del_init_rcu(list); + grp->exclusive = 0; if (!empty) unsubscribe_port(client, port, grp, &subs->info, ack); @@ -590,8 +586,8 @@ int snd_seq_port_connect(struct snd_seq_client *connector, subs->info = *info; atomic_set(&subs->ref_count, 0); - INIT_LIST_HEAD(&subs->src_list); - INIT_LIST_HEAD(&subs->dest_list); + INIT_HLIST_NODE(&subs->src_list); + INIT_HLIST_NODE(&subs->dest_list); exclusive = !!(info->flags & SNDRV_SEQ_PORT_SUBS_EXCLUSIVE); @@ -612,7 +608,7 @@ int snd_seq_port_connect(struct snd_seq_client *connector, delete_and_unsubscribe_port(src_client, src_port, subs, true, connector->number != src_client->number); error: - kfree(subs); + kfree_rcu(subs, rcu); return err; } @@ -633,7 +629,7 @@ int snd_seq_port_disconnect(struct snd_seq_client *connector, */ scoped_guard(rwsem_write, &dest->list_mutex) { /* look for the connection */ - list_for_each_entry(subs, &dest->list_head, dest_list) { + hlist_for_each_entry(subs, &dest->list_head, dest_list) { if (match_subs_info(info, &subs->info)) { __delete_and_unsubscribe_port(dest_client, dest_port, subs, false, @@ -648,7 +644,7 @@ int snd_seq_port_disconnect(struct snd_seq_client *connector, delete_and_unsubscribe_port(src_client, src_port, subs, true, connector->number != src_client->number); - kfree(subs); + kfree_rcu(subs, rcu); return 0; } @@ -662,7 +658,7 @@ int snd_seq_port_get_subscription(struct snd_seq_port_subs_info *src_grp, int err = -ENOENT; guard(rwsem_read)(&src_grp->list_mutex); - list_for_each_entry(s, &src_grp->list_head, src_list) { + hlist_for_each_entry(s, &src_grp->list_head, src_list) { if (addr_match(dest_addr, &s->info.dest)) { *subs = s->info; err = 0; diff --git a/sound/core/seq/seq_ports.h b/sound/core/seq/seq_ports.h index b689c0f4867c44..12ad86bf148907 100644 --- a/sound/core/seq/seq_ports.h +++ b/sound/core/seq/seq_ports.h @@ -28,17 +28,17 @@ struct snd_seq_subscribers { struct snd_seq_port_subscribe info; /* additional info */ - struct list_head src_list; /* link of sources */ - struct list_head dest_list; /* link of destinations */ + struct hlist_node src_list; /* link of sources */ + struct hlist_node dest_list; /* link of destinations */ atomic_t ref_count; + struct rcu_head rcu; /* for deferred free */ }; struct snd_seq_port_subs_info { - struct list_head list_head; /* list of subscribed ports */ + struct hlist_head list_head; /* list of subscribed ports */ unsigned int count; /* count of subscribers */ unsigned int exclusive: 1; /* exclusive mode */ struct rw_semaphore list_mutex; - rwlock_t list_lock; int (*open)(void *private_data, struct snd_seq_port_subscribe *info); int (*close)(void *private_data, struct snd_seq_port_subscribe *info); }; From 4c252fbc06d641d631ad55111a1bffc50c07f72c Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 10 Aug 2026 15:37:03 +0200 Subject: [PATCH 461/612] ALSA: seq: Use RCU for the client port list Each sequencer client keeps a list of its ports (ports_list_head) protected by both an rwlock (ports_lock) and a mutex (ports_mutex). The rwlock is taken read-side on the event delivery hot path: snd_seq_port_use_ptr() walks the list to resolve a port on every dispatched event, while the mutex serializes port creation/deletion. Ports change rarely but delivery happens constantly, so this is the another read-mostly case as the port subscriber list. Convert the port list traversal to RCU and drop the rwlock entirely; the existing ports_mutex keeps serializing the writers. The atomic delivery path (snd_seq_port_use_ptr(), snd_seq_port_query_nearest()) now runs lock-free under rcu_read_lock() instead of contending on the shared rwlock. The writers switch to list_add_tail_rcu()/list_del_rcu(). snd_seq_insert_port() now stores the port number and name before publishing the node so RCU readers only ever observe a fully initialized port. One drawback is that snd_seq_delete_all_ports() drops the O(1) splice trick and unlinks each port individually, though: the splice repointed the last port's ->next away from the list head, which would send a concurrent lockless reader off the end of the list. Unlike the subscriber objects, ports are not freed via kfree_rcu(): port_delete() must drain outstanding use_lock references (and run private_free()) synchronously. The rwlock previously guaranteed that no reader could take a new use_lock reference once the port was unlinked -- list_del under write_lock excluded snd_use_lock_use() under read_lock. list_del_rcu() offers no such exclusion, so a reader still traversing the list can grab a reference after the unlink. port_delete() therefore calls synchronize_rcu() after the port has been unlinked and before snd_use_lock_sync(): once the grace period elapses no new reference can appear, and the existing drain then frees the port safely. Dropping write_lock_irq() from the writers is safe: no writer runs in atomic/IRQ context, and the sole atomic reader now uses RCU, which is IRQ-safe. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260810133711.42483-3-tiwai@suse.de --- sound/core/seq/seq_clientmgr.c | 1 - sound/core/seq/seq_clientmgr.h | 1 - sound/core/seq/seq_ports.c | 51 +++++++++++++++------------------- 3 files changed, 23 insertions(+), 30 deletions(-) diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 77f5020f187396..b7cf14e3ddb32e 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -212,7 +212,6 @@ static struct snd_seq_client *seq_create_client1(int client_index, int poolsize) } client->type = NO_CLIENT; snd_use_lock_init(&client->use_lock); - rwlock_init(&client->ports_lock); mutex_init(&client->ports_mutex); INIT_LIST_HEAD(&client->ports_list_head); mutex_init(&client->ioctl_mutex); diff --git a/sound/core/seq/seq_clientmgr.h b/sound/core/seq/seq_clientmgr.h index feea8bb7d9870a..d7ffc5c1ed61a7 100644 --- a/sound/core/seq/seq_clientmgr.h +++ b/sound/core/seq/seq_clientmgr.h @@ -49,7 +49,6 @@ struct snd_seq_client { /* ports */ int num_ports; /* number of ports */ struct list_head ports_list_head; - rwlock_t ports_lock; struct mutex ports_mutex; struct mutex ioctl_mutex; int convert32; /* convert 32->64bit */ diff --git a/sound/core/seq/seq_ports.c b/sound/core/seq/seq_ports.c index 357c72ed0d3b4c..eb67eb0eeb147c 100644 --- a/sound/core/seq/seq_ports.c +++ b/sound/core/seq/seq_ports.c @@ -48,8 +48,8 @@ struct snd_seq_client_port *snd_seq_port_use_ptr(struct snd_seq_client *client, if (client == NULL) return NULL; - guard(read_lock)(&client->ports_lock); - list_for_each_entry(port, &client->ports_list_head, list) { + guard(rcu)(); + list_for_each_entry_rcu(port, &client->ports_list_head, list) { if (port->addr.port == num) { if (port->closing) break; /* deleting now */ @@ -71,8 +71,8 @@ struct snd_seq_client_port *snd_seq_port_query_nearest(struct snd_seq_client *cl num = pinfo->addr.port; found = NULL; - guard(read_lock)(&client->ports_lock); - list_for_each_entry(port, &client->ports_list_head, list) { + guard(rcu)(); + list_for_each_entry_rcu(port, &client->ports_list_head, list) { if ((port->capability & SNDRV_SEQ_PORT_CAP_INACTIVE) && !check_inactive) continue; /* skip inactive ports */ @@ -153,7 +153,6 @@ int snd_seq_insert_port(struct snd_seq_client *client, int port, num = max(port, 0); guard(mutex)(&client->ports_mutex); - guard(write_lock_irq)(&client->ports_lock); struct list_head *insert_before = &client->ports_list_head; list_for_each_entry(p, &client->ports_list_head, list) { if (p->addr.port == port) @@ -165,12 +164,13 @@ int snd_seq_insert_port(struct snd_seq_client *client, int port, if (port < 0) /* auto-probe mode */ num = p->addr.port + 1; } - /* insert the new port */ - list_add_tail(&new_port->list, insert_before); - client->num_ports++; + /* finish initializing the port before publishing it to RCU readers */ new_port->addr.port = num; /* store the port number in the port */ if (!new_port->name[0]) sprintf(new_port->name, "port-%d", num); + /* insert the new port */ + list_add_tail_rcu(&new_port->list, insert_before); + client->num_ports++; return num; } @@ -253,7 +253,13 @@ static int port_delete(struct snd_seq_client *client, { /* set closing flag and wait for all port access are gone */ port->closing = 1; - snd_use_lock_sync(&port->use_lock); + /* the port has already been unlinked from the client's port list; + * wait for a grace period so that RCU readers still traversing the + * list can no longer take a new use_lock reference, then drain the + * outstanding references before freeing + */ + synchronize_rcu(); + snd_use_lock_sync(&port->use_lock); /* clear subscribers info */ clear_subscriber_list(client, port, &port->c_src, true); @@ -276,11 +282,10 @@ int snd_seq_delete_port(struct snd_seq_client *client, int port) struct snd_seq_client_port *found = NULL, *p; scoped_guard(mutex, &client->ports_mutex) { - guard(write_lock_irq)(&client->ports_lock); list_for_each_entry(p, &client->ports_list_head, list) { if (p->addr.port == port) { /* ok found. delete from the list at first */ - list_del(&p->list); + list_del_rcu(&p->list); client->num_ports--; found = p; break; @@ -296,26 +301,16 @@ int snd_seq_delete_port(struct snd_seq_client *client, int port) /* delete the all ports belonging to the given client */ int snd_seq_delete_all_ports(struct snd_seq_client *client) { - struct list_head deleted_list; struct snd_seq_client_port *port, *tmp; - - /* move the port list to deleted_list, and - * clear the port list in the client data. + + /* unlink and delete each port; port_delete() waits for an RCU grace + * period before draining the port, so concurrent lockless readers can + * no longer take a new use_lock reference on it */ guard(mutex)(&client->ports_mutex); - scoped_guard(write_lock_irq, &client->ports_lock) { - if (!list_empty(&client->ports_list_head)) { - list_add(&deleted_list, &client->ports_list_head); - list_del_init(&client->ports_list_head); - } else { - INIT_LIST_HEAD(&deleted_list); - } - client->num_ports = 0; - } - - /* remove each port in deleted_list */ - list_for_each_entry_safe(port, tmp, &deleted_list, list) { - list_del(&port->list); + list_for_each_entry_safe(port, tmp, &client->ports_list_head, list) { + list_del_rcu(&port->list); + client->num_ports--; snd_seq_system_client_ev_port_exit(port->addr.client, port->addr.port); port_delete(client, port); } From 7a287e4615d623fade44bec48077263c7564abf6 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 10 Aug 2026 15:37:04 +0200 Subject: [PATCH 462/612] ALSA: seq: Use RCU for the client table The sequencer keeps a global table of clients (clienttab[]) indexed by client id, protected by the global clients_lock spinlock. The lookup snd_seq_client_use_ptr() reads a slot and takes a use_lock reference on the client, and this runs on the event delivery hot path: every dispatched event resolves its destination (and often source) client through it. The spinlock's only job on the read side is to make the "pointer is non-NULL" test and the reference increment indivisible with respect to the writer that nulls the slot and then drains the refcount. Clients come and go rarely but delivery happens constantly, so this is yet another read-mostly case as the port and subscriber lists. Convert the table to RCU: the read side now runs lock-free under rcu_read_lock() and takes the use_lock reference via rcu_dereference(), removing contention on the single global spinlock from the delivery path. The writers keep clients_lock (still needed to serialize slot allocation) and publish / unpublish via rcu_assign_pointer(); creation and destruction remain serialized at a higher level by register_mutex. As with the ports, the client is not freed via kfree_rcu(): its lifetime is governed by the use_lock refcount drained in seq_free_client1(). list_del under the old spinlock excluded a concurrent lookup from taking a new reference once the slot was nulled; rcu_assign_pointer(NULL) offers no such exclusion, so a reader still holding the old pointer can grab a reference after the unpublish. seq_free_client1() therefore calls synchronize_rcu() after nulling the slot and before snd_use_lock_sync(): once the grace period elapses no new reference can appear, and the existing drain then frees the client safely. clienttablock[] keeps its slot-reservation role (create/free are serialized by register_mutex); its read on the lookup path only gates module autoload, so a lockless read is harmless. Dropping the spinlock from the read path is safe: clients_lock is now taken only by the process-context writers, and the sole atomic reader uses RCU, which is IRQ-safe. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260810133711.42483-4-tiwai@suse.de --- sound/core/seq/seq_clientmgr.c | 43 ++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index b7cf14e3ddb32e..d4cac594bc8f1e 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -59,7 +59,7 @@ static DEFINE_MUTEX(register_mutex); * client table */ static char clienttablock[SNDRV_SEQ_MAX_CLIENTS]; -static struct snd_seq_client *clienttab[SNDRV_SEQ_MAX_CLIENTS]; +static struct snd_seq_client __rcu *clienttab[SNDRV_SEQ_MAX_CLIENTS]; static struct snd_seq_usage client_usage; /* @@ -95,15 +95,23 @@ static inline int snd_seq_write_pool_allocated(struct snd_seq_client *client) return snd_seq_total_cells(client->pool) > 0; } -/* return pointer to client structure for specified id */ -static struct snd_seq_client *clientptr(int clientid) +/* return pointer to client structure for specified id; call under RCU read-lock */ +static struct snd_seq_client *__clientptr(int clientid) { if (clientid < 0 || clientid >= SNDRV_SEQ_MAX_CLIENTS) { pr_debug("ALSA: seq: oops. Trying to get pointer to client %d\n", clientid); return NULL; } - return clienttab[clientid]; + return rcu_dereference_check(clienttab[clientid], + lockdep_is_held(&clients_lock)); +} + +/* return pointer to client structure for specified id */ +static struct snd_seq_client *clientptr(int clientid) +{ + guard(rcu)(); + return __clientptr(clientid); } static struct snd_seq_client *client_use_ptr(int clientid, bool load_module) @@ -115,8 +123,8 @@ static struct snd_seq_client *client_use_ptr(int clientid, bool load_module) clientid); return NULL; } - scoped_guard(spinlock_irqsave, &clients_lock) { - client = clientptr(clientid); + scoped_guard(rcu) { + client = __clientptr(clientid); if (client) return snd_seq_client_ref(client); if (clienttablock[clientid]) @@ -150,8 +158,8 @@ static struct snd_seq_client *client_use_ptr(int clientid, bool load_module) snd_seq_device_load_drivers(); } } - scoped_guard(spinlock_irqsave, &clients_lock) { - client = clientptr(clientid); + scoped_guard(rcu) { + client = __clientptr(clientid); if (client) return snd_seq_client_ref(client); } @@ -223,14 +231,17 @@ static struct snd_seq_client *seq_create_client1(int client_index, int poolsize) for (c = SNDRV_SEQ_DYNAMIC_CLIENTS_BEGIN; c < SNDRV_SEQ_MAX_CLIENTS; c++) { - if (clienttab[c] || clienttablock[c]) + if (rcu_access_pointer(clienttab[c]) || clienttablock[c]) continue; - clienttab[client->number = c] = client; + client->number = c; + rcu_assign_pointer(clienttab[c], client); return client; } } else { - if (clienttab[client_index] == NULL && !clienttablock[client_index]) { - clienttab[client->number = client_index] = client; + if (rcu_access_pointer(clienttab[client_index]) == NULL && + !clienttablock[client_index]) { + client->number = client_index; + rcu_assign_pointer(clienttab[client_index], client); return client; } } @@ -248,10 +259,16 @@ static int seq_free_client1(struct snd_seq_client *client) return 0; scoped_guard(spinlock_irq, &clients_lock) { clienttablock[client->number] = 1; - clienttab[client->number] = NULL; + rcu_assign_pointer(clienttab[client->number], NULL); } snd_seq_delete_all_ports(client); snd_seq_queue_client_leave(client->number); + /* the client has been unpublished from the table; wait for a grace + * period so that lockless readers (snd_seq_client_use_ptr()) that + * observed the old pointer can no longer take a new use_lock + * reference, then drain the outstanding references before freeing + */ + synchronize_rcu(); snd_use_lock_sync(&client->use_lock); if (client->pool) snd_seq_pool_delete(&client->pool); From 7ffa5d2462dcf307746a79e959ceac6cd5828bfe Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 10 Aug 2026 15:37:05 +0200 Subject: [PATCH 463/612] ALSA: seq: Use RCU for the virmidi file list Each virmidi device keeps a list of its opened input files (filelist) protected by both an rwlock (filelist_lock) and a rw_semaphore (filelist_sem). snd_virmidi_dev_receive_event() walks the list on the sequencer event input path -- read_lock() when the event is delivered in atomic context, down_read() otherwise -- decoding each incoming event into the file's rawmidi buffer. The writers (input open/close) take both locks to add/remove entries. This is another typical dual-lock read-mostly pattern as the port subscriber list: files are opened/closed rarely while the receive callback runs per event. Let's convert the traversal to RCU and drop the rwlock; the existing filelist_sem keeps serializing the writers. The atomic input path now runs lock-free under rcu_read_lock(), and both readers share a single list_for_each_entry_rcu() (valid under the rwsem via lockdep_is_held()). The writers switch to list_add_tail_rcu() / list_del_rcu(). snd_virmidi_input_close() freed the entry (parser and struct) immediately after list_del. A concurrent lockless reader in the atomic path may still be dereferencing it, so the close path now waits for an RCU grace period after list_del_rcu() before freeing; synchronize_rcu() is used rather than kfree_rcu() because the parser must also be released after the grace period, not just the struct. Non-atomic readers are already excluded by the down_write, so only the atomic RCU readers need the grace period. Dropping write_lock_irq() from the writers is safe: no writer runs in atomic/IRQ context, and the sole atomic reader now uses RCU, which is IRQ-safe. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260810133711.42483-5-tiwai@suse.de --- include/sound/seq_virmidi.h | 1 - sound/core/seq/seq_virmidi.c | 19 +++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/include/sound/seq_virmidi.h b/include/sound/seq_virmidi.h index 56a3f38df8c3bb..359cb363369d95 100644 --- a/include/sound/seq_virmidi.h +++ b/include/sound/seq_virmidi.h @@ -46,7 +46,6 @@ struct snd_virmidi_dev { int client; /* created/attached client */ int port; /* created/attached port */ unsigned int flags; /* SNDRV_VIRMIDI_* */ - rwlock_t filelist_lock; struct rw_semaphore filelist_sem; struct list_head filelist; }; diff --git a/sound/core/seq/seq_virmidi.c b/sound/core/seq/seq_virmidi.c index 982828650d4157..6208bf7f57bf18 100644 --- a/sound/core/seq/seq_virmidi.c +++ b/sound/core/seq/seq_virmidi.c @@ -78,10 +78,11 @@ static int snd_virmidi_dev_receive_event(struct snd_virmidi_dev *rdev, int len; if (atomic) - read_lock(&rdev->filelist_lock); + rcu_read_lock(); else down_read(&rdev->filelist_sem); - list_for_each_entry(vmidi, &rdev->filelist, list) { + list_for_each_entry_rcu(vmidi, &rdev->filelist, list, + lockdep_is_held(&rdev->filelist_sem)) { if (!READ_ONCE(vmidi->trigger)) continue; if (ev->type == SNDRV_SEQ_EVENT_SYSEX) { @@ -96,7 +97,7 @@ static int snd_virmidi_dev_receive_event(struct snd_virmidi_dev *rdev, } } if (atomic) - read_unlock(&rdev->filelist_lock); + rcu_read_unlock(); else up_read(&rdev->filelist_sem); @@ -200,8 +201,7 @@ static int snd_virmidi_input_open(struct snd_rawmidi_substream *substream) vmidi->port = rdev->port; runtime->private_data = vmidi; scoped_guard(rwsem_write, &rdev->filelist_sem) { - guard(write_lock_irq)(&rdev->filelist_lock); - list_add_tail(&vmidi->list, &rdev->filelist); + list_add_tail_rcu(&vmidi->list, &rdev->filelist); } vmidi->rdev = rdev; return 0; @@ -243,9 +243,13 @@ static int snd_virmidi_input_close(struct snd_rawmidi_substream *substream) struct snd_virmidi *vmidi = substream->runtime->private_data; scoped_guard(rwsem_write, &rdev->filelist_sem) { - guard(write_lock_irq)(&rdev->filelist_lock); - list_del(&vmidi->list); + list_del_rcu(&vmidi->list); } + /* wait for a grace period so that lockless readers in the atomic + * delivery path (snd_virmidi_dev_receive_event()) are no longer + * traversing this entry before its parser and memory are freed + */ + synchronize_rcu(); snd_midi_event_free(vmidi->parser); substream->runtime->private_data = NULL; kfree(vmidi); @@ -508,7 +512,6 @@ int snd_virmidi_new(struct snd_card *card, int device, struct snd_rawmidi **rrmi rdev->device = device; rdev->client = -1; init_rwsem(&rdev->filelist_sem); - rwlock_init(&rdev->filelist_lock); INIT_LIST_HEAD(&rdev->filelist); rdev->seq_mode = SNDRV_VIRMIDI_SEQ_DISPATCH; rmidi->private_data = rdev; From 0252ad169c5eb8d9cfd2ee646a7cd055865e1f12 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 10 Aug 2026 15:37:06 +0200 Subject: [PATCH 464/612] ALSA: seq: Use RCU for the UMP client output substream The UMP sequencer client protects its output rawmidi file (out_rfile) with an rwlock (output_lock). seq_ump_process_event(), the port's event_input callback, reads out_rfile.output under read_lock on every delivered UMP event, while the open/close paths (serialized by ump->open_mutex) publish and clear out_rfile under write_lock. Output is opened/closed only on the subscribe/use lifecycle while delivery happens per event, so this is another read-mostly hot path. Convert it to RCU and drop the rwlock. out_rfile is an embedded struct rather than a pointer, so instead of restructuring it, add an RCU-protected shadow of the substream (out_substream) for the reader; out_rfile itself becomes writer-only state accessed solely under open_mutex. The reader now runs lock-free under rcu_read_lock() via rcu_dereference(), and open publishes the substream with rcu_assign_pointer(). On close the substream is cleared with rcu_assign_pointer(NULL) and the rawmidi is released only after synchronize_rcu(), so no reader in the delivery path can still be writing to the substream when snd_rawmidi_kernel_release() runs. Dropping write_lock_irqsave() from the writers is safe: they run in process context under open_mutex, and the sole atomic reader now uses RCU, which is IRQ-safe. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260810133711.42483-6-tiwai@suse.de --- sound/core/seq/seq_ump_client.c | 38 +++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/sound/core/seq/seq_ump_client.c b/sound/core/seq/seq_ump_client.c index ccd93599b493b7..4c1e81db376f81 100644 --- a/sound/core/seq/seq_ump_client.c +++ b/sound/core/seq/seq_ump_client.c @@ -37,8 +37,11 @@ struct seq_ump_client { struct snd_ump_endpoint *ump; /* assigned endpoint */ int seq_client; /* sequencer client id */ int opened[2]; /* current opens for each direction */ - rwlock_t output_lock; /* protects out_rfile output access */ struct snd_rawmidi_file out_rfile; /* rawmidi for output */ + /* RCU-protected shadow of out_rfile.output for the delivery hot path; + * out_rfile itself is only touched by open/close under open_mutex + */ + struct snd_rawmidi_substream __rcu *out_substream; struct seq_ump_input_buffer input; /* input parser context */ void *ump_info[SNDRV_UMP_MAX_BLOCKS + 1]; /* shadow of seq client ump_info */ struct work_struct group_notify_work; /* FB change notification */ @@ -89,8 +92,8 @@ static int seq_ump_process_event(struct snd_seq_event *ev, int direct, unsigned char type; int len; - guard(read_lock_irqsave)(&client->output_lock); - substream = client->out_rfile.output; + guard(rcu)(); + substream = rcu_dereference(client->out_substream); if (!substream) return -ENODEV; if (!snd_seq_ev_is_ump(ev)) @@ -108,19 +111,22 @@ static int seq_ump_process_event(struct snd_seq_event *ev, int direct, static int seq_ump_client_open(struct seq_ump_client *client, int dir) { struct snd_ump_endpoint *ump = client->ump; - struct snd_rawmidi_file rfile = {}; int err; guard(mutex)(&ump->open_mutex); if (dir == STR_OUT && !client->opened[dir]) { + /* out_rfile is only accessed under open_mutex; the delivery + * path reads out_substream via RCU, so open into out_rfile + * directly and publish the substream afterwards + */ err = snd_rawmidi_kernel_open(&ump->core, 0, SNDRV_RAWMIDI_LFLG_OUTPUT | SNDRV_RAWMIDI_LFLG_APPEND, - &rfile); + &client->out_rfile); if (err < 0) return err; - scoped_guard(write_lock_irqsave, &client->output_lock) - client->out_rfile = rfile; + rcu_assign_pointer(client->out_substream, + client->out_rfile.output); } client->opened[dir]++; return 0; @@ -130,17 +136,18 @@ static int seq_ump_client_open(struct seq_ump_client *client, int dir) static int seq_ump_client_close(struct seq_ump_client *client, int dir) { struct snd_ump_endpoint *ump = client->ump; - struct snd_rawmidi_file rfile = {}; guard(mutex)(&ump->open_mutex); if (!--client->opened[dir]) { - if (dir == STR_OUT) { - scoped_guard(write_lock_irqsave, &client->output_lock) { - rfile = client->out_rfile; - client->out_rfile = (struct snd_rawmidi_file){}; - } - if (rfile.rmidi) - snd_rawmidi_kernel_release(&rfile); + if (dir == STR_OUT && client->out_rfile.rmidi) { + rcu_assign_pointer(client->out_substream, NULL); + /* wait for a grace period so that no reader in the + * delivery path is still writing to the substream + * before it is released + */ + synchronize_rcu(); + snd_rawmidi_kernel_release(&client->out_rfile); + client->out_rfile = (struct snd_rawmidi_file){}; } } return 0; @@ -480,7 +487,6 @@ static int snd_seq_ump_probe(struct snd_seq_device *dev) INIT_WORK(&client->group_notify_work, handle_group_notify); client->ump = ump; - rwlock_init(&client->output_lock); client->seq_client = snd_seq_create_kernel_client(card, ump->core.device, From 04a8e286bb2e3a0dc37980a8b7da76da339d1a88 Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Tue, 11 Aug 2026 10:49:00 +0800 Subject: [PATCH 465/612] ALSA: hda/realtek: Add quirk for Acer Predator PH16-71 The Acer Predator PH16-71 (subsystem 0x1025:0x166c) with Realtek ALC245 codec has a non-functional headset microphone. Adding the ALC2XX_FIXUP_HEADSET_MIC quirk resolves the issue. Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221641 Signed-off-by: Zhang Heng Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260811024902.134457-2-zhangheng@kylinos.cn --- 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 f92c044ea553cb..c86983fea13b5d 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7053,6 +7053,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1025, 0x160e, "Acer PT316-51S", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x161f, "Acer S40-54", ALC256_FIXUP_ACER_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1025, 0x1640, "Acer Aspire A315-44P", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x166c, "Acer Predator PH16-71", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x1679, "Acer Nitro 16 AN16-41", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x169a, "Acer Swift SFG16", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), SND_PCI_QUIRK(0x1025, 0x171e, "Acer Nitro ANV15-51", ALC245_FIXUP_ACER_MICMUTE_LED), From 9976513eb6422e7c3a1f35df49ad4ba2e1b9ac8b Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Tue, 11 Aug 2026 10:49:01 +0800 Subject: [PATCH 466/612] ALSA: hda/realtek: Add quirk for Lenovo Legion Pro 5 16ADR10 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Lenovo Legion Pro 5 16ADR10 (codec SSID 0x17aa:0x3926) suffers from distorted/crackling speaker output, as only one speaker pin is driven without proper COEF/amp initialization. Add HDA_CODEC_QUIRK applying ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN to enable both speaker pins and proper amp initialization, restoring clean audio output at all volume levels. Tested: Both internal speaker pairs are now driven correctly and distortion is gone; headphone output remains unaffected. Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221595 Signed-off-by: Zhang Heng Tested-by: Efe Yılmaz Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260811024902.134457-3-zhangheng@kylinos.cn --- 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 c86983fea13b5d..236ae507bca687 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -8064,6 +8064,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x38b8, "Yoga S780-14.5 proX AMD YC Dual", ALC287_FIXUP_TAS2781_I2C), SND_PCI_QUIRK(0x17aa, 0x38b9, "Yoga S780-14.5 proX AMD LX Dual", ALC287_FIXUP_TAS2781_I2C), SND_PCI_QUIRK(0x17aa, 0x38ba, "Yoga S780-14.5 Air AMD quad YC", ALC287_FIXUP_TAS2781_I2C), + HDA_CODEC_QUIRK(0x17aa, 0x3926, "Legion Pro 5 16ADR10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), /* Legion R9000P ADR10 shares PCI SSID 17aa:38bb with Yoga S780-14.5 Air AMD quad AAC; * use codec SSID to distinguish them */ From 9508f9f122d4af799cf4a422eb31eedd888b76d2 Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Tue, 11 Aug 2026 10:49:02 +0800 Subject: [PATCH 467/612] ALSA: usb-audio: Fix popping noise on Valeton GP-200 The Valeton GP-200 guitar multi-effects processor exhibits a continuous popping noise (~6Hz) during playback and recording. Force implicit feedback to resolve the issue. Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221662 Signed-off-by: Zhang Heng Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260811024902.134457-4-zhangheng@kylinos.cn --- sound/usb/quirks.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index d00d6a543a9fa3..eb0d012cf8560b 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2526,6 +2526,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_ALIGN_TRANSFER), DEVICE_FLG(0x534d, 0x2109, /* MacroSilicon MS2109 */ QUIRK_FLAG_ALIGN_TRANSFER), + DEVICE_FLG(0x84ef, 0x002a, /* Valeton GP-200 */ + QUIRK_FLAG_GENERIC_IMPLICIT_FB), DEVICE_FLG(0x84ef, 0x0082, /* Hotone Audio Pulze Mini */ QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL | QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL), From a9ac75b664d917220dfe2e3c1005402d7a18e84b Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Mon, 10 Aug 2026 21:21:22 -0700 Subject: [PATCH 468/612] ALSA: pci: asihpi: use pcim_iomap for managed PCI memory mapping Replace manual ioremap() calls with pcim_iomap() which uses devres for automatic cleanup. This eliminates the need for manual iounmap() in both the error path of asihpi_adapter_probe() and the asihpi_adapter_remove() function. The pcim_iomap() helper is cleaner and less error-prone since it handles unmapping automatically when the PCI device is released. Assisted-by: opencode/big-pickle Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260811042122.44923-1-rosenp@gmail.com Signed-off-by: Takashi Iwai --- sound/pci/asihpi/hpioctl.c | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/sound/pci/asihpi/hpioctl.c b/sound/pci/asihpi/hpioctl.c index 9de9ae7032b8fa..ec2da792e1c81a 100644 --- a/sound/pci/asihpi/hpioctl.c +++ b/sound/pci/asihpi/hpioctl.c @@ -385,8 +385,7 @@ int asihpi_adapter_probe(struct pci_dev *pci_dev, if (pci_resource_flags(pci_dev, idx) & IORESOURCE_MEM) { memlen = pci_resource_len(pci_dev, idx); pci.ap_mem_base[idx] = - ioremap(pci_resource_start(pci_dev, idx), - memlen); + pcim_iomap(pci_dev, idx, memlen); if (!pci.ap_mem_base[idx]) { HPI_DEBUG_LOG(ERROR, "ioremap failed, aborting\n"); @@ -509,13 +508,6 @@ int asihpi_adapter_probe(struct pci_dev *pci_dev, return 0; err: - while (--idx >= 0) { - if (pci.ap_mem_base[idx]) { - iounmap(pci.ap_mem_base[idx]); - pci.ap_mem_base[idx] = NULL; - } - } - if (adapter.p_buffer) { adapter.buffer_size = 0; vfree(adapter.p_buffer); @@ -527,14 +519,11 @@ int asihpi_adapter_probe(struct pci_dev *pci_dev, void asihpi_adapter_remove(struct pci_dev *pci_dev) { - int idx; struct hpi_message hm; struct hpi_response hr; struct hpi_adapter *pa; - struct hpi_pci pci; pa = pci_get_drvdata(pci_dev); - pci = pa->adapter->pci; /* Disable IRQ generation on DSP side */ hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, @@ -550,10 +539,6 @@ void asihpi_adapter_remove(struct pci_dev *pci_dev) hm.adapter_index = pa->adapter->index; hpi_send_recv_ex(&hm, &hr, HOWNER_KERNEL); - /* unmap PCI memory space, mapped during device init. */ - for (idx = 0; idx < HPI_MAX_ADAPTER_MEM_SPACES; ++idx) - iounmap(pci.ap_mem_base[idx]); - if (pa->irq) free_irq(pa->irq, pa); From 108704eecab9563a457ef6f32c7eef06e7703a5e Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Tue, 11 Aug 2026 14:27:34 +0800 Subject: [PATCH 469/612] ALSA: hda/realtek: Rename Line Out control to Headphone on ThinkPad X1 Carbon 6th The ThinkPad X1 Carbon 6th Gen (ALC285, SSID 17aa:225c) has no physical Line Out jack. The 3.5mm headphone jack is wired to the headphone DAC, but the ALSA HDA driver names the corresponding control as "Line Out Playback Volume" (node 0x02). PipeWire's ALSA Card Profile (ACP) silences "Line Out" when headphones are activated, which incorrectly mutes the headphone output. Add a quirk to rename the control to "Headphone Playback Volume" via alc285_lenovo_dac_rename(). Tested on openSUSE Tumbleweed (kernel 7.1.5): - Control renamed successfully, no name collision with "Headphone Playback Switch" - Headphone output works across multiple PipeWire/WirePlumber restarts and port switches Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221522 Signed-off-by: Zhang Heng Tested-by: Branislav Klocok Link: https://patch.msgid.link/20260811062734.400512-1-zhangheng@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 236ae507bca687..87d59a9dc55fee 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -2572,6 +2572,13 @@ static void alc282_fixup_asus_tx300(struct hda_codec *codec, } } +static void alc285_lenovo_dac_rename(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_BUILD) + rename_ctl(codec, "Line Out Playback Volume", + "Headphone Playback Volume"); +} static void alc290_fixup_mono_speakers(struct hda_codec *codec, const struct hda_fixup *fix, int action) { @@ -4289,6 +4296,7 @@ enum { ALC287_FIXUP_AW88399_I2C_2, ALC287_FIXUP_LENOVO_LEGION_AW88399, ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN_HEADSET, + ALC285_LENOVO_DAC_RENAME, }; /* A special fixup for Lenovo C940 and Yoga Duet 7; @@ -6998,6 +7006,10 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN, }, + [ALC285_LENOVO_DAC_RENAME] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_lenovo_dac_rename, + }, }; static const struct hda_quirk alc269_fixup_tbl[] = { @@ -7958,6 +7970,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x224b, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), SND_PCI_QUIRK(0x17aa, 0x224c, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), SND_PCI_QUIRK(0x17aa, 0x224d, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x225c, "Lenovo ThinkPad X1 Carbon 6th Gen", ALC285_LENOVO_DAC_RENAME), SND_PCI_QUIRK(0x17aa, 0x225d, "Thinkpad T480", ALC269_FIXUP_THINKPAD_LIMIT_INT_MIC_BOOST), SND_PCI_QUIRK(0x17aa, 0x2288, "Thinkpad X390", ALC285_FIXUP_THINKPAD_NO_BASS_SPK_HEADSET_JACK), SND_PCI_QUIRK(0x17aa, 0x2292, "Thinkpad X1 Carbon 7th", ALC285_FIXUP_THINKPAD_HEADSET_JACK), From 6f14f6a24f110f665023211bbfb6abc2ce12c948 Mon Sep 17 00:00:00 2001 From: Shenghao Ding Date: Tue, 11 Aug 2026 20:14:46 +0800 Subject: [PATCH 470/612] ASoC: tas2781: Fix compiling warning for tasdevice_set_capture_profile_id() Correct the mismatched function description, parameter names and return value documentation in the comment block. No functional code change, only comment and documentation update. Fixes: 431c15610d01 ("ASoC: tas2781: add capture_profile_id field and update the tuning_switch function") Signed-off-by: Shenghao Ding Link: https://patch.msgid.link/20260811121446.1805-1-shenghao-ding@ti.com Signed-off-by: Mark Brown --- sound/soc/codecs/tas2781-i2c.c | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/sound/soc/codecs/tas2781-i2c.c b/sound/soc/codecs/tas2781-i2c.c index 70229e8279a32a..c029345b464442 100644 --- a/sound/soc/codecs/tas2781-i2c.c +++ b/sound/soc/codecs/tas2781-i2c.c @@ -1001,18 +1001,25 @@ static int tasdevice_set_profile_id(struct snd_kcontrol *kcontrol, } /** - * tasdevice_get_capture_profile_id - Report current active capture profile - * ID to user space - * @kcontrol: ALSA kcontrol structure passed from ALSA core - * @ucontrol: User-space control element value buffer to write the result back + * tasdevice_set_capture_profile_id - Set runtime capture profile index via + * ALSA control + * @kcontrol: ALSA kcontrol handle that triggers this operation + * @ucontrol: User space control value carrying the new profile index * - * This function ensures the returned profile ID is always clamped inside the - * valid range advertised by the info callback, preventing accidental invalid - * values from being exposed to applications even if internal driver state is - * temporarily inconsistent. + * This mixer control handler validates the user-provided capture profile ID + * against the maximum valid index parsed from the loaded DSP firmware, + * then updates the runtime stored capture profile ID only if the new value + * differs from the current active one. It will immediately return -EINVAL + * if the submitted profile ID falls outside the valid range, including the + * edge case that no valid configuration blocks are detected in firmware. * - * Returns 0 on successful fill of the control value, no error conditions - * are defined for this getter callback. + * No actual DSP register write is performed in this handler. The updated + * profile ID will be applied to the hardware when the next ALSA capture + * stream starts up. Caller does not need to take extra codec lock here, + * as the ALSA control core already guarantees serialized execution. + * + * Return: 1 if profile ID value was changed, 0 if no modification needed, + * -EINVAL if the input profile ID is out of valid range */ static int tasdevice_set_capture_profile_id(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) From ce76c44c34ad1d4e0b1671ba376db814afbb3e83 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 6 Aug 2026 12:59:37 +0700 Subject: [PATCH 471/612] ASoC: Intel: KMB: Propagate -EPROBE_DEFER from IRQ lookup Return -EPROBE_DEFER from platform_get_irq_optional() so the driver is re-probed when the interrupt resource becomes available instead of continuing probe without an IRQ. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260806055937.24600-1-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/intel/keembay/kmb_platform.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/intel/keembay/kmb_platform.c b/sound/soc/intel/keembay/kmb_platform.c index 6659e8060ef3a9..48f132f0187883 100644 --- a/sound/soc/intel/keembay/kmb_platform.c +++ b/sound/soc/intel/keembay/kmb_platform.c @@ -873,6 +873,8 @@ static int kmb_plat_dai_probe(struct platform_device *pdev) if (kmb_i2s->use_pio) { irq = platform_get_irq_optional(pdev, 0); + if (irq == -EPROBE_DEFER) + return irq; if (irq > 0) { ret = devm_request_irq(dev, irq, kmb_i2s_irq_handler, 0, pdev->name, kmb_i2s); From 78983d82dc4c677c5cd2941ef828b1903ca51c9e Mon Sep 17 00:00:00 2001 From: Niranjan H Y Date: Fri, 7 Aug 2026 20:46:23 +0530 Subject: [PATCH 472/612] ASoC: tac5xx2-sdw: select REGMAP_SOUNDWIRE_MBQ "select REGMAP_SOUNDWIRE_MBQ" to fix build error: ERROR: modpost: "__devm_regmap_init_sdw_mbq" [sound/soc/codecs/snd-soc-tac5xx2-sdw.ko] undefined! Signed-off-by: Niranjan H Y Link: https://patch.msgid.link/20260807151623.3005-1-niranjan.hy@ti.com Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index d43475a63ac29b..e4d139108d7204 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -2157,6 +2157,7 @@ config SND_SOC_TAC5XX2_SDW tristate "Texas Instruments TAC5XX2 SoundWire Smart Amplifier" depends on SOUNDWIRE depends on SND_SOC_SDCA + select REGMAP_SOUNDWIRE_MBQ help This option enables support for Texas Instruments TAC5XX2 family of SoundWire Smart Amplifiers. This includes TAC5572, TAC5672, From 9e0698b77684c38f12eb86f828e8a49cc5624304 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 5 Aug 2026 11:45:56 +0700 Subject: [PATCH 473/612] ASoC: ti: omap-twl4030: Check for missing card name after parsing Return any error from snd_soc_of_parse_card_name() directly. If the helper returns successfully but card->name remains unset, report the missing card name explicitly before returning -ENODEV. Suggested-by: Andreas Kemnade Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260805044556.38183-1-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/ti/omap-twl4030.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sound/soc/ti/omap-twl4030.c b/sound/soc/ti/omap-twl4030.c index 8a3a792a21c640..a3ab52b2b64e2c 100644 --- a/sound/soc/ti/omap-twl4030.c +++ b/sound/soc/ti/omap-twl4030.c @@ -216,7 +216,11 @@ static int omap_twl4030_probe(struct platform_device *pdev) if (priv == NULL) return -ENOMEM; - if (snd_soc_of_parse_card_name(card, "ti,model")) { + ret = snd_soc_of_parse_card_name(card, "ti,model"); + if (ret) + return ret; + + if (!card->name) { dev_err(&pdev->dev, "Card name is not provided\n"); return -ENODEV; } From daa7ffd765ae67a83e77dae32c66ce2d6d995d19 Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Sun, 26 Jul 2026 23:12:26 +0200 Subject: [PATCH 474/612] ASoC: qcom: q6apm: keep the graph start count in sync with the DSP q6apm_graph_start() increments start_count even when APM_CMD_GRAPH_START fails, leaving the graph counted as running while the DSP never started it. A later start - a retried prepare, or a resume after a failed start - then finds a non-zero count, skips the command and returns success with no data flowing. Count the graph only once the DSP has accepted the start. The count then stays at zero for a graph that never started, so also stop decrementing below zero in q6apm_graph_stop(): the compressed free path stops unconditionally, and a negative count would make the next start skip the command in the same way. Fixes: 5477518b8a0e ("ASoC: qdsp6: audioreach: add q6apm support") Assisted-by: Claude:claude-opus-5 Signed-off-by: Jorijn van der Graaf Link: https://patch.msgid.link/20260726211226.94059-1-jorijnvdgraaf@catcrafts.net Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/q6apm.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/sound/soc/qcom/qdsp6/q6apm.c b/sound/soc/qcom/qdsp6/q6apm.c index 641d6d24322990..f167b9dae3fa14 100644 --- a/sound/soc/qcom/qdsp6/q6apm.c +++ b/sound/soc/qcom/qdsp6/q6apm.c @@ -802,14 +802,17 @@ EXPORT_SYMBOL_GPL(q6apm_graph_prepare); int q6apm_graph_start(struct q6apm_graph *graph) { struct audioreach_graph *ar_graph = graph->ar_graph; - int ret = 0; + int ret; - if (ar_graph->start_count == 0) + if (ar_graph->start_count == 0) { ret = audioreach_graph_mgmt_cmd(ar_graph, APM_CMD_GRAPH_START); + if (ret) + return ret; + } ar_graph->start_count++; - return ret; + return 0; } EXPORT_SYMBOL_GPL(q6apm_graph_start); @@ -817,6 +820,9 @@ int q6apm_graph_stop(struct q6apm_graph *graph) { struct audioreach_graph *ar_graph = graph->ar_graph; + if (ar_graph->start_count == 0) + return 0; + if (--ar_graph->start_count > 0) return 0; From f12afefb7b01f94d6d66d397f323a9914edbf70e Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Thu, 6 Aug 2026 16:32:31 -0700 Subject: [PATCH 475/612] ASoC: xilinx: formatter_pcm: pass aud_drv_data to irq handlers The irq handlers take a struct device pointer and call dev_get_drvdata() to obtain the driver data. However, the driver data is only set at the end of probe, after devm_request_irq(), so an interrupt taken in between causes the handlers to pass a NULL pointer to readl() and crash. Pass the private data directly as the devm_request_irq() argument instead of the device pointer, matching what the handlers expect. Fixes: 6f6c3c36f091 ("ASoC: xlnx: add pcm formatter platform driver") Assisted-by: opencode:deepseek-v4-flash-free Signed-off-by: Rosen Penev Reviewed-by: Michal Simek Link: https://patch.msgid.link/20260806233231.30631-1-rosenp@gmail.com Signed-off-by: Mark Brown --- sound/soc/xilinx/xlnx_formatter_pcm.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/sound/soc/xilinx/xlnx_formatter_pcm.c b/sound/soc/xilinx/xlnx_formatter_pcm.c index 8f7a76758535b8..f15336197ed31e 100644 --- a/sound/soc/xilinx/xlnx_formatter_pcm.c +++ b/sound/soc/xilinx/xlnx_formatter_pcm.c @@ -281,8 +281,7 @@ static irqreturn_t xlnx_mm2s_irq_handler(int irq, void *arg) { u32 val; void __iomem *reg; - struct device *dev = arg; - struct xlnx_pcm_drv_data *adata = dev_get_drvdata(dev); + struct xlnx_pcm_drv_data *adata = arg; reg = adata->mmio + XLNX_MM2S_OFFSET + XLNX_AUD_STS; val = readl(reg); @@ -300,8 +299,7 @@ static irqreturn_t xlnx_s2mm_irq_handler(int irq, void *arg) { u32 val; void __iomem *reg; - struct device *dev = arg; - struct xlnx_pcm_drv_data *adata = dev_get_drvdata(dev); + struct xlnx_pcm_drv_data *adata = arg; reg = adata->mmio + XLNX_S2MM_OFFSET + XLNX_AUD_STS; val = readl(reg); @@ -637,7 +635,7 @@ static int xlnx_formatter_pcm_probe(struct platform_device *pdev) } ret = devm_request_irq(dev, aud_drv_data->mm2s_irq, xlnx_mm2s_irq_handler, 0, - "xlnx_formatter_pcm_mm2s_irq", dev); + "xlnx_formatter_pcm_mm2s_irq", aud_drv_data); if (ret) { dev_err(dev, "xlnx audio mm2s irq request failed\n"); goto clk_err; @@ -664,7 +662,7 @@ static int xlnx_formatter_pcm_probe(struct platform_device *pdev) ret = devm_request_irq(dev, aud_drv_data->s2mm_irq, xlnx_s2mm_irq_handler, 0, "xlnx_formatter_pcm_s2mm_irq", - dev); + aud_drv_data); if (ret) { dev_err(dev, "xlnx audio s2mm irq request failed\n"); goto clk_err; From ae7fd6ff4c6713270d2efe6db87a4a58ccb7cc61 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Tue, 11 Aug 2026 14:18:16 +0100 Subject: [PATCH 476/612] regmap: sdw-mbq: Fix swap of timeout and retry times When polling Function Busy using read_poll_timeout() the total timeout and retry delay arguments are swapped. This leads to only a single retry being processed, it seems the existing users typically do succeed before the first retry. Swap the arguments over to ensure the correct polling time. Reported-by: Ville Saarinen Link: https://lore.kernel.org/linux-sound/ansTPGgVNoDJlA5r@opensource.cirrus.com/T/#m680731a2f307f1f5176b27ed5aa560ddc94e5d62 Fixes: 5bc493bf0c37 ("regmap: sdw-mbq: Add support for SDCA deferred controls") Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20260811131816.332082-1-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown --- drivers/base/regmap/regmap-sdw-mbq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/base/regmap/regmap-sdw-mbq.c b/drivers/base/regmap/regmap-sdw-mbq.c index 2585933d4946b7..6d7c760cbef2b0 100644 --- a/drivers/base/regmap/regmap-sdw-mbq.c +++ b/drivers/base/regmap/regmap-sdw-mbq.c @@ -59,7 +59,7 @@ static int regmap_sdw_mbq_poll_busy(struct sdw_slave *slave, unsigned int reg, if (ctx->readable_reg(dev, reg)) { ret = read_poll_timeout(sdw_read_no_pm, val, val < 0 || !(val & SDCA_CTL_ENTITY_0_FUNCTION_BUSY), - ctx->cfg.timeout_us, ctx->cfg.retry_us, + ctx->cfg.retry_us, ctx->cfg.timeout_us, false, slave, reg); if (val < 0) return val; From 079e27f52b929b554b90514b081ea40b3d632a25 Mon Sep 17 00:00:00 2001 From: Balamurugan C Date: Tue, 11 Aug 2026 08:53:54 +0800 Subject: [PATCH 477/612] ASoC: Intel: NVL: Add entry for HDMI-In capture support to non-I2S codec boards. Adding HDMI-In capture support for the NVL products which doesn't have onboard I2S codec. But need to support HDMI-In capture via I2S and audio playback through HDMI/DP monitor. Signed-off-by: Balamurugan C Signed-off-by: Bard Liao Link: https://patch.msgid.link/20260811005354.2884137-1-yung-chuan.liao@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/sof_ssp_amp.c | 6 ++++++ sound/soc/intel/common/soc-acpi-intel-nvl-match.c | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/sound/soc/intel/boards/sof_ssp_amp.c b/sound/soc/intel/boards/sof_ssp_amp.c index 729c0cd7c19c25..b80d24966cbd57 100644 --- a/sound/soc/intel/boards/sof_ssp_amp.c +++ b/sound/soc/intel/boards/sof_ssp_amp.c @@ -222,6 +222,12 @@ static const struct platform_device_id board_ids[] = { /* SSP 0 and SSP 2 are used for HDMI IN */ SOF_HDMI_PLAYBACK_PRESENT), }, + { + .name = "nvl_lt6911_hdmi_ssp", + .driver_data = (kernel_ulong_t)(SOF_SSP_MASK_HDMI_CAPTURE(0x5) | + /* SSP 0 and SSP 2 are used for HDMI IN */ + SOF_HDMI_PLAYBACK_PRESENT), + }, { } }; MODULE_DEVICE_TABLE(platform, board_ids); diff --git a/sound/soc/intel/common/soc-acpi-intel-nvl-match.c b/sound/soc/intel/common/soc-acpi-intel-nvl-match.c index 4a6208f821eb09..8f6e987c791e1b 100644 --- a/sound/soc/intel/common/soc-acpi-intel-nvl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-nvl-match.c @@ -56,6 +56,12 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_nvl_machines[] = { .tplg_quirk_mask = SND_SOC_ACPI_TPLG_INTEL_SSP_NUMBER | SND_SOC_ACPI_TPLG_INTEL_SSP_MSB, }, + /* place amp/hdmi-in only boards in the end of table */ + { + .id = "INTC10B0", + .drv_name = "nvl_lt6911_hdmi_ssp", + .sof_tplg_filename = "sof-nvl-hdmi-ssp02.tplg", + }, {}, }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_nvl_machines); From 59e1592d3c270ff4642d5d6dc55c545306eb0693 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Tue, 11 Aug 2026 22:18:35 +0900 Subject: [PATCH 478/612] ALSA: seq: Don't leak the extension cell pointer in the bounce payload The bounce_error_event() embeds the failed event in the bounce payload by pointing data.ext.ptr at it. When that event is a queued variable-length event, its own data.ext.ptr holds the address of its first extension cell, put there by snd_seq_event_dup(). The payload goes out verbatim through snd_seq_expand_var_event(), so the address reaches userspace. That is the same address commit 705dd6dcbc0e ("ALSA: seq: Clear variable event pointer on read") removed from the event header. The read path still clears it there, just above the call that expands the payload. Embed a sanitised copy instead, treated exactly as snd_seq_read() treats the header. A stack copy is enough because delivery is synchronous and snd_seq_event_dup() copies before returning. An unprivileged client reaches this by setting SNDRV_SEQ_FILTER_BOUNCE, queueing a variable-length event to a port that does not exist and reading the bounce back. Eight bytes on 64-bit, from its own pool. Fixes: efc86691e4d8 ("ALSA: seq: Fix kernel heap address leak in bounce_error_event()") Assisted-by: Claude:claude-opus-5 Signed-off-by: HyeongJun An Link: https://patch.msgid.link/20260811131835.3837024-1-sammiee5311@gmail.com Signed-off-by: Takashi Iwai --- sound/core/seq/seq_clientmgr.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index d4cac594bc8f1e..11fa7e8258193e 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -541,7 +541,7 @@ static int bounce_error_event(struct snd_seq_client *client, struct snd_seq_event *event, int err, int atomic, int hop) { - struct snd_seq_event bounce_ev; + struct snd_seq_event bounce_ev, quoted; int result; if (client == NULL || @@ -561,15 +561,19 @@ static int bounce_error_event(struct snd_seq_client *client, * For user clients, send SNDRV_SEQ_EVENT_BOUNCE with the * original event embedded as variable-length data. This * avoids exposing data.quote.event (a kernel pointer) to - * userspace. The variable-length path in snd_seq_event_dup() - * copies the event data from data.ext.ptr into chained cells, - * and snd_seq_expand_var_event() copies only the data content - * -- never the pointer -- to userspace. + * userspace. Sanitise the embedded copy too - a queued + * variable-length event carries the address of its own + * extension cell, and the payload goes out verbatim. */ + quoted = *event; + if (snd_seq_ev_is_variable("ed)) { + quoted.data.ext.len &= ~SNDRV_SEQ_EXT_MASK; + quoted.data.ext.ptr = NULL; + } bounce_ev.type = SNDRV_SEQ_EVENT_BOUNCE; bounce_ev.flags = SNDRV_SEQ_EVENT_LENGTH_VARIABLE; bounce_ev.data.ext.len = sizeof(struct snd_seq_event); - bounce_ev.data.ext.ptr = (char *)event; + bounce_ev.data.ext.ptr = (char *)"ed; } else { /* * For kernel clients, quote the event pointer directly. From 6ec64d757af9b75a3c64f9f7dad76bdc1efc06ca Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Tue, 11 Aug 2026 17:09:49 +0700 Subject: [PATCH 479/612] ASoC: pxa: Use devm_clk_get_optional() for extclk clock The Device Tree binding defines the extclk clock as an optional property, but the driver currently uses devm_clk_get() and manually handles the absence of the clock. Use devm_clk_get_optional() to match the binding and simplify the optional clock handling. This also propagates errors other than the absence of the optional clock, including -EPROBE_DEFER. This changes the existing behavior for errors other than -EPROBE_DEFER. RFC to discuss whether these errors should cause probe to fail rather than being treated as an unavailable optional clock. Fixes: 90eb6b59d311 ("ASoC: pxa-ssp: add support for an external clock in devicetree") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260811100949.61142-1-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/pxa/pxa-ssp.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/sound/soc/pxa/pxa-ssp.c b/sound/soc/pxa/pxa-ssp.c index f8054c1c59fae7..3a0abcb0bfbdf2 100644 --- a/sound/soc/pxa/pxa-ssp.c +++ b/sound/soc/pxa/pxa-ssp.c @@ -769,13 +769,10 @@ static int pxa_ssp_probe(struct snd_soc_dai *dai) goto err_priv; } - priv->extclk = devm_clk_get(dev, "extclk"); + priv->extclk = devm_clk_get_optional(dev, "extclk"); if (IS_ERR(priv->extclk)) { ret = PTR_ERR(priv->extclk); - if (ret == -EPROBE_DEFER) - goto err_priv; - - priv->extclk = NULL; + goto err_priv; } } else { priv->ssp = pxa_ssp_request(dai->id + 1, "SoC audio"); From 0286324da660875dc504fd65b3be87e9c8b9a547 Mon Sep 17 00:00:00 2001 From: LiangCheng Wang Date: Wed, 5 Aug 2026 15:55:43 +0800 Subject: [PATCH 480/612] ASoC: fsl-asoc-card: defer probe when the CPU DAI device is not ready fsl_asoc_card_probe() hard-fails with -EINVAL when the CPU DAI (SAI) platform device is not found. Like the codec, the CPU DAI may just be probed later than the machine driver; the order is not guaranteed and varies across kernel versions, so a permanent -EINVAL leaves the card unregistered with no analog playback or capture. Defer probe instead, mirroring commit e396dec46c56 ("ASoC: fsl-asoc-card: Defer probe when fail to find codec device"). Tested on i.MX8MP with an ALC5672 on SAI3: the card that failed to register on v6.18 now comes up during boot. Fixes: 708b4351f08c ("ASoC: fsl: Add Freescale Generic ASoC Sound Card with ASRC support") Signed-off-by: LiangCheng Wang Link: https://patch.msgid.link/20260805-fsl-asoc-defer-cpu-dai-v1-1-43f7f538e384@gmail.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl-asoc-card.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/fsl/fsl-asoc-card.c b/sound/soc/fsl/fsl-asoc-card.c index 709543308fe9e9..eff46666c8add6 100644 --- a/sound/soc/fsl/fsl-asoc-card.c +++ b/sound/soc/fsl/fsl-asoc-card.c @@ -728,8 +728,8 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) cpu_pdev = of_find_device_by_node(cpu_np); if (!cpu_pdev) { - dev_err(&pdev->dev, "failed to find CPU DAI device\n"); - ret = -EINVAL; + ret = dev_err_probe(&pdev->dev, -EPROBE_DEFER, + "failed to find CPU DAI device\n"); goto fail; } From 4d788788e36652cc965853d9f84f11759a259fb4 Mon Sep 17 00:00:00 2001 From: Prasad Kumpatla Date: Tue, 11 Aug 2026 14:16:04 +0530 Subject: [PATCH 481/612] ASoC: dt-bindings: qcom,sm8250: Add Maili sound card Add the Maili sound card compatible to the SM8450-family sound card bindings. Signed-off-by: Prasad Kumpatla Link: https://patch.msgid.link/20260811084605.1820056-2-prasad.kumpatla@oss.qualcomm.com Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/sound/qcom,sm8250.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml b/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml index 3e7216d61a5eb0..1536fcd96d6873 100644 --- a/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml +++ b/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml @@ -26,6 +26,7 @@ properties: - qcom,eliza-sndcard - qcom,hawi-sndcard - qcom,kaanapali-sndcard + - qcom,maili-sndcard - qcom,sm8475-sndcard - qcom,sm8550-sndcard - qcom,sm8650-sndcard From 19b7493a71625bcc8f29e1b858e99c7e42790156 Mon Sep 17 00:00:00 2001 From: Prasad Kumpatla Date: Tue, 11 Aug 2026 14:16:05 +0530 Subject: [PATCH 482/612] ASoC: qcom: sc8280xp: Add Maili sound card support Add the Maili sound card compatible. Maili can reuse the Hawi sound card data. Signed-off-by: Prasad Kumpatla Link: https://patch.msgid.link/20260811084605.1820056-3-prasad.kumpatla@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/qcom/sc8280xp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/qcom/sc8280xp.c b/sound/soc/qcom/sc8280xp.c index 913ae81be42408..1322601659d481 100644 --- a/sound/soc/qcom/sc8280xp.c +++ b/sound/soc/qcom/sc8280xp.c @@ -554,6 +554,7 @@ static const struct of_device_id snd_sc8280xp_dt_match[] = { { .compatible = "qcom,eliza-sndcard", .data = &eliza_priv_data }, { .compatible = "qcom,hawi-sndcard", .data = &hawi_priv_data }, { .compatible = "qcom,kaanapali-sndcard", .data = &kaanapali_priv_data }, + { .compatible = "qcom,maili-sndcard", .data = &hawi_priv_data }, { .compatible = "qcom,qcm6490-idp-sndcard", .data = &qcm6490_priv_data }, { .compatible = "qcom,qcs615-sndcard", .data = &qcs615_priv_data }, { .compatible = "qcom,qcs6490-rb3gen2-sndcard", .data = &qcs6490_priv_data }, From 8393e0bf593be4fe51ee8d8e3aff53d076e02baf Mon Sep 17 00:00:00 2001 From: Ville Saarinen Date: Sun, 9 Aug 2026 10:15:04 +0000 Subject: [PATCH 483/612] ASoC: amd: acp-config: force SoundWire probe on HP OmniBook X Flip 14 The BIOS on the HP OmniBook X Flip 14-kc0xxx (board 8EA1, Strix Point, ACP 7.2) reports acp-audio-config-flag = FLAG_AMD_LEGACY_ONLY_DMIC. That binds the legacy ACP driver and registers a PDM-only card, so the SoundWire links are never scanned: the two TAS2783 speaker amplifiers on link 0 and the RT712-VB jack codec on link 1 do not enumerate and the machine ends up with no usable playback path at all. Add a DMI entry for the board so the flag is overridden to 0 and snd_pci_ps probes instead. Developed with AI assistance. The assistant read the board's ACP configuration flag out of the running system, identified the flag override as the fix and drafted the DMI entry. All hardware measurements quoted above were run by the submitter on the affected machine. The submitter has reviewed the change, understands it and takes responsibility for it. Assisted-by: Claude:claude-opus-5 Signed-off-by: Ville Saarinen Link: https://patch.msgid.link/20260809101439.4798-2-wiza@saarinenkoti.fi 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 88e4230d66c3dd..4ce8573d4ca097 100644 --- a/sound/soc/amd/acp-config.c +++ b/sound/soc/amd/acp-config.c @@ -63,6 +63,13 @@ static const struct dmi_system_id acp70_acpi_flag_override_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Vivobook 18 M1807GA"), }, }, + { + /* HP OmniBook X Flip 14-kc0xxx (Strix Point, ACP 7.2) */ + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "HP"), + DMI_MATCH(DMI_BOARD_NAME, "8EA1"), + }, + }, {} }; From b48466e4be18822d58a62ae619ffb062220ad572 Mon Sep 17 00:00:00 2001 From: Ville Saarinen Date: Sun, 9 Aug 2026 10:15:09 +0000 Subject: [PATCH 484/612] ASoC: amd: acp70: add HP OmniBook X Flip 14 SoundWire machine Describe the SoundWire topology of the HP OmniBook X Flip 14-kc0xxx (board 8EA1): two TAS2783 smart amplifiers aggregated on link 0 (unique IDs 0xC and 0x9, group_position 0 and 1, name prefixes tas2783-1 and tas2783-2) driving the left and right internal speakers, and an RT712-VB on link 1 providing the headset jack on AIF1 and the internal DMIC array on AIF3. The RT712 amplifier path (AIF2) is left unused because the speakers are driven by the external TAS2783 pair, so the existing jack_dmic_endpoints array describes it exactly. The entry is gated on snd_soc_acpi_amd_sdca_is_device_rt712_vb() so it does not capture a board carrying a different link 1 codec. Developed with AI assistance. The assistant derived the link topology from the enumerated peripherals and drafted the table entry. The order of the two amplifier entries, which is what assigns the physical sides, was corrected after a listening test by the submitter. All hardware measurements quoted above were run by the submitter on the affected machine. The submitter has reviewed the change, understands it and takes responsibility for it. Assisted-by: Claude:claude-opus-5 Signed-off-by: Ville Saarinen Link: https://patch.msgid.link/20260809101439.4798-3-wiza@saarinenkoti.fi Signed-off-by: Mark Brown --- sound/soc/amd/acp/amd-acp70-acpi-match.c | 56 ++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/sound/soc/amd/acp/amd-acp70-acpi-match.c b/sound/soc/amd/acp/amd-acp70-acpi-match.c index ccd01152c87d0f..815e088b437d7f 100644 --- a/sound/soc/amd/acp/amd-acp70-acpi-match.c +++ b/sound/soc/amd/acp/amd-acp70-acpi-match.c @@ -659,6 +659,56 @@ static const struct snd_soc_acpi_link_adr acp70_rt721_l1u0_tas2783x2_l1u8b[] = { {} }; +static const struct snd_soc_acpi_adr_device rt712_vb_l1u0_adr[] = { + { + .adr = 0x000130025D071201ull, + /* + * On this platform speakers are provided by two TAS2783 amps, + * so the AIF2 amp path is left unused: jack + DMIC only. + */ + .num_endpoints = ARRAY_SIZE(jack_dmic_endpoints), + .endpoints = jack_dmic_endpoints, + .name_prefix = "rt712" + } +}; + +/* + * Unique ID 0xC drives the left speaker and 0x9 the right one. The order of the + * entries matters as much as the endpoints: asoc_sdw_parse_sdw_endpoints() + * appends them to the dailink in array order and sdw_compute_slave_ports() + * hands out payload block offsets in that same order, so the first entry is the + * one that receives channel 0. + */ +static const struct snd_soc_acpi_adr_device tas2783x2_l0u9c_adr[] = { + { + .adr = 0x00003C0102000001ull, + .num_endpoints = 1, + .endpoints = &spk_l_endpoint, + .name_prefix = "tas2783-1", + }, + { + .adr = 0x0000390102000001ull, + .num_endpoints = 1, + .endpoints = &spk_r_endpoint, + .name_prefix = "tas2783-2", + }, +}; + +/* HP OmniBook X Flip 14-kc0xxx (board 8EA1) */ +static const struct snd_soc_acpi_link_adr acp70_tas2783x2_l0u9c_rt712_vb_l1u0[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(tas2783x2_l0u9c_adr), + .adr_d = tas2783x2_l0u9c_adr, + }, + { + .mask = BIT(1), + .num_adr = ARRAY_SIZE(rt712_vb_l1u0_adr), + .adr_d = rt712_vb_l1u0_adr, + }, + {} +}; + static const struct snd_soc_acpi_endpoint rt721_endpoints[] = { { /* Jack Playback/Capture Endpoint (AIF1) */ .num = 0, @@ -704,6 +754,12 @@ struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sdw_machines[] = { .links = acp70_tas2783_2, .drv_name = "amd_sdw", }, + { + .link_mask = BIT(0) | BIT(1), + .links = acp70_tas2783x2_l0u9c_rt712_vb_l1u0, + .machine_check = snd_soc_acpi_amd_sdca_is_device_rt712_vb, + .drv_name = "amd_sdw", + }, { .link_mask = BIT(0) | BIT(1), .links = acp70_rt1320_l0_rt722_l1, From b992511180e126150c6ad3580a6fd568c385f4c6 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Tue, 11 Aug 2026 11:51:40 -0700 Subject: [PATCH 485/612] ASoC: xilinx: formatter_pcm: fix stream_data leak on open error In xlnx_formatter_pcm_open(), stream_data is allocated and adata->play_stream or adata->capture_stream is assigned early. If a later step, such as snd_pcm_hw_constraint_step() or snd_pcm_hw_constraint_integer(), fails, the function returns the error immediately. ALSA does not call the close callback when open fails, so stream_data is leaked and the stream pointer is left dangling, pointing to a substream that ALSA frees. A later interrupt would then call snd_pcm_period_elapsed() on the freed substream. Free stream_data and clear the stream pointer on the error paths. Fixes: 6f6c3c36f091 ("ASoC: xlnx: add pcm formatter platform driver") Assisted-by: opencode:deepseek-v4-flash-free Signed-off-by: Rosen Penev Reviewed-by: Michal Simek Link: https://patch.msgid.link/20260811185140.27149-1-rosenp@gmail.com Signed-off-by: Mark Brown --- sound/soc/xilinx/xlnx_formatter_pcm.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/sound/soc/xilinx/xlnx_formatter_pcm.c b/sound/soc/xilinx/xlnx_formatter_pcm.c index 55b5b473d85f9e..fce9c0a02ea348 100644 --- a/sound/soc/xilinx/xlnx_formatter_pcm.c +++ b/sound/soc/xilinx/xlnx_formatter_pcm.c @@ -384,7 +384,7 @@ static int xlnx_formatter_pcm_open(struct snd_soc_component *component, if (err) { dev_err(component->dev, "Unable to set constraint on period bytes\n"); - return err; + goto error; } /* Resize the buffer bytes as divisible by 64 */ @@ -394,7 +394,7 @@ static int xlnx_formatter_pcm_open(struct snd_soc_component *component, if (err) { dev_err(component->dev, "Unable to set constraint on buffer bytes\n"); - return err; + goto error; } /* Set periods as integer multiple */ @@ -403,7 +403,7 @@ static int xlnx_formatter_pcm_open(struct snd_soc_component *component, if (err < 0) { dev_err(component->dev, "Unable to set constraint on periods to be integer\n"); - return err; + goto error; } /* enable DMA IOC irq */ @@ -412,6 +412,14 @@ static int xlnx_formatter_pcm_open(struct snd_soc_component *component, writel(val, stream_data->mmio + XLNX_AUD_CTRL); return 0; + +error: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + adata->play_stream = NULL; + else + adata->capture_stream = NULL; + kfree(stream_data); + return err; } static int xlnx_formatter_pcm_close(struct snd_soc_component *component, From 21e958c4fd92d63139039430c246613505480689 Mon Sep 17 00:00:00 2001 From: Trevor Vorhees Date: Tue, 11 Aug 2026 20:44:10 -0400 Subject: [PATCH 486/612] ALSA: usb-audio: Fix sample rates for PreSonus AudioBox USB The fixed audio formats for the PreSonus AudioBox USB specify a discrete rate mask but leave nr_rates at zero and rate_table unset. find_format() therefore rejects every requested rate, preventing the playback and capture streams from being opened. Add the advertised 44100 and 48000 Hz rates to both streams and report their 24 significant bits. Fixes: 34fe4a9df247 ("ALSA: usb-audio: Add quirk for PreSonus AudioBox USB") Cc: stable@vger.kernel.org Signed-off-by: Trevor Vorhees Link: https://patch.msgid.link/20260811-audiobox-usb-fix-v1-1-13c8b7f071ea@proton.me Signed-off-by: Takashi Iwai --- sound/usb/quirks-table.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index 938908671d9346..0a3d39b8385bec 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -2692,6 +2692,7 @@ YAMAHA_DEVICE(0x7010, "UB99"), { QUIRK_DATA_AUDIOFORMAT(2) { .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .fmt_bits = 24, .channels = 2, .iface = 2, .altsetting = 1, @@ -2703,11 +2704,16 @@ YAMAHA_DEVICE(0x7010, "UB99"), SNDRV_PCM_RATE_48000, .rate_min = 44100, .rate_max = 48000, + .nr_rates = 2, + .rate_table = (unsigned int[]) { + 44100, 48000 + }, } }, { QUIRK_DATA_AUDIOFORMAT(3) { .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .fmt_bits = 24, .channels = 2, .iface = 3, .altsetting = 1, @@ -2719,6 +2725,10 @@ YAMAHA_DEVICE(0x7010, "UB99"), SNDRV_PCM_RATE_48000, .rate_min = 44100, .rate_max = 48000, + .nr_rates = 2, + .rate_table = (unsigned int[]) { + 44100, 48000 + }, } }, QUIRK_COMPOSITE_END From 67300656f6a690c0146b2bb375f3d30eb05a7bea Mon Sep 17 00:00:00 2001 From: Bob Song Date: Wed, 12 Aug 2026 11:30:07 +0800 Subject: [PATCH 487/612] ALSA: hda: simplify match functions and remove unreachable return hda_bus_match() has an unreachable 'return 1' after an if/else that covers both branches. Remove the superfluous return and simplify the control flow by dropping the else branch. hdac_codec_match() uses a redundant if/else to return 1 or 0. Simplify to a single return statement. Signed-off-by: Bob Song Link: https://patch.msgid.link/20260812033007.633564-1-songxiebing@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/core/hda_bus_type.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/sound/hda/core/hda_bus_type.c b/sound/hda/core/hda_bus_type.c index a4afd41b6f840b..e1e986a8b5b50e 100644 --- a/sound/hda/core/hda_bus_type.c +++ b/sound/hda/core/hda_bus_type.c @@ -39,10 +39,7 @@ EXPORT_SYMBOL_GPL(hdac_get_device_id); static int hdac_codec_match(struct hdac_device *dev, const struct hdac_driver *drv) { - if (hdac_get_device_id(dev, drv)) - return 1; - else - return 0; + return !!hdac_get_device_id(dev, drv); } static int hda_bus_match(struct device *dev, const struct device_driver *drv) @@ -59,9 +56,7 @@ static int hda_bus_match(struct device *dev, const struct device_driver *drv) */ if (hdrv->match) return hdrv->match(hdev, hdrv); - else - return hdac_codec_match(hdev, hdrv); - return 1; + return hdac_codec_match(hdev, hdrv); } static int hda_uevent(const struct device *dev, struct kobj_uevent_env *env) From 3cd6cb2dc9598278235b9c76bc188cd4fbc320e5 Mon Sep 17 00:00:00 2001 From: Bob Song Date: Wed, 12 Aug 2026 11:30:19 +0800 Subject: [PATCH 488/612] ALSA: hda/ca0132: set codec->spec to NULL after freeing ca0132_free() and dbpro_free() call kfree(codec->spec) without setting codec->spec to NULL afterward, leaving a dangling pointer. Set it to NULL. Signed-off-by: Bob Song Link: https://patch.msgid.link/20260812033019.635010-1-songxiebing@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/codecs/ca0132.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/hda/codecs/ca0132.c b/sound/hda/codecs/ca0132.c index 61c9fb42b1def9..aa2635d8ab928d 100644 --- a/sound/hda/codecs/ca0132.c +++ b/sound/hda/codecs/ca0132.c @@ -9628,6 +9628,7 @@ static void ca0132_free(struct hda_codec *codec) #endif kfree(spec->spec_init_verbs); kfree(codec->spec); + codec->spec = NULL; } static void dbpro_free(struct hda_codec *codec) @@ -9638,6 +9639,7 @@ static void dbpro_free(struct hda_codec *codec) kfree(spec->spec_init_verbs); kfree(codec->spec); + codec->spec = NULL; } static void ca0132_config(struct hda_codec *codec) From 59a2cd69b44007fa59566bc52da107fc2b814cf0 Mon Sep 17 00:00:00 2001 From: Bob Song Date: Wed, 12 Aug 2026 11:30:30 +0800 Subject: [PATCH 489/612] ALSA: hda/ca0132: replace sprintf() with snprintf() Replace six sprintf() calls that write to SNDRV_CTL_ELEM_ID_NAME_MAXLEN-sized buffers with snprintf() to avoid potential buffer overflows. Signed-off-by: Bob Song Link: https://patch.msgid.link/20260812033030.635417-1-songxiebing@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/codecs/ca0132.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/sound/hda/codecs/ca0132.c b/sound/hda/codecs/ca0132.c index aa2635d8ab928d..0f8fb62f021dea 100644 --- a/sound/hda/codecs/ca0132.c +++ b/sound/hda/codecs/ca0132.c @@ -5788,7 +5788,8 @@ static int ca0132_alt_mic_boost_info(struct snd_kcontrol *kcontrol, uinfo->value.enumerated.items = MIC_BOOST_NUM_OF_STEPS; if (uinfo->value.enumerated.item >= MIC_BOOST_NUM_OF_STEPS) uinfo->value.enumerated.item = MIC_BOOST_NUM_OF_STEPS - 1; - sprintf(namestr, "%d %s", (uinfo->value.enumerated.item * 10), sfx); + snprintf(namestr, sizeof(namestr), "%d %s", + (uinfo->value.enumerated.item * 10), sfx); strscpy(uinfo->value.enumerated.name, namestr); return 0; } @@ -5840,9 +5841,9 @@ static int ae5_headphone_gain_info(struct snd_kcontrol *kcontrol, uinfo->value.enumerated.items = AE5_HEADPHONE_GAIN_MAX; if (uinfo->value.enumerated.item >= AE5_HEADPHONE_GAIN_MAX) uinfo->value.enumerated.item = AE5_HEADPHONE_GAIN_MAX - 1; - sprintf(namestr, "%s %s", - ae5_headphone_gain_presets[uinfo->value.enumerated.item].name, - sfx); + snprintf(namestr, sizeof(namestr), "%s %s", + ae5_headphone_gain_presets[uinfo->value.enumerated.item].name, + sfx); strscpy(uinfo->value.enumerated.name, namestr); return 0; } @@ -5894,8 +5895,8 @@ static int ae5_sound_filter_info(struct snd_kcontrol *kcontrol, uinfo->value.enumerated.items = AE5_SOUND_FILTER_MAX; if (uinfo->value.enumerated.item >= AE5_SOUND_FILTER_MAX) uinfo->value.enumerated.item = AE5_SOUND_FILTER_MAX - 1; - sprintf(namestr, "%s", - ae5_filter_presets[uinfo->value.enumerated.item].name); + snprintf(namestr, sizeof(namestr), "%s", + ae5_filter_presets[uinfo->value.enumerated.item].name); strscpy(uinfo->value.enumerated.name, namestr); return 0; } @@ -6632,7 +6633,7 @@ static int ca0132_alt_add_effect_slider(struct hda_codec *codec, hda_nid_t nid, struct snd_kcontrol_new knew = HDA_CODEC_VOLUME_MONO(namestr, nid, 1, 0, type); - sprintf(namestr, "FX: %s %s Volume", pfx, dirstr[dir]); + snprintf(namestr, sizeof(namestr), "FX: %s %s Volume", pfx, dirstr[dir]); knew.tlv.c = NULL; @@ -6671,9 +6672,9 @@ static int add_fx_switch(struct hda_codec *codec, hda_nid_t nid, * prefix to OutFX or InFX enable controls. */ if (ca0132_use_alt_controls(spec) && (nid <= IN_EFFECT_END_NID)) - sprintf(namestr, "FX: %s %s Switch", pfx, dirstr[dir]); + snprintf(namestr, sizeof(namestr), "FX: %s %s Switch", pfx, dirstr[dir]); else - sprintf(namestr, "%s %s Switch", pfx, dirstr[dir]); + snprintf(namestr, sizeof(namestr), "%s %s Switch", pfx, dirstr[dir]); return snd_hda_ctl_add(codec, nid, snd_ctl_new1(&knew, codec)); } From e9966d450b4612423ad2f60827c47cd7657a14f9 Mon Sep 17 00:00:00 2001 From: Hongyang Zhao Date: Wed, 12 Aug 2026 17:35:43 +0800 Subject: [PATCH 490/612] ASoC: dt-bindings: es8316: Add regulator supplies The ES8316 has separate AVDD, CPVDD, DVDD and PVDD supply inputs for its analog, charge pump, digital core and digital I/O domains. Describe all four inputs so boards can model the codec power topology. The binding also covers ES8311 and ES8323, whose supply inputs differ, so restrict these properties to the ES8316 compatible. Keep them optional for compatibility with existing descriptions. Signed-off-by: Hongyang Zhao Link: https://patch.msgid.link/20260812-es8316-regulator-next-20260722-v2-1-e7078bc9bc9c@thundersoft.com Signed-off-by: Mark Brown --- .../bindings/sound/everest,es8316.yaml | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Documentation/devicetree/bindings/sound/everest,es8316.yaml b/Documentation/devicetree/bindings/sound/everest,es8316.yaml index fe5d938ca3102a..276c73bb47907b 100644 --- a/Documentation/devicetree/bindings/sound/everest,es8316.yaml +++ b/Documentation/devicetree/bindings/sound/everest,es8316.yaml @@ -30,6 +30,17 @@ description: | allOf: - $ref: dai-common.yaml# + - if: + properties: + compatible: + contains: + const: everest,es8316 + else: + properties: + avdd-supply: false + cpvdd-supply: false + dvdd-supply: false + pvdd-supply: false properties: compatible: @@ -49,6 +60,18 @@ properties: items: - const: mclk + avdd-supply: + description: Regulator providing the analog supply, from 2.0 V to 3.6 V + + cpvdd-supply: + description: Regulator providing the charge pump supply, from 1.6 V to 2.0 V + + dvdd-supply: + description: Regulator providing the digital core supply, from 1.6 V to 3.6 V + + pvdd-supply: + description: Regulator providing the digital I/O supply, from 1.6 V to 3.6 V + interrupts: maxItems: 1 description: Headphone detect interrupt @@ -77,6 +100,10 @@ examples: reg = <0x11>; clocks = <&clks 10>; clock-names = "mclk"; + avdd-supply = <®_3p3v>; + cpvdd-supply = <®_1p8v>; + dvdd-supply = <®_1p8v>; + pvdd-supply = <®_1p8v>; #sound-dai-cells = <0>; }; }; From c60279912ef005fe3ebaf2c139c19a14ccb42b5e Mon Sep 17 00:00:00 2001 From: Hongyang Zhao Date: Wed, 12 Aug 2026 17:35:44 +0800 Subject: [PATCH 491/612] ASoC: codecs: es8316: Add regulator support ES8316 has separate AVDD, CPVDD, DVDD and PVDD supply inputs. Request and enable the supplies during I2C probe, before initializing the regmap. Keep them enabled for the lifetime of the I2C device so the regmap cannot access an unpowered device and its cache remains synchronized if the ASoC component is unbound and rebound. Signed-off-by: Hongyang Zhao Link: https://patch.msgid.link/20260812-es8316-regulator-next-20260722-v2-2-e7078bc9bc9c@thundersoft.com Signed-off-by: Mark Brown --- sound/soc/codecs/es8316.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/sound/soc/codecs/es8316.c b/sound/soc/codecs/es8316.c index 3abe77423f29b1..983fa0bca4194d 100644 --- a/sound/soc/codecs/es8316.c +++ b/sound/soc/codecs/es8316.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -30,6 +31,13 @@ static const unsigned int supported_mclk_lrck_ratios[] = { 256, 384, 400, 500, 512, 768, 1024 }; +static const char * const es8316_supply_names[] = { + "avdd", + "cpvdd", + "dvdd", + "pvdd", +}; + struct es8316_priv { struct mutex lock; struct clk *mclk; @@ -871,6 +879,11 @@ static int es8316_i2c_probe(struct i2c_client *i2c_client) i2c_set_clientdata(i2c_client, es8316); + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(es8316_supply_names), + es8316_supply_names); + if (ret) + return dev_err_probe(dev, ret, "unable to enable supplies\n"); + es8316->regmap = devm_regmap_init_i2c(i2c_client, &es8316_regmap); if (IS_ERR(es8316->regmap)) return PTR_ERR(es8316->regmap); From 5b08a3afd2995e5312c4c47ffea9d21b651e8e13 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Fri, 7 Aug 2026 13:14:25 +0100 Subject: [PATCH 492/612] soundwire: dmi-quirks: Disable ghost Realtek on Asus ROG Zephyrus Duo The Asus ROG Zephyrus Duo also has a Realtek device in the ACPI that doesn't exist in the physical hardware. This confuses the machine driver into attempting to create DAI links for the device. Add a quirk to remove this device. Suggested-by: Bard Liao Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20260807121425.1952003-1-ckeepax@opensource.cirrus.com Signed-off-by: Vinod Koul --- drivers/soundwire/dmi-quirks.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/soundwire/dmi-quirks.c b/drivers/soundwire/dmi-quirks.c index f738e547a1f30a..768255dd12db6d 100644 --- a/drivers/soundwire/dmi-quirks.c +++ b/drivers/soundwire/dmi-quirks.c @@ -193,6 +193,13 @@ static const struct dmi_system_id adr_remap_quirk_table[] = { }, .driver_data = (void *)ghost_realtek, }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUS"), + DMI_MATCH(DMI_BOARD_NAME, "GX651AR"), + }, + .driver_data = (void *)ghost_realtek, + }, { .matches = { DMI_MATCH(DMI_SYS_VENDOR, "ASUS"), From 191fe151eb85ff6cb24189fc88c970745497c057 Mon Sep 17 00:00:00 2001 From: Dmytro Moroziuk Date: Wed, 12 Aug 2026 15:18:56 +0300 Subject: [PATCH 493/612] ALSA: hda/conexant: Add mute LED quirk for HP ProBook 440 G5 The HP ProBook 440 G5 requires the CXT_FIXUP_MUTE_LED_GPIO quirk to properly toggle the physical mute and mic-mute LEDs via the CX8200 codec. Without this quirk, the LEDs remain permanently dark. Signed-off-by: Dmytro Moroziuk Link: https://patch.msgid.link/20260812121856.30353-1-dmorozyk1@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/conexant.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/conexant.c b/sound/hda/codecs/conexant.c index c2f9409b76ef79..c517334b56fd9e 100644 --- a/sound/hda/codecs/conexant.c +++ b/sound/hda/codecs/conexant.c @@ -1107,6 +1107,7 @@ static const struct hda_quirk cxt5066_fixups[] = { SND_PCI_QUIRK(0x103c, 0x829a, "HP 800 G3 DM", CXT_FIXUP_HP_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x103c, 0x82b4, "HP ProDesk 600 G3", CXT_FIXUP_HP_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x103c, 0x836e, "HP ProBook 455 G5", CXT_FIXUP_MUTE_LED_GPIO), + SND_PCI_QUIRK(0x103c, 0x837b, "HP ProBook 440 G5", CXT_FIXUP_MUTE_LED_GPIO), SND_PCI_QUIRK(0x103c, 0x837f, "HP ProBook 470 G5", CXT_FIXUP_MUTE_LED_GPIO), SND_PCI_QUIRK(0x103c, 0x83b2, "HP EliteBook 840 G5", CXT_FIXUP_HP_DOCK), SND_PCI_QUIRK(0x103c, 0x83b3, "HP EliteBook 830 G5", CXT_FIXUP_HP_DOCK), From 0414dd7b6f8b8619c8f1595ba04edb43d405581e Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Wed, 12 Aug 2026 23:15:06 +0900 Subject: [PATCH 494/612] ALSA: seq: Drop the dead struct snd_seq_event_bounce The struct describes a bounce payload of an error code followed by the original event and its external data. No kernel has ever sent that. Before commit efc86691e4d8 ("ALSA: seq: Fix kernel heap address leak in bounce_error_event()") the kernel emitted no SNDRV_SEQ_EVENT_BOUNCE at all, and since then it sends the event record alone. Nothing has ever read it either. Its only accessor, snd_seq_event_bounce_ext_data(), has had no caller for the whole git history, and it did not even compile until commit c7e0b5bf9fff ("[ALSA] Remove xxx_t typedefs: Sequencer") incidentally repaired the type name it referred to, three years after the git import. Drop the accessor along with the struct. This removes a definition from a UAPI header. Since no kernel ever produced the layout, nothing can have parsed it, but a program that merely names the type will need to stop. Suggested-by: Takashi Iwai Assisted-by: Claude:claude-opus-5 Signed-off-by: HyeongJun An Link: https://patch.msgid.link/20260812141506.4016387-1-sammiee5311@gmail.com Signed-off-by: Takashi Iwai --- include/sound/asequencer.h | 3 --- include/uapi/sound/asequencer.h | 10 ---------- sound/core/seq/seq_clientmgr.c | 5 ++--- 3 files changed, 2 insertions(+), 16 deletions(-) diff --git a/include/sound/asequencer.h b/include/sound/asequencer.h index ddbb6bf801bbda..efad366736a497 100644 --- a/include/sound/asequencer.h +++ b/include/sound/asequencer.h @@ -11,9 +11,6 @@ #include #include -/* helper macro */ -#define snd_seq_event_bounce_ext_data(ev) ((void*)((char *)(ev)->data.ext.ptr + sizeof(struct snd_seq_event_bounce))) - /* * type check macros */ diff --git a/include/uapi/sound/asequencer.h b/include/uapi/sound/asequencer.h index a5c41f771e059b..3deba3965ca5bc 100644 --- a/include/uapi/sound/asequencer.h +++ b/include/uapi/sound/asequencer.h @@ -308,16 +308,6 @@ struct snd_seq_ump_event { }; }; -/* - * bounce event - stored as variable size data - */ -struct snd_seq_event_bounce { - int err; - struct snd_seq_event event; - /* external data follows here. */ -}; - - /* system information */ struct snd_seq_system_info { int queues; /* maximum queues count */ diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 11fa7e8258193e..5b86e75c2658a8 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -530,9 +530,8 @@ static struct snd_seq_client *get_event_dest_client(struct snd_seq_event *event) * Return the error event. * * If the receiver client is a user client, the original event is - * encapsulated in SNDRV_SEQ_EVENT_BOUNCE as variable length event. If - * the original event is also variable length, the external data is - * copied after the event record. + * encapsulated in SNDRV_SEQ_EVENT_BOUNCE as variable length event. The + * external data of a variable length event is not copied along. * If the receiver client is a kernel client, the original event is * quoted in SNDRV_SEQ_EVENT_KERNEL_ERROR, since this requires no extra * kmalloc. From 3d7fb01b36aa85b2fc912b51305fbe011d1c3290 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Mon, 10 Aug 2026 21:24:24 -0700 Subject: [PATCH 495/612] ASoC: mediatek: mt8365: use devm_platform_ioremap_resource helpers Simplify the probe function by using devm_platform_ioremap_resource() for the base address and devm_platform_get_and_ioremap_resource() for the SRAM, dropping the manual platform_get_resource() calls. Assisted-by: opencode:deepseek-v4-flash-free Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260811042424.66882-1-rosenp@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8365/mt8365-afe-pcm.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c b/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c index 5966ca18c7c979..e9497246f37c05 100644 --- a/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c +++ b/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c @@ -2120,19 +2120,15 @@ static int mt8365_afe_pcm_dev_probe(struct platform_device *pdev) spin_lock_init(&afe_priv->afe_ctrl_lock); mutex_init(&afe_priv->afe_clk_mutex); - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - afe->base_addr = devm_ioremap_resource(&pdev->dev, res); + afe->base_addr = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(afe->base_addr)) return PTR_ERR(afe->base_addr); - res = platform_get_resource(pdev, IORESOURCE_MEM, 1); - if (res) { - afe_priv->afe_sram_vir_addr = - devm_ioremap_resource(&pdev->dev, res); - if (!IS_ERR(afe_priv->afe_sram_vir_addr)) { - afe_priv->afe_sram_phy_addr = res->start; - afe_priv->afe_sram_size = resource_size(res); - } + afe_priv->afe_sram_vir_addr = + devm_platform_get_and_ioremap_resource(pdev, 1, &res); + if (!IS_ERR(afe_priv->afe_sram_vir_addr)) { + afe_priv->afe_sram_phy_addr = res->start; + afe_priv->afe_sram_size = resource_size(res); } /* initial audio related clock */ From 4fc945d8fdfcbe8d484a7ca840ea891b56145eda Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Wed, 12 Aug 2026 09:44:37 +0200 Subject: [PATCH 496/612] ASoC: samsung: i2s: drop secondary DAI for i2sv7 hardware variant Commit 9167f260477b ("ASoC: soc-generic-dmaengine: Handle DMA channel request failures correctly") started reporting DMA channel request failures during probe instead of silently ignoring them. This exposed a bug in the Samsung I2S driver: it always registered a second DAI and its associated "tx-sec" DMA channel, even for hardware variants that don't actually support it, such as i2sv7 used on Exynos5433. As a result, sound card probing on Exynos5433-based boards started failing, whereas previously it worked only because the channel request failure was ignored. Drop the QUIRK_SEC_DAI flag from i2sv7, since this variant does not have a secondary DAI and register "Secondary Playback" DAPM route only for variants with such interface. Signed-off-by: Marek Szyprowski Link: https://patch.msgid.link/20260812074438.3225001-1-m.szyprowski@samsung.com Signed-off-by: Mark Brown --- sound/soc/samsung/i2s.c | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c index 81d5dd36a24658..3e76cb81462f02 100644 --- a/sound/soc/samsung/i2s.c +++ b/sound/soc/samsung/i2s.c @@ -1116,15 +1116,31 @@ static const struct snd_soc_dapm_widget samsung_i2s_widgets[] = { static const struct snd_soc_dapm_route samsung_i2s_dapm_routes[] = { { "Playback Mixer", NULL, "Primary Playback" }, - { "Playback Mixer", NULL, "Secondary Playback" }, - { "Mixer DAI TX", NULL, "Playback Mixer" }, { "Primary Capture", NULL, "Mixer DAI RX" }, }; +static const struct snd_soc_dapm_route samsung_i2s_dapm_routes_sec_play[] = { + { "Playback Mixer", NULL, "Secondary Playback" }, +}; + +static int samsung_i2s_component_probe(struct snd_soc_component *component) +{ + struct samsung_i2s_priv *priv = snd_soc_component_get_drvdata(component); + + if (priv->quirks & QUIRK_SEC_DAI) + snd_soc_dapm_add_routes(snd_soc_component_to_dapm(component), + samsung_i2s_dapm_routes_sec_play, + ARRAY_SIZE(samsung_i2s_dapm_routes_sec_play)); + + return 0; +} + static const struct snd_soc_component_driver samsung_i2s_component = { .name = "samsung-i2s", + .probe = samsung_i2s_component_probe, + .dapm_widgets = samsung_i2s_widgets, .num_dapm_widgets = ARRAY_SIZE(samsung_i2s_widgets), @@ -1650,8 +1666,7 @@ static const struct samsung_i2s_dai_data i2sv6_dai_type __maybe_unused = { }; static const struct samsung_i2s_dai_data i2sv7_dai_type __maybe_unused = { - .quirks = QUIRK_PRI_6CHAN | QUIRK_SEC_DAI | QUIRK_NEED_RSTCLR | - QUIRK_SUPPORTS_TDM, + .quirks = QUIRK_PRI_6CHAN | QUIRK_NEED_RSTCLR | QUIRK_SUPPORTS_TDM, .pcm_rates = SNDRV_PCM_RATE_8000_192000, .i2s_variant_regs = &i2sv7_regs, }; From 00268f9452d2a0d660aa9c1bb0ca07a994af6a4f Mon Sep 17 00:00:00 2001 From: Andrey Golovko Date: Tue, 11 Aug 2026 22:14:35 +0300 Subject: [PATCH 497/612] regmap: sdw-mbq: don't call an unset readable_reg callback regmap_sdw_mbq_poll_busy() decides whether to poll the Function Busy bit by calling ctx->readable_reg(), which is a straight copy of config->readable_reg. That callback is optional: regmap_readable() treats a NULL ->readable_reg as "every register is readable", and drivers rely on that. es9356 and tac5xx2-sdw both build an MBQ regmap without one. Since commit ca1b11b36d82 ("regmap: sdw-mbq: Allow defers on undeferrable controls") the poll runs on every -ENODATA, not only for Controls the driver marked deferrable, so any of those devices answering COMMAND_IGNORED takes the kernel through a NULL function pointer. Treat a missing callback the way the rest of regmap does and poll. Fixes: 5bc493bf0c37 ("regmap: sdw-mbq: Add support for SDCA deferred controls") Signed-off-by: Andrey Golovko Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260811184500.5312-1-andrey.golovko@gmail.com Signed-off-by: Mark Brown --- drivers/base/regmap/regmap-sdw-mbq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/base/regmap/regmap-sdw-mbq.c b/drivers/base/regmap/regmap-sdw-mbq.c index 6d7c760cbef2b0..b0312f57c9800e 100644 --- a/drivers/base/regmap/regmap-sdw-mbq.c +++ b/drivers/base/regmap/regmap-sdw-mbq.c @@ -56,7 +56,7 @@ static int regmap_sdw_mbq_poll_busy(struct sdw_slave *slave, unsigned int reg, reg = SDW_SDCA_CTL(SDW_SDCA_CTL_FUNC(reg), 0, SDCA_CTL_ENTITY_0_FUNCTION_STATUS, 0); - if (ctx->readable_reg(dev, reg)) { + if (!ctx->readable_reg || ctx->readable_reg(dev, reg)) { ret = read_poll_timeout(sdw_read_no_pm, val, val < 0 || !(val & SDCA_CTL_ENTITY_0_FUNCTION_BUSY), ctx->cfg.retry_us, ctx->cfg.timeout_us, From 79a883d53960fb2eaf02f72a37182078b4fc938e Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 12 Aug 2026 15:43:11 +0700 Subject: [PATCH 498/612] ASoC: pxa: Drop redundant probe error messages devm_platform_ioremap_resource() does not report the error itself, but the error is already reported deeper in the call chain, so the dev_err() calls are redundant and can be removed. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260812084311.29188-1-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/pxa/pxa2xx-ac97-lib.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sound/soc/pxa/pxa2xx-ac97-lib.c b/sound/soc/pxa/pxa2xx-ac97-lib.c index d9c3935636da5f..d7f720431afb79 100644 --- a/sound/soc/pxa/pxa2xx-ac97-lib.c +++ b/sound/soc/pxa/pxa2xx-ac97-lib.c @@ -316,10 +316,8 @@ int pxa2xx_ac97_hw_probe(struct platform_device *dev) int irq; ac97_reg_base = devm_platform_ioremap_resource(dev, 0); - if (IS_ERR(ac97_reg_base)) { - dev_err(&dev->dev, "Missing MMIO resource\n"); + if (IS_ERR(ac97_reg_base)) return PTR_ERR(ac97_reg_base); - } if (cpu_is_pxa27x()) { /* Assert reset using GPIOD_OUT_HIGH, because reset is GPIO_ACTIVE_LOW */ From b215caca714ed1c3b41c4dc6a055454e28e9e6a1 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 12 Aug 2026 17:14:16 +0700 Subject: [PATCH 499/612] ASoC: mxs-saif: Use dev_err_probe() for error handling Replace dev_err() with dev_err_probe() to prevent log spam when probe returns -EPROBE_DEFER. Signed-off-by: bui duc phuc Reviewed-by: Frank Li Link: https://patch.msgid.link/20260812101418.37966-2-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mxs/mxs-saif.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/sound/soc/mxs/mxs-saif.c b/sound/soc/mxs/mxs-saif.c index a01a680ad4d71f..b877c978a04c62 100644 --- a/sound/soc/mxs/mxs-saif.c +++ b/sound/soc/mxs/mxs-saif.c @@ -826,12 +826,9 @@ static int mxs_saif_probe(struct platform_device *pdev) mxs_saif[saif->id] = saif; saif->clk = devm_clk_get(&pdev->dev, NULL); - if (IS_ERR(saif->clk)) { - ret = PTR_ERR(saif->clk); - dev_err(&pdev->dev, "Cannot get the clock: %d\n", - ret); - return ret; - } + if (IS_ERR(saif->clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(saif->clk), + "Cannot get the clock\n"); saif->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(saif->base)) From f0701e5fc299e4ff2cb80c1f00bf2f23b94d6b8a Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 12 Aug 2026 17:14:17 +0700 Subject: [PATCH 500/612] ASoC: mxs-saif: Drop redundant probe error messages The functions called here don't log the error themselves, but the error is already reported deeper in the call chain, so the dev_err() calls are redundant and can be removed. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260812101418.37966-3-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mxs/mxs-saif.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/sound/soc/mxs/mxs-saif.c b/sound/soc/mxs/mxs-saif.c index b877c978a04c62..a77cd516a1bfd2 100644 --- a/sound/soc/mxs/mxs-saif.c +++ b/sound/soc/mxs/mxs-saif.c @@ -841,10 +841,8 @@ static int mxs_saif_probe(struct platform_device *pdev) saif->dev = &pdev->dev; ret = devm_request_irq(&pdev->dev, irq, mxs_saif_irq, 0, dev_name(&pdev->dev), saif); - if (ret) { - dev_err(&pdev->dev, "failed to request irq\n"); + if (ret) return ret; - } platform_set_drvdata(pdev, saif); @@ -857,16 +855,12 @@ static int mxs_saif_probe(struct platform_device *pdev) ret = devm_snd_soc_register_component(&pdev->dev, &mxs_saif_component, &mxs_saif_dai, 1); - if (ret) { - dev_err(&pdev->dev, "register DAI failed\n"); + if (ret) return ret; - } ret = mxs_pcm_platform_register(&pdev->dev); - if (ret) { - dev_err(&pdev->dev, "register PCM failed: %d\n", ret); + if (ret) return ret; - } return 0; } From e84c06775d4a3508b8068bc84986b4a29e27b1d3 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 12 Aug 2026 17:14:18 +0700 Subject: [PATCH 501/612] ASoC: mxs-sgtl5000: Drop redundant probe error messages Remove the probe error messages to avoid duplicate error reporting, since the error is already reported by the called functions. Signed-off-by: bui duc phuc Reviewed-by: Daniel Baluta Reviewed-by: Frank Li Link: https://patch.msgid.link/20260812101418.37966-4-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mxs/mxs-sgtl5000.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/sound/soc/mxs/mxs-sgtl5000.c b/sound/soc/mxs/mxs-sgtl5000.c index f1c0e612313dd1..a253a48ca59c97 100644 --- a/sound/soc/mxs/mxs-sgtl5000.c +++ b/sound/soc/mxs/mxs-sgtl5000.c @@ -155,8 +155,6 @@ static int mxs_sgtl5000_probe(struct platform_device *pdev) ret = snd_soc_of_parse_audio_routing(card, "audio-routing"); if (ret) { - dev_err(&pdev->dev, "failed to parse audio-routing (%d)\n", - ret); mxs_saif_put_mclk(0); return ret; } From 0a91bb72980e4aa5f0c1be33d691e7767d7db9ed Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 5 Aug 2026 13:43:48 +0700 Subject: [PATCH 502/612] ASoC: spacemit: advertise only DMA-backed DAI streams The static DAI template initializes both playback and capture stream capabilities before dma-names is examined. As a result, snd_soc_dai_stream_valid() considers both directions valid even when the device only provides a single DMA channel. Move the playback and capture capability initialization into spacemit_i2s_init_dai(), where it is performed only for the stream directions backed by a corresponding DMA channel. This preserves the existing capabilities for devices with both "tx" and "rx" DMA channels, while preventing unsupported stream directions from being advertised. Initialize rate_min and rate_max together with the other stream capabilities to preserve the existing rate constraints. Fixes: fce217449075 ("ASoC: spacemit: add i2s support for K1 SoC") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260805064348.44283-1-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/spacemit/k1_i2s.c | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/sound/soc/spacemit/k1_i2s.c b/sound/soc/spacemit/k1_i2s.c index 2d5ea1fd5d49d3..28a7627692532d 100644 --- a/sound/soc/spacemit/k1_i2s.c +++ b/sound/soc/spacemit/k1_i2s.c @@ -354,22 +354,6 @@ static const struct snd_soc_dai_ops spacemit_i2s_dai_ops = { static struct snd_soc_dai_driver spacemit_i2s_dai = { .ops = &spacemit_i2s_dai_ops, - .playback = { - .channels_min = 1, - .channels_max = 2, - .rates = SPACEMIT_PCM_RATES, - .rate_min = SNDRV_PCM_RATE_8000, - .rate_max = SNDRV_PCM_RATE_48000, - .formats = SPACEMIT_PCM_FORMATS, - }, - .capture = { - .channels_min = 1, - .channels_max = 2, - .rates = SPACEMIT_PCM_RATES, - .rate_min = SNDRV_PCM_RATE_8000, - .rate_max = SNDRV_PCM_RATE_48000, - .formats = SPACEMIT_PCM_FORMATS, - }, .symmetric_rate = 1, }; @@ -399,6 +383,8 @@ static int spacemit_i2s_init_dai(struct spacemit_i2s_dev *i2s, dai->playback.channels_min = 1; dai->playback.channels_max = 2; dai->playback.rates = SPACEMIT_PCM_RATES; + dai->playback.rate_min = SNDRV_PCM_RATE_8000; + dai->playback.rate_max = SNDRV_PCM_RATE_48000; dai->playback.formats = SPACEMIT_PCM_FORMATS; i2s->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; @@ -411,6 +397,8 @@ static int spacemit_i2s_init_dai(struct spacemit_i2s_dev *i2s, dai->capture.channels_min = 1; dai->capture.channels_max = 2; dai->capture.rates = SPACEMIT_PCM_RATES; + dai->capture.rate_min = SNDRV_PCM_RATE_8000; + dai->capture.rate_max = SNDRV_PCM_RATE_48000; dai->capture.formats = SPACEMIT_PCM_FORMATS; i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; From 36aa66de481d29edd63cbad9b5c4dc18c340fdf6 Mon Sep 17 00:00:00 2001 From: Xu Rao Date: Thu, 13 Aug 2026 14:55:24 +0800 Subject: [PATCH 503/612] ALSA: hda/ext: preserve PPLCCTL bits when clearing reset snd_hdac_ext_stream_reset() polls PPLCCTL for STRST by masking the register value with AZX_PPLCCTL_STRST: val = readl(...) & AZX_PPLCCTL_STRST; The same masked value is then used when clearing STRST. Since val contains no bits other than STRST, clearing STRST from it always produces zero. The subsequent writel() therefore writes zero to the entire PPLCCTL register instead of clearing only the reset bit. PPLCCTL contains other stream control fields, including the stream tag in AZX_PPLCCTL_STRM_MASK. Those fields must not be modified as a side effect of clearing stream reset. Use snd_hdac_updatel() to clear STRST, matching the existing set-reset path and preserving all unrelated PPLCCTL bits. Fixes: df203a4e46f4 ("ALSA: hdac_ext: add extended stream capabilities") Cc: stable@vger.kernel.org Signed-off-by: Xu Rao Link: https://patch.msgid.link/43BB7930B0F07C09+20260813065524.1955696-1-raoxu@uniontech.com Signed-off-by: Takashi Iwai --- sound/hda/core/ext/stream.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/hda/core/ext/stream.c b/sound/hda/core/ext/stream.c index 4c7506d49f55a6..517bd151fcc38b 100644 --- a/sound/hda/core/ext/stream.c +++ b/sound/hda/core/ext/stream.c @@ -210,8 +210,8 @@ void snd_hdac_ext_stream_reset(struct hdac_ext_stream *hext_stream) break; udelay(3); } while (--timeout); - val &= ~AZX_PPLCCTL_STRST; - writel(val, hext_stream->pplc_addr + AZX_REG_PPLCCTL); + snd_hdac_updatel(hext_stream->pplc_addr, AZX_REG_PPLCCTL, + AZX_PPLCCTL_STRST, 0); udelay(3); timeout = 50; From bfdadd1656b6a21d30738b63909494e98ad7e3e7 Mon Sep 17 00:00:00 2001 From: Bramwel Barack Date: Wed, 12 Aug 2026 22:28:32 +0300 Subject: [PATCH 504/612] ALSA: hda/realtek: Add mute LED quirk for HP 250 G8 (0x85f3) The HP 250 G8 Laptop PC (subsystem 103c:85f3) using the Realtek ALC236 codec requires a specific quirk to enable the mute button LED. Currently, the audio mutes in software, but the physical indicator light remains unlit. Adding a quirk entry to the alc236_fixup_tbl with the ALC236_FIXUP_HP_MUTE_LED_COEFBIT2 fixup correctly maps the LED to the mute state via COEF index 0x07. Signed-off-by: Bramwel Barack Link: https://patch.msgid.link/20260812192832.69240-1-bramwelbarack89@gmail.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 87d59a9dc55fee..be7287ea24eccd 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7259,6 +7259,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x85c6, "HP Pavilion x360 Convertible 14-dy1xxx", ALC295_FIXUP_HP_MUTE_LED_COEFBIT11), SND_PCI_QUIRK(0x103c, 0x85de, "HP Envy x360 13-ar0xxx", ALC285_FIXUP_HP_ENVY_X360), SND_PCI_QUIRK(0x103c, 0x85f0, "HP Laptop 15-dw0xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x85f3, "HP 250 G8 Notebook PC", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), SND_PCI_QUIRK(0x103c, 0x8603, "HP Omen 17-cb0xxx", ALC285_FIXUP_HP_MUTE_LED), SND_PCI_QUIRK(0x103c, 0x860c, "HP ZBook 17 G6", ALC285_FIXUP_HP_GPIO_AMP_INIT), SND_PCI_QUIRK(0x103c, 0x860f, "HP ZBook 15 G6", ALC285_FIXUP_HP_GPIO_AMP_INIT), From 583e44f2828b9628535c099ebdeb8758c9b2d90f Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Wed, 12 Aug 2026 22:50:34 +0200 Subject: [PATCH 505/612] ASoC: meson: aiu: make aiu_formatter_i2s_drv static aiu_formatter_i2s_drv is indeed used only in the aiu and it is not meant to be exported. It should be static. Cc: Valerio Setti Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202608080634.hf6aJGPB-lkp@intel.com/ Fixes: 83b83024cdbf ("ASoC: meson: aiu: use aiu-formatter-i2s to format I2S output data") Signed-off-by: Jerome Brunet Reviewed-by: Valerio Setti Link: https://patch.msgid.link/20260812-aiu-formatter-static-v1-1-67936d57ba05@baylibre.com Signed-off-by: Mark Brown --- sound/soc/meson/aiu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/meson/aiu.c b/sound/soc/meson/aiu.c index 2668646e3597ee..535fbf7c163941 100644 --- a/sound/soc/meson/aiu.c +++ b/sound/soc/meson/aiu.c @@ -182,7 +182,7 @@ static const struct regmap_config aiu_regmap_cfg = { .max_register = 0x2ac, }; -const struct gx_formatter_driver aiu_formatter_i2s_drv = { +static const struct gx_formatter_driver aiu_formatter_i2s_drv = { .regmap_cfg = &aiu_regmap_cfg, .ops = &aiu_formatter_i2s_ops, }; From 91415225fbfb38c4b210e656e11a527a3760f6e6 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 12 Aug 2026 08:04:33 +0200 Subject: [PATCH 506/612] ALSA: uapi: Drop __bitwise and __force prefix We've used __bitwise and __force for some integer parameters for sanity-checks via sparse, with a hope that it'll reduce the misuse or incorrect assignments. This worked in principle, but OTOH, it's been quite a PITA, making the code much uglier than its gain, too, because one had to cast with __force everywhere. Also, Rust-binding would skip those defines because of __force usage, which will become more pains in near future. So let's drop __bitwise and __force prefix usages. In this patch, we start cleaning up the UAPI headers at first. The former bit-wised typedefs are still kept for compatibility for now. As it's only markers for sparse, the changes are absolutely safe, per se. Only that we'll need to watch out more carefully about the variable usage for PCM format type, etc. Reviewed-by: Cezary Rojewski Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260812060557.80445-2-tiwai@suse.de --- include/uapi/sound/asequencer.h | 8 +- include/uapi/sound/asound.h | 178 ++++++++++++++++---------------- 2 files changed, 93 insertions(+), 93 deletions(-) diff --git a/include/uapi/sound/asequencer.h b/include/uapi/sound/asequencer.h index 3deba3965ca5bc..c2e3d079c51ec7 100644 --- a/include/uapi/sound/asequencer.h +++ b/include/uapi/sound/asequencer.h @@ -338,10 +338,10 @@ struct snd_seq_running_info { /* client types */ -typedef int __bitwise snd_seq_client_type_t; -#define NO_CLIENT ((__force snd_seq_client_type_t) 0) -#define USER_CLIENT ((__force snd_seq_client_type_t) 1) -#define KERNEL_CLIENT ((__force snd_seq_client_type_t) 2) +typedef int snd_seq_client_type_t; +#define NO_CLIENT 0 +#define USER_CLIENT 1 +#define KERNEL_CLIENT 2 /* event filter flags */ #define SNDRV_SEQ_FILTER_BROADCAST (1U<<0) /* accept broadcast messages */ diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index 500599213f9397..c11da9656e3811 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -169,72 +169,72 @@ enum { SNDRV_PCM_STREAM_LAST = SNDRV_PCM_STREAM_CAPTURE, }; -typedef int __bitwise snd_pcm_access_t; -#define SNDRV_PCM_ACCESS_MMAP_INTERLEAVED ((__force snd_pcm_access_t) 0) /* interleaved mmap */ -#define SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED ((__force snd_pcm_access_t) 1) /* noninterleaved mmap */ -#define SNDRV_PCM_ACCESS_MMAP_COMPLEX ((__force snd_pcm_access_t) 2) /* complex mmap */ -#define SNDRV_PCM_ACCESS_RW_INTERLEAVED ((__force snd_pcm_access_t) 3) /* readi/writei */ -#define SNDRV_PCM_ACCESS_RW_NONINTERLEAVED ((__force snd_pcm_access_t) 4) /* readn/writen */ +typedef int snd_pcm_access_t; +#define SNDRV_PCM_ACCESS_MMAP_INTERLEAVED 0 /* interleaved mmap */ +#define SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED 1 /* noninterleaved mmap */ +#define SNDRV_PCM_ACCESS_MMAP_COMPLEX 2 /* complex mmap */ +#define SNDRV_PCM_ACCESS_RW_INTERLEAVED 3 /* readi/writei */ +#define SNDRV_PCM_ACCESS_RW_NONINTERLEAVED 4 /* readn/writen */ #define SNDRV_PCM_ACCESS_LAST SNDRV_PCM_ACCESS_RW_NONINTERLEAVED -typedef int __bitwise snd_pcm_format_t; -#define SNDRV_PCM_FORMAT_S8 ((__force snd_pcm_format_t) 0) -#define SNDRV_PCM_FORMAT_U8 ((__force snd_pcm_format_t) 1) -#define SNDRV_PCM_FORMAT_S16_LE ((__force snd_pcm_format_t) 2) -#define SNDRV_PCM_FORMAT_S16_BE ((__force snd_pcm_format_t) 3) -#define SNDRV_PCM_FORMAT_U16_LE ((__force snd_pcm_format_t) 4) -#define SNDRV_PCM_FORMAT_U16_BE ((__force snd_pcm_format_t) 5) -#define SNDRV_PCM_FORMAT_S24_LE ((__force snd_pcm_format_t) 6) /* low three bytes */ -#define SNDRV_PCM_FORMAT_S24_BE ((__force snd_pcm_format_t) 7) /* low three bytes */ -#define SNDRV_PCM_FORMAT_U24_LE ((__force snd_pcm_format_t) 8) /* low three bytes */ -#define SNDRV_PCM_FORMAT_U24_BE ((__force snd_pcm_format_t) 9) /* low three bytes */ +typedef int snd_pcm_format_t; +#define SNDRV_PCM_FORMAT_S8 0 +#define SNDRV_PCM_FORMAT_U8 1 +#define SNDRV_PCM_FORMAT_S16_LE 2 +#define SNDRV_PCM_FORMAT_S16_BE 3 +#define SNDRV_PCM_FORMAT_U16_LE 4 +#define SNDRV_PCM_FORMAT_U16_BE 5 +#define SNDRV_PCM_FORMAT_S24_LE 6 /* low three bytes */ +#define SNDRV_PCM_FORMAT_S24_BE 7 /* low three bytes */ +#define SNDRV_PCM_FORMAT_U24_LE 8 /* low three bytes */ +#define SNDRV_PCM_FORMAT_U24_BE 9 /* low three bytes */ /* * For S32/U32 formats, 'msbits' hardware parameter is often used to deliver information about the * available bit count in most significant bit. It's for the case of so-called 'left-justified' or * `right-padding` sample which has less width than 32 bit. */ -#define SNDRV_PCM_FORMAT_S32_LE ((__force snd_pcm_format_t) 10) -#define SNDRV_PCM_FORMAT_S32_BE ((__force snd_pcm_format_t) 11) -#define SNDRV_PCM_FORMAT_U32_LE ((__force snd_pcm_format_t) 12) -#define SNDRV_PCM_FORMAT_U32_BE ((__force snd_pcm_format_t) 13) -#define SNDRV_PCM_FORMAT_FLOAT_LE ((__force snd_pcm_format_t) 14) /* 4-byte float, IEEE-754 32-bit, range -1.0 to 1.0 */ -#define SNDRV_PCM_FORMAT_FLOAT_BE ((__force snd_pcm_format_t) 15) /* 4-byte float, IEEE-754 32-bit, range -1.0 to 1.0 */ -#define SNDRV_PCM_FORMAT_FLOAT64_LE ((__force snd_pcm_format_t) 16) /* 8-byte float, IEEE-754 64-bit, range -1.0 to 1.0 */ -#define SNDRV_PCM_FORMAT_FLOAT64_BE ((__force snd_pcm_format_t) 17) /* 8-byte float, IEEE-754 64-bit, range -1.0 to 1.0 */ -#define SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE ((__force snd_pcm_format_t) 18) /* IEC-958 subframe, Little Endian */ -#define SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE ((__force snd_pcm_format_t) 19) /* IEC-958 subframe, Big Endian */ -#define SNDRV_PCM_FORMAT_MU_LAW ((__force snd_pcm_format_t) 20) -#define SNDRV_PCM_FORMAT_A_LAW ((__force snd_pcm_format_t) 21) -#define SNDRV_PCM_FORMAT_IMA_ADPCM ((__force snd_pcm_format_t) 22) -#define SNDRV_PCM_FORMAT_MPEG ((__force snd_pcm_format_t) 23) -#define SNDRV_PCM_FORMAT_GSM ((__force snd_pcm_format_t) 24) -#define SNDRV_PCM_FORMAT_S20_LE ((__force snd_pcm_format_t) 25) /* in four bytes, LSB justified */ -#define SNDRV_PCM_FORMAT_S20_BE ((__force snd_pcm_format_t) 26) /* in four bytes, LSB justified */ -#define SNDRV_PCM_FORMAT_U20_LE ((__force snd_pcm_format_t) 27) /* in four bytes, LSB justified */ -#define SNDRV_PCM_FORMAT_U20_BE ((__force snd_pcm_format_t) 28) /* in four bytes, LSB justified */ +#define SNDRV_PCM_FORMAT_S32_LE 10 +#define SNDRV_PCM_FORMAT_S32_BE 11 +#define SNDRV_PCM_FORMAT_U32_LE 12 +#define SNDRV_PCM_FORMAT_U32_BE 13 +#define SNDRV_PCM_FORMAT_FLOAT_LE 14 /* 4-byte float, IEEE-754 32-bit, range -1.0 to 1.0 */ +#define SNDRV_PCM_FORMAT_FLOAT_BE 15 /* 4-byte float, IEEE-754 32-bit, range -1.0 to 1.0 */ +#define SNDRV_PCM_FORMAT_FLOAT64_LE 16 /* 8-byte float, IEEE-754 64-bit, range -1.0 to 1.0 */ +#define SNDRV_PCM_FORMAT_FLOAT64_BE 17 /* 8-byte float, IEEE-754 64-bit, range -1.0 to 1.0 */ +#define SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE 18 /* IEC-958 subframe, Little Endian */ +#define SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE 19 /* IEC-958 subframe, Big Endian */ +#define SNDRV_PCM_FORMAT_MU_LAW 20 +#define SNDRV_PCM_FORMAT_A_LAW 21 +#define SNDRV_PCM_FORMAT_IMA_ADPCM 22 +#define SNDRV_PCM_FORMAT_MPEG 23 +#define SNDRV_PCM_FORMAT_GSM 24 +#define SNDRV_PCM_FORMAT_S20_LE 25 /* in four bytes, LSB justified */ +#define SNDRV_PCM_FORMAT_S20_BE 26 /* in four bytes, LSB justified */ +#define SNDRV_PCM_FORMAT_U20_LE 27 /* in four bytes, LSB justified */ +#define SNDRV_PCM_FORMAT_U20_BE 28 /* in four bytes, LSB justified */ /* gap in the numbering for a future standard linear format */ -#define SNDRV_PCM_FORMAT_SPECIAL ((__force snd_pcm_format_t) 31) -#define SNDRV_PCM_FORMAT_S24_3LE ((__force snd_pcm_format_t) 32) /* in three bytes */ -#define SNDRV_PCM_FORMAT_S24_3BE ((__force snd_pcm_format_t) 33) /* in three bytes */ -#define SNDRV_PCM_FORMAT_U24_3LE ((__force snd_pcm_format_t) 34) /* in three bytes */ -#define SNDRV_PCM_FORMAT_U24_3BE ((__force snd_pcm_format_t) 35) /* in three bytes */ -#define SNDRV_PCM_FORMAT_S20_3LE ((__force snd_pcm_format_t) 36) /* in three bytes */ -#define SNDRV_PCM_FORMAT_S20_3BE ((__force snd_pcm_format_t) 37) /* in three bytes */ -#define SNDRV_PCM_FORMAT_U20_3LE ((__force snd_pcm_format_t) 38) /* in three bytes */ -#define SNDRV_PCM_FORMAT_U20_3BE ((__force snd_pcm_format_t) 39) /* in three bytes */ -#define SNDRV_PCM_FORMAT_S18_3LE ((__force snd_pcm_format_t) 40) /* in three bytes */ -#define SNDRV_PCM_FORMAT_S18_3BE ((__force snd_pcm_format_t) 41) /* in three bytes */ -#define SNDRV_PCM_FORMAT_U18_3LE ((__force snd_pcm_format_t) 42) /* in three bytes */ -#define SNDRV_PCM_FORMAT_U18_3BE ((__force snd_pcm_format_t) 43) /* in three bytes */ -#define SNDRV_PCM_FORMAT_G723_24 ((__force snd_pcm_format_t) 44) /* 8 samples in 3 bytes */ -#define SNDRV_PCM_FORMAT_G723_24_1B ((__force snd_pcm_format_t) 45) /* 1 sample in 1 byte */ -#define SNDRV_PCM_FORMAT_G723_40 ((__force snd_pcm_format_t) 46) /* 8 Samples in 5 bytes */ -#define SNDRV_PCM_FORMAT_G723_40_1B ((__force snd_pcm_format_t) 47) /* 1 sample in 1 byte */ -#define SNDRV_PCM_FORMAT_DSD_U8 ((__force snd_pcm_format_t) 48) /* DSD, 1-byte samples DSD (x8) */ -#define SNDRV_PCM_FORMAT_DSD_U16_LE ((__force snd_pcm_format_t) 49) /* DSD, 2-byte samples DSD (x16), little endian */ -#define SNDRV_PCM_FORMAT_DSD_U32_LE ((__force snd_pcm_format_t) 50) /* DSD, 4-byte samples DSD (x32), little endian */ -#define SNDRV_PCM_FORMAT_DSD_U16_BE ((__force snd_pcm_format_t) 51) /* DSD, 2-byte samples DSD (x16), big endian */ -#define SNDRV_PCM_FORMAT_DSD_U32_BE ((__force snd_pcm_format_t) 52) /* DSD, 4-byte samples DSD (x32), big endian */ +#define SNDRV_PCM_FORMAT_SPECIAL 31 +#define SNDRV_PCM_FORMAT_S24_3LE 32 /* in three bytes */ +#define SNDRV_PCM_FORMAT_S24_3BE 33 /* in three bytes */ +#define SNDRV_PCM_FORMAT_U24_3LE 34 /* in three bytes */ +#define SNDRV_PCM_FORMAT_U24_3BE 35 /* in three bytes */ +#define SNDRV_PCM_FORMAT_S20_3LE 36 /* in three bytes */ +#define SNDRV_PCM_FORMAT_S20_3BE 37 /* in three bytes */ +#define SNDRV_PCM_FORMAT_U20_3LE 38 /* in three bytes */ +#define SNDRV_PCM_FORMAT_U20_3BE 39 /* in three bytes */ +#define SNDRV_PCM_FORMAT_S18_3LE 40 /* in three bytes */ +#define SNDRV_PCM_FORMAT_S18_3BE 41 /* in three bytes */ +#define SNDRV_PCM_FORMAT_U18_3LE 42 /* in three bytes */ +#define SNDRV_PCM_FORMAT_U18_3BE 43 /* in three bytes */ +#define SNDRV_PCM_FORMAT_G723_24 44 /* 8 samples in 3 bytes */ +#define SNDRV_PCM_FORMAT_G723_24_1B 45 /* 1 sample in 1 byte */ +#define SNDRV_PCM_FORMAT_G723_40 46 /* 8 Samples in 5 bytes */ +#define SNDRV_PCM_FORMAT_G723_40_1B 47 /* 1 sample in 1 byte */ +#define SNDRV_PCM_FORMAT_DSD_U8 48 /* DSD, 1-byte samples DSD (x8) */ +#define SNDRV_PCM_FORMAT_DSD_U16_LE 49 /* DSD, 2-byte samples DSD (x16), little endian */ +#define SNDRV_PCM_FORMAT_DSD_U32_LE 50 /* DSD, 4-byte samples DSD (x32), little endian */ +#define SNDRV_PCM_FORMAT_DSD_U16_BE 51 /* DSD, 2-byte samples DSD (x16), big endian */ +#define SNDRV_PCM_FORMAT_DSD_U32_BE 52 /* DSD, 4-byte samples DSD (x32), big endian */ #define SNDRV_PCM_FORMAT_LAST SNDRV_PCM_FORMAT_DSD_U32_BE #define SNDRV_PCM_FORMAT_FIRST SNDRV_PCM_FORMAT_S8 @@ -265,11 +265,11 @@ typedef int __bitwise snd_pcm_format_t; #define SNDRV_PCM_FORMAT_U20 SNDRV_PCM_FORMAT_U20_BE #endif -typedef int __bitwise snd_pcm_subformat_t; -#define SNDRV_PCM_SUBFORMAT_STD ((__force snd_pcm_subformat_t) 0) -#define SNDRV_PCM_SUBFORMAT_MSBITS_MAX ((__force snd_pcm_subformat_t) 1) -#define SNDRV_PCM_SUBFORMAT_MSBITS_20 ((__force snd_pcm_subformat_t) 2) -#define SNDRV_PCM_SUBFORMAT_MSBITS_24 ((__force snd_pcm_subformat_t) 3) +typedef int snd_pcm_subformat_t; +#define SNDRV_PCM_SUBFORMAT_STD 0 +#define SNDRV_PCM_SUBFORMAT_MSBITS_MAX 1 +#define SNDRV_PCM_SUBFORMAT_MSBITS_20 2 +#define SNDRV_PCM_SUBFORMAT_MSBITS_24 3 #define SNDRV_PCM_SUBFORMAT_LAST SNDRV_PCM_SUBFORMAT_MSBITS_24 #define SNDRV_PCM_INFO_MMAP 0x00000001 /* hardware supports mmap */ @@ -303,16 +303,16 @@ typedef int __bitwise snd_pcm_subformat_t; #define __SND_STRUCT_TIME64 #endif -typedef int __bitwise snd_pcm_state_t; -#define SNDRV_PCM_STATE_OPEN ((__force snd_pcm_state_t) 0) /* stream is open */ -#define SNDRV_PCM_STATE_SETUP ((__force snd_pcm_state_t) 1) /* stream has a setup */ -#define SNDRV_PCM_STATE_PREPARED ((__force snd_pcm_state_t) 2) /* stream is ready to start */ -#define SNDRV_PCM_STATE_RUNNING ((__force snd_pcm_state_t) 3) /* stream is running */ -#define SNDRV_PCM_STATE_XRUN ((__force snd_pcm_state_t) 4) /* stream reached an xrun */ -#define SNDRV_PCM_STATE_DRAINING ((__force snd_pcm_state_t) 5) /* stream is draining */ -#define SNDRV_PCM_STATE_PAUSED ((__force snd_pcm_state_t) 6) /* stream is paused */ -#define SNDRV_PCM_STATE_SUSPENDED ((__force snd_pcm_state_t) 7) /* hardware is suspended */ -#define SNDRV_PCM_STATE_DISCONNECTED ((__force snd_pcm_state_t) 8) /* hardware is disconnected */ +typedef int snd_pcm_state_t; +#define SNDRV_PCM_STATE_OPEN 0 /* stream is open */ +#define SNDRV_PCM_STATE_SETUP 1 /* stream has a setup */ +#define SNDRV_PCM_STATE_PREPARED 2 /* stream is ready to start */ +#define SNDRV_PCM_STATE_RUNNING 3 /* stream is running */ +#define SNDRV_PCM_STATE_XRUN 4 /* stream reached an xrun */ +#define SNDRV_PCM_STATE_DRAINING 5 /* stream is draining */ +#define SNDRV_PCM_STATE_PAUSED 6 /* stream is paused */ +#define SNDRV_PCM_STATE_SUSPENDED 7 /* hardware is suspended */ +#define SNDRV_PCM_STATE_DISCONNECTED 8 /* hardware is disconnected */ #define SNDRV_PCM_STATE_LAST SNDRV_PCM_STATE_DISCONNECTED enum { @@ -1091,24 +1091,24 @@ struct snd_ctl_card_bytes { __u64 data; /* user buffer (pointer stored as __u64) */ }; -typedef int __bitwise snd_ctl_elem_type_t; -#define SNDRV_CTL_ELEM_TYPE_NONE ((__force snd_ctl_elem_type_t) 0) /* invalid */ -#define SNDRV_CTL_ELEM_TYPE_BOOLEAN ((__force snd_ctl_elem_type_t) 1) /* boolean type */ -#define SNDRV_CTL_ELEM_TYPE_INTEGER ((__force snd_ctl_elem_type_t) 2) /* integer type */ -#define SNDRV_CTL_ELEM_TYPE_ENUMERATED ((__force snd_ctl_elem_type_t) 3) /* enumerated type */ -#define SNDRV_CTL_ELEM_TYPE_BYTES ((__force snd_ctl_elem_type_t) 4) /* byte array */ -#define SNDRV_CTL_ELEM_TYPE_IEC958 ((__force snd_ctl_elem_type_t) 5) /* IEC958 (S/PDIF) setup */ -#define SNDRV_CTL_ELEM_TYPE_INTEGER64 ((__force snd_ctl_elem_type_t) 6) /* 64-bit integer type */ +typedef int snd_ctl_elem_type_t; +#define SNDRV_CTL_ELEM_TYPE_NONE 0 /* invalid */ +#define SNDRV_CTL_ELEM_TYPE_BOOLEAN 1 /* boolean type */ +#define SNDRV_CTL_ELEM_TYPE_INTEGER 2 /* integer type */ +#define SNDRV_CTL_ELEM_TYPE_ENUMERATED 3 /* enumerated type */ +#define SNDRV_CTL_ELEM_TYPE_BYTES 4 /* byte array */ +#define SNDRV_CTL_ELEM_TYPE_IEC958 5 /* IEC958 (S/PDIF) setup */ +#define SNDRV_CTL_ELEM_TYPE_INTEGER64 6 /* 64-bit integer type */ #define SNDRV_CTL_ELEM_TYPE_LAST SNDRV_CTL_ELEM_TYPE_INTEGER64 -typedef int __bitwise snd_ctl_elem_iface_t; -#define SNDRV_CTL_ELEM_IFACE_CARD ((__force snd_ctl_elem_iface_t) 0) /* global control */ -#define SNDRV_CTL_ELEM_IFACE_HWDEP ((__force snd_ctl_elem_iface_t) 1) /* hardware dependent device */ -#define SNDRV_CTL_ELEM_IFACE_MIXER ((__force snd_ctl_elem_iface_t) 2) /* virtual mixer device */ -#define SNDRV_CTL_ELEM_IFACE_PCM ((__force snd_ctl_elem_iface_t) 3) /* PCM device */ -#define SNDRV_CTL_ELEM_IFACE_RAWMIDI ((__force snd_ctl_elem_iface_t) 4) /* RawMidi device */ -#define SNDRV_CTL_ELEM_IFACE_TIMER ((__force snd_ctl_elem_iface_t) 5) /* timer device */ -#define SNDRV_CTL_ELEM_IFACE_SEQUENCER ((__force snd_ctl_elem_iface_t) 6) /* sequencer client */ +typedef int snd_ctl_elem_iface_t; +#define SNDRV_CTL_ELEM_IFACE_CARD 0 /* global control */ +#define SNDRV_CTL_ELEM_IFACE_HWDEP 1 /* hardware dependent device */ +#define SNDRV_CTL_ELEM_IFACE_MIXER 2 /* virtual mixer device */ +#define SNDRV_CTL_ELEM_IFACE_PCM 3 /* PCM device */ +#define SNDRV_CTL_ELEM_IFACE_RAWMIDI 4 /* RawMidi device */ +#define SNDRV_CTL_ELEM_IFACE_TIMER 5 /* timer device */ +#define SNDRV_CTL_ELEM_IFACE_SEQUENCER 6 /* sequencer client */ #define SNDRV_CTL_ELEM_IFACE_LAST SNDRV_CTL_ELEM_IFACE_SEQUENCER #define SNDRV_CTL_ELEM_ACCESS_READ (1<<0) From 3850dce65e2595feae868f9c4b1395e4bc1dfe6b Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 12 Aug 2026 08:04:34 +0200 Subject: [PATCH 507/612] ALSA: pcm: Drop __force casts Now that the bitwise parameter definitions are gone for PCM parameters, we don't have to cast with ugly __force prefix. Simply drop those superfluous casts. Reviewed-by: Cezary Rojewski Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260812060557.80445-3-tiwai@suse.de --- include/sound/pcm.h | 10 ++++------ include/sound/pcm_params.h | 13 +++++-------- sound/core/pcm.c | 12 +++++------- sound/core/pcm_misc.c | 25 +++++++++++-------------- sound/core/pcm_native.c | 28 ++++++++++++++-------------- 5 files changed, 39 insertions(+), 49 deletions(-) diff --git a/include/sound/pcm.h b/include/sound/pcm.h index 76fc33dce537dd..ab96396a744426 100644 --- a/include/sound/pcm.h +++ b/include/sound/pcm.h @@ -145,7 +145,7 @@ struct snd_pcm_ops { #define SNDRV_PCM_RATE_8000_768000 (SNDRV_PCM_RATE_8000_384000|\ SNDRV_PCM_RATE_705600|\ SNDRV_PCM_RATE_768000) -#define _SNDRV_PCM_FMTBIT(fmt) (1ULL << (__force int)SNDRV_PCM_FORMAT_##fmt) +#define _SNDRV_PCM_FMTBIT(fmt) (1ULL << SNDRV_PCM_FORMAT_##fmt) #define SNDRV_PCM_FMTBIT_S8 _SNDRV_PCM_FMTBIT(S8) #define SNDRV_PCM_FMTBIT_U8 _SNDRV_PCM_FMTBIT(U8) #define SNDRV_PCM_FMTBIT_S16_LE _SNDRV_PCM_FMTBIT(S16_LE) @@ -228,7 +228,7 @@ struct snd_pcm_ops { #define SNDRV_PCM_FMTBIT_U20 SNDRV_PCM_FMTBIT_U20_BE #endif -#define _SNDRV_PCM_SUBFMTBIT(fmt) BIT((__force int)SNDRV_PCM_SUBFORMAT_##fmt) +#define _SNDRV_PCM_SUBFMTBIT(fmt) BIT(SNDRV_PCM_SUBFORMAT_##fmt) #define SNDRV_PCM_SUBFMTBIT_STD _SNDRV_PCM_SUBFMTBIT(STD) #define SNDRV_PCM_SUBFMTBIT_MSBITS_MAX _SNDRV_PCM_SUBFMTBIT(MSBITS_MAX) #define SNDRV_PCM_SUBFMTBIT_MSBITS_20 _SNDRV_PCM_SUBFMTBIT(MSBITS_20) @@ -1515,7 +1515,7 @@ int snd_pcm_add_chmap_ctls(struct snd_pcm *pcm, int stream, */ static inline u64 pcm_format_to_bits(snd_pcm_format_t pcm_format) { - return 1ULL << (__force int) pcm_format; + return 1ULL << pcm_format; } /** @@ -1523,9 +1523,7 @@ static inline u64 pcm_format_to_bits(snd_pcm_format_t pcm_format) * @f: the iterator variable in snd_pcm_format_t type */ #define pcm_for_each_format(f) \ - for ((f) = SNDRV_PCM_FORMAT_FIRST; \ - (__force int)(f) <= (__force int)SNDRV_PCM_FORMAT_LAST; \ - (f) = (__force snd_pcm_format_t)((__force int)(f) + 1)) + for ((f) = SNDRV_PCM_FORMAT_FIRST; (f) <= SNDRV_PCM_FORMAT_LAST; (f)++) /* printk helpers */ #define pcm_err(pcm, fmt, args...) \ diff --git a/include/sound/pcm_params.h b/include/sound/pcm_params.h index fbf35df6e5cf58..bc43955fcb7676 100644 --- a/include/sound/pcm_params.h +++ b/include/sound/pcm_params.h @@ -71,7 +71,7 @@ static inline void snd_mask_set(struct snd_mask *mask, unsigned int val) static inline void snd_mask_set_format(struct snd_mask *mask, snd_pcm_format_t format) { - snd_mask_set(mask, (__force unsigned int)format); + snd_mask_set(mask, format); } static inline void snd_mask_reset(struct snd_mask *mask, unsigned int val) @@ -132,7 +132,7 @@ static inline int snd_mask_test(const struct snd_mask *mask, unsigned int val) static inline int snd_mask_test_format(const struct snd_mask *mask, snd_pcm_format_t format) { - return snd_mask_test(mask, (__force unsigned int)format); + return snd_mask_test(mask, format); } static inline int snd_mask_single(const struct snd_mask *mask) @@ -302,8 +302,7 @@ static inline int snd_interval_eq(const struct snd_interval *i1, const struct sn */ static inline snd_pcm_access_t params_access(const struct snd_pcm_hw_params *p) { - return (__force snd_pcm_access_t)snd_mask_min(hw_param_mask_c(p, - SNDRV_PCM_HW_PARAM_ACCESS)); + return snd_mask_min(hw_param_mask_c(p, SNDRV_PCM_HW_PARAM_ACCESS)); } /** @@ -312,8 +311,7 @@ static inline snd_pcm_access_t params_access(const struct snd_pcm_hw_params *p) */ static inline snd_pcm_format_t params_format(const struct snd_pcm_hw_params *p) { - return (__force snd_pcm_format_t)snd_mask_min(hw_param_mask_c(p, - SNDRV_PCM_HW_PARAM_FORMAT)); + return snd_mask_min(hw_param_mask_c(p, SNDRV_PCM_HW_PARAM_FORMAT)); } /** @@ -323,8 +321,7 @@ static inline snd_pcm_format_t params_format(const struct snd_pcm_hw_params *p) static inline snd_pcm_subformat_t params_subformat(const struct snd_pcm_hw_params *p) { - return (__force snd_pcm_subformat_t)snd_mask_min(hw_param_mask_c(p, - SNDRV_PCM_HW_PARAM_SUBFORMAT)); + return snd_mask_min(hw_param_mask_c(p, SNDRV_PCM_HW_PARAM_SUBFORMAT)); } /** diff --git a/sound/core/pcm.c b/sound/core/pcm.c index bfedf571e021b2..41c2cab7a52c41 100644 --- a/sound/core/pcm.c +++ b/sound/core/pcm.c @@ -211,11 +211,9 @@ static const char * const snd_pcm_format_names[] = { */ const char *snd_pcm_format_name(snd_pcm_format_t format) { - unsigned int format_num = (__force unsigned int)format; - - if (format_num >= ARRAY_SIZE(snd_pcm_format_names) || !snd_pcm_format_names[format_num]) + if (format >= ARRAY_SIZE(snd_pcm_format_names) || !snd_pcm_format_names[format]) return "Unknown"; - return snd_pcm_format_names[format_num]; + return snd_pcm_format_names[format]; } EXPORT_SYMBOL_GPL(snd_pcm_format_name); @@ -275,12 +273,12 @@ static const char *snd_pcm_stream_name(int stream) static const char *snd_pcm_access_name(snd_pcm_access_t access) { - return snd_pcm_access_names[(__force int)access]; + return snd_pcm_access_names[access]; } static const char *snd_pcm_subformat_name(snd_pcm_subformat_t subformat) { - return snd_pcm_subformat_names[(__force int)subformat]; + return snd_pcm_subformat_names[subformat]; } static const char *snd_pcm_tstamp_mode_name(int mode) @@ -290,7 +288,7 @@ static const char *snd_pcm_tstamp_mode_name(int mode) static const char *snd_pcm_state_name(snd_pcm_state_t state) { - return snd_pcm_state_names[(__force int)state]; + return snd_pcm_state_names[state]; } #if IS_ENABLED(CONFIG_SND_PCM_OSS) diff --git a/sound/core/pcm_misc.c b/sound/core/pcm_misc.c index 180b6b64a448e4..13de3b02aa3406 100644 --- a/sound/core/pcm_misc.c +++ b/sound/core/pcm_misc.c @@ -24,15 +24,12 @@ struct pcm_format_data { unsigned char silence[8]; /* silence data to fill */ }; -/* we do lots of calculations on snd_pcm_format_t; shut up sparse */ -#define INT __force int - static bool valid_format(snd_pcm_format_t format) { - return (INT)format >= 0 && (INT)format <= (INT)SNDRV_PCM_FORMAT_LAST; + return format >= 0 && format <= SNDRV_PCM_FORMAT_LAST; } -static const struct pcm_format_data pcm_formats[(INT)SNDRV_PCM_FORMAT_LAST+1] = { +static const struct pcm_format_data pcm_formats[SNDRV_PCM_FORMAT_LAST+1] = { [SNDRV_PCM_FORMAT_S8] = { .width = 8, .phys = 8, .le = -1, .signd = 1, .silence = {}, @@ -251,7 +248,7 @@ int snd_pcm_format_signed(snd_pcm_format_t format) int val; if (!valid_format(format)) return -EINVAL; - val = pcm_formats[(INT)format].signd; + val = pcm_formats[format].signd; if (val < 0) return -EINVAL; return val; @@ -300,7 +297,7 @@ int snd_pcm_format_little_endian(snd_pcm_format_t format) int val; if (!valid_format(format)) return -EINVAL; - val = pcm_formats[(INT)format].le; + val = pcm_formats[format].le; if (val < 0) return -EINVAL; return val; @@ -337,7 +334,7 @@ int snd_pcm_format_width(snd_pcm_format_t format) int val; if (!valid_format(format)) return -EINVAL; - val = pcm_formats[(INT)format].width; + val = pcm_formats[format].width; if (!val) return -EINVAL; return val; @@ -356,7 +353,7 @@ int snd_pcm_format_physical_width(snd_pcm_format_t format) int val; if (!valid_format(format)) return -EINVAL; - val = pcm_formats[(INT)format].phys; + val = pcm_formats[format].phys; if (!val) return -EINVAL; return val; @@ -390,9 +387,9 @@ const unsigned char *snd_pcm_format_silence_64(snd_pcm_format_t format) { if (!valid_format(format)) return NULL; - if (! pcm_formats[(INT)format].phys) + if (! pcm_formats[format].phys) return NULL; - return pcm_formats[(INT)format].silence; + return pcm_formats[format].silence; } EXPORT_SYMBOL(snd_pcm_format_silence_64); @@ -416,12 +413,12 @@ int snd_pcm_format_set_silence(snd_pcm_format_t format, void *data, unsigned int return -EINVAL; if (samples == 0) return 0; - width = pcm_formats[(INT)format].phys; /* physical width */ + width = pcm_formats[format].phys; /* physical width */ if (!width) return -EINVAL; - pat = pcm_formats[(INT)format].silence; + pat = pcm_formats[format].silence; /* signed or 1 byte data */ - if (pcm_formats[(INT)format].signd == 1 || width <= 8) { + if (pcm_formats[format].signd == 1 || width <= 8) { unsigned int bytes = samples * width / 8; memset(data, *pat, bytes); return 0; diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index f44dc334aac646..4a5057e7629df1 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -257,7 +257,7 @@ int snd_pcm_info_user(struct snd_pcm_substream *substream, } /* macro for simplified cast */ -#define PARAM_MASK_BIT(b) (1U << (__force int)(b)) +#define PARAM_MASK_BIT(b) (1U << (b)) static bool hw_support_mmap(struct snd_pcm_substream *substream) { @@ -489,7 +489,7 @@ static int fixup_unreferenced_params(struct snd_pcm_substream *substream, params->msbits = snd_interval_value(i); m = hw_param_mask_c(params, SNDRV_PCM_HW_PARAM_FORMAT); if (snd_mask_single(m)) { - snd_pcm_format_t format = (__force snd_pcm_format_t)snd_mask_min(m); + snd_pcm_format_t format = snd_mask_min(m); params->msbits = snd_pcm_format_width(format); } } @@ -497,13 +497,13 @@ static int fixup_unreferenced_params(struct snd_pcm_substream *substream, if (params->msbits) { m = hw_param_mask_c(params, SNDRV_PCM_HW_PARAM_FORMAT); if (snd_mask_single(m)) { - snd_pcm_format_t format = (__force snd_pcm_format_t)snd_mask_min(m); + snd_pcm_format_t format = snd_mask_min(m); if (snd_pcm_format_linear(format) && snd_pcm_format_width(format) != params->msbits) { m_rw = hw_param_mask(params, SNDRV_PCM_HW_PARAM_SUBFORMAT); snd_mask_reset(m_rw, - (__force unsigned)SNDRV_PCM_SUBFORMAT_MSBITS_MAX); + SNDRV_PCM_SUBFORMAT_MSBITS_MAX); if (snd_mask_empty(m_rw)) return -EINVAL; } @@ -1252,7 +1252,7 @@ static void snd_pcm_trigger_tstamp(struct snd_pcm_substream *substream) runtime->trigger_master = NULL; } -#define ACTION_ARG_IGNORE (__force snd_pcm_state_t)0 +#define ACTION_ARG_IGNORE 0 struct action_ops { int (*pre_action)(struct snd_pcm_substream *substream, @@ -1635,7 +1635,7 @@ EXPORT_SYMBOL_GPL(snd_pcm_stop_xrun); /* * pause callbacks: pass boolean (to start pause or resume) as state argument */ -#define pause_pushed(state) (__force bool)(state) +#define pause_pushed(state) (bool)(state) static int snd_pcm_pre_pause(struct snd_pcm_substream *substream, snd_pcm_state_t state) @@ -1707,14 +1707,14 @@ static const struct action_ops snd_pcm_action_pause = { static int snd_pcm_pause(struct snd_pcm_substream *substream, bool push) { return snd_pcm_action(&snd_pcm_action_pause, substream, - (__force snd_pcm_state_t)push); + (snd_pcm_state_t)push); } static int snd_pcm_pause_lock_irq(struct snd_pcm_substream *substream, bool push) { return snd_pcm_action_lock_irq(&snd_pcm_action_pause, substream, - (__force snd_pcm_state_t)push); + (snd_pcm_state_t)push); } #ifdef CONFIG_PM @@ -1982,7 +1982,7 @@ static int snd_pcm_pre_prepare(struct snd_pcm_substream *substream, snd_pcm_state_t state) { snd_pcm_state_t cur_state = snd_pcm_get_state(substream); - int f_flags = (__force int)state; + int f_flags = state; if (cur_state == SNDRV_PCM_STATE_OPEN || cur_state == SNDRV_PCM_STATE_DISCONNECTED) @@ -2050,7 +2050,7 @@ static int snd_pcm_prepare(struct snd_pcm_substream *substream, return snd_pcm_action_nonatomic(&snd_pcm_action_prepare, substream, - (__force snd_pcm_state_t)f_flags); + (snd_pcm_state_t)f_flags); } /* @@ -2461,7 +2461,7 @@ static int snd_pcm_hw_rule_format(struct snd_pcm_hw_params *params, if (bits <= 0) continue; /* ignore invalid formats */ if ((unsigned)bits < i->min || (unsigned)bits > i->max) - snd_mask_reset(&m, (__force unsigned)k); + snd_mask_reset(&m, k); } return snd_mask_refine(mask, &m); } @@ -2543,16 +2543,16 @@ static int snd_pcm_hw_rule_subformats(struct snd_pcm_hw_params *params, snd_mask_none(&m); /* All PCMs support at least the default STD subformat. */ - snd_mask_set(&m, (__force unsigned)SNDRV_PCM_SUBFORMAT_STD); + snd_mask_set(&m, SNDRV_PCM_SUBFORMAT_STD); pcm_for_each_format(f) { - if (!snd_mask_test(fmask, (__force unsigned)f)) + if (!snd_mask_test(fmask, f)) continue; if (f == SNDRV_PCM_FORMAT_S32_LE && *subformats) m.bits[0] |= *subformats; else if (snd_pcm_format_linear(f)) - snd_mask_set(&m, (__force unsigned)SNDRV_PCM_SUBFORMAT_MSBITS_MAX); + snd_mask_set(&m, SNDRV_PCM_SUBFORMAT_MSBITS_MAX); } return snd_mask_refine(sfmask, &m); From de8131f2f1ba763b68ded06c3b4a63fb70d5dd81 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 12 Aug 2026 08:04:35 +0200 Subject: [PATCH 508/612] ALSA: pcm: Avoid macros for SNDRV_PCM_FMTBIT and SNDRV_PCM_SUBFMTBIT Avoid macros to define SNDRV_PCM_FMTBIT_* and SNDRV_PCM_SUBFMTBIT_* contants but use plain bit shifts, instead. This allows bindgen and other tools aware of those definitions. Reviewed-by: Cezary Rojewski Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260812060557.80445-4-tiwai@suse.de --- include/sound/pcm.h | 112 ++++++++++++++++++++++---------------------- 1 file changed, 55 insertions(+), 57 deletions(-) diff --git a/include/sound/pcm.h b/include/sound/pcm.h index ab96396a744426..02d8689354c4ad 100644 --- a/include/sound/pcm.h +++ b/include/sound/pcm.h @@ -145,61 +145,60 @@ struct snd_pcm_ops { #define SNDRV_PCM_RATE_8000_768000 (SNDRV_PCM_RATE_8000_384000|\ SNDRV_PCM_RATE_705600|\ SNDRV_PCM_RATE_768000) -#define _SNDRV_PCM_FMTBIT(fmt) (1ULL << SNDRV_PCM_FORMAT_##fmt) -#define SNDRV_PCM_FMTBIT_S8 _SNDRV_PCM_FMTBIT(S8) -#define SNDRV_PCM_FMTBIT_U8 _SNDRV_PCM_FMTBIT(U8) -#define SNDRV_PCM_FMTBIT_S16_LE _SNDRV_PCM_FMTBIT(S16_LE) -#define SNDRV_PCM_FMTBIT_S16_BE _SNDRV_PCM_FMTBIT(S16_BE) -#define SNDRV_PCM_FMTBIT_U16_LE _SNDRV_PCM_FMTBIT(U16_LE) -#define SNDRV_PCM_FMTBIT_U16_BE _SNDRV_PCM_FMTBIT(U16_BE) -#define SNDRV_PCM_FMTBIT_S24_LE _SNDRV_PCM_FMTBIT(S24_LE) -#define SNDRV_PCM_FMTBIT_S24_BE _SNDRV_PCM_FMTBIT(S24_BE) -#define SNDRV_PCM_FMTBIT_U24_LE _SNDRV_PCM_FMTBIT(U24_LE) -#define SNDRV_PCM_FMTBIT_U24_BE _SNDRV_PCM_FMTBIT(U24_BE) +#define SNDRV_PCM_FMTBIT_S8 (1ULL << SNDRV_PCM_FORMAT_S8) +#define SNDRV_PCM_FMTBIT_U8 (1ULL << SNDRV_PCM_FORMAT_U8) +#define SNDRV_PCM_FMTBIT_S16_LE (1ULL << SNDRV_PCM_FORMAT_S16_LE) +#define SNDRV_PCM_FMTBIT_S16_BE (1ULL << SNDRV_PCM_FORMAT_S16_BE) +#define SNDRV_PCM_FMTBIT_U16_LE (1ULL << SNDRV_PCM_FORMAT_U16_LE) +#define SNDRV_PCM_FMTBIT_U16_BE (1ULL << SNDRV_PCM_FORMAT_U16_BE) +#define SNDRV_PCM_FMTBIT_S24_LE (1ULL << SNDRV_PCM_FORMAT_S24_LE) +#define SNDRV_PCM_FMTBIT_S24_BE (1ULL << SNDRV_PCM_FORMAT_S24_BE) +#define SNDRV_PCM_FMTBIT_U24_LE (1ULL << SNDRV_PCM_FORMAT_U24_LE) +#define SNDRV_PCM_FMTBIT_U24_BE (1ULL << SNDRV_PCM_FORMAT_U24_BE) // For S32/U32 formats, 'msbits' hardware parameter is often used to deliver information about the // available bit count in most significant bit. It's for the case of so-called 'left-justified' or // `right-padding` sample which has less width than 32 bit. -#define SNDRV_PCM_FMTBIT_S32_LE _SNDRV_PCM_FMTBIT(S32_LE) -#define SNDRV_PCM_FMTBIT_S32_BE _SNDRV_PCM_FMTBIT(S32_BE) -#define SNDRV_PCM_FMTBIT_U32_LE _SNDRV_PCM_FMTBIT(U32_LE) -#define SNDRV_PCM_FMTBIT_U32_BE _SNDRV_PCM_FMTBIT(U32_BE) -#define SNDRV_PCM_FMTBIT_FLOAT_LE _SNDRV_PCM_FMTBIT(FLOAT_LE) -#define SNDRV_PCM_FMTBIT_FLOAT_BE _SNDRV_PCM_FMTBIT(FLOAT_BE) -#define SNDRV_PCM_FMTBIT_FLOAT64_LE _SNDRV_PCM_FMTBIT(FLOAT64_LE) -#define SNDRV_PCM_FMTBIT_FLOAT64_BE _SNDRV_PCM_FMTBIT(FLOAT64_BE) -#define SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE _SNDRV_PCM_FMTBIT(IEC958_SUBFRAME_LE) -#define SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_BE _SNDRV_PCM_FMTBIT(IEC958_SUBFRAME_BE) -#define SNDRV_PCM_FMTBIT_MU_LAW _SNDRV_PCM_FMTBIT(MU_LAW) -#define SNDRV_PCM_FMTBIT_A_LAW _SNDRV_PCM_FMTBIT(A_LAW) -#define SNDRV_PCM_FMTBIT_IMA_ADPCM _SNDRV_PCM_FMTBIT(IMA_ADPCM) -#define SNDRV_PCM_FMTBIT_MPEG _SNDRV_PCM_FMTBIT(MPEG) -#define SNDRV_PCM_FMTBIT_GSM _SNDRV_PCM_FMTBIT(GSM) -#define SNDRV_PCM_FMTBIT_S20_LE _SNDRV_PCM_FMTBIT(S20_LE) -#define SNDRV_PCM_FMTBIT_U20_LE _SNDRV_PCM_FMTBIT(U20_LE) -#define SNDRV_PCM_FMTBIT_S20_BE _SNDRV_PCM_FMTBIT(S20_BE) -#define SNDRV_PCM_FMTBIT_U20_BE _SNDRV_PCM_FMTBIT(U20_BE) -#define SNDRV_PCM_FMTBIT_SPECIAL _SNDRV_PCM_FMTBIT(SPECIAL) -#define SNDRV_PCM_FMTBIT_S24_3LE _SNDRV_PCM_FMTBIT(S24_3LE) -#define SNDRV_PCM_FMTBIT_U24_3LE _SNDRV_PCM_FMTBIT(U24_3LE) -#define SNDRV_PCM_FMTBIT_S24_3BE _SNDRV_PCM_FMTBIT(S24_3BE) -#define SNDRV_PCM_FMTBIT_U24_3BE _SNDRV_PCM_FMTBIT(U24_3BE) -#define SNDRV_PCM_FMTBIT_S20_3LE _SNDRV_PCM_FMTBIT(S20_3LE) -#define SNDRV_PCM_FMTBIT_U20_3LE _SNDRV_PCM_FMTBIT(U20_3LE) -#define SNDRV_PCM_FMTBIT_S20_3BE _SNDRV_PCM_FMTBIT(S20_3BE) -#define SNDRV_PCM_FMTBIT_U20_3BE _SNDRV_PCM_FMTBIT(U20_3BE) -#define SNDRV_PCM_FMTBIT_S18_3LE _SNDRV_PCM_FMTBIT(S18_3LE) -#define SNDRV_PCM_FMTBIT_U18_3LE _SNDRV_PCM_FMTBIT(U18_3LE) -#define SNDRV_PCM_FMTBIT_S18_3BE _SNDRV_PCM_FMTBIT(S18_3BE) -#define SNDRV_PCM_FMTBIT_U18_3BE _SNDRV_PCM_FMTBIT(U18_3BE) -#define SNDRV_PCM_FMTBIT_G723_24 _SNDRV_PCM_FMTBIT(G723_24) -#define SNDRV_PCM_FMTBIT_G723_24_1B _SNDRV_PCM_FMTBIT(G723_24_1B) -#define SNDRV_PCM_FMTBIT_G723_40 _SNDRV_PCM_FMTBIT(G723_40) -#define SNDRV_PCM_FMTBIT_G723_40_1B _SNDRV_PCM_FMTBIT(G723_40_1B) -#define SNDRV_PCM_FMTBIT_DSD_U8 _SNDRV_PCM_FMTBIT(DSD_U8) -#define SNDRV_PCM_FMTBIT_DSD_U16_LE _SNDRV_PCM_FMTBIT(DSD_U16_LE) -#define SNDRV_PCM_FMTBIT_DSD_U32_LE _SNDRV_PCM_FMTBIT(DSD_U32_LE) -#define SNDRV_PCM_FMTBIT_DSD_U16_BE _SNDRV_PCM_FMTBIT(DSD_U16_BE) -#define SNDRV_PCM_FMTBIT_DSD_U32_BE _SNDRV_PCM_FMTBIT(DSD_U32_BE) +#define SNDRV_PCM_FMTBIT_S32_LE (1ULL << SNDRV_PCM_FORMAT_S32_LE) +#define SNDRV_PCM_FMTBIT_S32_BE (1ULL << SNDRV_PCM_FORMAT_S32_BE) +#define SNDRV_PCM_FMTBIT_U32_LE (1ULL << SNDRV_PCM_FORMAT_U32_LE) +#define SNDRV_PCM_FMTBIT_U32_BE (1ULL << SNDRV_PCM_FORMAT_U32_BE) +#define SNDRV_PCM_FMTBIT_FLOAT_LE (1ULL << SNDRV_PCM_FORMAT_FLOAT_LE) +#define SNDRV_PCM_FMTBIT_FLOAT_BE (1ULL << SNDRV_PCM_FORMAT_FLOAT_BE) +#define SNDRV_PCM_FMTBIT_FLOAT64_LE (1ULL << SNDRV_PCM_FORMAT_FLOAT64_LE) +#define SNDRV_PCM_FMTBIT_FLOAT64_BE (1ULL << SNDRV_PCM_FORMAT_FLOAT64_BE) +#define SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE (1ULL << SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE) +#define SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_BE (1ULL << SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE) +#define SNDRV_PCM_FMTBIT_MU_LAW (1ULL << SNDRV_PCM_FORMAT_MU_LAW) +#define SNDRV_PCM_FMTBIT_A_LAW (1ULL << SNDRV_PCM_FORMAT_A_LAW) +#define SNDRV_PCM_FMTBIT_IMA_ADPCM (1ULL << SNDRV_PCM_FORMAT_IMA_ADPCM) +#define SNDRV_PCM_FMTBIT_MPEG (1ULL << SNDRV_PCM_FORMAT_MPEG) +#define SNDRV_PCM_FMTBIT_GSM (1ULL << SNDRV_PCM_FORMAT_GSM) +#define SNDRV_PCM_FMTBIT_S20_LE (1ULL << SNDRV_PCM_FORMAT_S20_LE) +#define SNDRV_PCM_FMTBIT_U20_LE (1ULL << SNDRV_PCM_FORMAT_U20_LE) +#define SNDRV_PCM_FMTBIT_S20_BE (1ULL << SNDRV_PCM_FORMAT_S20_BE) +#define SNDRV_PCM_FMTBIT_U20_BE (1ULL << SNDRV_PCM_FORMAT_U20_BE) +#define SNDRV_PCM_FMTBIT_SPECIAL (1ULL << SNDRV_PCM_FORMAT_SPECIAL) +#define SNDRV_PCM_FMTBIT_S24_3LE (1ULL << SNDRV_PCM_FORMAT_S24_3LE) +#define SNDRV_PCM_FMTBIT_U24_3LE (1ULL << SNDRV_PCM_FORMAT_U24_3LE) +#define SNDRV_PCM_FMTBIT_S24_3BE (1ULL << SNDRV_PCM_FORMAT_S24_3BE) +#define SNDRV_PCM_FMTBIT_U24_3BE (1ULL << SNDRV_PCM_FORMAT_U24_3BE) +#define SNDRV_PCM_FMTBIT_S20_3LE (1ULL << SNDRV_PCM_FORMAT_S20_3LE) +#define SNDRV_PCM_FMTBIT_U20_3LE (1ULL << SNDRV_PCM_FORMAT_U20_3LE) +#define SNDRV_PCM_FMTBIT_S20_3BE (1ULL << SNDRV_PCM_FORMAT_S20_3BE) +#define SNDRV_PCM_FMTBIT_U20_3BE (1ULL << SNDRV_PCM_FORMAT_U20_3BE) +#define SNDRV_PCM_FMTBIT_S18_3LE (1ULL << SNDRV_PCM_FORMAT_S18_3LE) +#define SNDRV_PCM_FMTBIT_U18_3LE (1ULL << SNDRV_PCM_FORMAT_U18_3LE) +#define SNDRV_PCM_FMTBIT_S18_3BE (1ULL << SNDRV_PCM_FORMAT_S18_3BE) +#define SNDRV_PCM_FMTBIT_U18_3BE (1ULL << SNDRV_PCM_FORMAT_U18_3BE) +#define SNDRV_PCM_FMTBIT_G723_24 (1ULL << SNDRV_PCM_FORMAT_G723_24) +#define SNDRV_PCM_FMTBIT_G723_24_1B (1ULL << SNDRV_PCM_FORMAT_G723_24_1B) +#define SNDRV_PCM_FMTBIT_G723_40 (1ULL << SNDRV_PCM_FORMAT_G723_40) +#define SNDRV_PCM_FMTBIT_G723_40_1B (1ULL << SNDRV_PCM_FORMAT_G723_40_1B) +#define SNDRV_PCM_FMTBIT_DSD_U8 (1ULL << SNDRV_PCM_FORMAT_DSD_U8) +#define SNDRV_PCM_FMTBIT_DSD_U16_LE (1ULL << SNDRV_PCM_FORMAT_DSD_U16_LE) +#define SNDRV_PCM_FMTBIT_DSD_U32_LE (1ULL << SNDRV_PCM_FORMAT_DSD_U32_LE) +#define SNDRV_PCM_FMTBIT_DSD_U16_BE (1ULL << SNDRV_PCM_FORMAT_DSD_U16_BE) +#define SNDRV_PCM_FMTBIT_DSD_U32_BE (1ULL << SNDRV_PCM_FORMAT_DSD_U32_BE) #ifdef SNDRV_LITTLE_ENDIAN #define SNDRV_PCM_FMTBIT_S16 SNDRV_PCM_FMTBIT_S16_LE @@ -228,11 +227,10 @@ struct snd_pcm_ops { #define SNDRV_PCM_FMTBIT_U20 SNDRV_PCM_FMTBIT_U20_BE #endif -#define _SNDRV_PCM_SUBFMTBIT(fmt) BIT(SNDRV_PCM_SUBFORMAT_##fmt) -#define SNDRV_PCM_SUBFMTBIT_STD _SNDRV_PCM_SUBFMTBIT(STD) -#define SNDRV_PCM_SUBFMTBIT_MSBITS_MAX _SNDRV_PCM_SUBFMTBIT(MSBITS_MAX) -#define SNDRV_PCM_SUBFMTBIT_MSBITS_20 _SNDRV_PCM_SUBFMTBIT(MSBITS_20) -#define SNDRV_PCM_SUBFMTBIT_MSBITS_24 _SNDRV_PCM_SUBFMTBIT(MSBITS_24) +#define SNDRV_PCM_SUBFMTBIT_STD (1U << SNDRV_PCM_SUBFORMAT_STD) +#define SNDRV_PCM_SUBFMTBIT_MSBITS_MAX (1U << SNDRV_PCM_SUBFORMAT_MSBITS_MAX) +#define SNDRV_PCM_SUBFMTBIT_MSBITS_20 (1U << SNDRV_PCM_SUBFORMAT_MSBITS_20) +#define SNDRV_PCM_SUBFMTBIT_MSBITS_24 (1U << SNDRV_PCM_SUBFORMAT_MSBITS_24) struct snd_pcm_file { struct snd_pcm_substream *substream; From cfea0fbbdb28566be414c1db2496d807ef5daa74 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 12 Aug 2026 08:04:36 +0200 Subject: [PATCH 509/612] ALSA: control: Drop __force casts Now that the bitwise parameter definitions are gone for control parameters, we don't have to cast with ugly __force prefix. Simply drop those superfluous casts. Reviewed-by: Cezary Rojewski Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260812060557.80445-5-tiwai@suse.de --- sound/core/control_compat.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sound/core/control_compat.c b/sound/core/control_compat.c index f14d9f5e94be47..87f7ad5ab73911 100644 --- a/sound/core/control_compat.c +++ b/sound/core/control_compat.c @@ -226,8 +226,8 @@ static int copy_ctl_value_from_user(struct snd_card *card, if (type < 0) return type; - if (type == (__force int)SNDRV_CTL_ELEM_TYPE_BOOLEAN || - type == (__force int)SNDRV_CTL_ELEM_TYPE_INTEGER) { + if (type == SNDRV_CTL_ELEM_TYPE_BOOLEAN || + type == SNDRV_CTL_ELEM_TYPE_INTEGER) { for (i = 0; i < count; i++) { s32 __user *intp = valuep; int val; @@ -236,7 +236,7 @@ static int copy_ctl_value_from_user(struct snd_card *card, data->value.integer.value[i] = val; } } else { - size = get_elem_size((__force snd_ctl_elem_type_t)type, count); + size = get_elem_size(type, count); if (size < 0) { dev_err(card->dev, "snd_ioctl32_ctl_elem_value: unknown type %d\n", type); return -EINVAL; @@ -259,8 +259,8 @@ static int copy_ctl_value_to_user(void __user *userdata, struct snd_ctl_elem_value32 __user *data32 = userdata; int i, size; - if (type == (__force int)SNDRV_CTL_ELEM_TYPE_BOOLEAN || - type == (__force int)SNDRV_CTL_ELEM_TYPE_INTEGER) { + if (type == SNDRV_CTL_ELEM_TYPE_BOOLEAN || + type == SNDRV_CTL_ELEM_TYPE_INTEGER) { for (i = 0; i < count; i++) { s32 __user *intp = valuep; int val; @@ -269,7 +269,7 @@ static int copy_ctl_value_to_user(void __user *userdata, return -EFAULT; } } else { - size = get_elem_size((__force snd_ctl_elem_type_t)type, count); + size = get_elem_size(type, count); if (copy_to_user(valuep, data->value.bytes.data, size)) return -EFAULT; } From 01edf81ac8577278ecbd9371078bc85f0baa65ac Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 12 Aug 2026 08:04:37 +0200 Subject: [PATCH 510/612] ALSA: oss: Drop __force casts Now that the bitwise parameter definitions are gone for PCM parameters, we don't have to cast with ugly __force prefix. Simply drop those superfluous casts. Reviewed-by: Cezary Rojewski Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260812060557.80445-6-tiwai@suse.de --- sound/core/oss/pcm_oss.c | 16 ++++++++-------- sound/core/oss/pcm_plugin.c | 20 ++++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/sound/core/oss/pcm_oss.c b/sound/core/oss/pcm_oss.c index 0924f1ff1ae798..bd19328dca9e31 100644 --- a/sound/core/oss/pcm_oss.c +++ b/sound/core/oss/pcm_oss.c @@ -879,11 +879,11 @@ static int snd_pcm_oss_change_params_locked(struct snd_pcm_substream *substream) _snd_pcm_hw_param_min(sparams, SNDRV_PCM_HW_PARAM_PERIODS, 2, 0); snd_mask_none(&mask); if (atomic_read(&substream->mmap_count)) - snd_mask_set(&mask, (__force int)SNDRV_PCM_ACCESS_MMAP_INTERLEAVED); + snd_mask_set(&mask, SNDRV_PCM_ACCESS_MMAP_INTERLEAVED); else { - snd_mask_set(&mask, (__force int)SNDRV_PCM_ACCESS_RW_INTERLEAVED); + snd_mask_set(&mask, SNDRV_PCM_ACCESS_RW_INTERLEAVED); if (!direct) - snd_mask_set(&mask, (__force int)SNDRV_PCM_ACCESS_RW_NONINTERLEAVED); + snd_mask_set(&mask, SNDRV_PCM_ACCESS_RW_NONINTERLEAVED); } err = snd_pcm_hw_param_mask(substream, sparams, SNDRV_PCM_HW_PARAM_ACCESS, &mask); if (err < 0) { @@ -909,7 +909,7 @@ static int snd_pcm_oss_change_params_locked(struct snd_pcm_substream *substream) else sformat = snd_pcm_plug_slave_format(format, sformat_mask); - if ((__force int)sformat < 0 || + if (sformat < 0 || !snd_mask_test_format(sformat_mask, sformat)) { pcm_for_each_format(sformat) { if (snd_mask_test_format(sformat_mask, sformat) && @@ -921,7 +921,7 @@ static int snd_pcm_oss_change_params_locked(struct snd_pcm_substream *substream) goto failure; } format_found: - err = _snd_pcm_hw_param_set(sparams, SNDRV_PCM_HW_PARAM_FORMAT, (__force int)sformat, 0); + err = _snd_pcm_hw_param_set(sparams, SNDRV_PCM_HW_PARAM_FORMAT, sformat, 0); if (err < 0) goto failure; @@ -930,9 +930,9 @@ static int snd_pcm_oss_change_params_locked(struct snd_pcm_substream *substream) } else { _snd_pcm_hw_params_any(params); _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_ACCESS, - (__force int)SNDRV_PCM_ACCESS_RW_INTERLEAVED, 0); + SNDRV_PCM_ACCESS_RW_INTERLEAVED, 0); _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_FORMAT, - (__force int)snd_pcm_oss_format_from(runtime->oss.format), 0); + snd_pcm_oss_format_from(runtime->oss.format), 0); _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_CHANNELS, runtime->oss.channels, 0); _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_RATE, @@ -1875,7 +1875,7 @@ static int snd_pcm_oss_get_formats(struct snd_pcm_oss_file *pcm_oss_file) format_mask = hw_param_mask_c(params, SNDRV_PCM_HW_PARAM_FORMAT); for (fmt = 0; fmt < 32; ++fmt) { if (snd_mask_test(format_mask, fmt)) { - int f = snd_pcm_oss_format_to((__force snd_pcm_format_t)fmt); + int f = snd_pcm_oss_format_to(fmt); if (f >= 0) formats |= f; } diff --git a/sound/core/oss/pcm_plugin.c b/sound/core/oss/pcm_plugin.c index 5f4d6945a7df64..acf5ca663ba8ca 100644 --- a/sound/core/oss/pcm_plugin.c +++ b/sound/core/oss/pcm_plugin.c @@ -272,13 +272,13 @@ static int snd_pcm_plug_formats(const struct snd_mask *mask, SNDRV_PCM_FMTBIT_U24_3BE | SNDRV_PCM_FMTBIT_S24_3BE | SNDRV_PCM_FMTBIT_U32_LE | SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_BE | SNDRV_PCM_FMTBIT_S32_BE); - snd_mask_set(&formats, (__force int)SNDRV_PCM_FORMAT_MU_LAW); + snd_mask_set(&formats, SNDRV_PCM_FORMAT_MU_LAW); if (formats.bits[0] & lower_32_bits(linfmts)) formats.bits[0] |= lower_32_bits(linfmts); if (formats.bits[1] & upper_32_bits(linfmts)) formats.bits[1] |= upper_32_bits(linfmts); - return snd_mask_test(&formats, (__force int)format); + return snd_mask_test(&formats, format); } static const snd_pcm_format_t preferred_formats[] = { @@ -307,20 +307,20 @@ snd_pcm_format_t snd_pcm_plug_slave_format(snd_pcm_format_t format, { int i; - if (snd_mask_test(format_mask, (__force int)format)) + if (snd_mask_test(format_mask, format)) return format; if (!snd_pcm_plug_formats(format_mask, format)) - return (__force snd_pcm_format_t)-EINVAL; + return -EINVAL; if (snd_pcm_format_linear(format)) { unsigned int width = snd_pcm_format_width(format); int unsignd = snd_pcm_format_unsigned(format) > 0; int big = snd_pcm_format_big_endian(format) > 0; unsigned int badness, best = -1; - snd_pcm_format_t best_format = (__force snd_pcm_format_t)-1; + snd_pcm_format_t best_format = -1; for (i = 0; i < ARRAY_SIZE(preferred_formats); i++) { snd_pcm_format_t f = preferred_formats[i]; unsigned int w; - if (!snd_mask_test(format_mask, (__force int)f)) + if (!snd_mask_test(format_mask, f)) continue; w = snd_pcm_format_width(f); if (w >= width) @@ -334,21 +334,21 @@ snd_pcm_format_t snd_pcm_plug_slave_format(snd_pcm_format_t format, best = badness; } } - if ((__force int)best_format >= 0) + if (best_format >= 0) return best_format; else - return (__force snd_pcm_format_t)-EINVAL; + return -EINVAL; } else { switch (format) { case SNDRV_PCM_FORMAT_MU_LAW: for (i = 0; i < ARRAY_SIZE(preferred_formats); ++i) { snd_pcm_format_t format1 = preferred_formats[i]; - if (snd_mask_test(format_mask, (__force int)format1)) + if (snd_mask_test(format_mask, format1)) return format1; } fallthrough; default: - return (__force snd_pcm_format_t)-EINVAL; + return -EINVAL; } } } From a47bc1c7c473af0f5ca80a79b255c7f9ebf74898 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 12 Aug 2026 08:04:38 +0200 Subject: [PATCH 511/612] ALSA: kunit: Drop __force casts Now that the bitwise parameter definitions are gone for PCM parameters, we don't have to cast with ugly __force prefix. Simply drop those superfluous casts Reviewed-by: Cezary Rojewski Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260812060557.80445-7-tiwai@suse.de --- sound/core/sound_kunit.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/core/sound_kunit.c b/sound/core/sound_kunit.c index 84e337ecbddd0a..0376112cc67e0d 100644 --- a/sound/core/sound_kunit.c +++ b/sound/core/sound_kunit.c @@ -17,8 +17,8 @@ .name = #fmt, \ } -#define WRONG_FORMAT_1 (__force snd_pcm_format_t)((__force int)SNDRV_PCM_FORMAT_LAST + 1) -#define WRONG_FORMAT_2 (__force snd_pcm_format_t)-1 +#define WRONG_FORMAT_1 (SNDRV_PCM_FORMAT_LAST + 1) +#define WRONG_FORMAT_2 -1 #define VALID_NAME "ValidName" #define NAME_W_SPEC_CHARS "In%v@1id name" From 93aa34ef917738fbb1eb649881152ed521bf8855 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 12 Aug 2026 08:04:39 +0200 Subject: [PATCH 512/612] ALSA: aloop: Drop __force casts Now that the bitwise parameter definitions are gone for PCM parameters, we don't have to cast with ugly __force prefix. Simply drop those superfluous casts. Reviewed-by: Cezary Rojewski Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260812060557.80445-8-tiwai@suse.de --- sound/drivers/aloop.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c index 92ef821ddbeb48..4e3ea23ca9135b 100644 --- a/sound/drivers/aloop.c +++ b/sound/drivers/aloop.c @@ -1603,7 +1603,7 @@ static int loopback_format_info(struct snd_kcontrol *kcontrol, uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; uinfo->count = 1; uinfo->value.integer.min = 0; - uinfo->value.integer.max = (__force int)SNDRV_PCM_FORMAT_LAST; + uinfo->value.integer.max = SNDRV_PCM_FORMAT_LAST; uinfo->value.integer.step = 1; return 0; } @@ -1614,7 +1614,7 @@ static int loopback_format_get(struct snd_kcontrol *kcontrol, struct loopback *loopback = snd_kcontrol_chip(kcontrol); ucontrol->value.integer.value[0] = - (__force int)loopback->setup[kcontrol->id.subdevice] + loopback->setup[kcontrol->id.subdevice] [kcontrol->id.device].format; return 0; } From ba4239b6d8e1b4e3b702b9f69d39806b1ca2a4a9 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 12 Aug 2026 08:04:40 +0200 Subject: [PATCH 513/612] ALSA: hda: Drop __force casts Now that the bitwise parameter definitions are gone for PCM and control parameters, we don't have to cast with ugly __force prefix. Simply drop those superfluous casts. Reviewed-by: Cezary Rojewski Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260812060557.80445-9-tiwai@suse.de --- sound/hda/common/codec.c | 2 +- sound/hda/core/device.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/hda/common/codec.c b/sound/hda/common/codec.c index 641083c2376f8b..7d17d773cfbf3a 100644 --- a/sound/hda/common/codec.c +++ b/sound/hda/common/codec.c @@ -3379,7 +3379,7 @@ int snd_hda_add_new_ctls(struct hda_codec *codec, for (; knew->name; knew++) { struct snd_kcontrol *kctl; int addr = 0, idx = 0; - if (knew->iface == (__force snd_ctl_elem_iface_t)-1) + if (knew->iface == -1) continue; /* skip this codec private value */ for (;;) { kctl = snd_ctl_new1(knew, codec); diff --git a/sound/hda/core/device.c b/sound/hda/core/device.c index 160c8d0453b0e4..832494035f0a86 100644 --- a/sound/hda/core/device.c +++ b/sound/hda/core/device.c @@ -765,7 +765,7 @@ unsigned int snd_hdac_stream_format_bits(snd_pcm_format_t format, snd_pcm_subfor params_set_format(¶ms, snd_hdac_format_normalize(format)); snd_mask_set(hw_param_mask(¶ms, SNDRV_PCM_HW_PARAM_SUBFORMAT), - (__force unsigned int)subformat); + subformat); bits = snd_pcm_hw_params_bits(¶ms); if (maxbits) From 68edc3ded96bc3e1b8df639b5dc154e4a0e9e496 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 12 Aug 2026 08:04:41 +0200 Subject: [PATCH 514/612] ALSA: asihpi: Drop __force cast Now that the bitwise parameter definitions are gone for PCM parameters, we don't have to cast with ugly __force prefix. Simply drop a superfluous cast. Reviewed-by: Cezary Rojewski Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260812060557.80445-10-tiwai@suse.de --- sound/pci/asihpi/asihpi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/pci/asihpi/asihpi.c b/sound/pci/asihpi/asihpi.c index 4dbc79899c0919..b78e96caef9d08 100644 --- a/sound/pci/asihpi/asihpi.c +++ b/sound/pci/asihpi/asihpi.c @@ -280,7 +280,7 @@ static void print_hwparams(struct snd_pcm_substream *substream, snd_pcm_format_width(params_format(p)) / 8); } -#define INVALID_FORMAT (__force snd_pcm_format_t)(-1) +#define INVALID_FORMAT -1 static const snd_pcm_format_t hpi_to_alsa_formats[] = { INVALID_FORMAT, /* INVALID */ From 920b514c4328fb6deaab0c6beeb126fe3dc03fc5 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 12 Aug 2026 08:04:42 +0200 Subject: [PATCH 515/612] ALSA: emu10k1: Drop __force casts Now that the bitwise parameter definitions are gone for control parameters, we don't have to cast with ugly __force prefix. Simply drop those superfluous casts. Reviewed-by: Cezary Rojewski Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260812060557.80445-11-tiwai@suse.de --- sound/pci/emu10k1/emufx.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c index 08e0556bf16103..49cabb2eb2b799 100644 --- a/sound/pci/emu10k1/emufx.c +++ b/sound/pci/emu10k1/emufx.c @@ -990,7 +990,7 @@ static int snd_emu10k1_list_controls(struct snd_emu10k1 *emu, i < icode->gpr_list_control_count) { memset(gctl, 0, sizeof(*gctl)); id = &ctl->kcontrol->id; - gctl->id.iface = (__force int)id->iface; + gctl->id.iface = id->iface; strscpy(gctl->id.name, id->name, sizeof(gctl->id.name)); gctl->id.index = id->index; gctl->id.device = id->device; @@ -1156,7 +1156,7 @@ static void snd_emu10k1_init_mono_control2(struct snd_emu10k1_fx8010_control_gpr *ctl, const char *name, int gpr, int defval, int defval_hr) { - ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; + ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; strscpy(ctl->id.name, name); ctl->vcount = ctl->count = 1; if (high_res_gpr_volume) { @@ -1180,7 +1180,7 @@ static void snd_emu10k1_init_stereo_control2(struct snd_emu10k1_fx8010_control_gpr *ctl, const char *name, int gpr, int defval, int defval_hr) { - ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; + ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; strscpy(ctl->id.name, name); ctl->vcount = ctl->count = 2; if (high_res_gpr_volume) { @@ -1205,7 +1205,7 @@ static void snd_emu10k1_init_mono_onoff_control(struct snd_emu10k1_fx8010_control_gpr *ctl, const char *name, int gpr, int defval) { - ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; + ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; strscpy(ctl->id.name, name); ctl->vcount = ctl->count = 1; ctl->gpr[0] = gpr + 0; ctl->value[0] = defval; @@ -1218,7 +1218,7 @@ static void snd_emu10k1_init_stereo_onoff_control(struct snd_emu10k1_fx8010_control_gpr *ctl, const char *name, int gpr, int defval) { - ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; + ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; strscpy(ctl->id.name, name); ctl->vcount = ctl->count = 2; ctl->gpr[0] = gpr + 0; ctl->value[0] = defval; @@ -1542,7 +1542,7 @@ static int _snd_emu10k1_audigy_init_efx(struct snd_emu10k1 *emu) * Process tone control */ ctl = &controls[nctl + 0]; - ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; + ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; strscpy(ctl->id.name, "Tone Control - Bass"); ctl->vcount = 2; ctl->count = 10; @@ -1551,7 +1551,7 @@ static int _snd_emu10k1_audigy_init_efx(struct snd_emu10k1 *emu) ctl->value[0] = ctl->value[1] = 20; ctl->translation = EMU10K1_GPR_TRANSLATION_BASS; ctl = &controls[nctl + 1]; - ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; + ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; strscpy(ctl->id.name, "Tone Control - Treble"); ctl->vcount = 2; ctl->count = 10; @@ -2137,7 +2137,7 @@ static int _snd_emu10k1_init_efx(struct snd_emu10k1 *emu) * Process tone control */ ctl = &controls[i + 0]; - ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; + ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; strscpy(ctl->id.name, "Tone Control - Bass"); ctl->vcount = 2; ctl->count = 10; @@ -2147,7 +2147,7 @@ static int _snd_emu10k1_init_efx(struct snd_emu10k1 *emu) ctl->tlv = snd_emu10k1_bass_treble_db_scale; ctl->translation = EMU10K1_GPR_TRANSLATION_BASS; ctl = &controls[i + 1]; - ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; + ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; strscpy(ctl->id.name, "Tone Control - Treble"); ctl->vcount = 2; ctl->count = 10; From fde65373cb861edf560af50782b08a52b39e7585 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 12 Aug 2026 08:04:43 +0200 Subject: [PATCH 516/612] ASoC: fsl: Drop __force casts Now that the bitwise parameter definitions are gone for PCM parameters, we don't have to cast with ugly __force prefix. Simply drop those superfluous casts. Reviewed-by: Shengjiu Wang Acked-by: Mark Brown Reviewed-by: Cezary Rojewski Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260812060557.80445-12-tiwai@suse.de --- sound/soc/fsl/fsl-asoc-card.c | 2 +- sound/soc/fsl/fsl_asrc.c | 2 +- sound/soc/fsl/fsl_asrc_m2m.c | 10 +++++----- sound/soc/fsl/fsl_easrc.c | 2 +- sound/soc/fsl/fsl_qmc_audio.c | 8 ++++---- sound/soc/fsl/imx-card.c | 4 ++-- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/sound/soc/fsl/fsl-asoc-card.c b/sound/soc/fsl/fsl-asoc-card.c index 709543308fe9e9..9dc46ab1d3e12e 100644 --- a/sound/soc/fsl/fsl-asoc-card.c +++ b/sound/soc/fsl/fsl-asoc-card.c @@ -1118,7 +1118,7 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) } ret = of_property_read_u32(asrc_np, "fsl,asrc-format", &asrc_fmt); - priv->asrc_format = (__force snd_pcm_format_t)asrc_fmt; + priv->asrc_format = asrc_fmt; if (ret) { /* Fallback to old binding; translate to asrc_format */ ret = of_property_read_u32(asrc_np, "fsl,asrc-width", diff --git a/sound/soc/fsl/fsl_asrc.c b/sound/soc/fsl/fsl_asrc.c index f23c210322872c..11748c65d5ccc6 100644 --- a/sound/soc/fsl/fsl_asrc.c +++ b/sound/soc/fsl/fsl_asrc.c @@ -1358,7 +1358,7 @@ static int fsl_asrc_probe(struct platform_device *pdev) } ret = of_property_read_u32(np, "fsl,asrc-format", &asrc_fmt); - asrc->asrc_format = (__force snd_pcm_format_t)asrc_fmt; + asrc->asrc_format = asrc_fmt; if (ret) { ret = of_property_read_u32(np, "fsl,asrc-width", &width); if (ret) { diff --git a/sound/soc/fsl/fsl_asrc_m2m.c b/sound/soc/fsl/fsl_asrc_m2m.c index 7d39378c06228d..4bc40f328f587a 100644 --- a/sound/soc/fsl/fsl_asrc_m2m.c +++ b/sound/soc/fsl/fsl_asrc_m2m.c @@ -367,13 +367,13 @@ static int fsl_asrc_m2m_comp_set_params(struct snd_compr_stream *stream, if (ret) return -EINVAL; - if (pcm_format_to_bits((__force snd_pcm_format_t)params->codec.format) & cap.fmt_in) - pair->sample_format[IN] = (__force snd_pcm_format_t)params->codec.format; + if (pcm_format_to_bits(params->codec.format) & cap.fmt_in) + pair->sample_format[IN] = params->codec.format; else return -EINVAL; - if (pcm_format_to_bits((__force snd_pcm_format_t)params->codec.pcm_format) & cap.fmt_out) - pair->sample_format[OUT] = (__force snd_pcm_format_t)params->codec.pcm_format; + if (pcm_format_to_bits(params->codec.pcm_format) & cap.fmt_out) + pair->sample_format[OUT] = params->codec.pcm_format; else return -EINVAL; @@ -600,7 +600,7 @@ static int fsl_asrc_m2m_fill_codec_caps(struct fsl_asrc *asrc, cap.rate_in, cap.rate_in_count * sizeof(__u32)); codec->descriptor[j].num_sample_rates = cap.rate_in_count; - codec->descriptor[j].formats = (__force __u32)k; + codec->descriptor[j].formats = k; codec->descriptor[j].pcm_formats = cap.fmt_out; codec->descriptor[j].src.out_sample_rate_min = cap.rate_out[0]; codec->descriptor[j].src.out_sample_rate_max = diff --git a/sound/soc/fsl/fsl_easrc.c b/sound/soc/fsl/fsl_easrc.c index 8535ef844ce031..79eb2391058d2f 100644 --- a/sound/soc/fsl/fsl_easrc.c +++ b/sound/soc/fsl/fsl_easrc.c @@ -2227,7 +2227,7 @@ static int fsl_easrc_probe(struct platform_device *pdev) } ret = of_property_read_u32(np, "fsl,asrc-format", &asrc_fmt); - easrc->asrc_format = (__force snd_pcm_format_t)asrc_fmt; + easrc->asrc_format = asrc_fmt; if (ret) { dev_err(dev, "failed to asrc format\n"); return ret; diff --git a/sound/soc/fsl/fsl_qmc_audio.c b/sound/soc/fsl/fsl_qmc_audio.c index d0f644573f499d..f27934cf49da0f 100644 --- a/sound/soc/fsl/fsl_qmc_audio.c +++ b/sound/soc/fsl/fsl_qmc_audio.c @@ -503,8 +503,8 @@ static int qmc_dai_constraints_interleaved(struct snd_pcm_substream *substream, return ret; } - access = 1ULL << (__force int)SNDRV_PCM_ACCESS_MMAP_INTERLEAVED | - 1ULL << (__force int)SNDRV_PCM_ACCESS_RW_INTERLEAVED; + access = 1ULL << SNDRV_PCM_ACCESS_MMAP_INTERLEAVED | + 1ULL << SNDRV_PCM_ACCESS_RW_INTERLEAVED; ret = snd_pcm_hw_constraint_mask64(substream->runtime, SNDRV_PCM_HW_PARAM_ACCESS, access); if (ret) { @@ -532,8 +532,8 @@ static int qmc_dai_constraints_noninterleaved(struct snd_pcm_substream *substrea return ret; } - access = 1ULL << (__force int)SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED | - 1ULL << (__force int)SNDRV_PCM_ACCESS_RW_NONINTERLEAVED; + access = 1ULL << SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED | + 1ULL << SNDRV_PCM_ACCESS_RW_NONINTERLEAVED; ret = snd_pcm_hw_constraint_mask64(substream->runtime, SNDRV_PCM_HW_PARAM_ACCESS, access); if (ret) { diff --git a/sound/soc/fsl/imx-card.c b/sound/soc/fsl/imx-card.c index 43438af1e1c680..e3cb1438e837ba 100644 --- a/sound/soc/fsl/imx-card.c +++ b/sound/soc/fsl/imx-card.c @@ -531,7 +531,7 @@ static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); snd_mask_none(mask); - snd_mask_set(mask, (__force unsigned int)data->asrc_format); + snd_mask_set(mask, data->asrc_format); return 0; } @@ -684,7 +684,7 @@ static int imx_card_parse_of(struct imx_card_data *data) } ret = of_property_read_u32(args.np, "fsl,asrc-format", &asrc_fmt); - data->asrc_format = (__force snd_pcm_format_t)asrc_fmt; + data->asrc_format = asrc_fmt; if (ret) { /* Fallback to old binding; translate to asrc_format */ ret = of_property_read_u32(args.np, "fsl,asrc-width", &width); From 4effed8fe6d592aa0fb33ea913aaa437d902db85 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 12 Aug 2026 08:04:44 +0200 Subject: [PATCH 517/612] ASoC: Intel: avs: Drop __force cast Now that the bitwise parameter definitions are gone for PCM parameters, we don't have to cast with ugly __force prefix. Simply drop a superfluous cast. Acked-by: Cezary Rojewski Acked-by: Mark Brown Reviewed-by: Cezary Rojewski Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260812060557.80445-13-tiwai@suse.de --- sound/soc/intel/avs/probes.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/intel/avs/probes.c b/sound/soc/intel/avs/probes.c index 099119ad28b3cb..74096236984a00 100644 --- a/sound/soc/intel/avs/probes.c +++ b/sound/soc/intel/avs/probes.c @@ -144,7 +144,7 @@ static int avs_probe_compr_set_params(struct snd_compr_stream *cstream, ret = snd_compr_malloc_pages(cstream, rtd->buffer_size); if (ret < 0) return ret; - bps = snd_pcm_format_physical_width((__force snd_pcm_format_t)params->codec.format); + bps = snd_pcm_format_physical_width(params->codec.format); if (bps < 0) return bps; format_val = snd_hdac_stream_format(params->codec.ch_out, bps, params->codec.sample_rate); From 9543539d7ce26e246f15f9744372aaf27b0207b4 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 12 Aug 2026 08:04:45 +0200 Subject: [PATCH 518/612] ASoC: mediatek: Drop __force casts Now that the bitwise parameter definitions are gone for PCM parameters, we don't have to cast with ugly __force prefix. Simply drop those superfluous casts. Acked-by: Mark Brown Reviewed-by: Cezary Rojewski Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260812060557.80445-14-tiwai@suse.de --- sound/soc/mediatek/mt8183/mt8183-da7219-max98357.c | 4 ++-- sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c | 4 ++-- sound/soc/mediatek/mt8186/mt8186-mt6366.c | 2 +- sound/soc/mediatek/mt8188/mt8188-mt6359.c | 2 +- sound/soc/mediatek/mt8189/mt8189-nau8825.c | 2 +- sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c | 2 +- sound/soc/mediatek/mt8195/mt8195-mt6359.c | 4 ++-- sound/soc/mediatek/mt8196/mt8196-nau8825.c | 2 +- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/sound/soc/mediatek/mt8183/mt8183-da7219-max98357.c b/sound/soc/mediatek/mt8183/mt8183-da7219-max98357.c index 983f3b91119a92..aa3d1a588479bb 100644 --- a/sound/soc/mediatek/mt8183/mt8183-da7219-max98357.c +++ b/sound/soc/mediatek/mt8183/mt8183-da7219-max98357.c @@ -172,7 +172,7 @@ static int mt8183_i2s_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, { /* fix BE i2s format to S32_LE, clean param mask first */ snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), - 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); + 0, SNDRV_PCM_FORMAT_LAST); params_set_format(params, SNDRV_PCM_FORMAT_S32_LE); @@ -184,7 +184,7 @@ static int mt8183_rt1015_i2s_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, { /* fix BE i2s format to S24_LE, clean param mask first */ snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), - 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); + 0, SNDRV_PCM_FORMAT_LAST); params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); diff --git a/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c b/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c index 0bc1f11e17aa7a..dea7ad167a4978 100644 --- a/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c +++ b/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c @@ -99,7 +99,7 @@ static int mt8183_i2s_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, /* fix BE i2s format to S32_LE, clean param mask first */ snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), - 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); + 0, SNDRV_PCM_FORMAT_LAST); params_set_format(params, SNDRV_PCM_FORMAT_S32_LE); return 0; @@ -112,7 +112,7 @@ static int mt8183_rt1015_i2s_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, /* fix BE i2s format to S24_LE, clean param mask first */ snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), - 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); + 0, SNDRV_PCM_FORMAT_LAST); params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); return 0; diff --git a/sound/soc/mediatek/mt8186/mt8186-mt6366.c b/sound/soc/mediatek/mt8186/mt8186-mt6366.c index 22123b087c3cae..b68f474b63f420 100644 --- a/sound/soc/mediatek/mt8186/mt8186-mt6366.c +++ b/sound/soc/mediatek/mt8186/mt8186-mt6366.c @@ -387,7 +387,7 @@ static int mt8186_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, /* clean param mask first */ snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), - 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); + 0, SNDRV_PCM_FORMAT_LAST); params_set_format(params, fmt); diff --git a/sound/soc/mediatek/mt8188/mt8188-mt6359.c b/sound/soc/mediatek/mt8188/mt8188-mt6359.c index 55ebac0c3cef2a..75c90d1d165f77 100644 --- a/sound/soc/mediatek/mt8188/mt8188-mt6359.c +++ b/sound/soc/mediatek/mt8188/mt8188-mt6359.c @@ -623,7 +623,7 @@ static int mt8188_dptx_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, { /* fix BE i2s format to 32bit, clean param mask first */ snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), - 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); + 0, SNDRV_PCM_FORMAT_LAST); params_set_format(params, SNDRV_PCM_FORMAT_S32_LE); diff --git a/sound/soc/mediatek/mt8189/mt8189-nau8825.c b/sound/soc/mediatek/mt8189/mt8189-nau8825.c index e849e7a649bc35..5d652d0cf01e25 100644 --- a/sound/soc/mediatek/mt8189/mt8189-nau8825.c +++ b/sound/soc/mediatek/mt8189/mt8189-nau8825.c @@ -174,7 +174,7 @@ static int mt8189_dptx_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, /* fix BE i2s format to 32bit, clean param mask first */ snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), - 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); + 0, SNDRV_PCM_FORMAT_LAST); params_set_format(params, SNDRV_PCM_FORMAT_S32_LE); diff --git a/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c b/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c index 91c57765ab57b2..8af8b0a366d588 100644 --- a/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c +++ b/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c @@ -382,7 +382,7 @@ static int mt8192_i2s_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, { /* fix BE i2s format to S24_LE, clean param mask first */ snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), - 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); + 0, SNDRV_PCM_FORMAT_LAST); params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); diff --git a/sound/soc/mediatek/mt8195/mt8195-mt6359.c b/sound/soc/mediatek/mt8195/mt8195-mt6359.c index 4d62bc654a587b..fc293ca715023d 100644 --- a/sound/soc/mediatek/mt8195/mt8195-mt6359.c +++ b/sound/soc/mediatek/mt8195/mt8195-mt6359.c @@ -387,7 +387,7 @@ static int mt8195_dptx_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, { /* fix BE i2s format to S24_LE, clean param mask first */ snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), - 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); + 0, SNDRV_PCM_FORMAT_LAST); params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); @@ -650,7 +650,7 @@ static int mt8195_etdm_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, { /* fix BE i2s format to S24_LE, clean param mask first */ snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), - 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); + 0, SNDRV_PCM_FORMAT_LAST); params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); diff --git a/sound/soc/mediatek/mt8196/mt8196-nau8825.c b/sound/soc/mediatek/mt8196/mt8196-nau8825.c index c9424786c53dfe..1d1dad86365b67 100644 --- a/sound/soc/mediatek/mt8196/mt8196-nau8825.c +++ b/sound/soc/mediatek/mt8196/mt8196-nau8825.c @@ -180,7 +180,7 @@ static int mt8196_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, /* fix BE i2s format to 32bit, clean param mask first */ snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), - 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); + 0, SNDRV_PCM_FORMAT_LAST); params_set_format(params, SNDRV_PCM_FORMAT_S32_LE); return 0; From 0c4ef23e66a715019e27e679502e2be3bc358f81 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 12 Aug 2026 08:04:46 +0200 Subject: [PATCH 519/612] ASoC: meson: Drop __force cast Now that the bitwise parameter definitions are gone for PCM parameters, we don't have to cast with ugly __force prefix. Simply drop a superfluous cast. Acked-by: Mark Brown Reviewed-by: Cezary Rojewski Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260812060557.80445-15-tiwai@suse.de --- sound/soc/meson/meson-codec-glue.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/meson/meson-codec-glue.c b/sound/soc/meson/meson-codec-glue.c index 2ff6066e1b6ccd..8773bf06e15438 100644 --- a/sound/soc/meson/meson-codec-glue.c +++ b/sound/soc/meson/meson-codec-glue.c @@ -74,7 +74,7 @@ int meson_codec_glue_input_hw_params(struct snd_pcm_substream *substream, data->params.rates = snd_pcm_rate_to_rate_bit(params_rate(params)); data->params.rate_min = params_rate(params); data->params.rate_max = params_rate(params); - data->params.formats = 1ULL << (__force int) params_format(params); + data->params.formats = 1ULL << params_format(params); data->params.channels_min = params_channels(params); data->params.channels_max = params_channels(params); data->params.sig_bits = dai->driver->playback.sig_bits; From 931f4a1223e154fdea63071074cbaa633ebf394b Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 6 Aug 2026 12:21:23 +0700 Subject: [PATCH 520/612] ASoC: rockchip: rk3288_hdmi_analog: Drop redundant probe error messages Remove the probe error messages to avoid duplicate error reporting, since the error is already reported by the called functions. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260806052136.21034-2-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/rockchip/rk3288_hdmi_analog.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/sound/soc/rockchip/rk3288_hdmi_analog.c b/sound/soc/rockchip/rk3288_hdmi_analog.c index cf642a23c38ad0..d88d5ccfcdb239 100644 --- a/sound/soc/rockchip/rk3288_hdmi_analog.c +++ b/sound/soc/rockchip/rk3288_hdmi_analog.c @@ -185,10 +185,8 @@ static int snd_rk_mc_probe(struct platform_device *pdev) gpiod_set_consumer_name(machine->gpio_hp_en, "hp_en"); ret = snd_soc_of_parse_card_name(card, "rockchip,model"); - if (ret) { - dev_err(card->dev, "SoC parse card name failed %d\n", ret); + if (ret) return ret; - } rk_dailink.codecs[0].of_node = of_parse_phandle(np, "rockchip,audio-codec", @@ -223,11 +221,8 @@ static int snd_rk_mc_probe(struct platform_device *pdev) rk_dailink.platforms->of_node = rk_dailink.cpus->of_node; ret = snd_soc_of_parse_audio_routing(card, "rockchip,routing"); - if (ret) { - dev_err(&pdev->dev, - "Unable to parse 'rockchip,routing' property\n"); + if (ret) return ret; - } snd_soc_card_set_drvdata(card, machine); From 06f5854050f56775977f9972b78a21806de001f0 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 6 Aug 2026 12:21:24 +0700 Subject: [PATCH 521/612] ASoC: rockchip: rk3288_hdmi_analog: Use dev_err_probe() for error handling Replace dev_err() with dev_err_probe() to prevent log spam when probe returns -EPROBE_DEFER. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260806052136.21034-3-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/rockchip/rk3288_hdmi_analog.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/sound/soc/rockchip/rk3288_hdmi_analog.c b/sound/soc/rockchip/rk3288_hdmi_analog.c index d88d5ccfcdb239..541163ed56fc3a 100644 --- a/sound/soc/rockchip/rk3288_hdmi_analog.c +++ b/sound/soc/rockchip/rk3288_hdmi_analog.c @@ -205,10 +205,9 @@ static int snd_rk_mc_probe(struct platform_device *pdev) } ret = snd_soc_get_dai_name(&args, &rk_dailink.codecs[0].dai_name); - if (ret) { - dev_err(&pdev->dev, "Unable to get codec_dai_name\n"); - return ret; - } + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Unable to get codec_dai_name\n"); rk_dailink.cpus->of_node = of_parse_phandle(np, "rockchip,i2s-controller", 0); From b5e4b1159743e72564030256949ab37108184ed8 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 6 Aug 2026 12:21:25 +0700 Subject: [PATCH 522/612] ASoC: rockchip: rockchip_i2s: Use dev_err_probe() for error handling Replace dev_err() with dev_err_probe() to prevent log spam when probe returns -EPROBE_DEFER. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260806052136.21034-4-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/rockchip/rockchip_i2s.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/sound/soc/rockchip/rockchip_i2s.c b/sound/soc/rockchip/rockchip_i2s.c index 64c90316fa02d4..3e8d07b3fc1ebf 100644 --- a/sound/soc/rockchip/rockchip_i2s.c +++ b/sound/soc/rockchip/rockchip_i2s.c @@ -767,16 +767,14 @@ static int rockchip_i2s_probe(struct platform_device *pdev) /* try to prepare related clocks */ i2s->hclk = devm_clk_get_enabled(&pdev->dev, "i2s_hclk"); - if (IS_ERR(i2s->hclk)) { - dev_err(&pdev->dev, "Can't retrieve i2s bus clock\n"); - return PTR_ERR(i2s->hclk); - } + if (IS_ERR(i2s->hclk)) + return dev_err_probe(&pdev->dev, PTR_ERR(i2s->hclk), + "Can't retrieve i2s bus clock\n"); i2s->mclk = devm_clk_get(&pdev->dev, "i2s_clk"); - if (IS_ERR(i2s->mclk)) { - dev_err(&pdev->dev, "Can't retrieve i2s master clock\n"); - return PTR_ERR(i2s->mclk); - } + if (IS_ERR(i2s->mclk)) + return dev_err_probe(&pdev->dev, PTR_ERR(i2s->mclk), + "Can't retrieve i2s master clock\n"); regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res); if (IS_ERR(regs)) From c1bf7ae0c87a42cb81608f40aa99535119171915 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 6 Aug 2026 12:21:26 +0700 Subject: [PATCH 523/612] ASoC: rockchip: rockchip_i2s: Drop redundant probe error messages Remove the probe error messages to avoid duplicate error reporting, since the error is already reported by the called functions. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260806052136.21034-5-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/rockchip/rockchip_i2s.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/sound/soc/rockchip/rockchip_i2s.c b/sound/soc/rockchip/rockchip_i2s.c index 3e8d07b3fc1ebf..354430f916f9fe 100644 --- a/sound/soc/rockchip/rockchip_i2s.c +++ b/sound/soc/rockchip/rockchip_i2s.c @@ -829,16 +829,12 @@ static int rockchip_i2s_probe(struct platform_device *pdev) &rockchip_i2s_component, dai, 1); - if (ret) { - dev_err(&pdev->dev, "Could not register DAI\n"); + if (ret) return ret; - } ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); - if (ret) { - dev_err(&pdev->dev, "Could not register PCM\n"); + if (ret) return ret; - } return 0; } From be4f82d15235ad4341c6529ff6cca048c26eb5a4 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 6 Aug 2026 12:21:27 +0700 Subject: [PATCH 524/612] ASoC: rockchip: rockchip_i2s: Propagate -EPROBE_DEFER from devm_pinctrl_get() Return -EPROBE_DEFER from devm_pinctrl_get() instead of ignoring it and continuing probe. This allows the driver to be reprobed once the pinctrl provider becomes available. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260806052136.21034-6-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/rockchip/rockchip_i2s.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sound/soc/rockchip/rockchip_i2s.c b/sound/soc/rockchip/rockchip_i2s.c index 354430f916f9fe..261f36d4c2fd4f 100644 --- a/sound/soc/rockchip/rockchip_i2s.c +++ b/sound/soc/rockchip/rockchip_i2s.c @@ -790,7 +790,11 @@ static int rockchip_i2s_probe(struct platform_device *pdev) i2s->bclk_ratio = 64; i2s->pinctrl = devm_pinctrl_get(&pdev->dev); - if (!IS_ERR(i2s->pinctrl)) { + if (IS_ERR(i2s->pinctrl)) { + if (PTR_ERR(i2s->pinctrl) == -EPROBE_DEFER) + return -EPROBE_DEFER; + dev_dbg(&pdev->dev, "failed to find i2s pinctrl\n"); + } else { i2s->bclk_on = pinctrl_lookup_state(i2s->pinctrl, "bclk_on"); if (!IS_ERR_OR_NULL(i2s->bclk_on)) { i2s->bclk_off = pinctrl_lookup_state(i2s->pinctrl, "bclk_off"); @@ -799,8 +803,6 @@ static int rockchip_i2s_probe(struct platform_device *pdev) return -EINVAL; } } - } else { - dev_dbg(&pdev->dev, "failed to find i2s pinctrl\n"); } i2s_pinctrl_select_bclk_off(i2s); From 334d5ea7582b2e8bfc4cc3917941e88f2204a86d Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 6 Aug 2026 12:21:28 +0700 Subject: [PATCH 525/612] ASoC: rockchip: i2s-tdm: Inline PTR_ERR() in dev_err_probe() Pass PTR_ERR() directly to dev_err_probe() and avoid assigning it to the local variable first. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260806052136.21034-7-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/rockchip/rockchip_i2s_tdm.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sound/soc/rockchip/rockchip_i2s_tdm.c b/sound/soc/rockchip/rockchip_i2s_tdm.c index e6229a325ffe01..c9ad769a407258 100644 --- a/sound/soc/rockchip/rockchip_i2s_tdm.c +++ b/sound/soc/rockchip/rockchip_i2s_tdm.c @@ -1263,16 +1263,14 @@ static int rockchip_i2s_tdm_probe(struct platform_device *pdev) i2s_tdm->tx_reset = devm_reset_control_get_optional_exclusive(&pdev->dev, "tx-m"); if (IS_ERR(i2s_tdm->tx_reset)) { - ret = PTR_ERR(i2s_tdm->tx_reset); - return dev_err_probe(i2s_tdm->dev, ret, + return dev_err_probe(i2s_tdm->dev, PTR_ERR(i2s_tdm->tx_reset), "Error in tx-m reset control\n"); } i2s_tdm->rx_reset = devm_reset_control_get_optional_exclusive(&pdev->dev, "rx-m"); if (IS_ERR(i2s_tdm->rx_reset)) { - ret = PTR_ERR(i2s_tdm->rx_reset); - return dev_err_probe(i2s_tdm->dev, ret, + return dev_err_probe(i2s_tdm->dev, PTR_ERR(i2s_tdm->rx_reset), "Error in rx-m reset control\n"); } From 9fea4805ed95c0ebabd57e02b29cf06a1593b728 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 6 Aug 2026 12:21:29 +0700 Subject: [PATCH 526/612] ASoC: rockchip: i2s-tdm: Drop redundant probe error messages Remove the probe error messages to avoid duplicate error reporting, since the error is already reported by the called functions. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260806052136.21034-8-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/rockchip/rockchip_i2s_tdm.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/sound/soc/rockchip/rockchip_i2s_tdm.c b/sound/soc/rockchip/rockchip_i2s_tdm.c index c9ad769a407258..5ef7f109706d2e 100644 --- a/sound/soc/rockchip/rockchip_i2s_tdm.c +++ b/sound/soc/rockchip/rockchip_i2s_tdm.c @@ -1361,17 +1361,12 @@ static int rockchip_i2s_tdm_probe(struct platform_device *pdev) ret = devm_snd_soc_register_component(&pdev->dev, &rockchip_i2s_tdm_component, i2s_tdm->dai, 1); - - if (ret) { - dev_err(&pdev->dev, "Could not register DAI\n"); + if (ret) goto err_suspend; - } ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); - if (ret) { - dev_err(&pdev->dev, "Could not register PCM\n"); + if (ret) goto err_suspend; - } return 0; From de983524dcbec5eeaae7fde001fbe5ab9c02f28c Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 6 Aug 2026 12:21:30 +0700 Subject: [PATCH 527/612] ASoC: rockchip: rockchip_max98090: Drop redundant probe error messages Remove the probe error messages to avoid duplicate error reporting, since the error is already reported by the called functions. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260806052136.21034-9-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/rockchip/rockchip_max98090.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/sound/soc/rockchip/rockchip_max98090.c b/sound/soc/rockchip/rockchip_max98090.c index 075d0990a12660..426506a8e18e9d 100644 --- a/sound/soc/rockchip/rockchip_max98090.c +++ b/sound/soc/rockchip/rockchip_max98090.c @@ -428,11 +428,8 @@ static int snd_rk_mc_probe(struct platform_device *pdev) /* Parse card name. */ ret = snd_soc_of_parse_card_name(card, "rockchip,model"); - if (ret) { - dev_err(&pdev->dev, - "Soc parse card name failed %d\n", ret); + if (ret) return ret; - } /* register the soc card */ ret = devm_snd_soc_register_card(&pdev->dev, card); From 7d3f7eb890f131e5222a319958576d3271dfe0bd Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 6 Aug 2026 12:21:31 +0700 Subject: [PATCH 528/612] ASoC: rockchip: rockchip_pdm: Drop redundant probe error messages Remove the probe error messages to avoid duplicate error reporting, since the error is already reported by the called functions. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260806052136.21034-10-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/rockchip/rockchip_pdm.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/sound/soc/rockchip/rockchip_pdm.c b/sound/soc/rockchip/rockchip_pdm.c index 115e90d3bbfe05..343fda478901d2 100644 --- a/sound/soc/rockchip/rockchip_pdm.c +++ b/sound/soc/rockchip/rockchip_pdm.c @@ -630,10 +630,8 @@ static int rockchip_pdm_probe(struct platform_device *pdev) &rockchip_pdm_component, &rockchip_pdm_dai, 1); - if (ret) { - dev_err(&pdev->dev, "could not register dai: %d\n", ret); + if (ret) goto err_suspend; - } rockchip_pdm_rxctrl(pdm, 0); @@ -642,10 +640,8 @@ static int rockchip_pdm_probe(struct platform_device *pdev) goto err_suspend; ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); - if (ret) { - dev_err(&pdev->dev, "could not register pcm: %d\n", ret); + if (ret) goto err_suspend; - } return 0; From b300f1313b00b69315d6a016beb4ec227c10a2c3 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 6 Aug 2026 12:21:32 +0700 Subject: [PATCH 529/612] ASoC: rockchip: rockchip_rt5645: Drop redundant probe error messages Remove the probe error messages to avoid duplicate error reporting, since the error is already reported by the called functions. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260806052136.21034-11-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/rockchip/rockchip_rt5645.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/sound/soc/rockchip/rockchip_rt5645.c b/sound/soc/rockchip/rockchip_rt5645.c index 590b64b362f6cd..0432eeabc64e2e 100644 --- a/sound/soc/rockchip/rockchip_rt5645.c +++ b/sound/soc/rockchip/rockchip_rt5645.c @@ -191,11 +191,8 @@ static int snd_rk_mc_probe(struct platform_device *pdev) rk_dailink.platforms->of_node = rk_dailink.cpus->of_node; ret = snd_soc_of_parse_card_name(card, "rockchip,model"); - if (ret) { - dev_err(&pdev->dev, - "Soc parse card name failed %d\n", ret); + if (ret) goto put_cpu_of_node; - } ret = devm_snd_soc_register_card(&pdev->dev, card); if (ret) { From c97bdbf862c6ddd201a58ccf26b3a84519e20431 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 6 Aug 2026 12:21:33 +0700 Subject: [PATCH 530/612] ASoC: rockchip: rockchip_sai: Propagate -EPROBE_DEFER from IRQ lookup Return -EPROBE_DEFER from platform_get_irq_optional() so the driver is re-probed when the interrupt resource becomes available instead of continuing probe without an IRQ. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260806052136.21034-12-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/rockchip/rockchip_sai.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/rockchip/rockchip_sai.c b/sound/soc/rockchip/rockchip_sai.c index 585e89f61f0d9c..522b4f4f6a7aef 100644 --- a/sound/soc/rockchip/rockchip_sai.c +++ b/sound/soc/rockchip/rockchip_sai.c @@ -1428,6 +1428,8 @@ static int rockchip_sai_probe(struct platform_device *pdev) "Failed to initialize regmap\n"); irq = platform_get_irq_optional(pdev, 0); + if (irq == -EPROBE_DEFER) + return irq; if (irq > 0) { ret = devm_request_irq(&pdev->dev, irq, rockchip_sai_isr, IRQF_SHARED, node->name, sai); From 7a2229ef7cdb9e8e7dd15cdbc2974910f54c343d Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 6 Aug 2026 12:21:34 +0700 Subject: [PATCH 531/612] ASoC: rockchip: rockchip_sai: Return the original error code Return the original error code directly and drop the redundant error message since the called function already reports the failure. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260806052136.21034-13-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/rockchip/rockchip_sai.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sound/soc/rockchip/rockchip_sai.c b/sound/soc/rockchip/rockchip_sai.c index 522b4f4f6a7aef..1fdec12a0d4eb5 100644 --- a/sound/soc/rockchip/rockchip_sai.c +++ b/sound/soc/rockchip/rockchip_sai.c @@ -1434,8 +1434,7 @@ static int rockchip_sai_probe(struct platform_device *pdev) ret = devm_request_irq(&pdev->dev, irq, rockchip_sai_isr, IRQF_SHARED, node->name, sai); if (ret) - return dev_err_probe(&pdev->dev, ret, - "Failed to request irq %d\n", irq); + return ret; } else { dev_dbg(&pdev->dev, "Asked for an IRQ but got %d\n", irq); } @@ -1458,7 +1457,7 @@ static int rockchip_sai_probe(struct platform_device *pdev) ret = rockchip_sai_parse_paths(sai, node); if (ret) - return dev_err_probe(&pdev->dev, ret, "Failed to parse paths\n"); + return ret; /* * From here on, all register accesses need to be wrapped in From 26c9632c33f72735e7a9c88707a2014b50e7e281 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 6 Aug 2026 12:21:35 +0700 Subject: [PATCH 532/612] ASoC: rockchip: rockchip_sai: Drop redundant probe error messages Remove the probe error messages to avoid duplicate error reporting, since the error is already reported by the called functions. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260806052136.21034-14-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/rockchip/rockchip_sai.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/sound/soc/rockchip/rockchip_sai.c b/sound/soc/rockchip/rockchip_sai.c index 1fdec12a0d4eb5..30b5e71d09375c 100644 --- a/sound/soc/rockchip/rockchip_sai.c +++ b/sound/soc/rockchip/rockchip_sai.c @@ -1472,18 +1472,14 @@ static int rockchip_sai_probe(struct platform_device *pdev) return dev_err_probe(&pdev->dev, ret, "Failed to resume device\n"); ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); - if (ret) { - dev_err(&pdev->dev, "Failed to register PCM: %d\n", ret); + if (ret) goto err_runtime_suspend; - } ret = devm_snd_soc_register_component(&pdev->dev, &rockchip_sai_component, dai, 1); - if (ret) { - dev_err(&pdev->dev, "Failed to register component: %d\n", ret); + if (ret) goto err_runtime_suspend; - } pm_runtime_use_autosuspend(&pdev->dev); pm_runtime_put(&pdev->dev); From 5784ef446847cc01e43f3bb2ce63d3a72a9301ce Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 6 Aug 2026 12:21:36 +0700 Subject: [PATCH 533/612] ASoC: rockchip: spdif: Return the original error code Return the original error code directly and drop the redundant error message since the called function already reports the failure. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260806052136.21034-15-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/rockchip/rockchip_spdif.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/rockchip/rockchip_spdif.c b/sound/soc/rockchip/rockchip_spdif.c index 7f15bc7f8f35a3..2d53efd5dd2e3b 100644 --- a/sound/soc/rockchip/rockchip_spdif.c +++ b/sound/soc/rockchip/rockchip_spdif.c @@ -396,13 +396,13 @@ static int rk_spdif_probe(struct platform_device *pdev) ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); if (ret) - return dev_err_probe(&pdev->dev, ret, "Could not register PCM\n"); + return ret; ret = devm_snd_soc_register_component(&pdev->dev, &rk_spdif_component, &rk_spdif_dai, 1); if (ret) - return dev_err_probe(&pdev->dev, ret, "Could not register DAI\n"); + return ret; return 0; } From 4c51a83a4fe85dd58a56bed491740ecefcf8a110 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Mon, 10 Aug 2026 18:35:29 +0900 Subject: [PATCH 534/612] ASoC: fsl-asoc-card: Drop mclk management for nau8822 commit 93f12a7568269 ("ASoC: nau8822: add MCLK support") added MCLK handling directly in the nau8822 codec driver. The machine driver no longer needs to acquire and enable the codec MCLK on its behalf. Remove MCLK management in this machine driver that was introduced by commit 1075df4bdeb32 ("ASoC: fsl-asoc-card: add nau8822 support"). This avoids a potential double-enable and removes clock resource management from the machine driver where it does not belong. Additionally, the sound card may be unbound and rebound multiple times during its lifetime. Managing a codec clock resource in the machine driver would require careful cleanup in the card remove path to avoid reference count leaks. Leaving clock management to the codec driver, which has the same lifetime as the codec device, is the correct ownership model. The nau8822 compatible entry, DAI name, and PLL/FLL clock ID configuration are kept unchanged. Assisted-by: VeroCoder:claude-sonnet-4-6 Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260810093834.1511749-2-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl-asoc-card.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/sound/soc/fsl/fsl-asoc-card.c b/sound/soc/fsl/fsl-asoc-card.c index 709543308fe9e9..4c245aaf2847c2 100644 --- a/sound/soc/fsl/fsl-asoc-card.c +++ b/sound/soc/fsl/fsl-asoc-card.c @@ -69,7 +69,6 @@ static const struct snd_pcm_hw_constraint_list cs42888_channel_constraints = { /** * struct codec_priv - CODEC private data - * @mclk: Main clock of the CODEC * @mclk_freq: Clock rate of MCLK * @free_freq: Clock rate of MCLK for hw_free() * @mclk_id: MCLK (or main clock) id for set_sysclk() @@ -80,7 +79,6 @@ static const struct snd_pcm_hw_constraint_list cs42888_channel_constraints = { * to stay within PLL frequency limits */ struct codec_priv { - struct clk *mclk; unsigned long mclk_freq; unsigned long free_freq; u32 mclk_id; @@ -680,9 +678,6 @@ static int fsl_asoc_card_late_probe(struct snd_soc_card *card) dev_err(dev, "failed to set sysclk in %s\n", __func__); return ret; } - - if (!IS_ERR_OR_NULL(codec_priv->mclk)) - clk_prepare_enable(codec_priv->mclk); } return 0; @@ -933,8 +928,6 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) priv->codec_priv[0].fll_id = NAU8822_CLK_PLL; priv->codec_priv[0].pll_id = NAU8822_CLK_PLL; priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; - if (codec_dev[0]) - priv->codec_priv[0].mclk = devm_clk_get(codec_dev[0], NULL); } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8904")) { codec_dai_name[0] = "wm8904-hifi"; priv->codec_priv[0].mclk_id = WM8904_FLL_MCLK; From 76408e27e60f8deb087f7d84e866d7f03ac750f1 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Mon, 10 Aug 2026 18:35:30 +0900 Subject: [PATCH 535/612] ASoC: fsl-asoc-card: Move static compatible data to platform data Replace the large if/else chain of of_device_is_compatible() calls in probe() with a table-driven approach. Each compatible string now has a corresponding static const struct fsl_asoc_card_pdata descriptor stored in the of_device_id .data field. probe() calls of_device_get_match_data() once and reads all per-compatible configuration from the returned pointer: - DAI format - CPU SYSCLK direction and ratio overrides - TDM slot width - Codec DAI name, MCLK id, FLL/PLL ids, PLL S24 ratio - playback_only / capture_only direction restrictions - Default DAPM route table - Excluded PCM format mask (for SAI + WM8960/WM8962) - Optional probe_init callback (SPDIF multi-codec discovery) - Optional codec_init callback (codec-specific post-probe logic) This patch is a pure refactoring, no functional change is intended. Assisted-by: VeroCoder:claude-sonnet-4-6 Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260810093834.1511749-3-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl-asoc-card.c | 423 +++++++++++++++++++++++----------- 1 file changed, 287 insertions(+), 136 deletions(-) diff --git a/sound/soc/fsl/fsl-asoc-card.c b/sound/soc/fsl/fsl-asoc-card.c index 4c245aaf2847c2..10e432c916b8cf 100644 --- a/sound/soc/fsl/fsl-asoc-card.c +++ b/sound/soc/fsl/fsl-asoc-card.c @@ -107,12 +107,58 @@ struct cpu_priv { u32 slot_num; }; +struct fsl_asoc_card_priv; + +/* + * struct fsl_asoc_card_pdata - per-compatible static card description + * @sysclk_dir: initial CPU SYSCLK direction override (0 = leave default IN) + * @sysclk_ratio: SYSCLK ratio on sample rate (0 = not used) + * @slot_width: TDM slot width (0 = not TDM) + * @codec_dai_name: name of the codec DAI + * @codec_mclk_id: MCLK id passed to set_sysclk() for the codec + * @codec_fll_id: FLL id; only valid when has_pll is true + * @codec_pll_id: PLL id; only valid when has_pll is true + * @codec_pll_ratio_s24: PLL output ratio for S24_LE + * @has_pll: codec uses PLL/FLL; codec_fll_id and codec_pll_id are valid + * @dai_fmt: DAI format flags + * @playback_only: restrict card to playback direction + * @capture_only: restrict card to capture direction + * @dapm_routes: DAPM route table override + * @num_dapm_routes: number of entries in dapm_routes + * @exclude_format: PCM format bitmask excluded (for SAI + WM8960/WM8962) + * @codec_init: codec-specific init run after mclk_freq is populated + * @probe_init: optional DT-driven init run at end of probe() (e.g. SPDIF codec discovery) + */ +struct fsl_asoc_card_pdata { + u32 sysclk_dir[2]; + u32 sysclk_ratio[2]; + u32 slot_width; + const char *codec_dai_name; + u32 codec_mclk_id; + int codec_fll_id; + int codec_pll_id; + int codec_pll_ratio_s24; + bool has_pll; + bool playback_only; + bool capture_only; + u32 dai_fmt; + const struct snd_soc_dapm_route *dapm_routes; + int num_dapm_routes; + u64 exclude_format; + int (*codec_init)(struct fsl_asoc_card_priv *priv); + int (*probe_init)(struct device_node *codec_np[], + struct device_node *cpu_np, + const char *codec_dai_name[], + struct fsl_asoc_card_priv *priv); +}; + /** * struct fsl_asoc_card_priv - Freescale Generic ASOC card private data * @dai_link: DAI link structure including normal one and DPCM link * @hp_jack: Headphone Jack structure * @mic_jack: Microphone Jack structure * @pdev: platform device pointer + * @pdata: pointer to the per-compatible card platform data * @codec_priv: CODEC private data * @cpu_priv: CPU private data * @card: ASoC card structure @@ -133,6 +179,7 @@ struct fsl_asoc_card_priv { struct simple_util_jack hp_jack; struct simple_util_jack mic_jack; struct platform_device *pdev; + const struct fsl_asoc_card_pdata *pdata; struct codec_priv codec_priv[2]; struct cpu_priv cpu_priv; struct snd_soc_card card; @@ -607,6 +654,186 @@ static int fsl_asoc_card_spdif_init(struct device_node *codec_np[], return 0; } +static int fsl_asoc_card_cs42888_codec_init(struct fsl_asoc_card_priv *priv) +{ + unsigned long mclk_freq = priv->codec_priv[0].mclk_freq; + + priv->cpu_priv.sysclk_freq[TX] = mclk_freq; + priv->cpu_priv.sysclk_freq[RX] = mclk_freq; + + priv->constraint_channels = &cs42888_channel_constraints; + if (mclk_freq % 12288000 == 0) + priv->constraint_rates = &cs42888_rate_48k_constraints; + else if (mclk_freq % 11289600 == 0) + priv->constraint_rates = &cs42888_rate_44k_constraints; + else + dev_warn(&priv->pdev->dev, + "Unknown MCLK frequency %lu, no rate constraints\n", + mclk_freq); + + return 0; +} + +static int fsl_asoc_card_wm8958_codec_init(struct fsl_asoc_card_priv *priv) +{ + priv->codec_priv[0].free_freq = priv->codec_priv[0].mclk_freq; + return 0; +} + +static const struct fsl_asoc_card_pdata fsl_asoc_cs42888_pdata = { + .codec_dai_name = "cs42888", + .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBC_CFC, + .sysclk_dir = { SND_SOC_CLOCK_OUT, SND_SOC_CLOCK_OUT }, + .slot_width = 32, + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), + .codec_init = fsl_asoc_card_cs42888_codec_init, +}; + +static const struct fsl_asoc_card_pdata fsl_asoc_cs427x_pdata = { + .codec_dai_name = "cs4271-hifi", + .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, + .codec_mclk_id = CS427x_SYSCLK_MCLK, + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), +}; + +static const struct fsl_asoc_card_pdata fsl_asoc_sgtl5000_pdata = { + .codec_dai_name = "sgtl5000", + .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, + .codec_mclk_id = SGTL5000_SYSCLK, + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), +}; + +static const struct fsl_asoc_card_pdata fsl_asoc_tlv320aic32x4_pdata = { + .codec_dai_name = "tlv320aic32x4-hifi", + .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), +}; + +static const struct fsl_asoc_card_pdata fsl_asoc_tlv320aic31xx_pdata = { + .codec_dai_name = "tlv320dac31xx-hifi", + .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBC_CFC, + .sysclk_dir = { SND_SOC_CLOCK_OUT, SND_SOC_CLOCK_OUT }, + .playback_only = true, + .dapm_routes = audio_map_tx, + .num_dapm_routes = ARRAY_SIZE(audio_map_tx), +}; + +static const struct fsl_asoc_card_pdata fsl_asoc_wm8962_pdata = { + .codec_dai_name = "wm8962", + .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, + .codec_mclk_id = WM8962_SYSCLK_MCLK, + .has_pll = true, + .codec_fll_id = WM8962_SYSCLK_FLL, + .codec_pll_id = WM8962_FLL, + /* + * WM8962 has same BCLK generation limitations as WM8960. + * See WM8960 section for detailed explanation. + */ + .exclude_format = SNDRV_PCM_FMTBIT_S20_3LE, + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), +}; + +static const struct fsl_asoc_card_pdata fsl_asoc_wm8960_pdata = { + .codec_dai_name = "wm8960-hifi", + .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, + .has_pll = true, + .codec_fll_id = WM8960_SYSCLK_AUTO, + .codec_pll_id = WM8960_SYSCLK_AUTO, + /* + * WM8960 in master mode cannot generate exact 1.92 MHz BCLK + * required for S20_3LE (48kHz x 2ch x 20bit). Closest available + * is 2.048 MHz (SYSCLK/6), which causes right channel corruption. + * + * In SAI master mode, SAI derive BCLK from MCLK using integer + * dividers only. S20_3LE requires non-integer divider ratios + * with standard MCLK frequencies. For example, 48kHz stereo + * needs 1.920 MHz BCLK, which requires a divider of 6.4 from + * 12.288 MHz MCLK (not an integer). + */ + .exclude_format = SNDRV_PCM_FMTBIT_S20_3LE, + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), +}; + +static const struct fsl_asoc_card_pdata fsl_asoc_ac97_pdata = { + .codec_dai_name = "ac97-hifi", + .dai_fmt = SND_SOC_DAIFMT_AC97, + .dapm_routes = audio_map_ac97, + .num_dapm_routes = ARRAY_SIZE(audio_map_ac97), +}; + +static const struct fsl_asoc_card_pdata fsl_asoc_mqs_pdata = { + .codec_dai_name = "fsl-mqs-dai", + .dai_fmt = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBC_CFC | + SND_SOC_DAIFMT_NB_NF, + .playback_only = true, + .dapm_routes = audio_map_tx, + .num_dapm_routes = ARRAY_SIZE(audio_map_tx), +}; + +static const struct fsl_asoc_card_pdata fsl_asoc_wm8524_pdata = { + .codec_dai_name = "wm8524-hifi", + .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBC_CFC, + /* RX=0, TX=1: set TX (index 1) to CLOCK_OUT, RX stays at default IN */ + .sysclk_dir = { 0, SND_SOC_CLOCK_OUT }, + .sysclk_ratio = { 0, 256 }, + .slot_width = 32, + .playback_only = true, + .dapm_routes = audio_map_tx, + .num_dapm_routes = ARRAY_SIZE(audio_map_tx), +}; + +static const struct fsl_asoc_card_pdata fsl_asoc_si476x_pdata = { + .codec_dai_name = "si476x-codec", + .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBC_CFC, + .dapm_routes = audio_map_rx, + .num_dapm_routes = ARRAY_SIZE(audio_map_rx), +}; + +static const struct fsl_asoc_card_pdata fsl_asoc_wm8958_pdata = { + .codec_dai_name = "wm8994-aif1", + .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, + .codec_mclk_id = WM8994_FLL_SRC_MCLK1, + .has_pll = true, + .codec_fll_id = WM8994_SYSCLK_FLL1, + .codec_pll_id = WM8994_FLL1, + .codec_init = fsl_asoc_card_wm8958_codec_init, +}; + +static const struct fsl_asoc_card_pdata fsl_asoc_nau8822_pdata = { + .codec_dai_name = "nau8822-hifi", + .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, + .codec_mclk_id = NAU8822_CLK_MCLK, + .has_pll = true, + .codec_fll_id = NAU8822_CLK_PLL, + .codec_pll_id = NAU8822_CLK_PLL, + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), +}; + +static const struct fsl_asoc_card_pdata fsl_asoc_wm8904_pdata = { + .codec_dai_name = "wm8904-hifi", + .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, + .codec_mclk_id = WM8904_FLL_MCLK, + .has_pll = true, + .codec_fll_id = WM8904_CLK_FLL, + .codec_pll_id = WM8904_FLL_MCLK, + .codec_pll_ratio_s24 = 192, + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), +}; + +static const struct fsl_asoc_card_pdata fsl_asoc_spdif_pdata = { + .codec_dai_name = "spdif", + .dai_fmt = DAI_FMT_BASE, + .probe_init = fsl_asoc_card_spdif_init, +}; + static int hp_jack_event(struct notifier_block *nb, unsigned long event, void *data) { @@ -694,6 +921,7 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) struct device_node *frameprovider = NULL; struct platform_device *cpu_pdev; struct fsl_asoc_card_priv *priv; + const struct fsl_asoc_card_pdata *pdata; struct device *codec_dev[2] = { NULL, NULL }; struct snd_soc_dai_link_component *dlc; const char *codec_dai_name[2]; @@ -709,6 +937,11 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) priv->pdev = pdev; + pdata = of_device_get_match_data(&pdev->dev); + if (!pdata) + return -EINVAL; + priv->pdata = pdata; + cpu_np = of_parse_phandle(np, "audio-cpu", 0); /* Give a chance to old DT bindings */ if (!cpu_np) @@ -767,6 +1000,12 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) } } + if (pdata->codec_init) { + ret = pdata->codec_init(priv); + if (ret) + goto asrc_fail; + } + /* Default sample rate and format, will be updated in hw_params() */ priv->sample_rate = 44100; priv->sample_format = SNDRV_PCM_FORMAT_S16_LE; @@ -818,131 +1057,43 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) } /* Diversify the card configurations */ - if (of_device_is_compatible(np, "fsl,imx-audio-cs42888")) { - codec_dai_name[0] = "cs42888"; - priv->cpu_priv.sysclk_freq[TX] = priv->codec_priv[0].mclk_freq; - priv->cpu_priv.sysclk_freq[RX] = priv->codec_priv[0].mclk_freq; - priv->cpu_priv.sysclk_dir[TX] = SND_SOC_CLOCK_OUT; - priv->cpu_priv.sysclk_dir[RX] = SND_SOC_CLOCK_OUT; - priv->cpu_priv.slot_width = 32; - priv->dai_fmt |= SND_SOC_DAIFMT_CBC_CFC; - priv->constraint_channels = &cs42888_channel_constraints; - if (priv->codec_priv[0].mclk_freq % 12288000 == 0) - priv->constraint_rates = &cs42888_rate_48k_constraints; - else if (priv->codec_priv[0].mclk_freq % 11289600 == 0) - priv->constraint_rates = &cs42888_rate_44k_constraints; - else - dev_warn(&pdev->dev, "Unknown MCLK frequency %lu, no rate constraints\n", - priv->codec_priv[0].mclk_freq); - } else if (of_device_is_compatible(np, "fsl,imx-audio-cs427x")) { - codec_dai_name[0] = "cs4271-hifi"; - priv->codec_priv[0].mclk_id = CS427x_SYSCLK_MCLK; - priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; - } else if (of_device_is_compatible(np, "fsl,imx-audio-sgtl5000")) { - codec_dai_name[0] = "sgtl5000"; - priv->codec_priv[0].mclk_id = SGTL5000_SYSCLK; - priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; - } else if (of_device_is_compatible(np, "fsl,imx-audio-tlv320aic32x4")) { - codec_dai_name[0] = "tlv320aic32x4-hifi"; - priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; - } else if (of_device_is_compatible(np, "fsl,imx-audio-tlv320aic31xx")) { - codec_dai_name[0] = "tlv320dac31xx-hifi"; - priv->dai_fmt |= SND_SOC_DAIFMT_CBC_CFC; - priv->dai_link[1].playback_only = 1; - priv->dai_link[2].playback_only = 1; - priv->cpu_priv.sysclk_dir[TX] = SND_SOC_CLOCK_OUT; - priv->cpu_priv.sysclk_dir[RX] = SND_SOC_CLOCK_OUT; - priv->card.dapm_routes = audio_map_tx; - priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_tx); - } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8962")) { - codec_dai_name[0] = "wm8962"; - priv->codec_priv[0].mclk_id = WM8962_SYSCLK_MCLK; - priv->codec_priv[0].fll_id = WM8962_SYSCLK_FLL; - priv->codec_priv[0].pll_id = WM8962_FLL; - priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; - /* - * WM8962 has same BCLK generation limitations as WM8960. - * See WM8960 section for detailed explanation. - */ - if (of_node_name_eq(cpu_np, "sai")) - priv->exclude_format = SNDRV_PCM_FMTBIT_S20_3LE; - } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8960")) { - codec_dai_name[0] = "wm8960-hifi"; - priv->codec_priv[0].fll_id = WM8960_SYSCLK_AUTO; - priv->codec_priv[0].pll_id = WM8960_SYSCLK_AUTO; - priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; - /* - * WM8960 in master mode cannot generate exact 1.92 MHz BCLK - * required for S20_3LE (48kHz × 2ch × 20bit). Closest available - * is 2.048 MHz (SYSCLK/6), which causes right channel corruption. - * - * In SAI master mode, SAI derive BCLK from MCLK using integer - * dividers only. S20_3LE requires non-integer divider ratios - * with standard MCLK frequencies. For example, 48kHz stereo - * needs 1.920 MHz BCLK, which requires a divider of 6.4 from - * 12.288 MHz MCLK (not an integer). - */ - if (of_node_name_eq(cpu_np, "sai")) - priv->exclude_format = SNDRV_PCM_FMTBIT_S20_3LE; - } else if (of_device_is_compatible(np, "fsl,imx-audio-ac97")) { - codec_dai_name[0] = "ac97-hifi"; - priv->dai_fmt = SND_SOC_DAIFMT_AC97; - priv->card.dapm_routes = audio_map_ac97; - priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_ac97); - } else if (of_device_is_compatible(np, "fsl,imx-audio-mqs")) { - codec_dai_name[0] = "fsl-mqs-dai"; - priv->dai_fmt = SND_SOC_DAIFMT_LEFT_J | - SND_SOC_DAIFMT_CBC_CFC | - SND_SOC_DAIFMT_NB_NF; - priv->dai_link[1].playback_only = 1; - priv->dai_link[2].playback_only = 1; - priv->card.dapm_routes = audio_map_tx; - priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_tx); - } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8524")) { - codec_dai_name[0] = "wm8524-hifi"; - priv->dai_fmt |= SND_SOC_DAIFMT_CBC_CFC; + priv->cpu_priv.sysclk_dir[TX] = pdata->sysclk_dir[TX]; + priv->cpu_priv.sysclk_dir[RX] = pdata->sysclk_dir[RX]; + priv->cpu_priv.sysclk_ratio[TX] = pdata->sysclk_ratio[TX]; + priv->cpu_priv.sysclk_ratio[RX] = pdata->sysclk_ratio[RX]; + priv->cpu_priv.slot_width = pdata->slot_width; + + codec_dai_name[0] = pdata->codec_dai_name; + priv->codec_priv[0].mclk_id = pdata->codec_mclk_id; + if (pdata->has_pll) { + priv->codec_priv[0].fll_id = pdata->codec_fll_id; + priv->codec_priv[0].pll_id = pdata->codec_pll_id; + } + if (pdata->codec_pll_ratio_s24) + priv->codec_priv[0].pll_ratio_s24 = pdata->codec_pll_ratio_s24; + + if (pdata->playback_only) { priv->dai_link[1].playback_only = 1; priv->dai_link[2].playback_only = 1; - priv->cpu_priv.slot_width = 32; - priv->card.dapm_routes = audio_map_tx; - priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_tx); - priv->cpu_priv.sysclk_dir[TX] = SND_SOC_CLOCK_OUT; - priv->cpu_priv.sysclk_ratio[TX] = 256; - } else if (of_device_is_compatible(np, "fsl,imx-audio-si476x")) { - codec_dai_name[0] = "si476x-codec"; - priv->dai_fmt |= SND_SOC_DAIFMT_CBC_CFC; - priv->card.dapm_routes = audio_map_rx; - priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_rx); - } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8958")) { - codec_dai_name[0] = "wm8994-aif1"; - priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; - priv->codec_priv[0].mclk_id = WM8994_FLL_SRC_MCLK1; - priv->codec_priv[0].fll_id = WM8994_SYSCLK_FLL1; - priv->codec_priv[0].pll_id = WM8994_FLL1; - priv->codec_priv[0].free_freq = priv->codec_priv[0].mclk_freq; - priv->card.dapm_routes = NULL; - priv->card.num_dapm_routes = 0; - } else if (of_device_is_compatible(np, "fsl,imx-audio-nau8822")) { - codec_dai_name[0] = "nau8822-hifi"; - priv->codec_priv[0].mclk_id = NAU8822_CLK_MCLK; - priv->codec_priv[0].fll_id = NAU8822_CLK_PLL; - priv->codec_priv[0].pll_id = NAU8822_CLK_PLL; - priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; - } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8904")) { - codec_dai_name[0] = "wm8904-hifi"; - priv->codec_priv[0].mclk_id = WM8904_FLL_MCLK; - priv->codec_priv[0].fll_id = WM8904_CLK_FLL; - priv->codec_priv[0].pll_id = WM8904_FLL_MCLK; - priv->codec_priv[0].pll_ratio_s24 = 192; - priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; - } else if (of_device_is_compatible(np, "fsl,imx-audio-spdif")) { - ret = fsl_asoc_card_spdif_init(codec_np, cpu_np, codec_dai_name, priv); + } + if (pdata->capture_only) { + priv->dai_link[1].capture_only = 1; + priv->dai_link[2].capture_only = 1; + } + + priv->dai_fmt = pdata->dai_fmt; + + priv->card.dapm_routes = pdata->dapm_routes; + priv->card.num_dapm_routes = pdata->num_dapm_routes; + + if (pdata->exclude_format && of_node_name_eq(cpu_np, "sai")) + priv->exclude_format = pdata->exclude_format; + + if (pdata->probe_init) { + ret = pdata->probe_init(codec_np, cpu_np, + codec_dai_name, priv); if (ret) goto asrc_fail; - } else { - dev_err(&pdev->dev, "unknown Device Tree compatible\n"); - ret = -EINVAL; - goto asrc_fail; } /* @@ -1179,21 +1330,21 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) } static const struct of_device_id fsl_asoc_card_dt_ids[] = { - { .compatible = "fsl,imx-audio-ac97", }, - { .compatible = "fsl,imx-audio-cs42888", }, - { .compatible = "fsl,imx-audio-cs427x", }, - { .compatible = "fsl,imx-audio-tlv320aic32x4", }, - { .compatible = "fsl,imx-audio-tlv320aic31xx", }, - { .compatible = "fsl,imx-audio-sgtl5000", }, - { .compatible = "fsl,imx-audio-wm8962", }, - { .compatible = "fsl,imx-audio-wm8960", }, - { .compatible = "fsl,imx-audio-mqs", }, - { .compatible = "fsl,imx-audio-wm8524", }, - { .compatible = "fsl,imx-audio-si476x", }, - { .compatible = "fsl,imx-audio-wm8958", }, - { .compatible = "fsl,imx-audio-nau8822", }, - { .compatible = "fsl,imx-audio-wm8904", }, - { .compatible = "fsl,imx-audio-spdif", }, + { .compatible = "fsl,imx-audio-ac97", .data = &fsl_asoc_ac97_pdata }, + { .compatible = "fsl,imx-audio-cs42888", .data = &fsl_asoc_cs42888_pdata }, + { .compatible = "fsl,imx-audio-cs427x", .data = &fsl_asoc_cs427x_pdata }, + { .compatible = "fsl,imx-audio-tlv320aic32x4", .data = &fsl_asoc_tlv320aic32x4_pdata }, + { .compatible = "fsl,imx-audio-tlv320aic31xx", .data = &fsl_asoc_tlv320aic31xx_pdata }, + { .compatible = "fsl,imx-audio-sgtl5000", .data = &fsl_asoc_sgtl5000_pdata }, + { .compatible = "fsl,imx-audio-wm8962", .data = &fsl_asoc_wm8962_pdata }, + { .compatible = "fsl,imx-audio-wm8960", .data = &fsl_asoc_wm8960_pdata }, + { .compatible = "fsl,imx-audio-mqs", .data = &fsl_asoc_mqs_pdata }, + { .compatible = "fsl,imx-audio-wm8524", .data = &fsl_asoc_wm8524_pdata }, + { .compatible = "fsl,imx-audio-si476x", .data = &fsl_asoc_si476x_pdata }, + { .compatible = "fsl,imx-audio-wm8958", .data = &fsl_asoc_wm8958_pdata }, + { .compatible = "fsl,imx-audio-nau8822", .data = &fsl_asoc_nau8822_pdata }, + { .compatible = "fsl,imx-audio-wm8904", .data = &fsl_asoc_wm8904_pdata }, + { .compatible = "fsl,imx-audio-spdif", .data = &fsl_asoc_spdif_pdata }, {} }; MODULE_DEVICE_TABLE(of, fsl_asoc_card_dt_ids); From de27e0cadcce09053a8ae07575ecd5a327d8fec6 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Mon, 10 Aug 2026 18:35:31 +0900 Subject: [PATCH 536/612] ASoC: fsl-asoc-card: Move bound-component setup to late_probe Move all operations that require bound codec and CPU DAI components out of probe() and into late_probe(), which is the correct place for them now that ASoC supports deferrable card binding. late_probe() may be called multiple times after an unbind/rebind cycle, so every initialization step is guarded accordingly. Three new helpers are introduced: - fsl_asoc_card_init_cpu() CPU DAI-specific setup. Previously done in probe() while CPU DAI component maybe not ready. - fsl_asoc_card_init_codecs() Reads codec MCLK rates from the bound component devices, invokes the per-compatible pdata->codec_init callback if present. - fsl_asoc_card_init_jack() Registers headphone and microphone jacks. The call site of codec_init callbacks moves from probe() to fsl_asoc_card_init_codecs(), which runs in late_probe() after the bound codec device is known. This makes sure codecs can get proper MCLK. The old card-name fallback depended on codec_dev_name[], which required looking up the codec device in probe(). This is no longer valid under deferrable card binding because the codec component may not have probed yet. Since the DT binding requires "model", remove the fallback and fail with a clear error. Assisted-by: VeroCoder:claude-sonnet-4-6 Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260810093834.1511749-4-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl-asoc-card.c | 324 ++++++++++++++++++++-------------- 1 file changed, 191 insertions(+), 133 deletions(-) diff --git a/sound/soc/fsl/fsl-asoc-card.c b/sound/soc/fsl/fsl-asoc-card.c index 10e432c916b8cf..3532bf6d507e46 100644 --- a/sound/soc/fsl/fsl-asoc-card.c +++ b/sound/soc/fsl/fsl-asoc-card.c @@ -658,8 +658,14 @@ static int fsl_asoc_card_cs42888_codec_init(struct fsl_asoc_card_priv *priv) { unsigned long mclk_freq = priv->codec_priv[0].mclk_freq; - priv->cpu_priv.sysclk_freq[TX] = mclk_freq; - priv->cpu_priv.sysclk_freq[RX] = mclk_freq; + /* + * Set CPU sysclk frequency from codec MCLK only if not already + * set by the CPU DAI init (e.g. ESAI extal clock takes precedence). + */ + if (!priv->cpu_priv.sysclk_freq[TX]) + priv->cpu_priv.sysclk_freq[TX] = mclk_freq; + if (!priv->cpu_priv.sysclk_freq[RX]) + priv->cpu_priv.sysclk_freq[RX] = mclk_freq; priv->constraint_channels = &cs42888_channel_constraints; if (mclk_freq % 12288000 == 0) @@ -868,17 +874,173 @@ static struct notifier_block mic_jack_nb = { .notifier_call = mic_jack_event, }; -static int fsl_asoc_card_late_probe(struct snd_soc_card *card) +/* + * fsl_asoc_card_init_cpu - configure CPU DAI-specific settings. + * + * Called from late_probe() when the CPU DAI component is guaranteed bound. + */ +static int fsl_asoc_card_init_cpu(struct snd_soc_card *card, + struct snd_soc_pcm_runtime *rtd) +{ + struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card); + struct device_node *np = priv->pdev->dev.of_node; + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + const char *comp_drv_name = cpu_dai->component->driver->name; + struct device *dev = card->dev; + int ret; + + if (!strcmp(comp_drv_name, "fsl-ssi")) { + /* Only SSI needs to configure AUDMUX */ + ret = fsl_asoc_card_audmux_init(np, priv); + if (ret) { + dev_err(dev, "failed to init audmux\n"); + return ret; + } + } else if (!strcmp(comp_drv_name, "fsl-esai")) { + struct clk *esai_clk = clk_get(cpu_dai->dev, "extal"); + + if (!IS_ERR(esai_clk)) { + priv->cpu_priv.sysclk_freq[TX] = clk_get_rate(esai_clk); + priv->cpu_priv.sysclk_freq[RX] = clk_get_rate(esai_clk); + clk_put(esai_clk); + } else { + dev_warn(dev, "failed to get ESAI extal clock: %ld\n", PTR_ERR(esai_clk)); + } + + priv->cpu_priv.sysclk_id[TX] = ESAI_HCKT_EXTAL; + priv->cpu_priv.sysclk_id[RX] = ESAI_HCKR_EXTAL; + } else if (!strcmp(comp_drv_name, "fsl-sai")) { + priv->cpu_priv.sysclk_id[TX] = FSL_SAI_CLK_MAST1; + priv->cpu_priv.sysclk_id[RX] = FSL_SAI_CLK_MAST1; + + if (priv->pdata->exclude_format) + priv->exclude_format = priv->pdata->exclude_format; + } + + return 0; +} + +/* + * fsl_asoc_card_init_codecs - read codec MCLK rates and set codec sysclk. + * + * Called from late_probe() after all components are bound. + */ +static int fsl_asoc_card_init_codecs(struct snd_soc_card *card, + struct snd_soc_pcm_runtime *rtd) { struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card); - struct snd_soc_pcm_runtime *rtd = list_first_entry( - &card->rtd_list, struct snd_soc_pcm_runtime, list); + const struct fsl_asoc_card_pdata *pdata = priv->pdata; struct snd_soc_dai *codec_dai; struct codec_priv *codec_priv; struct device *dev = card->dev; int codec_idx; int ret; + /* Read MCLK rate from each bound codec component */ + for_each_rtd_codec_dais(rtd, codec_idx, codec_dai) { + struct clk *codec_clk = clk_get(codec_dai->component->dev, NULL); + + codec_priv = &priv->codec_priv[codec_idx]; + if (!IS_ERR(codec_clk)) { + codec_priv->mclk_freq = clk_get_rate(codec_clk); + clk_put(codec_clk); + } + } + + if (pdata->codec_init) { + ret = pdata->codec_init(priv); + if (ret) + return ret; + } + + for_each_rtd_codec_dais(rtd, codec_idx, codec_dai) { + codec_priv = &priv->codec_priv[codec_idx]; + + ret = snd_soc_dai_set_sysclk(codec_dai, codec_priv->mclk_id, + codec_priv->mclk_freq, SND_SOC_CLOCK_IN); + if (ret && ret != -ENOTSUPP) { + dev_err(dev, "failed to set sysclk in %s\n", __func__); + return ret; + } + } + + return 0; +} + +static void fsl_asoc_card_free_jack(struct snd_soc_card *card) +{ + struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card); + + if (priv->hp_jack.gpio.desc) { + snd_soc_jack_notifier_unregister(&priv->hp_jack.jack, &hp_jack_nb); + snd_soc_jack_free_gpios(&priv->hp_jack.jack, 1, &priv->hp_jack.gpio); + priv->hp_jack.gpio.desc = NULL; + } + + if (priv->mic_jack.gpio.desc) { + snd_soc_jack_notifier_unregister(&priv->mic_jack.jack, &mic_jack_nb); + snd_soc_jack_free_gpios(&priv->mic_jack.jack, 1, &priv->mic_jack.gpio); + priv->mic_jack.gpio.desc = NULL; + } +} + +/* + * fsl_asoc_card_init_jack - register optional headphone and mic jacks. + * + * Called from late_probe() once per card bind cycle. + */ +static int fsl_asoc_card_init_jack(struct snd_soc_card *card) +{ + struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card); + struct device_node *np = priv->pdev->dev.of_node; + int ret; + + /* + * Properties "hp-det-gpios" and "mic-det-gpios" are optional. + * simple_util_init_jack() checks for the GPIO property and + * does nothing if it is absent. + */ + if (of_property_present(np, "hp-det-gpios") || + of_property_present(np, "hp-det-gpio") /* deprecated */) { + ret = simple_util_init_jack(card, &priv->hp_jack, + 1, NULL, "Headphone Jack"); + if (ret) + return ret; + + snd_soc_jack_notifier_register(&priv->hp_jack.jack, &hp_jack_nb); + } + + if (of_property_present(np, "mic-det-gpios") || + of_property_present(np, "mic-det-gpio") /* deprecated */) { + ret = simple_util_init_jack(card, &priv->mic_jack, + 0, NULL, "Mic Jack"); + if (ret) + return ret; + + snd_soc_jack_notifier_register(&priv->mic_jack.jack, &mic_jack_nb); + } + + return 0; +} + +static int fsl_asoc_card_late_probe(struct snd_soc_card *card) +{ + struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_pcm_runtime *rtd; + int ret; + + /* Use the first rtd which carries the CPU+codec DAIs */ + rtd = list_first_entry(&card->rtd_list, + struct snd_soc_pcm_runtime, list); + + ret = fsl_asoc_card_init_jack(card); + if (ret) + goto jack_fail; + + ret = fsl_asoc_card_init_cpu(card, rtd); + if (ret) + goto jack_fail; + if (fsl_asoc_card_is_ac97(priv)) { #if IS_ENABLED(CONFIG_SND_AC97_CODEC) struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; @@ -896,16 +1058,20 @@ static int fsl_asoc_card_late_probe(struct snd_soc_card *card) return 0; } - for_each_rtd_codec_dais(rtd, codec_idx, codec_dai) { - codec_priv = &priv->codec_priv[codec_idx]; + ret = fsl_asoc_card_init_codecs(card, rtd); + if (ret) + goto jack_fail; - ret = snd_soc_dai_set_sysclk(codec_dai, codec_priv->mclk_id, - codec_priv->mclk_freq, SND_SOC_CLOCK_IN); - if (ret && ret != -ENOTSUPP) { - dev_err(dev, "failed to set sysclk in %s\n", __func__); - return ret; - } - } + return 0; + +jack_fail: + fsl_asoc_card_free_jack(card); + return ret; +} + +static int fsl_asoc_card_card_remove(struct snd_soc_card *card) +{ + fsl_asoc_card_free_jack(card); return 0; } @@ -919,13 +1085,10 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) struct platform_device *asrc_pdev = NULL; struct device_node *bitclkprovider = NULL; struct device_node *frameprovider = NULL; - struct platform_device *cpu_pdev; struct fsl_asoc_card_priv *priv; const struct fsl_asoc_card_pdata *pdata; - struct device *codec_dev[2] = { NULL, NULL }; struct snd_soc_dai_link_component *dlc; - const char *codec_dai_name[2]; - const char *codec_dev_name[2]; + const char *codec_dai_name[2] = { NULL, NULL }; u32 asrc_fmt = 0; int codec_idx; u32 width; @@ -938,8 +1101,10 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) priv->pdev = pdev; pdata = of_device_get_match_data(&pdev->dev); - if (!pdata) + if (!pdata) { + dev_err(&pdev->dev, "unknown Device Tree compatible\n"); return -EINVAL; + } priv->pdata = pdata; cpu_np = of_parse_phandle(np, "audio-cpu", 0); @@ -954,58 +1119,13 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) goto fail; } - cpu_pdev = of_find_device_by_node(cpu_np); - if (!cpu_pdev) { - dev_err(&pdev->dev, "failed to find CPU DAI device\n"); - ret = -EINVAL; - goto fail; - } - codec_np[0] = of_parse_phandle(np, "audio-codec", 0); codec_np[1] = of_parse_phandle(np, "audio-codec", 1); - for (codec_idx = 0; codec_idx < 2; codec_idx++) { - if (codec_np[codec_idx]) { - struct platform_device *codec_pdev; - struct i2c_client *codec_i2c; - - codec_i2c = of_find_i2c_device_by_node(codec_np[codec_idx]); - if (codec_i2c) { - codec_dev[codec_idx] = &codec_i2c->dev; - codec_dev_name[codec_idx] = codec_i2c->name; - } - if (!codec_dev[codec_idx]) { - codec_pdev = of_find_device_by_node(codec_np[codec_idx]); - if (codec_pdev) { - codec_dev[codec_idx] = &codec_pdev->dev; - codec_dev_name[codec_idx] = codec_pdev->name; - } - } - } - } - asrc_np = of_parse_phandle(np, "audio-asrc", 0); if (asrc_np) asrc_pdev = of_find_device_by_node(asrc_np); - /* Get the MCLK rate only, and leave it controlled by CODEC drivers */ - for (codec_idx = 0; codec_idx < 2; codec_idx++) { - if (codec_dev[codec_idx]) { - struct clk *codec_clk = clk_get(codec_dev[codec_idx], NULL); - - if (!IS_ERR(codec_clk)) { - priv->codec_priv[codec_idx].mclk_freq = clk_get_rate(codec_clk); - clk_put(codec_clk); - } - } - } - - if (pdata->codec_init) { - ret = pdata->codec_init(priv); - if (ret) - goto asrc_fail; - } - /* Default sample rate and format, will be updated in hw_params() */ priv->sample_rate = 44100; priv->sample_format = SNDRV_PCM_FORMAT_S16_LE; @@ -1086,9 +1206,6 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) priv->card.dapm_routes = pdata->dapm_routes; priv->card.num_dapm_routes = pdata->num_dapm_routes; - if (pdata->exclude_format && of_node_name_eq(cpu_np, "sai")) - priv->exclude_format = pdata->exclude_format; - if (pdata->probe_init) { ret = pdata->probe_init(codec_np, cpu_np, codec_dai_name, priv); @@ -1139,51 +1256,21 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) of_node_put(bitclkprovider); of_node_put(frameprovider); - if (!fsl_asoc_card_is_ac97(priv) && !codec_dev[0] - && codec_dai_name[0] != snd_soc_dummy_dlc.dai_name) { - dev_dbg(&pdev->dev, "failed to find codec device\n"); - ret = -EPROBE_DEFER; - goto asrc_fail; - } - - /* Common settings for corresponding Freescale CPU DAI driver */ - if (of_node_name_eq(cpu_np, "ssi")) { - /* Only SSI needs to configure AUDMUX */ - ret = fsl_asoc_card_audmux_init(np, priv); - if (ret) { - dev_err(&pdev->dev, "failed to init audmux\n"); - goto asrc_fail; - } - } else if (of_node_name_eq(cpu_np, "esai")) { - struct clk *esai_clk = clk_get(&cpu_pdev->dev, "extal"); - - if (!IS_ERR(esai_clk)) { - priv->cpu_priv.sysclk_freq[TX] = clk_get_rate(esai_clk); - priv->cpu_priv.sysclk_freq[RX] = clk_get_rate(esai_clk); - clk_put(esai_clk); - } else if (PTR_ERR(esai_clk) == -EPROBE_DEFER) { - ret = -EPROBE_DEFER; - goto asrc_fail; - } - - priv->cpu_priv.sysclk_id[1] = ESAI_HCKT_EXTAL; - priv->cpu_priv.sysclk_id[0] = ESAI_HCKR_EXTAL; - } else if (of_node_name_eq(cpu_np, "sai")) { - priv->cpu_priv.sysclk_id[1] = FSL_SAI_CLK_MAST1; - priv->cpu_priv.sysclk_id[0] = FSL_SAI_CLK_MAST1; - } - /* Initialize sound card */ priv->card.dev = &pdev->dev; priv->card.owner = THIS_MODULE; ret = snd_soc_of_parse_card_name(&priv->card, "model"); if (ret) { - snprintf(priv->name, sizeof(priv->name), "%s-audio", - fsl_asoc_card_is_ac97(priv) ? "ac97" : codec_dev_name[0]); - priv->card.name = priv->name; + /* + * "model" is required by the DT binding. Enforce it here so + * the driver fails with a clear message. + */ + dev_err(&pdev->dev, "Error parsing card name: %d\n", ret); + goto asrc_fail; } priv->card.dai_link = priv->dai_link; priv->card.late_probe = fsl_asoc_card_late_probe; + priv->card.remove = fsl_asoc_card_card_remove; priv->card.dapm_widgets = fsl_asoc_card_dapm_widgets; priv->card.num_dapm_widgets = ARRAY_SIZE(fsl_asoc_card_dapm_widgets); @@ -1290,39 +1377,10 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) goto asrc_fail; } - /* - * Properties "hp-det-gpios" and "mic-det-gpios" are optional, and - * simple_util_init_jack() uses these properties for creating - * Headphone Jack and Microphone Jack. - * - * The notifier is initialized in snd_soc_card_jack_new(), then - * snd_soc_jack_notifier_register can be called. - */ - if (of_property_present(np, "hp-det-gpios") || - of_property_present(np, "hp-det-gpio") /* deprecated */) { - ret = simple_util_init_jack(&priv->card, &priv->hp_jack, - 1, NULL, "Headphone Jack"); - if (ret) - goto asrc_fail; - - snd_soc_jack_notifier_register(&priv->hp_jack.jack, &hp_jack_nb); - } - - if (of_property_present(np, "mic-det-gpios") || - of_property_present(np, "mic-det-gpio") /* deprecated */) { - ret = simple_util_init_jack(&priv->card, &priv->mic_jack, - 0, NULL, "Mic Jack"); - if (ret) - goto asrc_fail; - - snd_soc_jack_notifier_register(&priv->mic_jack.jack, &mic_jack_nb); - } - asrc_fail: of_node_put(asrc_np); of_node_put(codec_np[0]); of_node_put(codec_np[1]); - put_device(&cpu_pdev->dev); fail: of_node_put(cpu_np); From 0196c4b4f82beec2114ee3c13888314ca551e32a Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Mon, 10 Aug 2026 21:19:25 -0700 Subject: [PATCH 537/612] ASoC: amd: acp: pass audio_drv_data to dma_irq_handler The IRQ handler only needs the audio_drv_data, so pass it directly as the request_irq argument instead of the device pointer and a dev_get_drvdata() lookup. Assisted-by: opencode:deepseek-v4-flash-free Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260811041925.25016-1-rosenp@gmail.com Signed-off-by: Mark Brown --- sound/soc/amd/acp-pcm-dma.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sound/soc/amd/acp-pcm-dma.c b/sound/soc/amd/acp-pcm-dma.c index 2b8848b6df2b44..ee75737202dc61 100644 --- a/sound/soc/amd/acp-pcm-dma.c +++ b/sound/soc/amd/acp-pcm-dma.c @@ -691,12 +691,10 @@ static irqreturn_t dma_irq_handler(int irq, void *arg) { u16 dscr_idx; u32 intr_flag, ext_intr_status; - struct audio_drv_data *irq_data; + struct audio_drv_data *irq_data = arg; void __iomem *acp_mmio; - struct device *dev = arg; bool valid_irq = false; - irq_data = dev_get_drvdata(dev); acp_mmio = irq_data->acp_mmio; ext_intr_status = acp_reg_read(acp_mmio, mmACP_EXTERNAL_INTR_STAT); @@ -1294,7 +1292,7 @@ static int acp_audio_probe(struct platform_device *pdev) return irq; status = devm_request_irq(&pdev->dev, irq, dma_irq_handler, - 0, "ACP_IRQ", &pdev->dev); + 0, "ACP_IRQ", audio_drv_data); if (status) { dev_err(&pdev->dev, "ACP IRQ request failed\n"); return status; From 4cc25cdd3cffa475edb8dec8199b3227038ebcfb Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 13 Aug 2026 16:42:16 +0200 Subject: [PATCH 538/612] ALSA: seq: midi: Optimize event_input locking with RCU The recent fix for serializing the output teardown introduced a spinlock invocation at every MIDI output event via event_process_midi. Since this is a hot path, let's do performance optimization with RCU. The new output_substream __rcu pointer is published via rcu_assign_pointer() in midisynth_use() after output_rfile is set, and cleared in midisynth_unuse() before the resource teardown. event_process_midi() reads it under rcu_read_lock() and bumps output_use_lock inside that section, which is necessary to close the window between the pointer dereference and the refcount increment. midisynth_unuse() calls synchronize_rcu() before snd_use_lock_sync(): this guarantees that any reader who obtained a non-NULL pointer has already called atomic_inc (output_use_lock), so the subsequent snd_use_lock_sync() sees the correct in-flight count. Fixes: ef7607ab1c8a ("ALSA: seq: midi: Serialize output teardown with event_input") Link: https://patch.msgid.link/20260813144224.753399-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/seq/seq_midi.c | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/sound/core/seq/seq_midi.c b/sound/core/seq/seq_midi.c index 2eb12199c92f90..c2f89aee1914b1 100644 --- a/sound/core/seq/seq_midi.c +++ b/sound/core/seq/seq_midi.c @@ -43,8 +43,8 @@ struct seq_midisynth { int device; int subdevice; struct snd_rawmidi_file input_rfile; - spinlock_t output_lock; /* protects output_rfile publication */ snd_use_lock_t output_use_lock; /* in-flight event_input users */ + struct snd_rawmidi_substream __rcu *output_substream; struct snd_rawmidi_file output_rfile; int seq_client; int seq_port; @@ -134,8 +134,8 @@ static int event_process_midi(struct snd_seq_event *ev, int direct, if (snd_BUG_ON(!msynth)) return -EINVAL; - scoped_guard(spinlock_irqsave, &msynth->output_lock) { - substream = msynth->output_rfile.output; + scoped_guard(rcu) { + substream = rcu_dereference(msynth->output_substream); if (!substream) return -ENODEV; snd_use_lock_use(&msynth->output_use_lock); @@ -177,7 +177,6 @@ static int snd_seq_midisynth_new(struct seq_midisynth *msynth, msynth->card = card; msynth->device = device; msynth->subdevice = subdevice; - spin_lock_init(&msynth->output_lock); snd_use_lock_init(&msynth->output_use_lock); return 0; } @@ -252,8 +251,8 @@ static int midisynth_use(void *private_data, struct snd_seq_port_subscribe *info return err; } snd_midi_event_reset_decode(msynth->parser); - scoped_guard(spinlock_irqsave, &msynth->output_lock) - msynth->output_rfile = rfile; + msynth->output_rfile = rfile; + rcu_assign_pointer(msynth->output_substream, rfile.output); return 0; } @@ -261,17 +260,16 @@ static int midisynth_use(void *private_data, struct snd_seq_port_subscribe *info static int midisynth_unuse(void *private_data, struct snd_seq_port_subscribe *info) { struct seq_midisynth *msynth = private_data; - struct snd_rawmidi_file rfile = {}; + struct snd_rawmidi_file rfile; - scoped_guard(spinlock_irqsave, &msynth->output_lock) { - rfile = msynth->output_rfile; - msynth->output_rfile = (struct snd_rawmidi_file){}; - } + rcu_assign_pointer(msynth->output_substream, NULL); + synchronize_rcu(); + snd_use_lock_sync(&msynth->output_use_lock); + rfile = msynth->output_rfile; + msynth->output_rfile = (struct snd_rawmidi_file){}; if (snd_BUG_ON(!rfile.output)) return -EINVAL; - - snd_use_lock_sync(&msynth->output_use_lock); snd_rawmidi_drain_output(rfile.output); return snd_rawmidi_kernel_release(&rfile); } From 9895573b0185f922be4cf500a0f2e5b9560ab1c3 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 13 Aug 2026 17:03:49 +0200 Subject: [PATCH 539/612] ALSA: hda/intel: Add sanity check for BAR0 size The recent reports from syzkaller showed that we can bind any wild PCI device to HD-audio controller, and if PCI BAR of the device is too small, it may lead to a crash, as the driver believes as if the full register range were accessible. For avoiding such a problem, add a safeguard before the actual probe to check the available BAR0 size. Note that the threshold (0x200) is chosen to cover all needed registers at probing. But this doesn't mean that it would cover fully for all features including the extended ones. Reported-by: syzbot+10cd2d1efe8eeb604bee@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=10cd2d1efe8eeb604bee Reported-by: syzbot+5ebe7cd17e48b4293660@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=5ebe7cd17e48b4293660 Link: https://patch.msgid.link/20260813150354.763502-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/hda/controllers/intel.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sound/hda/controllers/intel.c b/sound/hda/controllers/intel.c index 8f592032ac1591..1e6d97e08fee99 100644 --- a/sound/hda/controllers/intel.c +++ b/sound/hda/controllers/intel.c @@ -2197,6 +2197,15 @@ static int azx_probe(struct pci_dev *pci, dev_warn(&pci->dev, "dmic_detect option is deprecated, pass snd-intel-dspcfg.dsp_driver=1 option instead\n"); } + /* A sanity check against wild device binding; + * here the range 0x200 is enough for the registers used at probe, + * but it doesn't mean covering all HD-audio registers + */ + if (pci_resource_len(pci, 0) < 0x200) { + dev_err(&pci->dev, "Too small PCI BAR0\n"); + return -EINVAL; + } + err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, 0, &card); if (err < 0) { From 403f7f3ad3808a0096d84cf228fab68dc253fd9d Mon Sep 17 00:00:00 2001 From: John Keeping Date: Thu, 13 Aug 2026 16:08:08 +0100 Subject: [PATCH 540/612] ALSA: seq: midi: Serialize input teardown with event_input snd_midi_input_event() must not be running while a rawmidi substream is closing, since this can lead to the trigger state becoming out-of-step through this sequence in snd_rawmidi_input_trigger(): snd_rawmidi_input_trigger(up=0) snd_midi_input_event() -> snd_rawmidi_kernel_read() -> snd_rawmidi_input_trigger(up=1) -> cancel_work_sync() which ends with the underlying device being active unexpectedly. When this is called from close_substream(), further input can re-trigger the input event leaving it running after rawmidi_release_priv() has set rfile->rmidi to NULL which leads to: Unable to handle kernel NULL pointer dereference at virtual address 00000000000000b0 Call trace: snd_midi_input_event+0x3c/0x134 [snd_seq_midi] (P) snd_rawmidi_input_event_work+0x1c/0x2c process_one_work+0x150/0x3a4 worker_thread+0x190/0x318 Apply a similar approach to commit ef7607ab1c8ad ("ALSA: seq: midi: Serialize output teardown with event_input") which fixed the same issue in the output direction, but updated to use RCU following Takashi Iwai's proposed follow-on patch [1]. With this change in place, midisynth_unsubscribe() clears the input file so snd_midi_input_event() will not re-trigger the stream and will be quiesced by the cancel_work_sync() in snd_rawmidi_input_trigger(). [1] https://lore.kernel.org/linux-sound/20260813144224.753399-1-tiwai@suse.de/ Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: John Keeping Link: https://patch.msgid.link/20260813150810.795393-1-jkeeping@inmusicbrands.com Signed-off-by: Takashi Iwai --- sound/core/seq/seq_midi.c | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/sound/core/seq/seq_midi.c b/sound/core/seq/seq_midi.c index c2f89aee1914b1..a16a5debf3391b 100644 --- a/sound/core/seq/seq_midi.c +++ b/sound/core/seq/seq_midi.c @@ -42,6 +42,8 @@ struct seq_midisynth { struct snd_rawmidi *rmidi; int device; int subdevice; + struct snd_rawmidi_substream __rcu *input_substream; + snd_use_lock_t input_use_lock; /* in-flight event_input users */ struct snd_rawmidi_file input_rfile; snd_use_lock_t output_use_lock; /* in-flight event_input users */ struct snd_rawmidi_substream __rcu *output_substream; @@ -76,6 +78,14 @@ static void snd_midi_input_event(struct snd_rawmidi_substream *substream) msynth = runtime->private_data; if (msynth == NULL) return; + + scoped_guard(rcu) { + if (rcu_dereference(msynth->input_substream) != substream) + return; + + snd_use_lock_use(&msynth->input_use_lock); + } + memset(&ev, 0, sizeof(ev)); while (runtime->avail > 0) { res = snd_rawmidi_kernel_read(substream, buf, sizeof(buf)); @@ -95,6 +105,8 @@ static void snd_midi_input_event(struct snd_rawmidi_substream *substream) memset(&ev, 0, sizeof(ev)); } } + + snd_use_lock_free(&msynth->input_use_lock); } static int dump_midi(struct snd_rawmidi_substream *substream, const char *buf, int count) @@ -177,6 +189,7 @@ static int snd_seq_midisynth_new(struct seq_midisynth *msynth, msynth->card = card; msynth->device = device; msynth->subdevice = subdevice; + snd_use_lock_init(&msynth->input_use_lock); snd_use_lock_init(&msynth->output_use_lock); return 0; } @@ -187,28 +200,31 @@ static int midisynth_subscribe(void *private_data, struct snd_seq_port_subscribe int err; struct seq_midisynth *msynth = private_data; struct snd_rawmidi_runtime *runtime; + struct snd_rawmidi_file rfile = {}; struct snd_rawmidi_params params; /* open midi port */ err = snd_rawmidi_kernel_open(msynth->rmidi, msynth->subdevice, SNDRV_RAWMIDI_LFLG_INPUT, - &msynth->input_rfile); + &rfile); if (err < 0) { pr_debug("ALSA: seq_midi: midi input open failed!!!\n"); return err; } - runtime = msynth->input_rfile.input->runtime; + runtime = rfile.input->runtime; memset(¶ms, 0, sizeof(params)); params.avail_min = 1; params.buffer_size = input_buffer_size; - err = snd_rawmidi_input_params(msynth->input_rfile.input, ¶ms); + err = snd_rawmidi_input_params(rfile.input, ¶ms); if (err < 0) { - snd_rawmidi_kernel_release(&msynth->input_rfile); + snd_rawmidi_kernel_release(&rfile); return err; } snd_midi_event_reset_encode(msynth->parser); runtime->event = snd_midi_input_event; runtime->private_data = msynth; + msynth->input_rfile = rfile; + rcu_assign_pointer(msynth->input_substream, rfile.input); snd_rawmidi_kernel_read(msynth->input_rfile.input, NULL, 0); return 0; } @@ -218,10 +234,19 @@ static int midisynth_unsubscribe(void *private_data, struct snd_seq_port_subscri { int err; struct seq_midisynth *msynth = private_data; + struct snd_rawmidi_file rfile; - if (snd_BUG_ON(!msynth->input_rfile.input)) + rcu_assign_pointer(msynth->input_substream, NULL); + synchronize_rcu(); + snd_use_lock_sync(&msynth->input_use_lock); + + rfile = msynth->input_rfile; + msynth->input_rfile = (struct snd_rawmidi_file){}; + + if (snd_BUG_ON(!rfile.input)) return -EINVAL; - err = snd_rawmidi_kernel_release(&msynth->input_rfile); + + err = snd_rawmidi_kernel_release(&rfile); return err; } From bc34c37ed8265250c4c41c18953ab82fcc491c83 Mon Sep 17 00:00:00 2001 From: Hongyang Zhao Date: Thu, 13 Aug 2026 19:53:34 +0800 Subject: [PATCH 541/612] ASoC: dt-bindings: es8316: Fix supply property constraints The DT meta-schema requires a `then` clause when an `if` condition has an `else` clause. Invert the compatible check and move the supply property restrictions to `then` so they remain allowed only for ES8316. Fixes: e9966d450b46 ("ASoC: dt-bindings: es8316: Add regulator supplies") Reported-by: Rob Herring Closes: https://lore.kernel.org/r/20260812194234.GA693895-robh@kernel.org Signed-off-by: Hongyang Zhao Link: https://patch.msgid.link/20260813-b4-es8316-binding-conditional-fix-v1-1-6cd56aa1370c@thundersoft.com Signed-off-by: Mark Brown --- .../devicetree/bindings/sound/everest,es8316.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/sound/everest,es8316.yaml b/Documentation/devicetree/bindings/sound/everest,es8316.yaml index 276c73bb47907b..f4ff23120c5b55 100644 --- a/Documentation/devicetree/bindings/sound/everest,es8316.yaml +++ b/Documentation/devicetree/bindings/sound/everest,es8316.yaml @@ -33,9 +33,10 @@ allOf: - if: properties: compatible: - contains: - const: everest,es8316 - else: + not: + contains: + const: everest,es8316 + then: properties: avdd-supply: false cpvdd-supply: false From ba5401135aa508f0cb5414f269edba1fe90460da Mon Sep 17 00:00:00 2001 From: Shenghao Ding Date: Thu, 13 Aug 2026 16:05:40 +0800 Subject: [PATCH 542/612] ASoC: tas2781: Refactor calibration start kcontrol creation to separate helper Move the tas2781-specific calibration start kcontrol initialization logic out of tasdevice_create_cali_ctrls() into a new dedicated helper function create_tas2781_cali_start_ktrl(). This change eliminates duplicate inline code in the main calibration control registration routine, improves code readability, and makes further extension for custom calibration parameters much easier. No functional behavior changes. Signed-off-by: Shenghao Ding Link: https://patch.msgid.link/20260813080540.1030-1-shenghao-ding@ti.com Signed-off-by: Mark Brown --- sound/soc/codecs/tas2781-i2c.c | 71 ++++++++++++++++++++-------------- 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/sound/soc/codecs/tas2781-i2c.c b/sound/soc/codecs/tas2781-i2c.c index c029345b464442..6ea4d6488f4e02 100644 --- a/sound/soc/codecs/tas2781-i2c.c +++ b/sound/soc/codecs/tas2781-i2c.c @@ -1456,6 +1456,43 @@ static void alpa_cali_update(struct bulk_reg_val *p, p->val_len = 4; } +static int create_tas2781_cali_start_ktrl(struct tasdevice_priv + *priv, struct snd_kcontrol_new *cali_ctrl) +{ + struct soc_bytes_ext *ext_cali_start; + char *cali_start_name; + + ext_cali_start = devm_kzalloc(priv->dev, + sizeof(*ext_cali_start), GFP_KERNEL); + if (!ext_cali_start) + return -ENOMEM; + + cali_start_name = devm_kstrdup(priv->dev, + "Calibration Start", GFP_KERNEL); + if (!cali_start_name) + return -ENOMEM; + /* + * package structure for tas2781 ftc start: + * Pkg len (1 byte) + * Reg id (1 byte, constant 'r') + * book, page, register for pilot threshold, pilot tone + * and sine gain (12 bytes) + * for (i = 0; i < Device-Sum; i++) { + * Device #i index_info (1 byte) + * Sine gain for Device #i (8 bytes) + * } + */ + ext_cali_start->max = 14 + priv->ndev * 9; + cali_ctrl->name = cali_start_name; + cali_ctrl->iface = SNDRV_CTL_ELEM_IFACE_MIXER; + cali_ctrl->info = snd_soc_bytes_info_ext; + cali_ctrl->put = tas2781_calib_start_put; + cali_ctrl->get = tasdev_nop_get; + cali_ctrl->private_value = (unsigned long)ext_cali_start; + + return 0; +} + static int tasdevice_create_cali_ctrls(struct tasdevice_priv *priv) { struct calidata *cali_data = &priv->cali_data; @@ -1574,37 +1611,11 @@ static int tasdevice_create_cali_ctrls(struct tasdevice_priv *priv) */ cali_data->data[0] = 0xff; if (priv->chip_id == TAS2781) { - struct soc_bytes_ext *ext_cali_start; - char *cali_start_name; - - ext_cali_start = devm_kzalloc(priv->dev, - sizeof(*ext_cali_start), GFP_KERNEL); - if (!ext_cali_start) - return -ENOMEM; - - cali_start_name = devm_kstrdup(priv->dev, - "Calibration Start", GFP_KERNEL); - if (!cali_start_name) - return -ENOMEM; - /* - * package structure for tas2781 ftc start: - * Pkg len (1 byte) - * Reg id (1 byte, constant 'r') - * book, page, register for pilot threshold, pilot tone - * and sine gain (12 bytes) - * for (i = 0; i < Device-Sum; i++) { - * Device #i index_info (1 byte) - * Sine gain for Device #i (8 bytes) - * } - */ - ext_cali_start->max = 14 + priv->ndev * 9; - cali_ctrls[i].name = cali_start_name; - cali_ctrls[i].iface = SNDRV_CTL_ELEM_IFACE_MIXER; - cali_ctrls[i].info = snd_soc_bytes_info_ext; - cali_ctrls[i].put = tas2781_calib_start_put; - cali_ctrls[i].get = tasdev_nop_get; - cali_ctrls[i].private_value = (unsigned long)ext_cali_start; + rc = create_tas2781_cali_start_ktrl(priv, &cali_ctrls[i]); + if (rc != 0) + return rc; i++; + } return snd_soc_add_component_controls(priv->codec, cali_ctrls, From 9c0564fcc21aec4f5b7648f61865ce3fc2b84a7f Mon Sep 17 00:00:00 2001 From: Neil Andrews Date: Thu, 13 Aug 2026 20:41:24 +0000 Subject: [PATCH 543/612] ALSA: usb-audio: Rename the Audient iD14 monitor mix volume control On the Audient iD14 (2708:0008), feature unit 12 is traced through to the Speaker output terminal and is therefore exported as "Speaker Playback Volume". The name fits it badly. It advertises Volume on only four of its six logical channels, which the driver records as cmask=0xf, channels=4 on a 6-channel playback stream, and it sits on the monitor mixer branch rather than in the direct playback path: INPUT_TERMINAL 2 (USB streaming, 6ch) -> EXTENSION_UNIT 51 -> FEATURE_UNIT 10 (no controls) -> OUTPUT_TERMINAL 20 (Speaker) while FU 12 hangs off MIXER_UNIT 60 and feeds back into EXTENSION_UNIT 51. Userspace adopts the control as the stream's hardware playback volume, so any setting below 0 dB attenuates part of the stream and not the rest. Measured over the device's own digital loopback, with one -12 dBFS tone per channel played straight to hw:, PCM channel 0 is unaffected while channel 1 tracks the control: at 107/127 (-20 dB) the two read -15.89 and -35.89 dBFS, a 20.00 dB imbalance, and at 127/127 both read -15.89 dBFS. Give the unit a non-standard name so that it is no longer taken for the stream's master volume. Dropping the control instead also fixes the imbalance, but FU 12 keeps its value across a module reload, so dropping it strands a device that is already attenuated with nothing able to reset it. Renaming leaves the monitor gain reachable and that recovery path intact. The mapped name ends in "Playback" because a name from the map suppresses the automatic " Playback" but still gets " Volume" appended; the control comes out as "Monitor Mix Playback Volume". Tested on the ACP path with PipeWire, which is where the problem reproduces: the control now stays at 127 at every volume setting and the imbalance is 0.00 dB, and setting it by hand to 107 and back to 127 gives 20.00 dB and 0.00 dB as before. Link: https://lore.kernel.org/linux-sound/0102019fed22f9d3-fa294ec5-02f1-4fd3-b3fa-76efc14331cc-000000@eu-west-1.amazonses.com/T/#u Signed-off-by: Neil Andrews Link: https://patch.msgid.link/0102019ffcdbb1e6-9b59d3cc-ef05-4df1-8f9f-fb2f425bcda2-000000@eu-west-1.amazonses.com Signed-off-by: Takashi Iwai --- sound/usb/mixer_maps.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c index ce27fc871f5139..8046d5987d5b1c 100644 --- a/sound/usb/mixer_maps.c +++ b/sound/usb/mixer_maps.c @@ -505,6 +505,19 @@ 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. + */ +static const struct usbmix_name_map audient_id14_map[] = { + { 12, "Monitor Mix Playback" }, /* FU, partial coverage */ + {} +}; + /* * Control map entries */ @@ -588,6 +601,11 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { .id = USB_ID(0x2573, 0x0008), .map = maya44_map, }, + { + /* Audient iD14 */ + .id = USB_ID(0x2708, 0x0008), + .map = audient_id14_map, + }, { /* KEF X300A */ .id = USB_ID(0x27ac, 0x1000), From e5b03754d2707d89b8cc857f9c9c13efb1c6671d Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Fri, 14 Aug 2026 16:41:01 +0800 Subject: [PATCH 544/612] ALSA: hda/realtek: Drop duplicate quirk for Lenovo 0x17aa:0x38df The PCI SSID 17aa:38df is listed twice in alc269_fixup_tbl[], both mapping to the same fixup ALC287_FIXUP_TAS2781_I2C: SND_PCI_QUIRK(0x17aa, 0x38df, "Yoga Y990 Intel YC Dual", ALC287_FIXUP_TAS2781_I2C), ... SND_PCI_QUIRK(0x17aa, 0x38df, "Y990 YG DUAL", ALC287_FIXUP_TAS2781_I2C), The HDA quirk lookup (hda_quirk_lookup_id()) returns the first matching entry, so the second occurrence never takes effect; it is dead code. Drop the second entry. The retained "Yoga Y990 Intel YC Dual" label also follows the naming of the neighbouring 0x38e0 entry ("Yoga Y990 Intel VECO Dual"). Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20260814084101.504471-1-zhangheng@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 - 1 file changed, 1 deletion(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index be7287ea24eccd..3f0e276fc60603 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -8101,7 +8101,6 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x38df, "Yoga Y990 Intel YC Dual", ALC287_FIXUP_TAS2781_I2C), SND_PCI_QUIRK(0x17aa, 0x38e0, "Yoga Y990 Intel VECO Dual", ALC287_FIXUP_TAS2781_I2C), SND_PCI_QUIRK(0x17aa, 0x38f8, "Yoga Book 9i", ALC287_FIXUP_TAS2781_I2C), - SND_PCI_QUIRK(0x17aa, 0x38df, "Y990 YG DUAL", ALC287_FIXUP_TAS2781_I2C), /* Legion 7 15ASH11 shares PCI SSID 17aa:38f9 with Thinkbook 16P Gen5; * use codec SSID to distinguish them */ From 5ae1a690c522fea2900ff56c8c2ace7b059f5e04 Mon Sep 17 00:00:00 2001 From: Aleksandr Nogikh Date: Fri, 14 Aug 2026 12:05:43 +0000 Subject: [PATCH 545/612] ALSA: core: Fix use-after-free in snd_card_do_free() A use-after-free was detected in snd_card_do_free() when a sound card managed by devres is unbound while a user-space application still holds an open file descriptor. For managed cards, the memory is allocated using devres_alloc(), and its release function is set to __snd_card_release(), which calls snd_card_free(). When the device is unbound, the unbind thread calls snd_card_free(), which drops a reference to the card's device. If the user thread still has an open file descriptor, the reference count does not reach zero, and the unbind thread blocks on wait_for_completion(&released). When the user thread closes the file descriptor, it drops the final reference, invoking the device release callback release_card_device(), which calls snd_card_do_free(). snd_card_do_free() performs cleanup and calls complete(card->release_completion). This wakes up the unbind thread, which returns from snd_card_free() and __snd_card_release(). The devres core then immediately frees the memory block containing the snd_card structure. Meanwhile, the user thread continues execution in snd_card_do_free() and evaluates `if (!card->managed)`. It reads the `managed` boolean from the snd_card structure that was just freed by the unbind thread, triggering a KASAN use-after-free. Fix this by caching the value of card->managed in a local variable before calling complete(). This ensures that the card pointer is not dereferenced after the unbind thread has been woken up and potentially freed the card. BUG: KASAN: use-after-free in snd_card_do_free sound/core/init.c:604 [inline] BUG: KASAN: use-after-free in release_card_device+0x1ab/0x1b0 sound/core/init.c:153 Read of size 1 at addr ffff8881912ec909 by task syz-executor130/5857 Call Trace: dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120 print_address_description+0x55/0x1e0 mm/kasan/report.c:378 print_report+0x58/0x70 mm/kasan/report.c:482 kasan_report+0x117/0x150 mm/kasan/report.c:595 snd_card_do_free sound/core/init.c:604 [inline] release_card_device+0x1ab/0x1b0 sound/core/init.c:153 device_release+0xc4/0x1f0 drivers/base/core.c:-1 kobject_cleanup lib/kobject.c:689 [inline] kobject_release lib/kobject.c:720 [inline] kref_put include/linux/kref.h:65 [inline] kobject_put+0x222/0x550 lib/kobject.c:737 snd_card_file_remove+0x331/0x390 sound/core/init.c:1125 snd_pcm_release+0x12c/0x160 sound/core/pcm_native.c:2986 __fput+0x418/0xa50 fs/file_table.c:512 fput_close_sync+0x11f/0x240 fs/file_table.c:617 __do_sys_close fs/open.c:1511 [inline] __se_sys_close fs/open.c:1496 [inline] __x64_sys_close+0x7e/0x110 fs/open.c:1496 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f Fixes: e8ad415b7a55 ("ALSA: core: Add managed card creation") Assisted-by: Gemini:gemini-3.6-flash Gemini:gemini-3.1-pro-preview syzbot Reported-by: syzbot+7061d72c26b7daebe2b4@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=7061d72c26b7daebe2b4 Link: https://syzkaller.appspot.com/ai_job?id=24752a23-f0b6-49c1-bf20-4fa89c2e7eb2 Signed-off-by: Aleksandr Nogikh Link: https://patch.msgid.link/02042186-27b7-42a9-b64e-f93ce8fbe05a@mail.kernel.org Signed-off-by: Takashi Iwai --- sound/core/init.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/core/init.c b/sound/core/init.c index 19ec68db561b8d..2f7f83a7611b27 100644 --- a/sound/core/init.c +++ b/sound/core/init.c @@ -583,6 +583,8 @@ EXPORT_SYMBOL_GPL(snd_card_disconnect_sync); static int snd_card_do_free(struct snd_card *card) { + bool managed = card->managed; + card->releasing = true; #if IS_ENABLED(CONFIG_SND_MIXER_OSS) if (snd_mixer_oss_notify_callback) @@ -603,7 +605,7 @@ static int snd_card_do_free(struct snd_card *card) } if (card->release_completion) complete(card->release_completion); - if (!card->managed) + if (!managed) kfree(card); return 0; } From 888162dabf64128603b12ac2d23236cf2086b7ef Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Mon, 10 Aug 2026 11:40:42 +0100 Subject: [PATCH 546/612] ASoC: cs35l56: Request IRQ in cs35l56_common_probe() Call cs35l56_irq_request() in cs35l56_common_probe() instead of calling it afterwards in the probe() for each bus type. Calling cs35l56_irq_request() in each bus probe() is a legacy of dealing with the oddities of the SoundWire framework. It's no longer serving any useful purpose to do it outside of the main cs35l56_common_probe(). Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260810104045.60701-2-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l56-i2c.c | 10 +--------- sound/soc/codecs/cs35l56-sdw.c | 6 +----- sound/soc/codecs/cs35l56-spi.c | 10 +--------- sound/soc/codecs/cs35l56.c | 12 ++++++++++-- sound/soc/codecs/cs35l56.h | 2 +- 5 files changed, 14 insertions(+), 26 deletions(-) diff --git a/sound/soc/codecs/cs35l56-i2c.c b/sound/soc/codecs/cs35l56-i2c.c index 4f6ddf1c5a3f6a..5e69ddbe342a1d 100644 --- a/sound/soc/codecs/cs35l56-i2c.c +++ b/sound/soc/codecs/cs35l56-i2c.c @@ -51,15 +51,7 @@ static int cs35l56_i2c_probe(struct i2c_client *client) return dev_err_probe(cs35l56->base.dev, ret, "Failed to allocate register map\n"); } - ret = cs35l56_common_probe(cs35l56); - if (ret != 0) - return ret; - - ret = cs35l56_irq_request(&cs35l56->base, client->irq); - if (ret < 0) - cs35l56_remove(cs35l56); - - return ret; + return cs35l56_common_probe(cs35l56, client->irq); } static void cs35l56_i2c_remove(struct i2c_client *client) diff --git a/sound/soc/codecs/cs35l56-sdw.c b/sound/soc/codecs/cs35l56-sdw.c index 303d37e7d0bfd7..14bb5d1793d334 100644 --- a/sound/soc/codecs/cs35l56-sdw.c +++ b/sound/soc/codecs/cs35l56-sdw.c @@ -484,11 +484,7 @@ static int cs35l56_sdw_probe(struct sdw_slave *peripheral, const struct sdw_devi /* Start in cache-only until device is enumerated */ regcache_cache_only(cs35l56->base.regmap, true); - ret = cs35l56_common_probe(cs35l56); - if (ret != 0) - return ret; - - return 0; + return cs35l56_common_probe(cs35l56, -EINVAL); } static void cs35l56_sdw_remove(struct sdw_slave *peripheral) diff --git a/sound/soc/codecs/cs35l56-spi.c b/sound/soc/codecs/cs35l56-spi.c index b1eb924a5b6ccf..21b18da9e73d1d 100644 --- a/sound/soc/codecs/cs35l56-spi.c +++ b/sound/soc/codecs/cs35l56-spi.c @@ -40,15 +40,7 @@ static int cs35l56_spi_probe(struct spi_device *spi) if (ret) return ret; - ret = cs35l56_common_probe(cs35l56); - if (ret != 0) - return ret; - - ret = cs35l56_irq_request(&cs35l56->base, spi->irq); - if (ret < 0) - cs35l56_remove(cs35l56); - - return ret; + return cs35l56_common_probe(cs35l56, spi->irq); } static void cs35l56_spi_remove(struct spi_device *spi) diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c index 0b7b080939a184..619be47060a43a 100644 --- a/sound/soc/codecs/cs35l56.c +++ b/sound/soc/codecs/cs35l56.c @@ -1942,7 +1942,7 @@ static int cs35l56_try_get_broken_sdca_spkid_gpio(struct cs35l56_private *cs35l5 return ret; } -int cs35l56_common_probe(struct cs35l56_private *cs35l56) +int cs35l56_common_probe(struct cs35l56_private *cs35l56, int irq) { int ret; @@ -2019,16 +2019,24 @@ int cs35l56_common_probe(struct cs35l56_private *cs35l56) goto err_remove_wm_adsp; } + ret = cs35l56_irq_request(&cs35l56->base, irq); + if (ret) + goto err_remove_wm_adsp; + ret = snd_soc_register_component(cs35l56->base.dev, &soc_component_dev_cs35l56, cs35l56_dai, ARRAY_SIZE(cs35l56_dai)); if (ret < 0) { dev_err_probe(cs35l56->base.dev, ret, "Register codec failed\n"); - goto err_remove_wm_adsp; + goto err_free_irq; } return 0; +err_free_irq: + if (cs35l56->base.irq) + devm_free_irq(cs35l56->base.dev, cs35l56->base.irq, &cs35l56->base); + err_remove_wm_adsp: wm_adsp2_remove(&cs35l56->dsp); diff --git a/sound/soc/codecs/cs35l56.h b/sound/soc/codecs/cs35l56.h index 9acd2e7e17c93a..1ddee9ab6a8761 100644 --- a/sound/soc/codecs/cs35l56.h +++ b/sound/soc/codecs/cs35l56.h @@ -78,7 +78,7 @@ int cs35l56_system_resume_early(struct device *dev); int cs35l56_system_resume(struct device *dev); irqreturn_t cs35l56_irq(int irq, void *data); int cs35l56_irq_request(struct cs35l56_base *cs35l56_base, int irq); -int cs35l56_common_probe(struct cs35l56_private *cs35l56); +int cs35l56_common_probe(struct cs35l56_private *cs35l56, int irq); int cs35l56_init(struct cs35l56_private *cs35l56); void cs35l56_remove(struct cs35l56_private *cs35l56); From a075fef187a6fe8ef99b022a69bc0b9ed584ba93 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Mon, 10 Aug 2026 11:40:43 +0100 Subject: [PATCH 547/612] ASoC: cs35l56: Move cs35l56_irq_request() after cs35l56_irq() cs35l56_irq_request() references cs35l56_irq() but was above it in the source (although they are in the other order in the header file). Switch to convertional C ordering. This is preparation for a future patch that will stop exporting cs35l56_irq() and make it static. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260810104045.60701-3-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l56-shared.c | 38 +++++++++++++++---------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/sound/soc/codecs/cs35l56-shared.c b/sound/soc/codecs/cs35l56-shared.c index b40bd3a8d27b99..0880b6a02247cf 100644 --- a/sound/soc/codecs/cs35l56-shared.c +++ b/sound/soc/codecs/cs35l56-shared.c @@ -616,25 +616,6 @@ void cs35l56_system_reset(struct cs35l56_base *cs35l56_base, bool is_soundwire) } EXPORT_SYMBOL_NS_GPL(cs35l56_system_reset, "SND_SOC_CS35L56_SHARED"); -int cs35l56_irq_request(struct cs35l56_base *cs35l56_base, int irq) -{ - int ret; - - if (irq < 1) - return 0; - - ret = devm_request_threaded_irq(cs35l56_base->dev, irq, NULL, cs35l56_irq, - IRQF_ONESHOT | IRQF_SHARED | IRQF_TRIGGER_LOW, - "cs35l56", cs35l56_base); - if (!ret) - cs35l56_base->irq = irq; - else - dev_err(cs35l56_base->dev, "Failed to get IRQ: %d\n", ret); - - return ret; -} -EXPORT_SYMBOL_NS_GPL(cs35l56_irq_request, "SND_SOC_CS35L56_SHARED"); - irqreturn_t cs35l56_irq(int irq, void *data) { struct cs35l56_base *cs35l56_base = data; @@ -694,6 +675,25 @@ irqreturn_t cs35l56_irq(int irq, void *data) } EXPORT_SYMBOL_NS_GPL(cs35l56_irq, "SND_SOC_CS35L56_SHARED"); +int cs35l56_irq_request(struct cs35l56_base *cs35l56_base, int irq) +{ + int ret; + + if (irq < 1) + return 0; + + ret = devm_request_threaded_irq(cs35l56_base->dev, irq, NULL, cs35l56_irq, + IRQF_ONESHOT | IRQF_SHARED | IRQF_TRIGGER_LOW, + "cs35l56", cs35l56_base); + if (!ret) + cs35l56_base->irq = irq; + else + dev_err(cs35l56_base->dev, "Failed to get IRQ: %d\n", ret); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(cs35l56_irq_request, "SND_SOC_CS35L56_SHARED"); + int cs35l56_is_fw_reload_needed(struct cs35l56_base *cs35l56_base) { unsigned int val; From ee1811eacdbba15a374957c2bcc6ba7102e2b781 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Mon, 10 Aug 2026 11:40:44 +0100 Subject: [PATCH 548/612] soundwire: bus_type: Create IRQ mapping before calling driver probe() Call sdw_irq_create_mapping() before calling the peripheral driver probe() so that it is possible to request the IRQ during probe(). Previously creation of the mapping was conditional on the use_domain_irq flag in the driver properties. But these are filled in after probe(), which meant it wasn't possible to request the IRQ during probe(). This was ok for MFD drivers where only children requested the IRQ. But for normal drivers it led to the non-standard behavior of having to defer requesting the IRQ until after probe(). Signed-off-by: Richard Fitzgerald Acked-by: Vinod Koul Link: https://patch.msgid.link/20260810104045.60701-4-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- drivers/soundwire/bus_type.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/soundwire/bus_type.c b/drivers/soundwire/bus_type.c index e73c1bea905936..d61a97c5b41ef7 100644 --- a/drivers/soundwire/bus_type.c +++ b/drivers/soundwire/bus_type.c @@ -105,6 +105,9 @@ static int sdw_bus_probe(struct device *dev) } slave->index = ret; + /* Create IRQ mapping now so the driver can get it in probe() */ + sdw_irq_create_mapping(slave); + ret = drv->probe(slave, id); if (ret) { ida_free(&slave->bus->slave_ida, slave->index); @@ -117,9 +120,6 @@ static int sdw_bus_probe(struct device *dev) if (drv->ops && drv->ops->read_prop) drv->ops->read_prop(slave); - if (slave->prop.use_domain_irq) - sdw_irq_create_mapping(slave); - /* init the dynamic sysfs attributes we need */ ret = sdw_slave_sysfs_dpn_init(slave); if (ret < 0) From 243ca1fb53834dfa445ccb5d4dd8c7411e89b878 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Mon, 10 Aug 2026 11:40:45 +0100 Subject: [PATCH 549/612] ASoC: cs35l56: Use IRQ provided by the SoundWire core Replace the custom SoundWire IRQ handling with the generic nested IRQ provided by the SoundWire core. This removes the local IRQ work function and the convoluted IRQ masking and pm_runtime management around it. We still need the local functions to mask/disable and unmask/enable the SoundWire interrupts because the devices handled by the cs35l56 driver don't have the generic mask bit for the ImpDef1 interrupt so masking and unmasking has to use a custom mask bit. cs35l56_sdw_remove() doesn't need to call cs35l56_disable_sdw_interrupts() now that there isn't a local work function to be flushed. It only masks the custom interrupt mask bit and the rest of the handler cleanup will be done the normal way by devm_free_irq() in cs35l56_remove(). Similar applies to cs35l56_sdw_system_suspend() - it is enough to write the custom mask bits. cs35l56_irq() doesn't need to be exported because cs35l56_sdw.c isn't calling it. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260810104045.60701-5-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- include/sound/cs35l56.h | 1 - sound/soc/codecs/Kconfig | 1 + sound/soc/codecs/cs35l56-sdw.c | 65 +++++++------------------------ sound/soc/codecs/cs35l56-shared.c | 3 +- sound/soc/codecs/cs35l56.c | 45 ++++++++++----------- sound/soc/codecs/cs35l56.h | 9 +---- 6 files changed, 38 insertions(+), 86 deletions(-) diff --git a/include/sound/cs35l56.h b/include/sound/cs35l56.h index 2490b72c0a7a8c..45a5df574aa6d2 100644 --- a/include/sound/cs35l56.h +++ b/include/sound/cs35l56.h @@ -417,7 +417,6 @@ void cs35l56_wait_control_port_ready(void); void cs35l56_wait_min_reset_pulse(void); void cs35l56_system_reset(struct cs35l56_base *cs35l56_base, bool is_soundwire); int cs35l56_irq_request(struct cs35l56_base *cs35l56_base, int irq); -irqreturn_t cs35l56_irq(int irq, void *data); int cs35l56_is_fw_reload_needed(struct cs35l56_base *cs35l56_base); int cs35l56_runtime_suspend_common(struct cs35l56_base *cs35l56_base); int cs35l56_runtime_resume_common(struct cs35l56_base *cs35l56_base, bool is_soundwire); diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 43162c7d23cf2d..d731092824476e 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -896,6 +896,7 @@ config SND_SOC_CS35L56_SDW tristate "Cirrus Logic CS35L56 CODEC (SDW)" depends on SOUNDWIRE select REGMAP_SOUNDWIRE + select IRQ_DOMAIN select SND_SOC_CS35L56 select SND_SOC_CS35L56_SHARED help diff --git a/sound/soc/codecs/cs35l56-sdw.c b/sound/soc/codecs/cs35l56-sdw.c index 14bb5d1793d334..4fba59e80c37e1 100644 --- a/sound/soc/codecs/cs35l56-sdw.c +++ b/sound/soc/codecs/cs35l56-sdw.c @@ -231,7 +231,7 @@ static void cs35l56_sdw_init(struct sdw_slave *peripheral) * a soft reset. */ if (cs35l56->base.init_done) - cs35l56_unmask_soundwire_interrupts(cs35l56->sdw_peripheral); + cs35l56_unmask_soundwire_interrupts(cs35l56); out: pm_runtime_put_autosuspend(cs35l56->base.dev); @@ -240,47 +240,17 @@ static void cs35l56_sdw_init(struct sdw_slave *peripheral) static int cs35l56_sdw_interrupt(struct sdw_slave *peripheral, struct sdw_slave_intr_status *status) { - struct cs35l56_private *cs35l56 = dev_get_drvdata(&peripheral->dev); - - /* SoundWire core holds our pm_runtime when calling this function. */ - - dev_dbg(cs35l56->base.dev, "int control_port=%#x\n", status->control_port); - - if ((status->control_port & SDW_SCP_INT1_IMPL_DEF) == 0) - return 0; - /* - * Prevent bus manager suspending and possibly issuing a - * bus-reset before the queued work has run. + * The IRQ itself was handled through the regmap_irq handler, this is + * just clearing up the additional Cirrus SoundWire registers that are + * not covered by the SoundWire framework or the IRQ handler itself. */ - pm_runtime_get_noresume(cs35l56->base.dev); - - /* - * Mask and clear until it has been handled. - * None of the interrupts are time-critical so use the - * power-efficient queue. - */ - cs35l56_mask_soundwire_interrupts(peripheral); - queue_work(system_power_efficient_wq, &cs35l56->sdw_irq_work); + sdw_read_no_pm(peripheral, CS35L56_SDW_GEN_INT_STAT_1); + sdw_write_no_pm(peripheral, CS35L56_SDW_GEN_INT_STAT_1, 0xFF); return 0; } -static void cs35l56_sdw_irq_work(struct work_struct *work) -{ - struct cs35l56_private *cs35l56 = container_of(work, - struct cs35l56_private, - sdw_irq_work); - - cs35l56_irq(-1, &cs35l56->base); - - /* unmask interrupts */ - if (!cs35l56->sdw_irq_no_unmask) - cs35l56_unmask_soundwire_interrupts(cs35l56->sdw_peripheral); - - pm_runtime_put_autosuspend(cs35l56->base.dev); -} - static int cs35l56_sdw_read_prop(struct sdw_slave *peripheral) { struct cs35l56_private *cs35l56 = dev_get_drvdata(&peripheral->dev); @@ -302,6 +272,7 @@ static int cs35l56_sdw_read_prop(struct sdw_slave *peripheral) prop->source_ports = BIT(CS35L56_SDW1_CAPTURE_PORT); prop->sink_ports = BIT(CS35L56_SDW1_PLAYBACK_PORT); prop->paging_support = true; + prop->use_domain_irq = true; prop->quirks = SDW_SLAVE_QUIRKS_INVALID_INITIAL_PARITY; prop->scp_int1_mask = SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY | SDW_SCP_INT1_IMPL_DEF; @@ -406,7 +377,7 @@ static int __maybe_unused cs35l56_sdw_runtime_resume(struct device *dev) if (ret) return ret; - cs35l56_unmask_soundwire_interrupts(cs35l56->sdw_peripheral); + cs35l56_unmask_soundwire_interrupts(cs35l56); return 0; } @@ -418,21 +389,12 @@ static int __maybe_unused cs35l56_sdw_system_suspend(struct device *dev) if (!cs35l56->base.init_done) return 0; - cs35l56_disable_sdw_interrupts(cs35l56); + /* runtime_resume unmasks the interrupt */ + cs35l56_mask_soundwire_interrupts(cs35l56); return cs35l56_system_suspend(dev); } -static int __maybe_unused cs35l56_sdw_system_resume(struct device *dev) -{ - struct cs35l56_private *cs35l56 = dev_get_drvdata(dev); - - cs35l56->sdw_irq_no_unmask = false; - /* runtime_resume re-enables the interrupt */ - - return cs35l56_system_resume(dev); -} - static int cs35l56_sdw_probe(struct sdw_slave *peripheral, const struct sdw_device_id *id) { struct device *dev = &peripheral->dev; @@ -447,7 +409,6 @@ static int cs35l56_sdw_probe(struct sdw_slave *peripheral, const struct sdw_devi cs35l56->base.dev = dev; cs35l56->sdw_peripheral = peripheral; cs35l56->sdw_link_num = peripheral->bus->link_id; - INIT_WORK(&cs35l56->sdw_irq_work, cs35l56_sdw_irq_work); dev_set_drvdata(dev, cs35l56); @@ -484,21 +445,21 @@ static int cs35l56_sdw_probe(struct sdw_slave *peripheral, const struct sdw_devi /* Start in cache-only until device is enumerated */ regcache_cache_only(cs35l56->base.regmap, true); - return cs35l56_common_probe(cs35l56, -EINVAL); + return cs35l56_common_probe(cs35l56, peripheral->irq); } static void cs35l56_sdw_remove(struct sdw_slave *peripheral) { struct cs35l56_private *cs35l56 = dev_get_drvdata(&peripheral->dev); - cs35l56_disable_sdw_interrupts(cs35l56); + cs35l56_mask_soundwire_interrupts(cs35l56); cs35l56_remove(cs35l56); } static const struct dev_pm_ops cs35l56_sdw_pm = { SET_RUNTIME_PM_OPS(cs35l56_sdw_runtime_suspend, cs35l56_sdw_runtime_resume, NULL) - SYSTEM_SLEEP_PM_OPS(cs35l56_sdw_system_suspend, cs35l56_sdw_system_resume) + SYSTEM_SLEEP_PM_OPS(cs35l56_sdw_system_suspend, cs35l56_system_resume) LATE_SYSTEM_SLEEP_PM_OPS(cs35l56_system_suspend_late, cs35l56_system_resume_early) /* NOIRQ stage not needed, SoundWire doesn't use a hard IRQ */ }; diff --git a/sound/soc/codecs/cs35l56-shared.c b/sound/soc/codecs/cs35l56-shared.c index 0880b6a02247cf..7b3e37d462d61e 100644 --- a/sound/soc/codecs/cs35l56-shared.c +++ b/sound/soc/codecs/cs35l56-shared.c @@ -616,7 +616,7 @@ void cs35l56_system_reset(struct cs35l56_base *cs35l56_base, bool is_soundwire) } EXPORT_SYMBOL_NS_GPL(cs35l56_system_reset, "SND_SOC_CS35L56_SHARED"); -irqreturn_t cs35l56_irq(int irq, void *data) +static irqreturn_t cs35l56_irq(int irq, void *data) { struct cs35l56_base *cs35l56_base = data; unsigned int status1 = 0, status8 = 0, status20 = 0; @@ -673,7 +673,6 @@ irqreturn_t cs35l56_irq(int irq, void *data) return IRQ_HANDLED; } -EXPORT_SYMBOL_NS_GPL(cs35l56_irq, "SND_SOC_CS35L56_SHARED"); int cs35l56_irq_request(struct cs35l56_base *cs35l56_base, int irq) { diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c index 619be47060a43a..b9118ad8fab541 100644 --- a/sound/soc/codecs/cs35l56.c +++ b/sound/soc/codecs/cs35l56.c @@ -37,48 +37,49 @@ #include "wm_adsp.h" #include "cs35l56.h" -void cs35l56_mask_soundwire_interrupts(struct sdw_slave *peripheral) +void cs35l56_mask_soundwire_interrupts(struct cs35l56_private *cs35l56) { /* + * Mask unconditionally. + * * The read of GEN_INT_STAT_1 is required as per the SoundWire spec * for interrupt status bits to clear. * GEN_INT_MASK_1 masks the _inputs_ to GEN_INT_STAT1. */ - sdw_write_no_pm(peripheral, CS35L56_SDW_GEN_INT_MASK_1, 0); - sdw_read_no_pm(peripheral, CS35L56_SDW_GEN_INT_STAT_1); - sdw_write_no_pm(peripheral, CS35L56_SDW_GEN_INT_STAT_1, 0xFF); + sdw_write_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_MASK_1, 0); + sdw_read_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_STAT_1); + sdw_write_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_STAT_1, 0xFF); } EXPORT_SYMBOL_NS_GPL(cs35l56_mask_soundwire_interrupts, "SND_SOC_CS35L56_CORE"); -void cs35l56_unmask_soundwire_interrupts(struct sdw_slave *peripheral) +void cs35l56_unmask_soundwire_interrupts(struct cs35l56_private *cs35l56) { - sdw_write_no_pm(peripheral, CS35L56_SDW_GEN_INT_MASK_1, CS35L56_SDW_INT_MASK_CODEC_IRQ); + if (!cs35l56->base.irq) + return; + + sdw_write_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_MASK_1, + CS35L56_SDW_INT_MASK_CODEC_IRQ); } EXPORT_SYMBOL_NS_GPL(cs35l56_unmask_soundwire_interrupts, "SND_SOC_CS35L56_CORE"); -void cs35l56_disable_sdw_interrupts(struct cs35l56_private *cs35l56) +static void cs35l56_disable_sdw_interrupts(struct cs35l56_private *cs35l56) { if (!cs35l56->sdw_peripheral) return; - cs35l56->sdw_irq_no_unmask = true; - flush_work(&cs35l56->sdw_irq_work); - - /* Mask interrupts and flush in case sdw_irq_work was queued again */ - cs35l56_mask_soundwire_interrupts(cs35l56->sdw_peripheral); - flush_work(&cs35l56->sdw_irq_work); + cs35l56_mask_soundwire_interrupts(cs35l56); + if (cs35l56->base.irq) + disable_irq(cs35l56->base.irq); } -EXPORT_SYMBOL_NS_GPL(cs35l56_disable_sdw_interrupts, "SND_SOC_CS35L56_CORE"); -void cs35l56_enable_sdw_interrupts(struct cs35l56_private *cs35l56) +static void cs35l56_enable_sdw_interrupts(struct cs35l56_private *cs35l56) { - if (!cs35l56->sdw_peripheral) + if (!cs35l56->sdw_peripheral || !cs35l56->base.irq) return; - cs35l56->sdw_irq_no_unmask = false; - cs35l56_unmask_soundwire_interrupts(cs35l56->sdw_peripheral); + enable_irq(cs35l56->base.irq); + cs35l56_unmask_soundwire_interrupts(cs35l56); } -EXPORT_SYMBOL_NS_GPL(cs35l56_enable_sdw_interrupts, "SND_SOC_CS35L56_CORE"); static int cs35l56_dsp_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event); @@ -828,11 +829,7 @@ static void cs35l56_patch(struct cs35l56_private *cs35l56, bool firmware_missing { int ret; - /* - * Disable SoundWire interrupts to prevent race with IRQ work. - * Setting sdw_irq_no_unmask prevents the handler re-enabling - * the SoundWire interrupt. - */ + /* Disable SoundWire interrupts to prevent race with IRQ handler thread */ cs35l56_disable_sdw_interrupts(cs35l56); ret = cs35l56_firmware_shutdown(&cs35l56->base); diff --git a/sound/soc/codecs/cs35l56.h b/sound/soc/codecs/cs35l56.h index 1ddee9ab6a8761..35c02ae17de3de 100644 --- a/sound/soc/codecs/cs35l56.h +++ b/sound/soc/codecs/cs35l56.h @@ -39,8 +39,6 @@ struct cs35l56_private { struct sdw_slave *sdw_peripheral; struct regmap *sdw_bus_regmap; const char *fallback_fw_suffix; - struct work_struct sdw_irq_work; - bool sdw_irq_no_unmask; bool soft_resetting; bool sdw_attached; struct completion init_completion; @@ -65,10 +63,8 @@ static inline struct cs35l56_private *cs35l56_private_from_base(struct cs35l56_b extern const struct dev_pm_ops cs35l56_pm_ops_i2c_spi; -void cs35l56_mask_soundwire_interrupts(struct sdw_slave *peripheral); -void cs35l56_unmask_soundwire_interrupts(struct sdw_slave *peripheral); -void cs35l56_disable_sdw_interrupts(struct cs35l56_private *cs35l56); -void cs35l56_enable_sdw_interrupts(struct cs35l56_private *cs35l56); +void cs35l56_mask_soundwire_interrupts(struct cs35l56_private *cs35l56); +void cs35l56_unmask_soundwire_interrupts(struct cs35l56_private *cs35l56); int cs35l56_system_suspend(struct device *dev); int cs35l56_system_suspend_late(struct device *dev); @@ -76,7 +72,6 @@ int cs35l56_system_suspend_no_irq(struct device *dev); int cs35l56_system_resume_no_irq(struct device *dev); int cs35l56_system_resume_early(struct device *dev); int cs35l56_system_resume(struct device *dev); -irqreturn_t cs35l56_irq(int irq, void *data); int cs35l56_irq_request(struct cs35l56_base *cs35l56_base, int irq); int cs35l56_common_probe(struct cs35l56_private *cs35l56, int irq); int cs35l56_init(struct cs35l56_private *cs35l56); From a698e4a60fa54268a38f4e66378851a196cb139b Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Fri, 14 Aug 2026 16:12:38 +0800 Subject: [PATCH 550/612] ASoC: SOF: validate topology volume range before allocation SOF treats the topology mixer min and max values as non-negative indices into its volume table. It stores them in signed fields, allocates max + 1 entries through an int argument, and later indexes the table with the stored range. An inverted range is invalid, while a maximum at or above INT_MAX cannot be represented safely after the increment or in the signed fields. Validate the complete range before storing it or allocating the table. Fixes: 311ce4fe7637 ("ASoC: SOF: Add support for loading topologies") Assisted-by: Codex:gpt-5 Signed-off-by: Pengpeng Hou Acked-by: Peter Ujfalusi Link: https://patch.msgid.link/20260814081238.25434-1-pengpeng@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/sof/topology.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c index 820513bb257714..8338133899aba3 100644 --- a/sound/soc/sof/topology.c +++ b/sound/soc/sof/topology.c @@ -846,6 +846,7 @@ static int sof_control_load_volume(struct snd_soc_component *scomp, struct snd_soc_tplg_mixer_control *mc = container_of(hdr, struct snd_soc_tplg_mixer_control, hdr); int tlv[SOF_TLV_ITEMS]; + u32 min, max; unsigned int mask; int ret; @@ -853,6 +854,11 @@ static int sof_control_load_volume(struct snd_soc_component *scomp, if (le32_to_cpu(mc->num_channels) > SND_SOC_TPLG_MAX_CHAN) return -EINVAL; + min = le32_to_cpu(mc->min); + max = le32_to_cpu(mc->max); + if (min > max || max >= INT_MAX) + return -EINVAL; + /* * If control has more than 2 channels we need to override the info. This is because even if * ASoC layer has defined topology's max channel count to SND_SOC_TPLG_MAX_CHAN = 8, the @@ -863,12 +869,12 @@ static int sof_control_load_volume(struct snd_soc_component *scomp, kc->info = snd_sof_volume_info; scontrol->comp_id = sdev->next_comp_id; - scontrol->min_volume_step = le32_to_cpu(mc->min); - scontrol->max_volume_step = le32_to_cpu(mc->max); + scontrol->min_volume_step = min; + scontrol->max_volume_step = max; scontrol->num_channels = le32_to_cpu(mc->num_channels); - scontrol->max = le32_to_cpu(mc->max); - if (le32_to_cpu(mc->max) == 1) + scontrol->max = max; + if (max == 1) goto skip; /* extract tlv data */ @@ -878,7 +884,7 @@ static int sof_control_load_volume(struct snd_soc_component *scomp, } /* set up volume table */ - ret = set_up_volume_table(scontrol, tlv, le32_to_cpu(mc->max) + 1); + ret = set_up_volume_table(scontrol, tlv, max + 1); if (ret < 0) { dev_err(scomp->dev, "error: setting up volume table\n"); return ret; @@ -911,7 +917,7 @@ static int sof_control_load_volume(struct snd_soc_component *scomp, return 0; err: - if (le32_to_cpu(mc->max) > 1) + if (max > 1) kfree(scontrol->volume_table); return ret; From 0c7aeb0f5eceb95b5887bd8e83fef865e5a49a13 Mon Sep 17 00:00:00 2001 From: Andrey Golovko Date: Fri, 14 Aug 2026 09:40:00 +0300 Subject: [PATCH 551/612] ASoC: tas2783-sdw: do not treat read-only Controls as writable The regmap has no writeable_reg callback, so regmap considers every register up to max_register writable. That includes the read-only SDCA Controls the driver itself describes: the Latency of every Entity, the Clock Valid of every Clock Source, the actual power state of the Power Domain Entity, the protection status, the algorithm ready flag and the Extension Unit id and version. Most of them are also listed in tas2783_reg_default[] with a placeholder of zero, even though a default for, say, a latency reading is meaningless. Reading such a Control caches its real value, which no longer matches the placeholder, so regcache_sync() then tries to write it back. The peripheral rejects the transaction with -ENODATA and the sync aborts, leaving the rest of the cache unrestored. Add a writeable_reg callback that refuses the read-only Controls and otherwise keeps the previous behaviour. Every selector it lists is the read-only Control of its Entity type in sdca_function.h, and none of the Controls the driver writes is affected: the requested power state, the mutes, the Cluster Index, the protection mode, the algorithm enable and the file download Controls all stay writable. The list is static because the BIOS on the affected machines describes no Smart Amp SDCA function, so the driver runs its fallback tables and sdca_regmap_writeable() is not available to it. It would be good to have the list confirmed against the hardware documentation, and to know whether the read-only Controls belong in tas2783_reg_default[] at all. Signed-off-by: Andrey Golovko Link: https://patch.msgid.link/20260814094000.22118-2-andrey.golovko@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/tas2783-sdw.c | 51 ++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/sound/soc/codecs/tas2783-sdw.c b/sound/soc/codecs/tas2783-sdw.c index c217da5fccdf61..caf8fe1bf4db3b 100644 --- a/sound/soc/codecs/tas2783-sdw.c +++ b/sound/soc/codecs/tas2783-sdw.c @@ -495,6 +495,56 @@ static bool tas2783_readable_register(struct device *dev, unsigned int reg) return tas2783_sdca_mbq_size(dev, reg) > 0; } +static bool tas2783_writeable_register(struct device *dev, unsigned int reg) +{ + /* + * The Latency Control of every Entity, together with the Power Domain + * actual state and the protection status, is read-only. They are + * listed in tas2783_reg_default[] with a placeholder value, so without + * this a regcache_sync() would try to write them back and the + * peripheral would reject the transaction, aborting the sync. + */ + switch (reg) { + case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU21, 0x10, 0): + case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU23, 0x10, 0): + case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU26, 0x10, 0): + case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_XU22, 0x06, 0): + case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_XU22, 0x07, 0): + case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_XU22, 0x08, 0): + case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS24, 0x02, 0): + case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS21, 0x02, 0): + case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS25, 0x02, 0): + case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS26, 0x02, 0): + case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS28, 0x02, 0): + case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_PDE23, 0x10, 0): + case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_UDMPU23, 0x06, 0): + case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_SAPU29, 0x05, 0): + case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_SAPU29, 0x11, 0): + case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_PPU21, 0x06, 0): + case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_PPU26, 0x06, 0): + case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_IT21, 0x08, 0): + case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_IT29, 0x08, 0): + case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_IT26, 0x08, 0): + case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_IT28, 0x08, 0): + case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT24, 0x08, 0): + case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT23, 0x08, 0): + case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT25, 0x08, 0): + case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT28, 0x08, 0): + case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MU26, 0x06, 0): + case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT127, 0x08, 0): + case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU127, 0x10, 0): + case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS127, 0x02, 0): + case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU21, 0x08, 0): + case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU21, 0x04, 0): + case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU26, 0x08, 0): + case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU26, 0x04, 0): + return false; + + default: + return tas2783_sdca_mbq_size(dev, reg) > 0; + } +} + static bool tas2783_volatile_register(struct device *dev, u32 reg) { switch (reg) { @@ -516,6 +566,7 @@ static const struct regmap_config tas_regmap = { .reg_bits = 32, .val_bits = 8, .readable_reg = tas2783_readable_register, + .writeable_reg = tas2783_writeable_register, .volatile_reg = tas2783_volatile_register, .reg_defaults = tas2783_reg_default, .num_reg_defaults = ARRAY_SIZE(tas2783_reg_default), From 317ea3b870fd56c2c467b85ac181b938cbf53405 Mon Sep 17 00:00:00 2001 From: Giulio Gualtierotti Date: Sun, 16 Aug 2026 11:42:23 +0200 Subject: [PATCH 552/612] ALSA: hda/realtek: Add micmute LED quirk for Acer Aspire A515-57 The Acer Aspire A515-57 with subsystem ID 1025:1616 and Realtek ALC256 uses GPIO2 (0x04) for the microphone mute LED. Without a quirk, the GPIO mask and direction are not configured and the LED does not follow the microphone mute state. Reuse ALC256_FIXUP_ACER_SFG16_MICMUTE_LED, which configures GPIO2 as the microphone mute LED. Tested on an Acer Aspire A515-57 with ALC256 (10ec:0256, subsystem 1025:1616). GPIO mask and direction are 0x04 and GPIO data switches between 0x00 and 0x04; the LED device is registered and follows the microphone mute state. Signed-off-by: Giulio Gualtierotti Link: https://patch.msgid.link/20260816094223.36617-1-ggualtierotti.dev@mailbox.org 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 3f0e276fc60603..0ebbf91921c574 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7063,6 +7063,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1025, 0x1597, "Acer Nitro 5 AN517-55", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x159e, "Acer Nitro 5 AN515-46", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x160e, "Acer PT316-51S", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x1616, "Acer Aspire A515-57", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), SND_PCI_QUIRK(0x1025, 0x161f, "Acer S40-54", ALC256_FIXUP_ACER_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1025, 0x1640, "Acer Aspire A315-44P", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), SND_PCI_QUIRK(0x1025, 0x166c, "Acer Predator PH16-71", ALC2XX_FIXUP_HEADSET_MIC), From 19b02fecd8f857909af779afe3816e487ac10cd0 Mon Sep 17 00:00:00 2001 From: Yashraj Ghule Date: Sun, 16 Aug 2026 16:36:55 +0530 Subject: [PATCH 553/612] ALSA: hda/realtek: Fix mute LED for HP Victus 15-fa1xxx (MB 8C3F) The HP Victus 15-fa1xxx with motherboard 8C3F is missing the existing mute LED quirk for ALC245 codecs. Add the 103c:8c3f subsystem ID to the existing ALC245_FIXUP_HP_MUTE_LED_COEFBIT quirk. Tested on HP Victus 15-fa1xxx (MB 8C3F). The mute LED works as intended. Signed-off-by: Yashraj Ghule Link: https://patch.msgid.link/20260816110655.11592-1-yashrajghule.221@gmail.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 0ebbf91921c574..67c5692eee1fb7 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7472,6 +7472,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8c21, "HP Pavilion Plus Laptop 14-ey0XXX", ALC245_FIXUP_HP_X360_MUTE_LEDS), SND_PCI_QUIRK(0x103c, 0x8c2d, "HP Victus 15-fa1xxx (MB 8C2D)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8c30, "HP Victus 15-fb1xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8c3f, "HP Victus 15-fa1xxx (MB 8C3F)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8c46, "HP EliteBook 830 G11", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8c47, "HP EliteBook 840 G11", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8c48, "HP EliteBook 860 G11", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), From f6635d64e783ad66d800fefa57f897294004ea65 Mon Sep 17 00:00:00 2001 From: Zeliang Li Date: Sat, 15 Aug 2026 03:45:55 +0800 Subject: [PATCH 554/612] ALSA: hda/tas2781: Add hardware stabilization delay during firmware load retries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit During runtime resume transitions, loading calibration data blocks to the tas2781 amplifier may intermittently trigger transmission failures or block checksum mismatches (-EAGAIN) due to un-stabilized power rails or I2C bus glitches. The loop in tasdev_load_blk() decrements block->nr_retry and attempts an immediate re-transmission upon receiving -EAGAIN. However, without any inter-retry delay, all available retry slots are exhausted within less than a microsecond—long before the hardware can physically settle. This leads to permanent "ERROR_PRAM_CRCCHK" deadlocks and silent speakers on modern laptops after resuming media. Fix this cleanly by introducing a 2ms usleep_range() delay directly inside the tasdev_load_blk() retry paths prior to each 'continue' statement. This grants the chip sufficient time to stabilize before the next transmission attempt without introducing unnecessary latency on final failures. Signed-off-by: Zeliang Li Link: https://patch.msgid.link/20260815-master-v2-1-b4ea03c8b59e@gmail.com Signed-off-by: Takashi Iwai --- sound/soc/codecs/tas2781-fmwlib.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/tas2781-fmwlib.c b/sound/soc/codecs/tas2781-fmwlib.c index dcbeb9618195b7..bfabff583d5749 100644 --- a/sound/soc/codecs/tas2781-fmwlib.c +++ b/sound/soc/codecs/tas2781-fmwlib.c @@ -1849,16 +1849,26 @@ static int tasdev_load_blk(struct tasdevice_priv *tas_priv, } } if (ret == -EAGAIN) { - if (block->nr_retry > 0) + if (block->nr_retry > 0) { + /* Give the hardware time to stabilize before + * next block re-transmission attempt. + */ + usleep_range(2000, 2500); continue; + } } else if (ret < 0) /*err in current device, skip it*/ break; if (block->is_pchksum_present) { ret = tasdev_block_chksum(tas_priv, block, chn); if (ret == -EAGAIN) { - if (block->nr_retry > 0) + if (block->nr_retry > 0) { + /* Give the bus time to recover after + * a checksum mismatch error. + */ + usleep_range(2000, 2500); continue; + } } else if (ret < 0) /*err in current device, skip it*/ break; } From 75dc2eda659f6be4a370734f11baf25df8a9fd80 Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Mon, 17 Aug 2026 17:47:08 +0800 Subject: [PATCH 555/612] ALSA: hda/realtek: Fix Lenovo Yoga Slim 7 14AKP10 quirk ordering The Yoga Slim 7 14AKP10 has a PCI SSID of 17aa:38b4 but a codec SSID of 17aa:391a. The current quirk table contains a PCI quirk for 17aa:38b4 (for the Legion Slim 7 16IRH8) which matches first, so the codec-specific quirk for 17aa:391a is never applied. This results in the wrong fixup being used (CS35L41_I2C_2 instead of the correct bass speaker fixup), leaving the internal speakers misconfigured or silent. Remove the 17aa:391a entry from its PCI-SSID-sorted position and add it as an HDA_CODEC_QUIRK directly before the 17aa:38b4 entry, because it must match on the codec subsystem ID rather than the PCI SSID and it has to win over the colliding PCI quirk for the Legion Slim 7 16IRH8. A comment is added to explain the out-of-order placement, following the same style already used for the 17aa:38bb and 17aa:38f9 codec-SSID overrides. With this change, the correct ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN is applied, restoring speaker output and auto-mute functionality. The original quirk added in commit e6c888202297 ("ALSA: hda/realtek: Add quirk for Lenovo Yoga Slim 7 14AKP10") matched on the PCI SSID 17aa:391a, but this model actually exposes PCI SSID 17aa:38b4 (shared with the Legion Slim 7 16IRH8), so that quirk never matched and the bass speaker remained silent. Fix it by matching on the codec SSID and placing the entry before the colliding 17aa:38b4 PCI quirk. Fixes: e6c888202297 ("ALSA: hda/realtek: Add quirk for Lenovo Yoga Slim 7 14AKP10") Cc: stable@vger.kernel.org Link: https://bugzilla.kernel.org/show_bug.cgi?id=221298 Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20260817094708.222154-1-zhangheng@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 67c5692eee1fb7..cae327e1d90d66 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -8071,6 +8071,10 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x38a8, "Y780P AMD VECO dual", ALC287_FIXUP_TAS2781_I2C), SND_PCI_QUIRK(0x17aa, 0x38a9, "Thinkbook 16P", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), SND_PCI_QUIRK(0x17aa, 0x38ab, "Thinkbook 16P", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), + /* Lenovo Yoga Slim 7 14AKP10 shares PCI SSID 17aa:38b4 with Legion Slim 7 + * 16IRH8; use codec SSID to distinguish them + */ + HDA_CODEC_QUIRK(0x17aa, 0x391a, "Lenovo Yoga Slim 7 14AKP10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), SND_PCI_QUIRK(0x17aa, 0x38b4, "Legion Slim 7 16IRH8", ALC287_FIXUP_CS35L41_I2C_2), HDA_CODEC_QUIRK(0x17aa, 0x391c, "Lenovo Yoga 7 2-in-1 14AKP10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), HDA_CODEC_QUIRK(0x17aa, 0x391d, "Lenovo Yoga 7 2-in-1 16AKP10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), @@ -8118,7 +8122,6 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x3911, "Lenovo Yoga Pro 7 14IAH10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), SND_PCI_QUIRK(0x17aa, 0x3912, "Lenovo Xiaoxin 14 GT", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), SND_PCI_QUIRK(0x17aa, 0x3913, "Lenovo 145", ALC236_FIXUP_LENOVO_INV_DMIC), - SND_PCI_QUIRK(0x17aa, 0x391a, "Lenovo Yoga Slim 7 14AKP10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), SND_PCI_QUIRK(0x17aa, 0x391f, "Yoga S990-16 pro Quad YC Quad", ALC287_FIXUP_TXNW2781_I2C), SND_PCI_QUIRK(0x17aa, 0x3920, "Yoga S990-16 pro Quad VECO Quad", ALC287_FIXUP_TXNW2781_I2C), SND_PCI_QUIRK(0x17aa, 0x3929, "Thinkbook 13x Gen 5", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), From ceaebef91d1aff671a512d5f003766ea41152d94 Mon Sep 17 00:00:00 2001 From: Xu Rao Date: Mon, 17 Aug 2026 16:55:31 +0800 Subject: [PATCH 556/612] ALSA: hda: Add Lisuan HDMI controller and codec support Lisuan GPUs expose an HD-audio controller at PCI ID 4c54:5010 and an HDMI/DP codec with codec ID 0x4c545020. Neither ID is currently matched by the HDA stack, leaving HDMI/DP audio unavailable on these devices. The existing downstream support uses the generic HDMI codec path and attaches no Lisuan-specific capability flags to the controller. Its dedicated AZX driver type only changes the short driver name. Use the corresponding generic upstream paths instead: bind 4c54:5010 to AZX_DRIVER_GENERIC, register 0x4c545020 as MODEL_GENERIC, and add the Lisuan codec vendor name. This keeps the enablement minimal and avoids a vendor-only AZX driver type with no vendor-specific behavior. Signed-off-by: Xu Rao Link: https://patch.msgid.link/DE1B62191D573D37+20260817085531.992573-1-raoxu@uniontech.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/hdmi/hdmi.c | 1 + sound/hda/controllers/intel.c | 2 ++ sound/hda/core/device.c | 1 + 3 files changed, 4 insertions(+) diff --git a/sound/hda/codecs/hdmi/hdmi.c b/sound/hda/codecs/hdmi/hdmi.c index 053c41d98028a8..1e7a05c8773311 100644 --- a/sound/hda/codecs/hdmi/hdmi.c +++ b/sound/hda/codecs/hdmi/hdmi.c @@ -2348,6 +2348,7 @@ static const struct hda_device_id snd_hda_id_generichdmi[] = { HDA_CODEC_ID_MODEL(0x1d179f8e, "KX-7000 HDMI/DP", MODEL_GF), HDA_CODEC_ID_MODEL(0x1d179f8f, "KX-7000 HDMI/DP", MODEL_GF), HDA_CODEC_ID_MODEL(0x1d179f90, "KX-7000 HDMI/DP", MODEL_GF), + HDA_CODEC_ID_MODEL(0x4c545020, "Lisuan HDMI/DP", MODEL_GENERIC), HDA_CODEC_ID_MODEL(0x67663d82, "Arise 82 HDMI/DP", MODEL_GF), HDA_CODEC_ID_MODEL(0x67663d83, "Arise 83 HDMI/DP", MODEL_GF), HDA_CODEC_ID_MODEL(0x67663d84, "Arise 84 HDMI/DP", MODEL_GF), diff --git a/sound/hda/controllers/intel.c b/sound/hda/controllers/intel.c index 1e6d97e08fee99..24015c73a67f0c 100644 --- a/sound/hda/controllers/intel.c +++ b/sound/hda/controllers/intel.c @@ -2857,6 +2857,8 @@ static const struct pci_device_id azx_ids[] = { /* Hygon HDAudio */ { PCI_VDEVICE(HYGON, PCI_DEVICE_ID_HYGON_18H_M05H_HDA), .driver_data = AZX_DRIVER_HYGON | AZX_DCAPS_POSFIX_LPIB | AZX_DCAPS_NO_MSI }, + /* Lisuan HD-audio */ + { PCI_DEVICE(0x4c54, 0x5010), .driver_data = AZX_DRIVER_GENERIC }, { 0, } }; MODULE_DEVICE_TABLE(pci, azx_ids); diff --git a/sound/hda/core/device.c b/sound/hda/core/device.c index 832494035f0a86..776d629ba252d9 100644 --- a/sound/hda/core/device.c +++ b/sound/hda/core/device.c @@ -663,6 +663,7 @@ static const struct hda_vendor_id hda_vendor_ids[] = { { 0x1af4, "QEMU" }, { 0x1fa8, "Senarytech" }, { 0x434d, "C-Media" }, + { 0x4c54, "Lisuan" }, { 0x8086, "Intel" }, { 0x8384, "SigmaTel" }, {} /* terminator */ From 19eadf550ba518db6509eed3c3f34d4fc1e02ee7 Mon Sep 17 00:00:00 2001 From: Lianqin Hu Date: Mon, 17 Aug 2026 11:19:42 +0000 Subject: [PATCH 557/612] ALSA: usb-audio: Add delay quirk for SPACETOUCH USB Audio Audio control requests that set sampling frequency sometimes fail on this card. Adding delay between control messages eliminates that problem. usb 1-1: New USB device found, idVendor=0666, idProduct=0880 usb 1-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3 usb 1-1: Product: USB Audio usb 1-1: Manufacturer: SPACETOUCH usb 1-1: SerialNumber: 000000000 Signed-off-by: Lianqin Hu Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/TYUPR06MB6217D93F595D9995413C9721D2A72@TYUPR06MB6217.apcprd06.prod.outlook.com --- sound/usb/quirks.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 00b686a6c25b74..eb1750def0671c 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2314,7 +2314,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { DEVICE_FLG(0x0661, 0x0883, /* iBasso DC04 Ultra */ QUIRK_FLAG_DSD_RAW), DEVICE_FLG(0x0666, 0x0880, /* SPACETOUCH USB Audio */ - QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), + QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY | + QUIRK_FLAG_CTL_MSG_DELAY_5M), DEVICE_FLG(0x06f8, 0xb000, /* Hercules DJ Console (Windows Edition) */ QUIRK_FLAG_IGNORE_CTL_ERROR), DEVICE_FLG(0x06f8, 0xd002, /* Hercules DJ Console (Macintosh Edition) */ From ff722d025853a33a15b080459e4c52be28d44b6e Mon Sep 17 00:00:00 2001 From: Ninad Naik Date: Sat, 21 Mar 2026 19:32:11 +0530 Subject: [PATCH 558/612] ALSA: docs: fix dead link to Intel HD-audio spec The existing link redirects to a generic page. Update the link to the specification document. Signed-off-by: Ninad Naik Link: https://patch.msgid.link/20260321140212.5026-1-ninadnaik07@gmail.com Signed-off-by: Takashi Iwai --- Documentation/sound/hd-audio/notes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/sound/hd-audio/notes.rst b/Documentation/sound/hd-audio/notes.rst index 6993bfa159b440..c16f83875b800c 100644 --- a/Documentation/sound/hd-audio/notes.rst +++ b/Documentation/sound/hd-audio/notes.rst @@ -42,7 +42,7 @@ If you are interested in the deep debugging of HD-audio, read the HD-audio specification at first. The specification is found on Intel's web page, for example: -* https://www.intel.com/content/www/us/en/standards/high-definition-audio-specification.html +* https://www.intel.com/content/dam/www/public/us/en/documents/product-specifications/high-definition-audio-specification.pdf HD-Audio Controller From c139e7e44f58a6f8ddc9d850ea9924d34963b5da Mon Sep 17 00:00:00 2001 From: Xu Rao Date: Tue, 18 Aug 2026 16:38:08 +0800 Subject: [PATCH 559/612] ALSA: hda: Fix connection list comparison in proc output print_conn_list() compares the raw hardware connection list with the connection list cached by the HDA driver. When they differ, it prints an additional "In-driver Connection" line so that /proc/asound/card*/codec#* shows the topology actually used by the driver. The comparison currently passes conn_len directly to memcmp(). However, conn_len is a number of connection-list entries, while memcmp() expects a size in bytes. Both list and conn are arrays of hda_nid_t, which is u16, so only half of the connection data is compared. For example, for two-entry lists such as: hardware: 0x0c 0x0d cached: 0x0c 0x0e conn_len is 2, and the current comparison checks only the first hda_nid_t. The lists are therefore incorrectly treated as identical even though the second connection differs. This can happen legitimately when codec fixups replace a cached connection list with snd_hda_override_conn_list(). The codec routing used by the driver is not affected, but the proc output can hide the overridden driver-visible routing and provide misleading topology information during codec debugging. Convert the entry count to a byte size so that memcmp() covers the complete connection list. Fixes: 8b2c7a5c404d ("ALSA: hda - Add In-driver connection info") Signed-off-by: Xu Rao Link: https://patch.msgid.link/7B802A4E225CC808+20260818083808.2735120-1-raoxu@uniontech.com Signed-off-by: Takashi Iwai --- sound/hda/common/proc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/hda/common/proc.c b/sound/hda/common/proc.c index c83796b13d3de8..3dabdb418c7bff 100644 --- a/sound/hda/common/proc.c +++ b/sound/hda/common/proc.c @@ -624,7 +624,7 @@ static void print_conn_list(struct snd_info_buffer *buffer, /* Get Cache connections info */ cache_len = snd_hda_get_conn_list(codec, nid, &list); if (cache_len >= 0 && (cache_len != conn_len || - memcmp(list, conn, conn_len) != 0)) { + memcmp(list, conn, conn_len * sizeof(*conn)) != 0)) { snd_iprintf(buffer, " In-driver Connection: %d\n", cache_len); if (cache_len > 0) { snd_iprintf(buffer, " "); From dd0ae9a2e19a24c532c9aa80a44f572b4907bcdf Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Tue, 18 Aug 2026 01:46:29 +0000 Subject: [PATCH 560/612] 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 5305e1f9d97fd0..51a30c182167e6 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 561/612] 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 4e9b08cb653d14..81d2cbac0ea3cd 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 c41ac86802fc0a22a886915a43bcad2e8d482b02 Mon Sep 17 00:00:00 2001 From: Prasad Kumpatla Date: Tue, 18 Aug 2026 14:16:52 +0530 Subject: [PATCH 562/612] 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 2e879a3c09aef3..3a99703dbc4a0d 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 f613e2aee75e63..b4f9abbe02cb0b 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 cca77cd92bc17b..cf619ef6561a11 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 563/612] 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 bf1f872a09f45a..033197bdd8705e 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 564/612] 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 d231024206dbff..d9f256d5197332 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 565/612] 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 1322601659d481..4d48e1012cd4b1 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 566/612] 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 25cd12eab4b1fd..6422cc1703b6ad 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 6c97817e20598e5473094e0e38d1f51f1cf4dfff Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Tue, 18 Aug 2026 22:47:17 +0800 Subject: [PATCH 567/612] 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 25a6403044305d..60cc0721d0a6f3 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 568/612] 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 eb1750def0671c..f4e1eac639b933 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 6fd1b9225de1b09cd8dd79e1ccba8d84b4e94036 Mon Sep 17 00:00:00 2001 From: Andrey Golovko Date: Thu, 13 Aug 2026 19:40:00 +0300 Subject: [PATCH 569/612] 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 8a07ba2a29e5a7..3e091e78d8c253 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 570/612] 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 c217da5fccdf61..d32d8f441651ca 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 d736eba9c453fedce664fdf592c8b71ecff1932b Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Thu, 20 Aug 2026 09:41:17 +0800 Subject: [PATCH 571/612] 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 7d1a357ed90dc3..d6bfd8fb20e65e 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 572/612] 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 cae327e1d90d66..e3adcd114a36ef 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 573/612] 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 c517334b56fd9e..8e950c796209e5 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 dfb8bc1d286fd97b7cf47e3af5df84cfb068d214 Mon Sep 17 00:00:00 2001 From: Jack Yu Date: Thu, 20 Aug 2026 17:24:15 +0800 Subject: [PATCH 574/612] 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 2218f9918ae39a..13574513b181d9 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 7ab7d5feb50a29..46740281a5c17f 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 c73cb5b7c159246dd572277c668851a56e516019 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Thu, 20 Aug 2026 19:27:49 +0800 Subject: [PATCH 575/612] 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 d31eadf4be5f83..1ed6d4f0cb625d 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 576/612] 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 e3adcd114a36ef..dd57e0b00a85dc 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 577/612] 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 dd57e0b00a85dc..fe553db5b52f22 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 53dd1c1de851280c71385f49a24715e5615c926c Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Fri, 21 Aug 2026 16:37:31 +0100 Subject: [PATCH 578/612] 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 84506576437dad..79a2f898ea28c5 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 42be2137342c4d..82292f96ab3676 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 579/612] 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 8eb4e54b954b12..96259a6cbc59b2 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 580/612] 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 | 208 ++++++++++++++++++++---------- 1 file changed, 138 insertions(+), 70 deletions(-) diff --git a/sound/soc/tegra/tegra210_admaif.c b/sound/soc/tegra/tegra210_admaif.c index 7299c6bfcf1581..87e898cc35e424 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_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_RX_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 581/612] 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 f5d4a93dd6f162..1d78b4c1cc885e 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 9d4843ac81d15be27e0b03392a99a8bd88a2d86a Mon Sep 17 00:00:00 2001 From: Niranjan H Y Date: Thu, 20 Aug 2026 17:21:12 +0530 Subject: [PATCH 582/612] 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 3e091e78d8c253..a66dcc02fb59cb 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 583/612] 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 c062065a807dac..065e7d456240f0 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 eed8e6cf3498b5..a84c5ca515a7bb 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 584/612] 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 065e7d456240f0..a5f654cde69fc4 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 585/612] 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 8ae59c09487880..04940879dfd80b 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 4d3266d9cbb79e..e9c728bae8b978 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 5542c4ee6d1295..a7a5dba83a2f82 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 1fcccdc20a76b4639b00124cf2a379b837bd2bf7 Mon Sep 17 00:00:00 2001 From: Csaba Budai Date: Fri, 21 Aug 2026 12:25:14 +0200 Subject: [PATCH 586/612] 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 fe553db5b52f22..5c4ea24e9651cf 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 587/612] 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 f4e1eac639b933..0dc5695160ebd8 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 588/612] 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 b2171472e6cbac..71733e807a7c0b 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 c49709d7ad2581..abc2d7e579de4f 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 589/612] 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 5de182181edee2..3aa9a9c125575d 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 590/612] 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 71733e807a7c0b..e500ec84e3ef72 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 3aa9a9c125575d..ecaa8bc08d7ca9 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 0dc5695160ebd8..2f5276cf7339bb 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 abc2d7e579de4f..24436473931da1 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 591/612] 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 2f5276cf7339bb..5c544bd8872819 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 592/612] 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 8046d5987d5b1c..ace4ccad8f51c9 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 1035a8f63bae28e498b0e7b5ac91d749844a7158 Mon Sep 17 00:00:00 2001 From: Marouane El Moufid Date: Sun, 23 Aug 2026 13:55:48 +0000 Subject: [PATCH 593/612] 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 f8996416c3be15..8a9bc37f0b6ee8 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 594/612] 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 5c4ea24e9651cf..b08d25fb0e1792 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 60e73c07b3fc12958a63de77b482d18bda413a6a Mon Sep 17 00:00:00 2001 From: Shuming Fan Date: Mon, 24 Aug 2026 18:39:36 +0800 Subject: [PATCH 595/612] 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 5d60c3bcc6cd1d..8e76bf725cccf3 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 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 596/612] 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 5c544bd8872819..91938172912a5b 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 597/612] 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 0a3d39b8385bec..dd02c6294edf2b 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 598/612] 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 833c44c0a9506b..adf123dd79a2b2 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 599/612] 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 b08d25fb0e1792..f230a2be3914e0 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 600/612] 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 24112e49177968..9307da95efbef7 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 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 601/612] 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 f230a2be3914e0..e4349743a251eb 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 602/612] 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 2dde300a9220f6..4f2ccf9e1fa694 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 fd8ed52ecea6b93e23d52cb2758fb4fb4029067f Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Mon, 24 Aug 2026 21:03:02 +0800 Subject: [PATCH 603/612] 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 4f2ccf9e1fa694..d1d2ab1bfefa56 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 604/612] 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 88a6159364d0d1..4154035af41420 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 bf504e27f73e1d..34b4c7d6dbe674 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 82ad155c56e605..d183c8a000bd8a 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 84d2d96ac3f1bb467aca65adf2c19431e6eeb151 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Tue, 25 Aug 2026 15:19:49 +0700 Subject: [PATCH 605/612] 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 3b18d90e81c326..65b7ec52a05403 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 032bb633e0c34a88b70a4bfc65ebd4e2b510f706 Mon Sep 17 00:00:00 2001 From: Mehmet Aysel Date: Tue, 25 Aug 2026 12:24:31 +0300 Subject: [PATCH 606/612] 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 3021dc498c855a..ef6fa3cca8487d 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 607/612] 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 46fff565d73214..3a640e65231420 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 608/612] 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 ef6fa3cca8487d..a27336b132f287 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 14511c9b54ceeeef487409d73947c89ee8563590 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Tue, 25 Aug 2026 21:57:45 +0900 Subject: [PATCH 609/612] 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 c98e917cc911b7..06b85313af1260 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 610/612] 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 8e950c796209e5..7357dc91ac49b4 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 15596a87fcc6fa07a162858e5fd00ba31752096b Mon Sep 17 00:00:00 2001 From: Shengyu Qu Date: Thu, 27 Aug 2026 01:20:50 +0800 Subject: [PATCH 611/612] 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 a27336b132f287..2d50be8190c10e 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 74e3b979ce8b78a690f8b94ccf2e2c965f7f5c11 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 27 Aug 2026 13:39:03 +0200 Subject: [PATCH 612/612] 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 8cbacee57ce70b..3d13bbec1c5448 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;