]> git.ipfire.org Git - thirdparty/kernel/stable-queue.git/commitdiff
6.6-stable patches
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Tue, 21 Jul 2026 13:36:05 +0000 (15:36 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Tue, 21 Jul 2026 13:36:05 +0000 (15:36 +0200)
added patches:
acpi-bus-introduce-devm_acpi_install_notify_handler.patch
acpi-driver-check-acpi_companion-against-null-during-probe.patch
acpi-nfit-core-fix-acpi_nfit_init-error-cleanup.patch
acpi-nfit-core-fix-possible-deadlock-and-missing-notifications.patch
acpi-nfit-core-use-devm_acpi_install_notify_handler.patch
alsa-aoa-check-snd_ctl_new1-return-value.patch
audit-add-audit_log_nf_skb-helper-function.patch
audit-fix-potential-integer-overflow-in-audit_log_n_hex.patch
bitops-make-bytes_to_bits-treewide-available.patch
bluetooth-6lowpan-fix-cyclic-locking-warning-on-netdev-unregister.patch
bluetooth-l2cap-cancel-pending_rx_work-before-taking-conn-lock.patch
bluetooth-l2cap-fix-uaf-in-channel-timeout-by-holding-conn-ref.patch
bluetooth-l2cap-fix-use-after-free-in-l2cap_sock_new_connection_cb.patch
bpf-allow-lpm-map-access-from-sleepable-bpf-programs.patch
bpf-consistently-use-bpf_rcu_lock_held-everywhere.patch
btrfs-check-and-set-extent_delalloc_new-before-clearing-extent_delalloc.patch
btrfs-fix-false-io-failure-after-falling-back-to-buffered-write.patch
btrfs-fix-incorrect-buffered-io-fallback-for-append-direct-writes.patch
coresight-etb10-restore-atomic_t-for-shared-reading-state.patch
cpufreq-make-cpufreq_driver-exit-return-void.patch
cpufreq-pcc-remove-empty-exit-callback.patch
cpufreq-qcom-cpufreq-hw-fix-possible-double-free.patch
crypto-atmel-drop-explicit-initialization-of-struct-i2c_device_id-driver_data-to-0.patch
crypto-atmel-sha204a-drop-hwrng-quality-reduction-for-atsha204a.patch
crypto-qat-fix-restarting-state-leak-on-allocation-failure.patch
crypto-qat-fix-vf2pf-work-teardown-race-in-adf_disable_sriov.patch
exfat-add-exfat_get_dentry_set_by_ei-helper.patch
exfat-move-exfat_chain_set-out-of-__exfat_resolve_path.patch
exfat-move-free-cluster-out-of-exfat_init_ext_entry.patch
exfat-preserve-benign-secondary-entries-during-rename-and-move.patch
exfat-remove-unnecessary-read-entry-in-__exfat_rename.patch
exfat-rename-argument-name-for-exfat_move_file-and-exfat_rename_file.patch
gpio-sch-use-raw_spinlock_t-in-the-irq-startup-path.patch
hfs-hfsplus-fix-u32-overflow-in-check_and_correct_requested_length.patch
hfs-hfsplus-prevent-getting-negative-values-of-offset-length.patch
hid-add-haptics-page-defines.patch
hid-appleir-fix-uaf-on-pending-key_up_timer-in-remove.patch
hid-multitouch-fix-out-of-bounds-bit-access-on-mt_io_flags.patch
iio-common-st_sensors-honour-channel-endianness-in-read_axis_data.patch
iio-hid-sensor-rotation-fix-stale-or-zero-output-when-reading-raw-values.patch
iio-imu-adis-add-irqf_no_thread-to-non-fifo-trigger-irq.patch
iio-imu-inv_icm42600-fix-timestamping-by-limiting-fifo-reading.patch
iio-imu-inv_icm42600-stabilized-timestamp-in-interrupt.patch
iio-invensense-fix-timestamp-glitches-when-switching-frequency.patch
iio-invensense-remove-redundant-initialization-of-variable-period.patch
iio-pressure-mpl115-fix-runtime-pm-leak-on-read-error.patch
ksmbd-centralize-ksmbd_conn-final-release-to-plug-transport-leak.patch
ksmbd-track-the-connection-owning-a-byte-range-lock.patch
ksmbd-use-opener-credentials-for-fsctl-mutations.patch
media-nxp-imx8-isi-convert-to-platform-remove-callback-returning-void.patch
media-nxp-imx8-isi-fix-use-after-free-on-remove.patch
media-nxp-imx8-isi-use-devm_pm_runtime_enable-to-simplify-code.patch
mm-do-file-ownership-checks-with-the-proper-mount-idmap.patch
mm-shrinker-do-not-hold-rcu-lock-in-shrinker_debugfs_count_show.patch
mm-shrinker-remove-redundant-shrinker_rwsem-in-debugfs-operations.patch
mm-slab-do-not-limit-zeroing-to-orig_size-when-only-red-zoning-is-enabled.patch
netfilter-ebtables-use-vmalloc_array-to-improve-code.patch
netfilter-ebtables-zero-chainstack-array.patch
nvmet-auth-validate-reply-message-payload-bounds-against-transfer-length.patch
nvmet-remove-superfluous-initialization.patch
nvmet-return-dhchap-status-codes-from-nvmet_setup_auth.patch
pci-add-kerneldoc-for-pci_resize_resource.patch
pci-altera-fix-resource-leaks-on-probe-failure.patch
pci-controller-use-dev_fwnode-instead-of-of_fwnode_handle.patch
pci-fix-restoring-bars-on-bar-resize-rollback-path.patch
pci-free-saved-list-without-holding-pci_bus_sem.patch
pci-imx6-fix-imx6sx_gpr12_pcie_test_powerdown-handling.patch
pci-mediatek-convert-bool-to-single-quirks-entry-and-bitmap.patch
pci-mediatek-fix-irq-domain-leak-when-port-fails-to-enable.patch
pci-mediatek-use-generic-macro-for-tpvperl-delay.patch
pci-move-resizable-bar-code-to-rebar.c.patch
pci-prevent-resource-tree-corruption-when-bar-resize-fails.patch
pci-skip-resizable-bar-restore-on-read-error.patch
perf-x86-intel-uncore-defer-adl-global-pmon-enable-to-enable_box.patch
proc-protect-ptrace_may_access-with-exec_update_lock-fd-links.patch
proc-protect-ptrace_may_access-with-exec_update_lock-part-1.patch
proc-rename-proc_setattr-to-proc_nochmod_setattr.patch
regulator-scmi-fix-of_node-refcount-leak-in-scmi_regulator_probe.patch
regulator-scmi-simplify-with-scoped-for-each-of-child-loop.patch
seqlock-change-do_task_stat-to-use-scoped_seqlock_read.patch
seqlock-introduce-scoped_seqlock_read.patch
serial-8250_mid-disable-dma-for-selected-platforms.patch
serial-8250_mid-remove-8250_pci-usage.patch
smb-client-improve-unlocking-of-a-mutex-in-cifs_get_swn_reg.patch
smb-client-resolve-swn-tcon-from-live-registrations.patch
staging-rtl8723bs-core-move-constants-to-right-side-in-comparison.patch
staging-rtl8723bs-fix-oob-reads-in-rtw_get_sec_ie-rtw_get_wapi_ie-and-rtw_get_wps_attr.patch
staging-rtl8723bs-fix-spaces-around-binary-operators.patch
treewide-switch-rename-to-timer_delete.patch
usb-atm-ueagle-atm-wait-for-pre-firmware-load-in-.disconnect.patch
usb-gadget-f_fs-initialize-reset_work-at-allocation-time.patch
usb-gadget-f_fs-tie-read_buffer-lifetime-to-ffs_epfile.patch
usb-iowarrior-remove-inherent-race-with-minor-number.patch
usb-typec-tcpm-fix-vdm-type-for-enter-mode-commands.patch
vfio-mlx5-fix-racy-bitfields-and-tighten-struct-layout.patch
writeback-avoid-contention-on-wb-list_lock-when-switching-inodes.patch
writeback-fix-race-between-cgroup_writeback_umount-and-inode_switch_wbs.patch

98 files changed:
queue-6.6/acpi-bus-introduce-devm_acpi_install_notify_handler.patch [new file with mode: 0644]
queue-6.6/acpi-driver-check-acpi_companion-against-null-during-probe.patch [new file with mode: 0644]
queue-6.6/acpi-nfit-core-fix-acpi_nfit_init-error-cleanup.patch [new file with mode: 0644]
queue-6.6/acpi-nfit-core-fix-possible-deadlock-and-missing-notifications.patch [new file with mode: 0644]
queue-6.6/acpi-nfit-core-use-devm_acpi_install_notify_handler.patch [new file with mode: 0644]
queue-6.6/alsa-aoa-check-snd_ctl_new1-return-value.patch [new file with mode: 0644]
queue-6.6/audit-add-audit_log_nf_skb-helper-function.patch [new file with mode: 0644]
queue-6.6/audit-fix-potential-integer-overflow-in-audit_log_n_hex.patch [new file with mode: 0644]
queue-6.6/bitops-make-bytes_to_bits-treewide-available.patch [new file with mode: 0644]
queue-6.6/bluetooth-6lowpan-fix-cyclic-locking-warning-on-netdev-unregister.patch [new file with mode: 0644]
queue-6.6/bluetooth-l2cap-cancel-pending_rx_work-before-taking-conn-lock.patch [new file with mode: 0644]
queue-6.6/bluetooth-l2cap-fix-uaf-in-channel-timeout-by-holding-conn-ref.patch [new file with mode: 0644]
queue-6.6/bluetooth-l2cap-fix-use-after-free-in-l2cap_sock_new_connection_cb.patch [new file with mode: 0644]
queue-6.6/bpf-allow-lpm-map-access-from-sleepable-bpf-programs.patch [new file with mode: 0644]
queue-6.6/bpf-consistently-use-bpf_rcu_lock_held-everywhere.patch [new file with mode: 0644]
queue-6.6/btrfs-check-and-set-extent_delalloc_new-before-clearing-extent_delalloc.patch [new file with mode: 0644]
queue-6.6/btrfs-fix-false-io-failure-after-falling-back-to-buffered-write.patch [new file with mode: 0644]
queue-6.6/btrfs-fix-incorrect-buffered-io-fallback-for-append-direct-writes.patch [new file with mode: 0644]
queue-6.6/coresight-etb10-restore-atomic_t-for-shared-reading-state.patch [new file with mode: 0644]
queue-6.6/cpufreq-make-cpufreq_driver-exit-return-void.patch [new file with mode: 0644]
queue-6.6/cpufreq-pcc-remove-empty-exit-callback.patch [new file with mode: 0644]
queue-6.6/cpufreq-qcom-cpufreq-hw-fix-possible-double-free.patch [new file with mode: 0644]
queue-6.6/crypto-atmel-drop-explicit-initialization-of-struct-i2c_device_id-driver_data-to-0.patch [new file with mode: 0644]
queue-6.6/crypto-atmel-sha204a-drop-hwrng-quality-reduction-for-atsha204a.patch [new file with mode: 0644]
queue-6.6/crypto-qat-fix-restarting-state-leak-on-allocation-failure.patch [new file with mode: 0644]
queue-6.6/crypto-qat-fix-vf2pf-work-teardown-race-in-adf_disable_sriov.patch [new file with mode: 0644]
queue-6.6/exfat-add-exfat_get_dentry_set_by_ei-helper.patch [new file with mode: 0644]
queue-6.6/exfat-move-exfat_chain_set-out-of-__exfat_resolve_path.patch [new file with mode: 0644]
queue-6.6/exfat-move-free-cluster-out-of-exfat_init_ext_entry.patch [new file with mode: 0644]
queue-6.6/exfat-preserve-benign-secondary-entries-during-rename-and-move.patch [new file with mode: 0644]
queue-6.6/exfat-remove-unnecessary-read-entry-in-__exfat_rename.patch [new file with mode: 0644]
queue-6.6/exfat-rename-argument-name-for-exfat_move_file-and-exfat_rename_file.patch [new file with mode: 0644]
queue-6.6/gpio-sch-use-raw_spinlock_t-in-the-irq-startup-path.patch [new file with mode: 0644]
queue-6.6/hfs-hfsplus-fix-u32-overflow-in-check_and_correct_requested_length.patch [new file with mode: 0644]
queue-6.6/hfs-hfsplus-prevent-getting-negative-values-of-offset-length.patch [new file with mode: 0644]
queue-6.6/hid-add-haptics-page-defines.patch [new file with mode: 0644]
queue-6.6/hid-appleir-fix-uaf-on-pending-key_up_timer-in-remove.patch [new file with mode: 0644]
queue-6.6/hid-multitouch-fix-out-of-bounds-bit-access-on-mt_io_flags.patch [new file with mode: 0644]
queue-6.6/iio-common-st_sensors-honour-channel-endianness-in-read_axis_data.patch [new file with mode: 0644]
queue-6.6/iio-hid-sensor-rotation-fix-stale-or-zero-output-when-reading-raw-values.patch [new file with mode: 0644]
queue-6.6/iio-imu-adis-add-irqf_no_thread-to-non-fifo-trigger-irq.patch [new file with mode: 0644]
queue-6.6/iio-imu-inv_icm42600-fix-timestamping-by-limiting-fifo-reading.patch [new file with mode: 0644]
queue-6.6/iio-imu-inv_icm42600-stabilized-timestamp-in-interrupt.patch [new file with mode: 0644]
queue-6.6/iio-invensense-fix-timestamp-glitches-when-switching-frequency.patch [new file with mode: 0644]
queue-6.6/iio-invensense-remove-redundant-initialization-of-variable-period.patch [new file with mode: 0644]
queue-6.6/iio-pressure-mpl115-fix-runtime-pm-leak-on-read-error.patch [new file with mode: 0644]
queue-6.6/ksmbd-centralize-ksmbd_conn-final-release-to-plug-transport-leak.patch [new file with mode: 0644]
queue-6.6/ksmbd-track-the-connection-owning-a-byte-range-lock.patch [new file with mode: 0644]
queue-6.6/ksmbd-use-opener-credentials-for-fsctl-mutations.patch [new file with mode: 0644]
queue-6.6/media-nxp-imx8-isi-convert-to-platform-remove-callback-returning-void.patch [new file with mode: 0644]
queue-6.6/media-nxp-imx8-isi-fix-use-after-free-on-remove.patch [new file with mode: 0644]
queue-6.6/media-nxp-imx8-isi-use-devm_pm_runtime_enable-to-simplify-code.patch [new file with mode: 0644]
queue-6.6/mm-do-file-ownership-checks-with-the-proper-mount-idmap.patch [new file with mode: 0644]
queue-6.6/mm-shrinker-do-not-hold-rcu-lock-in-shrinker_debugfs_count_show.patch [new file with mode: 0644]
queue-6.6/mm-shrinker-remove-redundant-shrinker_rwsem-in-debugfs-operations.patch [new file with mode: 0644]
queue-6.6/mm-slab-do-not-limit-zeroing-to-orig_size-when-only-red-zoning-is-enabled.patch [new file with mode: 0644]
queue-6.6/netfilter-ebtables-use-vmalloc_array-to-improve-code.patch [new file with mode: 0644]
queue-6.6/netfilter-ebtables-zero-chainstack-array.patch [new file with mode: 0644]
queue-6.6/nvmet-auth-validate-reply-message-payload-bounds-against-transfer-length.patch [new file with mode: 0644]
queue-6.6/nvmet-remove-superfluous-initialization.patch [new file with mode: 0644]
queue-6.6/nvmet-return-dhchap-status-codes-from-nvmet_setup_auth.patch [new file with mode: 0644]
queue-6.6/pci-add-kerneldoc-for-pci_resize_resource.patch [new file with mode: 0644]
queue-6.6/pci-altera-fix-resource-leaks-on-probe-failure.patch [new file with mode: 0644]
queue-6.6/pci-controller-use-dev_fwnode-instead-of-of_fwnode_handle.patch [new file with mode: 0644]
queue-6.6/pci-fix-restoring-bars-on-bar-resize-rollback-path.patch [new file with mode: 0644]
queue-6.6/pci-free-saved-list-without-holding-pci_bus_sem.patch [new file with mode: 0644]
queue-6.6/pci-imx6-fix-imx6sx_gpr12_pcie_test_powerdown-handling.patch [new file with mode: 0644]
queue-6.6/pci-mediatek-convert-bool-to-single-quirks-entry-and-bitmap.patch [new file with mode: 0644]
queue-6.6/pci-mediatek-fix-irq-domain-leak-when-port-fails-to-enable.patch [new file with mode: 0644]
queue-6.6/pci-mediatek-use-generic-macro-for-tpvperl-delay.patch [new file with mode: 0644]
queue-6.6/pci-move-resizable-bar-code-to-rebar.c.patch [new file with mode: 0644]
queue-6.6/pci-prevent-resource-tree-corruption-when-bar-resize-fails.patch [new file with mode: 0644]
queue-6.6/pci-skip-resizable-bar-restore-on-read-error.patch [new file with mode: 0644]
queue-6.6/perf-x86-intel-uncore-defer-adl-global-pmon-enable-to-enable_box.patch [new file with mode: 0644]
queue-6.6/proc-protect-ptrace_may_access-with-exec_update_lock-fd-links.patch [new file with mode: 0644]
queue-6.6/proc-protect-ptrace_may_access-with-exec_update_lock-part-1.patch [new file with mode: 0644]
queue-6.6/proc-rename-proc_setattr-to-proc_nochmod_setattr.patch [new file with mode: 0644]
queue-6.6/regulator-scmi-fix-of_node-refcount-leak-in-scmi_regulator_probe.patch [new file with mode: 0644]
queue-6.6/regulator-scmi-simplify-with-scoped-for-each-of-child-loop.patch [new file with mode: 0644]
queue-6.6/seqlock-change-do_task_stat-to-use-scoped_seqlock_read.patch [new file with mode: 0644]
queue-6.6/seqlock-introduce-scoped_seqlock_read.patch [new file with mode: 0644]
queue-6.6/serial-8250_mid-disable-dma-for-selected-platforms.patch [new file with mode: 0644]
queue-6.6/serial-8250_mid-remove-8250_pci-usage.patch [new file with mode: 0644]
queue-6.6/series
queue-6.6/smb-client-improve-unlocking-of-a-mutex-in-cifs_get_swn_reg.patch [new file with mode: 0644]
queue-6.6/smb-client-resolve-swn-tcon-from-live-registrations.patch [new file with mode: 0644]
queue-6.6/staging-rtl8723bs-core-move-constants-to-right-side-in-comparison.patch [new file with mode: 0644]
queue-6.6/staging-rtl8723bs-fix-oob-reads-in-rtw_get_sec_ie-rtw_get_wapi_ie-and-rtw_get_wps_attr.patch [new file with mode: 0644]
queue-6.6/staging-rtl8723bs-fix-spaces-around-binary-operators.patch [new file with mode: 0644]
queue-6.6/treewide-switch-rename-to-timer_delete.patch [new file with mode: 0644]
queue-6.6/usb-atm-ueagle-atm-wait-for-pre-firmware-load-in-.disconnect.patch [new file with mode: 0644]
queue-6.6/usb-gadget-f_fs-initialize-reset_work-at-allocation-time.patch [new file with mode: 0644]
queue-6.6/usb-gadget-f_fs-tie-read_buffer-lifetime-to-ffs_epfile.patch [new file with mode: 0644]
queue-6.6/usb-iowarrior-remove-inherent-race-with-minor-number.patch [new file with mode: 0644]
queue-6.6/usb-typec-tcpm-fix-vdm-type-for-enter-mode-commands.patch [new file with mode: 0644]
queue-6.6/vfio-mlx5-fix-racy-bitfields-and-tighten-struct-layout.patch [new file with mode: 0644]
queue-6.6/writeback-avoid-contention-on-wb-list_lock-when-switching-inodes.patch [new file with mode: 0644]
queue-6.6/writeback-fix-race-between-cgroup_writeback_umount-and-inode_switch_wbs.patch [new file with mode: 0644]

diff --git a/queue-6.6/acpi-bus-introduce-devm_acpi_install_notify_handler.patch b/queue-6.6/acpi-bus-introduce-devm_acpi_install_notify_handler.patch
new file mode 100644 (file)
index 0000000..a52b2b8
--- /dev/null
@@ -0,0 +1,127 @@
+From stable+bounces-273310-greg=kroah.com@vger.kernel.org Fri Jul 10 22:06:44 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 10 Jul 2026 16:06:32 -0400
+Subject: ACPI: bus: Introduce devm_acpi_install_notify_handler()
+To: stable@vger.kernel.org
+Cc: "Rafael J. Wysocki" <rafael.j.wysocki@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260710200635.395836-2-sashal@kernel.org>
+
+From: "Rafael J. Wysocki" <rafael.j.wysocki@intel.com>
+
+[ Upstream commit ca70ce555a1eb8edf5fb1d5575f1fe19c9ae17f4 ]
+
+Introduce devm_acpi_install_notify_handler() for installing an ACPI
+notify handler managed by devres that will be removed automatically on
+driver detach.
+
+It installs the notify handler on the device object in the ACPI
+namespace that corresponds to the owner device's ACPI companion, if
+present (an error is returned if the owner device doesn't have an ACPI
+companion).
+
+Currently, there is no way to manually remove the notify handler
+installed by it because none of its users brought on subsequently
+will need to do that.
+
+Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
+[ rjw: Kerneldoc comment refinement ]
+Link: https://patch.msgid.link/2268031.irdbgypaU6@rafael.j.wysocki
+Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
+Stable-dep-of: 18a00ed0e718 ("ACPI: NFIT: core: Fix possible deadlock and missing notifications")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/acpi/bus.c      |   70 ++++++++++++++++++++++++++++++++++++++++++++++++
+ include/acpi/acpi_bus.h |    2 +
+ 2 files changed, 72 insertions(+)
+
+--- a/drivers/acpi/bus.c
++++ b/drivers/acpi/bus.c
+@@ -578,6 +578,76 @@ void acpi_dev_remove_notify_handler(stru
+ }
+ EXPORT_SYMBOL_GPL(acpi_dev_remove_notify_handler);
++struct acpi_notify_handler_devres {
++      acpi_notify_handler handler;
++      u32 handler_type;
++      struct acpi_device *adev;
++};
++
++static void devm_acpi_notify_handler_release(struct device *dev, void *res)
++{
++      struct acpi_notify_handler_devres *dr = res;
++
++      acpi_dev_remove_notify_handler(dr->adev, dr->handler_type,
++                                     dr->handler);
++}
++
++/**
++ * devm_acpi_install_notify_handler - Install an ACPI notify handler for a
++ *                                  managed device
++ * @dev: Device to install a notify handler for
++ * @handler_type: Type of the notify handler
++ * @handler: Handler function to install
++ *
++ * This function performs the same function as acpi_dev_install_notify_handler()
++ * called for the ACPI companion of @dev with the same @handler_type and
++ * @handler arguments, but the ACPI notify handler installed by it will be
++ * automatically removed on driver detach.
++ *
++ * Callers should ensure that all resources used by @handler have been allocated
++ * prior to invoking this function, in which case those resources should be
++ * devres-managed so that they won't be released before the notify handler
++ * removal.  Otherwise, special synchronization between @handler and the
++ * management of those resources is required.
++ *
++ * When the request fails, an error message is printed.  Don't add extra error
++ * messages at the call sites.
++ *
++ * Return: 0 on success or a negative error number.
++ */
++int devm_acpi_install_notify_handler(struct device *dev, u32 handler_type,
++                                   acpi_notify_handler handler)
++{
++      struct acpi_notify_handler_devres *dr;
++      struct acpi_device *adev;
++      int ret;
++
++      adev = ACPI_COMPANION(dev);
++      if (!adev && dev->bus == &acpi_bus_type)
++              adev = to_acpi_device(dev);
++
++      if (!adev)
++              return dev_err_probe(dev, -ENODEV, "No ACPI companion in %s()\n", __func__);
++
++      dr = devres_alloc(devm_acpi_notify_handler_release, sizeof(*dr), GFP_KERNEL);
++      if (!dr)
++              return -ENOMEM;
++
++      ret = acpi_dev_install_notify_handler(adev, handler_type, handler);
++      if (ret) {
++              devres_free(dr);
++              return dev_err_probe(dev, ret, "Failed to install an ACPI notify handler\n");
++      }
++
++      dr->handler = handler;
++      dr->handler_type = handler_type;
++      dr->adev = adev;
++      devres_add(dev, dr);
++
++      return 0;
++}
++EXPORT_SYMBOL_GPL(devm_acpi_install_notify_handler);
++
+ /* Handle events targeting \_SB device (at present only graceful shutdown) */
+ #define ACPI_SB_NOTIFY_SHUTDOWN_REQUEST 0x81
+--- a/include/acpi/acpi_bus.h
++++ b/include/acpi/acpi_bus.h
+@@ -521,6 +521,8 @@ int acpi_dev_install_notify_handler(stru
+ void acpi_dev_remove_notify_handler(struct acpi_device *adev,
+                                   u32 handler_type,
+                                   acpi_notify_handler handler);
++int devm_acpi_install_notify_handler(struct device *dev, u32 handler_type,
++                                   acpi_notify_handler handler);
+ extern int acpi_notifier_call_chain(struct acpi_device *, u32, u32);
+ extern int register_acpi_notifier(struct notifier_block *);
+ extern int unregister_acpi_notifier(struct notifier_block *);
diff --git a/queue-6.6/acpi-driver-check-acpi_companion-against-null-during-probe.patch b/queue-6.6/acpi-driver-check-acpi_companion-against-null-during-probe.patch
new file mode 100644 (file)
index 0000000..0cff255
--- /dev/null
@@ -0,0 +1,106 @@
+From stable+bounces-273309-greg=kroah.com@vger.kernel.org Fri Jul 10 22:06:45 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 10 Jul 2026 16:06:31 -0400
+Subject: ACPI: driver: Check ACPI_COMPANION() against NULL during probe
+To: stable@vger.kernel.org
+Cc: "Rafael J. Wysocki" <rafael.j.wysocki@intel.com>, Hans de Goede <johannes.goede@oss.qualcomm.com>, Andy Shevchenko <andriy.shevchenko@linux.intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260710200635.395836-1-sashal@kernel.org>
+
+From: "Rafael J. Wysocki" <rafael.j.wysocki@intel.com>
+
+[ Upstream commit e4865a56d013e86e46ea6acea15bb6eae01898ff ]
+
+Since every platform driver can be forced to match a device that doesn't
+match its list of device IDs because of device_match_driver_override(),
+platform drivers that rely on the existence of a device's ACPI companion
+object should verify its presence.
+
+Accordingly, add requisite ACPI_COMPANION() or ACPI_HANDLE() checks
+against NULL to 13 platform drivers handling core ACPI devices.
+
+Also change the value returned by the ACPI thermal zone driver when
+the device's ACPI companion is not present to -ENODEV for consistency
+with the other drivers.
+
+Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
+Reviewed-by: Hans de Goede <johannes.goede@oss.qualcomm.com>
+Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
+Link: https://patch.msgid.link/4516068.ejJDZkT8p0@rafael.j.wysocki
+Cc: 7.0+ <stable@vger.kernel.org> # 7.0+
+Stable-dep-of: 18a00ed0e718 ("ACPI: NFIT: core: Fix possible deadlock and missing notifications")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/acpi/acpi_tad.c      |    6 +++++-
+ drivers/acpi/pfr_telemetry.c |    6 +++++-
+ drivers/acpi/pfr_update.c    |    6 +++++-
+ drivers/acpi/thermal.c       |    2 +-
+ 4 files changed, 16 insertions(+), 4 deletions(-)
+
+--- a/drivers/acpi/acpi_tad.c
++++ b/drivers/acpi/acpi_tad.c
+@@ -588,12 +588,16 @@ static int acpi_tad_remove(struct platfo
+ static int acpi_tad_probe(struct platform_device *pdev)
+ {
+       struct device *dev = &pdev->dev;
+-      acpi_handle handle = ACPI_HANDLE(dev);
+       struct acpi_tad_driver_data *dd;
++      acpi_handle handle;
+       acpi_status status;
+       unsigned long long caps;
+       int ret;
++      handle = ACPI_HANDLE(dev);
++      if (!handle)
++              return -ENODEV;
++
+       ret = acpi_install_cmos_rtc_space_handler(handle);
+       if (ret < 0) {
+               dev_info(dev, "Unable to install space handler\n");
+--- a/drivers/acpi/pfr_telemetry.c
++++ b/drivers/acpi/pfr_telemetry.c
+@@ -365,10 +365,14 @@ static void pfrt_log_put_idx(void *data)
+ static int acpi_pfrt_log_probe(struct platform_device *pdev)
+ {
+-      acpi_handle handle = ACPI_HANDLE(&pdev->dev);
+       struct pfrt_log_device *pfrt_log_dev;
++      acpi_handle handle;
+       int ret;
++      handle = ACPI_HANDLE(&pdev->dev);
++      if (!handle)
++              return -ENODEV;
++
+       if (!acpi_has_method(handle, "_DSM")) {
+               dev_dbg(&pdev->dev, "Missing _DSM\n");
+               return -ENODEV;
+--- a/drivers/acpi/pfr_update.c
++++ b/drivers/acpi/pfr_update.c
+@@ -507,10 +507,14 @@ static void pfru_put_idx(void *data)
+ static int acpi_pfru_probe(struct platform_device *pdev)
+ {
+-      acpi_handle handle = ACPI_HANDLE(&pdev->dev);
+       struct pfru_device *pfru_dev;
++      acpi_handle handle;
+       int ret;
++      handle = ACPI_HANDLE(&pdev->dev);
++      if (!handle)
++              return -ENODEV;
++
+       if (!acpi_has_method(handle, "_DSM")) {
+               dev_dbg(&pdev->dev, "Missing _DSM\n");
+               return -ENODEV;
+--- a/drivers/acpi/thermal.c
++++ b/drivers/acpi/thermal.c
+@@ -926,7 +926,7 @@ static int acpi_thermal_add(struct acpi_
+       int result;
+       if (!device)
+-              return -EINVAL;
++              return -ENODEV;
+       tz = kzalloc(sizeof(struct acpi_thermal), GFP_KERNEL);
+       if (!tz)
diff --git a/queue-6.6/acpi-nfit-core-fix-acpi_nfit_init-error-cleanup.patch b/queue-6.6/acpi-nfit-core-fix-acpi_nfit_init-error-cleanup.patch
new file mode 100644 (file)
index 0000000..98957c9
--- /dev/null
@@ -0,0 +1,110 @@
+From stable+bounces-273245-greg=kroah.com@vger.kernel.org Fri Jul 10 16:01:51 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 10 Jul 2026 10:00:24 -0400
+Subject: ACPI: NFIT: core: Fix acpi_nfit_init() error cleanup
+To: stable@vger.kernel.org
+Cc: "Rafael J. Wysocki" <rafael.j.wysocki@intel.com>, Dave Jiang <dave.jiang@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260710140024.103597-1-sashal@kernel.org>
+
+From: "Rafael J. Wysocki" <rafael.j.wysocki@intel.com>
+
+[ Upstream commit 38bf27511ef41bffebd157ec3eba41fc89ba59cd ]
+
+If acpi_nfit_init() fails after adding the acpi_desc object to the
+acpi_descs list, that object is never removed from that list because
+the acpi_nfit_shutdown() devm action is not added for the NFIT device
+in that case.  Next, the acpi_nfit_init() failure causes
+acpi_nfit_probe() to fail, the acpi_desc object is freed, and a
+dangling pointer is left behind in the acpi_descs.  Any subsequent
+ACPI Machine Check Exception will trigger nfit_handle_mce() which
+iterates over acpi_descs and so a use-after-free will occur.
+
+Moreover, if acpi_nfit_probe() returns 0 after installing a notify
+handler for the NFIT device and without allocating the acpi_desc
+object and setting the NFIT device's driver data pointer, the
+acpi_desc object will be allocated by acpi_nfit_update_notify()
+and acpi_nfit_init() will be called to initialize it.  Regardless
+of whether or not acpi_nfit_init() fails in that case, the
+acpi_nfit_shutdown() devm action is not added for the NFIT device
+and acpi_desc is never removed from the acpi_descs list.  If the
+acpi_desc object is freed subsequently on driver removal, any
+subsequent ACPI MCE will lead to a use-after-free like in the
+previous case.
+
+To address the first issue mentioned above, make acpi_nfit_probe()
+call acpi_nfit_shutdown() directly on acpi_nfit_init() failures and
+to address the other one, add a remove callback to the driver and
+make it call acpi_nfit_shutdown().  Also, since it is now possible to
+pass NULL to acpi_nfit_shutdown() or the acpi_desc object passed to it
+may not have been initialized, add checks against NULL for acpi_desc and
+its nvdimm_bus field to that function and make acpi_nfit_unregister()
+clear the latter after unregistering the NVDIMM bus.
+
+Fixes: a61fe6f7902e ("nfit, tools/testing/nvdimm: unify common init for acpi_nfit_desc")
+Fixes: fbabd829fe76 ("acpi, nfit: fix module unload vs workqueue shutdown race")
+Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
+Cc: All applicable <stable@vger.kernel.org>
+Reviewed-by: Dave Jiang <dave.jiang@intel.com>
+Link: https://patch.msgid.link/1963615.tdWV9SEqCh@rafael.j.wysocki
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/acpi/nfit/core.c |   18 +++++++++++++++---
+ 1 file changed, 15 insertions(+), 3 deletions(-)
+
+--- a/drivers/acpi/nfit/core.c
++++ b/drivers/acpi/nfit/core.c
+@@ -3067,6 +3067,8 @@ static void acpi_nfit_unregister(void *d
+       struct acpi_nfit_desc *acpi_desc = data;
+       nvdimm_bus_unregister(acpi_desc->nvdimm_bus);
++      /* The nvdimm_bus object may have been freed, so clear the pointer. */
++      acpi_desc->nvdimm_bus = NULL;
+ }
+ int acpi_nfit_init(struct acpi_nfit_desc *acpi_desc, void *data, acpi_size sz)
+@@ -3307,7 +3309,10 @@ static void acpi_nfit_remove_notify_hand
+ void acpi_nfit_shutdown(void *data)
+ {
+       struct acpi_nfit_desc *acpi_desc = data;
+-      struct device *bus_dev = to_nvdimm_bus_dev(acpi_desc->nvdimm_bus);
++      struct device *bus_dev;
++
++      if (!acpi_desc || !acpi_desc->nvdimm_bus)
++              return;
+       /*
+        * Destruct under acpi_desc_lock so that nfit_handle_mce does not
+@@ -3322,6 +3327,7 @@ void acpi_nfit_shutdown(void *data)
+       mutex_unlock(&acpi_desc->init_mutex);
+       cancel_delayed_work_sync(&acpi_desc->dwork);
++      bus_dev = to_nvdimm_bus_dev(acpi_desc->nvdimm_bus);
+       /*
+        * Bounce the nvdimm bus lock to make sure any in-flight
+        * acpi_nfit_ars_rescan() submissions have had a chance to
+@@ -3399,9 +3405,14 @@ static int acpi_nfit_add(struct acpi_dev
+                               sz - sizeof(struct acpi_table_nfit));
+       if (rc)
+-              return rc;
++              acpi_nfit_shutdown(acpi_desc);
+-      return devm_add_action_or_reset(dev, acpi_nfit_shutdown, acpi_desc);
++      return rc;
++}
++
++static void acpi_nfit_remove(struct acpi_device *adev)
++{
++      acpi_nfit_shutdown(dev_get_drvdata(&adev->dev));
+ }
+ static void acpi_nfit_update_notify(struct device *dev, acpi_handle handle)
+@@ -3488,6 +3499,7 @@ static struct acpi_driver acpi_nfit_driv
+       .ids = acpi_nfit_ids,
+       .ops = {
+               .add = acpi_nfit_add,
++              .remove = acpi_nfit_remove,
+       },
+ };
diff --git a/queue-6.6/acpi-nfit-core-fix-possible-deadlock-and-missing-notifications.patch b/queue-6.6/acpi-nfit-core-fix-possible-deadlock-and-missing-notifications.patch
new file mode 100644 (file)
index 0000000..5a886ed
--- /dev/null
@@ -0,0 +1,174 @@
+From stable+bounces-273313-greg=kroah.com@vger.kernel.org Fri Jul 10 22:07:34 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 10 Jul 2026 16:06:35 -0400
+Subject: ACPI: NFIT: core: Fix possible deadlock and missing notifications
+To: stable@vger.kernel.org
+Cc: "Rafael J. Wysocki" <rafael.j.wysocki@intel.com>, Dave Jiang <dave.jiang@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260710200635.395836-5-sashal@kernel.org>
+
+From: "Rafael J. Wysocki" <rafael.j.wysocki@intel.com>
+
+[ Upstream commit 18a00ed0e718473f5c3fcfa49df46c944575e60c ]
+
+After commit 9b311b7313d6 ("ACPI: NFIT: Install Notify() handler before
+getting NFIT table"), ACPI NFIT driver removal may deadlock if an ACPI
+notify on the NFIT device is triggered concurrently.  A similar deadlock
+may occur if an ACPI notify on the NFIT device is triggered during a
+failing driver probe.
+
+The deadlock is possible because acpi_dev_remove_notify_handler() calls
+acpi_os_wait_events_complete() after removing the notify handler and the
+driver core invokes it under the NFIT platform device lock which is also
+acquired by acpi_nfit_notify().  Thus acpi_os_wait_events_complete() may
+be waiting for acpi_nfit_notify() to complete, but the latter may not be
+able to acquire the device lock which is being held by the driver core
+while the former is being executed.
+
+Moreover, after commit 03667e146f81 ("ACPI: NFIT: core: Convert the
+driver to a platform one"), there are no sysfs notifications regarding
+NVDIMM devices because __acpi_nvdimm_notify() always bails out after
+checking the driver data pointer of the device's parent.  That parent
+is the ACPI companion of the platform device used for driver binding,
+so its driver data pointer is always NULL after the commit in question
+which was overlooked by it.
+
+A remedy for the deadlock is to use a special separate lock for ACPI
+notify synchronization with driver probe and removal instead of the
+device lock of the NFIT device, while a remedy for the second issue
+is to populate the driver data pointer of the NFIT device's ACPI
+companion when the driver is ready to operate, so do both these things.
+However, since the new lock is not held across the entire teardown and
+acpi_nfit_notify() should do nothing when teardown is in progress, make
+it check the driver data pointer of the NFIT device's ACPI companion, in
+analogy with the existing check in __acpi_nvdimm_notify(), and bail out
+if that pointer is NULL.
+
+Fixes: 9b311b7313d6 ("ACPI: NFIT: Install Notify() handler before getting NFIT table")
+Fixes: 03667e146f81 ("ACPI: NFIT: core: Convert the driver to a platform one")
+Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
+Cc: All applicable <stable@vger.kernel.org> # 9995e4404ea4: ACPI: NFIT: core: Eliminate redundant local variable
+Reviewed-by: Dave Jiang <dave.jiang@intel.com>
+Link: https://patch.msgid.link/3420096.aeNJFYEL58@rafael.j.wysocki
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/acpi/nfit/core.c |   59 +++++++++++++++++++++++++++++++++++++----------
+ 1 file changed, 47 insertions(+), 12 deletions(-)
+
+--- a/drivers/acpi/nfit/core.c
++++ b/drivers/acpi/nfit/core.c
+@@ -55,6 +55,8 @@ MODULE_PARM_DESC(force_labels, "Opt-in t
+ LIST_HEAD(acpi_descs);
+ DEFINE_MUTEX(acpi_desc_lock);
++DEFINE_MUTEX(acpi_notify_lock);
++
+ static struct workqueue_struct *nfit_wq;
+ struct nfit_table_prev {
+@@ -1702,9 +1704,15 @@ static void acpi_nvdimm_notify(acpi_hand
+       struct acpi_device *adev = data;
+       struct device *dev = &adev->dev;
+-      device_lock(dev->parent);
+-      __acpi_nvdimm_notify(dev, event);
+-      device_unlock(dev->parent);
++      /*
++       * Locking is needed here for synchronization with driver probe and
++       * removal and the parent NFIT device's ACPI driver data pointer is
++       * NULL when teardown is in progress.
++       */
++      guard(mutex)(&acpi_notify_lock);
++
++      if (acpi_driver_data(to_acpi_device(dev->parent)))
++              __acpi_nvdimm_notify(dev, event);
+ }
+ static bool acpi_nvdimm_has_method(struct acpi_device *adev, char *method)
+@@ -3156,11 +3164,10 @@ EXPORT_SYMBOL_GPL(acpi_nfit_init);
+ static int acpi_nfit_flush_probe(struct nvdimm_bus_descriptor *nd_desc)
+ {
+       struct acpi_nfit_desc *acpi_desc = to_acpi_desc(nd_desc);
+-      struct device *dev = acpi_desc->dev;
+-      /* Bounce the device lock to flush acpi_nfit_add / acpi_nfit_notify */
+-      device_lock(dev);
+-      device_unlock(dev);
++      /* Bounce the notify lock to flush acpi_nfit_add / acpi_nfit_notify */
++      mutex_lock(&acpi_notify_lock);
++      mutex_unlock(&acpi_notify_lock);
+       /* Bounce the init_mutex to complete initial registration */
+       mutex_lock(&acpi_desc->init_mutex);
+@@ -3293,9 +3300,15 @@ static void acpi_nfit_notify(acpi_handle
+ {
+       struct acpi_device *adev = data;
+-      device_lock(&adev->dev);
+-      __acpi_nfit_notify(&adev->dev, handle, event);
+-      device_unlock(&adev->dev);
++      /*
++       * Locking is needed here for synchronization with driver probe and
++       * removal and the ACPI driver data pointer is NULL when teardown
++       * is in progress.
++       */
++      guard(mutex)(&acpi_notify_lock);
++
++      if (acpi_driver_data(adev))
++              __acpi_nfit_notify(&adev->dev, handle, event);
+ }
+ void acpi_nfit_shutdown(void *data)
+@@ -3342,6 +3355,12 @@ static int acpi_nfit_add(struct acpi_dev
+       acpi_size sz;
+       int rc = 0;
++      /*
++       * Prevent acpi_nfit_notify() from progressing until the probe is
++       * complete in case there is a concurrent event to process.
++       */
++      guard(mutex)(&acpi_notify_lock);
++
+       rc = devm_acpi_install_notify_handler(dev, ACPI_DEVICE_NOTIFY,
+                                             acpi_nfit_notify);
+       if (rc)
+@@ -3357,6 +3376,11 @@ static int acpi_nfit_add(struct acpi_dev
+                * data in the format of a series of NFIT Structures.
+                */
+               dev_dbg(dev, "failed to find NFIT at startup\n");
++              /*
++               * Let acpi_nfit_update_notify() run in case it will need to
++               * allocate the acpi_desc object.
++               */
++              adev->driver_data = dev;
+               return 0;
+       }
+@@ -3391,14 +3415,25 @@ static int acpi_nfit_add(struct acpi_dev
+                               + sizeof(struct acpi_table_nfit),
+                               sz - sizeof(struct acpi_table_nfit));
+-      if (rc)
++      if (rc) {
+               acpi_nfit_shutdown(acpi_desc);
++              return rc;
++      }
+-      return rc;
++      /*
++       * Let notify handlers operate (the actual value of the ACPI driver
++       * data pointer does not matter here so long as it is not NULL).
++       */
++      adev->driver_data = dev;
++      return 0;
+ }
+ static void acpi_nfit_remove(struct acpi_device *adev)
+ {
++      guard(mutex)(&acpi_notify_lock);
++
++      /* Make notify handlers bail out early going forward. */
++      adev->driver_data = NULL;
+       acpi_nfit_shutdown(dev_get_drvdata(&adev->dev));
+ }
diff --git a/queue-6.6/acpi-nfit-core-use-devm_acpi_install_notify_handler.patch b/queue-6.6/acpi-nfit-core-use-devm_acpi_install_notify_handler.patch
new file mode 100644 (file)
index 0000000..fc182be
--- /dev/null
@@ -0,0 +1,65 @@
+From stable+bounces-273311-greg=kroah.com@vger.kernel.org Fri Jul 10 22:06:52 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 10 Jul 2026 16:06:33 -0400
+Subject: ACPI: NFIT: core: Use devm_acpi_install_notify_handler()
+To: stable@vger.kernel.org
+Cc: "Rafael J. Wysocki" <rafael.j.wysocki@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260710200635.395836-3-sashal@kernel.org>
+
+From: "Rafael J. Wysocki" <rafael.j.wysocki@intel.com>
+
+[ Upstream commit 198541ad53c0d0d891fedea4098f9953a0f566c0 ]
+
+Now that devm_acpi_install_notify_handler() is available, use it in
+acpi_nfit_probe() instead of a custom devm action removing an ACPI
+notify handler installed via acpi_dev_install_notify_handler().
+
+Also drop the explicit ACPI_COMPANION() check against NULL that is
+not necessary any more becuase devm_acpi_install_notify_handler()
+carries out an equivalent check internally and use ACPI_HANDLE() to
+retrieve the platform device's ACPI handle.
+
+No intentional functional impact.
+
+Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
+Link: https://patch.msgid.link/3048737.e9J7NaK4W3@rafael.j.wysocki
+Stable-dep-of: 18a00ed0e718 ("ACPI: NFIT: core: Fix possible deadlock and missing notifications")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/acpi/nfit/core.c |   17 ++---------------
+ 1 file changed, 2 insertions(+), 15 deletions(-)
+
+--- a/drivers/acpi/nfit/core.c
++++ b/drivers/acpi/nfit/core.c
+@@ -3298,14 +3298,6 @@ static void acpi_nfit_notify(acpi_handle
+       device_unlock(&adev->dev);
+ }
+-static void acpi_nfit_remove_notify_handler(void *data)
+-{
+-      struct acpi_device *adev = data;
+-
+-      acpi_dev_remove_notify_handler(adev, ACPI_DEVICE_NOTIFY,
+-                                     acpi_nfit_notify);
+-}
+-
+ void acpi_nfit_shutdown(void *data)
+ {
+       struct acpi_nfit_desc *acpi_desc = data;
+@@ -3350,13 +3342,8 @@ static int acpi_nfit_add(struct acpi_dev
+       acpi_size sz;
+       int rc = 0;
+-      rc = acpi_dev_install_notify_handler(adev, ACPI_DEVICE_NOTIFY,
+-                                           acpi_nfit_notify);
+-      if (rc)
+-              return rc;
+-
+-      rc = devm_add_action_or_reset(dev, acpi_nfit_remove_notify_handler,
+-                                      adev);
++      rc = devm_acpi_install_notify_handler(dev, ACPI_DEVICE_NOTIFY,
++                                            acpi_nfit_notify);
+       if (rc)
+               return rc;
diff --git a/queue-6.6/alsa-aoa-check-snd_ctl_new1-return-value.patch b/queue-6.6/alsa-aoa-check-snd_ctl_new1-return-value.patch
new file mode 100644 (file)
index 0000000..0fe683c
--- /dev/null
@@ -0,0 +1,61 @@
+From stable+bounces-274012-greg=kroah.com@vger.kernel.org Mon Jul 13 22:53:49 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 13 Jul 2026 16:53:39 -0400
+Subject: ALSA: aoa: check snd_ctl_new1() return value
+To: stable@vger.kernel.org
+Cc: Zhao Dongdong <zhaodongdong@kylinos.cn>, Takashi Iwai <tiwai@suse.de>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260713205339.2148895-1-sashal@kernel.org>
+
+From: Zhao Dongdong <zhaodongdong@kylinos.cn>
+
+[ Upstream commit 8df560fefe6fed6a20b7e06720eeaeccec349ac0 ]
+
+snd_ctl_new1() can return NULL when memory allocation fails. In
+layout.c, the function does not check the return value before
+dereferencing ctl->id.name or passing to aoa_snd_ctl_add(), which can
+lead to a NULL pointer dereference.
+
+Add NULL checks after snd_ctl_new1() calls and return early if any
+fails.
+
+Assisted-by: Opencode:DeepSeek-V4-Flash
+Cc: stable@vger.kernel.org
+Fixes: f3d9478b2ce4 ("[ALSA] snd-aoa: add snd-aoa")
+Signed-off-by: Zhao Dongdong <zhaodongdong@kylinos.cn>
+Link: https://patch.msgid.link/tencent_35F3A25FEEBF190A2E15ED787754C57E3708@qq.com
+Signed-off-by: Takashi Iwai <tiwai@suse.de>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ sound/aoa/fabrics/layout.c |    6 ++++++
+ 1 file changed, 6 insertions(+)
+
+--- a/sound/aoa/fabrics/layout.c
++++ b/sound/aoa/fabrics/layout.c
+@@ -947,6 +947,8 @@ static void layout_attached_codec(struct
+                       if (lineout == 1)
+                               ldev->gpio.methods->set_lineout(codec->gpio, 1);
+                       ctl = snd_ctl_new1(&lineout_ctl, codec->gpio);
++                      if (!ctl)
++                              return;
+                       if (cc->connected & CC_LINEOUT_LABELLED_HEADPHONE)
+                               strscpy(ctl->id.name,
+                                       "Headphone Switch", sizeof(ctl->id.name));
+@@ -961,6 +963,8 @@ static void layout_attached_codec(struct
+                       if (ldev->have_lineout_detect) {
+                               ctl = snd_ctl_new1(&lineout_detect_choice,
+                                                  ldev);
++                              if (!ctl)
++                                      return;
+                               if (cc->connected & CC_LINEOUT_LABELLED_HEADPHONE)
+                                       strscpy(ctl->id.name,
+                                               "Headphone Detect Autoswitch",
+@@ -968,6 +972,8 @@ static void layout_attached_codec(struct
+                               aoa_snd_ctl_add(ctl);
+                               ctl = snd_ctl_new1(&lineout_detected,
+                                                  ldev);
++                              if (!ctl)
++                                      return;
+                               if (cc->connected & CC_LINEOUT_LABELLED_HEADPHONE)
+                                       strscpy(ctl->id.name,
+                                               "Headphone Detected",
diff --git a/queue-6.6/audit-add-audit_log_nf_skb-helper-function.patch b/queue-6.6/audit-add-audit_log_nf_skb-helper-function.patch
new file mode 100644 (file)
index 0000000..307cc3a
--- /dev/null
@@ -0,0 +1,296 @@
+From stable+bounces-278278-greg=kroah.com@vger.kernel.org Tue Jul 21 02:44:58 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 20:44:47 -0400
+Subject: audit: add audit_log_nf_skb helper function
+To: stable@vger.kernel.org
+Cc: Ricardo Robaina <rrobaina@redhat.com>, Florian Westphal <fw@strlen.de>, Paul Moore <paul@paul-moore.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721004448.3420885-1-sashal@kernel.org>
+
+From: Ricardo Robaina <rrobaina@redhat.com>
+
+[ Upstream commit f19590b07cb620be1fcd5474c49515e21a05d406 ]
+
+Netfilter code (net/netfilter/nft_log.c and net/netfilter/xt_AUDIT.c)
+have to be kept in sync. Both source files had duplicated versions of
+audit_ip4() and audit_ip6() functions, which can result in lack of
+consistency and/or duplicated work.
+
+This patch adds a helper function in audit.c that can be called by
+netfilter code commonly, aiming to improve maintainability and
+consistency.
+
+Suggested-by: Florian Westphal <fw@strlen.de>
+Suggested-by: Paul Moore <paul@paul-moore.com>
+Signed-off-by: Ricardo Robaina <rrobaina@redhat.com>
+Acked-by: Florian Westphal <fw@strlen.de>
+Signed-off-by: Paul Moore <paul@paul-moore.com>
+Stable-dep-of: 65dfde57d1e2 ("audit: fix potential integer overflow in audit_log_n_hex()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/audit.h    |    8 +++++
+ kernel/audit.c           |   64 +++++++++++++++++++++++++++++++++++++++++++++++
+ net/netfilter/nft_log.c  |   58 ------------------------------------------
+ net/netfilter/xt_AUDIT.c |   58 ------------------------------------------
+ 4 files changed, 74 insertions(+), 114 deletions(-)
+
+--- a/include/linux/audit.h
++++ b/include/linux/audit.h
+@@ -180,6 +180,8 @@ extern void                    audit_log_lost(const ch
+ extern int audit_log_task_context(struct audit_buffer *ab);
+ extern void audit_log_task_info(struct audit_buffer *ab);
++extern int audit_log_nf_skb(struct audit_buffer *ab,
++                          const struct sk_buff *skb, u8 nfproto);
+ extern int                audit_update_lsm_rules(void);
+@@ -245,6 +247,12 @@ static inline int audit_log_task_context
+ static inline void audit_log_task_info(struct audit_buffer *ab)
+ { }
++static inline int audit_log_nf_skb(struct audit_buffer *ab,
++                                 const struct sk_buff *skb, u8 nfproto)
++{
++      return 0;
++}
++
+ static inline kuid_t audit_get_loginuid(struct task_struct *tsk)
+ {
+       return INVALID_UID;
+--- a/kernel/audit.c
++++ b/kernel/audit.c
+@@ -57,6 +57,8 @@
+ #include <linux/freezer.h>
+ #include <linux/pid_namespace.h>
+ #include <net/netns/generic.h>
++#include <net/ip.h>
++#include <net/ipv6.h>
+ #include "audit.h"
+@@ -2304,6 +2306,68 @@ void audit_log_path_denied(int type, con
+       audit_log_end(ab);
+ }
++int audit_log_nf_skb(struct audit_buffer *ab,
++                   const struct sk_buff *skb, u8 nfproto)
++{
++      /* find the IP protocol in the case of NFPROTO_BRIDGE */
++      if (nfproto == NFPROTO_BRIDGE) {
++              switch (eth_hdr(skb)->h_proto) {
++              case htons(ETH_P_IP):
++                      nfproto = NFPROTO_IPV4;
++                      break;
++              case htons(ETH_P_IPV6):
++                      nfproto = NFPROTO_IPV6;
++                      break;
++              default:
++                      goto unknown_proto;
++              }
++      }
++
++      switch (nfproto) {
++      case NFPROTO_IPV4: {
++              struct iphdr iph;
++              const struct iphdr *ih;
++
++              ih = skb_header_pointer(skb, skb_network_offset(skb),
++                                      sizeof(iph), &iph);
++              if (!ih)
++                      return -ENOMEM;
++
++              audit_log_format(ab, " saddr=%pI4 daddr=%pI4 proto=%hhu",
++                               &ih->saddr, &ih->daddr, ih->protocol);
++              break;
++      }
++      case NFPROTO_IPV6: {
++              struct ipv6hdr iph;
++              const struct ipv6hdr *ih;
++              u8 nexthdr;
++              __be16 frag_off;
++
++              ih = skb_header_pointer(skb, skb_network_offset(skb),
++                                      sizeof(iph), &iph);
++              if (!ih)
++                      return -ENOMEM;
++
++              nexthdr = ih->nexthdr;
++              ipv6_skip_exthdr(skb, skb_network_offset(skb) + sizeof(iph),
++                               &nexthdr, &frag_off);
++
++              audit_log_format(ab, " saddr=%pI6c daddr=%pI6c proto=%hhu",
++                               &ih->saddr, &ih->daddr, nexthdr);
++              break;
++      }
++      default:
++              goto unknown_proto;
++      }
++
++      return 0;
++
++unknown_proto:
++      audit_log_format(ab, " saddr=? daddr=? proto=?");
++      return -EPFNOSUPPORT;
++}
++EXPORT_SYMBOL(audit_log_nf_skb);
++
+ /* global counter which is incremented every time something logs in */
+ static atomic_t session_id = ATOMIC_INIT(0);
+--- a/net/netfilter/nft_log.c
++++ b/net/netfilter/nft_log.c
+@@ -26,46 +26,10 @@ struct nft_log {
+       char                    *prefix;
+ };
+-static bool audit_ip4(struct audit_buffer *ab, struct sk_buff *skb)
+-{
+-      struct iphdr _iph;
+-      const struct iphdr *ih;
+-
+-      ih = skb_header_pointer(skb, skb_network_offset(skb), sizeof(_iph), &_iph);
+-      if (!ih)
+-              return false;
+-
+-      audit_log_format(ab, " saddr=%pI4 daddr=%pI4 proto=%hhu",
+-                       &ih->saddr, &ih->daddr, ih->protocol);
+-
+-      return true;
+-}
+-
+-static bool audit_ip6(struct audit_buffer *ab, struct sk_buff *skb)
+-{
+-      struct ipv6hdr _ip6h;
+-      const struct ipv6hdr *ih;
+-      u8 nexthdr;
+-      __be16 frag_off;
+-
+-      ih = skb_header_pointer(skb, skb_network_offset(skb), sizeof(_ip6h), &_ip6h);
+-      if (!ih)
+-              return false;
+-
+-      nexthdr = ih->nexthdr;
+-      ipv6_skip_exthdr(skb, skb_network_offset(skb) + sizeof(_ip6h), &nexthdr, &frag_off);
+-
+-      audit_log_format(ab, " saddr=%pI6c daddr=%pI6c proto=%hhu",
+-                       &ih->saddr, &ih->daddr, nexthdr);
+-
+-      return true;
+-}
+-
+ static void nft_log_eval_audit(const struct nft_pktinfo *pkt)
+ {
+       struct sk_buff *skb = pkt->skb;
+       struct audit_buffer *ab;
+-      int fam = -1;
+       if (!audit_enabled)
+               return;
+@@ -76,27 +40,7 @@ static void nft_log_eval_audit(const str
+       audit_log_format(ab, "mark=%#x", skb->mark);
+-      switch (nft_pf(pkt)) {
+-      case NFPROTO_BRIDGE:
+-              switch (eth_hdr(skb)->h_proto) {
+-              case htons(ETH_P_IP):
+-                      fam = audit_ip4(ab, skb) ? NFPROTO_IPV4 : -1;
+-                      break;
+-              case htons(ETH_P_IPV6):
+-                      fam = audit_ip6(ab, skb) ? NFPROTO_IPV6 : -1;
+-                      break;
+-              }
+-              break;
+-      case NFPROTO_IPV4:
+-              fam = audit_ip4(ab, skb) ? NFPROTO_IPV4 : -1;
+-              break;
+-      case NFPROTO_IPV6:
+-              fam = audit_ip6(ab, skb) ? NFPROTO_IPV6 : -1;
+-              break;
+-      }
+-
+-      if (fam == -1)
+-              audit_log_format(ab, " saddr=? daddr=? proto=-1");
++      audit_log_nf_skb(ab, skb, nft_pf(pkt));
+       audit_log_end(ab);
+ }
+--- a/net/netfilter/xt_AUDIT.c
++++ b/net/netfilter/xt_AUDIT.c
+@@ -28,46 +28,10 @@ MODULE_ALIAS("ip6t_AUDIT");
+ MODULE_ALIAS("ebt_AUDIT");
+ MODULE_ALIAS("arpt_AUDIT");
+-static bool audit_ip4(struct audit_buffer *ab, struct sk_buff *skb)
+-{
+-      struct iphdr _iph;
+-      const struct iphdr *ih;
+-
+-      ih = skb_header_pointer(skb, skb_network_offset(skb), sizeof(_iph), &_iph);
+-      if (!ih)
+-              return false;
+-
+-      audit_log_format(ab, " saddr=%pI4 daddr=%pI4 proto=%hhu",
+-                       &ih->saddr, &ih->daddr, ih->protocol);
+-
+-      return true;
+-}
+-
+-static bool audit_ip6(struct audit_buffer *ab, struct sk_buff *skb)
+-{
+-      struct ipv6hdr _ip6h;
+-      const struct ipv6hdr *ih;
+-      u8 nexthdr;
+-      __be16 frag_off;
+-
+-      ih = skb_header_pointer(skb, skb_network_offset(skb), sizeof(_ip6h), &_ip6h);
+-      if (!ih)
+-              return false;
+-
+-      nexthdr = ih->nexthdr;
+-      ipv6_skip_exthdr(skb, skb_network_offset(skb) + sizeof(_ip6h), &nexthdr, &frag_off);
+-
+-      audit_log_format(ab, " saddr=%pI6c daddr=%pI6c proto=%hhu",
+-                       &ih->saddr, &ih->daddr, nexthdr);
+-
+-      return true;
+-}
+-
+ static unsigned int
+ audit_tg(struct sk_buff *skb, const struct xt_action_param *par)
+ {
+       struct audit_buffer *ab;
+-      int fam = -1;
+       if (audit_enabled == AUDIT_OFF)
+               goto errout;
+@@ -77,27 +41,7 @@ audit_tg(struct sk_buff *skb, const stru
+       audit_log_format(ab, "mark=%#x", skb->mark);
+-      switch (xt_family(par)) {
+-      case NFPROTO_BRIDGE:
+-              switch (eth_hdr(skb)->h_proto) {
+-              case htons(ETH_P_IP):
+-                      fam = audit_ip4(ab, skb) ? NFPROTO_IPV4 : -1;
+-                      break;
+-              case htons(ETH_P_IPV6):
+-                      fam = audit_ip6(ab, skb) ? NFPROTO_IPV6 : -1;
+-                      break;
+-              }
+-              break;
+-      case NFPROTO_IPV4:
+-              fam = audit_ip4(ab, skb) ? NFPROTO_IPV4 : -1;
+-              break;
+-      case NFPROTO_IPV6:
+-              fam = audit_ip6(ab, skb) ? NFPROTO_IPV6 : -1;
+-              break;
+-      }
+-
+-      if (fam == -1)
+-              audit_log_format(ab, " saddr=? daddr=? proto=-1");
++      audit_log_nf_skb(ab, skb, xt_family(par));
+       audit_log_end(ab);
diff --git a/queue-6.6/audit-fix-potential-integer-overflow-in-audit_log_n_hex.patch b/queue-6.6/audit-fix-potential-integer-overflow-in-audit_log_n_hex.patch
new file mode 100644 (file)
index 0000000..72b81c1
--- /dev/null
@@ -0,0 +1,66 @@
+From stable+bounces-278279-greg=kroah.com@vger.kernel.org Tue Jul 21 02:45:01 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 20:44:48 -0400
+Subject: audit: fix potential integer overflow in audit_log_n_hex()
+To: stable@vger.kernel.org
+Cc: Ricardo Robaina <rrobaina@redhat.com>, Richard Guy Briggs <rgb@redhat.com>, Paul Moore <paul@paul-moore.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721004448.3420885-2-sashal@kernel.org>
+
+From: Ricardo Robaina <rrobaina@redhat.com>
+
+[ Upstream commit 65dfde57d1e29ce2b76fc23dd565eccd5c0bc0f0 ]
+
+The function calculates new_len as len << 1 for hex encoding. This
+has two overflow risks: the shift itself can overflow when len is
+large, and the result can be truncated when assigned to new_len
+(declared as int) from the size_t calculation.
+
+Fix by using check_shl_overflow() to catch shift overflow and
+changing new_len and loop counter i to size_t to prevent truncation.
+
+Cc: stable@vger.kernel.org
+Fixes: 168b7173959f ("AUDIT: Clean up logging of untrusted strings")
+Reviewed-by: Richard Guy Briggs <rgb@redhat.com>
+Signed-off-by: Ricardo Robaina <rrobaina@redhat.com>
+[PM: remove vertical whitspace noise]
+Signed-off-by: Paul Moore <paul@paul-moore.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ kernel/audit.c |   11 +++++++++--
+ 1 file changed, 9 insertions(+), 2 deletions(-)
+
+--- a/kernel/audit.c
++++ b/kernel/audit.c
+@@ -59,6 +59,7 @@
+ #include <net/netns/generic.h>
+ #include <net/ip.h>
+ #include <net/ipv6.h>
++#include <linux/overflow.h>
+ #include "audit.h"
+@@ -2034,7 +2035,8 @@ void audit_log_format(struct audit_buffe
+ void audit_log_n_hex(struct audit_buffer *ab, const unsigned char *buf,
+               size_t len)
+ {
+-      int i, avail, new_len;
++      int avail;
++      size_t i, new_len;
+       unsigned char *ptr;
+       struct sk_buff *skb;
+@@ -2044,7 +2046,12 @@ void audit_log_n_hex(struct audit_buffer
+       BUG_ON(!ab->skb);
+       skb = ab->skb;
+       avail = skb_tailroom(skb);
+-      new_len = len<<1;
++
++      if (check_shl_overflow(len, 1, &new_len)) {
++              audit_log_format(ab, "?");
++              return;
++      }
++
+       if (new_len >= avail) {
+               /* Round the buffer request up to the next multiple */
+               new_len = AUDIT_BUFSIZ*(((new_len-avail)/AUDIT_BUFSIZ) + 1);
diff --git a/queue-6.6/bitops-make-bytes_to_bits-treewide-available.patch b/queue-6.6/bitops-make-bytes_to_bits-treewide-available.patch
new file mode 100644 (file)
index 0000000..f5f87f6
--- /dev/null
@@ -0,0 +1,90 @@
+From stable+bounces-274000-greg=kroah.com@vger.kernel.org Mon Jul 13 22:39:35 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 13 Jul 2026 16:39:23 -0400
+Subject: bitops: make BYTES_TO_BITS() treewide-available
+To: stable@vger.kernel.org
+Cc: Alexander Lobakin <aleksander.lobakin@intel.com>, Andy Shevchenko <andriy.shevchenko@linux.intel.com>, Przemek Kitszel <przemyslaw.kitszel@intel.com>, Yury Norov <yury.norov@gmail.com>, "David S. Miller" <davem@davemloft.net>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260713203924.2143667-1-sashal@kernel.org>
+
+From: Alexander Lobakin <aleksander.lobakin@intel.com>
+
+[ Upstream commit 7d8296b250f2eed73f1758607926d4d258dea5d4 ]
+
+Avoid open-coding that simple expression each time by moving
+BYTES_TO_BITS() from the probes code to <linux/bitops.h> to export
+it to the rest of the kernel.
+Simplify the macro while at it. `BITS_PER_LONG / sizeof(long)` always
+equals to %BITS_PER_BYTE, regardless of the target architecture.
+Do the same for the tools ecosystem as well (incl. its version of
+bitops.h). The previous implementation had its implicit type of long,
+while the new one is int, so adjust the format literal accordingly in
+the perf code.
+
+Suggested-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
+Reviewed-by: Przemek Kitszel <przemyslaw.kitszel@intel.com>
+Acked-by: Yury Norov <yury.norov@gmail.com>
+Signed-off-by: Alexander Lobakin <aleksander.lobakin@intel.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+Stable-dep-of: 55052184ac90 ("iio: common: st_sensors: honour channel endianness in read_axis_data")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/bitops.h         |    2 ++
+ kernel/trace/trace_probe.c     |    2 --
+ tools/include/linux/bitops.h   |    2 ++
+ tools/perf/util/probe-finder.c |    4 +---
+ 4 files changed, 5 insertions(+), 5 deletions(-)
+
+--- a/include/linux/bitops.h
++++ b/include/linux/bitops.h
+@@ -20,6 +20,8 @@
+ #define BITS_TO_U32(nr)               __KERNEL_DIV_ROUND_UP(nr, BITS_PER_TYPE(u32))
+ #define BITS_TO_BYTES(nr)     __KERNEL_DIV_ROUND_UP(nr, BITS_PER_TYPE(char))
++#define BYTES_TO_BITS(nb)     ((nb) * BITS_PER_BYTE)
++
+ extern unsigned int __sw_hweight8(unsigned int w);
+ extern unsigned int __sw_hweight16(unsigned int w);
+ extern unsigned int __sw_hweight32(unsigned int w);
+--- a/kernel/trace/trace_probe.c
++++ b/kernel/trace/trace_probe.c
+@@ -1220,8 +1220,6 @@ parse_probe_arg(char *arg, const struct
+       return ret;
+ }
+-#define BYTES_TO_BITS(nb)     ((BITS_PER_LONG * (nb)) / sizeof(long))
+-
+ /* Bitfield type needs to be parsed into a fetch function */
+ static int __parse_bitfield_probe_arg(const char *bf,
+                                     const struct fetch_type *t,
+--- a/tools/include/linux/bitops.h
++++ b/tools/include/linux/bitops.h
+@@ -20,6 +20,8 @@
+ #define BITS_TO_U32(nr)               DIV_ROUND_UP(nr, BITS_PER_TYPE(u32))
+ #define BITS_TO_BYTES(nr)     DIV_ROUND_UP(nr, BITS_PER_TYPE(char))
++#define BYTES_TO_BITS(nb)     ((nb) * BITS_PER_BYTE)
++
+ extern unsigned int __sw_hweight8(unsigned int w);
+ extern unsigned int __sw_hweight16(unsigned int w);
+ extern unsigned int __sw_hweight32(unsigned int w);
+--- a/tools/perf/util/probe-finder.c
++++ b/tools/perf/util/probe-finder.c
+@@ -304,8 +304,6 @@ static_var:
+       return ret2;
+ }
+-#define BYTES_TO_BITS(nb)     ((nb) * BITS_PER_LONG / sizeof(long))
+-
+ static int convert_variable_type(Dwarf_Die *vr_die,
+                                struct probe_trace_arg *tvar,
+                                const char *cast, bool user_access)
+@@ -335,7 +333,7 @@ static int convert_variable_type(Dwarf_D
+               total = dwarf_bytesize(vr_die);
+               if (boffs < 0 || total < 0)
+                       return -ENOENT;
+-              ret = snprintf(buf, 16, "b%d@%d/%zd", bsize, boffs,
++              ret = snprintf(buf, 16, "b%d@%d/%d", bsize, boffs,
+                               BYTES_TO_BITS(total));
+               goto formatted;
+       }
diff --git a/queue-6.6/bluetooth-6lowpan-fix-cyclic-locking-warning-on-netdev-unregister.patch b/queue-6.6/bluetooth-6lowpan-fix-cyclic-locking-warning-on-netdev-unregister.patch
new file mode 100644 (file)
index 0000000..7dec330
--- /dev/null
@@ -0,0 +1,101 @@
+From stable+bounces-275051-greg=kroah.com@vger.kernel.org Wed Jul 15 23:26:50 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 17:26:42 -0400
+Subject: Bluetooth: 6lowpan: fix cyclic locking warning on netdev unregister
+To: stable@vger.kernel.org
+Cc: Pauli Virtanen <pav@iki.fi>, Luiz Augusto von Dentz <luiz.von.dentz@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715212643.278587-1-sashal@kernel.org>
+
+From: Pauli Virtanen <pav@iki.fi>
+
+[ Upstream commit 9707a015fe8f3ba8ec7c270f3b2b8efb38823d6b ]
+
+6lowpan.c has theoretically conflicting lock orderings, which lockdep
+complains about:
+
+    a) rtnl_lock > hdev->workqueue
+
+    from 6lowpan.c:delete_netdev -> rtnl_lock -> device_del
+    -> put_device(parent) -> hci_release_dev -> destroy_workqueue
+
+    b) hdev->workqueue > l2cap_conn->lock > chan->lock > rtnl_lock
+
+    from hci_rx_work -> 6lowpan.c:chan_ready_cb
+    -> lowpan_register_netdev, ifup -> rtnl_lock
+
+Actual deadlock appears not possible, as hci_rx_work is disabled and
+l2cap_conn flushed already on hdev unregister. Hence, do minimal thing
+to make lockdep happy by breaking chain a) by holding hdev refcount
+until after netdev put in 6lowpan.c.
+
+Fixes the lockdep complaint:
+WARNING: possible circular locking dependency detected.
+kworker/0:1/11 is trying to acquire lock:
+ffff8880023b3940 ((wq_completion)hci0#2){+.+.}-{0:0}, at: touch_wq_lockdep_map+0x8b/0x130
+but task is already holding lock:
+ffffffff95e4f9c0 (rtnl_mutex){+.+.}-{4:4}, at: lowpan_unregister_netdev+0xd/0x30
+Workqueue: events delete_netdev
+
+Signed-off-by: Pauli Virtanen <pav@iki.fi>
+Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
+Stable-dep-of: 6fef032af009 ("Bluetooth: L2CAP: Fix use-after-free in l2cap_sock_new_connection_cb()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/bluetooth/6lowpan.c |   25 +++++++++++++++++++++++--
+ 1 file changed, 23 insertions(+), 2 deletions(-)
+
+--- a/net/bluetooth/6lowpan.c
++++ b/net/bluetooth/6lowpan.c
+@@ -759,13 +759,33 @@ static inline struct l2cap_chan *chan_ne
+       return chan;
+ }
++static void unregister_dev(struct lowpan_btle_dev *dev)
++{
++      struct hci_dev *hdev = READ_ONCE(dev->hdev);
++
++      /* If netdev holds last reference to hci_dev (its parent device), this
++       * leads to theoretical cyclic locking on lowpan_unregister_netdev:
++       *
++       * rtnl_lock -> put_device(parent) -> hci_release_dev ->
++       * destroy_workqueue -> hci_rx_work -> l2cap_recv_acldata ->
++       * chan_ready_cb -> ifup -> rtnl_lock
++       *
++       * However, hci_rx_work is disabled in hci_unregister_dev, so this
++       * should not occur. Make lockdep happy by postponing hdev release after
++       * netdev put.
++       */
++      hci_dev_hold(hdev);
++      lowpan_unregister_netdev(dev->netdev);
++      hci_dev_put(hdev);
++}
++
+ static void delete_netdev(struct work_struct *work)
+ {
+       struct lowpan_btle_dev *entry = container_of(work,
+                                                    struct lowpan_btle_dev,
+                                                    delete_netdev);
+-      lowpan_unregister_netdev(entry->netdev);
++      unregister_dev(entry);
+       /* The entry pointer is deleted by the netdev destructor. */
+ }
+@@ -1254,6 +1274,7 @@ static void disconnect_devices(void)
+                       break;
+               new_dev->netdev = entry->netdev;
++              new_dev->hdev = entry->hdev;
+               INIT_LIST_HEAD(&new_dev->list);
+               list_add_rcu(&new_dev->list, &devices);
+@@ -1265,7 +1286,7 @@ static void disconnect_devices(void)
+               ifdown(entry->netdev);
+               BT_DBG("Unregistering netdev %s %p",
+                      entry->netdev->name, entry->netdev);
+-              lowpan_unregister_netdev(entry->netdev);
++              unregister_dev(entry);
+               kfree(entry);
+       }
+ }
diff --git a/queue-6.6/bluetooth-l2cap-cancel-pending_rx_work-before-taking-conn-lock.patch b/queue-6.6/bluetooth-l2cap-cancel-pending_rx_work-before-taking-conn-lock.patch
new file mode 100644 (file)
index 0000000..de55c47
--- /dev/null
@@ -0,0 +1,70 @@
+From stable+bounces-275048-greg=kroah.com@vger.kernel.org Wed Jul 15 23:18:49 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 17:18:42 -0400
+Subject: Bluetooth: L2CAP: cancel pending_rx_work before taking conn->lock
+To: stable@vger.kernel.org
+Cc: Runyu Xiao <runyu.xiao@seu.edu.cn>, Luiz Augusto von Dentz <luiz.von.dentz@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715211842.270885-1-sashal@kernel.org>
+
+From: Runyu Xiao <runyu.xiao@seu.edu.cn>
+
+[ Upstream commit 2641a9e0a1dd4af2e21995470a21d55dd35e5203 ]
+
+l2cap_conn_del() takes conn->lock and then calls cancel_work_sync() for
+pending_rx_work.  process_pending_rx() takes the same mutex, so teardown
+can deadlock against the worker it is flushing.
+
+This issue was found by our static analysis tool and then manually
+reviewed against the current tree.
+
+The grounded PoC kept the l2cap_conn_ready() -> queue_work(...,
+&conn->pending_rx_work) submit path, the l2cap_conn_del() ->
+cancel_work_sync(&conn->pending_rx_work) teardown path, and the
+process_pending_rx() -> mutex_lock(&conn->lock) worker edge.  Lockdep
+reported:
+
+  WARNING: possible circular locking dependency detected
+  process_pending_rx+0x21/0x2a [vuln_msv]
+  l2cap_conn_del.constprop.0+0x3f/0x4e [vuln_msv]
+  *** DEADLOCK ***
+
+Cancel pending_rx_work before taking conn->lock, matching the existing
+lock-before-drain ordering used for the two delayed works in the same
+teardown path.  The pending_rx queue is still purged after the work has
+been cancelled and conn->lock has been acquired.
+
+Fixes: 7ab56c3a6ecc ("Bluetooth: Fix deadlock in l2cap_conn_del()")
+Cc: stable@vger.kernel.org
+Signed-off-by: Runyu Xiao <runyu.xiao@seu.edu.cn>
+Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
+[ adjusted context for 6.6 lacking the disable_delayed_work_sync() conversion and the ida_destroy(&conn->tx_ida) line from the tx_ident IDA dependency ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/bluetooth/l2cap_core.c |   10 ++--------
+ 1 file changed, 2 insertions(+), 8 deletions(-)
+
+--- a/net/bluetooth/l2cap_core.c
++++ b/net/bluetooth/l2cap_core.c
+@@ -1760,19 +1760,13 @@ static void l2cap_conn_del(struct hci_co
+       BT_DBG("hcon %p conn %p, err %d", hcon, conn, err);
++      cancel_work_sync(&conn->pending_rx_work);
++
+       mutex_lock(&conn->lock);
+       kfree_skb(conn->rx_skb);
+       skb_queue_purge(&conn->pending_rx);
+-
+-      /* We can not call flush_work(&conn->pending_rx_work) here since we
+-       * might block if we are running on a worker from the same workqueue
+-       * pending_rx_work is waiting on.
+-       */
+-      if (work_pending(&conn->pending_rx_work))
+-              cancel_work_sync(&conn->pending_rx_work);
+-
+       cancel_delayed_work_sync(&conn->id_addr_timer);
+       l2cap_unregister_all_users(conn);
diff --git a/queue-6.6/bluetooth-l2cap-fix-uaf-in-channel-timeout-by-holding-conn-ref.patch b/queue-6.6/bluetooth-l2cap-fix-uaf-in-channel-timeout-by-holding-conn-ref.patch
new file mode 100644 (file)
index 0000000..18e53fa
--- /dev/null
@@ -0,0 +1,246 @@
+From stable+bounces-274704-greg=kroah.com@vger.kernel.org Wed Jul 15 04:52:03 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 22:51:56 -0400
+Subject: Bluetooth: L2CAP: Fix UAF in channel timeout by holding conn ref
+To: stable@vger.kernel.org
+Cc: Marco Elver <elver@google.com>, Siwei Zhang <oss@fourdim.xyz>, Luiz Augusto von Dentz <luiz.von.dentz@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715025156.113462-1-sashal@kernel.org>
+
+From: Marco Elver <elver@google.com>
+
+[ Upstream commit b66774b48dd98f07254951f74ea6f513efe7ff8b ]
+
+l2cap_chan_timeout() runs asynchronously and accesses chan->conn. If
+the connection is torn down while the timer is running or pending,
+chan->conn can be freed, leading to a use-after-free when the timer
+worker attempts to lock conn->lock:
+
+| BUG: KASAN: slab-use-after-free in instrument_atomic_read_write include/linux/instrumented.h:112 [inline]
+| BUG: KASAN: slab-use-after-free in atomic_long_try_cmpxchg_acquire include/linux/atomic/atomic-instrumented.h:4456 [inline]
+| BUG: KASAN: slab-use-after-free in __mutex_trylock_fast kernel/locking/mutex.c:161 [inline]
+| BUG: KASAN: slab-use-after-free in mutex_lock+0x4f/0xa0 kernel/locking/mutex.c:318
+| Write of size 8 at addr ffff8881298d9550 by task kworker/2:1/83
+|
+| CPU: 2 UID: 0 PID: 83 Comm: kworker/2:1 Not tainted 7.1.0-rc6-next-20260601-dirty #6 PREEMPT(full)
+| Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.17.0-debian-1.17.0-1 04/01/2014
+| Workqueue: events l2cap_chan_timeout
+| Call Trace:
+|  <TASK>
+|  instrument_atomic_read_write include/linux/instrumented.h:112 [inline]
+|  atomic_long_try_cmpxchg_acquire include/linux/atomic/atomic-instrumented.h:4456 [inline]
+|  __mutex_trylock_fast kernel/locking/mutex.c:161 [inline]
+|  mutex_lock+0x4f/0xa0 kernel/locking/mutex.c:318
+|  l2cap_chan_timeout+0x5d/0x1b0 net/bluetooth/l2cap_core.c:422
+|  process_one_work kernel/workqueue.c:3326 [inline]
+|  process_scheduled_works+0x7c8/0xfb0 kernel/workqueue.c:3409
+|  worker_thread+0x8a9/0xcf0 kernel/workqueue.c:3490
+|  kthread+0x346/0x430 kernel/kthread.c:436
+|  ret_from_fork+0x1a3/0x470 arch/x86/kernel/process.c:158
+|  ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
+|  </TASK>
+|
+| Allocated by task 320:
+|  l2cap_conn_add+0xa7/0x820 net/bluetooth/l2cap_core.c:7075
+|  l2cap_connect_cfm+0xdb/0xd70 net/bluetooth/l2cap_core.c:7452
+|  hci_connect_cfm include/net/bluetooth/hci_core.h:2139 [inline]
+|  hci_remote_features_evt+0x52f/0x9f0 net/bluetooth/hci_event.c:3760
+|  hci_event_func net/bluetooth/hci_event.c:7796 [inline]
+|  hci_event_packet+0x561/0xa70 net/bluetooth/hci_event.c:7847
+|  hci_rx_work+0x370/0x890 net/bluetooth/hci_core.c:4040
+|  process_one_work kernel/workqueue.c:3326 [inline]
+|  process_scheduled_works+0x7c8/0xfb0 kernel/workqueue.c:3409
+|  worker_thread+0x8a9/0xcf0 kernel/workqueue.c:3490
+|  kthread+0x346/0x430 kernel/kthread.c:436
+|  ret_from_fork+0x1a3/0x470 arch/x86/kernel/process.c:158
+|  ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
+|
+| Freed by task 322:
+|  hci_disconn_cfm include/net/bluetooth/hci_core.h:2154 [inline]
+|  hci_conn_hash_flush+0x101/0x1f0 net/bluetooth/hci_conn.c:2736
+|  hci_dev_close_sync+0x889/0xde0 net/bluetooth/hci_sync.c:5405
+|  hci_dev_do_close net/bluetooth/hci_core.c:502 [inline]
+|  hci_unregister_dev+0x1f7/0x370 net/bluetooth/hci_core.c:2679
+|  vhci_release+0x12a/0x180 drivers/bluetooth/hci_vhci.c:690
+|  __fput+0x369/0x890 fs/file_table.c:510
+|  task_work_run+0x160/0x1d0 kernel/task_work.c:233
+|  get_signal+0xf5b/0x1120 kernel/signal.c:2810
+|  arch_do_signal_or_restart+0x4d/0x600 arch/x86/kernel/signal.c:337
+|  __exit_to_user_mode_loop kernel/entry/common.c:64 [inline]
+|  exit_to_user_mode_loop+0x85/0x510 kernel/entry/common.c:98
+|  do_syscall_64+0x263/0x3d0 arch/x86/entry/syscall_64.c:100
+|  entry_SYSCALL_64_after_hwframe+0x77/0x7f
+|
+| The buggy address belongs to the object at ffff8881298d9400
+|  which belongs to the cache kmalloc-512 of size 512
+| The buggy address is located 336 bytes inside of
+|  freed 512-byte region [ffff8881298d9400, ffff8881298d9600)
+
+Fix it by having chan->conn hold a reference to l2cap_conn (via
+l2cap_conn_get) when the channel is added to the connection, and
+releasing it in the channel destructor. This ensures the l2cap_conn
+remains alive as long as the channel exists.
+
+A new FLAG_DEL channel flag is introduced to indicate that the channel
+has been deleted from its connection. l2cap_chan_del() atomically sets
+this flag using test_and_set_bit() instead of setting chan->conn to
+NULL. All asynchronous workers (l2cap_chan_timeout, l2cap_ack_timeout,
+l2cap_monitor_timeout, l2cap_retrans_timeout) and l2cap_chan_send()
+check FLAG_DEL to determine whether the channel has been torn down,
+rather than testing chan->conn for NULL.
+
+Fixes: 8c8e620467a7 ("Bluetooth: L2CAP: use chan timer to close channels in cleanup_listen()")
+Cc: <stable@vger.kernel.org>
+Cc: Siwei Zhang <oss@fourdim.xyz>
+Cc: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
+Assisted-by: Gemini:gemini-3.1-pro-preview
+Reported-by: https://sashiko.dev/#/patchset/20260521021249.3258069-1-oss%40fourdim.xyz
+Signed-off-by: Marco Elver <elver@google.com>
+Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/net/bluetooth/l2cap.h |    1 +
+ net/bluetooth/l2cap_core.c    |   34 ++++++++++++++++++++--------------
+ 2 files changed, 21 insertions(+), 14 deletions(-)
+
+--- a/include/net/bluetooth/l2cap.h
++++ b/include/net/bluetooth/l2cap.h
+@@ -742,6 +742,7 @@ enum {
+       FLAG_ECRED_CONN_REQ_SENT,
+       FLAG_PENDING_SECURITY,
+       FLAG_HOLD_HCI_CONN,
++      FLAG_DEL,
+ };
+ /* Lock nesting levels for L2CAP channels. We need these because lockdep
+--- a/net/bluetooth/l2cap_core.c
++++ b/net/bluetooth/l2cap_core.c
+@@ -411,7 +411,7 @@ static void l2cap_chan_timeout(struct wo
+       BT_DBG("chan %p state %s", chan, state_to_string(chan->state));
+-      if (!conn) {
++      if (test_bit(FLAG_DEL, &chan->flags)) {
+               l2cap_chan_put(chan);
+               return;
+       }
+@@ -422,6 +422,9 @@ static void l2cap_chan_timeout(struct wo
+        */
+       l2cap_chan_lock(chan);
++      if (test_bit(FLAG_DEL, &chan->flags))
++              goto unlock;
++
+       if (chan->state == BT_CONNECTED || chan->state == BT_CONFIG)
+               reason = ECONNREFUSED;
+       else if (chan->state == BT_CONNECT &&
+@@ -434,10 +437,10 @@ static void l2cap_chan_timeout(struct wo
+       chan->ops->close(chan);
++unlock:
+       l2cap_chan_unlock(chan);
+-      l2cap_chan_put(chan);
+-
+       mutex_unlock(&conn->lock);
++      l2cap_chan_put(chan);
+ }
+ struct l2cap_chan *l2cap_chan_create(void)
+@@ -490,6 +493,9 @@ static void l2cap_chan_destroy(struct kr
+       list_del(&chan->global_l);
+       write_unlock(&chan_list_lock);
++      if (chan->conn)
++              l2cap_conn_put(chan->conn);
++
+       kfree(chan);
+ }
+@@ -593,7 +599,7 @@ void __l2cap_chan_add(struct l2cap_conn
+       conn->disc_reason = HCI_ERROR_REMOTE_USER_TERM;
+-      chan->conn = conn;
++      chan->conn = l2cap_conn_get(conn);
+       switch (chan->chan_type) {
+       case L2CAP_CHAN_CONN_ORIENTED:
+@@ -648,30 +654,26 @@ void l2cap_chan_add(struct l2cap_conn *c
+ void l2cap_chan_del(struct l2cap_chan *chan, int err)
+ {
+-      struct l2cap_conn *conn = chan->conn;
+-
+       __clear_chan_timer(chan);
+-      BT_DBG("chan %p, conn %p, err %d, state %s", chan, conn, err,
++      BT_DBG("chan %p, err %d, state %s", chan, err,
+              state_to_string(chan->state));
+       chan->ops->teardown(chan, err);
+-      if (conn) {
++      if (!test_and_set_bit(FLAG_DEL, &chan->flags)) {
+               /* Delete from channel list */
+               list_del(&chan->list);
+               l2cap_chan_put(chan);
+-              chan->conn = NULL;
+-
+               /* Reference was only held for non-fixed channels or
+                * fixed channels that explicitly requested it using the
+                * FLAG_HOLD_HCI_CONN flag.
+                */
+               if (chan->chan_type != L2CAP_CHAN_FIXED ||
+                   test_bit(FLAG_HOLD_HCI_CONN, &chan->flags))
+-                      hci_conn_drop(conn->hcon);
++                      hci_conn_drop(chan->conn->hcon);
+       }
+       if (test_bit(CONF_NOT_COMPLETE, &chan->conf_state))
+@@ -1888,7 +1890,7 @@ static void l2cap_monitor_timeout(struct
+       l2cap_chan_lock(chan);
+-      if (!chan->conn) {
++      if (test_bit(FLAG_DEL, &chan->flags)) {
+               l2cap_chan_unlock(chan);
+               l2cap_chan_put(chan);
+               return;
+@@ -1909,7 +1911,7 @@ static void l2cap_retrans_timeout(struct
+       l2cap_chan_lock(chan);
+-      if (!chan->conn) {
++      if (test_bit(FLAG_DEL, &chan->flags)) {
+               l2cap_chan_unlock(chan);
+               l2cap_chan_put(chan);
+               return;
+@@ -2524,7 +2526,7 @@ int l2cap_chan_send(struct l2cap_chan *c
+       int err;
+       struct sk_buff_head seg_queue;
+-      if (!chan->conn)
++      if (test_bit(FLAG_DEL, &chan->flags))
+               return -ENOTCONN;
+       /* Connectionless channel */
+@@ -3121,12 +3123,16 @@ static void l2cap_ack_timeout(struct wor
+       l2cap_chan_lock(chan);
++      if (test_bit(FLAG_DEL, &chan->flags))
++              goto unlock;
++
+       frames_to_ack = __seq_offset(chan, chan->buffer_seq,
+                                    chan->last_acked_seq);
+       if (frames_to_ack)
+               l2cap_send_rr_or_rnr(chan, 0);
++unlock:
+       l2cap_chan_unlock(chan);
+       l2cap_chan_put(chan);
+ }
diff --git a/queue-6.6/bluetooth-l2cap-fix-use-after-free-in-l2cap_sock_new_connection_cb.patch b/queue-6.6/bluetooth-l2cap-fix-use-after-free-in-l2cap_sock_new_connection_cb.patch
new file mode 100644 (file)
index 0000000..befb069
--- /dev/null
@@ -0,0 +1,542 @@
+From stable+bounces-275052-greg=kroah.com@vger.kernel.org Wed Jul 15 23:26:52 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 17:26:43 -0400
+Subject: Bluetooth: L2CAP: Fix use-after-free in l2cap_sock_new_connection_cb()
+To: stable@vger.kernel.org
+Cc: Siwei Zhang <oss@fourdim.xyz>, stable@kernel.org, Luiz Augusto von Dentz <luiz.von.dentz@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715212643.278587-2-sashal@kernel.org>
+
+From: Siwei Zhang <oss@fourdim.xyz>
+
+[ Upstream commit 6fef032af0092ed5ccb767239a9ac1bc38c08a40 ]
+
+l2cap_sock_new_connection_cb() returned l2cap_pi(sk)->chan after
+release_sock(parent). Once the parent lock is dropped the newly
+enqueued child socket sk is reachable via the accept queue, so another
+task can accept and free it before the callback dereferences sk,
+resulting in a use-after-free.
+
+Rework the ->new_connection() op so the core, rather than the callback,
+owns the child channel's lifetime. The op now receives a pre-allocated
+new_chan and returns an errno instead of allocating and returning a
+channel. l2cap_new_connection() allocates the child channel and links
+it into the conn list via __l2cap_chan_add() before invoking the
+callback, so the conn-list reference keeps the channel alive once
+release_sock(parent) exposes the socket to other tasks.
+
+Channel configuration that was duplicated in l2cap_sock_init() and the
+various new_connection callbacks is consolidated into
+l2cap_chan_set_defaults(), which now inherits from the parent channel
+when one is supplied.
+
+Fixes: 8ffb929098a5 ("Bluetooth: Remove parent socket usage from l2cap_core.c")
+Cc: stable@kernel.org
+Assisted-by: Claude:claude-opus-4-8
+Signed-off-by: Siwei Zhang <oss@fourdim.xyz>
+Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/net/bluetooth/l2cap.h |   10 ++--
+ net/bluetooth/6lowpan.c       |   18 -------
+ net/bluetooth/l2cap_core.c    |   78 ++++++++++++++++++++++++++-----
+ net/bluetooth/l2cap_sock.c    |  103 +++++++++++++++++++-----------------------
+ net/bluetooth/smp.c           |   27 ++---------
+ 5 files changed, 127 insertions(+), 109 deletions(-)
+
+--- a/include/net/bluetooth/l2cap.h
++++ b/include/net/bluetooth/l2cap.h
+@@ -614,7 +614,8 @@ struct l2cap_chan {
+ struct l2cap_ops {
+       char                    *name;
+-      struct l2cap_chan       *(*new_connection) (struct l2cap_chan *chan);
++      int                     (*new_connection)(struct l2cap_chan *chan,
++                                                struct l2cap_chan *new_chan);
+       int                     (*recv) (struct l2cap_chan * chan,
+                                        struct sk_buff *skb);
+       void                    (*teardown) (struct l2cap_chan *chan, int err);
+@@ -879,9 +880,10 @@ static inline __u16 __next_seq(struct l2
+       return (seq + 1) % (chan->tx_win_max + 1);
+ }
+-static inline struct l2cap_chan *l2cap_chan_no_new_connection(struct l2cap_chan *chan)
++static inline int l2cap_chan_no_new_connection(struct l2cap_chan *chan,
++                                             struct l2cap_chan *new_chan)
+ {
+-      return NULL;
++      return -EOPNOTSUPP;
+ }
+ static inline int l2cap_chan_no_recv(struct l2cap_chan *chan, struct sk_buff *skb)
+@@ -957,7 +959,7 @@ int l2cap_chan_send(struct l2cap_chan *c
+ void l2cap_chan_busy(struct l2cap_chan *chan, int busy);
+ void l2cap_chan_rx_avail(struct l2cap_chan *chan, ssize_t rx_avail);
+ int l2cap_chan_check_security(struct l2cap_chan *chan, bool initiator);
+-void l2cap_chan_set_defaults(struct l2cap_chan *chan);
++void l2cap_chan_set_defaults(struct l2cap_chan *chan, struct l2cap_chan *pchan);
+ int l2cap_ertm_init(struct l2cap_chan *chan);
+ void l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan);
+ void __l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan);
+--- a/net/bluetooth/6lowpan.c
++++ b/net/bluetooth/6lowpan.c
+@@ -631,7 +631,7 @@ static struct l2cap_chan *chan_create(vo
+       if (!chan)
+               return NULL;
+-      l2cap_chan_set_defaults(chan);
++      l2cap_chan_set_defaults(chan, NULL);
+       chan->chan_type = L2CAP_CHAN_CONN_ORIENTED;
+       chan->mode = L2CAP_MODE_LE_FLOWCTL;
+@@ -744,21 +744,6 @@ static inline void chan_ready_cb(struct
+       ifup(dev->netdev);
+ }
+-static inline struct l2cap_chan *chan_new_conn_cb(struct l2cap_chan *pchan)
+-{
+-      struct l2cap_chan *chan;
+-
+-      chan = chan_create();
+-      if (!chan)
+-              return NULL;
+-
+-      chan->ops = pchan->ops;
+-
+-      BT_DBG("chan %p pchan %p", chan, pchan);
+-
+-      return chan;
+-}
+-
+ static void unregister_dev(struct lowpan_btle_dev *dev)
+ {
+       struct hci_dev *hdev = READ_ONCE(dev->hdev);
+@@ -900,7 +885,6 @@ static long chan_get_sndtimeo_cb(struct
+ static const struct l2cap_ops bt_6lowpan_chan_ops = {
+       .name                   = "L2CAP 6LoWPAN channel",
+-      .new_connection         = chan_new_conn_cb,
+       .recv                   = chan_recv_cb,
+       .close                  = chan_close_cb,
+       .state_change           = chan_state_change_cb,
+--- a/net/bluetooth/l2cap_core.c
++++ b/net/bluetooth/l2cap_core.c
+@@ -525,7 +525,10 @@ void l2cap_chan_put(struct l2cap_chan *c
+ }
+ EXPORT_SYMBOL_GPL(l2cap_chan_put);
+-void l2cap_chan_set_defaults(struct l2cap_chan *chan)
++/* Initialise @chan with default values, inheriting from the parent channel
++ * @pchan when it is given.
++ */
++void l2cap_chan_set_defaults(struct l2cap_chan *chan, struct l2cap_chan *pchan)
+ {
+       chan->fcs  = L2CAP_FCS_CRC16;
+       chan->max_tx = L2CAP_DEFAULT_MAX_TX;
+@@ -539,6 +542,31 @@ void l2cap_chan_set_defaults(struct l2ca
+       chan->retrans_timeout = L2CAP_DEFAULT_RETRANS_TO;
+       chan->monitor_timeout = L2CAP_DEFAULT_MONITOR_TO;
++      if (pchan) {
++              BT_DBG("chan %p pchan %p", chan, pchan);
++
++              chan->chan_type = pchan->chan_type;
++              chan->imtu = pchan->imtu;
++              chan->omtu = pchan->omtu;
++              chan->mode = pchan->mode;
++              chan->fcs = pchan->fcs;
++              chan->max_tx = pchan->max_tx;
++              chan->tx_win = pchan->tx_win;
++              chan->tx_win_max = pchan->tx_win_max;
++              chan->sec_level = pchan->sec_level;
++              chan->conf_state = pchan->conf_state;
++              chan->flags = pchan->flags;
++              chan->tx_credits = pchan->tx_credits;
++              chan->rx_credits = pchan->rx_credits;
++
++              if (chan->chan_type == L2CAP_CHAN_FIXED) {
++                      chan->scid = pchan->scid;
++                      chan->dcid = pchan->scid;
++              }
++
++              return;
++      }
++
+       chan->conf_state = 0;
+       set_bit(CONF_NOT_COMPLETE, &chan->conf_state);
+@@ -3975,6 +4003,38 @@ static inline int l2cap_command_rej(stru
+       return 0;
+ }
++/* Allocate and initialise a channel for an incoming connection.
++ *
++ * The channel inherits its configuration from @pchan and is linked into @conn
++ * before ->new_connection() runs, so the conn list reference keeps it alive if
++ * the callback exposes it (e.g. via the socket accept queue) before this
++ * returns. The l2cap_chan_create() reference is taken over by the subsystem on
++ * success and dropped here on failure.
++ */
++static struct l2cap_chan *l2cap_new_connection(struct l2cap_conn *conn,
++                                             struct l2cap_chan *pchan)
++{
++      struct l2cap_chan *chan;
++
++      chan = l2cap_chan_create();
++      if (!chan)
++              return NULL;
++
++      l2cap_chan_set_defaults(chan, pchan);
++      chan->ops = pchan->ops;
++
++      __l2cap_chan_add(conn, chan);
++
++      if (pchan->ops->new_connection &&
++          pchan->ops->new_connection(pchan, chan) < 0) {
++              l2cap_chan_del(chan, 0);
++              l2cap_chan_put(chan);
++              return NULL;
++      }
++
++      return chan;
++}
++
+ static void l2cap_connect(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd,
+                         u8 *data, u8 rsp_code)
+ {
+@@ -4021,7 +4081,7 @@ static void l2cap_connect(struct l2cap_c
+               goto response;
+       }
+-      chan = pchan->ops->new_connection(pchan);
++      chan = l2cap_new_connection(conn, pchan);
+       if (!chan)
+               goto response;
+@@ -4039,8 +4099,6 @@ static void l2cap_connect(struct l2cap_c
+       chan->psm  = psm;
+       chan->dcid = scid;
+-      __l2cap_chan_add(conn, chan);
+-
+       dcid = chan->scid;
+       __set_chan_timer(chan, chan->ops->get_sndtimeo(chan));
+@@ -4909,7 +4967,7 @@ static int l2cap_le_connect_req(struct l
+               goto response_unlock;
+       }
+-      chan = pchan->ops->new_connection(pchan);
++      chan = l2cap_new_connection(conn, pchan);
+       if (!chan) {
+               result = L2CAP_CR_LE_NO_MEM;
+               goto response_unlock;
+@@ -4924,8 +4982,6 @@ static int l2cap_le_connect_req(struct l
+       chan->omtu = mtu;
+       chan->remote_mps = mps;
+-      __l2cap_chan_add(conn, chan);
+-
+       l2cap_le_flowctl_init(chan, __le16_to_cpu(req->credits));
+       dcid = chan->scid;
+@@ -5134,7 +5190,7 @@ static inline int l2cap_ecred_conn_req(s
+                       continue;
+               }
+-              chan = pchan->ops->new_connection(pchan);
++              chan = l2cap_new_connection(conn, pchan);
+               if (!chan) {
+                       result = L2CAP_CR_LE_NO_MEM;
+                       continue;
+@@ -5149,8 +5205,6 @@ static inline int l2cap_ecred_conn_req(s
+               chan->omtu = mtu;
+               chan->remote_mps = mps;
+-              __l2cap_chan_add(conn, chan);
+-
+               l2cap_ecred_init(chan, __le16_to_cpu(req->credits));
+               /* Init response */
+@@ -7421,14 +7475,12 @@ static void l2cap_connect_cfm(struct hci
+                       goto next;
+               l2cap_chan_lock(pchan);
+-              chan = pchan->ops->new_connection(pchan);
++              chan = l2cap_new_connection(conn, pchan);
+               if (chan) {
+                       bacpy(&chan->src, &hcon->src);
+                       bacpy(&chan->dst, &hcon->dst);
+                       chan->src_type = bdaddr_src_type(hcon);
+                       chan->dst_type = dst_type;
+-
+-                      __l2cap_chan_add(conn, chan);
+               }
+               l2cap_chan_unlock(pchan);
+--- a/net/bluetooth/l2cap_sock.c
++++ b/net/bluetooth/l2cap_sock.c
+@@ -45,7 +45,8 @@ static struct bt_sock_list l2cap_sk_list
+ static const struct proto_ops l2cap_sock_ops;
+ static void l2cap_sock_init(struct sock *sk, struct sock *parent);
+ static struct sock *l2cap_sock_alloc(struct net *net, struct socket *sock,
+-                                   int proto, gfp_t prio, int kern);
++                                   int proto, gfp_t prio, int kern,
++                                   struct l2cap_chan *chan);
+ static void l2cap_sock_cleanup_listen(struct sock *parent);
+ bool l2cap_is_socket(struct socket *sock)
+@@ -1240,6 +1241,23 @@ done:
+       return err;
+ }
++/* Release the sock's ref on chan and clear the pointer so that the ref is
++ * dropped exactly once even if both l2cap_sock_kill() and
++ * l2cap_sock_destruct() run. Setting chan->data to NULL first stops any other
++ * task from dereferencing the now-dead sock pointer.
++ */
++static void l2cap_sock_put_chan(struct sock *sk)
++{
++      struct l2cap_chan *chan = l2cap_pi(sk)->chan;
++
++      if (!chan)
++              return;
++
++      chan->data = NULL;
++      l2cap_pi(sk)->chan = NULL;
++      l2cap_chan_put(chan);
++}
++
+ /* Kill socket (only if zapped and orphan)
+  * Must be called on unlocked socket, with l2cap channel lock.
+  */
+@@ -1250,13 +1268,9 @@ static void l2cap_sock_kill(struct sock
+       BT_DBG("sk %p state %s", sk, state_to_string(sk->sk_state));
+-      /* Sock is dead, so set chan data to NULL, avoid other task use invalid
+-       * sock pointer.
+-       */
+-      l2cap_pi(sk)->chan->data = NULL;
+-      /* Kill poor orphan */
++      l2cap_sock_put_chan(sk);
+-      l2cap_chan_put(l2cap_pi(sk)->chan);
++      /* Kill poor orphan */
+       sock_set_flag(sk, SOCK_DEAD);
+       sock_put(sk);
+ }
+@@ -1499,12 +1513,13 @@ static void l2cap_sock_cleanup_listen(st
+       }
+ }
+-static struct l2cap_chan *l2cap_sock_new_connection_cb(struct l2cap_chan *chan)
++static int l2cap_sock_new_connection_cb(struct l2cap_chan *chan,
++                                      struct l2cap_chan *new_chan)
+ {
+       struct sock *sk, *parent = chan->data;
+       if (!parent)
+-              return NULL;
++              return -EINVAL;
+       lock_sock(parent);
+@@ -1512,25 +1527,28 @@ static struct l2cap_chan *l2cap_sock_new
+       if (sk_acceptq_is_full(parent)) {
+               BT_DBG("backlog full %d", parent->sk_ack_backlog);
+               release_sock(parent);
+-              return NULL;
++              return -ENOBUFS;
+       }
+       sk = l2cap_sock_alloc(sock_net(parent), NULL, BTPROTO_L2CAP,
+-                            GFP_ATOMIC, 0);
++                            GFP_ATOMIC, 0, new_chan);
+       if (!sk) {
+               release_sock(parent);
+-              return NULL;
+-        }
++              return -ENOMEM;
++      }
+       bt_sock_reclassify_lock(sk, BTPROTO_L2CAP);
+       l2cap_sock_init(sk, parent);
++      /* The conn list reference taken by l2cap_new_connection() keeps new_chan
++       * alive once release_sock() lets another task free this socket.
++       */
+       bt_accept_enqueue(parent, sk, false);
+       release_sock(parent);
+-      return l2cap_pi(sk)->chan;
++      return 0;
+ }
+ static int l2cap_sock_recv_cb(struct l2cap_chan *chan, struct sk_buff *skb)
+@@ -1828,10 +1846,7 @@ static void l2cap_sock_destruct(struct s
+       BT_DBG("sk %p", sk);
+-      if (l2cap_pi(sk)->chan) {
+-              l2cap_pi(sk)->chan->data = NULL;
+-              l2cap_chan_put(l2cap_pi(sk)->chan);
+-      }
++      l2cap_sock_put_chan(sk);
+       list_for_each_entry_safe(rx_busy, next, &l2cap_pi(sk)->rx_busy, list) {
+               kfree_skb(rx_busy->skb);
+@@ -1863,30 +1878,12 @@ static void l2cap_sock_init(struct sock
+       BT_DBG("sk %p", sk);
+       if (parent) {
+-              struct l2cap_chan *pchan = l2cap_pi(parent)->chan;
+-
+               sk->sk_type = parent->sk_type;
+               bt_sk(sk)->flags = bt_sk(parent)->flags;
+-              chan->chan_type = pchan->chan_type;
+-              chan->imtu = pchan->imtu;
+-              chan->omtu = pchan->omtu;
+-              chan->conf_state = pchan->conf_state;
+-              chan->mode = pchan->mode;
+-              chan->fcs  = pchan->fcs;
+-              chan->max_tx = pchan->max_tx;
+-              chan->tx_win = pchan->tx_win;
+-              chan->tx_win_max = pchan->tx_win_max;
+-              chan->sec_level = pchan->sec_level;
+-              chan->flags = pchan->flags;
+-              chan->tx_credits = pchan->tx_credits;
+-              chan->rx_credits = pchan->rx_credits;
+-
+-              if (chan->chan_type == L2CAP_CHAN_FIXED) {
+-                      chan->scid = pchan->scid;
+-                      chan->dcid = pchan->scid;
+-              }
+-
++              /* Channel configuration is inherited from the parent by
++               * l2cap_new_connection().
++               */
+               security_sk_clone(parent, sk);
+       } else {
+               switch (sk->sk_type) {
+@@ -1912,7 +1909,7 @@ static void l2cap_sock_init(struct sock
+                       chan->mode = L2CAP_MODE_BASIC;
+               }
+-              l2cap_chan_set_defaults(chan);
++              l2cap_chan_set_defaults(chan, NULL);
+       }
+       /* Default config options */
+@@ -1931,10 +1928,10 @@ static struct proto l2cap_proto = {
+ };
+ static struct sock *l2cap_sock_alloc(struct net *net, struct socket *sock,
+-                                   int proto, gfp_t prio, int kern)
++                                   int proto, gfp_t prio, int kern,
++                                   struct l2cap_chan *chan)
+ {
+       struct sock *sk;
+-      struct l2cap_chan *chan;
+       sk = bt_sock_alloc(net, sock, &l2cap_proto, proto, prio, kern);
+       if (!sk)
+@@ -1945,16 +1942,7 @@ static struct sock *l2cap_sock_alloc(str
+       INIT_LIST_HEAD(&l2cap_pi(sk)->rx_busy);
+-      chan = l2cap_chan_create();
+-      if (!chan) {
+-              sk_free(sk);
+-              if (sock)
+-                      sock->sk = NULL;
+-              return NULL;
+-      }
+-
+-      l2cap_chan_hold(chan);
+-
++      /* The sock takes ownership of the caller's reference on chan. */
+       l2cap_pi(sk)->chan = chan;
+       return sk;
+@@ -1964,6 +1952,7 @@ static int l2cap_sock_create(struct net
+                            int kern)
+ {
+       struct sock *sk;
++      struct l2cap_chan *chan;
+       BT_DBG("sock %p", sock);
+@@ -1978,10 +1967,16 @@ static int l2cap_sock_create(struct net
+       sock->ops = &l2cap_sock_ops;
+-      sk = l2cap_sock_alloc(net, sock, protocol, GFP_ATOMIC, kern);
+-      if (!sk)
++      chan = l2cap_chan_create();
++      if (!chan)
+               return -ENOMEM;
++      sk = l2cap_sock_alloc(net, sock, protocol, GFP_ATOMIC, kern, chan);
++      if (!sk) {
++              l2cap_chan_put(chan);
++              return -ENOMEM;
++      }
++
+       l2cap_sock_init(sk, NULL);
+       bt_sock_link(&l2cap_sk_list, sk);
+       return 0;
+--- a/net/bluetooth/smp.c
++++ b/net/bluetooth/smp.c
+@@ -3235,34 +3235,19 @@ static const struct l2cap_ops smp_chan_o
+       .get_sndtimeo           = l2cap_chan_no_get_sndtimeo,
+ };
+-static inline struct l2cap_chan *smp_new_conn_cb(struct l2cap_chan *pchan)
++static inline int smp_new_conn_cb(struct l2cap_chan *chan,
++                                struct l2cap_chan *new_chan)
+ {
+-      struct l2cap_chan *chan;
+-
+-      BT_DBG("pchan %p", pchan);
+-
+-      chan = l2cap_chan_create();
+-      if (!chan)
+-              return NULL;
+-
+-      chan->chan_type = pchan->chan_type;
+-      chan->ops       = &smp_chan_ops;
+-      chan->scid      = pchan->scid;
+-      chan->dcid      = chan->scid;
+-      chan->imtu      = pchan->imtu;
+-      chan->omtu      = pchan->omtu;
+-      chan->mode      = pchan->mode;
++      new_chan->ops = &smp_chan_ops;
+       /* Other L2CAP channels may request SMP routines in order to
+        * change the security level. This means that the SMP channel
+        * lock must be considered in its own category to avoid lockdep
+        * warnings.
+        */
+-      atomic_set(&chan->nesting, L2CAP_NESTING_SMP);
+-
+-      BT_DBG("created chan %p", chan);
++      atomic_set(&new_chan->nesting, L2CAP_NESTING_SMP);
+-      return chan;
++      return 0;
+ }
+ static const struct l2cap_ops smp_root_chan_ops = {
+@@ -3333,7 +3318,7 @@ create_chan:
+       l2cap_add_scid(chan, cid);
+-      l2cap_chan_set_defaults(chan);
++      l2cap_chan_set_defaults(chan, NULL);
+       if (cid == L2CAP_CID_SMP) {
+               u8 bdaddr_type;
diff --git a/queue-6.6/bpf-allow-lpm-map-access-from-sleepable-bpf-programs.patch b/queue-6.6/bpf-allow-lpm-map-access-from-sleepable-bpf-programs.patch
new file mode 100644 (file)
index 0000000..39fdfb8
--- /dev/null
@@ -0,0 +1,100 @@
+From stable+bounces-277352-greg=kroah.com@vger.kernel.org Sat Jul 18 18:33:23 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sat, 18 Jul 2026 12:31:07 -0400
+Subject: bpf: Allow LPM map access from sleepable BPF programs
+To: stable@vger.kernel.org
+Cc: Vlad Poenaru <vlad.wing@gmail.com>, Emil Tsalapatis <emil@etsalapatis.com>, Alexei Starovoitov <ast@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260718163107.3338970-2-sashal@kernel.org>
+
+From: Vlad Poenaru <vlad.wing@gmail.com>
+
+[ Upstream commit 2f884d371fafea137afea504d49ee4a7c8d7985b ]
+
+trie_lookup_elem() annotates its rcu_dereference_check() walks with
+only rcu_read_lock_bh_held().  Because rcu_dereference_check(p, c)
+resolves to "c || rcu_read_lock_held()", this passes for XDP/NAPI and
+classic RCU readers but fails for sleepable BPF programs, which enter
+via __bpf_prog_enter_sleepable() and hold only rcu_read_lock_trace().
+
+trie_update_elem() and trie_delete_elem() have the same problem in a
+different form: they walk the trie with plain rcu_dereference(), which
+asserts rcu_read_lock_held() unconditionally.  Both are reachable from
+sleepable BPF programs via the bpf_map_update_elem / bpf_map_delete_elem
+helpers, and from the syscall path under classic rcu_read_lock().  In
+the writer paths the trie is actually protected by trie->lock (an
+rqspinlock taken across the walk); we never relied on the RCU read-side
+lock to keep nodes alive there.
+
+A sleepable LSM hook that ends up touching an LPM trie therefore
+triggers lockdep on debug kernels:
+
+  =============================
+  WARNING: suspicious RCU usage
+  7.1.0-... Tainted: G            E
+  -----------------------------
+  kernel/bpf/lpm_trie.c:249 suspicious rcu_dereference_check() usage!
+  1 lock held by net_tests/540:
+   #0: (rcu_tasks_trace_srcu_struct){....}-{0:0},
+       at: __bpf_prog_enter_sleepable+0x26/0x280
+  Call Trace:
+   dump_stack_lvl
+   lockdep_rcu_suspicious
+   trie_lookup_elem
+   bpf_prog_..._enforce_security_socket_connect
+   bpf_trampoline_...
+   security_socket_connect
+   __sys_connect
+   do_syscall_64
+
+This is lockdep-only -- no UAF, since Tasks Trace RCU does serialize
+against the trie's reclaim path -- but it spams the console once per
+distinct callsite on every debug kernel running a sleepable BPF LSM
+that touches an LPM trie, which is increasingly common.
+
+For the lookup path, switch the rcu_dereference_check() annotation
+from rcu_read_lock_bh_held() to bpf_rcu_lock_held(), which accepts all
+three contexts (classic, BH, Tasks Trace).  Other map types already
+follow this convention.
+
+For trie_update_elem() and trie_delete_elem(), annotate the walks as
+rcu_dereference_protected(*p, 1) -- matching trie_free() in the same
+file -- since trie->lock is held across the walk.  rqspinlock has no
+lockdep_map, so the predicate degenerates to '1' rather than
+lockdep_is_held(&trie->lock); the protection is real but not
+machine-verifiable.  trie_get_next_key() also uses bare
+rcu_dereference() but is reachable only from the BPF syscall, which
+holds classic rcu_read_lock() before dispatching, so it is left
+untouched.
+
+Fixes: 694cea395fde ("bpf: Allow RCU-protected lookups to happen from bh context")
+Cc: stable@vger.kernel.org
+Signed-off-by: Vlad Poenaru <vlad.wing@gmail.com>
+Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
+Link: https://lore.kernel.org/r/20260609135558.193287-2-vlad.wing@gmail.com
+Signed-off-by: Alexei Starovoitov <ast@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ kernel/bpf/lpm_trie.c |    4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/kernel/bpf/lpm_trie.c
++++ b/kernel/bpf/lpm_trie.c
+@@ -236,7 +236,7 @@ static void *trie_lookup_elem(struct bpf
+       /* Start walking the trie from the root node ... */
+-      for (node = rcu_dereference_check(trie->root, rcu_read_lock_bh_held());
++      for (node = rcu_dereference_check(trie->root, bpf_rcu_lock_held());
+            node;) {
+               unsigned int next_bit;
+               size_t matchlen;
+@@ -270,7 +270,7 @@ static void *trie_lookup_elem(struct bpf
+                */
+               next_bit = extract_bit(key->data, node->prefixlen);
+               node = rcu_dereference_check(node->child[next_bit],
+-                                           rcu_read_lock_bh_held());
++                                           bpf_rcu_lock_held());
+       }
+       if (!found)
diff --git a/queue-6.6/bpf-consistently-use-bpf_rcu_lock_held-everywhere.patch b/queue-6.6/bpf-consistently-use-bpf_rcu_lock_held-everywhere.patch
new file mode 100644 (file)
index 0000000..b55828e
--- /dev/null
@@ -0,0 +1,191 @@
+From stable+bounces-277351-greg=kroah.com@vger.kernel.org Sat Jul 18 18:33:20 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sat, 18 Jul 2026 12:31:06 -0400
+Subject: bpf: Consistently use bpf_rcu_lock_held() everywhere
+To: stable@vger.kernel.org
+Cc: Andrii Nakryiko <andrii@kernel.org>, Daniel Borkmann <daniel@iogearbox.net>, Jiri Olsa <jolsa@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260718163107.3338970-1-sashal@kernel.org>
+
+From: Andrii Nakryiko <andrii@kernel.org>
+
+[ Upstream commit 48a97ffc6c826640907d13b199e29008f4fe2c15 ]
+
+We have many places which open-code what's now is bpf_rcu_lock_held()
+macro, so replace all those places with a clean and short macro invocation.
+For that, move bpf_rcu_lock_held() macro into include/linux/bpf.h.
+
+Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
+Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
+Acked-by: Jiri Olsa <jolsa@kernel.org>
+Link: https://lore.kernel.org/bpf/20251014201403.4104511-1-andrii@kernel.org
+Stable-dep-of: 2f884d371faf ("bpf: Allow LPM map access from sleepable BPF programs")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/bpf.h               |    3 +++
+ include/linux/bpf_local_storage.h |    3 ---
+ kernel/bpf/hashtab.c              |   21 +++++++--------------
+ kernel/bpf/helpers.c              |   12 ++++--------
+ kernel/bpf/lpm_trie.c             |    6 ++----
+ 5 files changed, 16 insertions(+), 29 deletions(-)
+
+--- a/include/linux/bpf.h
++++ b/include/linux/bpf.h
+@@ -2047,6 +2047,9 @@ bpf_prog_run_array_uprobe(const struct b
+       return ret;
+ }
++#define bpf_rcu_lock_held() \
++      (rcu_read_lock_held() || rcu_read_lock_trace_held() || rcu_read_lock_bh_held())
++
+ #ifdef CONFIG_BPF_SYSCALL
+ DECLARE_PER_CPU(int, bpf_prog_active);
+ extern struct mutex bpf_stats_enabled_mutex;
+--- a/include/linux/bpf_local_storage.h
++++ b/include/linux/bpf_local_storage.h
+@@ -18,9 +18,6 @@
+ #define BPF_LOCAL_STORAGE_CACHE_SIZE  16
+-#define bpf_rcu_lock_held()                                                    \
+-      (rcu_read_lock_held() || rcu_read_lock_trace_held() ||                 \
+-       rcu_read_lock_bh_held())
+ struct bpf_local_storage_map_bucket {
+       struct hlist_head list;
+       raw_spinlock_t lock;
+--- a/kernel/bpf/hashtab.c
++++ b/kernel/bpf/hashtab.c
+@@ -681,8 +681,7 @@ static void *__htab_map_lookup_elem(stru
+       struct htab_elem *l;
+       u32 hash, key_size;
+-      WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() &&
+-                   !rcu_read_lock_bh_held());
++      WARN_ON_ONCE(!bpf_rcu_lock_held());
+       key_size = map->key_size;
+@@ -1117,8 +1116,7 @@ static long htab_map_update_elem(struct
+               /* unknown flags */
+               return -EINVAL;
+-      WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() &&
+-                   !rcu_read_lock_bh_held());
++      WARN_ON_ONCE(!bpf_rcu_lock_held());
+       key_size = map->key_size;
+@@ -1235,8 +1233,7 @@ static long htab_lru_map_update_elem(str
+               /* unknown flags */
+               return -EINVAL;
+-      WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() &&
+-                   !rcu_read_lock_bh_held());
++      WARN_ON_ONCE(!bpf_rcu_lock_held());
+       key_size = map->key_size;
+@@ -1304,8 +1301,7 @@ static long __htab_percpu_map_update_ele
+               /* unknown flags */
+               return -EINVAL;
+-      WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() &&
+-                   !rcu_read_lock_bh_held());
++      WARN_ON_ONCE(!bpf_rcu_lock_held());
+       key_size = map->key_size;
+@@ -1359,8 +1355,7 @@ static long __htab_lru_percpu_map_update
+               /* unknown flags */
+               return -EINVAL;
+-      WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() &&
+-                   !rcu_read_lock_bh_held());
++      WARN_ON_ONCE(!bpf_rcu_lock_held());
+       key_size = map->key_size;
+@@ -1437,8 +1432,7 @@ static long htab_map_delete_elem(struct
+       u32 hash, key_size;
+       int ret;
+-      WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() &&
+-                   !rcu_read_lock_bh_held());
++      WARN_ON_ONCE(!bpf_rcu_lock_held());
+       key_size = map->key_size;
+@@ -1473,8 +1467,7 @@ static long htab_lru_map_delete_elem(str
+       u32 hash, key_size;
+       int ret;
+-      WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() &&
+-                   !rcu_read_lock_bh_held());
++      WARN_ON_ONCE(!bpf_rcu_lock_held());
+       key_size = map->key_size;
+--- a/kernel/bpf/helpers.c
++++ b/kernel/bpf/helpers.c
+@@ -36,8 +36,7 @@
+  */
+ BPF_CALL_2(bpf_map_lookup_elem, struct bpf_map *, map, void *, key)
+ {
+-      WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() &&
+-                   !rcu_read_lock_bh_held());
++      WARN_ON_ONCE(!bpf_rcu_lock_held());
+       return (unsigned long) map->ops->map_lookup_elem(map, key);
+ }
+@@ -53,8 +52,7 @@ const struct bpf_func_proto bpf_map_look
+ BPF_CALL_4(bpf_map_update_elem, struct bpf_map *, map, void *, key,
+          void *, value, u64, flags)
+ {
+-      WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() &&
+-                   !rcu_read_lock_bh_held());
++      WARN_ON_ONCE(!bpf_rcu_lock_held());
+       return map->ops->map_update_elem(map, key, value, flags);
+ }
+@@ -71,8 +69,7 @@ const struct bpf_func_proto bpf_map_upda
+ BPF_CALL_2(bpf_map_delete_elem, struct bpf_map *, map, void *, key)
+ {
+-      WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() &&
+-                   !rcu_read_lock_bh_held());
++      WARN_ON_ONCE(!bpf_rcu_lock_held());
+       return map->ops->map_delete_elem(map, key);
+ }
+@@ -128,8 +125,7 @@ const struct bpf_func_proto bpf_map_peek
+ BPF_CALL_3(bpf_map_lookup_percpu_elem, struct bpf_map *, map, void *, key, u32, cpu)
+ {
+-      WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() &&
+-                   !rcu_read_lock_bh_held());
++      WARN_ON_ONCE(!bpf_rcu_lock_held());
+       return (unsigned long) map->ops->map_lookup_percpu_elem(map, key, cpu);
+ }
+--- a/kernel/bpf/lpm_trie.c
++++ b/kernel/bpf/lpm_trie.c
+@@ -353,8 +353,7 @@ static long trie_update_elem(struct bpf_
+        */
+       slot = &trie->root;
+-      while ((node = rcu_dereference_protected(*slot,
+-                                      lockdep_is_held(&trie->lock)))) {
++      while ((node = rcu_dereference_protected(*slot, 1))) {
+               matchlen = longest_prefix_match(trie, node, key);
+               if (node->prefixlen != matchlen ||
+@@ -475,8 +474,7 @@ static long trie_delete_elem(struct bpf_
+       trim = &trie->root;
+       trim2 = trim;
+       parent = NULL;
+-      while ((node = rcu_dereference_protected(
+-                     *trim, lockdep_is_held(&trie->lock)))) {
++      while ((node = rcu_dereference_protected(*trim, 1))) {
+               matchlen = longest_prefix_match(trie, node, key);
+               if (node->prefixlen != matchlen ||
diff --git a/queue-6.6/btrfs-check-and-set-extent_delalloc_new-before-clearing-extent_delalloc.patch b/queue-6.6/btrfs-check-and-set-extent_delalloc_new-before-clearing-extent_delalloc.patch
new file mode 100644 (file)
index 0000000..f22be06
--- /dev/null
@@ -0,0 +1,268 @@
+From stable+bounces-278182-greg=kroah.com@vger.kernel.org Mon Jul 20 20:08:54 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 14:08:44 -0400
+Subject: btrfs: check and set EXTENT_DELALLOC_NEW before clearing EXTENT_DELALLOC
+To: stable@vger.kernel.org
+Cc: Qu Wenruo <wqu@suse.com>, Filipe Manana <fdmanana@suse.com>, David Sterba <dsterba@suse.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260720180844.2922739-1-sashal@kernel.org>
+
+From: Qu Wenruo <wqu@suse.com>
+
+[ Upstream commit 95ee2231896d5f2a31760411429075a99d6045a7 ]
+
+[WARNING]
+When running test cases with injected errors or shutdown, e.g.
+generic/388 or generic/475, there is a chance that the following kernel
+warning is triggered:
+
+  BTRFS info (device dm-2): first mount of filesystem d8a19a28-3232-4809-b0df-38df83e71bff
+  BTRFS info (device dm-2): using crc32c checksum algorithm
+  BTRFS info (device dm-2): checking UUID tree
+  BTRFS info (device dm-2): turning on async discard
+  BTRFS info (device dm-2): enabling free space tree
+  BTRFS critical (device dm-2 state E): emergency shutdown
+  ------------[ cut here ]------------
+  WARNING: extent_io.c:1742 at extent_writepage_io+0x437/0x520 [btrfs], CPU#2: kworker/u43:2/651591
+  CPU: 2 UID: 0 PID: 651591 Comm: kworker/u43:2 Tainted: G        W  OE       7.0.0-rc6-custom+ #365 PREEMPT(full)  5804053f02137e627472d94b5128cc9fcb110e88
+  RIP: 0010:extent_writepage_io+0x437/0x520 [btrfs]
+  Call Trace:
+   <TASK>
+   extent_write_cache_pages+0x2a5/0x820 [btrfs 70299925d0856939e93b17d480651713b3cbba58]
+   btrfs_writepages+0x74/0x130 [btrfs 70299925d0856939e93b17d480651713b3cbba58]
+   do_writepages+0xd0/0x160
+   __writeback_single_inode+0x42/0x340
+   writeback_sb_inodes+0x22d/0x580
+   wb_writeback+0xc6/0x360
+   wb_workfn+0xbd/0x470
+   process_one_work+0x198/0x3b0
+   worker_thread+0x1c8/0x330
+   kthread+0xee/0x120
+   ret_from_fork+0x2a6/0x330
+   ret_from_fork_asm+0x11/0x20
+   </TASK>
+  ---[ end trace 0000000000000000 ]---
+  BTRFS error (device dm-2 state E): root 5 ino 259 folio 1323008 is marked dirty without notifying the fs
+  BTRFS error (device dm-2 state E): failed to submit blocks, root=5 inode=259 folio=1323008 submit_bitmap=0: -117
+  BTRFS info (device dm-2 state E): last unmount of filesystem d8a19a28-3232-4809-b0df-38df83e71bff
+
+[CAUSE]
+Inside btrfs we have the following pattern in several locations, for
+example inside btrfs_dirty_folio():
+
+       btrfs_clear_extent_bit(&inode->io_tree, start_pos, end_of_last_block,
+                              EXTENT_DELALLOC | EXTENT_DO_ACCOUNTING | EXTENT_DEFRAG,
+                              cached);
+
+       ret = btrfs_set_extent_delalloc(inode, start_pos, end_of_last_block,
+                                       extra_bits, cached);
+       if (ret)
+               return ret;
+
+However btrfs_set_extent_delalloc() can return IO errors other than -ENOMEM
+through the following callchain:
+
+ btrfs_set_extent_delalloc()
+ \- btrfs_find_new_delalloc_bytes()
+    \- btrfs_get_extent()
+       \- btrfs_lookup_file_extent()
+          \- btrfs_search_slot()
+
+When such IO error happened, the previous btrfs_clear_extent_bit() has
+cleared the EXTENT_DELALLOC for the range, and we're expecting
+btrfs_set_extent_delalloc() to re-set EXTENT_DELALLOC.
+
+But since btrfs_set_extent_delalloc() failed before
+btrfs_set_extent_bit(), EXTENT_DELALLOC flag is no longer present.
+
+And if the folio range is dirty before entering
+btrfs_set_extent_delalloc(), we got a dirty folio but no EXTENT_DELALLOC
+flag now.
+
+Then we hit the folio writeback:
+
+  extent_writepage()
+  |- writepage_delalloc()
+  |  No ordered extent is created, as there is no EXTENT_DELALLOC set
+  |  for the folio range.
+  |  This also means the folio has no ordered flag set.
+  |
+  |- extent_writepage_io()
+     \- if (unlikely(!folio_test_ordered(folio))
+        Now we hit the warning.
+
+[FIX]
+Introduce a new helper, btrfs_reset_extent_delalloc() to replace the
+currently open-coded btrfs_clear_extent_bit() +
+btrfs_set_extent_delalloc() combination.
+
+Instead of calling btrfs_clear_extent_bit() first, update
+EXTENT_DELALLOC_NEW first, as that part can fail due to metadata IO,
+meanwhile btrfs_clear_extent_bit() and btrfs_set_extent_bit() won't
+return any error but retry memory allocation until succeeded.
+
+This allows us to fail early without clearing EXTENT_DELALLOC bit, so
+even if that new btrfs_reset_extent_delalloc() failed before touching
+EXTENT_DELALLOC, the existing dirty range will still have their old
+EXTENT_DELALLOC flag present, thus avoid the warning.
+
+CC: stable@vger.kernel.org # 6.1+
+Reviewed-by: Filipe Manana <fdmanana@suse.com>
+Signed-off-by: Qu Wenruo <wqu@suse.com>
+Signed-off-by: David Sterba <dsterba@suse.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/btrfs/btrfs_inode.h |    2 +
+ fs/btrfs/file.c        |   12 +-------
+ fs/btrfs/inode.c       |   72 +++++++++++++++++++++++++++++++++++--------------
+ fs/btrfs/reflink.c     |    5 ---
+ 4 files changed, 57 insertions(+), 34 deletions(-)
+
+--- a/fs/btrfs/btrfs_inode.h
++++ b/fs/btrfs/btrfs_inode.h
+@@ -441,6 +441,8 @@ int btrfs_start_delalloc_roots(struct bt
+ int btrfs_set_extent_delalloc(struct btrfs_inode *inode, u64 start, u64 end,
+                             unsigned int extra_bits,
+                             struct extent_state **cached_state);
++int btrfs_reset_extent_delalloc(struct btrfs_inode *inode, u64 start, u64 end,
++                              unsigned int extra_bits, struct extent_state **cached_state);
+ struct btrfs_new_inode_args {
+       /* Input */
+--- a/fs/btrfs/file.c
++++ b/fs/btrfs/file.c
+@@ -151,16 +151,8 @@ int btrfs_dirty_pages(struct btrfs_inode
+       end_of_last_block = start_pos + num_bytes - 1;
+-      /*
+-       * The pages may have already been dirty, clear out old accounting so
+-       * we can set things up properly
+-       */
+-      clear_extent_bit(&inode->io_tree, start_pos, end_of_last_block,
+-                       EXTENT_DELALLOC | EXTENT_DO_ACCOUNTING | EXTENT_DEFRAG,
+-                       cached);
+-
+-      err = btrfs_set_extent_delalloc(inode, start_pos, end_of_last_block,
+-                                      extra_bits, cached);
++      err = btrfs_reset_extent_delalloc(inode, start_pos, end_of_last_block,
++                                        extra_bits, cached);
+       if (err)
+               return err;
+--- a/fs/btrfs/inode.c
++++ b/fs/btrfs/inode.c
+@@ -2666,7 +2666,11 @@ int btrfs_set_extent_delalloc(struct btr
+                             unsigned int extra_bits,
+                             struct extent_state **cached_state)
+ {
+-      WARN_ON(PAGE_ALIGNED(end));
++      const u32 blocksize = inode->root->fs_info->sectorsize;
++
++      /* Basic alignment check. */
++      ASSERT(IS_ALIGNED(start, blocksize));
++      ASSERT(IS_ALIGNED(end + 1, blocksize));
+       if (start >= i_size_read(&inode->vfs_inode) &&
+           !(inode->flags & BTRFS_INODE_PREALLOC)) {
+@@ -2872,6 +2876,50 @@ int btrfs_writepage_cow_fixup(struct pag
+       return -EAGAIN;
+ }
++/*
++ * Clear the old accounting flags and set EXTENT_DELALLOC for the range.
++ *
++ * Return <0 for error, in that case no range has EXTENT_DELALLOC bit cleared or set.
++ */
++int btrfs_reset_extent_delalloc(struct btrfs_inode *inode, u64 start, u64 end,
++                              unsigned int extra_bits, struct extent_state **cached_state)
++{
++      const u32 blocksize = inode->root->fs_info->sectorsize;
++
++      /* The @extra_bits can only be EXTENT_NORESERVE for now. */
++      ASSERT(!(extra_bits & ~EXTENT_NORESERVE));
++
++      /* Basic alignment check. */
++      ASSERT(IS_ALIGNED(start, blocksize));
++      ASSERT(IS_ALIGNED(end + 1, blocksize));
++
++      /*
++       * Check and set DELALLOC_NEW flag, this needs to search tree thus can
++       * fail early.  Thus we want to do this before clearing EXTENT_DELALLOC.
++       */
++      if (start >= i_size_read(&inode->vfs_inode) &&
++          !(inode->flags & BTRFS_INODE_PREALLOC)) {
++              /*
++               * There can't be any extents following EOF in this case so just
++               * set the delalloc new bit for the range directly.
++               */
++              extra_bits |= EXTENT_DELALLOC_NEW;
++      } else {
++              int ret;
++
++              ret = btrfs_find_new_delalloc_bytes(inode, start, end + 1 - start,
++                                                  NULL);
++              if (unlikely(ret))
++                      return ret;
++      }
++      /* Clear the old accounting as the range may already be dirty. */
++      clear_extent_bit(&inode->io_tree, start, end,
++                       EXTENT_DELALLOC | EXTENT_DO_ACCOUNTING |
++                       EXTENT_DEFRAG, cached_state);
++      return set_extent_bit(&inode->io_tree, start, end,
++                            EXTENT_DELALLOC | extra_bits, cached_state);
++}
++
+ static int insert_reserved_file_extent(struct btrfs_trans_handle *trans,
+                                      struct btrfs_inode *inode, u64 file_pos,
+                                      struct btrfs_file_extent_item *stack_fi,
+@@ -4799,12 +4847,7 @@ again:
+               goto again;
+       }
+-      clear_extent_bit(&inode->io_tree, block_start, block_end,
+-                       EXTENT_DELALLOC | EXTENT_DO_ACCOUNTING | EXTENT_DEFRAG,
+-                       &cached_state);
+-
+-      ret = btrfs_set_extent_delalloc(inode, block_start, block_end, 0,
+-                                      &cached_state);
++      ret = btrfs_reset_extent_delalloc(inode, block_start, block_end, 0, &cached_state);
+       if (ret) {
+               unlock_extent(io_tree, block_start, block_end, &cached_state);
+               goto out_unlock;
+@@ -8278,19 +8321,8 @@ again:
+               }
+       }
+-      /*
+-       * page_mkwrite gets called when the page is firstly dirtied after it's
+-       * faulted in, but write(2) could also dirty a page and set delalloc
+-       * bits, thus in this case for space account reason, we still need to
+-       * clear any delalloc bits within this page range since we have to
+-       * reserve data&meta space before lock_page() (see above comments).
+-       */
+-      clear_extent_bit(&BTRFS_I(inode)->io_tree, page_start, end,
+-                        EXTENT_DELALLOC | EXTENT_DO_ACCOUNTING |
+-                        EXTENT_DEFRAG, &cached_state);
+-
+-      ret2 = btrfs_set_extent_delalloc(BTRFS_I(inode), page_start, end, 0,
+-                                      &cached_state);
++      ret2 = btrfs_reset_extent_delalloc(BTRFS_I(inode), page_start, end, 0,
++                                         &cached_state);
+       if (ret2) {
+               unlock_extent(io_tree, page_start, page_end, &cached_state);
+               ret = VM_FAULT_SIGBUS;
+--- a/fs/btrfs/reflink.c
++++ b/fs/btrfs/reflink.c
+@@ -95,10 +95,7 @@ static int copy_inline_to_page(struct bt
+       if (ret < 0)
+               goto out_unlock;
+-      clear_extent_bit(&inode->io_tree, file_offset, range_end,
+-                       EXTENT_DELALLOC | EXTENT_DO_ACCOUNTING | EXTENT_DEFRAG,
+-                       NULL);
+-      ret = btrfs_set_extent_delalloc(inode, file_offset, range_end, 0, NULL);
++      ret = btrfs_reset_extent_delalloc(inode, file_offset, range_end, 0, NULL);
+       if (ret)
+               goto out_unlock;
diff --git a/queue-6.6/btrfs-fix-false-io-failure-after-falling-back-to-buffered-write.patch b/queue-6.6/btrfs-fix-false-io-failure-after-falling-back-to-buffered-write.patch
new file mode 100644 (file)
index 0000000..3ae231e
--- /dev/null
@@ -0,0 +1,186 @@
+From stable+bounces-278150-greg=kroah.com@vger.kernel.org Mon Jul 20 18:57:02 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 12:51:20 -0400
+Subject: btrfs: fix false IO failure after falling back to buffered write
+To: stable@vger.kernel.org
+Cc: Qu Wenruo <wqu@suse.com>, Boris Burkov <boris@bur.io>, David Sterba <dsterba@suse.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260720165120.2527510-1-sashal@kernel.org>
+
+From: Qu Wenruo <wqu@suse.com>
+
+[ Upstream commit 66ff4d366e7eb4d31813d2acabf3af512ce03aa5 ]
+
+[BUG]
+The test case generic/362 will fail with "nodatasum" mount option (*):
+
+ MOUNT_OPTIONS -- -o nodatasum /dev/mapper/test-scratch1 /mnt/scratch
+
+# generic/362  0s ... - output mismatch (see /home/adam/xfstests/results//generic/362.out.bad)
+#    --- tests/generic/362.out 2024-08-24 15:31:37.200000000 +0930
+#    +++ /home/adam/xfstests/results//generic/362.out.bad      2026-05-27 10:21:17.574771567 +0930
+#    @@ -1,2 +1,3 @@
+#     QA output created by 362
+#    +First write failed: Input/output error
+#     Silence is golden
+#    ...
+
+*: If the test case has been executed before with default data checksum,
+the failure will not reproduce. Need the following fix to make it
+reliably reproducible:
+https://lore.kernel.org/linux-btrfs/20260528111659.87113-1-wqu@suse.com/
+
+[CAUSE]
+Inside __iomap_dio_rw(), the -EFAULT/-ENOTBLK error is not directly returned.
+Thus we never got an error pointer from __iomap_dio_rw().
+
+The call chain looks like this:
+
+ btrfs_direct_write()
+ |- btrfs_dio_write()
+ |-  __iomap_dio_rw()
+ |  |- iomap_iter()
+ |  |  |- btrfs_dio_iomap_begin()
+ |  |     Now an ordered extent is allocated for the 4K write.
+ |  |
+ |  |- iomi.status = iomap_dio_iter()
+ |  |  Where iomap_dio_iter() returned -EFAULT.
+ |  |
+ |  |- ret = iomap_iter()
+ |  |  |- btrfs_dio_iomap_end()
+ |  |  |  |- btrfs_finish_ordered_extent(uptodate = false)
+ |  |  |  |  |- can_finish_ordered_extent()
+ |  |  |  |     |- btrfs_mark_ordered_extent_error()
+ |  |  |  |        |- mapping_set_error()
+ |  |  |  |           Now the address space is marked error.
+ |  |  |  | return -ENOTBLK
+ |  |  |- return -ENOTBLK
+ |  |- if (ret == -ENOTBLK) { ret = 0; }
+ |     Now the return value is reset to 0.
+ |     Thus no error pointer will be returned.
+ |
+ |- ret = iomap_dio_complete()
+ |  Since no byte is submitted, @ret is 0.
+ |
+ |- Fallback to buffered IO
+ |  And the buffered write finished without error
+ |
+ |- filemap_fdatawait_range()
+    |- filemap_check_errors()
+       The previous error is recorded, thus an error is returned
+
+However the buffered write is properly submitted and finished, the error
+is from the btrfs_finish_ordered_extent() call with @uptodate = false.
+
+[FIX]
+When a short dio write happened, any range that is submitted will have
+btrfs_extract_ordered_extent() to be called, thus the submitted range
+will always have an OE just covering the submitted range.
+
+The remaining OE range is never submitted, thus they should be treated
+as truncated, not an error. So that we can properly reclaim and not
+insert an unnecessary file extent item, without marking the mapping as
+error.
+
+Extract a helper, btrfs_mark_ordered_extent_truncated(), and utilize
+that helper to mark the direct IO ordered extent as truncated, so it
+won't cause failure for the later buffered fallback.
+
+[REASON FOR NO FIXES TAG]
+The bug itself is pretty old, at commit f85781fb505e ("btrfs: switch to
+iomap for direct IO") we're already passing @uptodate=false finishing
+the OE.
+But at that time OE with IOERR won't call mapping_set_error(), so it's
+not exposed.
+Later commit d61bec08b904 ("btrfs: mark ordered extent and inode with
+error if we fail to finish") finally exposed the bug, but that commit
+is doing a correct job, not the root cause.
+
+Anyway the bug is very old, dating back to 5.1x days, thus only CC to
+stable.
+
+CC: stable@vger.kernel.org # 5.15+
+Reviewed-by: Boris Burkov <boris@bur.io>
+Signed-off-by: Qu Wenruo <wqu@suse.com>
+Signed-off-by: David Sterba <dsterba@suse.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/btrfs/inode.c        |   23 +++++++++++++++--------
+ fs/btrfs/ordered-data.c |   12 ++++++++++++
+ fs/btrfs/ordered-data.h |    2 ++
+ 3 files changed, 29 insertions(+), 8 deletions(-)
+
+--- a/fs/btrfs/inode.c
++++ b/fs/btrfs/inode.c
+@@ -7797,12 +7797,23 @@ static int btrfs_dio_iomap_end(struct in
+       if (submitted < length) {
+               pos += submitted;
+               length -= submitted;
+-              if (write)
++              if (write) {
++                      /*
++                       * We have a short write, if there is any range
++                       * that is submitted properly, that part will have
++                       * its own OE split from the original one.
++                       *
++                       * So for the OE at dio_data->ordered, it's the part
++                       * that is not submitted, and should be marked
++                       * as fully truncated.
++                       */
++                      btrfs_mark_ordered_extent_truncated(dio_data->ordered, 0);
+                       btrfs_finish_ordered_extent(dio_data->ordered, NULL,
+-                                                  pos, length, false);
+-              else
++                                                  pos, length, true);
++              } else {
+                       unlock_extent(&BTRFS_I(inode)->io_tree, pos,
+                                     pos + length - 1, NULL);
++              }
+               ret = -ENOTBLK;
+       }
+       if (write) {
+@@ -8152,11 +8163,7 @@ static void btrfs_invalidate_folio(struc
+                                        EXTENT_LOCKED | EXTENT_DO_ACCOUNTING |
+                                        EXTENT_DEFRAG, &cached_state);
+-              spin_lock_irq(&inode->ordered_tree.lock);
+-              set_bit(BTRFS_ORDERED_TRUNCATED, &ordered->flags);
+-              ordered->truncated_len = min(ordered->truncated_len,
+-                                           cur - ordered->file_offset);
+-              spin_unlock_irq(&inode->ordered_tree.lock);
++              btrfs_mark_ordered_extent_truncated(ordered, cur - ordered->file_offset);
+               /*
+                * If the ordered extent has finished, we're safe to delete all
+--- a/fs/btrfs/ordered-data.c
++++ b/fs/btrfs/ordered-data.c
+@@ -303,6 +303,18 @@ void btrfs_add_ordered_sum(struct btrfs_
+       spin_unlock_irq(&tree->lock);
+ }
++void btrfs_mark_ordered_extent_truncated(struct btrfs_ordered_extent *ordered,
++                                       u64 truncate_len)
++{
++      struct btrfs_inode *inode = BTRFS_I(ordered->inode);
++
++      ASSERT(truncate_len <= ordered->num_bytes);
++      spin_lock_irq(&inode->ordered_tree.lock);
++      set_bit(BTRFS_ORDERED_TRUNCATED, &ordered->flags);
++      ordered->truncated_len = min(ordered->truncated_len, truncate_len);
++      spin_unlock_irq(&inode->ordered_tree.lock);
++}
++
+ static void finish_ordered_fn(struct btrfs_work *work)
+ {
+       struct btrfs_ordered_extent *ordered_extent;
+--- a/fs/btrfs/ordered-data.h
++++ b/fs/btrfs/ordered-data.h
+@@ -210,6 +210,8 @@ bool btrfs_try_lock_ordered_range(struct
+                                 struct extent_state **cached_state);
+ struct btrfs_ordered_extent *btrfs_split_ordered_extent(
+                       struct btrfs_ordered_extent *ordered, u64 len);
++void btrfs_mark_ordered_extent_truncated(struct btrfs_ordered_extent *ordered,
++                                       u64 truncate_len);
+ int __init ordered_data_init(void);
+ void __cold ordered_data_exit(void);
diff --git a/queue-6.6/btrfs-fix-incorrect-buffered-io-fallback-for-append-direct-writes.patch b/queue-6.6/btrfs-fix-incorrect-buffered-io-fallback-for-append-direct-writes.patch
new file mode 100644 (file)
index 0000000..64ca7c9
--- /dev/null
@@ -0,0 +1,200 @@
+From stable+bounces-278184-greg=kroah.com@vger.kernel.org Mon Jul 20 20:08:58 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 14:08:48 -0400
+Subject: btrfs: fix incorrect buffered IO fallback for append direct writes
+To: stable@vger.kernel.org
+Cc: Qu Wenruo <wqu@suse.com>, Boris Burkov <boris@bur.io>, David Sterba <dsterba@suse.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260720180848.2923096-2-sashal@kernel.org>
+
+From: Qu Wenruo <wqu@suse.com>
+
+[ Upstream commit ff66fe6662330226b3f486014c375538d91c44aa ]
+
+[BUG]
+With the previous bug of short direct writes fixed, test case
+generic/362 (*) still fails with the following error with nodatasum
+mount option:
+
+# generic/362  0s ... - output mismatch (see /home/adam/xfstests/results//generic/362.out.bad)
+# - output mismatch (see /home/adam/xfstests/results//generic/362.out.bad)
+#    --- tests/generic/362.out 2024-08-24 15:31:37.200000000 +0930
+#    +++ /home/adam/xfstests/results//generic/362.out.bad      2026-05-27 10:13:09.072485767 +0930
+#    @@ -1,2 +1,3 @@
+#     QA output created by 362
+#    +Wrong file size after first write, got 8192 expected 4096
+#     Silence is golden
+#    ...
+
+*: If the test case has been executed before with default data checksum,
+the failure will not reproduce. Need the following fix to make it
+reliably reproducible:
+https://lore.kernel.org/linux-btrfs/20260528111659.87113-1-wqu@suse.com/
+
+[CAUSE]
+Inside btrfs_dio_iomap_begin() for a direct write, we increase the isize
+if it's beyond the current isize.
+
+But if the direct io finished short, we do not revert the isize to the
+previous value nor to the short write end.
+
+Then if we need to fall back to buffered writes, and the write has
+IOCB_APPEND flag, then the buffered write will be positioned at the
+incorrect isize.
+
+The call chain looks like this:
+
+ btrfs_direct_write(pos=0, length=4K)
+ |- __iomap_dio_rw()
+ |  |- iomap_iter()
+ |  |  |- btrfs_dio_iomap_begin()
+ |  |     |- btrfs_get_blocks_direct_write()
+ |  |        |- i_size_write()
+ |  |           Which updates the isize to the write end (4K).
+ |  |
+ |  |- iomap_dio_iter()
+ |  |  Failed with -EFAULT on the first page.
+ |  |
+ |  |- iomap_iter()
+ |  |  |- btrfs_dio_iomap_end()
+ |  |     Detects a short write, return -ENOTBLK
+ |  |- if (ret == -ENOTBLK) { ret = 0;}
+ |     Which resets the return value.
+ |
+ |- ret = iomap_dio_complet()
+ |  Which returns 0.
+ |
+ |- btrfs_buffered_write(iocb, from);
+    |- generic_write_checks()
+       |- iocb->ki_pos = i_size_read()
+          Which is still the new size (4K), other than the original
+         isize 0.
+
+[FIX]
+Introduce the following btrfs_dio_data members:
+
+- old_isize
+
+- updated_isize
+  If the direct write has enlarged the isize.
+
+Then if we got a short write, and btrfs_dio_data::updated_isize is set,
+revert to the correct isize based on old_isize and current file
+position.
+
+And here we call i_size_write() without holding an extent lock, which is
+a very special case that we're safe to do:
+
+ - Only a single writer can be enlarging isize
+   Enlarging isize will take the exclusive inode lock.
+
+ - Buffered readers need to wait for the OE we're holding
+   Buffered readers will lock extent and wait for OE of the folio range.
+   Sometimes we can skip the OE wait, but since all page cache is
+   invalidated, the OE wait can not be skipped.
+
+But I do not think this is the most elegant solution, nor covers all
+cases. E.g. if the bio is submitted but IO failed, we are unable to do
+the revert.
+
+I believe the more elegant one would be extend the EXTENT_DIO_LOCKED
+lifespan for direct writes, so that we can update the isize when a
+write beyond EOF finished successfully.
+
+However that change is too huge for a small bug fix.
+So only implement the minimal partial fix for now.
+
+[REASON FOR NO FIXES TAG]
+The bug is again very old, before commit f85781fb505e ("btrfs: switch to
+iomap for direct IO") we are already increasing isize without a
+proper rollback for short writes.
+
+Thus only a CC to stable.
+
+CC: stable@vger.kernel.org # 5.15+
+Reviewed-by: Boris Burkov <boris@bur.io>
+Signed-off-by: Qu Wenruo <wqu@suse.com>
+Signed-off-by: David Sterba <dsterba@suse.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/btrfs/inode.c |   43 ++++++++++++++++++++++++++++++++++++++++++-
+ 1 file changed, 42 insertions(+), 1 deletion(-)
+
+--- a/fs/btrfs/inode.c
++++ b/fs/btrfs/inode.c
+@@ -80,10 +80,12 @@ struct btrfs_iget_args {
+ struct btrfs_dio_data {
+       ssize_t submitted;
++      loff_t old_isize;
+       struct extent_changeset *data_reserved;
+       struct btrfs_ordered_extent *ordered;
+       bool data_space_reserved;
+       bool nocow_done;
++      bool updated_isize;
+ };
+ struct btrfs_dio_private {
+@@ -7405,6 +7407,7 @@ static int btrfs_get_blocks_direct_write
+       bool space_reserved = false;
+       u64 len = *lenp;
+       u64 prev_len;
++      loff_t old_isize;
+       int ret = 0;
+       /*
+@@ -7520,8 +7523,14 @@ static int btrfs_get_blocks_direct_write
+        * Need to update the i_size under the extent lock so buffered
+        * readers will get the updated i_size when we unlock.
+        */
+-      if (start + len > i_size_read(inode))
++      old_isize = i_size_read(inode);
++      if (start + len > old_isize) {
++              if (!dio_data->updated_isize) {
++                      dio_data->old_isize = old_isize;
++                      dio_data->updated_isize = true;
++              }
+               i_size_write(inode, start + len);
++      }
+ out:
+       if (ret && space_reserved) {
+               btrfs_delalloc_release_extents(BTRFS_I(inode), len);
+@@ -7799,6 +7808,38 @@ static int btrfs_dio_iomap_end(struct in
+               length -= submitted;
+               if (write) {
+                       /*
++                       * Got a short write and have updated the isize, need to
++                       * revert the isize change.
++                       *
++                       * Normally we need to update isize with extent lock hold,
++                       * but we're safe due to the following factors:
++                       *
++                       * - Only a single writer can be enlarging isize
++                       *   Enlarging isize will take the exclusive inode lock.
++                       *
++                       * - Buffered readers need to wait for the OE we're holding
++                       *   Buffered readers will lock extent and wait for OE
++                       *   of the folio range, and since page cache is invalidated
++                       *   the OE wait can not be skipped.
++                       *
++                       * So here we are safe to revert the isize before
++                       * finishing the OE, and no reader of the remaining range
++                       * can see the enlarged size.
++                       *
++                       * TODO: Extend the DIO_LOCKED lifespan for direct writes,
++                       * and only enlarge isize after a successful write.
++                       */
++                      if (dio_data->updated_isize) {
++                              u64 new_isize;
++
++                              if (submitted == 0)
++                                      new_isize = dio_data->old_isize;
++                              else
++                                      new_isize = max(dio_data->old_isize, pos);
++                              i_size_write(inode, new_isize);
++                              dio_data->updated_isize = false;
++                      }
++                      /*
+                        * We have a short write, if there is any range
+                        * that is submitted properly, that part will have
+                        * its own OE split from the original one.
diff --git a/queue-6.6/coresight-etb10-restore-atomic_t-for-shared-reading-state.patch b/queue-6.6/coresight-etb10-restore-atomic_t-for-shared-reading-state.patch
new file mode 100644 (file)
index 0000000..d0b7d06
--- /dev/null
@@ -0,0 +1,123 @@
+From stable+bounces-274917-greg=kroah.com@vger.kernel.org Wed Jul 15 13:41:02 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 07:40:54 -0400
+Subject: coresight: etb10: restore atomic_t for shared reading state
+To: stable@vger.kernel.org
+Cc: Runyu Xiao <runyu.xiao@seu.edu.cn>, James Clark <james.clark@linaro.org>, Suzuki K Poulose <suzuki.poulose@arm.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715114054.728724-1-sashal@kernel.org>
+
+From: Runyu Xiao <runyu.xiao@seu.edu.cn>
+
+[ Upstream commit fa09f08ede3db3050ae16ae1ed92c902d0cada23 ]
+
+The etb10 miscdevice uses drvdata->reading as a shared exclusivity gate
+for userspace buffer access. etb_open() claims that gate with
+local_cmpxchg(), and etb_release() clears it with local_set().
+
+That gate is shared per-device state rather than CPU-local state. A
+running system can reach it whenever /dev/<etb> is opened, closed, and
+reopened by different tasks while the device remains registered, so the
+same drvdata->reading variable may be claimed on one CPU and later
+cleared on another.
+
+This code used to use atomic_t for the same gate, but commit
+27b10da8fff2 ("coresight: etb10: moving to local atomic operations")
+changed it to local_t even though the access pattern remained cross-task
+and cross-CPU. Restore atomic_t together with atomic_cmpxchg() and
+atomic_set() so the exclusivity gate again uses a primitive intended
+for shared state.
+
+The issue was found on Linux v6.18.21 by our static analysis tool while
+scanning surviving local_t-on-shared-state sites, and then manually
+reviewed against the live etb10 file-op path.
+
+It was runtime-validated with a reproducible QEMU no-device KCSAN PoC
+that kept the same report-local contract:
+
+  1. use one shared struct etb_drvdata carrier and its
+     drvdata->reading gate;
+  2. call etb_open() and etb_release() sequentially on that gate to
+     confirm the original claim/clear path;
+  3. bind the open side to CPU0 and the release side to CPU1 for the
+     same gate to show cross-CPU ownership;
+  4. run bound workers that repeatedly race etb_open() and
+     etb_release() on the same gate until KCSAN reports a target hit.
+
+The harness recorded:
+
+  L1 passed open=1 release=1
+  reading_after_open=1 reading_after_release=0
+  L2 passed open_cpu=0 release_cpu=1
+  cross_cpu_release=1 reading_after=0 open_ret=0
+
+Representative KCSAN excerpt from the no-device validation run:
+
+  BUG: KCSAN: data-race in etb_open.constprop.0.isra.0 [vuln_msv]
+
+  write to 0xffffffffc0003810 of 4 bytes by task 216 on cpu 1:
+   etb_open.constprop.0.isra.0+0x38/0x80 [vuln_msv]
+   l3_worker_thread_fn+0x4f/0xf0 [vuln_msv]
+   kthread+0x17e/0x1c0
+   ret_from_fork+0x22/0x30
+
+  read to 0xffffffffc0003810 of 4 bytes by task 215 on cpu 0:
+   etb_open.constprop.0.isra.0+0x18/0x80 [vuln_msv]
+   l3_worker_thread_fn+0x4f/0xf0 [vuln_msv]
+   kthread+0x17e/0x1c0
+   ret_from_fork+0x22/0x30
+
+  value changed: 0x00000000 -> 0x00000001
+
+  Reported by Kernel Concurrency Sanitizer on:
+  CPU: 0 PID: 215 Comm: etb10_l3_a Tainted: G           O       6.1.66 #2
+
+This no-device harness is not a real ETB10 hardware end-to-end run, but
+it preserves the same shared drvdata->reading gate and the same
+etb_open()/etb_release() claim/clear contract. No real ETB10 hardware
+was available for runtime testing.
+
+Build-tested with:
+  make olddefconfig
+  make -j"$(nproc)" drivers/hwtracing/coresight/coresight-etb10.o
+
+Fixes: 27b10da8fff2 ("coresight: etb10: moving to local atomic operations")
+Cc: stable@vger.kernel.org
+Signed-off-by: Runyu Xiao <runyu.xiao@seu.edu.cn>
+Reviewed-by: James Clark <james.clark@linaro.org>
+Signed-off-by: Suzuki K Poulose <suzuki.poulose@arm.com>
+Link: https://lore.kernel.org/r/20260528165201.319452-1-runyu.xiao@seu.edu.cn
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/hwtracing/coresight/coresight-etb10.c |    6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+--- a/drivers/hwtracing/coresight/coresight-etb10.c
++++ b/drivers/hwtracing/coresight/coresight-etb10.c
+@@ -86,7 +86,7 @@ struct etb_drvdata {
+       struct coresight_device *csdev;
+       struct miscdevice       miscdev;
+       spinlock_t              spinlock;
+-      local_t                 reading;
++      atomic_t                reading;
+       pid_t                   pid;
+       u8                      *buf;
+       u32                     mode;
+@@ -604,7 +604,7 @@ static int etb_open(struct inode *inode,
+       struct etb_drvdata *drvdata = container_of(file->private_data,
+                                                  struct etb_drvdata, miscdev);
+-      if (local_cmpxchg(&drvdata->reading, 0, 1))
++      if (atomic_cmpxchg(&drvdata->reading, 0, 1))
+               return -EBUSY;
+       dev_dbg(&drvdata->csdev->dev, "%s: successfully opened\n", __func__);
+@@ -642,7 +642,7 @@ static int etb_release(struct inode *ino
+ {
+       struct etb_drvdata *drvdata = container_of(file->private_data,
+                                                  struct etb_drvdata, miscdev);
+-      local_set(&drvdata->reading, 0);
++      atomic_set(&drvdata->reading, 0);
+       dev_dbg(&drvdata->csdev->dev, "%s: released\n", __func__);
+       return 0;
diff --git a/queue-6.6/cpufreq-make-cpufreq_driver-exit-return-void.patch b/queue-6.6/cpufreq-make-cpufreq_driver-exit-return-void.patch
new file mode 100644 (file)
index 0000000..881db2a
--- /dev/null
@@ -0,0 +1,619 @@
+From stable+bounces-276791-greg=kroah.com@vger.kernel.org Thu Jul 16 19:51:29 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Jul 2026 13:45:54 -0400
+Subject: cpufreq: Make cpufreq_driver->exit() return void
+To: stable@vger.kernel.org
+Cc: Lizhe <sensor1010@163.com>, Viresh Kumar <viresh.kumar@linaro.org>, AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>, Sudeep Holla <sudeep.holla@arm.com>, Mario Limonciello <mario.limonciello@amd.com>, Florian Fainelli <florian.fainelli@broadcom.com>, "Rafael J. Wysocki" <rafael@kernel.org>, Kevin Hilman <khilman@baylibre.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260716174555.787913-2-sashal@kernel.org>
+
+From: Lizhe <sensor1010@163.com>
+
+[ Upstream commit b4b1ddc9dfe997a5f492fa3a36487f8e7a5de30d ]
+
+The cpufreq core doesn't check the return type of the exit() callback
+and there is not much the core can do on failures at that point. Just
+drop the returned value and make it return void.
+
+Signed-off-by: Lizhe <sensor1010@163.com>
+[ Viresh: Reworked the patches to fix all missing changes together. ]
+Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
+Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com> # Mediatek
+Acked-by: Sudeep Holla <sudeep.holla@arm.com> # scpi, scmi, vexpress
+Acked-by: Mario Limonciello <mario.limonciello@amd.com> # amd
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com> # bmips
+Acked-by: Rafael J. Wysocki <rafael@kernel.org>
+Acked-by: Kevin Hilman <khilman@baylibre.com> # omap
+Stable-dep-of: bcb8889c4981 ("cpufreq: qcom-cpufreq-hw: Fix possible double free")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/cpufreq/acpi-cpufreq.c         |    4 +---
+ drivers/cpufreq/amd-pstate.c           |    7 ++-----
+ drivers/cpufreq/apple-soc-cpufreq.c    |    4 +---
+ drivers/cpufreq/bmips-cpufreq.c        |    4 +---
+ drivers/cpufreq/cppc_cpufreq.c         |    3 +--
+ drivers/cpufreq/cpufreq-dt.c           |    3 +--
+ drivers/cpufreq/e_powersaver.c         |    3 +--
+ drivers/cpufreq/intel_pstate.c         |    8 +++-----
+ drivers/cpufreq/mediatek-cpufreq-hw.c  |    4 +---
+ drivers/cpufreq/mediatek-cpufreq.c     |    4 +---
+ drivers/cpufreq/omap-cpufreq.c         |    3 +--
+ drivers/cpufreq/pasemi-cpufreq.c       |    6 ++----
+ drivers/cpufreq/powernow-k6.c          |    5 ++---
+ drivers/cpufreq/powernow-k7.c          |    3 +--
+ drivers/cpufreq/powernow-k8.c          |    6 ++----
+ drivers/cpufreq/powernv-cpufreq.c      |    4 +---
+ drivers/cpufreq/ppc_cbe_cpufreq.c      |    3 +--
+ drivers/cpufreq/qcom-cpufreq-hw.c      |    4 +---
+ drivers/cpufreq/qoriq-cpufreq.c        |    4 +---
+ drivers/cpufreq/scmi-cpufreq.c         |    4 +---
+ drivers/cpufreq/scpi-cpufreq.c         |    4 +---
+ drivers/cpufreq/sh-cpufreq.c           |    4 +---
+ drivers/cpufreq/sparc-us2e-cpufreq.c   |    3 +--
+ drivers/cpufreq/sparc-us3-cpufreq.c    |    3 +--
+ drivers/cpufreq/speedstep-centrino.c   |   10 +++-------
+ drivers/cpufreq/tegra194-cpufreq.c     |    4 +---
+ drivers/cpufreq/vexpress-spc-cpufreq.c |    5 ++---
+ include/linux/cpufreq.h                |    2 +-
+ 28 files changed, 37 insertions(+), 84 deletions(-)
+
+--- a/drivers/cpufreq/acpi-cpufreq.c
++++ b/drivers/cpufreq/acpi-cpufreq.c
+@@ -926,7 +926,7 @@ err_free:
+       return result;
+ }
+-static int acpi_cpufreq_cpu_exit(struct cpufreq_policy *policy)
++static void acpi_cpufreq_cpu_exit(struct cpufreq_policy *policy)
+ {
+       struct acpi_cpufreq_data *data = policy->driver_data;
+@@ -939,8 +939,6 @@ static int acpi_cpufreq_cpu_exit(struct
+       free_cpumask_var(data->freqdomain_cpus);
+       kfree(policy->freq_table);
+       kfree(data);
+-
+-      return 0;
+ }
+ static int acpi_cpufreq_resume(struct cpufreq_policy *policy)
+--- a/drivers/cpufreq/amd-pstate.c
++++ b/drivers/cpufreq/amd-pstate.c
+@@ -900,7 +900,7 @@ free_cpudata1:
+       return ret;
+ }
+-static int amd_pstate_cpu_exit(struct cpufreq_policy *policy)
++static void amd_pstate_cpu_exit(struct cpufreq_policy *policy)
+ {
+       struct amd_cpudata *cpudata = policy->driver_data;
+@@ -908,8 +908,6 @@ static int amd_pstate_cpu_exit(struct cp
+       freq_qos_remove_request(&cpudata->req[0]);
+       policy->fast_switch_possible = false;
+       kfree(cpudata);
+-
+-      return 0;
+ }
+ static int amd_pstate_cpu_resume(struct cpufreq_policy *policy)
+@@ -1353,7 +1351,7 @@ free_cpudata1:
+       return ret;
+ }
+-static int amd_pstate_epp_cpu_exit(struct cpufreq_policy *policy)
++static void amd_pstate_epp_cpu_exit(struct cpufreq_policy *policy)
+ {
+       struct amd_cpudata *cpudata = policy->driver_data;
+@@ -1363,7 +1361,6 @@ static int amd_pstate_epp_cpu_exit(struc
+       }
+       pr_debug("CPU %d exiting\n", policy->cpu);
+-      return 0;
+ }
+ static void amd_pstate_epp_update_limit(struct cpufreq_policy *policy)
+--- a/drivers/cpufreq/apple-soc-cpufreq.c
++++ b/drivers/cpufreq/apple-soc-cpufreq.c
+@@ -311,7 +311,7 @@ out_iounmap:
+       return ret;
+ }
+-static int apple_soc_cpufreq_exit(struct cpufreq_policy *policy)
++static void apple_soc_cpufreq_exit(struct cpufreq_policy *policy)
+ {
+       struct apple_cpu_priv *priv = policy->driver_data;
+@@ -319,8 +319,6 @@ static int apple_soc_cpufreq_exit(struct
+       dev_pm_opp_remove_all_dynamic(priv->cpu_dev);
+       iounmap(priv->reg_base);
+       kfree(priv);
+-
+-      return 0;
+ }
+ static struct cpufreq_driver apple_soc_cpufreq_driver = {
+--- a/drivers/cpufreq/bmips-cpufreq.c
++++ b/drivers/cpufreq/bmips-cpufreq.c
+@@ -121,11 +121,9 @@ static int bmips_cpufreq_target_index(st
+       return 0;
+ }
+-static int bmips_cpufreq_exit(struct cpufreq_policy *policy)
++static void bmips_cpufreq_exit(struct cpufreq_policy *policy)
+ {
+       kfree(policy->freq_table);
+-
+-      return 0;
+ }
+ static int bmips_cpufreq_init(struct cpufreq_policy *policy)
+--- a/drivers/cpufreq/cppc_cpufreq.c
++++ b/drivers/cpufreq/cppc_cpufreq.c
+@@ -707,7 +707,7 @@ out:
+       return ret;
+ }
+-static int cppc_cpufreq_cpu_exit(struct cpufreq_policy *policy)
++static void cppc_cpufreq_cpu_exit(struct cpufreq_policy *policy)
+ {
+       struct cppc_cpudata *cpu_data = policy->driver_data;
+       struct cppc_perf_caps *caps = &cpu_data->perf_caps;
+@@ -724,7 +724,6 @@ static int cppc_cpufreq_cpu_exit(struct
+                        caps->lowest_perf, cpu, ret);
+       cppc_cpufreq_put_cpu_data(policy);
+-      return 0;
+ }
+ static inline u64 get_delta(u64 t1, u64 t0)
+--- a/drivers/cpufreq/cpufreq-dt.c
++++ b/drivers/cpufreq/cpufreq-dt.c
+@@ -166,10 +166,9 @@ static int cpufreq_offline(struct cpufre
+       return 0;
+ }
+-static int cpufreq_exit(struct cpufreq_policy *policy)
++static void cpufreq_exit(struct cpufreq_policy *policy)
+ {
+       clk_put(policy->clk);
+-      return 0;
+ }
+ static struct cpufreq_driver dt_cpufreq_driver = {
+--- a/drivers/cpufreq/e_powersaver.c
++++ b/drivers/cpufreq/e_powersaver.c
+@@ -360,14 +360,13 @@ static int eps_cpu_init(struct cpufreq_p
+       return 0;
+ }
+-static int eps_cpu_exit(struct cpufreq_policy *policy)
++static void eps_cpu_exit(struct cpufreq_policy *policy)
+ {
+       unsigned int cpu = policy->cpu;
+       /* Bye */
+       kfree(eps_cpu[cpu]);
+       eps_cpu[cpu] = NULL;
+-      return 0;
+ }
+ static struct cpufreq_driver eps_driver = {
+--- a/drivers/cpufreq/intel_pstate.c
++++ b/drivers/cpufreq/intel_pstate.c
+@@ -2705,13 +2705,11 @@ static int intel_pstate_cpu_offline(stru
+       return intel_cpufreq_cpu_offline(policy);
+ }
+-static int intel_pstate_cpu_exit(struct cpufreq_policy *policy)
++static void intel_pstate_cpu_exit(struct cpufreq_policy *policy)
+ {
+       pr_debug("CPU %d exiting\n", policy->cpu);
+       policy->fast_switch_possible = false;
+-
+-      return 0;
+ }
+ static int __intel_pstate_cpu_init(struct cpufreq_policy *policy)
+@@ -3041,7 +3039,7 @@ pstate_exit:
+       return ret;
+ }
+-static int intel_cpufreq_cpu_exit(struct cpufreq_policy *policy)
++static void intel_cpufreq_cpu_exit(struct cpufreq_policy *policy)
+ {
+       struct freq_qos_request *req;
+@@ -3051,7 +3049,7 @@ static int intel_cpufreq_cpu_exit(struct
+       freq_qos_remove_request(req);
+       kfree(req);
+-      return intel_pstate_cpu_exit(policy);
++      intel_pstate_cpu_exit(policy);
+ }
+ static int intel_cpufreq_suspend(struct cpufreq_policy *policy)
+--- a/drivers/cpufreq/mediatek-cpufreq-hw.c
++++ b/drivers/cpufreq/mediatek-cpufreq-hw.c
+@@ -260,7 +260,7 @@ static int mtk_cpufreq_hw_cpu_init(struc
+       return 0;
+ }
+-static int mtk_cpufreq_hw_cpu_exit(struct cpufreq_policy *policy)
++static void mtk_cpufreq_hw_cpu_exit(struct cpufreq_policy *policy)
+ {
+       struct mtk_cpufreq_data *data = policy->driver_data;
+       struct resource *res = data->res;
+@@ -270,8 +270,6 @@ static int mtk_cpufreq_hw_cpu_exit(struc
+       writel_relaxed(0x0, data->reg_bases[REG_FREQ_ENABLE]);
+       iounmap(base);
+       release_mem_region(res->start, resource_size(res));
+-
+-      return 0;
+ }
+ static void mtk_cpufreq_register_em(struct cpufreq_policy *policy)
+--- a/drivers/cpufreq/mediatek-cpufreq.c
++++ b/drivers/cpufreq/mediatek-cpufreq.c
+@@ -599,13 +599,11 @@ static int mtk_cpufreq_init(struct cpufr
+       return 0;
+ }
+-static int mtk_cpufreq_exit(struct cpufreq_policy *policy)
++static void mtk_cpufreq_exit(struct cpufreq_policy *policy)
+ {
+       struct mtk_cpu_dvfs_info *info = policy->driver_data;
+       dev_pm_opp_free_cpufreq_table(info->cpu_dev, &policy->freq_table);
+-
+-      return 0;
+ }
+ static struct cpufreq_driver mtk_cpufreq_driver = {
+--- a/drivers/cpufreq/omap-cpufreq.c
++++ b/drivers/cpufreq/omap-cpufreq.c
+@@ -135,11 +135,10 @@ static int omap_cpu_init(struct cpufreq_
+       return 0;
+ }
+-static int omap_cpu_exit(struct cpufreq_policy *policy)
++static void omap_cpu_exit(struct cpufreq_policy *policy)
+ {
+       freq_table_free();
+       clk_put(policy->clk);
+-      return 0;
+ }
+ static struct cpufreq_driver omap_driver = {
+--- a/drivers/cpufreq/pasemi-cpufreq.c
++++ b/drivers/cpufreq/pasemi-cpufreq.c
+@@ -204,21 +204,19 @@ out:
+       return err;
+ }
+-static int pas_cpufreq_cpu_exit(struct cpufreq_policy *policy)
++static void pas_cpufreq_cpu_exit(struct cpufreq_policy *policy)
+ {
+       /*
+        * We don't support CPU hotplug. Don't unmap after the system
+        * has already made it to a running state.
+        */
+       if (system_state >= SYSTEM_RUNNING)
+-              return 0;
++              return;
+       if (sdcasr_mapbase)
+               iounmap(sdcasr_mapbase);
+       if (sdcpwr_mapbase)
+               iounmap(sdcpwr_mapbase);
+-
+-      return 0;
+ }
+ static int pas_cpufreq_target(struct cpufreq_policy *policy,
+--- a/drivers/cpufreq/powernow-k6.c
++++ b/drivers/cpufreq/powernow-k6.c
+@@ -219,7 +219,7 @@ have_busfreq:
+ }
+-static int powernow_k6_cpu_exit(struct cpufreq_policy *policy)
++static void powernow_k6_cpu_exit(struct cpufreq_policy *policy)
+ {
+       unsigned int i;
+@@ -234,10 +234,9 @@ static int powernow_k6_cpu_exit(struct c
+                       cpufreq_freq_transition_begin(policy, &freqs);
+                       powernow_k6_target(policy, i);
+                       cpufreq_freq_transition_end(policy, &freqs, 0);
+-                      break;
++                      return;
+               }
+       }
+-      return 0;
+ }
+ static unsigned int powernow_k6_get(unsigned int cpu)
+--- a/drivers/cpufreq/powernow-k7.c
++++ b/drivers/cpufreq/powernow-k7.c
+@@ -644,7 +644,7 @@ static int powernow_cpu_init(struct cpuf
+       return 0;
+ }
+-static int powernow_cpu_exit(struct cpufreq_policy *policy)
++static void powernow_cpu_exit(struct cpufreq_policy *policy)
+ {
+ #ifdef CONFIG_X86_POWERNOW_K7_ACPI
+       if (acpi_processor_perf) {
+@@ -655,7 +655,6 @@ static int powernow_cpu_exit(struct cpuf
+ #endif
+       kfree(powernow_table);
+-      return 0;
+ }
+ static struct cpufreq_driver powernow_driver = {
+--- a/drivers/cpufreq/powernow-k8.c
++++ b/drivers/cpufreq/powernow-k8.c
+@@ -1089,13 +1089,13 @@ err_out:
+       return -ENODEV;
+ }
+-static int powernowk8_cpu_exit(struct cpufreq_policy *pol)
++static void powernowk8_cpu_exit(struct cpufreq_policy *pol)
+ {
+       struct powernow_k8_data *data = per_cpu(powernow_data, pol->cpu);
+       int cpu;
+       if (!data)
+-              return -EINVAL;
++              return;
+       powernow_k8_cpu_exit_acpi(data);
+@@ -1104,8 +1104,6 @@ static int powernowk8_cpu_exit(struct cp
+       /* pol->cpus will be empty here, use related_cpus instead. */
+       for_each_cpu(cpu, pol->related_cpus)
+               per_cpu(powernow_data, cpu) = NULL;
+-
+-      return 0;
+ }
+ static void query_values_on_cpu(void *_err)
+--- a/drivers/cpufreq/powernv-cpufreq.c
++++ b/drivers/cpufreq/powernv-cpufreq.c
+@@ -874,7 +874,7 @@ static int powernv_cpufreq_cpu_init(stru
+       return 0;
+ }
+-static int powernv_cpufreq_cpu_exit(struct cpufreq_policy *policy)
++static void powernv_cpufreq_cpu_exit(struct cpufreq_policy *policy)
+ {
+       struct powernv_smp_call_data freq_data;
+       struct global_pstate_info *gpstates = policy->driver_data;
+@@ -886,8 +886,6 @@ static int powernv_cpufreq_cpu_exit(stru
+               del_timer_sync(&gpstates->timer);
+       kfree(policy->driver_data);
+-
+-      return 0;
+ }
+ static int powernv_cpufreq_reboot_notifier(struct notifier_block *nb,
+--- a/drivers/cpufreq/ppc_cbe_cpufreq.c
++++ b/drivers/cpufreq/ppc_cbe_cpufreq.c
+@@ -113,10 +113,9 @@ static int cbe_cpufreq_cpu_init(struct c
+       return 0;
+ }
+-static int cbe_cpufreq_cpu_exit(struct cpufreq_policy *policy)
++static void cbe_cpufreq_cpu_exit(struct cpufreq_policy *policy)
+ {
+       cbe_cpufreq_pmi_policy_exit(policy);
+-      return 0;
+ }
+ static int cbe_cpufreq_target(struct cpufreq_policy *policy,
+--- a/drivers/cpufreq/qcom-cpufreq-hw.c
++++ b/drivers/cpufreq/qcom-cpufreq-hw.c
+@@ -574,7 +574,7 @@ static int qcom_cpufreq_hw_cpu_init(stru
+       return qcom_cpufreq_hw_lmh_init(policy, index);
+ }
+-static int qcom_cpufreq_hw_cpu_exit(struct cpufreq_policy *policy)
++static void qcom_cpufreq_hw_cpu_exit(struct cpufreq_policy *policy)
+ {
+       struct device *cpu_dev = get_cpu_device(policy->cpu);
+       struct qcom_cpufreq_data *data = policy->driver_data;
+@@ -584,8 +584,6 @@ static int qcom_cpufreq_hw_cpu_exit(stru
+       qcom_cpufreq_hw_lmh_exit(data);
+       kfree(policy->freq_table);
+       kfree(data);
+-
+-      return 0;
+ }
+ static void qcom_cpufreq_ready(struct cpufreq_policy *policy)
+--- a/drivers/cpufreq/qoriq-cpufreq.c
++++ b/drivers/cpufreq/qoriq-cpufreq.c
+@@ -225,7 +225,7 @@ err_np:
+       return -ENODEV;
+ }
+-static int qoriq_cpufreq_cpu_exit(struct cpufreq_policy *policy)
++static void qoriq_cpufreq_cpu_exit(struct cpufreq_policy *policy)
+ {
+       struct cpu_data *data = policy->driver_data;
+@@ -233,8 +233,6 @@ static int qoriq_cpufreq_cpu_exit(struct
+       kfree(data->table);
+       kfree(data);
+       policy->driver_data = NULL;
+-
+-      return 0;
+ }
+ static int qoriq_cpufreq_target(struct cpufreq_policy *policy,
+--- a/drivers/cpufreq/scmi-cpufreq.c
++++ b/drivers/cpufreq/scmi-cpufreq.c
+@@ -247,7 +247,7 @@ out_free_priv:
+       return ret;
+ }
+-static int scmi_cpufreq_exit(struct cpufreq_policy *policy)
++static void scmi_cpufreq_exit(struct cpufreq_policy *policy)
+ {
+       struct scmi_data *priv = policy->driver_data;
+@@ -255,8 +255,6 @@ static int scmi_cpufreq_exit(struct cpuf
+       dev_pm_opp_remove_all_dynamic(priv->cpu_dev);
+       free_cpumask_var(priv->opp_shared_cpus);
+       kfree(priv);
+-
+-      return 0;
+ }
+ static void scmi_cpufreq_register_em(struct cpufreq_policy *policy)
+--- a/drivers/cpufreq/scpi-cpufreq.c
++++ b/drivers/cpufreq/scpi-cpufreq.c
+@@ -175,7 +175,7 @@ out_free_opp:
+       return ret;
+ }
+-static int scpi_cpufreq_exit(struct cpufreq_policy *policy)
++static void scpi_cpufreq_exit(struct cpufreq_policy *policy)
+ {
+       struct scpi_data *priv = policy->driver_data;
+@@ -183,8 +183,6 @@ static int scpi_cpufreq_exit(struct cpuf
+       dev_pm_opp_free_cpufreq_table(priv->cpu_dev, &policy->freq_table);
+       dev_pm_opp_remove_all_dynamic(priv->cpu_dev);
+       kfree(priv);
+-
+-      return 0;
+ }
+ static struct cpufreq_driver scpi_cpufreq_driver = {
+--- a/drivers/cpufreq/sh-cpufreq.c
++++ b/drivers/cpufreq/sh-cpufreq.c
+@@ -135,14 +135,12 @@ static int sh_cpufreq_cpu_init(struct cp
+       return 0;
+ }
+-static int sh_cpufreq_cpu_exit(struct cpufreq_policy *policy)
++static void sh_cpufreq_cpu_exit(struct cpufreq_policy *policy)
+ {
+       unsigned int cpu = policy->cpu;
+       struct clk *cpuclk = &per_cpu(sh_cpuclk, cpu);
+       clk_put(cpuclk);
+-
+-      return 0;
+ }
+ static struct cpufreq_driver sh_cpufreq_driver = {
+--- a/drivers/cpufreq/sparc-us2e-cpufreq.c
++++ b/drivers/cpufreq/sparc-us2e-cpufreq.c
+@@ -296,10 +296,9 @@ static int us2e_freq_cpu_init(struct cpu
+       return 0;
+ }
+-static int us2e_freq_cpu_exit(struct cpufreq_policy *policy)
++static void us2e_freq_cpu_exit(struct cpufreq_policy *policy)
+ {
+       us2e_freq_target(policy, 0);
+-      return 0;
+ }
+ static struct cpufreq_driver cpufreq_us2e_driver = {
+--- a/drivers/cpufreq/sparc-us3-cpufreq.c
++++ b/drivers/cpufreq/sparc-us3-cpufreq.c
+@@ -140,10 +140,9 @@ static int us3_freq_cpu_init(struct cpuf
+       return 0;
+ }
+-static int us3_freq_cpu_exit(struct cpufreq_policy *policy)
++static void us3_freq_cpu_exit(struct cpufreq_policy *policy)
+ {
+       us3_freq_target(policy, 0);
+-      return 0;
+ }
+ static struct cpufreq_driver cpufreq_us3_driver = {
+--- a/drivers/cpufreq/speedstep-centrino.c
++++ b/drivers/cpufreq/speedstep-centrino.c
+@@ -400,16 +400,12 @@ static int centrino_cpu_init(struct cpuf
+       return 0;
+ }
+-static int centrino_cpu_exit(struct cpufreq_policy *policy)
++static void centrino_cpu_exit(struct cpufreq_policy *policy)
+ {
+       unsigned int cpu = policy->cpu;
+-      if (!per_cpu(centrino_model, cpu))
+-              return -ENODEV;
+-
+-      per_cpu(centrino_model, cpu) = NULL;
+-
+-      return 0;
++      if (per_cpu(centrino_model, cpu))
++              per_cpu(centrino_model, cpu) = NULL;
+ }
+ /**
+--- a/drivers/cpufreq/tegra194-cpufreq.c
++++ b/drivers/cpufreq/tegra194-cpufreq.c
+@@ -526,14 +526,12 @@ static int tegra194_cpufreq_offline(stru
+       return 0;
+ }
+-static int tegra194_cpufreq_exit(struct cpufreq_policy *policy)
++static void tegra194_cpufreq_exit(struct cpufreq_policy *policy)
+ {
+       struct device *cpu_dev = get_cpu_device(policy->cpu);
+       dev_pm_opp_remove_all_dynamic(cpu_dev);
+       dev_pm_opp_of_cpumask_remove_table(policy->related_cpus);
+-
+-      return 0;
+ }
+ static int tegra194_cpufreq_set_target(struct cpufreq_policy *policy,
+--- a/drivers/cpufreq/vexpress-spc-cpufreq.c
++++ b/drivers/cpufreq/vexpress-spc-cpufreq.c
+@@ -447,7 +447,7 @@ static int ve_spc_cpufreq_init(struct cp
+       return 0;
+ }
+-static int ve_spc_cpufreq_exit(struct cpufreq_policy *policy)
++static void ve_spc_cpufreq_exit(struct cpufreq_policy *policy)
+ {
+       struct device *cpu_dev;
+@@ -455,11 +455,10 @@ static int ve_spc_cpufreq_exit(struct cp
+       if (!cpu_dev) {
+               pr_err("%s: failed to get cpu%d device\n", __func__,
+                      policy->cpu);
+-              return -ENODEV;
++              return;
+       }
+       put_cluster_clk_and_freq_table(cpu_dev, policy->related_cpus);
+-      return 0;
+ }
+ static struct cpufreq_driver ve_spc_cpufreq_driver = {
+--- a/include/linux/cpufreq.h
++++ b/include/linux/cpufreq.h
+@@ -388,7 +388,7 @@ struct cpufreq_driver {
+       int             (*online)(struct cpufreq_policy *policy);
+       int             (*offline)(struct cpufreq_policy *policy);
+-      int             (*exit)(struct cpufreq_policy *policy);
++      void            (*exit)(struct cpufreq_policy *policy);
+       int             (*suspend)(struct cpufreq_policy *policy);
+       int             (*resume)(struct cpufreq_policy *policy);
diff --git a/queue-6.6/cpufreq-pcc-remove-empty-exit-callback.patch b/queue-6.6/cpufreq-pcc-remove-empty-exit-callback.patch
new file mode 100644 (file)
index 0000000..ada03d9
--- /dev/null
@@ -0,0 +1,44 @@
+From stable+bounces-276790-greg=kroah.com@vger.kernel.org Thu Jul 16 19:51:32 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Jul 2026 13:45:53 -0400
+Subject: cpufreq: pcc: Remove empty exit() callback
+To: stable@vger.kernel.org
+Cc: Viresh Kumar <viresh.kumar@linaro.org>, "Rafael J. Wysocki" <rafael@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260716174555.787913-1-sashal@kernel.org>
+
+From: Viresh Kumar <viresh.kumar@linaro.org>
+
+[ Upstream commit dfd3e8b90b3660b706c3031b0f746377c571b324 ]
+
+The exit() callback is optional, remove the empty one.
+
+Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
+Acked-by: Rafael J. Wysocki <rafael@kernel.org>
+Stable-dep-of: bcb8889c4981 ("cpufreq: qcom-cpufreq-hw: Fix possible double free")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/cpufreq/pcc-cpufreq.c |    6 ------
+ 1 file changed, 6 deletions(-)
+
+--- a/drivers/cpufreq/pcc-cpufreq.c
++++ b/drivers/cpufreq/pcc-cpufreq.c
+@@ -564,18 +564,12 @@ out:
+       return result;
+ }
+-static int pcc_cpufreq_cpu_exit(struct cpufreq_policy *policy)
+-{
+-      return 0;
+-}
+-
+ static struct cpufreq_driver pcc_cpufreq_driver = {
+       .flags = CPUFREQ_CONST_LOOPS,
+       .get = pcc_get_freq,
+       .verify = pcc_cpufreq_verify,
+       .target = pcc_cpufreq_target,
+       .init = pcc_cpufreq_cpu_init,
+-      .exit = pcc_cpufreq_cpu_exit,
+       .name = "pcc-cpufreq",
+ };
diff --git a/queue-6.6/cpufreq-qcom-cpufreq-hw-fix-possible-double-free.patch b/queue-6.6/cpufreq-qcom-cpufreq-hw-fix-possible-double-free.patch
new file mode 100644 (file)
index 0000000..059dc23
--- /dev/null
@@ -0,0 +1,48 @@
+From stable+bounces-276792-greg=kroah.com@vger.kernel.org Thu Jul 16 19:51:28 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Jul 2026 13:45:55 -0400
+Subject: cpufreq: qcom-cpufreq-hw: Fix possible double free
+To: stable@vger.kernel.org
+Cc: Guangshuo Li <lgs201920130244@gmail.com>, Zhongqiu Han <zhongqiu.han@oss.qualcomm.com>, Viresh Kumar <viresh.kumar@linaro.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260716174555.787913-3-sashal@kernel.org>
+
+From: Guangshuo Li <lgs201920130244@gmail.com>
+
+[ Upstream commit bcb8889c4981fdde42d4fd2c29a77d510fe21da2 ]
+
+qcom_cpufreq.data is allocated with devm_kzalloc() in probe() as an
+array of per-domain data. qcom_cpufreq_hw_cpu_init() stores a pointer to
+one element of this array in policy->driver_data.
+
+qcom_cpufreq_hw_cpu_exit() currently calls kfree() on policy->driver_data.
+This is not valid because the memory is devm-managed. For the first
+domain, this can free the devm-managed allocation while the devres entry
+is still active, leading to a possible double free when the platform
+device is later detached. For other domains, the pointer may refer to an
+element inside the array rather than the allocation base.
+
+Remove the kfree(data) call and let devres release qcom_cpufreq.data.
+
+This issue was found by a static analysis tool I am developing.
+
+Fixes: 054a3ef683a1 ("cpufreq: qcom-hw: Allocate qcom_cpufreq_data during probe")
+Cc: stable@vger.kernel.org
+Signed-off-by: Guangshuo Li <lgs201920130244@gmail.com>
+Reviewed-by: Zhongqiu Han <zhongqiu.han@oss.qualcomm.com>
+Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/cpufreq/qcom-cpufreq-hw.c |    1 -
+ 1 file changed, 1 deletion(-)
+
+--- a/drivers/cpufreq/qcom-cpufreq-hw.c
++++ b/drivers/cpufreq/qcom-cpufreq-hw.c
+@@ -583,7 +583,6 @@ static void qcom_cpufreq_hw_cpu_exit(str
+       dev_pm_opp_of_cpumask_remove_table(policy->related_cpus);
+       qcom_cpufreq_hw_lmh_exit(data);
+       kfree(policy->freq_table);
+-      kfree(data);
+ }
+ static void qcom_cpufreq_ready(struct cpufreq_policy *policy)
diff --git a/queue-6.6/crypto-atmel-drop-explicit-initialization-of-struct-i2c_device_id-driver_data-to-0.patch b/queue-6.6/crypto-atmel-drop-explicit-initialization-of-struct-i2c_device_id-driver_data-to-0.patch
new file mode 100644 (file)
index 0000000..7afc519
--- /dev/null
@@ -0,0 +1,53 @@
+From stable+bounces-277783-greg=kroah.com@vger.kernel.org Mon Jul 20 15:33:26 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 09:30:25 -0400
+Subject: crypto: atmel - Drop explicit initialization of struct i2c_device_id::driver_data to 0
+To: stable@vger.kernel.org
+Cc: "Uwe Kleine-König" <u.kleine-koenig@baylibre.com>, "Herbert Xu" <herbert@gondor.apana.org.au>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260720133026.1149181-1-sashal@kernel.org>
+
+From: Uwe Kleine-König <u.kleine-koenig@baylibre.com>
+
+[ Upstream commit d86ad3911a5d4549297ed810ee450e5772fd665f ]
+
+These drivers don't use the driver_data member of struct i2c_device_id,
+so don't explicitly initialize this member.
+
+This prepares putting driver_data in an anonymous union which requires
+either no initialization or named designators. But it's also a nice
+cleanup on its own.
+
+Signed-off-by: Uwe Kleine-König <u.kleine-koenig@baylibre.com>
+Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
+Stable-dep-of: ea5e57cc9718 ("crypto: atmel-sha204a - drop hwrng quality reduction for ATSHA204A")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/crypto/atmel-ecc.c     |    2 +-
+ drivers/crypto/atmel-sha204a.c |    4 ++--
+ 2 files changed, 3 insertions(+), 3 deletions(-)
+
+--- a/drivers/crypto/atmel-ecc.c
++++ b/drivers/crypto/atmel-ecc.c
+@@ -380,7 +380,7 @@ MODULE_DEVICE_TABLE(of, atmel_ecc_dt_ids
+ #endif
+ static const struct i2c_device_id atmel_ecc_id[] = {
+-      { "atecc508a", 0 },
++      { "atecc508a" },
+       { }
+ };
+ MODULE_DEVICE_TABLE(i2c, atmel_ecc_id);
+--- a/drivers/crypto/atmel-sha204a.c
++++ b/drivers/crypto/atmel-sha204a.c
+@@ -139,8 +139,8 @@ static const struct of_device_id atmel_s
+ MODULE_DEVICE_TABLE(of, atmel_sha204a_dt_ids);
+ static const struct i2c_device_id atmel_sha204a_id[] = {
+-      { "atsha204", 0 },
+-      { "atsha204a", 0 },
++      { "atsha204" },
++      { "atsha204a" },
+       { /* sentinel */ }
+ };
+ MODULE_DEVICE_TABLE(i2c, atmel_sha204a_id);
diff --git a/queue-6.6/crypto-atmel-sha204a-drop-hwrng-quality-reduction-for-atsha204a.patch b/queue-6.6/crypto-atmel-sha204a-drop-hwrng-quality-reduction-for-atsha204a.patch
new file mode 100644 (file)
index 0000000..d217ba7
--- /dev/null
@@ -0,0 +1,101 @@
+From stable+bounces-277784-greg=kroah.com@vger.kernel.org Mon Jul 20 15:58:07 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 09:30:26 -0400
+Subject: crypto: atmel-sha204a - drop hwrng quality reduction for ATSHA204A
+To: stable@vger.kernel.org
+Cc: Thorsten Blum <thorsten.blum@linux.dev>, Ard Biesheuvel <ardb@kernel.org>, Herbert Xu <herbert@gondor.apana.org.au>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260720133026.1149181-2-sashal@kernel.org>
+
+From: Thorsten Blum <thorsten.blum@linux.dev>
+
+[ Upstream commit ea5e57cc97185329dcc5ebdcaae7e1500bf0ad0b ]
+
+Commit 8006aff15516 ("crypto: atmel-sha204a - Set hwrng quality to
+lowest possible") reduced the hwrng quality to 1 based on a review by
+Bill Cox [1]. However, despite its title, the review only tested the
+ATSHA204, not the ATSHA204A.
+
+In the same thread, Atmel engineer Landon Cox wrote "this behavior has
+been eliminated entirely"[2] in the ATSHA204A and "this problem does not
+affect the ATECC108 or the ATECC108A (or the ATSHA204A)"[3].
+
+According to the official ATSHA204A datasheet [4], the device contains a
+high-quality hardware RNG that combines its output with an internal seed
+value stored in EEPROM or SRAM to generate random numbers. The device
+also implements all security functions using SHA-256, and the driver
+uses the chip's Random command in seed-update mode.
+
+Keep 'quality = 1' for ATSHA204, but drop the explicit hwrng quality
+reduction for ATSHA204A and fall back to the hwrng core default.
+
+[1] https://www.metzdowd.com/pipermail/cryptography/2014-December/023858.html
+[2] https://www.metzdowd.com/pipermail/cryptography/2014-December/023852.html
+[3] https://www.metzdowd.com/pipermail/cryptography/2014-December/023886.html
+[4] https://ww1.microchip.com/downloads/en/DeviceDoc/ATSHA204A-Data-Sheet-40002025A.pdf
+
+Fixes: 8006aff15516 ("crypto: atmel-sha204a - Set hwrng quality to lowest possible")
+Cc: stable@vger.kernel.org
+Signed-off-by: Thorsten Blum <thorsten.blum@linux.dev>
+Reviewed-by: Ard Biesheuvel <ardb@kernel.org>
+Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/crypto/atmel-sha204a.c |   19 ++++++++++++-------
+ 1 file changed, 12 insertions(+), 7 deletions(-)
+
+--- a/drivers/crypto/atmel-sha204a.c
++++ b/drivers/crypto/atmel-sha204a.c
+@@ -18,6 +18,12 @@
+ #include <linux/workqueue.h>
+ #include "atmel-i2c.h"
++/*
++ * According to review by Bill Cox [1], the ATSHA204 has very low entropy.
++ * [1] https://www.metzdowd.com/pipermail/cryptography/2014-December/023858.html
++ */
++static const unsigned short atsha204_quality = 1;
++
+ static void atmel_sha204a_rng_done(struct atmel_i2c_work_data *work_data,
+                                  void *areq, int status)
+ {
+@@ -95,6 +101,7 @@ static int atmel_sha204a_rng_read(struct
+ static int atmel_sha204a_probe(struct i2c_client *client)
+ {
+       struct atmel_i2c_client_priv *i2c_priv;
++      const unsigned short *quality;
+       int ret;
+       ret = atmel_i2c_probe(client);
+@@ -108,11 +115,9 @@ static int atmel_sha204a_probe(struct i2
+       i2c_priv->hwrng.name = dev_name(&client->dev);
+       i2c_priv->hwrng.read = atmel_sha204a_rng_read;
+-      /*
+-       * According to review by Bill Cox [1], this HWRNG has very low entropy.
+-       * [1] https://www.metzdowd.com/pipermail/cryptography/2014-December/023858.html
+-       */
+-      i2c_priv->hwrng.quality = 1;
++      quality = i2c_get_match_data(client);
++      if (quality)
++              i2c_priv->hwrng.quality = *quality;
+       ret = devm_hwrng_register(&client->dev, &i2c_priv->hwrng);
+       if (ret)
+@@ -132,14 +137,14 @@ static void atmel_sha204a_remove(struct
+ }
+ static const struct of_device_id atmel_sha204a_dt_ids[] __maybe_unused = {
+-      { .compatible = "atmel,atsha204", },
++      { .compatible = "atmel,atsha204", .data = &atsha204_quality },
+       { .compatible = "atmel,atsha204a", },
+       { /* sentinel */ }
+ };
+ MODULE_DEVICE_TABLE(of, atmel_sha204a_dt_ids);
+ static const struct i2c_device_id atmel_sha204a_id[] = {
+-      { "atsha204" },
++      { "atsha204", (kernel_ulong_t)&atsha204_quality },
+       { "atsha204a" },
+       { /* sentinel */ }
+ };
diff --git a/queue-6.6/crypto-qat-fix-restarting-state-leak-on-allocation-failure.patch b/queue-6.6/crypto-qat-fix-restarting-state-leak-on-allocation-failure.patch
new file mode 100644 (file)
index 0000000..500ce37
--- /dev/null
@@ -0,0 +1,57 @@
+From stable+bounces-278191-greg=kroah.com@vger.kernel.org Mon Jul 20 20:38:43 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 14:38:34 -0400
+Subject: crypto: qat - fix restarting state leak on allocation failure
+To: stable@vger.kernel.org
+Cc: Ahsan Atta <ahsan.atta@intel.com>, Maksim Lukoshkov <maksim.lukoshkov@intel.com>, Giovanni Cabiddu <giovanni.cabiddu@intel.com>, Herbert Xu <herbert@gondor.apana.org.au>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260720183834.3080260-1-sashal@kernel.org>
+
+From: Ahsan Atta <ahsan.atta@intel.com>
+
+[ Upstream commit 7d3ed20f7e46b3e991936fedd7a28f3ff4aec8d2 ]
+
+In adf_dev_aer_schedule_reset(), ADF_STATUS_RESTARTING is set before
+allocating reset_data. If the allocation fails, the function returns
+-ENOMEM without queuing reset work, so nothing ever clears the bit.
+This leaves the device permanently stuck in the restarting state,
+causing all subsequent reset attempts to be silently skipped.
+
+Fix this by using test_and_set_bit() to atomically claim the
+RESTARTING state, preventing duplicate reset scheduling races under
+concurrent fatal error reporting. If the subsequent allocation fails,
+clear the bit to restore clean state so future reset attempts can
+proceed.
+
+Cc: stable@vger.kernel.org
+Fixes: d8cba25d2c68 ("crypto: qat - Intel(R) QAT driver framework")
+Signed-off-by: Ahsan Atta <ahsan.atta@intel.com>
+Co-developed-by: Maksim Lukoshkov <maksim.lukoshkov@intel.com>
+Signed-off-by: Maksim Lukoshkov <maksim.lukoshkov@intel.com>
+Reviewed-by: Giovanni Cabiddu <giovanni.cabiddu@intel.com>
+Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/crypto/intel/qat/qat_common/adf_aer.c |    7 ++++---
+ 1 file changed, 4 insertions(+), 3 deletions(-)
+
+--- a/drivers/crypto/intel/qat/qat_common/adf_aer.c
++++ b/drivers/crypto/intel/qat/qat_common/adf_aer.c
+@@ -113,13 +113,14 @@ static int adf_dev_aer_schedule_reset(st
+       struct adf_reset_dev_data *reset_data;
+       if (!adf_dev_started(accel_dev) ||
+-          test_bit(ADF_STATUS_RESTARTING, &accel_dev->status))
++          test_and_set_bit(ADF_STATUS_RESTARTING, &accel_dev->status))
+               return 0;
+-      set_bit(ADF_STATUS_RESTARTING, &accel_dev->status);
+       reset_data = kzalloc(sizeof(*reset_data), GFP_KERNEL);
+-      if (!reset_data)
++      if (!reset_data) {
++              clear_bit(ADF_STATUS_RESTARTING, &accel_dev->status);
+               return -ENOMEM;
++      }
+       reset_data->accel_dev = accel_dev;
+       init_completion(&reset_data->compl);
+       reset_data->mode = mode;
diff --git a/queue-6.6/crypto-qat-fix-vf2pf-work-teardown-race-in-adf_disable_sriov.patch b/queue-6.6/crypto-qat-fix-vf2pf-work-teardown-race-in-adf_disable_sriov.patch
new file mode 100644 (file)
index 0000000..1f7bc47
--- /dev/null
@@ -0,0 +1,216 @@
+From stable+bounces-274667-greg=kroah.com@vger.kernel.org Wed Jul 15 03:45:38 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 21:45:27 -0400
+Subject: crypto: qat - fix VF2PF work teardown race in adf_disable_sriov()
+To: stable@vger.kernel.org
+Cc: Giovanni Cabiddu <giovanni.cabiddu@intel.com>, Ahsan Atta <ahsan.atta@intel.com>, Herbert Xu <herbert@gondor.apana.org.au>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715014527.4133831-1-sashal@kernel.org>
+
+From: Giovanni Cabiddu <giovanni.cabiddu@intel.com>
+
+[ Upstream commit 277281c10c63791067d24d421f7c43a15faa9096 ]
+
+The VF2PF interrupt handler queues PF-side response work that stores a
+raw pointer to per-VF state (struct adf_accel_vf_info). Currently,
+adf_disable_sriov() destroys per-VF mutexes and frees vf_info without
+stopping new VF2PF work or waiting for in-flight workers to complete. A
+concurrently scheduled or already queued worker can then dereference
+freed memory.
+
+This manifests as a use-after-free when KASAN is enabled:
+
+  BUG: KASAN: null-ptr-deref in mutex_lock+0x76/0xe0
+  Write of size 8 at addr 0000000000000260 by task kworker/24:2/...
+  Workqueue: qat_pf2vf_resp_wq adf_iov_send_resp [intel_qat]
+  Call Trace:
+    kasan_report+0x119/0x140
+    mutex_lock+0x76/0xe0
+    adf_gen4_pfvf_send+0xd4/0x1f0 [intel_qat]
+    adf_recv_and_handle_vf2pf_msg+0x290/0x360 [intel_qat]
+    adf_iov_send_resp+0x8c/0xe0 [intel_qat]
+    process_one_work+0x6ac/0xfd0
+    worker_thread+0x4dd/0xd30
+    kthread+0x326/0x410
+    ret_from_fork+0x33b/0x670
+
+Add a PF-local flag, vf2pf_disabled, that gates work queueing, worker
+processing, and interrupt re-enabling during teardown. Set this flag
+atomically with the hardware interrupt mask inside
+adf_disable_all_vf2pf_interrupts(). After masking, synchronize the AE
+cluster MSI-X interrupt and flush the PF response workqueue before
+tearing down per-VF locks and state so all in-flight work completes
+before vf_info is destroyed.
+
+Introduce adf_enable_all_vf2pf_interrupts() to clear the flag and
+unmask all VF2PF interrupts under the same lock when SR-IOV is
+re-enabled. This ensures the software flag and hardware state transition
+atomically on both the enable and disable paths.
+
+Cc: stable@vger.kernel.org
+Fixes: ed8ccaef52fa ("crypto: qat - Add support for SRIOV")
+Signed-off-by: Giovanni Cabiddu <giovanni.cabiddu@intel.com>
+Reviewed-by: Ahsan Atta <ahsan.atta@intel.com>
+Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/crypto/intel/qat/qat_common/adf_accel_devices.h |    2 
+ drivers/crypto/intel/qat/qat_common/adf_common_drv.h    |    2 
+ drivers/crypto/intel/qat/qat_common/adf_isr.c           |   39 ++++++++++++++++
+ drivers/crypto/intel/qat/qat_common/adf_sriov.c         |   20 +++++++-
+ 4 files changed, 61 insertions(+), 2 deletions(-)
+
+--- a/drivers/crypto/intel/qat/qat_common/adf_accel_devices.h
++++ b/drivers/crypto/intel/qat/qat_common/adf_accel_devices.h
+@@ -314,6 +314,8 @@ struct adf_accel_dev {
+               struct {
+                       /* protects VF2PF interrupts access */
+                       spinlock_t vf2pf_ints_lock;
++                      /* prevents VF2PF handling from racing with VF state teardown */
++                      bool vf2pf_disabled;
+                       /* vf_info is non-zero when SR-IOV is init'ed */
+                       struct adf_accel_vf_info *vf_info;
+               } pf;
+--- a/drivers/crypto/intel/qat/qat_common/adf_common_drv.h
++++ b/drivers/crypto/intel/qat/qat_common/adf_common_drv.h
+@@ -123,6 +123,7 @@ void qat_comp_alg_callback(void *resp);
+ int adf_isr_resource_alloc(struct adf_accel_dev *accel_dev);
+ void adf_isr_resource_free(struct adf_accel_dev *accel_dev);
++void adf_isr_sync_ae_cluster(struct adf_accel_dev *accel_dev);
+ int adf_vf_isr_resource_alloc(struct adf_accel_dev *accel_dev);
+ void adf_vf_isr_resource_free(struct adf_accel_dev *accel_dev);
+@@ -195,6 +196,7 @@ void adf_misc_wq_flush(void);
+ int adf_sriov_configure(struct pci_dev *pdev, int numvfs);
+ void adf_disable_sriov(struct adf_accel_dev *accel_dev);
+ void adf_enable_vf2pf_interrupts(struct adf_accel_dev *accel_dev, u32 vf_mask);
++void adf_enable_all_vf2pf_interrupts(struct adf_accel_dev *accel_dev, u32 num_vfs);
+ void adf_disable_all_vf2pf_interrupts(struct adf_accel_dev *accel_dev);
+ bool adf_recv_and_handle_pf2vf_msg(struct adf_accel_dev *accel_dev);
+ bool adf_recv_and_handle_vf2pf_msg(struct adf_accel_dev *accel_dev, u32 vf_nr);
+--- a/drivers/crypto/intel/qat/qat_common/adf_isr.c
++++ b/drivers/crypto/intel/qat/qat_common/adf_isr.c
+@@ -62,6 +62,23 @@ void adf_enable_vf2pf_interrupts(struct
+       unsigned long flags;
+       spin_lock_irqsave(&accel_dev->pf.vf2pf_ints_lock, flags);
++      if (!READ_ONCE(accel_dev->pf.vf2pf_disabled))
++              GET_PFVF_OPS(accel_dev)->enable_vf2pf_interrupts(pmisc_addr, vf_mask);
++      spin_unlock_irqrestore(&accel_dev->pf.vf2pf_ints_lock, flags);
++}
++
++void adf_enable_all_vf2pf_interrupts(struct adf_accel_dev *accel_dev, u32 num_vfs)
++{
++      void __iomem *pmisc_addr = adf_get_pmisc_base(accel_dev);
++      unsigned long flags;
++      u32 vf_mask;
++
++      vf_mask = BIT_ULL(num_vfs) - 1;
++      if (!vf_mask)
++              return;
++
++      spin_lock_irqsave(&accel_dev->pf.vf2pf_ints_lock, flags);
++      WRITE_ONCE(accel_dev->pf.vf2pf_disabled, false);
+       GET_PFVF_OPS(accel_dev)->enable_vf2pf_interrupts(pmisc_addr, vf_mask);
+       spin_unlock_irqrestore(&accel_dev->pf.vf2pf_ints_lock, flags);
+ }
+@@ -72,6 +89,7 @@ void adf_disable_all_vf2pf_interrupts(st
+       unsigned long flags;
+       spin_lock_irqsave(&accel_dev->pf.vf2pf_ints_lock, flags);
++      WRITE_ONCE(accel_dev->pf.vf2pf_disabled, true);
+       GET_PFVF_OPS(accel_dev)->disable_all_vf2pf_interrupts(pmisc_addr);
+       spin_unlock_irqrestore(&accel_dev->pf.vf2pf_ints_lock, flags);
+ }
+@@ -151,6 +169,27 @@ static irqreturn_t adf_msix_isr_ae(int i
+       return IRQ_NONE;
+ }
++void adf_isr_sync_ae_cluster(struct adf_accel_dev *accel_dev)
++{
++      struct adf_accel_pci *pci_dev_info = &accel_dev->accel_pci_dev;
++      struct adf_hw_device_data *hw_data = GET_HW_DATA(accel_dev);
++      u32 num_entries = pci_dev_info->msix_entries.num_entries;
++      struct adf_irq *irqs = pci_dev_info->msix_entries.irqs;
++      u32 irq_idx;
++      int irq;
++
++      if (!test_bit(ADF_STATUS_IRQ_ALLOCATED, &accel_dev->status) || !irqs)
++              return;
++
++      irq_idx = num_entries > 1 ? hw_data->num_banks : 0;
++      if (irq_idx >= num_entries || !irqs[irq_idx].enabled)
++              return;
++
++      irq = pci_irq_vector(pci_dev_info->pci_dev, hw_data->num_banks);
++      if (irq > 0)
++              synchronize_irq(irq);
++}
++
+ static void adf_free_irqs(struct adf_accel_dev *accel_dev)
+ {
+       struct adf_accel_pci *pci_dev_info = &accel_dev->accel_pci_dev;
+--- a/drivers/crypto/intel/qat/qat_common/adf_sriov.c
++++ b/drivers/crypto/intel/qat/qat_common/adf_sriov.c
+@@ -26,18 +26,26 @@ static void adf_iov_send_resp(struct wor
+       u32 vf_nr = vf_info->vf_nr;
+       bool ret;
++      if (READ_ONCE(accel_dev->pf.vf2pf_disabled))
++              goto out;
++
+       ret = adf_recv_and_handle_vf2pf_msg(accel_dev, vf_nr);
+       if (ret)
+               /* re-enable interrupt on PF from this VF */
+               adf_enable_vf2pf_interrupts(accel_dev, 1 << vf_nr);
++out:
+       kfree(pf2vf_resp);
+ }
+ void adf_schedule_vf2pf_handler(struct adf_accel_vf_info *vf_info)
+ {
++      struct adf_accel_dev *accel_dev = vf_info->accel_dev;
+       struct adf_pf2vf_resp *pf2vf_resp;
++      if (READ_ONCE(accel_dev->pf.vf2pf_disabled))
++              return;
++
+       pf2vf_resp = kzalloc(sizeof(*pf2vf_resp), GFP_ATOMIC);
+       if (!pf2vf_resp)
+               return;
+@@ -47,6 +55,12 @@ void adf_schedule_vf2pf_handler(struct a
+       queue_work(pf2vf_resp_wq, &pf2vf_resp->pf2vf_resp_work);
+ }
++static void adf_flush_pf2vf_resp_wq(void)
++{
++      if (pf2vf_resp_wq)
++              flush_workqueue(pf2vf_resp_wq);
++}
++
+ static int adf_enable_sriov(struct adf_accel_dev *accel_dev)
+ {
+       struct pci_dev *pdev = accel_to_pci_dev(accel_dev);
+@@ -73,7 +87,7 @@ static int adf_enable_sriov(struct adf_a
+               hw_data->configure_iov_threads(accel_dev, true);
+       /* Enable VF to PF interrupts for all VFs */
+-      adf_enable_vf2pf_interrupts(accel_dev, BIT_ULL(totalvfs) - 1);
++      adf_enable_all_vf2pf_interrupts(accel_dev, totalvfs);
+       /*
+        * Due to the hardware design, when SR-IOV and the ring arbiter
+@@ -105,8 +119,10 @@ void adf_disable_sriov(struct adf_accel_
+       adf_pf2vf_notify_restarting(accel_dev);
+       pci_disable_sriov(accel_to_pci_dev(accel_dev));
+-      /* Disable VF to PF interrupts */
++      /* Block VF2PF work and disable VF to PF interrupts */
+       adf_disable_all_vf2pf_interrupts(accel_dev);
++      adf_isr_sync_ae_cluster(accel_dev);
++      adf_flush_pf2vf_resp_wq();
+       /* Clear Valid bits in AE Thread to PCIe Function Mapping */
+       if (hw_data->configure_iov_threads)
diff --git a/queue-6.6/exfat-add-exfat_get_dentry_set_by_ei-helper.patch b/queue-6.6/exfat-add-exfat_get_dentry_set_by_ei-helper.patch
new file mode 100644 (file)
index 0000000..2855960
--- /dev/null
@@ -0,0 +1,315 @@
+From stable+bounces-278570-greg=kroah.com@vger.kernel.org Tue Jul 21 13:52:35 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 07:47:41 -0400
+Subject: exfat: add exfat_get_dentry_set_by_ei() helper
+To: stable@vger.kernel.org
+Cc: Yuezhang Mo <Yuezhang.Mo@sony.com>, Aoyama Wataru <wataru.aoyama@sony.com>, Daniel Palmer <daniel.palmer@sony.com>, Sungjong Seo <sj1557.seo@samsung.com>, Namjae Jeon <linkinjeon@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721114743.3689685-4-sashal@kernel.org>
+
+From: Yuezhang Mo <Yuezhang.Mo@sony.com>
+
+[ Upstream commit ac844e91364a03c35838fd488437605fbe56f8c3 ]
+
+This helper gets the directory entry set of the file for the exfat
+inode which has been created.
+
+It's used to remove all the instances of the pattern it replaces
+making the code cleaner, it's also a preparation for changing ->dir
+to record the cluster where the directory entry set is located and
+changing ->entry to record the index of the directory entry within
+the cluster.
+
+Signed-off-by: Yuezhang Mo <Yuezhang.Mo@sony.com>
+Reviewed-by: Aoyama Wataru <wataru.aoyama@sony.com>
+Reviewed-by: Daniel Palmer <daniel.palmer@sony.com>
+Reviewed-by: Sungjong Seo <sj1557.seo@samsung.com>
+Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
+Stable-dep-of: 942296784b2a ("exfat: preserve benign secondary entries during rename and move")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/exfat/exfat_fs.h |    2 +
+ fs/exfat/inode.c    |    2 -
+ fs/exfat/namei.c    |   82 ++++++++++++++++++++++------------------------------
+ 3 files changed, 38 insertions(+), 48 deletions(-)
+
+--- a/fs/exfat/exfat_fs.h
++++ b/fs/exfat/exfat_fs.h
+@@ -498,6 +498,8 @@ struct exfat_dentry *exfat_get_dentry_ca
+ int exfat_get_dentry_set(struct exfat_entry_set_cache *es,
+               struct super_block *sb, struct exfat_chain *p_dir, int entry,
+               unsigned int type);
++#define exfat_get_dentry_set_by_ei(es, sb, ei)                \
++      exfat_get_dentry_set(es, sb, &(ei)->dir, (ei)->entry, ES_ALL_ENTRIES)
+ int exfat_put_dentry_set(struct exfat_entry_set_cache *es, int sync);
+ int exfat_count_dir_entries(struct super_block *sb, struct exfat_chain *p_dir);
+--- a/fs/exfat/inode.c
++++ b/fs/exfat/inode.c
+@@ -42,7 +42,7 @@ int __exfat_write_inode(struct inode *in
+       exfat_set_volume_dirty(sb);
+       /* get the directory entry of given file or directory */
+-      if (exfat_get_dentry_set(&es, sb, &(ei->dir), ei->entry, ES_ALL_ENTRIES))
++      if (exfat_get_dentry_set_by_ei(&es, sb, ei))
+               return -EIO;
+       ep = exfat_get_dentry_cached(&es, ES_IDX_FILE);
+       ep2 = exfat_get_dentry_cached(&es, ES_IDX_STREAM);
+--- a/fs/exfat/namei.c
++++ b/fs/exfat/namei.c
+@@ -786,29 +786,26 @@ unlock:
+ /* remove an entry, BUT don't truncate */
+ static int exfat_unlink(struct inode *dir, struct dentry *dentry)
+ {
+-      struct exfat_chain cdir;
+       struct exfat_dentry *ep;
+       struct super_block *sb = dir->i_sb;
+       struct inode *inode = dentry->d_inode;
+       struct exfat_inode_info *ei = EXFAT_I(inode);
+       struct buffer_head *bh;
+-      int num_entries, entry, err = 0;
++      int num_entries, err = 0;
+       mutex_lock(&EXFAT_SB(sb)->s_lock);
+-      exfat_chain_dup(&cdir, &ei->dir);
+-      entry = ei->entry;
+       if (ei->dir.dir == DIR_DELETED) {
+               exfat_err(sb, "abnormal access to deleted dentry");
+               err = -ENOENT;
+               goto unlock;
+       }
+-      ep = exfat_get_dentry(sb, &cdir, entry, &bh);
++      ep = exfat_get_dentry(sb, &ei->dir, ei->entry, &bh);
+       if (!ep) {
+               err = -EIO;
+               goto unlock;
+       }
+-      num_entries = exfat_count_ext_entries(sb, &cdir, entry, ep);
++      num_entries = exfat_count_ext_entries(sb, &ei->dir, ei->entry, ep);
+       if (num_entries < 0) {
+               err = -EIO;
+               brelse(bh);
+@@ -819,7 +816,7 @@ static int exfat_unlink(struct inode *di
+       exfat_set_volume_dirty(sb);
+       /* update the directory entry */
+-      if (exfat_remove_entries(dir, &cdir, entry, 0, num_entries)) {
++      if (exfat_remove_entries(dir, &ei->dir, ei->entry, 0, num_entries)) {
+               err = -EIO;
+               goto unlock;
+       }
+@@ -944,18 +941,15 @@ static int exfat_rmdir(struct inode *dir
+ {
+       struct inode *inode = dentry->d_inode;
+       struct exfat_dentry *ep;
+-      struct exfat_chain cdir, clu_to_free;
++      struct exfat_chain clu_to_free;
+       struct super_block *sb = inode->i_sb;
+       struct exfat_sb_info *sbi = EXFAT_SB(sb);
+       struct exfat_inode_info *ei = EXFAT_I(inode);
+       struct buffer_head *bh;
+-      int num_entries, entry, err;
++      int num_entries, err;
+       mutex_lock(&EXFAT_SB(inode->i_sb)->s_lock);
+-      exfat_chain_dup(&cdir, &ei->dir);
+-      entry = ei->entry;
+-
+       if (ei->dir.dir == DIR_DELETED) {
+               exfat_err(sb, "abnormal access to deleted dentry");
+               err = -ENOENT;
+@@ -973,13 +967,13 @@ static int exfat_rmdir(struct inode *dir
+               goto unlock;
+       }
+-      ep = exfat_get_dentry(sb, &cdir, entry, &bh);
++      ep = exfat_get_dentry(sb, &ei->dir, ei->entry, &bh);
+       if (!ep) {
+               err = -EIO;
+               goto unlock;
+       }
+-      num_entries = exfat_count_ext_entries(sb, &cdir, entry, ep);
++      num_entries = exfat_count_ext_entries(sb, &ei->dir, ei->entry, ep);
+       if (num_entries < 0) {
+               err = -EIO;
+               brelse(bh);
+@@ -989,7 +983,7 @@ static int exfat_rmdir(struct inode *dir
+       brelse(bh);
+       exfat_set_volume_dirty(sb);
+-      err = exfat_remove_entries(dir, &cdir, entry, 0, num_entries);
++      err = exfat_remove_entries(dir, &ei->dir, ei->entry, 0, num_entries);
+       if (err) {
+               exfat_err(sb, "failed to exfat_remove_entries : err(%d)", err);
+               goto unlock;
+@@ -1015,8 +1009,8 @@ unlock:
+       return err;
+ }
+-static int exfat_rename_file(struct inode *parent_inode, struct exfat_chain *p_dir,
+-              int oldentry, struct exfat_uni_name *p_uniname,
++static int exfat_rename_file(struct inode *parent_inode,
++              struct exfat_chain *p_dir, struct exfat_uni_name *p_uniname,
+               struct exfat_inode_info *ei)
+ {
+       int ret, num_old_entries, num_new_entries;
+@@ -1025,11 +1019,12 @@ static int exfat_rename_file(struct inod
+       struct buffer_head *new_bh, *old_bh;
+       int sync = IS_DIRSYNC(parent_inode);
+-      epold = exfat_get_dentry(sb, p_dir, oldentry, &old_bh);
++      epold = exfat_get_dentry(sb, &ei->dir, ei->entry, &old_bh);
+       if (!epold)
+               return -EIO;
+-      num_old_entries = exfat_count_ext_entries(sb, p_dir, oldentry, epold);
++      num_old_entries = exfat_count_ext_entries(sb, &ei->dir, ei->entry,
++              epold);
+       if (num_old_entries < 0)
+               return -EIO;
+       num_old_entries++;
+@@ -1059,7 +1054,7 @@ static int exfat_rename_file(struct inod
+               brelse(old_bh);
+               brelse(new_bh);
+-              epold = exfat_get_dentry(sb, p_dir, oldentry + 1, &old_bh);
++              epold = exfat_get_dentry(sb, &ei->dir, ei->entry + 1, &old_bh);
+               if (!epold)
+                       return -EIO;
+               epnew = exfat_get_dentry(sb, p_dir, newentry + 1, &new_bh);
+@@ -1078,7 +1073,7 @@ static int exfat_rename_file(struct inod
+               if (ret)
+                       return ret;
+-              exfat_remove_entries(parent_inode, p_dir, oldentry, 0,
++              exfat_remove_entries(parent_inode, &ei->dir, ei->entry, 0,
+                       num_old_entries);
+               ei->dir = *p_dir;
+               ei->entry = newentry;
+@@ -1090,10 +1085,10 @@ static int exfat_rename_file(struct inod
+               exfat_update_bh(old_bh, sync);
+               brelse(old_bh);
+-              exfat_remove_entries(parent_inode, p_dir, oldentry,
++              exfat_remove_entries(parent_inode, &ei->dir, ei->entry,
+                       ES_IDX_FIRST_FILENAME + 1, num_old_entries);
+-              ret = exfat_init_ext_entry(parent_inode, p_dir, oldentry,
++              ret = exfat_init_ext_entry(parent_inode, &ei->dir, ei->entry,
+                       num_new_entries, p_uniname);
+               if (ret)
+                       return ret;
+@@ -1101,20 +1096,20 @@ static int exfat_rename_file(struct inod
+       return 0;
+ }
+-static int exfat_move_file(struct inode *parent_inode, struct exfat_chain *p_olddir,
+-              int oldentry, struct exfat_chain *p_newdir,
+-              struct exfat_uni_name *p_uniname, struct exfat_inode_info *ei)
++static int exfat_move_file(struct inode *parent_inode,
++              struct exfat_chain *p_newdir, struct exfat_uni_name *p_uniname,
++              struct exfat_inode_info *ei)
+ {
+       int ret, newentry, num_new_entries, num_old_entries;
+       struct exfat_dentry *epmov, *epnew;
+       struct super_block *sb = parent_inode->i_sb;
+       struct buffer_head *mov_bh, *new_bh;
+-      epmov = exfat_get_dentry(sb, p_olddir, oldentry, &mov_bh);
++      epmov = exfat_get_dentry(sb, &ei->dir, ei->entry, &mov_bh);
+       if (!epmov)
+               return -EIO;
+-      num_old_entries = exfat_count_ext_entries(sb, p_olddir, oldentry,
++      num_old_entries = exfat_count_ext_entries(sb, &ei->dir, ei->entry,
+               epmov);
+       if (num_old_entries < 0)
+               return -EIO;
+@@ -1142,7 +1137,7 @@ static int exfat_move_file(struct inode
+       brelse(mov_bh);
+       brelse(new_bh);
+-      epmov = exfat_get_dentry(sb, p_olddir, oldentry + 1, &mov_bh);
++      epmov = exfat_get_dentry(sb, &ei->dir, ei->entry + 1, &mov_bh);
+       if (!epmov)
+               return -EIO;
+       epnew = exfat_get_dentry(sb, p_newdir, newentry + 1, &new_bh);
+@@ -1161,7 +1156,7 @@ static int exfat_move_file(struct inode
+       if (ret)
+               return ret;
+-      exfat_remove_entries(parent_inode, p_olddir, oldentry, 0,
++      exfat_remove_entries(parent_inode, &ei->dir, ei->entry, 0,
+               num_old_entries);
+       exfat_chain_set(&ei->dir, p_newdir->dir, p_newdir->size,
+@@ -1177,8 +1172,7 @@ static int __exfat_rename(struct inode *
+               struct dentry *new_dentry)
+ {
+       int ret;
+-      int dentry;
+-      struct exfat_chain olddir, newdir;
++      struct exfat_chain newdir;
+       struct exfat_uni_name uni_name;
+       struct super_block *sb = old_parent_inode->i_sb;
+       struct exfat_sb_info *sbi = EXFAT_SB(sb);
+@@ -1196,11 +1190,6 @@ static int __exfat_rename(struct inode *
+               return -ENOENT;
+       }
+-      exfat_chain_set(&olddir, EXFAT_I(old_parent_inode)->start_clu,
+-              EXFAT_B_TO_CLU_ROUND_UP(i_size_read(old_parent_inode), sbi),
+-              EXFAT_I(old_parent_inode)->flags);
+-      dentry = ei->entry;
+-
+       /* check whether new dir is existing directory and empty */
+       if (new_inode) {
+               ret = -EIO;
+@@ -1235,35 +1224,34 @@ static int __exfat_rename(struct inode *
+       exfat_set_volume_dirty(sb);
+-      if (olddir.dir == newdir.dir)
+-              ret = exfat_rename_file(new_parent_inode, &olddir, dentry,
++      if (new_parent_inode == old_parent_inode)
++              ret = exfat_rename_file(new_parent_inode, &newdir,
+                               &uni_name, ei);
+       else
+-              ret = exfat_move_file(new_parent_inode, &olddir, dentry,
+-                              &newdir, &uni_name, ei);
++              ret = exfat_move_file(new_parent_inode, &newdir,
++                              &uni_name, ei);
+       if (!ret && new_inode) {
+-              struct exfat_chain *p_dir = &(new_ei->dir);
+-              int new_entry = new_ei->entry;
+               struct exfat_dentry *ep;
+               struct buffer_head *new_bh = NULL;
+               /* delete entries of new_dir */
+-              ep = exfat_get_dentry(sb, p_dir, new_entry, &new_bh);
++              ep = exfat_get_dentry(sb, &new_ei->dir, new_ei->entry, &new_bh);
+               if (!ep) {
+                       ret = -EIO;
+                       goto del_out;
+               }
+-              num_entries = exfat_count_ext_entries(sb, p_dir, new_entry, ep);
++              num_entries = exfat_count_ext_entries(sb, &new_ei->dir,
++                      new_ei->entry, ep);
+               if (num_entries < 0) {
+                       ret = -EIO;
+                       goto del_out;
+               }
+               brelse(new_bh);
+-              if (exfat_remove_entries(new_inode, p_dir, new_entry, 0,
+-                              num_entries + 1)) {
++              if (exfat_remove_entries(new_inode, &new_ei->dir, new_ei->entry,
++                              0, num_entries + 1)) {
+                       ret = -EIO;
+                       goto del_out;
+               }
diff --git a/queue-6.6/exfat-move-exfat_chain_set-out-of-__exfat_resolve_path.patch b/queue-6.6/exfat-move-exfat_chain_set-out-of-__exfat_resolve_path.patch
new file mode 100644 (file)
index 0000000..c50b3c8
--- /dev/null
@@ -0,0 +1,261 @@
+From stable+bounces-278571-greg=kroah.com@vger.kernel.org Tue Jul 21 13:52:40 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 07:47:42 -0400
+Subject: exfat: move exfat_chain_set() out of __exfat_resolve_path()
+To: stable@vger.kernel.org
+Cc: Yuezhang Mo <Yuezhang.Mo@sony.com>, Aoyama Wataru <wataru.aoyama@sony.com>, Daniel Palmer <daniel.palmer@sony.com>, Sungjong Seo <sj1557.seo@samsung.com>, Namjae Jeon <linkinjeon@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721114743.3689685-5-sashal@kernel.org>
+
+From: Yuezhang Mo <Yuezhang.Mo@sony.com>
+
+[ Upstream commit 0891c7313d87a1b6baf7162bc2f0d755ce70383f ]
+
+__exfat_resolve_path() mixes two functions. The first one is to
+resolve and check if the path is valid. The second one is to output
+the cluster assigned to the directory.
+
+The second one is only needed when need to traverse the directory
+entries, and calling exfat_chain_set() so early causes p_dir to be
+passed as an argument multiple times, increasing the complexity of
+the code.
+
+This commit moves the call to exfat_chain_set() before traversing
+directory entries.
+
+Signed-off-by: Yuezhang Mo <Yuezhang.Mo@sony.com>
+Reviewed-by: Aoyama Wataru <wataru.aoyama@sony.com>
+Reviewed-by: Daniel Palmer <daniel.palmer@sony.com>
+Reviewed-by: Sungjong Seo <sj1557.seo@samsung.com>
+Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
+Stable-dep-of: 942296784b2a ("exfat: preserve benign secondary entries during rename and move")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/exfat/namei.c |   68 ++++++++++++++++++++++++-------------------------------
+ 1 file changed, 30 insertions(+), 38 deletions(-)
+
+--- a/fs/exfat/namei.c
++++ b/fs/exfat/namei.c
+@@ -343,6 +343,9 @@ static int exfat_find_empty_entry(struct
+               ei->hint_femp.eidx = EXFAT_HINT_NONE;
+       }
++      exfat_chain_set(p_dir, ei->start_clu,
++                      EXFAT_B_TO_CLU(i_size_read(inode), sbi), ei->flags);
++
+       while ((dentry = exfat_search_empty_slot(sb, &hint_femp, p_dir,
+                                       num_entries)) < 0) {
+               if (dentry == -EIO)
+@@ -419,14 +422,11 @@ static int exfat_find_empty_entry(struct
+  * Zero if it was successful; otherwise nonzero.
+  */
+ static int __exfat_resolve_path(struct inode *inode, const unsigned char *path,
+-              struct exfat_chain *p_dir, struct exfat_uni_name *p_uniname,
+-              int lookup)
++              struct exfat_uni_name *p_uniname, int lookup)
+ {
+       int namelen;
+       int lossy = NLS_NAME_NO_LOSSY;
+       struct super_block *sb = inode->i_sb;
+-      struct exfat_sb_info *sbi = EXFAT_SB(sb);
+-      struct exfat_inode_info *ei = EXFAT_I(inode);
+       int pathlen = strlen(path);
+       /*
+@@ -465,24 +465,19 @@ static int __exfat_resolve_path(struct i
+       if ((lossy && !lookup) || !namelen)
+               return (lossy & NLS_NAME_OVERLEN) ? -ENAMETOOLONG : -EINVAL;
+-      exfat_chain_set(p_dir, ei->start_clu,
+-              EXFAT_B_TO_CLU(i_size_read(inode), sbi), ei->flags);
+-
+       return 0;
+ }
+ static inline int exfat_resolve_path(struct inode *inode,
+-              const unsigned char *path, struct exfat_chain *dir,
+-              struct exfat_uni_name *uni)
++              const unsigned char *path, struct exfat_uni_name *uni)
+ {
+-      return __exfat_resolve_path(inode, path, dir, uni, 0);
++      return __exfat_resolve_path(inode, path, uni, 0);
+ }
+ static inline int exfat_resolve_path_for_lookup(struct inode *inode,
+-              const unsigned char *path, struct exfat_chain *dir,
+-              struct exfat_uni_name *uni)
++              const unsigned char *path, struct exfat_uni_name *uni)
+ {
+-      return __exfat_resolve_path(inode, path, dir, uni, 1);
++      return __exfat_resolve_path(inode, path, uni, 1);
+ }
+ static inline loff_t exfat_make_i_pos(struct exfat_dir_entry *info)
+@@ -502,7 +497,7 @@ static int exfat_add_entry(struct inode
+       int clu_size = 0;
+       unsigned int start_clu = EXFAT_FREE_CLUSTER;
+-      ret = exfat_resolve_path(inode, path, p_dir, &uniname);
++      ret = exfat_resolve_path(inode, path, &uniname);
+       if (ret)
+               goto out;
+@@ -623,10 +618,13 @@ static int exfat_find(struct inode *dir,
+               return -ENOENT;
+       /* check the validity of directory name in the given pathname */
+-      ret = exfat_resolve_path_for_lookup(dir, qname->name, &cdir, &uni_name);
++      ret = exfat_resolve_path_for_lookup(dir, qname->name, &uni_name);
+       if (ret)
+               return ret;
++      exfat_chain_set(&cdir, ei->start_clu,
++              EXFAT_B_TO_CLU(i_size_read(dir), sbi), ei->flags);
++
+       /* check the validation of hint_stat and initialize it if required */
+       if (ei->version != (inode_peek_iversion_raw(dir) & 0xffffffff)) {
+               ei->hint_stat.clu = cdir.dir;
+@@ -1010,8 +1008,7 @@ unlock:
+ }
+ static int exfat_rename_file(struct inode *parent_inode,
+-              struct exfat_chain *p_dir, struct exfat_uni_name *p_uniname,
+-              struct exfat_inode_info *ei)
++              struct exfat_uni_name *p_uniname, struct exfat_inode_info *ei)
+ {
+       int ret, num_old_entries, num_new_entries;
+       struct exfat_dentry *epold, *epnew;
+@@ -1035,13 +1032,14 @@ static int exfat_rename_file(struct inod
+       if (num_old_entries < num_new_entries) {
+               int newentry;
++              struct exfat_chain dir;
+-              newentry = exfat_find_empty_entry(parent_inode, p_dir,
++              newentry = exfat_find_empty_entry(parent_inode, &dir,
+                               num_new_entries);
+               if (newentry < 0)
+                       return newentry; /* -EIO or -ENOSPC */
+-              epnew = exfat_get_dentry(sb, p_dir, newentry, &new_bh);
++              epnew = exfat_get_dentry(sb, &dir, newentry, &new_bh);
+               if (!epnew)
+                       return -EIO;
+@@ -1057,7 +1055,7 @@ static int exfat_rename_file(struct inod
+               epold = exfat_get_dentry(sb, &ei->dir, ei->entry + 1, &old_bh);
+               if (!epold)
+                       return -EIO;
+-              epnew = exfat_get_dentry(sb, p_dir, newentry + 1, &new_bh);
++              epnew = exfat_get_dentry(sb, &dir, newentry + 1, &new_bh);
+               if (!epnew) {
+                       brelse(old_bh);
+                       return -EIO;
+@@ -1068,14 +1066,14 @@ static int exfat_rename_file(struct inod
+               brelse(old_bh);
+               brelse(new_bh);
+-              ret = exfat_init_ext_entry(parent_inode, p_dir, newentry,
++              ret = exfat_init_ext_entry(parent_inode, &dir, newentry,
+                       num_new_entries, p_uniname);
+               if (ret)
+                       return ret;
+               exfat_remove_entries(parent_inode, &ei->dir, ei->entry, 0,
+                       num_old_entries);
+-              ei->dir = *p_dir;
++              ei->dir = dir;
+               ei->entry = newentry;
+       } else {
+               if (exfat_get_entry_type(epold) == TYPE_FILE) {
+@@ -1097,13 +1095,13 @@ static int exfat_rename_file(struct inod
+ }
+ static int exfat_move_file(struct inode *parent_inode,
+-              struct exfat_chain *p_newdir, struct exfat_uni_name *p_uniname,
+-              struct exfat_inode_info *ei)
++              struct exfat_uni_name *p_uniname, struct exfat_inode_info *ei)
+ {
+       int ret, newentry, num_new_entries, num_old_entries;
+       struct exfat_dentry *epmov, *epnew;
+       struct super_block *sb = parent_inode->i_sb;
+       struct buffer_head *mov_bh, *new_bh;
++      struct exfat_chain newdir;
+       epmov = exfat_get_dentry(sb, &ei->dir, ei->entry, &mov_bh);
+       if (!epmov)
+@@ -1119,12 +1117,12 @@ static int exfat_move_file(struct inode
+       if (num_new_entries < 0)
+               return num_new_entries;
+-      newentry = exfat_find_empty_entry(parent_inode, p_newdir,
++      newentry = exfat_find_empty_entry(parent_inode, &newdir,
+                       num_new_entries);
+       if (newentry < 0)
+               return newentry; /* -EIO or -ENOSPC */
+-      epnew = exfat_get_dentry(sb, p_newdir, newentry, &new_bh);
++      epnew = exfat_get_dentry(sb, &newdir, newentry, &new_bh);
+       if (!epnew)
+               return -EIO;
+@@ -1140,7 +1138,7 @@ static int exfat_move_file(struct inode
+       epmov = exfat_get_dentry(sb, &ei->dir, ei->entry + 1, &mov_bh);
+       if (!epmov)
+               return -EIO;
+-      epnew = exfat_get_dentry(sb, p_newdir, newentry + 1, &new_bh);
++      epnew = exfat_get_dentry(sb, &newdir, newentry + 1, &new_bh);
+       if (!epnew) {
+               brelse(mov_bh);
+               return -EIO;
+@@ -1151,7 +1149,7 @@ static int exfat_move_file(struct inode
+       brelse(mov_bh);
+       brelse(new_bh);
+-      ret = exfat_init_ext_entry(parent_inode, p_newdir, newentry,
++      ret = exfat_init_ext_entry(parent_inode, &newdir, newentry,
+               num_new_entries, p_uniname);
+       if (ret)
+               return ret;
+@@ -1159,9 +1157,7 @@ static int exfat_move_file(struct inode
+       exfat_remove_entries(parent_inode, &ei->dir, ei->entry, 0,
+               num_old_entries);
+-      exfat_chain_set(&ei->dir, p_newdir->dir, p_newdir->size,
+-              p_newdir->flags);
+-
++      ei->dir = newdir;
+       ei->entry = newentry;
+       return 0;
+ }
+@@ -1172,7 +1168,6 @@ static int __exfat_rename(struct inode *
+               struct dentry *new_dentry)
+ {
+       int ret;
+-      struct exfat_chain newdir;
+       struct exfat_uni_name uni_name;
+       struct super_block *sb = old_parent_inode->i_sb;
+       struct exfat_sb_info *sbi = EXFAT_SB(sb);
+@@ -1217,19 +1212,16 @@ static int __exfat_rename(struct inode *
+       }
+       /* check the validity of directory name in the given new pathname */
+-      ret = exfat_resolve_path(new_parent_inode, new_path, &newdir,
+-                      &uni_name);
++      ret = exfat_resolve_path(new_parent_inode, new_path, &uni_name);
+       if (ret)
+               goto out;
+       exfat_set_volume_dirty(sb);
+       if (new_parent_inode == old_parent_inode)
+-              ret = exfat_rename_file(new_parent_inode, &newdir,
+-                              &uni_name, ei);
++              ret = exfat_rename_file(new_parent_inode, &uni_name, ei);
+       else
+-              ret = exfat_move_file(new_parent_inode, &newdir,
+-                              &uni_name, ei);
++              ret = exfat_move_file(new_parent_inode, &uni_name, ei);
+       if (!ret && new_inode) {
+               struct exfat_dentry *ep;
diff --git a/queue-6.6/exfat-move-free-cluster-out-of-exfat_init_ext_entry.patch b/queue-6.6/exfat-move-free-cluster-out-of-exfat_init_ext_entry.patch
new file mode 100644 (file)
index 0000000..e906f7d
--- /dev/null
@@ -0,0 +1,64 @@
+From stable+bounces-278567-greg=kroah.com@vger.kernel.org Tue Jul 21 13:52:00 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 07:47:38 -0400
+Subject: exfat: move free cluster out of exfat_init_ext_entry()
+To: stable@vger.kernel.org
+Cc: Yuezhang Mo <Yuezhang.Mo@sony.com>, Andy Wu <Andy.Wu@sony.com>, Aoyama Wataru <wataru.aoyama@sony.com>, Sungjong Seo <sj1557.seo@samsung.com>, Namjae Jeon <linkinjeon@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721114743.3689685-1-sashal@kernel.org>
+
+From: Yuezhang Mo <Yuezhang.Mo@sony.com>
+
+[ Upstream commit 4e1aa22fea106014397455506d1383d519c4d3d1 ]
+
+exfat_init_ext_entry() is an init function, it's a bit strange
+to free cluster in it. And the argument 'inode' will be removed
+from exfat_init_ext_entry(). So this commit changes to free the
+cluster in exfat_remove_entries().
+
+Code refinement, no functional changes.
+
+Signed-off-by: Yuezhang Mo <Yuezhang.Mo@sony.com>
+Reviewed-by: Andy Wu <Andy.Wu@sony.com>
+Reviewed-by: Aoyama Wataru <wataru.aoyama@sony.com>
+Reviewed-by: Sungjong Seo <sj1557.seo@samsung.com>
+Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
+Stable-dep-of: 942296784b2a ("exfat: preserve benign secondary entries during rename and move")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/exfat/dir.c   |    3 ---
+ fs/exfat/namei.c |    7 ++++---
+ 2 files changed, 4 insertions(+), 6 deletions(-)
+
+--- a/fs/exfat/dir.c
++++ b/fs/exfat/dir.c
+@@ -584,9 +584,6 @@ int exfat_init_ext_entry(struct inode *i
+               if (!ep)
+                       return -EIO;
+-              if (exfat_get_entry_type(ep) & TYPE_BENIGN_SEC)
+-                      exfat_free_benign_secondary_clusters(inode, ep);
+-
+               exfat_init_name_entry(ep, uniname);
+               exfat_update_bh(bh, sync);
+               brelse(bh);
+--- a/fs/exfat/namei.c
++++ b/fs/exfat/namei.c
+@@ -1089,13 +1089,14 @@ static int exfat_rename_file(struct inod
+               }
+               exfat_update_bh(old_bh, sync);
+               brelse(old_bh);
++
++              exfat_remove_entries(inode, p_dir, oldentry,
++                      ES_IDX_FIRST_FILENAME + 1, num_old_entries);
++
+               ret = exfat_init_ext_entry(inode, p_dir, oldentry,
+                       num_new_entries, p_uniname);
+               if (ret)
+                       return ret;
+-
+-              exfat_remove_entries(inode, p_dir, oldentry, num_new_entries,
+-                      num_old_entries);
+       }
+       return 0;
+ }
diff --git a/queue-6.6/exfat-preserve-benign-secondary-entries-during-rename-and-move.patch b/queue-6.6/exfat-preserve-benign-secondary-entries-during-rename-and-move.patch
new file mode 100644 (file)
index 0000000..b6852d2
--- /dev/null
@@ -0,0 +1,463 @@
+From stable+bounces-278572-greg=kroah.com@vger.kernel.org Tue Jul 21 13:52:45 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 07:47:43 -0400
+Subject: exfat: preserve benign secondary entries during rename and move
+To: stable@vger.kernel.org
+Cc: Rochan Avlur <rochan.avlur@gmail.com>, Yuezhang Mo <Yuezhang.Mo@sony.com>, Namjae Jeon <linkinjeon@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721114743.3689685-6-sashal@kernel.org>
+
+From: Rochan Avlur <rochan.avlur@gmail.com>
+
+[ Upstream commit 942296784b2a9439651750c42f540bf2579b330f ]
+
+Commit 8258ef28001a ("exfat: handle unreconized benign secondary
+entries") added cluster freeing for benign secondary entries inside
+exfat_remove_entries().  However, exfat_remove_entries() is also called
+from the rename and move paths (exfat_rename_file and exfat_move_file),
+where the old entry set is being relocated rather than deleted.  This
+causes benign secondary entries such as vendor extension entries to be
+silently destroyed on rename or cross-directory move, violating the
+exFAT spec requirement (section 8.2) that implementations preserve
+unrecognized benign secondary entries.
+
+Fix this by adding a free_benign parameter to exfat_remove_entries()
+so callers can suppress cluster freeing during relocation, and
+extending exfat_init_ext_entry() to copy trailing benign secondary
+entries from the old entry set into the new one internally.  Also
+clean up the error paths to delete newly allocated entries on failure.
+
+Fixes: 8258ef28001a ("exfat: handle unreconized benign secondary entries")
+Cc: stable@vger.kernel.org
+Link: https://lore.kernel.org/linux-fsdevel/CAG7tbBV--waov7XVu2FHQEc6paR92dufS=em9DW5Kzsrpu3iQg@mail.gmail.com/
+Signed-off-by: Rochan Avlur <rochan.avlur@gmail.com>
+Reviewed-by: Yuezhang Mo <Yuezhang.Mo@sony.com>
+Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/exfat/dir.c      |   45 ++++++++++--
+ fs/exfat/exfat_fs.h |    5 -
+ fs/exfat/namei.c    |  191 ++++++++++++++++++++++++++++++++++------------------
+ 3 files changed, 169 insertions(+), 72 deletions(-)
+
+--- a/fs/exfat/dir.c
++++ b/fs/exfat/dir.c
+@@ -552,21 +552,43 @@ static void exfat_free_benign_secondary_
+       exfat_free_cluster(inode, &dir);
+ }
++/*
++ * exfat_init_ext_entry - initialize extension entries of a dentry set
++ * @inode:       parent directory inode
++ * @p_dir:       directory the dentry set is in
++ * @entry:       index of the first entry of the dentry set
++ * @num_entries: number of entries excluding benign secondary entries
++ * @p_uniname:   filename to store
++ * @old_es:      optional source entry set with benign secondary entries, or NULL
++ * @num_extra:   number of benign secondary entries to copy from @old_es
++ *
++ * Set up the file, stream extension, and filename entries, optionally
++ * preserving @num_extra benign secondary entries from @old_es.  @old_es
++ * may refer to the same dentry set that is being initialized.
++ */
+ int exfat_init_ext_entry(struct inode *inode, struct exfat_chain *p_dir,
+-              int entry, int num_entries, struct exfat_uni_name *p_uniname)
++              int entry, int num_entries, struct exfat_uni_name *p_uniname,
++              struct exfat_entry_set_cache *old_es, int num_extra)
+ {
+       struct super_block *sb = inode->i_sb;
+-      int i;
++      int i, src_start = 0;
+       unsigned short *uniname = p_uniname->name;
+       struct exfat_dentry *ep;
+       struct buffer_head *bh;
+       int sync = IS_DIRSYNC(inode);
++      if (WARN_ON(num_extra < 0 || (num_extra && (!old_es ||
++                  old_es->num_entries < ES_IDX_FIRST_FILENAME + num_extra))))
++              num_extra = 0;
++
++      if (old_es && num_extra > 0)
++              src_start = old_es->num_entries - num_extra;
++
+       ep = exfat_get_dentry(sb, p_dir, entry, &bh);
+       if (!ep)
+               return -EIO;
+-      ep->dentry.file.num_ext = (unsigned char)(num_entries - 1);
++      ep->dentry.file.num_ext = (unsigned char)(num_entries - 1 + num_extra);
+       exfat_update_bh(bh, sync);
+       brelse(bh);
+@@ -579,6 +601,19 @@ int exfat_init_ext_entry(struct inode *i
+       exfat_update_bh(bh, sync);
+       brelse(bh);
++      if (old_es && num_extra > 0) {
++              for (i = 0; i < num_extra; i++) {
++                      ep = exfat_get_dentry(sb, p_dir,
++                                            entry + num_entries + i, &bh);
++                      if (!ep)
++                              return -EIO;
++
++                      *ep = *exfat_get_dentry_cached(old_es, src_start + i);
++                      exfat_update_bh(bh, sync);
++                      brelse(bh);
++              }
++      }
++
+       for (i = EXFAT_FIRST_CLUSTER; i < num_entries; i++) {
+               ep = exfat_get_dentry(sb, p_dir, entry + i, &bh);
+               if (!ep)
+@@ -595,7 +630,7 @@ int exfat_init_ext_entry(struct inode *i
+ }
+ int exfat_remove_entries(struct inode *inode, struct exfat_chain *p_dir,
+-              int entry, int order, int num_entries)
++              int entry, int order, int num_entries, bool free_benign)
+ {
+       struct super_block *sb = inode->i_sb;
+       int i;
+@@ -607,7 +642,7 @@ int exfat_remove_entries(struct inode *i
+               if (!ep)
+                       return -EIO;
+-              if (exfat_get_entry_type(ep) & TYPE_BENIGN_SEC)
++              if (free_benign && (exfat_get_entry_type(ep) & TYPE_BENIGN_SEC))
+                       exfat_free_benign_secondary_clusters(inode, ep);
+               exfat_set_entry_type(ep, TYPE_DELETED);
+--- a/fs/exfat/exfat_fs.h
++++ b/fs/exfat/exfat_fs.h
+@@ -480,9 +480,10 @@ int exfat_init_dir_entry(struct inode *i
+               int entry, unsigned int type, unsigned int start_clu,
+               unsigned long long size);
+ int exfat_init_ext_entry(struct inode *inode, struct exfat_chain *p_dir,
+-              int entry, int num_entries, struct exfat_uni_name *p_uniname);
++              int entry, int num_entries, struct exfat_uni_name *p_uniname,
++              struct exfat_entry_set_cache *old_es, int num_extra);
+ int exfat_remove_entries(struct inode *inode, struct exfat_chain *p_dir,
+-              int entry, int order, int num_entries);
++              int entry, int order, int num_entries, bool free_benign);
+ int exfat_update_dir_chksum(struct inode *inode, struct exfat_chain *p_dir,
+               int entry);
+ void exfat_update_dir_chksum_with_entry_set(struct exfat_entry_set_cache *es);
+--- a/fs/exfat/namei.c
++++ b/fs/exfat/namei.c
+@@ -531,7 +531,8 @@ static int exfat_add_entry(struct inode
+       if (ret)
+               goto out;
+-      ret = exfat_init_ext_entry(inode, p_dir, dentry, num_entries, &uniname);
++      ret = exfat_init_ext_entry(inode, p_dir, dentry, num_entries, &uniname,
++              NULL, 0);
+       if (ret)
+               goto out;
+@@ -814,7 +815,8 @@ static int exfat_unlink(struct inode *di
+       exfat_set_volume_dirty(sb);
+       /* update the directory entry */
+-      if (exfat_remove_entries(dir, &ei->dir, ei->entry, 0, num_entries)) {
++      if (exfat_remove_entries(dir, &ei->dir, ei->entry, 0, num_entries,
++                      true)) {
+               err = -EIO;
+               goto unlock;
+       }
+@@ -981,7 +983,8 @@ static int exfat_rmdir(struct inode *dir
+       brelse(bh);
+       exfat_set_volume_dirty(sb);
+-      err = exfat_remove_entries(dir, &ei->dir, ei->entry, 0, num_entries);
++      err = exfat_remove_entries(dir, &ei->dir, ei->entry, 0, num_entries,
++                      true);
+       if (err) {
+               exfat_err(sb, "failed to exfat_remove_entries : err(%d)", err);
+               goto unlock;
+@@ -1007,41 +1010,67 @@ unlock:
+       return err;
+ }
++/*
++ * Count benign secondary entries beyond the filename entries.
++ * Returns the count, or -EIO if the entry set is inconsistent.
++ */
++static int exfat_count_extra_entries(struct exfat_entry_set_cache *es)
++{
++      struct exfat_dentry *stream;
++      unsigned int name_entries;
++      int extra;
++
++      stream = exfat_get_dentry_cached(es, ES_IDX_STREAM);
++      name_entries = EXFAT_FILENAME_ENTRY_NUM(stream->dentry.stream.name_len);
++      extra = es->num_entries - (ES_IDX_FIRST_FILENAME + name_entries);
++
++      return extra >= 0 ? extra : -EIO;
++}
++
+ static int exfat_rename_file(struct inode *parent_inode,
+               struct exfat_uni_name *p_uniname, struct exfat_inode_info *ei)
+ {
+       int ret, num_old_entries, num_new_entries;
+       struct exfat_dentry *epold, *epnew;
+       struct super_block *sb = parent_inode->i_sb;
++      struct exfat_entry_set_cache old_es;
+       struct buffer_head *new_bh, *old_bh;
+       int sync = IS_DIRSYNC(parent_inode);
+-
+-      epold = exfat_get_dentry(sb, &ei->dir, ei->entry, &old_bh);
+-      if (!epold)
+-              return -EIO;
+-
+-      num_old_entries = exfat_count_ext_entries(sb, &ei->dir, ei->entry,
+-              epold);
+-      if (num_old_entries < 0)
+-              return -EIO;
+-      num_old_entries++;
++      unsigned int num_extra_entries, num_total_entries;
+       num_new_entries = exfat_calc_num_entries(p_uniname);
+       if (num_new_entries < 0)
+               return num_new_entries;
+-      if (num_old_entries < num_new_entries) {
++      ret = exfat_get_dentry_set_by_ei(&old_es, sb, ei);
++      if (ret)
++              return -EIO;
++
++      num_old_entries = old_es.num_entries;
++
++      ret = exfat_count_extra_entries(&old_es);
++      if (ret < 0)
++              goto put_old_es;
++      num_extra_entries = ret;
++      num_total_entries = num_new_entries + num_extra_entries;
++
++      if (num_old_entries < num_total_entries) {
+               int newentry;
+               struct exfat_chain dir;
+               newentry = exfat_find_empty_entry(parent_inode, &dir,
+-                              num_new_entries);
+-              if (newentry < 0)
+-                      return newentry; /* -EIO or -ENOSPC */
++                              num_total_entries);
++              if (newentry < 0) {
++                      ret = newentry; /* -EIO or -ENOSPC */
++                      goto put_old_es;
++              }
++              epold = exfat_get_dentry_cached(&old_es, ES_IDX_FILE);
+               epnew = exfat_get_dentry(sb, &dir, newentry, &new_bh);
+-              if (!epnew)
+-                      return -EIO;
++              if (!epnew) {
++                      ret = -EIO;
++                      goto put_old_es;
++              }
+               *epnew = *epold;
+               if (exfat_get_entry_type(epnew) == TYPE_FILE) {
+@@ -1049,33 +1078,43 @@ static int exfat_rename_file(struct inod
+                       ei->attr |= ATTR_ARCHIVE;
+               }
+               exfat_update_bh(new_bh, sync);
+-              brelse(old_bh);
+               brelse(new_bh);
+-              epold = exfat_get_dentry(sb, &ei->dir, ei->entry + 1, &old_bh);
+-              if (!epold)
+-                      return -EIO;
++              epold = exfat_get_dentry_cached(&old_es, ES_IDX_STREAM);
+               epnew = exfat_get_dentry(sb, &dir, newentry + 1, &new_bh);
+               if (!epnew) {
+-                      brelse(old_bh);
+-                      return -EIO;
++                      /* Best-effort delete to avoid duplicate entries */
++                      exfat_remove_entries(parent_inode, &dir, newentry, 0,
++                              num_total_entries, false);
++                      ret = -EIO;
++                      goto put_old_es;
+               }
+               *epnew = *epold;
+               exfat_update_bh(new_bh, sync);
+-              brelse(old_bh);
+               brelse(new_bh);
+               ret = exfat_init_ext_entry(parent_inode, &dir, newentry,
+-                      num_new_entries, p_uniname);
+-              if (ret)
+-                      return ret;
++                      num_new_entries, p_uniname, &old_es,
++                      num_extra_entries);
++              if (ret) {
++                      /* Best-effort delete to avoid duplicate entries */
++                      exfat_remove_entries(parent_inode, &dir, newentry, 0,
++                              num_total_entries, false);
++                      goto put_old_es;
++              }
+               exfat_remove_entries(parent_inode, &ei->dir, ei->entry, 0,
+-                      num_old_entries);
++                      num_old_entries, false);
+               ei->dir = dir;
+               ei->entry = newentry;
+       } else {
++              epold = exfat_get_dentry(sb, &ei->dir, ei->entry, &old_bh);
++              if (!epold) {
++                      ret = -EIO;
++                      goto put_old_es;
++              }
++
+               if (exfat_get_entry_type(epold) == TYPE_FILE) {
+                       epold->dentry.file.attr |= cpu_to_le16(ATTR_ARCHIVE);
+                       ei->attr |= ATTR_ARCHIVE;
+@@ -1083,15 +1122,21 @@ static int exfat_rename_file(struct inod
+               exfat_update_bh(old_bh, sync);
+               brelse(old_bh);
+-              exfat_remove_entries(parent_inode, &ei->dir, ei->entry,
+-                      ES_IDX_FIRST_FILENAME + 1, num_old_entries);
+-
+               ret = exfat_init_ext_entry(parent_inode, &ei->dir, ei->entry,
+-                      num_new_entries, p_uniname);
++                      num_new_entries, p_uniname, &old_es,
++                      num_extra_entries);
+               if (ret)
+-                      return ret;
++                      goto put_old_es;
++
++              /* Mark excess old entries as deleted (in-place shrink) */
++              exfat_remove_entries(parent_inode, &ei->dir, ei->entry,
++                      num_total_entries, num_old_entries, false);
+       }
+-      return 0;
++      ret = 0;
++
++put_old_es:
++      exfat_put_dentry_set(&old_es, false);
++      return ret;
+ }
+ static int exfat_move_file(struct inode *parent_inode,
+@@ -1100,31 +1145,40 @@ static int exfat_move_file(struct inode
+       int ret, newentry, num_new_entries, num_old_entries;
+       struct exfat_dentry *epmov, *epnew;
+       struct super_block *sb = parent_inode->i_sb;
+-      struct buffer_head *mov_bh, *new_bh;
++      struct exfat_entry_set_cache mov_es;
++      struct buffer_head *new_bh;
+       struct exfat_chain newdir;
+-
+-      epmov = exfat_get_dentry(sb, &ei->dir, ei->entry, &mov_bh);
+-      if (!epmov)
+-              return -EIO;
+-
+-      num_old_entries = exfat_count_ext_entries(sb, &ei->dir, ei->entry,
+-              epmov);
+-      if (num_old_entries < 0)
+-              return -EIO;
+-      num_old_entries++;
++      unsigned int num_extra_entries, num_total_entries;
+       num_new_entries = exfat_calc_num_entries(p_uniname);
+       if (num_new_entries < 0)
+               return num_new_entries;
++      ret = exfat_get_dentry_set_by_ei(&mov_es, sb, ei);
++      if (ret)
++              return -EIO;
++
++      num_old_entries = mov_es.num_entries;
++
++      ret = exfat_count_extra_entries(&mov_es);
++      if (ret < 0)
++              goto put_mov_es;
++      num_extra_entries = ret;
++      num_total_entries = num_new_entries + num_extra_entries;
++
+       newentry = exfat_find_empty_entry(parent_inode, &newdir,
+-                      num_new_entries);
+-      if (newentry < 0)
+-              return newentry; /* -EIO or -ENOSPC */
++                      num_total_entries);
++      if (newentry < 0) {
++              ret = newentry; /* -EIO or -ENOSPC */
++              goto put_mov_es;
++      }
++      epmov = exfat_get_dentry_cached(&mov_es, ES_IDX_FILE);
+       epnew = exfat_get_dentry(sb, &newdir, newentry, &new_bh);
+-      if (!epnew)
+-              return -EIO;
++      if (!epnew) {
++              ret = -EIO;
++              goto put_mov_es;
++      }
+       *epnew = *epmov;
+       if (exfat_get_entry_type(epnew) == TYPE_FILE) {
+@@ -1132,34 +1186,41 @@ static int exfat_move_file(struct inode
+               ei->attr |= ATTR_ARCHIVE;
+       }
+       exfat_update_bh(new_bh, IS_DIRSYNC(parent_inode));
+-      brelse(mov_bh);
+       brelse(new_bh);
+-      epmov = exfat_get_dentry(sb, &ei->dir, ei->entry + 1, &mov_bh);
+-      if (!epmov)
+-              return -EIO;
++      epmov = exfat_get_dentry_cached(&mov_es, ES_IDX_STREAM);
+       epnew = exfat_get_dentry(sb, &newdir, newentry + 1, &new_bh);
+       if (!epnew) {
+-              brelse(mov_bh);
+-              return -EIO;
++              /* Best-effort delete to avoid duplicate entries */
++              exfat_remove_entries(parent_inode, &newdir, newentry, 0,
++                      num_total_entries, false);
++              ret = -EIO;
++              goto put_mov_es;
+       }
+       *epnew = *epmov;
+       exfat_update_bh(new_bh, IS_DIRSYNC(parent_inode));
+-      brelse(mov_bh);
+       brelse(new_bh);
+       ret = exfat_init_ext_entry(parent_inode, &newdir, newentry,
+-              num_new_entries, p_uniname);
+-      if (ret)
+-              return ret;
++              num_new_entries, p_uniname, &mov_es, num_extra_entries);
++      if (ret) {
++              /* Best-effort delete to avoid duplicate entries */
++              exfat_remove_entries(parent_inode, &newdir, newentry, 0,
++                      num_total_entries, false);
++              goto put_mov_es;
++      }
+       exfat_remove_entries(parent_inode, &ei->dir, ei->entry, 0,
+-              num_old_entries);
++              num_old_entries, false);
+       ei->dir = newdir;
+       ei->entry = newentry;
+-      return 0;
++      ret = 0;
++
++put_mov_es:
++      exfat_put_dentry_set(&mov_es, false);
++      return ret;
+ }
+ /* rename or move a old file into a new file */
+@@ -1243,7 +1304,7 @@ static int __exfat_rename(struct inode *
+               brelse(new_bh);
+               if (exfat_remove_entries(new_inode, &new_ei->dir, new_ei->entry,
+-                              0, num_entries + 1)) {
++                              0, num_entries + 1, true)) {
+                       ret = -EIO;
+                       goto del_out;
+               }
diff --git a/queue-6.6/exfat-remove-unnecessary-read-entry-in-__exfat_rename.patch b/queue-6.6/exfat-remove-unnecessary-read-entry-in-__exfat_rename.patch
new file mode 100644 (file)
index 0000000..0d54d78
--- /dev/null
@@ -0,0 +1,88 @@
+From stable+bounces-278568-greg=kroah.com@vger.kernel.org Tue Jul 21 13:52:13 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 07:47:39 -0400
+Subject: exfat: remove unnecessary read entry in __exfat_rename()
+To: stable@vger.kernel.org
+Cc: Yuezhang Mo <Yuezhang.Mo@sony.com>, Aoyama Wataru <wataru.aoyama@sony.com>, Daniel Palmer <daniel.palmer@sony.com>, Sungjong Seo <sj1557.seo@samsung.com>, Namjae Jeon <linkinjeon@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721114743.3689685-2-sashal@kernel.org>
+
+From: Yuezhang Mo <Yuezhang.Mo@sony.com>
+
+[ Upstream commit 30ef0e0d7ff5b6dceda19d18a85d9d72a4909784 ]
+
+To determine whether it is a directory, there is no need to read its
+directory entry, just use S_ISDIR(inode->i_mode).
+
+Signed-off-by: Yuezhang Mo <Yuezhang.Mo@sony.com>
+Reviewed-by: Aoyama Wataru <wataru.aoyama@sony.com>
+Reviewed-by: Daniel Palmer <daniel.palmer@sony.com>
+Reviewed-by: Sungjong Seo <sj1557.seo@samsung.com>
+Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
+Stable-dep-of: 942296784b2a ("exfat: preserve benign secondary entries during rename and move")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/exfat/namei.c |   23 +++++++----------------
+ 1 file changed, 7 insertions(+), 16 deletions(-)
+
+--- a/fs/exfat/namei.c
++++ b/fs/exfat/namei.c
+@@ -1177,18 +1177,13 @@ static int __exfat_rename(struct inode *
+       int ret;
+       int dentry;
+       struct exfat_chain olddir, newdir;
+-      struct exfat_chain *p_dir = NULL;
+       struct exfat_uni_name uni_name;
+-      struct exfat_dentry *ep;
+       struct super_block *sb = old_parent_inode->i_sb;
+       struct exfat_sb_info *sbi = EXFAT_SB(sb);
+       const unsigned char *new_path = new_dentry->d_name.name;
+       struct inode *new_inode = new_dentry->d_inode;
+       int num_entries;
+       struct exfat_inode_info *new_ei = NULL;
+-      unsigned int new_entry_type = TYPE_UNUSED;
+-      int new_entry = 0;
+-      struct buffer_head *new_bh = NULL;
+       /* check the validity of pointer parameters */
+       if (new_path == NULL || strlen(new_path) == 0)
+@@ -1214,17 +1209,8 @@ static int __exfat_rename(struct inode *
+                       goto out;
+               }
+-              p_dir = &(new_ei->dir);
+-              new_entry = new_ei->entry;
+-              ep = exfat_get_dentry(sb, p_dir, new_entry, &new_bh);
+-              if (!ep)
+-                      goto out;
+-
+-              new_entry_type = exfat_get_entry_type(ep);
+-              brelse(new_bh);
+-
+               /* if new_inode exists, update ei */
+-              if (new_entry_type == TYPE_DIR) {
++              if (S_ISDIR(new_inode->i_mode)) {
+                       struct exfat_chain new_clu;
+                       new_clu.dir = new_ei->start_clu;
+@@ -1255,6 +1241,11 @@ static int __exfat_rename(struct inode *
+                               &newdir, &uni_name, ei);
+       if (!ret && new_inode) {
++              struct exfat_chain *p_dir = &(new_ei->dir);
++              int new_entry = new_ei->entry;
++              struct exfat_dentry *ep;
++              struct buffer_head *new_bh = NULL;
++
+               /* delete entries of new_dir */
+               ep = exfat_get_dentry(sb, p_dir, new_entry, &new_bh);
+               if (!ep) {
+@@ -1276,7 +1267,7 @@ static int __exfat_rename(struct inode *
+               }
+               /* Free the clusters if new_inode is a dir(as if exfat_rmdir) */
+-              if (new_entry_type == TYPE_DIR &&
++              if (S_ISDIR(new_inode->i_mode) &&
+                   new_ei->start_clu != EXFAT_EOF_CLUSTER) {
+                       /* new_ei, new_clu_to_free */
+                       struct exfat_chain new_clu_to_free;
diff --git a/queue-6.6/exfat-rename-argument-name-for-exfat_move_file-and-exfat_rename_file.patch b/queue-6.6/exfat-rename-argument-name-for-exfat_move_file-and-exfat_rename_file.patch
new file mode 100644 (file)
index 0000000..4f4547d
--- /dev/null
@@ -0,0 +1,149 @@
+From stable+bounces-278569-greg=kroah.com@vger.kernel.org Tue Jul 21 13:47:54 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 07:47:40 -0400
+Subject: exfat: rename argument name for exfat_move_file and exfat_rename_file
+To: stable@vger.kernel.org
+Cc: Yuezhang Mo <Yuezhang.Mo@sony.com>, Sungjong Seo <sj1557.seo@samsung.com>, Namjae Jeon <linkinjeon@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721114743.3689685-3-sashal@kernel.org>
+
+From: Yuezhang Mo <Yuezhang.Mo@sony.com>
+
+[ Upstream commit 06a2b0b3b490a6103376652c01c3ac6e8e22e654 ]
+
+In this exfat implementation, the relationship between inode and ei
+is ei=EXFAT_I(inode). However, in the arguments of exfat_move_file()
+and exfat_rename_file(), argument 'inode' indicates the parent
+directory, but argument 'ei' indicates the target file to be renamed.
+They do not have the above relationship, which is not friendly to code
+readers.
+
+So this commit renames 'inode' to 'parent_inode', making the argument
+name match its role.
+
+Signed-off-by: Yuezhang Mo <Yuezhang.Mo@sony.com>
+Reviewed-by: Sungjong Seo <sj1557.seo@samsung.com>
+Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
+Stable-dep-of: 942296784b2a ("exfat: preserve benign secondary entries during rename and move")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/exfat/namei.c |   36 +++++++++++++++++++-----------------
+ 1 file changed, 19 insertions(+), 17 deletions(-)
+
+--- a/fs/exfat/namei.c
++++ b/fs/exfat/namei.c
+@@ -1015,15 +1015,15 @@ unlock:
+       return err;
+ }
+-static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir,
++static int exfat_rename_file(struct inode *parent_inode, struct exfat_chain *p_dir,
+               int oldentry, struct exfat_uni_name *p_uniname,
+               struct exfat_inode_info *ei)
+ {
+       int ret, num_old_entries, num_new_entries;
+       struct exfat_dentry *epold, *epnew;
+-      struct super_block *sb = inode->i_sb;
++      struct super_block *sb = parent_inode->i_sb;
+       struct buffer_head *new_bh, *old_bh;
+-      int sync = IS_DIRSYNC(inode);
++      int sync = IS_DIRSYNC(parent_inode);
+       epold = exfat_get_dentry(sb, p_dir, oldentry, &old_bh);
+       if (!epold)
+@@ -1041,8 +1041,8 @@ static int exfat_rename_file(struct inod
+       if (num_old_entries < num_new_entries) {
+               int newentry;
+-              newentry =
+-                      exfat_find_empty_entry(inode, p_dir, num_new_entries);
++              newentry = exfat_find_empty_entry(parent_inode, p_dir,
++                              num_new_entries);
+               if (newentry < 0)
+                       return newentry; /* -EIO or -ENOSPC */
+@@ -1073,12 +1073,12 @@ static int exfat_rename_file(struct inod
+               brelse(old_bh);
+               brelse(new_bh);
+-              ret = exfat_init_ext_entry(inode, p_dir, newentry,
++              ret = exfat_init_ext_entry(parent_inode, p_dir, newentry,
+                       num_new_entries, p_uniname);
+               if (ret)
+                       return ret;
+-              exfat_remove_entries(inode, p_dir, oldentry, 0,
++              exfat_remove_entries(parent_inode, p_dir, oldentry, 0,
+                       num_old_entries);
+               ei->dir = *p_dir;
+               ei->entry = newentry;
+@@ -1090,10 +1090,10 @@ static int exfat_rename_file(struct inod
+               exfat_update_bh(old_bh, sync);
+               brelse(old_bh);
+-              exfat_remove_entries(inode, p_dir, oldentry,
++              exfat_remove_entries(parent_inode, p_dir, oldentry,
+                       ES_IDX_FIRST_FILENAME + 1, num_old_entries);
+-              ret = exfat_init_ext_entry(inode, p_dir, oldentry,
++              ret = exfat_init_ext_entry(parent_inode, p_dir, oldentry,
+                       num_new_entries, p_uniname);
+               if (ret)
+                       return ret;
+@@ -1101,13 +1101,13 @@ static int exfat_rename_file(struct inod
+       return 0;
+ }
+-static int exfat_move_file(struct inode *inode, struct exfat_chain *p_olddir,
++static int exfat_move_file(struct inode *parent_inode, struct exfat_chain *p_olddir,
+               int oldentry, struct exfat_chain *p_newdir,
+               struct exfat_uni_name *p_uniname, struct exfat_inode_info *ei)
+ {
+       int ret, newentry, num_new_entries, num_old_entries;
+       struct exfat_dentry *epmov, *epnew;
+-      struct super_block *sb = inode->i_sb;
++      struct super_block *sb = parent_inode->i_sb;
+       struct buffer_head *mov_bh, *new_bh;
+       epmov = exfat_get_dentry(sb, p_olddir, oldentry, &mov_bh);
+@@ -1124,7 +1124,8 @@ static int exfat_move_file(struct inode
+       if (num_new_entries < 0)
+               return num_new_entries;
+-      newentry = exfat_find_empty_entry(inode, p_newdir, num_new_entries);
++      newentry = exfat_find_empty_entry(parent_inode, p_newdir,
++                      num_new_entries);
+       if (newentry < 0)
+               return newentry; /* -EIO or -ENOSPC */
+@@ -1137,7 +1138,7 @@ static int exfat_move_file(struct inode
+               epnew->dentry.file.attr |= cpu_to_le16(ATTR_ARCHIVE);
+               ei->attr |= ATTR_ARCHIVE;
+       }
+-      exfat_update_bh(new_bh, IS_DIRSYNC(inode));
++      exfat_update_bh(new_bh, IS_DIRSYNC(parent_inode));
+       brelse(mov_bh);
+       brelse(new_bh);
+@@ -1151,16 +1152,17 @@ static int exfat_move_file(struct inode
+       }
+       *epnew = *epmov;
+-      exfat_update_bh(new_bh, IS_DIRSYNC(inode));
++      exfat_update_bh(new_bh, IS_DIRSYNC(parent_inode));
+       brelse(mov_bh);
+       brelse(new_bh);
+-      ret = exfat_init_ext_entry(inode, p_newdir, newentry, num_new_entries,
+-              p_uniname);
++      ret = exfat_init_ext_entry(parent_inode, p_newdir, newentry,
++              num_new_entries, p_uniname);
+       if (ret)
+               return ret;
+-      exfat_remove_entries(inode, p_olddir, oldentry, 0, num_old_entries);
++      exfat_remove_entries(parent_inode, p_olddir, oldentry, 0,
++              num_old_entries);
+       exfat_chain_set(&ei->dir, p_newdir->dir, p_newdir->size,
+               p_newdir->flags);
diff --git a/queue-6.6/gpio-sch-use-raw_spinlock_t-in-the-irq-startup-path.patch b/queue-6.6/gpio-sch-use-raw_spinlock_t-in-the-irq-startup-path.patch
new file mode 100644 (file)
index 0000000..cc08040
--- /dev/null
@@ -0,0 +1,164 @@
+From stable+bounces-274995-greg=kroah.com@vger.kernel.org Wed Jul 15 18:33:06 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 12:32:56 -0400
+Subject: gpio: sch: use raw_spinlock_t in the irq startup path
+To: stable@vger.kernel.org
+Cc: Runyu Xiao <runyu.xiao@seu.edu.cn>, Sebastian Andrzej Siewior <bigeasy@linutronix.de>, Andy Shevchenko <andriy.shevchenko@intel.com>, Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715163256.953315-1-sashal@kernel.org>
+
+From: Runyu Xiao <runyu.xiao@seu.edu.cn>
+
+[ Upstream commit 286533cb14a3c8a8bd39ff64ea2fc8e1aa0f638b ]
+
+sch_irq_unmask() enables the GPIO IRQ and then updates the controller
+state through sch_irq_mask_unmask(), which takes sch->lock with
+spin_lock_irqsave().  The callback can be reached from irq_startup()
+while setting up a requested IRQ.  That path is not sleepable, but on
+PREEMPT_RT a regular spinlock_t becomes a sleeping lock.
+
+This issue was found by our static analysis tool and then manually
+reviewed against the current tree.
+
+The grounded PoC kept the request_threaded_irq() -> __setup_irq() ->
+irq_startup() -> sch_irq_unmask() -> sch_irq_mask_unmask() carrier and
+used the original spin_lock_irqsave(&sch->lock) edge.  Lockdep reported:
+
+  BUG: sleeping function called from invalid context
+  hardirqs last disabled at ... __setup_irq.constprop.0 ... [vuln_msv]
+  sch_rt_spin_lock_irqsave+0x1c/0x30 [vuln_msv]
+  sch_irq_mask_unmask.constprop.0+0x31/0x70 [vuln_msv]
+  __setup_irq.constprop.0+0xd/0x30 [vuln_msv]
+
+Convert the SCH controller lock to raw_spinlock_t.  The same lock is
+also used by the GPIO direction and value callbacks, but those critical
+sections only update MMIO-backed GPIO registers and do not contain
+sleepable operations.  Keeping this register lock non-sleeping is
+therefore appropriate for the irqchip callbacks and does not change the
+GPIO-side locking contract.
+
+Fixes: 7a81638485c1 ("gpio: sch: Add edge event support")
+Cc: stable@vger.kernel.org
+Signed-off-by: Runyu Xiao <runyu.xiao@seu.edu.cn>
+Reviewed-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
+Reviewed-by: Andy Shevchenko <andriy.shevchenko@intel.com>
+Link: https://patch.msgid.link/20260617154035.1199948-2-runyu.xiao@seu.edu.cn
+Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
+[ adjusted context for 6.6's pre-MMIO `iobase` port I/O and void `sch_gpio_set()` (no set_rv `return 0;`) ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/gpio/gpio-sch.c |   32 ++++++++++++++++----------------
+ 1 file changed, 16 insertions(+), 16 deletions(-)
+
+--- a/drivers/gpio/gpio-sch.c
++++ b/drivers/gpio/gpio-sch.c
+@@ -38,7 +38,7 @@
+ struct sch_gpio {
+       struct gpio_chip chip;
+-      spinlock_t lock;
++      raw_spinlock_t lock;
+       unsigned short iobase;
+       unsigned short resume_base;
+@@ -102,9 +102,9 @@ static int sch_gpio_direction_in(struct
+       struct sch_gpio *sch = gpiochip_get_data(gc);
+       unsigned long flags;
+-      spin_lock_irqsave(&sch->lock, flags);
++      raw_spin_lock_irqsave(&sch->lock, flags);
+       sch_gpio_reg_set(sch, gpio_num, GIO, 1);
+-      spin_unlock_irqrestore(&sch->lock, flags);
++      raw_spin_unlock_irqrestore(&sch->lock, flags);
+       return 0;
+ }
+@@ -120,9 +120,9 @@ static void sch_gpio_set(struct gpio_chi
+       struct sch_gpio *sch = gpiochip_get_data(gc);
+       unsigned long flags;
+-      spin_lock_irqsave(&sch->lock, flags);
++      raw_spin_lock_irqsave(&sch->lock, flags);
+       sch_gpio_reg_set(sch, gpio_num, GLV, val);
+-      spin_unlock_irqrestore(&sch->lock, flags);
++      raw_spin_unlock_irqrestore(&sch->lock, flags);
+ }
+ static int sch_gpio_direction_out(struct gpio_chip *gc, unsigned int gpio_num,
+@@ -131,9 +131,9 @@ static int sch_gpio_direction_out(struct
+       struct sch_gpio *sch = gpiochip_get_data(gc);
+       unsigned long flags;
+-      spin_lock_irqsave(&sch->lock, flags);
++      raw_spin_lock_irqsave(&sch->lock, flags);
+       sch_gpio_reg_set(sch, gpio_num, GIO, 0);
+-      spin_unlock_irqrestore(&sch->lock, flags);
++      raw_spin_unlock_irqrestore(&sch->lock, flags);
+       /*
+        * according to the datasheet, writing to the level register has no
+@@ -193,14 +193,14 @@ static int sch_irq_type(struct irq_data
+               return -EINVAL;
+       }
+-      spin_lock_irqsave(&sch->lock, flags);
++      raw_spin_lock_irqsave(&sch->lock, flags);
+       sch_gpio_reg_set(sch, gpio_num, GTPE, rising);
+       sch_gpio_reg_set(sch, gpio_num, GTNE, falling);
+       irq_set_handler_locked(d, handle_edge_irq);
+-      spin_unlock_irqrestore(&sch->lock, flags);
++      raw_spin_unlock_irqrestore(&sch->lock, flags);
+       return 0;
+ }
+@@ -212,9 +212,9 @@ static void sch_irq_ack(struct irq_data
+       irq_hw_number_t gpio_num = irqd_to_hwirq(d);
+       unsigned long flags;
+-      spin_lock_irqsave(&sch->lock, flags);
++      raw_spin_lock_irqsave(&sch->lock, flags);
+       sch_gpio_reg_set(sch, gpio_num, GTS, 1);
+-      spin_unlock_irqrestore(&sch->lock, flags);
++      raw_spin_unlock_irqrestore(&sch->lock, flags);
+ }
+ static void sch_irq_mask_unmask(struct gpio_chip *gc, irq_hw_number_t gpio_num, int val)
+@@ -222,9 +222,9 @@ static void sch_irq_mask_unmask(struct g
+       struct sch_gpio *sch = gpiochip_get_data(gc);
+       unsigned long flags;
+-      spin_lock_irqsave(&sch->lock, flags);
++      raw_spin_lock_irqsave(&sch->lock, flags);
+       sch_gpio_reg_set(sch, gpio_num, GGPE, val);
+-      spin_unlock_irqrestore(&sch->lock, flags);
++      raw_spin_unlock_irqrestore(&sch->lock, flags);
+ }
+ static void sch_irq_mask(struct irq_data *d)
+@@ -265,12 +265,12 @@ static u32 sch_gpio_gpe_handler(acpi_han
+       int offset;
+       u32 ret;
+-      spin_lock_irqsave(&sch->lock, flags);
++      raw_spin_lock_irqsave(&sch->lock, flags);
+       core_status = inl(sch->iobase + CORE_BANK_OFFSET + GTS);
+       resume_status = inl(sch->iobase + RESUME_BANK_OFFSET + GTS);
+-      spin_unlock_irqrestore(&sch->lock, flags);
++      raw_spin_unlock_irqrestore(&sch->lock, flags);
+       pending = (resume_status << sch->resume_base) | core_status;
+       for_each_set_bit(offset, &pending, sch->chip.ngpio)
+@@ -336,7 +336,7 @@ static int sch_gpio_probe(struct platfor
+                                pdev->name))
+               return -EBUSY;
+-      spin_lock_init(&sch->lock);
++      raw_spin_lock_init(&sch->lock);
+       sch->iobase = res->start;
+       sch->chip = sch_gpio_chip;
+       sch->chip.label = dev_name(&pdev->dev);
diff --git a/queue-6.6/hfs-hfsplus-fix-u32-overflow-in-check_and_correct_requested_length.patch b/queue-6.6/hfs-hfsplus-fix-u32-overflow-in-check_and_correct_requested_length.patch
new file mode 100644 (file)
index 0000000..89fc8bf
--- /dev/null
@@ -0,0 +1,69 @@
+From stable+bounces-277240-greg=kroah.com@vger.kernel.org Sat Jul 18 03:34:08 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 17 Jul 2026 21:34:00 -0400
+Subject: hfs/hfsplus: fix u32 overflow in check_and_correct_requested_length
+To: stable@vger.kernel.org
+Cc: Tristan Madani <tristan@talencesecurity.com>, syzbot+6df204b70bf3261691c5@syzkaller.appspotmail.com, syzbot+e76bf3d19b85350571ac@syzkaller.appspotmail.com, Viacheslav Dubeyko <slava@dubeyko.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260718013400.2623033-2-sashal@kernel.org>
+
+From: Tristan Madani <tristan@talencesecurity.com>
+
+[ Upstream commit 966cb76fb2857a4242cab6ea2ea17acf818a3da7 ]
+
+check_and_correct_requested_length() compares (off + len) against
+node_size using u32 arithmetic.  When the caller passes a large len
+value (e.g. from an underflowed subtraction in hfs_brec_remove()),
+off + len can wrap past 2^32 and produce a small result, causing the
+bounds check to pass when it should fail.
+
+For example, with off=14 and len=0xFFFFFFF2 (underflowed from
+data_off - keyoffset - size in hfs_brec_remove), off + len wraps to 6,
+which is less than a typical node_size of 512, so the check passes and
+the subsequent memmove reads ~4GB past the node buffer.
+
+Fix this by widening the addition to u64 before comparing against
+node_size.  This prevents the u32 wrap while keeping the logic
+straightforward.
+
+Reported-by: syzbot+6df204b70bf3261691c5@syzkaller.appspotmail.com
+Closes: https://syzkaller.appspot.com/bug?extid=6df204b70bf3261691c5
+Tested-by: syzbot+6df204b70bf3261691c5@syzkaller.appspotmail.com
+Reported-by: syzbot+e76bf3d19b85350571ac@syzkaller.appspotmail.com
+Closes: https://syzkaller.appspot.com/bug?extid=e76bf3d19b85350571ac
+Tested-by: syzbot+e76bf3d19b85350571ac@syzkaller.appspotmail.com
+Fixes: a431930c9bac ("hfs: fix slab-out-of-bounds in hfs_bnode_read()")
+Cc: stable@vger.kernel.org
+Signed-off-by: Tristan Madani <tristan@talencesecurity.com>
+Reviewed-by: Viacheslav Dubeyko <slava@dubeyko.com>
+Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com>
+Link: https://lore.kernel.org/r/20260505111300.3592757-2-tristmd@gmail.com
+Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/hfs/bnode.c          |    2 +-
+ fs/hfsplus/hfsplus_fs.h |    2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+--- a/fs/hfs/bnode.c
++++ b/fs/hfs/bnode.c
+@@ -41,7 +41,7 @@ u32 check_and_correct_requested_length(s
+       node_size = node->tree->node_size;
+-      if ((off + len) > node_size) {
++      if ((u64)off + len > node_size) {
+               u32 new_len = node_size - off;
+               pr_err("requested length has been corrected: "
+--- a/fs/hfsplus/hfsplus_fs.h
++++ b/fs/hfsplus/hfsplus_fs.h
+@@ -613,7 +613,7 @@ u32 check_and_correct_requested_length(s
+       node_size = node->tree->node_size;
+-      if ((off + len) > node_size) {
++      if ((u64)off + len > node_size) {
+               u32 new_len = node_size - off;
+               pr_err("requested length has been corrected: "
diff --git a/queue-6.6/hfs-hfsplus-prevent-getting-negative-values-of-offset-length.patch b/queue-6.6/hfs-hfsplus-prevent-getting-negative-values-of-offset-length.patch
new file mode 100644 (file)
index 0000000..3949a50
--- /dev/null
@@ -0,0 +1,957 @@
+From stable+bounces-277241-greg=kroah.com@vger.kernel.org Sat Jul 18 03:34:10 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 17 Jul 2026 21:33:59 -0400
+Subject: hfs/hfsplus: prevent getting negative values of offset/length
+To: stable@vger.kernel.org
+Cc: Viacheslav Dubeyko <slava@dubeyko.com>, John Paul Adrian Glaubitz <glaubitz@physik.fu-berlin.de>, Yangtao Li <frank.li@vivo.com>, linux-fsdevel@vger.kernel.org, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260718013400.2623033-1-sashal@kernel.org>
+
+From: Viacheslav Dubeyko <slava@dubeyko.com>
+
+[ Upstream commit 00c14a09a70e10ae18eb3707d0059291425c04bd ]
+
+The syzbot reported KASAN out-of-bounds issue in
+hfs_bnode_move():
+
+[   45.588165][ T9821] hfs: dst 14, src 65536, len -65536
+[   45.588895][ T9821] ==================================================================
+[   45.590114][ T9821] BUG: KASAN: out-of-bounds in hfs_bnode_move+0xfd/0x140
+[   45.591127][ T9821] Read of size 18446744073709486080 at addr ffff888035935400 by task repro/9821
+[   45.592207][ T9821]
+[   45.592420][ T9821] CPU: 0 UID: 0 PID: 9821 Comm: repro Not tainted 6.16.0-rc7-dirty #42 PREEMPT(full)
+[   45.592428][ T9821] Hardware name: QEMU Ubuntu 24.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
+[   45.592431][ T9821] Call Trace:
+[   45.592434][ T9821]  <TASK>
+[   45.592437][ T9821]  dump_stack_lvl+0x1c1/0x2a0
+[   45.592446][ T9821]  ? __virt_addr_valid+0x1c8/0x5c0
+[   45.592454][ T9821]  ? __pfx_dump_stack_lvl+0x10/0x10
+[   45.592461][ T9821]  ? rcu_is_watching+0x15/0xb0
+[   45.592469][ T9821]  ? lock_release+0x4b/0x3e0
+[   45.592476][ T9821]  ? __virt_addr_valid+0x1c8/0x5c0
+[   45.592483][ T9821]  ? __virt_addr_valid+0x4a5/0x5c0
+[   45.592491][ T9821]  print_report+0x17e/0x7c0
+[   45.592497][ T9821]  ? __virt_addr_valid+0x1c8/0x5c0
+[   45.592504][ T9821]  ? __virt_addr_valid+0x4a5/0x5c0
+[   45.592511][ T9821]  ? __phys_addr+0xd3/0x180
+[   45.592519][ T9821]  ? hfs_bnode_move+0xfd/0x140
+[   45.592526][ T9821]  kasan_report+0x147/0x180
+[   45.592531][ T9821]  ? _printk+0xcf/0x120
+[   45.592537][ T9821]  ? hfs_bnode_move+0xfd/0x140
+[   45.592544][ T9821]  ? hfs_bnode_move+0xfd/0x140
+[   45.592552][ T9821]  kasan_check_range+0x2b0/0x2c0
+[   45.592557][ T9821]  ? hfs_bnode_move+0xfd/0x140
+[   45.592565][ T9821]  __asan_memmove+0x29/0x70
+[   45.592572][ T9821]  hfs_bnode_move+0xfd/0x140
+[   45.592580][ T9821]  hfs_brec_remove+0x473/0x560
+[   45.592589][ T9821]  hfs_cat_move+0x6fb/0x960
+[   45.592598][ T9821]  ? __pfx_hfs_cat_move+0x10/0x10
+[   45.592607][ T9821]  ? seqcount_lockdep_reader_access+0x122/0x1c0
+[   45.592614][ T9821]  ? lockdep_hardirqs_on+0x9c/0x150
+[   45.592631][ T9821]  ? __lock_acquire+0xaec/0xd80
+[   45.592641][ T9821]  hfs_rename+0x1dc/0x2d0
+[   45.592649][ T9821]  ? __pfx_hfs_rename+0x10/0x10
+[   45.592657][ T9821]  vfs_rename+0xac6/0xed0
+[   45.592664][ T9821]  ? __pfx_vfs_rename+0x10/0x10
+[   45.592670][ T9821]  ? d_alloc+0x144/0x190
+[   45.592677][ T9821]  ? bpf_lsm_path_rename+0x9/0x20
+[   45.592683][ T9821]  ? security_path_rename+0x17d/0x490
+[   45.592691][ T9821]  do_renameat2+0x890/0xc50
+[   45.592699][ T9821]  ? __pfx_do_renameat2+0x10/0x10
+[   45.592707][ T9821]  ? getname_flags+0x1e5/0x540
+[   45.592714][ T9821]  __x64_sys_rename+0x82/0x90
+[   45.592720][ T9821]  ? entry_SYSCALL_64_after_hwframe+0x77/0x7f
+[   45.592725][ T9821]  do_syscall_64+0xf3/0x3a0
+[   45.592741][ T9821]  ? exc_page_fault+0x9f/0xf0
+[   45.592748][ T9821]  entry_SYSCALL_64_after_hwframe+0x77/0x7f
+[   45.592754][ T9821] RIP: 0033:0x7f7f73fe3fc9
+[   45.592760][ T9821] Code: 00 c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 48
+[   45.592765][ T9821] RSP: 002b:00007ffc7e116cf8 EFLAGS: 00000283 ORIG_RAX: 0000000000000052
+[   45.592772][ T9821] RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007f7f73fe3fc9
+[   45.592776][ T9821] RDX: 0000200000000871 RSI: 0000200000000780 RDI: 00002000000003c0
+[   45.592781][ T9821] RBP: 00007ffc7e116d00 R08: 0000000000000000 R09: 00007ffc7e116d30
+[   45.592784][ T9821] R10: fffffffffffffff0 R11: 0000000000000283 R12: 00005557e81f8250
+[   45.592788][ T9821] R13: 0000000000000000 R14: 0000000000000000 R15: 0000000000000000
+[   45.592795][ T9821]  </TASK>
+[   45.592797][ T9821]
+[   45.619721][ T9821] The buggy address belongs to the physical page:
+[   45.620300][ T9821] page: refcount:1 mapcount:1 mapping:0000000000000000 index:0x559a88174 pfn:0x35935
+[   45.621150][ T9821] memcg:ffff88810a1d5b00
+[   45.621531][ T9821] anon flags: 0xfff60000020838(uptodate|dirty|lru|owner_2|swapbacked|node=0|zone=1|lastcpupid=0x7ff)
+[   45.622496][ T9821] raw: 00fff60000020838 ffffea0000d64d88 ffff888021753e10 ffff888029da0771
+[   45.623260][ T9821] raw: 0000000559a88174 0000000000000000 0000000100000000 ffff88810a1d5b00
+[   45.624030][ T9821] page dumped because: kasan: bad access detected
+[   45.624602][ T9821] page_owner tracks the page as allocated
+[   45.625115][ T9821] page last allocated via order 0, migratetype Movable, gfp_mask 0x140dca(GFP_HIGHUSER_MOVABLE|__GFP_ZERO0
+[   45.626685][ T9821]  post_alloc_hook+0x240/0x2a0
+[   45.627127][ T9821]  get_page_from_freelist+0x2101/0x21e0
+[   45.627628][ T9821]  __alloc_frozen_pages_noprof+0x274/0x380
+[   45.628154][ T9821]  alloc_pages_mpol+0x241/0x4b0
+[   45.628593][ T9821]  vma_alloc_folio_noprof+0xe4/0x210
+[   45.629066][ T9821]  folio_prealloc+0x30/0x180
+[   45.629487][ T9821]  __handle_mm_fault+0x34bd/0x5640
+[   45.629957][ T9821]  handle_mm_fault+0x40e/0x8e0
+[   45.630392][ T9821]  do_user_addr_fault+0xa81/0x1390
+[   45.630862][ T9821]  exc_page_fault+0x76/0xf0
+[   45.631273][ T9821]  asm_exc_page_fault+0x26/0x30
+[   45.631712][ T9821] page last free pid 5269 tgid 5269 stack trace:
+[   45.632281][ T9821]  free_unref_folios+0xc73/0x14c0
+[   45.632740][ T9821]  folios_put_refs+0x55b/0x640
+[   45.633177][ T9821]  free_pages_and_swap_cache+0x26d/0x510
+[   45.633685][ T9821]  tlb_flush_mmu+0x3a0/0x680
+[   45.634105][ T9821]  tlb_finish_mmu+0xd4/0x200
+[   45.634525][ T9821]  exit_mmap+0x44c/0xb70
+[   45.634914][ T9821]  __mmput+0x118/0x420
+[   45.635286][ T9821]  exit_mm+0x1da/0x2c0
+[   45.635659][ T9821]  do_exit+0x652/0x2330
+[   45.636039][ T9821]  do_group_exit+0x21c/0x2d0
+[   45.636457][ T9821]  __x64_sys_exit_group+0x3f/0x40
+[   45.636915][ T9821]  x64_sys_call+0x21ba/0x21c0
+[   45.637342][ T9821]  do_syscall_64+0xf3/0x3a0
+[   45.637756][ T9821]  entry_SYSCALL_64_after_hwframe+0x77/0x7f
+[   45.638290][ T9821] page has been migrated, last migrate reason: numa_misplaced
+[   45.638956][ T9821]
+[   45.639173][ T9821] Memory state around the buggy address:
+[   45.639677][ T9821]  ffff888035935300: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+[   45.640397][ T9821]  ffff888035935380: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+[   45.641117][ T9821] >ffff888035935400: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+[   45.641837][ T9821]                    ^
+[   45.642207][ T9821]  ffff888035935480: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+[   45.642929][ T9821]  ffff888035935500: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+[   45.643650][ T9821] ==================================================================
+
+This commit [1] fixes the issue if an offset inside of b-tree node
+or length of the request is bigger than b-tree node. However,
+this fix is still not ready for negative values
+of the offset or length. Moreover, negative values of
+the offset or length doesn't make sense for b-tree's
+operations. Because we could try to access the memory address
+outside of the beginning of memory page's addresses range.
+Also, using of negative values make logic very complicated,
+unpredictable, and we could access the wrong item(s)
+in the b-tree node.
+
+This patch changes b-tree interface by means of converting
+signed integer arguments of offset and length on u32 type.
+Such conversion has goal to prevent of using negative values
+unintentionally or by mistake in b-tree operations.
+
+[1] 'commit a431930c9bac ("hfs: fix slab-out-of-bounds in hfs_bnode_read()")'
+
+Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com>
+cc: John Paul Adrian Glaubitz <glaubitz@physik.fu-berlin.de>
+cc: Yangtao Li <frank.li@vivo.com>
+cc: linux-fsdevel@vger.kernel.org
+Link: https://lore.kernel.org/r/20251002200020.2578311-1-slava@dubeyko.com
+Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com>
+Stable-dep-of: 966cb76fb285 ("hfs/hfsplus: fix u32 overflow in check_and_correct_requested_length")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/hfs/bfind.c          |    2 -
+ fs/hfs/bnode.c          |   52 ++++++++++++++--------------
+ fs/hfs/brec.c           |    2 -
+ fs/hfs/btree.c          |    2 -
+ fs/hfs/btree.h          |   71 +++++++++++++++++++-------------------
+ fs/hfs/hfs_fs.h         |   88 ++++++++++++++++++++++++++++--------------------
+ fs/hfs/inode.c          |    3 +
+ fs/hfsplus/bfind.c      |    2 -
+ fs/hfsplus/bnode.c      |   60 ++++++++++++++++----------------
+ fs/hfsplus/brec.c       |    2 -
+ fs/hfsplus/btree.c      |    2 -
+ fs/hfsplus/hfsplus_fs.h |   38 ++++++++++----------
+ 12 files changed, 171 insertions(+), 153 deletions(-)
+
+--- a/fs/hfs/bfind.c
++++ b/fs/hfs/bfind.c
+@@ -167,7 +167,7 @@ release:
+       return res;
+ }
+-int hfs_brec_read(struct hfs_find_data *fd, void *rec, int rec_len)
++int hfs_brec_read(struct hfs_find_data *fd, void *rec, u32 rec_len)
+ {
+       int res;
+--- a/fs/hfs/bnode.c
++++ b/fs/hfs/bnode.c
+@@ -16,14 +16,14 @@
+ #include "btree.h"
+ static inline
+-bool is_bnode_offset_valid(struct hfs_bnode *node, int off)
++bool is_bnode_offset_valid(struct hfs_bnode *node, u32 off)
+ {
+       bool is_valid = off < node->tree->node_size;
+       if (!is_valid) {
+               pr_err("requested invalid offset: "
+                      "NODE: id %u, type %#x, height %u, "
+-                     "node_size %u, offset %d\n",
++                     "node_size %u, offset %u\n",
+                      node->this, node->type, node->height,
+                      node->tree->node_size, off);
+       }
+@@ -32,7 +32,7 @@ bool is_bnode_offset_valid(struct hfs_bn
+ }
+ static inline
+-int check_and_correct_requested_length(struct hfs_bnode *node, int off, int len)
++u32 check_and_correct_requested_length(struct hfs_bnode *node, u32 off, u32 len)
+ {
+       unsigned int node_size;
+@@ -42,12 +42,12 @@ int check_and_correct_requested_length(s
+       node_size = node->tree->node_size;
+       if ((off + len) > node_size) {
+-              int new_len = (int)node_size - off;
++              u32 new_len = node_size - off;
+               pr_err("requested length has been corrected: "
+                      "NODE: id %u, type %#x, height %u, "
+-                     "node_size %u, offset %d, "
+-                     "requested_len %d, corrected_len %d\n",
++                     "node_size %u, offset %u, "
++                     "requested_len %u, corrected_len %u\n",
+                      node->this, node->type, node->height,
+                      node->tree->node_size, off, len, new_len);
+@@ -57,12 +57,12 @@ int check_and_correct_requested_length(s
+       return len;
+ }
+-void hfs_bnode_read(struct hfs_bnode *node, void *buf, int off, int len)
++void hfs_bnode_read(struct hfs_bnode *node, void *buf, u32 off, u32 len)
+ {
+       struct page *page;
+-      int pagenum;
+-      int bytes_read;
+-      int bytes_to_read;
++      u32 pagenum;
++      u32 bytes_read;
++      u32 bytes_to_read;
+       memset(buf, 0, len);
+@@ -72,7 +72,7 @@ void hfs_bnode_read(struct hfs_bnode *no
+       if (len == 0) {
+               pr_err("requested zero length: "
+                      "NODE: id %u, type %#x, height %u, "
+-                     "node_size %u, offset %d, len %d\n",
++                     "node_size %u, offset %u, len %u\n",
+                      node->this, node->type, node->height,
+                      node->tree->node_size, off, len);
+               return;
+@@ -88,7 +88,7 @@ void hfs_bnode_read(struct hfs_bnode *no
+               if (pagenum >= node->tree->pages_per_bnode)
+                       break;
+               page = node->page[pagenum];
+-              bytes_to_read = min_t(int, len - bytes_read, PAGE_SIZE - off);
++              bytes_to_read = min_t(u32, len - bytes_read, PAGE_SIZE - off);
+               memcpy_from_page(buf + bytes_read, page, off, bytes_to_read);
+@@ -97,7 +97,7 @@ void hfs_bnode_read(struct hfs_bnode *no
+       }
+ }
+-u16 hfs_bnode_read_u16(struct hfs_bnode *node, int off)
++u16 hfs_bnode_read_u16(struct hfs_bnode *node, u32 off)
+ {
+       __be16 data;
+       // optimize later...
+@@ -105,7 +105,7 @@ u16 hfs_bnode_read_u16(struct hfs_bnode
+       return be16_to_cpu(data);
+ }
+-u8 hfs_bnode_read_u8(struct hfs_bnode *node, int off)
++u8 hfs_bnode_read_u8(struct hfs_bnode *node, u32 off)
+ {
+       u8 data;
+       // optimize later...
+@@ -113,10 +113,10 @@ u8 hfs_bnode_read_u8(struct hfs_bnode *n
+       return data;
+ }
+-void hfs_bnode_read_key(struct hfs_bnode *node, void *key, int off)
++void hfs_bnode_read_key(struct hfs_bnode *node, void *key, u32 off)
+ {
+       struct hfs_btree *tree;
+-      int key_len;
++      u32 key_len;
+       tree = node->tree;
+       if (node->type == HFS_NODE_LEAF ||
+@@ -127,14 +127,14 @@ void hfs_bnode_read_key(struct hfs_bnode
+       if (key_len > sizeof(hfs_btree_key) || key_len < 1) {
+               memset(key, 0, sizeof(hfs_btree_key));
+-              pr_err("hfs: Invalid key length: %d\n", key_len);
++              pr_err("hfs: Invalid key length: %u\n", key_len);
+               return;
+       }
+       hfs_bnode_read(node, key, off, key_len);
+ }
+-void hfs_bnode_write(struct hfs_bnode *node, void *buf, int off, int len)
++void hfs_bnode_write(struct hfs_bnode *node, void *buf, u32 off, u32 len)
+ {
+       struct page *page;
+@@ -144,7 +144,7 @@ void hfs_bnode_write(struct hfs_bnode *n
+       if (len == 0) {
+               pr_err("requested zero length: "
+                      "NODE: id %u, type %#x, height %u, "
+-                     "node_size %u, offset %d, len %d\n",
++                     "node_size %u, offset %u, len %u\n",
+                      node->this, node->type, node->height,
+                      node->tree->node_size, off, len);
+               return;
+@@ -159,20 +159,20 @@ void hfs_bnode_write(struct hfs_bnode *n
+       set_page_dirty(page);
+ }
+-void hfs_bnode_write_u16(struct hfs_bnode *node, int off, u16 data)
++void hfs_bnode_write_u16(struct hfs_bnode *node, u32 off, u16 data)
+ {
+       __be16 v = cpu_to_be16(data);
+       // optimize later...
+       hfs_bnode_write(node, &v, off, 2);
+ }
+-void hfs_bnode_write_u8(struct hfs_bnode *node, int off, u8 data)
++void hfs_bnode_write_u8(struct hfs_bnode *node, u32 off, u8 data)
+ {
+       // optimize later...
+       hfs_bnode_write(node, &data, off, 1);
+ }
+-void hfs_bnode_clear(struct hfs_bnode *node, int off, int len)
++void hfs_bnode_clear(struct hfs_bnode *node, u32 off, u32 len)
+ {
+       struct page *page;
+@@ -182,7 +182,7 @@ void hfs_bnode_clear(struct hfs_bnode *n
+       if (len == 0) {
+               pr_err("requested zero length: "
+                      "NODE: id %u, type %#x, height %u, "
+-                     "node_size %u, offset %d, len %d\n",
++                     "node_size %u, offset %u, len %u\n",
+                      node->this, node->type, node->height,
+                      node->tree->node_size, off, len);
+               return;
+@@ -197,8 +197,8 @@ void hfs_bnode_clear(struct hfs_bnode *n
+       set_page_dirty(page);
+ }
+-void hfs_bnode_copy(struct hfs_bnode *dst_node, int dst,
+-              struct hfs_bnode *src_node, int src, int len)
++void hfs_bnode_copy(struct hfs_bnode *dst_node, u32 dst,
++                  struct hfs_bnode *src_node, u32 src, u32 len)
+ {
+       struct page *src_page, *dst_page;
+@@ -218,7 +218,7 @@ void hfs_bnode_copy(struct hfs_bnode *ds
+       set_page_dirty(dst_page);
+ }
+-void hfs_bnode_move(struct hfs_bnode *node, int dst, int src, int len)
++void hfs_bnode_move(struct hfs_bnode *node, u32 dst, u32 src, u32 len)
+ {
+       struct page *page;
+       void *ptr;
+--- a/fs/hfs/brec.c
++++ b/fs/hfs/brec.c
+@@ -62,7 +62,7 @@ u16 hfs_brec_keylen(struct hfs_bnode *no
+       return retval;
+ }
+-int hfs_brec_insert(struct hfs_find_data *fd, void *entry, int entry_len)
++int hfs_brec_insert(struct hfs_find_data *fd, void *entry, u32 entry_len)
+ {
+       struct hfs_btree *tree;
+       struct hfs_bnode *node, *new_node;
+--- a/fs/hfs/btree.c
++++ b/fs/hfs/btree.c
+@@ -259,7 +259,7 @@ static struct hfs_bnode *hfs_bmap_new_bm
+ }
+ /* Make sure @tree has enough space for the @rsvd_nodes */
+-int hfs_bmap_reserve(struct hfs_btree *tree, int rsvd_nodes)
++int hfs_bmap_reserve(struct hfs_btree *tree, u32 rsvd_nodes)
+ {
+       struct inode *inode = tree->inode;
+       u32 count;
+--- a/fs/hfs/btree.h
++++ b/fs/hfs/btree.h
+@@ -86,48 +86,49 @@ struct hfs_find_data {
+ /* btree.c */
+-extern struct hfs_btree *hfs_btree_open(struct super_block *, u32, btree_keycmp);
+-extern void hfs_btree_close(struct hfs_btree *);
+-extern void hfs_btree_write(struct hfs_btree *);
+-extern int hfs_bmap_reserve(struct hfs_btree *, int);
+-extern struct hfs_bnode * hfs_bmap_alloc(struct hfs_btree *);
++extern struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id,
++                                      btree_keycmp keycmp);
++extern void hfs_btree_close(struct hfs_btree *tree);
++extern void hfs_btree_write(struct hfs_btree *tree);
++extern int hfs_bmap_reserve(struct hfs_btree *tree, u32 rsvd_nodes);
++extern struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree);
+ extern void hfs_bmap_free(struct hfs_bnode *node);
+ /* bnode.c */
+-extern void hfs_bnode_read(struct hfs_bnode *, void *, int, int);
+-extern u16 hfs_bnode_read_u16(struct hfs_bnode *, int);
+-extern u8 hfs_bnode_read_u8(struct hfs_bnode *, int);
+-extern void hfs_bnode_read_key(struct hfs_bnode *, void *, int);
+-extern void hfs_bnode_write(struct hfs_bnode *, void *, int, int);
+-extern void hfs_bnode_write_u16(struct hfs_bnode *, int, u16);
+-extern void hfs_bnode_write_u8(struct hfs_bnode *, int, u8);
+-extern void hfs_bnode_clear(struct hfs_bnode *, int, int);
+-extern void hfs_bnode_copy(struct hfs_bnode *, int,
+-                         struct hfs_bnode *, int, int);
+-extern void hfs_bnode_move(struct hfs_bnode *, int, int, int);
+-extern void hfs_bnode_dump(struct hfs_bnode *);
+-extern void hfs_bnode_unlink(struct hfs_bnode *);
+-extern struct hfs_bnode *hfs_bnode_findhash(struct hfs_btree *, u32);
+-extern struct hfs_bnode *hfs_bnode_find(struct hfs_btree *, u32);
+-extern void hfs_bnode_unhash(struct hfs_bnode *);
+-extern void hfs_bnode_free(struct hfs_bnode *);
+-extern struct hfs_bnode *hfs_bnode_create(struct hfs_btree *, u32);
+-extern void hfs_bnode_get(struct hfs_bnode *);
+-extern void hfs_bnode_put(struct hfs_bnode *);
++extern void hfs_bnode_read(struct hfs_bnode *node, void *buf, u32 off, u32 len);
++extern u16 hfs_bnode_read_u16(struct hfs_bnode *node, u32 off);
++extern u8 hfs_bnode_read_u8(struct hfs_bnode *node, u32 off);
++extern void hfs_bnode_read_key(struct hfs_bnode *node, void *key, u32 off);
++extern void hfs_bnode_write(struct hfs_bnode *node, void *buf, u32 off, u32 len);
++extern void hfs_bnode_write_u16(struct hfs_bnode *node, u32 off, u16 data);
++extern void hfs_bnode_write_u8(struct hfs_bnode *node, u32 off, u8 data);
++extern void hfs_bnode_clear(struct hfs_bnode *node, u32 off, u32 len);
++extern void hfs_bnode_copy(struct hfs_bnode *dst_node, u32 dst,
++                         struct hfs_bnode *src_node, u32 src, u32 len);
++extern void hfs_bnode_move(struct hfs_bnode *node, u32 dst, u32 src, u32 len);
++extern void hfs_bnode_dump(struct hfs_bnode *node);
++extern void hfs_bnode_unlink(struct hfs_bnode *node);
++extern struct hfs_bnode *hfs_bnode_findhash(struct hfs_btree *tree, u32 cnid);
++extern struct hfs_bnode *hfs_bnode_find(struct hfs_btree *tree, u32 num);
++extern void hfs_bnode_unhash(struct hfs_bnode *node);
++extern void hfs_bnode_free(struct hfs_bnode *node);
++extern struct hfs_bnode *hfs_bnode_create(struct hfs_btree *tree, u32 num);
++extern void hfs_bnode_get(struct hfs_bnode *node);
++extern void hfs_bnode_put(struct hfs_bnode *node);
+ /* brec.c */
+-extern u16 hfs_brec_lenoff(struct hfs_bnode *, u16, u16 *);
+-extern u16 hfs_brec_keylen(struct hfs_bnode *, u16);
+-extern int hfs_brec_insert(struct hfs_find_data *, void *, int);
+-extern int hfs_brec_remove(struct hfs_find_data *);
++extern u16 hfs_brec_lenoff(struct hfs_bnode *node, u16 rec, u16 *off);
++extern u16 hfs_brec_keylen(struct hfs_bnode *node, u16 rec);
++extern int hfs_brec_insert(struct hfs_find_data *fd, void *entry, u32 entry_len);
++extern int hfs_brec_remove(struct hfs_find_data *fd);
+ /* bfind.c */
+-extern int hfs_find_init(struct hfs_btree *, struct hfs_find_data *);
+-extern void hfs_find_exit(struct hfs_find_data *);
+-extern int __hfs_brec_find(struct hfs_bnode *, struct hfs_find_data *);
+-extern int hfs_brec_find(struct hfs_find_data *);
+-extern int hfs_brec_read(struct hfs_find_data *, void *, int);
+-extern int hfs_brec_goto(struct hfs_find_data *, int);
++extern int hfs_find_init(struct hfs_btree *tree, struct hfs_find_data *fd);
++extern void hfs_find_exit(struct hfs_find_data *fd);
++extern int __hfs_brec_find(struct hfs_bnode *bnode, struct hfs_find_data *fd);
++extern int hfs_brec_find(struct hfs_find_data *fd);
++extern int hfs_brec_read(struct hfs_find_data *fd, void *rec, u32 rec_len);
++extern int hfs_brec_goto(struct hfs_find_data *fd, int cnt);
+ struct hfs_bnode_desc {
+--- a/fs/hfs/hfs_fs.h
++++ b/fs/hfs/hfs_fs.h
+@@ -171,74 +171,90 @@ struct hfs_sb_info {
+ #define HFS_FLG_ALT_MDB_DIRTY 2
+ /* bitmap.c */
+-extern u32 hfs_vbm_search_free(struct super_block *, u32, u32 *);
+-extern int hfs_clear_vbm_bits(struct super_block *, u16, u16);
++extern u32 hfs_vbm_search_free(struct super_block *sb, u32 goal, u32 *num_bits);
++extern int hfs_clear_vbm_bits(struct super_block *sb, u16 start, u16 count);
+ /* catalog.c */
+-extern int hfs_cat_keycmp(const btree_key *, const btree_key *);
++extern int hfs_cat_keycmp(const btree_key *key1, const btree_key *key2);
+ struct hfs_find_data;
+-extern int hfs_cat_find_brec(struct super_block *, u32, struct hfs_find_data *);
+-extern int hfs_cat_create(u32, struct inode *, const struct qstr *, struct inode *);
+-extern int hfs_cat_delete(u32, struct inode *, const struct qstr *);
+-extern int hfs_cat_move(u32, struct inode *, const struct qstr *,
+-                      struct inode *, const struct qstr *);
+-extern void hfs_cat_build_key(struct super_block *, btree_key *, u32, const struct qstr *);
++extern int hfs_cat_find_brec(struct super_block *sb, u32 cnid,
++                           struct hfs_find_data *fd);
++extern int hfs_cat_create(u32 cnid, struct inode *dir,
++                        const struct qstr *str, struct inode *inode);
++extern int hfs_cat_delete(u32 cnid, struct inode *dir, const struct qstr *str);
++extern int hfs_cat_move(u32 cnid, struct inode *src_dir,
++                      const struct qstr *src_name,
++                      struct inode *dst_dir,
++                      const struct qstr *dst_name);
++extern void hfs_cat_build_key(struct super_block *sb, btree_key *key,
++                            u32 parent, const struct qstr *name);
+ /* dir.c */
+ extern const struct file_operations hfs_dir_operations;
+ extern const struct inode_operations hfs_dir_inode_operations;
+ /* extent.c */
+-extern int hfs_ext_keycmp(const btree_key *, const btree_key *);
++extern int hfs_ext_keycmp(const btree_key *key1, const btree_key *key2);
+ extern u16 hfs_ext_find_block(struct hfs_extent *ext, u16 off);
+-extern int hfs_free_fork(struct super_block *, struct hfs_cat_file *, int);
+-extern int hfs_ext_write_extent(struct inode *);
+-extern int hfs_extend_file(struct inode *);
+-extern void hfs_file_truncate(struct inode *);
++extern int hfs_free_fork(struct super_block *sb,
++                       struct hfs_cat_file *file, int type);
++extern int hfs_ext_write_extent(struct inode *inode);
++extern int hfs_extend_file(struct inode *inode);
++extern void hfs_file_truncate(struct inode *inode);
+-extern int hfs_get_block(struct inode *, sector_t, struct buffer_head *, int);
++extern int hfs_get_block(struct inode *inode, sector_t block,
++                       struct buffer_head *bh_result, int create);
+ /* inode.c */
+ extern const struct address_space_operations hfs_aops;
+ extern const struct address_space_operations hfs_btree_aops;
+ int hfs_write_begin(struct file *file, struct address_space *mapping,
+-              loff_t pos, unsigned len, struct page **pagep, void **fsdata);
+-extern struct inode *hfs_new_inode(struct inode *, const struct qstr *, umode_t);
+-extern void hfs_inode_write_fork(struct inode *, struct hfs_extent *, __be32 *, __be32 *);
+-extern int hfs_write_inode(struct inode *, struct writeback_control *);
+-extern int hfs_inode_setattr(struct mnt_idmap *, struct dentry *,
+-                           struct iattr *);
++              loff_t pos, unsigned int len, struct page **pagep,
++              void **fsdata);
++extern struct inode *hfs_new_inode(struct inode *dir, const struct qstr *name,
++                                 umode_t mode);
++extern void hfs_inode_write_fork(struct inode *inode, struct hfs_extent *ext,
++                               __be32 *log_size, __be32 *phys_size);
++extern int hfs_write_inode(struct inode *inode, struct writeback_control *wbc);
++extern int hfs_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
++                           struct iattr *attr);
+ extern void hfs_inode_read_fork(struct inode *inode, struct hfs_extent *ext,
+-                      __be32 log_size, __be32 phys_size, u32 clump_size);
+-extern struct inode *hfs_iget(struct super_block *, struct hfs_cat_key *, hfs_cat_rec *);
+-extern void hfs_evict_inode(struct inode *);
+-extern void hfs_delete_inode(struct inode *);
++                              __be32 __log_size, __be32 phys_size,
++                              u32 clump_size);
++extern struct inode *hfs_iget(struct super_block *sb, struct hfs_cat_key *key,
++                              hfs_cat_rec *rec);
++extern void hfs_evict_inode(struct inode *inode);
++extern void hfs_delete_inode(struct inode *inode);
+ /* attr.c */
+ extern const struct xattr_handler *hfs_xattr_handlers[];
+ /* mdb.c */
+-extern int hfs_mdb_get(struct super_block *);
+-extern void hfs_mdb_commit(struct super_block *);
+-extern void hfs_mdb_close(struct super_block *);
+-extern void hfs_mdb_put(struct super_block *);
++extern int hfs_mdb_get(struct super_block *sb);
++extern void hfs_mdb_commit(struct super_block *sb);
++extern void hfs_mdb_close(struct super_block *sb);
++extern void hfs_mdb_put(struct super_block *sb);
+ /* part_tbl.c */
+-extern int hfs_part_find(struct super_block *, sector_t *, sector_t *);
++extern int hfs_part_find(struct super_block *sb,
++                       sector_t *part_start, sector_t *part_size);
+ /* string.c */
+ extern const struct dentry_operations hfs_dentry_operations;
+-extern int hfs_hash_dentry(const struct dentry *, struct qstr *);
+-extern int hfs_strcmp(const unsigned char *, unsigned int,
+-                    const unsigned char *, unsigned int);
++extern int hfs_hash_dentry(const struct dentry *dentry, struct qstr *this);
++extern int hfs_strcmp(const unsigned char *s1, unsigned int len1,
++                    const unsigned char *s2, unsigned int len2);
+ extern int hfs_compare_dentry(const struct dentry *dentry,
+-              unsigned int len, const char *str, const struct qstr *name);
++                              unsigned int len, const char *str,
++                              const struct qstr *name);
+ /* trans.c */
+-extern void hfs_asc2mac(struct super_block *, struct hfs_name *, const struct qstr *);
+-extern int hfs_mac2asc(struct super_block *, char *, const struct hfs_name *);
++extern void hfs_asc2mac(struct super_block *sb,
++                      struct hfs_name *out, const struct qstr *in);
++extern int hfs_mac2asc(struct super_block *sb,
++                      char *out, const struct hfs_name *in);
+ /* super.c */
+ extern void hfs_mark_mdb_dirty(struct super_block *sb);
+--- a/fs/hfs/inode.c
++++ b/fs/hfs/inode.c
+@@ -50,7 +50,8 @@ static void hfs_write_failed(struct addr
+ }
+ int hfs_write_begin(struct file *file, struct address_space *mapping,
+-              loff_t pos, unsigned len, struct page **pagep, void **fsdata)
++              loff_t pos, unsigned int len, struct page **pagep,
++              void **fsdata)
+ {
+       int ret;
+--- a/fs/hfsplus/bfind.c
++++ b/fs/hfsplus/bfind.c
+@@ -210,7 +210,7 @@ release:
+       return res;
+ }
+-int hfs_brec_read(struct hfs_find_data *fd, void *rec, int rec_len)
++int hfs_brec_read(struct hfs_find_data *fd, void *rec, u32 rec_len)
+ {
+       int res;
+--- a/fs/hfsplus/bnode.c
++++ b/fs/hfsplus/bnode.c
+@@ -20,10 +20,10 @@
+ /* Copy a specified range of bytes from the raw data of a node */
+-void hfs_bnode_read(struct hfs_bnode *node, void *buf, int off, int len)
++void hfs_bnode_read(struct hfs_bnode *node, void *buf, u32 off, u32 len)
+ {
+       struct page **pagep;
+-      int l;
++      u32 l;
+       memset(buf, 0, len);
+@@ -33,7 +33,7 @@ void hfs_bnode_read(struct hfs_bnode *no
+       if (len == 0) {
+               pr_err("requested zero length: "
+                      "NODE: id %u, type %#x, height %u, "
+-                     "node_size %u, offset %d, len %d\n",
++                     "node_size %u, offset %u, len %u\n",
+                      node->this, node->type, node->height,
+                      node->tree->node_size, off, len);
+               return;
+@@ -45,17 +45,17 @@ void hfs_bnode_read(struct hfs_bnode *no
+       pagep = node->page + (off >> PAGE_SHIFT);
+       off &= ~PAGE_MASK;
+-      l = min_t(int, len, PAGE_SIZE - off);
++      l = min_t(u32, len, PAGE_SIZE - off);
+       memcpy_from_page(buf, *pagep, off, l);
+       while ((len -= l) != 0) {
+               buf += l;
+-              l = min_t(int, len, PAGE_SIZE);
++              l = min_t(u32, len, PAGE_SIZE);
+               memcpy_from_page(buf, *++pagep, 0, l);
+       }
+ }
+-u16 hfs_bnode_read_u16(struct hfs_bnode *node, int off)
++u16 hfs_bnode_read_u16(struct hfs_bnode *node, u32 off)
+ {
+       __be16 data;
+       /* TODO: optimize later... */
+@@ -63,7 +63,7 @@ u16 hfs_bnode_read_u16(struct hfs_bnode
+       return be16_to_cpu(data);
+ }
+-u8 hfs_bnode_read_u8(struct hfs_bnode *node, int off)
++u8 hfs_bnode_read_u8(struct hfs_bnode *node, u32 off)
+ {
+       u8 data;
+       /* TODO: optimize later... */
+@@ -71,10 +71,10 @@ u8 hfs_bnode_read_u8(struct hfs_bnode *n
+       return data;
+ }
+-void hfs_bnode_read_key(struct hfs_bnode *node, void *key, int off)
++void hfs_bnode_read_key(struct hfs_bnode *node, void *key, u32 off)
+ {
+       struct hfs_btree *tree;
+-      int key_len;
++      u32 key_len;
+       tree = node->tree;
+       if (node->type == HFS_NODE_LEAF ||
+@@ -86,17 +86,17 @@ void hfs_bnode_read_key(struct hfs_bnode
+       if (key_len > sizeof(hfsplus_btree_key) || key_len < 1) {
+               memset(key, 0, sizeof(hfsplus_btree_key));
+-              pr_err("hfsplus: Invalid key length: %d\n", key_len);
++              pr_err("hfsplus: Invalid key length: %u\n", key_len);
+               return;
+       }
+       hfs_bnode_read(node, key, off, key_len);
+ }
+-void hfs_bnode_write(struct hfs_bnode *node, void *buf, int off, int len)
++void hfs_bnode_write(struct hfs_bnode *node, void *buf, u32 off, u32 len)
+ {
+       struct page **pagep;
+-      int l;
++      u32 l;
+       if (!is_bnode_offset_valid(node, off))
+               return;
+@@ -104,7 +104,7 @@ void hfs_bnode_write(struct hfs_bnode *n
+       if (len == 0) {
+               pr_err("requested zero length: "
+                      "NODE: id %u, type %#x, height %u, "
+-                     "node_size %u, offset %d, len %d\n",
++                     "node_size %u, offset %u, len %u\n",
+                      node->this, node->type, node->height,
+                      node->tree->node_size, off, len);
+               return;
+@@ -116,29 +116,29 @@ void hfs_bnode_write(struct hfs_bnode *n
+       pagep = node->page + (off >> PAGE_SHIFT);
+       off &= ~PAGE_MASK;
+-      l = min_t(int, len, PAGE_SIZE - off);
++      l = min_t(u32, len, PAGE_SIZE - off);
+       memcpy_to_page(*pagep, off, buf, l);
+       set_page_dirty(*pagep);
+       while ((len -= l) != 0) {
+               buf += l;
+-              l = min_t(int, len, PAGE_SIZE);
++              l = min_t(u32, len, PAGE_SIZE);
+               memcpy_to_page(*++pagep, 0, buf, l);
+               set_page_dirty(*pagep);
+       }
+ }
+-void hfs_bnode_write_u16(struct hfs_bnode *node, int off, u16 data)
++void hfs_bnode_write_u16(struct hfs_bnode *node, u32 off, u16 data)
+ {
+       __be16 v = cpu_to_be16(data);
+       /* TODO: optimize later... */
+       hfs_bnode_write(node, &v, off, 2);
+ }
+-void hfs_bnode_clear(struct hfs_bnode *node, int off, int len)
++void hfs_bnode_clear(struct hfs_bnode *node, u32 off, u32 len)
+ {
+       struct page **pagep;
+-      int l;
++      u32 l;
+       if (!is_bnode_offset_valid(node, off))
+               return;
+@@ -146,7 +146,7 @@ void hfs_bnode_clear(struct hfs_bnode *n
+       if (len == 0) {
+               pr_err("requested zero length: "
+                      "NODE: id %u, type %#x, height %u, "
+-                     "node_size %u, offset %d, len %d\n",
++                     "node_size %u, offset %u, len %u\n",
+                      node->this, node->type, node->height,
+                      node->tree->node_size, off, len);
+               return;
+@@ -158,22 +158,22 @@ void hfs_bnode_clear(struct hfs_bnode *n
+       pagep = node->page + (off >> PAGE_SHIFT);
+       off &= ~PAGE_MASK;
+-      l = min_t(int, len, PAGE_SIZE - off);
++      l = min_t(u32, len, PAGE_SIZE - off);
+       memzero_page(*pagep, off, l);
+       set_page_dirty(*pagep);
+       while ((len -= l) != 0) {
+-              l = min_t(int, len, PAGE_SIZE);
++              l = min_t(u32, len, PAGE_SIZE);
+               memzero_page(*++pagep, 0, l);
+               set_page_dirty(*pagep);
+       }
+ }
+-void hfs_bnode_copy(struct hfs_bnode *dst_node, int dst,
+-                  struct hfs_bnode *src_node, int src, int len)
++void hfs_bnode_copy(struct hfs_bnode *dst_node, u32 dst,
++                  struct hfs_bnode *src_node, u32 src, u32 len)
+ {
+       struct page **src_page, **dst_page;
+-      int l;
++      u32 l;
+       hfs_dbg(BNODE_MOD, "copybytes: %u,%u,%u\n", dst, src, len);
+       if (!len)
+@@ -190,12 +190,12 @@ void hfs_bnode_copy(struct hfs_bnode *ds
+       dst &= ~PAGE_MASK;
+       if (src == dst) {
+-              l = min_t(int, len, PAGE_SIZE - src);
++              l = min_t(u32, len, PAGE_SIZE - src);
+               memcpy_page(*dst_page, src, *src_page, src, l);
+               set_page_dirty(*dst_page);
+               while ((len -= l) != 0) {
+-                      l = min_t(int, len, PAGE_SIZE);
++                      l = min_t(u32, len, PAGE_SIZE);
+                       memcpy_page(*++dst_page, 0, *++src_page, 0, l);
+                       set_page_dirty(*dst_page);
+               }
+@@ -227,11 +227,11 @@ void hfs_bnode_copy(struct hfs_bnode *ds
+       }
+ }
+-void hfs_bnode_move(struct hfs_bnode *node, int dst, int src, int len)
++void hfs_bnode_move(struct hfs_bnode *node, u32 dst, u32 src, u32 len)
+ {
+       struct page **src_page, **dst_page;
+       void *src_ptr, *dst_ptr;
+-      int l;
++      u32 l;
+       hfs_dbg(BNODE_MOD, "movebytes: %u,%u,%u\n", dst, src, len);
+       if (!len)
+@@ -301,7 +301,7 @@ void hfs_bnode_move(struct hfs_bnode *no
+               dst &= ~PAGE_MASK;
+               if (src == dst) {
+-                      l = min_t(int, len, PAGE_SIZE - src);
++                      l = min_t(u32, len, PAGE_SIZE - src);
+                       dst_ptr = kmap_local_page(*dst_page) + src;
+                       src_ptr = kmap_local_page(*src_page) + src;
+@@ -311,7 +311,7 @@ void hfs_bnode_move(struct hfs_bnode *no
+                       kunmap_local(dst_ptr);
+                       while ((len -= l) != 0) {
+-                              l = min_t(int, len, PAGE_SIZE);
++                              l = min_t(u32, len, PAGE_SIZE);
+                               dst_ptr = kmap_local_page(*++dst_page);
+                               src_ptr = kmap_local_page(*++src_page);
+                               memmove(dst_ptr, src_ptr, l);
+--- a/fs/hfsplus/brec.c
++++ b/fs/hfsplus/brec.c
+@@ -60,7 +60,7 @@ u16 hfs_brec_keylen(struct hfs_bnode *no
+       return retval;
+ }
+-int hfs_brec_insert(struct hfs_find_data *fd, void *entry, int entry_len)
++int hfs_brec_insert(struct hfs_find_data *fd, void *entry, u32 entry_len)
+ {
+       struct hfs_btree *tree;
+       struct hfs_bnode *node, *new_node;
+--- a/fs/hfsplus/btree.c
++++ b/fs/hfsplus/btree.c
+@@ -344,7 +344,7 @@ static struct hfs_bnode *hfs_bmap_new_bm
+ }
+ /* Make sure @tree has enough space for the @rsvd_nodes */
+-int hfs_bmap_reserve(struct hfs_btree *tree, int rsvd_nodes)
++int hfs_bmap_reserve(struct hfs_btree *tree, u32 rsvd_nodes)
+ {
+       struct inode *inode = tree->inode;
+       struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
+--- a/fs/hfsplus/hfsplus_fs.h
++++ b/fs/hfsplus/hfsplus_fs.h
+@@ -388,21 +388,21 @@ u32 hfsplus_calc_btree_clump_size(u32 bl
+ struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id);
+ void hfs_btree_close(struct hfs_btree *tree);
+ int hfs_btree_write(struct hfs_btree *tree);
+-int hfs_bmap_reserve(struct hfs_btree *tree, int rsvd_nodes);
++int hfs_bmap_reserve(struct hfs_btree *tree, u32 rsvd_nodes);
+ struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree);
+ void hfs_bmap_free(struct hfs_bnode *node);
+ /* bnode.c */
+-void hfs_bnode_read(struct hfs_bnode *node, void *buf, int off, int len);
+-u16 hfs_bnode_read_u16(struct hfs_bnode *node, int off);
+-u8 hfs_bnode_read_u8(struct hfs_bnode *node, int off);
+-void hfs_bnode_read_key(struct hfs_bnode *node, void *key, int off);
+-void hfs_bnode_write(struct hfs_bnode *node, void *buf, int off, int len);
+-void hfs_bnode_write_u16(struct hfs_bnode *node, int off, u16 data);
+-void hfs_bnode_clear(struct hfs_bnode *node, int off, int len);
+-void hfs_bnode_copy(struct hfs_bnode *dst_node, int dst,
+-                  struct hfs_bnode *src_node, int src, int len);
+-void hfs_bnode_move(struct hfs_bnode *node, int dst, int src, int len);
++void hfs_bnode_read(struct hfs_bnode *node, void *buf, u32 off, u32 len);
++u16 hfs_bnode_read_u16(struct hfs_bnode *node, u32 off);
++u8 hfs_bnode_read_u8(struct hfs_bnode *node, u32 off);
++void hfs_bnode_read_key(struct hfs_bnode *node, void *key, u32 off);
++void hfs_bnode_write(struct hfs_bnode *node, void *buf, u32 off, u32 len);
++void hfs_bnode_write_u16(struct hfs_bnode *node, u32 off, u16 data);
++void hfs_bnode_clear(struct hfs_bnode *node, u32 off, u32 len);
++void hfs_bnode_copy(struct hfs_bnode *dst_node, u32 dst,
++                  struct hfs_bnode *src_node, u32 src, u32 len);
++void hfs_bnode_move(struct hfs_bnode *node, u32 dst, u32 src, u32 len);
+ void hfs_bnode_dump(struct hfs_bnode *node);
+ void hfs_bnode_unlink(struct hfs_bnode *node);
+ struct hfs_bnode *hfs_bnode_findhash(struct hfs_btree *tree, u32 cnid);
+@@ -417,7 +417,7 @@ bool hfs_bnode_need_zeroout(struct hfs_b
+ /* brec.c */
+ u16 hfs_brec_lenoff(struct hfs_bnode *node, u16 rec, u16 *off);
+ u16 hfs_brec_keylen(struct hfs_bnode *node, u16 rec);
+-int hfs_brec_insert(struct hfs_find_data *fd, void *entry, int entry_len);
++int hfs_brec_insert(struct hfs_find_data *fd, void *entry, u32 entry_len);
+ int hfs_brec_remove(struct hfs_find_data *fd);
+ /* bfind.c */
+@@ -430,7 +430,7 @@ int hfs_find_rec_by_key(struct hfs_bnode
+ int __hfs_brec_find(struct hfs_bnode *bnode, struct hfs_find_data *fd,
+                   search_strategy_t rec_found);
+ int hfs_brec_find(struct hfs_find_data *fd, search_strategy_t do_key_compare);
+-int hfs_brec_read(struct hfs_find_data *fd, void *rec, int rec_len);
++int hfs_brec_read(struct hfs_find_data *fd, void *rec, u32 rec_len);
+ int hfs_brec_goto(struct hfs_find_data *fd, int cnt);
+ /* catalog.c */
+@@ -588,14 +588,14 @@ hfsplus_btree_lock_class(struct hfs_btre
+ }
+ static inline
+-bool is_bnode_offset_valid(struct hfs_bnode *node, int off)
++bool is_bnode_offset_valid(struct hfs_bnode *node, u32 off)
+ {
+       bool is_valid = off < node->tree->node_size;
+       if (!is_valid) {
+               pr_err("requested invalid offset: "
+                      "NODE: id %u, type %#x, height %u, "
+-                     "node_size %u, offset %d\n",
++                     "node_size %u, offset %u\n",
+                      node->this, node->type, node->height,
+                      node->tree->node_size, off);
+       }
+@@ -604,7 +604,7 @@ bool is_bnode_offset_valid(struct hfs_bn
+ }
+ static inline
+-int check_and_correct_requested_length(struct hfs_bnode *node, int off, int len)
++u32 check_and_correct_requested_length(struct hfs_bnode *node, u32 off, u32 len)
+ {
+       unsigned int node_size;
+@@ -614,12 +614,12 @@ int check_and_correct_requested_length(s
+       node_size = node->tree->node_size;
+       if ((off + len) > node_size) {
+-              int new_len = (int)node_size - off;
++              u32 new_len = node_size - off;
+               pr_err("requested length has been corrected: "
+                      "NODE: id %u, type %#x, height %u, "
+-                     "node_size %u, offset %d, "
+-                     "requested_len %d, corrected_len %d\n",
++                     "node_size %u, offset %u, "
++                     "requested_len %u, corrected_len %u\n",
+                      node->this, node->type, node->height,
+                      node->tree->node_size, off, len, new_len);
diff --git a/queue-6.6/hid-add-haptics-page-defines.patch b/queue-6.6/hid-add-haptics-page-defines.patch
new file mode 100644 (file)
index 0000000..4012211
--- /dev/null
@@ -0,0 +1,78 @@
+From stable+bounces-277063-greg=kroah.com@vger.kernel.org Fri Jul 17 16:13:39 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 17 Jul 2026 10:13:00 -0400
+Subject: HID: add haptics page defines
+To: stable@vger.kernel.org
+Cc: Angela Czubak <aczubak@google.com>, Jonathan Denose <jdenose@google.com>, Benjamin Tissoires <bentiss@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260717141302.1738251-1-sashal@kernel.org>
+
+From: Angela Czubak <aczubak@google.com>
+
+[ Upstream commit 5e0ae59159e3a07391a35865bb79ff335473fa79 ]
+
+Introduce haptic usages as defined in HID Usage Tables specification.
+Add HID units for newton and gram.
+
+Signed-off-by: Angela Czubak <aczubak@google.com>
+Co-developed-by: Jonathan Denose <jdenose@google.com>
+Signed-off-by: Jonathan Denose <jdenose@google.com>
+Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
+Stable-dep-of: 8813b0612275 ("HID: multitouch: fix out-of-bounds bit access on mt_io_flags")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/hid.h |   29 +++++++++++++++++++++++++++++
+ 1 file changed, 29 insertions(+)
+
+--- a/include/linux/hid.h
++++ b/include/linux/hid.h
+@@ -154,6 +154,7 @@ struct hid_item {
+ #define HID_UP_TELEPHONY      0x000b0000
+ #define HID_UP_CONSUMER               0x000c0000
+ #define HID_UP_DIGITIZER      0x000d0000
++#define HID_UP_HAPTIC         0x000e0000
+ #define HID_UP_PID            0x000f0000
+ #define HID_UP_BATTERY                0x00850000
+ #define HID_UP_CAMERA         0x00900000
+@@ -314,6 +315,28 @@ struct hid_item {
+ #define HID_DG_TOOLSERIALNUMBER       0x000d005b
+ #define HID_DG_LATENCYMODE    0x000d0060
++#define HID_HP_SIMPLECONTROLLER       0x000e0001
++#define HID_HP_WAVEFORMLIST   0x000e0010
++#define HID_HP_DURATIONLIST   0x000e0011
++#define HID_HP_AUTOTRIGGER    0x000e0020
++#define HID_HP_MANUALTRIGGER  0x000e0021
++#define HID_HP_AUTOTRIGGERASSOCIATEDCONTROL 0x000e0022
++#define HID_HP_INTENSITY      0x000e0023
++#define HID_HP_REPEATCOUNT    0x000e0024
++#define HID_HP_RETRIGGERPERIOD        0x000e0025
++#define HID_HP_WAVEFORMVENDORPAGE     0x000e0026
++#define HID_HP_WAVEFORMVENDORID       0x000e0027
++#define HID_HP_WAVEFORMCUTOFFTIME     0x000e0028
++#define HID_HP_WAVEFORMNONE   0x000e1001
++#define HID_HP_WAVEFORMSTOP   0x000e1002
++#define HID_HP_WAVEFORMCLICK  0x000e1003
++#define HID_HP_WAVEFORMBUZZCONTINUOUS 0x000e1004
++#define HID_HP_WAVEFORMRUMBLECONTINUOUS       0x000e1005
++#define HID_HP_WAVEFORMPRESS  0x000e1006
++#define HID_HP_WAVEFORMRELEASE        0x000e1007
++#define HID_HP_VENDORWAVEFORMMIN      0x000e2001
++#define HID_HP_VENDORWAVEFORMMAX      0x000e2fff
++
+ #define HID_BAT_ABSOLUTESTATEOFCHARGE 0x00850065
+ #define HID_BAT_CHARGING              0x00850044
+@@ -420,6 +443,12 @@ struct hid_item {
+ #define HID_BOOT_PROTOCOL     0
+ /*
++ * HID units
++ */
++#define HID_UNIT_GRAM         0x0101
++#define HID_UNIT_NEWTON               0xe111
++
++/*
+  * This is the global environment of the parser. This information is
+  * persistent for main-items. The global environment can be saved and
+  * restored with PUSH/POP statements.
diff --git a/queue-6.6/hid-appleir-fix-uaf-on-pending-key_up_timer-in-remove.patch b/queue-6.6/hid-appleir-fix-uaf-on-pending-key_up_timer-in-remove.patch
new file mode 100644 (file)
index 0000000..8dcada3
--- /dev/null
@@ -0,0 +1,150 @@
+From stable+bounces-277094-greg=kroah.com@vger.kernel.org Fri Jul 17 17:00:31 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 17 Jul 2026 10:59:59 -0400
+Subject: HID: appleir: fix UAF on pending key_up_timer in remove()
+To: stable@vger.kernel.org
+Cc: Manish Khadka <maskmemanish@gmail.com>, Jiri Kosina <jkosina@suse.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260717145959.1930377-2-sashal@kernel.org>
+
+From: Manish Khadka <maskmemanish@gmail.com>
+
+[ Upstream commit 75fe87e19d8aff81eb2c64d15d244ab8da4de945 ]
+
+appleir_remove() runs hid_hw_stop() before timer_delete_sync().
+hid_hw_stop() synchronously unregisters the HID input device via
+hid_disconnect() -> hidinput_disconnect() -> input_unregister_device(),
+which drops the last reference and frees the underlying input_dev when
+no userspace handle holds it open.
+
+key_up_tick() reads appleir->input_dev and calls input_report_key() /
+input_sync() on it.  The timer is armed from appleir_raw_event() with
+a HZ/8 (~125 ms) timeout on every keydown and key-repeat report.  If a
+key was pressed shortly before the device is disconnected, the timer
+can fire after hid_hw_stop() has freed input_dev but before the
+teardown drains it.
+
+A simple reorder is not sufficient.  Putting the timer drain first
+still leaves a window where a USB URB completion (raw_event) running
+during hid_hw_stop() can call mod_timer() and re-arm the timer, which
+then fires after hidinput_disconnect() has freed input_dev.  The same
+URB-completion window also lets raw_event() reach key_up(), key_down()
+and battery_flat() directly, all of which dereference
+appleir->input_dev.
+
+Introduce a 'removing' flag on struct appleir, gated by the existing
+spinlock.  appleir_remove() sets the flag under the lock and then
+shuts down the timer with timer_shutdown_sync(), which both drains any
+in-flight callback and permanently disables further mod_timer() calls.
+appleir_raw_event() and key_up_tick() bail out early if the flag is
+set, so no path can arm or run the timer, or dereference
+appleir->input_dev, after remove() has started tearing down.
+
+The keyrepeat and flatbattery branches of appleir_raw_event()
+previously called into the input layer without holding the spinlock;
+take it now so the flag check is well-defined.  This incidentally
+closes a pre-existing read-side race on appleir->current_key in the
+keyrepeat branch.
+
+This bug is structurally a sibling of commit 4db2af929279 ("HID:
+appletb-kbd: fix UAF in inactivity-timer cleanup path") and has been
+present since the driver was introduced.
+
+Fixes: 9a4a5574ce42 ("HID: appleir: add support for Apple ir devices")
+Cc: stable@vger.kernel.org
+Signed-off-by: Manish Khadka <maskmemanish@gmail.com>
+Signed-off-by: Jiri Kosina <jkosina@suse.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/hid/hid-appleir.c |   45 +++++++++++++++++++++++++++++++++++----------
+ 1 file changed, 35 insertions(+), 10 deletions(-)
+
+--- a/drivers/hid/hid-appleir.c
++++ b/drivers/hid/hid-appleir.c
+@@ -109,9 +109,10 @@ struct appleir {
+       struct hid_device *hid;
+       unsigned short keymap[ARRAY_SIZE(appleir_key_table)];
+       struct timer_list key_up_timer; /* timer for key up */
+-      spinlock_t lock;                /* protects .current_key */
++      spinlock_t lock;                /* protects .current_key, .removing */
+       int current_key;                /* the currently pressed key */
+       int prev_key_idx;               /* key index in a 2 packets message */
++      bool removing;                  /* set during teardown; gates input_dev access */
+ };
+ static int get_key(int data)
+@@ -172,7 +173,7 @@ static void key_up_tick(struct timer_lis
+       unsigned long flags;
+       spin_lock_irqsave(&appleir->lock, flags);
+-      if (appleir->current_key) {
++      if (!appleir->removing && appleir->current_key) {
+               key_up(hid, appleir, appleir->current_key);
+               appleir->current_key = 0;
+       }
+@@ -195,6 +196,10 @@ static int appleir_raw_event(struct hid_
+               int index;
+               spin_lock_irqsave(&appleir->lock, flags);
++              if (appleir->removing) {
++                      spin_unlock_irqrestore(&appleir->lock, flags);
++                      goto out;
++              }
+               /*
+                * If we already have a key down, take it up before marking
+                * this one down
+@@ -229,17 +234,25 @@ static int appleir_raw_event(struct hid_
+       appleir->prev_key_idx = 0;
+       if (!memcmp(data, keyrepeat, sizeof(keyrepeat))) {
+-              key_down(hid, appleir, appleir->current_key);
+-              /*
+-               * Remote doesn't do key up, either pull them up, in the test
+-               * above, or here set a timer which pulls them up after 1/8 s
+-               */
+-              mod_timer(&appleir->key_up_timer, jiffies + HZ / 8);
++              spin_lock_irqsave(&appleir->lock, flags);
++              if (!appleir->removing) {
++                      key_down(hid, appleir, appleir->current_key);
++                      /*
++                       * Remote doesn't do key up, either pull them up, in
++                       * the test above, or here set a timer which pulls them
++                       * up after 1/8 s
++                       */
++                      mod_timer(&appleir->key_up_timer, jiffies + HZ / 8);
++              }
++              spin_unlock_irqrestore(&appleir->lock, flags);
+               goto out;
+       }
+       if (!memcmp(data, flatbattery, sizeof(flatbattery))) {
+-              battery_flat(appleir);
++              spin_lock_irqsave(&appleir->lock, flags);
++              if (!appleir->removing)
++                      battery_flat(appleir);
++              spin_unlock_irqrestore(&appleir->lock, flags);
+               /* Fall through */
+       }
+@@ -318,8 +331,20 @@ fail:
+ static void appleir_remove(struct hid_device *hid)
+ {
+       struct appleir *appleir = hid_get_drvdata(hid);
++      unsigned long flags;
++
++      /*
++       * Mark the driver as tearing down so that any concurrent raw_event
++       * (e.g. from a USB URB completion that hid_hw_stop() has not yet
++       * killed) and the key_up_timer softirq stop touching input_dev
++       * before hid_hw_stop() frees it via hidinput_disconnect().
++       */
++      spin_lock_irqsave(&appleir->lock, flags);
++      appleir->removing = true;
++      spin_unlock_irqrestore(&appleir->lock, flags);
++
++      timer_shutdown_sync(&appleir->key_up_timer);
+       hid_hw_stop(hid);
+-      timer_delete_sync(&appleir->key_up_timer);
+ }
+ static const struct hid_device_id appleir_devices[] = {
diff --git a/queue-6.6/hid-multitouch-fix-out-of-bounds-bit-access-on-mt_io_flags.patch b/queue-6.6/hid-multitouch-fix-out-of-bounds-bit-access-on-mt_io_flags.patch
new file mode 100644 (file)
index 0000000..d8eb48c
--- /dev/null
@@ -0,0 +1,158 @@
+From stable+bounces-277064-greg=kroah.com@vger.kernel.org Fri Jul 17 16:13:09 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 17 Jul 2026 10:13:01 -0400
+Subject: HID: multitouch: fix out-of-bounds bit access on mt_io_flags
+To: stable@vger.kernel.org
+Cc: Trung Nguyen <trungnh@cystack.net>, Benjamin Tissoires <bentiss@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260717141302.1738251-2-sashal@kernel.org>
+
+From: Trung Nguyen <trungnh@cystack.net>
+
+[ Upstream commit 8813b0612275cc61fe9e6603d0ee019247ade6be ]
+
+mt_io_flags is a single unsigned long, but mt_process_slot(),
+mt_release_pending_palms() and mt_release_contacts() use it as a
+per-slot bitmap indexed by the slot number. That slot number is only
+bounded by td->maxcontacts, which is taken from the device's
+ContactCountMaximum feature report and can be up to 255, not by
+BITS_PER_LONG.
+
+As a result, a multitouch device that advertises a large contact count
+makes set_bit()/clear_bit() operate past the mt_io_flags word and
+corrupt the adjacent members of struct mt_device. The sticky-fingers
+release timer is the easiest way to reach this. mt_release_contacts()
+runs
+
+       for (i = 0; i < mt->num_slots; i++)
+               clear_bit(i, &td->mt_io_flags);
+
+with num_slots == maxcontacts. For maxcontacts around 250 the loop
+clears the bits that overlap td->applications.next, zeroing that list
+head, and the list_for_each_entry() that immediately follows then
+dereferences NULL. The kernel panics from timer (softirq) context. On a
+KASAN build this shows up as a general protection fault in
+mt_release_contacts() with a null-ptr-deref at offset 0x58, which is
+offsetof(struct mt_application, num_received).
+
+The state is reachable from an untrusted USB or Bluetooth HID
+multitouch device; no local privileges are required.
+
+Store the per-slot active state in a separately allocated bitmap sized
+for maxcontacts, the same pattern already used for pending_palm_slots,
+and keep only MT_IO_FLAGS_RUNNING in mt_io_flags. The two
+"mt_io_flags & MT_IO_SLOTS_MASK" arming checks become
+bitmap_empty(td->active_slots, td->maxcontacts).
+
+Move MT_IO_FLAGS_RUNNING back to bit 0. It was bumped to bit 32 by the
+same commit to leave the low byte for the slot bits; with the slot bits
+gone it fits in bit 0 again, which also keeps it within the unsigned
+long on 32-bit.
+
+Fixes: 46f781e0d151 ("HID: multitouch: fix sticky fingers")
+Cc: stable@vger.kernel.org
+Signed-off-by: Trung Nguyen <trungnh@cystack.net>
+Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/hid/hid-multitouch.c |   32 ++++++++++++++++++++------------
+ 1 file changed, 20 insertions(+), 12 deletions(-)
+
+--- a/drivers/hid/hid-multitouch.c
++++ b/drivers/hid/hid-multitouch.c
+@@ -31,6 +31,7 @@
+  * [1] https://gitlab.freedesktop.org/libevdev/hid-tools
+  */
++#include <linux/bitmap.h>
+ #include <linux/device.h>
+ #include <linux/hid.h>
+ #include <linux/module.h>
+@@ -83,8 +84,7 @@ enum latency_mode {
+       HID_LATENCY_HIGH = 1,
+ };
+-#define MT_IO_SLOTS_MASK              GENMASK(7, 0) /* reserve first 8 bits for slot tracking */
+-#define MT_IO_FLAGS_RUNNING           32
++#define MT_IO_FLAGS_RUNNING           0
+ static const bool mtrue = true;               /* default for true */
+ static const bool mfalse;             /* default for false */
+@@ -160,10 +160,9 @@ struct mt_device {
+       struct mt_class mtclass;        /* our mt device class */
+       struct timer_list release_timer;        /* to release sticky fingers */
+       struct hid_device *hdev;        /* hid_device we're attached to */
+-      unsigned long mt_io_flags;      /* mt flags (MT_IO_FLAGS_RUNNING)
+-                                       * first 8 bits are reserved for keeping the slot
+-                                       * states, this is fine because we only support up
+-                                       * to 250 slots (MT_MAX_MAXCONTACT)
++      unsigned long mt_io_flags;      /* mt flags (MT_IO_FLAGS_RUNNING) */
++      unsigned long *active_slots;    /* bitmap of slots with an active
++                                       * contact, sized for maxcontacts
+                                        */
+       __u8 inputmode_value;   /* InputMode HID feature value */
+       __u8 maxcontacts;
+@@ -947,7 +946,7 @@ static void mt_release_pending_palms(str
+       for_each_set_bit(slotnum, app->pending_palm_slots, td->maxcontacts) {
+               clear_bit(slotnum, app->pending_palm_slots);
+-              clear_bit(slotnum, &td->mt_io_flags);
++              clear_bit(slotnum, td->active_slots);
+               input_mt_slot(input, slotnum);
+               input_mt_report_slot_inactive(input);
+@@ -1153,9 +1152,9 @@ static int mt_process_slot(struct mt_dev
+               input_event(input, EV_ABS, ABS_MT_TOUCH_MAJOR, major);
+               input_event(input, EV_ABS, ABS_MT_TOUCH_MINOR, minor);
+-              set_bit(slotnum, &td->mt_io_flags);
++              set_bit(slotnum, td->active_slots);
+       } else {
+-              clear_bit(slotnum, &td->mt_io_flags);
++              clear_bit(slotnum, td->active_slots);
+       }
+       return 0;
+@@ -1290,7 +1289,7 @@ static void mt_touch_report(struct hid_d
+        * defect.
+        */
+       if (app->quirks & MT_QUIRK_STICKY_FINGERS) {
+-              if (td->mt_io_flags & MT_IO_SLOTS_MASK)
++              if (!bitmap_empty(td->active_slots, td->maxcontacts))
+                       mod_timer(&td->release_timer,
+                                 jiffies + msecs_to_jiffies(100));
+               else
+@@ -1330,6 +1329,15 @@ static int mt_touch_input_configured(str
+       if (td->is_buttonpad)
+               __set_bit(INPUT_PROP_BUTTONPAD, input->propbit);
++      if (!td->active_slots) {
++              td->active_slots = devm_kcalloc(&td->hdev->dev,
++                                              BITS_TO_LONGS(td->maxcontacts),
++                                              sizeof(long),
++                                              GFP_KERNEL);
++              if (!td->active_slots)
++                      return -ENOMEM;
++      }
++
+       app->pending_palm_slots = devm_kcalloc(&hi->input->dev,
+                                              BITS_TO_LONGS(td->maxcontacts),
+                                              sizeof(long),
+@@ -1738,7 +1746,7 @@ static void mt_release_contacts(struct h
+                       for (i = 0; i < mt->num_slots; i++) {
+                               input_mt_slot(input_dev, i);
+                               input_mt_report_slot_inactive(input_dev);
+-                              clear_bit(i, &td->mt_io_flags);
++                              clear_bit(i, td->active_slots);
+                       }
+                       input_mt_sync_frame(input_dev);
+                       input_sync(input_dev);
+@@ -1761,7 +1769,7 @@ static void mt_expired_timeout(struct ti
+        */
+       if (test_and_set_bit_lock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags))
+               return;
+-      if (td->mt_io_flags & MT_IO_SLOTS_MASK)
++      if (!bitmap_empty(td->active_slots, td->maxcontacts))
+               mt_release_contacts(hdev);
+       clear_bit_unlock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags);
+ }
diff --git a/queue-6.6/iio-common-st_sensors-honour-channel-endianness-in-read_axis_data.patch b/queue-6.6/iio-common-st_sensors-honour-channel-endianness-in-read_axis_data.patch
new file mode 100644 (file)
index 0000000..5270d6b
--- /dev/null
@@ -0,0 +1,109 @@
+From stable+bounces-274001-greg=kroah.com@vger.kernel.org Mon Jul 13 22:39:34 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 13 Jul 2026 16:39:24 -0400
+Subject: iio: common: st_sensors: honour channel endianness in read_axis_data
+To: stable@vger.kernel.org
+Cc: Herman van Hazendonk <github.com@herrie.org>, Andy Shevchenko <andriy.shevchenko@intel.com>, Jonathan Cameron <jic23@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260713203924.2143667-2-sashal@kernel.org>
+
+From: Herman van Hazendonk <github.com@herrie.org>
+
+[ Upstream commit 55052184ac9011db2ea983e54d6c21f0b1079a12 ]
+
+st_sensors_read_axis_data() unconditionally decoded multi-byte
+results with get_unaligned_le16() / get_unaligned_le24() regardless
+of the channel's declared scan_type.endianness.
+
+For every ST sensor that has used this helper since it was introduced
+this happened to be fine because the ST IMU/accel/gyro/pressure
+families publish their data registers as little-endian and the
+channel specs in those drivers declare IIO_LE accordingly.
+
+The LSM303DLH magnetometer however publishes its X/Y/Z output as a
+pair of big-endian bytes (the H register sits at the lower address,
+0x03/0x05/0x07, and the L register immediately after), and its
+channel specs in st_magn_core.c correctly declare IIO_BE -- but
+read_axis_data() ignored that and decoded as little-endian, swapping
+the high and low bytes of every magnetometer sample. The LSM303DLHC
+and LSM303DLM share the same st_magn_16bit_channels (IIO_BE) and
+were therefore byte-swapped by the same bug; users of those parts
+will see different in_magn_*_raw values after this fix lands.
+
+The bug is most visible on a stationary chip: in earth's field the
+true X reading is small and the high byte sits at 0x00, so swapping
+the bytes pins sysfs X at exactly the low byte's pattern (e.g. 0x00F0
+= 240). Y and Z still appear "to vary" because their magnitudes are
+larger and the noise in the low byte produces big swings in the
+swapped high byte:
+
+  before (LSM303DLH flat, sysfs in_magn_*_raw):
+      X=240 (stuck), Y= 12032..23296, Z=-16128..-9728
+
+  after (direct i2c-dev big-endian decode, same chip same orientation):
+      X≈-4096, Y≈210, Z≈80     (sensible values reflecting earth's
+                                ambient field at low gauss range)
+
+Fix read_axis_data() to dispatch on ch->scan_type.endianness and
+call get_unaligned_be16() / get_unaligned_be24() when the channel
+declares IIO_BE. Existing IIO_LE consumers (st_accel, st_gyro,
+st_pressure, st_lsm6dsx and others) are unaffected because their
+channel specs already declare IIO_LE and the LE path is unchanged.
+
+While restructuring the branches, replace the previously implicit
+silent-success-with-uninitialised-*data fall-through for
+byte_for_channel outside 1..3 with an explicit return -EINVAL. No
+in-tree ST sensor publishes such a channel, but the new behaviour
+is strictly safer than handing userspace garbage.
+
+Fixes: 23491b513bcd ("iio:common: Add STMicroelectronics common library")
+Cc: stable@vger.kernel.org
+Assisted-by: Claude:claude-opus-4-7 sparse smatch clang-analyzer coccinelle checkpatch
+Assisted-by: Sashiko:claude-opus-4-7
+Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
+Reviewed-by: Andy Shevchenko <andriy.shevchenko@intel.com>
+Signed-off-by: Jonathan Cameron <jic23@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/iio/common/st_sensors/st_sensors_core.c |   23 +++++++++++++++++------
+ 1 file changed, 17 insertions(+), 6 deletions(-)
+
+--- a/drivers/iio/common/st_sensors/st_sensors_core.c
++++ b/drivers/iio/common/st_sensors/st_sensors_core.c
+@@ -498,6 +498,7 @@ static int st_sensors_read_axis_data(str
+       u8 *outdata;
+       struct st_sensor_data *sdata = iio_priv(indio_dev);
+       unsigned int byte_for_channel;
++      u32 tmp;
+       byte_for_channel = DIV_ROUND_UP(ch->scan_type.realbits +
+                                       ch->scan_type.shift, 8);
+@@ -510,12 +511,22 @@ static int st_sensors_read_axis_data(str
+       if (err < 0)
+               goto st_sensors_free_memory;
+-      if (byte_for_channel == 1)
+-              *data = (s8)*outdata;
+-      else if (byte_for_channel == 2)
+-              *data = (s16)get_unaligned_le16(outdata);
+-      else if (byte_for_channel == 3)
+-              *data = (s32)sign_extend32(get_unaligned_le24(outdata), 23);
++      if (byte_for_channel == 1) {
++              tmp = *outdata;
++      } else if (byte_for_channel == 2) {
++              if (ch->scan_type.endianness == IIO_BE)
++                      tmp = get_unaligned_be16(outdata);
++              else
++                      tmp = get_unaligned_le16(outdata);
++      } else if (byte_for_channel == 3) {
++              if (ch->scan_type.endianness == IIO_BE)
++                      tmp = get_unaligned_be24(outdata);
++              else
++                      tmp = get_unaligned_le24(outdata);
++      } else {
++              return -EINVAL;
++      }
++      *data = sign_extend32(tmp, BYTES_TO_BITS(byte_for_channel) - 1);
+ st_sensors_free_memory:
+       kfree(outdata);
diff --git a/queue-6.6/iio-hid-sensor-rotation-fix-stale-or-zero-output-when-reading-raw-values.patch b/queue-6.6/iio-hid-sensor-rotation-fix-stale-or-zero-output-when-reading-raw-values.patch
new file mode 100644 (file)
index 0000000..cab095a
--- /dev/null
@@ -0,0 +1,89 @@
+From stable+bounces-273949-greg=kroah.com@vger.kernel.org Mon Jul 13 20:16:49 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 13 Jul 2026 14:13:19 -0400
+Subject: iio: hid-sensor-rotation: Fix stale or zero output when reading raw values
+To: stable@vger.kernel.org
+Cc: Zhang Lixu <lixu.zhang@intel.com>, Andy Shevchenko <andriy.shevchenko@intel.com>, Stable@vger.kernel.org, Jonathan Cameron <jic23@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260713181319.1932247-2-sashal@kernel.org>
+
+From: Zhang Lixu <lixu.zhang@intel.com>
+
+[ Upstream commit 3ce8d099e0afc5a7da75a2007a67f67c4f5a4af1 ]
+
+When reading the raw quaternion attribute (in_rot_quaternion_raw), the
+driver currently returns either all zeros (if the sensor was never enabled)
+or stale data (if the sensor was previously enabled) because it reads from
+the internal buffer without explicitly requesting a new sample from the
+sensor.
+
+To fix this, power up the sensor, call sensor_hub_input_attr_read_values()
+to issue a synchronous GET_REPORT and receive the full quaternion data
+directly into a local buffer, then decode the four components.
+
+Fixes: fc18dddc0625 ("iio: hid-sensors: Added device rotation support")
+Signed-off-by: Zhang Lixu <lixu.zhang@intel.com>
+Reviewed-by: Andy Shevchenko <andriy.shevchenko@intel.com>
+Cc: <Stable@vger.kernel.org>
+Signed-off-by: Jonathan Cameron <jic23@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/iio/orientation/hid-sensor-rotation.c |   40 ++++++++++++++++++++++++--
+ 1 file changed, 38 insertions(+), 2 deletions(-)
+
+--- a/drivers/iio/orientation/hid-sensor-rotation.c
++++ b/drivers/iio/orientation/hid-sensor-rotation.c
+@@ -69,6 +69,13 @@ static int dev_rot_read_raw(struct iio_d
+                               long mask)
+ {
+       struct dev_rot_state *rot_state = iio_priv(indio_dev);
++      struct hid_sensor_hub_device *hsdev = rot_state->common_attributes.hsdev;
++      struct hid_sensor_hub_attribute_info *info = &rot_state->quaternion;
++      u32 usage_id = HID_USAGE_SENSOR_ORIENT_QUATERNION;
++      union {
++              s16 val16[4];
++              s32 val32[4];
++      } raw_buf;
+       int ret_type;
+       int i;
+@@ -78,8 +85,37 @@ static int dev_rot_read_raw(struct iio_d
+       switch (mask) {
+       case IIO_CHAN_INFO_RAW:
+               if (size >= 4) {
+-                      for (i = 0; i < 4; ++i)
+-                              vals[i] = rot_state->scan.sampled_vals[i];
++                      if (info->size <= 0 || info->size > sizeof(raw_buf))
++                              return -EINVAL;
++
++                      hid_sensor_power_state(&rot_state->common_attributes, true);
++
++                      ret_type = sensor_hub_input_attr_read_values(hsdev,
++                                                                   hsdev->usage,
++                                                                   usage_id,
++                                                                   info->report_id,
++                                                                   SENSOR_HUB_SYNC,
++                                                                   info->size,
++                                                                   (u8 *)&raw_buf);
++
++                      hid_sensor_power_state(&rot_state->common_attributes, false);
++
++                      if (ret_type < 0)
++                              return ret_type;
++
++                      switch (info->size) {
++                      case sizeof(raw_buf.val16):
++                              for (i = 0; i < ARRAY_SIZE(raw_buf.val16); i++)
++                                      vals[i] = raw_buf.val16[i];
++                              break;
++                      case sizeof(raw_buf.val32):
++                              for (i = 0; i < ARRAY_SIZE(raw_buf.val32); i++)
++                                      vals[i] = raw_buf.val32[i];
++                              break;
++                      default:
++                              return -EINVAL;
++                      }
++
+                       ret_type = IIO_VAL_INT_MULTIPLE;
+                       *val_len =  4;
+               } else
diff --git a/queue-6.6/iio-imu-adis-add-irqf_no_thread-to-non-fifo-trigger-irq.patch b/queue-6.6/iio-imu-adis-add-irqf_no_thread-to-non-fifo-trigger-irq.patch
new file mode 100644 (file)
index 0000000..a285074
--- /dev/null
@@ -0,0 +1,49 @@
+From stable+bounces-273934-greg=kroah.com@vger.kernel.org Mon Jul 13 19:57:57 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 13 Jul 2026 13:57:46 -0400
+Subject: iio: imu: adis: add IRQF_NO_THREAD to non-FIFO trigger IRQ
+To: stable@vger.kernel.org
+Cc: Runyu Xiao <runyu.xiao@seu.edu.cn>, Jonathan Cameron <jic23@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260713175746.1907755-1-sashal@kernel.org>
+
+From: Runyu Xiao <runyu.xiao@seu.edu.cn>
+
+[ Upstream commit 6e1b9bff1202da55c464e36bd34a2b6863d7fe30 ]
+
+devm_adis_probe_trigger() registers iio_trigger_generic_data_rdy_poll()
+through devm_request_irq() on the non-FIFO path, but it does not add
+IRQF_NO_THREAD to the IRQ flags.
+
+When the kernel is booted with forced IRQ threading, the parent IRQ can
+otherwise be threaded by the IRQ core and the subsequent IIO trigger
+child IRQ is then dispatched from irq/... thread context instead of
+hardirq context. Because iio_trigger_generic_data_rdy_poll()
+immediately drives iio_trigger_poll(), this violates the hardirq-only
+IIO trigger helper contract and can push downstream trigger consumers
+through the wrong execution context.
+
+Add IRQF_NO_THREAD on top of the existing adis->irq_flag value for the
+non-FIFO request_irq() path, while preserving the current trigger
+polarity and IRQF_NO_AUTOEN behavior.
+
+Fixes: fec86c6b8369 ("iio: imu: adis: Add Managed device functions")
+Cc: stable@vger.kernel.org
+Signed-off-by: Runyu Xiao <runyu.xiao@seu.edu.cn>
+Signed-off-by: Jonathan Cameron <jic23@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/iio/imu/adis_trigger.c |    2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/iio/imu/adis_trigger.c
++++ b/drivers/iio/imu/adis_trigger.c
+@@ -79,7 +79,7 @@ int devm_adis_probe_trigger(struct adis
+       ret = devm_request_irq(&adis->spi->dev, adis->spi->irq,
+                              &iio_trigger_generic_data_rdy_poll,
+-                             adis->irq_flag,
++                             adis->irq_flag | IRQF_NO_THREAD,
+                              indio_dev->name,
+                              adis->trig);
+       if (ret)
diff --git a/queue-6.6/iio-imu-inv_icm42600-fix-timestamping-by-limiting-fifo-reading.patch b/queue-6.6/iio-imu-inv_icm42600-fix-timestamping-by-limiting-fifo-reading.patch
new file mode 100644 (file)
index 0000000..a644523
--- /dev/null
@@ -0,0 +1,72 @@
+From stable+bounces-273961-greg=kroah.com@vger.kernel.org Mon Jul 13 20:33:59 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 13 Jul 2026 14:33:48 -0400
+Subject: iio: imu: inv_icm42600: fix timestamping by limiting FIFO reading
+To: stable@vger.kernel.org
+Cc: Jean-Baptiste Maneyrol <jean-baptiste.maneyrol@tdk.com>, Jonathan Cameron <jic23@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260713183348.1951183-4-sashal@kernel.org>
+
+From: Jean-Baptiste Maneyrol <jean-baptiste.maneyrol@tdk.com>
+
+[ Upstream commit affe3f077d7a4eeb25937f5323ff059a54b4712c ]
+
+Timestamps are made by measuring the chip clock using the watermark
+interrupts. If we read more than watermark samples as done today, we
+are reducing the period between interrupts and distort the time
+measurement. Fix that by reading only watermark samples in the
+interrupt case.
+
+Fixes: 7f85e42a6c54 ("iio: imu: inv_icm42600: add buffer support in iio devices")
+Cc: stable@vger.kernel.org
+Signed-off-by: Jean-Baptiste Maneyrol <jean-baptiste.maneyrol@tdk.com>
+Signed-off-by: Jonathan Cameron <jic23@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c |    9 +++++----
+ drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.h |    1 +
+ 2 files changed, 6 insertions(+), 4 deletions(-)
+
+--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c
++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c
+@@ -257,6 +257,7 @@ int inv_icm42600_buffer_update_watermark
+       /* compute watermark value in bytes */
+       wm_size = watermark * packet_size;
++      st->fifo.watermark.value = watermark;
+       /* changing FIFO watermark requires to turn off watermark interrupt */
+       ret = regmap_update_bits_check(st->map, INV_ICM42600_REG_INT_SOURCE0,
+@@ -476,11 +477,10 @@ int inv_icm42600_buffer_fifo_read(struct
+       st->fifo.nb.accel = 0;
+       st->fifo.nb.total = 0;
+-      /* compute maximum FIFO read size */
++      /* compute maximum FIFO read size (watermark for max = 0 interrupt case) */
+       if (max == 0)
+-              max_count = sizeof(st->fifo.data);
+-      else
+-              max_count = max * inv_icm42600_get_packet_size(st->fifo.en);
++              max = st->fifo.watermark.value;
++      max_count = max * inv_icm42600_get_packet_size(st->fifo.en);
+       /* read FIFO count value */
+       raw_fifo_count = (__be16 *)st->buffer;
+@@ -592,6 +592,7 @@ int inv_icm42600_buffer_init(struct inv_
+       st->fifo.watermark.eff_gyro = 1;
+       st->fifo.watermark.eff_accel = 1;
++      st->fifo.watermark.value = 1;
+       /*
+        * Default FIFO configuration (bits 7 to 5)
+--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.h
++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.h
+@@ -34,6 +34,7 @@ struct inv_icm42600_fifo {
+               unsigned int accel;
+               unsigned int eff_gyro;
+               unsigned int eff_accel;
++              unsigned int value;
+       } watermark;
+       size_t count;
+       struct {
diff --git a/queue-6.6/iio-imu-inv_icm42600-stabilized-timestamp-in-interrupt.patch b/queue-6.6/iio-imu-inv_icm42600-stabilized-timestamp-in-interrupt.patch
new file mode 100644 (file)
index 0000000..c8ee896
--- /dev/null
@@ -0,0 +1,116 @@
+From stable+bounces-273960-greg=kroah.com@vger.kernel.org Mon Jul 13 20:33:58 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 13 Jul 2026 14:33:47 -0400
+Subject: iio: imu: inv_icm42600: stabilized timestamp in interrupt
+To: stable@vger.kernel.org
+Cc: Jean-Baptiste Maneyrol <jean-baptiste.maneyrol@tdk.com>, Jonathan Cameron <Jonathan.Cameron@huawei.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260713183348.1951183-3-sashal@kernel.org>
+
+From: Jean-Baptiste Maneyrol <jean-baptiste.maneyrol@tdk.com>
+
+[ Upstream commit d7bd473632d07f8a54655c270c0940cc3671c548 ]
+
+Use IRQF_ONESHOT flag to ensure the timestamp is not updated in the
+hard handler during the thread handler. And compute and use the
+effective watermark value that correspond to this first timestamp.
+
+This way we can ensure the timestamp is always corresponding to the
+value used by the timestamping mechanism. Otherwise, it is possible
+that between FIFO count read and FIFO processing the timestamp is
+overwritten in the hard handler.
+
+Fixes: ec74ae9fd37c ("iio: imu: inv_icm42600: add accurate timestamping")
+Cc: stable@vger.kernel.org
+Signed-off-by: Jean-Baptiste Maneyrol <jean-baptiste.maneyrol@tdk.com>
+Link: https://lore.kernel.org/r/20240529154717.651863-1-inv.git-commit@tdk.com
+Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
+Stable-dep-of: affe3f077d7a ("iio: imu: inv_icm42600: fix timestamping by limiting FIFO reading")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c |   19 +++++++++++++++++--
+ drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.h |    2 ++
+ drivers/iio/imu/inv_icm42600/inv_icm42600_core.c   |    1 +
+ 3 files changed, 20 insertions(+), 2 deletions(-)
+
+--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c
++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c
+@@ -222,10 +222,15 @@ int inv_icm42600_buffer_update_watermark
+       latency_accel = period_accel * wm_accel;
+       /* 0 value for watermark means that the sensor is turned off */
++      if (wm_gyro == 0 && wm_accel == 0)
++              return 0;
++
+       if (latency_gyro == 0) {
+               watermark = wm_accel;
++              st->fifo.watermark.eff_accel = wm_accel;
+       } else if (latency_accel == 0) {
+               watermark = wm_gyro;
++              st->fifo.watermark.eff_gyro = wm_gyro;
+       } else {
+               /* compute the smallest latency that is a multiple of both */
+               if (latency_gyro <= latency_accel)
+@@ -241,6 +246,13 @@ int inv_icm42600_buffer_update_watermark
+               watermark = latency / period;
+               if (watermark < 1)
+                       watermark = 1;
++              /* update effective watermark */
++              st->fifo.watermark.eff_gyro = latency / period_gyro;
++              if (st->fifo.watermark.eff_gyro < 1)
++                      st->fifo.watermark.eff_gyro = 1;
++              st->fifo.watermark.eff_accel = latency / period_accel;
++              if (st->fifo.watermark.eff_accel < 1)
++                      st->fifo.watermark.eff_accel = 1;
+       }
+       /* compute watermark value in bytes */
+@@ -517,7 +529,7 @@ int inv_icm42600_buffer_fifo_parse(struc
+       /* handle gyroscope timestamp and FIFO data parsing */
+       if (st->fifo.nb.gyro > 0) {
+               ts = iio_priv(st->indio_gyro);
+-              inv_sensors_timestamp_interrupt(ts, st->fifo.nb.gyro,
++              inv_sensors_timestamp_interrupt(ts, st->fifo.watermark.eff_gyro,
+                                               st->timestamp.gyro);
+               ret = inv_icm42600_gyro_parse_fifo(st->indio_gyro);
+               if (ret)
+@@ -527,7 +539,7 @@ int inv_icm42600_buffer_fifo_parse(struc
+       /* handle accelerometer timestamp and FIFO data parsing */
+       if (st->fifo.nb.accel > 0) {
+               ts = iio_priv(st->indio_accel);
+-              inv_sensors_timestamp_interrupt(ts, st->fifo.nb.accel,
++              inv_sensors_timestamp_interrupt(ts, st->fifo.watermark.eff_accel,
+                                               st->timestamp.accel);
+               ret = inv_icm42600_accel_parse_fifo(st->indio_accel);
+               if (ret)
+@@ -578,6 +590,9 @@ int inv_icm42600_buffer_init(struct inv_
+       unsigned int val;
+       int ret;
++      st->fifo.watermark.eff_gyro = 1;
++      st->fifo.watermark.eff_accel = 1;
++
+       /*
+        * Default FIFO configuration (bits 7 to 5)
+        * - use invalid value
+--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.h
++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.h
+@@ -32,6 +32,8 @@ struct inv_icm42600_fifo {
+       struct {
+               unsigned int gyro;
+               unsigned int accel;
++              unsigned int eff_gyro;
++              unsigned int eff_accel;
+       } watermark;
+       size_t count;
+       struct {
+--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c
++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c
+@@ -523,6 +523,7 @@ static int inv_icm42600_irq_init(struct
+       if (ret)
+               return ret;
++      irq_type |= IRQF_ONESHOT;
+       return devm_request_threaded_irq(dev, irq, inv_icm42600_irq_timestamp,
+                                        inv_icm42600_irq_handler, irq_type,
+                                        "inv_icm42600", st);
diff --git a/queue-6.6/iio-invensense-fix-timestamp-glitches-when-switching-frequency.patch b/queue-6.6/iio-invensense-fix-timestamp-glitches-when-switching-frequency.patch
new file mode 100644 (file)
index 0000000..2ac9391
--- /dev/null
@@ -0,0 +1,185 @@
+From stable+bounces-273959-greg=kroah.com@vger.kernel.org Mon Jul 13 20:34:02 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 13 Jul 2026 14:33:46 -0400
+Subject: iio: invensense: fix timestamp glitches when switching frequency
+To: stable@vger.kernel.org
+Cc: Jean-Baptiste Maneyrol <jean-baptiste.maneyrol@tdk.com>, Stable@vger.kernel.org, Jonathan Cameron <Jonathan.Cameron@huawei.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260713183348.1951183-2-sashal@kernel.org>
+
+From: Jean-Baptiste Maneyrol <jean-baptiste.maneyrol@tdk.com>
+
+[ Upstream commit bf8367b00c33c64a9391c262bb2e11d274c9f2a4 ]
+
+When a sensor is running and there is a FIFO frequency change due to
+another sensor turned on/off, there are glitches on timestamp. Fix that
+by using only interrupt timestamp when there is the corresponding sensor
+data in the FIFO.
+
+Delete FIFO period handling and simplify internal functions.
+
+Update integration inside inv_mpu6050 and inv_icm42600 drivers.
+
+Fixes: 0ecc363ccea7 ("iio: make invensense timestamp module generic")
+Cc: Stable@vger.kernel.org
+Signed-off-by: Jean-Baptiste Maneyrol <jean-baptiste.maneyrol@tdk.com>
+Link: https://lore.kernel.org/r/20240426094835.138389-1-inv.git-commit@tdk.com
+Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
+Stable-dep-of: affe3f077d7a ("iio: imu: inv_icm42600: fix timestamping by limiting FIFO reading")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/iio/common/inv_sensors/inv_sensors_timestamp.c |   24 +++++++----------
+ drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c     |   20 +++++---------
+ drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c             |    2 -
+ include/linux/iio/common/inv_sensors_timestamp.h       |    3 --
+ 4 files changed, 21 insertions(+), 28 deletions(-)
+
+--- a/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c
++++ b/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c
+@@ -78,13 +78,13 @@ int inv_sensors_timestamp_update_odr(str
+ }
+ EXPORT_SYMBOL_NS_GPL(inv_sensors_timestamp_update_odr, IIO_INV_SENSORS_TIMESTAMP);
+-static bool inv_validate_period(struct inv_sensors_timestamp *ts, uint32_t period, uint32_t mult)
++static bool inv_validate_period(struct inv_sensors_timestamp *ts, uint32_t period)
+ {
+       uint32_t period_min, period_max;
+       /* check that period is acceptable */
+-      period_min = ts->min_period * mult;
+-      period_max = ts->max_period * mult;
++      period_min = ts->min_period * ts->mult;
++      period_max = ts->max_period * ts->mult;
+       if (period > period_min && period < period_max)
+               return true;
+       else
+@@ -92,15 +92,15 @@ static bool inv_validate_period(struct i
+ }
+ static bool inv_update_chip_period(struct inv_sensors_timestamp *ts,
+-                                  uint32_t mult, uint32_t period)
++                                 uint32_t period)
+ {
+       uint32_t new_chip_period;
+-      if (!inv_validate_period(ts, period, mult))
++      if (!inv_validate_period(ts, period))
+               return false;
+       /* update chip internal period estimation */
+-      new_chip_period = period / mult;
++      new_chip_period = period / ts->mult;
+       inv_update_acc(&ts->chip_period, new_chip_period);
+       ts->period = ts->mult * ts->chip_period.val;
+@@ -133,16 +133,14 @@ static void inv_align_timestamp_it(struc
+ }
+ void inv_sensors_timestamp_interrupt(struct inv_sensors_timestamp *ts,
+-                                    uint32_t fifo_period, size_t fifo_nb,
+-                                    size_t sensor_nb, int64_t timestamp)
++                                   size_t sample_nb, int64_t timestamp)
+ {
+       struct inv_sensors_timestamp_interval *it;
+       int64_t delta, interval;
+-      const uint32_t fifo_mult = fifo_period / ts->chip.clock_period;
+       uint32_t period;
+       bool valid = false;
+-      if (fifo_nb == 0)
++      if (sample_nb == 0)
+               return;
+       /* update interrupt timestamp and compute chip and sensor periods */
+@@ -152,14 +150,14 @@ void inv_sensors_timestamp_interrupt(str
+       delta = it->up - it->lo;
+       if (it->lo != 0) {
+               /* compute period: delta time divided by number of samples */
+-              period = div_s64(delta, fifo_nb);
+-              valid = inv_update_chip_period(ts, fifo_mult, period);
++              period = div_s64(delta, sample_nb);
++              valid = inv_update_chip_period(ts, period);
+       }
+       /* no previous data, compute theoritical value from interrupt */
+       if (ts->timestamp == 0) {
+               /* elapsed time: sensor period * sensor samples number */
+-              interval = (int64_t)ts->period * (int64_t)sensor_nb;
++              interval = (int64_t)ts->period * (int64_t)sample_nb;
+               ts->timestamp = it->up - interval;
+               return;
+       }
+--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c
++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c
+@@ -515,20 +515,20 @@ int inv_icm42600_buffer_fifo_parse(struc
+               return 0;
+       /* handle gyroscope timestamp and FIFO data parsing */
+-      ts = iio_priv(st->indio_gyro);
+-      inv_sensors_timestamp_interrupt(ts, st->fifo.period, st->fifo.nb.total,
+-                                      st->fifo.nb.gyro, st->timestamp.gyro);
+       if (st->fifo.nb.gyro > 0) {
++              ts = iio_priv(st->indio_gyro);
++              inv_sensors_timestamp_interrupt(ts, st->fifo.nb.gyro,
++                                              st->timestamp.gyro);
+               ret = inv_icm42600_gyro_parse_fifo(st->indio_gyro);
+               if (ret)
+                       return ret;
+       }
+       /* handle accelerometer timestamp and FIFO data parsing */
+-      ts = iio_priv(st->indio_accel);
+-      inv_sensors_timestamp_interrupt(ts, st->fifo.period, st->fifo.nb.total,
+-                                      st->fifo.nb.accel, st->timestamp.accel);
+       if (st->fifo.nb.accel > 0) {
++              ts = iio_priv(st->indio_accel);
++              inv_sensors_timestamp_interrupt(ts, st->fifo.nb.accel,
++                                              st->timestamp.accel);
+               ret = inv_icm42600_accel_parse_fifo(st->indio_accel);
+               if (ret)
+                       return ret;
+@@ -556,9 +556,7 @@ int inv_icm42600_buffer_hwfifo_flush(str
+       if (st->fifo.nb.gyro > 0) {
+               ts = iio_priv(st->indio_gyro);
+-              inv_sensors_timestamp_interrupt(ts, st->fifo.period,
+-                                              st->fifo.nb.total, st->fifo.nb.gyro,
+-                                              gyro_ts);
++              inv_sensors_timestamp_interrupt(ts, st->fifo.nb.gyro, gyro_ts);
+               ret = inv_icm42600_gyro_parse_fifo(st->indio_gyro);
+               if (ret)
+                       return ret;
+@@ -566,9 +564,7 @@ int inv_icm42600_buffer_hwfifo_flush(str
+       if (st->fifo.nb.accel > 0) {
+               ts = iio_priv(st->indio_accel);
+-              inv_sensors_timestamp_interrupt(ts, st->fifo.period,
+-                                              st->fifo.nb.total, st->fifo.nb.accel,
+-                                              accel_ts);
++              inv_sensors_timestamp_interrupt(ts, st->fifo.nb.accel, accel_ts);
+               ret = inv_icm42600_accel_parse_fifo(st->indio_accel);
+               if (ret)
+                       return ret;
+--- a/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c
++++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c
+@@ -113,7 +113,7 @@ irqreturn_t inv_mpu6050_read_fifo(int ir
+               goto end_session;
+       /* Each FIFO data contains all sensors, so same number for FIFO and sensor data */
+       fifo_period = NSEC_PER_SEC / INV_MPU6050_DIVIDER_TO_FIFO_RATE(st->chip_config.divider);
+-      inv_sensors_timestamp_interrupt(&st->timestamp, fifo_period, nb, nb, pf->timestamp);
++      inv_sensors_timestamp_interrupt(&st->timestamp, nb, pf->timestamp);
+       inv_sensors_timestamp_apply_odr(&st->timestamp, fifo_period, nb, 0);
+       /* clear internal data buffer for avoiding kernel data leak */
+--- a/include/linux/iio/common/inv_sensors_timestamp.h
++++ b/include/linux/iio/common/inv_sensors_timestamp.h
+@@ -71,8 +71,7 @@ int inv_sensors_timestamp_update_odr(str
+                                    uint32_t period, bool fifo);
+ void inv_sensors_timestamp_interrupt(struct inv_sensors_timestamp *ts,
+-                                   uint32_t fifo_period, size_t fifo_nb,
+-                                   size_t sensor_nb, int64_t timestamp);
++                                   size_t sample_nb, int64_t timestamp);
+ static inline int64_t inv_sensors_timestamp_pop(struct inv_sensors_timestamp *ts)
+ {
diff --git a/queue-6.6/iio-invensense-remove-redundant-initialization-of-variable-period.patch b/queue-6.6/iio-invensense-remove-redundant-initialization-of-variable-period.patch
new file mode 100644 (file)
index 0000000..9a09bfa
--- /dev/null
@@ -0,0 +1,42 @@
+From stable+bounces-273958-greg=kroah.com@vger.kernel.org Mon Jul 13 20:33:55 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 13 Jul 2026 14:33:45 -0400
+Subject: iio: invensense: remove redundant initialization of variable period
+To: stable@vger.kernel.org
+Cc: Colin Ian King <colin.i.king@gmail.com>, Jean-Baptiste Maneyrol <jean-baptiste.maneyrol@tdk.com>, Jonathan Cameron <Jonathan.Cameron@huawei.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260713183348.1951183-1-sashal@kernel.org>
+
+From: Colin Ian King <colin.i.king@gmail.com>
+
+[ Upstream commit b58b13f156c00c2457035b7071eaaac105fe6836 ]
+
+The variable period is being initialized with a value that is never
+read, it is being re-assigned a new value later on before it is read.
+The initialization is redundant and can be removed.
+
+Cleans up clang scan build warning:
+Value stored to 'period' during its initialization is never
+read [deadcode.DeadStores]
+
+Signed-off-by: Colin Ian King <colin.i.king@gmail.com>
+Acked-by: Jean-Baptiste Maneyrol <jean-baptiste.maneyrol@tdk.com>
+Link: https://lore.kernel.org/r/20240106153202.54861-1-colin.i.king@gmail.com
+Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
+Stable-dep-of: affe3f077d7a ("iio: imu: inv_icm42600: fix timestamping by limiting FIFO reading")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/iio/common/inv_sensors/inv_sensors_timestamp.c |    2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c
++++ b/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c
+@@ -139,7 +139,7 @@ void inv_sensors_timestamp_interrupt(str
+       struct inv_sensors_timestamp_interval *it;
+       int64_t delta, interval;
+       const uint32_t fifo_mult = fifo_period / ts->chip.clock_period;
+-      uint32_t period = ts->period;
++      uint32_t period;
+       bool valid = false;
+       if (fifo_nb == 0)
diff --git a/queue-6.6/iio-pressure-mpl115-fix-runtime-pm-leak-on-read-error.patch b/queue-6.6/iio-pressure-mpl115-fix-runtime-pm-leak-on-read-error.patch
new file mode 100644 (file)
index 0000000..bc45ea0
--- /dev/null
@@ -0,0 +1,60 @@
+From stable+bounces-273981-greg=kroah.com@vger.kernel.org Mon Jul 13 21:44:36 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 13 Jul 2026 15:43:07 -0400
+Subject: iio: pressure: mpl115: fix runtime PM leak on read error
+To: stable@vger.kernel.org
+Cc: Biren Pandya <birenpandya@gmail.com>, Stable@vger.kernel.org, Jonathan Cameron <jic23@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260713194307.2064131-1-sashal@kernel.org>
+
+From: Biren Pandya <birenpandya@gmail.com>
+
+[ Upstream commit fbe67ff37a6fd855a6c097f84f3738bd13d0a898 ]
+
+mpl115_read_raw() takes a runtime PM reference with pm_runtime_get_sync()
+before reading the processed pressure or raw temperature, but on the read
+error path it returns without calling pm_runtime_put_autosuspend(). Each
+failed read therefore leaks a runtime PM reference and prevents the device
+from autosuspending.
+
+Drop the reference before checking the return value so both the success
+and error paths are balanced.
+
+Fixes: 0c3a333524a3 ("iio: pressure: mpl115: Implementing low power mode by shutdown gpio")
+Signed-off-by: Biren Pandya <birenpandya@gmail.com>
+Assisted-by: Claude:claude-opus-4-8 coccinelle
+Cc: <Stable@vger.kernel.org>
+Signed-off-by: Jonathan Cameron <jic23@kernel.org>
+[ moved pm_runtime_mark_last_busy() together with pm_runtime_put_autosuspend() ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/iio/pressure/mpl115.c |    8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+--- a/drivers/iio/pressure/mpl115.c
++++ b/drivers/iio/pressure/mpl115.c
+@@ -106,20 +106,20 @@ static int mpl115_read_raw(struct iio_de
+       case IIO_CHAN_INFO_PROCESSED:
+               pm_runtime_get_sync(data->dev);
+               ret = mpl115_comp_pressure(data, val, val2);
+-              if (ret < 0)
+-                      return ret;
+               pm_runtime_mark_last_busy(data->dev);
+               pm_runtime_put_autosuspend(data->dev);
++              if (ret < 0)
++                      return ret;
+               return IIO_VAL_INT_PLUS_MICRO;
+       case IIO_CHAN_INFO_RAW:
+               pm_runtime_get_sync(data->dev);
+               /* temperature -5.35 C / LSB, 472 LSB is 25 C */
+               ret = mpl115_read_temp(data);
+-              if (ret < 0)
+-                      return ret;
+               pm_runtime_mark_last_busy(data->dev);
+               pm_runtime_put_autosuspend(data->dev);
++              if (ret < 0)
++                      return ret;
+               *val = ret >> 6;
+               return IIO_VAL_INT;
diff --git a/queue-6.6/ksmbd-centralize-ksmbd_conn-final-release-to-plug-transport-leak.patch b/queue-6.6/ksmbd-centralize-ksmbd_conn-final-release-to-plug-transport-leak.patch
new file mode 100644 (file)
index 0000000..1cad4a1
--- /dev/null
@@ -0,0 +1,457 @@
+From stable+bounces-275395-greg=kroah.com@vger.kernel.org Thu Jul 16 15:39:07 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Jul 2026 09:37:25 -0400
+Subject: ksmbd: centralize ksmbd_conn final release to plug transport leak
+To: stable@vger.kernel.org
+Cc: DaeMyung Kang <charsyam@gmail.com>, Namjae Jeon <linkinjeon@kernel.org>, Steve French <stfrench@microsoft.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260716133726.212306-1-sashal@kernel.org>
+
+From: DaeMyung Kang <charsyam@gmail.com>
+
+[ Upstream commit b1f1e80620deb49daf63c2e677046599b693dc1f ]
+
+ksmbd_conn_free() is one of four sites that can observe the last
+refcount drop of a struct ksmbd_conn.  The other three
+
+    fs/smb/server/connection.c    ksmbd_conn_r_count_dec()
+    fs/smb/server/oplock.c        __free_opinfo()
+    fs/smb/server/vfs_cache.c     session_fd_check()
+
+end the conn with a bare kfree(), skipping
+ida_destroy(&conn->async_ida) and
+conn->transport->ops->free_transport(conn->transport).  Whenever one
+of them is the last putter, the embedded async_ida and the entire
+transport struct leak -- for TCP, that is also the struct socket and
+the kvec iov.
+
+__free_opinfo() being a final putter is not theoretical.  opinfo_put()
+queues the callback via call_rcu(&opinfo->rcu, free_opinfo_rcu), so
+ksmbd_server_terminate_conn() can deposit N opinfo releases in RCU and
+have ksmbd_conn_free() run in the handler thread before any of them
+fire.  ksmbd_conn_free() then observes refcnt > 0 and short-circuits;
+the last RCU-delivered __free_opinfo() falls onto its bare kfree(conn)
+branch and the transport is lost.
+
+A/B validation in a QEMU/virtme guest, mounting //127.0.0.1/testshare:
+each iteration holds 8 files open via sleep processes, force-closes
+TCP with "ss -K sport = :445", kills the holders, lazy-umounts;
+repeated 10 times, then ksmbd shutdown and kmemleak scan.
+
+    state         conn_alloc  conn_free  tcp_free  opi_rcu  kmemleak
+    ----------    ----------  ---------  --------  -------  --------
+    pre-patch         20          20        10       160        7
+    with patch        20          20        20       160        0
+
+Pre-patch conn_free=20 with tcp_free=10 directly demonstrates the
+bare-kfree paths skipping transport cleanup; kmemleak backtraces point
+into struct tcp_transport / iov.  With this patch tcp_free matches
+conn_free at 20/20 and kmemleak is clean.
+
+Move the per-struct final release into __ksmbd_conn_release_work() and
+route the three bare-kfree final-put sites through a new
+ksmbd_conn_put().  Those sites now pair ida_destroy() and
+free_transport() with kfree(conn) regardless of which holder happens
+to release the last reference.  stop_sessions() only triggers the
+transport shutdown and does not itself drop the last conn reference,
+so it is unaffected.
+
+The centralized release reaches sock_release() -> tcp_close() ->
+lock_sock_nested() (might_sleep) from every final putter, including
+__free_opinfo() invoked from an RCU softirq callback, which trips
+CONFIG_DEBUG_ATOMIC_SLEEP.  Defer the release to a dedicated
+ksmbd_conn_wq workqueue so ksmbd_conn_put() is safe from any
+non-sleeping context.
+
+Make ksmbd_file own a strong connection reference while fp->conn is
+non-NULL so durable-preserve and final-close paths cannot dereference
+a stale connection.  ksmbd_open_fd() and ksmbd_reopen_durable_fd()
+take the reference via ksmbd_conn_get() (the latter also reorders the
+fp->conn / fp->tcon assignments before __open_id() so the published fp
+is never observed with fp->conn == NULL); session_fd_check() and
+__ksmbd_close_fd() drop it via ksmbd_conn_put().  With that invariant,
+session_fd_check() can take a local conn pointer once and use it
+across the m_op_list and lock_list iterations even though op->conn
+puts may otherwise drop the last reference.
+
+At module exit the workqueue is flushed and destroyed after
+rcu_barrier(), so any release queued by a trailing RCU callback is
+drained before the inode hash and module text go away.
+
+Fixes: ee426bfb9d09 ("ksmbd: add refcnt to ksmbd_conn struct")
+Signed-off-by: DaeMyung Kang <charsyam@gmail.com>
+Acked-by: Namjae Jeon <linkinjeon@kernel.org>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Stable-dep-of: c1016dd1d8b2 ("ksmbd: track the connection owning a byte-range lock")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/server/connection.c |  101 ++++++++++++++++++++++++++++++++++++++-------
+ fs/smb/server/connection.h |    6 ++
+ fs/smb/server/oplock.c     |    7 ---
+ fs/smb/server/server.c     |   12 +++++
+ fs/smb/server/vfs_cache.c  |   60 ++++++++++++++++++++++----
+ 5 files changed, 156 insertions(+), 30 deletions(-)
+
+--- a/fs/smb/server/connection.c
++++ b/fs/smb/server/connection.c
+@@ -22,6 +22,81 @@ static struct ksmbd_conn_ops default_con
+ DEFINE_HASHTABLE(conn_list, CONN_HASH_BITS);
+ DECLARE_RWSEM(conn_list_lock);
++static struct workqueue_struct *ksmbd_conn_wq;
++
++int ksmbd_conn_wq_init(void)
++{
++      ksmbd_conn_wq = alloc_workqueue("ksmbd-conn-release",
++                                      WQ_UNBOUND | WQ_MEM_RECLAIM, 0);
++      if (!ksmbd_conn_wq)
++              return -ENOMEM;
++      return 0;
++}
++
++void ksmbd_conn_wq_destroy(void)
++{
++      if (ksmbd_conn_wq) {
++              destroy_workqueue(ksmbd_conn_wq);
++              ksmbd_conn_wq = NULL;
++      }
++}
++
++/*
++ * __ksmbd_conn_release_work() - perform the final, once-per-struct cleanup
++ * of a ksmbd_conn whose refcount has just dropped to zero.
++ *
++ * This is the common release path used by ksmbd_conn_put() for the embedded
++ * state that outlives the connection thread: async_ida and the attached
++ * transport (which owns the socket and iov for TCP).  Called from a workqueue
++ * so that sleep-allowed teardown (sock_release -> tcp_close ->
++ * lock_sock_nested) never runs from an RCU softirq callback (free_opinfo_rcu)
++ * or any other non-sleeping putter context.
++ */
++static void __ksmbd_conn_release_work(struct work_struct *work)
++{
++      struct ksmbd_conn *conn =
++              container_of(work, struct ksmbd_conn, release_work);
++
++      ida_destroy(&conn->async_ida);
++      conn->transport->ops->free_transport(conn->transport);
++      kfree(conn);
++}
++
++/**
++ * ksmbd_conn_get() - take a reference on @conn and return it.
++ *
++ * Returns @conn unchanged so callers can write
++ * "fp->conn = ksmbd_conn_get(work->conn);" in one expression.  Returns NULL
++ * if @conn is NULL.
++ */
++struct ksmbd_conn *ksmbd_conn_get(struct ksmbd_conn *conn)
++{
++      if (!conn)
++              return NULL;
++
++      atomic_inc(&conn->refcnt);
++      return conn;
++}
++
++/**
++ * ksmbd_conn_put() - drop a reference and, if it was the last, queue the
++ * release onto ksmbd_conn_wq so it runs from process context.
++ *
++ * Callable from any context including RCU softirq callbacks and non-sleeping
++ * locks; the actual release is deferred to the workqueue.  ksmbd_conn_wq is
++ * created in ksmbd_server_init() before any conn can be allocated and is
++ * destroyed in ksmbd_server_exit() after rcu_barrier(), so it is always
++ * non-NULL while a conn reference is held.
++ */
++void ksmbd_conn_put(struct ksmbd_conn *conn)
++{
++      if (!conn)
++              return;
++
++      if (atomic_dec_and_test(&conn->refcnt))
++              queue_work(ksmbd_conn_wq, &conn->release_work);
++}
++
+ /**
+  * ksmbd_conn_free() - free resources of the connection instance
+  *
+@@ -36,23 +111,19 @@ void ksmbd_conn_free(struct ksmbd_conn *
+       hash_del(&conn->hlist);
+       up_write(&conn_list_lock);
++      /*
++       * request_buf / preauth_info / mechToken are only ever accessed by the
++       * connection handler thread that owns @conn.  ksmbd_conn_free() is
++       * called from the transport free_transport() path when that thread is
++       * exiting, so it is safe to release them unconditionally even when
++       * ksmbd_conn_put() below is not the final putter (oplock / ksmbd_file
++       * holders only retain the conn pointer, not these per-thread buffers).
++       */
+       xa_destroy(&conn->sessions);
+       kvfree(conn->request_buf);
+       kfree(conn->preauth_info);
+       kfree(conn->mechToken);
+-      if (atomic_dec_and_test(&conn->refcnt)) {
+-              /*
+-               * async_ida is embedded in struct ksmbd_conn, so pair
+-               * ida_destroy() with the final kfree() rather than with
+-               * the unconditional field teardown above.  This keeps
+-               * the IDA valid for the entire lifetime of the struct,
+-               * even while other refcount holders (oplock / vfs
+-               * durable handles) still reference the connection.
+-               */
+-              ida_destroy(&conn->async_ida);
+-              conn->transport->ops->free_transport(conn->transport);
+-              kfree(conn);
+-      }
++      ksmbd_conn_put(conn);
+ }
+ /**
+@@ -79,6 +150,7 @@ struct ksmbd_conn *ksmbd_conn_alloc(void
+               conn->um = ERR_PTR(-EOPNOTSUPP);
+       if (IS_ERR(conn->um))
+               conn->um = NULL;
++      INIT_WORK(&conn->release_work, __ksmbd_conn_release_work);
+       atomic_set(&conn->req_running, 0);
+       atomic_set(&conn->r_count, 0);
+       atomic_set(&conn->refcnt, 1);
+@@ -457,8 +529,7 @@ void ksmbd_conn_r_count_dec(struct ksmbd
+       if (!atomic_dec_return(&conn->r_count) && waitqueue_active(&conn->r_count_q))
+               wake_up(&conn->r_count_q);
+-      if (atomic_dec_and_test(&conn->refcnt))
+-              kfree(conn);
++      ksmbd_conn_put(conn);
+ }
+ int ksmbd_conn_transport_init(void)
+--- a/fs/smb/server/connection.h
++++ b/fs/smb/server/connection.h
+@@ -15,6 +15,7 @@
+ #include <linux/kthread.h>
+ #include <linux/nls.h>
+ #include <linux/unicode.h>
++#include <linux/workqueue.h>
+ #include "smb_common.h"
+ #include "ksmbd_work.h"
+@@ -116,6 +117,7 @@ struct ksmbd_conn {
+       bool                            binding;
+       atomic_t                        refcnt;
+       bool                            is_aapl;
++      struct work_struct              release_work;
+ };
+ struct ksmbd_conn_ops {
+@@ -161,6 +163,10 @@ void ksmbd_conn_wait_idle(struct ksmbd_c
+ int ksmbd_conn_wait_idle_sess_id(struct ksmbd_conn *curr_conn, u64 sess_id);
+ struct ksmbd_conn *ksmbd_conn_alloc(void);
+ void ksmbd_conn_free(struct ksmbd_conn *conn);
++struct ksmbd_conn *ksmbd_conn_get(struct ksmbd_conn *conn);
++void ksmbd_conn_put(struct ksmbd_conn *conn);
++int ksmbd_conn_wq_init(void);
++void ksmbd_conn_wq_destroy(void);
+ bool ksmbd_conn_lookup_dialect(struct ksmbd_conn *c);
+ int ksmbd_conn_write(struct ksmbd_work *work);
+ int ksmbd_conn_rdma_read(struct ksmbd_conn *conn,
+--- a/fs/smb/server/oplock.c
++++ b/fs/smb/server/oplock.c
+@@ -30,7 +30,6 @@ static DEFINE_RWLOCK(lease_list_lock);
+ static struct oplock_info *alloc_opinfo(struct ksmbd_work *work,
+                                       u64 id, __u16 Tid)
+ {
+-      struct ksmbd_conn *conn = work->conn;
+       struct ksmbd_session *sess = work->sess;
+       struct oplock_info *opinfo;
+@@ -39,7 +38,7 @@ static struct oplock_info *alloc_opinfo(
+               return NULL;
+       opinfo->sess = sess;
+-      opinfo->conn = conn;
++      opinfo->conn = ksmbd_conn_get(work->conn);
+       opinfo->level = SMB2_OPLOCK_LEVEL_NONE;
+       opinfo->op_state = OPLOCK_STATE_NONE;
+       opinfo->pending_break = 0;
+@@ -50,7 +49,6 @@ static struct oplock_info *alloc_opinfo(
+       init_waitqueue_head(&opinfo->oplock_brk);
+       atomic_set(&opinfo->refcount, 1);
+       atomic_set(&opinfo->breaking_cnt, 0);
+-      atomic_inc(&opinfo->conn->refcnt);
+       return opinfo;
+ }
+@@ -132,8 +130,7 @@ static void __free_opinfo(struct oplock_
+ {
+       if (opinfo->is_lease)
+               free_lease(opinfo);
+-      if (opinfo->conn && atomic_dec_and_test(&opinfo->conn->refcnt))
+-              kfree(opinfo->conn);
++      ksmbd_conn_put(opinfo->conn);
+       kfree(opinfo);
+ }
+--- a/fs/smb/server/server.c
++++ b/fs/smb/server/server.c
+@@ -587,8 +587,14 @@ static int __init ksmbd_server_init(void
+       if (ret)
+               goto err_crypto_destroy;
++      ret = ksmbd_conn_wq_init();
++      if (ret)
++              goto err_workqueue_destroy;
++
+       return 0;
++err_workqueue_destroy:
++      ksmbd_workqueue_destroy();
+ err_crypto_destroy:
+       ksmbd_crypto_destroy();
+ err_release_inode_hash:
+@@ -614,6 +620,12 @@ static void __exit ksmbd_server_exit(voi
+ {
+       ksmbd_server_shutdown();
+       rcu_barrier();
++      /*
++       * ksmbd_conn_put() defers the final release onto ksmbd_conn_wq,
++       * so drain it after rcu_barrier() has fired any pending RCU
++       * callbacks that may have queued a release.
++       */
++      ksmbd_conn_wq_destroy();
+       ksmbd_release_inode_hash();
+ }
+--- a/fs/smb/server/vfs_cache.c
++++ b/fs/smb/server/vfs_cache.c
+@@ -402,6 +402,17 @@ static void __ksmbd_close_fd(struct ksmb
+               kfree(smb_lock);
+       }
++      /*
++       * Drop fp's strong reference on conn (taken in ksmbd_open_fd() /
++       * ksmbd_reopen_durable_fd()).  Durable fps that reached the
++       * scavenger have already had fp->conn cleared by session_fd_check(),
++       * in which case there is nothing to drop here.
++       */
++      if (fp->conn) {
++              ksmbd_conn_put(fp->conn);
++              fp->conn = NULL;
++      }
++
+       if (ksmbd_stream_fd(fp))
+               kfree(fp->stream.name);
+       kfree(fp->owner.name);
+@@ -694,7 +705,14 @@ struct ksmbd_file *ksmbd_open_fd(struct
+       atomic_set(&fp->refcount, 1);
+       fp->filp                = filp;
+-      fp->conn                = work->conn;
++      /*
++       * fp owns a strong reference on fp->conn for as long as fp->conn is
++       * non-NULL, so session_fd_check() and __ksmbd_close_fd() never
++       * dereference a dangling pointer.  Paired with ksmbd_conn_put() in
++       * session_fd_check() (durable preserve), in __ksmbd_close_fd()
++       * (final close), and on the error paths below.
++       */
++      fp->conn                = ksmbd_conn_get(work->conn);
+       fp->tcon                = work->tcon;
+       fp->volatile_id         = KSMBD_NO_FID;
+       fp->persistent_id       = KSMBD_NO_FID;
+@@ -716,6 +734,8 @@ struct ksmbd_file *ksmbd_open_fd(struct
+       return fp;
+ err_out:
++      /* fp->conn was set and refcounted before every branch here. */
++      ksmbd_conn_put(fp->conn);
+       kmem_cache_free(filp_cache, fp);
+       return ERR_PTR(ret);
+ }
+@@ -1049,25 +1069,32 @@ static bool session_fd_check(struct ksmb
+       if (!is_reconnectable(fp))
+               return false;
++      if (WARN_ON_ONCE(!fp->conn))
++              return false;
++
+       if (ksmbd_vfs_copy_durable_owner(fp, user))
+               return false;
++      /*
++       * fp owns a strong reference on fp->conn (taken in ksmbd_open_fd()
++       * / ksmbd_reopen_durable_fd()), so conn stays valid for the whole
++       * body of this function regardless of any op->conn puts below.
++       */
+       conn = fp->conn;
+       ci = fp->f_ci;
+       down_write(&ci->m_lock);
+       list_for_each_entry_rcu(op, &ci->m_op_list, op_entry) {
+               if (op->conn != conn)
+                       continue;
+-              if (op->conn && atomic_dec_and_test(&op->conn->refcnt))
+-                      kfree(op->conn);
++              ksmbd_conn_put(op->conn);
+               op->conn = NULL;
+       }
+       up_write(&ci->m_lock);
+       list_for_each_entry_safe(smb_lock, tmp_lock, &fp->lock_list, flist) {
+-              spin_lock(&fp->conn->llist_lock);
++              spin_lock(&conn->llist_lock);
+               list_del_init(&smb_lock->clist);
+-              spin_unlock(&fp->conn->llist_lock);
++              spin_unlock(&conn->llist_lock);
+       }
+       fp->conn = NULL;
+@@ -1078,6 +1105,8 @@ static bool session_fd_check(struct ksmb
+               fp->durable_scavenger_timeout =
+                       jiffies_to_msecs(jiffies) + fp->durable_timeout;
++      /* Drop fp's own reference on conn. */
++      ksmbd_conn_put(conn);
+       return true;
+ }
+@@ -1164,15 +1193,27 @@ int ksmbd_reopen_durable_fd(struct ksmbd
+       old_f_state = fp->f_state;
+       fp->f_state = FP_NEW;
++
++      /*
++       * Initialize fp's connection binding before publishing fp into the
++       * session's file table.  If __open_id() is ordered first, a
++       * concurrent teardown that iterates the table can observe a valid
++       * volatile_id with fp->conn == NULL and preserve a
++       * partially-initialized fp.  fp owns a strong reference on the new
++       * conn (see ksmbd_open_fd()); undo it on __open_id() failure.
++       */
++      fp->conn = ksmbd_conn_get(conn);
++      fp->tcon = work->tcon;
++
+       __open_id(&work->sess->file_table, fp, OPEN_ID_TYPE_VOLATILE_ID);
+       if (!has_file_id(fp->volatile_id)) {
++              fp->conn = NULL;
++              fp->tcon = NULL;
++              ksmbd_conn_put(conn);
+               fp->f_state = old_f_state;
+               return -EBADF;
+       }
+-      fp->conn = conn;
+-      fp->tcon = work->tcon;
+-
+       list_for_each_entry(smb_lock, &fp->lock_list, flist) {
+               spin_lock(&conn->llist_lock);
+               list_add_tail(&smb_lock->clist, &conn->lock_list);
+@@ -1184,8 +1225,7 @@ int ksmbd_reopen_durable_fd(struct ksmbd
+       list_for_each_entry_rcu(op, &ci->m_op_list, op_entry) {
+               if (op->conn)
+                       continue;
+-              op->conn = fp->conn;
+-              atomic_inc(&op->conn->refcnt);
++              op->conn = ksmbd_conn_get(fp->conn);
+       }
+       up_write(&ci->m_lock);
diff --git a/queue-6.6/ksmbd-track-the-connection-owning-a-byte-range-lock.patch b/queue-6.6/ksmbd-track-the-connection-owning-a-byte-range-lock.patch
new file mode 100644 (file)
index 0000000..7b16b44
--- /dev/null
@@ -0,0 +1,138 @@
+From stable+bounces-275394-greg=kroah.com@vger.kernel.org Thu Jul 16 15:38:08 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Jul 2026 09:37:26 -0400
+Subject: ksmbd: track the connection owning a byte-range lock
+To: stable@vger.kernel.org
+Cc: Namjae Jeon <linkinjeon@kernel.org>, Musaab Khan <musaab.khan@protonmail.com>, Steve French <stfrench@microsoft.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260716133726.212306-2-sashal@kernel.org>
+
+From: Namjae Jeon <linkinjeon@kernel.org>
+
+[ Upstream commit c1016dd1d8b2bcd1158bbaabe94a31bb7e7431fb ]
+
+SMB2_LOCK adds each granted byte-range lock to both the file lock list
+and the lock list of the connection which handled the request.  The
+final close and durable handle paths, however, remove the connection
+list entry while holding fp->conn->llist_lock.
+
+With SMB3 multichannel, the connection handling the LOCK request can be
+different from the connection which opened the file.  The entry can
+therefore be removed under a different spinlock from the one protecting
+the list it belongs to.  A concurrent traversal can then access freed
+struct ksmbd_lock and struct file_lock objects.
+
+Record the connection owning each lock's clist entry and hold a
+reference to it while the entry is linked.  Use that connection and its
+llist_lock for unlock, rollback, close, and durable preserve.  Durable
+reconnect assigns the new connection as the owner when publishing the
+locks again.
+
+Fixes: f5a544e3bab7 ("ksmbd: add support for SMB3 multichannel")
+Cc: stable@vger.kernel.org
+Reported-by: Musaab Khan <musaab.khan@protonmail.com>
+Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/server/smb2pdu.c   |   10 ++++++++--
+ fs/smb/server/vfs_cache.c |   23 +++++++++++++++++------
+ fs/smb/server/vfs_cache.h |    1 +
+ 3 files changed, 26 insertions(+), 8 deletions(-)
+
+--- a/fs/smb/server/smb2pdu.c
++++ b/fs/smb/server/smb2pdu.c
+@@ -7580,9 +7580,11 @@ int smb2_lock(struct ksmbd_work *work)
+                                               nolock = 0;
+                                               list_del(&cmp_lock->flist);
+                                               list_del(&cmp_lock->clist);
++                                              cmp_lock->conn = NULL;
+                                               spin_unlock(&conn->llist_lock);
+                                               up_read(&conn_list_lock);
++                                              ksmbd_conn_put(conn);
+                                               locks_free_lock(cmp_lock->fl);
+                                               kfree(cmp_lock);
+                                               goto out_check_cl;
+@@ -7717,6 +7719,7 @@ skip:
+                               goto out2;
+                       } else if (!rc) {
+                               list_add(&smb_lock->llist, &rollback_list);
++                              smb_lock->conn = ksmbd_conn_get(work->conn);
+                               spin_lock(&work->conn->llist_lock);
+                               list_add_tail(&smb_lock->clist,
+                                             &work->conn->lock_list);
+@@ -7771,11 +7774,14 @@ out:
+               }
+               list_del(&smb_lock->llist);
+-              spin_lock(&work->conn->llist_lock);
++              conn = smb_lock->conn;
++              spin_lock(&conn->llist_lock);
+               if (!list_empty(&smb_lock->flist))
+                       list_del(&smb_lock->flist);
+               list_del(&smb_lock->clist);
+-              spin_unlock(&work->conn->llist_lock);
++              smb_lock->conn = NULL;
++              spin_unlock(&conn->llist_lock);
++              ksmbd_conn_put(conn);
+               locks_free_lock(smb_lock->fl);
+               if (rlock)
+--- a/fs/smb/server/vfs_cache.c
++++ b/fs/smb/server/vfs_cache.c
+@@ -391,10 +391,14 @@ static void __ksmbd_close_fd(struct ksmb
+        * there are not accesses to fp->lock_list.
+        */
+       list_for_each_entry_safe(smb_lock, tmp_lock, &fp->lock_list, flist) {
+-              if (!list_empty(&smb_lock->clist) && fp->conn) {
+-                      spin_lock(&fp->conn->llist_lock);
+-                      list_del(&smb_lock->clist);
+-                      spin_unlock(&fp->conn->llist_lock);
++              struct ksmbd_conn *conn = smb_lock->conn;
++
++              if (conn) {
++                      spin_lock(&conn->llist_lock);
++                      list_del_init(&smb_lock->clist);
++                      smb_lock->conn = NULL;
++                      spin_unlock(&conn->llist_lock);
++                      ksmbd_conn_put(conn);
+               }
+               list_del(&smb_lock->flist);
+@@ -1092,9 +1096,15 @@ static bool session_fd_check(struct ksmb
+       up_write(&ci->m_lock);
+       list_for_each_entry_safe(smb_lock, tmp_lock, &fp->lock_list, flist) {
+-              spin_lock(&conn->llist_lock);
++              struct ksmbd_conn *lock_conn = smb_lock->conn;
++
++              if (!lock_conn)
++                      continue;
++              spin_lock(&lock_conn->llist_lock);
+               list_del_init(&smb_lock->clist);
+-              spin_unlock(&conn->llist_lock);
++              smb_lock->conn = NULL;
++              spin_unlock(&lock_conn->llist_lock);
++              ksmbd_conn_put(lock_conn);
+       }
+       fp->conn = NULL;
+@@ -1215,6 +1225,7 @@ int ksmbd_reopen_durable_fd(struct ksmbd
+       }
+       list_for_each_entry(smb_lock, &fp->lock_list, flist) {
++              smb_lock->conn = ksmbd_conn_get(conn);
+               spin_lock(&conn->llist_lock);
+               list_add_tail(&smb_lock->clist, &conn->lock_list);
+               spin_unlock(&conn->llist_lock);
+--- a/fs/smb/server/vfs_cache.h
++++ b/fs/smb/server/vfs_cache.h
+@@ -32,6 +32,7 @@ struct ksmbd_session;
+ struct ksmbd_lock {
+       struct file_lock *fl;
++      struct ksmbd_conn *conn;
+       struct list_head clist;
+       struct list_head flist;
+       struct list_head llist;
diff --git a/queue-6.6/ksmbd-use-opener-credentials-for-fsctl-mutations.patch b/queue-6.6/ksmbd-use-opener-credentials-for-fsctl-mutations.patch
new file mode 100644 (file)
index 0000000..6b437c6
--- /dev/null
@@ -0,0 +1,86 @@
+From stable+bounces-275316-greg=kroah.com@vger.kernel.org Thu Jul 16 13:52:33 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Jul 2026 07:52:27 -0400
+Subject: ksmbd: use opener credentials for FSCTL mutations
+To: stable@vger.kernel.org
+Cc: Namjae Jeon <linkinjeon@kernel.org>, Musaab Khan <musaab.khan@protonmail.com>, Steve French <stfrench@microsoft.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260716115227.1785304-1-sashal@kernel.org>
+
+From: Namjae Jeon <linkinjeon@kernel.org>
+
+[ Upstream commit c6394bcaf254c5baf9aff43376020be5db6d3316 ]
+
+SET_SPARSE, SET_ZERO_DATA and SET_COMPRESSION operate on an open SMB
+handle but call VFS xattr, fallocate or fileattr helpers with the current
+ksmbd worker credentials. Those helpers can revalidate inode permissions,
+ownership and LSM policy independently of the SMB handle access mask.
+
+Run each operation with the credentials captured in the target file when
+the handle was opened. Keep credential handling local to these single-file
+FSCTLs rather than applying session credentials to the complete IOCTL
+handler, which also contains handle-less and multi-handle operations.
+
+Cc: stable@vger.kernel.org
+Reported-by: Musaab Khan <musaab.khan@protonmail.com>
+Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/server/smb2pdu.c |    3 +++
+ fs/smb/server/vfs.c     |   20 +++++++++++++-------
+ 2 files changed, 16 insertions(+), 7 deletions(-)
+
+--- a/fs/smb/server/smb2pdu.c
++++ b/fs/smb/server/smb2pdu.c
+@@ -8194,6 +8194,7 @@ static inline int fsctl_set_sparse(struc
+       if (fp->f_ci->m_fattr != old_fattr &&
+           test_share_config_flag(work->tcon->share_conf,
+                                  KSMBD_SHARE_FLAG_STORE_DOS_ATTRS)) {
++              const struct cred *saved_cred;
+               struct xattr_dos_attrib da;
+               ret = ksmbd_vfs_get_dos_attrib_xattr(idmap,
+@@ -8202,9 +8203,11 @@ static inline int fsctl_set_sparse(struc
+                       goto out;
+               da.attr = le32_to_cpu(fp->f_ci->m_fattr);
++              saved_cred = override_creds(fp->filp->f_cred);
+               ret = ksmbd_vfs_set_dos_attrib_xattr(idmap,
+                                                    &fp->filp->f_path,
+                                                    &da, true);
++              revert_creds(saved_cred);
+               if (ret)
+                       fp->f_ci->m_fattr = old_fattr;
+       }
+--- a/fs/smb/server/vfs.c
++++ b/fs/smb/server/vfs.c
+@@ -994,15 +994,21 @@ void ksmbd_vfs_set_fadvise(struct file *
+ int ksmbd_vfs_zero_data(struct ksmbd_work *work, struct ksmbd_file *fp,
+                       loff_t off, loff_t len)
+ {
++      const struct cred *saved_cred;
++      int err;
++
+       smb_break_all_levII_oplock(work, fp, 1);
++      saved_cred = override_creds(fp->filp->f_cred);
+       if (fp->f_ci->m_fattr & FILE_ATTRIBUTE_SPARSE_FILE_LE)
+-              return vfs_fallocate(fp->filp,
+-                                   FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
+-                                   off, len);
+-
+-      return vfs_fallocate(fp->filp,
+-                           FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE,
+-                           off, len);
++              err = vfs_fallocate(fp->filp,
++                                  FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
++                                  off, len);
++      else
++              err = vfs_fallocate(fp->filp,
++                                  FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE,
++                                  off, len);
++      revert_creds(saved_cred);
++      return err;
+ }
+ int ksmbd_vfs_fqar_lseek(struct ksmbd_file *fp, loff_t start, loff_t length,
diff --git a/queue-6.6/media-nxp-imx8-isi-convert-to-platform-remove-callback-returning-void.patch b/queue-6.6/media-nxp-imx8-isi-convert-to-platform-remove-callback-returning-void.patch
new file mode 100644 (file)
index 0000000..93a2e3e
--- /dev/null
@@ -0,0 +1,64 @@
+From stable+bounces-275003-greg=kroah.com@vger.kernel.org Wed Jul 15 18:55:20 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 12:54:08 -0400
+Subject: media: nxp: imx8-isi: Convert to platform remove callback returning void
+To: stable@vger.kernel.org
+Cc: "Uwe Kleine-König" <u.kleine-koenig@pengutronix.de>, "Laurent Pinchart" <laurent.pinchart@ideasonboard.com>, "Hans Verkuil" <hverkuil-cisco@xs4all.nl>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260715165410.963211-1-sashal@kernel.org>
+
+From: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
+
+[ Upstream commit e992ee7eb56f827088f63c9d5210ab4da25a5c4d ]
+
+The .remove() callback for a platform driver returns an int which makes
+many driver authors wrongly assume it's possible to do error handling by
+returning an error code. However the value returned is ignored (apart
+from emitting a warning) and this typically results in resource leaks.
+
+To improve here there is a quest to make the remove callback return
+void. In the first step of this quest all drivers are converted to
+.remove_new(), which already returns void. Eventually after all drivers
+are converted, .remove_new() will be renamed to .remove().
+
+Trivially convert this driver from always returning zero in the remove
+callback to the void returning variant.
+
+Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
+Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
+Stable-dep-of: b670bf89824e ("media: nxp: imx8-isi: Fix use-after-free on remove")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c |    6 ++----
+ 1 file changed, 2 insertions(+), 4 deletions(-)
+
+--- a/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c
++++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c
+@@ -508,7 +508,7 @@ err_pm:
+       return ret;
+ }
+-static int mxc_isi_remove(struct platform_device *pdev)
++static void mxc_isi_remove(struct platform_device *pdev)
+ {
+       struct mxc_isi_dev *isi = platform_get_drvdata(pdev);
+       unsigned int i;
+@@ -525,8 +525,6 @@ static int mxc_isi_remove(struct platfor
+       mxc_isi_v4l2_cleanup(isi);
+       pm_runtime_disable(isi->dev);
+-
+-      return 0;
+ }
+ static const struct of_device_id mxc_isi_of_match[] = {
+@@ -539,7 +537,7 @@ MODULE_DEVICE_TABLE(of, mxc_isi_of_match
+ static struct platform_driver mxc_isi_driver = {
+       .probe          = mxc_isi_probe,
+-      .remove         = mxc_isi_remove,
++      .remove_new     = mxc_isi_remove,
+       .driver = {
+               .of_match_table = mxc_isi_of_match,
+               .name           = MXC_ISI_DRIVER_NAME,
diff --git a/queue-6.6/media-nxp-imx8-isi-fix-use-after-free-on-remove.patch b/queue-6.6/media-nxp-imx8-isi-fix-use-after-free-on-remove.patch
new file mode 100644 (file)
index 0000000..6eb8ffd
--- /dev/null
@@ -0,0 +1,73 @@
+From stable+bounces-275004-greg=kroah.com@vger.kernel.org Wed Jul 15 18:55:26 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 12:54:10 -0400
+Subject: media: nxp: imx8-isi: Fix use-after-free on remove
+To: stable@vger.kernel.org
+Cc: Xiaolei Wang <xiaolei.wang@windriver.com>, Frank Li <Frank.Li@nxp.com>, Laurent Pinchart <laurent.pinchart@ideasonboard.com>, Hans Verkuil <hverkuil+cisco@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715165410.963211-3-sashal@kernel.org>
+
+From: Xiaolei Wang <xiaolei.wang@windriver.com>
+
+[ Upstream commit b670bf89824ede5d07d20bb9bfbafb754846081d ]
+
+KASAN reports a slab-use-after-free in __media_entity_remove_link()
+during rmmod of imx8_isi:
+
+  BUG: KASAN: slab-use-after-free in __media_entity_remove_link+0x608/0x650
+  Read of size 2 at addr ffff0000d47cb02a by task rmmod/724
+
+  Call trace:
+   __media_entity_remove_link+0x608/0x650
+   __media_entity_remove_links+0x78/0x144
+   __media_device_unregister_entity+0x150/0x280
+   media_device_unregister_entity+0x48/0x68
+   v4l2_device_unregister_subdev+0x158/0x300
+   v4l2_async_unbind_subdev_one+0x22c/0x358
+   v4l2_async_nf_unbind_all_subdevs+0xfc/0x1c0
+   v4l2_async_nf_unregister+0x5c/0x14c
+   mxc_isi_remove+0x124/0x2a0 [imx8_isi]
+
+  Allocated by task 249:
+   __kmalloc_noprof+0x27c/0x690
+   mxc_isi_crossbar_init+0x22c/0x560 [imx8_isi]
+
+  Freed by task 724:
+   kfree+0x1e4/0x5b0
+   mxc_isi_crossbar_cleanup+0x34/0x80 [imx8_isi]
+   mxc_isi_remove+0x11c/0x2a0 [imx8_isi]
+
+The problem is that mxc_isi_remove() calls mxc_isi_crossbar_cleanup()
+before mxc_isi_v4l2_cleanup(). The crossbar cleanup frees the media
+entity pads, but the subsequent v4l2 cleanup still tries to remove
+media links that reference those pads.
+
+Fix this by calling mxc_isi_v4l2_cleanup() before
+mxc_isi_crossbar_cleanup() to ensure all media entities are properly
+unregistered while the pads are still valid.
+
+Fixes: cf21f328fcaf ("media: nxp: Add i.MX8 ISI driver")
+Cc: stable@vger.kernel.org
+Signed-off-by: Xiaolei Wang <xiaolei.wang@windriver.com>
+Reviewed-by: Frank Li <Frank.Li@nxp.com>
+Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+Link: https://patch.msgid.link/20260507041318.491594-2-xiaolei.wang@windriver.com
+Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+Signed-off-by: Hans Verkuil <hverkuil+cisco@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c |    2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c
++++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c
+@@ -521,8 +521,8 @@ static void mxc_isi_remove(struct platfo
+               mxc_isi_pipe_cleanup(pipe);
+       }
+-      mxc_isi_crossbar_cleanup(&isi->crossbar);
+       mxc_isi_v4l2_cleanup(isi);
++      mxc_isi_crossbar_cleanup(&isi->crossbar);
+ }
+ static const struct of_device_id mxc_isi_of_match[] = {
diff --git a/queue-6.6/media-nxp-imx8-isi-use-devm_pm_runtime_enable-to-simplify-code.patch b/queue-6.6/media-nxp-imx8-isi-use-devm_pm_runtime_enable-to-simplify-code.patch
new file mode 100644 (file)
index 0000000..4b972e2
--- /dev/null
@@ -0,0 +1,70 @@
+From stable+bounces-275002-greg=kroah.com@vger.kernel.org Wed Jul 15 18:54:20 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 12:54:09 -0400
+Subject: media: nxp: imx8-isi: use devm_pm_runtime_enable() to simplify code
+To: stable@vger.kernel.org
+Cc: Frank Li <Frank.Li@nxp.com>, Laurent Pinchart <laurent.pinchart@ideasonboard.com>, Hans Verkuil <hverkuil+cisco@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715165410.963211-2-sashal@kernel.org>
+
+From: Frank Li <Frank.Li@nxp.com>
+
+[ Upstream commit 078161dd44d6f848a62a473206d69025607736ec ]
+
+Use devm_pm_runtime_enable() to simplify code. Change to use
+dev_err_probe() because previous goto change to return.
+
+No functional change.
+
+Signed-off-by: Frank Li <Frank.Li@nxp.com>
+Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+Link: https://patch.msgid.link/20260116-cam_cleanup-v4-2-29ce01640443@nxp.com
+Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+Signed-off-by: Hans Verkuil <hverkuil+cisco@kernel.org>
+Stable-dep-of: b670bf89824e ("media: nxp: imx8-isi: Fix use-after-free on remove")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c |   16 +++++++---------
+ 1 file changed, 7 insertions(+), 9 deletions(-)
+
+--- a/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c
++++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c
+@@ -474,13 +474,14 @@ static int mxc_isi_probe(struct platform
+               return ret;
+       }
+-      pm_runtime_enable(dev);
++      ret = devm_pm_runtime_enable(dev);
++      if (ret)
++              return ret;
+       ret = mxc_isi_crossbar_init(isi);
+-      if (ret) {
+-              dev_err(dev, "Failed to initialize crossbar: %d\n", ret);
+-              goto err_pm;
+-      }
++      if (ret)
++              return dev_err_probe(dev, ret,
++                                   "Failed to initialize crossbar\n");
+       for (i = 0; i < isi->pdata->num_channels; ++i) {
+               ret = mxc_isi_pipe_init(isi, i);
+@@ -503,8 +504,7 @@ static int mxc_isi_probe(struct platform
+ err_xbar:
+       mxc_isi_crossbar_cleanup(&isi->crossbar);
+-err_pm:
+-      pm_runtime_disable(isi->dev);
++
+       return ret;
+ }
+@@ -523,8 +523,6 @@ static void mxc_isi_remove(struct platfo
+       mxc_isi_crossbar_cleanup(&isi->crossbar);
+       mxc_isi_v4l2_cleanup(isi);
+-
+-      pm_runtime_disable(isi->dev);
+ }
+ static const struct of_device_id mxc_isi_of_match[] = {
diff --git a/queue-6.6/mm-do-file-ownership-checks-with-the-proper-mount-idmap.patch b/queue-6.6/mm-do-file-ownership-checks-with-the-proper-mount-idmap.patch
new file mode 100644 (file)
index 0000000..3bd0af0
--- /dev/null
@@ -0,0 +1,107 @@
+From stable+bounces-278565-greg=kroah.com@vger.kernel.org Tue Jul 21 13:47:26 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 07:47:16 -0400
+Subject: mm: do file ownership checks with the proper mount idmap
+To: stable@vger.kernel.org
+Cc: Pedro Falcato <pfalcato@suse.de>, Jan Kara <jack@suse.cz>, "Christian Brauner (Amutable)" <brauner@kernel.org>, "David Hildenbrand (Arm)" <david@kernel.org>, Al Viro <viro@zeniv.linux.org.uk>, Jann Horn <jannh@google.com>, "Liam R. Howlett" <liam@infradead.org>, "Matthew Wilcox (Oracle)" <willy@infradead.org>, Vlastimil Babka <vbabka@kernel.org>, Andrew Morton <akpm@linux-foundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721114716.3688887-1-sashal@kernel.org>
+
+From: Pedro Falcato <pfalcato@suse.de>
+
+[ Upstream commit e187bc02f8fa4226d62814592cf064ee4557c470 ]
+
+Ever since idmapped mounts were introduced, inode ownership checks (for
+side-channel protection) in mincore() and madvise(MADV_PAGEOUT) were done
+against the nop_mnt_idmap, which completely ignores the file's mount's
+idmap.  This results in odd edgecases like:
+
+1) mount/bind-mount with an idmap userA:userB:1
+2) userB runs an owner_or_capable() check on file that is owned by userA
+on-disk/in-memory, but owned by userB after idmap translation
+3) owner_or_capable() mysteriously fails as the correct idmap wasn't supplied
+
+In the case of mincore/madvise MADV_PAGEOUT, this is usually benign,
+because file_permission(file, MAY_WRITE) will probably succeed, as it uses
+the proper idmap internally, but it does not need to be the case on e.g a
+0444 file where even the owner itself doesn't have permissions to write to
+it.
+
+Since this is clearly not trivial to get right, introduce a
+file_owner_or_capable() that can carry the correct semantics, and switch
+the various users in mm to it.
+
+The issue was found by manual code inspection & an off-list discussion
+with Jan Kara.
+
+Link: https://lore.kernel.org/20260625153853.913949-1-pfalcato@suse.de
+Fixes: 9caccd41541a ("fs: introduce MOUNT_ATTR_IDMAP")
+Signed-off-by: Pedro Falcato <pfalcato@suse.de>
+Reviewed-by: Jan Kara <jack@suse.cz>
+Reviewed-by: Christian Brauner (Amutable) <brauner@kernel.org>
+Acked-by: David Hildenbrand (Arm) <david@kernel.org>
+Cc: Al Viro <viro@zeniv.linux.org.uk>
+Cc: Jann Horn <jannh@google.com>
+Cc: Liam R. Howlett <liam@infradead.org>
+Cc: Matthew Wilcox (Oracle) <willy@infradead.org>
+Cc: Vlastimil Babka <vbabka@kernel.org>
+Cc: <stable@vger.kernel.org>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+[ dropped const from file_owner_or_capable()'s parameter since 6.6's file_mnt_idmap() takes a non-const struct file * ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/fs.h |    5 +++++
+ mm/filemap.c       |    2 +-
+ mm/madvise.c       |    3 +--
+ mm/mincore.c       |    3 +--
+ 4 files changed, 8 insertions(+), 5 deletions(-)
+
+--- a/include/linux/fs.h
++++ b/include/linux/fs.h
+@@ -2481,6 +2481,11 @@ static inline struct mnt_idmap *file_mnt
+       return mnt_idmap(file->f_path.mnt);
+ }
++static inline bool file_owner_or_capable(struct file *file)
++{
++      return inode_owner_or_capable(file_mnt_idmap(file), file_inode(file));
++}
++
+ /**
+  * is_idmapped_mnt - check whether a mount is mapped
+  * @mnt: the mount to check
+--- a/mm/filemap.c
++++ b/mm/filemap.c
+@@ -4314,7 +4314,7 @@ static inline bool can_do_cachestat(stru
+ {
+       if (f->f_mode & FMODE_WRITE)
+               return true;
+-      if (inode_owner_or_capable(file_mnt_idmap(f), file_inode(f)))
++      if (file_owner_or_capable(f))
+               return true;
+       return file_permission(f, MAY_WRITE) == 0;
+ }
+--- a/mm/madvise.c
++++ b/mm/madvise.c
+@@ -334,8 +334,7 @@ static inline bool can_do_file_pageout(s
+        * otherwise we'd be including shared non-exclusive mappings, which
+        * opens a side channel.
+        */
+-      return inode_owner_or_capable(&nop_mnt_idmap,
+-                                    file_inode(vma->vm_file)) ||
++      return file_owner_or_capable(vma->vm_file) ||
+              file_permission(vma->vm_file, MAY_WRITE) == 0;
+ }
+--- a/mm/mincore.c
++++ b/mm/mincore.c
+@@ -167,8 +167,7 @@ static inline bool can_do_mincore(struct
+        * for writing; otherwise we'd be including shared non-exclusive
+        * mappings, which opens a side channel.
+        */
+-      return inode_owner_or_capable(&nop_mnt_idmap,
+-                                    file_inode(vma->vm_file)) ||
++      return file_owner_or_capable(vma->vm_file) ||
+              file_permission(vma->vm_file, MAY_WRITE) == 0;
+ }
diff --git a/queue-6.6/mm-shrinker-do-not-hold-rcu-lock-in-shrinker_debugfs_count_show.patch b/queue-6.6/mm-shrinker-do-not-hold-rcu-lock-in-shrinker_debugfs_count_show.patch
new file mode 100644 (file)
index 0000000..1c62c51
--- /dev/null
@@ -0,0 +1,78 @@
+From stable+bounces-274916-greg=kroah.com@vger.kernel.org Wed Jul 15 13:40:50 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 07:40:37 -0400
+Subject: mm/shrinker: do not hold RCU lock in shrinker_debugfs_count_show()
+To: stable@vger.kernel.org
+Cc: Shakeel Butt <shakeel.butt@linux.dev>, Zenghui Yu <zenghui.yu@linux.dev>, Nhat Pham <nphamcs@gmail.com>, SeongJae Park <sj@kernel.org>, Qi Zheng <qi.zheng@linux.dev>, Muchun Song <muchun.song@linux.dev>, Roman Gushchin <roman.gushchin@linux.dev>, Dave Chinner <david@fromorbit.com>, Andrew Morton <akpm@linux-foundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715114037.728053-2-sashal@kernel.org>
+
+From: Shakeel Butt <shakeel.butt@linux.dev>
+
+[ Upstream commit b902890c62d200b3509cb5e09cf1e0a66553c128 ]
+
+Reading the debugfs "count" file of a memcg-aware shrinker can sleep
+inside an RCU read-side critical section:
+
+  BUG: sleeping function called from invalid context at kernel/cgroup/rstat.c:421
+  RCU nest depth: 1, expected: 0
+   css_rstat_flush
+   mem_cgroup_flush_stats
+   zswap_shrinker_count
+   shrinker_debugfs_count_show
+
+shrinker_debugfs_count_show() invokes the ->count_objects() callback under
+rcu_read_lock().  The zswap callback flushes memcg stats via
+css_rstat_flush(), which may sleep, so it must not run under RCU.
+
+The RCU lock is not needed here.  mem_cgroup_iter() takes RCU internally
+and returns a memcg holding a css reference (dropped on the next iteration
+or by mem_cgroup_iter_break()), so the memcg stays alive without it.  The
+shrinker is kept alive by the open debugfs file: shrinker_free() removes
+the debugfs entries via debugfs_remove_recursive(), which waits for
+in-flight readers to drain, before call_rcu(..., shrinker_free_rcu_cb).
+The sibling "scan" handler already invokes the sleeping ->scan_objects()
+callback with no RCU section.
+
+Drop the rcu_read_lock()/rcu_read_unlock().
+
+Link: https://lore.kernel.org/20260610232048.62930-1-shakeel.butt@linux.dev
+Fixes: 5035ebc644ae ("mm: shrinkers: introduce debugfs interface for memory shrinkers")
+Signed-off-by: Shakeel Butt <shakeel.butt@linux.dev>
+Reported-by: Zenghui Yu <zenghui.yu@linux.dev>
+Closes: https://lore.kernel.org/all/c052a064-cddb-494f-a0d8-f8a10b4b1c4d@linux.dev/
+Suggested-by: Nhat Pham <nphamcs@gmail.com>
+Reviewed-by: SeongJae Park <sj@kernel.org>
+Reviewed-by: Qi Zheng <qi.zheng@linux.dev>
+Tested-by: Zenghui Yu (Huawei) <zenghui.yu@linux.dev>
+Reviewed-by: Nhat Pham <nphamcs@gmail.com>
+Acked-by: Muchun Song <muchun.song@linux.dev>
+Reviewed-by: Roman Gushchin <roman.gushchin@linux.dev>
+Cc: Dave Chinner <david@fromorbit.com>
+Cc: <stable@vger.kernel.org>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ mm/shrinker_debug.c |    4 ----
+ 1 file changed, 4 deletions(-)
+
+--- a/mm/shrinker_debug.c
++++ b/mm/shrinker_debug.c
+@@ -55,8 +55,6 @@ static int shrinker_debugfs_count_show(s
+       if (!count_per_node)
+               return -ENOMEM;
+-      rcu_read_lock();
+-
+       memcg_aware = shrinker->flags & SHRINKER_MEMCG_AWARE;
+       memcg = mem_cgroup_iter(NULL, NULL, NULL);
+@@ -86,8 +84,6 @@ static int shrinker_debugfs_count_show(s
+               }
+       } while ((memcg = mem_cgroup_iter(NULL, memcg, NULL)) != NULL);
+-      rcu_read_unlock();
+-
+       kfree(count_per_node);
+       return ret;
+ }
diff --git a/queue-6.6/mm-shrinker-remove-redundant-shrinker_rwsem-in-debugfs-operations.patch b/queue-6.6/mm-shrinker-remove-redundant-shrinker_rwsem-in-debugfs-operations.patch
new file mode 100644 (file)
index 0000000..0c79624
--- /dev/null
@@ -0,0 +1,159 @@
+From stable+bounces-274915-greg=kroah.com@vger.kernel.org Wed Jul 15 13:40:49 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 07:40:36 -0400
+Subject: mm: shrinker: remove redundant shrinker_rwsem in debugfs operations
+To: stable@vger.kernel.org
+Cc: "Qi Zheng" <zhengqi.arch@bytedance.com>, "Muchun Song" <songmuchun@bytedance.com>, "Christian Brauner" <brauner@kernel.org>, "Christian König" <christian.koenig@amd.com>, "Chuck Lever" <cel@kernel.org>, "Daniel Vetter" <daniel@ffwll.ch>, "Daniel Vetter" <daniel.vetter@ffwll.ch>, "Darrick J. Wong" <djwong@kernel.org>, "Dave Chinner" <david@fromorbit.com>, "Greg Kroah-Hartman" <gregkh@linuxfoundation.org>, "Joel Fernandes" <joel@joelfernandes.org>, "Kirill Tkhai" <tkhai@ya.ru>, "Paul E. McKenney" <paulmck@kernel.org>, "Roman Gushchin" <roman.gushchin@linux.dev>, "Sergey Senozhatsky" <senozhatsky@chromium.org>, "Steven Price" <steven.price@arm.com>, "Theodore Ts'o" <tytso@mit.edu>, "Vlastimil Babka" <vbabka@suse.cz>, "Abhinav Kumar" <quic_abhinavk@quicinc.com>, "Alasdair Kergon" <agk@redhat.com>, "Alexander Viro" <viro@zeniv.linux.org.uk>, "Alyssa Rosenzweig" <alyssa.rosenzweig@collabora.com>, "Andreas Dilger" <adilger.kernel@dilger.ca>, "Andreas Gruenbacher" <agruenba@redhat.com>, "Anna Schumaker" <anna@kernel.org>, "Arnd Bergmann" <arnd@arndb.de>, "Bob Peterson" <rpeterso@redhat.com>, "Borislav Petkov" <bp@alien8.de>, "Carlos Llamas" <cmllamas@google.com>, "Chandan Babu R" <chandan.babu@oracle.com>, "Chao Yu" <chao@kernel.org>, "Chris Mason" <clm@fb.com>, "Coly Li" <colyli@suse.de>, "Dai Ngo" <Dai.Ngo@oracle.com>, "Dave Hansen" <dave.hansen@linux.intel.com>, "David Airlie" <airlied@gmail.com>, "David Hildenbrand" <david@redhat.com>, "David Sterba" <dsterba@suse.com>, "Dmitry Baryshkov" <dmitry.baryshkov@linaro.org>, "Gao Xiang" <hsiangkao@linux.alibaba.com>, "Huang Rui" <ray.huang@amd.com>, "Ingo Molnar" <mingo@redhat.com>, "Jaegeuk Kim" <jaegeuk@kernel.org>, "Jani Nikula" <jani.nikula@linux.intel.com>, "Jan Kara" <jack@suse.cz>, "Jason Wang" <jasowang@redhat.com>, "Jeff Layton" <jlayton@kernel.org>, "Jeffle Xu" <jefflexu@linux.alibaba.com>, "Joonas Lahtinen" <joonas.lahtinen@linux.intel.com>, "Josef Bacik" <josef@toxicpanda.com>, "Juergen Gross" <jgross@suse.com>, "Kent Overstreet" <kent.overstreet@gmail.com>, "Marijn Suijten" <marijn.suijten@somainline.org>, "Michael S. Tsirkin" <mst@redhat.com>, "Mike Snitzer" <snitzer@kernel.org>, "Minchan Kim" <minchan@kernel.org>, "Muchun Song" <muchun.song@linux.dev>, "Nadav Amit" <namit@vmware.com>, "Neil Brown" <neilb@suse.de>, "Oleksandr Tyshchenko" <oleksandr_tyshchenko@epam.com>, "Olga Kornievskaia" <kolga@netapp.com>, "Richard Weinberger" <richard@nod.at>, "Rob Clark" <robdclark@gmail.com>, "Rob Herring" <robh@kernel.org>, "Rodrigo Vivi" <rodrigo.vivi@intel.com>, "Sean Paul" <sean@poorly.run>, "Song Liu" <song@kernel.org>, "Stefano Stabellini" <sstabellini@kernel.org>, "Thomas Gleixner" <tglx@linutronix.de>, "Tomeu Vizoso" <tomeu.vizoso@collabora.com>, "Tom Talpey" <tom@talpey.com>, "Trond Myklebust" <trond.myklebust@hammerspace.com>, "Tvrtko Ursulin" <tvrtko.ursulin@linux.intel.com>, "Xuan Zhuo" <xuanzhuo@linux.alibaba.com>, "Yue Hu" <huyue2@coolpad.com>, "Andrew Morton" <akpm@linux-foundation.org>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260715114037.728053-1-sashal@kernel.org>
+
+From: Qi Zheng <zhengqi.arch@bytedance.com>
+
+[ Upstream commit 1dd49e58f966b1eecd935dc28458a8369ae94ad1 ]
+
+debugfs_remove_recursive() will wait for debugfs_file_put() to return, so
+the shrinker will not be freed when doing debugfs operations (such as
+shrinker_debugfs_count_show() and shrinker_debugfs_scan_write()), so there
+is no need to hold shrinker_rwsem during debugfs operations.
+
+Link: https://lkml.kernel.org/r/20230911092517.64141-4-zhengqi.arch@bytedance.com
+Signed-off-by: Qi Zheng <zhengqi.arch@bytedance.com>
+Reviewed-by: Muchun Song <songmuchun@bytedance.com>
+Cc: Christian Brauner <brauner@kernel.org>
+Cc: Christian König <christian.koenig@amd.com>
+Cc: Chuck Lever <cel@kernel.org>
+Cc: Daniel Vetter <daniel@ffwll.ch>
+Cc: Daniel Vetter <daniel.vetter@ffwll.ch>
+Cc: Darrick J. Wong <djwong@kernel.org>
+Cc: Dave Chinner <david@fromorbit.com>
+Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Cc: Joel Fernandes <joel@joelfernandes.org>
+Cc: Kirill Tkhai <tkhai@ya.ru>
+Cc: Paul E. McKenney <paulmck@kernel.org>
+Cc: Roman Gushchin <roman.gushchin@linux.dev>
+Cc: Sergey Senozhatsky <senozhatsky@chromium.org>
+Cc: Steven Price <steven.price@arm.com>
+Cc: Theodore Ts'o <tytso@mit.edu>
+Cc: Vlastimil Babka <vbabka@suse.cz>
+Cc: Abhinav Kumar <quic_abhinavk@quicinc.com>
+Cc: Alasdair Kergon <agk@redhat.com>
+Cc: Alexander Viro <viro@zeniv.linux.org.uk>
+Cc: Alyssa Rosenzweig <alyssa.rosenzweig@collabora.com>
+Cc: Andreas Dilger <adilger.kernel@dilger.ca>
+Cc: Andreas Gruenbacher <agruenba@redhat.com>
+Cc: Anna Schumaker <anna@kernel.org>
+Cc: Arnd Bergmann <arnd@arndb.de>
+Cc: Bob Peterson <rpeterso@redhat.com>
+Cc: Borislav Petkov <bp@alien8.de>
+Cc: Carlos Llamas <cmllamas@google.com>
+Cc: Chandan Babu R <chandan.babu@oracle.com>
+Cc: Chao Yu <chao@kernel.org>
+Cc: Chris Mason <clm@fb.com>
+Cc: Coly Li <colyli@suse.de>
+Cc: Dai Ngo <Dai.Ngo@oracle.com>
+Cc: Dave Hansen <dave.hansen@linux.intel.com>
+Cc: David Airlie <airlied@gmail.com>
+Cc: David Hildenbrand <david@redhat.com>
+Cc: David Sterba <dsterba@suse.com>
+Cc: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
+Cc: Gao Xiang <hsiangkao@linux.alibaba.com>
+Cc: Huang Rui <ray.huang@amd.com>
+Cc: Ingo Molnar <mingo@redhat.com>
+Cc: Jaegeuk Kim <jaegeuk@kernel.org>
+Cc: Jani Nikula <jani.nikula@linux.intel.com>
+Cc: Jan Kara <jack@suse.cz>
+Cc: Jason Wang <jasowang@redhat.com>
+Cc: Jeff Layton <jlayton@kernel.org>
+Cc: Jeffle Xu <jefflexu@linux.alibaba.com>
+Cc: Joonas Lahtinen <joonas.lahtinen@linux.intel.com>
+Cc: Josef Bacik <josef@toxicpanda.com>
+Cc: Juergen Gross <jgross@suse.com>
+Cc: Kent Overstreet <kent.overstreet@gmail.com>
+Cc: Marijn Suijten <marijn.suijten@somainline.org>
+Cc: "Michael S. Tsirkin" <mst@redhat.com>
+Cc: Mike Snitzer <snitzer@kernel.org>
+Cc: Minchan Kim <minchan@kernel.org>
+Cc: Muchun Song <muchun.song@linux.dev>
+Cc: Nadav Amit <namit@vmware.com>
+Cc: Neil Brown <neilb@suse.de>
+Cc: Oleksandr Tyshchenko <oleksandr_tyshchenko@epam.com>
+Cc: Olga Kornievskaia <kolga@netapp.com>
+Cc: Richard Weinberger <richard@nod.at>
+Cc: Rob Clark <robdclark@gmail.com>
+Cc: Rob Herring <robh@kernel.org>
+Cc: Rodrigo Vivi <rodrigo.vivi@intel.com>
+Cc: Sean Paul <sean@poorly.run>
+Cc: Song Liu <song@kernel.org>
+Cc: Stefano Stabellini <sstabellini@kernel.org>
+Cc: Thomas Gleixner <tglx@linutronix.de>
+Cc: Tomeu Vizoso <tomeu.vizoso@collabora.com>
+Cc: Tom Talpey <tom@talpey.com>
+Cc: Trond Myklebust <trond.myklebust@hammerspace.com>
+Cc: Tvrtko Ursulin <tvrtko.ursulin@linux.intel.com>
+Cc: Xuan Zhuo <xuanzhuo@linux.alibaba.com>
+Cc: Yue Hu <huyue2@coolpad.com>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+Stable-dep-of: b902890c62d2 ("mm/shrinker: do not hold RCU lock in shrinker_debugfs_count_show()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ mm/shrinker_debug.c |   16 +---------------
+ 1 file changed, 1 insertion(+), 15 deletions(-)
+
+--- a/mm/shrinker_debug.c
++++ b/mm/shrinker_debug.c
+@@ -49,17 +49,12 @@ static int shrinker_debugfs_count_show(s
+       struct mem_cgroup *memcg;
+       unsigned long total;
+       bool memcg_aware;
+-      int ret, nid;
++      int ret = 0, nid;
+       count_per_node = kcalloc(nr_node_ids, sizeof(unsigned long), GFP_KERNEL);
+       if (!count_per_node)
+               return -ENOMEM;
+-      ret = down_read_killable(&shrinker_rwsem);
+-      if (ret) {
+-              kfree(count_per_node);
+-              return ret;
+-      }
+       rcu_read_lock();
+       memcg_aware = shrinker->flags & SHRINKER_MEMCG_AWARE;
+@@ -92,7 +87,6 @@ static int shrinker_debugfs_count_show(s
+       } while ((memcg = mem_cgroup_iter(NULL, memcg, NULL)) != NULL);
+       rcu_read_unlock();
+-      up_read(&shrinker_rwsem);
+       kfree(count_per_node);
+       return ret;
+@@ -117,7 +111,6 @@ static ssize_t shrinker_debugfs_scan_wri
+       struct mem_cgroup *memcg = NULL;
+       int nid;
+       char kbuf[72];
+-      ssize_t ret;
+       read_len = size < (sizeof(kbuf) - 1) ? size : (sizeof(kbuf) - 1);
+       if (copy_from_user(kbuf, buf, read_len))
+@@ -146,12 +139,6 @@ static ssize_t shrinker_debugfs_scan_wri
+               return -EINVAL;
+       }
+-      ret = down_read_killable(&shrinker_rwsem);
+-      if (ret) {
+-              mem_cgroup_put(memcg);
+-              return ret;
+-      }
+-
+       sc.nid = nid;
+       sc.memcg = memcg;
+       sc.nr_to_scan = nr_to_scan;
+@@ -159,7 +146,6 @@ static ssize_t shrinker_debugfs_scan_wri
+       shrinker->scan_objects(shrinker, &sc);
+-      up_read(&shrinker_rwsem);
+       mem_cgroup_put(memcg);
+       return size;
diff --git a/queue-6.6/mm-slab-do-not-limit-zeroing-to-orig_size-when-only-red-zoning-is-enabled.patch b/queue-6.6/mm-slab-do-not-limit-zeroing-to-orig_size-when-only-red-zoning-is-enabled.patch
new file mode 100644 (file)
index 0000000..d84b6de
--- /dev/null
@@ -0,0 +1,70 @@
+From stable+bounces-277067-greg=kroah.com@vger.kernel.org Fri Jul 17 16:29:51 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 17 Jul 2026 10:22:44 -0400
+Subject: mm/slab: do not limit zeroing to orig_size when only red zoning is enabled
+To: stable@vger.kernel.org
+Cc: "Vlastimil Babka (SUSE)" <vbabka@kernel.org>, "Harry Yoo (Oracle)" <harry@kernel.org>, Hao Li <hao.li@linux.dev>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260717142244.1820826-1-sashal@kernel.org>
+
+From: "Vlastimil Babka (SUSE)" <vbabka@kernel.org>
+
+[ Upstream commit 648927ceb84021a25a0fbd5673740956f318d534 ]
+
+When init (zeroing) on allocation is requested, for kmalloc() we
+generally have to zero the full object size even if a smaller size is
+requested, in order to provide krealloc()'s __GFP_ZERO guarantees.
+
+But if we track the requested size, krealloc() uses that information to
+do the right thing, so we can zero only the requested size. With red
+zoning also enabled, any extra size became part of the red zone, so it
+must not be zeroed and thus we must zero only the requested size.
+
+However the current check is imprecise, and will trigger also when only
+SLAB_RED_ZONE is enabled without SLAB_STORE_USER (which enables tracking
+the requested size). This means enabling red zoning alone can compromise
+krealloc()'s __GFP_ZERO contract.
+
+Fix this by using slub_debug_orig_size() instead, which is the exact
+check for whether the requested size is tracked. We don't need to care
+if red zoning is also enabled or not. Also update and expand the
+comment accordingly.
+
+Fixes: 9ce67395f5a0 ("mm/slub: only zero requested size of buffer for kzalloc when debug enabled")
+Cc: stable@vger.kernel.org
+Link: https://patch.msgid.link/20260610-slab_alloc_flags-v2-1-7190909db118@kernel.org
+Reviewed-by: Harry Yoo (Oracle) <harry@kernel.org>
+Reviewed-by: Hao Li <hao.li@linux.dev>
+Signed-off-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ mm/slab.h |   17 ++++++++++-------
+ 1 file changed, 10 insertions(+), 7 deletions(-)
+
+--- a/mm/slab.h
++++ b/mm/slab.h
+@@ -734,14 +734,17 @@ static inline void slab_post_alloc_hook(
+       flags &= gfp_allowed_mask;
+       /*
+-       * For kmalloc object, the allocated memory size(object_size) is likely
+-       * larger than the requested size(orig_size). If redzone check is
+-       * enabled for the extra space, don't zero it, as it will be redzoned
+-       * soon. The redzone operation for this extra space could be seen as a
+-       * replacement of current poisoning under certain debug option, and
+-       * won't break other sanity checks.
++       * For kmalloc object, the allocated size (object_size) can be larger
++       * than the requested size (orig_size). We however need to zero the
++       * whole object_size to handle possible later krealloc() with
++       *__GFP_ZERO properly.
++       *
++       * But if we keep track of the requested size, krealloc() uses that
++       * information. Additionally if red zoning is enabled, the extra space
++       * is also red zone, so we should not overwrite it. So limit zeroing to
++       * orig_size if we track it.
+        */
+-      if (kmem_cache_debug_flags(s, SLAB_STORE_USER | SLAB_RED_ZONE) &&
++      if (kmem_cache_debug_flags(s, SLAB_STORE_USER) &&
+           (s->flags & SLAB_KMALLOC))
+               zero_size = orig_size;
diff --git a/queue-6.6/netfilter-ebtables-use-vmalloc_array-to-improve-code.patch b/queue-6.6/netfilter-ebtables-use-vmalloc_array-to-improve-code.patch
new file mode 100644 (file)
index 0000000..cf606a9
--- /dev/null
@@ -0,0 +1,77 @@
+From stable+bounces-275038-greg=kroah.com@vger.kernel.org Wed Jul 15 22:49:56 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 16:49:46 -0400
+Subject: netfilter: ebtables: Use vmalloc_array() to improve code
+To: stable@vger.kernel.org
+Cc: Qianfeng Rong <rongqianfeng@vivo.com>, Florian Westphal <fw@strlen.de>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715204947.205286-1-sashal@kernel.org>
+
+From: Qianfeng Rong <rongqianfeng@vivo.com>
+
+[ Upstream commit 46015e6b3ea75297b28d4806564f3f692cf11861 ]
+
+Remove array_size() calls and replace vmalloc() with vmalloc_array() to
+simplify the code.  vmalloc_array() is also optimized better, uses fewer
+instructions, and handles overflow more concisely[1].
+
+[1]: https://lore.kernel.org/lkml/abc66ec5-85a4-47e1-9759-2f60ab111971@vivo.com/
+Signed-off-by: Qianfeng Rong <rongqianfeng@vivo.com>
+Signed-off-by: Florian Westphal <fw@strlen.de>
+Stable-dep-of: cbfe53599eeb ("netfilter: ebtables: zero chainstack array")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/bridge/netfilter/ebtables.c |   14 +++++++-------
+ 1 file changed, 7 insertions(+), 7 deletions(-)
+
+--- a/net/bridge/netfilter/ebtables.c
++++ b/net/bridge/netfilter/ebtables.c
+@@ -923,8 +923,8 @@ static int translate_table(struct net *n
+                * if an error occurs
+                */
+               newinfo->chainstack =
+-                      vmalloc(array_size(nr_cpu_ids,
+-                                         sizeof(*(newinfo->chainstack))));
++                      vmalloc_array(nr_cpu_ids,
++                                    sizeof(*(newinfo->chainstack)));
+               if (!newinfo->chainstack)
+                       return -ENOMEM;
+               for_each_possible_cpu(i) {
+@@ -941,7 +941,7 @@ static int translate_table(struct net *n
+                       }
+               }
+-              cl_s = vmalloc(array_size(udc_cnt, sizeof(*cl_s)));
++              cl_s = vmalloc_array(udc_cnt, sizeof(*cl_s));
+               if (!cl_s)
+                       return -ENOMEM;
+               i = 0; /* the i'th udc */
+@@ -1021,8 +1021,8 @@ static int do_replace_finish(struct net
+        * the check on the size is done later, when we have the lock
+        */
+       if (repl->num_counters) {
+-              unsigned long size = repl->num_counters * sizeof(*counterstmp);
+-              counterstmp = vmalloc(size);
++              counterstmp = vmalloc_array(repl->num_counters,
++                                          sizeof(*counterstmp));
+               if (!counterstmp)
+                       return -ENOMEM;
+       }
+@@ -1389,7 +1389,7 @@ static int do_update_counters(struct net
+       if (num_counters == 0)
+               return -EINVAL;
+-      tmp = vmalloc(array_size(num_counters, sizeof(*tmp)));
++      tmp = vmalloc_array(num_counters, sizeof(*tmp));
+       if (!tmp)
+               return -ENOMEM;
+@@ -1531,7 +1531,7 @@ static int copy_counters_to_user(struct
+       if (num_counters != nentries)
+               return -EINVAL;
+-      counterstmp = vmalloc(array_size(nentries, sizeof(*counterstmp)));
++      counterstmp = vmalloc_array(nentries, sizeof(*counterstmp));
+       if (!counterstmp)
+               return -ENOMEM;
diff --git a/queue-6.6/netfilter-ebtables-zero-chainstack-array.patch b/queue-6.6/netfilter-ebtables-zero-chainstack-array.patch
new file mode 100644 (file)
index 0000000..469412f
--- /dev/null
@@ -0,0 +1,46 @@
+From stable+bounces-275039-greg=kroah.com@vger.kernel.org Wed Jul 15 22:49:55 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 16:49:47 -0400
+Subject: netfilter: ebtables: zero chainstack array
+To: stable@vger.kernel.org
+Cc: Florian Westphal <fw@strlen.de>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715204947.205286-2-sashal@kernel.org>
+
+From: Florian Westphal <fw@strlen.de>
+
+[ Upstream commit cbfe53599eebffd188938ab6774cc41794f6f9d5 ]
+
+sashiko reports:
+ looking at ebtables table
+ translation, could a sparse cpu_possible_mask lead to an uninitialized pointer
+ free?
+
+ If cpu_possible_mask is sparse (for example, CPU 0 and CPU 2 are possible,
+ but CPU 1 is not), the allocation loop skips CPU 1. If vmalloc_node() fails at
+ CPU 2, the cleanup loop will blindly decrement and call vfree() on
+ newinfo->chainstack[1].
+
+Not a real-world bug, such allocation isn't expected to fail
+in the first place.
+
+Cc: stable@vger.kernel.org
+Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
+Signed-off-by: Florian Westphal <fw@strlen.de>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/bridge/netfilter/ebtables.c |    3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+--- a/net/bridge/netfilter/ebtables.c
++++ b/net/bridge/netfilter/ebtables.c
+@@ -923,8 +923,7 @@ static int translate_table(struct net *n
+                * if an error occurs
+                */
+               newinfo->chainstack =
+-                      vmalloc_array(nr_cpu_ids,
+-                                    sizeof(*(newinfo->chainstack)));
++                      vcalloc(nr_cpu_ids, sizeof(*(newinfo->chainstack)));
+               if (!newinfo->chainstack)
+                       return -ENOMEM;
+               for_each_possible_cpu(i) {
diff --git a/queue-6.6/nvmet-auth-validate-reply-message-payload-bounds-against-transfer-length.patch b/queue-6.6/nvmet-auth-validate-reply-message-payload-bounds-against-transfer-length.patch
new file mode 100644 (file)
index 0000000..c56abd5
--- /dev/null
@@ -0,0 +1,77 @@
+From stable+bounces-277828-greg=kroah.com@vger.kernel.org Mon Jul 20 16:19:16 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 10:10:33 -0400
+Subject: nvmet-auth: validate reply message payload bounds against transfer length
+To: stable@vger.kernel.org
+Cc: Tianchu Chen <flynnnchen@tencent.com>, Hannes Reinecke <hare@kernel.org>, Keith Busch <kbusch@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260720141033.1500263-3-sashal@kernel.org>
+
+From: Tianchu Chen <flynnnchen@tencent.com>
+
+[ Upstream commit 3a413ece2504c70aa34a20be4dafec04e8c741f9 ]
+
+nvmet_auth_reply() accesses the variable-length rval[] array using
+attacker-controlled hl (hash length) and dhvlen (DH value length) fields
+without verifying they fit within the allocated buffer of tl bytes.
+
+A malicious NVMe-oF initiator can craft a DHCHAP_REPLY message with a
+small transfer length but large hl/dhvlen values, causing out-of-bounds
+heap reads when the target processes the DH public key (rval + 2*hl) or
+performs the host response memcmp.
+
+With DH authentication configured, the OOB pointer is passed directly to
+sg_init_one() and read by crypto_kpp_compute_shared_secret(), reaching
+up to 526 bytes past the buffer. This is exploitable pre-authentication.
+
+Add bounds validation ensuring sizeof(*data) + 2*hl + dhvlen <= tl before
+any access to the variable-length fields.
+
+Discovered by Atuin - Automated Vulnerability Discovery Engine.
+
+Fixes: db1312dd9548 ("nvmet: implement basic In-Band Authentication")
+Cc: stable@vger.kernel.org
+Reviewed-by: Hannes Reinecke <hare@kernel.org>
+Signed-off-by: Tianchu Chen <flynnnchen@tencent.com>
+Signed-off-by: Keith Busch <kbusch@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvme/target/fabrics-cmd-auth.c |   15 ++++++++++++---
+ 1 file changed, 12 insertions(+), 3 deletions(-)
+
+--- a/drivers/nvme/target/fabrics-cmd-auth.c
++++ b/drivers/nvme/target/fabrics-cmd-auth.c
+@@ -109,13 +109,22 @@ static u8 nvmet_auth_negotiate(struct nv
+       return 0;
+ }
+-static u8 nvmet_auth_reply(struct nvmet_req *req, void *d)
++static u8 nvmet_auth_reply(struct nvmet_req *req, void *d, u32 tl)
+ {
+       struct nvmet_ctrl *ctrl = req->sq->ctrl;
+       struct nvmf_auth_dhchap_reply_data *data = d;
+-      u16 dhvlen = le16_to_cpu(data->dhvlen);
++      u16 dhvlen;
+       u8 *response;
++      if (tl < sizeof(*data))
++              return NVME_AUTH_DHCHAP_FAILURE_INCORRECT_PAYLOAD;
++
++      dhvlen = le16_to_cpu(data->dhvlen);
++
++      /* Validate that hl and dhvlen fit within the transfer length */
++      if (sizeof(*data) + 2 * (size_t)data->hl + dhvlen > tl)
++              return NVME_AUTH_DHCHAP_FAILURE_INCORRECT_PAYLOAD;
++
+       pr_debug("%s: ctrl %d qid %d: data hl %d cvalid %d dhvlen %u\n",
+                __func__, ctrl->cntlid, req->sq->qid,
+                data->hl, data->cvalid, dhvlen);
+@@ -287,7 +296,7 @@ void nvmet_execute_auth_send(struct nvme
+       switch (data->auth_id) {
+       case NVME_AUTH_DHCHAP_MESSAGE_REPLY:
+-              dhchap_status = nvmet_auth_reply(req, d);
++              dhchap_status = nvmet_auth_reply(req, d, tl);
+               if (dhchap_status == 0)
+                       req->sq->dhchap_step =
+                               NVME_AUTH_DHCHAP_MESSAGE_SUCCESS1;
diff --git a/queue-6.6/nvmet-remove-superfluous-initialization.patch b/queue-6.6/nvmet-remove-superfluous-initialization.patch
new file mode 100644 (file)
index 0000000..e097e5a
--- /dev/null
@@ -0,0 +1,45 @@
+From stable+bounces-277826-greg=kroah.com@vger.kernel.org Mon Jul 20 16:49:20 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 10:10:31 -0400
+Subject: nvmet: remove superfluous initialization
+To: stable@vger.kernel.org
+Cc: Chaitanya Kulkarni <kch@nvidia.com>, Keith Busch <kbusch@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260720141033.1500263-1-sashal@kernel.org>
+
+From: Chaitanya Kulkarni <kch@nvidia.com>
+
+[ Upstream commit 8d30528a170905ede9ab6ab81f229e441808590b ]
+
+Remove superfluous initialization of status variable in
+nvmet_execute_admin_connect() and nvmet_execute_io_connect(), since it
+will get overwritten by nvmet_copy_from_sgl().
+
+Signed-off-by: Chaitanya Kulkarni <kch@nvidia.com>
+Signed-off-by: Keith Busch <kbusch@kernel.org>
+Stable-dep-of: 3a413ece2504 ("nvmet-auth: validate reply message payload bounds against transfer length")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvme/target/fabrics-cmd.c |    4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/nvme/target/fabrics-cmd.c
++++ b/drivers/nvme/target/fabrics-cmd.c
+@@ -209,7 +209,7 @@ static void nvmet_execute_admin_connect(
+       struct nvmf_connect_command *c = &req->cmd->connect;
+       struct nvmf_connect_data *d;
+       struct nvmet_ctrl *ctrl = NULL;
+-      u16 status = 0;
++      u16 status;
+       int ret;
+       if (!nvmet_check_transfer_len(req, sizeof(struct nvmf_connect_data)))
+@@ -287,7 +287,7 @@ static void nvmet_execute_io_connect(str
+       struct nvmf_connect_data *d;
+       struct nvmet_ctrl *ctrl;
+       u16 qid = le16_to_cpu(c->qid);
+-      u16 status = 0;
++      u16 status;
+       if (!nvmet_check_transfer_len(req, sizeof(struct nvmf_connect_data)))
+               return;
diff --git a/queue-6.6/nvmet-return-dhchap-status-codes-from-nvmet_setup_auth.patch b/queue-6.6/nvmet-return-dhchap-status-codes-from-nvmet_setup_auth.patch
new file mode 100644 (file)
index 0000000..d3d0d3f
--- /dev/null
@@ -0,0 +1,280 @@
+From stable+bounces-277827-greg=kroah.com@vger.kernel.org Mon Jul 20 16:49:21 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 10:10:32 -0400
+Subject: nvmet: return DHCHAP status codes from nvmet_setup_auth()
+To: stable@vger.kernel.org
+Cc: Hannes Reinecke <hare@kernel.org>, Christoph Hellwig <hch@lst.de>, Hannes Reinecke <hare@suse.de>, Daniel Wagner <dwagner@suse.de>, Keith Busch <kbusch@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260720141033.1500263-2-sashal@kernel.org>
+
+From: Hannes Reinecke <hare@kernel.org>
+
+[ Upstream commit 44e3c25efae8575e06f1c5d1dc40058a991e3cb2 ]
+
+A failure in nvmet_setup_auth() does not mean that the NVMe
+authentication command failed, so we should rather return a protocol
+error with a 'failure1' response than an NVMe status.
+
+Also update the type used for dhchap_step and dhchap_status to u8 to
+avoid confusions with nvme status. Furthermore, split dhchap_status and
+nvme status so we don't accidentally mix these return values.
+
+Reviewed-by: Christoph Hellwig <hch@lst.de>
+Signed-off-by: Hannes Reinecke <hare@suse.de>
+[dwagner: - use u8 as type for dhchap_{step|status}
+          - separate nvme status from dhcap_status]
+Signed-off-by: Daniel Wagner <dwagner@suse.de>
+Signed-off-by: Keith Busch <kbusch@kernel.org>
+Stable-dep-of: 3a413ece2504 ("nvmet-auth: validate reply message payload bounds against transfer length")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvme/target/auth.c             |   20 +++++--------
+ drivers/nvme/target/fabrics-cmd-auth.c |   49 ++++++++++++++++-----------------
+ drivers/nvme/target/fabrics-cmd.c      |   11 ++++---
+ drivers/nvme/target/nvmet.h            |    8 ++---
+ 4 files changed, 43 insertions(+), 45 deletions(-)
+
+--- a/drivers/nvme/target/auth.c
++++ b/drivers/nvme/target/auth.c
+@@ -125,12 +125,11 @@ int nvmet_setup_dhgroup(struct nvmet_ctr
+       return ret;
+ }
+-int nvmet_setup_auth(struct nvmet_ctrl *ctrl)
++u8 nvmet_setup_auth(struct nvmet_ctrl *ctrl)
+ {
+       int ret = 0;
+       struct nvmet_host_link *p;
+       struct nvmet_host *host = NULL;
+-      const char *hash_name;
+       down_read(&nvmet_config_sem);
+       if (nvmet_is_disc_subsys(ctrl->subsys))
+@@ -148,13 +147,16 @@ int nvmet_setup_auth(struct nvmet_ctrl *
+       }
+       if (!host) {
+               pr_debug("host %s not found\n", ctrl->hostnqn);
+-              ret = -EPERM;
++              ret = NVME_AUTH_DHCHAP_FAILURE_FAILED;
+               goto out_unlock;
+       }
+       ret = nvmet_setup_dhgroup(ctrl, host->dhchap_dhgroup_id);
+-      if (ret < 0)
++      if (ret < 0) {
+               pr_warn("Failed to setup DH group");
++              ret = NVME_AUTH_DHCHAP_FAILURE_DHGROUP_UNUSABLE;
++              goto out_unlock;
++      }
+       if (!host->dhchap_secret) {
+               pr_debug("No authentication provided\n");
+@@ -165,12 +167,6 @@ int nvmet_setup_auth(struct nvmet_ctrl *
+               pr_debug("Re-use existing hash ID %d\n",
+                        ctrl->shash_id);
+       } else {
+-              hash_name = nvme_auth_hmac_name(host->dhchap_hash_id);
+-              if (!hash_name) {
+-                      pr_warn("Hash ID %d invalid\n", host->dhchap_hash_id);
+-                      ret = -EINVAL;
+-                      goto out_unlock;
+-              }
+               ctrl->shash_id = host->dhchap_hash_id;
+       }
+@@ -179,7 +175,7 @@ int nvmet_setup_auth(struct nvmet_ctrl *
+       ctrl->host_key = nvme_auth_extract_key(host->dhchap_secret + 10,
+                                              host->dhchap_key_hash);
+       if (IS_ERR(ctrl->host_key)) {
+-              ret = PTR_ERR(ctrl->host_key);
++              ret = NVME_AUTH_DHCHAP_FAILURE_NOT_USABLE;
+               ctrl->host_key = NULL;
+               goto out_free_hash;
+       }
+@@ -197,7 +193,7 @@ int nvmet_setup_auth(struct nvmet_ctrl *
+       ctrl->ctrl_key = nvme_auth_extract_key(host->dhchap_ctrl_secret + 10,
+                                              host->dhchap_ctrl_key_hash);
+       if (IS_ERR(ctrl->ctrl_key)) {
+-              ret = PTR_ERR(ctrl->ctrl_key);
++              ret = NVME_AUTH_DHCHAP_FAILURE_NOT_USABLE;
+               ctrl->ctrl_key = NULL;
+               goto out_free_hash;
+       }
+--- a/drivers/nvme/target/fabrics-cmd-auth.c
++++ b/drivers/nvme/target/fabrics-cmd-auth.c
+@@ -31,7 +31,7 @@ void nvmet_auth_sq_init(struct nvmet_sq
+       sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_NEGOTIATE;
+ }
+-static u16 nvmet_auth_negotiate(struct nvmet_req *req, void *d)
++static u8 nvmet_auth_negotiate(struct nvmet_req *req, void *d)
+ {
+       struct nvmet_ctrl *ctrl = req->sq->ctrl;
+       struct nvmf_auth_dhchap_negotiate_data *data = d;
+@@ -109,7 +109,7 @@ static u16 nvmet_auth_negotiate(struct n
+       return 0;
+ }
+-static u16 nvmet_auth_reply(struct nvmet_req *req, void *d)
++static u8 nvmet_auth_reply(struct nvmet_req *req, void *d)
+ {
+       struct nvmet_ctrl *ctrl = req->sq->ctrl;
+       struct nvmf_auth_dhchap_reply_data *data = d;
+@@ -172,7 +172,7 @@ static u16 nvmet_auth_reply(struct nvmet
+       return 0;
+ }
+-static u16 nvmet_auth_failure2(void *d)
++static u8 nvmet_auth_failure2(void *d)
+ {
+       struct nvmf_auth_dhchap_failure_data *data = d;
+@@ -186,6 +186,7 @@ void nvmet_execute_auth_send(struct nvme
+       void *d;
+       u32 tl;
+       u16 status = 0;
++      u8 dhchap_status;
+       if (req->cmd->auth_send.secp != NVME_AUTH_DHCHAP_PROTOCOL_IDENTIFIER) {
+               status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;
+@@ -237,30 +238,32 @@ void nvmet_execute_auth_send(struct nvme
+       if (data->auth_type == NVME_AUTH_COMMON_MESSAGES) {
+               if (data->auth_id == NVME_AUTH_DHCHAP_MESSAGE_NEGOTIATE) {
+                       /* Restart negotiation */
+-                      pr_debug("%s: ctrl %d qid %d reset negotiation\n", __func__,
+-                               ctrl->cntlid, req->sq->qid);
++                      pr_debug("%s: ctrl %d qid %d reset negotiation\n",
++                               __func__, ctrl->cntlid, req->sq->qid);
+                       if (!req->sq->qid) {
+-                              if (nvmet_setup_auth(ctrl) < 0) {
+-                                      status = NVME_SC_INTERNAL;
+-                                      pr_err("ctrl %d qid 0 failed to setup"
+-                                             "re-authentication",
++                              dhchap_status = nvmet_setup_auth(ctrl);
++                              if (dhchap_status) {
++                                      pr_err("ctrl %d qid 0 failed to setup re-authentication\n",
+                                              ctrl->cntlid);
+-                                      goto done_failure1;
++                                      req->sq->dhchap_status = dhchap_status;
++                                      req->sq->dhchap_step =
++                                              NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;
++                                      goto done_kfree;
+                               }
+                       }
+-                      req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_NEGOTIATE;
++                      req->sq->dhchap_step =
++                              NVME_AUTH_DHCHAP_MESSAGE_NEGOTIATE;
+               } else if (data->auth_id != req->sq->dhchap_step)
+                       goto done_failure1;
+               /* Validate negotiation parameters */
+-              status = nvmet_auth_negotiate(req, d);
+-              if (status == 0)
++              dhchap_status = nvmet_auth_negotiate(req, d);
++              if (dhchap_status == 0)
+                       req->sq->dhchap_step =
+                               NVME_AUTH_DHCHAP_MESSAGE_CHALLENGE;
+               else {
+                       req->sq->dhchap_step =
+                               NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;
+-                      req->sq->dhchap_status = status;
+-                      status = 0;
++                      req->sq->dhchap_status = dhchap_status;
+               }
+               goto done_kfree;
+       }
+@@ -284,15 +287,14 @@ void nvmet_execute_auth_send(struct nvme
+       switch (data->auth_id) {
+       case NVME_AUTH_DHCHAP_MESSAGE_REPLY:
+-              status = nvmet_auth_reply(req, d);
+-              if (status == 0)
++              dhchap_status = nvmet_auth_reply(req, d);
++              if (dhchap_status == 0)
+                       req->sq->dhchap_step =
+                               NVME_AUTH_DHCHAP_MESSAGE_SUCCESS1;
+               else {
+                       req->sq->dhchap_step =
+                               NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;
+-                      req->sq->dhchap_status = status;
+-                      status = 0;
++                      req->sq->dhchap_status = dhchap_status;
+               }
+               goto done_kfree;
+       case NVME_AUTH_DHCHAP_MESSAGE_SUCCESS2:
+@@ -301,13 +303,12 @@ void nvmet_execute_auth_send(struct nvme
+                        __func__, ctrl->cntlid, req->sq->qid);
+               goto done_kfree;
+       case NVME_AUTH_DHCHAP_MESSAGE_FAILURE2:
+-              status = nvmet_auth_failure2(d);
+-              if (status) {
++              dhchap_status = nvmet_auth_failure2(d);
++              if (dhchap_status) {
+                       pr_warn("ctrl %d qid %d: authentication failed (%d)\n",
+-                              ctrl->cntlid, req->sq->qid, status);
+-                      req->sq->dhchap_status = status;
++                              ctrl->cntlid, req->sq->qid, dhchap_status);
++                      req->sq->dhchap_status = dhchap_status;
+                       req->sq->authenticated = false;
+-                      status = 0;
+               }
+               goto done_kfree;
+       default:
+--- a/drivers/nvme/target/fabrics-cmd.c
++++ b/drivers/nvme/target/fabrics-cmd.c
+@@ -210,7 +210,7 @@ static void nvmet_execute_admin_connect(
+       struct nvmf_connect_data *d;
+       struct nvmet_ctrl *ctrl = NULL;
+       u16 status;
+-      int ret;
++      u8 dhchap_status;
+       if (!nvmet_check_transfer_len(req, sizeof(struct nvmf_connect_data)))
+               return;
+@@ -252,11 +252,12 @@ static void nvmet_execute_admin_connect(
+       uuid_copy(&ctrl->hostid, &d->hostid);
+-      ret = nvmet_setup_auth(ctrl);
+-      if (ret < 0) {
+-              pr_err("Failed to setup authentication, error %d\n", ret);
++      dhchap_status = nvmet_setup_auth(ctrl);
++      if (dhchap_status) {
++              pr_err("Failed to setup authentication, dhchap status %u\n",
++                     dhchap_status);
+               nvmet_ctrl_put(ctrl);
+-              if (ret == -EPERM)
++              if (dhchap_status == NVME_AUTH_DHCHAP_FAILURE_FAILED)
+                       status = (NVME_SC_CONNECT_INVALID_HOST | NVME_SC_DNR);
+               else
+                       status = NVME_SC_INTERNAL;
+--- a/drivers/nvme/target/nvmet.h
++++ b/drivers/nvme/target/nvmet.h
+@@ -112,8 +112,8 @@ struct nvmet_sq {
+       bool                    authenticated;
+       struct delayed_work     auth_expired_work;
+       u16                     dhchap_tid;
+-      u16                     dhchap_status;
+-      int                     dhchap_step;
++      u8                      dhchap_status;
++      u8                      dhchap_step;
+       u8                      *dhchap_c1;
+       u8                      *dhchap_c2;
+       u32                     dhchap_s1;
+@@ -700,7 +700,7 @@ void nvmet_execute_auth_receive(struct n
+ int nvmet_auth_set_key(struct nvmet_host *host, const char *secret,
+                      bool set_ctrl);
+ int nvmet_auth_set_host_hash(struct nvmet_host *host, const char *hash);
+-int nvmet_setup_auth(struct nvmet_ctrl *ctrl);
++u8 nvmet_setup_auth(struct nvmet_ctrl *ctrl);
+ void nvmet_auth_sq_init(struct nvmet_sq *sq);
+ void nvmet_destroy_auth(struct nvmet_ctrl *ctrl);
+ void nvmet_auth_sq_free(struct nvmet_sq *sq);
+@@ -719,7 +719,7 @@ int nvmet_auth_ctrl_exponential(struct n
+ int nvmet_auth_ctrl_sesskey(struct nvmet_req *req,
+                           u8 *buf, int buf_size);
+ #else
+-static inline int nvmet_setup_auth(struct nvmet_ctrl *ctrl)
++static inline u8 nvmet_setup_auth(struct nvmet_ctrl *ctrl)
+ {
+       return 0;
+ }
diff --git a/queue-6.6/pci-add-kerneldoc-for-pci_resize_resource.patch b/queue-6.6/pci-add-kerneldoc-for-pci_resize_resource.patch
new file mode 100644 (file)
index 0000000..25e6d4f
--- /dev/null
@@ -0,0 +1,55 @@
+From stable+bounces-274640-greg=kroah.com@vger.kernel.org Wed Jul 15 02:18:05 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 20:17:54 -0400
+Subject: PCI: Add kerneldoc for pci_resize_resource()
+To: stable@vger.kernel.org
+Cc: "Ilpo Järvinen" <ilpo.jarvinen@linux.intel.com>, "Bjorn Helgaas" <bhelgaas@google.com>, "Alex Bennée" <alex.bennee@linaro.org>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260715001756.3783927-4-sashal@kernel.org>
+
+From: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
+
+[ Upstream commit d787018e2dfdc4c1331538e7a8717690d1b7c9b3 ]
+
+As pci_resize_resource() is meant to be used also outside of PCI core,
+document the interface with kerneldoc.
+
+Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
+Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
+Tested-by: Alex Bennée <alex.bennee@linaro.org> # AVA, AMD GPU
+Link: https://patch.msgid.link/20251113162628.5946-8-ilpo.jarvinen@linux.intel.com
+Stable-dep-of: ee7471fe968d ("PCI: Skip Resizable BAR restore on read error")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/pci/setup-res.c |   20 ++++++++++++++++++++
+ 1 file changed, 20 insertions(+)
+
+--- a/drivers/pci/setup-res.c
++++ b/drivers/pci/setup-res.c
+@@ -427,6 +427,26 @@ void pci_release_resource(struct pci_dev
+ }
+ EXPORT_SYMBOL(pci_release_resource);
++/**
++ * pci_resize_resource - reconfigure a Resizable BAR and resources
++ * @dev: the PCI device
++ * @resno: index of the BAR to be resized
++ * @size: new size as defined in the spec (0=1MB, 31=128TB)
++ * @exclude_bars: a mask of BARs that should not be released
++ *
++ * Reconfigure @resno to @size and re-run resource assignment algorithm
++ * with the new size.
++ *
++ * Prior to resize, release @dev resources that share a bridge window with
++ * @resno.  This unpins the bridge window resource to allow changing it.
++ *
++ * The caller may prevent releasing a particular BAR by providing
++ * @exclude_bars mask, but this may result in the resize operation failing
++ * due to insufficient space.
++ *
++ * Return: 0 on success, or negative on error. In case of an error, the
++ *         resources are restored to their original places.
++ */
+ int pci_resize_resource(struct pci_dev *dev, int resno, int size,
+                       int exclude_bars)
+ {
diff --git a/queue-6.6/pci-altera-fix-resource-leaks-on-probe-failure.patch b/queue-6.6/pci-altera-fix-resource-leaks-on-probe-failure.patch
new file mode 100644 (file)
index 0000000..b074c51
--- /dev/null
@@ -0,0 +1,99 @@
+From stable+bounces-274303-greg=kroah.com@vger.kernel.org Tue Jul 14 16:50:59 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 10:46:24 -0400
+Subject: PCI: altera: Fix resource leaks on probe failure
+To: stable@vger.kernel.org
+Cc: Mahesh Vaidya <mahesh.vaidya@altera.com>, Manivannan Sadhasivam <mani@kernel.org>, "Subhransu S. Prusty" <subhransu.sekhar.prusty@altera.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260714144624.2781507-1-sashal@kernel.org>
+
+From: Mahesh Vaidya <mahesh.vaidya@altera.com>
+
+[ Upstream commit 7a94138caeb27f3c49c1dbd93bf422098925bb28 ]
+
+The chained IRQ handler is set during probe, but is only removed during the
+driver remove(). If pci_host_probe() fails, the handler and INTx IRQ
+domain remain set even though the devm-managed host bridge storage
+containing struct altera_pcie will be released, leaving the handler with
+a stale data pointer.
+
+Interrupts are also enabled before pci_host_probe() is called. If probe
+fails after that point, the controller interrupt source should be disabled
+before the chained handler and INTx domain are removed.
+
+So set the chained handler only after the INTx domain has been created.
+Disable controller interrupts during IRQ teardown, and tear the IRQ setup
+down if pci_host_probe() fails.
+
+Fixes: c63aed7334c2 ("PCI: altera: Use pci_host_probe() to register host")
+Signed-off-by: Mahesh Vaidya <mahesh.vaidya@altera.com>
+[mani: commit log]
+Signed-off-by: Manivannan Sadhasivam <mani@kernel.org>
+Reviewed-by: Subhransu S. Prusty <subhransu.sekhar.prusty@altera.com>
+Cc: stable@vger.kernel.org
+Link: https://patch.msgid.link/20260430204330.3121003-3-mahesh.vaidya@altera.com
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/pci/controller/pcie-altera.c |   28 ++++++++++++++++++++++++++--
+ 1 file changed, 26 insertions(+), 2 deletions(-)
+
+--- a/drivers/pci/controller/pcie-altera.c
++++ b/drivers/pci/controller/pcie-altera.c
+@@ -680,8 +680,18 @@ static int altera_pcie_init_irq_domain(s
+       return 0;
+ }
++static void altera_pcie_disable_irq(struct altera_pcie *pcie)
++{
++      if (pcie->pcie_data->version == ALTERA_PCIE_V1 ||
++          pcie->pcie_data->version == ALTERA_PCIE_V2) {
++              /* Disable all P2A interrupts */
++              cra_writel(pcie, 0, P2A_INT_ENABLE);
++      }
++}
++
+ static void altera_pcie_irq_teardown(struct altera_pcie *pcie)
+ {
++      altera_pcie_disable_irq(pcie);
+       irq_set_chained_handler_and_data(pcie->irq, NULL, NULL);
+       irq_domain_remove(pcie->irq_domain);
+ }
+@@ -706,7 +716,6 @@ static int altera_pcie_parse_dt(struct a
+       if (pcie->irq < 0)
+               return pcie->irq;
+-      irq_set_chained_handler_and_data(pcie->irq, altera_pcie_isr, pcie);
+       return 0;
+ }
+@@ -791,6 +800,12 @@ static int altera_pcie_probe(struct plat
+               return ret;
+       }
++      /*
++       * The chained handler uses pcie->irq_domain, so set it only after the
++       * INTx domain has been created.
++       */
++      irq_set_chained_handler_and_data(pcie->irq, altera_pcie_isr, pcie);
++
+       /* clear all interrupts */
+       cra_writel(pcie, P2A_INT_STS_ALL, P2A_INT_STATUS);
+       /* enable all interrupts */
+@@ -801,7 +816,16 @@ static int altera_pcie_probe(struct plat
+       bridge->busnr = pcie->root_bus_nr;
+       bridge->ops = &altera_pcie_ops;
+-      return pci_host_probe(bridge);
++      ret = pci_host_probe(bridge);
++      if (ret)
++              goto err_teardown_irq;
++
++      return 0;
++
++err_teardown_irq:
++      altera_pcie_irq_teardown(pcie);
++
++      return ret;
+ }
+ static void altera_pcie_remove(struct platform_device *pdev)
diff --git a/queue-6.6/pci-controller-use-dev_fwnode-instead-of-of_fwnode_handle.patch b/queue-6.6/pci-controller-use-dev_fwnode-instead-of-of_fwnode_handle.patch
new file mode 100644 (file)
index 0000000..2d1158d
--- /dev/null
@@ -0,0 +1,144 @@
+From stable+bounces-274574-greg=kroah.com@vger.kernel.org Wed Jul 15 00:04:38 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 18:04:11 -0400
+Subject: PCI: controller: Use dev_fwnode() instead of of_fwnode_handle()
+To: stable@vger.kernel.org
+Cc: "Jiri Slaby (SUSE)" <jirislaby@kernel.org>, Arnd Bergmann <arnd@arndb.de>, Bjorn Helgaas <bhelgaas@google.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260714220414.3333873-1-sashal@kernel.org>
+
+From: "Jiri Slaby (SUSE)" <jirislaby@kernel.org>
+
+[ Upstream commit a103d2dede5683dabbac2c3374bc24b6a9434478 ]
+
+All irq_domain functions now accept fwnode instead of of_node. But many
+PCI controllers still extract dev to of_node and then of_node to fwnode.
+
+Instead, clean this up and simply use the dev_fwnode() helper to extract
+fwnode directly from dev. Internally, it still does dev => of_node =>
+fwnode steps, but it's now hidden from the users.
+
+In the case of altera, this also removes an unused 'node' variable that is
+only used when CONFIG_OF is enabled:
+
+  drivers/pci/controller/pcie-altera.c: In function 'altera_pcie_init_irq_domain':
+  drivers/pci/controller/pcie-altera.c:855:29: error: unused variable 'node' [-Werror=unused-variable]
+    855 |         struct device_node *node = dev->of_node;
+
+Signed-off-by: Jiri Slaby (SUSE) <jirislaby@kernel.org>
+Signed-off-by: Arnd Bergmann <arnd@arndb.de>   # altera
+[bhelgaas: squash together, rebase to precede msi-parent]
+Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
+Link: https://patch.msgid.link/20250521163329.2137973-1-arnd@kernel.org
+Link: https://patch.msgid.link/20250611104348.192092-16-jirislaby@kernel.org
+Link: https://patch.msgid.link/20250723065907.1841758-1-jirislaby@kernel.org
+Stable-dep-of: f865a57896bd ("PCI: mediatek: Fix IRQ domain leak when port fails to enable")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/pci/controller/dwc/pcie-designware-host.c    |    2 +-
+ drivers/pci/controller/mobiveil/pcie-mobiveil-host.c |    8 +++-----
+ drivers/pci/controller/pcie-altera-msi.c             |    2 +-
+ drivers/pci/controller/pcie-altera.c                 |    3 +--
+ drivers/pci/controller/pcie-mediatek-gen3.c          |    4 ++--
+ drivers/pci/controller/pcie-mediatek.c               |    2 +-
+ drivers/pci/controller/pcie-xilinx-nwl.c             |    2 +-
+ 7 files changed, 10 insertions(+), 13 deletions(-)
+
+--- a/drivers/pci/controller/dwc/pcie-designware-host.c
++++ b/drivers/pci/controller/dwc/pcie-designware-host.c
+@@ -233,7 +233,7 @@ static const struct irq_domain_ops dw_pc
+ int dw_pcie_allocate_domains(struct dw_pcie_rp *pp)
+ {
+       struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+-      struct fwnode_handle *fwnode = of_node_to_fwnode(pci->dev->of_node);
++      struct fwnode_handle *fwnode = dev_fwnode(pci->dev);
+       pp->irq_domain = irq_domain_create_linear(fwnode, pp->num_vectors,
+                                              &dw_pcie_msi_domain_ops, pp);
+--- a/drivers/pci/controller/mobiveil/pcie-mobiveil-host.c
++++ b/drivers/pci/controller/mobiveil/pcie-mobiveil-host.c
+@@ -442,7 +442,7 @@ static const struct irq_domain_ops msi_d
+ static int mobiveil_allocate_msi_domains(struct mobiveil_pcie *pcie)
+ {
+       struct device *dev = &pcie->pdev->dev;
+-      struct fwnode_handle *fwnode = of_node_to_fwnode(dev->of_node);
++      struct fwnode_handle *fwnode = dev_fwnode(dev);
+       struct mobiveil_msi *msi = &pcie->rp.msi;
+       mutex_init(&msi->lock);
+@@ -468,13 +468,11 @@ static int mobiveil_allocate_msi_domains
+ static int mobiveil_pcie_init_irq_domain(struct mobiveil_pcie *pcie)
+ {
+       struct device *dev = &pcie->pdev->dev;
+-      struct device_node *node = dev->of_node;
+       struct mobiveil_root_port *rp = &pcie->rp;
+       /* setup INTx */
+-      rp->intx_domain = irq_domain_add_linear(node, PCI_NUM_INTX,
+-                                              &intx_domain_ops, pcie);
+-
++      rp->intx_domain = irq_domain_create_linear(dev_fwnode(dev), PCI_NUM_INTX, &intx_domain_ops,
++                                                 pcie);
+       if (!rp->intx_domain) {
+               dev_err(dev, "Failed to get a INTx IRQ domain\n");
+               return -ENOMEM;
+--- a/drivers/pci/controller/pcie-altera-msi.c
++++ b/drivers/pci/controller/pcie-altera-msi.c
+@@ -171,7 +171,7 @@ static const struct irq_domain_ops msi_d
+ static int altera_allocate_domains(struct altera_msi *msi)
+ {
+-      struct fwnode_handle *fwnode = of_node_to_fwnode(msi->pdev->dev.of_node);
++      struct fwnode_handle *fwnode = dev_fwnode(&msi->pdev->dev);
+       msi->inner_domain = irq_domain_add_linear(NULL, msi->num_of_vectors,
+                                            &msi_domain_ops, msi);
+--- a/drivers/pci/controller/pcie-altera.c
++++ b/drivers/pci/controller/pcie-altera.c
+@@ -667,10 +667,9 @@ static void altera_pcie_isr(struct irq_d
+ static int altera_pcie_init_irq_domain(struct altera_pcie *pcie)
+ {
+       struct device *dev = &pcie->pdev->dev;
+-      struct device_node *node = dev->of_node;
+       /* Setup INTx */
+-      pcie->irq_domain = irq_domain_add_linear(node, PCI_NUM_INTX,
++      pcie->irq_domain = irq_domain_create_linear(dev_fwnode(dev), PCI_NUM_INTX,
+                                       &intx_domain_ops, pcie);
+       if (!pcie->irq_domain) {
+               dev_err(dev, "Failed to get a INTx IRQ domain\n");
+--- a/drivers/pci/controller/pcie-mediatek-gen3.c
++++ b/drivers/pci/controller/pcie-mediatek-gen3.c
+@@ -661,8 +661,8 @@ static int mtk_pcie_init_irq_domains(str
+       /* Setup MSI */
+       mutex_init(&pcie->lock);
+-      pcie->msi_bottom_domain = irq_domain_add_linear(node, PCIE_MSI_IRQS_NUM,
+-                                &mtk_msi_bottom_domain_ops, pcie);
++      pcie->msi_bottom_domain = irq_domain_create_linear(dev_fwnode(dev), PCIE_MSI_IRQS_NUM,
++                                                         &mtk_msi_bottom_domain_ops, pcie);
+       if (!pcie->msi_bottom_domain) {
+               dev_err(dev, "failed to create MSI bottom domain\n");
+               ret = -ENODEV;
+--- a/drivers/pci/controller/pcie-mediatek.c
++++ b/drivers/pci/controller/pcie-mediatek.c
+@@ -496,7 +496,7 @@ static struct msi_domain_info mtk_msi_do
+ static int mtk_pcie_allocate_msi_domains(struct mtk_pcie_port *port)
+ {
+-      struct fwnode_handle *fwnode = of_node_to_fwnode(port->pcie->dev->of_node);
++      struct fwnode_handle *fwnode = dev_fwnode(port->pcie->dev);
+       mutex_init(&port->lock);
+--- a/drivers/pci/controller/pcie-xilinx-nwl.c
++++ b/drivers/pci/controller/pcie-xilinx-nwl.c
+@@ -502,7 +502,7 @@ static int nwl_pcie_init_msi_irq_domain(
+ {
+ #ifdef CONFIG_PCI_MSI
+       struct device *dev = pcie->dev;
+-      struct fwnode_handle *fwnode = of_node_to_fwnode(dev->of_node);
++      struct fwnode_handle *fwnode = dev_fwnode(dev);
+       struct nwl_msi *msi = &pcie->msi;
+       msi->dev_domain = irq_domain_add_linear(NULL, INT_PCI_MSI_NR,
diff --git a/queue-6.6/pci-fix-restoring-bars-on-bar-resize-rollback-path.patch b/queue-6.6/pci-fix-restoring-bars-on-bar-resize-rollback-path.patch
new file mode 100644 (file)
index 0000000..a3a63d6
--- /dev/null
@@ -0,0 +1,384 @@
+From stable+bounces-274639-greg=kroah.com@vger.kernel.org Wed Jul 15 02:18:04 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 20:17:53 -0400
+Subject: PCI: Fix restoring BARs on BAR resize rollback path
+To: stable@vger.kernel.org
+Cc: "Ilpo Järvinen" <ilpo.jarvinen@linux.intel.com>, "Simon Richter" <Simon.Richter@hogyros.de>, "Alex Bennée" <alex.bennee@linaro.org>, "Bjorn Helgaas" <bhelgaas@google.com>, "Christian König" <christian.koenig@amd.com>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260715001756.3783927-3-sashal@kernel.org>
+
+From: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
+
+[ Upstream commit 337b1b566db087347194e4543ddfdfa5645275cc ]
+
+BAR resize operation is implemented in the pci_resize_resource() and
+pbus_reassign_bridge_resources() functions. pci_resize_resource() can be
+called either from __resource_resize_store() from sysfs or directly by the
+driver for the Endpoint Device.
+
+The pci_resize_resource() requires that caller has released the device
+resources that share the bridge window with the BAR to be resized as
+otherwise the bridge window is pinned in place and cannot be changed.
+
+pbus_reassign_bridge_resources() rolls back resources if the resize
+operation fails, but rollback is performed only for the bridge windows.
+Because releasing the device resources are done by the caller of the BAR
+resize interface, these functions performing the BAR resize do not have
+access to the device resources as they were before the resize.
+
+pbus_reassign_bridge_resources() could try __pci_bridge_assign_resources()
+after rolling back the bridge windows as they were, however, it will not
+guarantee the resource are assigned due to differences in how FW and the
+kernel assign the resources (alignment of the start address and tail).
+
+To perform rollback robustly, the BAR resize interface has to be altered to
+also release the device resources that share the bridge window with the BAR
+to be resized.
+
+Also, remove restoring from the entries failed list as saved list should
+now contain both the bridge windows and device resources so the extra
+restore is duplicated work.
+
+Some drivers (currently only amdgpu) want to prevent releasing some
+resources. Add exclude_bars param to pci_resize_resource() and make amdgpu
+pass its register BAR (BAR 2 or 5), which should never be released during
+resize operation. Normally 64-bit prefetchable resources do not share a
+bridge window with the 32-bit only register BAR, but there are various
+fallbacks in the resource assignment logic which may make the resources
+share the bridge window in rare cases.
+
+This change (together with the driver side changes) is to counter the
+resource releases that had to be done to prevent resource tree corruption
+in the ("PCI: Release assigned resource before restoring them") change. As
+such, it likely restores functionality in cases where device resources were
+released to avoid resource tree conflicts which appeared to be "working"
+when such conflicts were not correctly detected by the kernel.
+
+Reported-by: Simon Richter <Simon.Richter@hogyros.de>
+Link: https://lore.kernel.org/linux-pci/f9a8c975-f5d3-4dd2-988e-4371a1433a60@hogyros.de/
+Reported-by: Alex Bennée <alex.bennee@linaro.org>
+Link: https://lore.kernel.org/linux-pci/874irqop6b.fsf@draig.linaro.org/
+Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
+[bhelgaas: squash amdgpu BAR selection from
+https: //lore.kernel.org/r/20251114103053.13778-1-ilpo.jarvinen@linux.intel.com]
+Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
+Tested-by: Alex Bennée <alex.bennee@linaro.org> # AVA, AMD GPU
+Reviewed-by: Christian König <christian.koenig@amd.com>
+Link: https://patch.msgid.link/20251113162628.5946-7-ilpo.jarvinen@linux.intel.com
+Stable-dep-of: ee7471fe968d ("PCI: Skip Resizable BAR restore on read error")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/gpu/drm/amd/amdgpu/amdgpu_device.c  |    4 -
+ drivers/gpu/drm/i915/gt/intel_region_lmem.c |    2 
+ drivers/pci/pci-sysfs.c                     |   14 ---
+ drivers/pci/pci.h                           |    3 
+ drivers/pci/setup-bus.c                     |  101 +++++++++++++++++++---------
+ drivers/pci/setup-res.c                     |   19 +----
+ include/linux/pci.h                         |    4 -
+ 7 files changed, 87 insertions(+), 60 deletions(-)
+
+--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c
++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c
+@@ -1168,7 +1168,9 @@ int amdgpu_device_resize_fb_bar(struct a
+       pci_release_resource(adev->pdev, 0);
+-      r = pci_resize_resource(adev->pdev, 0, rbar_size);
++      r = pci_resize_resource(adev->pdev, 0, rbar_size,
++                              (adev->asic_type >= CHIP_BONAIRE) ? 1 << 5
++                                                                : 1 << 2);
+       if (r == -ENOSPC)
+               DRM_INFO("Not enough PCI address space for a large BAR.");
+       else if (r && r != -ENOTSUPP)
+--- a/drivers/gpu/drm/i915/gt/intel_region_lmem.c
++++ b/drivers/gpu/drm/i915/gt/intel_region_lmem.c
+@@ -37,7 +37,7 @@ _resize_bar(struct drm_i915_private *i91
+       _release_bars(pdev);
+-      ret = pci_resize_resource(pdev, resno, bar_size);
++      ret = pci_resize_resource(pdev, resno, bar_size, 0);
+       if (ret) {
+               drm_info(&i915->drm, "Failed to resize BAR%d to %dM (%pe)\n",
+                        resno, 1 << bar_size, ERR_PTR(ret));
+--- a/drivers/pci/pci-sysfs.c
++++ b/drivers/pci/pci-sysfs.c
+@@ -1459,8 +1459,8 @@ static ssize_t resource##n##_resize_stor
+                                         const char *buf, size_t count)\
+ {                                                                     \
+       struct pci_dev *pdev = to_pci_dev(dev);                         \
+-      unsigned long size, flags;                                      \
+-      int ret, i;                                                     \
++      unsigned long size;                                             \
++      int ret;                                                        \
+       u16 cmd;                                                        \
+                                                                       \
+       if (kstrtoul(buf, 0, &size) < 0)                                \
+@@ -1485,17 +1485,9 @@ static ssize_t resource##n##_resize_stor
+       pci_write_config_word(pdev, PCI_COMMAND,                        \
+                             cmd & ~PCI_COMMAND_MEMORY);               \
+                                                                       \
+-      flags = pci_resource_flags(pdev, n);                            \
+-                                                                      \
+       pci_remove_resource_files(pdev);                                \
+                                                                       \
+-      for (i = 0; i < PCI_STD_NUM_BARS; i++) {                        \
+-              if (pci_resource_len(pdev, i) &&                        \
+-                  pci_resource_flags(pdev, i) == flags)               \
+-                      pci_release_resource(pdev, i);                  \
+-      }                                                               \
+-                                                                      \
+-      ret = pci_resize_resource(pdev, n, size);                       \
++      ret = pci_resize_resource(pdev, n, size, 0);                    \
+                                                                       \
+       pci_assign_unassigned_bus_resources(pdev->bus);                 \
+                                                                       \
+--- a/drivers/pci/pci.h
++++ b/drivers/pci/pci.h
+@@ -263,6 +263,9 @@ enum pci_bar_type {
+ struct device *pci_get_host_bridge_device(struct pci_dev *dev);
+ void pci_put_host_bridge_device(struct device *dev);
++int pci_do_resource_release_and_resize(struct pci_dev *dev, int resno, int size,
++                                     int exclude_bars);
++
+ int pci_configure_extended_tags(struct pci_dev *dev, void *ign);
+ bool pci_bus_read_dev_vendor_id(struct pci_bus *bus, int devfn, u32 *pl,
+                               int crs_timeout);
+--- a/drivers/pci/setup-bus.c
++++ b/drivers/pci/setup-bus.c
+@@ -2221,18 +2221,16 @@ enable_all:
+ }
+ EXPORT_SYMBOL_GPL(pci_assign_unassigned_bridge_resources);
+-int pci_reassign_bridge_resources(struct pci_dev *bridge, unsigned long type)
++static int pci_reassign_bridge_resources(struct pci_dev *bridge, unsigned long type,
++                                       struct list_head *saved)
+ {
+       struct pci_dev_resource *dev_res;
+       struct pci_dev *next;
+-      LIST_HEAD(saved);
+       LIST_HEAD(added);
+       LIST_HEAD(failed);
+       unsigned int i;
+       int ret;
+-      down_read(&pci_bus_sem);
+-
+       /* Walk to the root hub, releasing bridge BARs when possible */
+       next = bridge;
+       do {
+@@ -2249,9 +2247,9 @@ int pci_reassign_bridge_resources(struct
+                       if (res->child)
+                               continue;
+-                      ret = add_to_list(&saved, bridge, res, 0, 0);
++                      ret = add_to_list(saved, bridge, res, 0, 0);
+                       if (ret)
+-                              goto cleanup;
++                              return ret;
+                       pci_info(bridge, "%s %pR: releasing\n", res_name, res);
+@@ -2267,67 +2265,108 @@ int pci_reassign_bridge_resources(struct
+               next = bridge->bus ? bridge->bus->self : NULL;
+       } while (next);
+-      if (list_empty(&saved)) {
+-              up_read(&pci_bus_sem);
++      if (list_empty(saved))
+               return -ENOENT;
+-      }
+       __pci_bus_size_bridges(bridge->subordinate, &added);
+       __pci_bridge_assign_resources(bridge, &added, &failed);
+       BUG_ON(!list_empty(&added));
+       if (!list_empty(&failed)) {
+-              ret = -ENOSPC;
+-              goto cleanup;
++              free_list(&failed);
++              return -ENOSPC;
+       }
+-      list_for_each_entry(dev_res, &saved, list) {
++      list_for_each_entry(dev_res, saved, list) {
+               /* Skip the bridge we just assigned resources for */
+               if (bridge == dev_res->dev)
+                       continue;
++              if (!dev_res->dev->subordinate)
++                      continue;
++
+               bridge = dev_res->dev;
+               pci_setup_bridge(bridge->subordinate);
+       }
+-      free_list(&saved);
+-      up_read(&pci_bus_sem);
+       return 0;
++}
+-cleanup:
+-      /* Restore size and flags */
+-      list_for_each_entry(dev_res, &failed, list) {
+-              struct resource *res = dev_res->res;
++int pci_do_resource_release_and_resize(struct pci_dev *pdev, int resno, int size,
++                                     int exclude_bars)
++{
++      struct resource *res = pdev->resource + resno;
++      unsigned long flags = res->flags;
++      struct pci_dev_resource *dev_res;
++      struct pci_bus *bus = pdev->bus;
++      struct resource *r;
++      LIST_HEAD(saved);
++      unsigned int i;
++      int ret = 0;
+-              res->start = dev_res->start;
+-              res->end = dev_res->end;
+-              res->flags = dev_res->flags;
++      down_read(&pci_bus_sem);
++
++      pci_dev_for_each_resource(pdev, r, i) {
++              if (i >= PCI_BRIDGE_RESOURCES)
++                      break;
++
++              if (exclude_bars & BIT(i))
++                      continue;
++
++              if (!pci_resource_len(pdev, i) || r->flags != flags)
++                      continue;
++
++              ret = add_to_list(&saved, pdev, r, 0, 0);
++              if (ret)
++                      goto restore;
++              pci_release_resource(pdev, i);
+       }
+-      free_list(&failed);
++      res->end = res->start + pci_rebar_size_to_bytes(size) - 1;
++
++      if (!bus->self)
++              goto out;
++
++      ret = pci_reassign_bridge_resources(bus->self, res->flags, &saved);
++      if (ret)
++              goto restore;
++
++out:
++      up_read(&pci_bus_sem);
++      free_list(&saved);
++      return ret;
++
++restore:
+       /* Revert to the old configuration */
+       list_for_each_entry(dev_res, &saved, list) {
+               struct resource *res = dev_res->res;
++              struct pci_dev *dev = dev_res->dev;
+-              bridge = dev_res->dev;
+-              i = res - bridge->resource;
++              i = res - dev->resource;
+               if (res->parent) {
+                       release_child_resources(res);
+-                      pci_release_resource(bridge, i);
++                      pci_release_resource(dev, i);
+               }
+               res->start = dev_res->start;
+               res->end = dev_res->end;
+               res->flags = dev_res->flags;
+-              pci_claim_resource(bridge, i);
+-              pci_setup_bridge(bridge->subordinate);
+-      }
+-      up_read(&pci_bus_sem);
+-      free_list(&saved);
++              if (pci_claim_resource(dev, i))
++                      continue;
+-      return ret;
++              if (i < PCI_BRIDGE_RESOURCES) {
++                      const char *res_name = pci_resource_name(dev, i);
++
++                      pci_update_resource(dev, i);
++                      pci_info(dev, "%s %pR: old value restored\n",
++                               res_name, res);
++              }
++              if (dev->subordinate)
++                      pci_setup_bridge(dev->subordinate);
++      }
++      goto out;
+ }
+ void pci_assign_unassigned_bus_resources(struct pci_bus *bus)
+--- a/drivers/pci/setup-res.c
++++ b/drivers/pci/setup-res.c
+@@ -427,9 +427,9 @@ void pci_release_resource(struct pci_dev
+ }
+ EXPORT_SYMBOL(pci_release_resource);
+-int pci_resize_resource(struct pci_dev *dev, int resno, int size)
++int pci_resize_resource(struct pci_dev *dev, int resno, int size,
++                      int exclude_bars)
+ {
+-      struct resource *res = dev->resource + resno;
+       struct pci_host_bridge *host;
+       int old, ret;
+       u32 sizes;
+@@ -440,10 +440,6 @@ int pci_resize_resource(struct pci_dev *
+       if (host->preserve_config)
+               return -ENOTSUPP;
+-      /* Make sure the resource isn't assigned before resizing it. */
+-      if (!(res->flags & IORESOURCE_UNSET))
+-              return -EBUSY;
+-
+       pci_read_config_word(dev, PCI_COMMAND, &cmd);
+       if (cmd & PCI_COMMAND_MEMORY)
+               return -EBUSY;
+@@ -463,19 +459,14 @@ int pci_resize_resource(struct pci_dev *
+       if (ret)
+               return ret;
+-      res->end = res->start + pci_rebar_size_to_bytes(size) - 1;
++      ret = pci_do_resource_release_and_resize(dev, resno, size, exclude_bars);
++      if (ret)
++              goto error_resize;
+-      /* Check if the new config works by trying to assign everything. */
+-      if (dev->bus->self) {
+-              ret = pci_reassign_bridge_resources(dev->bus->self, res->flags);
+-              if (ret)
+-                      goto error_resize;
+-      }
+       return 0;
+ error_resize:
+       pci_rebar_set_size(dev, resno, old);
+-      res->end = res->start + pci_rebar_size_to_bytes(old) - 1;
+       return ret;
+ }
+ EXPORT_SYMBOL(pci_resize_resource);
+--- a/include/linux/pci.h
++++ b/include/linux/pci.h
+@@ -1390,7 +1390,8 @@ static inline int pci_rebar_bytes_to_siz
+ }
+ u32 pci_rebar_get_possible_sizes(struct pci_dev *pdev, int bar);
+-int __must_check pci_resize_resource(struct pci_dev *dev, int i, int size);
++int __must_check pci_resize_resource(struct pci_dev *dev, int i, int size,
++                                   int exclude_bars);
+ int pci_select_bars(struct pci_dev *dev, unsigned long flags);
+ bool pci_device_is_present(struct pci_dev *pdev);
+ void pci_ignore_hotplug(struct pci_dev *dev);
+@@ -1460,7 +1461,6 @@ void pci_assign_unassigned_resources(voi
+ void pci_assign_unassigned_bridge_resources(struct pci_dev *bridge);
+ void pci_assign_unassigned_bus_resources(struct pci_bus *bus);
+ void pci_assign_unassigned_root_bus_resources(struct pci_bus *bus);
+-int pci_reassign_bridge_resources(struct pci_dev *bridge, unsigned long type);
+ int pci_enable_resources(struct pci_dev *, int mask);
+ void pci_assign_irq(struct pci_dev *dev);
+ struct resource *pci_find_resource(struct pci_dev *dev, struct resource *res);
diff --git a/queue-6.6/pci-free-saved-list-without-holding-pci_bus_sem.patch b/queue-6.6/pci-free-saved-list-without-holding-pci_bus_sem.patch
new file mode 100644 (file)
index 0000000..d200e44
--- /dev/null
@@ -0,0 +1,38 @@
+From stable+bounces-274638-greg=kroah.com@vger.kernel.org Wed Jul 15 02:18:34 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 20:17:52 -0400
+Subject: PCI: Free saved list without holding pci_bus_sem
+To: stable@vger.kernel.org
+Cc: "Ilpo Järvinen" <ilpo.jarvinen@linux.intel.com>, "Bjorn Helgaas" <bhelgaas@google.com>, "Alex Bennée" <alex.bennee@linaro.org>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260715001756.3783927-2-sashal@kernel.org>
+
+From: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
+
+[ Upstream commit 1d8a0506f69895b7cfd9d5c4546761c508231a8a ]
+
+Freeing the saved list does not require holding pci_bus_sem, so the
+critical section can be made shorter.
+
+Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
+Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
+Tested-by: Alex Bennée <alex.bennee@linaro.org> # AVA, AMD GPU
+Link: https://patch.msgid.link/20251113162628.5946-6-ilpo.jarvinen@linux.intel.com
+Stable-dep-of: ee7471fe968d ("PCI: Skip Resizable BAR restore on read error")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/pci/setup-bus.c |    2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/pci/setup-bus.c
++++ b/drivers/pci/setup-bus.c
+@@ -2324,8 +2324,8 @@ cleanup:
+               pci_claim_resource(bridge, i);
+               pci_setup_bridge(bridge->subordinate);
+       }
+-      free_list(&saved);
+       up_read(&pci_bus_sem);
++      free_list(&saved);
+       return ret;
+ }
diff --git a/queue-6.6/pci-imx6-fix-imx6sx_gpr12_pcie_test_powerdown-handling.patch b/queue-6.6/pci-imx6-fix-imx6sx_gpr12_pcie_test_powerdown-handling.patch
new file mode 100644 (file)
index 0000000..321b00b
--- /dev/null
@@ -0,0 +1,54 @@
+From stable+bounces-274468-greg=kroah.com@vger.kernel.org Tue Jul 14 18:58:05 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 12:56:09 -0400
+Subject: PCI: imx6: Fix IMX6SX_GPR12_PCIE_TEST_POWERDOWN handling
+To: stable@vger.kernel.org
+Cc: Richard Zhu <hongxing.zhu@nxp.com>, Manivannan Sadhasivam <mani@kernel.org>, Bjorn Helgaas <bhelgaas@google.com>, Frank Li <Frank.Li@nxp.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260714165609.2885969-1-sashal@kernel.org>
+
+From: Richard Zhu <hongxing.zhu@nxp.com>
+
+[ Upstream commit aad953fb4eed0df5486cd54ccad80ac197678e01 ]
+
+The IMX6SX_GPR12_PCIE_TEST_POWERDOWN bit does not control the PCIe
+reference clock on i.MX6SX. Instead, it is part of i.MX6SX PCIe core
+reset sequence.
+
+Move the IMX6SX_GPR12_PCIE_TEST_POWERDOWN assertion/deassertion into
+the core reset functions to properly reflect its purpose. Remove the
+.enable_ref_clk() callback for i.MX6SX since it was incorrectly
+manipulating this bit.
+
+Fixes: e3c06cd063d6 ("PCI: imx6: Add initial imx6sx support")
+Signed-off-by: Richard Zhu <hongxing.zhu@nxp.com>
+Signed-off-by: Manivannan Sadhasivam <mani@kernel.org>
+Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
+Reviewed-by: Frank Li <Frank.Li@nxp.com>
+Cc: stable@vger.kernel.org
+Link: https://patch.msgid.link/20260319090844.444987-1-hongxing.zhu@nxp.com
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/pci/controller/dwc/pci-imx6.c |    4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/pci/controller/dwc/pci-imx6.c
++++ b/drivers/pci/controller/dwc/pci-imx6.c
+@@ -560,8 +560,6 @@ static int imx6_pcie_enable_ref_clk(stru
+       switch (imx6_pcie->drvdata->variant) {
+       case IMX6SX:
+-              regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12,
+-                                 IMX6SX_GPR12_PCIE_TEST_POWERDOWN, 0);
+               break;
+       case IMX6QP:
+       case IMX6Q:
+@@ -733,6 +731,8 @@ static int imx6_pcie_deassert_core_reset
+               imx7d_pcie_wait_for_phy_pll_lock(imx6_pcie);
+               break;
+       case IMX6SX:
++              regmap_clear_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12,
++                                IMX6SX_GPR12_PCIE_TEST_POWERDOWN);
+               regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR5,
+                                  IMX6SX_GPR5_PCIE_BTNRST_RESET, 0);
+               break;
diff --git a/queue-6.6/pci-mediatek-convert-bool-to-single-quirks-entry-and-bitmap.patch b/queue-6.6/pci-mediatek-convert-bool-to-single-quirks-entry-and-bitmap.patch
new file mode 100644 (file)
index 0000000..7a2f346
--- /dev/null
@@ -0,0 +1,127 @@
+From stable+bounces-274575-greg=kroah.com@vger.kernel.org Wed Jul 15 00:05:12 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 18:04:12 -0400
+Subject: PCI: mediatek: Convert bool to single quirks entry and bitmap
+To: stable@vger.kernel.org
+Cc: Christian Marangi <ansuelsmth@gmail.com>, Manivannan Sadhasivam <mani@kernel.org>, AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260714220414.3333873-2-sashal@kernel.org>
+
+From: Christian Marangi <ansuelsmth@gmail.com>
+
+[ Upstream commit 04305367fab7ec9c98eeba315ad09c8b20abce93 ]
+
+To clean Mediatek SoC PCIe struct, convert all the bool to a bitmap and
+use a single quirks to reference all the values. This permits cleaner
+addition of new quirk without having to define a new bool in the struct.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: Manivannan Sadhasivam <mani@kernel.org>
+Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
+Link: https://patch.msgid.link/20251020111121.31779-4-ansuelsmth@gmail.com
+Stable-dep-of: f865a57896bd ("PCI: mediatek: Fix IRQ domain leak when port fails to enable")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/pci/controller/pcie-mediatek.c |   33 ++++++++++++++++++++-------------
+ 1 file changed, 20 insertions(+), 13 deletions(-)
+
+--- a/drivers/pci/controller/pcie-mediatek.c
++++ b/drivers/pci/controller/pcie-mediatek.c
+@@ -143,23 +143,31 @@
+ struct mtk_pcie_port;
+ /**
++ * enum mtk_pcie_quirks - MTK PCIe quirks
++ * @MTK_PCIE_FIX_CLASS_ID: host's class ID needed to be fixed
++ * @MTK_PCIE_FIX_DEVICE_ID: host's device ID needed to be fixed
++ * @MTK_PCIE_NO_MSI: Bridge has no MSI support, and relies on an external block
++ */
++enum mtk_pcie_quirks {
++      MTK_PCIE_FIX_CLASS_ID = BIT(0),
++      MTK_PCIE_FIX_DEVICE_ID = BIT(1),
++      MTK_PCIE_NO_MSI = BIT(2),
++};
++
++/**
+  * struct mtk_pcie_soc - differentiate between host generations
+- * @need_fix_class_id: whether this host's class ID needed to be fixed or not
+- * @need_fix_device_id: whether this host's device ID needed to be fixed or not
+- * @no_msi: Bridge has no MSI support, and relies on an external block
+  * @device_id: device ID which this host need to be fixed
+  * @ops: pointer to configuration access functions
+  * @startup: pointer to controller setting functions
+  * @setup_irq: pointer to initialize IRQ functions
++ * @quirks: PCIe device quirks.
+  */
+ struct mtk_pcie_soc {
+-      bool need_fix_class_id;
+-      bool need_fix_device_id;
+-      bool no_msi;
+       unsigned int device_id;
+       struct pci_ops *ops;
+       int (*startup)(struct mtk_pcie_port *port);
+       int (*setup_irq)(struct mtk_pcie_port *port, struct device_node *node);
++      enum mtk_pcie_quirks quirks;
+ };
+ /**
+@@ -720,7 +728,7 @@ static int mtk_pcie_startup_port_v2(stru
+       writel(val, port->base + PCIE_RST_CTRL);
+       /* Set up vendor ID and class code */
+-      if (soc->need_fix_class_id) {
++      if (soc->quirks & MTK_PCIE_FIX_CLASS_ID) {
+               val = PCI_VENDOR_ID_MEDIATEK;
+               writew(val, port->base + PCIE_CONF_VEND_ID);
+@@ -728,7 +736,7 @@ static int mtk_pcie_startup_port_v2(stru
+               writew(val, port->base + PCIE_CONF_CLASS_ID);
+       }
+-      if (soc->need_fix_device_id)
++      if (soc->quirks & MTK_PCIE_FIX_DEVICE_ID)
+               writew(soc->device_id, port->base + PCIE_CONF_DEVICE_ID);
+       /* 100ms timeout value should be enough for Gen1/2 training */
+@@ -1129,7 +1137,7 @@ static int mtk_pcie_probe(struct platfor
+       host->ops = pcie->soc->ops;
+       host->sysdata = pcie;
+-      host->msi_domain = pcie->soc->no_msi;
++      host->msi_domain = !!(pcie->soc->quirks & MTK_PCIE_NO_MSI);
+       err = pci_host_probe(host);
+       if (err)
+@@ -1217,9 +1225,9 @@ static const struct dev_pm_ops mtk_pcie_
+ };
+ static const struct mtk_pcie_soc mtk_pcie_soc_v1 = {
+-      .no_msi = true,
+       .ops = &mtk_pcie_ops,
+       .startup = mtk_pcie_startup_port,
++      .quirks = MTK_PCIE_NO_MSI,
+ };
+ static const struct mtk_pcie_soc mtk_pcie_soc_mt2712 = {
+@@ -1229,19 +1237,18 @@ static const struct mtk_pcie_soc mtk_pci
+ };
+ static const struct mtk_pcie_soc mtk_pcie_soc_mt7622 = {
+-      .need_fix_class_id = true,
+       .ops = &mtk_pcie_ops_v2,
+       .startup = mtk_pcie_startup_port_v2,
+       .setup_irq = mtk_pcie_setup_irq,
++      .quirks = MTK_PCIE_FIX_CLASS_ID,
+ };
+ static const struct mtk_pcie_soc mtk_pcie_soc_mt7629 = {
+-      .need_fix_class_id = true,
+-      .need_fix_device_id = true,
+       .device_id = PCI_DEVICE_ID_MEDIATEK_7629,
+       .ops = &mtk_pcie_ops_v2,
+       .startup = mtk_pcie_startup_port_v2,
+       .setup_irq = mtk_pcie_setup_irq,
++      .quirks = MTK_PCIE_FIX_CLASS_ID | MTK_PCIE_FIX_DEVICE_ID,
+ };
+ static const struct of_device_id mtk_pcie_ids[] = {
diff --git a/queue-6.6/pci-mediatek-fix-irq-domain-leak-when-port-fails-to-enable.patch b/queue-6.6/pci-mediatek-fix-irq-domain-leak-when-port-fails-to-enable.patch
new file mode 100644 (file)
index 0000000..9c4afb1
--- /dev/null
@@ -0,0 +1,170 @@
+From stable+bounces-274577-greg=kroah.com@vger.kernel.org Wed Jul 15 00:05:05 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 18:04:14 -0400
+Subject: PCI: mediatek: Fix IRQ domain leak when port fails to enable
+To: stable@vger.kernel.org
+Cc: Manivannan Sadhasivam <manivannan.sadhasivam@oss.qualcomm.com>, Manivannan Sadhasivam <mani@kernel.org>, Caleb James DeLisle <cjd@cjdns.fr>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260714220414.3333873-4-sashal@kernel.org>
+
+From: Manivannan Sadhasivam <manivannan.sadhasivam@oss.qualcomm.com>
+
+[ Upstream commit f865a57896bd92d7662eb2818d8f48872e2cbbc7 ]
+
+When mtk_pcie_enable_port() fails, mtk_pcie_port_free() removes the port
+from pcie->ports and frees the port structure. However, the IRQ domains set
+up earlier by mtk_pcie_init_irq_domain() are never freed.
+
+Fix this by refactoring mtk_pcie_irq_teardown() into a per-port helper,
+mtk_pcie_irq_teardown_port(), and calling it from mtk_pcie_setup() when
+mtk_pcie_enable_port() fails. Since the IRQ teardown must only happen in
+the probe error path (during resume, child devices may have active MSI
+mappings and the NOIRQ context prohibits sleeping locks),
+mtk_pcie_enable_port() is changed to return an error code so callers can
+distinguish the two paths and act accordingly.
+
+This issue was reported by Sashiko while reviewing the EcoNet EN7528 SoC
+support series.
+
+Fixes: b099631df160 ("PCI: mediatek: Add controller support for MT2712 and MT7622")
+Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@oss.qualcomm.com>
+Signed-off-by: Manivannan Sadhasivam <mani@kernel.org>
+Cc: stable@vger.kernel.org # 5.10
+Cc: Caleb James DeLisle <cjd@cjdns.fr>
+Link: https://patch.msgid.link/20260521174617.17692-1-mani@kernel.org
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/pci/controller/pcie-mediatek.c |   67 ++++++++++++++++++++-------------
+ 1 file changed, 42 insertions(+), 25 deletions(-)
+
+--- a/drivers/pci/controller/pcie-mediatek.c
++++ b/drivers/pci/controller/pcie-mediatek.c
+@@ -540,25 +540,29 @@ static void mtk_pcie_enable_msi(struct m
+       writel(val, port->base + PCIE_INT_MASK);
+ }
+-static void mtk_pcie_irq_teardown(struct mtk_pcie *pcie)
++static void mtk_pcie_irq_teardown_port(struct mtk_pcie_port *port)
+ {
+-      struct mtk_pcie_port *port, *tmp;
++      irq_set_chained_handler_and_data(port->irq, NULL, NULL);
+-      list_for_each_entry_safe(port, tmp, &pcie->ports, list) {
+-              irq_set_chained_handler_and_data(port->irq, NULL, NULL);
++      if (port->irq_domain)
++              irq_domain_remove(port->irq_domain);
+-              if (port->irq_domain)
+-                      irq_domain_remove(port->irq_domain);
++      if (IS_ENABLED(CONFIG_PCI_MSI)) {
++              if (port->msi_domain)
++                      irq_domain_remove(port->msi_domain);
++              if (port->inner_domain)
++                      irq_domain_remove(port->inner_domain);
++      }
+-              if (IS_ENABLED(CONFIG_PCI_MSI)) {
+-                      if (port->msi_domain)
+-                              irq_domain_remove(port->msi_domain);
+-                      if (port->inner_domain)
+-                              irq_domain_remove(port->inner_domain);
+-              }
++      irq_dispose_mapping(port->irq);
++}
+-              irq_dispose_mapping(port->irq);
+-      }
++static void mtk_pcie_irq_teardown(struct mtk_pcie *pcie)
++{
++      struct mtk_pcie_port *port, *tmp;
++
++      list_for_each_entry_safe(port, tmp, &pcie->ports, list)
++              mtk_pcie_irq_teardown_port(port);
+ }
+ static int mtk_pcie_intx_map(struct irq_domain *domain, unsigned int irq,
+@@ -841,7 +845,7 @@ static int mtk_pcie_startup_port(struct
+       return 0;
+ }
+-static void mtk_pcie_enable_port(struct mtk_pcie_port *port)
++static int mtk_pcie_enable_port(struct mtk_pcie_port *port)
+ {
+       struct mtk_pcie *pcie = port->pcie;
+       struct device *dev = pcie->dev;
+@@ -850,7 +854,7 @@ static void mtk_pcie_enable_port(struct
+       err = clk_prepare_enable(port->sys_ck);
+       if (err) {
+               dev_err(dev, "failed to enable sys_ck%d clock\n", port->slot);
+-              goto err_sys_clk;
++              return err;
+       }
+       err = clk_prepare_enable(port->ahb_ck);
+@@ -898,11 +902,15 @@ static void mtk_pcie_enable_port(struct
+               goto err_phy_on;
+       }
+-      if (!pcie->soc->startup(port))
+-              return;
++      err = pcie->soc->startup(port);
++      if (err) {
++              dev_info(dev, "Port%d link down\n", port->slot);
++              goto err_soc_startup;
++      }
+-      dev_info(dev, "Port%d link down\n", port->slot);
++      return 0;
++err_soc_startup:
+       phy_power_off(port->phy);
+ err_phy_on:
+       phy_exit(port->phy);
+@@ -918,8 +926,8 @@ err_aux_clk:
+       clk_disable_unprepare(port->ahb_ck);
+ err_ahb_clk:
+       clk_disable_unprepare(port->sys_ck);
+-err_sys_clk:
+-      mtk_pcie_port_free(port);
++
++      return err;
+ }
+ static int mtk_pcie_parse_port(struct mtk_pcie *pcie,
+@@ -1095,8 +1103,13 @@ static int mtk_pcie_setup(struct mtk_pci
+               return err;
+       /* enable each port, and then check link status */
+-      list_for_each_entry_safe(port, tmp, &pcie->ports, list)
+-              mtk_pcie_enable_port(port);
++      list_for_each_entry_safe(port, tmp, &pcie->ports, list) {
++              err = mtk_pcie_enable_port(port);
++              if (err) {
++                      mtk_pcie_irq_teardown_port(port);
++                      mtk_pcie_port_free(port);
++              }
++      }
+       /* power down PCIe subsys if slots are all empty (link down) */
+       if (list_empty(&pcie->ports))
+@@ -1198,14 +1211,18 @@ static int mtk_pcie_resume_noirq(struct
+ {
+       struct mtk_pcie *pcie = dev_get_drvdata(dev);
+       struct mtk_pcie_port *port, *tmp;
++      int err;
+       if (list_empty(&pcie->ports))
+               return 0;
+       clk_prepare_enable(pcie->free_ck);
+-      list_for_each_entry_safe(port, tmp, &pcie->ports, list)
+-              mtk_pcie_enable_port(port);
++      list_for_each_entry_safe(port, tmp, &pcie->ports, list) {
++              err = mtk_pcie_enable_port(port);
++              if (err)
++                      mtk_pcie_port_free(port);
++      }
+       /* In case of EP was removed while system suspend. */
+       if (list_empty(&pcie->ports))
diff --git a/queue-6.6/pci-mediatek-use-generic-macro-for-tpvperl-delay.patch b/queue-6.6/pci-mediatek-use-generic-macro-for-tpvperl-delay.patch
new file mode 100644 (file)
index 0000000..7a98629
--- /dev/null
@@ -0,0 +1,42 @@
+From stable+bounces-274576-greg=kroah.com@vger.kernel.org Wed Jul 15 00:04:59 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 18:04:13 -0400
+Subject: PCI: mediatek: Use generic MACRO for TPVPERL delay
+To: stable@vger.kernel.org
+Cc: Christian Marangi <ansuelsmth@gmail.com>, Manivannan Sadhasivam <mani@kernel.org>, AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260714220414.3333873-3-sashal@kernel.org>
+
+From: Christian Marangi <ansuelsmth@gmail.com>
+
+[ Upstream commit 2d58bc777728bfc37aa35dce7b90e72296cceb9f ]
+
+Use the generic PCI MACRO for TPVPERL delay to wait for clock and power
+stabilization after PERST# Signal instead of the raw value of 100 ms.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: Manivannan Sadhasivam <mani@kernel.org>
+Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
+Link: https://patch.msgid.link/20251020111121.31779-5-ansuelsmth@gmail.com
+Stable-dep-of: f865a57896bd ("PCI: mediatek: Fix IRQ domain leak when port fails to enable")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/pci/controller/pcie-mediatek.c |    7 +------
+ 1 file changed, 1 insertion(+), 6 deletions(-)
+
+--- a/drivers/pci/controller/pcie-mediatek.c
++++ b/drivers/pci/controller/pcie-mediatek.c
+@@ -714,12 +714,7 @@ static int mtk_pcie_startup_port_v2(stru
+        */
+       writel(PCIE_LINKDOWN_RST_EN, port->base + PCIE_RST_CTRL);
+-      /*
+-       * Described in PCIe CEM specification sections 2.2 (PERST# Signal) and
+-       * 2.2.1 (Initial Power-Up (G3 to S0)). The deassertion of PERST# should
+-       * be delayed 100ms (TPVPERL) for the power and clock to become stable.
+-       */
+-      msleep(100);
++      msleep(PCIE_T_PVPERL_MS);
+       /* De-assert PHY, PE, PIPE, MAC and configuration reset */
+       val = readl(port->base + PCIE_RST_CTRL);
diff --git a/queue-6.6/pci-move-resizable-bar-code-to-rebar.c.patch b/queue-6.6/pci-move-resizable-bar-code-to-rebar.c.patch
new file mode 100644 (file)
index 0000000..feb6b02
--- /dev/null
@@ -0,0 +1,510 @@
+From stable+bounces-274641-greg=kroah.com@vger.kernel.org Wed Jul 15 02:18:06 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 20:17:55 -0400
+Subject: PCI: Move Resizable BAR code to rebar.c
+To: stable@vger.kernel.org
+Cc: "Ilpo Järvinen" <ilpo.jarvinen@linux.intel.com>, "Bjorn Helgaas" <bhelgaas@google.com>, "Christian König" <christian.koenig@amd.com>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260715001756.3783927-5-sashal@kernel.org>
+
+From: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
+
+[ Upstream commit 9f71938cd77f32a448f40a288e409eca60e55486 ]
+
+For lack of a better place to put it, Resizable BAR code has been placed
+inside pci.c and setup-res.c that do not use it for anything.  Upcoming
+changes are going to add more Resizable BAR related functions, increasing
+the code size.
+
+As pci.c is huge as is, move the Resizable BAR related code and the BAR
+resize code from setup-res.c to rebar.c.
+
+Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
+Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
+Reviewed-by: Christian König <christian.koenig@amd.com>
+Link: https://patch.msgid.link/20251113180053.27944-2-ilpo.jarvinen@linux.intel.com
+Stable-dep-of: ee7471fe968d ("PCI: Skip Resizable BAR restore on read error")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ Documentation/driver-api/pci/pci.rst |    3 
+ drivers/pci/Makefile                 |    2 
+ drivers/pci/pci.c                    |  136 ----------------------
+ drivers/pci/pci.h                    |    1 
+ drivers/pci/rebar.c                  |  212 +++++++++++++++++++++++++++++++++++
+ drivers/pci/setup-res.c              |   64 ----------
+ 6 files changed, 217 insertions(+), 201 deletions(-)
+ create mode 100644 drivers/pci/rebar.c
+
+--- a/Documentation/driver-api/pci/pci.rst
++++ b/Documentation/driver-api/pci/pci.rst
+@@ -31,6 +31,9 @@ PCI Support Library
+ .. kernel-doc:: drivers/pci/slot.c
+    :export:
++.. kernel-doc:: drivers/pci/rebar.c
++   :export:
++
+ .. kernel-doc:: drivers/pci/rom.c
+    :export:
+--- a/drivers/pci/Makefile
++++ b/drivers/pci/Makefile
+@@ -4,7 +4,7 @@
+ obj-$(CONFIG_PCI)             += access.o bus.o probe.o host-bridge.o \
+                                  remove.o pci.o pci-driver.o search.o \
+-                                 pci-sysfs.o rom.o setup-res.o irq.o vpd.o \
++                                 pci-sysfs.o rebar.o rom.o setup-res.o irq.o vpd.o \
+                                  setup-bus.o vc.o mmap.o setup-irq.o
+ obj-$(CONFIG_PCI)             += msi/
+--- a/drivers/pci/pci.c
++++ b/drivers/pci/pci.c
+@@ -1870,33 +1870,6 @@ static void pci_restore_config_space(str
+       }
+ }
+-static void pci_restore_rebar_state(struct pci_dev *pdev)
+-{
+-      unsigned int pos, nbars, i;
+-      u32 ctrl;
+-
+-      pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_REBAR);
+-      if (!pos)
+-              return;
+-
+-      pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
+-      nbars = (ctrl & PCI_REBAR_CTRL_NBAR_MASK) >>
+-                  PCI_REBAR_CTRL_NBAR_SHIFT;
+-
+-      for (i = 0; i < nbars; i++, pos += 8) {
+-              struct resource *res;
+-              int bar_idx, size;
+-
+-              pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
+-              bar_idx = ctrl & PCI_REBAR_CTRL_BAR_IDX;
+-              res = pdev->resource + bar_idx;
+-              size = pci_rebar_bytes_to_size(resource_size(res));
+-              ctrl &= ~PCI_REBAR_CTRL_BAR_SIZE;
+-              ctrl |= size << PCI_REBAR_CTRL_BAR_SHIFT;
+-              pci_write_config_dword(pdev, pos + PCI_REBAR_CTRL, ctrl);
+-      }
+-}
+-
+ /**
+  * pci_restore_state - Restore the saved state of a PCI device
+  * @dev: PCI device that we're dealing with
+@@ -3830,115 +3803,6 @@ void pci_acs_init(struct pci_dev *dev)
+ }
+ /**
+- * pci_rebar_find_pos - find position of resize ctrl reg for BAR
+- * @pdev: PCI device
+- * @bar: BAR to find
+- *
+- * Helper to find the position of the ctrl register for a BAR.
+- * Returns -ENOTSUPP if resizable BARs are not supported at all.
+- * Returns -ENOENT if no ctrl register for the BAR could be found.
+- */
+-static int pci_rebar_find_pos(struct pci_dev *pdev, int bar)
+-{
+-      unsigned int pos, nbars, i;
+-      u32 ctrl;
+-
+-      pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_REBAR);
+-      if (!pos)
+-              return -ENOTSUPP;
+-
+-      pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
+-      nbars = (ctrl & PCI_REBAR_CTRL_NBAR_MASK) >>
+-                  PCI_REBAR_CTRL_NBAR_SHIFT;
+-
+-      for (i = 0; i < nbars; i++, pos += 8) {
+-              int bar_idx;
+-
+-              pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
+-              bar_idx = ctrl & PCI_REBAR_CTRL_BAR_IDX;
+-              if (bar_idx == bar)
+-                      return pos;
+-      }
+-
+-      return -ENOENT;
+-}
+-
+-/**
+- * pci_rebar_get_possible_sizes - get possible sizes for BAR
+- * @pdev: PCI device
+- * @bar: BAR to query
+- *
+- * Get the possible sizes of a resizable BAR as bitmask defined in the spec
+- * (bit 0=1MB, bit 19=512GB). Returns 0 if BAR isn't resizable.
+- */
+-u32 pci_rebar_get_possible_sizes(struct pci_dev *pdev, int bar)
+-{
+-      int pos;
+-      u32 cap;
+-
+-      pos = pci_rebar_find_pos(pdev, bar);
+-      if (pos < 0)
+-              return 0;
+-
+-      pci_read_config_dword(pdev, pos + PCI_REBAR_CAP, &cap);
+-      cap = FIELD_GET(PCI_REBAR_CAP_SIZES, cap);
+-
+-      /* Sapphire RX 5600 XT Pulse has an invalid cap dword for BAR 0 */
+-      if (pdev->vendor == PCI_VENDOR_ID_ATI && pdev->device == 0x731f &&
+-          bar == 0 && cap == 0x700)
+-              return 0x3f00;
+-
+-      return cap;
+-}
+-EXPORT_SYMBOL(pci_rebar_get_possible_sizes);
+-
+-/**
+- * pci_rebar_get_current_size - get the current size of a BAR
+- * @pdev: PCI device
+- * @bar: BAR to set size to
+- *
+- * Read the size of a BAR from the resizable BAR config.
+- * Returns size if found or negative error code.
+- */
+-int pci_rebar_get_current_size(struct pci_dev *pdev, int bar)
+-{
+-      int pos;
+-      u32 ctrl;
+-
+-      pos = pci_rebar_find_pos(pdev, bar);
+-      if (pos < 0)
+-              return pos;
+-
+-      pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
+-      return (ctrl & PCI_REBAR_CTRL_BAR_SIZE) >> PCI_REBAR_CTRL_BAR_SHIFT;
+-}
+-
+-/**
+- * pci_rebar_set_size - set a new size for a BAR
+- * @pdev: PCI device
+- * @bar: BAR to set size to
+- * @size: new size as defined in the spec (0=1MB, 19=512GB)
+- *
+- * Set the new size of a BAR as defined in the spec.
+- * Returns zero if resizing was successful, error code otherwise.
+- */
+-int pci_rebar_set_size(struct pci_dev *pdev, int bar, int size)
+-{
+-      int pos;
+-      u32 ctrl;
+-
+-      pos = pci_rebar_find_pos(pdev, bar);
+-      if (pos < 0)
+-              return pos;
+-
+-      pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
+-      ctrl &= ~PCI_REBAR_CTRL_BAR_SIZE;
+-      ctrl |= size << PCI_REBAR_CTRL_BAR_SHIFT;
+-      pci_write_config_dword(pdev, pos + PCI_REBAR_CTRL, ctrl);
+-      return 0;
+-}
+-
+-/**
+  * pci_enable_atomic_ops_to_root - enable AtomicOp requests to root port
+  * @dev: the PCI device
+  * @cap_mask: mask of desired AtomicOp sizes, including one or more of:
+--- a/drivers/pci/pci.h
++++ b/drivers/pci/pci.h
+@@ -643,6 +643,7 @@ static inline int acpi_get_rc_resources(
+ }
+ #endif
++void pci_restore_rebar_state(struct pci_dev *pdev);
+ int pci_rebar_get_current_size(struct pci_dev *pdev, int bar);
+ int pci_rebar_set_size(struct pci_dev *pdev, int bar, int size);
+ static inline u64 pci_rebar_size_to_bytes(int size)
+--- /dev/null
++++ b/drivers/pci/rebar.c
+@@ -0,0 +1,212 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * PCI Resizable BAR Extended Capability handling.
++ */
++
++#include <linux/bitfield.h>
++#include <linux/errno.h>
++#include <linux/export.h>
++#include <linux/ioport.h>
++#include <linux/pci.h>
++#include <linux/types.h>
++
++#include "pci.h"
++
++/**
++ * pci_rebar_find_pos - find position of resize ctrl reg for BAR
++ * @pdev: PCI device
++ * @bar: BAR to find
++ *
++ * Helper to find the position of the ctrl register for a BAR.
++ * Returns -ENOTSUPP if resizable BARs are not supported at all.
++ * Returns -ENOENT if no ctrl register for the BAR could be found.
++ */
++static int pci_rebar_find_pos(struct pci_dev *pdev, int bar)
++{
++      unsigned int pos, nbars, i;
++      u32 ctrl;
++
++      pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_REBAR);
++      if (!pos)
++              return -ENOTSUPP;
++
++      pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
++      nbars = (ctrl & PCI_REBAR_CTRL_NBAR_MASK) >>
++                  PCI_REBAR_CTRL_NBAR_SHIFT;
++
++      for (i = 0; i < nbars; i++, pos += 8) {
++              int bar_idx;
++
++              pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
++              bar_idx = ctrl & PCI_REBAR_CTRL_BAR_IDX;
++              if (bar_idx == bar)
++                      return pos;
++      }
++
++      return -ENOENT;
++}
++
++/**
++ * pci_rebar_get_possible_sizes - get possible sizes for BAR
++ * @pdev: PCI device
++ * @bar: BAR to query
++ *
++ * Get the possible sizes of a resizable BAR as bitmask defined in the spec
++ * (bit 0=1MB, bit 19=512GB). Returns 0 if BAR isn't resizable.
++ */
++u32 pci_rebar_get_possible_sizes(struct pci_dev *pdev, int bar)
++{
++      int pos;
++      u32 cap;
++
++      pos = pci_rebar_find_pos(pdev, bar);
++      if (pos < 0)
++              return 0;
++
++      pci_read_config_dword(pdev, pos + PCI_REBAR_CAP, &cap);
++      cap = FIELD_GET(PCI_REBAR_CAP_SIZES, cap);
++
++      /* Sapphire RX 5600 XT Pulse has an invalid cap dword for BAR 0 */
++      if (pdev->vendor == PCI_VENDOR_ID_ATI && pdev->device == 0x731f &&
++          bar == 0 && cap == 0x700)
++              return 0x3f00;
++
++      return cap;
++}
++EXPORT_SYMBOL(pci_rebar_get_possible_sizes);
++
++/**
++ * pci_rebar_get_current_size - get the current size of a BAR
++ * @pdev: PCI device
++ * @bar: BAR to set size to
++ *
++ * Read the size of a BAR from the resizable BAR config.
++ * Returns size if found or negative error code.
++ */
++int pci_rebar_get_current_size(struct pci_dev *pdev, int bar)
++{
++      int pos;
++      u32 ctrl;
++
++      pos = pci_rebar_find_pos(pdev, bar);
++      if (pos < 0)
++              return pos;
++
++      pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
++      return (ctrl & PCI_REBAR_CTRL_BAR_SIZE) >> PCI_REBAR_CTRL_BAR_SHIFT;
++}
++
++/**
++ * pci_rebar_set_size - set a new size for a BAR
++ * @pdev: PCI device
++ * @bar: BAR to set size to
++ * @size: new size as defined in the spec (0=1MB, 19=512GB)
++ *
++ * Set the new size of a BAR as defined in the spec.
++ * Returns zero if resizing was successful, error code otherwise.
++ */
++int pci_rebar_set_size(struct pci_dev *pdev, int bar, int size)
++{
++      int pos;
++      u32 ctrl;
++
++      pos = pci_rebar_find_pos(pdev, bar);
++      if (pos < 0)
++              return pos;
++
++      pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
++      ctrl &= ~PCI_REBAR_CTRL_BAR_SIZE;
++      ctrl |= size << PCI_REBAR_CTRL_BAR_SHIFT;
++      pci_write_config_dword(pdev, pos + PCI_REBAR_CTRL, ctrl);
++      return 0;
++}
++
++void pci_restore_rebar_state(struct pci_dev *pdev)
++{
++      unsigned int pos, nbars, i;
++      u32 ctrl;
++
++      pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_REBAR);
++      if (!pos)
++              return;
++
++      pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
++      nbars = FIELD_GET(PCI_REBAR_CTRL_NBAR_MASK, ctrl);
++
++      for (i = 0; i < nbars; i++, pos += 8) {
++              struct resource *res;
++              int bar_idx, size;
++
++              pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
++              bar_idx = ctrl & PCI_REBAR_CTRL_BAR_IDX;
++              res = pci_resource_n(pdev, bar_idx);
++              size = pci_rebar_bytes_to_size(resource_size(res));
++              ctrl &= ~PCI_REBAR_CTRL_BAR_SIZE;
++              ctrl |= size << PCI_REBAR_CTRL_BAR_SHIFT;
++              pci_write_config_dword(pdev, pos + PCI_REBAR_CTRL, ctrl);
++      }
++}
++
++/**
++ * pci_resize_resource - reconfigure a Resizable BAR and resources
++ * @dev: the PCI device
++ * @resno: index of the BAR to be resized
++ * @size: new size as defined in the spec (0=1MB, 31=128TB)
++ * @exclude_bars: a mask of BARs that should not be released
++ *
++ * Reconfigure @resno to @size and re-run resource assignment algorithm
++ * with the new size.
++ *
++ * Prior to resize, release @dev resources that share a bridge window with
++ * @resno.  This unpins the bridge window resource to allow changing it.
++ *
++ * The caller may prevent releasing a particular BAR by providing
++ * @exclude_bars mask, but this may result in the resize operation failing
++ * due to insufficient space.
++ *
++ * Return: 0 on success, or negative on error. In case of an error, the
++ *         resources are restored to their original places.
++ */
++int pci_resize_resource(struct pci_dev *dev, int resno, int size,
++                      int exclude_bars)
++{
++      struct pci_host_bridge *host;
++      int old, ret;
++      u32 sizes;
++      u16 cmd;
++
++      /* Check if we must preserve the firmware's resource assignment */
++      host = pci_find_host_bridge(dev->bus);
++      if (host->preserve_config)
++              return -ENOTSUPP;
++
++      pci_read_config_word(dev, PCI_COMMAND, &cmd);
++      if (cmd & PCI_COMMAND_MEMORY)
++              return -EBUSY;
++
++      sizes = pci_rebar_get_possible_sizes(dev, resno);
++      if (!sizes)
++              return -ENOTSUPP;
++
++      if (!(sizes & BIT(size)))
++              return -EINVAL;
++
++      old = pci_rebar_get_current_size(dev, resno);
++      if (old < 0)
++              return old;
++
++      ret = pci_rebar_set_size(dev, resno, size);
++      if (ret)
++              return ret;
++
++      ret = pci_do_resource_release_and_resize(dev, resno, size, exclude_bars);
++      if (ret)
++              goto error_resize;
++
++      return 0;
++
++error_resize:
++      pci_rebar_set_size(dev, resno, old);
++      return ret;
++}
++EXPORT_SYMBOL(pci_resize_resource);
+--- a/drivers/pci/setup-res.c
++++ b/drivers/pci/setup-res.c
+@@ -427,70 +427,6 @@ void pci_release_resource(struct pci_dev
+ }
+ EXPORT_SYMBOL(pci_release_resource);
+-/**
+- * pci_resize_resource - reconfigure a Resizable BAR and resources
+- * @dev: the PCI device
+- * @resno: index of the BAR to be resized
+- * @size: new size as defined in the spec (0=1MB, 31=128TB)
+- * @exclude_bars: a mask of BARs that should not be released
+- *
+- * Reconfigure @resno to @size and re-run resource assignment algorithm
+- * with the new size.
+- *
+- * Prior to resize, release @dev resources that share a bridge window with
+- * @resno.  This unpins the bridge window resource to allow changing it.
+- *
+- * The caller may prevent releasing a particular BAR by providing
+- * @exclude_bars mask, but this may result in the resize operation failing
+- * due to insufficient space.
+- *
+- * Return: 0 on success, or negative on error. In case of an error, the
+- *         resources are restored to their original places.
+- */
+-int pci_resize_resource(struct pci_dev *dev, int resno, int size,
+-                      int exclude_bars)
+-{
+-      struct pci_host_bridge *host;
+-      int old, ret;
+-      u32 sizes;
+-      u16 cmd;
+-
+-      /* Check if we must preserve the firmware's resource assignment */
+-      host = pci_find_host_bridge(dev->bus);
+-      if (host->preserve_config)
+-              return -ENOTSUPP;
+-
+-      pci_read_config_word(dev, PCI_COMMAND, &cmd);
+-      if (cmd & PCI_COMMAND_MEMORY)
+-              return -EBUSY;
+-
+-      sizes = pci_rebar_get_possible_sizes(dev, resno);
+-      if (!sizes)
+-              return -ENOTSUPP;
+-
+-      if (!(sizes & BIT(size)))
+-              return -EINVAL;
+-
+-      old = pci_rebar_get_current_size(dev, resno);
+-      if (old < 0)
+-              return old;
+-
+-      ret = pci_rebar_set_size(dev, resno, size);
+-      if (ret)
+-              return ret;
+-
+-      ret = pci_do_resource_release_and_resize(dev, resno, size, exclude_bars);
+-      if (ret)
+-              goto error_resize;
+-
+-      return 0;
+-
+-error_resize:
+-      pci_rebar_set_size(dev, resno, old);
+-      return ret;
+-}
+-EXPORT_SYMBOL(pci_resize_resource);
+-
+ int pci_enable_resources(struct pci_dev *dev, int mask)
+ {
+       u16 cmd, old_cmd;
diff --git a/queue-6.6/pci-prevent-resource-tree-corruption-when-bar-resize-fails.patch b/queue-6.6/pci-prevent-resource-tree-corruption-when-bar-resize-fails.patch
new file mode 100644 (file)
index 0000000..39ada15
--- /dev/null
@@ -0,0 +1,71 @@
+From stable+bounces-274637-greg=kroah.com@vger.kernel.org Wed Jul 15 02:18:03 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 20:17:51 -0400
+Subject: PCI: Prevent resource tree corruption when BAR resize fails
+To: stable@vger.kernel.org
+Cc: "Ilpo Järvinen" <ilpo.jarvinen@linux.intel.com>, "Simon Richter" <Simon.Richter@hogyros.de>, "Alex Bennée" <alex.bennee@linaro.org>, "Bjorn Helgaas" <bhelgaas@google.com>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260715001756.3783927-1-sashal@kernel.org>
+
+From: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
+
+[ Upstream commit 91c4c89db41499eea1b29c56655f79c3bae66e93 ]
+
+pbus_reassign_bridge_resources() saves bridge windows into the saved
+list before attempting to adjust resource assignments to perform a BAR
+resize operation. If resource adjustments cannot be completed fully,
+rollback is attempted by restoring the resource from the saved list.
+
+The rollback, however, does not check whether the resources it restores were
+assigned by the partial resize attempt. If restore changes addresses of the
+resource, it can result in corrupting the resource tree.
+
+An example of a corrupted resource tree with overlapping addresses:
+
+  6200000000000-6203fbfffffff : pciex@620c3c0000000
+    6200000000000-6203fbff0ffff : PCI Bus 0030:01
+      6200020000000-62000207fffff : 0030:01:00.0
+      6200000000000-6203fbff0ffff : PCI Bus 0030:02
+
+A resource that are assigned into the resource tree must remain
+unchanged. Thus, release such a resource before attempting to restore
+and claim it back.
+
+For simplicity, always do the release and claim back for the resource
+even in the cases where it is restored to the same address range.
+
+Note: this fix may "break" some cases where devices "worked" because
+the resource tree corruption allowed address space double counting to
+fit more resource than what can now be assigned without double
+counting. The upcoming changes to BAR resizing should address those
+scenarios (to the extent possible).
+
+Fixes: 8bb705e3e79d ("PCI: Add pci_resize_resource() for resizing BARs")
+Reported-by: Simon Richter <Simon.Richter@hogyros.de>
+Link: https://lore.kernel.org/linux-pci/67840a16-99b4-4d8c-9b5c-4721ab0970a2@hogyros.de/
+Reported-by: Alex Bennée <alex.bennee@linaro.org>
+Link: https://lore.kernel.org/linux-pci/874irqop6b.fsf@draig.linaro.org/
+Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
+Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
+Tested-by: Alex Bennée <alex.bennee@linaro.org> # AVA, AMD GPU
+Link: https://patch.msgid.link/20251113162628.5946-2-ilpo.jarvinen@linux.intel.com
+Stable-dep-of: ee7471fe968d ("PCI: Skip Resizable BAR restore on read error")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/pci/setup-bus.c |    5 +++++
+ 1 file changed, 5 insertions(+)
+
+--- a/drivers/pci/setup-bus.c
++++ b/drivers/pci/setup-bus.c
+@@ -2312,6 +2312,11 @@ cleanup:
+               bridge = dev_res->dev;
+               i = res - bridge->resource;
++              if (res->parent) {
++                      release_child_resources(res);
++                      pci_release_resource(bridge, i);
++              }
++
+               res->start = dev_res->start;
+               res->end = dev_res->end;
+               res->flags = dev_res->flags;
diff --git a/queue-6.6/pci-skip-resizable-bar-restore-on-read-error.patch b/queue-6.6/pci-skip-resizable-bar-restore-on-read-error.patch
new file mode 100644 (file)
index 0000000..dfd3c32
--- /dev/null
@@ -0,0 +1,61 @@
+From stable+bounces-274642-greg=kroah.com@vger.kernel.org Wed Jul 15 02:18:45 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 20:17:56 -0400
+Subject: PCI: Skip Resizable BAR restore on read error
+To: stable@vger.kernel.org
+Cc: Marco Nenciarini <mnencia@kcore.it>, Bjorn Helgaas <bhelgaas@google.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715001756.3783927-6-sashal@kernel.org>
+
+From: Marco Nenciarini <mnencia@kcore.it>
+
+[ Upstream commit ee7471fe968d210939be9046089a924cd23c8c3b ]
+
+pci_restore_rebar_state() uses the Resizable BAR Control register to decide
+how many BARs to restore (nbars) and which BAR each iteration addresses
+(bar_idx).
+
+When a device does not respond, config reads typically return
+PCI_ERROR_RESPONSE (~0).  Both fields are 3 bits wide, so nbars and bar_idx
+both evaluate to 7, past the spec's valid ranges for both fields.
+pci_resource_n() then returns an unrelated resource slot, whose size is
+used to derive a nonsensical value written back to the Resizable BAR
+Control register.
+
+Bail out if any Resizable BAR Control read returns PCI_ERROR_RESPONSE. No
+further BARs are touched, which is safe because a config read that returns
+PCI_ERROR_RESPONSE indicates the device is unreachable and restoration is
+pointless.
+
+Fixes: d3252ace0bc6 ("PCI: Restore resized BAR state on resume")
+Signed-off-by: Marco Nenciarini <mnencia@kcore.it>
+Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
+Cc: stable@vger.kernel.org
+Link: https://patch.msgid.link/666cac19b5daa0ab0e0ab64454e76b4d24465dbd.1776429882.git.mnencia@kcore.it
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/pci/rebar.c |    6 ++++++
+ 1 file changed, 6 insertions(+)
+
+--- a/drivers/pci/rebar.c
++++ b/drivers/pci/rebar.c
+@@ -131,6 +131,9 @@ void pci_restore_rebar_state(struct pci_
+               return;
+       pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
++      if (PCI_POSSIBLE_ERROR(ctrl))
++              return;
++
+       nbars = FIELD_GET(PCI_REBAR_CTRL_NBAR_MASK, ctrl);
+       for (i = 0; i < nbars; i++, pos += 8) {
+@@ -138,6 +141,9 @@ void pci_restore_rebar_state(struct pci_
+               int bar_idx, size;
+               pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
++              if (PCI_POSSIBLE_ERROR(ctrl))
++                      return;
++
+               bar_idx = ctrl & PCI_REBAR_CTRL_BAR_IDX;
+               res = pci_resource_n(pdev, bar_idx);
+               size = pci_rebar_bytes_to_size(resource_size(res));
diff --git a/queue-6.6/perf-x86-intel-uncore-defer-adl-global-pmon-enable-to-enable_box.patch b/queue-6.6/perf-x86-intel-uncore-defer-adl-global-pmon-enable-to-enable_box.patch
new file mode 100644 (file)
index 0000000..475ab5d
--- /dev/null
@@ -0,0 +1,57 @@
+From stable+bounces-276817-greg=kroah.com@vger.kernel.org Thu Jul 16 20:53:34 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Jul 2026 14:50:56 -0400
+Subject: perf/x86/intel/uncore: Defer ADL global PMON enable to enable_box()
+To: stable@vger.kernel.org
+Cc: Zide Chen <zide.chen@intel.com>, "Peter Zijlstra (Intel)" <peterz@infradead.org>, Dapeng Mi <dapeng1.mi@linux.intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260716185056.1020113-1-sashal@kernel.org>
+
+From: Zide Chen <zide.chen@intel.com>
+
+[ Upstream commit 9a0bb848a37150aeccc10088e141339917d995dc ]
+
+On some Raptor Cove CPUs, enabling uncore PMON globally at driver init
+may increase power consumption even when no perf events are in use.
+
+Drop adl_uncore_msr_init_box() and defer programming the global control
+register to enable_box(), so it is only set when a box is actually used.
+
+IMC and IMC freerunning counters use a separate control path and are
+unaffected.
+
+Fixes: 772ed05f3c5c ("perf/x86/intel/uncore: Add Alder Lake support")
+Signed-off-by: Zide Chen <zide.chen@intel.com>
+Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
+Reviewed-by: Dapeng Mi <dapeng1.mi@linux.intel.com>
+Cc: stable@vger.kernel.org
+Link: https://patch.msgid.link/20260602144908.263680-5-zide.chen@intel.com
+[ deleted adl_uncore_msr_init_box() in its 6.12 wrmsrl() spelling since the tree predates the wrmsrq() rename ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ arch/x86/events/intel/uncore_snb.c |    7 -------
+ 1 file changed, 7 deletions(-)
+
+--- a/arch/x86/events/intel/uncore_snb.c
++++ b/arch/x86/events/intel/uncore_snb.c
+@@ -537,12 +537,6 @@ void tgl_uncore_cpu_init(void)
+       skl_uncore_msr_ops.init_box = rkl_uncore_msr_init_box;
+ }
+-static void adl_uncore_msr_init_box(struct intel_uncore_box *box)
+-{
+-      if (box->pmu->pmu_idx == 0)
+-              wrmsrl(ADL_UNC_PERF_GLOBAL_CTL, SNB_UNC_GLOBAL_CTL_EN);
+-}
+-
+ static void adl_uncore_msr_enable_box(struct intel_uncore_box *box)
+ {
+       wrmsrl(ADL_UNC_PERF_GLOBAL_CTL, SNB_UNC_GLOBAL_CTL_EN);
+@@ -561,7 +555,6 @@ static void adl_uncore_msr_exit_box(stru
+ }
+ static struct intel_uncore_ops adl_uncore_msr_ops = {
+-      .init_box       = adl_uncore_msr_init_box,
+       .enable_box     = adl_uncore_msr_enable_box,
+       .disable_box    = adl_uncore_msr_disable_box,
+       .exit_box       = adl_uncore_msr_exit_box,
diff --git a/queue-6.6/proc-protect-ptrace_may_access-with-exec_update_lock-fd-links.patch b/queue-6.6/proc-protect-ptrace_may_access-with-exec_update_lock-fd-links.patch
new file mode 100644 (file)
index 0000000..6cd7984
--- /dev/null
@@ -0,0 +1,274 @@
+From stable+bounces-276783-greg=kroah.com@vger.kernel.org Thu Jul 16 19:00:00 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Jul 2026 12:58:56 -0400
+Subject: proc: protect ptrace_may_access() with exec_update_lock (FD links)
+To: stable@vger.kernel.org
+Cc: Jann Horn <jannh@google.com>, "Christian Brauner (Amutable)" <brauner@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260716165856.683482-2-sashal@kernel.org>
+
+From: Jann Horn <jannh@google.com>
+
+[ Upstream commit 6255da28d4bb5349fe18e84cb043ccd394eba75d ]
+
+proc_pid_get_link() and proc_pid_readlink() currently look up the task from
+the pid once, then do the ptrace access check on that task, then look up
+the task from the pid a second time to do the actual access.
+That's racy in several ways.
+
+To fix it, pass the task to the ->proc_get_link() handler, and instead of
+proc_fd_access_allowed(), introduce a new helper call_proc_get_link() that
+looks up and locks the task, does the access check, and calls
+->proc_get_link().
+
+Fixes: 778c1144771f ("[PATCH] proc: Use sane permission checks on the /proc/<pid>/fd/ symlinks")
+Cc: stable@vger.kernel.org
+Signed-off-by: Jann Horn <jannh@google.com>
+Link: https://patch.msgid.link/20260518-procfs-lockfix-part1-v1-2-5c3d20e0ac33@google.com
+Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/proc/base.c     |  119 ++++++++++++++++++++---------------------------------
+ fs/proc/fd.c       |   25 ++++-------
+ fs/proc/internal.h |    2 
+ 3 files changed, 58 insertions(+), 88 deletions(-)
+
+--- a/fs/proc/base.c
++++ b/fs/proc/base.c
+@@ -218,33 +218,24 @@ static int get_task_root(struct task_str
+       return result;
+ }
+-static int proc_cwd_link(struct dentry *dentry, struct path *path)
++static int proc_cwd_link(struct dentry *dentry, struct path *path,
++                       struct task_struct *task)
+ {
+-      struct task_struct *task = get_proc_task(d_inode(dentry));
+       int result = -ENOENT;
+-      if (task) {
+-              task_lock(task);
+-              if (task->fs) {
+-                      get_fs_pwd(task->fs, path);
+-                      result = 0;
+-              }
+-              task_unlock(task);
+-              put_task_struct(task);
++      task_lock(task);
++      if (task->fs) {
++              get_fs_pwd(task->fs, path);
++              result = 0;
+       }
++      task_unlock(task);
+       return result;
+ }
+-static int proc_root_link(struct dentry *dentry, struct path *path)
++static int proc_root_link(struct dentry *dentry, struct path *path,
++                        struct task_struct *task)
+ {
+-      struct task_struct *task = get_proc_task(d_inode(dentry));
+-      int result = -ENOENT;
+-
+-      if (task) {
+-              result = get_task_root(task, path);
+-              put_task_struct(task);
+-      }
+-      return result;
++      return get_task_root(task, path);
+ }
+ /*
+@@ -704,23 +695,6 @@ static int proc_pid_syscall(struct seq_f
+ /*                       Here the fs part begins                        */
+ /************************************************************************/
+-/* permission checks */
+-static bool proc_fd_access_allowed(struct inode *inode)
+-{
+-      struct task_struct *task;
+-      bool allowed = false;
+-      /* Allow access to a task's file descriptors if it is us or we
+-       * may use ptrace attach to the process and find out that
+-       * information.
+-       */
+-      task = get_proc_task(inode);
+-      if (task) {
+-              allowed = ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS);
+-              put_task_struct(task);
+-      }
+-      return allowed;
+-}
+-
+ int proc_nochmod_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
+                struct iattr *attr)
+ {
+@@ -1778,16 +1752,12 @@ static const struct file_operations proc
+       .release        = single_release,
+ };
+-static int proc_exe_link(struct dentry *dentry, struct path *exe_path)
++static int proc_exe_link(struct dentry *dentry, struct path *exe_path,
++                       struct task_struct *task)
+ {
+-      struct task_struct *task;
+       struct file *exe_file;
+-      task = get_proc_task(d_inode(dentry));
+-      if (!task)
+-              return -ENOENT;
+       exe_file = get_task_exe_file(task);
+-      put_task_struct(task);
+       if (exe_file) {
+               *exe_path = exe_file->f_path;
+               path_get(&exe_file->f_path);
+@@ -1797,26 +1767,42 @@ static int proc_exe_link(struct dentry *
+               return -ENOENT;
+ }
++static int call_proc_get_link(struct dentry *dentry, struct inode *inode, struct path *path_out)
++{
++      struct task_struct *task;
++      int ret;
++
++      task = get_proc_task(inode);
++      if (!task)
++              return -ENOENT;
++      ret = down_read_killable(&task->signal->exec_update_lock);
++      if (ret)
++              goto out_put_task;
++      if (!ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS)) {
++              ret = -EACCES;
++              goto out;
++      }
++      ret = PROC_I(inode)->op.proc_get_link(dentry, path_out, task);
++
++out:
++      up_read(&task->signal->exec_update_lock);
++out_put_task:
++      put_task_struct(task);
++      return ret;
++}
++
+ static const char *proc_pid_get_link(struct dentry *dentry,
+                                    struct inode *inode,
+                                    struct delayed_call *done)
+ {
+       struct path path;
+-      int error = -EACCES;
++      int error;
+       if (!dentry)
+               return ERR_PTR(-ECHILD);
+-
+-      /* Are we allowed to snoop on the tasks file descriptors? */
+-      if (!proc_fd_access_allowed(inode))
+-              goto out;
+-
+-      error = PROC_I(inode)->op.proc_get_link(dentry, &path);
+-      if (error)
+-              goto out;
+-
+-      error = nd_jump_link(&path);
+-out:
++      error = call_proc_get_link(dentry, inode, &path);
++      if (!error)
++              error = nd_jump_link(&path);
+       return ERR_PTR(error);
+ }
+@@ -1850,17 +1836,11 @@ static int proc_pid_readlink(struct dent
+       struct inode *inode = d_inode(dentry);
+       struct path path;
+-      /* Are we allowed to snoop on the tasks file descriptors? */
+-      if (!proc_fd_access_allowed(inode))
+-              goto out;
+-
+-      error = PROC_I(inode)->op.proc_get_link(dentry, &path);
+-      if (error)
+-              goto out;
+-
+-      error = do_proc_readlink(&path, buffer, buflen);
+-      path_put(&path);
+-out:
++      error = call_proc_get_link(dentry, inode, &path);
++      if (!error) {
++              error = do_proc_readlink(&path, buffer, buflen);
++              path_put(&path);
++      }
+       return error;
+ }
+@@ -2248,21 +2228,16 @@ static const struct dentry_operations ti
+       .d_delete       = pid_delete_dentry,
+ };
+-static int map_files_get_link(struct dentry *dentry, struct path *path)
++static int map_files_get_link(struct dentry *dentry, struct path *path,
++                            struct task_struct *task)
+ {
+       unsigned long vm_start, vm_end;
+       struct vm_area_struct *vma;
+-      struct task_struct *task;
+       struct mm_struct *mm;
+       int rc;
+       rc = -ENOENT;
+-      task = get_proc_task(d_inode(dentry));
+-      if (!task)
+-              goto out;
+-
+       mm = get_task_mm(task);
+-      put_task_struct(task);
+       if (!mm)
+               goto out;
+--- a/fs/proc/fd.c
++++ b/fs/proc/fd.c
+@@ -172,24 +172,19 @@ static const struct dentry_operations ti
+       .d_delete       = pid_delete_dentry,
+ };
+-static int proc_fd_link(struct dentry *dentry, struct path *path)
++static int proc_fd_link(struct dentry *dentry, struct path *path,
++                      struct task_struct *task)
+ {
+-      struct task_struct *task;
+       int ret = -ENOENT;
++      unsigned int fd = proc_fd(d_inode(dentry));
++      struct file *fd_file;
+-      task = get_proc_task(d_inode(dentry));
+-      if (task) {
+-              unsigned int fd = proc_fd(d_inode(dentry));
+-              struct file *fd_file;
+-
+-              fd_file = fget_task(task, fd);
+-              if (fd_file) {
+-                      *path = fd_file->f_path;
+-                      path_get(&fd_file->f_path);
+-                      ret = 0;
+-                      fput(fd_file);
+-              }
+-              put_task_struct(task);
++      fd_file = fget_task(task, fd);
++      if (fd_file) {
++              *path = fd_file->f_path;
++              path_get(&fd_file->f_path);
++              ret = 0;
++              fput(fd_file);
+       }
+       return ret;
+--- a/fs/proc/internal.h
++++ b/fs/proc/internal.h
+@@ -107,7 +107,7 @@ extern struct kmem_cache *proc_dir_entry
+ void pde_free(struct proc_dir_entry *pde);
+ union proc_op {
+-      int (*proc_get_link)(struct dentry *, struct path *);
++      int (*proc_get_link)(struct dentry *, struct path *, struct task_struct *);
+       int (*proc_show)(struct seq_file *m,
+               struct pid_namespace *ns, struct pid *pid,
+               struct task_struct *task);
diff --git a/queue-6.6/proc-protect-ptrace_may_access-with-exec_update_lock-part-1.patch b/queue-6.6/proc-protect-ptrace_may_access-with-exec_update_lock-part-1.patch
new file mode 100644 (file)
index 0000000..8efd76a
--- /dev/null
@@ -0,0 +1,207 @@
+From stable+bounces-277082-greg=kroah.com@vger.kernel.org Fri Jul 17 16:35:58 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 17 Jul 2026 10:34:50 -0400
+Subject: proc: protect ptrace_may_access() with exec_update_lock (part 1)
+To: stable@vger.kernel.org
+Cc: Jann Horn <jannh@google.com>, "Christian Brauner (Amutable)" <brauner@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260717143450.1877881-3-sashal@kernel.org>
+
+From: Jann Horn <jannh@google.com>
+
+[ Upstream commit 6650527444dadc63d84aa939d14ecba4fadb2f69 ]
+
+Fix the easy cases where procfs currently calls ptrace_may_access() without
+exec_update_lock protection, where the fix is to simply add the extra lock
+or use mm_access():
+
+ - do_task_stat(): grab exec_update_lock
+ - proc_pid_wchan(): grab exec_update_lock
+ - proc_map_files_lookup(): use mm_access() instead of get_task_mm()
+ - proc_map_files_readdir(): use mm_access() instead of get_task_mm()
+ - proc_ns_get_link(): grab exec_update_lock
+ - proc_ns_readlink(): grab exec_update_lock
+
+Fixes: f83ce3e6b02d ("proc: avoid information leaks to non-privileged processes")
+Cc: stable@vger.kernel.org
+Signed-off-by: Jann Horn <jannh@google.com>
+Link: https://patch.msgid.link/20260518-procfs-lockfix-part1-v1-1-5c3d20e0ac33@google.com
+Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/proc/array.c      |    6 ++++++
+ fs/proc/base.c       |   41 ++++++++++++++++++++++-------------------
+ fs/proc/namespaces.c |   12 ++++++++++++
+ 3 files changed, 40 insertions(+), 19 deletions(-)
+
+--- a/fs/proc/array.c
++++ b/fs/proc/array.c
+@@ -483,6 +483,11 @@ static int do_task_stat(struct seq_file
+       unsigned long flags;
+       int exit_code = task->exit_code;
+       struct signal_struct *sig = task->signal;
++      int ret;
++
++      ret = down_read_killable(&task->signal->exec_update_lock);
++      if (ret)
++              return ret;
+       state = *get_task_state(task);
+       vsize = eip = esp = 0;
+@@ -658,6 +663,7 @@ static int do_task_stat(struct seq_file
+               seq_puts(m, " 0");
+       seq_putc(m, '\n');
++      up_read(&task->signal->exec_update_lock);
+       if (mm)
+               mmput(mm);
+       return 0;
+--- a/fs/proc/base.c
++++ b/fs/proc/base.c
+@@ -414,18 +414,24 @@ static int proc_pid_wchan(struct seq_fil
+ {
+       unsigned long wchan;
+       char symname[KSYM_NAME_LEN];
++      int err;
++      err = down_read_killable(&task->signal->exec_update_lock);
++      if (err)
++              return err;
+       if (!ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS))
+               goto print0;
+       wchan = get_wchan(task);
+       if (wchan && !lookup_symbol_name(wchan, symname)) {
+               seq_puts(m, symname);
++              up_read(&task->signal->exec_update_lock);
+               return 0;
+       }
+ print0:
+       seq_putc(m, '0');
++      up_read(&task->signal->exec_update_lock);
+       return 0;
+ }
+ #endif /* CONFIG_KALLSYMS */
+@@ -2333,17 +2339,15 @@ static struct dentry *proc_map_files_loo
+       if (!task)
+               goto out;
+-      result = ERR_PTR(-EACCES);
+-      if (!ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS))
+-              goto out_put_task;
+-
+       result = ERR_PTR(-ENOENT);
+       if (dname_to_vma_addr(dentry, &vm_start, &vm_end))
+               goto out_put_task;
+-      mm = get_task_mm(task);
+-      if (!mm)
++      mm = mm_access(task, PTRACE_MODE_READ_FSCREDS);
++      if (IS_ERR(mm)) {
++              result = ERR_CAST(mm);
+               goto out_put_task;
++      }
+       result = ERR_PTR(-EINTR);
+       if (mmap_read_lock_killable(mm))
+@@ -2393,23 +2397,22 @@ proc_map_files_readdir(struct file *file
+       if (!task)
+               goto out;
+-      ret = -EACCES;
+-      if (!ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS))
+-              goto out_put_task;
+-
+       ret = 0;
+       if (!dir_emit_dots(file, ctx))
+               goto out_put_task;
+-      mm = get_task_mm(task);
+-      if (!mm)
++      mm = mm_access(task, PTRACE_MODE_READ_FSCREDS);
++      if (IS_ERR(mm)) {
++              ret = PTR_ERR(mm);
++              /* if the task has no mm, the directory should just be empty */
++              if (ret == -ESRCH)
++                      ret = 0;
+               goto out_put_task;
++      }
+       ret = mmap_read_lock_killable(mm);
+-      if (ret) {
+-              mmput(mm);
+-              goto out_put_task;
+-      }
++      if (ret)
++              goto out_put_mm;
+       nr_files = 0;
+@@ -2435,8 +2438,7 @@ proc_map_files_readdir(struct file *file
+               if (!p) {
+                       ret = -ENOMEM;
+                       mmap_read_unlock(mm);
+-                      mmput(mm);
+-                      goto out_put_task;
++                      goto out_put_mm;
+               }
+               p->start = vma->vm_start;
+@@ -2444,7 +2446,6 @@ proc_map_files_readdir(struct file *file
+               p->mode = vma->vm_file->f_mode;
+       }
+       mmap_read_unlock(mm);
+-      mmput(mm);
+       for (i = 0; i < nr_files; i++) {
+               char buf[4 * sizeof(long) + 2]; /* max: %lx-%lx\0 */
+@@ -2461,6 +2462,8 @@ proc_map_files_readdir(struct file *file
+               ctx->pos++;
+       }
++out_put_mm:
++      mmput(mm);
+ out_put_task:
+       put_task_struct(task);
+ out:
+--- a/fs/proc/namespaces.c
++++ b/fs/proc/namespaces.c
+@@ -55,6 +55,10 @@ static const char *proc_ns_get_link(stru
+       if (!task)
+               return ERR_PTR(-EACCES);
++      error = down_read_killable(&task->signal->exec_update_lock);
++      if (error)
++              goto out_put_task;
++
+       if (!ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS))
+               goto out;
+@@ -64,6 +68,8 @@ static const char *proc_ns_get_link(stru
+       error = nd_jump_link(&ns_path);
+ out:
++      up_read(&task->signal->exec_update_lock);
++out_put_task:
+       put_task_struct(task);
+       return ERR_PTR(error);
+ }
+@@ -80,11 +86,17 @@ static int proc_ns_readlink(struct dentr
+       if (!task)
+               return res;
++      res = down_read_killable(&task->signal->exec_update_lock);
++      if (res)
++              goto out_put_task;
++
+       if (ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS)) {
+               res = ns_get_name(name, sizeof(name), task, ns_ops);
+               if (res >= 0)
+                       res = readlink_copy(buffer, buflen, name);
+       }
++      up_read(&task->signal->exec_update_lock);
++out_put_task:
+       put_task_struct(task);
+       return res;
+ }
diff --git a/queue-6.6/proc-rename-proc_setattr-to-proc_nochmod_setattr.patch b/queue-6.6/proc-rename-proc_setattr-to-proc_nochmod_setattr.patch
new file mode 100644 (file)
index 0000000..d7ecd5e
--- /dev/null
@@ -0,0 +1,204 @@
+From stable+bounces-276782-greg=kroah.com@vger.kernel.org Thu Jul 16 19:00:07 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Jul 2026 12:58:55 -0400
+Subject: proc: rename proc_setattr to proc_nochmod_setattr
+To: stable@vger.kernel.org
+Cc: Christoph Hellwig <hch@lst.de>, Jan Kara <jack@suse.cz>, Christian Brauner <brauner@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260716165856.683482-1-sashal@kernel.org>
+
+From: Christoph Hellwig <hch@lst.de>
+
+[ Upstream commit 690005b0b1e6b567c88b7790e6d90d4d6c9e09cc ]
+
+What is currently proc_setattr is a special version added after the more
+general procfs ->seattr in commit 6d76fa58b050 ("Don't allow chmod() on
+the /proc/<pid>/ files").  Give it a name that reflects that to free the
+proc_setattr name and better describe what is doing.
+
+Signed-off-by: Christoph Hellwig <hch@lst.de>
+Link: https://patch.msgid.link/20260325063711.3298685-5-hch@lst.de
+Reviewed-by: Jan Kara <jack@suse.cz>
+Signed-off-by: Christian Brauner <brauner@kernel.org>
+Stable-dep-of: 6255da28d4bb ("proc: protect ptrace_may_access() with exec_update_lock (FD links)")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/proc/base.c       |   22 +++++++++++-----------
+ fs/proc/fd.c         |    6 +++---
+ fs/proc/internal.h   |    4 ++--
+ fs/proc/namespaces.c |    4 ++--
+ fs/proc/proc_net.c   |    2 +-
+ 5 files changed, 19 insertions(+), 19 deletions(-)
+
+--- a/fs/proc/base.c
++++ b/fs/proc/base.c
+@@ -721,7 +721,7 @@ static bool proc_fd_access_allowed(struc
+       return allowed;
+ }
+-int proc_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
++int proc_nochmod_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
+                struct iattr *attr)
+ {
+       int error;
+@@ -794,7 +794,7 @@ static int proc_pid_permission(struct mn
+ static const struct inode_operations proc_def_inode_operations = {
+-      .setattr        = proc_setattr,
++      .setattr        = proc_nochmod_setattr,
+ };
+ static int proc_single_show(struct seq_file *m, void *v)
+@@ -1867,7 +1867,7 @@ out:
+ const struct inode_operations proc_pid_link_inode_operations = {
+       .readlink       = proc_pid_readlink,
+       .get_link       = proc_pid_get_link,
+-      .setattr        = proc_setattr,
++      .setattr        = proc_nochmod_setattr,
+ };
+@@ -2317,7 +2317,7 @@ proc_map_files_get_link(struct dentry *d
+ static const struct inode_operations proc_map_files_link_inode_operations = {
+       .readlink       = proc_pid_readlink,
+       .get_link       = proc_map_files_get_link,
+-      .setattr        = proc_setattr,
++      .setattr        = proc_nochmod_setattr,
+ };
+ static struct dentry *
+@@ -2396,7 +2396,7 @@ out:
+ static const struct inode_operations proc_map_files_inode_operations = {
+       .lookup         = proc_map_files_lookup,
+       .permission     = proc_fd_permission,
+-      .setattr        = proc_setattr,
++      .setattr        = proc_nochmod_setattr,
+ };
+ static int
+@@ -2892,7 +2892,7 @@ static struct dentry *proc_##LSM##_attr_
+ static const struct inode_operations proc_##LSM##_attr_dir_inode_ops = { \
+       .lookup         = proc_##LSM##_attr_dir_lookup, \
+       .getattr        = pid_getattr, \
+-      .setattr        = proc_setattr, \
++      .setattr        = proc_nochmod_setattr, \
+ }
+ #ifdef CONFIG_SECURITY_SMACK
+@@ -2951,7 +2951,7 @@ static struct dentry *proc_attr_dir_look
+ static const struct inode_operations proc_attr_dir_inode_operations = {
+       .lookup         = proc_attr_dir_lookup,
+       .getattr        = pid_getattr,
+-      .setattr        = proc_setattr,
++      .setattr        = proc_nochmod_setattr,
+ };
+ #endif
+@@ -3444,7 +3444,7 @@ static struct dentry *proc_tgid_base_loo
+ static const struct inode_operations proc_tgid_base_inode_operations = {
+       .lookup         = proc_tgid_base_lookup,
+       .getattr        = pid_getattr,
+-      .setattr        = proc_setattr,
++      .setattr        = proc_nochmod_setattr,
+       .permission     = proc_pid_permission,
+ };
+@@ -3644,7 +3644,7 @@ static int proc_tid_comm_permission(stru
+ }
+ static const struct inode_operations proc_tid_comm_inode_operations = {
+-              .setattr        = proc_setattr,
++              .setattr        = proc_nochmod_setattr,
+               .permission     = proc_tid_comm_permission,
+ };
+@@ -3775,7 +3775,7 @@ static const struct file_operations proc
+ static const struct inode_operations proc_tid_base_inode_operations = {
+       .lookup         = proc_tid_base_lookup,
+       .getattr        = pid_getattr,
+-      .setattr        = proc_setattr,
++      .setattr        = proc_nochmod_setattr,
+ };
+ static struct dentry *proc_task_instantiate(struct dentry *dentry,
+@@ -3973,7 +3973,7 @@ static int proc_task_getattr(struct mnt_
+ static const struct inode_operations proc_task_inode_operations = {
+       .lookup         = proc_task_lookup,
+       .getattr        = proc_task_getattr,
+-      .setattr        = proc_setattr,
++      .setattr        = proc_nochmod_setattr,
+       .permission     = proc_pid_permission,
+ };
+--- a/fs/proc/fd.c
++++ b/fs/proc/fd.c
+@@ -104,7 +104,7 @@ static int proc_fdinfo_permission(struct
+ static const struct inode_operations proc_fdinfo_file_inode_operations = {
+       .permission     = proc_fdinfo_permission,
+-      .setattr        = proc_setattr,
++      .setattr        = proc_nochmod_setattr,
+ };
+ static const struct file_operations proc_fdinfo_file_operations = {
+@@ -374,7 +374,7 @@ const struct inode_operations proc_fd_in
+       .lookup         = proc_lookupfd,
+       .permission     = proc_fd_permission,
+       .getattr        = proc_fd_getattr,
+-      .setattr        = proc_setattr,
++      .setattr        = proc_nochmod_setattr,
+ };
+ static struct dentry *proc_fdinfo_instantiate(struct dentry *dentry,
+@@ -415,7 +415,7 @@ static int proc_readfdinfo(struct file *
+ const struct inode_operations proc_fdinfo_inode_operations = {
+       .lookup         = proc_lookupfdinfo,
+       .permission     = proc_fdinfo_permission,
+-      .setattr        = proc_setattr,
++      .setattr        = proc_nochmod_setattr,
+ };
+ const struct file_operations proc_fdinfo_operations = {
+--- a/fs/proc/internal.h
++++ b/fs/proc/internal.h
+@@ -183,8 +183,8 @@ extern int proc_pid_statm(struct seq_fil
+ extern const struct dentry_operations pid_dentry_operations;
+ extern int pid_getattr(struct mnt_idmap *, const struct path *,
+                      struct kstat *, u32, unsigned int);
+-extern int proc_setattr(struct mnt_idmap *, struct dentry *,
+-                      struct iattr *);
++int proc_nochmod_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
++                      struct iattr *attr);
+ extern void proc_pid_evict_inode(struct proc_inode *);
+ extern struct inode *proc_pid_make_inode(struct super_block *, struct task_struct *, umode_t);
+ extern void pid_update_inode(struct task_struct *, struct inode *);
+--- a/fs/proc/namespaces.c
++++ b/fs/proc/namespaces.c
+@@ -92,7 +92,7 @@ static int proc_ns_readlink(struct dentr
+ static const struct inode_operations proc_ns_link_inode_operations = {
+       .readlink       = proc_ns_readlink,
+       .get_link       = proc_ns_get_link,
+-      .setattr        = proc_setattr,
++      .setattr        = proc_nochmod_setattr,
+ };
+ static struct dentry *proc_ns_instantiate(struct dentry *dentry,
+@@ -179,5 +179,5 @@ out_no_task:
+ const struct inode_operations proc_ns_dir_inode_operations = {
+       .lookup         = proc_ns_dir_lookup,
+       .getattr        = pid_getattr,
+-      .setattr        = proc_setattr,
++      .setattr        = proc_nochmod_setattr,
+ };
+--- a/fs/proc/proc_net.c
++++ b/fs/proc/proc_net.c
+@@ -321,7 +321,7 @@ static int proc_tgid_net_getattr(struct
+ const struct inode_operations proc_net_inode_operations = {
+       .lookup         = proc_tgid_net_lookup,
+       .getattr        = proc_tgid_net_getattr,
+-      .setattr        = proc_setattr,
++      .setattr        = proc_nochmod_setattr,
+ };
+ static int proc_tgid_net_readdir(struct file *file, struct dir_context *ctx)
diff --git a/queue-6.6/regulator-scmi-fix-of_node-refcount-leak-in-scmi_regulator_probe.patch b/queue-6.6/regulator-scmi-fix-of_node-refcount-leak-in-scmi_regulator_probe.patch
new file mode 100644 (file)
index 0000000..d3b1069
--- /dev/null
@@ -0,0 +1,45 @@
+From stable+bounces-278286-greg=kroah.com@vger.kernel.org Tue Jul 21 02:46:38 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 20:45:19 -0400
+Subject: regulator: scmi: fix of_node refcount leak in scmi_regulator_probe()
+To: stable@vger.kernel.org
+Cc: Wentao Liang <vulab@iscas.ac.cn>, Mark Brown <broonie@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721004519.3422055-2-sashal@kernel.org>
+
+From: Wentao Liang <vulab@iscas.ac.cn>
+
+[ Upstream commit fa11039d6cdff84584a3ef8cc1f5e1b56e045da2 ]
+
+scmi_regulator_probe() calls of_find_node_by_name() which takes a
+reference on the returned device node. On the error path where
+process_scmi_regulator_of_node() fails, the function returns without
+calling of_node_put() on the child node, leaking the reference.
+
+Add of_node_put(np) on the error path to properly release the
+reference.
+
+Cc: stable@vger.kernel.org
+Fixes: 0fbeae70ee7c ("regulator: add SCMI driver")
+Signed-off-by: Wentao Liang <vulab@iscas.ac.cn>
+Link: https://patch.msgid.link/20260527104850.872415-1-vulab@iscas.ac.cn
+Signed-off-by: Mark Brown <broonie@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/regulator/scmi-regulator.c |    4 +++-
+ 1 file changed, 3 insertions(+), 1 deletion(-)
+
+--- a/drivers/regulator/scmi-regulator.c
++++ b/drivers/regulator/scmi-regulator.c
+@@ -345,8 +345,10 @@ static int scmi_regulator_probe(struct s
+       for_each_child_of_node_scoped(np, child) {
+               ret = process_scmi_regulator_of_node(sdev, ph, child, rinfo);
+               /* abort on any mem issue */
+-              if (ret == -ENOMEM)
++              if (ret == -ENOMEM) {
++                      of_node_put(np);
+                       return ret;
++              }
+       }
+       of_node_put(np);
+       /*
diff --git a/queue-6.6/regulator-scmi-simplify-with-scoped-for-each-of-child-loop.patch b/queue-6.6/regulator-scmi-simplify-with-scoped-for-each-of-child-loop.patch
new file mode 100644 (file)
index 0000000..2d4b59a
--- /dev/null
@@ -0,0 +1,52 @@
+From stable+bounces-278285-greg=kroah.com@vger.kernel.org Tue Jul 21 02:46:33 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 20:45:18 -0400
+Subject: regulator: scmi: Simplify with scoped for each OF child loop
+To: stable@vger.kernel.org
+Cc: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>, Mark Brown <broonie@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721004519.3422055-1-sashal@kernel.org>
+
+From: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+
+[ Upstream commit 99cf5db9cdd39136fd5dbd10bda833aa0f870452 ]
+
+Use scoped for_each_available_child_of_node_scoped() when iterating over
+device nodes to make code a bit simpler.
+
+Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Link: https://patch.msgid.link/20240814-cleanup-h-of-node-put-regulator-v1-7-87151088b883@linaro.org
+Signed-off-by: Mark Brown <broonie@kernel.org>
+Stable-dep-of: fa11039d6cdf ("regulator: scmi: fix of_node refcount leak in scmi_regulator_probe()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/regulator/scmi-regulator.c |    8 +++-----
+ 1 file changed, 3 insertions(+), 5 deletions(-)
+
+--- a/drivers/regulator/scmi-regulator.c
++++ b/drivers/regulator/scmi-regulator.c
+@@ -298,7 +298,7 @@ static int process_scmi_regulator_of_nod
+ static int scmi_regulator_probe(struct scmi_device *sdev)
+ {
+       int d, ret, num_doms;
+-      struct device_node *np, *child;
++      struct device_node *np;
+       const struct scmi_handle *handle = sdev->handle;
+       struct scmi_regulator_info *rinfo;
+       struct scmi_protocol_handle *ph;
+@@ -342,13 +342,11 @@ static int scmi_regulator_probe(struct s
+        */
+       of_node_get(handle->dev->of_node);
+       np = of_find_node_by_name(handle->dev->of_node, "regulators");
+-      for_each_child_of_node(np, child) {
++      for_each_child_of_node_scoped(np, child) {
+               ret = process_scmi_regulator_of_node(sdev, ph, child, rinfo);
+               /* abort on any mem issue */
+-              if (ret == -ENOMEM) {
+-                      of_node_put(child);
++              if (ret == -ENOMEM)
+                       return ret;
+-              }
+       }
+       of_node_put(np);
+       /*
diff --git a/queue-6.6/seqlock-change-do_task_stat-to-use-scoped_seqlock_read.patch b/queue-6.6/seqlock-change-do_task_stat-to-use-scoped_seqlock_read.patch
new file mode 100644 (file)
index 0000000..7ad7954
--- /dev/null
@@ -0,0 +1,56 @@
+From stable+bounces-277078-greg=kroah.com@vger.kernel.org Fri Jul 17 16:35:14 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 17 Jul 2026 10:34:49 -0400
+Subject: seqlock: Change do_task_stat() to use scoped_seqlock_read()
+To: stable@vger.kernel.org
+Cc: Oleg Nesterov <oleg@redhat.com>, "Peter Zijlstra (Intel)" <peterz@infradead.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260717143450.1877881-2-sashal@kernel.org>
+
+From: Oleg Nesterov <oleg@redhat.com>
+
+[ Upstream commit b76f72bea2c601afec81829ea427fc0d20f83216 ]
+
+To simplify the code and make it more readable.
+
+[peterz: change to new interface]
+Signed-off-by: Oleg Nesterov <oleg@redhat.com>
+Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
+Stable-dep-of: 6650527444da ("proc: protect ptrace_may_access() with exec_update_lock (part 1)")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/proc/array.c |    9 ++-------
+ 1 file changed, 2 insertions(+), 7 deletions(-)
+
+--- a/fs/proc/array.c
++++ b/fs/proc/array.c
+@@ -483,7 +483,6 @@ static int do_task_stat(struct seq_file
+       unsigned long flags;
+       int exit_code = task->exit_code;
+       struct signal_struct *sig = task->signal;
+-      unsigned int seq = 1;
+       state = *get_task_state(task);
+       vsize = eip = esp = 0;
+@@ -540,10 +539,7 @@ static int do_task_stat(struct seq_file
+       if (permitted && (!whole || num_threads < 2))
+               wchan = !task_is_running(task);
+-      do {
+-              seq++; /* 2 on the 1st/lockless path, otherwise odd */
+-              flags = read_seqbegin_or_lock_irqsave(&sig->stats_lock, &seq);
+-
++      scoped_seqlock_read (&sig->stats_lock, ss_lock_irqsave) {
+               cmin_flt = sig->cmin_flt;
+               cmaj_flt = sig->cmaj_flt;
+               cutime = sig->cutime;
+@@ -565,8 +561,7 @@ static int do_task_stat(struct seq_file
+                       }
+                       rcu_read_unlock();
+               }
+-      } while (need_seqretry(&sig->stats_lock, seq));
+-      done_seqretry_irqrestore(&sig->stats_lock, seq, flags);
++      }
+       if (whole) {
+               thread_group_cputime_adjusted(task, &utime, &stime);
diff --git a/queue-6.6/seqlock-introduce-scoped_seqlock_read.patch b/queue-6.6/seqlock-introduce-scoped_seqlock_read.patch
new file mode 100644 (file)
index 0000000..22d7f94
--- /dev/null
@@ -0,0 +1,164 @@
+From stable+bounces-277077-greg=kroah.com@vger.kernel.org Fri Jul 17 16:35:05 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 17 Jul 2026 10:34:48 -0400
+Subject: seqlock: Introduce scoped_seqlock_read()
+To: stable@vger.kernel.org
+Cc: Peter Zijlstra <peterz@infradead.org>, Oleg Nesterov <oleg@redhat.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260717143450.1877881-1-sashal@kernel.org>
+
+From: Peter Zijlstra <peterz@infradead.org>
+
+[ Upstream commit cc39f3872c0865bef992b713338df369554fa9e0 ]
+
+The read_seqbegin/need_seqretry/done_seqretry API is cumbersome and
+error prone. With the new helper the "typical" code like
+
+       int seq, nextseq;
+       unsigned long flags;
+
+       nextseq = 0;
+       do {
+               seq = nextseq;
+               flags = read_seqbegin_or_lock_irqsave(&seqlock, &seq);
+
+               // read-side critical section
+
+               nextseq = 1;
+       } while (need_seqretry(&seqlock, seq));
+       done_seqretry_irqrestore(&seqlock, seq, flags);
+
+can be rewritten as
+
+       scoped_seqlock_read (&seqlock, ss_lock_irqsave) {
+               // read-side critical section
+       }
+
+Original idea by Oleg Nesterov; with contributions from Linus.
+
+Originally-by: Oleg Nesterov <oleg@redhat.com>
+Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
+Stable-dep-of: 6650527444da ("proc: protect ptrace_may_access() with exec_update_lock (part 1)")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/seqlock.h |  111 ++++++++++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 111 insertions(+)
+
+--- a/include/linux/seqlock.h
++++ b/include/linux/seqlock.h
+@@ -1266,4 +1266,115 @@ done_seqretry_irqrestore(seqlock_t *lock
+       if (seq & 1)
+               read_sequnlock_excl_irqrestore(lock, flags);
+ }
++
++enum ss_state {
++      ss_done = 0,
++      ss_lock,
++      ss_lock_irqsave,
++      ss_lockless,
++};
++
++struct ss_tmp {
++      enum ss_state   state;
++      unsigned long   data;
++      spinlock_t      *lock;
++      spinlock_t      *lock_irqsave;
++};
++
++static inline void __scoped_seqlock_cleanup(struct ss_tmp *sst)
++{
++      if (sst->lock)
++              spin_unlock(sst->lock);
++      if (sst->lock_irqsave)
++              spin_unlock_irqrestore(sst->lock_irqsave, sst->data);
++}
++
++extern void __scoped_seqlock_invalid_target(void);
++
++#if defined(CONFIG_CC_IS_GCC) && CONFIG_GCC_VERSION < 90000
++/*
++ * For some reason some GCC-8 architectures (nios2, alpha) have trouble
++ * determining that the ss_done state is impossible in __scoped_seqlock_next()
++ * below.
++ */
++static inline void __scoped_seqlock_bug(void) { }
++#else
++/*
++ * Canary for compiler optimization -- if the compiler doesn't realize this is
++ * an impossible state, it very likely generates sub-optimal code here.
++ */
++extern void __scoped_seqlock_bug(void);
++#endif
++
++static inline void
++__scoped_seqlock_next(struct ss_tmp *sst, seqlock_t *lock, enum ss_state target)
++{
++      switch (sst->state) {
++      case ss_done:
++              __scoped_seqlock_bug();
++              return;
++
++      case ss_lock:
++      case ss_lock_irqsave:
++              sst->state = ss_done;
++              return;
++
++      case ss_lockless:
++              if (!read_seqretry(lock, sst->data)) {
++                      sst->state = ss_done;
++                      return;
++              }
++              break;
++      }
++
++      switch (target) {
++      case ss_done:
++              __scoped_seqlock_invalid_target();
++              return;
++
++      case ss_lock:
++              sst->lock = &lock->lock;
++              spin_lock(sst->lock);
++              sst->state = ss_lock;
++              return;
++
++      case ss_lock_irqsave:
++              sst->lock_irqsave = &lock->lock;
++              spin_lock_irqsave(sst->lock_irqsave, sst->data);
++              sst->state = ss_lock_irqsave;
++              return;
++
++      case ss_lockless:
++              sst->data = read_seqbegin(lock);
++              return;
++      }
++}
++
++#define __scoped_seqlock_read(_seqlock, _target, _s)                  \
++      for (struct ss_tmp _s __cleanup(__scoped_seqlock_cleanup) =     \
++           { .state = ss_lockless, .data = read_seqbegin(_seqlock) }; \
++           _s.state != ss_done;                                       \
++           __scoped_seqlock_next(&_s, _seqlock, _target))
++
++/**
++ * scoped_seqlock_read (lock, ss_state) - execute the read side critical
++ *                                        section without manual sequence
++ *                                        counter handling or calls to other
++ *                                        helpers
++ * @lock: pointer to seqlock_t protecting the data
++ * @ss_state: one of {ss_lock, ss_lock_irqsave, ss_lockless} indicating
++ *            the type of critical read section
++ *
++ * Example:
++ *
++ *     scoped_seqlock_read (&lock, ss_lock) {
++ *         // read-side critical section
++ *     }
++ *
++ * Starts with a lockess pass first. If it fails, restarts the critical
++ * section with the lock held.
++ */
++#define scoped_seqlock_read(_seqlock, _target)                                \
++      __scoped_seqlock_read(_seqlock, _target, __UNIQUE_ID(seqlock))
++
+ #endif /* __LINUX_SEQLOCK_H */
diff --git a/queue-6.6/serial-8250_mid-disable-dma-for-selected-platforms.patch b/queue-6.6/serial-8250_mid-disable-dma-for-selected-platforms.patch
new file mode 100644 (file)
index 0000000..ef78b0c
--- /dev/null
@@ -0,0 +1,64 @@
+From sashal@kernel.org Fri Jul 17 21:55:24 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 17 Jul 2026 15:55:20 -0400
+Subject: serial: 8250_mid: Disable DMA for selected platforms
+To: stable@vger.kernel.org
+Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com>, micas-opensource <zjianan156@gmail.com>, stable <stable@kernel.org>, Greg Kroah-Hartman <gregkh@linuxfoundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260717195520.2195138-2-sashal@kernel.org>
+
+From: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
+
+[ Upstream commit b1b4efea05a56c0995e4702a86d6624b4fdff32f ]
+
+In accordance with Errata (specification updates)
+HSUART May Stop Functioning when DMA is Active.
+
+- Denverton document #572409, rev 3.4, DNV60
+- Ice Lake Xeon D document #714070, ICXD65
+- Snowridge document #731931, SNR44
+
+For a quick fix just disable the respective callbacks during the device probe.
+Depending on the future development we might remove them completely.
+
+Reported-by: micas-opensource <zjianan156@gmail.com>
+Closes: https://lore.kernel.org/linux-serial/20250625031409.2404219-1-opensource@ruijie.com.cn/
+Fixes: 6ede6dcd87aa ("serial: 8250_mid: add support for DMA engine handling from UART MMIO")
+Cc: stable <stable@kernel.org>
+Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
+Link: https://patch.msgid.link/20260626094937.561776-1-andriy.shevchenko@linux.intel.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/tty/serial/8250/8250_mid.c |   13 +++++++++++--
+ 1 file changed, 11 insertions(+), 2 deletions(-)
+
+--- a/drivers/tty/serial/8250/8250_mid.c
++++ b/drivers/tty/serial/8250/8250_mid.c
+@@ -10,6 +10,7 @@
+ #include <linux/module.h>
+ #include <linux/pci.h>
+ #include <linux/rational.h>
++#include <linux/util_macros.h>
+ #include <linux/dma/hsu.h>
+@@ -368,8 +369,16 @@ static const struct mid8250_board dnv_bo
+       .freq = 133333333,
+       .base_baud = 115200,
+       .bar = 1,
+-      .setup = dnv_setup,
+-      .exit = dnv_exit,
++      /*
++       * Errata:
++       * HSUART May Stop Functioning when DMA is Active.
++       *
++       * - Denverton document #572409, rev 3.4, DNV60
++       * - Ice Lake Xeon D document #714070, ICXD65
++       * - Snowridge document #731931, SNR44
++       */
++      .setup = PTR_IF(false, dnv_setup),
++      .exit = PTR_IF(false, dnv_exit),
+ };
+ static const struct pci_device_id pci_ids[] = {
diff --git a/queue-6.6/serial-8250_mid-remove-8250_pci-usage.patch b/queue-6.6/serial-8250_mid-remove-8250_pci-usage.patch
new file mode 100644 (file)
index 0000000..8ee7804
--- /dev/null
@@ -0,0 +1,120 @@
+From sashal@kernel.org Fri Jul 17 21:55:23 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 17 Jul 2026 15:55:19 -0400
+Subject: serial: 8250_mid: Remove 8250_pci usage
+To: stable@vger.kernel.org
+Cc: "Ilpo Järvinen" <ilpo.jarvinen@linux.intel.com>, "Greg Kroah-Hartman" <gregkh@linuxfoundation.org>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260717195520.2195138-1-sashal@kernel.org>
+
+From: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
+
+[ Upstream commit a136abd7e7abe0f1247f8ffde6cc7c8ab09f985b ]
+
+8250_mid uses FL_*BASE* from linux/8250_pci.h and nothing else. The
+code can be simplified by directly defining BARs within the driver
+instead.
+
+Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
+Link: https://lore.kernel.org/r/20230915094336.13278-1-ilpo.jarvinen@linux.intel.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Stable-dep-of: b1b4efea05a5 ("serial: 8250_mid: Disable DMA for selected platforms")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/tty/serial/8250/8250_mid.c |   18 +++++++-----------
+ 1 file changed, 7 insertions(+), 11 deletions(-)
+
+--- a/drivers/tty/serial/8250/8250_mid.c
++++ b/drivers/tty/serial/8250/8250_mid.c
+@@ -12,7 +12,6 @@
+ #include <linux/rational.h>
+ #include <linux/dma/hsu.h>
+-#include <linux/8250_pci.h>
+ #include "8250.h"
+@@ -32,9 +31,9 @@
+ struct mid8250;
+ struct mid8250_board {
+-      unsigned int flags;
+       unsigned long freq;
+       unsigned int base_baud;
++      unsigned int bar;
+       int (*setup)(struct mid8250 *, struct uart_port *p);
+       void (*exit)(struct mid8250 *);
+ };
+@@ -169,7 +168,6 @@ static int dnv_setup(struct mid8250 *mid
+ {
+       struct hsu_dma_chip *chip = &mid->dma_chip;
+       struct pci_dev *pdev = to_pci_dev(p->dev);
+-      unsigned int bar = FL_GET_BASE(mid->board->flags);
+       int ret;
+       pci_set_master(pdev);
+@@ -183,7 +181,7 @@ static int dnv_setup(struct mid8250 *mid
+       chip->dev = &pdev->dev;
+       chip->irq = pci_irq_vector(pdev, 0);
+       chip->regs = p->membase;
+-      chip->length = pci_resource_len(pdev, bar);
++      chip->length = pci_resource_len(pdev, mid->board->bar);
+       chip->offset = DNV_DMA_CHAN_OFFSET;
+       /* Falling back to PIO mode if DMA probing fails */
+@@ -291,7 +289,6 @@ static int mid8250_probe(struct pci_dev
+ {
+       struct uart_8250_port uart;
+       struct mid8250 *mid;
+-      unsigned int bar;
+       int ret;
+       ret = pcim_enable_device(pdev);
+@@ -303,7 +300,6 @@ static int mid8250_probe(struct pci_dev
+               return -ENOMEM;
+       mid->board = (struct mid8250_board *)id->driver_data;
+-      bar = FL_GET_BASE(mid->board->flags);
+       memset(&uart, 0, sizeof(struct uart_8250_port));
+@@ -316,8 +312,8 @@ static int mid8250_probe(struct pci_dev
+       uart.port.flags = UPF_SHARE_IRQ | UPF_FIXED_PORT | UPF_FIXED_TYPE;
+       uart.port.set_termios = mid8250_set_termios;
+-      uart.port.mapbase = pci_resource_start(pdev, bar);
+-      uart.port.membase = pcim_iomap(pdev, bar, 0);
++      uart.port.mapbase = pci_resource_start(pdev, mid->board->bar);
++      uart.port.membase = pcim_iomap(pdev, mid->board->bar, 0);
+       if (!uart.port.membase)
+               return -ENOMEM;
+@@ -353,25 +349,25 @@ static void mid8250_remove(struct pci_de
+ }
+ static const struct mid8250_board pnw_board = {
+-      .flags = FL_BASE0,
+       .freq = 50000000,
+       .base_baud = 115200,
++      .bar = 0,
+       .setup = pnw_setup,
+       .exit = pnw_exit,
+ };
+ static const struct mid8250_board tng_board = {
+-      .flags = FL_BASE0,
+       .freq = 38400000,
+       .base_baud = 1843200,
++      .bar = 0,
+       .setup = tng_setup,
+       .exit = tng_exit,
+ };
+ static const struct mid8250_board dnv_board = {
+-      .flags = FL_BASE1,
+       .freq = 133333333,
+       .base_baud = 115200,
++      .bar = 1,
+       .setup = dnv_setup,
+       .exit = dnv_exit,
+ };
index 33ae8675945e5397998a3a67182e7c15edaecfaa..57608333ee67681aba4be153ffb868d267db1a13 100644 (file)
@@ -1147,3 +1147,100 @@ mtd-rawnand-fsl_ifc-return-errors-for-failed-page-reads.patch
 mtd-rawnand-lpc32xx_mlc-fail-dma-transfers-on-timeout.patch
 mtd-rawnand-lpc32xx_slc-fail-dma-transfer-on-completion-timeout.patch
 perf-x86-amd-brs-fix-kernel-address-leakage.patch
+acpi-nfit-core-fix-acpi_nfit_init-error-cleanup.patch
+acpi-driver-check-acpi_companion-against-null-during-probe.patch
+acpi-bus-introduce-devm_acpi_install_notify_handler.patch
+acpi-nfit-core-use-devm_acpi_install_notify_handler.patch
+acpi-nfit-core-fix-possible-deadlock-and-missing-notifications.patch
+iio-imu-adis-add-irqf_no_thread-to-non-fifo-trigger-irq.patch
+iio-hid-sensor-rotation-fix-stale-or-zero-output-when-reading-raw-values.patch
+iio-invensense-remove-redundant-initialization-of-variable-period.patch
+iio-invensense-fix-timestamp-glitches-when-switching-frequency.patch
+iio-imu-inv_icm42600-stabilized-timestamp-in-interrupt.patch
+iio-imu-inv_icm42600-fix-timestamping-by-limiting-fifo-reading.patch
+iio-pressure-mpl115-fix-runtime-pm-leak-on-read-error.patch
+bitops-make-bytes_to_bits-treewide-available.patch
+iio-common-st_sensors-honour-channel-endianness-in-read_axis_data.patch
+alsa-aoa-check-snd_ctl_new1-return-value.patch
+pci-altera-fix-resource-leaks-on-probe-failure.patch
+vfio-mlx5-fix-racy-bitfields-and-tighten-struct-layout.patch
+pci-imx6-fix-imx6sx_gpr12_pcie_test_powerdown-handling.patch
+pci-controller-use-dev_fwnode-instead-of-of_fwnode_handle.patch
+pci-mediatek-convert-bool-to-single-quirks-entry-and-bitmap.patch
+pci-mediatek-use-generic-macro-for-tpvperl-delay.patch
+pci-mediatek-fix-irq-domain-leak-when-port-fails-to-enable.patch
+pci-prevent-resource-tree-corruption-when-bar-resize-fails.patch
+pci-free-saved-list-without-holding-pci_bus_sem.patch
+pci-fix-restoring-bars-on-bar-resize-rollback-path.patch
+pci-add-kerneldoc-for-pci_resize_resource.patch
+pci-move-resizable-bar-code-to-rebar.c.patch
+pci-skip-resizable-bar-restore-on-read-error.patch
+staging-rtl8723bs-core-move-constants-to-right-side-in-comparison.patch
+staging-rtl8723bs-fix-spaces-around-binary-operators.patch
+staging-rtl8723bs-fix-oob-reads-in-rtw_get_sec_ie-rtw_get_wapi_ie-and-rtw_get_wps_attr.patch
+crypto-qat-fix-vf2pf-work-teardown-race-in-adf_disable_sriov.patch
+bluetooth-l2cap-fix-uaf-in-channel-timeout-by-holding-conn-ref.patch
+mm-shrinker-remove-redundant-shrinker_rwsem-in-debugfs-operations.patch
+mm-shrinker-do-not-hold-rcu-lock-in-shrinker_debugfs_count_show.patch
+coresight-etb10-restore-atomic_t-for-shared-reading-state.patch
+gpio-sch-use-raw_spinlock_t-in-the-irq-startup-path.patch
+media-nxp-imx8-isi-convert-to-platform-remove-callback-returning-void.patch
+media-nxp-imx8-isi-use-devm_pm_runtime_enable-to-simplify-code.patch
+media-nxp-imx8-isi-fix-use-after-free-on-remove.patch
+netfilter-ebtables-use-vmalloc_array-to-improve-code.patch
+netfilter-ebtables-zero-chainstack-array.patch
+bluetooth-l2cap-cancel-pending_rx_work-before-taking-conn-lock.patch
+bluetooth-6lowpan-fix-cyclic-locking-warning-on-netdev-unregister.patch
+bluetooth-l2cap-fix-use-after-free-in-l2cap_sock_new_connection_cb.patch
+smb-client-improve-unlocking-of-a-mutex-in-cifs_get_swn_reg.patch
+smb-client-resolve-swn-tcon-from-live-registrations.patch
+ksmbd-use-opener-credentials-for-fsctl-mutations.patch
+ksmbd-centralize-ksmbd_conn-final-release-to-plug-transport-leak.patch
+ksmbd-track-the-connection-owning-a-byte-range-lock.patch
+proc-rename-proc_setattr-to-proc_nochmod_setattr.patch
+proc-protect-ptrace_may_access-with-exec_update_lock-fd-links.patch
+cpufreq-pcc-remove-empty-exit-callback.patch
+cpufreq-make-cpufreq_driver-exit-return-void.patch
+cpufreq-qcom-cpufreq-hw-fix-possible-double-free.patch
+writeback-avoid-contention-on-wb-list_lock-when-switching-inodes.patch
+writeback-fix-race-between-cgroup_writeback_umount-and-inode_switch_wbs.patch
+perf-x86-intel-uncore-defer-adl-global-pmon-enable-to-enable_box.patch
+hid-add-haptics-page-defines.patch
+hid-multitouch-fix-out-of-bounds-bit-access-on-mt_io_flags.patch
+mm-slab-do-not-limit-zeroing-to-orig_size-when-only-red-zoning-is-enabled.patch
+seqlock-introduce-scoped_seqlock_read.patch
+seqlock-change-do_task_stat-to-use-scoped_seqlock_read.patch
+proc-protect-ptrace_may_access-with-exec_update_lock-part-1.patch
+treewide-switch-rename-to-timer_delete.patch
+hid-appleir-fix-uaf-on-pending-key_up_timer-in-remove.patch
+serial-8250_mid-remove-8250_pci-usage.patch
+serial-8250_mid-disable-dma-for-selected-platforms.patch
+hfs-hfsplus-prevent-getting-negative-values-of-offset-length.patch
+hfs-hfsplus-fix-u32-overflow-in-check_and_correct_requested_length.patch
+bpf-consistently-use-bpf_rcu_lock_held-everywhere.patch
+bpf-allow-lpm-map-access-from-sleepable-bpf-programs.patch
+usb-iowarrior-remove-inherent-race-with-minor-number.patch
+usb-atm-ueagle-atm-wait-for-pre-firmware-load-in-.disconnect.patch
+usb-typec-tcpm-fix-vdm-type-for-enter-mode-commands.patch
+crypto-atmel-drop-explicit-initialization-of-struct-i2c_device_id-driver_data-to-0.patch
+crypto-atmel-sha204a-drop-hwrng-quality-reduction-for-atsha204a.patch
+usb-gadget-f_fs-initialize-reset_work-at-allocation-time.patch
+nvmet-remove-superfluous-initialization.patch
+nvmet-return-dhchap-status-codes-from-nvmet_setup_auth.patch
+nvmet-auth-validate-reply-message-payload-bounds-against-transfer-length.patch
+usb-gadget-f_fs-tie-read_buffer-lifetime-to-ffs_epfile.patch
+btrfs-check-and-set-extent_delalloc_new-before-clearing-extent_delalloc.patch
+crypto-qat-fix-restarting-state-leak-on-allocation-failure.patch
+audit-add-audit_log_nf_skb-helper-function.patch
+audit-fix-potential-integer-overflow-in-audit_log_n_hex.patch
+regulator-scmi-simplify-with-scoped-for-each-of-child-loop.patch
+regulator-scmi-fix-of_node-refcount-leak-in-scmi_regulator_probe.patch
+mm-do-file-ownership-checks-with-the-proper-mount-idmap.patch
+exfat-move-free-cluster-out-of-exfat_init_ext_entry.patch
+exfat-remove-unnecessary-read-entry-in-__exfat_rename.patch
+exfat-rename-argument-name-for-exfat_move_file-and-exfat_rename_file.patch
+exfat-add-exfat_get_dentry_set_by_ei-helper.patch
+exfat-move-exfat_chain_set-out-of-__exfat_resolve_path.patch
+exfat-preserve-benign-secondary-entries-during-rename-and-move.patch
+btrfs-fix-false-io-failure-after-falling-back-to-buffered-write.patch
+btrfs-fix-incorrect-buffered-io-fallback-for-append-direct-writes.patch
diff --git a/queue-6.6/smb-client-improve-unlocking-of-a-mutex-in-cifs_get_swn_reg.patch b/queue-6.6/smb-client-improve-unlocking-of-a-mutex-in-cifs_get_swn_reg.patch
new file mode 100644 (file)
index 0000000..e2d235b
--- /dev/null
@@ -0,0 +1,65 @@
+From stable+bounces-275113-greg=kroah.com@vger.kernel.org Thu Jul 16 03:16:05 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 21:15:57 -0400
+Subject: smb: client: Improve unlocking of a mutex in cifs_get_swn_reg()
+To: stable@vger.kernel.org
+Cc: Markus Elfring <elfring@users.sourceforge.net>, Steve French <stfrench@microsoft.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260716011558.683323-1-sashal@kernel.org>
+
+From: Markus Elfring <elfring@users.sourceforge.net>
+
+[ Upstream commit e2080b70c5851a132547bec3bd7dde847e649678 ]
+
+Use two additional labels so that another bit of common code can be better
+reused at the end of this function implementation.
+
+Signed-off-by: Markus Elfring <elfring@users.sourceforge.net>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Stable-dep-of: ec457f9afe5a ("smb: client: resolve SWN tcon from live registrations")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/client/cifs_swn.c |   13 ++++++-------
+ 1 file changed, 6 insertions(+), 7 deletions(-)
+
+--- a/fs/smb/client/cifs_swn.c
++++ b/fs/smb/client/cifs_swn.c
+@@ -313,17 +313,15 @@ static struct cifs_swn_reg *cifs_get_swn
+       reg = cifs_find_swn_reg(tcon);
+       if (!IS_ERR(reg)) {
+               kref_get(&reg->ref_count);
+-              mutex_unlock(&cifs_swnreg_idr_mutex);
+-              return reg;
++              goto unlock;
+       } else if (PTR_ERR(reg) != -EEXIST) {
+-              mutex_unlock(&cifs_swnreg_idr_mutex);
+-              return reg;
++              goto unlock;
+       }
+       reg = kmalloc(sizeof(struct cifs_swn_reg), GFP_ATOMIC);
+       if (reg == NULL) {
+-              mutex_unlock(&cifs_swnreg_idr_mutex);
+-              return ERR_PTR(-ENOMEM);
++              ret = -ENOMEM;
++              goto fail_unlock;
+       }
+       kref_init(&reg->ref_count);
+@@ -354,7 +352,7 @@ static struct cifs_swn_reg *cifs_get_swn
+       reg->ip_notify = (tcon->capabilities & SMB2_SHARE_CAP_SCALEOUT);
+       reg->tcon = tcon;
+-
++unlock:
+       mutex_unlock(&cifs_swnreg_idr_mutex);
+       return reg;
+@@ -365,6 +363,7 @@ fail_idr:
+       idr_remove(&cifs_swnreg_idr, reg->id);
+ fail:
+       kfree(reg);
++fail_unlock:
+       mutex_unlock(&cifs_swnreg_idr_mutex);
+       return ERR_PTR(ret);
+ }
diff --git a/queue-6.6/smb-client-resolve-swn-tcon-from-live-registrations.patch b/queue-6.6/smb-client-resolve-swn-tcon-from-live-registrations.patch
new file mode 100644 (file)
index 0000000..3c1c3a9
--- /dev/null
@@ -0,0 +1,604 @@
+From stable+bounces-275114-greg=kroah.com@vger.kernel.org Thu Jul 16 03:16:11 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 21:15:58 -0400
+Subject: smb: client: resolve SWN tcon from live registrations
+To: stable@vger.kernel.org
+Cc: Michael Bommarito <michael.bommarito@gmail.com>, Steve French <stfrench@microsoft.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260716011558.683323-2-sashal@kernel.org>
+
+From: Michael Bommarito <michael.bommarito@gmail.com>
+
+[ Upstream commit ec457f9afe5ae9538bdcd58fd4cb442b9787e183 ]
+
+cifs_swn_notify() looks up a witness registration by id under
+cifs_swnreg_idr_mutex, drops the mutex, and then uses the registration's
+cached tcon pointer.  That pointer is not a lifetime reference, and it is
+not a stable representative once cifs_get_swn_reg() lets multiple tcons
+for the same net/share name share one registration id.
+
+A same-share second mount can keep the cifs_swn_reg alive after the first
+tcon unregisters and is freed.  The registration then still points at the
+freed first tcon, so taking tc_lock or incrementing tc_count through
+swnreg->tcon only moves the use-after-free earlier.  Taking tc_lock while
+holding cifs_swnreg_idr_mutex also violates the documented CIFS lock
+order.
+
+Fix this by making the registration store only the stable witness
+identity: id, net name, share name, and notify flags.  When a notify
+arrives, copy that identity under cifs_swnreg_idr_mutex, drop the mutex,
+then find and pin a live witness tcon that currently matches the net/share
+pair under the normal cifs_tcp_ses_lock -> tc_lock order.  The notification
+path uses that pinned tcon directly and drops the reference when done.
+
+Registration and unregister messages now use the live tcon passed by the
+caller instead of a cached tcon in the registration.  The final unregister
+send is folded into cifs_swn_unregister() while the registration is still
+protected by cifs_swnreg_idr_mutex.  This removes the previous
+find/drop/reacquire raw-pointer window.  The release path only removes the
+idr entry and frees the stable identity strings.
+
+This preserves the intended one-registration/many-tcon behavior: a
+registration id represents a net/share pair, and notify handling acts on a
+live representative selected at use time.  It also preserves CLIENT_MOVE
+ordering for the representative tcon because the old-IP unregister is sent
+before cifs_swn_register() sends the new-IP register.
+
+Fixes: fed979a7e082 ("cifs: Set witness notification handler for messages from userspace daemon")
+Cc: stable@vger.kernel.org
+Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
+Assisted-by: Claude:claude-opus-4-7
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/client/cifs_swn.c |  314 ++++++++++++++++++++++++++++++++++++++---------
+ fs/smb/client/trace.h    |    2 
+ 2 files changed, 262 insertions(+), 54 deletions(-)
+
+--- a/fs/smb/client/cifs_swn.c
++++ b/fs/smb/client/cifs_swn.c
+@@ -28,10 +28,54 @@ struct cifs_swn_reg {
+       bool net_name_notify;
+       bool share_name_notify;
+       bool ip_notify;
++};
+-      struct cifs_tcon *tcon;
++struct cifs_swn_reg_info {
++      int id;
++      unsigned int ref_count;
++      const char *net_name;
++      const char *share_name;
++      bool net_name_notify;
++      bool share_name_notify;
++      bool ip_notify;
+ };
++static void cifs_swn_snapshot_reg(struct cifs_swn_reg *swnreg,
++                                struct cifs_swn_reg_info *info)
++{
++      info->id = swnreg->id;
++      info->ref_count = kref_read(&swnreg->ref_count);
++      info->net_name = swnreg->net_name;
++      info->share_name = swnreg->share_name;
++      info->net_name_notify = swnreg->net_name_notify;
++      info->share_name_notify = swnreg->share_name_notify;
++      info->ip_notify = swnreg->ip_notify;
++}
++
++static int cifs_swn_dup_reg(struct cifs_swn_reg *swnreg,
++                          struct cifs_swn_reg_info *info)
++{
++      cifs_swn_snapshot_reg(swnreg, info);
++
++      info->net_name = kstrdup(swnreg->net_name, GFP_KERNEL);
++      if (!info->net_name)
++              return -ENOMEM;
++
++      info->share_name = kstrdup(swnreg->share_name, GFP_KERNEL);
++      if (!info->share_name) {
++              kfree(info->net_name);
++              return -ENOMEM;
++      }
++
++      return 0;
++}
++
++static void cifs_swn_free_reg_info(struct cifs_swn_reg_info *info)
++{
++      kfree(info->net_name);
++      kfree(info->share_name);
++}
++
+ static int cifs_swn_auth_info_krb(struct cifs_tcon *tcon, struct sk_buff *skb)
+ {
+       int ret;
+@@ -73,7 +117,8 @@ static int cifs_swn_auth_info_ntlm(struc
+  * The authentication information to connect to the witness service is bundled
+  * into the message.
+  */
+-static int cifs_swn_send_register_message(struct cifs_swn_reg *swnreg)
++static int cifs_swn_send_register_message(struct cifs_swn_reg_info *swnreg,
++                                        struct cifs_tcon *tcon)
+ {
+       struct sk_buff *skb;
+       struct genlmsghdr *hdr;
+@@ -111,10 +156,10 @@ static int cifs_swn_send_register_messag
+        * told to switch to it (client move message). In these cases we unregister from the
+        * server address and register to the new address when we receive the notification.
+        */
+-      if (swnreg->tcon->ses->server->use_swn_dstaddr)
+-              addr = &swnreg->tcon->ses->server->swn_dstaddr;
++      if (tcon->ses->server->use_swn_dstaddr)
++              addr = &tcon->ses->server->swn_dstaddr;
+       else
+-              addr = &swnreg->tcon->ses->server->dstaddr;
++              addr = &tcon->ses->server->dstaddr;
+       ret = nla_put(skb, CIFS_GENL_ATTR_SWN_IP, sizeof(struct sockaddr_storage), addr);
+       if (ret < 0)
+@@ -138,10 +183,10 @@ static int cifs_swn_send_register_messag
+                       goto nlmsg_fail;
+       }
+-      authtype = cifs_select_sectype(swnreg->tcon->ses->server, swnreg->tcon->ses->sectype);
++      authtype = cifs_select_sectype(tcon->ses->server, tcon->ses->sectype);
+       switch (authtype) {
+       case Kerberos:
+-              ret = cifs_swn_auth_info_krb(swnreg->tcon, skb);
++              ret = cifs_swn_auth_info_krb(tcon, skb);
+               if (ret < 0) {
+                       cifs_dbg(VFS, "%s: Failed to get kerberos auth info: %d\n", __func__, ret);
+                       goto nlmsg_fail;
+@@ -149,7 +194,7 @@ static int cifs_swn_send_register_messag
+               break;
+       case NTLMv2:
+       case RawNTLMSSP:
+-              ret = cifs_swn_auth_info_ntlm(swnreg->tcon, skb);
++              ret = cifs_swn_auth_info_ntlm(tcon, skb);
+               if (ret < 0) {
+                       cifs_dbg(VFS, "%s: Failed to get NTLM auth info: %d\n", __func__, ret);
+                       goto nlmsg_fail;
+@@ -179,7 +224,8 @@ fail:
+ /*
+  * Sends an uregister message to the userspace daemon based on the registration
+  */
+-static int cifs_swn_send_unregister_message(struct cifs_swn_reg *swnreg)
++static int cifs_swn_send_unregister_message(struct cifs_swn_reg_info *swnreg,
++                                          struct cifs_tcon *tcon)
+ {
+       struct sk_buff *skb;
+       struct genlmsghdr *hdr;
+@@ -208,7 +254,7 @@ static int cifs_swn_send_unregister_mess
+               goto nlmsg_fail;
+       ret = nla_put(skb, CIFS_GENL_ATTR_SWN_IP, sizeof(struct sockaddr_storage),
+-                      &swnreg->tcon->ses->server->dstaddr);
++                      &tcon->ses->server->dstaddr);
+       if (ret < 0)
+               goto nlmsg_fail;
+@@ -245,6 +291,88 @@ nlmsg_fail:
+ }
+ /*
++ * Allocation-free mirror of extract_hostname() + extract_sharename() from
++ * fs/smb/client/unc.c.  Those helpers kmalloc(GFP_KERNEL); this runs under
++ * cifs_tcp_ses_lock and tcon->tc_lock, both spinlocks, so we mirror their
++ * parsing in place against the caller's stable net_name/share_name strings.
++ * Keep in sync with unc.c.
++ */
++static bool cifs_swn_tcon_matches(struct cifs_tcon *tcon,
++                                const char *net_name,
++                                const char *share_name)
++{
++      const char *unc = tcon->tree_name;
++      const char *host, *share, *delim;
++      size_t host_len, share_len;
++
++      if (!tcon->use_witness)
++              return false;
++
++      /* extract_hostname: require strlen(unc) >= 3 */
++      if (strnlen(unc, 3) < 3)
++              return false;
++      /* extract_hostname: skip all leading '\' characters */
++      for (host = unc; *host == '\\'; host++)
++              ;
++      if (!*host)
++              return false;
++      delim = strchr(host, '\\');
++      if (!delim)
++              return false;
++      host_len = delim - host;
++      if (strlen(net_name) != host_len ||
++          strncasecmp(host, net_name, host_len))
++              return false;
++
++      /* extract_sharename: start at unc + 2, then first '\' onward */
++      share = unc + 2;
++      delim = strchr(share, '\\');
++      if (!delim)
++              return false;
++      share = delim + 1;
++      share_len = strlen(share);
++
++      return strlen(share_name) == share_len &&
++             !strncasecmp(share, share_name, share_len);
++}
++
++/*
++ * One SWN registration id represents one net/share name pair.  Multiple
++ * mounted tcons can therefore share the id.  Pick a live representative at
++ * use time instead of caching the first tcon pointer in the registration.
++ */
++static struct cifs_tcon *cifs_swn_get_tcon(struct cifs_swn_reg_info *swnreg)
++{
++      struct TCP_Server_Info *server;
++      struct cifs_ses *ses;
++      struct cifs_tcon *tcon;
++
++      spin_lock(&cifs_tcp_ses_lock);
++      list_for_each_entry(server, &cifs_tcp_ses_list, tcp_ses_list) {
++              list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) {
++                      list_for_each_entry(tcon, &ses->tcon_list, tcon_list) {
++                              spin_lock(&tcon->tc_lock);
++                              if (tcon->status == TID_EXITING ||
++                                  !cifs_swn_tcon_matches(tcon, swnreg->net_name,
++                                                         swnreg->share_name)) {
++                                      spin_unlock(&tcon->tc_lock);
++                                      continue;
++                              }
++                              ++tcon->tc_count;
++                              trace_smb3_tcon_ref(tcon->debug_id,
++                                                  tcon->tc_count,
++                                                  netfs_trace_tcon_ref_get_swn_notify);
++                              spin_unlock(&tcon->tc_lock);
++                              spin_unlock(&cifs_tcp_ses_lock);
++                              return tcon;
++                      }
++              }
++      }
++      spin_unlock(&cifs_tcp_ses_lock);
++      return NULL;
++}
++
++/*
+  * Try to find a matching registration for the tcon's server name and share name.
+  * Calls to this function must be protected by cifs_swnreg_idr_mutex.
+  * TODO Try to avoid memory allocations
+@@ -350,8 +478,6 @@ static struct cifs_swn_reg *cifs_get_swn
+       reg->net_name_notify = true;
+       reg->share_name_notify = true;
+       reg->ip_notify = (tcon->capabilities & SMB2_SHARE_CAP_SCALEOUT);
+-
+-      reg->tcon = tcon;
+ unlock:
+       mutex_unlock(&cifs_swnreg_idr_mutex);
+@@ -371,11 +497,6 @@ fail_unlock:
+ static void cifs_swn_reg_release(struct kref *ref)
+ {
+       struct cifs_swn_reg *swnreg = container_of(ref, struct cifs_swn_reg, ref_count);
+-      int ret;
+-
+-      ret = cifs_swn_send_unregister_message(swnreg);
+-      if (ret < 0)
+-              cifs_dbg(VFS, "%s: Failed to send unregister message: %d\n", __func__, ret);
+       idr_remove(&cifs_swnreg_idr, swnreg->id);
+       kfree(swnreg->net_name);
+@@ -383,23 +504,33 @@ static void cifs_swn_reg_release(struct
+       kfree(swnreg);
+ }
+-static void cifs_put_swn_reg(struct cifs_swn_reg *swnreg)
++static void cifs_put_swn_reg_locked(struct cifs_swn_reg *swnreg,
++                                  struct cifs_tcon *tcon)
+ {
+-      mutex_lock(&cifs_swnreg_idr_mutex);
++      if (kref_read(&swnreg->ref_count) == 1) {
++              struct cifs_swn_reg_info swnreg_info;
++              int ret;
++
++              cifs_swn_snapshot_reg(swnreg, &swnreg_info);
++              ret = cifs_swn_send_unregister_message(&swnreg_info, tcon);
++              if (ret < 0)
++                      cifs_dbg(VFS, "%s: Failed to send unregister message: %d\n",
++                               __func__, ret);
++      }
++
+       kref_put(&swnreg->ref_count, cifs_swn_reg_release);
+-      mutex_unlock(&cifs_swnreg_idr_mutex);
+ }
+-static int cifs_swn_resource_state_changed(struct cifs_swn_reg *swnreg, const char *name, int state)
++static int cifs_swn_resource_state_changed(struct cifs_tcon *tcon, const char *name, int state)
+ {
+       switch (state) {
+       case CIFS_SWN_RESOURCE_STATE_UNAVAILABLE:
+               cifs_dbg(FYI, "%s: resource name '%s' become unavailable\n", __func__, name);
+-              cifs_signal_cifsd_for_reconnect(swnreg->tcon->ses->server, true);
++              cifs_signal_cifsd_for_reconnect(tcon->ses->server, true);
+               break;
+       case CIFS_SWN_RESOURCE_STATE_AVAILABLE:
+               cifs_dbg(FYI, "%s: resource name '%s' become available\n", __func__, name);
+-              cifs_signal_cifsd_for_reconnect(swnreg->tcon->ses->server, true);
++              cifs_signal_cifsd_for_reconnect(tcon->ses->server, true);
+               break;
+       case CIFS_SWN_RESOURCE_STATE_UNKNOWN:
+               cifs_dbg(FYI, "%s: resource name '%s' changed to unknown state\n", __func__, name);
+@@ -505,7 +636,7 @@ unlock:
+       return ret;
+ }
+-static int cifs_swn_client_move(struct cifs_swn_reg *swnreg, struct sockaddr_storage *addr)
++static int cifs_swn_client_move(struct cifs_tcon *tcon, struct sockaddr_storage *addr)
+ {
+       struct sockaddr_in *ipv4 = (struct sockaddr_in *)addr;
+       struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)addr;
+@@ -515,14 +646,17 @@ static int cifs_swn_client_move(struct c
+       else if (addr->ss_family == AF_INET6)
+               cifs_dbg(FYI, "%s: move to %pI6\n", __func__, &ipv6->sin6_addr);
+-      return cifs_swn_reconnect(swnreg->tcon, addr);
++      return cifs_swn_reconnect(tcon, addr);
+ }
+ int cifs_swn_notify(struct sk_buff *skb, struct genl_info *info)
+ {
+       struct cifs_swn_reg *swnreg;
++      struct cifs_swn_reg_info swnreg_info;
++      struct cifs_tcon *tcon;
+       char name[256];
+       int type;
++      int ret = 0;
+       if (info->attrs[CIFS_GENL_ATTR_SWN_REGISTRATION_ID]) {
+               int swnreg_id;
+@@ -530,21 +664,34 @@ int cifs_swn_notify(struct sk_buff *skb,
+               swnreg_id = nla_get_u32(info->attrs[CIFS_GENL_ATTR_SWN_REGISTRATION_ID]);
+               mutex_lock(&cifs_swnreg_idr_mutex);
+               swnreg = idr_find(&cifs_swnreg_idr, swnreg_id);
+-              mutex_unlock(&cifs_swnreg_idr_mutex);
+               if (swnreg == NULL) {
++                      mutex_unlock(&cifs_swnreg_idr_mutex);
+                       cifs_dbg(FYI, "%s: registration id %d not found\n", __func__, swnreg_id);
+                       return -EINVAL;
+               }
++              ret = cifs_swn_dup_reg(swnreg, &swnreg_info);
++              mutex_unlock(&cifs_swnreg_idr_mutex);
++              if (ret)
++                      return ret;
+       } else {
+               cifs_dbg(FYI, "%s: missing registration id attribute\n", __func__);
+               return -EINVAL;
+       }
++      tcon = cifs_swn_get_tcon(&swnreg_info);
++      if (!tcon) {
++              cifs_dbg(FYI, "%s: registration id %d has no live tcon\n",
++                       __func__, swnreg_info.id);
++              ret = -ENODEV;
++              goto free_info;
++      }
++
+       if (info->attrs[CIFS_GENL_ATTR_SWN_NOTIFICATION_TYPE]) {
+               type = nla_get_u32(info->attrs[CIFS_GENL_ATTR_SWN_NOTIFICATION_TYPE]);
+       } else {
+               cifs_dbg(FYI, "%s: missing notification type attribute\n", __func__);
+-              return -EINVAL;
++              ret = -EINVAL;
++              goto out;
+       }
+       switch (type) {
+@@ -556,15 +703,18 @@ int cifs_swn_notify(struct sk_buff *skb,
+                                       sizeof(name));
+               } else {
+                       cifs_dbg(FYI, "%s: missing resource name attribute\n", __func__);
+-                      return -EINVAL;
++                      ret = -EINVAL;
++                      goto out;
+               }
+               if (info->attrs[CIFS_GENL_ATTR_SWN_RESOURCE_STATE]) {
+                       state = nla_get_u32(info->attrs[CIFS_GENL_ATTR_SWN_RESOURCE_STATE]);
+               } else {
+                       cifs_dbg(FYI, "%s: missing resource state attribute\n", __func__);
+-                      return -EINVAL;
++                      ret = -EINVAL;
++                      goto out;
+               }
+-              return cifs_swn_resource_state_changed(swnreg, name, state);
++              ret = cifs_swn_resource_state_changed(tcon, name, state);
++              break;
+       }
+       case CIFS_SWN_NOTIFICATION_CLIENT_MOVE: {
+               struct sockaddr_storage addr;
+@@ -573,28 +723,36 @@ int cifs_swn_notify(struct sk_buff *skb,
+                       nla_memcpy(&addr, info->attrs[CIFS_GENL_ATTR_SWN_IP], sizeof(addr));
+               } else {
+                       cifs_dbg(FYI, "%s: missing IP address attribute\n", __func__);
+-                      return -EINVAL;
++                      ret = -EINVAL;
++                      goto out;
+               }
+-              return cifs_swn_client_move(swnreg, &addr);
++              ret = cifs_swn_client_move(tcon, &addr);
++              break;
+       }
+       default:
+               cifs_dbg(FYI, "%s: unknown notification type %d\n", __func__, type);
+               break;
+       }
+-      return 0;
++out:
++      cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_swn_notify);
++free_info:
++      cifs_swn_free_reg_info(&swnreg_info);
++      return ret;
+ }
+ int cifs_swn_register(struct cifs_tcon *tcon)
+ {
+       struct cifs_swn_reg *swnreg;
++      struct cifs_swn_reg_info swnreg_info;
+       int ret;
+       swnreg = cifs_get_swn_reg(tcon);
+       if (IS_ERR(swnreg))
+               return PTR_ERR(swnreg);
+-      ret = cifs_swn_send_register_message(swnreg);
++      cifs_swn_snapshot_reg(swnreg, &swnreg_info);
++      ret = cifs_swn_send_register_message(&swnreg_info, tcon);
+       if (ret < 0) {
+               cifs_dbg(VFS, "%s: Failed to send swn register message: %d\n", __func__, ret);
+               /* Do not put the swnreg or return error, the echo task will retry */
+@@ -615,35 +773,68 @@ int cifs_swn_unregister(struct cifs_tcon
+               return PTR_ERR(swnreg);
+       }
++      cifs_put_swn_reg_locked(swnreg, tcon);
+       mutex_unlock(&cifs_swnreg_idr_mutex);
+-      cifs_put_swn_reg(swnreg);
+-
+       return 0;
+ }
+-void cifs_swn_dump(struct seq_file *m)
++/*
++ * Snapshot one registration under cifs_swnreg_idr_mutex and return.  Callers
++ * intentionally do the per-registration network/genlmsg work without the
++ * mutex held, both to keep the critical section short and to avoid nesting
++ * cifs_swnreg_idr_mutex inside the higher tc_lock when a live tcon is then
++ * pinned for the send.
++ */
++static int cifs_swn_get_next_reg_info(int *id, struct cifs_swn_reg_info *info)
+ {
+       struct cifs_swn_reg *swnreg;
++      int ret = 0;
++
++      mutex_lock(&cifs_swnreg_idr_mutex);
++      swnreg = idr_get_next(&cifs_swnreg_idr, id);
++      if (swnreg) {
++              ret = cifs_swn_dup_reg(swnreg, info);
++              if (!ret) {
++                      *id = swnreg->id + 1;
++                      ret = 1;
++              }
++      }
++      mutex_unlock(&cifs_swnreg_idr_mutex);
++
++      return ret;
++}
++
++void cifs_swn_dump(struct seq_file *m)
++{
++      struct cifs_swn_reg_info swnreg_info;
++      struct cifs_tcon *tcon;
+       struct sockaddr_in *sa;
+       struct sockaddr_in6 *sa6;
+-      int id;
++      int id = 0;
++      int ret;
+       seq_puts(m, "Witness registrations:");
+-      mutex_lock(&cifs_swnreg_idr_mutex);
+-      idr_for_each_entry(&cifs_swnreg_idr, swnreg, id) {
++      while ((ret = cifs_swn_get_next_reg_info(&id, &swnreg_info)) > 0) {
+               seq_printf(m, "\nId: %u Refs: %u Network name: '%s'%s Share name: '%s'%s Ip address: ",
+-                              id, kref_read(&swnreg->ref_count),
+-                              swnreg->net_name, swnreg->net_name_notify ? "(y)" : "(n)",
+-                              swnreg->share_name, swnreg->share_name_notify ? "(y)" : "(n)");
+-              switch (swnreg->tcon->ses->server->dstaddr.ss_family) {
++                         swnreg_info.id, swnreg_info.ref_count,
++                         swnreg_info.net_name, swnreg_info.net_name_notify ? "(y)" : "(n)",
++                         swnreg_info.share_name, swnreg_info.share_name_notify ? "(y)" : "(n)");
++
++              tcon = cifs_swn_get_tcon(&swnreg_info);
++              if (!tcon) {
++                      seq_puts(m, "(no live tcon)");
++                      goto next;
++              }
++
++              switch (tcon->ses->server->dstaddr.ss_family) {
+               case AF_INET:
+-                      sa = (struct sockaddr_in *) &swnreg->tcon->ses->server->dstaddr;
++                      sa = (struct sockaddr_in *)&tcon->ses->server->dstaddr;
+                       seq_printf(m, "%pI4", &sa->sin_addr.s_addr);
+                       break;
+               case AF_INET6:
+-                      sa6 = (struct sockaddr_in6 *) &swnreg->tcon->ses->server->dstaddr;
++                      sa6 = (struct sockaddr_in6 *)&tcon->ses->server->dstaddr;
+                       seq_printf(m, "%pI6", &sa6->sin6_addr.s6_addr);
+                       if (sa6->sin6_scope_id)
+                               seq_printf(m, "%%%u", sa6->sin6_scope_id);
+@@ -651,23 +842,38 @@ void cifs_swn_dump(struct seq_file *m)
+               default:
+                       seq_puts(m, "(unknown)");
+               }
+-              seq_printf(m, "%s", swnreg->ip_notify ? "(y)" : "(n)");
++              cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_swn_notify);
++next:
++              seq_printf(m, "%s", swnreg_info.ip_notify ? "(y)" : "(n)");
++              cifs_swn_free_reg_info(&swnreg_info);
+       }
+-      mutex_unlock(&cifs_swnreg_idr_mutex);
++      if (ret < 0)
++              seq_printf(m, "\nFailed to snapshot witness registration: %d", ret);
+       seq_puts(m, "\n");
+ }
+ void cifs_swn_check(void)
+ {
+-      struct cifs_swn_reg *swnreg;
+-      int id;
++      struct cifs_swn_reg_info swnreg_info;
++      struct cifs_tcon *tcon;
++      int id = 0;
+       int ret;
+-      mutex_lock(&cifs_swnreg_idr_mutex);
+-      idr_for_each_entry(&cifs_swnreg_idr, swnreg, id) {
+-              ret = cifs_swn_send_register_message(swnreg);
++      while ((ret = cifs_swn_get_next_reg_info(&id, &swnreg_info)) > 0) {
++              tcon = cifs_swn_get_tcon(&swnreg_info);
++              if (!tcon) {
++                      cifs_dbg(FYI, "%s: registration id %d has no live tcon\n",
++                               __func__, swnreg_info.id);
++                      goto free_info;
++              }
++
++              ret = cifs_swn_send_register_message(&swnreg_info, tcon);
+               if (ret < 0)
+                       cifs_dbg(FYI, "%s: Failed to send register message: %d\n", __func__, ret);
++              cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_swn_notify);
++free_info:
++              cifs_swn_free_reg_info(&swnreg_info);
+       }
+-      mutex_unlock(&cifs_swnreg_idr_mutex);
++      if (ret < 0)
++              cifs_dbg(FYI, "%s: Failed to snapshot registration: %d\n", __func__, ret);
+ }
+--- a/fs/smb/client/trace.h
++++ b/fs/smb/client/trace.h
+@@ -35,6 +35,7 @@
+       EM(netfs_trace_tcon_ref_get_find,               "GET Find  ") \
+       EM(netfs_trace_tcon_ref_get_find_sess_tcon,     "GET FndSes") \
+       EM(netfs_trace_tcon_ref_get_reconnect_server,   "GET Reconn") \
++      EM(netfs_trace_tcon_ref_get_swn_notify,         "GET SwnNot") \
+       EM(netfs_trace_tcon_ref_new,                    "NEW       ") \
+       EM(netfs_trace_tcon_ref_new_ipc,                "NEW Ipc   ") \
+       EM(netfs_trace_tcon_ref_new_reconnect_server,   "NEW Reconn") \
+@@ -46,6 +47,7 @@
+       EM(netfs_trace_tcon_ref_put_mnt_ctx,            "PUT MntCtx") \
+       EM(netfs_trace_tcon_ref_put_dfs_refer,          "PUT DfsRfr") \
+       EM(netfs_trace_tcon_ref_put_reconnect_server,   "PUT Reconn") \
++      EM(netfs_trace_tcon_ref_put_swn_notify,         "PUT SwnNot") \
+       EM(netfs_trace_tcon_ref_put_tlink,              "PUT Tlink ") \
+       EM(netfs_trace_tcon_ref_see_cancelled_close,    "SEE Cn-Cls") \
+       EM(netfs_trace_tcon_ref_see_fscache_collision,  "SEE FV-CO!") \
diff --git a/queue-6.6/staging-rtl8723bs-core-move-constants-to-right-side-in-comparison.patch b/queue-6.6/staging-rtl8723bs-core-move-constants-to-right-side-in-comparison.patch
new file mode 100644 (file)
index 0000000..40b557f
--- /dev/null
@@ -0,0 +1,56 @@
+From stable+bounces-274646-greg=kroah.com@vger.kernel.org Wed Jul 15 02:22:18 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 20:22:08 -0400
+Subject: staging: rtl8723bs: core: move constants to right side in comparison
+To: stable@vger.kernel.org
+Cc: William Hansen-Baird <william.hansen.baird@gmail.com>, Greg Kroah-Hartman <gregkh@linuxfoundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715002210.3789107-1-sashal@kernel.org>
+
+From: William Hansen-Baird <william.hansen.baird@gmail.com>
+
+[ Upstream commit cf0f2680c30d4b438a5e84b73dc70be405936b54 ]
+
+Move constants to right side in if-statement conditions.
+
+Signed-off-by: William Hansen-Baird <william.hansen.baird@gmail.com>
+Link: https://patch.msgid.link/20251224100329.762141-3-william.hansen.baird@gmail.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Stable-dep-of: 1463ca3ec660 ("staging: rtl8723bs: fix OOB reads in rtw_get_sec_ie(), rtw_get_wapi_ie(), and rtw_get_wps_attr()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/staging/rtl8723bs/core/rtw_ieee80211.c |    4 ++--
+ drivers/staging/rtl8723bs/core/rtw_security.c  |    2 +-
+ 2 files changed, 3 insertions(+), 3 deletions(-)
+
+--- a/drivers/staging/rtl8723bs/core/rtw_ieee80211.c
++++ b/drivers/staging/rtl8723bs/core/rtw_ieee80211.c
+@@ -1012,7 +1012,7 @@ static int rtw_get_cipher_info(struct wl
+       pbuf = rtw_get_wpa_ie(&pnetwork->network.ies[12], &wpa_ielen, pnetwork->network.ie_length-12);
+       if (pbuf && (wpa_ielen > 0)) {
+-              if (_SUCCESS == rtw_parse_wpa_ie(pbuf, wpa_ielen+2, &group_cipher, &pairwise_cipher, &is8021x)) {
++              if (rtw_parse_wpa_ie(pbuf, wpa_ielen+2, &group_cipher, &pairwise_cipher, &is8021x) == _SUCCESS) {
+                       pnetwork->bcn_info.pairwise_cipher = pairwise_cipher;
+                       pnetwork->bcn_info.group_cipher = group_cipher;
+                       pnetwork->bcn_info.is_8021x = is8021x;
+@@ -1022,7 +1022,7 @@ static int rtw_get_cipher_info(struct wl
+               pbuf = rtw_get_wpa2_ie(&pnetwork->network.ies[12], &wpa_ielen, pnetwork->network.ie_length-12);
+               if (pbuf && (wpa_ielen > 0)) {
+-                      if (_SUCCESS == rtw_parse_wpa2_ie(pbuf, wpa_ielen+2, &group_cipher, &pairwise_cipher, &is8021x)) {
++                      if (rtw_parse_wpa2_ie(pbuf, wpa_ielen+2, &group_cipher, &pairwise_cipher, &is8021x) == _SUCCESS) {
+                               pnetwork->bcn_info.pairwise_cipher = pairwise_cipher;
+                               pnetwork->bcn_info.group_cipher = group_cipher;
+                               pnetwork->bcn_info.is_8021x = is8021x;
+--- a/drivers/staging/rtl8723bs/core/rtw_security.c
++++ b/drivers/staging/rtl8723bs/core/rtw_security.c
+@@ -1538,7 +1538,7 @@ void rtw_sec_restore_wep_key(struct adap
+       struct security_priv *securitypriv = &(adapter->securitypriv);
+       signed int keyid;
+-      if ((_WEP40_ == securitypriv->dot11PrivacyAlgrthm) || (_WEP104_ == securitypriv->dot11PrivacyAlgrthm)) {
++      if ((securitypriv->dot11PrivacyAlgrthm == _WEP40_) || (securitypriv->dot11PrivacyAlgrthm == _WEP104_)) {
+               for (keyid = 0; keyid < 4; keyid++) {
+                       if (securitypriv->key_mask & BIT(keyid)) {
+                               if (keyid == securitypriv->dot11PrivacyKeyIndex)
diff --git a/queue-6.6/staging-rtl8723bs-fix-oob-reads-in-rtw_get_sec_ie-rtw_get_wapi_ie-and-rtw_get_wps_attr.patch b/queue-6.6/staging-rtl8723bs-fix-oob-reads-in-rtw_get_sec_ie-rtw_get_wapi_ie-and-rtw_get_wps_attr.patch
new file mode 100644 (file)
index 0000000..edf130c
--- /dev/null
@@ -0,0 +1,94 @@
+From stable+bounces-274648-greg=kroah.com@vger.kernel.org Wed Jul 15 02:22:21 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 20:22:10 -0400
+Subject: staging: rtl8723bs: fix OOB reads in rtw_get_sec_ie(), rtw_get_wapi_ie(), and rtw_get_wps_attr()
+To: stable@vger.kernel.org
+Cc: Alexandru Hossu <hossu.alexandru@gmail.com>, stable <stable@kernel.org>, Greg Kroah-Hartman <gregkh@linuxfoundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715002210.3789107-3-sashal@kernel.org>
+
+From: Alexandru Hossu <hossu.alexandru@gmail.com>
+
+[ Upstream commit 1463ca3ec6601cbb097d8d87dbf5dcf1cb86a344 ]
+
+Three IE/attribute parsing functions have missing bounds checks.
+
+rtw_get_sec_ie() and rtw_get_wapi_ie() iterate over a raw IE buffer
+without verifying that the header bytes (tag + length) are within the
+remaining buffer before reading them.  Additionally, rtw_get_sec_ie()
+compares the 4-byte WPA OUI at cnt+2 without checking that at least
+6 bytes remain, and rtw_get_wapi_ie() compares a 4-byte WAPI OUI at
+cnt+6 without checking that at least 10 bytes remain.
+
+rtw_get_wps_attr() reads wps_ie[0] and wps_ie+2 unconditionally at
+entry, before verifying that wps_ielen is large enough to contain
+the 6-byte WPS IE header (element_id + length + 4-byte OUI).  Inside
+the attribute loop, get_unaligned_be16() is called on attr_ptr and
+attr_ptr+2 without checking that 4 bytes remain in the buffer.
+
+Add a cnt+2 bounds check before each loop body in rtw_get_sec_ie()
+and rtw_get_wapi_ie(), guard each multi-byte comparison with a minimum
+IE length requirement, add a wps_ielen < 6 early return in
+rtw_get_wps_attr(), and add a 4-byte bounds check in its inner loop.
+
+Fixes: 554c0a3abf21 ("staging: Add rtl8723bs sdio wifi driver")
+Cc: stable <stable@kernel.org>
+Signed-off-by: Alexandru Hossu <hossu.alexandru@gmail.com>
+Link: https://patch.msgid.link/20260522004531.1038924-8-hossu.alexandru@gmail.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/staging/rtl8723bs/core/rtw_ieee80211.c |   15 +++++++++++++++
+ 1 file changed, 15 insertions(+)
+
+--- a/drivers/staging/rtl8723bs/core/rtw_ieee80211.c
++++ b/drivers/staging/rtl8723bs/core/rtw_ieee80211.c
+@@ -584,9 +584,14 @@ int rtw_get_wapi_ie(u8 *in_ie, uint in_l
+       cnt = (_TIMESTAMP_ + _BEACON_ITERVAL_ + _CAPABILITY_);
+       while (cnt < in_len) {
++              if (cnt + 2 > in_len)
++                      break;
++              if (cnt + 2 + in_ie[cnt + 1] > in_len)
++                      break;
+               authmode = in_ie[cnt];
+               if (authmode == WLAN_EID_BSS_AC_ACCESS_DELAY &&
++                  in_ie[cnt + 1] >= 8 &&
+                   (!memcmp(&in_ie[cnt + 6], wapi_oui1, 4) ||
+                    !memcmp(&in_ie[cnt + 6], wapi_oui2, 4))) {
+                       if (wapi_ie)
+@@ -619,9 +624,14 @@ void rtw_get_sec_ie(u8 *in_ie, uint in_l
+       cnt = (_TIMESTAMP_ + _BEACON_ITERVAL_ + _CAPABILITY_);
+       while (cnt < in_len) {
++              if (cnt + 2 > in_len)
++                      break;
++              if (cnt + 2 + in_ie[cnt + 1] > in_len)
++                      break;
+               authmode = in_ie[cnt];
+               if ((authmode == WLAN_EID_VENDOR_SPECIFIC) &&
++                  in_ie[cnt + 1] >= 4 &&
+                   (!memcmp(&in_ie[cnt + 2], &wpa_oui[0], 4))) {
+                       if (wpa_ie)
+                               memcpy(wpa_ie, &in_ie[cnt], in_ie[cnt + 1] + 2);
+@@ -706,6 +716,9 @@ u8 *rtw_get_wps_attr(u8 *wps_ie, uint wp
+       if (len_attr)
+               *len_attr = 0;
++      if (wps_ielen < 6)
++              return attr_ptr;
++
+       if ((wps_ie[0] != WLAN_EID_VENDOR_SPECIFIC) ||
+               (memcmp(wps_ie + 2, wps_oui, 4))) {
+               return attr_ptr;
+@@ -716,6 +729,8 @@ u8 *rtw_get_wps_attr(u8 *wps_ie, uint wp
+       while (attr_ptr - wps_ie < wps_ielen) {
+               /*  4 = 2(Attribute ID) + 2(Length) */
++              if (attr_ptr + 4 > wps_ie + wps_ielen)
++                      break;
+               u16 attr_id = get_unaligned_be16(attr_ptr);
+               u16 attr_data_len = get_unaligned_be16(attr_ptr + 2);
+               u16 attr_len = attr_data_len + 4;
diff --git a/queue-6.6/staging-rtl8723bs-fix-spaces-around-binary-operators.patch b/queue-6.6/staging-rtl8723bs-fix-spaces-around-binary-operators.patch
new file mode 100644 (file)
index 0000000..fdbaaf6
--- /dev/null
@@ -0,0 +1,206 @@
+From stable+bounces-274647-greg=kroah.com@vger.kernel.org Wed Jul 15 02:22:20 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 20:22:09 -0400
+Subject: staging: rtl8723bs: fix spaces around binary operators
+To: stable@vger.kernel.org
+Cc: Nikolay Kulikov <nikolayof23@gmail.com>, Ethan Tidmore <ethantidmore06@gmail.com>, Greg Kroah-Hartman <gregkh@linuxfoundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715002210.3789107-2-sashal@kernel.org>
+
+From: Nikolay Kulikov <nikolayof23@gmail.com>
+
+[ Upstream commit 0c9d1b56f9af0762a5be5118e1bb962f23784dc4 ]
+
+Add missing spaces and fix line length to comply with kernel coding
+style.
+
+Signed-off-by: Nikolay Kulikov <nikolayof23@gmail.com>
+Reviewed-by: Ethan Tidmore <ethantidmore06@gmail.com>
+Link: https://patch.msgid.link/20260221172751.52329-1-nikolayof23@gmail.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Stable-dep-of: 1463ca3ec660 ("staging: rtl8723bs: fix OOB reads in rtw_get_sec_ie(), rtw_get_wapi_ie(), and rtw_get_wps_attr()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/staging/rtl8723bs/core/rtw_ieee80211.c |   78 +++++++++++++------------
+ 1 file changed, 42 insertions(+), 36 deletions(-)
+
+--- a/drivers/staging/rtl8723bs/core/rtw_ieee80211.c
++++ b/drivers/staging/rtl8723bs/core/rtw_ieee80211.c
+@@ -455,10 +455,10 @@ int rtw_parse_wpa_ie(u8 *wpa_ie, int wpa
+               return _FAIL;
+       }
+-      if ((*wpa_ie != WLAN_EID_VENDOR_SPECIFIC) || (*(wpa_ie+1) != (u8)(wpa_ie_len - 2)) ||
+-         (memcmp(wpa_ie+2, RTW_WPA_OUI_TYPE, WPA_SELECTOR_LEN))) {
++      if ((*wpa_ie != WLAN_EID_VENDOR_SPECIFIC) ||
++          (*(wpa_ie + 1) != (u8)(wpa_ie_len - 2)) ||
++          (memcmp(wpa_ie + 2, RTW_WPA_OUI_TYPE, WPA_SELECTOR_LEN)))
+               return _FAIL;
+-      }
+       pos = wpa_ie;
+@@ -518,7 +518,7 @@ int rtw_parse_wpa2_ie(u8 *rsn_ie, int rs
+               return _FAIL;
+       }
+-      if ((*rsn_ie != WLAN_EID_RSN) || (*(rsn_ie+1) != (u8)(rsn_ie_len - 2)))
++      if ((*rsn_ie != WLAN_EID_RSN) || (*(rsn_ie + 1) != (u8)(rsn_ie_len - 2)))
+               return _FAIL;
+       pos = rsn_ie;
+@@ -586,18 +586,18 @@ int rtw_get_wapi_ie(u8 *in_ie, uint in_l
+       while (cnt < in_len) {
+               authmode = in_ie[cnt];
+-              /* if (authmode == WLAN_EID_BSS_AC_ACCESS_DELAY) */
+-              if (authmode == WLAN_EID_BSS_AC_ACCESS_DELAY && (!memcmp(&in_ie[cnt+6], wapi_oui1, 4) ||
+-                                      !memcmp(&in_ie[cnt+6], wapi_oui2, 4))) {
++              if (authmode == WLAN_EID_BSS_AC_ACCESS_DELAY &&
++                  (!memcmp(&in_ie[cnt + 6], wapi_oui1, 4) ||
++                   !memcmp(&in_ie[cnt + 6], wapi_oui2, 4))) {
+                       if (wapi_ie)
+-                              memcpy(wapi_ie, &in_ie[cnt], in_ie[cnt+1]+2);
++                              memcpy(wapi_ie, &in_ie[cnt], in_ie[cnt + 1] + 2);
+                       if (wapi_len)
+-                              *wapi_len = in_ie[cnt+1]+2;
++                              *wapi_len = in_ie[cnt + 1] + 2;
+-                      cnt += in_ie[cnt+1]+2;  /* get next */
++                      cnt += in_ie[cnt + 1] + 2;  /* get next */
+               } else {
+-                      cnt += in_ie[cnt+1]+2;   /* get next */
++                      cnt += in_ie[cnt + 1] + 2;   /* get next */
+               }
+       }
+@@ -621,9 +621,10 @@ void rtw_get_sec_ie(u8 *in_ie, uint in_l
+       while (cnt < in_len) {
+               authmode = in_ie[cnt];
+-              if ((authmode == WLAN_EID_VENDOR_SPECIFIC) && (!memcmp(&in_ie[cnt+2], &wpa_oui[0], 4))) {
++              if ((authmode == WLAN_EID_VENDOR_SPECIFIC) &&
++                  (!memcmp(&in_ie[cnt + 2], &wpa_oui[0], 4))) {
+                       if (wpa_ie)
+-                              memcpy(wpa_ie, &in_ie[cnt], in_ie[cnt+1]+2);
++                              memcpy(wpa_ie, &in_ie[cnt], in_ie[cnt + 1] + 2);
+                       *wpa_len = in_ie[cnt + 1] + 2;
+                       cnt += in_ie[cnt + 1] + 2;  /* get next */
+@@ -632,10 +633,10 @@ void rtw_get_sec_ie(u8 *in_ie, uint in_l
+                               if (rsn_ie)
+                                       memcpy(rsn_ie, &in_ie[cnt], in_ie[cnt + 1] + 2);
+-                              *rsn_len = in_ie[cnt+1]+2;
+-                              cnt += in_ie[cnt+1]+2;  /* get next */
++                              *rsn_len = in_ie[cnt + 1] + 2;
++                              cnt += in_ie[cnt + 1] + 2;  /* get next */
+                       } else {
+-                              cnt += in_ie[cnt+1]+2;   /* get next */
++                              cnt += in_ie[cnt + 1] + 2;   /* get next */
+                       }
+               }
+       }
+@@ -667,20 +668,20 @@ u8 *rtw_get_wps_ie(u8 *in_ie, uint in_le
+       while (cnt < in_len) {
+               eid = in_ie[cnt];
+-              if ((eid == WLAN_EID_VENDOR_SPECIFIC) && (!memcmp(&in_ie[cnt+2], wps_oui, 4))) {
++              if ((eid == WLAN_EID_VENDOR_SPECIFIC) && (!memcmp(&in_ie[cnt + 2], wps_oui, 4))) {
+                       wpsie_ptr = &in_ie[cnt];
+                       if (wps_ie)
+-                              memcpy(wps_ie, &in_ie[cnt], in_ie[cnt+1]+2);
++                              memcpy(wps_ie, &in_ie[cnt], in_ie[cnt + 1] + 2);
+                       if (wps_ielen)
+-                              *wps_ielen = in_ie[cnt+1]+2;
++                              *wps_ielen = in_ie[cnt + 1] + 2;
+-                      cnt += in_ie[cnt+1]+2;
++                      cnt += in_ie[cnt + 1] + 2;
+                       break;
+               }
+-              cnt += in_ie[cnt+1]+2; /* goto next */
++              cnt += in_ie[cnt + 1] + 2; /* goto next */
+       }
+       return wpsie_ptr;
+@@ -758,12 +759,12 @@ u8 *rtw_get_wps_attr_content(u8 *wps_ie,
+       if (attr_ptr && attr_len) {
+               if (buf_content)
+-                      memcpy(buf_content, attr_ptr+4, attr_len-4);
++                      memcpy(buf_content, attr_ptr + 4, attr_len - 4);
+               if (len_content)
+-                      *len_content = attr_len-4;
++                      *len_content = attr_len - 4;
+-              return attr_ptr+4;
++              return attr_ptr + 4;
+       }
+       return NULL;
+@@ -1009,20 +1010,25 @@ static int rtw_get_cipher_info(struct wl
+       int group_cipher = 0, pairwise_cipher = 0, is8021x = 0;
+       int ret = _FAIL;
+-      pbuf = rtw_get_wpa_ie(&pnetwork->network.ies[12], &wpa_ielen, pnetwork->network.ie_length-12);
++      pbuf = rtw_get_wpa_ie(&pnetwork->network.ies[12],
++                            &wpa_ielen,
++                            pnetwork->network.ie_length - 12);
+       if (pbuf && (wpa_ielen > 0)) {
+-              if (rtw_parse_wpa_ie(pbuf, wpa_ielen+2, &group_cipher, &pairwise_cipher, &is8021x) == _SUCCESS) {
++              if (rtw_parse_wpa_ie(pbuf, wpa_ielen + 2, &group_cipher,
++                                   &pairwise_cipher, &is8021x) == _SUCCESS) {
+                       pnetwork->bcn_info.pairwise_cipher = pairwise_cipher;
+                       pnetwork->bcn_info.group_cipher = group_cipher;
+                       pnetwork->bcn_info.is_8021x = is8021x;
+                       ret = _SUCCESS;
+               }
+       } else {
+-              pbuf = rtw_get_wpa2_ie(&pnetwork->network.ies[12], &wpa_ielen, pnetwork->network.ie_length-12);
++              pbuf = rtw_get_wpa2_ie(&pnetwork->network.ies[12], &wpa_ielen,
++                                     pnetwork->network.ie_length - 12);
+               if (pbuf && (wpa_ielen > 0)) {
+-                      if (rtw_parse_wpa2_ie(pbuf, wpa_ielen+2, &group_cipher, &pairwise_cipher, &is8021x) == _SUCCESS) {
++                      if (rtw_parse_wpa2_ie(pbuf, wpa_ielen + 2, &group_cipher,
++                                            &pairwise_cipher, &is8021x) == _SUCCESS) {
+                               pnetwork->bcn_info.pairwise_cipher = pairwise_cipher;
+                               pnetwork->bcn_info.group_cipher = group_cipher;
+                               pnetwork->bcn_info.is_8021x = is8021x;
+@@ -1091,21 +1097,21 @@ u16 rtw_mcs_rate(u8 bw_40MHz, u8 short_G
+       u16 max_rate = 0;
+       if (MCS_rate[0] & BIT(7))
+-              max_rate = (bw_40MHz) ? ((short_GI)?1500:1350):((short_GI)?722:650);
++              max_rate = (bw_40MHz) ? ((short_GI) ? 1500 : 1350) : ((short_GI) ? 722 : 650);
+       else if (MCS_rate[0] & BIT(6))
+-              max_rate = (bw_40MHz) ? ((short_GI)?1350:1215):((short_GI)?650:585);
++              max_rate = (bw_40MHz) ? ((short_GI) ? 1350 : 1215) : ((short_GI) ? 650 : 585);
+       else if (MCS_rate[0] & BIT(5))
+-              max_rate = (bw_40MHz) ? ((short_GI)?1200:1080):((short_GI)?578:520);
++              max_rate = (bw_40MHz) ? ((short_GI) ? 1200 : 1080) : ((short_GI) ? 578 : 520);
+       else if (MCS_rate[0] & BIT(4))
+-              max_rate = (bw_40MHz) ? ((short_GI)?900:810):((short_GI)?433:390);
++              max_rate = (bw_40MHz) ? ((short_GI) ? 900 : 810) : ((short_GI) ? 433 : 390);
+       else if (MCS_rate[0] & BIT(3))
+-              max_rate = (bw_40MHz) ? ((short_GI)?600:540):((short_GI)?289:260);
++              max_rate = (bw_40MHz) ? ((short_GI) ? 600 : 540) : ((short_GI) ? 289 : 260);
+       else if (MCS_rate[0] & BIT(2))
+-              max_rate = (bw_40MHz) ? ((short_GI)?450:405):((short_GI)?217:195);
++              max_rate = (bw_40MHz) ? ((short_GI) ? 450 : 405) : ((short_GI) ? 217 : 195);
+       else if (MCS_rate[0] & BIT(1))
+-              max_rate = (bw_40MHz) ? ((short_GI)?300:270):((short_GI)?144:130);
++              max_rate = (bw_40MHz) ? ((short_GI) ? 300 : 270) : ((short_GI) ? 144 : 130);
+       else if (MCS_rate[0] & BIT(0))
+-              max_rate = (bw_40MHz) ? ((short_GI)?150:135):((short_GI)?72:65);
++              max_rate = (bw_40MHz) ? ((short_GI) ? 150 : 135) : ((short_GI) ? 72 : 65);
+       return max_rate;
+ }
diff --git a/queue-6.6/treewide-switch-rename-to-timer_delete.patch b/queue-6.6/treewide-switch-rename-to-timer_delete.patch
new file mode 100644 (file)
index 0000000..8da90c5
--- /dev/null
@@ -0,0 +1,37 @@
+From stable+bounces-277093-greg=kroah.com@vger.kernel.org Fri Jul 17 17:00:26 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 17 Jul 2026 10:59:58 -0400
+Subject: treewide: Switch/rename to timer_delete[_sync]()
+To: stable@vger.kernel.org
+Cc: Thomas Gleixner <tglx@linutronix.de>, Ingo Molnar <mingo@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260717145959.1930377-1-sashal@kernel.org>
+
+From: Thomas Gleixner <tglx@linutronix.de>
+
+[ Upstream commit 8fa7292fee5c5240402371ea89ab285ec856c916 ]
+
+timer_delete[_sync]() replaces del_timer[_sync](). Convert the whole tree
+over and remove the historical wrapper inlines.
+
+Conversion was done with coccinelle plus manual fixups where necessary.
+
+Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
+Signed-off-by: Ingo Molnar <mingo@kernel.org>
+Stable-dep-of: 75fe87e19d8a ("HID: appleir: fix UAF on pending key_up_timer in remove()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/hid/hid-appleir.c |    2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/hid/hid-appleir.c
++++ b/drivers/hid/hid-appleir.c
+@@ -319,7 +319,7 @@ static void appleir_remove(struct hid_de
+ {
+       struct appleir *appleir = hid_get_drvdata(hid);
+       hid_hw_stop(hid);
+-      del_timer_sync(&appleir->key_up_timer);
++      timer_delete_sync(&appleir->key_up_timer);
+ }
+ static const struct hid_device_id appleir_devices[] = {
diff --git a/queue-6.6/usb-atm-ueagle-atm-wait-for-pre-firmware-load-in-.disconnect.patch b/queue-6.6/usb-atm-ueagle-atm-wait-for-pre-firmware-load-in-.disconnect.patch
new file mode 100644 (file)
index 0000000..74c71c7
--- /dev/null
@@ -0,0 +1,175 @@
+From stable+bounces-277506-greg=kroah.com@vger.kernel.org Sun Jul 19 18:57:18 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 19 Jul 2026 12:57:06 -0400
+Subject: usb: atm: ueagle-atm: wait for pre-firmware load in .disconnect()
+To: stable@vger.kernel.org
+Cc: Mauricio Faria de Oliveira <mfo@igalia.com>, stable <stable@kernel.org>, syzbot+ce1e5a1b4e086b43e56d@syzkaller.appspotmail.com, syzbot+306212936b13e520679d@syzkaller.appspotmail.com, syzbot+457452d30bcdda75ead2@syzkaller.appspotmail.com, Andrey Tsygunka <aitsygunka@yandex.ru>, Stanislaw Gruszka <stf_xl@wp.pl>, Greg Kroah-Hartman <gregkh@linuxfoundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260719165706.748133-1-sashal@kernel.org>
+
+From: Mauricio Faria de Oliveira <mfo@igalia.com>
+
+[ Upstream commit e2674dfbed8a30d57e2bc872c4bfa6c3eec918bf ]
+
+ueagle-atm uses the asynchronous request_firmware_nowait() in .probe(),
+but does not wait for its completion, not even in .disconnect(); so, if the
+device is unplugged meanwhile, its teardown runs concurrently with that.
+
+Even though this inconsistency is worth addressing on its own, it has also
+triggered several bug reports in syzbot over the years (some auto-closed)
+where the firmware sysfs fallback mechanism (CONFIG_FW_LOADER_USER_HELPER)
+creates a firmware subdirectory in the device directory during its removal,
+which might hit unexpected conditions in kernfs, apparently, depending at
+which point the add and remove operations raced. (See links.)
+
+The pattern is:
+
+usb ?-?: Direct firmware load for ueagle-atm/eagle?.fw failed with error -2
+usb ?-?: Falling back to sysfs fallback for: ueagle-atm/eagle?.fw
+<ERROR>
+Call trace:
+ ...
+ kernfs_create_dir_ns
+ sysfs_create_dir_ns
+ create_dir
+ kobject_add_internal
+ kobject_add_varg
+ kobject_add
+ class_dir_create_and_add
+ get_device_parent
+ device_add
+ fw_load_sysfs_fallback
+ fw_load_from_user_helper
+ firmware_fallback_sysfs
+ _request_firmware
+ request_firmware_work_func
+ ...
+
+(Some variations are observed, after fw_load_sysfs_fallback(), e.g., [1].)
+
+While the kernfs side is being looked at, the ueagle-atm side can be fixed
+by waiting for the pre-firmware load in the .disconnect() handler.
+
+This change has a similar approach to previous work by Andrey Tsygunka [2]
+(wait_for_completion() in .disconnect()), but it is relatively different in
+design/implementation; using the Originally-by tag for credit assignment.
+
+This has been tested with:
+- synthetic reproducer to check the error path;
+- USB gadget (virtual device) to check the firmware upload path;
+- QEMU device emulator to check the device ID re-enumeration path;
+(The latter two were written by Claude; no other code/text in this commit.)
+
+Links (year first reported):
+ 2025 https://syzbot.org/bug?extid=ce1e5a1b4e086b43e56d
+ 2025 https://syzbot.org/bug?extid=9af8471255ac36e34fd4
+ 2024 https://syzbot.org/bug?extid=306212936b13e520679d
+ 2023 https://syzkaller.appspot.com/bug?extid=457452d30bcdda75ead2
+ 2022 https://syzbot.org/bug?extid=782984d6f1701b526edb
+ 2021 https://syzbot.org/bug?id=f3f221579f4ef7e9691281f3c6f56c05f83e8490
+ 2021 https://syzbot.org/bug?id=84d86f0d71394829df6fc53daf6642c045983881
+ 2021 https://syzbot.org/bug?id=3302dc1c0e2b9c94f2e8edb404eabc9267bc6f90
+
+[1] https://syzkaller.appspot.com/bug?extid=457452d30bcdda75ead2
+[2] https://lore.kernel.org/lkml/20250410093146.3776801-2-aitsygunka@yandex.ru/
+
+Cc: stable <stable@kernel.org>
+Reported-by: syzbot+ce1e5a1b4e086b43e56d@syzkaller.appspotmail.com
+Closes: https://syzbot.org/bug?extid=ce1e5a1b4e086b43e56d
+Reported-by: syzbot+306212936b13e520679d@syzkaller.appspotmail.com
+Closes: https://syzbot.org/bug?extid=306212936b13e520679d
+Reported-by: syzbot+457452d30bcdda75ead2@syzkaller.appspotmail.com
+Closes: https://syzkaller.appspot.com/bug?extid=457452d30bcdda75ead2
+Originally-by: Andrey Tsygunka <aitsygunka@yandex.ru>
+Fixes: b72458a80c75 ("[PATCH] USB: Eagle and ADI 930 usb adsl modem driver")
+Assisted-by: Claude:claude-opus-4.7 # usb gadget & qemu device for testing
+Signed-off-by: Mauricio Faria de Oliveira <mfo@igalia.com>
+Acked-by: Stanislaw Gruszka <stf_xl@wp.pl>
+Link: https://patch.msgid.link/20260526-ueagle-atm_req-fw-sync-v3-1-93c01961daaf@igalia.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/usb/atm/ueagle-atm.c |   36 +++++++++++++++++++++++++++++++-----
+ 1 file changed, 31 insertions(+), 5 deletions(-)
+
+--- a/drivers/usb/atm/ueagle-atm.c
++++ b/drivers/usb/atm/ueagle-atm.c
+@@ -599,7 +599,9 @@ static int uea_send_modem_cmd(struct usb
+ static void uea_upload_pre_firmware(const struct firmware *fw_entry,
+                                                               void *context)
+ {
+-      struct usb_device *usb = context;
++      struct usb_interface *intf = context;
++      struct usb_device *usb = interface_to_usbdev(intf);
++      struct completion *fw_done = usb_get_intfdata(intf);
+       const u8 *pfw;
+       u8 value;
+       u32 crc = 0;
+@@ -670,15 +672,17 @@ err_fw_corrupted:
+ err:
+       release_firmware(fw_entry);
+       uea_leaves(usb);
++      complete(fw_done);
+ }
+ /*
+  * uea_load_firmware - Load usb firmware for pre-firmware devices.
+  */
+-static int uea_load_firmware(struct usb_device *usb, unsigned int ver)
++static int uea_load_firmware(struct usb_interface *intf, unsigned int ver)
+ {
+       int ret;
+       char *fw_name = EAGLE_FIRMWARE;
++      struct usb_device *usb = interface_to_usbdev(intf);
+       uea_enters(usb);
+       uea_info(usb, "pre-firmware device, uploading firmware\n");
+@@ -702,7 +706,7 @@ static int uea_load_firmware(struct usb_
+       }
+       ret = request_firmware_nowait(THIS_MODULE, 1, fw_name, &usb->dev,
+-                                      GFP_KERNEL, usb,
++                                      GFP_KERNEL, intf,
+                                       uea_upload_pre_firmware);
+       if (ret)
+               uea_err(usb, "firmware %s is not available\n", fw_name);
+@@ -2598,8 +2602,23 @@ static int uea_probe(struct usb_interfac
+       usb_reset_device(usb);
+-      if (UEA_IS_PREFIRM(id))
+-              return uea_load_firmware(usb, UEA_CHIP_VERSION(id));
++      if (UEA_IS_PREFIRM(id)) {
++              struct completion *fw_done;
++
++              /* Wait for the firmware load to be done, in .disconnect() */
++              fw_done = kzalloc(sizeof(*fw_done), GFP_KERNEL);
++              if (!fw_done)
++                      return -ENOMEM;
++
++              init_completion(fw_done);
++              usb_set_intfdata(intf, fw_done);
++
++              ret = uea_load_firmware(intf, UEA_CHIP_VERSION(id));
++              if (ret)
++                      kfree(fw_done);
++
++              return ret;
++      }
+       ret = usbatm_usb_probe(intf, id, &uea_usbatm_driver);
+       if (ret == 0) {
+@@ -2630,6 +2649,13 @@ static void uea_disconnect(struct usb_in
+               usbatm_usb_disconnect(intf);
+               mutex_unlock(&uea_mutex);
+               uea_info(usb, "ADSL device removed\n");
++      } else if (usb->config->desc.bNumInterfaces == 1) {
++              struct completion *fw_done = usb_get_intfdata(intf);
++
++              uea_dbg(usb, "pre-firmware device, waiting firmware upload\n");
++              wait_for_completion(fw_done);
++              uea_dbg(usb, "pre-firmware device, finished waiting\n");
++              kfree(fw_done);
+       }
+       uea_leaves(usb);
diff --git a/queue-6.6/usb-gadget-f_fs-initialize-reset_work-at-allocation-time.patch b/queue-6.6/usb-gadget-f_fs-initialize-reset_work-at-allocation-time.patch
new file mode 100644 (file)
index 0000000..a9851cd
--- /dev/null
@@ -0,0 +1,94 @@
+From sashal@kernel.org Mon Jul 20 15:30:41 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 09:30:36 -0400
+Subject: usb: gadget: f_fs: initialize reset_work at allocation time
+To: stable@vger.kernel.org
+Cc: "Tyler Baker" <tyler.baker@oss.qualcomm.com>, stable <stable@kernel.org>, "Loic Poulain" <loic.poulain@oss.qualcomm.com>, "Dmitry Baryshkov" <dmitry.baryshkov@oss.qualcomm.com>, "Srinivas Kandagatla" <srinivas.kandagatla@oss.qualcomm.com>, "Peter Chen" <peter.chen@kernel.org>, "MichaÅ‚ Nazarewicz" <mina86@mina86.com>, "Greg Kroah-Hartman" <gregkh@linuxfoundation.org>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260720133036.1149470-1-sashal@kernel.org>
+
+From: Tyler Baker <tyler.baker@oss.qualcomm.com>
+
+[ Upstream commit 3137b243c93982fe3460335e12f9247739766e10 ]
+
+ffs_fs_kill_sb() unconditionally calls cancel_work_sync() on
+ffs->reset_work when a functionfs instance is unmounted:
+
+       ffs_data_reset(ffs);
+       cancel_work_sync(&ffs->reset_work);
+
+However ffs->reset_work is only ever initialized via INIT_WORK() in
+ffs_func_set_alt() and ffs_func_disable(), and only on the
+FFS_DEACTIVATED path. That state is reached solely by ffs_data_closed()
+when the instance is mounted with the "no_disconnect" option, so for the
+common case (no "no_disconnect", or mounted and unmounted without ever
+being deactivated) reset_work is never initialized.
+
+ffs_data_new() allocates the ffs_data with kzalloc_obj() and does not
+initialize reset_work, and ffs_data_reset()/ffs_data_clear() do not touch
+it either, so reset_work.func is left NULL. cancel_work_sync() on such a
+work then trips the WARN_ON(!work->func) guard in __flush_work():
+
+  WARNING: kernel/workqueue.c:4301 at __flush_work+0x330/0x360, CPU#3: umount
+  Call trace:
+   __flush_work
+   cancel_work_sync
+   ffs_fs_kill_sb [usb_f_fs]
+   deactivate_locked_super
+   deactivate_super
+   cleanup_mnt
+   __cleanup_mnt
+   task_work_run
+   exit_to_user_mode_loop
+   el0_svc
+
+On older kernels cancel_work_sync() on a zero-initialized work struct was
+a silent no-op, which hid the missing initialization.
+
+Initialize reset_work once in ffs_data_new() so it is always valid for
+the lifetime of the ffs_data, and drop the now-redundant INIT_WORK()
+calls from the two deactivation paths.
+
+Fixes: 18d6b32fca38 ("usb: gadget: f_fs: add "no_disconnect" mode")
+Cc: stable <stable@kernel.org>
+Signed-off-by: Tyler Baker <tyler.baker@oss.qualcomm.com>
+Cc: Loic Poulain <loic.poulain@oss.qualcomm.com>
+Cc: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
+Cc: Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com>
+Tested-by: Loic Poulain <loic.poulain@oss.qualcomm.com>
+Reviewed-by: Peter Chen <peter.chen@kernel.org>
+Acked-by: MichaÅ‚ Nazarewicz <mina86@mina86.com>
+Link: https://patch.msgid.link/20260609193635.2284430-1-tyler.baker@oss.qualcomm.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+[ dropped the ffs_func_disable hunk since 6.6 predates the disable-body split and routes disable through ffs_func_set_alt, so removing the single lazy INIT_WORK there covers both paths ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/usb/gadget/function/f_fs.c |    3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+--- a/drivers/usb/gadget/function/f_fs.c
++++ b/drivers/usb/gadget/function/f_fs.c
+@@ -255,6 +255,7 @@ static int ffs_acquire_dev(const char *d
+ static void ffs_release_dev(struct ffs_dev *ffs_dev);
+ static int ffs_ready(struct ffs_data *ffs);
+ static void ffs_closed(struct ffs_data *ffs);
++static void ffs_reset_work(struct work_struct *work);
+ /* Misc helper functions ****************************************************/
+@@ -1734,6 +1735,7 @@ static struct ffs_data *ffs_data_new(con
+       init_waitqueue_head(&ffs->ev.waitq);
+       init_waitqueue_head(&ffs->wait);
+       init_completion(&ffs->ep0req_completion);
++      INIT_WORK(&ffs->reset_work, ffs_reset_work);
+       /* XXX REVISIT need to update it in some places, or do we? */
+       ffs->ev.can_stall = 1;
+@@ -3261,7 +3263,6 @@ static int ffs_func_set_alt(struct usb_f
+       if (ffs->state == FFS_DEACTIVATED) {
+               ffs->state = FFS_CLOSING;
+-              INIT_WORK(&ffs->reset_work, ffs_reset_work);
+               schedule_work(&ffs->reset_work);
+               return -ENODEV;
+       }
diff --git a/queue-6.6/usb-gadget-f_fs-tie-read_buffer-lifetime-to-ffs_epfile.patch b/queue-6.6/usb-gadget-f_fs-tie-read_buffer-lifetime-to-ffs_epfile.patch
new file mode 100644 (file)
index 0000000..1fc0f11
--- /dev/null
@@ -0,0 +1,55 @@
+From sashal@kernel.org Mon Jul 20 16:17:44 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 10:17:37 -0400
+Subject: usb: gadget: f_fs: Tie read_buffer lifetime to ffs_epfile
+To: stable@vger.kernel.org
+Cc: Neill Kapron <nkapron@google.com>, stable <stable@kernel.org>, Greg Kroah-Hartman <gregkh@linuxfoundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260720141737.1619471-1-sashal@kernel.org>
+
+From: Neill Kapron <nkapron@google.com>
+
+[ Upstream commit 8bdcf96eb135aebacac319667f87db034fb38406 ]
+
+Currently, ffs_epfile_release unconditionally frees the endpoint's
+read_buffer when a file descriptor is closed. If userspace explicitly
+opens the endpoint multiple times and closes one, the read_buffer is
+destroyed. This can lead to silent data loss if other file descriptors
+are still actively reading from the endpoint.
+
+By tying the lifetime of the read_buffer to the ffs_epfile structure itself
+(which is destroyed when the functionfs instance is torn down in
+ffs_epfiles_destroy), we eliminate the brittle dependency on open/release
+calls while correctly matching the conceptual lifetime of unread data on
+the hardware endpoint.
+
+Fixes: 9353afbbfa7b ("usb: gadget: f_fs: buffer data from â€˜oversized’ OUT requests")
+Cc: stable <stable@kernel.org>
+Assisted-by: Antigravity:gemini-3.1-pro
+Signed-off-by: Neill Kapron <nkapron@google.com>
+Link: https://patch.msgid.link/20260619040609.4010746-3-nkapron@google.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+[ adjusted context in ffs_epfiles_destroy() since 6.12 lacks the simple_remove_by_name() rework and still uses dentry-based cleanup ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/usb/gadget/function/f_fs.c |    2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/usb/gadget/function/f_fs.c
++++ b/drivers/usb/gadget/function/f_fs.c
+@@ -1269,7 +1269,6 @@ ffs_epfile_release(struct inode *inode,
+ {
+       struct ffs_epfile *epfile = inode->i_private;
+-      __ffs_epfile_read_buffer_free(epfile);
+       ffs_data_closed(epfile->ffs);
+       return 0;
+@@ -1893,6 +1892,7 @@ static void ffs_epfiles_destroy(struct f
+       for (; count; --count, ++epfile) {
+               BUG_ON(mutex_is_locked(&epfile->mutex));
++              __ffs_epfile_read_buffer_free(epfile);
+               if (epfile->dentry) {
+                       d_delete(epfile->dentry);
+                       dput(epfile->dentry);
diff --git a/queue-6.6/usb-iowarrior-remove-inherent-race-with-minor-number.patch b/queue-6.6/usb-iowarrior-remove-inherent-race-with-minor-number.patch
new file mode 100644 (file)
index 0000000..9821697
--- /dev/null
@@ -0,0 +1,109 @@
+From stable+bounces-277483-greg=kroah.com@vger.kernel.org Sun Jul 19 17:17:20 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 19 Jul 2026 11:17:13 -0400
+Subject: usb: iowarrior: remove inherent race with minor number
+To: stable@vger.kernel.org
+Cc: Oliver Neukum <oneukum@suse.com>, Greg Kroah-Hartman <gregkh@linuxfoundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260719151714.124320-1-sashal@kernel.org>
+
+From: Oliver Neukum <oneukum@suse.com>
+
+[ Upstream commit 56dd29088c9d9510c48a8ebad2465248fde36551 ]
+
+The driver saves the minor number it gets upon registration
+in its descriptor for debugging purposes. However, there is
+inevitably a window between registration and saving the correct
+minor in a descriptor. During this window the debugging output
+will be wrong.
+As wrong debug output is worse than no debug output, just
+remove it.
+
+Signed-off-by: Oliver Neukum <oneukum@suse.com>
+Link: https://patch.msgid.link/20260312094619.1590556-1-oneukum@suse.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Stable-dep-of: c602254ba4c1 ("USB: iowarrior: fix use-after-free on disconnect race")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/usb/misc/iowarrior.c |   17 +++--------------
+ 1 file changed, 3 insertions(+), 14 deletions(-)
+
+--- a/drivers/usb/misc/iowarrior.c
++++ b/drivers/usb/misc/iowarrior.c
+@@ -74,7 +74,6 @@ struct iowarrior {
+       struct mutex mutex;                     /* locks this structure */
+       struct usb_device *udev;                /* save off the usb device pointer */
+       struct usb_interface *interface;        /* the interface for this device */
+-      unsigned char minor;                    /* the starting minor number for this device */
+       struct usb_endpoint_descriptor *int_out_endpoint;       /* endpoint for reading (needed for IOW56 only) */
+       struct usb_endpoint_descriptor *int_in_endpoint;        /* endpoint for reading */
+       struct urb *int_in_urb;         /* the urb for reading data */
+@@ -246,7 +245,6 @@ static void iowarrior_write_callback(str
+  */
+ static inline void iowarrior_delete(struct iowarrior *dev)
+ {
+-      dev_dbg(&dev->interface->dev, "minor %d\n", dev->minor);
+       kfree(dev->int_in_buffer);
+       usb_free_urb(dev->int_in_urb);
+       kfree(dev->read_queue);
+@@ -297,9 +295,6 @@ static ssize_t iowarrior_read(struct fil
+               goto exit;
+       }
+-      dev_dbg(&dev->interface->dev, "minor %d, count = %zd\n",
+-              dev->minor, count);
+-
+       /* read count must be packet size (+ time stamp) */
+       if ((count != dev->report_size)
+           && (count != (dev->report_size + 1))) {
+@@ -379,8 +374,6 @@ static ssize_t iowarrior_write(struct fi
+               retval = -ENODEV;
+               goto exit;
+       }
+-      dev_dbg(&dev->interface->dev, "minor %d, count = %zd\n",
+-              dev->minor, count);
+       /* if count is 0 we're already done */
+       if (count == 0) {
+               retval = 0;
+@@ -523,9 +516,6 @@ static long iowarrior_ioctl(struct file
+               goto error_out;
+       }
+-      dev_dbg(&dev->interface->dev, "minor %d, cmd 0x%.4x, arg %ld\n",
+-              dev->minor, cmd, arg);
+-
+       retval = 0;
+       io_res = 0;
+       switch (cmd) {
+@@ -672,8 +662,6 @@ static int iowarrior_release(struct inod
+       if (!dev)
+               return -ENODEV;
+-      dev_dbg(&dev->interface->dev, "minor %d\n", dev->minor);
+-
+       /* lock our device */
+       mutex_lock(&dev->mutex);
+@@ -776,6 +764,7 @@ static int iowarrior_probe(struct usb_in
+       struct usb_host_interface *iface_desc;
+       int retval = -ENOMEM;
+       int res;
++      int minor;
+       /* allocate memory for our device state and initialize it */
+       dev = kzalloc(sizeof(struct iowarrior), GFP_KERNEL);
+@@ -891,12 +880,12 @@ static int iowarrior_probe(struct usb_in
+               goto error;
+       }
+-      dev->minor = interface->minor;
++      minor = interface->minor;
+       /* let the user know what node this device is now attached to */
+       dev_info(&interface->dev, "IOWarrior product=0x%x, serial=%s interface=%d "
+                "now attached to iowarrior%d\n", dev->product_id, dev->chip_serial,
+-               iface_desc->desc.bInterfaceNumber, dev->minor - IOWARRIOR_MINOR_BASE);
++               iface_desc->desc.bInterfaceNumber, minor - IOWARRIOR_MINOR_BASE);
+       return retval;
+ error:
diff --git a/queue-6.6/usb-typec-tcpm-fix-vdm-type-for-enter-mode-commands.patch b/queue-6.6/usb-typec-tcpm-fix-vdm-type-for-enter-mode-commands.patch
new file mode 100644 (file)
index 0000000..4d4f271
--- /dev/null
@@ -0,0 +1,39 @@
+From stable+bounces-277507-greg=kroah.com@vger.kernel.org Sun Jul 19 18:57:19 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 19 Jul 2026 12:57:13 -0400
+Subject: usb: typec: tcpm: Fix VDM type for Enter Mode commands
+To: stable@vger.kernel.org
+Cc: Andy Yan <andyshrk@163.com>, stable <stable@kernel.org>, Heikki Krogerus <heikki.krogerus@linux.intel.com>, Greg Kroah-Hartman <gregkh@linuxfoundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260719165713.748572-1-sashal@kernel.org>
+
+From: Andy Yan <andyshrk@163.com>
+
+[ Upstream commit 9cff680e47632b7723cb19f9c5e63669063c3417 ]
+
+VDO() second parameter is VDM type (bit 15): 1 for SVDM, 0 for UVDM.
+Using 'vdo ? 2 : 1' corrupts SVID low bit when vdo is non-NULL
+(2 << 15 = BIT(16)). Enter Mode is always SVDM, hardcode to 1.
+
+Fixes: 8face9aa57c8 ("usb: typec: Add parameter for the VDO to typec_altmode_enter()")
+Cc: stable <stable@kernel.org>
+Signed-off-by: Andy Yan <andyshrk@163.com>
+Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Link: https://patch.msgid.link/20260604105059.18750-1-andyshrk@163.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/usb/typec/tcpm/tcpm.c |    2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/usb/typec/tcpm/tcpm.c
++++ b/drivers/usb/typec/tcpm/tcpm.c
+@@ -2237,7 +2237,7 @@ static int tcpm_altmode_enter(struct typ
+       if (svdm_version < 0)
+               return svdm_version;
+-      header = VDO(altmode->svid, vdo ? 2 : 1, svdm_version, CMD_ENTER_MODE);
++      header = VDO(altmode->svid, 1, svdm_version, CMD_ENTER_MODE);
+       header |= VDO_OPOS(altmode->mode);
+       tcpm_queue_vdm_unlocked(port, header, vdo, vdo ? 1 : 0);
diff --git a/queue-6.6/vfio-mlx5-fix-racy-bitfields-and-tighten-struct-layout.patch b/queue-6.6/vfio-mlx5-fix-racy-bitfields-and-tighten-struct-layout.patch
new file mode 100644 (file)
index 0000000..47ca5ad
--- /dev/null
@@ -0,0 +1,92 @@
+From stable+bounces-274304-greg=kroah.com@vger.kernel.org Tue Jul 14 16:51:27 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 10:46:37 -0400
+Subject: vfio/mlx5: Fix racy bitfields and tighten struct layout
+To: stable@vger.kernel.org
+Cc: Alex Williamson <alex.williamson@nvidia.com>, Yishai Hadas <yishaih@nvidia.com>, Kevin Tian <kevin.tian@intel.com>, Alex Williamson <alex@shazbot.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260714144637.2781948-1-sashal@kernel.org>
+
+From: Alex Williamson <alex.williamson@nvidia.com>
+
+[ Upstream commit f2365a63b02ddea32e7db78b742c2503ec7b81f1 ]
+
+Bitfield operations are not atomic, they use a read-modify-write
+pattern, therefore we should be careful not to pack bitfields that
+can be concurrently updated into the same storage unit.
+
+This split takes a binary approach: flags that are only modified
+pre/post open/close remain bitfields, flags modified from user
+action, including actions that reach across to another device (ex.
+reset) use dedicated storage units.
+
+Note mlx5_vhca_page_tracker.status is relocated to fill the alignment
+hole this split exposes.
+
+Bitfield justifications:
+
+  migrate_cap: written only in mlx5vf_cmd_set_migratable() at probe
+  chunk_mode: written only in mlx5vf_cmd_set_migratable() at probe
+  mig_state_cap: written only in mlx5vf_cmd_set_migratable() at probe
+
+Dedicated storage units:
+
+  mdev_detach: written in the VF attach/detach event notifier
+               mlx5fv_vf_event() at runtime
+  log_active: written in mlx5vf_start_page_tracker()/
+              mlx5vf_stop_page_tracker() during runtime dirty tracking
+  deferred_reset: written in mlx5vf_state_mutex_unlock()/
+                  mlx5vf_pci_aer_reset_done() during runtime reset handling
+  is_err: set by tracker error handling and dirty-log polling at runtime
+  object_changed: set by tracker event handling and cleared by dirty-log
+                  polling at runtime
+
+Fixes: 61a2f1460fd0 ("vfio/mlx5: Manage the VF attach/detach callback from the PF")
+Fixes: 79c3cf279926 ("vfio/mlx5: Init QP based resources for dirty tracking")
+Fixes: f886473071d6 ("vfio/mlx5: Add support for tracker object change event")
+Cc: Yishai Hadas <yishaih@nvidia.com>
+Cc: stable@vger.kernel.org
+Assisted-by: Claude:claude-opus-4-8
+Signed-off-by: Alex Williamson <alex.williamson@nvidia.com>
+Reviewed-by: Kevin Tian <kevin.tian@intel.com>
+Link: https://lore.kernel.org/r/20260615191241.688297-5-alex.williamson@nvidia.com
+Signed-off-by: Alex Williamson <alex@shazbot.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/vfio/pci/mlx5/cmd.h |   13 ++++++++-----
+ 1 file changed, 8 insertions(+), 5 deletions(-)
+
+--- a/drivers/vfio/pci/mlx5/cmd.h
++++ b/drivers/vfio/pci/mlx5/cmd.h
+@@ -147,23 +147,26 @@ struct mlx5_vhca_qp {
+ struct mlx5_vhca_page_tracker {
+       u32 id;
+       u32 pdn;
+-      u8 is_err:1;
++      /* Flags modified at runtime - dedicated storage unit */
++      u8 is_err;
++      int status;
+       struct mlx5_uars_page *uar;
+       struct mlx5_vhca_cq cq;
+       struct mlx5_vhca_qp *host_qp;
+       struct mlx5_vhca_qp *fw_qp;
+       struct mlx5_nb nb;
+-      int status;
+ };
+ struct mlx5vf_pci_core_device {
+       struct vfio_pci_core_device core_device;
+       int vf_id;
+       u16 vhca_id;
++      /* Flags only modified on setup/release - bitfield ok */
+       u8 migrate_cap:1;
+-      u8 deferred_reset:1;
+-      u8 mdev_detach:1;
+-      u8 log_active:1;
++      /* Flags modified at runtime - dedicated storage unit */
++      u8 mdev_detach;
++      u8 log_active;
++      u8 deferred_reset;
+       struct completion tracker_comp;
+       /* protect migration state */
+       struct mutex state_mutex;
diff --git a/queue-6.6/writeback-avoid-contention-on-wb-list_lock-when-switching-inodes.patch b/queue-6.6/writeback-avoid-contention-on-wb-list_lock-when-switching-inodes.patch
new file mode 100644 (file)
index 0000000..7e5f91e
--- /dev/null
@@ -0,0 +1,294 @@
+From stable+bounces-276794-greg=kroah.com@vger.kernel.org Thu Jul 16 19:50:37 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Jul 2026 13:50:29 -0400
+Subject: writeback: Avoid contention on wb->list_lock when switching inodes
+To: stable@vger.kernel.org
+Cc: Jan Kara <jack@suse.cz>, Tejun Heo <tj@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260716175030.804337-1-sashal@kernel.org>
+
+From: Jan Kara <jack@suse.cz>
+
+[ Upstream commit e1b849cfa6b61f1c866a908c9e8dd9b5aaab820b ]
+
+There can be multiple inode switch works that are trying to switch
+inodes to / from the same wb. This can happen in particular if some
+cgroup exits which owns many (thousands) inodes and we need to switch
+them all. In this case several inode_switch_wbs_work_fn() instances will
+be just spinning on the same wb->list_lock while only one of them makes
+forward progress. This wastes CPU cycles and quickly leads to softlockup
+reports and unusable system.
+
+Instead of running several inode_switch_wbs_work_fn() instances in
+parallel switching to the same wb and contending on wb->list_lock, run
+just one work item per wb and manage a queue of isw items switching to
+this wb.
+
+Acked-by: Tejun Heo <tj@kernel.org>
+Signed-off-by: Jan Kara <jack@suse.cz>
+Stable-dep-of: cba38ec4cbd3 ("writeback: fix race between cgroup_writeback_umount() and inode_switch_wbs()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/fs-writeback.c                |   99 ++++++++++++++++++++++++---------------
+ include/linux/backing-dev-defs.h |    4 +
+ include/linux/writeback.h        |    2 
+ mm/backing-dev.c                 |    5 +
+ 4 files changed, 74 insertions(+), 36 deletions(-)
+
+--- a/fs/fs-writeback.c
++++ b/fs/fs-writeback.c
+@@ -345,7 +345,8 @@ static struct bdi_writeback *inode_to_wb
+ }
+ struct inode_switch_wbs_context {
+-      struct rcu_work         work;
++      /* List of queued switching contexts for the wb */
++      struct llist_node       list;
+       /*
+        * Multiple inodes can be switched at once.  The switching procedure
+@@ -355,7 +356,6 @@ struct inode_switch_wbs_context {
+        * array embedded into struct inode_switch_wbs_context.  Otherwise
+        * an inode could be left in a non-consistent state.
+        */
+-      struct bdi_writeback    *new_wb;
+       struct inode            *inodes[];
+ };
+@@ -464,13 +464,11 @@ skip_switch:
+       return switched;
+ }
+-static void inode_switch_wbs_work_fn(struct work_struct *work)
++static void process_inode_switch_wbs(struct bdi_writeback *new_wb,
++                                   struct inode_switch_wbs_context *isw)
+ {
+-      struct inode_switch_wbs_context *isw =
+-              container_of(to_rcu_work(work), struct inode_switch_wbs_context, work);
+       struct backing_dev_info *bdi = inode_to_bdi(isw->inodes[0]);
+       struct bdi_writeback *old_wb = isw->inodes[0]->i_wb;
+-      struct bdi_writeback *new_wb = isw->new_wb;
+       unsigned long nr_switched = 0;
+       struct inode **inodep;
+@@ -530,6 +528,38 @@ relock:
+       atomic_dec(&isw_nr_in_flight);
+ }
++void inode_switch_wbs_work_fn(struct work_struct *work)
++{
++      struct bdi_writeback *new_wb = container_of(work, struct bdi_writeback,
++                                                  switch_work);
++      struct inode_switch_wbs_context *isw, *next_isw;
++      struct llist_node *list;
++
++      /*
++       * Grab out reference to wb so that it cannot get freed under us
++       * after we process all the isw items.
++       */
++      wb_get(new_wb);
++      while (1) {
++              list = llist_del_all(&new_wb->switch_wbs_ctxs);
++              /* Nothing to do? */
++              if (!list)
++                      break;
++              /*
++               * In addition to synchronizing among switchers, I_WB_SWITCH
++               * tells the RCU protected stat update paths to grab the i_page
++               * lock so that stat transfer can synchronize against them.
++               * Let's continue after I_WB_SWITCH is guaranteed to be
++               * visible.
++               */
++              synchronize_rcu();
++
++              llist_for_each_entry_safe(isw, next_isw, list, list)
++                      process_inode_switch_wbs(new_wb, isw);
++      }
++      wb_put(new_wb);
++}
++
+ static bool inode_prepare_wbs_switch(struct inode *inode,
+                                    struct bdi_writeback *new_wb)
+ {
+@@ -559,6 +589,13 @@ static bool inode_prepare_wbs_switch(str
+       return true;
+ }
++static void wb_queue_isw(struct bdi_writeback *wb,
++                       struct inode_switch_wbs_context *isw)
++{
++      if (llist_add(&isw->list, &wb->switch_wbs_ctxs))
++              queue_work(isw_wq, &wb->switch_work);
++}
++
+ /**
+  * inode_switch_wbs - change the wb association of an inode
+  * @inode: target inode
+@@ -572,6 +609,7 @@ static void inode_switch_wbs(struct inod
+       struct backing_dev_info *bdi = inode_to_bdi(inode);
+       struct cgroup_subsys_state *memcg_css;
+       struct inode_switch_wbs_context *isw;
++      struct bdi_writeback *new_wb = NULL;
+       /* noop if seems to be already in progress */
+       if (inode->i_state & I_WB_SWITCH)
+@@ -596,40 +634,34 @@ static void inode_switch_wbs(struct inod
+       if (!memcg_css)
+               goto out_free;
+-      isw->new_wb = wb_get_create(bdi, memcg_css, GFP_ATOMIC);
++      new_wb = wb_get_create(bdi, memcg_css, GFP_ATOMIC);
+       css_put(memcg_css);
+-      if (!isw->new_wb)
++      if (!new_wb)
+               goto out_free;
+-      if (!inode_prepare_wbs_switch(inode, isw->new_wb))
++      if (!inode_prepare_wbs_switch(inode, new_wb))
+               goto out_free;
+       isw->inodes[0] = inode;
+-      /*
+-       * In addition to synchronizing among switchers, I_WB_SWITCH tells
+-       * the RCU protected stat update paths to grab the i_page
+-       * lock so that stat transfer can synchronize against them.
+-       * Let's continue after I_WB_SWITCH is guaranteed to be visible.
+-       */
+-      INIT_RCU_WORK(&isw->work, inode_switch_wbs_work_fn);
+-      queue_rcu_work(isw_wq, &isw->work);
++      wb_queue_isw(new_wb, isw);
+       return;
+ out_free:
+       atomic_dec(&isw_nr_in_flight);
+-      if (isw->new_wb)
+-              wb_put(isw->new_wb);
++      if (new_wb)
++              wb_put(new_wb);
+       kfree(isw);
+ }
+-static bool isw_prepare_wbs_switch(struct inode_switch_wbs_context *isw,
++static bool isw_prepare_wbs_switch(struct bdi_writeback *new_wb,
++                                 struct inode_switch_wbs_context *isw,
+                                  struct list_head *list, int *nr)
+ {
+       struct inode *inode;
+       list_for_each_entry(inode, list, i_io_list) {
+-              if (!inode_prepare_wbs_switch(inode, isw->new_wb))
++              if (!inode_prepare_wbs_switch(inode, new_wb))
+                       continue;
+               isw->inodes[*nr] = inode;
+@@ -653,6 +685,7 @@ bool cleanup_offline_cgwb(struct bdi_wri
+ {
+       struct cgroup_subsys_state *memcg_css;
+       struct inode_switch_wbs_context *isw;
++      struct bdi_writeback *new_wb;
+       int nr;
+       bool restart = false;
+@@ -665,12 +698,12 @@ bool cleanup_offline_cgwb(struct bdi_wri
+       for (memcg_css = wb->memcg_css->parent; memcg_css;
+            memcg_css = memcg_css->parent) {
+-              isw->new_wb = wb_get_create(wb->bdi, memcg_css, GFP_KERNEL);
+-              if (isw->new_wb)
++              new_wb = wb_get_create(wb->bdi, memcg_css, GFP_KERNEL);
++              if (new_wb)
+                       break;
+       }
+-      if (unlikely(!isw->new_wb))
+-              isw->new_wb = &wb->bdi->wb; /* wb_get() is noop for bdi's wb */
++      if (unlikely(!new_wb))
++              new_wb = &wb->bdi->wb; /* wb_get() is noop for bdi's wb */
+       nr = 0;
+       spin_lock(&wb->list_lock);
+@@ -682,27 +715,21 @@ bool cleanup_offline_cgwb(struct bdi_wri
+        * bandwidth restrictions, as writeback of inode metadata is not
+        * accounted for.
+        */
+-      restart = isw_prepare_wbs_switch(isw, &wb->b_attached, &nr);
++      restart = isw_prepare_wbs_switch(new_wb, isw, &wb->b_attached, &nr);
+       if (!restart)
+-              restart = isw_prepare_wbs_switch(isw, &wb->b_dirty_time, &nr);
++              restart = isw_prepare_wbs_switch(new_wb, isw, &wb->b_dirty_time,
++                                               &nr);
+       spin_unlock(&wb->list_lock);
+       /* no attached inodes? bail out */
+       if (nr == 0) {
+               atomic_dec(&isw_nr_in_flight);
+-              wb_put(isw->new_wb);
++              wb_put(new_wb);
+               kfree(isw);
+               return restart;
+       }
+-      /*
+-       * In addition to synchronizing among switchers, I_WB_SWITCH tells
+-       * the RCU protected stat update paths to grab the i_page
+-       * lock so that stat transfer can synchronize against them.
+-       * Let's continue after I_WB_SWITCH is guaranteed to be visible.
+-       */
+-      INIT_RCU_WORK(&isw->work, inode_switch_wbs_work_fn);
+-      queue_rcu_work(isw_wq, &isw->work);
++      wb_queue_isw(new_wb, isw);
+       return restart;
+ }
+--- a/include/linux/backing-dev-defs.h
++++ b/include/linux/backing-dev-defs.h
+@@ -152,6 +152,10 @@ struct bdi_writeback {
+       struct list_head blkcg_node;    /* anchored at blkcg->cgwb_list */
+       struct list_head b_attached;    /* attached inodes, protected by list_lock */
+       struct list_head offline_node;  /* anchored at offline_cgwbs */
++      struct work_struct switch_work; /* work used to perform inode switching
++                                       * to this wb */
++      struct llist_head switch_wbs_ctxs;      /* queued contexts for
++                                               * writeback switching */
+       union {
+               struct work_struct release_work;
+--- a/include/linux/writeback.h
++++ b/include/linux/writeback.h
+@@ -283,6 +283,8 @@ static inline void wbc_init_bio(struct w
+               bio_associate_blkg_from_css(bio, wbc->wb->blkcg_css);
+ }
++void inode_switch_wbs_work_fn(struct work_struct *work);
++
+ #else /* CONFIG_CGROUP_WRITEBACK */
+ static inline void inode_attach_wb(struct inode *inode, struct folio *folio)
+--- a/mm/backing-dev.c
++++ b/mm/backing-dev.c
+@@ -545,6 +545,7 @@ static void cgwb_release_workfn(struct w
+       wb_exit(wb);
+       bdi_put(bdi);
+       WARN_ON_ONCE(!list_empty(&wb->b_attached));
++      WARN_ON_ONCE(work_pending(&wb->switch_work));
+       call_rcu(&wb->rcu, cgwb_free_rcu);
+ }
+@@ -621,6 +622,8 @@ static int cgwb_create(struct backing_de
+       wb->memcg_css = memcg_css;
+       wb->blkcg_css = blkcg_css;
+       INIT_LIST_HEAD(&wb->b_attached);
++      INIT_WORK(&wb->switch_work, inode_switch_wbs_work_fn);
++      init_llist_head(&wb->switch_wbs_ctxs);
+       INIT_WORK(&wb->release_work, cgwb_release_workfn);
+       set_bit(WB_registered, &wb->state);
+       bdi_get(bdi);
+@@ -751,6 +754,8 @@ static int cgwb_bdi_init(struct backing_
+       if (!ret) {
+               bdi->wb.memcg_css = &root_mem_cgroup->css;
+               bdi->wb.blkcg_css = blkcg_root_css;
++              INIT_WORK(&bdi->wb.switch_work, inode_switch_wbs_work_fn);
++              init_llist_head(&bdi->wb.switch_wbs_ctxs);
+       }
+       return ret;
+ }
diff --git a/queue-6.6/writeback-fix-race-between-cgroup_writeback_umount-and-inode_switch_wbs.patch b/queue-6.6/writeback-fix-race-between-cgroup_writeback_umount-and-inode_switch_wbs.patch
new file mode 100644 (file)
index 0000000..2b9676b
--- /dev/null
@@ -0,0 +1,181 @@
+From stable+bounces-276795-greg=kroah.com@vger.kernel.org Thu Jul 16 19:56:02 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Jul 2026 13:50:30 -0400
+Subject: writeback: fix race between cgroup_writeback_umount() and inode_switch_wbs()
+To: stable@vger.kernel.org
+Cc: Baokun Li <libaokun@linux.alibaba.com>, Jan Kara <jack@suse.cz>, Tejun Heo <tj@kernel.org>, "Christian Brauner (Amutable)" <brauner@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260716175030.804337-2-sashal@kernel.org>
+
+From: Baokun Li <libaokun@linux.alibaba.com>
+
+[ Upstream commit cba38ec4cbd3a7b8b942a8d52531a05be8a9ff0d ]
+
+When a container exits, the following BUG_ON() is occasionally triggered:
+
+==================================================================
+ VFS: Busy inodes after unmount of sdb (ext4)
+ ------------[ cut here ]------------
+ kernel BUG at fs/super.c:695!
+ CPU: 3 PID: 6 Comm: containerd-shim Tainted: G OE K 6.6 #1
+ pstate: 63400009 (nZCv daif +PAN -UAO +TCO +DIT -SSBS BTYPE=--)
+ pc : generic_shutdown_super+0xf0/0x100
+ lr : generic_shutdown_super+0xf0/0x100
+ Call trace:
+  generic_shutdown_super+0xf0/0x100
+  kill_block_super+0x20/0x48
+  ext4_kill_sb+0x28/0x60
+  deactivate_locked_super+0x54/0x130
+  deactivate_super+0x84/0xa0
+  cleanup_mnt+0xa4/0x140
+  __cleanup_mnt+0x18/0x28
+  task_work_run+0x78/0xe0
+  do_notify_resume+0x204/0x240
+==================================================================
+
+The root cause is a race between cgroup_writeback_umount() and
+inode_switch_wbs()/cleanup_offline_cgwb(). There is a window between
+inode_prepare_wbs_switch() returning true and the subsequent
+wb_queue_isw() call. Following is the process that triggers the issue:
+
+      CPU A (umount)           |          CPU B (writeback)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                                 inode_switch_wbs/cleanup_offline_cgwb
+                                  atomic_inc(&isw_nr_in_flight)
+                                  inode_prepare_wbs_switch
+                                   -> passes SB_ACTIVE check
+                                   __iget(inode)
+ generic_shutdown_super
+  sb->s_flags &= ~SB_ACTIVE
+  cgroup_writeback_umount(sb)
+   smp_mb()
+   atomic_read(&isw_nr_in_flight)
+   rcu_barrier()
+    -> no pending RCU callbacks
+   flush_workqueue(isw_wq)
+    -> nothing queued, returns
+  evict_inodes(sb)
+   -> Inode skipped as isw still holds a ref.
+  sop->put_super(sb)
+   /* destroys percpu counters */
+  -> VFS: Busy inodes after unmount!
+                                  wb_queue_isw()
+                                   queue_work(isw_wq, ...)
+                                  /* later in work function */
+                                  inode_switch_wbs_work_fn
+                                   process_inode_switch_wbs
+                                    iput() -> evict
+                                     percpu_counter_dec() // UAF!
+
+Fix this by extending the RCU read-side critical section in
+inode_switch_wbs() and cleanup_offline_cgwb() to cover from
+inode_prepare_wbs_switch() through wb_queue_isw().  Since there is
+no sleep in this window, rcu_read_lock() can be used.  Then add a
+synchronize_rcu() in cgroup_writeback_umount() before the existing
+rcu_barrier(), so that all in-flight switchers that have passed the
+SB_ACTIVE check have completed queue_work() before flush_workqueue()
+is called.
+
+The existing rcu_barrier() is intentionally retained so this fix can
+be backported unchanged to stable kernels (5.10.y, 6.6.y, ...) that
+still queue switches via queue_rcu_work(). It is a no-op on current
+mainline (since commit e1b849cfa6b6 ("writeback: Avoid contention on
+wb->list_lock when switching inodes")) and is removed in a follow-up
+patch.
+
+Fixes: a1a0e23e4903 ("writeback: flush inode cgroup wb switches instead of pinning super_block")
+Cc: stable@vger.kernel.org
+Suggested-by: Jan Kara <jack@suse.cz>
+Link: https://lore.kernel.org/all/mxnjq2l6guusfchvauxr3v7c4bwjasybxlleqbbh4efloeqspz@iqylk76ohufz
+Reviewed-by: Jan Kara <jack@suse.cz>
+Signed-off-by: Baokun Li <libaokun@linux.alibaba.com>
+Link: https://patch.msgid.link/20260521095016.2791354-2-libaokun@linux.alibaba.com
+Acked-by: Tejun Heo <tj@kernel.org>
+Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/fs-writeback.c |   31 +++++++++++++++++++++++++++++--
+ 1 file changed, 29 insertions(+), 2 deletions(-)
+
+--- a/fs/fs-writeback.c
++++ b/fs/fs-writeback.c
+@@ -625,12 +625,19 @@ static void inode_switch_wbs(struct inod
+       atomic_inc(&isw_nr_in_flight);
+-      /* find and pin the new wb */
++      /*
++       * Paired with synchronize_rcu() in cgroup_writeback_umount():
++       * holding rcu_read_lock across inode_prepare_wbs_switch()
++       * (covering the SB_ACTIVE check and the inode grab) and
++       * wb_queue_isw() ensures synchronize_rcu() cannot return until
++       * the work is queued, so the subsequent flush_workqueue() will
++       * wait for the switch.
++       */
+       rcu_read_lock();
++      /* find and pin the new wb */
+       memcg_css = css_from_id(new_wb_id, &memory_cgrp_subsys);
+       if (memcg_css && !css_tryget(memcg_css))
+               memcg_css = NULL;
+-      rcu_read_unlock();
+       if (!memcg_css)
+               goto out_free;
+@@ -645,9 +652,11 @@ static void inode_switch_wbs(struct inod
+       isw->inodes[0] = inode;
+       wb_queue_isw(new_wb, isw);
++      rcu_read_unlock();
+       return;
+ out_free:
++      rcu_read_unlock();
+       atomic_dec(&isw_nr_in_flight);
+       if (new_wb)
+               wb_put(new_wb);
+@@ -706,6 +715,14 @@ bool cleanup_offline_cgwb(struct bdi_wri
+               new_wb = &wb->bdi->wb; /* wb_get() is noop for bdi's wb */
+       nr = 0;
++      /*
++       * Paired with synchronize_rcu() in cgroup_writeback_umount().
++       * Holding rcu_read_lock across the SB_ACTIVE check, the inode grab
++       * and wb_queue_isw() ensures synchronize_rcu() cannot return until
++       * the work is queued, so the subsequent flush_workqueue() will wait
++       * for the switch.
++       */
++      rcu_read_lock();
+       spin_lock(&wb->list_lock);
+       /*
+        * In addition to the inodes that have completed writeback, also switch
+@@ -723,6 +740,7 @@ bool cleanup_offline_cgwb(struct bdi_wri
+       /* no attached inodes? bail out */
+       if (nr == 0) {
++              rcu_read_unlock();
+               atomic_dec(&isw_nr_in_flight);
+               wb_put(new_wb);
+               kfree(isw);
+@@ -730,6 +748,7 @@ bool cleanup_offline_cgwb(struct bdi_wri
+       }
+       wb_queue_isw(new_wb, isw);
++      rcu_read_unlock();
+       return restart;
+ }
+@@ -1163,6 +1182,14 @@ void cgroup_writeback_umount(void)
+       if (atomic_read(&isw_nr_in_flight)) {
+               /*
++               * Paired with rcu_read_lock() in inode_switch_wbs() and
++               * cleanup_offline_cgwb().  synchronize_rcu() waits for any
++               * in-flight switcher that already passed the SB_ACTIVE check
++               * to finish queueing its work, so flush_workqueue() below
++               * will then drain it.
++               */
++              synchronize_rcu();
++              /*
+                * Use rcu_barrier() to wait for all pending callbacks to
+                * ensure that all in-flight wb switches are in the workqueue.
+                */