]> git.ipfire.org Git - thirdparty/kernel/stable-queue.git/commitdiff
6.12-stable patches
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 30 Jul 2026 12:50:11 +0000 (14:50 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 30 Jul 2026 12:50:11 +0000 (14:50 +0200)
added patches:
afs-drop-the-net-parameter-from-afs_unuse_cell.patch
afs-fix-afs_server-ref-accounting.patch
afs-fix-dynamic-lookup-to-fail-on-cell-lookup-failure.patch
afs-fix-lack-of-locking-around-modifications-of-net-cells_dyn_ino.patch
afs-improve-server-refcount-active-count-tracing.patch
afs-make-afs_lookup_cell-take-a-trace-note.patch
afs-simplify-cell-record-handling.patch
afs-use-the-per-peer-app-data-provided-by-rxrpc.patch
alsa-hda-conexant-remove-mic-bias-threshold-override.patch
alsa-hda-fix-cached-processing-coefficient-verbs.patch
arm64-dts-qcom-correct-rbr-opp-entry.patch
arm64-dts-qcom-hamoa-fix-opp-tables-for-all-displayport-controllers.patch
asoc-mediatek-mt8183-afe-pcm-shorten-memif_data-table-using-macros.patch
asoc-mediatek-mt8183-afe-pcm-support-32-bit-dma-addresses.patch
asoc-mediatek-mt8183-afe-pcm-use-local-dev-pointer-in-driver-callbacks.patch
asoc-mediatek-mt8183-check-runtime-resume-during-probe.patch
asoc-mediatek-mt8192-afe-pcm-simplify-probe-with-local-dev-variable.patch
asoc-mediatek-mt8192-check-runtime-resume-during-probe.patch
ata-libata-core-reject-an-invalid-concurrent-positioning-ranges-count.patch
audit-fix-recursive-locking-deadlock-in-audit_dupe_exe.patch
audit-use-unsigned-int-instead-of-unsigned.patch
audit-widen-ino-fields-to-u64.patch
block-add-helper-add_disk_final.patch
block-remove-redundant-gd_need_part_scan-in-add_disk_final.patch
bluetooth-add-pa_link-to-distinguish-big-sync-and-pa-sync-connections.patch
bluetooth-hci_core-fix-not-accounting-for-bis-cis-pa-links-separately.patch
bootconfig-fix-null-pointer-arithmetic-in-xbc_snprint_cmdline.patch
bootconfig-move-xbc_snprint_cmdline-to-lib-bootconfig.c.patch
bpf-refactor-acquire-release-_reference_state.patch
bpf-refactor-check_ctx_access.patch
bpf-reset-register-bounds-before-narrowing-retval-range-in-check_mem_access.patch
cleanup-add-a-scoped-version-of-class.patch
cleanup-fix-scoped_class.patch
cred-add-kernel_cred-helper.patch
cred-add-scoped_with_kernel_creds.patch
dm-avoid-leaking-the-caller-s-thread-keyring-via-the-table-device-file.patch
dm-integrity-fix-leaking-uninitialized-kernel-memory.patch
dma-dw-edma-fix-build-warning-in-dw_edma_pcie_probe.patch
dmaengine-dw-edma-fix-confusing-cleanup.h-syntax.patch
dmaengine-dw-edma-pcie-reject-devices-without-driver-data.patch
fbcon-rename-struct-fbcon_ops-to-struct-fbcon_par.patch
fbcon-use-correct-type-for-vc_resize-return-value.patch
fpga-dfl-afu-validate-dma-mapping-length-in-afu_dma_map_region.patch
fs-resctrl-fix-double-add-of-pseudo-locked-region-s-rmid-to-free-list.patch
gpio-mt7621-avoid-corruption-of-shared-interrupt-trigger-state.patch
gpu-buddy-bail-out-of-try_harder-when-alignment-cannot-be-honoured.patch
gpu-move-drm-buddy-allocator-one-level-up-part-two.patch
gve-fix-header-buffer-corruption-with-header-split-and-hw-gro.patch
i2c-davinci-unregister-cpufreq-notifier-on-probe-failure.patch
i2c-i801-fix-hardware-state-machine-corruption-in-error-path.patch
i2c-imx-fix-locked-bus-on-smbus-block-read-of-0-atomic.patch
i2c-imx-separate-atomic-dma-and-non-dma-use-case.patch
iommufd-avoid-partial-fault-group-delivery-in-iommufd_fault_fops_read.patch
iommufd-break-the-loop-on-failure-in-iommufd_fault_fops_read.patch
iommufd-reject-invalid-read-count-in-iommufd_fault_fops_read.patch
ipmi-fix-refcount-leak-in-i_ipmi_request.patch
landlock-fix-landlock_scope_signal-bypass-on-the-sigio-path.patch
landlock-prepare-to-use-credential-instead-of-domain-for-fowner.patch
locking-rt-fix-the-incorrect-rcu-protection-in-rt_spin_unlock.patch
mm-hugetlb-fix-hugetlb-cgroup-rsvd-charge-uncharge-mismatch.patch
mm-mm_init-fix-pageblock-migratetype-for-zone_device-compound-pages.patch
mm-prepare-to-move-subsection_map_init-to-mm-sparse-vmemmap.c.patch
mm-sparse-vmemmap-fix-dax-vmemmap-accounting-with-optimization.patch
mm-sparse-vmemmap-fix-vmemmap-accounting-underflow.patch
mmc-vub300-fix-use-after-free-on-disconnect.patch
mmc-vub300-fix-use-after-free-on-probe-failure.patch
mmc-vub300-rename-probe-error-labels.patch
mtd-maps-vmu-flash-fix-fault-in-unaligned-fixup.patch
net-ip6_tunnel-require-cap_net_admin-in-the-device-netns-for-changelink.patch
net-ipa-fix-smem-state-handle-leaks-in-smp2p-init.patch
net-macb-drop-in-flight-tx-skbs-on-close.patch
net-mana-validate-the-packet-length-reported-by-the-nic.patch
net-mlx5-hws-fix-matcher-leak-on-resize-target-setup-failure.patch
net-mlx5-hws-rearrange-to-prevent-forward-declaration.patch
netfilter-bitwise-rename-some-boolean-operation-functions.patch
netfilter-nf_conntrack_sip-remove-net-variable-shadowing.patch
netfilter-nf_conntrack_sip-validate-skb_dst-before-accessing-it.patch
netfilter-nf_tables-remove-register-tracking-infrastructure.patch
netfilter-nf_tables-remove-unused-nft_reduce_is_readonly.patch
netfilter-nft_fib-reject-fib-expression-on-the-netdev-egress-hook.patch
nfsd-pass-nfsd_file-to-nfsd_iter_read.patch
nvmet-auth-reject-short-auth_receive-buffers.patch
nvmet-introduce-nvmet_req_transfer_len.patch
octeontx2-af-cn10k-restrict-vf-lmtline-sharing-to-its-own-pf.patch
octeontx2-annotate-mmio-regions-as-__iomem.patch
octeontx2-pf-clear-stale-mailbox-irq-state-before-request_irq.patch
octeontx2-pf-fix-sqb-pointer-leak-on-init-failure.patch
octeontx2-vf-clear-stale-mailbox-irq-state-before-request_irq.patch
ovl-use-linked-upper-dentry-in-copy-up-tmpfile.patch
remoteproc-xlnx-check-remote-core-state.patch
rxrpc-allow-the-app-to-store-private-data-on-peer-structs.patch
rxrpc-don-t-need-barrier-for-tx_bottom-and-acks_hard_ack.patch
rxrpc-fix-cpu-time-starvation-in-i-o-thread.patch
rxrpc-fix-notification-vs-call-release-vs-recvmsg.patch
rxrpc-fix-socket-notification-race.patch
rxrpc-pull-out-certain-app-callback-funcs-into-an-ops-table.patch
rxrpc-serialize-kernel-accept-preallocation-with-socket-teardown.patch
rxrpc-use-irq-disabling-spinlocks-between-app-and-i-o-thread.patch
sunrpc-add-helpers-to-convert-xdr_buf-byte-ranges-to-scatterlists.patch
sunrpc-allocate-a-separate-bvec-array-for-socket-sends.patch
sunrpc-return-an-error-from-xdr_buf_to_bvec-on-overflow.patch
tcp-decrement-tcp_md5_needed-static-branch.patch
tcp-defer-md5sig_info-kfree-past-rcu-grace-period-in-tcp_connect.patch
thunderbolt-keep-xdomain-reference-during-the-lifetime-of-a-service.patch
thunderbolt-prevent-xdomain-delayed-work-use-after-free-on-disconnect.patch
thunderbolt-remove-service-debugfs-entries-during-unregister.patch
thunderbolt-remove-xdomain-from-the-bus-without-holding-tb-lock.patch
tipc-restrict-socket-queue-dumps-in-enqueue-tracepoints.patch
usb-gadget-fsl-udc-fix-dev_printk-device.patch
usb-gadget-use-str_enable_disable-like-helpers.patch
vduse-avoid-leaking-information-to-userspace.patch
vduse-remove-unused-vaddr-parameter-of-vduse_domain_free_coherent.patch
vduse-take-out-allocations-from-vduse_dev_alloc_coherent.patch
vduse-use-fixed-4kb-bounce-pages-for-non-4kb-page-size.patch
vfs-audit-introduce-kern_path_parent-for-audit.patch
wifi-libertas_tf-fix-use-after-free-in-lbtf_free_adapter.patch
xfrm-nat_keepalive-avoid-double-free-on-send-error.patch
xfrm-use-nested-bh-locking-for-nat_keepalive_sk_ipv.patch
xfs-don-t-replace-the-wrong-part-of-the-cow-fork.patch
xfs-factor-out-xfs_attr3_leaf_init.patch

121 files changed:
queue-6.12/afs-drop-the-net-parameter-from-afs_unuse_cell.patch [new file with mode: 0644]
queue-6.12/afs-fix-afs_server-ref-accounting.patch [new file with mode: 0644]
queue-6.12/afs-fix-dynamic-lookup-to-fail-on-cell-lookup-failure.patch [new file with mode: 0644]
queue-6.12/afs-fix-lack-of-locking-around-modifications-of-net-cells_dyn_ino.patch [new file with mode: 0644]
queue-6.12/afs-improve-server-refcount-active-count-tracing.patch [new file with mode: 0644]
queue-6.12/afs-make-afs_lookup_cell-take-a-trace-note.patch [new file with mode: 0644]
queue-6.12/afs-simplify-cell-record-handling.patch [new file with mode: 0644]
queue-6.12/afs-use-the-per-peer-app-data-provided-by-rxrpc.patch [new file with mode: 0644]
queue-6.12/alsa-hda-conexant-remove-mic-bias-threshold-override.patch [new file with mode: 0644]
queue-6.12/alsa-hda-fix-cached-processing-coefficient-verbs.patch [new file with mode: 0644]
queue-6.12/arm64-dts-qcom-correct-rbr-opp-entry.patch [new file with mode: 0644]
queue-6.12/arm64-dts-qcom-hamoa-fix-opp-tables-for-all-displayport-controllers.patch [new file with mode: 0644]
queue-6.12/asoc-mediatek-mt8183-afe-pcm-shorten-memif_data-table-using-macros.patch [new file with mode: 0644]
queue-6.12/asoc-mediatek-mt8183-afe-pcm-support-32-bit-dma-addresses.patch [new file with mode: 0644]
queue-6.12/asoc-mediatek-mt8183-afe-pcm-use-local-dev-pointer-in-driver-callbacks.patch [new file with mode: 0644]
queue-6.12/asoc-mediatek-mt8183-check-runtime-resume-during-probe.patch [new file with mode: 0644]
queue-6.12/asoc-mediatek-mt8192-afe-pcm-simplify-probe-with-local-dev-variable.patch [new file with mode: 0644]
queue-6.12/asoc-mediatek-mt8192-check-runtime-resume-during-probe.patch [new file with mode: 0644]
queue-6.12/ata-libata-core-reject-an-invalid-concurrent-positioning-ranges-count.patch [new file with mode: 0644]
queue-6.12/audit-fix-recursive-locking-deadlock-in-audit_dupe_exe.patch [new file with mode: 0644]
queue-6.12/audit-use-unsigned-int-instead-of-unsigned.patch [new file with mode: 0644]
queue-6.12/audit-widen-ino-fields-to-u64.patch [new file with mode: 0644]
queue-6.12/block-add-helper-add_disk_final.patch [new file with mode: 0644]
queue-6.12/block-remove-redundant-gd_need_part_scan-in-add_disk_final.patch [new file with mode: 0644]
queue-6.12/bluetooth-add-pa_link-to-distinguish-big-sync-and-pa-sync-connections.patch [new file with mode: 0644]
queue-6.12/bluetooth-hci_core-fix-not-accounting-for-bis-cis-pa-links-separately.patch [new file with mode: 0644]
queue-6.12/bootconfig-fix-null-pointer-arithmetic-in-xbc_snprint_cmdline.patch [new file with mode: 0644]
queue-6.12/bootconfig-move-xbc_snprint_cmdline-to-lib-bootconfig.c.patch [new file with mode: 0644]
queue-6.12/bpf-refactor-acquire-release-_reference_state.patch [new file with mode: 0644]
queue-6.12/bpf-refactor-check_ctx_access.patch [new file with mode: 0644]
queue-6.12/bpf-reset-register-bounds-before-narrowing-retval-range-in-check_mem_access.patch [new file with mode: 0644]
queue-6.12/cleanup-add-a-scoped-version-of-class.patch [new file with mode: 0644]
queue-6.12/cleanup-fix-scoped_class.patch [new file with mode: 0644]
queue-6.12/cred-add-kernel_cred-helper.patch [new file with mode: 0644]
queue-6.12/cred-add-scoped_with_kernel_creds.patch [new file with mode: 0644]
queue-6.12/dm-avoid-leaking-the-caller-s-thread-keyring-via-the-table-device-file.patch [new file with mode: 0644]
queue-6.12/dm-integrity-fix-leaking-uninitialized-kernel-memory.patch [new file with mode: 0644]
queue-6.12/dma-dw-edma-fix-build-warning-in-dw_edma_pcie_probe.patch [new file with mode: 0644]
queue-6.12/dmaengine-dw-edma-fix-confusing-cleanup.h-syntax.patch [new file with mode: 0644]
queue-6.12/dmaengine-dw-edma-pcie-reject-devices-without-driver-data.patch [new file with mode: 0644]
queue-6.12/fbcon-rename-struct-fbcon_ops-to-struct-fbcon_par.patch [new file with mode: 0644]
queue-6.12/fbcon-use-correct-type-for-vc_resize-return-value.patch [new file with mode: 0644]
queue-6.12/fpga-dfl-afu-validate-dma-mapping-length-in-afu_dma_map_region.patch [new file with mode: 0644]
queue-6.12/fs-resctrl-fix-double-add-of-pseudo-locked-region-s-rmid-to-free-list.patch [new file with mode: 0644]
queue-6.12/gpio-mt7621-avoid-corruption-of-shared-interrupt-trigger-state.patch [new file with mode: 0644]
queue-6.12/gpu-buddy-bail-out-of-try_harder-when-alignment-cannot-be-honoured.patch [new file with mode: 0644]
queue-6.12/gpu-move-drm-buddy-allocator-one-level-up-part-two.patch [new file with mode: 0644]
queue-6.12/gve-fix-header-buffer-corruption-with-header-split-and-hw-gro.patch [new file with mode: 0644]
queue-6.12/i2c-davinci-unregister-cpufreq-notifier-on-probe-failure.patch [new file with mode: 0644]
queue-6.12/i2c-i801-fix-hardware-state-machine-corruption-in-error-path.patch [new file with mode: 0644]
queue-6.12/i2c-imx-fix-locked-bus-on-smbus-block-read-of-0-atomic.patch [new file with mode: 0644]
queue-6.12/i2c-imx-separate-atomic-dma-and-non-dma-use-case.patch [new file with mode: 0644]
queue-6.12/iommufd-avoid-partial-fault-group-delivery-in-iommufd_fault_fops_read.patch [new file with mode: 0644]
queue-6.12/iommufd-break-the-loop-on-failure-in-iommufd_fault_fops_read.patch [new file with mode: 0644]
queue-6.12/iommufd-reject-invalid-read-count-in-iommufd_fault_fops_read.patch [new file with mode: 0644]
queue-6.12/ipmi-fix-refcount-leak-in-i_ipmi_request.patch [new file with mode: 0644]
queue-6.12/landlock-fix-landlock_scope_signal-bypass-on-the-sigio-path.patch [new file with mode: 0644]
queue-6.12/landlock-prepare-to-use-credential-instead-of-domain-for-fowner.patch [new file with mode: 0644]
queue-6.12/locking-rt-fix-the-incorrect-rcu-protection-in-rt_spin_unlock.patch [new file with mode: 0644]
queue-6.12/mm-hugetlb-fix-hugetlb-cgroup-rsvd-charge-uncharge-mismatch.patch [new file with mode: 0644]
queue-6.12/mm-mm_init-fix-pageblock-migratetype-for-zone_device-compound-pages.patch [new file with mode: 0644]
queue-6.12/mm-prepare-to-move-subsection_map_init-to-mm-sparse-vmemmap.c.patch [new file with mode: 0644]
queue-6.12/mm-sparse-vmemmap-fix-dax-vmemmap-accounting-with-optimization.patch [new file with mode: 0644]
queue-6.12/mm-sparse-vmemmap-fix-vmemmap-accounting-underflow.patch [new file with mode: 0644]
queue-6.12/mmc-vub300-fix-use-after-free-on-disconnect.patch [new file with mode: 0644]
queue-6.12/mmc-vub300-fix-use-after-free-on-probe-failure.patch [new file with mode: 0644]
queue-6.12/mmc-vub300-rename-probe-error-labels.patch [new file with mode: 0644]
queue-6.12/mtd-maps-vmu-flash-fix-fault-in-unaligned-fixup.patch [new file with mode: 0644]
queue-6.12/net-ip6_tunnel-require-cap_net_admin-in-the-device-netns-for-changelink.patch [new file with mode: 0644]
queue-6.12/net-ipa-fix-smem-state-handle-leaks-in-smp2p-init.patch [new file with mode: 0644]
queue-6.12/net-macb-drop-in-flight-tx-skbs-on-close.patch [new file with mode: 0644]
queue-6.12/net-mana-validate-the-packet-length-reported-by-the-nic.patch [new file with mode: 0644]
queue-6.12/net-mlx5-hws-fix-matcher-leak-on-resize-target-setup-failure.patch [new file with mode: 0644]
queue-6.12/net-mlx5-hws-rearrange-to-prevent-forward-declaration.patch [new file with mode: 0644]
queue-6.12/netfilter-bitwise-rename-some-boolean-operation-functions.patch [new file with mode: 0644]
queue-6.12/netfilter-nf_conntrack_sip-remove-net-variable-shadowing.patch [new file with mode: 0644]
queue-6.12/netfilter-nf_conntrack_sip-validate-skb_dst-before-accessing-it.patch [new file with mode: 0644]
queue-6.12/netfilter-nf_tables-remove-register-tracking-infrastructure.patch [new file with mode: 0644]
queue-6.12/netfilter-nf_tables-remove-unused-nft_reduce_is_readonly.patch [new file with mode: 0644]
queue-6.12/netfilter-nft_fib-reject-fib-expression-on-the-netdev-egress-hook.patch [new file with mode: 0644]
queue-6.12/nfsd-pass-nfsd_file-to-nfsd_iter_read.patch [new file with mode: 0644]
queue-6.12/nvmet-auth-reject-short-auth_receive-buffers.patch [new file with mode: 0644]
queue-6.12/nvmet-introduce-nvmet_req_transfer_len.patch [new file with mode: 0644]
queue-6.12/octeontx2-af-cn10k-restrict-vf-lmtline-sharing-to-its-own-pf.patch [new file with mode: 0644]
queue-6.12/octeontx2-annotate-mmio-regions-as-__iomem.patch [new file with mode: 0644]
queue-6.12/octeontx2-pf-clear-stale-mailbox-irq-state-before-request_irq.patch [new file with mode: 0644]
queue-6.12/octeontx2-pf-fix-sqb-pointer-leak-on-init-failure.patch [new file with mode: 0644]
queue-6.12/octeontx2-vf-clear-stale-mailbox-irq-state-before-request_irq.patch [new file with mode: 0644]
queue-6.12/ovl-use-linked-upper-dentry-in-copy-up-tmpfile.patch [new file with mode: 0644]
queue-6.12/remoteproc-xlnx-check-remote-core-state.patch [new file with mode: 0644]
queue-6.12/rxrpc-allow-the-app-to-store-private-data-on-peer-structs.patch [new file with mode: 0644]
queue-6.12/rxrpc-don-t-need-barrier-for-tx_bottom-and-acks_hard_ack.patch [new file with mode: 0644]
queue-6.12/rxrpc-fix-cpu-time-starvation-in-i-o-thread.patch [new file with mode: 0644]
queue-6.12/rxrpc-fix-notification-vs-call-release-vs-recvmsg.patch [new file with mode: 0644]
queue-6.12/rxrpc-fix-socket-notification-race.patch [new file with mode: 0644]
queue-6.12/rxrpc-pull-out-certain-app-callback-funcs-into-an-ops-table.patch [new file with mode: 0644]
queue-6.12/rxrpc-serialize-kernel-accept-preallocation-with-socket-teardown.patch [new file with mode: 0644]
queue-6.12/rxrpc-use-irq-disabling-spinlocks-between-app-and-i-o-thread.patch [new file with mode: 0644]
queue-6.12/series
queue-6.12/sunrpc-add-helpers-to-convert-xdr_buf-byte-ranges-to-scatterlists.patch [new file with mode: 0644]
queue-6.12/sunrpc-allocate-a-separate-bvec-array-for-socket-sends.patch [new file with mode: 0644]
queue-6.12/sunrpc-return-an-error-from-xdr_buf_to_bvec-on-overflow.patch [new file with mode: 0644]
queue-6.12/tcp-decrement-tcp_md5_needed-static-branch.patch [new file with mode: 0644]
queue-6.12/tcp-defer-md5sig_info-kfree-past-rcu-grace-period-in-tcp_connect.patch [new file with mode: 0644]
queue-6.12/thunderbolt-keep-xdomain-reference-during-the-lifetime-of-a-service.patch [new file with mode: 0644]
queue-6.12/thunderbolt-prevent-xdomain-delayed-work-use-after-free-on-disconnect.patch [new file with mode: 0644]
queue-6.12/thunderbolt-remove-service-debugfs-entries-during-unregister.patch [new file with mode: 0644]
queue-6.12/thunderbolt-remove-xdomain-from-the-bus-without-holding-tb-lock.patch [new file with mode: 0644]
queue-6.12/tipc-restrict-socket-queue-dumps-in-enqueue-tracepoints.patch [new file with mode: 0644]
queue-6.12/usb-gadget-fsl-udc-fix-dev_printk-device.patch [new file with mode: 0644]
queue-6.12/usb-gadget-use-str_enable_disable-like-helpers.patch [new file with mode: 0644]
queue-6.12/vduse-avoid-leaking-information-to-userspace.patch [new file with mode: 0644]
queue-6.12/vduse-remove-unused-vaddr-parameter-of-vduse_domain_free_coherent.patch [new file with mode: 0644]
queue-6.12/vduse-take-out-allocations-from-vduse_dev_alloc_coherent.patch [new file with mode: 0644]
queue-6.12/vduse-use-fixed-4kb-bounce-pages-for-non-4kb-page-size.patch [new file with mode: 0644]
queue-6.12/vfs-audit-introduce-kern_path_parent-for-audit.patch [new file with mode: 0644]
queue-6.12/wifi-libertas_tf-fix-use-after-free-in-lbtf_free_adapter.patch [new file with mode: 0644]
queue-6.12/xfrm-nat_keepalive-avoid-double-free-on-send-error.patch [new file with mode: 0644]
queue-6.12/xfrm-use-nested-bh-locking-for-nat_keepalive_sk_ipv.patch [new file with mode: 0644]
queue-6.12/xfs-don-t-replace-the-wrong-part-of-the-cow-fork.patch [new file with mode: 0644]
queue-6.12/xfs-factor-out-xfs_attr3_leaf_init.patch [new file with mode: 0644]

diff --git a/queue-6.12/afs-drop-the-net-parameter-from-afs_unuse_cell.patch b/queue-6.12/afs-drop-the-net-parameter-from-afs_unuse_cell.patch
new file mode 100644 (file)
index 0000000..f9d8255
--- /dev/null
@@ -0,0 +1,210 @@
+From stable+bounces-290946-greg=kroah.com@vger.kernel.org Thu Jul 30 03:02:52 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 21:01:32 -0400
+Subject: afs: Drop the net parameter from afs_unuse_cell()
+To: stable@vger.kernel.org
+Cc: David Howells <dhowells@redhat.com>, Marc Dionne <marc.dionne@auristor.com>, linux-afs@lists.infradead.org, linux-fsdevel@vger.kernel.org, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260730010137.1700032-3-sashal@kernel.org>
+
+From: David Howells <dhowells@redhat.com>
+
+[ Upstream commit 469c82b558628482002e4819d35b7670f0a989fd ]
+
+Remove the redundant net parameter to afs_unuse_cell() as cell->net can be
+used instead.
+
+Signed-off-by: David Howells <dhowells@redhat.com>
+cc: Marc Dionne <marc.dionne@auristor.com>
+cc: linux-afs@lists.infradead.org
+cc: linux-fsdevel@vger.kernel.org
+Link: https://lore.kernel.org/r/20250224234154.2014840-12-dhowells@redhat.com/ # v1
+Link: https://lore.kernel.org/r/20250310094206.801057-8-dhowells@redhat.com/ # v4
+Stable-dep-of: 330e2c514823 ("afs: Fix dynamic lookup to fail on cell lookup failure")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/afs/cell.c              |   12 ++++++------
+ fs/afs/dynroot.c           |    4 ++--
+ fs/afs/internal.h          |    2 +-
+ fs/afs/mntpt.c             |    2 +-
+ fs/afs/proc.c              |    2 +-
+ fs/afs/super.c             |    9 ++++-----
+ fs/afs/vl_alias.c          |    4 ++--
+ include/trace/events/afs.h |    1 +
+ 8 files changed, 18 insertions(+), 18 deletions(-)
+
+--- a/fs/afs/cell.c
++++ b/fs/afs/cell.c
+@@ -339,7 +339,7 @@ cell_already_exists:
+               goto wait_for_cell;
+       goto error_noput;
+ error:
+-      afs_unuse_cell(net, cell, afs_cell_trace_unuse_lookup);
++      afs_unuse_cell(cell, afs_cell_trace_unuse_lookup_error);
+ error_noput:
+       _leave(" = %d [error]", ret);
+       return ERR_PTR(ret);
+@@ -402,7 +402,7 @@ int afs_cell_init(struct afs_net *net, c
+                                      lockdep_is_held(&net->cells_lock));
+       up_write(&net->cells_lock);
+-      afs_unuse_cell(net, old_root, afs_cell_trace_unuse_ws);
++      afs_unuse_cell(old_root, afs_cell_trace_unuse_ws);
+       _leave(" = 0");
+       return 0;
+ }
+@@ -522,7 +522,7 @@ static void afs_cell_destroy(struct rcu_
+       trace_afs_cell(cell->debug_id, r, atomic_read(&cell->active), afs_cell_trace_free);
+       afs_put_vlserverlist(net, rcu_access_pointer(cell->vl_servers));
+-      afs_unuse_cell(net, cell->alias_of, afs_cell_trace_unuse_alias);
++      afs_unuse_cell(cell->alias_of, afs_cell_trace_unuse_alias);
+       key_put(cell->anonymous_key);
+       idr_remove(&net->cells_dyn_ino, cell->dynroot_ino);
+       kfree(cell->name - 1);
+@@ -610,7 +610,7 @@ struct afs_cell *afs_use_cell(struct afs
+  * Record a cell becoming less active.  When the active counter reaches 1, it
+  * is scheduled for destruction, but may get reactivated.
+  */
+-void afs_unuse_cell(struct afs_net *net, struct afs_cell *cell, enum afs_cell_trace reason)
++void afs_unuse_cell(struct afs_cell *cell, enum afs_cell_trace reason)
+ {
+       unsigned int debug_id;
+       time64_t now, expire_delay;
+@@ -634,7 +634,7 @@ void afs_unuse_cell(struct afs_net *net,
+       WARN_ON(a == 0);
+       if (a == 1)
+               /* 'cell' may now be garbage collected. */
+-              afs_set_cell_timer(net, expire_delay);
++              afs_set_cell_timer(cell->net, expire_delay);
+ }
+ /*
+@@ -959,7 +959,7 @@ void afs_cell_purge(struct afs_net *net)
+       ws = rcu_replace_pointer(net->ws_cell, NULL,
+                                lockdep_is_held(&net->cells_lock));
+       up_write(&net->cells_lock);
+-      afs_unuse_cell(net, ws, afs_cell_trace_unuse_ws);
++      afs_unuse_cell(ws, afs_cell_trace_unuse_ws);
+       _debug("del timer");
+       if (del_timer_sync(&net->cells_timer))
+--- a/fs/afs/dynroot.c
++++ b/fs/afs/dynroot.c
+@@ -125,7 +125,7 @@ static struct dentry *afs_dynroot_lookup
+       return d_splice_alias(inode, dentry);
+ out:
+-      afs_unuse_cell(cell->net, cell, afs_cell_trace_unuse_lookup_dynroot);
++      afs_unuse_cell(cell, afs_cell_trace_unuse_lookup_dynroot);
+ out_no_cell:
+       if (!inode)
+               return d_splice_alias(inode, dentry);
+@@ -167,7 +167,7 @@ static void afs_dynroot_d_release(struct
+ {
+       struct afs_cell *cell = dentry->d_fsdata;
+-      afs_unuse_cell(cell->net, cell, afs_cell_trace_unuse_dynroot_mntpt);
++      afs_unuse_cell(cell, afs_cell_trace_unuse_dynroot_mntpt);
+ }
+ /*
+--- a/fs/afs/internal.h
++++ b/fs/afs/internal.h
+@@ -1045,7 +1045,7 @@ struct afs_cell *afs_lookup_cell(struct
+                                const char *vllist, bool excl,
+                                enum afs_cell_trace trace);
+ extern struct afs_cell *afs_use_cell(struct afs_cell *, enum afs_cell_trace);
+-extern void afs_unuse_cell(struct afs_net *, struct afs_cell *, enum afs_cell_trace);
++void afs_unuse_cell(struct afs_cell *cell, enum afs_cell_trace reason);
+ extern struct afs_cell *afs_get_cell(struct afs_cell *, enum afs_cell_trace);
+ extern void afs_see_cell(struct afs_cell *, enum afs_cell_trace);
+ extern void afs_put_cell(struct afs_cell *, enum afs_cell_trace);
+--- a/fs/afs/mntpt.c
++++ b/fs/afs/mntpt.c
+@@ -87,7 +87,7 @@ static int afs_mntpt_set_params(struct f
+               ctx->force = true;
+       }
+       if (ctx->cell) {
+-              afs_unuse_cell(ctx->net, ctx->cell, afs_cell_trace_unuse_mntpt);
++              afs_unuse_cell(ctx->cell, afs_cell_trace_unuse_mntpt);
+               ctx->cell = NULL;
+       }
+       if (test_bit(AFS_VNODE_PSEUDODIR, &vnode->flags)) {
+--- a/fs/afs/proc.c
++++ b/fs/afs/proc.c
+@@ -130,7 +130,7 @@ static int afs_proc_cells_write(struct f
+               }
+               if (test_and_set_bit(AFS_CELL_FL_NO_GC, &cell->flags))
+-                      afs_unuse_cell(net, cell, afs_cell_trace_unuse_no_pin);
++                      afs_unuse_cell(cell, afs_cell_trace_unuse_no_pin);
+       } else {
+               goto inval;
+       }
+--- a/fs/afs/super.c
++++ b/fs/afs/super.c
+@@ -297,7 +297,7 @@ static int afs_parse_source(struct fs_co
+                              cellnamesz, cellnamesz, cellname ?: "");
+                       return PTR_ERR(cell);
+               }
+-              afs_unuse_cell(ctx->net, ctx->cell, afs_cell_trace_unuse_parse);
++              afs_unuse_cell(ctx->cell, afs_cell_trace_unuse_parse);
+               afs_see_cell(cell, afs_cell_trace_see_source);
+               ctx->cell = cell;
+       }
+@@ -394,7 +394,7 @@ static int afs_validate_fc(struct fs_con
+                               ctx->key = NULL;
+                               cell = afs_use_cell(ctx->cell->alias_of,
+                                                   afs_cell_trace_use_fc_alias);
+-                              afs_unuse_cell(ctx->net, ctx->cell, afs_cell_trace_unuse_fc);
++                              afs_unuse_cell(ctx->cell, afs_cell_trace_unuse_fc);
+                               ctx->cell = cell;
+                               goto reget_key;
+                       }
+@@ -520,9 +520,8 @@ static struct afs_super_info *afs_alloc_
+ static void afs_destroy_sbi(struct afs_super_info *as)
+ {
+       if (as) {
+-              struct afs_net *net = afs_net(as->net_ns);
+               afs_put_volume(as->volume, afs_volume_trace_put_destroy_sbi);
+-              afs_unuse_cell(net, as->cell, afs_cell_trace_unuse_sbi);
++              afs_unuse_cell(as->cell, afs_cell_trace_unuse_sbi);
+               put_net(as->net_ns);
+               kfree(as);
+       }
+@@ -606,7 +605,7 @@ static void afs_free_fc(struct fs_contex
+       afs_destroy_sbi(fc->s_fs_info);
+       afs_put_volume(ctx->volume, afs_volume_trace_put_free_fc);
+-      afs_unuse_cell(ctx->net, ctx->cell, afs_cell_trace_unuse_fc);
++      afs_unuse_cell(ctx->cell, afs_cell_trace_unuse_fc);
+       key_put(ctx->key);
+       kfree(ctx);
+ }
+--- a/fs/afs/vl_alias.c
++++ b/fs/afs/vl_alias.c
+@@ -205,11 +205,11 @@ static int afs_query_for_alias(struct af
+                       goto is_alias;
+               if (mutex_lock_interruptible(&cell->net->proc_cells_lock) < 0) {
+-                      afs_unuse_cell(cell->net, p, afs_cell_trace_unuse_check_alias);
++                      afs_unuse_cell(p, afs_cell_trace_unuse_check_alias);
+                       return -ERESTARTSYS;
+               }
+-              afs_unuse_cell(cell->net, p, afs_cell_trace_unuse_check_alias);
++              afs_unuse_cell(p, afs_cell_trace_unuse_check_alias);
+       }
+       mutex_unlock(&cell->net->proc_cells_lock);
+--- a/include/trace/events/afs.h
++++ b/include/trace/events/afs.h
+@@ -197,6 +197,7 @@ enum yfs_cm_operation {
+       EM(afs_cell_trace_unuse_fc,             "UNU fc    ") \
+       EM(afs_cell_trace_unuse_lookup,         "UNU lookup") \
+       EM(afs_cell_trace_unuse_lookup_dynroot, "UNU lu-dyn") \
++      EM(afs_cell_trace_unuse_lookup_error,   "UNU lu-err") \
+       EM(afs_cell_trace_unuse_mntpt,          "UNU mntpt ") \
+       EM(afs_cell_trace_unuse_no_pin,         "UNU no-pin") \
+       EM(afs_cell_trace_unuse_parse,          "UNU parse ") \
diff --git a/queue-6.12/afs-fix-afs_server-ref-accounting.patch b/queue-6.12/afs-fix-afs_server-ref-accounting.patch
new file mode 100644 (file)
index 0000000..62a770e
--- /dev/null
@@ -0,0 +1,1095 @@
+From stable+bounces-290952-greg=kroah.com@vger.kernel.org Thu Jul 30 03:02:14 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 21:01:35 -0400
+Subject: afs: Fix afs_server ref accounting
+To: stable@vger.kernel.org
+Cc: David Howells <dhowells@redhat.com>, Marc Dionne <marc.dionne@auristor.com>, linux-afs@lists.infradead.org, linux-fsdevel@vger.kernel.org, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260730010137.1700032-6-sashal@kernel.org>
+
+From: David Howells <dhowells@redhat.com>
+
+[ Upstream commit 4882ba78574e2d8c579658f65f6784b0d139d173 ]
+
+The current way that afs_server refs are accounted and cleaned up sometimes
+cause rmmod to hang when it is waiting for cell records to be removed.  The
+problem is that the cell cleanup might occasionally happen before the
+server cleanup and then there's nothing that causes the cell to
+garbage-collect the remaining servers as they become inactive.
+
+Partially fix this by:
+
+ (1) Give each afs_server record its own management timer that rather than
+     relying on the cell manager's central timer to drive each individual
+     cell's maintenance work item to garbage collect servers.
+
+     This timer is set when afs_unuse_server() reduces a server's activity
+     count to zero and will schedule the server's destroyer work item upon
+     firing.
+
+ (2) Give each afs_server record its own destroyer work item that removes
+     the record from the cell's database, shuts down the timer, cancels any
+     pending work for itself, sends an RPC to the server to cancel
+     outstanding callbacks.
+
+     This change, in combination with the timer, obviates the need to try
+     and coordinate so closely between the cell record and a bunch of other
+     server records to try and tear everything down in a coordinated
+     fashion.  With this, the cell record is pinned until the server RCU is
+     complete and namespace/module removal will wait until all the cell
+     records are removed.
+
+ (3) Now that incoming calls are mapped to servers (and thus cells) using
+     data attached to an rxrpc_peer, the UUID-to-server mapping tree is
+     moved from the namespace to the cell (cell->fs_servers).  This means
+     there can no longer be duplicates therein - and that allows the
+     mapping tree to be simpler as there doesn't need to be a chain of
+     same-UUID servers that are in different cells.
+
+ (4) The lock protecting the UUID mapping tree is switched to an
+     rw_semaphore on the cell rather than a seqlock on the namespace as
+     it's now only used during mounting in contexts in which we're allowed
+     to sleep.
+
+ (5) When it comes time for a cell that is being removed to purge its set
+     of servers, it just needs to iterate over them and wake them up.  Once
+     a server becomes inactive, its destroyer work item will observe the
+     state of the cell and immediately remove that record.
+
+ (6) When a server record is removed, it is marked AFS_SERVER_FL_EXPIRED to
+     prevent reattempts at removal.  The record will be dispatched to RCU
+     for destruction once its refcount reaches 0.
+
+ (7) The AFS_SERVER_FL_UNCREATED/CREATING flags are used to synchronise
+     simultaneous creation attempts.  If one attempt fails, it will abandon
+     the attempt and allow another to try again.
+
+     Note that the record can't just be abandoned when dead as it's bound
+     into a server list attached to a volume and only subject to
+     replacement if the server list obtained for the volume from the VLDB
+     changes.
+
+Signed-off-by: David Howells <dhowells@redhat.com>
+cc: Marc Dionne <marc.dionne@auristor.com>
+cc: linux-afs@lists.infradead.org
+cc: linux-fsdevel@vger.kernel.org
+Link: https://lore.kernel.org/r/20250224234154.2014840-15-dhowells@redhat.com/ # v1
+Link: https://lore.kernel.org/r/20250310094206.801057-11-dhowells@redhat.com/ # v4
+Stable-dep-of: 330e2c514823 ("afs: Fix dynamic lookup to fail on cell lookup failure")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/afs/cell.c              |    3 
+ fs/afs/fsclient.c          |    4 
+ fs/afs/internal.h          |   54 ++--
+ fs/afs/main.c              |   10 
+ fs/afs/server.c            |  560 +++++++++++++++++++--------------------------
+ fs/afs/server_list.c       |    4 
+ include/trace/events/afs.h |    7 
+ 7 files changed, 287 insertions(+), 355 deletions(-)
+
+--- a/fs/afs/cell.c
++++ b/fs/afs/cell.c
+@@ -169,7 +169,7 @@ static struct afs_cell *afs_alloc_cell(s
+       INIT_HLIST_HEAD(&cell->proc_volumes);
+       seqlock_init(&cell->volume_lock);
+       cell->fs_servers = RB_ROOT;
+-      seqlock_init(&cell->fs_lock);
++      init_rwsem(&cell->fs_lock);
+       rwlock_init(&cell->vl_servers_lock);
+       cell->flags = (1 << AFS_CELL_FL_CHECK_ALIAS);
+@@ -840,6 +840,7 @@ final_destruction:
+       /* The root volume is pinning the cell */
+       afs_put_volume(cell->root_volume, afs_volume_trace_put_cell_root);
+       cell->root_volume = NULL;
++      afs_purge_servers(cell);
+       afs_put_cell(cell, afs_cell_trace_put_destroy);
+ }
+--- a/fs/afs/fsclient.c
++++ b/fs/afs/fsclient.c
+@@ -1650,7 +1650,7 @@ int afs_fs_give_up_all_callbacks(struct
+       bp = call->request;
+       *bp++ = htonl(FSGIVEUPALLCALLBACKS);
+-      call->server = afs_use_server(server, afs_server_trace_use_give_up_cb);
++      call->server = afs_use_server(server, false, afs_server_trace_use_give_up_cb);
+       afs_make_call(call, GFP_NOFS);
+       afs_wait_for_call_to_complete(call);
+       ret = call->error;
+@@ -1756,7 +1756,7 @@ bool afs_fs_get_capabilities(struct afs_
+               return false;
+       call->key       = key;
+-      call->server    = afs_use_server(server, afs_server_trace_use_get_caps);
++      call->server    = afs_use_server(server, false, afs_server_trace_use_get_caps);
+       call->peer      = rxrpc_kernel_get_peer(estate->addresses->addrs[addr_index].peer);
+       call->probe     = afs_get_endpoint_state(estate, afs_estate_trace_get_getcaps);
+       call->probe_index = addr_index;
+--- a/fs/afs/internal.h
++++ b/fs/afs/internal.h
+@@ -317,18 +317,11 @@ struct afs_net {
+        * cell, but in practice, people create aliases and subsets and there's
+        * no easy way to distinguish them.
+        */
+-      seqlock_t               fs_lock;        /* For fs_servers, fs_probe_*, fs_proc */
+-      struct rb_root          fs_servers;     /* afs_server (by server UUID or address) */
++      seqlock_t               fs_lock;        /* For fs_probe_*, fs_proc */
+       struct list_head        fs_probe_fast;  /* List of afs_server to probe at 30s intervals */
+       struct list_head        fs_probe_slow;  /* List of afs_server to probe at 5m intervals */
+       struct hlist_head       fs_proc;        /* procfs servers list */
+-      struct hlist_head       fs_addresses;   /* afs_server (by lowest IPv6 addr) */
+-      seqlock_t               fs_addr_lock;   /* For fs_addresses[46] */
+-
+-      struct work_struct      fs_manager;
+-      struct timer_list       fs_timer;
+-
+       struct work_struct      fs_prober;
+       struct timer_list       fs_probe_timer;
+       atomic_t                servers_outstanding;
+@@ -424,7 +417,7 @@ struct afs_cell {
+       /* Active fileserver interaction state. */
+       struct rb_root          fs_servers;     /* afs_server (by server UUID) */
+-      seqlock_t               fs_lock;        /* For fs_servers  */
++      struct rw_semaphore     fs_lock;        /* For fs_servers  */
+       /* VL server list. */
+       rwlock_t                vl_servers_lock; /* Lock on vl_servers */
+@@ -559,22 +552,22 @@ struct afs_server {
+       };
+       struct afs_cell         *cell;          /* Cell to which belongs (pins ref) */
+-      struct rb_node          uuid_rb;        /* Link in net->fs_servers */
+-      struct afs_server __rcu *uuid_next;     /* Next server with same UUID */
+-      struct afs_server       *uuid_prev;     /* Previous server with same UUID */
+-      struct list_head        probe_link;     /* Link in net->fs_probe_list */
+-      struct hlist_node       addr_link;      /* Link in net->fs_addresses6 */
++      struct rb_node          uuid_rb;        /* Link in cell->fs_servers */
++      struct list_head        probe_link;     /* Link in net->fs_probe_* */
+       struct hlist_node       proc_link;      /* Link in net->fs_proc */
+       struct list_head        volumes;        /* RCU list of afs_server_entry objects */
+-      struct afs_server       *gc_next;       /* Next server in manager's list */
++      struct work_struct      destroyer;      /* Work item to try and destroy a server */
++      struct timer_list       timer;          /* Management timer */
+       time64_t                unuse_time;     /* Time at which last unused */
+       unsigned long           flags;
+ #define AFS_SERVER_FL_RESPONDING 0            /* The server is responding */
+ #define AFS_SERVER_FL_UPDATING        1
+ #define AFS_SERVER_FL_NEEDS_UPDATE 2          /* Fileserver address list is out of date */
+-#define AFS_SERVER_FL_NOT_READY       4               /* The record is not ready for use */
+-#define AFS_SERVER_FL_NOT_FOUND       5               /* VL server says no such server */
+-#define AFS_SERVER_FL_VL_FAIL 6               /* Failed to access VL server */
++#define AFS_SERVER_FL_UNCREATED       3               /* The record needs creating */
++#define AFS_SERVER_FL_CREATING        4               /* The record is being created */
++#define AFS_SERVER_FL_EXPIRED 5               /* The record has expired */
++#define AFS_SERVER_FL_NOT_FOUND       6               /* VL server says no such server */
++#define AFS_SERVER_FL_VL_FAIL 7               /* Failed to access VL server */
+ #define AFS_SERVER_FL_MAY_HAVE_CB 8           /* May have callbacks on this fileserver */
+ #define AFS_SERVER_FL_IS_YFS  16              /* Server is YFS not AFS */
+ #define AFS_SERVER_FL_NO_IBULK        17              /* Fileserver doesn't support FS.InlineBulkStatus */
+@@ -584,6 +577,7 @@ struct afs_server {
+       atomic_t                active;         /* Active user count */
+       u32                     addr_version;   /* Address list version */
+       u16                     service_id;     /* Service ID we're using. */
++      short                   create_error;   /* Creation error */
+       unsigned int            rtt;            /* Server's current RTT in uS */
+       unsigned int            debug_id;       /* Debugging ID for traces */
+@@ -1478,19 +1472,29 @@ extern void __exit afs_clean_up_permit_c
+ extern spinlock_t afs_server_peer_lock;
+ struct afs_server *afs_find_server(const struct rxrpc_peer *peer);
+-extern struct afs_server *afs_find_server_by_uuid(struct afs_net *, const uuid_t *);
+ extern struct afs_server *afs_lookup_server(struct afs_cell *, struct key *, const uuid_t *, u32);
+ extern struct afs_server *afs_get_server(struct afs_server *, enum afs_server_trace);
+-extern struct afs_server *afs_use_server(struct afs_server *, enum afs_server_trace);
+-extern void afs_unuse_server(struct afs_net *, struct afs_server *, enum afs_server_trace);
+-extern void afs_unuse_server_notime(struct afs_net *, struct afs_server *, enum afs_server_trace);
++struct afs_server *afs_use_server(struct afs_server *server, bool activate,
++                                enum afs_server_trace reason);
++void afs_unuse_server(struct afs_net *net, struct afs_server *server,
++                    enum afs_server_trace reason);
++void afs_unuse_server_notime(struct afs_net *net, struct afs_server *server,
++                           enum afs_server_trace reason);
+ extern void afs_put_server(struct afs_net *, struct afs_server *, enum afs_server_trace);
+-extern void afs_manage_servers(struct work_struct *);
+-extern void afs_servers_timer(struct timer_list *);
++void afs_purge_servers(struct afs_cell *cell);
+ extern void afs_fs_probe_timer(struct timer_list *);
+-extern void __net_exit afs_purge_servers(struct afs_net *);
++void __net_exit afs_wait_for_servers(struct afs_net *net);
+ bool afs_check_server_record(struct afs_operation *op, struct afs_server *server, struct key *key);
++static inline void afs_see_server(struct afs_server *server, enum afs_server_trace trace)
++{
++      int r = refcount_read(&server->ref);
++      int a = atomic_read(&server->active);
++
++      trace_afs_server(server->debug_id, r, a, trace);
++
++}
++
+ static inline void afs_inc_servers_outstanding(struct afs_net *net)
+ {
+       atomic_inc(&net->servers_outstanding);
+--- a/fs/afs/main.c
++++ b/fs/afs/main.c
+@@ -86,16 +86,10 @@ static int __net_init afs_net_init(struc
+       INIT_HLIST_HEAD(&net->proc_cells);
+       seqlock_init(&net->fs_lock);
+-      net->fs_servers = RB_ROOT;
+       INIT_LIST_HEAD(&net->fs_probe_fast);
+       INIT_LIST_HEAD(&net->fs_probe_slow);
+       INIT_HLIST_HEAD(&net->fs_proc);
+-      INIT_HLIST_HEAD(&net->fs_addresses);
+-      seqlock_init(&net->fs_addr_lock);
+-
+-      INIT_WORK(&net->fs_manager, afs_manage_servers);
+-      timer_setup(&net->fs_timer, afs_servers_timer, 0);
+       INIT_WORK(&net->fs_prober, afs_fs_probe_dispatcher);
+       timer_setup(&net->fs_probe_timer, afs_fs_probe_timer, 0);
+       atomic_set(&net->servers_outstanding, 1);
+@@ -131,7 +125,7 @@ error_open_socket:
+       net->live = false;
+       afs_fs_probe_cleanup(net);
+       afs_cell_purge(net);
+-      afs_purge_servers(net);
++      afs_wait_for_servers(net);
+ error_cell_init:
+       net->live = false;
+       afs_proc_cleanup(net);
+@@ -153,7 +147,7 @@ static void __net_exit afs_net_exit(stru
+       net->live = false;
+       afs_fs_probe_cleanup(net);
+       afs_cell_purge(net);
+-      afs_purge_servers(net);
++      afs_wait_for_servers(net);
+       afs_close_socket(net);
+       afs_proc_cleanup(net);
+       afs_put_sysnames(net->sysnames);
+--- a/fs/afs/server.c
++++ b/fs/afs/server.c
+@@ -14,9 +14,9 @@
+ static unsigned afs_server_gc_delay = 10;     /* Server record timeout in seconds */
+ static atomic_t afs_server_debug_id;
+-static struct afs_server *afs_maybe_use_server(struct afs_server *,
+-                                             enum afs_server_trace);
+ static void __afs_put_server(struct afs_net *, struct afs_server *);
++static void afs_server_timer(struct timer_list *timer);
++static void afs_server_destroyer(struct work_struct *work);
+ /*
+  * Find a server by one of its addresses.
+@@ -27,148 +27,91 @@ struct afs_server *afs_find_server(const
+       if (!server)
+               return NULL;
+-      return afs_maybe_use_server(server, afs_server_trace_use_cm_call);
++      return afs_use_server(server, false, afs_server_trace_use_cm_call);
+ }
+ /*
+- * Look up a server by its UUID and mark it active.
++ * Look up a server by its UUID and mark it active.  The caller must hold
++ * cell->fs_lock.
+  */
+-struct afs_server *afs_find_server_by_uuid(struct afs_net *net, const uuid_t *uuid)
++static struct afs_server *afs_find_server_by_uuid(struct afs_cell *cell, const uuid_t *uuid)
+ {
+-      struct afs_server *server = NULL;
++      struct afs_server *server;
+       struct rb_node *p;
+-      int diff, seq = 1;
++      int diff;
+       _enter("%pU", uuid);
+-      do {
+-              /* Unfortunately, rbtree walking doesn't give reliable results
+-               * under just the RCU read lock, so we have to check for
+-               * changes.
+-               */
+-              if (server)
+-                      afs_unuse_server(net, server, afs_server_trace_unuse_uuid_rsq);
+-              server = NULL;
+-              seq++; /* 2 on the 1st/lockless path, otherwise odd */
+-              read_seqbegin_or_lock(&net->fs_lock, &seq);
+-
+-              p = net->fs_servers.rb_node;
+-              while (p) {
+-                      server = rb_entry(p, struct afs_server, uuid_rb);
+-
+-                      diff = memcmp(uuid, &server->uuid, sizeof(*uuid));
+-                      if (diff < 0) {
+-                              p = p->rb_left;
+-                      } else if (diff > 0) {
+-                              p = p->rb_right;
+-                      } else {
+-                              afs_use_server(server, afs_server_trace_use_by_uuid);
+-                              break;
+-                      }
++      p = cell->fs_servers.rb_node;
++      while (p) {
++              server = rb_entry(p, struct afs_server, uuid_rb);
+-                      server = NULL;
++              diff = memcmp(uuid, &server->uuid, sizeof(*uuid));
++              if (diff < 0) {
++                      p = p->rb_left;
++              } else if (diff > 0) {
++                      p = p->rb_right;
++              } else {
++                      if (test_bit(AFS_SERVER_FL_UNCREATED, &server->flags))
++                              return NULL; /* Need a write lock */
++                      afs_use_server(server, true, afs_server_trace_use_by_uuid);
++                      return server;
+               }
+-      } while (need_seqretry(&net->fs_lock, seq));
+-
+-      done_seqretry(&net->fs_lock, seq);
++      }
+-      _leave(" = %p", server);
+-      return server;
++      return NULL;
+ }
+ /*
+- * Install a server record in the namespace tree.  If there's a clash, we stick
+- * it into a list anchored on whichever afs_server struct is actually in the
+- * tree.
++ * Install a server record in the cell tree.  The caller must hold an exclusive
++ * lock on cell->fs_lock.
+  */
+ static struct afs_server *afs_install_server(struct afs_cell *cell,
+-                                           struct afs_server *candidate)
++                                           struct afs_server **candidate)
+ {
+-      const struct afs_endpoint_state *estate;
+-      const struct afs_addr_list *alist;
+-      struct afs_server *server, *next;
++      struct afs_server *server;
+       struct afs_net *net = cell->net;
+       struct rb_node **pp, *p;
+       int diff;
+       _enter("%p", candidate);
+-      write_seqlock(&net->fs_lock);
+-
+       /* Firstly install the server in the UUID lookup tree */
+-      pp = &net->fs_servers.rb_node;
++      pp = &cell->fs_servers.rb_node;
+       p = NULL;
+       while (*pp) {
+               p = *pp;
+               _debug("- consider %p", p);
+               server = rb_entry(p, struct afs_server, uuid_rb);
+-              diff = memcmp(&candidate->uuid, &server->uuid, sizeof(uuid_t));
+-              if (diff < 0) {
++              diff = memcmp(&(*candidate)->uuid, &server->uuid, sizeof(uuid_t));
++              if (diff < 0)
+                       pp = &(*pp)->rb_left;
+-              } else if (diff > 0) {
++              else if (diff > 0)
+                       pp = &(*pp)->rb_right;
+-              } else {
+-                      if (server->cell == cell)
+-                              goto exists;
+-
+-                      /* We have the same UUID representing servers in
+-                       * different cells.  Append the new server to the list.
+-                       */
+-                      for (;;) {
+-                              next = rcu_dereference_protected(
+-                                      server->uuid_next,
+-                                      lockdep_is_held(&net->fs_lock.lock));
+-                              if (!next)
+-                                      break;
+-                              server = next;
+-                      }
+-                      rcu_assign_pointer(server->uuid_next, candidate);
+-                      candidate->uuid_prev = server;
+-                      server = candidate;
+-                      goto added_dup;
+-              }
++              else
++                      goto exists;
+       }
+-      server = candidate;
++      server = *candidate;
++      *candidate = NULL;
+       rb_link_node(&server->uuid_rb, p, pp);
+-      rb_insert_color(&server->uuid_rb, &net->fs_servers);
++      rb_insert_color(&server->uuid_rb, &cell->fs_servers);
++      write_seqlock(&net->fs_lock);
+       hlist_add_head_rcu(&server->proc_link, &net->fs_proc);
++      write_sequnlock(&net->fs_lock);
+       afs_get_cell(cell, afs_cell_trace_get_server);
+-added_dup:
+-      write_seqlock(&net->fs_addr_lock);
+-      estate = rcu_dereference_protected(server->endpoint_state,
+-                                         lockdep_is_held(&net->fs_addr_lock.lock));
+-      alist = estate->addresses;
+-
+-      /* Secondly, if the server has any IPv4 and/or IPv6 addresses, install
+-       * it in the IPv4 and/or IPv6 reverse-map lists.
+-       *
+-       * TODO: For speed we want to use something other than a flat list
+-       * here; even sorting the list in terms of lowest address would help a
+-       * bit, but anything we might want to do gets messy and memory
+-       * intensive.
+-       */
+-      if (alist->nr_addrs > 0)
+-              hlist_add_head_rcu(&server->addr_link, &net->fs_addresses);
+-
+-      write_sequnlock(&net->fs_addr_lock);
+-
+ exists:
+-      afs_get_server(server, afs_server_trace_get_install);
+-      write_sequnlock(&net->fs_lock);
++      afs_use_server(server, true, afs_server_trace_get_install);
+       return server;
+ }
+ /*
+- * Allocate a new server record and mark it active.
++ * Allocate a new server record and mark it as active but uncreated.
+  */
+-static struct afs_server *afs_alloc_server(struct afs_cell *cell,
+-                                         const uuid_t *uuid,
+-                                         struct afs_addr_list *alist)
++static struct afs_server *afs_alloc_server(struct afs_cell *cell, const uuid_t *uuid)
+ {
+-      struct afs_endpoint_state *estate;
+       struct afs_server *server;
+       struct afs_net *net = cell->net;
+@@ -176,65 +119,49 @@ static struct afs_server *afs_alloc_serv
+       server = kzalloc(sizeof(struct afs_server), GFP_KERNEL);
+       if (!server)
+-              goto enomem;
+-
+-      estate = kzalloc(sizeof(struct afs_endpoint_state), GFP_KERNEL);
+-      if (!estate)
+-              goto enomem_server;
++              return NULL;
+       refcount_set(&server->ref, 1);
+-      atomic_set(&server->active, 1);
++      atomic_set(&server->active, 0);
++      __set_bit(AFS_SERVER_FL_UNCREATED, &server->flags);
+       server->debug_id = atomic_inc_return(&afs_server_debug_id);
+-      server->addr_version = alist->version;
+       server->uuid = *uuid;
+       rwlock_init(&server->fs_lock);
++      INIT_WORK(&server->destroyer, &afs_server_destroyer);
++      timer_setup(&server->timer, afs_server_timer, 0);
+       INIT_LIST_HEAD(&server->volumes);
+       init_waitqueue_head(&server->probe_wq);
+       INIT_LIST_HEAD(&server->probe_link);
++      INIT_HLIST_NODE(&server->proc_link);
+       spin_lock_init(&server->probe_lock);
+       server->cell = cell;
+       server->rtt = UINT_MAX;
+       server->service_id = FS_SERVICE;
+-
+       server->probe_counter = 1;
+       server->probed_at = jiffies - LONG_MAX / 2;
+-      refcount_set(&estate->ref, 1);
+-      estate->addresses = alist;
+-      estate->server_id = server->debug_id;
+-      estate->probe_seq = 1;
+-      rcu_assign_pointer(server->endpoint_state, estate);
+       afs_inc_servers_outstanding(net);
+-      trace_afs_server(server->debug_id, 1, 1, afs_server_trace_alloc);
+-      trace_afs_estate(estate->server_id, estate->probe_seq, refcount_read(&estate->ref),
+-                       afs_estate_trace_alloc_server);
+       _leave(" = %p", server);
+       return server;
+-
+-enomem_server:
+-      kfree(server);
+-enomem:
+-      _leave(" = NULL [nomem]");
+-      return NULL;
+ }
+ /*
+  * Look up an address record for a server
+  */
+-static struct afs_addr_list *afs_vl_lookup_addrs(struct afs_cell *cell,
+-                                               struct key *key, const uuid_t *uuid)
++static struct afs_addr_list *afs_vl_lookup_addrs(struct afs_server *server,
++                                               struct key *key)
+ {
+       struct afs_vl_cursor vc;
+       struct afs_addr_list *alist = NULL;
+       int ret;
+       ret = -ERESTARTSYS;
+-      if (afs_begin_vlserver_operation(&vc, cell, key)) {
++      if (afs_begin_vlserver_operation(&vc, server->cell, key)) {
+               while (afs_select_vlserver(&vc)) {
+                       if (test_bit(AFS_VLSERVER_FL_IS_YFS, &vc.server->flags))
+-                              alist = afs_yfsvl_get_endpoints(&vc, uuid);
++                              alist = afs_yfsvl_get_endpoints(&vc, &server->uuid);
+                       else
+-                              alist = afs_vl_get_addrs_u(&vc, uuid);
++                              alist = afs_vl_get_addrs_u(&vc, &server->uuid);
+               }
+               ret = afs_end_vlserver_operation(&vc);
+@@ -250,67 +177,116 @@ static struct afs_addr_list *afs_vl_look
+ struct afs_server *afs_lookup_server(struct afs_cell *cell, struct key *key,
+                                    const uuid_t *uuid, u32 addr_version)
+ {
+-      struct afs_addr_list *alist;
+-      struct afs_server *server, *candidate;
++      struct afs_addr_list *alist = NULL;
++      struct afs_server *server, *candidate = NULL;
++      bool creating = false;
++      int ret;
+       _enter("%p,%pU", cell->net, uuid);
+-      server = afs_find_server_by_uuid(cell->net, uuid);
++      down_read(&cell->fs_lock);
++      server = afs_find_server_by_uuid(cell, uuid);
++      /* Won't see servers marked uncreated. */
++      up_read(&cell->fs_lock);
++
+       if (server) {
++              timer_delete_sync(&server->timer);
++              if (test_bit(AFS_SERVER_FL_CREATING, &server->flags))
++                      goto wait_for_creation;
+               if (server->addr_version != addr_version)
+                       set_bit(AFS_SERVER_FL_NEEDS_UPDATE, &server->flags);
+               return server;
+       }
+-      alist = afs_vl_lookup_addrs(cell, key, uuid);
+-      if (IS_ERR(alist))
+-              return ERR_CAST(alist);
+-
+-      candidate = afs_alloc_server(cell, uuid, alist);
++      candidate = afs_alloc_server(cell, uuid);
+       if (!candidate) {
+               afs_put_addrlist(alist, afs_alist_trace_put_server_oom);
+               return ERR_PTR(-ENOMEM);
+       }
+-      server = afs_install_server(cell, candidate);
+-      if (server != candidate) {
+-              afs_put_addrlist(alist, afs_alist_trace_put_server_dup);
++      down_write(&cell->fs_lock);
++      server = afs_install_server(cell, &candidate);
++      if (test_bit(AFS_SERVER_FL_CREATING, &server->flags)) {
++              /* We need to wait for creation to complete. */
++              up_write(&cell->fs_lock);
++              goto wait_for_creation;
++      }
++      if (test_bit(AFS_SERVER_FL_UNCREATED, &server->flags)) {
++              set_bit(AFS_SERVER_FL_CREATING, &server->flags);
++              clear_bit(AFS_SERVER_FL_UNCREATED, &server->flags);
++              creating = true;
++      }
++      up_write(&cell->fs_lock);
++      timer_delete_sync(&server->timer);
++
++      /* If we get to create the server, we look up the addresses and then
++       * immediately dispatch an asynchronous probe to each interface on the
++       * fileserver.  This will make sure the repeat-probing service is
++       * started.
++       */
++      if (creating) {
++              alist = afs_vl_lookup_addrs(server, key);
++              if (IS_ERR(alist)) {
++                      ret = PTR_ERR(alist);
++                      goto create_failed;
++              }
++
++              ret = afs_fs_probe_fileserver(cell->net, server, alist, key);
++              if (ret)
++                      goto create_failed;
++
++              clear_and_wake_up_bit(AFS_SERVER_FL_CREATING, &server->flags);
++      }
++
++out:
++      afs_put_addrlist(alist, afs_alist_trace_put_server_create);
++      if (candidate) {
++              kfree(rcu_access_pointer(server->endpoint_state));
+               kfree(candidate);
+-      } else {
+-              /* Immediately dispatch an asynchronous probe to each interface
+-               * on the fileserver.  This will make sure the repeat-probing
+-               * service is started.
+-               */
+-              afs_fs_probe_fileserver(cell->net, server, alist, key);
++              afs_dec_servers_outstanding(cell->net);
+       }
++      return server ?: ERR_PTR(ret);
+-      return server;
+-}
++wait_for_creation:
++      afs_see_server(server, afs_server_trace_wait_create);
++      wait_on_bit(&server->flags, AFS_SERVER_FL_CREATING, TASK_UNINTERRUPTIBLE);
++      if (test_bit_acquire(AFS_SERVER_FL_UNCREATED, &server->flags)) {
++              /* Barrier: read flag before error */
++              ret = READ_ONCE(server->create_error);
++              afs_put_server(cell->net, server, afs_server_trace_unuse_create_fail);
++              server = NULL;
++              goto out;
++      }
+-/*
+- * Set the server timer to fire after a given delay, assuming it's not already
+- * set for an earlier time.
+- */
+-static void afs_set_server_timer(struct afs_net *net, time64_t delay)
+-{
+-      if (net->live) {
+-              afs_inc_servers_outstanding(net);
+-              if (timer_reduce(&net->fs_timer, jiffies + delay * HZ))
+-                      afs_dec_servers_outstanding(net);
++      ret = 0;
++      goto out;
++
++create_failed:
++      down_write(&cell->fs_lock);
++
++      WRITE_ONCE(server->create_error, ret);
++      smp_wmb(); /* Barrier: set error before flag. */
++      set_bit(AFS_SERVER_FL_UNCREATED, &server->flags);
++
++      clear_and_wake_up_bit(AFS_SERVER_FL_CREATING, &server->flags);
++
++      if (test_bit(AFS_SERVER_FL_UNCREATED, &server->flags)) {
++              clear_bit(AFS_SERVER_FL_UNCREATED, &server->flags);
++              creating = true;
+       }
++      afs_unuse_server(cell->net, server, afs_server_trace_unuse_create_fail);
++      server = NULL;
++
++      up_write(&cell->fs_lock);
++      goto out;
+ }
+ /*
+- * Server management timer.  We have an increment on fs_outstanding that we
+- * need to pass along to the work item.
++ * Set/reduce a server's timer.
+  */
+-void afs_servers_timer(struct timer_list *timer)
++static void afs_set_server_timer(struct afs_server *server, unsigned int delay_secs)
+ {
+-      struct afs_net *net = container_of(timer, struct afs_net, fs_timer);
+-
+-      _enter("");
+-      if (!queue_work(afs_wq, &net->fs_manager))
+-              afs_dec_servers_outstanding(net);
++      mod_timer(&server->timer, jiffies + delay_secs * HZ);
+ }
+ /*
+@@ -329,32 +305,20 @@ struct afs_server *afs_get_server(struct
+ }
+ /*
+- * Try to get a reference on a server object.
++ * Get an active count on a server object and maybe remove from the inactive
++ * list.
+  */
+-static struct afs_server *afs_maybe_use_server(struct afs_server *server,
+-                                             enum afs_server_trace reason)
+-{
+-      unsigned int a;
+-      int r;
+-
+-      if (!__refcount_inc_not_zero(&server->ref, &r))
+-              return NULL;
+-
+-      a = atomic_inc_return(&server->active);
+-      trace_afs_server(server->debug_id, r + 1, a, reason);
+-      return server;
+-}
+-
+-/*
+- * Get an active count on a server object.
+- */
+-struct afs_server *afs_use_server(struct afs_server *server, enum afs_server_trace reason)
++struct afs_server *afs_use_server(struct afs_server *server, bool activate,
++                                enum afs_server_trace reason)
+ {
+       unsigned int a;
+       int r;
+       __refcount_inc(&server->ref, &r);
+       a = atomic_inc_return(&server->active);
++      if (a == 1 && activate &&
++          !test_bit(AFS_SERVER_FL_EXPIRED, &server->flags))
++              del_timer(&server->timer);
+       trace_afs_server(server->debug_id, r + 1, a, reason);
+       return server;
+@@ -388,13 +352,16 @@ void afs_put_server(struct afs_net *net,
+ void afs_unuse_server_notime(struct afs_net *net, struct afs_server *server,
+                            enum afs_server_trace reason)
+ {
+-      if (server) {
+-              unsigned int active = atomic_dec_return(&server->active);
++      if (!server)
++              return;
+-              if (active == 0)
+-                      afs_set_server_timer(net, afs_server_gc_delay);
+-              afs_put_server(net, server, reason);
++      if (atomic_dec_and_test(&server->active)) {
++              if (test_bit(AFS_SERVER_FL_EXPIRED, &server->flags) ||
++                  READ_ONCE(server->cell->state) >= AFS_CELL_FAILED)
++                      schedule_work(&server->destroyer);
+       }
++
++      afs_put_server(net, server, reason);
+ }
+ /*
+@@ -403,10 +370,22 @@ void afs_unuse_server_notime(struct afs_
+ void afs_unuse_server(struct afs_net *net, struct afs_server *server,
+                     enum afs_server_trace reason)
+ {
+-      if (server) {
+-              server->unuse_time = ktime_get_real_seconds();
+-              afs_unuse_server_notime(net, server, reason);
++      if (!server)
++              return;
++
++      if (atomic_dec_and_test(&server->active)) {
++              if (!test_bit(AFS_SERVER_FL_EXPIRED, &server->flags) &&
++                  READ_ONCE(server->cell->state) < AFS_CELL_FAILED) {
++                      time64_t unuse_time = ktime_get_real_seconds();
++
++                      server->unuse_time = unuse_time;
++                      afs_set_server_timer(server, afs_server_gc_delay);
++              } else {
++                      schedule_work(&server->destroyer);
++              }
+       }
++
++      afs_put_server(net, server, reason);
+ }
+ static void afs_server_rcu(struct rcu_head *rcu)
+@@ -436,166 +415,119 @@ static void afs_give_up_callbacks(struct
+ }
+ /*
+- * destroy a dead server
++ * Check to see if the server record has expired.
+  */
+-static void afs_destroy_server(struct afs_net *net, struct afs_server *server)
++static bool afs_has_server_expired(const struct afs_server *server)
+ {
+-      struct afs_endpoint_state *estate;
++      time64_t expires_at;
+-      if (test_bit(AFS_SERVER_FL_MAY_HAVE_CB, &server->flags))
+-              afs_give_up_callbacks(net, server);
++      if (atomic_read(&server->active))
++              return false;
+-      /* Unbind the rxrpc_peer records from the server. */
+-      estate = rcu_access_pointer(server->endpoint_state);
+-      if (estate)
+-              afs_set_peer_appdata(server, estate->addresses, NULL);
++      if (server->cell->net->live ||
++          server->cell->state >= AFS_CELL_FAILED) {
++              trace_afs_server(server->debug_id, refcount_read(&server->ref),
++                               0, afs_server_trace_purging);
++              return true;
++      }
+-      afs_put_server(net, server, afs_server_trace_destroy);
++      expires_at = server->unuse_time;
++      if (!test_bit(AFS_SERVER_FL_VL_FAIL, &server->flags) &&
++          !test_bit(AFS_SERVER_FL_NOT_FOUND, &server->flags))
++              expires_at += afs_server_gc_delay;
++
++      return ktime_get_real_seconds() > expires_at;
+ }
+ /*
+- * Garbage collect any expired servers.
++ * Remove a server record from it's parent cell's database.
+  */
+-static void afs_gc_servers(struct afs_net *net, struct afs_server *gc_list)
++static bool afs_remove_server_from_cell(struct afs_server *server)
+ {
+-      struct afs_server *server, *next, *prev;
+-      int active;
++      struct afs_cell *cell = server->cell;
+-      while ((server = gc_list)) {
+-              gc_list = server->gc_next;
+-
+-              write_seqlock(&net->fs_lock);
+-
+-              active = atomic_read(&server->active);
+-              if (active == 0) {
+-                      trace_afs_server(server->debug_id, refcount_read(&server->ref),
+-                                       active, afs_server_trace_gc);
+-                      next = rcu_dereference_protected(
+-                              server->uuid_next, lockdep_is_held(&net->fs_lock.lock));
+-                      prev = server->uuid_prev;
+-                      if (!prev) {
+-                              /* The one at the front is in the tree */
+-                              if (!next) {
+-                                      rb_erase(&server->uuid_rb, &net->fs_servers);
+-                              } else {
+-                                      rb_replace_node_rcu(&server->uuid_rb,
+-                                                          &next->uuid_rb,
+-                                                          &net->fs_servers);
+-                                      next->uuid_prev = NULL;
+-                              }
+-                      } else {
+-                              /* This server is not at the front */
+-                              rcu_assign_pointer(prev->uuid_next, next);
+-                              if (next)
+-                                      next->uuid_prev = prev;
+-                      }
+-
+-                      list_del(&server->probe_link);
+-                      hlist_del_rcu(&server->proc_link);
+-                      if (!hlist_unhashed(&server->addr_link))
+-                              hlist_del_rcu(&server->addr_link);
+-              }
+-              write_sequnlock(&net->fs_lock);
++      down_write(&cell->fs_lock);
+-              if (active == 0)
+-                      afs_destroy_server(net, server);
++      if (!afs_has_server_expired(server)) {
++              up_write(&cell->fs_lock);
++              return false;
+       }
++
++      set_bit(AFS_SERVER_FL_EXPIRED, &server->flags);
++      _debug("expire %pU %u", &server->uuid, atomic_read(&server->active));
++      afs_see_server(server, afs_server_trace_see_expired);
++      rb_erase(&server->uuid_rb, &cell->fs_servers);
++      up_write(&cell->fs_lock);
++      return true;
+ }
+-/*
+- * Manage the records of servers known to be within a network namespace.  This
+- * includes garbage collecting unused servers.
+- *
+- * Note also that we were given an increment on net->servers_outstanding by
+- * whoever queued us that we need to deal with before returning.
+- */
+-void afs_manage_servers(struct work_struct *work)
++static void afs_server_destroyer(struct work_struct *work)
+ {
+-      struct afs_net *net = container_of(work, struct afs_net, fs_manager);
+-      struct afs_server *gc_list = NULL;
+-      struct rb_node *cursor;
+-      time64_t now = ktime_get_real_seconds(), next_manage = TIME64_MAX;
+-      bool purging = !net->live;
++      struct afs_endpoint_state *estate;
++      struct afs_server *server = container_of(work, struct afs_server, destroyer);
++      struct afs_net *net = server->cell->net;
+-      _enter("");
++      afs_see_server(server, afs_server_trace_see_destroyer);
+-      /* Trawl the server list looking for servers that have expired from
+-       * lack of use.
+-       */
+-      read_seqlock_excl(&net->fs_lock);
++      if (test_bit(AFS_SERVER_FL_EXPIRED, &server->flags))
++              return;
+-      for (cursor = rb_first(&net->fs_servers); cursor; cursor = rb_next(cursor)) {
+-              struct afs_server *server =
+-                      rb_entry(cursor, struct afs_server, uuid_rb);
+-              int active = atomic_read(&server->active);
+-
+-              _debug("manage %pU %u", &server->uuid, active);
+-
+-              if (purging) {
+-                      trace_afs_server(server->debug_id, refcount_read(&server->ref),
+-                                       active, afs_server_trace_purging);
+-                      if (active != 0)
+-                              pr_notice("Can't purge s=%08x\n", server->debug_id);
+-              }
++      if (!afs_remove_server_from_cell(server))
++              return;
+-              if (active == 0) {
+-                      time64_t expire_at = server->unuse_time;
++      timer_shutdown_sync(&server->timer);
++      cancel_work(&server->destroyer);
+-                      if (!test_bit(AFS_SERVER_FL_VL_FAIL, &server->flags) &&
+-                          !test_bit(AFS_SERVER_FL_NOT_FOUND, &server->flags))
+-                              expire_at += afs_server_gc_delay;
+-                      if (purging || expire_at <= now) {
+-                              server->gc_next = gc_list;
+-                              gc_list = server;
+-                      } else if (expire_at < next_manage) {
+-                              next_manage = expire_at;
+-                      }
+-              }
+-      }
++      if (test_bit(AFS_SERVER_FL_MAY_HAVE_CB, &server->flags))
++              afs_give_up_callbacks(net, server);
+-      read_sequnlock_excl(&net->fs_lock);
++      /* Unbind the rxrpc_peer records from the server. */
++      estate = rcu_access_pointer(server->endpoint_state);
++      if (estate)
++              afs_set_peer_appdata(server, estate->addresses, NULL);
+-      /* Update the timer on the way out.  We have to pass an increment on
+-       * servers_outstanding in the namespace that we are in to the timer or
+-       * the work scheduler.
+-       */
+-      if (!purging && next_manage < TIME64_MAX) {
+-              now = ktime_get_real_seconds();
++      write_seqlock(&net->fs_lock);
++      list_del_init(&server->probe_link);
++      if (!hlist_unhashed(&server->proc_link))
++              hlist_del_rcu(&server->proc_link);
++      write_sequnlock(&net->fs_lock);
+-              if (next_manage - now <= 0) {
+-                      if (queue_work(afs_wq, &net->fs_manager))
+-                              afs_inc_servers_outstanding(net);
+-              } else {
+-                      afs_set_server_timer(net, next_manage - now);
+-              }
+-      }
++      afs_put_server(net, server, afs_server_trace_destroy);
++}
+-      afs_gc_servers(net, gc_list);
++static void afs_server_timer(struct timer_list *timer)
++{
++      struct afs_server *server = container_of(timer, struct afs_server, timer);
+-      afs_dec_servers_outstanding(net);
+-      _leave(" [%d]", atomic_read(&net->servers_outstanding));
++      afs_see_server(server, afs_server_trace_see_timer);
++      if (!test_bit(AFS_SERVER_FL_EXPIRED, &server->flags))
++              schedule_work(&server->destroyer);
+ }
+-static void afs_queue_server_manager(struct afs_net *net)
++/*
++ * Wake up all the servers in a cell so that they can purge themselves.
++ */
++void afs_purge_servers(struct afs_cell *cell)
+ {
+-      afs_inc_servers_outstanding(net);
+-      if (!queue_work(afs_wq, &net->fs_manager))
+-              afs_dec_servers_outstanding(net);
++      struct afs_server *server;
++      struct rb_node *rb;
++
++      down_read(&cell->fs_lock);
++      for (rb = rb_first(&cell->fs_servers); rb; rb = rb_next(rb)) {
++              server = rb_entry(rb, struct afs_server, uuid_rb);
++              afs_see_server(server, afs_server_trace_see_purge);
++              schedule_work(&server->destroyer);
++      }
++      up_read(&cell->fs_lock);
+ }
+ /*
+- * Purge list of servers.
++ * Wait for outstanding servers.
+  */
+-void afs_purge_servers(struct afs_net *net)
++void afs_wait_for_servers(struct afs_net *net)
+ {
+       _enter("");
+-      if (del_timer_sync(&net->fs_timer))
+-              afs_dec_servers_outstanding(net);
+-
+-      afs_queue_server_manager(net);
+-
+-      _debug("wait");
+       atomic_dec(&net->servers_outstanding);
+       wait_var_event(&net->servers_outstanding,
+                      !atomic_read(&net->servers_outstanding));
+@@ -619,7 +551,7 @@ static noinline bool afs_update_server_r
+                        atomic_read(&server->active),
+                        afs_server_trace_update);
+-      alist = afs_vl_lookup_addrs(op->volume->cell, op->key, &server->uuid);
++      alist = afs_vl_lookup_addrs(server, op->key);
+       if (IS_ERR(alist)) {
+               rcu_read_lock();
+               estate = rcu_dereference(server->endpoint_state);
+--- a/fs/afs/server_list.c
++++ b/fs/afs/server_list.c
+@@ -97,8 +97,8 @@ struct afs_server_list *afs_alloc_server
+                               break;
+               if (j < slist->nr_servers) {
+                       if (slist->servers[j].server == server) {
+-                              afs_unuse_server(volume->cell->net, server,
+-                                               afs_server_trace_unuse_slist_isort);
++                              afs_unuse_server_notime(volume->cell->net, server,
++                                                      afs_server_trace_unuse_slist_isort);
+                               continue;
+                       }
+--- a/include/trace/events/afs.h
++++ b/include/trace/events/afs.h
+@@ -127,7 +127,6 @@ enum yfs_cm_operation {
+       E_(afs_call_trace_work,                 "QUEUE")
+ #define afs_server_traces \
+-      EM(afs_server_trace_alloc,              "ALLOC    ") \
+       EM(afs_server_trace_callback,           "CALLBACK ") \
+       EM(afs_server_trace_destroy,            "DESTROY  ") \
+       EM(afs_server_trace_free,               "FREE     ") \
+@@ -137,12 +136,14 @@ enum yfs_cm_operation {
+       EM(afs_server_trace_purging,            "PURGE    ") \
+       EM(afs_server_trace_put_cbi,            "PUT cbi  ") \
+       EM(afs_server_trace_put_probe,          "PUT probe") \
++      EM(afs_server_trace_see_destroyer,      "SEE destr") \
+       EM(afs_server_trace_see_expired,        "SEE expd ") \
++      EM(afs_server_trace_see_purge,          "SEE purge") \
++      EM(afs_server_trace_see_timer,          "SEE timer") \
+       EM(afs_server_trace_unuse_call,         "UNU call ") \
+       EM(afs_server_trace_unuse_create_fail,  "UNU cfail") \
+       EM(afs_server_trace_unuse_slist,        "UNU slist") \
+       EM(afs_server_trace_unuse_slist_isort,  "UNU isort") \
+-      EM(afs_server_trace_unuse_uuid_rsq,     "PUT u-req") \
+       EM(afs_server_trace_update,             "UPDATE   ") \
+       EM(afs_server_trace_use_by_uuid,        "USE uuid ") \
+       EM(afs_server_trace_use_cm_call,        "USE cm-cl") \
+@@ -229,7 +230,7 @@ enum yfs_cm_operation {
+       EM(afs_alist_trace_put_getaddru,        "PUT GtAdrU") \
+       EM(afs_alist_trace_put_parse_empty,     "PUT p-empt") \
+       EM(afs_alist_trace_put_parse_error,     "PUT p-err ") \
+-      EM(afs_alist_trace_put_server_dup,      "PUT sv-dup") \
++      EM(afs_alist_trace_put_server_create,   "PUT sv-crt") \
+       EM(afs_alist_trace_put_server_oom,      "PUT sv-oom") \
+       EM(afs_alist_trace_put_server_update,   "PUT sv-upd") \
+       EM(afs_alist_trace_put_vlgetcaps,       "PUT vgtcap") \
diff --git a/queue-6.12/afs-fix-dynamic-lookup-to-fail-on-cell-lookup-failure.patch b/queue-6.12/afs-fix-dynamic-lookup-to-fail-on-cell-lookup-failure.patch
new file mode 100644 (file)
index 0000000..04ac3a0
--- /dev/null
@@ -0,0 +1,347 @@
+From stable+bounces-290954-greg=kroah.com@vger.kernel.org Thu Jul 30 03:05:15 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 21:01:37 -0400
+Subject: afs: Fix dynamic lookup to fail on cell lookup failure
+To: stable@vger.kernel.org
+Cc: David Howells <dhowells@redhat.com>, Markus Suvanto <markus.suvanto@gmail.com>, Marc Dionne <marc.dionne@auristor.com>, linux-afs@lists.infradead.org, Christian Brauner <brauner@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260730010137.1700032-8-sashal@kernel.org>
+
+From: David Howells <dhowells@redhat.com>
+
+[ Upstream commit 330e2c514823008b22e6afd2055715bc46dd8d55 ]
+
+When a process tries to access an entry in /afs, normally what happens is
+that an automount dentry is created by ->lookup() and then triggered, which
+jumps through the ->d_automount() op.  Currently, afs_dynroot_lookup() does
+not do cell DNS lookup, leaving that to afs_d_automount() to perform -
+however, it is possible to use access() or stat() on the automount point,
+which will always return successfully, have briefly created an afs_cell
+record if one did not already exist.
+
+This means that something like:
+
+        test -d "/afs/.west" && echo Directory exists
+
+will print "Directory exists" even though no such cell is configured.  This
+breaks the "west" python module available on PIP as it expects this access
+to fail.
+
+Now, it could be possible to make afs_dynroot_lookup() perform the DNS[*]
+lookup, but that would make "ls --color /afs" do this for each cell in /afs
+that is listed but not yet probed.  kafs-client, probably wrongly, preloads
+the entire cell database and all the known cells are then listed in /afs -
+and doing ls /afs would be very, very slow, especially if any cell supplied
+addresses but was wholly inaccessible.
+
+ [*] When I say "DNS", actually read getaddrinfo(), which could use any one
+     of a host of mechanisms.  Could also use static configuration.
+
+To fix this, make the following changes:
+
+ (1) Create an enum to specify the origination point of a call to
+     afs_lookup_cell() and pass this value into that function in place of
+     the "excl" parameter (which can be derived from it).  There are six
+     points of origination:
+
+        - Cell preload through /proc/net/afs/cells
+        - Root cell config through /proc/net/afs/rootcell
+        - Lookup in dynamic root
+        - Automount trigger
+        - Direct mount with mount() syscall
+        - Alias check where YFS tells us the cell name is different
+
+ (2) Add an extra state into the afs_cell state machine to indicate a cell
+     that's been initialised, but not yet looked up.  This is separate from
+     one that can be considered active and has been looked up at least
+     once.
+
+ (3) Make afs_lookup_cell() vary its behaviour more, depending on where it
+     was called from:
+
+     If called from preload or root cell config, DNS lookup will not happen
+     until we definitely want to use the cell (dynroot mount, automount,
+     direct mount or alias check).  The cell will appear in /afs but stat()
+     won't trigger DNS lookup.
+
+     If the cell already exists, dynroot will not wait for the DNS lookup
+     to complete.  If the cell did not already exist, dynroot will wait.
+
+     If called from automount, direct mount or alias check, it will wait
+     for the DNS lookup to complete.
+
+ (4) Make afs_lookup_cell() return an error if lookup failed in one way or
+     another.  We try to return -ENOENT if the DNS says the cell does not
+     exist and -EDESTADDRREQ if we couldn't access the DNS.
+
+Reported-by: Markus Suvanto <markus.suvanto@gmail.com>
+Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220685
+Signed-off-by: David Howells <dhowells@redhat.com>
+Link: https://patch.msgid.link/1784747.1761158912@warthog.procyon.org.uk
+Fixes: 1d0b929fc070 ("afs: Change dynroot to create contents on demand")
+Tested-by: Markus Suvanto <markus.suvanto@gmail.com>
+cc: Marc Dionne <marc.dionne@auristor.com>
+cc: linux-afs@lists.infradead.org
+Signed-off-by: Christian Brauner <brauner@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/afs/cell.c     |   78 +++++++++++++++++++++++++++++++++++++++++++++---------
+ fs/afs/dynroot.c  |    3 +-
+ fs/afs/internal.h |   12 +++++++-
+ fs/afs/mntpt.c    |    3 +-
+ fs/afs/proc.c     |    3 +-
+ fs/afs/super.c    |    2 -
+ fs/afs/vl_alias.c |    3 +-
+ 7 files changed, 86 insertions(+), 18 deletions(-)
+
+--- a/fs/afs/cell.c
++++ b/fs/afs/cell.c
+@@ -228,7 +228,7 @@ error:
+  * @name:     The name of the cell.
+  * @namesz:   The strlen of the cell name.
+  * @vllist:   A colon/comma separated list of numeric IP addresses or NULL.
+- * @excl:     T if an error should be given if the cell name already exists.
++ * @reason:   The reason we're doing the lookup
+  * @trace:    The reason to be logged if the lookup is successful.
+  *
+  * Look up a cell record by name and query the DNS for VL server addresses if
+@@ -238,7 +238,8 @@ error:
+  */
+ struct afs_cell *afs_lookup_cell(struct afs_net *net,
+                                const char *name, unsigned int namesz,
+-                               const char *vllist, bool excl,
++                               const char *vllist,
++                               enum afs_lookup_cell_for reason,
+                                enum afs_cell_trace trace)
+ {
+       struct afs_cell *cell, *candidate, *cursor;
+@@ -246,12 +247,18 @@ struct afs_cell *afs_lookup_cell(struct
+       enum afs_cell_state state;
+       int ret, n;
+-      _enter("%s,%s", name, vllist);
++      _enter("%s,%s,%u", name, vllist, reason);
+-      if (!excl) {
++      if (reason != AFS_LOOKUP_CELL_PRELOAD) {
+               cell = afs_find_cell(net, name, namesz, trace);
+-              if (!IS_ERR(cell))
++              if (!IS_ERR(cell)) {
++                      if (reason == AFS_LOOKUP_CELL_DYNROOT)
++                              goto no_wait;
++                      if (cell->state == AFS_CELL_SETTING_UP ||
++                          cell->state == AFS_CELL_UNLOOKED)
++                              goto lookup_cell;
+                       goto wait_for_cell;
++              }
+       }
+       /* Assume we're probably going to create a cell and preallocate and
+@@ -297,26 +304,69 @@ struct afs_cell *afs_lookup_cell(struct
+       rb_insert_color(&cell->net_node, &net->cells);
+       up_write(&net->cells_lock);
+-      afs_queue_cell(cell, afs_cell_trace_queue_new);
++lookup_cell:
++      if (reason != AFS_LOOKUP_CELL_PRELOAD &&
++          reason != AFS_LOOKUP_CELL_ROOTCELL) {
++              set_bit(AFS_CELL_FL_DO_LOOKUP, &cell->flags);
++              afs_queue_cell(cell, afs_cell_trace_queue_new);
++      }
+ wait_for_cell:
+-      _debug("wait_for_cell");
+       state = smp_load_acquire(&cell->state); /* vs error */
+-      if (state != AFS_CELL_ACTIVE &&
+-          state != AFS_CELL_DEAD) {
++      switch (state) {
++      case AFS_CELL_ACTIVE:
++      case AFS_CELL_DEAD:
++              break;
++      case AFS_CELL_UNLOOKED:
++      default:
++              if (reason == AFS_LOOKUP_CELL_PRELOAD ||
++                  reason == AFS_LOOKUP_CELL_ROOTCELL)
++                      break;
++              _debug("wait_for_cell");
+               afs_see_cell(cell, afs_cell_trace_wait);
+               wait_var_event(&cell->state,
+                              ({
+                                      state = smp_load_acquire(&cell->state); /* vs error */
+                                      state == AFS_CELL_ACTIVE || state == AFS_CELL_DEAD;
+                              }));
++              _debug("waited_for_cell %d %d", cell->state, cell->error);
+       }
++no_wait:
+       /* Check the state obtained from the wait check. */
++      state = smp_load_acquire(&cell->state); /* vs error */
+       if (state == AFS_CELL_DEAD) {
+               ret = cell->error;
+               goto error;
+       }
++      if (state == AFS_CELL_ACTIVE) {
++              switch (cell->dns_status) {
++              case DNS_LOOKUP_NOT_DONE:
++                      if (cell->dns_source == DNS_RECORD_FROM_CONFIG) {
++                              ret = 0;
++                              break;
++                      }
++                      fallthrough;
++              default:
++                      ret = -EIO;
++                      goto error;
++              case DNS_LOOKUP_GOOD:
++              case DNS_LOOKUP_GOOD_WITH_BAD:
++                      ret = 0;
++                      break;
++              case DNS_LOOKUP_GOT_NOT_FOUND:
++                      ret = -ENOENT;
++                      goto error;
++              case DNS_LOOKUP_BAD:
++                      ret = -EREMOTEIO;
++                      goto error;
++              case DNS_LOOKUP_GOT_LOCAL_FAILURE:
++              case DNS_LOOKUP_GOT_TEMP_FAILURE:
++              case DNS_LOOKUP_GOT_NS_FAILURE:
++                      ret = -EDESTADDRREQ;
++                      goto error;
++              }
++      }
+       _leave(" = %p [cell]", cell);
+       return cell;
+@@ -324,7 +374,7 @@ wait_for_cell:
+ cell_already_exists:
+       _debug("cell exists");
+       cell = cursor;
+-      if (excl) {
++      if (reason == AFS_LOOKUP_CELL_PRELOAD) {
+               ret = -EEXIST;
+       } else {
+               afs_use_cell(cursor, trace);
+@@ -383,7 +433,8 @@ int afs_cell_init(struct afs_net *net, c
+               return -EINVAL;
+       /* allocate a cell record for the root/workstation cell */
+-      new_root = afs_lookup_cell(net, rootcell, len, vllist, false,
++      new_root = afs_lookup_cell(net, rootcell, len, vllist,
++                                 AFS_LOOKUP_CELL_ROOTCELL,
+                                  afs_cell_trace_use_lookup_ws);
+       if (IS_ERR(new_root)) {
+               _leave(" = %ld", PTR_ERR(new_root));
+@@ -778,6 +829,7 @@ static bool afs_manage_cell(struct afs_c
+       switch (cell->state) {
+       case AFS_CELL_SETTING_UP:
+               goto set_up_cell;
++      case AFS_CELL_UNLOOKED:
+       case AFS_CELL_ACTIVE:
+               goto cell_is_active;
+       case AFS_CELL_REMOVING:
+@@ -798,7 +850,7 @@ set_up_cell:
+               goto remove_cell;
+       }
+-      afs_set_cell_state(cell, AFS_CELL_ACTIVE);
++      afs_set_cell_state(cell, AFS_CELL_UNLOOKED);
+ cell_is_active:
+       if (afs_has_cell_expired(cell, &next_manage))
+@@ -808,6 +860,8 @@ cell_is_active:
+               ret = afs_update_cell(cell);
+               if (ret < 0)
+                       cell->error = ret;
++              if (cell->state == AFS_CELL_UNLOOKED)
++                      afs_set_cell_state(cell, AFS_CELL_ACTIVE);
+       }
+       if (next_manage < TIME64_MAX && cell->net->live) {
+--- a/fs/afs/dynroot.c
++++ b/fs/afs/dynroot.c
+@@ -108,7 +108,8 @@ static struct dentry *afs_dynroot_lookup
+               dotted = true;
+       }
+-      cell = afs_lookup_cell(net, name, len, NULL, false,
++      cell = afs_lookup_cell(net, name, len, NULL,
++                             AFS_LOOKUP_CELL_DYNROOT,
+                              afs_cell_trace_use_lookup_dynroot);
+       if (IS_ERR(cell)) {
+               ret = PTR_ERR(cell);
+--- a/fs/afs/internal.h
++++ b/fs/afs/internal.h
+@@ -353,6 +353,7 @@ extern const char afs_init_sysname[];
+ enum afs_cell_state {
+       AFS_CELL_SETTING_UP,
++      AFS_CELL_UNLOOKED,
+       AFS_CELL_ACTIVE,
+       AFS_CELL_REMOVING,
+       AFS_CELL_DEAD,
+@@ -1034,9 +1035,18 @@ static inline bool afs_cb_is_broken(unsi
+ extern int afs_cell_init(struct afs_net *, const char *);
+ extern struct afs_cell *afs_find_cell(struct afs_net *, const char *, unsigned,
+                                     enum afs_cell_trace);
++enum afs_lookup_cell_for {
++      AFS_LOOKUP_CELL_DYNROOT,
++      AFS_LOOKUP_CELL_MOUNTPOINT,
++      AFS_LOOKUP_CELL_DIRECT_MOUNT,
++      AFS_LOOKUP_CELL_PRELOAD,
++      AFS_LOOKUP_CELL_ROOTCELL,
++      AFS_LOOKUP_CELL_ALIAS_CHECK,
++};
+ struct afs_cell *afs_lookup_cell(struct afs_net *net,
+                                const char *name, unsigned int namesz,
+-                               const char *vllist, bool excl,
++                               const char *vllist,
++                               enum afs_lookup_cell_for reason,
+                                enum afs_cell_trace trace);
+ extern struct afs_cell *afs_use_cell(struct afs_cell *, enum afs_cell_trace);
+ void afs_unuse_cell(struct afs_cell *cell, enum afs_cell_trace reason);
+--- a/fs/afs/mntpt.c
++++ b/fs/afs/mntpt.c
+@@ -107,7 +107,8 @@ static int afs_mntpt_set_params(struct f
+               if (size > AFS_MAXCELLNAME)
+                       return -ENAMETOOLONG;
+-              cell = afs_lookup_cell(ctx->net, p, size, NULL, false,
++              cell = afs_lookup_cell(ctx->net, p, size, NULL,
++                                     AFS_LOOKUP_CELL_MOUNTPOINT,
+                                      afs_cell_trace_use_lookup_mntpt);
+               if (IS_ERR(cell)) {
+                       pr_err("kAFS: unable to lookup cell '%pd'\n", mntpt);
+--- a/fs/afs/proc.c
++++ b/fs/afs/proc.c
+@@ -122,7 +122,8 @@ static int afs_proc_cells_write(struct f
+       if (strcmp(buf, "add") == 0) {
+               struct afs_cell *cell;
+-              cell = afs_lookup_cell(net, name, strlen(name), args, true,
++              cell = afs_lookup_cell(net, name, strlen(name), args,
++                                     AFS_LOOKUP_CELL_PRELOAD,
+                                      afs_cell_trace_use_lookup_add);
+               if (IS_ERR(cell)) {
+                       ret = PTR_ERR(cell);
+--- a/fs/afs/super.c
++++ b/fs/afs/super.c
+@@ -290,7 +290,7 @@ static int afs_parse_source(struct fs_co
+       /* lookup the cell record */
+       if (cellname) {
+               cell = afs_lookup_cell(ctx->net, cellname, cellnamesz,
+-                                     NULL, false,
++                                     NULL, AFS_LOOKUP_CELL_DIRECT_MOUNT,
+                                      afs_cell_trace_use_lookup_mount);
+               if (IS_ERR(cell)) {
+                       pr_err("kAFS: unable to lookup cell '%*.*s'\n",
+--- a/fs/afs/vl_alias.c
++++ b/fs/afs/vl_alias.c
+@@ -269,7 +269,8 @@ static int yfs_check_canonical_cell_name
+       if (!name_len || name_len > AFS_MAXCELLNAME)
+               master = ERR_PTR(-EOPNOTSUPP);
+       else
+-              master = afs_lookup_cell(cell->net, cell_name, name_len, NULL, false,
++              master = afs_lookup_cell(cell->net, cell_name, name_len, NULL,
++                                       AFS_LOOKUP_CELL_ALIAS_CHECK,
+                                        afs_cell_trace_use_lookup_canonical);
+       kfree(cell_name);
+       if (IS_ERR(master))
diff --git a/queue-6.12/afs-fix-lack-of-locking-around-modifications-of-net-cells_dyn_ino.patch b/queue-6.12/afs-fix-lack-of-locking-around-modifications-of-net-cells_dyn_ino.patch
new file mode 100644 (file)
index 0000000..88dc318
--- /dev/null
@@ -0,0 +1,77 @@
+From stable+bounces-290959-greg=kroah.com@vger.kernel.org Thu Jul 30 03:06:42 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 21:01:39 -0400
+Subject: afs: Fix lack of locking around modifications of net->cells_dyn_ino
+To: stable@vger.kernel.org
+Cc: David Howells <dhowells@redhat.com>, Marc Dionne <marc.dionne@auristor.com>, linux-afs@lists.infradead.org, "Christian Brauner (Amutable)" <brauner@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260730010140.1700208-8-sashal@kernel.org>
+
+From: David Howells <dhowells@redhat.com>
+
+[ Upstream commit 55e841836c6f4646490f7b0347192b7a92d431ba ]
+
+Fix the lack of locking around modifications of net->cells_dyn_ino by
+taking net->cells_lock exclusively.  This also requires to cell to be
+removed from net->cells_dyn_ino in afs_destroy_cell_work() rather than in
+afs_cell_destroy() as the latter runs in RCU cleanup context and sleeping
+locks cannot be taken there.
+
+Fixes: 1d0b929fc070 ("afs: Change dynroot to create contents on demand")
+Closes: https://sashiko.dev/#/patchset/20260618074903.2374756-1-dhowells%40redhat.com
+Signed-off-by: David Howells <dhowells@redhat.com>
+Link: https://patch.msgid.link/20260622090856.2746629-19-dhowells@redhat.com
+cc: Marc Dionne <marc.dionne@auristor.com>
+cc: linux-afs@lists.infradead.org
+Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
+Stable-dep-of: 26f17ce6fa3f ("afs: Fix premature cell exposure through /afs")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/afs/cell.c    |    8 +++++++-
+ fs/afs/dynroot.c |    2 +-
+ 2 files changed, 8 insertions(+), 2 deletions(-)
+
+--- a/fs/afs/cell.c
++++ b/fs/afs/cell.c
+@@ -198,8 +198,10 @@ static struct afs_cell *afs_alloc_cell(s
+       cell->dns_source = vllist->source;
+       cell->dns_status = vllist->status;
+       smp_store_release(&cell->dns_lookup_count, 1); /* vs source/status */
++      down_write(&net->cells_lock);
+       ret = idr_alloc_cyclic(&net->cells_dyn_ino, cell,
+                              2, INT_MAX / 2, GFP_KERNEL);
++      up_write(&net->cells_lock);
+       if (ret < 0)
+               goto error;
+       atomic_inc(&net->cells_outstanding);
+@@ -572,7 +574,6 @@ static void afs_cell_destroy(struct rcu_
+       afs_put_vlserverlist(net, rcu_access_pointer(cell->vl_servers));
+       afs_unuse_cell(cell->alias_of, afs_cell_trace_unuse_alias);
+       key_put(cell->anonymous_key);
+-      idr_remove(&net->cells_dyn_ino, cell->dynroot_ino);
+       kfree(cell->name - 1);
+       kfree(cell);
+@@ -587,6 +588,11 @@ static void afs_destroy_cell_work(struct
+       afs_see_cell(cell, afs_cell_trace_destroy);
+       timer_delete_sync(&cell->management_timer);
+       cancel_work_sync(&cell->manager);
++
++      down_write(&cell->net->cells_lock);
++      idr_remove(&cell->net->cells_dyn_ino, cell->dynroot_ino);
++      up_write(&cell->net->cells_lock);
++
+       call_rcu(&cell->rcu, afs_cell_destroy);
+ }
+--- a/fs/afs/dynroot.c
++++ b/fs/afs/dynroot.c
+@@ -278,7 +278,7 @@ static struct dentry *afs_lookup_atcell(
+ }
+ /*
+- * Transcribe the cell database into readdir content under the RCU read lock.
++ * Transcribe the cell database into readdir content under net->cells_lock.
+  * Each cell produces two entries, one prefixed with a dot and one not.
+  */
+ static int afs_dynroot_readdir_cells(struct afs_net *net, struct dir_context *ctx)
diff --git a/queue-6.12/afs-improve-server-refcount-active-count-tracing.patch b/queue-6.12/afs-improve-server-refcount-active-count-tracing.patch
new file mode 100644 (file)
index 0000000..e482b35
--- /dev/null
@@ -0,0 +1,181 @@
+From stable+bounces-290944-greg=kroah.com@vger.kernel.org Thu Jul 30 03:02:25 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 21:01:30 -0400
+Subject: afs: Improve server refcount/active count tracing
+To: stable@vger.kernel.org
+Cc: David Howells <dhowells@redhat.com>, Marc Dionne <marc.dionne@auristor.com>, linux-afs@lists.infradead.org, linux-fsdevel@vger.kernel.org, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260730010137.1700032-1-sashal@kernel.org>
+
+From: David Howells <dhowells@redhat.com>
+
+[ Upstream commit 76daa300d41acc1180f8a46eead36905054beafb ]
+
+Improve server refcount/active count tracing to distinguish between simply
+getting/putting a ref and using/unusing the server record (which changes
+the activity count as well as the refcount).  This makes it a bit easier to
+work out what's going on.
+
+Signed-off-by: David Howells <dhowells@redhat.com>
+cc: Marc Dionne <marc.dionne@auristor.com>
+cc: linux-afs@lists.infradead.org
+cc: linux-fsdevel@vger.kernel.org
+Link: https://lore.kernel.org/r/20250224234154.2014840-10-dhowells@redhat.com/ # v1
+Link: https://lore.kernel.org/r/20250310094206.801057-6-dhowells@redhat.com/ # v4
+Stable-dep-of: 330e2c514823 ("afs: Fix dynamic lookup to fail on cell lookup failure")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/afs/fsclient.c          |    4 ++--
+ fs/afs/rxrpc.c             |    2 +-
+ fs/afs/server.c            |   11 ++++++-----
+ fs/afs/server_list.c       |    4 ++--
+ include/trace/events/afs.h |   27 +++++++++++++++------------
+ 5 files changed, 26 insertions(+), 22 deletions(-)
+
+--- a/fs/afs/fsclient.c
++++ b/fs/afs/fsclient.c
+@@ -1650,7 +1650,7 @@ int afs_fs_give_up_all_callbacks(struct
+       bp = call->request;
+       *bp++ = htonl(FSGIVEUPALLCALLBACKS);
+-      call->server = afs_use_server(server, afs_server_trace_give_up_cb);
++      call->server = afs_use_server(server, afs_server_trace_use_give_up_cb);
+       afs_make_call(call, GFP_NOFS);
+       afs_wait_for_call_to_complete(call);
+       ret = call->error;
+@@ -1756,7 +1756,7 @@ bool afs_fs_get_capabilities(struct afs_
+               return false;
+       call->key       = key;
+-      call->server    = afs_use_server(server, afs_server_trace_get_caps);
++      call->server    = afs_use_server(server, afs_server_trace_use_get_caps);
+       call->peer      = rxrpc_kernel_get_peer(estate->addresses->addrs[addr_index].peer);
+       call->probe     = afs_get_endpoint_state(estate, afs_estate_trace_get_getcaps);
+       call->probe_index = addr_index;
+--- a/fs/afs/rxrpc.c
++++ b/fs/afs/rxrpc.c
+@@ -190,7 +190,7 @@ static void afs_free_call(struct afs_cal
+       if (call->type->destructor)
+               call->type->destructor(call);
+-      afs_unuse_server_notime(call->net, call->server, afs_server_trace_put_call);
++      afs_unuse_server_notime(call->net, call->server, afs_server_trace_unuse_call);
+       kfree(call->request);
+       o = atomic_read(&net->nr_outstanding_calls);
+--- a/fs/afs/server.c
++++ b/fs/afs/server.c
+@@ -33,7 +33,7 @@ struct afs_server *afs_find_server(struc
+       do {
+               if (server)
+-                      afs_unuse_server_notime(net, server, afs_server_trace_put_find_rsq);
++                      afs_unuse_server_notime(net, server, afs_server_trace_unuse_find_rsq);
+               server = NULL;
+               seq++; /* 2 on the 1st/lockless path, otherwise odd */
+               read_seqbegin_or_lock(&net->fs_addr_lock, &seq);
+@@ -49,7 +49,7 @@ struct afs_server *afs_find_server(struc
+               server = NULL;
+               continue;
+       found:
+-              server = afs_maybe_use_server(server, afs_server_trace_get_by_addr);
++              server = afs_maybe_use_server(server, afs_server_trace_use_by_addr);
+       } while (need_seqretry(&net->fs_addr_lock, seq));
+@@ -76,7 +76,7 @@ struct afs_server *afs_find_server_by_uu
+                * changes.
+                */
+               if (server)
+-                      afs_unuse_server(net, server, afs_server_trace_put_uuid_rsq);
++                      afs_unuse_server(net, server, afs_server_trace_unuse_uuid_rsq);
+               server = NULL;
+               seq++; /* 2 on the 1st/lockless path, otherwise odd */
+               read_seqbegin_or_lock(&net->fs_lock, &seq);
+@@ -91,7 +91,7 @@ struct afs_server *afs_find_server_by_uu
+                       } else if (diff > 0) {
+                               p = p->rb_right;
+                       } else {
+-                              afs_use_server(server, afs_server_trace_get_by_uuid);
++                              afs_use_server(server, afs_server_trace_use_by_uuid);
+                               break;
+                       }
+@@ -273,7 +273,8 @@ static struct afs_addr_list *afs_vl_look
+ }
+ /*
+- * Get or create a fileserver record.
++ * Get or create a fileserver record and return it with an active-use count on
++ * it.
+  */
+ struct afs_server *afs_lookup_server(struct afs_cell *cell, struct key *key,
+                                    const uuid_t *uuid, u32 addr_version)
+--- a/fs/afs/server_list.c
++++ b/fs/afs/server_list.c
+@@ -16,7 +16,7 @@ void afs_put_serverlist(struct afs_net *
+       if (slist && refcount_dec_and_test(&slist->usage)) {
+               for (i = 0; i < slist->nr_servers; i++)
+                       afs_unuse_server(net, slist->servers[i].server,
+-                                       afs_server_trace_put_slist);
++                                       afs_server_trace_unuse_slist);
+               kfree_rcu(slist, rcu);
+       }
+ }
+@@ -98,7 +98,7 @@ struct afs_server_list *afs_alloc_server
+               if (j < slist->nr_servers) {
+                       if (slist->servers[j].server == server) {
+                               afs_unuse_server(volume->cell->net, server,
+-                                               afs_server_trace_put_slist_isort);
++                                               afs_server_trace_unuse_slist_isort);
+                               continue;
+                       }
+--- a/include/trace/events/afs.h
++++ b/include/trace/events/afs.h
+@@ -132,22 +132,25 @@ enum yfs_cm_operation {
+       EM(afs_server_trace_destroy,            "DESTROY  ") \
+       EM(afs_server_trace_free,               "FREE     ") \
+       EM(afs_server_trace_gc,                 "GC       ") \
+-      EM(afs_server_trace_get_by_addr,        "GET addr ") \
+-      EM(afs_server_trace_get_by_uuid,        "GET uuid ") \
+-      EM(afs_server_trace_get_caps,           "GET caps ") \
+       EM(afs_server_trace_get_install,        "GET inst ") \
+-      EM(afs_server_trace_get_new_cbi,        "GET cbi  ") \
+       EM(afs_server_trace_get_probe,          "GET probe") \
+-      EM(afs_server_trace_give_up_cb,         "giveup-cb") \
+       EM(afs_server_trace_purging,            "PURGE    ") \
+-      EM(afs_server_trace_put_call,           "PUT call ") \
+       EM(afs_server_trace_put_cbi,            "PUT cbi  ") \
+-      EM(afs_server_trace_put_find_rsq,       "PUT f-rsq") \
+       EM(afs_server_trace_put_probe,          "PUT probe") \
+-      EM(afs_server_trace_put_slist,          "PUT slist") \
+-      EM(afs_server_trace_put_slist_isort,    "PUT isort") \
+-      EM(afs_server_trace_put_uuid_rsq,       "PUT u-req") \
+-      E_(afs_server_trace_update,             "UPDATE")
++      EM(afs_server_trace_see_expired,        "SEE expd ") \
++      EM(afs_server_trace_unuse_call,         "UNU call ") \
++      EM(afs_server_trace_unuse_create_fail,  "UNU cfail") \
++      EM(afs_server_trace_unuse_find_rsq,     "UNU f-rsq") \
++      EM(afs_server_trace_unuse_slist,        "UNU slist") \
++      EM(afs_server_trace_unuse_slist_isort,  "UNU isort") \
++      EM(afs_server_trace_unuse_uuid_rsq,     "PUT u-req") \
++      EM(afs_server_trace_update,             "UPDATE   ") \
++      EM(afs_server_trace_use_by_addr,        "USE addr ") \
++      EM(afs_server_trace_use_by_uuid,        "USE uuid ") \
++      EM(afs_server_trace_use_cm_call,        "USE cm-cl") \
++      EM(afs_server_trace_use_get_caps,       "USE gcaps") \
++      EM(afs_server_trace_use_give_up_cb,     "USE gvupc") \
++      E_(afs_server_trace_wait_create,        "WAIT crt ")
+ #define afs_volume_traces \
+       EM(afs_volume_trace_alloc,              "ALLOC         ") \
+@@ -1369,7 +1372,7 @@ TRACE_EVENT(afs_server,
+                   __entry->reason = reason;
+                          ),
+-          TP_printk("s=%08x %s u=%d a=%d",
++          TP_printk("s=%08x %s r=%d a=%d",
+                     __entry->server,
+                     __print_symbolic(__entry->reason, afs_server_traces),
+                     __entry->ref,
diff --git a/queue-6.12/afs-make-afs_lookup_cell-take-a-trace-note.patch b/queue-6.12/afs-make-afs_lookup_cell-take-a-trace-note.patch
new file mode 100644 (file)
index 0000000..1cffd08
--- /dev/null
@@ -0,0 +1,176 @@
+From stable+bounces-290945-greg=kroah.com@vger.kernel.org Thu Jul 30 03:02:38 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 21:01:31 -0400
+Subject: afs: Make afs_lookup_cell() take a trace note
+To: stable@vger.kernel.org
+Cc: David Howells <dhowells@redhat.com>, Marc Dionne <marc.dionne@auristor.com>, linux-afs@lists.infradead.org, linux-fsdevel@vger.kernel.org, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260730010137.1700032-2-sashal@kernel.org>
+
+From: David Howells <dhowells@redhat.com>
+
+[ Upstream commit 92c48157ade88e7a543a64af4a806613fbde2ef3 ]
+
+Pass a note to be added to the afs_cell tracepoint to afs_lookup_cell() so
+that different callers can be distinguished.
+
+Signed-off-by: David Howells <dhowells@redhat.com>
+cc: Marc Dionne <marc.dionne@auristor.com>
+cc: linux-afs@lists.infradead.org
+cc: linux-fsdevel@vger.kernel.org
+Link: https://lore.kernel.org/r/20250224234154.2014840-11-dhowells@redhat.com/ # v1
+Link: https://lore.kernel.org/r/20250310094206.801057-7-dhowells@redhat.com/ # v4
+Stable-dep-of: 330e2c514823 ("afs: Fix dynamic lookup to fail on cell lookup failure")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/afs/cell.c              |   13 ++++++++-----
+ fs/afs/dynroot.c           |    3 ++-
+ fs/afs/internal.h          |    6 ++++--
+ fs/afs/mntpt.c             |    3 ++-
+ fs/afs/proc.c              |    3 ++-
+ fs/afs/super.c             |    3 ++-
+ fs/afs/vl_alias.c          |    3 ++-
+ include/trace/events/afs.h |    7 ++++++-
+ 8 files changed, 28 insertions(+), 13 deletions(-)
+
+--- a/fs/afs/cell.c
++++ b/fs/afs/cell.c
+@@ -233,6 +233,7 @@ error:
+  * @namesz:   The strlen of the cell name.
+  * @vllist:   A colon/comma separated list of numeric IP addresses or NULL.
+  * @excl:     T if an error should be given if the cell name already exists.
++ * @trace:    The reason to be logged if the lookup is successful.
+  *
+  * Look up a cell record by name and query the DNS for VL server addresses if
+  * needed.  Note that that actual DNS query is punted off to the manager thread
+@@ -241,7 +242,8 @@ error:
+  */
+ struct afs_cell *afs_lookup_cell(struct afs_net *net,
+                                const char *name, unsigned int namesz,
+-                               const char *vllist, bool excl)
++                               const char *vllist, bool excl,
++                               enum afs_cell_trace trace)
+ {
+       struct afs_cell *cell, *candidate, *cursor;
+       struct rb_node *parent, **pp;
+@@ -251,7 +253,7 @@ struct afs_cell *afs_lookup_cell(struct
+       _enter("%s,%s", name, vllist);
+       if (!excl) {
+-              cell = afs_find_cell(net, name, namesz, afs_cell_trace_use_lookup);
++              cell = afs_find_cell(net, name, namesz, trace);
+               if (!IS_ERR(cell))
+                       goto wait_for_cell;
+       }
+@@ -327,7 +329,7 @@ cell_already_exists:
+       if (excl) {
+               ret = -EEXIST;
+       } else {
+-              afs_use_cell(cursor, afs_cell_trace_use_lookup);
++              afs_use_cell(cursor, trace);
+               ret = 0;
+       }
+       up_write(&net->cells_lock);
+@@ -382,8 +384,9 @@ int afs_cell_init(struct afs_net *net, c
+       if (cp && cp < rootcell + len)
+               return -EINVAL;
+-      /* allocate a cell record for the root cell */
+-      new_root = afs_lookup_cell(net, rootcell, len, vllist, false);
++      /* allocate a cell record for the root/workstation cell */
++      new_root = afs_lookup_cell(net, rootcell, len, vllist, false,
++                                 afs_cell_trace_use_lookup_ws);
+       if (IS_ERR(new_root)) {
+               _leave(" = %ld", PTR_ERR(new_root));
+               return PTR_ERR(new_root);
+--- a/fs/afs/dynroot.c
++++ b/fs/afs/dynroot.c
+@@ -108,7 +108,8 @@ static struct dentry *afs_dynroot_lookup
+               dotted = true;
+       }
+-      cell = afs_lookup_cell(net, name, len, NULL, false);
++      cell = afs_lookup_cell(net, name, len, NULL, false,
++                             afs_cell_trace_use_lookup_dynroot);
+       if (IS_ERR(cell)) {
+               ret = PTR_ERR(cell);
+               goto out_no_cell;
+--- a/fs/afs/internal.h
++++ b/fs/afs/internal.h
+@@ -1040,8 +1040,10 @@ static inline bool afs_cb_is_broken(unsi
+ extern int afs_cell_init(struct afs_net *, const char *);
+ extern struct afs_cell *afs_find_cell(struct afs_net *, const char *, unsigned,
+                                     enum afs_cell_trace);
+-extern struct afs_cell *afs_lookup_cell(struct afs_net *, const char *, unsigned,
+-                                      const char *, bool);
++struct afs_cell *afs_lookup_cell(struct afs_net *net,
++                               const char *name, unsigned int namesz,
++                               const char *vllist, bool excl,
++                               enum afs_cell_trace trace);
+ extern struct afs_cell *afs_use_cell(struct afs_cell *, enum afs_cell_trace);
+ extern void afs_unuse_cell(struct afs_net *, struct afs_cell *, enum afs_cell_trace);
+ extern struct afs_cell *afs_get_cell(struct afs_cell *, enum afs_cell_trace);
+--- a/fs/afs/mntpt.c
++++ b/fs/afs/mntpt.c
+@@ -107,7 +107,8 @@ static int afs_mntpt_set_params(struct f
+               if (size > AFS_MAXCELLNAME)
+                       return -ENAMETOOLONG;
+-              cell = afs_lookup_cell(ctx->net, p, size, NULL, false);
++              cell = afs_lookup_cell(ctx->net, p, size, NULL, false,
++                                     afs_cell_trace_use_lookup_mntpt);
+               if (IS_ERR(cell)) {
+                       pr_err("kAFS: unable to lookup cell '%pd'\n", mntpt);
+                       return PTR_ERR(cell);
+--- a/fs/afs/proc.c
++++ b/fs/afs/proc.c
+@@ -122,7 +122,8 @@ static int afs_proc_cells_write(struct f
+       if (strcmp(buf, "add") == 0) {
+               struct afs_cell *cell;
+-              cell = afs_lookup_cell(net, name, strlen(name), args, true);
++              cell = afs_lookup_cell(net, name, strlen(name), args, true,
++                                     afs_cell_trace_use_lookup_add);
+               if (IS_ERR(cell)) {
+                       ret = PTR_ERR(cell);
+                       goto done;
+--- a/fs/afs/super.c
++++ b/fs/afs/super.c
+@@ -290,7 +290,8 @@ static int afs_parse_source(struct fs_co
+       /* lookup the cell record */
+       if (cellname) {
+               cell = afs_lookup_cell(ctx->net, cellname, cellnamesz,
+-                                     NULL, false);
++                                     NULL, false,
++                                     afs_cell_trace_use_lookup_mount);
+               if (IS_ERR(cell)) {
+                       pr_err("kAFS: unable to lookup cell '%*.*s'\n",
+                              cellnamesz, cellnamesz, cellname ?: "");
+--- a/fs/afs/vl_alias.c
++++ b/fs/afs/vl_alias.c
+@@ -269,7 +269,8 @@ static int yfs_check_canonical_cell_name
+       if (!name_len || name_len > AFS_MAXCELLNAME)
+               master = ERR_PTR(-EOPNOTSUPP);
+       else
+-              master = afs_lookup_cell(cell->net, cell_name, name_len, NULL, false);
++              master = afs_lookup_cell(cell->net, cell_name, name_len, NULL, false,
++                                       afs_cell_trace_use_lookup_canonical);
+       kfree(cell_name);
+       if (IS_ERR(master))
+               return PTR_ERR(master);
+--- a/include/trace/events/afs.h
++++ b/include/trace/events/afs.h
+@@ -208,7 +208,12 @@ enum yfs_cm_operation {
+       EM(afs_cell_trace_use_check_alias,      "USE chk-al") \
+       EM(afs_cell_trace_use_fc,               "USE fc    ") \
+       EM(afs_cell_trace_use_fc_alias,         "USE fc-al ") \
+-      EM(afs_cell_trace_use_lookup,           "USE lookup") \
++      EM(afs_cell_trace_use_lookup_add,       "USE lu-add") \
++      EM(afs_cell_trace_use_lookup_canonical, "USE lu-can") \
++      EM(afs_cell_trace_use_lookup_dynroot,   "USE lu-dyn") \
++      EM(afs_cell_trace_use_lookup_mntpt,     "USE lu-mpt") \
++      EM(afs_cell_trace_use_lookup_mount,     "USE lu-mnt") \
++      EM(afs_cell_trace_use_lookup_ws,        "USE lu-ws ") \
+       EM(afs_cell_trace_use_mntpt,            "USE mntpt ") \
+       EM(afs_cell_trace_use_pin,              "USE pin   ") \
+       EM(afs_cell_trace_use_probe,            "USE probe ") \
diff --git a/queue-6.12/afs-simplify-cell-record-handling.patch b/queue-6.12/afs-simplify-cell-record-handling.patch
new file mode 100644 (file)
index 0000000..6140630
--- /dev/null
@@ -0,0 +1,842 @@
+From stable+bounces-290953-greg=kroah.com@vger.kernel.org Thu Jul 30 03:02:18 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 21:01:36 -0400
+Subject: afs: Simplify cell record handling
+To: stable@vger.kernel.org
+Cc: David Howells <dhowells@redhat.com>, Marc Dionne <marc.dionne@auristor.com>, linux-afs@lists.infradead.org, linux-fsdevel@vger.kernel.org, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260730010137.1700032-7-sashal@kernel.org>
+
+From: David Howells <dhowells@redhat.com>
+
+[ Upstream commit e2c2cb8ef07affd9f69497ea128fa801240fdf32 ]
+
+Simplify afs_cell record handling to avoid very occasional races that cause
+module removal to hang (it waits for all cell records to be removed).
+
+There are two things that particularly contribute to the difficulty:
+firstly, the code tries to pass a ref on the cell to the cell's maintenance
+work item (which gets awkward if the work item is already queued); and,
+secondly, there's an overall cell manager that tries to use just one timer
+for the entire cell collection (to avoid having loads of timers).  However,
+both of these are probably unnecessarily restrictive.
+
+To simplify this, the following changes are made:
+
+ (1) The cell record collection manager is removed.  Each cell record
+     manages itself individually.
+
+ (2) Each afs_cell is given a second work item (cell->destroyer) that is
+     queued when its refcount reaches zero.  This is not done in the
+     context of the putting thread as it might be in an inconvenient place
+     to sleep.
+
+ (3) Each afs_cell is given its own timer.  The timer is used to expire the
+     cell record after a period of unuse if not otherwise pinned and can
+     also be used for other maintenance tasks if necessary (of which there
+     are currently none as DNS refresh is triggered by filesystem
+     operations).
+
+ (4) The afs_cell manager work item (cell->manager) is no longer given a
+     ref on the cell when queued; rather, the manager must be deleted.
+     This does away with the need to deal with the consequences of losing a
+     race to queue cell->manager.  Clean up of extra queuing is deferred to
+     the destroyer.
+
+ (5) The cell destroyer work item makes sure the cell timer is removed and
+     that the normal cell work is cancelled before farming the actual
+     destruction off to RCU.
+
+ (6) When a network namespace is destroyed or the kafs module is unloaded,
+     it's now a simple matter of marking the namespace as dead then just
+     waking up all the cell work items.  They will then remove and destroy
+     themselves once all remaining activity counts and/or a ref counts are
+     dropped.  This makes sure that all server records are dropped first.
+
+ (7) The cell record state set is reduced to just four states: SETTING_UP,
+     ACTIVE, REMOVING and DEAD.  The record persists in the active state
+     even when it's not being used until the time comes to remove it rather
+     than downgrading it to an inactive state from whence it can be
+     restored.
+
+     This means that the cell still appears in /proc and /afs when not in
+     use until it switches to the REMOVING state - at which point it is
+     removed.
+
+     Note that the REMOVING state is included so that someone wanting to
+     resurrect the cell record is forced to wait whilst the cell is torn
+     down in that state.  Once it's in the DEAD state, it has been removed
+     from net->cells tree and is no longer findable and can be replaced.
+
+Signed-off-by: David Howells <dhowells@redhat.com>
+cc: Marc Dionne <marc.dionne@auristor.com>
+cc: linux-afs@lists.infradead.org
+cc: linux-fsdevel@vger.kernel.org
+Link: https://lore.kernel.org/r/20250224234154.2014840-16-dhowells@redhat.com/ # v1
+Link: https://lore.kernel.org/r/20250310094206.801057-12-dhowells@redhat.com/ # v4
+Stable-dep-of: 330e2c514823 ("afs: Fix dynamic lookup to fail on cell lookup failure")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/afs/cell.c              |  404 ++++++++++++++++++---------------------------
+ fs/afs/dynroot.c           |    4 
+ fs/afs/internal.h          |   16 -
+ fs/afs/main.c              |    3 
+ fs/afs/server.c            |    8 
+ fs/afs/vl_rotate.c         |    2 
+ include/trace/events/afs.h |   23 +-
+ 7 files changed, 187 insertions(+), 273 deletions(-)
+
+--- a/fs/afs/cell.c
++++ b/fs/afs/cell.c
+@@ -20,8 +20,9 @@ static unsigned __read_mostly afs_cell_m
+ static unsigned __read_mostly afs_cell_max_ttl = 24 * 60 * 60;
+ static atomic_t cell_debug_id;
+-static void afs_queue_cell_manager(struct afs_net *);
+-static void afs_manage_cell_work(struct work_struct *);
++static void afs_cell_timer(struct timer_list *timer);
++static void afs_destroy_cell_work(struct work_struct *work);
++static void afs_manage_cell_work(struct work_struct *work);
+ static void afs_dec_cells_outstanding(struct afs_net *net)
+ {
+@@ -29,19 +30,11 @@ static void afs_dec_cells_outstanding(st
+               wake_up_var(&net->cells_outstanding);
+ }
+-/*
+- * Set the cell timer to fire after a given delay, assuming it's not already
+- * set for an earlier time.
+- */
+-static void afs_set_cell_timer(struct afs_net *net, time64_t delay)
++static void afs_set_cell_state(struct afs_cell *cell, enum afs_cell_state state)
+ {
+-      if (net->live) {
+-              atomic_inc(&net->cells_outstanding);
+-              if (timer_reduce(&net->cells_timer, jiffies + delay * HZ))
+-                      afs_dec_cells_outstanding(net);
+-      } else {
+-              afs_queue_cell_manager(net);
+-      }
++      smp_store_release(&cell->state, state); /* Commit cell changes before state */
++      smp_wmb(); /* Set cell state before task state */
++      wake_up_var(&cell->state);
+ }
+ /*
+@@ -116,7 +109,7 @@ static struct afs_cell *afs_alloc_cell(s
+                                      const char *name, unsigned int namelen,
+                                      const char *addresses)
+ {
+-      struct afs_vlserver_list *vllist;
++      struct afs_vlserver_list *vllist = NULL;
+       struct afs_cell *cell;
+       int i, ret;
+@@ -163,7 +156,9 @@ static struct afs_cell *afs_alloc_cell(s
+       cell->net = net;
+       refcount_set(&cell->ref, 1);
+       atomic_set(&cell->active, 0);
++      INIT_WORK(&cell->destroyer, afs_destroy_cell_work);
+       INIT_WORK(&cell->manager, afs_manage_cell_work);
++      timer_setup(&cell->management_timer, afs_cell_timer, 0);
+       init_rwsem(&cell->vs_lock);
+       cell->volumes = RB_ROOT;
+       INIT_HLIST_HEAD(&cell->proc_volumes);
+@@ -220,6 +215,7 @@ parse_failed:
+       if (ret == -EINVAL)
+               printk(KERN_ERR "kAFS: bad VL server IP address\n");
+ error:
++      afs_put_vlserverlist(cell->net, vllist);
+       kfree(cell->name - 1);
+       kfree(cell);
+       _leave(" = %d", ret);
+@@ -296,26 +292,28 @@ struct afs_cell *afs_lookup_cell(struct
+       cell = candidate;
+       candidate = NULL;
+-      atomic_set(&cell->active, 2);
+-      trace_afs_cell(cell->debug_id, refcount_read(&cell->ref), 2, afs_cell_trace_insert);
++      afs_use_cell(cell, trace);
+       rb_link_node_rcu(&cell->net_node, parent, pp);
+       rb_insert_color(&cell->net_node, &net->cells);
+       up_write(&net->cells_lock);
+-      afs_queue_cell(cell, afs_cell_trace_get_queue_new);
++      afs_queue_cell(cell, afs_cell_trace_queue_new);
+ wait_for_cell:
+-      trace_afs_cell(cell->debug_id, refcount_read(&cell->ref), atomic_read(&cell->active),
+-                     afs_cell_trace_wait);
+       _debug("wait_for_cell");
+-      wait_var_event(&cell->state,
+-                     ({
+-                             state = smp_load_acquire(&cell->state); /* vs error */
+-                             state == AFS_CELL_ACTIVE || state == AFS_CELL_REMOVED;
+-                     }));
++      state = smp_load_acquire(&cell->state); /* vs error */
++      if (state != AFS_CELL_ACTIVE &&
++          state != AFS_CELL_DEAD) {
++              afs_see_cell(cell, afs_cell_trace_wait);
++              wait_var_event(&cell->state,
++                             ({
++                                     state = smp_load_acquire(&cell->state); /* vs error */
++                                     state == AFS_CELL_ACTIVE || state == AFS_CELL_DEAD;
++                             }));
++      }
+       /* Check the state obtained from the wait check. */
+-      if (state == AFS_CELL_REMOVED) {
++      if (state == AFS_CELL_DEAD) {
+               ret = cell->error;
+               goto error;
+       }
+@@ -397,7 +395,6 @@ int afs_cell_init(struct afs_net *net, c
+       /* install the new cell */
+       down_write(&net->cells_lock);
+-      afs_see_cell(new_root, afs_cell_trace_see_ws);
+       old_root = rcu_replace_pointer(net->ws_cell, new_root,
+                                      lockdep_is_held(&net->cells_lock));
+       up_write(&net->cells_lock);
+@@ -532,30 +529,14 @@ static void afs_cell_destroy(struct rcu_
+       _leave(" [destroyed]");
+ }
+-/*
+- * Queue the cell manager.
+- */
+-static void afs_queue_cell_manager(struct afs_net *net)
+-{
+-      int outstanding = atomic_inc_return(&net->cells_outstanding);
+-
+-      _enter("%d", outstanding);
+-
+-      if (!queue_work(afs_wq, &net->cells_manager))
+-              afs_dec_cells_outstanding(net);
+-}
+-
+-/*
+- * Cell management timer.  We have an increment on cells_outstanding that we
+- * need to pass along to the work item.
+- */
+-void afs_cells_timer(struct timer_list *timer)
++static void afs_destroy_cell_work(struct work_struct *work)
+ {
+-      struct afs_net *net = container_of(timer, struct afs_net, cells_timer);
++      struct afs_cell *cell = container_of(work, struct afs_cell, destroyer);
+-      _enter("");
+-      if (!queue_work(afs_wq, &net->cells_manager))
+-              afs_dec_cells_outstanding(net);
++      afs_see_cell(cell, afs_cell_trace_destroy);
++      timer_delete_sync(&cell->management_timer);
++      cancel_work_sync(&cell->manager);
++      call_rcu(&cell->rcu, afs_cell_destroy);
+ }
+ /*
+@@ -587,7 +568,7 @@ void afs_put_cell(struct afs_cell *cell,
+               if (zero) {
+                       a = atomic_read(&cell->active);
+                       WARN(a != 0, "Cell active count %u > 0\n", a);
+-                      call_rcu(&cell->rcu, afs_cell_destroy);
++                      WARN_ON(!queue_work(afs_wq, &cell->destroyer));
+               }
+       }
+ }
+@@ -599,10 +580,9 @@ struct afs_cell *afs_use_cell(struct afs
+ {
+       int r, a;
+-      r = refcount_read(&cell->ref);
+-      WARN_ON(r == 0);
++      __refcount_inc(&cell->ref, &r);
+       a = atomic_inc_return(&cell->active);
+-      trace_afs_cell(cell->debug_id, r, a, reason);
++      trace_afs_cell(cell->debug_id, r + 1, a, reason);
+       return cell;
+ }
+@@ -614,6 +594,7 @@ void afs_unuse_cell(struct afs_cell *cel
+ {
+       unsigned int debug_id;
+       time64_t now, expire_delay;
++      bool zero;
+       int r, a;
+       if (!cell)
+@@ -628,13 +609,15 @@ void afs_unuse_cell(struct afs_cell *cel
+               expire_delay = afs_cell_gc_delay;
+       debug_id = cell->debug_id;
+-      r = refcount_read(&cell->ref);
+       a = atomic_dec_return(&cell->active);
+-      trace_afs_cell(debug_id, r, a, reason);
+-      WARN_ON(a == 0);
+-      if (a == 1)
++      if (!a)
+               /* 'cell' may now be garbage collected. */
+-              afs_set_cell_timer(cell->net, expire_delay);
++              afs_set_cell_timer(cell, expire_delay);
++
++      zero = __refcount_dec_and_test(&cell->ref, &r);
++      trace_afs_cell(debug_id, r - 1, a, reason);
++      if (zero)
++              WARN_ON(!queue_work(afs_wq, &cell->destroyer));
+ }
+ /*
+@@ -654,9 +637,27 @@ void afs_see_cell(struct afs_cell *cell,
+  */
+ void afs_queue_cell(struct afs_cell *cell, enum afs_cell_trace reason)
+ {
+-      afs_get_cell(cell, reason);
+-      if (!queue_work(afs_wq, &cell->manager))
+-              afs_put_cell(cell, afs_cell_trace_put_queue_fail);
++      queue_work(afs_wq, &cell->manager);
++}
++
++/*
++ * Cell-specific management timer.
++ */
++static void afs_cell_timer(struct timer_list *timer)
++{
++      struct afs_cell *cell = container_of(timer, struct afs_cell, management_timer);
++
++      afs_see_cell(cell, afs_cell_trace_see_mgmt_timer);
++      if (refcount_read(&cell->ref) > 0 && cell->net->live)
++              queue_work(afs_wq, &cell->manager);
++}
++
++/*
++ * Set/reduce the cell timer.
++ */
++void afs_set_cell_timer(struct afs_cell *cell, unsigned int delay_secs)
++{
++      timer_reduce(&cell->management_timer, jiffies + delay_secs * HZ);
+ }
+ /*
+@@ -739,212 +740,125 @@ static void afs_deactivate_cell(struct a
+       _leave("");
+ }
++static bool afs_has_cell_expired(struct afs_cell *cell, time64_t *_next_manage)
++{
++      const struct afs_vlserver_list *vllist;
++      time64_t expire_at = cell->last_inactive;
++      time64_t now = ktime_get_real_seconds();
++
++      if (atomic_read(&cell->active))
++              return false;
++      if (!cell->net->live)
++              return true;
++
++      vllist = rcu_dereference_protected(cell->vl_servers, true);
++      if (vllist && vllist->nr_servers > 0)
++              expire_at += afs_cell_gc_delay;
++
++      if (expire_at <= now)
++              return true;
++      if (expire_at < *_next_manage)
++              *_next_manage = expire_at;
++      return false;
++}
++
+ /*
+  * Manage a cell record, initialising and destroying it, maintaining its DNS
+  * records.
+  */
+-static void afs_manage_cell(struct afs_cell *cell)
++static bool afs_manage_cell(struct afs_cell *cell)
+ {
+       struct afs_net *net = cell->net;
+-      int ret, active;
++      time64_t next_manage = TIME64_MAX;
++      int ret;
+       _enter("%s", cell->name);
+-again:
+       _debug("state %u", cell->state);
+       switch (cell->state) {
+-      case AFS_CELL_INACTIVE:
+-      case AFS_CELL_FAILED:
+-              down_write(&net->cells_lock);
+-              active = 1;
+-              if (atomic_try_cmpxchg_relaxed(&cell->active, &active, 0)) {
+-                      rb_erase(&cell->net_node, &net->cells);
+-                      trace_afs_cell(cell->debug_id, refcount_read(&cell->ref), 0,
+-                                     afs_cell_trace_unuse_delete);
+-                      smp_store_release(&cell->state, AFS_CELL_REMOVED);
+-              }
+-              up_write(&net->cells_lock);
+-              if (cell->state == AFS_CELL_REMOVED) {
+-                      wake_up_var(&cell->state);
+-                      goto final_destruction;
+-              }
+-              if (cell->state == AFS_CELL_FAILED)
+-                      goto done;
+-              smp_store_release(&cell->state, AFS_CELL_UNSET);
+-              wake_up_var(&cell->state);
+-              goto again;
+-
+-      case AFS_CELL_UNSET:
+-              smp_store_release(&cell->state, AFS_CELL_ACTIVATING);
+-              wake_up_var(&cell->state);
+-              goto again;
++      case AFS_CELL_SETTING_UP:
++              goto set_up_cell;
++      case AFS_CELL_ACTIVE:
++              goto cell_is_active;
++      case AFS_CELL_REMOVING:
++              WARN_ON_ONCE(1);
++              return false;
++      case AFS_CELL_DEAD:
++              return false;
++      default:
++              _debug("bad state %u", cell->state);
++              WARN_ON_ONCE(1); /* Unhandled state */
++              return false;
++      }
+-      case AFS_CELL_ACTIVATING:
+-              ret = afs_activate_cell(net, cell);
+-              if (ret < 0)
+-                      goto activation_failed;
++set_up_cell:
++      ret = afs_activate_cell(net, cell);
++      if (ret < 0) {
++              cell->error = ret;
++              goto remove_cell;
++      }
+-              smp_store_release(&cell->state, AFS_CELL_ACTIVE);
+-              wake_up_var(&cell->state);
+-              goto again;
++      afs_set_cell_state(cell, AFS_CELL_ACTIVE);
+-      case AFS_CELL_ACTIVE:
+-              if (atomic_read(&cell->active) > 1) {
+-                      if (test_and_clear_bit(AFS_CELL_FL_DO_LOOKUP, &cell->flags)) {
+-                              ret = afs_update_cell(cell);
+-                              if (ret < 0)
+-                                      cell->error = ret;
+-                      }
+-                      goto done;
+-              }
+-              smp_store_release(&cell->state, AFS_CELL_DEACTIVATING);
+-              wake_up_var(&cell->state);
+-              goto again;
+-
+-      case AFS_CELL_DEACTIVATING:
+-              if (atomic_read(&cell->active) > 1)
+-                      goto reverse_deactivation;
+-              afs_deactivate_cell(net, cell);
+-              smp_store_release(&cell->state, AFS_CELL_INACTIVE);
+-              wake_up_var(&cell->state);
+-              goto again;
++cell_is_active:
++      if (afs_has_cell_expired(cell, &next_manage))
++              goto remove_cell;
+-      case AFS_CELL_REMOVED:
+-              goto done;
++      if (test_and_clear_bit(AFS_CELL_FL_DO_LOOKUP, &cell->flags)) {
++              ret = afs_update_cell(cell);
++              if (ret < 0)
++                      cell->error = ret;
++      }
+-      default:
+-              break;
++      if (next_manage < TIME64_MAX && cell->net->live) {
++              time64_t now = ktime_get_real_seconds();
++
++              if (next_manage - now <= 0)
++                      afs_queue_cell(cell, afs_cell_trace_queue_again);
++              else
++                      afs_set_cell_timer(cell, next_manage - now);
+       }
+-      _debug("bad state %u", cell->state);
+-      BUG(); /* Unhandled state */
++      _leave(" [done %u]", cell->state);
++      return false;
+-activation_failed:
+-      cell->error = ret;
+-      afs_deactivate_cell(net, cell);
++remove_cell:
++      down_write(&net->cells_lock);
+-      smp_store_release(&cell->state, AFS_CELL_FAILED); /* vs error */
+-      wake_up_var(&cell->state);
+-      goto again;
++      if (atomic_read(&cell->active)) {
++              up_write(&net->cells_lock);
++              goto cell_is_active;
++      }
+-reverse_deactivation:
+-      smp_store_release(&cell->state, AFS_CELL_ACTIVE);
+-      wake_up_var(&cell->state);
+-      _leave(" [deact->act]");
+-      return;
++      /* Make sure that the expiring server records are going to see the fact
++       * that the cell is caput.
++       */
++      afs_set_cell_state(cell, AFS_CELL_REMOVING);
+-done:
+-      _leave(" [done %u]", cell->state);
+-      return;
++      afs_deactivate_cell(net, cell);
++      afs_purge_servers(cell);
++
++      rb_erase(&cell->net_node, &net->cells);
++      afs_see_cell(cell, afs_cell_trace_unuse_delete);
++      up_write(&net->cells_lock);
+-final_destruction:
+       /* The root volume is pinning the cell */
+       afs_put_volume(cell->root_volume, afs_volume_trace_put_cell_root);
+       cell->root_volume = NULL;
+-      afs_purge_servers(cell);
+-      afs_put_cell(cell, afs_cell_trace_put_destroy);
++
++      afs_set_cell_state(cell, AFS_CELL_DEAD);
++      return true;
+ }
+ static void afs_manage_cell_work(struct work_struct *work)
+ {
+       struct afs_cell *cell = container_of(work, struct afs_cell, manager);
++      bool final_put;
+-      afs_manage_cell(cell);
+-      afs_put_cell(cell, afs_cell_trace_put_queue_work);
+-}
+-
+-/*
+- * Manage the records of cells known to a network namespace.  This includes
+- * updating the DNS records and garbage collecting unused cells that were
+- * automatically added.
+- *
+- * Note that constructed cell records may only be removed from net->cells by
+- * this work item, so it is safe for this work item to stash a cursor pointing
+- * into the tree and then return to caller (provided it skips cells that are
+- * still under construction).
+- *
+- * Note also that we were given an increment on net->cells_outstanding by
+- * whoever queued us that we need to deal with before returning.
+- */
+-void afs_manage_cells(struct work_struct *work)
+-{
+-      struct afs_net *net = container_of(work, struct afs_net, cells_manager);
+-      struct rb_node *cursor;
+-      time64_t now = ktime_get_real_seconds(), next_manage = TIME64_MAX;
+-      bool purging = !net->live;
+-
+-      _enter("");
+-
+-      /* Trawl the cell database looking for cells that have expired from
+-       * lack of use and cells whose DNS results have expired and dispatch
+-       * their managers.
+-       */
+-      down_read(&net->cells_lock);
+-
+-      for (cursor = rb_first(&net->cells); cursor; cursor = rb_next(cursor)) {
+-              struct afs_cell *cell =
+-                      rb_entry(cursor, struct afs_cell, net_node);
+-              unsigned active;
+-              bool sched_cell = false;
+-
+-              active = atomic_read(&cell->active);
+-              trace_afs_cell(cell->debug_id, refcount_read(&cell->ref),
+-                             active, afs_cell_trace_manage);
+-
+-              ASSERTCMP(active, >=, 1);
+-
+-              if (purging) {
+-                      if (test_and_clear_bit(AFS_CELL_FL_NO_GC, &cell->flags)) {
+-                              active = atomic_dec_return(&cell->active);
+-                              trace_afs_cell(cell->debug_id, refcount_read(&cell->ref),
+-                                             active, afs_cell_trace_unuse_pin);
+-                      }
+-              }
+-
+-              if (active == 1) {
+-                      struct afs_vlserver_list *vllist;
+-                      time64_t expire_at = cell->last_inactive;
+-
+-                      read_lock(&cell->vl_servers_lock);
+-                      vllist = rcu_dereference_protected(
+-                              cell->vl_servers,
+-                              lockdep_is_held(&cell->vl_servers_lock));
+-                      if (vllist->nr_servers > 0)
+-                              expire_at += afs_cell_gc_delay;
+-                      read_unlock(&cell->vl_servers_lock);
+-                      if (purging || expire_at <= now)
+-                              sched_cell = true;
+-                      else if (expire_at < next_manage)
+-                              next_manage = expire_at;
+-              }
+-
+-              if (!purging) {
+-                      if (test_bit(AFS_CELL_FL_DO_LOOKUP, &cell->flags))
+-                              sched_cell = true;
+-              }
+-
+-              if (sched_cell)
+-                      afs_queue_cell(cell, afs_cell_trace_get_queue_manage);
+-      }
+-
+-      up_read(&net->cells_lock);
+-
+-      /* Update the timer on the way out.  We have to pass an increment on
+-       * cells_outstanding in the namespace that we are in to the timer or
+-       * the work scheduler.
+-       */
+-      if (!purging && next_manage < TIME64_MAX) {
+-              now = ktime_get_real_seconds();
+-
+-              if (next_manage - now <= 0) {
+-                      if (queue_work(afs_wq, &net->cells_manager))
+-                              atomic_inc(&net->cells_outstanding);
+-              } else {
+-                      afs_set_cell_timer(net, next_manage - now);
+-              }
+-      }
+-
+-      afs_dec_cells_outstanding(net);
+-      _leave(" [%d]", atomic_read(&net->cells_outstanding));
++      afs_see_cell(cell, afs_cell_trace_manage);
++      final_put = afs_manage_cell(cell);
++      afs_see_cell(cell, afs_cell_trace_managed);
++      if (final_put)
++              afs_put_cell(cell, afs_cell_trace_put_final);
+ }
+ /*
+@@ -953,6 +867,7 @@ void afs_manage_cells(struct work_struct
+ void afs_cell_purge(struct afs_net *net)
+ {
+       struct afs_cell *ws;
++      struct rb_node *cursor;
+       _enter("");
+@@ -962,12 +877,19 @@ void afs_cell_purge(struct afs_net *net)
+       up_write(&net->cells_lock);
+       afs_unuse_cell(ws, afs_cell_trace_unuse_ws);
+-      _debug("del timer");
+-      if (del_timer_sync(&net->cells_timer))
+-              atomic_dec(&net->cells_outstanding);
++      _debug("kick cells");
++      down_read(&net->cells_lock);
++      for (cursor = rb_first(&net->cells); cursor; cursor = rb_next(cursor)) {
++              struct afs_cell *cell = rb_entry(cursor, struct afs_cell, net_node);
++
++              afs_see_cell(cell, afs_cell_trace_purge);
+-      _debug("kick mgr");
+-      afs_queue_cell_manager(net);
++              if (test_and_clear_bit(AFS_CELL_FL_NO_GC, &cell->flags))
++                      afs_unuse_cell(cell, afs_cell_trace_unuse_pin);
++
++              afs_queue_cell(cell, afs_cell_trace_queue_purge);
++      }
++      up_read(&net->cells_lock);
+       _debug("wait");
+       wait_var_event(&net->cells_outstanding,
+--- a/fs/afs/dynroot.c
++++ b/fs/afs/dynroot.c
+@@ -293,8 +293,8 @@ static int afs_dynroot_readdir_cells(str
+               cell = idr_get_next(&net->cells_dyn_ino, &ix);
+               if (!cell)
+                       return 0;
+-              if (READ_ONCE(cell->state) == AFS_CELL_FAILED ||
+-                  READ_ONCE(cell->state) == AFS_CELL_REMOVED) {
++              if (READ_ONCE(cell->state) == AFS_CELL_REMOVING ||
++                  READ_ONCE(cell->state) == AFS_CELL_DEAD) {
+                       ctx->pos += 2;
+                       ctx->pos &= ~1;
+                       continue;
+--- a/fs/afs/internal.h
++++ b/fs/afs/internal.h
+@@ -304,8 +304,6 @@ struct afs_net {
+       struct rb_root          cells;
+       struct idr              cells_dyn_ino;  /* cell->dynroot_ino mapping */
+       struct afs_cell __rcu   *ws_cell;
+-      struct work_struct      cells_manager;
+-      struct timer_list       cells_timer;
+       atomic_t                cells_outstanding;
+       struct rw_semaphore     cells_lock;
+       struct mutex            cells_alias_lock;
+@@ -354,13 +352,10 @@ struct afs_net {
+ extern const char afs_init_sysname[];
+ enum afs_cell_state {
+-      AFS_CELL_UNSET,
+-      AFS_CELL_ACTIVATING,
++      AFS_CELL_SETTING_UP,
+       AFS_CELL_ACTIVE,
+-      AFS_CELL_DEACTIVATING,
+-      AFS_CELL_INACTIVE,
+-      AFS_CELL_FAILED,
+-      AFS_CELL_REMOVED,
++      AFS_CELL_REMOVING,
++      AFS_CELL_DEAD,
+ };
+ /*
+@@ -391,7 +386,9 @@ struct afs_cell {
+       struct afs_cell         *alias_of;      /* The cell this is an alias of */
+       struct afs_volume       *root_volume;   /* The root.cell volume if there is one */
+       struct key              *anonymous_key; /* anonymous user key for this cell */
++      struct work_struct      destroyer;      /* Destroyer for cell */
+       struct work_struct      manager;        /* Manager for init/deinit/dns */
++      struct timer_list       management_timer; /* General management timer */
+       struct hlist_node       proc_link;      /* /proc cell list link */
+       time64_t                dns_expiry;     /* Time AFSDB/SRV record expires */
+       time64_t                last_inactive;  /* Time of last drop of usage count */
+@@ -1047,8 +1044,7 @@ extern struct afs_cell *afs_get_cell(str
+ extern void afs_see_cell(struct afs_cell *, enum afs_cell_trace);
+ extern void afs_put_cell(struct afs_cell *, enum afs_cell_trace);
+ extern void afs_queue_cell(struct afs_cell *, enum afs_cell_trace);
+-extern void afs_manage_cells(struct work_struct *);
+-extern void afs_cells_timer(struct timer_list *);
++void afs_set_cell_timer(struct afs_cell *cell, unsigned int delay_secs);
+ extern void __net_exit afs_cell_purge(struct afs_net *);
+ /*
+--- a/fs/afs/main.c
++++ b/fs/afs/main.c
+@@ -78,9 +78,6 @@ static int __net_init afs_net_init(struc
+       net->cells = RB_ROOT;
+       idr_init(&net->cells_dyn_ino);
+       init_rwsem(&net->cells_lock);
+-      INIT_WORK(&net->cells_manager, afs_manage_cells);
+-      timer_setup(&net->cells_timer, afs_cells_timer, 0);
+-
+       mutex_init(&net->cells_alias_lock);
+       mutex_init(&net->proc_cells_lock);
+       INIT_HLIST_HEAD(&net->proc_cells);
+--- a/fs/afs/server.c
++++ b/fs/afs/server.c
+@@ -103,7 +103,7 @@ static struct afs_server *afs_install_se
+       afs_get_cell(cell, afs_cell_trace_get_server);
+ exists:
+-      afs_use_server(server, true, afs_server_trace_get_install);
++      afs_use_server(server, true, afs_server_trace_use_install);
+       return server;
+ }
+@@ -357,7 +357,7 @@ void afs_unuse_server_notime(struct afs_
+       if (atomic_dec_and_test(&server->active)) {
+               if (test_bit(AFS_SERVER_FL_EXPIRED, &server->flags) ||
+-                  READ_ONCE(server->cell->state) >= AFS_CELL_FAILED)
++                  READ_ONCE(server->cell->state) >= AFS_CELL_REMOVING)
+                       schedule_work(&server->destroyer);
+       }
+@@ -375,7 +375,7 @@ void afs_unuse_server(struct afs_net *ne
+       if (atomic_dec_and_test(&server->active)) {
+               if (!test_bit(AFS_SERVER_FL_EXPIRED, &server->flags) &&
+-                  READ_ONCE(server->cell->state) < AFS_CELL_FAILED) {
++                  READ_ONCE(server->cell->state) < AFS_CELL_REMOVING) {
+                       time64_t unuse_time = ktime_get_real_seconds();
+                       server->unuse_time = unuse_time;
+@@ -425,7 +425,7 @@ static bool afs_has_server_expired(const
+               return false;
+       if (server->cell->net->live ||
+-          server->cell->state >= AFS_CELL_FAILED) {
++          server->cell->state >= AFS_CELL_REMOVING) {
+               trace_afs_server(server->debug_id, refcount_read(&server->ref),
+                                0, afs_server_trace_purging);
+               return true;
+--- a/fs/afs/vl_rotate.c
++++ b/fs/afs/vl_rotate.c
+@@ -48,7 +48,7 @@ static bool afs_start_vl_iteration(struc
+           cell->dns_expiry <= ktime_get_real_seconds()) {
+               dns_lookup_count = smp_load_acquire(&cell->dns_lookup_count);
+               set_bit(AFS_CELL_FL_DO_LOOKUP, &cell->flags);
+-              afs_queue_cell(cell, afs_cell_trace_get_queue_dns);
++              afs_queue_cell(cell, afs_cell_trace_queue_dns);
+               if (cell->dns_source == DNS_RECORD_UNAVAILABLE) {
+                       if (wait_var_event_interruptible(
+--- a/include/trace/events/afs.h
++++ b/include/trace/events/afs.h
+@@ -131,7 +131,6 @@ enum yfs_cm_operation {
+       EM(afs_server_trace_destroy,            "DESTROY  ") \
+       EM(afs_server_trace_free,               "FREE     ") \
+       EM(afs_server_trace_gc,                 "GC       ") \
+-      EM(afs_server_trace_get_install,        "GET inst ") \
+       EM(afs_server_trace_get_probe,          "GET probe") \
+       EM(afs_server_trace_purging,            "PURGE    ") \
+       EM(afs_server_trace_put_cbi,            "PUT cbi  ") \
+@@ -149,6 +148,7 @@ enum yfs_cm_operation {
+       EM(afs_server_trace_use_cm_call,        "USE cm-cl") \
+       EM(afs_server_trace_use_get_caps,       "USE gcaps") \
+       EM(afs_server_trace_use_give_up_cb,     "USE gvupc") \
++      EM(afs_server_trace_use_install,        "USE inst ") \
+       E_(afs_server_trace_wait_create,        "WAIT crt ")
+ #define afs_volume_traces \
+@@ -171,37 +171,36 @@ enum yfs_cm_operation {
+ #define afs_cell_traces \
+       EM(afs_cell_trace_alloc,                "ALLOC     ") \
++      EM(afs_cell_trace_destroy,              "DESTROY   ") \
+       EM(afs_cell_trace_free,                 "FREE      ") \
+       EM(afs_cell_trace_get_atcell,           "GET atcell") \
+-      EM(afs_cell_trace_get_queue_dns,        "GET q-dns ") \
+-      EM(afs_cell_trace_get_queue_manage,     "GET q-mng ") \
+-      EM(afs_cell_trace_get_queue_new,        "GET q-new ") \
+       EM(afs_cell_trace_get_server,           "GET server") \
+       EM(afs_cell_trace_get_vol,              "GET vol   ") \
+-      EM(afs_cell_trace_insert,               "INSERT    ") \
+-      EM(afs_cell_trace_manage,               "MANAGE    ") \
++      EM(afs_cell_trace_purge,                "PURGE     ") \
+       EM(afs_cell_trace_put_atcell,           "PUT atcell") \
+       EM(afs_cell_trace_put_candidate,        "PUT candid") \
+-      EM(afs_cell_trace_put_destroy,          "PUT destry") \
+-      EM(afs_cell_trace_put_queue_work,       "PUT q-work") \
+-      EM(afs_cell_trace_put_queue_fail,       "PUT q-fail") \
++      EM(afs_cell_trace_put_final,            "PUT final ") \
+       EM(afs_cell_trace_put_server,           "PUT server") \
+       EM(afs_cell_trace_put_vol,              "PUT vol   ") \
++      EM(afs_cell_trace_queue_again,          "QUE again ") \
++      EM(afs_cell_trace_queue_dns,            "QUE dns   ") \
++      EM(afs_cell_trace_queue_new,            "QUE new   ") \
++      EM(afs_cell_trace_queue_purge,          "QUE purge ") \
++      EM(afs_cell_trace_manage,               "MANAGE    ") \
++      EM(afs_cell_trace_managed,              "MANAGED   ") \
+       EM(afs_cell_trace_see_source,           "SEE source") \
+-      EM(afs_cell_trace_see_ws,               "SEE ws    ") \
++      EM(afs_cell_trace_see_mgmt_timer,       "SEE mtimer") \
+       EM(afs_cell_trace_unuse_alias,          "UNU alias ") \
+       EM(afs_cell_trace_unuse_check_alias,    "UNU chk-al") \
+       EM(afs_cell_trace_unuse_delete,         "UNU delete") \
+       EM(afs_cell_trace_unuse_dynroot_mntpt,  "UNU dyn-mp") \
+       EM(afs_cell_trace_unuse_fc,             "UNU fc    ") \
+-      EM(afs_cell_trace_unuse_lookup,         "UNU lookup") \
+       EM(afs_cell_trace_unuse_lookup_dynroot, "UNU lu-dyn") \
+       EM(afs_cell_trace_unuse_lookup_error,   "UNU lu-err") \
+       EM(afs_cell_trace_unuse_mntpt,          "UNU mntpt ") \
+       EM(afs_cell_trace_unuse_no_pin,         "UNU no-pin") \
+       EM(afs_cell_trace_unuse_parse,          "UNU parse ") \
+       EM(afs_cell_trace_unuse_pin,            "UNU pin   ") \
+-      EM(afs_cell_trace_unuse_probe,          "UNU probe ") \
+       EM(afs_cell_trace_unuse_sbi,            "UNU sbi   ") \
+       EM(afs_cell_trace_unuse_ws,             "UNU ws    ") \
+       EM(afs_cell_trace_use_alias,            "USE alias ") \
diff --git a/queue-6.12/afs-use-the-per-peer-app-data-provided-by-rxrpc.patch b/queue-6.12/afs-use-the-per-peer-app-data-provided-by-rxrpc.patch
new file mode 100644 (file)
index 0000000..4d8431c
--- /dev/null
@@ -0,0 +1,521 @@
+From stable+bounces-290950-greg=kroah.com@vger.kernel.org Thu Jul 30 03:04:15 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 21:01:34 -0400
+Subject: afs: Use the per-peer app data provided by rxrpc
+To: stable@vger.kernel.org
+Cc: David Howells <dhowells@redhat.com>, Marc Dionne <marc.dionne@auristor.com>, linux-afs@lists.infradead.org, linux-fsdevel@vger.kernel.org, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260730010137.1700032-5-sashal@kernel.org>
+
+From: David Howells <dhowells@redhat.com>
+
+[ Upstream commit 40e8b52fe8c8ab6920ea5f59c5469b6918cce624 ]
+
+Make use of the per-peer application data that rxrpc now allows the
+application to store on the rxrpc_peer struct to hold a back pointer to the
+afs_server record that peer represents an endpoint for.
+
+Then, when a call comes in to the AFS cache manager, this can be used to
+map it to the correct server record rather than having to use a
+UUID-to-server mapping table and having to do an additional lookup.
+
+Signed-off-by: David Howells <dhowells@redhat.com>
+cc: Marc Dionne <marc.dionne@auristor.com>
+cc: linux-afs@lists.infradead.org
+cc: linux-fsdevel@vger.kernel.org
+Link: https://lore.kernel.org/r/20250224234154.2014840-14-dhowells@redhat.com/ # v1
+Link: https://lore.kernel.org/r/20250310094206.801057-10-dhowells@redhat.com/ # v4
+Stable-dep-of: 330e2c514823 ("afs: Fix dynamic lookup to fail on cell lookup failure")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/afs/addr_list.c         |   50 +++++++++++++++++++++++++
+ fs/afs/cmservice.c         |   87 +++++++++------------------------------------
+ fs/afs/fs_probe.c          |   32 +++++++++++-----
+ fs/afs/internal.h          |    9 +++-
+ fs/afs/proc.c              |   10 ++++-
+ fs/afs/rxrpc.c             |    6 +++
+ fs/afs/server.c            |   46 ++++++-----------------
+ include/trace/events/afs.h |    4 --
+ net/rxrpc/peer_object.c    |    4 +-
+ 9 files changed, 125 insertions(+), 123 deletions(-)
+
+--- a/fs/afs/addr_list.c
++++ b/fs/afs/addr_list.c
+@@ -362,3 +362,53 @@ int afs_merge_fs_addr6(struct afs_net *n
+       alist->nr_addrs++;
+       return 0;
+ }
++
++/*
++ * Set the app data on the rxrpc peers an address list points to
++ */
++void afs_set_peer_appdata(struct afs_server *server,
++                        struct afs_addr_list *old_alist,
++                        struct afs_addr_list *new_alist)
++{
++      unsigned long data = (unsigned long)server;
++      int n = 0, o = 0;
++
++      if (!old_alist) {
++              /* New server.  Just set all. */
++              for (; n < new_alist->nr_addrs; n++)
++                      rxrpc_kernel_set_peer_data(new_alist->addrs[n].peer, data);
++              return;
++      }
++      if (!new_alist) {
++              /* Dead server.  Just remove all. */
++              for (; o < old_alist->nr_addrs; o++)
++                      rxrpc_kernel_set_peer_data(old_alist->addrs[o].peer, 0);
++              return;
++      }
++
++      /* Walk through the two lists simultaneously, setting new peers and
++       * clearing old ones.  The two lists are ordered by pointer to peer
++       * record.
++       */
++      while (n < new_alist->nr_addrs && o < old_alist->nr_addrs) {
++              struct rxrpc_peer *pn = new_alist->addrs[n].peer;
++              struct rxrpc_peer *po = old_alist->addrs[o].peer;
++
++              if (pn == po)
++                      continue;
++              if (pn < po) {
++                      rxrpc_kernel_set_peer_data(pn, data);
++                      n++;
++              } else {
++                      rxrpc_kernel_set_peer_data(po, 0);
++                      o++;
++              }
++      }
++
++      if (n < new_alist->nr_addrs)
++              for (; n < new_alist->nr_addrs; n++)
++                      rxrpc_kernel_set_peer_data(new_alist->addrs[n].peer, data);
++      if (o < old_alist->nr_addrs)
++              for (; o < old_alist->nr_addrs; o++)
++                      rxrpc_kernel_set_peer_data(old_alist->addrs[o].peer, 0);
++}
+--- a/fs/afs/cmservice.c
++++ b/fs/afs/cmservice.c
+@@ -139,49 +139,6 @@ bool afs_cm_incoming_call(struct afs_cal
+ }
+ /*
+- * Find the server record by peer address and record a probe to the cache
+- * manager from a server.
+- */
+-static int afs_find_cm_server_by_peer(struct afs_call *call)
+-{
+-      struct sockaddr_rxrpc srx;
+-      struct afs_server *server;
+-      struct rxrpc_peer *peer;
+-
+-      peer = rxrpc_kernel_get_call_peer(call->net->socket, call->rxcall);
+-
+-      server = afs_find_server(call->net, peer);
+-      if (!server) {
+-              trace_afs_cm_no_server(call, &srx);
+-              return 0;
+-      }
+-
+-      call->server = server;
+-      return 0;
+-}
+-
+-/*
+- * Find the server record by server UUID and record a probe to the cache
+- * manager from a server.
+- */
+-static int afs_find_cm_server_by_uuid(struct afs_call *call,
+-                                    struct afs_uuid *uuid)
+-{
+-      struct afs_server *server;
+-
+-      rcu_read_lock();
+-      server = afs_find_server_by_uuid(call->net, call->request);
+-      rcu_read_unlock();
+-      if (!server) {
+-              trace_afs_cm_no_server_u(call, call->request);
+-              return 0;
+-      }
+-
+-      call->server = server;
+-      return 0;
+-}
+-
+-/*
+  * Clean up a cache manager call.
+  */
+ static void afs_cm_destructor(struct afs_call *call)
+@@ -322,10 +279,7 @@ static int afs_deliver_cb_callback(struc
+       if (!afs_check_call_state(call, AFS_CALL_SV_REPLYING))
+               return afs_io_error(call, afs_io_error_cm_reply);
+-
+-      /* we'll need the file server record as that tells us which set of
+-       * vnodes to operate upon */
+-      return afs_find_cm_server_by_peer(call);
++      return 0;
+ }
+ /*
+@@ -349,18 +303,10 @@ static void SRXAFSCB_InitCallBackState(s
+  */
+ static int afs_deliver_cb_init_call_back_state(struct afs_call *call)
+ {
+-      int ret;
+-
+       _enter("");
+       afs_extract_discard(call, 0);
+-      ret = afs_extract_data(call, false);
+-      if (ret < 0)
+-              return ret;
+-
+-      /* we'll need the file server record as that tells us which set of
+-       * vnodes to operate upon */
+-      return afs_find_cm_server_by_peer(call);
++      return afs_extract_data(call, false);
+ }
+ /*
+@@ -373,8 +319,6 @@ static int afs_deliver_cb_init_call_back
+       __be32 *b;
+       int ret;
+-      _enter("");
+-
+       _enter("{%u}", call->unmarshall);
+       switch (call->unmarshall) {
+@@ -420,9 +364,18 @@ static int afs_deliver_cb_init_call_back
+       if (!afs_check_call_state(call, AFS_CALL_SV_REPLYING))
+               return afs_io_error(call, afs_io_error_cm_reply);
+-      /* we'll need the file server record as that tells us which set of
+-       * vnodes to operate upon */
+-      return afs_find_cm_server_by_uuid(call, call->request);
++      if (!call->server) {
++              trace_afs_cm_no_server_u(call, call->request);
++              return 0;
++      }
++
++      if (memcmp(call->request, &call->server->_uuid, sizeof(call->server->_uuid)) != 0) {
++              pr_notice("Callback UUID does not match fileserver UUID\n");
++              trace_afs_cm_no_server_u(call, call->request);
++              return 0;
++      }
++
++      return 0;
+ }
+ /*
+@@ -454,7 +407,7 @@ static int afs_deliver_cb_probe(struct a
+       if (!afs_check_call_state(call, AFS_CALL_SV_REPLYING))
+               return afs_io_error(call, afs_io_error_cm_reply);
+-      return afs_find_cm_server_by_peer(call);
++      return 0;
+ }
+ /*
+@@ -531,7 +484,7 @@ static int afs_deliver_cb_probe_uuid(str
+       if (!afs_check_call_state(call, AFS_CALL_SV_REPLYING))
+               return afs_io_error(call, afs_io_error_cm_reply);
+-      return afs_find_cm_server_by_peer(call);
++      return 0;
+ }
+ /*
+@@ -591,7 +544,7 @@ static int afs_deliver_cb_tell_me_about_
+       if (!afs_check_call_state(call, AFS_CALL_SV_REPLYING))
+               return afs_io_error(call, afs_io_error_cm_reply);
+-      return afs_find_cm_server_by_peer(call);
++      return 0;
+ }
+ /*
+@@ -665,9 +618,5 @@ static int afs_deliver_yfs_cb_callback(s
+       if (!afs_check_call_state(call, AFS_CALL_SV_REPLYING))
+               return afs_io_error(call, afs_io_error_cm_reply);
+-
+-      /* We'll need the file server record as that tells us which set of
+-       * vnodes to operate upon.
+-       */
+-      return afs_find_cm_server_by_peer(call);
++      return 0;
+ }
+--- a/fs/afs/fs_probe.c
++++ b/fs/afs/fs_probe.c
+@@ -235,20 +235,20 @@ out:
+  * Probe all of a fileserver's addresses to find out the best route and to
+  * query its capabilities.
+  */
+-void afs_fs_probe_fileserver(struct afs_net *net, struct afs_server *server,
+-                           struct afs_addr_list *new_alist, struct key *key)
++int afs_fs_probe_fileserver(struct afs_net *net, struct afs_server *server,
++                          struct afs_addr_list *new_alist, struct key *key)
+ {
+       struct afs_endpoint_state *estate, *old;
+-      struct afs_addr_list *alist;
++      struct afs_addr_list *old_alist = NULL, *alist;
+       unsigned long unprobed;
+       _enter("%pU", &server->uuid);
+       estate = kzalloc(sizeof(*estate), GFP_KERNEL);
+       if (!estate)
+-              return;
++              return -ENOMEM;
+-      refcount_set(&estate->ref, 1);
++      refcount_set(&estate->ref, 2);
+       estate->server_id = server->debug_id;
+       estate->rtt = UINT_MAX;
+@@ -256,21 +256,31 @@ void afs_fs_probe_fileserver(struct afs_
+       old = rcu_dereference_protected(server->endpoint_state,
+                                       lockdep_is_held(&server->fs_lock));
+-      estate->responsive_set = old->responsive_set;
+-      estate->addresses = afs_get_addrlist(new_alist ?: old->addresses,
+-                                           afs_alist_trace_get_estate);
++      if (old) {
++              estate->responsive_set = old->responsive_set;
++              if (!new_alist)
++                      new_alist = old->addresses;
++      }
++
++      if (old_alist != new_alist)
++              afs_set_peer_appdata(server, old_alist, new_alist);
++
++      estate->addresses = afs_get_addrlist(new_alist, afs_alist_trace_get_estate);
+       alist = estate->addresses;
+       estate->probe_seq = ++server->probe_counter;
+       atomic_set(&estate->nr_probing, alist->nr_addrs);
++      if (new_alist)
++              server->addr_version = new_alist->version;
+       rcu_assign_pointer(server->endpoint_state, estate);
+-      set_bit(AFS_ESTATE_SUPERSEDED, &old->flags);
+       write_unlock(&server->fs_lock);
++      if (old)
++              set_bit(AFS_ESTATE_SUPERSEDED, &old->flags);
+       trace_afs_estate(estate->server_id, estate->probe_seq, refcount_read(&estate->ref),
+                        afs_estate_trace_alloc_probe);
+-      afs_get_address_preferences(net, alist);
++      afs_get_address_preferences(net, new_alist);
+       server->probed_at = jiffies;
+       unprobed = (1UL << alist->nr_addrs) - 1;
+@@ -293,6 +303,8 @@ void afs_fs_probe_fileserver(struct afs_
+       }
+       afs_put_endpoint_state(old, afs_estate_trace_put_probe);
++      afs_put_endpoint_state(estate, afs_estate_trace_put_probe);
++      return 0;
+ }
+ /*
+--- a/fs/afs/internal.h
++++ b/fs/afs/internal.h
+@@ -1004,6 +1004,9 @@ extern int afs_merge_fs_addr4(struct afs
+                             __be32 xdr, u16 port);
+ extern int afs_merge_fs_addr6(struct afs_net *net, struct afs_addr_list *addr,
+                             __be32 *xdr, u16 port);
++void afs_set_peer_appdata(struct afs_server *server,
++                        struct afs_addr_list *old_alist,
++                        struct afs_addr_list *new_alist);
+ /*
+  * addr_prefs.c
+@@ -1190,8 +1193,8 @@ struct afs_endpoint_state *afs_get_endpo
+                                                 enum afs_estate_trace where);
+ void afs_put_endpoint_state(struct afs_endpoint_state *estate, enum afs_estate_trace where);
+ extern void afs_fileserver_probe_result(struct afs_call *);
+-void afs_fs_probe_fileserver(struct afs_net *net, struct afs_server *server,
+-                           struct afs_addr_list *new_addrs, struct key *key);
++int afs_fs_probe_fileserver(struct afs_net *net, struct afs_server *server,
++                          struct afs_addr_list *new_alist, struct key *key);
+ int afs_wait_for_fs_probes(struct afs_operation *op, struct afs_server_state *states, bool intr);
+ extern void afs_probe_fileserver(struct afs_net *, struct afs_server *);
+ extern void afs_fs_probe_dispatcher(struct work_struct *);
+@@ -1474,7 +1477,7 @@ extern void __exit afs_clean_up_permit_c
+  */
+ extern spinlock_t afs_server_peer_lock;
+-extern struct afs_server *afs_find_server(struct afs_net *, const struct rxrpc_peer *);
++struct afs_server *afs_find_server(const struct rxrpc_peer *peer);
+ extern struct afs_server *afs_find_server_by_uuid(struct afs_net *, const uuid_t *);
+ extern struct afs_server *afs_lookup_server(struct afs_cell *, struct key *, const uuid_t *, u32);
+ extern struct afs_server *afs_get_server(struct afs_server *, enum afs_server_trace);
+--- a/fs/afs/proc.c
++++ b/fs/afs/proc.c
+@@ -444,8 +444,6 @@ static int afs_proc_servers_show(struct
+       }
+       server = list_entry(v, struct afs_server, proc_link);
+-      estate = rcu_dereference(server->endpoint_state);
+-      alist = estate->addresses;
+       seq_printf(m, "%pU %3d %3d %s\n",
+                  &server->uuid,
+                  refcount_read(&server->ref),
+@@ -455,10 +453,16 @@ static int afs_proc_servers_show(struct
+                  server->flags, server->rtt);
+       seq_printf(m, "  - probe: last=%d\n",
+                  (int)(jiffies - server->probed_at) / HZ);
++
++      estate = rcu_dereference(server->endpoint_state);
++      if (!estate)
++              goto out;
+       failed = estate->failed_set;
+       seq_printf(m, "  - ESTATE pq=%x np=%u rsp=%lx f=%lx\n",
+                  estate->probe_seq, atomic_read(&estate->nr_probing),
+                  estate->responsive_set, estate->failed_set);
++
++      alist = estate->addresses;
+       seq_printf(m, "  - ALIST v=%u ap=%u\n",
+                  alist->version, alist->addr_pref_version);
+       for (i = 0; i < alist->nr_addrs; i++) {
+@@ -471,6 +475,8 @@ static int afs_proc_servers_show(struct
+                          rxrpc_kernel_get_srtt(addr->peer),
+                          addr->last_error, addr->prio);
+       }
++
++out:
+       return 0;
+ }
+--- a/fs/afs/rxrpc.c
++++ b/fs/afs/rxrpc.c
+@@ -791,8 +791,14 @@ static void afs_rx_discard_new_call(stru
+ static void afs_rx_new_call(struct sock *sk, struct rxrpc_call *rxcall,
+                           unsigned long user_call_ID)
+ {
++      struct afs_call *call = (struct afs_call *)user_call_ID;
+       struct afs_net *net = afs_sock2net(sk);
++      call->peer = rxrpc_kernel_get_call_peer(sk->sk_socket, call->rxcall);
++      call->server = afs_find_server(call->peer);
++      if (!call->server)
++              trace_afs_cm_no_server(call, rxrpc_kernel_remote_srx(call->peer));
++
+       if (net->live)
+               queue_work(afs_wq, &net->charge_preallocation_work);
+ }
+--- a/fs/afs/server.c
++++ b/fs/afs/server.c
+@@ -21,42 +21,13 @@ static void __afs_put_server(struct afs_
+ /*
+  * Find a server by one of its addresses.
+  */
+-struct afs_server *afs_find_server(struct afs_net *net, const struct rxrpc_peer *peer)
++struct afs_server *afs_find_server(const struct rxrpc_peer *peer)
+ {
+-      const struct afs_endpoint_state *estate;
+-      const struct afs_addr_list *alist;
+-      struct afs_server *server = NULL;
+-      unsigned int i;
+-      int seq = 1;
+-
+-      rcu_read_lock();
+-
+-      do {
+-              if (server)
+-                      afs_unuse_server_notime(net, server, afs_server_trace_unuse_find_rsq);
+-              server = NULL;
+-              seq++; /* 2 on the 1st/lockless path, otherwise odd */
+-              read_seqbegin_or_lock(&net->fs_addr_lock, &seq);
+-
+-              hlist_for_each_entry_rcu(server, &net->fs_addresses, addr_link) {
+-                      estate = rcu_dereference(server->endpoint_state);
+-                      alist = estate->addresses;
+-                      for (i = 0; i < alist->nr_addrs; i++)
+-                              if (alist->addrs[i].peer == peer)
+-                                      goto found;
+-              }
++      struct afs_server *server = (struct afs_server *)rxrpc_kernel_get_peer_data(peer);
+-              server = NULL;
+-              continue;
+-      found:
+-              server = afs_maybe_use_server(server, afs_server_trace_use_by_addr);
+-
+-      } while (need_seqretry(&net->fs_addr_lock, seq));
+-
+-      done_seqretry(&net->fs_addr_lock, seq);
+-
+-      rcu_read_unlock();
+-      return server;
++      if (!server)
++              return NULL;
++      return afs_maybe_use_server(server, afs_server_trace_use_cm_call);
+ }
+ /*
+@@ -469,9 +440,16 @@ static void afs_give_up_callbacks(struct
+  */
+ static void afs_destroy_server(struct afs_net *net, struct afs_server *server)
+ {
++      struct afs_endpoint_state *estate;
++
+       if (test_bit(AFS_SERVER_FL_MAY_HAVE_CB, &server->flags))
+               afs_give_up_callbacks(net, server);
++      /* Unbind the rxrpc_peer records from the server. */
++      estate = rcu_access_pointer(server->endpoint_state);
++      if (estate)
++              afs_set_peer_appdata(server, estate->addresses, NULL);
++
+       afs_put_server(net, server, afs_server_trace_destroy);
+ }
+--- a/include/trace/events/afs.h
++++ b/include/trace/events/afs.h
+@@ -140,12 +140,10 @@ enum yfs_cm_operation {
+       EM(afs_server_trace_see_expired,        "SEE expd ") \
+       EM(afs_server_trace_unuse_call,         "UNU call ") \
+       EM(afs_server_trace_unuse_create_fail,  "UNU cfail") \
+-      EM(afs_server_trace_unuse_find_rsq,     "UNU f-rsq") \
+       EM(afs_server_trace_unuse_slist,        "UNU slist") \
+       EM(afs_server_trace_unuse_slist_isort,  "UNU isort") \
+       EM(afs_server_trace_unuse_uuid_rsq,     "PUT u-req") \
+       EM(afs_server_trace_update,             "UPDATE   ") \
+-      EM(afs_server_trace_use_by_addr,        "USE addr ") \
+       EM(afs_server_trace_use_by_uuid,        "USE uuid ") \
+       EM(afs_server_trace_use_cm_call,        "USE cm-cl") \
+       EM(afs_server_trace_use_get_caps,       "USE gcaps") \
+@@ -1119,7 +1117,7 @@ TRACE_EVENT(afs_bulkstat_error,
+           );
+ TRACE_EVENT(afs_cm_no_server,
+-          TP_PROTO(struct afs_call *call, struct sockaddr_rxrpc *srx),
++          TP_PROTO(struct afs_call *call, const struct sockaddr_rxrpc *srx),
+           TP_ARGS(call, srx),
+--- a/net/rxrpc/peer_object.c
++++ b/net/rxrpc/peer_object.c
+@@ -451,7 +451,7 @@ void rxrpc_destroy_all_peers(struct rxrp
+                       continue;
+               hlist_for_each_entry(peer, &rxnet->peer_hash[i], hash_link) {
+-                      pr_err("Leaked peer %u {%u} %pISp\n",
++                      pr_err("Leaked peer %x {%u} %pISp\n",
+                              peer->debug_id,
+                              refcount_read(&peer->ref),
+                              &peer->srx.transport);
+@@ -468,7 +468,7 @@ void rxrpc_destroy_all_peers(struct rxrp
+  */
+ struct rxrpc_peer *rxrpc_kernel_get_call_peer(struct socket *sock, struct rxrpc_call *call)
+ {
+-      return call->peer;
++      return rxrpc_get_peer(call->peer, rxrpc_peer_get_application);
+ }
+ EXPORT_SYMBOL(rxrpc_kernel_get_call_peer);
diff --git a/queue-6.12/alsa-hda-conexant-remove-mic-bias-threshold-override.patch b/queue-6.12/alsa-hda-conexant-remove-mic-bias-threshold-override.patch
new file mode 100644 (file)
index 0000000..9a99341
--- /dev/null
@@ -0,0 +1,47 @@
+From stable+bounces-287797-greg=kroah.com@vger.kernel.org Wed Jul 22 12:57:35 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 06:51:04 -0400
+Subject: ALSA: hda: conexant: Remove mic bias threshold override
+To: stable@vger.kernel.org
+Cc: Zhang Heng <zhangheng@kylinos.cn>, Takashi Iwai <tiwai@suse.de>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722105104.989130-1-sashal@kernel.org>
+
+From: Zhang Heng <zhangheng@kylinos.cn>
+
+[ Upstream commit f52524da7084c1a54683ae9fbc73e93fff19dd64 ]
+
+Remove the mic bias current comparator threshold override (NID 0x1c,
+verb 0x320, value 0x010) from Conexant codec driver.
+
+This override was originally intended to support volume up/down controls on
+headsets with inline remote controls, but it causes microphone detection
+failures on some headsets with impedance less than 1k ohm.
+
+After consulting with the vendor's engineers, it was confirmed that this
+setting is board-specific and should be handled by BIOS/firmware rather
+than the generic codec driver, especially since inline remote support
+is not currently implemented.
+
+Fixes: 7aeb25908648 ("ALSA: hda/conexant: Fix headset auto detect fail in cx8070 and SN6140")
+Cc: stable@vger.kernel.org
+Signed-off-by: Zhang Heng <zhangheng@kylinos.cn>
+Link: https://patch.msgid.link/20260713100329.306892-1-zhangheng@kylinos.cn
+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/pci/hda/patch_conexant.c |    3 ---
+ 1 file changed, 3 deletions(-)
+
+--- a/sound/pci/hda/patch_conexant.c
++++ b/sound/pci/hda/patch_conexant.c
+@@ -168,9 +168,6 @@ static void cx_fixup_headset_recog(struc
+ {
+       unsigned int mic_present;
+-      /* fix some headset type recognize fail issue, such as EDIFIER headset */
+-      /* set micbias output current comparator threshold from 66% to 55%. */
+-      snd_hda_codec_write(codec, 0x1c, 0, 0x320, 0x010);
+       /* set OFF voltage for DFET from -1.2V to -0.8V, set headset micbias register
+        * value adjustment trim from 2.2K ohms to 2.0K ohms.
+        */
diff --git a/queue-6.12/alsa-hda-fix-cached-processing-coefficient-verbs.patch b/queue-6.12/alsa-hda-fix-cached-processing-coefficient-verbs.patch
new file mode 100644 (file)
index 0000000..726c4a2
--- /dev/null
@@ -0,0 +1,62 @@
+From stable+bounces-287802-greg=kroah.com@vger.kernel.org Wed Jul 22 13:03:12 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 07:01:22 -0400
+Subject: ALSA: hda: Fix cached processing coefficient verbs
+To: stable@vger.kernel.org
+Cc: Xu Rao <raoxu@uniontech.com>, Takashi Iwai <tiwai@suse.de>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722110122.1006393-1-sashal@kernel.org>
+
+From: Xu Rao <raoxu@uniontech.com>
+
+[ Upstream commit f67be28fdf8b5d31ac1cc1152bb17250f9f8f513 ]
+
+Intel HD Audio defines Coefficient Index and Processing Coefficient as
+separate audio widget controls in the Audio Widget Verb Definitions:
+Coefficient Index selects the coefficient slot, while Processing
+Coefficient accesses the value at the selected slot.
+
+hda_reg_read_coef() selects the slot with AC_VERB_SET_COEF_INDEX, but
+then uses AC_VERB_GET_COEF_INDEX for the value read.  That reads back the
+selected index instead of the coefficient value.  hda_reg_write_coef()
+has the same issue and builds the value write from AC_VERB_GET_COEF_INDEX
+instead of AC_VERB_SET_PROC_COEF.
+
+This only affects the regmap coefficient cache path used by codecs that
+set codec->cache_coef.  Direct coefficient helpers already use the normal
+SET_COEF_INDEX followed by GET_PROC_COEF or SET_PROC_COEF sequence, which
+is likely why this has not been noticed widely.
+
+Use AC_VERB_GET_PROC_COEF for cached coefficient reads and
+AC_VERB_SET_PROC_COEF for cached coefficient writes.
+
+Fixes: 40ba66a702b8 ("ALSA: hda - Add cache support for COEF read/write")
+Cc: stable@vger.kernel.org
+Signed-off-by: Xu Rao <raoxu@uniontech.com>
+Link: https://patch.msgid.link/DB9023BF2920BA99+20260707132419.1731342-1-raoxu@uniontech.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/hda/hdac_regmap.c |    4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/sound/hda/hdac_regmap.c
++++ b/sound/hda/hdac_regmap.c
+@@ -214,7 +214,7 @@ static int hda_reg_read_coef(struct hdac
+       err = snd_hdac_exec_verb(codec, verb, 0, NULL);
+       if (err < 0)
+               return err;
+-      verb = (reg & ~0xfffff) | (AC_VERB_GET_COEF_INDEX << 8);
++      verb = (reg & ~0xfffff) | (AC_VERB_GET_PROC_COEF << 8);
+       return snd_hdac_exec_verb(codec, verb, 0, val);
+ }
+@@ -232,7 +232,7 @@ static int hda_reg_write_coef(struct hda
+       err = snd_hdac_exec_verb(codec, verb, 0, NULL);
+       if (err < 0)
+               return err;
+-      verb = (reg & ~0xfffff) | (AC_VERB_GET_COEF_INDEX << 8) |
++      verb = (reg & ~0xfffff) | (AC_VERB_SET_PROC_COEF << 8) |
+               (val & 0xffff);
+       return snd_hdac_exec_verb(codec, verb, 0, NULL);
+ }
diff --git a/queue-6.12/arm64-dts-qcom-correct-rbr-opp-entry.patch b/queue-6.12/arm64-dts-qcom-correct-rbr-opp-entry.patch
new file mode 100644 (file)
index 0000000..56c9dab
--- /dev/null
@@ -0,0 +1,309 @@
+From stable+bounces-288120-greg=kroah.com@vger.kernel.org Thu Jul 23 01:53:22 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 19:46:59 -0400
+Subject: arm64: dts: qcom: correct RBR opp entry
+To: stable@vger.kernel.org
+Cc: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>, Bjorn Andersson <andersson@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722234700.2612312-1-sashal@kernel.org>
+
+From: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
+
+[ Upstream commit a5c21b9bd5f531e50141b0484faabb707b92f1e2 ]
+
+DisplayPort Reduced Bit Rate uses link rate of 1.62 Gbps, the main link
+clock should be 162 MHz. Having the incorrect frequency (160 MHz) in the
+OPP table will result in selecting wrong link frequency. Correct the
+entry in the OPP table.
+
+Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
+Link: https://lore.kernel.org/r/20260304-msm-fix-rbr-v1-1-b9eba986eaef@oss.qualcomm.com
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Stable-dep-of: c17e22094667 ("arm64: dts: qcom: hamoa: Fix OPP tables for all DisplayPort controllers")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ arch/arm64/boot/dts/qcom/sc7180.dtsi   |    4 ++--
+ arch/arm64/boot/dts/qcom/sc7280.dtsi   |    8 ++++----
+ arch/arm64/boot/dts/qcom/sc8180x.dtsi  |   12 ++++++------
+ arch/arm64/boot/dts/qcom/sc8280xp.dtsi |   32 ++++++++++++++++----------------
+ arch/arm64/boot/dts/qcom/sm6350.dtsi   |    4 ++--
+ arch/arm64/boot/dts/qcom/sm8150.dtsi   |    4 ++--
+ arch/arm64/boot/dts/qcom/sm8250.dtsi   |    4 ++--
+ arch/arm64/boot/dts/qcom/sm8350.dtsi   |    4 ++--
+ arch/arm64/boot/dts/qcom/sm8450.dtsi   |    4 ++--
+ arch/arm64/boot/dts/qcom/x1e80100.dtsi |   16 ++++++++--------
+ 10 files changed, 46 insertions(+), 46 deletions(-)
+
+--- a/arch/arm64/boot/dts/qcom/sc7180.dtsi
++++ b/arch/arm64/boot/dts/qcom/sc7180.dtsi
+@@ -3410,8 +3410,8 @@
+                               dp_opp_table: opp-table {
+                                       compatible = "operating-points-v2";
+-                                      opp-160000000 {
+-                                              opp-hz = /bits/ 64 <160000000>;
++                                      opp-162000000 {
++                                              opp-hz = /bits/ 64 <162000000>;
+                                               required-opps = <&rpmhpd_opp_low_svs>;
+                                       };
+--- a/arch/arm64/boot/dts/qcom/sc7280.dtsi
++++ b/arch/arm64/boot/dts/qcom/sc7280.dtsi
+@@ -4723,8 +4723,8 @@
+                               edp_opp_table: opp-table {
+                                       compatible = "operating-points-v2";
+-                                      opp-160000000 {
+-                                              opp-hz = /bits/ 64 <160000000>;
++                                      opp-162000000 {
++                                              opp-hz = /bits/ 64 <162000000>;
+                                               required-opps = <&rpmhpd_opp_low_svs>;
+                                       };
+@@ -4820,8 +4820,8 @@
+                               dp_opp_table: opp-table {
+                                       compatible = "operating-points-v2";
+-                                      opp-160000000 {
+-                                              opp-hz = /bits/ 64 <160000000>;
++                                      opp-162000000 {
++                                              opp-hz = /bits/ 64 <162000000>;
+                                               required-opps = <&rpmhpd_opp_low_svs>;
+                                       };
+--- a/arch/arm64/boot/dts/qcom/sc8180x.dtsi
++++ b/arch/arm64/boot/dts/qcom/sc8180x.dtsi
+@@ -3269,8 +3269,8 @@
+                               dp0_opp_table: opp-table {
+                                       compatible = "operating-points-v2";
+-                                      opp-160000000 {
+-                                              opp-hz = /bits/ 64 <160000000>;
++                                      opp-162000000 {
++                                              opp-hz = /bits/ 64 <162000000>;
+                                               required-opps = <&rpmhpd_opp_low_svs>;
+                                       };
+@@ -3347,8 +3347,8 @@
+                               dp1_opp_table: opp-table {
+                                       compatible = "operating-points-v2";
+-                                      opp-160000000 {
+-                                              opp-hz = /bits/ 64 <160000000>;
++                                      opp-162000000 {
++                                              opp-hz = /bits/ 64 <162000000>;
+                                               required-opps = <&rpmhpd_opp_low_svs>;
+                                       };
+@@ -3415,8 +3415,8 @@
+                               edp_opp_table: opp-table {
+                                       compatible = "operating-points-v2";
+-                                      opp-160000000 {
+-                                              opp-hz = /bits/ 64 <160000000>;
++                                      opp-162000000 {
++                                              opp-hz = /bits/ 64 <162000000>;
+                                               required-opps = <&rpmhpd_opp_low_svs>;
+                                       };
+--- a/arch/arm64/boot/dts/qcom/sc8280xp.dtsi
++++ b/arch/arm64/boot/dts/qcom/sc8280xp.dtsi
+@@ -4259,8 +4259,8 @@
+                               mdss0_dp0_opp_table: opp-table {
+                                       compatible = "operating-points-v2";
+-                                      opp-160000000 {
+-                                              opp-hz = /bits/ 64 <160000000>;
++                                      opp-162000000 {
++                                              opp-hz = /bits/ 64 <162000000>;
+                                               required-opps = <&rpmhpd_opp_low_svs>;
+                                       };
+@@ -4337,8 +4337,8 @@
+                               mdss0_dp1_opp_table: opp-table {
+                                       compatible = "operating-points-v2";
+-                                      opp-160000000 {
+-                                              opp-hz = /bits/ 64 <160000000>;
++                                      opp-162000000 {
++                                              opp-hz = /bits/ 64 <162000000>;
+                                               required-opps = <&rpmhpd_opp_low_svs>;
+                                       };
+@@ -4409,8 +4409,8 @@
+                               mdss0_dp2_opp_table: opp-table {
+                                       compatible = "operating-points-v2";
+-                                      opp-160000000 {
+-                                              opp-hz = /bits/ 64 <160000000>;
++                                      opp-162000000 {
++                                              opp-hz = /bits/ 64 <162000000>;
+                                               required-opps = <&rpmhpd_opp_low_svs>;
+                                       };
+@@ -4481,8 +4481,8 @@
+                               mdss0_dp3_opp_table: opp-table {
+                                       compatible = "operating-points-v2";
+-                                      opp-160000000 {
+-                                              opp-hz = /bits/ 64 <160000000>;
++                                      opp-162000000 {
++                                              opp-hz = /bits/ 64 <162000000>;
+                                               required-opps = <&rpmhpd_opp_low_svs>;
+                                       };
+@@ -5547,8 +5547,8 @@
+                               mdss1_dp0_opp_table: opp-table {
+                                       compatible = "operating-points-v2";
+-                                      opp-160000000 {
+-                                              opp-hz = /bits/ 64 <160000000>;
++                                      opp-162000000 {
++                                              opp-hz = /bits/ 64 <162000000>;
+                                               required-opps = <&rpmhpd_opp_low_svs>;
+                                       };
+@@ -5619,8 +5619,8 @@
+                               mdss1_dp1_opp_table: opp-table {
+                                       compatible = "operating-points-v2";
+-                                      opp-160000000 {
+-                                              opp-hz = /bits/ 64 <160000000>;
++                                      opp-162000000 {
++                                              opp-hz = /bits/ 64 <162000000>;
+                                               required-opps = <&rpmhpd_opp_low_svs>;
+                                       };
+@@ -5691,8 +5691,8 @@
+                               mdss1_dp2_opp_table: opp-table {
+                                       compatible = "operating-points-v2";
+-                                      opp-160000000 {
+-                                              opp-hz = /bits/ 64 <160000000>;
++                                      opp-162000000 {
++                                              opp-hz = /bits/ 64 <162000000>;
+                                               required-opps = <&rpmhpd_opp_low_svs>;
+                                       };
+@@ -5763,8 +5763,8 @@
+                               mdss1_dp3_opp_table: opp-table {
+                                       compatible = "operating-points-v2";
+-                                      opp-160000000 {
+-                                              opp-hz = /bits/ 64 <160000000>;
++                                      opp-162000000 {
++                                              opp-hz = /bits/ 64 <162000000>;
+                                               required-opps = <&rpmhpd_opp_low_svs>;
+                                       };
+--- a/arch/arm64/boot/dts/qcom/sm6350.dtsi
++++ b/arch/arm64/boot/dts/qcom/sm6350.dtsi
+@@ -2222,8 +2222,8 @@
+                               dp_opp_table: opp-table {
+                                       compatible = "operating-points-v2";
+-                                      opp-160000000 {
+-                                              opp-hz = /bits/ 64 <160000000>;
++                                      opp-162000000 {
++                                              opp-hz = /bits/ 64 <162000000>;
+                                               required-opps = <&rpmhpd_opp_low_svs>;
+                                       };
+--- a/arch/arm64/boot/dts/qcom/sm8150.dtsi
++++ b/arch/arm64/boot/dts/qcom/sm8150.dtsi
+@@ -3932,8 +3932,8 @@
+                               dp_opp_table: opp-table {
+                                       compatible = "operating-points-v2";
+-                                      opp-160000000 {
+-                                              opp-hz = /bits/ 64 <160000000>;
++                                      opp-162000000 {
++                                              opp-hz = /bits/ 64 <162000000>;
+                                               required-opps = <&rpmhpd_opp_low_svs>;
+                                       };
+--- a/arch/arm64/boot/dts/qcom/sm8250.dtsi
++++ b/arch/arm64/boot/dts/qcom/sm8250.dtsi
+@@ -4818,8 +4818,8 @@
+                               dp_opp_table: opp-table {
+                                       compatible = "operating-points-v2";
+-                                      opp-160000000 {
+-                                              opp-hz = /bits/ 64 <160000000>;
++                                      opp-162000000 {
++                                              opp-hz = /bits/ 64 <162000000>;
+                                               required-opps = <&rpmhpd_opp_low_svs>;
+                                       };
+--- a/arch/arm64/boot/dts/qcom/sm8350.dtsi
++++ b/arch/arm64/boot/dts/qcom/sm8350.dtsi
+@@ -2909,8 +2909,8 @@
+                               dp_opp_table: opp-table {
+                                       compatible = "operating-points-v2";
+-                                      opp-160000000 {
+-                                              opp-hz = /bits/ 64 <160000000>;
++                                      opp-162000000 {
++                                              opp-hz = /bits/ 64 <162000000>;
+                                               required-opps = <&rpmhpd_opp_low_svs>;
+                                       };
+--- a/arch/arm64/boot/dts/qcom/sm8450.dtsi
++++ b/arch/arm64/boot/dts/qcom/sm8450.dtsi
+@@ -3223,8 +3223,8 @@
+                               dp_opp_table: opp-table {
+                                       compatible = "operating-points-v2";
+-                                      opp-160000000 {
+-                                              opp-hz = /bits/ 64 <160000000>;
++                                      opp-162000000 {
++                                              opp-hz = /bits/ 64 <162000000>;
+                                               required-opps = <&rpmhpd_opp_low_svs>;
+                                       };
+--- a/arch/arm64/boot/dts/qcom/x1e80100.dtsi
++++ b/arch/arm64/boot/dts/qcom/x1e80100.dtsi
+@@ -4758,8 +4758,8 @@
+                               mdss_dp0_opp_table: opp-table {
+                                       compatible = "operating-points-v2";
+-                                      opp-160000000 {
+-                                              opp-hz = /bits/ 64 <160000000>;
++                                      opp-162000000 {
++                                              opp-hz = /bits/ 64 <162000000>;
+                                               required-opps = <&rpmhpd_opp_low_svs>;
+                                       };
+@@ -4841,8 +4841,8 @@
+                               mdss_dp1_opp_table: opp-table {
+                                       compatible = "operating-points-v2";
+-                                      opp-160000000 {
+-                                              opp-hz = /bits/ 64 <160000000>;
++                                      opp-162000000 {
++                                              opp-hz = /bits/ 64 <162000000>;
+                                               required-opps = <&rpmhpd_opp_low_svs>;
+                                       };
+@@ -4923,8 +4923,8 @@
+                               mdss_dp2_opp_table: opp-table {
+                                       compatible = "operating-points-v2";
+-                                      opp-160000000 {
+-                                              opp-hz = /bits/ 64 <160000000>;
++                                      opp-162000000 {
++                                              opp-hz = /bits/ 64 <162000000>;
+                                               required-opps = <&rpmhpd_opp_low_svs>;
+                                       };
+@@ -5002,8 +5002,8 @@
+                               mdss_dp3_opp_table: opp-table {
+                                       compatible = "operating-points-v2";
+-                                      opp-160000000 {
+-                                              opp-hz = /bits/ 64 <160000000>;
++                                      opp-162000000 {
++                                              opp-hz = /bits/ 64 <162000000>;
+                                               required-opps = <&rpmhpd_opp_low_svs>;
+                                       };
diff --git a/queue-6.12/arm64-dts-qcom-hamoa-fix-opp-tables-for-all-displayport-controllers.patch b/queue-6.12/arm64-dts-qcom-hamoa-fix-opp-tables-for-all-displayport-controllers.patch
new file mode 100644 (file)
index 0000000..c57be20
--- /dev/null
@@ -0,0 +1,176 @@
+From stable+bounces-288119-greg=kroah.com@vger.kernel.org Thu Jul 23 01:53:30 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 19:47:00 -0400
+Subject: arm64: dts: qcom: hamoa: Fix OPP tables for all DisplayPort controllers
+To: stable@vger.kernel.org
+Cc: Abel Vesa <abel.vesa@oss.qualcomm.com>, Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>, Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>, Bjorn Andersson <andersson@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722234700.2612312-2-sashal@kernel.org>
+
+From: Abel Vesa <abel.vesa@oss.qualcomm.com>
+
+[ Upstream commit c17e220946675232d383620ed9cff6685735ec48 ]
+
+According to internal documentation, the corners specific for each rate
+from the DP link clock are:
+ - LOWSVS_D1 -> 19.2 MHz
+ - LOWSVS    -> 270 MHz
+ - SVS       -> 540 MHz (594 MHz in case of DP3)
+ - SVS_L1    -> 594 MHz
+ - NOM       -> 810 MHz
+ - NOM_L1    -> 810 MHz
+ - TURBO     -> 810 MHz
+
+So fix all tables for each of the four controllers according to the
+documentation, but since DP0 through DP2 have the same entries in their
+tables, lets drop the DP1 and DP2 and have all of them share the DP0
+table instead. However keep a separate table for the DP3 as it is
+different for the SVS, compared to the rest of the controllers.
+
+The 19.2 MHz @ LOWSVS_D1 isn't needed as it's not an actual working
+frequency and the controller will never select it. So remove it.
+
+Cc: stable@vger.kernel.org # v6.9+
+Fixes: 1940c25eaa63 ("arm64: dts: qcom: x1e80100: Add display nodes")
+Suggested-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
+Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
+Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
+Signed-off-by: Abel Vesa <abel.vesa@oss.qualcomm.com>
+Link: https://lore.kernel.org/r/20260323-hamoa-fix-dp3-opp-table-v3-1-a823776bd1b0@oss.qualcomm.com
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ arch/arm64/boot/dts/qcom/x1e80100.dtsi |   77 +++++----------------------------
+ 1 file changed, 12 insertions(+), 65 deletions(-)
+
+--- a/arch/arm64/boot/dts/qcom/x1e80100.dtsi
++++ b/arch/arm64/boot/dts/qcom/x1e80100.dtsi
+@@ -4758,18 +4758,18 @@
+                               mdss_dp0_opp_table: opp-table {
+                                       compatible = "operating-points-v2";
+-                                      opp-162000000 {
+-                                              opp-hz = /bits/ 64 <162000000>;
+-                                              required-opps = <&rpmhpd_opp_low_svs>;
+-                                      };
+-
+                                       opp-270000000 {
+                                               opp-hz = /bits/ 64 <270000000>;
+-                                              required-opps = <&rpmhpd_opp_svs>;
++                                              required-opps = <&rpmhpd_opp_low_svs>;
+                                       };
+                                       opp-540000000 {
+                                               opp-hz = /bits/ 64 <540000000>;
++                                              required-opps = <&rpmhpd_opp_svs>;
++                                      };
++
++                                      opp-594000000 {
++                                              opp-hz = /bits/ 64 <594000000>;
+                                               required-opps = <&rpmhpd_opp_svs_l1>;
+                                       };
+@@ -4806,7 +4806,7 @@
+                               assigned-clock-parents = <&usb_1_ss1_qmpphy QMP_USB43DP_DP_LINK_CLK>,
+                                                        <&usb_1_ss1_qmpphy QMP_USB43DP_DP_VCO_DIV_CLK>;
+-                              operating-points-v2 = <&mdss_dp1_opp_table>;
++                              operating-points-v2 = <&mdss_dp0_opp_table>;
+                               power-domains = <&rpmhpd RPMHPD_MMCX>;
+@@ -4837,30 +4837,6 @@
+                                               };
+                                       };
+                               };
+-
+-                              mdss_dp1_opp_table: opp-table {
+-                                      compatible = "operating-points-v2";
+-
+-                                      opp-162000000 {
+-                                              opp-hz = /bits/ 64 <162000000>;
+-                                              required-opps = <&rpmhpd_opp_low_svs>;
+-                                      };
+-
+-                                      opp-270000000 {
+-                                              opp-hz = /bits/ 64 <270000000>;
+-                                              required-opps = <&rpmhpd_opp_svs>;
+-                                      };
+-
+-                                      opp-540000000 {
+-                                              opp-hz = /bits/ 64 <540000000>;
+-                                              required-opps = <&rpmhpd_opp_svs_l1>;
+-                                      };
+-
+-                                      opp-810000000 {
+-                                              opp-hz = /bits/ 64 <810000000>;
+-                                              required-opps = <&rpmhpd_opp_nom>;
+-                                      };
+-                              };
+                       };
+                       mdss_dp2: displayport-controller@ae9a000 {
+@@ -4889,7 +4865,7 @@
+                               assigned-clock-parents = <&usb_1_ss2_qmpphy QMP_USB43DP_DP_LINK_CLK>,
+                                                        <&usb_1_ss2_qmpphy QMP_USB43DP_DP_VCO_DIV_CLK>;
+-                              operating-points-v2 = <&mdss_dp2_opp_table>;
++                              operating-points-v2 = <&mdss_dp0_opp_table>;
+                               power-domains = <&rpmhpd RPMHPD_MMCX>;
+@@ -4919,30 +4895,6 @@
+                                               };
+                                       };
+                               };
+-
+-                              mdss_dp2_opp_table: opp-table {
+-                                      compatible = "operating-points-v2";
+-
+-                                      opp-162000000 {
+-                                              opp-hz = /bits/ 64 <162000000>;
+-                                              required-opps = <&rpmhpd_opp_low_svs>;
+-                                      };
+-
+-                                      opp-270000000 {
+-                                              opp-hz = /bits/ 64 <270000000>;
+-                                              required-opps = <&rpmhpd_opp_svs>;
+-                                      };
+-
+-                                      opp-540000000 {
+-                                              opp-hz = /bits/ 64 <540000000>;
+-                                              required-opps = <&rpmhpd_opp_svs_l1>;
+-                                      };
+-
+-                                      opp-810000000 {
+-                                              opp-hz = /bits/ 64 <810000000>;
+-                                              required-opps = <&rpmhpd_opp_nom>;
+-                                      };
+-                              };
+                       };
+                       mdss_dp3: displayport-controller@aea0000 {
+@@ -5002,19 +4954,14 @@
+                               mdss_dp3_opp_table: opp-table {
+                                       compatible = "operating-points-v2";
+-                                      opp-162000000 {
+-                                              opp-hz = /bits/ 64 <162000000>;
+-                                              required-opps = <&rpmhpd_opp_low_svs>;
+-                                      };
+-
+                                       opp-270000000 {
+                                               opp-hz = /bits/ 64 <270000000>;
+-                                              required-opps = <&rpmhpd_opp_svs>;
++                                              required-opps = <&rpmhpd_opp_low_svs>;
+                                       };
+-                                      opp-540000000 {
+-                                              opp-hz = /bits/ 64 <540000000>;
+-                                              required-opps = <&rpmhpd_opp_svs_l1>;
++                                      opp-594000000 {
++                                              opp-hz = /bits/ 64 <594000000>;
++                                              required-opps = <&rpmhpd_opp_svs>;
+                                       };
+                                       opp-810000000 {
diff --git a/queue-6.12/asoc-mediatek-mt8183-afe-pcm-shorten-memif_data-table-using-macros.patch b/queue-6.12/asoc-mediatek-mt8183-afe-pcm-shorten-memif_data-table-using-macros.patch
new file mode 100644 (file)
index 0000000..bb7fe79
--- /dev/null
@@ -0,0 +1,285 @@
+From stable+bounces-288164-greg=kroah.com@vger.kernel.org Thu Jul 23 04:29:19 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 22:29:06 -0400
+Subject: ASoC: mediatek: mt8183-afe-pcm: Shorten memif_data table using macros
+To: stable@vger.kernel.org
+Cc: Chen-Yu Tsai <wenst@chromium.org>, AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>, Mark Brown <broonie@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723022909.2845199-1-sashal@kernel.org>
+
+From: Chen-Yu Tsai <wenst@chromium.org>
+
+[ Upstream commit 91c2685430f217ae8f2866e4372948eaf123b5c4 ]
+
+The memif_data table describes all the supported PCM channels for the
+audio frontend. Most of the fields are either the same or can be derived
+from the interface's name. This results in a very long table (in source
+code) that can be shortened with macros.
+
+Do just that. Some "convenience" macros were added to cover non-existent
+register fields that would otherwise require multiple layers of macros
+to handle.
+
+Signed-off-by: Chen-Yu Tsai <wenst@chromium.org>
+Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
+Link: https://patch.msgid.link/20250515073825.4155297-2-wenst@chromium.org
+Signed-off-by: Mark Brown <broonie@kernel.org>
+Stable-dep-of: f0334fbfd107 ("ASoC: mediatek: mt8183: Check runtime resume during probe")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ sound/soc/mediatek/mt8183/mt8183-afe-pcm.c |  243 ++++++-----------------------
+ 1 file changed, 54 insertions(+), 189 deletions(-)
+
+--- a/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c
++++ b/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c
+@@ -425,196 +425,61 @@ static const struct snd_soc_component_dr
+       .name = "mt8183-afe-pcm-dai",
+ };
++#define MT8183_MEMIF_BASE(_id, _en_reg, _fs_reg, _mono_reg)    \
++      [MT8183_MEMIF_##_id] = { \
++              .name = #_id,                           \
++              .id = MT8183_MEMIF_##_id,               \
++              .reg_ofs_base = AFE_##_id##_BASE,       \
++              .reg_ofs_cur = AFE_##_id##_CUR,         \
++              .reg_ofs_end = AFE_##_id##_END,         \
++              .fs_reg = (_fs_reg),                    \
++              .fs_shift = _id##_MODE_SFT,             \
++              .fs_maskbit = _id##_MODE_MASK,          \
++              .mono_reg = (_mono_reg),                \
++              .mono_shift = _id##_DATA_SFT,           \
++              .enable_reg = (_en_reg),                \
++              .enable_shift = _id##_ON_SFT,           \
++              .hd_reg = AFE_MEMIF_HD_MODE,            \
++              .hd_align_reg = AFE_MEMIF_HDALIGN,      \
++              .hd_shift = _id##_HD_SFT,               \
++              .hd_align_mshift = _id##_HD_ALIGN_SFT,  \
++              .agent_disable_reg = -1,                \
++              .agent_disable_shift = -1,              \
++              .msb_reg = -1,                          \
++              .msb_shift = -1,                        \
++      }
++
++#define MT8183_MEMIF(_id, _fs_reg, _mono_reg) \
++              MT8183_MEMIF_BASE(_id, AFE_DAC_CON0, _fs_reg, _mono_reg)
++
++/* For convenience with macros: missing register fields */
++#define MOD_DAI_DATA_SFT      -1
++#define HDMI_MODE_SFT         -1
++#define HDMI_MODE_MASK                -1
++#define HDMI_DATA_SFT         -1
++#define HDMI_ON_SFT           -1
++
++/* For convenience with macros: register name differences */
++#define AFE_VUL12_BASE                AFE_VUL_D2_BASE
++#define AFE_VUL12_CUR         AFE_VUL_D2_CUR
++#define AFE_VUL12_END         AFE_VUL_D2_END
++#define AWB2_HD_ALIGN_SFT     AWB2_ALIGN_SFT
++#define VUL12_DATA_SFT                VUL12_MONO_SFT
++#define AFE_HDMI_BASE         AFE_HDMI_OUT_BASE
++#define AFE_HDMI_CUR          AFE_HDMI_OUT_CUR
++#define AFE_HDMI_END          AFE_HDMI_OUT_END
++
+ static const struct mtk_base_memif_data memif_data[MT8183_MEMIF_NUM] = {
+-      [MT8183_MEMIF_DL1] = {
+-              .name = "DL1",
+-              .id = MT8183_MEMIF_DL1,
+-              .reg_ofs_base = AFE_DL1_BASE,
+-              .reg_ofs_cur = AFE_DL1_CUR,
+-              .fs_reg = AFE_DAC_CON1,
+-              .fs_shift = DL1_MODE_SFT,
+-              .fs_maskbit = DL1_MODE_MASK,
+-              .mono_reg = AFE_DAC_CON1,
+-              .mono_shift = DL1_DATA_SFT,
+-              .enable_reg = AFE_DAC_CON0,
+-              .enable_shift = DL1_ON_SFT,
+-              .hd_reg = AFE_MEMIF_HD_MODE,
+-              .hd_align_reg = AFE_MEMIF_HDALIGN,
+-              .hd_shift = DL1_HD_SFT,
+-              .hd_align_mshift = DL1_HD_ALIGN_SFT,
+-              .agent_disable_reg = -1,
+-              .agent_disable_shift = -1,
+-              .msb_reg = -1,
+-              .msb_shift = -1,
+-      },
+-      [MT8183_MEMIF_DL2] = {
+-              .name = "DL2",
+-              .id = MT8183_MEMIF_DL2,
+-              .reg_ofs_base = AFE_DL2_BASE,
+-              .reg_ofs_cur = AFE_DL2_CUR,
+-              .fs_reg = AFE_DAC_CON1,
+-              .fs_shift = DL2_MODE_SFT,
+-              .fs_maskbit = DL2_MODE_MASK,
+-              .mono_reg = AFE_DAC_CON1,
+-              .mono_shift = DL2_DATA_SFT,
+-              .enable_reg = AFE_DAC_CON0,
+-              .enable_shift = DL2_ON_SFT,
+-              .hd_reg = AFE_MEMIF_HD_MODE,
+-              .hd_align_reg = AFE_MEMIF_HDALIGN,
+-              .hd_shift = DL2_HD_SFT,
+-              .hd_align_mshift = DL2_HD_ALIGN_SFT,
+-              .agent_disable_reg = -1,
+-              .agent_disable_shift = -1,
+-              .msb_reg = -1,
+-              .msb_shift = -1,
+-      },
+-      [MT8183_MEMIF_DL3] = {
+-              .name = "DL3",
+-              .id = MT8183_MEMIF_DL3,
+-              .reg_ofs_base = AFE_DL3_BASE,
+-              .reg_ofs_cur = AFE_DL3_CUR,
+-              .fs_reg = AFE_DAC_CON2,
+-              .fs_shift = DL3_MODE_SFT,
+-              .fs_maskbit = DL3_MODE_MASK,
+-              .mono_reg = AFE_DAC_CON1,
+-              .mono_shift = DL3_DATA_SFT,
+-              .enable_reg = AFE_DAC_CON0,
+-              .enable_shift = DL3_ON_SFT,
+-              .hd_reg = AFE_MEMIF_HD_MODE,
+-              .hd_align_reg = AFE_MEMIF_HDALIGN,
+-              .hd_shift = DL3_HD_SFT,
+-              .hd_align_mshift = DL3_HD_ALIGN_SFT,
+-              .agent_disable_reg = -1,
+-              .agent_disable_shift = -1,
+-              .msb_reg = -1,
+-              .msb_shift = -1,
+-      },
+-      [MT8183_MEMIF_VUL2] = {
+-              .name = "VUL2",
+-              .id = MT8183_MEMIF_VUL2,
+-              .reg_ofs_base = AFE_VUL2_BASE,
+-              .reg_ofs_cur = AFE_VUL2_CUR,
+-              .fs_reg = AFE_DAC_CON2,
+-              .fs_shift = VUL2_MODE_SFT,
+-              .fs_maskbit = VUL2_MODE_MASK,
+-              .mono_reg = AFE_DAC_CON2,
+-              .mono_shift = VUL2_DATA_SFT,
+-              .enable_reg = AFE_DAC_CON0,
+-              .enable_shift = VUL2_ON_SFT,
+-              .hd_reg = AFE_MEMIF_HD_MODE,
+-              .hd_align_reg = AFE_MEMIF_HDALIGN,
+-              .hd_shift = VUL2_HD_SFT,
+-              .hd_align_mshift = VUL2_HD_ALIGN_SFT,
+-              .agent_disable_reg = -1,
+-              .agent_disable_shift = -1,
+-              .msb_reg = -1,
+-              .msb_shift = -1,
+-      },
+-      [MT8183_MEMIF_AWB] = {
+-              .name = "AWB",
+-              .id = MT8183_MEMIF_AWB,
+-              .reg_ofs_base = AFE_AWB_BASE,
+-              .reg_ofs_cur = AFE_AWB_CUR,
+-              .fs_reg = AFE_DAC_CON1,
+-              .fs_shift = AWB_MODE_SFT,
+-              .fs_maskbit = AWB_MODE_MASK,
+-              .mono_reg = AFE_DAC_CON1,
+-              .mono_shift = AWB_DATA_SFT,
+-              .enable_reg = AFE_DAC_CON0,
+-              .enable_shift = AWB_ON_SFT,
+-              .hd_reg = AFE_MEMIF_HD_MODE,
+-              .hd_align_reg = AFE_MEMIF_HDALIGN,
+-              .hd_shift = AWB_HD_SFT,
+-              .hd_align_mshift = AWB_HD_ALIGN_SFT,
+-              .agent_disable_reg = -1,
+-              .agent_disable_shift = -1,
+-              .msb_reg = -1,
+-              .msb_shift = -1,
+-      },
+-      [MT8183_MEMIF_AWB2] = {
+-              .name = "AWB2",
+-              .id = MT8183_MEMIF_AWB2,
+-              .reg_ofs_base = AFE_AWB2_BASE,
+-              .reg_ofs_cur = AFE_AWB2_CUR,
+-              .fs_reg = AFE_DAC_CON2,
+-              .fs_shift = AWB2_MODE_SFT,
+-              .fs_maskbit = AWB2_MODE_MASK,
+-              .mono_reg = AFE_DAC_CON2,
+-              .mono_shift = AWB2_DATA_SFT,
+-              .enable_reg = AFE_DAC_CON0,
+-              .enable_shift = AWB2_ON_SFT,
+-              .hd_reg = AFE_MEMIF_HD_MODE,
+-              .hd_align_reg = AFE_MEMIF_HDALIGN,
+-              .hd_shift = AWB2_HD_SFT,
+-              .hd_align_mshift = AWB2_ALIGN_SFT,
+-              .agent_disable_reg = -1,
+-              .agent_disable_shift = -1,
+-              .msb_reg = -1,
+-              .msb_shift = -1,
+-      },
+-      [MT8183_MEMIF_VUL12] = {
+-              .name = "VUL12",
+-              .id = MT8183_MEMIF_VUL12,
+-              .reg_ofs_base = AFE_VUL_D2_BASE,
+-              .reg_ofs_cur = AFE_VUL_D2_CUR,
+-              .fs_reg = AFE_DAC_CON0,
+-              .fs_shift = VUL12_MODE_SFT,
+-              .fs_maskbit = VUL12_MODE_MASK,
+-              .mono_reg = AFE_DAC_CON0,
+-              .mono_shift = VUL12_MONO_SFT,
+-              .enable_reg = AFE_DAC_CON0,
+-              .enable_shift = VUL12_ON_SFT,
+-              .hd_reg = AFE_MEMIF_HD_MODE,
+-              .hd_align_reg = AFE_MEMIF_HDALIGN,
+-              .hd_shift = VUL12_HD_SFT,
+-              .hd_align_mshift = VUL12_HD_ALIGN_SFT,
+-              .agent_disable_reg = -1,
+-              .agent_disable_shift = -1,
+-              .msb_reg = -1,
+-              .msb_shift = -1,
+-      },
+-      [MT8183_MEMIF_MOD_DAI] = {
+-              .name = "MOD_DAI",
+-              .id = MT8183_MEMIF_MOD_DAI,
+-              .reg_ofs_base = AFE_MOD_DAI_BASE,
+-              .reg_ofs_cur = AFE_MOD_DAI_CUR,
+-              .fs_reg = AFE_DAC_CON1,
+-              .fs_shift = MOD_DAI_MODE_SFT,
+-              .fs_maskbit = MOD_DAI_MODE_MASK,
+-              .mono_reg = -1,
+-              .mono_shift = 0,
+-              .enable_reg = AFE_DAC_CON0,
+-              .enable_shift = MOD_DAI_ON_SFT,
+-              .hd_reg = AFE_MEMIF_HD_MODE,
+-              .hd_align_reg = AFE_MEMIF_HDALIGN,
+-              .hd_shift = MOD_DAI_HD_SFT,
+-              .hd_align_mshift = MOD_DAI_HD_ALIGN_SFT,
+-              .agent_disable_reg = -1,
+-              .agent_disable_shift = -1,
+-              .msb_reg = -1,
+-              .msb_shift = -1,
+-      },
+-      [MT8183_MEMIF_HDMI] = {
+-              .name = "HDMI",
+-              .id = MT8183_MEMIF_HDMI,
+-              .reg_ofs_base = AFE_HDMI_OUT_BASE,
+-              .reg_ofs_cur = AFE_HDMI_OUT_CUR,
+-              .fs_reg = -1,
+-              .fs_shift = -1,
+-              .fs_maskbit = -1,
+-              .mono_reg = -1,
+-              .mono_shift = -1,
+-              .enable_reg = -1,       /* control in tdm for sync start */
+-              .enable_shift = -1,
+-              .hd_reg = AFE_MEMIF_HD_MODE,
+-              .hd_align_reg = AFE_MEMIF_HDALIGN,
+-              .hd_shift = HDMI_HD_SFT,
+-              .hd_align_mshift = HDMI_HD_ALIGN_SFT,
+-              .agent_disable_reg = -1,
+-              .agent_disable_shift = -1,
+-              .msb_reg = -1,
+-              .msb_shift = -1,
+-      },
++      MT8183_MEMIF(DL1, AFE_DAC_CON1, AFE_DAC_CON1),
++      MT8183_MEMIF(DL2, AFE_DAC_CON1, AFE_DAC_CON1),
++      MT8183_MEMIF(DL3, AFE_DAC_CON2, AFE_DAC_CON1),
++      MT8183_MEMIF(VUL2, AFE_DAC_CON2, AFE_DAC_CON2),
++      MT8183_MEMIF(AWB, AFE_DAC_CON1, AFE_DAC_CON1),
++      MT8183_MEMIF(AWB2, AFE_DAC_CON2, AFE_DAC_CON2),
++      MT8183_MEMIF(VUL12, AFE_DAC_CON0, AFE_DAC_CON0),
++      MT8183_MEMIF(MOD_DAI, AFE_DAC_CON1, -1),
++      /* enable control in tdm for sync start */
++      MT8183_MEMIF_BASE(HDMI, -1, -1, -1),
+ };
+ static const struct mtk_base_irq_data irq_data[MT8183_IRQ_NUM] = {
diff --git a/queue-6.12/asoc-mediatek-mt8183-afe-pcm-support-32-bit-dma-addresses.patch b/queue-6.12/asoc-mediatek-mt8183-afe-pcm-support-32-bit-dma-addresses.patch
new file mode 100644 (file)
index 0000000..a736c90
--- /dev/null
@@ -0,0 +1,80 @@
+From stable+bounces-288165-greg=kroah.com@vger.kernel.org Thu Jul 23 04:29:19 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 22:29:07 -0400
+Subject: ASoC: mediatek: mt8183-afe-pcm: Support >32 bit DMA addresses
+To: stable@vger.kernel.org
+Cc: Chen-Yu Tsai <wenst@chromium.org>, AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>, Mark Brown <broonie@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723022909.2845199-2-sashal@kernel.org>
+
+From: Chen-Yu Tsai <wenst@chromium.org>
+
+[ Upstream commit 9e7bc5cb8d089d9799e17a9ac99c5da9b13b02e3 ]
+
+The AFE DMA hardware supports up to 34 bits for DMA addresses. This is
+missing from the driver and prevents reserved memory regions from
+working properly when the allocated region is above the 4GB line.
+
+Fill in the related register offsets for each DAI, and also set the
+DMA mask. Also fill in the LSB end register offsets for completeness.
+
+Fixes: a94aec035a12 ("ASoC: mediatek: mt8183: add platform driver")
+Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
+Signed-off-by: Chen-Yu Tsai <wenst@chromium.org>
+Link: https://patch.msgid.link/20250612074901.4023253-8-wenst@chromium.org
+Signed-off-by: Mark Brown <broonie@kernel.org>
+Stable-dep-of: f0334fbfd107 ("ASoC: mediatek: mt8183: Check runtime resume during probe")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ sound/soc/mediatek/mt8183/mt8183-afe-pcm.c |   14 ++++++++++++++
+ 1 file changed, 14 insertions(+)
+
+--- a/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c
++++ b/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c
+@@ -6,6 +6,7 @@
+ // Author: KaiChieh Chuang <kaichieh.chuang@mediatek.com>
+ #include <linux/delay.h>
++#include <linux/dma-mapping.h>
+ #include <linux/module.h>
+ #include <linux/mfd/syscon.h>
+ #include <linux/of.h>
+@@ -432,6 +433,9 @@ static const struct snd_soc_component_dr
+               .reg_ofs_base = AFE_##_id##_BASE,       \
+               .reg_ofs_cur = AFE_##_id##_CUR,         \
+               .reg_ofs_end = AFE_##_id##_END,         \
++              .reg_ofs_base_msb = AFE_##_id##_BASE_MSB,       \
++              .reg_ofs_cur_msb = AFE_##_id##_CUR_MSB,         \
++              .reg_ofs_end_msb = AFE_##_id##_END_MSB,         \
+               .fs_reg = (_fs_reg),                    \
+               .fs_shift = _id##_MODE_SFT,             \
+               .fs_maskbit = _id##_MODE_MASK,          \
+@@ -463,11 +467,17 @@ static const struct snd_soc_component_dr
+ #define AFE_VUL12_BASE                AFE_VUL_D2_BASE
+ #define AFE_VUL12_CUR         AFE_VUL_D2_CUR
+ #define AFE_VUL12_END         AFE_VUL_D2_END
++#define AFE_VUL12_BASE_MSB    AFE_VUL_D2_BASE_MSB
++#define AFE_VUL12_CUR_MSB     AFE_VUL_D2_CUR_MSB
++#define AFE_VUL12_END_MSB     AFE_VUL_D2_END_MSB
+ #define AWB2_HD_ALIGN_SFT     AWB2_ALIGN_SFT
+ #define VUL12_DATA_SFT                VUL12_MONO_SFT
+ #define AFE_HDMI_BASE         AFE_HDMI_OUT_BASE
+ #define AFE_HDMI_CUR          AFE_HDMI_OUT_CUR
+ #define AFE_HDMI_END          AFE_HDMI_OUT_END
++#define AFE_HDMI_BASE_MSB     AFE_HDMI_OUT_BASE_MSB
++#define AFE_HDMI_CUR_MSB      AFE_HDMI_OUT_CUR_MSB
++#define AFE_HDMI_END_MSB      AFE_HDMI_OUT_END_MSB
+ static const struct mtk_base_memif_data memif_data[MT8183_MEMIF_NUM] = {
+       MT8183_MEMIF(DL1, AFE_DAC_CON1, AFE_DAC_CON1),
+@@ -951,6 +961,10 @@ static int mt8183_afe_pcm_dev_probe(stru
+       struct reset_control *rstc;
+       int i, irq_id, ret;
++      ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(34));
++      if (ret)
++              return ret;
++
+       afe = devm_kzalloc(&pdev->dev, sizeof(*afe), GFP_KERNEL);
+       if (!afe)
+               return -ENOMEM;
diff --git a/queue-6.12/asoc-mediatek-mt8183-afe-pcm-use-local-dev-pointer-in-driver-callbacks.patch b/queue-6.12/asoc-mediatek-mt8183-afe-pcm-use-local-dev-pointer-in-driver-callbacks.patch
new file mode 100644 (file)
index 0000000..5329a4a
--- /dev/null
@@ -0,0 +1,144 @@
+From stable+bounces-288166-greg=kroah.com@vger.kernel.org Thu Jul 23 04:30:44 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 22:29:08 -0400
+Subject: ASoC: mediatek: mt8183-afe-pcm: use local `dev` pointer in driver callbacks
+To: stable@vger.kernel.org
+Cc: Chen-Yu Tsai <wenst@chromium.org>, AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>, Mark Brown <broonie@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723022909.2845199-3-sashal@kernel.org>
+
+From: Chen-Yu Tsai <wenst@chromium.org>
+
+[ Upstream commit bb8d8ba4715cb8f997d63d90ba935f6073595df5 ]
+
+The probe and remove functions in the mt8183-afe-pcm driver repeatedly uses
+`&pdev->dev` for |struct device *|, but then assigns this value to
+`afe->dev` and uses that in other places in the same function.
+
+Store `&pdev->dev` in a local pointer and use that exclusively to avoid
+the numerous dereferences and to make the code more consistent. Lines
+are reflowed where it makes sense.
+
+Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
+Signed-off-by: Chen-Yu Tsai <wenst@chromium.org>
+Link: https://patch.msgid.link/20250612074901.4023253-10-wenst@chromium.org
+Signed-off-by: Mark Brown <broonie@kernel.org>
+Stable-dep-of: f0334fbfd107 ("ASoC: mediatek: mt8183: Check runtime resume during probe")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ sound/soc/mediatek/mt8183/mt8183-afe-pcm.c |   37 +++++++++++++----------------
+ 1 file changed, 17 insertions(+), 20 deletions(-)
+
+--- a/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c
++++ b/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c
+@@ -957,27 +957,25 @@ static int mt8183_afe_pcm_dev_probe(stru
+ {
+       struct mtk_base_afe *afe;
+       struct mt8183_afe_private *afe_priv;
+-      struct device *dev;
++      struct device *dev = &pdev->dev;
+       struct reset_control *rstc;
+       int i, irq_id, ret;
+-      ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(34));
++      ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(34));
+       if (ret)
+               return ret;
+-      afe = devm_kzalloc(&pdev->dev, sizeof(*afe), GFP_KERNEL);
++      afe = devm_kzalloc(dev, sizeof(*afe), GFP_KERNEL);
+       if (!afe)
+               return -ENOMEM;
+       platform_set_drvdata(pdev, afe);
+-      afe->platform_priv = devm_kzalloc(&pdev->dev, sizeof(*afe_priv),
+-                                        GFP_KERNEL);
++      afe->platform_priv = devm_kzalloc(dev, sizeof(*afe_priv), GFP_KERNEL);
+       if (!afe->platform_priv)
+               return -ENOMEM;
+       afe_priv = afe->platform_priv;
+-      afe->dev = &pdev->dev;
+-      dev = afe->dev;
++      afe->dev = dev;
+       ret = of_reserved_mem_device_init(dev);
+       if (ret) {
+@@ -1028,7 +1026,7 @@ static int mt8183_afe_pcm_dev_probe(stru
+       /* enable clock for regcache get default value from hw */
+       afe_priv->pm_runtime_bypass_reg_ctl = true;
+-      pm_runtime_get_sync(&pdev->dev);
++      pm_runtime_get_sync(dev);
+       ret = regmap_reinit_cache(afe->regmap, &mt8183_afe_regmap_config);
+       if (ret) {
+@@ -1036,7 +1034,7 @@ static int mt8183_afe_pcm_dev_probe(stru
+               goto err_pm_disable;
+       }
+-      pm_runtime_put_sync(&pdev->dev);
++      pm_runtime_put_sync(dev);
+       afe_priv->pm_runtime_bypass_reg_ctl = false;
+       regcache_cache_only(afe->regmap, true);
+@@ -1094,7 +1092,7 @@ static int mt8183_afe_pcm_dev_probe(stru
+       for (i = 0; i < ARRAY_SIZE(dai_register_cbs); i++) {
+               ret = dai_register_cbs[i](afe);
+               if (ret) {
+-                      dev_warn(afe->dev, "dai register i %d fail, ret %d\n",
++                      dev_warn(dev, "dai register i %d fail, ret %d\n",
+                                i, ret);
+                       goto err_pm_disable;
+               }
+@@ -1103,8 +1101,7 @@ static int mt8183_afe_pcm_dev_probe(stru
+       /* init dai_driver and component_driver */
+       ret = mtk_afe_combine_sub_dai(afe);
+       if (ret) {
+-              dev_warn(afe->dev, "mtk_afe_combine_sub_dai fail, ret %d\n",
+-                       ret);
++              dev_warn(dev, "mtk_afe_combine_sub_dai fail, ret %d\n", ret);
+               goto err_pm_disable;
+       }
+@@ -1116,16 +1113,14 @@ static int mt8183_afe_pcm_dev_probe(stru
+       afe->runtime_suspend = mt8183_afe_runtime_suspend;
+       /* register component */
+-      ret = devm_snd_soc_register_component(&pdev->dev,
+-                                            &mtk_afe_pcm_platform,
++      ret = devm_snd_soc_register_component(dev, &mtk_afe_pcm_platform,
+                                             NULL, 0);
+       if (ret) {
+               dev_warn(dev, "err_platform\n");
+               goto err_pm_disable;
+       }
+-      ret = devm_snd_soc_register_component(afe->dev,
+-                                            &mt8183_afe_pcm_dai_component,
++      ret = devm_snd_soc_register_component(dev, &mt8183_afe_pcm_dai_component,
+                                             afe->dai_drivers,
+                                             afe->num_dai_drivers);
+       if (ret) {
+@@ -1136,15 +1131,17 @@ static int mt8183_afe_pcm_dev_probe(stru
+       return ret;
+ err_pm_disable:
+-      pm_runtime_disable(&pdev->dev);
++      pm_runtime_disable(dev);
+       return ret;
+ }
+ static void mt8183_afe_pcm_dev_remove(struct platform_device *pdev)
+ {
+-      pm_runtime_disable(&pdev->dev);
+-      if (!pm_runtime_status_suspended(&pdev->dev))
+-              mt8183_afe_runtime_suspend(&pdev->dev);
++      struct device *dev = &pdev->dev;
++
++      pm_runtime_disable(dev);
++      if (!pm_runtime_status_suspended(dev))
++              mt8183_afe_runtime_suspend(dev);
+ }
+ static const struct of_device_id mt8183_afe_pcm_dt_match[] = {
diff --git a/queue-6.12/asoc-mediatek-mt8183-check-runtime-resume-during-probe.patch b/queue-6.12/asoc-mediatek-mt8183-check-runtime-resume-during-probe.patch
new file mode 100644 (file)
index 0000000..a70c15f
--- /dev/null
@@ -0,0 +1,60 @@
+From stable+bounces-288167-greg=kroah.com@vger.kernel.org Thu Jul 23 04:30:48 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 22:29:09 -0400
+Subject: ASoC: mediatek: mt8183: Check runtime resume during probe
+To: stable@vger.kernel.org
+Cc: "Cássio Gabriel" <cassiogabrielcontato@gmail.com>, "Mark Brown" <broonie@kernel.org>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260723022909.2845199-4-sashal@kernel.org>
+
+From: Cássio Gabriel <cassiogabrielcontato@gmail.com>
+
+[ Upstream commit f0334fbfd107682d0c95f3f71e25f6127038e2b9 ]
+
+The MT8183 AFE probe uses pm_runtime_get_sync() before reading hardware
+defaults into the regmap cache, but does not check whether runtime resume
+failed. If regmap_reinit_cache() then fails, the temporary runtime PM
+usage count is also not released.
+
+Use pm_runtime_resume_and_get() so resume failures abort probe without
+leaking a usage count, and release the temporary reference before
+handling the regmap cache result.
+
+Fixes: a94aec035a12 ("ASoC: mediatek: mt8183: add platform driver")
+Cc: stable@vger.kernel.org
+Signed-off-by: Cássio Gabriel <cassiogabrielcontato@gmail.com>
+Link: https://patch.msgid.link/20260527-asoc-mt8183-probe-cleanup-v1-2-4f4f5593c8d1@gmail.com
+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>
+---
+ sound/soc/mediatek/mt8183/mt8183-afe-pcm.c |   12 ++++++++----
+ 1 file changed, 8 insertions(+), 4 deletions(-)
+
+--- a/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c
++++ b/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c
+@@ -1026,17 +1026,21 @@ static int mt8183_afe_pcm_dev_probe(stru
+       /* enable clock for regcache get default value from hw */
+       afe_priv->pm_runtime_bypass_reg_ctl = true;
+-      pm_runtime_get_sync(dev);
+-
+-      ret = regmap_reinit_cache(afe->regmap, &mt8183_afe_regmap_config);
++      ret = pm_runtime_resume_and_get(dev);
+       if (ret) {
+-              dev_err(dev, "regmap_reinit_cache fail, ret %d\n", ret);
++              afe_priv->pm_runtime_bypass_reg_ctl = false;
+               goto err_pm_disable;
+       }
++      ret = regmap_reinit_cache(afe->regmap, &mt8183_afe_regmap_config);
+       pm_runtime_put_sync(dev);
+       afe_priv->pm_runtime_bypass_reg_ctl = false;
++      if (ret) {
++              dev_err(dev, "regmap_reinit_cache fail, ret %d\n", ret);
++              goto err_pm_disable;
++      }
++
+       regcache_cache_only(afe->regmap, true);
+       regcache_mark_dirty(afe->regmap);
diff --git a/queue-6.12/asoc-mediatek-mt8192-afe-pcm-simplify-probe-with-local-dev-variable.patch b/queue-6.12/asoc-mediatek-mt8192-afe-pcm-simplify-probe-with-local-dev-variable.patch
new file mode 100644 (file)
index 0000000..2f0056a
--- /dev/null
@@ -0,0 +1,94 @@
+From stable+bounces-288152-greg=kroah.com@vger.kernel.org Thu Jul 23 03:18:39 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 21:17:14 -0400
+Subject: ASoC: mediatek: mt8192-afe-pcm: Simplify probe() with local dev variable
+To: stable@vger.kernel.org
+Cc: Tang Bin <tangbin@cmss.chinamobile.com>, Mark Brown <broonie@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723011715.2780615-1-sashal@kernel.org>
+
+From: Tang Bin <tangbin@cmss.chinamobile.com>
+
+[ Upstream commit 01981565c764c554cc96e2d30a71c42975171416 ]
+
+Simplify the function mt8192_afe_pcm_dev_probe() by
+using local 'dev' instead of '&pdev->dev'.
+
+Signed-off-by: Tang Bin <tangbin@cmss.chinamobile.com>
+Link: https://patch.msgid.link/20241025080026.2393-1-tangbin@cmss.chinamobile.com
+Signed-off-by: Mark Brown <broonie@kernel.org>
+Stable-dep-of: e24d5dde56a5 ("ASoC: mediatek: mt8192: Check runtime resume during probe")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ sound/soc/mediatek/mt8192/mt8192-afe-pcm.c |   19 +++++++++----------
+ 1 file changed, 9 insertions(+), 10 deletions(-)
+
+--- a/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c
++++ b/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c
+@@ -2164,27 +2164,26 @@ static int mt8192_afe_pcm_dev_probe(stru
+ {
+       struct mtk_base_afe *afe;
+       struct mt8192_afe_private *afe_priv;
+-      struct device *dev;
++      struct device *dev = &pdev->dev;
+       struct reset_control *rstc;
+       int i, ret, irq_id;
+-      ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(34));
++      ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(34));
+       if (ret)
+               return ret;
+-      afe = devm_kzalloc(&pdev->dev, sizeof(*afe), GFP_KERNEL);
++      afe = devm_kzalloc(dev, sizeof(*afe), GFP_KERNEL);
+       if (!afe)
+               return -ENOMEM;
+       platform_set_drvdata(pdev, afe);
+-      afe->platform_priv = devm_kzalloc(&pdev->dev, sizeof(*afe_priv),
++      afe->platform_priv = devm_kzalloc(dev, sizeof(*afe_priv),
+                                         GFP_KERNEL);
+       if (!afe->platform_priv)
+               return -ENOMEM;
+       afe_priv = afe->platform_priv;
+-      afe->dev = &pdev->dev;
+-      dev = afe->dev;
++      afe->dev = dev;
+       ret = of_reserved_mem_device_init(dev);
+       if (ret) {
+@@ -2212,7 +2211,7 @@ static int mt8192_afe_pcm_dev_probe(stru
+       if (ret)
+               return dev_err_probe(dev, ret, "failed to trigger audio reset\n");
+-      ret = devm_pm_runtime_enable(&pdev->dev);
++      ret = devm_pm_runtime_enable(dev);
+       if (ret)
+               return ret;
+@@ -2228,13 +2227,13 @@ static int mt8192_afe_pcm_dev_probe(stru
+       /* enable clock for regcache get default value from hw */
+       afe_priv->pm_runtime_bypass_reg_ctl = true;
+-      pm_runtime_get_sync(&pdev->dev);
++      pm_runtime_get_sync(dev);
+       ret = regmap_reinit_cache(afe->regmap, &mt8192_afe_regmap_config);
+       if (ret)
+               return dev_err_probe(dev, ret, "regmap_reinit_cache fail\n");
+-      pm_runtime_put_sync(&pdev->dev);
++      pm_runtime_put_sync(dev);
+       afe_priv->pm_runtime_bypass_reg_ctl = false;
+       regcache_cache_only(afe->regmap, true);
+@@ -2301,7 +2300,7 @@ static int mt8192_afe_pcm_dev_probe(stru
+       afe->runtime_suspend = mt8192_afe_runtime_suspend;
+       /* register platform */
+-      ret = devm_snd_soc_register_component(&pdev->dev,
++      ret = devm_snd_soc_register_component(dev,
+                                             &mtk_afe_pcm_platform,
+                                             afe->dai_drivers,
+                                             afe->num_dai_drivers);
diff --git a/queue-6.12/asoc-mediatek-mt8192-check-runtime-resume-during-probe.patch b/queue-6.12/asoc-mediatek-mt8192-check-runtime-resume-during-probe.patch
new file mode 100644 (file)
index 0000000..2de7049
--- /dev/null
@@ -0,0 +1,62 @@
+From stable+bounces-288151-greg=kroah.com@vger.kernel.org Thu Jul 23 03:18:34 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 21:17:15 -0400
+Subject: ASoC: mediatek: mt8192: Check runtime resume during probe
+To: stable@vger.kernel.org
+Cc: "Cássio Gabriel" <cassiogabrielcontato@gmail.com>, "Mark Brown" <broonie@kernel.org>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260723011715.2780615-2-sashal@kernel.org>
+
+From: Cássio Gabriel <cassiogabrielcontato@gmail.com>
+
+[ Upstream commit e24d5dde56a50946020b134fa8448869093db76a ]
+
+The MT8192 AFE probe enables runtime PM temporarily while reinitializing
+the regmap cache from hardware, but it uses pm_runtime_get_sync()
+without checking the return value. If runtime resume fails, probe keeps
+going without the device necessarily being accessible, and
+pm_runtime_get_sync() may leave the PM usage count incremented.
+
+The regmap_reinit_cache() failure path also returns before dropping the
+temporary PM reference and before clearing pm_runtime_bypass_reg_ctl.
+
+Use pm_runtime_resume_and_get() so resume failures do not leak a usage
+count, and clear the temporary bypass flag after dropping the probe PM
+reference on all regmap_reinit_cache() outcomes.
+
+Fixes: 125ab5d588b0 ("ASoC: mediatek: mt8192: add platform driver")
+Cc: stable@vger.kernel.org
+Signed-off-by: Cássio Gabriel <cassiogabrielcontato@gmail.com>
+Link: https://patch.msgid.link/20260527-asoc-mt8192-probe-cleanup-v1-2-1bb834d05b72@gmail.com
+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>
+---
+ sound/soc/mediatek/mt8192/mt8192-afe-pcm.c |   12 ++++++++----
+ 1 file changed, 8 insertions(+), 4 deletions(-)
+
+--- a/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c
++++ b/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c
+@@ -2227,15 +2227,19 @@ static int mt8192_afe_pcm_dev_probe(stru
+       /* enable clock for regcache get default value from hw */
+       afe_priv->pm_runtime_bypass_reg_ctl = true;
+-      pm_runtime_get_sync(dev);
++      ret = pm_runtime_resume_and_get(dev);
++      if (ret) {
++              afe_priv->pm_runtime_bypass_reg_ctl = false;
++              return dev_err_probe(dev, ret, "failed to resume device\n");
++      }
+       ret = regmap_reinit_cache(afe->regmap, &mt8192_afe_regmap_config);
+-      if (ret)
+-              return dev_err_probe(dev, ret, "regmap_reinit_cache fail\n");
+-
+       pm_runtime_put_sync(dev);
+       afe_priv->pm_runtime_bypass_reg_ctl = false;
++      if (ret)
++              return dev_err_probe(dev, ret, "regmap_reinit_cache fail\n");
++
+       regcache_cache_only(afe->regmap, true);
+       regcache_mark_dirty(afe->regmap);
diff --git a/queue-6.12/ata-libata-core-reject-an-invalid-concurrent-positioning-ranges-count.patch b/queue-6.12/ata-libata-core-reject-an-invalid-concurrent-positioning-ranges-count.patch
new file mode 100644 (file)
index 0000000..a4fa324
--- /dev/null
@@ -0,0 +1,100 @@
+From stable+bounces-290226-greg=kroah.com@vger.kernel.org Wed Jul 29 04:31:55 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 22:29:48 -0400
+Subject: ata: libata-core: Reject an invalid concurrent positioning ranges count
+To: stable@vger.kernel.org
+Cc: Bryam Vargas <hexlabsecurity@proton.me>, Damien Le Moal <dlemoal@kernel.org>, Niklas Cassel <cassel@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260729022948.1112469-1-sashal@kernel.org>
+
+From: Bryam Vargas <hexlabsecurity@proton.me>
+
+[ Upstream commit 533a0b940f901c15e5cbbd4b5d66e871c209e8ce ]
+
+ata_dev_config_cpr() takes the number of range descriptors from buf[0]
+of the concurrent positioning ranges log (up to 255), which the device
+reports independently of the log size in the GPL directory. The count is
+then walked at a fixed 32-byte stride in two places with no bound: the
+log read here, and the INQUIRY VPD page B9h emitter, which writes one
+descriptor per range into the fixed 2048-byte ata_scsi_rbuf. A device
+reporting a count larger than its own log overflows the read buffer (up
+to 7704 bytes past a 512-byte slab), and a count above 62 overflows the
+response buffer on the emit side.
+
+Bound the count once, on probe, against both the log the device returned
+and the number of descriptors the VPD B9h response buffer can hold
+(ATA_DEV_MAX_CPR, derived from the rbuf size). Reject an out-of-range
+count with a warning; this keeps the emitter in bounds with no separate
+change there.
+
+Suggested-by: Damien Le Moal <dlemoal@kernel.org>
+Fixes: fe22e1c2f705 ("libata: support concurrent positioning ranges log")
+Fixes: c745dfc541e7 ("libata: fix reading concurrent positioning ranges log")
+Cc: stable@vger.kernel.org
+Signed-off-by: Bryam Vargas <hexlabsecurity@proton.me>
+Reviewed-by: Niklas Cassel <cassel@kernel.org>
+Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
+[ adapted `kzalloc_flex()` allocation to `kzalloc(struct_size(...), GFP_KERNEL)` and adjusted context offsets. ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/ata/libata-core.c |   18 ++++++++++++++++++
+ drivers/ata/libata-scsi.c |    2 --
+ drivers/ata/libata.h      |    9 +++++++++
+ 3 files changed, 27 insertions(+), 2 deletions(-)
+
+--- a/drivers/ata/libata-core.c
++++ b/drivers/ata/libata-core.c
+@@ -2804,6 +2804,24 @@ static void ata_dev_config_cpr(struct at
+       if (!nr_cpr)
+               goto out;
++      /*
++       * The device reports the number of CPR descriptors independently of the
++       * log size, and that count is also used to emit VPD page B9h into the
++       * fixed-size rbuf. Reject a count larger than what that buffer can hold
++       * (ATA_DEV_MAX_CPR) or larger than the log the device actually returned.
++       */
++      if (nr_cpr > ATA_DEV_MAX_CPR) {
++              ata_dev_warn(dev,
++                           "Too many concurrent positioning ranges\n");
++              goto out;
++      }
++
++      if (buf_len < 64 + (size_t)nr_cpr * 32) {
++              ata_dev_warn(dev,
++                           "Invalid number of concurrent positioning ranges\n");
++              goto out;
++      }
++
+       cpr_log = kzalloc(struct_size(cpr_log, cpr, nr_cpr), GFP_KERNEL);
+       if (!cpr_log)
+               goto out;
+--- a/drivers/ata/libata-scsi.c
++++ b/drivers/ata/libata-scsi.c
+@@ -37,8 +37,6 @@
+ #include "libata.h"
+ #include "libata-transport.h"
+-#define ATA_SCSI_RBUF_SIZE    2048
+-
+ static DEFINE_SPINLOCK(ata_scsi_rbuf_lock);
+ static u8 ata_scsi_rbuf[ATA_SCSI_RBUF_SIZE];
+--- a/drivers/ata/libata.h
++++ b/drivers/ata/libata.h
+@@ -145,6 +145,15 @@ static inline void ata_acpi_bind_dev(str
+ #endif
+ /* libata-scsi.c */
++#define ATA_SCSI_RBUF_SIZE    2048
++
++/*
++ * Maximum number of concurrent positioning ranges (CPR) supported. The ACS
++ * specifications allow up to 255, but we limit this to the number of CPR
++ * descriptors that fit in the rbuf buffer used to emit VPD page B9h.
++ */
++#define ATA_DEV_MAX_CPR               min(255, ((ATA_SCSI_RBUF_SIZE - 64) / 32))
++
+ extern struct ata_device *ata_scsi_find_dev(struct ata_port *ap,
+                                           const struct scsi_device *scsidev);
+ extern int ata_scsi_add_hosts(struct ata_host *host,
diff --git a/queue-6.12/audit-fix-recursive-locking-deadlock-in-audit_dupe_exe.patch b/queue-6.12/audit-fix-recursive-locking-deadlock-in-audit_dupe_exe.patch
new file mode 100644 (file)
index 0000000..c8b10e2
--- /dev/null
@@ -0,0 +1,364 @@
+From stable+bounces-287660-greg=kroah.com@vger.kernel.org Wed Jul 22 02:00:45 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 19:59:04 -0400
+Subject: audit: fix recursive locking deadlock in audit_dupe_exe()
+To: stable@vger.kernel.org
+Cc: Ricardo Robaina <rrobaina@redhat.com>, stable@kernel.org, Waiman Long <longman@redhat.com>, Richard Guy Briggs <rgb@redhat.com>, Nathan Chancellor <nathan@kernel.org>, Paul Moore <paul@paul-moore.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721235904.525962-4-sashal@kernel.org>
+
+From: Ricardo Robaina <rrobaina@redhat.com>
+
+[ Upstream commit 81905b5acbe77284734438df3fbec1158e6429a3 ]
+
+A deadlock occurs in the audit subsystem when duplicating
+executable-related rules.
+
+When a file is moved (e.g., via do_renameat2()), the VFS layer locks
+the parent directory (I_MUTEX_PARENT), which synchronously triggers an
+fsnotify_move event. If an existing executable audit rule matches the
+file being moved, the audit subsystem catches this event and calls
+audit_dupe_exe() to duplicate the watch and update the rule. Then,
+audit_alloc_mark() would call kern_path_parent() to resolve the path,
+leading to a blind attempt to acquire the exact same I_MUTEX_PARENT lock
+already held by the task, resulting in the following recursive locking
+deadlock:
+
+ ============================================
+ WARNING: possible recursive locking detected
+ 6.12.0-55.27.1.el10_0.x86_64+debug #1 Not tainted
+ --------------------------------------------
+ mv/5099 is trying to acquire lock:
+ ffff888132845358 (&inode->i_sb->s_type->i_mutex_dir_key/1){+.+.}-{3:3},
+ at: __kern_path_locked+0x10a/0x2f0
+
+ but task is already holding lock:
+ ffff888132846b58 (&inode->i_sb->s_type->i_mutex_dir_key/1){+.+.}-{3:3},
+ at: lock_two_directories+0x13f/0x2b0
+
+ other info that might help us debug this:
+  Possible unsafe locking scenario:
+
+        CPU0
+        ----
+   lock(&inode->i_sb->s_type->i_mutex_dir_key/1);
+   lock(&inode->i_sb->s_type->i_mutex_dir_key/1);
+
+  *** DEADLOCK ***
+
+  May be due to missing lock nesting notation
+
+  6 locks held by mv/5099:
+  #0: ffff888112a9c440 (sb_writers#13)
+  at: do_renameat2+0x34c/0xbc0
+  #1: ffff888112a9c790 (&type->s_vfs_rename_key#3)
+  at: do_renameat2+0x415/0xbc0
+  #2: ffff888132846b58 (&inode->i_sb->s_type->i_mutex_dir_key/1)
+  at: lock_two_directories+0x13f/0x2b0
+  #3: ffff888132845358 (&inode->i_sb->s_type->i_mutex_dir_key/5)
+  at: lock_two_directories+0x175/0x2b0
+  #4: ffffffffb3a1fb10 (&fsnotify_mark_srcu)
+  at: fsnotify+0x454/0x28a0
+  #5: ffffffffaf886230 (audit_filter_mutex)
+  at: audit_update_watch+0x36/0x11e0
+
+ stack backtrace:
+ Call Trace:
+  <TASK>
+  dump_stack_lvl+0x6f/0xb0
+  print_deadlock_bug.cold+0xbd/0xca
+  validate_chain+0x83a/0xf00
+  __lock_acquire+0xcac/0x1d20
+  lock_acquire.part.0+0x11b/0x360
+  down_write_nested+0x9f/0x230
+  __kern_path_locked+0x10a/0x2f0
+  kern_path_locked+0x26/0x40
+  audit_alloc_mark+0xfb/0x4f0
+  audit_dupe_exe+0x6c/0xe0
+  audit_dupe_rule+0x6c2/0xc00
+  audit_update_watch+0x4cc/0x11e0
+  audit_watch_handle_event+0x12c/0x1b0
+  send_to_group+0x5d0/0x8b0
+  fsnotify+0x615/0x28a0
+  fsnotify_move+0x1d8/0x630
+  vfs_rename+0xdcd/0x1df0
+  do_renameat2+0x9d4/0xbc0
+  __x64_sys_renameat+0x192/0x260
+  do_syscall_64+0x92/0x180
+  entry_SYSCALL_64_after_hwframe+0x76/0x7e
+ RIP: 0033:0x7f0491fe8c4e
+ Code: 0f 1f 40 00 48 8b 15 c1 e1 16 00 f7 d8 64 89 02 b8 ff ff ff ff
+ c3 66 0f 1f 44 00 00 f3 0f 1e fa 49 89 ca b8 08 01 00 00 0f 05 <48>
+ 3d 00 f0 ff ff 77 0a c3 66 0f 1f 84 00 00 00 00 00 48 8b 15 89
+ RSP: 002b:00007ffc7210bf38 EFLAGS: 00000246 ORIG_RAX: 0000000000000108
+ RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007f0491fe8c4e
+ RDX: 0000000000000003 RSI: 00007ffc7210e6c8 RDI: 00000000ffffff9c
+ RBP: 0000000000000000 R08: 0000000000000000 R09: 0000000000000001
+ R10: 00005575eb2dae2a R11: 0000000000000246 R12: 00005575eb2dae2a
+ R13: 00007ffc7210e6c8 R14: 0000000000000003 R15: 00000000ffffff9c
+  </TASK>
+
+The aforementioned deadlock can be consistently reproduced by running
+the script below:
+
+ audit-dupe-exe-deadlock.sh
+ --------------------------
+ #!/bin/bash
+ auditctl -D
+ mkdir -p /tmp/foo
+ touch /tmp/file
+ auditctl -a always,exit -F exe=/tmp/file -F path=/tmp/file -S all -k dr
+ mv /tmp/file /tmp/foo/file
+ rm -Rf /tmp/foo
+
+This patch fixes the issue by introducing struct audit_watch_ctx to pass
+the fsnotify event context down to audit_alloc_mark(). By utilizing the
+already-resolved directory inode provided by the event, we bypass the
+kern_path_parent() path resolution entirely, safely avoiding the
+recursive lock. Furthermore, it explicitly allows duplicate fsnotify
+marks (allow_dups = 1) during the rename update, allowing the new rule's
+mark to safely coexist with the old rule's mark until the old rule is
+freed.
+
+P.S.: This issue was identified and reproduced during a comprehensive
+code coverage analysis of the audit subsystem. The full report is
+available at the link below:
+
+https://people.redhat.com/rrobaina/audit-code-coverage-analysis.pdf
+
+P.P.S: With the permission of both Ricardo and Nathan, I've squashed a
+fixup patch from Nathan that addresses a compile time error when
+CONFIG_AUDITSYSCALL=n.
+
+Cc: stable@kernel.org
+Fixes: 34d99af52ad4 ("audit: implement audit by executable")
+Acked-by: Waiman Long <longman@redhat.com>
+Acked-by: Richard Guy Briggs <rgb@redhat.com>
+Signed-off-by: Nathan Chancellor <nathan@kernel.org>
+Signed-off-by: Ricardo Robaina <rrobaina@redhat.com>
+[PM: move link metadata into the msg, apply fix from NC]
+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.h          |   17 ++++++++++++-----
+ kernel/audit_fsnotify.c |   32 +++++++++++++++++++++++---------
+ kernel/audit_watch.c    |   25 +++++++++++++++++--------
+ kernel/auditfilter.c    |    9 +++++----
+ 4 files changed, 57 insertions(+), 26 deletions(-)
+
+--- a/kernel/audit.h
++++ b/kernel/audit.h
+@@ -250,8 +250,13 @@ extern int audit_del_rule(struct audit_e
+ extern void audit_free_rule_rcu(struct rcu_head *head);
+ extern struct list_head audit_filter_list[];
+-extern struct audit_entry *audit_dupe_rule(struct audit_krule *old);
++struct audit_watch_ctx {
++      struct inode *dir;
++      struct inode *child;
++};
++extern struct audit_entry *audit_dupe_rule(struct audit_krule *old,
++                                         struct audit_watch_ctx *ctx);
+ extern void audit_log_d_path_exe(struct audit_buffer *ab,
+                                struct mm_struct *mm);
+@@ -274,13 +279,15 @@ extern char *audit_watch_path(struct aud
+ extern int audit_watch_compare(struct audit_watch *watch, u64 ino, dev_t dev);
+ extern struct audit_fsnotify_mark *audit_alloc_mark(struct audit_krule *krule,
+-                                                  char *pathname, int len);
++                                                  char *pathname, int len,
++                                                  struct audit_watch_ctx *ctx);
+ extern char *audit_mark_path(struct audit_fsnotify_mark *mark);
+ extern void audit_remove_mark(struct audit_fsnotify_mark *audit_mark);
+ extern void audit_remove_mark_rule(struct audit_krule *krule);
+ extern int audit_mark_compare(struct audit_fsnotify_mark *mark, u64 ino,
+                             dev_t dev);
+-extern int audit_dupe_exe(struct audit_krule *new, struct audit_krule *old);
++extern int audit_dupe_exe(struct audit_krule *new, struct audit_krule *old,
++                        struct audit_watch_ctx *ctx);
+ extern int audit_exe_compare(struct task_struct *tsk,
+                            struct audit_fsnotify_mark *mark);
+@@ -311,13 +318,13 @@ extern struct list_head *audit_killed_tr
+ #define audit_watch_path(w) ""
+ #define audit_watch_compare(w, i, d) 0
+-#define audit_alloc_mark(k, p, l) (ERR_PTR(-EINVAL))
++#define audit_alloc_mark(k, p, l, c) (ERR_PTR(-EINVAL))
+ #define audit_mark_path(m) ""
+ #define audit_remove_mark(m) do { } while (0)
+ #define audit_remove_mark_rule(k) do { } while (0)
+ #define audit_mark_compare(m, i, d) 0
+ #define audit_exe_compare(t, m) (-EINVAL)
+-#define audit_dupe_exe(n, o) (-EINVAL)
++#define audit_dupe_exe(n, o, c) (-EINVAL)
+ #define audit_remove_tree_rule(rule) BUG()
+ #define audit_add_tree_rule(rule) -EINVAL
+--- a/kernel/audit_fsnotify.c
++++ b/kernel/audit_fsnotify.c
+@@ -71,19 +71,30 @@ static void audit_update_mark(struct aud
+       audit_mark->ino = inode ? inode->i_ino : AUDIT_INO_UNSET;
+ }
+-struct audit_fsnotify_mark *audit_alloc_mark(struct audit_krule *krule, char *pathname, int len)
++struct audit_fsnotify_mark *audit_alloc_mark(struct audit_krule *krule, char *pathname,
++                                           int len, struct audit_watch_ctx *ctx)
+ {
+       struct audit_fsnotify_mark *audit_mark;
+       struct path path;
+       struct dentry *dentry;
+-      int ret;
++      struct inode *dir, *child;
++      int ret, allow_dups;
+       if (pathname[0] != '/' || pathname[len-1] == '/')
+               return ERR_PTR(-EINVAL);
+-      dentry = kern_path_parent(pathname, &path);
+-      if (IS_ERR(dentry))
+-              return ERR_CAST(dentry); /* returning an error */
++      if (!ctx) {
++              dentry = kern_path_parent(pathname, &path);
++              if (IS_ERR(dentry))
++                      return ERR_CAST(dentry); /* returning an error */
++              dir = d_inode(path.dentry);
++              child = d_inode(dentry);
++              allow_dups = 0;
++      } else {
++              dir = ctx->dir;
++              child = ctx->child;
++              allow_dups = 1;
++      }
+       audit_mark = kzalloc(sizeof(*audit_mark), GFP_KERNEL);
+       if (unlikely(!audit_mark)) {
+@@ -94,18 +105,21 @@ struct audit_fsnotify_mark *audit_alloc_
+       fsnotify_init_mark(&audit_mark->mark, audit_fsnotify_group);
+       audit_mark->mark.mask = AUDIT_FS_EVENTS;
+       audit_mark->path = pathname;
+-      audit_update_mark(audit_mark, dentry->d_inode);
+       audit_mark->rule = krule;
+-      ret = fsnotify_add_inode_mark(&audit_mark->mark, path.dentry->d_inode, 0);
++      audit_update_mark(audit_mark, child);
++      ret = fsnotify_add_inode_mark(&audit_mark->mark, dir, allow_dups);
++
+       if (ret < 0) {
+               audit_mark->path = NULL;
+               fsnotify_put_mark(&audit_mark->mark);
+               audit_mark = ERR_PTR(ret);
+       }
+ out:
+-      dput(dentry);
+-      path_put(&path);
++      if (!ctx) {
++              dput(dentry);
++              path_put(&path);
++      }
+       return audit_mark;
+ }
+--- a/kernel/audit_watch.c
++++ b/kernel/audit_watch.c
+@@ -244,7 +244,8 @@ static void audit_watch_log_rule_change(
+ /* Update inode info in audit rules based on filesystem event. */
+ static void audit_update_watch(struct audit_parent *parent,
+                              const struct qstr *dname, dev_t dev,
+-                             u64 ino, unsigned int invalidating)
++                             u64 ino, unsigned int invalidating,
++                             struct audit_watch_ctx *ctx)
+ {
+       struct audit_watch *owatch, *nwatch, *nextw;
+       struct audit_krule *r, *nextr;
+@@ -280,7 +281,7 @@ static void audit_update_watch(struct au
+                       list_del(&oentry->rule.rlist);
+                       list_del_rcu(&oentry->list);
+-                      nentry = audit_dupe_rule(&oentry->rule);
++                      nentry = audit_dupe_rule(&oentry->rule, ctx);
+                       if (IS_ERR(nentry)) {
+                               list_del(&oentry->rule.list);
+                               audit_panic("error updating watch, removing");
+@@ -479,10 +480,17 @@ static int audit_watch_handle_event(stru
+       if (WARN_ON_ONCE(inode_mark->group != audit_watch_group))
+               return 0;
+-      if (mask & (FS_CREATE|FS_MOVED_TO) && inode)
+-              audit_update_watch(parent, dname, inode->i_sb->s_dev, inode->i_ino, 0);
+-      else if (mask & (FS_DELETE|FS_MOVED_FROM))
+-              audit_update_watch(parent, dname, AUDIT_DEV_UNSET, AUDIT_INO_UNSET, 1);
++      if (mask & (FS_CREATE|FS_MOVED_TO) && inode) {
++              struct audit_watch_ctx ctx = { .dir = dir, .child = inode };
++
++              audit_update_watch(parent, dname, inode->i_sb->s_dev, inode->i_ino, 0,
++                                 &ctx);
++      } else if (mask & (FS_DELETE|FS_MOVED_FROM)) {
++              struct audit_watch_ctx ctx = { .dir = dir, .child = NULL };
++
++              audit_update_watch(parent, dname, AUDIT_DEV_UNSET, AUDIT_INO_UNSET, 1,
++                                 &ctx);
++      }
+       else if (mask & (FS_DELETE_SELF|FS_UNMOUNT|FS_MOVE_SELF))
+               audit_remove_parent_watches(parent);
+@@ -505,7 +513,8 @@ static int __init audit_watch_init(void)
+ }
+ device_initcall(audit_watch_init);
+-int audit_dupe_exe(struct audit_krule *new, struct audit_krule *old)
++int audit_dupe_exe(struct audit_krule *new, struct audit_krule *old,
++                 struct audit_watch_ctx *ctx)
+ {
+       struct audit_fsnotify_mark *audit_mark;
+       char *pathname;
+@@ -514,7 +523,7 @@ int audit_dupe_exe(struct audit_krule *n
+       if (!pathname)
+               return -ENOMEM;
+-      audit_mark = audit_alloc_mark(new, pathname, strlen(pathname));
++      audit_mark = audit_alloc_mark(new, pathname, strlen(pathname), ctx);
+       if (IS_ERR(audit_mark)) {
+               kfree(pathname);
+               return PTR_ERR(audit_mark);
+--- a/kernel/auditfilter.c
++++ b/kernel/auditfilter.c
+@@ -590,7 +590,7 @@ static struct audit_entry *audit_data_to
+                               err = PTR_ERR(str);
+                               goto exit_free;
+                       }
+-                      audit_mark = audit_alloc_mark(&entry->rule, str, f_val);
++                      audit_mark = audit_alloc_mark(&entry->rule, str, f_val, NULL);
+                       if (IS_ERR(audit_mark)) {
+                               kfree(str);
+                               err = PTR_ERR(audit_mark);
+@@ -818,7 +818,8 @@ static inline int audit_dupe_lsm_field(s
+  * rule with the new rule in the filterlist, then free the old rule.
+  * The rlist element is undefined; list manipulations are handled apart from
+  * the initial copy. */
+-struct audit_entry *audit_dupe_rule(struct audit_krule *old)
++struct audit_entry *audit_dupe_rule(struct audit_krule *old,
++                                  struct audit_watch_ctx *ctx)
+ {
+       u32 fcount = old->field_count;
+       struct audit_entry *entry;
+@@ -877,7 +878,7 @@ struct audit_entry *audit_dupe_rule(stru
+                               new->filterkey = fk;
+                       break;
+               case AUDIT_EXE:
+-                      err = audit_dupe_exe(new, old);
++                      err = audit_dupe_exe(new, old, ctx);
+                       break;
+               }
+               if (err) {
+@@ -1408,7 +1409,7 @@ static int update_lsm_rule(struct audit_
+       if (!security_audit_rule_known(r))
+               return 0;
+-      nentry = audit_dupe_rule(r);
++      nentry = audit_dupe_rule(r, NULL);
+       if (entry->rule.exe)
+               audit_remove_mark(entry->rule.exe);
+       if (IS_ERR(nentry)) {
diff --git a/queue-6.12/audit-use-unsigned-int-instead-of-unsigned.patch b/queue-6.12/audit-use-unsigned-int-instead-of-unsigned.patch
new file mode 100644 (file)
index 0000000..c74761a
--- /dev/null
@@ -0,0 +1,190 @@
+From stable+bounces-287659-greg=kroah.com@vger.kernel.org Wed Jul 22 02:19:46 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 19:59:03 -0400
+Subject: audit: use 'unsigned int' instead of 'unsigned'
+To: stable@vger.kernel.org
+Cc: Ricardo Robaina <rrobaina@redhat.com>, Paul Moore <paul@paul-moore.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721235904.525962-3-sashal@kernel.org>
+
+From: Ricardo Robaina <rrobaina@redhat.com>
+
+[ Upstream commit 8b226771014beab1292081151a99530886ce54b4 ]
+
+Address checkpatch.pl warning below, across the audit subsystem:
+
+  WARNING: Prefer 'unsigned int' to bare use of 'unsigned'
+
+Minor cleanup, no functional changes.
+
+Signed-off-by: Ricardo Robaina <rrobaina@redhat.com>
+Signed-off-by: Paul Moore <paul@paul-moore.com>
+Stable-dep-of: 81905b5acbe7 ("audit: fix recursive locking deadlock in audit_dupe_exe()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/audit.h      |    4 ++--
+ include/linux/audit_arch.h |   12 ++++++------
+ kernel/audit.c             |    2 +-
+ kernel/audit.h             |    2 +-
+ kernel/audit_tree.c        |    2 +-
+ kernel/auditfilter.c       |    8 ++++----
+ kernel/auditsc.c           |    2 +-
+ lib/compat_audit.c         |   12 ++++++------
+ 8 files changed, 22 insertions(+), 22 deletions(-)
+
+--- a/include/linux/audit.h
++++ b/include/linux/audit.h
+@@ -123,8 +123,8 @@ enum audit_nfcfgop {
+       AUDIT_NFT_OP_INVALID,
+ };
+-extern int __init audit_register_class(int class, unsigned *list);
+-extern int audit_classify_syscall(int abi, unsigned syscall);
++extern int __init audit_register_class(int class, unsigned int *list);
++extern int audit_classify_syscall(int abi, unsigned int syscall);
+ extern int audit_classify_arch(int arch);
+ /* audit_names->type values */
+--- a/include/linux/audit_arch.h
++++ b/include/linux/audit_arch.h
+@@ -21,13 +21,13 @@ enum auditsc_class_t {
+       AUDITSC_NVALS /* count */
+ };
+-extern int audit_classify_compat_syscall(int abi, unsigned syscall);
++extern int audit_classify_compat_syscall(int abi, unsigned int syscall);
+ /* only for compat system calls */
+-extern unsigned compat_write_class[];
+-extern unsigned compat_read_class[];
+-extern unsigned compat_dir_class[];
+-extern unsigned compat_chattr_class[];
+-extern unsigned compat_signal_class[];
++extern unsigned int compat_write_class[];
++extern unsigned int compat_read_class[];
++extern unsigned int compat_dir_class[];
++extern unsigned int compat_chattr_class[];
++extern unsigned int compat_signal_class[];
+ #endif
+--- a/kernel/audit.c
++++ b/kernel/audit.c
+@@ -1985,7 +1985,7 @@ static void audit_log_vformat(struct aud
+                * here and AUDIT_BUFSIZ is at least 1024, then we can
+                * log everything that printk could have logged. */
+               avail = audit_expand(ab,
+-                      max_t(unsigned, AUDIT_BUFSIZ, 1+len-avail));
++                      max_t(unsigned int, AUDIT_BUFSIZ, 1+len-avail));
+               if (!avail)
+                       goto out_va_end;
+               len = vsnprintf(skb_tail_pointer(skb), avail, fmt, args2);
+--- a/kernel/audit.h
++++ b/kernel/audit.h
+@@ -227,7 +227,7 @@ static inline int audit_hash_ino(u64 ino
+ /* Indicates that audit should log the full pathname. */
+ #define AUDIT_NAME_FULL -1
+-extern int audit_match_class(int class, unsigned syscall);
++extern int audit_match_class(int class, unsigned int syscall);
+ extern int audit_comparator(const u32 left, const u32 op, const u32 right);
+ extern int audit_uid_comparator(kuid_t left, u32 op, kuid_t right);
+ extern int audit_gid_comparator(kgid_t left, u32 op, kgid_t right);
+--- a/kernel/audit_tree.c
++++ b/kernel/audit_tree.c
+@@ -33,7 +33,7 @@ struct audit_chunk {
+       struct audit_node {
+               struct list_head list;
+               struct audit_tree *owner;
+-              unsigned index;         /* index; upper bit indicates 'will prune' */
++              unsigned int index;     /* index; upper bit indicates 'will prune' */
+       } owners[] __counted_by(count);
+ };
+--- a/kernel/auditfilter.c
++++ b/kernel/auditfilter.c
+@@ -165,13 +165,13 @@ static inline int audit_to_inode(struct
+ static __u32 *classes[AUDIT_SYSCALL_CLASSES];
+-int __init audit_register_class(int class, unsigned *list)
++int __init audit_register_class(int class, unsigned int *list)
+ {
+       __u32 *p = kcalloc(AUDIT_BITMASK_SIZE, sizeof(__u32), GFP_KERNEL);
+       if (!p)
+               return -ENOMEM;
+       while (*list != ~0U) {
+-              unsigned n = *list++;
++              unsigned int n = *list++;
+               if (n >= AUDIT_BITMASK_SIZE * 32 - AUDIT_SYSCALL_CLASSES) {
+                       kfree(p);
+                       return -EINVAL;
+@@ -186,7 +186,7 @@ int __init audit_register_class(int clas
+       return 0;
+ }
+-int audit_match_class(int class, unsigned syscall)
++int audit_match_class(int class, unsigned int syscall)
+ {
+       if (unlikely(syscall >= AUDIT_BITMASK_SIZE * 32))
+               return 0;
+@@ -237,7 +237,7 @@ static int audit_match_signal(struct aud
+ /* Common user-space to kernel rule translation. */
+ static inline struct audit_entry *audit_to_entry_common(struct audit_rule_data *rule)
+ {
+-      unsigned listnr;
++      unsigned int listnr;
+       struct audit_entry *entry;
+       int i, err;
+--- a/kernel/auditsc.c
++++ b/kernel/auditsc.c
+@@ -150,7 +150,7 @@ static const struct audit_nfcfgop_tab au
+ static int audit_match_perm(struct audit_context *ctx, int mask)
+ {
+-      unsigned n;
++      unsigned int n;
+       if (unlikely(!ctx))
+               return 0;
+--- a/lib/compat_audit.c
++++ b/lib/compat_audit.c
+@@ -4,32 +4,32 @@
+ #include <linux/audit_arch.h>
+ #include <asm/unistd32.h>
+-unsigned compat_dir_class[] = {
++unsigned int compat_dir_class[] = {
+ #include <asm-generic/audit_dir_write.h>
+ ~0U
+ };
+-unsigned compat_read_class[] = {
++unsigned int compat_read_class[] = {
+ #include <asm-generic/audit_read.h>
+ ~0U
+ };
+-unsigned compat_write_class[] = {
++unsigned int compat_write_class[] = {
+ #include <asm-generic/audit_write.h>
+ ~0U
+ };
+-unsigned compat_chattr_class[] = {
++unsigned int compat_chattr_class[] = {
+ #include <asm-generic/audit_change_attr.h>
+ ~0U
+ };
+-unsigned compat_signal_class[] = {
++unsigned int compat_signal_class[] = {
+ #include <asm-generic/audit_signal.h>
+ ~0U
+ };
+-int audit_classify_compat_syscall(int abi, unsigned syscall)
++int audit_classify_compat_syscall(int abi, unsigned int syscall)
+ {
+       switch (syscall) {
+ #ifdef __NR_open
diff --git a/queue-6.12/audit-widen-ino-fields-to-u64.patch b/queue-6.12/audit-widen-ino-fields-to-u64.patch
new file mode 100644 (file)
index 0000000..3be00e9
--- /dev/null
@@ -0,0 +1,159 @@
+From stable+bounces-287658-greg=kroah.com@vger.kernel.org Wed Jul 22 02:00:42 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 19:59:02 -0400
+Subject: audit: widen ino fields to u64
+To: stable@vger.kernel.org
+Cc: Jeff Layton <jlayton@kernel.org>, Paul Moore <paul@paul-moore.com>, Christian Brauner <brauner@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721235904.525962-2-sashal@kernel.org>
+
+From: Jeff Layton <jlayton@kernel.org>
+
+[ Upstream commit 125dfa218134df7cc112667e92984de9d8cd0bf6 ]
+
+inode->i_ino is being widened from unsigned long to u64. The audit
+subsystem uses unsigned long ino in struct fields, function parameters,
+and local variables that store inode numbers from arbitrary filesystems.
+On 32-bit platforms this truncates inode numbers that exceed 32 bits,
+which will cause incorrect audit log entries and broken watch/mark
+comparisons.
+
+Widen all audit ino fields, parameters, and locals to u64, and update
+the inode format string from %lu to %llu to match.
+
+Signed-off-by: Jeff Layton <jlayton@kernel.org>
+Link: https://patch.msgid.link/20260304-iino-u64-v3-2-2257ad83d372@kernel.org
+Acked-by: Paul Moore <paul@paul-moore.com>
+Signed-off-by: Christian Brauner <brauner@kernel.org>
+Stable-dep-of: 81905b5acbe7 ("audit: fix recursive locking deadlock in audit_dupe_exe()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/audit.h   |    2 +-
+ kernel/audit.h          |    9 ++++-----
+ kernel/audit_fsnotify.c |    2 +-
+ kernel/audit_watch.c    |   10 +++++-----
+ kernel/auditsc.c        |    4 ++--
+ 5 files changed, 13 insertions(+), 14 deletions(-)
+
+--- a/include/linux/audit.h
++++ b/include/linux/audit.h
+@@ -16,7 +16,7 @@
+ #include <uapi/linux/netfilter/nf_tables.h>
+ #include <uapi/linux/fanotify.h>
+-#define AUDIT_INO_UNSET ((unsigned long)-1)
++#define AUDIT_INO_UNSET ((u64)-1)
+ #define AUDIT_DEV_UNSET ((dev_t)-1)
+ struct audit_sig_info {
+--- a/kernel/audit.h
++++ b/kernel/audit.h
+@@ -75,7 +75,7 @@ struct audit_names {
+       int                     name_len;       /* number of chars to log */
+       bool                    hidden;         /* don't log this record */
+-      unsigned long           ino;
++      u64                     ino;
+       dev_t                   dev;
+       umode_t                 mode;
+       kuid_t                  uid;
+@@ -219,9 +219,9 @@ extern int auditd_test_task(struct task_
+ #define AUDIT_INODE_BUCKETS   32
+ extern struct list_head audit_inode_hash[AUDIT_INODE_BUCKETS];
+-static inline int audit_hash_ino(u32 ino)
++static inline int audit_hash_ino(u64 ino)
+ {
+-      return (ino & (AUDIT_INODE_BUCKETS-1));
++      return ((u32)ino & (AUDIT_INODE_BUCKETS-1));
+ }
+ /* Indicates that audit should log the full pathname. */
+@@ -271,8 +271,7 @@ extern int audit_to_watch(struct audit_k
+ extern int audit_add_watch(struct audit_krule *krule, struct list_head **list);
+ extern void audit_remove_watch_rule(struct audit_krule *krule);
+ extern char *audit_watch_path(struct audit_watch *watch);
+-extern int audit_watch_compare(struct audit_watch *watch, unsigned long ino,
+-                             dev_t dev);
++extern int audit_watch_compare(struct audit_watch *watch, u64 ino, dev_t dev);
+ extern struct audit_fsnotify_mark *audit_alloc_mark(struct audit_krule *krule,
+                                                   char *pathname, int len);
+--- a/kernel/audit_fsnotify.c
++++ b/kernel/audit_fsnotify.c
+@@ -25,7 +25,7 @@
+  */
+ struct audit_fsnotify_mark {
+       dev_t dev;              /* associated superblock device */
+-      unsigned long ino;      /* associated inode number */
++      u64 ino;                /* associated inode number */
+       char *path;             /* insertion path */
+       struct fsnotify_mark mark; /* fsnotify mark on the inode */
+       struct audit_krule *rule;
+--- a/kernel/audit_watch.c
++++ b/kernel/audit_watch.c
+@@ -37,7 +37,7 @@ struct audit_watch {
+       refcount_t              count;  /* reference count */
+       dev_t                   dev;    /* associated superblock device */
+       char                    *path;  /* insertion path */
+-      unsigned long           ino;    /* associated inode number */
++      u64                     ino;    /* associated inode number */
+       struct audit_parent     *parent; /* associated parent */
+       struct list_head        wlist;  /* entry in parent->watches list */
+       struct list_head        rules;  /* anchor for krule->rlist */
+@@ -125,7 +125,7 @@ char *audit_watch_path(struct audit_watc
+       return watch->path;
+ }
+-int audit_watch_compare(struct audit_watch *watch, unsigned long ino, dev_t dev)
++int audit_watch_compare(struct audit_watch *watch, u64 ino, dev_t dev)
+ {
+       return (watch->ino != AUDIT_INO_UNSET) &&
+               (watch->ino == ino) &&
+@@ -285,7 +285,7 @@ static void audit_update_watch(struct au
+                               list_del(&oentry->rule.list);
+                               audit_panic("error updating watch, removing");
+                       } else {
+-                              int h = audit_hash_ino((u32)ino);
++                              int h = audit_hash_ino(ino);
+                               /*
+                                * nentry->rule.watch == oentry->rule.watch so
+@@ -439,7 +439,7 @@ int audit_add_watch(struct audit_krule *
+       audit_add_to_parent(krule, parent);
+-      h = audit_hash_ino((u32)watch->ino);
++      h = audit_hash_ino(watch->ino);
+       *list = &audit_inode_hash[h];
+ error:
+       path_put(&parent_path);
+@@ -527,7 +527,7 @@ int audit_dupe_exe(struct audit_krule *n
+ int audit_exe_compare(struct task_struct *tsk, struct audit_fsnotify_mark *mark)
+ {
+       struct file *exe_file;
+-      unsigned long ino;
++      u64 ino;
+       dev_t dev;
+       /* only do exe filtering if we are recording @current events/records */
+--- a/kernel/auditsc.c
++++ b/kernel/auditsc.c
+@@ -884,7 +884,7 @@ static int audit_filter_inode_name(struc
+                                  struct audit_names *n,
+                                  struct audit_context *ctx)
+ {
+-      int h = audit_hash_ino((u32)n->ino);
++      int h = audit_hash_ino(n->ino);
+       struct list_head *list = &audit_inode_hash[h];
+       return __audit_filter_op(tsk, ctx, list, n, ctx->major);
+@@ -1549,7 +1549,7 @@ static void audit_log_name(struct audit_
+               audit_log_format(ab, " name=(null)");
+       if (n->ino != AUDIT_INO_UNSET)
+-              audit_log_format(ab, " inode=%lu dev=%02x:%02x mode=%#ho ouid=%u ogid=%u rdev=%02x:%02x",
++              audit_log_format(ab, " inode=%llu dev=%02x:%02x mode=%#ho ouid=%u ogid=%u rdev=%02x:%02x",
+                                n->ino,
+                                MAJOR(n->dev),
+                                MINOR(n->dev),
diff --git a/queue-6.12/block-add-helper-add_disk_final.patch b/queue-6.12/block-add-helper-add_disk_final.patch
new file mode 100644 (file)
index 0000000..ad88116
--- /dev/null
@@ -0,0 +1,100 @@
+From stable+bounces-289610-greg=kroah.com@vger.kernel.org Mon Jul 27 18:06:59 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 11:59:27 -0400
+Subject: block: add helper add_disk_final()
+To: stable@vger.kernel.org
+Cc: Ming Lei <ming.lei@redhat.com>, Hannes Reinecke <hare@suse.de>, Nilay Shroff <nilay@linux.ibm.com>, Christoph Hellwig <hch@lst.de>, Jens Axboe <axboe@kernel.dk>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727155928.1732187-1-sashal@kernel.org>
+
+From: Ming Lei <ming.lei@redhat.com>
+
+[ Upstream commit 5fad1490ef510e3b70ad8b0a5a1e28a26638a95f ]
+
+Add helper add_disk_final() for scanning partitions, announcing disk and
+handling the last thing for adding disk.
+
+No functional change, and prepare for prevent adding disk from happening
+when updating nr_hw_queues.
+
+Signed-off-by: Ming Lei <ming.lei@redhat.com>
+Reviewed-by: Hannes Reinecke <hare@suse.de>
+Reviewed-by: Nilay Shroff <nilay@linux.ibm.com>
+Reviewed-by: Christoph Hellwig <hch@lst.de>
+Link: https://lore.kernel.org/r/20250505141805.2751237-8-ming.lei@redhat.com
+Signed-off-by: Jens Axboe <axboe@kernel.dk>
+Stable-dep-of: 181bb9c9eae4 ("block: remove redundant GD_NEED_PART_SCAN in add_disk_final()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ block/genhd.c |   46 +++++++++++++++++++++++++++-------------------
+ 1 file changed, 27 insertions(+), 19 deletions(-)
+
+--- a/block/genhd.c
++++ b/block/genhd.c
+@@ -382,6 +382,32 @@ int disk_scan_partitions(struct gendisk
+       return ret;
+ }
++static void add_disk_final(struct gendisk *disk)
++{
++      struct device *ddev = disk_to_dev(disk);
++
++      if (!(disk->flags & GENHD_FL_HIDDEN)) {
++              /* Make sure the first partition scan will be proceed */
++              if (get_capacity(disk) && disk_has_partscan(disk))
++                      set_bit(GD_NEED_PART_SCAN, &disk->state);
++
++              bdev_add(disk->part0, ddev->devt);
++              if (get_capacity(disk))
++                      disk_scan_partitions(disk, BLK_OPEN_READ);
++
++              /*
++               * Announce the disk and partitions after all partitions are
++               * created. (for hidden disks uevents remain suppressed forever)
++               */
++              dev_set_uevent_suppress(ddev, 0);
++              disk_uevent(disk, KOBJ_ADD);
++      }
++
++      blk_apply_bdi_limits(disk->bdi, &disk->queue->limits);
++      disk_add_events(disk);
++      set_bit(GD_ADDED, &disk->state);
++}
++
+ /**
+  * device_add_disk - add disk information to kernel list
+  * @parent: parent device for the disk
+@@ -500,21 +526,6 @@ int __must_check device_add_disk(struct
+                                       &disk->bdi->dev->kobj, "bdi");
+               if (ret)
+                       goto out_unregister_bdi;
+-
+-              /* Make sure the first partition scan will be proceed */
+-              if (get_capacity(disk) && disk_has_partscan(disk))
+-                      set_bit(GD_NEED_PART_SCAN, &disk->state);
+-
+-              bdev_add(disk->part0, ddev->devt);
+-              if (get_capacity(disk))
+-                      disk_scan_partitions(disk, BLK_OPEN_READ);
+-
+-              /*
+-               * Announce the disk and partitions after all partitions are
+-               * created. (for hidden disks uevents remain suppressed forever)
+-               */
+-              dev_set_uevent_suppress(ddev, 0);
+-              disk_uevent(disk, KOBJ_ADD);
+       } else {
+               /*
+                * Even if the block_device for a hidden gendisk is not
+@@ -523,10 +534,7 @@ int __must_check device_add_disk(struct
+                */
+               disk->part0->bd_dev = MKDEV(disk->major, disk->first_minor);
+       }
+-
+-      blk_apply_bdi_limits(disk->bdi, &disk->queue->limits);
+-      disk_add_events(disk);
+-      set_bit(GD_ADDED, &disk->state);
++      add_disk_final(disk);
+       return 0;
+ out_unregister_bdi:
diff --git a/queue-6.12/block-remove-redundant-gd_need_part_scan-in-add_disk_final.patch b/queue-6.12/block-remove-redundant-gd_need_part_scan-in-add_disk_final.patch
new file mode 100644 (file)
index 0000000..2d50973
--- /dev/null
@@ -0,0 +1,65 @@
+From stable+bounces-289611-greg=kroah.com@vger.kernel.org Mon Jul 27 18:07:02 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 11:59:28 -0400
+Subject: block: remove redundant GD_NEED_PART_SCAN in add_disk_final()
+To: stable@vger.kernel.org
+Cc: Connor Williamson <connordw@amazon.com>, Christoph Hellwig <hch@lst.de>, Jens Axboe <axboe@kernel.dk>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727155928.1732187-2-sashal@kernel.org>
+
+From: Connor Williamson <connordw@amazon.com>
+
+[ Upstream commit 181bb9c9eae4f69fe510a62a42c2932d0314a800 ]
+
+add_disk_final() sets GD_NEED_PART_SCAN before calling bdev_add(),
+then calls disk_scan_partitions() which sets the flag itself. The
+early set is redundant and introduces a race.
+
+Between bdev_add() and disk_scan_partitions(), concurrent openers
+(multipathd, blkid, LVM) see the flag in blkdev_get_whole() and
+trigger bdev_disk_changed(). When disk_scan_partitions() then runs,
+it calls bdev_disk_changed() again, dropping the partitions the
+concurrent opener already created before re-adding them, which can
+result in transient partition disappearances.
+
+The race is observable by inserting an msleep() between bdev_add()
+and disk_scan_partitions() while running concurrent open() calls
+during device bind. Without artificial delay, it manifests under
+scheduling pressure during boot on systems with aggressive device
+scanners (multipathd, systemd-udevd).
+
+Therefore, do not set GD_NEED_PART_SCAN in add_disk_final(). Other
+GD_NEED_PART_SCAN consumers (blkdev_get_whole(),
+sd_need_revalidate()) should not be affected as the flag
+is set internally by disk_scan_partitions().
+
+The retry-on-next-open intention from commit e5cfefa97bcc
+("block: fix scan partition for exclusively open device again")
+should also not be affected as the early return paths in
+disk_scan_partitions() should be unreachable at device registration
+time (bd_holder is NULL and open_partitions is zero).
+
+Fixes: e5cfefa97bcc ("block: fix scan partition for exclusively open device again")
+Cc: stable@vger.kernel.org
+Signed-off-by: Connor Williamson <connordw@amazon.com>
+Reviewed-by: Christoph Hellwig <hch@lst.de>
+Link: https://patch.msgid.link/20260615130715.53693-1-connordw@amazon.com
+Signed-off-by: Jens Axboe <axboe@kernel.dk>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ block/genhd.c |    4 ----
+ 1 file changed, 4 deletions(-)
+
+--- a/block/genhd.c
++++ b/block/genhd.c
+@@ -387,10 +387,6 @@ static void add_disk_final(struct gendis
+       struct device *ddev = disk_to_dev(disk);
+       if (!(disk->flags & GENHD_FL_HIDDEN)) {
+-              /* Make sure the first partition scan will be proceed */
+-              if (get_capacity(disk) && disk_has_partscan(disk))
+-                      set_bit(GD_NEED_PART_SCAN, &disk->state);
+-
+               bdev_add(disk->part0, ddev->devt);
+               if (get_capacity(disk))
+                       disk_scan_partitions(disk, BLK_OPEN_READ);
diff --git a/queue-6.12/bluetooth-add-pa_link-to-distinguish-big-sync-and-pa-sync-connections.patch b/queue-6.12/bluetooth-add-pa_link-to-distinguish-big-sync-and-pa-sync-connections.patch
new file mode 100644 (file)
index 0000000..b4953c9
--- /dev/null
@@ -0,0 +1,386 @@
+From stable+bounces-290941-greg=kroah.com@vger.kernel.org Thu Jul 30 03:00:26 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 20:58:16 -0400
+Subject: Bluetooth: Add PA_LINK to distinguish BIG sync and PA sync connections
+To: stable@vger.kernel.org
+Cc: Yang Li <yang.li@amlogic.com>, Luiz Augusto von Dentz <luiz.von.dentz@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260730005817.1696365-1-sashal@kernel.org>
+
+From: Yang Li <yang.li@amlogic.com>
+
+[ Upstream commit a7bcffc673de219af2698fbb90627016233de67b ]
+
+Currently, BIS_LINK is used for both BIG sync and PA sync connections,
+which makes it impossible to distinguish them when searching for a PA
+sync connection.
+
+Adding PA_LINK will make the distinction clearer and simplify future
+extensions for PA-related features.
+
+Signed-off-by: Yang Li <yang.li@amlogic.com>
+Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
+Stable-dep-of: 9d4b01a0bf8d ("Bluetooth: hci_core: Fix not accounting for BIS/CIS/PA links separately")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/net/bluetooth/hci.h      |    1 +
+ include/net/bluetooth/hci_core.h |   10 +++++++---
+ net/bluetooth/hci_conn.c         |   13 ++++++++-----
+ net/bluetooth/hci_core.c         |   27 +++++++++++++++------------
+ net/bluetooth/hci_event.c        |    7 ++++---
+ net/bluetooth/hci_sync.c         |   10 +++++-----
+ net/bluetooth/iso.c              |    6 ++++--
+ net/bluetooth/mgmt.c             |    1 +
+ 8 files changed, 45 insertions(+), 30 deletions(-)
+
+--- a/include/net/bluetooth/hci.h
++++ b/include/net/bluetooth/hci.h
+@@ -560,6 +560,7 @@ enum {
+ #define LE_LINK               0x80
+ #define CIS_LINK      0x82
+ #define BIS_LINK      0x83
++#define PA_LINK               0x84
+ #define INVALID_LINK  0xff
+ /* LMP features */
+--- a/include/net/bluetooth/hci_core.h
++++ b/include/net/bluetooth/hci_core.h
+@@ -1001,6 +1001,7 @@ static inline void hci_conn_hash_add(str
+               break;
+       case CIS_LINK:
+       case BIS_LINK:
++      case PA_LINK:
+               h->iso_num++;
+               break;
+       }
+@@ -1028,6 +1029,7 @@ static inline void hci_conn_hash_del(str
+               break;
+       case CIS_LINK:
+       case BIS_LINK:
++      case PA_LINK:
+               h->iso_num--;
+               break;
+       }
+@@ -1046,6 +1048,7 @@ static inline unsigned int hci_conn_num(
+               return h->sco_num;
+       case CIS_LINK:
+       case BIS_LINK:
++      case PA_LINK:
+               return h->iso_num;
+       default:
+               return 0;
+@@ -1128,7 +1131,7 @@ hci_conn_hash_lookup_create_pa_sync(stru
+       rcu_read_lock();
+       list_for_each_entry_rcu(c, &h->list, list) {
+-              if (c->type != BIS_LINK)
++              if (c->type != PA_LINK)
+                       continue;
+               if (!test_bit(HCI_CONN_CREATE_PA_SYNC, &c->flags))
+@@ -1344,7 +1347,7 @@ hci_conn_hash_lookup_big_sync_pend(struc
+       rcu_read_lock();
+       list_for_each_entry_rcu(c, &h->list, list) {
+-              if (c->type != BIS_LINK)
++              if (c->type != PA_LINK)
+                       continue;
+               if (handle == c->iso_qos.bcast.big && num_bis == c->num_bis) {
+@@ -1413,7 +1416,7 @@ hci_conn_hash_lookup_pa_sync_handle(stru
+       rcu_read_lock();
+       list_for_each_entry_rcu(c, &h->list, list) {
+-              if (c->type != BIS_LINK)
++              if (c->type != PA_LINK)
+                       continue;
+               /* Ignore the listen hcon, we are looking
+@@ -2025,6 +2028,7 @@ static inline int hci_proto_connect_ind(
+       case CIS_LINK:
+       case BIS_LINK:
++      case PA_LINK:
+               return iso_connect_ind(hdev, bdaddr, flags);
+       default:
+--- a/net/bluetooth/hci_conn.c
++++ b/net/bluetooth/hci_conn.c
+@@ -785,7 +785,7 @@ static int hci_le_big_terminate(struct h
+       d->sync_handle = conn->sync_handle;
+       if (test_and_clear_bit(HCI_CONN_PA_SYNC, &conn->flags)) {
+-              hci_conn_hash_list_flag(hdev, find_bis, BIS_LINK,
++              hci_conn_hash_list_flag(hdev, find_bis, PA_LINK,
+                                       HCI_CONN_PA_SYNC, d);
+               if (!d->count)
+@@ -914,6 +914,7 @@ static struct hci_conn *__hci_conn_add(s
+               break;
+       case CIS_LINK:
+       case BIS_LINK:
++      case PA_LINK:
+               if (hdev->iso_mtu)
+                       /* Dedicated ISO Buffer exists */
+                       break;
+@@ -980,6 +981,7 @@ static struct hci_conn *__hci_conn_add(s
+               break;
+       case CIS_LINK:
+       case BIS_LINK:
++      case PA_LINK:
+               /* conn->src should reflect the local identity address */
+               hci_copy_identity_address(hdev, &conn->src, &conn->src_type);
+@@ -1033,7 +1035,6 @@ static struct hci_conn *__hci_conn_add(s
+       }
+       hci_conn_init_sysfs(conn);
+-
+       return conn;
+ }
+@@ -1077,6 +1078,7 @@ static void hci_conn_cleanup_child(struc
+               break;
+       case CIS_LINK:
+       case BIS_LINK:
++      case PA_LINK:
+               if ((conn->state != BT_CONNECTED &&
+                   !test_bit(HCI_CONN_CREATE_CIS, &conn->flags)) ||
+                   test_bit(HCI_CONN_BIG_CREATED, &conn->flags))
+@@ -1152,7 +1154,8 @@ void hci_conn_del(struct hci_conn *conn)
+       } else {
+               /* Unacked ISO frames */
+               if (conn->type == CIS_LINK ||
+-                  conn->type == BIS_LINK) {
++                  conn->type == BIS_LINK ||
++                  conn->type == PA_LINK) {
+                       if (hdev->iso_pkts)
+                               hdev->iso_cnt += conn->sent;
+                       else if (hdev->le_pkts)
+@@ -2089,7 +2092,7 @@ struct hci_conn *hci_pa_create_sync(stru
+       bt_dev_dbg(hdev, "dst %pMR type %d sid %d", dst, dst_type, sid);
+-      conn = hci_conn_add_unset(hdev, BIS_LINK, dst, HCI_ROLE_SLAVE);
++      conn = hci_conn_add_unset(hdev, PA_LINK, dst, HCI_ROLE_SLAVE);
+       if (IS_ERR(conn))
+               return conn;
+@@ -2266,7 +2269,7 @@ struct hci_conn *hci_connect_bis(struct
+        * the start periodic advertising and create BIG commands have
+        * been queued
+        */
+-      hci_conn_hash_list_state(hdev, bis_mark_per_adv, BIS_LINK,
++      hci_conn_hash_list_state(hdev, bis_mark_per_adv, PA_LINK,
+                                BT_BOUND, &data);
+       /* Queue start periodic advertising and create BIG */
+--- a/net/bluetooth/hci_core.c
++++ b/net/bluetooth/hci_core.c
+@@ -2960,12 +2960,14 @@ int hci_recv_frame(struct hci_dev *hdev,
+       case HCI_ACLDATA_PKT:
+               /* Detect if ISO packet has been sent as ACL */
+               if (hci_conn_num(hdev, CIS_LINK) ||
+-                  hci_conn_num(hdev, BIS_LINK)) {
++                  hci_conn_num(hdev, BIS_LINK) ||
++                      hci_conn_num(hdev, PA_LINK)) {
+                       __u16 handle = __le16_to_cpu(hci_acl_hdr(skb)->handle);
+                       __u8 type;
+                       type = hci_conn_lookup_type(hdev, hci_handle(handle));
+-                      if (type == CIS_LINK || type == BIS_LINK)
++                      if (type == CIS_LINK || type == BIS_LINK ||
++                          type == PA_LINK)
+                               hci_skb_pkt_type(skb) = HCI_ISODATA_PKT;
+               }
+               break;
+@@ -3402,6 +3404,7 @@ static inline void hci_quote_sent(struct
+               break;
+       case CIS_LINK:
+       case BIS_LINK:
++      case PA_LINK:
+               cnt = hdev->iso_mtu ? hdev->iso_cnt :
+                       hdev->le_mtu ? hdev->le_cnt : hdev->acl_cnt;
+               break;
+@@ -3415,7 +3418,7 @@ static inline void hci_quote_sent(struct
+ }
+ static struct hci_conn *hci_low_sent(struct hci_dev *hdev, __u8 type,
+-                                   __u8 type2, int *quote)
++                                   int *quote)
+ {
+       struct hci_conn_hash *h = &hdev->conn_hash;
+       struct hci_conn *conn = NULL, *c;
+@@ -3427,7 +3430,7 @@ static struct hci_conn *hci_low_sent(str
+       rcu_read_lock();
+       list_for_each_entry_rcu(c, &h->list, list) {
+-              if ((c->type != type && c->type != type2) ||
++              if (c->type != type ||
+                   skb_queue_empty(&c->data_q))
+                       continue;
+@@ -3631,7 +3634,7 @@ static void hci_sched_sco(struct hci_dev
+       else
+               cnt = &hdev->sco_cnt;
+-      while (*cnt && (conn = hci_low_sent(hdev, type, type, &quote))) {
++      while (*cnt && (conn = hci_low_sent(hdev, type, &quote))) {
+               while (quote-- && (skb = skb_dequeue(&conn->data_q))) {
+                       BT_DBG("skb %p len %d", skb, skb->len);
+                       hci_send_frame(hdev, skb);
+@@ -3750,8 +3753,8 @@ static void hci_sched_le(struct hci_dev
+               hci_prio_recalculate(hdev, LE_LINK);
+ }
+-/* Schedule CIS */
+-static void hci_sched_iso(struct hci_dev *hdev)
++/* Schedule iso */
++static void hci_sched_iso(struct hci_dev *hdev, __u8 type)
+ {
+       struct hci_conn *conn;
+       struct sk_buff *skb;
+@@ -3759,14 +3762,12 @@ static void hci_sched_iso(struct hci_dev
+       BT_DBG("%s", hdev->name);
+-      if (!hci_conn_num(hdev, CIS_LINK) &&
+-          !hci_conn_num(hdev, BIS_LINK))
++      if (!hci_conn_num(hdev, type))
+               return;
+       cnt = hdev->iso_pkts ? &hdev->iso_cnt :
+               hdev->le_pkts ? &hdev->le_cnt : &hdev->acl_cnt;
+-      while (*cnt && (conn = hci_low_sent(hdev, CIS_LINK, BIS_LINK,
+-                                          &quote))) {
++      while (*cnt && (conn = hci_low_sent(hdev, type, &quote))) {
+               while (quote-- && (skb = skb_dequeue(&conn->data_q))) {
+                       BT_DBG("skb %p len %d", skb, skb->len);
+                       hci_send_frame(hdev, skb);
+@@ -3791,7 +3792,9 @@ static void hci_tx_work(struct work_stru
+               /* Schedule queues and send stuff to HCI driver */
+               hci_sched_sco(hdev, SCO_LINK);
+               hci_sched_sco(hdev, ESCO_LINK);
+-              hci_sched_iso(hdev);
++              hci_sched_iso(hdev, CIS_LINK);
++              hci_sched_iso(hdev, BIS_LINK);
++              hci_sched_iso(hdev, PA_LINK);
+               hci_sched_acl(hdev);
+               hci_sched_le(hdev);
+       }
+--- a/net/bluetooth/hci_event.c
++++ b/net/bluetooth/hci_event.c
+@@ -4455,6 +4455,7 @@ static void hci_num_comp_pkts_evt(struct
+               case CIS_LINK:
+               case BIS_LINK:
++              case PA_LINK:
+                       if (hdev->iso_pkts) {
+                               hdev->iso_cnt += count;
+                               if (hdev->iso_cnt > hdev->iso_pkts)
+@@ -6421,7 +6422,7 @@ static void hci_le_pa_sync_estabilished_
+       conn->sync_handle = le16_to_cpu(ev->handle);
+       conn->sid = HCI_SID_INVALID;
+-      mask |= hci_proto_connect_ind(hdev, &ev->bdaddr, BIS_LINK,
++      mask |= hci_proto_connect_ind(hdev, &ev->bdaddr, PA_LINK,
+                                     &flags);
+       if (!(mask & HCI_LM_ACCEPT)) {
+               hci_le_pa_term_sync(hdev, ev->handle);
+@@ -6432,7 +6433,7 @@ static void hci_le_pa_sync_estabilished_
+               goto unlock;
+       /* Add connection to indicate PA sync event */
+-      pa_sync = hci_conn_add_unset(hdev, BIS_LINK, BDADDR_ANY,
++      pa_sync = hci_conn_add_unset(hdev, PA_LINK, BDADDR_ANY,
+                                    HCI_ROLE_SLAVE);
+       if (IS_ERR(pa_sync))
+@@ -6463,7 +6464,7 @@ static void hci_le_per_adv_report_evt(st
+       hci_dev_lock(hdev);
+-      mask |= hci_proto_connect_ind(hdev, BDADDR_ANY, BIS_LINK, &flags);
++      mask |= hci_proto_connect_ind(hdev, BDADDR_ANY, PA_LINK, &flags);
+       if (!(mask & HCI_LM_ACCEPT))
+               goto unlock;
+--- a/net/bluetooth/hci_sync.c
++++ b/net/bluetooth/hci_sync.c
+@@ -2988,7 +2988,7 @@ static int hci_le_set_ext_scan_param_syn
+               if (sent) {
+                       struct hci_conn *conn;
+-                      conn = hci_conn_hash_lookup_ba(hdev, BIS_LINK,
++                      conn = hci_conn_hash_lookup_ba(hdev, PA_LINK,
+                                                      &sent->bdaddr);
+                       if (conn) {
+                               struct bt_iso_qos *qos = &conn->iso_qos;
+@@ -5552,7 +5552,7 @@ static int hci_disconnect_sync(struct hc
+ {
+       struct hci_cp_disconnect cp;
+-      if (conn->type == BIS_LINK) {
++      if (conn->type == BIS_LINK || conn->type == PA_LINK) {
+               /* This is a BIS connection, hci_conn_del will
+                * do the necessary cleanup.
+                */
+@@ -5621,7 +5621,7 @@ static int hci_connect_cancel_sync(struc
+               return HCI_ERROR_LOCAL_HOST_TERM;
+       }
+-      if (conn->type == BIS_LINK) {
++      if (conn->type == BIS_LINK || conn->type == PA_LINK) {
+               /* There is no way to cancel a BIS without terminating the BIG
+                * which is done later on connection cleanup.
+                */
+@@ -5686,7 +5686,7 @@ static int hci_reject_conn_sync(struct h
+       if (conn->type == CIS_LINK)
+               return hci_le_reject_cis_sync(hdev, conn, reason);
+-      if (conn->type == BIS_LINK)
++      if (conn->type == BIS_LINK || conn->type == PA_LINK)
+               return -EINVAL;
+       if (conn->type == SCO_LINK || conn->type == ESCO_LINK)
+@@ -7157,7 +7157,7 @@ static void create_pa_complete(struct hc
+               goto unlock;
+       /* Add connection to indicate PA sync error */
+-      pa_sync = hci_conn_add_unset(hdev, BIS_LINK, BDADDR_ANY,
++      pa_sync = hci_conn_add_unset(hdev, PA_LINK, BDADDR_ANY,
+                                    HCI_ROLE_SLAVE);
+       if (IS_ERR(pa_sync))
+--- a/net/bluetooth/iso.c
++++ b/net/bluetooth/iso.c
+@@ -2211,7 +2211,8 @@ done:
+ static void iso_connect_cfm(struct hci_conn *hcon, __u8 status)
+ {
+-      if (hcon->type != CIS_LINK && hcon->type != BIS_LINK) {
++      if (hcon->type != CIS_LINK && hcon->type != BIS_LINK &&
++          hcon->type != PA_LINK) {
+               if (hcon->type != LE_LINK)
+                       return;
+@@ -2252,7 +2253,8 @@ static void iso_connect_cfm(struct hci_c
+ static void iso_disconn_cfm(struct hci_conn *hcon, __u8 reason)
+ {
+-      if (hcon->type != CIS_LINK && hcon->type != BIS_LINK)
++      if (hcon->type != CIS_LINK && hcon->type !=  BIS_LINK &&
++          hcon->type != PA_LINK)
+               return;
+       BT_DBG("hcon %p reason %d", hcon, reason);
+--- a/net/bluetooth/mgmt.c
++++ b/net/bluetooth/mgmt.c
+@@ -3258,6 +3258,7 @@ static u8 link_to_bdaddr(u8 link_type, u
+       switch (link_type) {
+       case CIS_LINK:
+       case BIS_LINK:
++      case PA_LINK:
+       case LE_LINK:
+               switch (addr_type) {
+               case ADDR_LE_DEV_PUBLIC:
diff --git a/queue-6.12/bluetooth-hci_core-fix-not-accounting-for-bis-cis-pa-links-separately.patch b/queue-6.12/bluetooth-hci_core-fix-not-accounting-for-bis-cis-pa-links-separately.patch
new file mode 100644 (file)
index 0000000..3333a9c
--- /dev/null
@@ -0,0 +1,100 @@
+From stable+bounces-290942-greg=kroah.com@vger.kernel.org Thu Jul 30 03:00:41 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 20:58:17 -0400
+Subject: Bluetooth: hci_core: Fix not accounting for BIS/CIS/PA links separately
+To: stable@vger.kernel.org
+Cc: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260730005817.1696365-2-sashal@kernel.org>
+
+From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
+
+[ Upstream commit 9d4b01a0bf8d2163ae129c9c537cb0753ad5a2aa ]
+
+This fixes the likes of hci_conn_num(CIS_LINK) returning the total of
+ISO connection which includes BIS_LINK as well, so this splits the
+iso_num into each link type and introduces hci_iso_num that can be used
+in places where the total number of ISO connection still needs to be
+used.
+
+Fixes: 23205562ffc8 ("Bluetooth: separate CIS_LINK and BIS_LINK link types")
+Fixes: a7bcffc673de ("Bluetooth: Add PA_LINK to distinguish BIG sync and PA sync connections")
+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/hci_core.h |   30 +++++++++++++++++++++++++-----
+ 1 file changed, 25 insertions(+), 5 deletions(-)
+
+--- a/include/net/bluetooth/hci_core.h
++++ b/include/net/bluetooth/hci_core.h
+@@ -128,7 +128,9 @@ struct hci_conn_hash {
+       struct list_head list;
+       unsigned int     acl_num;
+       unsigned int     sco_num;
+-      unsigned int     iso_num;
++      unsigned int     cis_num;
++      unsigned int     bis_num;
++      unsigned int     pa_num;
+       unsigned int     le_num;
+       unsigned int     le_num_peripheral;
+ };
+@@ -1000,9 +1002,13 @@ static inline void hci_conn_hash_add(str
+               h->sco_num++;
+               break;
+       case CIS_LINK:
++              h->cis_num++;
++              break;
+       case BIS_LINK:
++              h->bis_num++;
++              break;
+       case PA_LINK:
+-              h->iso_num++;
++              h->pa_num++;
+               break;
+       }
+ }
+@@ -1028,9 +1034,13 @@ static inline void hci_conn_hash_del(str
+               h->sco_num--;
+               break;
+       case CIS_LINK:
++              h->cis_num--;
++              break;
+       case BIS_LINK:
++              h->bis_num--;
++              break;
+       case PA_LINK:
+-              h->iso_num--;
++              h->pa_num--;
+               break;
+       }
+ }
+@@ -1047,9 +1057,11 @@ static inline unsigned int hci_conn_num(
+       case ESCO_LINK:
+               return h->sco_num;
+       case CIS_LINK:
++              return h->cis_num;
+       case BIS_LINK:
++              return h->bis_num;
+       case PA_LINK:
+-              return h->iso_num;
++              return h->pa_num;
+       default:
+               return 0;
+       }
+@@ -1059,7 +1071,15 @@ static inline unsigned int hci_conn_coun
+ {
+       struct hci_conn_hash *c = &hdev->conn_hash;
+-      return c->acl_num + c->sco_num + c->le_num + c->iso_num;
++      return c->acl_num + c->sco_num + c->le_num + c->cis_num + c->bis_num +
++              c->pa_num;
++}
++
++static inline unsigned int hci_iso_count(struct hci_dev *hdev)
++{
++      struct hci_conn_hash *c = &hdev->conn_hash;
++
++      return c->cis_num + c->bis_num;
+ }
+ static inline bool hci_conn_valid(struct hci_dev *hdev, struct hci_conn *conn)
diff --git a/queue-6.12/bootconfig-fix-null-pointer-arithmetic-in-xbc_snprint_cmdline.patch b/queue-6.12/bootconfig-fix-null-pointer-arithmetic-in-xbc_snprint_cmdline.patch
new file mode 100644 (file)
index 0000000..cdfbe67
--- /dev/null
@@ -0,0 +1,97 @@
+From stable+bounces-290094-greg=kroah.com@vger.kernel.org Tue Jul 28 19:53:38 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 13:07:08 -0400
+Subject: bootconfig: fix NULL-pointer arithmetic in xbc_snprint_cmdline()
+To: stable@vger.kernel.org
+Cc: Breno Leitao <leitao@debian.org>, "Masami Hiramatsu (Google)" <mhiramat@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260728170708.183603-2-sashal@kernel.org>
+
+From: Breno Leitao <leitao@debian.org>
+
+[ Upstream commit dec4d8118c179b3d12bca7e609054c6011c4f2ce ]
+
+xbc_snprint_cmdline() is meant to be called twice: first with
+buf=NULL, size=0 to probe the rendered length, then with a real
+buffer to fill it (the standard snprintf() two-pass pattern). The
+probe call makes the function compute "buf + size" (NULL + 0) and,
+on every iteration, advance "buf += ret" from that NULL base and
+pass the result back into snprintf().
+
+Pointer arithmetic on a NULL pointer is undefined behavior. It is
+harmless in the in-kernel callers today, but the follow-up patches
+run this same code in the userspace tools/bootconfig parser at kernel
+build time, where host UBSan / FORTIFY_SOURCE abort the build.
+
+Track a running written length (size_t) instead of mutating @buf, and
+only form "buf + len" when @buf is non-NULL. snprintf(NULL, 0, ...)
+is itself well defined and returns the would-be length, so the
+two-pass "probe then fill" usage returns identical byte counts.
+
+Link: https://lore.kernel.org/all/20260626-bootconfig_using_tools-v7-1-24ab72139c29@debian.org/
+
+Fixes: 51887d03aca1 ("bootconfig: init: Allow admin to use bootconfig for kernel command line")
+Cc: stable@vger.kernel.org
+Signed-off-by: Breno Leitao <leitao@debian.org>
+Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ lib/bootconfig.c |   23 ++++++++++++++++-------
+ 1 file changed, 16 insertions(+), 7 deletions(-)
+
+--- a/lib/bootconfig.c
++++ b/lib/bootconfig.c
+@@ -424,10 +424,18 @@ static char xbc_namebuf[XBC_KEYLEN_MAX]
+ int __init xbc_snprint_cmdline(char *buf, size_t size, struct xbc_node *root)
+ {
+       struct xbc_node *knode, *vnode;
+-      char *end = buf + size;
+       const char *val, *q;
++      size_t len = 0;
+       int ret;
++      /*
++       * Track the running written length rather than advancing @buf, so we
++       * never form "buf + size" or "buf += ret" while @buf is NULL (the
++       * size-probe call passes buf=NULL, size=0). NULL pointer arithmetic
++       * is undefined behavior and trips host UBSan / FORTIFY_SOURCE when
++       * this renderer runs at kernel build time. snprintf(NULL, 0, ...)
++       * itself is well defined and returns the would-be length.
++       */
+       xbc_node_for_each_key_value(root, knode, val) {
+               ret = xbc_node_compose_key_after(root, knode,
+                                       xbc_namebuf, XBC_KEYLEN_MAX);
+@@ -436,10 +444,11 @@ int __init xbc_snprint_cmdline(char *buf
+               vnode = xbc_node_get_child(knode);
+               if (!vnode) {
+-                      ret = snprintf(buf, rest(buf, end), "%s ", xbc_namebuf);
++                      ret = snprintf(buf ? buf + len : NULL, rest(len, size),
++                                     "%s ", xbc_namebuf);
+                       if (ret < 0)
+                               return ret;
+-                      buf += ret;
++                      len += ret;
+                       continue;
+               }
+               xbc_array_for_each_value(vnode, val) {
+@@ -449,15 +458,15 @@ int __init xbc_snprint_cmdline(char *buf
+                        * whitespace.
+                        */
+                       q = strpbrk(val, " \t\r\n") ? "\"" : "";
+-                      ret = snprintf(buf, rest(buf, end), "%s=%s%s%s ",
+-                                     xbc_namebuf, q, val, q);
++                      ret = snprintf(buf ? buf + len : NULL, rest(len, size),
++                                     "%s=%s%s%s ", xbc_namebuf, q, val, q);
+                       if (ret < 0)
+                               return ret;
+-                      buf += ret;
++                      len += ret;
+               }
+       }
+-      return buf - (end - size);
++      return len;
+ }
+ #undef rest
diff --git a/queue-6.12/bootconfig-move-xbc_snprint_cmdline-to-lib-bootconfig.c.patch b/queue-6.12/bootconfig-move-xbc_snprint_cmdline-to-lib-bootconfig.c.patch
new file mode 100644 (file)
index 0000000..03b6e49
--- /dev/null
@@ -0,0 +1,165 @@
+From stable+bounces-290093-greg=kroah.com@vger.kernel.org Tue Jul 28 19:53:27 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 13:07:07 -0400
+Subject: bootconfig: move xbc_snprint_cmdline() to lib/bootconfig.c
+To: stable@vger.kernel.org
+Cc: Breno Leitao <leitao@debian.org>, "Masami Hiramatsu (Google)" <mhiramat@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260728170708.183603-1-sashal@kernel.org>
+
+From: Breno Leitao <leitao@debian.org>
+
+[ Upstream commit 5a643e4623238e14b03d75ca0d4eda0645720cee ]
+
+Move xbc_snprint_cmdline() from init/main.c to lib/bootconfig.c so the
+function (and its xbc_namebuf scratch buffer) becomes part of the shared
+parser library. tools/bootconfig already compiles lib/bootconfig.c
+directly, which lets a follow-up patch reuse the same renderer in the
+userspace tool to convert a bootconfig file into a flat cmdline string
+at build time.
+
+No functional change.
+
+Link: https://lore.kernel.org/all/20260508-bootconfig_using_tools-v1-1-1132219aa773@debian.org/
+
+Signed-off-by: Breno Leitao <leitao@debian.org>
+Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
+Stable-dep-of: dec4d8118c17 ("bootconfig: fix NULL-pointer arithmetic in xbc_snprint_cmdline()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/bootconfig.h |    3 ++
+ init/main.c                |   45 ------------------------------------
+ lib/bootconfig.c           |   56 +++++++++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 59 insertions(+), 45 deletions(-)
+
+--- a/include/linux/bootconfig.h
++++ b/include/linux/bootconfig.h
+@@ -265,6 +265,9 @@ static inline struct xbc_node * __init x
+ int __init xbc_node_compose_key_after(struct xbc_node *root,
+                       struct xbc_node *node, char *buf, size_t size);
++/* Render key/value pairs under @root as a flat cmdline string */
++int __init xbc_snprint_cmdline(char *buf, size_t size, struct xbc_node *root);
++
+ /**
+  * xbc_node_compose_key() - Compose full key string of the XBC node
+  * @node: An XBC node.
+--- a/init/main.c
++++ b/init/main.c
+@@ -318,51 +318,6 @@ static void * __init get_boot_config_fro
+ #ifdef CONFIG_BOOT_CONFIG
+-static char xbc_namebuf[XBC_KEYLEN_MAX] __initdata;
+-
+-#define rest(dst, end) ((end) > (dst) ? (end) - (dst) : 0)
+-
+-static int __init xbc_snprint_cmdline(char *buf, size_t size,
+-                                    struct xbc_node *root)
+-{
+-      struct xbc_node *knode, *vnode;
+-      char *end = buf + size;
+-      const char *val, *q;
+-      int ret;
+-
+-      xbc_node_for_each_key_value(root, knode, val) {
+-              ret = xbc_node_compose_key_after(root, knode,
+-                                      xbc_namebuf, XBC_KEYLEN_MAX);
+-              if (ret < 0)
+-                      return ret;
+-
+-              vnode = xbc_node_get_child(knode);
+-              if (!vnode) {
+-                      ret = snprintf(buf, rest(buf, end), "%s ", xbc_namebuf);
+-                      if (ret < 0)
+-                              return ret;
+-                      buf += ret;
+-                      continue;
+-              }
+-              xbc_array_for_each_value(vnode, val) {
+-                      /*
+-                       * For prettier and more readable /proc/cmdline, only
+-                       * quote the value when necessary, i.e. when it contains
+-                       * whitespace.
+-                       */
+-                      q = strpbrk(val, " \t\r\n") ? "\"" : "";
+-                      ret = snprintf(buf, rest(buf, end), "%s=%s%s%s ",
+-                                     xbc_namebuf, q, val, q);
+-                      if (ret < 0)
+-                              return ret;
+-                      buf += ret;
+-              }
+-      }
+-
+-      return buf - (end - size);
+-}
+-#undef rest
+-
+ /* Make an extra command line under given key word */
+ static char * __init xbc_make_cmdline(const char *key)
+ {
+--- a/lib/bootconfig.c
++++ b/lib/bootconfig.c
+@@ -405,6 +405,62 @@ const char * __init xbc_node_find_next_k
+               return "";      /* No value key */
+ }
++static char xbc_namebuf[XBC_KEYLEN_MAX] __initdata;
++
++#define rest(dst, end) ((end) > (dst) ? (end) - (dst) : 0)
++
++/**
++ * xbc_snprint_cmdline() - Render bootconfig keys under @root as a cmdline string
++ * @buf: Destination buffer (may be NULL when @size is 0 to query the length)
++ * @size: Size of @buf in bytes
++ * @root: Subtree root whose key=value pairs should be rendered
++ *
++ * Walk all key/value pairs under @root and emit them as a space-separated
++ * cmdline string into @buf. Values containing whitespace are quoted with
++ * double quotes. Returns the number of bytes that would be written if @buf
++ * were large enough (matching snprintf semantics), or a negative errno on
++ * failure.
++ */
++int __init xbc_snprint_cmdline(char *buf, size_t size, struct xbc_node *root)
++{
++      struct xbc_node *knode, *vnode;
++      char *end = buf + size;
++      const char *val, *q;
++      int ret;
++
++      xbc_node_for_each_key_value(root, knode, val) {
++              ret = xbc_node_compose_key_after(root, knode,
++                                      xbc_namebuf, XBC_KEYLEN_MAX);
++              if (ret < 0)
++                      return ret;
++
++              vnode = xbc_node_get_child(knode);
++              if (!vnode) {
++                      ret = snprintf(buf, rest(buf, end), "%s ", xbc_namebuf);
++                      if (ret < 0)
++                              return ret;
++                      buf += ret;
++                      continue;
++              }
++              xbc_array_for_each_value(vnode, val) {
++                      /*
++                       * For prettier and more readable /proc/cmdline, only
++                       * quote the value when necessary, i.e. when it contains
++                       * whitespace.
++                       */
++                      q = strpbrk(val, " \t\r\n") ? "\"" : "";
++                      ret = snprintf(buf, rest(buf, end), "%s=%s%s%s ",
++                                     xbc_namebuf, q, val, q);
++                      if (ret < 0)
++                              return ret;
++                      buf += ret;
++              }
++      }
++
++      return buf - (end - size);
++}
++#undef rest
++
+ /* XBC parse and tree build */
+ static int __init xbc_init_node(struct xbc_node *node, char *data, uint32_t flag)
diff --git a/queue-6.12/bpf-refactor-acquire-release-_reference_state.patch b/queue-6.12/bpf-refactor-acquire-release-_reference_state.patch
new file mode 100644 (file)
index 0000000..ea28419
--- /dev/null
@@ -0,0 +1,116 @@
+From stable+bounces-289595-greg=kroah.com@vger.kernel.org Mon Jul 27 17:52:10 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 11:41:01 -0400
+Subject: bpf: Refactor {acquire,release}_reference_state
+To: stable@vger.kernel.org
+Cc: Kumar Kartikeya Dwivedi <memxor@gmail.com>, Eduard Zingerman <eddyz87@gmail.com>, Alexei Starovoitov <ast@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727154103.1515347-1-sashal@kernel.org>
+
+From: Kumar Kartikeya Dwivedi <memxor@gmail.com>
+
+[ Upstream commit 769b0f1c821455ab29baf42491e1ea1d726451fa ]
+
+In preparation for introducing support for more reference types which
+have to add and remove reference state, refactor the
+acquire_reference_state and release_reference_state functions to share
+common logic.
+
+The acquire_reference_state function simply handles growing the acquired
+refs and returning the pointer to the new uninitialized element, which
+can be filled in by the caller.
+
+The release_reference_state function simply erases a reference state
+entry in the acquired_refs array and shrinks it. The callers are
+responsible for finding the suitable element by matching on various
+fields of the reference state and requesting deletion through this
+function. It is not supposed to be called directly.
+
+Existing callers of release_reference_state were using it to find and
+remove state for a given ref_obj_id without scrubbing the associated
+registers in the verifier state. Introduce release_reference_nomark to
+provide this functionality and convert callers. We now use this new
+release_reference_nomark function within release_reference as well.
+It needs to operate on a verifier state instead of taking verifier env
+as mark_ptr_or_null_regs requires operating on verifier state of the
+two branches of a NULL condition check, therefore env->cur_state cannot
+be used directly.
+
+Acked-by: Eduard Zingerman <eddyz87@gmail.com>
+Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
+Link: https://lore.kernel.org/r/20241204030400.208005-3-memxor@gmail.com
+Signed-off-by: Alexei Starovoitov <ast@kernel.org>
+Stable-dep-of: 5e0b273e0a62 ("bpf: Reset register bounds before narrowing retval range in check_mem_access()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ kernel/bpf/verifier.c |   28 +++++++++++++++-------------
+ 1 file changed, 15 insertions(+), 13 deletions(-)
+
+--- a/kernel/bpf/verifier.c
++++ b/kernel/bpf/verifier.c
+@@ -7207,11 +7207,12 @@ static int check_mem_access(struct bpf_v
+               if (!err && value_regno >= 0 && (t == BPF_READ || rdonly_mem))
+                       mark_reg_unknown(env, regs, value_regno);
+       } else if (reg->type == PTR_TO_CTX) {
+-              bool is_retval = false;
++              struct bpf_insn_access_aux info = {
++                      .reg_type = SCALAR_VALUE,
++                      .is_ldsx = is_ldsx,
++                      .log = &env->log,
++              };
+               struct bpf_retval_range range;
+-              enum bpf_reg_type reg_type = SCALAR_VALUE;
+-              struct btf *btf = NULL;
+-              u32 btf_id = 0;
+               if (t == BPF_WRITE && value_regno >= 0 &&
+                   is_pointer_value(env, value_regno)) {
+@@ -7223,8 +7224,9 @@ static int check_mem_access(struct bpf_v
+               if (err < 0)
+                       return err;
+-              err = check_ctx_access(env, insn_idx, off, size, t, &reg_type, &btf,
+-                                     &btf_id, &is_retval, is_ldsx);
++              err = check_ctx_access(env, insn_idx, off, size, t, &info.reg_type,
++                                     &info.btf, &info.btf_id, &info.is_retval,
++                                     info.is_ldsx);
+               if (err)
+                       verbose_linfo(env, insn_idx, "; ");
+               if (!err && t == BPF_READ && value_regno >= 0) {
+@@ -7232,8 +7234,8 @@ static int check_mem_access(struct bpf_v
+                        * PTR_TO_PACKET[_META,_END]. In the latter
+                        * case, we know the offset is zero.
+                        */
+-                      if (reg_type == SCALAR_VALUE) {
+-                              if (is_retval && get_func_retval_range(env->prog, &range)) {
++                      if (info.reg_type == SCALAR_VALUE) {
++                              if (info.is_retval && get_func_retval_range(env->prog, &range)) {
+                                       err = __mark_reg_s32_range(env, regs, value_regno,
+                                                                  range.minval, range.maxval);
+                                       if (err)
+@@ -7244,7 +7246,7 @@ static int check_mem_access(struct bpf_v
+                       } else {
+                               mark_reg_known_zero(env, regs,
+                                                   value_regno);
+-                              if (type_may_be_null(reg_type))
++                              if (type_may_be_null(info.reg_type))
+                                       regs[value_regno].id = ++env->id_gen;
+                               /* A load of ctx field could have different
+                                * actual load size with the one encoded in the
+@@ -7252,12 +7254,12 @@ static int check_mem_access(struct bpf_v
+                                * a sub-register.
+                                */
+                               regs[value_regno].subreg_def = DEF_NOT_SUBREG;
+-                              if (base_type(reg_type) == PTR_TO_BTF_ID) {
+-                                      regs[value_regno].btf = btf;
+-                                      regs[value_regno].btf_id = btf_id;
++                              if (base_type(info.reg_type) == PTR_TO_BTF_ID) {
++                                      regs[value_regno].btf = info.btf;
++                                      regs[value_regno].btf_id = info.btf_id;
+                               }
+                       }
+-                      regs[value_regno].type = reg_type;
++                      regs[value_regno].type = info.reg_type;
+               }
+       } else if (reg->type == PTR_TO_STACK) {
diff --git a/queue-6.12/bpf-refactor-check_ctx_access.patch b/queue-6.12/bpf-refactor-check_ctx_access.patch
new file mode 100644 (file)
index 0000000..8de02f3
--- /dev/null
@@ -0,0 +1,84 @@
+From stable+bounces-289596-greg=kroah.com@vger.kernel.org Mon Jul 27 17:49:26 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 11:41:02 -0400
+Subject: bpf: Refactor check_ctx_access()
+To: stable@vger.kernel.org
+Cc: Amery Hung <ameryhung@gmail.com>, Alexei Starovoitov <ast@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727154103.1515347-2-sashal@kernel.org>
+
+From: Amery Hung <ameryhung@gmail.com>
+
+[ Upstream commit 201b62ccc83153d2925d310a2afe762905e0c455 ]
+
+Reduce the variable passing madness surrounding check_ctx_access().
+Currently, check_mem_access() passes many pointers to local variables to
+check_ctx_access(). They are used to initialize "struct
+bpf_insn_access_aux info" in check_ctx_access() and then passed to
+is_valid_access(). Then, check_ctx_access() takes the data our from
+info and write them back the pointers to pass them back. This can be
+simpilified by moving info up to check_mem_access().
+
+No functional change.
+
+Signed-off-by: Amery Hung <ameryhung@gmail.com>
+Link: https://lore.kernel.org/r/20250221175644.1822383-1-ameryhung@gmail.com
+Signed-off-by: Alexei Starovoitov <ast@kernel.org>
+Stable-dep-of: 5e0b273e0a62 ("bpf: Reset register bounds before narrowing retval range in check_mem_access()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ kernel/bpf/verifier.c |   27 +++++----------------------
+ 1 file changed, 5 insertions(+), 22 deletions(-)
+
+--- a/kernel/bpf/verifier.c
++++ b/kernel/bpf/verifier.c
+@@ -5829,18 +5829,10 @@ static int check_packet_access(struct bp
+ /* check access to 'struct bpf_context' fields.  Supports fixed offsets only */
+ static int check_ctx_access(struct bpf_verifier_env *env, int insn_idx, int off, int size,
+-                          enum bpf_access_type t, enum bpf_reg_type *reg_type,
+-                          struct btf **btf, u32 *btf_id, bool *is_retval, bool is_ldsx)
++                          enum bpf_access_type t, struct bpf_insn_access_aux *info)
+ {
+-      struct bpf_insn_access_aux info = {
+-              .reg_type = *reg_type,
+-              .log = &env->log,
+-              .is_retval = false,
+-              .is_ldsx = is_ldsx,
+-      };
+-
+       if (env->ops->is_valid_access &&
+-          env->ops->is_valid_access(off, size, t, env->prog, &info)) {
++          env->ops->is_valid_access(off, size, t, env->prog, info)) {
+               /* A non zero info.ctx_field_size indicates that this field is a
+                * candidate for later verifier transformation to load the whole
+                * field and then apply a mask when accessed with a narrower
+@@ -5848,15 +5840,8 @@ static int check_ctx_access(struct bpf_v
+                * will only allow for whole field access and rejects any other
+                * type of narrower access.
+                */
+-              *reg_type = info.reg_type;
+-              *is_retval = info.is_retval;
+-
+-              if (base_type(*reg_type) == PTR_TO_BTF_ID) {
+-                      *btf = info.btf;
+-                      *btf_id = info.btf_id;
+-              } else {
+-                      env->insn_aux_data[insn_idx].ctx_field_size = info.ctx_field_size;
+-              }
++              if (base_type(info->reg_type) != PTR_TO_BTF_ID)
++                      env->insn_aux_data[insn_idx].ctx_field_size = info->ctx_field_size;
+               /* remember the offset of last byte accessed in ctx */
+               if (env->prog->aux->max_ctx_offset < off + size)
+                       env->prog->aux->max_ctx_offset = off + size;
+@@ -7224,9 +7209,7 @@ static int check_mem_access(struct bpf_v
+               if (err < 0)
+                       return err;
+-              err = check_ctx_access(env, insn_idx, off, size, t, &info.reg_type,
+-                                     &info.btf, &info.btf_id, &info.is_retval,
+-                                     info.is_ldsx);
++              err = check_ctx_access(env, insn_idx, off, size, t, &info);
+               if (err)
+                       verbose_linfo(env, insn_idx, "; ");
+               if (!err && t == BPF_READ && value_regno >= 0) {
diff --git a/queue-6.12/bpf-reset-register-bounds-before-narrowing-retval-range-in-check_mem_access.patch b/queue-6.12/bpf-reset-register-bounds-before-narrowing-retval-range-in-check_mem_access.patch
new file mode 100644 (file)
index 0000000..519de4e
--- /dev/null
@@ -0,0 +1,51 @@
+From stable+bounces-289597-greg=kroah.com@vger.kernel.org Mon Jul 27 17:52:29 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 11:41:03 -0400
+Subject: bpf: Reset register bounds before narrowing retval range in check_mem_access()
+To: stable@vger.kernel.org
+Cc: Tristan Madani <tristan@talencesecurity.com>, Eduard Zingerman <eddyz87@gmail.com>, Alexei Starovoitov <ast@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727154103.1515347-3-sashal@kernel.org>
+
+From: Tristan Madani <tristan@talencesecurity.com>
+
+[ Upstream commit 5e0b273e0a62cc04ec338c7b502797c66c2ed42a ]
+
+When the BPF verifier processes a context load of an LSM hook return
+value, it calls __mark_reg_s32_range() to narrow the register to the
+hook's valid range. However, __mark_reg_s32_range() intersects the new
+range with the register's existing bounds using max_t()/min_t() rather
+than replacing them.
+
+If the destination register carries stale bounds from a prior instruction
+(e.g. BPF_MOV64_IMM), the intersection can produce a range narrower than
+reality. The verifier then believes it knows the register's exact value,
+while at runtime the actual hook return value is loaded, creating a
+verifier/runtime mismatch that can be used to bypass BPF memory safety
+checks.
+
+The else branch already calls mark_reg_unknown() to reset register state
+before any narrowing. Apply the same reset in the is_retval path so
+stale bounds are cleared before __mark_reg_s32_range() intersects.
+
+Fixes: 5d99e198be27 ("bpf, lsm: Add check for BPF LSM return value")
+Cc: stable@vger.kernel.org
+Signed-off-by: Tristan Madani <tristan@talencesecurity.com>
+Acked-by: Eduard Zingerman <eddyz87@gmail.com>
+Link: https://lore.kernel.org/r/20260622230123.3695446-2-tristmd@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/verifier.c |    1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/kernel/bpf/verifier.c
++++ b/kernel/bpf/verifier.c
+@@ -7219,6 +7219,7 @@ static int check_mem_access(struct bpf_v
+                        */
+                       if (info.reg_type == SCALAR_VALUE) {
+                               if (info.is_retval && get_func_retval_range(env->prog, &range)) {
++                                      mark_reg_unknown(env, regs, value_regno);
+                                       err = __mark_reg_s32_range(env, regs, value_regno,
+                                                                  range.minval, range.maxval);
+                                       if (err)
diff --git a/queue-6.12/cleanup-add-a-scoped-version-of-class.patch b/queue-6.12/cleanup-add-a-scoped-version-of-class.patch
new file mode 100644 (file)
index 0000000..f2501a8
--- /dev/null
@@ -0,0 +1,45 @@
+From stable+bounces-289715-greg=kroah.com@vger.kernel.org Tue Jul 28 00:25:07 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 18:21:05 -0400
+Subject: cleanup: add a scoped version of CLASS()
+To: stable@vger.kernel.org
+Cc: Christian Brauner <brauner@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727222109.2119761-1-sashal@kernel.org>
+
+From: Christian Brauner <brauner@kernel.org>
+
+[ Upstream commit 5c21c5f22d0701ac6c1cafc0e8de4bf42e5c53e5 ]
+
+This will make it possible to use:
+
+scoped_class() {
+}
+
+constructs to limit variables to certain scopes and still perform
+auto-cleanup.
+
+Signed-off-by: Christian Brauner <brauner@kernel.org>
+Stable-dep-of: 981ccd97f715 ("dm: avoid leaking the caller's thread keyring via the table device file")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/cleanup.h |    8 ++++++++
+ 1 file changed, 8 insertions(+)
+
+--- a/include/linux/cleanup.h
++++ b/include/linux/cleanup.h
+@@ -277,6 +277,14 @@ static inline class_##_name##_t class_##
+       class_##_name##_t var __cleanup(class_##_name##_destructor) =   \
+               class_##_name##_constructor
++#define scoped_class(_name, var, args)                          \
++      for (CLASS(_name, var)(args);                           \
++           __guard_ptr(_name)(&var) || !__is_cond_ptr(_name); \
++           ({ goto _label; }))                                \
++              if (0) {                                        \
++_label:                                                         \
++                      break;                                  \
++              } else
+ /*
+  * DEFINE_GUARD(name, type, lock, unlock):
diff --git a/queue-6.12/cleanup-fix-scoped_class.patch b/queue-6.12/cleanup-fix-scoped_class.patch
new file mode 100644 (file)
index 0000000..35af75a
--- /dev/null
@@ -0,0 +1,57 @@
+From stable+bounces-289716-greg=kroah.com@vger.kernel.org Tue Jul 28 00:21:17 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 18:21:06 -0400
+Subject: cleanup: fix scoped_class()
+To: stable@vger.kernel.org
+Cc: Christian Brauner <brauner@kernel.org>, Jens Axboe <axboe@kernel.dk>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727222109.2119761-2-sashal@kernel.org>
+
+From: Christian Brauner <brauner@kernel.org>
+
+[ Upstream commit 4e97bae1b412cd6ed8053b3d8a242122952985cc ]
+
+This is a class, not a guard so why on earth is it checking for guard
+pointers or conditional lock acquisition? None of it makes any sense at
+all.
+
+I'm not sure what happened back then. Maybe I had a brief psychedelic
+period that I completely forgot about and spaced out into a zone where
+that initial macro implementation made any sense at all.
+
+Link: https://patch.msgid.link/20251103-work-creds-init_cred-v1-1-cb3ec8711a6a@kernel.org
+Fixes: 5c21c5f22d07 ("cleanup: add a scoped version of CLASS()")
+Reviewed-by: Jens Axboe <axboe@kernel.dk>
+Signed-off-by: Christian Brauner <brauner@kernel.org>
+Stable-dep-of: 981ccd97f715 ("dm: avoid leaking the caller's thread keyring via the table device file")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/cleanup.h |   15 ++++++++-------
+ 1 file changed, 8 insertions(+), 7 deletions(-)
+
+--- a/include/linux/cleanup.h
++++ b/include/linux/cleanup.h
+@@ -277,15 +277,16 @@ static inline class_##_name##_t class_##
+       class_##_name##_t var __cleanup(class_##_name##_destructor) =   \
+               class_##_name##_constructor
+-#define scoped_class(_name, var, args)                          \
+-      for (CLASS(_name, var)(args);                           \
+-           __guard_ptr(_name)(&var) || !__is_cond_ptr(_name); \
+-           ({ goto _label; }))                                \
+-              if (0) {                                        \
+-_label:                                                         \
+-                      break;                                  \
++#define __scoped_class(_name, var, _label, args...)        \
++      for (CLASS(_name, var)(args); ; ({ goto _label; })) \
++              if (0) {                                   \
++_label:                                                    \
++                      break;                             \
+               } else
++#define scoped_class(_name, var, args...) \
++      __scoped_class(_name, var, __UNIQUE_ID(label), args)
++
+ /*
+  * DEFINE_GUARD(name, type, lock, unlock):
+  *    trivial wrapper around DEFINE_CLASS() above specifically
diff --git a/queue-6.12/cred-add-kernel_cred-helper.patch b/queue-6.12/cred-add-kernel_cred-helper.patch
new file mode 100644 (file)
index 0000000..e0405fd
--- /dev/null
@@ -0,0 +1,48 @@
+From stable+bounces-289717-greg=kroah.com@vger.kernel.org Tue Jul 28 00:25:14 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 18:21:07 -0400
+Subject: cred: add kernel_cred() helper
+To: stable@vger.kernel.org
+Cc: Christian Brauner <brauner@kernel.org>, Jens Axboe <axboe@kernel.dk>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727222109.2119761-3-sashal@kernel.org>
+
+From: Christian Brauner <brauner@kernel.org>
+
+[ Upstream commit 4c7ceeb62d3330b6fb2b549ae833a92c0f481f3e ]
+
+Access kernel creds based off of init_task. This will let us avoid any
+direct access to init_cred.
+
+Link: https://patch.msgid.link/20251103-work-creds-init_cred-v1-2-cb3ec8711a6a@kernel.org
+Reviewed-by: Jens Axboe <axboe@kernel.dk>
+Signed-off-by: Christian Brauner <brauner@kernel.org>
+Stable-dep-of: 981ccd97f715 ("dm: avoid leaking the caller's thread keyring via the table device file")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/cred.h |    7 +++++++
+ 1 file changed, 7 insertions(+)
+
+--- a/include/linux/cred.h
++++ b/include/linux/cred.h
+@@ -20,6 +20,8 @@
+ struct cred;
+ struct inode;
++extern struct task_struct init_task;
++
+ /*
+  * COW Supplementary groups list
+  */
+@@ -158,6 +160,11 @@ extern void abort_creds(struct cred *);
+ extern const struct cred *override_creds(const struct cred *);
+ extern void revert_creds(const struct cred *);
+ extern struct cred *prepare_kernel_cred(struct task_struct *);
++static inline const struct cred *kernel_cred(void)
++{
++      /* shut up sparse */
++      return rcu_dereference_raw(init_task.cred);
++}
+ extern int set_security_override(struct cred *, u32);
+ extern int set_security_override_from_ctx(struct cred *, const char *);
+ extern int set_create_files_as(struct cred *, struct inode *);
diff --git a/queue-6.12/cred-add-scoped_with_kernel_creds.patch b/queue-6.12/cred-add-scoped_with_kernel_creds.patch
new file mode 100644 (file)
index 0000000..5e4aae7
--- /dev/null
@@ -0,0 +1,47 @@
+From stable+bounces-289718-greg=kroah.com@vger.kernel.org Tue Jul 28 00:21:17 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 18:21:08 -0400
+Subject: cred: add scoped_with_kernel_creds()
+To: stable@vger.kernel.org
+Cc: Christian Brauner <brauner@kernel.org>, Jens Axboe <axboe@kernel.dk>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727222109.2119761-4-sashal@kernel.org>
+
+From: Christian Brauner <brauner@kernel.org>
+
+[ Upstream commit ae40e6c65791f47c76cc14d0cce2707fe6053f72 ]
+
+Add a new cleanup class for override creds. We can make use of this in a
+bunch of places going forward.
+
+Based on this scoped_with_kernel_creds() that can be used to temporarily
+assume kernel credentials for specific tasks such as firmware loading,
+or coredump socket connections. At no point will the caller interact
+with the kernel credentials directly.
+
+Link: https://patch.msgid.link/20251103-work-creds-init_cred-v1-4-cb3ec8711a6a@kernel.org
+Reviewed-by: Jens Axboe <axboe@kernel.dk>
+Signed-off-by: Christian Brauner <brauner@kernel.org>
+Stable-dep-of: 981ccd97f715 ("dm: avoid leaking the caller's thread keyring via the table device file")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/cred.h |    8 ++++++++
+ 1 file changed, 8 insertions(+)
+
+--- a/include/linux/cred.h
++++ b/include/linux/cred.h
+@@ -205,6 +205,14 @@ static inline struct cred *get_new_cred(
+       return get_new_cred_many(cred, 1);
+ }
++DEFINE_CLASS(override_creds,
++           const struct cred *,
++           revert_creds(_T),
++           override_creds(override_cred), const struct cred *override_cred)
++
++#define scoped_with_kernel_creds() \
++      scoped_class(override_creds, __UNIQUE_ID(cred), kernel_cred())
++
+ /**
+  * get_cred_many - Get references on a set of credentials
+  * @cred: The credentials to reference
diff --git a/queue-6.12/dm-avoid-leaking-the-caller-s-thread-keyring-via-the-table-device-file.patch b/queue-6.12/dm-avoid-leaking-the-caller-s-thread-keyring-via-the-table-device-file.patch
new file mode 100644 (file)
index 0000000..d48fb99
--- /dev/null
@@ -0,0 +1,72 @@
+From stable+bounces-289719-greg=kroah.com@vger.kernel.org Tue Jul 28 00:21:18 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 18:21:09 -0400
+Subject: dm: avoid leaking the caller's thread keyring via the table device file
+To: stable@vger.kernel.org
+Cc: Ingo Blechschmidt <iblech@speicherleck.de>, Mikulas Patocka <mpatocka@redhat.com>, Ondrej Kozina <okozina@redhat.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727222109.2119761-5-sashal@kernel.org>
+
+From: Ingo Blechschmidt <iblech@speicherleck.de>
+
+[ Upstream commit 981ccd97f7153d310dfa92a534525bbaf46752c2 ]
+
+The refactoring in commit a28d893eb327 ("md: port block device access to file")
+accidentally causes the caller's thread keyring to be kept alive long
+beyond the caller's lifetime.
+
+As a result, "cryptsetup luksSuspend" silently fails to wipe the
+LUKS volume key from memory.
+
+In detail: "cryptsetup luksOpen" uses its supposedly ephemeral thread
+keyring to pass the volume key to the kernel. dm-crypt's
+crypt_set_keyring_key() copies the key material into its own
+crypt_config structure and then drops its own reference to the key in
+the keyring with key_put().
+
+With this fix, restoring pre-v6.9 behavior, the copy in the thread
+keyring is then promptly garbage collected, such that exactly one copy
+of the volume key remains. This single copy is correctly wiped from
+memory on "cryptsetup luksSuspend".
+
+Without this fix, the thread keyring and the volume key in it remains.
+This second copy is only freed on "luksClose". "luksSuspend" neither
+knows about this copy nor has any way to remove it, so the key remains
+recoverable from RAM after a suspend that is documented to have wiped it.
+
+This fix should not introduce new security problems, as the code is
+anyway gated by CAP_SYS_ADMIN. The device-mapper core, not the calling
+task, is the legitimate owner of this long-lived file.
+
+Fixes: a28d893eb327 ("md: port block device access to file")
+Closes: https://gitlab.com/cryptsetup/cryptsetup/-/work_items/993
+Link: https://www.speicherleck.de/iblech/cryptsetup-luksSuspend-issue-reproduction/
+Signed-off-by: Ingo Blechschmidt <iblech@speicherleck.de>
+Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
+Cc: stable@vger.kernel.org
+Tested-by: Ondrej Kozina <okozina@redhat.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/md/dm.c |   11 ++++++++++-
+ 1 file changed, 10 insertions(+), 1 deletion(-)
+
+--- a/drivers/md/dm.c
++++ b/drivers/md/dm.c
+@@ -737,7 +737,16 @@ static struct table_device *open_table_d
+               return ERR_PTR(-ENOMEM);
+       refcount_set(&td->count, 1);
+-      bdev_file = bdev_file_open_by_dev(dev, mode, _dm_claim_ptr, NULL);
++      /*
++       * Open the backing device with kernel rather than caller
++       * credentials. Otherwise the caller's credentials would be
++       * pinned in bdev_file->f_cred until the table device is closed.
++       * That would keep the caller's thread keyring alive long beyond the
++       * lifetime of the caller, breaking userspace expectation (e.g.
++       * cryptsetup(8) leaking the LUKS volume key).
++       */
++      scoped_with_kernel_creds()
++              bdev_file = bdev_file_open_by_dev(dev, mode, _dm_claim_ptr, NULL);
+       if (IS_ERR(bdev_file)) {
+               r = PTR_ERR(bdev_file);
+               goto out_free_td;
diff --git a/queue-6.12/dm-integrity-fix-leaking-uninitialized-kernel-memory.patch b/queue-6.12/dm-integrity-fix-leaking-uninitialized-kernel-memory.patch
new file mode 100644 (file)
index 0000000..87d73a4
--- /dev/null
@@ -0,0 +1,37 @@
+From stable+bounces-289643-greg=kroah.com@vger.kernel.org Mon Jul 27 20:43:55 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 14:39:57 -0400
+Subject: dm-integrity: fix leaking uninitialized kernel memory
+To: stable@vger.kernel.org
+Cc: Mikulas Patocka <mpatocka@redhat.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727183957.1545701-1-sashal@kernel.org>
+
+From: Mikulas Patocka <mpatocka@redhat.com>
+
+[ Upstream commit 7bb03b2b01b814a9fc14afbfc2cbb2cca5b34750 ]
+
+If hash size is less than device's tuple size, dm-integrity is supposed
+to zero the remaining space. There was a bug in the code that zeroing
+didn't work. This commit fixes it.
+
+Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
+Assisted-by: Claude:claude-opus-4.6
+Fixes: fb0987682c62 ("dm-integrity: introduce the Inline mode")
+Cc: stable@vger.kernel.org
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/md/dm-integrity.c |    2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/md/dm-integrity.c
++++ b/drivers/md/dm-integrity.c
+@@ -2502,7 +2502,7 @@ skip_spinlock:
+                       struct bio_vec bv = bio_iter_iovec(bio, dio->bio_details.bi_iter);
+                       const char *mem = bvec_kmap_local(&bv);
+                       if (ic->tag_size < ic->tuple_size)
+-                              memset(dio->integrity_payload + pos + ic->tag_size, 0, ic->tuple_size - ic->tuple_size);
++                              memset(dio->integrity_payload + pos + ic->tag_size, 0, ic->tuple_size - ic->tag_size);
+                       integrity_sector_checksum(ic, dio->bio_details.bi_iter.bi_sector, mem, dio->integrity_payload + pos);
+                       kunmap_local(mem);
+                       pos += ic->tuple_size;
diff --git a/queue-6.12/dma-dw-edma-fix-build-warning-in-dw_edma_pcie_probe.patch b/queue-6.12/dma-dw-edma-fix-build-warning-in-dw_edma_pcie_probe.patch
new file mode 100644 (file)
index 0000000..2f5825e
--- /dev/null
@@ -0,0 +1,177 @@
+From stable+bounces-289277-greg=kroah.com@vger.kernel.org Sun Jul 26 15:22:06 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 26 Jul 2026 09:21:52 -0400
+Subject: dma: dw-edma: Fix build warning in dw_edma_pcie_probe()
+To: stable@vger.kernel.org
+Cc: Abinash Singh <abinashlalotra@gmail.com>, Abinash Singh <abinashsinghlalotra@gmail.com>, Manivannan Sadhasivam <mani@kernel.org>, Vinod Koul <vkoul@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260726132154.327006-1-sashal@kernel.org>
+
+From: Abinash Singh <abinashlalotra@gmail.com>
+
+[ Upstream commit 3df63fa8f2afd051848e37ef1b8299dee28d4f87 ]
+
+The function dw_edma_pcie_probe() in dw-edma-pcie.c triggered a
+frame size warning:
+ld.lld:warning:
+  drivers/dma/dw-edma/dw-edma-pcie.c:162:0: stack frame size (1040) exceeds limit (1024) in function 'dw_edma_pcie_probe'
+
+This patch reduces the stack usage by dynamically allocating the
+`vsec_data` structure using kmalloc(), rather than placing it on
+the stack. This eliminates the overflow warning and improves kernel
+robustness.
+
+Signed-off-by: Abinash Singh <abinashsinghlalotra@gmail.com>
+Acked-by: Manivannan Sadhasivam <mani@kernel.org>
+Link: https://lore.kernel.org/r/20250705160055.808165-1-abinashsinghlalotra@gmail.com
+Signed-off-by: Vinod Koul <vkoul@kernel.org>
+Stable-dep-of: 11d7cfe0c119 ("dmaengine: dw-edma-pcie: Reject devices without driver data")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/dma/dw-edma/dw-edma-pcie.c |   62 +++++++++++++++++++------------------
+ 1 file changed, 33 insertions(+), 29 deletions(-)
+
+--- a/drivers/dma/dw-edma/dw-edma-pcie.c
++++ b/drivers/dma/dw-edma/dw-edma-pcie.c
+@@ -160,12 +160,16 @@ static int dw_edma_pcie_probe(struct pci
+                             const struct pci_device_id *pid)
+ {
+       struct dw_edma_pcie_data *pdata = (void *)pid->driver_data;
+-      struct dw_edma_pcie_data vsec_data;
++      struct dw_edma_pcie_data *vsec_data __free(kfree) = NULL;
+       struct device *dev = &pdev->dev;
+       struct dw_edma_chip *chip;
+       int err, nr_irqs;
+       int i, mask;
++      vsec_data = kmalloc(sizeof(*vsec_data), GFP_KERNEL);
++      if (!vsec_data)
++              return -ENOMEM;
++
+       /* Enable PCI device */
+       err = pcim_enable_device(pdev);
+       if (err) {
+@@ -173,23 +177,23 @@ static int dw_edma_pcie_probe(struct pci
+               return err;
+       }
+-      memcpy(&vsec_data, pdata, sizeof(struct dw_edma_pcie_data));
++      memcpy(vsec_data, pdata, sizeof(struct dw_edma_pcie_data));
+       /*
+        * Tries to find if exists a PCIe Vendor-Specific Extended Capability
+        * for the DMA, if one exists, then reconfigures it.
+        */
+-      dw_edma_pcie_get_vsec_dma_data(pdev, &vsec_data);
++      dw_edma_pcie_get_vsec_dma_data(pdev, vsec_data);
+       /* Mapping PCI BAR regions */
+-      mask = BIT(vsec_data.rg.bar);
+-      for (i = 0; i < vsec_data.wr_ch_cnt; i++) {
+-              mask |= BIT(vsec_data.ll_wr[i].bar);
+-              mask |= BIT(vsec_data.dt_wr[i].bar);
+-      }
+-      for (i = 0; i < vsec_data.rd_ch_cnt; i++) {
+-              mask |= BIT(vsec_data.ll_rd[i].bar);
+-              mask |= BIT(vsec_data.dt_rd[i].bar);
++      mask = BIT(vsec_data->rg.bar);
++      for (i = 0; i < vsec_data->wr_ch_cnt; i++) {
++              mask |= BIT(vsec_data->ll_wr[i].bar);
++              mask |= BIT(vsec_data->dt_wr[i].bar);
++      }
++      for (i = 0; i < vsec_data->rd_ch_cnt; i++) {
++              mask |= BIT(vsec_data->ll_rd[i].bar);
++              mask |= BIT(vsec_data->dt_rd[i].bar);
+       }
+       err = pcim_iomap_regions(pdev, mask, pci_name(pdev));
+       if (err) {
+@@ -212,7 +216,7 @@ static int dw_edma_pcie_probe(struct pci
+               return -ENOMEM;
+       /* IRQs allocation */
+-      nr_irqs = pci_alloc_irq_vectors(pdev, 1, vsec_data.irqs,
++      nr_irqs = pci_alloc_irq_vectors(pdev, 1, vsec_data->irqs,
+                                       PCI_IRQ_MSI | PCI_IRQ_MSIX);
+       if (nr_irqs < 1) {
+               pci_err(pdev, "fail to alloc IRQ vector (number of IRQs=%u)\n",
+@@ -223,22 +227,22 @@ static int dw_edma_pcie_probe(struct pci
+       /* Data structure initialization */
+       chip->dev = dev;
+-      chip->mf = vsec_data.mf;
++      chip->mf = vsec_data->mf;
+       chip->nr_irqs = nr_irqs;
+       chip->ops = &dw_edma_pcie_plat_ops;
+-      chip->ll_wr_cnt = vsec_data.wr_ch_cnt;
+-      chip->ll_rd_cnt = vsec_data.rd_ch_cnt;
++      chip->ll_wr_cnt = vsec_data->wr_ch_cnt;
++      chip->ll_rd_cnt = vsec_data->rd_ch_cnt;
+-      chip->reg_base = pcim_iomap_table(pdev)[vsec_data.rg.bar];
++      chip->reg_base = pcim_iomap_table(pdev)[vsec_data->rg.bar];
+       if (!chip->reg_base)
+               return -ENOMEM;
+       for (i = 0; i < chip->ll_wr_cnt; i++) {
+               struct dw_edma_region *ll_region = &chip->ll_region_wr[i];
+               struct dw_edma_region *dt_region = &chip->dt_region_wr[i];
+-              struct dw_edma_block *ll_block = &vsec_data.ll_wr[i];
+-              struct dw_edma_block *dt_block = &vsec_data.dt_wr[i];
++              struct dw_edma_block *ll_block = &vsec_data->ll_wr[i];
++              struct dw_edma_block *dt_block = &vsec_data->dt_wr[i];
+               ll_region->vaddr.io = pcim_iomap_table(pdev)[ll_block->bar];
+               if (!ll_region->vaddr.io)
+@@ -262,8 +266,8 @@ static int dw_edma_pcie_probe(struct pci
+       for (i = 0; i < chip->ll_rd_cnt; i++) {
+               struct dw_edma_region *ll_region = &chip->ll_region_rd[i];
+               struct dw_edma_region *dt_region = &chip->dt_region_rd[i];
+-              struct dw_edma_block *ll_block = &vsec_data.ll_rd[i];
+-              struct dw_edma_block *dt_block = &vsec_data.dt_rd[i];
++              struct dw_edma_block *ll_block = &vsec_data->ll_rd[i];
++              struct dw_edma_block *dt_block = &vsec_data->dt_rd[i];
+               ll_region->vaddr.io = pcim_iomap_table(pdev)[ll_block->bar];
+               if (!ll_region->vaddr.io)
+@@ -295,31 +299,31 @@ static int dw_edma_pcie_probe(struct pci
+               pci_dbg(pdev, "Version:\tUnknown (0x%x)\n", chip->mf);
+       pci_dbg(pdev, "Registers:\tBAR=%u, off=0x%.8lx, sz=0x%zx bytes, addr(v=%p)\n",
+-              vsec_data.rg.bar, vsec_data.rg.off, vsec_data.rg.sz,
++              vsec_data->rg.bar, vsec_data->rg.off, vsec_data->rg.sz,
+               chip->reg_base);
+       for (i = 0; i < chip->ll_wr_cnt; i++) {
+               pci_dbg(pdev, "L. List:\tWRITE CH%.2u, BAR=%u, off=0x%.8lx, sz=0x%zx bytes, addr(v=%p, p=%pa)\n",
+-                      i, vsec_data.ll_wr[i].bar,
+-                      vsec_data.ll_wr[i].off, chip->ll_region_wr[i].sz,
++                      i, vsec_data->ll_wr[i].bar,
++                      vsec_data->ll_wr[i].off, chip->ll_region_wr[i].sz,
+                       chip->ll_region_wr[i].vaddr.io, &chip->ll_region_wr[i].paddr);
+               pci_dbg(pdev, "Data:\tWRITE CH%.2u, BAR=%u, off=0x%.8lx, sz=0x%zx bytes, addr(v=%p, p=%pa)\n",
+-                      i, vsec_data.dt_wr[i].bar,
+-                      vsec_data.dt_wr[i].off, chip->dt_region_wr[i].sz,
++                      i, vsec_data->dt_wr[i].bar,
++                      vsec_data->dt_wr[i].off, chip->dt_region_wr[i].sz,
+                       chip->dt_region_wr[i].vaddr.io, &chip->dt_region_wr[i].paddr);
+       }
+       for (i = 0; i < chip->ll_rd_cnt; i++) {
+               pci_dbg(pdev, "L. List:\tREAD CH%.2u, BAR=%u, off=0x%.8lx, sz=0x%zx bytes, addr(v=%p, p=%pa)\n",
+-                      i, vsec_data.ll_rd[i].bar,
+-                      vsec_data.ll_rd[i].off, chip->ll_region_rd[i].sz,
++                      i, vsec_data->ll_rd[i].bar,
++                      vsec_data->ll_rd[i].off, chip->ll_region_rd[i].sz,
+                       chip->ll_region_rd[i].vaddr.io, &chip->ll_region_rd[i].paddr);
+               pci_dbg(pdev, "Data:\tREAD CH%.2u, BAR=%u, off=0x%.8lx, sz=0x%zx bytes, addr(v=%p, p=%pa)\n",
+-                      i, vsec_data.dt_rd[i].bar,
+-                      vsec_data.dt_rd[i].off, chip->dt_region_rd[i].sz,
++                      i, vsec_data->dt_rd[i].bar,
++                      vsec_data->dt_rd[i].off, chip->dt_region_rd[i].sz,
+                       chip->dt_region_rd[i].vaddr.io, &chip->dt_region_rd[i].paddr);
+       }
diff --git a/queue-6.12/dmaengine-dw-edma-fix-confusing-cleanup.h-syntax.patch b/queue-6.12/dmaengine-dw-edma-fix-confusing-cleanup.h-syntax.patch
new file mode 100644 (file)
index 0000000..14a9992
--- /dev/null
@@ -0,0 +1,55 @@
+From stable+bounces-289280-greg=kroah.com@vger.kernel.org Sun Jul 26 15:22:13 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 26 Jul 2026 09:21:53 -0400
+Subject: dmaengine: dw-edma: Fix confusing cleanup.h syntax
+To: stable@vger.kernel.org
+Cc: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>, Manivannan Sadhasivam <mani@kernel.org>, Vinod Koul <vkoul@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260726132154.327006-2-sashal@kernel.org>
+
+From: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
+
+[ Upstream commit f9ef8dedee34e2d7828d5a6a0643cd969aaa8437 ]
+
+Initializing automatic __free variables to NULL without need (e.g.
+branches with different allocations), followed by actual allocation is
+in contrary to explicit coding rules guiding cleanup.h:
+
+"Given that the "__free(...) = NULL" pattern for variables defined at
+the top of the function poses this potential interdependency problem the
+recommendation is to always define and assign variables in one statement
+and not group variable definitions at the top of the function when
+__free() is used."
+
+Code does not have a bug, but is less readable and uses discouraged
+coding practice, so fix that by moving declaration to the place of
+assignment.
+
+Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
+Reviewed-by: Manivannan Sadhasivam <mani@kernel.org>
+Link: https://patch.msgid.link/20251208020729.4654-2-krzysztof.kozlowski@oss.qualcomm.com
+Signed-off-by: Vinod Koul <vkoul@kernel.org>
+Stable-dep-of: 11d7cfe0c119 ("dmaengine: dw-edma-pcie: Reject devices without driver data")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/dma/dw-edma/dw-edma-pcie.c |    4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/dma/dw-edma/dw-edma-pcie.c
++++ b/drivers/dma/dw-edma/dw-edma-pcie.c
+@@ -160,13 +160,13 @@ static int dw_edma_pcie_probe(struct pci
+                             const struct pci_device_id *pid)
+ {
+       struct dw_edma_pcie_data *pdata = (void *)pid->driver_data;
+-      struct dw_edma_pcie_data *vsec_data __free(kfree) = NULL;
+       struct device *dev = &pdev->dev;
+       struct dw_edma_chip *chip;
+       int err, nr_irqs;
+       int i, mask;
+-      vsec_data = kmalloc(sizeof(*vsec_data), GFP_KERNEL);
++      struct dw_edma_pcie_data *vsec_data __free(kfree) =
++              kmalloc(sizeof(*vsec_data), GFP_KERNEL);
+       if (!vsec_data)
+               return -ENOMEM;
diff --git a/queue-6.12/dmaengine-dw-edma-pcie-reject-devices-without-driver-data.patch b/queue-6.12/dmaengine-dw-edma-pcie-reject-devices-without-driver-data.patch
new file mode 100644 (file)
index 0000000..1e5c868
--- /dev/null
@@ -0,0 +1,43 @@
+From stable+bounces-289279-greg=kroah.com@vger.kernel.org Sun Jul 26 15:22:18 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 26 Jul 2026 09:21:54 -0400
+Subject: dmaengine: dw-edma-pcie: Reject devices without driver data
+To: stable@vger.kernel.org
+Cc: Koichiro Den <den@valinux.co.jp>, Frank Li <Frank.Li@nxp.com>, Vinod Koul <vkoul@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260726132154.327006-3-sashal@kernel.org>
+
+From: Koichiro Den <den@valinux.co.jp>
+
+[ Upstream commit 11d7cfe0c119691b2dafbb699bbca90258c678aa ]
+
+dw_edma_pcie_probe() treats the PCI device ID driver_data as the
+template for the controller layout and copies it unconditionally. A
+device bound dynamically via sysfs can match the driver without that
+data, which leads to a NULL pointer dereference.
+
+Reject such matches before enabling the device.
+
+Fixes: 41aaff2a2ac0 ("dmaengine: Add Synopsys eDMA IP PCIe glue-logic")
+Cc: stable@vger.kernel.org
+Signed-off-by: Koichiro Den <den@valinux.co.jp>
+Reviewed-by: Frank Li <Frank.Li@nxp.com>
+Link: https://patch.msgid.link/20260521142153.2957432-3-den@valinux.co.jp
+Signed-off-by: Vinod Koul <vkoul@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/dma/dw-edma/dw-edma-pcie.c |    3 +++
+ 1 file changed, 3 insertions(+)
+
+--- a/drivers/dma/dw-edma/dw-edma-pcie.c
++++ b/drivers/dma/dw-edma/dw-edma-pcie.c
+@@ -165,6 +165,9 @@ static int dw_edma_pcie_probe(struct pci
+       int err, nr_irqs;
+       int i, mask;
++      if (!pdata)
++              return -ENODEV;
++
+       struct dw_edma_pcie_data *vsec_data __free(kfree) =
+               kmalloc(sizeof(*vsec_data), GFP_KERNEL);
+       if (!vsec_data)
diff --git a/queue-6.12/fbcon-rename-struct-fbcon_ops-to-struct-fbcon_par.patch b/queue-6.12/fbcon-rename-struct-fbcon_ops-to-struct-fbcon_par.patch
new file mode 100644 (file)
index 0000000..0a31c63
--- /dev/null
@@ -0,0 +1,2460 @@
+From stable+bounces-287902-greg=kroah.com@vger.kernel.org Wed Jul 22 16:52:24 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 10:48:41 -0400
+Subject: fbcon: Rename struct fbcon_ops to struct fbcon_par
+To: stable@vger.kernel.org
+Cc: Thomas Zimmermann <tzimmermann@suse.de>, Sam Ravnborg <sam@ravnborg.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722144842.1601797-1-sashal@kernel.org>
+
+From: Thomas Zimmermann <tzimmermann@suse.de>
+
+[ Upstream commit a6adbbc4c32a016146e117b1e9e5242724a75e10 ]
+
+The type struct fbcon_ops contains fbcon state and callbacks. As the
+callbacks will be removed from struct fbcon_ops, rename the data type
+to struct fbcon_par. Also rename the variables from ops to par.
+
+The _par postfix ("private access registers") is used throughout the
+fbdev subsystem for per-driver state. The fbcon pointer within struct
+fb_info is also named fbcon_par. Hence, the new naming fits existing
+practice.
+
+v2:
+- rename struct fbcon_ops to struct fbcon_par
+- fix build for CONFIG_FB_TILEBITTING=n (kernel test robot)
+- fix indention
+
+Signed-off-by: Thomas Zimmermann <tzimmermann@suse.de>
+Reviewed-by: Sam Ravnborg <sam@ravnborg.org>
+Link: https://lore.kernel.org/r/20250909124616.143365-3-tzimmermann@suse.de
+Stable-dep-of: 84202754fb17 ("fbcon: Use correct type for vc_resize() return value")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/video/fbdev/core/bitblit.c      |  120 ++++-----
+ drivers/video/fbdev/core/fbcon.c        |  417 +++++++++++++++-----------------
+ drivers/video/fbdev/core/fbcon.h        |    6 
+ drivers/video/fbdev/core/fbcon_ccw.c    |  146 +++++------
+ drivers/video/fbdev/core/fbcon_cw.c     |  146 +++++------
+ drivers/video/fbdev/core/fbcon_rotate.c |   44 +--
+ drivers/video/fbdev/core/fbcon_rotate.h |    6 
+ drivers/video/fbdev/core/fbcon_ud.c     |  162 ++++++------
+ drivers/video/fbdev/core/softcursor.c   |   20 -
+ drivers/video/fbdev/core/tileblit.c     |   28 +-
+ 10 files changed, 543 insertions(+), 552 deletions(-)
+
+--- a/drivers/video/fbdev/core/bitblit.c
++++ b/drivers/video/fbdev/core/bitblit.c
+@@ -261,10 +261,10 @@ static void bit_cursor(struct vc_data *v
+                      int fg, int bg)
+ {
+       struct fb_cursor cursor;
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       unsigned short charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff;
+       int w = DIV_ROUND_UP(vc->vc_font.width, 8), c;
+-      int y = real_y(ops->p, vc->state.y);
++      int y = real_y(par->p, vc->state.y);
+       int attribute, use_sw = vc->vc_cursor_type & CUR_SW;
+       int err = 1;
+       char *src;
+@@ -278,10 +278,10 @@ static void bit_cursor(struct vc_data *v
+       attribute = get_attribute(info, c);
+       src = vc->vc_font.data + ((c & charmask) * (w * vc->vc_font.height));
+-      if (ops->cursor_state.image.data != src ||
+-          ops->cursor_reset) {
+-          ops->cursor_state.image.data = src;
+-          cursor.set |= FB_CUR_SETIMAGE;
++      if (par->cursor_state.image.data != src ||
++          par->cursor_reset) {
++              par->cursor_state.image.data = src;
++              cursor.set |= FB_CUR_SETIMAGE;
+       }
+       if (attribute) {
+@@ -290,46 +290,46 @@ static void bit_cursor(struct vc_data *v
+               dst = kmalloc_array(w, vc->vc_font.height, GFP_ATOMIC);
+               if (!dst)
+                       return;
+-              kfree(ops->cursor_data);
+-              ops->cursor_data = dst;
++              kfree(par->cursor_data);
++              par->cursor_data = dst;
+               update_attr(dst, src, attribute, vc);
+               src = dst;
+       }
+-      if (ops->cursor_state.image.fg_color != fg ||
+-          ops->cursor_state.image.bg_color != bg ||
+-          ops->cursor_reset) {
+-              ops->cursor_state.image.fg_color = fg;
+-              ops->cursor_state.image.bg_color = bg;
++      if (par->cursor_state.image.fg_color != fg ||
++          par->cursor_state.image.bg_color != bg ||
++          par->cursor_reset) {
++              par->cursor_state.image.fg_color = fg;
++              par->cursor_state.image.bg_color = bg;
+               cursor.set |= FB_CUR_SETCMAP;
+       }
+-      if ((ops->cursor_state.image.dx != (vc->vc_font.width * vc->state.x)) ||
+-          (ops->cursor_state.image.dy != (vc->vc_font.height * y)) ||
+-          ops->cursor_reset) {
+-              ops->cursor_state.image.dx = vc->vc_font.width * vc->state.x;
+-              ops->cursor_state.image.dy = vc->vc_font.height * y;
++      if ((par->cursor_state.image.dx != (vc->vc_font.width * vc->state.x)) ||
++          (par->cursor_state.image.dy != (vc->vc_font.height * y)) ||
++          par->cursor_reset) {
++              par->cursor_state.image.dx = vc->vc_font.width * vc->state.x;
++              par->cursor_state.image.dy = vc->vc_font.height * y;
+               cursor.set |= FB_CUR_SETPOS;
+       }
+-      if (ops->cursor_state.image.height != vc->vc_font.height ||
+-          ops->cursor_state.image.width != vc->vc_font.width ||
+-          ops->cursor_reset) {
+-              ops->cursor_state.image.height = vc->vc_font.height;
+-              ops->cursor_state.image.width = vc->vc_font.width;
++      if (par->cursor_state.image.height != vc->vc_font.height ||
++          par->cursor_state.image.width != vc->vc_font.width ||
++          par->cursor_reset) {
++              par->cursor_state.image.height = vc->vc_font.height;
++              par->cursor_state.image.width = vc->vc_font.width;
+               cursor.set |= FB_CUR_SETSIZE;
+       }
+-      if (ops->cursor_state.hot.x || ops->cursor_state.hot.y ||
+-          ops->cursor_reset) {
+-              ops->cursor_state.hot.x = cursor.hot.y = 0;
++      if (par->cursor_state.hot.x || par->cursor_state.hot.y ||
++          par->cursor_reset) {
++              par->cursor_state.hot.x = cursor.hot.y = 0;
+               cursor.set |= FB_CUR_SETHOT;
+       }
+       if (cursor.set & FB_CUR_SETSIZE ||
+-          vc->vc_cursor_type != ops->p->cursor_shape ||
+-          ops->cursor_state.mask == NULL ||
+-          ops->cursor_reset) {
++          vc->vc_cursor_type != par->p->cursor_shape ||
++          par->cursor_state.mask == NULL ||
++          par->cursor_reset) {
+               char *mask = kmalloc_array(w, vc->vc_font.height, GFP_ATOMIC);
+               int cur_height, size, i = 0;
+               u8 msk = 0xff;
+@@ -337,13 +337,13 @@ static void bit_cursor(struct vc_data *v
+               if (!mask)
+                       return;
+-              kfree(ops->cursor_state.mask);
+-              ops->cursor_state.mask = mask;
++              kfree(par->cursor_state.mask);
++              par->cursor_state.mask = mask;
+-              ops->p->cursor_shape = vc->vc_cursor_type;
++              par->p->cursor_shape = vc->vc_cursor_type;
+               cursor.set |= FB_CUR_SETSHAPE;
+-              switch (CUR_SIZE(ops->p->cursor_shape)) {
++              switch (CUR_SIZE(par->p->cursor_shape)) {
+               case CUR_NONE:
+                       cur_height = 0;
+                       break;
+@@ -372,19 +372,19 @@ static void bit_cursor(struct vc_data *v
+                       mask[i++] = msk;
+       }
+-      ops->cursor_state.enable = enable && !use_sw;
++      par->cursor_state.enable = enable && !use_sw;
+       cursor.image.data = src;
+-      cursor.image.fg_color = ops->cursor_state.image.fg_color;
+-      cursor.image.bg_color = ops->cursor_state.image.bg_color;
+-      cursor.image.dx = ops->cursor_state.image.dx;
+-      cursor.image.dy = ops->cursor_state.image.dy;
+-      cursor.image.height = ops->cursor_state.image.height;
+-      cursor.image.width = ops->cursor_state.image.width;
+-      cursor.hot.x = ops->cursor_state.hot.x;
+-      cursor.hot.y = ops->cursor_state.hot.y;
+-      cursor.mask = ops->cursor_state.mask;
+-      cursor.enable = ops->cursor_state.enable;
++      cursor.image.fg_color = par->cursor_state.image.fg_color;
++      cursor.image.bg_color = par->cursor_state.image.bg_color;
++      cursor.image.dx = par->cursor_state.image.dx;
++      cursor.image.dy = par->cursor_state.image.dy;
++      cursor.image.height = par->cursor_state.image.height;
++      cursor.image.width = par->cursor_state.image.width;
++      cursor.hot.x = par->cursor_state.hot.x;
++      cursor.hot.y = par->cursor_state.hot.y;
++      cursor.mask = par->cursor_state.mask;
++      cursor.enable = par->cursor_state.enable;
+       cursor.image.depth = 1;
+       cursor.rop = ROP_XOR;
+@@ -394,31 +394,31 @@ static void bit_cursor(struct vc_data *v
+       if (err)
+               soft_cursor(info, &cursor);
+-      ops->cursor_reset = 0;
++      par->cursor_reset = 0;
+ }
+ static int bit_update_start(struct fb_info *info)
+ {
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       int err;
+-      err = fb_pan_display(info, &ops->var);
+-      ops->var.xoffset = info->var.xoffset;
+-      ops->var.yoffset = info->var.yoffset;
+-      ops->var.vmode = info->var.vmode;
++      err = fb_pan_display(info, &par->var);
++      par->var.xoffset = info->var.xoffset;
++      par->var.yoffset = info->var.yoffset;
++      par->var.vmode = info->var.vmode;
+       return err;
+ }
+-void fbcon_set_bitops(struct fbcon_ops *ops)
++void fbcon_set_bitops(struct fbcon_par *par)
+ {
+-      ops->bmove = bit_bmove;
+-      ops->clear = bit_clear;
+-      ops->putcs = bit_putcs;
+-      ops->clear_margins = bit_clear_margins;
+-      ops->cursor = bit_cursor;
+-      ops->update_start = bit_update_start;
+-      ops->rotate_font = NULL;
++      par->bmove = bit_bmove;
++      par->clear = bit_clear;
++      par->putcs = bit_putcs;
++      par->clear_margins = bit_clear_margins;
++      par->cursor = bit_cursor;
++      par->update_start = bit_update_start;
++      par->rotate_font = NULL;
+-      if (ops->rotate)
+-              fbcon_set_rotate(ops);
++      if (par->rotate)
++              fbcon_set_rotate(par);
+ }
+--- a/drivers/video/fbdev/core/fbcon.c
++++ b/drivers/video/fbdev/core/fbcon.c
+@@ -200,27 +200,27 @@ static struct device *fbcon_device;
+ #ifdef CONFIG_FRAMEBUFFER_CONSOLE_ROTATION
+ static inline void fbcon_set_rotation(struct fb_info *info)
+ {
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       if (!(info->flags & FBINFO_MISC_TILEBLITTING) &&
+-          ops->p->con_rotate < 4)
+-              ops->rotate = ops->p->con_rotate;
++          par->p->con_rotate < 4)
++              par->rotate = par->p->con_rotate;
+       else
+-              ops->rotate = 0;
++              par->rotate = 0;
+ }
+ static void fbcon_rotate(struct fb_info *info, u32 rotate)
+ {
+-      struct fbcon_ops *ops= info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       struct fb_info *fb_info;
+-      if (!ops || ops->currcon == -1)
++      if (!par || par->currcon == -1)
+               return;
+-      fb_info = fbcon_info_from_console(ops->currcon);
++      fb_info = fbcon_info_from_console(par->currcon);
+       if (info == fb_info) {
+-              struct fbcon_display *p = &fb_display[ops->currcon];
++              struct fbcon_display *p = &fb_display[par->currcon];
+               if (rotate < 4)
+                       p->con_rotate = rotate;
+@@ -233,12 +233,12 @@ static void fbcon_rotate(struct fb_info
+ static void fbcon_rotate_all(struct fb_info *info, u32 rotate)
+ {
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       struct vc_data *vc;
+       struct fbcon_display *p;
+       int i;
+-      if (!ops || ops->currcon < 0 || rotate > 3)
++      if (!par || par->currcon < 0 || rotate > 3)
+               return;
+       for (i = first_fb_vc; i <= last_fb_vc; i++) {
+@@ -256,9 +256,9 @@ static void fbcon_rotate_all(struct fb_i
+ #else
+ static inline void fbcon_set_rotation(struct fb_info *info)
+ {
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+-      ops->rotate = FB_ROTATE_UR;
++      par->rotate = FB_ROTATE_UR;
+ }
+ static void fbcon_rotate(struct fb_info *info, u32 rotate)
+@@ -274,9 +274,9 @@ static void fbcon_rotate_all(struct fb_i
+ static int fbcon_get_rotate(struct fb_info *info)
+ {
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+-      return (ops) ? ops->rotate : 0;
++      return (par) ? par->rotate : 0;
+ }
+ static bool fbcon_skip_panic(struct fb_info *info)
+@@ -293,10 +293,10 @@ static bool fbcon_skip_panic(struct fb_i
+ static inline int fbcon_is_inactive(struct vc_data *vc, struct fb_info *info)
+ {
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       return (info->state != FBINFO_STATE_RUNNING ||
+-              vc->vc_mode != KD_TEXT || ops->graphics || fbcon_skip_panic(info));
++              vc->vc_mode != KD_TEXT || par->graphics || fbcon_skip_panic(info));
+ }
+ static int get_color(struct vc_data *vc, struct fb_info *info,
+@@ -368,7 +368,7 @@ static int get_color(struct vc_data *vc,
+ static void fb_flashcursor(struct work_struct *work)
+ {
+-      struct fbcon_ops *ops = container_of(work, struct fbcon_ops, cursor_work.work);
++      struct fbcon_par *par = container_of(work, struct fbcon_par, cursor_work.work);
+       struct fb_info *info;
+       struct vc_data *vc = NULL;
+       int c;
+@@ -383,10 +383,10 @@ static void fb_flashcursor(struct work_s
+               return;
+       /* protected by console_lock */
+-      info = ops->info;
++      info = par->info;
+-      if (ops->currcon != -1)
+-              vc = vc_cons[ops->currcon].d;
++      if (par->currcon != -1)
++              vc = vc_cons[par->currcon].d;
+       if (!vc || !con_is_visible(vc) ||
+           fbcon_info_from_console(vc->vc_num) != info ||
+@@ -396,29 +396,29 @@ static void fb_flashcursor(struct work_s
+       }
+       c = scr_readw((u16 *) vc->vc_pos);
+-      enable = ops->cursor_flash && !ops->cursor_state.enable;
+-      ops->cursor(vc, info, enable, get_color(vc, info, c, 1),
++      enable = par->cursor_flash && !par->cursor_state.enable;
++      par->cursor(vc, info, enable, get_color(vc, info, c, 1),
+                   get_color(vc, info, c, 0));
+       console_unlock();
+-      queue_delayed_work(system_power_efficient_wq, &ops->cursor_work,
+-                         ops->cur_blink_jiffies);
++      queue_delayed_work(system_power_efficient_wq, &par->cursor_work,
++                         par->cur_blink_jiffies);
+ }
+ static void fbcon_add_cursor_work(struct fb_info *info)
+ {
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       if (!fbcon_cursor_noblink)
+-              queue_delayed_work(system_power_efficient_wq, &ops->cursor_work,
+-                                 ops->cur_blink_jiffies);
++              queue_delayed_work(system_power_efficient_wq, &par->cursor_work,
++                                 par->cur_blink_jiffies);
+ }
+ static void fbcon_del_cursor_work(struct fb_info *info)
+ {
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+-      cancel_delayed_work_sync(&ops->cursor_work);
++      cancel_delayed_work_sync(&par->cursor_work);
+ }
+ #ifndef MODULE
+@@ -578,7 +578,7 @@ static void fbcon_prepare_logo(struct vc
+                              int cols, int rows, int new_cols, int new_rows)
+ {
+       /* Need to make room for the logo */
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       int cnt, erase = vc->vc_video_erase_char, step;
+       unsigned short *save = NULL, *r, *q;
+       int logo_height;
+@@ -594,7 +594,7 @@ static void fbcon_prepare_logo(struct vc
+        */
+       if (fb_get_color_depth(&info->var, &info->fix) == 1)
+               erase &= ~0x400;
+-      logo_height = fb_prepare_logo(info, ops->rotate);
++      logo_height = fb_prepare_logo(info, par->rotate);
+       logo_lines = DIV_ROUND_UP(logo_height, vc->vc_font.height);
+       q = (unsigned short *) (vc->vc_origin +
+                               vc->vc_size_row * rows);
+@@ -666,15 +666,15 @@ static void fbcon_prepare_logo(struct vc
+ #ifdef CONFIG_FB_TILEBLITTING
+ static void set_blitting_type(struct vc_data *vc, struct fb_info *info)
+ {
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+-      ops->p = &fb_display[vc->vc_num];
++      par->p = &fb_display[vc->vc_num];
+       if ((info->flags & FBINFO_MISC_TILEBLITTING))
+               fbcon_set_tileops(vc, info);
+       else {
+               fbcon_set_rotation(info);
+-              fbcon_set_bitops(ops);
++              fbcon_set_bitops(par);
+       }
+ }
+@@ -691,12 +691,12 @@ static int fbcon_invalid_charcount(struc
+ #else
+ static void set_blitting_type(struct vc_data *vc, struct fb_info *info)
+ {
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       info->flags &= ~FBINFO_MISC_TILEBLITTING;
+-      ops->p = &fb_display[vc->vc_num];
++      par->p = &fb_display[vc->vc_num];
+       fbcon_set_rotation(info);
+-      fbcon_set_bitops(ops);
++      fbcon_set_bitops(par);
+ }
+ static int fbcon_invalid_charcount(struct fb_info *info, unsigned charcount)
+@@ -716,13 +716,13 @@ static void fbcon_release(struct fb_info
+       module_put(info->fbops->owner);
+       if (info->fbcon_par) {
+-              struct fbcon_ops *ops = info->fbcon_par;
++              struct fbcon_par *par = info->fbcon_par;
+               fbcon_del_cursor_work(info);
+-              kfree(ops->cursor_state.mask);
+-              kfree(ops->cursor_data);
+-              kfree(ops->cursor_src);
+-              kfree(ops->fontbuffer);
++              kfree(par->cursor_state.mask);
++              kfree(par->cursor_data);
++              kfree(par->cursor_src);
++              kfree(par->fontbuffer);
+               kfree(info->fbcon_par);
+               info->fbcon_par = NULL;
+       }
+@@ -730,7 +730,7 @@ static void fbcon_release(struct fb_info
+ static int fbcon_open(struct fb_info *info)
+ {
+-      struct fbcon_ops *ops;
++      struct fbcon_par *par;
+       if (!try_module_get(info->fbops->owner))
+               return -ENODEV;
+@@ -744,16 +744,16 @@ static int fbcon_open(struct fb_info *in
+       }
+       unlock_fb_info(info);
+-      ops = kzalloc(sizeof(struct fbcon_ops), GFP_KERNEL);
+-      if (!ops) {
++      par = kzalloc(sizeof(*par), GFP_KERNEL);
++      if (!par) {
+               fbcon_release(info);
+               return -ENOMEM;
+       }
+-      INIT_DELAYED_WORK(&ops->cursor_work, fb_flashcursor);
+-      ops->info = info;
+-      info->fbcon_par = ops;
+-      ops->cur_blink_jiffies = HZ / 5;
++      INIT_DELAYED_WORK(&par->cursor_work, fb_flashcursor);
++      par->info = info;
++      info->fbcon_par = par;
++      par->cur_blink_jiffies = HZ / 5;
+       return 0;
+ }
+@@ -800,12 +800,12 @@ static void con2fb_release_oldinfo(struc
+ static void con2fb_init_display(struct vc_data *vc, struct fb_info *info,
+                               int unit, int show_logo)
+ {
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       int ret;
+-      ops->currcon = fg_console;
++      par->currcon = fg_console;
+-      if (info->fbops->fb_set_par && !ops->initialized) {
++      if (info->fbops->fb_set_par && !par->initialized) {
+               ret = info->fbops->fb_set_par(info);
+               if (ret)
+@@ -814,8 +814,8 @@ static void con2fb_init_display(struct v
+                               "error code %d\n", ret);
+       }
+-      ops->initialized = true;
+-      ops->graphics = 0;
++      par->initialized = true;
++      par->graphics = 0;
+       fbcon_set_disp(info, &info->var, unit);
+       if (show_logo) {
+@@ -952,7 +952,7 @@ static const char *fbcon_startup(void)
+       struct vc_data *vc = vc_cons[fg_console].d;
+       const struct font_desc *font = NULL;
+       struct fb_info *info = NULL;
+-      struct fbcon_ops *ops;
++      struct fbcon_par *par;
+       int rows, cols;
+       /*
+@@ -972,10 +972,10 @@ static const char *fbcon_startup(void)
+       if (fbcon_open(info))
+               return NULL;
+-      ops = info->fbcon_par;
+-      ops->currcon = -1;
+-      ops->graphics = 1;
+-      ops->cur_rotate = -1;
++      par = info->fbcon_par;
++      par->currcon = -1;
++      par->graphics = 1;
++      par->cur_rotate = -1;
+       p->con_rotate = initial_rotation;
+       if (p->con_rotate == -1)
+@@ -998,8 +998,8 @@ static const char *fbcon_startup(void)
+               vc->vc_font.charcount = font->charcount;
+       }
+-      cols = FBCON_SWAP(ops->rotate, info->var.xres, info->var.yres);
+-      rows = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres);
++      cols = FBCON_SWAP(par->rotate, info->var.xres, info->var.yres);
++      rows = FBCON_SWAP(par->rotate, info->var.yres, info->var.xres);
+       cols /= vc->vc_font.width;
+       rows /= vc->vc_font.height;
+       vc_resize(vc, cols, rows);
+@@ -1017,7 +1017,7 @@ static const char *fbcon_startup(void)
+ static void fbcon_init(struct vc_data *vc, bool init)
+ {
+       struct fb_info *info;
+-      struct fbcon_ops *ops;
++      struct fbcon_par *par;
+       struct vc_data **default_mode = vc->vc_display_fg;
+       struct vc_data *svc = *default_mode;
+       struct fbcon_display *t, *p = &fb_display[vc->vc_num];
+@@ -1092,8 +1092,8 @@ static void fbcon_init(struct vc_data *v
+       if (!*vc->uni_pagedict_loc)
+               con_copy_unimap(vc, svc);
+-      ops = info->fbcon_par;
+-      ops->cur_blink_jiffies = msecs_to_jiffies(vc->vc_cur_blink_ms);
++      par = info->fbcon_par;
++      par->cur_blink_jiffies = msecs_to_jiffies(vc->vc_cur_blink_ms);
+       p->con_rotate = initial_rotation;
+       if (p->con_rotate == -1)
+@@ -1105,8 +1105,8 @@ static void fbcon_init(struct vc_data *v
+       cols = vc->vc_cols;
+       rows = vc->vc_rows;
+-      new_cols = FBCON_SWAP(ops->rotate, info->var.xres, info->var.yres);
+-      new_rows = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres);
++      new_cols = FBCON_SWAP(par->rotate, info->var.xres, info->var.yres);
++      new_rows = FBCON_SWAP(par->rotate, info->var.yres, info->var.xres);
+       new_cols /= vc->vc_font.width;
+       new_rows /= vc->vc_font.height;
+@@ -1118,7 +1118,7 @@ static void fbcon_init(struct vc_data *v
+        * We need to do it in fbcon_init() to prevent screen corruption.
+        */
+       if (con_is_visible(vc) && vc->vc_mode == KD_TEXT) {
+-              if (info->fbops->fb_set_par && !ops->initialized) {
++              if (info->fbops->fb_set_par && !par->initialized) {
+                       ret = info->fbops->fb_set_par(info);
+                       if (ret)
+@@ -1127,10 +1127,10 @@ static void fbcon_init(struct vc_data *v
+                                       "error code %d\n", ret);
+               }
+-              ops->initialized = true;
++              par->initialized = true;
+       }
+-      ops->graphics = 0;
++      par->graphics = 0;
+ #ifdef CONFIG_FRAMEBUFFER_CONSOLE_LEGACY_ACCELERATION
+       if ((info->flags & FBINFO_HWACCEL_COPYAREA) &&
+@@ -1154,12 +1154,12 @@ static void fbcon_init(struct vc_data *v
+       if (logo)
+               fbcon_prepare_logo(vc, info, cols, rows, new_cols, new_rows);
+-      if (ops->rotate_font && ops->rotate_font(info, vc)) {
+-              ops->rotate = FB_ROTATE_UR;
++      if (par->rotate_font && par->rotate_font(info, vc)) {
++              par->rotate = FB_ROTATE_UR;
+               set_blitting_type(vc, info);
+       }
+-      ops->p = &fb_display[fg_console];
++      par->p = &fb_display[fg_console];
+ }
+ static void fbcon_free_font(struct fbcon_display *p)
+@@ -1197,7 +1197,7 @@ static void fbcon_deinit(struct vc_data
+ {
+       struct fbcon_display *p = &fb_display[vc->vc_num];
+       struct fb_info *info;
+-      struct fbcon_ops *ops;
++      struct fbcon_par *par;
+       int idx;
+       fbcon_free_font(p);
+@@ -1212,15 +1212,15 @@ static void fbcon_deinit(struct vc_data
+       if (!info)
+               goto finished;
+-      ops = info->fbcon_par;
++      par = info->fbcon_par;
+-      if (!ops)
++      if (!par)
+               goto finished;
+       if (con_is_visible(vc))
+               fbcon_del_cursor_work(info);
+-      ops->initialized = false;
++      par->initialized = false;
+ finished:
+       fbcon_free_font(p);
+@@ -1267,7 +1267,7 @@ static void __fbcon_clear(struct vc_data
+                         unsigned int height, unsigned int width)
+ {
+       struct fb_info *info = fbcon_info_from_console(vc->vc_num);
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       int fg, bg;
+       struct fbcon_display *p = &fb_display[vc->vc_num];
+       u_int y_break;
+@@ -1282,7 +1282,7 @@ static void __fbcon_clear(struct vc_data
+               vc->vc_top = 0;
+               /*
+                * If the font dimensions are not an integral of the display
+-               * dimensions then the ops->clear below won't end up clearing
++               * dimensions then the par->clear below won't end up clearing
+                * the margins.  Call clear_margins here in case the logo
+                * bitmap stretched into the margin area.
+                */
+@@ -1296,11 +1296,10 @@ static void __fbcon_clear(struct vc_data
+       y_break = p->vrows - p->yscroll;
+       if (sy < y_break && sy + height - 1 >= y_break) {
+               u_int b = y_break - sy;
+-              ops->clear(vc, info, real_y(p, sy), sx, b, width, fg, bg);
+-              ops->clear(vc, info, real_y(p, sy + b), sx, height - b,
+-                               width, fg, bg);
++              par->clear(vc, info, real_y(p, sy), sx, b, width, fg, bg);
++              par->clear(vc, info, real_y(p, sy + b), sx, height - b, width, fg, bg);
+       } else
+-              ops->clear(vc, info, real_y(p, sy), sx, height, width, fg, bg);
++              par->clear(vc, info, real_y(p, sy), sx, height, width, fg, bg);
+ }
+ static void fbcon_clear(struct vc_data *vc, unsigned int sy, unsigned int sx,
+@@ -1314,10 +1313,10 @@ static void fbcon_putcs(struct vc_data *
+ {
+       struct fb_info *info = fbcon_info_from_console(vc->vc_num);
+       struct fbcon_display *p = &fb_display[vc->vc_num];
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       if (!fbcon_is_inactive(vc, info))
+-              ops->putcs(vc, info, s, count, real_y(p, ypos), xpos,
++              par->putcs(vc, info, s, count, real_y(p, ypos), xpos,
+                          get_color(vc, info, scr_readw(s), 1),
+                          get_color(vc, info, scr_readw(s), 0));
+ }
+@@ -1325,19 +1324,19 @@ static void fbcon_putcs(struct vc_data *
+ static void fbcon_clear_margins(struct vc_data *vc, int bottom_only)
+ {
+       struct fb_info *info = fbcon_info_from_console(vc->vc_num);
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       if (!fbcon_is_inactive(vc, info))
+-              ops->clear_margins(vc, info, margin_color, bottom_only);
++              par->clear_margins(vc, info, margin_color, bottom_only);
+ }
+ static void fbcon_cursor(struct vc_data *vc, bool enable)
+ {
+       struct fb_info *info = fbcon_info_from_console(vc->vc_num);
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       int c = scr_readw((u16 *) vc->vc_pos);
+-      ops->cur_blink_jiffies = msecs_to_jiffies(vc->vc_cur_blink_ms);
++      par->cur_blink_jiffies = msecs_to_jiffies(vc->vc_cur_blink_ms);
+       if (fbcon_is_inactive(vc, info) || vc->vc_deccm != 1)
+               return;
+@@ -1347,12 +1346,12 @@ static void fbcon_cursor(struct vc_data
+       else
+               fbcon_add_cursor_work(info);
+-      ops->cursor_flash = enable;
++      par->cursor_flash = enable;
+-      if (!ops->cursor)
++      if (!par->cursor)
+               return;
+-      ops->cursor(vc, info, enable, get_color(vc, info, c, 1),
++      par->cursor(vc, info, enable, get_color(vc, info, c, 1),
+                   get_color(vc, info, c, 0));
+ }
+@@ -1366,7 +1365,7 @@ static void fbcon_set_disp(struct fb_inf
+       struct fbcon_display *p, *t;
+       struct vc_data **default_mode, *vc;
+       struct vc_data *svc;
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       int rows, cols;
+       unsigned long ret = 0;
+@@ -1399,7 +1398,7 @@ static void fbcon_set_disp(struct fb_inf
+       var->yoffset = info->var.yoffset;
+       var->xoffset = info->var.xoffset;
+       fb_set_var(info, var);
+-      ops->var = info->var;
++      par->var = info->var;
+       vc->vc_can_do_color = (fb_get_color_depth(&info->var, &info->fix)!=1);
+       vc->vc_complement_mask = vc->vc_can_do_color ? 0x7700 : 0x0800;
+       if (vc->vc_font.charcount == 256) {
+@@ -1415,8 +1414,8 @@ static void fbcon_set_disp(struct fb_inf
+       if (!*vc->uni_pagedict_loc)
+               con_copy_unimap(vc, svc);
+-      cols = FBCON_SWAP(ops->rotate, info->var.xres, info->var.yres);
+-      rows = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres);
++      cols = FBCON_SWAP(par->rotate, info->var.xres, info->var.yres);
++      rows = FBCON_SWAP(par->rotate, info->var.yres, info->var.xres);
+       cols /= vc->vc_font.width;
+       rows /= vc->vc_font.height;
+       ret = vc_resize(vc, cols, rows);
+@@ -1428,16 +1427,16 @@ static void fbcon_set_disp(struct fb_inf
+ static __inline__ void ywrap_up(struct vc_data *vc, int count)
+ {
+       struct fb_info *info = fbcon_info_from_console(vc->vc_num);
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       struct fbcon_display *p = &fb_display[vc->vc_num];
+       p->yscroll += count;
+       if (p->yscroll >= p->vrows)     /* Deal with wrap */
+               p->yscroll -= p->vrows;
+-      ops->var.xoffset = 0;
+-      ops->var.yoffset = p->yscroll * vc->vc_font.height;
+-      ops->var.vmode |= FB_VMODE_YWRAP;
+-      ops->update_start(info);
++      par->var.xoffset = 0;
++      par->var.yoffset = p->yscroll * vc->vc_font.height;
++      par->var.vmode |= FB_VMODE_YWRAP;
++      par->update_start(info);
+       scrollback_max += count;
+       if (scrollback_max > scrollback_phys_max)
+               scrollback_max = scrollback_phys_max;
+@@ -1447,16 +1446,16 @@ static __inline__ void ywrap_up(struct v
+ static __inline__ void ywrap_down(struct vc_data *vc, int count)
+ {
+       struct fb_info *info = fbcon_info_from_console(vc->vc_num);
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       struct fbcon_display *p = &fb_display[vc->vc_num];
+       p->yscroll -= count;
+       if (p->yscroll < 0)     /* Deal with wrap */
+               p->yscroll += p->vrows;
+-      ops->var.xoffset = 0;
+-      ops->var.yoffset = p->yscroll * vc->vc_font.height;
+-      ops->var.vmode |= FB_VMODE_YWRAP;
+-      ops->update_start(info);
++      par->var.xoffset = 0;
++      par->var.yoffset = p->yscroll * vc->vc_font.height;
++      par->var.vmode |= FB_VMODE_YWRAP;
++      par->update_start(info);
+       scrollback_max -= count;
+       if (scrollback_max < 0)
+               scrollback_max = 0;
+@@ -1467,19 +1466,19 @@ static __inline__ void ypan_up(struct vc
+ {
+       struct fb_info *info = fbcon_info_from_console(vc->vc_num);
+       struct fbcon_display *p = &fb_display[vc->vc_num];
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       p->yscroll += count;
+       if (p->yscroll > p->vrows - vc->vc_rows) {
+-              ops->bmove(vc, info, p->vrows - vc->vc_rows,
++              par->bmove(vc, info, p->vrows - vc->vc_rows,
+                           0, 0, 0, vc->vc_rows, vc->vc_cols);
+               p->yscroll -= p->vrows - vc->vc_rows;
+       }
+-      ops->var.xoffset = 0;
+-      ops->var.yoffset = p->yscroll * vc->vc_font.height;
+-      ops->var.vmode &= ~FB_VMODE_YWRAP;
+-      ops->update_start(info);
++      par->var.xoffset = 0;
++      par->var.yoffset = p->yscroll * vc->vc_font.height;
++      par->var.vmode &= ~FB_VMODE_YWRAP;
++      par->update_start(info);
+       fbcon_clear_margins(vc, 1);
+       scrollback_max += count;
+       if (scrollback_max > scrollback_phys_max)
+@@ -1490,7 +1489,7 @@ static __inline__ void ypan_up(struct vc
+ static __inline__ void ypan_up_redraw(struct vc_data *vc, int t, int count)
+ {
+       struct fb_info *info = fbcon_info_from_console(vc->vc_num);
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       struct fbcon_display *p = &fb_display[vc->vc_num];
+       p->yscroll += count;
+@@ -1500,10 +1499,10 @@ static __inline__ void ypan_up_redraw(st
+               fbcon_redraw_move(vc, p, t + count, vc->vc_rows - count, t);
+       }
+-      ops->var.xoffset = 0;
+-      ops->var.yoffset = p->yscroll * vc->vc_font.height;
+-      ops->var.vmode &= ~FB_VMODE_YWRAP;
+-      ops->update_start(info);
++      par->var.xoffset = 0;
++      par->var.yoffset = p->yscroll * vc->vc_font.height;
++      par->var.vmode &= ~FB_VMODE_YWRAP;
++      par->update_start(info);
+       fbcon_clear_margins(vc, 1);
+       scrollback_max += count;
+       if (scrollback_max > scrollback_phys_max)
+@@ -1515,19 +1514,19 @@ static __inline__ void ypan_down(struct
+ {
+       struct fb_info *info = fbcon_info_from_console(vc->vc_num);
+       struct fbcon_display *p = &fb_display[vc->vc_num];
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       p->yscroll -= count;
+       if (p->yscroll < 0) {
+-              ops->bmove(vc, info, 0, 0, p->vrows - vc->vc_rows,
++              par->bmove(vc, info, 0, 0, p->vrows - vc->vc_rows,
+                           0, vc->vc_rows, vc->vc_cols);
+               p->yscroll += p->vrows - vc->vc_rows;
+       }
+-      ops->var.xoffset = 0;
+-      ops->var.yoffset = p->yscroll * vc->vc_font.height;
+-      ops->var.vmode &= ~FB_VMODE_YWRAP;
+-      ops->update_start(info);
++      par->var.xoffset = 0;
++      par->var.yoffset = p->yscroll * vc->vc_font.height;
++      par->var.vmode &= ~FB_VMODE_YWRAP;
++      par->update_start(info);
+       fbcon_clear_margins(vc, 1);
+       scrollback_max -= count;
+       if (scrollback_max < 0)
+@@ -1538,7 +1537,7 @@ static __inline__ void ypan_down(struct
+ static __inline__ void ypan_down_redraw(struct vc_data *vc, int t, int count)
+ {
+       struct fb_info *info = fbcon_info_from_console(vc->vc_num);
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       struct fbcon_display *p = &fb_display[vc->vc_num];
+       p->yscroll -= count;
+@@ -1548,10 +1547,10 @@ static __inline__ void ypan_down_redraw(
+               fbcon_redraw_move(vc, p, t, vc->vc_rows - count, t + count);
+       }
+-      ops->var.xoffset = 0;
+-      ops->var.yoffset = p->yscroll * vc->vc_font.height;
+-      ops->var.vmode &= ~FB_VMODE_YWRAP;
+-      ops->update_start(info);
++      par->var.xoffset = 0;
++      par->var.yoffset = p->yscroll * vc->vc_font.height;
++      par->var.vmode &= ~FB_VMODE_YWRAP;
++      par->update_start(info);
+       fbcon_clear_margins(vc, 1);
+       scrollback_max -= count;
+       if (scrollback_max < 0)
+@@ -1600,7 +1599,7 @@ static void fbcon_redraw_blit(struct vc_
+       unsigned short *d = (unsigned short *)
+           (vc->vc_origin + vc->vc_size_row * line);
+       unsigned short *s = d + offset;
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       while (count--) {
+               unsigned short *start = s;
+@@ -1613,8 +1612,8 @@ static void fbcon_redraw_blit(struct vc_
+                       if (c == scr_readw(d)) {
+                               if (s > start) {
+-                                      ops->bmove(vc, info, line + ycount, x,
+-                                                 line, x, 1, s-start);
++                                      par->bmove(vc, info, line + ycount, x,
++                                                 line, x, 1, s - start);
+                                       x += s - start + 1;
+                                       start = s + 1;
+                               } else {
+@@ -1629,8 +1628,7 @@ static void fbcon_redraw_blit(struct vc_
+                       d++;
+               } while (s < le);
+               if (s > start)
+-                      ops->bmove(vc, info, line + ycount, x, line, x, 1,
+-                                 s-start);
++                      par->bmove(vc, info, line + ycount, x, line, x, 1, s - start);
+               console_conditional_schedule();
+               if (ycount > 0)
+                       line++;
+@@ -1701,7 +1699,7 @@ static void fbcon_bmove_rec(struct vc_da
+                           int dy, int dx, int height, int width, u_int y_break)
+ {
+       struct fb_info *info = fbcon_info_from_console(vc->vc_num);
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       u_int b;
+       if (sy < y_break && sy + height > y_break) {
+@@ -1735,8 +1733,7 @@ static void fbcon_bmove_rec(struct vc_da
+               }
+               return;
+       }
+-      ops->bmove(vc, info, real_y(p, sy), sx, real_y(p, dy), dx,
+-                 height, width);
++      par->bmove(vc, info, real_y(p, sy), sx, real_y(p, dy), dx, height, width);
+ }
+ static void fbcon_bmove(struct vc_data *vc, int sy, int sx, int dy, int dx,
+@@ -1963,15 +1960,13 @@ static void updatescrollmode_accel(struc
+                                       struct vc_data *vc)
+ {
+ #ifdef CONFIG_FRAMEBUFFER_CONSOLE_LEGACY_ACCELERATION
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       int cap = info->flags;
+       u16 t = 0;
+-      int ypan = FBCON_SWAP(ops->rotate, info->fix.ypanstep,
+-                                info->fix.xpanstep);
+-      int ywrap = FBCON_SWAP(ops->rotate, info->fix.ywrapstep, t);
+-      int yres = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres);
+-      int vyres = FBCON_SWAP(ops->rotate, info->var.yres_virtual,
+-                                 info->var.xres_virtual);
++      int ypan = FBCON_SWAP(par->rotate, info->fix.ypanstep, info->fix.xpanstep);
++      int ywrap = FBCON_SWAP(par->rotate, info->fix.ywrapstep, t);
++      int yres = FBCON_SWAP(par->rotate, info->var.yres, info->var.xres);
++      int vyres = FBCON_SWAP(par->rotate, info->var.yres_virtual, info->var.xres_virtual);
+       int good_pan = (cap & FBINFO_HWACCEL_YPAN) &&
+               divides(ypan, vc->vc_font.height) && vyres > yres;
+       int good_wrap = (cap & FBINFO_HWACCEL_YWRAP) &&
+@@ -2004,11 +1999,10 @@ static void updatescrollmode(struct fbco
+                                       struct fb_info *info,
+                                       struct vc_data *vc)
+ {
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       int fh = vc->vc_font.height;
+-      int yres = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres);
+-      int vyres = FBCON_SWAP(ops->rotate, info->var.yres_virtual,
+-                                 info->var.xres_virtual);
++      int yres = FBCON_SWAP(par->rotate, info->var.yres, info->var.xres);
++      int vyres = FBCON_SWAP(par->rotate, info->var.yres_virtual, info->var.xres_virtual);
+       p->vrows = vyres/fh;
+       if (yres > (fh * (vc->vc_rows + 1)))
+@@ -2027,7 +2021,7 @@ static int fbcon_resize(struct vc_data *
+                       unsigned int height, bool from_user)
+ {
+       struct fb_info *info = fbcon_info_from_console(vc->vc_num);
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       struct fbcon_display *p = &fb_display[vc->vc_num];
+       struct fb_var_screeninfo var = info->var;
+       int x_diff, y_diff, virt_w, virt_h, virt_fw, virt_fh;
+@@ -2050,12 +2044,10 @@ static int fbcon_resize(struct vc_data *
+                       return -EINVAL;
+       }
+-      virt_w = FBCON_SWAP(ops->rotate, width, height);
+-      virt_h = FBCON_SWAP(ops->rotate, height, width);
+-      virt_fw = FBCON_SWAP(ops->rotate, vc->vc_font.width,
+-                               vc->vc_font.height);
+-      virt_fh = FBCON_SWAP(ops->rotate, vc->vc_font.height,
+-                               vc->vc_font.width);
++      virt_w = FBCON_SWAP(par->rotate, width, height);
++      virt_h = FBCON_SWAP(par->rotate, height, width);
++      virt_fw = FBCON_SWAP(par->rotate, vc->vc_font.width, vc->vc_font.height);
++      virt_fh = FBCON_SWAP(par->rotate, vc->vc_font.height, vc->vc_font.width);
+       var.xres = virt_w * virt_fw;
+       var.yres = virt_h * virt_fh;
+       x_diff = info->var.xres - var.xres;
+@@ -2081,7 +2073,7 @@ static int fbcon_resize(struct vc_data *
+                       fb_set_var(info, &var);
+               }
+               var_to_display(p, &info->var, info);
+-              ops->var = info->var;
++              par->var = info->var;
+       }
+       updatescrollmode(p, info, vc);
+       return 0;
+@@ -2090,13 +2082,13 @@ static int fbcon_resize(struct vc_data *
+ static bool fbcon_switch(struct vc_data *vc)
+ {
+       struct fb_info *info, *old_info = NULL;
+-      struct fbcon_ops *ops;
++      struct fbcon_par *par;
+       struct fbcon_display *p = &fb_display[vc->vc_num];
+       struct fb_var_screeninfo var;
+       int i, ret, prev_console;
+       info = fbcon_info_from_console(vc->vc_num);
+-      ops = info->fbcon_par;
++      par = info->fbcon_par;
+       if (logo_shown >= 0) {
+               struct vc_data *conp2 = vc_cons[logo_shown].d;
+@@ -2107,7 +2099,7 @@ static bool fbcon_switch(struct vc_data
+               logo_shown = FBCON_LOGO_CANSHOW;
+       }
+-      prev_console = ops->currcon;
++      prev_console = par->currcon;
+       if (prev_console != -1)
+               old_info = fbcon_info_from_console(prev_console);
+       /*
+@@ -2120,9 +2112,9 @@ static bool fbcon_switch(struct vc_data
+        */
+       fbcon_for_each_registered_fb(i) {
+               if (fbcon_registered_fb[i]->fbcon_par) {
+-                      struct fbcon_ops *o = fbcon_registered_fb[i]->fbcon_par;
++                      struct fbcon_par *par = fbcon_registered_fb[i]->fbcon_par;
+-                      o->currcon = vc->vc_num;
++                      par->currcon = vc->vc_num;
+               }
+       }
+       memset(&var, 0, sizeof(struct fb_var_screeninfo));
+@@ -2136,7 +2128,7 @@ static bool fbcon_switch(struct vc_data
+       info->var.activate = var.activate;
+       var.vmode |= info->var.vmode & ~FB_VMODE_MASK;
+       fb_set_var(info, &var);
+-      ops->var = info->var;
++      par->var = info->var;
+       if (old_info != NULL && (old_info != info ||
+                                info->flags & FBINFO_MISC_ALWAYS_SETPAR)) {
+@@ -2154,16 +2146,16 @@ static bool fbcon_switch(struct vc_data
+       }
+       if (fbcon_is_inactive(vc, info) ||
+-          ops->blank_state != FB_BLANK_UNBLANK)
++          par->blank_state != FB_BLANK_UNBLANK)
+               fbcon_del_cursor_work(info);
+       else
+               fbcon_add_cursor_work(info);
+       set_blitting_type(vc, info);
+-      ops->cursor_reset = 1;
++      par->cursor_reset = 1;
+-      if (ops->rotate_font && ops->rotate_font(info, vc)) {
+-              ops->rotate = FB_ROTATE_UR;
++      if (par->rotate_font && par->rotate_font(info, vc)) {
++              par->rotate = FB_ROTATE_UR;
+               set_blitting_type(vc, info);
+       }
+@@ -2194,8 +2186,8 @@ static bool fbcon_switch(struct vc_data
+       scrollback_current = 0;
+       if (!fbcon_is_inactive(vc, info)) {
+-          ops->var.xoffset = ops->var.yoffset = p->yscroll = 0;
+-          ops->update_start(info);
++              par->var.xoffset = par->var.yoffset = p->yscroll = 0;
++              par->update_start(info);
+       }
+       fbcon_set_palette(vc, color_table);
+@@ -2204,7 +2196,7 @@ static bool fbcon_switch(struct vc_data
+       if (logo_shown == FBCON_LOGO_DRAW) {
+               logo_shown = fg_console;
+-              fb_show_logo(info, ops->rotate);
++              fb_show_logo(info, par->rotate);
+               update_region(vc,
+                             vc->vc_origin + vc->vc_size_row * vc->vc_top,
+                             vc->vc_size_row * (vc->vc_bottom -
+@@ -2233,27 +2225,27 @@ static bool fbcon_blank(struct vc_data *
+                       bool mode_switch)
+ {
+       struct fb_info *info = fbcon_info_from_console(vc->vc_num);
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       if (mode_switch) {
+               struct fb_var_screeninfo var = info->var;
+-              ops->graphics = 1;
++              par->graphics = 1;
+               if (!blank) {
+                       var.activate = FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE |
+                               FB_ACTIVATE_KD_TEXT;
+                       fb_set_var(info, &var);
+-                      ops->graphics = 0;
+-                      ops->var = info->var;
++                      par->graphics = 0;
++                      par->var = info->var;
+               }
+       }
+       if (!fbcon_is_inactive(vc, info)) {
+-              if (ops->blank_state != blank) {
+-                      ops->blank_state = blank;
++              if (par->blank_state != blank) {
++                      par->blank_state = blank;
+                       fbcon_cursor(vc, !blank);
+-                      ops->cursor_flash = (!blank);
++                      par->cursor_flash = (!blank);
+                       if (fb_blank(info, blank))
+                               fbcon_generic_blank(vc, info, blank);
+@@ -2264,7 +2256,7 @@ static bool fbcon_blank(struct vc_data *
+       }
+       if (mode_switch || fbcon_is_inactive(vc, info) ||
+-          ops->blank_state != FB_BLANK_UNBLANK)
++          par->blank_state != FB_BLANK_UNBLANK)
+               fbcon_del_cursor_work(info);
+       else
+               fbcon_add_cursor_work(info);
+@@ -2275,10 +2267,10 @@ static bool fbcon_blank(struct vc_data *
+ static void fbcon_debug_enter(struct vc_data *vc)
+ {
+       struct fb_info *info = fbcon_info_from_console(vc->vc_num);
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+-      ops->save_graphics = ops->graphics;
+-      ops->graphics = 0;
++      par->save_graphics = par->graphics;
++      par->graphics = 0;
+       if (info->fbops->fb_debug_enter)
+               info->fbops->fb_debug_enter(info);
+       fbcon_set_palette(vc, color_table);
+@@ -2287,9 +2279,9 @@ static void fbcon_debug_enter(struct vc_
+ static void fbcon_debug_leave(struct vc_data *vc)
+ {
+       struct fb_info *info = fbcon_info_from_console(vc->vc_num);
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+-      ops->graphics = ops->save_graphics;
++      par->graphics = par->save_graphics;
+       if (info->fbops->fb_debug_leave)
+               info->fbops->fb_debug_leave(info);
+ }
+@@ -2424,7 +2416,7 @@ static int fbcon_do_set_font(struct vc_d
+                            const u8 * data, int userfont)
+ {
+       struct fb_info *info = fbcon_info_from_console(vc->vc_num);
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       struct fbcon_display *p = &fb_display[vc->vc_num];
+       int resize, ret, old_userfont, old_width, old_height, old_charcount;
+       u8 *old_data = vc->vc_font.data;
+@@ -2451,8 +2443,8 @@ static int fbcon_do_set_font(struct vc_d
+       if (resize) {
+               int cols, rows;
+-              cols = FBCON_SWAP(ops->rotate, info->var.xres, info->var.yres);
+-              rows = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres);
++              cols = FBCON_SWAP(par->rotate, info->var.xres, info->var.yres);
++              rows = FBCON_SWAP(par->rotate, info->var.yres, info->var.xres);
+               cols /= w;
+               rows /= h;
+               ret = vc_resize(vc, cols, rows);
+@@ -2657,11 +2649,11 @@ static void fbcon_invert_region(struct v
+ void fbcon_suspended(struct fb_info *info)
+ {
+       struct vc_data *vc = NULL;
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+-      if (!ops || ops->currcon < 0)
++      if (!par || par->currcon < 0)
+               return;
+-      vc = vc_cons[ops->currcon].d;
++      vc = vc_cons[par->currcon].d;
+       /* Clear cursor, restore saved data */
+       fbcon_cursor(vc, false);
+@@ -2670,27 +2662,27 @@ void fbcon_suspended(struct fb_info *inf
+ void fbcon_resumed(struct fb_info *info)
+ {
+       struct vc_data *vc;
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+-      if (!ops || ops->currcon < 0)
++      if (!par || par->currcon < 0)
+               return;
+-      vc = vc_cons[ops->currcon].d;
++      vc = vc_cons[par->currcon].d;
+       update_screen(vc);
+ }
+ static void fbcon_modechanged(struct fb_info *info)
+ {
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       struct vc_data *vc;
+       struct fbcon_display *p;
+       int rows, cols;
+-      if (!ops || ops->currcon < 0)
++      if (!par || par->currcon < 0)
+               return;
+-      vc = vc_cons[ops->currcon].d;
++      vc = vc_cons[par->currcon].d;
+       if (vc->vc_mode != KD_TEXT ||
+-          fbcon_info_from_console(ops->currcon) != info)
++          fbcon_info_from_console(par->currcon) != info)
+               return;
+       p = &fb_display[vc->vc_num];
+@@ -2698,8 +2690,8 @@ static void fbcon_modechanged(struct fb_
+       if (con_is_visible(vc)) {
+               var_to_display(p, &info->var, info);
+-              cols = FBCON_SWAP(ops->rotate, info->var.xres, info->var.yres);
+-              rows = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres);
++              cols = FBCON_SWAP(par->rotate, info->var.xres, info->var.yres);
++              rows = FBCON_SWAP(par->rotate, info->var.yres, info->var.xres);
+               cols /= vc->vc_font.width;
+               rows /= vc->vc_font.height;
+               vc_resize(vc, cols, rows);
+@@ -2708,8 +2700,8 @@ static void fbcon_modechanged(struct fb_
+               scrollback_current = 0;
+               if (!fbcon_is_inactive(vc, info)) {
+-                  ops->var.xoffset = ops->var.yoffset = p->yscroll = 0;
+-                  ops->update_start(info);
++                      par->var.xoffset = par->var.yoffset = p->yscroll = 0;
++                      par->update_start(info);
+               }
+               fbcon_set_palette(vc, color_table);
+@@ -2719,12 +2711,12 @@ static void fbcon_modechanged(struct fb_
+ static void fbcon_set_all_vcs(struct fb_info *info)
+ {
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       struct vc_data *vc;
+       struct fbcon_display *p;
+       int i, rows, cols, fg = -1;
+-      if (!ops || ops->currcon < 0)
++      if (!par || par->currcon < 0)
+               return;
+       for (i = first_fb_vc; i <= last_fb_vc; i++) {
+@@ -2741,8 +2733,8 @@ static void fbcon_set_all_vcs(struct fb_
+               p = &fb_display[vc->vc_num];
+               set_blitting_type(vc, info);
+               var_to_display(p, &info->var, info);
+-              cols = FBCON_SWAP(ops->rotate, info->var.xres, info->var.yres);
+-              rows = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres);
++              cols = FBCON_SWAP(par->rotate, info->var.xres, info->var.yres);
++              rows = FBCON_SWAP(par->rotate, info->var.yres, info->var.xres);
+               cols /= vc->vc_font.width;
+               rows /= vc->vc_font.height;
+               vc_resize(vc, cols, rows);
+@@ -2765,13 +2757,13 @@ EXPORT_SYMBOL(fbcon_update_vcs);
+ /* let fbcon check if it supports a new screen resolution */
+ int fbcon_modechange_possible(struct fb_info *info, struct fb_var_screeninfo *var)
+ {
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       struct vc_data *vc;
+       unsigned int i;
+       WARN_CONSOLE_UNLOCKED();
+-      if (!ops)
++      if (!par)
+               return 0;
+       /* prevent setting a screen size which is smaller than font size */
+@@ -3069,15 +3061,14 @@ int fbcon_fb_registered(struct fb_info *
+ void fbcon_fb_blanked(struct fb_info *info, int blank)
+ {
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       struct vc_data *vc;
+-      if (!ops || ops->currcon < 0)
++      if (!par || par->currcon < 0)
+               return;
+-      vc = vc_cons[ops->currcon].d;
+-      if (vc->vc_mode != KD_TEXT ||
+-                      fbcon_info_from_console(ops->currcon) != info)
++      vc = vc_cons[par->currcon].d;
++      if (vc->vc_mode != KD_TEXT || fbcon_info_from_console(par->currcon) != info)
+               return;
+       if (con_is_visible(vc)) {
+@@ -3086,7 +3077,7 @@ void fbcon_fb_blanked(struct fb_info *in
+               else
+                       do_unblank_screen(0);
+       }
+-      ops->blank_state = blank;
++      par->blank_state = blank;
+ }
+ void fbcon_new_modelist(struct fb_info *info)
+@@ -3276,7 +3267,7 @@ static ssize_t show_cursor_blink(struct
+                                struct device_attribute *attr, char *buf)
+ {
+       struct fb_info *info;
+-      struct fbcon_ops *ops;
++      struct fbcon_par *par;
+       int idx, blink = -1;
+       console_lock();
+@@ -3286,12 +3277,12 @@ static ssize_t show_cursor_blink(struct
+               goto err;
+       info = fbcon_registered_fb[idx];
+-      ops = info->fbcon_par;
++      par = info->fbcon_par;
+-      if (!ops)
++      if (!par)
+               goto err;
+-      blink = delayed_work_pending(&ops->cursor_work);
++      blink = delayed_work_pending(&par->cursor_work);
+ err:
+       console_unlock();
+       return sysfs_emit(buf, "%d\n", blink);
+--- a/drivers/video/fbdev/core/fbcon.h
++++ b/drivers/video/fbdev/core/fbcon.h
+@@ -50,7 +50,7 @@ struct fbcon_display {
+     const struct fb_videomode *mode;
+ };
+-struct fbcon_ops {
++struct fbcon_par {
+       void (*bmove)(struct vc_data *vc, struct fb_info *info, int sy,
+                     int sx, int dy, int dx, int height, int width);
+       void (*clear)(struct vc_data *vc, struct fb_info *info, int sy,
+@@ -185,7 +185,7 @@ static inline u_short fb_scrollmode(stru
+ #ifdef CONFIG_FB_TILEBLITTING
+ extern void fbcon_set_tileops(struct vc_data *vc, struct fb_info *info);
+ #endif
+-extern void fbcon_set_bitops(struct fbcon_ops *ops);
++extern void fbcon_set_bitops(struct fbcon_par *par);
+ extern int  soft_cursor(struct fb_info *info, struct fb_cursor *cursor);
+ #define FBCON_ATTRIBUTE_UNDERLINE 1
+@@ -224,7 +224,7 @@ static inline int get_attribute(struct f
+         (i == FB_ROTATE_UR || i == FB_ROTATE_UD) ? _r : _v; })
+ #ifdef CONFIG_FRAMEBUFFER_CONSOLE_ROTATION
+-extern void fbcon_set_rotate(struct fbcon_ops *ops);
++extern void fbcon_set_rotate(struct fbcon_par *par);
+ #else
+ #define fbcon_set_rotate(x) do {} while(0)
+ #endif /* CONFIG_FRAMEBUFFER_CONSOLE_ROTATION */
+--- a/drivers/video/fbdev/core/fbcon_ccw.c
++++ b/drivers/video/fbdev/core/fbcon_ccw.c
+@@ -63,9 +63,9 @@ static void ccw_update_attr(u8 *dst, u8
+ static void ccw_bmove(struct vc_data *vc, struct fb_info *info, int sy,
+                    int sx, int dy, int dx, int height, int width)
+ {
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       struct fb_copyarea area;
+-      u32 vyres = GETVYRES(ops->p, info);
++      u32 vyres = GETVYRES(par->p, info);
+       area.sx = sy * vc->vc_font.height;
+       area.sy = vyres - ((sx + width) * vc->vc_font.width);
+@@ -80,9 +80,9 @@ static void ccw_bmove(struct vc_data *vc
+ static void ccw_clear(struct vc_data *vc, struct fb_info *info, int sy,
+                    int sx, int height, int width, int fg, int bg)
+ {
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       struct fb_fillrect region;
+-      u32 vyres = GETVYRES(ops->p, info);
++      u32 vyres = GETVYRES(par->p, info);
+       region.color = bg;
+       region.dx = sy * vc->vc_font.height;
+@@ -99,13 +99,13 @@ static inline void ccw_putcs_aligned(str
+                                   u32 d_pitch, u32 s_pitch, u32 cellsize,
+                                   struct fb_image *image, u8 *buf, u8 *dst)
+ {
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       u16 charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff;
+       u32 idx = (vc->vc_font.height + 7) >> 3;
+       u8 *src;
+       while (cnt--) {
+-              src = ops->fontbuffer + (scr_readw(s--) & charmask)*cellsize;
++              src = par->fontbuffer + (scr_readw(s--) & charmask) * cellsize;
+               if (attr) {
+                       ccw_update_attr(buf, src, attr, vc);
+@@ -130,7 +130,7 @@ static void ccw_putcs(struct vc_data *vc
+                     int fg, int bg)
+ {
+       struct fb_image image;
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       u32 width = (vc->vc_font.height + 7)/8;
+       u32 cellsize = width * vc->vc_font.width;
+       u32 maxcnt = info->pixmap.size/cellsize;
+@@ -139,9 +139,9 @@ static void ccw_putcs(struct vc_data *vc
+       u32 cnt, pitch, size;
+       u32 attribute = get_attribute(info, scr_readw(s));
+       u8 *dst, *buf = NULL;
+-      u32 vyres = GETVYRES(ops->p, info);
++      u32 vyres = GETVYRES(par->p, info);
+-      if (!ops->fontbuffer)
++      if (!par->fontbuffer)
+               return;
+       image.fg_color = fg;
+@@ -221,28 +221,28 @@ static void ccw_cursor(struct vc_data *v
+                      int fg, int bg)
+ {
+       struct fb_cursor cursor;
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       unsigned short charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff;
+       int w = (vc->vc_font.height + 7) >> 3, c;
+-      int y = real_y(ops->p, vc->state.y);
++      int y = real_y(par->p, vc->state.y);
+       int attribute, use_sw = vc->vc_cursor_type & CUR_SW;
+       int err = 1, dx, dy;
+       char *src;
+-      u32 vyres = GETVYRES(ops->p, info);
++      u32 vyres = GETVYRES(par->p, info);
+-      if (!ops->fontbuffer)
++      if (!par->fontbuffer)
+               return;
+       cursor.set = 0;
+       c = scr_readw((u16 *) vc->vc_pos);
+       attribute = get_attribute(info, c);
+-      src = ops->fontbuffer + ((c & charmask) * (w * vc->vc_font.width));
++      src = par->fontbuffer + ((c & charmask) * (w * vc->vc_font.width));
+-      if (ops->cursor_state.image.data != src ||
+-          ops->cursor_reset) {
+-          ops->cursor_state.image.data = src;
+-          cursor.set |= FB_CUR_SETIMAGE;
++      if (par->cursor_state.image.data != src ||
++          par->cursor_reset) {
++              par->cursor_state.image.data = src;
++              cursor.set |= FB_CUR_SETIMAGE;
+       }
+       if (attribute) {
+@@ -251,49 +251,49 @@ static void ccw_cursor(struct vc_data *v
+               dst = kmalloc_array(w, vc->vc_font.width, GFP_ATOMIC);
+               if (!dst)
+                       return;
+-              kfree(ops->cursor_data);
+-              ops->cursor_data = dst;
++              kfree(par->cursor_data);
++              par->cursor_data = dst;
+               ccw_update_attr(dst, src, attribute, vc);
+               src = dst;
+       }
+-      if (ops->cursor_state.image.fg_color != fg ||
+-          ops->cursor_state.image.bg_color != bg ||
+-          ops->cursor_reset) {
+-              ops->cursor_state.image.fg_color = fg;
+-              ops->cursor_state.image.bg_color = bg;
++      if (par->cursor_state.image.fg_color != fg ||
++          par->cursor_state.image.bg_color != bg ||
++          par->cursor_reset) {
++              par->cursor_state.image.fg_color = fg;
++              par->cursor_state.image.bg_color = bg;
+               cursor.set |= FB_CUR_SETCMAP;
+       }
+-      if (ops->cursor_state.image.height != vc->vc_font.width ||
+-          ops->cursor_state.image.width != vc->vc_font.height ||
+-          ops->cursor_reset) {
+-              ops->cursor_state.image.height = vc->vc_font.width;
+-              ops->cursor_state.image.width = vc->vc_font.height;
++      if (par->cursor_state.image.height != vc->vc_font.width ||
++          par->cursor_state.image.width != vc->vc_font.height ||
++          par->cursor_reset) {
++              par->cursor_state.image.height = vc->vc_font.width;
++              par->cursor_state.image.width = vc->vc_font.height;
+               cursor.set |= FB_CUR_SETSIZE;
+       }
+       dx = y * vc->vc_font.height;
+       dy = vyres - ((vc->state.x + 1) * vc->vc_font.width);
+-      if (ops->cursor_state.image.dx != dx ||
+-          ops->cursor_state.image.dy != dy ||
+-          ops->cursor_reset) {
+-              ops->cursor_state.image.dx = dx;
+-              ops->cursor_state.image.dy = dy;
++      if (par->cursor_state.image.dx != dx ||
++          par->cursor_state.image.dy != dy ||
++          par->cursor_reset) {
++              par->cursor_state.image.dx = dx;
++              par->cursor_state.image.dy = dy;
+               cursor.set |= FB_CUR_SETPOS;
+       }
+-      if (ops->cursor_state.hot.x || ops->cursor_state.hot.y ||
+-          ops->cursor_reset) {
+-              ops->cursor_state.hot.x = cursor.hot.y = 0;
++      if (par->cursor_state.hot.x || par->cursor_state.hot.y ||
++          par->cursor_reset) {
++              par->cursor_state.hot.x = cursor.hot.y = 0;
+               cursor.set |= FB_CUR_SETHOT;
+       }
+       if (cursor.set & FB_CUR_SETSIZE ||
+-          vc->vc_cursor_type != ops->p->cursor_shape ||
+-          ops->cursor_state.mask == NULL ||
+-          ops->cursor_reset) {
++          vc->vc_cursor_type != par->p->cursor_shape ||
++          par->cursor_state.mask == NULL ||
++          par->cursor_reset) {
+               char *tmp, *mask = kmalloc_array(w, vc->vc_font.width,
+                                                GFP_ATOMIC);
+               int cur_height, size, i = 0;
+@@ -309,13 +309,13 @@ static void ccw_cursor(struct vc_data *v
+                       return;
+               }
+-              kfree(ops->cursor_state.mask);
+-              ops->cursor_state.mask = mask;
++              kfree(par->cursor_state.mask);
++              par->cursor_state.mask = mask;
+-              ops->p->cursor_shape = vc->vc_cursor_type;
++              par->p->cursor_shape = vc->vc_cursor_type;
+               cursor.set |= FB_CUR_SETSHAPE;
+-              switch (CUR_SIZE(ops->p->cursor_shape)) {
++              switch (CUR_SIZE(par->p->cursor_shape)) {
+               case CUR_NONE:
+                       cur_height = 0;
+                       break;
+@@ -348,19 +348,19 @@ static void ccw_cursor(struct vc_data *v
+               kfree(tmp);
+       }
+-      ops->cursor_state.enable = enable && !use_sw;
++      par->cursor_state.enable = enable && !use_sw;
+       cursor.image.data = src;
+-      cursor.image.fg_color = ops->cursor_state.image.fg_color;
+-      cursor.image.bg_color = ops->cursor_state.image.bg_color;
+-      cursor.image.dx = ops->cursor_state.image.dx;
+-      cursor.image.dy = ops->cursor_state.image.dy;
+-      cursor.image.height = ops->cursor_state.image.height;
+-      cursor.image.width = ops->cursor_state.image.width;
+-      cursor.hot.x = ops->cursor_state.hot.x;
+-      cursor.hot.y = ops->cursor_state.hot.y;
+-      cursor.mask = ops->cursor_state.mask;
+-      cursor.enable = ops->cursor_state.enable;
++      cursor.image.fg_color = par->cursor_state.image.fg_color;
++      cursor.image.bg_color = par->cursor_state.image.bg_color;
++      cursor.image.dx = par->cursor_state.image.dx;
++      cursor.image.dy = par->cursor_state.image.dy;
++      cursor.image.height = par->cursor_state.image.height;
++      cursor.image.width = par->cursor_state.image.width;
++      cursor.hot.x = par->cursor_state.hot.x;
++      cursor.hot.y = par->cursor_state.hot.y;
++      cursor.mask = par->cursor_state.mask;
++      cursor.enable = par->cursor_state.enable;
+       cursor.image.depth = 1;
+       cursor.rop = ROP_XOR;
+@@ -370,32 +370,32 @@ static void ccw_cursor(struct vc_data *v
+       if (err)
+               soft_cursor(info, &cursor);
+-      ops->cursor_reset = 0;
++      par->cursor_reset = 0;
+ }
+ static int ccw_update_start(struct fb_info *info)
+ {
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       u32 yoffset;
+-      u32 vyres = GETVYRES(ops->p, info);
++      u32 vyres = GETVYRES(par->p, info);
+       int err;
+-      yoffset = (vyres - info->var.yres) - ops->var.xoffset;
+-      ops->var.xoffset = ops->var.yoffset;
+-      ops->var.yoffset = yoffset;
+-      err = fb_pan_display(info, &ops->var);
+-      ops->var.xoffset = info->var.xoffset;
+-      ops->var.yoffset = info->var.yoffset;
+-      ops->var.vmode = info->var.vmode;
++      yoffset = (vyres - info->var.yres) - par->var.xoffset;
++      par->var.xoffset = par->var.yoffset;
++      par->var.yoffset = yoffset;
++      err = fb_pan_display(info, &par->var);
++      par->var.xoffset = info->var.xoffset;
++      par->var.yoffset = info->var.yoffset;
++      par->var.vmode = info->var.vmode;
+       return err;
+ }
+-void fbcon_rotate_ccw(struct fbcon_ops *ops)
++void fbcon_rotate_ccw(struct fbcon_par *par)
+ {
+-      ops->bmove = ccw_bmove;
+-      ops->clear = ccw_clear;
+-      ops->putcs = ccw_putcs;
+-      ops->clear_margins = ccw_clear_margins;
+-      ops->cursor = ccw_cursor;
+-      ops->update_start = ccw_update_start;
++      par->bmove = ccw_bmove;
++      par->clear = ccw_clear;
++      par->putcs = ccw_putcs;
++      par->clear_margins = ccw_clear_margins;
++      par->cursor = ccw_cursor;
++      par->update_start = ccw_update_start;
+ }
+--- a/drivers/video/fbdev/core/fbcon_cw.c
++++ b/drivers/video/fbdev/core/fbcon_cw.c
+@@ -48,9 +48,9 @@ static void cw_update_attr(u8 *dst, u8 *
+ static void cw_bmove(struct vc_data *vc, struct fb_info *info, int sy,
+                    int sx, int dy, int dx, int height, int width)
+ {
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       struct fb_copyarea area;
+-      u32 vxres = GETVXRES(ops->p, info);
++      u32 vxres = GETVXRES(par->p, info);
+       area.sx = vxres - ((sy + height) * vc->vc_font.height);
+       area.sy = sx * vc->vc_font.width;
+@@ -65,9 +65,9 @@ static void cw_bmove(struct vc_data *vc,
+ static void cw_clear(struct vc_data *vc, struct fb_info *info, int sy,
+                    int sx, int height, int width, int fg, int bg)
+ {
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       struct fb_fillrect region;
+-      u32 vxres = GETVXRES(ops->p, info);
++      u32 vxres = GETVXRES(par->p, info);
+       region.color = bg;
+       region.dx = vxres - ((sy + height) * vc->vc_font.height);
+@@ -84,13 +84,13 @@ static inline void cw_putcs_aligned(stru
+                                   u32 d_pitch, u32 s_pitch, u32 cellsize,
+                                   struct fb_image *image, u8 *buf, u8 *dst)
+ {
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       u16 charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff;
+       u32 idx = (vc->vc_font.height + 7) >> 3;
+       u8 *src;
+       while (cnt--) {
+-              src = ops->fontbuffer + (scr_readw(s++) & charmask)*cellsize;
++              src = par->fontbuffer + (scr_readw(s++) & charmask) * cellsize;
+               if (attr) {
+                       cw_update_attr(buf, src, attr, vc);
+@@ -115,7 +115,7 @@ static void cw_putcs(struct vc_data *vc,
+                     int fg, int bg)
+ {
+       struct fb_image image;
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       u32 width = (vc->vc_font.height + 7)/8;
+       u32 cellsize = width * vc->vc_font.width;
+       u32 maxcnt = info->pixmap.size/cellsize;
+@@ -124,9 +124,9 @@ static void cw_putcs(struct vc_data *vc,
+       u32 cnt, pitch, size;
+       u32 attribute = get_attribute(info, scr_readw(s));
+       u8 *dst, *buf = NULL;
+-      u32 vxres = GETVXRES(ops->p, info);
++      u32 vxres = GETVXRES(par->p, info);
+-      if (!ops->fontbuffer)
++      if (!par->fontbuffer)
+               return;
+       image.fg_color = fg;
+@@ -204,28 +204,28 @@ static void cw_cursor(struct vc_data *vc
+                     int fg, int bg)
+ {
+       struct fb_cursor cursor;
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       unsigned short charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff;
+       int w = (vc->vc_font.height + 7) >> 3, c;
+-      int y = real_y(ops->p, vc->state.y);
++      int y = real_y(par->p, vc->state.y);
+       int attribute, use_sw = vc->vc_cursor_type & CUR_SW;
+       int err = 1, dx, dy;
+       char *src;
+-      u32 vxres = GETVXRES(ops->p, info);
++      u32 vxres = GETVXRES(par->p, info);
+-      if (!ops->fontbuffer)
++      if (!par->fontbuffer)
+               return;
+       cursor.set = 0;
+       c = scr_readw((u16 *) vc->vc_pos);
+       attribute = get_attribute(info, c);
+-      src = ops->fontbuffer + ((c & charmask) * (w * vc->vc_font.width));
++      src = par->fontbuffer + ((c & charmask) * (w * vc->vc_font.width));
+-      if (ops->cursor_state.image.data != src ||
+-          ops->cursor_reset) {
+-          ops->cursor_state.image.data = src;
+-          cursor.set |= FB_CUR_SETIMAGE;
++      if (par->cursor_state.image.data != src ||
++          par->cursor_reset) {
++              par->cursor_state.image.data = src;
++              cursor.set |= FB_CUR_SETIMAGE;
+       }
+       if (attribute) {
+@@ -234,49 +234,49 @@ static void cw_cursor(struct vc_data *vc
+               dst = kmalloc_array(w, vc->vc_font.width, GFP_ATOMIC);
+               if (!dst)
+                       return;
+-              kfree(ops->cursor_data);
+-              ops->cursor_data = dst;
++              kfree(par->cursor_data);
++              par->cursor_data = dst;
+               cw_update_attr(dst, src, attribute, vc);
+               src = dst;
+       }
+-      if (ops->cursor_state.image.fg_color != fg ||
+-          ops->cursor_state.image.bg_color != bg ||
+-          ops->cursor_reset) {
+-              ops->cursor_state.image.fg_color = fg;
+-              ops->cursor_state.image.bg_color = bg;
++      if (par->cursor_state.image.fg_color != fg ||
++          par->cursor_state.image.bg_color != bg ||
++          par->cursor_reset) {
++              par->cursor_state.image.fg_color = fg;
++              par->cursor_state.image.bg_color = bg;
+               cursor.set |= FB_CUR_SETCMAP;
+       }
+-      if (ops->cursor_state.image.height != vc->vc_font.width ||
+-          ops->cursor_state.image.width != vc->vc_font.height ||
+-          ops->cursor_reset) {
+-              ops->cursor_state.image.height = vc->vc_font.width;
+-              ops->cursor_state.image.width = vc->vc_font.height;
++      if (par->cursor_state.image.height != vc->vc_font.width ||
++          par->cursor_state.image.width != vc->vc_font.height ||
++          par->cursor_reset) {
++              par->cursor_state.image.height = vc->vc_font.width;
++              par->cursor_state.image.width = vc->vc_font.height;
+               cursor.set |= FB_CUR_SETSIZE;
+       }
+       dx = vxres - ((y * vc->vc_font.height) + vc->vc_font.height);
+       dy = vc->state.x * vc->vc_font.width;
+-      if (ops->cursor_state.image.dx != dx ||
+-          ops->cursor_state.image.dy != dy ||
+-          ops->cursor_reset) {
+-              ops->cursor_state.image.dx = dx;
+-              ops->cursor_state.image.dy = dy;
++      if (par->cursor_state.image.dx != dx ||
++          par->cursor_state.image.dy != dy ||
++          par->cursor_reset) {
++              par->cursor_state.image.dx = dx;
++              par->cursor_state.image.dy = dy;
+               cursor.set |= FB_CUR_SETPOS;
+       }
+-      if (ops->cursor_state.hot.x || ops->cursor_state.hot.y ||
+-          ops->cursor_reset) {
+-              ops->cursor_state.hot.x = cursor.hot.y = 0;
++      if (par->cursor_state.hot.x || par->cursor_state.hot.y ||
++          par->cursor_reset) {
++              par->cursor_state.hot.x = cursor.hot.y = 0;
+               cursor.set |= FB_CUR_SETHOT;
+       }
+       if (cursor.set & FB_CUR_SETSIZE ||
+-          vc->vc_cursor_type != ops->p->cursor_shape ||
+-          ops->cursor_state.mask == NULL ||
+-          ops->cursor_reset) {
++          vc->vc_cursor_type != par->p->cursor_shape ||
++          par->cursor_state.mask == NULL ||
++          par->cursor_reset) {
+               char *tmp, *mask = kmalloc_array(w, vc->vc_font.width,
+                                                GFP_ATOMIC);
+               int cur_height, size, i = 0;
+@@ -292,13 +292,13 @@ static void cw_cursor(struct vc_data *vc
+                       return;
+               }
+-              kfree(ops->cursor_state.mask);
+-              ops->cursor_state.mask = mask;
++              kfree(par->cursor_state.mask);
++              par->cursor_state.mask = mask;
+-              ops->p->cursor_shape = vc->vc_cursor_type;
++              par->p->cursor_shape = vc->vc_cursor_type;
+               cursor.set |= FB_CUR_SETSHAPE;
+-              switch (CUR_SIZE(ops->p->cursor_shape)) {
++              switch (CUR_SIZE(par->p->cursor_shape)) {
+               case CUR_NONE:
+                       cur_height = 0;
+                       break;
+@@ -331,19 +331,19 @@ static void cw_cursor(struct vc_data *vc
+               kfree(tmp);
+       }
+-      ops->cursor_state.enable = enable && !use_sw;
++      par->cursor_state.enable = enable && !use_sw;
+       cursor.image.data = src;
+-      cursor.image.fg_color = ops->cursor_state.image.fg_color;
+-      cursor.image.bg_color = ops->cursor_state.image.bg_color;
+-      cursor.image.dx = ops->cursor_state.image.dx;
+-      cursor.image.dy = ops->cursor_state.image.dy;
+-      cursor.image.height = ops->cursor_state.image.height;
+-      cursor.image.width = ops->cursor_state.image.width;
+-      cursor.hot.x = ops->cursor_state.hot.x;
+-      cursor.hot.y = ops->cursor_state.hot.y;
+-      cursor.mask = ops->cursor_state.mask;
+-      cursor.enable = ops->cursor_state.enable;
++      cursor.image.fg_color = par->cursor_state.image.fg_color;
++      cursor.image.bg_color = par->cursor_state.image.bg_color;
++      cursor.image.dx = par->cursor_state.image.dx;
++      cursor.image.dy = par->cursor_state.image.dy;
++      cursor.image.height = par->cursor_state.image.height;
++      cursor.image.width = par->cursor_state.image.width;
++      cursor.hot.x = par->cursor_state.hot.x;
++      cursor.hot.y = par->cursor_state.hot.y;
++      cursor.mask = par->cursor_state.mask;
++      cursor.enable = par->cursor_state.enable;
+       cursor.image.depth = 1;
+       cursor.rop = ROP_XOR;
+@@ -353,32 +353,32 @@ static void cw_cursor(struct vc_data *vc
+       if (err)
+               soft_cursor(info, &cursor);
+-      ops->cursor_reset = 0;
++      par->cursor_reset = 0;
+ }
+ static int cw_update_start(struct fb_info *info)
+ {
+-      struct fbcon_ops *ops = info->fbcon_par;
+-      u32 vxres = GETVXRES(ops->p, info);
++      struct fbcon_par *par = info->fbcon_par;
++      u32 vxres = GETVXRES(par->p, info);
+       u32 xoffset;
+       int err;
+-      xoffset = vxres - (info->var.xres + ops->var.yoffset);
+-      ops->var.yoffset = ops->var.xoffset;
+-      ops->var.xoffset = xoffset;
+-      err = fb_pan_display(info, &ops->var);
+-      ops->var.xoffset = info->var.xoffset;
+-      ops->var.yoffset = info->var.yoffset;
+-      ops->var.vmode = info->var.vmode;
++      xoffset = vxres - (info->var.xres + par->var.yoffset);
++      par->var.yoffset = par->var.xoffset;
++      par->var.xoffset = xoffset;
++      err = fb_pan_display(info, &par->var);
++      par->var.xoffset = info->var.xoffset;
++      par->var.yoffset = info->var.yoffset;
++      par->var.vmode = info->var.vmode;
+       return err;
+ }
+-void fbcon_rotate_cw(struct fbcon_ops *ops)
++void fbcon_rotate_cw(struct fbcon_par *par)
+ {
+-      ops->bmove = cw_bmove;
+-      ops->clear = cw_clear;
+-      ops->putcs = cw_putcs;
+-      ops->clear_margins = cw_clear_margins;
+-      ops->cursor = cw_cursor;
+-      ops->update_start = cw_update_start;
++      par->bmove = cw_bmove;
++      par->clear = cw_clear;
++      par->putcs = cw_putcs;
++      par->clear_margins = cw_clear_margins;
++      par->cursor = cw_cursor;
++      par->update_start = cw_update_start;
+ }
+--- a/drivers/video/fbdev/core/fbcon_rotate.c
++++ b/drivers/video/fbdev/core/fbcon_rotate.c
+@@ -20,35 +20,35 @@
+ static int fbcon_rotate_font(struct fb_info *info, struct vc_data *vc)
+ {
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       int len, err = 0;
+       int s_cellsize, d_cellsize, i;
+       const u8 *src;
+       u8 *dst;
+-      if (vc->vc_font.data == ops->fontdata &&
+-          ops->p->con_rotate == ops->cur_rotate)
++      if (vc->vc_font.data == par->fontdata &&
++          par->p->con_rotate == par->cur_rotate)
+               goto finished;
+-      src = ops->fontdata = vc->vc_font.data;
+-      ops->cur_rotate = ops->p->con_rotate;
++      src = par->fontdata = vc->vc_font.data;
++      par->cur_rotate = par->p->con_rotate;
+       len = vc->vc_font.charcount;
+       s_cellsize = ((vc->vc_font.width + 7)/8) *
+               vc->vc_font.height;
+       d_cellsize = s_cellsize;
+-      if (ops->rotate == FB_ROTATE_CW ||
+-          ops->rotate == FB_ROTATE_CCW)
++      if (par->rotate == FB_ROTATE_CW ||
++          par->rotate == FB_ROTATE_CCW)
+               d_cellsize = ((vc->vc_font.height + 7)/8) *
+                       vc->vc_font.width;
+       if (info->fbops->fb_sync)
+               info->fbops->fb_sync(info);
+-      if (ops->fd_size < d_cellsize * len) {
+-              kfree(ops->fontbuffer);
+-              ops->fontbuffer = NULL;
+-              ops->fd_size = 0;
++      if (par->fd_size < d_cellsize * len) {
++              kfree(par->fontbuffer);
++              par->fontbuffer = NULL;
++              par->fd_size = 0;
+               dst = kmalloc_array(len, d_cellsize, GFP_KERNEL);
+@@ -57,14 +57,14 @@ static int fbcon_rotate_font(struct fb_i
+                       goto finished;
+               }
+-              ops->fd_size = d_cellsize * len;
+-              ops->fontbuffer = dst;
++              par->fd_size = d_cellsize * len;
++              par->fontbuffer = dst;
+       }
+-      dst = ops->fontbuffer;
+-      memset(dst, 0, ops->fd_size);
++      dst = par->fontbuffer;
++      memset(dst, 0, par->fd_size);
+-      switch (ops->rotate) {
++      switch (par->rotate) {
+       case FB_ROTATE_UD:
+               for (i = len; i--; ) {
+                       rotate_ud(src, dst, vc->vc_font.width,
+@@ -96,19 +96,19 @@ finished:
+       return err;
+ }
+-void fbcon_set_rotate(struct fbcon_ops *ops)
++void fbcon_set_rotate(struct fbcon_par *par)
+ {
+-      ops->rotate_font = fbcon_rotate_font;
++      par->rotate_font = fbcon_rotate_font;
+-      switch(ops->rotate) {
++      switch (par->rotate) {
+       case FB_ROTATE_CW:
+-              fbcon_rotate_cw(ops);
++              fbcon_rotate_cw(par);
+               break;
+       case FB_ROTATE_UD:
+-              fbcon_rotate_ud(ops);
++              fbcon_rotate_ud(par);
+               break;
+       case FB_ROTATE_CCW:
+-              fbcon_rotate_ccw(ops);
++              fbcon_rotate_ccw(par);
+               break;
+       }
+ }
+--- a/drivers/video/fbdev/core/fbcon_rotate.h
++++ b/drivers/video/fbdev/core/fbcon_rotate.h
+@@ -90,7 +90,7 @@ static inline void rotate_ccw(const char
+       }
+ }
+-extern void fbcon_rotate_cw(struct fbcon_ops *ops);
+-extern void fbcon_rotate_ud(struct fbcon_ops *ops);
+-extern void fbcon_rotate_ccw(struct fbcon_ops *ops);
++extern void fbcon_rotate_cw(struct fbcon_par *par);
++extern void fbcon_rotate_ud(struct fbcon_par *par);
++extern void fbcon_rotate_ccw(struct fbcon_par *par);
+ #endif
+--- a/drivers/video/fbdev/core/fbcon_ud.c
++++ b/drivers/video/fbdev/core/fbcon_ud.c
+@@ -48,10 +48,10 @@ static void ud_update_attr(u8 *dst, u8 *
+ static void ud_bmove(struct vc_data *vc, struct fb_info *info, int sy,
+                    int sx, int dy, int dx, int height, int width)
+ {
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       struct fb_copyarea area;
+-      u32 vyres = GETVYRES(ops->p, info);
+-      u32 vxres = GETVXRES(ops->p, info);
++      u32 vyres = GETVYRES(par->p, info);
++      u32 vxres = GETVXRES(par->p, info);
+       area.sy = vyres - ((sy + height) * vc->vc_font.height);
+       area.sx = vxres - ((sx + width) * vc->vc_font.width);
+@@ -66,10 +66,10 @@ static void ud_bmove(struct vc_data *vc,
+ static void ud_clear(struct vc_data *vc, struct fb_info *info, int sy,
+                    int sx, int height, int width, int fg, int bg)
+ {
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       struct fb_fillrect region;
+-      u32 vyres = GETVYRES(ops->p, info);
+-      u32 vxres = GETVXRES(ops->p, info);
++      u32 vyres = GETVYRES(par->p, info);
++      u32 vxres = GETVXRES(par->p, info);
+       region.color = bg;
+       region.dy = vyres - ((sy + height) * vc->vc_font.height);
+@@ -86,13 +86,13 @@ static inline void ud_putcs_aligned(stru
+                                   u32 d_pitch, u32 s_pitch, u32 cellsize,
+                                   struct fb_image *image, u8 *buf, u8 *dst)
+ {
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       u16 charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff;
+       u32 idx = vc->vc_font.width >> 3;
+       u8 *src;
+       while (cnt--) {
+-              src = ops->fontbuffer + (scr_readw(s--) & charmask)*cellsize;
++              src = par->fontbuffer + (scr_readw(s--) & charmask) * cellsize;
+               if (attr) {
+                       ud_update_attr(buf, src, attr, vc);
+@@ -119,7 +119,7 @@ static inline void ud_putcs_unaligned(st
+                                     struct fb_image *image, u8 *buf,
+                                     u8 *dst)
+ {
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       u16 charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff;
+       u32 shift_low = 0, mod = vc->vc_font.width % 8;
+       u32 shift_high = 8;
+@@ -127,7 +127,7 @@ static inline void ud_putcs_unaligned(st
+       u8 *src;
+       while (cnt--) {
+-              src = ops->fontbuffer + (scr_readw(s--) & charmask)*cellsize;
++              src = par->fontbuffer + (scr_readw(s--) & charmask) * cellsize;
+               if (attr) {
+                       ud_update_attr(buf, src, attr, vc);
+@@ -152,7 +152,7 @@ static void ud_putcs(struct vc_data *vc,
+                     int fg, int bg)
+ {
+       struct fb_image image;
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       u32 width = (vc->vc_font.width + 7)/8;
+       u32 cellsize = width * vc->vc_font.height;
+       u32 maxcnt = info->pixmap.size/cellsize;
+@@ -161,10 +161,10 @@ static void ud_putcs(struct vc_data *vc,
+       u32 mod = vc->vc_font.width % 8, cnt, pitch, size;
+       u32 attribute = get_attribute(info, scr_readw(s));
+       u8 *dst, *buf = NULL;
+-      u32 vyres = GETVYRES(ops->p, info);
+-      u32 vxres = GETVXRES(ops->p, info);
++      u32 vyres = GETVYRES(par->p, info);
++      u32 vxres = GETVXRES(par->p, info);
+-      if (!ops->fontbuffer)
++      if (!par->fontbuffer)
+               return;
+       image.fg_color = fg;
+@@ -251,29 +251,29 @@ static void ud_cursor(struct vc_data *vc
+                     int fg, int bg)
+ {
+       struct fb_cursor cursor;
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       unsigned short charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff;
+       int w = (vc->vc_font.width + 7) >> 3, c;
+-      int y = real_y(ops->p, vc->state.y);
++      int y = real_y(par->p, vc->state.y);
+       int attribute, use_sw = vc->vc_cursor_type & CUR_SW;
+       int err = 1, dx, dy;
+       char *src;
+-      u32 vyres = GETVYRES(ops->p, info);
+-      u32 vxres = GETVXRES(ops->p, info);
++      u32 vyres = GETVYRES(par->p, info);
++      u32 vxres = GETVXRES(par->p, info);
+-      if (!ops->fontbuffer)
++      if (!par->fontbuffer)
+               return;
+       cursor.set = 0;
+       c = scr_readw((u16 *) vc->vc_pos);
+       attribute = get_attribute(info, c);
+-      src = ops->fontbuffer + ((c & charmask) * (w * vc->vc_font.height));
++      src = par->fontbuffer + ((c & charmask) * (w * vc->vc_font.height));
+-      if (ops->cursor_state.image.data != src ||
+-          ops->cursor_reset) {
+-          ops->cursor_state.image.data = src;
+-          cursor.set |= FB_CUR_SETIMAGE;
++      if (par->cursor_state.image.data != src ||
++          par->cursor_reset) {
++              par->cursor_state.image.data = src;
++              cursor.set |= FB_CUR_SETIMAGE;
+       }
+       if (attribute) {
+@@ -282,49 +282,49 @@ static void ud_cursor(struct vc_data *vc
+               dst = kmalloc_array(w, vc->vc_font.height, GFP_ATOMIC);
+               if (!dst)
+                       return;
+-              kfree(ops->cursor_data);
+-              ops->cursor_data = dst;
++              kfree(par->cursor_data);
++              par->cursor_data = dst;
+               ud_update_attr(dst, src, attribute, vc);
+               src = dst;
+       }
+-      if (ops->cursor_state.image.fg_color != fg ||
+-          ops->cursor_state.image.bg_color != bg ||
+-          ops->cursor_reset) {
+-              ops->cursor_state.image.fg_color = fg;
+-              ops->cursor_state.image.bg_color = bg;
++      if (par->cursor_state.image.fg_color != fg ||
++          par->cursor_state.image.bg_color != bg ||
++          par->cursor_reset) {
++              par->cursor_state.image.fg_color = fg;
++              par->cursor_state.image.bg_color = bg;
+               cursor.set |= FB_CUR_SETCMAP;
+       }
+-      if (ops->cursor_state.image.height != vc->vc_font.height ||
+-          ops->cursor_state.image.width != vc->vc_font.width ||
+-          ops->cursor_reset) {
+-              ops->cursor_state.image.height = vc->vc_font.height;
+-              ops->cursor_state.image.width = vc->vc_font.width;
++      if (par->cursor_state.image.height != vc->vc_font.height ||
++          par->cursor_state.image.width != vc->vc_font.width ||
++          par->cursor_reset) {
++              par->cursor_state.image.height = vc->vc_font.height;
++              par->cursor_state.image.width = vc->vc_font.width;
+               cursor.set |= FB_CUR_SETSIZE;
+       }
+       dy = vyres - ((y * vc->vc_font.height) + vc->vc_font.height);
+       dx = vxres - ((vc->state.x * vc->vc_font.width) + vc->vc_font.width);
+-      if (ops->cursor_state.image.dx != dx ||
+-          ops->cursor_state.image.dy != dy ||
+-          ops->cursor_reset) {
+-              ops->cursor_state.image.dx = dx;
+-              ops->cursor_state.image.dy = dy;
++      if (par->cursor_state.image.dx != dx ||
++          par->cursor_state.image.dy != dy ||
++          par->cursor_reset) {
++              par->cursor_state.image.dx = dx;
++              par->cursor_state.image.dy = dy;
+               cursor.set |= FB_CUR_SETPOS;
+       }
+-      if (ops->cursor_state.hot.x || ops->cursor_state.hot.y ||
+-          ops->cursor_reset) {
+-              ops->cursor_state.hot.x = cursor.hot.y = 0;
++      if (par->cursor_state.hot.x || par->cursor_state.hot.y ||
++          par->cursor_reset) {
++              par->cursor_state.hot.x = cursor.hot.y = 0;
+               cursor.set |= FB_CUR_SETHOT;
+       }
+       if (cursor.set & FB_CUR_SETSIZE ||
+-          vc->vc_cursor_type != ops->p->cursor_shape ||
+-          ops->cursor_state.mask == NULL ||
+-          ops->cursor_reset) {
++          vc->vc_cursor_type != par->p->cursor_shape ||
++          par->cursor_state.mask == NULL ||
++          par->cursor_reset) {
+               char *mask = kmalloc_array(w, vc->vc_font.height, GFP_ATOMIC);
+               int cur_height, size, i = 0;
+               u8 msk = 0xff;
+@@ -332,13 +332,13 @@ static void ud_cursor(struct vc_data *vc
+               if (!mask)
+                       return;
+-              kfree(ops->cursor_state.mask);
+-              ops->cursor_state.mask = mask;
++              kfree(par->cursor_state.mask);
++              par->cursor_state.mask = mask;
+-              ops->p->cursor_shape = vc->vc_cursor_type;
++              par->p->cursor_shape = vc->vc_cursor_type;
+               cursor.set |= FB_CUR_SETSHAPE;
+-              switch (CUR_SIZE(ops->p->cursor_shape)) {
++              switch (CUR_SIZE(par->p->cursor_shape)) {
+               case CUR_NONE:
+                       cur_height = 0;
+                       break;
+@@ -371,19 +371,19 @@ static void ud_cursor(struct vc_data *vc
+                       mask[i++] = ~msk;
+       }
+-      ops->cursor_state.enable = enable && !use_sw;
++      par->cursor_state.enable = enable && !use_sw;
+       cursor.image.data = src;
+-      cursor.image.fg_color = ops->cursor_state.image.fg_color;
+-      cursor.image.bg_color = ops->cursor_state.image.bg_color;
+-      cursor.image.dx = ops->cursor_state.image.dx;
+-      cursor.image.dy = ops->cursor_state.image.dy;
+-      cursor.image.height = ops->cursor_state.image.height;
+-      cursor.image.width = ops->cursor_state.image.width;
+-      cursor.hot.x = ops->cursor_state.hot.x;
+-      cursor.hot.y = ops->cursor_state.hot.y;
+-      cursor.mask = ops->cursor_state.mask;
+-      cursor.enable = ops->cursor_state.enable;
++      cursor.image.fg_color = par->cursor_state.image.fg_color;
++      cursor.image.bg_color = par->cursor_state.image.bg_color;
++      cursor.image.dx = par->cursor_state.image.dx;
++      cursor.image.dy = par->cursor_state.image.dy;
++      cursor.image.height = par->cursor_state.image.height;
++      cursor.image.width = par->cursor_state.image.width;
++      cursor.hot.x = par->cursor_state.hot.x;
++      cursor.hot.y = par->cursor_state.hot.y;
++      cursor.mask = par->cursor_state.mask;
++      cursor.enable = par->cursor_state.enable;
+       cursor.image.depth = 1;
+       cursor.rop = ROP_XOR;
+@@ -393,36 +393,36 @@ static void ud_cursor(struct vc_data *vc
+       if (err)
+               soft_cursor(info, &cursor);
+-      ops->cursor_reset = 0;
++      par->cursor_reset = 0;
+ }
+ static int ud_update_start(struct fb_info *info)
+ {
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       int xoffset, yoffset;
+-      u32 vyres = GETVYRES(ops->p, info);
+-      u32 vxres = GETVXRES(ops->p, info);
++      u32 vyres = GETVYRES(par->p, info);
++      u32 vxres = GETVXRES(par->p, info);
+       int err;
+-      xoffset = vxres - info->var.xres - ops->var.xoffset;
+-      yoffset = vyres - info->var.yres - ops->var.yoffset;
++      xoffset = vxres - info->var.xres - par->var.xoffset;
++      yoffset = vyres - info->var.yres - par->var.yoffset;
+       if (yoffset < 0)
+               yoffset += vyres;
+-      ops->var.xoffset = xoffset;
+-      ops->var.yoffset = yoffset;
+-      err = fb_pan_display(info, &ops->var);
+-      ops->var.xoffset = info->var.xoffset;
+-      ops->var.yoffset = info->var.yoffset;
+-      ops->var.vmode = info->var.vmode;
++      par->var.xoffset = xoffset;
++      par->var.yoffset = yoffset;
++      err = fb_pan_display(info, &par->var);
++      par->var.xoffset = info->var.xoffset;
++      par->var.yoffset = info->var.yoffset;
++      par->var.vmode = info->var.vmode;
+       return err;
+ }
+-void fbcon_rotate_ud(struct fbcon_ops *ops)
++void fbcon_rotate_ud(struct fbcon_par *par)
+ {
+-      ops->bmove = ud_bmove;
+-      ops->clear = ud_clear;
+-      ops->putcs = ud_putcs;
+-      ops->clear_margins = ud_clear_margins;
+-      ops->cursor = ud_cursor;
+-      ops->update_start = ud_update_start;
++      par->bmove = ud_bmove;
++      par->clear = ud_clear;
++      par->putcs = ud_putcs;
++      par->clear_margins = ud_clear_margins;
++      par->cursor = ud_cursor;
++      par->update_start = ud_update_start;
+ }
+--- a/drivers/video/fbdev/core/softcursor.c
++++ b/drivers/video/fbdev/core/softcursor.c
+@@ -21,7 +21,7 @@
+ int soft_cursor(struct fb_info *info, struct fb_cursor *cursor)
+ {
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       unsigned int scan_align = info->pixmap.scan_align - 1;
+       unsigned int buf_align = info->pixmap.buf_align - 1;
+       unsigned int i, size, dsize, s_pitch, d_pitch;
+@@ -34,19 +34,19 @@ int soft_cursor(struct fb_info *info, st
+       s_pitch = (cursor->image.width + 7) >> 3;
+       dsize = s_pitch * cursor->image.height;
+-      if (dsize + sizeof(struct fb_image) != ops->cursor_size) {
+-              kfree(ops->cursor_src);
+-              ops->cursor_size = dsize + sizeof(struct fb_image);
+-
+-              ops->cursor_src = kmalloc(ops->cursor_size, GFP_ATOMIC);
+-              if (!ops->cursor_src) {
+-                      ops->cursor_size = 0;
++      if (dsize + sizeof(struct fb_image) != par->cursor_size) {
++              kfree(par->cursor_src);
++              par->cursor_size = dsize + sizeof(struct fb_image);
++
++              par->cursor_src = kmalloc(par->cursor_size, GFP_ATOMIC);
++              if (!par->cursor_src) {
++                      par->cursor_size = 0;
+                       return -ENOMEM;
+               }
+       }
+-      src = ops->cursor_src + sizeof(struct fb_image);
+-      image = (struct fb_image *)ops->cursor_src;
++      src = par->cursor_src + sizeof(struct fb_image);
++      image = (struct fb_image *)par->cursor_src;
+       *image = cursor->image;
+       d_pitch = (s_pitch + scan_align) & ~scan_align;
+--- a/drivers/video/fbdev/core/tileblit.c
++++ b/drivers/video/fbdev/core/tileblit.c
+@@ -151,34 +151,34 @@ static void tile_cursor(struct vc_data *
+ static int tile_update_start(struct fb_info *info)
+ {
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+       int err;
+-      err = fb_pan_display(info, &ops->var);
+-      ops->var.xoffset = info->var.xoffset;
+-      ops->var.yoffset = info->var.yoffset;
+-      ops->var.vmode = info->var.vmode;
++      err = fb_pan_display(info, &par->var);
++      par->var.xoffset = info->var.xoffset;
++      par->var.yoffset = info->var.yoffset;
++      par->var.vmode = info->var.vmode;
+       return err;
+ }
+ void fbcon_set_tileops(struct vc_data *vc, struct fb_info *info)
+ {
+       struct fb_tilemap map;
+-      struct fbcon_ops *ops = info->fbcon_par;
++      struct fbcon_par *par = info->fbcon_par;
+-      ops->bmove = tile_bmove;
+-      ops->clear = tile_clear;
+-      ops->putcs = tile_putcs;
+-      ops->clear_margins = tile_clear_margins;
+-      ops->cursor = tile_cursor;
+-      ops->update_start = tile_update_start;
++      par->bmove = tile_bmove;
++      par->clear = tile_clear;
++      par->putcs = tile_putcs;
++      par->clear_margins = tile_clear_margins;
++      par->cursor = tile_cursor;
++      par->update_start = tile_update_start;
+-      if (ops->p) {
++      if (par->p) {
+               map.width = vc->vc_font.width;
+               map.height = vc->vc_font.height;
+               map.depth = 1;
+               map.length = vc->vc_font.charcount;
+-              map.data = ops->p->fontdata;
++              map.data = par->p->fontdata;
+               info->tileops->fb_settile(info, &map);
+       }
+ }
diff --git a/queue-6.12/fbcon-use-correct-type-for-vc_resize-return-value.patch b/queue-6.12/fbcon-use-correct-type-for-vc_resize-return-value.patch
new file mode 100644 (file)
index 0000000..13ec501
--- /dev/null
@@ -0,0 +1,45 @@
+From stable+bounces-287900-greg=kroah.com@vger.kernel.org Wed Jul 22 17:03:58 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 10:48:42 -0400
+Subject: fbcon: Use correct type for vc_resize() return value
+To: stable@vger.kernel.org
+Cc: Jiacheng Yu <yujiacheng3@huawei.com>, Thomas Zimmermann <tzimmermann@suse.de>, Helge Deller <deller@gmx.de>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722144842.1601797-2-sashal@kernel.org>
+
+From: Jiacheng Yu <yujiacheng3@huawei.com>
+
+[ Upstream commit 84202754fb1727dc3ee87f47104e4162ecc8ba3a ]
+
+The return value of vc_resize() is int, but fbcon_set_disp() stores it
+in an unsigned long variable. While the !ret check happens to work
+correctly by coincidence (negative values become large positive values),
+the types should match. Use int instead.
+
+Eliminates the following W=3 warning:
+
+  drivers/video/fbdev/core/fbcon.c: In function 'fbcon_set_disp':
+  drivers/video/fbdev/core/fbcon.c:1494:14: warning: implicit conversion from 'int' to 'unsigned long' [-Wconversion]
+
+Fixes: af0db3c1f898 ("fbdev: Fix vmalloc out-of-bounds write in fast_imageblit")
+Cc: stable@vger.kernel.org # v6.17+
+Signed-off-by: Jiacheng Yu <yujiacheng3@huawei.com>
+Reviewed-by: Thomas Zimmermann <tzimmermann@suse.de>
+Signed-off-by: Helge Deller <deller@gmx.de>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/video/fbdev/core/fbcon.c |    3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+--- a/drivers/video/fbdev/core/fbcon.c
++++ b/drivers/video/fbdev/core/fbcon.c
+@@ -1366,8 +1366,7 @@ static void fbcon_set_disp(struct fb_inf
+       struct vc_data **default_mode, *vc;
+       struct vc_data *svc;
+       struct fbcon_par *par = info->fbcon_par;
+-      int rows, cols;
+-      unsigned long ret = 0;
++      int rows, cols, ret;
+       p = &fb_display[unit];
diff --git a/queue-6.12/fpga-dfl-afu-validate-dma-mapping-length-in-afu_dma_map_region.patch b/queue-6.12/fpga-dfl-afu-validate-dma-mapping-length-in-afu_dma_map_region.patch
new file mode 100644 (file)
index 0000000..4d4bc48
--- /dev/null
@@ -0,0 +1,46 @@
+From stable+bounces-281853-greg=kroah.com@vger.kernel.org Tue Jul 21 21:04:18 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 14:49:06 -0400
+Subject: fpga: dfl-afu: validate DMA mapping length in afu_dma_map_region()
+To: stable@vger.kernel.org
+Cc: Sebastian Alba Vives <sebasjosue84@gmail.com>, Xu Yilun <yilun.xu@intel.com>, Xu Yilun <yilun.xu@linux.intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721184906.383124-1-sashal@kernel.org>
+
+From: Sebastian Alba Vives <sebasjosue84@gmail.com>
+
+[ Upstream commit fc3b071a7c8dc0f5d56defddf6e6fd5aaa3e1e27 ]
+
+afu_ioctl_dma_map() accepts a 64-bit length from userspace via
+DFL_FPGA_PORT_DMA_MAP ioctl without an upper bound check. The value
+is passed to afu_dma_pin_pages() where npages is derived as
+length >> PAGE_SHIFT and passed to pin_user_pages_fast() which takes
+int nr_pages, causing implicit truncation if length is very large.
+
+Validate map.length at the ioctl entry point before calling
+afu_dma_map_region(), rejecting values whose page count exceeds
+INT_MAX.
+
+Fixes: fa8dda1edef9 ("fpga: dfl: afu: add DFL_FPGA_PORT_DMA_MAP/UNMAP ioctls support")
+Cc: stable@vger.kernel.org
+Signed-off-by: Sebastian Alba Vives <sebasjosue84@gmail.com>
+Reviewed-by: Xu Yilun <yilun.xu@intel.com>
+Link: https://lore.kernel.org/r/20260518190742.61426-3-sebasjosue84@gmail.com
+Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/fpga/dfl-afu-main.c |    3 +++
+ 1 file changed, 3 insertions(+)
+
+--- a/drivers/fpga/dfl-afu-main.c
++++ b/drivers/fpga/dfl-afu-main.c
+@@ -720,6 +720,9 @@ afu_ioctl_dma_map(struct dfl_feature_pla
+       if (map.argsz < minsz || map.flags)
+               return -EINVAL;
++      if (map.length >> PAGE_SHIFT > (u64)INT_MAX)
++              return -EINVAL;
++
+       ret = afu_dma_map_region(pdata, map.user_addr, map.length, &map.iova);
+       if (ret)
+               return ret;
diff --git a/queue-6.12/fs-resctrl-fix-double-add-of-pseudo-locked-region-s-rmid-to-free-list.patch b/queue-6.12/fs-resctrl-fix-double-add-of-pseudo-locked-region-s-rmid-to-free-list.patch
new file mode 100644 (file)
index 0000000..8a4ffa3
--- /dev/null
@@ -0,0 +1,60 @@
+From stable+bounces-290227-greg=kroah.com@vger.kernel.org Wed Jul 29 04:30:01 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 22:29:52 -0400
+Subject: fs/resctrl: Fix double-add of pseudo-locked region's RMID to free list
+To: stable@vger.kernel.org
+Cc: Reinette Chatre <reinette.chatre@intel.com>, "Borislav Petkov (AMD)" <bp@alien8.de>, stable@kernel.org, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260729022952.1112657-1-sashal@kernel.org>
+
+From: Reinette Chatre <reinette.chatre@intel.com>
+
+[ Upstream commit b9f089723aee892efc77c349ae47a6b452b293c4 ]
+
+A pseudo-locked group's RMID is freed when it is created. On unmount
+rmdir_all_sub() unconditionally frees all RMID of all groups, resulting
+in a double-free of the pseudo-locked group's RMID. The consequence of this
+is that the original free results in the pseudo-locked group's RMID being
+added to the rmid_free_lru linked list and the second free then attempts
+to add the same RMID entry to the rmid_free_lru again.
+
+Do not double-free a pseudo-locked group's RMID.
+
+Fixes: e0bdfe8e36f3 ("x86/intel_rdt: Support creation/removal of pseudo-locked region")
+Signed-off-by: Reinette Chatre <reinette.chatre@intel.com>
+Signed-off-by: Borislav Petkov (AMD) <bp@alien8.de>
+Cc: <stable@kernel.org>
+Link: https://patch.msgid.link/551432dd7e624a862b8e58314c38aaba0afff3e9.1783377598.git.reinette.chatre@intel.com
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ arch/x86/kernel/cpu/resctrl/rdtgroup.c |   12 +++++++-----
+ 1 file changed, 7 insertions(+), 5 deletions(-)
+
+--- a/arch/x86/kernel/cpu/resctrl/rdtgroup.c
++++ b/arch/x86/kernel/cpu/resctrl/rdtgroup.c
+@@ -2922,10 +2922,6 @@ static void rmdir_all_sub(void)
+               if (rdtgrp == &rdtgroup_default)
+                       continue;
+-              if (rdtgrp->mode == RDT_MODE_PSEUDO_LOCKSETUP ||
+-                  rdtgrp->mode == RDT_MODE_PSEUDO_LOCKED)
+-                      rdtgroup_pseudo_lock_remove(rdtgrp);
+-
+               /*
+                * Give any CPUs back to the default group. We cannot copy
+                * cpu_online_mask because a CPU might have executed the
+@@ -2934,7 +2930,13 @@ static void rmdir_all_sub(void)
+               cpumask_or(&rdtgroup_default.cpu_mask,
+                          &rdtgroup_default.cpu_mask, &rdtgrp->cpu_mask);
+-              free_rmid(rdtgrp->closid, rdtgrp->mon.rmid);
++              if (rdtgrp->mode == RDT_MODE_PSEUDO_LOCKSETUP ||
++                  rdtgrp->mode == RDT_MODE_PSEUDO_LOCKED) {
++                      rdtgroup_pseudo_lock_remove(rdtgrp);
++              } else {
++                      /* Pseudo-locked group's RMID is freed during setup. */
++                      free_rmid(rdtgrp->closid, rdtgrp->mon.rmid);
++              }
+               kernfs_remove(rdtgrp->kn);
+               list_del(&rdtgrp->rdtgroup_list);
diff --git a/queue-6.12/gpio-mt7621-avoid-corruption-of-shared-interrupt-trigger-state.patch b/queue-6.12/gpio-mt7621-avoid-corruption-of-shared-interrupt-trigger-state.patch
new file mode 100644 (file)
index 0000000..4391443
--- /dev/null
@@ -0,0 +1,44 @@
+From stable+bounces-290047-greg=kroah.com@vger.kernel.org Tue Jul 28 18:13:03 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 11:43:27 -0400
+Subject: gpio: mt7621: avoid corruption of shared interrupt trigger state
+To: stable@vger.kernel.org
+Cc: Sergio Paracuellos <sergio.paracuellos@gmail.com>, Sashiko <sashiko-bot@kernel.org>, Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260728154327.4192271-1-sashal@kernel.org>
+
+From: Sergio Paracuellos <sergio.paracuellos@gmail.com>
+
+[ Upstream commit 1781172526d1092323af443fa03f00e6de560401 ]
+
+The bank-shared fields like 'rising' and 'falling' are modified using
+non-atomic read-modify-write operations. Since every gpio chip instance
+represents an entire bank of 32 pins, if 'mediatek_gpio_irq_type()' is
+called concurrently for different IRQs on the same bank a possible overwrite
+of each other's configuration is possible. Thus, protect this state with
+'gpio_generic_lock_irqsave' lock in the same way it is handled in irp_chip
+'mediatek_gpio_irq_mask()' and 'mediatek_gpio_irq_unmask()' callbacks.
+
+Cc: stable@vger.kernel.org
+Reported-by: Sashiko <sashiko-bot@kernel.org>
+Fixes: 4ba9c3afda41 ("gpio: mt7621: Add a driver for MT7621")
+Signed-off-by: Sergio Paracuellos <sergio.paracuellos@gmail.com>
+Link: https://patch.msgid.link/20260626060112.2498324-2-sergio.paracuellos@gmail.com
+Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
+[ Changed `guard(gpio_generic_lock_irqsave)(&rg->chip)` to `guard(spinlock_irqsave)(&rg->lock)` as the generic GPIO chip lock does not exist in this tree. ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/gpio/gpio-mt7621.c |    2 ++
+ 1 file changed, 2 insertions(+)
+
+--- a/drivers/gpio/gpio-mt7621.c
++++ b/drivers/gpio/gpio-mt7621.c
+@@ -156,6 +156,8 @@ mediatek_gpio_irq_type(struct irq_data *
+       int pin = d->hwirq;
+       u32 mask = BIT(pin);
++      guard(spinlock_irqsave)(&rg->lock);
++
+       if (type == IRQ_TYPE_PROBE) {
+               if ((rg->rising | rg->falling |
+                    rg->hlevel | rg->llevel) & mask)
diff --git a/queue-6.12/gpu-buddy-bail-out-of-try_harder-when-alignment-cannot-be-honoured.patch b/queue-6.12/gpu-buddy-bail-out-of-try_harder-when-alignment-cannot-be-honoured.patch
new file mode 100644 (file)
index 0000000..ce29f8d
--- /dev/null
@@ -0,0 +1,140 @@
+From stable+bounces-288440-greg=kroah.com@vger.kernel.org Thu Jul 23 19:08:01 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 12:54:43 -0400
+Subject: gpu/buddy: bail out of try_harder when alignment cannot be honoured
+To: stable@vger.kernel.org
+Cc: "Arunpravin Paneer Selvam" <Arunpravin.PaneerSelvam@amd.com>, "Christian König" <christian.koenig@amd.com>, "Matthew Auld" <matthew.auld@intel.com>, "Timur Kristóf" <timur.kristof@gmail.com>, "John Olender" <john.olender@gmail.com>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260723165444.3392251-2-sashal@kernel.org>
+
+From: Arunpravin Paneer Selvam <Arunpravin.PaneerSelvam@amd.com>
+
+[ Upstream commit 56bc6384314fb9ae98975fb2af8b143097ede3dc ]
+
+The try_harder contiguous fallback could return a range whose start
+offset did not match the caller's min_block_size. When a candidate's
+start is misaligned, realign it: free the misaligned run and reallocate
+exactly @size at the next lower min_block_size boundary. This keeps the
+returned size unchanged with no surplus to trim, and rejects the request
+only when no aligned candidate fits.
+
+v2: align misaligned candidates down to min_block_size instead of
+    bailing out, for both the RHS and LHS paths (Matthew).
+
+Fixes: 0a1844bf0b53 ("drm/buddy: Improve contiguous memory allocation")
+Suggested-by: Christian König <christian.koenig@amd.com>
+Cc: Matthew Auld <matthew.auld@intel.com>
+Cc: Christian König <christian.koenig@amd.com>
+Cc: Timur Kristóf <timur.kristof@gmail.com>
+Cc: stable@vger.kernel.org
+Reviewed-by: Matthew Auld <matthew.auld@intel.com>
+Tested-by: John Olender <john.olender@gmail.com>
+Signed-off-by: Arunpravin Paneer Selvam <Arunpravin.PaneerSelvam@amd.com>
+Link: https://patch.msgid.link/20260709131050.1022759-1-Arunpravin.PaneerSelvam@amd.com
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/gpu/buddy.c |   65 ++++++++++++++++++++++++++++++++++++----------------
+ 1 file changed, 45 insertions(+), 20 deletions(-)
+
+--- a/drivers/gpu/buddy.c
++++ b/drivers/gpu/buddy.c
+@@ -901,22 +901,30 @@ static int __gpu_buddy_alloc_range(struc
+                            blocks, total_allocated_on_err);
+ }
++static int __alloc_contig_aligned_retry(struct gpu_buddy *mm,
++                                      u64 unaligned_offset,
++                                      u64 size,
++                                      u64 min_block_size,
++                                      struct list_head *blocks)
++{
++      u64 aligned_offset = round_down(unaligned_offset, min_block_size);
++
++      return __gpu_buddy_alloc_range(mm, aligned_offset, size, NULL, blocks);
++}
++
+ static int __alloc_contig_try_harder(struct gpu_buddy *mm,
+                                    u64 size,
+                                    u64 min_block_size,
+                                    struct list_head *blocks)
+ {
+-      u64 rhs_offset, lhs_offset, lhs_size, filled;
++      u64 rhs_offset, lhs_offset, filled;
+       struct gpu_buddy_block *block;
+       unsigned int tree, order;
+-      LIST_HEAD(blocks_lhs);
+-      unsigned long pages;
+       u64 modify_size;
+       int err;
+       modify_size = rounddown_pow_of_two(size);
+-      pages = modify_size >> ilog2(mm->chunk_size);
+-      order = fls(pages) - 1;
++      order = ilog2(modify_size) - ilog2(mm->chunk_size);
+       if (order == 0)
+               return -ENOSPC;
+@@ -932,31 +940,48 @@ static int __alloc_contig_try_harder(str
+               while (iter) {
+                       block = rbtree_get_free_block(iter);
+-                      /* Allocate blocks traversing RHS */
+                       rhs_offset = gpu_buddy_block_offset(block);
++
++                      /* Allocate blocks traversing RHS */
+                       err =  __gpu_buddy_alloc_range(mm, rhs_offset, size,
+                                                      &filled, blocks);
+-                      if (!err || err != -ENOSPC)
++                      if (err && err != -ENOSPC)
+                               return err;
+-
+-                      lhs_size = max((size - filled), min_block_size);
+-                      if (!IS_ALIGNED(lhs_size, min_block_size))
+-                              lhs_size = round_up(lhs_size, min_block_size);
+-
+-                      /* Allocate blocks traversing LHS */
+-                      lhs_offset = gpu_buddy_block_offset(block) - lhs_size;
+-                      err =  __gpu_buddy_alloc_range(mm, lhs_offset, lhs_size,
+-                                                     NULL, &blocks_lhs);
++                      if (!err && IS_ALIGNED(rhs_offset, min_block_size))
++                              return 0;
+                       if (!err) {
+-                              list_splice(&blocks_lhs, blocks);
++                              /* Allocate the unaligned RHS offset using round_down */
++                              gpu_buddy_free_list_internal(mm, blocks);
++                              err = __alloc_contig_aligned_retry(mm, rhs_offset,
++                                                                 size,
++                                                                 min_block_size,
++                                                                 blocks);
++                              if (!err)
++                                      return 0;
++                              if (err != -ENOSPC) {
++                                      gpu_buddy_free_list_internal(mm, blocks);
++                                      return err;
++                              }
++                              goto next;
++                      }
++
++                      if (size - filled > rhs_offset)
++                              goto next;
++
++                      lhs_offset = rhs_offset - (size - filled);
++
++                      /* Allocate the unaligned LHS offset using round_down */
++                      gpu_buddy_free_list_internal(mm, blocks);
++                      err = __alloc_contig_aligned_retry(mm, lhs_offset, size,
++                                                         min_block_size, blocks);
++                      if (!err)
+                               return 0;
+-                      } else if (err != -ENOSPC) {
++                      if (err != -ENOSPC) {
+                               gpu_buddy_free_list_internal(mm, blocks);
+                               return err;
+                       }
+-                      /* Free blocks for the next iteration */
++next:
+                       gpu_buddy_free_list_internal(mm, blocks);
+-
+                       iter = rb_prev(iter);
+               }
+       }
diff --git a/queue-6.12/gpu-move-drm-buddy-allocator-one-level-up-part-two.patch b/queue-6.12/gpu-move-drm-buddy-allocator-one-level-up-part-two.patch
new file mode 100644 (file)
index 0000000..5ce3eed
--- /dev/null
@@ -0,0 +1,6105 @@
+From stable+bounces-288441-greg=kroah.com@vger.kernel.org Thu Jul 23 18:58:13 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 12:54:42 -0400
+Subject: gpu: Move DRM buddy allocator one level up (part two)
+To: stable@vger.kernel.org
+Cc: Joel Fernandes <joelagnelf@nvidia.com>, Dave Airlie <airlied@redhat.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723165444.3392251-1-sashal@kernel.org>
+
+From: Joel Fernandes <joelagnelf@nvidia.com>
+
+[ Upstream commit ba110db8e1bc206c13fd7d985e79b033f53bfdea ]
+
+Move the DRM buddy allocator one level up so that it can be used by GPU
+drivers (example, nova-core) that have usecases other than DRM (such as
+VFIO vGPU support). Modify the API, structures and Kconfigs to use
+"gpu_buddy" terminology. Adapt the drivers and tests to use the new API.
+
+The commit cannot be split due to bisectability, however no functional
+change is intended. Verified by running K-UNIT tests and build tested
+various configurations.
+
+Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
+Reviewed-by: Dave Airlie <airlied@redhat.com>
+[airlied: I've split this into two so git can find copies easier.
+I've also just nuked drm_random library, that stuff needs to be done
+elsewhere and only the buddy tests seem to be using it].
+Signed-off-by: Dave Airlie <airlied@redhat.com>
+Stable-dep-of: 56bc6384314f ("gpu/buddy: bail out of try_harder when alignment cannot be honoured")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ Documentation/gpu/drm-mm.rst                         |   10 
+ drivers/gpu/Kconfig                                  |   13 
+ drivers/gpu/Makefile                                 |    3 
+ drivers/gpu/buddy.c                                  | 1322 +++++++++++++++++++
+ drivers/gpu/drm/Kconfig                              |    6 
+ drivers/gpu/drm/Makefile                             |    1 
+ drivers/gpu/drm/amd/amdgpu/amdgpu_res_cursor.h       |   12 
+ drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.c         |   77 -
+ drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.h         |   18 
+ drivers/gpu/drm/drm_buddy.c                          | 1279 ------------------
+ drivers/gpu/drm/i915/gem/i915_gem_ttm.c              |    2 
+ drivers/gpu/drm/i915/i915_scatterlist.c              |   10 
+ drivers/gpu/drm/i915/i915_ttm_buddy_manager.c        |   55 
+ drivers/gpu/drm/i915/i915_ttm_buddy_manager.h        |    4 
+ drivers/gpu/drm/i915/selftests/intel_memory_region.c |   20 
+ drivers/gpu/drm/lib/drm_random.c                     |   43 
+ drivers/gpu/drm/lib/drm_random.h                     |   28 
+ drivers/gpu/drm/tests/Makefile                       |    1 
+ drivers/gpu/drm/tests/drm_buddy_test.c               |  778 -----------
+ drivers/gpu/drm/tests/drm_exec_test.c                |    2 
+ drivers/gpu/drm/tests/drm_mm_test.c                  |    2 
+ drivers/gpu/drm/ttm/tests/ttm_bo_validate_test.c     |    4 
+ drivers/gpu/drm/ttm/tests/ttm_mock_manager.c         |   18 
+ drivers/gpu/drm/ttm/tests/ttm_mock_manager.h         |    4 
+ drivers/gpu/drm/xe/xe_res_cursor.h                   |   34 
+ drivers/gpu/drm/xe/xe_ttm_vram_mgr.c                 |   73 -
+ drivers/gpu/drm/xe/xe_ttm_vram_mgr_types.h           |    4 
+ drivers/gpu/tests/Makefile                           |    4 
+ drivers/gpu/tests/gpu_buddy_test.c                   |  778 +++++++++++
+ drivers/gpu/tests/gpu_random.c                       |   43 
+ drivers/gpu/tests/gpu_random.h                       |   28 
+ drivers/video/Kconfig                                |    1 
+ include/drm/drm_buddy.h                              |  174 --
+ include/linux/gpu_buddy.h                            |  188 ++
+ 34 files changed, 2576 insertions(+), 2463 deletions(-)
+ create mode 100644 drivers/gpu/Kconfig
+ create mode 100644 drivers/gpu/buddy.c
+ create mode 100644 drivers/gpu/tests/Makefile
+ rename drivers/gpu/{drm/tests/drm_buddy_test.c => tests/gpu_buddy_test.c} (70%)
+ rename drivers/gpu/{drm/lib/drm_random.c => tests/gpu_random.c} (58%)
+ rename drivers/gpu/{drm/lib/drm_random.h => tests/gpu_random.h} (53%)
+ create mode 100644 include/linux/gpu_buddy.h
+
+--- a/Documentation/gpu/drm-mm.rst
++++ b/Documentation/gpu/drm-mm.rst
+@@ -509,8 +509,14 @@ DRM GPUVM Function References
+ DRM Buddy Allocator
+ ===================
+-DRM Buddy Function References
+------------------------------
++Buddy Allocator Function References (GPU buddy)
++-----------------------------------------------
++
++.. kernel-doc:: drivers/gpu/buddy.c
++   :export:
++
++DRM Buddy Specific Logging Function References
++----------------------------------------------
+ .. kernel-doc:: drivers/gpu/drm/drm_buddy.c
+    :export:
+--- /dev/null
++++ b/drivers/gpu/Kconfig
+@@ -0,0 +1,13 @@
++# SPDX-License-Identifier: GPL-2.0
++
++config GPU_BUDDY
++       bool
++       help
++         A page based buddy allocator for GPU memory.
++
++config GPU_BUDDY_KUNIT_TEST
++       tristate "KUnit tests for GPU buddy allocator" if !KUNIT_ALL_TESTS
++       depends on GPU_BUDDY && KUNIT
++       default KUNIT_ALL_TESTS
++       help
++         KUnit tests for the GPU buddy allocator.
+--- a/drivers/gpu/Makefile
++++ b/drivers/gpu/Makefile
+@@ -2,6 +2,7 @@
+ # drm/tegra depends on host1x, so if both drivers are built-in care must be
+ # taken to initialize them in the correct order. Link order is the only way
+ # to ensure this currently.
+-obj-y                 += host1x/ drm/ vga/
++obj-y                 += host1x/ drm/ vga/ tests/
+ obj-$(CONFIG_IMX_IPUV3_CORE)  += ipu-v3/
+ obj-$(CONFIG_TRACE_GPU_MEM)           += trace/
++obj-$(CONFIG_GPU_BUDDY)               += buddy.o
+--- /dev/null
++++ b/drivers/gpu/buddy.c
+@@ -0,0 +1,1322 @@
++// SPDX-License-Identifier: MIT
++/*
++ * Copyright Â© 2021 Intel Corporation
++ */
++
++#include <kunit/test-bug.h>
++
++#include <linux/export.h>
++#include <linux/kmemleak.h>
++#include <linux/module.h>
++#include <linux/sizes.h>
++
++#include <linux/gpu_buddy.h>
++
++static struct kmem_cache *slab_blocks;
++
++static struct gpu_buddy_block *gpu_block_alloc(struct gpu_buddy *mm,
++                                             struct gpu_buddy_block *parent,
++                                             unsigned int order,
++                                             u64 offset)
++{
++      struct gpu_buddy_block *block;
++
++      BUG_ON(order > GPU_BUDDY_MAX_ORDER);
++
++      block = kmem_cache_zalloc(slab_blocks, GFP_KERNEL);
++      if (!block)
++              return NULL;
++
++      block->header = offset;
++      block->header |= order;
++      block->parent = parent;
++
++      RB_CLEAR_NODE(&block->rb);
++
++      BUG_ON(block->header & GPU_BUDDY_HEADER_UNUSED);
++      return block;
++}
++
++static void gpu_block_free(struct gpu_buddy *mm,
++                         struct gpu_buddy_block *block)
++{
++      kmem_cache_free(slab_blocks, block);
++}
++
++static enum gpu_buddy_free_tree
++get_block_tree(struct gpu_buddy_block *block)
++{
++      return gpu_buddy_block_is_clear(block) ?
++             GPU_BUDDY_CLEAR_TREE : GPU_BUDDY_DIRTY_TREE;
++}
++
++static struct gpu_buddy_block *
++rbtree_get_free_block(const struct rb_node *node)
++{
++      return node ? rb_entry(node, struct gpu_buddy_block, rb) : NULL;
++}
++
++static struct gpu_buddy_block *
++rbtree_last_free_block(struct rb_root *root)
++{
++      return rbtree_get_free_block(rb_last(root));
++}
++
++static bool rbtree_is_empty(struct rb_root *root)
++{
++      return RB_EMPTY_ROOT(root);
++}
++
++static bool gpu_buddy_block_offset_less(const struct gpu_buddy_block *block,
++                                      const struct gpu_buddy_block *node)
++{
++      return gpu_buddy_block_offset(block) < gpu_buddy_block_offset(node);
++}
++
++static bool rbtree_block_offset_less(struct rb_node *block,
++                                   const struct rb_node *node)
++{
++      return gpu_buddy_block_offset_less(rbtree_get_free_block(block),
++                                         rbtree_get_free_block(node));
++}
++
++static void rbtree_insert(struct gpu_buddy *mm,
++                        struct gpu_buddy_block *block,
++                        enum gpu_buddy_free_tree tree)
++{
++      rb_add(&block->rb,
++             &mm->free_trees[tree][gpu_buddy_block_order(block)],
++             rbtree_block_offset_less);
++}
++
++static void rbtree_remove(struct gpu_buddy *mm,
++                        struct gpu_buddy_block *block)
++{
++      unsigned int order = gpu_buddy_block_order(block);
++      enum gpu_buddy_free_tree tree;
++      struct rb_root *root;
++
++      tree = get_block_tree(block);
++      root = &mm->free_trees[tree][order];
++
++      rb_erase(&block->rb, root);
++      RB_CLEAR_NODE(&block->rb);
++}
++
++static void clear_reset(struct gpu_buddy_block *block)
++{
++      block->header &= ~GPU_BUDDY_HEADER_CLEAR;
++}
++
++static void mark_cleared(struct gpu_buddy_block *block)
++{
++      block->header |= GPU_BUDDY_HEADER_CLEAR;
++}
++
++static void mark_allocated(struct gpu_buddy *mm,
++                         struct gpu_buddy_block *block)
++{
++      block->header &= ~GPU_BUDDY_HEADER_STATE;
++      block->header |= GPU_BUDDY_ALLOCATED;
++
++      rbtree_remove(mm, block);
++}
++
++static void mark_free(struct gpu_buddy *mm,
++                    struct gpu_buddy_block *block)
++{
++      enum gpu_buddy_free_tree tree;
++
++      block->header &= ~GPU_BUDDY_HEADER_STATE;
++      block->header |= GPU_BUDDY_FREE;
++
++      tree = get_block_tree(block);
++      rbtree_insert(mm, block, tree);
++}
++
++static void mark_split(struct gpu_buddy *mm,
++                     struct gpu_buddy_block *block)
++{
++      block->header &= ~GPU_BUDDY_HEADER_STATE;
++      block->header |= GPU_BUDDY_SPLIT;
++
++      rbtree_remove(mm, block);
++}
++
++static inline bool overlaps(u64 s1, u64 e1, u64 s2, u64 e2)
++{
++      return s1 <= e2 && e1 >= s2;
++}
++
++static inline bool contains(u64 s1, u64 e1, u64 s2, u64 e2)
++{
++      return s1 <= s2 && e1 >= e2;
++}
++
++static struct gpu_buddy_block *
++__get_buddy(struct gpu_buddy_block *block)
++{
++      struct gpu_buddy_block *parent;
++
++      parent = block->parent;
++      if (!parent)
++              return NULL;
++
++      if (parent->left == block)
++              return parent->right;
++
++      return parent->left;
++}
++
++static unsigned int __gpu_buddy_free(struct gpu_buddy *mm,
++                                   struct gpu_buddy_block *block,
++                                   bool force_merge)
++{
++      struct gpu_buddy_block *parent;
++      unsigned int order;
++
++      while ((parent = block->parent)) {
++              struct gpu_buddy_block *buddy;
++
++              buddy = __get_buddy(block);
++
++              if (!gpu_buddy_block_is_free(buddy))
++                      break;
++
++              if (!force_merge) {
++                      /*
++                       * Check the block and its buddy clear state and exit
++                       * the loop if they both have the dissimilar state.
++                       */
++                      if (gpu_buddy_block_is_clear(block) !=
++                          gpu_buddy_block_is_clear(buddy))
++                              break;
++
++                      if (gpu_buddy_block_is_clear(block))
++                              mark_cleared(parent);
++              }
++
++              rbtree_remove(mm, buddy);
++              if (force_merge && gpu_buddy_block_is_clear(buddy))
++                      mm->clear_avail -= gpu_buddy_block_size(mm, buddy);
++
++              gpu_block_free(mm, block);
++              gpu_block_free(mm, buddy);
++
++              block = parent;
++      }
++
++      order = gpu_buddy_block_order(block);
++      mark_free(mm, block);
++
++      return order;
++}
++
++static int __force_merge(struct gpu_buddy *mm,
++                       u64 start,
++                       u64 end,
++                       unsigned int min_order)
++{
++      unsigned int tree, order;
++      int i;
++
++      if (!min_order)
++              return -ENOMEM;
++
++      if (min_order > mm->max_order)
++              return -EINVAL;
++
++      for_each_free_tree(tree) {
++              for (i = min_order - 1; i >= 0; i--) {
++                      struct rb_node *iter = rb_last(&mm->free_trees[tree][i]);
++
++                      while (iter) {
++                              struct gpu_buddy_block *block, *buddy;
++                              u64 block_start, block_end;
++
++                              block = rbtree_get_free_block(iter);
++                              iter = rb_prev(iter);
++
++                              if (!block || !block->parent)
++                                      continue;
++
++                              block_start = gpu_buddy_block_offset(block);
++                              block_end = block_start + gpu_buddy_block_size(mm, block) - 1;
++
++                              if (!contains(start, end, block_start, block_end))
++                                      continue;
++
++                              buddy = __get_buddy(block);
++                              if (!gpu_buddy_block_is_free(buddy))
++                                      continue;
++
++                              WARN_ON(gpu_buddy_block_is_clear(block) ==
++                                      gpu_buddy_block_is_clear(buddy));
++
++                              /*
++                               * Advance to the next node when the current node is the buddy,
++                               * as freeing the block will also remove its buddy from the tree.
++                               */
++                              if (iter == &buddy->rb)
++                                      iter = rb_prev(iter);
++
++                              rbtree_remove(mm, block);
++                              if (gpu_buddy_block_is_clear(block))
++                                      mm->clear_avail -= gpu_buddy_block_size(mm, block);
++
++                              order = __gpu_buddy_free(mm, block, true);
++                              if (order >= min_order)
++                                      return 0;
++                      }
++              }
++      }
++
++      return -ENOMEM;
++}
++
++/**
++ * gpu_buddy_init - init memory manager
++ *
++ * @mm: GPU buddy manager to initialize
++ * @size: size in bytes to manage
++ * @chunk_size: minimum page size in bytes for our allocations
++ *
++ * Initializes the memory manager and its resources.
++ *
++ * Returns:
++ * 0 on success, error code on failure.
++ */
++int gpu_buddy_init(struct gpu_buddy *mm, u64 size, u64 chunk_size)
++{
++      unsigned int i, j, root_count = 0;
++      u64 offset = 0;
++
++      if (size < chunk_size)
++              return -EINVAL;
++
++      if (chunk_size < SZ_4K)
++              return -EINVAL;
++
++      if (!is_power_of_2(chunk_size))
++              return -EINVAL;
++
++      size = round_down(size, chunk_size);
++
++      mm->size = size;
++      mm->avail = size;
++      mm->clear_avail = 0;
++      mm->chunk_size = chunk_size;
++      mm->max_order = ilog2(size) - ilog2(chunk_size);
++
++      BUG_ON(mm->max_order > GPU_BUDDY_MAX_ORDER);
++
++      mm->free_trees = kmalloc_array(GPU_BUDDY_MAX_FREE_TREES,
++                                     sizeof(*mm->free_trees),
++                                     GFP_KERNEL);
++      if (!mm->free_trees)
++              return -ENOMEM;
++
++      for_each_free_tree(i) {
++              mm->free_trees[i] = kmalloc_array(mm->max_order + 1,
++                                                sizeof(struct rb_root),
++                                                GFP_KERNEL);
++              if (!mm->free_trees[i])
++                      goto out_free_tree;
++
++              for (j = 0; j <= mm->max_order; ++j)
++                      mm->free_trees[i][j] = RB_ROOT;
++      }
++
++      mm->n_roots = hweight64(size);
++
++      mm->roots = kmalloc_array(mm->n_roots,
++                                sizeof(struct gpu_buddy_block *),
++                                GFP_KERNEL);
++      if (!mm->roots)
++              goto out_free_tree;
++
++      /*
++       * Split into power-of-two blocks, in case we are given a size that is
++       * not itself a power-of-two.
++       */
++      do {
++              struct gpu_buddy_block *root;
++              unsigned int order;
++              u64 root_size;
++
++              order = ilog2(size) - ilog2(chunk_size);
++              root_size = chunk_size << order;
++
++              root = gpu_block_alloc(mm, NULL, order, offset);
++              if (!root)
++                      goto out_free_roots;
++
++              mark_free(mm, root);
++
++              BUG_ON(root_count > mm->max_order);
++              BUG_ON(gpu_buddy_block_size(mm, root) < chunk_size);
++
++              mm->roots[root_count] = root;
++
++              offset += root_size;
++              size -= root_size;
++              root_count++;
++      } while (size);
++
++      return 0;
++
++out_free_roots:
++      while (root_count--)
++              gpu_block_free(mm, mm->roots[root_count]);
++      kfree(mm->roots);
++out_free_tree:
++      while (i--)
++              kfree(mm->free_trees[i]);
++      kfree(mm->free_trees);
++      return -ENOMEM;
++}
++EXPORT_SYMBOL(gpu_buddy_init);
++
++/**
++ * gpu_buddy_fini - tear down the memory manager
++ *
++ * @mm: GPU buddy manager to free
++ *
++ * Cleanup memory manager resources and the freetree
++ */
++void gpu_buddy_fini(struct gpu_buddy *mm)
++{
++      u64 root_size, size, start;
++      unsigned int order;
++      int i;
++
++      size = mm->size;
++
++      for (i = 0; i < mm->n_roots; ++i) {
++              order = ilog2(size) - ilog2(mm->chunk_size);
++              start = gpu_buddy_block_offset(mm->roots[i]);
++              __force_merge(mm, start, start + size, order);
++
++              if (WARN_ON(!gpu_buddy_block_is_free(mm->roots[i])))
++                      kunit_fail_current_test("buddy_fini() root");
++
++              gpu_block_free(mm, mm->roots[i]);
++
++              root_size = mm->chunk_size << order;
++              size -= root_size;
++      }
++
++      WARN_ON(mm->avail != mm->size);
++
++      for_each_free_tree(i)
++              kfree(mm->free_trees[i]);
++      kfree(mm->free_trees);
++      kfree(mm->roots);
++}
++EXPORT_SYMBOL(gpu_buddy_fini);
++
++static int split_block(struct gpu_buddy *mm,
++                     struct gpu_buddy_block *block)
++{
++      unsigned int block_order = gpu_buddy_block_order(block) - 1;
++      u64 offset = gpu_buddy_block_offset(block);
++
++      BUG_ON(!gpu_buddy_block_is_free(block));
++      BUG_ON(!gpu_buddy_block_order(block));
++
++      block->left = gpu_block_alloc(mm, block, block_order, offset);
++      if (!block->left)
++              return -ENOMEM;
++
++      block->right = gpu_block_alloc(mm, block, block_order,
++                                     offset + (mm->chunk_size << block_order));
++      if (!block->right) {
++              gpu_block_free(mm, block->left);
++              return -ENOMEM;
++      }
++
++      mark_split(mm, block);
++
++      if (gpu_buddy_block_is_clear(block)) {
++              mark_cleared(block->left);
++              mark_cleared(block->right);
++              clear_reset(block);
++      }
++
++      mark_free(mm, block->left);
++      mark_free(mm, block->right);
++
++      return 0;
++}
++
++/**
++ * gpu_get_buddy - get buddy address
++ *
++ * @block: GPU buddy block
++ *
++ * Returns the corresponding buddy block for @block, or NULL
++ * if this is a root block and can't be merged further.
++ * Requires some kind of locking to protect against
++ * any concurrent allocate and free operations.
++ */
++struct gpu_buddy_block *
++gpu_get_buddy(struct gpu_buddy_block *block)
++{
++      return __get_buddy(block);
++}
++EXPORT_SYMBOL(gpu_get_buddy);
++
++/**
++ * gpu_buddy_reset_clear - reset blocks clear state
++ *
++ * @mm: GPU buddy manager
++ * @is_clear: blocks clear state
++ *
++ * Reset the clear state based on @is_clear value for each block
++ * in the freetree.
++ */
++void gpu_buddy_reset_clear(struct gpu_buddy *mm, bool is_clear)
++{
++      enum gpu_buddy_free_tree src_tree, dst_tree;
++      u64 root_size, size, start;
++      unsigned int order;
++      int i;
++
++      size = mm->size;
++      for (i = 0; i < mm->n_roots; ++i) {
++              order = ilog2(size) - ilog2(mm->chunk_size);
++              start = gpu_buddy_block_offset(mm->roots[i]);
++              __force_merge(mm, start, start + size, order);
++
++              root_size = mm->chunk_size << order;
++              size -= root_size;
++      }
++
++      src_tree = is_clear ? GPU_BUDDY_DIRTY_TREE : GPU_BUDDY_CLEAR_TREE;
++      dst_tree = is_clear ? GPU_BUDDY_CLEAR_TREE : GPU_BUDDY_DIRTY_TREE;
++
++      for (i = 0; i <= mm->max_order; ++i) {
++              struct rb_root *root = &mm->free_trees[src_tree][i];
++              struct gpu_buddy_block *block, *tmp;
++
++              rbtree_postorder_for_each_entry_safe(block, tmp, root, rb) {
++                      rbtree_remove(mm, block);
++                      if (is_clear) {
++                              mark_cleared(block);
++                              mm->clear_avail += gpu_buddy_block_size(mm, block);
++                      } else {
++                              clear_reset(block);
++                              mm->clear_avail -= gpu_buddy_block_size(mm, block);
++                      }
++
++                      rbtree_insert(mm, block, dst_tree);
++              }
++      }
++}
++EXPORT_SYMBOL(gpu_buddy_reset_clear);
++
++/**
++ * gpu_buddy_free_block - free a block
++ *
++ * @mm: GPU buddy manager
++ * @block: block to be freed
++ */
++void gpu_buddy_free_block(struct gpu_buddy *mm,
++                        struct gpu_buddy_block *block)
++{
++      BUG_ON(!gpu_buddy_block_is_allocated(block));
++      mm->avail += gpu_buddy_block_size(mm, block);
++      if (gpu_buddy_block_is_clear(block))
++              mm->clear_avail += gpu_buddy_block_size(mm, block);
++
++      __gpu_buddy_free(mm, block, false);
++}
++EXPORT_SYMBOL(gpu_buddy_free_block);
++
++static void __gpu_buddy_free_list(struct gpu_buddy *mm,
++                                struct list_head *objects,
++                                bool mark_clear,
++                                bool mark_dirty)
++{
++      struct gpu_buddy_block *block, *on;
++
++      WARN_ON(mark_dirty && mark_clear);
++
++      list_for_each_entry_safe(block, on, objects, link) {
++              if (mark_clear)
++                      mark_cleared(block);
++              else if (mark_dirty)
++                      clear_reset(block);
++              gpu_buddy_free_block(mm, block);
++              cond_resched();
++      }
++      INIT_LIST_HEAD(objects);
++}
++
++static void gpu_buddy_free_list_internal(struct gpu_buddy *mm,
++                                       struct list_head *objects)
++{
++      /*
++       * Don't touch the clear/dirty bit, since allocation is still internal
++       * at this point. For example we might have just failed part of the
++       * allocation.
++       */
++      __gpu_buddy_free_list(mm, objects, false, false);
++}
++
++/**
++ * gpu_buddy_free_list - free blocks
++ *
++ * @mm: GPU buddy manager
++ * @objects: input list head to free blocks
++ * @flags: optional flags like GPU_BUDDY_CLEARED
++ */
++void gpu_buddy_free_list(struct gpu_buddy *mm,
++                       struct list_head *objects,
++                       unsigned int flags)
++{
++      bool mark_clear = flags & GPU_BUDDY_CLEARED;
++
++      __gpu_buddy_free_list(mm, objects, mark_clear, !mark_clear);
++}
++EXPORT_SYMBOL(gpu_buddy_free_list);
++
++static bool block_incompatible(struct gpu_buddy_block *block, unsigned int flags)
++{
++      bool needs_clear = flags & GPU_BUDDY_CLEAR_ALLOCATION;
++
++      return needs_clear != gpu_buddy_block_is_clear(block);
++}
++
++static struct gpu_buddy_block *
++__alloc_range_bias(struct gpu_buddy *mm,
++                 u64 start, u64 end,
++                 unsigned int order,
++                 unsigned long flags,
++                 bool fallback)
++{
++      u64 req_size = mm->chunk_size << order;
++      struct gpu_buddy_block *block;
++      struct gpu_buddy_block *buddy;
++      LIST_HEAD(dfs);
++      int err;
++      int i;
++
++      end = end - 1;
++
++      for (i = 0; i < mm->n_roots; ++i)
++              list_add_tail(&mm->roots[i]->tmp_link, &dfs);
++
++      do {
++              u64 block_start;
++              u64 block_end;
++
++              block = list_first_entry_or_null(&dfs,
++                                               struct gpu_buddy_block,
++                                               tmp_link);
++              if (!block)
++                      break;
++
++              list_del(&block->tmp_link);
++
++              if (gpu_buddy_block_order(block) < order)
++                      continue;
++
++              block_start = gpu_buddy_block_offset(block);
++              block_end = block_start + gpu_buddy_block_size(mm, block) - 1;
++
++              if (!overlaps(start, end, block_start, block_end))
++                      continue;
++
++              if (gpu_buddy_block_is_allocated(block))
++                      continue;
++
++              if (block_start < start || block_end > end) {
++                      u64 adjusted_start = max(block_start, start);
++                      u64 adjusted_end = min(block_end, end);
++
++                      if (round_down(adjusted_end + 1, req_size) <=
++                          round_up(adjusted_start, req_size))
++                              continue;
++              }
++
++              if (!fallback && block_incompatible(block, flags))
++                      continue;
++
++              if (contains(start, end, block_start, block_end) &&
++                  order == gpu_buddy_block_order(block)) {
++                      /*
++                       * Find the free block within the range.
++                       */
++                      if (gpu_buddy_block_is_free(block))
++                              return block;
++
++                      continue;
++              }
++
++              if (!gpu_buddy_block_is_split(block)) {
++                      err = split_block(mm, block);
++                      if (unlikely(err))
++                              goto err_undo;
++              }
++
++              list_add(&block->right->tmp_link, &dfs);
++              list_add(&block->left->tmp_link, &dfs);
++      } while (1);
++
++      return ERR_PTR(-ENOSPC);
++
++err_undo:
++      /*
++       * We really don't want to leave around a bunch of split blocks, since
++       * bigger is better, so make sure we merge everything back before we
++       * free the allocated blocks.
++       */
++      buddy = __get_buddy(block);
++      if (buddy &&
++          (gpu_buddy_block_is_free(block) &&
++           gpu_buddy_block_is_free(buddy)))
++              __gpu_buddy_free(mm, block, false);
++      return ERR_PTR(err);
++}
++
++static struct gpu_buddy_block *
++__gpu_buddy_alloc_range_bias(struct gpu_buddy *mm,
++                           u64 start, u64 end,
++                           unsigned int order,
++                           unsigned long flags)
++{
++      struct gpu_buddy_block *block;
++      bool fallback = false;
++
++      block = __alloc_range_bias(mm, start, end, order,
++                                 flags, fallback);
++      if (IS_ERR(block))
++              return __alloc_range_bias(mm, start, end, order,
++                                        flags, !fallback);
++
++      return block;
++}
++
++static struct gpu_buddy_block *
++get_maxblock(struct gpu_buddy *mm,
++           unsigned int order,
++           enum gpu_buddy_free_tree tree)
++{
++      struct gpu_buddy_block *max_block = NULL, *block = NULL;
++      struct rb_root *root;
++      unsigned int i;
++
++      for (i = order; i <= mm->max_order; ++i) {
++              root = &mm->free_trees[tree][i];
++              block = rbtree_last_free_block(root);
++              if (!block)
++                      continue;
++
++              if (!max_block) {
++                      max_block = block;
++                      continue;
++              }
++
++              if (gpu_buddy_block_offset(block) >
++                  gpu_buddy_block_offset(max_block)) {
++                      max_block = block;
++              }
++      }
++
++      return max_block;
++}
++
++static struct gpu_buddy_block *
++alloc_from_freetree(struct gpu_buddy *mm,
++                  unsigned int order,
++                  unsigned long flags)
++{
++      struct gpu_buddy_block *block = NULL;
++      struct rb_root *root;
++      enum gpu_buddy_free_tree tree;
++      unsigned int tmp;
++      int err;
++
++      tree = (flags & GPU_BUDDY_CLEAR_ALLOCATION) ?
++              GPU_BUDDY_CLEAR_TREE : GPU_BUDDY_DIRTY_TREE;
++
++      if (flags & GPU_BUDDY_TOPDOWN_ALLOCATION) {
++              block = get_maxblock(mm, order, tree);
++              if (block)
++                      /* Store the obtained block order */
++                      tmp = gpu_buddy_block_order(block);
++      } else {
++              for (tmp = order; tmp <= mm->max_order; ++tmp) {
++                      /* Get RB tree root for this order and tree */
++                      root = &mm->free_trees[tree][tmp];
++                      block = rbtree_last_free_block(root);
++                      if (block)
++                              break;
++              }
++      }
++
++      if (!block) {
++              /* Try allocating from the other tree */
++              tree = (tree == GPU_BUDDY_CLEAR_TREE) ?
++                      GPU_BUDDY_DIRTY_TREE : GPU_BUDDY_CLEAR_TREE;
++
++              for (tmp = order; tmp <= mm->max_order; ++tmp) {
++                      root = &mm->free_trees[tree][tmp];
++                      block = rbtree_last_free_block(root);
++                      if (block)
++                              break;
++              }
++
++              if (!block)
++                      return ERR_PTR(-ENOSPC);
++      }
++
++      BUG_ON(!gpu_buddy_block_is_free(block));
++
++      while (tmp != order) {
++              err = split_block(mm, block);
++              if (unlikely(err))
++                      goto err_undo;
++
++              block = block->right;
++              tmp--;
++      }
++      return block;
++
++err_undo:
++      if (tmp != order)
++              __gpu_buddy_free(mm, block, false);
++      return ERR_PTR(err);
++}
++
++static int __alloc_range(struct gpu_buddy *mm,
++                       struct list_head *dfs,
++                       u64 start, u64 size,
++                       struct list_head *blocks,
++                       u64 *total_allocated_on_err)
++{
++      struct gpu_buddy_block *block;
++      struct gpu_buddy_block *buddy;
++      u64 total_allocated = 0;
++      LIST_HEAD(allocated);
++      u64 end;
++      int err;
++
++      end = start + size - 1;
++
++      do {
++              u64 block_start;
++              u64 block_end;
++
++              block = list_first_entry_or_null(dfs,
++                                               struct gpu_buddy_block,
++                                               tmp_link);
++              if (!block)
++                      break;
++
++              list_del(&block->tmp_link);
++
++              block_start = gpu_buddy_block_offset(block);
++              block_end = block_start + gpu_buddy_block_size(mm, block) - 1;
++
++              if (!overlaps(start, end, block_start, block_end))
++                      continue;
++
++              if (gpu_buddy_block_is_allocated(block)) {
++                      err = -ENOSPC;
++                      goto err_free;
++              }
++
++              if (contains(start, end, block_start, block_end)) {
++                      if (gpu_buddy_block_is_free(block)) {
++                              mark_allocated(mm, block);
++                              total_allocated += gpu_buddy_block_size(mm, block);
++                              mm->avail -= gpu_buddy_block_size(mm, block);
++                              if (gpu_buddy_block_is_clear(block))
++                                      mm->clear_avail -= gpu_buddy_block_size(mm, block);
++                              list_add_tail(&block->link, &allocated);
++                              continue;
++                      } else if (!mm->clear_avail) {
++                              err = -ENOSPC;
++                              goto err_free;
++                      }
++              }
++
++              if (!gpu_buddy_block_is_split(block)) {
++                      err = split_block(mm, block);
++                      if (unlikely(err))
++                              goto err_undo;
++              }
++
++              list_add(&block->right->tmp_link, dfs);
++              list_add(&block->left->tmp_link, dfs);
++      } while (1);
++
++      if (total_allocated < size) {
++              err = -ENOSPC;
++              goto err_free;
++      }
++
++      list_splice_tail(&allocated, blocks);
++
++      return 0;
++
++err_undo:
++      /*
++       * We really don't want to leave around a bunch of split blocks, since
++       * bigger is better, so make sure we merge everything back before we
++       * free the allocated blocks.
++       */
++      buddy = __get_buddy(block);
++      if (buddy &&
++          (gpu_buddy_block_is_free(block) &&
++           gpu_buddy_block_is_free(buddy)))
++              __gpu_buddy_free(mm, block, false);
++
++err_free:
++      if (err == -ENOSPC && total_allocated_on_err) {
++              list_splice_tail(&allocated, blocks);
++              *total_allocated_on_err = total_allocated;
++      } else {
++              gpu_buddy_free_list_internal(mm, &allocated);
++      }
++
++      return err;
++}
++
++static int __gpu_buddy_alloc_range(struct gpu_buddy *mm,
++                                 u64 start,
++                                 u64 size,
++                                 u64 *total_allocated_on_err,
++                                 struct list_head *blocks)
++{
++      LIST_HEAD(dfs);
++      int i;
++
++      for (i = 0; i < mm->n_roots; ++i)
++              list_add_tail(&mm->roots[i]->tmp_link, &dfs);
++
++      return __alloc_range(mm, &dfs, start, size,
++                           blocks, total_allocated_on_err);
++}
++
++static int __alloc_contig_try_harder(struct gpu_buddy *mm,
++                                   u64 size,
++                                   u64 min_block_size,
++                                   struct list_head *blocks)
++{
++      u64 rhs_offset, lhs_offset, lhs_size, filled;
++      struct gpu_buddy_block *block;
++      unsigned int tree, order;
++      LIST_HEAD(blocks_lhs);
++      unsigned long pages;
++      u64 modify_size;
++      int err;
++
++      modify_size = rounddown_pow_of_two(size);
++      pages = modify_size >> ilog2(mm->chunk_size);
++      order = fls(pages) - 1;
++      if (order == 0)
++              return -ENOSPC;
++
++      for_each_free_tree(tree) {
++              struct rb_root *root;
++              struct rb_node *iter;
++
++              root = &mm->free_trees[tree][order];
++              if (rbtree_is_empty(root))
++                      continue;
++
++              iter = rb_last(root);
++              while (iter) {
++                      block = rbtree_get_free_block(iter);
++
++                      /* Allocate blocks traversing RHS */
++                      rhs_offset = gpu_buddy_block_offset(block);
++                      err =  __gpu_buddy_alloc_range(mm, rhs_offset, size,
++                                                     &filled, blocks);
++                      if (!err || err != -ENOSPC)
++                              return err;
++
++                      lhs_size = max((size - filled), min_block_size);
++                      if (!IS_ALIGNED(lhs_size, min_block_size))
++                              lhs_size = round_up(lhs_size, min_block_size);
++
++                      /* Allocate blocks traversing LHS */
++                      lhs_offset = gpu_buddy_block_offset(block) - lhs_size;
++                      err =  __gpu_buddy_alloc_range(mm, lhs_offset, lhs_size,
++                                                     NULL, &blocks_lhs);
++                      if (!err) {
++                              list_splice(&blocks_lhs, blocks);
++                              return 0;
++                      } else if (err != -ENOSPC) {
++                              gpu_buddy_free_list_internal(mm, blocks);
++                              return err;
++                      }
++                      /* Free blocks for the next iteration */
++                      gpu_buddy_free_list_internal(mm, blocks);
++
++                      iter = rb_prev(iter);
++              }
++      }
++
++      return -ENOSPC;
++}
++
++/**
++ * gpu_buddy_block_trim - free unused pages
++ *
++ * @mm: GPU buddy manager
++ * @start: start address to begin the trimming.
++ * @new_size: original size requested
++ * @blocks: Input and output list of allocated blocks.
++ * MUST contain single block as input to be trimmed.
++ * On success will contain the newly allocated blocks
++ * making up the @new_size. Blocks always appear in
++ * ascending order
++ *
++ * For contiguous allocation, we round up the size to the nearest
++ * power of two value, drivers consume *actual* size, so remaining
++ * portions are unused and can be optionally freed with this function
++ *
++ * Returns:
++ * 0 on success, error code on failure.
++ */
++int gpu_buddy_block_trim(struct gpu_buddy *mm,
++                       u64 *start,
++                       u64 new_size,
++                       struct list_head *blocks)
++{
++      struct gpu_buddy_block *parent;
++      struct gpu_buddy_block *block;
++      u64 block_start, block_end;
++      LIST_HEAD(dfs);
++      u64 new_start;
++      int err;
++
++      if (!list_is_singular(blocks))
++              return -EINVAL;
++
++      block = list_first_entry(blocks,
++                               struct gpu_buddy_block,
++                               link);
++
++      block_start = gpu_buddy_block_offset(block);
++      block_end = block_start + gpu_buddy_block_size(mm, block);
++
++      if (WARN_ON(!gpu_buddy_block_is_allocated(block)))
++              return -EINVAL;
++
++      if (new_size > gpu_buddy_block_size(mm, block))
++              return -EINVAL;
++
++      if (!new_size || !IS_ALIGNED(new_size, mm->chunk_size))
++              return -EINVAL;
++
++      if (new_size == gpu_buddy_block_size(mm, block))
++              return 0;
++
++      new_start = block_start;
++      if (start) {
++              new_start = *start;
++
++              if (new_start < block_start)
++                      return -EINVAL;
++
++              if (!IS_ALIGNED(new_start, mm->chunk_size))
++                      return -EINVAL;
++
++              if (range_overflows(new_start, new_size, block_end))
++                      return -EINVAL;
++      }
++
++      list_del(&block->link);
++      mark_free(mm, block);
++      mm->avail += gpu_buddy_block_size(mm, block);
++      if (gpu_buddy_block_is_clear(block))
++              mm->clear_avail += gpu_buddy_block_size(mm, block);
++
++      /* Prevent recursively freeing this node */
++      parent = block->parent;
++      block->parent = NULL;
++
++      list_add(&block->tmp_link, &dfs);
++      err =  __alloc_range(mm, &dfs, new_start, new_size, blocks, NULL);
++      if (err) {
++              mark_allocated(mm, block);
++              mm->avail -= gpu_buddy_block_size(mm, block);
++              if (gpu_buddy_block_is_clear(block))
++                      mm->clear_avail -= gpu_buddy_block_size(mm, block);
++              list_add(&block->link, blocks);
++      }
++
++      block->parent = parent;
++      return err;
++}
++EXPORT_SYMBOL(gpu_buddy_block_trim);
++
++static struct gpu_buddy_block *
++__gpu_buddy_alloc_blocks(struct gpu_buddy *mm,
++                       u64 start, u64 end,
++                       unsigned int order,
++                       unsigned long flags)
++{
++      if (flags & GPU_BUDDY_RANGE_ALLOCATION)
++              /* Allocate traversing within the range */
++              return  __gpu_buddy_alloc_range_bias(mm, start, end,
++                                                   order, flags);
++      else
++              /* Allocate from freetree */
++              return alloc_from_freetree(mm, order, flags);
++}
++
++/**
++ * gpu_buddy_alloc_blocks - allocate power-of-two blocks
++ *
++ * @mm: GPU buddy manager to allocate from
++ * @start: start of the allowed range for this block
++ * @end: end of the allowed range for this block
++ * @size: size of the allocation in bytes
++ * @min_block_size: alignment of the allocation
++ * @blocks: output list head to add allocated blocks
++ * @flags: GPU_BUDDY_*_ALLOCATION flags
++ *
++ * alloc_range_bias() called on range limitations, which traverses
++ * the tree and returns the desired block.
++ *
++ * alloc_from_freetree() called when *no* range restrictions
++ * are enforced, which picks the block from the freetree.
++ *
++ * Returns:
++ * 0 on success, error code on failure.
++ */
++int gpu_buddy_alloc_blocks(struct gpu_buddy *mm,
++                         u64 start, u64 end, u64 size,
++                         u64 min_block_size,
++                         struct list_head *blocks,
++                         unsigned long flags)
++{
++      struct gpu_buddy_block *block = NULL;
++      u64 original_size, original_min_size;
++      unsigned int min_order, order;
++      LIST_HEAD(allocated);
++      unsigned long pages;
++      int err;
++
++      if (size < mm->chunk_size)
++              return -EINVAL;
++
++      if (min_block_size < mm->chunk_size)
++              return -EINVAL;
++
++      if (!is_power_of_2(min_block_size))
++              return -EINVAL;
++
++      if (!IS_ALIGNED(start | end | size, mm->chunk_size))
++              return -EINVAL;
++
++      if (end > mm->size)
++              return -EINVAL;
++
++      if (range_overflows(start, size, mm->size))
++              return -EINVAL;
++
++      /* Actual range allocation */
++      if (start + size == end) {
++              if (!IS_ALIGNED(start | end, min_block_size))
++                      return -EINVAL;
++
++              return __gpu_buddy_alloc_range(mm, start, size, NULL, blocks);
++      }
++
++      original_size = size;
++      original_min_size = min_block_size;
++
++      /* Roundup the size to power of 2 */
++      if (flags & GPU_BUDDY_CONTIGUOUS_ALLOCATION) {
++              size = roundup_pow_of_two(size);
++              min_block_size = size;
++      /* Align size value to min_block_size */
++      } else if (!IS_ALIGNED(size, min_block_size)) {
++              size = round_up(size, min_block_size);
++      }
++
++      pages = size >> ilog2(mm->chunk_size);
++      order = fls(pages) - 1;
++      min_order = ilog2(min_block_size) - ilog2(mm->chunk_size);
++
++      if (order > mm->max_order || size > mm->size) {
++              if ((flags & GPU_BUDDY_CONTIGUOUS_ALLOCATION) &&
++                  !(flags & GPU_BUDDY_RANGE_ALLOCATION))
++                      return __alloc_contig_try_harder(mm, original_size,
++                                                       original_min_size, blocks);
++
++              return -EINVAL;
++      }
++
++      do {
++              order = min(order, (unsigned int)fls(pages) - 1);
++              BUG_ON(order > mm->max_order);
++              BUG_ON(order < min_order);
++
++              do {
++                      block = __gpu_buddy_alloc_blocks(mm, start,
++                                                       end,
++                                                       order,
++                                                       flags);
++                      if (!IS_ERR(block))
++                              break;
++
++                      if (order-- == min_order) {
++                              /* Try allocation through force merge method */
++                              if (mm->clear_avail &&
++                                  !__force_merge(mm, start, end, min_order)) {
++                                      block = __gpu_buddy_alloc_blocks(mm, start,
++                                                                       end,
++                                                                       min_order,
++                                                                       flags);
++                                      if (!IS_ERR(block)) {
++                                              order = min_order;
++                                              break;
++                                      }
++                              }
++
++                              /*
++                               * Try contiguous block allocation through
++                               * try harder method.
++                               */
++                              if (flags & GPU_BUDDY_CONTIGUOUS_ALLOCATION &&
++                                  !(flags & GPU_BUDDY_RANGE_ALLOCATION))
++                                      return __alloc_contig_try_harder(mm,
++                                                                       original_size,
++                                                                       original_min_size,
++                                                                       blocks);
++                              err = -ENOSPC;
++                              goto err_free;
++                      }
++              } while (1);
++
++              mark_allocated(mm, block);
++              mm->avail -= gpu_buddy_block_size(mm, block);
++              if (gpu_buddy_block_is_clear(block))
++                      mm->clear_avail -= gpu_buddy_block_size(mm, block);
++              kmemleak_update_trace(block);
++              list_add_tail(&block->link, &allocated);
++
++              pages -= BIT(order);
++
++              if (!pages)
++                      break;
++      } while (1);
++
++      /* Trim the allocated block to the required size */
++      if (!(flags & GPU_BUDDY_TRIM_DISABLE) &&
++          original_size != size) {
++              struct list_head *trim_list;
++              LIST_HEAD(temp);
++              u64 trim_size;
++
++              trim_list = &allocated;
++              trim_size = original_size;
++
++              if (!list_is_singular(&allocated)) {
++                      block = list_last_entry(&allocated, typeof(*block), link);
++                      list_move(&block->link, &temp);
++                      trim_list = &temp;
++                      trim_size = gpu_buddy_block_size(mm, block) -
++                              (size - original_size);
++              }
++
++              gpu_buddy_block_trim(mm,
++                                   NULL,
++                                   trim_size,
++                                   trim_list);
++
++              if (!list_empty(&temp))
++                      list_splice_tail(trim_list, &allocated);
++      }
++
++      list_splice_tail(&allocated, blocks);
++      return 0;
++
++err_free:
++      gpu_buddy_free_list_internal(mm, &allocated);
++      return err;
++}
++EXPORT_SYMBOL(gpu_buddy_alloc_blocks);
++
++/**
++ * gpu_buddy_block_print - print block information
++ *
++ * @mm: GPU buddy manager
++ * @block: GPU buddy block
++ */
++void gpu_buddy_block_print(struct gpu_buddy *mm,
++                         struct gpu_buddy_block *block)
++{
++      u64 start = gpu_buddy_block_offset(block);
++      u64 size = gpu_buddy_block_size(mm, block);
++
++      pr_info("%#018llx-%#018llx: %llu\n", start, start + size, size);
++}
++EXPORT_SYMBOL(gpu_buddy_block_print);
++
++/**
++ * gpu_buddy_print - print allocator state
++ *
++ * @mm: GPU buddy manager
++ * @p: GPU printer to use
++ */
++void gpu_buddy_print(struct gpu_buddy *mm)
++{
++      int order;
++
++      pr_info("chunk_size: %lluKiB, total: %lluMiB, free: %lluMiB, clear_free: %lluMiB\n",
++              mm->chunk_size >> 10, mm->size >> 20, mm->avail >> 20, mm->clear_avail >> 20);
++
++      for (order = mm->max_order; order >= 0; order--) {
++              struct gpu_buddy_block *block, *tmp;
++              struct rb_root *root;
++              u64 count = 0, free;
++              unsigned int tree;
++
++              for_each_free_tree(tree) {
++                      root = &mm->free_trees[tree][order];
++
++                      rbtree_postorder_for_each_entry_safe(block, tmp, root, rb) {
++                              BUG_ON(!gpu_buddy_block_is_free(block));
++                              count++;
++                      }
++              }
++
++              free = count * (mm->chunk_size << order);
++              if (free < SZ_1M)
++                      pr_info("order-%2d free: %8llu KiB, blocks: %llu\n",
++                              order, free >> 10, count);
++              else
++                      pr_info("order-%2d free: %8llu MiB, blocks: %llu\n",
++                              order, free >> 20, count);
++      }
++}
++EXPORT_SYMBOL(gpu_buddy_print);
++
++static void gpu_buddy_module_exit(void)
++{
++      kmem_cache_destroy(slab_blocks);
++}
++
++static int __init gpu_buddy_module_init(void)
++{
++      slab_blocks = KMEM_CACHE(gpu_buddy_block, 0);
++      if (!slab_blocks)
++              return -ENOMEM;
++
++      return 0;
++}
++
++module_init(gpu_buddy_module_init);
++module_exit(gpu_buddy_module_exit);
++
++MODULE_DESCRIPTION("GPU Buddy Allocator");
++MODULE_LICENSE("Dual MIT/GPL");
+--- a/drivers/gpu/drm/Kconfig
++++ b/drivers/gpu/drm/Kconfig
+@@ -85,7 +85,6 @@ config DRM_KUNIT_TEST
+       select DRM_EXPORT_FOR_TESTS if m
+       select DRM_GEM_SHMEM_HELPER
+       select DRM_KUNIT_TEST_HELPERS
+-      select DRM_LIB_RANDOM
+       select PRIME_NUMBERS
+       default KUNIT_ALL_TESTS
+       help
+@@ -325,6 +324,7 @@ config DRM_GPUVM
+ config DRM_BUDDY
+       tristate
+       depends on DRM
++      select GPU_BUDDY
+       help
+         A page based buddy allocator
+@@ -507,10 +507,6 @@ config DRM_EXPORT_FOR_TESTS
+ config DRM_PANEL_BACKLIGHT_QUIRKS
+       tristate
+-config DRM_LIB_RANDOM
+-      bool
+-      default n
+-
+ config DRM_PRIVACY_SCREEN
+       bool
+       default n
+--- a/drivers/gpu/drm/Makefile
++++ b/drivers/gpu/drm/Makefile
+@@ -75,7 +75,6 @@ drm-y := \
+       drm_vblank_work.o \
+       drm_vma_manager.o \
+       drm_writeback.o
+-drm-$(CONFIG_DRM_LIB_RANDOM) += lib/drm_random.o
+ drm-$(CONFIG_COMPAT) += drm_ioc32.o
+ drm-$(CONFIG_DRM_PANEL) += drm_panel.o
+ drm-$(CONFIG_OF) += drm_of.o
+--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_res_cursor.h
++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_res_cursor.h
+@@ -55,7 +55,7 @@ static inline void amdgpu_res_first(stru
+                                   uint64_t start, uint64_t size,
+                                   struct amdgpu_res_cursor *cur)
+ {
+-      struct drm_buddy_block *block;
++      struct gpu_buddy_block *block;
+       struct list_head *head, *next;
+       struct drm_mm_node *node;
+@@ -71,7 +71,7 @@ static inline void amdgpu_res_first(stru
+               head = &to_amdgpu_vram_mgr_resource(res)->blocks;
+               block = list_first_entry_or_null(head,
+-                                               struct drm_buddy_block,
++                                               struct gpu_buddy_block,
+                                                link);
+               if (!block)
+                       goto fallback;
+@@ -81,7 +81,7 @@ static inline void amdgpu_res_first(stru
+                       next = block->link.next;
+                       if (next != head)
+-                              block = list_entry(next, struct drm_buddy_block, link);
++                              block = list_entry(next, struct gpu_buddy_block, link);
+               }
+               cur->start = amdgpu_vram_mgr_block_start(block) + start;
+@@ -124,7 +124,7 @@ fallback:
+  */
+ static inline void amdgpu_res_next(struct amdgpu_res_cursor *cur, uint64_t size)
+ {
+-      struct drm_buddy_block *block;
++      struct gpu_buddy_block *block;
+       struct drm_mm_node *node;
+       struct list_head *next;
+@@ -145,7 +145,7 @@ static inline void amdgpu_res_next(struc
+               block = cur->node;
+               next = block->link.next;
+-              block = list_entry(next, struct drm_buddy_block, link);
++              block = list_entry(next, struct gpu_buddy_block, link);
+               cur->node = block;
+               cur->start = amdgpu_vram_mgr_block_start(block);
+@@ -173,7 +173,7 @@ static inline void amdgpu_res_next(struc
+  */
+ static inline bool amdgpu_res_cleared(struct amdgpu_res_cursor *cur)
+ {
+-      struct drm_buddy_block *block;
++      struct gpu_buddy_block *block;
+       switch (cur->mem_type) {
+       case TTM_PL_VRAM:
+--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.c
++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.c
+@@ -24,6 +24,7 @@
+ #include <linux/dma-mapping.h>
+ #include <drm/ttm/ttm_range_manager.h>
++#include <drm/drm_buddy.h>
+ #include "amdgpu.h"
+ #include "amdgpu_vm.h"
+@@ -52,15 +53,15 @@ to_amdgpu_device(struct amdgpu_vram_mgr
+       return container_of(mgr, struct amdgpu_device, mman.vram_mgr);
+ }
+-static inline struct drm_buddy_block *
++static inline struct gpu_buddy_block *
+ amdgpu_vram_mgr_first_block(struct list_head *list)
+ {
+-      return list_first_entry_or_null(list, struct drm_buddy_block, link);
++      return list_first_entry_or_null(list, struct gpu_buddy_block, link);
+ }
+ static inline bool amdgpu_is_vram_mgr_blocks_contiguous(struct list_head *head)
+ {
+-      struct drm_buddy_block *block;
++      struct gpu_buddy_block *block;
+       u64 start, size;
+       block = amdgpu_vram_mgr_first_block(head);
+@@ -71,7 +72,7 @@ static inline bool amdgpu_is_vram_mgr_bl
+               start = amdgpu_vram_mgr_block_start(block);
+               size = amdgpu_vram_mgr_block_size(block);
+-              block = list_entry(block->link.next, struct drm_buddy_block, link);
++              block = list_entry(block->link.next, struct gpu_buddy_block, link);
+               if (start + size != amdgpu_vram_mgr_block_start(block))
+                       return false;
+       }
+@@ -81,7 +82,7 @@ static inline bool amdgpu_is_vram_mgr_bl
+ static inline u64 amdgpu_vram_mgr_blocks_size(struct list_head *head)
+ {
+-      struct drm_buddy_block *block;
++      struct gpu_buddy_block *block;
+       u64 size = 0;
+       list_for_each_entry(block, head, link)
+@@ -254,7 +255,7 @@ const struct attribute_group amdgpu_vram
+  * Calculate how many bytes of the DRM BUDDY block are inside visible VRAM
+  */
+ static u64 amdgpu_vram_mgr_vis_size(struct amdgpu_device *adev,
+-                                  struct drm_buddy_block *block)
++                                  struct gpu_buddy_block *block)
+ {
+       u64 start = amdgpu_vram_mgr_block_start(block);
+       u64 end = start + amdgpu_vram_mgr_block_size(block);
+@@ -279,7 +280,7 @@ u64 amdgpu_vram_mgr_bo_visible_size(stru
+       struct amdgpu_device *adev = amdgpu_ttm_adev(bo->tbo.bdev);
+       struct ttm_resource *res = bo->tbo.resource;
+       struct amdgpu_vram_mgr_resource *vres = to_amdgpu_vram_mgr_resource(res);
+-      struct drm_buddy_block *block;
++      struct gpu_buddy_block *block;
+       u64 usage = 0;
+       if (amdgpu_gmc_vram_full_visible(&adev->gmc))
+@@ -299,15 +300,15 @@ static void amdgpu_vram_mgr_do_reserve(s
+ {
+       struct amdgpu_vram_mgr *mgr = to_vram_mgr(man);
+       struct amdgpu_device *adev = to_amdgpu_device(mgr);
+-      struct drm_buddy *mm = &mgr->mm;
++      struct gpu_buddy *mm = &mgr->mm;
+       struct amdgpu_vram_reservation *rsv, *temp;
+-      struct drm_buddy_block *block;
++      struct gpu_buddy_block *block;
+       uint64_t vis_usage;
+       list_for_each_entry_safe(rsv, temp, &mgr->reservations_pending, blocks) {
+-              if (drm_buddy_alloc_blocks(mm, rsv->start, rsv->start + rsv->size,
++              if (gpu_buddy_alloc_blocks(mm, rsv->start, rsv->start + rsv->size,
+                                          rsv->size, mm->chunk_size, &rsv->allocated,
+-                                         DRM_BUDDY_RANGE_ALLOCATION))
++                                         GPU_BUDDY_RANGE_ALLOCATION))
+                       continue;
+               block = amdgpu_vram_mgr_first_block(&rsv->allocated);
+@@ -460,8 +461,8 @@ static int amdgpu_vram_mgr_new(struct tt
+       struct amdgpu_vram_mgr_resource *vres;
+       u64 size, remaining_size, lpfn, fpfn;
+       unsigned int adjust_dcc_size = 0;
+-      struct drm_buddy *mm = &mgr->mm;
+-      struct drm_buddy_block *block;
++      struct gpu_buddy *mm = &mgr->mm;
++      struct gpu_buddy_block *block;
+       unsigned long pages_per_block;
+       int r;
+@@ -503,17 +504,17 @@ static int amdgpu_vram_mgr_new(struct tt
+       INIT_LIST_HEAD(&vres->blocks);
+       if (place->flags & TTM_PL_FLAG_TOPDOWN)
+-              vres->flags |= DRM_BUDDY_TOPDOWN_ALLOCATION;
++              vres->flags |= GPU_BUDDY_TOPDOWN_ALLOCATION;
+       if (bo->flags & AMDGPU_GEM_CREATE_VRAM_CONTIGUOUS)
+-              vres->flags |= DRM_BUDDY_CONTIGUOUS_ALLOCATION;
++              vres->flags |= GPU_BUDDY_CONTIGUOUS_ALLOCATION;
+       if (bo->flags & AMDGPU_GEM_CREATE_VRAM_CLEARED)
+-              vres->flags |= DRM_BUDDY_CLEAR_ALLOCATION;
++              vres->flags |= GPU_BUDDY_CLEAR_ALLOCATION;
+       if (fpfn || lpfn != mgr->mm.size)
+               /* Allocate blocks in desired range */
+-              vres->flags |= DRM_BUDDY_RANGE_ALLOCATION;
++              vres->flags |= GPU_BUDDY_RANGE_ALLOCATION;
+       if (bo->flags & AMDGPU_GEM_CREATE_GFX12_DCC &&
+           adev->gmc.gmc_funcs->get_dcc_alignment)
+@@ -526,7 +527,7 @@ static int amdgpu_vram_mgr_new(struct tt
+               dcc_size = roundup_pow_of_two(vres->base.size + adjust_dcc_size);
+               remaining_size = (u64)dcc_size;
+-              vres->flags |= DRM_BUDDY_TRIM_DISABLE;
++              vres->flags |= GPU_BUDDY_TRIM_DISABLE;
+       }
+       mutex_lock(&mgr->lock);
+@@ -546,7 +547,7 @@ static int amdgpu_vram_mgr_new(struct tt
+               BUG_ON(min_block_size < mm->chunk_size);
+-              r = drm_buddy_alloc_blocks(mm, fpfn,
++              r = gpu_buddy_alloc_blocks(mm, fpfn,
+                                          lpfn,
+                                          size,
+                                          min_block_size,
+@@ -555,7 +556,7 @@ static int amdgpu_vram_mgr_new(struct tt
+               if (unlikely(r == -ENOSPC) && pages_per_block == ~0ul &&
+                   !(place->flags & TTM_PL_FLAG_CONTIGUOUS)) {
+-                      vres->flags &= ~DRM_BUDDY_CONTIGUOUS_ALLOCATION;
++                      vres->flags &= ~GPU_BUDDY_CONTIGUOUS_ALLOCATION;
+                       pages_per_block = max_t(u32, 2UL << (20UL - PAGE_SHIFT),
+                                               tbo->page_alignment);
+@@ -572,7 +573,7 @@ static int amdgpu_vram_mgr_new(struct tt
+       }
+       if (bo->flags & AMDGPU_GEM_CREATE_VRAM_CONTIGUOUS && adjust_dcc_size) {
+-              struct drm_buddy_block *dcc_block;
++              struct gpu_buddy_block *dcc_block;
+               unsigned long dcc_start;
+               u64 trim_start;
+@@ -582,7 +583,7 @@ static int amdgpu_vram_mgr_new(struct tt
+                       roundup((unsigned long)amdgpu_vram_mgr_block_start(dcc_block),
+                               adjust_dcc_size);
+               trim_start = (u64)dcc_start;
+-              drm_buddy_block_trim(mm, &trim_start,
++              gpu_buddy_block_trim(mm, &trim_start,
+                                    (u64)vres->base.size,
+                                    &vres->blocks);
+       }
+@@ -620,7 +621,7 @@ static int amdgpu_vram_mgr_new(struct tt
+       return 0;
+ error_free_blocks:
+-      drm_buddy_free_list(mm, &vres->blocks, 0);
++      gpu_buddy_free_list(mm, &vres->blocks, 0);
+       mutex_unlock(&mgr->lock);
+ error_fini:
+       ttm_resource_fini(man, &vres->base);
+@@ -643,15 +644,15 @@ static void amdgpu_vram_mgr_del(struct t
+       struct amdgpu_vram_mgr_resource *vres = to_amdgpu_vram_mgr_resource(res);
+       struct amdgpu_vram_mgr *mgr = to_vram_mgr(man);
+       struct amdgpu_device *adev = to_amdgpu_device(mgr);
+-      struct drm_buddy *mm = &mgr->mm;
+-      struct drm_buddy_block *block;
++      struct gpu_buddy *mm = &mgr->mm;
++      struct gpu_buddy_block *block;
+       uint64_t vis_usage = 0;
+       mutex_lock(&mgr->lock);
+       list_for_each_entry(block, &vres->blocks, link)
+               vis_usage += amdgpu_vram_mgr_vis_size(adev, block);
+-      drm_buddy_free_list(mm, &vres->blocks, vres->flags);
++      gpu_buddy_free_list(mm, &vres->blocks, vres->flags);
+       amdgpu_vram_mgr_do_reserve(man);
+       mutex_unlock(&mgr->lock);
+@@ -690,7 +691,7 @@ int amdgpu_vram_mgr_alloc_sgt(struct amd
+       if (!*sgt)
+               return -ENOMEM;
+-      /* Determine the number of DRM_BUDDY blocks to export */
++      /* Determine the number of GPU_BUDDY blocks to export */
+       amdgpu_res_first(res, offset, length, &cursor);
+       while (cursor.remaining) {
+               num_entries++;
+@@ -706,10 +707,10 @@ int amdgpu_vram_mgr_alloc_sgt(struct amd
+               sg->length = 0;
+       /*
+-       * Walk down DRM_BUDDY blocks to populate scatterlist nodes
+-       * @note: Use iterator api to get first the DRM_BUDDY block
++       * Walk down GPU_BUDDY blocks to populate scatterlist nodes
++       * @note: Use iterator api to get first the GPU_BUDDY block
+        * and the number of bytes from it. Access the following
+-       * DRM_BUDDY block(s) if more buffer needs to exported
++       * GPU_BUDDY block(s) if more buffer needs to exported
+        */
+       amdgpu_res_first(res, offset, length, &cursor);
+       for_each_sgtable_sg((*sgt), sg, i) {
+@@ -794,10 +795,10 @@ uint64_t amdgpu_vram_mgr_vis_usage(struc
+ void amdgpu_vram_mgr_clear_reset_blocks(struct amdgpu_device *adev)
+ {
+       struct amdgpu_vram_mgr *mgr = &adev->mman.vram_mgr;
+-      struct drm_buddy *mm = &mgr->mm;
++      struct gpu_buddy *mm = &mgr->mm;
+       mutex_lock(&mgr->lock);
+-      drm_buddy_reset_clear(mm, false);
++      gpu_buddy_reset_clear(mm, false);
+       mutex_unlock(&mgr->lock);
+ }
+@@ -817,7 +818,7 @@ static bool amdgpu_vram_mgr_intersects(s
+                                      size_t size)
+ {
+       struct amdgpu_vram_mgr_resource *mgr = to_amdgpu_vram_mgr_resource(res);
+-      struct drm_buddy_block *block;
++      struct gpu_buddy_block *block;
+       /* Check each drm buddy block individually */
+       list_for_each_entry(block, &mgr->blocks, link) {
+@@ -850,7 +851,7 @@ static bool amdgpu_vram_mgr_compatible(s
+                                      size_t size)
+ {
+       struct amdgpu_vram_mgr_resource *mgr = to_amdgpu_vram_mgr_resource(res);
+-      struct drm_buddy_block *block;
++      struct gpu_buddy_block *block;
+       /* Check each drm buddy block individually */
+       list_for_each_entry(block, &mgr->blocks, link) {
+@@ -879,7 +880,7 @@ static void amdgpu_vram_mgr_debug(struct
+                                 struct drm_printer *printer)
+ {
+       struct amdgpu_vram_mgr *mgr = to_vram_mgr(man);
+-      struct drm_buddy *mm = &mgr->mm;
++      struct gpu_buddy *mm = &mgr->mm;
+       struct amdgpu_vram_reservation *rsv;
+       drm_printf(printer, "  vis usage:%llu\n",
+@@ -938,7 +939,7 @@ int amdgpu_vram_mgr_init(struct amdgpu_d
+       if (!adev->gmc.is_app_apu) {
+               man->func = &amdgpu_vram_mgr_func;
+-              err = drm_buddy_init(&mgr->mm, man->size, PAGE_SIZE);
++              err = gpu_buddy_init(&mgr->mm, man->size, PAGE_SIZE);
+               if (err)
+                       return err;
+       } else {
+@@ -977,11 +978,11 @@ void amdgpu_vram_mgr_fini(struct amdgpu_
+               kfree(rsv);
+       list_for_each_entry_safe(rsv, temp, &mgr->reserved_pages, blocks) {
+-              drm_buddy_free_list(&mgr->mm, &rsv->allocated, 0);
++              gpu_buddy_free_list(&mgr->mm, &rsv->allocated, 0);
+               kfree(rsv);
+       }
+       if (!adev->gmc.is_app_apu)
+-              drm_buddy_fini(&mgr->mm);
++              gpu_buddy_fini(&mgr->mm);
+       mutex_unlock(&mgr->lock);
+       ttm_resource_manager_cleanup(man);
+--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.h
++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.h
+@@ -24,11 +24,11 @@
+ #ifndef __AMDGPU_VRAM_MGR_H__
+ #define __AMDGPU_VRAM_MGR_H__
+-#include <drm/drm_buddy.h>
++#include <linux/gpu_buddy.h>
+ struct amdgpu_vram_mgr {
+       struct ttm_resource_manager manager;
+-      struct drm_buddy mm;
++      struct gpu_buddy mm;
+       /* protects access to buffer objects */
+       struct mutex lock;
+       struct list_head reservations_pending;
+@@ -43,19 +43,19 @@ struct amdgpu_vram_mgr_resource {
+       unsigned long flags;
+ };
+-static inline u64 amdgpu_vram_mgr_block_start(struct drm_buddy_block *block)
++static inline u64 amdgpu_vram_mgr_block_start(struct gpu_buddy_block *block)
+ {
+-      return drm_buddy_block_offset(block);
++      return gpu_buddy_block_offset(block);
+ }
+-static inline u64 amdgpu_vram_mgr_block_size(struct drm_buddy_block *block)
++static inline u64 amdgpu_vram_mgr_block_size(struct gpu_buddy_block *block)
+ {
+-      return (u64)PAGE_SIZE << drm_buddy_block_order(block);
++      return (u64)PAGE_SIZE << gpu_buddy_block_order(block);
+ }
+-static inline bool amdgpu_vram_mgr_is_cleared(struct drm_buddy_block *block)
++static inline bool amdgpu_vram_mgr_is_cleared(struct gpu_buddy_block *block)
+ {
+-      return drm_buddy_block_is_clear(block);
++      return gpu_buddy_block_is_clear(block);
+ }
+ static inline struct amdgpu_vram_mgr_resource *
+@@ -66,7 +66,7 @@ to_amdgpu_vram_mgr_resource(struct ttm_r
+ static inline void amdgpu_vram_mgr_set_cleared(struct ttm_resource *res)
+ {
+-      to_amdgpu_vram_mgr_resource(res)->flags |= DRM_BUDDY_CLEARED;
++      to_amdgpu_vram_mgr_resource(res)->flags |= GPU_BUDDY_CLEARED;
+ }
+ #endif
+--- a/drivers/gpu/drm/drm_buddy.c
++++ b/drivers/gpu/drm/drm_buddy.c
+@@ -3,1252 +3,16 @@
+  * Copyright Â© 2021 Intel Corporation
+  */
++#include <kunit/test-bug.h>
++
++#include <linux/export.h>
+ #include <linux/kmemleak.h>
+ #include <linux/module.h>
+ #include <linux/sizes.h>
++#include <linux/gpu_buddy.h>
+ #include <drm/drm_buddy.h>
+-
+-enum drm_buddy_free_tree {
+-      DRM_BUDDY_CLEAR_TREE = 0,
+-      DRM_BUDDY_DIRTY_TREE,
+-      DRM_BUDDY_MAX_FREE_TREES,
+-};
+-
+-static struct kmem_cache *slab_blocks;
+-
+-#define for_each_free_tree(tree) \
+-      for ((tree) = 0; (tree) < DRM_BUDDY_MAX_FREE_TREES; (tree)++)
+-
+-static struct drm_buddy_block *drm_block_alloc(struct drm_buddy *mm,
+-                                             struct drm_buddy_block *parent,
+-                                             unsigned int order,
+-                                             u64 offset)
+-{
+-      struct drm_buddy_block *block;
+-
+-      BUG_ON(order > DRM_BUDDY_MAX_ORDER);
+-
+-      block = kmem_cache_zalloc(slab_blocks, GFP_KERNEL);
+-      if (!block)
+-              return NULL;
+-
+-      block->header = offset;
+-      block->header |= order;
+-      block->parent = parent;
+-
+-      RB_CLEAR_NODE(&block->rb);
+-
+-      BUG_ON(block->header & DRM_BUDDY_HEADER_UNUSED);
+-      return block;
+-}
+-
+-static void drm_block_free(struct drm_buddy *mm,
+-                         struct drm_buddy_block *block)
+-{
+-      kmem_cache_free(slab_blocks, block);
+-}
+-
+-static enum drm_buddy_free_tree
+-get_block_tree(struct drm_buddy_block *block)
+-{
+-      return drm_buddy_block_is_clear(block) ?
+-             DRM_BUDDY_CLEAR_TREE : DRM_BUDDY_DIRTY_TREE;
+-}
+-
+-static struct drm_buddy_block *
+-rbtree_get_free_block(const struct rb_node *node)
+-{
+-      return node ? rb_entry(node, struct drm_buddy_block, rb) : NULL;
+-}
+-
+-static struct drm_buddy_block *
+-rbtree_last_free_block(struct rb_root *root)
+-{
+-      return rbtree_get_free_block(rb_last(root));
+-}
+-
+-static bool rbtree_is_empty(struct rb_root *root)
+-{
+-      return RB_EMPTY_ROOT(root);
+-}
+-
+-static bool drm_buddy_block_offset_less(const struct drm_buddy_block *block,
+-                                      const struct drm_buddy_block *node)
+-{
+-      return drm_buddy_block_offset(block) < drm_buddy_block_offset(node);
+-}
+-
+-static bool rbtree_block_offset_less(struct rb_node *block,
+-                                   const struct rb_node *node)
+-{
+-      return drm_buddy_block_offset_less(rbtree_get_free_block(block),
+-                                         rbtree_get_free_block(node));
+-}
+-
+-static void rbtree_insert(struct drm_buddy *mm,
+-                        struct drm_buddy_block *block,
+-                        enum drm_buddy_free_tree tree)
+-{
+-      rb_add(&block->rb,
+-             &mm->free_trees[tree][drm_buddy_block_order(block)],
+-             rbtree_block_offset_less);
+-}
+-
+-static void rbtree_remove(struct drm_buddy *mm,
+-                        struct drm_buddy_block *block)
+-{
+-      unsigned int order = drm_buddy_block_order(block);
+-      enum drm_buddy_free_tree tree;
+-      struct rb_root *root;
+-
+-      tree = get_block_tree(block);
+-      root = &mm->free_trees[tree][order];
+-
+-      rb_erase(&block->rb, root);
+-      RB_CLEAR_NODE(&block->rb);
+-}
+-
+-static void clear_reset(struct drm_buddy_block *block)
+-{
+-      block->header &= ~DRM_BUDDY_HEADER_CLEAR;
+-}
+-
+-static void mark_cleared(struct drm_buddy_block *block)
+-{
+-      block->header |= DRM_BUDDY_HEADER_CLEAR;
+-}
+-
+-static void mark_allocated(struct drm_buddy *mm,
+-                         struct drm_buddy_block *block)
+-{
+-      block->header &= ~DRM_BUDDY_HEADER_STATE;
+-      block->header |= DRM_BUDDY_ALLOCATED;
+-
+-      rbtree_remove(mm, block);
+-}
+-
+-static void mark_free(struct drm_buddy *mm,
+-                    struct drm_buddy_block *block)
+-{
+-      enum drm_buddy_free_tree tree;
+-
+-      block->header &= ~DRM_BUDDY_HEADER_STATE;
+-      block->header |= DRM_BUDDY_FREE;
+-
+-      tree = get_block_tree(block);
+-      rbtree_insert(mm, block, tree);
+-}
+-
+-static void mark_split(struct drm_buddy *mm,
+-                     struct drm_buddy_block *block)
+-{
+-      block->header &= ~DRM_BUDDY_HEADER_STATE;
+-      block->header |= DRM_BUDDY_SPLIT;
+-
+-      rbtree_remove(mm, block);
+-}
+-
+-static inline bool overlaps(u64 s1, u64 e1, u64 s2, u64 e2)
+-{
+-      return s1 <= e2 && e1 >= s2;
+-}
+-
+-static inline bool contains(u64 s1, u64 e1, u64 s2, u64 e2)
+-{
+-      return s1 <= s2 && e1 >= e2;
+-}
+-
+-static struct drm_buddy_block *
+-__get_buddy(struct drm_buddy_block *block)
+-{
+-      struct drm_buddy_block *parent;
+-
+-      parent = block->parent;
+-      if (!parent)
+-              return NULL;
+-
+-      if (parent->left == block)
+-              return parent->right;
+-
+-      return parent->left;
+-}
+-
+-static unsigned int __drm_buddy_free(struct drm_buddy *mm,
+-                                   struct drm_buddy_block *block,
+-                                   bool force_merge)
+-{
+-      struct drm_buddy_block *parent;
+-      unsigned int order;
+-
+-      while ((parent = block->parent)) {
+-              struct drm_buddy_block *buddy;
+-
+-              buddy = __get_buddy(block);
+-
+-              if (!drm_buddy_block_is_free(buddy))
+-                      break;
+-
+-              if (!force_merge) {
+-                      /*
+-                       * Check the block and its buddy clear state and exit
+-                       * the loop if they both have the dissimilar state.
+-                       */
+-                      if (drm_buddy_block_is_clear(block) !=
+-                          drm_buddy_block_is_clear(buddy))
+-                              break;
+-
+-                      if (drm_buddy_block_is_clear(block))
+-                              mark_cleared(parent);
+-              }
+-
+-              rbtree_remove(mm, buddy);
+-              if (force_merge && drm_buddy_block_is_clear(buddy))
+-                      mm->clear_avail -= drm_buddy_block_size(mm, buddy);
+-
+-              drm_block_free(mm, block);
+-              drm_block_free(mm, buddy);
+-
+-              block = parent;
+-      }
+-
+-      order = drm_buddy_block_order(block);
+-      mark_free(mm, block);
+-
+-      return order;
+-}
+-
+-static int __force_merge(struct drm_buddy *mm,
+-                       u64 start,
+-                       u64 end,
+-                       unsigned int min_order)
+-{
+-      unsigned int tree, order;
+-      int i;
+-
+-      if (!min_order)
+-              return -ENOMEM;
+-
+-      if (min_order > mm->max_order)
+-              return -EINVAL;
+-
+-      for_each_free_tree(tree) {
+-              for (i = min_order - 1; i >= 0; i--) {
+-                      struct rb_node *iter = rb_last(&mm->free_trees[tree][i]);
+-
+-                      while (iter) {
+-                              struct drm_buddy_block *block, *buddy;
+-                              u64 block_start, block_end;
+-
+-                              block = rbtree_get_free_block(iter);
+-                              iter = rb_prev(iter);
+-
+-                              if (!block || !block->parent)
+-                                      continue;
+-
+-                              block_start = drm_buddy_block_offset(block);
+-                              block_end = block_start + drm_buddy_block_size(mm, block) - 1;
+-
+-                              if (!contains(start, end, block_start, block_end))
+-                                      continue;
+-
+-                              buddy = __get_buddy(block);
+-                              if (!drm_buddy_block_is_free(buddy))
+-                                      continue;
+-
+-                              WARN_ON(drm_buddy_block_is_clear(block) ==
+-                                      drm_buddy_block_is_clear(buddy));
+-
+-                              /*
+-                               * Advance to the next node when the current node is the buddy,
+-                               * as freeing the block will also remove its buddy from the tree.
+-                               */
+-                              if (iter == &buddy->rb)
+-                                      iter = rb_prev(iter);
+-
+-                              rbtree_remove(mm, block);
+-                              if (drm_buddy_block_is_clear(block))
+-                                      mm->clear_avail -= drm_buddy_block_size(mm, block);
+-
+-                              order = __drm_buddy_free(mm, block, true);
+-                              if (order >= min_order)
+-                                      return 0;
+-                      }
+-              }
+-      }
+-
+-      return -ENOMEM;
+-}
+-
+-/**
+- * drm_buddy_init - init memory manager
+- *
+- * @mm: DRM buddy manager to initialize
+- * @size: size in bytes to manage
+- * @chunk_size: minimum page size in bytes for our allocations
+- *
+- * Initializes the memory manager and its resources.
+- *
+- * Returns:
+- * 0 on success, error code on failure.
+- */
+-int drm_buddy_init(struct drm_buddy *mm, u64 size, u64 chunk_size)
+-{
+-      unsigned int i, j, root_count = 0;
+-      u64 offset = 0;
+-
+-      if (size < chunk_size)
+-              return -EINVAL;
+-
+-      if (chunk_size < SZ_4K)
+-              return -EINVAL;
+-
+-      if (!is_power_of_2(chunk_size))
+-              return -EINVAL;
+-
+-      size = round_down(size, chunk_size);
+-
+-      mm->size = size;
+-      mm->avail = size;
+-      mm->clear_avail = 0;
+-      mm->chunk_size = chunk_size;
+-      mm->max_order = ilog2(size) - ilog2(chunk_size);
+-
+-      BUG_ON(mm->max_order > DRM_BUDDY_MAX_ORDER);
+-
+-      mm->free_trees = kmalloc_array(DRM_BUDDY_MAX_FREE_TREES,
+-                                     sizeof(*mm->free_trees),
+-                                     GFP_KERNEL);
+-      if (!mm->free_trees)
+-              return -ENOMEM;
+-
+-      for_each_free_tree(i) {
+-              mm->free_trees[i] = kmalloc_array(mm->max_order + 1,
+-                                                sizeof(struct rb_root),
+-                                                GFP_KERNEL);
+-              if (!mm->free_trees[i])
+-                      goto out_free_tree;
+-
+-              for (j = 0; j <= mm->max_order; ++j)
+-                      mm->free_trees[i][j] = RB_ROOT;
+-      }
+-
+-      mm->n_roots = hweight64(size);
+-
+-      mm->roots = kmalloc_array(mm->n_roots,
+-                                sizeof(struct drm_buddy_block *),
+-                                GFP_KERNEL);
+-      if (!mm->roots)
+-              goto out_free_tree;
+-
+-      /*
+-       * Split into power-of-two blocks, in case we are given a size that is
+-       * not itself a power-of-two.
+-       */
+-      do {
+-              struct drm_buddy_block *root;
+-              unsigned int order;
+-              u64 root_size;
+-
+-              order = ilog2(size) - ilog2(chunk_size);
+-              root_size = chunk_size << order;
+-
+-              root = drm_block_alloc(mm, NULL, order, offset);
+-              if (!root)
+-                      goto out_free_roots;
+-
+-              mark_free(mm, root);
+-
+-              BUG_ON(root_count > mm->max_order);
+-              BUG_ON(drm_buddy_block_size(mm, root) < chunk_size);
+-
+-              mm->roots[root_count] = root;
+-
+-              offset += root_size;
+-              size -= root_size;
+-              root_count++;
+-      } while (size);
+-
+-      return 0;
+-
+-out_free_roots:
+-      while (root_count--)
+-              drm_block_free(mm, mm->roots[root_count]);
+-      kfree(mm->roots);
+-out_free_tree:
+-      while (i--)
+-              kfree(mm->free_trees[i]);
+-      kfree(mm->free_trees);
+-      return -ENOMEM;
+-}
+-EXPORT_SYMBOL(drm_buddy_init);
+-
+-/**
+- * drm_buddy_fini - tear down the memory manager
+- *
+- * @mm: DRM buddy manager to free
+- *
+- * Cleanup memory manager resources and the freetree
+- */
+-void drm_buddy_fini(struct drm_buddy *mm)
+-{
+-      u64 root_size, size, start;
+-      unsigned int order;
+-      int i;
+-
+-      size = mm->size;
+-
+-      for (i = 0; i < mm->n_roots; ++i) {
+-              order = ilog2(size) - ilog2(mm->chunk_size);
+-              start = drm_buddy_block_offset(mm->roots[i]);
+-              __force_merge(mm, start, start + size, order);
+-
+-              WARN_ON(!drm_buddy_block_is_free(mm->roots[i]));
+-              drm_block_free(mm, mm->roots[i]);
+-
+-              root_size = mm->chunk_size << order;
+-              size -= root_size;
+-      }
+-
+-      WARN_ON(mm->avail != mm->size);
+-
+-      for_each_free_tree(i)
+-              kfree(mm->free_trees[i]);
+-      kfree(mm->free_trees);
+-      kfree(mm->roots);
+-}
+-EXPORT_SYMBOL(drm_buddy_fini);
+-
+-static int split_block(struct drm_buddy *mm,
+-                     struct drm_buddy_block *block)
+-{
+-      unsigned int block_order = drm_buddy_block_order(block) - 1;
+-      u64 offset = drm_buddy_block_offset(block);
+-
+-      BUG_ON(!drm_buddy_block_is_free(block));
+-      BUG_ON(!drm_buddy_block_order(block));
+-
+-      block->left = drm_block_alloc(mm, block, block_order, offset);
+-      if (!block->left)
+-              return -ENOMEM;
+-
+-      block->right = drm_block_alloc(mm, block, block_order,
+-                                     offset + (mm->chunk_size << block_order));
+-      if (!block->right) {
+-              drm_block_free(mm, block->left);
+-              return -ENOMEM;
+-      }
+-
+-      mark_split(mm, block);
+-
+-      if (drm_buddy_block_is_clear(block)) {
+-              mark_cleared(block->left);
+-              mark_cleared(block->right);
+-              clear_reset(block);
+-      }
+-
+-      mark_free(mm, block->left);
+-      mark_free(mm, block->right);
+-
+-      return 0;
+-}
+-
+-/**
+- * drm_get_buddy - get buddy address
+- *
+- * @block: DRM buddy block
+- *
+- * Returns the corresponding buddy block for @block, or NULL
+- * if this is a root block and can't be merged further.
+- * Requires some kind of locking to protect against
+- * any concurrent allocate and free operations.
+- */
+-struct drm_buddy_block *
+-drm_get_buddy(struct drm_buddy_block *block)
+-{
+-      return __get_buddy(block);
+-}
+-EXPORT_SYMBOL(drm_get_buddy);
+-
+-/**
+- * drm_buddy_reset_clear - reset blocks clear state
+- *
+- * @mm: DRM buddy manager
+- * @is_clear: blocks clear state
+- *
+- * Reset the clear state based on @is_clear value for each block
+- * in the freetree.
+- */
+-void drm_buddy_reset_clear(struct drm_buddy *mm, bool is_clear)
+-{
+-      enum drm_buddy_free_tree src_tree, dst_tree;
+-      u64 root_size, size, start;
+-      unsigned int order;
+-      int i;
+-
+-      size = mm->size;
+-      for (i = 0; i < mm->n_roots; ++i) {
+-              order = ilog2(size) - ilog2(mm->chunk_size);
+-              start = drm_buddy_block_offset(mm->roots[i]);
+-              __force_merge(mm, start, start + size, order);
+-
+-              root_size = mm->chunk_size << order;
+-              size -= root_size;
+-      }
+-
+-      src_tree = is_clear ? DRM_BUDDY_DIRTY_TREE : DRM_BUDDY_CLEAR_TREE;
+-      dst_tree = is_clear ? DRM_BUDDY_CLEAR_TREE : DRM_BUDDY_DIRTY_TREE;
+-
+-      for (i = 0; i <= mm->max_order; ++i) {
+-              struct rb_root *root = &mm->free_trees[src_tree][i];
+-              struct drm_buddy_block *block, *tmp;
+-
+-              rbtree_postorder_for_each_entry_safe(block, tmp, root, rb) {
+-                      rbtree_remove(mm, block);
+-                      if (is_clear) {
+-                              mark_cleared(block);
+-                              mm->clear_avail += drm_buddy_block_size(mm, block);
+-                      } else {
+-                              clear_reset(block);
+-                              mm->clear_avail -= drm_buddy_block_size(mm, block);
+-                      }
+-
+-                      rbtree_insert(mm, block, dst_tree);
+-              }
+-      }
+-}
+-EXPORT_SYMBOL(drm_buddy_reset_clear);
+-
+-/**
+- * drm_buddy_free_block - free a block
+- *
+- * @mm: DRM buddy manager
+- * @block: block to be freed
+- */
+-void drm_buddy_free_block(struct drm_buddy *mm,
+-                        struct drm_buddy_block *block)
+-{
+-      BUG_ON(!drm_buddy_block_is_allocated(block));
+-      mm->avail += drm_buddy_block_size(mm, block);
+-      if (drm_buddy_block_is_clear(block))
+-              mm->clear_avail += drm_buddy_block_size(mm, block);
+-
+-      __drm_buddy_free(mm, block, false);
+-}
+-EXPORT_SYMBOL(drm_buddy_free_block);
+-
+-static void __drm_buddy_free_list(struct drm_buddy *mm,
+-                                struct list_head *objects,
+-                                bool mark_clear,
+-                                bool mark_dirty)
+-{
+-      struct drm_buddy_block *block, *on;
+-
+-      WARN_ON(mark_dirty && mark_clear);
+-
+-      list_for_each_entry_safe(block, on, objects, link) {
+-              if (mark_clear)
+-                      mark_cleared(block);
+-              else if (mark_dirty)
+-                      clear_reset(block);
+-              drm_buddy_free_block(mm, block);
+-              cond_resched();
+-      }
+-      INIT_LIST_HEAD(objects);
+-}
+-
+-static void drm_buddy_free_list_internal(struct drm_buddy *mm,
+-                                       struct list_head *objects)
+-{
+-      /*
+-       * Don't touch the clear/dirty bit, since allocation is still internal
+-       * at this point. For example we might have just failed part of the
+-       * allocation.
+-       */
+-      __drm_buddy_free_list(mm, objects, false, false);
+-}
+-
+-/**
+- * drm_buddy_free_list - free blocks
+- *
+- * @mm: DRM buddy manager
+- * @objects: input list head to free blocks
+- * @flags: optional flags like DRM_BUDDY_CLEARED
+- */
+-void drm_buddy_free_list(struct drm_buddy *mm,
+-                       struct list_head *objects,
+-                       unsigned int flags)
+-{
+-      bool mark_clear = flags & DRM_BUDDY_CLEARED;
+-
+-      __drm_buddy_free_list(mm, objects, mark_clear, !mark_clear);
+-}
+-EXPORT_SYMBOL(drm_buddy_free_list);
+-
+-static bool block_incompatible(struct drm_buddy_block *block, unsigned int flags)
+-{
+-      bool needs_clear = flags & DRM_BUDDY_CLEAR_ALLOCATION;
+-
+-      return needs_clear != drm_buddy_block_is_clear(block);
+-}
+-
+-static struct drm_buddy_block *
+-__alloc_range_bias(struct drm_buddy *mm,
+-                 u64 start, u64 end,
+-                 unsigned int order,
+-                 unsigned long flags,
+-                 bool fallback)
+-{
+-      u64 req_size = mm->chunk_size << order;
+-      struct drm_buddy_block *block;
+-      struct drm_buddy_block *buddy;
+-      LIST_HEAD(dfs);
+-      int err;
+-      int i;
+-
+-      end = end - 1;
+-
+-      for (i = 0; i < mm->n_roots; ++i)
+-              list_add_tail(&mm->roots[i]->tmp_link, &dfs);
+-
+-      do {
+-              u64 block_start;
+-              u64 block_end;
+-
+-              block = list_first_entry_or_null(&dfs,
+-                                               struct drm_buddy_block,
+-                                               tmp_link);
+-              if (!block)
+-                      break;
+-
+-              list_del(&block->tmp_link);
+-
+-              if (drm_buddy_block_order(block) < order)
+-                      continue;
+-
+-              block_start = drm_buddy_block_offset(block);
+-              block_end = block_start + drm_buddy_block_size(mm, block) - 1;
+-
+-              if (!overlaps(start, end, block_start, block_end))
+-                      continue;
+-
+-              if (drm_buddy_block_is_allocated(block))
+-                      continue;
+-
+-              if (block_start < start || block_end > end) {
+-                      u64 adjusted_start = max(block_start, start);
+-                      u64 adjusted_end = min(block_end, end);
+-
+-                      if (round_down(adjusted_end + 1, req_size) <=
+-                          round_up(adjusted_start, req_size))
+-                              continue;
+-              }
+-
+-              if (!fallback && block_incompatible(block, flags))
+-                      continue;
+-
+-              if (contains(start, end, block_start, block_end) &&
+-                  order == drm_buddy_block_order(block)) {
+-                      /*
+-                       * Find the free block within the range.
+-                       */
+-                      if (drm_buddy_block_is_free(block))
+-                              return block;
+-
+-                      continue;
+-              }
+-
+-              if (!drm_buddy_block_is_split(block)) {
+-                      err = split_block(mm, block);
+-                      if (unlikely(err))
+-                              goto err_undo;
+-              }
+-
+-              list_add(&block->right->tmp_link, &dfs);
+-              list_add(&block->left->tmp_link, &dfs);
+-      } while (1);
+-
+-      return ERR_PTR(-ENOSPC);
+-
+-err_undo:
+-      /*
+-       * We really don't want to leave around a bunch of split blocks, since
+-       * bigger is better, so make sure we merge everything back before we
+-       * free the allocated blocks.
+-       */
+-      buddy = __get_buddy(block);
+-      if (buddy &&
+-          (drm_buddy_block_is_free(block) &&
+-           drm_buddy_block_is_free(buddy)))
+-              __drm_buddy_free(mm, block, false);
+-      return ERR_PTR(err);
+-}
+-
+-static struct drm_buddy_block *
+-__drm_buddy_alloc_range_bias(struct drm_buddy *mm,
+-                           u64 start, u64 end,
+-                           unsigned int order,
+-                           unsigned long flags)
+-{
+-      struct drm_buddy_block *block;
+-      bool fallback = false;
+-
+-      block = __alloc_range_bias(mm, start, end, order,
+-                                 flags, fallback);
+-      if (IS_ERR(block))
+-              return __alloc_range_bias(mm, start, end, order,
+-                                        flags, !fallback);
+-
+-      return block;
+-}
+-
+-static struct drm_buddy_block *
+-get_maxblock(struct drm_buddy *mm,
+-           unsigned int order,
+-           enum drm_buddy_free_tree tree)
+-{
+-      struct drm_buddy_block *max_block = NULL, *block = NULL;
+-      struct rb_root *root;
+-      unsigned int i;
+-
+-      for (i = order; i <= mm->max_order; ++i) {
+-              root = &mm->free_trees[tree][i];
+-              block = rbtree_last_free_block(root);
+-              if (!block)
+-                      continue;
+-
+-              if (!max_block) {
+-                      max_block = block;
+-                      continue;
+-              }
+-
+-              if (drm_buddy_block_offset(block) >
+-                  drm_buddy_block_offset(max_block)) {
+-                      max_block = block;
+-              }
+-      }
+-
+-      return max_block;
+-}
+-
+-static struct drm_buddy_block *
+-alloc_from_freetree(struct drm_buddy *mm,
+-                  unsigned int order,
+-                  unsigned long flags)
+-{
+-      struct drm_buddy_block *block = NULL;
+-      struct rb_root *root;
+-      enum drm_buddy_free_tree tree;
+-      unsigned int tmp;
+-      int err;
+-
+-      tree = (flags & DRM_BUDDY_CLEAR_ALLOCATION) ?
+-              DRM_BUDDY_CLEAR_TREE : DRM_BUDDY_DIRTY_TREE;
+-
+-      if (flags & DRM_BUDDY_TOPDOWN_ALLOCATION) {
+-              block = get_maxblock(mm, order, tree);
+-              if (block)
+-                      /* Store the obtained block order */
+-                      tmp = drm_buddy_block_order(block);
+-      } else {
+-              for (tmp = order; tmp <= mm->max_order; ++tmp) {
+-                      /* Get RB tree root for this order and tree */
+-                      root = &mm->free_trees[tree][tmp];
+-                      block = rbtree_last_free_block(root);
+-                      if (block)
+-                              break;
+-              }
+-      }
+-
+-      if (!block) {
+-              /* Try allocating from the other tree */
+-              tree = (tree == DRM_BUDDY_CLEAR_TREE) ?
+-                      DRM_BUDDY_DIRTY_TREE : DRM_BUDDY_CLEAR_TREE;
+-
+-              for (tmp = order; tmp <= mm->max_order; ++tmp) {
+-                      root = &mm->free_trees[tree][tmp];
+-                      block = rbtree_last_free_block(root);
+-                      if (block)
+-                              break;
+-              }
+-
+-              if (!block)
+-                      return ERR_PTR(-ENOSPC);
+-      }
+-
+-      BUG_ON(!drm_buddy_block_is_free(block));
+-
+-      while (tmp != order) {
+-              err = split_block(mm, block);
+-              if (unlikely(err))
+-                      goto err_undo;
+-
+-              block = block->right;
+-              tmp--;
+-      }
+-      return block;
+-
+-err_undo:
+-      if (tmp != order)
+-              __drm_buddy_free(mm, block, false);
+-      return ERR_PTR(err);
+-}
+-
+-static int __alloc_range(struct drm_buddy *mm,
+-                       struct list_head *dfs,
+-                       u64 start, u64 size,
+-                       struct list_head *blocks,
+-                       u64 *total_allocated_on_err)
+-{
+-      struct drm_buddy_block *block;
+-      struct drm_buddy_block *buddy;
+-      u64 total_allocated = 0;
+-      LIST_HEAD(allocated);
+-      u64 end;
+-      int err;
+-
+-      end = start + size - 1;
+-
+-      do {
+-              u64 block_start;
+-              u64 block_end;
+-
+-              block = list_first_entry_or_null(dfs,
+-                                               struct drm_buddy_block,
+-                                               tmp_link);
+-              if (!block)
+-                      break;
+-
+-              list_del(&block->tmp_link);
+-
+-              block_start = drm_buddy_block_offset(block);
+-              block_end = block_start + drm_buddy_block_size(mm, block) - 1;
+-
+-              if (!overlaps(start, end, block_start, block_end))
+-                      continue;
+-
+-              if (drm_buddy_block_is_allocated(block)) {
+-                      err = -ENOSPC;
+-                      goto err_free;
+-              }
+-
+-              if (contains(start, end, block_start, block_end)) {
+-                      if (drm_buddy_block_is_free(block)) {
+-                              mark_allocated(mm, block);
+-                              total_allocated += drm_buddy_block_size(mm, block);
+-                              mm->avail -= drm_buddy_block_size(mm, block);
+-                              if (drm_buddy_block_is_clear(block))
+-                                      mm->clear_avail -= drm_buddy_block_size(mm, block);
+-                              list_add_tail(&block->link, &allocated);
+-                              continue;
+-                      } else if (!mm->clear_avail) {
+-                              err = -ENOSPC;
+-                              goto err_free;
+-                      }
+-              }
+-
+-              if (!drm_buddy_block_is_split(block)) {
+-                      err = split_block(mm, block);
+-                      if (unlikely(err))
+-                              goto err_undo;
+-              }
+-
+-              list_add(&block->right->tmp_link, dfs);
+-              list_add(&block->left->tmp_link, dfs);
+-      } while (1);
+-
+-      if (total_allocated < size) {
+-              err = -ENOSPC;
+-              goto err_free;
+-      }
+-
+-      list_splice_tail(&allocated, blocks);
+-
+-      return 0;
+-
+-err_undo:
+-      /*
+-       * We really don't want to leave around a bunch of split blocks, since
+-       * bigger is better, so make sure we merge everything back before we
+-       * free the allocated blocks.
+-       */
+-      buddy = __get_buddy(block);
+-      if (buddy &&
+-          (drm_buddy_block_is_free(block) &&
+-           drm_buddy_block_is_free(buddy)))
+-              __drm_buddy_free(mm, block, false);
+-
+-err_free:
+-      if (err == -ENOSPC && total_allocated_on_err) {
+-              list_splice_tail(&allocated, blocks);
+-              *total_allocated_on_err = total_allocated;
+-      } else {
+-              drm_buddy_free_list_internal(mm, &allocated);
+-      }
+-
+-      return err;
+-}
+-
+-static int __drm_buddy_alloc_range(struct drm_buddy *mm,
+-                                 u64 start,
+-                                 u64 size,
+-                                 u64 *total_allocated_on_err,
+-                                 struct list_head *blocks)
+-{
+-      LIST_HEAD(dfs);
+-      int i;
+-
+-      for (i = 0; i < mm->n_roots; ++i)
+-              list_add_tail(&mm->roots[i]->tmp_link, &dfs);
+-
+-      return __alloc_range(mm, &dfs, start, size,
+-                           blocks, total_allocated_on_err);
+-}
+-
+-static int __alloc_contig_try_harder(struct drm_buddy *mm,
+-                                   u64 size,
+-                                   u64 min_block_size,
+-                                   struct list_head *blocks)
+-{
+-      u64 rhs_offset, lhs_offset, lhs_size, filled;
+-      struct drm_buddy_block *block;
+-      unsigned int tree, order;
+-      LIST_HEAD(blocks_lhs);
+-      unsigned long pages;
+-      u64 modify_size;
+-      int err;
+-
+-      modify_size = rounddown_pow_of_two(size);
+-      pages = modify_size >> ilog2(mm->chunk_size);
+-      order = fls(pages) - 1;
+-      if (order == 0)
+-              return -ENOSPC;
+-
+-      for_each_free_tree(tree) {
+-              struct rb_root *root;
+-              struct rb_node *iter;
+-
+-              root = &mm->free_trees[tree][order];
+-              if (rbtree_is_empty(root))
+-                      continue;
+-
+-              iter = rb_last(root);
+-              while (iter) {
+-                      block = rbtree_get_free_block(iter);
+-
+-                      /* Allocate blocks traversing RHS */
+-                      rhs_offset = drm_buddy_block_offset(block);
+-                      err =  __drm_buddy_alloc_range(mm, rhs_offset, size,
+-                                                     &filled, blocks);
+-                      if (!err || err != -ENOSPC)
+-                              return err;
+-
+-                      lhs_size = max((size - filled), min_block_size);
+-                      if (!IS_ALIGNED(lhs_size, min_block_size))
+-                              lhs_size = round_up(lhs_size, min_block_size);
+-
+-                      /* Allocate blocks traversing LHS */
+-                      lhs_offset = drm_buddy_block_offset(block) - lhs_size;
+-                      err =  __drm_buddy_alloc_range(mm, lhs_offset, lhs_size,
+-                                                     NULL, &blocks_lhs);
+-                      if (!err) {
+-                              list_splice(&blocks_lhs, blocks);
+-                              return 0;
+-                      } else if (err != -ENOSPC) {
+-                              drm_buddy_free_list_internal(mm, blocks);
+-                              return err;
+-                      }
+-                      /* Free blocks for the next iteration */
+-                      drm_buddy_free_list_internal(mm, blocks);
+-
+-                      iter = rb_prev(iter);
+-              }
+-      }
+-
+-      return -ENOSPC;
+-}
+-
+-/**
+- * drm_buddy_block_trim - free unused pages
+- *
+- * @mm: DRM buddy manager
+- * @start: start address to begin the trimming.
+- * @new_size: original size requested
+- * @blocks: Input and output list of allocated blocks.
+- * MUST contain single block as input to be trimmed.
+- * On success will contain the newly allocated blocks
+- * making up the @new_size. Blocks always appear in
+- * ascending order
+- *
+- * For contiguous allocation, we round up the size to the nearest
+- * power of two value, drivers consume *actual* size, so remaining
+- * portions are unused and can be optionally freed with this function
+- *
+- * Returns:
+- * 0 on success, error code on failure.
+- */
+-int drm_buddy_block_trim(struct drm_buddy *mm,
+-                       u64 *start,
+-                       u64 new_size,
+-                       struct list_head *blocks)
+-{
+-      struct drm_buddy_block *parent;
+-      struct drm_buddy_block *block;
+-      u64 block_start, block_end;
+-      LIST_HEAD(dfs);
+-      u64 new_start;
+-      int err;
+-
+-      if (!list_is_singular(blocks))
+-              return -EINVAL;
+-
+-      block = list_first_entry(blocks,
+-                               struct drm_buddy_block,
+-                               link);
+-
+-      block_start = drm_buddy_block_offset(block);
+-      block_end = block_start + drm_buddy_block_size(mm, block);
+-
+-      if (WARN_ON(!drm_buddy_block_is_allocated(block)))
+-              return -EINVAL;
+-
+-      if (new_size > drm_buddy_block_size(mm, block))
+-              return -EINVAL;
+-
+-      if (!new_size || !IS_ALIGNED(new_size, mm->chunk_size))
+-              return -EINVAL;
+-
+-      if (new_size == drm_buddy_block_size(mm, block))
+-              return 0;
+-
+-      new_start = block_start;
+-      if (start) {
+-              new_start = *start;
+-
+-              if (new_start < block_start)
+-                      return -EINVAL;
+-
+-              if (!IS_ALIGNED(new_start, mm->chunk_size))
+-                      return -EINVAL;
+-
+-              if (range_overflows(new_start, new_size, block_end))
+-                      return -EINVAL;
+-      }
+-
+-      list_del(&block->link);
+-      mark_free(mm, block);
+-      mm->avail += drm_buddy_block_size(mm, block);
+-      if (drm_buddy_block_is_clear(block))
+-              mm->clear_avail += drm_buddy_block_size(mm, block);
+-
+-      /* Prevent recursively freeing this node */
+-      parent = block->parent;
+-      block->parent = NULL;
+-
+-      list_add(&block->tmp_link, &dfs);
+-      err =  __alloc_range(mm, &dfs, new_start, new_size, blocks, NULL);
+-      if (err) {
+-              mark_allocated(mm, block);
+-              mm->avail -= drm_buddy_block_size(mm, block);
+-              if (drm_buddy_block_is_clear(block))
+-                      mm->clear_avail -= drm_buddy_block_size(mm, block);
+-              list_add(&block->link, blocks);
+-      }
+-
+-      block->parent = parent;
+-      return err;
+-}
+-EXPORT_SYMBOL(drm_buddy_block_trim);
+-
+-static struct drm_buddy_block *
+-__drm_buddy_alloc_blocks(struct drm_buddy *mm,
+-                       u64 start, u64 end,
+-                       unsigned int order,
+-                       unsigned long flags)
+-{
+-      if (flags & DRM_BUDDY_RANGE_ALLOCATION)
+-              /* Allocate traversing within the range */
+-              return  __drm_buddy_alloc_range_bias(mm, start, end,
+-                                                   order, flags);
+-      else
+-              /* Allocate from freetree */
+-              return alloc_from_freetree(mm, order, flags);
+-}
+-
+-/**
+- * drm_buddy_alloc_blocks - allocate power-of-two blocks
+- *
+- * @mm: DRM buddy manager to allocate from
+- * @start: start of the allowed range for this block
+- * @end: end of the allowed range for this block
+- * @size: size of the allocation in bytes
+- * @min_block_size: alignment of the allocation
+- * @blocks: output list head to add allocated blocks
+- * @flags: DRM_BUDDY_*_ALLOCATION flags
+- *
+- * alloc_range_bias() called on range limitations, which traverses
+- * the tree and returns the desired block.
+- *
+- * alloc_from_freetree() called when *no* range restrictions
+- * are enforced, which picks the block from the freetree.
+- *
+- * Returns:
+- * 0 on success, error code on failure.
+- */
+-int drm_buddy_alloc_blocks(struct drm_buddy *mm,
+-                         u64 start, u64 end, u64 size,
+-                         u64 min_block_size,
+-                         struct list_head *blocks,
+-                         unsigned long flags)
+-{
+-      struct drm_buddy_block *block = NULL;
+-      u64 original_size, original_min_size;
+-      unsigned int min_order, order;
+-      LIST_HEAD(allocated);
+-      unsigned long pages;
+-      int err;
+-
+-      if (size < mm->chunk_size)
+-              return -EINVAL;
+-
+-      if (min_block_size < mm->chunk_size)
+-              return -EINVAL;
+-
+-      if (!is_power_of_2(min_block_size))
+-              return -EINVAL;
+-
+-      if (!IS_ALIGNED(start | end | size, mm->chunk_size))
+-              return -EINVAL;
+-
+-      if (end > mm->size)
+-              return -EINVAL;
+-
+-      if (range_overflows(start, size, mm->size))
+-              return -EINVAL;
+-
+-      /* Actual range allocation */
+-      if (start + size == end) {
+-              if (!IS_ALIGNED(start | end, min_block_size))
+-                      return -EINVAL;
+-
+-              return __drm_buddy_alloc_range(mm, start, size, NULL, blocks);
+-      }
+-
+-      original_size = size;
+-      original_min_size = min_block_size;
+-
+-      /* Roundup the size to power of 2 */
+-      if (flags & DRM_BUDDY_CONTIGUOUS_ALLOCATION) {
+-              size = roundup_pow_of_two(size);
+-              min_block_size = size;
+-      /* Align size value to min_block_size */
+-      } else if (!IS_ALIGNED(size, min_block_size)) {
+-              size = round_up(size, min_block_size);
+-      }
+-
+-      pages = size >> ilog2(mm->chunk_size);
+-      order = fls(pages) - 1;
+-      min_order = ilog2(min_block_size) - ilog2(mm->chunk_size);
+-
+-      if (order > mm->max_order || size > mm->size) {
+-              if ((flags & DRM_BUDDY_CONTIGUOUS_ALLOCATION) &&
+-                  !(flags & DRM_BUDDY_RANGE_ALLOCATION))
+-                      return __alloc_contig_try_harder(mm, original_size,
+-                                                       original_min_size, blocks);
+-
+-              return -EINVAL;
+-      }
+-
+-      do {
+-              order = min(order, (unsigned int)fls(pages) - 1);
+-              BUG_ON(order > mm->max_order);
+-              BUG_ON(order < min_order);
+-
+-              do {
+-                      block = __drm_buddy_alloc_blocks(mm, start,
+-                                                       end,
+-                                                       order,
+-                                                       flags);
+-                      if (!IS_ERR(block))
+-                              break;
+-
+-                      if (order-- == min_order) {
+-                              /* Try allocation through force merge method */
+-                              if (mm->clear_avail &&
+-                                  !__force_merge(mm, start, end, min_order)) {
+-                                      block = __drm_buddy_alloc_blocks(mm, start,
+-                                                                       end,
+-                                                                       min_order,
+-                                                                       flags);
+-                                      if (!IS_ERR(block)) {
+-                                              order = min_order;
+-                                              break;
+-                                      }
+-                              }
+-
+-                              /*
+-                               * Try contiguous block allocation through
+-                               * try harder method.
+-                               */
+-                              if (flags & DRM_BUDDY_CONTIGUOUS_ALLOCATION &&
+-                                  !(flags & DRM_BUDDY_RANGE_ALLOCATION))
+-                                      return __alloc_contig_try_harder(mm,
+-                                                                       original_size,
+-                                                                       original_min_size,
+-                                                                       blocks);
+-                              err = -ENOSPC;
+-                              goto err_free;
+-                      }
+-              } while (1);
+-
+-              mark_allocated(mm, block);
+-              mm->avail -= drm_buddy_block_size(mm, block);
+-              if (drm_buddy_block_is_clear(block))
+-                      mm->clear_avail -= drm_buddy_block_size(mm, block);
+-              kmemleak_update_trace(block);
+-              list_add_tail(&block->link, &allocated);
+-
+-              pages -= BIT(order);
+-
+-              if (!pages)
+-                      break;
+-      } while (1);
+-
+-      /* Trim the allocated block to the required size */
+-      if (!(flags & DRM_BUDDY_TRIM_DISABLE) &&
+-          original_size != size) {
+-              struct list_head *trim_list;
+-              LIST_HEAD(temp);
+-              u64 trim_size;
+-
+-              trim_list = &allocated;
+-              trim_size = original_size;
+-
+-              if (!list_is_singular(&allocated)) {
+-                      block = list_last_entry(&allocated, typeof(*block), link);
+-                      list_move(&block->link, &temp);
+-                      trim_list = &temp;
+-                      trim_size = drm_buddy_block_size(mm, block) -
+-                              (size - original_size);
+-              }
+-
+-              drm_buddy_block_trim(mm,
+-                                   NULL,
+-                                   trim_size,
+-                                   trim_list);
+-
+-              if (!list_empty(&temp))
+-                      list_splice_tail(trim_list, &allocated);
+-      }
+-
+-      list_splice_tail(&allocated, blocks);
+-      return 0;
+-
+-err_free:
+-      drm_buddy_free_list_internal(mm, &allocated);
+-      return err;
+-}
+-EXPORT_SYMBOL(drm_buddy_alloc_blocks);
++#include <drm/drm_print.h>
+ /**
+  * drm_buddy_block_print - print block information
+@@ -1257,12 +21,12 @@ EXPORT_SYMBOL(drm_buddy_alloc_blocks);
+  * @block: DRM buddy block
+  * @p: DRM printer to use
+  */
+-void drm_buddy_block_print(struct drm_buddy *mm,
+-                         struct drm_buddy_block *block,
++void drm_buddy_block_print(struct gpu_buddy *mm,
++                         struct gpu_buddy_block *block,
+                          struct drm_printer *p)
+ {
+-      u64 start = drm_buddy_block_offset(block);
+-      u64 size = drm_buddy_block_size(mm, block);
++      u64 start = gpu_buddy_block_offset(block);
++      u64 size = gpu_buddy_block_size(mm, block);
+       drm_printf(p, "%#018llx-%#018llx: %llu\n", start, start + size, size);
+ }
+@@ -1274,7 +38,7 @@ EXPORT_SYMBOL(drm_buddy_block_print);
+  * @mm: DRM buddy manager
+  * @p: DRM printer to use
+  */
+-void drm_buddy_print(struct drm_buddy *mm, struct drm_printer *p)
++void drm_buddy_print(struct gpu_buddy *mm, struct drm_printer *p)
+ {
+       int order;
+@@ -1282,7 +46,7 @@ void drm_buddy_print(struct drm_buddy *m
+                  mm->chunk_size >> 10, mm->size >> 20, mm->avail >> 20, mm->clear_avail >> 20);
+       for (order = mm->max_order; order >= 0; order--) {
+-              struct drm_buddy_block *block, *tmp;
++              struct gpu_buddy_block *block, *tmp;
+               struct rb_root *root;
+               u64 count = 0, free;
+               unsigned int tree;
+@@ -1291,7 +55,7 @@ void drm_buddy_print(struct drm_buddy *m
+                       root = &mm->free_trees[tree][order];
+                       rbtree_postorder_for_each_entry_safe(block, tmp, root, rb) {
+-                              BUG_ON(!drm_buddy_block_is_free(block));
++                              BUG_ON(!gpu_buddy_block_is_free(block));
+                               count++;
+                       }
+               }
+@@ -1309,22 +73,5 @@ void drm_buddy_print(struct drm_buddy *m
+ }
+ EXPORT_SYMBOL(drm_buddy_print);
+-static void drm_buddy_module_exit(void)
+-{
+-      kmem_cache_destroy(slab_blocks);
+-}
+-
+-static int __init drm_buddy_module_init(void)
+-{
+-      slab_blocks = KMEM_CACHE(drm_buddy_block, 0);
+-      if (!slab_blocks)
+-              return -ENOMEM;
+-
+-      return 0;
+-}
+-
+-module_init(drm_buddy_module_init);
+-module_exit(drm_buddy_module_exit);
+-
+-MODULE_DESCRIPTION("DRM Buddy Allocator");
++MODULE_DESCRIPTION("DRM-specific GPU Buddy Allocator Print Helpers");
+ MODULE_LICENSE("Dual MIT/GPL");
+--- a/drivers/gpu/drm/i915/gem/i915_gem_ttm.c
++++ b/drivers/gpu/drm/i915/gem/i915_gem_ttm.c
+@@ -7,7 +7,7 @@
+ #include <drm/ttm/ttm_placement.h>
+ #include <drm/ttm/ttm_tt.h>
+-#include <drm/drm_buddy.h>
++#include <linux/gpu_buddy.h>
+ #include "i915_drv.h"
+ #include "i915_ttm_buddy_manager.h"
+--- a/drivers/gpu/drm/i915/i915_scatterlist.c
++++ b/drivers/gpu/drm/i915/i915_scatterlist.c
+@@ -7,7 +7,7 @@
+ #include "i915_scatterlist.h"
+ #include "i915_ttm_buddy_manager.h"
+-#include <drm/drm_buddy.h>
++#include <linux/gpu_buddy.h>
+ #include <drm/drm_mm.h>
+ #include <linux/slab.h>
+@@ -167,9 +167,9 @@ struct i915_refct_sgt *i915_rsgt_from_bu
+       struct i915_ttm_buddy_resource *bman_res = to_ttm_buddy_resource(res);
+       const u64 size = res->size;
+       const u32 max_segment = round_down(UINT_MAX, page_alignment);
+-      struct drm_buddy *mm = bman_res->mm;
++      struct gpu_buddy *mm = bman_res->mm;
+       struct list_head *blocks = &bman_res->blocks;
+-      struct drm_buddy_block *block;
++      struct gpu_buddy_block *block;
+       struct i915_refct_sgt *rsgt;
+       struct scatterlist *sg;
+       struct sg_table *st;
+@@ -202,8 +202,8 @@ struct i915_refct_sgt *i915_rsgt_from_bu
+       list_for_each_entry(block, blocks, link) {
+               u64 block_size, offset;
+-              block_size = min_t(u64, size, drm_buddy_block_size(mm, block));
+-              offset = drm_buddy_block_offset(block);
++              block_size = min_t(u64, size, gpu_buddy_block_size(mm, block));
++              offset = gpu_buddy_block_offset(block);
+               while (block_size) {
+                       u64 len;
+--- a/drivers/gpu/drm/i915/i915_ttm_buddy_manager.c
++++ b/drivers/gpu/drm/i915/i915_ttm_buddy_manager.c
+@@ -8,6 +8,7 @@
+ #include <drm/ttm/ttm_placement.h>
+ #include <drm/ttm/ttm_bo.h>
++#include <linux/gpu_buddy.h>
+ #include <drm/drm_buddy.h>
+ #include "i915_ttm_buddy_manager.h"
+@@ -16,7 +17,7 @@
+ struct i915_ttm_buddy_manager {
+       struct ttm_resource_manager manager;
+-      struct drm_buddy mm;
++      struct gpu_buddy mm;
+       struct list_head reserved;
+       struct mutex lock;
+       unsigned long visible_size;
+@@ -38,7 +39,7 @@ static int i915_ttm_buddy_man_alloc(stru
+ {
+       struct i915_ttm_buddy_manager *bman = to_buddy_manager(man);
+       struct i915_ttm_buddy_resource *bman_res;
+-      struct drm_buddy *mm = &bman->mm;
++      struct gpu_buddy *mm = &bman->mm;
+       unsigned long n_pages, lpfn;
+       u64 min_page_size;
+       u64 size;
+@@ -57,13 +58,13 @@ static int i915_ttm_buddy_man_alloc(stru
+       bman_res->mm = mm;
+       if (place->flags & TTM_PL_FLAG_TOPDOWN)
+-              bman_res->flags |= DRM_BUDDY_TOPDOWN_ALLOCATION;
++              bman_res->flags |= GPU_BUDDY_TOPDOWN_ALLOCATION;
+       if (place->flags & TTM_PL_FLAG_CONTIGUOUS)
+-              bman_res->flags |= DRM_BUDDY_CONTIGUOUS_ALLOCATION;
++              bman_res->flags |= GPU_BUDDY_CONTIGUOUS_ALLOCATION;
+       if (place->fpfn || lpfn != man->size)
+-              bman_res->flags |= DRM_BUDDY_RANGE_ALLOCATION;
++              bman_res->flags |= GPU_BUDDY_RANGE_ALLOCATION;
+       GEM_BUG_ON(!bman_res->base.size);
+       size = bman_res->base.size;
+@@ -89,7 +90,7 @@ static int i915_ttm_buddy_man_alloc(stru
+               goto err_free_res;
+       }
+-      err = drm_buddy_alloc_blocks(mm, (u64)place->fpfn << PAGE_SHIFT,
++      err = gpu_buddy_alloc_blocks(mm, (u64)place->fpfn << PAGE_SHIFT,
+                                    (u64)lpfn << PAGE_SHIFT,
+                                    (u64)n_pages << PAGE_SHIFT,
+                                    min_page_size,
+@@ -101,15 +102,15 @@ static int i915_ttm_buddy_man_alloc(stru
+       if (lpfn <= bman->visible_size) {
+               bman_res->used_visible_size = PFN_UP(bman_res->base.size);
+       } else {
+-              struct drm_buddy_block *block;
++              struct gpu_buddy_block *block;
+               list_for_each_entry(block, &bman_res->blocks, link) {
+                       unsigned long start =
+-                              drm_buddy_block_offset(block) >> PAGE_SHIFT;
++                              gpu_buddy_block_offset(block) >> PAGE_SHIFT;
+                       if (start < bman->visible_size) {
+                               unsigned long end = start +
+-                                      (drm_buddy_block_size(mm, block) >> PAGE_SHIFT);
++                                      (gpu_buddy_block_size(mm, block) >> PAGE_SHIFT);
+                               bman_res->used_visible_size +=
+                                       min(end, bman->visible_size) - start;
+@@ -126,7 +127,7 @@ static int i915_ttm_buddy_man_alloc(stru
+       return 0;
+ err_free_blocks:
+-      drm_buddy_free_list(mm, &bman_res->blocks, 0);
++      gpu_buddy_free_list(mm, &bman_res->blocks, 0);
+       mutex_unlock(&bman->lock);
+ err_free_res:
+       ttm_resource_fini(man, &bman_res->base);
+@@ -141,7 +142,7 @@ static void i915_ttm_buddy_man_free(stru
+       struct i915_ttm_buddy_manager *bman = to_buddy_manager(man);
+       mutex_lock(&bman->lock);
+-      drm_buddy_free_list(&bman->mm, &bman_res->blocks, 0);
++      gpu_buddy_free_list(&bman->mm, &bman_res->blocks, 0);
+       bman->visible_avail += bman_res->used_visible_size;
+       mutex_unlock(&bman->lock);
+@@ -156,8 +157,8 @@ static bool i915_ttm_buddy_man_intersect
+ {
+       struct i915_ttm_buddy_resource *bman_res = to_ttm_buddy_resource(res);
+       struct i915_ttm_buddy_manager *bman = to_buddy_manager(man);
+-      struct drm_buddy *mm = &bman->mm;
+-      struct drm_buddy_block *block;
++      struct gpu_buddy *mm = &bman->mm;
++      struct gpu_buddy_block *block;
+       if (!place->fpfn && !place->lpfn)
+               return true;
+@@ -176,9 +177,9 @@ static bool i915_ttm_buddy_man_intersect
+       /* Check each drm buddy block individually */
+       list_for_each_entry(block, &bman_res->blocks, link) {
+               unsigned long fpfn =
+-                      drm_buddy_block_offset(block) >> PAGE_SHIFT;
++                      gpu_buddy_block_offset(block) >> PAGE_SHIFT;
+               unsigned long lpfn = fpfn +
+-                      (drm_buddy_block_size(mm, block) >> PAGE_SHIFT);
++                      (gpu_buddy_block_size(mm, block) >> PAGE_SHIFT);
+               if (place->fpfn < lpfn && place->lpfn > fpfn)
+                       return true;
+@@ -194,8 +195,8 @@ static bool i915_ttm_buddy_man_compatibl
+ {
+       struct i915_ttm_buddy_resource *bman_res = to_ttm_buddy_resource(res);
+       struct i915_ttm_buddy_manager *bman = to_buddy_manager(man);
+-      struct drm_buddy *mm = &bman->mm;
+-      struct drm_buddy_block *block;
++      struct gpu_buddy *mm = &bman->mm;
++      struct gpu_buddy_block *block;
+       if (!place->fpfn && !place->lpfn)
+               return true;
+@@ -209,9 +210,9 @@ static bool i915_ttm_buddy_man_compatibl
+       /* Check each drm buddy block individually */
+       list_for_each_entry(block, &bman_res->blocks, link) {
+               unsigned long fpfn =
+-                      drm_buddy_block_offset(block) >> PAGE_SHIFT;
++                      gpu_buddy_block_offset(block) >> PAGE_SHIFT;
+               unsigned long lpfn = fpfn +
+-                      (drm_buddy_block_size(mm, block) >> PAGE_SHIFT);
++                      (gpu_buddy_block_size(mm, block) >> PAGE_SHIFT);
+               if (fpfn < place->fpfn || lpfn > place->lpfn)
+                       return false;
+@@ -224,7 +225,7 @@ static void i915_ttm_buddy_man_debug(str
+                                    struct drm_printer *printer)
+ {
+       struct i915_ttm_buddy_manager *bman = to_buddy_manager(man);
+-      struct drm_buddy_block *block;
++      struct gpu_buddy_block *block;
+       mutex_lock(&bman->lock);
+       drm_printf(printer, "default_page_size: %lluKiB\n",
+@@ -293,7 +294,7 @@ int i915_ttm_buddy_man_init(struct ttm_d
+       if (!bman)
+               return -ENOMEM;
+-      err = drm_buddy_init(&bman->mm, size, chunk_size);
++      err = gpu_buddy_init(&bman->mm, size, chunk_size);
+       if (err)
+               goto err_free_bman;
+@@ -333,7 +334,7 @@ int i915_ttm_buddy_man_fini(struct ttm_d
+ {
+       struct ttm_resource_manager *man = ttm_manager_type(bdev, type);
+       struct i915_ttm_buddy_manager *bman = to_buddy_manager(man);
+-      struct drm_buddy *mm = &bman->mm;
++      struct gpu_buddy *mm = &bman->mm;
+       int ret;
+       ttm_resource_manager_set_used(man, false);
+@@ -345,8 +346,8 @@ int i915_ttm_buddy_man_fini(struct ttm_d
+       ttm_set_driver_manager(bdev, type, NULL);
+       mutex_lock(&bman->lock);
+-      drm_buddy_free_list(mm, &bman->reserved, 0);
+-      drm_buddy_fini(mm);
++      gpu_buddy_free_list(mm, &bman->reserved, 0);
++      gpu_buddy_fini(mm);
+       bman->visible_avail += bman->visible_reserved;
+       WARN_ON_ONCE(bman->visible_avail != bman->visible_size);
+       mutex_unlock(&bman->lock);
+@@ -371,15 +372,15 @@ int i915_ttm_buddy_man_reserve(struct tt
+                              u64 start, u64 size)
+ {
+       struct i915_ttm_buddy_manager *bman = to_buddy_manager(man);
+-      struct drm_buddy *mm = &bman->mm;
++      struct gpu_buddy *mm = &bman->mm;
+       unsigned long fpfn = start >> PAGE_SHIFT;
+       unsigned long flags = 0;
+       int ret;
+-      flags |= DRM_BUDDY_RANGE_ALLOCATION;
++      flags |= GPU_BUDDY_RANGE_ALLOCATION;
+       mutex_lock(&bman->lock);
+-      ret = drm_buddy_alloc_blocks(mm, start,
++      ret = gpu_buddy_alloc_blocks(mm, start,
+                                    start + size,
+                                    size, mm->chunk_size,
+                                    &bman->reserved,
+--- a/drivers/gpu/drm/i915/i915_ttm_buddy_manager.h
++++ b/drivers/gpu/drm/i915/i915_ttm_buddy_manager.h
+@@ -13,7 +13,7 @@
+ struct ttm_device;
+ struct ttm_resource_manager;
+-struct drm_buddy;
++struct gpu_buddy;
+ /**
+  * struct i915_ttm_buddy_resource
+@@ -33,7 +33,7 @@ struct i915_ttm_buddy_resource {
+       struct list_head blocks;
+       unsigned long flags;
+       unsigned long used_visible_size;
+-      struct drm_buddy *mm;
++      struct gpu_buddy *mm;
+ };
+ /**
+--- a/drivers/gpu/drm/i915/selftests/intel_memory_region.c
++++ b/drivers/gpu/drm/i915/selftests/intel_memory_region.c
+@@ -6,7 +6,7 @@
+ #include <linux/prime_numbers.h>
+ #include <linux/sort.h>
+-#include <drm/drm_buddy.h>
++#include <linux/gpu_buddy.h>
+ #include "../i915_selftest.h"
+@@ -371,7 +371,7 @@ static int igt_mock_splintered_region(vo
+       struct drm_i915_private *i915 = mem->i915;
+       struct i915_ttm_buddy_resource *res;
+       struct drm_i915_gem_object *obj;
+-      struct drm_buddy *mm;
++      struct gpu_buddy *mm;
+       unsigned int expected_order;
+       LIST_HEAD(objects);
+       u64 size;
+@@ -455,8 +455,8 @@ static int igt_mock_max_segment(void *ar
+       struct drm_i915_private *i915 = mem->i915;
+       struct i915_ttm_buddy_resource *res;
+       struct drm_i915_gem_object *obj;
+-      struct drm_buddy_block *block;
+-      struct drm_buddy *mm;
++      struct gpu_buddy_block *block;
++      struct gpu_buddy *mm;
+       struct list_head *blocks;
+       struct scatterlist *sg;
+       I915_RND_STATE(prng);
+@@ -495,8 +495,8 @@ static int igt_mock_max_segment(void *ar
+       mm = res->mm;
+       size = 0;
+       list_for_each_entry(block, blocks, link) {
+-              if (drm_buddy_block_size(mm, block) > size)
+-                      size = drm_buddy_block_size(mm, block);
++              if (gpu_buddy_block_size(mm, block) > size)
++                      size = gpu_buddy_block_size(mm, block);
+       }
+       if (size < max_segment) {
+               pr_err("%s: Failed to create a huge contiguous block [> %u], largest block %lld\n",
+@@ -535,14 +535,14 @@ static u64 igt_object_mappable_total(str
+       struct intel_memory_region *mr = obj->mm.region;
+       struct i915_ttm_buddy_resource *bman_res =
+               to_ttm_buddy_resource(obj->mm.res);
+-      struct drm_buddy *mm = bman_res->mm;
+-      struct drm_buddy_block *block;
++      struct gpu_buddy *mm = bman_res->mm;
++      struct gpu_buddy_block *block;
+       u64 total;
+       total = 0;
+       list_for_each_entry(block, &bman_res->blocks, link) {
+-              u64 start = drm_buddy_block_offset(block);
+-              u64 end = start + drm_buddy_block_size(mm, block);
++              u64 start = gpu_buddy_block_offset(block);
++              u64 end = start + gpu_buddy_block_size(mm, block);
+               if (start < resource_size(&mr->io))
+                       total += min_t(u64, end, resource_size(&mr->io)) - start;
+--- a/drivers/gpu/drm/lib/drm_random.c
++++ /dev/null
+@@ -1,43 +0,0 @@
+-// SPDX-License-Identifier: GPL-2.0
+-#include <linux/bitops.h>
+-#include <linux/kernel.h>
+-#include <linux/random.h>
+-#include <linux/slab.h>
+-#include <linux/types.h>
+-
+-#include "drm_random.h"
+-
+-u32 drm_prandom_u32_max_state(u32 ep_ro, struct rnd_state *state)
+-{
+-      return upper_32_bits((u64)prandom_u32_state(state) * ep_ro);
+-}
+-EXPORT_SYMBOL(drm_prandom_u32_max_state);
+-
+-void drm_random_reorder(unsigned int *order, unsigned int count,
+-                      struct rnd_state *state)
+-{
+-      unsigned int i, j;
+-
+-      for (i = 0; i < count; ++i) {
+-              BUILD_BUG_ON(sizeof(unsigned int) > sizeof(u32));
+-              j = drm_prandom_u32_max_state(count, state);
+-              swap(order[i], order[j]);
+-      }
+-}
+-EXPORT_SYMBOL(drm_random_reorder);
+-
+-unsigned int *drm_random_order(unsigned int count, struct rnd_state *state)
+-{
+-      unsigned int *order, i;
+-
+-      order = kmalloc_array(count, sizeof(*order), GFP_KERNEL);
+-      if (!order)
+-              return order;
+-
+-      for (i = 0; i < count; i++)
+-              order[i] = i;
+-
+-      drm_random_reorder(order, count, state);
+-      return order;
+-}
+-EXPORT_SYMBOL(drm_random_order);
+--- a/drivers/gpu/drm/lib/drm_random.h
++++ /dev/null
+@@ -1,28 +0,0 @@
+-/* SPDX-License-Identifier: GPL-2.0 */
+-#ifndef __DRM_RANDOM_H__
+-#define __DRM_RANDOM_H__
+-
+-/* This is a temporary home for a couple of utility functions that should
+- * be transposed to lib/ at the earliest convenience.
+- */
+-
+-#include <linux/random.h>
+-
+-#define DRM_RND_STATE_INITIALIZER(seed__) ({                          \
+-      struct rnd_state state__;                                       \
+-      prandom_seed_state(&state__, (seed__));                         \
+-      state__;                                                        \
+-})
+-
+-#define DRM_RND_STATE(name__, seed__) \
+-      struct rnd_state name__ = DRM_RND_STATE_INITIALIZER(seed__)
+-
+-unsigned int *drm_random_order(unsigned int count,
+-                             struct rnd_state *state);
+-void drm_random_reorder(unsigned int *order,
+-                      unsigned int count,
+-                      struct rnd_state *state);
+-u32 drm_prandom_u32_max_state(u32 ep_ro,
+-                            struct rnd_state *state);
+-
+-#endif /* !__DRM_RANDOM_H__ */
+--- a/drivers/gpu/drm/tests/Makefile
++++ b/drivers/gpu/drm/tests/Makefile
+@@ -4,7 +4,6 @@ obj-$(CONFIG_DRM_KUNIT_TEST_HELPERS) +=
+       drm_kunit_helpers.o
+ obj-$(CONFIG_DRM_KUNIT_TEST) += \
+-      drm_buddy_test.o \
+       drm_cmdline_parser_test.o \
+       drm_connector_test.o \
+       drm_damage_helper_test.o \
+--- a/drivers/gpu/drm/tests/drm_buddy_test.c
++++ /dev/null
+@@ -1,778 +0,0 @@
+-// SPDX-License-Identifier: MIT
+-/*
+- * Copyright Â© 2019 Intel Corporation
+- * Copyright Â© 2022 Maíra Canal <mairacanal@riseup.net>
+- */
+-
+-#include <kunit/test.h>
+-
+-#include <linux/prime_numbers.h>
+-#include <linux/sched/signal.h>
+-#include <linux/sizes.h>
+-
+-#include <drm/drm_buddy.h>
+-
+-#include "../lib/drm_random.h"
+-
+-static unsigned int random_seed;
+-
+-static inline u64 get_size(int order, u64 chunk_size)
+-{
+-      return (1 << order) * chunk_size;
+-}
+-
+-static void drm_test_buddy_alloc_range_bias(struct kunit *test)
+-{
+-      u32 mm_size, size, ps, bias_size, bias_start, bias_end, bias_rem;
+-      DRM_RND_STATE(prng, random_seed);
+-      unsigned int i, count, *order;
+-      struct drm_buddy_block *block;
+-      unsigned long flags;
+-      struct drm_buddy mm;
+-      LIST_HEAD(allocated);
+-
+-      bias_size = SZ_1M;
+-      ps = roundup_pow_of_two(prandom_u32_state(&prng) % bias_size);
+-      ps = max(SZ_4K, ps);
+-      mm_size = (SZ_8M-1) & ~(ps-1); /* Multiple roots */
+-
+-      kunit_info(test, "mm_size=%u, ps=%u\n", mm_size, ps);
+-
+-      KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, ps),
+-                             "buddy_init failed\n");
+-
+-      count = mm_size / bias_size;
+-      order = drm_random_order(count, &prng);
+-      KUNIT_EXPECT_TRUE(test, order);
+-
+-      /*
+-       * Idea is to split the address space into uniform bias ranges, and then
+-       * in some random order allocate within each bias, using various
+-       * patterns within. This should detect if allocations leak out from a
+-       * given bias, for example.
+-       */
+-
+-      for (i = 0; i < count; i++) {
+-              LIST_HEAD(tmp);
+-              u32 size;
+-
+-              bias_start = order[i] * bias_size;
+-              bias_end = bias_start + bias_size;
+-              bias_rem = bias_size;
+-
+-              /* internal round_up too big */
+-              KUNIT_ASSERT_TRUE_MSG(test,
+-                                    drm_buddy_alloc_blocks(&mm, bias_start,
+-                                                           bias_end, bias_size + ps, bias_size,
+-                                                           &allocated,
+-                                                           DRM_BUDDY_RANGE_ALLOCATION),
+-                                    "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
+-                                    bias_start, bias_end, bias_size, bias_size);
+-
+-              /* size too big */
+-              KUNIT_ASSERT_TRUE_MSG(test,
+-                                    drm_buddy_alloc_blocks(&mm, bias_start,
+-                                                           bias_end, bias_size + ps, ps,
+-                                                           &allocated,
+-                                                           DRM_BUDDY_RANGE_ALLOCATION),
+-                                    "buddy_alloc didn't fail with bias(%x-%x), size=%u, ps=%u\n",
+-                                    bias_start, bias_end, bias_size + ps, ps);
+-
+-              /* bias range too small for size */
+-              KUNIT_ASSERT_TRUE_MSG(test,
+-                                    drm_buddy_alloc_blocks(&mm, bias_start + ps,
+-                                                           bias_end, bias_size, ps,
+-                                                           &allocated,
+-                                                           DRM_BUDDY_RANGE_ALLOCATION),
+-                                    "buddy_alloc didn't fail with bias(%x-%x), size=%u, ps=%u\n",
+-                                    bias_start + ps, bias_end, bias_size, ps);
+-
+-              /* bias misaligned */
+-              KUNIT_ASSERT_TRUE_MSG(test,
+-                                    drm_buddy_alloc_blocks(&mm, bias_start + ps,
+-                                                           bias_end - ps,
+-                                                           bias_size >> 1, bias_size >> 1,
+-                                                           &allocated,
+-                                                           DRM_BUDDY_RANGE_ALLOCATION),
+-                                    "buddy_alloc h didn't fail with bias(%x-%x), size=%u, ps=%u\n",
+-                                    bias_start + ps, bias_end - ps, bias_size >> 1, bias_size >> 1);
+-
+-              /* single big page */
+-              KUNIT_ASSERT_FALSE_MSG(test,
+-                                     drm_buddy_alloc_blocks(&mm, bias_start,
+-                                                            bias_end, bias_size, bias_size,
+-                                                            &tmp,
+-                                                            DRM_BUDDY_RANGE_ALLOCATION),
+-                                     "buddy_alloc i failed with bias(%x-%x), size=%u, ps=%u\n",
+-                                     bias_start, bias_end, bias_size, bias_size);
+-              drm_buddy_free_list(&mm, &tmp, 0);
+-
+-              /* single page with internal round_up */
+-              KUNIT_ASSERT_FALSE_MSG(test,
+-                                     drm_buddy_alloc_blocks(&mm, bias_start,
+-                                                            bias_end, ps, bias_size,
+-                                                            &tmp,
+-                                                            DRM_BUDDY_RANGE_ALLOCATION),
+-                                     "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
+-                                     bias_start, bias_end, ps, bias_size);
+-              drm_buddy_free_list(&mm, &tmp, 0);
+-
+-              /* random size within */
+-              size = max(round_up(prandom_u32_state(&prng) % bias_rem, ps), ps);
+-              if (size)
+-                      KUNIT_ASSERT_FALSE_MSG(test,
+-                                             drm_buddy_alloc_blocks(&mm, bias_start,
+-                                                                    bias_end, size, ps,
+-                                                                    &tmp,
+-                                                                    DRM_BUDDY_RANGE_ALLOCATION),
+-                                             "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
+-                                             bias_start, bias_end, size, ps);
+-
+-              bias_rem -= size;
+-              /* too big for current avail */
+-              KUNIT_ASSERT_TRUE_MSG(test,
+-                                    drm_buddy_alloc_blocks(&mm, bias_start,
+-                                                           bias_end, bias_rem + ps, ps,
+-                                                           &allocated,
+-                                                           DRM_BUDDY_RANGE_ALLOCATION),
+-                                    "buddy_alloc didn't fail with bias(%x-%x), size=%u, ps=%u\n",
+-                                    bias_start, bias_end, bias_rem + ps, ps);
+-
+-              if (bias_rem) {
+-                      /* random fill of the remainder */
+-                      size = max(round_up(prandom_u32_state(&prng) % bias_rem, ps), ps);
+-                      size = max(size, ps);
+-
+-                      KUNIT_ASSERT_FALSE_MSG(test,
+-                                             drm_buddy_alloc_blocks(&mm, bias_start,
+-                                                                    bias_end, size, ps,
+-                                                                    &allocated,
+-                                                                    DRM_BUDDY_RANGE_ALLOCATION),
+-                                             "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
+-                                             bias_start, bias_end, size, ps);
+-                      /*
+-                       * Intentionally allow some space to be left
+-                       * unallocated, and ideally not always on the bias
+-                       * boundaries.
+-                       */
+-                      drm_buddy_free_list(&mm, &tmp, 0);
+-              } else {
+-                      list_splice_tail(&tmp, &allocated);
+-              }
+-      }
+-
+-      kfree(order);
+-      drm_buddy_free_list(&mm, &allocated, 0);
+-      drm_buddy_fini(&mm);
+-
+-      /*
+-       * Something more free-form. Idea is to pick a random starting bias
+-       * range within the address space and then start filling it up. Also
+-       * randomly grow the bias range in both directions as we go along. This
+-       * should give us bias start/end which is not always uniform like above,
+-       * and in some cases will require the allocator to jump over already
+-       * allocated nodes in the middle of the address space.
+-       */
+-
+-      KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, ps),
+-                             "buddy_init failed\n");
+-
+-      bias_start = round_up(prandom_u32_state(&prng) % (mm_size - ps), ps);
+-      bias_end = round_up(bias_start + prandom_u32_state(&prng) % (mm_size - bias_start), ps);
+-      bias_end = max(bias_end, bias_start + ps);
+-      bias_rem = bias_end - bias_start;
+-
+-      do {
+-              u32 size = max(round_up(prandom_u32_state(&prng) % bias_rem, ps), ps);
+-
+-              KUNIT_ASSERT_FALSE_MSG(test,
+-                                     drm_buddy_alloc_blocks(&mm, bias_start,
+-                                                            bias_end, size, ps,
+-                                                            &allocated,
+-                                                            DRM_BUDDY_RANGE_ALLOCATION),
+-                                     "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
+-                                     bias_start, bias_end, size, ps);
+-              bias_rem -= size;
+-
+-              /*
+-               * Try to randomly grow the bias range in both directions, or
+-               * only one, or perhaps don't grow at all.
+-               */
+-              do {
+-                      u32 old_bias_start = bias_start;
+-                      u32 old_bias_end = bias_end;
+-
+-                      if (bias_start)
+-                              bias_start -= round_up(prandom_u32_state(&prng) % bias_start, ps);
+-                      if (bias_end != mm_size)
+-                              bias_end += round_up(prandom_u32_state(&prng) % (mm_size - bias_end), ps);
+-
+-                      bias_rem += old_bias_start - bias_start;
+-                      bias_rem += bias_end - old_bias_end;
+-              } while (!bias_rem && (bias_start || bias_end != mm_size));
+-      } while (bias_rem);
+-
+-      KUNIT_ASSERT_EQ(test, bias_start, 0);
+-      KUNIT_ASSERT_EQ(test, bias_end, mm_size);
+-      KUNIT_ASSERT_TRUE_MSG(test,
+-                            drm_buddy_alloc_blocks(&mm, bias_start, bias_end,
+-                                                   ps, ps,
+-                                                   &allocated,
+-                                                   DRM_BUDDY_RANGE_ALLOCATION),
+-                            "buddy_alloc passed with bias(%x-%x), size=%u\n",
+-                            bias_start, bias_end, ps);
+-
+-      drm_buddy_free_list(&mm, &allocated, 0);
+-      drm_buddy_fini(&mm);
+-
+-      /*
+-       * Allocate cleared blocks in the bias range when the DRM buddy's clear avail is
+-       * zero. This will validate the bias range allocation in scenarios like system boot
+-       * when no cleared blocks are available and exercise the fallback path too. The resulting
+-       * blocks should always be dirty.
+-       */
+-
+-      KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, ps),
+-                             "buddy_init failed\n");
+-
+-      bias_start = round_up(prandom_u32_state(&prng) % (mm_size - ps), ps);
+-      bias_end = round_up(bias_start + prandom_u32_state(&prng) % (mm_size - bias_start), ps);
+-      bias_end = max(bias_end, bias_start + ps);
+-      bias_rem = bias_end - bias_start;
+-
+-      flags = DRM_BUDDY_CLEAR_ALLOCATION | DRM_BUDDY_RANGE_ALLOCATION;
+-      size = max(round_up(prandom_u32_state(&prng) % bias_rem, ps), ps);
+-
+-      KUNIT_ASSERT_FALSE_MSG(test,
+-                             drm_buddy_alloc_blocks(&mm, bias_start,
+-                                                    bias_end, size, ps,
+-                                                    &allocated,
+-                                                    flags),
+-                             "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
+-                             bias_start, bias_end, size, ps);
+-
+-      list_for_each_entry(block, &allocated, link)
+-              KUNIT_EXPECT_EQ(test, drm_buddy_block_is_clear(block), false);
+-
+-      drm_buddy_free_list(&mm, &allocated, 0);
+-      drm_buddy_fini(&mm);
+-}
+-
+-static void drm_test_buddy_alloc_clear(struct kunit *test)
+-{
+-      unsigned long n_pages, total, i = 0;
+-      DRM_RND_STATE(prng, random_seed);
+-      const unsigned long ps = SZ_4K;
+-      struct drm_buddy_block *block;
+-      const int max_order = 12;
+-      LIST_HEAD(allocated);
+-      struct drm_buddy mm;
+-      unsigned int order;
+-      u32 mm_size, size;
+-      LIST_HEAD(dirty);
+-      LIST_HEAD(clean);
+-
+-      mm_size = SZ_4K << max_order;
+-      KUNIT_EXPECT_FALSE(test, drm_buddy_init(&mm, mm_size, ps));
+-
+-      KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
+-
+-      /*
+-       * Idea is to allocate and free some random portion of the address space,
+-       * returning those pages as non-dirty and randomly alternate between
+-       * requesting dirty and non-dirty pages (not going over the limit
+-       * we freed as non-dirty), putting that into two separate lists.
+-       * Loop over both lists at the end checking that the dirty list
+-       * is indeed all dirty pages and vice versa. Free it all again,
+-       * keeping the dirty/clear status.
+-       */
+-      KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+-                                                          5 * ps, ps, &allocated,
+-                                                          DRM_BUDDY_TOPDOWN_ALLOCATION),
+-                              "buddy_alloc hit an error size=%lu\n", 5 * ps);
+-      drm_buddy_free_list(&mm, &allocated, DRM_BUDDY_CLEARED);
+-
+-      n_pages = 10;
+-      do {
+-              unsigned long flags;
+-              struct list_head *list;
+-              int slot = i % 2;
+-
+-              if (slot == 0) {
+-                      list = &dirty;
+-                      flags = 0;
+-              } else {
+-                      list = &clean;
+-                      flags = DRM_BUDDY_CLEAR_ALLOCATION;
+-              }
+-
+-              KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+-                                                                  ps, ps, list,
+-                                                                  flags),
+-                                      "buddy_alloc hit an error size=%lu\n", ps);
+-      } while (++i < n_pages);
+-
+-      list_for_each_entry(block, &clean, link)
+-              KUNIT_EXPECT_EQ(test, drm_buddy_block_is_clear(block), true);
+-
+-      list_for_each_entry(block, &dirty, link)
+-              KUNIT_EXPECT_EQ(test, drm_buddy_block_is_clear(block), false);
+-
+-      drm_buddy_free_list(&mm, &clean, DRM_BUDDY_CLEARED);
+-
+-      /*
+-       * Trying to go over the clear limit for some allocation.
+-       * The allocation should never fail with reasonable page-size.
+-       */
+-      KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+-                                                          10 * ps, ps, &clean,
+-                                                          DRM_BUDDY_CLEAR_ALLOCATION),
+-                              "buddy_alloc hit an error size=%lu\n", 10 * ps);
+-
+-      drm_buddy_free_list(&mm, &clean, DRM_BUDDY_CLEARED);
+-      drm_buddy_free_list(&mm, &dirty, 0);
+-      drm_buddy_fini(&mm);
+-
+-      KUNIT_EXPECT_FALSE(test, drm_buddy_init(&mm, mm_size, ps));
+-
+-      /*
+-       * Create a new mm. Intentionally fragment the address space by creating
+-       * two alternating lists. Free both lists, one as dirty the other as clean.
+-       * Try to allocate double the previous size with matching min_page_size. The
+-       * allocation should never fail as it calls the force_merge. Also check that
+-       * the page is always dirty after force_merge. Free the page as dirty, then
+-       * repeat the whole thing, increment the order until we hit the max_order.
+-       */
+-
+-      i = 0;
+-      n_pages = mm_size / ps;
+-      do {
+-              struct list_head *list;
+-              int slot = i % 2;
+-
+-              if (slot == 0)
+-                      list = &dirty;
+-              else
+-                      list = &clean;
+-
+-              KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+-                                                                  ps, ps, list, 0),
+-                                      "buddy_alloc hit an error size=%lu\n", ps);
+-      } while (++i < n_pages);
+-
+-      drm_buddy_free_list(&mm, &clean, DRM_BUDDY_CLEARED);
+-      drm_buddy_free_list(&mm, &dirty, 0);
+-
+-      order = 1;
+-      do {
+-              size = SZ_4K << order;
+-
+-              KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+-                                                                  size, size, &allocated,
+-                                                                  DRM_BUDDY_CLEAR_ALLOCATION),
+-                                      "buddy_alloc hit an error size=%u\n", size);
+-              total = 0;
+-              list_for_each_entry(block, &allocated, link) {
+-                      if (size != mm_size)
+-                              KUNIT_EXPECT_EQ(test, drm_buddy_block_is_clear(block), false);
+-                      total += drm_buddy_block_size(&mm, block);
+-              }
+-              KUNIT_EXPECT_EQ(test, total, size);
+-
+-              drm_buddy_free_list(&mm, &allocated, 0);
+-      } while (++order <= max_order);
+-
+-      drm_buddy_fini(&mm);
+-
+-      /*
+-       * Create a new mm with a non power-of-two size. Allocate a random size, free as
+-       * cleared and then call fini. This will ensure the multi-root force merge during
+-       * fini.
+-       */
+-      mm_size = 12 * SZ_4K;
+-      size = max(round_up(prandom_u32_state(&prng) % mm_size, ps), ps);
+-      KUNIT_EXPECT_FALSE(test, drm_buddy_init(&mm, mm_size, ps));
+-      KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+-                                                          size, ps, &allocated,
+-                                                          DRM_BUDDY_TOPDOWN_ALLOCATION),
+-                              "buddy_alloc hit an error size=%u\n", size);
+-      drm_buddy_free_list(&mm, &allocated, DRM_BUDDY_CLEARED);
+-      drm_buddy_fini(&mm);
+-}
+-
+-static void drm_test_buddy_alloc_contiguous(struct kunit *test)
+-{
+-      const unsigned long ps = SZ_4K, mm_size = 16 * 3 * SZ_4K;
+-      unsigned long i, n_pages, total;
+-      struct drm_buddy_block *block;
+-      struct drm_buddy mm;
+-      LIST_HEAD(left);
+-      LIST_HEAD(middle);
+-      LIST_HEAD(right);
+-      LIST_HEAD(allocated);
+-
+-      KUNIT_EXPECT_FALSE(test, drm_buddy_init(&mm, mm_size, ps));
+-
+-      /*
+-       * Idea is to fragment the address space by alternating block
+-       * allocations between three different lists; one for left, middle and
+-       * right. We can then free a list to simulate fragmentation. In
+-       * particular we want to exercise the DRM_BUDDY_CONTIGUOUS_ALLOCATION,
+-       * including the try_harder path.
+-       */
+-
+-      i = 0;
+-      n_pages = mm_size / ps;
+-      do {
+-              struct list_head *list;
+-              int slot = i % 3;
+-
+-              if (slot == 0)
+-                      list = &left;
+-              else if (slot == 1)
+-                      list = &middle;
+-              else
+-                      list = &right;
+-              KUNIT_ASSERT_FALSE_MSG(test,
+-                                     drm_buddy_alloc_blocks(&mm, 0, mm_size,
+-                                                            ps, ps, list, 0),
+-                                     "buddy_alloc hit an error size=%lu\n",
+-                                     ps);
+-      } while (++i < n_pages);
+-
+-      KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+-                                                         3 * ps, ps, &allocated,
+-                                                         DRM_BUDDY_CONTIGUOUS_ALLOCATION),
+-                             "buddy_alloc didn't error size=%lu\n", 3 * ps);
+-
+-      drm_buddy_free_list(&mm, &middle, 0);
+-      KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+-                                                         3 * ps, ps, &allocated,
+-                                                         DRM_BUDDY_CONTIGUOUS_ALLOCATION),
+-                             "buddy_alloc didn't error size=%lu\n", 3 * ps);
+-      KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+-                                                         2 * ps, ps, &allocated,
+-                                                         DRM_BUDDY_CONTIGUOUS_ALLOCATION),
+-                             "buddy_alloc didn't error size=%lu\n", 2 * ps);
+-
+-      drm_buddy_free_list(&mm, &right, 0);
+-      KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+-                                                         3 * ps, ps, &allocated,
+-                                                         DRM_BUDDY_CONTIGUOUS_ALLOCATION),
+-                             "buddy_alloc didn't error size=%lu\n", 3 * ps);
+-      /*
+-       * At this point we should have enough contiguous space for 2 blocks,
+-       * however they are never buddies (since we freed middle and right) so
+-       * will require the try_harder logic to find them.
+-       */
+-      KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+-                                                          2 * ps, ps, &allocated,
+-                                                          DRM_BUDDY_CONTIGUOUS_ALLOCATION),
+-                             "buddy_alloc hit an error size=%lu\n", 2 * ps);
+-
+-      drm_buddy_free_list(&mm, &left, 0);
+-      KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+-                                                          3 * ps, ps, &allocated,
+-                                                          DRM_BUDDY_CONTIGUOUS_ALLOCATION),
+-                             "buddy_alloc hit an error size=%lu\n", 3 * ps);
+-
+-      total = 0;
+-      list_for_each_entry(block, &allocated, link)
+-              total += drm_buddy_block_size(&mm, block);
+-
+-      KUNIT_ASSERT_EQ(test, total, ps * 2 + ps * 3);
+-
+-      drm_buddy_free_list(&mm, &allocated, 0);
+-      drm_buddy_fini(&mm);
+-}
+-
+-static void drm_test_buddy_alloc_pathological(struct kunit *test)
+-{
+-      u64 mm_size, size, start = 0;
+-      struct drm_buddy_block *block;
+-      const int max_order = 3;
+-      unsigned long flags = 0;
+-      int order, top;
+-      struct drm_buddy mm;
+-      LIST_HEAD(blocks);
+-      LIST_HEAD(holes);
+-      LIST_HEAD(tmp);
+-
+-      /*
+-       * Create a pot-sized mm, then allocate one of each possible
+-       * order within. This should leave the mm with exactly one
+-       * page left. Free the largest block, then whittle down again.
+-       * Eventually we will have a fully 50% fragmented mm.
+-       */
+-
+-      mm_size = SZ_4K << max_order;
+-      KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, SZ_4K),
+-                             "buddy_init failed\n");
+-
+-      KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
+-
+-      for (top = max_order; top; top--) {
+-              /* Make room by freeing the largest allocated block */
+-              block = list_first_entry_or_null(&blocks, typeof(*block), link);
+-              if (block) {
+-                      list_del(&block->link);
+-                      drm_buddy_free_block(&mm, block);
+-              }
+-
+-              for (order = top; order--;) {
+-                      size = get_size(order, mm.chunk_size);
+-                      KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start,
+-                                                                          mm_size, size, size,
+-                                                                              &tmp, flags),
+-                                      "buddy_alloc hit -ENOMEM with order=%d, top=%d\n",
+-                                      order, top);
+-
+-                      block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
+-                      KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
+-
+-                      list_move_tail(&block->link, &blocks);
+-              }
+-
+-              /* There should be one final page for this sub-allocation */
+-              size = get_size(0, mm.chunk_size);
+-              KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+-                                                                  size, size, &tmp, flags),
+-                                                         "buddy_alloc hit -ENOMEM for hole\n");
+-
+-              block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
+-              KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
+-
+-              list_move_tail(&block->link, &holes);
+-
+-              size = get_size(top, mm.chunk_size);
+-              KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+-                                                                 size, size, &tmp, flags),
+-                                                        "buddy_alloc unexpectedly succeeded at top-order %d/%d, it should be full!",
+-                                                        top, max_order);
+-      }
+-
+-      drm_buddy_free_list(&mm, &holes, 0);
+-
+-      /* Nothing larger than blocks of chunk_size now available */
+-      for (order = 1; order <= max_order; order++) {
+-              size = get_size(order, mm.chunk_size);
+-              KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+-                                                                 size, size, &tmp, flags),
+-                                                        "buddy_alloc unexpectedly succeeded at order %d, it should be full!",
+-                                                        order);
+-      }
+-
+-      list_splice_tail(&holes, &blocks);
+-      drm_buddy_free_list(&mm, &blocks, 0);
+-      drm_buddy_fini(&mm);
+-}
+-
+-static void drm_test_buddy_alloc_pessimistic(struct kunit *test)
+-{
+-      u64 mm_size, size, start = 0;
+-      struct drm_buddy_block *block, *bn;
+-      const unsigned int max_order = 16;
+-      unsigned long flags = 0;
+-      struct drm_buddy mm;
+-      unsigned int order;
+-      LIST_HEAD(blocks);
+-      LIST_HEAD(tmp);
+-
+-      /*
+-       * Create a pot-sized mm, then allocate one of each possible
+-       * order within. This should leave the mm with exactly one
+-       * page left.
+-       */
+-
+-      mm_size = SZ_4K << max_order;
+-      KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, SZ_4K),
+-                             "buddy_init failed\n");
+-
+-      KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
+-
+-      for (order = 0; order < max_order; order++) {
+-              size = get_size(order, mm.chunk_size);
+-              KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+-                                                                  size, size, &tmp, flags),
+-                                                         "buddy_alloc hit -ENOMEM with order=%d\n",
+-                                                         order);
+-
+-              block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
+-              KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
+-
+-              list_move_tail(&block->link, &blocks);
+-      }
+-
+-      /* And now the last remaining block available */
+-      size = get_size(0, mm.chunk_size);
+-      KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+-                                                          size, size, &tmp, flags),
+-                                                 "buddy_alloc hit -ENOMEM on final alloc\n");
+-
+-      block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
+-      KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
+-
+-      list_move_tail(&block->link, &blocks);
+-
+-      /* Should be completely full! */
+-      for (order = max_order; order--;) {
+-              size = get_size(order, mm.chunk_size);
+-              KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+-                                                                 size, size, &tmp, flags),
+-                                                        "buddy_alloc unexpectedly succeeded, it should be full!");
+-      }
+-
+-      block = list_last_entry(&blocks, typeof(*block), link);
+-      list_del(&block->link);
+-      drm_buddy_free_block(&mm, block);
+-
+-      /* As we free in increasing size, we make available larger blocks */
+-      order = 1;
+-      list_for_each_entry_safe(block, bn, &blocks, link) {
+-              list_del(&block->link);
+-              drm_buddy_free_block(&mm, block);
+-
+-              size = get_size(order, mm.chunk_size);
+-              KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+-                                                                  size, size, &tmp, flags),
+-                                                         "buddy_alloc hit -ENOMEM with order=%d\n",
+-                                                         order);
+-
+-              block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
+-              KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
+-
+-              list_del(&block->link);
+-              drm_buddy_free_block(&mm, block);
+-              order++;
+-      }
+-
+-      /* To confirm, now the whole mm should be available */
+-      size = get_size(max_order, mm.chunk_size);
+-      KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+-                                                          size, size, &tmp, flags),
+-                                                 "buddy_alloc (realloc) hit -ENOMEM with order=%d\n",
+-                                                 max_order);
+-
+-      block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
+-      KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
+-
+-      list_del(&block->link);
+-      drm_buddy_free_block(&mm, block);
+-      drm_buddy_free_list(&mm, &blocks, 0);
+-      drm_buddy_fini(&mm);
+-}
+-
+-static void drm_test_buddy_alloc_optimistic(struct kunit *test)
+-{
+-      u64 mm_size, size, start = 0;
+-      struct drm_buddy_block *block;
+-      unsigned long flags = 0;
+-      const int max_order = 16;
+-      struct drm_buddy mm;
+-      LIST_HEAD(blocks);
+-      LIST_HEAD(tmp);
+-      int order;
+-
+-      /*
+-       * Create a mm with one block of each order available, and
+-       * try to allocate them all.
+-       */
+-
+-      mm_size = SZ_4K * ((1 << (max_order + 1)) - 1);
+-
+-      KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, SZ_4K),
+-                             "buddy_init failed\n");
+-
+-      KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
+-
+-      for (order = 0; order <= max_order; order++) {
+-              size = get_size(order, mm.chunk_size);
+-              KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+-                                                                  size, size, &tmp, flags),
+-                                                         "buddy_alloc hit -ENOMEM with order=%d\n",
+-                                                         order);
+-
+-              block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
+-              KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
+-
+-              list_move_tail(&block->link, &blocks);
+-      }
+-
+-      /* Should be completely full! */
+-      size = get_size(0, mm.chunk_size);
+-      KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+-                                                         size, size, &tmp, flags),
+-                                                "buddy_alloc unexpectedly succeeded, it should be full!");
+-
+-      drm_buddy_free_list(&mm, &blocks, 0);
+-      drm_buddy_fini(&mm);
+-}
+-
+-static void drm_test_buddy_alloc_limit(struct kunit *test)
+-{
+-      u64 size = U64_MAX, start = 0;
+-      struct drm_buddy_block *block;
+-      unsigned long flags = 0;
+-      LIST_HEAD(allocated);
+-      struct drm_buddy mm;
+-
+-      KUNIT_EXPECT_FALSE(test, drm_buddy_init(&mm, size, SZ_4K));
+-
+-      KUNIT_EXPECT_EQ_MSG(test, mm.max_order, DRM_BUDDY_MAX_ORDER,
+-                          "mm.max_order(%d) != %d\n", mm.max_order,
+-                                              DRM_BUDDY_MAX_ORDER);
+-
+-      size = mm.chunk_size << mm.max_order;
+-      KUNIT_EXPECT_FALSE(test, drm_buddy_alloc_blocks(&mm, start, size, size,
+-                                                      mm.chunk_size, &allocated, flags));
+-
+-      block = list_first_entry_or_null(&allocated, struct drm_buddy_block, link);
+-      KUNIT_EXPECT_TRUE(test, block);
+-
+-      KUNIT_EXPECT_EQ_MSG(test, drm_buddy_block_order(block), mm.max_order,
+-                          "block order(%d) != %d\n",
+-                                              drm_buddy_block_order(block), mm.max_order);
+-
+-      KUNIT_EXPECT_EQ_MSG(test, drm_buddy_block_size(&mm, block),
+-                          BIT_ULL(mm.max_order) * mm.chunk_size,
+-                                              "block size(%llu) != %llu\n",
+-                                              drm_buddy_block_size(&mm, block),
+-                                              BIT_ULL(mm.max_order) * mm.chunk_size);
+-
+-      drm_buddy_free_list(&mm, &allocated, 0);
+-      drm_buddy_fini(&mm);
+-}
+-
+-static int drm_buddy_suite_init(struct kunit_suite *suite)
+-{
+-      while (!random_seed)
+-              random_seed = get_random_u32();
+-
+-      kunit_info(suite, "Testing DRM buddy manager, with random_seed=0x%x\n",
+-                 random_seed);
+-
+-      return 0;
+-}
+-
+-static struct kunit_case drm_buddy_tests[] = {
+-      KUNIT_CASE(drm_test_buddy_alloc_limit),
+-      KUNIT_CASE(drm_test_buddy_alloc_optimistic),
+-      KUNIT_CASE(drm_test_buddy_alloc_pessimistic),
+-      KUNIT_CASE(drm_test_buddy_alloc_pathological),
+-      KUNIT_CASE(drm_test_buddy_alloc_contiguous),
+-      KUNIT_CASE(drm_test_buddy_alloc_clear),
+-      KUNIT_CASE(drm_test_buddy_alloc_range_bias),
+-      {}
+-};
+-
+-static struct kunit_suite drm_buddy_test_suite = {
+-      .name = "drm_buddy",
+-      .suite_init = drm_buddy_suite_init,
+-      .test_cases = drm_buddy_tests,
+-};
+-
+-kunit_test_suite(drm_buddy_test_suite);
+-
+-MODULE_AUTHOR("Intel Corporation");
+-MODULE_DESCRIPTION("Kunit test for drm_buddy functions");
+-MODULE_LICENSE("GPL");
+--- a/drivers/gpu/drm/tests/drm_exec_test.c
++++ b/drivers/gpu/drm/tests/drm_exec_test.c
+@@ -16,8 +16,6 @@
+ #include <drm/drm_gem.h>
+ #include <drm/drm_kunit_helpers.h>
+-#include "../lib/drm_random.h"
+-
+ struct drm_exec_priv {
+       struct device *dev;
+       struct drm_device *drm;
+--- a/drivers/gpu/drm/tests/drm_mm_test.c
++++ b/drivers/gpu/drm/tests/drm_mm_test.c
+@@ -15,8 +15,6 @@
+ #include <drm/drm_mm.h>
+-#include "../lib/drm_random.h"
+-
+ enum {
+       BEST,
+       BOTTOMUP,
+--- a/drivers/gpu/drm/ttm/tests/ttm_bo_validate_test.c
++++ b/drivers/gpu/drm/ttm/tests/ttm_bo_validate_test.c
+@@ -251,7 +251,7 @@ static void ttm_bo_validate_basic(struct
+                                  NULL, &dummy_ttm_bo_destroy);
+       KUNIT_EXPECT_EQ(test, err, 0);
+-      snd_place = ttm_place_kunit_init(test, snd_mem, DRM_BUDDY_TOPDOWN_ALLOCATION);
++      snd_place = ttm_place_kunit_init(test, snd_mem, GPU_BUDDY_TOPDOWN_ALLOCATION);
+       snd_placement = ttm_placement_kunit_init(test, snd_place, 1);
+       err = ttm_bo_validate(bo, snd_placement, &ctx_val);
+@@ -263,7 +263,7 @@ static void ttm_bo_validate_basic(struct
+       KUNIT_EXPECT_TRUE(test, ttm_tt_is_populated(bo->ttm));
+       KUNIT_EXPECT_EQ(test, bo->resource->mem_type, snd_mem);
+       KUNIT_EXPECT_EQ(test, bo->resource->placement,
+-                      DRM_BUDDY_TOPDOWN_ALLOCATION);
++                      GPU_BUDDY_TOPDOWN_ALLOCATION);
+       ttm_bo_put(bo);
+       ttm_mock_manager_fini(priv->ttm_dev, snd_mem);
+--- a/drivers/gpu/drm/ttm/tests/ttm_mock_manager.c
++++ b/drivers/gpu/drm/ttm/tests/ttm_mock_manager.c
+@@ -27,7 +27,7 @@ static int ttm_mock_manager_alloc(struct
+ {
+       struct ttm_mock_manager *manager = to_mock_mgr(man);
+       struct ttm_mock_resource *mock_res;
+-      struct drm_buddy *mm = &manager->mm;
++      struct gpu_buddy *mm = &manager->mm;
+       u64 lpfn, fpfn, alloc_size;
+       int err;
+@@ -43,14 +43,14 @@ static int ttm_mock_manager_alloc(struct
+       INIT_LIST_HEAD(&mock_res->blocks);
+       if (place->flags & TTM_PL_FLAG_TOPDOWN)
+-              mock_res->flags |= DRM_BUDDY_TOPDOWN_ALLOCATION;
++              mock_res->flags |= GPU_BUDDY_TOPDOWN_ALLOCATION;
+       if (place->flags & TTM_PL_FLAG_CONTIGUOUS)
+-              mock_res->flags |= DRM_BUDDY_CONTIGUOUS_ALLOCATION;
++              mock_res->flags |= GPU_BUDDY_CONTIGUOUS_ALLOCATION;
+       alloc_size = (uint64_t)mock_res->base.size;
+       mutex_lock(&manager->lock);
+-      err = drm_buddy_alloc_blocks(mm, fpfn, lpfn, alloc_size,
++      err = gpu_buddy_alloc_blocks(mm, fpfn, lpfn, alloc_size,
+                                    manager->default_page_size,
+                                    &mock_res->blocks,
+                                    mock_res->flags);
+@@ -63,7 +63,7 @@ static int ttm_mock_manager_alloc(struct
+       return 0;
+ error_free_blocks:
+-      drm_buddy_free_list(mm, &mock_res->blocks, 0);
++      gpu_buddy_free_list(mm, &mock_res->blocks, 0);
+       ttm_resource_fini(man, &mock_res->base);
+       mutex_unlock(&manager->lock);
+@@ -75,10 +75,10 @@ static void ttm_mock_manager_free(struct
+ {
+       struct ttm_mock_manager *manager = to_mock_mgr(man);
+       struct ttm_mock_resource *mock_res = to_mock_mgr_resource(res);
+-      struct drm_buddy *mm = &manager->mm;
++      struct gpu_buddy *mm = &manager->mm;
+       mutex_lock(&manager->lock);
+-      drm_buddy_free_list(mm, &mock_res->blocks, 0);
++      gpu_buddy_free_list(mm, &mock_res->blocks, 0);
+       mutex_unlock(&manager->lock);
+       ttm_resource_fini(man, res);
+@@ -102,7 +102,7 @@ int ttm_mock_manager_init(struct ttm_dev
+       mutex_init(&manager->lock);
+-      err = drm_buddy_init(&manager->mm, size, PAGE_SIZE);
++      err = gpu_buddy_init(&manager->mm, size, PAGE_SIZE);
+       if (err) {
+               kfree(manager);
+@@ -138,7 +138,7 @@ void ttm_mock_manager_fini(struct ttm_de
+       ttm_resource_manager_set_used(man, false);
+       mutex_lock(&mock_man->lock);
+-      drm_buddy_fini(&mock_man->mm);
++      gpu_buddy_fini(&mock_man->mm);
+       mutex_unlock(&mock_man->lock);
+       ttm_set_driver_manager(bdev, mem_type, NULL);
+--- a/drivers/gpu/drm/ttm/tests/ttm_mock_manager.h
++++ b/drivers/gpu/drm/ttm/tests/ttm_mock_manager.h
+@@ -5,11 +5,11 @@
+ #ifndef TTM_MOCK_MANAGER_H
+ #define TTM_MOCK_MANAGER_H
+-#include <drm/drm_buddy.h>
++#include <linux/gpu_buddy.h>
+ struct ttm_mock_manager {
+       struct ttm_resource_manager man;
+-      struct drm_buddy mm;
++      struct gpu_buddy mm;
+       u64 default_page_size;
+       /* protects allocations of mock buffer objects */
+       struct mutex lock;
+--- a/drivers/gpu/drm/xe/xe_res_cursor.h
++++ b/drivers/gpu/drm/xe/xe_res_cursor.h
+@@ -44,10 +44,10 @@ struct xe_res_cursor {
+       void *node;
+       u32 mem_type;
+       struct scatterlist *sgl;
+-      struct drm_buddy *mm;
++      struct gpu_buddy *mm;
+ };
+-static struct drm_buddy *xe_res_get_buddy(struct ttm_resource *res)
++static struct gpu_buddy *xe_res_get_buddy(struct ttm_resource *res)
+ {
+       struct ttm_resource_manager *mgr;
+@@ -81,30 +81,30 @@ static inline void xe_res_first(struct t
+       case XE_PL_STOLEN:
+       case XE_PL_VRAM0:
+       case XE_PL_VRAM1: {
+-              struct drm_buddy_block *block;
++              struct gpu_buddy_block *block;
+               struct list_head *head, *next;
+-              struct drm_buddy *mm = xe_res_get_buddy(res);
++              struct gpu_buddy *mm = xe_res_get_buddy(res);
+               head = &to_xe_ttm_vram_mgr_resource(res)->blocks;
+               block = list_first_entry_or_null(head,
+-                                               struct drm_buddy_block,
++                                               struct gpu_buddy_block,
+                                                link);
+               if (!block)
+                       goto fallback;
+-              while (start >= drm_buddy_block_size(mm, block)) {
+-                      start -= drm_buddy_block_size(mm, block);
++              while (start >= gpu_buddy_block_size(mm, block)) {
++                      start -= gpu_buddy_block_size(mm, block);
+                       next = block->link.next;
+                       if (next != head)
+-                              block = list_entry(next, struct drm_buddy_block,
++                              block = list_entry(next, struct gpu_buddy_block,
+                                                  link);
+               }
+               cur->mm = mm;
+-              cur->start = drm_buddy_block_offset(block) + start;
+-              cur->size = min(drm_buddy_block_size(mm, block) - start,
++              cur->start = gpu_buddy_block_offset(block) + start;
++              cur->size = min(gpu_buddy_block_size(mm, block) - start,
+                               size);
+               cur->remaining = size;
+               cur->node = block;
+@@ -175,7 +175,7 @@ static inline void xe_res_first_sg(const
+  */
+ static inline void xe_res_next(struct xe_res_cursor *cur, u64 size)
+ {
+-      struct drm_buddy_block *block;
++      struct gpu_buddy_block *block;
+       struct list_head *next;
+       u64 start;
+@@ -205,18 +205,18 @@ static inline void xe_res_next(struct xe
+               block = cur->node;
+               next = block->link.next;
+-              block = list_entry(next, struct drm_buddy_block, link);
++              block = list_entry(next, struct gpu_buddy_block, link);
+-              while (start >= drm_buddy_block_size(cur->mm, block)) {
+-                      start -= drm_buddy_block_size(cur->mm, block);
++              while (start >= gpu_buddy_block_size(cur->mm, block)) {
++                      start -= gpu_buddy_block_size(cur->mm, block);
+                       next = block->link.next;
+-                      block = list_entry(next, struct drm_buddy_block, link);
++                      block = list_entry(next, struct gpu_buddy_block, link);
+               }
+-              cur->start = drm_buddy_block_offset(block) + start;
+-              cur->size = min(drm_buddy_block_size(cur->mm, block) - start,
++              cur->start = gpu_buddy_block_offset(block) + start;
++              cur->size = min(gpu_buddy_block_size(cur->mm, block) - start,
+                               cur->remaining);
+               cur->node = block;
+               break;
+--- a/drivers/gpu/drm/xe/xe_ttm_vram_mgr.c
++++ b/drivers/gpu/drm/xe/xe_ttm_vram_mgr.c
+@@ -5,6 +5,7 @@
+  */
+ #include <drm/drm_managed.h>
++#include <drm/drm_buddy.h>
+ #include <drm/ttm/ttm_placement.h>
+ #include <drm/ttm/ttm_range_manager.h>
+@@ -15,16 +16,16 @@
+ #include "xe_res_cursor.h"
+ #include "xe_ttm_vram_mgr.h"
+-static inline struct drm_buddy_block *
++static inline struct gpu_buddy_block *
+ xe_ttm_vram_mgr_first_block(struct list_head *list)
+ {
+-      return list_first_entry_or_null(list, struct drm_buddy_block, link);
++      return list_first_entry_or_null(list, struct gpu_buddy_block, link);
+ }
+-static inline bool xe_is_vram_mgr_blocks_contiguous(struct drm_buddy *mm,
++static inline bool xe_is_vram_mgr_blocks_contiguous(struct gpu_buddy *mm,
+                                                   struct list_head *head)
+ {
+-      struct drm_buddy_block *block;
++      struct gpu_buddy_block *block;
+       u64 start, size;
+       block = xe_ttm_vram_mgr_first_block(head);
+@@ -32,12 +33,12 @@ static inline bool xe_is_vram_mgr_blocks
+               return false;
+       while (head != block->link.next) {
+-              start = drm_buddy_block_offset(block);
+-              size = drm_buddy_block_size(mm, block);
++              start = gpu_buddy_block_offset(block);
++              size = gpu_buddy_block_size(mm, block);
+-              block = list_entry(block->link.next, struct drm_buddy_block,
++              block = list_entry(block->link.next, struct gpu_buddy_block,
+                                  link);
+-              if (start + size != drm_buddy_block_offset(block))
++              if (start + size != gpu_buddy_block_offset(block))
+                       return false;
+       }
+@@ -51,7 +52,7 @@ static int xe_ttm_vram_mgr_new(struct tt
+ {
+       struct xe_ttm_vram_mgr *mgr = to_xe_ttm_vram_mgr(man);
+       struct xe_ttm_vram_mgr_resource *vres;
+-      struct drm_buddy *mm = &mgr->mm;
++      struct gpu_buddy *mm = &mgr->mm;
+       u64 size, remaining_size, min_page_size;
+       unsigned long lpfn;
+       int err;
+@@ -78,10 +79,10 @@ static int xe_ttm_vram_mgr_new(struct tt
+       INIT_LIST_HEAD(&vres->blocks);
+       if (place->flags & TTM_PL_FLAG_TOPDOWN)
+-              vres->flags |= DRM_BUDDY_TOPDOWN_ALLOCATION;
++              vres->flags |= GPU_BUDDY_TOPDOWN_ALLOCATION;
+       if (place->fpfn || lpfn != man->size >> PAGE_SHIFT)
+-              vres->flags |= DRM_BUDDY_RANGE_ALLOCATION;
++              vres->flags |= GPU_BUDDY_RANGE_ALLOCATION;
+       if (WARN_ON(!vres->base.size)) {
+               err = -EINVAL;
+@@ -137,7 +138,7 @@ static int xe_ttm_vram_mgr_new(struct tt
+                */
+               u64 alloc_size = min_t(u64, remaining_size, SZ_2G);
+-              err = drm_buddy_alloc_blocks(mm, (u64)place->fpfn << PAGE_SHIFT,
++              err = gpu_buddy_alloc_blocks(mm, (u64)place->fpfn << PAGE_SHIFT,
+                                            (u64)lpfn << PAGE_SHIFT,
+                                            alloc_size,
+                                            min_page_size,
+@@ -150,20 +151,20 @@ static int xe_ttm_vram_mgr_new(struct tt
+       } while (remaining_size);
+       if (place->flags & TTM_PL_FLAG_CONTIGUOUS) {
+-              if (!drm_buddy_block_trim(mm, NULL, vres->base.size, &vres->blocks))
++              if (!gpu_buddy_block_trim(mm, NULL, vres->base.size, &vres->blocks))
+                       size = vres->base.size;
+       }
+       if (lpfn <= mgr->visible_size >> PAGE_SHIFT) {
+               vres->used_visible_size = size;
+       } else {
+-              struct drm_buddy_block *block;
++              struct gpu_buddy_block *block;
+               list_for_each_entry(block, &vres->blocks, link) {
+-                      u64 start = drm_buddy_block_offset(block);
++                      u64 start = gpu_buddy_block_offset(block);
+                       if (start < mgr->visible_size) {
+-                              u64 end = start + drm_buddy_block_size(mm, block);
++                              u64 end = start + gpu_buddy_block_size(mm, block);
+                               vres->used_visible_size +=
+                                       min(end, mgr->visible_size) - start;
+@@ -183,11 +184,11 @@ static int xe_ttm_vram_mgr_new(struct tt
+        * the object.
+        */
+       if (vres->base.placement & TTM_PL_FLAG_CONTIGUOUS) {
+-              struct drm_buddy_block *block = list_first_entry(&vres->blocks,
++              struct gpu_buddy_block *block = list_first_entry(&vres->blocks,
+                                                                typeof(*block),
+                                                                link);
+-              vres->base.start = drm_buddy_block_offset(block) >> PAGE_SHIFT;
++              vres->base.start = gpu_buddy_block_offset(block) >> PAGE_SHIFT;
+       } else {
+               vres->base.start = XE_BO_INVALID_OFFSET;
+       }
+@@ -196,7 +197,7 @@ static int xe_ttm_vram_mgr_new(struct tt
+       return 0;
+ error_free_blocks:
+-      drm_buddy_free_list(mm, &vres->blocks, 0);
++      gpu_buddy_free_list(mm, &vres->blocks, 0);
+       mutex_unlock(&mgr->lock);
+ error_fini:
+       ttm_resource_fini(man, &vres->base);
+@@ -211,10 +212,10 @@ static void xe_ttm_vram_mgr_del(struct t
+       struct xe_ttm_vram_mgr_resource *vres =
+               to_xe_ttm_vram_mgr_resource(res);
+       struct xe_ttm_vram_mgr *mgr = to_xe_ttm_vram_mgr(man);
+-      struct drm_buddy *mm = &mgr->mm;
++      struct gpu_buddy *mm = &mgr->mm;
+       mutex_lock(&mgr->lock);
+-      drm_buddy_free_list(mm, &vres->blocks, 0);
++      gpu_buddy_free_list(mm, &vres->blocks, 0);
+       mgr->visible_avail += vres->used_visible_size;
+       mutex_unlock(&mgr->lock);
+@@ -227,7 +228,7 @@ static void xe_ttm_vram_mgr_debug(struct
+                                 struct drm_printer *printer)
+ {
+       struct xe_ttm_vram_mgr *mgr = to_xe_ttm_vram_mgr(man);
+-      struct drm_buddy *mm = &mgr->mm;
++      struct gpu_buddy *mm = &mgr->mm;
+       mutex_lock(&mgr->lock);
+       drm_printf(printer, "default_page_size: %lluKiB\n",
+@@ -250,8 +251,8 @@ static bool xe_ttm_vram_mgr_intersects(s
+       struct xe_ttm_vram_mgr *mgr = to_xe_ttm_vram_mgr(man);
+       struct xe_ttm_vram_mgr_resource *vres =
+               to_xe_ttm_vram_mgr_resource(res);
+-      struct drm_buddy *mm = &mgr->mm;
+-      struct drm_buddy_block *block;
++      struct gpu_buddy *mm = &mgr->mm;
++      struct gpu_buddy_block *block;
+       if (!place->fpfn && !place->lpfn)
+               return true;
+@@ -261,9 +262,9 @@ static bool xe_ttm_vram_mgr_intersects(s
+       list_for_each_entry(block, &vres->blocks, link) {
+               unsigned long fpfn =
+-                      drm_buddy_block_offset(block) >> PAGE_SHIFT;
++                      gpu_buddy_block_offset(block) >> PAGE_SHIFT;
+               unsigned long lpfn = fpfn +
+-                      (drm_buddy_block_size(mm, block) >> PAGE_SHIFT);
++                      (gpu_buddy_block_size(mm, block) >> PAGE_SHIFT);
+               if (place->fpfn < lpfn && place->lpfn > fpfn)
+                       return true;
+@@ -280,8 +281,8 @@ static bool xe_ttm_vram_mgr_compatible(s
+       struct xe_ttm_vram_mgr *mgr = to_xe_ttm_vram_mgr(man);
+       struct xe_ttm_vram_mgr_resource *vres =
+               to_xe_ttm_vram_mgr_resource(res);
+-      struct drm_buddy *mm = &mgr->mm;
+-      struct drm_buddy_block *block;
++      struct gpu_buddy *mm = &mgr->mm;
++      struct gpu_buddy_block *block;
+       if (!place->fpfn && !place->lpfn)
+               return true;
+@@ -291,9 +292,9 @@ static bool xe_ttm_vram_mgr_compatible(s
+       list_for_each_entry(block, &vres->blocks, link) {
+               unsigned long fpfn =
+-                      drm_buddy_block_offset(block) >> PAGE_SHIFT;
++                      gpu_buddy_block_offset(block) >> PAGE_SHIFT;
+               unsigned long lpfn = fpfn +
+-                      (drm_buddy_block_size(mm, block) >> PAGE_SHIFT);
++                      (gpu_buddy_block_size(mm, block) >> PAGE_SHIFT);
+               if (fpfn < place->fpfn || lpfn > place->lpfn)
+                       return false;
+@@ -323,7 +324,7 @@ static void ttm_vram_mgr_fini(struct drm
+       WARN_ON_ONCE(mgr->visible_avail != mgr->visible_size);
+-      drm_buddy_fini(&mgr->mm);
++      gpu_buddy_fini(&mgr->mm);
+       ttm_resource_manager_cleanup(&mgr->manager);
+@@ -347,7 +348,7 @@ int __xe_ttm_vram_mgr_init(struct xe_dev
+       mgr->visible_avail = io_size;
+       ttm_resource_manager_init(man, &xe->ttm, size);
+-      err = drm_buddy_init(&mgr->mm, man->size, default_page_size);
++      err = gpu_buddy_init(&mgr->mm, man->size, default_page_size);
+       if (err)
+               return err;
+@@ -389,7 +390,7 @@ int xe_ttm_vram_mgr_alloc_sgt(struct xe_
+       if (!*sgt)
+               return -ENOMEM;
+-      /* Determine the number of DRM_BUDDY blocks to export */
++      /* Determine the number of GPU_BUDDY blocks to export */
+       xe_res_first(res, offset, length, &cursor);
+       while (cursor.remaining) {
+               num_entries++;
+@@ -405,10 +406,10 @@ int xe_ttm_vram_mgr_alloc_sgt(struct xe_
+               sg->length = 0;
+       /*
+-       * Walk down DRM_BUDDY blocks to populate scatterlist nodes
+-       * @note: Use iterator api to get first the DRM_BUDDY block
++       * Walk down GPU_BUDDY blocks to populate scatterlist nodes
++       * @note: Use iterator api to get first the GPU_BUDDY block
+        * and the number of bytes from it. Access the following
+-       * DRM_BUDDY block(s) if more buffer needs to exported
++       * GPU_BUDDY block(s) if more buffer needs to exported
+        */
+       xe_res_first(res, offset, length, &cursor);
+       for_each_sgtable_sg((*sgt), sg, i) {
+--- a/drivers/gpu/drm/xe/xe_ttm_vram_mgr_types.h
++++ b/drivers/gpu/drm/xe/xe_ttm_vram_mgr_types.h
+@@ -6,7 +6,7 @@
+ #ifndef _XE_TTM_VRAM_MGR_TYPES_H_
+ #define _XE_TTM_VRAM_MGR_TYPES_H_
+-#include <drm/drm_buddy.h>
++#include <linux/gpu_buddy.h>
+ #include <drm/ttm/ttm_device.h>
+ struct xe_mem_region;
+@@ -20,7 +20,7 @@ struct xe_ttm_vram_mgr {
+       /** @manager: Base TTM resource manager */
+       struct ttm_resource_manager manager;
+       /** @mm: DRM buddy allocator which manages the VRAM */
+-      struct drm_buddy mm;
++      struct gpu_buddy mm;
+       /** @vram: ptr to details of associated VRAM region */
+       struct xe_mem_region *vram;
+       /** @visible_size: Proped size of the CPU visible portion */
+--- /dev/null
++++ b/drivers/gpu/tests/Makefile
+@@ -0,0 +1,4 @@
++# SPDX-License-Identifier: GPL-2.0
++
++gpu_buddy_tests-y = gpu_buddy_test.o gpu_random.o
++obj-$(CONFIG_GPU_BUDDY_KUNIT_TEST) += gpu_buddy_tests.o
+--- /dev/null
++++ b/drivers/gpu/tests/gpu_buddy_test.c
+@@ -0,0 +1,778 @@
++// SPDX-License-Identifier: MIT
++/*
++ * Copyright Â© 2019 Intel Corporation
++ * Copyright Â© 2022 Maíra Canal <mairacanal@riseup.net>
++ */
++
++#include <kunit/test.h>
++
++#include <linux/prime_numbers.h>
++#include <linux/sched/signal.h>
++#include <linux/sizes.h>
++
++#include <linux/gpu_buddy.h>
++
++#include "gpu_random.h"
++
++static unsigned int random_seed;
++
++static inline u64 get_size(int order, u64 chunk_size)
++{
++      return (1 << order) * chunk_size;
++}
++
++static void drm_test_buddy_alloc_range_bias(struct kunit *test)
++{
++      u32 mm_size, size, ps, bias_size, bias_start, bias_end, bias_rem;
++      GPU_RND_STATE(prng, random_seed);
++      unsigned int i, count, *order;
++      struct gpu_buddy_block *block;
++      unsigned long flags;
++      struct gpu_buddy mm;
++      LIST_HEAD(allocated);
++
++      bias_size = SZ_1M;
++      ps = roundup_pow_of_two(prandom_u32_state(&prng) % bias_size);
++      ps = max(SZ_4K, ps);
++      mm_size = (SZ_8M-1) & ~(ps-1); /* Multiple roots */
++
++      kunit_info(test, "mm_size=%u, ps=%u\n", mm_size, ps);
++
++      KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_init(&mm, mm_size, ps),
++                             "buddy_init failed\n");
++
++      count = mm_size / bias_size;
++      order = gpu_random_order(count, &prng);
++      KUNIT_EXPECT_TRUE(test, order);
++
++      /*
++       * Idea is to split the address space into uniform bias ranges, and then
++       * in some random order allocate within each bias, using various
++       * patterns within. This should detect if allocations leak out from a
++       * given bias, for example.
++       */
++
++      for (i = 0; i < count; i++) {
++              LIST_HEAD(tmp);
++              u32 size;
++
++              bias_start = order[i] * bias_size;
++              bias_end = bias_start + bias_size;
++              bias_rem = bias_size;
++
++              /* internal round_up too big */
++              KUNIT_ASSERT_TRUE_MSG(test,
++                                    gpu_buddy_alloc_blocks(&mm, bias_start,
++                                                           bias_end, bias_size + ps, bias_size,
++                                                           &allocated,
++                                                           GPU_BUDDY_RANGE_ALLOCATION),
++                                    "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
++                                    bias_start, bias_end, bias_size, bias_size);
++
++              /* size too big */
++              KUNIT_ASSERT_TRUE_MSG(test,
++                                    gpu_buddy_alloc_blocks(&mm, bias_start,
++                                                           bias_end, bias_size + ps, ps,
++                                                           &allocated,
++                                                           GPU_BUDDY_RANGE_ALLOCATION),
++                                    "buddy_alloc didn't fail with bias(%x-%x), size=%u, ps=%u\n",
++                                    bias_start, bias_end, bias_size + ps, ps);
++
++              /* bias range too small for size */
++              KUNIT_ASSERT_TRUE_MSG(test,
++                                    gpu_buddy_alloc_blocks(&mm, bias_start + ps,
++                                                           bias_end, bias_size, ps,
++                                                           &allocated,
++                                                           GPU_BUDDY_RANGE_ALLOCATION),
++                                    "buddy_alloc didn't fail with bias(%x-%x), size=%u, ps=%u\n",
++                                    bias_start + ps, bias_end, bias_size, ps);
++
++              /* bias misaligned */
++              KUNIT_ASSERT_TRUE_MSG(test,
++                                    gpu_buddy_alloc_blocks(&mm, bias_start + ps,
++                                                           bias_end - ps,
++                                                           bias_size >> 1, bias_size >> 1,
++                                                           &allocated,
++                                                           GPU_BUDDY_RANGE_ALLOCATION),
++                                    "buddy_alloc h didn't fail with bias(%x-%x), size=%u, ps=%u\n",
++                                    bias_start + ps, bias_end - ps, bias_size >> 1, bias_size >> 1);
++
++              /* single big page */
++              KUNIT_ASSERT_FALSE_MSG(test,
++                                     gpu_buddy_alloc_blocks(&mm, bias_start,
++                                                            bias_end, bias_size, bias_size,
++                                                            &tmp,
++                                                            GPU_BUDDY_RANGE_ALLOCATION),
++                                     "buddy_alloc i failed with bias(%x-%x), size=%u, ps=%u\n",
++                                     bias_start, bias_end, bias_size, bias_size);
++              gpu_buddy_free_list(&mm, &tmp, 0);
++
++              /* single page with internal round_up */
++              KUNIT_ASSERT_FALSE_MSG(test,
++                                     gpu_buddy_alloc_blocks(&mm, bias_start,
++                                                            bias_end, ps, bias_size,
++                                                            &tmp,
++                                                            GPU_BUDDY_RANGE_ALLOCATION),
++                                     "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
++                                     bias_start, bias_end, ps, bias_size);
++              gpu_buddy_free_list(&mm, &tmp, 0);
++
++              /* random size within */
++              size = max(round_up(prandom_u32_state(&prng) % bias_rem, ps), ps);
++              if (size)
++                      KUNIT_ASSERT_FALSE_MSG(test,
++                                             gpu_buddy_alloc_blocks(&mm, bias_start,
++                                                                    bias_end, size, ps,
++                                                                    &tmp,
++                                                                    GPU_BUDDY_RANGE_ALLOCATION),
++                                             "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
++                                             bias_start, bias_end, size, ps);
++
++              bias_rem -= size;
++              /* too big for current avail */
++              KUNIT_ASSERT_TRUE_MSG(test,
++                                    gpu_buddy_alloc_blocks(&mm, bias_start,
++                                                           bias_end, bias_rem + ps, ps,
++                                                           &allocated,
++                                                           GPU_BUDDY_RANGE_ALLOCATION),
++                                    "buddy_alloc didn't fail with bias(%x-%x), size=%u, ps=%u\n",
++                                    bias_start, bias_end, bias_rem + ps, ps);
++
++              if (bias_rem) {
++                      /* random fill of the remainder */
++                      size = max(round_up(prandom_u32_state(&prng) % bias_rem, ps), ps);
++                      size = max(size, ps);
++
++                      KUNIT_ASSERT_FALSE_MSG(test,
++                                             gpu_buddy_alloc_blocks(&mm, bias_start,
++                                                                    bias_end, size, ps,
++                                                                    &allocated,
++                                                                    GPU_BUDDY_RANGE_ALLOCATION),
++                                             "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
++                                             bias_start, bias_end, size, ps);
++                      /*
++                       * Intentionally allow some space to be left
++                       * unallocated, and ideally not always on the bias
++                       * boundaries.
++                       */
++                      gpu_buddy_free_list(&mm, &tmp, 0);
++              } else {
++                      list_splice_tail(&tmp, &allocated);
++              }
++      }
++
++      kfree(order);
++      gpu_buddy_free_list(&mm, &allocated, 0);
++      gpu_buddy_fini(&mm);
++
++      /*
++       * Something more free-form. Idea is to pick a random starting bias
++       * range within the address space and then start filling it up. Also
++       * randomly grow the bias range in both directions as we go along. This
++       * should give us bias start/end which is not always uniform like above,
++       * and in some cases will require the allocator to jump over already
++       * allocated nodes in the middle of the address space.
++       */
++
++      KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_init(&mm, mm_size, ps),
++                             "buddy_init failed\n");
++
++      bias_start = round_up(prandom_u32_state(&prng) % (mm_size - ps), ps);
++      bias_end = round_up(bias_start + prandom_u32_state(&prng) % (mm_size - bias_start), ps);
++      bias_end = max(bias_end, bias_start + ps);
++      bias_rem = bias_end - bias_start;
++
++      do {
++              u32 size = max(round_up(prandom_u32_state(&prng) % bias_rem, ps), ps);
++
++              KUNIT_ASSERT_FALSE_MSG(test,
++                                     gpu_buddy_alloc_blocks(&mm, bias_start,
++                                                            bias_end, size, ps,
++                                                            &allocated,
++                                                            GPU_BUDDY_RANGE_ALLOCATION),
++                                     "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
++                                     bias_start, bias_end, size, ps);
++              bias_rem -= size;
++
++              /*
++               * Try to randomly grow the bias range in both directions, or
++               * only one, or perhaps don't grow at all.
++               */
++              do {
++                      u32 old_bias_start = bias_start;
++                      u32 old_bias_end = bias_end;
++
++                      if (bias_start)
++                              bias_start -= round_up(prandom_u32_state(&prng) % bias_start, ps);
++                      if (bias_end != mm_size)
++                              bias_end += round_up(prandom_u32_state(&prng) % (mm_size - bias_end), ps);
++
++                      bias_rem += old_bias_start - bias_start;
++                      bias_rem += bias_end - old_bias_end;
++              } while (!bias_rem && (bias_start || bias_end != mm_size));
++      } while (bias_rem);
++
++      KUNIT_ASSERT_EQ(test, bias_start, 0);
++      KUNIT_ASSERT_EQ(test, bias_end, mm_size);
++      KUNIT_ASSERT_TRUE_MSG(test,
++                            gpu_buddy_alloc_blocks(&mm, bias_start, bias_end,
++                                                   ps, ps,
++                                                   &allocated,
++                                                   GPU_BUDDY_RANGE_ALLOCATION),
++                            "buddy_alloc passed with bias(%x-%x), size=%u\n",
++                            bias_start, bias_end, ps);
++
++      gpu_buddy_free_list(&mm, &allocated, 0);
++      gpu_buddy_fini(&mm);
++
++      /*
++       * Allocate cleared blocks in the bias range when the DRM buddy's clear avail is
++       * zero. This will validate the bias range allocation in scenarios like system boot
++       * when no cleared blocks are available and exercise the fallback path too. The resulting
++       * blocks should always be dirty.
++       */
++
++      KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_init(&mm, mm_size, ps),
++                             "buddy_init failed\n");
++
++      bias_start = round_up(prandom_u32_state(&prng) % (mm_size - ps), ps);
++      bias_end = round_up(bias_start + prandom_u32_state(&prng) % (mm_size - bias_start), ps);
++      bias_end = max(bias_end, bias_start + ps);
++      bias_rem = bias_end - bias_start;
++
++      flags = GPU_BUDDY_CLEAR_ALLOCATION | GPU_BUDDY_RANGE_ALLOCATION;
++      size = max(round_up(prandom_u32_state(&prng) % bias_rem, ps), ps);
++
++      KUNIT_ASSERT_FALSE_MSG(test,
++                             gpu_buddy_alloc_blocks(&mm, bias_start,
++                                                    bias_end, size, ps,
++                                                    &allocated,
++                                                    flags),
++                             "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
++                             bias_start, bias_end, size, ps);
++
++      list_for_each_entry(block, &allocated, link)
++              KUNIT_EXPECT_EQ(test, gpu_buddy_block_is_clear(block), false);
++
++      gpu_buddy_free_list(&mm, &allocated, 0);
++      gpu_buddy_fini(&mm);
++}
++
++static void drm_test_buddy_alloc_clear(struct kunit *test)
++{
++      unsigned long n_pages, total, i = 0;
++      GPU_RND_STATE(prng, random_seed);
++      const unsigned long ps = SZ_4K;
++      struct gpu_buddy_block *block;
++      const int max_order = 12;
++      LIST_HEAD(allocated);
++      struct gpu_buddy mm;
++      unsigned int order;
++      u32 mm_size, size;
++      LIST_HEAD(dirty);
++      LIST_HEAD(clean);
++
++      mm_size = SZ_4K << max_order;
++      KUNIT_EXPECT_FALSE(test, gpu_buddy_init(&mm, mm_size, ps));
++
++      KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
++
++      /*
++       * Idea is to allocate and free some random portion of the address space,
++       * returning those pages as non-dirty and randomly alternate between
++       * requesting dirty and non-dirty pages (not going over the limit
++       * we freed as non-dirty), putting that into two separate lists.
++       * Loop over both lists at the end checking that the dirty list
++       * is indeed all dirty pages and vice versa. Free it all again,
++       * keeping the dirty/clear status.
++       */
++      KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size,
++                                                          5 * ps, ps, &allocated,
++                                                          GPU_BUDDY_TOPDOWN_ALLOCATION),
++                              "buddy_alloc hit an error size=%lu\n", 5 * ps);
++      gpu_buddy_free_list(&mm, &allocated, GPU_BUDDY_CLEARED);
++
++      n_pages = 10;
++      do {
++              unsigned long flags;
++              struct list_head *list;
++              int slot = i % 2;
++
++              if (slot == 0) {
++                      list = &dirty;
++                      flags = 0;
++              } else {
++                      list = &clean;
++                      flags = GPU_BUDDY_CLEAR_ALLOCATION;
++              }
++
++              KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size,
++                                                                  ps, ps, list,
++                                                                  flags),
++                                      "buddy_alloc hit an error size=%lu\n", ps);
++      } while (++i < n_pages);
++
++      list_for_each_entry(block, &clean, link)
++              KUNIT_EXPECT_EQ(test, gpu_buddy_block_is_clear(block), true);
++
++      list_for_each_entry(block, &dirty, link)
++              KUNIT_EXPECT_EQ(test, gpu_buddy_block_is_clear(block), false);
++
++      gpu_buddy_free_list(&mm, &clean, GPU_BUDDY_CLEARED);
++
++      /*
++       * Trying to go over the clear limit for some allocation.
++       * The allocation should never fail with reasonable page-size.
++       */
++      KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size,
++                                                          10 * ps, ps, &clean,
++                                                          GPU_BUDDY_CLEAR_ALLOCATION),
++                              "buddy_alloc hit an error size=%lu\n", 10 * ps);
++
++      gpu_buddy_free_list(&mm, &clean, GPU_BUDDY_CLEARED);
++      gpu_buddy_free_list(&mm, &dirty, 0);
++      gpu_buddy_fini(&mm);
++
++      KUNIT_EXPECT_FALSE(test, gpu_buddy_init(&mm, mm_size, ps));
++
++      /*
++       * Create a new mm. Intentionally fragment the address space by creating
++       * two alternating lists. Free both lists, one as dirty the other as clean.
++       * Try to allocate double the previous size with matching min_page_size. The
++       * allocation should never fail as it calls the force_merge. Also check that
++       * the page is always dirty after force_merge. Free the page as dirty, then
++       * repeat the whole thing, increment the order until we hit the max_order.
++       */
++
++      i = 0;
++      n_pages = mm_size / ps;
++      do {
++              struct list_head *list;
++              int slot = i % 2;
++
++              if (slot == 0)
++                      list = &dirty;
++              else
++                      list = &clean;
++
++              KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size,
++                                                                  ps, ps, list, 0),
++                                      "buddy_alloc hit an error size=%lu\n", ps);
++      } while (++i < n_pages);
++
++      gpu_buddy_free_list(&mm, &clean, GPU_BUDDY_CLEARED);
++      gpu_buddy_free_list(&mm, &dirty, 0);
++
++      order = 1;
++      do {
++              size = SZ_4K << order;
++
++              KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size,
++                                                                  size, size, &allocated,
++                                                                  GPU_BUDDY_CLEAR_ALLOCATION),
++                                      "buddy_alloc hit an error size=%u\n", size);
++              total = 0;
++              list_for_each_entry(block, &allocated, link) {
++                      if (size != mm_size)
++                              KUNIT_EXPECT_EQ(test, gpu_buddy_block_is_clear(block), false);
++                      total += gpu_buddy_block_size(&mm, block);
++              }
++              KUNIT_EXPECT_EQ(test, total, size);
++
++              gpu_buddy_free_list(&mm, &allocated, 0);
++      } while (++order <= max_order);
++
++      gpu_buddy_fini(&mm);
++
++      /*
++       * Create a new mm with a non power-of-two size. Allocate a random size, free as
++       * cleared and then call fini. This will ensure the multi-root force merge during
++       * fini.
++       */
++      mm_size = 12 * SZ_4K;
++      size = max(round_up(prandom_u32_state(&prng) % mm_size, ps), ps);
++      KUNIT_EXPECT_FALSE(test, gpu_buddy_init(&mm, mm_size, ps));
++      KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size,
++                                                          size, ps, &allocated,
++                                                          GPU_BUDDY_TOPDOWN_ALLOCATION),
++                              "buddy_alloc hit an error size=%u\n", size);
++      gpu_buddy_free_list(&mm, &allocated, GPU_BUDDY_CLEARED);
++      gpu_buddy_fini(&mm);
++}
++
++static void drm_test_buddy_alloc_contiguous(struct kunit *test)
++{
++      const unsigned long ps = SZ_4K, mm_size = 16 * 3 * SZ_4K;
++      unsigned long i, n_pages, total;
++      struct gpu_buddy_block *block;
++      struct gpu_buddy mm;
++      LIST_HEAD(left);
++      LIST_HEAD(middle);
++      LIST_HEAD(right);
++      LIST_HEAD(allocated);
++
++      KUNIT_EXPECT_FALSE(test, gpu_buddy_init(&mm, mm_size, ps));
++
++      /*
++       * Idea is to fragment the address space by alternating block
++       * allocations between three different lists; one for left, middle and
++       * right. We can then free a list to simulate fragmentation. In
++       * particular we want to exercise the GPU_BUDDY_CONTIGUOUS_ALLOCATION,
++       * including the try_harder path.
++       */
++
++      i = 0;
++      n_pages = mm_size / ps;
++      do {
++              struct list_head *list;
++              int slot = i % 3;
++
++              if (slot == 0)
++                      list = &left;
++              else if (slot == 1)
++                      list = &middle;
++              else
++                      list = &right;
++              KUNIT_ASSERT_FALSE_MSG(test,
++                                     gpu_buddy_alloc_blocks(&mm, 0, mm_size,
++                                                            ps, ps, list, 0),
++                                     "buddy_alloc hit an error size=%lu\n",
++                                     ps);
++      } while (++i < n_pages);
++
++      KUNIT_ASSERT_TRUE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size,
++                                                         3 * ps, ps, &allocated,
++                                                         GPU_BUDDY_CONTIGUOUS_ALLOCATION),
++                             "buddy_alloc didn't error size=%lu\n", 3 * ps);
++
++      gpu_buddy_free_list(&mm, &middle, 0);
++      KUNIT_ASSERT_TRUE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size,
++                                                         3 * ps, ps, &allocated,
++                                                         GPU_BUDDY_CONTIGUOUS_ALLOCATION),
++                             "buddy_alloc didn't error size=%lu\n", 3 * ps);
++      KUNIT_ASSERT_TRUE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size,
++                                                         2 * ps, ps, &allocated,
++                                                         GPU_BUDDY_CONTIGUOUS_ALLOCATION),
++                             "buddy_alloc didn't error size=%lu\n", 2 * ps);
++
++      gpu_buddy_free_list(&mm, &right, 0);
++      KUNIT_ASSERT_TRUE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size,
++                                                         3 * ps, ps, &allocated,
++                                                         GPU_BUDDY_CONTIGUOUS_ALLOCATION),
++                             "buddy_alloc didn't error size=%lu\n", 3 * ps);
++      /*
++       * At this point we should have enough contiguous space for 2 blocks,
++       * however they are never buddies (since we freed middle and right) so
++       * will require the try_harder logic to find them.
++       */
++      KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size,
++                                                          2 * ps, ps, &allocated,
++                                                          GPU_BUDDY_CONTIGUOUS_ALLOCATION),
++                             "buddy_alloc hit an error size=%lu\n", 2 * ps);
++
++      gpu_buddy_free_list(&mm, &left, 0);
++      KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size,
++                                                          3 * ps, ps, &allocated,
++                                                          GPU_BUDDY_CONTIGUOUS_ALLOCATION),
++                             "buddy_alloc hit an error size=%lu\n", 3 * ps);
++
++      total = 0;
++      list_for_each_entry(block, &allocated, link)
++              total += gpu_buddy_block_size(&mm, block);
++
++      KUNIT_ASSERT_EQ(test, total, ps * 2 + ps * 3);
++
++      gpu_buddy_free_list(&mm, &allocated, 0);
++      gpu_buddy_fini(&mm);
++}
++
++static void drm_test_buddy_alloc_pathological(struct kunit *test)
++{
++      u64 mm_size, size, start = 0;
++      struct gpu_buddy_block *block;
++      const int max_order = 3;
++      unsigned long flags = 0;
++      int order, top;
++      struct gpu_buddy mm;
++      LIST_HEAD(blocks);
++      LIST_HEAD(holes);
++      LIST_HEAD(tmp);
++
++      /*
++       * Create a pot-sized mm, then allocate one of each possible
++       * order within. This should leave the mm with exactly one
++       * page left. Free the largest block, then whittle down again.
++       * Eventually we will have a fully 50% fragmented mm.
++       */
++
++      mm_size = SZ_4K << max_order;
++      KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_init(&mm, mm_size, SZ_4K),
++                             "buddy_init failed\n");
++
++      KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
++
++      for (top = max_order; top; top--) {
++              /* Make room by freeing the largest allocated block */
++              block = list_first_entry_or_null(&blocks, typeof(*block), link);
++              if (block) {
++                      list_del(&block->link);
++                      gpu_buddy_free_block(&mm, block);
++              }
++
++              for (order = top; order--;) {
++                      size = get_size(order, mm.chunk_size);
++                      KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, start,
++                                                                          mm_size, size, size,
++                                                                              &tmp, flags),
++                                      "buddy_alloc hit -ENOMEM with order=%d, top=%d\n",
++                                      order, top);
++
++                      block = list_first_entry_or_null(&tmp, struct gpu_buddy_block, link);
++                      KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
++
++                      list_move_tail(&block->link, &blocks);
++              }
++
++              /* There should be one final page for this sub-allocation */
++              size = get_size(0, mm.chunk_size);
++              KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size,
++                                                                  size, size, &tmp, flags),
++                                                         "buddy_alloc hit -ENOMEM for hole\n");
++
++              block = list_first_entry_or_null(&tmp, struct gpu_buddy_block, link);
++              KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
++
++              list_move_tail(&block->link, &holes);
++
++              size = get_size(top, mm.chunk_size);
++              KUNIT_ASSERT_TRUE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size,
++                                                                 size, size, &tmp, flags),
++                                                        "buddy_alloc unexpectedly succeeded at top-order %d/%d, it should be full!",
++                                                        top, max_order);
++      }
++
++      gpu_buddy_free_list(&mm, &holes, 0);
++
++      /* Nothing larger than blocks of chunk_size now available */
++      for (order = 1; order <= max_order; order++) {
++              size = get_size(order, mm.chunk_size);
++              KUNIT_ASSERT_TRUE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size,
++                                                                 size, size, &tmp, flags),
++                                                        "buddy_alloc unexpectedly succeeded at order %d, it should be full!",
++                                                        order);
++      }
++
++      list_splice_tail(&holes, &blocks);
++      gpu_buddy_free_list(&mm, &blocks, 0);
++      gpu_buddy_fini(&mm);
++}
++
++static void drm_test_buddy_alloc_pessimistic(struct kunit *test)
++{
++      u64 mm_size, size, start = 0;
++      struct gpu_buddy_block *block, *bn;
++      const unsigned int max_order = 16;
++      unsigned long flags = 0;
++      struct gpu_buddy mm;
++      unsigned int order;
++      LIST_HEAD(blocks);
++      LIST_HEAD(tmp);
++
++      /*
++       * Create a pot-sized mm, then allocate one of each possible
++       * order within. This should leave the mm with exactly one
++       * page left.
++       */
++
++      mm_size = SZ_4K << max_order;
++      KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_init(&mm, mm_size, SZ_4K),
++                             "buddy_init failed\n");
++
++      KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
++
++      for (order = 0; order < max_order; order++) {
++              size = get_size(order, mm.chunk_size);
++              KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size,
++                                                                  size, size, &tmp, flags),
++                                                         "buddy_alloc hit -ENOMEM with order=%d\n",
++                                                         order);
++
++              block = list_first_entry_or_null(&tmp, struct gpu_buddy_block, link);
++              KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
++
++              list_move_tail(&block->link, &blocks);
++      }
++
++      /* And now the last remaining block available */
++      size = get_size(0, mm.chunk_size);
++      KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size,
++                                                          size, size, &tmp, flags),
++                                                 "buddy_alloc hit -ENOMEM on final alloc\n");
++
++      block = list_first_entry_or_null(&tmp, struct gpu_buddy_block, link);
++      KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
++
++      list_move_tail(&block->link, &blocks);
++
++      /* Should be completely full! */
++      for (order = max_order; order--;) {
++              size = get_size(order, mm.chunk_size);
++              KUNIT_ASSERT_TRUE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size,
++                                                                 size, size, &tmp, flags),
++                                                        "buddy_alloc unexpectedly succeeded, it should be full!");
++      }
++
++      block = list_last_entry(&blocks, typeof(*block), link);
++      list_del(&block->link);
++      gpu_buddy_free_block(&mm, block);
++
++      /* As we free in increasing size, we make available larger blocks */
++      order = 1;
++      list_for_each_entry_safe(block, bn, &blocks, link) {
++              list_del(&block->link);
++              gpu_buddy_free_block(&mm, block);
++
++              size = get_size(order, mm.chunk_size);
++              KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size,
++                                                                  size, size, &tmp, flags),
++                                                         "buddy_alloc hit -ENOMEM with order=%d\n",
++                                                         order);
++
++              block = list_first_entry_or_null(&tmp, struct gpu_buddy_block, link);
++              KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
++
++              list_del(&block->link);
++              gpu_buddy_free_block(&mm, block);
++              order++;
++      }
++
++      /* To confirm, now the whole mm should be available */
++      size = get_size(max_order, mm.chunk_size);
++      KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size,
++                                                          size, size, &tmp, flags),
++                                                 "buddy_alloc (realloc) hit -ENOMEM with order=%d\n",
++                                                 max_order);
++
++      block = list_first_entry_or_null(&tmp, struct gpu_buddy_block, link);
++      KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
++
++      list_del(&block->link);
++      gpu_buddy_free_block(&mm, block);
++      gpu_buddy_free_list(&mm, &blocks, 0);
++      gpu_buddy_fini(&mm);
++}
++
++static void drm_test_buddy_alloc_optimistic(struct kunit *test)
++{
++      u64 mm_size, size, start = 0;
++      struct gpu_buddy_block *block;
++      unsigned long flags = 0;
++      const int max_order = 16;
++      struct gpu_buddy mm;
++      LIST_HEAD(blocks);
++      LIST_HEAD(tmp);
++      int order;
++
++      /*
++       * Create a mm with one block of each order available, and
++       * try to allocate them all.
++       */
++
++      mm_size = SZ_4K * ((1 << (max_order + 1)) - 1);
++
++      KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_init(&mm, mm_size, SZ_4K),
++                             "buddy_init failed\n");
++
++      KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
++
++      for (order = 0; order <= max_order; order++) {
++              size = get_size(order, mm.chunk_size);
++              KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size,
++                                                                  size, size, &tmp, flags),
++                                                         "buddy_alloc hit -ENOMEM with order=%d\n",
++                                                         order);
++
++              block = list_first_entry_or_null(&tmp, struct gpu_buddy_block, link);
++              KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
++
++              list_move_tail(&block->link, &blocks);
++      }
++
++      /* Should be completely full! */
++      size = get_size(0, mm.chunk_size);
++      KUNIT_ASSERT_TRUE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size,
++                                                         size, size, &tmp, flags),
++                                                "buddy_alloc unexpectedly succeeded, it should be full!");
++
++      gpu_buddy_free_list(&mm, &blocks, 0);
++      gpu_buddy_fini(&mm);
++}
++
++static void drm_test_buddy_alloc_limit(struct kunit *test)
++{
++      u64 size = U64_MAX, start = 0;
++      struct gpu_buddy_block *block;
++      unsigned long flags = 0;
++      LIST_HEAD(allocated);
++      struct gpu_buddy mm;
++
++      KUNIT_EXPECT_FALSE(test, gpu_buddy_init(&mm, size, SZ_4K));
++
++      KUNIT_EXPECT_EQ_MSG(test, mm.max_order, GPU_BUDDY_MAX_ORDER,
++                          "mm.max_order(%d) != %d\n", mm.max_order,
++                                              GPU_BUDDY_MAX_ORDER);
++
++      size = mm.chunk_size << mm.max_order;
++      KUNIT_EXPECT_FALSE(test, gpu_buddy_alloc_blocks(&mm, start, size, size,
++                                                      mm.chunk_size, &allocated, flags));
++
++      block = list_first_entry_or_null(&allocated, struct gpu_buddy_block, link);
++      KUNIT_EXPECT_TRUE(test, block);
++
++      KUNIT_EXPECT_EQ_MSG(test, gpu_buddy_block_order(block), mm.max_order,
++                          "block order(%d) != %d\n",
++                                              gpu_buddy_block_order(block), mm.max_order);
++
++      KUNIT_EXPECT_EQ_MSG(test, gpu_buddy_block_size(&mm, block),
++                          BIT_ULL(mm.max_order) * mm.chunk_size,
++                                              "block size(%llu) != %llu\n",
++                                              gpu_buddy_block_size(&mm, block),
++                                              BIT_ULL(mm.max_order) * mm.chunk_size);
++
++      gpu_buddy_free_list(&mm, &allocated, 0);
++      gpu_buddy_fini(&mm);
++}
++
++static int gpu_buddy_suite_init(struct kunit_suite *suite)
++{
++      while (!random_seed)
++              random_seed = get_random_u32();
++
++      kunit_info(suite, "Testing DRM buddy manager, with random_seed=0x%x\n",
++                 random_seed);
++
++      return 0;
++}
++
++static struct kunit_case gpu_buddy_tests[] = {
++      KUNIT_CASE(drm_test_buddy_alloc_limit),
++      KUNIT_CASE(drm_test_buddy_alloc_optimistic),
++      KUNIT_CASE(drm_test_buddy_alloc_pessimistic),
++      KUNIT_CASE(drm_test_buddy_alloc_pathological),
++      KUNIT_CASE(drm_test_buddy_alloc_contiguous),
++      KUNIT_CASE(drm_test_buddy_alloc_clear),
++      KUNIT_CASE(drm_test_buddy_alloc_range_bias),
++      {}
++};
++
++static struct kunit_suite gpu_buddy_test_suite = {
++      .name = "gpu_buddy",
++      .suite_init = gpu_buddy_suite_init,
++      .test_cases = gpu_buddy_tests,
++};
++
++kunit_test_suite(gpu_buddy_test_suite);
++
++MODULE_AUTHOR("Intel Corporation");
++MODULE_DESCRIPTION("Kunit test for gpu_buddy functions");
++MODULE_LICENSE("GPL");
+--- /dev/null
++++ b/drivers/gpu/tests/gpu_random.c
+@@ -0,0 +1,43 @@
++// SPDX-License-Identifier: GPL-2.0
++#include <linux/bitops.h>
++#include <linux/kernel.h>
++#include <linux/random.h>
++#include <linux/slab.h>
++#include <linux/types.h>
++
++#include "gpu_random.h"
++
++u32 gpu_prandom_u32_max_state(u32 ep_ro, struct rnd_state *state)
++{
++      return upper_32_bits((u64)prandom_u32_state(state) * ep_ro);
++}
++EXPORT_SYMBOL(gpu_prandom_u32_max_state);
++
++void gpu_random_reorder(unsigned int *order, unsigned int count,
++                      struct rnd_state *state)
++{
++      unsigned int i, j;
++
++      for (i = 0; i < count; ++i) {
++              BUILD_BUG_ON(sizeof(unsigned int) > sizeof(u32));
++              j = gpu_prandom_u32_max_state(count, state);
++              swap(order[i], order[j]);
++      }
++}
++EXPORT_SYMBOL(gpu_random_reorder);
++
++unsigned int *gpu_random_order(unsigned int count, struct rnd_state *state)
++{
++      unsigned int *order, i;
++
++      order = kmalloc_array(count, sizeof(*order), GFP_KERNEL);
++      if (!order)
++              return order;
++
++      for (i = 0; i < count; i++)
++              order[i] = i;
++
++      gpu_random_reorder(order, count, state);
++      return order;
++}
++EXPORT_SYMBOL(gpu_random_order);
+--- /dev/null
++++ b/drivers/gpu/tests/gpu_random.h
+@@ -0,0 +1,28 @@
++/* SPDX-License-Identifier: GPL-2.0 */
++#ifndef __GPU_RANDOM_H__
++#define __GPU_RANDOM_H__
++
++/* This is a temporary home for a couple of utility functions that should
++ * be transposed to lib/ at the earliest convenience.
++ */
++
++#include <linux/random.h>
++
++#define GPU_RND_STATE_INITIALIZER(seed__) ({                          \
++      struct rnd_state state__;                                       \
++      prandom_seed_state(&state__, (seed__));                         \
++      state__;                                                        \
++})
++
++#define GPU_RND_STATE(name__, seed__) \
++      struct rnd_state name__ = GPU_RND_STATE_INITIALIZER(seed__)
++
++unsigned int *gpu_random_order(unsigned int count,
++                             struct rnd_state *state);
++void gpu_random_reorder(unsigned int *order,
++                      unsigned int count,
++                      struct rnd_state *state);
++u32 gpu_prandom_u32_max_state(u32 ep_ro,
++                            struct rnd_state *state);
++
++#endif /* !__GPU_RANDOM_H__ */
+--- a/drivers/video/Kconfig
++++ b/drivers/video/Kconfig
+@@ -37,6 +37,7 @@ source "drivers/char/agp/Kconfig"
+ source "drivers/gpu/vga/Kconfig"
++source "drivers/gpu/Kconfig"
+ source "drivers/gpu/host1x/Kconfig"
+ source "drivers/gpu/ipu-v3/Kconfig"
+--- a/include/drm/drm_buddy.h
++++ b/include/drm/drm_buddy.h
+@@ -6,175 +6,13 @@
+ #ifndef __DRM_BUDDY_H__
+ #define __DRM_BUDDY_H__
+-#include <linux/bitops.h>
+-#include <linux/list.h>
+-#include <linux/slab.h>
+-#include <linux/sched.h>
+-#include <linux/rbtree.h>
++#include <linux/gpu_buddy.h>
+-#include <drm/drm_print.h>
++struct drm_printer;
+-#define range_overflows(start, size, max) ({ \
+-      typeof(start) start__ = (start); \
+-      typeof(size) size__ = (size); \
+-      typeof(max) max__ = (max); \
+-      (void)(&start__ == &size__); \
+-      (void)(&start__ == &max__); \
+-      start__ >= max__ || size__ > max__ - start__; \
+-})
+-
+-#define DRM_BUDDY_RANGE_ALLOCATION            BIT(0)
+-#define DRM_BUDDY_TOPDOWN_ALLOCATION          BIT(1)
+-#define DRM_BUDDY_CONTIGUOUS_ALLOCATION               BIT(2)
+-#define DRM_BUDDY_CLEAR_ALLOCATION            BIT(3)
+-#define DRM_BUDDY_CLEARED                     BIT(4)
+-#define DRM_BUDDY_TRIM_DISABLE                        BIT(5)
+-
+-struct drm_buddy_block {
+-#define DRM_BUDDY_HEADER_OFFSET GENMASK_ULL(63, 12)
+-#define DRM_BUDDY_HEADER_STATE  GENMASK_ULL(11, 10)
+-#define   DRM_BUDDY_ALLOCATED    (1 << 10)
+-#define   DRM_BUDDY_FREE         (2 << 10)
+-#define   DRM_BUDDY_SPLIT        (3 << 10)
+-#define DRM_BUDDY_HEADER_CLEAR  GENMASK_ULL(9, 9)
+-/* Free to be used, if needed in the future */
+-#define DRM_BUDDY_HEADER_UNUSED GENMASK_ULL(8, 6)
+-#define DRM_BUDDY_HEADER_ORDER  GENMASK_ULL(5, 0)
+-      u64 header;
+-
+-      struct drm_buddy_block *left;
+-      struct drm_buddy_block *right;
+-      struct drm_buddy_block *parent;
+-
+-      void *private; /* owned by creator */
+-
+-      /*
+-       * While the block is allocated by the user through drm_buddy_alloc*,
+-       * the user has ownership of the link, for example to maintain within
+-       * a list, if so desired. As soon as the block is freed with
+-       * drm_buddy_free* ownership is given back to the mm.
+-       */
+-      union {
+-              struct rb_node rb;
+-              struct list_head link;
+-      };
+-
+-      struct list_head tmp_link;
+-};
+-
+-/* Order-zero must be at least SZ_4K */
+-#define DRM_BUDDY_MAX_ORDER (63 - 12)
+-
+-/*
+- * Binary Buddy System.
+- *
+- * Locking should be handled by the user, a simple mutex around
+- * drm_buddy_alloc* and drm_buddy_free* should suffice.
+- */
+-struct drm_buddy {
+-      /* Maintain a free list for each order. */
+-      struct rb_root **free_trees;
+-
+-      /*
+-       * Maintain explicit binary tree(s) to track the allocation of the
+-       * address space. This gives us a simple way of finding a buddy block
+-       * and performing the potentially recursive merge step when freeing a
+-       * block.  Nodes are either allocated or free, in which case they will
+-       * also exist on the respective free list.
+-       */
+-      struct drm_buddy_block **roots;
+-
+-      /*
+-       * Anything from here is public, and remains static for the lifetime of
+-       * the mm. Everything above is considered do-not-touch.
+-       */
+-      unsigned int n_roots;
+-      unsigned int max_order;
+-
+-      /* Must be at least SZ_4K */
+-      u64 chunk_size;
+-      u64 size;
+-      u64 avail;
+-      u64 clear_avail;
+-};
+-
+-static inline u64
+-drm_buddy_block_offset(const struct drm_buddy_block *block)
+-{
+-      return block->header & DRM_BUDDY_HEADER_OFFSET;
+-}
+-
+-static inline unsigned int
+-drm_buddy_block_order(struct drm_buddy_block *block)
+-{
+-      return block->header & DRM_BUDDY_HEADER_ORDER;
+-}
+-
+-static inline unsigned int
+-drm_buddy_block_state(struct drm_buddy_block *block)
+-{
+-      return block->header & DRM_BUDDY_HEADER_STATE;
+-}
+-
+-static inline bool
+-drm_buddy_block_is_allocated(struct drm_buddy_block *block)
+-{
+-      return drm_buddy_block_state(block) == DRM_BUDDY_ALLOCATED;
+-}
+-
+-static inline bool
+-drm_buddy_block_is_clear(struct drm_buddy_block *block)
+-{
+-      return block->header & DRM_BUDDY_HEADER_CLEAR;
+-}
+-
+-static inline bool
+-drm_buddy_block_is_free(struct drm_buddy_block *block)
+-{
+-      return drm_buddy_block_state(block) == DRM_BUDDY_FREE;
+-}
+-
+-static inline bool
+-drm_buddy_block_is_split(struct drm_buddy_block *block)
+-{
+-      return drm_buddy_block_state(block) == DRM_BUDDY_SPLIT;
+-}
+-
+-static inline u64
+-drm_buddy_block_size(struct drm_buddy *mm,
+-                   struct drm_buddy_block *block)
+-{
+-      return mm->chunk_size << drm_buddy_block_order(block);
+-}
+-
+-int drm_buddy_init(struct drm_buddy *mm, u64 size, u64 chunk_size);
+-
+-void drm_buddy_fini(struct drm_buddy *mm);
+-
+-struct drm_buddy_block *
+-drm_get_buddy(struct drm_buddy_block *block);
+-
+-int drm_buddy_alloc_blocks(struct drm_buddy *mm,
+-                         u64 start, u64 end, u64 size,
+-                         u64 min_page_size,
+-                         struct list_head *blocks,
+-                         unsigned long flags);
+-
+-int drm_buddy_block_trim(struct drm_buddy *mm,
+-                       u64 *start,
+-                       u64 new_size,
+-                       struct list_head *blocks);
+-
+-void drm_buddy_reset_clear(struct drm_buddy *mm, bool is_clear);
+-
+-void drm_buddy_free_block(struct drm_buddy *mm, struct drm_buddy_block *block);
+-
+-void drm_buddy_free_list(struct drm_buddy *mm,
+-                       struct list_head *objects,
+-                       unsigned int flags);
+-
+-void drm_buddy_print(struct drm_buddy *mm, struct drm_printer *p);
+-void drm_buddy_block_print(struct drm_buddy *mm,
+-                         struct drm_buddy_block *block,
++/* DRM-specific GPU Buddy Allocator print helpers */
++void drm_buddy_print(struct gpu_buddy *mm, struct drm_printer *p);
++void drm_buddy_block_print(struct gpu_buddy *mm,
++                         struct gpu_buddy_block *block,
+                          struct drm_printer *p);
+ #endif
+--- /dev/null
++++ b/include/linux/gpu_buddy.h
+@@ -0,0 +1,188 @@
++/* SPDX-License-Identifier: MIT */
++/*
++ * Copyright Â© 2021 Intel Corporation
++ */
++
++#ifndef __GPU_BUDDY_H__
++#define __GPU_BUDDY_H__
++
++#include <linux/bitops.h>
++#include <linux/list.h>
++#include <linux/slab.h>
++#include <linux/sched.h>
++#include <linux/rbtree.h>
++
++#include <drm/drm_print.h>
++
++#define range_overflows(start, size, max) ({ \
++      typeof(start) start__ = (start); \
++      typeof(size) size__ = (size); \
++      typeof(max) max__ = (max); \
++      (void)(&start__ == &size__); \
++      (void)(&start__ == &max__); \
++      start__ >= max__ || size__ > max__ - start__; \
++})
++
++#define GPU_BUDDY_RANGE_ALLOCATION            BIT(0)
++#define GPU_BUDDY_TOPDOWN_ALLOCATION          BIT(1)
++#define GPU_BUDDY_CONTIGUOUS_ALLOCATION               BIT(2)
++#define GPU_BUDDY_CLEAR_ALLOCATION            BIT(3)
++#define GPU_BUDDY_CLEARED                     BIT(4)
++#define GPU_BUDDY_TRIM_DISABLE                        BIT(5)
++
++enum gpu_buddy_free_tree {
++      GPU_BUDDY_CLEAR_TREE = 0,
++      GPU_BUDDY_DIRTY_TREE,
++      GPU_BUDDY_MAX_FREE_TREES,
++};
++
++#define for_each_free_tree(tree) \
++      for ((tree) = 0; (tree) < GPU_BUDDY_MAX_FREE_TREES; (tree)++)
++
++struct gpu_buddy_block {
++#define GPU_BUDDY_HEADER_OFFSET GENMASK_ULL(63, 12)
++#define GPU_BUDDY_HEADER_STATE  GENMASK_ULL(11, 10)
++#define   GPU_BUDDY_ALLOCATED    (1 << 10)
++#define   GPU_BUDDY_FREE         (2 << 10)
++#define   GPU_BUDDY_SPLIT        (3 << 10)
++#define GPU_BUDDY_HEADER_CLEAR  GENMASK_ULL(9, 9)
++/* Free to be used, if needed in the future */
++#define GPU_BUDDY_HEADER_UNUSED GENMASK_ULL(8, 6)
++#define GPU_BUDDY_HEADER_ORDER  GENMASK_ULL(5, 0)
++      u64 header;
++
++      struct gpu_buddy_block *left;
++      struct gpu_buddy_block *right;
++      struct gpu_buddy_block *parent;
++
++      void *private; /* owned by creator */
++
++      /*
++       * While the block is allocated by the user through gpu_buddy_alloc*,
++       * the user has ownership of the link, for example to maintain within
++       * a list, if so desired. As soon as the block is freed with
++       * gpu_buddy_free* ownership is given back to the mm.
++       */
++      union {
++              struct rb_node rb;
++              struct list_head link;
++      };
++
++      struct list_head tmp_link;
++};
++
++/* Order-zero must be at least SZ_4K */
++#define GPU_BUDDY_MAX_ORDER (63 - 12)
++
++/*
++ * Binary Buddy System.
++ *
++ * Locking should be handled by the user, a simple mutex around
++ * gpu_buddy_alloc* and gpu_buddy_free* should suffice.
++ */
++struct gpu_buddy {
++      /* Maintain a free list for each order. */
++      struct rb_root **free_trees;
++
++      /*
++       * Maintain explicit binary tree(s) to track the allocation of the
++       * address space. This gives us a simple way of finding a buddy block
++       * and performing the potentially recursive merge step when freeing a
++       * block.  Nodes are either allocated or free, in which case they will
++       * also exist on the respective free list.
++       */
++      struct gpu_buddy_block **roots;
++
++      /*
++       * Anything from here is public, and remains static for the lifetime of
++       * the mm. Everything above is considered do-not-touch.
++       */
++      unsigned int n_roots;
++      unsigned int max_order;
++
++      /* Must be at least SZ_4K */
++      u64 chunk_size;
++      u64 size;
++      u64 avail;
++      u64 clear_avail;
++};
++
++static inline u64
++gpu_buddy_block_offset(const struct gpu_buddy_block *block)
++{
++      return block->header & GPU_BUDDY_HEADER_OFFSET;
++}
++
++static inline unsigned int
++gpu_buddy_block_order(struct gpu_buddy_block *block)
++{
++      return block->header & GPU_BUDDY_HEADER_ORDER;
++}
++
++static inline unsigned int
++gpu_buddy_block_state(struct gpu_buddy_block *block)
++{
++      return block->header & GPU_BUDDY_HEADER_STATE;
++}
++
++static inline bool
++gpu_buddy_block_is_allocated(struct gpu_buddy_block *block)
++{
++      return gpu_buddy_block_state(block) == GPU_BUDDY_ALLOCATED;
++}
++
++static inline bool
++gpu_buddy_block_is_clear(struct gpu_buddy_block *block)
++{
++      return block->header & GPU_BUDDY_HEADER_CLEAR;
++}
++
++static inline bool
++gpu_buddy_block_is_free(struct gpu_buddy_block *block)
++{
++      return gpu_buddy_block_state(block) == GPU_BUDDY_FREE;
++}
++
++static inline bool
++gpu_buddy_block_is_split(struct gpu_buddy_block *block)
++{
++      return gpu_buddy_block_state(block) == GPU_BUDDY_SPLIT;
++}
++
++static inline u64
++gpu_buddy_block_size(struct gpu_buddy *mm,
++                   struct gpu_buddy_block *block)
++{
++      return mm->chunk_size << gpu_buddy_block_order(block);
++}
++
++int gpu_buddy_init(struct gpu_buddy *mm, u64 size, u64 chunk_size);
++
++void gpu_buddy_fini(struct gpu_buddy *mm);
++
++struct gpu_buddy_block *
++gpu_get_buddy(struct gpu_buddy_block *block);
++
++int gpu_buddy_alloc_blocks(struct gpu_buddy *mm,
++                         u64 start, u64 end, u64 size,
++                         u64 min_page_size,
++                         struct list_head *blocks,
++                         unsigned long flags);
++
++int gpu_buddy_block_trim(struct gpu_buddy *mm,
++                       u64 *start,
++                       u64 new_size,
++                       struct list_head *blocks);
++
++void gpu_buddy_reset_clear(struct gpu_buddy *mm, bool is_clear);
++
++void gpu_buddy_free_block(struct gpu_buddy *mm, struct gpu_buddy_block *block);
++
++void gpu_buddy_free_list(struct gpu_buddy *mm,
++                       struct list_head *objects,
++                       unsigned int flags);
++
++void gpu_buddy_print(struct gpu_buddy *mm);
++void gpu_buddy_block_print(struct gpu_buddy *mm,
++                         struct gpu_buddy_block *block);
++#endif
diff --git a/queue-6.12/gve-fix-header-buffer-corruption-with-header-split-and-hw-gro.patch b/queue-6.12/gve-fix-header-buffer-corruption-with-header-split-and-hw-gro.patch
new file mode 100644 (file)
index 0000000..8b34854
--- /dev/null
@@ -0,0 +1,143 @@
+From stable+bounces-289989-greg=kroah.com@vger.kernel.org Tue Jul 28 15:21:31 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 09:20:04 -0400
+Subject: gve: fix header buffer corruption with header-split and HW-GRO
+To: stable@vger.kernel.org
+Cc: Ankit Garg <nktgrg@google.com>, Praveen Kaligineedi <pkaligineedi@google.com>, Jordan Rhee <jordanrhee@google.com>, Harshitha Ramamurthy <hramamurthy@google.com>, Joshua Washington <joshwash@google.com>, Eric Dumazet <edumazet@google.com>, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260728132004.3954154-2-sashal@kernel.org>
+
+From: Ankit Garg <nktgrg@google.com>
+
+[ Upstream commit d676c9a73bdcd8237425dbb826f2bd1a25c36e40 ]
+
+The DQO RX datapath programs a per-buffer-queue-descriptor
+header_buf_addr at post time and reads the split header back at
+completion time. Both the post and the read currently index the
+header buffer by queue position rather than by the buffer's identity:
+
+  - post (gve_rx_post_buffers_dqo): header_buf_addr is computed from
+    bufq->tail
+  - read (gve_rx_dqo): the header is read from desc_idx (the completion
+    queue head index)
+
+This relies on the buffer-queue index and the completion-queue index
+being equal for the start of every packet, i.e. on the device consuming
+posted buffers and returning completions in the exact same order. That
+assumption does not hold once HW-GRO is enabled with multiple
+flows: coalesced segments are accepted and completed in an order that
+may differ from the order buffers were posted, and segments from
+different flows may interleave.
+
+That results in two problems:
+
+1. Wrong header slot on read. Because the read offset is derived from
+   the completion index (desc_idx) while the device wrote the header to
+   the address programmed for the buffer's buf_id, the driver can copy
+   a header belonging to a different packet. This shows up as
+   throughput drop (about 30% drop and large numbers of TCP
+   retransmissions) with header-split and HW-GRO both enabled and many
+   streams.
+
+2. Header buffer reused while still owned by the device. The driver
+   advances bufq->head by one per completion and re-posts buffers based
+   on that. Arrival of N RX completions only guarantees that at least N
+   RX buffer descriptors have been read by the device. It does not
+   guarantee that the device has relinquished the ownership of all the
+   buffers corresponding to those N descriptors. With out-of-order
+   completions (e.g. the completion for a packet copied into buffer N
+   arrives before the completion for a packet copied into buffer N-1),
+   the driver can re-post and overwrite a header buffer that the device
+   is still going to write into, corrupting the header of a packet
+   whose completion has not yet been processed.
+
+Fix both issues by indexing the header buffer by buf_id on both the post
+and read paths. Reading from buf_id's slot is therefore always correct
+regardless of completion ordering (fixes problem 1).
+
+Indexing by buf_id also ties each header slot to the lifetime of its
+buffer state. A buffer state is only returned to the free/recycle lists
+when its own completion (buf_id) is processed, so its header slot can
+only be re-posted after the device is done with it. This makes header
+slot reuse safe under out-of-order completions (fixes problem 2).
+
+Allocate (gve_rx_alloc_hdr_bufs) and free (gve_rx_free_hdr_bufs) the
+header buffers based on num_buf_states to match the buf_id indexing.
+
+Cc: stable@vger.kernel.org
+Fixes: 5e37d8254e7f ("gve: Add header split data path")
+Signed-off-by: Ankit Garg <nktgrg@google.com>
+Reviewed-by: Praveen Kaligineedi <pkaligineedi@google.com>
+Reviewed-by: Jordan Rhee <jordanrhee@google.com>
+Reviewed-by: Harshitha Ramamurthy <hramamurthy@google.com>
+Signed-off-by: Joshua Washington <joshwash@google.com>
+Reviewed-by: Eric Dumazet <edumazet@google.com>
+Link: https://patch.msgid.link/20260617013208.3781453-1-joshwash@google.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/net/ethernet/google/gve/gve_rx_dqo.c |   28 +++++++++++++++++----------
+ 1 file changed, 18 insertions(+), 10 deletions(-)
+
+--- a/drivers/net/ethernet/google/gve/gve_rx_dqo.c
++++ b/drivers/net/ethernet/google/gve/gve_rx_dqo.c
+@@ -212,11 +212,13 @@ static void gve_rx_starvation_timer(stru
+ static void gve_rx_free_hdr_bufs(struct gve_priv *priv, struct gve_rx_ring *rx)
+ {
+       struct device *hdev = &priv->pdev->dev;
+-      int buf_count = rx->dqo.bufq.mask + 1;
+       if (rx->dqo.hdr_bufs.data) {
+-              dma_free_coherent(hdev, priv->header_buf_size * buf_count,
+-                                rx->dqo.hdr_bufs.data, rx->dqo.hdr_bufs.addr);
++              size_t size =
++                      (size_t)priv->header_buf_size * rx->dqo.num_buf_states;
++
++              dma_free_coherent(hdev, size, rx->dqo.hdr_bufs.data,
++                                rx->dqo.hdr_bufs.addr);
+               rx->dqo.hdr_bufs.data = NULL;
+       }
+ }
+@@ -421,7 +423,7 @@ int gve_rx_alloc_ring_dqo(struct gve_pri
+       /* Allocate header buffers for header-split */
+       if (cfg->enable_header_split)
+-              if (gve_rx_alloc_hdr_bufs(priv, rx, buffer_queue_slots))
++              if (gve_rx_alloc_hdr_bufs(priv, rx, rx->dqo.num_buf_states))
+                       goto err;
+       /* Allocate RX completion queue */
+@@ -557,10 +559,13 @@ void gve_rx_post_buffers_dqo(struct gve_
+               desc->buf_id = cpu_to_le16(buf_state - rx->dqo.buf_states);
+               desc->buf_addr = cpu_to_le64(buf_state->addr +
+                                            buf_state->page_info.page_offset);
+-              if (rx->dqo.hdr_bufs.data)
++              if (rx->dqo.hdr_bufs.data) {
++                      u16 buf_id = le16_to_cpu(desc->buf_id);
++
+                       desc->header_buf_addr =
+                               cpu_to_le64(rx->dqo.hdr_bufs.addr +
+-                                          priv->header_buf_size * bufq->tail);
++                                      (size_t)priv->header_buf_size * buf_id);
++              }
+               bufq->tail = (bufq->tail + 1) & bufq->mask;
+               complq->num_free_slots--;
+@@ -827,10 +832,13 @@ static int gve_rx_dqo(struct napi_struct
+               int unsplit = 0;
+               if (hdr_len && !hbo) {
+-                      rx->ctx.skb_head = gve_rx_copy_data(priv->dev, napi,
+-                                                          rx->dqo.hdr_bufs.data +
+-                                                          desc_idx * priv->header_buf_size,
+-                                                          hdr_len);
++                      size_t offset =
++                              (size_t)buffer_id * priv->header_buf_size;
++
++                      rx->ctx.skb_head =
++                              gve_rx_copy_data(priv->dev, napi,
++                                               rx->dqo.hdr_bufs.data + offset,
++                                               hdr_len);
+                       if (unlikely(!rx->ctx.skb_head))
+                               goto error;
+                       rx->ctx.skb_tail = rx->ctx.skb_head;
diff --git a/queue-6.12/i2c-davinci-unregister-cpufreq-notifier-on-probe-failure.patch b/queue-6.12/i2c-davinci-unregister-cpufreq-notifier-on-probe-failure.patch
new file mode 100644 (file)
index 0000000..3c34d21
--- /dev/null
@@ -0,0 +1,50 @@
+From stable+bounces-282938-greg=kroah.com@vger.kernel.org Tue Jul 21 21:57:25 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 15:36:03 -0400
+Subject: i2c: davinci: Unregister cpufreq notifier on probe failure
+To: stable@vger.kernel.org
+Cc: Haoxiang Li <haoxiang_li2024@163.com>, Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>, Andi Shyti <andi.shyti@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721193603.410310-1-sashal@kernel.org>
+
+From: Haoxiang Li <haoxiang_li2024@163.com>
+
+[ Upstream commit e43f32816a1b1fe5a86279411626fe3a9be56d45 ]
+
+davinci_i2c_probe() registers a cpufreq transition notifier before adding
+the I2C adapter.  If i2c_add_numbered_adapter() fails, the probe error path
+releases the device resources without unregistering the notifier.
+
+Add a dedicated error path to unregister the cpufreq notifier after
+i2c_add_numbered_adapter() fails.
+
+Fixes: 82c0de11b734 ("i2c: davinci: Add cpufreq support")
+Signed-off-by: Haoxiang Li <haoxiang_li2024@163.com>
+Cc: <stable@vger.kernel.org> # v2.6.36+
+Reviewed-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
+Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
+Link: https://lore.kernel.org/r/20260610030513.2651018-1-haoxiang_li2024@163.com
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/i2c/busses/i2c-davinci.c |    4 +++-
+ 1 file changed, 3 insertions(+), 1 deletion(-)
+
+--- a/drivers/i2c/busses/i2c-davinci.c
++++ b/drivers/i2c/busses/i2c-davinci.c
+@@ -864,13 +864,15 @@ static int davinci_i2c_probe(struct plat
+       adap->nr = pdev->id;
+       r = i2c_add_numbered_adapter(adap);
+       if (r)
+-              goto err_unuse_clocks;
++              goto err_cpufreq;
+       pm_runtime_mark_last_busy(dev->dev);
+       pm_runtime_put_autosuspend(dev->dev);
+       return 0;
++err_cpufreq:
++      i2c_davinci_cpufreq_deregister(dev);
+ err_unuse_clocks:
+       pm_runtime_dont_use_autosuspend(dev->dev);
+       pm_runtime_put_sync(dev->dev);
diff --git a/queue-6.12/i2c-i801-fix-hardware-state-machine-corruption-in-error-path.patch b/queue-6.12/i2c-i801-fix-hardware-state-machine-corruption-in-error-path.patch
new file mode 100644 (file)
index 0000000..027a1b3
--- /dev/null
@@ -0,0 +1,68 @@
+From stable+bounces-287675-greg=kroah.com@vger.kernel.org Wed Jul 22 04:46:00 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 20:51:11 -0400
+Subject: i2c: i801: fix hardware state machine corruption in error path
+To: stable@vger.kernel.org
+Cc: Mingyu Wang <25181214217@stu.xidian.edu.cn>, Andi Shyti <andi.shyti@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722005111.605431-1-sashal@kernel.org>
+
+From: Mingyu Wang <25181214217@stu.xidian.edu.cn>
+
+[ Upstream commit 10dd1a736d557e310a77117832874729a0175d57 ]
+
+A severe livelock and subsequent Hung Task panic were observed in the
+i2c-i801 driver during concurrent Fuzzing. The crash is caused by an
+unconditional hardware register cleanup in the error handling path of
+i801_access().
+
+When i801_check_pre() fails (e.g., returning -EBUSY because the SMBus
+controller is actively used by BIOS/ACPI), the kernel does not actually
+acquire the hardware ownership. However, the code jumps to the 'out'
+label and executes:
+
+    iowrite8(SMBHSTSTS_INUSE_STS | STATUS_FLAGS, SMBHSTSTS(priv));
+
+This forcefully clears the INUSE_STS lock and resets the hardware status
+flags without owning the controller. Doing so interrupts ongoing BIOS/ACPI
+transactions and totally corrupts the SMBus hardware state machine.
+
+Consequently, all subsequent i801_access() calls fail at the pre-check
+stage, triggering an endless stream of "SMBus is busy, can't use it!"
+error logs. Over a slow serial console, this printk flood monopolizes
+the CPU (Console Livelock), starving other processes trying to acquire
+the mmap_lock down_read semaphore, ultimately triggering the hung task
+watchdog.
+
+Fix this by moving the 'out' label below the hardware register cleanup.
+If i801_check_pre() fails, we safely bypass the iowrite8() and only
+release the software locks (pm_runtime and mutex), strictly adhering to
+the rule of not releasing resources that were never acquired.
+
+Fixes: 1f760b87e54c ("i2c: i801: Call i801_check_pre() from i801_access()")
+Signed-off-by: Mingyu Wang <25181214217@stu.xidian.edu.cn>
+Cc: <stable@vger.kernel.org> # v6.3+
+Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
+Link: https://lore.kernel.org/r/20260512093534.348655-1-w15303746062@163.com
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/i2c/busses/i2c-i801.c |    2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/i2c/busses/i2c-i801.c
++++ b/drivers/i2c/busses/i2c-i801.c
+@@ -930,13 +930,13 @@ static s32 i801_access(struct i2c_adapte
+        */
+       if (hwpec)
+               outb_p(inb_p(SMBAUXCTL(priv)) & ~SMBAUXCTL_CRC, SMBAUXCTL(priv));
+-out:
+       /*
+        * Unlock the SMBus device for use by BIOS/ACPI,
+        * and clear status flags if not done already.
+        */
+       outb_p(SMBHSTSTS_INUSE_STS | STATUS_FLAGS, SMBHSTSTS(priv));
++out:
+       pm_runtime_mark_last_busy(&priv->pci_dev->dev);
+       pm_runtime_put_autosuspend(&priv->pci_dev->dev);
+       mutex_unlock(&priv->acpi_lock);
diff --git a/queue-6.12/i2c-imx-fix-locked-bus-on-smbus-block-read-of-0-atomic.patch b/queue-6.12/i2c-imx-fix-locked-bus-on-smbus-block-read-of-0-atomic.patch
new file mode 100644 (file)
index 0000000..12e8c96
--- /dev/null
@@ -0,0 +1,87 @@
+From stable+bounces-289477-greg=kroah.com@vger.kernel.org Mon Jul 27 14:19:47 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 08:19:38 -0400
+Subject: i2c: imx: fix locked bus on SMBus block-read of 0 (atomic)
+To: stable@vger.kernel.org
+Cc: Vincent Jardin <vjardin@free.fr>, Oleksij Rempel <o.rempel@pengutronix.de>, Carlos Song <carlos.song@nxp.com>, Stefan Eichenberger <eichest@gmail.com>, Andi Shyti <andi.shyti@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727121938.3777594-2-sashal@kernel.org>
+
+From: Vincent Jardin <vjardin@free.fr>
+
+[ Upstream commit cb2fc37857693b55909fb77dc2c87cfbc1cdc476 ]
+
+SMBus 3.1 6.5.7 allows a Block Read byte count of 0, but the atomic
+(polling) path rejects it as -EPROTO. Worse, it returns without a
+NACK+STOP: the next receive cycle has already started, so the target
+keeps holding SDA and the bus stays stuck until a power cycle for
+this i2c controller.
+
+Reading I2DR to obtain the count likewise arms the next byte on the
+count > I2C_SMBUS_BLOCK_MAX path, which also returned -EPROTO directly
+and left the bus held.
+
+Handle both: NACK the in-flight dummy byte (TXAK) and extend msgs->len so
+the existing last-byte handling emits STOP; the dummy byte is discarded.
+A count of 0 is a valid empty block read; a count above
+I2C_SMBUS_BLOCK_MAX is still reported as -EPROTO, but only after the bus
+has been released.
+
+The interrupt-driven path has the same flaw from a later commit and is
+fixed separately, as it carries a different Fixes: tag and stable range.
+
+Fixes: 8e8782c71595 ("i2c: imx: add SMBus block read support")
+Signed-off-by: Vincent Jardin <vjardin@free.fr>
+Cc: <stable@vger.kernel.org> # v3.16+
+Acked-by: Oleksij Rempel <o.rempel@pengutronix.de>
+Acked-by: Carlos Song <carlos.song@nxp.com>
+Reviewed-by: Stefan Eichenberger <eichest@gmail.com>
+Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
+Link: https://lore.kernel.org/r/20260713-for-upstream-i2c-lx2160-fix-v1-v3-1-073ac9e103a5@free.fr
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/i2c/busses/i2c-imx.c |   19 ++++++++++++++++---
+ 1 file changed, 16 insertions(+), 3 deletions(-)
+
+--- a/drivers/i2c/busses/i2c-imx.c
++++ b/drivers/i2c/busses/i2c-imx.c
+@@ -1180,6 +1180,7 @@ static int i2c_imx_read(struct imx_i2c_s
+       int i, result;
+       unsigned int temp;
+       int block_data = msgs->flags & I2C_M_RECV_LEN;
++      int block_err = 0;
+       result = i2c_imx_prepare_read(i2c_imx, msgs, atomic, false);
+       if (result)
+@@ -1201,8 +1202,20 @@ static int i2c_imx_read(struct imx_i2c_s
+                */
+               if ((!i) && block_data) {
+                       len = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2DR);
+-                      if ((len == 0) || (len > I2C_SMBUS_BLOCK_MAX))
+-                              return -EPROTO;
++                      if ((len == 0) || (len > I2C_SMBUS_BLOCK_MAX)) {
++                              /*
++                               * SMBus 3.1 6.5.7: support count byte of 0.
++                               * I2C_SMBUS_BLOCK_MAX case should not hold the SDA either.
++                               */
++                              if (len > I2C_SMBUS_BLOCK_MAX)
++                                      block_err = -EPROTO;
++                              temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
++                              temp |= I2CR_TXAK;
++                              imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
++                              msgs->buf[0] = 0;
++                              msgs->len = 2;
++                              continue;
++                      }
+                       dev_dbg(&i2c_imx->adapter.dev,
+                               "<%s> read length: 0x%X\n",
+                               __func__, len);
+@@ -1250,7 +1263,7 @@ static int i2c_imx_read(struct imx_i2c_s
+                       "<%s> read byte: B%d=0x%X\n",
+                       __func__, i, msgs->buf[i]);
+       }
+-      return 0;
++      return block_err;
+ }
+ static int i2c_imx_atomic_read(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs,
diff --git a/queue-6.12/i2c-imx-separate-atomic-dma-and-non-dma-use-case.patch b/queue-6.12/i2c-imx-separate-atomic-dma-and-non-dma-use-case.patch
new file mode 100644 (file)
index 0000000..7dc1000
--- /dev/null
@@ -0,0 +1,194 @@
+From stable+bounces-289476-greg=kroah.com@vger.kernel.org Mon Jul 27 14:26:20 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 08:19:37 -0400
+Subject: i2c: imx: separate atomic, dma and non-dma use case
+To: stable@vger.kernel.org
+Cc: Stefan Eichenberger <stefan.eichenberger@toradex.com>, Frank Li <Frank.Li@nxp.com>, Oleksij Rempel <o.rempel@pengutronix.de>, Andi Shyti <andi.shyti@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727121938.3777594-1-sashal@kernel.org>
+
+From: Stefan Eichenberger <stefan.eichenberger@toradex.com>
+
+[ Upstream commit b460b15b3cc23ef3639cc51043bf8b2a70ca1878 ]
+
+Separate the atomic, dma and non-dma use case as a preparation step for
+moving the non-dma use case to the isr to avoid rescheduling while a
+transfer is in progress.
+
+Signed-off-by: Stefan Eichenberger <stefan.eichenberger@toradex.com>
+Reviewed-by: Frank Li <Frank.Li@nxp.com>
+Acked-by: Oleksij Rempel <o.rempel@pengutronix.de>
+Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
+Stable-dep-of: cb2fc3785769 ("i2c: imx: fix locked bus on SMBus block-read of 0 (atomic)")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/i2c/busses/i2c-imx.c |  107 ++++++++++++++++++++++++++++---------------
+ 1 file changed, 70 insertions(+), 37 deletions(-)
+
+--- a/drivers/i2c/busses/i2c-imx.c
++++ b/drivers/i2c/busses/i2c-imx.c
+@@ -1007,6 +1007,43 @@ static int i2c_imx_dma_write(struct imx_
+       return i2c_imx_acked(i2c_imx);
+ }
++static int i2c_imx_prepare_read(struct imx_i2c_struct *i2c_imx,
++                              struct i2c_msg *msgs, bool atomic,
++                              bool use_dma)
++{
++      int result;
++      unsigned int temp = 0;
++
++      /* write slave address */
++      imx_i2c_write_reg(i2c_8bit_addr_from_msg(msgs), i2c_imx, IMX_I2C_I2DR);
++      result = i2c_imx_trx_complete(i2c_imx, atomic);
++      if (result)
++              return result;
++      result = i2c_imx_acked(i2c_imx);
++      if (result)
++              return result;
++
++      dev_dbg(&i2c_imx->adapter.dev, "<%s> setup bus\n", __func__);
++
++      /* setup bus to read data */
++      temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
++      temp &= ~I2CR_MTX;
++
++      /*
++       * Reset the I2CR_TXAK flag initially for SMBus block read since the
++       * length is unknown
++       */
++      if (msgs->len - 1)
++              temp &= ~I2CR_TXAK;
++      if (use_dma)
++              temp |= I2CR_DMAEN;
++
++      imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
++      imx_i2c_read_reg(i2c_imx, IMX_I2C_I2DR); /* dummy read */
++
++      return 0;
++}
++
+ static int i2c_imx_dma_read(struct imx_i2c_struct *i2c_imx,
+                       struct i2c_msg *msgs, bool is_lastmsg)
+ {
+@@ -1017,6 +1054,11 @@ static int i2c_imx_dma_read(struct imx_i
+       struct imx_i2c_dma *dma = i2c_imx->dma;
+       struct device *dev = &i2c_imx->adapter.dev;
++      result = i2c_imx_prepare_read(i2c_imx, msgs, false, true);
++      if (result)
++              return result;
++
++      dev_dbg(&i2c_imx->adapter.dev, "<%s> read data\n", __func__);
+       dma->chan_using = dma->chan_rx;
+       dma->dma_transfer_dir = DMA_DEV_TO_MEM;
+@@ -1127,50 +1169,24 @@ static int i2c_imx_write(struct imx_i2c_
+       return 0;
+ }
++static int i2c_imx_atomic_write(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs)
++{
++      return i2c_imx_write(i2c_imx, msgs, true);
++}
++
+ static int i2c_imx_read(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs,
+                       bool is_lastmsg, bool atomic)
+ {
+       int i, result;
+       unsigned int temp;
+       int block_data = msgs->flags & I2C_M_RECV_LEN;
+-      int use_dma = i2c_imx->dma && msgs->flags & I2C_M_DMA_SAFE &&
+-              msgs->len >= DMA_THRESHOLD && !block_data;
+-
+-      dev_dbg(&i2c_imx->adapter.dev,
+-              "<%s> write slave address: addr=0x%x\n",
+-              __func__, i2c_8bit_addr_from_msg(msgs));
+-      /* write slave address */
+-      imx_i2c_write_reg(i2c_8bit_addr_from_msg(msgs), i2c_imx, IMX_I2C_I2DR);
+-      result = i2c_imx_trx_complete(i2c_imx, atomic);
+-      if (result)
+-              return result;
+-      result = i2c_imx_acked(i2c_imx);
++      result = i2c_imx_prepare_read(i2c_imx, msgs, atomic, false);
+       if (result)
+               return result;
+-      dev_dbg(&i2c_imx->adapter.dev, "<%s> setup bus\n", __func__);
+-
+-      /* setup bus to read data */
+-      temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
+-      temp &= ~I2CR_MTX;
+-
+-      /*
+-       * Reset the I2CR_TXAK flag initially for SMBus block read since the
+-       * length is unknown
+-       */
+-      if ((msgs->len - 1) || block_data)
+-              temp &= ~I2CR_TXAK;
+-      if (use_dma)
+-              temp |= I2CR_DMAEN;
+-      imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
+-      imx_i2c_read_reg(i2c_imx, IMX_I2C_I2DR); /* dummy read */
+-
+       dev_dbg(&i2c_imx->adapter.dev, "<%s> read data\n", __func__);
+-      if (use_dma)
+-              return i2c_imx_dma_read(i2c_imx, msgs, is_lastmsg);
+-
+       /* read data */
+       for (i = 0; i < msgs->len; i++) {
+               u8 len = 0;
+@@ -1237,6 +1253,12 @@ static int i2c_imx_read(struct imx_i2c_s
+       return 0;
+ }
++static int i2c_imx_atomic_read(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs,
++                             bool is_lastmsg)
++{
++      return i2c_imx_read(i2c_imx, msgs, is_lastmsg, true);
++}
++
+ static int i2c_imx_xfer_common(struct i2c_adapter *adapter,
+                              struct i2c_msg *msgs, int num, bool atomic)
+ {
+@@ -1244,6 +1266,7 @@ static int i2c_imx_xfer_common(struct i2
+       int result;
+       bool is_lastmsg = false;
+       struct imx_i2c_struct *i2c_imx = i2c_get_adapdata(adapter);
++      int use_dma = 0;
+       /* Start I2C transfer */
+       result = i2c_imx_start(i2c_imx, atomic);
+@@ -1296,15 +1319,25 @@ static int i2c_imx_xfer_common(struct i2
+                       (temp & I2SR_SRW ? 1 : 0), (temp & I2SR_IIF ? 1 : 0),
+                       (temp & I2SR_RXAK ? 1 : 0));
+ #endif
++
++              use_dma = i2c_imx->dma && msgs[i].len >= DMA_THRESHOLD &&
++                      msgs[i].flags & I2C_M_DMA_SAFE;
+               if (msgs[i].flags & I2C_M_RD) {
+-                      result = i2c_imx_read(i2c_imx, &msgs[i], is_lastmsg, atomic);
++                      int block_data = msgs->flags & I2C_M_RECV_LEN;
++
++                      if (atomic)
++                              result = i2c_imx_atomic_read(i2c_imx, &msgs[i], is_lastmsg);
++                      else if (use_dma && !block_data)
++                              result = i2c_imx_dma_read(i2c_imx, &msgs[i], is_lastmsg);
++                      else
++                              result = i2c_imx_read(i2c_imx, &msgs[i], is_lastmsg, false);
+               } else {
+-                      if (!atomic &&
+-                          i2c_imx->dma && msgs[i].len >= DMA_THRESHOLD &&
+-                              msgs[i].flags & I2C_M_DMA_SAFE)
++                      if (atomic)
++                              result = i2c_imx_atomic_write(i2c_imx, &msgs[i]);
++                      else if (use_dma)
+                               result = i2c_imx_dma_write(i2c_imx, &msgs[i]);
+                       else
+-                              result = i2c_imx_write(i2c_imx, &msgs[i], atomic);
++                              result = i2c_imx_write(i2c_imx, &msgs[i], false);
+               }
+               if (result)
+                       goto fail0;
diff --git a/queue-6.12/iommufd-avoid-partial-fault-group-delivery-in-iommufd_fault_fops_read.patch b/queue-6.12/iommufd-avoid-partial-fault-group-delivery-in-iommufd_fault_fops_read.patch
new file mode 100644 (file)
index 0000000..e581ef5
--- /dev/null
@@ -0,0 +1,74 @@
+From stable+bounces-279661-greg=kroah.com@vger.kernel.org Tue Jul 21 19:25:28 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 13:13:53 -0400
+Subject: iommufd: Avoid partial fault group delivery in iommufd_fault_fops_read()
+To: stable@vger.kernel.org
+Cc: Nicolin Chen <nicolinc@nvidia.com>, Pranjal Shrivastava <praan@google.com>, Kevin Tian <kevin.tian@intel.com>, Jason Gunthorpe <jgg@nvidia.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721171353.111431-3-sashal@kernel.org>
+
+From: Nicolin Chen <nicolinc@nvidia.com>
+
+[ Upstream commit 091ab6d70dc444f56ed14faedbcacfc979f4c613 ]
+
+The cookie returned by xa_alloc() in iommufd_fault_fops_read() is per fault
+group, but the inner copy_to_user() runs per fault inside the group. If a
+copy fails mid-group, xa_erase clears the cookie and the group is restored
+to the deliver list, yet done is not rolled back. The function returns the
+partial byte count, with the successfully copied faults sitting at offsets
+below done carrying the now-erased cookie. The next read() then re-fetches
+the group, allocates a fresh cookie, and re-delivers every fault including
+the ones already copied; userspace sees duplicates carrying the new cookie,
+and a stale cookie that can never be responded to.
+
+Use a local group_done variable that tracks the per-group progress inside
+the inner loop, and only commit done = group_done after the inner loop has
+finished successfully. On a copy_to_user failure the outer break skips the
+commit, so done remains at its prior start-of-group baseline; the partial
+bytes already written past done are undefined to userspace per the read(2)
+contract, and the next read re-delivers the whole group atomically.
+
+Fixes: 07838f7fd529 ("iommufd: Add iommufd fault object")
+Link: https://patch.msgid.link/r/360cab4d4aeccb0bae275a970e2b3c340a71e0e0.1780343944.git.nicolinc@nvidia.com
+Cc: stable@vger.kernel.org
+Assisted-by: Claude:claude-opus-4-7
+Signed-off-by: Nicolin Chen <nicolinc@nvidia.com>
+Reviewed-by: Pranjal Shrivastava <praan@google.com>
+Reviewed-by: Kevin Tian <kevin.tian@intel.com>
+Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/iommu/iommufd/fault.c |    7 +++++--
+ 1 file changed, 5 insertions(+), 2 deletions(-)
+
+--- a/drivers/iommu/iommufd/fault.c
++++ b/drivers/iommu/iommufd/fault.c
+@@ -270,6 +270,8 @@ static ssize_t iommufd_fault_fops_read(s
+       mutex_lock(&fault->mutex);
+       while ((group = iommufd_fault_deliver_fetch(fault))) {
++              size_t group_done = done;
++
+               if (done >= count ||
+                   group->fault_count * fault_size > count - done) {
+                       iommufd_fault_deliver_restore(fault, group);
+@@ -291,16 +293,17 @@ static ssize_t iommufd_fault_fops_read(s
+                       iommufd_compose_fault_message(&iopf->fault,
+                                                     &data, idev,
+                                                     group->cookie);
+-                      if (copy_to_user(buf + done, &data, fault_size)) {
++                      if (copy_to_user(buf + group_done, &data, fault_size)) {
+                               xa_erase(&fault->response, group->cookie);
+                               iommufd_fault_deliver_restore(fault, group);
+                               rc = -EFAULT;
+                               break;
+                       }
+-                      done += fault_size;
++                      group_done += fault_size;
+               }
+               if (rc)
+                       break;
++              done = group_done;
+       }
+       mutex_unlock(&fault->mutex);
diff --git a/queue-6.12/iommufd-break-the-loop-on-failure-in-iommufd_fault_fops_read.patch b/queue-6.12/iommufd-break-the-loop-on-failure-in-iommufd_fault_fops_read.patch
new file mode 100644 (file)
index 0000000..82a3bc3
--- /dev/null
@@ -0,0 +1,43 @@
+From stable+bounces-279638-greg=kroah.com@vger.kernel.org Tue Jul 21 20:37:42 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 13:13:11 -0400
+Subject: iommufd: Break the loop on failure in iommufd_fault_fops_read()
+To: stable@vger.kernel.org
+Cc: Nicolin Chen <nicolinc@nvidia.com>, Pranjal Shrivastava <praan@google.com>, Kevin Tian <kevin.tian@intel.com>, Jason Gunthorpe <jgg@nvidia.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721171311.108330-1-sashal@kernel.org>
+
+From: Nicolin Chen <nicolinc@nvidia.com>
+
+[ Upstream commit 172fc8b19825a0f5884c38f2289188284e2d45ee ]
+
+On a copy_to_user() failure inside the inner list_for_each_entry, only the
+inner loop breaks; the outer while re-fetches the just-restored fault group
+and retries the failing copy_to_user() forever, spinning the reader at 100%
+CPU with fault->mutex held.
+
+Check rc after the inner loop and break the outer while as well.
+
+Fixes: 07838f7fd529 ("iommufd: Add iommufd fault object")
+Link: https://patch.msgid.link/r/336a9b6e44fe66a24199d3be777c405c85c98622.1780343944.git.nicolinc@nvidia.com
+Cc: stable@vger.kernel.org
+Signed-off-by: Nicolin Chen <nicolinc@nvidia.com>
+Reviewed-by: Pranjal Shrivastava <praan@google.com>
+Reviewed-by: Kevin Tian <kevin.tian@intel.com>
+Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/iommu/iommufd/fault.c |    2 ++
+ 1 file changed, 2 insertions(+)
+
+--- a/drivers/iommu/iommufd/fault.c
++++ b/drivers/iommu/iommufd/fault.c
+@@ -299,6 +299,8 @@ static ssize_t iommufd_fault_fops_read(s
+                       }
+                       done += fault_size;
+               }
++              if (rc)
++                      break;
+       }
+       mutex_unlock(&fault->mutex);
diff --git a/queue-6.12/iommufd-reject-invalid-read-count-in-iommufd_fault_fops_read.patch b/queue-6.12/iommufd-reject-invalid-read-count-in-iommufd_fault_fops_read.patch
new file mode 100644 (file)
index 0000000..7b614a9
--- /dev/null
@@ -0,0 +1,44 @@
+From stable+bounces-279627-greg=kroah.com@vger.kernel.org Tue Jul 21 19:24:01 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 13:12:50 -0400
+Subject: iommufd: Reject invalid read count in iommufd_fault_fops_read()
+To: stable@vger.kernel.org
+Cc: Nicolin Chen <nicolinc@nvidia.com>, Pranjal Shrivastava <praan@google.com>, Kevin Tian <kevin.tian@intel.com>, Jason Gunthorpe <jgg@nvidia.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721171250.107567-1-sashal@kernel.org>
+
+From: Nicolin Chen <nicolinc@nvidia.com>
+
+[ Upstream commit 47916a54eeb2a9e654512ee609f71bd5b29db702 ]
+
+The read count must be large enough to hold one fault or a group's faults.
+
+iommufd_fault_fops_read() does not validate the count, but returns 0 as if
+the read had succeeded while leaving the pending fault in the queue.
+
+Return -EINVAL in the undersize cases.
+
+Fixes: 07838f7fd529 ("iommufd: Add iommufd fault object")
+Link: https://patch.msgid.link/r/85c118a606fbedc5c132a1f5ec223a5ba23b92d2.1780343944.git.nicolinc@nvidia.com
+Cc: stable@vger.kernel.org
+Signed-off-by: Nicolin Chen <nicolinc@nvidia.com>
+Reviewed-by: Pranjal Shrivastava <praan@google.com>
+Reviewed-by: Kevin Tian <kevin.tian@intel.com>
+Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/iommu/iommufd/fault.c |    3 +++
+ 1 file changed, 3 insertions(+)
+
+--- a/drivers/iommu/iommufd/fault.c
++++ b/drivers/iommu/iommufd/fault.c
+@@ -273,6 +273,9 @@ static ssize_t iommufd_fault_fops_read(s
+               if (done >= count ||
+                   group->fault_count * fault_size > count - done) {
+                       iommufd_fault_deliver_restore(fault, group);
++                      /* Read count doesn't fit the first fault group */
++                      if (done == 0)
++                              rc = -EINVAL;
+                       break;
+               }
diff --git a/queue-6.12/ipmi-fix-refcount-leak-in-i_ipmi_request.patch b/queue-6.12/ipmi-fix-refcount-leak-in-i_ipmi_request.patch
new file mode 100644 (file)
index 0000000..c4fe41d
--- /dev/null
@@ -0,0 +1,60 @@
+From stable+bounces-290096-greg=kroah.com@vger.kernel.org Tue Jul 28 19:41:00 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 13:07:22 -0400
+Subject: ipmi: fix refcount leak in i_ipmi_request()
+To: stable@vger.kernel.org
+Cc: Wentao Liang <vulab@iscas.ac.cn>, Corey Minyard <corey@minyard.net>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260728170722.184404-1-sashal@kernel.org>
+
+From: Wentao Liang <vulab@iscas.ac.cn>
+
+[ Upstream commit a3f3859cecacb64f18fd446271ece9a3b3f2d4de ]
+
+When a caller provides a `supplied_recv` message to i_ipmi_request(),
+the function increments the user's `nr_msgs` reference count. If an
+error occurs later, the out_err cleanup path only frees the recv_msg
+if the function allocated it itself (i.e., !supplied_recv). In the
+supplied_recv case the cleanup is skipped, leaving the reference count
+elevated. The caller ipmi_request_supply_msgs() does not release the
+supplied_recv on error, so the reference is permanently leaked.
+
+Fix this by explicitly reverting the reference count operations when a
+supplied recv_msg with a valid user pointer is present in the error
+path: decrement nr_msgs and drop the user's kref.
+
+Cc: stable@vger.kernel.org
+Fixes: b52da4054ee0 ("ipmi: Rework user message limit handling")
+Signed-off-by: Wentao Liang <vulab@iscas.ac.cn>
+Message-ID: <20260603120634.3758747-1-vulab@iscas.ac.cn>
+Signed-off-by: Corey Minyard <corey@minyard.net>
+[ changed `free_ipmi_user` to `free_user` in the two added `kref_put()` calls ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/char/ipmi/ipmi_msghandler.c |    8 ++++++++
+ 1 file changed, 8 insertions(+)
+
+--- a/drivers/char/ipmi/ipmi_msghandler.c
++++ b/drivers/char/ipmi/ipmi_msghandler.c
+@@ -2331,6 +2331,10 @@ static int i_ipmi_request(struct ipmi_us
+               if (smi_msg == NULL) {
+                       if (!supplied_recv)
+                               ipmi_free_recv_msg(recv_msg);
++                      else if (recv_msg->user) {
++                              atomic_dec(&recv_msg->user->nr_msgs);
++                              kref_put(&recv_msg->user->refcount, free_user);
++                      }
+                       return -ENOMEM;
+               }
+       }
+@@ -2373,6 +2377,10 @@ out_err:
+                       ipmi_free_smi_msg(smi_msg);
+               if (!supplied_recv)
+                       ipmi_free_recv_msg(recv_msg);
++              else if (recv_msg->user) {
++                      atomic_dec(&recv_msg->user->nr_msgs);
++                      kref_put(&recv_msg->user->refcount, free_user);
++              }
+       } else {
+               dev_dbg(intf->si_dev, "Send: %*ph\n",
+                       smi_msg->data_size, smi_msg->data);
diff --git a/queue-6.12/landlock-fix-landlock_scope_signal-bypass-on-the-sigio-path.patch b/queue-6.12/landlock-fix-landlock_scope_signal-bypass-on-the-sigio-path.patch
new file mode 100644 (file)
index 0000000..ec23511
--- /dev/null
@@ -0,0 +1,164 @@
+From stable+bounces-288959-greg=kroah.com@vger.kernel.org Fri Jul 24 19:00:37 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 12:44:56 -0400
+Subject: landlock: Fix LANDLOCK_SCOPE_SIGNAL bypass on the SIGIO path
+To: stable@vger.kernel.org
+Cc: "Bryam Vargas" <hexlabsecurity@proton.me>, "Günther Noack" <gnoack3000@gmail.com>, "Mickaël Salaün" <mic@digikod.net>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260724164456.469658-2-sashal@kernel.org>
+
+From: Bryam Vargas <hexlabsecurity@proton.me>
+
+[ Upstream commit 4b80320ca7ed03d6e683f95b6066565dc97b9f92 ]
+
+LANDLOCK_SCOPE_SIGNAL must prevent a sandboxed process from signaling
+processes outside its Landlock domain.  It can be bypassed through the
+asynchronous SIGIO delivery path.
+
+A sandboxed process that owns any file or socket can arm it with
+fcntl(fd, F_SETOWN, -pgid), fcntl(fd, F_SETSIG, SIGKILL) and O_ASYNC, so
+that an I/O event makes the kernel deliver the chosen signal to the
+whole process group.  As the head of its process group's task list (the
+default position right after fork()) that group can also hold the
+non-sandboxed process that launched it, e.g. a supervisor or a security
+monitor.  The sandbox can thus kill or signal the processes
+LANDLOCK_SCOPE_SIGNAL is meant to protect from it.
+
+The scope is enforced in hook_file_send_sigiotask() against the Landlock
+domain recorded at F_SETOWN time, not the live domain of the sender.
+control_current_fowner() decides whether to record that domain and skips
+recording it when the fowner target is in the caller's thread group,
+which is safe only for a single-task target (PIDTYPE_PID, PIDTYPE_TGID).
+For a process group (PIDTYPE_PGID) pid_task() returns only one member;
+recording is skipped whenever that member shares the caller's thread
+group, and hook_file_send_sigiotask() then lets the signal fan out to
+the whole group unchecked.
+
+Record the domain for every non single-process target so the scope is
+enforced against each group member at delivery time.
+
+That recording is necessary but not sufficient on its own: the kernel
+signals a process group through its members' thread-group leaders, and
+the leader of the registrant's own process can carry a different
+Landlock domain than the sibling thread that armed the owner.
+domain_is_scoped() would then deny that leader, even though commit
+18eb75f3af40 ("landlock: Always allow signals between threads of the
+same process") requires same-process delivery to be allowed.
+hook_task_kill() avoids this by evaluating same_thread_group() live, per
+recipient; the SIGIO path instead delegates the whole decision to a
+single registration-time check, which a process-group fan-out cannot
+honor.
+
+So also record the registrant's thread group next to its domain and
+exempt it at delivery: hook_file_send_sigiotask() allows the signal
+whenever the recipient belongs to the registrant's own process,
+restoring the same-process guarantee while keeping out-of-domain group
+members blocked.  The direct kill() path (hook_task_kill) already
+evaluates the live domain and is unaffected.
+
+Fixes: 18eb75f3af40 ("landlock: Always allow signals between threads of the same process")
+Cc: stable@vger.kernel.org
+Signed-off-by: Bryam Vargas <hexlabsecurity@proton.me>
+Reviewed-by: Günther Noack <gnoack3000@gmail.com>
+Link: https://patch.msgid.link/56bffc24f3d0d08b45a686a48e99766b0a0821fa.1780614610.git.hexlabsecurity@proton.me
+[mic: Check pid_type earlier and improve comment, fix commit message,
+fix comment formatting]
+Signed-off-by: Mickaël Salaün <mic@digikod.net>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ security/landlock/fs.c   |   14 ++++++++++++++
+ security/landlock/fs.h   |   10 ++++++++++
+ security/landlock/task.c |   11 +++++++++++
+ 3 files changed, 35 insertions(+)
+
+--- a/security/landlock/fs.c
++++ b/security/landlock/fs.c
+@@ -1666,6 +1666,14 @@ static bool control_current_fowner(struc
+       lockdep_assert_held(&fown->lock);
+       /*
++       * A process-group or session owner (PIDTYPE_PGID/PIDTYPE_SID) fans the
++       * signal out to every member at delivery time, so record the domain and
++       * let hook_file_send_sigiotask() check the live scope per recipient.
++       */
++      if (fown->pid_type != PIDTYPE_PID && fown->pid_type != PIDTYPE_TGID)
++              return true;
++
++      /*
+        * Some callers (e.g. fcntl_dirnotify) may not be in an RCU read-side
+        * critical section.
+        */
+@@ -1681,6 +1689,7 @@ static void hook_file_set_fowner(struct
+ {
+       struct landlock_ruleset *prev_dom;
+       struct landlock_cred_security fown_subject = {};
++      struct pid *prev_tg, *fown_tg = NULL;
+       size_t fown_layer = 0;
+       /*
+@@ -1706,21 +1715,26 @@ static void hook_file_set_fowner(struct
+               if (new_subject) {
+                       landlock_get_ruleset(new_subject->domain);
+                       fown_subject = *new_subject;
++                      fown_tg = get_pid(task_tgid(current));
+               }
+       }
+       prev_dom = landlock_file(file)->fown_subject.domain;
++      prev_tg = landlock_file(file)->fown_tg;
+       landlock_file(file)->fown_subject = fown_subject;
++      landlock_file(file)->fown_tg = fown_tg;
+ #ifdef CONFIG_AUDIT
+       landlock_file(file)->fown_layer = fown_layer;
+ #endif /* CONFIG_AUDIT*/
+       /* May be called in an RCU read-side critical section. */
+       landlock_put_ruleset_deferred(prev_dom);
++      put_pid(prev_tg);
+ }
+ static void hook_file_free_security(struct file *file)
+ {
++      put_pid(landlock_file(file)->fown_tg);
+       landlock_put_ruleset_deferred(landlock_file(file)->fown_subject.domain);
+ }
+--- a/security/landlock/fs.h
++++ b/security/landlock/fs.h
+@@ -70,6 +70,16 @@ struct landlock_file_security {
+        * euid.
+        */
+       struct landlock_cred_security fown_subject;
++      /**
++       * @fown_tg: Thread group of the task that set the file owner, pinned
++       * while @fown_subject holds a domain.  It lets
++       * hook_file_send_sigiotask() always allow a SIGIO delivered to the
++       * owner's own process -- e.g. the thread-group leader reached through a
++       * process-group owner -- matching the same-process exemption of
++       * hook_task_kill().  NULL when no domain is recorded.  Protected by
++       * file->f_owner->lock, like @fown_subject.
++       */
++      struct pid *fown_tg;
+ };
+ /**
+--- a/security/landlock/task.c
++++ b/security/landlock/task.c
+@@ -314,6 +314,17 @@ static int hook_file_send_sigiotask(stru
+       if (!subject->domain)
+               return 0;
++      /*
++       * Always allow delivery to the file owner's own process, including a
++       * thread-group leader reached through a process-group owner.  This
++       * mirrors hook_task_kill()'s same-process exemption and preserves the
++       * guarantee of commit 18eb75f3af40 ("landlock: Always allow signals
++       * between threads of the same process"), which the registration-time
++       * check cannot honor for a process-group target.
++       */
++      if (task_tgid(tsk) == landlock_file(fown->file)->fown_tg)
++              return 0;
++
+       scoped_guard(rcu)
+       {
+               is_scoped = domain_is_scoped(subject->domain,
diff --git a/queue-6.12/landlock-prepare-to-use-credential-instead-of-domain-for-fowner.patch b/queue-6.12/landlock-prepare-to-use-credential-instead-of-domain-for-fowner.patch
new file mode 100644 (file)
index 0000000..850ca6f
--- /dev/null
@@ -0,0 +1,188 @@
+From stable+bounces-288958-greg=kroah.com@vger.kernel.org Fri Jul 24 19:00:37 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 12:44:55 -0400
+Subject: landlock: Prepare to use credential instead of domain for fowner
+To: stable@vger.kernel.org
+Cc: "Mickaël Salaün" <mic@digikod.net>, "Günther Noack" <gnoack@google.com>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260724164456.469658-1-sashal@kernel.org>
+
+From: Mickaël Salaün <mic@digikod.net>
+
+[ Upstream commit 79625f1b3a3df63d3289a0781fdf121bc42966f7 ]
+
+This cosmetic change is needed for audit support, specifically to be
+able to filter according to cross-execution boundaries.
+
+struct landlock_file_security's size stay the same for now but it will
+increase with struct landlock_cred_security's size.
+
+Only save Landlock domain in hook_file_set_fowner() if the current
+domain has LANDLOCK_SCOPE_SIGNAL, which was previously done for each
+hook_file_send_sigiotask() calls.  This should improve a bit
+performance.
+
+Replace hardcoded LANDLOCK_SCOPE_SIGNAL with the signal_scope.scope
+variable.
+
+Use scoped guards for RCU read-side critical sections.
+
+Cc: Günther Noack <gnoack@google.com>
+Link: https://lore.kernel.org/r/20250320190717.2287696-8-mic@digikod.net
+Signed-off-by: Mickaël Salaün <mic@digikod.net>
+Stable-dep-of: 4b80320ca7ed ("landlock: Fix LANDLOCK_SCOPE_SIGNAL bypass on the SIGIO path")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ security/landlock/fs.c   |   37 +++++++++++++++++++++++++++++++------
+ security/landlock/fs.h   |   23 +++++++++++++++++------
+ security/landlock/task.c |   25 ++++++++++++++++---------
+ 3 files changed, 64 insertions(+), 21 deletions(-)
+
+--- a/security/landlock/fs.c
++++ b/security/landlock/fs.c
+@@ -1680,15 +1680,40 @@ static bool control_current_fowner(struc
+ static void hook_file_set_fowner(struct file *file)
+ {
+       struct landlock_ruleset *prev_dom;
+-      struct landlock_ruleset *new_dom = NULL;
++      struct landlock_cred_security fown_subject = {};
++      size_t fown_layer = 0;
++
++      /*
++       * Keep this local to match the layout expected by follow-up changes
++       * shared with audit-enabled kernels.
++       */
++      (void)fown_layer;
+       if (control_current_fowner(file_f_owner(file))) {
+-              new_dom = landlock_get_current_domain();
+-              landlock_get_ruleset(new_dom);
++              static const struct access_masks signal_scope = {
++                      .scope = LANDLOCK_SCOPE_SIGNAL,
++              };
++              struct landlock_ruleset *const current_domain =
++                      landlock_get_current_domain();
++              const struct landlock_cred_security applicable_subject = {
++                      .domain = current_domain,
++              };
++              const struct landlock_cred_security *new_subject = NULL;
++
++              if (landlock_get_applicable_domain(current_domain, signal_scope))
++                      new_subject = &applicable_subject;
++
++              if (new_subject) {
++                      landlock_get_ruleset(new_subject->domain);
++                      fown_subject = *new_subject;
++              }
+       }
+-      prev_dom = landlock_file(file)->fown_domain;
+-      landlock_file(file)->fown_domain = new_dom;
++      prev_dom = landlock_file(file)->fown_subject.domain;
++      landlock_file(file)->fown_subject = fown_subject;
++#ifdef CONFIG_AUDIT
++      landlock_file(file)->fown_layer = fown_layer;
++#endif /* CONFIG_AUDIT*/
+       /* May be called in an RCU read-side critical section. */
+       landlock_put_ruleset_deferred(prev_dom);
+@@ -1696,7 +1721,7 @@ static void hook_file_set_fowner(struct
+ static void hook_file_free_security(struct file *file)
+ {
+-      landlock_put_ruleset_deferred(landlock_file(file)->fown_domain);
++      landlock_put_ruleset_deferred(landlock_file(file)->fown_subject.domain);
+ }
+ static struct security_hook_list landlock_hooks[] __ro_after_init = {
+--- a/security/landlock/fs.h
++++ b/security/landlock/fs.h
+@@ -1,9 +1,10 @@
+ /* SPDX-License-Identifier: GPL-2.0-only */
+ /*
+- * Landlock LSM - Filesystem management and hooks
++ * Landlock - Filesystem management and hooks
+  *
+  * Copyright Â© 2017-2020 Mickaël Salaün <mic@digikod.net>
+  * Copyright Â© 2018-2020 ANSSI
++ * Copyright Â© 2024-2025 Microsoft Corporation
+  */
+ #ifndef _SECURITY_LANDLOCK_FS_H
+@@ -13,6 +14,7 @@
+ #include <linux/init.h>
+ #include <linux/rcupdate.h>
++#include "cred.h"
+ #include "ruleset.h"
+ #include "setup.h"
+@@ -52,13 +54,22 @@ struct landlock_file_security {
+        * needed to authorize later operations on the open file.
+        */
+       access_mask_t allowed_access;
++
++#ifdef CONFIG_AUDIT
++      /**
++       * @fown_layer: Compatibility storage for follow-up fowner changes.
++       */
++      u8 fown_layer;
++#endif /* CONFIG_AUDIT */
++
+       /**
+-       * @fown_domain: Domain of the task that set the PID that may receive a
+-       * signal e.g., SIGURG when writing MSG_OOB to the related socket.
+-       * This pointer is protected by the related file->f_owner->lock, as for
+-       * fown_struct's members: pid, uid, and euid.
++       * @fown_subject: Landlock credential of the task that set the PID that
++       * may receive a signal e.g., SIGURG when writing MSG_OOB to the
++       * related socket.  This pointer is protected by the related
++       * file->f_owner->lock, as for fown_struct's members: pid, uid, and
++       * euid.
+        */
+-      struct landlock_ruleset *fown_domain;
++      struct landlock_cred_security fown_subject;
+ };
+ /**
+--- a/security/landlock/task.c
++++ b/security/landlock/task.c
+@@ -297,22 +297,29 @@ static int hook_task_kill(struct task_st
+ static int hook_file_send_sigiotask(struct task_struct *tsk,
+                                   struct fown_struct *fown, int signum)
+ {
+-      const struct landlock_ruleset *dom;
++      const struct landlock_cred_security *subject;
+       bool is_scoped = false;
+       /* Lock already held by send_sigio() and send_sigurg(). */
+       lockdep_assert_held(&fown->lock);
+-      dom = landlock_get_applicable_domain(
+-              landlock_file(fown->file)->fown_domain, signal_scope);
++      subject = &landlock_file(fown->file)->fown_subject;
+-      /* Quick return for unowned socket. */
+-      if (!dom)
++      /*
++       * Quick return for unowned socket.
++       *
++       * subject->domain has already been filtered when saved by
++       * hook_file_set_fowner(), so there is no need to call
++       * landlock_get_applicable_subject() here.
++       */
++      if (!subject->domain)
+               return 0;
+-      rcu_read_lock();
+-      is_scoped = domain_is_scoped(dom, landlock_get_task_domain(tsk),
+-                                   LANDLOCK_SCOPE_SIGNAL);
+-      rcu_read_unlock();
++      scoped_guard(rcu)
++      {
++              is_scoped = domain_is_scoped(subject->domain,
++                                           landlock_get_task_domain(tsk),
++                                           signal_scope.scope);
++      }
+       if (is_scoped)
+               return -EPERM;
diff --git a/queue-6.12/locking-rt-fix-the-incorrect-rcu-protection-in-rt_spin_unlock.patch b/queue-6.12/locking-rt-fix-the-incorrect-rcu-protection-in-rt_spin_unlock.patch
new file mode 100644 (file)
index 0000000..151429c
--- /dev/null
@@ -0,0 +1,110 @@
+From stable+bounces-289807-greg=kroah.com@vger.kernel.org Tue Jul 28 04:46:24 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 22:46:07 -0400
+Subject: locking/rt: Fix the incorrect RCU protection in rt_spin_unlock()
+To: stable@vger.kernel.org
+Cc: Thomas Gleixner <tglx@kernel.org>, syzbot+000c800a02097aaa10ed@syzkaller.appspotmail.com, Jann Horn <jannh@google.com>, Sebastian Andrzej Siewior <bigeasy@linutronix.de>, Al Viro <viro@zeniv.linux.org.uk>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260728024607.3052461-1-sashal@kernel.org>
+
+From: Thomas Gleixner <tglx@kernel.org>
+
+[ Upstream commit 89038cc87d80c77e7aa6f42a64b2573b74af339f ]
+
+rt_spin_unlock() releases the RCU protection before unlocking the
+lock. That opens the door for the following UAF scenario:
+
+ T1                                    T2
+ spin_lock(&p->lock);          rcu_read_lock();
+ invalidate(p);                        p = rcu_dereference(ptr);
+ rcu_assign_pointer(ptr, NULL);        if (!p) return;
+ spin_unlock(&p->lock);                spin_lock(&p->lock)
+                                  lock(&lock->lock);
+                                  rcu_read_lock();
+ kfree_rcu(p);                 rcu_read_unlock();
+                               ....
+                               spin_unlock(&p->lock)
+                                 rcu_read_unlock(); // Ends grace period
+ rcu_do_batch()
+   kfree(p);
+                           UAF ->        rt_mutex_cmpxchg_release(&lock->lock...)
+
+Regular spinlocks keep preemption disabled accross the unlock operation,
+which provides full RCU protection, but the RT substitution fails to
+resemble that. Same applies for the rwlock substitution.
+
+Move the rcu_read_unlock() invocation past the unlock operations to match
+the non-RT semantics. This makes it asymmetric vs. rt_xxx_lock(), but
+that's harmless as the caller needs to hold RCU read lock across the lock
+operation. The migrate_enable() call stays before the unlock operation
+because there is no per CPU operation in the unlock path which would
+require migration to be kept disabled.
+
+Fixes: 0f383b6dc96e ("locking/spinlock: Provide RT variant")
+Reported-by: syzbot+000c800a02097aaa10ed@syzkaller.appspotmail.com
+Decoded-by: Jann Horn <jannh@google.com>
+Signed-off-by: Thomas Gleixner <tglx@kernel.org>
+Reviewed-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
+Acked-by: Al Viro <viro@zeniv.linux.org.uk>
+Cc: stable@vger.kernel.org
+Link: https://patch.msgid.link/87jyrud75z.ffs@fw13
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ kernel/locking/spinlock_rt.c |   27 ++++++++++++++++++++++++---
+ 1 file changed, 24 insertions(+), 3 deletions(-)
+
+--- a/kernel/locking/spinlock_rt.c
++++ b/kernel/locking/spinlock_rt.c
+@@ -79,10 +79,27 @@ void __sched rt_spin_unlock(spinlock_t *
+ {
+       spin_release(&lock->dep_map, _RET_IP_);
+       migrate_enable();
+-      rcu_read_unlock();
+       if (unlikely(!rt_mutex_cmpxchg_release(&lock->lock, current, NULL)))
+               rt_mutex_slowunlock(&lock->lock);
++
++      /*
++       * This must be last to prevent the following UAF:
++       *
++       * T1                                   T2
++       * spin_lock(&p->lock);                 rcu_read_lock();
++       * invalidate(p);                       p = rcu_dereference(ptr);
++       * rcu_assign_pointer(ptr, NULL);       if (!p) return;
++       * spin_unlock(&p->lock);               spin_lock(&p->lock);
++       * kfree_rcu(p);                        rcu_read_unlock();
++       *                                      ....
++       *                                      spin_unlock(&p->lock)
++       *                                        rcu_read_unlock(); // Ends grace period
++       * rcu_do_batch()
++       *   kfree(p);
++       *                          UAF ->        rt_mutex_cmpxchg_release(&p->lock.lock...)
++       */
++      rcu_read_unlock();
+ }
+ EXPORT_SYMBOL(rt_spin_unlock);
+@@ -262,17 +279,21 @@ void __sched rt_read_unlock(rwlock_t *rw
+ {
+       rwlock_release(&rwlock->dep_map, _RET_IP_);
+       migrate_enable();
+-      rcu_read_unlock();
+       rwbase_read_unlock(&rwlock->rwbase, TASK_RTLOCK_WAIT);
++
++      /* This must be last. See comment in rt_spin_unlock() */
++      rcu_read_unlock();
+ }
+ EXPORT_SYMBOL(rt_read_unlock);
+ void __sched rt_write_unlock(rwlock_t *rwlock)
+ {
+       rwlock_release(&rwlock->dep_map, _RET_IP_);
+-      rcu_read_unlock();
+       migrate_enable();
+       rwbase_write_unlock(&rwlock->rwbase);
++
++      /* This must be last. See comment in rt_spin_unlock() */
++      rcu_read_unlock();
+ }
+ EXPORT_SYMBOL(rt_write_unlock);
diff --git a/queue-6.12/mm-hugetlb-fix-hugetlb-cgroup-rsvd-charge-uncharge-mismatch.patch b/queue-6.12/mm-hugetlb-fix-hugetlb-cgroup-rsvd-charge-uncharge-mismatch.patch
new file mode 100644 (file)
index 0000000..42a25ae
--- /dev/null
@@ -0,0 +1,88 @@
+From stable+bounces-288841-greg=kroah.com@vger.kernel.org Fri Jul 24 13:22:38 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 07:00:52 -0400
+Subject: mm/hugetlb: fix hugetlb cgroup rsvd charge/uncharge mismatch
+To: stable@vger.kernel.org
+Cc: Deepanshu Kartikey <kartikey406@gmail.com>, syzbot+226c1f947186f8fef796@syzkaller.appspotmail.com, Muchun Song <muchun.song@linux.dev>, David Hildenbrand <david@kernel.org>, Oscar Salvador <osalvador@suse.de>, Mina Almasry <almasrymina@google.com>, Andrew Morton <akpm@linux-foundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260724110052.3984407-1-sashal@kernel.org>
+
+From: Deepanshu Kartikey <kartikey406@gmail.com>
+
+[ Upstream commit 15807d0ddde37407af72859426b654f3d1972b00 ]
+
+In alloc_hugetlb_folio(), a single h_cg pointer is used for both the rsvd
+and non-rsvd hugetlb cgroup charges.  When map_chg is set,
+hugetlb_cgroup_charge_cgroup_rsvd() stores the charged cgroup in h_cg, but
+the immediately following hugetlb_cgroup_charge_cgroup() overwrites h_cg
+with the non-rsvd cgroup pointer.
+
+As a result, hugetlb_cgroup_commit_charge_rsvd() stores the wrong
+(non-rsvd) cgroup pointer into the folio's rsvd slot.
+
+When the folio is later freed, free_huge_folio() unconditionally calls
+both hugetlb_cgroup_uncharge_folio() and
+hugetlb_cgroup_uncharge_folio_rsvd().  The rsvd uncharge reads back the
+wrong cgroup from the folio and decrements a counter that was never
+charged for that cgroup, causing a page_counter underflow:
+
+  page_counter underflow: -512 nr_pages=512
+  WARNING: mm/page_counter.c:61 at page_counter_cancel
+
+Fix this by introducing a separate h_cg_rsvd pointer exclusively for the
+rsvd charge path, keeping the rsvd and non-rsvd charges fully independent
+through their charge, commit, and error uncharge paths.
+
+Link: https://lore.kernel.org/20260328065534.346053-1-kartikey406@gmail.com
+Fixes: 08cf9faf7558 ("hugetlb_cgroup: support noreserve mappings")
+Reported-by: syzbot+226c1f947186f8fef796@syzkaller.appspotmail.com
+Closes: https://syzkaller.appspot.com/bug?extid=226c1f947186f8fef796
+Signed-off-by: Deepanshu Kartikey <kartikey406@gmail.com>
+Reviewed-by: Muchun Song <muchun.song@linux.dev>
+Cc: David Hildenbrand <david@kernel.org>
+Cc: Oscar Salvador <osalvador@suse.de>
+Cc: Mina Almasry <almasrymina@google.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/hugetlb.c |    7 ++++---
+ 1 file changed, 4 insertions(+), 3 deletions(-)
+
+--- a/mm/hugetlb.c
++++ b/mm/hugetlb.c
+@@ -2976,6 +2976,7 @@ struct folio *alloc_hugetlb_folio(struct
+       long gbl_chg;
+       int memcg_charge_ret, ret, idx;
+       struct hugetlb_cgroup *h_cg = NULL;
++      struct hugetlb_cgroup *h_cg_rsvd = NULL;
+       struct mem_cgroup *memcg;
+       bool deferred_reserve;
+       gfp_t gfp = htlb_alloc_mask(h) | __GFP_RETRY_MAYFAIL;
+@@ -3019,7 +3020,7 @@ struct folio *alloc_hugetlb_folio(struct
+       deferred_reserve = map_chg || avoid_reserve;
+       if (deferred_reserve) {
+               ret = hugetlb_cgroup_charge_cgroup_rsvd(
+-                      idx, pages_per_huge_page(h), &h_cg);
++                      idx, pages_per_huge_page(h), &h_cg_rsvd);
+               if (ret)
+                       goto out_subpool_put;
+       }
+@@ -3056,7 +3057,7 @@ struct folio *alloc_hugetlb_folio(struct
+        */
+       if (deferred_reserve) {
+               hugetlb_cgroup_commit_charge_rsvd(idx, pages_per_huge_page(h),
+-                                                h_cg, folio);
++                                                h_cg_rsvd, folio);
+       }
+       spin_unlock_irq(&hugetlb_lock);
+@@ -3097,7 +3098,7 @@ out_uncharge_cgroup:
+ out_uncharge_cgroup_reservation:
+       if (deferred_reserve)
+               hugetlb_cgroup_uncharge_cgroup_rsvd(idx, pages_per_huge_page(h),
+-                                                  h_cg);
++                                                  h_cg_rsvd);
+ out_subpool_put:
+       if (map_chg || avoid_reserve)
+               hugepage_subpool_put_pages(spool, 1);
diff --git a/queue-6.12/mm-mm_init-fix-pageblock-migratetype-for-zone_device-compound-pages.patch b/queue-6.12/mm-mm_init-fix-pageblock-migratetype-for-zone_device-compound-pages.patch
new file mode 100644 (file)
index 0000000..ec3c5a2
--- /dev/null
@@ -0,0 +1,115 @@
+From stable+bounces-289041-greg=kroah.com@vger.kernel.org Sat Jul 25 03:37:49 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 21:37:41 -0400
+Subject: mm/mm_init: fix pageblock migratetype for ZONE_DEVICE compound pages
+To: stable@vger.kernel.org
+Cc: Muchun Song <songmuchun@bytedance.com>, "Mike Rapoport (Microsoft)" <rppt@kernel.org>, Oscar Salvador <osalvador@suse.de>, "David Hildenbrand (Arm)" <david@kernel.org>, "Liam R. Howlett" <liam@infradead.org>, "Aneesh Kumar K.V" <aneesh.kumar@linux.ibm.com>, Joao Martins <joao.m.martins@oracle.com>, Lorenzo Stoakes <ljs@kernel.org>, Madhavan Srinivasan <maddy@linux.ibm.com>, Michael Ellerman <mpe@ellerman.id.au>, Michal Hocko <mhocko@suse.com>, Nicholas Piggin <npiggin@gmail.com>, Suren Baghdasaryan <surenb@google.com>, Vlastimil Babka <vbabka@kernel.org>, Andrew Morton <akpm@linux-foundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260725013741.1892750-1-sashal@kernel.org>
+
+From: Muchun Song <songmuchun@bytedance.com>
+
+[ Upstream commit 94405c6136839f7c462249c8b4b957bcb9527a9d ]
+
+The memmap_init_zone_device() function only initializes the migratetype of
+the first pageblock of a compound page.  If the compound page size exceeds
+pageblock_nr_pages (e.g., 1GB hugepages with 2MB pageblocks), subsequent
+pageblocks in the compound page remain uninitialized.
+
+Move the migratetype initialization out of __init_zone_device_page() and
+into a separate pageblock_migratetype_init_range() function.  This
+iterates over the entire PFN range of the memory, ensuring that all
+pageblocks are correctly initialized.
+
+Also remove the stale confusing comment about MEMINIT_HOTPLUG above the
+migratetype setting since it is an obsolete relic from commit 966cf44f637e
+("mm: defer ZONE_DEVICE page initialization to the point where we init
+pgmap") and no longer makes sense here.
+
+Link: https://lore.kernel.org/20260428081855.1249045-6-songmuchun@bytedance.com
+Fixes: c4386bd8ee3a ("mm/memremap: add ZONE_DEVICE support for compound pages")
+Signed-off-by: Muchun Song <songmuchun@bytedance.com>
+Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
+Reviewed-by: Oscar Salvador <osalvador@suse.de>
+Acked-by: David Hildenbrand (Arm) <david@kernel.org>
+Acked-by: Liam R. Howlett <liam@infradead.org>
+Cc: "Aneesh Kumar K.V" <aneesh.kumar@linux.ibm.com>
+Cc: Joao Martins <joao.m.martins@oracle.com>
+Cc: Lorenzo Stoakes <ljs@kernel.org>
+Cc: Madhavan Srinivasan <maddy@linux.ibm.com>
+Cc: Michael Ellerman <mpe@ellerman.id.au>
+Cc: Michal Hocko <mhocko@suse.com>
+Cc: Nicholas Piggin <npiggin@gmail.com>
+Cc: Suren Baghdasaryan <surenb@google.com>
+Cc: Vlastimil Babka <vbabka@kernel.org>
+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/mm_init.c |   34 +++++++++++++++++++---------------
+ 1 file changed, 19 insertions(+), 15 deletions(-)
+
+--- a/mm/mm_init.c
++++ b/mm/mm_init.c
+@@ -650,6 +650,20 @@ static inline void fixup_hashdist(void)
+ static inline void fixup_hashdist(void) {}
+ #endif /* CONFIG_NUMA */
++#ifdef CONFIG_ZONE_DEVICE
++static __meminit void pageblock_migratetype_init_range(unsigned long pfn,
++              unsigned long nr_pages, int migratetype)
++{
++      const unsigned long end = pfn + nr_pages;
++
++      for (pfn = pageblock_align(pfn); pfn < end; pfn += pageblock_nr_pages) {
++              set_pageblock_migratetype(pfn_to_page(pfn), migratetype);
++              if (IS_ALIGNED(pfn, PAGES_PER_SECTION))
++                      cond_resched();
++      }
++}
++#endif
++
+ #ifdef CONFIG_DEFERRED_STRUCT_PAGE_INIT
+ static inline void pgdat_set_deferred_range(pg_data_t *pgdat)
+ {
+@@ -1000,21 +1014,6 @@ static void __ref __init_zone_device_pag
+       page->zone_device_data = NULL;
+       /*
+-       * Mark the block movable so that blocks are reserved for
+-       * movable at startup. This will force kernel allocations
+-       * to reserve their blocks rather than leaking throughout
+-       * the address space during boot when many long-lived
+-       * kernel allocations are made.
+-       *
+-       * Please note that MEMINIT_HOTPLUG path doesn't clear memmap
+-       * because this is done early in section_activate()
+-       */
+-      if (pageblock_aligned(pfn)) {
+-              set_pageblock_migratetype(page, MIGRATE_MOVABLE);
+-              cond_resched();
+-      }
+-
+-      /*
+        * ZONE_DEVICE pages are released directly to the driver page allocator
+        * which will set the page count to 1 when allocating the page.
+        */
+@@ -1105,6 +1104,9 @@ void __ref memmap_init_zone_device(struc
+               __init_zone_device_page(page, pfn, zone_idx, nid, pgmap);
++              if (IS_ALIGNED(pfn, PAGES_PER_SECTION))
++                      cond_resched();
++
+               if (pfns_per_compound == 1)
+                       continue;
+@@ -1112,6 +1114,8 @@ void __ref memmap_init_zone_device(struc
+                                    compound_nr_pages(pfn, altmap, pgmap));
+       }
++      pageblock_migratetype_init_range(start_pfn, nr_pages, MIGRATE_MOVABLE);
++
+       pr_debug("%s initialised %lu pages in %ums\n", __func__,
+               nr_pages, jiffies_to_msecs(jiffies - start));
+ }
diff --git a/queue-6.12/mm-prepare-to-move-subsection_map_init-to-mm-sparse-vmemmap.c.patch b/queue-6.12/mm-prepare-to-move-subsection_map_init-to-mm-sparse-vmemmap.c.patch
new file mode 100644 (file)
index 0000000..eacc1d9
--- /dev/null
@@ -0,0 +1,832 @@
+From stable+bounces-289228-greg=kroah.com@vger.kernel.org Sun Jul 26 14:03:06 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 26 Jul 2026 08:00:45 -0400
+Subject: mm: prepare to move subsection_map_init() to mm/sparse-vmemmap.c
+To: stable@vger.kernel.org
+Cc: "David Hildenbrand (Arm)" <david@kernel.org>, "Lorenzo Stoakes (Oracle)" <ljs@kernel.org>, "Mike Rapoport (Microsoft)" <rppt@kernel.org>, Axel Rasmussen <axelrasmussen@google.com>, Liam Howlett <liam.howlett@oracle.com>, Michal Hocko <mhocko@suse.com>, Oscar Salvador <osalvador@suse.de>, Sidhartha Kumar <sidhartha.kumar@oracle.com>, Suren Baghdasaryan <surenb@google.com>, Vlastimil Babka <vbabka@kernel.org>, Wei Xu <weixugc@google.com>, Yuanchu Xie <yuanchu@google.com>, Andrew Morton <akpm@linux-foundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260726120046.4138231-1-sashal@kernel.org>
+
+From: "David Hildenbrand (Arm)" <david@kernel.org>
+
+[ Upstream commit fead6dcff83b02f8d6dc3c1ebbe4e09c05c54ee5 ]
+
+We want to move subsection_map_init() to mm/sparse-vmemmap.c.
+
+To prepare for getting rid of subsection_map_init() in mm/sparse.c
+completely, use a static inline function for !CONFIG_SPARSEMEM_VMEMMAP.
+
+While at it, move the declaration to internal.h and rename it to
+"sparse_init_subsection_map()".
+
+Link: https://lkml.kernel.org/r/20260320-sparsemem_cleanups-v2-11-096addc8800d@kernel.org
+Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
+Reviewed-by: Lorenzo Stoakes (Oracle) <ljs@kernel.org>
+Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
+Cc: Axel Rasmussen <axelrasmussen@google.com>
+Cc: Liam Howlett <liam.howlett@oracle.com>
+Cc: Michal Hocko <mhocko@suse.com>
+Cc: Oscar Salvador <osalvador@suse.de>
+Cc: Sidhartha Kumar <sidhartha.kumar@oracle.com>
+Cc: Suren Baghdasaryan <surenb@google.com>
+Cc: Vlastimil Babka <vbabka@kernel.org>
+Cc: Wei Xu <weixugc@google.com>
+Cc: Yuanchu Xie <yuanchu@google.com>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+Stable-dep-of: 721a73e30c9e ("mm/sparse-vmemmap: fix DAX vmemmap accounting with optimization")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ arch/arm64/mm/mmu.c            |    5 
+ arch/loongarch/mm/init.c       |    5 
+ arch/powerpc/mm/mem.c          |    5 
+ arch/riscv/mm/init.c           |    5 
+ arch/s390/mm/init.c            |    5 
+ arch/x86/mm/init_64.c          |    5 
+ include/linux/memory_hotplug.h |    8 -
+ include/linux/mmzone.h         |    7 
+ mm/internal.h                  |   47 ++++++
+ mm/memory_hotplug.c            |   13 -
+ mm/memremap.c                  |    4 
+ mm/mm_init.c                   |    2 
+ mm/sparse-vmemmap.c            |  310 +++++++++++++++++++++++++++++++++++++++++
+ mm/sparse.c                    |   80 ----------
+ 14 files changed, 399 insertions(+), 102 deletions(-)
+
+--- a/arch/arm64/mm/mmu.c
++++ b/arch/arm64/mm/mmu.c
+@@ -1384,12 +1384,13 @@ int arch_add_memory(int nid, u64 start,
+       return ret;
+ }
+-void arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap)
++void arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap,
++                      struct dev_pagemap *pgmap)
+ {
+       unsigned long start_pfn = start >> PAGE_SHIFT;
+       unsigned long nr_pages = size >> PAGE_SHIFT;
+-      __remove_pages(start_pfn, nr_pages, altmap);
++      __remove_pages(start_pfn, nr_pages, altmap, pgmap);
+       __remove_pgd_mapping(swapper_pg_dir, __phys_to_virt(start), size);
+ }
+--- a/arch/loongarch/mm/init.c
++++ b/arch/loongarch/mm/init.c
+@@ -103,12 +103,13 @@ int arch_add_memory(int nid, u64 start,
+       return ret;
+ }
+-void arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap)
++void arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap,
++                      struct dev_pagemap *pgmap)
+ {
+       unsigned long start_pfn = start >> PAGE_SHIFT;
+       unsigned long nr_pages = size >> PAGE_SHIFT;
+-      __remove_pages(start_pfn, nr_pages, altmap);
++      __remove_pages(start_pfn, nr_pages, altmap, pgmap);
+ }
+ #ifdef CONFIG_NUMA
+--- a/arch/powerpc/mm/mem.c
++++ b/arch/powerpc/mm/mem.c
+@@ -157,12 +157,13 @@ int __ref arch_add_memory(int nid, u64 s
+       return rc;
+ }
+-void __ref arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap)
++void __ref arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap,
++                            struct dev_pagemap *pgmap)
+ {
+       unsigned long start_pfn = start >> PAGE_SHIFT;
+       unsigned long nr_pages = size >> PAGE_SHIFT;
+-      __remove_pages(start_pfn, nr_pages, altmap);
++      __remove_pages(start_pfn, nr_pages, altmap, pgmap);
+       arch_remove_linear_mapping(start, size);
+ }
+ #endif
+--- a/arch/riscv/mm/init.c
++++ b/arch/riscv/mm/init.c
+@@ -1839,9 +1839,10 @@ int __ref arch_add_memory(int nid, u64 s
+       return ret;
+ }
+-void __ref arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap)
++void __ref arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap,
++                            struct dev_pagemap *pgmap)
+ {
+-      __remove_pages(start >> PAGE_SHIFT, size >> PAGE_SHIFT, altmap);
++      __remove_pages(start >> PAGE_SHIFT, size >> PAGE_SHIFT, altmap, pgmap);
+       remove_linear_mapping(start, size);
+       flush_tlb_all();
+ }
+--- a/arch/s390/mm/init.c
++++ b/arch/s390/mm/init.c
+@@ -290,12 +290,13 @@ int arch_add_memory(int nid, u64 start,
+       return rc;
+ }
+-void arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap)
++void arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap,
++                      struct dev_pagemap *pgmap)
+ {
+       unsigned long start_pfn = start >> PAGE_SHIFT;
+       unsigned long nr_pages = size >> PAGE_SHIFT;
+-      __remove_pages(start_pfn, nr_pages, altmap);
++      __remove_pages(start_pfn, nr_pages, altmap, pgmap);
+       vmem_remove_mapping(start, size);
+ }
+ #endif /* CONFIG_MEMORY_HOTPLUG */
+--- a/arch/x86/mm/init_64.c
++++ b/arch/x86/mm/init_64.c
+@@ -1293,12 +1293,13 @@ kernel_physical_mapping_remove(unsigned
+       remove_pagetable(start, end, true, NULL);
+ }
+-void __ref arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap)
++void __ref arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap,
++                            struct dev_pagemap *pgmap)
+ {
+       unsigned long start_pfn = start >> PAGE_SHIFT;
+       unsigned long nr_pages = size >> PAGE_SHIFT;
+-      __remove_pages(start_pfn, nr_pages, altmap);
++      __remove_pages(start_pfn, nr_pages, altmap, pgmap);
+       kernel_physical_mapping_remove(start, start + size);
+ }
+ #endif /* CONFIG_MEMORY_HOTPLUG */
+--- a/include/linux/memory_hotplug.h
++++ b/include/linux/memory_hotplug.h
+@@ -153,9 +153,10 @@ static inline bool movable_node_is_enabl
+       return movable_node_enabled;
+ }
+-extern void arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap);
++extern void arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap,
++                             struct dev_pagemap *pgmap);
+ extern void __remove_pages(unsigned long start_pfn, unsigned long nr_pages,
+-                         struct vmem_altmap *altmap);
++                         struct vmem_altmap *altmap, struct dev_pagemap *pgmap);
+ /* reasonably generic interface to expand the physical pages */
+ extern int __add_pages(int nid, unsigned long start_pfn, unsigned long nr_pages,
+@@ -321,7 +322,8 @@ extern int sparse_add_section(int nid, u
+               unsigned long nr_pages, struct vmem_altmap *altmap,
+               struct dev_pagemap *pgmap);
+ extern void sparse_remove_section(unsigned long pfn, unsigned long nr_pages,
+-                                struct vmem_altmap *altmap);
++                                struct vmem_altmap *altmap,
++                                struct dev_pagemap *pgmap);
+ extern struct page *sparse_decode_mem_map(unsigned long coded_mem_map,
+                                         unsigned long pnum);
+ extern struct zone *zone_for_pfn_range(int online_type, int nid,
+--- a/include/linux/mmzone.h
++++ b/include/linux/mmzone.h
+@@ -1815,15 +1815,13 @@ struct mem_section_usage {
+       unsigned long pageblock_flags[0];
+ };
+-void subsection_map_init(unsigned long pfn, unsigned long nr_pages);
+-
+ struct page;
+ struct page_ext;
+ struct mem_section {
+       /*
+        * This is, logically, a pointer to an array of struct
+        * pages.  However, it is stored with some other magic.
+-       * (see sparse.c::sparse_init_one_section())
++       * (see sparse_init_one_section())
+        *
+        * Additionally during early boot we encode node id of
+        * the location of the section here to guide allocation.
+@@ -2092,8 +2090,9 @@ void sparse_init(void);
+ #else
+ #define sparse_init() do {} while (0)
+ #define sparse_index_init(_sec, _nid)  do {} while (0)
++#define sparse_vmemmap_init_nid_early(_nid) do {} while (0)
++#define sparse_vmemmap_init_nid_late(_nid) do {} while (0)
+ #define pfn_in_present_section pfn_valid
+-#define subsection_map_init(_pfn, _nr_pages) do {} while (0)
+ #endif /* CONFIG_SPARSEMEM */
+ #endif /* !__GENERATING_BOUNDS.H */
+--- a/mm/internal.h
++++ b/mm/internal.h
+@@ -749,6 +749,53 @@ extern void *memmap_alloc(phys_addr_t si
+ void memmap_init_range(unsigned long, int, unsigned long, unsigned long,
+               unsigned long, enum meminit_context, struct vmem_altmap *, int);
++#ifdef CONFIG_SPARSEMEM
++int sparse_index_init(unsigned long section_nr, int nid);
++
++static inline void sparse_init_one_section(struct mem_section *ms,
++              unsigned long pnum, struct page *mem_map,
++              struct mem_section_usage *usage, unsigned long flags)
++{
++      unsigned long coded_mem_map;
++
++      BUILD_BUG_ON(SECTION_MAP_LAST_BIT > PFN_SECTION_SHIFT);
++
++      /*
++       * We encode the start PFN of the section into the mem_map such that
++       * page_to_pfn() on !CONFIG_SPARSEMEM_VMEMMAP can simply subtract it
++       * from the page pointer to obtain the PFN.
++       */
++      coded_mem_map = (unsigned long)(mem_map - section_nr_to_pfn(pnum));
++      VM_WARN_ON_ONCE(coded_mem_map & ~SECTION_MAP_MASK);
++
++      ms->section_mem_map &= ~SECTION_MAP_MASK;
++      ms->section_mem_map |= coded_mem_map;
++      ms->section_mem_map |= flags | SECTION_HAS_MEM_MAP;
++      ms->usage = usage;
++}
++
++static inline void __section_mark_present(struct mem_section *ms,
++              unsigned long section_nr)
++{
++      if (section_nr > __highest_present_section_nr)
++              __highest_present_section_nr = section_nr;
++
++      ms->section_mem_map |= SECTION_MARKED_PRESENT;
++}
++#endif /* CONFIG_SPARSEMEM */
++
++/*
++ * mm/sparse-vmemmap.c
++ */
++#ifdef CONFIG_SPARSEMEM_VMEMMAP
++void sparse_init_subsection_map(unsigned long pfn, unsigned long nr_pages);
++#else
++static inline void sparse_init_subsection_map(unsigned long pfn,
++              unsigned long nr_pages)
++{
++}
++#endif /* CONFIG_SPARSEMEM_VMEMMAP */
++
+ #if defined CONFIG_COMPACTION || defined CONFIG_CMA
+ /*
+--- a/mm/memory_hotplug.c
++++ b/mm/memory_hotplug.c
+@@ -564,6 +564,7 @@ void remove_pfn_range_from_zone(struct z
+  * @pfn: starting pageframe (must be aligned to start of a section)
+  * @nr_pages: number of pages to remove (must be multiple of section size)
+  * @altmap: alternative device page map or %NULL if default memmap is used
++ * @pgmap: device page map or %NULL if not ZONE_DEVICE
+  *
+  * Generic helper function to remove section mappings and sysfs entries
+  * for the section of the memory we are removing. Caller needs to make
+@@ -571,7 +572,7 @@ void remove_pfn_range_from_zone(struct z
+  * calling offline_pages().
+  */
+ void __remove_pages(unsigned long pfn, unsigned long nr_pages,
+-                  struct vmem_altmap *altmap)
++                  struct vmem_altmap *altmap, struct dev_pagemap *pgmap)
+ {
+       const unsigned long end_pfn = pfn + nr_pages;
+       unsigned long cur_nr_pages;
+@@ -586,7 +587,7 @@ void __remove_pages(unsigned long pfn, u
+               /* Select all remaining pages up to the next section boundary */
+               cur_nr_pages = min(end_pfn - pfn,
+                                  SECTION_ALIGN_UP(pfn + 1) - pfn);
+-              sparse_remove_section(pfn, cur_nr_pages, altmap);
++              sparse_remove_section(pfn, cur_nr_pages, altmap, pgmap);
+       }
+ }
+@@ -1411,7 +1412,7 @@ static void remove_memory_blocks_and_alt
+               remove_memory_block_devices(cur_start, memblock_size);
+-              arch_remove_memory(cur_start, memblock_size, altmap);
++              arch_remove_memory(cur_start, memblock_size, altmap, NULL);
+               /* Verify that all vmemmap pages have actually been freed. */
+               WARN(altmap->alloc, "Altmap not fully unmapped");
+@@ -1456,7 +1457,7 @@ static int create_altmaps_and_memory_blo
+               ret = create_memory_block_devices(cur_start, memblock_size,
+                                                 params.altmap, group);
+               if (ret) {
+-                      arch_remove_memory(cur_start, memblock_size, params.altmap);
++                      arch_remove_memory(cur_start, memblock_size, params.altmap, NULL);
+                       kfree(params.altmap);
+                       goto out;
+               }
+@@ -1534,7 +1535,7 @@ int add_memory_resource(int nid, struct
+               /* create memory block devices after memory was added */
+               ret = create_memory_block_devices(start, size, NULL, group);
+               if (ret) {
+-                      arch_remove_memory(start, size, params.altmap);
++                      arch_remove_memory(start, size, params.altmap, NULL);
+                       goto error;
+               }
+       }
+@@ -2273,7 +2274,7 @@ static int try_remove_memory(u64 start,
+                * No altmaps present, do the removal directly
+                */
+               remove_memory_block_devices(start, size);
+-              arch_remove_memory(start, size, NULL);
++              arch_remove_memory(start, size, NULL, NULL);
+       } else {
+               /* all memblocks in the range have altmaps */
+               remove_memory_blocks_and_altmaps(start, size);
+--- a/mm/memremap.c
++++ b/mm/memremap.c
+@@ -122,10 +122,10 @@ static void pageunmap_range(struct dev_p
+                                  PHYS_PFN(range_len(range)));
+       if (pgmap->type == MEMORY_DEVICE_PRIVATE) {
+               __remove_pages(PHYS_PFN(range->start),
+-                             PHYS_PFN(range_len(range)), NULL);
++                             PHYS_PFN(range_len(range)), NULL, pgmap);
+       } else {
+               arch_remove_memory(range->start, range_len(range),
+-                              pgmap_altmap(pgmap));
++                              pgmap_altmap(pgmap), pgmap);
+               kasan_remove_zero_shadow(__va(range->start), range_len(range));
+       }
+       mem_hotplug_done();
+--- a/mm/mm_init.c
++++ b/mm/mm_init.c
+@@ -1835,7 +1835,7 @@ void __init free_area_init(unsigned long
+               pr_info("  node %3d: [mem %#018Lx-%#018Lx]\n", nid,
+                       (u64)start_pfn << PAGE_SHIFT,
+                       ((u64)end_pfn << PAGE_SHIFT) - 1);
+-              subsection_map_init(start_pfn, end_pfn - start_pfn);
++              sparse_init_subsection_map(start_pfn, end_pfn - start_pfn);
+       }
+       /* Initialise every node */
+--- a/mm/sparse-vmemmap.c
++++ b/mm/sparse-vmemmap.c
+@@ -31,6 +31,8 @@
+ #include <asm/dma.h>
++#include "internal.h"
++
+ /*
+  * Allocate a block of memory to be used to back the virtual memory map
+  * or to back the page tables that are used to create the mapping.
+@@ -476,3 +478,311 @@ struct page * __meminit __populate_secti
+       return pfn_to_page(pfn);
+ }
++
++static void subsection_mask_set(unsigned long *map, unsigned long pfn,
++              unsigned long nr_pages)
++{
++      int idx = subsection_map_index(pfn);
++      int end = subsection_map_index(pfn + nr_pages - 1);
++
++      bitmap_set(map, idx, end - idx + 1);
++}
++
++void __init sparse_init_subsection_map(unsigned long pfn, unsigned long nr_pages)
++{
++      int end_sec_nr = pfn_to_section_nr(pfn + nr_pages - 1);
++      unsigned long nr, start_sec_nr = pfn_to_section_nr(pfn);
++
++      for (nr = start_sec_nr; nr <= end_sec_nr; nr++) {
++              struct mem_section *ms;
++              unsigned long pfns;
++
++              pfns = min(nr_pages, PAGES_PER_SECTION
++                              - (pfn & ~PAGE_SECTION_MASK));
++              ms = __nr_to_section(nr);
++              subsection_mask_set(ms->usage->subsection_map, pfn, pfns);
++
++              pr_debug("%s: sec: %lu pfns: %lu set(%d, %d)\n", __func__, nr,
++                              pfns, subsection_map_index(pfn),
++                              subsection_map_index(pfn + pfns - 1));
++
++              pfn += pfns;
++              nr_pages -= pfns;
++      }
++}
++
++#ifdef CONFIG_MEMORY_HOTPLUG
++
++/* Mark all memory sections within the pfn range as online */
++void online_mem_sections(unsigned long start_pfn, unsigned long end_pfn)
++{
++      unsigned long pfn;
++
++      for (pfn = start_pfn; pfn < end_pfn; pfn += PAGES_PER_SECTION) {
++              unsigned long section_nr = pfn_to_section_nr(pfn);
++              struct mem_section *ms = __nr_to_section(section_nr);
++
++              ms->section_mem_map |= SECTION_IS_ONLINE;
++      }
++}
++
++/* Mark all memory sections within the pfn range as offline */
++void offline_mem_sections(unsigned long start_pfn, unsigned long end_pfn)
++{
++      unsigned long pfn;
++
++      for (pfn = start_pfn; pfn < end_pfn; pfn += PAGES_PER_SECTION) {
++              unsigned long section_nr = pfn_to_section_nr(pfn);
++              struct mem_section *ms = __nr_to_section(section_nr);
++
++              ms->section_mem_map &= ~SECTION_IS_ONLINE;
++      }
++}
++
++static struct page * __meminit populate_section_memmap(unsigned long pfn,
++              unsigned long nr_pages, int nid, struct vmem_altmap *altmap,
++              struct dev_pagemap *pgmap)
++{
++      struct page *page = __populate_section_memmap(pfn, nr_pages, nid, altmap,
++                                                    pgmap);
++
++      memmap_pages_add(DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE));
++
++      return page;
++}
++
++static void depopulate_section_memmap(unsigned long pfn, unsigned long nr_pages,
++              struct vmem_altmap *altmap, struct dev_pagemap *pgmap)
++{
++      unsigned long start = (unsigned long) pfn_to_page(pfn);
++      unsigned long end = start + nr_pages * sizeof(struct page);
++
++      memmap_pages_add(-1L * (DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE)));
++      vmemmap_free(start, end, altmap);
++}
++
++static void free_map_bootmem(struct page *memmap)
++{
++      unsigned long start = (unsigned long)memmap;
++      unsigned long end = (unsigned long)(memmap + PAGES_PER_SECTION);
++
++      memmap_boot_pages_add(-1L * (DIV_ROUND_UP(PAGES_PER_SECTION * sizeof(struct page),
++                                                PAGE_SIZE)));
++      vmemmap_free(start, end, NULL);
++}
++
++static int clear_subsection_map(unsigned long pfn, unsigned long nr_pages)
++{
++      DECLARE_BITMAP(map, SUBSECTIONS_PER_SECTION) = { 0 };
++      DECLARE_BITMAP(tmp, SUBSECTIONS_PER_SECTION) = { 0 };
++      struct mem_section *ms = __pfn_to_section(pfn);
++      unsigned long *subsection_map = ms->usage
++              ? &ms->usage->subsection_map[0] : NULL;
++
++      subsection_mask_set(map, pfn, nr_pages);
++      if (subsection_map)
++              bitmap_and(tmp, map, subsection_map, SUBSECTIONS_PER_SECTION);
++
++      if (WARN(!subsection_map || !bitmap_equal(tmp, map, SUBSECTIONS_PER_SECTION),
++                              "section already deactivated (%#lx + %ld)\n",
++                              pfn, nr_pages))
++              return -EINVAL;
++
++      bitmap_xor(subsection_map, map, subsection_map, SUBSECTIONS_PER_SECTION);
++      return 0;
++}
++
++static bool is_subsection_map_empty(struct mem_section *ms)
++{
++      return bitmap_empty(&ms->usage->subsection_map[0],
++                          SUBSECTIONS_PER_SECTION);
++}
++
++static int fill_subsection_map(unsigned long pfn, unsigned long nr_pages)
++{
++      struct mem_section *ms = __pfn_to_section(pfn);
++      DECLARE_BITMAP(map, SUBSECTIONS_PER_SECTION) = { 0 };
++      unsigned long *subsection_map;
++      int rc = 0;
++
++      subsection_mask_set(map, pfn, nr_pages);
++
++      subsection_map = &ms->usage->subsection_map[0];
++
++      if (bitmap_empty(map, SUBSECTIONS_PER_SECTION))
++              rc = -EINVAL;
++      else if (bitmap_intersects(map, subsection_map, SUBSECTIONS_PER_SECTION))
++              rc = -EEXIST;
++      else
++              bitmap_or(subsection_map, map, subsection_map,
++                              SUBSECTIONS_PER_SECTION);
++
++      return rc;
++}
++
++/*
++ * To deactivate a memory region, there are 3 cases to handle:
++ *
++ * 1. deactivation of a partial hot-added section:
++ *      a) section was present at memory init.
++ *      b) section was hot-added post memory init.
++ * 2. deactivation of a complete hot-added section.
++ * 3. deactivation of a complete section from memory init.
++ *
++ * For 1, when subsection_map does not empty we will not be freeing the
++ * usage map, but still need to free the vmemmap range.
++ */
++static void section_deactivate(unsigned long pfn, unsigned long nr_pages,
++              struct vmem_altmap *altmap, struct dev_pagemap *pgmap)
++{
++      struct mem_section *ms = __pfn_to_section(pfn);
++      bool section_is_early = early_section(ms);
++      struct page *memmap = NULL;
++      bool empty;
++
++      if (clear_subsection_map(pfn, nr_pages))
++              return;
++
++      empty = is_subsection_map_empty(ms);
++      if (empty) {
++              /*
++               * Mark the section invalid so that valid_section()
++               * return false. This prevents code from dereferencing
++               * ms->usage array.
++               */
++              ms->section_mem_map &= ~SECTION_HAS_MEM_MAP;
++
++              /*
++               * When removing an early section, the usage map is kept (as the
++               * usage maps of other sections fall into the same page). It
++               * will be re-used when re-adding the section - which is then no
++               * longer an early section. If the usage map is PageReserved, it
++               * was allocated during boot.
++               */
++              if (!PageReserved(virt_to_page(ms->usage))) {
++                      kfree_rcu(ms->usage, rcu);
++                      WRITE_ONCE(ms->usage, NULL);
++              }
++              memmap = pfn_to_page(SECTION_ALIGN_DOWN(pfn));
++      }
++
++      /*
++       * The memmap of early sections is always fully populated. See
++       * section_activate() and pfn_valid() .
++       */
++      if (!section_is_early)
++              depopulate_section_memmap(pfn, nr_pages, altmap, pgmap);
++      else if (memmap)
++              free_map_bootmem(memmap);
++
++      if (empty)
++              ms->section_mem_map = (unsigned long)NULL;
++}
++
++static struct page * __meminit section_activate(int nid, unsigned long pfn,
++              unsigned long nr_pages, struct vmem_altmap *altmap,
++              struct dev_pagemap *pgmap)
++{
++      struct mem_section *ms = __pfn_to_section(pfn);
++      struct mem_section_usage *usage = NULL;
++      struct page *memmap;
++      int rc;
++
++      if (!ms->usage) {
++              usage = kzalloc(mem_section_usage_size(), GFP_KERNEL);
++              if (!usage)
++                      return ERR_PTR(-ENOMEM);
++              ms->usage = usage;
++      }
++
++      rc = fill_subsection_map(pfn, nr_pages);
++      if (rc) {
++              if (usage)
++                      ms->usage = NULL;
++              kfree(usage);
++              return ERR_PTR(rc);
++      }
++
++      /*
++       * The early init code does not consider partially populated
++       * initial sections, it simply assumes that memory will never be
++       * referenced.  If we hot-add memory into such a section then we
++       * do not need to populate the memmap and can simply reuse what
++       * is already there.
++       */
++      if (nr_pages < PAGES_PER_SECTION && early_section(ms))
++              return pfn_to_page(pfn);
++
++      memmap = populate_section_memmap(pfn, nr_pages, nid, altmap, pgmap);
++      if (!memmap) {
++              section_deactivate(pfn, nr_pages, altmap, pgmap);
++              return ERR_PTR(-ENOMEM);
++      }
++
++      return memmap;
++}
++
++/**
++ * sparse_add_section - add a memory section, or populate an existing one
++ * @nid: The node to add section on
++ * @start_pfn: start pfn of the memory range
++ * @nr_pages: number of pfns to add in the section
++ * @altmap: alternate pfns to allocate the memmap backing store
++ * @pgmap: alternate compound page geometry for devmap mappings
++ *
++ * This is only intended for hotplug.
++ *
++ * Note that only VMEMMAP supports sub-section aligned hotplug,
++ * the proper alignment and size are gated by check_pfn_span().
++ *
++ *
++ * Return:
++ * * 0                - On success.
++ * * -EEXIST  - Section has been present.
++ * * -ENOMEM  - Out of memory.
++ */
++int __meminit sparse_add_section(int nid, unsigned long start_pfn,
++              unsigned long nr_pages, struct vmem_altmap *altmap,
++              struct dev_pagemap *pgmap)
++{
++      unsigned long section_nr = pfn_to_section_nr(start_pfn);
++      struct mem_section *ms;
++      struct page *memmap;
++      int ret;
++
++      ret = sparse_index_init(section_nr, nid);
++      if (ret < 0)
++              return ret;
++
++      memmap = section_activate(nid, start_pfn, nr_pages, altmap, pgmap);
++      if (IS_ERR(memmap))
++              return PTR_ERR(memmap);
++
++      /*
++       * Poison uninitialized struct pages in order to catch invalid flags
++       * combinations.
++       */
++      page_init_poison(memmap, sizeof(struct page) * nr_pages);
++
++      ms = __nr_to_section(section_nr);
++      __section_mark_present(ms, section_nr);
++
++      /* Align memmap to section boundary in the subsection case */
++      if (section_nr_to_pfn(section_nr) != start_pfn)
++              memmap = pfn_to_page(section_nr_to_pfn(section_nr));
++      sparse_init_one_section(ms, section_nr, memmap, ms->usage, 0);
++
++      return 0;
++}
++
++void sparse_remove_section(unsigned long pfn, unsigned long nr_pages,
++              struct vmem_altmap *altmap, struct dev_pagemap *pgmap)
++{
++      struct mem_section *ms = __pfn_to_section(pfn);
++
++      if (WARN_ON_ONCE(!valid_section(ms)))
++              return;
++
++      section_deactivate(pfn, nr_pages, altmap, pgmap);
++}
++#endif /* CONFIG_MEMORY_HOTPLUG */
+--- a/mm/sparse.c
++++ b/mm/sparse.c
+@@ -79,7 +79,7 @@ static noinline struct mem_section __ref
+       return section;
+ }
+-static int __meminit sparse_index_init(unsigned long section_nr, int nid)
++int __meminit sparse_index_init(unsigned long section_nr, int nid)
+ {
+       unsigned long root = SECTION_NR_TO_ROOT(section_nr);
+       struct mem_section *section;
+@@ -103,7 +103,7 @@ static int __meminit sparse_index_init(u
+       return 0;
+ }
+ #else /* !SPARSEMEM_EXTREME */
+-static inline int sparse_index_init(unsigned long section_nr, int nid)
++int sparse_index_init(unsigned long section_nr, int nid)
+ {
+       return 0;
+ }
+@@ -161,14 +161,6 @@ static void __meminit mminit_validate_me
+  * those loops early.
+  */
+ unsigned long __highest_present_section_nr;
+-static void __section_mark_present(struct mem_section *ms,
+-              unsigned long section_nr)
+-{
+-      if (section_nr > __highest_present_section_nr)
+-              __highest_present_section_nr = section_nr;
+-
+-      ms->section_mem_map |= SECTION_MARKED_PRESENT;
+-}
+ #define for_each_present_section_nr(start, section_nr)                \
+       for (section_nr = next_present_section_nr(start-1);     \
+@@ -180,44 +172,6 @@ static inline unsigned long first_presen
+       return next_present_section_nr(-1);
+ }
+-#ifdef CONFIG_SPARSEMEM_VMEMMAP
+-static void subsection_mask_set(unsigned long *map, unsigned long pfn,
+-              unsigned long nr_pages)
+-{
+-      int idx = subsection_map_index(pfn);
+-      int end = subsection_map_index(pfn + nr_pages - 1);
+-
+-      bitmap_set(map, idx, end - idx + 1);
+-}
+-
+-void __init subsection_map_init(unsigned long pfn, unsigned long nr_pages)
+-{
+-      int end_sec_nr = pfn_to_section_nr(pfn + nr_pages - 1);
+-      unsigned long nr, start_sec_nr = pfn_to_section_nr(pfn);
+-
+-      for (nr = start_sec_nr; nr <= end_sec_nr; nr++) {
+-              struct mem_section *ms;
+-              unsigned long pfns;
+-
+-              pfns = min(nr_pages, PAGES_PER_SECTION
+-                              - (pfn & ~PAGE_SECTION_MASK));
+-              ms = __nr_to_section(nr);
+-              subsection_mask_set(ms->usage->subsection_map, pfn, pfns);
+-
+-              pr_debug("%s: sec: %lu pfns: %lu set(%d, %d)\n", __func__, nr,
+-                              pfns, subsection_map_index(pfn),
+-                              subsection_map_index(pfn + pfns - 1));
+-
+-              pfn += pfns;
+-              nr_pages -= pfns;
+-      }
+-}
+-#else
+-void __init subsection_map_init(unsigned long pfn, unsigned long nr_pages)
+-{
+-}
+-#endif
+-
+ /* Record a memory area against a node. */
+ static void __init memory_present(int nid, unsigned long start, unsigned long end)
+ {
+@@ -268,20 +222,6 @@ static void __init memblocks_present(voi
+               memory_present(nid, start, end);
+ }
+-/*
+- * Subtle, we encode the real pfn into the mem_map such that
+- * the identity pfn - section_mem_map will return the actual
+- * physical page frame number.
+- */
+-static unsigned long sparse_encode_mem_map(struct page *mem_map, unsigned long pnum)
+-{
+-      unsigned long coded_mem_map =
+-              (unsigned long)(mem_map - (section_nr_to_pfn(pnum)));
+-      BUILD_BUG_ON(SECTION_MAP_LAST_BIT > PFN_SECTION_SHIFT);
+-      BUG_ON(coded_mem_map & ~SECTION_MAP_MASK);
+-      return coded_mem_map;
+-}
+-
+ #ifdef CONFIG_MEMORY_HOTPLUG
+ /*
+  * Decode mem_map from the coded memmap
+@@ -294,16 +234,6 @@ struct page *sparse_decode_mem_map(unsig
+ }
+ #endif /* CONFIG_MEMORY_HOTPLUG */
+-static void __meminit sparse_init_one_section(struct mem_section *ms,
+-              unsigned long pnum, struct page *mem_map,
+-              struct mem_section_usage *usage, unsigned long flags)
+-{
+-      ms->section_mem_map &= ~SECTION_MAP_MASK;
+-      ms->section_mem_map |= sparse_encode_mem_map(mem_map, pnum)
+-              | SECTION_HAS_MEM_MAP | flags;
+-      ms->usage = usage;
+-}
+-
+ static unsigned long usemap_size(void)
+ {
+       return BITS_TO_LONGS(SECTION_BLOCKFLAGS_BITS) * sizeof(unsigned long);
+@@ -587,6 +517,7 @@ void __init sparse_init(void)
+       vmemmap_populate_print_last();
+ }
++#ifndef CONFIG_SPARSEMEM_VMEMMAP
+ #ifdef CONFIG_MEMORY_HOTPLUG
+ /* Mark all memory sections within the pfn range as online */
+@@ -930,7 +861,6 @@ int __meminit sparse_add_section(int nid
+               page_init_poison(memmap, sizeof(struct page) * nr_pages);
+       ms = __nr_to_section(section_nr);
+-      set_section_nid(section_nr, nid);
+       __section_mark_present(ms, section_nr);
+       /* Align memmap to section boundary in the subsection case */
+@@ -942,7 +872,8 @@ int __meminit sparse_add_section(int nid
+ }
+ void sparse_remove_section(unsigned long pfn, unsigned long nr_pages,
+-                         struct vmem_altmap *altmap)
++                         struct vmem_altmap *altmap,
++                         struct dev_pagemap *pgmap)
+ {
+       struct mem_section *ms = __pfn_to_section(pfn);
+@@ -952,3 +883,4 @@ void sparse_remove_section(unsigned long
+       section_deactivate(pfn, nr_pages, altmap);
+ }
+ #endif /* CONFIG_MEMORY_HOTPLUG */
++#endif /* !CONFIG_SPARSEMEM_VMEMMAP */
diff --git a/queue-6.12/mm-sparse-vmemmap-fix-dax-vmemmap-accounting-with-optimization.patch b/queue-6.12/mm-sparse-vmemmap-fix-dax-vmemmap-accounting-with-optimization.patch
new file mode 100644 (file)
index 0000000..bba2722
--- /dev/null
@@ -0,0 +1,112 @@
+From stable+bounces-289229-greg=kroah.com@vger.kernel.org Sun Jul 26 14:00:55 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 26 Jul 2026 08:00:46 -0400
+Subject: mm/sparse-vmemmap: fix DAX vmemmap accounting with optimization
+To: stable@vger.kernel.org
+Cc: Muchun Song <songmuchun@bytedance.com>, "Mike Rapoport (Microsoft)" <rppt@kernel.org>, Oscar Salvador <osalvador@suse.de>, "David Hildenbrand (Arm)" <david@kernel.org>, "Liam R. Howlett" <liam@infradead.org>, "Aneesh Kumar K.V" <aneesh.kumar@linux.ibm.com>, Joao Martins <joao.m.martins@oracle.com>, Lorenzo Stoakes <ljs@kernel.org>, Madhavan Srinivasan <maddy@linux.ibm.com>, Michael Ellerman <mpe@ellerman.id.au>, Michal Hocko <mhocko@suse.com>, Nicholas Piggin <npiggin@gmail.com>, Suren Baghdasaryan <surenb@google.com>, Vlastimil Babka <vbabka@kernel.org>, Andrew Morton <akpm@linux-foundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260726120046.4138231-2-sashal@kernel.org>
+
+From: Muchun Song <songmuchun@bytedance.com>
+
+[ Upstream commit 721a73e30c9e3e8fcffe1725bcede1bbd20b4918 ]
+
+When vmemmap optimization is enabled for DAX, the nr_memmap_pages counter
+in /proc/vmstat is incorrect.  The current code always accounts for the
+full, non-optimized vmemmap size, but vmemmap optimization reduces the
+actual number of vmemmap pages by reusing tail pages.  This causes the
+system to overcount vmemmap usage, leading to inaccurate page statistics
+in /proc/vmstat.
+
+Fix this by introducing section_nr_vmemmap_pages(), which returns the
+exact vmemmap page count for a given pfn range based on whether
+optimization is in effect.
+
+Link: https://lore.kernel.org/20260428081855.1249045-5-songmuchun@bytedance.com
+Fixes: 15995a352474 ("mm: report per-page metadata information")
+Signed-off-by: Muchun Song <songmuchun@bytedance.com>
+Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
+Acked-by: Oscar Salvador <osalvador@suse.de>
+Acked-by: David Hildenbrand (Arm) <david@kernel.org>
+Acked-by: Liam R. Howlett <liam@infradead.org>
+Cc: "Aneesh Kumar K.V" <aneesh.kumar@linux.ibm.com>
+Cc: Joao Martins <joao.m.martins@oracle.com>
+Cc: Lorenzo Stoakes <ljs@kernel.org>
+Cc: Madhavan Srinivasan <maddy@linux.ibm.com>
+Cc: Michael Ellerman <mpe@ellerman.id.au>
+Cc: Michal Hocko <mhocko@suse.com>
+Cc: Nicholas Piggin <npiggin@gmail.com>
+Cc: Suren Baghdasaryan <surenb@google.com>
+Cc: Vlastimil Babka <vbabka@kernel.org>
+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/sparse-vmemmap.c |   34 ++++++++++++++++++++++++++++++----
+ 1 file changed, 30 insertions(+), 4 deletions(-)
+
+--- a/mm/sparse-vmemmap.c
++++ b/mm/sparse-vmemmap.c
+@@ -539,6 +539,31 @@ void offline_mem_sections(unsigned long
+       }
+ }
++static int __meminit section_nr_vmemmap_pages(unsigned long pfn, unsigned long nr_pages,
++              struct vmem_altmap *altmap, struct dev_pagemap *pgmap)
++{
++      const unsigned int order = pgmap ? pgmap->vmemmap_shift : 0;
++      const unsigned long pages_per_compound = 1UL << order;
++
++      VM_WARN_ON_ONCE(!IS_ALIGNED(pfn | nr_pages, PAGES_PER_SUBSECTION));
++      VM_WARN_ON_ONCE(nr_pages > PAGES_PER_SECTION);
++
++      if (!vmemmap_can_optimize(altmap, pgmap))
++              return DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE);
++
++      if (order < PFN_SECTION_SHIFT) {
++              VM_WARN_ON_ONCE(!IS_ALIGNED(pfn | nr_pages, pages_per_compound));
++              return VMEMMAP_RESERVE_NR * nr_pages / pages_per_compound;
++      }
++
++      VM_WARN_ON_ONCE(!IS_ALIGNED(pfn | nr_pages, PAGES_PER_SECTION));
++
++      if (IS_ALIGNED(pfn, pages_per_compound))
++              return VMEMMAP_RESERVE_NR;
++
++      return 0;
++}
++
+ static struct page * __meminit populate_section_memmap(unsigned long pfn,
+               unsigned long nr_pages, int nid, struct vmem_altmap *altmap,
+               struct dev_pagemap *pgmap)
+@@ -546,7 +571,7 @@ static struct page * __meminit populate_
+       struct page *page = __populate_section_memmap(pfn, nr_pages, nid, altmap,
+                                                     pgmap);
+-      memmap_pages_add(DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE));
++      memmap_pages_add(section_nr_vmemmap_pages(pfn, nr_pages, altmap, pgmap));
+       return page;
+ }
+@@ -557,7 +582,7 @@ static void depopulate_section_memmap(un
+       unsigned long start = (unsigned long) pfn_to_page(pfn);
+       unsigned long end = start + nr_pages * sizeof(struct page);
+-      memmap_pages_add(-1L * (DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE)));
++      memmap_pages_add(-section_nr_vmemmap_pages(pfn, nr_pages, altmap, pgmap));
+       vmemmap_free(start, end, altmap);
+ }
+@@ -565,9 +590,10 @@ static void free_map_bootmem(struct page
+ {
+       unsigned long start = (unsigned long)memmap;
+       unsigned long end = (unsigned long)(memmap + PAGES_PER_SECTION);
++      unsigned long pfn = page_to_pfn(memmap);
+-      memmap_boot_pages_add(-1L * (DIV_ROUND_UP(PAGES_PER_SECTION * sizeof(struct page),
+-                                                PAGE_SIZE)));
++      memmap_boot_pages_add(-section_nr_vmemmap_pages(pfn, PAGES_PER_SECTION,
++                                                      NULL, NULL));
+       vmemmap_free(start, end, NULL);
+ }
diff --git a/queue-6.12/mm-sparse-vmemmap-fix-vmemmap-accounting-underflow.patch b/queue-6.12/mm-sparse-vmemmap-fix-vmemmap-accounting-underflow.patch
new file mode 100644 (file)
index 0000000..b1a2738
--- /dev/null
@@ -0,0 +1,171 @@
+From stable+bounces-288842-greg=kroah.com@vger.kernel.org Fri Jul 24 13:23:00 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 07:00:55 -0400
+Subject: mm/sparse-vmemmap: fix vmemmap accounting underflow
+To: stable@vger.kernel.org
+Cc: Muchun Song <songmuchun@bytedance.com>, "Mike Rapoport (Microsoft)" <rppt@kernel.org>, Oscar Salvador <osalvador@suse.de>, "David Hildenbrand (Arm)" <david@kernel.org>, "Liam R. Howlett" <liam@infradead.org>, "Aneesh Kumar K.V" <aneesh.kumar@linux.ibm.com>, Joao Martins <joao.m.martins@oracle.com>, Lorenzo Stoakes <ljs@kernel.org>, Madhavan Srinivasan <maddy@linux.ibm.com>, Michael Ellerman <mpe@ellerman.id.au>, Michal Hocko <mhocko@suse.com>, Nicholas Piggin <npiggin@gmail.com>, Suren Baghdasaryan <surenb@google.com>, Vlastimil Babka <vbabka@kernel.org>, Andrew Morton <akpm@linux-foundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260724110055.3984461-1-sashal@kernel.org>
+
+From: Muchun Song <songmuchun@bytedance.com>
+
+[ Upstream commit c373f7f98e6ad591c85d40548cf8b6443be69311 ]
+
+Patch series "mm: Fix vmemmap optimization accounting and initialization",
+v8.
+
+The series fixes several bugs in vmemmap optimization, mainly around
+incorrect page accounting and memmap initialization in DAX and memory
+hotplug paths.  It also fixes pageblock migratetype initialization and
+struct page initialization for ZONE_DEVICE compound pages.
+
+Patches 1-4 fix vmemmap accounting issues.  Patch 1 fixes an accounting
+underflow in the section activation failure path by moving vmemmap page
+accounting into the lower-level allocation and freeing helpers.  Patch 2
+fixes incorrect altmap passing in the memory hotplug error path.  Patch 3
+passes pgmap through memory deactivation paths so the teardown side can
+determine whether vmemmap optimization was in effect.  Patch 4 uses that
+information to account the optimized DAX vmemmap size correctly.
+
+Patches 5-6 fix initialization issues in mm/mm_init.  One makes sure all
+pageblocks in ZONE_DEVICE compound pages get their migratetype
+initialized.  The other fixes a case where DAX memory hotplug reuses an
+unoptimized early-section memmap while compound_nr_pages() still assumes
+vmemmap optimization, leaving tail struct pages uninitialized.
+
+This patch (of 6):
+
+In section_activate(), if populate_section_memmap() fails, the error
+handling path calls section_deactivate() to roll back the state.  This
+causes a vmemmap accounting imbalance.
+
+Since commit c3576889d87b ("mm: fix accounting of memmap pages"), memmap
+pages are accounted for only after populate_section_memmap() succeeds.
+However, the failure path unconditionally calls section_deactivate(),
+which decreases the vmemmap count.  Consequently, a failure in
+populate_section_memmap() leads to an accounting underflow, incorrectly
+reducing the system's tracked vmemmap usage.
+
+Fix this more thoroughly by moving all accounting calls into the lower
+level functions that actually perform the vmemmap allocation and freeing:
+
+  - populate_section_memmap() accounts for newly allocated vmemmap pages -
+depopulate_section_memmap() unaccounts when vmemmap is freed
+
+This ensures proper accounting in all code paths, including error handling
+and early section cases.
+
+Link: https://lore.kernel.org/20260428081855.1249045-1-songmuchun@bytedance.com
+Link: https://lore.kernel.org/20260428081855.1249045-2-songmuchun@bytedance.com
+Fixes: c3576889d87b ("mm: fix accounting of memmap pages")
+Signed-off-by: Muchun Song <songmuchun@bytedance.com>
+Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
+Acked-by: Oscar Salvador <osalvador@suse.de>
+Acked-by: David Hildenbrand (Arm) <david@kernel.org>
+Acked-by: Liam R. Howlett <liam@infradead.org>
+Cc: "Aneesh Kumar K.V" <aneesh.kumar@linux.ibm.com>
+Cc: Joao Martins <joao.m.martins@oracle.com>
+Cc: Lorenzo Stoakes <ljs@kernel.org>
+Cc: Madhavan Srinivasan <maddy@linux.ibm.com>
+Cc: Michael Ellerman <mpe@ellerman.id.au>
+Cc: Michal Hocko <mhocko@suse.com>
+Cc: Nicholas Piggin <npiggin@gmail.com>
+Cc: Suren Baghdasaryan <surenb@google.com>
+Cc: Vlastimil Babka <vbabka@kernel.org>
+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/sparse.c |   32 ++++++++++++++++++++++----------
+ 1 file changed, 22 insertions(+), 10 deletions(-)
+
+--- a/mm/sparse.c
++++ b/mm/sparse.c
+@@ -633,7 +633,12 @@ static struct page * __meminit populate_
+               unsigned long nr_pages, int nid, struct vmem_altmap *altmap,
+               struct dev_pagemap *pgmap)
+ {
+-      return __populate_section_memmap(pfn, nr_pages, nid, altmap, pgmap);
++      struct page *page = __populate_section_memmap(pfn, nr_pages, nid, altmap,
++                                                    pgmap);
++
++      memmap_pages_add(DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE));
++
++      return page;
+ }
+ static void depopulate_section_memmap(unsigned long pfn, unsigned long nr_pages,
+@@ -642,13 +647,17 @@ static void depopulate_section_memmap(un
+       unsigned long start = (unsigned long) pfn_to_page(pfn);
+       unsigned long end = start + nr_pages * sizeof(struct page);
++      memmap_pages_add(-1L * (DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE)));
+       vmemmap_free(start, end, altmap);
+ }
++
+ static void free_map_bootmem(struct page *memmap)
+ {
+       unsigned long start = (unsigned long)memmap;
+       unsigned long end = (unsigned long)(memmap + PAGES_PER_SECTION);
++      memmap_boot_pages_add(-1L * (DIV_ROUND_UP(PAGES_PER_SECTION * sizeof(struct page),
++                                                PAGE_SIZE)));
+       vmemmap_free(start, end, NULL);
+ }
+@@ -705,13 +714,18 @@ static struct page * __meminit populate_
+               unsigned long nr_pages, int nid, struct vmem_altmap *altmap,
+               struct dev_pagemap *pgmap)
+ {
+-      return kvmalloc_node(array_size(sizeof(struct page),
+-                                      PAGES_PER_SECTION), GFP_KERNEL, nid);
++      struct page *page = kvmalloc_node(array_size(sizeof(struct page),
++                                              PAGES_PER_SECTION), GFP_KERNEL, nid);
++
++      memmap_pages_add(DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE));
++
++      return page;
+ }
+ static void depopulate_section_memmap(unsigned long pfn, unsigned long nr_pages,
+               struct vmem_altmap *altmap)
+ {
++      memmap_pages_add(-1L * (DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE)));
+       kvfree(pfn_to_page(pfn));
+ }
+@@ -724,6 +738,9 @@ static void free_map_bootmem(struct page
+       nr_pages = PAGE_ALIGN(PAGES_PER_SECTION * sizeof(struct page))
+               >> PAGE_SHIFT;
++      memmap_boot_pages_add(-1L * (DIV_ROUND_UP(PAGES_PER_SECTION * sizeof(struct page),
++                                                PAGE_SIZE)));
++
+       for (i = 0; i < nr_pages; i++, page++) {
+               magic = page->index;
+@@ -817,14 +834,10 @@ static void section_deactivate(unsigned
+        * The memmap of early sections is always fully populated. See
+        * section_activate() and pfn_valid() .
+        */
+-      if (!section_is_early) {
+-              memmap_pages_add(-1L * (DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE)));
++      if (!section_is_early)
+               depopulate_section_memmap(pfn, nr_pages, altmap);
+-      } else if (memmap) {
+-              memmap_boot_pages_add(-1L * (DIV_ROUND_UP(nr_pages * sizeof(struct page),
+-                                                        PAGE_SIZE)));
++      else if (memmap)
+               free_map_bootmem(memmap);
+-      }
+       if (empty)
+               ms->section_mem_map = (unsigned long)NULL;
+@@ -869,7 +882,6 @@ static struct page * __meminit section_a
+               section_deactivate(pfn, nr_pages, altmap);
+               return ERR_PTR(-ENOMEM);
+       }
+-      memmap_pages_add(DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE));
+       return memmap;
+ }
diff --git a/queue-6.12/mmc-vub300-fix-use-after-free-on-disconnect.patch b/queue-6.12/mmc-vub300-fix-use-after-free-on-disconnect.patch
new file mode 100644 (file)
index 0000000..09a464d
--- /dev/null
@@ -0,0 +1,90 @@
+From stable+bounces-289742-greg=kroah.com@vger.kernel.org Tue Jul 28 01:37:22 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 19:34:42 -0400
+Subject: mmc: vub300: fix use-after-free on disconnect
+To: stable@vger.kernel.org
+Cc: Johan Hovold <johan@kernel.org>, Binbin Zhou <zhoubinbin@loongson.cn>, Ulf Hansson <ulf.hansson@linaro.org>
+Message-ID: <20260727233445.2426041-1-sashal@kernel.org>
+
+From: Johan Hovold <johan@kernel.org>
+
+The vub300 driver maintains an explicit reference count for the
+controller and its driver data and the last reference can in theory be
+dropped after the driver has been unbound.
+
+This specifically means that the controller allocation must not be
+device managed as that can lead to use-after-free.
+
+Note that the lifetime is currently also incorrectly tied the parent USB
+device rather than interface, which can lead to memory leaks if the
+driver is unbound without its device being physically disconnected (e.g.
+on probe deferral).
+
+Fix both issues by reverting to non-managed allocation of the controller.
+
+Fixes: dcfdd698dc52 ("mmc: vub300: Use devm_mmc_alloc_host() helper")
+Cc: stable@vger.kernel.org # 6.17+
+Cc: Binbin Zhou <zhoubinbin@loongson.cn>
+Signed-off-by: Johan Hovold <johan@kernel.org>
+Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
+(cherry picked from commit 8f4d20a710225ec7a565f6a0459862d3b1f32330)
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/mmc/host/vub300.c |   12 ++++++------
+ 1 file changed, 6 insertions(+), 6 deletions(-)
+
+--- a/drivers/mmc/host/vub300.c
++++ b/drivers/mmc/host/vub300.c
+@@ -2279,7 +2279,7 @@ static int vub300_probe(struct usb_inter
+               dev_err(&vub300->udev->dev,
+                   "Could not find two sets of bulk-in/out endpoint pairs\n");
+               retval = -EINVAL;
+-              goto error5;
++              goto err_free_host;
+       }
+       retval =
+               usb_control_msg(vub300->udev, usb_rcvctrlpipe(vub300->udev, 0),
+@@ -2288,14 +2288,14 @@ static int vub300_probe(struct usb_inter
+                               0x0000, 0x0000, &vub300->hc_info,
+                               sizeof(vub300->hc_info), 1000);
+       if (retval < 0)
+-              goto error5;
++              goto err_free_host;
+       retval =
+               usb_control_msg(vub300->udev, usb_sndctrlpipe(vub300->udev, 0),
+                               SET_ROM_WAIT_STATES,
+                               USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+                               firmware_rom_wait_states, 0x0000, NULL, 0, 1000);
+       if (retval < 0)
+-              goto error5;
++              goto err_free_host;
+       dev_info(&vub300->udev->dev,
+                "operating_mode = %s %s %d MHz %s %d byte USB packets\n",
+                (mmc->caps & MMC_CAP_SDIO_IRQ) ? "IRQs" : "POLL",
+@@ -2310,7 +2310,7 @@ static int vub300_probe(struct usb_inter
+                               0x0000, 0x0000, &vub300->system_port_status,
+                               sizeof(vub300->system_port_status), 1000);
+       if (retval < 0) {
+-              goto error5;
++              goto err_free_host;
+       } else if (sizeof(vub300->system_port_status) == retval) {
+               vub300->card_present =
+                       (0x0001 & vub300->system_port_status.port_flags) ? 1 : 0;
+@@ -2318,7 +2318,7 @@ static int vub300_probe(struct usb_inter
+                       (0x0010 & vub300->system_port_status.port_flags) ? 1 : 0;
+       } else {
+               retval = -EINVAL;
+-              goto error5;
++              goto err_free_host;
+       }
+       usb_set_intfdata(interface, vub300);
+       INIT_DELAYED_WORK(&vub300->pollwork, vub300_pollwork_thread);
+@@ -2348,7 +2348,7 @@ static int vub300_probe(struct usb_inter
+       return 0;
+ error6:
+       del_timer_sync(&vub300->inactivity_timer);
+-error5:
++err_free_host:
+       mmc_free_host(mmc);
+       /*
+        * and hence also frees vub300
diff --git a/queue-6.12/mmc-vub300-fix-use-after-free-on-probe-failure.patch b/queue-6.12/mmc-vub300-fix-use-after-free-on-probe-failure.patch
new file mode 100644 (file)
index 0000000..0473f0f
--- /dev/null
@@ -0,0 +1,87 @@
+From stable+bounces-289744-greg=kroah.com@vger.kernel.org Tue Jul 28 01:38:01 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 19:34:44 -0400
+Subject: mmc: vub300: fix use-after-free on probe failure
+To: stable@vger.kernel.org
+Cc: Guangshuo Li <lgs201920130244@gmail.com>, Johan Hovold <johan@kernel.org>, Ulf Hansson <ulfh@kernel.org>
+Message-ID: <20260727233445.2426041-3-sashal@kernel.org>
+
+From: Guangshuo Li <lgs201920130244@gmail.com>
+
+The vub300 driver lifetime-manages its controller state using
+vub300->kref, with vub300_delete() freeing the mmc host when the last
+reference is dropped. The probe error path after the inactivity timer has
+been armed still bypasses that lifetime rule, however, and falls through
+to mmc_free_host() directly if mmc_add_host() fails.
+
+The race window is between arming the inactivity timer and reaching the
+probe error unwind after mmc_add_host() fails:
+
+        probe thread                     timer/workqueue
+        ------------                     ---------------
+        kref_init(&vub300->kref)         ref = 1
+        kref_get(&vub300->kref)          ref = 2, timer ref
+        add_timer(inactivity_timer)      fires after one second
+        |
+        |   race window
+        |<---------------------------------------------------->
+        |
+        mmc_add_host(mmc)
+                                         inactivity timer fires
+                                         vub300_queue_dead_work()
+                                           kref_get()          ref = 3
+                                           queue_work(deadwork)
+        mmc_add_host() fails
+        timer_delete_sync()
+        mmc_free_host(mmc)
+          frees vub300
+                                         deadwork runs
+                                           use-after-free
+
+The inactivity timeout is one second, so this would require
+mmc_add_host() to both fail and take more than one second to do so. This
+is unlikely to happen in practice, but the error path is still wrong.
+
+timer_delete_sync() only waits for the timer callback itself. It does
+not flush deadwork that the callback may already have queued. As a
+result, queued deadwork can still hold a kref while the probe error path
+directly frees the backing mmc host, including the vub300 storage.
+
+Fix this by using the same lifetime mechanism as disconnect. Clear
+vub300->interface so that the timer callback and any queued deadwork
+return early and drop their references, then drop the initial probe
+reference and return without falling through to err_free_host.
+
+Fixes: 0613ad2401f8 ("mmc: vub300: fix return value check of mmc_add_host()")
+Signed-off-by: Guangshuo Li <lgs201920130244@gmail.com>
+Reviewed-by: Johan Hovold <johan@kernel.org>
+Cc: stable@vger.kernel.org
+Signed-off-by: Ulf Hansson <ulfh@kernel.org>
+(cherry picked from commit a3b5f242997a3be7404112fd48784881560aea57)
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/mmc/host/vub300.c |   10 +++++++---
+ 1 file changed, 7 insertions(+), 3 deletions(-)
+
+--- a/drivers/mmc/host/vub300.c
++++ b/drivers/mmc/host/vub300.c
+@@ -2343,12 +2343,16 @@ static int vub300_probe(struct usb_inter
+                        interface_to_InterfaceNumber(interface));
+       retval = mmc_add_host(mmc);
+       if (retval)
+-              goto err_delete_timer;
++              goto err_stop_io;
+       return 0;
+-err_delete_timer:
+-      timer_delete_sync(&vub300->inactivity_timer);
++err_stop_io:
++      vub300->interface = NULL;
++      kref_put(&vub300->kref, vub300_delete);
++
++      return retval;
++
+ err_free_host:
+       mmc_free_host(mmc);
+       /*
diff --git a/queue-6.12/mmc-vub300-rename-probe-error-labels.patch b/queue-6.12/mmc-vub300-rename-probe-error-labels.patch
new file mode 100644 (file)
index 0000000..b8ccb85
--- /dev/null
@@ -0,0 +1,79 @@
+From stable+bounces-289743-greg=kroah.com@vger.kernel.org Tue Jul 28 01:34:53 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 19:34:43 -0400
+Subject: mmc: vub300: rename probe error labels
+To: stable@vger.kernel.org
+Cc: Johan Hovold <johan@kernel.org>, Ulf Hansson <ulf.hansson@linaro.org>
+Message-ID: <20260727233445.2426041-2-sashal@kernel.org>
+
+From: Johan Hovold <johan@kernel.org>
+
+Error labels should be named after what they do.
+
+Rename the probe error labels.
+
+Signed-off-by: Johan Hovold <johan@kernel.org>
+Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
+(cherry picked from commit 5b8b35d6f4fa758dd5e8ae18526ea1c73f6787e0)
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/mmc/host/vub300.c |   20 +++++++++++---------
+ 1 file changed, 11 insertions(+), 9 deletions(-)
+
+--- a/drivers/mmc/host/vub300.c
++++ b/drivers/mmc/host/vub300.c
+@@ -2114,19 +2114,19 @@ static int vub300_probe(struct usb_inter
+       command_out_urb = usb_alloc_urb(0, GFP_KERNEL);
+       if (!command_out_urb) {
+               retval = -ENOMEM;
+-              goto error0;
++              goto err_put_udev;
+       }
+       command_res_urb = usb_alloc_urb(0, GFP_KERNEL);
+       if (!command_res_urb) {
+               retval = -ENOMEM;
+-              goto error1;
++              goto err_free_out_urb;
+       }
+       /* this also allocates memory for our VUB300 mmc host device */
+       mmc = mmc_alloc_host(sizeof(struct vub300_mmc_host), &udev->dev);
+       if (!mmc) {
+               retval = -ENOMEM;
+               dev_err(&udev->dev, "not enough memory for the mmc_host\n");
+-              goto error4;
++              goto err_free_res_urb;
+       }
+       /* MMC core transfer sizes tunable parameters */
+       mmc->caps = 0;
+@@ -2343,23 +2343,25 @@ static int vub300_probe(struct usb_inter
+                        interface_to_InterfaceNumber(interface));
+       retval = mmc_add_host(mmc);
+       if (retval)
+-              goto error6;
++              goto err_delete_timer;
+       return 0;
+-error6:
+-      del_timer_sync(&vub300->inactivity_timer);
++
++err_delete_timer:
++      timer_delete_sync(&vub300->inactivity_timer);
+ err_free_host:
+       mmc_free_host(mmc);
+       /*
+        * and hence also frees vub300
+        * which is contained at the end of struct mmc
+        */
+-error4:
++err_free_res_urb:
+       usb_free_urb(command_res_urb);
+-error1:
++err_free_out_urb:
+       usb_free_urb(command_out_urb);
+-error0:
++err_put_udev:
+       usb_put_dev(udev);
++
+       return retval;
+ }
diff --git a/queue-6.12/mtd-maps-vmu-flash-fix-fault-in-unaligned-fixup.patch b/queue-6.12/mtd-maps-vmu-flash-fix-fault-in-unaligned-fixup.patch
new file mode 100644 (file)
index 0000000..680146c
--- /dev/null
@@ -0,0 +1,58 @@
+From stable+bounces-289091-greg=kroah.com@vger.kernel.org Sat Jul 25 13:02:56 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sat, 25 Jul 2026 07:02:46 -0400
+Subject: mtd: maps: vmu-flash: fix fault in unaligned fixup
+To: stable@vger.kernel.org
+Cc: Florian Fuchs <fuchsfl@gmail.com>, Miquel Raynal <miquel.raynal@bootlin.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260725110246.1858961-1-sashal@kernel.org>
+
+From: Florian Fuchs <fuchsfl@gmail.com>
+
+[ Upstream commit 79d1661502c6e4b6f626185cef72cf2fa78116e1 ]
+
+Use kzalloc_obj() / kzalloc_objs() to allocate the memcard structs,
+instead of kmalloc_obj() / kmalloc_objs() to prevent access to
+uninitialized data.
+
+Fixes runtime error: Fault in unaligned fixup: 0000 [#1] at
+mtd_get_fact_prot_info.
+
+Fixes: 47a72688fae7 ("mtd: flash mapping support for Dreamcast VMU.")
+Cc: stable@vger.kernel.org
+Signed-off-by: Florian Fuchs <fuchsfl@gmail.com>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/mtd/maps/vmu-flash.c |    8 +++-----
+ 1 file changed, 3 insertions(+), 5 deletions(-)
+
+--- a/drivers/mtd/maps/vmu-flash.c
++++ b/drivers/mtd/maps/vmu-flash.c
+@@ -610,7 +610,7 @@ static int vmu_connect(struct maple_devi
+       basic_flash_data = be32_to_cpu(mdev->devinfo.function_data[c - 1]);
+-      card = kmalloc(sizeof(struct memcard), GFP_KERNEL);
++      card = kzalloc_obj(struct memcard);
+       if (!card) {
+               error = -ENOMEM;
+               goto fail_nomem;
+@@ -628,15 +628,13 @@ static int vmu_connect(struct maple_devi
+       * Not sure there are actually any multi-partition devices in the
+       * real world, but the hardware supports them, so, so will we
+       */
+-      card->parts = kmalloc_array(card->partitions, sizeof(struct vmupart),
+-                                  GFP_KERNEL);
++      card->parts = kzalloc_objs(struct vmupart, card->partitions);
+       if (!card->parts) {
+               error = -ENOMEM;
+               goto fail_partitions;
+       }
+-      card->mtd = kmalloc_array(card->partitions, sizeof(struct mtd_info),
+-                                GFP_KERNEL);
++      card->mtd = kzalloc_objs(struct mtd_info, card->partitions);
+       if (!card->mtd) {
+               error = -ENOMEM;
+               goto fail_mtd_info;
diff --git a/queue-6.12/net-ip6_tunnel-require-cap_net_admin-in-the-device-netns-for-changelink.patch b/queue-6.12/net-ip6_tunnel-require-cap_net_admin-in-the-device-netns-for-changelink.patch
new file mode 100644 (file)
index 0000000..ac81dae
--- /dev/null
@@ -0,0 +1,48 @@
+From stable+bounces-289987-greg=kroah.com@vger.kernel.org Tue Jul 28 15:25:44 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 09:19:53 -0400
+Subject: net: ip6_tunnel: require CAP_NET_ADMIN in the device netns for changelink
+To: stable@vger.kernel.org
+Cc: Maoyi Xie <maoyixie.tju@gmail.com>, Xiao Liang <shaw.leon@gmail.com>, Kuniyuki Iwashima <kuniyu@google.com>, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260728131953.3953668-1-sashal@kernel.org>
+
+From: Maoyi Xie <maoyixie.tju@gmail.com>
+
+[ Upstream commit 2496fa0b7d180b3ad356b514e7ff93bb14e6140a ]
+
+ip6_tnl_changelink() operates on at most two netns, dev_net(dev) and the
+tunnel link netns t->net. They differ once the device is created in or
+moved to a netns other than the one the request runs in. The rtnl
+changelink path checks CAP_NET_ADMIN only against dev_net(dev), so a
+caller privileged there but not in t->net can rewrite a tunnel that
+lives in t->net.
+
+Gate ip6_tnl_changelink() on rtnl_dev_link_net_capable() at its top,
+before any attribute is parsed.
+
+Reported-by: Xiao Liang <shaw.leon@gmail.com>
+Closes: https://lore.kernel.org/netdev/CABAhCOSzP1vaThGV35_VnsRCb=87_CPjPVsTHbq905k8A+BuUg@mail.gmail.com/
+Fixes: 0bd8762824e7 ("ip6tnl: add x-netns support")
+Cc: stable@vger.kernel.org
+Signed-off-by: Maoyi Xie <maoyixie.tju@gmail.com>
+Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
+Link: https://patch.msgid.link/20260612085941.3158249-5-maoyixie.tju@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/ipv6/ip6_tunnel.c |    3 +++
+ 1 file changed, 3 insertions(+)
+
+--- a/net/ipv6/ip6_tunnel.c
++++ b/net/ipv6/ip6_tunnel.c
+@@ -2053,6 +2053,9 @@ static int ip6_tnl_changelink(struct net
+       struct ip6_tnl_net *ip6n = net_generic(net, ip6_tnl_net_id);
+       struct ip_tunnel_encap ipencap;
++      if (!rtnl_dev_link_net_capable(dev, net))
++              return -EPERM;
++
+       if (dev == ip6n->fb_tnl_dev)
+               return -EINVAL;
diff --git a/queue-6.12/net-ipa-fix-smem-state-handle-leaks-in-smp2p-init.patch b/queue-6.12/net-ipa-fix-smem-state-handle-leaks-in-smp2p-init.patch
new file mode 100644 (file)
index 0000000..32f1ee5
--- /dev/null
@@ -0,0 +1,91 @@
+From stable+bounces-290233-greg=kroah.com@vger.kernel.org Wed Jul 29 04:47:28 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 22:40:04 -0400
+Subject: net: ipa: fix SMEM state handle leaks in SMP2P init
+To: stable@vger.kernel.org
+Cc: Haoxiang Li <haoxiang_li2024@163.com>, Larysa Zaremba <larysa.zaremba@intel.com>, Alex Elder <elder@riscstar.com>, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260729024004.1127220-1-sashal@kernel.org>
+
+From: Haoxiang Li <haoxiang_li2024@163.com>
+
+[ Upstream commit 96ca1e658ae459276292bd6d971ab5d8c7e0379a ]
+
+ipa_smp2p_init() acquires two Qualcomm SMEM state handles with
+qcom_smem_state_get(). However, neither the init error paths
+nor ipa_smp2p_exit() release them.
+
+Release both handles with qcom_smem_state_put() in the init
+error paths and in ipa_smp2p_exit().
+
+Fixes: 530f9216a953 ("soc: qcom: ipa: AP/modem communications")
+Cc: stable@vger.kernel.org
+Signed-off-by: Haoxiang Li <haoxiang_li2024@163.com>
+Reviewed-by: Larysa Zaremba <larysa.zaremba@intel.com>
+Reviewed-by: Alex Elder <elder@riscstar.com>
+Link: https://patch.msgid.link/20260624065955.2822765-1-haoxiang_li2024@163.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+[ kzalloc_obj() context line kept as kzalloc(sizeof(*smp2p), GFP_KERNEL) since ipa_smp2p.c was not yet converted in this tree ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/net/ipa/ipa_smp2p.c |   30 ++++++++++++++++++++++--------
+ 1 file changed, 22 insertions(+), 8 deletions(-)
+
+--- a/drivers/net/ipa/ipa_smp2p.c
++++ b/drivers/net/ipa/ipa_smp2p.c
+@@ -234,19 +234,27 @@ ipa_smp2p_init(struct ipa *ipa, struct p
+                                         &valid_bit);
+       if (IS_ERR(valid_state))
+               return PTR_ERR(valid_state);
+-      if (valid_bit >= 32)            /* BITS_PER_U32 */
+-              return -EINVAL;
++      if (valid_bit >= 32) {          /* BITS_PER_U32 */
++              ret = -EINVAL;
++              goto err_valid_state_put;
++      }
+       enabled_state = qcom_smem_state_get(dev, "ipa-clock-enabled",
+                                           &enabled_bit);
+-      if (IS_ERR(enabled_state))
+-              return PTR_ERR(enabled_state);
+-      if (enabled_bit >= 32)          /* BITS_PER_U32 */
+-              return -EINVAL;
++      if (IS_ERR(enabled_state)) {
++              ret = PTR_ERR(enabled_state);
++              goto err_valid_state_put;
++      }
++      if (enabled_bit >= 32) {                /* BITS_PER_U32 */
++              ret = -EINVAL;
++              goto err_enabled_state_put;
++      }
+       smp2p = kzalloc(sizeof(*smp2p), GFP_KERNEL);
+-      if (!smp2p)
+-              return -ENOMEM;
++      if (!smp2p) {
++              ret = -ENOMEM;
++              goto err_enabled_state_put;
++      }
+       smp2p->ipa = ipa;
+@@ -291,6 +299,10 @@ err_null_smp2p:
+       ipa->smp2p = NULL;
+       mutex_destroy(&smp2p->mutex);
+       kfree(smp2p);
++err_enabled_state_put:
++      qcom_smem_state_put(enabled_state);
++err_valid_state_put:
++      qcom_smem_state_put(valid_state);
+       return ret;
+ }
+@@ -307,6 +319,8 @@ void ipa_smp2p_exit(struct ipa *ipa)
+       ipa_smp2p_power_release(ipa);
+       ipa->smp2p = NULL;
+       mutex_destroy(&smp2p->mutex);
++      qcom_smem_state_put(smp2p->enabled_state);
++      qcom_smem_state_put(smp2p->valid_state);
+       kfree(smp2p);
+ }
diff --git a/queue-6.12/net-macb-drop-in-flight-tx-skbs-on-close.patch b/queue-6.12/net-macb-drop-in-flight-tx-skbs-on-close.patch
new file mode 100644 (file)
index 0000000..d87bf32
--- /dev/null
@@ -0,0 +1,64 @@
+From stable+bounces-290228-greg=kroah.com@vger.kernel.org Wed Jul 29 04:30:20 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 22:30:02 -0400
+Subject: net: macb: drop in-flight Tx SKBs on close
+To: stable@vger.kernel.org
+Cc: "Théo Lebrun" <theo.lebrun@bootlin.com>, "Nicolai Buchwitz" <nb@tipi-net.de>, "Paolo Abeni" <pabeni@redhat.com>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260729023002.1113287-1-sashal@kernel.org>
+
+From: Théo Lebrun <theo.lebrun@bootlin.com>
+
+[ Upstream commit 27f575836cfebbf872dec020428742b10650a955 ]
+
+The MACB driver has since forever leaked the outgoing SKBs that
+have not yet been marked as completed. They live in queue->tx_skb
+which gets freed without remorse nor checking.
+
+macb_free_consistent() gets called in a few codepaths, but only close will
+trigger the added expressions. In macb_open() and macb_alloc_consistent()
+failure cases, queues' tx_skb just got allocated and are empty.
+
+Fixes: 89e5785fc8a6 ("[PATCH] Atmel MACB ethernet driver")
+Cc: stable@vger.kernel.org
+Reviewed-by: Nicolai Buchwitz <nb@tipi-net.de>
+Signed-off-by: Théo Lebrun <theo.lebrun@bootlin.com>
+Link: https://patch.msgid.link/20260702-macb-drop-tx-v4-1-1c833eebdbc8@bootlin.com
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+[ adjusted context to free each queue's Tx/Rx rings individually since 6.12 lacks the single-dma_alloc_coherent refactor ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/net/ethernet/cadence/macb_main.c |   22 ++++++++++++++++++++--
+ 1 file changed, 20 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/ethernet/cadence/macb_main.c
++++ b/drivers/net/ethernet/cadence/macb_main.c
+@@ -2582,8 +2582,26 @@ static void macb_free_consistent(struct
+       bp->macbgem_ops.mog_free_rx_buffers(bp);
+       for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) {
+-              kfree(queue->tx_skb);
+-              queue->tx_skb = NULL;
++              if (queue->tx_skb) {
++                      unsigned int dropped = 0, tail;
++
++                      for (tail = queue->tx_tail; tail != queue->tx_head;
++                           tail++) {
++                              if (macb_tx_skb(queue, tail)->skb)
++                                      dropped++;
++                              macb_tx_unmap(bp, macb_tx_skb(queue, tail), 0);
++                      }
++
++                      queue->stats.tx_dropped += dropped;
++                      bp->dev->stats.tx_dropped += dropped;
++
++                      kfree(queue->tx_skb);
++                      queue->tx_skb = NULL;
++              }
++
++              queue->tx_head = 0;
++              queue->tx_tail = 0;
++
+               if (queue->tx_ring) {
+                       size = TX_RING_BYTES(bp) + bp->tx_bd_rd_prefetch;
+                       dma_free_coherent(&bp->pdev->dev, size,
diff --git a/queue-6.12/net-mana-validate-the-packet-length-reported-by-the-nic.patch b/queue-6.12/net-mana-validate-the-packet-length-reported-by-the-nic.patch
new file mode 100644 (file)
index 0000000..30a7943
--- /dev/null
@@ -0,0 +1,50 @@
+From stable+bounces-289819-greg=kroah.com@vger.kernel.org Tue Jul 28 05:02:11 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 23:02:04 -0400
+Subject: net: mana: Validate the packet length reported by the NIC
+To: stable@vger.kernel.org
+Cc: Dexuan Cui <decui@microsoft.com>, Haiyang Zhang <haiyangz@microsoft.com>, Paolo Abeni <pabeni@redhat.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260728030204.3123559-1-sashal@kernel.org>
+
+From: Dexuan Cui <decui@microsoft.com>
+
+[ Upstream commit 2e2a83b4998af4384e677d3b2ac08565274279bf ]
+
+Validate the packet length reported in the RX CQE before passing it
+to skb processing. The CQE is supplied by the NIC device and should
+not be blindly trusted.
+
+Cc: stable@vger.kernel.org
+Reviewed-by: Haiyang Zhang <haiyangz@microsoft.com>
+Signed-off-by: Dexuan Cui <decui@microsoft.com>
+Fixes: ca9c54d2d6a5 ("net: mana: Add a driver for Microsoft Azure Network Adapter (MANA)")
+Link: https://patch.msgid.link/20260702041237.617719-2-decui@microsoft.com
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/net/ethernet/microsoft/mana/mana_en.c |   13 +++++++++++++
+ 1 file changed, 13 insertions(+)
+
+--- a/drivers/net/ethernet/microsoft/mana/mana_en.c
++++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
+@@ -1724,6 +1724,19 @@ static void mana_process_rx_cqe(struct m
+       rxbuf_oob = &rxq->rx_oobs[curr];
+       WARN_ON_ONCE(rxbuf_oob->wqe_inf.wqe_size_in_bu != 1);
++      if (unlikely(pktlen > rxq->datasize)) {
++              /* Increase it even if mana_rx_skb() isn't called. */
++              rxq->rx_cq.work_done++;
++
++              ++ndev->stats.rx_dropped;
++              netdev_warn_once(ndev,
++                               "Dropped oversized RX packet: len=%u, datasize=%u\n",
++                               pktlen, rxq->datasize);
++
++              /* Reuse the RX buffer since rxbuf_oob is unchanged. */
++              goto drop;
++      }
++
+       mana_refill_rx_oob(dev, rxq, rxbuf_oob, &old_buf, &old_fp);
+       /* Unsuccessful refill will have old_buf == NULL.
diff --git a/queue-6.12/net-mlx5-hws-fix-matcher-leak-on-resize-target-setup-failure.patch b/queue-6.12/net-mlx5-hws-fix-matcher-leak-on-resize-target-setup-failure.patch
new file mode 100644 (file)
index 0000000..3c7831b
--- /dev/null
@@ -0,0 +1,52 @@
+From stable+bounces-290182-greg=kroah.com@vger.kernel.org Wed Jul 29 00:18:36 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 18:17:55 -0400
+Subject: net/mlx5: HWS, fix matcher leak on resize target setup failure
+To: stable@vger.kernel.org
+Cc: Dawei Feng <dawei.feng@seu.edu.cn>, Yevgeny Kliteynik <kliteyn@nvidia.com>, Tariq Toukan <tariqt@nvidia.com>, Paolo Abeni <pabeni@redhat.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260728221755.746200-2-sashal@kernel.org>
+
+From: Dawei Feng <dawei.feng@seu.edu.cn>
+
+[ Upstream commit bb09d0e64ecaa0aa0f7d1133a1696ed74dead295 ]
+
+hws_bwc_matcher_move() allocates a replacement matcher before setting it
+as the resize target. If mlx5hws_matcher_resize_set_target() fails, the
+replacement matcher is not attached anywhere and is leaked.
+
+Fix the leak by destroying the replacement matcher before returning from
+the resize-target failure path.
+
+The bug was first flagged by an experimental analysis tool we are
+developing for kernel memory-management bugs while analyzing
+v6.13-rc1. The tool is still under development and is not yet publicly
+available. Manual inspection confirms that the bug is still
+present in v7.1.1.
+
+An x86_64 allyesconfig build showed no new warnings. As we do not have a
+mlx5 HWS-capable device to test with, no runtime testing was able to be
+performed.
+
+Fixes: 2111bb970c78 ("net/mlx5: HWS, added backward-compatible API handling")
+Cc: stable@vger.kernel.org
+Signed-off-by: Dawei Feng <dawei.feng@seu.edu.cn>
+Reviewed-by: Yevgeny Kliteynik <kliteyn@nvidia.com>
+Acked-by: Tariq Toukan <tariqt@nvidia.com>
+Link: https://patch.msgid.link/20260629064049.3852759-1-dawei.feng@seu.edu.cn
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/net/ethernet/mellanox/mlx5/core/steering/hws/mlx5hws_bwc.c |    1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/mlx5hws_bwc.c
++++ b/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/mlx5hws_bwc.c
+@@ -225,6 +225,7 @@ static int hws_bwc_matcher_move(struct m
+       ret = mlx5hws_matcher_resize_set_target(old_matcher, new_matcher);
+       if (ret) {
+               mlx5hws_err(ctx, "Rehash error: failed setting resize target\n");
++              mlx5hws_matcher_destroy(new_matcher);
+               return ret;
+       }
diff --git a/queue-6.12/net-mlx5-hws-rearrange-to-prevent-forward-declaration.patch b/queue-6.12/net-mlx5-hws-rearrange-to-prevent-forward-declaration.patch
new file mode 100644 (file)
index 0000000..6cc26df
--- /dev/null
@@ -0,0 +1,405 @@
+From stable+bounces-290181-greg=kroah.com@vger.kernel.org Wed Jul 29 00:23:23 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 18:17:54 -0400
+Subject: net/mlx5: HWS, Rearrange to prevent forward declaration
+To: stable@vger.kernel.org
+Cc: Yevgeny Kliteynik <kliteyn@nvidia.com>, Mark Bloch <mbloch@nvidia.com>, Simon Horman <horms@kernel.org>, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260728221755.746200-1-sashal@kernel.org>
+
+From: Yevgeny Kliteynik <kliteyn@nvidia.com>
+
+[ Upstream commit 29063103f864fb63f7f7c436e670c5804df1b55b ]
+
+As a preparation for the following patch that will add support
+for shrinking empty matchers, rearrange the code to prevent
+forward declaration of functions.
+
+Signed-off-by: Yevgeny Kliteynik <kliteyn@nvidia.com>
+Signed-off-by: Mark Bloch <mbloch@nvidia.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Link: https://patch.msgid.link/20250703185431.445571-9-mbloch@nvidia.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Stable-dep-of: bb09d0e64eca ("net/mlx5: HWS, fix matcher leak on resize target setup failure")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/net/ethernet/mellanox/mlx5/core/steering/hws/mlx5hws_bwc.c |  354 +++++-----
+ 1 file changed, 177 insertions(+), 177 deletions(-)
+
+--- a/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/mlx5hws_bwc.c
++++ b/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/mlx5hws_bwc.c
+@@ -63,6 +63,183 @@ static void hws_bwc_matcher_init_attr(st
+       attr->max_num_of_at_attach = MLX5HWS_BWC_MATCHER_ATTACH_AT_NUM;
+ }
++static int hws_bwc_queue_poll(struct mlx5hws_context *ctx,
++                            u16 queue_id,
++                            u32 *pending_rules,
++                            bool drain)
++{
++      struct mlx5hws_flow_op_result comp[MLX5HWS_BWC_MATCHER_REHASH_BURST_TH];
++      u16 burst_th = hws_bwc_get_burst_th(ctx, queue_id);
++      bool got_comp = *pending_rules >= burst_th;
++      bool queue_full;
++      int err = 0;
++      int ret;
++      int i;
++
++      /* Check if there are any completions at all */
++      if (!got_comp && !drain)
++              return 0;
++
++      queue_full = mlx5hws_send_engine_full(&ctx->send_queue[queue_id]);
++      while (queue_full || ((got_comp || drain) && *pending_rules)) {
++              ret = mlx5hws_send_queue_poll(ctx, queue_id, comp, burst_th);
++              if (unlikely(ret < 0)) {
++                      mlx5hws_err(ctx, "BWC poll error: polling queue %d returned %d\n",
++                                  queue_id, ret);
++                      return -EINVAL;
++              }
++
++              if (ret) {
++                      (*pending_rules) -= ret;
++                      for (i = 0; i < ret; i++) {
++                              if (unlikely(comp[i].status != MLX5HWS_FLOW_OP_SUCCESS)) {
++                                      mlx5hws_err(ctx,
++                                                  "BWC poll error: polling queue %d returned completion with error\n",
++                                                  queue_id);
++                                      err = -EINVAL;
++                              }
++                      }
++                      queue_full = false;
++              }
++
++              got_comp = !!ret;
++      }
++
++      return err;
++}
++
++static int hws_bwc_matcher_move_all_simple(struct mlx5hws_bwc_matcher *bwc_matcher)
++{
++      struct mlx5hws_context *ctx = bwc_matcher->matcher->tbl->ctx;
++      u16 bwc_queues = mlx5hws_bwc_queues(ctx);
++      struct mlx5hws_bwc_rule **bwc_rules;
++      struct mlx5hws_rule_attr rule_attr;
++      u32 *pending_rules;
++      int i, j, ret = 0;
++      bool all_done;
++      u16 burst_th;
++
++      mlx5hws_bwc_rule_fill_attr(bwc_matcher, 0, 0, &rule_attr);
++
++      pending_rules = kcalloc(bwc_queues, sizeof(*pending_rules), GFP_KERNEL);
++      if (!pending_rules)
++              return -ENOMEM;
++
++      bwc_rules = kcalloc(bwc_queues, sizeof(*bwc_rules), GFP_KERNEL);
++      if (!bwc_rules) {
++              ret = -ENOMEM;
++              goto free_pending_rules;
++      }
++
++      for (i = 0; i < bwc_queues; i++) {
++              if (list_empty(&bwc_matcher->rules[i]))
++                      bwc_rules[i] = NULL;
++              else
++                      bwc_rules[i] = list_first_entry(&bwc_matcher->rules[i],
++                                                      struct mlx5hws_bwc_rule,
++                                                      list_node);
++      }
++
++      do {
++              all_done = true;
++
++              for (i = 0; i < bwc_queues; i++) {
++                      rule_attr.queue_id = mlx5hws_bwc_get_queue_id(ctx, i);
++                      burst_th = hws_bwc_get_burst_th(ctx, rule_attr.queue_id);
++
++                      for (j = 0; j < burst_th && bwc_rules[i]; j++) {
++                              rule_attr.burst = !!((j + 1) % burst_th);
++                              ret = mlx5hws_matcher_resize_rule_move(bwc_matcher->matcher,
++                                                                     bwc_rules[i]->rule,
++                                                                     &rule_attr);
++                              if (unlikely(ret)) {
++                                      mlx5hws_err(ctx,
++                                                  "Moving BWC rule failed during rehash (%d)\n",
++                                                  ret);
++                                      goto free_bwc_rules;
++                              }
++
++                              all_done = false;
++                              pending_rules[i]++;
++                              bwc_rules[i] = list_is_last(&bwc_rules[i]->list_node,
++                                                          &bwc_matcher->rules[i]) ?
++                                             NULL : list_next_entry(bwc_rules[i], list_node);
++
++                              ret = hws_bwc_queue_poll(ctx, rule_attr.queue_id,
++                                                       &pending_rules[i], false);
++                              if (unlikely(ret))
++                                      goto free_bwc_rules;
++                      }
++              }
++      } while (!all_done);
++
++      /* drain all the bwc queues */
++      for (i = 0; i < bwc_queues; i++) {
++              if (pending_rules[i]) {
++                      u16 queue_id = mlx5hws_bwc_get_queue_id(ctx, i);
++
++                      mlx5hws_send_engine_flush_queue(&ctx->send_queue[queue_id]);
++                      ret = hws_bwc_queue_poll(ctx, queue_id,
++                                               &pending_rules[i], true);
++                      if (unlikely(ret))
++                              goto free_bwc_rules;
++              }
++      }
++
++free_bwc_rules:
++      kfree(bwc_rules);
++free_pending_rules:
++      kfree(pending_rules);
++
++      return ret;
++}
++
++static int hws_bwc_matcher_move_all(struct mlx5hws_bwc_matcher *bwc_matcher)
++{
++      return hws_bwc_matcher_move_all_simple(bwc_matcher);
++}
++
++static int hws_bwc_matcher_move(struct mlx5hws_bwc_matcher *bwc_matcher)
++{
++      struct mlx5hws_context *ctx = bwc_matcher->matcher->tbl->ctx;
++      struct mlx5hws_matcher_attr matcher_attr = {0};
++      struct mlx5hws_matcher *old_matcher;
++      struct mlx5hws_matcher *new_matcher;
++      int ret;
++
++      hws_bwc_matcher_init_attr(&matcher_attr,
++                                bwc_matcher->priority,
++                                bwc_matcher->size_log);
++
++      old_matcher = bwc_matcher->matcher;
++      new_matcher = mlx5hws_matcher_create(old_matcher->tbl,
++                                           &bwc_matcher->mt, 1,
++                                           bwc_matcher->at,
++                                           bwc_matcher->num_of_at,
++                                           &matcher_attr);
++      if (!new_matcher) {
++              mlx5hws_err(ctx, "Rehash error: matcher creation failed\n");
++              return -ENOMEM;
++      }
++
++      ret = mlx5hws_matcher_resize_set_target(old_matcher, new_matcher);
++      if (ret) {
++              mlx5hws_err(ctx, "Rehash error: failed setting resize target\n");
++              return ret;
++      }
++
++      ret = hws_bwc_matcher_move_all(bwc_matcher);
++      if (ret) {
++              mlx5hws_err(ctx, "Rehash error: moving rules failed\n");
++              return -ENOMEM;
++      }
++
++      bwc_matcher->matcher = new_matcher;
++      mlx5hws_matcher_destroy(old_matcher);
++
++      return 0;
++}
++
+ int mlx5hws_bwc_matcher_create_simple(struct mlx5hws_bwc_matcher *bwc_matcher,
+                                     struct mlx5hws_table *table,
+                                     u32 priority,
+@@ -210,51 +387,6 @@ int mlx5hws_bwc_matcher_destroy(struct m
+       return 0;
+ }
+-static int hws_bwc_queue_poll(struct mlx5hws_context *ctx,
+-                            u16 queue_id,
+-                            u32 *pending_rules,
+-                            bool drain)
+-{
+-      struct mlx5hws_flow_op_result comp[MLX5HWS_BWC_MATCHER_REHASH_BURST_TH];
+-      u16 burst_th = hws_bwc_get_burst_th(ctx, queue_id);
+-      bool got_comp = *pending_rules >= burst_th;
+-      bool queue_full;
+-      int err = 0;
+-      int ret;
+-      int i;
+-
+-      /* Check if there are any completions at all */
+-      if (!got_comp && !drain)
+-              return 0;
+-
+-      queue_full = mlx5hws_send_engine_full(&ctx->send_queue[queue_id]);
+-      while (queue_full || ((got_comp || drain) && *pending_rules)) {
+-              ret = mlx5hws_send_queue_poll(ctx, queue_id, comp, burst_th);
+-              if (unlikely(ret < 0)) {
+-                      mlx5hws_err(ctx, "BWC poll error: polling queue %d returned %d\n",
+-                                  queue_id, ret);
+-                      return -EINVAL;
+-              }
+-
+-              if (ret) {
+-                      (*pending_rules) -= ret;
+-                      for (i = 0; i < ret; i++) {
+-                              if (unlikely(comp[i].status != MLX5HWS_FLOW_OP_SUCCESS)) {
+-                                      mlx5hws_err(ctx,
+-                                                  "BWC poll error: polling queue %d returned completion with error\n",
+-                                                  queue_id);
+-                                      err = -EINVAL;
+-                              }
+-                      }
+-                      queue_full = false;
+-              }
+-
+-              got_comp = !!ret;
+-      }
+-
+-      return err;
+-}
+-
+ void
+ mlx5hws_bwc_rule_fill_attr(struct mlx5hws_bwc_matcher *bwc_matcher,
+                          u16 bwc_queue_idx,
+@@ -556,138 +688,6 @@ hws_bwc_matcher_find_at(struct mlx5hws_b
+       return -1;
+ }
+-static int hws_bwc_matcher_move_all_simple(struct mlx5hws_bwc_matcher *bwc_matcher)
+-{
+-      struct mlx5hws_context *ctx = bwc_matcher->matcher->tbl->ctx;
+-      u16 bwc_queues = mlx5hws_bwc_queues(ctx);
+-      struct mlx5hws_bwc_rule **bwc_rules;
+-      struct mlx5hws_rule_attr rule_attr;
+-      u32 *pending_rules;
+-      int i, j, ret = 0;
+-      bool all_done;
+-      u16 burst_th;
+-
+-      mlx5hws_bwc_rule_fill_attr(bwc_matcher, 0, 0, &rule_attr);
+-
+-      pending_rules = kcalloc(bwc_queues, sizeof(*pending_rules), GFP_KERNEL);
+-      if (!pending_rules)
+-              return -ENOMEM;
+-
+-      bwc_rules = kcalloc(bwc_queues, sizeof(*bwc_rules), GFP_KERNEL);
+-      if (!bwc_rules) {
+-              ret = -ENOMEM;
+-              goto free_pending_rules;
+-      }
+-
+-      for (i = 0; i < bwc_queues; i++) {
+-              if (list_empty(&bwc_matcher->rules[i]))
+-                      bwc_rules[i] = NULL;
+-              else
+-                      bwc_rules[i] = list_first_entry(&bwc_matcher->rules[i],
+-                                                      struct mlx5hws_bwc_rule,
+-                                                      list_node);
+-      }
+-
+-      do {
+-              all_done = true;
+-
+-              for (i = 0; i < bwc_queues; i++) {
+-                      rule_attr.queue_id = mlx5hws_bwc_get_queue_id(ctx, i);
+-                      burst_th = hws_bwc_get_burst_th(ctx, rule_attr.queue_id);
+-
+-                      for (j = 0; j < burst_th && bwc_rules[i]; j++) {
+-                              rule_attr.burst = !!((j + 1) % burst_th);
+-                              ret = mlx5hws_matcher_resize_rule_move(bwc_matcher->matcher,
+-                                                                     bwc_rules[i]->rule,
+-                                                                     &rule_attr);
+-                              if (unlikely(ret)) {
+-                                      mlx5hws_err(ctx,
+-                                                  "Moving BWC rule failed during rehash (%d)\n",
+-                                                  ret);
+-                                      goto free_bwc_rules;
+-                              }
+-
+-                              all_done = false;
+-                              pending_rules[i]++;
+-                              bwc_rules[i] = list_is_last(&bwc_rules[i]->list_node,
+-                                                          &bwc_matcher->rules[i]) ?
+-                                             NULL : list_next_entry(bwc_rules[i], list_node);
+-
+-                              ret = hws_bwc_queue_poll(ctx, rule_attr.queue_id,
+-                                                       &pending_rules[i], false);
+-                              if (unlikely(ret))
+-                                      goto free_bwc_rules;
+-                      }
+-              }
+-      } while (!all_done);
+-
+-      /* drain all the bwc queues */
+-      for (i = 0; i < bwc_queues; i++) {
+-              if (pending_rules[i]) {
+-                      u16 queue_id = mlx5hws_bwc_get_queue_id(ctx, i);
+-
+-                      mlx5hws_send_engine_flush_queue(&ctx->send_queue[queue_id]);
+-                      ret = hws_bwc_queue_poll(ctx, queue_id,
+-                                               &pending_rules[i], true);
+-                      if (unlikely(ret))
+-                              goto free_bwc_rules;
+-              }
+-      }
+-
+-free_bwc_rules:
+-      kfree(bwc_rules);
+-free_pending_rules:
+-      kfree(pending_rules);
+-
+-      return ret;
+-}
+-
+-static int hws_bwc_matcher_move_all(struct mlx5hws_bwc_matcher *bwc_matcher)
+-{
+-      return hws_bwc_matcher_move_all_simple(bwc_matcher);
+-}
+-
+-static int hws_bwc_matcher_move(struct mlx5hws_bwc_matcher *bwc_matcher)
+-{
+-      struct mlx5hws_context *ctx = bwc_matcher->matcher->tbl->ctx;
+-      struct mlx5hws_matcher_attr matcher_attr = {0};
+-      struct mlx5hws_matcher *old_matcher;
+-      struct mlx5hws_matcher *new_matcher;
+-      int ret;
+-
+-      hws_bwc_matcher_init_attr(&matcher_attr,
+-                                bwc_matcher->priority,
+-                                bwc_matcher->size_log);
+-
+-      old_matcher = bwc_matcher->matcher;
+-      new_matcher = mlx5hws_matcher_create(old_matcher->tbl,
+-                                           &bwc_matcher->mt, 1,
+-                                           bwc_matcher->at,
+-                                           bwc_matcher->num_of_at,
+-                                           &matcher_attr);
+-      if (!new_matcher) {
+-              mlx5hws_err(ctx, "Rehash error: matcher creation failed\n");
+-              return -ENOMEM;
+-      }
+-
+-      ret = mlx5hws_matcher_resize_set_target(old_matcher, new_matcher);
+-      if (ret) {
+-              mlx5hws_err(ctx, "Rehash error: failed setting resize target\n");
+-              return ret;
+-      }
+-
+-      ret = hws_bwc_matcher_move_all(bwc_matcher);
+-      if (ret) {
+-              mlx5hws_err(ctx, "Rehash error: moving rules failed\n");
+-              return -ENOMEM;
+-      }
+-
+-      bwc_matcher->matcher = new_matcher;
+-      mlx5hws_matcher_destroy(old_matcher);
+-
+-      return 0;
+-}
+-
+ static int
+ hws_bwc_matcher_rehash_size(struct mlx5hws_bwc_matcher *bwc_matcher)
+ {
diff --git a/queue-6.12/netfilter-bitwise-rename-some-boolean-operation-functions.patch b/queue-6.12/netfilter-bitwise-rename-some-boolean-operation-functions.patch
new file mode 100644 (file)
index 0000000..7f9b0ee
--- /dev/null
@@ -0,0 +1,161 @@
+From stable+bounces-288424-greg=kroah.com@vger.kernel.org Thu Jul 23 19:05:45 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 12:54:16 -0400
+Subject: netfilter: bitwise: rename some boolean operation functions
+To: stable@vger.kernel.org
+Cc: Jeremy Sowden <jeremy@azazel.net>, Pablo Neira Ayuso <pablo@netfilter.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723165419.3390514-1-sashal@kernel.org>
+
+From: Jeremy Sowden <jeremy@azazel.net>
+
+[ Upstream commit a12143e6084c502fc3cfaa8b717bffc8c14cf806 ]
+
+In the next patch we add support for doing AND, OR and XOR operations
+directly in the kernel, so rename some functions and an enum constant
+related to mask-and-xor boolean operations.
+
+Signed-off-by: Jeremy Sowden <jeremy@azazel.net>
+Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
+Stable-dep-of: d07955dd34ec ("netfilter: nft_fib: reject fib expression on the netdev egress hook")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/uapi/linux/netfilter/nf_tables.h |   10 ++++++---
+ net/netfilter/nft_bitwise.c              |   34 +++++++++++++++----------------
+ 2 files changed, 24 insertions(+), 20 deletions(-)
+
+--- a/include/uapi/linux/netfilter/nf_tables.h
++++ b/include/uapi/linux/netfilter/nf_tables.h
+@@ -564,16 +564,20 @@ enum nft_immediate_attributes {
+ /**
+  * enum nft_bitwise_ops - nf_tables bitwise operations
+  *
+- * @NFT_BITWISE_BOOL: mask-and-xor operation used to implement NOT, AND, OR and
+- *                    XOR boolean operations
++ * @NFT_BITWISE_MASK_XOR: mask-and-xor operation used to implement NOT, AND, OR
++ *                        and XOR boolean operations
+  * @NFT_BITWISE_LSHIFT: left-shift operation
+  * @NFT_BITWISE_RSHIFT: right-shift operation
+  */
+ enum nft_bitwise_ops {
+-      NFT_BITWISE_BOOL,
++      NFT_BITWISE_MASK_XOR,
+       NFT_BITWISE_LSHIFT,
+       NFT_BITWISE_RSHIFT,
+ };
++/*
++ * Old name for NFT_BITWISE_MASK_XOR.  Retained for backwards-compatibility.
++ */
++#define NFT_BITWISE_BOOL NFT_BITWISE_MASK_XOR
+ /**
+  * enum nft_bitwise_attributes - nf_tables bitwise expression netlink attributes
+--- a/net/netfilter/nft_bitwise.c
++++ b/net/netfilter/nft_bitwise.c
+@@ -25,8 +25,8 @@ struct nft_bitwise {
+       struct nft_data         data;
+ };
+-static void nft_bitwise_eval_bool(u32 *dst, const u32 *src,
+-                                const struct nft_bitwise *priv)
++static void nft_bitwise_eval_mask_xor(u32 *dst, const u32 *src,
++                                    const struct nft_bitwise *priv)
+ {
+       unsigned int i;
+@@ -68,8 +68,8 @@ void nft_bitwise_eval(const struct nft_e
+       u32 *dst = &regs->data[priv->dreg];
+       switch (priv->op) {
+-      case NFT_BITWISE_BOOL:
+-              nft_bitwise_eval_bool(dst, src, priv);
++      case NFT_BITWISE_MASK_XOR:
++              nft_bitwise_eval_mask_xor(dst, src, priv);
+               break;
+       case NFT_BITWISE_LSHIFT:
+               nft_bitwise_eval_lshift(dst, src, priv);
+@@ -90,8 +90,8 @@ static const struct nla_policy nft_bitwi
+       [NFTA_BITWISE_DATA]     = { .type = NLA_NESTED },
+ };
+-static int nft_bitwise_init_bool(struct nft_bitwise *priv,
+-                               const struct nlattr *const tb[])
++static int nft_bitwise_init_mask_xor(struct nft_bitwise *priv,
++                                   const struct nlattr *const tb[])
+ {
+       struct nft_data_desc mask = {
+               .type   = NFT_DATA_VALUE,
+@@ -186,7 +186,7 @@ static int nft_bitwise_init(const struct
+       if (tb[NFTA_BITWISE_OP]) {
+               priv->op = ntohl(nla_get_be32(tb[NFTA_BITWISE_OP]));
+               switch (priv->op) {
+-              case NFT_BITWISE_BOOL:
++              case NFT_BITWISE_MASK_XOR:
+               case NFT_BITWISE_LSHIFT:
+               case NFT_BITWISE_RSHIFT:
+                       break;
+@@ -194,12 +194,12 @@ static int nft_bitwise_init(const struct
+                       return -EOPNOTSUPP;
+               }
+       } else {
+-              priv->op = NFT_BITWISE_BOOL;
++              priv->op = NFT_BITWISE_MASK_XOR;
+       }
+       switch(priv->op) {
+-      case NFT_BITWISE_BOOL:
+-              err = nft_bitwise_init_bool(priv, tb);
++      case NFT_BITWISE_MASK_XOR:
++              err = nft_bitwise_init_mask_xor(priv, tb);
+               break;
+       case NFT_BITWISE_LSHIFT:
+       case NFT_BITWISE_RSHIFT:
+@@ -210,8 +210,8 @@ static int nft_bitwise_init(const struct
+       return err;
+ }
+-static int nft_bitwise_dump_bool(struct sk_buff *skb,
+-                               const struct nft_bitwise *priv)
++static int nft_bitwise_dump_mask_xor(struct sk_buff *skb,
++                                   const struct nft_bitwise *priv)
+ {
+       if (nft_data_dump(skb, NFTA_BITWISE_MASK, &priv->mask,
+                         NFT_DATA_VALUE, priv->len) < 0)
+@@ -249,8 +249,8 @@ static int nft_bitwise_dump(struct sk_bu
+               return -1;
+       switch (priv->op) {
+-      case NFT_BITWISE_BOOL:
+-              err = nft_bitwise_dump_bool(skb, priv);
++      case NFT_BITWISE_MASK_XOR:
++              err = nft_bitwise_dump_mask_xor(skb, priv);
+               break;
+       case NFT_BITWISE_LSHIFT:
+       case NFT_BITWISE_RSHIFT:
+@@ -270,7 +270,7 @@ static int nft_bitwise_offload(struct nf
+       const struct nft_bitwise *priv = nft_expr_priv(expr);
+       struct nft_offload_reg *reg = &ctx->regs[priv->dreg];
+-      if (priv->op != NFT_BITWISE_BOOL)
++      if (priv->op != NFT_BITWISE_MASK_XOR)
+               return -EOPNOTSUPP;
+       if (memcmp(&priv->xor, &zero, sizeof(priv->xor)) ||
+@@ -407,7 +407,7 @@ nft_bitwise_fast_dump(struct sk_buff *sk
+               return -1;
+       if (nla_put_be32(skb, NFTA_BITWISE_LEN, htonl(sizeof(u32))))
+               return -1;
+-      if (nla_put_be32(skb, NFTA_BITWISE_OP, htonl(NFT_BITWISE_BOOL)))
++      if (nla_put_be32(skb, NFTA_BITWISE_OP, htonl(NFT_BITWISE_MASK_XOR)))
+               return -1;
+       data.data[0] = priv->mask;
+@@ -502,7 +502,7 @@ nft_bitwise_select_ops(const struct nft_
+               return &nft_bitwise_ops;
+       if (tb[NFTA_BITWISE_OP] &&
+-          ntohl(nla_get_be32(tb[NFTA_BITWISE_OP])) != NFT_BITWISE_BOOL)
++          ntohl(nla_get_be32(tb[NFTA_BITWISE_OP])) != NFT_BITWISE_MASK_XOR)
+               return &nft_bitwise_ops;
+       return &nft_bitwise_fast_ops;
diff --git a/queue-6.12/netfilter-nf_conntrack_sip-remove-net-variable-shadowing.patch b/queue-6.12/netfilter-nf_conntrack_sip-remove-net-variable-shadowing.patch
new file mode 100644 (file)
index 0000000..d575f2b
--- /dev/null
@@ -0,0 +1,39 @@
+From stable+bounces-288358-greg=kroah.com@vger.kernel.org Thu Jul 23 16:39:44 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 10:34:29 -0400
+Subject: netfilter: nf_conntrack_sip: remove net variable shadowing
+To: stable@vger.kernel.org
+Cc: Florian Westphal <fw@strlen.de>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723143430.3226511-1-sashal@kernel.org>
+
+From: Florian Westphal <fw@strlen.de>
+
+[ Upstream commit 7970d6aaf710db166de98c5356a260089896fae5 ]
+
+net is already set, derived from nf_conn.
+I don't see how the device could be living in a different netns
+than the conntrack entry.
+
+Remove the extra variable and re-use existing one.
+
+Signed-off-by: Florian Westphal <fw@strlen.de>
+Stable-dep-of: e5e24a365a5e ("netfilter: nf_conntrack_sip: validate skb_dst() before accessing it")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/netfilter/nf_conntrack_sip.c |    3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+--- a/net/netfilter/nf_conntrack_sip.c
++++ b/net/netfilter/nf_conntrack_sip.c
+@@ -948,9 +948,8 @@ static int set_expected_rtp_rtcp(struct
+               saddr = &ct->tuplehash[!dir].tuple.src.u3;
+       } else if (sip_external_media) {
+               struct net_device *dev = skb_dst(skb)->dev;
+-              struct net *net = dev_net(dev);
+-              struct flowi fl;
+               struct dst_entry *dst = NULL;
++              struct flowi fl;
+               memset(&fl, 0, sizeof(fl));
diff --git a/queue-6.12/netfilter-nf_conntrack_sip-validate-skb_dst-before-accessing-it.patch b/queue-6.12/netfilter-nf_conntrack_sip-validate-skb_dst-before-accessing-it.patch
new file mode 100644 (file)
index 0000000..de29b66
--- /dev/null
@@ -0,0 +1,56 @@
+From stable+bounces-288359-greg=kroah.com@vger.kernel.org Thu Jul 23 16:39:45 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 10:34:30 -0400
+Subject: netfilter: nf_conntrack_sip: validate skb_dst() before accessing it
+To: stable@vger.kernel.org
+Cc: Pablo Neira Ayuso <pablo@netfilter.org>, Ren Wei <n05ec@lzu.edu.cn>, Florian Westphal <fw@strlen.de>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723143430.3226511-2-sashal@kernel.org>
+
+From: Pablo Neira Ayuso <pablo@netfilter.org>
+
+[ Upstream commit e5e24a365a5e024efef63cc49abb345fbd4852c5 ]
+
+tc ingress and openvswitch do not guarantee routing information to be
+available. These subsystems use the conntrack helper infrastructure, and
+the SIP helper relies on the skb_dst() to be present if
+sip_external_media is set to 1 (which is disabled by default as a module
+parameter).
+
+This effectively disables the sip_external_media toggle for these
+subsystems without resulting in a crash.
+
+Fixes: cae3a2627520 ("openvswitch: Allow attaching helpers to ct action")
+Fixes: b57dc7c13ea9 ("net/sched: Introduce action ct")
+Cc: stable@vger.kernel.org
+Reported-by: Ren Wei <n05ec@lzu.edu.cn>
+Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
+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/netfilter/nf_conntrack_sip.c |    7 +++++--
+ 1 file changed, 5 insertions(+), 2 deletions(-)
+
+--- a/net/netfilter/nf_conntrack_sip.c
++++ b/net/netfilter/nf_conntrack_sip.c
+@@ -947,7 +947,6 @@ static int set_expected_rtp_rtcp(struct
+                       return NF_ACCEPT;
+               saddr = &ct->tuplehash[!dir].tuple.src.u3;
+       } else if (sip_external_media) {
+-              struct net_device *dev = skb_dst(skb)->dev;
+               struct dst_entry *dst = NULL;
+               struct flowi fl;
+@@ -969,7 +968,11 @@ static int set_expected_rtp_rtcp(struct
+                * through the same interface as the signalling peer.
+                */
+               if (dst) {
+-                      bool external_media = (dst->dev == dev);
++                      const struct dst_entry *this_dst = skb_dst(skb);
++                      bool external_media = false;
++
++                      if (this_dst && dst->dev == this_dst->dev)
++                              external_media = true;
+                       dst_release(dst);
+                       if (external_media)
diff --git a/queue-6.12/netfilter-nf_tables-remove-register-tracking-infrastructure.patch b/queue-6.12/netfilter-nf_tables-remove-register-tracking-infrastructure.patch
new file mode 100644 (file)
index 0000000..c811754
--- /dev/null
@@ -0,0 +1,1645 @@
+From stable+bounces-288429-greg=kroah.com@vger.kernel.org Thu Jul 23 18:57:23 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 12:54:18 -0400
+Subject: netfilter: nf_tables: remove register tracking infrastructure
+To: stable@vger.kernel.org
+Cc: Florian Westphal <fw@strlen.de>, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723165419.3390514-3-sashal@kernel.org>
+
+From: Florian Westphal <fw@strlen.de>
+
+[ Upstream commit 6b94d081f81dd524626f7aab2b98a9de335edb72 ]
+
+This facility was disabled in commit
+9e539c5b6d9c ("netfilter: nf_tables: disable expression reduction infra"),
+because not all nft_exprs guarantee they will update the destination
+register: some may set NFT_BREAK instead to cancel evaluation of the
+rule.
+
+This has been dead code ever since.
+There are no plans to salvage this at this time, so remove this.
+
+Signed-off-by: Florian Westphal <fw@strlen.de>
+Link: https://patch.msgid.link/20260224205048.4718-10-fw@strlen.de
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Stable-dep-of: d07955dd34ec ("netfilter: nft_fib: reject fib expression on the netdev egress hook")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/net/netfilter/nf_tables.h        |   32 ---------
+ include/net/netfilter/nft_fib.h          |    2 
+ include/net/netfilter/nft_meta.h         |    3 
+ net/bridge/netfilter/nft_meta_bridge.c   |   20 ------
+ net/bridge/netfilter/nft_reject_bridge.c |    1 
+ net/ipv4/netfilter/nft_dup_ipv4.c        |    1 
+ net/ipv4/netfilter/nft_fib_ipv4.c        |    2 
+ net/ipv4/netfilter/nft_reject_ipv4.c     |    1 
+ net/ipv6/netfilter/nft_dup_ipv6.c        |    1 
+ net/ipv6/netfilter/nft_fib_ipv6.c        |    2 
+ net/ipv6/netfilter/nft_reject_ipv6.c     |    1 
+ net/netfilter/nf_tables_api.c            |   67 --------------------
+ net/netfilter/nft_bitwise.c              |  103 -------------------------------
+ net/netfilter/nft_byteorder.c            |   11 ---
+ net/netfilter/nft_cmp.c                  |    3 
+ net/netfilter/nft_compat.c               |   10 ---
+ net/netfilter/nft_connlimit.c            |    1 
+ net/netfilter/nft_counter.c              |    1 
+ net/netfilter/nft_ct.c                   |   46 -------------
+ net/netfilter/nft_dup_netdev.c           |    1 
+ net/netfilter/nft_dynset.c               |    1 
+ net/netfilter/nft_exthdr.c               |   34 ----------
+ net/netfilter/nft_fib.c                  |   42 ------------
+ net/netfilter/nft_fib_inet.c             |    1 
+ net/netfilter/nft_fib_netdev.c           |    1 
+ net/netfilter/nft_flow_offload.c         |    1 
+ net/netfilter/nft_fwd_netdev.c           |    2 
+ net/netfilter/nft_hash.c                 |   36 ----------
+ net/netfilter/nft_immediate.c            |   12 ---
+ net/netfilter/nft_last.c                 |    1 
+ net/netfilter/nft_limit.c                |    2 
+ net/netfilter/nft_log.c                  |    1 
+ net/netfilter/nft_lookup.c               |   12 ---
+ net/netfilter/nft_masq.c                 |    3 
+ net/netfilter/nft_meta.c                 |   45 -------------
+ net/netfilter/nft_nat.c                  |    2 
+ net/netfilter/nft_numgen.c               |   22 ------
+ net/netfilter/nft_objref.c               |    2 
+ net/netfilter/nft_osf.c                  |   25 -------
+ net/netfilter/nft_payload.c              |   47 --------------
+ net/netfilter/nft_queue.c                |    2 
+ net/netfilter/nft_quota.c                |    1 
+ net/netfilter/nft_range.c                |    1 
+ net/netfilter/nft_redir.c                |    3 
+ net/netfilter/nft_reject_inet.c          |    1 
+ net/netfilter/nft_reject_netdev.c        |    1 
+ net/netfilter/nft_rt.c                   |    1 
+ net/netfilter/nft_socket.c               |   26 -------
+ net/netfilter/nft_synproxy.c             |    1 
+ net/netfilter/nft_tproxy.c               |    1 
+ net/netfilter/nft_tunnel.c               |   26 -------
+ net/netfilter/nft_xfrm.c                 |   27 --------
+ 52 files changed, 692 deletions(-)
+
+--- a/include/net/netfilter/nf_tables.h
++++ b/include/net/netfilter/nf_tables.h
+@@ -123,17 +123,6 @@ struct nft_regs {
+       };
+ };
+-struct nft_regs_track {
+-      struct {
+-              const struct nft_expr           *selector;
+-              const struct nft_expr           *bitwise;
+-              u8                              num_reg;
+-      } regs[NFT_REG32_NUM];
+-
+-      const struct nft_expr                   *cur;
+-      const struct nft_expr                   *last;
+-};
+-
+ /* Store/load an u8, u16 or u64 integer to/from the u32 data register.
+  *
+  * Note, when using concatenations, register allocation happens at 32-bit
+@@ -424,8 +413,6 @@ int nft_expr_clone(struct nft_expr *dst,
+ void nft_expr_destroy(const struct nft_ctx *ctx, struct nft_expr *expr);
+ int nft_expr_dump(struct sk_buff *skb, unsigned int attr,
+                 const struct nft_expr *expr, bool reset);
+-bool nft_expr_reduce_bitwise(struct nft_regs_track *track,
+-                           const struct nft_expr *expr);
+ struct nft_set_ext;
+@@ -940,7 +927,6 @@ struct nft_offload_ctx;
+  *    @destroy_clone: destruction clone function
+  *    @dump: function to dump parameters
+  *    @validate: validate expression, called during loop detection
+- *    @reduce: reduce expression
+  *    @gc: garbage collection expression
+  *    @offload: hardware offload expression
+  *    @offload_action: function to report true/false to allocate one slot or not in the flow
+@@ -974,8 +960,6 @@ struct nft_expr_ops {
+                                               bool reset);
+       int                             (*validate)(const struct nft_ctx *ctx,
+                                                   const struct nft_expr *expr);
+-      bool                            (*reduce)(struct nft_regs_track *track,
+-                                                const struct nft_expr *expr);
+       bool                            (*gc)(struct net *net,
+                                             const struct nft_expr *expr);
+       int                             (*offload)(struct nft_offload_ctx *ctx,
+@@ -1951,20 +1935,4 @@ static inline u64 nft_net_tstamp(const s
+       return nft_pernet(net)->tstamp;
+ }
+-#define __NFT_REDUCE_READONLY 1UL
+-#define NFT_REDUCE_READONLY   (void *)__NFT_REDUCE_READONLY
+-
+-void nft_reg_track_update(struct nft_regs_track *track,
+-                        const struct nft_expr *expr, u8 dreg, u8 len);
+-void nft_reg_track_cancel(struct nft_regs_track *track, u8 dreg, u8 len);
+-void __nft_reg_track_cancel(struct nft_regs_track *track, u8 dreg);
+-
+-static inline bool nft_reg_track_cmp(struct nft_regs_track *track,
+-                                   const struct nft_expr *expr, u8 dreg)
+-{
+-      return track->regs[dreg].selector &&
+-             track->regs[dreg].selector->ops == expr->ops &&
+-             track->regs[dreg].num_reg == 0;
+-}
+-
+ #endif /* _NET_NF_TABLES_H */
+--- a/include/net/netfilter/nft_fib.h
++++ b/include/net/netfilter/nft_fib.h
+@@ -66,6 +66,4 @@ void nft_fib6_eval(const struct nft_expr
+ void nft_fib_store_result(void *reg, const struct nft_fib *priv,
+                         const struct net_device *dev);
+-bool nft_fib_reduce(struct nft_regs_track *track,
+-                  const struct nft_expr *expr);
+ #endif
+--- a/include/net/netfilter/nft_meta.h
++++ b/include/net/netfilter/nft_meta.h
+@@ -43,9 +43,6 @@ void nft_meta_set_destroy(const struct n
+ int nft_meta_set_validate(const struct nft_ctx *ctx,
+                         const struct nft_expr *expr);
+-bool nft_meta_get_reduce(struct nft_regs_track *track,
+-                       const struct nft_expr *expr);
+-
+ struct nft_inner_tun_ctx;
+ void nft_meta_inner_eval(const struct nft_expr *expr,
+                        struct nft_regs *regs, const struct nft_pktinfo *pkt,
+--- a/net/bridge/netfilter/nft_meta_bridge.c
++++ b/net/bridge/netfilter/nft_meta_bridge.c
+@@ -104,7 +104,6 @@ static const struct nft_expr_ops nft_met
+       .eval           = nft_meta_bridge_get_eval,
+       .init           = nft_meta_bridge_get_init,
+       .dump           = nft_meta_get_dump,
+-      .reduce         = nft_meta_get_reduce,
+ };
+ static void nft_meta_bridge_set_eval(const struct nft_expr *expr,
+@@ -151,24 +150,6 @@ static int nft_meta_bridge_set_init(cons
+       return 0;
+ }
+-static bool nft_meta_bridge_set_reduce(struct nft_regs_track *track,
+-                                     const struct nft_expr *expr)
+-{
+-      int i;
+-
+-      for (i = 0; i < NFT_REG32_NUM; i++) {
+-              if (!track->regs[i].selector)
+-                      continue;
+-
+-              if (track->regs[i].selector->ops != &nft_meta_bridge_get_ops)
+-                      continue;
+-
+-              __nft_reg_track_cancel(track, i);
+-      }
+-
+-      return false;
+-}
+-
+ static int nft_meta_bridge_set_validate(const struct nft_ctx *ctx,
+                                       const struct nft_expr *expr)
+ {
+@@ -193,7 +174,6 @@ static const struct nft_expr_ops nft_met
+       .init           = nft_meta_bridge_set_init,
+       .destroy        = nft_meta_set_destroy,
+       .dump           = nft_meta_set_dump,
+-      .reduce         = nft_meta_bridge_set_reduce,
+       .validate       = nft_meta_bridge_set_validate,
+ };
+--- a/net/bridge/netfilter/nft_reject_bridge.c
++++ b/net/bridge/netfilter/nft_reject_bridge.c
+@@ -184,7 +184,6 @@ static const struct nft_expr_ops nft_rej
+       .init           = nft_reject_init,
+       .dump           = nft_reject_dump,
+       .validate       = nft_reject_bridge_validate,
+-      .reduce         = NFT_REDUCE_READONLY,
+ };
+ static struct nft_expr_type nft_reject_bridge_type __read_mostly = {
+--- a/net/ipv4/netfilter/nft_dup_ipv4.c
++++ b/net/ipv4/netfilter/nft_dup_ipv4.c
+@@ -76,7 +76,6 @@ static const struct nft_expr_ops nft_dup
+       .eval           = nft_dup_ipv4_eval,
+       .init           = nft_dup_ipv4_init,
+       .dump           = nft_dup_ipv4_dump,
+-      .reduce         = NFT_REDUCE_READONLY,
+ };
+ static const struct nla_policy nft_dup_ipv4_policy[NFTA_DUP_MAX + 1] = {
+--- a/net/ipv4/netfilter/nft_fib_ipv4.c
++++ b/net/ipv4/netfilter/nft_fib_ipv4.c
+@@ -162,7 +162,6 @@ static const struct nft_expr_ops nft_fib
+       .init           = nft_fib_init,
+       .dump           = nft_fib_dump,
+       .validate       = nft_fib_validate,
+-      .reduce         = nft_fib_reduce,
+ };
+ static const struct nft_expr_ops nft_fib4_ops = {
+@@ -172,7 +171,6 @@ static const struct nft_expr_ops nft_fib
+       .init           = nft_fib_init,
+       .dump           = nft_fib_dump,
+       .validate       = nft_fib_validate,
+-      .reduce         = nft_fib_reduce,
+ };
+ static const struct nft_expr_ops *
+--- a/net/ipv4/netfilter/nft_reject_ipv4.c
++++ b/net/ipv4/netfilter/nft_reject_ipv4.c
+@@ -45,7 +45,6 @@ static const struct nft_expr_ops nft_rej
+       .init           = nft_reject_init,
+       .dump           = nft_reject_dump,
+       .validate       = nft_reject_validate,
+-      .reduce         = NFT_REDUCE_READONLY,
+ };
+ static struct nft_expr_type nft_reject_ipv4_type __read_mostly = {
+--- a/net/ipv6/netfilter/nft_dup_ipv6.c
++++ b/net/ipv6/netfilter/nft_dup_ipv6.c
+@@ -74,7 +74,6 @@ static const struct nft_expr_ops nft_dup
+       .eval           = nft_dup_ipv6_eval,
+       .init           = nft_dup_ipv6_init,
+       .dump           = nft_dup_ipv6_dump,
+-      .reduce         = NFT_REDUCE_READONLY,
+ };
+ static const struct nla_policy nft_dup_ipv6_policy[NFTA_DUP_MAX + 1] = {
+--- a/net/ipv6/netfilter/nft_fib_ipv6.c
++++ b/net/ipv6/netfilter/nft_fib_ipv6.c
+@@ -225,7 +225,6 @@ static const struct nft_expr_ops nft_fib
+       .init           = nft_fib_init,
+       .dump           = nft_fib_dump,
+       .validate       = nft_fib_validate,
+-      .reduce         = nft_fib_reduce,
+ };
+ static const struct nft_expr_ops nft_fib6_ops = {
+@@ -235,7 +234,6 @@ static const struct nft_expr_ops nft_fib
+       .init           = nft_fib_init,
+       .dump           = nft_fib_dump,
+       .validate       = nft_fib_validate,
+-      .reduce         = nft_fib_reduce,
+ };
+ static const struct nft_expr_ops *
+--- a/net/ipv6/netfilter/nft_reject_ipv6.c
++++ b/net/ipv6/netfilter/nft_reject_ipv6.c
+@@ -46,7 +46,6 @@ static const struct nft_expr_ops nft_rej
+       .init           = nft_reject_init,
+       .dump           = nft_reject_dump,
+       .validate       = nft_reject_validate,
+-      .reduce         = NFT_REDUCE_READONLY,
+ };
+ static struct nft_expr_type nft_reject_ipv6_type __read_mostly = {
+--- a/net/netfilter/nf_tables_api.c
++++ b/net/netfilter/nf_tables_api.c
+@@ -802,58 +802,6 @@ static int nft_delflowtable(struct nft_c
+       return 0;
+ }
+-static void __nft_reg_track_clobber(struct nft_regs_track *track, u8 dreg)
+-{
+-      int i;
+-
+-      for (i = track->regs[dreg].num_reg; i > 0; i--)
+-              __nft_reg_track_cancel(track, dreg - i);
+-}
+-
+-static void __nft_reg_track_update(struct nft_regs_track *track,
+-                                 const struct nft_expr *expr,
+-                                 u8 dreg, u8 num_reg)
+-{
+-      track->regs[dreg].selector = expr;
+-      track->regs[dreg].bitwise = NULL;
+-      track->regs[dreg].num_reg = num_reg;
+-}
+-
+-void nft_reg_track_update(struct nft_regs_track *track,
+-                        const struct nft_expr *expr, u8 dreg, u8 len)
+-{
+-      unsigned int regcount;
+-      int i;
+-
+-      __nft_reg_track_clobber(track, dreg);
+-
+-      regcount = DIV_ROUND_UP(len, NFT_REG32_SIZE);
+-      for (i = 0; i < regcount; i++, dreg++)
+-              __nft_reg_track_update(track, expr, dreg, i);
+-}
+-EXPORT_SYMBOL_GPL(nft_reg_track_update);
+-
+-void nft_reg_track_cancel(struct nft_regs_track *track, u8 dreg, u8 len)
+-{
+-      unsigned int regcount;
+-      int i;
+-
+-      __nft_reg_track_clobber(track, dreg);
+-
+-      regcount = DIV_ROUND_UP(len, NFT_REG32_SIZE);
+-      for (i = 0; i < regcount; i++, dreg++)
+-              __nft_reg_track_cancel(track, dreg);
+-}
+-EXPORT_SYMBOL_GPL(nft_reg_track_cancel);
+-
+-void __nft_reg_track_cancel(struct nft_regs_track *track, u8 dreg)
+-{
+-      track->regs[dreg].selector = NULL;
+-      track->regs[dreg].bitwise = NULL;
+-      track->regs[dreg].num_reg = 0;
+-}
+-EXPORT_SYMBOL_GPL(__nft_reg_track_cancel);
+-
+ /*
+  * Tables
+  */
+@@ -9685,16 +9633,9 @@ void nf_tables_trans_destroy_flush_work(
+ }
+ EXPORT_SYMBOL_GPL(nf_tables_trans_destroy_flush_work);
+-static bool nft_expr_reduce(struct nft_regs_track *track,
+-                          const struct nft_expr *expr)
+-{
+-      return false;
+-}
+-
+ static int nf_tables_commit_chain_prepare(struct net *net, struct nft_chain *chain)
+ {
+       const struct nft_expr *expr, *last;
+-      struct nft_regs_track track = {};
+       unsigned int size, data_size;
+       void *data, *data_boundary;
+       struct nft_rule_dp *prule;
+@@ -9731,15 +9672,7 @@ static int nf_tables_commit_chain_prepar
+                       return -ENOMEM;
+               size = 0;
+-              track.last = nft_expr_last(rule);
+               nft_rule_for_each_expr(expr, last, rule) {
+-                      track.cur = expr;
+-
+-                      if (nft_expr_reduce(&track, expr)) {
+-                              expr = track.cur;
+-                              continue;
+-                      }
+-
+                       if (WARN_ON_ONCE(data + size + expr->ops->size > data_boundary))
+                               return -ENOMEM;
+--- a/net/netfilter/nft_bitwise.c
++++ b/net/netfilter/nft_bitwise.c
+@@ -282,60 +282,12 @@ static int nft_bitwise_offload(struct nf
+       return 0;
+ }
+-static bool nft_bitwise_reduce(struct nft_regs_track *track,
+-                             const struct nft_expr *expr)
+-{
+-      const struct nft_bitwise *priv = nft_expr_priv(expr);
+-      const struct nft_bitwise *bitwise;
+-      unsigned int regcount;
+-      u8 dreg;
+-      int i;
+-
+-      if (!track->regs[priv->sreg].selector)
+-              return false;
+-
+-      bitwise = nft_expr_priv(track->regs[priv->dreg].selector);
+-      if (track->regs[priv->sreg].selector == track->regs[priv->dreg].selector &&
+-          track->regs[priv->sreg].num_reg == 0 &&
+-          track->regs[priv->dreg].bitwise &&
+-          track->regs[priv->dreg].bitwise->ops == expr->ops &&
+-          priv->sreg == bitwise->sreg &&
+-          priv->dreg == bitwise->dreg &&
+-          priv->op == bitwise->op &&
+-          priv->len == bitwise->len &&
+-          !memcmp(&priv->mask, &bitwise->mask, sizeof(priv->mask)) &&
+-          !memcmp(&priv->xor, &bitwise->xor, sizeof(priv->xor)) &&
+-          !memcmp(&priv->data, &bitwise->data, sizeof(priv->data))) {
+-              track->cur = expr;
+-              return true;
+-      }
+-
+-      if (track->regs[priv->sreg].bitwise ||
+-          track->regs[priv->sreg].num_reg != 0) {
+-              nft_reg_track_cancel(track, priv->dreg, priv->len);
+-              return false;
+-      }
+-
+-      if (priv->sreg != priv->dreg) {
+-              nft_reg_track_update(track, track->regs[priv->sreg].selector,
+-                                   priv->dreg, priv->len);
+-      }
+-
+-      dreg = priv->dreg;
+-      regcount = DIV_ROUND_UP(priv->len, NFT_REG32_SIZE);
+-      for (i = 0; i < regcount; i++, dreg++)
+-              track->regs[dreg].bitwise = expr;
+-
+-      return false;
+-}
+-
+ static const struct nft_expr_ops nft_bitwise_ops = {
+       .type           = &nft_bitwise_type,
+       .size           = NFT_EXPR_SIZE(sizeof(struct nft_bitwise)),
+       .eval           = nft_bitwise_eval,
+       .init           = nft_bitwise_init,
+       .dump           = nft_bitwise_dump,
+-      .reduce         = nft_bitwise_reduce,
+       .offload        = nft_bitwise_offload,
+ };
+@@ -437,48 +389,12 @@ static int nft_bitwise_fast_offload(stru
+       return 0;
+ }
+-static bool nft_bitwise_fast_reduce(struct nft_regs_track *track,
+-                                  const struct nft_expr *expr)
+-{
+-      const struct nft_bitwise_fast_expr *priv = nft_expr_priv(expr);
+-      const struct nft_bitwise_fast_expr *bitwise;
+-
+-      if (!track->regs[priv->sreg].selector)
+-              return false;
+-
+-      bitwise = nft_expr_priv(track->regs[priv->dreg].selector);
+-      if (track->regs[priv->sreg].selector == track->regs[priv->dreg].selector &&
+-          track->regs[priv->dreg].bitwise &&
+-          track->regs[priv->dreg].bitwise->ops == expr->ops &&
+-          priv->sreg == bitwise->sreg &&
+-          priv->dreg == bitwise->dreg &&
+-          priv->mask == bitwise->mask &&
+-          priv->xor == bitwise->xor) {
+-              track->cur = expr;
+-              return true;
+-      }
+-
+-      if (track->regs[priv->sreg].bitwise) {
+-              nft_reg_track_cancel(track, priv->dreg, NFT_REG32_SIZE);
+-              return false;
+-      }
+-
+-      if (priv->sreg != priv->dreg) {
+-              track->regs[priv->dreg].selector =
+-                      track->regs[priv->sreg].selector;
+-      }
+-      track->regs[priv->dreg].bitwise = expr;
+-
+-      return false;
+-}
+-
+ const struct nft_expr_ops nft_bitwise_fast_ops = {
+       .type           = &nft_bitwise_type,
+       .size           = NFT_EXPR_SIZE(sizeof(struct nft_bitwise_fast_expr)),
+       .eval           = NULL, /* inlined */
+       .init           = nft_bitwise_fast_init,
+       .dump           = nft_bitwise_fast_dump,
+-      .reduce         = nft_bitwise_fast_reduce,
+       .offload        = nft_bitwise_fast_offload,
+ };
+@@ -515,22 +431,3 @@ struct nft_expr_type nft_bitwise_type __
+       .maxattr        = NFTA_BITWISE_MAX,
+       .owner          = THIS_MODULE,
+ };
+-
+-bool nft_expr_reduce_bitwise(struct nft_regs_track *track,
+-                           const struct nft_expr *expr)
+-{
+-      const struct nft_expr *last = track->last;
+-      const struct nft_expr *next;
+-
+-      if (expr == last)
+-              return false;
+-
+-      next = nft_expr_next(expr);
+-      if (next->ops == &nft_bitwise_ops)
+-              return nft_bitwise_reduce(track, next);
+-      else if (next->ops == &nft_bitwise_fast_ops)
+-              return nft_bitwise_fast_reduce(track, next);
+-
+-      return false;
+-}
+-EXPORT_SYMBOL_GPL(nft_expr_reduce_bitwise);
+--- a/net/netfilter/nft_byteorder.c
++++ b/net/netfilter/nft_byteorder.c
+@@ -170,23 +170,12 @@ nla_put_failure:
+       return -1;
+ }
+-static bool nft_byteorder_reduce(struct nft_regs_track *track,
+-                               const struct nft_expr *expr)
+-{
+-      struct nft_byteorder *priv = nft_expr_priv(expr);
+-
+-      nft_reg_track_cancel(track, priv->dreg, priv->len);
+-
+-      return false;
+-}
+-
+ static const struct nft_expr_ops nft_byteorder_ops = {
+       .type           = &nft_byteorder_type,
+       .size           = NFT_EXPR_SIZE(sizeof(struct nft_byteorder)),
+       .eval           = nft_byteorder_eval,
+       .init           = nft_byteorder_init,
+       .dump           = nft_byteorder_dump,
+-      .reduce         = nft_byteorder_reduce,
+ };
+ struct nft_expr_type nft_byteorder_type __read_mostly = {
+--- a/net/netfilter/nft_cmp.c
++++ b/net/netfilter/nft_cmp.c
+@@ -190,7 +190,6 @@ static const struct nft_expr_ops nft_cmp
+       .eval           = nft_cmp_eval,
+       .init           = nft_cmp_init,
+       .dump           = nft_cmp_dump,
+-      .reduce         = NFT_REDUCE_READONLY,
+       .offload        = nft_cmp_offload,
+ };
+@@ -282,7 +281,6 @@ const struct nft_expr_ops nft_cmp_fast_o
+       .eval           = NULL, /* inlined */
+       .init           = nft_cmp_fast_init,
+       .dump           = nft_cmp_fast_dump,
+-      .reduce         = NFT_REDUCE_READONLY,
+       .offload        = nft_cmp_fast_offload,
+ };
+@@ -376,7 +374,6 @@ const struct nft_expr_ops nft_cmp16_fast
+       .eval           = NULL, /* inlined */
+       .init           = nft_cmp16_fast_init,
+       .dump           = nft_cmp16_fast_dump,
+-      .reduce         = NFT_REDUCE_READONLY,
+       .offload        = nft_cmp16_fast_offload,
+ };
+--- a/net/netfilter/nft_compat.c
++++ b/net/netfilter/nft_compat.c
+@@ -794,14 +794,6 @@ static const struct nfnetlink_subsystem
+ static struct nft_expr_type nft_match_type;
+-static bool nft_match_reduce(struct nft_regs_track *track,
+-                           const struct nft_expr *expr)
+-{
+-      const struct xt_match *match = expr->ops->data;
+-
+-      return strcmp(match->name, "comment") == 0;
+-}
+-
+ static const struct nft_expr_ops *
+ nft_match_select_ops(const struct nft_ctx *ctx,
+                    const struct nlattr * const tb[])
+@@ -844,7 +836,6 @@ nft_match_select_ops(const struct nft_ct
+       ops->dump = nft_match_dump;
+       ops->validate = nft_match_validate;
+       ops->data = match;
+-      ops->reduce = nft_match_reduce;
+       matchsize = NFT_EXPR_SIZE(XT_ALIGN(match->matchsize));
+       if (matchsize > NFT_MATCH_LARGE_THRESH) {
+@@ -933,7 +924,6 @@ nft_target_select_ops(const struct nft_c
+       ops->destroy = nft_target_destroy;
+       ops->dump = nft_target_dump;
+       ops->data = target;
+-      ops->reduce = NFT_REDUCE_READONLY;
+       if (family == NFPROTO_BRIDGE) {
+               ops->eval = nft_target_eval_bridge;
+--- a/net/netfilter/nft_connlimit.c
++++ b/net/netfilter/nft_connlimit.c
+@@ -247,7 +247,6 @@ static const struct nft_expr_ops nft_con
+       .destroy_clone  = nft_connlimit_destroy_clone,
+       .dump           = nft_connlimit_dump,
+       .gc             = nft_connlimit_gc,
+-      .reduce         = NFT_REDUCE_READONLY,
+ };
+ static struct nft_expr_type nft_connlimit_type __read_mostly = {
+--- a/net/netfilter/nft_counter.c
++++ b/net/netfilter/nft_counter.c
+@@ -313,7 +313,6 @@ static const struct nft_expr_ops nft_cou
+       .destroy_clone  = nft_counter_destroy,
+       .dump           = nft_counter_dump,
+       .clone          = nft_counter_clone,
+-      .reduce         = NFT_REDUCE_READONLY,
+       .offload        = nft_counter_offload,
+       .offload_stats  = nft_counter_offload_stats,
+ };
+--- a/net/netfilter/nft_ct.c
++++ b/net/netfilter/nft_ct.c
+@@ -679,29 +679,6 @@ nla_put_failure:
+       return -1;
+ }
+-static bool nft_ct_get_reduce(struct nft_regs_track *track,
+-                            const struct nft_expr *expr)
+-{
+-      const struct nft_ct *priv = nft_expr_priv(expr);
+-      const struct nft_ct *ct;
+-
+-      if (!nft_reg_track_cmp(track, expr, priv->dreg)) {
+-              nft_reg_track_update(track, expr, priv->dreg, priv->len);
+-              return false;
+-      }
+-
+-      ct = nft_expr_priv(track->regs[priv->dreg].selector);
+-      if (priv->key != ct->key) {
+-              nft_reg_track_update(track, expr, priv->dreg, priv->len);
+-              return false;
+-      }
+-
+-      if (!track->regs[priv->dreg].bitwise)
+-              return true;
+-
+-      return nft_expr_reduce_bitwise(track, expr);
+-}
+-
+ static int nft_ct_set_dump(struct sk_buff *skb,
+                          const struct nft_expr *expr, bool reset)
+ {
+@@ -736,27 +713,8 @@ static const struct nft_expr_ops nft_ct_
+       .init           = nft_ct_get_init,
+       .destroy        = nft_ct_get_destroy,
+       .dump           = nft_ct_get_dump,
+-      .reduce         = nft_ct_get_reduce,
+ };
+-static bool nft_ct_set_reduce(struct nft_regs_track *track,
+-                            const struct nft_expr *expr)
+-{
+-      int i;
+-
+-      for (i = 0; i < NFT_REG32_NUM; i++) {
+-              if (!track->regs[i].selector)
+-                      continue;
+-
+-              if (track->regs[i].selector->ops != &nft_ct_get_ops)
+-                      continue;
+-
+-              __nft_reg_track_cancel(track, i);
+-      }
+-
+-      return false;
+-}
+-
+ #ifdef CONFIG_MITIGATION_RETPOLINE
+ static const struct nft_expr_ops nft_ct_get_fast_ops = {
+       .type           = &nft_ct_type,
+@@ -765,7 +723,6 @@ static const struct nft_expr_ops nft_ct_
+       .init           = nft_ct_get_init,
+       .destroy        = nft_ct_get_destroy,
+       .dump           = nft_ct_get_dump,
+-      .reduce         = nft_ct_set_reduce,
+ };
+ #endif
+@@ -776,7 +733,6 @@ static const struct nft_expr_ops nft_ct_
+       .init           = nft_ct_set_init,
+       .destroy        = nft_ct_set_destroy,
+       .dump           = nft_ct_set_dump,
+-      .reduce         = nft_ct_set_reduce,
+ };
+ #ifdef CONFIG_NF_CONNTRACK_ZONES
+@@ -787,7 +743,6 @@ static const struct nft_expr_ops nft_ct_
+       .init           = nft_ct_set_init,
+       .destroy        = nft_ct_set_destroy,
+       .dump           = nft_ct_set_dump,
+-      .reduce         = nft_ct_set_reduce,
+ };
+ #endif
+@@ -857,7 +812,6 @@ static const struct nft_expr_ops nft_not
+       .type           = &nft_notrack_type,
+       .size           = NFT_EXPR_SIZE(0),
+       .eval           = nft_notrack_eval,
+-      .reduce         = NFT_REDUCE_READONLY,
+ };
+ static struct nft_expr_type nft_notrack_type __read_mostly = {
+--- a/net/netfilter/nft_dup_netdev.c
++++ b/net/netfilter/nft_dup_netdev.c
+@@ -80,7 +80,6 @@ static const struct nft_expr_ops nft_dup
+       .eval           = nft_dup_netdev_eval,
+       .init           = nft_dup_netdev_init,
+       .dump           = nft_dup_netdev_dump,
+-      .reduce         = NFT_REDUCE_READONLY,
+       .offload        = nft_dup_netdev_offload,
+       .offload_action = nft_dup_netdev_offload_action,
+ };
+--- a/net/netfilter/nft_dynset.c
++++ b/net/netfilter/nft_dynset.c
+@@ -430,7 +430,6 @@ static const struct nft_expr_ops nft_dyn
+       .activate       = nft_dynset_activate,
+       .deactivate     = nft_dynset_deactivate,
+       .dump           = nft_dynset_dump,
+-      .reduce         = NFT_REDUCE_READONLY,
+ };
+ struct nft_expr_type nft_dynset_type __read_mostly = {
+--- a/net/netfilter/nft_exthdr.c
++++ b/net/netfilter/nft_exthdr.c
+@@ -701,40 +701,12 @@ static int nft_exthdr_dump_strip(struct
+       return nft_exthdr_dump_common(skb, priv);
+ }
+-static bool nft_exthdr_reduce(struct nft_regs_track *track,
+-                             const struct nft_expr *expr)
+-{
+-      const struct nft_exthdr *priv = nft_expr_priv(expr);
+-      const struct nft_exthdr *exthdr;
+-
+-      if (!nft_reg_track_cmp(track, expr, priv->dreg)) {
+-              nft_reg_track_update(track, expr, priv->dreg, priv->len);
+-              return false;
+-      }
+-
+-      exthdr = nft_expr_priv(track->regs[priv->dreg].selector);
+-      if (priv->type != exthdr->type ||
+-          priv->op != exthdr->op ||
+-          priv->flags != exthdr->flags ||
+-          priv->offset != exthdr->offset ||
+-          priv->len != exthdr->len) {
+-              nft_reg_track_update(track, expr, priv->dreg, priv->len);
+-              return false;
+-      }
+-
+-      if (!track->regs[priv->dreg].bitwise)
+-              return true;
+-
+-      return nft_expr_reduce_bitwise(track, expr);
+-}
+-
+ static const struct nft_expr_ops nft_exthdr_ipv6_ops = {
+       .type           = &nft_exthdr_type,
+       .size           = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)),
+       .eval           = nft_exthdr_ipv6_eval,
+       .init           = nft_exthdr_init,
+       .dump           = nft_exthdr_dump,
+-      .reduce         = nft_exthdr_reduce,
+ };
+ static const struct nft_expr_ops nft_exthdr_ipv4_ops = {
+@@ -743,7 +715,6 @@ static const struct nft_expr_ops nft_ext
+       .eval           = nft_exthdr_ipv4_eval,
+       .init           = nft_exthdr_ipv4_init,
+       .dump           = nft_exthdr_dump,
+-      .reduce         = nft_exthdr_reduce,
+ };
+ static const struct nft_expr_ops nft_exthdr_tcp_ops = {
+@@ -752,7 +723,6 @@ static const struct nft_expr_ops nft_ext
+       .eval           = nft_exthdr_tcp_eval,
+       .init           = nft_exthdr_init,
+       .dump           = nft_exthdr_dump,
+-      .reduce         = nft_exthdr_reduce,
+ };
+ static const struct nft_expr_ops nft_exthdr_tcp_set_ops = {
+@@ -761,7 +731,6 @@ static const struct nft_expr_ops nft_ext
+       .eval           = nft_exthdr_tcp_set_eval,
+       .init           = nft_exthdr_tcp_set_init,
+       .dump           = nft_exthdr_dump_set,
+-      .reduce         = NFT_REDUCE_READONLY,
+ };
+ static const struct nft_expr_ops nft_exthdr_tcp_strip_ops = {
+@@ -770,7 +739,6 @@ static const struct nft_expr_ops nft_ext
+       .eval           = nft_exthdr_tcp_strip_eval,
+       .init           = nft_exthdr_tcp_strip_init,
+       .dump           = nft_exthdr_dump_strip,
+-      .reduce         = NFT_REDUCE_READONLY,
+ };
+ static const struct nft_expr_ops nft_exthdr_sctp_ops = {
+@@ -779,7 +747,6 @@ static const struct nft_expr_ops nft_ext
+       .eval           = nft_exthdr_sctp_eval,
+       .init           = nft_exthdr_init,
+       .dump           = nft_exthdr_dump,
+-      .reduce         = nft_exthdr_reduce,
+ };
+ static const struct nft_expr_ops nft_exthdr_dccp_ops = {
+@@ -788,7 +755,6 @@ static const struct nft_expr_ops nft_ext
+       .eval           = nft_exthdr_dccp_eval,
+       .init           = nft_exthdr_dccp_init,
+       .dump           = nft_exthdr_dump,
+-      .reduce         = nft_exthdr_reduce,
+ };
+ static const struct nft_expr_ops *
+--- a/net/netfilter/nft_fib.c
++++ b/net/netfilter/nft_fib.c
+@@ -168,48 +168,6 @@ void nft_fib_store_result(void *reg, con
+ }
+ EXPORT_SYMBOL_GPL(nft_fib_store_result);
+-bool nft_fib_reduce(struct nft_regs_track *track,
+-                  const struct nft_expr *expr)
+-{
+-      const struct nft_fib *priv = nft_expr_priv(expr);
+-      unsigned int len = NFT_REG32_SIZE;
+-      const struct nft_fib *fib;
+-
+-      switch (priv->result) {
+-      case NFT_FIB_RESULT_OIF:
+-              break;
+-      case NFT_FIB_RESULT_OIFNAME:
+-              if (priv->flags & NFTA_FIB_F_PRESENT)
+-                      len = NFT_REG32_SIZE;
+-              else
+-                      len = IFNAMSIZ;
+-              break;
+-      case NFT_FIB_RESULT_ADDRTYPE:
+-           break;
+-      default:
+-              WARN_ON_ONCE(1);
+-              break;
+-      }
+-
+-      if (!nft_reg_track_cmp(track, expr, priv->dreg)) {
+-              nft_reg_track_update(track, expr, priv->dreg, len);
+-              return false;
+-      }
+-
+-      fib = nft_expr_priv(track->regs[priv->dreg].selector);
+-      if (priv->result != fib->result ||
+-          priv->flags != fib->flags) {
+-              nft_reg_track_update(track, expr, priv->dreg, len);
+-              return false;
+-      }
+-
+-      if (!track->regs[priv->dreg].bitwise)
+-              return true;
+-
+-      return false;
+-}
+-EXPORT_SYMBOL_GPL(nft_fib_reduce);
+-
+ MODULE_LICENSE("GPL");
+ MODULE_DESCRIPTION("Query routing table from nftables");
+ MODULE_AUTHOR("Florian Westphal <fw@strlen.de>");
+--- a/net/netfilter/nft_fib_inet.c
++++ b/net/netfilter/nft_fib_inet.c
+@@ -49,7 +49,6 @@ static const struct nft_expr_ops nft_fib
+       .init           = nft_fib_init,
+       .dump           = nft_fib_dump,
+       .validate       = nft_fib_validate,
+-      .reduce         = nft_fib_reduce,
+ };
+ static struct nft_expr_type nft_fib_inet_type __read_mostly = {
+--- a/net/netfilter/nft_fib_netdev.c
++++ b/net/netfilter/nft_fib_netdev.c
+@@ -58,7 +58,6 @@ static const struct nft_expr_ops nft_fib
+       .init           = nft_fib_init,
+       .dump           = nft_fib_dump,
+       .validate       = nft_fib_validate,
+-      .reduce         = nft_fib_reduce,
+ };
+ static struct nft_expr_type nft_fib_netdev_type __read_mostly = {
+--- a/net/netfilter/nft_flow_offload.c
++++ b/net/netfilter/nft_flow_offload.c
+@@ -483,7 +483,6 @@ static const struct nft_expr_ops nft_flo
+       .destroy        = nft_flow_offload_destroy,
+       .validate       = nft_flow_offload_validate,
+       .dump           = nft_flow_offload_dump,
+-      .reduce         = NFT_REDUCE_READONLY,
+ };
+ static struct nft_expr_type nft_flow_offload_type __read_mostly = {
+--- a/net/netfilter/nft_fwd_netdev.c
++++ b/net/netfilter/nft_fwd_netdev.c
+@@ -228,7 +228,6 @@ static const struct nft_expr_ops nft_fwd
+       .init           = nft_fwd_neigh_init,
+       .dump           = nft_fwd_neigh_dump,
+       .validate       = nft_fwd_validate,
+-      .reduce         = NFT_REDUCE_READONLY,
+ };
+ static const struct nft_expr_ops nft_fwd_netdev_ops = {
+@@ -238,7 +237,6 @@ static const struct nft_expr_ops nft_fwd
+       .init           = nft_fwd_netdev_init,
+       .dump           = nft_fwd_netdev_dump,
+       .validate       = nft_fwd_validate,
+-      .reduce         = NFT_REDUCE_READONLY,
+       .offload        = nft_fwd_netdev_offload,
+       .offload_action = nft_fwd_netdev_offload_action,
+ };
+--- a/net/netfilter/nft_hash.c
++++ b/net/netfilter/nft_hash.c
+@@ -166,16 +166,6 @@ nla_put_failure:
+       return -1;
+ }
+-static bool nft_jhash_reduce(struct nft_regs_track *track,
+-                           const struct nft_expr *expr)
+-{
+-      const struct nft_jhash *priv = nft_expr_priv(expr);
+-
+-      nft_reg_track_cancel(track, priv->dreg, sizeof(u32));
+-
+-      return false;
+-}
+-
+ static int nft_symhash_dump(struct sk_buff *skb,
+                           const struct nft_expr *expr, bool reset)
+ {
+@@ -196,30 +186,6 @@ nla_put_failure:
+       return -1;
+ }
+-static bool nft_symhash_reduce(struct nft_regs_track *track,
+-                             const struct nft_expr *expr)
+-{
+-      struct nft_symhash *priv = nft_expr_priv(expr);
+-      struct nft_symhash *symhash;
+-
+-      if (!nft_reg_track_cmp(track, expr, priv->dreg)) {
+-              nft_reg_track_update(track, expr, priv->dreg, sizeof(u32));
+-              return false;
+-      }
+-
+-      symhash = nft_expr_priv(track->regs[priv->dreg].selector);
+-      if (priv->offset != symhash->offset ||
+-          priv->modulus != symhash->modulus) {
+-              nft_reg_track_update(track, expr, priv->dreg, sizeof(u32));
+-              return false;
+-      }
+-
+-      if (!track->regs[priv->dreg].bitwise)
+-              return true;
+-
+-      return false;
+-}
+-
+ static struct nft_expr_type nft_hash_type;
+ static const struct nft_expr_ops nft_jhash_ops = {
+       .type           = &nft_hash_type,
+@@ -227,7 +193,6 @@ static const struct nft_expr_ops nft_jha
+       .eval           = nft_jhash_eval,
+       .init           = nft_jhash_init,
+       .dump           = nft_jhash_dump,
+-      .reduce         = nft_jhash_reduce,
+ };
+ static const struct nft_expr_ops nft_symhash_ops = {
+@@ -236,7 +201,6 @@ static const struct nft_expr_ops nft_sym
+       .eval           = nft_symhash_eval,
+       .init           = nft_symhash_init,
+       .dump           = nft_symhash_dump,
+-      .reduce         = nft_symhash_reduce,
+ };
+ static const struct nft_expr_ops *
+--- a/net/netfilter/nft_immediate.c
++++ b/net/netfilter/nft_immediate.c
+@@ -320,17 +320,6 @@ static bool nft_immediate_offload_action
+       return false;
+ }
+-static bool nft_immediate_reduce(struct nft_regs_track *track,
+-                               const struct nft_expr *expr)
+-{
+-      const struct nft_immediate_expr *priv = nft_expr_priv(expr);
+-
+-      if (priv->dreg != NFT_REG_VERDICT)
+-              nft_reg_track_cancel(track, priv->dreg, priv->dlen);
+-
+-      return false;
+-}
+-
+ static const struct nft_expr_ops nft_imm_ops = {
+       .type           = &nft_imm_type,
+       .size           = NFT_EXPR_SIZE(sizeof(struct nft_immediate_expr)),
+@@ -341,7 +330,6 @@ static const struct nft_expr_ops nft_imm
+       .destroy        = nft_immediate_destroy,
+       .dump           = nft_immediate_dump,
+       .validate       = nft_immediate_validate,
+-      .reduce         = nft_immediate_reduce,
+       .offload        = nft_immediate_offload,
+       .offload_action = nft_immediate_offload_action,
+ };
+--- a/net/netfilter/nft_last.c
++++ b/net/netfilter/nft_last.c
+@@ -125,7 +125,6 @@ static const struct nft_expr_ops nft_las
+       .destroy        = nft_last_destroy,
+       .clone          = nft_last_clone,
+       .dump           = nft_last_dump,
+-      .reduce         = NFT_REDUCE_READONLY,
+ };
+ struct nft_expr_type nft_last_type __read_mostly = {
+--- a/net/netfilter/nft_limit.c
++++ b/net/netfilter/nft_limit.c
+@@ -243,7 +243,6 @@ static const struct nft_expr_ops nft_lim
+       .destroy        = nft_limit_pkts_destroy,
+       .clone          = nft_limit_pkts_clone,
+       .dump           = nft_limit_pkts_dump,
+-      .reduce         = NFT_REDUCE_READONLY,
+ };
+ static void nft_limit_bytes_eval(const struct nft_expr *expr,
+@@ -299,7 +298,6 @@ static const struct nft_expr_ops nft_lim
+       .dump           = nft_limit_bytes_dump,
+       .clone          = nft_limit_bytes_clone,
+       .destroy        = nft_limit_bytes_destroy,
+-      .reduce         = NFT_REDUCE_READONLY,
+ };
+ static const struct nft_expr_ops *
+--- a/net/netfilter/nft_log.c
++++ b/net/netfilter/nft_log.c
+@@ -291,7 +291,6 @@ static const struct nft_expr_ops nft_log
+       .init           = nft_log_init,
+       .destroy        = nft_log_destroy,
+       .dump           = nft_log_dump,
+-      .reduce         = NFT_REDUCE_READONLY,
+ };
+ static struct nft_expr_type nft_log_type __read_mostly = {
+--- a/net/netfilter/nft_lookup.c
++++ b/net/netfilter/nft_lookup.c
+@@ -269,17 +269,6 @@ static int nft_lookup_validate(const str
+       return 0;
+ }
+-static bool nft_lookup_reduce(struct nft_regs_track *track,
+-                            const struct nft_expr *expr)
+-{
+-      const struct nft_lookup *priv = nft_expr_priv(expr);
+-
+-      if (priv->set->flags & NFT_SET_MAP)
+-              nft_reg_track_cancel(track, priv->dreg, priv->set->dlen);
+-
+-      return false;
+-}
+-
+ static const struct nft_expr_ops nft_lookup_ops = {
+       .type           = &nft_lookup_type,
+       .size           = NFT_EXPR_SIZE(sizeof(struct nft_lookup)),
+@@ -290,7 +279,6 @@ static const struct nft_expr_ops nft_loo
+       .destroy        = nft_lookup_destroy,
+       .dump           = nft_lookup_dump,
+       .validate       = nft_lookup_validate,
+-      .reduce         = nft_lookup_reduce,
+ };
+ struct nft_expr_type nft_lookup_type __read_mostly = {
+--- a/net/netfilter/nft_masq.c
++++ b/net/netfilter/nft_masq.c
+@@ -143,7 +143,6 @@ static const struct nft_expr_ops nft_mas
+       .destroy        = nft_masq_ipv4_destroy,
+       .dump           = nft_masq_dump,
+       .validate       = nft_masq_validate,
+-      .reduce         = NFT_REDUCE_READONLY,
+ };
+ static struct nft_expr_type nft_masq_ipv4_type __read_mostly = {
+@@ -171,7 +170,6 @@ static const struct nft_expr_ops nft_mas
+       .destroy        = nft_masq_ipv6_destroy,
+       .dump           = nft_masq_dump,
+       .validate       = nft_masq_validate,
+-      .reduce         = NFT_REDUCE_READONLY,
+ };
+ static struct nft_expr_type nft_masq_ipv6_type __read_mostly = {
+@@ -213,7 +211,6 @@ static const struct nft_expr_ops nft_mas
+       .destroy        = nft_masq_inet_destroy,
+       .dump           = nft_masq_dump,
+       .validate       = nft_masq_validate,
+-      .reduce         = NFT_REDUCE_READONLY,
+ };
+ static struct nft_expr_type nft_masq_inet_type __read_mostly = {
+--- a/net/netfilter/nft_meta.c
++++ b/net/netfilter/nft_meta.c
+@@ -742,60 +742,16 @@ static int nft_meta_get_offload(struct n
+       return 0;
+ }
+-bool nft_meta_get_reduce(struct nft_regs_track *track,
+-                       const struct nft_expr *expr)
+-{
+-      const struct nft_meta *priv = nft_expr_priv(expr);
+-      const struct nft_meta *meta;
+-
+-      if (!nft_reg_track_cmp(track, expr, priv->dreg)) {
+-              nft_reg_track_update(track, expr, priv->dreg, priv->len);
+-              return false;
+-      }
+-
+-      meta = nft_expr_priv(track->regs[priv->dreg].selector);
+-      if (priv->key != meta->key ||
+-          priv->dreg != meta->dreg) {
+-              nft_reg_track_update(track, expr, priv->dreg, priv->len);
+-              return false;
+-      }
+-
+-      if (!track->regs[priv->dreg].bitwise)
+-              return true;
+-
+-      return nft_expr_reduce_bitwise(track, expr);
+-}
+-EXPORT_SYMBOL_GPL(nft_meta_get_reduce);
+-
+ static const struct nft_expr_ops nft_meta_get_ops = {
+       .type           = &nft_meta_type,
+       .size           = NFT_EXPR_SIZE(sizeof(struct nft_meta)),
+       .eval           = nft_meta_get_eval,
+       .init           = nft_meta_get_init,
+       .dump           = nft_meta_get_dump,
+-      .reduce         = nft_meta_get_reduce,
+       .validate       = nft_meta_get_validate,
+       .offload        = nft_meta_get_offload,
+ };
+-static bool nft_meta_set_reduce(struct nft_regs_track *track,
+-                              const struct nft_expr *expr)
+-{
+-      int i;
+-
+-      for (i = 0; i < NFT_REG32_NUM; i++) {
+-              if (!track->regs[i].selector)
+-                      continue;
+-
+-              if (track->regs[i].selector->ops != &nft_meta_get_ops)
+-                      continue;
+-
+-              __nft_reg_track_cancel(track, i);
+-      }
+-
+-      return false;
+-}
+-
+ static const struct nft_expr_ops nft_meta_set_ops = {
+       .type           = &nft_meta_type,
+       .size           = NFT_EXPR_SIZE(sizeof(struct nft_meta)),
+@@ -803,7 +759,6 @@ static const struct nft_expr_ops nft_met
+       .init           = nft_meta_set_init,
+       .destroy        = nft_meta_set_destroy,
+       .dump           = nft_meta_set_dump,
+-      .reduce         = nft_meta_set_reduce,
+       .validate       = nft_meta_set_validate,
+ };
+--- a/net/netfilter/nft_nat.c
++++ b/net/netfilter/nft_nat.c
+@@ -320,7 +320,6 @@ static const struct nft_expr_ops nft_nat
+       .destroy        = nft_nat_destroy,
+       .dump           = nft_nat_dump,
+       .validate       = nft_nat_validate,
+-      .reduce         = NFT_REDUCE_READONLY,
+ };
+ static struct nft_expr_type nft_nat_type __read_mostly = {
+@@ -351,7 +350,6 @@ static const struct nft_expr_ops nft_nat
+       .destroy        = nft_nat_destroy,
+       .dump           = nft_nat_dump,
+       .validate       = nft_nat_validate,
+-      .reduce         = NFT_REDUCE_READONLY,
+ };
+ static struct nft_expr_type nft_inet_nat_type __read_mostly = {
+--- a/net/netfilter/nft_numgen.c
++++ b/net/netfilter/nft_numgen.c
+@@ -84,16 +84,6 @@ err:
+       return err;
+ }
+-static bool nft_ng_inc_reduce(struct nft_regs_track *track,
+-                               const struct nft_expr *expr)
+-{
+-      const struct nft_ng_inc *priv = nft_expr_priv(expr);
+-
+-      nft_reg_track_cancel(track, priv->dreg, NFT_REG32_SIZE);
+-
+-      return false;
+-}
+-
+ static int nft_ng_dump(struct sk_buff *skb, enum nft_registers dreg,
+                      u32 modulus, enum nft_ng_types type, u32 offset)
+ {
+@@ -178,16 +168,6 @@ static int nft_ng_random_dump(struct sk_
+                          priv->offset);
+ }
+-static bool nft_ng_random_reduce(struct nft_regs_track *track,
+-                               const struct nft_expr *expr)
+-{
+-      const struct nft_ng_random *priv = nft_expr_priv(expr);
+-
+-      nft_reg_track_cancel(track, priv->dreg, NFT_REG32_SIZE);
+-
+-      return false;
+-}
+-
+ static struct nft_expr_type nft_ng_type;
+ static const struct nft_expr_ops nft_ng_inc_ops = {
+       .type           = &nft_ng_type,
+@@ -196,7 +176,6 @@ static const struct nft_expr_ops nft_ng_
+       .init           = nft_ng_inc_init,
+       .destroy        = nft_ng_inc_destroy,
+       .dump           = nft_ng_inc_dump,
+-      .reduce         = nft_ng_inc_reduce,
+ };
+ static const struct nft_expr_ops nft_ng_random_ops = {
+@@ -205,7 +184,6 @@ static const struct nft_expr_ops nft_ng_
+       .eval           = nft_ng_random_eval,
+       .init           = nft_ng_random_init,
+       .dump           = nft_ng_random_dump,
+-      .reduce         = nft_ng_random_reduce,
+ };
+ static const struct nft_expr_ops *
+--- a/net/netfilter/nft_objref.c
++++ b/net/netfilter/nft_objref.c
+@@ -123,7 +123,6 @@ static const struct nft_expr_ops nft_obj
+       .deactivate     = nft_objref_deactivate,
+       .dump           = nft_objref_dump,
+       .validate       = nft_objref_validate,
+-      .reduce         = NFT_REDUCE_READONLY,
+ };
+ struct nft_objref_map {
+@@ -245,7 +244,6 @@ static const struct nft_expr_ops nft_obj
+       .destroy        = nft_objref_map_destroy,
+       .dump           = nft_objref_map_dump,
+       .validate       = nft_objref_map_validate,
+-      .reduce         = NFT_REDUCE_READONLY,
+ };
+ static const struct nft_expr_ops *
+--- a/net/netfilter/nft_osf.c
++++ b/net/netfilter/nft_osf.c
+@@ -131,30 +131,6 @@ static int nft_osf_validate(const struct
+       return nft_chain_validate_hooks(ctx->chain, hooks);
+ }
+-static bool nft_osf_reduce(struct nft_regs_track *track,
+-                         const struct nft_expr *expr)
+-{
+-      struct nft_osf *priv = nft_expr_priv(expr);
+-      struct nft_osf *osf;
+-
+-      if (!nft_reg_track_cmp(track, expr, priv->dreg)) {
+-              nft_reg_track_update(track, expr, priv->dreg, NFT_OSF_MAXGENRELEN);
+-              return false;
+-      }
+-
+-      osf = nft_expr_priv(track->regs[priv->dreg].selector);
+-      if (priv->flags != osf->flags ||
+-          priv->ttl != osf->ttl) {
+-              nft_reg_track_update(track, expr, priv->dreg, NFT_OSF_MAXGENRELEN);
+-              return false;
+-      }
+-
+-      if (!track->regs[priv->dreg].bitwise)
+-              return true;
+-
+-      return false;
+-}
+-
+ static struct nft_expr_type nft_osf_type;
+ static const struct nft_expr_ops nft_osf_op = {
+       .eval           = nft_osf_eval,
+@@ -163,7 +139,6 @@ static const struct nft_expr_ops nft_osf
+       .dump           = nft_osf_dump,
+       .type           = &nft_osf_type,
+       .validate       = nft_osf_validate,
+-      .reduce         = nft_osf_reduce,
+ };
+ static struct nft_expr_type nft_osf_type __read_mostly = {
+--- a/net/netfilter/nft_payload.c
++++ b/net/netfilter/nft_payload.c
+@@ -250,31 +250,6 @@ nla_put_failure:
+       return -1;
+ }
+-static bool nft_payload_reduce(struct nft_regs_track *track,
+-                             const struct nft_expr *expr)
+-{
+-      const struct nft_payload *priv = nft_expr_priv(expr);
+-      const struct nft_payload *payload;
+-
+-      if (!nft_reg_track_cmp(track, expr, priv->dreg)) {
+-              nft_reg_track_update(track, expr, priv->dreg, priv->len);
+-              return false;
+-      }
+-
+-      payload = nft_expr_priv(track->regs[priv->dreg].selector);
+-      if (priv->base != payload->base ||
+-          priv->offset != payload->offset ||
+-          priv->len != payload->len) {
+-              nft_reg_track_update(track, expr, priv->dreg, priv->len);
+-              return false;
+-      }
+-
+-      if (!track->regs[priv->dreg].bitwise)
+-              return true;
+-
+-      return nft_expr_reduce_bitwise(track, expr);
+-}
+-
+ static bool nft_payload_offload_mask(struct nft_offload_reg *reg,
+                                    u32 priv_len, u32 field_len)
+ {
+@@ -578,7 +553,6 @@ static const struct nft_expr_ops nft_pay
+       .eval           = nft_payload_eval,
+       .init           = nft_payload_init,
+       .dump           = nft_payload_dump,
+-      .reduce         = nft_payload_reduce,
+       .offload        = nft_payload_offload,
+ };
+@@ -588,7 +562,6 @@ const struct nft_expr_ops nft_payload_fa
+       .eval           = nft_payload_eval,
+       .init           = nft_payload_init,
+       .dump           = nft_payload_dump,
+-      .reduce         = nft_payload_reduce,
+       .offload        = nft_payload_offload,
+ };
+@@ -1008,32 +981,12 @@ nla_put_failure:
+       return -1;
+ }
+-static bool nft_payload_set_reduce(struct nft_regs_track *track,
+-                                 const struct nft_expr *expr)
+-{
+-      int i;
+-
+-      for (i = 0; i < NFT_REG32_NUM; i++) {
+-              if (!track->regs[i].selector)
+-                      continue;
+-
+-              if (track->regs[i].selector->ops != &nft_payload_ops &&
+-                  track->regs[i].selector->ops != &nft_payload_fast_ops)
+-                      continue;
+-
+-              __nft_reg_track_cancel(track, i);
+-      }
+-
+-      return false;
+-}
+-
+ static const struct nft_expr_ops nft_payload_set_ops = {
+       .type           = &nft_payload_type,
+       .size           = NFT_EXPR_SIZE(sizeof(struct nft_payload_set)),
+       .eval           = nft_payload_set_eval,
+       .init           = nft_payload_set_init,
+       .dump           = nft_payload_set_dump,
+-      .reduce         = nft_payload_set_reduce,
+ };
+ static const struct nft_expr_ops *
+--- a/net/netfilter/nft_queue.c
++++ b/net/netfilter/nft_queue.c
+@@ -191,7 +191,6 @@ static const struct nft_expr_ops nft_que
+       .init           = nft_queue_init,
+       .dump           = nft_queue_dump,
+       .validate       = nft_queue_validate,
+-      .reduce         = NFT_REDUCE_READONLY,
+ };
+ static const struct nft_expr_ops nft_queue_sreg_ops = {
+@@ -201,7 +200,6 @@ static const struct nft_expr_ops nft_que
+       .init           = nft_queue_sreg_init,
+       .dump           = nft_queue_sreg_dump,
+       .validate       = nft_queue_validate,
+-      .reduce         = NFT_REDUCE_READONLY,
+ };
+ static const struct nft_expr_ops *
+--- a/net/netfilter/nft_quota.c
++++ b/net/netfilter/nft_quota.c
+@@ -266,7 +266,6 @@ static const struct nft_expr_ops nft_quo
+       .destroy        = nft_quota_destroy,
+       .clone          = nft_quota_clone,
+       .dump           = nft_quota_dump,
+-      .reduce         = NFT_REDUCE_READONLY,
+ };
+ static struct nft_expr_type nft_quota_type __read_mostly = {
+--- a/net/netfilter/nft_range.c
++++ b/net/netfilter/nft_range.c
+@@ -138,7 +138,6 @@ static const struct nft_expr_ops nft_ran
+       .eval           = nft_range_eval,
+       .init           = nft_range_init,
+       .dump           = nft_range_dump,
+-      .reduce         = NFT_REDUCE_READONLY,
+ };
+ struct nft_expr_type nft_range_type __read_mostly = {
+--- a/net/netfilter/nft_redir.c
++++ b/net/netfilter/nft_redir.c
+@@ -146,7 +146,6 @@ static const struct nft_expr_ops nft_red
+       .destroy        = nft_redir_ipv4_destroy,
+       .dump           = nft_redir_dump,
+       .validate       = nft_redir_validate,
+-      .reduce         = NFT_REDUCE_READONLY,
+ };
+ static struct nft_expr_type nft_redir_ipv4_type __read_mostly = {
+@@ -174,7 +173,6 @@ static const struct nft_expr_ops nft_red
+       .destroy        = nft_redir_ipv6_destroy,
+       .dump           = nft_redir_dump,
+       .validate       = nft_redir_validate,
+-      .reduce         = NFT_REDUCE_READONLY,
+ };
+ static struct nft_expr_type nft_redir_ipv6_type __read_mostly = {
+@@ -203,7 +201,6 @@ static const struct nft_expr_ops nft_red
+       .destroy        = nft_redir_inet_destroy,
+       .dump           = nft_redir_dump,
+       .validate       = nft_redir_validate,
+-      .reduce         = NFT_REDUCE_READONLY,
+ };
+ static struct nft_expr_type nft_redir_inet_type __read_mostly = {
+--- a/net/netfilter/nft_reject_inet.c
++++ b/net/netfilter/nft_reject_inet.c
+@@ -79,7 +79,6 @@ static const struct nft_expr_ops nft_rej
+       .init           = nft_reject_init,
+       .dump           = nft_reject_dump,
+       .validate       = nft_reject_inet_validate,
+-      .reduce         = NFT_REDUCE_READONLY,
+ };
+ static struct nft_expr_type nft_reject_inet_type __read_mostly = {
+--- a/net/netfilter/nft_reject_netdev.c
++++ b/net/netfilter/nft_reject_netdev.c
+@@ -158,7 +158,6 @@ static const struct nft_expr_ops nft_rej
+       .init           = nft_reject_init,
+       .dump           = nft_reject_dump,
+       .validate       = nft_reject_netdev_validate,
+-      .reduce         = NFT_REDUCE_READONLY,
+ };
+ static struct nft_expr_type nft_reject_netdev_type __read_mostly = {
+--- a/net/netfilter/nft_rt.c
++++ b/net/netfilter/nft_rt.c
+@@ -195,7 +195,6 @@ static const struct nft_expr_ops nft_rt_
+       .init           = nft_rt_get_init,
+       .dump           = nft_rt_get_dump,
+       .validate       = nft_rt_validate,
+-      .reduce         = NFT_REDUCE_READONLY,
+ };
+ struct nft_expr_type nft_rt_type __read_mostly = {
+--- a/net/netfilter/nft_socket.c
++++ b/net/netfilter/nft_socket.c
+@@ -249,31 +249,6 @@ static int nft_socket_dump(struct sk_buf
+       return 0;
+ }
+-static bool nft_socket_reduce(struct nft_regs_track *track,
+-                            const struct nft_expr *expr)
+-{
+-      const struct nft_socket *priv = nft_expr_priv(expr);
+-      const struct nft_socket *socket;
+-
+-      if (!nft_reg_track_cmp(track, expr, priv->dreg)) {
+-              nft_reg_track_update(track, expr, priv->dreg, priv->len);
+-              return false;
+-      }
+-
+-      socket = nft_expr_priv(track->regs[priv->dreg].selector);
+-      if (priv->key != socket->key ||
+-          priv->dreg != socket->dreg ||
+-          priv->level != socket->level) {
+-              nft_reg_track_update(track, expr, priv->dreg, priv->len);
+-              return false;
+-      }
+-
+-      if (!track->regs[priv->dreg].bitwise)
+-              return true;
+-
+-      return nft_expr_reduce_bitwise(track, expr);
+-}
+-
+ static int nft_socket_validate(const struct nft_ctx *ctx,
+                              const struct nft_expr *expr)
+ {
+@@ -296,7 +271,6 @@ static const struct nft_expr_ops nft_soc
+       .init           = nft_socket_init,
+       .dump           = nft_socket_dump,
+       .validate       = nft_socket_validate,
+-      .reduce         = nft_socket_reduce,
+ };
+ static struct nft_expr_type nft_socket_type __read_mostly = {
+--- a/net/netfilter/nft_synproxy.c
++++ b/net/netfilter/nft_synproxy.c
+@@ -290,7 +290,6 @@ static const struct nft_expr_ops nft_syn
+       .dump           = nft_synproxy_dump,
+       .type           = &nft_synproxy_type,
+       .validate       = nft_synproxy_validate,
+-      .reduce         = NFT_REDUCE_READONLY,
+ };
+ static struct nft_expr_type nft_synproxy_type __read_mostly = {
+--- a/net/netfilter/nft_tproxy.c
++++ b/net/netfilter/nft_tproxy.c
+@@ -331,7 +331,6 @@ static const struct nft_expr_ops nft_tpr
+       .init           = nft_tproxy_init,
+       .destroy        = nft_tproxy_destroy,
+       .dump           = nft_tproxy_dump,
+-      .reduce         = NFT_REDUCE_READONLY,
+       .validate       = nft_tproxy_validate,
+ };
+--- a/net/netfilter/nft_tunnel.c
++++ b/net/netfilter/nft_tunnel.c
+@@ -124,31 +124,6 @@ nla_put_failure:
+       return -1;
+ }
+-static bool nft_tunnel_get_reduce(struct nft_regs_track *track,
+-                                const struct nft_expr *expr)
+-{
+-      const struct nft_tunnel *priv = nft_expr_priv(expr);
+-      const struct nft_tunnel *tunnel;
+-
+-      if (!nft_reg_track_cmp(track, expr, priv->dreg)) {
+-              nft_reg_track_update(track, expr, priv->dreg, priv->len);
+-              return false;
+-      }
+-
+-      tunnel = nft_expr_priv(track->regs[priv->dreg].selector);
+-      if (priv->key != tunnel->key ||
+-          priv->dreg != tunnel->dreg ||
+-          priv->mode != tunnel->mode) {
+-              nft_reg_track_update(track, expr, priv->dreg, priv->len);
+-              return false;
+-      }
+-
+-      if (!track->regs[priv->dreg].bitwise)
+-              return true;
+-
+-      return false;
+-}
+-
+ static struct nft_expr_type nft_tunnel_type;
+ static const struct nft_expr_ops nft_tunnel_get_ops = {
+       .type           = &nft_tunnel_type,
+@@ -156,7 +131,6 @@ static const struct nft_expr_ops nft_tun
+       .eval           = nft_tunnel_get_eval,
+       .init           = nft_tunnel_get_init,
+       .dump           = nft_tunnel_get_dump,
+-      .reduce         = nft_tunnel_get_reduce,
+ };
+ static struct nft_expr_type nft_tunnel_type __read_mostly = {
+--- a/net/netfilter/nft_xfrm.c
++++ b/net/netfilter/nft_xfrm.c
+@@ -258,32 +258,6 @@ static int nft_xfrm_validate(const struc
+       return nft_chain_validate_hooks(ctx->chain, hooks);
+ }
+-static bool nft_xfrm_reduce(struct nft_regs_track *track,
+-                          const struct nft_expr *expr)
+-{
+-      const struct nft_xfrm *priv = nft_expr_priv(expr);
+-      const struct nft_xfrm *xfrm;
+-
+-      if (!nft_reg_track_cmp(track, expr, priv->dreg)) {
+-              nft_reg_track_update(track, expr, priv->dreg, priv->len);
+-              return false;
+-      }
+-
+-      xfrm = nft_expr_priv(track->regs[priv->dreg].selector);
+-      if (priv->key != xfrm->key ||
+-          priv->dreg != xfrm->dreg ||
+-          priv->dir != xfrm->dir ||
+-          priv->spnum != xfrm->spnum) {
+-              nft_reg_track_update(track, expr, priv->dreg, priv->len);
+-              return false;
+-      }
+-
+-      if (!track->regs[priv->dreg].bitwise)
+-              return true;
+-
+-      return nft_expr_reduce_bitwise(track, expr);
+-}
+-
+ static struct nft_expr_type nft_xfrm_type;
+ static const struct nft_expr_ops nft_xfrm_get_ops = {
+       .type           = &nft_xfrm_type,
+@@ -292,7 +266,6 @@ static const struct nft_expr_ops nft_xfr
+       .init           = nft_xfrm_get_init,
+       .dump           = nft_xfrm_get_dump,
+       .validate       = nft_xfrm_validate,
+-      .reduce         = nft_xfrm_reduce,
+ };
+ static struct nft_expr_type nft_xfrm_type __read_mostly = {
diff --git a/queue-6.12/netfilter-nf_tables-remove-unused-nft_reduce_is_readonly.patch b/queue-6.12/netfilter-nf_tables-remove-unused-nft_reduce_is_readonly.patch
new file mode 100644 (file)
index 0000000..09aca47
--- /dev/null
@@ -0,0 +1,39 @@
+From stable+bounces-288425-greg=kroah.com@vger.kernel.org Thu Jul 23 19:06:00 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 12:54:17 -0400
+Subject: netfilter: nf_tables: Remove unused nft_reduce_is_readonly()
+To: stable@vger.kernel.org
+Cc: Yue Haibing <yuehaibing@huawei.com>, Simon Horman <horms@kernel.org>, Pablo Neira Ayuso <pablo@netfilter.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723165419.3390514-2-sashal@kernel.org>
+
+From: Yue Haibing <yuehaibing@huawei.com>
+
+[ Upstream commit bf6788742b8d6c73de441e088a71de7154f0d4aa ]
+
+Since commit 9e539c5b6d9c ("netfilter: nf_tables: disable expression
+reduction infra") this is unused.
+
+Signed-off-by: Yue Haibing <yuehaibing@huawei.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
+Stable-dep-of: d07955dd34ec ("netfilter: nft_fib: reject fib expression on the netdev egress hook")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/net/netfilter/nf_tables.h |    5 -----
+ 1 file changed, 5 deletions(-)
+
+--- a/include/net/netfilter/nf_tables.h
++++ b/include/net/netfilter/nf_tables.h
+@@ -1954,11 +1954,6 @@ static inline u64 nft_net_tstamp(const s
+ #define __NFT_REDUCE_READONLY 1UL
+ #define NFT_REDUCE_READONLY   (void *)__NFT_REDUCE_READONLY
+-static inline bool nft_reduce_is_readonly(const struct nft_expr *expr)
+-{
+-      return expr->ops->reduce == NFT_REDUCE_READONLY;
+-}
+-
+ void nft_reg_track_update(struct nft_regs_track *track,
+                         const struct nft_expr *expr, u8 dreg, u8 len);
+ void nft_reg_track_cancel(struct nft_regs_track *track, u8 dreg, u8 len);
diff --git a/queue-6.12/netfilter-nft_fib-reject-fib-expression-on-the-netdev-egress-hook.patch b/queue-6.12/netfilter-nft_fib-reject-fib-expression-on-the-netdev-egress-hook.patch
new file mode 100644 (file)
index 0000000..238443f
--- /dev/null
@@ -0,0 +1,100 @@
+From stable+bounces-288427-greg=kroah.com@vger.kernel.org Thu Jul 23 19:06:09 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 12:54:19 -0400
+Subject: netfilter: nft_fib: reject fib expression on the netdev egress hook
+To: stable@vger.kernel.org
+Cc: Theodor Arsenij Larionov-Trichkine <theodorlarionov@gmail.com>, Florian Westphal <fw@strlen.de>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723165419.3390514-4-sashal@kernel.org>
+
+From: Theodor Arsenij Larionov-Trichkine <theodorlarionov@gmail.com>
+
+[ Upstream commit d07955dd34ecae17d35d8c7d0a273a3fba653a8c ]
+
+A fib expression in a netdev egress base chain dereferences nft_in(pkt),
+NULL on the transmit path, causing a NULL pointer dereference at eval.
+nft_fib_validate() masks the hook with NF_INET_* values, but netdev hook
+numbers are a separate enum that aliases them (NF_NETDEV_EGRESS ==
+NF_INET_LOCAL_IN), so an egress chain passes validation and then faults.
+
+Add nft_fib_netdev_validate() that limits each result/flag to the netdev
+hook where the device it reads exists: the input-device cases (OIF,
+OIFNAME, ADDRTYPE with F_IIF) to ingress, the output-device case (ADDRTYPE
+with F_OIF) to egress, ADDRTYPE with no device flag to both. Also restrict
+nft_fib_validate() to NFPROTO_IPV4/IPV6/INET so its NF_INET_* masks are
+not applied to another family's hooks.
+
+Fixes: 42df6e1d221d ("netfilter: Introduce egress hook")
+Cc: stable@vger.kernel.org
+Link: https://lore.kernel.org/netfilter-devel/ajxsjcDOnwllMfoR@strlen.de/
+Signed-off-by: Theodor Arsenij Larionov-Trichkine <theodorlarionov@gmail.com>
+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/netfilter/nft_fib.c        |    9 +++++++++
+ net/netfilter/nft_fib_netdev.c |   29 ++++++++++++++++++++++++++++-
+ 2 files changed, 37 insertions(+), 1 deletion(-)
+
+--- a/net/netfilter/nft_fib.c
++++ b/net/netfilter/nft_fib.c
+@@ -31,6 +31,15 @@ int nft_fib_validate(const struct nft_ct
+       const struct nft_fib *priv = nft_expr_priv(expr);
+       unsigned int hooks;
++      switch (ctx->family) {
++      case NFPROTO_IPV4:
++      case NFPROTO_IPV6:
++      case NFPROTO_INET:
++              break;
++      default:
++              return -EOPNOTSUPP;
++      }
++
+       switch (priv->result) {
+       case NFT_FIB_RESULT_OIF:
+       case NFT_FIB_RESULT_OIFNAME:
+--- a/net/netfilter/nft_fib_netdev.c
++++ b/net/netfilter/nft_fib_netdev.c
+@@ -50,6 +50,33 @@ static void nft_fib_netdev_eval(const st
+       regs->verdict.code = NFT_BREAK;
+ }
++static int nft_fib_netdev_validate(const struct nft_ctx *ctx,
++                                 const struct nft_expr *expr)
++{
++      const struct nft_fib *priv = nft_expr_priv(expr);
++      unsigned int hooks;
++
++      switch (priv->result) {
++      case NFT_FIB_RESULT_OIF:
++      case NFT_FIB_RESULT_OIFNAME:
++              hooks = (1 << NF_NETDEV_INGRESS);
++              break;
++      case NFT_FIB_RESULT_ADDRTYPE:
++              if (priv->flags & NFTA_FIB_F_IIF)
++                      hooks = (1 << NF_NETDEV_INGRESS);
++              else if (priv->flags & NFTA_FIB_F_OIF)
++                      hooks = (1 << NF_NETDEV_EGRESS);
++              else
++                      hooks = (1 << NF_NETDEV_INGRESS) |
++                              (1 << NF_NETDEV_EGRESS);
++              break;
++      default:
++              return -EINVAL;
++      }
++
++      return nft_chain_validate_hooks(ctx->chain, hooks);
++}
++
+ static struct nft_expr_type nft_fib_netdev_type;
+ static const struct nft_expr_ops nft_fib_netdev_ops = {
+       .type           = &nft_fib_netdev_type,
+@@ -57,7 +84,7 @@ static const struct nft_expr_ops nft_fib
+       .eval           = nft_fib_netdev_eval,
+       .init           = nft_fib_init,
+       .dump           = nft_fib_dump,
+-      .validate       = nft_fib_validate,
++      .validate       = nft_fib_netdev_validate,
+ };
+ static struct nft_expr_type nft_fib_netdev_type __read_mostly = {
diff --git a/queue-6.12/nfsd-pass-nfsd_file-to-nfsd_iter_read.patch b/queue-6.12/nfsd-pass-nfsd_file-to-nfsd_iter_read.patch
new file mode 100644 (file)
index 0000000..02351c5
--- /dev/null
@@ -0,0 +1,116 @@
+From stable+bounces-288530-greg=kroah.com@vger.kernel.org Thu Jul 23 22:01:08 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 15:58:59 -0400
+Subject: NFSD: pass nfsd_file to nfsd_iter_read()
+To: stable@vger.kernel.org
+Cc: Mike Snitzer <snitzer@kernel.org>, Jeff Layton <jlayton@kernel.org>, NeilBrown <neil@brown.name>, Christoph Hellwig <hch@lst.de>, Chuck Lever <chuck.lever@oracle.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723195904.3685200-1-sashal@kernel.org>
+
+From: Mike Snitzer <snitzer@kernel.org>
+
+[ Upstream commit 803bc849f0039291f546ba0e2237faebeb5c073e ]
+
+Prepare for nfsd_iter_read() to use the DIO alignment stored in
+nfsd_file by passing the nfsd_file to nfsd_iter_read() rather than
+just the file which is associaed with the nfsd_file.
+
+This means nfsd4_encode_readv() now also needs the nfsd_file rather
+than the file.  Instead of changing the file arg to be the nfsd_file,
+we discard the file arg as the nfsd_file (and indeed the file) is
+already available via the "read" argument.
+
+Signed-off-by: Mike Snitzer <snitzer@kernel.org>
+Reviewed-by: Jeff Layton <jlayton@kernel.org>
+Reviewed-by: NeilBrown <neil@brown.name>
+Reviewed-by: Christoph Hellwig <hch@lst.de>
+Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
+Stable-dep-of: 18c1cc698861 ("SUNRPC: Return an error from xdr_buf_to_bvec() on overflow")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/nfsd/nfs4xdr.c |    8 ++++----
+ fs/nfsd/vfs.c     |    7 ++++---
+ fs/nfsd/vfs.h     |    2 +-
+ 3 files changed, 9 insertions(+), 8 deletions(-)
+
+--- a/fs/nfsd/nfs4xdr.c
++++ b/fs/nfsd/nfs4xdr.c
+@@ -4384,7 +4384,7 @@ out_err:
+ static __be32 nfsd4_encode_readv(struct nfsd4_compoundres *resp,
+                                struct nfsd4_read *read,
+-                               struct file *file, unsigned long maxcount)
++                               unsigned long maxcount)
+ {
+       struct xdr_stream *xdr = resp->xdr;
+       unsigned int base = xdr->buf->page_len & ~PAGE_MASK;
+@@ -4395,7 +4395,7 @@ static __be32 nfsd4_encode_readv(struct
+       if (xdr_reserve_space_vec(xdr, maxcount) < 0)
+               return nfserr_resource;
+-      nfserr = nfsd_iter_read(resp->rqstp, read->rd_fhp, file,
++      nfserr = nfsd_iter_read(resp->rqstp, read->rd_fhp, read->rd_nf,
+                               read->rd_offset, &maxcount, base,
+                               &read->rd_eof);
+       read->rd_length = maxcount;
+@@ -4444,7 +4444,7 @@ nfsd4_encode_read(struct nfsd4_compoundr
+       if (file->f_op->splice_read && splice_ok)
+               nfserr = nfsd4_encode_splice_read(resp, read, file, maxcount);
+       else
+-              nfserr = nfsd4_encode_readv(resp, read, file, maxcount);
++              nfserr = nfsd4_encode_readv(resp, read, maxcount);
+       if (nfserr) {
+               xdr_truncate_encode(xdr, starting_len);
+               return nfserr;
+@@ -5311,7 +5311,7 @@ nfsd4_encode_read_plus_data(struct nfsd4
+       if (file->f_op->splice_read && splice_ok)
+               nfserr = nfsd4_encode_splice_read(resp, read, file, maxcount);
+       else
+-              nfserr = nfsd4_encode_readv(resp, read, file, maxcount);
++              nfserr = nfsd4_encode_readv(resp, read, maxcount);
+       if (nfserr)
+               return nfserr;
+--- a/fs/nfsd/vfs.c
++++ b/fs/nfsd/vfs.c
+@@ -1067,7 +1067,7 @@ __be32 nfsd_splice_read(struct svc_rqst
+  * nfsd_iter_read - Perform a VFS read using an iterator
+  * @rqstp: RPC transaction context
+  * @fhp: file handle of file to be read
+- * @file: opened struct file of file to be read
++ * @nf: opened struct nfsd_file of file to be read
+  * @offset: starting byte offset
+  * @count: IN: requested number of bytes; OUT: number of bytes read
+  * @base: offset in first page of read buffer
+@@ -1080,9 +1080,10 @@ __be32 nfsd_splice_read(struct svc_rqst
+  * returned.
+  */
+ __be32 nfsd_iter_read(struct svc_rqst *rqstp, struct svc_fh *fhp,
+-                    struct file *file, loff_t offset, unsigned long *count,
++                    struct nfsd_file *nf, loff_t offset, unsigned long *count,
+                     unsigned int base, u32 *eof)
+ {
++      struct file *file = nf->nf_file;
+       unsigned long v, total;
+       struct iov_iter iter;
+       loff_t ppos = offset;
+@@ -1283,7 +1284,7 @@ __be32 nfsd_read(struct svc_rqst *rqstp,
+       if (file->f_op->splice_read && nfsd_read_splice_ok(rqstp))
+               err = nfsd_splice_read(rqstp, fhp, file, offset, count, eof);
+       else
+-              err = nfsd_iter_read(rqstp, fhp, file, offset, count, 0, eof);
++              err = nfsd_iter_read(rqstp, fhp, nf, offset, count, 0, eof);
+       nfsd_file_put(nf);
+       trace_nfsd_read_done(rqstp, fhp, offset, *count);
+--- a/fs/nfsd/vfs.h
++++ b/fs/nfsd/vfs.h
+@@ -122,7 +122,7 @@ __be32             nfsd_splice_read(struct svc_rqst
+                               unsigned long *count,
+                               u32 *eof);
+ __be32                nfsd_iter_read(struct svc_rqst *rqstp, struct svc_fh *fhp,
+-                              struct file *file, loff_t offset,
++                              struct nfsd_file *nf, loff_t offset,
+                               unsigned long *count, unsigned int base,
+                               u32 *eof);
+ bool          nfsd_read_splice_ok(struct svc_rqst *rqstp);
diff --git a/queue-6.12/nvmet-auth-reject-short-auth_receive-buffers.patch b/queue-6.12/nvmet-auth-reject-short-auth_receive-buffers.patch
new file mode 100644 (file)
index 0000000..528929e
--- /dev/null
@@ -0,0 +1,85 @@
+From stable+bounces-289491-greg=kroah.com@vger.kernel.org Mon Jul 27 14:49:33 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 08:43:59 -0400
+Subject: nvmet-auth: reject short AUTH_RECEIVE buffers
+To: stable@vger.kernel.org
+Cc: Michael Bommarito <michael.bommarito@gmail.com>, Hannes Reinecke <hare@kernel.org>, Keith Busch <kbusch@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727124359.172046-2-sashal@kernel.org>
+
+From: Michael Bommarito <michael.bommarito@gmail.com>
+
+[ Upstream commit 779575bc35c687697ba69e904f2cd22e60112534 ]
+
+nvmet_execute_auth_receive() trusts the AUTH_RECEIVE allocation length
+after checking only that it is nonzero and matches the transfer length.
+In the SUCCESS1 and FAILURE1/default states, that lets a remote NVMe-oF
+initiator reach the fixed-size DH-HMAC-CHAP response builders with a
+kmalloc() buffer shorter than the response, so nvmet_auth_success1() and
+nvmet_auth_failure1() write past the allocation; both only WARN_ON the
+short length and then format the message anyway.
+
+Impact: A remote NVMe-oF initiator with access to an auth-enabled target
+can trigger a 16-byte heap out-of-bounds write via a one-byte
+AUTH_RECEIVE allocation length.
+
+Compute the minimum response length for the current DH-HMAC-CHAP step in
+nvmet_auth_receive_data_len() and report a zero data length when the
+host-supplied allocation length is shorter, so the existing zero-length
+check in nvmet_execute_auth_receive() rejects the command before any
+builder runs. The SUCCESS1 minimum is sizeof(struct
+nvmf_auth_dhchap_success1_data) plus the HMAC hash length, because the
+response hash is written into the rval[] flexible-array tail, so the
+minimum is state dependent rather than a flat sizeof. CHALLENGE keeps its
+existing variable-length guard in nvmet_auth_challenge().
+
+This is reachable only when in-band DH-HMAC-CHAP authentication is
+configured on the target.
+
+Fixes: db1312dd9548 ("nvmet: implement basic In-Band Authentication")
+Cc: stable@vger.kernel.org
+Assisted-by: Codex:gpt-5-5-xhigh
+Assisted-by: Claude:claude-opus-4-8
+Reviewed-by: Hannes Reinecke <hare@kernel.org>
+Signed-off-by: Michael Bommarito <michael.bommarito@gmail.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 |   26 +++++++++++++++++++++++++-
+ 1 file changed, 25 insertions(+), 1 deletion(-)
+
+--- a/drivers/nvme/target/fabrics-cmd-auth.c
++++ b/drivers/nvme/target/fabrics-cmd-auth.c
+@@ -445,7 +445,31 @@ static void nvmet_auth_failure1(struct n
+ u32 nvmet_auth_receive_data_len(struct nvmet_req *req)
+ {
+-      return le32_to_cpu(req->cmd->auth_receive.al);
++      struct nvmet_ctrl *ctrl = req->sq->ctrl;
++      u32 al = le32_to_cpu(req->cmd->auth_receive.al);
++      u32 min_len;
++
++      /*
++       * Reject too-short al before kmalloc(al), since the SUCCESS1 and
++       * FAILURE1/default builders write fixed response headers into it.
++       */
++      switch (req->sq->dhchap_step) {
++      case NVME_AUTH_DHCHAP_MESSAGE_CHALLENGE:
++              return al;
++      case NVME_AUTH_DHCHAP_MESSAGE_SUCCESS1:
++              min_len = sizeof(struct nvmf_auth_dhchap_success1_data);
++              if (req->sq->dhchap_c2)
++                      min_len += nvme_auth_hmac_hash_len(ctrl->shash_id);
++              break;
++      default:
++              min_len = sizeof(struct nvmf_auth_dhchap_failure_data);
++              break;
++      }
++
++      if (al < min_len)
++              return 0;
++
++      return al;
+ }
+ void nvmet_execute_auth_receive(struct nvmet_req *req)
diff --git a/queue-6.12/nvmet-introduce-nvmet_req_transfer_len.patch b/queue-6.12/nvmet-introduce-nvmet_req_transfer_len.patch
new file mode 100644 (file)
index 0000000..2466336
--- /dev/null
@@ -0,0 +1,289 @@
+From stable+bounces-289490-greg=kroah.com@vger.kernel.org Mon Jul 27 14:49:29 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 08:43:58 -0400
+Subject: nvmet: Introduce nvmet_req_transfer_len()
+To: stable@vger.kernel.org
+Cc: Damien Le Moal <dlemoal@kernel.org>, Christoph Hellwig <hch@lst.de>, Rick Wertenbroek <rick.wertenbroek@gmail.com>, Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>, Keith Busch <kbusch@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727124359.172046-1-sashal@kernel.org>
+
+From: Damien Le Moal <dlemoal@kernel.org>
+
+[ Upstream commit 43043c9b97258a008b3402cfbbf1c5d82151c77f ]
+
+Add the new function nvmet_req_transfer_len() to parse a request command
+to extract the transfer length of the command. This function
+implementation relies on multiple helper functions for parsing I/O
+commands (nvmet_io_cmd_transfer_len()), admin commands
+(nvmet_admin_cmd_data_len()) and fabrics connect commands
+(nvmet_connect_cmd_data_len).
+
+Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
+Reviewed-by: Christoph Hellwig <hch@lst.de>
+Tested-by: Rick Wertenbroek <rick.wertenbroek@gmail.com>
+Tested-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
+Signed-off-by: Keith Busch <kbusch@kernel.org>
+Stable-dep-of: 779575bc35c6 ("nvmet-auth: reject short AUTH_RECEIVE buffers")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvme/target/admin-cmd.c        |   21 ++++++++++++++++
+ drivers/nvme/target/core.c             |   37 ++++++++++++++++++++++++++++
+ drivers/nvme/target/discovery.c        |   14 ++++++++++
+ drivers/nvme/target/fabrics-cmd-auth.c |   14 +++++++++-
+ drivers/nvme/target/fabrics-cmd.c      |   43 +++++++++++++++++++++++++++++++++
+ drivers/nvme/target/nvmet.h            |    8 ++++++
+ 6 files changed, 135 insertions(+), 2 deletions(-)
+
+--- a/drivers/nvme/target/admin-cmd.c
++++ b/drivers/nvme/target/admin-cmd.c
+@@ -1008,6 +1008,27 @@ out:
+       nvmet_req_complete(req, status);
+ }
++u32 nvmet_admin_cmd_data_len(struct nvmet_req *req)
++{
++      struct nvme_command *cmd = req->cmd;
++
++      if (nvme_is_fabrics(cmd))
++              return nvmet_fabrics_admin_cmd_data_len(req);
++      if (nvmet_is_disc_subsys(nvmet_req_subsys(req)))
++              return nvmet_discovery_cmd_data_len(req);
++
++      switch (cmd->common.opcode) {
++      case nvme_admin_get_log_page:
++              return nvmet_get_log_page_len(cmd);
++      case nvme_admin_identify:
++              return NVME_IDENTIFY_DATA_SIZE;
++      case nvme_admin_get_features:
++              return nvmet_feat_data_len(req, le32_to_cpu(cmd->common.cdw10));
++      default:
++              return 0;
++      }
++}
++
+ u16 nvmet_parse_admin_cmd(struct nvmet_req *req)
+ {
+       struct nvme_command *cmd = req->cmd;
+--- a/drivers/nvme/target/core.c
++++ b/drivers/nvme/target/core.c
+@@ -891,6 +891,33 @@ static inline u16 nvmet_io_cmd_check_acc
+       return 0;
+ }
++static u32 nvmet_io_cmd_transfer_len(struct nvmet_req *req)
++{
++      struct nvme_command *cmd = req->cmd;
++      u32 metadata_len = 0;
++
++      if (nvme_is_fabrics(cmd))
++              return nvmet_fabrics_io_cmd_data_len(req);
++
++      if (!req->ns)
++              return 0;
++
++      switch (req->cmd->common.opcode) {
++      case nvme_cmd_read:
++      case nvme_cmd_write:
++      case nvme_cmd_zone_append:
++              if (req->sq->ctrl->pi_support && nvmet_ns_has_pi(req->ns))
++                      metadata_len = nvmet_rw_metadata_len(req);
++              return nvmet_rw_data_len(req) + metadata_len;
++      case nvme_cmd_dsm:
++              return nvmet_dsm_len(req);
++      case nvme_cmd_zone_mgmt_recv:
++              return (le32_to_cpu(req->cmd->zmr.numd) + 1) << 2;
++      default:
++              return 0;
++      }
++}
++
+ static u16 nvmet_parse_io_cmd(struct nvmet_req *req)
+ {
+       struct nvme_command *cmd = req->cmd;
+@@ -1015,6 +1042,16 @@ void nvmet_req_uninit(struct nvmet_req *
+ }
+ EXPORT_SYMBOL_GPL(nvmet_req_uninit);
++size_t nvmet_req_transfer_len(struct nvmet_req *req)
++{
++      if (likely(req->sq->qid != 0))
++              return nvmet_io_cmd_transfer_len(req);
++      if (unlikely(!req->sq->ctrl))
++              return nvmet_connect_cmd_data_len(req);
++      return nvmet_admin_cmd_data_len(req);
++}
++EXPORT_SYMBOL_GPL(nvmet_req_transfer_len);
++
+ bool nvmet_check_transfer_len(struct nvmet_req *req, size_t len)
+ {
+       if (unlikely(len != req->transfer_len)) {
+--- a/drivers/nvme/target/discovery.c
++++ b/drivers/nvme/target/discovery.c
+@@ -373,6 +373,20 @@ static void nvmet_execute_disc_get_featu
+       nvmet_req_complete(req, stat);
+ }
++u32 nvmet_discovery_cmd_data_len(struct nvmet_req *req)
++{
++      struct nvme_command *cmd = req->cmd;
++
++      switch (cmd->common.opcode) {
++      case nvme_admin_get_log_page:
++              return nvmet_get_log_page_len(req->cmd);
++      case nvme_admin_identify:
++              return NVME_IDENTIFY_DATA_SIZE;
++      default:
++              return 0;
++      }
++}
++
+ u16 nvmet_parse_discovery_cmd(struct nvmet_req *req)
+ {
+       struct nvme_command *cmd = req->cmd;
+--- a/drivers/nvme/target/fabrics-cmd-auth.c
++++ b/drivers/nvme/target/fabrics-cmd-auth.c
+@@ -188,6 +188,11 @@ static u8 nvmet_auth_failure2(void *d)
+       return data->rescode_exp;
+ }
++u32 nvmet_auth_send_data_len(struct nvmet_req *req)
++{
++      return le32_to_cpu(req->cmd->auth_send.tl);
++}
++
+ void nvmet_execute_auth_send(struct nvmet_req *req)
+ {
+       struct nvmet_ctrl *ctrl = req->sq->ctrl;
+@@ -215,7 +220,7 @@ void nvmet_execute_auth_send(struct nvme
+                       offsetof(struct nvmf_auth_send_command, spsp1);
+               goto done;
+       }
+-      tl = le32_to_cpu(req->cmd->auth_send.tl);
++      tl = nvmet_auth_send_data_len(req);
+       if (!tl) {
+               status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
+               req->error_loc =
+@@ -438,6 +443,11 @@ static void nvmet_auth_failure1(struct n
+       data->rescode_exp = req->sq->dhchap_status;
+ }
++u32 nvmet_auth_receive_data_len(struct nvmet_req *req)
++{
++      return le32_to_cpu(req->cmd->auth_receive.al);
++}
++
+ void nvmet_execute_auth_receive(struct nvmet_req *req)
+ {
+       struct nvmet_ctrl *ctrl = req->sq->ctrl;
+@@ -463,7 +473,7 @@ void nvmet_execute_auth_receive(struct n
+                       offsetof(struct nvmf_auth_receive_command, spsp1);
+               goto done;
+       }
+-      al = le32_to_cpu(req->cmd->auth_receive.al);
++      al = nvmet_auth_receive_data_len(req);
+       if (!al) {
+               status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
+               req->error_loc =
+--- a/drivers/nvme/target/fabrics-cmd.c
++++ b/drivers/nvme/target/fabrics-cmd.c
+@@ -82,6 +82,22 @@ static void nvmet_execute_prop_get(struc
+       nvmet_req_complete(req, status);
+ }
++u32 nvmet_fabrics_admin_cmd_data_len(struct nvmet_req *req)
++{
++      struct nvme_command *cmd = req->cmd;
++
++      switch (cmd->fabrics.fctype) {
++#ifdef CONFIG_NVME_TARGET_AUTH
++      case nvme_fabrics_type_auth_send:
++              return nvmet_auth_send_data_len(req);
++      case nvme_fabrics_type_auth_receive:
++              return nvmet_auth_receive_data_len(req);
++#endif
++      default:
++              return 0;
++      }
++}
++
+ u16 nvmet_parse_fabrics_admin_cmd(struct nvmet_req *req)
+ {
+       struct nvme_command *cmd = req->cmd;
+@@ -111,6 +127,22 @@ u16 nvmet_parse_fabrics_admin_cmd(struct
+       return 0;
+ }
++u32 nvmet_fabrics_io_cmd_data_len(struct nvmet_req *req)
++{
++      struct nvme_command *cmd = req->cmd;
++
++      switch (cmd->fabrics.fctype) {
++#ifdef CONFIG_NVME_TARGET_AUTH
++      case nvme_fabrics_type_auth_send:
++              return nvmet_auth_send_data_len(req);
++      case nvme_fabrics_type_auth_receive:
++              return nvmet_auth_receive_data_len(req);
++#endif
++      default:
++              return 0;
++      }
++}
++
+ u16 nvmet_parse_fabrics_io_cmd(struct nvmet_req *req)
+ {
+       struct nvme_command *cmd = req->cmd;
+@@ -342,6 +374,17 @@ out_ctrl_put:
+       goto out;
+ }
++u32 nvmet_connect_cmd_data_len(struct nvmet_req *req)
++{
++      struct nvme_command *cmd = req->cmd;
++
++      if (!nvme_is_fabrics(cmd) ||
++          cmd->fabrics.fctype != nvme_fabrics_type_connect)
++              return 0;
++
++      return sizeof(struct nvmf_connect_data);
++}
++
+ u16 nvmet_parse_connect_cmd(struct nvmet_req *req)
+ {
+       struct nvme_command *cmd = req->cmd;
+--- a/drivers/nvme/target/nvmet.h
++++ b/drivers/nvme/target/nvmet.h
+@@ -466,18 +466,24 @@ void nvmet_start_keep_alive_timer(struct
+ void nvmet_stop_keep_alive_timer(struct nvmet_ctrl *ctrl);
+ u16 nvmet_parse_connect_cmd(struct nvmet_req *req);
++u32 nvmet_connect_cmd_data_len(struct nvmet_req *req);
+ void nvmet_bdev_set_limits(struct block_device *bdev, struct nvme_id_ns *id);
+ u16 nvmet_bdev_parse_io_cmd(struct nvmet_req *req);
+ u16 nvmet_file_parse_io_cmd(struct nvmet_req *req);
+ u16 nvmet_bdev_zns_parse_io_cmd(struct nvmet_req *req);
++u32 nvmet_admin_cmd_data_len(struct nvmet_req *req);
+ u16 nvmet_parse_admin_cmd(struct nvmet_req *req);
++u32 nvmet_discovery_cmd_data_len(struct nvmet_req *req);
+ u16 nvmet_parse_discovery_cmd(struct nvmet_req *req);
+ u16 nvmet_parse_fabrics_admin_cmd(struct nvmet_req *req);
++u32 nvmet_fabrics_admin_cmd_data_len(struct nvmet_req *req);
+ u16 nvmet_parse_fabrics_io_cmd(struct nvmet_req *req);
++u32 nvmet_fabrics_io_cmd_data_len(struct nvmet_req *req);
+ bool nvmet_req_init(struct nvmet_req *req, struct nvmet_cq *cq,
+               struct nvmet_sq *sq, const struct nvmet_fabrics_ops *ops);
+ void nvmet_req_uninit(struct nvmet_req *req);
++size_t nvmet_req_transfer_len(struct nvmet_req *req);
+ bool nvmet_check_transfer_len(struct nvmet_req *req, size_t len);
+ bool nvmet_check_data_len_lte(struct nvmet_req *req, size_t data_len);
+ void nvmet_req_complete(struct nvmet_req *req, u16 status);
+@@ -720,7 +726,9 @@ static inline void nvmet_req_bio_put(str
+ }
+ #ifdef CONFIG_NVME_TARGET_AUTH
++u32 nvmet_auth_send_data_len(struct nvmet_req *req);
+ void nvmet_execute_auth_send(struct nvmet_req *req);
++u32 nvmet_auth_receive_data_len(struct nvmet_req *req);
+ void nvmet_execute_auth_receive(struct nvmet_req *req);
+ int nvmet_auth_set_key(struct nvmet_host *host, const char *secret,
+                      bool set_ctrl);
diff --git a/queue-6.12/octeontx2-af-cn10k-restrict-vf-lmtline-sharing-to-its-own-pf.patch b/queue-6.12/octeontx2-af-cn10k-restrict-vf-lmtline-sharing-to-its-own-pf.patch
new file mode 100644 (file)
index 0000000..f0360fc
--- /dev/null
@@ -0,0 +1,78 @@
+From stable+bounces-290049-greg=kroah.com@vger.kernel.org Tue Jul 28 19:03:00 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 11:43:39 -0400
+Subject: octeontx2-af: cn10k: restrict VF LMTLINE sharing to its own PF
+To: stable@vger.kernel.org
+Cc: Junrui Luo <moonafterrain@outlook.com>, Yuhao Jiang <danisjiang@gmail.com>, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260728154339.4192777-1-sashal@kernel.org>
+
+From: Junrui Luo <moonafterrain@outlook.com>
+
+[ Upstream commit 8cdcf3d2caacdee7ddd363705fb4d93b0c1a0915 ]
+
+rvu_mbox_handler_lmtst_tbl_setup() uses req->base_pcifunc as a direct
+index into the LMT map table to read another function's LMTLINE
+physical base address and copy it into the caller's own LMT map table
+entry. The mailbox dispatcher authenticates req->hdr.pcifunc from the
+IRQ source, but req->base_pcifunc is a separate payload field and is
+not sanitized.
+
+Reject the request with -EPERM when a VF caller's base_pcifunc is not a
+valid function under its own PF. is_pf_func_valid() bounds the FUNC field
+to the PF's configured VF count, keeping the computed index inside the
+caller's own slot block.
+
+Fixes: 893ae97214c3 ("octeontx2-af: cn10k: Support configurable LMTST regions")
+Reported-by: Yuhao Jiang <danisjiang@gmail.com>
+Cc: stable@vger.kernel.org
+Signed-off-by: Junrui Luo <moonafterrain@outlook.com>
+Link: https://patch.msgid.link/SYBPR01MB78811656934E713B77DA6CEDAFE62@SYBPR01MB7881.ausprd01.prod.outlook.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+[ Dropped `rvu->pdev` argument from `rvu_get_pf()` calls and folded in the `is_pf_func_valid()` de-static plus its `rvu.h` declaration from commit 2156a29aecff. ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/net/ethernet/marvell/octeontx2/af/rvu.c       |    2 +-
+ drivers/net/ethernet/marvell/octeontx2/af/rvu.h       |    1 +
+ drivers/net/ethernet/marvell/octeontx2/af/rvu_cn10k.c |    9 +++++++++
+ 3 files changed, 11 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu.c
++++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu.c
+@@ -440,7 +440,7 @@ struct rvu_pfvf *rvu_get_pfvf(struct rvu
+               return &rvu->pf[rvu_get_pf(pcifunc)];
+ }
+-static bool is_pf_func_valid(struct rvu *rvu, u16 pcifunc)
++bool is_pf_func_valid(struct rvu *rvu, u16 pcifunc)
+ {
+       int pf, vf, nvfs;
+       u64 cfg;
+--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu.h
++++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu.h
+@@ -825,6 +825,7 @@ int rvu_get_pf(u16 pcifunc);
+ struct rvu_pfvf *rvu_get_pfvf(struct rvu *rvu, int pcifunc);
+ void rvu_get_pf_numvfs(struct rvu *rvu, int pf, int *numvfs, int *hwvf);
+ bool is_block_implemented(struct rvu_hwinfo *hw, int blkaddr);
++bool is_pf_func_valid(struct rvu *rvu, u16 pcifunc);
+ bool is_pffunc_map_valid(struct rvu *rvu, u16 pcifunc, int blktype);
+ int rvu_get_lf(struct rvu *rvu, struct rvu_block *block, u16 pcifunc, u16 slot);
+ int rvu_lf_reset(struct rvu *rvu, struct rvu_block *block, int lf);
+--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_cn10k.c
++++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_cn10k.c
+@@ -178,6 +178,15 @@ int rvu_mbox_handler_lmtst_tbl_setup(str
+        * pcifunc (will be the one who is calling this mailbox).
+        */
+       if (req->base_pcifunc) {
++              /* A VF is untrusted and must not redirect its LMTLINE to
++               * another PF's region, so confine VF callers to their own PF.
++               */
++              if (is_vf(req->hdr.pcifunc) &&
++                  (!is_pf_func_valid(rvu, req->base_pcifunc) ||
++                   rvu_get_pf(req->hdr.pcifunc) !=
++                   rvu_get_pf(req->base_pcifunc)))
++                      return -EPERM;
++
+               /* Calculating the LMT table index equivalent to primary
+                * pcifunc.
+                */
diff --git a/queue-6.12/octeontx2-annotate-mmio-regions-as-__iomem.patch b/queue-6.12/octeontx2-annotate-mmio-regions-as-__iomem.patch
new file mode 100644 (file)
index 0000000..2d706a6
--- /dev/null
@@ -0,0 +1,99 @@
+From stable+bounces-288043-greg=kroah.com@vger.kernel.org Wed Jul 22 20:12:49 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 14:07:47 -0400
+Subject: octeontx2: Annotate mmio regions as __iomem
+To: stable@vger.kernel.org
+Cc: Subbaraya Sundeep <sbhatta@marvell.com>, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722180748.2202109-1-sashal@kernel.org>
+
+From: Subbaraya Sundeep <sbhatta@marvell.com>
+
+[ Upstream commit d0976b43956ee8c8bd093223df9115bfcf63dfe5 ]
+
+This patch removes unnecessary typecasts by marking the
+mbox_regions array as __iomem since it is used to store
+pointers to memory-mapped I/O (MMIO) regions. Also simplified
+the call to readq() in PF driver by removing redundant type casts.
+
+Signed-off-by: Subbaraya Sundeep <sbhatta@marvell.com>
+Link: https://patch.msgid.link/1749484309-3434-1-git-send-email-sbhatta@marvell.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Stable-dep-of: 0b352f04b9be ("octeontx2-vf: clear stale mailbox IRQ state before request_irq()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/net/ethernet/marvell/octeontx2/af/rvu.c      |   12 ++++++------
+ drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c |    3 +--
+ 2 files changed, 7 insertions(+), 8 deletions(-)
+
+--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu.c
++++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu.c
+@@ -2401,7 +2401,7 @@ static inline void rvu_afvf_mbox_up_hand
+       __rvu_mbox_up_handler(mwork, TYPE_AFVF);
+ }
+-static int rvu_get_mbox_regions(struct rvu *rvu, void **mbox_addr,
++static int rvu_get_mbox_regions(struct rvu *rvu, void __iomem **mbox_addr,
+                               int num, int type, unsigned long *pf_bmap)
+ {
+       struct rvu_hwinfo *hw = rvu->hw;
+@@ -2426,7 +2426,7 @@ static int rvu_get_mbox_regions(struct r
+                               bar4 = rvupf_read64(rvu, RVU_PF_VF_BAR4_ADDR);
+                               bar4 += region * MBOX_SIZE;
+                       }
+-                      mbox_addr[region] = (void *)ioremap_wc(bar4, MBOX_SIZE);
++                      mbox_addr[region] = ioremap_wc(bar4, MBOX_SIZE);
+                       if (!mbox_addr[region])
+                               goto error;
+               }
+@@ -2449,7 +2449,7 @@ static int rvu_get_mbox_regions(struct r
+                                         RVU_AF_PF_BAR4_ADDR);
+                       bar4 += region * MBOX_SIZE;
+               }
+-              mbox_addr[region] = (void *)ioremap_wc(bar4, MBOX_SIZE);
++              mbox_addr[region] = ioremap_wc(bar4, MBOX_SIZE);
+               if (!mbox_addr[region])
+                       goto error;
+       }
+@@ -2457,7 +2457,7 @@ static int rvu_get_mbox_regions(struct r
+ error:
+       while (region--)
+-              iounmap((void __iomem *)mbox_addr[region]);
++              iounmap(mbox_addr[region]);
+       return -ENOMEM;
+ }
+@@ -2467,10 +2467,10 @@ static int rvu_mbox_init(struct rvu *rvu
+                        void (mbox_up_handler)(struct work_struct *))
+ {
+       int err = -EINVAL, i, dir, dir_up;
++      void __iomem **mbox_regions;
+       void __iomem *reg_base;
+       struct rvu_work *mwork;
+       unsigned long *pf_bmap;
+-      void **mbox_regions;
+       const char *name;
+       u64 cfg;
+@@ -2493,7 +2493,7 @@ static int rvu_mbox_init(struct rvu *rvu
+       mutex_init(&rvu->mbox_lock);
+-      mbox_regions = kcalloc(num, sizeof(void *), GFP_KERNEL);
++      mbox_regions = kcalloc(num, sizeof(void __iomem *), GFP_KERNEL);
+       if (!mbox_regions) {
+               err = -ENOMEM;
+               goto free_bitmap;
+--- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c
++++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c
+@@ -593,8 +593,7 @@ static int otx2_pfvf_mbox_init(struct ot
+               base = pci_resource_start(pf->pdev, PCI_MBOX_BAR_NUM) +
+                      MBOX_SIZE;
+       else
+-              base = readq((void __iomem *)((u64)pf->reg_base +
+-                                            RVU_PF_VF_BAR4_ADDR));
++              base = readq(pf->reg_base + RVU_PF_VF_BAR4_ADDR);
+       hwbase = ioremap_wc(base, MBOX_SIZE * pf->total_vfs);
+       if (!hwbase) {
diff --git a/queue-6.12/octeontx2-pf-clear-stale-mailbox-irq-state-before-request_irq.patch b/queue-6.12/octeontx2-pf-clear-stale-mailbox-irq-state-before-request_irq.patch
new file mode 100644 (file)
index 0000000..27529e6
--- /dev/null
@@ -0,0 +1,67 @@
+From stable+bounces-288045-greg=kroah.com@vger.kernel.org Wed Jul 22 20:13:00 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 14:07:57 -0400
+Subject: octeontx2-pf: clear stale mailbox IRQ state before request_irq()
+To: stable@vger.kernel.org
+Cc: Runyu Xiao <runyu.xiao@seu.edu.cn>, Simon Horman <horms@kernel.org>, Ratheesh Kannoth <rkannoth@marvell.com>, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722180757.2202348-2-sashal@kernel.org>
+
+From: Runyu Xiao <runyu.xiao@seu.edu.cn>
+
+[ Upstream commit f918554fb7246e89b98ef90abe80801f038258b3 ]
+
+otx2_register_mbox_intr() currently installs the PF mailbox IRQ handler
+before clearing stale mailbox interrupt state. The function itself then
+comments that the local interrupt bits must be cleared first to avoid
+spurious interrupts, but that clear happens only after request_irq() has
+already exposed the handler to irq delivery.
+
+A running system can reach this during PF mailbox interrupt registration
+while stale or latched RVU_PF_INT state is still present. If delivery
+happens in the request_irq()-to-clear window,
+otx2_pfaf_mbox_intr_handler() can run before local quiesce and touch
+the same pf->mbox and pf->mbox_wq carrier that probe and teardown later
+reuse or destroy.
+
+Move the stale mailbox interrupt clear ahead of request_irq(), but keep
+interrupt enabling after the handler is installed. This closes the
+pre-clear early-IRQ window without creating a new enable-before-handler
+window.
+
+Fixes: 5a6d7c9daef3 ("octeontx2-pf: Mailbox communication with AF")
+Cc: stable@vger.kernel.org
+Signed-off-by: Runyu Xiao <runyu.xiao@seu.edu.cn>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Reviewed-by: Ratheesh Kannoth <rkannoth@marvell.com>
+Link: https://patch.msgid.link/20260611160014.3202224-2-runyu.xiao@seu.edu.cn
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c |    8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+--- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c
++++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c
+@@ -1023,6 +1023,9 @@ static int otx2_register_mbox_intr(struc
+       char *irq_name;
+       int err;
++      /* Clear stale mailbox interrupt state before installing the handler. */
++      otx2_write64(pf, RVU_PF_INT, BIT_ULL(0));
++
+       /* Register mailbox interrupt handler */
+       irq_name = &hw->irq_name[RVU_PF_INT_VEC_AFPF_MBOX * NAME_SIZE];
+       snprintf(irq_name, NAME_SIZE, "RVUPFAF Mbox");
+@@ -1034,10 +1037,7 @@ static int otx2_register_mbox_intr(struc
+               return err;
+       }
+-      /* Enable mailbox interrupt for msgs coming from AF.
+-       * First clear to avoid spurious interrupts, if any.
+-       */
+-      otx2_write64(pf, RVU_PF_INT, BIT_ULL(0));
++      /* Enable mailbox interrupt for msgs coming from AF. */
+       otx2_write64(pf, RVU_PF_INT_ENA_W1S, BIT_ULL(0));
+       if (!probe_af)
diff --git a/queue-6.12/octeontx2-pf-fix-sqb-pointer-leak-on-init-failure.patch b/queue-6.12/octeontx2-pf-fix-sqb-pointer-leak-on-init-failure.patch
new file mode 100644 (file)
index 0000000..c592ffe
--- /dev/null
@@ -0,0 +1,79 @@
+From stable+bounces-290199-greg=kroah.com@vger.kernel.org Wed Jul 29 02:31:02 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 20:30:53 -0400
+Subject: octeontx2-pf: fix SQB pointer leak on init failure
+To: stable@vger.kernel.org
+Cc: Dawei Feng <dawei.feng@seu.edu.cn>, Ratheesh Kannoth <rkannoth@marvell.com>, Paolo Abeni <pabeni@redhat.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260729003053.889460-1-sashal@kernel.org>
+
+From: Dawei Feng <dawei.feng@seu.edu.cn>
+
+[ Upstream commit 62e7df6d042aeebd5efb581074e28865c04477be ]
+
+otx2_init_hw_resources() initializes SQ aura and pool resources before
+several later setup steps. On failure, err_free_sq_ptrs only frees SQB
+pages, leaving the per-SQ sqb_ptrs arrays behind.
+
+Use otx2_free_sq_res() for the SQ unwind path and let it free sqb_ptrs
+even when sq->sqe has not been allocated yet.
+
+The bug was first flagged by an experimental analysis tool we are
+developing for kernel memory-management bugs while analyzing
+v6.13-rc1. The tool is still under development and is not yet publicly
+available. Manual inspection confirms that the bug is still
+present in v7.1.1.
+
+An x86_64 allyesconfig build showed no new warnings. As we do not have an
+OcteonTX2 PF device and the corresponding AF mailbox setup to test with,
+no runtime testing was able to be performed.
+
+Fixes: caa2da34fd25 ("octeontx2-pf: Initialize and config queues")
+Cc: stable@vger.kernel.org
+Reviewed-by: Ratheesh Kannoth <rkannoth@marvell.com>
+Signed-off-by: Dawei Feng <dawei.feng@seu.edu.cn>
+Link: https://patch.msgid.link/20260630071625.349996-1-dawei.feng@seu.edu.cn
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c |   17 ++++++++---------
+ 1 file changed, 8 insertions(+), 9 deletions(-)
+
+--- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c
++++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c
+@@ -1438,13 +1438,13 @@ static void otx2_free_sq_res(struct otx2
+       otx2_sq_free_sqbs(pf);
+       for (qidx = 0; qidx < otx2_get_total_tx_queues(pf); qidx++) {
+               sq = &qset->sq[qidx];
+-              /* Skip freeing Qos queues if they are not initialized */
+-              if (!sq->sqe)
+-                      continue;
+-              qmem_free(pf->dev, sq->sqe);
+-              qmem_free(pf->dev, sq->tso_hdrs);
+-              qmem_free(pf->dev, sq->timestamps);
+-              kfree(sq->sg);
++              /* sq->sqe is not initialized for unused QoS queues */
++              if (sq->sqe) {
++                      qmem_free(pf->dev, sq->sqe);
++                      qmem_free(pf->dev, sq->tso_hdrs);
++                      qmem_free(pf->dev, sq->timestamps);
++                      kfree(sq->sg);
++              }
+               kfree(sq->sqb_ptrs);
+       }
+ }
+@@ -1570,13 +1570,12 @@ static int otx2_init_hw_resources(struct
+       return err;
+ err_free_nix_queues:
+-      otx2_free_sq_res(pf);
+       otx2_free_cq_res(pf);
+       otx2_ctx_disable(mbox, NIX_AQ_CTYPE_RQ, false);
+ err_free_txsch:
+       otx2_txschq_stop(pf);
+ err_free_sq_ptrs:
+-      otx2_sq_free_sqbs(pf);
++      otx2_free_sq_res(pf);
+ err_free_rq_ptrs:
+       otx2_free_aura_ptr(pf, AURA_NIX_RQ);
+       otx2_ctx_disable(mbox, NPA_AQ_CTYPE_POOL, true);
diff --git a/queue-6.12/octeontx2-vf-clear-stale-mailbox-irq-state-before-request_irq.patch b/queue-6.12/octeontx2-vf-clear-stale-mailbox-irq-state-before-request_irq.patch
new file mode 100644 (file)
index 0000000..409f7c8
--- /dev/null
@@ -0,0 +1,75 @@
+From stable+bounces-288042-greg=kroah.com@vger.kernel.org Wed Jul 22 20:12:46 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 14:07:48 -0400
+Subject: octeontx2-vf: clear stale mailbox IRQ state before request_irq()
+To: stable@vger.kernel.org
+Cc: Runyu Xiao <runyu.xiao@seu.edu.cn>, Simon Horman <horms@kernel.org>, Ratheesh Kannoth <rkannoth@marvell.com>, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722180748.2202109-2-sashal@kernel.org>
+
+From: Runyu Xiao <runyu.xiao@seu.edu.cn>
+
+[ Upstream commit 0b352f04b9be2c83c0240aa6dae7257fefa90464 ]
+
+otx2vf_register_mbox_intr() currently installs the VF mailbox IRQ
+handler before clearing stale mailbox interrupt state. The code then says
+that local interrupt bits should be cleared first to avoid spurious
+interrupts, but that clear still happens only after request_irq() has
+already made the handler reachable.
+
+A running system can reach this during VF mailbox interrupt registration
+while stale or latched RVU_VF_INT state is still present. If delivery
+happens in the request_irq()-to-clear window,
+otx2vf_vfaf_mbox_intr_handler() can run before local quiesce and touch
+the same vf->mbox and vf->mbox_wq carrier that probe and teardown later
+reuse or destroy.
+
+Move the stale mailbox interrupt clear ahead of request_irq(), but keep
+interrupt enabling after the handler is installed. This closes the
+pre-clear early-IRQ window without creating a new enable-before-handler
+window.
+
+Fixes: 3184fb5ba96e ("octeontx2-vf: Virtual function driver support")
+Cc: stable@vger.kernel.org
+Signed-off-by: Runyu Xiao <runyu.xiao@seu.edu.cn>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Reviewed-by: Ratheesh Kannoth <rkannoth@marvell.com>
+Link: https://patch.msgid.link/20260611160014.3202224-3-runyu.xiao@seu.edu.cn
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c |   13 ++++++++-----
+ 1 file changed, 8 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c
++++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c
+@@ -245,9 +245,15 @@ static int otx2vf_register_mbox_intr(str
+ {
+       struct otx2_hw *hw = &vf->hw;
+       struct msg_req *req;
++      u64 mbox_int_mask;
+       char *irq_name;
+       int err;
++      mbox_int_mask = BIT_ULL(0);
++
++      /* Clear stale mailbox interrupt state before installing the handler. */
++      otx2_write64(vf, RVU_VF_INT, mbox_int_mask);
++
+       /* Register mailbox interrupt handler */
+       irq_name = &hw->irq_name[RVU_VF_INT_VEC_MBOX * NAME_SIZE];
+       snprintf(irq_name, NAME_SIZE, "RVUVFAF Mbox");
+@@ -259,11 +265,8 @@ static int otx2vf_register_mbox_intr(str
+               return err;
+       }
+-      /* Enable mailbox interrupt for msgs coming from PF.
+-       * First clear to avoid spurious interrupts, if any.
+-       */
+-      otx2_write64(vf, RVU_VF_INT, BIT_ULL(0));
+-      otx2_write64(vf, RVU_VF_INT_ENA_W1S, BIT_ULL(0));
++      /* Enable mailbox interrupt for msgs coming from PF. */
++      otx2_write64(vf, RVU_VF_INT_ENA_W1S, mbox_int_mask);
+       if (!probe_pf)
+               return 0;
diff --git a/queue-6.12/ovl-use-linked-upper-dentry-in-copy-up-tmpfile.patch b/queue-6.12/ovl-use-linked-upper-dentry-in-copy-up-tmpfile.patch
new file mode 100644 (file)
index 0000000..7cdc6eb
--- /dev/null
@@ -0,0 +1,105 @@
+From stable+bounces-289568-greg=kroah.com@vger.kernel.org Mon Jul 27 15:43:16 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 09:40:26 -0400
+Subject: ovl: use linked upper dentry in copy-up tmpfile
+To: stable@vger.kernel.org
+Cc: Souvik Banerjee <souvik@amlalabs.com>, Amir Goldstein <amir73il@gmail.com>, Miklos Szeredi <mszeredi@redhat.com>, "Christian Brauner (Amutable)" <brauner@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727134026.589031-1-sashal@kernel.org>
+
+From: Souvik Banerjee <souvik@amlalabs.com>
+
+[ Upstream commit e348eecd4d8fa8d18a5157ff59f7be1dc59c5928 ]
+
+ovl_copy_up_tmpfile() stores the disconnected O_TMPFILE dentry as the
+overlay's upper dentry reference via ovl_inode_update().  vfs_tmpfile()
+allocated this dentry via d_alloc(parentpath->dentry, &slash_name), so
+d_name is "/" and d_parent is c->workdir.  Local upper filesystems
+(ext4, btrfs, xfs, ...) immediately rename it to "#<inum>" via
+d_mark_tmpfile() inside their ->tmpfile() op; FUSE and virtiofs do
+not, so both fields stay that way.  Neither identifies the destination
+directory and filename where ovl_do_link() actually linked the file.
+
+When the upper filesystem implements ->d_revalidate() (e.g. FUSE or
+virtiofs), ovl_revalidate_real() calls it with the dentry's parent
+inode and a snapshot of d_name.  The server tries to look up "/" inside
+c->workdir, fails, and overlayfs reports -ESTALE.
+
+This causes persistent ESTALE errors for any file that was copied up via
+the tmpfile path, breaking dpkg, apt, and other tools that do
+rename-over-existing on overlayfs with a FUSE/virtiofs upper.
+
+Before commit 6b52243f633e ("ovl: fold copy-up helpers into callers"),
+the tmpfile copy-up path used a dedicated helper ovl_link_tmpfile()
+that captured the linked destination dentry returned by ovl_do_link():
+
+    err = ovl_do_link(temp, udir, upper);
+    ...
+    if (!err)
+        *newdentry = dget(upper);
+
+and published it via ovl_inode_update(d_inode(c->dentry), newdentry).
+The fold inlined ovl_do_link() into ovl_copy_up_tmpfile() but dropped
+the dget(upper) capture, and rewrote the publish line as
+ovl_inode_update(d_inode(c->dentry), dget(temp)) â€” where temp is the
+disconnected O_TMPFILE dentry.
+
+Fix by keeping a reference to the linked destination dentry after
+ovl_do_link() succeeds, and publishing that dentry at the existing
+ovl_inode_update() call site.  The non-tmpfile/workdir path continues to
+publish the renamed temporary dentry.
+
+Reproducer:
+  - Mount overlayfs with virtiofs (or a FUSE fs whose server advertises
+    FUSE_TMPFILE) as upper
+  - Run: dpkg -i <any .deb>
+  - Observe: "error installing new file '...': Stale file handle"
+
+Fixes: 6b52243f633e ("ovl: fold copy-up helpers into callers")
+Cc: stable@vger.kernel.org # v4.20+
+Signed-off-by: Souvik Banerjee <souvik@amlalabs.com>
+Link: https://patch.msgid.link/20260501232735.2610824-1-souvik@amlalabs.com
+Reviewed-by: Amir Goldstein <amir73il@gmail.com>
+Reviewed-by: Miklos Szeredi <mszeredi@redhat.com>
+Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
+[ adapted scoped credential and creation helpers to explicit credential, locking, lookup, and cleanup handling ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/overlayfs/copy_up.c |   12 ++++++++++--
+ 1 file changed, 10 insertions(+), 2 deletions(-)
+
+--- a/fs/overlayfs/copy_up.c
++++ b/fs/overlayfs/copy_up.c
+@@ -864,7 +864,7 @@ static int ovl_copy_up_tmpfile(struct ov
+ {
+       struct ovl_fs *ofs = OVL_FS(c->dentry->d_sb);
+       struct inode *udir = d_inode(c->destdir);
+-      struct dentry *temp, *upper;
++      struct dentry *temp, *upper, *newdentry = NULL;
+       struct file *tmpfile;
+       struct ovl_cu_creds cc;
+       int err;
+@@ -901,6 +901,14 @@ static int ovl_copy_up_tmpfile(struct ov
+       err = PTR_ERR(upper);
+       if (!IS_ERR(upper)) {
+               err = ovl_do_link(ofs, temp, udir, upper);
++              if (!err) {
++                      /*
++                       * Record the linked dentry -- not the disconnected
++                       * O_TMPFILE dentry -- so that ->d_revalidate() on
++                       * the upper fs sees the real parent/name.
++                       */
++                      newdentry = dget(upper);
++              }
+               dput(upper);
+       }
+       inode_unlock(udir);
+@@ -916,7 +924,7 @@ static int ovl_copy_up_tmpfile(struct ov
+       if (!c->metacopy)
+               ovl_set_upperdata(d_inode(c->dentry));
+-      ovl_inode_update(d_inode(c->dentry), dget(temp));
++      ovl_inode_update(d_inode(c->dentry), newdentry);
+ out:
+       ovl_end_write(c->dentry);
diff --git a/queue-6.12/remoteproc-xlnx-check-remote-core-state.patch b/queue-6.12/remoteproc-xlnx-check-remote-core-state.patch
new file mode 100644 (file)
index 0000000..04320f9
--- /dev/null
@@ -0,0 +1,147 @@
+From stable+bounces-288601-greg=kroah.com@vger.kernel.org Fri Jul 24 03:25:06 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 21:24:56 -0400
+Subject: remoteproc: xlnx: Check remote core state
+To: stable@vger.kernel.org
+Cc: Tanmay Shah <tanmay.shah@amd.com>, Beleswar Padhi <b-padhi@ti.com>, Michal Simek <michal.simek@amd.com>, Mathieu Poirier <mathieu.poirier@linaro.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260724012456.3832700-1-sashal@kernel.org>
+
+From: Tanmay Shah <tanmay.shah@amd.com>
+
+[ Upstream commit a48df51d23138388900995add2854cda4aa68e55 ]
+
+The remote state is set to RPROC_DETACHED if the resource table is found
+in the memory. However, this can be wrong if the remote is not started,
+but firmware is still loaded in the memory. Use PM_GET_NODE_STATUS call
+to the firmware to request the state of the RPU node. If the RPU is
+actually out of reset and running, only then move the remote state to
+RPROC_DETACHED, otherwise keep the remote state to RPROC_OFFLINE.
+
+Signed-off-by: Tanmay Shah <tanmay.shah@amd.com>
+Fixes: bca4b02ef92e ("remoteproc: xlnx: Add attach detach support")
+Reviewed-by: Beleswar Padhi <b-padhi@ti.com>
+Acked-by: Michal Simek <michal.simek@amd.com>
+Cc: stable@vger.kernel.org
+Link: https://lore.kernel.org/r/20260428221855.313752-1-tanmay.shah@amd.com
+Signed-off-by: Mathieu Poirier <mathieu.poirier@linaro.org>
+[ replaced the unavailable zynqmp_pm_get_node_status() helper with a direct zynqmp_pm_invoke_fn() call and exported it for modular builds. ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/firmware/xilinx/zynqmp.c        |    1 
+ drivers/remoteproc/xlnx_r5_remoteproc.c |   50 +++++++++++++++++++++++++-------
+ include/linux/firmware/xlnx-zynqmp.h    |   13 ++++++++
+ 3 files changed, 54 insertions(+), 10 deletions(-)
+
+--- a/drivers/firmware/xilinx/zynqmp.c
++++ b/drivers/firmware/xilinx/zynqmp.c
+@@ -386,6 +386,7 @@ int zynqmp_pm_invoke_fn(u32 pm_api_id, u
+       return do_fw_call(ret_payload, 8, smc_arg[0], smc_arg[1], smc_arg[2], smc_arg[3],
+                         smc_arg[4], smc_arg[5], smc_arg[6], smc_arg[7]);
+ }
++EXPORT_SYMBOL_GPL(zynqmp_pm_invoke_fn);
+ static u32 pm_api_version;
+ static u32 pm_tz_version;
+--- a/drivers/remoteproc/xlnx_r5_remoteproc.c
++++ b/drivers/remoteproc/xlnx_r5_remoteproc.c
+@@ -927,16 +927,6 @@ static struct zynqmp_r5_core *zynqmp_r5_
+               goto free_rproc;
+       }
+-      /*
+-       * If firmware is already available in the memory then move rproc state
+-       * to DETACHED. Firmware can be preloaded via debugger or by any other
+-       * agent (processors) in the system.
+-       * If firmware isn't available in the memory and resource table isn't
+-       * found, then rproc state remains OFFLINE.
+-       */
+-      if (!zynqmp_r5_get_rsc_table_va(r5_core))
+-              r5_rproc->state = RPROC_DETACHED;
+-
+       r5_core->rproc = r5_rproc;
+       return r5_core;
+@@ -1189,6 +1179,7 @@ static int zynqmp_r5_core_init(struct zy
+ {
+       struct device *dev = cluster->dev;
+       struct zynqmp_r5_core *r5_core;
++      u32 payload[PAYLOAD_ARG_CNT];
+       int ret = -EINVAL, i;
+       r5_core = cluster->r5_cores[0];
+@@ -1234,6 +1225,45 @@ static int zynqmp_r5_core_init(struct zy
+               ret = zynqmp_r5_get_sram_banks(r5_core);
+               if (ret)
+                       return ret;
++
++              /*
++               * It is possible that firmware is loaded into the memory, but
++               * RPU (remote) is not running. In such case, RPU state will be
++               * moved to RPROC_DETACHED wrongfully. To avoid it first make
++               * sure RPU is power-on and out of reset before parsing for the
++               * resource table.
++               */
++              ret = zynqmp_pm_feature(PM_GET_NODE_STATUS);
++              if (ret < PM_API_VERSION_2)
++                      ret = -EOPNOTSUPP;
++              else
++                      ret = zynqmp_pm_invoke_fn(PM_GET_NODE_STATUS, payload, 1,
++                                                r5_core->pm_domain_id);
++              if (ret) {
++                      dev_warn(r5_core->dev,
++                               "failed to get rpu node status, err %d\n", ret);
++                      continue;
++              }
++
++              /*
++               * If RPU state is power on and out of reset i.e. running, then
++               * assign RPROC_DETACHED state. If the RPU is not out of reset
++               * then do not attempt to attach to the remote processor.
++               */
++              if (payload[1] == PM_NODE_RUNNING) {
++                      /*
++                       * Not all the firmware that is running on the remote
++                       * core is expected to have the resource table. The
++                       * firmware might not use RPMsg at all, and in that case
++                       * resource table becomes irrelevant. However, we still
++                       * need to make sure that running core is not reported
++                       * as offline. so do not decide remote core state based
++                       * on the resource table availability
++                       */
++                      if (zynqmp_r5_get_rsc_table_va(r5_core))
++                              dev_dbg(r5_core->dev, "rsc tbl not found\n");
++                      r5_core->rproc->state = RPROC_DETACHED;
++              }
+       }
+       return 0;
+--- a/include/linux/firmware/xlnx-zynqmp.h
++++ b/include/linux/firmware/xlnx-zynqmp.h
+@@ -143,6 +143,7 @@ enum pm_api_cb_id {
+ enum pm_api_id {
+       PM_API_FEATURES = 0,
+       PM_GET_API_VERSION = 1,
++      PM_GET_NODE_STATUS = 3,
+       PM_REGISTER_NOTIFIER = 5,
+       PM_FORCE_POWERDOWN = 8,
+       PM_REQUEST_WAKEUP = 10,
+@@ -519,6 +520,18 @@ enum pm_gem_config_type {
+ };
+ /**
++ * enum pm_node_status - Device node status provided by xilpm fw
++ * @PM_NODE_UNUSED: Device is not used
++ * @PM_NODE_RUNNING: Device is power-on and out of reset
++ * @PM_NODE_HALT: Device is power-on but in the reset state
++ */
++enum pm_node_status {
++      PM_NODE_UNUSED = 0,
++      PM_NODE_RUNNING = 1,
++      PM_NODE_HALT = 12,
++};
++
++/**
+  * struct zynqmp_pm_query_data - PM query data
+  * @qid:      query ID
+  * @arg1:     Argument 1 of query data
diff --git a/queue-6.12/rxrpc-allow-the-app-to-store-private-data-on-peer-structs.patch b/queue-6.12/rxrpc-allow-the-app-to-store-private-data-on-peer-structs.patch
new file mode 100644 (file)
index 0000000..4f57c41
--- /dev/null
@@ -0,0 +1,94 @@
+From stable+bounces-290948-greg=kroah.com@vger.kernel.org Thu Jul 30 03:03:13 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 21:01:33 -0400
+Subject: rxrpc: Allow the app to store private data on peer structs
+To: stable@vger.kernel.org
+Cc: David Howells <dhowells@redhat.com>, Marc Dionne <marc.dionne@auristor.com>, Jakub Kicinski <kuba@kernel.org>, "David S. Miller" <davem@davemloft.net>, Eric Dumazet <edumazet@google.com>, Paolo Abeni <pabeni@redhat.com>, Simon Horman <horms@kernel.org>, linux-afs@lists.infradead.org, linux-fsdevel@vger.kernel.org, netdev@vger.kernel.org, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260730010137.1700032-4-sashal@kernel.org>
+
+From: David Howells <dhowells@redhat.com>
+
+[ Upstream commit f3a123b25429dc03508e3bf598373005220b7203 ]
+
+Provide a way for the application (e.g. the afs filesystem) to store
+private data on the rxrpc_peer structs for later retrieval via the call
+object.
+
+This will allow afs to store a pointer to the afs_server object on the
+rxrpc_peer struct, thereby obviating the need for afs to keep lookup tables
+by which it can associate an incoming call with server that transmitted it.
+
+Signed-off-by: David Howells <dhowells@redhat.com>
+cc: Marc Dionne <marc.dionne@auristor.com>
+cc: Jakub Kicinski <kuba@kernel.org>
+cc: "David S. Miller" <davem@davemloft.net>
+cc: Eric Dumazet <edumazet@google.com>
+cc: Paolo Abeni <pabeni@redhat.com>
+cc: Simon Horman <horms@kernel.org>
+cc: linux-afs@lists.infradead.org
+cc: linux-fsdevel@vger.kernel.org
+cc: netdev@vger.kernel.org
+Link: https://lore.kernel.org/r/20250224234154.2014840-13-dhowells@redhat.com/ # v1
+Link: https://lore.kernel.org/r/20250310094206.801057-9-dhowells@redhat.com/ # v4
+Stable-dep-of: 330e2c514823 ("afs: Fix dynamic lookup to fail on cell lookup failure")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/net/af_rxrpc.h  |    2 ++
+ net/rxrpc/ar-internal.h |    1 +
+ net/rxrpc/peer_object.c |   26 ++++++++++++++++++++++++++
+ 3 files changed, 29 insertions(+)
+
+--- a/include/net/af_rxrpc.h
++++ b/include/net/af_rxrpc.h
+@@ -74,6 +74,8 @@ struct rxrpc_peer *rxrpc_kernel_get_peer
+ struct rxrpc_peer *rxrpc_kernel_get_call_peer(struct socket *sock, struct rxrpc_call *call);
+ const struct sockaddr_rxrpc *rxrpc_kernel_remote_srx(const struct rxrpc_peer *peer);
+ const struct sockaddr *rxrpc_kernel_remote_addr(const struct rxrpc_peer *peer);
++unsigned long rxrpc_kernel_set_peer_data(struct rxrpc_peer *peer, unsigned long app_data);
++unsigned long rxrpc_kernel_get_peer_data(const struct rxrpc_peer *peer);
+ unsigned int rxrpc_kernel_get_srtt(const struct rxrpc_peer *);
+ int rxrpc_kernel_charge_accept(struct socket *sock, rxrpc_notify_rx_t notify_rx,
+                              unsigned long user_call_ID, gfp_t gfp,
+--- a/net/rxrpc/ar-internal.h
++++ b/net/rxrpc/ar-internal.h
+@@ -333,6 +333,7 @@ struct rxrpc_peer {
+       struct hlist_head       error_targets;  /* targets for net error distribution */
+       struct rb_root          service_conns;  /* Service connections */
+       struct list_head        keepalive_link; /* Link in net->peer_keepalive[] */
++      unsigned long           app_data;       /* Application data (e.g. afs_server) */
+       unsigned int            last_tx_at;     /* Last time packet sent here (time64_t LSW) */
+       seqlock_t               service_conn_lock;
+       spinlock_t              lock;           /* access lock */
+--- a/net/rxrpc/peer_object.c
++++ b/net/rxrpc/peer_object.c
+@@ -510,3 +510,29 @@ const struct sockaddr *rxrpc_kernel_remo
+               (peer ? &peer->srx.transport : &rxrpc_null_addr.transport);
+ }
+ EXPORT_SYMBOL(rxrpc_kernel_remote_addr);
++
++/**
++ * rxrpc_kernel_set_peer_data - Set app-specific data on a peer.
++ * @peer: The peer to alter
++ * @app_data: The data to set
++ *
++ * Set the app-specific data on a peer.  AF_RXRPC makes no effort to retain
++ * anything the data might refer to.  The previous app_data is returned.
++ */
++unsigned long rxrpc_kernel_set_peer_data(struct rxrpc_peer *peer, unsigned long app_data)
++{
++      return xchg(&peer->app_data, app_data);
++}
++EXPORT_SYMBOL(rxrpc_kernel_set_peer_data);
++
++/**
++ * rxrpc_kernel_get_peer_data - Get app-specific data from a peer.
++ * @peer: The peer to query
++ *
++ * Retrieve the app-specific data from a peer.
++ */
++unsigned long rxrpc_kernel_get_peer_data(const struct rxrpc_peer *peer)
++{
++      return peer->app_data;
++}
++EXPORT_SYMBOL(rxrpc_kernel_get_peer_data);
diff --git a/queue-6.12/rxrpc-don-t-need-barrier-for-tx_bottom-and-acks_hard_ack.patch b/queue-6.12/rxrpc-don-t-need-barrier-for-tx_bottom-and-acks_hard_ack.patch
new file mode 100644 (file)
index 0000000..3aa1a1f
--- /dev/null
@@ -0,0 +1,82 @@
+From stable+bounces-287940-greg=kroah.com@vger.kernel.org Wed Jul 22 18:32:15 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 12:08:02 -0400
+Subject: rxrpc: Don't need barrier for ->tx_bottom and ->acks_hard_ack
+To: stable@vger.kernel.org
+Cc: David Howells <dhowells@redhat.com>, Marc Dionne <marc.dionne@auristor.com>, linux-afs@lists.infradead.org, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722160805.1741176-2-sashal@kernel.org>
+
+From: David Howells <dhowells@redhat.com>
+
+[ Upstream commit 6396b48ac0a77165f9c2c40ab03d6c8188c89739 ]
+
+We don't need a barrier for the ->tx_bottom value (which indicates the
+lowest sequence still in the transmission queue) and the ->acks_hard_ack
+value (which tracks the DATA packets hard-ack'd by the latest ACK packet
+received and thus indicates which DATA packets can now be discarded) as the
+app thread doesn't use either value as a reference to memory to access.
+Rather, the app thread merely uses these as a guide to how much space is
+available in the transmission queue
+
+Change the code to use READ/WRITE_ONCE() instead.
+
+Also, change rxrpc_check_tx_space() to use the same value for tx_bottom
+throughout.
+
+Signed-off-by: David Howells <dhowells@redhat.com>
+cc: Marc Dionne <marc.dionne@auristor.com>
+cc: linux-afs@lists.infradead.org
+Link: https://patch.msgid.link/20241204074710.990092-18-dhowells@redhat.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Stable-dep-of: e66f8f32f501 ("rxrpc: Fix socket notification race")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/rxrpc/sendmsg.c |    8 +++++---
+ net/rxrpc/txbuf.c   |    4 ++--
+ 2 files changed, 7 insertions(+), 5 deletions(-)
+
+--- a/net/rxrpc/sendmsg.c
++++ b/net/rxrpc/sendmsg.c
+@@ -93,9 +93,11 @@ no_wait:
+  */
+ static bool rxrpc_check_tx_space(struct rxrpc_call *call, rxrpc_seq_t *_tx_win)
+ {
++      rxrpc_seq_t tx_bottom = READ_ONCE(call->tx_bottom);
++
+       if (_tx_win)
+-              *_tx_win = call->tx_bottom;
+-      return call->tx_prepared - call->tx_bottom < 256;
++              *_tx_win = tx_bottom;
++      return call->tx_prepared - tx_bottom < 256;
+ }
+ /*
+@@ -137,7 +139,7 @@ static int rxrpc_wait_for_tx_window_wait
+               rtt = 2;
+       timeout = rtt;
+-      tx_start = smp_load_acquire(&call->acks_hard_ack);
++      tx_start = READ_ONCE(call->acks_hard_ack);
+       for (;;) {
+               set_current_state(TASK_UNINTERRUPTIBLE);
+--- a/net/rxrpc/txbuf.c
++++ b/net/rxrpc/txbuf.c
+@@ -216,14 +216,14 @@ void rxrpc_shrink_call_tx_buffer(struct
+       while ((txb = list_first_entry_or_null(&call->tx_buffer,
+                                              struct rxrpc_txbuf, call_link))) {
+-              hard_ack = smp_load_acquire(&call->acks_hard_ack);
++              hard_ack = call->acks_hard_ack;
+               if (before(hard_ack, txb->seq))
+                       break;
+               if (txb->seq != call->tx_bottom + 1)
+                       rxrpc_see_txbuf(txb, rxrpc_txbuf_see_out_of_step);
+               ASSERTCMP(txb->seq, ==, call->tx_bottom + 1);
+-              smp_store_release(&call->tx_bottom, call->tx_bottom + 1);
++              WRITE_ONCE(call->tx_bottom, call->tx_bottom + 1);
+               list_del_rcu(&txb->call_link);
+               trace_rxrpc_txqueue(call, rxrpc_txqueue_dequeue);
diff --git a/queue-6.12/rxrpc-fix-cpu-time-starvation-in-i-o-thread.patch b/queue-6.12/rxrpc-fix-cpu-time-starvation-in-i-o-thread.patch
new file mode 100644 (file)
index 0000000..1ae0646
--- /dev/null
@@ -0,0 +1,403 @@
+From stable+bounces-287939-greg=kroah.com@vger.kernel.org Wed Jul 22 18:32:12 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 12:08:01 -0400
+Subject: rxrpc: Fix CPU time starvation in I/O thread
+To: stable@vger.kernel.org
+Cc: David Howells <dhowells@redhat.com>, Marc Dionne <marc.dionne@auristor.com>, linux-afs@lists.infradead.org, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722160805.1741176-1-sashal@kernel.org>
+
+From: David Howells <dhowells@redhat.com>
+
+[ Upstream commit 9e3cccd176b5ec6ff78693287fb03097e453e69c ]
+
+Starvation can happen in the rxrpc I/O thread because it goes back to the
+top of the I/O loop after it does any one thing without trying to give any
+other connection or call CPU time.  Also, because it processes one call
+packet at a time, it tries to do the retransmission loop after each ACK
+without checking to see if there are other ACKs already in the queue that
+can update the SACK state.
+
+Fix this by:
+
+ (1) Add a received-packet queue on each call.
+
+ (2) Distribute packets from the master Rx queue to the individual call,
+     conn and error queues and 'poking' calls to add them to the attend
+     queue first thing in the I/O thread.
+
+ (3) Go through all the attention-seeking connections and calls before
+     going back to the top of the I/O thread.  Each queue is extracted as a
+     whole and then gone through so that new additions to insert themselves
+     into the queue.
+
+ (4) Make the call event handler go through all the packets currently on
+     the call's rx_queue before transmitting and retransmitting DATA
+     packets.
+
+ (5) Drop the skb argument from the call event handler as this is now
+     replaced with the rx_queue.  Instead, keep track of whether we
+     received a packet or an ACK for the tests that used to rely on that.
+
+Signed-off-by: David Howells <dhowells@redhat.com>
+cc: Marc Dionne <marc.dionne@auristor.com>
+cc: linux-afs@lists.infradead.org
+Link: https://patch.msgid.link/20241204074710.990092-14-dhowells@redhat.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Stable-dep-of: e66f8f32f501 ("rxrpc: Fix socket notification race")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/trace/events/rxrpc.h |    2 
+ net/rxrpc/ar-internal.h      |   10 +++-
+ net/rxrpc/call_accept.c      |    2 
+ net/rxrpc/call_event.c       |   31 +++++++-----
+ net/rxrpc/call_object.c      |    2 
+ net/rxrpc/conn_client.c      |   12 ++--
+ net/rxrpc/input.c            |    2 
+ net/rxrpc/io_thread.c        |  104 +++++++++++++++++++++----------------------
+ net/rxrpc/peer_event.c       |    2 
+ 9 files changed, 93 insertions(+), 74 deletions(-)
+
+--- a/include/trace/events/rxrpc.h
++++ b/include/trace/events/rxrpc.h
+@@ -121,6 +121,7 @@
+       EM(rxrpc_call_poke_conn_abort,          "Conn-abort")   \
+       EM(rxrpc_call_poke_error,               "Error")        \
+       EM(rxrpc_call_poke_idle,                "Idle")         \
++      EM(rxrpc_call_poke_rx_packet,           "Rx-packet")    \
+       EM(rxrpc_call_poke_set_timeout,         "Set-timo")     \
+       EM(rxrpc_call_poke_start,               "Start")        \
+       EM(rxrpc_call_poke_timer,               "Timer")        \
+@@ -139,6 +140,7 @@
+       EM(rxrpc_skb_new_error_report,          "NEW error-rpt") \
+       EM(rxrpc_skb_new_jumbo_subpacket,       "NEW jumbo-sub") \
+       EM(rxrpc_skb_new_unshared,              "NEW unshared ") \
++      EM(rxrpc_skb_put_call_rx,               "PUT call-rx  ") \
+       EM(rxrpc_skb_put_conn_secured,          "PUT conn-secd") \
+       EM(rxrpc_skb_put_conn_work,             "PUT conn-work") \
+       EM(rxrpc_skb_put_error_report,          "PUT error-rep") \
+--- a/net/rxrpc/ar-internal.h
++++ b/net/rxrpc/ar-internal.h
+@@ -683,6 +683,7 @@ struct rxrpc_call {
+       /* Received data tracking */
+       struct sk_buff_head     recvmsg_queue;  /* Queue of packets ready for recvmsg() */
++      struct sk_buff_head     rx_queue;       /* Queue of packets for this call to receive */
+       struct sk_buff_head     rx_oos_queue;   /* Queue of out of sequence packets */
+       void                    *rx_dec_buffer; /* Decryption buffer */
+       unsigned short          rx_dec_bsize;   /* rx_dec_buffer size */
+@@ -871,7 +872,7 @@ void rxrpc_propose_delay_ACK(struct rxrp
+ void rxrpc_shrink_call_tx_buffer(struct rxrpc_call *);
+ void rxrpc_resend(struct rxrpc_call *call, struct sk_buff *ack_skb);
+-bool rxrpc_input_call_event(struct rxrpc_call *call, struct sk_buff *skb);
++bool rxrpc_input_call_event(struct rxrpc_call *call);
+ /*
+  * call_object.c
+@@ -1320,6 +1321,13 @@ static inline bool after_eq(u32 seq1, u3
+         return (s32)(seq1 - seq2) >= 0;
+ }
++static inline void rxrpc_queue_rx_call_packet(struct rxrpc_call *call, struct sk_buff *skb)
++{
++      rxrpc_get_skb(skb, rxrpc_skb_get_call_rx);
++      __skb_queue_tail(&call->rx_queue, skb);
++      rxrpc_poke_call(call, rxrpc_call_poke_rx_packet);
++}
++
+ /*
+  * debug tracing
+  */
+--- a/net/rxrpc/call_accept.c
++++ b/net/rxrpc/call_accept.c
+@@ -415,7 +415,7 @@ bool rxrpc_new_incoming_call(struct rxrp
+       }
+       _leave(" = %p{%d}", call, call->debug_id);
+-      rxrpc_input_call_event(call, skb);
++      rxrpc_queue_rx_call_packet(call, skb);
+       rxrpc_put_call(call, rxrpc_call_put_input);
+       return true;
+--- a/net/rxrpc/call_event.c
++++ b/net/rxrpc/call_event.c
+@@ -315,10 +315,11 @@ static void rxrpc_send_initial_ping(stru
+ /*
+  * Handle retransmission and deferred ACK/abort generation.
+  */
+-bool rxrpc_input_call_event(struct rxrpc_call *call, struct sk_buff *skb)
++bool rxrpc_input_call_event(struct rxrpc_call *call)
+ {
++      struct sk_buff *skb;
+       ktime_t now, t;
+-      bool resend = false;
++      bool resend = false, saw_ack = false;
+       s32 abort_code;
+       rxrpc_see_call(call, rxrpc_call_see_input);
+@@ -328,9 +329,6 @@ bool rxrpc_input_call_event(struct rxrpc
+              call->debug_id, rxrpc_call_states[__rxrpc_call_state(call)],
+              call->events);
+-      if (__rxrpc_call_is_complete(call))
+-              goto out;
+-
+       /* Handle abort request locklessly, vs rxrpc_propose_abort(). */
+       abort_code = smp_load_acquire(&call->send_abort);
+       if (abort_code) {
+@@ -339,11 +337,20 @@ bool rxrpc_input_call_event(struct rxrpc
+               goto out;
+       }
+-      if (skb && skb->mark == RXRPC_SKB_MARK_ERROR)
+-              goto out;
++      while ((skb = __skb_dequeue(&call->rx_queue))) {
++              struct rxrpc_skb_priv *sp = rxrpc_skb(skb);
++
++              if (__rxrpc_call_is_complete(call) ||
++                  skb->mark == RXRPC_SKB_MARK_ERROR) {
++                      rxrpc_free_skb(skb, rxrpc_skb_put_call_rx);
++                      goto out;
++              }
++
++              saw_ack |= sp->hdr.type == RXRPC_PACKET_TYPE_ACK;
+-      if (skb)
+               rxrpc_input_call_packet(call, skb);
++              rxrpc_free_skb(skb, rxrpc_skb_put_call_rx);
++      }
+       /* If we see our async-event poke, check for timeout trippage. */
+       now = ktime_get_real();
+@@ -409,12 +416,8 @@ bool rxrpc_input_call_event(struct rxrpc
+                              rxrpc_propose_ack_ping_for_keepalive);
+       }
+-      if (skb) {
+-              struct rxrpc_skb_priv *sp = rxrpc_skb(skb);
+-
+-              if (sp->hdr.type == RXRPC_PACKET_TYPE_ACK)
+-                      rxrpc_congestion_degrade(call);
+-      }
++      if (saw_ack)
++              rxrpc_congestion_degrade(call);
+       if (test_and_clear_bit(RXRPC_CALL_EV_INITIAL_PING, &call->events))
+               rxrpc_send_initial_ping(call);
+--- a/net/rxrpc/call_object.c
++++ b/net/rxrpc/call_object.c
+@@ -147,6 +147,7 @@ struct rxrpc_call *rxrpc_alloc_call(stru
+       INIT_LIST_HEAD(&call->attend_link);
+       INIT_LIST_HEAD(&call->tx_sendmsg);
+       INIT_LIST_HEAD(&call->tx_buffer);
++      skb_queue_head_init(&call->rx_queue);
+       skb_queue_head_init(&call->recvmsg_queue);
+       skb_queue_head_init(&call->rx_oos_queue);
+       init_waitqueue_head(&call->waitq);
+@@ -535,6 +536,7 @@ void rxrpc_get_call(struct rxrpc_call *c
+ static void rxrpc_cleanup_ring(struct rxrpc_call *call)
+ {
+       rxrpc_purge_queue(&call->recvmsg_queue);
++      rxrpc_purge_queue(&call->rx_queue);
+       rxrpc_purge_queue(&call->rx_oos_queue);
+       kfree(call->rx_dec_buffer);
+ }
+--- a/net/rxrpc/conn_client.c
++++ b/net/rxrpc/conn_client.c
+@@ -508,16 +508,18 @@ static void rxrpc_activate_channels(stru
+ void rxrpc_connect_client_calls(struct rxrpc_local *local)
+ {
+       struct rxrpc_call *call;
++      LIST_HEAD(new_client_calls);
+-      while ((call = list_first_entry_or_null(&local->new_client_calls,
+-                                              struct rxrpc_call, wait_link))
+-             ) {
++      spin_lock(&local->client_call_lock);
++      list_splice_tail_init(&local->new_client_calls, &new_client_calls);
++      spin_unlock(&local->client_call_lock);
++
++      while ((call = list_first_entry_or_null(&new_client_calls,
++                                              struct rxrpc_call, wait_link))) {
+               struct rxrpc_bundle *bundle = call->bundle;
+-              spin_lock(&local->client_call_lock);
+               list_move_tail(&call->wait_link, &bundle->waiting_calls);
+               rxrpc_see_call(call, rxrpc_call_see_waiting_call);
+-              spin_unlock(&local->client_call_lock);
+               if (rxrpc_bundle_has_space(bundle))
+                       rxrpc_activate_channels(bundle);
+--- a/net/rxrpc/input.c
++++ b/net/rxrpc/input.c
+@@ -1122,5 +1122,5 @@ void rxrpc_implicit_end_call(struct rxrp
+               break;
+       }
+-      rxrpc_input_call_event(call, skb);
++      rxrpc_input_call_event(call);
+ }
+--- a/net/rxrpc/io_thread.c
++++ b/net/rxrpc/io_thread.c
+@@ -318,7 +318,6 @@ static int rxrpc_input_packet_on_conn(st
+       struct rxrpc_channel *chan;
+       struct rxrpc_call *call = NULL;
+       unsigned int channel;
+-      bool ret;
+       if (sp->hdr.securityIndex != conn->security_ix)
+               return rxrpc_direct_abort(skb, rxrpc_eproto_wrong_security,
+@@ -400,9 +399,9 @@ static int rxrpc_input_packet_on_conn(st
+                                              peer_srx, skb);
+       }
+-      ret = rxrpc_input_call_event(call, skb);
++      rxrpc_queue_rx_call_packet(call, skb);
+       rxrpc_put_call(call, rxrpc_call_put_input);
+-      return ret;
++      return true;
+ }
+ /*
+@@ -419,6 +418,8 @@ int rxrpc_io_thread(void *data)
+       ktime_t now;
+ #endif
+       bool should_stop;
++      LIST_HEAD(conn_attend_q);
++      LIST_HEAD(call_attend_q);
+       complete(&local->io_thread_ready);
+@@ -429,43 +430,25 @@ int rxrpc_io_thread(void *data)
+       for (;;) {
+               rxrpc_inc_stat(local->rxnet, stat_io_loop);
+-              /* Deal with connections that want immediate attention. */
+-              conn = list_first_entry_or_null(&local->conn_attend_q,
+-                                              struct rxrpc_connection,
+-                                              attend_link);
+-              if (conn) {
+-                      spin_lock_bh(&local->lock);
+-                      list_del_init(&conn->attend_link);
+-                      spin_unlock_bh(&local->lock);
+-
+-                      rxrpc_input_conn_event(conn, NULL);
+-                      rxrpc_put_connection(conn, rxrpc_conn_put_poke);
+-                      continue;
++              /* Inject a delay into packets if requested. */
++#ifdef CONFIG_AF_RXRPC_INJECT_RX_DELAY
++              now = ktime_get_real();
++              while ((skb = skb_peek(&local->rx_delay_queue))) {
++                      if (ktime_before(now, skb->tstamp))
++                              break;
++                      skb = skb_dequeue(&local->rx_delay_queue);
++                      skb_queue_tail(&local->rx_queue, skb);
+               }
++#endif
+-              if (test_and_clear_bit(RXRPC_CLIENT_CONN_REAP_TIMER,
+-                                     &local->client_conn_flags))
+-                      rxrpc_discard_expired_client_conns(local);
+-
+-              /* Deal with calls that want immediate attention. */
+-              if ((call = list_first_entry_or_null(&local->call_attend_q,
+-                                                   struct rxrpc_call,
+-                                                   attend_link))) {
+-                      spin_lock_bh(&local->lock);
+-                      list_del_init(&call->attend_link);
+-                      spin_unlock_bh(&local->lock);
+-
+-                      trace_rxrpc_call_poked(call);
+-                      rxrpc_input_call_event(call, NULL);
+-                      rxrpc_put_call(call, rxrpc_call_put_poke);
+-                      continue;
++              if (!skb_queue_empty(&local->rx_queue)) {
++                      spin_lock_irq(&local->rx_queue.lock);
++                      skb_queue_splice_tail_init(&local->rx_queue, &rx_queue);
++                      spin_unlock_irq(&local->rx_queue.lock);
+               }
+-              if (!list_empty(&local->new_client_calls))
+-                      rxrpc_connect_client_calls(local);
+-
+-              /* Process received packets and errors. */
+-              if ((skb = __skb_dequeue(&rx_queue))) {
++              /* Distribute packets and errors. */
++              while ((skb = __skb_dequeue(&rx_queue))) {
+                       struct rxrpc_skb_priv *sp = rxrpc_skb(skb);
+                       switch (skb->mark) {
+                       case RXRPC_SKB_MARK_PACKET:
+@@ -489,27 +472,46 @@ int rxrpc_io_thread(void *data)
+                               rxrpc_free_skb(skb, rxrpc_skb_put_unknown);
+                               break;
+                       }
+-                      continue;
+               }
+-              /* Inject a delay into packets if requested. */
+-#ifdef CONFIG_AF_RXRPC_INJECT_RX_DELAY
+-              now = ktime_get_real();
+-              while ((skb = skb_peek(&local->rx_delay_queue))) {
+-                      if (ktime_before(now, skb->tstamp))
+-                              break;
+-                      skb = skb_dequeue(&local->rx_delay_queue);
+-                      skb_queue_tail(&local->rx_queue, skb);
++              /* Deal with connections that want immediate attention. */
++              spin_lock_bh(&local->lock);
++              list_splice_tail_init(&local->conn_attend_q, &conn_attend_q);
++              spin_unlock_bh(&local->lock);
++
++              while ((conn = list_first_entry_or_null(&conn_attend_q,
++                                                      struct rxrpc_connection,
++                                                      attend_link))) {
++                      spin_lock_bh(&local->lock);
++                      list_del_init(&conn->attend_link);
++                      spin_unlock_bh(&local->lock);
++                      rxrpc_input_conn_event(conn, NULL);
++                      rxrpc_put_connection(conn, rxrpc_conn_put_poke);
+               }
+-#endif
+-              if (!skb_queue_empty(&local->rx_queue)) {
+-                      spin_lock_irq(&local->rx_queue.lock);
+-                      skb_queue_splice_tail_init(&local->rx_queue, &rx_queue);
+-                      spin_unlock_irq(&local->rx_queue.lock);
+-                      continue;
++              if (test_and_clear_bit(RXRPC_CLIENT_CONN_REAP_TIMER,
++                                     &local->client_conn_flags))
++                      rxrpc_discard_expired_client_conns(local);
++
++              /* Deal with calls that want immediate attention. */
++              spin_lock_bh(&local->lock);
++              list_splice_tail_init(&local->call_attend_q, &call_attend_q);
++              spin_unlock_bh(&local->lock);
++
++              while ((call = list_first_entry_or_null(&call_attend_q,
++                                                      struct rxrpc_call,
++                                                      attend_link))) {
++                      spin_lock_bh(&local->lock);
++                      list_del_init(&call->attend_link);
++                      spin_unlock_bh(&local->lock);
++                      trace_rxrpc_call_poked(call);
++                      rxrpc_input_call_event(call);
++                      rxrpc_put_call(call, rxrpc_call_put_poke);
+               }
++              if (!list_empty(&local->new_client_calls))
++                      rxrpc_connect_client_calls(local);
++
+               set_current_state(TASK_INTERRUPTIBLE);
+               should_stop = kthread_should_stop();
+               if (!skb_queue_empty(&local->rx_queue) ||
+--- a/net/rxrpc/peer_event.c
++++ b/net/rxrpc/peer_event.c
+@@ -216,7 +216,7 @@ static void rxrpc_distribute_error(struc
+               rxrpc_see_call(call, rxrpc_call_see_distribute_error);
+               rxrpc_set_call_completion(call, compl, 0, -err);
+-              rxrpc_input_call_event(call, skb);
++              rxrpc_input_call_event(call);
+               spin_lock(&peer->lock);
+       }
diff --git a/queue-6.12/rxrpc-fix-notification-vs-call-release-vs-recvmsg.patch b/queue-6.12/rxrpc-fix-notification-vs-call-release-vs-recvmsg.patch
new file mode 100644 (file)
index 0000000..c22b474
--- /dev/null
@@ -0,0 +1,138 @@
+From stable+bounces-287942-greg=kroah.com@vger.kernel.org Wed Jul 22 18:32:08 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 12:08:04 -0400
+Subject: rxrpc: Fix notification vs call-release vs recvmsg
+To: stable@vger.kernel.org
+Cc: David Howells <dhowells@redhat.com>, Jeffrey Altman <jaltman@auristor.com>, Marc Dionne <marc.dionne@auristor.com>, "Junvyyang, Tencent Zhuque Lab" <zhuque@tencent.com>, LePremierHomme <kwqcheii@proton.me>, Simon Horman <horms@kernel.org>, linux-afs@lists.infradead.org, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722160805.1741176-4-sashal@kernel.org>
+
+From: David Howells <dhowells@redhat.com>
+
+[ Upstream commit 2fd895842d49c23137ae48252dd211e5d6d8a3ed ]
+
+When a call is released, rxrpc takes the spinlock and removes it from
+->recvmsg_q in an effort to prevent racing recvmsg() invocations from
+seeing the same call.  Now, rxrpc_recvmsg() only takes the spinlock when
+actually removing a call from the queue; it doesn't, however, take it in
+the lead up to that when it checks to see if the queue is empty.  It *does*
+hold the socket lock, which prevents a recvmsg/recvmsg race - but this
+doesn't prevent sendmsg from ending the call because sendmsg() drops the
+socket lock and relies on the call->user_mutex.
+
+Fix this by firstly removing the bit in rxrpc_release_call() that dequeues
+the released call and, instead, rely on recvmsg() to simply discard
+released calls (done in a preceding fix).
+
+Secondly, rxrpc_notify_socket() is abandoned if the call is already marked
+as released rather than trying to be clever by setting both pointers in
+call->recvmsg_link to NULL to trick list_empty().  This isn't perfect and
+can still race, resulting in a released call on the queue, but recvmsg()
+will now clean that up.
+
+Fixes: 17926a79320a ("[AF_RXRPC]: Provide secure RxRPC sockets for use by userspace and kernel both")
+Signed-off-by: David Howells <dhowells@redhat.com>
+Reviewed-by: Jeffrey Altman <jaltman@auristor.com>
+cc: Marc Dionne <marc.dionne@auristor.com>
+cc: Junvyyang, Tencent Zhuque Lab <zhuque@tencent.com>
+cc: LePremierHomme <kwqcheii@proton.me>
+cc: Simon Horman <horms@kernel.org>
+cc: linux-afs@lists.infradead.org
+Link: https://patch.msgid.link/20250717074350.3767366-4-dhowells@redhat.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Stable-dep-of: e66f8f32f501 ("rxrpc: Fix socket notification race")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/trace/events/rxrpc.h |    3 ++-
+ net/rxrpc/call_object.c      |   28 ++++++++++++----------------
+ net/rxrpc/recvmsg.c          |    4 ++++
+ 3 files changed, 18 insertions(+), 17 deletions(-)
+
+--- a/include/trace/events/rxrpc.h
++++ b/include/trace/events/rxrpc.h
+@@ -278,10 +278,10 @@
+       EM(rxrpc_call_put_poke,                 "PUT poke    ") \
+       EM(rxrpc_call_put_recvmsg,              "PUT recvmsg ") \
+       EM(rxrpc_call_put_recvmsg_peek_nowait,  "PUT peek-nwt") \
++      EM(rxrpc_call_put_release_recvmsg_q,    "PUT rls-rcmq") \
+       EM(rxrpc_call_put_release_sock,         "PUT rls-sock") \
+       EM(rxrpc_call_put_release_sock_tba,     "PUT rls-sk-a") \
+       EM(rxrpc_call_put_sendmsg,              "PUT sendmsg ") \
+-      EM(rxrpc_call_put_unnotify,             "PUT unnotify") \
+       EM(rxrpc_call_put_userid_exists,        "PUT u-exists") \
+       EM(rxrpc_call_put_userid,               "PUT user-id ") \
+       EM(rxrpc_call_see_accept,               "SEE accept  ") \
+@@ -294,6 +294,7 @@
+       EM(rxrpc_call_see_disconnected,         "SEE disconn ") \
+       EM(rxrpc_call_see_distribute_error,     "SEE dist-err") \
+       EM(rxrpc_call_see_input,                "SEE input   ") \
++      EM(rxrpc_call_see_notify_released,      "SEE nfy-rlsd") \
+       EM(rxrpc_call_see_recvmsg,              "SEE recvmsg ") \
+       EM(rxrpc_call_see_recvmsg_requeue,      "SEE recv-rqu") \
+       EM(rxrpc_call_see_recvmsg_requeue_first, "SEE recv-rqF") \
+--- a/net/rxrpc/call_object.c
++++ b/net/rxrpc/call_object.c
+@@ -547,7 +547,7 @@ static void rxrpc_cleanup_ring(struct rx
+ void rxrpc_release_call(struct rxrpc_sock *rx, struct rxrpc_call *call)
+ {
+       struct rxrpc_connection *conn = call->conn;
+-      bool put = false, putu = false;
++      bool putu = false;
+       _enter("{%d,%d}", call->debug_id, refcount_read(&call->ref));
+@@ -559,23 +559,13 @@ void rxrpc_release_call(struct rxrpc_soc
+       rxrpc_put_call_slot(call);
+-      /* Make sure we don't get any more notifications */
++      /* Note that at this point, the call may still be on or may have been
++       * added back on to the socket receive queue.  recvmsg() must discard
++       * released calls.  The CALL_RELEASED flag should prevent further
++       * notifications.
++       */
+       spin_lock_irq(&rx->recvmsg_lock);
+-
+-      if (!list_empty(&call->recvmsg_link)) {
+-              _debug("unlinking once-pending call %p { e=%lx f=%lx }",
+-                     call, call->events, call->flags);
+-              list_del(&call->recvmsg_link);
+-              put = true;
+-      }
+-
+-      /* list_empty() must return false in rxrpc_notify_socket() */
+-      call->recvmsg_link.next = NULL;
+-      call->recvmsg_link.prev = NULL;
+-
+       spin_unlock_irq(&rx->recvmsg_lock);
+-      if (put)
+-              rxrpc_put_call(call, rxrpc_call_put_unnotify);
+       write_lock(&rx->call_lock);
+@@ -624,6 +614,12 @@ void rxrpc_release_calls_on_socket(struc
+               rxrpc_put_call(call, rxrpc_call_put_release_sock);
+       }
++      while ((call = list_first_entry_or_null(&rx->recvmsg_q,
++                                              struct rxrpc_call, recvmsg_link))) {
++              list_del_init(&call->recvmsg_link);
++              rxrpc_put_call(call, rxrpc_call_put_release_recvmsg_q);
++      }
++
+       _leave("");
+ }
+--- a/net/rxrpc/recvmsg.c
++++ b/net/rxrpc/recvmsg.c
+@@ -29,6 +29,10 @@ void rxrpc_notify_socket(struct rxrpc_ca
+       if (!list_empty(&call->recvmsg_link))
+               return;
++      if (test_bit(RXRPC_CALL_RELEASED, &call->flags)) {
++              rxrpc_see_call(call, rxrpc_call_see_notify_released);
++              return;
++      }
+       rcu_read_lock();
diff --git a/queue-6.12/rxrpc-fix-socket-notification-race.patch b/queue-6.12/rxrpc-fix-socket-notification-race.patch
new file mode 100644 (file)
index 0000000..cee415a
--- /dev/null
@@ -0,0 +1,47 @@
+From stable+bounces-287943-greg=kroah.com@vger.kernel.org Wed Jul 22 18:10:20 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 12:08:05 -0400
+Subject: rxrpc: Fix socket notification race
+To: stable@vger.kernel.org
+Cc: David Howells <dhowells@redhat.com>, Marc Dionne <marc.dionne@auristor.com>, Jeffrey Altman <jaltman@auristor.com>, Simon Horman <horms@kernel.org>, linux-afs@lists.infradead.org, stable@kernel.org, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722160805.1741176-5-sashal@kernel.org>
+
+From: David Howells <dhowells@redhat.com>
+
+[ Upstream commit e66f8f32f50116670dbbee5bc9e692cd2cd0c8f8 ]
+
+There's a race between rxrpc_recvmsg() and rxrpc_notify_socket(), whereby
+the latter's attempt to avoid disabling interrupts and taking the socket's
+recvmsg_lock if the call is already queued may happen simultaneously with
+the former's discarding of a call that has nothing queued.
+
+Fix this by removing the shortcut.  Note that this only affects userspace's
+use of AF_RXRPC; the AFS filesystem driver doesn't use the socket queue.
+
+Fixes: 248f219cb8bc ("rxrpc: Rewrite the data and ack handling code")
+Link: https://sashiko.dev/#/patchset/20260616155749.2125907-1-dhowells%40redhat.com
+Signed-off-by: David Howells <dhowells@redhat.com>
+cc: Marc Dionne <marc.dionne@auristor.com>
+cc: Jeffrey Altman <jaltman@auristor.com>
+cc: Simon Horman <horms@kernel.org>
+cc: linux-afs@lists.infradead.org
+cc: stable@kernel.org
+Link: https://patch.msgid.link/20260624163819.3017002-10-dhowells@redhat.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/rxrpc/recvmsg.c |    2 --
+ 1 file changed, 2 deletions(-)
+
+--- a/net/rxrpc/recvmsg.c
++++ b/net/rxrpc/recvmsg.c
+@@ -27,8 +27,6 @@ void rxrpc_notify_socket(struct rxrpc_ca
+       _enter("%d", call->debug_id);
+-      if (!list_empty(&call->recvmsg_link))
+-              return;
+       if (test_bit(RXRPC_CALL_RELEASED, &call->flags)) {
+               rxrpc_see_call(call, rxrpc_call_see_notify_released);
+               return;
diff --git a/queue-6.12/rxrpc-pull-out-certain-app-callback-funcs-into-an-ops-table.patch b/queue-6.12/rxrpc-pull-out-certain-app-callback-funcs-into-an-ops-table.patch
new file mode 100644 (file)
index 0000000..c13a3da
--- /dev/null
@@ -0,0 +1,294 @@
+From stable+bounces-287863-greg=kroah.com@vger.kernel.org Wed Jul 22 16:03:05 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 09:54:49 -0400
+Subject: rxrpc: Pull out certain app callback funcs into an ops table
+To: stable@vger.kernel.org
+Cc: David Howells <dhowells@redhat.com>, Marc Dionne <marc.dionne@auristor.com>, Simon Horman <horms@kernel.org>, linux-afs@lists.infradead.org, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722135450.1365902-1-sashal@kernel.org>
+
+From: David Howells <dhowells@redhat.com>
+
+[ Upstream commit 23738cc8048322cf324f330cd697380fb3455da5 ]
+
+A number of functions separately furnish an AF_RXRPC socket with callback
+function pointers into a kernel app (such as the AFS filesystem) that is
+using it.  Replace most of these with an ops table for the entire socket.
+This makes it easier to add more callback functions.
+
+Note that the call incoming data processing callback is retaind as that
+gets set to different things, depending on the type of op.
+
+Signed-off-by: David Howells <dhowells@redhat.com>
+cc: Marc Dionne <marc.dionne@auristor.com>
+cc: Simon Horman <horms@kernel.org>
+cc: linux-afs@lists.infradead.org
+Link: https://patch.msgid.link/20250411095303.2316168-3-dhowells@redhat.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Stable-dep-of: dc175389b18c ("rxrpc: serialize kernel accept preallocation with socket teardown")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/afs/rxrpc.c          |   11 ++++++++---
+ include/net/af_rxrpc.h  |   25 +++++++++++++++----------
+ net/rxrpc/af_rxrpc.c    |   20 ++++++++------------
+ net/rxrpc/ar-internal.h |    3 +--
+ net/rxrpc/call_accept.c |   34 ++++++++++++++++------------------
+ net/rxrpc/rxperf.c      |   10 +++++++---
+ 6 files changed, 55 insertions(+), 48 deletions(-)
+
+--- a/fs/afs/rxrpc.c
++++ b/fs/afs/rxrpc.c
+@@ -24,8 +24,15 @@ static void afs_wake_up_async_call(struc
+ static void afs_process_async_call(struct work_struct *);
+ static void afs_rx_new_call(struct sock *, struct rxrpc_call *, unsigned long);
+ static void afs_rx_discard_new_call(struct rxrpc_call *, unsigned long);
++static void afs_rx_attach(struct rxrpc_call *rxcall, unsigned long user_call_ID);
+ static int afs_deliver_cm_op_id(struct afs_call *);
++static const struct rxrpc_kernel_ops afs_rxrpc_callback_ops = {
++      .notify_new_call        = afs_rx_new_call,
++      .discard_new_call       = afs_rx_discard_new_call,
++      .user_attach_call       = afs_rx_attach,
++};
++
+ /* asynchronous incoming call initial processing */
+ static const struct afs_call_type afs_RXCMxxxx = {
+       .name           = "CB.xxxx",
+@@ -84,8 +91,7 @@ int afs_open_socket(struct afs_net *net)
+        * it sends back to us.
+        */
+-      rxrpc_kernel_new_call_notification(socket, afs_rx_new_call,
+-                                         afs_rx_discard_new_call);
++      rxrpc_kernel_set_notifications(socket, &afs_rxrpc_callback_ops);
+       ret = kernel_listen(socket, INT_MAX);
+       if (ret < 0)
+@@ -758,7 +764,6 @@ void afs_charge_preallocation(struct wor
+               if (rxrpc_kernel_charge_accept(net->socket,
+                                              afs_wake_up_async_call,
+-                                             afs_rx_attach,
+                                              (unsigned long)call,
+                                              GFP_KERNEL,
+                                              call->debug_id) < 0)
+--- a/include/net/af_rxrpc.h
++++ b/include/net/af_rxrpc.h
+@@ -29,18 +29,23 @@ enum rxrpc_interruptibility {
+  */
+ extern atomic_t rxrpc_debug_id;
++/*
++ * Operations table for rxrpc to call out to a kernel application (e.g. kAFS).
++ */
++struct rxrpc_kernel_ops {
++      void (*notify_new_call)(struct sock *sk, struct rxrpc_call *call,
++                              unsigned long user_call_ID);
++      void (*discard_new_call)(struct rxrpc_call *call, unsigned long user_call_ID);
++      void (*user_attach_call)(struct rxrpc_call *call, unsigned long user_call_ID);
++};
++
+ typedef void (*rxrpc_notify_rx_t)(struct sock *, struct rxrpc_call *,
+                                 unsigned long);
+ typedef void (*rxrpc_notify_end_tx_t)(struct sock *, struct rxrpc_call *,
+                                     unsigned long);
+-typedef void (*rxrpc_notify_new_call_t)(struct sock *, struct rxrpc_call *,
+-                                      unsigned long);
+-typedef void (*rxrpc_discard_new_call_t)(struct rxrpc_call *, unsigned long);
+-typedef void (*rxrpc_user_attach_call_t)(struct rxrpc_call *, unsigned long);
+-void rxrpc_kernel_new_call_notification(struct socket *,
+-                                      rxrpc_notify_new_call_t,
+-                                      rxrpc_discard_new_call_t);
++void rxrpc_kernel_set_notifications(struct socket *sock,
++                                  const struct rxrpc_kernel_ops *app_ops);
+ struct rxrpc_call *rxrpc_kernel_begin_call(struct socket *sock,
+                                          struct rxrpc_peer *peer,
+                                          struct key *key,
+@@ -70,9 +75,9 @@ struct rxrpc_peer *rxrpc_kernel_get_call
+ const struct sockaddr_rxrpc *rxrpc_kernel_remote_srx(const struct rxrpc_peer *peer);
+ const struct sockaddr *rxrpc_kernel_remote_addr(const struct rxrpc_peer *peer);
+ unsigned int rxrpc_kernel_get_srtt(const struct rxrpc_peer *);
+-int rxrpc_kernel_charge_accept(struct socket *, rxrpc_notify_rx_t,
+-                             rxrpc_user_attach_call_t, unsigned long, gfp_t,
+-                             unsigned int);
++int rxrpc_kernel_charge_accept(struct socket *sock, rxrpc_notify_rx_t notify_rx,
++                             unsigned long user_call_ID, gfp_t gfp,
++                             unsigned int debug_id);
+ void rxrpc_kernel_set_tx_length(struct socket *, struct rxrpc_call *, s64);
+ bool rxrpc_kernel_check_life(const struct socket *, const struct rxrpc_call *);
+ u32 rxrpc_kernel_get_epoch(struct socket *, struct rxrpc_call *);
+--- a/net/rxrpc/af_rxrpc.c
++++ b/net/rxrpc/af_rxrpc.c
+@@ -464,24 +464,20 @@ u32 rxrpc_kernel_get_epoch(struct socket
+ EXPORT_SYMBOL(rxrpc_kernel_get_epoch);
+ /**
+- * rxrpc_kernel_new_call_notification - Get notifications of new calls
+- * @sock: The socket to intercept received messages on
+- * @notify_new_call: Function to be called when new calls appear
+- * @discard_new_call: Function to discard preallocated calls
++ * rxrpc_kernel_set_notifications - Set table of callback operations
++ * @sock: The socket to install table upon
++ * @app_ops: Callback operation table to set
+  *
+- * Allow a kernel service to be given notifications about new calls.
++ * Allow a kernel service to set a table of event notifications on a socket.
+  */
+-void rxrpc_kernel_new_call_notification(
+-      struct socket *sock,
+-      rxrpc_notify_new_call_t notify_new_call,
+-      rxrpc_discard_new_call_t discard_new_call)
++void rxrpc_kernel_set_notifications(struct socket *sock,
++                                  const struct rxrpc_kernel_ops *app_ops)
+ {
+       struct rxrpc_sock *rx = rxrpc_sk(sock->sk);
+-      rx->notify_new_call = notify_new_call;
+-      rx->discard_new_call = discard_new_call;
++      rx->app_ops = app_ops;
+ }
+-EXPORT_SYMBOL(rxrpc_kernel_new_call_notification);
++EXPORT_SYMBOL(rxrpc_kernel_set_notifications);
+ /**
+  * rxrpc_kernel_set_max_life - Set maximum lifespan on a call
+--- a/net/rxrpc/ar-internal.h
++++ b/net/rxrpc/ar-internal.h
+@@ -142,8 +142,7 @@ struct rxrpc_backlog {
+ struct rxrpc_sock {
+       /* WARNING: sk has to be the first member */
+       struct sock             sk;
+-      rxrpc_notify_new_call_t notify_new_call; /* Func to notify of new call */
+-      rxrpc_discard_new_call_t discard_new_call; /* Func to discard a new call */
++      const struct rxrpc_kernel_ops *app_ops; /* Table of kernel app notification funcs */
+       struct rxrpc_local      *local;         /* local endpoint */
+       struct rxrpc_backlog    *backlog;       /* Preallocation for services */
+       spinlock_t              incoming_lock;  /* Incoming call vs service shutdown lock */
+--- a/net/rxrpc/call_accept.c
++++ b/net/rxrpc/call_accept.c
+@@ -34,7 +34,6 @@ static void rxrpc_dummy_notify(struct so
+ static int rxrpc_service_prealloc_one(struct rxrpc_sock *rx,
+                                     struct rxrpc_backlog *b,
+                                     rxrpc_notify_rx_t notify_rx,
+-                                    rxrpc_user_attach_call_t user_attach_call,
+                                     unsigned long user_call_ID, gfp_t gfp,
+                                     unsigned int debug_id)
+ {
+@@ -123,9 +122,10 @@ static int rxrpc_service_prealloc_one(st
+       call->user_call_ID = user_call_ID;
+       call->notify_rx = notify_rx;
+-      if (user_attach_call) {
++      if (rx->app_ops &&
++          rx->app_ops->user_attach_call) {
+               rxrpc_get_call(call, rxrpc_call_get_kernel_service);
+-              user_attach_call(call, user_call_ID);
++              rx->app_ops->user_attach_call(call, user_call_ID);
+       }
+       rxrpc_get_call(call, rxrpc_call_get_userid);
+@@ -221,9 +221,10 @@ void rxrpc_discard_prealloc(struct rxrpc
+               struct rxrpc_call *call = b->call_backlog[tail];
+               rxrpc_see_call(call, rxrpc_call_see_discard);
+               rcu_assign_pointer(call->socket, rx);
+-              if (rx->discard_new_call) {
++              if (rx->app_ops &&
++                  rx->app_ops->discard_new_call) {
+                       _debug("discard %lx", call->user_call_ID);
+-                      rx->discard_new_call(call, call->user_call_ID);
++                      rx->app_ops->discard_new_call(call, call->user_call_ID);
+                       if (call->notify_rx)
+                               call->notify_rx = rxrpc_dummy_notify;
+                       rxrpc_put_call(call, rxrpc_call_put_kernel);
+@@ -392,8 +393,9 @@ bool rxrpc_new_incoming_call(struct rxrp
+       rxrpc_incoming_call(rx, call, skb);
+       conn = call->conn;
+-      if (rx->notify_new_call)
+-              rx->notify_new_call(&rx->sk, call, call->user_call_ID);
++      if (rx->app_ops &&
++          rx->app_ops->notify_new_call)
++              rx->app_ops->notify_new_call(&rx->sk, call, call->user_call_ID);
+       spin_lock(&conn->state_lock);
+       if (conn->state == RXRPC_CONN_SERVICE_UNSECURED) {
+@@ -445,8 +447,7 @@ int rxrpc_user_charge_accept(struct rxrp
+       if (rx->sk.sk_state == RXRPC_CLOSE)
+               return -ESHUTDOWN;
+-      return rxrpc_service_prealloc_one(rx, b, NULL, NULL, user_call_ID,
+-                                        GFP_KERNEL,
++      return rxrpc_service_prealloc_one(rx, b, NULL, user_call_ID, GFP_KERNEL,
+                                         atomic_inc_return(&rxrpc_debug_id));
+ }
+@@ -454,20 +455,18 @@ int rxrpc_user_charge_accept(struct rxrp
+  * rxrpc_kernel_charge_accept - Charge up socket with preallocated calls
+  * @sock: The socket on which to preallocate
+  * @notify_rx: Event notification function for the call
+- * @user_attach_call: Func to attach call to user_call_ID
+  * @user_call_ID: The tag to attach to the preallocated call
+  * @gfp: The allocation conditions.
+  * @debug_id: The tracing debug ID.
+  *
+- * Charge up the socket with preallocated calls, each with a user ID.  A
+- * function should be provided to effect the attachment from the user's side.
+- * The user is given a ref to hold on the call.
++ * Charge up the socket with preallocated calls, each with a user ID.  The
++ * ->user_attach_call() callback function should be provided to effect the
++ * attachment from the user's side.  The user is given a ref to hold on the
++ * call.
+  *
+  * Note that the call may be come connected before this function returns.
+  */
+-int rxrpc_kernel_charge_accept(struct socket *sock,
+-                             rxrpc_notify_rx_t notify_rx,
+-                             rxrpc_user_attach_call_t user_attach_call,
++int rxrpc_kernel_charge_accept(struct socket *sock, rxrpc_notify_rx_t notify_rx,
+                              unsigned long user_call_ID, gfp_t gfp,
+                              unsigned int debug_id)
+ {
+@@ -477,8 +476,7 @@ int rxrpc_kernel_charge_accept(struct so
+       if (sock->sk->sk_state == RXRPC_CLOSE)
+               return -ESHUTDOWN;
+-      return rxrpc_service_prealloc_one(rx, b, notify_rx,
+-                                        user_attach_call, user_call_ID,
++      return rxrpc_service_prealloc_one(rx, b, notify_rx, user_call_ID,
+                                         gfp, debug_id);
+ }
+ EXPORT_SYMBOL(rxrpc_kernel_charge_accept);
+--- a/net/rxrpc/rxperf.c
++++ b/net/rxrpc/rxperf.c
+@@ -136,6 +136,12 @@ static void rxperf_notify_end_reply_tx(s
+                             RXPERF_CALL_SV_AWAIT_ACK);
+ }
++static const struct rxrpc_kernel_ops rxperf_rxrpc_callback_ops = {
++      .notify_new_call        = rxperf_rx_new_call,
++      .discard_new_call       = rxperf_rx_discard_new_call,
++      .user_attach_call       = rxperf_rx_attach,
++};
++
+ /*
+  * Charge the incoming call preallocation.
+  */
+@@ -161,7 +167,6 @@ static void rxperf_charge_preallocation(
+               if (rxrpc_kernel_charge_accept(rxperf_socket,
+                                              rxperf_notify_rx,
+-                                             rxperf_rx_attach,
+                                              (unsigned long)call,
+                                              GFP_KERNEL,
+                                              call->debug_id) < 0)
+@@ -209,8 +214,7 @@ static int rxperf_open_socket(void)
+       if (ret < 0)
+               goto error_2;
+-      rxrpc_kernel_new_call_notification(socket, rxperf_rx_new_call,
+-                                         rxperf_rx_discard_new_call);
++      rxrpc_kernel_set_notifications(socket, &rxperf_rxrpc_callback_ops);
+       ret = kernel_listen(socket, INT_MAX);
+       if (ret < 0)
diff --git a/queue-6.12/rxrpc-serialize-kernel-accept-preallocation-with-socket-teardown.patch b/queue-6.12/rxrpc-serialize-kernel-accept-preallocation-with-socket-teardown.patch
new file mode 100644 (file)
index 0000000..4476d09
--- /dev/null
@@ -0,0 +1,79 @@
+From stable+bounces-287864-greg=kroah.com@vger.kernel.org Wed Jul 22 16:03:44 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 09:54:50 -0400
+Subject: rxrpc: serialize kernel accept preallocation with socket teardown
+To: stable@vger.kernel.org
+Cc: Li Daming <d4n.for.sec@gmail.com>, Yuan Tan <yuantan098@gmail.com>, Yifan Wu <yifanwucs@gmail.com>, Juefei Pu <tomapufckgml@gmail.com>, Xin Liu <bird@lzu.edu.cn>, Ren Wei <n05ec@lzu.edu.cn>, David Howells <dhowells@redhat.com>, Marc Dionne <marc.dionne@auristor.com>, Jeffrey Altman <jaltman@auristor.com>, Simon Horman <horms@kernel.org>, linux-afs@lists.infradead.org, stable@kernel.org, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722135450.1365902-2-sashal@kernel.org>
+
+From: Li Daming <d4n.for.sec@gmail.com>
+
+[ Upstream commit dc175389b18c29a5303ee83169ec653adfae3e17 ]
+
+rxrpc_kernel_charge_accept() reads rx->backlog without any
+socket/backlog synchronization and passes that raw pointer into
+rxrpc_service_prealloc_one(). A concurrent rxrpc_discard_prealloc()
+sets rx->backlog = NULL and frees the backlog rings, so a kernel
+preallocation worker can keep using a freed struct rxrpc_backlog
+while updating *_backlog_head/tail and array slots.
+
+Serialize the state check and backlog lookup with the socket lock,
+and reject kernel preallocation once teardown has disabled
+listening or discarded the service backlog.
+
+Fixes: 00e907127e6f ("rxrpc: Preallocate peers, conns and calls for incoming service requests")
+Reported-by: Yuan Tan <yuantan098@gmail.com>
+Reported-by: Yifan Wu <yifanwucs@gmail.com>
+Reported-by: Juefei Pu <tomapufckgml@gmail.com>
+Reported-by: Xin Liu <bird@lzu.edu.cn>
+Signed-off-by: Li Daming <d4n.for.sec@gmail.com>
+Signed-off-by: Ren Wei <n05ec@lzu.edu.cn>
+Signed-off-by: David Howells <dhowells@redhat.com>
+cc: Marc Dionne <marc.dionne@auristor.com>
+cc: Jeffrey Altman <jaltman@auristor.com>
+cc: Simon Horman <horms@kernel.org>
+cc: linux-afs@lists.infradead.org
+cc: stable@kernel.org
+Link: https://patch.msgid.link/20260609140911.838677-6-dhowells@redhat.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/rxrpc/call_accept.c |   25 +++++++++++++++++++------
+ 1 file changed, 19 insertions(+), 6 deletions(-)
+
+--- a/net/rxrpc/call_accept.c
++++ b/net/rxrpc/call_accept.c
+@@ -470,13 +470,26 @@ int rxrpc_kernel_charge_accept(struct so
+                              unsigned long user_call_ID, gfp_t gfp,
+                              unsigned int debug_id)
+ {
+-      struct rxrpc_sock *rx = rxrpc_sk(sock->sk);
+-      struct rxrpc_backlog *b = rx->backlog;
++      struct rxrpc_backlog *b;
++      struct rxrpc_sock *rx;
++      struct sock *sk;
++      int ret;
+-      if (sock->sk->sk_state == RXRPC_CLOSE)
+-              return -ESHUTDOWN;
++      sk = sock->sk;
++      rx = rxrpc_sk(sk);
+-      return rxrpc_service_prealloc_one(rx, b, notify_rx, user_call_ID,
+-                                        gfp, debug_id);
++      lock_sock(sk);
++      if (sk->sk_state != RXRPC_SERVER_LISTENING || !rx->backlog) {
++              ret = -ESHUTDOWN;
++              goto out;
++      }
++
++      b = rx->backlog;
++      ret = rxrpc_service_prealloc_one(rx, b, notify_rx, user_call_ID,
++                                       gfp, debug_id);
++
++out:
++      release_sock(sk);
++      return ret;
+ }
+ EXPORT_SYMBOL(rxrpc_kernel_charge_accept);
diff --git a/queue-6.12/rxrpc-use-irq-disabling-spinlocks-between-app-and-i-o-thread.patch b/queue-6.12/rxrpc-use-irq-disabling-spinlocks-between-app-and-i-o-thread.patch
new file mode 100644 (file)
index 0000000..202e34e
--- /dev/null
@@ -0,0 +1,473 @@
+From stable+bounces-287941-greg=kroah.com@vger.kernel.org Wed Jul 22 18:33:59 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 12:08:03 -0400
+Subject: rxrpc: Use irq-disabling spinlocks between app and I/O thread
+To: stable@vger.kernel.org
+Cc: David Howells <dhowells@redhat.com>, Marc Dionne <marc.dionne@auristor.com>, linux-afs@lists.infradead.org, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722160805.1741176-3-sashal@kernel.org>
+
+From: David Howells <dhowells@redhat.com>
+
+[ Upstream commit a2ea9a9072607c2fd6442bd1ffb4dbdbf882aed7 ]
+
+Where a spinlock is used by both the application thread and the I/O thread,
+use irq-disabling locking so that an interrupt taken on the app thread
+doesn't also slow down the I/O thread.
+
+Signed-off-by: David Howells <dhowells@redhat.com>
+cc: Marc Dionne <marc.dionne@auristor.com>
+cc: linux-afs@lists.infradead.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Stable-dep-of: e66f8f32f501 ("rxrpc: Fix socket notification race")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/rxrpc/af_rxrpc.c    |    4 ++--
+ net/rxrpc/call_accept.c |   20 ++++++++++----------
+ net/rxrpc/call_object.c |   14 +++++++-------
+ net/rxrpc/conn_client.c |   12 ++++++------
+ net/rxrpc/conn_event.c  |   14 +++++++-------
+ net/rxrpc/conn_object.c |    8 ++++----
+ net/rxrpc/input.c       |    5 +----
+ net/rxrpc/io_thread.c   |    8 ++++----
+ net/rxrpc/peer_event.c  |    8 ++++----
+ net/rxrpc/peer_object.c |    1 +
+ net/rxrpc/recvmsg.c     |   22 +++++++++++-----------
+ net/rxrpc/security.c    |    4 ++--
+ 12 files changed, 59 insertions(+), 61 deletions(-)
+
+--- a/net/rxrpc/af_rxrpc.c
++++ b/net/rxrpc/af_rxrpc.c
+@@ -408,9 +408,9 @@ void rxrpc_kernel_shutdown_call(struct s
+               /* Make sure we're not going to call back into a kernel service */
+               if (call->notify_rx) {
+-                      spin_lock(&call->notify_lock);
++                      spin_lock_irq(&call->notify_lock);
+                       call->notify_rx = rxrpc_dummy_notify_rx;
+-                      spin_unlock(&call->notify_lock);
++                      spin_unlock_irq(&call->notify_lock);
+               }
+       }
+       mutex_unlock(&call->user_mutex);
+--- a/net/rxrpc/call_accept.c
++++ b/net/rxrpc/call_accept.c
+@@ -189,8 +189,8 @@ void rxrpc_discard_prealloc(struct rxrpc
+       /* Make sure that there aren't any incoming calls in progress before we
+        * clear the preallocation buffers.
+        */
+-      spin_lock(&rx->incoming_lock);
+-      spin_unlock(&rx->incoming_lock);
++      spin_lock_irq(&rx->incoming_lock);
++      spin_unlock_irq(&rx->incoming_lock);
+       head = b->peer_backlog_head;
+       tail = b->peer_backlog_tail;
+@@ -349,7 +349,7 @@ bool rxrpc_new_incoming_call(struct rxrp
+       if (sp->hdr.type != RXRPC_PACKET_TYPE_DATA)
+               return rxrpc_protocol_error(skb, rxrpc_eproto_no_service_call);
+-      read_lock(&local->services_lock);
++      read_lock_irq(&local->services_lock);
+       /* Weed out packets to services we're not offering.  Packets that would
+        * begin a call are explicitly rejected and the rest are just
+@@ -406,12 +406,12 @@ bool rxrpc_new_incoming_call(struct rxrp
+       spin_unlock(&conn->state_lock);
+       spin_unlock(&rx->incoming_lock);
+-      read_unlock(&local->services_lock);
++      read_unlock_irq(&local->services_lock);
+       if (hlist_unhashed(&call->error_link)) {
+-              spin_lock(&call->peer->lock);
++              spin_lock_irq(&call->peer->lock);
+               hlist_add_head(&call->error_link, &call->peer->error_targets);
+-              spin_unlock(&call->peer->lock);
++              spin_unlock_irq(&call->peer->lock);
+       }
+       _leave(" = %p{%d}", call, call->debug_id);
+@@ -420,20 +420,20 @@ bool rxrpc_new_incoming_call(struct rxrp
+       return true;
+ unsupported_service:
+-      read_unlock(&local->services_lock);
++      read_unlock_irq(&local->services_lock);
+       return rxrpc_direct_abort(skb, rxrpc_abort_service_not_offered,
+                                 RX_INVALID_OPERATION, -EOPNOTSUPP);
+ unsupported_security:
+-      read_unlock(&local->services_lock);
++      read_unlock_irq(&local->services_lock);
+       return rxrpc_direct_abort(skb, rxrpc_abort_service_not_offered,
+                                 RX_INVALID_OPERATION, -EKEYREJECTED);
+ no_call:
+       spin_unlock(&rx->incoming_lock);
+-      read_unlock(&local->services_lock);
++      read_unlock_irq(&local->services_lock);
+       _leave(" = f [%u]", skb->mark);
+       return false;
+ discard:
+-      read_unlock(&local->services_lock);
++      read_unlock_irq(&local->services_lock);
+       return true;
+ }
+--- a/net/rxrpc/call_object.c
++++ b/net/rxrpc/call_object.c
+@@ -48,7 +48,7 @@ void rxrpc_poke_call(struct rxrpc_call *
+       bool busy;
+       if (!test_bit(RXRPC_CALL_DISCONNECTED, &call->flags)) {
+-              spin_lock_bh(&local->lock);
++              spin_lock_irq(&local->lock);
+               busy = !list_empty(&call->attend_link);
+               trace_rxrpc_poke_call(call, busy, what);
+               if (!busy && !rxrpc_try_get_call(call, rxrpc_call_get_poke))
+@@ -56,7 +56,7 @@ void rxrpc_poke_call(struct rxrpc_call *
+               if (!busy) {
+                       list_add_tail(&call->attend_link, &local->call_attend_q);
+               }
+-              spin_unlock_bh(&local->lock);
++              spin_unlock_irq(&local->lock);
+               if (!busy)
+                       rxrpc_wake_up_io_thread(local);
+       }
+@@ -303,9 +303,9 @@ static int rxrpc_connect_call(struct rxr
+       trace_rxrpc_client(NULL, -1, rxrpc_client_queue_new_call);
+       rxrpc_get_call(call, rxrpc_call_get_io_thread);
+-      spin_lock(&local->client_call_lock);
++      spin_lock_irq(&local->client_call_lock);
+       list_add_tail(&call->wait_link, &local->new_client_calls);
+-      spin_unlock(&local->client_call_lock);
++      spin_unlock_irq(&local->client_call_lock);
+       rxrpc_wake_up_io_thread(local);
+       return 0;
+@@ -435,7 +435,7 @@ error_attached_to_socket:
+ /*
+  * Set up an incoming call.  call->conn points to the connection.
+- * This is called in BH context and isn't allowed to fail.
++ * This is called with interrupts disabled and isn't allowed to fail.
+  */
+ void rxrpc_incoming_call(struct rxrpc_sock *rx,
+                        struct rxrpc_call *call,
+@@ -560,7 +560,7 @@ void rxrpc_release_call(struct rxrpc_soc
+       rxrpc_put_call_slot(call);
+       /* Make sure we don't get any more notifications */
+-      spin_lock(&rx->recvmsg_lock);
++      spin_lock_irq(&rx->recvmsg_lock);
+       if (!list_empty(&call->recvmsg_link)) {
+               _debug("unlinking once-pending call %p { e=%lx f=%lx }",
+@@ -573,7 +573,7 @@ void rxrpc_release_call(struct rxrpc_soc
+       call->recvmsg_link.next = NULL;
+       call->recvmsg_link.prev = NULL;
+-      spin_unlock(&rx->recvmsg_lock);
++      spin_unlock_irq(&rx->recvmsg_lock);
+       if (put)
+               rxrpc_put_call(call, rxrpc_call_put_unnotify);
+--- a/net/rxrpc/conn_client.c
++++ b/net/rxrpc/conn_client.c
+@@ -510,9 +510,9 @@ void rxrpc_connect_client_calls(struct r
+       struct rxrpc_call *call;
+       LIST_HEAD(new_client_calls);
+-      spin_lock(&local->client_call_lock);
++      spin_lock_irq(&local->client_call_lock);
+       list_splice_tail_init(&local->new_client_calls, &new_client_calls);
+-      spin_unlock(&local->client_call_lock);
++      spin_unlock_irq(&local->client_call_lock);
+       while ((call = list_first_entry_or_null(&new_client_calls,
+                                               struct rxrpc_call, wait_link))) {
+@@ -547,9 +547,9 @@ void rxrpc_expose_client_call(struct rxr
+                       set_bit(RXRPC_CONN_DONT_REUSE, &conn->flags);
+               trace_rxrpc_client(conn, channel, rxrpc_client_exposed);
+-              spin_lock(&call->peer->lock);
++              spin_lock_irq(&call->peer->lock);
+               hlist_add_head(&call->error_link, &call->peer->error_targets);
+-              spin_unlock(&call->peer->lock);
++              spin_unlock_irq(&call->peer->lock);
+       }
+ }
+@@ -590,9 +590,9 @@ void rxrpc_disconnect_client_call(struct
+               ASSERTCMP(call->call_id, ==, 0);
+               ASSERT(!test_bit(RXRPC_CALL_EXPOSED, &call->flags));
+               /* May still be on ->new_client_calls. */
+-              spin_lock(&local->client_call_lock);
++              spin_lock_irq(&local->client_call_lock);
+               list_del_init(&call->wait_link);
+-              spin_unlock(&local->client_call_lock);
++              spin_unlock_irq(&local->client_call_lock);
+               return;
+       }
+--- a/net/rxrpc/conn_event.c
++++ b/net/rxrpc/conn_event.c
+@@ -26,7 +26,7 @@ static bool rxrpc_set_conn_aborted(struc
+       bool aborted = false;
+       if (conn->state != RXRPC_CONN_ABORTED) {
+-              spin_lock(&conn->state_lock);
++              spin_lock_irq(&conn->state_lock);
+               if (conn->state != RXRPC_CONN_ABORTED) {
+                       conn->abort_code = abort_code;
+                       conn->error      = err;
+@@ -37,7 +37,7 @@ static bool rxrpc_set_conn_aborted(struc
+                       set_bit(RXRPC_CONN_EV_ABORT_CALLS, &conn->events);
+                       aborted = true;
+               }
+-              spin_unlock(&conn->state_lock);
++              spin_unlock_irq(&conn->state_lock);
+       }
+       return aborted;
+@@ -268,12 +268,12 @@ static int rxrpc_process_event(struct rx
+               return conn->security->respond_to_challenge(conn, skb);
+       case RXRPC_PACKET_TYPE_RESPONSE:
+-              spin_lock(&conn->state_lock);
++              spin_lock_irq(&conn->state_lock);
+               if (conn->state != RXRPC_CONN_SERVICE_CHALLENGING) {
+-                      spin_unlock(&conn->state_lock);
++                      spin_unlock_irq(&conn->state_lock);
+                       return 0;
+               }
+-              spin_unlock(&conn->state_lock);
++              spin_unlock_irq(&conn->state_lock);
+               ret = rxrpc_verify_response(conn, skb);
+               if (ret < 0)
+@@ -284,12 +284,12 @@ static int rxrpc_process_event(struct rx
+               if (ret < 0)
+                       return ret;
+-              spin_lock(&conn->state_lock);
++              spin_lock_irq(&conn->state_lock);
+               if (conn->state == RXRPC_CONN_SERVICE_CHALLENGING) {
+                       conn->state = RXRPC_CONN_SERVICE;
+                       secured = true;
+               }
+-              spin_unlock(&conn->state_lock);
++              spin_unlock_irq(&conn->state_lock);
+               if (secured) {
+                       /* Offload call state flipping to the I/O thread.  As
+--- a/net/rxrpc/conn_object.c
++++ b/net/rxrpc/conn_object.c
+@@ -31,13 +31,13 @@ void rxrpc_poke_conn(struct rxrpc_connec
+       if (WARN_ON_ONCE(!local))
+               return;
+-      spin_lock_bh(&local->lock);
++      spin_lock_irq(&local->lock);
+       busy = !list_empty(&conn->attend_link);
+       if (!busy) {
+               rxrpc_get_connection(conn, why);
+               list_add_tail(&conn->attend_link, &local->conn_attend_q);
+       }
+-      spin_unlock_bh(&local->lock);
++      spin_unlock_irq(&local->lock);
+       rxrpc_wake_up_io_thread(local);
+ }
+@@ -197,9 +197,9 @@ void rxrpc_disconnect_call(struct rxrpc_
+       call->peer->cong_ssthresh = call->cong_ssthresh;
+       if (!hlist_unhashed(&call->error_link)) {
+-              spin_lock(&call->peer->lock);
++              spin_lock_irq(&call->peer->lock);
+               hlist_del_init(&call->error_link);
+-              spin_unlock(&call->peer->lock);
++              spin_unlock_irq(&call->peer->lock);
+       }
+       if (rxrpc_is_client_call(call)) {
+--- a/net/rxrpc/input.c
++++ b/net/rxrpc/input.c
+@@ -365,7 +365,7 @@ static void rxrpc_input_queue_data(struc
+       struct rxrpc_skb_priv *sp = rxrpc_skb(skb);
+       bool last = sp->hdr.flags & RXRPC_LAST_PACKET;
+-      __skb_queue_tail(&call->recvmsg_queue, skb);
++      skb_queue_tail(&call->recvmsg_queue, skb);
+       rxrpc_input_update_ack_window(call, window, wtop);
+       trace_rxrpc_receive(call, last ? why + 1 : why, sp->hdr.serial, sp->hdr.seq);
+       if (last)
+@@ -442,7 +442,6 @@ static void rxrpc_input_data_one(struct
+               rxrpc_get_skb(skb, rxrpc_skb_get_to_recvmsg);
+-              spin_lock(&call->recvmsg_queue.lock);
+               rxrpc_input_queue_data(call, skb, window, wtop, rxrpc_receive_queue);
+               *_notify = true;
+@@ -464,8 +463,6 @@ static void rxrpc_input_data_one(struct
+                                              rxrpc_receive_queue_oos);
+               }
+-              spin_unlock(&call->recvmsg_queue.lock);
+-
+               call->ackr_sack_base = sack;
+       } else {
+               unsigned int slot;
+--- a/net/rxrpc/io_thread.c
++++ b/net/rxrpc/io_thread.c
+@@ -475,9 +475,9 @@ int rxrpc_io_thread(void *data)
+               }
+               /* Deal with connections that want immediate attention. */
+-              spin_lock_bh(&local->lock);
++              spin_lock_irq(&local->lock);
+               list_splice_tail_init(&local->conn_attend_q, &conn_attend_q);
+-              spin_unlock_bh(&local->lock);
++              spin_unlock_irq(&local->lock);
+               while ((conn = list_first_entry_or_null(&conn_attend_q,
+                                                       struct rxrpc_connection,
+@@ -494,9 +494,9 @@ int rxrpc_io_thread(void *data)
+                       rxrpc_discard_expired_client_conns(local);
+               /* Deal with calls that want immediate attention. */
+-              spin_lock_bh(&local->lock);
++              spin_lock_irq(&local->lock);
+               list_splice_tail_init(&local->call_attend_q, &call_attend_q);
+-              spin_unlock_bh(&local->lock);
++              spin_unlock_irq(&local->lock);
+               while ((call = list_first_entry_or_null(&call_attend_q,
+                                                       struct rxrpc_call,
+--- a/net/rxrpc/peer_event.c
++++ b/net/rxrpc/peer_event.c
+@@ -205,23 +205,23 @@ static void rxrpc_distribute_error(struc
+       struct rxrpc_call *call;
+       HLIST_HEAD(error_targets);
+-      spin_lock(&peer->lock);
++      spin_lock_irq(&peer->lock);
+       hlist_move_list(&peer->error_targets, &error_targets);
+       while (!hlist_empty(&error_targets)) {
+               call = hlist_entry(error_targets.first,
+                                  struct rxrpc_call, error_link);
+               hlist_del_init(&call->error_link);
+-              spin_unlock(&peer->lock);
++              spin_unlock_irq(&peer->lock);
+               rxrpc_see_call(call, rxrpc_call_see_distribute_error);
+               rxrpc_set_call_completion(call, compl, 0, -err);
+               rxrpc_input_call_event(call);
+-              spin_lock(&peer->lock);
++              spin_lock_irq(&peer->lock);
+       }
+-      spin_unlock(&peer->lock);
++      spin_unlock_irq(&peer->lock);
+ }
+ /*
+--- a/net/rxrpc/peer_object.c
++++ b/net/rxrpc/peer_object.c
+@@ -304,6 +304,7 @@ static void rxrpc_free_peer(struct rxrpc
+  * Set up a new incoming peer.  There shouldn't be any other matching peers
+  * since we've already done a search in the list from the non-reentrant context
+  * (the data_ready handler) that is the only place we can add new peers.
++ * Called with interrupts disabled.
+  */
+ void rxrpc_new_incoming_peer(struct rxrpc_local *local, struct rxrpc_peer *peer)
+ {
+--- a/net/rxrpc/recvmsg.c
++++ b/net/rxrpc/recvmsg.c
+@@ -36,16 +36,16 @@ void rxrpc_notify_socket(struct rxrpc_ca
+       sk = &rx->sk;
+       if (rx && sk->sk_state < RXRPC_CLOSE) {
+               if (call->notify_rx) {
+-                      spin_lock(&call->notify_lock);
++                      spin_lock_irq(&call->notify_lock);
+                       call->notify_rx(sk, call, call->user_call_ID);
+-                      spin_unlock(&call->notify_lock);
++                      spin_unlock_irq(&call->notify_lock);
+               } else {
+-                      spin_lock(&rx->recvmsg_lock);
++                      spin_lock_irq(&rx->recvmsg_lock);
+                       if (list_empty(&call->recvmsg_link)) {
+                               rxrpc_get_call(call, rxrpc_call_get_notify_socket);
+                               list_add_tail(&call->recvmsg_link, &rx->recvmsg_q);
+                       }
+-                      spin_unlock(&rx->recvmsg_lock);
++                      spin_unlock_irq(&rx->recvmsg_lock);
+                       if (!sock_flag(sk, SOCK_DEAD)) {
+                               _debug("call %ps", sk->sk_data_ready);
+@@ -379,14 +379,14 @@ try_again:
+        * We also want to weed out calls that got requeued whilst we were
+        * shovelling data out.
+        */
+-      spin_lock(&rx->recvmsg_lock);
++      spin_lock_irq(&rx->recvmsg_lock);
+       l = rx->recvmsg_q.next;
+       call = list_entry(l, struct rxrpc_call, recvmsg_link);
+       if (!rxrpc_call_is_complete(call) &&
+           skb_queue_empty(&call->recvmsg_queue)) {
+               list_del_init(&call->recvmsg_link);
+-              spin_unlock(&rx->recvmsg_lock);
++              spin_unlock_irq(&rx->recvmsg_lock);
+               release_sock(&rx->sk);
+               trace_rxrpc_recvmsg(call->debug_id, rxrpc_recvmsg_unqueue, 0);
+               rxrpc_put_call(call, rxrpc_call_put_recvmsg);
+@@ -407,7 +407,7 @@ try_again:
+               list_del_init(&call->recvmsg_link);
+       else
+               rxrpc_get_call(call, rxrpc_call_get_recvmsg);
+-      spin_unlock(&rx->recvmsg_lock);
++      spin_unlock_irq(&rx->recvmsg_lock);
+       call_debug_id = call->debug_id;
+       trace_rxrpc_recvmsg(call_debug_id, rxrpc_recvmsg_dequeue, 0);
+@@ -502,17 +502,17 @@ error_unlock_call:
+ error_requeue_call:
+       if (!(flags & MSG_PEEK)) {
+-              spin_lock(&rx->recvmsg_lock);
++              spin_lock_irq(&rx->recvmsg_lock);
+               if (list_empty(&call->recvmsg_link)) {
+                       list_add(&call->recvmsg_link, &rx->recvmsg_q);
+                       rxrpc_see_call(call, rxrpc_call_see_recvmsg_requeue);
+-                      spin_unlock(&rx->recvmsg_lock);
++                      spin_unlock_irq(&rx->recvmsg_lock);
+               } else if (list_is_first(&call->recvmsg_link, &rx->recvmsg_q)) {
+-                      spin_unlock(&rx->recvmsg_lock);
++                      spin_unlock_irq(&rx->recvmsg_lock);
+                       rxrpc_put_call(call, rxrpc_call_see_recvmsg_requeue_first);
+               } else {
+                       list_move(&call->recvmsg_link, &rx->recvmsg_q);
+-                      spin_unlock(&rx->recvmsg_lock);
++                      spin_unlock_irq(&rx->recvmsg_lock);
+                       rxrpc_put_call(call, rxrpc_call_see_recvmsg_requeue_move);
+               }
+               trace_rxrpc_recvmsg(call_debug_id, rxrpc_recvmsg_requeue, 0);
+--- a/net/rxrpc/security.c
++++ b/net/rxrpc/security.c
+@@ -114,10 +114,10 @@ found:
+       if (conn->state == RXRPC_CONN_CLIENT_UNSECURED) {
+               ret = conn->security->init_connection_security(conn, token);
+               if (ret == 0) {
+-                      spin_lock(&conn->state_lock);
++                      spin_lock_irq(&conn->state_lock);
+                       if (conn->state == RXRPC_CONN_CLIENT_UNSECURED)
+                               conn->state = RXRPC_CONN_CLIENT;
+-                      spin_unlock(&conn->state_lock);
++                      spin_unlock_irq(&conn->state_lock);
+               }
+       }
+       mutex_unlock(&conn->security_lock);
index ba8d2498faefb29397101c9a6feab9bb14f77181..475426b4c716d6bcdd675ea49f8a11a806287f6f 100644 (file)
@@ -462,3 +462,123 @@ gve-fix-rx-queue-stall-on-alloc-failure.patch
 rust-allow-suspicious_runtime_symbol_definitions-lint-for-rust-1.98.patch
 mm-damon-core-validate-ranges-in-damon_set_regions.patch
 mm-damon-core-disallow-overlapping-input-ranges-for-damon_set_regions.patch
+iommufd-reject-invalid-read-count-in-iommufd_fault_fops_read.patch
+iommufd-break-the-loop-on-failure-in-iommufd_fault_fops_read.patch
+iommufd-avoid-partial-fault-group-delivery-in-iommufd_fault_fops_read.patch
+fpga-dfl-afu-validate-dma-mapping-length-in-afu_dma_map_region.patch
+i2c-davinci-unregister-cpufreq-notifier-on-probe-failure.patch
+vfs-audit-introduce-kern_path_parent-for-audit.patch
+audit-widen-ino-fields-to-u64.patch
+audit-use-unsigned-int-instead-of-unsigned.patch
+audit-fix-recursive-locking-deadlock-in-audit_dupe_exe.patch
+i2c-i801-fix-hardware-state-machine-corruption-in-error-path.patch
+alsa-hda-conexant-remove-mic-bias-threshold-override.patch
+alsa-hda-fix-cached-processing-coefficient-verbs.patch
+rxrpc-pull-out-certain-app-callback-funcs-into-an-ops-table.patch
+rxrpc-serialize-kernel-accept-preallocation-with-socket-teardown.patch
+xfs-factor-out-xfs_attr3_leaf_init.patch
+xfs-don-t-replace-the-wrong-part-of-the-cow-fork.patch
+fbcon-rename-struct-fbcon_ops-to-struct-fbcon_par.patch
+fbcon-use-correct-type-for-vc_resize-return-value.patch
+rxrpc-fix-cpu-time-starvation-in-i-o-thread.patch
+rxrpc-don-t-need-barrier-for-tx_bottom-and-acks_hard_ack.patch
+rxrpc-use-irq-disabling-spinlocks-between-app-and-i-o-thread.patch
+rxrpc-fix-notification-vs-call-release-vs-recvmsg.patch
+rxrpc-fix-socket-notification-race.patch
+tipc-restrict-socket-queue-dumps-in-enqueue-tracepoints.patch
+vduse-use-fixed-4kb-bounce-pages-for-non-4kb-page-size.patch
+vduse-remove-unused-vaddr-parameter-of-vduse_domain_free_coherent.patch
+vduse-take-out-allocations-from-vduse_dev_alloc_coherent.patch
+vduse-avoid-leaking-information-to-userspace.patch
+octeontx2-annotate-mmio-regions-as-__iomem.patch
+octeontx2-vf-clear-stale-mailbox-irq-state-before-request_irq.patch
+octeontx2-pf-clear-stale-mailbox-irq-state-before-request_irq.patch
+arm64-dts-qcom-correct-rbr-opp-entry.patch
+arm64-dts-qcom-hamoa-fix-opp-tables-for-all-displayport-controllers.patch
+asoc-mediatek-mt8192-afe-pcm-simplify-probe-with-local-dev-variable.patch
+asoc-mediatek-mt8192-check-runtime-resume-during-probe.patch
+asoc-mediatek-mt8183-afe-pcm-shorten-memif_data-table-using-macros.patch
+asoc-mediatek-mt8183-afe-pcm-support-32-bit-dma-addresses.patch
+asoc-mediatek-mt8183-afe-pcm-use-local-dev-pointer-in-driver-callbacks.patch
+asoc-mediatek-mt8183-check-runtime-resume-during-probe.patch
+netfilter-nf_conntrack_sip-remove-net-variable-shadowing.patch
+netfilter-nf_conntrack_sip-validate-skb_dst-before-accessing-it.patch
+netfilter-bitwise-rename-some-boolean-operation-functions.patch
+netfilter-nf_tables-remove-unused-nft_reduce_is_readonly.patch
+netfilter-nf_tables-remove-register-tracking-infrastructure.patch
+netfilter-nft_fib-reject-fib-expression-on-the-netdev-egress-hook.patch
+gpu-move-drm-buddy-allocator-one-level-up-part-two.patch
+gpu-buddy-bail-out-of-try_harder-when-alignment-cannot-be-honoured.patch
+nfsd-pass-nfsd_file-to-nfsd_iter_read.patch
+sunrpc-allocate-a-separate-bvec-array-for-socket-sends.patch
+sunrpc-add-helpers-to-convert-xdr_buf-byte-ranges-to-scatterlists.patch
+sunrpc-return-an-error-from-xdr_buf_to_bvec-on-overflow.patch
+remoteproc-xlnx-check-remote-core-state.patch
+mm-hugetlb-fix-hugetlb-cgroup-rsvd-charge-uncharge-mismatch.patch
+mm-sparse-vmemmap-fix-vmemmap-accounting-underflow.patch
+landlock-prepare-to-use-credential-instead-of-domain-for-fowner.patch
+landlock-fix-landlock_scope_signal-bypass-on-the-sigio-path.patch
+mm-mm_init-fix-pageblock-migratetype-for-zone_device-compound-pages.patch
+mtd-maps-vmu-flash-fix-fault-in-unaligned-fixup.patch
+mm-prepare-to-move-subsection_map_init-to-mm-sparse-vmemmap.c.patch
+mm-sparse-vmemmap-fix-dax-vmemmap-accounting-with-optimization.patch
+thunderbolt-keep-xdomain-reference-during-the-lifetime-of-a-service.patch
+thunderbolt-remove-service-debugfs-entries-during-unregister.patch
+thunderbolt-remove-xdomain-from-the-bus-without-holding-tb-lock.patch
+thunderbolt-prevent-xdomain-delayed-work-use-after-free-on-disconnect.patch
+dma-dw-edma-fix-build-warning-in-dw_edma_pcie_probe.patch
+dmaengine-dw-edma-fix-confusing-cleanup.h-syntax.patch
+dmaengine-dw-edma-pcie-reject-devices-without-driver-data.patch
+i2c-imx-separate-atomic-dma-and-non-dma-use-case.patch
+i2c-imx-fix-locked-bus-on-smbus-block-read-of-0-atomic.patch
+xfrm-use-nested-bh-locking-for-nat_keepalive_sk_ipv.patch
+xfrm-nat_keepalive-avoid-double-free-on-send-error.patch
+tcp-defer-md5sig_info-kfree-past-rcu-grace-period-in-tcp_connect.patch
+tcp-decrement-tcp_md5_needed-static-branch.patch
+nvmet-introduce-nvmet_req_transfer_len.patch
+nvmet-auth-reject-short-auth_receive-buffers.patch
+ovl-use-linked-upper-dentry-in-copy-up-tmpfile.patch
+bpf-refactor-acquire-release-_reference_state.patch
+bpf-refactor-check_ctx_access.patch
+bpf-reset-register-bounds-before-narrowing-retval-range-in-check_mem_access.patch
+block-add-helper-add_disk_final.patch
+block-remove-redundant-gd_need_part_scan-in-add_disk_final.patch
+dm-integrity-fix-leaking-uninitialized-kernel-memory.patch
+cleanup-add-a-scoped-version-of-class.patch
+cleanup-fix-scoped_class.patch
+cred-add-kernel_cred-helper.patch
+cred-add-scoped_with_kernel_creds.patch
+dm-avoid-leaking-the-caller-s-thread-keyring-via-the-table-device-file.patch
+wifi-libertas_tf-fix-use-after-free-in-lbtf_free_adapter.patch
+mmc-vub300-fix-use-after-free-on-disconnect.patch
+mmc-vub300-rename-probe-error-labels.patch
+mmc-vub300-fix-use-after-free-on-probe-failure.patch
+locking-rt-fix-the-incorrect-rcu-protection-in-rt_spin_unlock.patch
+net-mana-validate-the-packet-length-reported-by-the-nic.patch
+net-ip6_tunnel-require-cap_net_admin-in-the-device-netns-for-changelink.patch
+gve-fix-header-buffer-corruption-with-header-split-and-hw-gro.patch
+gpio-mt7621-avoid-corruption-of-shared-interrupt-trigger-state.patch
+octeontx2-af-cn10k-restrict-vf-lmtline-sharing-to-its-own-pf.patch
+bootconfig-move-xbc_snprint_cmdline-to-lib-bootconfig.c.patch
+bootconfig-fix-null-pointer-arithmetic-in-xbc_snprint_cmdline.patch
+ipmi-fix-refcount-leak-in-i_ipmi_request.patch
+net-mlx5-hws-rearrange-to-prevent-forward-declaration.patch
+net-mlx5-hws-fix-matcher-leak-on-resize-target-setup-failure.patch
+octeontx2-pf-fix-sqb-pointer-leak-on-init-failure.patch
+ata-libata-core-reject-an-invalid-concurrent-positioning-ranges-count.patch
+fs-resctrl-fix-double-add-of-pseudo-locked-region-s-rmid-to-free-list.patch
+net-macb-drop-in-flight-tx-skbs-on-close.patch
+net-ipa-fix-smem-state-handle-leaks-in-smp2p-init.patch
+bluetooth-add-pa_link-to-distinguish-big-sync-and-pa-sync-connections.patch
+bluetooth-hci_core-fix-not-accounting-for-bis-cis-pa-links-separately.patch
+afs-improve-server-refcount-active-count-tracing.patch
+afs-make-afs_lookup_cell-take-a-trace-note.patch
+afs-drop-the-net-parameter-from-afs_unuse_cell.patch
+rxrpc-allow-the-app-to-store-private-data-on-peer-structs.patch
+afs-use-the-per-peer-app-data-provided-by-rxrpc.patch
+afs-fix-afs_server-ref-accounting.patch
+afs-simplify-cell-record-handling.patch
+afs-fix-dynamic-lookup-to-fail-on-cell-lookup-failure.patch
+afs-fix-lack-of-locking-around-modifications-of-net-cells_dyn_ino.patch
+usb-gadget-use-str_enable_disable-like-helpers.patch
+usb-gadget-fsl-udc-fix-dev_printk-device.patch
diff --git a/queue-6.12/sunrpc-add-helpers-to-convert-xdr_buf-byte-ranges-to-scatterlists.patch b/queue-6.12/sunrpc-add-helpers-to-convert-xdr_buf-byte-ranges-to-scatterlists.patch
new file mode 100644 (file)
index 0000000..c3149fd
--- /dev/null
@@ -0,0 +1,279 @@
+From stable+bounces-288527-greg=kroah.com@vger.kernel.org Thu Jul 23 22:00:59 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 15:59:01 -0400
+Subject: SUNRPC: Add helpers to convert xdr_buf byte ranges to scatterlists
+To: stable@vger.kernel.org
+Cc: Chuck Lever <chuck.lever@oracle.com>, Jeff Layton <jlayton@kernel.org>, Anna Schumaker <anna.schumaker@hammerspace.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723195904.3685200-3-sashal@kernel.org>
+
+From: Chuck Lever <chuck.lever@oracle.com>
+
+[ Upstream commit e9be933959b581effd426f93b86654f5fbf0c574 ]
+
+The crypto/krb5 library accepts data in scatterlist form, but
+the GSS-API layer presents RPC payloads as struct xdr_buf.
+Bridge that gap with a pair of helper functions:
+
+  xdr_buf_to_sg()        - populate a caller-supplied scatterlist
+                           array from a byte range
+  xdr_buf_to_sg_alloc()  - populate a caller-supplied inline
+                           scatterlist, chaining to a heap-
+                           allocated overflow for large payloads
+
+The inline array (typically stack-allocated at eight entries)
+covers the common case of small RPCs with no heap allocation
+on the encrypt/decrypt path. Only buffers spanning many pages
+incur a kmalloc for the chained extension.
+
+The segment-walking logic follows the same head, page array,
+tail traversal as xdr_process_buf(), but populates a
+scatterlist directly rather than invoking a per-segment
+callback. sg_next() traversal makes the walker safe for
+chained scatterlists. Once subsequent patches reroute all
+per-message crypto operations through crypto/krb5,
+xdr_process_buf() loses its last callers and is removed.
+
+Assisted-by: Claude:claude-opus-4-6
+Reviewed-by: Jeff Layton <jlayton@kernel.org>
+Acked-by: Anna Schumaker <anna.schumaker@hammerspace.com>
+Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
+Stable-dep-of: 18c1cc698861 ("SUNRPC: Return an error from xdr_buf_to_bvec() on overflow")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/sunrpc/xdr.h |   15 +++
+ net/sunrpc/xdr.c           |  199 +++++++++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 214 insertions(+)
+
+--- a/include/linux/sunrpc/xdr.h
++++ b/include/linux/sunrpc/xdr.h
+@@ -141,6 +141,21 @@ int       xdr_alloc_bvec(struct xdr_buf *buf,
+ void  xdr_free_bvec(struct xdr_buf *buf);
+ unsigned int xdr_buf_to_bvec(struct bio_vec *bvec, unsigned int bvec_size,
+                            const struct xdr_buf *xdr);
++int xdr_buf_to_sg(const struct xdr_buf *buf, unsigned int offset,
++                unsigned int len, struct scatterlist *sg, unsigned int nsg);
++int xdr_buf_to_sg_alloc(const struct xdr_buf *buf, unsigned int offset,
++                      unsigned int len, struct scatterlist *sg_head,
++                      unsigned int sg_head_nents,
++                      struct scatterlist **sg_overflow, gfp_t gfp);
++
++/*
++ * Inline scatterlist entries for xdr_buf_to_sg_alloc().  Sized to cover the
++ * head kvec, tail kvec, and a few page fragments without any heap allocation.
++ */
++enum {
++      XDR_BUF_TO_SG_NENTS     = 8,
++};
++
+ static inline __be32 *xdr_encode_array(__be32 *p, const void *s, unsigned int len)
+ {
+--- a/net/sunrpc/xdr.c
++++ b/net/sunrpc/xdr.c
+@@ -219,6 +219,205 @@ bvec_overflow:
+ }
+ /**
++ * xdr_buf_to_sg - Populate a scatterlist from an xdr_buf range
++ * @buf: xdr_buf to map
++ * @offset: starting byte offset within @buf
++ * @len: number of bytes to cover
++ * @sg: scatterlist array initialized with sg_init_table()
++ * @nsg: number of entries available in @sg
++ *
++ * @sg is traversed with sg_next(), so callers may pass a list
++ * assembled with sg_chain().
++ *
++ * Return: on success, the number of scatterlist entries used; the
++ * last used entry is marked with sg_mark_end().  On failure, a
++ * negative errno.
++ */
++int xdr_buf_to_sg(const struct xdr_buf *buf, unsigned int offset,
++                unsigned int len, struct scatterlist *sg, unsigned int nsg)
++{
++      unsigned int page_len, thislen, page_offset;
++      struct scatterlist *cur = sg, *prev = NULL;
++      int nents = 0;
++      int i;
++
++      if (len == 0)
++              return 0;
++
++      if (offset >= buf->head[0].iov_len) {
++              offset -= buf->head[0].iov_len;
++      } else {
++              thislen = min_t(unsigned int,
++                              buf->head[0].iov_len - offset, len);
++              if (nents >= nsg)
++                      return -ENOSPC;
++              sg_set_buf(cur, buf->head[0].iov_base + offset,
++                         thislen);
++              prev = cur;
++              cur = sg_next(cur);
++              nents++;
++              len -= thislen;
++              offset = 0;
++      }
++      if (len == 0)
++              goto done;
++
++      if (offset >= buf->page_len) {
++              offset -= buf->page_len;
++      } else {
++              page_len = min(buf->page_len - offset, len);
++              len -= page_len;
++              page_offset = (offset + buf->page_base) & (PAGE_SIZE - 1);
++              i = (offset + buf->page_base) >> PAGE_SHIFT;
++              thislen = PAGE_SIZE - page_offset;
++              do {
++                      if (thislen > page_len)
++                              thislen = page_len;
++                      if (nents >= nsg)
++                              return -ENOSPC;
++                      sg_set_page(cur, buf->pages[i],
++                                  thislen, page_offset);
++                      prev = cur;
++                      cur = sg_next(cur);
++                      nents++;
++                      page_len -= thislen;
++                      i++;
++                      page_offset = 0;
++                      thislen = PAGE_SIZE;
++              } while (page_len != 0);
++              offset = 0;
++      }
++      if (len == 0)
++              goto done;
++
++      if (offset < buf->tail[0].iov_len) {
++              thislen = min_t(unsigned int,
++                              buf->tail[0].iov_len - offset, len);
++              if (nents >= nsg)
++                      return -ENOSPC;
++              sg_set_buf(cur, buf->tail[0].iov_base + offset,
++                         thislen);
++              prev = cur;
++              nents++;
++              len -= thislen;
++      }
++      if (len != 0)
++              return -EINVAL;
++
++done:
++      if (prev)
++              sg_mark_end(prev);
++      return nents;
++}
++EXPORT_SYMBOL_GPL(xdr_buf_to_sg);
++
++/*
++ * Count the scatterlist entries needed to cover [offset, offset + len)
++ * within @buf.  Mirrors the walk in xdr_buf_to_sg() so the caller can
++ * size an allocation that matches the requested sub-range rather than
++ * the full xdr_buf.
++ */
++static unsigned int xdr_buf_sg_nents(const struct xdr_buf *buf,
++                                   unsigned int offset, unsigned int len)
++{
++      unsigned int nsg = 0, thislen, page_offset;
++
++      if (len == 0)
++              return 0;
++
++      if (offset < buf->head[0].iov_len) {
++              thislen = min_t(unsigned int,
++                              buf->head[0].iov_len - offset, len);
++              nsg++;
++              len -= thislen;
++              offset = 0;
++      } else {
++              offset -= buf->head[0].iov_len;
++      }
++      if (len == 0)
++              return nsg;
++
++      if (offset < buf->page_len) {
++              thislen = min(buf->page_len - offset, len);
++              page_offset = (offset + buf->page_base) & (PAGE_SIZE - 1);
++              nsg += DIV_ROUND_UP(page_offset + thislen, PAGE_SIZE);
++              len -= thislen;
++              offset = 0;
++      } else {
++              offset -= buf->page_len;
++      }
++      if (len == 0)
++              return nsg;
++
++      if (offset < buf->tail[0].iov_len)
++              nsg++;
++      return nsg;
++}
++
++/**
++ * xdr_buf_to_sg_alloc - Populate a scatterlist for an xdr_buf range
++ * @buf: xdr_buf to map
++ * @offset: starting byte offset within @buf
++ * @len: number of bytes to cover
++ * @sg_head: caller-provided scatterlist array (typically stack-allocated)
++ * @sg_head_nents: number of entries in @sg_head
++ * @sg_overflow: OUT: chained extension, or NULL when @sg_head sufficed
++ * @gfp: memory allocation flags for overflow
++ *
++ * Populates @sg_head directly when the xdr_buf fits.  When more
++ * entries are needed, an overflow scatterlist is allocated and
++ * chained from @sg_head so that the result is traversable with
++ * sg_next().
++ *
++ * Return: on success, the number of populated scatterlist entries
++ * (counting only data entries, not chain entries).  @sg_head is
++ * the head of the resulting list.  Caller must kfree @sg_overflow
++ * when done.  On failure, a negative errno.
++ */
++int xdr_buf_to_sg_alloc(const struct xdr_buf *buf, unsigned int offset,
++                      unsigned int len, struct scatterlist *sg_head,
++                      unsigned int sg_head_nents,
++                      struct scatterlist **sg_overflow, gfp_t gfp)
++{
++      unsigned int nsg;
++      int ret;
++
++      *sg_overflow = NULL;
++      if (len == 0)
++              return 0;
++
++      nsg = xdr_buf_sg_nents(buf, offset, len);
++      if (nsg == 0)
++              return -EINVAL;
++
++      if (nsg <= sg_head_nents) {
++              sg_init_table(sg_head, nsg);
++      } else {
++              /* +1 replaces the slot sg_chain() consumes as the link. */
++              unsigned int overflow_nents = nsg - sg_head_nents + 1;
++              struct scatterlist *overflow;
++
++              overflow = kmalloc_array(overflow_nents, sizeof(*overflow),
++                                       gfp);
++              if (!overflow)
++                      return -ENOMEM;
++
++              sg_init_table(sg_head, sg_head_nents);
++              sg_init_table(overflow, overflow_nents);
++              sg_chain(sg_head, sg_head_nents, overflow);
++              *sg_overflow = overflow;
++      }
++
++      ret = xdr_buf_to_sg(buf, offset, len, sg_head, nsg);
++      if (ret < 0) {
++              kfree(*sg_overflow);
++              *sg_overflow = NULL;
++      }
++      return ret;
++}
++EXPORT_SYMBOL_GPL(xdr_buf_to_sg_alloc);
++
++/**
+  * xdr_inline_pages - Prepare receive buffer for a large reply
+  * @xdr: xdr_buf into which reply will be placed
+  * @offset: expected offset where data payload will start, in bytes
diff --git a/queue-6.12/sunrpc-allocate-a-separate-bvec-array-for-socket-sends.patch b/queue-6.12/sunrpc-allocate-a-separate-bvec-array-for-socket-sends.patch
new file mode 100644 (file)
index 0000000..e03444d
--- /dev/null
@@ -0,0 +1,140 @@
+From stable+bounces-288528-greg=kroah.com@vger.kernel.org Thu Jul 23 22:01:01 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 15:59:00 -0400
+Subject: sunrpc: allocate a separate bvec array for socket sends
+To: stable@vger.kernel.org
+Cc: Jeff Layton <jlayton@kernel.org>, NeilBrown <neil@brown.name>, Chuck Lever <chuck.lever@oracle.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723195904.3685200-2-sashal@kernel.org>
+
+From: Jeff Layton <jlayton@kernel.org>
+
+[ Upstream commit 6b3b697d65d46a0f640216a3f6c72856c159c567 ]
+
+svc_tcp_sendmsg() calls xdr_buf_to_bvec() with the second slot of
+rq_bvec as the start, but doesn't reduce the array length by one, which
+could lead to an array overrun. Also, rq_bvec is always rq_maxpages in
+length, which can be too short in some cases, since the TCP record
+marker consumes a slot.
+
+Fix both problems by adding a separate bvec array to the svc_sock that
+is specifically for sending. For TCP, make this array one slot longer
+than rq_maxpages, to account for the record marker. For UDP, only
+allocate as large an array as we need since it's limited to 64k of
+payload.
+
+Signed-off-by: Jeff Layton <jlayton@kernel.org>
+Reviewed-by: NeilBrown <neil@brown.name>
+Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
+Stable-dep-of: 18c1cc698861 ("SUNRPC: Return an error from xdr_buf_to_bvec() on overflow")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/sunrpc/svcsock.h |    3 +++
+ net/sunrpc/svcsock.c           |   37 ++++++++++++++++++++++++++++---------
+ 2 files changed, 31 insertions(+), 9 deletions(-)
+
+--- a/include/linux/sunrpc/svcsock.h
++++ b/include/linux/sunrpc/svcsock.h
+@@ -26,6 +26,9 @@ struct svc_sock {
+       void                    (*sk_odata)(struct sock *);
+       void                    (*sk_owspace)(struct sock *);
++      /* For sends (protected by xpt_mutex) */
++      struct bio_vec          *sk_bvec;
++
+       /* private TCP part */
+       /* On-the-wire fragment header: */
+       __be32                  sk_marker;
+--- a/net/sunrpc/svcsock.c
++++ b/net/sunrpc/svcsock.c
+@@ -68,6 +68,17 @@
+ #define RPCDBG_FACILITY       RPCDBG_SVCXPRT
++/*
++ * For UDP:
++ * 1 for header page
++ * enough pages for RPCSVC_MAXPAYLOAD_UDP
++ * 1 in case payload is not aligned
++ * 1 for tail page
++ */
++enum {
++      SUNRPC_MAX_UDP_SENDPAGES = 1 + RPCSVC_MAXPAYLOAD_UDP / PAGE_SIZE + 1 + 1
++};
++
+ /* To-do: to avoid tying up an nfsd thread while waiting for a
+  * handshake request, the request could instead be deferred.
+  */
+@@ -750,15 +761,14 @@ static int svc_udp_sendto(struct svc_rqs
+       if (svc_xprt_is_dead(xprt))
+               goto out_notconn;
+-      count = xdr_buf_to_bvec(rqstp->rq_bvec,
+-                              ARRAY_SIZE(rqstp->rq_bvec), xdr);
++      count = xdr_buf_to_bvec(svsk->sk_bvec, SUNRPC_MAX_UDP_SENDPAGES, xdr);
+-      iov_iter_bvec(&msg.msg_iter, ITER_SOURCE, rqstp->rq_bvec,
++      iov_iter_bvec(&msg.msg_iter, ITER_SOURCE, svsk->sk_bvec,
+                     count, rqstp->rq_res.len);
+       err = sock_sendmsg(svsk->sk_sock, &msg);
+       if (err == -ECONNREFUSED) {
+               /* ICMP error on earlier request. */
+-              iov_iter_bvec(&msg.msg_iter, ITER_SOURCE, rqstp->rq_bvec,
++              iov_iter_bvec(&msg.msg_iter, ITER_SOURCE, svsk->sk_bvec,
+                             count, rqstp->rq_res.len);
+               err = sock_sendmsg(svsk->sk_sock, &msg);
+       }
+@@ -1248,19 +1258,19 @@ static int svc_tcp_sendmsg(struct svc_so
+       *sentp = 0;
+       /* The stream record marker is copied into a temporary page
+-       * fragment buffer so that it can be included in rq_bvec.
++       * fragment buffer so that it can be included in sk_bvec.
+        */
+       buf = page_frag_alloc(&svsk->sk_frag_cache, sizeof(marker),
+                             GFP_KERNEL);
+       if (!buf)
+               return -ENOMEM;
+       memcpy(buf, &marker, sizeof(marker));
+-      bvec_set_virt(rqstp->rq_bvec, buf, sizeof(marker));
++      bvec_set_virt(svsk->sk_bvec, buf, sizeof(marker));
+-      count = xdr_buf_to_bvec(rqstp->rq_bvec + 1,
+-                              ARRAY_SIZE(rqstp->rq_bvec) - 1, &rqstp->rq_res);
++      count = xdr_buf_to_bvec(svsk->sk_bvec + 1, RPCSVC_MAXPAGES,
++                              &rqstp->rq_res);
+-      iov_iter_bvec(&msg.msg_iter, ITER_SOURCE, rqstp->rq_bvec,
++      iov_iter_bvec(&msg.msg_iter, ITER_SOURCE, svsk->sk_bvec,
+                     1 + count, sizeof(marker) + rqstp->rq_res.len);
+       ret = sock_sendmsg(svsk->sk_sock, &msg);
+       page_frag_free(buf);
+@@ -1423,6 +1433,13 @@ static struct svc_sock *svc_setup_socket
+       if (!svsk)
+               return ERR_PTR(-ENOMEM);
++      svsk->sk_bvec = kcalloc(RPCSVC_MAXPAGES + 1, sizeof(*svsk->sk_bvec),
++                              GFP_KERNEL);
++      if (!svsk->sk_bvec) {
++              kfree(svsk);
++              return ERR_PTR(-ENOMEM);
++      }
++
+       inet = sock->sk;
+       if (pmap_register) {
+@@ -1432,6 +1449,7 @@ static struct svc_sock *svc_setup_socket
+                                    inet->sk_protocol,
+                                    ntohs(inet_sk(inet)->inet_sport));
+               if (err < 0) {
++                      kfree(svsk->sk_bvec);
+                       kfree(svsk);
+                       return ERR_PTR(err);
+               }
+@@ -1651,5 +1669,6 @@ static void svc_sock_free(struct svc_xpr
+       if (pfc->va)
+               __page_frag_cache_drain(virt_to_head_page(pfc->va),
+                                       pfc->pagecnt_bias);
++      kfree(svsk->sk_bvec);
+       kfree(svsk);
+ }
diff --git a/queue-6.12/sunrpc-return-an-error-from-xdr_buf_to_bvec-on-overflow.patch b/queue-6.12/sunrpc-return-an-error-from-xdr_buf_to_bvec-on-overflow.patch
new file mode 100644 (file)
index 0000000..9f68399
--- /dev/null
@@ -0,0 +1,142 @@
+From stable+bounces-288531-greg=kroah.com@vger.kernel.org Thu Jul 23 22:01:10 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 15:59:03 -0400
+Subject: SUNRPC: Return an error from xdr_buf_to_bvec() on overflow
+To: stable@vger.kernel.org
+Cc: Chuck Lever <chuck.lever@oracle.com>, Chris Mason <clm@meta.com>, Jeff Layton <jlayton@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723195904.3685200-5-sashal@kernel.org>
+
+From: Chuck Lever <chuck.lever@oracle.com>
+
+[ Upstream commit 18c1cc69886192e33536498289d26dba6894e3d5 ]
+
+xdr_buf_to_bvec() returns a slot count even when the caller's bvec
+budget is exhausted partway through the xdr_buf. Callers feed that
+count into iov_iter_bvec() and continue as if the conversion had
+succeeded, silently sending or writing fewer bytes than the data
+length declares. For an NFS WRITE the server reports the truncated
+transfer to the client as full success.
+
+The overflow represents an internal invariant violation: a higher
+layer reserved a bvec budget too small for the xdr_buf it then
+asked the encoder to convert. That is a server-side fault, not a
+media I/O failure and not a malformed client argument.
+
+Change xdr_buf_to_bvec() to return a signed int and have the
+overflow label return -ESERVERFAULT. Update the three callers to
+detect the negative return and fail the request: nfsd_vfs_write()
+folds the error into host_err, which nfserrno() translates to
+nfserr_serverfault for the WRITE reply; svc_udp_sendto() and
+svc_tcp_sendmsg() propagate the error out of the send path.
+
+Reported-by: Chris Mason <clm@meta.com>
+Fixes: 2eb2b9358181 ("SUNRPC: Convert svc_tcp_sendmsg to use bio_vecs directly")
+Cc: stable@vger.kernel.org
+Reviewed-by: Jeff Layton <jlayton@kernel.org>
+Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/sunrpc/xdr.h |    4 ++--
+ net/sunrpc/svcsock.c       |   14 ++++++++++++--
+ net/sunrpc/xdr.c           |   11 ++++++-----
+ 3 files changed, 20 insertions(+), 9 deletions(-)
+
+--- a/include/linux/sunrpc/xdr.h
++++ b/include/linux/sunrpc/xdr.h
+@@ -139,8 +139,8 @@ void       xdr_terminate_string(const struct x
+ size_t        xdr_buf_pagecount(const struct xdr_buf *buf);
+ int   xdr_alloc_bvec(struct xdr_buf *buf, gfp_t gfp);
+ void  xdr_free_bvec(struct xdr_buf *buf);
+-unsigned int xdr_buf_to_bvec(struct bio_vec *bvec, unsigned int bvec_size,
+-                           const struct xdr_buf *xdr);
++int xdr_buf_to_bvec(struct bio_vec *bvec, unsigned int bvec_size,
++                  const struct xdr_buf *xdr);
+ int xdr_buf_to_sg(const struct xdr_buf *buf, unsigned int offset,
+                 unsigned int len, struct scatterlist *sg, unsigned int nsg);
+ int xdr_buf_to_sg_alloc(const struct xdr_buf *buf, unsigned int offset,
+--- a/net/sunrpc/svcsock.c
++++ b/net/sunrpc/svcsock.c
+@@ -748,7 +748,7 @@ static int svc_udp_sendto(struct svc_rqs
+               .msg_flags      = MSG_SPLICE_PAGES,
+               .msg_controllen = sizeof(buffer),
+       };
+-      unsigned int count;
++      int count;
+       int err;
+       svc_udp_release_ctxt(xprt, rqstp->rq_xprt_ctxt);
+@@ -762,6 +762,10 @@ static int svc_udp_sendto(struct svc_rqs
+               goto out_notconn;
+       count = xdr_buf_to_bvec(svsk->sk_bvec, SUNRPC_MAX_UDP_SENDPAGES, xdr);
++      if (count < 0) {
++              err = count;
++              goto out_trace;
++      }
+       iov_iter_bvec(&msg.msg_iter, ITER_SOURCE, svsk->sk_bvec,
+                     count, rqstp->rq_res.len);
+@@ -773,6 +777,7 @@ static int svc_udp_sendto(struct svc_rqs
+               err = sock_sendmsg(svsk->sk_sock, &msg);
+       }
++out_trace:
+       trace_svcsock_udp_send(xprt, err);
+       mutex_unlock(&xprt->xpt_mutex);
+@@ -1251,7 +1256,7 @@ static int svc_tcp_sendmsg(struct svc_so
+       struct msghdr msg = {
+               .msg_flags      = MSG_SPLICE_PAGES,
+       };
+-      unsigned int count;
++      int count;
+       void *buf;
+       int ret;
+@@ -1269,10 +1274,15 @@ static int svc_tcp_sendmsg(struct svc_so
+       count = xdr_buf_to_bvec(svsk->sk_bvec + 1, RPCSVC_MAXPAGES,
+                               &rqstp->rq_res);
++      if (count < 0) {
++              ret = count;
++              goto out;
++      }
+       iov_iter_bvec(&msg.msg_iter, ITER_SOURCE, svsk->sk_bvec,
+                     1 + count, sizeof(marker) + rqstp->rq_res.len);
+       ret = sock_sendmsg(svsk->sk_sock, &msg);
++out:
+       page_frag_free(buf);
+       if (ret < 0)
+               return ret;
+--- a/net/sunrpc/xdr.c
++++ b/net/sunrpc/xdr.c
+@@ -167,13 +167,14 @@ xdr_free_bvec(struct xdr_buf *buf)
+ /**
+  * xdr_buf_to_bvec - Copy components of an xdr_buf into a bio_vec array
+  * @bvec: bio_vec array to populate
+- * @bvec_size: element count of @bio_vec
++ * @bvec_size: element count of @bvec
+  * @xdr: xdr_buf to be copied
+  *
+- * Returns the number of entries consumed in @bvec.
++ * Returns the number of entries consumed in @bvec on success, or
++ * -ESERVERFAULT when @xdr does not fit within @bvec_size entries.
+  */
+-unsigned int xdr_buf_to_bvec(struct bio_vec *bvec, unsigned int bvec_size,
+-                           const struct xdr_buf *xdr)
++int xdr_buf_to_bvec(struct bio_vec *bvec, unsigned int bvec_size,
++                  const struct xdr_buf *xdr)
+ {
+       const struct kvec *head = xdr->head;
+       const struct kvec *tail = xdr->tail;
+@@ -215,7 +216,7 @@ unsigned int xdr_buf_to_bvec(struct bio_
+ bvec_overflow:
+       pr_warn_once("%s: bio_vec array overflow\n", __func__);
+-      return count;
++      return -ESERVERFAULT;
+ }
+ /**
diff --git a/queue-6.12/tcp-decrement-tcp_md5_needed-static-branch.patch b/queue-6.12/tcp-decrement-tcp_md5_needed-static-branch.patch
new file mode 100644 (file)
index 0000000..1b76656
--- /dev/null
@@ -0,0 +1,44 @@
+From stable+bounces-289482-greg=kroah.com@vger.kernel.org Mon Jul 27 14:24:16 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 08:19:56 -0400
+Subject: tcp: Decrement tcp_md5_needed static branch
+To: stable@vger.kernel.org
+Cc: Dmitry Safonov <0x7f454c46@gmail.com>, Qihang <q.h.hack.winter@gmail.com>, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727121956.3778569-2-sashal@kernel.org>
+
+From: Dmitry Safonov <0x7f454c46@gmail.com>
+
+[ Upstream commit 6f6e860e370c9e4e919b92118a25e9e1f82e9180 ]
+
+In case of early freeing an unwanted TCP-MD5 key on TCP-AO connect(),
+md5sig_info is freed right away (and set to NULL). Later, at
+the moment of socket destruction, the static branch counter
+is not getting decremented.
+
+Add a missing decrement for TCP-MD5 static branch.
+
+Reported-by: Qihang <q.h.hack.winter@gmail.com>
+Fixes: 0aadc73995d0 ("net/tcp: Prevent TCP-MD5 with TCP-AO being set")
+Cc: stable@vger.kernel.org
+Signed-off-by: Dmitry Safonov <0x7f454c46@gmail.com>
+Link: https://patch.msgid.link/20260625-tcp-md5-connect-v3-3-1fd313d6c1e0@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/ipv4/tcp_output.c |    4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/net/ipv4/tcp_output.c
++++ b/net/ipv4/tcp_output.c
+@@ -4124,8 +4124,8 @@ int tcp_connect(struct sock *sk)
+                       tcp_clear_md5_list(sk);
+                       md5sig = rcu_replace_pointer(tp->md5sig_info, NULL,
+                                                    lockdep_sock_is_held(sk));
+-                      if (md5sig)
+-                              kfree_rcu(md5sig, rcu);
++                      kfree_rcu(md5sig, rcu);
++                      static_branch_slow_dec_deferred(&tcp_md5_needed);
+               }
+       }
+ #endif
diff --git a/queue-6.12/tcp-defer-md5sig_info-kfree-past-rcu-grace-period-in-tcp_connect.patch b/queue-6.12/tcp-defer-md5sig_info-kfree-past-rcu-grace-period-in-tcp_connect.patch
new file mode 100644 (file)
index 0000000..186d2eb
--- /dev/null
@@ -0,0 +1,113 @@
+From stable+bounces-289481-greg=kroah.com@vger.kernel.org Mon Jul 27 14:24:16 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 08:19:55 -0400
+Subject: tcp: defer md5sig_info kfree past RCU grace period in tcp_connect
+To: stable@vger.kernel.org
+Cc: Michael Bommarito <michael.bommarito@gmail.com>, Eric Dumazet <edumazet@google.com>, Dmitry Safonov <dima@arista.com>, Qihang <q.h.hack.winter@gmail.com>, Dmitry Safonov <0x7f454c46@gmail.com>, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727121956.3778569-1-sashal@kernel.org>
+
+From: Michael Bommarito <michael.bommarito@gmail.com>
+
+[ Upstream commit b74cd55038905d5e74c1de109ab78a30b2ea0e1f ]
+
+The md5+ao reconciliation in tcp_connect() (net/ipv4/tcp_output.c)
+has two symmetric branches:
+
+       if (needs_md5) {
+               tcp_ao_destroy_sock(sk, false);
+       } else if (needs_ao) {
+               tcp_clear_md5_list(sk);
+               kfree(rcu_replace_pointer(tp->md5sig_info, NULL, ...));
+       }
+
+Both branches free a per-socket auth-info object while the socket is
+in TCP_SYN_SENT and is already on the inet ehash (inserted by
+inet_hash_connect() in tcp_v4_connect()). Both branches are reachable
+by softirq RX-path readers that load the corresponding info pointer
+via implicit RCU before bh_lock_sock_nested() is taken.
+
+The needs_md5 branch is fixed in the prior patch by re-introducing
+the call_rcu() free in tcp_ao_destroy_sock(): the equivalent per-key
+loop runs inside tcp_ao_info_free_rcu(), the RCU callback, so by the
+time it frees each tcp_ao_key all softirq readers that captured the
+container have already completed rcu_read_unlock().
+
+The needs_ao branch is not symmetric in the same way. The container
+free can be deferred via kfree_rcu(md5sig, rcu) -- struct
+tcp_md5sig_info already has the required rcu member
+(include/net/tcp.h:1999-2002), and the rest of the tree already does
+this in the tcp_md5sig_info_add() rollback paths
+(net/ipv4/tcp_ipv4.c:1410, 1436). But the per-key teardown is done
+by tcp_clear_md5_list() in process context BEFORE the container's
+RCU grace period: it walks &md5sig->head and frees each
+tcp_md5sig_key with bare hlist_del + kfree. A concurrent softirq
+reader in __tcp_md5_do_lookup() / __tcp_md5_do_lookup_exact()
+(tcp_ipv4.c:1253, 1298) walks the same list via
+hlist_for_each_entry_rcu() and races with that bare kfree on the
+keys themselves -- a per-key slab use-after-free of the same class
+as the TCP-AO bug, on the same race window.
+
+Fix this in two halves:
+
+  1. Convert the bare kfree() in tcp_connect() to kfree_rcu() so the
+     md5sig_info container joins the rest of the md5sig lifecycle.
+     The local-variable lift is mechanical and required because
+     kfree_rcu() is a macro that expects an lvalue.
+
+  2. Make tcp_clear_md5_list() RCU-safe by replacing hlist_del +
+     kfree(key) with hlist_del_rcu + kfree_rcu(key, rcu). struct
+     tcp_md5sig_key already carries the rcu member
+     (include/net/tcp.h:1995) and tcp_md5_do_del()
+     (net/ipv4/tcp_ipv4.c:1456) already uses kfree_rcu, so this
+     restores the lifecycle invariant the rest of the file follows
+     rather than introducing a one-off.
+
+The other caller of tcp_clear_md5_list() is tcp_md5_destruct_sock()
+(net/ipv4/tcp.c:412), which runs from the sock destructor when the
+socket is already unhashed and unreachable; the extra grace period
+there is unnecessary but harmless. Making the helper unconditionally
+RCU-safe is the cleaner contract.
+
+The needs_ao branch is not reachable by the userns reproducer used
+to demonstrate the AO-side splat (the repro installs both keys but
+ends up in the needs_md5 branch because the connect peer matches
+the MD5 key, not the AO key); however the symmetric race exists
+and a maintainer touching this code should not have to think about
+which branch escapes RCU and which one does not.
+
+Fixes: 51e547e8c89c ("tcp: Free TCP-AO/TCP-MD5 info/keys without RCU")
+Cc: stable@vger.kernel.org # v6.18+
+Suggested-by: Eric Dumazet <edumazet@google.com>
+Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
+Reviewed-by: Dmitry Safonov <dima@arista.com>
+Reviewed-by: Eric Dumazet <edumazet@google.com>
+[also credits to Qihang, who found that this races with tcp-diag]
+Reported-by: Qihang <q.h.hack.winter@gmail.com>
+Signed-off-by: Dmitry Safonov <0x7f454c46@gmail.com>
+Link: https://patch.msgid.link/20260625-tcp-md5-connect-v3-2-1fd313d6c1e0@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Stable-dep-of: 6f6e860e370c ("tcp: Decrement tcp_md5_needed static branch")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/ipv4/tcp_output.c |    8 ++++++--
+ 1 file changed, 6 insertions(+), 2 deletions(-)
+
+--- a/net/ipv4/tcp_output.c
++++ b/net/ipv4/tcp_output.c
+@@ -4119,9 +4119,13 @@ int tcp_connect(struct sock *sk)
+               if (needs_md5) {
+                       tcp_ao_destroy_sock(sk, false);
+               } else if (needs_ao) {
++                      struct tcp_md5sig_info *md5sig;
++
+                       tcp_clear_md5_list(sk);
+-                      kfree(rcu_replace_pointer(tp->md5sig_info, NULL,
+-                                                lockdep_sock_is_held(sk)));
++                      md5sig = rcu_replace_pointer(tp->md5sig_info, NULL,
++                                                   lockdep_sock_is_held(sk));
++                      if (md5sig)
++                              kfree_rcu(md5sig, rcu);
+               }
+       }
+ #endif
diff --git a/queue-6.12/thunderbolt-keep-xdomain-reference-during-the-lifetime-of-a-service.patch b/queue-6.12/thunderbolt-keep-xdomain-reference-during-the-lifetime-of-a-service.patch
new file mode 100644 (file)
index 0000000..2a19366
--- /dev/null
@@ -0,0 +1,42 @@
+From stable+bounces-289260-greg=kroah.com@vger.kernel.org Sun Jul 26 14:36:19 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 26 Jul 2026 08:36:10 -0400
+Subject: thunderbolt: Keep XDomain reference during the lifetime of a service
+To: stable@vger.kernel.org
+Cc: Mika Westerberg <mika.westerberg@linux.intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260726123613.37376-1-sashal@kernel.org>
+
+From: Mika Westerberg <mika.westerberg@linux.intel.com>
+
+[ Upstream commit 8b4060998637f06975fceee9b73845d8672d411e ]
+
+This is needed because we release the service ID in tb_service_release()
+and the ID array is owned by the parent XDomain.
+
+Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
+Stable-dep-of: 2c5d2d3c3f70 ("thunderbolt: Prevent XDomain delayed work use-after-free on disconnect")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/thunderbolt/xdomain.c |    3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+--- a/drivers/thunderbolt/xdomain.c
++++ b/drivers/thunderbolt/xdomain.c
+@@ -1008,6 +1008,7 @@ static void tb_service_release(struct de
+       ida_free(&xd->service_ids, svc->id);
+       kfree(svc->key);
+       kfree(svc);
++      tb_xdomain_put(xd);
+ }
+ const struct device_type tb_service_type = {
+@@ -1116,7 +1117,7 @@ static void enumerate_services(struct tb
+               svc->id = id;
+               svc->dev.bus = &tb_bus_type;
+               svc->dev.type = &tb_service_type;
+-              svc->dev.parent = &xd->dev;
++              svc->dev.parent = get_device(&xd->dev);
+               dev_set_name(&svc->dev, "%s.%d", dev_name(&xd->dev), svc->id);
+               tb_service_debugfs_init(svc);
diff --git a/queue-6.12/thunderbolt-prevent-xdomain-delayed-work-use-after-free-on-disconnect.patch b/queue-6.12/thunderbolt-prevent-xdomain-delayed-work-use-after-free-on-disconnect.patch
new file mode 100644 (file)
index 0000000..b23599b
--- /dev/null
@@ -0,0 +1,90 @@
+From stable+bounces-289263-greg=kroah.com@vger.kernel.org Sun Jul 26 14:36:20 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 26 Jul 2026 08:36:13 -0400
+Subject: thunderbolt: Prevent XDomain delayed work use-after-free on disconnect
+To: stable@vger.kernel.org
+Cc: Michael Bommarito <michael.bommarito@gmail.com>, Mika Westerberg <mika.westerberg@linux.intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260726123613.37376-4-sashal@kernel.org>
+
+From: Michael Bommarito <michael.bommarito@gmail.com>
+
+[ Upstream commit 2c5d2d3c3f70cde2565d7b279b544893a2035842 ]
+
+tb_xdp_handle_request() runs on system_wq and queues
+xd->state_work via queue_delayed_work() in three request handlers:
+PROPERTIES_CHANGED_REQUEST, UUID_REQUEST (via start_handshake),
+and LINK_STATE_CHANGE_REQUEST.  Similarly, update_xdomain() queues
+xd->properties_changed_work when local properties change.
+
+Concurrently, tb_xdomain_remove() calls stop_handshake() which does
+cancel_delayed_work_sync() on both delayed works.  Later,
+tb_xdomain_unregister() calls device_unregister() which eventually
+frees the xdomain.  Since commit 559c1e1e0134 ("thunderbolt: Run
+tb_xdp_handle_request() in system workqueue") moved the request
+handler off tb->wq, the handler and the remove path are no longer
+serialized.  If queue_delayed_work() executes after
+cancel_delayed_work_sync() but before the xdomain is freed, the
+delayed work fires on a freed object.
+
+Add xd->removing that tb_xdomain_remove() sets under xd->lock
+before calling stop_handshake().  Each external queue site holds
+the same lock and checks removing before calling
+queue_delayed_work().  This provides the mutual exclusion needed:
+either the queue site acquires the lock first and queues work that
+the subsequent cancel will see, or the remove path acquires the
+lock first and the queue site observes removing == true and skips
+the queue.
+
+Fixes: 559c1e1e0134 ("thunderbolt: Run tb_xdp_handle_request() in system workqueue")
+Cc: stable@vger.kernel.org
+Assisted-by: Claude:claude-opus-4-7
+Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
+Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/thunderbolt/xdomain.c |   26 +++++++++++++-------------
+ 1 file changed, 13 insertions(+), 13 deletions(-)
+
+--- a/drivers/thunderbolt/xdomain.c
++++ b/drivers/thunderbolt/xdomain.c
+@@ -905,6 +905,19 @@ void tb_unregister_service_driver(struct
+ }
+ EXPORT_SYMBOL_GPL(tb_unregister_service_driver);
++static int update_xdomain(struct device *dev, void *data)
++{
++      struct tb_xdomain *xd;
++
++      xd = tb_to_xdomain(dev);
++      if (xd) {
++              queue_delayed_work(xd->tb->wq, &xd->properties_changed_work,
++                                 msecs_to_jiffies(50));
++      }
++
++      return 0;
++}
++
+ static ssize_t key_show(struct device *dev, struct device_attribute *attr,
+                       char *buf)
+ {
+@@ -2480,19 +2493,6 @@ bool tb_xdomain_handle_request(struct tb
+       return ret > 0;
+ }
+-static int update_xdomain(struct device *dev, void *data)
+-{
+-      struct tb_xdomain *xd;
+-
+-      xd = tb_to_xdomain(dev);
+-      if (xd) {
+-              queue_delayed_work(xd->tb->wq, &xd->properties_changed_work,
+-                                 msecs_to_jiffies(50));
+-      }
+-
+-      return 0;
+-}
+-
+ static void update_all_xdomains(void)
+ {
+       bus_for_each_dev(&tb_bus_type, NULL, NULL, update_xdomain);
diff --git a/queue-6.12/thunderbolt-remove-service-debugfs-entries-during-unregister.patch b/queue-6.12/thunderbolt-remove-service-debugfs-entries-during-unregister.patch
new file mode 100644 (file)
index 0000000..0bce7b9
--- /dev/null
@@ -0,0 +1,76 @@
+From stable+bounces-289261-greg=kroah.com@vger.kernel.org Sun Jul 26 14:36:19 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 26 Jul 2026 08:36:11 -0400
+Subject: thunderbolt: Remove service debugfs entries during unregister
+To: stable@vger.kernel.org
+Cc: Mika Westerberg <mika.westerberg@linux.intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260726123613.37376-2-sashal@kernel.org>
+
+From: Mika Westerberg <mika.westerberg@linux.intel.com>
+
+[ Upstream commit 4d5fc3f4068568dfcb8cbe2852b4adc56394aa26 ]
+
+We add them as part of the register path so to keep it symmetric remove
+them as part of the unregister path. This also removes them even if the
+service itself is not yet released (but is unregistered), thus allowing
+new register with the same service name to happen.
+
+Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
+Stable-dep-of: 2c5d2d3c3f70 ("thunderbolt: Prevent XDomain delayed work use-after-free on disconnect")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/thunderbolt/xdomain.c |   14 +++++++++++---
+ 1 file changed, 11 insertions(+), 3 deletions(-)
+
+--- a/drivers/thunderbolt/xdomain.c
++++ b/drivers/thunderbolt/xdomain.c
+@@ -1004,7 +1004,6 @@ static void tb_service_release(struct de
+       struct tb_service *svc = container_of(dev, struct tb_service, dev);
+       struct tb_xdomain *xd = tb_service_parent(svc);
+-      tb_service_debugfs_remove(svc);
+       ida_free(&xd->service_ids, svc->id);
+       kfree(svc->key);
+       kfree(svc);
+@@ -1019,6 +1018,14 @@ const struct device_type tb_service_type
+ };
+ EXPORT_SYMBOL_GPL(tb_service_type);
++static void __unregister_service(struct device *dev)
++{
++      struct tb_service *svc = tb_to_service(dev);
++
++      tb_service_debugfs_remove(svc);
++      device_unregister(&svc->dev);
++}
++
+ static int remove_missing_service(struct device *dev, void *data)
+ {
+       struct tb_xdomain *xd = data;
+@@ -1030,7 +1037,7 @@ static int remove_missing_service(struct
+       if (!tb_property_find(xd->remote_properties, svc->key,
+                             TB_PROPERTY_TYPE_DIRECTORY))
+-              device_unregister(dev);
++              __unregister_service(dev);
+       return 0;
+ }
+@@ -1123,6 +1130,7 @@ static void enumerate_services(struct tb
+               tb_service_debugfs_init(svc);
+               if (device_register(&svc->dev)) {
++                      tb_service_debugfs_remove(svc);
+                       put_device(&svc->dev);
+                       break;
+               }
+@@ -2053,7 +2061,7 @@ void tb_xdomain_add(struct tb_xdomain *x
+ static int unregister_service(struct device *dev, void *data)
+ {
+-      device_unregister(dev);
++      __unregister_service(dev);
+       return 0;
+ }
diff --git a/queue-6.12/thunderbolt-remove-xdomain-from-the-bus-without-holding-tb-lock.patch b/queue-6.12/thunderbolt-remove-xdomain-from-the-bus-without-holding-tb-lock.patch
new file mode 100644 (file)
index 0000000..ead1e29
--- /dev/null
@@ -0,0 +1,337 @@
+From stable+bounces-289262-greg=kroah.com@vger.kernel.org Sun Jul 26 14:36:20 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 26 Jul 2026 08:36:12 -0400
+Subject: thunderbolt: Remove XDomain from the bus without holding tb->lock
+To: stable@vger.kernel.org
+Cc: Mika Westerberg <mika.westerberg@linux.intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260726123613.37376-3-sashal@kernel.org>
+
+From: Mika Westerberg <mika.westerberg@linux.intel.com>
+
+[ Upstream commit a8937f35cf39c39c64325aa84d0463d866850857 ]
+
+Currently we call device_unregister() for services and the XDomain
+itself with tb->lock held. This prevents the service drivers from
+calling any functions that may take it. For this reason separate
+removing the XDomain from the topology data structures (where we need
+the lock) from unregistering the device from the bus (where remove
+callbacks of the drivers are being called).
+
+Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
+Stable-dep-of: 2c5d2d3c3f70 ("thunderbolt: Prevent XDomain delayed work use-after-free on disconnect")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/thunderbolt/debugfs.c |    2 +
+ drivers/thunderbolt/domain.c  |   30 +++++++++++++++++++++
+ drivers/thunderbolt/icm.c     |    5 +++
+ drivers/thunderbolt/switch.c  |   14 +++++++++
+ drivers/thunderbolt/tb.c      |   59 ++++++++++++++++++++----------------------
+ drivers/thunderbolt/tb.h      |    2 +
+ drivers/thunderbolt/xdomain.c |   53 +++++++++++++++++++++++--------------
+ 7 files changed, 115 insertions(+), 50 deletions(-)
+
+--- a/drivers/thunderbolt/debugfs.c
++++ b/drivers/thunderbolt/debugfs.c
+@@ -1508,6 +1508,8 @@ static void margining_port_remove(struct
+       if (!port->usb4)
+               return;
++      if (!port->usb4->margining)
++              return;
+       snprintf(dir_name, sizeof(dir_name), "port%d", port->port);
+       parent = debugfs_lookup(dir_name, port->sw->debugfs_dir);
+--- a/drivers/thunderbolt/domain.c
++++ b/drivers/thunderbolt/domain.c
+@@ -871,6 +871,36 @@ int tb_domain_disconnect_all_paths(struc
+       return bus_for_each_dev(&tb_bus_type, NULL, tb, disconnect_xdomain);
+ }
++struct unregister_context {
++      const struct tb *tb;
++      int n;
++};
++
++static int unregister_unplugged_xdomain(struct device *dev, void *data)
++{
++      struct unregister_context *ctx = data;
++      struct tb_xdomain *xd;
++
++      xd = tb_to_xdomain(dev);
++      if (xd && xd->tb == ctx->tb && xd->is_unplugged) {
++              tb_xdomain_unregister(xd);
++              ctx->n++;
++      }
++      return 0;
++}
++
++int tb_domain_unregister_unplugged_xdomains(struct tb *tb)
++{
++      struct unregister_context ctx;
++
++      ctx.tb = tb_domain_get(tb);
++      ctx.n = 0;
++      bus_for_each_dev(&tb_bus_type, NULL, &ctx, unregister_unplugged_xdomain);
++      tb_domain_put(tb);
++
++      return ctx.n;
++}
++
+ int tb_domain_init(void)
+ {
+       int ret;
+--- a/drivers/thunderbolt/icm.c
++++ b/drivers/thunderbolt/icm.c
+@@ -713,6 +713,7 @@ static void remove_xdomain(struct tb_xdo
+       sw = tb_to_switch(xd->dev.parent);
+       tb_port_at(xd->route, sw)->xdomain = NULL;
++      xd->is_unplugged = true;
+       tb_xdomain_remove(xd);
+ }
+@@ -1728,6 +1729,8 @@ static void icm_handle_notification(stru
+       kfree(n->pkg);
+       kfree(n);
++
++      tb_domain_unregister_unplugged_xdomains(tb);
+ }
+ static void icm_handle_event(struct tb *tb, enum tb_cfg_pkg_type type,
+@@ -2078,6 +2081,8 @@ static void icm_rescan_work(struct work_
+       if (tb->root_switch)
+               icm_free_unplugged_children(tb->root_switch);
+       mutex_unlock(&tb->lock);
++
++      tb_domain_unregister_unplugged_xdomains(tb);
+ }
+ static void icm_complete(struct tb *tb)
+--- a/drivers/thunderbolt/switch.c
++++ b/drivers/thunderbolt/switch.c
+@@ -3554,6 +3554,20 @@ int tb_switch_resume(struct tb_switch *s
+                               tb_port_warn(port,
+                                            "lost during suspend, disconnecting\n");
+                               tb_sw_set_unplugged(port->remote->sw);
++                      } else if (port->xdomain) {
++                              /*
++                               * If the user replaced the XDomain with
++                               * another router, this will succeed in
++                               * which case we must remove the XDomain
++                               * before adding the new router.
++                               */
++                              err = tb_cfg_get_upstream_port(sw->tb->ctl,
++                                                             port->xdomain->route);
++                              if (err > 0) {
++                                      tb_port_warn(port,
++                                                   "XDomain was disconnected\n");
++                                      port->xdomain->is_unplugged = true;
++                              }
+                       }
+               }
+       }
+--- a/drivers/thunderbolt/tb.c
++++ b/drivers/thunderbolt/tb.c
+@@ -2450,6 +2450,8 @@ put_sw:
+ out:
+       mutex_unlock(&tb->lock);
++      tb_domain_unregister_unplugged_xdomains(tb);
++
+       pm_runtime_mark_last_busy(&tb->dev);
+       pm_runtime_put_autosuspend(&tb->dev);
+@@ -3008,6 +3010,24 @@ static void tb_restore_children(struct t
+       }
+ }
++static void tb_free_unplugged_xdomains(struct tb_switch *sw)
++{
++      struct tb_port *port;
++
++      tb_switch_for_each_port(sw, port) {
++              if (tb_is_upstream_port(port))
++                      continue;
++              if (port->xdomain && port->xdomain->is_unplugged) {
++                      tb_retimer_remove_all(port);
++                      tb_xdomain_remove(port->xdomain);
++                      tb_port_unconfigure_xdomain(port);
++                      port->xdomain = NULL;
++              } else if (port->remote) {
++                      tb_free_unplugged_xdomains(port->remote->sw);
++              }
++      }
++}
++
+ static int tb_resume_noirq(struct tb *tb)
+ {
+       struct tb_cm *tcm = tb_priv(tb);
+@@ -3027,6 +3047,7 @@ static int tb_resume_noirq(struct tb *tb
+       tb_switch_resume(tb->root_switch, false);
+       tb_free_invalid_tunnels(tb);
+       tb_free_unplugged_children(tb->root_switch);
++      tb_free_unplugged_xdomains(tb->root_switch);
+       tb_restore_children(tb->root_switch);
+       /*
+@@ -3069,28 +3090,6 @@ static int tb_resume_noirq(struct tb *tb
+       return 0;
+ }
+-static int tb_free_unplugged_xdomains(struct tb_switch *sw)
+-{
+-      struct tb_port *port;
+-      int ret = 0;
+-
+-      tb_switch_for_each_port(sw, port) {
+-              if (tb_is_upstream_port(port))
+-                      continue;
+-              if (port->xdomain && port->xdomain->is_unplugged) {
+-                      tb_retimer_remove_all(port);
+-                      tb_xdomain_remove(port->xdomain);
+-                      tb_port_unconfigure_xdomain(port);
+-                      port->xdomain = NULL;
+-                      ret++;
+-              } else if (port->remote) {
+-                      ret += tb_free_unplugged_xdomains(port->remote->sw);
+-              }
+-      }
+-
+-      return ret;
+-}
+-
+ static int tb_freeze_noirq(struct tb *tb)
+ {
+       struct tb_cm *tcm = tb_priv(tb);
+@@ -3110,14 +3109,14 @@ static int tb_thaw_noirq(struct tb *tb)
+ static void tb_complete(struct tb *tb)
+ {
+       /*
+-       * Release any unplugged XDomains and if there is a case where
++       * Unregister unplugged XDomains and if there is a case where
+        * another domain is swapped in place of unplugged XDomain we
+        * need to run another rescan.
+        */
+-      mutex_lock(&tb->lock);
+-      if (tb_free_unplugged_xdomains(tb->root_switch))
+-              tb_scan_switch(tb->root_switch);
+-      mutex_unlock(&tb->lock);
++      if (tb_domain_unregister_unplugged_xdomains(tb)) {
++              scoped_guard(mutex, &tb->lock)
++                      tb_scan_switch(tb->root_switch);
++      }
+ }
+ static int tb_runtime_suspend(struct tb *tb)
+@@ -3144,11 +3143,11 @@ static void tb_remove_work(struct work_s
+       struct tb *tb = tcm_to_tb(tcm);
+       mutex_lock(&tb->lock);
+-      if (tb->root_switch) {
++      if (tb->root_switch)
+               tb_free_unplugged_children(tb->root_switch);
+-              tb_free_unplugged_xdomains(tb->root_switch);
+-      }
+       mutex_unlock(&tb->lock);
++
++      tb_free_unplugged_xdomains(tb->root_switch);
+ }
+ static int tb_runtime_resume(struct tb *tb)
+--- a/drivers/thunderbolt/tb.h
++++ b/drivers/thunderbolt/tb.h
+@@ -786,6 +786,7 @@ int tb_domain_disconnect_xdomain_paths(s
+                                      int transmit_path, int transmit_ring,
+                                      int receive_path, int receive_ring);
+ int tb_domain_disconnect_all_paths(struct tb *tb);
++int tb_domain_unregister_unplugged_xdomains(struct tb *tb);
+ static inline struct tb *tb_domain_get(struct tb *tb)
+ {
+@@ -1233,6 +1234,7 @@ struct tb_xdomain *tb_xdomain_alloc(stru
+                                   const uuid_t *remote_uuid);
+ void tb_xdomain_add(struct tb_xdomain *xd);
+ void tb_xdomain_remove(struct tb_xdomain *xd);
++void tb_xdomain_unregister(struct tb_xdomain *xd);
+ struct tb_xdomain *tb_xdomain_find_by_link_depth(struct tb *tb, u8 link,
+                                                u8 depth);
+--- a/drivers/thunderbolt/xdomain.c
++++ b/drivers/thunderbolt/xdomain.c
+@@ -2066,41 +2066,54 @@ static int unregister_service(struct dev
+ }
+ /**
+- * tb_xdomain_remove() - Remove XDomain from the bus
++ * tb_xdomain_remove() - Remove XDomain
+  * @xd: XDomain to remove
+  *
+- * This will stop all ongoing configuration work and remove the XDomain
+- * along with any services from the bus. When the last reference to @xd
+- * is released the object will be released as well.
++ * This will stop all ongoing configuration work. XDomain is not removed
++ * from the bus if it was added. That needs to be done separately by
++ * calling tb_xdomain_unregister().
++ *
++ * Called with @tb->lock held.
+  */
+ void tb_xdomain_remove(struct tb_xdomain *xd)
+ {
+       tb_xdomain_debugfs_remove(xd);
+-
+       stop_handshake(xd);
+-
+-      device_for_each_child_reverse(&xd->dev, xd, unregister_service);
+-
+       tb_xdomain_link_exit(xd);
+-      /*
+-       * Undo runtime PM here explicitly because it is possible that
+-       * the XDomain was never added to the bus and thus device_del()
+-       * is not called for it (device_del() would handle this otherwise).
+-       */
+-      pm_runtime_disable(&xd->dev);
+-      pm_runtime_put_noidle(&xd->dev);
+-      pm_runtime_set_suspended(&xd->dev);
+-
+       if (!device_is_registered(&xd->dev)) {
++              /*
++               * Undo runtime PM here explicitly because it is
++               * possible that the XDomain was never added to the bus
++               * and thus device_del() is not called for it
++               * (device_del() would handle this otherwise).
++               */
++              pm_runtime_disable(&xd->dev);
++              pm_runtime_put_noidle(&xd->dev);
++              pm_runtime_set_suspended(&xd->dev);
+               put_device(&xd->dev);
+-      } else {
+-              dev_info(&xd->dev, "host disconnected\n");
+-              device_unregister(&xd->dev);
+       }
+ }
+ /**
++ * tb_xdomain_unregister() - Unregister XDomain
++ * @xd: XDomain to unregister
++ *
++ * This will unregister the XDomain along with any services from the
++ * bus. When the last reference to @xd is released the object will be
++ * released as well.
++ */
++void tb_xdomain_unregister(struct tb_xdomain *xd)
++{
++      lockdep_assert_not_held(&xd->tb->lock);
++
++      device_for_each_child_reverse(&xd->dev, xd, unregister_service);
++
++      dev_info(&xd->dev, "host disconnected\n");
++      device_unregister(&xd->dev);
++}
++
++/**
+  * tb_xdomain_lane_bonding_enable() - Enable lane bonding on XDomain
+  * @xd: XDomain connection
+  *
diff --git a/queue-6.12/tipc-restrict-socket-queue-dumps-in-enqueue-tracepoints.patch b/queue-6.12/tipc-restrict-socket-queue-dumps-in-enqueue-tracepoints.patch
new file mode 100644 (file)
index 0000000..3a27dbc
--- /dev/null
@@ -0,0 +1,97 @@
+From stable+bounces-287970-greg=kroah.com@vger.kernel.org Wed Jul 22 18:56:34 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 12:54:29 -0400
+Subject: tipc: restrict socket queue dumps in enqueue tracepoints
+To: stable@vger.kernel.org
+Cc: Li Xiasong <lixiasong1@huawei.com>, Tung Nguyen <tung.quang.nguyen@est.tech>, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722165429.1853397-1-sashal@kernel.org>
+
+From: Li Xiasong <lixiasong1@huawei.com>
+
+[ Upstream commit acd7df8d955480a6f6e5bb809da67b1500cc3cf4 ]
+
+tipc_sk_enqueue() runs with sk->sk_lock.slock held while the socket is
+owned by user context. The spinlock protects the backlog queue in this
+path, but it does not serialize against the socket owner consuming or
+purging sk_receive_queue.
+
+KASAN reported:
+
+  CPU: 14 UID: 0 PID: 1050 Comm: tipc3 Not tainted 7.1.0-rc6+ #126 PREEMPT(lazy)
+  Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.15.0-1 04/01/2014
+  Call Trace:
+    <TASK>
+    dump_stack_lvl+0x76/0xa0 lib/dump_stack.c:123
+    print_report+0xce/0x5b0 mm/kasan/report.c:482
+    kasan_report+0xc6/0x100 mm/kasan/report.c:597
+    __asan_report_load4_noabort+0x14/0x30 mm/kasan/report_generic.c:380
+    tipc_skb_dump+0x1327/0x16f0 net/tipc/trace.c:73
+    tipc_list_dump+0x208/0x2e0 net/tipc/trace.c:187
+    tipc_sk_dump+0xaf6/0xd60 net/tipc/socket.c:3996
+    trace_event_raw_event_tipc_sk_class+0x312/0x5a0 net/tipc/trace.h:188
+    tipc_sk_rcv+0xb1d/0x1d50 net/tipc/socket.c:2497
+    tipc_node_xmit+0x1c3/0x1440 net/tipc/node.c:1689
+    __tipc_sendmsg+0x97a/0x1440 net/tipc/socket.c:1512
+    tipc_sendmsg+0x52/0x80 net/tipc/socket.c:1400
+    sock_sendmsg+0x2f6/0x3e0 net/socket.c:825
+    splice_to_socket+0x7f9/0x1010 fs/splice.c:884
+    do_splice+0xe21/0x2330 fs/splice.c:936
+    __do_splice+0x153/0x260 fs/splice.c:1431
+    __x64_sys_splice+0x150/0x230 fs/splice.c:1616
+    x64_sys_call+0xeb5/0x2790 arch/x86/entry/syscall_64.c:41
+    do_syscall_64+0xf3/0x620 arch/x86/entry/syscall_64.c:63
+    entry_SYSCALL_64_after_hwframe+0x76/0x7e arch/x86/entry/entry_64.S:130
+  RIP: 0033:0x71624e8aafe2
+  Code: 08 0f 85 71 3a ff ff 49 89 fb 48 89 f0 48 89 d7 48 89 ce 4c 89 c2 4d 89 ca 4c 8b 44 24 08 4c 8b 4c 24 10 4c 89 5c 24 08 0f 05 <c3> 66 2e 0f 1f 84 00 00 00 00 00 66 2e 0f 1f 84 00 00 00 00 00 66
+  RSP: 002b:0000716157ffed68 EFLAGS: 00000246 ORIG_RAX: 0000000000000113
+  RAX: ffffffffffffffda RBX: 0000716157fff6c0 RCX: 000071624e8aafe2
+  RDX: 000000000000005f RSI: 0000000000000000 RDI: 0000000000000066
+  RBP: 0000716157ffed90 R08: 0000000000008000 R09: 0000000000000001
+  R10: 0000000000000000 R11: 0000000000000246 R12: ffffffffffffff00
+  R13: 0000000000000021 R14: 0000000000000000 R15: 00007fff89799c40
+    </TASK>
+
+The TIPC_DUMP_ALL tracepoints in tipc_sk_enqueue() also dump
+sk_receive_queue and can therefore dereference skbs that the socket
+owner has already dequeued or freed. Restrict these dumps to
+TIPC_DUMP_SK_BKLGQ, which matches the queue protected by the held
+spinlock.
+
+Keep the change limited to the enqueue path, where the unsafe queue dump
+is reachable while the socket is owned by user context.
+
+Fixes: 01e661ebfbad ("tipc: add trace_events for tipc socket")
+Cc: stable@vger.kernel.org
+Signed-off-by: Li Xiasong <lixiasong1@huawei.com>
+Reviewed-by: Tung Nguyen <tung.quang.nguyen@est.tech>
+Link: https://patch.msgid.link/20260611135647.3666727-1-lixiasong1@huawei.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/tipc/socket.c |    6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+--- a/net/tipc/socket.c
++++ b/net/tipc/socket.c
+@@ -2456,17 +2456,17 @@ static void tipc_sk_enqueue(struct sk_bu
+                       atomic_set(dcnt, 0);
+               lim = rcvbuf_limit(sk, skb) + atomic_read(dcnt);
+               if (likely(!sk_add_backlog(sk, skb, lim))) {
+-                      trace_tipc_sk_overlimit1(sk, skb, TIPC_DUMP_ALL,
++                      trace_tipc_sk_overlimit1(sk, skb, TIPC_DUMP_SK_BKLGQ,
+                                                "bklg & rcvq >90% allocated!");
+                       continue;
+               }
+-              trace_tipc_sk_dump(sk, skb, TIPC_DUMP_ALL, "err_overload!");
++              trace_tipc_sk_dump(sk, skb, TIPC_DUMP_SK_BKLGQ, "err_overload!");
+               /* Overload => reject message back to sender */
+               onode = tipc_own_addr(sock_net(sk));
+               atomic_inc(&sk->sk_drops);
+               if (tipc_msg_reverse(onode, &skb, TIPC_ERR_OVERLOAD)) {
+-                      trace_tipc_sk_rej_msg(sk, skb, TIPC_DUMP_ALL,
++                      trace_tipc_sk_rej_msg(sk, skb, TIPC_DUMP_SK_BKLGQ,
+                                             "@sk_enqueue!");
+                       __skb_queue_tail(xmitq, skb);
+               }
diff --git a/queue-6.12/usb-gadget-fsl-udc-fix-dev_printk-device.patch b/queue-6.12/usb-gadget-fsl-udc-fix-dev_printk-device.patch
new file mode 100644 (file)
index 0000000..d113fa2
--- /dev/null
@@ -0,0 +1,422 @@
+From stable+bounces-290986-greg=kroah.com@vger.kernel.org Thu Jul 30 04:31:09 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 22:31:00 -0400
+Subject: USB: gadget: fsl-udc: fix dev_printk() device
+To: stable@vger.kernel.org
+Cc: "Johan Hovold" <johan@kernel.org>, stable <stable@kernel.org>, "Uwe Kleine-König" <u.kleine-koenig@baylibre.com>, "Greg Kroah-Hartman" <gregkh@linuxfoundation.org>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260730023101.1859045-2-sashal@kernel.org>
+
+From: Johan Hovold <johan@kernel.org>
+
+[ Upstream commit c4dd150fceab281496acb3a643ae712aacb74864 ]
+
+A change replacing custom printk() macros with dev_printk() incorrectly
+used the gadget struct device instead of the controller struct device
+(including for messages printed before the gadget device name has been
+initialised).
+
+Switch to using the controller platform device with dev_printk() so that
+the controller device and driver names are included in log messages as
+expected.
+
+Fixes: 6025f20f16c2 ("usb: gadget: fsl-udc: Replace custom log wrappers by dev_{err,warn,dbg,vdbg}")
+Cc: stable <stable@kernel.org>
+Cc: Uwe Kleine-König <u.kleine-koenig@baylibre.com>
+Signed-off-by: Johan Hovold <johan@kernel.org>
+Acked-by: Uwe Kleine-König <u.kleine-koenig@baylibre.com>
+Link: https://patch.msgid.link/20260702141536.90887-4-johan@kernel.org
+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/gadget/udc/fsl_udc_core.c |   92 +++++++++++++++-------------------
+ drivers/usb/gadget/udc/fsl_usb2_udc.h |    1 
+ 2 files changed, 43 insertions(+), 50 deletions(-)
+
+--- a/drivers/usb/gadget/udc/fsl_udc_core.c
++++ b/drivers/usb/gadget/udc/fsl_udc_core.c
+@@ -184,7 +184,7 @@ __acquires(ep->udc->lock)
+       usb_gadget_unmap_request(&ep->udc->gadget, &req->req, ep_is_in(ep));
+       if (status && (status != -ESHUTDOWN))
+-              dev_vdbg(&udc->gadget.dev, "complete %s req %p stat %d len %u/%u\n",
++              dev_vdbg(udc->dev, "complete %s req %p stat %d len %u/%u\n",
+                        ep->ep.name, &req->req, status,
+                        req->req.actual, req->req.length);
+@@ -286,7 +286,7 @@ static int dr_controller_setup(struct fs
+       timeout = jiffies + FSL_UDC_RESET_TIMEOUT;
+       while (fsl_readl(&dr_regs->usbcmd) & USB_CMD_CTRL_RESET) {
+               if (time_after(jiffies, timeout)) {
+-                      dev_err(&udc->gadget.dev, "udc reset timeout!\n");
++                      dev_err(udc->dev, "udc reset timeout!\n");
+                       return -ETIMEDOUT;
+               }
+               cpu_relax();
+@@ -309,7 +309,7 @@ static int dr_controller_setup(struct fs
+       tmp &= USB_EP_LIST_ADDRESS_MASK;
+       fsl_writel(tmp, &dr_regs->endpointlistaddr);
+-      dev_vdbg(&udc->gadget.dev,
++      dev_vdbg(udc->dev,
+                "vir[qh_base] is %p phy[qh_base] is 0x%8x reg is 0x%8x\n",
+                udc->ep_qh, (int)tmp,
+                fsl_readl(&dr_regs->endpointlistaddr));
+@@ -500,7 +500,7 @@ static void struct_ep_qh_setup(struct fs
+               tmp = max_pkt_len << EP_QUEUE_HEAD_MAX_PKT_LEN_POS;
+               break;
+       default:
+-              dev_vdbg(&udc->gadget.dev, "error ep type is %d\n", ep_type);
++              dev_vdbg(udc->dev, "error ep type is %d\n", ep_type);
+               return;
+       }
+       if (zlt)
+@@ -613,7 +613,7 @@ static int fsl_ep_enable(struct usb_ep *
+       spin_unlock_irqrestore(&udc->lock, flags);
+       retval = 0;
+-      dev_vdbg(&udc->gadget.dev, "enabled %s (ep%d%s) maxpacket %d\n",
++      dev_vdbg(udc->dev, "enabled %s (ep%d%s) maxpacket %d\n",
+                ep->ep.name, ep->ep.desc->bEndpointAddress & 0x0f,
+                (desc->bEndpointAddress & USB_DIR_IN) ? "in" : "out",
+                max);
+@@ -634,13 +634,8 @@ static int fsl_ep_disable(struct usb_ep
+       int ep_num;
+       ep = container_of(_ep, struct fsl_ep, ep);
+-      if (!_ep || !ep->ep.desc) {
+-              /*
+-               * dev_vdbg(&udc->gadget.dev, "%s not enabled\n",
+-               *       _ep ? ep->ep.name : NULL);
+-               */
++      if (!_ep || !ep->ep.desc)
+               return -EINVAL;
+-      }
+       /* disable ep on controller */
+       ep_num = ep_index(ep);
+@@ -664,7 +659,7 @@ static int fsl_ep_disable(struct usb_ep
+       ep->stopped = 1;
+       spin_unlock_irqrestore(&udc->lock, flags);
+-      dev_vdbg(&udc->gadget.dev, "disabled %s OK\n", _ep->name);
++      dev_vdbg(udc->dev, "disabled %s OK\n", _ep->name);
+       return 0;
+ }
+@@ -724,9 +719,6 @@ static void fsl_queue_td(struct fsl_ep *
+ {
+       u32 temp, bitmask, tmp_stat;
+-      /* dev_vdbg(&udc->gadget.dev, "QH addr Register 0x%8x\n", dr_regs->endpointlistaddr);
+-      dev_vdbg(&udc->gadget.dev, "ep_qh[%d] addr is 0x%8x\n", i, (u32)&(ep->udc->ep_qh[i])); */
+-
+       bitmask = ep_is_in(ep)
+               ? (1 << (ep_index(ep) + 16))
+               : (1 << (ep_index(ep)));
+@@ -813,7 +805,7 @@ static struct ep_td_struct *fsl_build_dt
+               *is_last = 0;
+       if ((*is_last) == 0)
+-              dev_vdbg(&udc_controller->gadget.dev, "multi-dtd request!\n");
++              dev_vdbg(udc_controller->dev, "multi-dtd request!\n");
+       /* Fill in the transfer size; set active bit */
+       swap_temp = ((*length << DTD_LENGTH_BIT_POS) | DTD_STATUS_ACTIVE);
+@@ -825,7 +817,7 @@ static struct ep_td_struct *fsl_build_dt
+       mb();
+-      dev_vdbg(&udc_controller->gadget.dev, "length = %d address= 0x%x\n", *length, (int)*dma);
++      dev_vdbg(udc_controller->dev, "length = %d address= 0x%x\n", *length, (int)*dma);
+       return dtd;
+ }
+@@ -876,11 +868,11 @@ fsl_ep_queue(struct usb_ep *_ep, struct
+       /* catch various bogus parameters */
+       if (!_req || !req->req.complete || !req->req.buf
+                       || !list_empty(&req->queue)) {
+-              dev_vdbg(&udc->gadget.dev, "%s, bad params\n", __func__);
++              dev_vdbg(udc->dev, "%s, bad params\n", __func__);
+               return -EINVAL;
+       }
+       if (unlikely(!ep->ep.desc)) {
+-              dev_vdbg(&udc->gadget.dev, "%s, bad ep\n", __func__);
++              dev_vdbg(udc->dev, "%s, bad ep\n", __func__);
+               return -EINVAL;
+       }
+       if (usb_endpoint_xfer_isoc(ep->ep.desc)) {
+@@ -1040,7 +1032,7 @@ static int fsl_ep_set_halt(struct usb_ep
+               udc->ep0_dir = 0;
+       }
+ out:
+-      dev_vdbg(&udc->gadget.dev, "%s %s halt stat %d\n", ep->ep.name,
++      dev_vdbg(udc->dev, "%s %s halt stat %d\n", ep->ep.name,
+                value ?  "set" : "clear", status);
+       return status;
+@@ -1109,7 +1101,7 @@ static void fsl_ep_fifo_flush(struct usb
+               /* Wait until flush complete */
+               while (fsl_readl(&dr_regs->endptflush)) {
+                       if (time_after(jiffies, timeout)) {
+-                              dev_err(&udc_controller->gadget.dev,
++                              dev_err(udc_controller->dev,
+                                       "ep flush timeout\n");
+                               return;
+                       }
+@@ -1182,7 +1174,7 @@ static int fsl_vbus_session(struct usb_g
+       udc = container_of(gadget, struct fsl_udc, gadget);
+       spin_lock_irqsave(&udc->lock, flags);
+-      dev_vdbg(&gadget->dev, "VBUS %s\n", str_on_off(is_active));
++      dev_vdbg(udc->dev, "VBUS %s\n", str_on_off(is_active));
+       udc->vbus_active = (is_active != 0);
+       if (can_pullup(udc))
+               fsl_writel((fsl_readl(&dr_regs->usbcmd) | USB_CMD_RUN_STOP),
+@@ -1548,7 +1540,7 @@ static void ep0_req_complete(struct fsl_
+               udc->ep0_state = WAIT_FOR_SETUP;
+               break;
+       case WAIT_FOR_SETUP:
+-              dev_err(&udc->gadget.dev, "Unexpected ep0 packets\n");
++              dev_err(udc->dev, "Unexpected ep0 packets\n");
+               break;
+       default:
+               ep0stall(udc);
+@@ -1617,7 +1609,7 @@ static int process_ep_req(struct fsl_udc
+               errors = hc32_to_cpu(curr_td->size_ioc_sts);
+               if (errors & DTD_ERROR_MASK) {
+                       if (errors & DTD_STATUS_HALTED) {
+-                              dev_err(&udc->gadget.dev, "dTD error %08x QH=%d\n", errors, pipe);
++                              dev_err(udc->dev, "dTD error %08x QH=%d\n", errors, pipe);
+                               /* Clear the errors and Halt condition */
+                               tmp = hc32_to_cpu(curr_qh->size_ioc_int_sts);
+                               tmp &= ~errors;
+@@ -1628,26 +1620,26 @@ static int process_ep_req(struct fsl_udc
+                               break;
+                       }
+                       if (errors & DTD_STATUS_DATA_BUFF_ERR) {
+-                              dev_vdbg(&udc->gadget.dev, "Transfer overflow\n");
++                              dev_vdbg(udc->dev, "Transfer overflow\n");
+                               status = -EPROTO;
+                               break;
+                       } else if (errors & DTD_STATUS_TRANSACTION_ERR) {
+-                              dev_vdbg(&udc->gadget.dev, "ISO error\n");
++                              dev_vdbg(udc->dev, "ISO error\n");
+                               status = -EILSEQ;
+                               break;
+                       } else
+-                              dev_err(&udc->gadget.dev,
++                              dev_err(udc->dev,
+                                       "Unknown error has occurred (0x%x)!\n",
+                                       errors);
+               } else if (hc32_to_cpu(curr_td->size_ioc_sts)
+                               & DTD_STATUS_ACTIVE) {
+-                      dev_vdbg(&udc->gadget.dev, "Request not complete\n");
++                      dev_vdbg(udc->dev, "Request not complete\n");
+                       status = REQ_UNCOMPLETE;
+                       return status;
+               } else if (remaining_length) {
+                       if (direction) {
+-                              dev_vdbg(&udc->gadget.dev,
++                              dev_vdbg(udc->dev,
+                                        "Transmit dTD remaining length not zero\n");
+                               status = -EPROTO;
+                               break;
+@@ -1655,8 +1647,7 @@ static int process_ep_req(struct fsl_udc
+                               break;
+                       }
+               } else {
+-                      dev_vdbg(&udc->gadget.dev,
+-                               "dTD transmitted successful\n");
++                      dev_vdbg(udc->dev, "dTD transmitted successful\n");
+               }
+               if (j != curr_req->dtd_count - 1)
+@@ -1699,7 +1690,7 @@ static void dtd_complete_irq(struct fsl_
+               /* If the ep is configured */
+               if (!curr_ep->ep.name) {
+-                      dev_warn(&udc->gadget.dev, "Invalid EP?\n");
++                      dev_warn(udc->dev, "Invalid EP?\n");
+                       continue;
+               }
+@@ -1708,7 +1699,7 @@ static void dtd_complete_irq(struct fsl_
+                               queue) {
+                       status = process_ep_req(udc, i, curr_req);
+-                      dev_vdbg(&udc->gadget.dev,
++                      dev_vdbg(udc->dev,
+                                "status of process_ep_req= %d, ep = %d\n",
+                                status, ep_num);
+                       if (status == REQ_UNCOMPLETE)
+@@ -1829,7 +1820,7 @@ static void reset_irq(struct fsl_udc *ud
+       while (fsl_readl(&dr_regs->endpointprime)) {
+               /* Wait until all endptprime bits cleared */
+               if (time_after(jiffies, timeout)) {
+-                      dev_err(&udc->gadget.dev, "Timeout for reset\n");
++                      dev_err(udc->dev, "Timeout for reset\n");
+                       break;
+               }
+               cpu_relax();
+@@ -1839,7 +1830,7 @@ static void reset_irq(struct fsl_udc *ud
+       fsl_writel(0xffffffff, &dr_regs->endptflush);
+       if (fsl_readl(&dr_regs->portsc1) & PORTSCX_PORT_RESET) {
+-              dev_vdbg(&udc->gadget.dev, "Bus reset\n");
++              dev_vdbg(udc->dev, "Bus reset\n");
+               /* Bus is reseting */
+               udc->bus_reset = 1;
+               /* Reset all the queues, include XD, dTD, EP queue
+@@ -1847,7 +1838,7 @@ static void reset_irq(struct fsl_udc *ud
+               reset_queues(udc, true);
+               udc->usb_state = USB_STATE_DEFAULT;
+       } else {
+-              dev_vdbg(&udc->gadget.dev, "Controller reset\n");
++              dev_vdbg(udc->dev, "Controller reset\n");
+               /* initialize usb hw reg except for regs for EP, not
+                * touch usbintr reg */
+               dr_controller_setup(udc);
+@@ -1881,7 +1872,7 @@ static irqreturn_t fsl_udc_irq(int irq,
+       /* Clear notification bits */
+       fsl_writel(irq_src, &dr_regs->usbsts);
+-      /* dev_vdbg(&udc->gadget.dev, "irq_src [0x%8x]", irq_src); */
++      /* dev_vdbg(udc->dev, "irq_src [0x%8x]", irq_src); */
+       /* Need to resume? */
+       if (udc->usb_state == USB_STATE_SUSPENDED)
+@@ -1890,7 +1881,7 @@ static irqreturn_t fsl_udc_irq(int irq,
+       /* USB Interrupt */
+       if (irq_src & USB_STS_INT) {
+-              dev_vdbg(&udc->gadget.dev, "Packet int\n");
++              dev_vdbg(udc->dev, "Packet int\n");
+               /* Setup package, we only support ep0 as control ep */
+               if (fsl_readl(&dr_regs->endptsetupstat) & EP_SETUP_STATUS_EP0) {
+                       tripwire_handler(udc, 0,
+@@ -1919,7 +1910,7 @@ static irqreturn_t fsl_udc_irq(int irq,
+       /* Reset Received */
+       if (irq_src & USB_STS_RESET) {
+-              dev_vdbg(&udc->gadget.dev, "reset int\n");
++              dev_vdbg(udc->dev, "reset int\n");
+               reset_irq(udc);
+               status = IRQ_HANDLED;
+       }
+@@ -1931,7 +1922,7 @@ static irqreturn_t fsl_udc_irq(int irq,
+       }
+       if (irq_src & (USB_STS_ERR | USB_STS_SYS_ERR)) {
+-              dev_vdbg(&udc->gadget.dev, "Error IRQ %x\n", irq_src);
++              dev_vdbg(udc->dev, "Error IRQ %x\n", irq_src);
+       }
+       spin_unlock_irqrestore(&udc->lock, flags);
+@@ -1967,7 +1958,7 @@ static int fsl_udc_start(struct usb_gadg
+                                       udc_controller->transceiver->otg,
+                                                   &udc_controller->gadget);
+                       if (retval < 0) {
+-                              dev_err(&udc_controller->gadget.dev, "can't bind to transceiver\n");
++                              dev_err(udc_controller->dev, "can't bind to transceiver\n");
+                               udc_controller->driver = NULL;
+                               return retval;
+                       }
+@@ -2252,7 +2243,7 @@ static int struct_udc_setup(struct fsl_u
+       udc->eps = kcalloc(udc->max_ep, sizeof(struct fsl_ep), GFP_KERNEL);
+       if (!udc->eps) {
+-              dev_err(&udc->gadget.dev, "kmalloc udc endpoint status failed\n");
++              dev_err(udc->dev, "kmalloc udc endpoint status failed\n");
+               goto eps_alloc_failed;
+       }
+@@ -2267,7 +2258,7 @@ static int struct_udc_setup(struct fsl_u
+       udc->ep_qh = dma_alloc_coherent(&pdev->dev, size,
+                                       &udc->ep_qh_dma, GFP_KERNEL);
+       if (!udc->ep_qh) {
+-              dev_err(&udc->gadget.dev, "malloc QHs for udc failed\n");
++              dev_err(udc->dev, "malloc QHs for udc failed\n");
+               goto ep_queue_alloc_failed;
+       }
+@@ -2278,14 +2269,14 @@ static int struct_udc_setup(struct fsl_u
+       udc->status_req = container_of(fsl_alloc_request(NULL, GFP_KERNEL),
+                       struct fsl_req, req);
+       if (!udc->status_req) {
+-              dev_err(&udc->gadget.dev, "kzalloc for udc status request failed\n");
++              dev_err(udc->dev, "kzalloc for udc status request failed\n");
+               goto udc_status_alloc_failed;
+       }
+       /* allocate a small amount of memory to get valid address */
+       udc->status_req->req.buf = kmalloc(8, GFP_KERNEL);
+       if (!udc->status_req->req.buf) {
+-              dev_err(&udc->gadget.dev, "kzalloc for udc request buffer failed\n");
++              dev_err(udc->dev, "kzalloc for udc request buffer failed\n");
+               goto udc_req_buf_alloc_failed;
+       }
+@@ -2373,6 +2364,7 @@ static int fsl_udc_probe(struct platform
+       if (udc_controller == NULL)
+               return -ENOMEM;
++      udc_controller->dev = &pdev->dev;
+       pdata = dev_get_platdata(&pdev->dev);
+       udc_controller->pdata = pdata;
+       spin_lock_init(&udc_controller->lock);
+@@ -2382,7 +2374,7 @@ static int fsl_udc_probe(struct platform
+       if (pdata->operating_mode == FSL_USB2_DR_OTG) {
+               udc_controller->transceiver = usb_get_phy(USB_PHY_TYPE_USB2);
+               if (IS_ERR_OR_NULL(udc_controller->transceiver)) {
+-                      dev_err(&udc_controller->gadget.dev, "Can't find OTG driver!\n");
++                      dev_err(&pdev->dev, "Can't find OTG driver!\n");
+                       ret = -ENODEV;
+                       goto err_kfree;
+               }
+@@ -2398,7 +2390,7 @@ static int fsl_udc_probe(struct platform
+       if (pdata->operating_mode == FSL_USB2_DR_DEVICE) {
+               if (!request_mem_region(res->start, resource_size(res),
+                                       driver_name)) {
+-                      dev_err(&udc_controller->gadget.dev, "request mem region for %s failed\n", pdev->name);
++                      dev_err(&pdev->dev, "failed to request mem region\n");
+                       ret = -EBUSY;
+                       goto err_kfree;
+               }
+@@ -2429,7 +2421,7 @@ static int fsl_udc_probe(struct platform
+       /* Read Device Controller Capability Parameters register */
+       dccparams = fsl_readl(&dr_regs->dccparams);
+       if (!(dccparams & DCCPARAMS_DC)) {
+-              dev_err(&udc_controller->gadget.dev, "This SOC doesn't support device role\n");
++              dev_err(&pdev->dev, "This SOC doesn't support device role\n");
+               ret = -ENODEV;
+               goto err_exit;
+       }
+@@ -2447,14 +2439,14 @@ static int fsl_udc_probe(struct platform
+       ret = request_irq(udc_controller->irq, fsl_udc_irq, IRQF_SHARED,
+                       driver_name, udc_controller);
+       if (ret != 0) {
+-              dev_err(&udc_controller->gadget.dev, "cannot request irq %d err %d\n",
++              dev_err(&pdev->dev, "cannot request irq %d err %d\n",
+                               udc_controller->irq, ret);
+               goto err_exit;
+       }
+       /* Initialize the udc structure including QH member and other member */
+       if (struct_udc_setup(udc_controller, pdev)) {
+-              dev_err(&udc_controller->gadget.dev, "Can't initialize udc data structure\n");
++              dev_err(&pdev->dev, "Can't initialize udc data structure\n");
+               ret = -ENOMEM;
+               goto err_free_irq;
+       }
+--- a/drivers/usb/gadget/udc/fsl_usb2_udc.h
++++ b/drivers/usb/gadget/udc/fsl_usb2_udc.h
+@@ -470,6 +470,7 @@ struct fsl_ep {
+ #define EP_DIR_OUT    0
+ struct fsl_udc {
++      struct device *dev;
+       struct usb_gadget gadget;
+       struct usb_gadget_driver *driver;
+       struct fsl_usb2_platform_data *pdata;
diff --git a/queue-6.12/usb-gadget-use-str_enable_disable-like-helpers.patch b/queue-6.12/usb-gadget-use-str_enable_disable-like-helpers.patch
new file mode 100644 (file)
index 0000000..63d9c22
--- /dev/null
@@ -0,0 +1,262 @@
+From sashal@kernel.org Thu Jul 30 04:31:04 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 22:30:59 -0400
+Subject: USB: gadget: Use str_enable_disable-like helpers
+To: stable@vger.kernel.org
+Cc: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>, Greg Kroah-Hartman <gregkh@linuxfoundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260730023101.1859045-1-sashal@kernel.org>
+
+From: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+
+[ Upstream commit 5b6dc50e9ed870fffbf2ae6de77b30fb0d15eab8 ]
+
+Replace ternary (condition ? "enable" : "disable") syntax with helpers
+from string_choices.h because:
+1. Simple function call with one argument is easier to read.  Ternary
+   operator has three arguments and with wrapping might lead to quite
+   long code.
+2. Is slightly shorter thus also easier to read.
+3. It brings uniformity in the text - same string.
+4. Allows deduping by the linker, which results in a smaller binary
+   file.
+
+Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Link: https://lore.kernel.org/r/20250114-str-enable-disable-usb-v1-5-c8405df47c19@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Stable-dep-of: c4dd150fceab ("USB: gadget: fsl-udc: fix dev_printk() device")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/usb/gadget/function/f_ecm.c         |    4 ++--
+ drivers/usb/gadget/function/f_ncm.c         |    3 ++-
+ drivers/usb/gadget/function/u_serial.c      |    3 ++-
+ drivers/usb/gadget/legacy/inode.c           |    3 ++-
+ drivers/usb/gadget/udc/aspeed-vhub/hub.c    |    3 ++-
+ drivers/usb/gadget/udc/at91_udc.c           |    3 ++-
+ drivers/usb/gadget/udc/cdns2/cdns2-gadget.c |   13 +++++++------
+ drivers/usb/gadget/udc/dummy_hcd.c          |    3 ++-
+ drivers/usb/gadget/udc/fsl_udc_core.c       |    3 ++-
+ drivers/usb/gadget/udc/omap_udc.c           |    3 ++-
+ drivers/usb/gadget/udc/pxa27x_udc.c         |    3 ++-
+ 11 files changed, 27 insertions(+), 17 deletions(-)
+
+--- a/drivers/usb/gadget/function/f_ecm.c
++++ b/drivers/usb/gadget/function/f_ecm.c
+@@ -14,6 +14,7 @@
+ #include <linux/module.h>
+ #include <linux/device.h>
+ #include <linux/etherdevice.h>
++#include <linux/string_choices.h>
+ #include <linux/usb/gadget.h>
+@@ -390,8 +391,7 @@ static void ecm_do_notify(struct f_ecm *
+               event->wLength = 0;
+               req->length = sizeof *event;
+-              DBG(cdev, "notify connect %s\n",
+-                              ecm->is_open ? "true" : "false");
++              DBG(cdev, "notify connect %s\n", str_true_false(ecm->is_open));
+               ecm->notify_state = ECM_NOTIFY_SPEED;
+               break;
+--- a/drivers/usb/gadget/function/f_ncm.c
++++ b/drivers/usb/gadget/function/f_ncm.c
+@@ -18,6 +18,7 @@
+ #include <linux/device.h>
+ #include <linux/etherdevice.h>
+ #include <linux/crc32.h>
++#include <linux/string_choices.h>
+ #include <linux/usb/cdc.h>
+ #include <linux/usb/gadget.h>
+@@ -560,7 +561,7 @@ static void ncm_do_notify(struct f_ncm *
+               req->length = sizeof *event;
+               DBG(cdev, "notify connect %s\n",
+-                              ncm->is_open ? "true" : "false");
++                              str_true_false(ncm->is_open));
+               ncm->notify_state = NCM_NOTIFY_NONE;
+               break;
+--- a/drivers/usb/gadget/function/u_serial.c
++++ b/drivers/usb/gadget/function/u_serial.c
+@@ -21,6 +21,7 @@
+ #include <linux/tty.h>
+ #include <linux/tty_flip.h>
+ #include <linux/slab.h>
++#include <linux/string_choices.h>
+ #include <linux/export.h>
+ #include <linux/module.h>
+ #include <linux/console.h>
+@@ -1541,7 +1542,7 @@ static int __init userial_init(void)
+       pr_debug("%s: registered %d ttyGS* device%s\n", __func__,
+                       MAX_U_SERIAL_PORTS,
+-                      (MAX_U_SERIAL_PORTS == 1) ? "" : "s");
++                      str_plural(MAX_U_SERIAL_PORTS));
+       return status;
+ fail:
+--- a/drivers/usb/gadget/legacy/inode.c
++++ b/drivers/usb/gadget/legacy/inode.c
+@@ -20,6 +20,7 @@
+ #include <linux/uaccess.h>
+ #include <linux/sched.h>
+ #include <linux/slab.h>
++#include <linux/string_choices.h>
+ #include <linux/poll.h>
+ #include <linux/kthread.h>
+ #include <linux/aio.h>
+@@ -1182,7 +1183,7 @@ ep0_fasync (int f, struct file *fd, int
+ {
+       struct dev_data         *dev = fd->private_data;
+       // caller must F_SETOWN before signal delivery happens
+-      VDEBUG (dev, "%s %s\n", __func__, on ? "on" : "off");
++      VDEBUG(dev, "%s %s\n", __func__, str_on_off(on));
+       return fasync_helper (f, fd, on, &dev->fasync);
+ }
+--- a/drivers/usb/gadget/udc/aspeed-vhub/hub.c
++++ b/drivers/usb/gadget/udc/aspeed-vhub/hub.c
+@@ -22,6 +22,7 @@
+ #include <linux/usb/gadget.h>
+ #include <linux/of.h>
+ #include <linux/regmap.h>
++#include <linux/string_choices.h>
+ #include <linux/dma-mapping.h>
+ #include <linux/bcd.h>
+ #include <linux/version.h>
+@@ -219,7 +220,7 @@ static int ast_vhub_hub_dev_feature(stru
+       if (wValue == USB_DEVICE_REMOTE_WAKEUP) {
+               ep->vhub->wakeup_en = is_set;
+               EPDBG(ep, "Hub remote wakeup %s\n",
+-                    is_set ? "enabled" : "disabled");
++                    str_enabled_disabled(is_set));
+               return std_req_complete;
+       }
+--- a/drivers/usb/gadget/udc/at91_udc.c
++++ b/drivers/usb/gadget/udc/at91_udc.c
+@@ -16,6 +16,7 @@
+ #include <linux/delay.h>
+ #include <linux/ioport.h>
+ #include <linux/slab.h>
++#include <linux/string_choices.h>
+ #include <linux/errno.h>
+ #include <linux/list.h>
+ #include <linux/interrupt.h>
+@@ -131,7 +132,7 @@ static void proc_ep_show(struct seq_file
+       seq_printf(s, "csr %08x rxbytes=%d %s %s %s" EIGHTBITS "\n",
+               csr,
+               (csr & 0x07ff0000) >> 16,
+-              (csr & (1 << 15)) ? "enabled" : "disabled",
++              str_enabled_disabled(csr & (1 << 15)),
+               (csr & (1 << 11)) ? "DATA1" : "DATA0",
+               types[(csr & 0x700) >> 8],
+--- a/drivers/usb/gadget/udc/cdns2/cdns2-gadget.c
++++ b/drivers/usb/gadget/udc/cdns2/cdns2-gadget.c
+@@ -29,6 +29,7 @@
+ #include <linux/pm_runtime.h>
+ #include <linux/interrupt.h>
+ #include <linux/property.h>
++#include <linux/string_choices.h>
+ #include <linux/dmapool.h>
+ #include <linux/iopoll.h>
+@@ -2233,12 +2234,12 @@ static int cdns2_init_eps(struct cdns2_d
+               dev_dbg(pdev->dev, "Init %s, SupType: CTRL: %s, INT: %s, "
+                       "BULK: %s, ISOC %s, SupDir IN: %s, OUT: %s\n",
+                       pep->name,
+-                      (pep->endpoint.caps.type_control) ? "yes" : "no",
+-                      (pep->endpoint.caps.type_int) ? "yes" : "no",
+-                      (pep->endpoint.caps.type_bulk) ? "yes" : "no",
+-                      (pep->endpoint.caps.type_iso) ? "yes" : "no",
+-                      (pep->endpoint.caps.dir_in) ? "yes" : "no",
+-                      (pep->endpoint.caps.dir_out) ? "yes" : "no");
++                      str_yes_no(pep->endpoint.caps.type_control),
++                      str_yes_no(pep->endpoint.caps.type_int),
++                      str_yes_no(pep->endpoint.caps.type_bulk),
++                      str_yes_no(pep->endpoint.caps.type_iso),
++                      str_yes_no(pep->endpoint.caps.dir_in),
++                      str_yes_no(pep->endpoint.caps.dir_out));
+               INIT_LIST_HEAD(&pep->pending_list);
+               INIT_LIST_HEAD(&pep->deferred_list);
+--- a/drivers/usb/gadget/udc/dummy_hcd.c
++++ b/drivers/usb/gadget/udc/dummy_hcd.c
+@@ -28,6 +28,7 @@
+ #include <linux/delay.h>
+ #include <linux/ioport.h>
+ #include <linux/slab.h>
++#include <linux/string_choices.h>
+ #include <linux/errno.h>
+ #include <linux/init.h>
+ #include <linux/hrtimer.h>
+@@ -649,7 +650,7 @@ static int dummy_enable(struct usb_ep *_
+               desc->bEndpointAddress & 0x0f,
+               (desc->bEndpointAddress & USB_DIR_IN) ? "in" : "out",
+               usb_ep_type_string(usb_endpoint_type(desc)),
+-              max, ep->stream_en ? "enabled" : "disabled");
++              max, str_enabled_disabled(ep->stream_en));
+       /* at this point real hardware should be NAKing transfers
+        * to that endpoint, until a buffer is queued to it.
+--- a/drivers/usb/gadget/udc/fsl_udc_core.c
++++ b/drivers/usb/gadget/udc/fsl_udc_core.c
+@@ -22,6 +22,7 @@
+ #include <linux/errno.h>
+ #include <linux/err.h>
+ #include <linux/slab.h>
++#include <linux/string_choices.h>
+ #include <linux/init.h>
+ #include <linux/list.h>
+ #include <linux/interrupt.h>
+@@ -1181,7 +1182,7 @@ static int fsl_vbus_session(struct usb_g
+       udc = container_of(gadget, struct fsl_udc, gadget);
+       spin_lock_irqsave(&udc->lock, flags);
+-      dev_vdbg(&gadget->dev, "VBUS %s\n", is_active ? "on" : "off");
++      dev_vdbg(&gadget->dev, "VBUS %s\n", str_on_off(is_active));
+       udc->vbus_active = (is_active != 0);
+       if (can_pullup(udc))
+               fsl_writel((fsl_readl(&dr_regs->usbcmd) | USB_CMD_RUN_STOP),
+--- a/drivers/usb/gadget/udc/omap_udc.c
++++ b/drivers/usb/gadget/udc/omap_udc.c
+@@ -18,6 +18,7 @@
+ #include <linux/errno.h>
+ #include <linux/delay.h>
+ #include <linux/slab.h>
++#include <linux/string_choices.h>
+ #include <linux/timer.h>
+ #include <linux/list.h>
+ #include <linux/interrupt.h>
+@@ -1248,7 +1249,7 @@ static int omap_vbus_session(struct usb_
+       udc = container_of(gadget, struct omap_udc, gadget);
+       spin_lock_irqsave(&udc->lock, flags);
+-      VDBG("VBUS %s\n", is_active ? "on" : "off");
++      VDBG("VBUS %s\n", str_on_off(is_active));
+       udc->vbus_active = (is_active != 0);
+       if (cpu_is_omap15xx()) {
+               /* "software" detect, ignored if !VBUS_MODE_1510 */
+--- a/drivers/usb/gadget/udc/pxa27x_udc.c
++++ b/drivers/usb/gadget/udc/pxa27x_udc.c
+@@ -20,6 +20,7 @@
+ #include <linux/gpio.h>
+ #include <linux/gpio/consumer.h>
+ #include <linux/slab.h>
++#include <linux/string_choices.h>
+ #include <linux/prefetch.h>
+ #include <linux/byteorder/generic.h>
+ #include <linux/platform_data/pxa2xx_udc.h>
+@@ -1083,7 +1084,7 @@ static int pxa_ep_queue(struct usb_ep *_
+       is_first_req = list_empty(&ep->queue);
+       ep_dbg(ep, "queue req %p(first=%s), len %d buf %p\n",
+-                      _req, is_first_req ? "yes" : "no",
++                      _req, str_yes_no(is_first_req),
+                       _req->length, _req->buf);
+       if (!ep->enabled) {
diff --git a/queue-6.12/vduse-avoid-leaking-information-to-userspace.patch b/queue-6.12/vduse-avoid-leaking-information-to-userspace.patch
new file mode 100644 (file)
index 0000000..5b981f0
--- /dev/null
@@ -0,0 +1,53 @@
+From stable+bounces-287974-greg=kroah.com@vger.kernel.org Wed Jul 22 19:15:33 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 12:54:33 -0400
+Subject: VDUSE: avoid leaking information to userspace
+To: stable@vger.kernel.org
+Cc: "Jason Wang" <jasowang@redhat.com>, "Xie Yongji" <xieyongji@bytedance.com>, "Eugenio Pérez" <eperezma@redhat.com>, "Michael S. Tsirkin" <mst@redhat.com>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260722165433.1853588-4-sashal@kernel.org>
+
+From: Jason Wang <jasowang@redhat.com>
+
+[ Upstream commit 9c1523803445ee0348f62b77793266dd981596e0 ]
+
+The bounceing is not necessarily page aligned, so current VDUSE can
+leak kernel information through mapping bounce pages to
+userspace. Allocate bounce pages with __GFP_ZERO to avoid leaking
+information to userspace.
+
+Fixes: 8c773d53fb7b ("vduse: Implement an MMU-based software IOTLB")
+Cc: stable@vger.kernel.org
+Signed-off-by: Jason Wang <jasowang@redhat.com>
+Reviewed-by: Xie Yongji <xieyongji@bytedance.com>
+Reviewed-by: Eugenio Pérez <eperezma@redhat.com>
+Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
+Message-ID: <20260130050750.4050-1-jasowang@redhat.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/vdpa/vdpa_user/iova_domain.c |    2 +-
+ drivers/vdpa/vdpa_user/vduse_dev.c   |    2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/vdpa/vdpa_user/iova_domain.c
++++ b/drivers/vdpa/vdpa_user/iova_domain.c
+@@ -124,7 +124,7 @@ static int vduse_domain_map_bounce_page(
+               if (!map->bounce_page) {
+                       head_map = &domain->bounce_maps[(iova & PAGE_MASK) >> BOUNCE_MAP_SHIFT];
+                       if (!head_map->bounce_page) {
+-                              tmp_page = alloc_page(GFP_ATOMIC);
++                              tmp_page = alloc_page(GFP_ATOMIC | __GFP_ZERO);
+                               if (!tmp_page)
+                                       return -ENOMEM;
+                               if (cmpxchg(&head_map->bounce_page, NULL, tmp_page))
+--- a/drivers/vdpa/vdpa_user/vduse_dev.c
++++ b/drivers/vdpa/vdpa_user/vduse_dev.c
+@@ -887,7 +887,7 @@ static void *vduse_dev_alloc_coherent(st
+       *dma_addr = DMA_MAPPING_ERROR;
+-      addr = alloc_pages_exact(size, flag);
++      addr = alloc_pages_exact(size, flag | __GFP_ZERO);
+       if (!addr)
+               return NULL;
diff --git a/queue-6.12/vduse-remove-unused-vaddr-parameter-of-vduse_domain_free_coherent.patch b/queue-6.12/vduse-remove-unused-vaddr-parameter-of-vduse_domain_free_coherent.patch
new file mode 100644 (file)
index 0000000..eef1f20
--- /dev/null
@@ -0,0 +1,61 @@
+From stable+bounces-287972-greg=kroah.com@vger.kernel.org Wed Jul 22 19:15:43 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 12:54:31 -0400
+Subject: vduse: remove unused vaddr parameter of vduse_domain_free_coherent
+To: stable@vger.kernel.org
+Cc: "Eugenio Pérez" <eperezma@redhat.com>, "Michael S. Tsirkin" <mst@redhat.com>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260722165433.1853588-2-sashal@kernel.org>
+
+From: Eugenio Pérez <eperezma@redhat.com>
+
+[ Upstream commit 766e1749c0ef6a09651be9b8a8283d508c322b58 ]
+
+We will modify the function in next patches so let's clean it first.
+
+Signed-off-by: Eugenio Pérez <eperezma@redhat.com>
+Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
+Message-Id: <20260119143306.1818855-9-eperezma@redhat.com>
+Stable-dep-of: 9c1523803445 ("VDUSE: avoid leaking information to userspace")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/vdpa/vdpa_user/iova_domain.c |    3 +--
+ drivers/vdpa/vdpa_user/iova_domain.h |    3 +--
+ drivers/vdpa/vdpa_user/vduse_dev.c   |    2 +-
+ 3 files changed, 3 insertions(+), 5 deletions(-)
+
+--- a/drivers/vdpa/vdpa_user/iova_domain.c
++++ b/drivers/vdpa/vdpa_user/iova_domain.c
+@@ -528,8 +528,7 @@ err:
+ }
+ void vduse_domain_free_coherent(struct vduse_iova_domain *domain, size_t size,
+-                              void *vaddr, dma_addr_t dma_addr,
+-                              unsigned long attrs)
++                              dma_addr_t dma_addr, unsigned long attrs)
+ {
+       struct iova_domain *iovad = &domain->consistent_iovad;
+       struct vhost_iotlb_map *map;
+--- a/drivers/vdpa/vdpa_user/iova_domain.h
++++ b/drivers/vdpa/vdpa_user/iova_domain.h
+@@ -72,8 +72,7 @@ void *vduse_domain_alloc_coherent(struct
+                                 gfp_t flag, unsigned long attrs);
+ void vduse_domain_free_coherent(struct vduse_iova_domain *domain, size_t size,
+-                              void *vaddr, dma_addr_t dma_addr,
+-                              unsigned long attrs);
++                              dma_addr_t dma_addr, unsigned long attrs);
+ void vduse_domain_reset_bounce_map(struct vduse_iova_domain *domain);
+--- a/drivers/vdpa/vdpa_user/vduse_dev.c
++++ b/drivers/vdpa/vdpa_user/vduse_dev.c
+@@ -904,7 +904,7 @@ static void vduse_dev_free_coherent(stru
+       struct vduse_dev *vdev = dev_to_vduse(dev);
+       struct vduse_iova_domain *domain = vdev->domain;
+-      vduse_domain_free_coherent(domain, size, vaddr, dma_addr, attrs);
++      vduse_domain_free_coherent(domain, size, dma_addr, attrs);
+ }
+ static size_t vduse_dev_max_mapping_size(struct device *dev)
diff --git a/queue-6.12/vduse-take-out-allocations-from-vduse_dev_alloc_coherent.patch b/queue-6.12/vduse-take-out-allocations-from-vduse_dev_alloc_coherent.patch
new file mode 100644 (file)
index 0000000..9699e5f
--- /dev/null
@@ -0,0 +1,147 @@
+From stable+bounces-287973-greg=kroah.com@vger.kernel.org Wed Jul 22 19:04:38 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 12:54:32 -0400
+Subject: vduse: take out allocations from vduse_dev_alloc_coherent
+To: stable@vger.kernel.org
+Cc: "Eugenio Pérez" <eperezma@redhat.com>, "Jason Wang" <jasowang@redhat.com>, "Michael S. Tsirkin" <mst@redhat.com>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260722165433.1853588-3-sashal@kernel.org>
+
+From: Eugenio Pérez <eperezma@redhat.com>
+
+[ Upstream commit 489d76520612abf9a4ede4344349105406c91a73 ]
+
+The function vduse_dev_alloc_coherent will be called under rwlock in
+next patches.  Make it out of the lock to avoid increasing its fail
+rate.
+
+Acked-by: Jason Wang <jasowang@redhat.com>
+Signed-off-by: Eugenio Pérez <eperezma@redhat.com>
+Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
+Message-Id: <20260119143306.1818855-10-eperezma@redhat.com>
+Stable-dep-of: 9c1523803445 ("VDUSE: avoid leaking information to userspace")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/vdpa/vdpa_user/iova_domain.c |   24 +++++++-----------------
+ drivers/vdpa/vdpa_user/iova_domain.h |    5 ++---
+ drivers/vdpa/vdpa_user/vduse_dev.c   |   14 ++++++++++----
+ 3 files changed, 19 insertions(+), 24 deletions(-)
+
+--- a/drivers/vdpa/vdpa_user/iova_domain.c
++++ b/drivers/vdpa/vdpa_user/iova_domain.c
+@@ -493,17 +493,15 @@ void vduse_domain_unmap_page(struct vdus
+       vduse_domain_free_iova(iovad, dma_addr, size);
+ }
+-void *vduse_domain_alloc_coherent(struct vduse_iova_domain *domain,
+-                                size_t size, dma_addr_t *dma_addr,
+-                                gfp_t flag, unsigned long attrs)
++dma_addr_t vduse_domain_alloc_coherent(struct vduse_iova_domain *domain,
++                                     size_t size, void *orig)
+ {
+       struct iova_domain *iovad = &domain->consistent_iovad;
+       unsigned long limit = domain->iova_limit;
+       dma_addr_t iova = vduse_domain_alloc_iova(iovad, size, limit);
+-      void *orig = alloc_pages_exact(size, flag);
+-      if (!iova || !orig)
+-              goto err;
++      if (!iova)
++              return DMA_MAPPING_ERROR;
+       spin_lock(&domain->iotlb_lock);
+       if (vduse_iotlb_add_range(domain, (u64)iova, (u64)iova + size - 1,
+@@ -514,17 +512,12 @@ void *vduse_domain_alloc_coherent(struct
+       }
+       spin_unlock(&domain->iotlb_lock);
+-      *dma_addr = iova;
++      return iova;
+-      return orig;
+ err:
+-      *dma_addr = DMA_MAPPING_ERROR;
+-      if (orig)
+-              free_pages_exact(orig, size);
+-      if (iova)
+-              vduse_domain_free_iova(iovad, iova, size);
++      vduse_domain_free_iova(iovad, iova, size);
+-      return NULL;
++      return DMA_MAPPING_ERROR;
+ }
+ void vduse_domain_free_coherent(struct vduse_iova_domain *domain, size_t size,
+@@ -533,7 +526,6 @@ void vduse_domain_free_coherent(struct v
+       struct iova_domain *iovad = &domain->consistent_iovad;
+       struct vhost_iotlb_map *map;
+       struct vdpa_map_file *map_file;
+-      phys_addr_t pa;
+       spin_lock(&domain->iotlb_lock);
+       map = vhost_iotlb_itree_first(domain->iotlb, (u64)dma_addr,
+@@ -545,12 +537,10 @@ void vduse_domain_free_coherent(struct v
+       map_file = (struct vdpa_map_file *)map->opaque;
+       fput(map_file->file);
+       kfree(map_file);
+-      pa = map->addr;
+       vhost_iotlb_map_free(domain->iotlb, map);
+       spin_unlock(&domain->iotlb_lock);
+       vduse_domain_free_iova(iovad, dma_addr, size);
+-      free_pages_exact(phys_to_virt(pa), size);
+ }
+ static vm_fault_t vduse_domain_mmap_fault(struct vm_fault *vmf)
+--- a/drivers/vdpa/vdpa_user/iova_domain.h
++++ b/drivers/vdpa/vdpa_user/iova_domain.h
+@@ -67,9 +67,8 @@ void vduse_domain_unmap_page(struct vdus
+                            dma_addr_t dma_addr, size_t size,
+                            enum dma_data_direction dir, unsigned long attrs);
+-void *vduse_domain_alloc_coherent(struct vduse_iova_domain *domain,
+-                                size_t size, dma_addr_t *dma_addr,
+-                                gfp_t flag, unsigned long attrs);
++dma_addr_t vduse_domain_alloc_coherent(struct vduse_iova_domain *domain,
++                                     size_t size, void *orig);
+ void vduse_domain_free_coherent(struct vduse_iova_domain *domain, size_t size,
+                               dma_addr_t dma_addr, unsigned long attrs);
+--- a/drivers/vdpa/vdpa_user/vduse_dev.c
++++ b/drivers/vdpa/vdpa_user/vduse_dev.c
+@@ -883,18 +883,23 @@ static void *vduse_dev_alloc_coherent(st
+ {
+       struct vduse_dev *vdev = dev_to_vduse(dev);
+       struct vduse_iova_domain *domain = vdev->domain;
+-      unsigned long iova;
+       void *addr;
+       *dma_addr = DMA_MAPPING_ERROR;
+-      addr = vduse_domain_alloc_coherent(domain, size,
+-                              (dma_addr_t *)&iova, flag, attrs);
++
++      addr = alloc_pages_exact(size, flag);
+       if (!addr)
+               return NULL;
+-      *dma_addr = (dma_addr_t)iova;
++      *dma_addr = vduse_domain_alloc_coherent(domain, size, addr);
++      if (*dma_addr == DMA_MAPPING_ERROR)
++              goto err;
+       return addr;
++
++err:
++      free_pages_exact(addr, size);
++      return NULL;
+ }
+ static void vduse_dev_free_coherent(struct device *dev, size_t size,
+@@ -905,6 +910,7 @@ static void vduse_dev_free_coherent(stru
+       struct vduse_iova_domain *domain = vdev->domain;
+       vduse_domain_free_coherent(domain, size, dma_addr, attrs);
++      free_pages_exact(vaddr, size);
+ }
+ static size_t vduse_dev_max_mapping_size(struct device *dev)
diff --git a/queue-6.12/vduse-use-fixed-4kb-bounce-pages-for-non-4kb-page-size.patch b/queue-6.12/vduse-use-fixed-4kb-bounce-pages-for-non-4kb-page-size.patch
new file mode 100644 (file)
index 0000000..8cb25db
--- /dev/null
@@ -0,0 +1,292 @@
+From stable+bounces-287971-greg=kroah.com@vger.kernel.org Wed Jul 22 19:04:36 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 12:54:30 -0400
+Subject: vduse: Use fixed 4KB bounce pages for non-4KB page size
+To: stable@vger.kernel.org
+Cc: Sheng Zhao <sheng.zhao@bytedance.com>, "Michael S. Tsirkin" <mst@redhat.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722165433.1853588-1-sashal@kernel.org>
+
+From: Sheng Zhao <sheng.zhao@bytedance.com>
+
+[ Upstream commit 3fc3068e7247c94dec08e93fea422a1bb649bfe5 ]
+
+The allocation granularity of bounce pages is PAGE_SIZE. This may cause
+even small IO requests to occupy an entire bounce page exclusively. The
+kind of memory waste will be more significant when PAGE_SIZE is larger
+than 4KB (e.g. arm64 with 64KB pages).
+
+So, optimize it by using fixed 4KB bounce maps and iova allocation
+granularity. A single IO request occupies at least a 4KB bounce page
+instead of the entire memory page of PAGE_SIZE.
+
+Signed-off-by: Sheng Zhao <sheng.zhao@bytedance.com>
+Message-Id: <20250925113516.60305-1-sheng.zhao@bytedance.com>
+Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
+Stable-dep-of: 9c1523803445 ("VDUSE: avoid leaking information to userspace")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/vdpa/vdpa_user/iova_domain.c |  130 +++++++++++++++++++++++------------
+ drivers/vdpa/vdpa_user/iova_domain.h |    5 +
+ 2 files changed, 94 insertions(+), 41 deletions(-)
+
+--- a/drivers/vdpa/vdpa_user/iova_domain.c
++++ b/drivers/vdpa/vdpa_user/iova_domain.c
+@@ -103,19 +103,38 @@ void vduse_domain_clear_map(struct vduse
+ static int vduse_domain_map_bounce_page(struct vduse_iova_domain *domain,
+                                        u64 iova, u64 size, u64 paddr)
+ {
+-      struct vduse_bounce_map *map;
++      struct vduse_bounce_map *map, *head_map;
++      struct page *tmp_page;
+       u64 last = iova + size - 1;
+       while (iova <= last) {
+-              map = &domain->bounce_maps[iova >> PAGE_SHIFT];
++              /*
++               * When PAGE_SIZE is larger than 4KB, multiple adjacent bounce_maps will
++               * point to the same memory page of PAGE_SIZE. Since bounce_maps originate
++               * from IO requests, we may not be able to guarantee that the orig_phys
++               * values of all IO requests within the same 64KB memory page are contiguous.
++               * Therefore, we need to store them separately.
++               *
++               * Bounce pages are allocated on demand. As a result, it may occur that
++               * multiple bounce pages corresponding to the same 64KB memory page attempt
++               * to allocate memory simultaneously, so we use cmpxchg to handle this
++               * concurrency.
++               */
++              map = &domain->bounce_maps[iova >> BOUNCE_MAP_SHIFT];
+               if (!map->bounce_page) {
+-                      map->bounce_page = alloc_page(GFP_ATOMIC);
+-                      if (!map->bounce_page)
+-                              return -ENOMEM;
++                      head_map = &domain->bounce_maps[(iova & PAGE_MASK) >> BOUNCE_MAP_SHIFT];
++                      if (!head_map->bounce_page) {
++                              tmp_page = alloc_page(GFP_ATOMIC);
++                              if (!tmp_page)
++                                      return -ENOMEM;
++                              if (cmpxchg(&head_map->bounce_page, NULL, tmp_page))
++                                      __free_page(tmp_page);
++                      }
++                      map->bounce_page = head_map->bounce_page;
+               }
+               map->orig_phys = paddr;
+-              paddr += PAGE_SIZE;
+-              iova += PAGE_SIZE;
++              paddr += BOUNCE_MAP_SIZE;
++              iova += BOUNCE_MAP_SIZE;
+       }
+       return 0;
+ }
+@@ -127,12 +146,17 @@ static void vduse_domain_unmap_bounce_pa
+       u64 last = iova + size - 1;
+       while (iova <= last) {
+-              map = &domain->bounce_maps[iova >> PAGE_SHIFT];
++              map = &domain->bounce_maps[iova >> BOUNCE_MAP_SHIFT];
+               map->orig_phys = INVALID_PHYS_ADDR;
+-              iova += PAGE_SIZE;
++              iova += BOUNCE_MAP_SIZE;
+       }
+ }
++static unsigned int offset_in_bounce_page(dma_addr_t addr)
++{
++      return (addr & ~BOUNCE_MAP_MASK);
++}
++
+ static void do_bounce(phys_addr_t orig, void *addr, size_t size,
+                     enum dma_data_direction dir)
+ {
+@@ -163,7 +187,7 @@ static void vduse_domain_bounce(struct v
+ {
+       struct vduse_bounce_map *map;
+       struct page *page;
+-      unsigned int offset;
++      unsigned int offset, head_offset;
+       void *addr;
+       size_t sz;
+@@ -171,9 +195,10 @@ static void vduse_domain_bounce(struct v
+               return;
+       while (size) {
+-              map = &domain->bounce_maps[iova >> PAGE_SHIFT];
+-              offset = offset_in_page(iova);
+-              sz = min_t(size_t, PAGE_SIZE - offset, size);
++              map = &domain->bounce_maps[iova >> BOUNCE_MAP_SHIFT];
++              head_offset = offset_in_page(iova);
++              offset = offset_in_bounce_page(iova);
++              sz = min_t(size_t, BOUNCE_MAP_SIZE - offset, size);
+               if (WARN_ON(!map->bounce_page ||
+                           map->orig_phys == INVALID_PHYS_ADDR))
+@@ -183,7 +208,7 @@ static void vduse_domain_bounce(struct v
+                      map->user_bounce_page : map->bounce_page;
+               addr = kmap_local_page(page);
+-              do_bounce(map->orig_phys + offset, addr + offset, sz, dir);
++              do_bounce(map->orig_phys + offset, addr + head_offset, sz, dir);
+               kunmap_local(addr);
+               size -= sz;
+               iova += sz;
+@@ -218,7 +243,7 @@ vduse_domain_get_bounce_page(struct vdus
+       struct page *page = NULL;
+       read_lock(&domain->bounce_lock);
+-      map = &domain->bounce_maps[iova >> PAGE_SHIFT];
++      map = &domain->bounce_maps[iova >> BOUNCE_MAP_SHIFT];
+       if (domain->user_bounce_pages || !map->bounce_page)
+               goto out;
+@@ -236,7 +261,7 @@ vduse_domain_free_kernel_bounce_pages(st
+       struct vduse_bounce_map *map;
+       unsigned long pfn, bounce_pfns;
+-      bounce_pfns = domain->bounce_size >> PAGE_SHIFT;
++      bounce_pfns = domain->bounce_size >> BOUNCE_MAP_SHIFT;
+       for (pfn = 0; pfn < bounce_pfns; pfn++) {
+               map = &domain->bounce_maps[pfn];
+@@ -246,7 +271,8 @@ vduse_domain_free_kernel_bounce_pages(st
+               if (!map->bounce_page)
+                       continue;
+-              __free_page(map->bounce_page);
++              if (!((pfn << BOUNCE_MAP_SHIFT) & ~PAGE_MASK))
++                      __free_page(map->bounce_page);
+               map->bounce_page = NULL;
+       }
+ }
+@@ -254,8 +280,12 @@ vduse_domain_free_kernel_bounce_pages(st
+ int vduse_domain_add_user_bounce_pages(struct vduse_iova_domain *domain,
+                                      struct page **pages, int count)
+ {
+-      struct vduse_bounce_map *map;
+-      int i, ret;
++      struct vduse_bounce_map *map, *head_map;
++      int i, j, ret;
++      int inner_pages = PAGE_SIZE / BOUNCE_MAP_SIZE;
++      int bounce_pfns = domain->bounce_size >> BOUNCE_MAP_SHIFT;
++      struct page *head_page = NULL;
++      bool need_copy;
+       /* Now we don't support partial mapping */
+       if (count != (domain->bounce_size >> PAGE_SHIFT))
+@@ -267,16 +297,23 @@ int vduse_domain_add_user_bounce_pages(s
+               goto out;
+       for (i = 0; i < count; i++) {
+-              map = &domain->bounce_maps[i];
+-              if (map->bounce_page) {
++              need_copy = false;
++              head_map = &domain->bounce_maps[(i * inner_pages)];
++              head_page = head_map->bounce_page;
++              for (j = 0; j < inner_pages; j++) {
++                      if ((i * inner_pages + j) >= bounce_pfns)
++                              break;
++                      map = &domain->bounce_maps[(i * inner_pages + j)];
+                       /* Copy kernel page to user page if it's in use */
+-                      if (map->orig_phys != INVALID_PHYS_ADDR)
+-                              memcpy_to_page(pages[i], 0,
+-                                             page_address(map->bounce_page),
+-                                             PAGE_SIZE);
++                      if ((head_page) && (map->orig_phys != INVALID_PHYS_ADDR))
++                              need_copy = true;
++                      map->user_bounce_page = pages[i];
+               }
+-              map->user_bounce_page = pages[i];
+               get_page(pages[i]);
++              if ((head_page) && (need_copy))
++                      memcpy_to_page(pages[i], 0,
++                                     page_address(head_page),
++                                     PAGE_SIZE);
+       }
+       domain->user_bounce_pages = true;
+       ret = 0;
+@@ -288,8 +325,12 @@ out:
+ void vduse_domain_remove_user_bounce_pages(struct vduse_iova_domain *domain)
+ {
+-      struct vduse_bounce_map *map;
+-      unsigned long i, count;
++      struct vduse_bounce_map *map, *head_map;
++      unsigned long i, j, count;
++      int inner_pages = PAGE_SIZE / BOUNCE_MAP_SIZE;
++      int bounce_pfns = domain->bounce_size >> BOUNCE_MAP_SHIFT;
++      struct page *head_page = NULL;
++      bool need_copy;
+       write_lock(&domain->bounce_lock);
+       if (!domain->user_bounce_pages)
+@@ -297,20 +338,27 @@ void vduse_domain_remove_user_bounce_pag
+       count = domain->bounce_size >> PAGE_SHIFT;
+       for (i = 0; i < count; i++) {
+-              struct page *page = NULL;
+-
+-              map = &domain->bounce_maps[i];
+-              if (WARN_ON(!map->user_bounce_page))
++              need_copy = false;
++              head_map = &domain->bounce_maps[(i * inner_pages)];
++              if (WARN_ON(!head_map->user_bounce_page))
+                       continue;
++              head_page = head_map->user_bounce_page;
+-              /* Copy user page to kernel page if it's in use */
+-              if (map->orig_phys != INVALID_PHYS_ADDR) {
+-                      page = map->bounce_page;
+-                      memcpy_from_page(page_address(page),
+-                                       map->user_bounce_page, 0, PAGE_SIZE);
++              for (j = 0; j < inner_pages; j++) {
++                      if ((i * inner_pages + j) >= bounce_pfns)
++                              break;
++                      map = &domain->bounce_maps[(i * inner_pages + j)];
++                      if (WARN_ON(!map->user_bounce_page))
++                              continue;
++                      /* Copy user page to kernel page if it's in use */
++                      if ((map->orig_phys != INVALID_PHYS_ADDR) && (head_map->bounce_page))
++                              need_copy = true;
++                      map->user_bounce_page = NULL;
+               }
+-              put_page(map->user_bounce_page);
+-              map->user_bounce_page = NULL;
++              if (need_copy)
++                      memcpy_from_page(page_address(head_map->bounce_page),
++                                       head_page, 0, PAGE_SIZE);
++              put_page(head_page);
+       }
+       domain->user_bounce_pages = false;
+ out:
+@@ -581,7 +629,7 @@ vduse_domain_create(unsigned long iova_l
+       unsigned long pfn, bounce_pfns;
+       int ret;
+-      bounce_pfns = PAGE_ALIGN(bounce_size) >> PAGE_SHIFT;
++      bounce_pfns = PAGE_ALIGN(bounce_size) >> BOUNCE_MAP_SHIFT;
+       if (iova_limit <= bounce_size)
+               return NULL;
+@@ -613,7 +661,7 @@ vduse_domain_create(unsigned long iova_l
+       rwlock_init(&domain->bounce_lock);
+       spin_lock_init(&domain->iotlb_lock);
+       init_iova_domain(&domain->stream_iovad,
+-                      PAGE_SIZE, IOVA_START_PFN);
++                      BOUNCE_MAP_SIZE, IOVA_START_PFN);
+       ret = iova_domain_init_rcaches(&domain->stream_iovad);
+       if (ret)
+               goto err_iovad_stream;
+--- a/drivers/vdpa/vdpa_user/iova_domain.h
++++ b/drivers/vdpa/vdpa_user/iova_domain.h
+@@ -19,6 +19,11 @@
+ #define INVALID_PHYS_ADDR (~(phys_addr_t)0)
++#define BOUNCE_MAP_SHIFT      12
++#define BOUNCE_MAP_SIZE       (1 << BOUNCE_MAP_SHIFT)
++#define BOUNCE_MAP_MASK       (~(BOUNCE_MAP_SIZE - 1))
++#define BOUNCE_MAP_ALIGN(addr)        (((addr) + BOUNCE_MAP_SIZE - 1) & ~(BOUNCE_MAP_SIZE - 1))
++
+ struct vduse_bounce_map {
+       struct page *bounce_page;
+       struct page *user_bounce_page;
diff --git a/queue-6.12/vfs-audit-introduce-kern_path_parent-for-audit.patch b/queue-6.12/vfs-audit-introduce-kern_path_parent-for-audit.patch
new file mode 100644 (file)
index 0000000..8d2d5db
--- /dev/null
@@ -0,0 +1,191 @@
+From stable+bounces-287657-greg=kroah.com@vger.kernel.org Wed Jul 22 02:19:43 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 19:59:01 -0400
+Subject: VFS/audit: introduce kern_path_parent() for audit
+To: stable@vger.kernel.org
+Cc: NeilBrown <neil@brown.name>, Christian Brauner <brauner@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721235904.525962-1-sashal@kernel.org>
+
+From: NeilBrown <neil@brown.name>
+
+[ Upstream commit 76a53de6f7ff0641570364234fb4489f4d4fc8e9 ]
+
+audit_alloc_mark() and audit_get_nd() both need to perform a path
+lookup getting the parent dentry (which must exist) and the final
+target (following a LAST_NORM name) which sometimes doesn't need to
+exist.
+
+They don't need the parent to be locked, but use kern_path_locked() or
+kern_path_locked_negative() anyway.  This is somewhat misleading to the
+casual reader.
+
+This patch introduces a more targeted function, kern_path_parent(),
+which returns not holding locks.  On success the "path" will
+be set to the parent, which must be found, and the return value is the
+dentry of the target, which might be negative.
+
+This will clear the way to rename kern_path_locked() which is
+otherwise only used to prepare for removing something.
+
+It also allows us to remove kern_path_locked_negative(), which is
+transformed into the new kern_path_parent().
+
+Signed-off-by: NeilBrown <neil@brown.name>
+Signed-off-by: Christian Brauner <brauner@kernel.org>
+Stable-dep-of: 81905b5acbe7 ("audit: fix recursive locking deadlock in audit_dupe_exe()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/namei.c              |   43 +++++++++++++++++++++++++++++++++++++++++++
+ include/linux/namei.h   |    1 +
+ kernel/audit.h          |    4 ++--
+ kernel/audit_fsnotify.c |    9 +++------
+ kernel/audit_watch.c    |    9 ++++++---
+ 5 files changed, 55 insertions(+), 11 deletions(-)
+
+--- a/fs/namei.c
++++ b/fs/namei.c
+@@ -2705,6 +2705,49 @@ static struct dentry *__kern_path_locked
+       return d;
+ }
++/**
++ * kern_path_parent: lookup path returning parent and target
++ * @name: path name
++ * @path: path to store parent in
++ *
++ * The path @name should end with a normal component, not "." or ".." or "/".
++ * A lookup is performed and if successful the parent information
++ * is store in @parent and the dentry is returned.
++ *
++ * The dentry maybe negative, the parent will be positive.
++ *
++ * Returns:  dentry or error.
++ */
++struct dentry *kern_path_parent(const char *name, struct path *path)
++{
++      struct filename *filename = getname_kernel(name);
++      struct path parent_path;
++      struct dentry *d;
++      struct qstr last;
++      int type, error;
++
++      error = filename_parentat(AT_FDCWD, filename, 0, &parent_path, &last, &type);
++      if (error) {
++              d = ERR_PTR(error);
++              goto out;
++      }
++      if (unlikely(type != LAST_NORM)) {
++              path_put(&parent_path);
++              d = ERR_PTR(-EINVAL);
++              goto out;
++      }
++
++      d = lookup_one_len_unlocked(last.name, parent_path.dentry, last.len);
++      if (IS_ERR(d)) {
++              path_put(&parent_path);
++              goto out;
++      }
++      *path = parent_path;
++out:
++      putname(filename);
++      return d;
++}
++
+ struct dentry *kern_path_locked(const char *name, struct path *path)
+ {
+       struct filename *filename = getname_kernel(name);
+--- a/include/linux/namei.h
++++ b/include/linux/namei.h
+@@ -51,6 +51,7 @@ struct dentry *lookup_one_qstr_excl(cons
+                                   struct dentry *base,
+                                   unsigned int flags);
+ extern int kern_path(const char *, unsigned, struct path *);
++struct dentry *kern_path_parent(const char *name, struct path *parent);
+ extern struct dentry *kern_path_create(int, const char *, struct path *, unsigned int);
+ extern struct dentry *user_path_create(int, const char __user *, struct path *, unsigned int);
+--- a/kernel/audit.h
++++ b/kernel/audit.h
+@@ -279,8 +279,8 @@ extern struct audit_fsnotify_mark *audit
+ extern char *audit_mark_path(struct audit_fsnotify_mark *mark);
+ extern void audit_remove_mark(struct audit_fsnotify_mark *audit_mark);
+ extern void audit_remove_mark_rule(struct audit_krule *krule);
+-extern int audit_mark_compare(struct audit_fsnotify_mark *mark,
+-                            unsigned long ino, dev_t dev);
++extern int audit_mark_compare(struct audit_fsnotify_mark *mark, u64 ino,
++                            dev_t dev);
+ extern int audit_dupe_exe(struct audit_krule *new, struct audit_krule *old);
+ extern int audit_exe_compare(struct task_struct *tsk,
+                            struct audit_fsnotify_mark *mark);
+--- a/kernel/audit_fsnotify.c
++++ b/kernel/audit_fsnotify.c
+@@ -57,7 +57,7 @@ char *audit_mark_path(struct audit_fsnot
+       return mark->path;
+ }
+-int audit_mark_compare(struct audit_fsnotify_mark *mark, unsigned long ino, dev_t dev)
++int audit_mark_compare(struct audit_fsnotify_mark *mark, u64 ino, dev_t dev)
+ {
+       if (mark->ino == AUDIT_INO_UNSET)
+               return 0;
+@@ -76,17 +76,14 @@ struct audit_fsnotify_mark *audit_alloc_
+       struct audit_fsnotify_mark *audit_mark;
+       struct path path;
+       struct dentry *dentry;
+-      struct inode *inode;
+       int ret;
+       if (pathname[0] != '/' || pathname[len-1] == '/')
+               return ERR_PTR(-EINVAL);
+-      dentry = kern_path_locked(pathname, &path);
++      dentry = kern_path_parent(pathname, &path);
+       if (IS_ERR(dentry))
+               return ERR_CAST(dentry); /* returning an error */
+-      inode = path.dentry->d_inode;
+-      inode_unlock(inode);
+       audit_mark = kzalloc(sizeof(*audit_mark), GFP_KERNEL);
+       if (unlikely(!audit_mark)) {
+@@ -100,7 +97,7 @@ struct audit_fsnotify_mark *audit_alloc_
+       audit_update_mark(audit_mark, dentry->d_inode);
+       audit_mark->rule = krule;
+-      ret = fsnotify_add_inode_mark(&audit_mark->mark, inode, 0);
++      ret = fsnotify_add_inode_mark(&audit_mark->mark, path.dentry->d_inode, 0);
+       if (ret < 0) {
+               audit_mark->path = NULL;
+               fsnotify_put_mark(&audit_mark->mark);
+--- a/kernel/audit_watch.c
++++ b/kernel/audit_watch.c
+@@ -244,7 +244,7 @@ static void audit_watch_log_rule_change(
+ /* Update inode info in audit rules based on filesystem event. */
+ static void audit_update_watch(struct audit_parent *parent,
+                              const struct qstr *dname, dev_t dev,
+-                             unsigned long ino, unsigned invalidating)
++                             u64 ino, unsigned int invalidating)
+ {
+       struct audit_watch *owatch, *nwatch, *nextw;
+       struct audit_krule *r, *nextr;
+@@ -347,15 +347,18 @@ static void audit_remove_parent_watches(
+ /* Get path information necessary for adding watches. */
+ static int audit_get_nd(struct audit_watch *watch, struct path *parent)
+ {
+-      struct dentry *d = kern_path_locked(watch->path, parent);
++      struct dentry *d;
++
++      d = kern_path_parent(watch->path, parent);
+       if (IS_ERR(d))
+               return PTR_ERR(d);
++
+       if (d_is_positive(d)) {
+               /* update watch filter fields */
+               watch->dev = d->d_sb->s_dev;
+               watch->ino = d_backing_inode(d)->i_ino;
+       }
+-      inode_unlock(d_backing_inode(parent->dentry));
++
+       dput(d);
+       return 0;
+ }
diff --git a/queue-6.12/wifi-libertas_tf-fix-use-after-free-in-lbtf_free_adapter.patch b/queue-6.12/wifi-libertas_tf-fix-use-after-free-in-lbtf_free_adapter.patch
new file mode 100644 (file)
index 0000000..e9d2536
--- /dev/null
@@ -0,0 +1,49 @@
+From stable+bounces-289739-greg=kroah.com@vger.kernel.org Tue Jul 28 01:36:40 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 19:34:31 -0400
+Subject: wifi: libertas_tf: fix use-after-free in lbtf_free_adapter()
+To: stable@vger.kernel.org
+Cc: Maoyi Xie <maoyixie.tju@gmail.com>, Johannes Berg <johannes.berg@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727233431.2424901-1-sashal@kernel.org>
+
+From: Maoyi Xie <maoyixie.tju@gmail.com>
+
+[ Upstream commit aa6dcd5c8dd9ba1d7d0f60093bcda41c0d6d438d ]
+
+lbtf_free_adapter() calls timer_delete(&priv->command_timer), which does
+not wait for a running command_timer_fn() callback. lbtf_free_adapter()
+runs on the teardown path right before ieee80211_free_hw() frees priv,
+both in lbtf_remove_card() and in the probe error path. command_timer is
+armed by mod_timer() in lbtf_cmd() whenever a firmware command is sent.
+command_timer_fn() dereferences priv. If a command times out as the
+device is removed, command_timer_fn() runs concurrently with teardown and
+dereferences priv after it has been freed.
+
+This is the same use-after-free that commit 03cc8f90d053 ("wifi: libertas:
+fix use-after-free in lbs_free_adapter()") fixed in the sibling libertas
+driver. The libertas_tf variant has the identical pattern and was left
+unchanged. Use timer_delete_sync() so any in-flight callback completes
+before priv is freed.
+
+Fixes: 06b16ae53192 ("libertas_tf: main.c, data paths and mac80211 handlers")
+Cc: stable@vger.kernel.org
+Signed-off-by: Maoyi Xie <maoyixie.tju@gmail.com>
+Link: https://patch.msgid.link/178211481807.2212567.8773346114561900100@maoyixie.com
+Signed-off-by: Johannes Berg <johannes.berg@intel.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/net/wireless/marvell/libertas_tf/main.c |    2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/net/wireless/marvell/libertas_tf/main.c
++++ b/drivers/net/wireless/marvell/libertas_tf/main.c
+@@ -174,7 +174,7 @@ static void lbtf_free_adapter(struct lbt
+ {
+       lbtf_deb_enter(LBTF_DEB_MAIN);
+       lbtf_free_cmd_buffer(priv);
+-      del_timer(&priv->command_timer);
++      timer_delete_sync(&priv->command_timer);
+       lbtf_deb_leave(LBTF_DEB_MAIN);
+ }
diff --git a/queue-6.12/xfrm-nat_keepalive-avoid-double-free-on-send-error.patch b/queue-6.12/xfrm-nat_keepalive-avoid-double-free-on-send-error.patch
new file mode 100644 (file)
index 0000000..c65577d
--- /dev/null
@@ -0,0 +1,91 @@
+From stable+bounces-289480-greg=kroah.com@vger.kernel.org Mon Jul 27 14:24:14 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 08:19:52 -0400
+Subject: xfrm: nat_keepalive: avoid double free on send error
+To: stable@vger.kernel.org
+Cc: Qianyu Luo <qianyuluo3@gmail.com>, Yuan Tan <yuantan098@gmail.com>, Xin Liu <bird@lzu.edu.cn>, Ren Wei <n05ec@lzu.edu.cn>, Eyal Birger <eyal.birger@gmail.com>, Steffen Klassert <steffen.klassert@secunet.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727121952.3778327-2-sashal@kernel.org>
+
+From: Qianyu Luo <qianyuluo3@gmail.com>
+
+[ Upstream commit 226f4a490d1a938fc838d8f8c46a4eca864c0d78 ]
+
+nat_keepalive_send() frees the keepalive skb whenever the IPv4 or IPv6
+send helper reports an error.
+
+That cleanup is only correct before the skb is handed to the output
+path. Once ip_build_and_send_pkt() or ip6_xmit() takes ownership, the
+networking stack may already have consumed the skb before returning an
+error, so freeing it again is unsafe.
+
+Handle the pre-handoff failure cases inside nat_keepalive_send_ipv4()
+and nat_keepalive_send_ipv6(), where the caller still owns the skb, and
+keep nat_keepalive_send() responsible only for family dispatch and the
+unsupported-family cleanup path.
+
+Fixes: f531d13bdfe3 ("xfrm: support sending NAT keepalives in ESP in UDP states")
+Cc: stable@vger.kernel.org
+Reported-by: Yuan Tan <yuantan098@gmail.com>
+Reported-by: Xin Liu <bird@lzu.edu.cn>
+Signed-off-by: Qianyu Luo <qianyuluo3@gmail.com>
+Signed-off-by: Ren Wei <n05ec@lzu.edu.cn>
+Reviewed-by: Eyal Birger <eyal.birger@gmail.com>
+Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/xfrm/xfrm_nat_keepalive.c |   15 +++++++++------
+ 1 file changed, 9 insertions(+), 6 deletions(-)
+
+--- a/net/xfrm/xfrm_nat_keepalive.c
++++ b/net/xfrm/xfrm_nat_keepalive.c
+@@ -55,8 +55,10 @@ static int nat_keepalive_send_ipv4(struc
+                          ka->encap_sport, sock_net_uid(net, NULL));
+       rt = ip_route_output_key(net, &fl4);
+-      if (IS_ERR(rt))
++      if (IS_ERR(rt)) {
++              kfree_skb(skb);
+               return PTR_ERR(rt);
++      }
+       skb_dst_set(skb, &rt->dst);
+@@ -101,6 +103,7 @@ static int nat_keepalive_send_ipv6(struc
+       dst = ipv6_stub->ipv6_dst_lookup_flow(net, sk, &fl6, NULL);
+       if (IS_ERR(dst)) {
+               local_unlock_nested_bh(&nat_keepalive_sk_ipv6.bh_lock);
++              kfree_skb(skb);
+               return PTR_ERR(dst);
+       }
+@@ -118,7 +121,6 @@ static void nat_keepalive_send(struct na
+                                       sizeof(struct ipv6hdr)) +
+                                   sizeof(struct udphdr);
+       const u8 nat_ka_payload = 0xFF;
+-      int err = -EAFNOSUPPORT;
+       struct sk_buff *skb;
+       struct udphdr *uh;
+@@ -140,16 +142,17 @@ static void nat_keepalive_send(struct na
+       switch (ka->family) {
+       case AF_INET:
+-              err = nat_keepalive_send_ipv4(skb, ka);
++              nat_keepalive_send_ipv4(skb, ka);
+               break;
+ #if IS_ENABLED(CONFIG_IPV6)
+       case AF_INET6:
+-              err = nat_keepalive_send_ipv6(skb, ka, uh);
++              nat_keepalive_send_ipv6(skb, ka, uh);
+               break;
+ #endif
+-      }
+-      if (err)
++      default:
+               kfree_skb(skb);
++              break;
++      }
+ }
+ struct nat_keepalive_work_ctx {
diff --git a/queue-6.12/xfrm-use-nested-bh-locking-for-nat_keepalive_sk_ipv.patch b/queue-6.12/xfrm-use-nested-bh-locking-for-nat_keepalive_sk_ipv.patch
new file mode 100644 (file)
index 0000000..a64140f
--- /dev/null
@@ -0,0 +1,122 @@
+From stable+bounces-289479-greg=kroah.com@vger.kernel.org Mon Jul 27 14:19:59 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 08:19:51 -0400
+Subject: xfrm: Use nested-BH locking for nat_keepalive_sk_ipv[46]
+To: stable@vger.kernel.org
+Cc: Sebastian Andrzej Siewior <bigeasy@linutronix.de>, Steffen Klassert <steffen.klassert@secunet.com>, Herbert Xu <herbert@gondor.apana.org.au>, Paolo Abeni <pabeni@redhat.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727121952.3778327-1-sashal@kernel.org>
+
+From: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
+
+[ Upstream commit 9c607d4b6589d4d380a85784514bcf4cceee1e11 ]
+
+nat_keepalive_sk_ipv[46] is a per-CPU variable and relies on disabled BH
+for its locking. Without per-CPU locking in local_bh_disable() on
+PREEMPT_RT this data structure requires explicit locking.
+
+Use sock_bh_locked which has a sock pointer and a local_lock_t. Use
+local_lock_nested_bh() for locking. This change adds only lockdep
+coverage and does not alter the functional behaviour for !PREEMPT_RT.
+
+Cc: Steffen Klassert <steffen.klassert@secunet.com>
+Cc: Herbert Xu <herbert@gondor.apana.org.au>
+Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
+Link: https://patch.msgid.link/20250512092736.229935-7-bigeasy@linutronix.de
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+Stable-dep-of: 226f4a490d1a ("xfrm: nat_keepalive: avoid double free on send error")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/xfrm/xfrm_nat_keepalive.c |   30 ++++++++++++++++++++----------
+ 1 file changed, 20 insertions(+), 10 deletions(-)
+
+--- a/net/xfrm/xfrm_nat_keepalive.c
++++ b/net/xfrm/xfrm_nat_keepalive.c
+@@ -9,9 +9,13 @@
+ #include <net/ip6_checksum.h>
+ #include <net/xfrm.h>
+-static DEFINE_PER_CPU(struct sock *, nat_keepalive_sk_ipv4);
++static DEFINE_PER_CPU(struct sock_bh_locked, nat_keepalive_sk_ipv4) = {
++      .bh_lock = INIT_LOCAL_LOCK(bh_lock),
++};
+ #if IS_ENABLED(CONFIG_IPV6)
+-static DEFINE_PER_CPU(struct sock *, nat_keepalive_sk_ipv6);
++static DEFINE_PER_CPU(struct sock_bh_locked, nat_keepalive_sk_ipv6) = {
++      .bh_lock = INIT_LOCAL_LOCK(bh_lock),
++};
+ #endif
+ struct nat_keepalive {
+@@ -56,10 +60,12 @@ static int nat_keepalive_send_ipv4(struc
+       skb_dst_set(skb, &rt->dst);
+-      sk = *this_cpu_ptr(&nat_keepalive_sk_ipv4);
++      local_lock_nested_bh(&nat_keepalive_sk_ipv4.bh_lock);
++      sk = this_cpu_read(nat_keepalive_sk_ipv4.sock);
+       sock_net_set(sk, net);
+       err = ip_build_and_send_pkt(skb, sk, fl4.saddr, fl4.daddr, NULL, tos);
+       sock_net_set(sk, &init_net);
++      local_unlock_nested_bh(&nat_keepalive_sk_ipv4.bh_lock);
+       return err;
+ }
+@@ -89,15 +95,19 @@ static int nat_keepalive_send_ipv6(struc
+       fl6.fl6_sport = ka->encap_sport;
+       fl6.fl6_dport = ka->encap_dport;
+-      sk = *this_cpu_ptr(&nat_keepalive_sk_ipv6);
++      local_lock_nested_bh(&nat_keepalive_sk_ipv6.bh_lock);
++      sk = this_cpu_read(nat_keepalive_sk_ipv6.sock);
+       sock_net_set(sk, net);
+       dst = ipv6_stub->ipv6_dst_lookup_flow(net, sk, &fl6, NULL);
+-      if (IS_ERR(dst))
++      if (IS_ERR(dst)) {
++              local_unlock_nested_bh(&nat_keepalive_sk_ipv6.bh_lock);
+               return PTR_ERR(dst);
++      }
+       skb_dst_set(skb, dst);
+       err = ipv6_stub->ip6_xmit(sk, skb, &fl6, skb->mark, NULL, 0, 0);
+       sock_net_set(sk, &init_net);
++      local_unlock_nested_bh(&nat_keepalive_sk_ipv6.bh_lock);
+       return err;
+ }
+ #endif
+@@ -202,7 +212,7 @@ static void nat_keepalive_work(struct wo
+                                     (ctx.next_run - ctx.now) * HZ);
+ }
+-static int nat_keepalive_sk_init(struct sock * __percpu *socks,
++static int nat_keepalive_sk_init(struct sock_bh_locked __percpu *socks,
+                                unsigned short family)
+ {
+       struct sock *sk;
+@@ -214,22 +224,22 @@ static int nat_keepalive_sk_init(struct
+               if (err < 0)
+                       goto err;
+-              *per_cpu_ptr(socks, i) = sk;
++              per_cpu_ptr(socks, i)->sock = sk;
+       }
+       return 0;
+ err:
+       for_each_possible_cpu(i)
+-              inet_ctl_sock_destroy(*per_cpu_ptr(socks, i));
++              inet_ctl_sock_destroy(per_cpu_ptr(socks, i)->sock);
+       return err;
+ }
+-static void nat_keepalive_sk_fini(struct sock * __percpu *socks)
++static void nat_keepalive_sk_fini(struct sock_bh_locked __percpu *socks)
+ {
+       int i;
+       for_each_possible_cpu(i)
+-              inet_ctl_sock_destroy(*per_cpu_ptr(socks, i));
++              inet_ctl_sock_destroy(per_cpu_ptr(socks, i)->sock);
+ }
+ void xfrm_nat_keepalive_state_updated(struct xfrm_state *x)
diff --git a/queue-6.12/xfs-don-t-replace-the-wrong-part-of-the-cow-fork.patch b/queue-6.12/xfs-don-t-replace-the-wrong-part-of-the-cow-fork.patch
new file mode 100644 (file)
index 0000000..984df29
--- /dev/null
@@ -0,0 +1,387 @@
+From stable+bounces-287872-greg=kroah.com@vger.kernel.org Wed Jul 22 16:03:28 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 09:55:01 -0400
+Subject: xfs: don't replace the wrong part of the cow fork
+To: stable@vger.kernel.org
+Cc: "Darrick J. Wong" <djwong@kernel.org>, Christoph Hellwig <hch@lst.de>, Carlos Maiolino <cem@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722135501.1366960-2-sashal@kernel.org>
+
+From: "Darrick J. Wong" <djwong@kernel.org>
+
+[ Upstream commit a1caeeadbf57ff86dfc3454398c46de86056a74e ]
+
+LOLLM points out that xfs_iext_lookup_extent can return a @got where
+got->br_startoff < startoff.  In this case, xrep_cow_replace_range
+replaces the entire mapping instead of just the part that had been
+marked bad in the bitmap, but advances the bitmap cursor in
+xrep_cow_replace by the amount replaced.  As a result, we fail to
+replace the end of the bad range, and replace part of the good range.
+
+Fix this by rewriting the replace method to handle replacing the middle
+of a cow fork mapping.  This we do by returning both the current mapping
+as @got, and the subset of the mapping that we want to replace as @rep,
+using @rep to store the results of the new allocation, and comparing
+@rep to @got to figure out the exact transformations needed.
+
+Cc: stable@vger.kernel.org # v6.8
+Fixes: dbbdbd0086320a ("xfs: repair problems in CoW forks")
+Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
+Assisted-by: LOLLM # finding obvious bugs
+Reviewed-by: Christoph Hellwig <hch@lst.de>
+Signed-off-by: Carlos Maiolino <cem@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/xfs/scrub/cow_repair.c |  182 +++++++++++++++++++++++++++++-----------------
+ fs/xfs/scrub/trace.h      |   28 ++++---
+ 2 files changed, 135 insertions(+), 75 deletions(-)
+
+--- a/fs/xfs/scrub/cow_repair.c
++++ b/fs/xfs/scrub/cow_repair.c
+@@ -73,12 +73,6 @@ struct xrep_cow {
+       unsigned int            next_bno;
+ };
+-/* CoW staging extent. */
+-struct xrep_cow_extent {
+-      xfs_fsblock_t           fsbno;
+-      xfs_extlen_t            len;
+-};
+-
+ /*
+  * Mark the part of the file range that corresponds to the given physical
+  * space.  Caller must ensure that the physical range is within xc->irec.
+@@ -315,22 +309,21 @@ out_pag:
+ STATIC int
+ xrep_cow_alloc(
+       struct xfs_scrub        *sc,
+-      xfs_extlen_t            maxlen,
+-      struct xrep_cow_extent  *repl)
++      struct xfs_bmbt_irec    *del)
+ {
+       struct xfs_alloc_arg    args = {
+               .tp             = sc->tp,
+               .mp             = sc->mp,
+               .oinfo          = XFS_RMAP_OINFO_SKIP_UPDATE,
+               .minlen         = 1,
+-              .maxlen         = maxlen,
++              .maxlen         = del->br_blockcount,
+               .prod           = 1,
+               .resv           = XFS_AG_RESV_NONE,
+               .datatype       = XFS_ALLOC_USERDATA,
+       };
+       int                     error;
+-      error = xfs_trans_reserve_more(sc->tp, maxlen, 0);
++      error = xfs_trans_reserve_more(sc->tp, del->br_blockcount, 0);
+       if (error)
+               return error;
+@@ -343,8 +336,8 @@ xrep_cow_alloc(
+       xfs_refcount_alloc_cow_extent(sc->tp, args.fsbno, args.len);
+-      repl->fsbno = args.fsbno;
+-      repl->len = args.len;
++      del->br_startblock = args.fsbno;
++      del->br_blockcount = args.len;
+       return 0;
+ }
+@@ -358,19 +351,19 @@ static inline int
+ xrep_cow_find_mapping(
+       struct xrep_cow         *xc,
+       struct xfs_iext_cursor  *icur,
+-      xfs_fileoff_t           startoff,
+-      struct xfs_bmbt_irec    *got)
++      xfs_fileoff_t           badoff,
++      xfs_extlen_t            badlen,
++      struct xfs_bmbt_irec    *got,
++      struct xfs_bmbt_irec    *rep)
+ {
+       struct xfs_inode        *ip = xc->sc->ip;
+       struct xfs_ifork        *ifp = xfs_ifork_ptr(ip, XFS_COW_FORK);
+-      if (!xfs_iext_lookup_extent(ip, ifp, startoff, icur, got))
+-              goto bad;
+-
+-      if (got->br_startoff > startoff)
++      if (!xfs_iext_lookup_extent(ip, ifp, badoff, icur, got))
+               goto bad;
++      memcpy(rep, got, sizeof(*rep));
+-      if (got->br_blockcount == 0)
++      if (got->br_startoff > badoff)
+               goto bad;
+       if (isnullstartblock(got->br_startblock))
+@@ -379,6 +372,24 @@ xrep_cow_find_mapping(
+       if (xfs_bmap_is_written_extent(got))
+               goto bad;
++      if (got->br_startoff < badoff) {
++              const int64_t   delta = badoff - got->br_startoff;
++
++              rep->br_blockcount -= delta;
++              rep->br_startoff += delta;
++              rep->br_startblock += delta;
++      }
++
++      if (got->br_startoff + got->br_blockcount > badoff + badlen) {
++              const int64_t   delta = (got->br_startoff + got->br_blockcount) -
++                                      (badoff + badlen);
++
++              rep->br_blockcount -= delta;
++      }
++
++      if (got->br_blockcount == 0)
++              goto bad;
++
+       return 0;
+ bad:
+       ASSERT(0);
+@@ -389,46 +400,92 @@ bad:
+ #define REPLACE_RIGHT_SIDE    (1U << 1)
+ /*
+- * Given a CoW fork mapping @got and a replacement mapping @repl, remap the
+- * beginning of @got with the space described by @rep.
++ * Given a CoW fork mapping @got and a replacement mapping @rep, map the space
++ * described by @rep into the cow fork, pushing aside @got as necessary.  @icur
++ * must point to iext tree leaf containing @got.
+  */
+ static inline void
+ xrep_cow_replace_mapping(
+-      struct xfs_inode                *ip,
+-      struct xfs_iext_cursor          *icur,
+-      const struct xfs_bmbt_irec      *got,
+-      const struct xrep_cow_extent    *repl)
++      struct xfs_inode        *ip,
++      struct xfs_iext_cursor  *icur,
++      struct xfs_bmbt_irec    *got,
++      struct xfs_bmbt_irec    *rep)
+ {
+-      struct xfs_bmbt_irec            new = *got; /* struct copy */
++      struct xfs_ifork        *ifp = xfs_ifork_ptr(ip, XFS_COW_FORK);
++      xfs_fileoff_t           rep_endoff =
++                      rep->br_startoff + rep->br_blockcount;
++      xfs_fileoff_t           got_endoff =
++                      got->br_startoff + got->br_blockcount;
++      uint32_t                state = BMAP_COWFORK;
+-      ASSERT(repl->len > 0);
++      ASSERT(rep->br_blockcount > 0);
+       ASSERT(!isnullstartblock(got->br_startblock));
++      ASSERT(got->br_startoff <= rep->br_startoff);
++      ASSERT(got_endoff >= rep_endoff);
+-      trace_xrep_cow_replace_mapping(ip, got, repl->fsbno, repl->len);
++      trace_xrep_cow_replace_mapping(ip, got, rep);
+-      if (got->br_blockcount == repl->len) {
++      if (got->br_startoff == rep->br_startoff)
++              state |= BMAP_LEFT_FILLING;
++      if (got_endoff == rep_endoff)
++              state |= BMAP_RIGHT_FILLING;
++
++      switch (state & (BMAP_LEFT_FILLING | BMAP_RIGHT_FILLING)) {
++      case BMAP_LEFT_FILLING | BMAP_RIGHT_FILLING:
+               /*
+-               * The new extent is a complete replacement for the existing
+-               * extent.  Update the COW fork record.
++               * Replacement matches the whole mapping, update the record.
+                */
+-              new.br_startblock = repl->fsbno;
+-              xfs_iext_update_extent(ip, BMAP_COWFORK, icur, &new);
+-              return;
+-      }
++              xfs_iext_update_extent(ip, state, icur, rep);
++              break;
++      case BMAP_LEFT_FILLING:
++              /*
++               * Replace the first part of the mapping: Update the cursor
++               * position with the new mapping, then add a record with the
++               * tail of the old mapping.
++               */
++              got->br_startoff = rep_endoff;
++              got->br_blockcount -= rep->br_blockcount;
++              got->br_startblock += rep->br_blockcount;
++
++              xfs_iext_update_extent(ip, state, icur, rep);
++              xfs_iext_next(ifp, icur);
++              xfs_iext_insert(ip, icur, got, state);
++              break;
++      case BMAP_RIGHT_FILLING:
++              /*
++               * Replacing the last part of the mapping.  Shorten the current
++               * mapping then add a record with the new mapping.
++               */
++              got->br_blockcount -= rep->br_blockcount;
+-      /*
+-       * The new extent can replace the beginning of the COW fork record.
+-       * Move the left side of @got upwards, then insert the new record.
+-       */
+-      new.br_startoff += repl->len;
+-      new.br_startblock += repl->len;
+-      new.br_blockcount -= repl->len;
+-      xfs_iext_update_extent(ip, BMAP_COWFORK, icur, &new);
+-
+-      new.br_startoff = got->br_startoff;
+-      new.br_startblock = repl->fsbno;
+-      new.br_blockcount = repl->len;
+-      xfs_iext_insert(ip, icur, &new, BMAP_COWFORK);
++              xfs_iext_update_extent(ip, state, icur, got);
++              xfs_iext_next(ifp, icur);
++              xfs_iext_insert(ip, icur, rep, state);
++              break;
++      case 0:
++              /*
++               * Replacing the middle of the extent.  Shorten the current
++               * mapping, add a new record with the new mapping, and add a
++               * second new record with the tail of the old mapping.
++               */
++              got->br_blockcount = rep->br_startoff - got->br_startoff;
++
++              struct xfs_bmbt_irec    new = {
++                      .br_startoff    = rep_endoff,
++                      .br_blockcount  = got_endoff - rep_endoff,
++                      .br_state       = got->br_state,
++                      .br_startblock  = got->br_startblock +
++                                              rep->br_blockcount +
++                                              got->br_blockcount,
++              };
++
++              xfs_iext_update_extent(ip, state, icur, got);
++              xfs_iext_next(ifp, icur);
++              xfs_iext_insert(ip, icur, rep, state);
++              xfs_iext_next(ifp, icur);
++              xfs_iext_insert(ip, icur, &new, state);
++              break;
++      }
+ }
+ /*
+@@ -442,30 +499,27 @@ xrep_cow_replace_range(
+       xfs_extlen_t            *blockcount)
+ {
+       struct xfs_iext_cursor  icur;
+-      struct xrep_cow_extent  repl;
+-      struct xfs_bmbt_irec    got;
++      struct xfs_bmbt_irec    got, rep;
+       struct xfs_scrub        *sc = xc->sc;
+-      xfs_fileoff_t           nextoff;
+-      xfs_extlen_t            alloc_len;
++      xfs_fsblock_t           old_fsbno;
+       int                     error;
+       /*
+-       * Put the existing CoW fork mapping in @got.  If @got ends before
+-       * @rep, truncate @rep so we only replace one extent mapping at a time.
++       * Put the existing CoW fork mapping in @got, and put in @rep the
++       * contents of @got trimmed to @startoff/@blockcount.  We only want
++       * to replace the bad region, and only one mapping at a time.
+        */
+-      error = xrep_cow_find_mapping(xc, &icur, startoff, &got);
++      error = xrep_cow_find_mapping(xc, &icur, startoff, *blockcount, &got,
++                      &rep);
+       if (error)
+               return error;
+-      nextoff = min(startoff + *blockcount,
+-                    got.br_startoff + got.br_blockcount);
++      old_fsbno = rep.br_startblock;
+       /*
+        * Allocate a replacement extent.  If we don't fill all the blocks,
+        * shorten the quantity that will be deleted in this step.
+        */
+-      alloc_len = min_t(xfs_fileoff_t, XFS_MAX_BMBT_EXTLEN,
+-                        nextoff - startoff);
+-      error = xrep_cow_alloc(sc, alloc_len, &repl);
++      error = xrep_cow_alloc(sc, &rep);
+       if (error)
+               return error;
+@@ -473,7 +527,7 @@ xrep_cow_replace_range(
+        * Replace the old mapping with the new one, and commit the metadata
+        * changes made so far.
+        */
+-      xrep_cow_replace_mapping(sc->ip, &icur, &got, &repl);
++      xrep_cow_replace_mapping(sc->ip, &icur, &got, &rep);
+       xfs_inode_set_cowblocks_tag(sc->ip);
+       error = xfs_defer_finish(&sc->tp);
+@@ -481,12 +535,12 @@ xrep_cow_replace_range(
+               return error;
+       /* Note the old CoW staging extents; we'll reap them all later. */
+-      error = xfsb_bitmap_set(&xc->old_cowfork_fsblocks, got.br_startblock,
+-                      repl.len);
++      error = xfsb_bitmap_set(&xc->old_cowfork_fsblocks, old_fsbno,
++                      rep.br_blockcount);
+       if (error)
+               return error;
+-      *blockcount = repl.len;
++      *blockcount = rep.br_blockcount;
+       return 0;
+ }
+--- a/fs/xfs/scrub/trace.h
++++ b/fs/xfs/scrub/trace.h
+@@ -2553,9 +2553,9 @@ TRACE_EVENT(xrep_cow_mark_file_range,
+ );
+ TRACE_EVENT(xrep_cow_replace_mapping,
+-      TP_PROTO(struct xfs_inode *ip, const struct xfs_bmbt_irec *irec,
+-               xfs_fsblock_t new_startblock, xfs_extlen_t new_blockcount),
+-      TP_ARGS(ip, irec, new_startblock, new_blockcount),
++      TP_PROTO(struct xfs_inode *ip, const struct xfs_bmbt_irec *got,
++               const struct xfs_bmbt_irec *rep),
++      TP_ARGS(ip, got, rep),
+       TP_STRUCT__entry(
+               __field(dev_t, dev)
+               __field(xfs_ino_t, ino)
+@@ -2563,28 +2563,34 @@ TRACE_EVENT(xrep_cow_replace_mapping,
+               __field(xfs_fileoff_t, startoff)
+               __field(xfs_filblks_t, blockcount)
+               __field(xfs_exntst_t, state)
++              __field(xfs_fileoff_t, new_startoff)
+               __field(xfs_fsblock_t, new_startblock)
+               __field(xfs_extlen_t, new_blockcount)
++              __field(xfs_exntst_t, new_state)
+       ),
+       TP_fast_assign(
+               __entry->dev = ip->i_mount->m_super->s_dev;
+               __entry->ino = ip->i_ino;
+-              __entry->startoff = irec->br_startoff;
+-              __entry->startblock = irec->br_startblock;
+-              __entry->blockcount = irec->br_blockcount;
+-              __entry->state = irec->br_state;
+-              __entry->new_startblock = new_startblock;
+-              __entry->new_blockcount = new_blockcount;
++              __entry->startoff = got->br_startoff;
++              __entry->startblock = got->br_startblock;
++              __entry->blockcount = got->br_blockcount;
++              __entry->state = got->br_state;
++              __entry->new_startoff = rep->br_startoff;
++              __entry->new_startblock = rep->br_startblock;
++              __entry->new_blockcount = rep->br_blockcount;
++              __entry->new_state = rep->br_state;
+       ),
+-      TP_printk("dev %d:%d ino 0x%llx startoff 0x%llx startblock 0x%llx fsbcount 0x%llx state 0x%x new_startblock 0x%llx new_fsbcount 0x%x",
++      TP_printk("dev %d:%d ino 0x%llx startoff 0x%llx startblock 0x%llx fsbcount 0x%llx state 0x%x new_startoff 0x%llx new_startblock 0x%llx new_fsbcount 0x%x new_state 0x%x",
+                 MAJOR(__entry->dev), MINOR(__entry->dev),
+                 __entry->ino,
+                 __entry->startoff,
+                 __entry->startblock,
+                 __entry->blockcount,
+                 __entry->state,
++                __entry->new_startoff,
+                 __entry->new_startblock,
+-                __entry->new_blockcount)
++                __entry->new_blockcount,
++                __entry->new_state)
+ );
+ TRACE_EVENT(xrep_cow_free_staging,
diff --git a/queue-6.12/xfs-factor-out-xfs_attr3_leaf_init.patch b/queue-6.12/xfs-factor-out-xfs_attr3_leaf_init.patch
new file mode 100644 (file)
index 0000000..5f31fcc
--- /dev/null
@@ -0,0 +1,69 @@
+From stable+bounces-287870-greg=kroah.com@vger.kernel.org Wed Jul 22 16:18:10 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 09:55:00 -0400
+Subject: xfs: factor out xfs_attr3_leaf_init
+To: stable@vger.kernel.org
+Cc: Long Li <leo.lilong@huawei.com>, "Darrick J. Wong" <djwong@kernel.org>, Carlos Maiolino <cem@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722135501.1366960-1-sashal@kernel.org>
+
+From: Long Li <leo.lilong@huawei.com>
+
+[ Upstream commit e65bb55d7f8c2041c8fdb73cd29b0b4cad4ed847 ]
+
+Factor out wrapper xfs_attr3_leaf_init function, which exported for
+external use.
+
+Reviewed-by: Darrick J. Wong <djwong@kernel.org>
+Signed-off-by: Long Li <leo.lilong@huawei.com>
+Signed-off-by: Carlos Maiolino <cem@kernel.org>
+Stable-dep-of: a1caeeadbf57 ("xfs: don't replace the wrong part of the cow fork")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/xfs/libxfs/xfs_attr_leaf.c |   22 ++++++++++++++++++++++
+ fs/xfs/libxfs/xfs_attr_leaf.h |    3 +++
+ 2 files changed, 25 insertions(+)
+
+--- a/fs/xfs/libxfs/xfs_attr_leaf.c
++++ b/fs/xfs/libxfs/xfs_attr_leaf.c
+@@ -1330,6 +1330,28 @@ xfs_attr3_leaf_create(
+ }
+ /*
++ * Reinitialize an existing attr fork block as an empty leaf, and attach
++ * the buffer to tp.
++ */
++int
++xfs_attr3_leaf_init(
++      struct xfs_trans        *tp,
++      struct xfs_inode        *dp,
++      xfs_dablk_t             blkno)
++{
++      struct xfs_buf          *bp = NULL;
++      struct xfs_da_args      args = {
++              .trans          = tp,
++              .dp             = dp,
++              .owner          = dp->i_ino,
++              .geo            = dp->i_mount->m_attr_geo,
++      };
++
++      ASSERT(tp != NULL);
++
++      return xfs_attr3_leaf_create(&args, blkno, &bp);
++}
++/*
+  * Split the leaf node, rebalance, then add the new entry.
+  *
+  * Returns 0 if the entry was added, 1 if a further split is needed or a
+--- a/fs/xfs/libxfs/xfs_attr_leaf.h
++++ b/fs/xfs/libxfs/xfs_attr_leaf.h
+@@ -86,6 +86,9 @@ int  xfs_attr3_leaf_list_int(struct xfs_b
+ /*
+  * Routines used for shrinking the Btree.
+  */
++
++int   xfs_attr3_leaf_init(struct xfs_trans *tp, struct xfs_inode *dp,
++                              xfs_dablk_t blkno);
+ int   xfs_attr3_leaf_toosmall(struct xfs_da_state *state, int *retval);
+ void  xfs_attr3_leaf_unbalance(struct xfs_da_state *state,
+                                      struct xfs_da_state_blk *drop_blk,